fix(physics): match live tangent response transitions
Correct the common projection to spin*0.4 + normal_velocity*response_tangent and seal line/circle behavior against current Wine one-substep traces. Preserve record-two's in-response special-respawn continuation, producing the live (16698,21191)/(-302,-1809) state after publishing (17000,23000)/(0,3040), and update the readable C evidence. Test Plan: - bash original/tools/test_reconstructed_c.sh - python3 original/tools/audit_reconstruction.py --require-complete - cargo test --all-targets - cargo clippy --all-targets --all-features -- -D warnings - rumdl check original/C_RECONSTRUCTION_FINAL_AUDIT.md tdkpin-rs/CHANGELOG.md tdkpin-rs/RECONSTRUCTION.md tdkpin-rs/README.md - live Wine record 2 trace: /tmp/original-special-respawn.csv - live Wine object 155 trace: /tmp/original-circle155-after.csv - git diff --check
This commit is contained in:
+57
-4
@@ -803,7 +803,15 @@ impl Game {
|
||||
|
||||
if let Some(wall_id) = hit_wall {
|
||||
if wall_id == 2 {
|
||||
let special_respawn = self.special_respawn_pending();
|
||||
let response_spin = self.ball.spin;
|
||||
self.drain(events);
|
||||
if special_respawn {
|
||||
let origin = MilliVec::from_position(self.ball.position);
|
||||
self.ball.position = origin.add(velocity).to_position();
|
||||
self.ball.velocity = velocity.to_velocity_per_second();
|
||||
self.ball.spin = response_spin;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if wall_id == 25
|
||||
@@ -2006,9 +2014,7 @@ impl Game {
|
||||
events.push(Event::Drain);
|
||||
return;
|
||||
}
|
||||
if self.score_mode == ScoreMode::Normal
|
||||
&& matches!(self.multiball_state, MultiballState::Ready | MultiballState::Active)
|
||||
{
|
||||
if self.special_respawn_pending() {
|
||||
self.ball = Ball {
|
||||
position: vec2(17.0, 23.0),
|
||||
velocity: MilliVec { x: 0, y: 3_040 }.to_velocity_per_second(),
|
||||
@@ -2064,6 +2070,12 @@ impl Game {
|
||||
self.finished = true;
|
||||
}
|
||||
|
||||
fn special_respawn_pending(&self) -> bool {
|
||||
self.secondary_ball.is_none()
|
||||
&& self.score_mode == ScoreMode::Normal
|
||||
&& matches!(self.multiball_state, MultiballState::Ready | MultiballState::Active)
|
||||
}
|
||||
|
||||
fn save_current_rule_state(&mut self) {
|
||||
self.players[self.current_player].rules = RuleState {
|
||||
wheel_holes: self.wheel_holes,
|
||||
@@ -2640,7 +2652,20 @@ mod tests {
|
||||
|
||||
game.fixed_update(&mut events);
|
||||
assert!(!game.ball.in_launcher);
|
||||
assert!(game.ball.position.y > 23.0);
|
||||
assert_eq!(
|
||||
MilliVec::from_position(game.ball.position),
|
||||
MilliVec {
|
||||
x: 17_000,
|
||||
y: 26_055,
|
||||
}
|
||||
);
|
||||
assert_eq!(
|
||||
MilliVec::from_velocity_per_second(game.ball.velocity),
|
||||
MilliVec {
|
||||
x: 0,
|
||||
y: 3_055,
|
||||
}
|
||||
);
|
||||
assert_eq!(game.wheel_reset_gate, WheelResetGate::Suppressed);
|
||||
|
||||
game.ball.position = vec2(17.0, 31.0);
|
||||
@@ -2649,6 +2674,34 @@ mod tests {
|
||||
assert_eq!(game.wheel_reset_gate, WheelResetGate::Enabled);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn record_two_response_continues_after_special_respawn_like_live_original() {
|
||||
let mut game = Game::new(1);
|
||||
game.multiball_state = MultiballState::Active;
|
||||
game.ball.in_launcher = false;
|
||||
game.ball.position = vec2(157.0, 453.0);
|
||||
game.ball.velocity = MilliVec { x: 0, y: 3_000 }.to_velocity_per_second();
|
||||
|
||||
assert!(game.fixed_update(&mut Vec::new()));
|
||||
|
||||
assert_eq!(
|
||||
MilliVec::from_position(game.ball.position),
|
||||
MilliVec {
|
||||
x: 16_698,
|
||||
y: 21_191,
|
||||
}
|
||||
);
|
||||
assert_eq!(
|
||||
MilliVec::from_velocity_per_second(game.ball.velocity),
|
||||
MilliVec {
|
||||
x: -302,
|
||||
y: -1_809,
|
||||
}
|
||||
);
|
||||
assert_eq!(game.multiball_state, MultiballState::Unavailable);
|
||||
assert_eq!(game.wheel_reset_gate, WheelResetGate::Suppressed);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn armed_effect_seven_also_special_respawns_before_multiball_starts() {
|
||||
let mut game = Game::new(1);
|
||||
|
||||
@@ -280,7 +280,7 @@ fn apply_response(
|
||||
}
|
||||
let projection = spin
|
||||
.multiply(Real48::from_bytes([0x7f, 0xcd, 0xcc, 0xcc, 0xcc, 0x4c]))
|
||||
.subtract(coefficients.tangent)
|
||||
.add(Real48::from_i32(collision_velocity).multiply(coefficients.tangent))
|
||||
.round_i32();
|
||||
let spin = spin
|
||||
.multiply(Real48::from_bytes([0x80, 0x9a, 0x99, 0x99, 0x99, 0x19]))
|
||||
@@ -662,7 +662,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn outer_shooter_wall_matches_the_zero_spin_c_response() {
|
||||
fn outer_shooter_wall_matches_the_live_tangent_response() {
|
||||
let old = MilliVec {
|
||||
x: 326_000,
|
||||
y: 200_045,
|
||||
@@ -677,12 +677,12 @@ mod tests {
|
||||
0.6,
|
||||
0.1,
|
||||
));
|
||||
assert_eq!(velocity, MilliVec { x: -1_800, y: 45 });
|
||||
assert_eq!(velocity, MilliVec { x: -1_800, y: 345 });
|
||||
assert_eq!(
|
||||
old.add(velocity),
|
||||
MilliVec {
|
||||
x: 324_200,
|
||||
y: 200_090
|
||||
y: 200_390
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -705,7 +705,7 @@ mod tests {
|
||||
|
||||
let response = candidate.resolve(MilliVec { x: 3_000, y: 1_000 }, Real48::ZERO);
|
||||
|
||||
assert_eq!(response.velocity, MilliVec { x: -1_800, y: 1_000 });
|
||||
assert_eq!(response.velocity, MilliVec { x: -1_800, y: 1_300 });
|
||||
assert_eq!(response.surface_distance, 2_000);
|
||||
}
|
||||
|
||||
@@ -764,7 +764,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn inner_shooter_wall_matches_the_zero_spin_c_response() {
|
||||
fn inner_shooter_wall_matches_the_live_tangent_response() {
|
||||
let old = MilliVec {
|
||||
x: 320_600,
|
||||
y: 199_325,
|
||||
@@ -779,12 +779,12 @@ mod tests {
|
||||
0.6,
|
||||
0.1,
|
||||
));
|
||||
assert_eq!(velocity, MilliVec { x: 1_080, y: -210 });
|
||||
assert_eq!(velocity, MilliVec { x: 1_080, y: -390 });
|
||||
assert_eq!(
|
||||
old.add(velocity),
|
||||
MilliVec {
|
||||
x: 321_680,
|
||||
y: 199_115
|
||||
y: 198_935
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -855,7 +855,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ordinary_circle_matches_the_zero_spin_c_response() {
|
||||
fn ordinary_circle_matches_the_live_zero_spin_response() {
|
||||
let old = MilliVec {
|
||||
x: 183_000,
|
||||
y: 70_090,
|
||||
@@ -871,18 +871,18 @@ mod tests {
|
||||
0.1,
|
||||
0.0,
|
||||
));
|
||||
assert_eq!(velocity, MilliVec { x: 0, y: 564 });
|
||||
assert_eq!(velocity, MilliVec { x: 94, y: 564 });
|
||||
assert_eq!(
|
||||
old.add(velocity),
|
||||
MilliVec {
|
||||
x: 183_000,
|
||||
x: 183_094,
|
||||
y: 70_654
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bumper_circle_matches_the_zero_spin_c_response() {
|
||||
fn bumper_circle_matches_the_live_zero_spin_response() {
|
||||
let old = MilliVec {
|
||||
x: 165_000,
|
||||
y: 172_015,
|
||||
@@ -898,18 +898,18 @@ mod tests {
|
||||
0.1,
|
||||
0.4,
|
||||
));
|
||||
assert_eq!(velocity, MilliVec { x: 0, y: 3_096 });
|
||||
assert_eq!(velocity, MilliVec { x: 197, y: 3_096 });
|
||||
assert_eq!(
|
||||
old.add(velocity),
|
||||
MilliVec {
|
||||
x: 165_000,
|
||||
x: 165_197,
|
||||
y: 175_111
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn retained_spin_reproduces_the_live_circle_tangent() {
|
||||
fn retained_spin_adds_to_the_live_circle_tangent() {
|
||||
let response = circle_collision_response(
|
||||
MilliVec {
|
||||
x: 183_000,
|
||||
@@ -922,7 +922,7 @@ mod tests {
|
||||
Real48::from_i32(-235),
|
||||
)
|
||||
.expect("live probe enters object 155");
|
||||
assert_eq!(response.velocity, MilliVec { x: 94, y: 564 });
|
||||
assert_eq!(response.velocity, MilliVec { x: 188, y: 564 });
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
Reference in New Issue
Block a user