8.6 KiB
Custom map lighting
Custom .skate maps use the existing StandardMaterial PBR path: albedo, normal,
ORM, emissive, alpha mask/blend, and UV1 lightmaps remain intact. A scene-owned
shadowed directional light and ambient light now illuminate those surfaces and
PBR characters. Shadow cascades cover 100 metres; local authored point/spot
lights retain their existing settings. Area lights remain unsupported.
The environment header supplies start hour and orbit azimuth; extended headers also supply sun/moon colour, intensity and day/night ambient. The adapter maps unit sun intensity to 10,000 lux and unit ambient to 1,000 brightness. These are portable rendering choices, not recovered retail constants. The start hour is fixed: there is no new day/night controller or per-frame light update. Existing baked diffuse on lightmapped custom surfaces is retained. The package horizon colour remains the background; native sky sidecars are only loaded for retail scenes. Zero authored light intensity is respected.
Provenance and ambiguity
Startup and in-process loading share RetailScene::for_map. It checks embedded retail material definitions, WMET retail-manifest metadata, RWCM collision, SKYB retail sky, native collision edges and native rails. It never classifies from a filename, map title or ordinary lightmap presence. The Blender exporter writes WMET only for its retained SKATE3_RETAIL_MANIFEST; the retail pipeline also writes WMET/RWCM. This conservatively protects imported Skate 2 scenes whose material definitions may be absent, and edited retail-derived maps. WCFG, BMAT and MOBJ are general authoring records, not retail evidence.
A legacy retail export stripped of all native evidence is indistinguishable from custom PBR geometry. A custom rebuild retaining native records is treated as retail. No unreliable name heuristic or new authoring UI is introduced. Retain provenance when re-exporting retail maps. This classification protects the existing rendering path; it does not add support for missing retail shader families or missing sky/irradiance sidecars.
Scene lifetime
Directional/local lights, sky and backdrop are MapEntity-owned and retire with their scene. Ambient/background are replaced on publication. The gameplay camera switches retail HDR/tone on and off; the GPU exposure meter resets on a new scene generation. Retail character conversion retains original material handles and layers, restores them on each transition, and binds fresh authored lighting only in a retail scene. Dedicated zero-illuminance retail shadow maps are removed/recreated, preserving the separate player-only baked-world map. Character conversion is restricted to PlayerRoot descendants; only the gameplay camera acquires shadow layer 28. Scoring layer 31 and session overlays keep their existing cameras/materials. Custom character PBR materials respond to the custom scene lights without a separate character light rig.
Manual visual checks
- Open an ordinary custom map: compare upward and vertical surfaces, moving player shadows, normal maps, transparent textures and emissive surfaces.
- Check a map with local lights and/or baked indirect lighting; confirm those remain visible. Check an authored night start and zero-intensity sun.
- Open vanilla Skate 3 and imported Skate 2 maps: verify baked scenery remains unchanged and the dedicated player shadow remains visible where supported.
- Switch vanilla -> custom -> vanilla several times without restarting. Check sky/background, exposure, character shading and shadows for stale state or increasing brightness. Repeat with a custom character and clothing changes.
- Open scoring HUD, session marker and customization overlays on both map types; verify no world/player silhouettes or lighting appear in overlays.
- Rename a custom package to a retail-like name: it must still get custom lighting. A retail-derived package retaining provenance must stay retail.
No game, recomp, Steam or gameplay automation was launched for this change.
Automated validation
cargo check -p skate-game --tests --no-default-features --release --offline
passed with existing warnings. A static-CRT release test harness was linked
directly into the worktree's ignored private output using the shared Cargo
cache. All six focused CPU tests passed: map_render::tests (3),
retail_character::tests (2), and camera::environment_tests (1). These checks
do not validate GPU appearance; use the visual checklist above.
SKATE15 loading is also covered by ten data-reader tests (including legacy
versions, both compression codecs, all storage transforms, texture references
and malformed input). A user-supplied v15 custom map passed the ignored CPU
supplied_custom_map_prepares_and_retires check: decoding, runtime validation,
material/mesh preparation, publication, custom-light classification and scene
retirement. Supply SKATE_TEST_CUSTOM_MAP and SKATE_TEST_CUSTOM_ASSETS to run
that check on another local custom map; no user map bytes are committed.
A subsequent offboard-query exit was traced to validating the unused fourth
SIMD lane as a position coordinate. Static swept-line queries now validate
only XYZ and radius, matching their dot3/Vector3 collision math. The regression
checks identical hits with infinite/NaN fourth lanes and rejection of invalid
XYZ/radius. Six contact-toolkit tests pass. The existing
embedded_static_rwcm_hits_distinct_actor_query_ids test fails its metadata
expectation (matching_group == -1 && geometry == 0) before executing the
changed query path; that unrelated fixture expectation remains unchanged.
Custom collision performance
Portable map collision previously occupied one query mesh, so nearby queries scanned the entire map. It now uses contiguous 64-triangle bounds in the existing static hierarchy, built once during loading. Triangle order, geometry IDs, filters, packed surfaces and adjacency remain unchanged. Native RWCM clusters retain their existing path.
A static query benchmark on the supplied custom map (89,613 triangles) sampled
257 local segments with identical candidate IDs/order before and after.
Triangle checks fell from 23,030,541 to 4,198,157; measured candidate-query time
fell from 16.51 ms to 3.82 ms total (about 4.3x). A 16-triangle partition was
also tried, but had higher query time (4.69 ms) despite fewer triangle checks.
These are CPU microbenchmark results, not gameplay FPS or GPU measurements.
Ten map tests passed. The supplied-map benchmark is opt-in via
SKATE_TEST_CUSTOM_MAP. Frame timing can be captured with the existing
SKATE_PERF_REPORT environment setting (10-second warmup, report/exit after
25 seconds); no automated gameplay was run.
Frame capture: mesh binding preparation
The user-run capture after collision clustering measured 83.5 FPS / 11.98 ms mean frame time. Main schedule time was 2.80 ms, physics 1.55 ms/frame, and the render mesh-bind-group preparation section 9.75 ms. CPU query improvements therefore did not address the dominant measured render preparation cost.
The vendored renderer now caches complete per-phase model/skin/morph/lightmap binding sets, retaining at most four rotating allocation combinations per phase. Buffer contents can update without recreating descriptors. Keys include model allocation/offset/size, current/previous skin and morph buffer IDs, morph mesh/texture/skin-layout identities, lightmap revision and layout identity. Pipeline changes clear the cache; removed phases and replaced map resources retire their entries. The existing opt-in GPU cache test covers rotating allocations, reuse, size changes, skin/morph replacement and bounded retention. A new gameplay capture is required to quantify the resulting FPS change.
Validation: release build check passed, and the explicit Vulkan GPU cache regression test passed. No gameplay was launched for this change.
Remaining hitch investigation
The next user capture measured 525.1 FPS, 1.90 ms mean frame time and 0.126 ms mesh binding preparation. Two clusters of slow frames remained; the maximum was 26.71 ms, with only 4.01 ms in the main schedule and 2.71 ms in physics. Another 25.53 ms frame contained no physics tick. The aggregate render metrics cannot identify those individual stalls. No specific hitch cause is confirmed.
The opt-in profiler now retains timestamped main/render samples, pending pipeline counts, and bounded slow-section events (at least 2 ms, at most 256). Main/render sample buffers are preallocated. Reports still write only at the end of the capture; normal launches do not enable this instrumentation. The release compile check passed. Another user-run capture is required to locate the remaining stalls; no game or gameplay automation was launched.