This commit is contained in:
Jason Dekarske 2026-07-11 13:16:27 -07:00
parent 94c6949426
commit 5eefcd23bf
4 changed files with 302 additions and 713 deletions

View File

@ -71,7 +71,6 @@ impl Game {
broom_y: f32,
velocity: f32,
curl: i8,
friction: f32,
) -> Result<Vec<StonePath>, String> {
if self.turn_team != team {
return Err("Not your turn".to_string());
@ -81,9 +80,9 @@ impl Game {
}
self.active_stones.clear();
let trajectory =
self.physics
.throw(self.turn_team, broom_x, broom_y, velocity, curl, friction)?;
let trajectory = self
.physics
.throw(self.turn_team, broom_x, broom_y, velocity, curl)?;
self.active_stones = self.physics.current_stones();
self.phase = GamePhase::Simulating;
@ -102,9 +101,8 @@ impl Game {
broom_y: f32,
velocity: f32,
curl: i8,
friction: f32,
) -> Result<ThrowOutcome, String> {
let trajectories = self.handle_throw(team, broom_x, broom_y, velocity, curl, friction)?;
let trajectories = self.handle_throw(team, broom_x, broom_y, velocity, curl)?;
self.finish_simulation();
let state_message = self.game_state_message();
@ -259,7 +257,7 @@ impl Room {
#[cfg(test)]
mod tests {
use super::*;
use crate::physics::DRAW_VELOCITY;
use crate::protocol::DRAW_VELOCITY;
#[test]
fn starts_in_waiting_phase() {
@ -285,7 +283,7 @@ mod tests {
game.start();
let turn = game.turn_team;
let wrong = turn.other();
let result = game.handle_throw(wrong, 0.5, 38.7, DRAW_VELOCITY, 1, 1.0);
let result = game.handle_throw(wrong, 0.5, 38.7, DRAW_VELOCITY, 1);
assert!(result.is_err());
}
@ -294,7 +292,7 @@ mod tests {
let mut game = Game::new();
game.start();
let turn = game.turn_team;
let result = game.handle_throw(turn, 0.5, 38.7, DRAW_VELOCITY, 1, 1.0);
let result = game.handle_throw(turn, 0.5, 38.7, DRAW_VELOCITY, 1);
assert!(result.is_ok());
let paths = result.unwrap();
assert!(!paths.is_empty());
@ -308,7 +306,7 @@ mod tests {
game.start();
let turn = game.turn_team;
// (0.0, 30.0) is well outside HOUSE_RADIUS of HOUSE_CENTER
let result = game.handle_throw(turn, 0.0, 30.0, DRAW_VELOCITY, 1, 1.0);
let result = game.handle_throw(turn, 0.0, 30.0, DRAW_VELOCITY, 1);
assert!(
result.is_ok(),
"broom outside house should be allowed: {:?}",
@ -322,7 +320,7 @@ mod tests {
game.start();
let turn = game.turn_team;
let outcome = game
.process_throw(turn, 0.0, HOUSE_CENTER.1, DRAW_VELOCITY, 0, 1.0)
.process_throw(turn, 0.0, HOUSE_CENTER.1, DRAW_VELOCITY, 0)
.unwrap();
// ThrowOutcome must not carry end_scored
assert!(!outcome.trajectories.is_empty());
@ -366,7 +364,7 @@ mod tests {
let mut game = Game::new();
game.start();
let turn = game.turn_team;
game.handle_throw(turn, 0.0, HOUSE_CENTER.1, DRAW_VELOCITY, 0, 1.0)
game.handle_throw(turn, 0.0, HOUSE_CENTER.1, DRAW_VELOCITY, 0)
.unwrap();
match turn {
Team::Team1 => assert_eq!(game.stones_team1, STONES_PER_TEAM - 1),

View File

@ -180,12 +180,11 @@ fn spawn_message_handler(
broom_y,
velocity,
curl,
friction,
}) => {
let mut room_guard = room.lock().await;
match room_guard
.game
.process_throw(team, broom_x, broom_y, velocity, curl, friction)
.process_throw(team, broom_x, broom_y, velocity, curl)
{
Ok(ThrowOutcome {
trajectories,

File diff suppressed because it is too large Load Diff

View File

@ -17,26 +17,45 @@ pub const SHEET_LENGTH: f32 = 45.0;
pub const HOUSE_CENTER: (f32, f32) = (0.0, 38.5);
pub const HOUSE_RADIUS: f32 = 6.0 * FEET_TO_METERS; // 12 ft diameter → 6 ft radius
pub const HOG_LINE_Y: f32 = 21.0;
pub const BACK_LINE_Y: f32 = 42.0;
/// Backline touches the outer house ring (tee + 6 ft).
pub const BACK_LINE_Y: f32 = HOUSE_CENTER.1 + HOUSE_RADIUS;
pub const HACK_Y: f32 = 2.0;
// Stone physical properties
pub const STONE_RADIUS: f32 = 0.15;
pub const STONE_MASS: f32 = 20.0;
pub const STONE_FRICTION: f32 = 0.015;
/// Newton restitution for stonestone contacts (Rapier, Average combine).
/// Curling granite is nearly elastic on contact; low e makes takeouts feel like
/// putty (both limp together). ~0.9 → both keep going along impact direction.
/// Newton restitution for stonestone contacts (Rapier, Max/Average combiners).
pub const STONE_RESTITUTION: f32 = 0.9;
/// Soft guard end of the throw slider. Weight 1 → MIN_SPEED.
/// Calibrated with DRAW_VELOCITY so mid-slider (weight 5) lands near the tee.
/// Shared with the frontend; binary sim does not clamp on it (clients send free velocity).
/// Stop offsets (feet relative to tee) for weights 1..=10 and hack.
/// Positive = past tee toward backline (behind the tee).
pub const WEIGHT_STOP_OFFSET_FT: [f32; 10] =
[-11.0, -9.0, -7.0, -5.0, -3.0, -1.0, 0.0, 1.0, 3.0, 5.0];
pub const HACK_STOP_OFFSET_FT: f32 = 12.0;
/// m/s for weights 1..=10 (calibrated via open-ice stop distance).
pub const WEIGHT_SPEEDS: [f32; 10] = [
2.2573, 2.2783, 2.2992, 2.3199, 2.3404, 2.3608, 2.3709, 2.3810, 2.4012, 2.4211,
];
pub const HACK_SPEED: f32 = 2.4899;
/// Takeouts: open-ice stop at hack+N feet (N = 6,12,18,24).
pub const BOARD_SPEED: f32 = 2.5492;
pub const CONTROL_SPEED: f32 = 2.6408;
pub const NORMAL_SPEED: f32 = 2.7448;
pub const PEEL_SPEED: f32 = 2.8499;
pub const BOARD_STOP_OFFSET_FT: f32 = HACK_STOP_OFFSET_FT + 6.0;
pub const CONTROL_STOP_OFFSET_FT: f32 = HACK_STOP_OFFSET_FT + 15.0;
pub const NORMAL_STOP_OFFSET_FT: f32 = HACK_STOP_OFFSET_FT + 25.0;
pub const PEEL_STOP_OFFSET_FT: f32 = HACK_STOP_OFFSET_FT + 35.0;
/// Tee-line weight (category 7).
pub const DRAW_VELOCITY: f32 = WEIGHT_SPEEDS[6];
/// UI soft end (weight 1).
#[allow(dead_code)]
pub const MIN_SPEED: f32 = 1.9;
/// Heavy end of the throw slider. Weight 10 → MAX_SPEED.
/// Keep span so weight 5 ≈ DRAW_VELOCITY (2.38).
pub const MIN_SPEED: f32 = WEIGHT_SPEEDS[0];
/// UI heavy end of draw weights (weight 10). Peel/hack sit above this.
#[allow(dead_code)]
pub const MAX_SPEED: f32 = 3.0;
pub const MAX_SPEED: f32 = WEIGHT_SPEEDS[9];
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Hash, Default)]
#[serde(rename_all = "snake_case")]
@ -89,17 +108,12 @@ pub enum ClientMessage {
velocity: f32,
#[serde(default = "default_curl")]
curl: i8,
#[serde(default = "default_friction")]
friction: f32,
},
}
fn default_curl() -> i8 {
1
}
fn default_friction() -> f32 {
1.0
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EndScore {
@ -185,7 +199,7 @@ mod tests {
#[test]
fn throw_message_uses_velocity_not_weight() {
let json = r#"{"type":"throw","team":"team1","broom_x":0.5,"broom_y":38.5,"velocity":4.2,"curl":1,"friction":1.0}"#;
let json = r#"{"type":"throw","team":"team1","broom_x":0.5,"broom_y":38.5,"velocity":4.2,"curl":1}"#;
let msg: ClientMessage = serde_json::from_str(json).unwrap();
match msg {
ClientMessage::Throw {