Repository navigation
[M1-DET-05] Replay diff: componentStateHash, World::stateDiff, diffReplays, laige-replay --diff - #44
Merged
Merged
Conversation
…plays, laige-replay --diff FR-11.3 (diff two replays by frame/state). Two new public surfaces: - World::componentStateHash(tick): the state hash's canonical steps 1-4 (tick, live handles, archetype assignment, component bytes) with the PRNG substream state EXCLUDED — the diff's tick-alignment key (a different-seed replay diverges in substream state from tick 0 while its components may match for hundreds of ticks). - World::stateDiff(other, maxItems, fn): the bounded per-(entity, component) comparison in canonical order (slots ascending; presence item before component items; components ascending in the union of the two worlds' sets); exact total count, callback for the first maxItems; non-owning byte views into the worlds' columns. - diffReplays(logA, logB, worldA, worldB, configA, configB, maxEntries): identity check per log (both reported), determinism check, lock-step beginFrame/runSystems walk aligned on the component-state hash, honest fullStateDivergent flag from the full state hash, bounded state diff at the first divergent tick, length divergence, maxEntries 0/count-only, >64 clamped. CLI: laige-replay --diff <logA> <logB> --config <cfg> [--entries N]: stable structured stdout report (replay_diff result=..., frames, first_divergent_tick, full_state_divergent, differing_items, reported_items, per-item slot/component/presence/hex lines), human summary + diagnostics on stderr, exit 0 identical / 1 divergent or length / 2 usage-identity-IO. Tests: ctest -R replay_diff (StateDiff.* + ReplayDiff.* suites — the bounded diff contract, the tick-37 integration scenario: two different-seed replays first diverging at tick 37 report tick 37 and the diverged component, the identity/determinism rejections, the draw-path KAT vs an independent Prng) + four laige-replay CLI entries (replay_diff_identical/length/identity/usage; template gains SETUP2). Docs: replay.md (diff section, report grammar, testing), entity.md (componentStateHash/stateDiff section, roadmap). laige-api.json regenerated.
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.
Summary
M1-DET-05 (roadmap/M1-heartbeat.md):
laige-replay --diff <logA> <logB>— replay both logs, align by tick, report the first divergent tick + the diffed state components (bounded report, not a full dump). FR-11.3; later consumed by the editor replay viewer (M5-ED-15).New public surfaces
World::componentStateHash(tick)(src/laige-sim/include/laige/sim/entity.h,src/laige-sim/state_hash.cpp) — the state hash's canonical steps 1-4 (tick, live handles, archetype assignment, component bytes) with the PRNG substream state EXCLUDED. It is the diff's tick-alignment key: a different-seed replay diverges in substream state from tick 0 while its components may match for hundreds of ticks, so aligning on the fullstateHashwould always report tick 0 and hide the first real component divergence.stateHashnow shares steps 1-4 with this via one extracted helper (single canonical implementation).World::stateDiff(other, maxItems, fn)(entity.h) — bounded per-(entity, component) comparison in canonical order (slots ascending; entity-presence item before component items; components ascending in the union of the two worlds' sets). Returns the exact total difference count; the callback runs for the firstmaxItemsitems. ItembytesA/bytesBare non-owning views into the worlds' component columns (PERF-005; valid until the next mutation of the involved entities).laige::diffReplays(logA, logB, worldA, worldB, configA, configB, maxEntries = 16)(src/laige-sim/replay_diff.{h,cpp}) — identity check per log (both checked, both reported;replay/diff_identity_mismatchwarn), determinism check (replay/diff_determinism_disabledwarn), lock-stepbeginFrame()/runSystems()walk aligned on the component-state hash, honestfullStateDivergentflag from the fullstateHash(never silent, CORE-008), bounded state diff at the first divergence, length divergence (firstDivergentTick = alignedTicks + 1).maxEntries: 0 = count only, >64 clamped (kMaxReplayDiffEntries); default 16 (kReplayDiffDefaultEntries).CLI (
tools/replay/laige-replay.cpp)laige-replay --diff <logA> <logB> --config <cfg> [--entries N]— per-log config derived from the base config with that log's header seed. Stdout is the stable structured report only (machine-greppable; the editor viewer's source):Stderr: human summary, the identity-mismatch field report, the PRNG note when
full_state_divergent, usage errors. Exit codes: 0 identical; 1 divergent or length-divergent; 2 usage/config/log/identity/engine error.--diffis mutually exclusive with--log/--expect;--entriestakes an integer in 0..64.Tests
ctest -R replay_diff— newStateDiff.*+ReplayDiff.*suites intests/laige-sim/replay_diff_tests.cpp(13 tests): identical worlds report nothing; component-byte differences with canonical slot/component order; entity- and component-presence items; bounded report vs exact count; the component-state hash's PRNG-excluded scope; the roadmap integration scenario — two different-seed replays whose first component divergence lands at tick 37 report tick 37 and the diverged component (machine-greppablereplay-diff-37line); tick-0 initial-state divergence; length divergence; the identical case; identity-mismatch rejection (both logs named, per field); determinism-disabled rejection (recorded under the disabled identity, so the determinism check — not the identity check — fires); draw-path KAT against an independentlaige::Prng.tests/replay/) — the check-script template gains an optional second setup step (SETUP2, backward compatible) plus a<DIFFBUDGET>placeholder; new fixturereplay_smoke_budget128.json; four entries:replay_diff_identical(two 16-tick recordings, same fixture:result=identical, exit 0),replay_diff_length(16 vs 10 ticks:result=length_divergence,first_divergent_tick=11, exit 1),replay_diff_identity(budget-128 config against a budget-64 log:config_hash DIFFERSreport, exit 2),replay_diff_usage(--diffwith one log path, exit 2).Docs
docs/api/replay.md— intro updated; new "The diff mode" section (API, result field table, steps, performance, misuse warnings: fresh identically-registered worlds, scenario logs need the scenario binary, entry byte views die with the worlds) and CLI report grammar + exit codes; Testing and CI bullets for both new suites.docs/api/entity.md— new "The component-state hash and the state diff" section; Roadmap context bullet for M1-DET-05.roadmap/M1-heartbeat.md— M1-DET-05 checkbox flipped to[x].laige-api.jsonregenerated (cmake --build build --target laige-api);api-check-freshgreen.Verification (all green)
build(Debug, g++):ctest -R replay_diff(13 unit tests + 4 CLI entries),ctest -R replay16/16, fulllaige-sim_tests, include-lint/API/determinism-lint suites 11/11, hello sample suite.build-asan(Debug, ASan):ctest -R replay_diff, fulllaige-sim_tests(70 s, no findings), all 20 replay entries.build-clang(Debug, Clang, -Werror): clean compile;ctest -R replay_diff, fulllaige-sim_tests.build-tsan(Debug, TSan,TSAN_OPTIONS=halt_on_error=1):ctest -R replay_diff, all 20 replay entries.Notes:
ENGINE-RULE-EXCEPTIONadded. No dependencies added.{n}regexes, CRLF normalization, gtest temp dir), and scenario-log diffs from the scenario binary (documented constraint, identity check rejects schema-mismatched logs).