Commit Graph
378 Commits
Author SHA1 Message Date
Hails 0da0a30b02 Merge branch 'audio/moddability' into audio/moddability-2 2026-10-04 22:42:34 -06:00
Hails 7beceffb15 Merge branch 'gameplay/audio' into audio/moddability 2026-10-04 22:42:32 -06:00
Hails feca560105 Audio modding: invalidate is engine API (no dead-code warning) 2026-10-04 18:59:49 -06:00
Hails bf948cc482 Audio: no dead-code warnings (test-only surface map and prefetch query)
Library::surface_map and PrefetchPool::contains are only read by tests; mark them
cfg(test) (the manifest field stays, read by the vault check). No behaviour change.
2026-10-04 18:56:51 -06:00
Hails 378ce835bf Dev test mod: a different sound per autotest step, a compact top-left HUD
The audible autotest used one beep for eight steps, so steps couldn't be told apart.
Each step now has its own self-made sound (synthesize.py): bell (map Baby_Cry_1), chime
(F6 post, Buoy_Bell patch 88), wood block (F8), triangle (F10), low horn (F11 landing),
click (pop rule), whistle (Digit5), shaker (Digit6), boing (honk rule). HUD: one compact
box top-left, every line fits one row, live countdown; camera return after step 3.
2026-10-04 17:47:01 -06:00
Hails 3bd9b2c8d6 Dev test mod: Digit3 taxis on separate rings (no six-car beating)
Six identical taxis at one rpm and level, each detuned once by C04's own Random op,
beat at 4-8 Hz (the "lawn mower" heard in the audible autotest). Each taxi now drives its
own ring (radius 3+3i m, 2+2i m/s), headless engine-band modulation at single-taxi level.
2026-10-04 16:49:10 -06:00
Hails ab3e8bbcb6 Merge audio/moddability (#36: event tags follow the running player) into audio/moddability-2
mod_rules: Tags is now Copy with fixed arrays, so the rule-set key is the Tags value and
the tests build arrays. No-mod e2e bench identical to m2 (84 / 84); tests pass.
2026-10-04 16:46:41 -06:00
Hails c4c98243e1 Dev test mod: autotest mode (timed steps, read-back checks, audible / muted HUD)
mods/audio-content-test runs every action on a timer once the map's native audio runs
(settings autotest, autotest_step, autotest_gap, autotest_muted) and logs one
AUTOTEST <check> ok|fail line per read-back check; one bottom-left HUD box with the step,
what to listen for and a red MUTED TEST header on muted runs. Doc 16 M8b.
2026-10-04 16:36:37 -06:00
Hails cc190888fa Audio modding: event tags follow the running player (subscribe before native start)
A mod subscribing in on_load (before native audio runs) kept empty pop / land tags all
session. events_frame now copies the player's pop / land ids on every subscribed frame
(Copy, no allocation; nothing runs without a subscriber); id 0 never tags pop / land.
Found by the in-game autotest. Test, doc 16 entry; no-mod e2e bench identical (84 / 84).
2026-10-04 16:36:09 -06:00
Hails fda3382e9e Audio modding 2: mod cars and peds get their own MixMap instance by default
- sdk.world_audio.spawn(key, 'traffic'|'ped', ...) without slots gets its own instance on
  the private MixMap, as mod emitters do; slots = 'shared' (alias 'retail') opts into
  retail's pools. Capability world_audio 4. Engine-published objects are unchanged.
- Full private pool (16 cars + 16 peds, all mods): the nearest play, the rest in reach wait
  silently; never refused, never in retail's pools. read(key).waiting / .slots,
  info().own, a one-time log line.
- Dev mods: audio-content-test uses the default; world-audio-test asks for 'shared'
  (retail's limits) with an "Own instances" setting.
- No mod active: e2e bench byte-identical (84 / 84); suites pass (known failures only).
2026-10-04 15:15:18 -06:00
Hails 39debf506d Audio modding 2: hot swap, hot reload, MixMap inputs, own instances, Csis projects, seed
Completes the "Later" list (doc 16 section M):
- Hot swap: an audio mod turning on / off is swapped into the running audio in place.
  Banks keep their runtime id and constructor order, held posts are re-bound with their
  payload; Splice trees, wheel streams, projects, tuning and speech swap too. Only a
  MixMap or grain-bed change (or a swap error) still restarts the sound.
  sdk.audio.info().swaps / last_change.
- Hot reload: editing audio.json or the files it names swaps the overlay while the
  script keeps running.
- MixMap inputs: sdk.audio.set_mixmap_input / mixmap_inputs drive retail's own
  controllers (validated, owned, 16 per mod / 64 total, released values restored).
- Own instances: world_audio spawn {slots='own'} plays mod cars / peds on a private
  MixMap (16 + 16), retail pools untouched.
- Csis projects: audio.json add.projects ships a mod's own classes, functions and
  globals; name clashes are refused; installed / removed without a restart.
- Seed: sdk.audio.seed / SKATE_AUDIO_SEED seeds all audio generators.
- Rules: play.frame = 'owner' puts offsets in the owner's axes; rule sounds follow
  moving published emitters.
- Capabilities: audio 4, audio_content 3, audio_events 3, world_audio 3.
- Dev test mod: Digit1-6, a second HUD line, its own self-made dev.csi.
- No mod active: e2e bench byte-identical 84 / 84 after every step.
2026-10-04 14:38:56 -06:00
Hails b321f77799 Audio modding 2: the decided defaults (own emitter slots, native routing, positional rules)
- Mod emitters get their own instances by default (ModEmitterSlots::Extra); "shared" stays
  selectable, SKATE_AUDIO_MOD_EMITTER_SLOTS overrides either way.
- Mod WAVs route through the native mixer by default when the native audio runs and a voice
  is free, else the Bevy path; native = true / false forces one. Positional native sounds
  default to a 40 m squared reach.
- Replace / layer rule sounds are positional: `at` = owner (default, followed each frame,
  optional offset), world (fixed position) or centre; optional falloff; default reach is the
  owner's retail one (emitter record, cars 40 m, peds 50 m, skaters 30 m).
- Dev test mod, Skyline and audio-example use the defaults; SDK docs and doc 16 updated
  (section L questions written as decisions).
- No mod active: e2e bench byte-identical, 84 / 84; new tests for each default.
2026-10-04 11:21:27 -06:00
Hails b1af7ab819 Merge branch 'audio/moddability' into audio/moddability-2
# Conflicts:
#	docs/hails-additions/16-audio-modding.md
#	mods/audio-content-test/mod.json
2026-10-04 10:13:18 -06:00
Hails fc38e09039 Audio modding 2: native mod voices, live tuning, mod emitters and zones, sound rules
- Mod WAVs can play through the game's own mixer (per-mod mixer banks, retail emitter
  words, a private MixMap), beside the existing Bevy path (mod_voices.rs).
- Tuning can change while the game runs: sdk.audio.set_tuning / tuning / tuned, applied
  between passes, exact restore on nil or mod disable (tuning.rs, audio_tuning.rs).
- Mods add world emitters and reverb zones through the published WorldEmitter /
  ReverbZoneVolume components (world_audio kinds).
- Sound rules: mods mute, replace or layer the game's own sounds by identity, from
  audio.json `rules` or sdk.audio.rule (mod_rules.rs, audio_rules.rs); empty or
  rules-only overlays no longer restart the sound.
- Capabilities: audio 3, audio_tuning 1, audio_events 2, audio_content 2, world_audio 2.
- Dev test mod: F8 native voices, F9 tuning, F10 emitter + reverb zone, F11 a mute rule.
- Headless e2e bench byte-identical to the #36 head after every step; doc 16 section L.
2026-10-04 10:04:07 -06:00
Hails 8403400fe3 Audio modding: review gaps closed, empty Lua tables read as lists
- Mod crossfade banks play: crossfade layouts come from the bank's program or from
  `add.crossfade_layouts` (crossfade.rs, crossfade_layouts.rs); dev test mod fade sample.
- Speech clips and takes are checked at merge time and by check_mod.
- Empty Lua tables (`{}`) were parsed as maps and rejected by list fields; one helper
  (lua_list.rs) now reads them as empty lists in every list field of every mod command.
- Traffic lights answered in the world-audio spec (Skate 3 has none).
- Doc 16 and PULL-REQUESTS.md updated.
2026-10-04 10:03:45 -06:00
Hails 691289ca35 Docs: traffic lights answered (Skate 3 has no traffic-light or crossing sounds) 2026-10-04 10:03:26 -06:00
Hails 813d23ba2b Audio modding: world-gaps overlay and event tests, docs after the merge
- data-gated test: main-cast takes, ped one-shot tuning (body-fall ids,
  tazer time), the speech echo delay and the Tazer bank's samples come
  from an overlay;
- the world event-tag test also drives a tazing, falling ped (tags
  "tazer" and "body_fall");
- doc 16: speech archives (livingworld / maincast), world tuning examples,
  new tags and event rows, verification after the merge; PULL-REQUESTS row M.
2026-10-03 21:57:47 -06:00
Hails 11af7797a6 Merge gameplay/audio (cb71483: main 4488651, GPLv3) into audio/moddability 2026-10-03 21:46:49 -06:00
Hails e985ed56c8 Merge gameplay/audio (3ed0652) into audio/moddability
Brings optimisation pass 2, the tools split (#37) and the world gaps
(tazer, ped body falls, speech echo / voice details, main-cast speech,
NPC loose-board slide) under the audio modding layer:
- main-cast speech takes go through the content overlay (archive
  "maincast"), like the living world's;
- ped speech (phone ring), body-fall and foot-plant Splice starts and the
  NPC skaters' starts are recorded through the observe-only event wrapper;
  tazer posts get a slot name; new tags "tazer" and "body_fall"; main-cast
  line starts are speech rows with class "maincast";
- the new world tuning (ped_objects, speech_voice) is overlay-reachable
  through tuning.world like the rest of the world tuning.
2026-10-03 21:46:43 -06:00
Hails cb7148305a Docs: upstream is GPLv3 now (sync with main 4488651) 2026-10-03 21:38:14 -06:00
Hails 756dd17be2 Merge main (4488651, synced with upstream: GPLv3 licence) into gameplay/audio 2026-10-03 21:36:51 -06:00
Hails 3ed0652d7e Merge world/gaps into gameplay/audio
# Conflicts:
#	crates/skate-audio/src/mixer.rs
2026-10-03 21:27:34 -06:00
Hails 7ef35c45b0 Audio: world gaps closed (tazer, ped falls, speech details, echo, main-cast speech, NPC board slide)
Tazer and PedBodyFall ported from the retail mechanism and checked against the recordings; ped speech gets its PEAK filter, the voice float, the speech inputs, the phone and photographer lines, first-free-stream choice and the queue clock; the speech echo send (pre-gain send -> high pass -> distance delay -> low pass -> environment bus); the main-cast speech channel for pros, NPC skaters' own speech and message pairs, with its opt-in setup decode; the NPC skater's board slide. Engine and mod surfaces extended (reactions, tazer, body fall, phone / photo events); retail values are setup-data defaults. Local player output byte-identical.
2026-10-03 21:17:18 -06:00
Ethanw05 4488651c35 Document GPLv3 scope and preserve third-party licensing 2026-10-03 21:08:44 -06:00
Ethanw05 8280fc44a4 Add GNU GPL version 3 license 2026-10-03 21:08:33 -06:00
Hails df96ef46e2 Tools: move the non-audio helpers to their own PR (#37)
regression-checks, setup-equivalence, collision-inspect, world-stream-inspect, vault-inspect and recomp-code-search now live on tooling/published-tools (#37); the audio tools stay here. Index, doc 14 and the PR plan point there.
2026-10-03 20:29:52 -06:00
Hails 4c043959c0 Audio: optimisation pass 2 (allocation-free render, faster voices)
- AUDIO_TIMING logs voices / instances / grains per block (tools/audio-bench summary prints them).
- The render path is allocation-free in real play: subscriber lists and constructor lists read in place, call
  parameters on the stack, instance memory and client lists from pools (render allocations per block 0.17-0.63 ->
  <= 0.001; full world 1.825 -> 0.004); render_alloc tests cover walks, posts, calls, globals and a retail world host.
- Voices: direct PCM reads when a block provably stays inside the sample; shared scratch planes in the mixer
  (full world render p50 -11 to -14 %).
- MixMap keeps its held-input list between ticks; Jitter processes without a per-frame Vec.
- Every change byte-identical in all 84 e2e outputs; two attempted changes dropped (no real gain).
2026-10-03 20:19:23 -06:00
Hails f7de66731e Audio modding R6: modding guide, sdk example and dev test mod 2026-10-03 20:13:05 -06:00
Hails 2d053289c5 Audio modding R5: observe-only audio events 2026-10-03 20:13:03 -06:00
Hails 8195e0785b Audio modding R3: retail posts, globals and a read-only MixMap view for mods 2026-10-03 20:13:01 -06:00
Hails 9484484d30 Audio modding R2: map audio as data (run-time map list, sidecar and .skate tag) 2026-10-03 20:12:58 -06:00
Hails 439a66f2d9 Audio modding R1: content overlay (audio.json), deep check_mod, restart, conflicts 2026-10-03 20:12:55 -06:00
Hails 414673850a Merge branch 'gameplay/audio' into audio/moddability
# Conflicts:
#	docs/hails-additions/README.md
2026-10-03 18:35:21 -06:00
Hails 7f120de0b8 Audio: world / NPC sources complete, ped speech, specs published
- Ped speech plays: speech manager -> library -> the living world's two streams, with queue / interrupts and the
  near / far lines by distance; levels and filters from the retail mechanism (filters equal to the recomp's, gain
  median within 0.5 %). Silent and logged when the opt-in speech decode is absent.
- NPC skater: wheels and clothing on its own instance and a bail grunt; tricks, treatment and footsteps stay off for
  NPCs as in retail.
- Per-model ped data (shoe class, voice, far threshold) and traffic engine records per model exported by setup;
  traffic relative-speed input and front / rear sound points; world hosts process before the mixer tick.
- Test mod: opt-in (off by default), higher world-object limits (48 per mod, 128 total), a per-second summary log.
- Specs published in docs/hails-additions/audio-specs/; code comments point at them; test data paths behind env vars
  (no .local defaults).
2026-10-03 18:34:48 -06:00
Hails 8bf4653206 Docs: audio modding design (doc 16)
The plan for making the native audio moddable: a content overlay that replaces or adds banks, samples, speech and emitters by retail identity; map audio as data so custom maps get emitters and ambience; posting retail sounds and globals from mods; observe-only audio events; example mods. Retail stays byte-identical with no mod active.
2026-10-03 18:07:59 -06:00
Hails a4ec831a02 Audio: host clock on physics steps, review fixes, world / NPC audio hook-in
Review fixes:
- The audio host takes each physics step exactly once (one-step pulses latched, nothing while paused or silenced,
  at most 4 steps per frame), so pushes and other one-frame events are never dropped or doubled. e2e at 60 fps and
  above is byte-identical; 30 / 45 fps change only by the latch and the step pair.
- Removed the emitter PROFILES fallback, the stray trace dump and the audition script; data-gated tests are
  #[ignore = "needs the private install data"] and fail loudly when run without it; per-post log lines behind
  SKATE_AUDIO_TRACE; checked bank parsing, finite output, camera-cut Doppler reset, fewer render allocations.
- The 15 verdict-settled A/B switches are removed; their "on" behaviour is the only one (byte-identical).

World / NPC audio, hookable by engine systems and mods:
- Map-change fix: world and NPC hosts reset when the map's banks are unloaded, so surviving objects sound again.
- Engine surface (skate-game::world_audio): TrafficAudio / PedAudio / NpcSkaterAudio components, horn / alarm /
  speech messages, read-back of who is audible; retail limits (4 cars within 40 m, 15 peds within 50 m with
  footsteps for the nearest 3, one NPC skater within 30 m), measured in the recompilation.
- Mod surface (Lua API 2, `sdk.world_audio.*`); test mod `mods/world-audio-test/` (cars, peds, a ghost skater).
- Per-skater audio state builder (local player output identical); remote players take the NPC skater slot; an
  opt-in non-retail "more audible" setting.
- Docs: doc 15 "Hooking up world audio", doc 11 sections, PR plan.
2026-10-03 16:21:12 -06:00
Hails df012ec5dd Docs and tools: riding collision posts settled, couch testing, recomp bail / collision analysis
- tools/recomp-trace: bail_impacts.py (per-bail ragdoll impacts and sound posts) and collision_posts.py (collision-sound posts by poster and owner, with the local-rider flag); README entries.
- Doc 11: listening verdicts, the riding body-collision posts are NPC skaters' (local only on big-drop landings and bumps).
- Doc 13: playing the recomp from the couch and closing it (settings overlay, LB+RB+Back hold).
- Doc 14: the added tools.
2026-10-03 14:30:17 -06:00
Hails 4152769251 Audio: turn / brake slews on the console cadence; grind material is the tag - 1
- Retail steps the board's turn intensity (0.06) and brake (0.05) once per SkateBoard process call, with no dt,
  so on the 30 fps console a full turn fades over 333 ms and the brake over 667 ms. Ours stepped per 1/60 s tick,
  twice as fast. Both now step once per console frame, frame-rate independent (identical at 30-365 fps); the NPC
  bed follows the same cadence. SKATE_AEMS_SLEW_CONSOLE=0 = old (byte-identical renders).
- Concrete ledges grinded as metal: retail stores every audio material as the surface tag - 1 (as our wheels,
  deck and feet already did), but the grind material used the raw tag and read the next AudioSurfaceMap entry
  (University ledge tag 66 -> metal). Now material_of_tag like the rest; matches the recomp's grind starts
  (material 65, concrete GRINDS samples, no Skate_Metal) at the same spots.
- Docs: doc 11 sections for both, gotchas.
2026-10-03 13:42:56 -06:00
Hails bebdf0cc9b Audio: bail speed curve, |Δv·n|, console-cadence posters, NPC grain bed
- Body region impacts are scaled by retail's centre-of-mass speed curve (x1 at rest up to x5 from ~0.95 m/s,
  read from the vault; no clamp after the multiply), so bails reach the tier-1 / tier-2 body and concrete hits.
  Setup exports the curve; older installs get the identical default. SKATE_AEMS_BODY_CURVE=0 = old.
- Region impacts take |Δv·n| as retail does (matches 12,054 of 12,054 recomp region lines).
- The body and deck posters' cooldowns count console frames (0.5 s / 0.2 s at any frame rate).
  SKATE_AEMS_BODY_CONSOLE=0 / SKATE_AEMS_DECK_CONSOLE=0 = old.
- Second grain bed for the NPC skater (retail's per-instance SkateBoard update; inert until the engine has
  NPC skaters). The local player's renders are byte-identical.
- The state log records the COM speed; the e2e harness replays it.
- Docs: doc 11 (bail density: the missing speed curve, not the ragdoll; the hooks' local test), PR plan,
  docs index; the published recomp trace reader knows the new line kinds.
2026-10-03 11:37:32 -06:00
Hails 418dcecd41 Audio: interim cue tables retired, native water splash, published tools
- The interim cue tables and their opt-outs are gone; the native runtime is the only audio path. Old
  settings/audio.json files still load ("interim" / "native" ignored). Missing native data logs an error
  instead of falling back.
- Water splash from retail's mechanism (SFXObj_OffBoard start / update): Skate_Collisions 1187 / 1197 once
  per water stay, 1198 when the board is in water.
- The random interim riding bed is removed; riding collision and cloth sounds compared with the recomp
  (doc 11). Native output otherwise byte-identical (13 scenarios + 5 sessions).
- Published tools under tools/ (one folder per tool with a README; the recomp ones labelled for use with the
  research hooks), doc 14 and PR plan bucket L.
- Docs: doc 11 (retirement, bail density measured against the recomp, collision posts), doc 13 (usage guide).
2026-10-03 10:20:08 -06:00
Hails 6499b7dea0 Docs: seam-hit prominence closed as a likely recomp artefact 2026-10-03 09:11:01 -06:00
Hails a6d9512f99 Audio: native by default, console cadence, optimisations, review fixes, world foundations
- Native AEMS runtime is the default (`SKATE_AEMS=0` or `"interim": true` for the interim tables, kept).
- MixMap, eEQChain clear and Jitter on the console's 30 Hz cadence, frame-rate independent.
- Optimisation pass (render p50 210 -> 130 us, 0 allocations per block, byte-identical) and hardware FMA
  dispatch in the new skate-audio-fma crate (p50 -> ~55 us, byte-identical; skate-audio stays forbid(unsafe)).
- World emitter banks decoded ahead on a worker thread (no game-thread decode spikes).
- Session-review fixes: push sounds at the foot plant, push-foot plant/lift, bail body impacts, grind on/off.
- Foundations, inert until the engine has the systems: traffic and ped sounds, ped speech rules, NPC skaters'
  board sounds.
- Docs: doc 11 sections, doc 13 (the recompilation research hooks, reference only), PR plan.
2026-10-03 09:08:06 -06:00
Hails 2fcbcc3002 New audio who dis
Native port of Skate 3's retail audio runtime (AEMS) behind SKATE_AEMS=1, plus the world audio work:
- skate-audio crate: formats, patch-program evaluator, voice graph, mixer, MixMap, granular rolling bed and its
  bus chain, Splice contacts, the buses (env reverb network, eEQChain, flanger returns, collision and footstep
  submixes, reverb zones) and the player components (rolling layers, seams on the console's 30 fps cadence,
  grinds, skids, squeaks, tricks, treatment, footsteps, clothing, hands on deck, wind, wheels, collision manager).
- game_audio host: native runtime, player audio state from our physics, world emitters from the maps' .ems files,
  random distant one-shots, zone ambience, state log, e2e render harness, timing.
- Setup: audio export of the banks, grains, MixMap, vault tuning and reverb zones.
- skate-core: per-wheel / per-part audio surfaces and seam patterns (additive).
- Docs: docs/hails-additions/11-audio.md and the PR plan.
2026-10-02 22:58:17 -06:00
Hails b4c31d374c Docs: PR plan — audio draft #32, all open PRs synced with main 7ae67f2 2026-10-02 09:11:28 -06:00
Hails 5e9e4dd784 Audio: retail world emitters from the maps' .ems files
Replaces our own water placement (water.rs: water collision grouped into
bodies, a random 1-2.5 s piece every 0.7-1.3 s that piled up 2-3 deep, which
the user heard as overlapping water at the University plaza).

Setup (audio manifest v4):
- audio_formats: .ems reader (72-byte records) and name_id, Bob Jenkins'
  lookup8 64-bit hash with the seed the sound ids use;
- audio_export: every .ems file with each sound's attributes from the disc's
  skatercollections emitter class 0xF0CEF367088EFFF8 (volume, bank, patch
  selector, volume type, falloff), resolved through parents; decodes every
  bank the sfx_*/skateschool emitters place (100 banks; all 486 sound
  emitters resolve to a bank on the disc).

Game (game_audio/emitters.rs, behaviour taken from TU3):
- sphere or ellipsoid (semi-axes = extents along forward/up/side), inner core
  at full level, level = volume x falloff ((1-d)^2, 1-d or flat);
- at most 5 emitters, in discovery order, released when the listener leaves;
- each water bank plays its program's measured pattern (two-voice relay of
  shuffled one-shots or one loop, with its level/pitch movement) until the
  AEMS evaluator is ported.

Verified muted in game: University plaza starts water_fountain and the
channel fountains_waterlaps_left; Aletown canal fountains_waterlaps_left.
2026-10-02 09:11:28 -06:00
Hails f0ef959462 Game audio: escape the raw NUL bytes in a library.rs test literal
The WAV-header byte string held literal NUL bytes, so git stored the whole
file as binary (no diffs). Written as \x00 escapes; same bytes at compile
time.
2026-10-02 08:22:18 -06:00
Hails 818f109c27 Merge gameplay/water (synced with main 7ae67f2) into gameplay/audio 2026-10-02 08:16:27 -06:00
Hails 06556fac53 Docs: PR plan after upstream merged #26/#17/#18 and its own ISO fix
PR E merged upstream; PR A trimmed to the long-paths change (upstream fixed
the extract-xiso order itself); new row K (solver iterations); upstream sync
notes and known-failure list updated.
2026-10-02 08:16:21 -06:00
Hails 7c5c6b2c51 Game audio: retail-measured layers, transient-safe one-shots, 75% default volume
- Pop, flip-catch and landing cues built from the layers retail plays per
  moment (bank, record, level, delay, envelope measured from TU3 traces),
  with hollow/normal landing tables and per-kind chances.
- One-shots start at their final level (no fade-in eating the transient);
  loops still fade in.
- Riding bed from the measured statistics.
- Master volume defaults to 75 % (100 % is unity gain; 25 % was too quiet
  to judge retail parity).
2026-10-02 08:16:21 -06:00
Hails 7a9e6dc26f Physics: solve the board/skater with retail's 50 constraint iterations
The stock RWMaxIterations (25) is only the Simulation's setup value; TU3
solves with 50 on every gameplay frame. With 25 the constraints converged
less: wheels sank ~10x faster at rest, the ollie deck sat ~1 cm low and
the jump velocity the pop sound reads was too small (rolling ollie
0.21-0.29 vs retail 0.50), so pops picked the quiet row.

Maps validate identically; water traces stay within expectations. See
docs/hails-additions/12-solver-iterations.md.
2026-10-02 08:16:20 -06:00