Forráskód Böngészése

Hide gameplay HUD windows during Ember ending movies

Millie 4 napja
szülő
commit
50773b7c0c

+ 17 - 11
MISSION_EMBER_HANDOVER.md

@@ -1,22 +1,24 @@
 # 1AU / mission_ember — implementation and engineering handover
 # 1AU / mission_ember — implementation and engineering handover
 
 
-Prepared 6 September 2026 for Millie and the next agent working on Sunrise. Read this document before the older plans or transcripts. It describes the implementation at **`88a9632`**, followed by the documentation/archive commit identified in the delivery manifest. Later historical notes often describe approaches that have already been replaced.
+Prepared 6 September 2026 for Millie and the next agent working on Sunrise. Read this document before the older plans or transcripts. The delivered ZIPs preserve **`b9880dc`**, whose gameplay implementation was `88a9632`. This working handover now also documents the subsequent gameplay-HUD correction; the existing delivery archives have not been replaced. Later historical notes often describe approaches that have already been replaced.
 
 
 ## 1. Current result and first action
 ## 1. Current result and first action
 
 
 1AU is scripted from the opening cinematic through the escape and two ending movies. The user has repeatedly played the route and described it as almost complete. The latest investigation isolated the ending presentation problem after resolving native playback, texture residency, movie sequencing and return to orbit.
 1AU is scripted from the opening cinematic through the escape and two ending movies. The user has repeatedly played the route and described it as almost complete. The latest investigation isolated the ending presentation problem after resolving native playback, texture residency, movie sequencing and return to orbit.
 
 
-**The next action is a visual test of the correction in `88a9632`.** Do not start by rewriting mission progression or the movie loader.
+**The next action is a visual test of the installed gameplay-HUD filter.** The user tested `88a9632` and reported reticle, ammunition, abilities, radar and mission objectives still rendering over the movie. The log confirmed `requested=43 selected=33 applied=33` at t=766752 and STM playing at t=767072. Native HUD ownership is separate from the cinematic overlay window; see section 17. Do not start by rewriting mission progression or the movie loader.
 
 
-The immediately preceding build, `41e2728`, played both movies through the native decoder. Movie one was skipped with Escape; movie two reached its natural end. The game accepted completion, selected orbit and reached native `orbit_setup`. However, a loading window covered the video. The new build changes the selected UI state from `0x22` (`loading`) to `0x21` (`cinematic_overlay`). That mapping is now verified through the native state-to-window tables, rather than inferred from the broad cinematic category. The correction is compiled, tested and installed, but **the user has not yet visually tested it**.
+The earlier build, `41e2728`, played both movies through the native decoder. Movie one was skipped with Escape; movie two reached its natural end. The game accepted completion, selected orbit and reached native `orbit_setup`. However, a loading window covered the video. `88a9632` changed the selected UI state from `0x22` (`loading`) to `0x21` (`cinematic_overlay`). That mapping is verified through the native state-to-window tables, rather than inferred from the broad cinematic category. The subsequent HUD filter keeps that correction and excludes only the native role-18 HUD window's drawing during owned movie presentation. It is built, ABI-verified and installed. The first installation attempt stopped before any game writes because Destiny was running; installation succeeded after a subsequent process check confirmed the game had closed.
+
+Installed HUD-correction DLL SHA-256: `c2e84a350012ffd11eaeb39ef39bcb385b986c42dac5c0386ac0d0325f7fcd5b`. It still requires an in-game HUD/subtitle/menu test. The guarded installer verified the DLL and all 17 mission scripts against their source hashes.
 
 
 Latest installed DLL SHA-256:
 Latest installed DLL SHA-256:
 
 
 ```text
 ```text
-e8388cd407e2c90d423fdf4574ef0b1eb7e3a848bd1148710aeca890ef326343
+c2e84a350012ffd11eaeb39ef39bcb385b986c42dac5c0386ac0d0325f7fcd5b
 ```
 ```
 
 
