Repository navigation
Research Report R9 Box Map
Round-9 pass: can a TNC custom map use a brand-new map name? Static analysis of the runtime snapshot found no step that limits map names to the retail list — the name only has to be listed in dlc_0's packagemapspec.json. A box map is built as a third map in dlc_0's own chunk_22, next to c2v1 and c2v1_custom, and all build checks pass. Not yet seen in the game; the route also depends on the dev-menu command branch, which is itself still unconfirmed in game.
Date: 2026-10-02. Nothing was installed and the game was not started. Nothing
was written under <Steam library> or Saved Games; the install was only
read. Builds went only to D:/wolf-decomp/tmp/boxmap/. New files:
tools/build_boxmap.py and this report.
Yes, a new map name works. Static analysis of the runtime snapshot
(re_probes/runtime_87400/module.bin, same EXE SHA256 as installed) found no
step that limits map names to the retail list. You only have to list the name
in dlc/dlc_0/base/packagemapspec.json.
The box map is therefore built as a third map in dlc_0's own chunk_22,
next to c2v1 and c2v1_custom. Retail does the same thing: dlc_2's
chunk_22 holds c2v1, c2v2 and c2v3. The c2v1 slot (kit level H) stays
byte-identical, so the box costs no map slot. A second folder such as
dlc_00 is worse because:
- it would need its own dlcid (SearchDLC drops a duplicate id,
0xabe2c6); - it would need its own 886 MB texdb link, sound packs and pages.
Not yet seen in the game. Static analysis covers every step up to the
spawn. The whole route depends on the dev menu command branch
(mods/tnc_devmenu_fix), and that branch is itself still unconfirmed in game.
"Read" means the result comes from disassembly or decompiled code of the snapshot. "Measured" means it was observed in data or in the game.
| # | Step | Address | Result | Basis |
|---|---|---|---|---|
| 1 |
devmap callback |
0xa7e0d0 → parser 0xa7bd10
|
The only map-related rejection in the parser is the existence check (#2). The parser's other strings are argument errors for -spawnspot, -checkpoint, -layername and the like. |
read |
| 2 | Existence check |
0xa7b330, called at 0xa7cd07
|
The check builds maps/<name> ('maps/%s' 0x26c6418) and appends '.%s'. If [0x30b54e8] >= 2 it calls only resource-manager slot +0x80, then slot +0x88. Slot +0x80 failing gives "Map %s is not present in this package". Slot +0x88 failing gives "map %s is not installed". The loose-file test ("Can't find map") runs only when the value is below 2. |
read |
| 2a | Value of [0x30b54e8]
|
0x30b54e8 |
2 in the snapshot. Map load sets it to 3 (0xac407d). |
measured (snapshot) |
| 2b | Slot +0x80 = 0xabf3d0
|
vtbl 0x26d2a08
|
Returns true if the name is in the base spec or in any DLC spec. | read |
| 2c | Slot +0x88 = GetIsChunkForMapInstalled 0xabf6a0
|
0xabf841, 0xabf86e
|
Spec lookup 0xabf440. For a DLC entry it logs "dlc maps are considdered installed for now". It then checks only base/ref chunks with IsChunkInstalled, which is always 1 on PC (chunklist.md). The result is true. |
read |
| 3 | Name lookup | 0xac5d10 |
Strips the extension and every leading maps/, then runs a linear, case-insensitive string compare over the spec entries (stride 0x68). There is no hash and no fixed list. |
read |
| 4 | Base before DLC, first hit |
0xabf440, 0xabf500
|
Searches this+8 (base) first, then the DLC list (this+0x30, 0x160 per DLC) in folder-scan order. The first hit wins. Prefix '%s/%s' = DLC path + base/. Matches dlc4_cfile.md. |
read; DLC-vs-DLC precedence measured in game on 2026-09-24 |
| 5 | Mount by name (LoadNamedResourceBlock) | 0xac1730 |
Calls 0xabf6a0. For a DLC entry it mounts DLC sound at 0xac1a47 with the prefix, then base chunk/chunkbase, then <prefix>chunk_<chunkid>.resources and .texdb (0xac2308–0xac241c). Refchunks are mounted from base/ (0xac24ef loop). |
read |
| 6 | The map needs its mapresources |
0xac217b, 0xac27a9, 0xac281d
|
Formats 'generated/buildGame/%s.mapResources' and looks it up in the mounted containers. If it is missing: "The map %s isn't present in the cache file" and the load is aborted. Retail stores the name in lower case, so the lookup is case-insensitive. |
read |
| 7 | Other resources named after the map | strings + refs |
'%s/_combo/%s.proc' 0x26c64b0; '_combo/_world.tome' 0x381d78; '_combo/sky.bmodel' 0x381b9f; '_combo/_world.bmodel' 0x26062e0; '_combo/_world.bshadows' 0x381fcf; '/_combo/world.hkshape' (fn 0x4b0700); '_combo/_world.pvs' 0x2651bc8; '%s/%s.ambientsh' 0x75a4a2; '%s/mega.decl' (MapInit 0x11b55f0); 'generated/staticstreamtree/%s.bsstree' 0x2606448; '/devmapinfo.json' (0xafcc20, only on the -checkpoint path); 'maps/%s/lightprobes/%s_row.bimage' (per light-probe entity; the box has none). The .prockd and .hknavmesh names follow the retail pattern; I found no format string for them. |
read |
| 8 | Side file textures.cache
|
0xac40a7–0xac40c9
|
'%smaps/%s/textures.cache' = <dlc>/base/maps/<name>/textures.cache → MountMemoryDatabase 0x793090. If the file is missing: warning "Failed to open", then return without popping the heap it pushed, so the heap stack loses one slot per load. The box needs this file under its own name.
|
read |
| 9 | Virtual texture pages |
0x8457cb–0x845871
|
'%s%s/%s.pages' = DLC prefix + virtualtextures/ + the material's landpagefile. That file name comes from the mega.decl text, not from the map name. The box keeps c2v1's landpagefile, so it reads dlc_0/virtualtextures/maps/game/dlc/c02/c2v1.pages, which already exists. The _vmtr pages (0xac5471) are optional (file-exists test first). |
read |
| 10 | Sound |
0x8cb91f, caller 0x8cc6a7
|
prodspec has no entry for the map, so the game logs "[SOUND] No banks found for map '%s'" (a warning). The caller ignores the result. The box therefore has no map-specific banks; global sounds still play. | read |
| 11 | MapInit with no chapter part | resolver call 0x11b6733, writes 0x11b6798–0x11b6818
|
0xDF5860 matches the map name first; no part names game/wolfsdk/box, so it returns false. Chapter, variation and part stay 0, because each load gets a fresh game: AllocGame 0x11a7aa0 → 0x11ade60 zeroes +0x163a08/10/18. The playerDef comes from the worldspawn's reflected playerDef field (+0x930, record 0x20eaea0; set at 0x11b55f0, decomp line 1248). That field is null here. |
read |
| 12 | Player with null playerDef | SetupPlayer 0x11bb7b9–0x11bb7f3
|
Falls back to entityDef "player". It is fatal ("Failed to load player entityDef") only if that decl is missing. It is in gameresources.resources. Mission init 0xf973b0 tests part and variation for 0. No chapter or variation data is needed.
|
read; the player decl's presence was measured (archive index) |
| 13 | An extra name in dlc_0's spec does not break startup | installed dlc/dlc_0/base/packagemapspec.json
|
Lists game/dlc/c02/c2v1_custom as well as c2v1 since 2026-09-24. The kit levels have loaded with it since then. |
measured (indirect) |
Gaps: 0xabf3d0 and 0xabf6a0 were read from disassembly, not run.
SearchDLC (0xabdcf0) was not re-traced; it is taken from dlc4_cfile.md
and r2_mapnodlc.md. There it is measured by log order and a
screenshot-confirmed run.
python tools/build_boxmap.py takes about 2 minutes and builds twice.
--once builds once. Output in D:/wolf-decomp/tmp/boxmap/box/:
| File | Content |
|---|---|
dlc_0/base/chunk_22.resources |
147,335,539 B, sha256 1fc43560a43cb6f833c0261bfda1245b54cddcb2546946645c92ef84c167a58f. It is kit level H (sha cd7e9340…, identical to the installed dlc_0 chunk) plus 29 added entries. Nothing was replaced or removed. |
dlc_0/base/packagemapspec.json |
c2v1_custom and c2v1 as mapinstall writes them, plus {"name": "game/wolfsdk/box", "chunkid": 22, "basechunkid": 6, "layer": -1, "refchunks": [9, 13]}, patchlevel 3 |
dlc_0/dlcinfo.json |
{"dlcid": 4} |
side_files.json |
The 14 dlc_2 side files mapinstall already links, plus one new link name: base/maps/game/wolfsdk/box/textures.cache → dlc_2's base/maps/game/dlc/c02/c2v1/textures.cache
|
box.glb |
The room as glTF: 6 nodes, so it can be opened in Blender |
devmenu_entry.txt, manifest.json
|
The dev menu item, plus hashes, added names, engine-name evidence and check results |
The 29 entries:
-
17 map-level c2v1 entries copied under the new name:
maps/game/wolfsdk/box.entities.../box/_combo/box.proc.../box/box.prockdgenerated/buildgame/game/wolfsdk/box.mapresources-
_world.tome(0 B, as in H) and-metadata.json -
box.ambientsh,box.hknavmesh,devmapinfo.json mega/megatrans/megafoliagetrans.decl-
_world.bmodelandsky.bmodel(40 B each in H) _world.pvs-
world.hkshape(c06p2's, as in H) generated/staticstreamtree/game/wolfsdk/box.bsstree
-
12 new entries: a
modeland acmfor each of the 6 slabs.
The rename rule (maplevel()/rename()) takes the map's own entries:
-
maps/<m>.entities,generated/buildgame|staticstreamtree/<m>.*; - under
maps/<m>/:_combo/*,<base>.*,devmapinfo.json,mega*.decl.
Per-entity resources stay under c2v1 (trigger cms, lightprobes, kiscules). The box has none of those entities.
The box itself is reused, not newly written code:
-
Room: inside 8 × 8 × 4 m, slabs 0.25 m thick, floor top at
(5, -183, -4.5). That is the kit hall floor inside c2v1's proc areas. c06p2's world collision is ≥ 295 m away. -
Slabs:
build_gltfmap.pieces()builds them: r4 model writer, box cm with contents | 0x18 (variant D),megatexturedModel = false, materialtextures/common/blockout_gray, one.proctexel each (variant H recipe). -
.entities: H's worldspawn verbatim;player_start_1, cloned fromc2v1_ps_challenge_01with no layer, 2 m south of the centre and 0.1 m above the floor;wolfsdk_box_light, a clone of H's point lightkitmap_light_hall_fillat 3.2 m, radius 9, warm white. -
.mapresources: H's 20,838 rows with the map-level rows renamed, the map list set tomaps/game/wolfsdk/box, and 12 always-load rows.
-
Container:
- Before finalize,
probe_idclhashrejects the file (negative control). - Finalize moves the data hash of exactly the 29 added entries.
- Entry count is 7,130 + 29.
- All 7,130 base entries are unchanged (payload, version, flags, dependencies), so c2v1 is still kit level H.
- The 29 entries are each present once, as written.
-
probe_datahash+0x50 7158/7158(the 1 mode-4 entry is skipped, as in every build).probe_idclhash+0x08 1/1 +0x20 1/1.
- Before finalize,
-
Naming:
naming_problems()returns [] on the built container. It checks four things:- every map-level c2v1 entry has a copy under the new name;
- no mapresources row of the box still names a map-level c2v1 resource;
- every such row exists renamed, and the map list is the new name;
- the box's text payloads do not contain
game/dlc/c02/c2v1. The only allowed exception is themega*.declpage reference (#9).
The controls each fail as they should:
-
A:
box.ambientshleft under the old name only; - B: its mapresources row left on the old name;
-
C: the old map path inside the box
.entities.
Separately, all 14 engine-derived names from the format strings exist under
game/wolfsdk/box._world.bshadowsis missing on purpose, as it is in H, which loaded in game. -
.entities:- byte-exact round trip;
-
build_custom_map.checkfinds nothing (996 entityDefs mounted through the new spec); - exactly 1 worldspawn, 1 start, 1 light and 6 static instances;
- every slab model resolves, and every cm resolves "exact" to our own cm;
- the start and the light are inside the room;
- the 6 collision boxes equal the designed slabs (closed room).
-
.mapresourcesand.proc: base rows kept in order with only the map-level ones renamed, plus 12 always-load rows; the only unresolved map-level row is_world.bshadows, exactly as in H; the.prockeeps all base records and gains 1 record per slab. -
Determinism: two builds give the same sha256.
wolfsdk/mapinstall.py and the devmenu builder belong to others, so here is
what they would need:
-
mapinstall.py:- let
install()write the box entry as a third spec map (or take the spec from the build); - add one hardlink:
dlc_0/base/maps/game/wolfsdk/box/textures.cache→dlc_2/base/maps/game/dlc/c02/c2v1/textures.cache; - record both in
wolfsdk_karte.json, so uninstall recognises them.
After that:
python -m wolfsdk map-install D:\wolf-decomp\tmp\boxmap\box\dlc_0\base\chunk_22.resources. - let
-
tools/build_devmenu_fix.py: add the item below todevmenuoption/mainand raisenumfrom 3 to 4. Field order and quoting are the same as the existing campaign command entries.
item[3] = {
devMenuDisplayName = "WolfSDK: Box";
devMenuCommandStrings = {
num = 7;
item[0] = "si_spawnspot \"player_start_1\"";
item[1] = "si_nextlayeractive \"layer/dlc/c2v1\"";
item[2] = "si_filllayeractive \"\"";
item[3] = "si_devprogression \"\"";
item[4] = "si_checkpointName \"\"";
item[5] = "si_gamemode 0";
item[6] = "devmap game/wolfsdk/box";
}
}
layer/dlc/c2v1 loads the same layered preload rows as in the kit-level
tests. The box's own entities have no layer.
Revert the mods in the loader first: mapinstall forbids creating files in dlc_0 while tncpatch journals it.
$g = "<Steam library>\steamapps\common\Wolfenstein.II.The.New.Colossus\dlc"
$b = "D:\wolf-decomp\tmp\boxmap\box\dlc_0\base"
Copy-Item "$g\dlc_0\base\packagemapspec.json" "D:\wolf-decomp\tmp\boxmap\spec_before.json"
Copy-Item "$b\chunk_22.resources" "$g\dlc_0\base\chunk_22.resources.new"
Move-Item -Force "$g\dlc_0\base\chunk_22.resources.new" "$g\dlc_0\base\chunk_22.resources"
Remove-Item "$g\dlc_0\base\packagemapspec.json"; Copy-Item "$b\packagemapspec.json" "$g\dlc_0\base\packagemapspec.json"
New-Item -ItemType Directory -Force "$g\dlc_0\base\maps\game\wolfsdk\box" | Out-Null
New-Item -ItemType HardLink -Path "$g\dlc_0\base\maps\game\wolfsdk\box\textures.cache" -Target "$g\dlc_2\base\maps\game\dlc\c02\c2v1\textures.cache"Never open the hardlinked files for writing. To replace one, delete it first and then copy. Undo:
- copy
re_probes\kitmap\variants\H\chunk_22.resourcesback the same way; - put
spec_before.jsonback; -
Remove-Item -Recurse "$g\dlc_0\base\maps\game". This only unlinks the link; dlc_2's file stays.
Until it is restored, the loader's uninstall will name the changed chunk and spec as unknown.
- Apply the mods (with the box item in tnc_devmenu_fix) and start the game.
Open the dev menu → "WolfSDK: console test". The console must show
g_debugLayerChanges= 1. If it does not, the command branch does not work and the box test means nothing. - Dev menu → "WolfSDK: Box".
- Pass: you spawn in a closed grey room (8 × 8 × 4 m) lit by one warm light, can walk, do not fall, and cannot leave through the walls.
- Afterwards type
condump boxmap1.txtin the console and send the file fromSaved Games/.../base/. Lines that explain a failure:
| Log line | Meaning |
|---|---|
Map game/wolfsdk/box is not present in this package |
spec not installed |
map ... is not installed |
the installed check in step 2c failed |
The map ... isn't present in the cache file |
the old chunk is still in dlc_0 |
MountMemoryDatabase: Failed to open ...box/textures.cache |
the link is missing |
[SOUND] No banks found for map 'game/wolfsdk/box' |
expected, harmless |
Failed to load player entityDef |
the player fallback broke |
Falling through the floor means a collision problem. A black room means a lighting problem.
Variant B if campaign mode misbehaves: use si_gamemode 5 plus an extra line
si_mainmenustart 1, which is what the c2v1 entries use.
- The dev menu command branch, and
devmapitself, have never run in game. All of r8's analysis is static. - A non-retail map name has never been loaded: steps 1–12 are static only.
- New
model/cmentries have never been loaded in game. This is DESIGN.md risk 1, the same as thebuild_gltfmapbuild. - Campaign mode (
si_gamemode 0) with no chapter part on a DLC-chunk map:- the player becomes the generic
playerentityDef, with that decl's default inventory; - whether the game makes an initial autosave for a chapterless map is unknown.
- the player becomes the generic
- Lighting: borrowed lightmap texels plus one dynamic light. The room may look blotchy or dark (variant G looked blotchy).
- No map sound banks (the warning from step 10). Ambience will be silent.
- Static reading: the heap-stack leak when
textures.cacheis missing is inferred from decompiled code. The link avoids that case, so it is not tested.
No fallback into the c2v1 slot was built, because the new name is not ruled
out. If the box fails at steps 2–6 (one of the "not present", "not installed"
or "cache file" lines above), the same pieces can go into
c2v1.entities the way the kit level does, launched with the existing
"Death is Coming (C2V1)" dev menu entry.
Related: Research Report: Chunklist, Research Report: Dlc4 Cfile, Research Report: R2 Mapnodlc, Research Report: R8 Devmenu Fix Verify, Research Report: R10 Modtest Map, TNC Custom Map Status, DLC Overrides.
English Wiki generated from the measured research notes in docs/. Evidence grades and uncertainty are preserved intentionally.
- WolfSDK Loader · DE
- WolfSDK Studio · DE
- FAQ · DE
- Mod format · DE
- Modding Guide
- Modding Surface
- CVar Catalog
- CVar Validation (old scrape)
- Texture Database
- Developer Mode
- Dev Menu
- Custom-Map Status