Wolfenstein Modloader Buy me a coffeeKaffee spendieren

Wiki / Latest research roundsNeueste Forschungsrunden

AI Spawn Waves: Trigger → Spawner → Wave

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

In plain wordsIn einfachen Worten

This report looks at how Wolfenstein II: The New Colossus makes enemies appear when the player walks into a zone, and how multi-wave fights are built. Almost all triggers go through mission scripts, but a few rare ones link the zone straight to the spawner with no script at all. That simple route is the recommended recipe for the project's custom kit map. Everything is read from game files; nothing was built and tested in the running game.

  • 99.4% of 3,884 triggers use script nodes; only 2 retail triggers link to a spawner with no script at all.
  • maxActiveAIs limits how many enemies one spawner keeps alive at once; it is set on 51% of spawners.
  • The game builds multiple waves with one separate spawner per wave, never by re-firing the same one.
  • Recipe A for the kit map needs one trigger box, one spawner and hidden enemies, and no new archive names.
  • Whether a brand-new script entry would load (needed for Recipe B) is still open.

Dieser Bericht untersucht, wie Wolfenstein II: The New Colossus Gegner erscheinen lässt, wenn der Spieler eine Zone betritt, und wie Kämpfe mit mehreren Wellen gebaut sind. Fast alle Auslöser laufen über Missionsskripte, aber ein paar seltene verbinden die Zone direkt mit dem Spawner, ganz ohne Skript. Dieser einfache Weg ist das empfohlene Rezept für die eigene Kit-Karte des Projekts. Alles wurde aus Spieldateien gelesen; nichts wurde gebaut und im laufenden Spiel getestet.

  • 99,4 % von 3.884 Auslösern nutzen Skriptknoten; nur 2 Auslöser im Spiel verbinden ganz ohne Skript mit einem Spawner.
  • maxActiveAIs begrenzt, wie viele Gegner ein Spawner gleichzeitig am Leben hält; es ist bei 51 % der Spawner gesetzt.
  • Das Spiel baut mehrere Wellen mit einem eigenen Spawner pro Welle, nie durch erneutes Auslösen desselben.
  • Rezept A für die Kit-Karte braucht eine Auslösebox, einen Spawner und versteckte Gegner, aber keine neuen Archivnamen.
  • Ob ein ganz neuer Skripteintrag geladen würde (nötig für Rezept B), ist noch offen.

Technical detailsTechnische Details

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

Research pass asking how a trigger volume reaches an AI spawner, what maxActiveAIs/respawn/reinforcement fields actually gate, how multi-wave encounters are built, and what the minimal AI-wave setup for the custom kit map would be. Everything below is read directly from the retail text corpus (a 29-map/84-archive census) or derived from already-proven writers — the game was never launched; nothing here has been built and tested live. Two real wiring routes exist in retail, one almost exclusively used (kiscule-mediated) and one rare but genuinely present without any kiscule involvement at all; the minimal, lowest-risk kit-map recipe uses only the rare, kiscule-free route, because the kit map currently has no kiscule entity left to attach a script-based version to.

The two wiring routes, corpus-wide

Route Share Mechanism
Kiscule-mediated 99.4% (3,862/3,884 triggers) The idTrigger entity carries no targets field at all; a kiscule TriggerNotification node fires ActionTrigger.Activate, which holds an EntityReference to the spawner.
Direct entity-layer 0.6% (20/3,884 raw instances, 9 distinct trigger entities) The idTrigger's own edit.targets list names the idTarget_AISpawn directly — no kiscule involved in the chain at all, or dual-wired (kiscule also independently references the same trigger, belt-and-suspenders).

Of the 9 distinct direct-entity-layer triggers, 7 are dual-wired and only 2 are genuinely kiscule-free, confirmed two independent ways (a typed kiscule-graph reference scan, and a raw full-text substring search across every script either host map loads — zero hits in both):

c02p0_combat_triggers_trigger_12              -> c02p0_combat_target_ai_spawn_10   (1-AI wave)
c1v3_venus_combat_trigger_before_boss_spawn    -> c1v3_venus_combat_target_ai_spawn_1 (4-AI wave, no maxActiveAIs)

These two are the retail precedent a kiscule-free kit-map trigger should be modeled on.

