///|
/// The page's door into the library. It holds the bytes the page brought and
/// hands them to `Replay`; every index the page shows is drawn by
/// `@moonseed.below` — or, for the comparison bars, by the raw modulo the page
/// asked for — over that same replay.
///
/// The platform source is not used here on purpose: a wasm-gc host has no
/// `random_get`, so `@system.source` fails closed rather than invent bytes.
/// Gathering bytes is the page's business; turning them into unbiased indices
/// is this library's.
let pool : FixedArray[Byte] = FixedArray::make(16384, b'\x00')
///|
let filled : Array[Int32] = [0]
///|
/// The tile's own replay, so its strokes advance one stream while each plot
/// starts from the front.
let live : Array[@moonseed.Replay] = []
///|
/// Forget every byte pushed so far.
pub fn reset() -> Unit {
filled[0] = 0
live.clear()
}
///|
/// Add one byte. The page pushes a few thousand of these at load.
pub fn push(b : Int32) -> Unit {
if filled[0] < pool.length() {
pool[filled[0]] = b.to_byte()
filled[0] = filled[0] + 1
}
}
///|
/// A fresh replay over the bytes pushed so far.
fn replay() -> @moonseed.Replay {
@moonseed.Replay::new(Bytes::from_array(pool[:filled[0]]))
}
///|
/// How many bytes the page has given.
pub fn taken() -> Int32 {
filled[0]
}
///|
/// Restart the tile's stream at the front of the pool.
pub fn open() -> Int32 {
live.clear()
live.push(replay())
filled[0]
}
///|
/// The next byte, as a number in [0, 1), drawn through `below(256)` so the page
/// never reasons about a mask itself. Past the end of the pool it reports the
/// middle value rather than pretending.
pub fn unit() -> Double {
if live.length() == 0 {
return 0.5
}
let v = @moonseed.below(b"\x01\x00", live[0]) catch { _ => return 0.5 }
(v[0].to_int() * 256 + v[1].to_int()).to_double() / 256.0
}
///|
/// How many of `trials` draws of `below(upper)` land in bucket `bin` of `bins`
/// equal buckets over [0, `upper`). Every bucket counts the same run: the
/// replay starts at the front each time, so the bars partition one sample.
pub fn below_hits(
upper : Int32,
trials : Int32,
bin : Int32,
bins : Int32,
) -> Int32 {
if upper <= 0 || bins <= 0 || trials <= 0 || filled[0] == 0 {
return 0
}
let width = if upper % bins == 0 { upper / bins } else { upper / bins + 1 }
let bound = bound_bytes(upper)
let src = replay()
let mut hits = 0
for _i = 0; _i < trials; _i = _i + 1 {
let v = bound_value(@moonseed.below(bound[:], src)) catch { _ => break }
if v / width == bin {
hits = hits + 1
}
}
hits
}
///|
/// The same count, but by reducing one raw byte modulo `upper`: the biased draw
/// `below` exists to avoid. Same replay, same number of draws.
pub fn mod_hits(
upper : Int32,
trials : Int32,
bin : Int32,
bins : Int32,
) -> Int32 {
if upper <= 0 || bins <= 0 || trials <= 0 || filled[0] == 0 {
return 0
}
let width = if upper % bins == 0 { upper / bins } else { upper / bins + 1 }
let src = replay()
let one = FixedArray::make(1, b'\x00')
let mut hits = 0
for _i = 0; _i < trials; _i = _i + 1 {
let v = try {
src.fill(one)
one[0].to_int() % upper
} catch {
_ => break
}
if v / width == bin {
hits = hits + 1
}
}
hits
}
///|
/// `n` as a big-endian bound, four bytes wide: what `below` reads.
fn bound_bytes(n : Int32) -> Bytes {
let out = FixedArray::make(4, b'\x00')
out[0] = ((n >> 24) & 0xFF).to_byte()
out[1] = ((n >> 16) & 0xFF).to_byte()
out[2] = ((n >> 8) & 0xFF).to_byte()
out[3] = (n & 0xFF).to_byte()
Bytes::from_array(out[:])
}
///|
/// The same number read back out of four big-endian bytes.
fn bound_value(b : Bytes) -> Int32 {
let mut v = 0
for i in 0..