Files
BT411/engine/MUNGA_L4/l4riobank.h
T
CydandClaude Opus 5 61563c9efe Glass cockpit refit: one mode resolver, one button geometry, and a cockpit that scales
Replicates the RP412 cockpit line into BT411's own architecture -- porting the
geometry and keeping our renderers, so BT_SHOT single-frame verification stays
intact.

MODE RESOLVER.  Where the secondary displays go was decided in TWO places with
duplicated precedence, and the boot banner read NEITHER -- it announced
"per-display cockpit windows" for every glass boot, surround included.  The
split had also broken BT_COCKPIT=0: documented as the dock-bottom opt-out, the
profile block converted it to BT_GLASS_PANELS=1, so the docked strip was
UNREACHABLE under the glass profile.  One resolver now, consumed by the banner,
the pad-panel decision and the window sizing; BT_GLASS_PANELS is explicit-only;
dock/window modes auto-raise BT_PAD_PANEL so the button field always has a home.

L4RIOBANK -- one button geometry.  Both renderers carried their own copy and had
drifted: an MFD button was 156x138 reaching under the glass in the exploded
window and a 76x24 sliver entirely OUTSIDE the glass in the surround.  One
module owns it now, both are consumers, placement stays per-renderer.  The pod's
under-glass rule (RP412 L4MFDVIEW): reach half the glass in behind the display,
leave a lamp strip clearing the edge, paint buttons first and imagery over --
so the lamp reads as a bar and practically the whole display is the press
target.  The strip scales off the display's SHORT axis (the map is portrait)
with a readable floor.  Retired L4GLASSWIN's three local placers and seven
layout constants.

LAMP FLASH DECODE was wrong [T1].  BTLampBrightnessOf returned max(state1,
state2) and blanked on the alternate phase; RIO::LampState (L4RIO.h [T0]) says
solid shows state 1 and flashing ALTERNATES the two.  Agrees only when one state
is Off -- true for the Panic lamp, which is why it survived -- but L4LAMP.cpp:252
commands flashFast + state1Dim + state2Bright, a dim->bright pulse that rendered
as a hard bright->off blink.  Three copies existed (l4vb16.h, L4GLASSWIN,
L4PADPANEL), all three wrong; the two locals now forward to the one fixed inline.

