[M1-DET-01] Deterministic mode + sim math rules (G-R8 trait, substreams, sim-source scan) - #34
Merged
Merged
Conversation
…ms, sim-source scan)
Deterministic mode + sim math rules (FR-1.4, S-7, PRD 10.3; ARCH-010;
M1-DET-01 scope, nothing else):
- determinism.h (new public header, src/laige-sim/include/laige/sim/):
SimMathBackend (FixedPoint16_16 default / FloatPinned32),
DeterminismConfig {enabled, math}, the G-R8 trait
detail::IsDeterminismSafe<T> (integers, enums, fpx16_16, float as the
fp32_pinned Scalar, the four SimMath<B>::Vec2/Vec3; double never safe),
detail::areDeterminismSafeMembers, and LAIGE_DETERMINISM_SAFE(Type,
Members...) with a static_assert at the mark site. Enforced by a
third static_assert in World::registerSystem folding
detail::IoComponentSafety over the declared I/O (actionable message).
- PRNG substreams: World::Options gains seed/deterministic;
registerSystem derives Prng::deriveSubstream(seed, systemId) per
system (id 0 = master, never assigned) into the SystemRecord;
runSystems hands the stream to SystemContext.rng (Prng*, NSDMI) and
advances it in place - the stream state IS the replay state.
SystemContext stays single-arg-initializable.
- EngineConfig appends seed (u64, kDefaultSimulationSeed = 0) +
DeterminismConfig (existing 3-member aggregate inits keep
compiling). parseEngineConfig gains seed (0..2^53 - the ADR 0003
exact-double bound; 2^53+1 is indistinguishable from 2^53 and
accepted; 2^53+2 is the smallest rejectable) + the determinism
object (rejections config/seed_invalid, config/determinism_invalid,
config/determinism_enabled_invalid, config/determinism_math_invalid;
unknown nested key -> config/unknown_key warn, ignored; first
failure wins). Engine::create registers the backend-matching
built-in FIRST and builds the matching PresentationSnapshot
(type-erased detail::PresentationHandle - zero added allocations;
headless setup stays exactly 3). engine/run_started gains
seed/determinism/math fields (the laige-run CLI summary line is
unchanged; --replay remains the M1-DET-02 stub).
- tools/laige-determinism-lint (new; Python 3 stdlib): the sim-source
scan over src/laige-sim/** - D1a float/double type tokens, D1b float
literals, D1c double literals, D2 unordered_* containers, D3
malformed exception markers. A char scanner strips //, /* */ comments,
string/char literals, and raw strings before matching (word-bounded,
case-sensitive). Same-line '// LAIGE-DETERM-EXCEPTION: G-R8
<reason>' markers are the documented false-positive policy; every
suppressed line is counted and printed (EXC-006). Exit 0/1/2.
- CI: new determinism-lint job in BOTH ci-pull.yml and ci.yml
(ubuntu-24.04, python3 tools/laige-determinism-lint).
- Tests: tests/laige-sim/determinism_tests.cpp (suites DeterminismMode/
DeterminismEngine/DeterminismConfigParse; ctest -R determinism_mode) -
a trivial moving-entity sim produces bit-identical FNV-1a per-tick
state hashes over 256 ticks in two consecutive runs (same build,
same seed); a different seed diverges; substreams match
Prng::deriveSubstream exactly and are independent; deterministic ==
false -> ctx.rng == nullptr; backend selection both ways; the full
config-key table. tests/laige-sim/compile_fail/ (4 fixtures +
expect-compile-result.cmake.in; ctest -R trait_compile): the positive
fixture compiles; the three negatives (a double member, an unmarked
user struct, a double in the mark's member list) each fail to
compile with the G-R8 message (exit-code + stderr-fragment
assertions). tests/tools gains the determinism-lint-* fixture tests
(clean tree with one marked exception -> 0; one violation per rule
-> 1; real tree -> 0).
- Docs (same change, DOC-006/007): NEW docs/concepts/determinism.md
(the ARCH-010 scope statement) + NEW docs/api/determinism.md;
updates to docs/api/{engine,entity,system_registry,sim_math,
prng}.md, docs/testing.md, docs/getting-started/building.md,
docs/concepts/README.md, docs/README.md, src/laige-sim/README.md,
tools/README.md. laige-api.json regenerated (573 -> 588 symbols).
- Roadmap: M1-DET-01 checkbox, progress board (14/25, 34/193),
change-log line.
Verified: ctest 55/55 on build (Debug GCC 16.2.1), 55/55 on
build-asan (ASan+UBSan leak-free), 55/55 on build-tsan
(halt_on_error=1); ctest -R determinism_mode 1/1 (14 cases);
ctest -R trait_compile 4/4; ctest -R determinism-lint 3/3;
python3 tools/laige-include-lint OK; python3
tools/laige-determinism-lint OK (17 files, 0 violations, 15 marked
exceptions); laige-api-scanner --check OK (588 symbols); CI YAML valid
(determinism-lint job present in both workflows). Zero new warnings
under NFR-8.10.
Deviation (surfaced, not silent): declared dependency M1-CFG-01 has
NOT landed - the seed/determinism keys sit on the PROVISIONAL
parseEngineConfig surface (documented as provisional in engine.md, the
header preamble, and the change-log line); M1-CFG-01 owns the final
versioned schema.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Deterministic mode + sim math rules (FR-1.4, S-7, PRD §10.3; ARCH-010; M1-DET-01 scope, nothing else).
What this lands
src/laige-sim/include/laige/sim/determinism.h:SimMathBackend(FixedPoint16_16default /FloatPinned32),DeterminismConfig,detail::IsDeterminismSafe<T>(integers, enums,fpx16_16,floatas the fp32_pinned Scalar, the fourSimMath<B>::Vec2/Vec3;doublenever safe), andLAIGE_DETERMINISM_SAFE(Type, Members...)with astatic_assertat the mark site. Enforced by a thirdstatic_assertinWorld::registerSystemover the declared I/O, with an actionable message.World::Options.seed/deterministic;registerSystemderivesPrng::deriveSubstream(seed, systemId)per system (id 0 = master, never assigned);runSystemshands the stream toSystemContext.rng(advanced in place — the stream state IS the replay state).EngineConfigappendsseed(u64) +DeterminismConfig(existing 3-member aggregate inits keep compiling);parseEngineConfiggainsseed(0..2^53 exact-double bound; 2^53+2 is the smallest rejectable) +determinism(4 new rejection events, unknown nested key → warn + ignore, first failure wins);Engine::createregisters the backend-matching built-in first and builds the matchingPresentationSnapshot(zero added allocations — headless setup stays exactly 3);run_startedgainsseed/determinism/math(thelaige-runCLI summary line is unchanged;--replayremains the M1-DET-02 stub).tools/laige-determinism-lint(Python 3 stdlib): D1a/D1b/D1c rawfloat/double, D2unordered_*, D3 malformed markers, oversrc/laige-sim/**with comments/literals/raw strings stripped; same-line// LAIGE-DETERM-EXCEPTION: G-R8 <reason>markers are the documented false-positive policy, every suppressed line counted + printed (EXC-006). Newdeterminism-lintCI job in both workflows + 3 CTest fixture tests intests/tools.tests/laige-sim/determinism_tests.cpp(14 cases;ctest -R determinism_mode): a trivial moving-entity sim produces bit-identical FNV-1a per-tick state hashes over 256 ticks in two consecutive runs (same build, same seed); different seed diverges; substreams matchPrng::deriveSubstreamexactly and are independent;deterministic == false→ctx.rng == nullptr; backend selection both ways; the full config-key table.tests/laige-sim/compile_fail/(4 fixtures;ctest -R trait_compile): 1 positive + 3 negative, each asserting exit code and the G-R8 stderr fragment.docs/concepts/determinism.md(the ARCH-010 scope statement) +docs/api/determinism.md; updates toengine.md,entity.md,system_registry.md,sim_math.md,prng.md,testing.md,building.md, module READMEs, index docs.laige-api.jsonregenerated (573 → 588 symbols). Roadmap checkbox + change-log line + progress board.Verify (all green)
ctest --test-dir build(Debug GCC 16.2.1)ctest --test-dir build-asan(ASan+UBSan)ctest --test-dir build-tsan(halt_on_error=1)ctest -R determinism_modectest -R trait_compilectest -R determinism-lintpython3 tools/laige-include-lintpython3 tools/laige-determinism-lint./build/bin/laige-api-scanner --root . --check laige-api.jsonZero new warnings under NFR-8.10. CI YAML validated (job present in both workflows).
Notes / limitations
seed/determinismkeys sit on the provisionalparseEngineConfigsurface (documented in engine.md, the header preamble, and the change-log line); M1-CFG-01 owns the final versioned schema.engine.cpp(ADR 0003 JSON number policy),determinism.h(the trait's ownfloatregistration),entity.h/system.h/system_timing.cpp(wall-clock diagnostics, ARCH-009),presentation.h(wall-clock alpha).