BT410 5.3.82: the MFD bleed -- mechanism found (uninitialised translation table)
Operator report: artifacts bleed from the colour/radar display into the MFDs, present in BT411 too, absent in the original. Almost certainly the same "intermittent plane-write artifact" that survived seven earlier hypotheses. The chain, every link read from the code: 1. The colour head is SIX BITS (L4GAUGE.CFG:4395, sec mask 0x003F), and the MFDs are bits 8-15 of the same 16-bit words -- one plane-packed framebuffer, which is why a colour-head defect surfaces as MFD garbage. 2. BuildSecondaryTranslation (L4VB16.CPP:5419) fills exactly 64 entries of int translationTable[256]; entries 64..255 are never initialised. 3. DrawPixelMap8 (L4VB16.CPP:2708) indexes that table with an 8-BIT source pixel -- no clamp. 4. DrawPoint/Replace (L4VB16.CPP:637) ORs the colour in UNMASKED; the writer assumes the caller already confined it to the port's bits. 5. The sec port's own art supplies out-of-range indices, and they are SENTINELS not colour data: AVACRIT.PCC is 94% <= 63 with 6% at exactly 231; BTSEC1.PCX is 99.8% <= 63 with 0.2% at 254. Transparent they are skipped; OPAQUE they index the uninitialised tail. Why the original is clean: translationTable lives in a heap-allocated port, and on the pod that allocation was almost certainly zeroed -- garbage reads as colour 0, black, invisible. Our heap has different history. That also explains the intermittency that defeated the earlier hypotheses: it depends on heap contents, not on drawing logic. Narrowed to three opaque call sites (BTL4GAU2.CPP:916/1159, BTL4GAUG.CPP:1953). The fix belongs in our tree via the source410-shadows- CODE rule -- zero the table's tail after BuildSecondaryTranslation -- which is worth doing regardless of which site is guilty, since it turns a heap-dependent artifact into a black pixel. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1969,3 +1969,75 @@ free, since every limb hinge has the same problem.
|
||||
So the reconstruction's articulation work is DONE and correct as of 5.3.80;
|
||||
what remains is a decoder gap in the viewing tool, which is a different
|
||||
component with a different owner.
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
THE MFD BLEED (operator report): MECHANISM FOUND -- AN UNINITIALISED TRANSLATION
|
||||
TABLE INDEXED BY 8-BIT ART ON A 6-BIT PORT
|
||||
--------------------------------------------------------------------------------
|
||||
Operator, 2026-07-29: artifacts bleed from the radar/colour display into the
|
||||
MFDs -- present in the BT411 builds too, ABSENT in the original. That last
|
||||
clause is the key: whatever it is, both reconstructions share it and the
|
||||
shipped game does not. This is almost certainly the same "intermittent
|
||||
plane-write artifact" that has survived seven earlier hypotheses in this file.
|
||||
|
||||
THE CHAIN, every link read from the code:
|
||||
|
||||
1. L4GAUGE.CFG:4395 configure(0,sec,270,0x003F,clut0,rgb,btspal.pcc)
|
||||
The colour head is SIX BITS. The MFDs are bits 8-15 of the SAME 16-bit
|
||||
words (0x0100..0x8000), which is why a colour-head defect can only ever
|
||||
show up as MFD garbage -- one framebuffer, ten plane-packed ports.
|
||||
|
||||
2. L4VB16.CPP:5419 L4GraphicsPort::BuildSecondaryTranslation()
|
||||
BitWrangler wrangler(bitMask & 0xFF, 8);
|
||||
do { *destination++ = wrangler.Value; } while (wrangler.IncrementActive());
|
||||
For mask 0x3F this writes exactly 64 entries of `int translationTable[256]`
|
||||
(L4VB16.HPP:487). Entries 64..255 are NEVER INITIALISED.
|
||||
|
||||
3. L4VB16.CPP:2708 DrawPixelMap8
|
||||
color = (Word) translation_table[source_data];
|
||||
An 8-BIT source pixel indexes that table directly. No clamp, no mask.
|
||||
|
||||
4. L4VB16.CPP:637 DrawPoint / Replace
|
||||
bitmask = ~bitmask;
|
||||
*dest = (Word)((*dest & bitmask) | color);
|
||||
The colour is OR'd in UNMASKED. The writer's contract is "caller passes a
|
||||
colour already confined to this port's bits" -- which holds for entries
|
||||
0..63 and is violated by whatever garbage sits at 64..255.
|
||||
|
||||
5. The art supplies the out-of-range indices. Decoding the sec port's own
|
||||
bitmaps (the CFG scopes widgets with `port = <name>;`):
|
||||
AVACRIT.PCC 172x217 94%% of pixels <= 63, 6%% at exactly 231
|
||||
BTSEC1.PCX 480x640 99.8%% <= 63, 0.2%% at exactly 254
|
||||
ADPAL.PCC 2x5 all 10 pixels = 99 (a palette STRIP, not art)
|
||||
The high values are SENTINELS -- transparency keys and ramp markers --
|
||||
not general colour data. Drawn transparent they are skipped and never
|
||||
touch the table; drawn OPAQUE they index the uninitialised tail.
|
||||
|
||||
WHY THE ORIGINAL IS CLEAN. translationTable lives in a heap-allocated
|
||||
L4GraphicsPort. On the pod the allocation was almost certainly fresh zeroed
|
||||
memory, so entries 64..255 read 0 -> colour 0 -> black -> invisible against a
|
||||
black head. Our build (and BT411's) has different heap history, so the same
|
||||
reads return junk with bits in the MFD planes. That also explains the
|
||||
intermittency that defeated the earlier hypotheses: the artifact depends on
|
||||
heap CONTENTS, not on any drawing logic, which is exactly why it never
|
||||
correlated with anything in the widget code.
|
||||
|
||||
SUSPECTS, narrowed to three. Only the OPAQUE path can push a sentinel through
|
||||
the table; our reconstruction has exactly three such call sites:
|
||||
BTL4GAU2.CPP:916 localView.DrawBitMapOpaque(0, 0, base)
|
||||
BTL4GAU2.CPP:1159 view->DrawBitMapOpaque(0, 0, bmp)
|
||||
BTL4GAUG.CPP:1953 localView.DrawBitMapOpaque(foregroundColor, 0, bit_map, ...)
|
||||
Next step is to check DrawBitMapOpaque's semantics -- whether it SUBSTITUTES
|
||||
the background colour for key pixels (safe) or maps every pixel through the
|
||||
table (the bug) -- and then which of the three draws sec-port art containing
|
||||
231/254.
|
||||
|
||||
THE FIX, when it comes, goes in OUR tree, not the archive. L4GREND.CPP:543
|
||||
constructs the ports, and translationTable/BuildSecondaryTranslation are
|
||||
protected, so a subclass cannot be slipped in from outside. The established
|
||||
mechanism is the build's source410-shadows-CODE rule: bring the TU into
|
||||
restoration/source410/MUNGA_L4/ and zero the tail there
|
||||
(`for (i = 1 << numberOfBits; i < 256; ++i) translationTable[i] = 0;`), which
|
||||
makes our behaviour deterministic AND matches what the original actually did
|
||||
at runtime. Worth doing regardless of which call site is guilty -- it turns a
|
||||
heap-dependent artifact into a black pixel.
|
||||
|
||||
Reference in New Issue
Block a user