fix(input): restore multiball nudge slot updates

Preserve the Win16 key path's two-stage multiball behavior: consume the active-window random formula, then apply one separately recovered slot impulse to both saved ball velocities. This restores the original RNG advancement and prevents ball one and ball two from receiving invented different nudges.

Test Plan:
- cargo test --all-targets
- cargo clippy --all-targets --all-features -- -D warnings
- rumdl check CHANGELOG.md RECONSTRUCTION.md README.md
- git diff --check
This commit is contained in:
2026-08-23 18:16:09 +02:00
parent 3d5a105c10
commit 0661665374
3 changed files with 66 additions and 12 deletions
+62 -11
View File
@@ -622,24 +622,34 @@ impl Game {
}
Nudge::None => return,
}
self.ball.velocity = velocity.to_velocity_per_second();
if let Some(secondary) = &mut self.secondary_ball {
let mut slot_velocity = MilliVec::from_velocity_per_second(secondary.velocity);
match nudge {
let slot_impulse = match nudge {
Nudge::Left | Nudge::Right => {
let amount = if nudge == Nudge::Left { -600 } else { 600 };
slot_velocity.x = slot_velocity.x.wrapping_add(amount);
MilliVec {
x: if nudge == Nudge::Left { -600 } else { 600 },
y: 0,
}
}
Nudge::Center => {
let horizontal = (i32::from(self.random.below(21)) - 10) * SCALAR;
let vertical = -(i32::from(self.random.below(100)) + 50) * SCALAR;
slot_velocity.x = slot_velocity.x.wrapping_add(horizontal);
slot_velocity.y = slot_velocity.y.wrapping_add(vertical);
MilliVec {
x: horizontal,
y: vertical,
}
}
Nudge::None => {}
}
secondary.velocity = slot_velocity.to_velocity_per_second();
Nudge::None => MilliVec::default(),
};
let mut primary_slot = MilliVec::from_velocity_per_second(self.ball.velocity);
primary_slot.x = primary_slot.x.wrapping_add(slot_impulse.x);
primary_slot.y = primary_slot.y.wrapping_add(slot_impulse.y);
self.ball.velocity = primary_slot.to_velocity_per_second();
let mut secondary_slot = MilliVec::from_velocity_per_second(secondary.velocity);
secondary_slot.x = secondary_slot.x.wrapping_add(slot_impulse.x);
secondary_slot.y = secondary_slot.y.wrapping_add(slot_impulse.y);
secondary.velocity = secondary_slot.to_velocity_per_second();
} else {
self.ball.velocity = velocity.to_velocity_per_second();
}
self.nudge_shake = 0.18;
@@ -2690,6 +2700,47 @@ mod tests {
assert_eq!(game.tilt_counter, 24);
}
#[test]
fn multiball_nudge_consumes_active_draws_then_updates_both_saved_slots() {
let mut game = Game::new_with_seed(1, 7);
game.ball.in_launcher = false;
game.ball.velocity = MilliVec { x: 100, y: 200 }.to_velocity_per_second();
game.secondary_ball = Some(Ball {
position: vec2(200.0, 200.0),
velocity: MilliVec { x: -300, y: 400 }.to_velocity_per_second(),
in_launcher: false,
spin: Real48::ZERO,
capture_age: 0,
});
let mut reference = BorlandRandom::new(7);
let _discarded_active_x = reference.real48();
let _discarded_active_y = reference.below(20);
let shared_impulse = MilliVec {
x: (i32::from(reference.below(21)) - 10) * 15,
y: -(i32::from(reference.below(100)) + 50) * 15,
};
let _tilt_threshold = reference.below(10);
game.apply_nudge(Nudge::Center, &mut Vec::new());
assert_eq!(
MilliVec::from_velocity_per_second(game.ball.velocity),
MilliVec {
x: 100 + shared_impulse.x,
y: 200 + shared_impulse.y,
}
);
let secondary = game.secondary_ball.expect("slot two must remain active");
assert_eq!(
MilliVec::from_velocity_per_second(secondary.velocity),
MilliVec {
x: -300 + shared_impulse.x,
y: 400 + shared_impulse.y,
}
);
assert_eq!(game.random.seed(), reference.seed());
}
#[test]
fn claw_sprite_rects_follow_the_recovered_four_row_sheet() {
let source = |frame, bank| {