fix(rules): preserve lock holes when arming effect seven

Record 148 writes player item 20 and its own type-three contact sentinel. It
does not clear lock-hole items or contacts 129 through 133; the panel completion
path owns that reset. Rust treated record 148 as a wheel-reset sensor and erased
all five lock holes immediately.

Rename the recovered mechanism to the special/effect-seven hole and preserve
existing lock progress when it is captured. Keep its multiball-ready state,
contact 2 publication, and later special-respawn suppression unchanged.

Test Plan:
- `cargo test --workspace --all-targets --all-features` -- 120 passed
- `cargo clippy --workspace --all-targets --all-features -- -D warnings` -- passed
- `rumdl check --flavor commonmark RECONSTRUCTION.md CHANGELOG.md` -- passed
- `git diff --cached --check` -- passed
This commit is contained in:
2026-08-23 20:07:39 +02:00
parent 2d14823c04
commit 8d436b8a4b
4 changed files with 32 additions and 27 deletions
+26 -25
View File
@@ -11,7 +11,7 @@ use crate::{
real48::Real48,
table::{
BUMPERS, EFFECT_SENSOR, LOCK_HOLES, PASSIVE_CIRCLES, TARGET_SENSORS, WALLS,
WHEEL_RESET_SENSOR,
SPECIAL_HOLE_SENSOR,
},
};
use macroquad::prelude::{Rect, Vec2, vec2};
@@ -228,7 +228,7 @@ enum BallDoubleState {
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
enum WheelResetGate {
enum SpecialHoleGate {
#[default]
Enabled,
Suppressed,
@@ -354,7 +354,7 @@ pub struct Game {
target_effect: u8,
claw_frame_seconds: f32,
multiball_state: MultiballState,
wheel_reset_gate: WheelResetGate,
special_hole_gate: SpecialHoleGate,
score_mode: ScoreMode,
ball_double: BallDoubleState,
}
@@ -398,7 +398,7 @@ impl Game {
target_effect: 0,
claw_frame_seconds: CLAW_FRAME_SECONDS,
multiball_state: MultiballState::Unavailable,
wheel_reset_gate: WheelResetGate::Enabled,
special_hole_gate: SpecialHoleGate::Enabled,
score_mode: ScoreMode::Normal,
ball_double: BallDoubleState::Inactive,
}
@@ -986,7 +986,7 @@ impl Game {
EFFECT_SENSOR.radius,
))
{
self.wheel_reset_gate = WheelResetGate::Enabled;
self.special_hole_gate = SpecialHoleGate::Enabled;
}
self.check_target_sensors(
ball,
@@ -995,7 +995,7 @@ impl Game {
140..=147,
events,
);
if let Some(completed_action) = self.check_wheel_reset(
if let Some(completed_action) = self.check_special_hole(
ball,
ball_number,
ball_count,
@@ -1458,7 +1458,7 @@ impl Game {
EFFECT_SENSOR.radius,
))
{
self.wheel_reset_gate = WheelResetGate::Enabled;
self.special_hole_gate = SpecialHoleGate::Enabled;
}
if old_position.y >= 250_000 {
return SensorScanResult {
@@ -1512,7 +1512,7 @@ impl Game {
events,
);
if let Some(completed_action) =
self.check_wheel_reset(
self.check_special_hole(
ball,
ball_number,
ball_count,
@@ -1583,7 +1583,7 @@ impl Game {
if surface_distance < -11_000
&& contact_allowed
&& !(record_id == 148 && self.wheel_reset_gate == WheelResetGate::Suppressed)
&& !(record_id == 148 && self.special_hole_gate == SpecialHoleGate::Suppressed)
{
if ball.capture_age < 300 {
let mut velocity = MilliVec::from_velocity_per_second(ball.velocity);
@@ -1720,7 +1720,7 @@ impl Game {
}
#[allow(clippy::too_many_arguments)]
fn check_wheel_reset(
fn check_special_hole(
&mut self,
ball: &mut Ball,
ball_number: u16,
@@ -1734,9 +1734,9 @@ impl Game {
ball,
ball_number,
ball_count,
WHEEL_RESET_SENSOR.id,
WHEEL_RESET_SENSOR.center,
WHEEL_RESET_SENSOR.radius,
SPECIAL_HOLE_SENSOR.id,
SPECIAL_HOLE_SENSOR.center,
SPECIAL_HOLE_SENSOR.radius,
old_position,
predicted_position,
best_capture,
@@ -1745,7 +1745,6 @@ impl Game {
CaptureStep::Outside | CaptureStep::Holding => return None,
CaptureStep::Complete => {}
}
self.wheel_holes.fill(false);
self.multiball_state = MultiballState::Ready;
events.push(Event::Wheel);
Some(if ball_count == 1 {
@@ -1906,7 +1905,7 @@ impl Game {
{
return false;
}
self.wheel_reset_gate = WheelResetGate::Enabled;
self.special_hole_gate = SpecialHoleGate::Enabled;
let damping = Real48::from_bytes([0x80, 0x66, 0x66, 0x66, 0x66, 0x66]);
velocity.x = Real48::from_i32(velocity.x).multiply(damping).round_i32();
let vertical_factor = Real48::from_i32(1).subtract(
@@ -2085,11 +2084,11 @@ impl Game {
};
self.multiball_state = MultiballState::Unavailable;
self.record_contacts[148] = 0;
self.wheel_reset_gate = WheelResetGate::Suppressed;
self.special_hole_gate = SpecialHoleGate::Suppressed;
return;
}
self.ball_double = BallDoubleState::Inactive;
self.wheel_reset_gate = WheelResetGate::Enabled;
self.special_hole_gate = SpecialHoleGate::Enabled;
let player = &mut self.players[self.current_player];
if player.extra_balls > 0 {
player.extra_balls -= 1;
@@ -2776,7 +2775,7 @@ mod tests {
}
#[test]
fn wheel_reset_arms_effect_seven_multiball() {
fn special_hole_arms_effect_seven_multiball() {
let mut game = Game::new_with_seed(1, 7);
let mut events = Vec::new();
game.multiball_state = MultiballState::Ready;
@@ -2824,7 +2823,7 @@ mod tests {
);
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.special_hole_gate, SpecialHoleGate::Suppressed);
game.fixed_update(&mut events);
assert!(!game.ball.in_launcher);
@@ -2842,12 +2841,12 @@ mod tests {
y: 3_055,
}
);
assert_eq!(game.wheel_reset_gate, WheelResetGate::Suppressed);
assert_eq!(game.special_hole_gate, SpecialHoleGate::Suppressed);
game.ball.position = vec2(17.0, 31.0);
game.ball.velocity = Vec2::ZERO;
game.fixed_update(&mut events);
assert_eq!(game.wheel_reset_gate, WheelResetGate::Enabled);
assert_eq!(game.special_hole_gate, SpecialHoleGate::Enabled);
}
#[test]
@@ -2875,7 +2874,7 @@ mod tests {
}
);
assert_eq!(game.multiball_state, MultiballState::Unavailable);
assert_eq!(game.wheel_reset_gate, WheelResetGate::Suppressed);
assert_eq!(game.special_hole_gate, SpecialHoleGate::Suppressed);
}
#[test]
@@ -2893,7 +2892,7 @@ mod tests {
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);
assert_eq!(game.special_hole_gate, SpecialHoleGate::Suppressed);
}
#[test]
@@ -3901,10 +3900,12 @@ mod tests {
complete_stationary_type_three_capture(
&mut game,
WHEEL_RESET_SENSOR.center,
SPECIAL_HOLE_SENSOR.center,
&mut events,
);
assert!(game.wheel_holes.iter().all(|filled| !*filled));
assert!(game.wheel_holes.iter().all(|filled| *filled));
assert_eq!(game.multiball_state, MultiballState::Ready);
assert_eq!(game.record_contacts[148], 2);
assert_eq!(events.last(), Some(&Event::Wheel));
assert_eq!(
events