fix(game): restore the recovered collision table
Replace the mirrored, partial wall transcription with all active static line and circle objects from the Win16 registration table. Preserve the original object IDs for drain and shooter-stop handling, and use bounded physics substeps so fast balls cannot skip thin rails. Test Plan: - cargo fmt --all -- --check - cargo test - cargo clippy --all-targets --all-features -- -D warnings - git diff --check
This commit is contained in:
@@ -22,7 +22,7 @@ implementation.
|
|||||||
| Artwork | Exact | All 34 custom DIB images, three standard bitmaps, icon, and palette derivatives are preserved in `assets/original/`. The game uses the original 640x460 table, loading, help, ball, wheel, robot, magnet, media, and diamond frames. |
|
| Artwork | Exact | All 34 custom DIB images, three standard bitmaps, icon, and palette derivatives are preserved in `assets/original/`. The game uses the original 640x460 table, loading, help, ball, wheel, robot, magnet, media, and diamond frames. |
|
||||||
| Audio | Exact samples | All 16 mono PCM WAV resources are embedded unchanged. Their trigger roles were recovered from resource use and gameplay context. |
|
| Audio | Exact samples | All 16 mono PCM WAV resources are embedded unchanged. Their trigger roles were recovered from resource use and gameplay context. |
|
||||||
| Help and languages | Exact | Original resource images 1001-1005 are displayed directly. |
|
| Help and languages | Exact | Original resource images 1001-1005 are displayed directly. |
|
||||||
| Playfield collision layout | Recovered | The active static line segments are transcribed from the original 175-object registration table. The original sideways coordinates are transformed with `screen = (y, 478 - x)`. The lower apron is split around the visible center drain; moving flippers and round bumpers use equivalent native Rust bodies. |
|
| Playfield collision layout | Recovered | All 109 active type-2 line objects and 40 static active type-1 circles are transcribed from the original 175-object registration table. The registration routine converts its sideways inputs with `screen = (y, x - 20)` and accumulates explicitly relative objects. Object 174 is omitted because the original overwrites it with the live ball every frame. Moving flippers use equivalent native Rust bodies. |
|
||||||
| Physics arithmetic | Reimplemented | The Win16 fixed-point/timer engine is replaced by deterministic fixed-step floating-point integration. Restitution and impulses are tuned to the recovered table but are not instruction-for-instruction equivalents. |
|
| Physics arithmetic | Reimplemented | The Win16 fixed-point/timer engine is replaced by deterministic fixed-step floating-point integration. Restitution and impulses are tuned to the recovered table but are not instruction-for-instruction equivalents. |
|
||||||
| Rules | Behaviorally recovered | Player count, controls, wheel holes, magnetic saves, robot grip, four-position ball lock, target banks, increasing bumper value, nine-part TDK diamond, permanent double scoring for a completed diamond, KByte media progression, and media extra balls follow the original help and code paths. |
|
| Rules | Behaviorally recovered | Player count, controls, wheel holes, magnetic saves, robot grip, four-position ball lock, target banks, increasing bumper value, nine-part TDK diamond, permanent double scoring for a completed diamond, KByte media progression, and media extra balls follow the original help and code paths. |
|
||||||
| Numeric scoring | Partly inferred | Visible 2000-6000 target values and recovered registration values are preserved. Some bumper, bank-completion, robot, wheel, lock, and media thresholds are best-evidence reconstructions because the decompiler did not recover meaningful names or a clean rule table. |
|
| Numeric scoring | Partly inferred | Visible 2000-6000 target values and recovered registration values are preserved. Some bumper, bank-completion, robot, wheel, lock, and media thresholds are best-evidence reconstructions because the decompiler did not recover meaningful names or a clean rule table. |
|
||||||
@@ -66,4 +66,5 @@ decoded, build-ready subset; it does not replace that evidence archive.
|
|||||||
- `assets.rs`: compile-time original asset embedding;
|
- `assets.rs`: compile-time original asset embedding;
|
||||||
- `game.rs`: fixed-step game state, recovered walls, rules, and scoring;
|
- `game.rs`: fixed-step game state, recovered walls, rules, and scoring;
|
||||||
- `geometry.rs`: segment/circle collision primitives;
|
- `geometry.rs`: segment/circle collision primitives;
|
||||||
|
- `table.rs`: source-traceable collision objects recovered from the Win16 table;
|
||||||
- `persistence.rs`: platform paths, settings, high scores, and legacy import.
|
- `persistence.rs`: platform paths, settings, high scores, and legacy import.
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ use crate::{
|
|||||||
assets::Assets,
|
assets::Assets,
|
||||||
game::{Controls, Event, Game},
|
game::{Controls, Event, Game},
|
||||||
persistence::{HighScore, Language, Persistence, SavedData, insert_high_score},
|
persistence::{HighScore, Language, Persistence, SavedData, insert_high_score},
|
||||||
|
table::BUMPERS,
|
||||||
};
|
};
|
||||||
use macroquad::prelude::*;
|
use macroquad::prelude::*;
|
||||||
|
|
||||||
@@ -460,13 +461,10 @@ impl App {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
for (index, center) in [vec2(207.0, 109.0), vec2(167.0, 148.0), vec2(219.0, 167.0)]
|
for (index, bumper) in BUMPERS.into_iter().enumerate() {
|
||||||
.into_iter()
|
|
||||||
.enumerate()
|
|
||||||
{
|
|
||||||
if game.bumper_flash[index] > 0.0 {
|
if game.bumper_flash[index] > 0.0 {
|
||||||
draw_circle_lines(center.x, center.y, 17.0, 3.0, WHITE);
|
draw_circle_lines(bumper.center.x, bumper.center.y, 17.0, 3.0, WHITE);
|
||||||
draw_circle_lines(center.x, center.y, 13.0, 2.0, YELLOW);
|
draw_circle_lines(bumper.center.x, bumper.center.y, 13.0, 2.0, YELLOW);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if game.player().diamond_segments > 0 {
|
if game.player().diamond_segments > 0 {
|
||||||
@@ -503,8 +501,8 @@ impl App {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (start, end) in [
|
for (start, end) in [
|
||||||
(vec2(103.0, 394.0), vec2(58.0, 374.0)),
|
(vec2(103.0, 397.0), vec2(58.0, 374.0)),
|
||||||
(vec2(211.0, 394.0), vec2(256.0, 374.0)),
|
(vec2(210.0, 397.0), vec2(256.0, 374.0)),
|
||||||
] {
|
] {
|
||||||
draw_line(start.x, start.y, end.x, end.y, 22.0, BLACK);
|
draw_line(start.x, start.y, end.x, end.y, 22.0, BLACK);
|
||||||
draw_circle(start.x, start.y, 11.0, BLACK);
|
draw_circle(start.x, start.y, 11.0, BLACK);
|
||||||
|
|||||||
+102
-171
@@ -1,6 +1,8 @@
|
|||||||
use crate::geometry::{Segment, circle_collision, segment_collision};
|
use crate::{
|
||||||
|
geometry::{Segment, circle_collision, segment_collision},
|
||||||
|
table::{BUMPERS, PASSIVE_CIRCLES, WALLS},
|
||||||
|
};
|
||||||
use macroquad::prelude::{Vec2, vec2};
|
use macroquad::prelude::{Vec2, vec2};
|
||||||
use std::sync::LazyLock;
|
|
||||||
|
|
||||||
const BALL_RADIUS: f32 = 7.0;
|
const BALL_RADIUS: f32 = 7.0;
|
||||||
const GRAVITY: f32 = 135.0;
|
const GRAVITY: f32 = 135.0;
|
||||||
@@ -228,59 +230,91 @@ impl Game {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
self.ball.velocity.y += GRAVITY * dt;
|
let travel = self.ball.velocity.length() * dt;
|
||||||
self.ball.velocity *= 1.0 - dt * 0.055;
|
let maximum_step_travel = BALL_RADIUS * 0.45;
|
||||||
self.ball.velocity = self.ball.velocity.clamp_length_max(MAX_SPEED);
|
let mut substeps = 1_u8;
|
||||||
self.ball.position += self.ball.velocity * dt;
|
while f32::from(substeps) * maximum_step_travel < travel && substeps < 12 {
|
||||||
|
substeps += 1;
|
||||||
|
}
|
||||||
|
let substep = dt / f32::from(substeps);
|
||||||
|
for _ in 0..substeps {
|
||||||
|
self.ball.velocity.y += GRAVITY * substep;
|
||||||
|
self.ball.velocity *= 1.0 - substep * 0.055;
|
||||||
|
self.ball.velocity = self.ball.velocity.clamp_length_max(MAX_SPEED);
|
||||||
|
self.ball.position += self.ball.velocity * substep;
|
||||||
|
|
||||||
for wall in table_walls() {
|
let mut drained = false;
|
||||||
segment_collision(
|
for wall in WALLS {
|
||||||
|
// Object 25 is the one-way shooter stop. It catches a returning
|
||||||
|
// ball, but must not reflect an upward launch from the line.
|
||||||
|
if wall.id == 25 && self.ball.velocity.y < 0.0 {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let hit = segment_collision(
|
||||||
|
&mut self.ball.position,
|
||||||
|
&mut self.ball.velocity,
|
||||||
|
BALL_RADIUS,
|
||||||
|
wall.segment,
|
||||||
|
);
|
||||||
|
if hit && wall.id == 2 {
|
||||||
|
drained = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if drained {
|
||||||
|
self.drain(events);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for circle in PASSIVE_CIRCLES {
|
||||||
|
debug_assert_ne!(circle.id, 174, "the live ball is not a static obstacle");
|
||||||
|
circle_collision(
|
||||||
|
&mut self.ball.position,
|
||||||
|
&mut self.ball.velocity,
|
||||||
|
BALL_RADIUS,
|
||||||
|
circle.center,
|
||||||
|
circle.radius,
|
||||||
|
0.0,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let left_flipper = flipper_segment(vec2(103.0, 397.0), self.left_flipper_angle);
|
||||||
|
let right_flipper = flipper_segment(vec2(210.0, 397.0), self.right_flipper_angle);
|
||||||
|
if segment_collision(
|
||||||
&mut self.ball.position,
|
&mut self.ball.position,
|
||||||
&mut self.ball.velocity,
|
&mut self.ball.velocity,
|
||||||
BALL_RADIUS,
|
BALL_RADIUS + 2.0,
|
||||||
*wall,
|
left_flipper,
|
||||||
);
|
) && self.left_flipper_angle > -2.55
|
||||||
}
|
{
|
||||||
|
self.ball.velocity += vec2(-20.0, -115.0);
|
||||||
let left_flipper = flipper_segment(vec2(103.0, 394.0), self.left_flipper_angle);
|
}
|
||||||
let right_flipper = flipper_segment(vec2(211.0, 394.0), self.right_flipper_angle);
|
if segment_collision(
|
||||||
if segment_collision(
|
|
||||||
&mut self.ball.position,
|
|
||||||
&mut self.ball.velocity,
|
|
||||||
BALL_RADIUS + 2.0,
|
|
||||||
left_flipper,
|
|
||||||
) && self.left_flipper_angle > -2.55
|
|
||||||
{
|
|
||||||
self.ball.velocity += vec2(-20.0, -115.0);
|
|
||||||
}
|
|
||||||
if segment_collision(
|
|
||||||
&mut self.ball.position,
|
|
||||||
&mut self.ball.velocity,
|
|
||||||
BALL_RADIUS + 2.0,
|
|
||||||
right_flipper,
|
|
||||||
) && self.right_flipper_angle < -0.58
|
|
||||||
{
|
|
||||||
self.ball.velocity += vec2(20.0, -115.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (index, center) in [vec2(207.0, 109.0), vec2(167.0, 148.0), vec2(219.0, 167.0)]
|
|
||||||
.into_iter()
|
|
||||||
.enumerate()
|
|
||||||
{
|
|
||||||
let hit = circle_collision(
|
|
||||||
&mut self.ball.position,
|
&mut self.ball.position,
|
||||||
&mut self.ball.velocity,
|
&mut self.ball.velocity,
|
||||||
BALL_RADIUS,
|
BALL_RADIUS + 2.0,
|
||||||
center,
|
right_flipper,
|
||||||
14.0,
|
) && self.right_flipper_angle < -0.58
|
||||||
105.0,
|
{
|
||||||
);
|
self.ball.velocity += vec2(20.0, -115.0);
|
||||||
if hit && !self.tilted && self.bumper_cooldown <= 0.0 {
|
}
|
||||||
self.add_score(self.player().bumper_value);
|
|
||||||
self.bonus = self.bonus.saturating_add(100);
|
for (index, bumper) in BUMPERS.into_iter().enumerate() {
|
||||||
self.bumper_cooldown = 0.08;
|
let hit = circle_collision(
|
||||||
self.bumper_flash[index] = 0.16;
|
&mut self.ball.position,
|
||||||
events.push(Event::Bumper);
|
&mut self.ball.velocity,
|
||||||
|
BALL_RADIUS,
|
||||||
|
bumper.center,
|
||||||
|
bumper.radius,
|
||||||
|
105.0,
|
||||||
|
);
|
||||||
|
if hit && !self.tilted && self.bumper_cooldown <= 0.0 {
|
||||||
|
self.add_score(self.player().bumper_value);
|
||||||
|
self.bonus = self.bonus.saturating_add(100);
|
||||||
|
self.bumper_cooldown = 0.08;
|
||||||
|
self.bumper_flash[index] = 0.16;
|
||||||
|
events.push(Event::Bumper);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -487,8 +521,8 @@ impl Game {
|
|||||||
|
|
||||||
pub fn flippers(&self) -> (Segment, Segment) {
|
pub fn flippers(&self) -> (Segment, Segment) {
|
||||||
(
|
(
|
||||||
flipper_segment(vec2(103.0, 394.0), self.left_flipper_angle),
|
flipper_segment(vec2(103.0, 397.0), self.left_flipper_angle),
|
||||||
flipper_segment(vec2(211.0, 394.0), self.right_flipper_angle),
|
flipper_segment(vec2(210.0, 397.0), self.right_flipper_angle),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -498,117 +532,6 @@ fn flipper_segment(pivot: Vec2, angle: f32) -> Segment {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::too_many_lines)]
|
#[allow(clippy::too_many_lines)]
|
||||||
fn table_walls() -> &'static [Segment] {
|
|
||||||
static WALLS: LazyLock<Vec<Segment>> = LazyLock::new(|| {
|
|
||||||
vec![
|
|
||||||
Segment::new(Vec2::new(125.0, 21.0), Vec2::new(7.0, 61.0), 0.82),
|
|
||||||
Segment::new(Vec2::new(300.0, 3.0), Vec2::new(10.0, 3.0), 0.82),
|
|
||||||
Segment::new(Vec2::new(120.0, 6.0), Vec2::new(120.0, 26.0), 0.82),
|
|
||||||
Segment::new(Vec2::new(195.0, 26.0), Vec2::new(195.0, 6.0), 0.82),
|
|
||||||
Segment::new(Vec2::new(134.0, 33.0), Vec2::new(134.0, 16.0), 0.82),
|
|
||||||
Segment::new(Vec2::new(148.0, 11.0), Vec2::new(148.0, 33.0), 0.82),
|
|
||||||
Segment::new(Vec2::new(164.0, 12.0), Vec2::new(165.0, 33.0), 0.82),
|
|
||||||
Segment::new(Vec2::new(180.0, 11.0), Vec2::new(179.0, 33.0), 0.82),
|
|
||||||
Segment::new(Vec2::new(304.0, 64.0), Vec2::new(183.0, 21.0), 0.82),
|
|
||||||
Segment::new(Vec2::new(298.0, 417.0), Vec2::new(298.0, 313.0), 0.82),
|
|
||||||
Segment::new(Vec2::new(298.0, 336.0), Vec2::new(310.0, 336.0), 0.82),
|
|
||||||
Segment::new(Vec2::new(292.0, 374.0), Vec2::new(291.0, 342.0), 0.82),
|
|
||||||
Segment::new(Vec2::new(281.0, 378.0), Vec2::new(300.0, 369.0), 0.82),
|
|
||||||
Segment::new(Vec2::new(275.0, 343.0), Vec2::new(290.0, 382.0), 0.82),
|
|
||||||
Segment::new(Vec2::new(248.0, 364.0), Vec2::new(264.0, 339.0), 0.82),
|
|
||||||
Segment::new(Vec2::new(257.0, 387.0), Vec2::new(249.0, 364.0), 0.82),
|
|
||||||
Segment::new(Vec2::new(277.0, 429.0), Vec2::new(257.0, 387.0), 0.82),
|
|
||||||
Segment::new(Vec2::new(287.0, 446.0), Vec2::new(277.0, 427.0), 0.82),
|
|
||||||
Segment::new(Vec2::new(303.0, 430.0), Vec2::new(283.0, 436.0), 0.82),
|
|
||||||
Segment::new(Vec2::new(320.0, 410.0), Vec2::new(302.0, 430.0), 0.82),
|
|
||||||
Segment::new(Vec2::new(320.0, 21.0), Vec2::new(320.0, 410.0), 0.82),
|
|
||||||
Segment::new(Vec2::new(307.0, 437.0), Vec2::new(316.0, 430.0), 0.82),
|
|
||||||
Segment::new(Vec2::new(296.0, 442.0), Vec2::new(307.0, 437.0), 0.82),
|
|
||||||
Segment::new(Vec2::new(285.0, 443.0), Vec2::new(296.0, 442.0), 0.82),
|
|
||||||
// Preserve the recovered lower apron on either side of the center drain.
|
|
||||||
Segment::new(Vec2::new(112.0, 443.0), Vec2::new(141.0, 443.0), 0.82),
|
|
||||||
Segment::new(Vec2::new(173.0, 443.0), Vec2::new(285.0, 443.0), 0.82),
|
|
||||||
Segment::new(Vec2::new(99.0, 438.0), Vec2::new(121.0, 444.0), 0.82),
|
|
||||||
Segment::new(Vec2::new(78.0, 427.0), Vec2::new(100.0, 438.0), 0.82),
|
|
||||||
Segment::new(Vec2::new(66.0, 409.0), Vec2::new(79.0, 428.0), 0.82),
|
|
||||||
Segment::new(Vec2::new(57.0, 385.0), Vec2::new(68.0, 413.0), 0.82),
|
|
||||||
Segment::new(Vec2::new(59.0, 304.0), Vec2::new(59.0, 396.0), 0.82),
|
|
||||||
Segment::new(Vec2::new(67.0, 288.0), Vec2::new(58.0, 311.0), 0.82),
|
|
||||||
Segment::new(Vec2::new(41.0, 268.0), Vec2::new(64.0, 279.0), 0.82),
|
|
||||||
Segment::new(Vec2::new(22.0, 293.0), Vec2::new(31.0, 272.0), 0.82),
|
|
||||||
Segment::new(Vec2::new(22.0, 447.0), Vec2::new(22.0, 293.0), 0.82),
|
|
||||||
Segment::new(Vec2::new(11.0, 445.0), Vec2::new(23.0, 445.0), 0.82),
|
|
||||||
Segment::new(Vec2::new(13.0, 288.0), Vec2::new(13.0, 448.0), 0.82),
|
|
||||||
Segment::new(Vec2::new(32.0, 228.0), Vec2::new(12.0, 293.0), 0.82),
|
|
||||||
Segment::new(Vec2::new(10.0, 158.0), Vec2::new(33.0, 227.0), 0.82),
|
|
||||||
Segment::new(Vec2::new(12.0, 51.0), Vec2::new(12.0, 197.0), 0.82),
|
|
||||||
Segment::new(Vec2::new(37.0, 111.0), Vec2::new(37.0, 166.0), 0.82),
|
|
||||||
Segment::new(Vec2::new(110.0, 74.0), Vec2::new(37.0, 111.0), 0.82),
|
|
||||||
Segment::new(Vec2::new(19.0, 73.0), Vec2::new(92.0, 50.0), 0.82),
|
|
||||||
Segment::new(Vec2::new(19.0, 167.0), Vec2::new(19.0, 73.0), 0.82),
|
|
||||||
Segment::new(Vec2::new(141.0, 45.0), Vec2::new(112.0, 72.0), 0.82),
|
|
||||||
Segment::new(Vec2::new(97.0, 47.0), Vec2::new(130.0, 31.0), 0.82),
|
|
||||||
Segment::new(Vec2::new(0.0, 433.0), Vec2::new(7.0, 478.0), 0.82),
|
|
||||||
Segment::new(Vec2::new(292.0, 73.0), Vec2::new(292.0, 167.0), 0.82),
|
|
||||||
Segment::new(Vec2::new(221.0, 50.0), Vec2::new(292.0, 73.0), 0.82),
|
|
||||||
Segment::new(Vec2::new(275.0, 111.0), Vec2::new(205.0, 74.0), 0.82),
|
|
||||||
Segment::new(Vec2::new(275.0, 166.0), Vec2::new(275.0, 111.0), 0.82),
|
|
||||||
Segment::new(Vec2::new(177.0, 31.0), Vec2::new(216.0, 47.0), 0.82),
|
|
||||||
Segment::new(Vec2::new(201.0, 72.0), Vec2::new(172.0, 45.0), 0.82),
|
|
||||||
Segment::new(Vec2::new(40.0, 257.0), Vec2::new(73.0, 273.0), 0.82),
|
|
||||||
Segment::new(Vec2::new(73.0, 273.0), Vec2::new(67.0, 285.0), 0.82),
|
|
||||||
Segment::new(Vec2::new(34.0, 269.0), Vec2::new(40.0, 257.0), 0.82),
|
|
||||||
Segment::new(Vec2::new(292.0, 231.0), Vec2::new(292.0, 205.0), 0.82),
|
|
||||||
Segment::new(Vec2::new(292.0, 205.0), Vec2::new(302.0, 205.0), 0.82),
|
|
||||||
Segment::new(Vec2::new(302.0, 231.0), Vec2::new(292.0, 231.0), 0.82),
|
|
||||||
Segment::new(Vec2::new(292.0, 247.0), Vec2::new(292.0, 222.0), 0.82),
|
|
||||||
Segment::new(Vec2::new(292.0, 222.0), Vec2::new(302.0, 222.0), 0.82),
|
|
||||||
Segment::new(Vec2::new(302.0, 247.0), Vec2::new(292.0, 247.0), 0.82),
|
|
||||||
Segment::new(Vec2::new(292.0, 261.0), Vec2::new(292.0, 237.0), 0.82),
|
|
||||||
Segment::new(Vec2::new(292.0, 237.0), Vec2::new(302.0, 237.0), 0.82),
|
|
||||||
Segment::new(Vec2::new(302.0, 261.0), Vec2::new(292.0, 261.0), 0.82),
|
|
||||||
Segment::new(Vec2::new(292.0, 277.0), Vec2::new(292.0, 252.0), 0.82),
|
|
||||||
Segment::new(Vec2::new(292.0, 252.0), Vec2::new(302.0, 252.0), 0.82),
|
|
||||||
Segment::new(Vec2::new(302.0, 276.0), Vec2::new(292.0, 276.0), 0.82),
|
|
||||||
Segment::new(Vec2::new(292.0, 291.0), Vec2::new(292.0, 268.0), 0.82),
|
|
||||||
Segment::new(Vec2::new(292.0, 269.0), Vec2::new(302.0, 268.0), 0.82),
|
|
||||||
Segment::new(Vec2::new(302.0, 295.0), Vec2::new(292.0, 290.0), 0.82),
|
|
||||||
Segment::new(Vec2::new(148.0, 254.0), Vec2::new(203.0, 236.0), 0.82),
|
|
||||||
Segment::new(Vec2::new(215.0, 261.0), Vec2::new(158.0, 280.0), 0.82),
|
|
||||||
Segment::new(Vec2::new(187.0, 232.0), Vec2::new(209.0, 225.0), 0.82),
|
|
||||||
Segment::new(Vec2::new(209.0, 225.0), Vec2::new(214.0, 240.0), 0.82),
|
|
||||||
Segment::new(Vec2::new(191.0, 244.0), Vec2::new(187.0, 232.0), 0.82),
|
|
||||||
Segment::new(Vec2::new(169.0, 239.0), Vec2::new(198.0, 229.0), 0.82),
|
|
||||||
Segment::new(Vec2::new(198.0, 229.0), Vec2::new(202.0, 241.0), 0.82),
|
|
||||||
Segment::new(Vec2::new(173.0, 251.0), Vec2::new(169.0, 239.0), 0.82),
|
|
||||||
Segment::new(Vec2::new(153.0, 245.0), Vec2::new(182.0, 235.0), 0.82),
|
|
||||||
Segment::new(Vec2::new(182.0, 235.0), Vec2::new(186.0, 247.0), 0.82),
|
|
||||||
Segment::new(Vec2::new(157.0, 257.0), Vec2::new(153.0, 245.0), 0.82),
|
|
||||||
Segment::new(Vec2::new(139.0, 252.0), Vec2::new(163.0, 241.0), 0.82),
|
|
||||||
Segment::new(Vec2::new(163.0, 241.0), Vec2::new(167.0, 253.0), 0.82),
|
|
||||||
Segment::new(Vec2::new(144.0, 267.0), Vec2::new(139.0, 252.0), 0.82),
|
|
||||||
Segment::new(Vec2::new(84.0, 318.0), Vec2::new(105.0, 329.0), 0.82),
|
|
||||||
Segment::new(Vec2::new(105.0, 329.0), Vec2::new(101.0, 339.0), 0.82),
|
|
||||||
Segment::new(Vec2::new(80.0, 328.0), Vec2::new(84.0, 318.0), 0.82),
|
|
||||||
Segment::new(Vec2::new(87.0, 327.0), Vec2::new(111.0, 339.0), 0.82),
|
|
||||||
Segment::new(Vec2::new(77.0, 370.0), Vec2::new(77.0, 331.0), 0.82),
|
|
||||||
Segment::new(Vec2::new(87.0, 372.0), Vec2::new(77.0, 370.0), 0.82),
|
|
||||||
Segment::new(Vec2::new(110.0, 337.0), Vec2::new(87.0, 372.0), 0.82),
|
|
||||||
Segment::new(Vec2::new(184.0, 426.0), Vec2::new(172.0, 403.0), 0.82),
|
|
||||||
Segment::new(Vec2::new(193.0, 396.0), Vec2::new(205.0, 417.0), 0.82),
|
|
||||||
Segment::new(Vec2::new(213.0, 426.0), Vec2::new(201.0, 403.0), 0.82),
|
|
||||||
Segment::new(Vec2::new(222.0, 396.0), Vec2::new(234.0, 417.0), 0.82),
|
|
||||||
Segment::new(Vec2::new(242.0, 426.0), Vec2::new(230.0, 403.0), 0.82),
|
|
||||||
Segment::new(Vec2::new(251.0, 396.0), Vec2::new(263.0, 417.0), 0.82),
|
|
||||||
Segment::new(Vec2::new(291.0, 205.0), Vec2::new(298.0, 187.0), 0.82),
|
|
||||||
Segment::new(Vec2::new(298.0, 378.0), Vec2::new(298.0, 58.0), 0.82),
|
|
||||||
Segment::new(Vec2::new(298.0, 288.0), Vec2::new(298.0, 211.0), 0.82),
|
|
||||||
Segment::new(Vec2::new(298.0, 313.0), Vec2::new(291.0, 290.0), 0.82),
|
|
||||||
]
|
|
||||||
});
|
|
||||||
&WALLS
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
@@ -661,21 +584,29 @@ mod tests {
|
|||||||
fn stalled_ball_search_restores_motion() {
|
fn stalled_ball_search_restores_motion() {
|
||||||
let mut game = Game::new(1);
|
let mut game = Game::new(1);
|
||||||
game.ball.in_launcher = false;
|
game.ball.in_launcher = false;
|
||||||
game.ball.position = vec2(220.0, 437.0);
|
game.ball.position = vec2(250.0, 300.0);
|
||||||
game.ball.velocity = Vec2::ZERO;
|
game.ball.velocity = Vec2::ZERO;
|
||||||
|
game.stalled_for = BALL_SEARCH_DELAY - 1.0 / 120.0;
|
||||||
|
|
||||||
let mut events = Vec::new();
|
let events = game.update(1.0 / 120.0, 3, Controls::default());
|
||||||
for _ in 0..600 {
|
|
||||||
events.extend(game.update(1.0 / 120.0, 3, Controls::default()));
|
|
||||||
if events.contains(&Event::BallSearch) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
assert!(events.contains(&Event::BallSearch));
|
assert!(events.contains(&Event::BallSearch));
|
||||||
assert!(game.ball.velocity.y < 0.0);
|
assert!(game.ball.velocity.y < 0.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn fast_ball_cannot_tunnel_through_the_top_rail() {
|
||||||
|
let mut game = Game::new(1);
|
||||||
|
game.ball.in_launcher = false;
|
||||||
|
game.ball.position = vec2(270.0, 30.0);
|
||||||
|
game.ball.velocity = vec2(0.0, -MAX_SPEED);
|
||||||
|
|
||||||
|
game.fixed_update(1.0 / 30.0, &mut Vec::new());
|
||||||
|
|
||||||
|
assert!(game.ball.position.y >= 15.0 + BALL_RADIUS - 0.01);
|
||||||
|
assert!(game.ball.velocity.y > 0.0);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn tilt_latches_and_disables_flippers_and_scoring() {
|
fn tilt_latches_and_disables_flippers_and_scoring() {
|
||||||
let mut game = Game::new(1);
|
let mut game = Game::new(1);
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ mod assets;
|
|||||||
mod game;
|
mod game;
|
||||||
mod geometry;
|
mod geometry;
|
||||||
mod persistence;
|
mod persistence;
|
||||||
|
mod table;
|
||||||
|
|
||||||
use app::App;
|
use app::App;
|
||||||
use macroquad::prelude::*;
|
use macroquad::prelude::*;
|
||||||
|
|||||||
@@ -0,0 +1,231 @@
|
|||||||
|
//! Collision primitives recovered from the original Win16 object table.
|
||||||
|
//!
|
||||||
|
//! `FUN_1000_34ab` registers objects 1 through 175 through
|
||||||
|
//! `FUN_1008_0138`. The latter reveals the coordinate conversion: ordinary
|
||||||
|
//! objects use `(param_34, param_32 - 20)` and `(param_30, param_28 - 20)`;
|
||||||
|
//! relative objects accumulate those values from the preceding endpoint.
|
||||||
|
//! The old port used `478 - x`, which mirrored the complete table vertically.
|
||||||
|
|
||||||
|
use crate::geometry::Segment;
|
||||||
|
use macroquad::prelude::Vec2;
|
||||||
|
|
||||||
|
const WALL_BOUNCE: f32 = 0.82;
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
pub struct TableSegment {
|
||||||
|
pub id: u8,
|
||||||
|
pub segment: Segment,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TableSegment {
|
||||||
|
const fn new(id: u8, start: Vec2, end: Vec2) -> Self {
|
||||||
|
Self {
|
||||||
|
id,
|
||||||
|
segment: Segment::new(start, end, WALL_BOUNCE),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
pub struct StaticCircle {
|
||||||
|
pub id: u8,
|
||||||
|
pub center: Vec2,
|
||||||
|
pub radius: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl StaticCircle {
|
||||||
|
const fn new(id: u8, center: Vec2, radius: f32) -> Self {
|
||||||
|
Self { id, center, radius }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Active type-2 objects after later registrations overwrite objects 169 and
|
||||||
|
/// 170. Object 2 is the drain sensor and object 25 is the shooter stop; callers
|
||||||
|
/// apply their special behavior after detecting contact.
|
||||||
|
pub const WALLS: &[TableSegment] = &[
|
||||||
|
TableSegment::new(1, Vec2::new(7.0, 397.0), Vec2::new(125.0, 437.0)),
|
||||||
|
TableSegment::new(2, Vec2::new(10.0, 455.0), Vec2::new(300.0, 455.0)),
|
||||||
|
TableSegment::new(3, Vec2::new(134.0, 442.0), Vec2::new(134.0, 425.0)),
|
||||||
|
TableSegment::new(5, Vec2::new(148.0, 425.0), Vec2::new(148.0, 447.0)),
|
||||||
|
TableSegment::new(7, Vec2::new(165.0, 425.0), Vec2::new(164.0, 446.0)),
|
||||||
|
TableSegment::new(9, Vec2::new(179.0, 425.0), Vec2::new(180.0, 447.0)),
|
||||||
|
TableSegment::new(10, Vec2::new(183.0, 437.0), Vec2::new(304.0, 394.0)),
|
||||||
|
TableSegment::new(11, Vec2::new(298.0, 145.0), Vec2::new(298.0, 41.0)),
|
||||||
|
TableSegment::new(12, Vec2::new(310.0, 122.0), Vec2::new(298.0, 122.0)),
|
||||||
|
TableSegment::new(14, Vec2::new(291.0, 116.0), Vec2::new(292.0, 84.0)),
|
||||||
|
TableSegment::new(15, Vec2::new(300.0, 89.0), Vec2::new(281.0, 80.0)),
|
||||||
|
TableSegment::new(16, Vec2::new(290.0, 76.0), Vec2::new(275.0, 115.0)),
|
||||||
|
TableSegment::new(18, Vec2::new(264.0, 119.0), Vec2::new(248.0, 94.0)),
|
||||||
|
TableSegment::new(19, Vec2::new(249.0, 94.0), Vec2::new(257.0, 71.0)),
|
||||||
|
TableSegment::new(20, Vec2::new(257.0, 71.0), Vec2::new(277.0, 29.0)),
|
||||||
|
TableSegment::new(21, Vec2::new(277.0, 31.0), Vec2::new(287.0, 12.0)),
|
||||||
|
TableSegment::new(22, Vec2::new(283.0, 22.0), Vec2::new(303.0, 28.0)),
|
||||||
|
TableSegment::new(23, Vec2::new(302.0, 28.0), Vec2::new(320.0, 48.0)),
|
||||||
|
TableSegment::new(24, Vec2::new(320.0, 48.0), Vec2::new(320.0, 437.0)),
|
||||||
|
TableSegment::new(25, Vec2::new(315.0, 413.0), Vec2::new(332.0, 413.0)),
|
||||||
|
TableSegment::new(27, Vec2::new(328.0, 58.0), Vec2::new(326.0, 47.0)),
|
||||||
|
TableSegment::new(28, Vec2::new(326.0, 47.0), Vec2::new(322.0, 37.0)),
|
||||||
|
TableSegment::new(29, Vec2::new(322.0, 37.0), Vec2::new(316.0, 28.0)),
|
||||||
|
TableSegment::new(30, Vec2::new(316.0, 28.0), Vec2::new(307.0, 21.0)),
|
||||||
|
TableSegment::new(31, Vec2::new(307.0, 21.0), Vec2::new(296.0, 16.0)),
|
||||||
|
TableSegment::new(32, Vec2::new(296.0, 16.0), Vec2::new(285.0, 15.0)),
|
||||||
|
TableSegment::new(33, Vec2::new(285.0, 15.0), Vec2::new(112.0, 15.0)),
|
||||||
|
TableSegment::new(34, Vec2::new(121.0, 14.0), Vec2::new(99.0, 20.0)),
|
||||||
|
TableSegment::new(35, Vec2::new(100.0, 20.0), Vec2::new(78.0, 31.0)),
|
||||||
|
TableSegment::new(36, Vec2::new(79.0, 30.0), Vec2::new(66.0, 49.0)),
|
||||||
|
TableSegment::new(37, Vec2::new(68.0, 45.0), Vec2::new(57.0, 73.0)),
|
||||||
|
TableSegment::new(38, Vec2::new(59.0, 62.0), Vec2::new(59.0, 154.0)),
|
||||||
|
TableSegment::new(39, Vec2::new(58.0, 147.0), Vec2::new(67.0, 170.0)),
|
||||||
|
TableSegment::new(41, Vec2::new(64.0, 179.0), Vec2::new(41.0, 190.0)),
|
||||||
|
TableSegment::new(43, Vec2::new(31.0, 186.0), Vec2::new(22.0, 165.0)),
|
||||||
|
TableSegment::new(44, Vec2::new(22.0, 165.0), Vec2::new(22.0, 11.0)),
|
||||||
|
TableSegment::new(45, Vec2::new(23.0, 13.0), Vec2::new(11.0, 13.0)),
|
||||||
|
TableSegment::new(46, Vec2::new(13.0, 10.0), Vec2::new(13.0, 170.0)),
|
||||||
|
TableSegment::new(47, Vec2::new(12.0, 165.0), Vec2::new(32.0, 230.0)),
|
||||||
|
TableSegment::new(49, Vec2::new(33.0, 231.0), Vec2::new(10.0, 300.0)),
|
||||||
|
TableSegment::new(50, Vec2::new(12.0, 261.0), Vec2::new(12.0, 407.0)),
|
||||||
|
TableSegment::new(55, Vec2::new(59.0, 287.0), Vec2::new(107.0, 359.0)),
|
||||||
|
TableSegment::new(57, Vec2::new(49.0, 294.0), Vec2::new(2.0, 268.0)),
|
||||||
|
TableSegment::new(59, Vec2::new(44.0, 287.0), Vec2::new(44.0, 242.0)),
|
||||||
|
TableSegment::new(61, Vec2::new(37.0, 292.0), Vec2::new(37.0, 347.0)),
|
||||||
|
TableSegment::new(62, Vec2::new(37.0, 347.0), Vec2::new(110.0, 384.0)),
|
||||||
|
TableSegment::new(63, Vec2::new(92.0, 408.0), Vec2::new(19.0, 385.0)),
|
||||||
|
TableSegment::new(64, Vec2::new(19.0, 385.0), Vec2::new(19.0, 291.0)),
|
||||||
|
TableSegment::new(66, Vec2::new(112.0, 386.0), Vec2::new(141.0, 413.0)),
|
||||||
|
TableSegment::new(68, Vec2::new(130.0, 427.0), Vec2::new(97.0, 411.0)),
|
||||||
|
TableSegment::new(70, Vec2::new(267.0, 292.0), Vec2::new(267.0, 337.0)),
|
||||||
|
TableSegment::new(72, Vec2::new(261.0, 299.0), Vec2::new(214.0, 321.0)),
|
||||||
|
TableSegment::new(74, Vec2::new(252.0, 289.0), Vec2::new(302.0, 218.0)),
|
||||||
|
TableSegment::new(76, Vec2::new(292.0, 291.0), Vec2::new(292.0, 385.0)),
|
||||||
|
TableSegment::new(77, Vec2::new(292.0, 385.0), Vec2::new(221.0, 408.0)),
|
||||||
|
TableSegment::new(78, Vec2::new(205.0, 384.0), Vec2::new(275.0, 347.0)),
|
||||||
|
TableSegment::new(79, Vec2::new(275.0, 347.0), Vec2::new(275.0, 292.0)),
|
||||||
|
TableSegment::new(81, Vec2::new(216.0, 411.0), Vec2::new(177.0, 427.0)),
|
||||||
|
TableSegment::new(83, Vec2::new(172.0, 413.0), Vec2::new(201.0, 386.0)),
|
||||||
|
TableSegment::new(84, Vec2::new(73.0, 185.0), Vec2::new(40.0, 201.0)),
|
||||||
|
TableSegment::new(85, Vec2::new(67.0, 173.0), Vec2::new(73.0, 185.0)),
|
||||||
|
TableSegment::new(86, Vec2::new(40.0, 201.0), Vec2::new(34.0, 189.0)),
|
||||||
|
TableSegment::new(90, Vec2::new(292.0, 253.0), Vec2::new(292.0, 227.0)),
|
||||||
|
TableSegment::new(91, Vec2::new(302.0, 253.0), Vec2::new(292.0, 253.0)),
|
||||||
|
TableSegment::new(92, Vec2::new(292.0, 227.0), Vec2::new(302.0, 227.0)),
|
||||||
|
TableSegment::new(93, Vec2::new(292.0, 236.0), Vec2::new(292.0, 211.0)),
|
||||||
|
TableSegment::new(94, Vec2::new(302.0, 236.0), Vec2::new(292.0, 236.0)),
|
||||||
|
TableSegment::new(95, Vec2::new(292.0, 211.0), Vec2::new(302.0, 211.0)),
|
||||||
|
TableSegment::new(96, Vec2::new(292.0, 221.0), Vec2::new(292.0, 197.0)),
|
||||||
|
TableSegment::new(97, Vec2::new(302.0, 221.0), Vec2::new(292.0, 221.0)),
|
||||||
|
TableSegment::new(98, Vec2::new(292.0, 197.0), Vec2::new(302.0, 197.0)),
|
||||||
|
TableSegment::new(99, Vec2::new(292.0, 206.0), Vec2::new(292.0, 181.0)),
|
||||||
|
TableSegment::new(100, Vec2::new(302.0, 206.0), Vec2::new(292.0, 206.0)),
|
||||||
|
TableSegment::new(101, Vec2::new(292.0, 182.0), Vec2::new(302.0, 182.0)),
|
||||||
|
TableSegment::new(102, Vec2::new(292.0, 190.0), Vec2::new(292.0, 167.0)),
|
||||||
|
TableSegment::new(103, Vec2::new(302.0, 190.0), Vec2::new(292.0, 189.0)),
|
||||||
|
TableSegment::new(104, Vec2::new(292.0, 168.0), Vec2::new(302.0, 163.0)),
|
||||||
|
TableSegment::new(105, Vec2::new(203.0, 222.0), Vec2::new(148.0, 204.0)),
|
||||||
|
TableSegment::new(107, Vec2::new(158.0, 178.0), Vec2::new(215.0, 197.0)),
|
||||||
|
TableSegment::new(109, Vec2::new(209.0, 233.0), Vec2::new(187.0, 226.0)),
|
||||||
|
TableSegment::new(110, Vec2::new(214.0, 218.0), Vec2::new(209.0, 233.0)),
|
||||||
|
TableSegment::new(111, Vec2::new(187.0, 226.0), Vec2::new(191.0, 214.0)),
|
||||||
|
TableSegment::new(112, Vec2::new(198.0, 229.0), Vec2::new(169.0, 219.0)),
|
||||||
|
TableSegment::new(113, Vec2::new(202.0, 217.0), Vec2::new(198.0, 229.0)),
|
||||||
|
TableSegment::new(114, Vec2::new(169.0, 219.0), Vec2::new(173.0, 207.0)),
|
||||||
|
TableSegment::new(115, Vec2::new(182.0, 223.0), Vec2::new(153.0, 213.0)),
|
||||||
|
TableSegment::new(116, Vec2::new(186.0, 211.0), Vec2::new(182.0, 223.0)),
|
||||||
|
TableSegment::new(117, Vec2::new(153.0, 213.0), Vec2::new(157.0, 201.0)),
|
||||||
|
TableSegment::new(118, Vec2::new(163.0, 217.0), Vec2::new(139.0, 206.0)),
|
||||||
|
TableSegment::new(119, Vec2::new(167.0, 205.0), Vec2::new(163.0, 217.0)),
|
||||||
|
TableSegment::new(120, Vec2::new(139.0, 206.0), Vec2::new(144.0, 191.0)),
|
||||||
|
TableSegment::new(121, Vec2::new(105.0, 129.0), Vec2::new(84.0, 140.0)),
|
||||||
|
TableSegment::new(122, Vec2::new(101.0, 119.0), Vec2::new(105.0, 129.0)),
|
||||||
|
TableSegment::new(123, Vec2::new(84.0, 140.0), Vec2::new(80.0, 130.0)),
|
||||||
|
TableSegment::new(124, Vec2::new(111.0, 119.0), Vec2::new(87.0, 131.0)),
|
||||||
|
TableSegment::new(126, Vec2::new(77.0, 127.0), Vec2::new(77.0, 88.0)),
|
||||||
|
TableSegment::new(127, Vec2::new(77.0, 88.0), Vec2::new(87.0, 86.0)),
|
||||||
|
TableSegment::new(128, Vec2::new(87.0, 86.0), Vec2::new(110.0, 121.0)),
|
||||||
|
TableSegment::new(156, Vec2::new(172.0, 55.0), Vec2::new(184.0, 32.0)),
|
||||||
|
TableSegment::new(158, Vec2::new(205.0, 41.0), Vec2::new(193.0, 62.0)),
|
||||||
|
TableSegment::new(160, Vec2::new(201.0, 55.0), Vec2::new(213.0, 32.0)),
|
||||||
|
TableSegment::new(162, Vec2::new(234.0, 41.0), Vec2::new(222.0, 62.0)),
|
||||||
|
TableSegment::new(164, Vec2::new(230.0, 55.0), Vec2::new(242.0, 32.0)),
|
||||||
|
TableSegment::new(166, Vec2::new(263.0, 41.0), Vec2::new(251.0, 62.0)),
|
||||||
|
TableSegment::new(168, Vec2::new(298.0, 271.0), Vec2::new(291.0, 253.0)),
|
||||||
|
TableSegment::new(169, Vec2::new(328.0, 422.0), Vec2::new(328.0, 58.0)),
|
||||||
|
TableSegment::new(170, Vec2::new(298.0, 400.0), Vec2::new(298.0, 80.0)),
|
||||||
|
TableSegment::new(171, Vec2::new(298.0, 247.0), Vec2::new(298.0, 170.0)),
|
||||||
|
TableSegment::new(173, Vec2::new(291.0, 168.0), Vec2::new(298.0, 145.0)),
|
||||||
|
];
|
||||||
|
|
||||||
|
/// Active type-1 solid circles, excluding the three scored bumpers and object
|
||||||
|
/// 174, whose position is overwritten every frame for the live ball.
|
||||||
|
pub const PASSIVE_CIRCLES: &[StaticCircle] = &[
|
||||||
|
StaticCircle::new(4, Vec2::new(141.0, 426.0), 8.0),
|
||||||
|
StaticCircle::new(8, Vec2::new(172.0, 426.0), 8.0),
|
||||||
|
StaticCircle::new(13, Vec2::new(298.0, 116.0), 7.0),
|
||||||
|
StaticCircle::new(17, Vec2::new(269.0, 114.0), 7.0),
|
||||||
|
StaticCircle::new(26, Vec2::new(257.0, 95.0), 7.0),
|
||||||
|
StaticCircle::new(40, Vec2::new(60.0, 173.0), 8.0),
|
||||||
|
StaticCircle::new(42, Vec2::new(38.0, 183.0), 8.0),
|
||||||
|
StaticCircle::new(48, Vec2::new(25.0, 231.0), 8.0),
|
||||||
|
StaticCircle::new(54, Vec2::new(53.0, 292.0), 8.0),
|
||||||
|
StaticCircle::new(56, Vec2::new(50.0, 287.0), 8.0),
|
||||||
|
StaticCircle::new(58, Vec2::new(52.0, 287.0), 8.0),
|
||||||
|
StaticCircle::new(60, Vec2::new(29.0, 294.0), 10.0),
|
||||||
|
StaticCircle::new(65, Vec2::new(103.0, 397.0), 15.0),
|
||||||
|
StaticCircle::new(67, Vec2::new(134.0, 419.0), 9.0),
|
||||||
|
StaticCircle::new(69, Vec2::new(260.0, 292.0), 8.0),
|
||||||
|
StaticCircle::new(71, Vec2::new(260.0, 291.0), 8.0),
|
||||||
|
StaticCircle::new(73, Vec2::new(261.0, 291.0), 8.0),
|
||||||
|
StaticCircle::new(75, Vec2::new(284.0, 292.0), 9.0),
|
||||||
|
StaticCircle::new(80, Vec2::new(210.0, 397.0), 15.0),
|
||||||
|
StaticCircle::new(82, Vec2::new(179.0, 419.0), 9.0),
|
||||||
|
StaticCircle::new(106, Vec2::new(156.0, 192.0), 14.0),
|
||||||
|
StaticCircle::new(108, Vec2::new(210.0, 210.0), 14.0),
|
||||||
|
StaticCircle::new(125, Vec2::new(84.0, 126.0), 8.0),
|
||||||
|
StaticCircle::new(134, Vec2::new(124.0, 76.0), 20.0),
|
||||||
|
StaticCircle::new(135, Vec2::new(100.0, 58.0), 17.0),
|
||||||
|
StaticCircle::new(136, Vec2::new(133.0, 48.0), 17.0),
|
||||||
|
StaticCircle::new(137, Vec2::new(154.0, 76.0), 17.0),
|
||||||
|
StaticCircle::new(138, Vec2::new(133.0, 105.0), 17.0),
|
||||||
|
StaticCircle::new(139, Vec2::new(100.0, 94.0), 17.0),
|
||||||
|
StaticCircle::new(155, Vec2::new(183.0, 59.0), 11.0),
|
||||||
|
StaticCircle::new(157, Vec2::new(195.0, 36.0), 11.0),
|
||||||
|
StaticCircle::new(159, Vec2::new(212.0, 59.0), 11.0),
|
||||||
|
StaticCircle::new(161, Vec2::new(224.0, 36.0), 11.0),
|
||||||
|
StaticCircle::new(163, Vec2::new(241.0, 59.0), 11.0),
|
||||||
|
StaticCircle::new(165, Vec2::new(253.0, 36.0), 11.0),
|
||||||
|
StaticCircle::new(167, Vec2::new(299.0, 248.0), 9.0),
|
||||||
|
StaticCircle::new(172, Vec2::new(299.0, 171.0), 9.0),
|
||||||
|
];
|
||||||
|
|
||||||
|
pub const BUMPERS: [StaticCircle; 3] = [
|
||||||
|
StaticCircle::new(51, Vec2::new(165.0, 148.0), 23.0),
|
||||||
|
StaticCircle::new(52, Vec2::new(206.0, 110.0), 23.0),
|
||||||
|
StaticCircle::new(53, Vec2::new(219.0, 165.0), 23.0),
|
||||||
|
];
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn recovered_object_inventory_is_complete() {
|
||||||
|
assert_eq!(WALLS.len(), 109);
|
||||||
|
assert_eq!(PASSIVE_CIRCLES.len(), 37);
|
||||||
|
assert_eq!(BUMPERS.len(), 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn recovered_landmarks_match_the_visible_table() {
|
||||||
|
let left_flipper_post = PASSIVE_CIRCLES
|
||||||
|
.iter()
|
||||||
|
.find(|circle| circle.id == 65)
|
||||||
|
.expect("object 65 should be the left flipper post");
|
||||||
|
assert_eq!(left_flipper_post.center, Vec2::new(103.0, 397.0));
|
||||||
|
|
||||||
|
let shooter_wall = WALLS
|
||||||
|
.iter()
|
||||||
|
.find(|wall| wall.id == 169)
|
||||||
|
.expect("object 169 should be the outside shooter wall");
|
||||||
|
assert_eq!(shooter_wall.segment.start, Vec2::new(328.0, 422.0));
|
||||||
|
assert_eq!(shooter_wall.segment.end, Vec2::new(328.0, 58.0));
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user