fix(ui): restore effect and player display assets
Load and render DAT400-407 effect panels with the preserved wheel overlap, and add deterministic effect-7 coverage. Restore exact 1008:0996/0b9e score fields, configured-player rows, current/status indicators, signed status thresholds, and six 5x5 per-player ball-marker slots. Test Plan: - cargo test --all-targets - cargo clippy --all-targets --all-features -- -D warnings - rumdl check CHANGELOG.md RECONSTRUCTION.md README.md - cargo run --quiet -- --simulate effect-7 --step 0 --screenshot /tmp/tdkpin-effect7.png - cargo run --quiet -- --simulate launcher --step 0 --screenshot /tmp/tdkpin-display.png - git diff --check
This commit is contained in:
@@ -10,6 +10,9 @@ and this project adheres to
|
|||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
|
- Load and draw all DAT400-407 player/effect sprites at `(55,79)`, preserving
|
||||||
|
the original saved 14x14 wheel overlap at `(84,77)`; selected bonuses,
|
||||||
|
collect-bonus, and release-ball states are now visible.
|
||||||
- Restore display blits from `1008:0996`/`1008:0b9e`: 130x40 score fields at
|
- Restore display blits from `1008:0996`/`1008:0b9e`: 130x40 score fields at
|
||||||
X=478, eight places ending at X=606, exact 139x31 configured-player rows,
|
X=478, eight places ending at X=606, exact 139x31 configured-player rows,
|
||||||
11x11 active/status markers, 5x5 remaining-ball markers for every player,
|
11x11 active/status markers, 5x5 remaining-ball markers for every player,
|
||||||
|
|||||||
+1
-1
@@ -45,7 +45,7 @@ cargo run -- --simulate claw-6 --at 0.15 \
|
|||||||
|
|
||||||
Use `--step N` instead of `--at SECONDS` to reproduce one exact update. The
|
Use `--step N` instead of `--at SECONDS` to reproduce one exact update. The
|
||||||
available scenarios are `loading`, `attract`, `highscores`, `name-entry`,
|
available scenarios are `loading`, `attract`, `highscores`, `name-entry`,
|
||||||
`autoplay`, `launcher`, `flippers`, `panel`, `targets`, `claw-1`, `claw-6`,
|
`effect-7`, `autoplay`, `launcher`, `flippers`, `panel`, `targets`, `claw-1`, `claw-6`,
|
||||||
`claw-7`, and `claw-18`; `--seed N` fixes random choices.
|
`claw-7`, and `claw-18`; `--seed N` fixes random choices.
|
||||||
`autoplay` charges each ball and operates the flippers from live ball position
|
`autoplay` charges each ball and operates the flippers from live ball position
|
||||||
for long end-to-end validation runs.
|
for long end-to-end validation runs.
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ implementation.
|
|||||||
|
|
||||||
| Subsystem | Rust status | Evidence and boundary |
|
| 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. 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. |
|
| 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. |
|
||||||
| 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 and nudge-then-tilt ordering. WAV 2022 is loaded by the original generic resource loop but has no playback call and is therefore never played by Rust. |
|
| 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 and nudge-then-tilt ordering. 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. |
|
| 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. |
|
| 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. |
|
||||||
|
|||||||
@@ -688,6 +688,14 @@ impl App {
|
|||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
draw_texture(
|
||||||
|
&self.assets.player_effects[usize::from(game.player_effect())],
|
||||||
|
55.0,
|
||||||
|
79.0,
|
||||||
|
WHITE,
|
||||||
|
);
|
||||||
|
self.draw_inactive_table_region(84, 77, 14, 14);
|
||||||
|
|
||||||
if let Some(frame) = game.panel_frame {
|
if let Some(frame) = game.panel_frame {
|
||||||
self.draw_panel_animation(frame);
|
self.draw_panel_animation(frame);
|
||||||
} else if let Some(state) = game
|
} else if let Some(state) = game
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ pub struct Assets {
|
|||||||
pub loading_progress: Texture2D,
|
pub loading_progress: Texture2D,
|
||||||
pub highscore_background: Texture2D,
|
pub highscore_background: Texture2D,
|
||||||
pub help: [Texture2D; 5],
|
pub help: [Texture2D; 5],
|
||||||
|
pub player_effects: [Texture2D; 8],
|
||||||
pub media: [Texture2D; 4],
|
pub media: [Texture2D; 4],
|
||||||
pub diamond: [Texture2D; 9],
|
pub diamond: [Texture2D; 9],
|
||||||
pub wheel: Texture2D,
|
pub wheel: Texture2D,
|
||||||
@@ -40,6 +41,16 @@ impl Assets {
|
|||||||
texture(include_bytes!("../assets/original/images/dat_01004.png")),
|
texture(include_bytes!("../assets/original/images/dat_01004.png")),
|
||||||
texture(include_bytes!("../assets/original/images/dat_01005.png")),
|
texture(include_bytes!("../assets/original/images/dat_01005.png")),
|
||||||
];
|
];
|
||||||
|
let player_effects = [
|
||||||
|
texture(include_bytes!("../assets/original/images/dat_00400.png")),
|
||||||
|
texture(include_bytes!("../assets/original/images/dat_00401.png")),
|
||||||
|
texture(include_bytes!("../assets/original/images/dat_00402.png")),
|
||||||
|
texture(include_bytes!("../assets/original/images/dat_00403.png")),
|
||||||
|
texture(include_bytes!("../assets/original/images/dat_00404.png")),
|
||||||
|
texture(include_bytes!("../assets/original/images/dat_00405.png")),
|
||||||
|
texture(include_bytes!("../assets/original/images/dat_00406.png")),
|
||||||
|
texture(include_bytes!("../assets/original/images/dat_00407.png")),
|
||||||
|
];
|
||||||
let media = [
|
let media = [
|
||||||
texture(include_bytes!("../assets/original/images/dat_00701.png")),
|
texture(include_bytes!("../assets/original/images/dat_00701.png")),
|
||||||
texture(include_bytes!("../assets/original/images/dat_00702.png")),
|
texture(include_bytes!("../assets/original/images/dat_00702.png")),
|
||||||
@@ -153,6 +164,7 @@ impl Assets {
|
|||||||
loading_progress,
|
loading_progress,
|
||||||
highscore_background,
|
highscore_background,
|
||||||
help,
|
help,
|
||||||
|
player_effects,
|
||||||
media,
|
media,
|
||||||
diamond,
|
diamond,
|
||||||
wheel,
|
wheel,
|
||||||
|
|||||||
@@ -406,6 +406,11 @@ impl Game {
|
|||||||
events
|
events
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn begin_effect_scenario(&mut self, effect: u8) {
|
||||||
|
self.target_effect = effect.min(7);
|
||||||
|
self.object_active[usize::from(EFFECT_SENSOR.id)] = self.target_effect != 0;
|
||||||
|
}
|
||||||
|
|
||||||
fn fire_launcher(&mut self) {
|
fn fire_launcher(&mut self) {
|
||||||
self.launcher_velocity_milli = self
|
self.launcher_velocity_milli = self
|
||||||
.launcher_velocity_milli
|
.launcher_velocity_milli
|
||||||
@@ -1801,6 +1806,10 @@ impl Game {
|
|||||||
self.object_active[usize::from(EFFECT_SENSOR.id)]
|
self.object_active[usize::from(EFFECT_SENSOR.id)]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn player_effect(&self) -> u8 {
|
||||||
|
self.target_effect.min(7)
|
||||||
|
}
|
||||||
|
|
||||||
fn next_claw_terminal_frame(&mut self) -> u8 {
|
fn next_claw_terminal_frame(&mut self) -> u8 {
|
||||||
CLAW_TERMINAL_FRAMES[usize::from(self.random.below(4))]
|
CLAW_TERMINAL_FRAMES[usize::from(self.random.below(4))]
|
||||||
}
|
}
|
||||||
@@ -2450,6 +2459,7 @@ mod tests {
|
|||||||
assert!((1..=6).contains(&game.target_effect));
|
assert!((1..=6).contains(&game.target_effect));
|
||||||
assert!(game.effect_target_active());
|
assert!(game.effect_target_active());
|
||||||
game.target_effect = 3;
|
game.target_effect = 3;
|
||||||
|
assert_eq!(game.player_effect(), 3);
|
||||||
game.ball.in_launcher = false;
|
game.ball.in_launcher = false;
|
||||||
game.ball.position = EFFECT_SENSOR.center;
|
game.ball.position = EFFECT_SENSOR.center;
|
||||||
game.check_sensor_objects(
|
game.check_sensor_objects(
|
||||||
@@ -2462,6 +2472,7 @@ mod tests {
|
|||||||
assert_eq!(game.secondary_score_display(), 50_000);
|
assert_eq!(game.secondary_score_display(), 50_000);
|
||||||
assert_eq!(game.player().score, 2_000);
|
assert_eq!(game.player().score, 2_000);
|
||||||
assert_eq!(game.target_effect, 0);
|
assert_eq!(game.target_effect, 0);
|
||||||
|
assert_eq!(game.player_effect(), 0);
|
||||||
assert!(!game.effect_target_active());
|
assert!(!game.effect_target_active());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ pub enum Scenario {
|
|||||||
Attract,
|
Attract,
|
||||||
HighScores,
|
HighScores,
|
||||||
NameEntry,
|
NameEntry,
|
||||||
|
Effect7,
|
||||||
Autoplay,
|
Autoplay,
|
||||||
Launcher,
|
Launcher,
|
||||||
Flippers,
|
Flippers,
|
||||||
@@ -35,6 +36,7 @@ impl Scenario {
|
|||||||
Self::Attract => "attract",
|
Self::Attract => "attract",
|
||||||
Self::HighScores => "highscores",
|
Self::HighScores => "highscores",
|
||||||
Self::NameEntry => "name-entry",
|
Self::NameEntry => "name-entry",
|
||||||
|
Self::Effect7 => "effect-7",
|
||||||
Self::Autoplay => "autoplay",
|
Self::Autoplay => "autoplay",
|
||||||
Self::Launcher => "launcher",
|
Self::Launcher => "launcher",
|
||||||
Self::Flippers => "flippers",
|
Self::Flippers => "flippers",
|
||||||
@@ -57,6 +59,7 @@ impl Scenario {
|
|||||||
| Self::Attract
|
| Self::Attract
|
||||||
| Self::HighScores
|
| Self::HighScores
|
||||||
| Self::NameEntry
|
| Self::NameEntry
|
||||||
|
| Self::Effect7
|
||||||
| Self::Autoplay
|
| Self::Autoplay
|
||||||
| Self::Launcher
|
| Self::Launcher
|
||||||
| Self::Flippers
|
| Self::Flippers
|
||||||
@@ -75,6 +78,7 @@ impl FromStr for Scenario {
|
|||||||
"attract" => Ok(Self::Attract),
|
"attract" => Ok(Self::Attract),
|
||||||
"highscores" => Ok(Self::HighScores),
|
"highscores" => Ok(Self::HighScores),
|
||||||
"name-entry" => Ok(Self::NameEntry),
|
"name-entry" => Ok(Self::NameEntry),
|
||||||
|
"effect-7" => Ok(Self::Effect7),
|
||||||
"autoplay" => Ok(Self::Autoplay),
|
"autoplay" => Ok(Self::Autoplay),
|
||||||
"launcher" => Ok(Self::Launcher),
|
"launcher" => Ok(Self::Launcher),
|
||||||
"flippers" => Ok(Self::Flippers),
|
"flippers" => Ok(Self::Flippers),
|
||||||
@@ -85,7 +89,7 @@ impl FromStr for Scenario {
|
|||||||
"claw-7" => Ok(Self::Claw7),
|
"claw-7" => Ok(Self::Claw7),
|
||||||
"claw-18" => Ok(Self::Claw18),
|
"claw-18" => Ok(Self::Claw18),
|
||||||
_ => Err(format!(
|
_ => Err(format!(
|
||||||
"unknown scenario {value:?}; expected loading, attract, highscores, name-entry, autoplay, launcher, flippers, panel, targets, claw-1, claw-6, claw-7, or claw-18"
|
"unknown scenario {value:?}; expected loading, attract, highscores, name-entry, effect-7, autoplay, launcher, flippers, panel, targets, claw-1, claw-6, claw-7, or claw-18"
|
||||||
)),
|
)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -169,7 +173,7 @@ fn seconds_to_step(value: &str) -> Result<u64, String> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub const fn usage() -> &'static str {
|
pub const fn usage() -> &'static str {
|
||||||
"Usage:\n tdkpin-rs\n tdkpin-rs --simulate SCENARIO [--at SECONDS | --step N] [--screenshot FILE.png] [--trace FILE.json] [--seed N]\n\nScenarios: loading, attract, highscores, name-entry, autoplay, launcher, flippers, panel, targets, claw-1, claw-6, claw-7, claw-18"
|
"Usage:\n tdkpin-rs\n tdkpin-rs --simulate SCENARIO [--at SECONDS | --step N] [--screenshot FILE.png] [--trace FILE.json] [--seed N]\n\nScenarios: loading, attract, highscores, name-entry, effect-7, autoplay, launcher, flippers, panel, targets, claw-1, claw-6, claw-7, claw-18"
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, PartialEq)]
|
#[derive(Debug, Serialize, PartialEq)]
|
||||||
@@ -229,6 +233,9 @@ impl Simulation {
|
|||||||
game.target_rotation_state = Some(0);
|
game.target_rotation_state = Some(0);
|
||||||
game.wheel_holes = [true, false, true, false, false];
|
game.wheel_holes = [true, false, true, false, false];
|
||||||
}
|
}
|
||||||
|
if scenario == Scenario::Effect7 {
|
||||||
|
game.begin_effect_scenario(7);
|
||||||
|
}
|
||||||
let initial_events = scenario
|
let initial_events = scenario
|
||||||
.claw_terminal()
|
.claw_terminal()
|
||||||
.map_or_else(Vec::new, |terminal| game.begin_claw_scenario(terminal));
|
.map_or_else(Vec::new, |terminal| game.begin_claw_scenario(terminal));
|
||||||
@@ -364,6 +371,7 @@ fn controls_for(scenario: Scenario, step: u64) -> Controls {
|
|||||||
| Scenario::Attract
|
| Scenario::Attract
|
||||||
| Scenario::HighScores
|
| Scenario::HighScores
|
||||||
| Scenario::NameEntry
|
| Scenario::NameEntry
|
||||||
|
| Scenario::Effect7
|
||||||
| Scenario::Panel
|
| Scenario::Panel
|
||||||
| Scenario::Targets
|
| Scenario::Targets
|
||||||
| Scenario::Claw1
|
| Scenario::Claw1
|
||||||
|
|||||||
Reference in New Issue
Block a user