Track missions/anatomy, 2026-09-24. Files: tools/probe_mission_anatomy.py, re_probes/missions/anatomy/ (anatomy.json, graphs/*.txt, run.log, this report).
All data was extracted read-only from the Retail installation, the played bundle re_probes/dlc4_override/chunk_22.resources, and the runtime dump re_probes/runtime_87400/module.bin. The game was not launched.
python tools/probe_mission_anatomy.py # ~90 s, 55 Checks (7 Negative Controls), Return 1 on FAIL
Result in One Sentence
A mission is not a standalone format. It consists of a mission Decl, several missionobj Decls (each with title and description as #str_ keys), and a Kiscule Script that registers, starts, completes, and (unused in Retail) fails via ActionMission nodes. The entire process is triggered by world events (Triggers, Use, Shoot, PickUp), connected by global Custom Events. The goal "Enter the offices" from the screenshot is traced node-by-node (Section 2). This same pattern applies to DLC map c3v1 and campaign map c02p1. All 55 checks are green. Runtime evidence exists for the start chain (Screenshot); everything else is structurally documented but unverified at runtime.
1. The Chain from Chapter Selection to HUD Text
chaptervariation dlc/c2v1 (patch_3; id 230)
├─ parts[0].map = game/dlc/c02/c2v1
├─ parts[0].layers = [layer/dlc/c2v1] ← unlocks Entities
├─ parts[0].spawnSpot = c2v1_gameplay_player_start_1 (does not exist in the map, E14)
└─ parts[0].mainMission = cobalt/c2v1/mission_c2v1_main
│
c2v1.entities: entity { layers { "layer/dlc/c2v1" } entityDef sacramento_gameplay_kiscule_c2v1
class idKisculeEntity, edit.externalKisculeDecl = …/sacramento_gameplay_kiscule_c2v1.decl }
│
extkisclule …/c2v1_gameplay/kiscules/sacramento_gameplay_kiscule_c2v1.decl (122 nodes)
│ ActionMission(mission, objectives[], showNotification, completeHides)
▼
missionobj cobalt/c2v1/mission_c2v1_main_obj_01_find (patch_3)
title = #str_mission_cobalt_c2v1_main_obj_01_find_name
desc = #str_mission_cobalt_c2v1_main_obj_01_find_summary
parentMission = cobalt/c2v2/mission_c2v2_main ← Retail copy error, does not affect (E13)
material = textures/guis/mission_fc/c2v1/c2v1_mission_obj_1
▼
strings/english.lang (patch_4, cfile): "Enter the offices"
- Layer determines which scripts run. 31 of the 33 Kiscule Entities from c2v1 are active in the Story run (M01). The other two (
sacramento_gameplay_kiscule_challengeand…combat_sim_screen_fx_893872923) reside inlayer/dlc/c2v1_challenge1and only run in the Challenge Variation (E11). Entities without alayersblock always run. missionDecl (edit):title,description,missionFlags(TYPE_MAIN,TYPE_OPTIONAL,TYPE_DISTRICT,TYPE_HIDEONCOMPLETE…),material,duration. c2v1: "Assassinate Jack's torturer",TYPE_MAIN.missionobjDecl knows onlytitle,desc,material, andparentMissionacross all 338 Retail Decls (D01). There is no counter field, and no mission or goal text inenglish.langhas a counter placeholder (D02).- Both Decl types reside in
gameresources/patch_Nand are therefore mounted for every map. The Decl dialect is written and read bit-exactly: 624/624 copies of mission/missionobj/chaptervariation run through Parser → Emitter (R02, including Retail manual work: spaces after;, empty{}).
2. "Enter the offices": Start and End, Node by Node
All nodes are located in sacramento_gameplay_kiscule_c2v1.decl. The chains are exactly fixed in the Probe (E01–E03). A renamed event (N01) or a dangling edge (N04) causes failure.
START (Runtime occupied: Screenshot) BeginGame[1620406561] (ignoreInitialFlag) └─OnBeginGame→ Delay[1525383468] 500 ms └─Finished→ ActionMission[1476098740].MissionStart mission_c2v1_main, objectives = obj_01…obj_04, showNotification=false → Engine 0xF6F660 "Mission added" + 0xF6E8F0 "Adding objective … to mission" (X07) └─MissionStart→ Delay[671776109] 4100 ms └─Finished→ SendCustomEvent[1671718539] "start_obj_01" (global) ⇢ GetCustomEvent[886242348] "start_obj_01" └─Received→ ActionMission[260028346].ObjectiveStart objectives=[obj_01_find], showNotification=TRUE → Engine 0xF6EA20 "Start objective …" / "Show hud objective …" (X04) → HUD: „NEW OBJECTIVE" + „Enter the offices" (~4.6 s after map start)
END
idTrigger sacramento_gameplay_triggers_obj_1_complete (layer/dlc/c2v1)
Position (-4.75, -193.75, -0.95), Form cm "maps/game/dlc/c02/c2v1/
Machine-readable with all parameters: anatomy.json → enter_the_offices
(start_trace, complete_trace, graph with 24 nodes/24 edges from chapter variation to HUD).
3. All four objectives of c2v1
Objective (missionobj) |
HUD Title (patch_3) | Start | End |
|---|---|---|---|
obj_01_find |
Enter the offices | BeginGame → 500+4100 ms → start_obj_01 |
Trigger …triggers_obj_1_complete → end_obj_01 |
obj_02_locate |
Locate Hans' office | start_obj_02 from completion of obj_01 |
Gate (once) from Trigger …obj_2_complete or hans_notice_player (Office Script: Hans notices player) or Hans dead → end_obj_02 |
obj_03_kill_hans |
Kill Ubercommander Hans | start_obj_03 from completion of obj_02 |
AiKilledNotification Hans → 2000 ms → end_obj_03 |
obj_04_kill_all |
Escape (patch_2 said "Leave no witnesses") | Completion of obj_03 → world marker removed → ActionRemove → start_obj_04 |
UseNotification on sacramento_gameplay_prop_use_animated_exit_level → end_obj_04 |
- Example: one
MissionStartnode registers all objectives silently; each objective has anObjectiveStartnode with a HUD message and anObjectiveCompletenode without. Transitions occur via global Custom Eventsstart_obj_NN/end_obj_NN. - "Counter Update" does not exist as a Decl or HUD field (D01, D02). Counting is done in the script. Retail example (P01, 27 cases):
AiKilledNotificationwith a list of N enemies andmaxNotificationCount−1 or ≥ N →Counter(endCount=N)→Reached End. This implies: the notification fires per kill.SetActiveObjective(3 Retail usages, c02p1) sets the active objective anew; log "Set active on missionIndex… objectiveIndex…". - Failure:
ActionMissionhas inputsObjectiveEndandMissionEnd. In code, both call 0xF6F210, set flag|=2, and sendnotice_FailedObjectiveornotice_FailedMission(X02).MissionNotificationhas outputsObjectiveFailedandMissionFailed(X06). Retail wires none of these: 0 of 2977 scripts (S01, M03). Missions fail in-game only through player death (Checkpoint). What a failed objective does on the HUD is unknown. - Dead end in Retail script:
SendCustomEvent[1660263701] "end_level"has no input;c2v1_combat_target_ai_spawn_7is wired nowhere (A01).
4. Mission End: Exit → Chronicle → Next Map
UseNotification[1195927002] on sacramento_gameplay_prop_use_animated_exit_level
(idProp2 prop/use_animated, CLIPMODEL_BOX 0,2 m, flags.hide=true until world marker shows it,
LOOT_TOUCH_USE_TIMED 1000 ms, textOverride #str_useable_fc_c2v3_escape = "Escape (Exit level)")
└─Used→ SendCustomEvent "end_obj_04" ⇢ GetCustomEvent[711716177]
└─→ ActionMission[1709067129].ObjectiveComplete + .MissionComplete (Flag |=1, notice_CompleteMission)
└─→ SendCustomEvent[618596321] "end_level" ⇢ GetCustomEvent[188420141]
└─→ Delay[16548334] 10 ms → ActionChronicle[1306067857].End (completed=true, returnToSas=false)
└─End→ ActionNextMap[1488751441].NextMap (mode MODE_AUTO, mapName "")
E03 checks this chain exactly. MODE_AUTO without map names presumably means "next part according to variation" (nextVariationId = 232). The chapter completion display belongs to ActionChronicle. Both are unseen at runtime. Counter-examples end differently: in c3v1, Level-Exit → Bink → ActionChronicle(returnToSas=true), and only a custom Engine event MapMovementNotification.ChronicleToSAS triggers ActionNextMap. In c02p1, UseNotification (Level-Exit) → ActionConsumeInventory → ActionNextMap.
5. Enemies: Trigger → Spawner → Group → Alarm
Spawner (idTarget_AISpawn) |
triggered by | Targets |
|---|---|---|
…spawn_firstarea |
Trigger …trigger_firstarea → ActionTrigger |
5 × soldier_light, Group FirstArea |
…spawn_secondarea_initial |
Trigger …secondarea_initial(1) → ActionRemoveAIFromVolume (Empty Lobby) → 300 ms → ActionTrigger |
Human + 7 × soldier_heavy, SecondArea |
…spawn_secondarea_labs |
Trigger …secondarea_labs (+ Cleanup) |
4, ThirdArea, maxActiveAIs 13 |
…spawn_commanderarea_initial |
Trigger …commanderarea_initial → 2 × Volume empty → 300 ms |
6 including Hans, Commander |
…commanderarea_secondfloor / …thirdfloor |
Script and directly via idTrigger.edit.targets (A04) |
6 or 1 |
…spawn_5, …spawn_6 |
alarmSpawner of both idAlarmSystem |
Spawn-Scenepoints → hidden AI (Reinforcement on Alarm) |
…spawn_7 |
nothing (A01) | 1 Elite |
- Spawned enemies stand ready but hidden (
flags.hide = true) in the map. The spawner names them inedit.targetsor namesidScenePoint_Spawn, which in turn name AI templates. idEncounterGroupMgr c2v1_combat_ai_groupmanager_1: Group commanders, SecondArea, FirstArea, ThirdArea. Commander and ThirdArea each have anidAlarmSystem(A03). The affiliation is stored in the enemy itself (groupNamein theaiEditableblock). An alarm system names itsidAlarmboxes (targets) and its reinforcement spawner (alarmSpawner).- "Room empty" is measured by Retail with
AIVolumeCountNotification(onlyAlive=true,min 0,max 0, 42 of 54 cases, P02). c2v1 uses it once, in the VO script (c2v1_snd_vo_volume_default_first_room). - Side effect in the played bundle (O03): The three lobby soldiers converted to Elite (
soldier_light_1/3/5) have lost theirgroupName = "FirstArea". The spawner has none, andretype()takes over the body entirely. For waves in custom missions, the group must be retained during conversion.
6. Doors and Locks
c2v1 has no script-controlled door. The 45 "doors" are idProp2 with prop/doors/locked (5) or prop/doors/unavaliable (40), each an invisible CLIPMODEL_BOX use point in front of a static door geometry. None of them is addressed by a script (A05). Locking and unlocking runs in Retail via ActionActivatable (Enable/Disable of Triggers and Use-Props), ActionShowHide/ActionRemove, and Gate nodes. c02p1 shows key logic with PickupNotification → show Use-Prop → UseNotification → Gate. idKisculeNodeDoorNotification exists in the Exe but is not used in any Retail script.
7. What Engine Code Says (module.bin, read only)
| Finding | Address | Check |
|---|---|---|
ActionMission registers exactly 7 inputs: MissionStart/Complete/End, ObjectiveStart/Complete/End, SetActiveObjective |
Port table 0x988FF5 | X01, N07 |
| MissionStart sets up the mission and adds objectives, without HUD | 0xF6F660, 0xF6E8F0 | X07 |
| ObjectiveStart → "Start objective %s on mission %s", "Show hud objective %s on mission %s" | 0xF6EA20 | X04 |
ObjectiveComplete / MissionComplete → Flag |=1, notice_CompleteObjective / notice_CompleteMission |
0xF6EDA0 | X03 |
ObjectiveEnd / MissionEnd → Flag |=2, notice_FailedObjective / notice_FailedMission |
0xF6F210 | X02 |
The log lines hang on g_debugMissions (intValue RVA 0x2FAF9C0) |
4 comparisons in 0xF6EA20 | X04 |
The DevMenu command kce <event> (Custom Event from console) is missing in the Retail Exe |
– | X05 |
The HUD widget (idMenuWidget_ObjectiveNotifier, texts #str_new_objective = "NEW OBJECTIVE", #str_objective_completed = "OBJECTIVE COMPLETED") is a guess based on RTTI and strings. The code path up to 0xF6EA20 is confirmed.
Test aid for the user: g_debugMissions 1 in the console, if you allow setting the value, should write "Start objective …", "Show hud objective …", and "Complete objective …" into qconsole.log. This is the direct runtime proof of a custom mission.
8. Counterexamples: other DLCs and Campaign
| Map | Variation / Layer | Mission | Objectives | End |
|---|---|---|---|---|
| c3v1 (dlc_3) | dlc/c3v1 / layer/dlc/c3v1 |
"Escape Anchorage Naval Base" | 6: Exit the Docks, Find Another Way Through, Search for Intel, Take the Elevator, Ascend Elevator Shaft, Steal a Truck | Use gp_c3v1_levelexit → MissionComplete, parallel → Bink film → ActionChronicle(returnToSas=true); then engine event MapMovementNotification.ChronicleToSAS → NextMap |
| c02p1 (Campaign) | c02/campaign_fergus / campaign, fergus, c02p1, code |
"Under siege" | 6 (of which obj_04_cutwall is registered but never started in the loaded scripts) |
Use …interact_use_level_exit → ActionConsumeInventory (Laser rifle) → NextMap |
The same extraction as for c2v1 confirms the pattern in both maps: a silent MissionStart lists all objectives, each ObjectiveStart displays a HUD message, pure ObjectiveComplete nodes display none, and every objective has a Decl and an English title (G-c3v1, G-c02p1, G-c2v1).
9. Building Blocks for Custom Missions (Documented in Retail)
Catalog of all 47 relevant node types with parameters and fixed connections:
anatomy.json → building_blocks, measured across 2977 scripts in 210 node types.
| User Request | Retail Prototype | Chain |
|---|---|---|
| "Eliminate the Officers" | P01 (27×) | AiKilledNotification(listenedEntities = Officers, maxNotificationCount = N) → Counter(endCount N) → Reached End → SendCustomEvent end_obj → ActionMission.ObjectiveComplete |
| "Find the Documents" | c02p1 PickupNotification, c07p2 "Find New Orleans Dossier" |
PickupNotification (or UseNotification on a Use-Prop) → ObjectiveComplete |
| "Survive three waves" | c2v1 waves, P02 | Wave k: ActionTrigger(targetEntities = Spawner k); End: AIVolumeCountNotification(onlyAlive, 0..0) or Kill-Counter → Delay → Wave k+1; Counter(endCount 3) → Objective Complete |
| "Reach the Exit" | c2v1 Exit | World marker on prop/use_animated (CLIPMODEL_BOX, no new cm needed) → UseNotification → MissionComplete → ActionChronicle → ActionNextMap |
| Objective Text | anatomy.json → reusable_objectives |
283 existing missionobjs with English titles, e.g., "Kill them all", "Kill all enemies", "Kill Everyone", "Search for Intel", "Find New Orleans Dossier", "Find exit", "Escape" |
| Failure | Only Code (X02) | ActionMission.ObjectiveEnd – never used in Retail, runtime unknown |
The shortest build path without new archive entries, derived and not built:
-
Script: The payload of the existing entry
extkisclule maps/game/dlc/c02/c2v1_gameplay/kiscules/sacramento_gameplay_kiscule_c2v1.declis replaced in the Override-Chunk. The Kiscule entity already indicates this, the name remains. This is the same payload swap as with the.entitiesentry inbuild_dlc4_override.py. Kiscule text and containers are bit-exactly writable for all 569 DLC scripts (R01), while the 2408 base scripts according todocs/kiscule.md. -
Objectives/Text: Existing
mission-/missionobj-Decls reference them. They reside in the always-mounted base archives. That an objective with a foreignparentMissionis displayed under another mission is documented by the screenshot (E13). -
Triggers: The 48 existing c2v1 triggers are used. Their forms are local boxes (T02). A new
idTriggerentity with theclipModelNameof an existing trigger at a different position is structurally plausible, but without a Retail prototype: all 1773 Retail triggers have their own form, 0 shared, 0 ×CLIPMODEL_BOX(T01). Alternatively, a new cm viatools/probe_cmfmt.py --box, also untested at runtime. -
Enemies: Existing spawners and hidden AIs are retyped (retype) while retaining
groupName(O03), or new AI entities are added.
New objective texts require new #str_ entries in strings/english.lang. This is an encrypted cfile,
where the name is part of the key, and there is no German .lang. Additionally, new missionobj
entries are required in the container. Both constitute independent work (Track missions/text, Add-Entry path).
10. Open, Unconfirmed at Runtime
- Everything except the start chain of obj_01 (Screenshot) is only structurally supported. This applies to the end sequence, obj_02–04, mission end, waves, and alarms.
- What an
ObjectiveEnd(Failure) on the HUD indicates and whether something follows afterwards. - Whether a Trigger-Form can hang between two Entities (Form reuse).
- Which player start takes effect. The variation requires
c2v1_gameplay_player_start_1, which does not exist (E14). In the played bundle, the two starts are swapped. If the player starts at the top of the office, the obj_01-Trigger (Lobby Ventilation) is behind them, and "Enter the offices" remains open until they pass through there. MODE_AUTO/nextVariationIdas the target ofActionNextMap, and how the Chronicle display looks at the end.- The semantics of Custom Events with
global = falsecannot be seen from the data. The model assumes "only in the same script". In c2v1, all events are global.
11. Tests (55, including 7 negative controls, all green)
| ID | Statement |
|---|---|
| M01 | c2v1: 31 active scripts from 33 Kiscule-Entities, 0 dead edges |
| E01–E03 | Start-, End-, and End chain exact (Node IDs and Types) |
| E04–E06 | HUD flags, silent MissionStart with 4 objectives, schedule 500/4100 ms |
| E07–E08 | Trigger-Entity, Layer, cm-Form (local box, bit 0x4000) |
| E09–E14 | Variation, Layer, Kiscule-Entity, Challenge inactive, title "Enter the offices", parentMission copy error, missing spawnSpot |
| M02–M03 | 4 objectives complete, no failure wired |
| A01–A05 | 9 Spawners (6 script, 2 alarm, 1 dead), first wave exact, 4 groups/2 alarms, 2 triggers with targets, doors |
| X01–X07 | Engine: Port table, Failure-, End-, Start-, and MissionStart-path, g_debugMissions, kce missing, MissionNotification outputs |
| G-* | Patterns and map ends in c2v1, c3v1, c02p1 |
| O01–O03 | played bundle: scripts byte-identical, referenced entities present, 3 lost groups |
| P01–P02 | Retail pattern Kill-Counter (27/27) and "Room empty" (42/54) |
| S01, R01, R02 | 2977 scripts, End-Ports 0×; 569/569 DLC-scripts and 624/624 Decls bit-exact |
| D01–D03 | missionobj fields, no counter placeholders, 283 reusable objectives |
| T01–T02 | 1773 Retail Triggers without BOX/shared form; c2v1 forms local |
| N01–N07 | Negative controls: Event renamed, String missing, Trigger renamed, Edge detached, Decl indentation, Kiscule byte, Port table shifted – all rejected |