From 678361906986f857e98bbbe824c7eede671a9d4d Mon Sep 17 00:00:00 2001 From: arcattack Date: Sun, 19 Jul 2026 17:30:35 -0500 Subject: [PATCH] Gitea #9: the upper-MFD PRESET pages (3 MFDs x 5) live -- SetPresetMode table re-decoded to the ModeMFD bits + desktop J/K/L page cycle The preset system was unwired by ONE defect in the message layer: the SetPresetMode @004d1b24 table @0051dbf0 had been transcribed from the section dump as BIG-endian dwords ({0x1e,0x01000000} instead of {0x1e,0x01}), so a preset press set a garbage high bit -- and for group 1 items 3-4 / group 2 items 0-2 stomped the LIVE NonMapping / Intercom / ModeSecondary* bits -- while the real page bits never moved. Ground truth (section_dump.txt:72901-72908, little-endian + BTL4MODE.HPP [T0]): set = ModeMFD{1,2,3}{Quad,Eng1-4} = 1<<(group*5+item), bits 0-14, fully disjoint from the #6 secondary trio (bits 18-20); group 2 is MFD3, NOT a duplicate of the secondary views. What the 15 presets show (l4gauge.cfg): group = the MFD (Mfd1 lower left / Mfd2 upper center / Mfd3 lower right), item 0 = the btquad.pcx Quad overview (up to 4 vehicleSubSystems cluster panels), items 1-4 = the full-screen engineering-detail pages (bteng.pcx + prepEngr screens group*4+1..4 + the cluster eng children: GENERATOR SELECT A-D, POWER graph, COOLING loop, DAMAGE, ammo). Empty screens are authored per mech (Blackhawk: 3/11/12 empty). Authentic dispatch (streamed "L4" .CTL EventMappings, BT_CTRLMAP_LOG dump): each MFD owns the 8-button RIO bank around it, MODE-MASK-gated -- Mfd1 = 0x08-0x0F, Mfd2 = 0x20-0x27, Mfd3 = 0x00-0x07. Quad page -> direct-select buttons for the POPULATED eng pages (mapper msgs Aux1Eng1-4 0x4-0x7 / Aux2* 0x9-0xC / Aux3* 0xE-0x11); eng page -> one back-to-Quad button (0x3/0x8/0xD) + per-subsystem controls. These records already install and fire on desktop (btinput passes the live manager mask), so the NUMPAD profile's 0x20-0x27 keys page MFD2 authentically. Port wiring (the #6 pattern): keys J/K/L -> actions Mfd1/2/3Cycle -> gBTPresetCycle -> L4MechControlsMapper::CyclePresetModeNow(group) -- a documented desktop shim (24 mode-dependent pod buttons don't fit a keyboard) that cycles Quad -> populated Eng pages -> Quad, visiting exactly the pod-reachable set; the body is the authentic SetPresetMode. Dev-composite: BTDrawGaugeSurfaces now draws the Eng1-3 planes at their sibling cells and skips any mono plane whose channel is BlankColor, honoring the mode-driven reconfigure (RemapGraphicsPort) -- each dev cell shows the ACTIVE page like the pod monitor. This supersedes and removes the 2026-07-12 GAUGREND "frozen-dial" scaffold (forced all 15 page bits active under BT_DEV_GAUGES; it pinned the shared Eng plane on the highest screen and ate the page flips). Pixel-verified (BT_PRESET_TEST + BT_DEV_GAUGES_DOCK + BT_SHOT, Blackhawk): all three MFDs page Quad -> SYSTEM NN eng details -> back to Quad in lockstep with the [mode] preset mask log; group 0 skips the authored-empty screen 3, group 2 skips 11/12. Un-regressed: N display cycle (0x450421->0x490421->0x510421, page bits intact), M control mode, CONTROLS.MAP 52 bindings parse clean. Diags: BT_MODE_LOG ([mode] preset), BT_PRESET_TEST=. Co-Authored-By: Claude Fable 5 --- content/CONTROLS.MAP | 5 +- content/CONTROLS_NUMPAD.MAP | 3 + context/decomp-reference.md | 3 +- context/gauges-hud.md | 53 ++++++++++ context/pod-hardware.md | 14 ++- engine/MUNGA/GAUGREND.cpp | 26 ++--- engine/MUNGA_L4/L4VB16.cpp | 23 ++++- engine/MUNGA_L4/L4VB16.h | 14 ++- game/reconstructed/CLASSMAP.md | 4 +- game/reconstructed/btinput.cpp | 11 ++ game/reconstructed/btinput.hpp | 3 + game/reconstructed/btl4mppr.cpp | 173 ++++++++++++++++++++++++++++++-- game/reconstructed/btl4mppr.hpp | 6 ++ game/reconstructed/mech4.cpp | 34 +++++++ 14 files changed, 336 insertions(+), 36 deletions(-) diff --git a/content/CONTROLS.MAP b/content/CONTROLS.MAP index 46f46d9..3dfcd88 100644 --- a/content/CONTROLS.MAP +++ b/content/CONTROLS.MAP @@ -32,7 +32,7 @@ # LeftStickX LeftStickY RightStickX RightStickY # LeftTrigger RightTrigger # action ViewToggle LookBehind AllStop ModeCycle DisplayCycle Valve Flush -# ConfigHold Generator1-4 Reconnect +# ConfigHold Generator1-4 Reconnect Mfd1Cycle Mfd2Cycle Mfd3Cycle # pckey sends an authentic 1995 PC-keyboard hotkey (the in-cockpit # dispatcher): '+'/'-' = target zoom, 'w'..'o' = pilot select, # 'a','b','c','d','f','g','s','v','x','z' = display presets, ... @@ -76,6 +76,9 @@ key C action Valve # coolant valve cycle key H action Flush # coolant flush (HOLD -- dumps reservoir coolant) key M action ModeCycle # Basic -> Standard -> Veteran controls key N action DisplayCycle # secondary schematic: Damage -> Critical -> Heat +key J action Mfd1Cycle # lower-left MFD page: Quad -> Eng detail pages +key K action Mfd2Cycle # upper-center MFD page (engineering) +key L action Mfd3Cycle # lower-right MFD page key V action ViewToggle # cockpit <-> chase camera (dev) key B action LookBehind # HOLD = the pod's rear-view button key F5 action Generator1 diff --git a/content/CONTROLS_NUMPAD.MAP b/content/CONTROLS_NUMPAD.MAP index f8eb82c..15d1482 100644 --- a/content/CONTROLS_NUMPAD.MAP +++ b/content/CONTROLS_NUMPAD.MAP @@ -53,6 +53,9 @@ key F7 action Generator3 key F8 action Generator4 key F9 action Reconnect key F10 action Valve +key J action Mfd1Cycle # lower-left MFD page: Quad -> Eng detail pages +key K action Mfd2Cycle # upper-center MFD page (engineering) +key L action Mfd3Cycle # lower-right MFD page # ---- Upper MFD banks: number row = the top aux row, QWERTY row = the row # ---- under it, left to right across the Left / Center / Right panels. diff --git a/context/decomp-reference.md b/context/decomp-reference.md index c606347..3e0252f 100644 --- a/context/decomp-reference.md +++ b/context/decomp-reference.md @@ -283,8 +283,9 @@ default-ON (`'0'` disables). | `BT_SELF=<[pilots] tag>` | claim a specific roster seat in relay mode; absent/`auto` = the relay assigns one (SEAT_REQUEST) | | `BT_RELAY_TCP_ONLY=1` | disable the relay UDP channel (all unreliable traffic rides the TCP relay conn) | | `BT_INPUT_LOG` | binding-engine trace: CONTROLS.MAP load, button/keypad emissions, pad connect, torso twist/elev axis ([[pod-hardware]] §Desktop input remap) | -| `BT_MODE_LOG` | `[mode] display notify` — the ModeManager mask after each secondary-view swap (btl4mppr.cpp NotifyOfDisplayModeChange, Gitea #6) | +| `BT_MODE_LOG` | `[mode] display notify` / `[mode] preset (g,i)` — the ModeManager mask after each secondary-view swap (Gitea #6) or upper-MFD preset-page swap (SetPresetMode, Gitea #9) (btl4mppr.cpp) | | `BT_VIEWCYCLE_TEST=` / `BT_MODECYCLE_TEST=` | scripted verify (mech4.cpp): pulse a secondary-schematic cycle / a control-mode cycle at the frame and every 300 after — pixel-verifiable with `BT_DEV_GAUGES_DOCK`+`BT_SHOT` | +| `BT_PRESET_TEST=` | scripted verify (mech4.cpp, Gitea #9): from the frame, pulse an upper-MFD preset-page cycle on all three MFDs every 120 frames (Quad → populated Eng pages → Quad) — pixel-verifiable with `BT_DEV_GAUGES_DOCK`+`BT_SHOT` | | `BT_CTRLMAP_LOG` | streamed `.CTL` record audit (L4CTRL.cpp CreateStreamedMappings): Direct records with the resolved member offset + EVENT records (button→subsystem message; found the pod's secondary-panel Cycle buttons 0x15/0x18) | | `BT_CAM_LOG` | camera-seat/broadcast trace: streamed camera network count, director pick + Players-group census, ship follow state, PlayerLink dispatch/receive, ranking-window render ([[multiplayer]] camera seat) | | `BT_SPEC_LOG` | manual spec-audit dump at subsystem ctor: torso speed/limits, heat-sink count, reservoir capacity, per-subsystem coolant loop ([[pod-hardware]] §Spec cross-check) | diff --git a/context/gauges-hud.md b/context/gauges-hud.md index 07a414e..8845215 100644 --- a/context/gauges-hud.md +++ b/context/gauges-hud.md @@ -9,6 +9,7 @@ open_questions: - "MessageBoard LIVE 2026-07-12 (kill ticker); remaining: only strip 0 (kill) is produced -- survey the other btsmsgs.pcx strips for authentic producers" - "HUD binary attr table @005110c0 offsets conflict with the port HUD layout on 3 slots (0x1D8/0x1EC/0x1F8) -- re-dump before publishing" - "Secondary-view cycling RESOLVED 2026-07-19 (Gitea #6): the selector is the DISPLAY mode (CycleDisplayMode -> vtbl+0x4C override @4d1ae4), NOT CycleControlMode; desktop 'N' / pad RightThumb wired; pixel-verified dama->crit->heat" + - "Upper-MFD PRESET pages RESOLVED 2026-07-19 (Gitea #9): SetPresetMode table @0051dbf0 re-decoded (little-endian -> ModeMFD bits 0-14), per-MFD pod button banks identified from the .CTL dump, desktop J/K/L cycle wired; remaining: the always-active 0x28-0x2F / 0x1A-0x1D per-subsystem msg-4 records (subs 3-13 -- likely the condenser-valve/coolant buttons) unidentified" - "MP DEATHS resolved 2026-07-12 (observed-death tally + display clamp); remaining: verify multi-death tallies stay in sync across a long session (GAUGE_COMPOSITE.md)" --- @@ -105,6 +106,58 @@ BT_SHOT): ARMOR DAMAGE silhouette → CRITICAL DAMAGE subsystem list → HEAT DA mask 0x450421→0x490421→0x510421. Diags: `BT_MODE_LOG`, `BT_VIEWCYCLE_TEST=`, `BT_MODECYCLE_TEST=`. +## The upper-MFD PRESET pages — 3 MFDs × 5 pages (Gitea #9, RESOLVED 2026-07-19) [T0/T1/T2] +The three preset-able MFDs are **Mfd1 (lower left) / Mfd2 (upper center) / Mfd3 (lower right)**, +each a PAIR of bit-planes on one physical monitor: the base **Quad** plane (`Mfd1/2/3`, masks +0x0100/0x0400/0x1000, `btquad.pcx`) and the **engineering-page** plane (`Eng1/2/3`, masks +0x0200/0x0800/0x2000, `bteng.pcx`). Mode bits (BTL4MODE.HPP [T0], bits 0-14): +`ModeMFD{1,2,3}{Quad,Eng1-4}` = `1<<(group*5+item)` — **fully disjoint** from Mapping/NonMapping +(15/16), Intercom (17) and the #6 Secondary* trio (18-20); `ModeInitial` puts all three MFDs on +Quad. **What the pages show:** Quad = up to four `vehicleSubSystems` cluster mini-panels (the +quadrants, geometry table @0x51bf34); Eng item i = the FULL-SCREEN engineering detail of the +subsystem streamed onto aux screen `group*4+i` (`sub+0x1dc`; `prepEngr` screens 1-12: "SYSTEM NN" ++ per-class label cells + the cluster's eng child — GENERATOR SELECT A-D, POWER graph, COOLING +loop, DAMAGE (MJ), ammo count …). Unpopulated screens are authored-empty per mech (Blackhawk: +9 of 12 — scr 1/2/4=PPC/Streak6/ERMed, 5-8=Sensors/Myomers/ERMed/ERMed, 9/10=Streak6/PPC; +screens 3, 11, 12 empty). + +**`SetPresetMode(group,item)` @004d1b24** swaps the page: table @0051dbf0 = 15 `{clear,set}` +pairs — set = the page's ModeMFD bit, clear = the group's other four (item 0) or all five +(items 1-4). ⚠ The old reconstruction had transcribed the little-endian set column as +BIG-endian dwords (0x01→0x01000000 …), so a preset press set a garbage high bit — and for +group 1 items 3-4 / group 2 items 0-2 STOMPED the live NonMapping/Intercom/Secondary* bits — +while the page bits never moved (the "presets unwired" defect). Fixed against +section_dump.txt:72901-72908; the "does group 2 duplicate #6's secondary views?" concern is +resolved: **no** — group 2 = MFD3, bits 10-14. + +**Authentic dispatch (streamed type-19 "L4" .CTL, 121 records dumped via `BT_CTRLMAP_LOG`):** +every MFD has its own 8-button RIO bank whose meanings are MODE-MASK-gated — **Mfd1 = +buttons 0x08-0x0F (AuxLowerLeft), Mfd2 = 0x20-0x27 (AuxUpperCenter), Mfd3 = 0x00-0x07 +(AuxLowerRight)**. On a Quad page the bank's buttons DIRECT-SELECT the populated Eng pages +(mapper EventMappings → `MechRIOMapper` messages `Aux1Eng1-4`=0x4-0x7, `Aux2Eng1-4`=0x9-0xC, +`Aux3Eng1-4`=0xE-0x11 → SetPresetMode; no button is streamed for an empty screen); on an Eng +page one button returns to Quad (`Aux1/2/3Quad` = 0x3/0x8/0xD) and the rest remap to the SHOWN +subsystem (per-subsystem msgs: 0x4-0x7 = SelectGeneratorA-D, 0x8 = ToggleGeneratorMode, 0x9 = +ConfigureMappables, 0x3/0xb = unjam/eject-class functions). The `MechRIOMapper` keyboard cases +in @004d1bf0 mirror it as three key rows: `1-4`=MFD1 Eng1-4/`5`=Quad, `a s d f`/`g`, +`z x c v`/`b` — mostly claimed by the port's WASD bindings, hence dead on desktop. +**Port wiring:** the streamed records install and fire on desktop (btinput passes the LIVE +manager mask on button press, so the NUMPAD profile's 0x20-0x27 keys page MFD2 authentically); +the default WASD profile adds **J/K/L → actions Mfd1/2/3Cycle → gBTPresetCycle → +`CyclePresetModeNow(group)`** (btl4mppr.cpp; a port cycle-key shim — 24 mode-dependent pod +buttons don't fit a keyboard — that visits exactly the pod-reachable set: Quad + populated Eng +pages; the body is the authentic SetPresetMode). + +**Dev-composite change:** `BTDrawGaugeSurfaces` (L4VB16.cpp) now draws the Eng1-3 planes into +their sibling's cell and SKIPS any mono plane whose port channel is currently `BlankColor` — +honoring the mode-driven `reconfigure` (RemapGraphicsPort) so each dev cell shows the ACTIVE +page, like the pod monitor. This SUPERSEDED the 2026-07-12 "frozen-dial" scaffold +(GAUGREND.cpp force-activated all 15 page bits under BT_DEV_GAUGES — removed; it made every eng +screen paint over the shared Eng plane, pinning it on the highest screen). Pixel-verified live +(BT_PRESET_TEST): all three MFDs page Quad → eng details → back to Quad in lockstep with the +[mode] mask log; N/M un-regressed. Diags: `BT_MODE_LOG` ([mode] preset lines), +`BT_PRESET_TEST=`, `BT_CTRLMAP_LOG`. + ## pilotList (Comm KILLS/DEATHS) row semantics + the −1 [T1/T2] One ROW PER PILOT in the mission (2-player MP = 2 rows — not duplicate displays). KILLS = `killCount` (the victim's ScoreMessageHandler credits the shooter cross-player, works for both diff --git a/context/pod-hardware.md b/context/pod-hardware.md index 98117d6..f417963 100644 --- a/context/pod-hardware.md +++ b/context/pod-hardware.md @@ -27,8 +27,8 @@ the `MechControlsMapper` interprets them ([[locomotion]]). [T2] ### The button space + lamps [T0, L4CTRL.h enum] buttonGroup addresses 0x00-0x47: `AuxLowerRight/Left 1-8` (0x00-0x0F), `Secondary1-12` (0x10-0x1B), -`AuxUpperCenter/Left/Right 1-8` (0x20-0x37 — the preset/eng "display eng data" banks; **0x30-0x37 = -the target HOTBOX**, pilot select), icom/door 0x39-0x3C, `Panic` 0x3D (the config-mode lamp), +`AuxUpperCenter/Left/Right 1-8` (0x20-0x37; **0x30-0x37 = the target HOTBOX**, pilot select), +icom/door 0x39-0x3C, `Panic` 0x3D (the config-mode lamp), `Throttle1` 0x3F (throttle-head = REVERSE THRUST), joystick cluster 0x40-0x47 (Main trigger 0x40, hat 0x41-0x44, Pinky/ThumbLow/ThumbHigh 0x45-0x47 — the four MAPPABLE fire buttons). "Lamp buttons" are literal: physical illuminated pushbuttons in panels around the screens; the RIO `LampRequest` @@ -37,6 +37,16 @@ per-lamp names). A lit button = active. Keypads are NOT buttons: `keyboardGroup[ KeyboardExternal]` carry key VALUES ('0'-'9','A'-'F', L4CTRL.cpp:2526). RIO event convention: press = `buttonGroup[a].Update(a+1, modeMask)` (mask saved), release = `Update(-a-1, savedMask)` (L4CTRL.cpp:2470-2520). +**Bank→MFD assignment (Gitea #9, from the streamed "L4" .CTL EventMapping dump [T1]):** each +preset-able MFD owns the 8-button bank AROUND it, mode-mask-gated per page — **Mfd1 (lower +left) = 0x08-0x0F (AuxLowerLeft), Mfd2 (upper center) = 0x20-0x27 (AuxUpperCenter), Mfd3 +(lower right) = 0x00-0x07 (AuxLowerRight)**: Quad page → the outer buttons direct-select the +populated Eng pages; Eng page → one button back to Quad, the rest drive the shown subsystem +(generator select A-D, gen mode, weapon config/unjam). Always-active: **0x28-0x2F +(AuxUpperLeft) → per-subsystem msg 0x4 to roster subs 3-9** and secondary-panel 0x1A-0x1D → +subs 10-13 msg 0x4 (identities unverified — likely the condenser-valve / coolant buttons +[T4]); 0x10/0x11 = map ZoomIn/Out, 0x15 = CycleDisplayMode, 0x18 = CycleControlMode (#6). +Full decode: [[gauges-hud]] §preset pages. ### Desktop input remap — CONTROLS.MAP + XInput (2026-07-18) [T2 live] `game/reconstructed/btinput.cpp` + `content/CONTROLS.MAP` (WASD-classic default, compiled-in twin; diff --git a/engine/MUNGA/GAUGREND.cpp b/engine/MUNGA/GAUGREND.cpp index 61978fe..1c2f6af 100644 --- a/engine/MUNGA/GAUGREND.cpp +++ b/engine/MUNGA/GAUGREND.cpp @@ -3677,22 +3677,16 @@ void Check(application->GetModeManager()); ModeMask current_mode_mask = application->GetModeManager()->GetModeMask(); - // DEV-COMPOSITE (2026-07-12, the frozen-dial fix): page-gated gauges (mode - // = their MFD page bit) freeze when their page isn't mode-active -- on the - // pod you FLIP pages, but the dev window shows every page surface at once, - // so six of seven weapon dials sat frozen on screen (user-hit; the arcs - // executed exactly 8 times at startup then never again). Under - // BT_DEV_GAUGES force the 15 MFD page-plane bits (BTL4 mode bits 0..14 = - // MFD1/2/3 Quad+Eng1-4) active so every visible page runs. The secondary - // trio (bits 18-20) stays authentic -- those views SHARE pixels and are - // exclusive by design. - { - static int s_devAllPages = -1; - if (s_devAllPages < 0) - s_devAllPages = (getenv("BT_DEV_GAUGES") != NULL) ? 1 : 0; - if (s_devAllPages) - current_mode_mask |= (ModeMask)0x7FFF; - } + // DEV-COMPOSITE history: a 2026-07-12 scaffold ("the frozen-dial fix") + // forced the 15 MFD page-plane bits (BTL4 mode bits 0..14 = MFD1/2/3 + // Quad+Eng1-4) active under BT_DEV_GAUGES, because the dev panel then drew + // every page plane at once and page-gated dials froze. REMOVED for Gitea + // #9 (2026-07-19): the dev composite now honors the mode-driven + // `reconfigure` (a BlankColor plane is skipped -- L4VB16.cpp + // BTDrawGaugeSurfaces), so exactly the ACTIVE page of each MFD is shown, + // like the pod -- and the working SetPresetMode page switching NEEDS the + // authentic gating (the force made every eng screen paint over the shared + // Eng plane, pinning it on the highest screen and ignoring page flips). ModeMask previous_mode_mask = application->GetModeManager()->GetPreviousModeMask(); ModeMask diff --git a/engine/MUNGA_L4/L4VB16.cpp b/engine/MUNGA_L4/L4VB16.cpp index ecec317..aebb191 100644 --- a/engine/MUNGA_L4/L4VB16.cpp +++ b/engine/MUNGA_L4/L4VB16.cpp @@ -167,7 +167,7 @@ void SVGA16::DrawDevSurface(LPDIRECT3DDEVICE9 device, int slot, int mask, int pa { int w = pixelBuffer.Data.Size.x; // 640 int h = pixelBuffer.Data.Size.y; // 480 - if (device == NULL || slot < 0 || slot >= 8 || w <= 0 || h <= 0) return; + if (device == NULL || slot < 0 || slot >= 10 || w <= 0 || h <= 0) return; // Lazily create this surface's texture on the MAIN device (MANAGED -> lockable + // survives device reset). @@ -289,7 +289,13 @@ struct BTGaugeSurfaceDesc // buffer, so past ~1.5x you magnify, not sharpen. static const float kBTPanelW = 1320.0f; static const float kBTPanelH = 480.0f; -static const BTGaugeSurfaceDesc kBTGaugeSurfaces[6] = +// Gitea #9: each preset-able MFD is TWO bit-planes sharing one physical +// monitor -- the base Quad plane (Mfd1/2/3) and the engineering-page plane +// (Eng1/2/3). The mode-driven `reconfigure` swaps which of the pair is +// channel-enabled and which is BlankColor; the draw loop below skips the +// blanked plane, so the dev cell shows exactly what the pod monitor shows. +// The Eng entries share their sibling's cell rect and FOLLOW it in the list. +static const BTGaugeSurfaceDesc kBTGaugeSurfaces[9] = { { "Heat", 0xFFFF, 0.0f / kBTPanelW, 0.0f, 320.0f / kBTPanelW, 0.5f, 0 }, { "Mfd2", 0xFFFF, 320.0f / kBTPanelW, 0.0f, 320.0f / kBTPanelW, 0.5f, 0 }, @@ -297,6 +303,9 @@ static const BTGaugeSurfaceDesc kBTGaugeSurfaces[6] = { "Mfd3", 0xFFFF, 320.0f / kBTPanelW, 0.5f, 320.0f / kBTPanelW, 0.5f, 0 }, { "sec", -1, 640.0f / kBTPanelW, 0.0f, 360.0f / kBTPanelW, 1.0f, 1 }, { "Comm", 0xFFFF, 1000.0f / kBTPanelW, 0.0f, 320.0f / kBTPanelW, 0.5f, 0 }, + { "Eng2", 0xFFFF, 320.0f / kBTPanelW, 0.0f, 320.0f / kBTPanelW, 0.5f, 0 }, + { "Eng1", 0xFFFF, 0.0f / kBTPanelW, 0.5f, 320.0f / kBTPanelW, 0.5f, 0 }, + { "Eng3", 0xFFFF, 320.0f / kBTPanelW, 0.5f, 320.0f / kBTPanelW, 0.5f, 0 }, }; // @@ -345,11 +354,17 @@ void BTDrawGaugeSurfaces(LPDIRECT3DDEVICE9 device, float px, float py, float pw, } if (svga == NULL) return; - for (int i = 0; i < 6; i++) + for (int i = 0; i < 9; i++) { const BTGaugeSurfaceDesc &d = kBTGaugeSurfaces[i]; L4GraphicsPort *port = static_cast(gr->GetGraphicsPort(d.portName)); if (port == NULL) continue; // this surface's port isn't configured -> skip + // Gitea #9: honor the live mode-driven `reconfigure` -- a mono plane + // whose channel is currently BlankColor contributes nothing on the pod + // monitor (its palette bits translate to black), so skip it; its + // non-blank sibling (Mfd or Eng, same cell rect) draws instead. + if (d.monoTint >= 0 && port->GetEnableID() == L4GraphicsPort::BlankColor) + continue; // Secondary-CRT unrotation direction is env-flippable (BT_GAUGE_SEC_ROT=3 // for the other way) in case the assumed pod mounting is mirrored. int rot = d.rotateCCW; @@ -4309,7 +4324,7 @@ SVGA16::SVGA16( ):Video16BitBuffered(init_width, init_height) { BuildWindows(init_width,init_height,windowed, secondaryIndex, aux1Index, aux2Index); - for (int _i = 0; _i < 8; _i++) // DEV-COMPOSITE: lazily created on first surface draw + for (int _i = 0; _i < 10; _i++) // DEV-COMPOSITE: lazily created on first surface draw mDevSurfaceTex[_i] = NULL; //STUBBED: VIDEO RB 1/15/07 # if defined(DEBUG) diff --git a/engine/MUNGA_L4/L4VB16.h b/engine/MUNGA_L4/L4VB16.h index 140c7e6..55f56c2 100644 --- a/engine/MUNGA_L4/L4VB16.h +++ b/engine/MUNGA_L4/L4VB16.h @@ -292,8 +292,9 @@ private: int mDisplayToUpdate; - LPDIRECT3DTEXTURE9 mDevSurfaceTex[8]; // DEV-COMPOSITE: one texture per gauge surface - // (sec/radar + the 5 MFD bit-planes), on the MAIN device. + LPDIRECT3DTEXTURE9 mDevSurfaceTex[10]; // DEV-COMPOSITE: one texture per gauge surface + // (sec/radar + the 5 MFD bit-planes + the Eng1-3 + // preset-page planes, Gitea #9), on the MAIN device. public: // DEV-COMPOSITE: extract ONE gauge surface from the shared pixelBuffer into // mDevSurfaceTex[slot] on the given (main) device + draw it as a quad at (dstX,dstY, @@ -462,6 +463,15 @@ public: Check(this); return bitMask; } + // DEV-COMPOSITE (Gitea #9): the dock honors the live reconfigure state -- + // a plane whose channel is currently BlankColor contributes nothing on the + // pod's shared-word palette LUT, so the dev panel skips drawing it. + L4GraphicsPort::ChannelEnableID + GetEnableID() + { + Check(this); + return channelEnable; + } SVGA16::PaletteID paletteID; diff --git a/game/reconstructed/CLASSMAP.md b/game/reconstructed/CLASSMAP.md index 6f1033b..a589996 100644 --- a/game/reconstructed/CLASSMAP.md +++ b/game/reconstructed/CLASSMAP.md @@ -151,7 +151,9 @@ Subsystem (MUNGA base — have source: RP/MUNGA/SUBSYSTM.HPP) │ control mode never touches the secondary view). NotifyOfDisplayModeChange-override@4d1ae4 (vtbl+0x4C; ex-"SetControlMode"; Gitea #6 reconstructed │ 2026-07-19 = THE secondary Damage/Critical/Heat selector, mode bits18-20, table @0051dbe4={0x40000,0x80000,0x100000}; pod input = streamed │ EventMapping secondary-panel button 0x15 -> msg 0x15 CycleDisplayMode; button 0x18 -> msg 0x14 CycleControlMode). -│ SetPresetMode@4d1b24 (group*5+item -> {clear,set} mask table @0051dbf0[15]). +│ SetPresetMode@4d1b24 (group*5+item -> {clear,set} mask table @0051dbf0[15]; Gitea #9 decode: set = ModeMFD{1,2,3}{Quad,Eng1-4} +│ = 1<<(group*5+item), bits 0-14 [section_dump:72901-72908 little-endian]; clear = the group's other/all page bits. +│ Group = the MFD (0/1/2 = lower-left/upper-center/lower-right), item 0 = Quad overview, 1-4 = eng-detail pages). │ EnterConfiguration@4d1840 (+0x38) / ExitConfiguration@4d18dc (+0x3C) (task #6 relabel (buttons 0x40..0x47, hat-skip 0x41->0x45, │ mode 0x8000=ConfigReady). AddOrErase evt/dir RET no-ops @4d195c/@4d1964 (+0x40/+0x44; TM can't regroup). KeypressMessageHandler@4d1bf0 │ (shared dispatcher: zoom/preset/pilot-cycle/look keys + fake-button dev keys; default -> @4d141c). diff --git a/game/reconstructed/btinput.cpp b/game/reconstructed/btinput.cpp index cf3b3ae..f2af152 100644 --- a/game/reconstructed/btinput.cpp +++ b/game/reconstructed/btinput.cpp @@ -79,6 +79,9 @@ enum BTActionID BTActGenerator3, BTActGenerator4, BTActReconnect, + BTActMfd1Cycle, // Gitea #9: cycle the lower-left MFD preset page + BTActMfd2Cycle, // Gitea #9: cycle the upper-center MFD preset page + BTActMfd3Cycle, // Gitea #9: cycle the lower-right MFD preset page BTActCount }; @@ -199,6 +202,8 @@ static const BTName sActionNames[] = {"Generator1", BTActGenerator1}, {"Generator2", BTActGenerator2}, {"Generator3", BTActGenerator3}, {"Generator4", BTActGenerator4}, {"Reconnect", BTActReconnect}, + {"Mfd1Cycle", BTActMfd1Cycle}, {"Mfd2Cycle", BTActMfd2Cycle}, + {"Mfd3Cycle", BTActMfd3Cycle}, {0, 0} }; @@ -269,6 +274,9 @@ static const char *sDefaultProfile = "key H action Flush\n" "key M action ModeCycle\n" "key N action DisplayCycle\n" + "key J action Mfd1Cycle\n" + "key K action Mfd2Cycle\n" + "key L action Mfd3Cycle\n" "key V action ViewToggle\n" "key B action LookBehind\n" "key F5 action Generator1\n" @@ -971,6 +979,9 @@ void case BTActValve: next.valve = 1; break; case BTActFlush: next.flush = 1; break; case BTActConfigHold: next.configHold = 1; break; + case BTActMfd1Cycle: next.mfdCycle[0] = 1; break; + case BTActMfd2Cycle: next.mfdCycle[1] = 1; break; + case BTActMfd3Cycle: next.mfdCycle[2] = 1; break; case BTActGenerator1: next.genSel = 4; break; case BTActGenerator2: next.genSel = 5; break; case BTActGenerator3: next.genSel = 6; break; diff --git a/game/reconstructed/btinput.hpp b/game/reconstructed/btinput.hpp index d22517d..11be1a6 100644 --- a/game/reconstructed/btinput.hpp +++ b/game/reconstructed/btinput.hpp @@ -56,6 +56,9 @@ struct BTInputState int flush; // Gitea #7: coolant flush HELD (InjectCoolant) int configHold; int genSel; // 0 = none, 4..7 = Generator A..D, 8 = reconnect + // Gitea #9: per-MFD preset-page cycle (desktop senders for SetPresetMode; + // index 0/1/2 = Mfd1 lower-left / Mfd2 upper-center / Mfd3 lower-right) + int mfdCycle[3]; }; extern BTInputState gBTInput; diff --git a/game/reconstructed/btl4mppr.cpp b/game/reconstructed/btl4mppr.cpp index 61d37ab..bec20b0 100644 --- a/game/reconstructed/btl4mppr.cpp +++ b/game/reconstructed/btl4mppr.cpp @@ -86,6 +86,8 @@ // DAT_0051dbe4[3] = {0x40000,0x80000,0x100000} secondary-VIEW mode-masks // (ModeSecondaryDamage/Critical/Heat, indexed by displayMode) // DAT_0051dbf0[15] = {clear,set} mode-mask pairs (group*5+item), preset select +// (little-endian: set = ModeMFD{1,2,3}{Quad,Eng1-4} bits 0-14 +// -- Gitea #9; see SetPresetMode below) // DAT_0051dcd0[8] = {0x37,0x36,0x35,0x34,0x33,0x32,0x31,0x30} hotbox buttons // // ModeManager (app+0x50): +0x4 currentMode mask, +0x8 savedMode mask. @@ -425,6 +427,23 @@ L4MechControlsMapper::MessageHandlerSet& } } + // + // (3b) PORT (Gitea #9): the desktop J/K/L per-MFD preset-page cycle + // edges (mech4 poll) -- the sender for the pod's per-MFD page-button + // banks; the body is the authentic SetPresetMode. + // + { + extern int gBTPresetCycle[3]; + for (int mfd_group = 0; mfd_group < 3; ++mfd_group) + { + if (gBTPresetCycle[mfd_group]) + { + gBTPresetCycle[mfd_group] = 0; + CyclePresetModeNow(mfd_group); + } + } + } + // // (4) Target-range zoom: slew current exponent toward demand, then set // the Mech's target range = 250 * 2^exponent. @@ -547,9 +566,24 @@ L4MechControlsMapper::MessageHandlerSet& // SetPresetMode @004d1b24 //############################################################################# // -// Each (group,item) selects a preset; the table @0051dbf0 holds {clear,set} -// mode-mask pairs that remove the previous preset of that group and enable the -// chosen one. +// Each (group,item) selects an upper-MFD PRESET page; the table @0051dbf0 +// holds {clear,set} mode-mask pairs that remove the previous preset of that +// group and enable the chosen one. Group = the MFD (0/1/2 = Mfd1 lower-left / +// Mfd2 upper-center / Mfd3 lower-right); item 0 = the Quad overview page +// (four subsystem cluster panels), items 1-4 = the four full-screen +// engineering-detail pages (aux screens group*4+1..group*4+4, prepEngr). +// +// Gitea #9 TABLE FIX (2026-07-19): the set column had been transcribed from +// the section dump as BIG-endian dwords ({0x1e,0x01000000} instead of +// {0x1e,0x01}, etc.) -- so a preset press set a garbage high bit (and, for +// group 1 items 3-4 / group 2 items 0-2, stomped the LIVE ModeNonMapping / +// ModeIntercom / ModeSecondary* bits) while the ModeMFD* page bits never +// moved: the panels stayed pinned on their Quad pages. Ground truth +// (section_dump.txt:72901-72908, little-endian) decodes the table to exactly +// the BTL4MODE.HPP [T0] ModeMFD{1,2,3}{Quad,Eng1-4} allocation, bits 0-14: +// set = 1 << (group*5 + item); clear = the group's other four page bits +// (item 0) or all five (items 1-4). The presets are fully disjoint from the +// Mapping/NonMapping (15/16), Intercom (17) and Secondary* (18-20) bits. // void L4MechControlsMapper::SetPresetMode(int group, int item) @@ -557,14 +591,31 @@ L4MechControlsMapper::MessageHandlerSet& struct PresetMaskPair { ModeMask clear, set; }; static const PresetMaskPair preset_mask[15] = // @0051dbf0 (group*5+item) { - {0x1e, 0x01000000}, {0x1f, 0x02000000}, {0x1f, 0x04000000}, - {0x1f, 0x08000000}, {0x1f, 0x10000000}, - {0x3c0, 0x20000000}, {0x3e0, 0x40000000}, {0x3e0, 0x80000000}, - {0x3e0, 0x00010000}, {0x3e0, 0x00020000}, - {0x7800,0x00040000}, {0x7c00,0x00080000}, {0x7c00,0x00100000}, - {0x7c00,0x00200000}, {0x7c00,0x00400000} + // group 0 = Mfd1 (lower left) + {0x001e, BTL4ModeManager::ModeMFD1Quad}, // 0x0001 + {0x001f, BTL4ModeManager::ModeMFD1Eng1}, // 0x0002 + {0x001f, BTL4ModeManager::ModeMFD1Eng2}, // 0x0004 + {0x001f, BTL4ModeManager::ModeMFD1Eng3}, // 0x0008 + {0x001f, BTL4ModeManager::ModeMFD1Eng4}, // 0x0010 + // group 1 = Mfd2 (upper center) + {0x03c0, BTL4ModeManager::ModeMFD2Quad}, // 0x0020 + {0x03e0, BTL4ModeManager::ModeMFD2Eng1}, // 0x0040 + {0x03e0, BTL4ModeManager::ModeMFD2Eng2}, // 0x0080 + {0x03e0, BTL4ModeManager::ModeMFD2Eng3}, // 0x0100 + {0x03e0, BTL4ModeManager::ModeMFD2Eng4}, // 0x0200 + // group 2 = Mfd3 (lower right) + {0x7800, BTL4ModeManager::ModeMFD3Quad}, // 0x0400 + {0x7c00, BTL4ModeManager::ModeMFD3Eng1}, // 0x0800 + {0x7c00, BTL4ModeManager::ModeMFD3Eng2}, // 0x1000 + {0x7c00, BTL4ModeManager::ModeMFD3Eng3}, // 0x2000 + {0x7c00, BTL4ModeManager::ModeMFD3Eng4} // 0x4000 }; + if (group < 0 || group > 2 || item < 0 || item > 4) + { + return; + } + int index = group * 5 + item; BTL4ModeManager *mode_manager = @@ -572,6 +623,110 @@ L4MechControlsMapper::MessageHandlerSet& mode_manager->RemoveModeMask(preset_mask[index].clear); mode_manager->AddModeMask(preset_mask[index].set); + + // DIAG (BT_MODE_LOG): prove the ModeMFD* page bit actually flipped for + // the gauge reconfigure/prepEngr layer gating. + if (getenv("BT_MODE_LOG")) + DEBUG_STREAM << "[mode] preset (" << group << "," << item + << ") -> manager mask 0x" << std::hex + << mode_manager->GetModeMask() << std::dec << "\n" << std::flush; + } + +// +//############################################################################# +// CyclePresetModeNow (PORT desktop sender -- Gitea #9, no binary analog) +//############################################################################# +// +// On the pod every MFD has its own 8-button bank (Mfd1 lower-left = RIO +// buttons 0x08-0x0F, Mfd2 upper-center = 0x20-0x27, Mfd3 lower-right = +// 0x00-0x07) whose meanings are MODE-MASK-gated by the streamed "L4" .CTL +// EventMappings: on a Quad page the bank's outer buttons DIRECT-SELECT the +// populated Eng pages (mapper messages Aux1Eng1..Aux3Eng4 = 0x4-0x7 / 0x9-0xC +// / 0xE-0x11 -> SetPresetMode), on an Eng page one button returns to Quad +// (Aux1Quad/Aux2Quad/Aux3Quad = 0x3/0x8/0xD) and the rest drive the shown +// subsystem. A desktop keyboard cannot host 24 mode-dependent buttons, so +// the port cycles each MFD with one key (J/K/L -> mech4 edge poll -> +// gBTPresetCycle -> here); the message BODY is the authentic SetPresetMode. +// The cycle visits exactly the pages the pod's streamed buttons could reach: +// Quad always, an Eng page only if a roster subsystem is assigned to that +// aux screen (screens group*4+1 .. group*4+4; the shipped .CTL carries NO +// select button for an unpopulated screen -- e.g. the Blackhawk's screens +// 3, 11 and 12 are authored empty). +// + // The vehicleSubSystems/prepEngr aux-screen bridge (powersub.cpp). + extern bool BTGetSubsystemAuxScreen( + Subsystem *sub, int *screen, int *placement, char *label64); + + void + L4MechControlsMapper::CyclePresetModeNow(int group) + { + if (group < 0 || group > 2) + { + return; + } + + BTL4ModeManager *mode_manager = + (BTL4ModeManager*)application->GetModeManager(); + + // + // Current page = the set bit among the group's five ModeMFD* bits. + // + ModeMask mask = mode_manager->GetModeMask(); + int current = 0; // default: Quad + for (int i = 0; i < 5; ++i) + { + if (mask & (1 << (group * 5 + i))) + { + current = i; + break; + } + } + + // + // Which aux screens are populated on this mech (streamed sub+0x1dc)? + // + Mech *mech = GetMech(); + bool populated[5] = { true, false, false, false, false }; // Quad always + if (mech != NULL) + { + int count = mech->GetSubsystemCount(); + for (int i = 1; i < count; ++i) + { + Subsystem *sub = mech->GetSubsystem(i); + if (sub == NULL) + { + continue; + } + int screen = 0, placement = -1; + if (!BTGetSubsystemAuxScreen(sub, &screen, &placement, NULL)) + { + continue; + } + int item = screen - group * 4; // Eng item 1..4 of THIS group? + if (item >= 1 && item <= 4) + { + populated[item] = true; + } + } + } + + // + // Advance to the next reachable page (wraps through Quad). + // + int next = current; + for (int step = 1; step <= 5; ++step) + { + int candidate = (current + step) % 5; + if (populated[candidate]) + { + next = candidate; + break; + } + } + if (next != current) + { + SetPresetMode(group, next); + } } // diff --git a/game/reconstructed/btl4mppr.hpp b/game/reconstructed/btl4mppr.hpp index e4aea7b..5a12fef 100644 --- a/game/reconstructed/btl4mppr.hpp +++ b/game/reconstructed/btl4mppr.hpp @@ -127,6 +127,12 @@ NotifyOfDisplayModeChange(int new_mode); // @004d1ae4 (+0x4C, secondary-view mask swap) void SetPresetMode(int group, int item); // @004d1b24 (preset mode-mask) + void + CyclePresetModeNow(int group); // PORT (Gitea #9): desktop J/K/L + // per-MFD page cycle; visits Quad + + // the POPULATED Eng pages only (the + // reachable set of the pod's streamed + // page buttons); body = SetPresetMode //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Configuration support (overrides the MechControlsMapper traps; task #6 diff --git a/game/reconstructed/mech4.cpp b/game/reconstructed/mech4.cpp index 68adbb2..2c19f29 100644 --- a/game/reconstructed/mech4.cpp +++ b/game/reconstructed/mech4.cpp @@ -653,6 +653,7 @@ static int gBTMissileKey = 0; static int gBTPinkyKey = 0; // key '4' = the pod's 4th fire button (Pinky 0x45) int gBTModeCycle = 0; // 'M' edge: cycle the control mode (mapper consumes) int gBTDisplayCycle = 0; // 'N' edge: cycle the secondary schematic (Gitea #6, mapper consumes) +int gBTPresetCycle[3] = {0,0,0}; // J/K/L edges: cycle an upper-MFD preset page (Gitea #9, L4 mapper consumes) int gBTLookBehind = 0; // 'V' held: the pod's rear-view button (task #68) float gBTTwistAxis = 0.0f; // Q/E torso-twist deflection (assisted-mode stick X) float gBTElevAxis = 0.0f; // R/F torso-elevation (pitch aim, stick Y) @@ -2680,6 +2681,19 @@ void if (nNow && !sPrevN) gBTDisplayCycle = 1; sPrevN = nNow; } + // Gitea #9: J/K/L cycle an upper-MFD preset page + // (Quad -> populated Eng pages -> Quad) -- the desktop + // sender for the pod's per-MFD page-button banks + // (SetPresetMode, consumed by the L4 mapper). + { + static int sPrevMfd[3] = {0,0,0}; + for (int mg = 0; mg < 3; ++mg) + { + const int gNow = gBTInput.mfdCycle[mg]; + if (gNow && !sPrevMfd[mg]) gBTPresetCycle[mg] = 1; + sPrevMfd[mg] = gNow; + } + } // (task #68) LookBehind action HELD = the pod's rear-view // button (releases back to the forward view). Rear-mounted // weapons (blackhawk/owens back racks) fire only in it. @@ -5191,6 +5205,26 @@ void } } + // Gitea #9 scripted verify (BT_PRESET_TEST=): from the given + // frame, pulse a preset-page cycle on ALL THREE upper MFDs every 120 + // frames (Quad -> populated Eng pages -> Quad, per MFD) -- with + // BT_DEV_GAUGES_DOCK + BT_SHOT the Mfd1/Mfd2/Mfd3 page swaps are + // pixel-verifiable ([mode] preset log lines under BT_MODE_LOG). + if ((Entity *)this == application->GetViewpointEntity() + && getenv("BT_PRESET_TEST")) + { + static int s_ptFrame = 0; + ++s_ptFrame; + int t0 = atoi(getenv("BT_PRESET_TEST")); + if (t0 < 1) t0 = 600; + if (s_ptFrame >= t0 && (s_ptFrame - t0) % 120 == 0) + { + gBTPresetCycle[0] = 1; + gBTPresetCycle[1] = 1; + gBTPresetCycle[2] = 1; + } + } + // Gitea #7 scripted verify (BT_FLUSH_TEST=): hold the coolant // flush for ~60 sim frames starting at the given frame -- the // reservoir drains ([flush] log), the coolant vertBar drops, and the