fix(game): restore deliberate plunger charging

Advance the recovered eleven-frame plunger over a full one-second hold and use that same frame selection for rendering. This makes a short tap visibly and physically weaker while a complete pull still clears the shooter lane.

Test Plan:
- cargo fmt --check
- cargo test
- cargo clippy --all-targets --all-features -- -D warnings
- git diff --check
This commit is contained in:
2026-08-22 18:22:24 +02:00
parent 4ef563361b
commit 8d76e9539f
2 changed files with 45 additions and 7 deletions
+6 -3
View File
@@ -438,8 +438,6 @@ impl App {
}
}
if game.ball.in_launcher {
let frame = [0.05, 0.15, 0.25, 0.35, 0.45, 0.55, 0.65, 0.75, 0.85, 0.95]
.partition_point(|threshold| game.launcher_charge >= *threshold);
draw_texture_ex(
&self.assets.plunger,
314.0,
@@ -447,7 +445,12 @@ impl App {
WHITE,
DrawTextureParams {
dest_size: Some(vec2(21.0, 18.0)),
source: Some(Rect::new(frame as f32 * 21.0, 0.0, 20.0, 17.0)),
source: Some(Rect::new(
game.launcher_frame() as f32 * 21.0,
0.0,
20.0,
17.0,
)),
..Default::default()
},
);
+39 -4
View File
@@ -22,7 +22,12 @@ const BALL_SEARCH_DELAY: f32 = 3.0;
const BALL_SEARCH_SPEED: f32 = 24.0;
const TILT_THRESHOLD: f32 = 1.15;
const LAUNCHER_POSITION: Vec2 = Vec2::new(325.0, 413.0);
const LAUNCHER_CHARGE_SECONDS: f32 = 0.55;
const LAUNCHER_FRAME_THRESHOLDS: [f32; 10] =
[0.05, 0.15, 0.25, 0.35, 0.45, 0.55, 0.65, 0.75, 0.85, 0.95];
// The original advances resource 901 through repeated Down-key events. A
// fixed duration keeps that deliberate hold consistent across modern systems
// whose keyboard-repeat delay and rate differ.
const LAUNCHER_CHARGE_SECONDS: f32 = 1.0;
const LAUNCH_SPEED_MIN: f32 = 330.0;
const LAUNCH_SPEED_RANGE: f32 = 100.0;
@@ -159,6 +164,10 @@ impl Game {
&self.players[self.current_player]
}
pub fn launcher_frame(&self) -> usize {
LAUNCHER_FRAME_THRESHOLDS.partition_point(|threshold| self.launcher_charge >= *threshold)
}
fn fire_launcher(&mut self) {
let launch_speed = LAUNCH_SPEED_MIN + self.launcher_charge * LAUNCH_SPEED_RANGE;
self.ball.in_launcher = false;
@@ -185,7 +194,12 @@ impl Game {
if self.ball.in_launcher && !self.tilted {
if controls.launch_down {
let charge_step = frame_time.min(0.05) / LAUNCHER_CHARGE_SECONDS;
self.launcher_charge = (self.launcher_charge + charge_step).min(1.0);
let next_charge = self.launcher_charge + charge_step;
self.launcher_charge = if next_charge >= 1.0 - f32::EPSILON * 4.0 {
1.0
} else {
next_charge
};
self.launcher_was_down = true;
} else if self.launcher_was_down {
self.fire_launcher();
@@ -649,17 +663,38 @@ mod tests {
let quick_speed = -quick.ball.velocity.y;
let mut charged = Game::new(1);
launch_ball(&mut charged, 33);
launch_ball(&mut charged, 60);
let charged_speed = -charged.ball.velocity.y;
assert!(charged_speed > quick_speed + 80.0);
}
#[test]
fn maximum_launch_requires_a_deliberate_hold() {
let mut game = Game::new(1);
let held = Controls {
launch_down: true,
..Controls::default()
};
for _ in 0..30 {
game.update(1.0 / 60.0, 3, held);
}
assert!((0.45..0.55).contains(&game.launcher_charge));
assert_eq!(game.launcher_frame(), 5);
for _ in 0..30 {
game.update(1.0 / 60.0, 3, held);
}
assert!((game.launcher_charge - 1.0).abs() <= f32::EPSILON);
assert_eq!(game.launcher_frame(), 10);
}
#[test]
fn charged_ball_clears_the_shooter_lane() {
for detail in 1..=5 {
let mut game = Game::new(1);
launch_ball(&mut game, 33);
launch_ball(&mut game, 60);
let mut entered_table = false;
let mut returned_to_launcher = false;
let mut minimum_y = game.ball.position.y;