5.3.85 raised a hypothesis and explicitly flagged it as unverified: the 1345
colour-mapper entries (656 cmCrit, 662 cmHeat, 27 cmArmor) all sit on port 0,
their mode is ModeSecondaryCritical, and they name adpal.pcc / adpal2.pcc /
heatpal.pcc -- three files the art sweep flags, with ADPAL and ADPAL2 being 2x5
and ENTIRELY index 99. If those pixels were blitted as colour indices, 1345
widgets would each be writing out-of-range on the six-bit head, which would
dwarf the 676px bar and would fire in-mission, exactly where the operator sees
the artifact.
Read the implementation. It does not happen.
ColorMapper::ColorMapper (BTL4GAUG.CPP:389) takes the two names into the
palette8Bin -- not a pixelmap bin, not a bitmap bin -- and the execute path
(:526) is
Palette8 *palette = warehouse->palette8Bin.GetIfAlreadyExists(...);
PaletteTriplet &entry = palette->Color[currentColorIndex];
...
graphicsPort->SetColor(&entry, colorSlot);
It reads the file's VGA PALETTE, lifts one RGB triple, and pushes it into a
hardware colour slot. No pixel of ADPAL ever reaches a framebuffer, so its
index-99 content cannot matter. The palette PAIR is a flash toggle
(paletteToggle alternates per frame), which is also what twoColorMode's
name comparison is for.
NEGATIVE. Recorded as a closed hypothesis rather than deleted, so it does not
get re-raised the next time somebody greps the sweep and sees ADPAL at 100%
out-of-range.
What this leaves: the in-mission exposure, if there is one, is NOT the
mappers. The *CRIT/*HEAT silhouettes still carry 1500-2500px of index 231
each, and which widget blits them is still unestablished. The boot cockpit
does not draw them, which is why it lights only 1030 pixels.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
138 lines
6.5 KiB
Markdown
138 lines
6.5 KiB
Markdown
# L4GREND.CPP — reconstruction shadow notes
|
||
|
||
The shadow exists for ONE reason: to construct a `BTL4GraphicsPort` instead of
|
||
an `L4GraphicsPort`, so the colour head's uninitialised translation-table tail
|
||
gets zeroed. Everything else in the file is the archive's, verbatim.
|
||
|
||
The mechanism, the proof, and the honest verification status live in the file's
|
||
own header comment. This sidecar carries the survey work that did not belong in
|
||
a source comment.
|
||
|
||
## The hazard, in one line
|
||
|
||
The colour head is **six bits** (`sec`, mask `0x003F`, `L4GAUGE.CFG`), so
|
||
`BuildSecondaryTranslation` (`L4VB16.CPP:5419`) fills only `1<<numberOfBits` =
|
||
**64** of `int translationTable[256]`. Any source pixel `> 63` reads
|
||
uninitialised memory, and `DrawPixelMap8` ORs the result into the shared
|
||
plane-packed framebuffer **unmasked**.
|
||
|
||
Worth noting that even `Replace` leaks. The opaque inner loop is
|
||
|
||
```c
|
||
color = (Word) translation_table[source_data];
|
||
case GraphicsDisplay::Replace:
|
||
*dest_pointer = (Word)((*dest_pointer & bitmask) | color);
|
||
```
|
||
|
||
`& bitmask` clears *this* port's bits, then `| color` ORs the whole table entry
|
||
back — including whatever garbage sits in other ports' bit positions. There is
|
||
no drawing operation that contains the damage.
|
||
|
||
## The draw site — answers a long-open question
|
||
|
||
`restoration/source410/BT_L4/BTL4GAU2.CPP:1524`, the `bgPixelMap` widget:
|
||
|
||
```c
|
||
localView.MoveToAbsolute(0, 0);
|
||
localView.DrawPixelMap8(True, 0, bg, 0, 0, // opaque, full image
|
||
bg->Data.Size.x - 1, bg->Data.Size.y - 1);
|
||
```
|
||
|
||
`BT_VIS_LOG` places it at `[w] bgPixelMap port=0 at 0,0` — port 0 is `sec`.
|
||
Full-image, opaque, on the six-bit head: every pixel of the background goes
|
||
through the table, sentinels included. Of the three `DrawBitMapOpaque` sites
|
||
that were under suspicion, none is the culprit; this `DrawPixelMap8` is.
|
||
|
||
## Art inventory — 94 of 428 gauge files carry out-of-range pixels
|
||
|
||
Swept every `.PCX`/`.PCC` under `ALPHA_1/REL410/BT/GAUGE` (all the art there is
|
||
— nothing lives outside that directory). Distinct out-of-range values:
|
||
|
||
| index | files | shape | reading |
|
||
|---|---|---|---|
|
||
| 231 | 53 | sparse, ~4–7%, in a mech-silhouette region of 172×217 `*CRIT`/`*HEAT` art | avatar damage/heat art |
|
||
| 255 | 27 | sparse, ~5–7%, `*HT` art (252×200, 229×199, 303×200) | heat backgrounds |
|
||
| 254 | 2 | **solid 52×13 rect** in `BTSEC1.PCX` | the confirmed offender |
|
||
| 226/228/229 | 8/8/7 | 1–2px specks in `QJAK*.PCC` | paint spatter, harmless |
|
||
| 99 | 2 | `ADPAL.PCC`, `ADPAL2.PCC` — 2×5, **100%** at 99 | palette swatches, see below |
|
||
| 100 | 1 | `HEATPAL.PCC` — 3×3, one pixel | ditto |
|
||
| 120, 127 | 1 each | | |
|
||
|
||
`BTSEC1.PCX` — "BT SEC-ondary", the colour head's own background — is the only
|
||
one whose out-of-range region is a **solid rectangle**, and the only one proven
|
||
to reach the screen: 676 pixels of index 254 at source (199,526)-(250,538),
|
||
which the port's **rotation 270** maps to screen (526,228)-(538,279) via
|
||
`screen = (src_y, 479 - src_x)`. The provoke mask measured exactly that box.
|
||
|
||
## What is NOT yet resolved
|
||
|
||
**The 354px cluster.** The provoke control lights 1030 pixels on the boot
|
||
cockpit: 676 for the bar, and a 33×32 cluster at screen (67,3)-(99,34) that is
|
||
*not* file-borne — no gauge file has an out-of-range count anywhere near 354,
|
||
and all art lives in that one directory. It is therefore generated at runtime.
|
||
The obvious suspect, `CreateMutantPixelmap8` (`BTL4GAU3.CPP:1067`), is ruled
|
||
out: it is an inert stub returning NULL, so `recoloredMech` never blits in our
|
||
build. Unattributed. The fix covers it either way, since it zeroes the whole
|
||
tail regardless of who indexes it.
|
||
|
||
That the boot cockpit lights only 1030 pixels says the avatar art is not being
|
||
drawn on that screen. It does not say anything about a live mission — the
|
||
`*CRIT`/`*HEAT` silhouettes carry 1500–2500 pixels of index 231 apiece, so if
|
||
something blits them on port 0 the in-mission artifact is several times the
|
||
size of the bar. Which widget blits them is not yet established.
|
||
|
||
## Closed hypothesis: the colour mappers are NOT a second exposure
|
||
|
||
Recorded because it was raised as a serious escalation and then killed, and the
|
||
kill is worth keeping so nobody re-raises it.
|
||
|
||
The worry: `L4GAUGE.CFG` carries **1345** colour-mapper entries — 656 `cmCrit`,
|
||
662 `cmHeat`, 27 `cmArmor` — `BT_VIS_LOG` puts every one of them on **port 0**,
|
||
their mode is literally `ModeSecondaryCritical`, and they name `adpal.pcc`,
|
||
`adpal2.pcc`, `heatpal.pcc`, three of which the sweep flags (`ADPAL`/`ADPAL2`
|
||
are 2×5 and **entirely** index 99). If those pixels were blitted as colour
|
||
indices, 1345 widgets would each be writing out-of-range on the six-bit head.
|
||
|
||
They are not blitted. `ColorMapper::ColorMapper` (`BTL4GAUG.CPP:389`) takes the
|
||
two names into the **`palette8Bin`**, not a pixelmap or bitmap bin, and the
|
||
execute path (`:526`) is
|
||
|
||
```c
|
||
Palette8 *palette = warehouse->palette8Bin.GetIfAlreadyExists(paletteName[paletteToggle]);
|
||
PaletteTriplet &entry = palette->Color[currentColorIndex];
|
||
...
|
||
graphicsPort->SetColor(&entry, colorSlot);
|
||
```
|
||
|
||
It reads the file's **VGA palette**, takes one RGB triple, and pushes it into a
|
||
hardware colour slot. No pixel of `ADPAL` ever reaches a framebuffer, so their
|
||
index-99 content is irrelevant. The two-palette pair is a flash/toggle
|
||
(`paletteToggle` alternating each frame), which is also why `twoColorMode`
|
||
compares the two names.
|
||
|
||
**Conclusion: negative.** The mappers are not an exposure, and the search for
|
||
the in-mission source has to go elsewhere.
|
||
|
||
## A shipped data bug, found in passing — do NOT fix
|
||
|
||
```
|
||
L4GAUGE.CFG:2513: cmCrit(I,ModeSecondaryCritical,56,adpal.pcc,adpa12.pcc,AmmoBinGAUSS);
|
||
L4GAUGE.CFG:2514: cmCrit(J,ModeSecondaryCritical,57,adpal.pcc,adpa12.pcc,AmmoBinAFC25);
|
||
```
|
||
|
||
`adpa12.pcc` — digit one for lowercase L — does not exist. `adpal2.pcc` does.
|
||
Two of 1345 lines, so the GAUSS and AFC25 ammo-bin criticals have been loading
|
||
a missing palette since 1996. This is the original's bug, present in the
|
||
shipped config; the archive is sacred and the shipped binary lives with it, so
|
||
we live with it too. Noted only so the next person who sees a warning about
|
||
`adpa12.pcc` does not go hunting for a reconstruction defect.
|
||
|
||
## Method note
|
||
|
||
Zero is the right fix precisely because it is invisible — and that is what made
|
||
it unverifiable. The `BT_TRANS_PROVOKE` positive control (fill the tail with
|
||
`0xFF00` instead) is what turned "I cannot see a difference" into "1030 pixels,
|
||
here they are, here is the shape". See `emulator/render-bridge/gauge-ab/README.md`
|
||
for the rig, `oormask.py` for the mask, and the caution about within-boot vs
|
||
across-boot noise floors that nearly cost this fix.
|