red radar: a FLASHING radar button goes red instead of yellow

Player request.  The radar's buttons are the yellow class, so an alerting lamp
pulsed yellow among yellows -- a brightness change only, which is the night-16
miss that already produced the #172 alert ring and the protrusion bonus.  The
bright colour of a FLASHING radar button now becomes the house red
(230,70,70 -- the MFD family), so an alert reads as a COLOUR change.

Only the bright phase moves: dim and off keep the yellow family, so a radar
button that is not alerting looks exactly as before.  Flashing is bits 0-1 of
the lamp word (0 = solid), i.e. the pod's own way of saying alert -- no new
state, just a different colour for the state it already had.

ONE decode, BTRadarLampBright(lampState) in L4VB16.h beside BTLampBrightnessOf,
consumed by all THREE desktop renderers -- CkLampColors (surround), PaintGlass
(exploded windows) and L4PADPANEL -- so they cannot drift apart the way the
button GEOMETRY did before L4RIOBANK.  BT_RADAR_FLASH_RED=0 restores the yellow
bright.

GLASS-ONLY DEVIATION (#154-class): the pod's buttons are single-colour backlit
hardware and cannot change colour.  This lives entirely in the three desktop
renderers; the serial RIO lamp path is untouched, so a real pod is unaffected
by construction.

Also adds the bench hook this needed: BT_LAMPTEST=<addr>[,<state>] forces one
lamp's state at PadRIO::GetLampState -- the single read point all three
renderers share -- so lamp rendering can be checked without inducing the fault
that would raise it.  Default 0x33 is the Panic lamp's alert shape (flashFast +
state1Off + state2Bright).

