Author SHA1 Message Date
CydandClaude Opus 4.8 70ada2789f sec-surface phantoms: #48 wrap lit idx-254 art in live armor colors; fix + palette-aware glass token
Playtester report: (1) the CONTROL MODE stack showed boxes around the inactive
MID/ADV entries, (2) a phantom block between the HEADING dial and the ARMOR
rosette.  Root-caused [T1] by a 4-agent investigation + art decode:

  The #48 translation-table cycle-fill (6bb03ae, 2026-07-25) mapped
  out-of-range art indices in-plane as (index mod 2^bits) -- so art index 254
  landed on plane slot 62 = the LIVE colorMapperMultiArmor right-armor damage
  slot.  And idx-254 art EXISTS: every SMODE.PCC frame fills the INACTIVE
  mode-box interiors with 254, and BTSEC1.PCX carries a stray 52x13 idx-254
  bar at port (199-250,101-113) between the heading dial and the rosette (a
  scratch duplicate of the rosette quadrant bars).  Both lit up in the current
  right-armor color (adpal ramp green/orange/red) on EVERY render path.  On
  the shipped machine those regions rendered BLACK (the garbage entry's low
  plane bits were 0), which is why the 2026-07-19 smode audit -- run before
  the cycle-fill landed -- verified CORRECT.

FIX 1 (BuildSecondaryTranslation): map [2^bits..255] to translationTable[0]
  (plane BACKGROUND) -- the authentic on-screen result, same no-leak
  guarantee.  Verified on both paths: MID/ADV back to authored borders+text
  (idx 5/9), no interior fills; the phantom bar gone; BAS badge and the live
  armor rosette (in-range slots 60-63) untouched.

FIX 2 (glass dirty-skip palette blindness, latent in c9e25e5): the
  ColorMapper family animates by CLUT writes with zero pixel churn, which the
  pixel-checksum token cannot see -- the glass radar held stale armor tints
  between pixel repaints.  New SVGA16::paletteGeneration, bumped by the
  palette writers (BuildSecondaryColor only on a REAL entry change -- the
  adpal/adpal2 flash alternates every Execute writing identical RGB at zero
  damage, so the skip survives; full rebuilds bump unconditionally), folded
  into GlassWindowToken for palette-expanding windows.

KB: gauges-hud #48 REFINED addendum; glass-cockpit repaint-pump section
  (records f3d27f5 + c9e25e5 + this); GAUGE_COMPOSITE row 33 corrected
  (ControlsMapper/ControlMode, not DisplayMode) + re-verification note.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-10 10:44:23 -05:00
CydandClaude Opus 4.8 3aeb2dbbe8 glass panels: add the PlaneChecksum header decl (completes c9e25e5)
The dirty-skip commit staged the header via a lowercase path (l4vb16.h) while git
tracks it as L4VB16.h on this case-insensitive FS, so the PlaneChecksum declaration
was left out -- c9e25e5 as committed would not build from clean (L4VB16.cpp defines
SVGA16::PlaneChecksum, L4GLASSWIN.cpp calls it, but the header never declared it).
This adds the one-line decl.  HEAD now builds standalone.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-09 23:03:41 -05:00
CydandClaude Opus 4.8 c9e25e59c5 glass panels: dirty-skip -- repaint only the windows whose gauges changed
Follow-on to the HALFTONE->COLORONCOLOR fix.  BTGlassPanels_Tick repainted all 7
glass windows every ~16 Hz pump unconditionally.  Now each window carries a change
token and the pump re-blits ONLY the windows whose token differs:

  token = FNV-1a checksum of the shared gauge pixelBuffer masked to the bits this
          window can show (SVGA16::PlaneChecksum over primary + Eng-twin + RGB-group
          ports)  combined with  each button's RENDERED lamp brightness + held/latch.

Folding the flash BRIGHTNESS (not the raw lamp state) into the token means a flashing
lamp repaints exactly when it toggles; a static panel or an idle cockpit skips its
expand + StretchDIBits entirely.  The masked checksum reads the RENDERED RESULT of the
gauges' values, so it catches everything -- discrete value gauges, the continuous
radar sweep, any imagery -- with no gauge->port->window plumbing and no risk of a
frozen display (full pass, no stride; collision-free in practice).

