fix(scoring): restore marker comparison and hit sound order
The original score helper compares signed high words and unsigned low words, which is a signed 32-bit comparison of the wrapped score bits. Rust used an unsigned comparison and could award a media marker after the score crossed the sign bit. Type-four trigger handling also queued WAVE 2004 after score mutation, so it replaced WAVE 2007 when the same hit crossed an extra-ball threshold. Use the binary's comparison semantics and dispatch the type-four hit sound before both ordinary target and player-effect score mutations. Regression tests cover both trigger paths, the exact monophonic event order, and a score with the high bit set. Test Plan: - `cargo test --workspace --all-targets --all-features` -- 115 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:
+73
-4
@@ -34,6 +34,13 @@ const DETAIL_SUBSTEPS: [u8; 5] = [5, 4, 3, 2, 1];
|
||||
const CLAW_TERMINAL_FRAMES: [u8; 4] = [1, 6, 7, 18];
|
||||
const ORIGINAL_BALL_SPEED_PER_SECOND: f32 = 380.0;
|
||||
|
||||
#[allow(clippy::cast_possible_wrap)]
|
||||
const fn score_reaches_marker(score: u32, threshold: u32) -> bool {
|
||||
// 1000:bc36 compares signed high words and, when equal, unsigned low
|
||||
// words. That is exactly a signed 32-bit comparison of the bit patterns.
|
||||
(score as i32) >= (threshold as i32)
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default)]
|
||||
pub struct Controls {
|
||||
pub left_flipper: bool,
|
||||
@@ -1736,6 +1743,8 @@ impl Game {
|
||||
let entered = touched && !self.trigger_flags[effect_index];
|
||||
self.trigger_flags[effect_index] = touched;
|
||||
if entered {
|
||||
events.push(Event::Target);
|
||||
events.push(Event::Sound(2004));
|
||||
self.add_score(EFFECT_SENSOR.score, events);
|
||||
match self.target_effect {
|
||||
1..=5 => {
|
||||
@@ -1769,8 +1778,6 @@ impl Game {
|
||||
}
|
||||
self.target_effect = 0;
|
||||
self.object_active[effect_index] = false;
|
||||
events.push(Event::Target);
|
||||
events.push(Event::Sound(2004));
|
||||
self.randomize_trigger_velocity(ball);
|
||||
}
|
||||
}
|
||||
@@ -1803,9 +1810,9 @@ impl Game {
|
||||
if !entered {
|
||||
continue;
|
||||
}
|
||||
self.add_score(sensor.score, events);
|
||||
events.push(Event::Target);
|
||||
events.push(Event::Sound(2004));
|
||||
self.add_score(sensor.score, events);
|
||||
self.record_countdowns[usize::from(sensor.id)] = if sensor.id <= 147 { 20 } else { 10 };
|
||||
if (150..=152).contains(&sensor.id) {
|
||||
self.top_targets[usize::from(sensor.id - 150)] = true;
|
||||
@@ -2014,7 +2021,7 @@ impl Game {
|
||||
let player = &mut self.players[self.current_player];
|
||||
player.score = player.score.wrapping_add(points.wrapping_mul(multiplier));
|
||||
let level = usize::from(player.media_level);
|
||||
if level < THRESHOLDS.len() && player.score >= THRESHOLDS[level] {
|
||||
if level < THRESHOLDS.len() && score_reaches_marker(player.score, THRESHOLDS[level]) {
|
||||
player.media_level += 1;
|
||||
player.extra_balls = player.extra_balls.wrapping_add(1);
|
||||
events.push(Event::Media);
|
||||
@@ -2621,6 +2628,68 @@ mod tests {
|
||||
.collect::<Vec<_>>(),
|
||||
[2007; 4]
|
||||
);
|
||||
|
||||
let mut wrapped = Game::new(1);
|
||||
wrapped.players[0].score = 0x8000_0000;
|
||||
let mut wrapped_events = Vec::new();
|
||||
wrapped.add_score(0, &mut wrapped_events);
|
||||
assert_eq!(wrapped.player().media_level, 0);
|
||||
assert_eq!(wrapped.player().extra_balls, 0);
|
||||
assert!(wrapped_events.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn type_four_hit_sound_precedes_a_crossed_media_marker() {
|
||||
let mut target_game = Game::new_with_seed(1, 7);
|
||||
target_game.players[0].score = 139_500;
|
||||
let sensor = TARGET_SENSORS
|
||||
.into_iter()
|
||||
.find(|sensor| sensor.id == 150)
|
||||
.expect("record 150 must be present");
|
||||
let mut target_ball = target_game.ball;
|
||||
target_ball.position = sensor.center;
|
||||
let mut target_events = Vec::new();
|
||||
target_game.check_target_sensors(
|
||||
&mut target_ball,
|
||||
MilliVec::from_position(sensor.center),
|
||||
MilliVec::default(),
|
||||
150..=152,
|
||||
&mut target_events,
|
||||
);
|
||||
assert_eq!(
|
||||
target_events,
|
||||
[
|
||||
Event::Target,
|
||||
Event::Sound(2004),
|
||||
Event::Media,
|
||||
Event::ExtraBall,
|
||||
Event::Sound(2007),
|
||||
]
|
||||
);
|
||||
|
||||
let mut effect_game = Game::new_with_seed(1, 7);
|
||||
effect_game.players[0].score = 139_500;
|
||||
effect_game.begin_effect_scenario(1);
|
||||
let mut effect_ball = effect_game.ball;
|
||||
effect_ball.position = EFFECT_SENSOR.center;
|
||||
let mut effect_events = Vec::new();
|
||||
effect_game.check_effect_sensor(
|
||||
&mut effect_ball,
|
||||
1,
|
||||
MilliVec::from_position(EFFECT_SENSOR.center),
|
||||
MilliVec::default(),
|
||||
&mut effect_events,
|
||||
);
|
||||
assert_eq!(
|
||||
effect_events,
|
||||
[
|
||||
Event::Target,
|
||||
Event::Sound(2004),
|
||||
Event::Media,
|
||||
Event::ExtraBall,
|
||||
Event::Sound(2007),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
Reference in New Issue
Block a user