Wolfenstein Modloader Buy me a coffeeKaffee spendieren

Wiki / The New OrderThe New Order

The 25 Cards from Wolfenstein: The New Order

ReferenceNachschlagewerk Original page in the GitHub wikiOriginalseite im GitHub-Wiki

In plain wordsIn einfachen Worten

Every level in Wolfenstein: The New Order has a text file that lists where enemies, weapons, ammo, health packs and collectibles are placed. This page catalogs all 25 of these map files and calculates how much each one can be edited while still fitting into its old slot in the game archive. Small same-length swaps fit; adding anything new forces a rewrite of a whole archive file of 73 to 466 MB. None of this has been tested in the game.

  • The 25 maps hold 69,951 entities, including 1,167 enemies, 467 weapons, 333 ammo, 569 health items and 284 secrets.
  • Seven maps contain no enemies at all (intro, intermission and transition sections).
  • On 15 of 25 maps an enemy type can be swapped for one with an equally long name without a rebuild (size change between −8 and +56 bytes).
  • Adding even one enemy never fits: one enemy block compresses to 1,700–2,900 bytes, more than the largest free space of 1,185 bytes.
  • Not tested in the game: whether the engine accepts a modified map file at all.

Jedes Level von Wolfenstein: The New Order hat eine Textdatei, die festlegt, wo Gegner, Waffen, Munition, Medipacks und Sammelobjekte stehen. Diese Seite katalogisiert alle 25 dieser Kartendateien und berechnet, wie stark sich jede ändern lässt, ohne den alten Platz im Spiel-Archiv zu sprengen. Kleine Tausche mit gleicher Länge passen; alles Neue erzwingt das Neuschreiben einer ganzen Archivdatei von 73 bis 466 MB. Nichts davon wurde im Spiel getestet.

  • Die 25 Karten enthalten 69.951 Entities, darunter 1.167 Gegner, 467 Waffen, 333 Munitionspakete, 569 Heilgegenstände und 284 Geheimnisse.
  • Sieben Karten enthalten gar keine Gegner (Intro-, Zwischen- und Übergangsabschnitte).
  • Auf 15 von 25 Karten lässt sich ein Gegnertyp ohne Neubau gegen einen mit gleich langem Namen tauschen (Größenänderung zwischen −8 und +56 Byte).
  • Schon ein einziger zusätzlicher Gegner passt nie: Ein Gegnerblock braucht komprimiert 1.700–2.900 Byte, mehr als der größte freie Platz von 1.185 Byte.
  • Nicht im Spiel getestet: ob die Engine eine geänderte Kartendatei überhaupt annimmt.

Technical detailsTechnische Details

Der technische Teil ist auf Englisch – so, wie er im Wiki steht. Unterstrichene Begriffe haben eine Erklärung auf Deutsch.

All data here is measured against Retail files (Steam, id Tech 5, .index/.resources). Tool used: tools/entities.py. Reading occurs exclusively from base/_wolfsdk_backup/ — the installation is patched, and therefore pristine() from tools/build_gore_deep.py provides the originals. Statements without measurement are marked as unverified.

Read-only. This document describes no write operation. No mod was created, nothing applied, nothing reset. The cost accounting in Section 3 is purely theoretical: the modified card bytes were compressed and measured in memory, never written to disk.


1. What an .entities File Is

Each card lies as a file asset in the archive, uncompressed plaintext in Decl dialect:

Version 3
entity {
    entityDef pick_up_prop_weapon_shotgun_1 {
    inherit = "prop/weapon/shotgun";
    class = "idItem";
    expandInheritance = false;
    edit = {
        spawnPosition = { x = -23388.927734375; y = -396.16; z = 2403.78; }
    }
}
}

Three fields carry the entire placement information:

  • inherit — what is there. ai/… an enemy, prop/weapon/… a weapon, prop/ammo/… ammunition, prop/health/… a medipack, prop/secret/… a collectible, player/start a spawn point.
  • class — the engine class that instantiates the block (idAI, idItem, idLight, idTarget_Count, …).
  • edit.spawnPosition — where. If an axis is missing, it does not appear in the file; tools/entities.py displays it as ?. That the engine reads a missing axis as 0 is plausible but unverified.

Parsing with wolfsdk/decl.py — No Second Parser Needed

The dialect is identical to that of the entityDef decls which Decl already handles. Three places deviate, and Decl tolerates all three instead of aborting:

