perf(test): cap the 3p loop-shortcut grind drives at the measured priming point#6608
Conversation
…ming point
The three longest-running tests in the 21621-test corpus all live in
loop_shortcut.rs, and all three spent ~99.9% of their wall clock in a
`drive_collect` grind that runs BEFORE their assertions and never exits
early. Measured: 500 beats = 19.30s of drive vs 9.4ms for the behaviour
actually under test (inject offer -> DeclareShortcut -> accept -> E1
drive-and-measure).
The grind was pure waste. A cap sweep over {4, 8, 12, 16, 24, 32, 48, 64,
100} shows every assertion and every reach-guard behaves identically at
every cap, and each test's revert-probe still flips it to FAIL at every
cap >= 4 -- the loop is primed by beat ~4 and nothing changes through beat
500. Replace the three literals with one shared `PRIMED_LOOP_BEATS = 24`
(a directly swept value, 6x margin over the measured priming point)
carrying the measurement as its rationale.
interactive_3p_subset_lethal_does_not_crown 49.672s -> 2.446s (20.3x)
injected_3p_one_faller_no_crown 49.814s -> 2.407s (20.7x)
injected_3p_unequal_life_pin_all_no_crown 56.062s -> 4.850s (11.6x)
Discrimination is preserved and re-proven on the SHIPPED tests, not on
probe copies: each still FAILS under the specific defect it exists to
catch -- weakened `nonfallers.len() != 1` (loop_check.rs), bypassed
E1-measure `live_mandatory_loop_winner` gate, and removed F2
`fallers_lives_pairwise_equal` re-check. Every panic lands on the test's
intended discriminator assertion, not on a reach-guard.
No production code, no fixture, and no assertion body changes: the entire
non-comment delta is the new const plus three cap substitutions.
Two doc corrections the measurements forced, both pre-existing errors:
- The M1 REVERT-FAIL note claimed the mutation flips "the two no-crown
assertions". Measured: only the `wf` assertion discriminates; the
event-scan stays TRUE at every cap. Now stated accurately.
- The equal-life half of `setup_3p_both_fall` consumes 0 beats -- its
mandatory single-non-faller loop is already crowned by the natural
bridge while the kick-off resolves. Cap-independent, so this was
equally true at the old cap 200. The const doc carries the measured
per-drive beat counts rather than a blanket claim.
Assisted-by: ClaudeCode:claude-opus-4.5
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (1)
📝 WalkthroughWalkthroughChangesThe integration test harness now detects loop recurrence during capped drives. Subset-lethal and injected soundness tests use a shared 24-beat cap and assert priming before evaluating no-crown outcomes. Subset-lethal loop test validation
Estimated code review effort: 2 (Simple) | ~10 minutes Suggested reviewers: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@crates/engine/tests/integration/loop_shortcut.rs`:
- Around line 1105-1131: Add a positive reach-guard in the no-crown test flows
around the natural loop-detection/no-crown decision in drive_collect, proving
that terminal-state evaluation occurred before asserting !GameOver and no
GameOver event. Ensure upstream exhaustion or the PRIMED_LOOP_BEATS cap cannot
make these negative assertions pass without reaching that branch; retain or
adjust the cap only if necessary to guarantee the guard is reached.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: 1d882964-8ffd-4488-ac5d-d986d83bf6e1
📒 Files selected for processing (1)
crates/engine/tests/integration/loop_shortcut.rs
matthewevans
left a comment
There was a problem hiding this comment.
Request changes — the cap reduction needs a decision-point reach guard.
🔴 Blocker
crates/engine/tests/integration/loop_shortcut.rs:1149 now drives only PRIMED_LOOP_BEATS, while drive_collect returns the current WaitingFor state when its loop simply exhausts (crates/engine/tests/integration/loop_shortcut.rs:256). The new documentation explicitly says the subset-lethal and unequal-life runs consume all 24 beats without hitting a terminal exit (crates/engine/tests/integration/loop_shortcut.rs:1104), and their reach guards establish only that players lost life (:1153, :1516). That does not show that the loop-classification decision was evaluated: a regression which delays sampling/classification beyond beat 24 still leaves the runner in Priority, makes the no-crown negative assertions pass, and satisfies the life-loss guards.
CodeRabbit raised the same current-head finding; tracing the production path confirms it: the actual classification is in interactive_loop_bridge / find_live_loop_winner (crates/engine/src/game/engine.rs:604, :784), not in drive_collect.
Suggested fix: add a positive, externally observable reach guard for the classification/sampling decision that the three no-crown tests exercise (or retain a cap that demonstrably reaches it). The guard must fail when the cap ends before that decision, rather than relying on the documented manual sweep.
✅ Clean
The change is tightly scoped to the integration test and retains the existing fixtures and assertions.
Recommendation: request changes; re-review once the cap has a discriminating decision-point guard.
…assume Review of phase-rs#6608 (matthewevans, CodeRabbit) found the three no-crown tests had no positive guard proving the loop-classification decision was actually reached: a regression that delayed sampling or classification past the drive cap would leave the runner in `Priority`, satisfy the existing life-loss guards, and read green. Add `drive_collect_primed`, which drives through the existing `drive_collect` beat by beat and reports whether any prior in `GameState::loop_detect_ring` compared equal to the live state modulo resources -- `loop_states_equal_modulo_resources`, the engine's own public predicate, so no recurrence logic is reimplemented test-side. A ring hit forces the bridge's §3 conjuncts to have held at that state, so `find_live_loop_winner` -> `live_mandatory_loop_winner` demonstrably ran on it. Measured, and the measurement shaped the design: - Recurrence is PHASE-dependent, not monotone. Over caps {1,2,3,4,8,24} it holds at 2 and 8 and not at 1/3/4/24 (period 6) -- so the obvious terminal-state check would report false on a perfectly primed loop. Hence the per-beat scan, short-circuited at the first hit: 5.6-17.5 ms per drive, priming at beat 2. Suite still 69/69 in 8.1s. - Discriminating: forcing the cap to 1 (below the beat-2 priming point) flips all three tests to FAIL on this guard. - Placed LAST in `interactive_3p_subset_lethal_does_not_crown`: there the classification and the wrongful-crown failure mode share one drive, and measured, a guard placed first steals the M1 panic (M1 crowns at beat ~1, before recurrence) and reports the wrong cause. Guard last, M1 lands on the intended `wf` no-crown assertion. - Asserted on the unequal half only of the F2 test: the equal half consumes 0 beats (already crowned as the kick-off resolved), so it has no drive in which to recur, and it is the unequal half the F2 revert-probe flips. - M1/M2/M3 re-run on the shipped text: each still fails at its intended discriminator (lines 1212 / 1425 / 1640), not at a guard. Also corrects two claims in the const doc that measurement falsified. The life-loss guards do not "FAIL LOUDLY" when priming is delayed, and this new guard does not close the separate bridge-suppression blind spot either: suppressing the live-detect bridge leaves all three tests green at cap 24 AND at cap 500 (while two offer-dependent tests fail), because the ring sampler is an independent gate. That blind spot is cap-independent -- no cap closes it -- which is why a guard was needed rather than a bigger cap, and it is inherent to a negative test. The doc now says so, states the equality-disjunct-only scope (`loop_states_cover_modulo_growth` is `pub(crate)`), and drops an interpolated "beats 14, 20" claim that was never probed. Still zero production changes; every assertion condition and the cap itself are unchanged. Assisted-by: ClaudeCode:claude-opus-4.5
|
🤖 AI text below 🤖 Guard added in f69974a — you were right, and the measurement went further than the review did. What the guard is. It fails when the cap ends before the decision, as you required. Forcing Three measurements shaped it, and one of them contradicts the obvious implementation.
M1/M2/M3 were re-run on the shipped text: each still fails at its intended discriminator (lines 1212 / 1425 / 1640), never at a guard. One correction to the record, in your favour. I also measured whether any cap could have satisfied the alternative you offered ("or retain a cap that demonstrably reaches it"). It cannot. Making the bridge's And the new guard does not close that particular hole either — the ring sampler is a separate gate that the suppression doesn't touch, so the ring still fills and Two smaller doc corrections the measurements forced, both flagged rather than buried: the guard checks only the equality disjunct of the engine's |
matthewevans
left a comment
There was a problem hiding this comment.
Approved on 91e687b0aede0d8e97412dd286cdfbbe86d8d639.
The recurrence guard uses the engine's loop_states_equal_modulo_resources predicate at every driven beat, so the capped no-crown cases now fail if they never reach the live ring/bridge regime. The change is test-only, scoped to the existing integration seam, and the current CodeRabbit review has no actionable finding.
🤖 AI text below 🤖
Summary
The three longest-running tests in the entire 21621-test corpus all live in
loop_shortcut.rs, and all three spent ~99.9% of their wall clock in adrive_collectgrind that runs before their assertions and never exits early. A cap sweep proves the grind buys zero discrimination past beat ~4, so this replaces the three cap literals with one measured shared constant — 49.7s/49.8s/56.1s → 2.4s/2.5s/4.8s, with every assertion, fixture, and revert-probe flip preserved.Files changed
crates/engine/tests/integration/loop_shortcut.rs— newPRIMED_LOOP_BEATSconst + 3 cap substitutions + doc correctionsTrack
Developer
LLM
Model: claude-opus-4-5-20260514
Tier: Frontier
Thinking: high
Implementation method (required)
Method: /engine-implementer
CR references
None. No rules logic changes — test-runtime only. The three existing citations in touched doc comments (
CR 119.8,CR 104.2a,CR 704.5a) survive verbatim; zero CR numbers added or removed (git diff | grep -oE 'CR [0-9.]+'on both+and-lines is empty).docs/MagicCompRules.txtis gitignored and absent from this worktree, so no new CR number could be verified — per CLAUDE.md, none was written.Verification
Where the time went (measured, not estimated)
drive_collectThe behaviour actually under test costs 10–35ms. Per-beat cost is
record_loop_detect_sample()(a fullnormalize_for_loopdeepGameStateclone, ring cap 16) plusinteractive_loop_bridgerescanning all ≤16 priors twice — paid every beat, forever, because the subset-lethal bridge deliberately falls through to the pre-feature grind and so never reachesdrive_collect's terminal-state exit.Before / after
interactive_3p_subset_lethal_does_not_crowninjected_3p_one_faller_no_crowninjected_3p_unequal_life_pin_all_no_crownDebug build, contended (Tilt building concurrently) — upper bounds, and deliberately not encoded as a timing assertion.
Why cap 24 preserves discrimination
Cap sweep over {4, 8, 12, 16, 24, 32, 48, 64, 100}, run on both a clean tree and one tree per defect:
PASSat every cap — including test 3'seq_crownsreach-guard and test 2's rolled-backP2 == 20.FAILat every cap ≥ 4.Behaviour is flat from beat 4 to beat 500, so the old 500/500/200 caps bought nothing. 24 is itself a swept value (6× margin over the measured ≤4 priming point), not an interpolation.
Flip-proofs on the SHIPPED tests (not on probe copies)
The sweep above ran on temporary probe clones, so it was re-run against the real committed tests, one defect per build:
loop_check.rs—nonfallers.len() != 1weakened to any-faller-winsinteractive_3p_subset_lethal_does_not_crown:1165"subset-lethal loop must NOT crown a winner (CR 104.2a), got GameOver { winner: Some(PlayerId(0)) }"engine.rs— E1-measurelive_mandatory_loop_winnergate bypassedinjected_3p_one_faller_no_crown:1338"subset-lethal loop must NOT crown (CR 104.2a), got GameOver { winner: Some(PlayerId(0)) }"engine.rs— F2fallers_lives_pairwise_equal≥2-faller re-check removedinjected_3p_unequal_life_pin_all_no_crown:1544"unequal-life ≥2-faller drain must NOT crown (CR 704.3 simultaneity), got GameOver { winner: Some(PlayerId(0)) }"Every panic lands on the test's intended discriminator, not on a reach-guard — the optimized tests fail for the right reason, not incidentally. The production tree was verified pristine (
git diff --stat crates/engine/src/empty) after each mutation.Non-vacuity
Stripping all comments from both
HEAD:and the new file and diffing shows the complete non-comment delta is the new const plus three cap substitutions. Every assertion body and every fixture value (1000 / 1050 / 20, gap 50) is byte-identical.Commands
cargo nextest run -p engine -E 'test(interactive_3p_subset_lethal_does_not_crown) | test(injected_3p_one_faller_no_crown) | test(injected_3p_unequal_life_pin_all_no_crown)'— 3 passed, 4.828s total (2.389 / 2.522 / 4.813)cargo nextest run -p engine -E 'binary(integration) and test(loop_shortcut)'— 69 passed, 8.578s (also coversloop_shortcut_activation+loop_shortcut_mana_engine)cargo clippy -p engine --all-targets -- -D warnings— clean, 0 warningscargo fmt --all— no reformatAll run in a dedicated worktree with its own
target/(Tilt watches a different checkout, soscripts/tilt-wait.shwould return3= cannot-answer, not a failure).Gate A
Gate A PASS head=345b34be61ca53eeff81eab32698a2108c0e8904 base=2c6596de9360ef3e8d5df1c20e863631201253aa
Anchored on
crates/engine/tests/integration/loop_shortcut.rs:1954—POISON_RIDER, the file's existing convention of a file-scopeconstplaced after a section banner, above its consuming tests (also:2079,:2532,:2703,:4077)crates/engine/tests/integration/loop_shortcut.rs:253—drive_collect, the unchanged shared driver whosecapargument is the only thing this PR re-valuesFinal review-impl
Final review-impl PASS head=345b34be61ca53eeff81eab32698a2108c0e8904
Claimed parse impact
None. No parser file touched, so no Gate A parser-combinator concern applies beyond the pre-commit gate output above.
Scope Expansion
None. Two pre-existing doc errors surfaced during measurement and are corrected in place (no assertion body changed):
wfassertion discriminates — the event-scan staystrueunder that mutation at every cap 4…100. Now stated accurately. Fixing the assertion itself would require editing an assertion body, so it is disclosed rather than changed.setup_3p_both_fallconsumes 0 beats — its mandatory single-non-faller loop is already crowned by the natural bridge while the kick-off resolves. This is cap-independent, so it was equally true at the old cap 200. The const doc now carries measured per-drive beat counts (24/24, 24/24, 0/24) instead of a blanket claim.Validation Failures
None.
CI Failures
None.
Summary by CodeRabbit