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
+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);
}
}
//