This page documents assets/config.toml options and the physics performed each fixed step.
What happens each physics step
- Film decay: the sweeping “water film” on the sheet decays exponentially toward 0 with time constant
film_decay_s. - Per-stone integration: for each moving stone:
- Sample the local surface at the stone to get multipliers for friction, curl and angular damping, plus a tiny “cross‑slope” lateral bias.
- Apply forces:
- Drag (linear + quadratic): opposes motion, magnitudes
lin_drag · |v|andquad_drag · |v|². - Base (Coulomb‑like) friction:
mu_base · gopposing motion. - Curl force: perpendicular to motion, magnitude
curl_coeff · effective_spin · |v| · curl_speed_factor(|v|). - Cross‑slope bias: a small extra push to the left of travel proportional to local slope and speed.
curl_speed_factor(|v|) = 1 / (1 + curl_speed_suppression · |v|); when suppression is zero the factor stays at 1. - Drag (linear + quadratic): opposes motion, magnitudes
- Integrate with explicit Euler; snap to full stop if a drag reversal would flip velocity this frame.
- Angular damping:
ω *= clamp(1 − ang_drag · dt, 0, 1); advance heading byω · dt. - Bounds: mark out of play if the stone crosses back lines or boards.
- Sleep: if speed stays below
0.2 m/sfor0.25 s, snapv=0, ω=0.
- Wear: shave pebble under the running band by a small amount proportional to distance traveled this step.
- Sweeping: when active, ahead of the stone: slightly flatten the pebble and charge the temporary film (which then decays).
- Collisions: resolve elastic disc–disc contacts (unit restitution on the normal; no angular impulse).
Effective spin mapping (handles very low/high rotation gracefully)
The curl strength uses a smoothed “effective spin” (in the direction of ω):
reference = curl_spin_reference
min_value = curl_spin_min
max_value = curl_spin_max
gain = ((reference - min_value) * reference) / (max_value - reference)
blend = gain / (|ω| + gain) // 0..1
eff_mag = min_value + (max_value - min_value) * blend
effective_spin = eff_mag * sign(ω)
variation_multiplier = eff_mag / reference // scales lateral-bias response
Low |ω| → effective_spin ≈ max; high |ω| → ≈ min. If |ω| is essentially zero, curl is 0 for that instant but the bias scaling uses max/reference.
Config reference (assets/config.toml)
[match]
| Key | What it does |
|---|---|
ends_to_play | Number of regulation ends before extras are considered. |
stones_per_team | Stones each team may throw per end. |
hammer | Team with the hammer to start ("Red" or "Yellow"). |
ice_preset | Selects one of the base ice blocks below (ice_fast, ice_average, ice_slow). |
[ice_*] — Base ice (global)
These set the global “feel” of the sheet. Local variability from pebble multiplies these.
| Key | Description |
|---|---|
mu_base | Speed‑independent drag term (Coulomb‑like). Part of the base opposing force. |
curl_coeff | Scale for the perpendicular curl force. |
lin_drag | Speed‑proportional drag. |
quad_drag | Speed‑squared drag for higher velocities. |
ang_drag | Angular damping per second. |
curl_speed_suppression | k in 1 / (1 + k · |v|); higher values reduce curl at higher speeds. |
curl_spin_reference | Spin (rad/s) at which curl matches your baseline. |
curl_spin_min | Minimum effective curl at very high |ω|. |
curl_spin_max | Maximum effective curl near zero |ω|. |
[pebble] — 2D sheet, wear & sweeping
Grid & seeding
cell_m- Grid spacing (m) for the pebble field.
init_mean_um- Target average pebble height (μm) when seeding.
init_std_um- Standard deviation of initial noise (μm).
noise_octaves- Layers of low‑frequency fractal noise for smooth variation.
height_clip_um- Clamp for deviations ± from the mean (μm).
seed- RNG seed for deterministic pebble generation.
randomize_seed_each_game- When true, pick a new seed every game.
persist_across_ends- Keep worn pebble between ends (otherwise re‑seed).
Coupling local height → physics
Let h be the local height average over the running band, and dh = (h − mean)/clip clamped to a sane range. The surface model returns per‑point multipliers:
| Key | Effect |
|---|---|
mu_alpha | Scales mu_base by ~(1 + mu_alpha · dh). |
lin_alpha | Scales lin_drag by ~(1 + lin_alpha · dh). |
quad_alpha | Scales quad_drag by ~(1 + quad_alpha · dh). |
curl_alpha | Scales curl_coeff by ~(1 + curl_alpha · dh). |
ang_damp_alpha | Scales ang_drag by ~(1 + ang_damp_alpha · dh). |
Exact clamping/limits are handled in code to avoid non‑physical values.
Cross‑slope drift (micro lateral bias)
We compute a normalized cross‑slope from the local gradient (projected onto the left‑normal of travel). This produces a tiny lateral acceleration per unit speed:
| Key | Meaning |
|---|---|
side_grad_gamma | Gain from normalized cross‑slope to lateral bias. |
side_grad_spin_ref | Spin (rad/s) where drift is halved. |
side_grad_spin_power | How quickly drift falls with spin. |
side_grad_min_factor | Floor for drift even at heavy rotation. |
Wear (traffic)
Each step, we shave pebble under the running band:
travel = |v| · dt
wear = wear_um_per_pass · travel · (1 + 0.1 · |ω|)
radius = running_band_radius · wear_radius_scale
apply wear uniformly inside that disk at the current stone position
| Key | Meaning |
|---|---|
wear_um_per_pass | Microns flattened per metre traveled under the band. |
wear_radius_scale | Expand/shrink the wear footprint vs the band radius. |
Sweeping (ahead of the stone)
When sweeping is active, we act at a point ahead of the stone:
center = pos + normalize(v) · lookahead_m
radius = sweep_radius_m
// Permanent effect:
flatten += sweep_flatten_um_per_s · dt
// Temporary effect:
film := min(1, film + Δ) // charged while sweeping
film → decays exponentially with time constant film_decay_s
// The surface model uses film (0..1) to reduce friction by film_friction_scale
| Key | Meaning |
|---|---|
lookahead_m | Distance in front of the stone where sweeping acts. |
sweep_radius_m | Radius of the sweep zone. |
sweep_flatten_um_per_s | Microns per second of permanent flattening while sweeping. |
film_friction_scale | How strongly film reduces friction (0 = none, 1 = full). |
film_decay_s | Exponential decay constant for film once sweeping stops. |
Forces & integration (formulas)
// Inputs at sample point:
v = current linear velocity (2D)
speed = |v|
dir = normalize(v)
left = (-dir.y, dir.x) // left-normal
local = surface.sample(pos, v, ω, heading)
// Local multipliers supplied by the surface model:
μ_scale, lin_scale, quad_scale, curl_scale, ang_scale, bias_per_speed = local
// Drag (already multiplied by mass in code):
F_lin = lin_drag · speed · m · lin_scale
F_quad = quad_drag · speed² · m · quad_scale
F_base = mu_base · g · m · μ_scale
F_drag = -(F_lin + F_quad + F_base) · dir
// Curl (mass-independent term in code; acceleration comes after /m):
spin = effective_spin(ω) // see mapping above
curl_speed_factor = 1 / (1 + curl_speed_suppression · speed)
F_curl = (curl_coeff · curl_scale · spin · speed · curl_speed_factor) · left
// Cross-slope micro-bias:
F_bias = (bias_per_speed · speed · m · variation_multiplier(spin)) · left
// Integrate:
a = (F_drag + F_curl + F_bias) / m
v' = v + a · dt
pos' = pos + v · dt
ω' = ω * clamp(1 - ang_drag · ang_scale · dt, 0, 1)
curl_speed_factor suppresses curl at higher speeds; when curl_speed_suppression = 0 the factor stays at 1.
If v' · v ≤ 0 (drag reversal), we snap the stone to rest to avoid jitter. Sleep triggers if speed stays below threshold for the configured window.
Practical tuning tips
- Overall pace: start with
mu_base,lin_drag,quad_drag. Get hog‑to‑hog times right first. - Curl shape: use
curl_coefffor magnitude; then refine the feel withcurl_spin_*to shape late vs early finish. - Consistency vs traffic: increase
wear_um_per_passfor more visible tracks; slightly reducecurl_alphaif worn paths feel too “grabby”. - Sweeping feel:
film_friction_scalegives the immediate glide benefit;sweep_flatten_um_per_smakes paths play straighter next stone. - Micro‑bias realism: small
side_grad_gamma(≈ 0.02–0.10) with sensible spin falloff produces believable, non‑twitchy drift.
Geometry & constants
- Sheet: 45.720 m × 4.750 m; near hog at
x=0; far hog atx=21.945. - Running‑band diameter constant:
0.127 m(used for wear/sampling radius). - Stones default to
19 kg, radius0.1455 m.