fix(ui): render cumulative bumper-value items

The five word quads at indices 1 through 5 are the visible progression for the
recovered bumper-value byte. The original activates them cumulatively as the
value rises from 1,000 to 6,000. Rust implemented the score progression but did
not render any of these player items during gameplay.

Share the exact quad bounds with the attract animation and draw the first
value/1000-1 regions from DAT997. Seed the deterministic targets scenario with
the maximum recovered value so framebuffer validation covers all five lamps.

Test Plan:
- `cargo test --workspace --all-targets --all-features` -- 120 passed
- `cargo clippy --workspace --all-targets --all-features -- -D warnings` -- passed
- `rumdl check --flavor commonmark RECONSTRUCTION.md CHANGELOG.md` -- passed
- `cargo run -- --simulate targets --step 26 --screenshot /tmp/tdkpin-targets-bumper.png` -- passed; all five recovered regions visually inspected at 640x460
- `git diff --cached --check` -- passed
This commit is contained in:
2026-08-23 20:11:06 +02:00
parent ac254f9e45
commit 71938c0062
4 changed files with 29 additions and 8 deletions
+25 -7
View File
@@ -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]
+1
View File
@@ -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];
}