Verified [T2] by pixel tally of the radar window with BT_LAMPTEST=0x1b
(GeneratorB's lamp, the night-16 one): 620 house-red px with the feature on, 0
with BT_RADAR_FLASH_RED=0, and the radar-yellow count falling by exactly that
many (2820 -> 2196).  Screenshot: the one flashing lamp is red, every other
radar button still yellow.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Cyd
2026-08-14 14:36:31 -05:00
co-authored by Claude Opus 4.8
parent ccfa875b65
commit 2f9c1146a3
6 changed files with 116 additions and 11 deletions
+22
View File
@@ -115,6 +115,28 @@ each, so they cover the foot band too, and the hit test takes the first match
what keeps 0x16/0x17/0x1F/0x1E reachable. That class of bug is why the verification below tests
first-hit reachability, not mere presence.
**A FLASHING radar button goes RED — `BT_RADAR_FLASH_RED` (2026-08-14, player request)
[T2 pixel-measured]. GLASS-ONLY DEVIATION (#154-class).** The radar's buttons are the yellow
class, so an alerting lamp pulsed *yellow among yellows* — a brightness change only, and the
night-16 miss that also produced the #172 ring and the protrusion bonus. The three desktop
renderers now swap the **bright** colour of a **flashing** radar button to the house red
(230,70,70 — the MFD family), so an alert reads as a *colour* change. Only the bright phase
moves; dim/off keep the yellow family, so a non-alerting radar button is unchanged. One
decode, `BTRadarLampBright(lampState)` in `L4VB16.h` beside `BTLampBrightnessOf`, consumed by
all three renderers (`CkLampColors` in the surround, `PaintGlass` in the exploded windows,
`L4PADPANEL`) so they cannot drift — the lesson of the L4RIOBANK geometry split. `=0` restores
the yellow bright. The pod's own buttons are single-colour backlit hardware and cannot do this;
the serial RIO lamp path is untouched, so this is desktop-only by construction.
**Bench hook `BT_LAMPTEST=<addr>[,<state>]` (2026-08-14).** Forces ONE lamp's state at
`PadRIO::GetLampState` — the single read point all three desktop renderers share — so lamp
rendering can be checked without inducing the fault that would raise it (a generator trip, an
ammo alarm). Default state `0x33` = the Panic lamp's alert shape (flashFast + state1Off +
state2Bright, a hard dark/bright pulse); addresses and states take `0x` hex. This is how the
red-flash change above was verified: `BT_LAMPTEST=0x1b` (GeneratorB's radar lamp, the night-16
one) then a pixel tally of the radar window — **620 house-red px with the feature on, 0 with
`BT_RADAR_FLASH_RED=0`**, and the yellow count falling by exactly that many (2820 → 2196).
## Sticky window placement — BT_GLASS_LAYOUT (2026-07-28) [T2 round-trip-verified]
The `BT_GLASS_PANELS` windows self-place in a pod-faithful ring around the main window
+10 -3
View File
@@ -1472,14 +1472,21 @@ static void
const GButton &b = w->buttons[i];
const RECT &r = b.rect;
int held = (b.address == pressedAddress) || latched[b.address & 0x7F];
int shade = LampBrightnessOf(PadRIO::GetLampState(b.address), tick);
int lampState = PadRIO::GetLampState(b.address);
int shade = LampBrightnessOf(lampState, tick);
if (held)
shade = 3;
// A FLASHING radar button goes RED at its bright phase (L4VB16.h).
unsigned long radarBright = BTRadarLampBright(lampState);
COLORREF radarBrightRGB = RGB((radarBright >> 16) & 0xFF,
(radarBright >> 8) & 0xFF,
radarBright & 0xFF);
COLORREF fill, text_color;
if (b.color == ClrYellow)
{
fill = (shade == 3) ? RGB(245, 210, 60)
fill = (shade == 3) ? radarBrightRGB
: (shade != 0) ? RGB(140, 118, 38)
: RGB(70, 60, 24);
text_color = RGB(0, 0, 0);
@@ -1501,7 +1508,7 @@ static void
// Solid lamp fill + a bright border. Under the imagery, only the protruding
// edge shows once the surface is blitted on top.
{
COLORREF bright = (b.color == ClrYellow) ? RGB(245, 210, 60)
COLORREF bright = (b.color == ClrYellow) ? radarBrightRGB
: (b.color == ClrBlue) ? RGB(205, 228, 255)
: RGB(230, 70, 70);
HBRUSH face = CreateSolidBrush(fill);
+9 -2
View File
@@ -290,14 +290,21 @@ static void
// commanded lamp state (with flash phase) decides.
//
int shade = 0;
int lampState = 0;
if (lamp_capable)
{
shade = LampBrightnessOf(PadRIO::GetLampState(cell.address), tick);
lampState = PadRIO::GetLampState(cell.address);
shade = LampBrightnessOf(lampState, tick);
}
if (held)
{
shade = 3;
}
// A FLASHING radar/Sec button goes RED at its bright phase (L4VB16.h).
unsigned long radarBright = BTRadarLampBright(lampState);
COLORREF radarBrightRGB = RGB((radarBright >> 16) & 0xFF,
(radarBright >> 8) & 0xFF,
radarBright & 0xFF);
COLORREF fill;
COLORREF text_color;
@@ -308,7 +315,7 @@ static void
}
else if (yellow)
{
fill = (shade == 3) ? RGB(245, 210, 60)
fill = (shade == 3) ? radarBrightRGB
: (shade != 0) ? RGB(140, 118, 38)
: RGB(70, 60, 24);
text_color = RGB(0, 0, 0);
+24
View File
@@ -1282,6 +1282,30 @@ int BTPadRIOActive(void)
int
PadRIO::GetLampState(int unit)
{
//
// BENCH HOOK (2026-08-14): BT_LAMPTEST=<addr>[,<state>] forces ONE lamp's
// state, so the desktop lamp rendering can be checked without inducing the
// fault that would raise it (a generator trip, an ammo alarm, ...). The
// default state is the alert shape the Panic lamp uses -- flashFast +
// state1Off + state2Bright (0x33), a hard dark/bright pulse. Addresses and
// states take 0x hex. Read once; every desktop renderer reads its lamps
// through here, so one hook covers the surround, the exploded windows and
// the pad panel alike.
//
static int s_testAddr = -2;
static int s_testState = 0x33;
if (s_testAddr == -2)
{
s_testAddr = -1;
const char *e = getenv("BT_LAMPTEST");
if (e != NULL)
sscanf(e, "%i,%i", &s_testAddr, &s_testState);
}
if (s_testAddr >= 0 && unit == s_testAddr)
{
return s_testState;
}
if (activeInstance == NULL || unit < 0 || unit >= LampCount)
{
return 0;
+11 -6
View File
@@ -1028,12 +1028,17 @@ static void CkFill(LPDIRECT3DDEVICE9 dev, int x, int y, int w, int h, D3DCOLOR c
}
// Lamp fill + border for a button, from the glass PaintGlass color ramps.
static void CkLampColors(int colorClass, int shade, int inert, D3DCOLOR *fill, D3DCOLOR *border)
static void CkLampColors(int colorClass, int shade, int inert, D3DCOLOR *fill, D3DCOLOR *border,
int lampState = 0)
{
if (colorClass == 1) // yellow (radar)
if (colorClass == 1) // yellow (radar) -- RED while flashing (see L4VB16.h)
{
*border = D3DCOLOR_XRGB(245, 210, 60);
*fill = (shade == 3) ? D3DCOLOR_XRGB(245, 210, 60)
unsigned long bright = BTRadarLampBright(lampState);
D3DCOLOR brightC = D3DCOLOR_XRGB((int)((bright >> 16) & 0xFF),
(int)((bright >> 8) & 0xFF),
(int)( bright & 0xFF));
*border = brightC;
*fill = (shade == 3) ? brightC
: (shade != 0) ? D3DCOLOR_XRGB(140, 118, 38)
: D3DCOLOR_XRGB(70, 60, 24);
}
@@ -1218,7 +1223,7 @@ void BTDrawCockpitPanels(LPDIRECT3DDEVICE9 device)
int shade = BTLampBrightnessOf(state, tick);
if (shade < 2) continue; // dark half-cycle: no ring
D3DCOLOR fill, border;
CkLampColors(b.colorClass, 3 /*bright*/, b.inert, &fill, &border);
CkLampColors(b.colorClass, 3 /*bright*/, b.inert, &fill, &border, state);
int rx = b.x - kRingGap - kRingWidth;
int ry = b.y - kRingGap - kRingWidth;
int rw = b.w + 2 * (kRingGap + kRingWidth);
@@ -1239,7 +1244,7 @@ void BTDrawCockpitPanels(LPDIRECT3DDEVICE9 device)
int shade = BTLampBrightnessOf(state, tick);
if (gCkPressed == b.address || gCkLatched[b.address & 0x7F]) shade = 3;
D3DCOLOR fill, border;
CkLampColors(b.colorClass, shade, b.inert, &fill, &border);
CkLampColors(b.colorClass, shade, b.inert, &fill, &border, state);
CkFill(device, b.x, b.y, b.w, b.h, border);
CkFill(device, b.x + 1, b.y + 1, b.w - 2, b.h - 2, fill);
+40
View File
@@ -110,6 +110,46 @@ inline int BTLampBrightnessOf(int state, unsigned long tick)
return ((tick / half) & 1) ? level2 : level1;
}
//
// Is this lamp FLASHING rather than solid? Bits 0-1 of the wire word are the
// flash mode (0 = solid, 1/2/3 = slow/med/fast), so any non-zero value means
// the lamp is pulsing -- which is the pod's own way of saying ALERT.
//
// RADAR FLASH = RED (2026-08-14, player request; GLASS-ONLY DEVIATION). The
// radar's buttons are the yellow class, and a yellow lamp pulsing among other
// yellow lamps is easy to miss -- the night-16 miss that also produced the
// #172 ring and the protrusion bonus. The desktop renderers therefore swap
// the BRIGHT colour of a FLASHING radar button to the red family, so an alert
// reads as a colour change and not just a brightness change. The pod's own
// buttons are physical bicolour-less lamps and cannot do this, so it is a
// desktop affordance only -- and it is confined to the three desktop
// renderers (surround, exploded windows, pad panel); the serial RIO lamp
// path is untouched. BT_RADAR_FLASH_RED=0 restores the yellow bright.
//
inline int BTLampIsFlashing(int state)
{
return (state & 0x3) != 0;
}
//
// The bright colour a radar (yellow-class) button should use right now: the
// red family while the lamp is flashing, its own yellow otherwise. ONE
// definition, consumed by all three desktop renderers so they cannot drift
// (the lesson of the L4RIOBANK geometry split). Returns 0x00RRGGBB.
//
inline unsigned long BTRadarLampBright(int lampState)
{
static int enabled = -1;
if (enabled < 0)
{
const char *e = getenv("BT_RADAR_FLASH_RED");
enabled = (e != 0 && e[0] == '0') ? 0 : 1; // default ON
}
return (enabled && BTLampIsFlashing(lampState))
? 0x00E64646ul // 230,70,70 -- the house red (MFD family)
: 0x00F5D23Cul; // 245,210,60 -- the radar's own yellow
}
//########################################################################
//######################### Video16BitBuffered ###########################
//########################################################################