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
|
## Explicit pending evidence
|
||||||
|
|
||||||
- Differentially verify the recovered 10/20/30/40/50 ms timer choices against
|
- Transcribe the full raw fixed-point flipper impulse calculation. Production
|
||||||
live original captures; the Rust port intentionally uses the default 30 ms
|
behavior currently uses live-fitted up/down transfer curves with exact record
|
||||||
claw cadence independently of render rate.
|
geometry and probe tests.
|
||||||
- Transcribe the full fixed-point flipper impulse calculation, then compare it
|
- Reproduce original callback batching for non-default detail settings. Physics
|
||||||
with the Rust floating-point collision response.
|
substeps and claw cadence are recovered, but Rust presents intermediate 10 ms
|
||||||
- Force each chosen claw terminal in the live original and compare its complete
|
physics states instead of batching 1-5 substeps into one Win16 repaint.
|
||||||
frame/trajectory sequence with the deterministic Rust trace. A controlled
|
|
||||||
collision probe has covered terminal 18; terminals 1, 6, and 7 remain.
|
|
||||||
|
|
||||||
Those items are intentionally not counted as semantic parity. The readable
|
Those items are intentionally not counted as semantic parity. The readable
|
||||||
C file preserves their raw constants and labels the unresolved conversion so a
|
C files preserve their raw constants and label unresolved batching/fitted
|
||||||
later implementation cannot silently turn an estimate into claimed evidence.
|
arithmetic so later work cannot silently turn an estimate into claimed evidence.
|
||||||
|
|
||||||
## Runtime comparison on 2026-08-22
|
## Runtime comparison on 2026-08-22
|
||||||
|
|
||||||
|
|||||||
+3
-2
@@ -64,8 +64,9 @@ validation runs.
|
|||||||
| Sound | F12 | - |
|
| Sound | F12 | - |
|
||||||
|
|
||||||
The five original speed choices remain available in settings. Physics now uses
|
The five original speed choices remain available in settings. Physics now uses
|
||||||
the original invariant 100 Hz millipixel substep; exact setting-specific timer
|
the original invariant 100 Hz millipixel substep, and claw animation follows the
|
||||||
batching for presentation and mechanism animation remains under reconstruction.
|
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,
|
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
|
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
|
// frame. Keeping that cadence independent of render rate makes captures
|
||||||
// deterministic on modern machines.
|
// deterministic on modern machines.
|
||||||
const CLAW_FRAME_SECONDS: f32 = 0.030;
|
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 CLAW_TERMINAL_FRAMES: [u8; 4] = [1, 6, 7, 18];
|
||||||
const ORIGINAL_BALL_SPEED_PER_SECOND: f32 = 380.0;
|
const ORIGINAL_BALL_SPEED_PER_SECOND: f32 = 380.0;
|
||||||
|
|
||||||
@@ -225,6 +226,7 @@ pub struct Game {
|
|||||||
trigger_contacts: [bool; 176],
|
trigger_contacts: [bool; 176],
|
||||||
object_active: [bool; 176],
|
object_active: [bool; 176],
|
||||||
target_effect: u8,
|
target_effect: u8,
|
||||||
|
claw_frame_seconds: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Game {
|
impl Game {
|
||||||
@@ -263,6 +265,7 @@ impl Game {
|
|||||||
trigger_contacts: [false; 176],
|
trigger_contacts: [false; 176],
|
||||||
object_active: initial_object_activity(),
|
object_active: initial_object_activity(),
|
||||||
target_effect: 0,
|
target_effect: 0,
|
||||||
|
claw_frame_seconds: CLAW_FRAME_SECONDS,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -315,11 +318,12 @@ impl Game {
|
|||||||
self.player_entry = PlayerEntry::Closed;
|
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 {
|
if self.finished {
|
||||||
return Vec::new();
|
return Vec::new();
|
||||||
}
|
}
|
||||||
self.last_collision_id = None;
|
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 mut events = Vec::new();
|
||||||
let old_flippers = self.flippers;
|
let old_flippers = self.flippers;
|
||||||
self.flippers.left_raised = controls.left_flipper && !self.tilted;
|
self.flippers.left_raised = controls.left_flipper && !self.tilted;
|
||||||
@@ -909,8 +913,8 @@ impl Game {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
self.claw.frame_accumulator += dt;
|
self.claw.frame_accumulator += dt;
|
||||||
while self.claw.active && self.claw.frame_accumulator >= CLAW_FRAME_SECONDS {
|
while self.claw.active && self.claw.frame_accumulator >= self.claw_frame_seconds {
|
||||||
self.claw.frame_accumulator -= CLAW_FRAME_SECONDS;
|
self.claw.frame_accumulator -= self.claw_frame_seconds;
|
||||||
self.advance_claw(events);
|
self.advance_claw(events);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1581,6 +1585,22 @@ mod tests {
|
|||||||
assert!(!game.claw.active);
|
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]
|
#[test]
|
||||||
fn claw_uses_the_original_circular_trigger_not_a_broad_rectangle() {
|
fn claw_uses_the_original_circular_trigger_not_a_broad_rectangle() {
|
||||||
let mut game = Game::new(1);
|
let mut game = Game::new(1);
|
||||||
|
|||||||
Reference in New Issue
Block a user