Files
iw4L/docs/MAP-LOAD.md
T
vtroc c72783395d Charge a span to the frame it ran in, and prepare an image variant once.
A 47.78 ms frame reported a 36.35 ms `Load` inside an `Update` its own row
implied was 11.87 ms, and 364 frames summed their root spans to more than their
own wall. Three mistakes stacked: a span was credited to whichever frame was
open when it *closed*, its parent was whichever span had opened most recently
anywhere in the process, and the remainder was a sum of overlapping intervals
clamped at zero.

Now a span is charged by intersection with the frame's wall, and what it ran
before the frame opened is `carried_in_ns` rather than nothing. Nesting comes
from a thread-local stack, so the render thread no longer adopts the main
thread's spans, and a close out of order is counted. `unclassified` is wall
minus the union of the root intervals, which cannot go negative and counts two
threads inside one nanosecond once. The report prints how many frames still
hold a span longer than its parent.

`present_hud_surfaces` and `present_hud_stage_max` measured wall *between*
systems, which `.chain()` leaves the executor free to fill. They are renamed
`*_schedule_interval`, tabled apart, and given a number that is the HUD's:
`hud_tess_body`, taken inside the tess flush bodies, with `hud_tess_jobs`
beside it. The screenshot flag now stays up through the readback, the encode
and the write instead of only while the request queues.

An image decode gains an identity — the resolved IWD candidate list plus the
recipe bits that change the payload — so two plans that would prepare the same
bytes share one decode through a registry of weak handles, and a claim the
merge drops is reported as the same variant twice or as another game's
different image under the same name. Merge precedence and the final content are
untouched. The load job row splits budget wait from decode, renames
`join_wait_ms` to what it measures, and carries `produced_bytes`; the budget
wait is bounded so it cannot outlive a join order that stops guaranteeing
progress. The manifest walks the nested artifact cache instead of reading zero
files from its top directory, and prints the viewport the passes were sized by
beside the surface.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-15 21:19:25 +03:00

2.7 KiB

Map load: what lives where

Three floors: disk (asset_transport), format (fastfile_* → the asset_iw4 IR), products (asset_* + assets). assets knows the disk through the transport and nothing of the format; fastfile_* knows the format and opens no files. Installing the match is session, not here.

console/ui `map` → session::lifecycle   SessionSwapRequest, tear the previous
                     occupancy down, then MapLoadApproved{request_id}
  → assets::match_load    start_match_load onto load_pool() (MatchLoadBusy),
                          poll_match_load → PreparedMatchReady
  → session::match_apply  apply_prepared_match → scene + SimWorld + MatchInstalled

Cancellation is MatchLoadAbort(request_id): the walk returns MatchLoadOutcome::Canceled at its next checkpoint, throwing nothing away. Failure is MapLoadFailed, from discovery and install; a request ends once.

The walk: assets::session_load::load_prepared_match

The single entry point; the composition root sees no fastfile_* and no AssetSink. Inside are parallel walks on load_pool(): the archive, startup materials, common_mp, the IW5/T5 weapon bundle, language zones. common_mp goes before the map — its techset tables are needed before build_world_draw. Output is MatchLoadOutcome::Ready with a PreparedMatch (world, one material population, clip, weapons, catalogs and PreparedMap).

A walk enqueues its own image plan the moment it is ready, and the catalog keeps one row per image name. Two plans resolving the same archive entry with the same recipe share one decode; a name three games spell alike but fill differently is an override, not a duplicate, and load_jobs.csv (BENCH.md) counts the two apart. The per-game adapter is assets::lane (ZoneGame → iw4/iw5/t5); a lane gap is a typed LaneGap, never silence.

Installing it is session::match_apply, in two halves. Preflight builds a MatchInstallPlan — mode, doors, objectives, scene conversion, drawable world — and publishes nothing until it hands one over. Commit publishes it, boots the sim and writes MatchInstalled.

Cache, and poking it: iw4l-artifacts/cache/<kind>/<prefix>/<key>

Content-addressed leaf in asset_transport::artifact_cache (cache_get / cache_put, fnv1a64). A miss is silent — the caller computes the value anyway — and a hit must be the same bytes a miss would have written. The key names every input; if the encoder changed, bump the format word. Live kinds: mips, wgsl, localize and xwma_pcm, whose miss is a batched ffmpeg. IW4L_GAMES holds the game trees; no folder name is hardcoded. Live it is make map mp_boneyard (RUN.md), with stages in LoadProgress.