fix(ui): reproduce long-idle item index wrap

The attract item animation computes `18 - phase` as a signed value without a
lower bound. At phases 19 through 21, the subsequent 16-bit handle index wraps
backward from the DAT801 table into DAT703, DAT702, and DAT701, copying each
bitmap's upper 68x61 region into the item panel. Rust stopped drawing after
phase 18 and hid this observable long-idle behavior.

Render those three adjacent status crops and return to the inactive panel for
later phases. Raise the deterministic simulation ceiling to 100,000 frames so
the first wrapped phase at 10.94 minutes can be captured and inspected.

Test Plan:
- `cargo test --workspace --all-targets --all-features` -- 125 passed
- `cargo clippy --workspace --all-targets --all-features -- -D warnings` -- passed
- `rumdl check --flavor commonmark RECONSTRUCTION.md CHANGELOG.md` -- passed
- `cargo run -- --simulate attract --step 78797 --screenshot /tmp/tdkpin-attract-wrap-19.png` -- passed; DAT703 crop visually inspected at 640x460
- `git diff --cached --check` -- passed
This commit is contained in:
2026-08-23 20:45:54 +02:00
parent db0ec0bdc4
commit 5b8351ca1f
4 changed files with 29 additions and 3 deletions
+23
View File
@@ -159,6 +159,15 @@ impl AttractAnimation {
(item != 0).then(|| usize::try_from(item - 1).unwrap_or(0))
}
fn wrapped_status_item_index(self) -> Option<usize> {
match self.tick / 1_152 {
19 => Some(2),
20 => Some(1),
21 => Some(0),
_ => None,
}
}
fn marquee_source_x(self) -> Option<f32> {
(self.score_counter != 0).then(|| {
let phase = self.score_counter % 49;
@@ -611,6 +620,16 @@ impl App {
300.0,
WHITE,
);
} else if let Some(status) = self.attract.wrapped_status_item_index() {
draw_texture_region(
&self.assets.media[status],
123.0,
300.0,
68.0,
61.0,
0.0,
0.0,
);
}
self.draw_intro_marquee();
}
@@ -1405,6 +1424,10 @@ mod tests {
assert_eq!(at(10_368).item_index(), Some(8));
assert_eq!(at(11_520).item_index(), Some(7));
assert_eq!(at(20_736).item_index(), None);
assert_eq!(at(21_888).wrapped_status_item_index(), Some(2));
assert_eq!(at(23_040).wrapped_status_item_index(), Some(1));
assert_eq!(at(24_192).wrapped_status_item_index(), Some(0));
assert_eq!(at(25_344).wrapped_status_item_index(), None);
}
#[test]