Wolfenstein Modloader Buy me a coffeeKaffee spendieren

Wiki / Custom maps and missionsEigene Karten und Missionen

Writing Kiscule – Scripts for Custom Missions

Partly solvedTeilweise gelöst Original page in the GitHub wikiOriginalseite im GitHub-Wiki

In plain wordsIn einfachen Worten

Wolfenstein II: The New Colossus runs its mission logic with Kiscule scripts: node graphs such as "trigger entered, spawn a wave, count kills, complete objective". This page shows that all 3,162 script files of the game can be read and written back byte for byte. It builds a model of the node types, a validator and a first new script from scratch. The new script is structurally valid, but it has not yet been run in the game.

  • All 3,162 script files in the install round-trip byte-exactly.
  • The model covers 83,812 nodes in 209 types; one simple edge rule holds for all 103,687 connections.
  • The validator accepts 2,927 of 2,977 retail scripts (the 50 rejects are known game defects) and rejects all 12 deliberately broken test scripts.
  • A new 11-node script is built: enter the area, a wave of 5 enemies spawns, and 1 second after the fifth kill the objective completes.
  • Two test archives for the Gestapo office map are ready. Runtime is unconfirmed: nothing has run in the game yet.

Wolfenstein II: The New Colossus steuert seine Missionslogik mit Kiscule-Skripten: Knotengraphen wie „Trigger betreten, Welle spawnen, Kills zählen, Ziel erfüllen“. Diese Seite zeigt, dass sich alle 3.162 Skriptdateien des Spiels byte-genau lesen und zurückschreiben lassen. Sie baut ein Modell der Knotentypen, einen Validator und ein erstes neues Skript von Grund auf. Das neue Skript ist strukturell gültig, lief aber noch nicht im Spiel.

  • Alle 3.162 Skriptdateien der Installation lassen sich byte-genau einlesen und zurückschreiben.
  • Das Modell umfasst 83.812 Knoten in 209 Typen; eine einfache Verbindungsregel gilt für alle 103.687 Verbindungen.
  • Der Validator akzeptiert 2.927 von 2.977 Original-Skripten (die 50 abgelehnten sind bekannte Fehler im Spiel) und lehnt alle 12 absichtlich kaputten Testskripte ab.
  • Ein neues Skript mit 11 Knoten entsteht: Bereich betreten, eine Welle von 5 Gegnern spawnt, und 1 Sekunde nach dem fünften Kill ist das Ziel erfüllt.
  • Zwei Testarchive für die Gestapo-Büro-Karte liegen bereit. Im Spiel ist nichts bestätigt: Es lief noch nichts.

Technical detailsTechnische Details

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

Track kiscule, 2026-09-25. Tool: tools/probe_kiscule_write.py, output in re_probes/missions/kiscule/. The game was not started. Only read from <Steam library> and Saved Games; nothing was written.

python tools/probe_kiscule_write.py all      # 86 checks, 0 failures, exit 0  (lauf_all.log)

Summary

Question Answer Evidence (Check in lauf_all.log)
Read and write bit-exactly? Yes. All 3162 extkisclule payloads of the installation run text-bit-exact and container-bit-exact: 3061 Retail, plus 101 in the Mod folder dlc_0. The path is compfile via tnccrypt, then kiscule.Doc. Round-Trip-Block, plus verification with a flipped byte
The "3129" from the order? 3129 = 3061 Retail + 68. The 68 are the dlc_2 copy, which was installed as dlc_4 during the tnccrypt measurement. Today, dlc_0 holds the Gestapo-Office file: 68 Retail-identical scripts and 33 new (c2v1_custom_*). own check, calculates the number
Model? 83,812 nodes in 209 types from 2,977 unique Retail scripts. The edge rule "out → in, impulse → impulse, value → value" holds for all 103,687 edges. Model-Block; signatures.json
Validator? Retail positive control: 2,927 of 2,977 scripts are clean. The 50 rejected lie exclusively in four known Retail defect classes. All 12 negative controls are rejected, including the hanging edge and the unknown node type. Validator-Block and Negative-Block
New script from scratch? Yes. It has 11 nodes and 9 edges: Trigger → Wave → all dead → goal met → next goal. Every edge is a pattern found in Retail. Build-Block
How does a script get executed? An entity of class idKisculeEntity has externalKisculeDecl = "<Decl-Name>". This requires an extkisclule entry of the same name and a line in .mapresources. This holds without exception for all 3,020 Retail bindings. Binding-Block
In-game? Structurally valid, runtime unconfirmed. Two test chunks are ready (Section 6). –

