-
Notifications
You must be signed in to change notification settings - Fork 0
Experiment: replay verification at scale + FP perturbation sensitivity #47
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
10 commits
Select commit
Hold shift + click to select a range
d23d66d
Add replay batch recording, perturbation hook, and deterministic iter…
gmliao a318199
Add replay-scale-fp-perturbation experiment results
gmliao eed235a
Drop raw runner logs from results; keep one artifact JSON per run
gmliao fcdd41e
Add --move-every to ReevaluationRunner record mode
gmliao d303e71
Add workload variants, long-horizon runs, and cross-arch verify script
gmliao f498d47
Clarify float vs fixed-point perturbation terminology in README
gmliao 9cd363b
Clarify LSB as fixed-point quantization step; frame verification as a…
gmliao 9c48092
Note that coordinates are type-enforced fixed-point (Position2/IVec2)…
gmliao 5c9769f
Add cross-arch (x86_64) replay verification result
gmliao 7bf210a
Integrate cross-arch verification into results and tables
gmliao File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
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
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
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
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
98 changes: 98 additions & 0 deletions
98
Notes/plans/2026-08-31-replay-scale-fp-perturbation-experiment-design.md
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,98 @@ | ||
| # Experiment design: replay verification at scale + FP perturbation sensitivity | ||
|
|
||
| Design run under `sst-experiment` (step 2b). Topic: `deep-research/replay-scale-fp-perturbation/`. | ||
| Card approved by the maintainer in chat on 2026-08-31. Everything in this round (knobs, this | ||
| note, data) lands via the `experiment/replay-scale-fp-perturbation` PR. | ||
|
|
||
| ## Question | ||
|
|
||
| (A) Over dozens of recordings and tens of thousands of ticks, does per-tick hash replay | ||
| verification stay at 0 mismatches? (B) When a floating-point / fixed-point perturbation is | ||
| injected into replay, does the verification detect it, and with what latency in ticks? | ||
|
|
||
| ## Design decisions | ||
|
|
||
| ### D1: Headless batch recording — `ReevaluationRunner --record` | ||
|
|
||
| **Chosen:** a new `--record` mode runs an in-process live `LandKeeper` with | ||
| `enableLiveStateHashRecording`, joins 5 players, injects a deterministic `MoveTo` client | ||
| event per player every 20 ticks (integer-math targets, same style as the EncodingBenchmark | ||
| `--active-players` injector), steps N ticks, and saves via `ReevaluationRecorder.save`. | ||
| `landID = "hero-defense:batch-<seed>"` — the RNG seed is derived from the landID, so each | ||
| seed index yields a distinct recording. | ||
|
|
||
| **Rejected:** looping the WebSocket E2E recorder against a live GameServer — orders of | ||
| magnitude slower, adds transport nondeterminism unrelated to the question, and cannot run | ||
| 30 recordings unattended in reasonable time. | ||
|
|
||
| **Why this answers the Question:** live-mode `LandKeeper` records injected actions/client | ||
| events and per-tick hashes exactly as the server path does; replaying these records through | ||
| `ReevaluationEngine` is the same verification the paper describes, now at ~30 × 1,200 ticks. | ||
|
|
||
| ### D2: Perturbation hook — env-driven, inside `MovementSystem.updatePlayerMovement` | ||
|
|
||
| **Chosen:** three env vars (read once per process), `HERO_PERTURB_TICK`, | ||
| `HERO_PERTURB_MODE` (`float` | `fixed`), `HERO_PERTURB_EPS`. At the configured tick, every | ||
| player currently moving gets perturbed: `float` adds eps to the Float `moveSpeed` before | ||
| fixed-point quantization; `fixed` adds eps raw LSB units (1 LSB = 0.001 world units) to the | ||
| quantized x coordinate after the movement step. Unset env = hook fully inert (recording and | ||
| normal replay are untouched). | ||
|
|
||
| **Rejected:** a perturbation flag inside `ReevaluationEngine` (core `Sources/`, would need a | ||
| core PR and couples the engine to an experiment concern); patching the record file itself | ||
| (tests the parser, not the determinism pipeline). | ||
|
|
||
| **Why this answers the Question:** the reviewer objection is about sensitivity to FP | ||
| non-determinism in game logic. Perturbing the actual movement computation during replay is | ||
| exactly that failure mode; sub-LSB float noise vs >=1 LSB shifts separates "absorbed by | ||
| fixed-point quantization" from "detected by hash comparison". | ||
|
|
||
| ### D3: Metric semantics (mandatory) | ||
|
|
||
| - Part A, per recording: `total_ticks` = maxTickId+1; `mismatch_ticks` = count of ticks where | ||
| the replayed hash differs from the recorded ground-truth hash (second check: run1 vs run2 | ||
| of the replay); `total_actions` / `total_client_events` from record statistics. One unit = | ||
| one tick compared. | ||
| - Part B, per (recording, eps) cell: `detected` = verification exited with >=1 recorded-hash | ||
| mismatch; `detection_latency_ticks` = first mismatched tickId − perturb tick (600); null | ||
| when not detected. One unit = one perturbed replay run. | ||
| - All runs `swift run -c release`; runner stdout kept as `results/<run-id>.log`. | ||
|
|
||
| ### D4: Matrix | ||
|
|
||
| | Part | axis | values | | ||
| |---|---|---| | ||
| | A | seed | 1…30 (landID-derived), 1,200 ticks each | | ||
| | B | eps | float 1e-7 (sub-LSB), fixed +1 LSB, fixed +1000 LSB | | ||
| | B | recordings | seeds 1…10, perturb tick 600 | | ||
|
|
||
| Fixed: 5 players, MoveTo every 20 ticks, no turrets, single room, same host (Apple M2, arm64). | ||
|
|
||
| ## Risks | ||
|
|
||
| - Same-architecture only this round (arm64 record → arm64 replay); the 2026-02 evidence | ||
| already covers arm64 → x86_64. Stated in Caveats. | ||
| - Sub-LSB float perturbation may be absorbed (0 detections) — that is a result, not a failure. | ||
| - If players are not moving at tick 600 the perturbation is a no-op; the 20-tick MoveTo | ||
| cadence with far targets keeps all players moving throughout. | ||
|
|
||
| ## Findings during execution (2026-08-31) | ||
|
|
||
| 1. **Replay sequence-counter gap.** Re-evaluation replays recorded inputs with their recorded | ||
| sequence numbers but never advances the shared output-sequence counter past them, so server | ||
| events emitted during replay carry different `sequence` values than the recording even when | ||
| state evolution is identical. The runner now re-checks such mismatches by content (tickId, | ||
| type, payload, target) and reports a pure sequence difference as an explicit warning instead | ||
| of a failure. A core fix (advancing the counter during replay) is tracked as follow-up work. | ||
| 2. **Order-dependent game logic (real determinism bug, found by scaling).** The first full run | ||
| failed 28/30: replays diverged from recordings at scattered ticks, and one seed even diverged | ||
| between two replays in the same process. Field-level diff at the first divergent tick showed | ||
| players' `lastFireTick`/`rotation`/`resources` differing — firing order and target selection | ||
| depended on `Dictionary` iteration order (`for (id, x) in dict` in the tick handler, and a | ||
| strict `<` nearest-target comparison that broke ties by iteration order). Fix: sorted-key | ||
| iteration for players/monsters/turrets and a lowest-id tie-break in | ||
| `CombatSystem.findNearestMonsterInRange`. After the fix: 30/30 recordings verify with zero | ||
| mismatches. The five short recordings used previously never surfaced this because sparse | ||
| combat rarely hit an order-dependent branch. | ||
| 3. The pre-existing committed fixtures (`reevaluation-records/1..3-hero-defense.json`, January) | ||
| no longer replay against current game logic — expected staleness, they are not used by tests. |
Oops, something went wrong.
Oops, something went wrong.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When
HERO_PERTURB_MODE=fixed, this converts a raw-LSB count to world-spaceFloatby manually dividing by 1000 and then requantizing it. For fractional, sufficiently large, or precision-losingHERO_PERTURB_EPSvalues, the applied delta can differ from the requested LSB count, trap duringInt32conversion, or wrap when added to the coordinate, invalidating the experiment rather than producing a controlled perturbation. Parse a bounded integer LSB value and add a semantic DeterministicMath helper instead of manipulating the scale directly.AGENTS.md reference: AGENTS.md:L130-L138
Useful? React with 👍 / 👎.