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
+147
View File
@@ -216,6 +216,10 @@ impl App {
}
fn play_event(&self, event: Event) {
if event == Event::StopSound {
self.assets.stop_all_sounds();
return;
}
if let Some(resource) = event.sound_resource() {
self.assets.play(resource, self.saved.settings.sounds);
}
@@ -363,6 +367,10 @@ impl App {
return;
};
if let Some(frame) = game.panel_frame {
self.draw_panel_animation(frame);
}
if game.wheel_animation > 0.0 {
let frame = animation_frame(f64::from(0.65 - game.wheel_animation), 18.0, 5);
draw_texture_ex(
@@ -508,6 +516,120 @@ impl App {
}
}
#[allow(clippy::cast_precision_loss, clippy::too_many_lines)]
fn draw_panel_animation(&self, frame: u16) {
draw_texture_region(&self.assets.active_table, 79.0, 0.0, 91.0, 123.0, 79.0, 0.0);
let frame = i32::from(frame);
let narrow_a = &self.assets.panel_pair_narrow_a;
let narrow_b = &self.assets.panel_pair_narrow_b;
let wide_a = &self.assets.panel_pair_wide_a;
let wide_b = &self.assets.panel_pair_wide_b;
if (1..=44).contains(&frame) {
draw_texture_region(
narrow_a,
99.0,
0.0,
45.0,
(frame * 2) as f32,
0.0,
((45 - frame) * 2 - 1) as f32,
);
} else if (45..=57).contains(&frame) {
draw_texture_region(narrow_a, 99.0, 0.0, 45.0, 88.0, 0.0, 0.0);
} else if (58..=65).contains(&frame) {
draw_texture_region(narrow_b, 99.0, 0.0, 45.0, 88.0, 0.0, 0.0);
} else if (66..=128).contains(&frame) {
draw_texture_region(&self.assets.wheel, 79.0, 33.0, 91.0, 90.0, 180.0, 90.0);
if frame < 83 {
draw_texture_region(wide_a, 79.0, ((82 - frame) * 2) as f32, 91.0, 90.0, 0.0, 0.0);
} else {
let height = (128 - frame) * 2;
draw_texture_region(
wide_a,
79.0,
0.0,
91.0,
height as f32,
0.0,
((frame - 82) * 2) as f32,
);
}
if frame < 111 {
let height = (110 - frame) * 2;
draw_texture_region(
narrow_b,
99.0,
0.0,
45.0,
height as f32,
0.0,
((frame - 66) * 2) as f32,
);
}
} else if (129..=158).contains(&frame) {
draw_texture_region(&self.assets.wheel, 79.0, 33.0, 91.0, 90.0, 180.0, 90.0);
} else if (159..=221).contains(&frame) {
draw_texture_region(&self.assets.wheel, 79.0, 33.0, 91.0, 90.0, 180.0, 90.0);
if frame < 204 {
let height = (frame - 159) * 2;
draw_texture_region(
wide_b,
79.0,
0.0,
91.0,
height as f32,
0.0,
((204 - frame) * 2) as f32,
);
} else {
draw_texture_region(
wide_b,
79.0,
((frame - 204) * 2) as f32,
91.0,
90.0,
0.0,
0.0,
);
}
if frame > 176 {
if frame == 221 {
draw_texture_region(narrow_b, 99.0, 0.0, 45.0, 88.0, 0.0, 0.0);
} else {
let height = (frame - 177) * 2;
draw_texture_region(
narrow_b,
99.0,
0.0,
45.0,
height as f32,
0.0,
((222 - frame) * 2 - 1) as f32,
);
}
}
} else if (222..=229).contains(&frame) {
draw_texture_region(wide_b, 79.0, 33.0, 91.0, 90.0, 0.0, 0.0);
draw_texture_region(narrow_b, 99.0, 0.0, 45.0, 88.0, 0.0, 0.0);
} else if (230..=237).contains(&frame) {
draw_texture_region(wide_b, 79.0, 33.0, 91.0, 90.0, 0.0, 0.0);
draw_texture_region(narrow_a, 99.0, 0.0, 45.0, 88.0, 0.0, 0.0);
} else if (238..=281).contains(&frame) {
draw_texture_region(wide_b, 79.0, 33.0, 91.0, 90.0, 0.0, 0.0);
let height = (281 - frame) * 2;
draw_texture_region(
narrow_a,
99.0,
0.0,
45.0,
height as f32,
0.0,
((frame - 238) * 2) as f32,
);
}
}
#[allow(clippy::cast_precision_loss)]
fn draw_displays(&self, game: &Game) {
for region in [
@@ -751,6 +873,31 @@ fn draw_panel(x: f32, y: f32, width: f32, height: f32) {
draw_rectangle_lines(x + 4.0, y + 4.0, width - 8.0, height - 8.0, 2.0, DARKGRAY);
}
fn draw_texture_region(
texture: &Texture2D,
destination_x: f32,
destination_y: f32,
width: f32,
height: f32,
source_x: f32,
source_y: f32,
) {
if width <= 0.0 || height <= 0.0 {
return;
}
draw_texture_ex(
texture,
destination_x,
destination_y,
WHITE,
DrawTextureParams {
dest_size: Some(vec2(width, height)),
source: Some(Rect::new(source_x, source_y, width, height)),
..Default::default()
},
);
}
#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
fn animation_frame(elapsed: f64, frames_per_second: f64, frame_count: usize) -> usize {
((elapsed.max(0.0) * frames_per_second) as usize) % frame_count