- 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).
- 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.
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.
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.
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.
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.
- 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).
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.
Brings in upstream #26 (indexed collection lookups, our PR E), #17, #18 and
upstream's own extract-xiso fix via our main. The two conflicts are the
extract-xiso call sites, where both sides made the same fix: take upstream's
lines (it also keeps the extract log outside the temporary directory).
- docs/hails-additions/11-audio.md (current design, formats incl. the
SPLC patch tree, cue table, how it was tuned, prior work upstream
#1/#4 and why their runtime is not adopted), README row 11,
PULL-REQUESTS row J.
- tools/audio_audition.py: dev page to audition decoded samples (local
.local/ output only).
- trace-downtown-lag.json: dev-only perf-trace output from TRACE_PLAY.bat
(header only, no capture); committed because everything pending is
committed, not needed by the build.
New crates/skate-game/src/game_audio/ module (Bevy audio):
- Loudness rules: master 25 % by default (settings/audio.json, Master/
Ambience/Effects rows in the GRAPHICS menu), --mute; a clip may be
raised only to its own full-scale peak (x4 max); fades, voice and
repeat limits; silent while the menu is open or a replay runs. Mod
audio follows the master volume and shares the one listener.
- Per-map ambience beds; water emitters on the map's water bodies
(fountains: moving water, canals/lakes: lapping).
- Rolling: grain speed bands by board speed and the wheels' audio
surface (new WheelLineState::audio_surfaces in skate-core, additive),
restarted cleanly after leaving the ground.
- Cues found from physics/animation state each tick: pop, flip/grab
(new trick only), landing (+ weight layer by impact), board-down,
grind (metal vs other surfaces), powerslide and foot drag (shuffled),
push, bail tiers, splash (retail patch record 869, played with its
layers), footsteps from foot strikes. Sample picks confirmed by ear;
each matches a retail SPLC patch record.
- AUDIO_CUE / AUDIO_LOOP / AUDIO_EVENT log lines; SKATE_AUDIO_TRACE=1.
- Read-only helpers: physics::foot_clearance, an animation-event latch
in AnimationInput (physics never reads it).
- skate-data example audio_surfaces (per-map audio IDs, AT= point query).
- audio_formats.py: EA SNR (EA-XMA) stream scanner with block-chain
validation, SPLC bank streams and patch tree (records, layers,
containers; verified on all 20 banks), .grain files (speed-sweep
recordings), loopable speed bands, gain-free 5->2 downmix.
- audio_export.py: decodes only what the engine plays (22 ambience
beds, 14 grains x 6 speed bands, wheel spins, 23 sample banks) to
PCM16 WAV in assets/private/audio with audio_manifest.json (v3,
incl. SPLC patch trees). Decoder: pinned vgmstream r2117
(SHA-256 checked) via the existing dependency() helper.
- Registered as optional asset group `audio` (versions, receipts,
asset_exports, install); a failure records audio-availability.json
and the game runs silent. Other groups' fingerprints unchanged.
- .gitignore: retail audio extensions and *.wav (Skyline mod WAVs
excepted). Tests updated for the new group.
- 09-water.md: physics, camera, splash and look after in-play testing
- 10-sky-shader-test.md, README index
- PULL-REQUESTS.md: PR H prepared against upstream 60efdef
- water-test-teleport: F7 Aletown canal, F5 position readout, the
"Teleported" text clears after 5 s
- Water is solid only where geometry lies within 0.5 m under it; deep
water makes no contacts and is detected from position (physics/water.rs).
Body buoyancy only over deep water; the board floats with water drag.
On-foot support follows the same rule.
- Water camera (high, behind, looking down) with an edge vignette through
retail_exposure -> retail_tone, driven by the water bail state.
- Entry splash from the game's water particle sprite; setup extracts the
particle sprites from miscboot.big (environment group, particles.py).
- Family 33 look matched to RPCS3 footage: 4x ripple scale, blurred and
tinted reflection, anti-tiling with swells, calmer small bodies
(water_bodies.rs areas), waves at 0.75x (FrameStateData.pca_slow).
- Ignored water_drop trace test; SKATE_VERIFY_AT for timed --verify
captures.
retail_sky.wgsl reads forward_io's Vertex.position, which Bevy only
declares under VERTEX_POSITIONS; the mesh pipeline sets it for every mesh
with positions, but the test's validate() helper did not, so
sky_shader_validates failed (also on upstream 60efdef). All six shader
validation tests pass.
- PULL-REQUESTS.md: pending edits to the upstream PR plan
- mods/water-test-teleport: dev-only mod for testing water (F2/F3/F4
drop into University fountain, University reservoir, DownTown fountain;
Shift+F2/F3/F4 stand beside them). Not intended for upstream.
- water_surfaces example: view spots beside water, render materials at a
point, model materials, texture and material inspection; SKATE material
ids are 1-based (an off-by-one misreported water materials)
- 09-water.md: frame-state fix, water time, why University water looks
darker than DownTown's (authored data), splashes still open
- README: fork renamed to rusty-trucks
- --extract-ocean-pca runs before the crash supervisor, whose report
window would block setup on a failed (non-zero) extraction
- ocean_pca.convert: install.spawn() pipes are text mode; reading them as
bytes aborted the environment refresh
- test_ocean_pca runs convert through the real spawn() for success and
failure
Verified: refresh rebuilt only the environment group (90 s) and wrote
assets/private/ocean-pca.json.
The shared FrameStateData buffer (clock, ocean PCA frame, shadow floor) was
updated by replacing the ShaderStorageBuffer asset's data every frame. Bevy
0.18 then creates a new GPU buffer, while every world material's bind group
keeps the first one, so all world shaders saw clock 0 and no PCA frame:
water never animated, family 14 UV scrolling and the shadow floor froze.
- Create the buffer once (COPY_DST), extract FrameStateData to the render
world and write it with RenderQueue::write_buffer (PrepareResources)
- Water families 30/33 use clock.z: retail water time runs at half
real-time speed and loops every 10 s (behaviour of the disc build's
water update; re-implemented, reference only)
- The unsupported-family warning lists the shaders it falls back for
Confirmed in play: water animates on University and DownTown.
See docs/hails-additions/09-water.md.
Water and ocean shaders need assets/private/ocean-pca.json, which setup
never created, so 52 water/ocean materials rendered static (or black).
- skate_data::xex: XEX2 unpacker (retail AES file key + CBC payload, LZX
or basic decompression), no new dependencies
- skate_data::ocean_pca: locates cPCAWaterAnimationData's table in any
build by its code addressing (disc build: 0x82FC3978; TU3: 0x830118D8)
- skate3rust --extract-ocean-pca <default.xex> <out.json>
- Setup environment group runs it (optional content); refreshes that group
In-play check of the rendered water still pending.
See docs/hails-additions/09-water.md.
Water is retail collision surface type 12 (ID 1591); only DownTown and
University have it. The skater's water flag and height (Collision+3481/+28
-> Processed2488 bit30 / 2924) were never written, so the wipeout state's
water path (special surface, buoyancy, IsInWater) could never start.
- publish_water: body contact (SkeletonCollision material 12) or board
contact (collision flag bit 25) sets the flag and water height
- No recovered retail check bails on water: touching it now requests an
ordinary bail (request slot 33, logged WATER_BAIL); project choice
- water_surfaces example lists collision surface types and water per map
Confirmed in play: bails on University fountain/reservoir and DownTown.
See docs/hails-additions/09-water.md.
One skate3rust process validates TEST_WORLD and every converted map over
stdin/stdout (SKATE_MAP_CHECK lines). Existing checks still fail a map;
new spawn-support / neutral-startup / invisible-collision checks report
warnings. Setup falls back to per-map --check-assets with an older exe.
See docs/hails-additions/06-map-validator.md (PR F).
Note: install.py also carries PR G's customiser-overlap hunks
(available_memory, overlap_customiser, Background); they are interleaved
with the validator wiring in _install and are documented in doc 08.
Collections are looked up through a lazily built OnceLock<HashMap> index
with first-match semantics, identical to the linear scan on 145,807 real
lookups. See docs/hails-additions/07-collections-index.md (PR E).
Retail collision includes clustered meshes in which no unit carries a
surface ID (unit flag 0x80 unset). Across every shipped district these
are only 12-triangle boxes: zone, trigger and level-bounds volumes. The
loader made them solid, creating invisible walls (SkateSchool spawn
area, DownTown's Kube Tower plaza, MegaPark's stadium entrance, Maloof).
Authored player positions lie inside some of them, so they are not solid
in the original game.
visit_clusters now decodes and validates each mesh fully and skips it
when no unit has a surface ID. Meshes mixing surfaced and surfaceless
units are ordinary geometry and stay whole. RetailTriangle records
has_surface.
Collision triangles loaded: SkateSchool -84, DownTown -24, Maloof -24,
MegaPark -12, all other maps unchanged. Confirmed in play.
See docs/hails-additions/05-collision-volumes.md.
Every map's startup position and default landing zone is its baked
spawn. The old rule (upward triangle nearest the origin) put the skater
over the void on MegaPark and on the roof of Industrial Skate Park and
StartPark, because park origins sit at a corner and retail floors are
sometimes wound downward.
New map_starts.py resolves each district's start from retail data: the
world row's start locator (parks), a chosen fe_locations row for
districts with several (University career start, Industrial Haystings
Park, DownTown Rosalita Skate Park, Maloof street), or the district's
only fe_locations row. Position and heading come from the district's
global_locators records; the heading is now written into the .skate
header. Districts without an authored start fall back to geometry:
compact parks use the new ground_spawn search, city-sized districts keep
the original rule (ground_spawn put Industrial under the streets).
See docs/hails-additions/04-map-spawns.md.
Implements issue #14. A dedicated thread owns SDL3 (3.4.16, statically
linked via sdl3 =0.20.0) and publishes per-slot snapshots re-expressed as
the XInput-shaped XboxState, so the TU3 conversion in skate-core is
unchanged. Conversions are exact for XInput-backed pads (stick Y via
bitwise NOT, trigger round-to-nearest). SKATE3_INPUT=xinput selects the
original transport, which is also the fallback if SDL fails to start.
Optional settings/controller.json maps paddles to XInput buttons.
Build: scripts/Ensure-CMake.ps1 locates CMake (PATH or Visual Studio's
copy) for the SDL source build. sdl3-sys is vendored with a build.rs
patch setting CMAKE_MSVC_RUNTIME_LIBRARY from crt-static, because SDL's
CMake (policy CMP0091) ignores cmake-rs' -MT and the static-CRT release
build failed with unresolved __imp_* symbols.
See docs/hails-additions/03-sdl3-gamepad-input.md and vendor/README.md.
Roster work paths under deep checkouts exceed MAX_PATH (260), leaving
pro skaters unavailable. If LongPathsEnabled is off, setup asks once for
elevation to enable it and relaunches itself, since Windows reads the
setting at process start. Declining keeps the previous behaviour.
See docs/hails-additions/02-long-paths.md.
extract-xiso stops parsing options at the first image path, so
`-x <iso> -d <dir>` extracted into the working directory and then failed
on "-d" as a second image (exit 1 -> "Conversion failed"). Use
`-x -d <dir> <iso>` in both call sites.
See docs/hails-additions/01-iso-extraction.md.
MotionFactory already dispatches CreateGrindAttributes, ControlGrindCrouch
and GrindControlFade to motion_grind; the duplicate stock-gameplay variants
were unreachable and contradicted the routing test.
Native static registration uses matchingID -1; the RWCM unit group only
splits clusters. Using the group filtered static-world line queries for
actors whose query ID differed from it.
Add generalized engine access and challenge mods, repair SKATE turn handling, and improve collision prediction and physics transitions. Reduce mod snapshot and material update overhead, preserve authored GLB materials with lighting and shadows, and improve CPU profiling.