fix(physics): process sensors before collision response
Evaluate type-three captures from the pre-movement position, retain their missing radial rim candidates, and run type-four motion randomization before candidate resolution and position publication. Sensor groups now execute in binary ID order without Hold or Complete prematurely aborting later records. Test Plan: - cargo test --all-targets - cargo clippy --all-targets --all-features -- -D warnings - rumdl check CHANGELOG.md RECONSTRUCTION.md README.md - git diff --check
This commit is contained in:
+265
-71
@@ -5,8 +5,8 @@ use crate::{
|
||||
original_physics::{
|
||||
CollisionMaterial, CollisionResponse, GRAVITY_MILLI_PER_STEP,
|
||||
MAXIMUM_SPEED_MILLI_PER_STEP, MilliVec, STEP_SECONDS, StaticCollisionCandidate,
|
||||
ball_collision_response, circle_collision_candidate, line_collision_candidate,
|
||||
milli_distance, path_intersects_circle,
|
||||
ball_collision_response, capture_collision_candidate, circle_collision_candidate,
|
||||
line_collision_candidate, milli_distance, path_intersects_circle,
|
||||
},
|
||||
real48::Real48,
|
||||
table::{
|
||||
@@ -222,6 +222,12 @@ enum BallAction {
|
||||
Remove,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
struct SensorScanResult {
|
||||
action: BallAction,
|
||||
capture_candidate: Option<(u8, StaticCollisionCandidate)>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
struct RuleState {
|
||||
wheel_holes: [bool; 5],
|
||||
@@ -667,22 +673,40 @@ impl Game {
|
||||
}
|
||||
|
||||
let old_position = MilliVec::from_position(self.ball.position);
|
||||
let mut velocity = MilliVec::from_velocity_per_second(self.ball.velocity);
|
||||
let initial_secondary = self.secondary_ball;
|
||||
let ball_count = if initial_secondary.is_some() { 2 } else { 1 };
|
||||
let mut ball = self.ball;
|
||||
let mut velocity = MilliVec::from_velocity_per_second(ball.velocity);
|
||||
velocity.y += GRAVITY_MILLI_PER_STEP;
|
||||
velocity.clamp_speed(MAXIMUM_SPEED_MILLI_PER_STEP);
|
||||
self.apply_magnetic_fields(old_position, &mut velocity);
|
||||
let movement_velocity = velocity;
|
||||
let mut position = old_position.add(velocity);
|
||||
let best_static = self.find_static_collision_candidate(old_position, velocity);
|
||||
let mut best_collision = best_static.map(|(id, is_wall, candidate)| {
|
||||
(id, is_wall, candidate.resolve(velocity, self.ball.spin))
|
||||
});
|
||||
let mut best_static = self.find_static_collision_candidate(old_position, velocity);
|
||||
ball.velocity = velocity.to_velocity_per_second();
|
||||
let scan = self.check_sensor_objects_for_ball(
|
||||
&mut ball,
|
||||
1,
|
||||
ball_count,
|
||||
old_position,
|
||||
movement_velocity,
|
||||
events,
|
||||
);
|
||||
if let Some((id, candidate)) = scan.capture_candidate
|
||||
&& best_static.is_none_or(|(_, _, closest)| {
|
||||
candidate.surface_distance <= closest.surface_distance
|
||||
})
|
||||
{
|
||||
best_static = Some((id, false, candidate));
|
||||
}
|
||||
velocity = MilliVec::from_velocity_per_second(ball.velocity);
|
||||
let mut best_collision = best_static
|
||||
.map(|(id, is_wall, candidate)| (id, is_wall, candidate.resolve(velocity, ball.spin)));
|
||||
let mut transferred_secondary_velocity = None;
|
||||
if let Some(secondary) = self.secondary_ball {
|
||||
if let Some(secondary) = initial_secondary {
|
||||
let response = ball_collision_response(
|
||||
old_position,
|
||||
velocity,
|
||||
self.ball.spin,
|
||||
ball.spin,
|
||||
MilliVec::from_position(secondary.position),
|
||||
MilliVec::from_velocity_per_second(secondary.velocity),
|
||||
);
|
||||
@@ -708,10 +732,9 @@ impl Game {
|
||||
let mut auxiliary_fired = false;
|
||||
let (hit_wall, hit_circle) = if let Some((object_id, is_wall, response)) = best_collision {
|
||||
velocity = response.velocity;
|
||||
self.ball.spin = response.spin;
|
||||
ball.spin = response.spin;
|
||||
auxiliary_fired = response.auxiliary_fired;
|
||||
velocity.clamp_speed(MAXIMUM_SPEED_MILLI_PER_STEP);
|
||||
position = old_position.add(velocity);
|
||||
self.last_collision_id = Some(object_id);
|
||||
if object_id == 175
|
||||
&& let (Some(secondary), Some(transferred)) =
|
||||
@@ -727,8 +750,10 @@ impl Game {
|
||||
} else {
|
||||
(None, None)
|
||||
};
|
||||
self.ball.position = position.to_position();
|
||||
self.ball.velocity = velocity.to_velocity_per_second();
|
||||
velocity.clamp_speed(MAXIMUM_SPEED_MILLI_PER_STEP);
|
||||
ball.position = old_position.add(velocity).to_position();
|
||||
ball.velocity = velocity.to_velocity_per_second();
|
||||
self.ball = ball;
|
||||
|
||||
if let Some(wall_id) = hit_wall {
|
||||
if wall_id == 2 {
|
||||
@@ -765,8 +790,20 @@ impl Game {
|
||||
events.push(Event::Sound(2006));
|
||||
}
|
||||
|
||||
if self.check_sensor_objects(old_position, movement_velocity, events) != BallAction::Keep {
|
||||
return true;
|
||||
match scan.action {
|
||||
BallAction::Keep => {}
|
||||
BallAction::Suspend => return true,
|
||||
BallAction::Reset => {
|
||||
self.reset_ball_to_launcher();
|
||||
return true;
|
||||
}
|
||||
BallAction::Remove => {
|
||||
self.ball = self
|
||||
.secondary_ball
|
||||
.take()
|
||||
.expect("a multiball capture must leave the other slot active");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if self.ball.position.y > 470.0 {
|
||||
@@ -877,6 +914,7 @@ impl Game {
|
||||
self.advance_secondary_ball_slot(events, true)
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_lines)]
|
||||
fn advance_secondary_ball_slot(
|
||||
&mut self,
|
||||
events: &mut Vec<Event>,
|
||||
@@ -885,16 +923,34 @@ impl Game {
|
||||
let Some(mut ball) = self.secondary_ball.take() else {
|
||||
return true;
|
||||
};
|
||||
let primary_position = self.ball.position;
|
||||
let primary_velocity = self.ball.velocity;
|
||||
let old_position = MilliVec::from_position(ball.position);
|
||||
let mut velocity = MilliVec::from_velocity_per_second(ball.velocity);
|
||||
velocity.y += GRAVITY_MILLI_PER_STEP;
|
||||
velocity.clamp_speed(MAXIMUM_SPEED_MILLI_PER_STEP);
|
||||
self.apply_magnetic_fields(old_position, &mut velocity);
|
||||
let movement_velocity = velocity;
|
||||
let best_static = self.find_static_collision_candidate(old_position, velocity);
|
||||
let mut best_collision = best_static.map(|(id, is_wall, candidate)| {
|
||||
(id, is_wall, candidate.resolve(velocity, ball.spin))
|
||||
});
|
||||
let mut best_static = self.find_static_collision_candidate(old_position, velocity);
|
||||
ball.velocity = velocity.to_velocity_per_second();
|
||||
let scan = self.check_sensor_objects_for_ball(
|
||||
&mut ball,
|
||||
2,
|
||||
2,
|
||||
old_position,
|
||||
movement_velocity,
|
||||
events,
|
||||
);
|
||||
if let Some((id, candidate)) = scan.capture_candidate
|
||||
&& best_static.is_none_or(|(_, _, closest)| {
|
||||
candidate.surface_distance <= closest.surface_distance
|
||||
})
|
||||
{
|
||||
best_static = Some((id, false, candidate));
|
||||
}
|
||||
velocity = MilliVec::from_velocity_per_second(ball.velocity);
|
||||
let mut best_collision = best_static
|
||||
.map(|(id, is_wall, candidate)| (id, is_wall, candidate.resolve(velocity, ball.spin)));
|
||||
|
||||
let mut transferred_primary_velocity = None;
|
||||
if primary_slot_active
|
||||
@@ -902,8 +958,8 @@ impl Game {
|
||||
old_position,
|
||||
velocity,
|
||||
ball.spin,
|
||||
MilliVec::from_position(self.ball.position),
|
||||
MilliVec::from_velocity_per_second(self.ball.velocity),
|
||||
MilliVec::from_position(primary_position),
|
||||
MilliVec::from_velocity_per_second(primary_velocity),
|
||||
)
|
||||
&& best_collision.is_none_or(|(_, _, closest)| {
|
||||
response.surface_distance <= closest.surface_distance
|
||||
@@ -936,6 +992,7 @@ impl Game {
|
||||
self.ball.velocity = transferred.to_velocity_per_second();
|
||||
}
|
||||
}
|
||||
velocity.clamp_speed(MAXIMUM_SPEED_MILLI_PER_STEP);
|
||||
ball.position = old_position.add(velocity).to_position();
|
||||
ball.velocity = velocity.to_velocity_per_second();
|
||||
if hit == Some((2, true)) || ball.position.y > 470.0 {
|
||||
@@ -962,19 +1019,11 @@ impl Game {
|
||||
events.push(Event::Bumper);
|
||||
events.push(Event::Sound(2006));
|
||||
}
|
||||
let action = self.check_sensor_objects_for_ball(
|
||||
&mut ball,
|
||||
2,
|
||||
2,
|
||||
old_position,
|
||||
movement_velocity,
|
||||
events,
|
||||
);
|
||||
match action {
|
||||
match scan.action {
|
||||
BallAction::Keep | BallAction::Suspend => self.secondary_ball = Some(ball),
|
||||
BallAction::Reset | BallAction::Remove => return true,
|
||||
}
|
||||
collided || action == BallAction::Suspend
|
||||
collided || scan.action == BallAction::Suspend
|
||||
}
|
||||
|
||||
fn apply_wall_rule(&mut self, object_id: u8, events: &mut Vec<Event>) {
|
||||
@@ -1074,6 +1123,7 @@ impl Game {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
fn check_sensor_objects(
|
||||
&mut self,
|
||||
old_position: MilliVec,
|
||||
@@ -1082,7 +1132,7 @@ impl Game {
|
||||
) -> BallAction {
|
||||
let ball_count = if self.secondary_ball.is_some() { 2 } else { 1 };
|
||||
let mut ball = self.ball;
|
||||
let action = self.check_sensor_objects_for_ball(
|
||||
let scan = self.check_sensor_objects_for_ball(
|
||||
&mut ball,
|
||||
1,
|
||||
ball_count,
|
||||
@@ -1090,7 +1140,7 @@ impl Game {
|
||||
movement_velocity,
|
||||
events,
|
||||
);
|
||||
match action {
|
||||
match scan.action {
|
||||
BallAction::Keep | BallAction::Suspend => self.ball = ball,
|
||||
BallAction::Reset => self.reset_ball_to_launcher(),
|
||||
BallAction::Remove => {
|
||||
@@ -1100,7 +1150,7 @@ impl Game {
|
||||
.expect("a multiball capture must leave the other slot active");
|
||||
}
|
||||
}
|
||||
action
|
||||
scan.action
|
||||
}
|
||||
|
||||
fn check_sensor_objects_for_ball(
|
||||
@@ -1111,9 +1161,14 @@ impl Game {
|
||||
old_position: MilliVec,
|
||||
movement_velocity: MilliVec,
|
||||
events: &mut Vec<Event>,
|
||||
) -> BallAction {
|
||||
) -> SensorScanResult {
|
||||
let mut capture_candidate = None;
|
||||
let mut action = BallAction::Keep;
|
||||
if old_position.y >= 250_000 {
|
||||
return BallAction::Keep;
|
||||
return SensorScanResult {
|
||||
action: BallAction::Keep,
|
||||
capture_candidate,
|
||||
};
|
||||
}
|
||||
if !self.claw.active {
|
||||
match self.capture_record_step(
|
||||
@@ -1124,30 +1179,52 @@ impl Game {
|
||||
CLAW_TRIGGER_CENTER,
|
||||
CLAW_TRIGGER_RADIUS,
|
||||
old_position,
|
||||
&mut capture_candidate,
|
||||
events,
|
||||
) {
|
||||
CaptureStep::Holding => return BallAction::Keep,
|
||||
CaptureStep::Holding | CaptureStep::Outside => {}
|
||||
CaptureStep::Complete => {
|
||||
if ball_count == 1 {
|
||||
let terminal_frame = self.next_claw_terminal_frame();
|
||||
self.begin_claw_capture_after_hold(ball, terminal_frame, events);
|
||||
return BallAction::Suspend;
|
||||
action = BallAction::Suspend;
|
||||
} else {
|
||||
action = BallAction::Remove;
|
||||
}
|
||||
return BallAction::Remove;
|
||||
}
|
||||
CaptureStep::Outside => {}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(action) =
|
||||
self.check_lock_holes(ball, ball_number, ball_count, old_position, events)
|
||||
if let Some(completed_action) =
|
||||
self.check_lock_holes(
|
||||
ball,
|
||||
ball_number,
|
||||
ball_count,
|
||||
old_position,
|
||||
&mut capture_candidate,
|
||||
events,
|
||||
)
|
||||
{
|
||||
return action;
|
||||
action = completed_action;
|
||||
}
|
||||
if let Some(action) =
|
||||
self.check_wheel_reset(ball, ball_number, ball_count, old_position, events)
|
||||
self.check_target_sensors(
|
||||
ball,
|
||||
old_position,
|
||||
movement_velocity,
|
||||
140..=147,
|
||||
events,
|
||||
);
|
||||
if let Some(completed_action) =
|
||||
self.check_wheel_reset(
|
||||
ball,
|
||||
ball_number,
|
||||
ball_count,
|
||||
old_position,
|
||||
&mut capture_candidate,
|
||||
events,
|
||||
)
|
||||
{
|
||||
return action;
|
||||
action = completed_action;
|
||||
}
|
||||
self.check_effect_sensor(
|
||||
ball,
|
||||
@@ -1156,8 +1233,17 @@ impl Game {
|
||||
movement_velocity,
|
||||
events,
|
||||
);
|
||||
self.check_target_sensors(ball, old_position, movement_velocity, events);
|
||||
BallAction::Keep
|
||||
self.check_target_sensors(
|
||||
ball,
|
||||
old_position,
|
||||
movement_velocity,
|
||||
150..=152,
|
||||
events,
|
||||
);
|
||||
SensorScanResult {
|
||||
action,
|
||||
capture_candidate,
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::cast_possible_truncation, clippy::too_many_arguments)]
|
||||
@@ -1170,11 +1256,14 @@ impl Game {
|
||||
center: Vec2,
|
||||
radius: f32,
|
||||
previous_position: MilliVec,
|
||||
best_capture: &mut Option<(u8, StaticCollisionCandidate)>,
|
||||
events: &mut Vec<Event>,
|
||||
) -> CaptureStep {
|
||||
let current_position = MilliVec::from_position(ball.position);
|
||||
let center = MilliVec::from_position(center);
|
||||
let radius = (radius * 1_000.0).round() as i32;
|
||||
let center_view = center;
|
||||
let center = MilliVec::from_position(center_view);
|
||||
let radius_view = radius;
|
||||
let radius = (radius_view * 1_000.0).round() as i32;
|
||||
let dx = center.x.wrapping_sub(current_position.x);
|
||||
let dy = center
|
||||
.y
|
||||
@@ -1240,6 +1329,23 @@ impl Game {
|
||||
self.record_contacts[contact_index] = 0;
|
||||
ball.capture_age = 0;
|
||||
}
|
||||
let velocity = MilliVec::from_velocity_per_second(ball.velocity);
|
||||
if surface_distance <= milli_distance(velocity)
|
||||
&& surface_distance >= -11_000
|
||||
&& contact != 0
|
||||
&& let Some(candidate) = capture_collision_candidate(
|
||||
current_position,
|
||||
velocity,
|
||||
center_view,
|
||||
radius_view,
|
||||
CollisionMaterial::line(0.6, 0.0),
|
||||
)
|
||||
&& best_capture.is_none_or(|(_, closest)| {
|
||||
candidate.surface_distance <= closest.surface_distance
|
||||
})
|
||||
{
|
||||
*best_capture = Some((record_id, candidate));
|
||||
}
|
||||
CaptureStep::Outside
|
||||
}
|
||||
|
||||
@@ -1249,6 +1355,7 @@ impl Game {
|
||||
ball_number: u16,
|
||||
ball_count: u16,
|
||||
old_position: MilliVec,
|
||||
best_capture: &mut Option<(u8, StaticCollisionCandidate)>,
|
||||
events: &mut Vec<Event>,
|
||||
) -> Option<BallAction> {
|
||||
for (index, sensor) in LOCK_HOLES.into_iter().enumerate() {
|
||||
@@ -1260,10 +1367,10 @@ impl Game {
|
||||
sensor.center,
|
||||
sensor.radius,
|
||||
old_position,
|
||||
best_capture,
|
||||
events,
|
||||
) {
|
||||
CaptureStep::Outside => continue,
|
||||
CaptureStep::Holding => return Some(BallAction::Keep),
|
||||
CaptureStep::Outside | CaptureStep::Holding => continue,
|
||||
CaptureStep::Complete => {}
|
||||
}
|
||||
let filled_before = self.wheel_holes.iter().filter(|filled| **filled).count();
|
||||
@@ -1296,6 +1403,7 @@ impl Game {
|
||||
ball_number: u16,
|
||||
ball_count: u16,
|
||||
old_position: MilliVec,
|
||||
best_capture: &mut Option<(u8, StaticCollisionCandidate)>,
|
||||
events: &mut Vec<Event>,
|
||||
) -> Option<BallAction> {
|
||||
match self.capture_record_step(
|
||||
@@ -1306,10 +1414,10 @@ impl Game {
|
||||
WHEEL_RESET_SENSOR.center,
|
||||
WHEEL_RESET_SENSOR.radius,
|
||||
old_position,
|
||||
best_capture,
|
||||
events,
|
||||
) {
|
||||
CaptureStep::Outside => return None,
|
||||
CaptureStep::Holding => return Some(BallAction::Keep),
|
||||
CaptureStep::Outside | CaptureStep::Holding => return None,
|
||||
CaptureStep::Complete => {}
|
||||
}
|
||||
self.wheel_holes.fill(false);
|
||||
@@ -1333,7 +1441,6 @@ impl Game {
|
||||
if self.tilted {
|
||||
return;
|
||||
}
|
||||
let current_position = MilliVec::from_position(ball.position);
|
||||
let effect_index = usize::from(EFFECT_SENSOR.id);
|
||||
if self.object_active[effect_index] {
|
||||
let touched = path_intersects_circle(
|
||||
@@ -1343,12 +1450,7 @@ impl Game {
|
||||
EFFECT_SENSOR.radius,
|
||||
);
|
||||
let entered = touched && !self.trigger_flags[effect_index];
|
||||
self.trigger_flags[effect_index] = path_intersects_circle(
|
||||
current_position,
|
||||
MilliVec::default(),
|
||||
EFFECT_SENSOR.center,
|
||||
EFFECT_SENSOR.radius,
|
||||
);
|
||||
self.trigger_flags[effect_index] = touched;
|
||||
if entered {
|
||||
self.add_score(EFFECT_SENSOR.score, events);
|
||||
match self.target_effect {
|
||||
@@ -1389,13 +1491,16 @@ impl Game {
|
||||
ball: &mut Ball,
|
||||
old_position: MilliVec,
|
||||
movement_velocity: MilliVec,
|
||||
record_ids: std::ops::RangeInclusive<u8>,
|
||||
events: &mut Vec<Event>,
|
||||
) {
|
||||
if self.tilted {
|
||||
return;
|
||||
}
|
||||
let current_position = MilliVec::from_position(ball.position);
|
||||
for sensor in TARGET_SENSORS {
|
||||
if !record_ids.contains(&sensor.id) {
|
||||
continue;
|
||||
}
|
||||
let touched = path_intersects_circle(
|
||||
old_position,
|
||||
movement_velocity,
|
||||
@@ -1404,12 +1509,7 @@ impl Game {
|
||||
);
|
||||
let contact_index = usize::from(sensor.id);
|
||||
let entered = touched && !self.trigger_flags[contact_index];
|
||||
self.trigger_flags[contact_index] = path_intersects_circle(
|
||||
current_position,
|
||||
MilliVec::default(),
|
||||
sensor.center,
|
||||
sensor.radius,
|
||||
);
|
||||
self.trigger_flags[contact_index] = touched;
|
||||
if !entered {
|
||||
continue;
|
||||
}
|
||||
@@ -1781,6 +1881,7 @@ mod tests {
|
||||
events: &mut Vec<Event>,
|
||||
) -> CaptureStep {
|
||||
let mut ball = game.ball;
|
||||
let mut capture_candidate = None;
|
||||
let result = game.capture_record_step(
|
||||
&mut ball,
|
||||
1,
|
||||
@@ -1789,6 +1890,7 @@ mod tests {
|
||||
center,
|
||||
radius,
|
||||
previous_position,
|
||||
&mut capture_candidate,
|
||||
events,
|
||||
);
|
||||
game.ball = ball;
|
||||
@@ -2215,7 +2317,8 @@ mod tests {
|
||||
old_position,
|
||||
movement,
|
||||
&mut events,
|
||||
),
|
||||
)
|
||||
.action,
|
||||
BallAction::Keep
|
||||
);
|
||||
|
||||
@@ -2249,7 +2352,8 @@ mod tests {
|
||||
MilliVec::from_position(EFFECT_SENSOR.center),
|
||||
MilliVec::default(),
|
||||
&mut events,
|
||||
),
|
||||
)
|
||||
.action,
|
||||
BallAction::Keep
|
||||
);
|
||||
|
||||
@@ -3108,6 +3212,96 @@ mod tests {
|
||||
assert_eq!(game.ball.capture_age, 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn production_type_three_pull_tests_the_pre_movement_position() {
|
||||
let sensor = LOCK_HOLES[0];
|
||||
let mut game = Game::new(1);
|
||||
game.object_active.fill(false);
|
||||
game.object_active[usize::from(sensor.id)] = true;
|
||||
game.ball.in_launcher = false;
|
||||
game.ball.position = sensor.center + vec2(7.5, 0.0);
|
||||
game.ball.velocity = MilliVec { x: 3_800, y: 0 }.to_velocity_per_second();
|
||||
let old_position = MilliVec::from_position(game.ball.position);
|
||||
let mut expected_velocity = MilliVec { x: 3_800, y: 15 };
|
||||
expected_velocity.clamp_speed(MAXIMUM_SPEED_MILLI_PER_STEP);
|
||||
let damping = Real48::from_bytes([0x80, 0x66, 0x66, 0x66, 0x66, 0x66]);
|
||||
expected_velocity.x = Real48::from_i32(expected_velocity.x)
|
||||
.multiply(damping)
|
||||
.round_i32()
|
||||
.wrapping_sub(150);
|
||||
expected_velocity.y = Real48::from_i32(expected_velocity.y)
|
||||
.multiply(damping)
|
||||
.round_i32()
|
||||
.wrapping_sub(150);
|
||||
|
||||
assert!(!game.fixed_update(STEP_SECONDS, &mut Vec::new()));
|
||||
|
||||
assert_eq!(
|
||||
MilliVec::from_velocity_per_second(game.ball.velocity),
|
||||
expected_velocity
|
||||
);
|
||||
assert_eq!(
|
||||
MilliVec::from_position(game.ball.position),
|
||||
old_position.add(expected_velocity)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn production_type_three_contacted_rim_participates_in_candidate_ordering() {
|
||||
let sensor = LOCK_HOLES[0];
|
||||
let mut game = Game::new(1);
|
||||
game.object_active.fill(false);
|
||||
game.object_active[usize::from(sensor.id)] = true;
|
||||
game.record_contacts[usize::from(sensor.id)] = 99;
|
||||
game.ball.in_launcher = false;
|
||||
game.ball.position = sensor.center + vec2(17.0, 0.0);
|
||||
game.ball.velocity = MilliVec { x: -3_000, y: 0 }.to_velocity_per_second();
|
||||
let old_position = MilliVec::from_position(game.ball.position);
|
||||
|
||||
assert!(game.fixed_update(STEP_SECONDS, &mut Vec::new()));
|
||||
|
||||
let velocity = MilliVec::from_velocity_per_second(game.ball.velocity);
|
||||
assert!(velocity.x > 0, "the type-three rim must reflect the ball");
|
||||
assert_eq!(game.last_collision_id, Some(sensor.id));
|
||||
assert_eq!(
|
||||
MilliVec::from_position(game.ball.position),
|
||||
old_position.add(velocity)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn production_type_four_randomizes_motion_before_position_publication() {
|
||||
let sensor = TARGET_SENSORS
|
||||
.into_iter()
|
||||
.find(|sensor| sensor.id == 150)
|
||||
.expect("record 150 must be present");
|
||||
let mut game = Game::new_with_seed(1, 7);
|
||||
game.object_active.fill(false);
|
||||
game.object_active[usize::from(sensor.id)] = true;
|
||||
game.ball.in_launcher = false;
|
||||
game.ball.position = vec2(205.0, 58.0);
|
||||
game.ball.velocity = MilliVec { x: 0, y: -2_000 }.to_velocity_per_second();
|
||||
let old_position = MilliVec::from_position(game.ball.position);
|
||||
let mut expected_game = game.clone();
|
||||
let mut expected_ball = game.ball;
|
||||
expected_ball.velocity = MilliVec { x: 0, y: -1_985 }.to_velocity_per_second();
|
||||
expected_game.randomize_trigger_velocity(&mut expected_ball);
|
||||
let expected_velocity = MilliVec::from_velocity_per_second(expected_ball.velocity);
|
||||
|
||||
game.fixed_update(STEP_SECONDS, &mut Vec::new());
|
||||
|
||||
assert_eq!(game.player().score, 500);
|
||||
assert_eq!(
|
||||
MilliVec::from_velocity_per_second(game.ball.velocity),
|
||||
expected_velocity
|
||||
);
|
||||
assert_eq!(
|
||||
MilliVec::from_position(game.ball.position),
|
||||
old_position.add(expected_velocity)
|
||||
);
|
||||
assert_eq!(game.random.seed(), expected_game.random.seed());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn panel_completion_uses_all_281_frames_and_original_sound_boundaries() {
|
||||
let mut game = Game::new(1);
|
||||
|
||||
Reference in New Issue
Block a user