Files
TeslaRel410/emulator/render-bridge/gauge-ab/barbox.py
T
CydandClaude Fable 5 943219345a BT410 5.3.84: the yellow bar and the MFD bleed are ONE bug -- BTSEC1.PCX index 254, and the offender is named
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>
2026-07-29 22:36:59 -05:00

38 lines
1.1 KiB
Python

#
# What does each build draw in the out-of-range BAR box?
#
# The provoke mask localised the offending block to a 13x52 rectangle. The
# question that decides whether the fix cures the operator's yellow bar is
# simply: in the SEC (colour) plane, does the shipped binary light those
# pixels, and do we?
#
import sys
from PIL import Image
BOX = (8, 52, 648, 531)
BAR = (526, 228, 539, 280) # x0,y0,x1,y1 in cropped coords
SEC = 0x003F
def scan(path):
im = Image.open(path).convert("RGB").crop(BOX)
px = im.load()
lit = 0
vals = {}
for y in range(BAR[1], BAR[3]):
for x in range(BAR[0], BAR[2]):
r, g, b = px[x, y]
wd = ((r >> 3) << 11) | ((g >> 2) << 5) | (b >> 3)
v = wd & SEC
if v:
lit += 1
vals[v] = vals.get(v, 0) + 1
top = sorted(vals.items(), key=lambda kv: -kv[1])[:3]
return lit, top
for p in sys.argv[1:]:
lit, top = scan(p)
print("%-20s sec-lit %4d/676 top sec values: %s" %
(p, lit, ", ".join("0x%02X x%d" % t for t in top)))