Second operator report, same day: "what is that yellow orange artifact on the
display, it too shows up here and in bt411 but not in the original" -- a
solid vertical bar beside the RANGE readout on the colour head.
It is the same defect as 5.3.83's MFD bleed. Not a similar one: the same
676 pixels.
HOW IT WAS CAUGHT. 5.3.83 shipped a fix that could not be verified, because
the fix makes something INVISIBLE and the A/B rig's boot cockpit already
showed zero MFD extras -- the unfixed build looked perfectly clean. So this
commit adds a POSITIVE CONTROL instead of another argument:
BT_TRANS_PROVOKE fills the colour head's uninitialised
translationTable[64..255] with 0xFF00 -- every high-byte head bit --
rather than the zero the fix installs. Any draw that indexes the tail
then lights ALL the mono heads at once.
On the boot cockpit that lights exactly 1030 pixels: Eng1/Eng2/Eng3 +1030
each, Mfd1 +684, Mfd2 +704, Mfd3 +1074, Comm +676, against 0 extras with the
fix. The bug was firing the whole time. Our heap simply happened to hold
zeros in that tail -- the same luck the shipped binary has been having, which
is exactly why the operator sees the artifact and the A/B rig does not.
THE OFFENDER, NAMED. oormask.py renders which pixels those are and prints
their horizontal run lengths. The mask is a glyph cluster plus 52 runs of
13px -- a SOLID 13x52 BAR at screen (526,228)-(538,279). Solid means a
rectangle in the source art, so decode the art:
BTSEC1.PCX, the colour head's own 480x640 background, contains exactly
ONE out-of-range value in the entire image: index 254, exactly 676
pixels, a solid 52x13 rectangle at (199,526)-(250,538).
The sec port is configured at ROTATION 270, mapping source (x,y) ->
screen (y, 479-x). That puts the rectangle at screen x 526..538,
y 229..280. Measured mask: x 526..538, y 228..279. Same rectangle, to
the pixel.
ONE READ, TWO SYMPTOMS. translationTable[254] is never written (
BuildSecondaryTranslation fills only 1<<numberOfBits = 64 entries), and
DrawPoint ORs the result in unmasked. Whatever the heap left there decides
which symptom the operator sees:
low 6 bits set -> a coloured block on the COLOUR head, beside the RANGE
readout. The yellow-orange bar.
high 8 bits set -> garbage in the MFD / ENG / COMM planes. The bleed.
Both reports, one uninitialised int. It also explains the "not in the
original" asymmetry without needing the original to differ in code: it does
not differ, it is just getting zeros there. And it explains BT411 showing it
too -- both reconstructions inherit the read from the archive.
5.3.83's zero-fill therefore cures both, and turns a heap-lottery into a
guarantee. Still not DIRECTLY observed cured, because no rig we have was
showing the artifact to begin with; that honesty is recorded in the file
header rather than smoothed over.
ALSO IN: oormask.py, barbox.py, vis_provoke.conf, and a README section on
positive controls -- when a fix replaces garbage with a benign value, build
the variant that replaces it with a maximally LOUD value, because that turns
"I see no difference" into a number and separates "the fix works" from "this
screen never exercised the path".
METHOD NOTE, recorded in the README because it nearly cost the fix: three
grabs of one running instance score IDENTICALLY, so the within-boot noise
floor is zero -- and that is the wrong floor. Judging a rebuild needs the
ACROSS-boot floor (~6px on the MFD heads). A single bad grab, caught
mid-draw, read as a 2700px regression and nearly got a correct change
reverted.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
130 lines
5.4 KiB
Markdown
130 lines
5.4 KiB
Markdown
# gauge-ab -- the cockpit A/B rig
|
|
|
|
Runs the reconstruction and the shipped 4.10 binary against the **same mount**
|
|
(same `GAUGE/L4GAUGE.CFG`, same egg, same mech) and compares what each one
|
|
paints into the gauge framebuffer.
|
|
|
|
With `L4VIDEO=OFF` and `L4GAUGE=640x480x16`, the DOSBox window *is* the packed
|
|
gauge framebuffer -- the exact buffer the pod's VDB splits into its physical
|
|
heads. So a screen grab is a lossless read of the cockpit.
|
|
|
|
## Running
|
|
|
|
```bash
|
|
bash ab.sh rec # stage the fresh build + run the reconstruction
|
|
bash ab.sh shp # run the shipped binary
|
|
powershell -File grab.ps1 -Out ours.png
|
|
python3 planes.py shipped.png ours.png
|
|
```
|
|
|
|
**Always launch through `ab.sh`.** The confs run `BTL4REC.EXE` out of the
|
|
mount, *not* `build410/btl4opt.exe`. Forgetting to re-stage silently measures
|
|
the previous binary -- on 2026-07-27 that cost a full round of wrong
|
|
conclusions (a fix "did nothing" three times because the tested exe never
|
|
changed, and the correct first hypothesis was discarded on that evidence).
|
|
`ab.sh` re-stages every time and kills any running DOSBox first (two
|
|
instances share `OUT.TXT` and corrupt the trace).
|
|
|
|
## The instruments
|
|
|
|
### `planes.py` -- the per-head scoreboard (use this first)
|
|
|
|
The framebuffer is **plane-packed**: `L4GAUGE.CFG` gives each of the ten
|
|
logical ports a *bit mask*, not a rectangle --
|
|
|
|
```
|
|
configure(8,Heat, 0,0x4000,clut2,blue, NULL); # UL
|
|
configure(9,Comm, 0,0x8000,clut2,red, NULL); # UR
|
|
```
|
|
|
|
-- so all six heads occupy the same x/y and are separated only by bit. RGB
|
|
comparison is therefore meaningless: a "magenta artifact" is really *the wrong
|
|
heads lit at that pixel*. DOSBox presents the buffer as RGB565 with bit
|
|
replication, so the original 16-bit word is recovered exactly by
|
|
`R>>3, G>>2, B>>3`.
|
|
|
|
`planes.py` reports, per head, how many pixels the shipped binary lights, how
|
|
many we light, how many we're **missing**, how many are **extra**, and the
|
|
bounding box of the extras. Missing vs extra is the useful signal: extras are
|
|
*our* bugs, missing is unbuilt work.
|
|
|
|
### `score.py` -- one whole-frame number
|
|
|
|
Coarse (the big MFD heads dominate it). Good for "did this regress", useless
|
|
for locating anything. Prefer `planes.py`.
|
|
|
|
### `BT_VIS_LOG` -- the widget map
|
|
|
|
`set BT_VIS_LOG=1` in the conf makes the build print every gauge widget the
|
|
interpreter constructs, with its port and authored position:
|
|
|
|
```
|
|
[w] pilotList port=9 at 0,0
|
|
[w] rankAndScore port=9 at 33,0
|
|
[gen] port=0 x=443 y=322 lampA='sgena.pcc' ... on=1 col0=3 col1=9
|
|
[cluster] port=4 x=0 y=240 title='qsensors.pcc' banner=1
|
|
```
|
|
|
|
The `[w]` line comes from the single dispatch every widget is built through
|
|
(`MethodDescription::Execute`, MUNGA/GAUGREND.CPP), so the map is complete.
|
|
This is what turns "a diff at framebuffer (67,3)" into "that head has no
|
|
drawing widget there, so look elsewhere".
|
|
|
|
## Method
|
|
|
|
Source inspection has now produced **five** wrong suspects on this one
|
|
artifact; each was settled -- or killed -- by one instrumented run. So:
|
|
|
|
1. `planes.py` first: *which head*, and is it missing or extra?
|
|
2. `BT_VIS_LOG`: which widgets exist on that head, at what position?
|
|
3. Only then read code, and only that widget's.
|
|
|
|
And re-stage before believing any measurement.
|
|
|
|
## Positive controls: making an invisible bug measurable
|
|
|
|
A fix that makes something *disappear* cannot be verified on a screen where
|
|
nothing was visible in the first place. The translation-table artifact
|
|
(BT410 5.3.83) is the worked example, and the trick generalises.
|
|
|
|
`vis_provoke.conf` sets `BT_TRANS_PROVOKE=1`, which makes the
|
|
`BTL4GraphicsPort` ctor (source410/MUNGA_L4/L4GREND.CPP) fill the colour
|
|
head's uninitialised `translationTable[64..255]` with `0xFF00` -- every
|
|
high-byte head bit -- instead of the zero the fix installs. Any draw that
|
|
indexes the tail then lights *all* the mono heads at once, so:
|
|
|
|
```
|
|
bash ab.sh rec # baseline, tail = 0
|
|
... -conf vis_provoke.conf # same binary, tail = 0xFF00
|
|
python3 planes.py shipped.png ours_provoke.png # Eng1/2/3 extras = the hit count
|
|
```
|
|
|
|
On the boot cockpit that is **1030 pixels**. With the fix it is **0**. The
|
|
bug was always firing; the heap simply happened to hold zeros.
|
|
|
|
`oormask.py shipped.png ours_provoke.png mask.png` then renders *where* those
|
|
pixels are and prints their horizontal run lengths. Solid runs mean a
|
|
rectangle in the source art, not scattered noise -- that is what identified
|
|
BTSEC1.PCX's 52x13 block of index 254, which the port's 270-degree rotation
|
|
lands at screen (526,228)-(538,279).
|
|
|
|
`barbox.py <pngs...>` reports what each build draws inside that box in the
|
|
sec plane, which is how "all five captures are identical here" got
|
|
established.
|
|
|
|
**The generalisable move:** when a fix replaces garbage with a benign value,
|
|
build the variant that replaces it with a *maximally loud* value. That
|
|
converts "I cannot see any difference" into a number, and it distinguishes
|
|
"the fix works" from "this screen never exercised the path".
|
|
|
|
## Noise floors are not interchangeable
|
|
|
|
Three grabs of one running instance scored *identically* on the MFD heads, so
|
|
the within-boot noise floor is zero. That floor is the wrong one: it says
|
|
nothing about a fresh process. Re-booting the *same* binary moves the MFD
|
|
heads by ~6px, and only that across-boot floor can judge a rebuild.
|
|
|
|
A first "fixed" capture appeared to regress the MFDs by ~2700px and nearly
|
|
got a correct change reverted. It was a bad grab caught mid-draw. Two
|
|
agreeing runs, compared against the across-boot floor, is the rule.
|