A mechanic package depends on ludic.base and nothing else: ports (records of function values for now) for questions, queues for facts, verbs for changes, phases for order and its own versioned save section. The runner inits, resets, saves and loads systems in the order added and ticks them phase by phase; a missing save section is a reset. Tests for each piece and a worked route between two toy mechanics live under tests/. The old ludic.core (engine ECS components) is used by examples/library and stays. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
44 lines
1.2 KiB
Text
44 lines
1.2 KiB
Text
# ludic.base/rng.ludic - a stream of dice a mechanic is handed, so it never draws from the global
|
|
# generator and moving it cannot shift anyone else's rolls. xorshift32; the same seed, the same
|
|
# rolls, on every machine.
|
|
export property Rng {
|
|
s: int = 0x2545F491
|
|
}
|
|
|
|
export function rng_new(seed: int) -> Rng {
|
|
let r = new Rng
|
|
rng_seed(r, seed)
|
|
return r
|
|
}
|
|
|
|
export function rng_seed(r: Rng, seed: int) -> void {
|
|
var x = seed * 0x2C1B3C6D + 0x297A2D39
|
|
if x == 0 { x = 0x6B43A9B5 }
|
|
r.s = x
|
|
}
|
|
|
|
# a whole number in [0, 2^31); the right shift is masked because Ludic's is arithmetic
|
|
export function rng_next(r: Rng) -> int {
|
|
var x = r.s
|
|
x = x ^ (x << 13)
|
|
x = x ^ ((x >> 17) & 0x7FFF)
|
|
x = x ^ (x << 5)
|
|
r.s = x
|
|
return x & 0x7FFFFFFF
|
|
}
|
|
|
|
# [0, 1), in steps of 1/65536 so it is exact in any float
|
|
export function rng_float(r: Rng) -> float {
|
|
return float(rng_next(r) & 0xFFFF) / 65536.0
|
|
}
|
|
|
|
# a whole number in [lo, hi]. Not rng_range: that name is the global generator's (Random.range)
|
|
export function rng_between(r: Rng, lo: int, hi: int) -> int {
|
|
if hi <= lo { return lo }
|
|
return lo + rng_next(r) % (hi - lo + 1)
|
|
}
|
|
|
|
# a float in [lo, hi)
|
|
export function rng_span(r: Rng, lo: float, hi: float) -> float {
|
|
return lo + (hi - lo) * rng_float(r)
|
|
}
|