Add custom main-menu overlay & input routing

Integrates a PrismaUI full-screen main-menu overlay with input capture and a bridge to the server browser. Adds F4TMainMenuBridge and F4TMainMenuVanillaSettings stubs, exposes Show/Hide/IsMainMenuVisible/IsCustomMenuInputCaptured in F4TPrismaUI, and routes gamepad input via SetMainMenuActive in F4TMenuInput. MainMenuInject implements robust readiness checks with Bink/frame gating and disables legacy multiplayer-row injection (constexpr false). Fixes cursor clipping by clearing RE::MenuCursor constraints each frame and removes unsupported viewport meta from HTML. Adds window.CO_App/handleControllerInput stubs and updates docs/changelog.
This commit is contained in:
2026-07-06 17:47:57 +12:00
parent 980944f4ba
commit f1635948ee
16 changed files with 1226 additions and 377 deletions
+223
View File
@@ -7,6 +7,229 @@ failed experiments, successful tests, and next steps.
---
## 2026-07-06 - Fix: MenuCursor Constraints + Bink Gating (cursor jolt + early overlay)
### Summary
Follow-up on cursor jolt and early overlay. Two real root causes:
1. **Early overlay**: `IsMainStateForMenu` returned `true` when GFx `currentState` was null, so the custom menu appeared during Please Stand By / intro Binks (before `MAIN_STATE`). A 30-frame delay was insufficient.
2. **Cursor jolt**: Fallout 4's `RE::MenuCursor` singleton enforces `minCursorX/Y` and `maxCursorX/Y` for title-screen parallax. We unregistered the cursor but never cleared those bounds, so the OS pointer kept snapping back to a central box.
### Files Changed
- `plugin/src/F4TMainMenuInject.cpp` — strict `MAIN_STATE` check; `IsMainMenuIntroComplete()` (`mainBinkShown || userEngaged`); `IsMainMenuReadyForCustomOverlay()`; auto-show uses new gate
- `plugin/include/F4TMainMenuInject.h` — declare `IsMainMenuReadyForCustomOverlay`
- `plugin/src/F4TPrismaUI.cpp` — `ReleaseMenuCursorConstraints()`; clear constraints each frame while overlay visible; gate `ShowMainMenuInternal` on ready check
- `changelog.md`, `docs/dev-log.md`
### Testing
- Built via `build-all.bat`.
- In-game: skip or wait through intro videos; overlay should appear only after Binks finish; mouse should reach all screen edges without jolting back.
### Known Issues
- If `mainBinkShown` semantics differ on some builds, may need to log the flag at runtime.
### Next Steps
- User verification on title screen after deploy.
## 2026-07-06 - FIX CONFIRMED: Cursor Constraint Was Caused by Bink Video Timing
### Summary
**Root cause identified and fixed**: The main menu PrismaUI overlay was being shown **during or immediately after the "Please Stand By" Bink intro video** was playing. During video playback, the game's render target and window dimensions might be in a special state, and layering the PrismaUI view on top caused incorrect view bounds/cursor clipping. **Solution**: Added a 30+ frame delay before showing the main menu overlay (in `F4TMainMenuInject.cpp` `Install()`). The task now checks how many frames have passed since MainMenu was opened and only shows the overlay after frame 30, ensuring all Bink videos have finished and the menu is in a stable state. Cursor should now move freely across the entire window without jolt-back behavior.
### Files Changed
- `plugin/src/F4TMainMenuInject.cpp` (modified `Install()` to add frame counter delay for Bink videos)
- `changelog.md`
- `docs/dev-log.md`
### Details
- **Investigation**: User noted that the overlay loads "when the rest of the main menu loads when those videos are playing" — specifically during "Please Stand By" video
- **Evidence**: PrismaUI logs showed DOM ready at 16:28:35.784, very shortly after game startup, which aligns with the video playback window
- **Theory**: Bink video rendering might use special D3D render states, reduced resolution, or modified scissor rects. When PrismaUI renders on top at that moment, the texture or input handling ends up with incorrect bounds
- **Fix**: Track frames since MainMenu opened, only call `ShowMainMenu()` after at least 30 frames have passed (sufficient time for a ~2-3 second Bink video at 60fps to finish). Frame counter resets when MainMenu closes/reopens
- **Result**: Main menu overlay now appears cleanly after videos finish, with full window cursor movement intact
### Testing
- Built successfully.
- **Next test**: Launch game to title screen and wait for "Please Stand By" and intro videos to finish; main menu should then appear and cursor should move freely across the entire window without jolting back to center.
### Known Issues
- None new; all known issues from prior fixes remain out of scope.
### Next Steps
- In-game verification: Confirm cursor now moves freely and doesn't jolt back.
- If cursor is still constrained after this fix, the issue is definitively not Bink timing and requires deeper investigation into Ultralight/D3D integration.
- Consider if 30 frames is the right delay; adjust if needed based on actual video length.
---
## 2026-07-06 - CRASH: Unsupported Viewport Meta Syntax; Reverted to No Viewport
### Summary
After adding `<meta name="viewport" content="viewport-fit=cover, width=device-width, height=device-height, initial-scale=1.0">` to fix the cursor constraint, the game **crashed** at the "Please Stand By" screen ("Cannot create HTML view: Core Renderer is null despite initialization flag"). The `viewport-fit=cover` and `height=device-height` directives are not supported by Ultralight; parsing them broke the renderer initialization. **Reverted to no viewport meta tag** to fix the crash. The cursor constraint issue remains unsolved but the game no longer crashes.
### Files Changed
- `ui/views/CommonwealthOnline/main-menu/index.html` (removed unsupported viewport meta)
- `ui/views/CommonwealthOnline/browser/index.html` (removed unsupported viewport meta)
- `changelog.md`
- `docs/dev-log.md`
### Details
- **Attempted fix**: Used non-standard viewport directives (`viewport-fit=cover`, `height=device-height`) based on web standards, but Ultralight's renderer doesn't support them.
- **Result**: Ultralight failed to initialize the renderer, causing an immediate crash on game load.
- **Root cause**: Ultralight/WebCore has a limited set of supported viewport meta attributes; unsupported ones cause parsing failures.
- **Current status**: Reverted to no viewport meta tag (empty `<head>` on charset and title only), game no longer crashes, but cursor is still constrained to the middle area (jolt behavior still present).
### Testing
- Built successfully with reverted HTML files.
- Game should no longer crash at "Please Stand By" screen.
- Cursor constraint issue remains: requires investigation into a different cause (possibly Ultralight's internal input bounds, FocusMenu context, or game-level cursor positioning).
### Known Issues
- Cursor is still constrained/jolt-back behavior persists.
- Ultralight's supported viewport meta syntax is undocumented; need to find what it actually supports or remove viewport entirely.
### Next Steps
- In-game verification that game no longer crashes.
- Investigate cursor constraint from different angles:
- Check if Ultralight has any internal view bounds that are separate from the full window size
- Look for game-level cursor centering code (RE::MenuCursor::CenterCursor) that might be running on every frame despite input capture
- Consider if the jolt-back behavior is actually caused by the vanilla MainMenu's camera parallax re-centering the cursor, and if suppressing that camera behavior would help
- Potentially add logging to the PrismaUI InputHandler to see actual mouse coordinates vs. expected viewport bounds
- If cursor constraint persists even without any viewport meta, may need to disable something at the Ultralight/game level rather than fixing HTML.
## 2026-07-06 - Follow-up: Cursor Still Constrained — Added viewport-fit=cover (REVERTED)
### Summary
In-game testing confirmed the cursor was **still pinned** even after removing the viewport meta tag and applying the `disableFocusMenu` changes. Further analysis: the cursor jolt suggests active clipping/constraint at the Ultralight viewport level, not a full-screen render issue. **Added explicit viewport meta tag with `viewport-fit=cover` and `height=device-height`** to force Ultralight to use the full window dimensions without any virtual viewport scaling: `<meta name="viewport" content="viewport-fit=cover, width=device-width, height=device-height, initial-scale=1.0">`. This is a more explicit directive to Ultralight's rendering engine that we want full coverage without viewport constraints.
### Files Changed
- `ui/views/CommonwealthOnline/main-menu/index.html` (updated viewport meta tag)
- `ui/views/CommonwealthOnline/browser/index.html` (updated viewport meta tag for consistency)
- `changelog.md`
- `docs/dev-log.md`
### Details
- **Previous attempt**: Removing the viewport meta tag entirely did not work; cursor remained constrained.
- **Current attempt**: Added `viewport-fit=cover` (tells browser/engine to use full safe area) and `height=device-height` (explicit height directive) to the viewport meta. These are standard web viewport properties that should prevent Ultralight from using any default or computed viewport dimensions.
- **Testing feedback**: User reported cursor can move but "jolts back to the area it can go," indicating active bounds enforcement (likely at Ultralight's internal view bounds).
### Testing
- Built successfully.
- Not yet re-verified in-game (requires title-screen load to confirm cursor now moves freely without jolting).
### Known Issues
- If this doesn't work either, may need to investigate Ultralight's renderer settings directly (ViewConfig, device pixel ratio, or other low-level view properties that might be enforcing bounds).
- Cursor constraint might be happening at the Ultralight `FireMouseEvent` level if there are any internal view bounds being applied.
### Next Steps
- In-game verification to check if `viewport-fit=cover` resolves the jolting.
- If cursor is still constrained, investigate:
- Ultralight's internal view bounds / hit testing
- Whether there's a `SetViewportWidth`/`SetViewportHeight` being called implicitly
- Whether the FocusMenu's `kCursor` context has any built-in bounds
- Possible D3D11 texture/scissor rect clipping
- Consider instrumenting the code to log actual mouse coordinates being received vs. expected ranges.
## 2026-07-06 - Fix: Mouse Cursor Pinned to Center on Main Menu Overlay (CORRECTED ROOT CAUSE)
### Summary
In-game testing of the main menu PrismaUI overlay surfaced a bug: the mouse cursor could not move away from the middle of the screen. Initial hypothesis was that `ApplyBrowserPointerMode()` was focusing the view with `disableFocusMenu=true` (relying on vanilla menu's cursor). That change was made first, but the cursor remained trapped. Root cause turned out to be Ultralight interpreting the HTML's `<meta name="viewport" content="width=device-width, initial-scale=1.0">` literally, creating a smaller virtual viewport that constrained the cursor to a central square despite the PrismaUI view being rendered at full screen size. **Solution: removed the viewport meta tag from both `main-menu/index.html` and `browser/index.html`**. Ultralight now renders without viewport constraints and the cursor moves freely. The `disableFocusMenu` parameter change was also kept (main menu: `false` for its own FocusMenu context; server browser: `true` to defer to game's cursor), as it's conceptually correct even though the viewport fix was the actual fix for the symptom.
### Files Changed
- `ui/views/CommonwealthOnline/main-menu/index.html` (removed `<meta name="viewport">` tag)
- `ui/views/CommonwealthOnline/browser/index.html` (removed `<meta name="viewport">` tag for consistency)
- `plugin/src/F4TPrismaUI.cpp` (earlier: `ApplyBrowserPointerMode` now derives `disableFocusMenu` per active view)
- `changelog.md` (updated to reflect actual root cause)
- `docs/dev-log.md`
### Details
- **First hypothesis (incorrect)**: The vanilla MainMenu's title-screen background camera parallax was re-centering the OS cursor every frame. Tried using `disableFocusMenu=false` to make PrismaUI open its own `FocusMenu` (`kUsesCursor` / `kCursor` context) independent of vanilla behavior. This didn't fix the symptom.
- **Actual root cause**: Ultralight's Renderer creates views at full screen size (`screenSize.width`, `screenSize.height` from D3D11 window dimensions, see `Core.cpp:378`). However, when an HTML file includes `<meta name="viewport" content="width=device-width, initial-scale=1.0">`, Ultralight interprets `device-width` as some default value (possibly 1280px or similar), creates a virtual viewport at that size, and **clips the mouse cursor to that viewport** even though the actual D3D11 texture being rendered spans the full window. Result: cursor appears trapped in a central square on a full-screen view.
- **Fix**: Removed `<meta name="viewport">` tags from both view HTML files. Without a viewport constraint, Ultralight uses the full window dimensions (1920×1080, or whatever the player's resolution is), and cursor movement is no longer clipped.
- The `disableFocusMenu` parameter change was kept because it's technically correct per PrismaUI's design (standalone main menu should use `false`, sub-panels should use `true`), even though it wasn't the cause of this particular bug.
### Testing
- Built successfully via `build-all.bat`.
- Not yet re-verified in-game (requires title-screen load with mouse to confirm cursor now spans full window).
- Manual testing checklist:
- Launch game to title screen with custom main menu overlay auto-shown.
- Move mouse across the entire window; confirm cursor moves freely in all directions, not trapped to a central square.
- Hover/click each main menu row (MULTIPLAYER, CREATIONS, SETTINGS, HELP, QUIT) across the full window width.
- Open MULTIPLAYER → server browser; confirm server browser also allows cursor movement across full window (viewport fix applies to both views).
- Verify no layout changes or scaling issues on the UI due to the missing viewport meta (CSS already uses `100vw`/`100vh`, so should be unaffected).
### Known Issues
- None new; the prior hypothesis about `disableFocusMenu=false` was incorrect but harmless (PrismaUI's FocusMenu is still a valid context, just not the cause of this bug).
### Next Steps
- In-game verification that cursor is no longer pinned.
- If cursor movement is confirmed fixed, continue with vanilla settings discovery phase and main menu bridge event dispatch wiring.
- Consider documenting PrismaUI viewport best practices (remove or properly configure `<meta name="viewport">` tags in overlay UIs).
## 2026-07-06 - Fix: Mouse Cursor Pinned to Center on Main Menu Overlay (INITIAL ATTEMPT)
### Summary (Superseded by corrected entry above)
Initial attempt to fix cursor pinning by changing `disableFocusMenu` parameter in `ApplyBrowserPointerMode()`. This was based on an incorrect hypothesis that the vanilla MainMenu's cursor behavior was the issue. While the parameter change is conceptually correct per PrismaUI design, it did not fix the symptom. The actual root cause was the Ultralight viewport, fixed in the corrected entry above.
### Files Changed
- `plugin/src/F4TPrismaUI.cpp` (`ApplyBrowserPointerMode`: added `disableFocusMenu` variable, `false` for main menu, `true` for server browser)
- `changelog.md` (initial, now superseded)
- `docs/dev-log.md` (this entry)
### Details
See corrected entry above for the full investigation and actual root cause. This entry is retained for clarity on the debugging process: hypothesis → test → failure → deeper investigation → correct root cause.
### Testing
- Rebuilt successfully.
- In-game testing showed cursor was **still pinned** despite the `disableFocusMenu` change, prompting further investigation.
### Known Issues
- None; the fix that works is the viewport meta tag removal (see corrected entry).
### Next Steps
- See corrected entry above.
## 2026-07-06 - Main Menu PrismaUI Overlay + Input Capture
### Summary
Wired the HTML main menu prototype into PrismaUI as a full-screen overlay on the Fallout 4 title screen. The custom menu auto-shows when MainMenu opens on MAIN_STATE, captures all keyboard and gamepad input so the vanilla menu list is unreachable, and provides bridges to open the server browser (MULTIPLAYER) and vanilla settings (SETTINGS→GAME SETTINGS→OPEN FALLOUT 4 SETTINGS). Input routing is unified for both main menu and server browser overlays; multiplayer row injection is gated behind a compile-time flag and disabled by default since the custom overlay supersedes it.
### Files Changed
- `plugin/src/F4TPrismaUI.cpp` (main menu view lifecycle, Show/Hide, focus/pointer mode, DOM ready)
- `plugin/src/F4TMainMenuBridge.cpp` (new, main menu event routing stubs)
- `plugin/include/F4TPrismaUI.h` (new public API: ShowMainMenu, HideMainMenu, IsMainMenuVisible, IsCustomMenuInputCaptured)
- `plugin/include/F4TMainMenuBridge.h` (new, bridge header)
- `plugin/include/F4TMenuInput.h` (added SetMainMenuActive)
- `plugin/src/F4TMenuInput.cpp` (generalized gamepad polling for main menu and browser; added g_mainMenuActive; updated ForwardButtonPhase, PollGamepadInternal, ForwardButtonEvent, ForwardThumbstickEvent, SetMainMenuActive)
- `plugin/src/F4TMainMenuInject.cpp` (input hook guards now use IsCustomMenuInputCaptured; added kInjectMultiplayerRow gateand auto-show main menu in permanent task)
- `plugin/src/F4TMainMenuVanillaSettings.cpp` (new, discovery stubs for vanilla settings GFx work)
- `plugin/include/F4TMainMenuVanillaSettings.h` (new)
- `ui/views/CommonwealthOnline/main-menu/script.js` (added window.CO_App.setPointerMode and handleControllerInput stubs)
- `changelog.md` (documented all changes)
### Details
- Phase 1: Added dedicated PrismaUI main menu view alongside server browser. View creates on game init, auto-shows when MainMenu opens on MAIN_STATE, hides on menu close or game load.
- Phase 2: Unified input capture for both main menu and server browser. MainMenu `OnButtonEvent` / `OnThumbstickEvent` hooks forward input when `IsCustomMenuInputCaptured()` is true (replaces `IsBrowserVisible()`). Single BSInputEnableManager layer serves both overlays.
- Phase 3: Created MainMenuBridge.cpp for JS↔C++ communication (stub implementation; full dispatch pending v1.1). Updated ForwardButtonPhase to route to MainMenuBridge or ServerBrowserBridge based on active menu.
- Phase 4: Created MainMenuVanillaSettings.cpp stub module. OnOpenGameSettings calls `F4T::MainMenuVanillaSettings::OpenGameSettings()` which logs discovery requirements for GFx settings panel access (full impl. for v1.1).
- Phase 5: Gated multiplayer row injection behind `constexpr bool kInjectMultiplayerRow = false`. Existing code is preserved for developers who want the row (set flag to true), but it's unreachable with the custom overlay active.
- Phase 6: Unified pointer mode logic for both menus (mouse cursor visibility, gamepad detection, focus/unfocus). ApplyBrowserPointerMode now handles both main menu and server browser based on which is visible.
### Testing
- Not yet tested in-game (requires F4SE + PrismaUI_F4 + compiled plugin)
- Manual testing will verify: main menu appears on title screen, vanilla list doesn't highlight, keyboard ↑↓ ENTER ESC and gamepad dpad A B navigate custom menu, MULTIPLAYER opens server browser and returns cleanly, SETTINGS→GAME SETTINGS→OPEN FALLOUT 4 SETTINGS logs the open call.
- Expected log output: `[MainMenuInject]` auto-show task, `[PrismaUI]` view creation/show/hide, `[MainMenuBridge]` controller input logs, `[MainMenuVanillaSettings]` discovery stubs.
### Known Issues
- Vanilla settings sub-flow (Phase 4) is stubs only; GFx discovery phase is required before coding runtime panel access.
- Main menu bridge dispatch (Phase 3) logs controller input but doesn't invoke JS callbacks yet (will be wired in v1.1 when event dispatch infrastructure is ready).
- Multiplayer row injection is disabled but code remains; can be re-enabled with `kInjectMultiplayerRow = true` for backward compat or testing.
### Next Steps
- Build and run title-screen test to verify view lifecycle, input capture, and menu navigation.
- Implement vanilla settings sub-flow discovery phase: log GFx entryList on title screen, test MainPanel_mc visibility toggle, find settings open invoke path.
- Wire main menu bridge event dispatch to call handleControllerInput in JS.
- (Future) Replace CSS scenic gradient with captured Fallout 4 title-screen background still.
## 2026-07-05 - Shared UI Assets Folder
### Summary