Place What Decl Makes From It
Version 3 as first line a scalar Version = 3
renderModelInfo = ! { … } the ! becomes a scalar, the block following is flattened
"float" gravity = 1066.0; typed keys shift around one token

None of these places lie on the path to inherit, class, or spawnPosition, and the block boundaries remain intact because Decl delimits blocks by bracket depth, not grammar. Verification: count the line starts entity { in raw text and the blocks provided by the parser — identical across all 25 cards (--summary outputs the line "Block Count Verification"). The parser swallows nothing.

Cost: c01p1.entities (2,356,914 B) yields 70,683 paths in 3.4 s with ~78 MB peak memory. All 25 cards combined: 25 s. Anyone wishing to byte-exactly address the edit internals would need to extend Decl with ! and typed keys — not with a custom parser.


2. What Is in the Cards

python tools/entities.py --summary
Map Chunk usize csize Entities ai Weapons Ammo Health Secrets
c01p1 chunk1 2 356 914 159 750 4 106 0 0 0 3 3
c01p2 chunk2 4 334 134 289 855 5 553 102 37 45 55 10
c01p3 chunk2 3 223 959 229 143 3 815 91 20 18 57 12
c01p4 chunk2 1 010 142 81 599 1 724 15 0 1 2 3
c02p0 chunk3 63 775 6 763 112 0 0 0 0 0
c02p1 chunk3 1 882 208 131 026 2 024 46 8 8 13 11
c03p1 chunk4 113 418 14 460 205 0 0 0 0 6
c03p2 chunk4 3 871 412 246 251 4 867 90 37 47 69 17
c03p3 chunk4 617 856 42 608 1 042 0 0 0 0 3
c04p1 chunk5 774 449 65 686 1 305 9 0 0 2 8
c04p2_3 chunk5 4 855 203 315 550 6 475 132 43 23 32 13
c05_07_09 chunk6 4 037 943 287 281 5 332 96 30 18 59 30
c06p1 chunk7 4 027 482 271 631 4 915 96 32 21 30 10
c06p2 chunk7 1 760 060 127 545 2 336 31 16 25 33 13
c08p1_2 chunk8 2 826 712 188 846 3 700 74 14 0 18 16
c10p1 chunk9 2 411 984 171 854 3 298 38 19 22 16 27
c11p1 chunk10 1 588 739 92 545 1 853 46 27 8 13 17
c11p2 chunk10 821 903 60 772 1 397 0 0 0 0 8
c12p1 chunk11 305 362 12 906 607 0 0 0 0 1
c12p2 chunk11 2 803 240 196 016 3 090 89 48 39 46 23
c13p1 chunk12 4 303 907 247 721 5 380 111 33 0 47 29
c14p1 chunk12 1 857 951 128 742 2 288 44 20 21 25 11
c16p0 chunk13 112 693 12 293 243 0 0 0 0 0
c16p1 chunk13 2 239 929 124 375 2 930 47 30 21 20 10
c16p2 chunk13 909 123 73 922 1 354 10 53 16 29 3
25 Maps 53 110 498 3 579 140 69 951 1 167 467 333 569 284

The category sums match exactly with docs/moddingflaeche-tno.md (1 167 / 467 / 333 / 569 / 284). The total number of blocks is 69 951, not 69 952 — the difference is a counting error in the earlier survey, which Section 1's cross-check confirms as 69 951.

Seven maps contain no enemies at all (c01p1, c02p0, c03p1, c03p3, c11p2, c12p1, c16p0). These are the intro, intermission, and transition sections. Anyone wanting to move enemies around is working on the remaining 18.

c16p2 is the exception in the other direction: 53 weapon spawn points with only 10 enemies — the final chapter dispenses supplies, no foes.

What a single map yields

python tools/entities.py --map c12p2 --stats

provides entity count, categories, the ten most frequent engine classes, and the complete species list for enemies, weapons, ammo, health, and secrets. Example c12p2:

Enemy species (89 items, 8 types)
   ai/tungsten/c12/part_02/infantry_1960                32
   ai/tungsten/c12/part_02/infantry_1960_snipertrains   31
   ai/tungsten/c12/part_02/rocket_trooper                7
   ai/tungsten/c12/part_02/supersoldat                   6
   ...
Weapon spawn points (48 items, 7 types)
   prop/weapon/handgrenadeemp                           25
   prop/weapon/assaultrifle1960                          8
   ...

Finding individual entities

python tools/entities.py --map c06p1 --find prop/weapon
python tools/entities.py --map c16p2 --find "^ai/"

The pattern is a regex and runs against inherit, the entity name, and the class. Output includes Name, inherit, the length of inherit, and spawnPosition. The length column isn't there for show — it's the key to Section 4.


3. The Cost Question

Installation.apply knows two ways to write back a modified asset (wolfsdk/patch.py):

  1. In-Place. Archive.write_in_place compresses the new content with deflate_exact to exactly the old csize and overwrites the slot. Offset and csize remain untouched; only usize in the index moves. Cost: one seek and a few hundred thousand bytes.
  2. Rebuild. If the new content does not fit into csize, Archive.rebuild is called and the entire chunkN.resources is rewritten, all entries are realigned, and the index is refilled.

The boundary between the two is a single number:

free = csize − length(our own Deflate of the original)

As many compressed bytes the modified card may grow. If the number is exceeded, apply falls back to the Rebuild path.

python tools/entities.py --cost
Card Chunk csize Rebuild writes free Slot +1 char raw→comp. one Entity-Block raw→comp.
c01p1 chunk1 159 750 73 MB 428 438 — 234 → 71
c01p2 chunk2 289 855 368 MB 727 728 +11 → +2 580 → 194
c01p3 chunk2 229 143 368 MB 279 288 +42 → +12 9 589 → 2 647
c01p4 chunk2 81 599 368 MB 34 35 +11 → +52 1 074 → 247
c02p0 chunk3 6 763 121 MB 0 5 — —
c02p1 chunk3 131 026 121 MB 711 725 +31 → +4 9 533 → 2 811
c03p1 chunk4 14 460 159 MB 0 4 — —
c03p2 chunk4 246 251 159 MB 1 071 1 076 +20 → +0 10 055 → 2 751
c03p3 chunk4 42 608 159 MB 54 54 — 151 → 42
c04p1 chunk5 65 686 233 MB 61 71 +1 → +0 9 821 → 2 559
c04p2_3 chunk5 315 550 233 MB 774 776 +24 → +4 6 350 → 1 910
c05_07_09 chunk6 287 281 278 MB 393 408 +31 → +3 376 → 91
c06p1 chunk7 271 631 204 MB 476 477 +51 → +12 6 210 → 1 740
c06p2 chunk7 127 545 204 MB 372 379 +7 → +5 933 → 276
c08p1_2 chunk8 188 846 161 MB 506 508 +30 → +2 657 → 204
c10p1 chunk9 171 854 204 MB 436 438 +6 → +1 9 419 → 2 792
c11p1 chunk10 92 545 172 MB 281 296 +6 → +1 9 266 → 2 881
c11p2 chunk10 60 772 172 MB 24 36 — 151 → 40
c12p1 chunk11 12 906 140 MB 0 6 — —
c12p2 chunk11 196 016 140 MB 567 567 +32 → +7 355 → 134
c13p1 chunk12 247 721 466 MB 1 185 1 192 +71 → +9 10 148 → 2 679
c14p1 chunk12 128 742 466 MB 383 393 +12 → +2 10 138 → 2 792
c16p0 chunk13 12 293 208 MB 0 11 — —
c16p1 chunk13 124 375 208 MB 676 685 +30 → +2 448 → 143
c16p2 chunk13 73 922 208 MB 77 91 +7 → +6 7 317 → 2 337

Rebuild costs per Chunk — treating a single card as "rebuilding" means rewriting the entire file, and revert must fully restore it afterwards:

Chunk Size affected cards
chunk12 466 MB c13p1, c14p1
chunk2 368 MB c01p2, c01p3, c01p4
chunk6 278 MB c05_07_09
chunk5 233 MB c04p1, c04p2_3
chunk13 208 MB c16p0, c16p1, c16p2
chunk7 204 MB c06p1, c06p2
chunk9 204 MB c10p1
chunk10 172 MB c11p1, c11p2
chunk8 161 MB c08p1_2
chunk4 159 MB c03p1, c03p2, c03p3
chunk11 140 MB c12p1, c12p2
chunk3 121 MB c02p0, c02p1
chunk1 73 MB c01p1

What the numbers mean

The available space is better than "0–428 bytes" sounds — but only for the right kind of change. frei counts compressed bytes. Card text compresses extremely well because it consists of tens of thousands of nearly identical blocks. The "+1 character" column measures the exchange rate: if the most common enemy name becomes one character longer in all its occurrences, that costs 71 raw bytes on c13p1, but only 9 compressed. The rate varies by card between 1:1 and 15:1, with a median of about 6:1 — including an outlier in the opposite direction (c01p4, see below).

Given this, for a typical card with 400 bytes of available space: Text changes on the order of 2,000 characters fit without a rebuild. This is sufficient for dozens of reordered inherit targets or shifted coordinates. The number is a rule of thumb — for the specific change, continue to measure.

But a single additional enemy fits nowhere. The last column shows why. A real ai/ block from later cards is 6,000 to 10,000 bytes raw and 1,700 to 2,900 bytes compressed — these blocks drag entire edit sections with Scene Points, patrol paths, and animation bindings. Even the largest measured space (c13p1, 1,185 B) lies below that. Adding entities always means a rebuild. Only in enemy-free cards with small blocks (c01p1: 234 B/block) do additional entries fit — but there is nothing there one would want to add.

Four cards have exactly zero space: c02p0, c03p1, c12p1, c16p0. There, our Deflate hits the delivered csize byte-for-byte. Any change costing even a single byte forces a rebuild. All four are enemy-free and uninteresting for placement mods anyway.

The Slot column is a footnote. Archive.slot_size assigns each entry its csize plus up to 15 bytes of alignment padding. These 0–15 bytes would theoretically be additionally available, but Archive.write_in_place does not use them — it keeps csize intentionally constant because a gap in the data stream causes the engine to misaddress entries later (No address, error: 487). The column exists only to make visible that nothing can be retrieved there.

Ranking: which card is cheap?

Cheap means: lots of space and a small chunk if a rebuild does occur anyway.

  1. c02p1 — 711 B free, rebuild only 121 MB. The cheapest card with enemies at all. 46 ai/, 8 weapons.
  2. c12p2 — 567 B free, 140 MB. 89 enemies, 48 weapon locations: the best ratio of content to cost in the entire game.
  3. c03p2 — 1,071 B free, 159 MB. 90 enemies, 37 weapons.
  4. c08p1_2 — 506 B free, 161 MB.
  5. c11p1 — 281 B free, 172 MB.

Expensive:

  • c13p1 and c14p1 both lie in chunk12 — 466 MB per rebuild, the highest costs in the game, although c13p1 has the largest space at 1,185 B. Whoever can change c13p1 in-place saves the most here.
  • c01p2, c01p3, c01p4 in chunk2 — 368 MB. c01p4 has only 34 B free for that.
  • c16p2 — 77 B free at 208 MB Chunk: 53 weapon locations, but little room to move.

4. The length-preserving change

The pattern is defined as _same_width() in tools/build_gore_deep.py: a number reformatted so that it keeps exactly the same number of characters. Translated to cards means swapping an enemy type against one of equal length.

Important: equal length guarantees nothing. Deflate depends on content, not just length — two texts of equal length compress differently. Equal length keeps the difference only small. Therefore, it was actually measured for each card: apply swap, re-compress, read the difference from the original.

tools/entities.py --cost searches for this by finding the most common ai/ type per card, taking another with an identical character count (different case of the same name does not count — that would not be a swap), replacing all occurrences, and measuring again.

Card Swap Occurrence Δ Deflate Fits in free?
c01p2 …/soldier_trenches_var2 → …_var3 11× −2 B yes (727)
c01p3 c01/part_03/soldier → kamphund/kamphund46 42× +56 B yes (279)
c02p1 …/drone/ranged_drone → …/pistol/commander60 5× +5 B yes (711)
c03p2 …/blackguard → …/soldier_02 20× +2 B yes (1 071)
c04p1 …/rifle/prisonguard01 → …/baton/prisonguard02 1× −8 B yes (61)
c04p2_3 part_03/rifle/prisonguard01 → part_02/baton/… 24× +12 B yes (774)
c05_07_09 c15/part_01/blackguard → …/panzerhund 26× +8 B yes (393)
c06p1 …/drone/drone_moon → …/robot/guardrobot 6× +15 B yes (476)
c06p2 …/rifle/airforce_02 → …_03 2× −2 B yes (372)
c08p1_2 …/camp_guard_reception_03 → …_02 4× −4 B yes (506)
c10p1 …/rifle/infantry60_07 → …_01 4× ±0 B yes (436)
c11p1 …/open_area_04 → …_02 6× ±0 B yes (281)
c14p1 …/rifle/airforce_02 → …_03 5× −2 B yes (383)
c16p1 …/fire/firetrooper → …/robot/guardrobot 5× +6 B yes (676)
c16p2 …/mecha_deathshead_stage2 → …_stage3 1× +1 B yes (77)

15 of 25 cards can be changed this way without a rebuild. The difference lies between −8 and +56 bytes and remained within the margin in every single case. Notable: four swaps make the file even smaller.

The other ten fail not due to size, but due to the lack of a partner: seven cards have no ai/ spawns at all (c01p1, c02p0, c03p1, c03p3, c11p2, c12p1, c16p0), and in c01p4, c12p2, and c13p1 there are no two enemy types with the same character count among the 5 to 9 existing ones. The solution there is: use another enemy type from a different card with matching length — the inherit path is a global Decl name and not bound to the card. Whether the engine loads an enemy from Chapter 6 into Chapter 12 without the associated models and animations is unconfirmed.

The length column in --find is exactly for that purpose:

python tools/entities.py --map c16p2 --find "^ai/"

   Name                                  inherit                                len  spawnPosition
   cb_ai_mecha_deathshead_stage2         ai/…/mecha_deathshead_stage2            47  (? 1792 ?)
   cb_ai_mecha_deathshead_stage3         ai/…/mecha_deathshead_stage3            47  (224 192 -512)
   cb_ai_prototyperobot                  ai/…/prototyperobot                     38  (-111.78 3886.07 ?)
   cb_ai_scriptedanimweb_mdh_stagger     ai/scriptedanimweb                      18  (-192 -1472 -342)

Same number in len = swappable without growth.

What else works with length preservation

  • Coordinates with the same digit count. x = -23388.927734375 has 17 digits; any other 17-digit number fits. That is exactly what _same_width() is written for — it sacrifices decimal places before letting the file grow. Moving a pickup a few meters costs zero bytes this way.
  • Weapons against weapons of equal length. Counted: prop/weapon/shotgun ↔ prop/weapon/handgun (both 19 characters), prop/weapon/ar60marksman ↔ prop/weapon/kitchenknife (both 24). prop/weapon/knife (17), prop/weapon/handgrenadeemp (26), and prop/weapon/assaultrifle1960 (28) stand alone. The lengths are listed in --find.
  • Ammo counts, provided they appear as a number within the block and the digit count remains the same (5 → 9, not 5 → 10).

What does not work

  • Add or remove entities. Adding destroys every room (Section 3). Removing makes the file smaller — it always fits in csize, because deflate_exact fills the remainder with a // comment. Whether the engine's Entities parser accepts // is unconfirmed and was not tested here; the Decl parser does.
  • Extend names. The 5:1 exchange rate works in both directions, but c01p4 shows the outlier: +11 raw bytes cost +52 compressed there — more than the 34 bytes of room. A well-compressed file can react to a small change with a large jump if it causes a Deflate block to be re-partitioned. Measure, not estimate.

5. Open Risks

  • None of this has been tested in-game. The cost calculation is byte-exact and reproducible; whether the engine accepts a modified .entities file is not demonstrated. From particle experiences in tools/build_gore_deep.py, it is known that streamed resources are silently rejected if something is wrong when their pre-calculated size does not match. For .entities, it is unconfirmed whether they follow the same streaming path.
  • The // padding mechanism of deflate_exact depends on the reader skipping comments. For Decls, this is confirmed; for .entities, it is unconfirmed.
  • Rebuild is slow and risky. Writing 466 MB anew, and revert must copy back the same amount. An interruption mid-way leaves a half-written .resources file — Archive.rebuild writes to a .wolfsdk_tmp file and replaces it only at the end, which covers this case.
  • Enemies from foreign chapters (the solution for the ten maps without an equally long partner) assume that models, animations, and sounds are in the same chunk or loaded subsequently. Unconfirmed.

6. Tool

python tools/entities.py                      # Card list
python tools/entities.py --summary            # all 25, ~25 s
python tools/entities.py --map c12p2 --stats  # what is inside
python tools/entities.py --map c12p2 --find "prop/weapon" [--limit N]
python tools/entities.py --cost               # Cost calculation, ~2 min

All read-only. The tool does not open any file for writing and creates no mods.