mirror of
https://github.com/SK8-ENGINE/skate-3-rust-engine.git
synced 2026-10-26 19:47:44 +00:00
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.
136 lines
6.8 KiB
Markdown
136 lines
6.8 KiB
Markdown
# 5. Invisible collision volumes
|
||
|
||
## Problem
|
||
|
||
On SkateSchool the player hit an invisible wall in the middle of the spawn
|
||
area and could not skate around. Logs showed normal physics states (no
|
||
failures), so the obstruction was in the collision data.
|
||
|
||
## Root cause
|
||
|
||
Retail collision archives contain a small number of clustered meshes in which
|
||
**no unit carries a surface ID** (unit flag `0x80` unset; units read as flags
|
||
`0x21` instead of the usual `0xa1`). Every one of them, in every shipped
|
||
district, is a 12-triangle axis-aligned box: zone, trigger or level-bounds
|
||
volumes. The game's loader (`skate_world::retail_collision_world`, via
|
||
`skate_data::retail_collision::visit_clusters`) turned every triangle into
|
||
solid collision, so these volumes became invisible walls, floors and ceilings.
|
||
|
||
SkateSchool has seven, all in its global stream (`cSim_Global.xsf`). Names
|
||
are those of the matching retail trigger-volume records (see "What the boxes
|
||
are" below):
|
||
|
||
| Box (X / Y / Z, metres) | Name | Effect in game |
|
||
|---|---|---|
|
||
| −79.1…16.5 / −1.53…6.65 / −43.3…52.3 | `tut_sksc_inthehub_vol_02` | covers the whole school area; the authored start (y 0.11) is inside it |
|
||
| −62.1…−0.5 / −1.53…6.65 / −3.6…12.7 | `tut_sksc_inthehub_vol_01` | the invisible wall in the spawn area |
|
||
| −11.0…2.5 / 0…4.17 / −2.6…1.6 (three identical) | `coach_frank_sksc` / `ws_sksc_coachfrank_instance_01` | invisible block near the origin |
|
||
| −212…578 / −73…79 / −483…492 | `tut_sksc_reset_vol01` | level bounds: floor under the whole map |
|
||
| 104.1…199.7 / −4.1…4.1 / 151.5…247.1 | `tut_sksc_wrongway_vol_01` | — |
|
||
|
||
Evidence that these are not solid in the original game: every authored
|
||
SkateSchool locator (Coach Frank, the hub, all tutorial stations) is at
|
||
y 0.0–0.4 on the real floor (surface 258), i.e. inside the big box; and in
|
||
DownTown, a surfaceless box (X 259–333, Y 48.9–57.7, Z −152…−71) encloses
|
||
the Kube Tower landing zone (`Z_DT_CubeTower`, y 55.9) and four authored
|
||
challenge start points.
|
||
|
||
Before the spawn change in doc 4, SkateSchool's spawn (y 7.63) stood on top
|
||
of the big box, which hid the problem.
|
||
|
||
## Survey of all districts
|
||
|
||
Classified per mesh by surface-ID presence:
|
||
|
||
| District | Fully surfaceless meshes | Notes |
|
||
|---|---|---|
|
||
| SkateSchool | 7 (all 12-triangle boxes) | global stream |
|
||
| MaloofMoneyCup | 2 (12-triangle boxes) | global stream |
|
||
| DownTown | 2 (12-triangle boxes) | city cell streams |
|
||
| MegaPark | 1 (12-triangle box) | global stream |
|
||
| others | 0 | |
|
||
|
||
**Mixed** meshes (some units with surface IDs, some without) are common in
|
||
real geometry: University cells (up to 31k triangles), Industrial, DownTown
|
||
(138 meshes), BlackBoxPark, and the main geometry of several parks. A
|
||
per-triangle filter would therefore remove real floors; the rule must be per
|
||
mesh.
|
||
|
||
## Change
|
||
|
||
`crates/skate-data/src/retail_collision.rs`:
|
||
|
||
- `RetailTriangle` gains `has_surface` (unit flag `0x80`).
|
||
- `mesh_clusters` decodes and validates a whole mesh (including the triangle
|
||
count check) before visiting its clusters, and skips the mesh when no unit
|
||
has a surface ID. `visit_clusters` returns the number of triangles visited.
|
||
Memory: one decoded mesh at a time instead of one cluster.
|
||
|
||
The fix is in the game, so existing installations benefit without an asset
|
||
refresh.
|
||
|
||
## Verification
|
||
|
||
- New test `skips_meshes_without_any_surface_ids` (fixture mesh with its
|
||
surface ID stripped is skipped; the others are visited intact). All
|
||
`skate-data` library and integration tests pass, as do the game's
|
||
collision/world tests.
|
||
- Collision triangles loaded per map (`SKATE_RWCM_READY`), before → after:
|
||
SkateSchool 91,790 → 91,706 (−84 = 7 boxes), DownTown −24, MaloofMoneyCup
|
||
−24, MegaPark −12, all other maps unchanged. Every map passes
|
||
`--check-assets`.
|
||
- In play: SkateSchool (spawn area free to skate) and DownTown tested
|
||
thoroughly; the Maloof and MegaPark box locations (below) were visited
|
||
afterwards and are also clear.
|
||
|
||
Further evidence the volumes are triggers: MegaPark's only box
|
||
(`tele_stadium_to_world_volume_a`, X 61.4–73.2, Y −4.1…4.1,
|
||
Z −140.8…−132.6) surrounds the stadium's arrival
|
||
point from the world (`tele_world_to_stadium_dest_locator_01`, 2 m from its
|
||
centre), and one of Maloof's two boxes lies 14 m from its arrival point from
|
||
DownTown (`tele_dwtn_to_mmcp_dest_locator_01`). The other Maloof box
|
||
(2.8 × 3.4 × 2.0 m) is 17 m from a street challenge start.
|
||
|
||
## Notes for upstream
|
||
|
||
- `cargo test -p skate-data` currently fails to compile three examples
|
||
(`apt_data`, `hud_data`, `scoring_flow_data`) that reference game-only
|
||
modules; this predates this change. `--lib --tests` runs the test suite.
|
||
- The mesh-level rule is empirical (all 12 such meshes in the shipped data are
|
||
boxes, and authored player positions lie inside some). If retail code that
|
||
consumes these volumes (zones/triggers) is ported later, they should be
|
||
routed there instead of dropped.
|
||
|
||
## What the boxes are (research, 2026-10-02)
|
||
|
||
Every surfaceless box is the shape of a named **trigger volume**: a volume-set
|
||
record (type `0x00EB0019`: matrix, bounding box, link, instance id, name) in the
|
||
same stream, whose bounding box matches the box to the millimetre. Retail
|
||
registers them as triggers when the stream loads and posts enter/exit events;
|
||
they are never solid. All 12 boxes:
|
||
|
||
| District | Volumes |
|
||
|---|---|
|
||
| SkateSchool | `coach_frank_sksc`, `ws_sksc_coachfrank_instance_01` (same box, 3 meshes), `tut_sksc_inthehub_vol_01`, `tut_sksc_inthehub_vol_02`, `tut_sksc_reset_vol01`, `tut_sksc_wrongway_vol_01` |
|
||
| MegaPark | `tele_stadium_to_world_volume_a` |
|
||
| MaloofMoneyCup | `tele_mega_ramp_up_volume_01` (X −9.3…−6.5, Z 54.5…56.5), `tele_mmcp_to_dwtn_volume_01` (X 36.2…42.1, Z 12.8…26.6) |
|
||
| DownTown | `dwtn_sessionspot_01_kubetower_volume_01` (X 259–333), `dwtn_sessionspot_02_spillway_volume_02` (X 66.2…105.9, Y 50.2…59.0, Z −244.7…−212.2) |
|
||
|
||
The `tele_*` volumes are where retail offers a teleport (a yes/no challenge
|
||
prompt). Exporting them as data the game can use (a "which volumes contain this
|
||
point" query) is planned follow-up work; this change only stops them being
|
||
solid. Research source: the TU3 code via the skate3recomp static recompilation
|
||
(reference only).
|
||
|
||
## Correction (2026-10-02)
|
||
|
||
An earlier version of this document gave the SkateSchool spawn-wall box as
|
||
X −66…−62. That came from an analysis script using the vendored Python
|
||
collision decoder, which reads the 16-bit compressed vertex deltas as
|
||
**signed**; vertices in clusters spanning more than 32.8 m then wrap by
|
||
65.536 m. The game's Rust decoder (`retail_collision.rs`) reads them unsigned,
|
||
as retail does, so **the game and this change were never affected**: the
|
||
triangle counts and play tests above are unchanged. Re-run with unsigned
|
||
deltas, every box now matches its retail trigger-volume record exactly. The
|
||
real box is X −62.1…−0.5 (`tut_sksc_inthehub_vol_01`).
|