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
This commit is contained in:
2026-08-23 20:02:32 +02:00
parent a7c2779f73
commit 25019514c7
2 changed files with 8 additions and 2 deletions
+3 -2
View File
@@ -19,8 +19,9 @@ and this project adheres to
full normal velocity instead of always subtracting 1,000. full normal velocity instead of always subtracting 1,000.
- Restore `1000:ae6e`'s required-ball special respawn: armed or completed - Restore `1000:ae6e`'s required-ball special respawn: armed or completed
effect-seven play grants one final `(17,23)/(0,3040)` ball without consuming 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 a marker, clears required state including record 148's contact word, and
broadphase-active type-4 record reenables it. 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 - Replace the invented post-move `y>470` fallback with the original pre-scan
prediction bounds `x<=0 || x>340000 || y<=0 || y>460000`, including prediction bounds `x<=0 || x>340000 || y<=0 || y>460000`, including
one-slot removal during multiball and next-substep handling after a response. one-slot removal during multiball and next-substep handling after a response.
+5
View File
@@ -2054,6 +2054,7 @@ impl Game {
capture_age: 0, capture_age: 0,
}; };
self.multiball_state = MultiballState::Unavailable; self.multiball_state = MultiballState::Unavailable;
self.record_contacts[148] = 0;
self.wheel_reset_gate = WheelResetGate::Suppressed; self.wheel_reset_gate = WheelResetGate::Suppressed;
return; return;
} }
@@ -2747,6 +2748,7 @@ mod tests {
let mut game = Game::new_with_seed(1, 7); let mut game = Game::new_with_seed(1, 7);
let mut events = Vec::new(); let mut events = Vec::new();
game.multiball_state = MultiballState::Ready; game.multiball_state = MultiballState::Ready;
game.record_contacts[148] = 2;
game.apply_wall_rule(84, &mut events); game.apply_wall_rule(84, &mut events);
assert_eq!(game.target_effect, 7); assert_eq!(game.target_effect, 7);
@@ -2789,6 +2791,7 @@ mod tests {
MilliVec { x: 0, y: 3_040 } MilliVec { x: 0, y: 3_040 }
); );
assert_eq!(game.multiball_state, MultiballState::Unavailable); assert_eq!(game.multiball_state, MultiballState::Unavailable);
assert_eq!(game.record_contacts[148], 0);
assert_eq!(game.wheel_reset_gate, WheelResetGate::Suppressed); assert_eq!(game.wheel_reset_gate, WheelResetGate::Suppressed);
game.fixed_update(&mut events); game.fixed_update(&mut events);
@@ -2847,6 +2850,7 @@ mod tests {
fn armed_effect_seven_also_special_respawns_before_multiball_starts() { fn armed_effect_seven_also_special_respawns_before_multiball_starts() {
let mut game = Game::new(1); let mut game = Game::new(1);
game.multiball_state = MultiballState::Ready; game.multiball_state = MultiballState::Ready;
game.record_contacts[148] = 2;
let balls = game.player().balls; let balls = game.player().balls;
let mut events = Vec::new(); let mut events = Vec::new();
@@ -2856,6 +2860,7 @@ mod tests {
assert_eq!(game.player().balls, balls); assert_eq!(game.player().balls, balls);
assert_eq!(game.ball.position, vec2(17.0, 23.0)); assert_eq!(game.ball.position, vec2(17.0, 23.0));
assert_eq!(game.multiball_state, MultiballState::Unavailable); assert_eq!(game.multiball_state, MultiballState::Unavailable);
assert_eq!(game.record_contacts[148], 0);
assert_eq!(game.wheel_reset_gate, WheelResetGate::Suppressed); assert_eq!(game.wheel_reset_gate, WheelResetGate::Suppressed);
} }