Worker w-mapmanifest-a2 (Attempt 3; a1 paused, a2 aborted due to session limit). All numbers below are newly generated for this run.
New/Changed: tools/probe_mapmanifest.py, re_probes/mapmanifest/.
Result in one sentence
A new asset requires exactly one manifest line per resource at the map level (and even that is just a preload, not a permission). Stream-Tree, Modelstream, PVS/Tome, Static-Shadows, and Navmesh are not touched. All five are precalculated and only reusable. Any path accepts an object that does not appear in them; this is documented by code but unverified at runtime.
1. .mapresources – Format and Round-Trip
MRL v1, read from 0xABFE30 and called from
idResourceManagerLocal::LoadNamedResourceBlock (0xAC1730), which opens
generated/buildGame/%s.mapResources via idResourceList::Load (0xABA110):
'MRL\x01' | vec<str> layers | vec<u32> default_layers | vec<{str, vec<u32>}> groups
| vec<str> types | u32 n, n × (u32 type, str name, u8[24] mask) | vec<str> maps
A line remains valid if mask & active != 0 holds (0xAC070D..0xAC0726).
0xAC047C sets bit 191 unconditionally, so the mask 00×23 + 80 means "always load". In the 41 effective manifests, 629,035 of 787,053 lines have exactly this mask; 158,018 have only layer bits, neither. Zero lines have string bits.
| Check | Result |
|---|---|
roundtrip (all copies in Install) |
129/129 byte-identical, 126 different contents |
roundtrip --extra (+ all 29 copies in re_probes/, including c2v1_custom in bundle) |
158/158 (6 broken probe containers reported as SKIP, they contain no manifests) |
negative (halved, 1 byte too short, empty, Magic, line count ±1, type index, layer index, trailing byte, string length; Duplicate, 23-byte mask) |
12/12 rejected |
select: runtime counters "Load %i resources" / "skipped by layer tests" from qconsole-logs only recalculated from the manifest |
6/6 matched; Counter samples (lowest patch copy, bit 191 removed, ignoreLayers where it differs) 0/12 matched |
The read copy is the one with the highest patch level. This proves select: The lowest copy matches the counter states in none of the 6 cases.
2. Is the Manifest the Definitive List? No, It's a Preload List.
- Code:
Load(0xABA110) searches for the name (0xAB8F80). If missing, the function creates and loads the resource (vtable+0xC8,+0x28). The only brake isresource_errorInGame: 1 = Warning (0xABA419), 2 = FatalError (0xABA446). The live value in the running game is 0, default "0", description "0 - Nothing. 1 - Warning. 2 - Fatal Error - if loaded during gameplay" (idCVar at0x2F53F70, name at +0x18). - Convention: Each of the 17 Retail map chunks lists 100% of its entries (144,713 entries, 0 unlisted). The counter sample
gameresourcesshows 37,790 unlisted entries, so the check can fail. - Cross-container: 612,969 of 786,228 resolvable manifest lines exist only outside the chunk of the map. The name is resolved across all mounted containers, so the new asset may reside in its own container.
- 825 Retail lines name resources that do not exist in any container (anim 713, image 90, model 20, cm 2). One line before its payload is thus Retail practice.
3. The Four Questions
| Layer | What it is | New Asset | Authorable? | Evidence |
|---|---|---|---|---|
| mapresources | Per-map/layer preload list | +1 line, mask …80 (recommended, not mandatory) |
yes, author |
§1, §2 |
| staticStreamTree | Texture-streaming (references Images, not models) | no action | reuse only; if missing, the engine builds it at runtime | Load 0x3820AF only if image_useLooseStreaming (live 1) and global 0x30B54E8 ≥ 2 (live 2); NULL → 0x803995 → BuildStaticStreamTree 0x798480. Corpus sst: only 35,482/131,894 (26.9 %) of $streamed-Images reside in their map's tree; c06p2 provides a 44-byte tree with 0 names |
| modelstream | only Vertex-Paint per instance | no action | – | all 249 in chunk_22 named …/vertexpaint/…vpaint$model=…; 0x5981A0 loads only if the instance's vpaint field is non-empty (0xA47845, 0x5982CC) |
| pvs + tome | Umbra-cell PVS, anchored at the Tome | no action | reuse-only (Umbra-Precompute) | 0x6DC130: pvs+0x70 != tome+8 → Warning "PVS '%s' is stale, not using", PVS=NULL; queries then return −1 (0x6DC4B1). .pvs (11.7 KB) and .tome name no single object, so a new object cannot be "missing" therein. bonusmap01 is delivered without .pvs. What the caller does with −1 is not traced |
| staticShadowGeom | baked shadow geometry (11 MB at c2v1) | no action (impossible) | reuse-only | 0x381F92: only if r_useStaticShadows (live 1), NULL is silently skipped. The new object is not in the bake; whether it casts dynamic shadows is unconfirmed |
| hknavmesh | baked Havok AI Navmesh | no action (impossible) | reuse-only | NULL → only warning "Failed to load navmesh" (0xEA29E5, 0x12385C0 = Warning). Runtime cutter exists (hkaiNavMeshCutter-RTTI, idEntity::InitNavObstacle); Retail uses isNavObstacle = true; at 28 func/separable/compound-Entities (c2v3, c04p2). New traversable area: not possible |
Follow-up for new geometry: It occludes nothing (no Occluder), is not in static shadows, and is not ground for the AI. Obstacles can only be handled via the runtime cutter. Therefore, it belongs where these three things do not interfere.
4. THE RESULT: minimal change amount for a new asset
- Payload under exactly the name the Entity forms (e.g.,
models/wolfsdk/box.lwo$static_instance=1$lodgroup=default, see meshfmt), in any mounted container. Gap: No tool adds a new entry to an existing IDCL container (idcl.rebuildreplaces only).tools/dlcmod.baue_archivbuilds a new container without dependency records; whether models load without Dep-Records is open. - Manifest: +1 line per resource, mask
000…0080, in the copy with highest priority:python tools/probe_mapmanifest.py author --source re_probes/idclwriter/chunk_22.resources \ --map game/dlc/c02/c2v1_custom \ --type model --name 'models/wolfsdk/box.lwo$static_instance=1$lodgroup=default' \ --type cm --name 'models/wolfsdk/box.lwo$filterclip=18$simplify=20$filteroccluders=true' \ --out re_probes/mapmanifest/c2v1_custom_plus2.mapresources \ --container re_probes/mapmanifest/c2v1_custom_plus2/base/chunk_22.resourcesResult: 7/7 Manifest checks (old lines unchanged, header byte-identical, preload count +2), 4/4 Container checks. Independently verified:probe_datahash7127/7127,probe_idclhash+0x08/+0x20 OK. Exactly 1 data hash moved, 0 Dep-Records. The cm name format comes from the corpus (1,217 cm lines undermodels/in c2v1) and must be confirmed by cmfmt. - Entity that places the asset (entitypipe). Without it, nothing loads, because the manifest places nothing.
- Not needed: modelstream, staticStreamTree, pvs, tome, bshadows, hknavmesh.
Point 2 is strictly optional: Without the line, Load loads the asset on first reference, silently with errorInGame=0. With the line, it preloads when loading the map, just as Retail does to 100%.
5. What cannot be shown offline
"Listed" does not mean "loaded at runtime". Everything above is structurally valid after these checks, runtime unconfirmed. This applies particularly to behavior without a Stream-Tree entry, without static shadows, with PVS −1, and for Navmesh-Cutting on an idStaticInstanceEntity. The container c2v1_custom_plus2 references a box whose payload is not yet in any container (Point 1). It is a mechanic proof, not a test candidate.
6. Corrections to a1
- "PVS stale → nothing gets culled": not substantiated. Only PVS=NULL and −1 from queries are substantiated.
- "Skipping very large static shadows" makes missing bshadows non-critical: incorrectly justified. This is a console hack (
r_discardLargeStatShadowsAtLoadHack, live −1 = on PC off). Missing bshadows are non-critical due to the NULL-test in0x382036. global_resolution.jsonwas an outdated duplicate ofresolve.jsonand has been deleted.
7. Commands (all from repo root)
| Command | Expected |
|---|---|
python tools/probe_mapmanifest.py roundtrip |
round-trip byte-exact: 129/129, exit 0 |
python tools/probe_mapmanifest.py negative |
negative controls: 12/12 rejected |
python tools/probe_mapmanifest.py select |
runtime counters reproduced: 6/6 negative controls matching: 0/12 |
python tools/probe_mapmanifest.py coverage |
17 map-chunks unlisted= 0, gameresources unlisted=37790 |
python tools/probe_mapmanifest.py resolve --show 0 |
rows 787053, resolvable 786228, unresolvable 825, ONLY outside …: 612969 |
python tools/probe_mapmanifest.py presence |
maps 39; absent … 'pvs': 1 (bonusmap01), otherwise 0 |
python tools/probe_mapmanifest.py sst |
streamed image rows 131894, named … 35482 (26.9%) |
python tools/probe_mapmanifest.py code |
anchors confirmed: 56/56, cvars confirmed: 6/6 (shift 0); with --shift 1 0/56, 0/6, exit 1 |
python tools/probe_datahash.py re_probes/mapmanifest/c2v1_custom_plus2/base/chunk_22.resources |
7127/7127, exit 0 (counter-check: unrepaired map_alias_v3/bundle_headerfixed gives 7123/7127, exit 1) |
python tools/probe_mapmanifest.py author --map game/dlc/c02/c2v1 --type file --name maps/game/dlc/c02/c2v1/_combo/_world.tome |
rejected, already listed, exit 1 |