Measured (dev box, solo mission, BT_GLASS_DIRTY): ~31 pumps/2s -> 4-15 window-repaints
vs 217-224 always-on (~15-40x fewer); avg frame work 3.3 -> 0.93ms.  BT_GLASS_DIRTY=1
logs the repaint tally.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-09 23:01:20 -05:00
CydandClaude Opus 4.8 f3d27f51c2 glass panels: drop HALFTONE stretch (the per-display-mode perf sink)
Playtester (Dave, SCREECH-PC, 4K) reported ~20 fps in the exploded per-display
glass panels vs ~130 fps in the cockpit surround -- same scene, same machine
(solo_20260809.log; maxDraw 57-92ms in panels vs 6-31ms surround).

Root cause: BlitSurface used SetStretchBltMode(HALFTONE) -- GDI's slowest,
per-output-pixel resample filter -- and BTGlassPanels_Tick repaints all 7 glass
windows SYNCHRONOUSLY on the main render thread every ~16 Hz.  7x HALFTONE
StretchDIBits of a 640x480 surface per pump stalls the frame.

Default to COLORONCOLOR (nearest); BT_GLASS_SMOOTH=1 restores HALFTONE.  The
MFDs are low-res pixel content, so nearest reads crisp -- arguably closer to the
pod CRT than the blur.  A/B on the dev box (fast, understates Dave's gain):
avg work 3.3->1.4ms, maxDraw stall 12-27->7ms, ~2.2x more frames per window.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-09 22:40:25 -05:00
6 changed files with 237 additions and 11 deletions
+16
View File
@@ -783,6 +783,22 @@ pattern across entries [2^bits..255] (high-index art degrades to its
index-mod-2^bits colour IN-PLANE, can never leak). The 1995 binary ships the index-mod-2^bits colour IN-PLANE, can never leak). The 1995 binary ships the
SAME 64-entry fill and relied on 6-bit art discipline -- garbage is not a SAME 64-entry fill and relied on 6-bit art discipline -- garbage is not a
preservable behaviour, so the cycle-fill is a guarded PORT deviation. preservable behaviour, so the cycle-fill is a guarded PORT deviation.
**REFINED 2026-08-10 [T1] -- the cycle-fill itself made phantoms.** The
in-plane wrap mapped art index 254 onto plane slot 62 = the LIVE
`colorMapperMultiArmor` right-armor damage slot, and idx-254 art EXISTS:
every SMODE.PCC frame fills the INACTIVE control-mode box interiors with 254
(active border/text idx 9 yellow, inactive borders idx 5 orange-red), and
BTSEC1.PCX carries a stray 52x13 idx-254 bar at port (199-250, 101-113) --
between the heading dial and the armor rosette, geometrically a scratch
duplicate of the rosette quadrant bars. Both lit up in the current
right-armor colour (adpal ramp green/orange/red) on EVERY render path --
playtester-reported as "red boxes around MID/ADV" + "a block between Armor
and Heading". On the shipped machine those regions rendered BLACK (the
garbage entry's low plane bits were 0 -> in-plane index 0), so the fill now
maps [2^bits..255] to `translationTable[0]` (plane BACKGROUND) -- authentic
on-screen result, same no-leak guarantee. NB the 2026-07-19 audit verified
smode BEFORE the 07-25 cycle-fill landed, which is why the row said CORRECT.
Post-fix: **0 leaks over 60s** on the same probe; sim3 3-pod regression clean. Post-fix: **0 leaks over 60s** on the same probe; sim3 3-pod regression clean.
**The tripwires are DEFAULT-ON in every build** (BT_PLANE_AUDIT=0 opts out): the **The tripwires are DEFAULT-ON in every build** (BT_PLANE_AUDIT=0 opts out): the
plane-leak trap, plus an out-of-bounds draw-start trap in `buildDestPointer` plane-leak trap, plus an out-of-bounds draw-start trap in `buildDestPointer`
+25
View File
@@ -203,6 +203,31 @@ appears in the cfg like any panel and honours `,noframe`. Verified: a drag wrote
at 321,222. NB the plasma window blits directly every frame (`GetDC`+`StretchDIBits`), so unlike at 321,222. NB the plasma window blits directly every frame (`GetDC`+`StretchDIBits`), so unlike
the panels it has no `WM_TIMER` focus-throttle to worry about. the panels it has no `WM_TIMER` focus-throttle to worry about.
## Glass-panel repaint pump — perf + dirty-skip (2026-08-09/10) [T2 measured]
The exploded per-display windows are pure CPU/GDI (`ExpandPlaneToBGRA` +
`StretchDIBits`), repainted synchronously on the MAIN render thread by
`BTGlassPanels_Tick` (~16 Hz). Three layered changes (branch `glass-panel-perf`)
after a playtester reported ~20 fps in panels mode vs ~130 fps in the surround:
1. **HALFTONE → COLORONCOLOR** (`f3d27f5`): the `HALFTONE` stretch (GDI's
per-output-pixel resample) × 7 windows per pump was the primary sink; nearest
is crisper for the low-res MFD pixels anyway. `BT_GLASS_SMOOTH=1` restores.
2. **Per-window dirty-skip** (`c9e25e5`): each window carries a change token =
FNV over `SVGA16::PlaneChecksum(mask)` (the shared pixelBuffer masked to every
port that can feed the window) + each button's RENDERED lamp brightness +
held/latch. The pump re-blits only windows whose token moved: ~31 pumps/2s →
4-15 window repaints vs 217-224 always-on. `BT_GLASS_DIRTY=1` logs the tally.
3. **Palette generation in the token** (2026-08-10): the ColorMapper family
(armor rosette tints, adpal/adpal2 damage flash) animates by CLUT writes with
ZERO pixel churn — invisible to a pixel checksum. `SVGA16::paletteGeneration`
is bumped by the palette writers (`BuildSecondaryColor` only on a REAL entry
change, since the flash alternates palettes every Execute writing identical
RGB at zero damage; full rebuilds unconditionally) and folded into the token
for palette-expanding windows (`monoTint < 0`, or all under `BT_GLASS_MFD_PAL`).
Without it the glass radar held stale armor tints between pixel repaints while
the D3D surround (which re-expands every frame) tracked live.
**Turning it OFF: `L4PLASMA=NONE` (also `OFF`/`0`, 2026-08-06) [T2].** `L4GREND` creates a **Turning it OFF: `L4PLASMA=NONE` (also `OFF`/`0`, 2026-08-06) [T2].** `L4GREND` creates a
marquee whenever `L4PLASMA` is set at all (`SCREEN` → the desktop window, anything else → a real marquee whenever `L4PLASMA` is set at all (`SCREEN` → the desktop window, anything else → a real
`PlasmaDisplay` on that serial port), and the GLASS profile force-defaults it to `SCREEN` — so on `PlasmaDisplay` on that serial port), and the GLASS profile force-defaults it to `SCREEN` — so on
+1 -1
View File
@@ -696,7 +696,7 @@ reconfigure/externalConfigure); parse-skip list EMPTY ([gskip]=0), all 50 attr b
| 30 | sec: schematic CRITICAL view (cmCrit) | subsystem simulationState/damage | LIVE this audit: N-cycle shows the full subsystem list (GEN A-D, LOOP 1-6, HUD, SENSORS, GYRO, TORSO, weapons) | T2 | CORRECT | | 30 | sec: schematic CRITICAL view (cmCrit) | subsystem simulationState/damage | LIVE this audit: N-cycle shows the full subsystem list (GEN A-D, LOOP 1-6, HUD, SENSORS, GYRO, TORSO, weapons) | T2 | CORRECT |
| 31 | sec: schematic HEAT view (cmHeat) | subsystem currentTemperature tint | #6 pixel-verified; re-cycled this audit (mask 0x450421→0x490421→0x510421) | T2 | CORRECT | | 31 | sec: schematic HEAT view (cmHeat) | subsystem currentTemperature tint | #6 pixel-verified; re-cycled this audit (mask 0x450421→0x490421→0x510421) | T2 | CORRECT |
| 32 | sec: view cycling (N / pod 0x15) | CycleDisplayMode → vtbl+0x4C @4d1ae4 | #6 resolution re-verified live ([mode] display notify 0/1/2) | T2 | CORRECT | | 32 | sec: view cycling (N / pod 0x15) | CycleDisplayMode → vtbl+0x4C @4d1ae4 | #6 resolution re-verified live ([mode] display notify 0/1/2) | T2 | CORRECT |
| 33 | sec: CONTROL MODE lamp (BAS/MID/ADV) | ControlsMapper/DisplayMode oneOfSeveralPixInt | attr wave incr.5; M-cycle verified #6 | T2 | CORRECT | | 33 | sec: CONTROL MODE lamp (BAS/MID/ADV) | ControlsMapper/**ControlMode** oneOfSeveralPixInt (row previously mislabeled DisplayMode — that is the sibling sdspmod) | attr wave incr.5; M-cycle verified #6. **2026-08-10:** this row's verification PREDATED the 07-25 #48 cycle-fill, which then lit the inactive box interiors (art idx 254 → live armor slot 62) — playtester-reported, re-fixed by mapping out-of-range indices to plane background (see context/gauges-hud.md §#48 REFINED); both render paths re-verified against the reference | T2 | CORRECT |
| 34 | sec: duck / searchlight button lamps | duckState (ctor-zeroed only) / Searchlight LightOn | duckState writer missing (P3 leftover) | T3 | DEFERRED-FEED (duck); CORRECT (light attr published) | | 34 | sec: duck / searchlight button lamps | duckState (ctor-zeroed only) / Searchlight LightOn | duckState writer missing (P3 leftover) | T3 | DEFERRED-FEED (duck); CORRECT (light attr published) |
| 35 | sec: messageBoard ticker | StatusMessagePool (NULL stub) + kill ticker strip 0 | 7fc4acb; kill ticker live 2026-07-12, other strips unsurveyed | T2/T3 | DEFERRED-FEED (partial) | | 35 | sec: messageBoard ticker | StatusMessagePool (NULL stub) + kill ticker strip 0 | 7fc4acb; kill ticker live 2026-07-12, other strips unsurveyed | T2/T3 | DEFERRED-FEED (partial) |
| 36 | MFD preset paging (J/K/L, pod RIO banks) | SetPresetMode table @0051dbf0 (little-endian re-decode) | #9; re-verified live this audit: cycles visit EXACTLY the populated set (MFD1: 1,2,4; MFD2: 1-4; MFD3: 1,2) | T2 | CORRECT | | 36 | MFD preset paging (J/K/L, pod RIO banks) | SetPresetMode table @0051dbf0 (little-endian re-decode) | #9; re-verified live this audit: cycles visit EXACTLY the populated set (MFD1: 1,2,4; MFD2: 1-4; MFD3: 1,2) | T2 | CORRECT |
+113 -4
View File
@@ -127,6 +127,12 @@ struct GWin
char monitorName[40]; // the PHYSICAL monitor this window landed on char monitorName[40]; // the PHYSICAL monitor this window landed on
// (\.\DISPLAYn), stamped at creation; shown // (\.\DISPLAYn), stamped at creation; shown
// by BT_POD_IDENT so the cab can be mapped. // by BT_POD_IDENT so the cab can be mapped.
// Dirty-skip (2026-08-09): the last change token the pump blitted for this
// window (plane checksum + lamp render). Zero-init (static storage); the pump
// re-blits only when the token differs, so unchanged panels cost nothing.
unsigned long lastToken;
int haveToken;
}; };
static GWin gWins[8]; static GWin gWins[8];
@@ -1180,7 +1186,16 @@ static void
info.bmiHeader.biCompression = BI_RGB; info.bmiHeader.biCompression = BI_RGB;
const RECT &r = w->surfaceRect; const RECT &r = w->surfaceRect;
SetStretchBltMode(dc, HALFTONE); // STRETCH MODE (perf, 2026-08-09): default COLORONCOLOR. HALFTONE runs a
// per-output-pixel resample filter; done synchronously for all 7 glass windows
// every ~16 Hz repaint (BTGlassPanels_Tick), it was the per-display-mode perf
// sink -- playtesters saw ~20 fps in the exploded panels vs ~130 fps in the
// cockpit surround (same scene, same machine). The MFDs are low-res pixel
// content, so nearest-neighbour reads crisp (and closer to the pod CRT).
// BT_GLASS_SMOOTH=1 restores HALFTONE for anyone who prefers smoothing to speed.
static int sSmooth = -1;
if (sSmooth < 0) sSmooth = getenv("BT_GLASS_SMOOTH") ? 1 : 0;
SetStretchBltMode(dc, sSmooth ? HALFTONE : COLORONCOLOR);
SetBrushOrgEx(dc, 0, 0, NULL); SetBrushOrgEx(dc, 0, 0, NULL);
StretchDIBits(dc, StretchDIBits(dc,
r.left, r.top, r.right - r.left, r.bottom - r.top, r.left, r.top, r.right - r.left, r.bottom - r.top,
@@ -1778,6 +1793,74 @@ void
// one-shot re-snap); a focused window just repaints from whichever fires first. // one-shot re-snap); a focused window just repaints from whichever fires first.
//########################################################################### //###########################################################################
//
// GLASS DIRTY-SKIP (2026-08-09): re-blit only the windows whose content changed.
// Each window is a plane of the ONE shared gauge pixelBuffer, so its change token is
// the masked plane checksum (SVGA16::PlaneChecksum over every port that can feed the
// window) combined with each button's RENDERED lamp brightness + held/latched state.
// Folding the flash BRIGHTNESS in (not the raw lamp state) means a flashing lamp
// repaints exactly when it toggles, and a static panel -- or a fully idle cockpit --
// skips its expand + StretchDIBits entirely. The plane checksum is the only added
// cost (once per window per ~16 Hz pump) and is far cheaper than the paint it saves.
//
static unsigned long
GlassWindowToken(GaugeRenderer *gr, GWin *w, unsigned long tick)
{
unsigned long token = 2166136261UL;
if (w->portPrimary != NULL && gr != NULL)
{
// Every plane that could feed the window (primary + Eng twin + RGB group +
// their twins) -- OR their masks so a change to ANY is caught; checksum once.
const char *ports[8];
int np = 0;
ports[np++] = w->portPrimary;
if (w->portAlt != NULL) ports[np++] = w->portAlt;
for (int gi = 0; gi < w->groupCount && np < 7; ++gi)
{
ports[np++] = w->groupPort[gi];
if (w->groupAlt[gi] != NULL && np < 8) ports[np++] = w->groupAlt[gi];
}
int combined = 0;
SVGA16 *svga = NULL;
for (int i = 0; i < np; ++i)
{
L4GraphicsPort *p =
static_cast<L4GraphicsPort*>(gr->GetGraphicsPort(ports[i]));
if (p == NULL) continue;
combined |= p->GetBitMask();
if (svga == NULL) svga = static_cast<SVGA16*>(p->graphicsDisplay);
}
if (svga != NULL && combined != 0)
{
token ^= svga->PlaneChecksum(combined);
// PALETTE-ANIMATED content (2026-08-10): a palette-expanding window
// (the radar; MFDs under BT_GLASS_MFD_PAL) changes colour with ZERO
// pixel writes -- the ColorMapper family (armor rosette tints, the
// adpal/adpal2 damage flash) writes CLUT entries. Fold the palette
// write generation in so those changes repaint; mono-tint windows
// don't read the palette and keep their pixel-only token.
static int sPalTok = -1;
if (sPalTok < 0) sPalTok = getenv("BT_GLASS_MFD_PAL") ? 1 : 0;
if (w->monoTint < 0 || sPalTok)
token = (token ^ svga->PaletteGeneration()) * 16777619UL;
}
}
// Lamps: each button's RENDERED brightness + held/latched, so a flash toggle or a
// press repaints exactly the window it lives on.
for (int j = 0; j < w->buttonCount; ++j)
{
int addr = w->buttons[j].address;
unsigned long shade =
(unsigned long)LampBrightnessOf(PadRIO::GetLampState(addr), tick);
int held = (addr == pressedAddress) || latched[addr & 0x7F];
token = (token ^ ((unsigned long)(addr & 0xFF) << 4)
^ (shade << 1) ^ (unsigned long)held) * 16777619UL;
}
return token;
}
void void
BTGlassPanels_Tick() BTGlassPanels_Tick()
{ {
@@ -1790,12 +1873,38 @@ void
return; return;
sLastPaint = now; sLastPaint = now;
GaugeRenderer *gr = BTResolveGaugeRenderer();
int repainted = 0;
for (int i = 0; i < gWinCount; ++i) for (int i = 0; i < gWinCount; ++i)
{ {
if (gWins[i].hwnd != NULL) GWin &w = gWins[i];
if (w.hwnd == NULL)
continue;
unsigned long token = GlassWindowToken(gr, &w, now);
if (w.haveToken && token == w.lastToken)
continue; // gauges + lamps unchanged -> skip this window
w.lastToken = token;
w.haveToken = 1;
++repainted;
InvalidateRect(w.hwnd, NULL, FALSE);
UpdateWindow(w.hwnd); // synchronous paint, not the throttled queue
}
// BT_GLASS_DIRTY: report how many window-repaints the dirty-skip let through vs
// the old fixed gWinCount-per-pump, so the saving is visible.
static int sDirtyLog = -1;
if (sDirtyLog < 0) sDirtyLog = getenv("BT_GLASS_DIRTY") ? 1 : 0;
if (sDirtyLog)
{
static unsigned long sWin = 0;
static int sPumps = 0, sPaints = 0;
++sPumps; sPaints += repainted;
if (now - sWin >= 2000)
{ {
InvalidateRect(gWins[i].hwnd, NULL, FALSE); DEBUG_STREAM << "[glass-dirty] " << sPumps << " pumps -> " << sPaints
UpdateWindow(gWins[i].hwnd); // synchronous paint, not the throttled queue << " window-repaints (was " << (sPumps * gWinCount) << " always-on)\n"
<< std::flush;
sWin = now; sPumps = 0; sPaints = 0;
} }
} }
} }
+62 -6
View File
@@ -692,6 +692,28 @@ void SVGA16::DrawDevSurface(LPDIRECT3DDEVICE9 device, int slot, int mask, int pa
device->DrawPrimitiveUP(D3DPT_TRIANGLEFAN, 2, quad, sizeof(InsetVert)); device->DrawPrimitiveUP(D3DPT_TRIANGLEFAN, 2, quad, sizeof(InsetVert));
} }
//===========================================================================//
// GLASS dirty-skip: FNV-1a over the shared pixelBuffer masked to `mask` -- the bits
// one glass window can show. The glass repaint pump compares this per window and
// re-blits only the ones whose plane changed (L4GLASSWIN BTGlassPanels_Tick). Full
// pass (no stride) so a single-word gauge change is never missed; ~640*480 cheap
// integer ops, run at most once per window per ~16 Hz pump.
//===========================================================================//
unsigned long SVGA16::PlaneChecksum(int mask) const
{
int w = pixelBuffer.Data.Size.x;
int h = pixelBuffer.Data.Size.y;
const Word *p = pixelBuffer.Data.MapPointer;
if (p == NULL || w <= 0 || h <= 0)
return 0;
unsigned long sum = 2166136261UL; // FNV-1a offset basis
Word m = (Word)mask;
int n = w * h;
for (int i = 0; i < n; ++i)
sum = (sum ^ (unsigned long)(p[i] & m)) * 16777619UL;
return sum;
}
//===========================================================================// //===========================================================================//
// GLASS per-display windows -- the CPU (no-D3D) analog of DrawDevSurface: expand // GLASS per-display windows -- the CPU (no-D3D) analog of DrawDevSurface: expand
// one bit-plane of the shared gauge pixelBuffer into a 32-bit BGRA image that the // one bit-plane of the shared gauge pixelBuffer into a 32-bit BGRA image that the
@@ -5423,6 +5445,7 @@ SVGA16::SVGA16(
BuildWindows(init_width,init_height,windowed, secondaryIndex, aux1Index, aux2Index); BuildWindows(init_width,init_height,windowed, secondaryIndex, aux1Index, aux2Index);
for (int _i = 0; _i < 10; _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; mDevSurfaceTex[_i] = NULL;
paletteGeneration = 0; // GLASS dirty-skip palette tracking
//STUBBED: VIDEO RB 1/15/07 //STUBBED: VIDEO RB 1/15/07
# if defined(DEBUG) # if defined(DEBUG)
Tell("SVGA16::SVGA16()\n"); Tell("SVGA16::SVGA16()\n");
@@ -7168,6 +7191,7 @@ void
svga_palette->paletteData.Valid = True; svga_palette->paletteData.Valid = True;
svga_palette->modified = True; svga_palette->modified = True;
((SVGA16 *) graphicsDisplay)->paletteGeneration++; // glass dirty-skip (full rebuild)
Check_Fpu(); Check_Fpu();
} }
@@ -7212,6 +7236,13 @@ void
SVGA16Palette SVGA16Palette
*svga_palette(&((SVGA16 *) graphicsDisplay)->palette[paletteID]); *svga_palette(&((SVGA16 *) graphicsDisplay)->palette[paletteID]);
// GLASS dirty-skip: track whether this write CHANGES anything -- the
// ColorMapper family calls this every Execute (the adpal/adpal2 flash
// alternates palettes even at zero damage, usually writing identical RGB),
// so bump the generation only on a real change or every glass window that
// palette-expands would repaint every pump for nothing.
int palette_changed = 0;
//------------------------------------------- //-------------------------------------------
// If any of the ...TransparentZero modes are used, // If any of the ...TransparentZero modes are used,
// leave color zero undefined for this bit group by // leave color zero undefined for this bit group by
@@ -7253,21 +7284,31 @@ void
{ {
case RedChannel: case RedChannel:
case RedChannelTransparentZero: case RedChannelTransparentZero:
if (destination_triplet->Red != source_triplet->Red)
palette_changed = 1;
destination_triplet->Red = source_triplet->Red; destination_triplet->Red = source_triplet->Red;
break; break;
case GreenChannel: case GreenChannel:
case GreenChannelTransparentZero: case GreenChannelTransparentZero:
if (destination_triplet->Green != source_triplet->Green)
palette_changed = 1;
destination_triplet->Green = source_triplet->Green; destination_triplet->Green = source_triplet->Green;
break; break;
case BlueChannel: case BlueChannel:
case BlueChannelTransparentZero: case BlueChannelTransparentZero:
if (destination_triplet->Blue != source_triplet->Blue)
palette_changed = 1;
destination_triplet->Blue = source_triplet->Blue; destination_triplet->Blue = source_triplet->Blue;
break; break;
case AllChannels: case AllChannels:
case AllChannelsTransparentZero: case AllChannelsTransparentZero:
if (destination_triplet->Red != source_triplet->Red
|| destination_triplet->Green != source_triplet->Green
|| destination_triplet->Blue != source_triplet->Blue)
palette_changed = 1;
*destination_triplet = *source_triplet; *destination_triplet = *source_triplet;
break; break;
} }
@@ -7288,6 +7329,8 @@ void
svga_palette->paletteData.Valid = True; svga_palette->paletteData.Valid = True;
svga_palette->modified = True; svga_palette->modified = True;
if (palette_changed)
((SVGA16 *) graphicsDisplay)->paletteGeneration++; // glass dirty-skip
Check_Fpu(); Check_Fpu();
} }
@@ -7391,6 +7434,7 @@ void
svga_palette->paletteData.Valid = True; svga_palette->paletteData.Valid = True;
svga_palette->modified = True; svga_palette->modified = True;
((SVGA16 *) graphicsDisplay)->paletteGeneration++; // glass dirty-skip (full rebuild)
# if defined(TESTPALETTE) # if defined(TESTPALETTE)
std::cout << "L4GraphicsPort::BuildAuxiliaryPalette for port " << std::cout << "L4GraphicsPort::BuildAuxiliaryPalette for port " <<
@@ -7501,6 +7545,7 @@ void
svga_palette->paletteData.Valid = True; svga_palette->paletteData.Valid = True;
svga_palette->modified = True; svga_palette->modified = True;
((SVGA16 *) graphicsDisplay)->paletteGeneration++; // glass dirty-skip (full rebuild)
Check_Fpu(); Check_Fpu();
} }
@@ -7535,17 +7580,28 @@ void
// LEFT AS HEAP GARBAGE and any pixmap pixel >= that count wrote the // LEFT AS HEAP GARBAGE and any pixmap pixel >= that count wrote the
// garbage's high bits into other displays' planes (the #48 stray // garbage's high bits into other displays' planes (the #48 stray
// blocks; convicted live by BT_PLANE_AUDIT -- the 480x640 radar // blocks; convicted live by BT_PLANE_AUDIT -- the 480x640 radar
// background carries index 217). Cycle the in-plane pattern across // background carries index 217). Map the remainder to the plane
// the remainder: high-index art degrades to its (index mod 2^bits) // BACKGROUND (translationTable[0]): high-index art renders as the
// colour IN-PLANE, and can never leak. (The 1995 binary shipped the // port's background colour in-plane, and can never leak.
// same 64-entry fill and relied on art discipline; garbage is not a //
// preservable behaviour, so this is a guarded PORT deviation.) // REFINED 2026-08-10 (the sec-surface phantoms): the first #48 fill
// cycled the remainder IN-PLANE (index mod 2^bits), which mapped art
// index 254 onto plane slot 62 -- a LIVE colorMapperMultiArmor damage
// slot. SMODE.PCC's inactive control-mode box interiors and a stray
// 52x13 idx-254 bar baked into BTSEC1.PCX (between the heading dial
// and the armor rosette) lit up in the current right-armor damage
// colour on every render path. On the shipped machine those regions
// rendered BLACK (the heap garbage's low plane bits were 0 ->
// in-plane index 0), so background IS the authentic on-screen result.
// (The 1995 binary shipped the same 64-entry fill and relied on art
// discipline; garbage is not a preservable behaviour, so this stays a
// guarded PORT deviation.)
{ {
int filled = 1; int filled = 1;
for (int b = bitMask & 0xFF; b != 0; b &= (b - 1)) for (int b = bitMask & 0xFF; b != 0; b &= (b - 1))
filled <<= 1; filled <<= 1;
for (int i = filled; i < 256; ++i) for (int i = filled; i < 256; ++i)
translationTable[i] = translationTable[i & (filled - 1)]; translationTable[i] = translationTable[0];
} }
Check_Fpu(); Check_Fpu();
} }
+20
View File
@@ -382,6 +382,15 @@ protected:
SVGA16Palette SVGA16Palette
palette[PaletteCount]; palette[PaletteCount];
// GLASS dirty-skip (2026-08-10): bumped by the palette writers
// (L4GraphicsPort::BuildSecondaryColor on a REAL entry change; the full
// palette rebuilds unconditionally) so the glass repaint token can see
// palette-only animation -- the ColorMapper family (armor rosette tints,
// damage flash) changes COLORS with zero pixel writes, which the pixel
// checksum alone can never catch.
unsigned long
paletteGeneration;
private: private:
int NUMGAUGEWINDOWS; int NUMGAUGEWINDOWS;
HWND *gaugeWindows; HWND *gaugeWindows;
@@ -419,6 +428,17 @@ public:
// dwords; the image is written TOP-DOWN. *outW/*outH receive the produced size. // dwords; the image is written TOP-DOWN. *outW/*outH receive the produced size.
void ExpandPlaneToBGRA(int mask, int paletteID, int monoTint, int rotateQuadrant, void ExpandPlaneToBGRA(int mask, int paletteID, int monoTint, int rotateQuadrant,
unsigned long *dst, int *outW, int *outH); unsigned long *dst, int *outW, int *outH);
// GLASS dirty-skip (L4GLASSWIN, 2026-08-09): FNV-1a checksum of the shared
// gauge pixelBuffer, masked to the bits a given window can show. Lets the
// glass repaint pump re-blit ONLY the windows whose plane actually changed
// (idle MFDs / static panels skip; the sweeping radar keeps updating).
unsigned long PlaneChecksum(int mask) const;
// GLASS dirty-skip: the palette write generation (see paletteGeneration).
// Folded into the repaint token of palette-expanding windows so ColorMapper
// palette animation (armor tints / damage flash) repaints without pixel churn.
unsigned long PaletteGeneration() const { return paletteGeneration; }
}; };
//######################################################################## //########################################################################