What the gating fields do

  • maxActiveAIs (on idTarget_AISpawn, set on 504/983 = 51% of all spawners corpus-wide): throttles how many of that spawner's own targets may be alive simultaneously. -1 = unlimited (83 uses); small integers 2–15 are typical (mode 12). It does not gate a shared pool across spawners — idEncounterGroupMgr (independently re-verified by reading a real retail block) carries no member list, only a name/alarm-system pointer, so cross-spawner limits are not visible in the data at all.
  • idScenePoint_Spawn.reuseDelay (2,764 scene points corpus-wide, mean 4.49 s, range 0–20 s): lets the same world position hand out another AI from its template list after a cooldown, gated by the owning spawner's maxActiveAIs. This is the respawn-point mechanic.
  • idAlarmSystem.alarmSpawner (present on 195/196 = 99.5% of all alarm systems): the reinforcement mechanic. Retail always keeps a separate, otherwise-idle spawner dedicated to reinforcements — it never reuses a combat spawner that triggers already feed.
  • Observed pattern, not proven mechanically: retail's proven multi-wave pattern is one dedicated spawner per wave, never re-triggering the same spawner twice. A kiscule AiKilledNotification → Counter → Delay → ActionTrigger chain fires the next wave's separate spawner. This exact pattern is not just inferred — it has already been built and structurally verified (37/37 checks) as a working two-wave container elsewhere in this project.
  • Whether a single spawner honors maxActiveAIs as a live gate on repeated firing (true re-trigger/respawn of the same spawner) versus being one-shot per name was not settled by disassembly — the field is read through the engine's generic reflection-table binding, not a single named code site, so a targeted disassembly attempt hit a wall. The practical answer is the data pattern itself: retail never does this, so a mod shouldn't rely on unverified re-trigger semantics either.

Minimal recipe for the kit map

The kit map today has zero triggers, spawners, alarms, scene points, or kiscule entities — the skeleton-reduction rules that build it strip idKisculeEntity, all AI classes, and all trigger/logic entities outright. This makes a kiscule-free recipe the natural, lower-risk fit:

Recipe A — entity-layer only, no kiscule (recommended):

  1. One new .cm collision box for the trigger volume (existing round-trip writer).
  2. One new idTrigger entity: inherit="triggers/trigger", class="idTrigger", renderModelInfo.model=NULL, clipModelInfo.clipModelName = the new box, edit.targets={num=1; item[0]="<spawner>"}.
  3. One new idTarget_AISpawn entity, modeled on the c1v3_venus... retail precedent above: edit.targets naming N new hidden AI (corpus-typical wave size 3–8, mean 5.28), maxActiveAIs optional.
  4. N new idAI2 entities, flags.hide=true, reusing an AI definition already mounted for the kit map, placed on its already-validated navmesh-backed area.
  5. No kiscule entity, no idEncounterGroupMgr/idAlarmSystem/idScenePoint_* needed — all proven optional by the retail precedent above.

This adds zero new decl/archive-entry names — only a .entities text edit (existing byte-exact round-trip proof) and one new .cm entry (existing round-trip proof) — which is why it is lower-risk, not merely shorter.

Recipe B — kiscule multi-wave (more faithful, higher risk): reuses the already-built-and-verified kill-counter → delay → next-wave pattern verbatim, but every kiscule write this project has ever proven appends to the payload of a kiscule entry a retail container already has. The kit map has no idKisculeEntity left to append to, so Recipe B needs either restoring one retail kiscule entity wholesale and stripping it down (untried), or authoring a genuinely new kiscule archive entry under a name no shipped container has — a step the project's tools can technically perform but which no report has actually exercised. This is the single open question that would decide whether Recipe B is viable at all.

Failed / corrected

  • An earlier pass's two cited "kiscule-free" retail precedents (commanderarea_secondfloor/thirdfloor) are actually dual-wired — kiscule also independently reaches them. The two retail precedents used above (c02p0_combat_triggers_trigger_12, c1v3_venus_combat_trigger_before_boss_spawn) are the corrected, confirmed-bare pair.

Open questions

  • Whether a brand-new kiscule archive entry (Recipe B) is structurally acceptable to the loader at all.
  • reuseDelay/maxActiveAIs omission-default semantics (does omitting maxActiveAIs mean unlimited, or targets.num?) — unresolved, needs disassembly support not yet available.
  • A small residual (~17%) of sampled kiscule-only triggers whose name a reference scan could not find referenced anywhere — most likely a method gap (a reference mechanism the scan doesn't model) rather than more undiscovered kiscule-free triggers, but not conclusively ruled out.
  • Everything here is data/structure only: no AI spawn, wave, or alarm has been observed in a running game in this project yet.

In-game test (not yet run)

Install a kit-map variant containing Recipe A's new trigger + spawner + hidden AI, start the game with developer mode enabled, load the test map, and walk into the trigger volume. If the previously-invisible AI appears exactly on crossing the trigger box and engages, that is the first runtime evidence for the pure entity-layer spawn route in this project — until then it rests only on retail precedent, never built or started.

Related: Mission Anatomy, Mission Kiscule Scripting, Kiscule Scripting, TNC Modding Surface.