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=<frame>.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
arcattack
2026-07-19 17:30:35 -05:00
co-authored by Claude Fable 5
parent 1d6339b226
commit 6783619069
14 changed files with 336 additions and 36 deletions
+4 -1
View File
@@ -32,7 +32,7 @@
# <src> 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
+3
View File
@@ -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.
+2 -1
View File
@@ -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=<frame>` / `BT_MODECYCLE_TEST=<frame>` | 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=<frame>` | 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) |
+53
View File
@@ -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=<frame>`,
`BT_MODECYCLE_TEST=<frame>`.
## 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=<frame>`, `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
+12 -2
View File
@@ -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;
+10 -16
View File
@@ -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
+19 -4
View File
@@ -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<L4GraphicsPort*>(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<n> or Eng<n>, 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)
+12 -2
View File
@@ -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;
+3 -1
View File
@@ -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).
+11
View File
@@ -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;
+3
View File
@@ -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;
+164 -9
View File
@@ -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);
}
}
//
+6
View File
@@ -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
+34
View File
@@ -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=<frame>): 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=<frame>): hold the coolant
// flush for ~60 sim frames starting at the given frame -- the
// reservoir drains ([flush] log), the coolant vertBar drops, and the