fix(game): restore six-state target rotation

Replace the free-running 68x60 wheel animation with the original six-callback
91x90 DAT600 mechanism. Start WAVE 2011 at state one, advance once per selected
detail timer, and rotate the five per-player lock contact/item values exactly at
state six.

Decode the 17x17 DAT600 target image/mask pair and render all six recovered
position sets over the correct atlas or overlay-B base. Preserve filled targets
in their resting state after rotation and add a deterministic `targets` scenario
for trace and framebuffer validation.

Test Plan:
- `cargo test --all-targets` -- passed, 74 tests
- `cargo clippy --all-targets -- -D warnings` -- passed
- `rumdl check CHANGELOG.md README.md RECONSTRUCTION.md` -- passed
- rendered all six target-rotation states -- visually verified
- `git diff --cached --check` -- passed
This commit is contained in:
2026-08-23 17:53:40 +02:00
parent 4b84460c93
commit 096d923c79
7 changed files with 151 additions and 43 deletions
+30 -4
View File
@@ -17,6 +17,7 @@ pub enum Scenario {
Launcher,
Flippers,
Panel,
Targets,
Claw1,
Claw6,
Claw7,
@@ -30,6 +31,7 @@ impl Scenario {
Self::Launcher => "launcher",
Self::Flippers => "flippers",
Self::Panel => "panel",
Self::Targets => "targets",
Self::Claw1 => "claw-1",
Self::Claw6 => "claw-6",
Self::Claw7 => "claw-7",
@@ -43,7 +45,7 @@ impl Scenario {
Self::Claw6 => Some(6),
Self::Claw7 => Some(7),
Self::Claw18 => Some(18),
Self::Autoplay | Self::Launcher | Self::Flippers | Self::Panel => None,
Self::Autoplay | Self::Launcher | Self::Flippers | Self::Panel | Self::Targets => None,
}
}
}
@@ -57,12 +59,13 @@ impl FromStr for Scenario {
"launcher" => Ok(Self::Launcher),
"flippers" => Ok(Self::Flippers),
"panel" => Ok(Self::Panel),
"targets" => Ok(Self::Targets),
"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, panel, claw-1, claw-6, claw-7, or claw-18"
"unknown scenario {value:?}; expected autoplay, launcher, flippers, panel, targets, claw-1, claw-6, claw-7, or claw-18"
)),
}
}
@@ -146,7 +149,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, panel, 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, targets, claw-1, claw-6, claw-7, claw-18"
}
#[derive(Debug, Serialize, PartialEq)]
@@ -202,6 +205,10 @@ impl Simulation {
game.panel_frame = Some(0);
game.wheel_holes.fill(true);
}
if scenario == Scenario::Targets {
game.target_rotation_state = Some(0);
game.wheel_holes = [true, false, true, false, false];
}
let initial_events = scenario
.claw_terminal()
.map_or_else(Vec::new, |terminal| game.begin_claw_scenario(terminal));
@@ -326,7 +333,12 @@ fn controls_for(scenario: Scenario, step: u64) -> Controls {
..Controls::default()
}
}
Scenario::Panel | Scenario::Claw1 | Scenario::Claw6 | Scenario::Claw7 | Scenario::Claw18 => {
Scenario::Panel
| Scenario::Targets
| Scenario::Claw1
| Scenario::Claw6
| Scenario::Claw7
| Scenario::Claw18 => {
Controls::default()
}
}
@@ -461,6 +473,20 @@ mod tests {
assert_eq!(events.iter().filter(|event| **event == "Sound(2012)").count(), 2);
}
#[test]
fn target_scenario_runs_all_six_rotation_states() {
let mut simulation = Simulation::new(Scenario::Targets, 1);
simulation.advance_to(4);
assert_eq!(simulation.game.target_rotation_state, Some(1));
assert_eq!(simulation.trace[4].events, ["Sound(2011)"]);
simulation.advance_to(22);
assert_eq!(simulation.game.target_rotation_state, Some(6));
assert_eq!(simulation.game.wheel_holes, [false, true, false, false, true]);
simulation.advance_to(26);
assert_eq!(simulation.game.target_rotation_state, None);
}
#[test]
fn every_claw_scenario_completes_its_seeded_capture() {
for scenario in [