Skip to content

Research Report R3 Missionsetter

LopeKinz edited this page Oct 1, 2026 · 1 revision

Fixing the Missing HUD Objective Banner for Out-of-Order Missions

Earlier work found that a custom map's HUD objective banner ("Enter the offices") doesn't appear when a mission is entered out of normal story progression — for example, jumping straight to it via chapter select — and traced the gate to a field read at a fixed memory offset on the player object whose writer couldn't be found anywhere in the executable. This pass pushed the search further across the entire 92 MB executable image (not just the code section) using four additional search methods, and still found no writer — but worked out what the gate actually checks, and found a shipped developer test script that solves exactly this scenario the same way this report's fix does. The result is a confirmed three-line code change to the project's mission-building tool, validated by running the tool's own full build-and-check pipeline end to end; the fix itself has not yet been confirmed inside a running game.

What the gate actually is

The suspicious field is not a per-session write at all — it's a copy of the mission decl's own missionFlags field. The game's internal enum table shows TYPE_MAIN is exactly the bit the gate tests, and every main-story mission decl checked (including the one the custom demo map uses) is TYPE_MAIN. So the real rule is: "if the player is still tracking a different main-story mission, don't show this one's banner" — a deliberate "one main-story objective tracked at a time" design, not a bug. Nothing in the normal ObjectiveStart/MissionStart mission-script handlers, nor the custom chain's own nodes, ever writes the tracked-mission pointer that this gate reads — confirmed by an exhaustive, five-method search of the entire executable image (direct displacement, a data-driven offset table, 16/64-bit and big-endian encodings, and a compiler "split-offset" pattern) that still finds the literal field offset exactly once: the read, never a write.

The confirmed fix

A corpus-wide scan of all 2,408 retail mission scripts' use of the SetActiveObjective node found only four wired uses in the entire game. One of them is a shipped developer test harness script that does exactly what this fix proposes: fires ObjectiveStart immediately followed (500 ms later) by SetActiveObjective, from the same out-of-band trigger, with showNotification="false" on the SetActiveObjective call. This is strong precedent that the game's own developers solved this exact "entered outside normal progression" scenario the same way.

SetActiveObjective's own notifier function (traced in full this pass) has a different, same-mission TYPE_MAIN check that is always satisfied here, and its showNotification parameter only selects a cosmetic "banner kind" flag on the queued HUD record — it does not gate whether the state update happens at all. That's why "false" (not "true") is the correct value: using "true" risks a double banner flash in the normal, non-broken case.

The exact change, tools/build_mission.py

         get(oid + ".start", "start_" + oid, 0)
         mission(oid + ".show", [o["decl"]], "true", 1)
         s.link(oid + ".start", "Received", oid + ".show", "ObjectiveStart")
+        mission(oid + ".activate", [o["decl"]], "false", 2)
+        s.link(oid + ".start", "Received", oid + ".activate", "SetActiveObjective")
         get(oid + ".end", "end_" + oid, 8)

Two more small edits are needed only because the tool's own offline self-check hardcodes the expected event trace — not because anything is functionally wrong without them:

     for i in chain:
-        exp += [("ObjectiveStart", (by[i]["decl"],)), ("ObjectiveComplete", (by[i]["decl"],))]
+        exp += [("ObjectiveStart", (by[i]["decl"],)), ("SetActiveObjective", (by[i]["decl"],)),
+                ("ObjectiveComplete", (by[i]["decl"],))]
-        cut_at = exp.index(("ObjectiveStart", (wobj["decl"],))) + 1
+        cut_at = exp.index(("SetActiveObjective", (wobj["decl"],))) + 1

This exact diff was run through the whole build_mission.py pipeline end to end (structure validation, retail-edge-pattern checks, offline event simulation including negative controls, container hashing) and passes every check; the unmodified baseline run reproduces the exact container hash already on record, confirming no incidental drift from the harness itself.

In-game test

  1. Apply the three edits above and rebuild the mission container (python tools/build_mission.py <demo spec>).
  2. Install as before (copy into the custom-map DLC folder) and start the "Gestapo Office" episode fresh (not a continued save).
  3. If "Enter the offices" now appears: fix confirmed. If not: enable the mission debug cvar, play to the same point, and check the console dump for a "Set active on missionIndex" log line — its absence points to a node-wiring problem before this path; its presence with still no visible banner points to a problem inside the banner-drawing code itself, beyond anything this fix touches.

Clone this wiki locally