fix(attract): restore all remainder-driven cycles
The same raw Div32 pattern used by the item animation appears in every nested idle phase: after division by 32, 40, or 16, the binary copies the remainder from CX:BX before the second division. Readable C and Rust used quotients for targets, word quads, the record strip, and the four-point chase, turning short repeating chases into slow one-shot progressions. Use `%32/2`, `%32/4`, `%40/4`, and `%16/4` for those cycles alongside the already corrected `%144/8` items. Preserve the first-cycle phase-zero delay, then repeat each incremental overlay state exactly. Add C and Rust coverage at phase activation, reversal, deactivation, and wrap boundaries. Test Plan: - raw instruction review at `1000:01b7-063b` -- all remainder transfers confirmed - `cargo test --workspace --all-targets --all-features` -- 125 passed - `cargo clippy --workspace --all-targets --all-features -- -D warnings` -- passed - `bash original/tools/test_reconstructed_c.sh` -- passed - `python3 original/tools/audit_reconstruction.py --require-complete` -- passed with zero incomplete or unclassified units - `cargo run -- --simulate attract --step 145 --screenshot /tmp/tdkpin-attract-remainder.png` -- passed; visually inspected at 640x460 - `rumdl check --flavor commonmark RECONSTRUCTION.md CHANGELOG.md` -- passed - `git diff --cached --check` -- passed
This commit is contained in:
+29
-24
@@ -115,18 +115,18 @@ impl AttractAnimation {
|
||||
}
|
||||
|
||||
fn target_active(self, index: usize) -> bool {
|
||||
let start = if index == 0 {
|
||||
2
|
||||
let index = u32::try_from(index).unwrap_or(0);
|
||||
let first_activation = if index == 0 {
|
||||
32
|
||||
} else {
|
||||
u32::try_from(index).unwrap_or(0) * 64
|
||||
index * 2
|
||||
};
|
||||
let end = u32::try_from(index + 8).unwrap_or(8) * 64;
|
||||
self.tick >= start && self.tick < end
|
||||
self.tick >= first_activation && self.tick.wrapping_sub(index * 2) % 32 < 16
|
||||
}
|
||||
|
||||
fn active_word_quad(self) -> Option<usize> {
|
||||
(self.tick >= 4).then(|| {
|
||||
let phase = (self.tick / 128).min(8);
|
||||
let phase = (self.tick % 32) / 4;
|
||||
usize::try_from(if phase <= 4 { phase } else { 8 - phase }).unwrap_or(0)
|
||||
})
|
||||
}
|
||||
@@ -136,17 +136,17 @@ impl AttractAnimation {
|
||||
}
|
||||
|
||||
fn record_strip_active(self, index: usize) -> bool {
|
||||
let start = if index == 0 {
|
||||
4
|
||||
let index = u32::try_from(index).unwrap_or(0);
|
||||
let first_activation = if index == 0 {
|
||||
40
|
||||
} else {
|
||||
u32::try_from(index).unwrap_or(0) * 160
|
||||
index * 4
|
||||
};
|
||||
let end = u32::try_from(index + 5).unwrap_or(5) * 160;
|
||||
self.tick >= start && self.tick < end
|
||||
self.tick >= first_activation && self.tick.wrapping_sub(index * 4) % 40 < 20
|
||||
}
|
||||
|
||||
fn chase_index(self) -> Option<usize> {
|
||||
(self.tick >= 4).then(|| usize::try_from((self.tick / 64).min(3)).unwrap_or(0))
|
||||
(self.tick >= 4).then(|| usize::try_from((self.tick % 16) / 4).unwrap_or(0))
|
||||
}
|
||||
|
||||
fn item_index(self) -> Option<usize> {
|
||||
@@ -1384,22 +1384,27 @@ mod tests {
|
||||
..AttractAnimation::default()
|
||||
};
|
||||
|
||||
assert!(!at(1).target_active(0));
|
||||
assert!(at(2).target_active(0));
|
||||
assert!(at(64).target_active(1));
|
||||
assert!(!at(512).target_active(0));
|
||||
assert!(!at(31).target_active(0));
|
||||
assert!(at(32).target_active(0));
|
||||
assert!(at(2).target_active(1));
|
||||
assert!(!at(18).target_active(1));
|
||||
assert!(at(34).target_active(1));
|
||||
assert_eq!(at(3).active_word_quad(), None);
|
||||
assert_eq!(at(4).active_word_quad(), Some(0));
|
||||
assert_eq!(at(512).active_word_quad(), Some(4));
|
||||
assert_eq!(at(640).active_word_quad(), Some(3));
|
||||
assert_eq!(at(4).active_word_quad(), Some(1));
|
||||
assert_eq!(at(16).active_word_quad(), Some(4));
|
||||
assert_eq!(at(20).active_word_quad(), Some(3));
|
||||
assert_eq!(at(32).active_word_quad(), Some(0));
|
||||
assert!(!at(31).magnetic_records_active());
|
||||
assert!(at(32).magnetic_records_active());
|
||||
assert!(!at(48).magnetic_records_active());
|
||||
assert!(at(4).record_strip_active(0));
|
||||
assert!(at(160).record_strip_active(1));
|
||||
assert!(!at(800).record_strip_active(0));
|
||||
assert_eq!(at(4).chase_index(), Some(0));
|
||||
assert_eq!(at(192).chase_index(), Some(3));
|
||||
assert!(!at(39).record_strip_active(0));
|
||||
assert!(at(40).record_strip_active(0));
|
||||
assert!(at(4).record_strip_active(1));
|
||||
assert!(!at(24).record_strip_active(1));
|
||||
assert_eq!(at(4).chase_index(), Some(1));
|
||||
assert_eq!(at(8).chase_index(), Some(2));
|
||||
assert_eq!(at(12).chase_index(), Some(3));
|
||||
assert_eq!(at(16).chase_index(), Some(0));
|
||||
assert_eq!(at(7).item_index(), None);
|
||||
assert_eq!(at(8).item_index(), Some(0));
|
||||
assert_eq!(at(72).item_index(), Some(8));
|
||||
|
||||
Reference in New Issue
Block a user