mirror of
https://github.com/SK8-ENGINE/skate-3-rust-engine.git
synced 2026-10-26 19:47:44 +00:00
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.
125 lines
6.8 KiB
Markdown
125 lines
6.8 KiB
Markdown
# 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)
|