From b366da6f1ec43bf7d8d5d4b04f45e85e2c9b8563 Mon Sep 17 00:00:00 2001 From: Cyd Date: Wed, 29 Jul 2026 21:06:22 -0500 Subject: [PATCH] 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 --- restoration/source410/RENDER-ROADMAP.NOTES.md | 72 +++++++++++++++++++ 1 file changed, 72 insertions(+) diff --git a/restoration/source410/RENDER-ROADMAP.NOTES.md b/restoration/source410/RENDER-ROADMAP.NOTES.md index e8dd6423..67214232 100644 --- a/restoration/source410/RENDER-ROADMAP.NOTES.md +++ b/restoration/source410/RENDER-ROADMAP.NOTES.md @@ -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 = ;`): + 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.