fix(rules): finish tilted type-three captures
The original capture handler does not request a launcher reset when a single ball completes a type-three record during Tilt. It calls the normal ball-end state machine instead. Rust returned Reset for every single-ball capture, granting tilted lock-hole captures a free ball and bypassing record 148's special-respawn decision. Add an explicit Finish action for tilted single-ball type-three completions. Lock holes now consume the ball with the tilted drain cue suppressed; record 148 still publishes its required contact first, so the same finish call can consume it through the special-respawn branch without using a marker. Test Plan: - `cargo test --workspace --all-targets --all-features` -- 125 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:
+71
-3
@@ -247,6 +247,7 @@ enum BallAction {
|
||||
Suspend,
|
||||
Reset,
|
||||
Remove,
|
||||
Finish,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
@@ -885,6 +886,10 @@ impl Game {
|
||||
self.reset_ball_to_launcher();
|
||||
return true;
|
||||
}
|
||||
BallAction::Finish => {
|
||||
self.drain(events);
|
||||
return true;
|
||||
}
|
||||
BallAction::Remove => {
|
||||
self.ball = self
|
||||
.secondary_ball
|
||||
@@ -1269,7 +1274,7 @@ impl Game {
|
||||
}
|
||||
match scan.action {
|
||||
BallAction::Keep | BallAction::Suspend => self.secondary_ball = Some(ball),
|
||||
BallAction::Reset | BallAction::Remove => return true,
|
||||
BallAction::Reset | BallAction::Remove | BallAction::Finish => return true,
|
||||
}
|
||||
collided || scan.action == BallAction::Suspend
|
||||
}
|
||||
@@ -1434,6 +1439,10 @@ impl Game {
|
||||
match scan.action {
|
||||
BallAction::Keep | BallAction::Suspend => self.ball = ball,
|
||||
BallAction::Reset => self.reset_ball_to_launcher(),
|
||||
BallAction::Finish => {
|
||||
self.ball = ball;
|
||||
self.drain(events);
|
||||
}
|
||||
BallAction::Remove => {
|
||||
self.ball = self
|
||||
.secondary_ball
|
||||
@@ -1727,7 +1736,11 @@ impl Game {
|
||||
}
|
||||
events.push(Event::Lock);
|
||||
if ball_count == 1 {
|
||||
BallAction::Reset
|
||||
if self.tilted {
|
||||
BallAction::Finish
|
||||
} else {
|
||||
BallAction::Reset
|
||||
}
|
||||
} else {
|
||||
BallAction::Remove
|
||||
}
|
||||
@@ -1762,7 +1775,11 @@ impl Game {
|
||||
self.multiball_state = MultiballState::Ready;
|
||||
events.push(Event::Wheel);
|
||||
Some(if ball_count == 1 {
|
||||
BallAction::Reset
|
||||
if self.tilted {
|
||||
BallAction::Finish
|
||||
} else {
|
||||
BallAction::Reset
|
||||
}
|
||||
} else {
|
||||
BallAction::Remove
|
||||
})
|
||||
@@ -2646,6 +2663,57 @@ mod tests {
|
||||
assert!(events.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tilted_type_three_completion_finishes_or_special_respawns() {
|
||||
let mut lock = Game::new(1);
|
||||
lock.tilted = true;
|
||||
lock.ball.in_launcher = false;
|
||||
lock.ball.position = LOCK_HOLES[0].center;
|
||||
lock.ball.velocity = Vec2::ZERO;
|
||||
lock.ball.capture_age = 300;
|
||||
let mut lock_events = Vec::new();
|
||||
assert_eq!(
|
||||
lock.check_sensor_objects(
|
||||
MilliVec::from_position(LOCK_HOLES[0].center),
|
||||
MilliVec::default(),
|
||||
&mut lock_events,
|
||||
),
|
||||
BallAction::Finish
|
||||
);
|
||||
assert_eq!(lock.player().balls, 2);
|
||||
assert_eq!(lock.player().secondary_score, 10_000);
|
||||
assert!(lock.ball.in_launcher);
|
||||
assert!(!lock.tilted);
|
||||
assert!(lock_events.contains(&Event::Drain));
|
||||
assert!(!lock_events.contains(&Event::Sound(2008)));
|
||||
|
||||
let mut special = Game::new(1);
|
||||
special.tilted = true;
|
||||
special.ball.in_launcher = false;
|
||||
special.ball.position = SPECIAL_HOLE_SENSOR.center;
|
||||
special.ball.velocity = Vec2::ZERO;
|
||||
special.ball.capture_age = 300;
|
||||
let balls = special.player().balls;
|
||||
let mut special_events = Vec::new();
|
||||
assert_eq!(
|
||||
special.check_sensor_objects(
|
||||
MilliVec::from_position(SPECIAL_HOLE_SENSOR.center),
|
||||
MilliVec::default(),
|
||||
&mut special_events,
|
||||
),
|
||||
BallAction::Finish
|
||||
);
|
||||
assert_eq!(special.player().balls, balls);
|
||||
assert_eq!(special.ball.position, vec2(17.0, 23.0));
|
||||
assert!(special.tilted);
|
||||
assert_eq!(special.multiball_state, MultiballState::Unavailable);
|
||||
assert_eq!(special.record_contacts[148], 0);
|
||||
assert!(!special_events.contains(&Event::Drain));
|
||||
assert!(!special_events
|
||||
.iter()
|
||||
.any(|event| event.sound_resource().is_some()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn recovered_target_banks_deactivate_and_rearm_their_line_groups() {
|
||||
let mut game = Game::new(1);
|
||||
|
||||
Reference in New Issue
Block a user