-Installation manifest: `build/apex-cooling-installation.json`. Last backup: `build/apex-cooling-install-backup-20260906-210821`. The install replaced the DLL and 17 mission Lua files, verified their hashes, and preserved settings, save and generated SDK. The agent did not launch or stop the game.
+Installation manifest: `build/ember-installation.json`. Last backup: `build/ember-install-backup-20260906-212949-077349`. The old `build/apex-cooling-installation.json` still records the pre-filter delivery baseline. The install replaced the DLL and 17 mission Lua files, verified their hashes, and preserved settings, save and generated SDK. The agent did not launch or stop the game.
 
 
 ### What the evidence establishes
 ### What the evidence establishes
 
 
@@ -27,7 +29,7 @@ Installation manifest: `build/apex-cooling-installation.json`. Last backup: `bui
 | Movie picture | User explicitly confirmed visible first-movie picture on `15e507e`. Subsequent captures show valid video surfaces for both movies. |
 | Movie picture | User explicitly confirmed visible first-movie picture on `15e507e`. Subsequent captures show valid video surfaces for both movies. |
 | Movie sequencing | `41e2728` produced first completion, second request, second playing and second natural completion. |
 | Movie sequencing | `41e2728` produced first completion, second request, second playing and second natural completion. |
 | Orbit handoff | `41e2728` accepted completion, queued orbit, requested native cleanup and reached orbit setup. |
 | Orbit handoff | `41e2728` accepted completion, queued orbit, requested native cleanup and reached orbit setup. |
-| Unobscured picture, subtitles and menus on latest build | Pending visual test of `88a9632`. Previous build rendered `loading` over playback. |
+| Gameplay HUD, subtitles and menus | `88a9632` selects state 33, but the user reports gameplay HUD over the movie. The new role-18 drawing filter is installed and awaiting visual testing. Subtitle/menu behavior still needs visual confirmation. |
 | Remaining finale polish | Beam sound/screen effect, escape scorch, explosions, extraction-ship path and menu/skip behavior need targeted confirmation. Earlier source changes are not proof of visual or audio success. |
 | Remaining finale polish | Beam sound/screen effect, escape scorch, explosions, extraction-ship path and menu/skip behavior need targeted confirmation. Earlier source changes are not proof of visual or audio success. |
 | Fireteam wipe and respawn presentation | Implemented and covered by tests; a multiplayer acceptance run is still valuable. Do not imply solo observations prove co-op correctness. |
 | Fireteam wipe and respawn presentation | Implemented and covered by tests; a multiplayer acceptance run is still valuable. Do not imply solo observations prove co-op correctness. |
 
 
@@ -425,7 +427,11 @@ The live UI capture showed `loading` asset `80B46D88` alongside `subtitle_overla
 
 
 **`88a9632` changes the substitution to `0x21`.** It leaves other requested UI states alone, retains presentation across the movie handoff, and clears it on final completion or failure. Native drawing, window updates and input remain intact. The tests now follow state → enum → actual name hash and check that true loading/menu requests are preserved.
 **`88a9632` changes the substitution to `0x21`.** It leaves other requested UI states alone, retains presentation across the movie handoff, and clears it on final completion or failure. Native drawing, window updates and input remain intact. The tests now follow state → enum → actual name hash and check that true loading/menu requests are preserved.
 
 
-The user briefly opened menus for a read-only capture on the old build, but that only caught a short-lived additional window while the loading state was active. It is not a validated inventory/settings allowlist. There is currently no selective draw mask in the source.
+The user briefly opened menus for a read-only capture on the old build, but that only caught a short-lived additional window while the loading state was active. It is not a validated inventory/settings allowlist.
+
+The next `88a9632` test left the gameplay HUD over the video. Native `1316FC0` independently creates `hud` (name hash `268AB804`) or an equipment-specific override, then assigns semantic role 18 through `13165C0`. That setter writes window offset `310`; it is distinct from the state enum at `410`. The current `bootflow/ember_movie_hud.cpp` filter checks this role only at the verified root-window draw caller `132C1BD` → `13D9060`, returning before the entire HUD subtree, including cached child commands, is submitted. Recursive child widgets use the same callee but do not have a full window allocation, so the exact return-address guard must precede the role read. Do not remove that guard.
+
+Both `132BD80` layers and every other window role continue drawing. Native updates/input/visibility and user preferences remain unchanged. The filter is active only while the ending bridge owns presentation, spanning the movie handoff and ending on final completion/failure. It reports `hud_filter_attached` at startup and `hud_draw_suppressed` with the actual window identity on its first suppression. The native verifier checks the root construction, caller/callee and role setter; visual validation is pending. This is narrower than the old blanket layer suppression. Latest log and disassembly evidence: `build/first-encounter-audit/b9880dc-hud/`.
 
 
 A remaining interaction concern to test: the direct player uses a foreground Escape edge to call native stop. Native in-engine cutscenes have a type-6 skip incident; direct movies do not. Verify that closing inventory/settings does not accidentally skip the movie. If a change is needed, base it on native menu/input ownership rather than simply consuming all Escape presses.
 A remaining interaction concern to test: the direct player uses a foreground Escape edge to call native stop. Native in-engine cutscenes have a type-6 skip incident; direct movies do not. Verify that closing inventory/settings does not accidentally skip the movie. If a change is needed, base it on native menu/input ownership rather than simply consuming all Escape presses.
 
 
