diff --git a/tdkpin-rs/CHANGELOG.md b/tdkpin-rs/CHANGELOG.md index 8b102ae..46acfc7 100644 --- a/tdkpin-rs/CHANGELOG.md +++ b/tdkpin-rs/CHANGELOG.md @@ -97,6 +97,8 @@ and this project adheres to - Replace invented bumper outline/cooldown effects with the original per-record 5/20/10 callback countdowns and exact overlay-A render rectangles for bumpers 51-53 and targets 140-147/150-152. +- Render the five cumulative bumper-value lamps from original player items 1-5 + at their recovered Word-Quad bounds as the value rises from 1,000 to 6,000. - Replace the free-running wheel animation with the original six-callback 91x90 target rotation, WAVE 2011 start, exact DAT600 frames/target positions, and state-6 rotation of the five per-player contact/item values. It advances diff --git a/tdkpin-rs/RECONSTRUCTION.md b/tdkpin-rs/RECONSTRUCTION.md index cebced5..b9238a9 100644 --- a/tdkpin-rs/RECONSTRUCTION.md +++ b/tdkpin-rs/RECONSTRUCTION.md @@ -19,7 +19,7 @@ implementation. | Subsystem | Rust status | Evidence and boundary | | --- | --- | --- | -| 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, help, ball, wheel, robot, plunger, media, and item frames. DAT400-407 expose the current per-player bonus/collect/release effect at `(55,79)` while preserving the saved 14x14 wheel overlap. Loading presents DAT995 and advances the original DAT994 strip at raw `1000:531e` destination `(202,328)`, height 13, and clipped width `(stage-1)*7` through stages 2-35. Record 148's armed player-item-20 state uses its exact overlay-A Word-Quad `(9,14)-(25,30)`. | +| 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, help, ball, wheel, robot, plunger, media, and item frames. DAT400-407 expose the current per-player bonus/collect/release effect at `(55,79)` while preserving the saved 14x14 wheel overlap. Loading presents DAT995 and advances the original DAT994 strip at raw `1000:531e` destination `(202,328)`, height 13, and clipped width `(stage-1)*7` through stages 2-35. Player items 1-5 cumulatively light the five recovered bumper-value Word-Quads from 2,000 through 6,000 points. Record 148's armed player-item-20 state uses its exact overlay-A Word-Quad `(9,14)-(25,30)`. | | Audio | Exact samples and recovered dispatch | All 16 mono PCM WAV resources are embedded unchanged. Playback is emitted at the reconstructed call sites, including multi-sound bank completions, nudge-then-tilt ordering, and Tilt suppression of the pre-reset WAVE-2008 drain cue. Like Win16 `SndPlaySound(SND_ASYNC|SND_NODEFAULT)`, each new resource stops the previous one and plays at the host mixer level without an application-side attenuation. WAV 2022 is loaded by the original generic resource loop but has no playback call and is therefore never played by Rust. | | Help and languages | Exact | Original resource images 1001-1005 are displayed directly. | | 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. Every static record retains its `+0x49` layer mask; the scanner selects layer 1 below the old Y value 250,000 and layer 2 at or above it, while mask 3 records remain shared. Before detection, the raw `1000:9b69` predicted position must lie in the record bounds derived with the registered five-pixel ball margin. Type-2 records retain every recovered Real48 normal/tangent response pair and registered one-sided orientation. Type-1 records retain their swept-circle radius, radial rebound, tangent coupling, and bumper kick. Detection retains unresolved normal/material candidates so later scan-time motion changes can participate in the selected response. Raw stack slot `SS:...d8a2` makes the first detected record ID win; later nearer candidates are stored but never applied, and both C/Rust have two-candidate regression coverage for this quirk. Each flipper uses its exact two line records plus moving tip circle in both positions. Moving-flipper contact ports `1000:7ed9` rather than fitting live samples: delta-specific pivots/edges, integer cross gates, radial/penetration calculations, response-record gain, and position/velocity publication are tested against all four C harness directions and the raised release geometry. Object 174 is overwritten with the live first ball and Rust handles its ball-to-ball role directly. | diff --git a/tdkpin-rs/src/app.rs b/tdkpin-rs/src/app.rs index 24080b5..cad022e 100644 --- a/tdkpin-rs/src/app.rs +++ b/tdkpin-rs/src/app.rs @@ -19,6 +19,20 @@ const TARGET_POSITIONS: [[(f32, f32); 5]; 6] = [ [(21.0, 60.0), (9.0, 28.0), (35.0, 7.0), (62.0, 25.0), (54.0, 56.0)], [(8.0, 35.0), (26.0, 8.0), (59.0, 18.0), (59.0, 50.0), (27.0, 61.0)], ]; +const BUMPER_VALUE_REGIONS: [(i32, i32, i32, i32); 5] = [ + (103, 304, 121, 323), + (119, 280, 137, 299), + (148, 272, 166, 291), + (177, 280, 195, 299), + (193, 303, 211, 322), +]; + +fn bumper_value_indicator_count(value: u32) -> usize { + usize::try_from(value / 1_000) + .unwrap_or(0) + .saturating_sub(1) + .min(BUMPER_VALUE_REGIONS.len()) +} fn visible_score_digits(mut value: u32) -> Vec<(u8, u8)> { let mut digits = Vec::with_capacity(8); @@ -533,13 +547,7 @@ impl App { } if let Some(active_quad) = self.attract.active_word_quad() { - let (left, top, right, bottom) = [ - (103, 304, 121, 323), - (119, 280, 137, 299), - (148, 272, 166, 291), - (177, 280, 195, 299), - (193, 303, 211, 322), - ][active_quad]; + let (left, top, right, bottom) = BUMPER_VALUE_REGIONS[active_quad]; self.draw_active_table_region(left, top, right - left + 1, bottom - top + 1); } @@ -738,6 +746,12 @@ impl App { let index = usize::from(game.player().diamond_segments.min(9) - 1); draw_texture(&self.assets.diamond[index], 123.0, 300.0, WHITE); } + for &(left, top, right, bottom) in BUMPER_VALUE_REGIONS + .iter() + .take(bumper_value_indicator_count(game.player().bumper_value)) + { + self.draw_active_table_region(left, top, right - left + 1, bottom - top + 1); + } 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)), @@ -1326,6 +1340,10 @@ mod tests { assert_eq!(displayed_player_markers(3, 0, true), 2); assert_eq!(displayed_player_markers(3, 0, false), 3); assert_eq!(displayed_player_markers(3, 4, true), 6); + assert_eq!(bumper_value_indicator_count(1_000), 0); + assert_eq!(bumper_value_indicator_count(2_000), 1); + assert_eq!(bumper_value_indicator_count(6_000), 5); + assert_eq!(bumper_value_indicator_count(u32::MAX), 5); } #[test] diff --git a/tdkpin-rs/src/simulation.rs b/tdkpin-rs/src/simulation.rs index e536425..1a7eaa4 100644 --- a/tdkpin-rs/src/simulation.rs +++ b/tdkpin-rs/src/simulation.rs @@ -232,6 +232,7 @@ impl Simulation { if scenario == Scenario::Targets { game.ball.in_launcher = false; game.ball.position = macroquad::prelude::vec2(170.0, 230.0); + game.players[0].bumper_value = 6_000; game.target_rotation_state = Some(0); game.wheel_holes = [true, false, true, false, false]; }