From 25019514c7e711a9a25bef199c5a94c1d913f1e2 Mon Sep 17 00:00:00 2001 From: ddidderr Date: Sun, 23 Aug 2026 20:02:32 +0200 Subject: [PATCH] fix(multiball): clear record 148 contact on respawn The required-effect word at 1028:399a is record 148's +0x43 contact field. The original special respawn clears that exact word before suppressing the capture gate. Rust reset only its derived multiball state and left contact 2 behind, allowing a synthetic type-three rim candidate after respawn. Clear record_contacts[148] with the derived state. Extend both armed and active multiball respawn regressions to begin with the binary's contact sentinel and prove that the transition removes it. Test Plan: - `cargo test --workspace --all-targets --all-features` -- 118 passed - `cargo clippy --workspace --all-targets --all-features -- -D warnings` -- passed - `rumdl check --flavor commonmark CHANGELOG.md` -- passed - `git diff --cached --check` -- passed --- tdkpin-rs/CHANGELOG.md | 5 +++-- tdkpin-rs/src/game.rs | 5 +++++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/tdkpin-rs/CHANGELOG.md b/tdkpin-rs/CHANGELOG.md index 3879765..e798935 100644 --- a/tdkpin-rs/CHANGELOG.md +++ b/tdkpin-rs/CHANGELOG.md @@ -19,8 +19,9 @@ and this project adheres to full normal velocity instead of always subtracting 1,000. - Restore `1000:ae6e`'s required-ball special respawn: armed or completed effect-seven play grants one final `(17,23)/(0,3040)` ball without consuming - a marker, clears required state, and suppresses record 148 until the next - broadphase-active type-4 record reenables it. + a marker, clears required state including record 148's contact word, and + suppresses record 148 until the next broadphase-active type-4 record + reenables it. - Replace the invented post-move `y>470` fallback with the original pre-scan prediction bounds `x<=0 || x>340000 || y<=0 || y>460000`, including one-slot removal during multiball and next-substep handling after a response. diff --git a/tdkpin-rs/src/game.rs b/tdkpin-rs/src/game.rs index b3ca3fc..3e9f209 100644 --- a/tdkpin-rs/src/game.rs +++ b/tdkpin-rs/src/game.rs @@ -2054,6 +2054,7 @@ impl Game { capture_age: 0, }; self.multiball_state = MultiballState::Unavailable; + self.record_contacts[148] = 0; self.wheel_reset_gate = WheelResetGate::Suppressed; return; } @@ -2747,6 +2748,7 @@ mod tests { let mut game = Game::new_with_seed(1, 7); let mut events = Vec::new(); game.multiball_state = MultiballState::Ready; + game.record_contacts[148] = 2; game.apply_wall_rule(84, &mut events); assert_eq!(game.target_effect, 7); @@ -2789,6 +2791,7 @@ mod tests { MilliVec { x: 0, y: 3_040 } ); assert_eq!(game.multiball_state, MultiballState::Unavailable); + assert_eq!(game.record_contacts[148], 0); assert_eq!(game.wheel_reset_gate, WheelResetGate::Suppressed); game.fixed_update(&mut events); @@ -2847,6 +2850,7 @@ mod tests { fn armed_effect_seven_also_special_respawns_before_multiball_starts() { let mut game = Game::new(1); game.multiball_state = MultiballState::Ready; + game.record_contacts[148] = 2; let balls = game.player().balls; let mut events = Vec::new(); @@ -2856,6 +2860,7 @@ mod tests { assert_eq!(game.player().balls, balls); assert_eq!(game.ball.position, vec2(17.0, 23.0)); assert_eq!(game.multiball_state, MultiballState::Unavailable); + assert_eq!(game.record_contacts[148], 0); assert_eq!(game.wheel_reset_gate, WheelResetGate::Suppressed); }