diff --git a/docs/OPEN_ISSUES_FOR_TESTERS.txt b/docs/OPEN_ISSUES_FOR_TESTERS.txt index 05ce071..53132d1 100644 --- a/docs/OPEN_ISSUES_FOR_TESTERS.txt +++ b/docs/OPEN_ISSUES_FOR_TESTERS.txt @@ -37,9 +37,19 @@ A. FIXED -- PLEASE VERIFY (headliners first: new fixes fielded THIS build) TEST: take a leak (ram someone) -- the generator lamp for THAT loop flashes, and stops when it should. #136 Leaking SHUT loop responds to component toggles - EXPLAINED AS AUTHENTIC: the alarm SOUND is one-shot per new leak, - the lamp FLASH is continuous while leaking. Confirm the description - matches what you see and we close it. + EXPLAINED AS AUTHENTIC (refined): the leak voice STARTS at the leak, + LOOPS on its own while the leak persists, STOPS when it seals -- so + hearing it again does NOT mean a new leak. The lamp FLASH is + continuous while leaking. Confirm and we close it. +#172 "Coolant leak but no flashing indicator" -- WHERE TO LOOK + The lamp always flashed; it was in a place nobody watches. A + GENERATOR leak flashes the generator button BY THE RADAR (bottom + centre) -- NOT any MFD. That is authentic: no MFD carries a + generator-leak indicator. A WEAPON's leak lamp lives on its + engineering page and flashes when you page there (also authentic). + Loop leaks flash the loop button immediately. TEST Friday: take a + leak, look AT THE RADAR RAIL -- tell us if it still reads as missing + even knowing where to look. #128 Ramming terrain = instant coolant leak Ram damage eligibility fixed -- walls and rocks no longer puncture loops. TEST: bump terrain hard; leaks come from combat, not curbs. diff --git a/engine/MUNGA_L4/L4VB16.cpp b/engine/MUNGA_L4/L4VB16.cpp index d9c1aaa..4628f72 100644 --- a/engine/MUNGA_L4/L4VB16.cpp +++ b/engine/MUNGA_L4/L4VB16.cpp @@ -1180,20 +1180,35 @@ void BTDrawCockpitPanels(LPDIRECT3DDEVICE9 device) CkFill(device, 0, L.viewY, L.viewX, L.viewH, band); // left CkFill(device, L.viewX + L.viewW, L.viewY, L.canvasW - (L.viewX + L.viewW), L.viewH, band); // right - // 2a) #172 ALERT BLOOM (glass-only deviation, #154-class, user-approved - // family): an ALERTING lamp in a surround strip bank is a ~6px sliver - // (minimumStrip) -- Elengil's fatal GeneratorB leak FLASHED lamp 0x1b - // for 28.4s and nobody saw it (night-16, ticket #172; the pod's - // physical backlit buttons don't have this problem). Draw a GROWN - // halo rect FIRST, under everything: neighbors repaint their own - // faces and the surfaces cover the inward part, so only the outward - // bloom survives -- which respects the "no on-top overlay" rule - // (twice field-broken, see the note below step 3). Fires ONLY while - // the lamp genuinely alternates (flash bits set AND the two levels - // differ), so steady lamps and same-level pulses are untouched. + // 2a) #172 ALERT RING (glass-only deviation, #154-class, user-approved + // 2026-08-13 -- the first cut, a +8px grown-face bloom, read as "the + // button got fat" on the bench and was rejected): an ALERTING lamp in + // a surround strip bank is a ~6px sliver (minimumStrip) -- Elengil's + // fatal GeneratorB leak FLASHED lamp 0x1b for 28.4s and nobody saw it + // (night-16; the pod's physical backlit buttons don't have this + // problem). Draw a thin PULSING OUTLINE 3px outside the lamp's + // border, UNDER everything: the lamp keeps its exact size, neighbors + // repaint their own faces, the surfaces cover the inward arc, and + // only the outward ring survives -- respecting the "no on-top + // overlay" rule (twice field-broken, see the note below step 3). + // The ring blinks WITH the lamp (same brightness sample) and only + // while it genuinely alternates (flash bits set AND levels differ); + // it vanishes on the lamp's own dark half-cycle, so it reads as + // "alarm light" rather than geometry. + // GATED OFF BY DEFAULT (BT_ALERT_RING=1 to enable), 2026-08-13: the + // bench + Elengil's layout receipts proved the lamp paints and blinks + // correctly on screen (see [lampblink]; code-identical loop in 913) -- + // the field issue is EXPECTATION (generator leak lights the button by + // the RADAR, authentically; testers watch the MFDs), which the + // handout now states. The ring stays available in case Friday shows + // testers still miss it KNOWING where to look. unsigned long tick = GetTickCount(); - const int kAlertBloom = 8; - for (int i = 0; i < L.buttonCount; i++) + static int s_alertRing = -1; + if (s_alertRing < 0) + { const char *ar = getenv("BT_ALERT_RING"); s_alertRing = (ar && *ar == '1') ? 1 : 0; } + const int kRingGap = 3; // lamp border -> ring inner edge + const int kRingWidth = 2; + for (int i = 0; s_alertRing && i < L.buttonCount; i++) { const BTCockpitBtn &b = L.buttons[i]; int state = CkLampState(b.address); @@ -1201,10 +1216,18 @@ void BTDrawCockpitPanels(LPDIRECT3DDEVICE9 device) && (((state >> 2) & 0x3) != ((state >> 4) & 0x3))) { int shade = BTLampBrightnessOf(state, tick); + if (shade < 2) continue; // dark half-cycle: no ring D3DCOLOR fill, border; - CkLampColors(b.colorClass, shade, b.inert, &fill, &border); - CkFill(device, b.x - kAlertBloom, b.y - kAlertBloom, - b.w + 2 * kAlertBloom, b.h + 2 * kAlertBloom, fill); + CkLampColors(b.colorClass, 3 /*bright*/, b.inert, &fill, &border); + int rx = b.x - kRingGap - kRingWidth; + int ry = b.y - kRingGap - kRingWidth; + int rw = b.w + 2 * (kRingGap + kRingWidth); + int rh = b.h + 2 * (kRingGap + kRingWidth); + // four bars = the ring; the gap between ring and lamp stays band-dark + CkFill(device, rx, ry, rw, kRingWidth, fill); // top + CkFill(device, rx, ry + rh - kRingWidth, rw, kRingWidth, fill); // bottom + CkFill(device, rx, ry, kRingWidth, rh, fill); // left + CkFill(device, rx + rw - kRingWidth, ry, kRingWidth, rh, fill); // right } } @@ -1220,6 +1243,31 @@ void BTDrawCockpitPanels(LPDIRECT3DDEVICE9 device) 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); + // #172 SHADE-TRANSITION receipt (BT_LAMP_LOG only, first 24 blinks): + // proves the surround paint loop actually COMPUTES the alternation -- + // the "did the pixels flash" question, answered without a screenshot + // (the PNG-capture rig cratered the frame rate to ~1fps and was + // scrapped). This base fill loop is code-identical to 913's, so its + // verdict applies to the field build. + if (getenv("BT_LAMP_LOG")) + { + static unsigned char s_lastShade[128]; + static int s_blinkLog = 0; + int slot2 = b.address & 0x7F; + if ((state & 0x3) != 0 + && (((state >> 2) & 0x3) != ((state >> 4) & 0x3)) + && (unsigned char)shade != s_lastShade[slot2] + && s_blinkLog < 24) + { + ++s_blinkLog; + DEBUG_STREAM << "[lampblink] 0x" << std::hex << b.address + << std::dec << " shade " << (int)s_lastShade[slot2] + << "->" << shade << " tick=" << tick + << "\n" << std::flush; + } + s_lastShade[slot2] = (unsigned char)shade; + } + // #172 paint receipt: one line per ALERT edge per lamp -- the night-16 // forensics could prove the lamp STATE but had zero visibility into // pixels ("the seam-to-pixels observability hole"). Cheap: fires only @@ -1235,7 +1283,7 @@ void BTDrawCockpitPanels(LPDIRECT3DDEVICE9 device) DEBUG_STREAM << "[lamppx] 0x" << std::hex << b.address << (alerting ? " alert ON" : " alert OFF") << " cell=" << std::dec << b.w << "x" << b.h - << "+bloom" << (alerting ? kAlertBloom : 0) + << (alerting ? "+ring" : "") << " at " << b.x << "," << b.y << " state=0x" << std::hex << state << std::dec << "\n" << std::flush;