Gitea #48: arm the artifact tripwires BY DEFAULT + surface detections through the matchlog

The user's ask: if the translation-table fix did not quite catch it, will the
next playtest LOG the artifact?  Before this commit, no -- BT_PLANE_AUDIT was
opt-in via env var, and playtest machines set none.

Changes (all engine L4VB16):
  * BT_PLANE_AUDIT is now DEFAULT-ON, =0 opts out.  Armed cost is a couple of
    integer compares per DRAW CALL (not per pixel); confirmed zero log noise
    on a clean default-env run post-fix.
  * A SECOND trap class: out-of-bounds draw STARTS, checked always-on in
    buildDestPointer (the funnel every primitive builds its pointer through).
    Release builds compile Verify() to nothing, so a wild start previously
    computed a silent rogue pointer into (or past) the shared buffer.  Now
    logged ([plane] OOB) and CLAMPED in-bounds.
  * First detection of each class writes a GLITCH matchlog record
    (kind=plane / kind=oob with position+color+mask), so the evidence arrives
    with the round logs the operator already collects -- unattended.
  * Both existing plane-leak trap levels (display primitives + the port-level
    pixmap table scan) flipped to the same default-on gate.

Honest coverage note (in gauges-hud.md): an in-buffer EXTENT overrun from a
valid start (a blit walking past the right/bottom edge into later rows of the
SAME plane) is not trapped -- per-pixel walk checks are too hot.  The plane
trap still catches any such overrun whose color leaks planes, which is the
class the night-3 artifacts belonged to.

