fix(game): restore the 281-frame panel sequence

Replace the immediate five-hole cleanup with the original 281-callback panel
state machine. Suspend physics, clear contact/item state at the recovered
boundaries, and emit WAVE 2013, WAVE 2012, and stop events at frames
1/45/58/66/129/159/222/230/238 before resuming after cleanup.

Decode DAT600's four AND/OR mask-image pairs into transparent native textures
and render all nine phase formulas with the original source rectangles,
destination offsets, variable heights, and opaque wheel copies. Add a `panel`
simulation scenario for deterministic trace and framebuffer validation.

Test Plan:
- `cargo test --all-targets` -- passed, 72 tests
- `cargo clippy --all-targets -- -D warnings` -- passed
- `rumdl check CHANGELOG.md README.md RECONSTRUCTION.md` -- passed
- rendered panel frames 1, 66, 159, 238, and completion -- visually verified
- `git diff --cached --check` -- passed
This commit is contained in:
2026-08-23 17:49:04 +02:00
parent 6337291461
commit 4b84460c93
7 changed files with 303 additions and 11 deletions
+33 -4
View File
@@ -16,6 +16,7 @@ pub enum Scenario {
Autoplay,
Launcher,
Flippers,
Panel,
Claw1,
Claw6,
Claw7,
@@ -28,6 +29,7 @@ impl Scenario {
Self::Autoplay => "autoplay",
Self::Launcher => "launcher",
Self::Flippers => "flippers",
Self::Panel => "panel",
Self::Claw1 => "claw-1",
Self::Claw6 => "claw-6",
Self::Claw7 => "claw-7",
@@ -41,7 +43,7 @@ impl Scenario {
Self::Claw6 => Some(6),
Self::Claw7 => Some(7),
Self::Claw18 => Some(18),
Self::Autoplay | Self::Launcher | Self::Flippers => None,
Self::Autoplay | Self::Launcher | Self::Flippers | Self::Panel => None,
}
}
}
@@ -54,12 +56,13 @@ impl FromStr for Scenario {
"autoplay" => Ok(Self::Autoplay),
"launcher" => Ok(Self::Launcher),
"flippers" => Ok(Self::Flippers),
"panel" => Ok(Self::Panel),
"claw-1" => Ok(Self::Claw1),
"claw-6" => Ok(Self::Claw6),
"claw-7" => Ok(Self::Claw7),
"claw-18" => Ok(Self::Claw18),
_ => Err(format!(
"unknown scenario {value:?}; expected autoplay, launcher, flippers, claw-1, claw-6, claw-7, or claw-18"
"unknown scenario {value:?}; expected autoplay, launcher, flippers, panel, claw-1, claw-6, claw-7, or claw-18"
)),
}
}
@@ -143,7 +146,7 @@ fn seconds_to_step(value: &str) -> Result<u64, String> {
}
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: autoplay, launcher, flippers, 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: autoplay, launcher, flippers, panel, claw-1, claw-6, claw-7, claw-18"
}
#[derive(Debug, Serialize, PartialEq)]
@@ -157,6 +160,7 @@ pub struct Snapshot {
pub flippers: FlipperSnapshot,
pub launcher_charge: f32,
pub collision_id: Option<u8>,
pub panel_frame: Option<u16>,
pub events: Vec<String>,
}
@@ -194,6 +198,10 @@ pub struct Simulation {
impl Simulation {
pub fn new(scenario: Scenario, seed: u32) -> Self {
let mut game = Game::new_with_seed(1, seed);
if scenario == Scenario::Panel {
game.panel_frame = Some(0);
game.wheel_holes.fill(true);
}
let initial_events = scenario
.claw_terminal()
.map_or_else(Vec::new, |terminal| game.begin_claw_scenario(terminal));
@@ -274,6 +282,7 @@ impl Simulation {
},
launcher_charge: self.game.launcher_charge,
collision_id: self.game.last_collision_id,
panel_frame: self.game.panel_frame,
events: events
.into_iter()
.map(|event| format!("{event:?}"))
@@ -317,7 +326,7 @@ fn controls_for(scenario: Scenario, step: u64) -> Controls {
..Controls::default()
}
}
Scenario::Claw1 | Scenario::Claw6 | Scenario::Claw7 | Scenario::Claw18 => {
Scenario::Panel | Scenario::Claw1 | Scenario::Claw6 | Scenario::Claw7 | Scenario::Claw18 => {
Controls::default()
}
}
@@ -432,6 +441,26 @@ mod tests {
assert!(!simulation.game.flippers.left_raised);
}
#[test]
fn panel_scenario_runs_all_frames_and_cleans_up() {
let mut simulation = Simulation::new(Scenario::Panel, 1);
simulation.advance_to(4);
assert_eq!(simulation.game.panel_frame, Some(1));
assert_eq!(simulation.trace[4].events, ["Sound(2013)"]);
simulation.advance_to(1_020);
assert_eq!(simulation.game.panel_frame, None);
assert_eq!(simulation.game.wheel_holes, [false; 5]);
let events = simulation
.trace
.iter()
.flat_map(|snapshot| snapshot.events.iter().map(String::as_str))
.collect::<Vec<_>>();
assert_eq!(events.iter().filter(|event| **event == "StopSound").count(), 3);
assert_eq!(events.iter().filter(|event| **event == "Sound(2013)").count(), 4);
assert_eq!(events.iter().filter(|event| **event == "Sound(2012)").count(), 2);
}
#[test]
fn every_claw_scenario_completes_its_seeded_capture() {
for scenario in [