@@ -506,9 +512,9 @@ For the DLL carried in the archive, add `--dll _snapshot/runtime/steam_api64.dll
 
 
 ## 21. Next test and debugging decision tree
 ## 21. Next test and debugging decision tree
 
 
-1. Have the user manually launch the installed `88a9632` build. Confirm `frame_attached` and `presentation_attached` in the new log.
+1. Have the user manually launch the installed HUD-filter DLL; its SHA-256 is in section 1. Confirm `frame_attached`, `presentation_attached` and `hud_filter_attached` in the new log.
 2. Reach the final escape trigger normally. Confirm one movie-1 request. Preserve the current trigger and progression if it already fires.
 2. Reach the final escape trigger normally. Confirm one movie-1 request. Preserve the current trigger and progression if it already fires.
-3. Expect requested gameplay state 43 to select/applied state **33**, not 34. Check that the picture is visible, gameplay HUD/loading window is absent and subtitles appear when enabled.
+3. Expect requested gameplay state 43 to select/applied state **33**, not 34, and require `hud_draw_suppressed role=18` during playback. Check that the picture is visible, reticle/ammunition/abilities/radar/objective HUD is absent and subtitles appear when enabled.
 4. Open inventory and settings; verify normal input and rendering, including closing them without unintended skip. The native state is preferred to a new mask, but this behavior has not yet been observed on the new build.
 4. Open inventory and settings; verify normal input and rendering, including closing them without unintended skip. The native state is preferred to a new mask, but this behavior has not yet been observed on the new build.
 5. Test first-movie natural EOF in one run and explicit skip in another. The last run established first skip and second natural EOF, not every combination.
 5. Test first-movie natural EOF in one run and explicit skip in another. The last run established first skip and second natural EOF, not every combination.
 6. Verify second-movie picture and subtitles; require native `playing movie=2`, not just its queued/submitted log.
 6. Verify second-movie picture and subtitles; require native `playing movie=2`, not just its queued/submitted log.
@@ -531,10 +537,10 @@ Keep read-only evidence scoped to the relevant process/module and never recover
 
 
 ## 22. Handover completion checklist
 ## 22. Handover completion checklist
 
 
-- Source at `88a9632` is the last installed gameplay implementation.
+- The delivered archives remain source `b9880dc` with installed gameplay `88a9632`. This working tree additionally carries the HUD correction described in sections 1 and 17; do not assume it is in the old ZIPs.
 - This document, the reusable installer and the two pre-existing untracked handovers are included in the final documentation commit, with no co-author trailer.
 - This document, the reusable installer and the two pre-existing untracked handovers are included in the final documentation commit, with no co-author trailer.
 - Branch ZIP includes all committed files and branch history; scripts ZIP includes all 17 Lua files.
 - Branch ZIP includes all committed files and branch history; scripts ZIP includes all 17 Lua files.
 - Runtime DLL, matching generated SDK and selected diagnostic data are supplemental branch-archive assets with checksums.
 - Runtime DLL, matching generated SDK and selected diagnostic data are supplemental branch-archive assets with checksums.
 - Archive verification and the exact final commit are recorded in the delivery manifest.
 - Archive verification and the exact final commit are recorded in the delivery manifest.
 - No game launch, stop, new gameplay change or settings/save replacement is part of packaging.
 - No game launch, stop, new gameplay change or settings/save replacement is part of packaging.
-- First unresolved acceptance item remains the visible result of selecting `cinematic_overlay` on `88a9632`.
+- First unresolved acceptance item is the role-18 HUD filter, subtitles and menu behavior during both ending movies.

+ 2 - 0
Sunrise/src/client/hooks/bootflow/bootflow_hook_lifecycle.cpp

@@ -31,6 +31,7 @@ constexpr std::array kFixes{
     Fix{&stage_loading_cinematics, &publish_loading_cinematics},
     Fix{&stage_loading_cinematics, &publish_loading_cinematics},
     Fix{&stage_ember_movie_tick, &publish_ember_movie_tick},
     Fix{&stage_ember_movie_tick, &publish_ember_movie_tick},
     Fix{&stage_ember_movie_ui, &publish_ember_movie_ui},
     Fix{&stage_ember_movie_ui, &publish_ember_movie_ui},
+    Fix{&stage_ember_movie_hud, &publish_ember_movie_hud},
     Fix{&stage_ember_sunburn, &publish_ember_sunburn},
     Fix{&stage_ember_sunburn, &publish_ember_sunburn},
     Fix{&stage_owner_activity_slot, &publish_owner_activity_slot},
     Fix{&stage_owner_activity_slot, &publish_owner_activity_slot},
     Fix{&stage_region_private, &publish_region_private},
     Fix{&stage_region_private, &publish_region_private},
@@ -114,6 +115,7 @@ void uninstall() noexcept {
     uninstall_loading_cinematics();
     uninstall_loading_cinematics();
     uninstall_ember_sunburn();
     uninstall_ember_sunburn();
     uninstall_ember_movie_tick();
     uninstall_ember_movie_tick();
+    uninstall_ember_movie_hud();
     uninstall_ember_movie_ui();
     uninstall_ember_movie_ui();
     uninstall_orbit_handoff();
     uninstall_orbit_handoff();
     uninstall_composition_check();
     uninstall_composition_check();

+ 68 - 0
Sunrise/src/client/hooks/bootflow/ember_movie_hud.cpp

@@ -0,0 +1,68 @@
+#include <cstdint>
+#include <cstring>
+#include <intrin.h>
+#include "internal.h"
+#include "../ember_movies/ember_movies.h"
+#include "../../../core/logging/log.h"
+
+namespace sunrise::client::hooks::bootflow {
+namespace {
+hooking::detour::Handle handle{};
+const void* windowDrawReturn{};
+using DrawWidget = void(__fastcall*)(void*, void*, void*);
+
+void __fastcall draw_widget(void* widget, void* context, void* commands) noexcept {
+    const bool presenting = ember_movies::presenting();
+    static thread_local bool reported{};
+    if (!presenting) reported = false;
+    // 13D9060 also submits nested widgets. Only the verified window-list caller
+    // (132C1BD) supplies a full window; never read +310 from a child widget.
+    if (presenting && widget && _ReturnAddress() == windowDrawReturn) {
+        std::int32_t role{};
+        std::memcpy(&role, static_cast<const std::byte*>(widget) + 0x310, sizeof(role));
+        // 131778E assigns role 18 through 13165C0 to the gameplay HUD, including
+        // equipment-specific replacements of the default "hud" window.
+        // Skip its entire cached subtree, not the layer containing subtitles,
+        // menus and cinematic_overlay. Native update/input/visibility stay intact.
+        if (role == 0x12) {
+            if (!reported) {
+                std::uint32_t asset{}, name{};
+                std::memcpy(&asset, static_cast<const std::byte*>(widget) + 0x18, sizeof(asset));
+                std::memcpy(&name, static_cast<const std::byte*>(widget) + 0x24, sizeof(name));
+                core::log::writef(core::log::Channel::client, core::log::Level::info,
+                    "ev=ember_movie result=hud_draw_suppressed role=%d asset=%08X name=%08X",
+                    role, asset, name);
+                reported = true;
+            }
+            return;
+        }
+    }
+    if (auto original = reinterpret_cast<DrawWidget>(handle.original))
+        original(widget, context, commands);
+}
+}
+
+StageResult stage_ember_movie_hud(hooking::detour::Spec& spec) noexcept {
+    if (handle.attached) return StageResult::attached;
+    constexpr auto windowDrawSig = signature<signature_length("4C 8B C7 48 8D 54 24 70 48 8B CE E8 ? ? ? ? 48 8B CF E8 ? ? ? ?")>(
+        "4C 8B C7 48 8D 54 24 70 48 8B CE E8 ? ? ? ? 48 8B CF E8 ? ? ? ?");
+    constexpr auto widgetSig = signature<signature_length("40 57 41 55 41 56 48 83 EC 30 48 8B F9 4D 8B F0 0F B7 49 1E 4C 8B EA")>(
+        "40 57 41 55 41 56 48 83 EC 30 48 8B F9 4D 8B F0 0F B7 49 1E 4C 8B EA");
+    const auto* caller = scan_main_image_unique(windowDrawSig, "ember_movie_hud_window_call");
+    auto* target = scan_main_image_unique(widgetSig, "ember_movie_hud_widget_draw");
+    if (!caller || !target || resolve_relative(caller + 12, caller + 16) != target)
+        return StageResult::unavailable;
+    windowDrawReturn = caller + 16;
+    spec = {target, reinterpret_cast<void*>(&draw_widget)};
+    return StageResult::staged;
+}
+void publish_ember_movie_hud(const hooking::detour::Handle& value) noexcept {
+    handle = value;
+    core::log::write(core::log::Channel::client, core::log::Level::info,
+        value.attached ? "ev=ember_movie result=hud_filter_attached"
+                       : "ev=ember_movie result=hud_filter_attach_failed");
+}
+void uninstall_ember_movie_hud() noexcept {
+    static_cast<void>(hooking::detour::uninstall(handle));
+}
+}

+ 3 - 0
Sunrise/src/client/hooks/bootflow/internal.h

@@ -82,6 +82,9 @@ void uninstall_ember_movie_tick() noexcept;
 [[nodiscard]] StageResult stage_ember_movie_ui(hooking::detour::Spec& spec) noexcept;
 [[nodiscard]] StageResult stage_ember_movie_ui(hooking::detour::Spec& spec) noexcept;
 void publish_ember_movie_ui(const hooking::detour::Handle& handle) noexcept;
 void publish_ember_movie_ui(const hooking::detour::Handle& handle) noexcept;
 void uninstall_ember_movie_ui() noexcept;
 void uninstall_ember_movie_ui() noexcept;
+[[nodiscard]] StageResult stage_ember_movie_hud(hooking::detour::Spec& spec) noexcept;
+void publish_ember_movie_hud(const hooking::detour::Handle& handle) noexcept;
+void uninstall_ember_movie_hud() noexcept;
 [[nodiscard]] StageResult stage_ember_sunburn(hooking::detour::Spec& spec) noexcept;
 [[nodiscard]] StageResult stage_ember_sunburn(hooking::detour::Spec& spec) noexcept;
 void publish_ember_sunburn(const hooking::detour::Handle& handle) noexcept;
 void publish_ember_sunburn(const hooking::detour::Handle& handle) noexcept;
 void uninstall_ember_sunburn() noexcept;
 void uninstall_ember_sunburn() noexcept;

+ 10 - 0
docs/mission-ember-final-corrections.md

@@ -1,5 +1,15 @@
 # 1AU: movie loader, surge audio and escape explosions
 # 1AU: movie loader, surge audio and escape explosions
 
 
+## Gameplay HUD survives cinematic overlay selection
+
+The follow-up test of `88a9632` (delivery source commit `b9880dc`) reports reticle, ammunition, abilities, radar and mission objectives drawn over the movie. The captured log confirms `presentation_state requested=43 selected=33 applied=33 category=6` at t=766752 and native STM playing at t=767072. The selected window state is correct; it does not by itself remove gameplay HUD ownership while Apex remains loaded. Evidence is under `build/first-encounter-audit/b9880dc-hud/`.
+
+Native window manager `1316FC0` has an independent HUD branch: it selects the `hud` name at `1317689` (or an equipment override), creates the window at `1317784`, then calls `13165C0` with role `18` at `1317796`. That setter stores the role at window offset `310`. This is different from the window-state enum at `410`.
+
+The correction preserves cinematic state `21h` and adds a narrowly scoped draw filter in `bootflow/ember_movie_hud.cpp`. Only the full-window submission at `132C1BD` to `13D9060` can read offset `310`; nested widgets using the same draw function are passed through untouched. During owned Ember movie presentation, role 18 skips submission of the complete HUD subtree, including cached child commands. All other roles and both native UI layers continue normally, retaining subtitles and inventory/settings rendering/input. No saved preferences, visibility bits, native lists or gameplay mission state are changed. Presentation stays owned through the STM/CNN handoff and clears on final completion/failure.
+
+The mapped-image verifier checks both unique signatures, the caller/callee relationship, root-pointer construction and the native role setter. The next live test must confirm `hud_draw_suppressed`, the absence of the reported HUD, subtitles, menus and restoration after leaving playback. This is a specific gameplay-window filter, not the removed blanket UI-layer mask. The release DLL built successfully and was installed after the game closed, with all 18 destination hashes verified. DLL SHA-256: `c2e84a350012ffd11eaeb39ef39bcb385b986c42dac5c0386ac0d0325f7fcd5b`; manifest `build/ember-installation.json`, backup `build/ember-install-backup-20260906-212949-077349`. No game launch or stop was performed. Visual acceptance remains pending.
+
 ## Correct the cinematic loading state to the playback overlay
 ## Correct the cinematic loading state to the playback overlay
 
 
 The `41e2728` test exposed a specific presentation regression: `presentation_state requested=43 selected=34 applied=34 category=6` at t=281212, followed by valid movie playback. A read-only UI capture found window `80B46D88`, name hash `D505DEBB` = FNV-1(`loading`), above the video, alongside `80BC6681`, name hash `7737E414` = FNV-1(`subtitle_overlay`). Valid 1920x800 surfaces were captured for both movies. The previous verification established category membership, but misidentified state `0x22` as playback.
 The `41e2728` test exposed a specific presentation regression: `presentation_state requested=43 selected=34 applied=34 category=6` at t=281212, followed by valid movie playback. A read-only UI capture found window `80B46D88`, name hash `D505DEBB` = FNV-1(`loading`), above the video, alongside `80BC6681`, name hash `7737E414` = FNV-1(`subtitle_overlay`). Valid 1920x800 surfaces were captured for both movies. The previous verification established category membership, but misidentified state `0x22` as playback.

+ 5 - 1
docs/mission-ember-prerendered-ending.md

@@ -51,7 +51,11 @@ The user confirmed both movie audio tracks in sequence and the mission-complete
 
 
 The drawing-layer and Sunrise overlay suppression have been removed. The first state-based attempt (`41e2728`) incorrectly selected `0x22`. Although `0x21` and `0x22` both map to UI category 4, they select different windows: native `1312540` and `13126E0` map `0x21` to enum 26 / `cinematic_overlay` (`17A73819`), and `0x22` to enum 29 / `loading` (`D505DEBB`). The live capture confirmed the `loading` window above otherwise valid video and subtitles.
 The drawing-layer and Sunrise overlay suppression have been removed. The first state-based attempt (`41e2728`) incorrectly selected `0x22`. Although `0x21` and `0x22` both map to UI category 4, they select different windows: native `1312540` and `13126E0` map `0x21` to enum 26 / `cinematic_overlay` (`17A73819`), and `0x22` to enum 29 / `loading` (`D505DEBB`). The live capture confirmed the `loading` window above otherwise valid video and subtitles.
 
 
-While the ending bridge owns playback, its state hook now replaces normal gameplay choices `0x2B/0x2D/0x2F` with **`0x21`** and calls the original `E1CD60` transition. Other states pass through, including menus and actual loading/error requests. Native window/category management handles the change; neither `132BD80` drawing layer is skipped. Presentation remains active between STM and CNN, then clears on CNN completion or failure. The mapped-image verifier checks the full state-to-window mapping, names and cinematic branch; unobscured video and normal menu behavior still need the next live test.
+While the ending bridge owns playback, its state hook now replaces normal gameplay choices `0x2B/0x2D/0x2F` with **`0x21`** and calls the original `E1CD60` transition. Other states pass through, including menus and actual loading/error requests. Native window/category management handles the change; neither `132BD80` drawing layer is skipped. Presentation remains active between STM and CNN, then clears on CNN completion or failure.
+
+The next user test reported reticle, ammunition, abilities, radar and mission objectives over the movie despite `requested=43 selected=33 applied=33`. Native `1316FC0` independently creates a gameplay HUD window and marks it with role 18 through `13165C0`, at window offset `310`. Selecting `cinematic_overlay` alone does not exclude that window in the held Apex world.
+
+`bootflow/ember_movie_hud.cpp` now filters only the root HUD window's submission to `13D9060` while Ember owns movie presentation. It verifies the full-window caller at `132C1BD` before reading the role: recursive child-widget calls to the same function must never be treated as full windows. Role 18 includes equipment-specific HUD replacements. Other window roles, native layer drawing, updates, input, subtitles and menus pass through. Skipping the root also excludes its cached child commands. The filter releases with presentation ownership; no visibility flags or user preferences are changed. Logs report `hud_filter_attached` and the first actual `hud_draw_suppressed` per presentation. The native verifier checks the caller, callee, HUD assignment and exact role offset. This correction still needs an in-game HUD/subtitle/menu test.
 
 
 The `41e2728` run confirmed native CNN playback after STM, CNN natural EOF and immediate orbit selection followed by `orbit_setup`. The loading overlay is the remaining presentation defect captured in that run.
 The `41e2728` run confirmed native CNN playback after STM, CNN natural EOF and immediate orbit selection followed by `orbit_setup`. The loading overlay is the remaining presentation defect captured in that run.
 
 

+ 22 - 1
tests/verify_ember_movie_native.py

@@ -100,7 +100,28 @@ target(0x1317366,0,0x13126E0)
 # The UI explicitly excludes loading state 22h from its cinematic-overlay branch.
 # The UI explicitly excludes loading state 22h from its cinematic-overlay branch.
 assert data[0x1317094:0x131709A] == bytes.fromhex('41 83 fc 22 74 05')
 assert data[0x1317094:0x131709A] == bytes.fromhex('41 83 fc 22 74 05')
 assert fnv1('subtitle_overlay') == 0x7737E414
 assert fnv1('subtitle_overlay') == 0x7737E414
-print('Native UI 21h -> cinematic_overlay; 22h -> loading verified; no draw suppression.')
+print('Native UI 21h -> cinematic_overlay; 22h -> loading verified; UI layers preserved.')
+# A playback overlay does not remove the separate gameplay HUD. Filter only
+# its full-window submission, before 13D9060 can replay the cached subtree.
+hud = 'Sunrise/src/client/hooks/bootflow/ember_movie_hud.cpp'
+window_call = signature(hud, 'windowDrawSig')
+widget_draw = signature(hud, 'widgetSig')
+assert window_call == 0x132C1B2 and widget_draw == 0x13D9060
+target(window_call, 11, widget_draw)
+assert window_call + 16 == 0x132C1C2
+# The caller passes the full window at intrusive-list node minus 600h.
+assert data[0x132C0F9:0x132C100] == bytes.fromhex('48 8d b3 00 fa ff ff')
+assert data[0x132C1BA:0x132C1BD] == bytes.fromhex('48 8b ce')
+# Child submissions share this callee but have a different caller and only a
+# widget-sized allocation. They must pass through without accessing +310.
+target(0x13D91FA, 0, widget_draw)
+# Native HUD creation selects "hud" (or an equipment override), then assigns
+# the semantic role 18. The role setter writes window+310, not state enum +410.
+assert struct.unpack_from('<I', data, 0x1FE2060)[0] == fnv1('hud')
+assert data[0x131778E:0x1317796] == bytes.fromhex('ba 12 00 00 00 48 8b c8')
+target(0x1317796, 0, 0x13165C0)
+assert data[0x13165DA:0x13165E0] == bytes.fromhex('89 90 10 03 00 00')
+print('Native gameplay HUD role 18 and root-only cached draw boundary verified.')
 for offset, expected in [(0x96, 0x4294D0), (0xD1, 0x423EF0), (0x14C, 0x4312D0), (0x157, 0x435AA0)]:
 for offset, expected in [(0x96, 0x4294D0), (0xD1, 0x423EF0), (0x14C, 0x4312D0), (0x157, 0x435AA0)]:
     target(load, offset, expected)
     target(load, offset, expected)
 for offset, expected in [(0x85, 0x42C650), (0x9F, 0x425310)]:
 for offset, expected in [(0x85, 0x42C650), (0x9F, 0x425310)]: