Skip to content

fix: engine correctness (seeded randomness, delay stranding, gate weights)#35

Merged
zntznt merged 1 commit into
mainfrom
fix/engine-correctness
Jul 3, 2026
Merged

fix: engine correctness (seeded randomness, delay stranding, gate weights)#35
zntznt merged 1 commit into
mainfrom
fix/engine-correctness

Conversation

@zntznt

@zntznt zntznt commented Jul 3, 2026

Copy link
Copy Markdown
Owner

Batch 1 of the codebase review fixes: simulation-engine correctness. Every fix ships with a regression test that was verified to FAIL against the pre-fix engine (via stash), so each test genuinely pins its bug. Suite grows 185 to 194, green with and without mathjs installed (both evaluator paths).

  • Seeded formula randomness (the big one): random(), randomInt(), pickRandom() in formulas now draw from the engine's seeded SimRandom stream instead of math.js's Math.random-backed RNG; the legacy new Function path gets the same helpers plus a Math stand-in whose random() is seeded. Injected per evaluation, never baked into the shared compile cache, so Monte Carlo trials stay isolated. "Same seed, same results" now actually holds when formulas roll dice
  • Delay stranding: reset() rebuilds a delay node's in-flight queue from its color map (one batch per color, releasing after the node's delay), mirroring the queue rebuild, so diagrams saved mid-run (or delays given a starting amount) release their resources instead of holding them forever
  • Zero-weight gate outputs no longer receive remainder units; the round-robin covers weighted outputs only, keeping the all-zero even-split fallback (also fixes delay-release routing)
  • Fractional capacity can no longer overfill by the remainder unit (_fairAllocate floors on entry)
  • rollDice caps the dice count at 10000 (matching the poisson guard), so 999999999d6 can't freeze the tab
  • History decimation filters by step phase instead of array index, so the timeline no longer skips samples right after each stride doubling
  • Checkpoints capture the RNG position: SimRandom exposes getState/setState (identical algorithm, state lifted out of the closure) and capture/restoreState include it, so restoring a seeded checkpoint replays the exact same draws; old snapshots without the field degrade gracefully

Deliberately not addressed here: the mid-tick order-dependence of activator conditions (conditions read live values while pulls/gates/converters mutate mid-fire). That is a simulation-semantics change that deserves its own discussion.

- Seeded reproducibility: formula randomness (random/randomInt/pickRandom
  in math.js, plus Math.random in the legacy Function fallback) now draws
  from SimRandom via the evaluation scope, so seeded runs with random in
  any formula are bit-for-bit reproducible. Injected per evaluation, never
  baked into compiled code, since _mathCompileCache is shared across
  Monte Carlo trials.
- Delay save/load: reset() rebuilds a delay's _queue from its colorMap
  (one batch per color, releasing after the node's delay), mirroring the
  QUEUE _fifo rebuild, so in-flight resources saved mid-run are released
  instead of stranding in the node forever.
- Zero-weight gate outputs: _proportionalShares hands the integer
  remainder round-robin over positive-weight outputs only (all-zero
  even-split fallback kept), fixing deterministic gates and delay
  releases leaking units down weight-0 connections.
- Fractional capacity: _fairAllocate floors `available` so a capacity
  like 5.5 can no longer overfill a pool to 6.
- rollDice: dice count capped at 10000 (matching sampleDist's poisson
  guard) so 999999999d6 can't freeze the tick.
- History decimation: filter retained snapshots by step phase instead of
  array index, keeping recording aligned with the doubled stride (no
  skipped samples right after each doubling).
- Checkpoints: SimRandom exposes getState()/setState() and captureState/
  restoreState carry the mulberry32 position, so restoring a seeded run
  replays the exact same stochastic draws.

Adds 9 regression tests (194 total); each fails against the pre-fix
engine and passes now, with and without mathjs installed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@zntznt
zntznt merged commit f829328 into main Jul 3, 2026
1 check passed
@zntznt
zntznt deleted the fix/engine-correctness branch July 16, 2026 21:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant