Commit graph

4 commits

Author SHA1 Message Date
alex
989c692c53 rng: model chance(), the only entry point that can cost zero words
Both early-outs return without touching the generator: p <= 0 false, p >= 1
true, otherwise exactly one word compared with a strict <. The zero-cost cases
decide stream alignment wherever a caller's probability climbs -- the spy
counter-mission adds 0.2f per failed turn and stops drawing entirely from the
fifth. A model without the early-outs drifts one word from there on, for ever.

NaN takes neither early-out in the original, so it draws and returns false;
reproduced rather than smoothed over.
2026-09-08 22:32:14 -04:00
alex
dc43f93910 mars::rng: the seven draw entry points, with their word costs
A per-turn RNG budget is only as good as the entry-point table, and ours had
three of the seven. Adds the two that are modellable and documents the rest.

  float_range(lo, hi)     exactly one word.  Narrows TWICE -- the scaled product
                          is stored to a 4-byte float before lo is added, and the
                          sum is stored again.  Evaluating in double and narrowing
                          once disagrees on a measurable fraction of words, and the
                          test asserts the two models are distinguishable so the
                          shortcut cannot creep back.
  int_range_bell(lo, hi)  AT LEAST TWO words.  Triangular, not uniform: the span is
                          split into h/2 and h - h/2 (truncating toward zero) and
                          each half drawn inclusively, first half first.  The bounds
                          reach the draw as unsigned, so an inverted range yields a
                          huge first bound rather than an empty one; reproduced, not
                          corrected.

Documented but deliberately not modelled: a truncated-normal integer range built
on rejection sampling around a Box-Muller pair.  It costs TWO WORDS PER ATTEMPT
and the attempt count is unbounded, and predicting its stream position needs log,
sqrt and cos to agree bit for bit with the original CRT.  Nothing in the strategic
turn reaches it.  It is recorded so a ledger that meets it does not score its two
words as one draw.

Also recorded in docs/mars-rng.md, because each is a way a word budget goes wrong:

  * two calling conventions for one generator -- three entry points take the state
    block (the object plus four bytes) and four take the object itself, and one
    caller uses both within forty bytes of itself;
  * two different divisors in the same image, 1/(2^32 - 1) for the unit draw and
    2^-32 (with a +0.5 offset on the word) for the normal path;
  * the unit draw is inlined at twenty-eight sites across eleven functions, so any
    budget assembled by counting calls is a LOWER BOUND.  Exactly one of those
    eleven is reachable from the strategic turn driver.

Host ctest 36/36 and tools/clean_room_check.sh run as separate commands, both
clean.  No src/shim change, so no cross-build is implicated.
2026-09-08 09:59:05 -04:00
alex
dd97137fb0 b3: research hook + RNG-snapshot compare design; fix draw mapping to y/(2^32-1), NextInt inclusive, float32 odds, spend floor, decay constants 2026-09-07 23:14:08 -04:00
alex
b594a0a90d mars/stream + mars/rng: Streamable reader/writer/shapes, MT19937; 3 real saves exact + byte-identical round-trip 2026-09-07 17:46:06 -04:00