Files
skate-3-rust-engine/docs/hails-additions/06-map-validator.md
T
Hails 18f41a33c5 Tooling: streaming map validator (--validate-maps) wired into setup
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.
2026-10-01 00:13:53 -06:00

125 lines
6.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 6. Streaming map validator (PR F, tooling)
## Problem
Setup validated every converted map by launching the game once per map
(`skate3rust --assets <stage> --map <map> --check-assets`, inside each
`map_job` subprocess), plus a `--test-world --check-assets` launch before and
after the map stage and one per recovered old map. Each launch cost ~7–8 s, of
which only 0.1–2 s depended on the map: ~6 s was the same stock loading
(skater runtime 3.2 s, controls + camera 2.5 s) repeated every time.
The check was also weak: a map passed as long as it loaded. It did not catch the
spawn problems found in play (skater falling into the void on MegaPark, standing
on an invisible collision-only floor in Industrial).
## Change
### Game: `skate3rust --assets <dir> --validate-maps`
New module `crates/skate-game/src/map_validation.rs`, dispatched from `main.rs`
after the stock loads (config, gameplay config, manifest, graphs):
- Loads once: animation source, controls and camera (fail fast, like
`--check-assets`). Prints `SKATE_VALIDATOR_READY seconds=…` on stdout.
- Reads one request per stdin line (a `.skate` path or `TEST_WORLD`) and answers
with one `SKATE_MAP_CHECK {json}` stdout line; exits 0 at EOF. All other game
output stays on stderr. Works through the crash supervisor, which forwards the
child's stdout/stderr unchanged and lets it inherit stdin.
- Per request:
- **errors** (the map is rejected, as before): map load,
`skate_world::validate_runtime`, physics with the map's collision, skater
runtime. A panic inside one map becomes that map's error (`catch_unwind`).
- **warnings** (reported, never rejecting): from
`physics/startup_check.rs` — no collision within 10 m below the spawn; deck
spawned away from the map spawn/heading; a neutral startup (zero actions,
as with no controller) must settle with wheel support for 12 consecutive
ticks within 240 ticks (4 s at 60 Hz), with finite body state, no physics
failure and a solved wheel contact; and the collision floor under the spawn
must have a visible (render) surface within 1 m.
- JSON fields: `path, ok, errors, warnings, collision_triangles, spawn,
support_drop, grounded_ticks, visible_floor_gap, seconds, timings`.
- `--check-assets` is unchanged.
### Setup (`tools/asset_pipeline/install.py`)
- `MapValidator` runs one validator process; `start_validator` falls back to
per-map `--check-assets` launches if it cannot start (e.g. an older game exe),
and `validate()` falls back for the rest of the run if the process dies.
- The pre-map stock check, every map (as soon as its conversion finishes, while
the remaining maps keep converting), recovered old maps and the final
test-world check all go to the same process. The per-map launch was removed
from `convert_map`.
- Process launching (packaged-exe DLL/PATH handling) moved from `run()` into a
shared `spawn()`; `run()` behaves as before.
- Warnings are written to `assets/private/map-status/<map>-validation.json`
(`status: "warning"`) by `record_validation`. New module
`validation_report.py` wraps `optional_content.summary` and adds them to
`setup-report.json` and the setup message; `install.py` and `tools/setup.py`
call the wrapper. `optional_content.py` itself is deliberately untouched: it is
part of the hud/environment/maps fingerprints **and** of every
character-customiser stage version (`customiser_cache.SHARED`), so editing it
would make every existing installation rebuild the customiser (~4 min), which
has no equivalence mechanism. (A first version edited it; the fingerprint
check that caught this compares against the committed tree, not a local
install.)
- `pipeline-equivalence.json`: removing validation from `convert_map` changes the
`maps` fingerprint without changing any output, so an old→new pair is added
(combined with PR G's stream change, see doc 8). Verified: an installation
built from the committed code rebuilds no group, and the customiser
fingerprint and all five stage versions are unchanged.
## Verification
- Validator on the test world and all 10 maps: all `ok`, no warnings; collision
triangle counts equal the post-volume-fix baseline.
- Negative cases (bad spawns written into copies of real maps):
MegaPark's old void spawn → "no collision within 10 m below the spawn" +
"wheels never found support"; Industrial's old harbour-bed spawn → "no visible
surface … (invisible collision)".
- Known limit: a spawn on a solid, visible roof (Industrial Skate Park's old
roof spawn) is not flagged — rooftops are legitimate skate spots; only
authored start data (doc 4) distinguishes them.
- Unit tests: `map_validation::tests` (visible-surface heights, single-line JSON
with the documented fields); Python `test_map_validator.py` with a fake
validator speaking the protocol (results, warnings into `setup-report.json`,
start failure fallback, process death, request injection, pipes closed under
`-W error::ResourceWarning`). Existing setup/pipeline suites pass (40 tests).
- Fingerprints: with the equivalence pairs, an existing installation rebuilds
no groups.
- Full setup with the real game: see "Setup run" below.
### Setup run
Full fresh setup from the extracted disc with the real game exe (Windows 11,
i7-14700KF, 3 map workers): **466 s, 0 warnings**; the validator was used
throughout (no fallback).
| Step | Before | After |
|---|---|---|
| Pre-map stock check (`--test-world --check-assets` → validator start + `TEST_WORLD`) | ~6 s | ~1 s |
| Per-map validation | ~7–9 s each, inside each map job | 0.28–1.79 s each, in the parent while other maps convert |
| Map stage (convert + validate 10 maps) | ~217 s | 183 s |
Per-map `phase_seconds.validate` in `maps.json`: University 1.54, DownTown 1.79,
Industrial 1.11, parks 0.28–0.37 s. Afterwards `check_maps`-style validation of
all maps: all ok, no warnings, collision triangles identical; baked spawns
identical to the authored values (doc 4).
## Notes
- The ignored private test `private_extracted_map_supports_production_gameplay_startup`
uses a fixed 24-tick window and fails on Industrial Skate Park, whose authored
start is 0.75 m above the floor (≈0.39 s ≈ 23–24 ticks of fall). The map is
fine in play; the validator uses the settle rule above instead. The test was
left unchanged.
- Without PR E (collection index) every per-map check would still take ~5.8 s.
## Files
- `crates/skate-game/src/map_validation.rs` (new), `physics/startup_check.rs` (new),
`main.rs`, `config.rs`, `physics.rs`, `skater_animation.rs` (`AnimationSource::load` visibility),
`retail_character.rs` + `tests/map_transition.rs` (new `Config` field in test literals)
- `tools/asset_pipeline/install.py`, `validation_report.py` (new), `pipeline-equivalence.json`,
`test_map_validator.py` (new), `tools/setup.py` (message wording)