Verified: full clean rebuild -- 40 pre-existing LNK2019 only (the
engine->game BTMatchLog linkage resolved, /FORCE-trap checked); default-env
probe run: 0 [plane] lines, game unaffected.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
arcattack
2026-07-25 17:28:13 -05:00
co-authored by Claude Fable 5
parent 6bb03aed0b
commit 1d31af61d7
3 changed files with 69 additions and 4 deletions
+1 -1
View File
@@ -419,7 +419,7 @@ default-ON (`'0'` disables).
| `BT_GAUGE_SKIP_LOG` | `[gskip] <primitive>` per unregistered gauge widget the parse skips |
| `BT_VALVE_LOG` | condenser valve flow distribution |
| `BT_BAYTEST=<frame>` | #46 rig: send message 1 (crit-induced cook-off arm) to the first AmmoBin at the given sim frame — the 10 s fuse then runs live (`scratchpad/baytest.py` / `baypurge.py`) |
| `BT_PLANE_AUDIT` | #48 forensics: log every gauge draw whose color carries bits outside its port's plane mask (the cross-display leak trap; `[plane] LEAK/PORT` lines) |
| `BT_PLANE_AUDIT` | #48 forensics -- **DEFAULT-ON** (`=0` opts out): logs any gauge draw whose color carries bits outside its port's plane mask (`[plane] LEAK/PORT`) AND any out-of-bounds draw start (`[plane] OOB`, clamped). First detection of each also writes a `GLITCH` **matchlog** record, so playtest evidence arrives with the round logs even unattended |
| `BT_LOOP_AUDIT` | `[loop-audit]` per-sound `sample-flag × source-render-type -> AL_LOOPING` (Gitea #51; rig `scratchpad/loopaudit.py`) |
| `BT_LOOP_LEGACY=1` | restore the OLD always-loop rule (`AL_LOOPING = sample != ForceStatic`) for a field A/B — see the loop-flag note in [[wintesla-port]] |
| `BT_AUDIO_DUMP` | once-a-second `[playing]` dump of every playing AL source + gain/pitch/**loop** — catches a stuck looping source red-handed |
+10
View File
@@ -664,6 +664,16 @@ 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
preservable behaviour, so the cycle-fill is a guarded PORT deviation.
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
plane-leak trap, plus an out-of-bounds draw-start trap in `buildDestPointer`
(release `Verify` is a no-op, so a wild start previously wrote silently -- now
logged `[plane] OOB` and clamped). First detection of either class writes a
`GLITCH` matchlog record, so if the artifact has a third cause we have not
seen, the evidence lands in the round logs the operator already collects --
no env setup needed on playtest machines. (Coverage note: an in-buffer
EXTENT overrun from a valid start -- a blit walking past the right/bottom edge
into later rows of the same plane -- is NOT trapped; per-pixel walk checks are
too hot. The plane trap still catches it whenever the color leaks planes.)
SECOND real defect fixed en route: sessions configure the CAMERA seat first
(`cameraInit` -- which builds the MISSION-REVIEW context: `configure(0, sec,
0, 0x00FF, native, rgb, mrpal.pcc)`, a DirectColor context whose table holds
+58 -3
View File
@@ -1768,6 +1768,43 @@ void
Verify(screenY >= 0);
Verify(screenY < height);
// #48 tripwire (DEFAULT-ON; BT_PLANE_AUDIT=0 opts out): release builds
// compile Verify() to NOTHING, so an out-of-bounds draw start silently
// computes a wild pointer into (or past) the shared buffer -- the OTHER
// artifact class this funnel can catch, since every primitive builds its
// start pointer here. Log + clamp; first hit also lands in the matchlog.
{
static int s_oobOn = -1;
if (s_oobOn < 0)
{
const char *e = getenv("BT_PLANE_AUDIT");
s_oobOn = (e != NULL && *e == '0') ? 0 : 1;
}
if (s_oobOn &&
(screenX < 0 || screenX >= width || screenY < 0 || screenY >= height))
{
static int s_oobCount = 0;
++s_oobCount;
if (s_oobCount == 1)
{
extern int BTMatchLogActive();
extern void BTMatchLog(const char *tag, const char *format, ...);
if (BTMatchLogActive())
BTMatchLog("GLITCH", "kind=oob x=%d y=%d w=%d h=%d",
screenX, screenY, width, height);
}
if (s_oobCount <= 100 || (s_oobCount % 500) == 0)
DEBUG_STREAM << "[plane] OOB #" << s_oobCount
<< " draw start at(" << screenX << "," << screenY
<< ") outside " << width << "x" << height
<< " -- clamped" << "\n" << std::flush;
if (screenX < 0) screenX = 0;
if (screenX >= width) screenX = width - 1;
if (screenY < 0) screenY = 0;
if (screenY >= height) screenY = height - 1;
}
}
*pixel_pointer = pixelBuffer.Data.MapPointer +
screenX +
(screenY * width);
@@ -1861,8 +1898,15 @@ void
static void BTPlaneAudit(const char *prim, int color, int bitmask,
Enumeration operation, int x, int y)
{
// DEFAULT-ON (#48 tripwire): playtest machines set no env vars, so the
// trap must arm itself. BT_PLANE_AUDIT=0 opts out. Cost when armed is a
// couple of integer ops per DRAW CALL (not per pixel).
static int s_on = -1;
if (s_on < 0) s_on = (getenv("BT_PLANE_AUDIT") != NULL) ? 1 : 0;
if (s_on < 0)
{
const char *e = getenv("BT_PLANE_AUDIT");
s_on = (e != NULL && *e == '0') ? 0 : 1;
}
if (!s_on) return;
int leak;
@@ -1879,6 +1923,17 @@ static void BTPlaneAudit(const char *prim, int color, int bitmask,
static int s_count = 0;
++s_count;
// Surface the FIRST detection through the matchlog pipeline -- those files
// are collected after every playtest round, so the evidence arrives even
// if nobody was watching btl4.log.
if (s_count == 1)
{
extern int BTMatchLogActive();
extern void BTMatchLog(const char *tag, const char *format, ...);
if (BTMatchLogActive())
BTMatchLog("GLITCH", "kind=plane prim=%s x=%d y=%d color=0x%x mask=0x%x leak=0x%x op=%d",
prim, x, y, color, bitmask, leak, (int)operation);
}
if (s_count <= 200 || (s_count % 500) == 0)
DEBUG_STREAM << "[plane] LEAK #" << s_count << " " << prim
<< " at(" << x << "," << y << ")"
@@ -3921,7 +3976,7 @@ void
{
{
static int s_paOn = -1;
if (s_paOn < 0) s_paOn = (getenv("BT_PLANE_AUDIT") != NULL) ? 1 : 0;
if (s_paOn < 0) { const char *e = getenv("BT_PLANE_AUDIT"); s_paOn = (e != NULL && *e == '0') ? 0 : 1; }
if (s_paOn && translation_table != NULL)
for (int _pa = 0; _pa < 256; ++_pa)
if ((translation_table[_pa] & ~bitmask & 0xFFFF) != 0)
@@ -6447,7 +6502,7 @@ void
// #48 forensics: name the PORT + pixmap for any leaking table entries.
{
static int s_paOn = -1;
if (s_paOn < 0) s_paOn = (getenv("BT_PLANE_AUDIT") != NULL) ? 1 : 0;
if (s_paOn < 0) { const char *e = getenv("BT_PLANE_AUDIT"); s_paOn = (e != NULL && *e == '0') ? 0 : 1; }
if (s_paOn)
for (int _pa = 0; _pa < 256; ++_pa)
if ((translationTable[_pa] & ~bitMask & 0xFFFF) != 0)