THE COCKPIT SCALES.  The fixed canvas was stretched into whatever the client
area was, so a window dragged to a different shape squashed the instruments (the
projection was aspect-corrected in task #20; the panels never were).  Now one
uniform scale, centred, leftover black.  D3D9 applies it as a Present
destination rect, which DISCARD forbids -- so the WINDOWED swap effect becomes
COPY when the surround is up and multisampling is off.  The click mapping had to
follow (mapping against the full client drifts the hit test off every button by
the bar width), as did the world aspect (under a uniform scale it is the view
rect's own).  -fit / -windowed-fullscreen: borderless over the monitor.

 - ordering trap: the first WM_SIZE beats the device, so a -fit boot logged
   aspect=3.14 and applied it on frame 1.  The letterbox INTENT is decided in
   btl4main; L4VIDEO only confirms or withdraws it.

PLAYER-TUNABLE DISPLAYS.  BT_MFD_SCALE (+ _UL/_UC/_UR/_LL/_LR), BT_RADAR_SCALE,
BT_RADAR_POS (CENTER/LEFT/RIGHT/MIDLEFT/MIDRIGHT).  The surround BANDS derive
from the resolved sizes -- that is why the sizes could not stay constants: the
band a display hangs in has to grow with it or the canvas clips it.  100%
reproduces the historical L276 R276 T223 B336 exactly.  A corner map goes flush
to the CANVAS edge and the lower MFD slides beside it (measuring off the view
edge overlapped them by 232px).

MAP LEGEND GRID -- measured, not inherited.  scratchpad/measurelegend.py over a
native capture: top 3, cell 102, pitch 107 of 640.  RP412's map is 13 + 6x102 @
105 -- same cell height, different top and pitch, so its numbers do NOT
transfer.  Our old even division had the pitch right by luck and sat 3px high of
the labels.

environ.ini.  It was read ~300 lines into WinMain, AFTER the platform-profile
block had run its getenv()s -- so every setting the profile reads was silently
ignored FROM THE FILE and only worked as a real env var.  It also putenv()'d
comments verbatim.  Now loaded immediately after the first-breath line, comments
skipped, the real environment WINS over the file, and a fully documented default
is written on first run (the bindings.txt convention: untracked, so
extract-over-top never clobbers a player's settings).

VERIFICATION HARNESS (new, reusable): BT_RIOBANK_LOG=1 dumps every bank;
checkbank.py proves no address is SHADOWED (an address whose rect is covered by
earlier buttons is dead however big it looks -- the overlapping under-glass banks
make that a live hazard); clickbank.py posts a real click at every button centre.

Verified: 72/72 placed, 0 shadowed, 72/72 dispatched in BOTH modes, after a
resize, at 150%/135%, and at 75%+BOTTOMRIGHT; wide/tall drags and -fit
undistorted on a 3440x1440; exploded/dock/pod/dev un-regressed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-26 02:23:40 -05:00

150 lines
6.9 KiB
C

//===========================================================================//
// File: l4riobank.h //
// Project: MUNGA_L4 Brick: cockpit RIO button-bank geometry //
// Contents: the ONE layout rule for the buttons around a pod display //
//---------------------------------------------------------------------------//
// Copyright (C) 1994-1995, Virtual World Entertainment, Inc. //
// PROPRIETARY AND CONFIDENTIAL //
//===========================================================================//
#ifndef _L4RIOBANK_H_
#define _L4RIOBANK_H_
//###########################################################################
// Every pod secondary display carries a bank of illuminated RIO pushbuttons
// mounted around its glass. We render that field TWICE -- once composited
// into the main window (the COCKPIT SURROUND, L4VB16.cpp) and once per
// display in its own desktop window (the EXPLODED view, L4GLASSWIN.cpp) --
// and until 2026-07-26 each renderer carried its own copy of the geometry.
// They drifted: the exploded windows put the buttons UNDER the glass (a big
// click target, only a thin lamp strip clearing the edge) while the surround
// left the MFD lamps entirely outside the surface, so the same button was a
// 156x138 target in one mode and a 76x24 sliver in the other.
//
// This is that geometry, once. A caller supplies a display rect and the
// bank's anchor address(es); it gets back the button rects IN THAT SAME
// SPACE plus the bounding box the bank needs. Placement -- WHERE each
// display sits -- stays with the renderer, because that genuinely differs
// (one composited canvas vs seven desktop windows).
//
// THE UNDER-GLASS RULE (ported from RP412 L4MFDVIEW, which took it from the
// pod): a button reaches half the display in BEHIND the glass and only
// `strip` clears the edge. The renderer draws the buttons FIRST and the
// display imagery OVER them, so the lamp reads as a slim bar while
// practically the whole display is the press target. Nothing is occluded --
// the picture wins every pixel it covers.
//
// Addresses per [[pod-hardware]] "The button space + lamps"; the anchors are
// the caller's -- this file never hardcodes a bank address.
//###########################################################################
enum BTRioBankStyle
{
BTRioBankMfd = 0, // 4 buttons above the glass + 4 below, addresses
// DESCENDING from anchorA row-major (0x2F..0x28)
BTRioBankRadar // 6 down each side (anchorA left, anchorB right)
// + an optional 4 along the bottom
};
enum
{
BTRioBankMaxButtons = 24
};
struct BTRioButton
{
int address; // RIO buttonGroup address (0x00-0x47)
int x, y, w, h; // caller-space rect (the FULL hit target)
int colorClass; // 0 red (MFD) / 1 yellow (radar) / 2 blue (flight)
int inert; // 1 = no authored .CTL mapping -> drawn greyed
const char *label; // non-NULL -> the face is drawn + labelled
};
//---------------------------------------------------------------------------
// How much of a button shows, and how the field is spaced. Both renderers
// work at different scales (the surround halves the glass; the exploded
// windows are native), so this is DERIVED from the display size -- the pod
// rule applied at whatever size you hand it.
//---------------------------------------------------------------------------
struct BTRioBankMetrics
{
int strip; // how much of a button clears the display edge (the lamp)
int gap; // gap between neighbouring buttons in a row/column
int cellW, cellH; // the labelled no-glass cell (flight bank, map foot row)
};
struct BTRioBank
{
BTRioButton buttons[BTRioBankMaxButtons];
int buttonCount;
// The bank + display bounding box, in the caller's space. A window
// caller sizes its client to boundsW/H and passes an origin that puts
// boundsX/Y at zero; the composited caller ignores these and just uses
// the button rects, which are already in canvas space.
int boundsX, boundsY, boundsW, boundsH;
};
//---------------------------------------------------------------------------
// The pod rule at a given display size. The lamp strip scales off the SHORT
// axis against the native 480 (an MFD is 640x480 landscape, the map 480x640
// portrait -- both have a 480 short axis) and never drops below a readable
// floor, because a 5px lamp on a half-scale surround MFD is a smudge.
//---------------------------------------------------------------------------
void BTRioBankMetricsFor(int displayW, int displayH, BTRioBankMetrics *out);
//---------------------------------------------------------------------------
// Lay out one display's bank. originX/Y is the DISPLAY's top-left; button
// rects come back around it, reaching `strip` outside the glass (so they can
// land at negative coordinates relative to the origin -- see boundsX/Y).
// anchorA/B bank base addresses (style-dependent; see BTRioBankStyle)
// bottomAddrs BTRioBankRadar only: the 4 foot-row addresses, or NULL
//---------------------------------------------------------------------------
void BTRioBankLayout(
BTRioBankStyle style,
int originX,
int originY,
int displayW,
int displayH,
int anchorA,
int anchorB,
const BTRioBankMetrics *metrics,
const int *bottomAddrs,
BTRioBank *out
);
//---------------------------------------------------------------------------
// The no-display bank (throttle/panic 0x38-0x3F, joystick/fire 0x40-0x47):
// a grid of labelled cells, drawn in full since there is no glass to hide
// behind. APPENDS to `out` (so both columns share one bank) and grows its
// bounds; zero `out` before the first call. `labels`/`inert` are `count`
// long and may be NULL.
//
// These cells carry TEXT, so their size is the caller's call, not a scale of
// some glass -- fill metrics->cellW/cellH yourself (the surround runs bigger
// cells than the exploded window because its canvas is half-scale and the
// labels still have to read).
//---------------------------------------------------------------------------
void BTRioBankFlightGrid(
int originX,
int originY,
int baseAddress,
int count,
int columns,
const char *const *labels,
const int *inert,
const BTRioBankMetrics *metrics,
BTRioBank *out
);
//---------------------------------------------------------------------------
// One-shot geometry dump: `BT_RIOBANK_LOG=1` prints every button's address and
// rect as the banks are built, so the field can be checked for coverage and
// address shadowing without driving a mouse. Capped at the first few banks so
// a per-frame caller (the surround recomputes its layout every draw) prints
// one pass and then goes quiet.
//---------------------------------------------------------------------------
void BTRioBankDump(const char *tag, const BTRioBank *bank);
#endif