Repository navigation
TNO Modding Surface
A map of what has not yet been touched in the game. All numbers and field names originate from the Original Data located under base_wolfsdk_backup\ (the installation is patched—those reading from base\ are measuring their own Mods). Reproducible with:
python tools/survey_modding_surface.py # full report
python tools/survey_modding_surface.py --probes # only the four probes
python tools/survey_modding_surface.py --type ammo --samples 3Status: 105 Asset Types, 203,843 Index Entries, of which 76,364 unique (Type, Name) pairs (the remainder are duplicate slots of the same asset in different chunks—apply patches all copies).
The types here split into 84 Text / 21 Binary—according to the counting rule in tools/survey_modding_surface.py, which checks one sample per type (is_text(): over 95% printable characters in the first 4096 bytes) and knows only two classes. Engine-Modding-Surface.md counts the same 105 types as 80 Text / 5 Mixed / 20 Binary; the four mixed types whose sample was text land here on the Text side. Same measurement, different rules.
25,887 unique Assets in Text Types (not Index Entries—the number 60,233 in Home.md and File-Formats.md §4 counts entries including duplicates and thus is not contradictory to this).
25,559 of these are valid UTF-8, and 100% of these 25,559 parse wolfsdk/decl.py without error—each one was sent through Decl(), no single DeclError. The 328 outliers are 327 embedded binary files under file: (PS4 Save Icons, e.g., ../submission/orbis/gmc/save_data_icon.png) and a renderProg (brokenwindshieldspecular) with non-UTF-8 bytes in the shader text.
| Rating | What |
|---|---|
| Guaranteed | 73 unused Text Decl Types, identical format to already used ones; sound (9,445), ammo (94), aiBehavior (36), inventoryItem (47), footstepEvents, playerUpgradeAmmo, weaponUpgrade … |
| Probable |
gamemodesp as a Cheat path around the CVar lock; enigma to unlock Bonus Modes; .entities Maps (switch Enemies/Pickups); .script Maps (full Script VM) |
| Provenly Impossible |
particle (measured), Virtual-Texture Pixel, chapter17/Map xa01 (Map missing in Archive), Changes to renderProg without Shader-Recompile proof |
Full list: python tools/survey_modding_surface.py (first section). The brief classification—Count = unique (Type, Name):
Binary, uninteresting for Decl-Modding (21 types, 50,477 unique Assets):
sample 19,545, cm 8,403, anim 7,490, voicetrack 5,115, model 2,294,
image 2,272, prtMeshDist 1,484, discreteAnimation 1,204, cg 565,
cuttableResource 565, baseModel 463, atlas 371, skeleton 368,
jointconversion 251, aas 37, glassModel 25, foliageModel 10, font 6,
staticParticleModel 4, video 4, morphVertices 1.
image is used despite being Binary (BIM replacement, wolfsdk/bim.py).
Text, already used (10 types):
material 3,781, entityDef 1,173, damage 153, weapon 86,
projectileImpactEffect 112, projectile 102, aiBehaviorVo 14,
goreBehavior 9, articulatedFigure 9, difficultysetting 5.
Text, provenly impossible: particle 1,563.
Text, unused: 73 types. The following sections go through the top 20 and those with high leverage.
Each with real field names from real Assets.
Example sound:vo/english/commander/commander2_ai_paalarmresponse_indoor_05:
{
inherit = "inheritences/vo/vo_battle_alarmresponses";
edit = { soundFiles = { num = 1; item[0] = "sound/vo/.../..._05.wav"; } }
}
Real fields over the inventory: soundFiles.item[N] (up to item[11]+), volume.min / volume.max (1,955 / 1,877 occurrences), minDistance (1,000), maxDistance (1,316), offset (652), falloff (346, e.g., "sound/falloffs/impacts"), rmsTable (557), groups (383, e.g., "SSG_IMPACTS"), soundChannel (278, e.g., "SND_CHANNEL_DAMAGE"), soundShaderFlags (1,716, e.g., "SSF_DISTANCE_BASED_STEREO SSF_PASS_HDR").
Editable: complete remix. Make enemies louder/quieter (maxDistance), make weapons punchier (volume.max), swap sound sources (soundFiles.item[0] to point to another .wav in the archive). This is pure set on existing paths — no risk.
Only one field: tracks.num + tracks.item[N] → voicetrack/... names. Example voiceover:vo/ai/efforts/eff_soldier_meleehit, num = 33. aiBehaviorVo (already used) points to these Decls; this is the level below — which specific tracks an event may draw from. A voiceover with num = 1 makes a voice monotone, swapping item[N] places foreign audio recordings on an event.
The most interesting type after entityDef. This is the animation-event table per model. Measured frequencies: 44,667× event/frame/row/locked, 26,160× sound, 18,132× anim, 8,307× projectileImpactEffect, 1,049× voiceover, 767× particle, 1,609× tag/trans/rot (Muzzle points etc.), 659× hitTestGroup (e.g., "neck").
Example assets: md6Def:weapons/german_knife.md6, md6Def:items/cigarette.md6.
Editable: Hang or shift sounds/effects on animation frames (frame = 12; event = "ae_leftFoot"), change hit zone assignments (hitTestGroup), move Muzzle offsets (tag = "muzzle"; trans = (0 0 0)). Warning: particle references here are only names — they point to impossible particle Decls, but the reference itself is changeable (to another existing particle).
Rigid field schema, identical in all 1,205 Decls:
model, linearFriction 0.1, angularFriction 0.1, contactFriction 0.2,
linearFrictionWater 1.0, angularFrictionWater 1.0, bouncyness 0.35,
gravity 0 0 -750.0, maxSimulationTime 4, clipMask
solid, ikclip, shotclip, worldCollisionOnly 1.
27 Decls additionally have an explosion block with radius 5000, impulse 325000, falloff 0.05, angular 1500, duration 0.25.
12 have particleTrail + trailSpacing.
Example: breakable:breakable/rifle, breakable:breakable/shotgunshell.
Editable immediately: Shell casings that bounce (bouncyness), stay longer (maxSimulationTime 4 → 60), fall in slow motion (gravity). Explosive barrels with larger radius. A 10-line generator over all 1,205 Decls is an afternoon's work.
Single lines: renderParm:sortlight = { Vec 400 },
renderParm:adaptiveglareblurstep = { Vec 1 }. Only name + number(s), no semantics in the asset. What a parameter does is stated only in the shader that reads it. Unclear which ones visibly affect — here one would have to try individually.
Curve data. table:sound/falloffs/impacts:
{ clamp min 0 max 1 left 0 right 1 {0:1, 0.0576923:1, 0.102885:0.913725,
0.552885:0.843137, 0.797115:0.611765, 0.914423:0.423529, 0.969231:0.235294,
0.997791:0} }
These tables are referenced by sound, playerProps (worldFadeTable, framescale, lowhealthpulse) and Render code. Whoever wants to rebuild the health pulse effect, sound falloff, or damage feedback curves does it here. table:console_add_small_lights_3 = { snap {0}} shows the minimal form.
env:maps/game/wolf/c06/c06p2/area4:
{ inherit { wolf/c6/c6p2_hangar }
renderParms { userEnvMap env/game/wolf/c06/c06p2_hangar_bottomroom_px } }
Pro area-specific render parameters set on a map. This allows changing the lighting/atmosphere mood per area without modifying the map itself.
120 unique filenames: 25 .entities maps, 37 .script files,
6 strings/*.lang, debugmenu.txt, .proc/.envareas (binary-adjacent).
fx:fx/maps/c01p2/dynamitedoor contains editEvents.item[N] with
type = "FX_PARTICLE" / "FX_SOUND" / "FX_SCREEN_SHAKE", each with
duration, startCondition/stopCondition ("FX_NONE"), as well as
particleParms.declPrt and soundParms.sound.
Editable: Add screenshake on every hit, swap explosion sounds, extend effect durations. Important: particles are only referenced here — swapping them for another already existing particle is allowed, but changing the particle itself is not (Section 6).
Contains HLSL source text. renderProg:softparticleblendmodst:
{ inherit particleBlend
state { blend GL_SRC_ALPHA GL_ONE_MINUS_SRC_ALPHA depthmask twosided }
hlsl_vp { #define VP_UNITTEXCOORD #include "softparticle_vp.inc" ... }
hlsl_fp { ... color.xyz = lerp( $fogColor.xyz, color.xyz, fogVal ); ... } }
Evidence from WolfNewOrder_x64.exe: the strings
Failed to compile render prog %s from %s,
While compiling %s program %s from %s, Compiled '%s': %.1f ms and the
Cvars r_loadPrecompiledShaders ("If true, cache compiled shaders to
generated directory.") and r_dumpGeneratedGLSL. The engine can therefore translate at runtime. Unclear remains whether the Retail build does this as well or if a precompiled cache takes precedence — base\generated\ currently contains only decls/particle/*.bin, md6/, and world/, no shader cache. This speaks in favor of runtime translation, but is not proof. The first test would be a harmless color change in a single hlsl_fp.
job:chapter_12/obj/find_and_enter_car_number_6:
jobNameId, jobSummaryId, jobType = "JOBTYPE_REQUIRED", jobParent,
jobMaterial. Mission objectives: Text, Symbol, Main/Sub-objective classification, Hierarchy. Cosmetic but cleanly moddable.
animWeb:ai/tungsten/robot/composite/c08/p1 carries a props block with
Editor state (gridSize, showGrid) as well as the content fields
models { "characters/robot/base.md6" } and
composite { "ai/tungsten/robot/base", }. The large ones (up to 125 kB,
subWeb:player/fp_hands/assaultrifle1960) contain the actual state machines. This is the animation state machine for weapons and enemies — powerful but difficult to meaningfully change without an editor.
modelLOD:maps/game/wolf/c02/c02p0/megalod_foliage_bush_bush_02_nolod_2.decl:
fade = 4, LODs.item[0].distance = 600000, dimShadows = "DIMSHADOW_ON".
Practically only vegetation. Visibility/shadows per object.
tutorialEvent:location/c15: inherit = "location/general", headingText,
bodyText (both #str_ references). Together with strings/english.lang
(Section 4.3), any tutorial and location overlay can be rewritten.
ammo:ammo/rocketlauncher — real fields and values:
lowAmmoInClip = 2; lowAmmoInPocket = 3; minRandomCount = 1; maxRandomCount = 2; maxCount = 10; count = 8; itemType = "ITEMTYPE_ROCKETS"; ammoType = "AMMOTYPE_ROCKETS"; projectileDecl = "projectile/rocketlauncher"; weapon = "weapon/assaultrifle1960"; extraFireInterval = 666; initialBurstModeOverride = "BURSTMODE_SINGLE"; burstCountsOverride[0] = 1; giveable = true; secondaryAmmo = true;
Immediately: Maximum ammo per type (maxCount), how much can be collected (minRandomCount/maxRandomCount/count), which projectile fires a specific ammo type (projectileDecl), fire mode overrides. This is the lever for "more ammo" without Cheat-Cvars.
secrets:secrets/c11p2/wyatt:
map = "game/wolf/c11/c11p2";
layers = { num = 1; item[0] = "layer/tungsten/timeline/wyatt"; }
secrets = { num = 0; // remember to increase this
// item[ 0 ] = { id = 1157; type = "SECRETYPE"; entityName = "YOUR_ENTITY";
// name = "#str_empty"; desc = "#str_empty"; }
}
The commented-out template in the original shows the schema: id (number), type, entityName (name of an Entity from the .entities file), name, desc. The id values reappear in enigma (Section 3.3).
skins:md6/models/items/paper/paper_01:
names { "models/items/paper/paper_01" "models/items/paper/paper_01_2" } — Material replacement pairs. Combined with the already used material swap, this allows skinning entire models instead of individual materials.
flare:tl/simple03: subflares.item[0] with width, height, color.w = 0.4, mtr = "textures/w_lensflares/blobexppos2". Lensflares enlarge, desaturate, or disable.
achievement:achievement_08: achievementName, description, id = "GAME_ACHIEVEMENT_08", trigger.item[0] = "TAID_RELEASE_FRIEND". The trigger name is changeable, but Steam achievements are server-side validated — worthless as a modding target, useful as a reference guide.
inventoryItem:inventory/items/c11/mysteriousorb:
internalName, icon, needIcon, haveIcon, usable, giveable, dropable, stackable, price, sellPrice, playerCanSell, playerCanSeeInInventory. Simpler: itemUse = "INVENTORY_USE_NONE" and giveItemsOnReceive. This allows quest items to be collectible or drop-able.
All 5 Decls, measured:
| Decl | startingHealth | startingArmor | gameModeFlags |
|---|---|---|---|
gamemode/campaign |
100 | 0 | Field missing |
gamemode/999 |
999 | 0 | FLAG_AMMOUNLIMITED FLAG_NOOVERCHARGE |
gamemode/walkinthepark |
100 | 0 | FLAG_HIDEHUD |
gamemode/hardcore |
100 | 0 | FLAG_HEALTHARMORREMOVED |
gamemode/ironman |
100 | 0 | FLAG_ONELIFE |
The four bonus modes additionally carry gameModeType, gameDifficulty (DIFFICULTY_NIGHTMARE or DIFFICULTY_HARD), displayName, description, menuHelp, menuUnlockMaterial.
The complete Flag Enum — from WolfNewOrder_x64.exe, qualified names idDeclGameModeSP::FLAG_*, contiguous in the string pool at 0xdefa.. directly after the GAMEMODE_* enum:
FLAG_NONE FLAG_AMMOUNLIMITED FLAG_HIDEHUD FLAG_ONELIFE
FLAG_NOOVERCHARGE FLAG_HEALTHARMORREMOVED
There is nothing more. Five effective flags. The field schema of the Decl type stands as a Reflection table in the binary (0xce6127):
idStrId displayName · idStrId description · idStrId menuHelp ·
GameModeSP gameModeType · gameDificulty_t gameDifficulty ·
int startingHealth · int startingArmor · int enigmaIndex ·
idDeclGameModeSP::GameModeSpFlags gameModeFlags · idMaterial menuUnlockMaterial
Notable: enigmaIndex exists as a field but is not present in any single released Decl.
The fact that flags are read at runtime is verifiable: idGameLocal leads, according to the reflection table (0xcaa737), to an array idDeclGameModeSP *[5] gameModesSP as well as the fields currentGameMode and currentGameModeFlags. The five Decl names appear as a static table in the Binary (0xffba67) in Enum order:
gamemode/campaign, gamemode/999, gamemode/walkinthepark, gamemode/hardcore, gamemode/ironman. Index 0 is the normal campaign. The debug output block at 0xf54968 prints StartingHealth and AmmoUnlimited alongside GameModeSP/gameDificulty_t.
What the flags mean is stated by the game itself in
file:strings/english.lang, #str_menu_gamemode_1_description:
"Play Wolfenstein in 999 mode. \n- 999% more action \n- start with 999 health \n- unlimited ammo \n- ÜBER difficulty"
Two buildable paths:
-
gamemodesp:gamemode/campaignreceivesstartingHealth = 999and agameModeFlags = "FLAG_AMMOUNLIMITED". Since the field is missing fromcampaign, this can only be done as a full replacement ("file"), not as aset—setrequires an existing path. The field belongs to the Decl type (four other Decls carry it), so the risk is low. - Set
startingHealthingamemode/campaignto a high value usingset— a single line that should work immediately.
The Cvar g_infiniteAmmo is listed in wolfsdk/cvars.py::RETAIL_BLOCKED
(measured: WARNING: g_infiniteAmmo CANNOT be set in retail!). The Decl path completely bypasses this restriction because it does not touch any Cvar.
Untested. The game was not started for this investigation.
All 5 Decls, complete:
| Decl | Relationships | Other |
|---|---|---|
faction/auth |
faction/player → ATTITUDE_HATE, faction/resistance → ATTITUDE_HATE
|
factionNameId = "#str_people_with_radical_political_views" |
faction/player |
faction/auth → ATTITUDE_HATE
|
|
faction/resistance |
faction/player → ATTITUDE_LIKE, faction/auth → ATTITUDE_HATE
|
|
faction/neutral |
none | |
faction/auth/panzerhund |
none | parentFaction = "faction/auth" |
Structure: edit.relationships.relationships.num + item[N].otherFaction /
item[N].attitude. Nesting is truly double (relationships twice) — this is not a typo.
The complete Attitude Enum from the Binary:
ATTITUDE_UNKNOWN, ATTITUDE_IDOLIZE, ATTITUDE_LOVE, ATTITUDE_LIKE,
ATTITUDE_NEUTRAL, ATTITUDE_DISLIKE, ATTITUDE_DESPISE, ATTITUDE_HATE,
ATTITUDE_MAX. Eight usable levels; the game uses only three.
Who belongs to which faction is stored in entityDef as
faction.myFaction. Measured across all 1,173 entityDefs — 288 hits:
faction/auth 237
faction/player 31
faction/resistance 14
faction/auth/panzerhund 6
Buildable: The Panzerhund has its own faction with parentFaction, but
no relationships of its own. If one were to assign a relationships block to faction/auth/panzerhund with faction/auth → ATTITUDE_HATE, Panzerhounds would attack the guards. This is the cleanest lever: a faction that already exists, is already tracked separately, and already has six entityDefs.
Alternatively: assign a new faction to one of the 237 faction/auth enemies.
Unclear whether the engine accepts a faction not present as a name in the Binary — searching for faction/ in the Exe yields zero hits, so factions are resolved purely via Decl names. This suggests that new faction names will work, though it does not prove it (a new faction asset cannot be created with the current patcher anyway, only existing ones can be replaced).
Both changes are full replacements, not set — they add fields.
Four Decls, each:
enigma:tab0 → gameMode = "gamemode/999"
enigma:tab1 → gameMode = "gamemode/walkinthepark"
enigma:tab2 → gameMode = "gamemode/hardcore"
enigma:tab3 → gameMode = "gamemode/ironman"
Each with codes.num = 2 and per code requiredSecrets.num = 9 plus nine numeric IDs (tab0 requires, for example, 120, 121, 122, 140, 141, 142, 160, 202, 320).
Total: 4 × 2 × 9 = 72 secrets for all four bonus modes.
The IDs are the same ones assigned by the secrets Decls (Section 2.16).
Buildable: requiredSecrets.num = 0 in all four — this would be a pure set on existing paths (8 values). Whether the engine considers "0 required secrets" as "fulfilled" or "never fulfillable" is unclear.
Alternatively: set all 9 IDs per code to an ID obtained early in the game. This is also a pure set and semantically unambiguous.
This is the more elegant cheat path than 3.1: instead of bending the campaign, you unlock the 999 mode, which the game itself provides.
The direct answer to the third question — and the largest unexplored area overall.
As file assets in the archive, plain text in Decl dialect:
entity {
entityDef foliage_bush_bush_02_nolod_1 {
inherit = "foliage/bush/bush_02_nolod";
class = "idEntityTree";
expandInheritance = false;
edit = {
renderModelInfo { model = "..."; megatexturedModel = true; traceGroup = 10; }
spawnPosition = { x = -224; y = 968; z = -384; }
}
}
}
Measured across all 25 maps (inherit targets):
| Category | Count |
|---|---|
ai/… (Enemy Spawns) |
1 167 |
prop/weapon/… |
467 |
prop/health/… |
569 |
prop/ammo/… |
333 |
prop/secret/… |
284 |
player/start |
235 |
prop/armor/… |
176 |
triggers/trigger |
1 529 |
target/… total |
21 269 |
func/… total |
18 566 |
Concrete examples: prop/weapon/assaultrifle1960 62×,
prop/weapon/knife 55×, prop/weapon/shotgun 43×,
prop/health/medpacks/small 193×, prop/ammo/heavy/small 89×,
prop/secret/enigmacode 65×, prop/secret/gold 50×.
Enemy examples: ai/tungsten/c13/part_01/rifle/spacemarine 71×,
ai/tungsten/c06/part_01/rifle/blackguard_elite 51×,
ai/tungsten/kamphund/kamphund46 20×.
With this, everything can be changed: place a weapon earlier (change the inherit of a prop/weapon entry), swap an enemy (change the inherit of an ai/… entry), shift position (spawnPosition), remove an enemy, double ammo.
The limitation, measured: In-Place patching practically always fails.
maps/game/wolf/c01/c01p1.entities is 2 356 914 B uncompressed at csize = 159 750; our own Deflate of the same original yields 159 322 B — 428 Byte headroom. For c02p0.entities and c02p0.script: 0 Bytes. Any real change thus forces a complete archive rebuild (Installation.apply can do this, rebuilds path in wolfsdk/patch.py), meaning: rewrite the entire chunk file.
c01p1.entities resides in chunk1 (73 MB), c01p2.entities in chunk2 (368 MB). Feasible, but slow — and revert must then replay the full copy.
37 script files as file assets (32 map scripts + 5 global), also plain text.
file:maps/game/wolf/c02/c02p0.script (415 B):
void main() { }
void call_freelook( entity activatedEntity, entity activator )
{
$thirdPersonHands_0.setViewAnglesFromCamera();
$thirdPersonHands_0.attachLimitViewCamera( "camera", 5, 1, 10.0f, 0.0f );
}file:script/script_events.script (246 921 B) declares 913 scriptEvent signatures. Among them:
giveInventoryItem removeInventoryItem removeAllInventoryItems itemInInventory numOfItemTypeInInventory setHealth getHealth increaseHealth decreaseHealth resetHealth spawn getLastSpawned teleport respawn respawnWithFade kill killAI killByType radiusDamage damage setTakesDamage equipBestWeapon dropWeapons enableWeapon disableWeapon inhibitWeapon spawnLootBox startDreamMode endDreamMode
The Engine translates at runtime: idCompiler, Compile failed in file %s., ---------- Compile stats ----------, Exceeded maximum allowed number of functions (%d), the Opcode set (OP_PUSH_ENT, OP_GOTO, …), and the Cvar script_scriptEventsPath ("If set, tells engine to load script_events.script from specified location"). There is no bytecode cache in the archive — only the .script source texts.
This is by far the most powerful unused lever: a single line $player.giveInventoryItem("weapon/laserkraftwerk"); in main() of a map would be a Loadout Cheat without any Cvar. Unverified — neither built nor started.
Practical note: the five global scripts (script/script_main.script, script_util.script, script_defs.script, script_events.script, debug_drawtext.script) lie in all 13 Chunks — a patch to them would rewrite all 13 archives (~2.7 GB). The map scripts lie each in exactly one Chunk. So always change the map script, never the globals.
file:strings/english.lang (868 273 B), UTF-8 with BOM, CRLF:
{
"#str_achievement_00_name" "Wolfenstein master"
"#str_menu_gamemode_1_title" "999"
}
Every #str_ reference from every Decl lands here. Complete renaming of weapons, missions, subtitles, and menus. Six languages available (English, French, Italian, Polish, Russian, Spanish).
Another reservation than 4.1, not the same. .lang lies in all 6 entries raw in the archive (csize == usize), although Deflate would compress it to 28% — measured in UI-Assets.md §3. Thus the Deflate headroom calculation from 4.1 does not apply, but rather the raw write path in resources.plan_in_place(): It places the bytes unchanged at the offset, demanding for this exactly the same length. Practically this means byte-length-true replacement — "CONTINUE" can become "WEITER ", not "WEITERSPIELEN". No rebuild needed. Whoever instead runs a rebuild converts the entry from raw to Deflate, and whether the Engine tolerates it is not measured.
31 player*-entityDefs carry startingInventory — one per chapter section plus player, chapter_common_default, and chapter_start_default as base. Measured num distribution:
| num | Number of Decls |
|---|---|
| 0 | 3 |
| 1 | 17 |
| 2 | 5 |
| 3 | 1 |
| 4 | 1 |
| 5 | 2 |
| 6 | 2 |
Minimal form, entityDef:player/game/wolf/c01/c01p1:
startingInventory = { num = 1; item[0] = {
startSlot = "EQUIPPED"; inventoryDecl = "weapon/knife46cutonly"; } }
Full form, entityDef:player/game/wolf/c16/c16p0 — delivered, not constructed:
startingInventory = { num = 6;
item[0] = { startSlot = "BACKPACK"; inventoryDecl = "weapon/knife"; }
item[1] = { startSlot = "BACKPACK"; inventoryDecl = "weapon/laserkraftwerk"; }
item[2] = { startSlot = "EQUIPPED"; inventoryDecl = "weapon/assaultrifle1960"; }
item[3] = { startSlot = "BACKPACK"; inventoryDecl = "weapon/shotgun"; }
item[4] = { startSlot = "BACKPACK"; inventoryDecl = "weapon/ar60marksman"; }
item[5] = { startSlot = "BACKPACK"; inventoryDecl = "weapon/handgun"; } }
entityDef:player/game/wolf/c11/c11p1 (num = 5) additionally shows that non-weapons can also be placed there:
item[3].inventoryDecl = "weaponupgrades/shotgun_shrapnel".
This definitively answers the question regarding starting equipment: startSlot knows at least "EQUIPPED" and "BACKPACK", while inventoryDecl accepts any weapon/ or weaponupgrades/ name, with multiple entries being populated.
Additional measured starting weapons: c01p2/c01p3 → weapon/knife46, c02p0/c02p1 → weapon/kitchenknife, c03p1/c03p2 → weapon/knife, c03p3 → weapon/specialweapon/teatray.
The existing mod wolfsdk.widerstand already utilizes this field — but only on the AI side (entityDef:ai/tungsten/c06/part_01/rifle/airforce → edit.startingInventory.item[0].inventoryDecl = weapon/ai/shotgun).
The player side remains unused. Two approaches:
-
Surgical (
set) on a chapter that already has enough slots: Changeedit.startingInventory.item[4].inventoryDeclfromc11p1orc16p0to another weapon. Immediately executable, single line. -
Full replacement for the early chapters: copy the
startingInventoryblock fromc16p0toc01p1= play with a laser power plant from minute one. Player Decls are ~28 kB, so rather than manual work, a generated full replacement (build_*.py) is more appropriate.
Also on the player Decl and unused: playerHealth.components.components[1].cur (used by example mod example.dev_loadloadout), maxTimeUnderwater.value = 50000, sprintAirReductionScale = 1, suffocationDamage = "damage/base/suffocation".
playerUpgradeAmmo:upgrade1:
{ inherit = "upgrade0"; edit = { slots = {
slots[ 1 ] = { type = "AMMOTYPE_LIGHT"; amount = 10; }
slots[ 2 ] = { type = "AMMOTYPE_HEAVY"; amount = 20; } } } }
Perk tiers for ammo capacity. Increasing amount means carrying more ammo, without cheating.
weaponUpgrade:weaponupgrades/shotgun_shrapnel:
internalName, entityDef = "prop/inventory/upgrades/shotgun_shrapnel",
weaponToUpgrade = "weapon/shotgun", lockUpgrade = "UPGRADES_SHRAPNEL",
giveable, dropable, removeOnUse, usable, singular,
useOnReceive, upgradeWeaponMode.
Reloading weaponToUpgrade means: applying an upgrade to a different weapon. playerProps:player/default lists all 17 Upgrade Decls under upgradeData — there the mapping resides.
| Type | Count | Description | Assessment |
|---|---|---|---|
footstepEvents |
1 |
sneakEventDecl = NULL with comment "We set all but landEventDecl to NULL as we don't want the AI to hear anything but landings". Set to "base/footsteps/walk" → Enemies hear the player sneaking. |
safe, single line, immediately noticeable |
aiBehavior |
36 |
dodging.gunfireDodgeCoverChance, painDiveCoverChance, gunfireFlinchChance, usesSearch, usesAvoid, hasCrouches, closeMeleeDist, minChargeDistance, aimOffset, rolePreferenceOrder, forwardCoverCaps.rifleCoverCaps.crouchCaps.{fireOver,blindfireLeft,peekOver,hide}
|
safe, the actual AI lever |
aiEvent |
39 |
aiEvent:base/misc/body: eventClass = "AIEVENT_BODY", priority = "AIPRIORITY_VERY_LOW", radius = 256, duration = -1, attached, instigatorStimulus
|
safe — Perception radii for corpses, gunfire, alarms |
aiTurnParms |
6 | maxAngle = 1.3 |
safe, trivial |
trackingParms |
6 |
kickScale, rate.value = 50.0, acceleration, deceleration, springMaxLength = 128.0
|
safe — Tower/drone target tracking |
twitchPain |
4 |
damageDirectionGroups.item[N].damageDir = "DAMAGEDIR_FRONT" + jointGroups.item[N].aliasName
|
safe, supplements gore work |
globalflag |
1 | 17 named progress flags with saveID, e.g., flag_c04_lasercutter_upgrade, flag_c04_secret_locker
|
safe to read, effect of change unclear |
health / playerArmor
|
18 / 11 |
itemType = "ITEMTYPE_HEALTH", armorAmount = 5
|
safe |
gore |
12 | only inherit chains (soldier_generic_heavy ← soldier_generic), empty edit
|
safe, but empty — content sits in goreBehavior (used) |
specialEvent |
1 | specialEventType = "SPECIAL_EVENT_VAPORIZE" |
safe, a value |
lootPool |
1 |
{ } — completely empty |
nothing to retrieve |
destructiblePiece |
22 |
default = { }, many empty |
likely nothing to retrieve |
credits |
1 | creditList.item[N].{entryType,localizedStr,entry0} |
safe, cosmetic |
-
particle(1,563 Decls). Known and measured: the engine streams particles as named resource blocks with pre-calculated size; every change is rejected (idFile_RingBuffer file '<name>.bin' skipping <n> bytes), measured at 35 skips against 0 in the original. Confirmed by the filesystem side:base\generated\decls\particle\*.bincontains the written streams (e.g.,wolf/blood/takedownblood01.bin). Identicalusizeis insufficient;csizemust also match. Replacing remains the reference to a particle (infx,md6Def,breakable,entityDef) — not the particle itself. -
Virtual-Texture-Pixel. Materials with
usevirtualmapping 1retrieve their image data fromvirtualtextures/*.pages, not from the archive. The material Decl is changeable, but the pixels behind it are not. -
chapter17/ Mapxa01.chapter:chapter17isCHAPTERTYPE_SIDEMISSIONwithmap = "game/wolf/xa01/xa01". Searched across all 25,559 text assets:xa01appears in exactly three locations —chapter:chapter17,mapInfo:game/wolf/xa01/xa01(Content:{ edit = { } }), and the six.langfiles. There is nomaps/game/wolf/xa01/xa01.entities. The map is not in the game. No Decl intervention retrieves it back. -
Creating new assets.
Installation.applyreplaces existing entries; there is no path that inserts a new(type, name)entry into the index. Everything following applies only to existing assets. A new enemy type, a new weapon, a new faction: not without patcher extension. -
In-Place-Patch of large text assets. Measured (Section 4.1): the Deflate playroom is 0–428 bytes.
.entities,.script, and.langalways enforce a chunk rebuild.
Explicitly marked as unclear and not glossed over:
- Whether the Retail
renderProgHLSL is translated at runtime (2.10). - Whether
requiredSecrets.num = 0counts as "fulfilled" (3.3). - Whether a faction name works that the Binary does not know (3.2).
- Whether
script_scriptEventsPathcan be set in Retail — the CVar is not listed in the measuredRETAIL_BLOCKEDlist, but this list was derived from a limited sample. - Which of the 810
renderParmvalues appear visible (2.5).
None of these were tested during an actual game session. For this survey, the game was not launched and no mod was applied.
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