1. Format and Round-Trip

An extkisclule entry consists of two layers:

  1. compfile (wolfsdk/tnccrypt.compfile_unwrap/wrap). A 16-byte header i64 usize, u64 csize, followed by Kraken on LEVEL_NORMAL. Retail writes a file raw (usize < 0) if Kraken does not make it smaller. This affects the 17 empty scripts. The tool applies the same rule (wrap()) and matches all 3,162 containers byte-exactly.
  2. Decl plaintext. It begins with a line break and ends on NUL. Each line is either key = {, }, or key = value;, indented with a tab per depth. Line endings are LF or CRLF per file. Parser and emitter come from tools/kiscule.py (only imported).

The check compares both levels, text and container. A verification flips a byte and requires the comparison to fail.

2. The Model: Nodes, Connections, Edges, Variables

2.1 Structure

edit = { kiscule = {
    version = 6;                 Retail: 6 (2872), 5 (83), -1 (5), missing (17 empty)
    nodes     = { num; item[i] = Node }
    comments  = { num; ... }     Editor notes, no runtime effect
    variables = { num; ... }     named graph variables
} }

A node, here the Delay node from our script (abbreviated):

item[8] = { name = "idKisculeNodeDelay"; <- Type = RTTI class of the EXE id = 152245281; <- unique in the file nodePos = { x = 1500; y = 300; } <- editor layout only parameters = { class = "idKisculeNodeDelayParameters"; object = { delay = 1000; resetOnStart = false; } } connectionPoints = { num = 17; item[1] = { name = "Start"; id = 1863508535; type = "InputRegular"; properties = 8; visible = true; connections = { num = 1; item[0] = { name = ""; id = 1103097362; endNodeID = 67139160; <- Counter endConnectionPointID = 536032890; } <- its "Reached End" } } ...


### 2.2 Rules, measured via Retail

* **Connection types.** There are six. For the impulse (flow) they are `InputRegular`, `InputEvent` (one), `OutputRegular`, and `OutputNotification` (out). For values they are `InputVariable` (in) and `OutputVariable` (out). Measured edge types: Value→Value 41 180, Regular→Regular 33 156, Notification→Regular 29 339, plus 12 to `InputEvent`. Other combinations do **not** occur. This is the validator's edge rule.
* **An edge exists at both ends.** The output carries the `endNodeID/endConnectionPointID` of the input and vice versa. The key is the pair (node-ID, connection-ID). The edge `id` is unsuitable for this; it is not identical at both ends (see `docs/kiscule.md` §3.3).
* **Parameters.** Each parameter appears once in the node (`parameters.object`) and can additionally arrive from outside as an `InputVariable` of the same name. An example is `listenedEntities`, fed by an `EntityReference`. Thus, entities hang off event nodes.
* **Actions pass the impulse further.** For every impulse input of an action (`Activate`, `ObjectiveComplete`, `Send` …) there is an `OutputRegular` of the same name. It fires after the action; chains are built over it. Retail uses this for example with `ActionMission.ObjectiveComplete → SendCustomEvent` (49 times).
* **Variables.** The `variables` block contains `idKisculeGraphVariable{Bool 618, Int 279, EntityReference 69, Time 56, Float 35, ListEntityReference 27, EntityDef 2}`. Each variable has the fields `name`, `global`, `isCrossMap`, `defaultValue`, and `value`. It is read and written via `NamedVariable*` nodes with `variableName`. Of these, 2819 refer to a variable of the same file and 22 do not, thus cross-script.
* **Events between scripts.** `SendCustomEvent{eventName, global=true}` → `GetCustomEvent{eventName}`. The mission logic of c2v1 is wired exactly like this (§4.2).
* **Node type = class.** Each of the 209 Retail types is an RTTI class of the EXE. All 388 node and parameter class names appear verbatim in the runtime image `runtime_87400/module.bin`. Further 101 `idKisculeNode*` classes exist in the EXE but are not used in Retail. Whether the engine loads them is untested.

### 2.3 Node types for missions

The table was generated from `signatures.json`, measured across all Retail occurrences. "Impulse out" refers only to the true event outputs, excluding the pass-through outputs of the same name. "Value in" are the parameters; each can also be connected as an `InputVariable`.

| Node | Role | Nodes/Scripts | Input Impulse | Output Impulse | Input Values (Parameters) |
|---|---|---:|---|---|---|
| `BeginGame` | Map/Script starts | 686/568 | - | OnBeginGame, OnBeginGameAlways | ignoreInitialFlag |
| `TriggerNotification` | Entity enters/exits a Trigger Volume | 1617/533 | Reset | Empty, Out, Touching, Untouched | classFilter, enabled, listenedEntities, maxNotificationCount, waitingTime / Value from: activator |
| `VolumeNotification` | Volume entry/exit | 934/478 | Reset | Empty, Enter, Leave, Out, Touching, Untouched | classFilter, enabled, listenedEntities, maxNotificationCount, waitingTime / Value from: activator |
| `UseNotification` | Player uses an object | 486/398 | Reset | CancelUsed, Out, Used | classFilter, enabled, listenedEntities, maxNotificationCount, waitingTime / Value from: activator |
| `PickupNotification` | Item picked up ("Found Documents") | 77/71 | Reset | Out, Pickup | enabled, listenedEntities, maxNotificationCount, waitingTime / Value from: activator |
| `AiKilledNotification` | Listed AI killed | 204/123 | Reset | DeathDone, Killed, Out | enabled, listenedEntities, maxNotificationCount, waitingTime / Value from: activator, attacker, victim |
| `AINotification` | AI State | 136/83 | Reset | Entered Combat, Lost Vision Of Enemy, Noticed Something, Out, Spotted Enemy, Spotted Player | enabled, listenedEntities, maxNotificationCount, waitingTime |
| `AIVolumeCountNotification` | Number of AIs in a volume | 54/41 | Reset | Count Reached, Out | enabled, listenedEntities, maxCount, maxNotificationCount, minCount, onlyAlive, onlyDead, types, waitingTime |
| `DamageNotification` | Entity takes damage | 622/456 | Reset | Damaged, Out | enabled, filterDamageSource, filterDamageType, listenedEntities, maxNotificationCount, waitingTime |
| `GetCustomEvent` | Receive named event | 8848/1672 | - | Received | enabled, eventName, global, maxNotificationCount |
| `SendCustomEvent` | Send named event | 3159/899 | Send | - | eventName, global |
| `Counter` | Counts to endCount → "Reached End" | 169/97 | Decrement, Increment, Reset | Out, Reached End | autoReset, currentCount, endCount, startCount |
| `Gate` | Open/Closed | 3399/965 | Close, Open, Test, Toggle | Gate is Closed, Gate is Open, Out | autoCloseCount, isOpen |
| `Delay` | Delay in ms | 4760/1360 | Pause, Reset, Resume, Start, Stop | Finished, Out | delay, resetOnStart |
| `Relay` | Relay impulse | 328/162 | Out | - | - |
| `ActionTrigger` | Entities trigger (idTarget_AISpawn → Wave) | 663/371 | Activate | - | activator, targetEntities |
| `ActionAISpawn` | Spawn AI directly (Retail only 2×) | 2/2 | Activate, Deactivate, Spawn | Empty, Ended, Spawned, Started | AIs, enabled, interval, maxActiveAIs, maxNumOfSpawn, randomSpawn, spawnScenepoints |
| `ActionShowHide` | Show/hide entities | 2195/1332 | Hide, Show, Toggle (+ Combinations) | Out | detachAnyAttachedObjectOnHide, hideEntities, showEntities, toggleEntities |
| `ActionMission` | Start, fulfill, end mission/objective | 576/79 | MissionStart, MissionComplete, MissionEnd, ObjectiveStart, ObjectiveComplete, ObjectiveEnd, SetActiveObjective | Out | mission, objectives, player, showNotification, completeHides, keepMissionInJournal |
| `ActionWorldMarker` | Show/hide objective marker | 449/115 | Hide, Show | - | entity, showHideEntity |
| `ActionAutosave` | Autosave | 265/67 | Autosave | Out | persistToStorage, saveSlot, triggered |
| `ActionNextMap` | Next map / Mission end | 99/88 | NextMap | Out | mapName, spawnSpot, mode, variation, layers, addTimelineLayer |
| `ActionChronicle` | Complete DLC chapter | 10/10 | End | - | completed, player, returnToSas |
| `ActionActivatable` | Activate/deactivate Scenepoints/Triggers/Volumes | 481/272 | Disable, Enable, Toggle | - | entities |
| `ActionSoundPostEvent` | Play Wwise event | 1839/467 | Post | Out | soundEvent, entity, entities, distanceCull |
| `ActionSoundVoiceOver2` | Voiceover | 3992/302 | Play, Stop | Approach fail, Approach play, Finished, Interrupted | voiceOver, soundEvent, fromWho, playFrom, subtitleOverride, … |
| `ActionAIManipulate` | Set AI behavior | 407/145 | In | Out | ais, kill, perception, detectPlayer, searchPlayer, … |
| `ActionRemove` | Remove entities | 516/332 | Remove | - | entities |
| `EntityReference` | A map entity name | 17160/2745 | Set | - | value → Value from: value |
| `ListEntityReference` | List of entity names | 1356/586 | Set | - | value → Value from: value |
| `PlayerEnt` | The player | 1887/909 | - | - | Value from: Player |
| `ActiveAIList` | All active AIs | 77/45 | - | - | Value from: AIs |

**Assignment of Mission Components from the Order:**

* *Trigger entered* → `TriggerNotification.Touching` with `classFilter = "idPlayer"`. The Trigger Entity is provided via `EntityReference` → `listenedEntities`. For volume, there is `VolumeNotification.Enter`.
* *AI killed* → `AiKilledNotification.Killed`. It fires **per kill**, `victim` provides the corpse. Evidence: Retail counts this per `Counter` up to 22 (`c1v2_challenge`, 22 AI, `endCount` 22).
* *Counter reached* → `Counter.Reached End`.
* *Spawn* → `ActionTrigger.Activate` on an `idTarget_AISpawn` Entity. Retail does this 364 times. The wave consists of the Entities in whose `targets` list they appear. Alternatively, there is `ActionShowHide.Show` on AI (725 times).
* *Objective update/complete/mission end* → `ActionMission` with `ObjectiveStart`, `ObjectiveComplete`, `SetActiveObjective`, `MissionComplete` or `MissionEnd`. For the chapter end in the DLC, `ActionChronicle.End` is added, for map switching `ActionNextMap`.
* *Door open* → **In Retail there is no door node.** `idKisculeNodeDoorNotification` exists only as an RTTI class and is used nowhere. Doors are `idDynamicEntity`/`idProp2`, mostly with `idConstraint_Hinge`. Where Retail moves a door via script, it uses `ParametricTranslation.entity` (118 references to Entities with "door" in the name), `ActionRotateEntity.targets` (50), `KeyframedTranslation` (31), `ActionShowHide` (hide 95, show 92) or a Midnight sequence (`OpMidnightParametersControl.Door`, 18).
* *Sound play* → `ActionSoundPostEvent.Post` with `soundEvent` (Wwise) to `entity`, for speech `ActionSoundVoiceOver2.Play`.

## 3. The Validator

`validate(text, db, entity_names)` in `tools/probe_kiscule_write.py` returns an error list. Empty means valid. It checks the following:

* **Structure.** The grammar, every `num`/`item[]` count, and the NUL at the end. The emitter must deliver the same bytes (canonical). The block sequence is `version/nodes/comments/variables`, where version is a Retail value.
* **Nodes.** At least one (otherwise the runtime message "Kiscule entity '%s' is empty" applies). Node IDs are unique. The type must be known to Retail, the parameter class appropriate, every connection (Name, Type) for this type known, connection IDs unique.
* **Edges.** The target exists (Node and Connection). The edge is recorded at both ends. The Edge Rule Direction/Type applies.
* **Map.** With `entity_names`, every value of an `EntityReference`/`ListEntityReference` must be present as `entityDef` in the target map.

**Positive control.** Across all 2977 Retail scripts, 2927 are clean. The 50 rejections distribute as follows: 25 × edge to empty, 17 × empty, 7 × one-sided, 1 × duplicate connection ID. These are exactly the Retail defects from `docs/kiscule.md` §3.3. The check fails as soon as another class appears or less than 95% are clean.

**Negative controls.** Every mutation of our script must be rejected:

| Mutation | Message |
|---|---|
| **dangling edge, endNodeID is empty** | `Edge 28456371 points to empty: Node 424242 missing` |
| **dangling edge, endConnectionPointID is empty** | `… Connection 424242 to Node 651609970 missing` |
| **unknown node type** (`idKisculeNodeCounterX`) | `unknown node type idKisculeNodeCounterX [67139160]` |
| Back of an edge deleted | `Edge … only one-sided` |
| unknown connection (`ReachedEnd`) | `unknown connection ReachedEnd:OutputRegular` |
| Input to output reversed | `Edge … connects OutputNotification with OutputRegular` |
| `nodes.num` too high by 1 | `Structure: num = 12, but item[11] missing` |
| Entity that does not exist in the map | `Entity 'c2v1_gibtsnicht' does not exist in the map` |
| duplicate node ID | `duplicate node ID 509157573` |
| empty graph | `no nodes …` |
| NUL missing at end | `Structure: no NUL at end` |
| Entity template without `externalKisculeDecl` | `Template does not match (1/0 replacements)` |

## 4. The Minimal Script

### 4.1 Graph (`mission_wave_c2v1.graph.txt`)

EntityReference "c2v1_combat_triggers_trigger_firstarea" ──value──▶ TriggerNotification.listenedEntities TriggerNotification (classFilter idPlayer, max 1) ──Touching──▶ ActionTrigger.Activate EntityReference "c2v1_combat_target_ai_spawn_firstarea" ──value──▶ ActionTrigger.targetEntities ListEntityReference [5 KI of the spawn list] ──value──▶ AiKilledNotification.listenedEntities AiKilledNotification (max 5) ──Killed──▶ Counter.Increment (0 → 5) Counter ──Reached End──▶ Delay.Start (1000 ms) ──Finished──▶ ActionMission.ObjectiveComplete PlayerEnt ──Player──▶ ActionMission.player ActionMission (mission_c2v1_main, obj_01_find, showNotification) ──ObjectiveComplete──▶ SendCustomEvent "start_obj_02" (global)


The script has 11 nodes, 9 edges, 30,591 bytes of text, and 3,361 bytes of payload (`mission_wave_c2v1.extkisclule`, sha256 `4eae4d81…d532`). The build is reproducible because the IDs are deterministically derived from a seed.

**How it is created.** Every node is a copy of a real Retail node of the same type, with the most common connection count, full connection list, and `properties`/`visible` as in the game. New are IDs, parameters, and edges. The class `Script` (`add`, `link`, `render`) is the write API: `link()` records every edge at both ends.

### 4.2 Why exactly so – Retail evidence, each as a test

* **Each of the 9 edges is a Retail edge pattern**, counted as (Type.Connection → Type.Connection):
  EntityReference→TriggerNotification 1562, Touching→ActionTrigger.Activate 176,
  EntityReference→ActionTrigger.targetEntities 660, ListEntityReference→AiKilled 62,
  Killed→Counter.Increment 43, Counter.Reached End→Delay.Start 16,
  Delay.Finished→ActionMission.ObjectiveComplete 11, PlayerEnt→ActionMission.player 220,
  ObjectiveComplete→SendCustomEvent 49. *The direct edge Counter→ObjectiveComplete does not exist in Retail (0). The check therefore failed in the first version, and the Delay node was included.*
* **The Retail script `c2v1_combat_kiscule_firstarea` remains unchanged within it.** It consists of Trigger → Spawn target, with identical types, identical parameters, and 3 identical edges. Retail does exactly this at this point. We only attach the kill branch.
* **The counting pattern originates from Retail.** `maxNotificationCount` = number of listed KIs = `Counter.endCount` appears in 10 Retail cases (`gp073 hangarfight` 2, `c1v1_combat` 4, `c1v2_challenge` 22, …).
* **Spawn and death counting in the same script.** 13 Retail scripts fire an `idTarget_AISpawn` and listen for its KI via `AiKilledNotification` (`cb_kiscule_horton` 8 KIs, `combat_streets` 10, `c3v2_combat_enter_trainstation` 14, `c2v3_combat_kiscule` from the same DLC …). This is our pattern.
* **The wave.** `c2v1_combat_target_ai_spawn_firstarea` is an `idTarget_AISpawn` with 5 KIs in `targets`. In the installed override, 3 of them are switched to `elite_heavy`, but remain `idAI2` with the same names. The kill notification goes via the name, so this fits.

### 4.3 The Retail mission chain in c2v1 – and why the test would otherwise show nothing

`sacramento_gameplay_kiscule_c2v1.decl` runs the mission. The following chain is measured and recorded as a check:

BeginGame → Delay 500 → ActionMission.MissionStart (4 targets) → Delay 4100 → Send "start_obj_01" → Get → ActionMission.ObjectiveStart(obj_01_find) Trigger "sacramento_gameplay_triggers_obj_1_complete".Touching → Send "end_obj_01" → Get → ActionMission.ObjectiveComplete(obj_01_find) → Send "start_obj_02" → …


This Retail trigger is located at (−4.75, −193.75), **approximately 11 units** next to our wave trigger at (−14.45, −199.30). The enemies stand behind it (y −172 … −193). The player would thus fulfill "Enter the offices" already upon entering before the wave is dead, and our script would remain invisible.

**Therefore both candidates contain a second, minimal intervention.** In the mission script, exactly one `TriggerNotification` node (ID 1728583878) is set from `enabled = true` to `enabled = false`. Only this single line changes, which has been verified. Our script takes over the continuation that Retail makes at that same point: after `ObjectiveComplete`, it sends `start_obj_02`. It has also been verified across all scripts binding the c2v1 map:

* `end_obj_01` is sent by only this single node and receives only from the Retail fulfiller. No other script loses anything.
* `start_obj_02` listens only to the mission script.
* After the intervention, the Retail path is dead, and **our script is the only active fulfiller of obj_01**. This is read in the built chunk.
* A second disabling is rejected as a negative control.

(Initially, the check also counted scripts from other maps, `c2v2` and `c2v1_custom`, and failed. Now it counts only scripts binding the map via `externalKisculeDecl`.)

## 5. Name binding: how a script comes to execution

Across all 39 Retail `.entities`:

* All **3020** `idKisculeEntity` entities reference a script via `edit.externalKisculeDecl`. Inline Kiscule does not occur, nor do `targets`.
* Each reference resolves to exactly one extkisclule entry: 3000 exact matches, 20 case-insensitive. The entry name is the Decl name.
* Per map (36/36 with readable `.mapresources`), the extkisclule lines in `.mapresources` are precisely the lowercase references. A new script therefore requires **one new MRL line**.
* The 24-byte field of each MRL line is the layer mask of the binding entity (2761/2761). Bit i corresponds to Layer i of the MRL layer list. Without a layer, only bit 191 is set (`…80`), causing the script to always load.

A new script binds using this entity (`entity_binding_example.txt`). It is the Retail entity `c2v1_combat_kiscule_firstarea`, with only Name and Decl changed:

entity { entityDef mission_kiscule_wave_1 { inherit = "kiscule"; class = "idKisculeEntity"; expandInheritance = false; poolType = "POOLTYPE_NONE"; poolGranularity = 2; edit = { soundParms = { enableQuickSave = true; noOcclusion = true; } spawnPosition = { x = -1.1000000238; y = -195.200088501; } externalKisculeDecl = "maps/game/dlc/c02/c2v1_mission/kiscules/mission_kiscule_wave_1.decl"; } } }

```

This includes an extkisclule entry with exactly this name (lowercase) and the MRL line (extkisclule, <name>, LayerMask) in generated/buildgame/game/dlc/c02/c2v1.mapresources.

6. The Test Chunks (for Master, not installed)

Both are built from the installed, playable file: re_probes/dlc4_override/chunk_22.resources (sha256 cadfbfce…b2f5, identical to dlc/dlc_0/base/chunk_22.resources). Both pass probe_idclhash and probe_datahash. Before finalize, stale_data_hashes reports exactly the changed entries; for A, probe_idclhash additionally rejects. This is the cross-check.

Candidate A (recommended first) Candidate B
File re_probes/missions/kiscule/chunk_22.resources re_probes/missions/kiscule/neu/chunk_22.resources
sha256 847bdc99d88b93d632ec37917fd3b2cdd9a67113a5d903589f58be5d6508c82f 1e2678737af38a03e12d4f95180e9272ae072544ed768a7fdd3dcf0b06f1d102
Size 151 706 115 bytes, 7128 entries 151 705 441 bytes, 7129 entries
Path our script under the Retail name c2v1_combat_kiscule_firstarea.decl new name c2v1_mission/kiscules/mission_kiscule_wave_1.decl, new entity mission_kiscule_wave_1 at the position of the firstarea entity, new MRL line
Changed Entries 927 (firstarea) and 934 (Mission script), only size/hash (+0x40/+0x48/+0x50) .entities, .mapresources, Mission script; new entry at the end (with write_archive, like the 143 alias entries of the installed container); all other 7125 payloads byte-identical; all 1462 images +0x60 unchanged
Tested whether the content of a script is interchangeable additionally, whether a new entity plus new Decl name executes, thus the path for custom missions

Installation (Master): the chosen file as dlc/dlc_0/base/chunk_22.resources, nothing else. The remaining dlc_0 files remain unchanged.

What should be visible in-game (normal mission start c2v1):

  1. After approximately 4.6 s, "Enter the offices" appears as usual.
  2. Enter the first area. The wave appears as in Retail; there are five firstarea enemies.
  3. Upon entering the offices, the objective is no longer fulfilled.
  4. Approximately 1 s after the fifth kill of the wave, "Enter the offices" is fulfilled, and the next objective (obj_02) begins.

Evaluation: Point 4 confirms Script Binding, AiKilled/Counter, and ActionMission. If the objective is never fulfilled, our script did not run, or the branch under AiKilled does not contribute, and the mission remains stuck at Objective 1. If the wave does not appear, the trigger part did not run already. If the objective is fulfilled upon entering the offices, the intervention into the mission script was not loaded. Test B only if A works. Then B separates "Exchange Content" from "Bind New Script".

7. What Is Not Proven

  • Runtime. None of this ran in-game. All statements are structural or derived from Retail patterns.
  • ObjectiveComplete on an objective that has not yet started. The objective starts 4.6s after map start; killing five enemies in less than 3.6s is hardly possible. What the engine does in that case is unmeasured.
  • version = 5/6/-1, the properties bits 1/4/32, and the edge id. They are copied from templates; their meaning is open (see docs/kiscule.md §9).
  • The 101 unused node classes of the EXE (including DoorNotification, LogicGateAnd, HudTimer). Whether the engine loads them is untested.
  • Custom Objective Text/Objectives ("Eliminate the officers") require new missionobj Decls and Strings. This is not part of this track; see re_probes/missions/text/. Our script uses the existing objective cobalt/c2v1/mission_c2v1_main_obj_01_find.
  • 3 Retail maps have a .mapresources in a different layout. The binding rule was not recalculated there.

8. Files

File Content
tools/probe_kiscule_write.py Round-Trip, Model, Validator, Binding, Script Build, both candidates; all = everything
lauf_all.log, manifest_all.json Last full run (86 OK, 0 FAIL, exit 0) with all numbers and hashes
signatures.json Signature DB: per node type connections, parameters, classes, RTTI bases
mission_wave_c2v1.decl / .extkisclule / .graph.txt The new script: Text, Archive Payload, readable graph
entity_binding_example.txt The binding entity for Path B
chunk_22.resources, neu/chunk_22.resources Candidate A and B