fix(game): restore one-shot magnetic fields

Replace the global timed magnet and drain teleport with the three recovered
field records. Map top targets 150-152 to center field 6 and outlane fields
153-154, apply upward pull only inside each registered rectangle, and deactivate
the field after it pulls the ball through its upper boundary.

Render each active field from its exact original active-table rectangle instead
of drawing invented yellow triangles.

Test Plan:
- `cargo test --all-targets` -- 48 passed
- `cargo clippy --all-targets -- -D warnings` -- passed
- production build plus Windows GNU and macOS checks -- passed
- live field-153 entry direction matched and Rust field lifecycle test passed
- `git diff --cached --check` -- passed
This commit is contained in:
2026-08-22 21:43:13 +02:00
parent b86810ebed
commit 4b56a23fb4
4 changed files with 81 additions and 29 deletions
+16 -13
View File
@@ -410,19 +410,22 @@ impl App {
let index = usize::from(game.player().diamond_segments.min(9) - 1);
draw_texture(&self.assets.diamond[index], 123.0, 298.0, WHITE);
}
if game.magnets > 0.0 {
for x in [18.0, 296.0] {
draw_triangle(
vec2(x, 345.0),
vec2(x - 7.0, 359.0),
vec2(x + 7.0, 359.0),
YELLOW,
);
draw_triangle(
vec2(x, 367.0),
vec2(x - 7.0, 381.0),
vec2(x + 7.0, 381.0),
YELLOW,
for (object_id, region) in [
(6, Rect::new(151.0, 389.0, 13.0, 49.0)),
(153, Rect::new(11.0, 344.0, 13.0, 49.0)),
(154, Rect::new(291.0, 346.0, 13.0, 49.0)),
] {
if game.magnetic_field_active(object_id) {
draw_texture_ex(
&self.assets.active_table,
region.x,
region.y,
WHITE,
DrawTextureParams {
dest_size: Some(vec2(region.w, region.h)),
source: Some(region),
..Default::default()
},
);
}
}
+58 -15
View File
@@ -199,7 +199,6 @@ pub struct Game {
pub bonus: u32,
pub wheel_holes: [bool; 5],
pub top_targets: [bool; 3],
pub magnets: f32,
pub tilted: bool,
pub flippers: Flippers,
pub claw: Claw,
@@ -237,7 +236,6 @@ impl Game {
bonus: 0,
wheel_holes: [false; 5],
top_targets: [false; 3],
magnets: 0.0,
tilted: false,
flippers: Flippers::default(),
claw: Claw::default(),
@@ -381,7 +379,6 @@ impl Game {
fn fixed_update(&mut self, dt: f32, events: &mut Vec<Event>) {
self.bumper_cooldown = (self.bumper_cooldown - dt).max(0.0);
self.nudge_cooldown = (self.nudge_cooldown - dt).max(0.0);
self.magnets = (self.magnets - dt).max(0.0);
self.wheel_animation = (self.wheel_animation - dt).max(0.0);
self.nudge_shake = (self.nudge_shake - dt).max(0.0);
for flash in &mut self.bumper_flash {
@@ -407,6 +404,7 @@ impl Game {
let mut velocity = MilliVec::from_velocity_per_second(self.ball.velocity);
velocity.y += GRAVITY_MILLI_PER_STEP;
velocity.clamp_speed(MAXIMUM_SPEED_MILLI_PER_STEP);
self.apply_magnetic_fields(old_position, &mut velocity);
let movement_velocity = velocity;
let mut position = old_position.add(velocity);
let mut best_collision: Option<(u8, bool, CollisionResponse)> = None;
@@ -468,15 +466,7 @@ impl Game {
if let Some(wall_id) = hit_wall {
if wall_id == 2 {
if self.magnets > 0.0
&& (self.ball.position.x < 145.0 || self.ball.position.x > 175.0)
{
self.ball.position.y = 410.0;
self.ball.velocity = vec2((157.0 - self.ball.position.x) * 2.0, -245.0);
self.magnets = 0.0;
} else {
self.drain(events);
}
self.drain(events);
return;
}
if wall_id == 25
@@ -691,7 +681,8 @@ impl Game {
events.push(Event::Target);
if (150..=152).contains(&sensor.id) {
self.top_targets[usize::from(sensor.id - 150)] = true;
self.magnets = self.magnets.max(0.3);
let field_id = [153, 6, 154][usize::from(sensor.id - 150)];
self.object_active[field_id] = true;
}
}
}
@@ -705,6 +696,39 @@ impl Game {
self.launcher_velocity_milli = 0;
}
fn apply_magnetic_fields(&mut self, old_position: MilliVec, velocity: &mut MilliVec) {
for (object_id, min_x, min_y, max_x, max_y) in [
(6, 143_000, 421_000, 169_000, 452_000),
(153, 1_000, 315_000, 32_000, 389_000),
(154, 278_000, 315_000, 312_000, 387_000),
] {
if !self.object_active[object_id] {
continue;
}
let predicted = old_position.add(*velocity);
let sweep_min_x = old_position.x.min(predicted.x);
let sweep_max_x = old_position.x.max(predicted.x);
let sweep_min_y = old_position.y.min(predicted.y);
let sweep_max_y = old_position.y.max(predicted.y);
if sweep_max_x < min_x
|| sweep_min_x > max_x
|| sweep_max_y < min_y
|| sweep_min_y > max_y
{
continue;
}
velocity.y = velocity.y.min(-3_000);
if old_position.add(*velocity).y < min_y {
self.object_active[object_id] = false;
}
}
}
pub fn magnetic_field_active(&self, object_id: usize) -> bool {
debug_assert!([6, 153, 154].contains(&object_id));
self.object_active[object_id]
}
fn next_claw_terminal_frame(&mut self) -> u8 {
let value = self.next_random_value();
CLAW_TERMINAL_FRAMES[value as usize % CLAW_TERMINAL_FRAMES.len()]
@@ -844,7 +868,6 @@ impl Game {
self.bonus = 0;
self.top_targets.fill(false);
self.wheel_holes.fill(false);
self.magnets = 0.0;
self.tilted = false;
self.nudge_meter = 0.0;
self.bumper_flash.fill(0.0);
@@ -1449,7 +1472,27 @@ mod tests {
);
assert_eq!(game.player().score, 500);
assert_eq!(events, [Event::Target]);
assert!((game.magnets - 0.3).abs() < f32::EPSILON);
assert!(game.object_active[153]);
assert!(!game.object_active[6]);
assert!(!game.object_active[154]);
let mut field_velocity = MilliVec { x: 0, y: 2_015 };
game.apply_magnetic_fields(
MilliVec {
x: 15_000,
y: 350_000,
},
&mut field_velocity,
);
assert_eq!(field_velocity, MilliVec { x: 0, y: -3_000 });
game.apply_magnetic_fields(
MilliVec {
x: 15_000,
y: 317_000,
},
&mut field_velocity,
);
assert!(!game.object_active[153]);
game.check_sensor_objects(
MilliVec::from_position(game.ball.position),