fix(game): honor original mechanism timer choices
Map detail levels 1-5 to the recovered 50, 40, 30, 20, and 10 ms callback periods for claw animation while retaining the invariant 10 ms physics step. This removes the hard-coded default cadence from non-default configurations. Keep repaint batching documented separately because Rust exposes intermediate physics states instead of reproducing Win16 GDI callback batching. Test Plan: - `cargo test --all-targets` -- 51 passed - `cargo clippy --all-targets -- -D warnings` -- passed - `cargo build --profile production` -- passed - all five claw cadence boundary tests -- passed - `git diff --cached --check` -- passed
This commit is contained in:
@@ -50,18 +50,16 @@ renamed.
|
||||
|
||||
## Explicit pending evidence
|
||||
|
||||
- Differentially verify the recovered 10/20/30/40/50 ms timer choices against
|
||||
live original captures; the Rust port intentionally uses the default 30 ms
|
||||
claw cadence independently of render rate.
|
||||
- Transcribe the full fixed-point flipper impulse calculation, then compare it
|
||||
with the Rust floating-point collision response.
|
||||
- Force each chosen claw terminal in the live original and compare its complete
|
||||
frame/trajectory sequence with the deterministic Rust trace. A controlled
|
||||
collision probe has covered terminal 18; terminals 1, 6, and 7 remain.
|
||||
- Transcribe the full raw fixed-point flipper impulse calculation. Production
|
||||
behavior currently uses live-fitted up/down transfer curves with exact record
|
||||
geometry and probe tests.
|
||||
- Reproduce original callback batching for non-default detail settings. Physics
|
||||
substeps and claw cadence are recovered, but Rust presents intermediate 10 ms
|
||||
physics states instead of batching 1-5 substeps into one Win16 repaint.
|
||||
|
||||
Those items are intentionally not counted as semantic parity. The readable
|
||||
C file preserves their raw constants and labels the unresolved conversion so a
|
||||
later implementation cannot silently turn an estimate into claimed evidence.
|
||||
C files preserve their raw constants and label unresolved batching/fitted
|
||||
arithmetic so later work cannot silently turn an estimate into claimed evidence.
|
||||
|
||||
## Runtime comparison on 2026-08-22
|
||||
|
||||
|
||||
+3
-2
@@ -64,8 +64,9 @@ validation runs.
|
||||
| Sound | F12 | - |
|
||||
|
||||
The five original speed choices remain available in settings. Physics now uses
|
||||
the original invariant 100 Hz millipixel substep; exact setting-specific timer
|
||||
batching for presentation and mechanism animation remains under reconstruction.
|
||||
the original invariant 100 Hz millipixel substep, and claw animation follows the
|
||||
recovered 50/40/30/20/10 ms timer choices. Setting-specific repaint batching
|
||||
remains a presentation-only reconstruction boundary.
|
||||
|
||||
The help screen is the original artwork in English, German, French, Italian,
|
||||
or Spanish. As instructed on that screen, double-clicking its upper-left exit
|
||||
|
||||
+23
-3
@@ -31,6 +31,7 @@ const CLAW_TRIGGER_RADIUS: f32 = 19.0;
|
||||
// frame. Keeping that cadence independent of render rate makes captures
|
||||
// deterministic on modern machines.
|
||||
const CLAW_FRAME_SECONDS: f32 = 0.030;
|
||||
const DETAIL_TIMER_SECONDS: [f32; 5] = [0.050, 0.040, 0.030, 0.020, 0.010];
|
||||
const CLAW_TERMINAL_FRAMES: [u8; 4] = [1, 6, 7, 18];
|
||||
const ORIGINAL_BALL_SPEED_PER_SECOND: f32 = 380.0;
|
||||
|
||||
@@ -225,6 +226,7 @@ pub struct Game {
|
||||
trigger_contacts: [bool; 176],
|
||||
object_active: [bool; 176],
|
||||
target_effect: u8,
|
||||
claw_frame_seconds: f32,
|
||||
}
|
||||
|
||||
impl Game {
|
||||
@@ -263,6 +265,7 @@ impl Game {
|
||||
trigger_contacts: [false; 176],
|
||||
object_active: initial_object_activity(),
|
||||
target_effect: 0,
|
||||
claw_frame_seconds: CLAW_FRAME_SECONDS,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -315,11 +318,12 @@ impl Game {
|
||||
self.player_entry = PlayerEntry::Closed;
|
||||
}
|
||||
|
||||
pub fn update(&mut self, frame_time: f32, _detail: u8, controls: Controls) -> Vec<Event> {
|
||||
pub fn update(&mut self, frame_time: f32, detail: u8, controls: Controls) -> Vec<Event> {
|
||||
if self.finished {
|
||||
return Vec::new();
|
||||
}
|
||||
self.last_collision_id = None;
|
||||
self.claw_frame_seconds = DETAIL_TIMER_SECONDS[usize::from(detail.clamp(1, 5) - 1)];
|
||||
let mut events = Vec::new();
|
||||
let old_flippers = self.flippers;
|
||||
self.flippers.left_raised = controls.left_flipper && !self.tilted;
|
||||
@@ -909,8 +913,8 @@ impl Game {
|
||||
return;
|
||||
}
|
||||
self.claw.frame_accumulator += dt;
|
||||
while self.claw.active && self.claw.frame_accumulator >= CLAW_FRAME_SECONDS {
|
||||
self.claw.frame_accumulator -= CLAW_FRAME_SECONDS;
|
||||
while self.claw.active && self.claw.frame_accumulator >= self.claw_frame_seconds {
|
||||
self.claw.frame_accumulator -= self.claw_frame_seconds;
|
||||
self.advance_claw(events);
|
||||
}
|
||||
}
|
||||
@@ -1581,6 +1585,22 @@ mod tests {
|
||||
assert!(!game.claw.active);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn claw_frame_cadence_follows_the_five_original_timer_choices() {
|
||||
for (detail, frame_seconds) in DETAIL_TIMER_SECONDS.into_iter().enumerate() {
|
||||
let mut game = Game::new_with_seed(1, 7);
|
||||
game.begin_claw_scenario(6);
|
||||
let detail = u8::try_from(detail + 1).expect("five detail levels fit u8");
|
||||
game.update(0.0, detail, Controls::default());
|
||||
let mut events = Vec::new();
|
||||
|
||||
game.update_claw(frame_seconds - 0.001, &mut events);
|
||||
assert_eq!(game.claw.frame, 10);
|
||||
game.update_claw(0.001_1, &mut events);
|
||||
assert_eq!(game.claw.frame, 9);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn claw_uses_the_original_circular_trigger_not_a_broad_rectangle() {
|
||||
let mut game = Game::new(1);
|
||||
|
||||
Reference in New Issue
Block a user