Put the frame in a container with no decoder, and find the palette is not free

ROADMAP K2. DLXP1: a 49,664 B record that is 97 sectors exactly, no index and
no length word, because a packed record's length is geometry rather than
content. 582.0 KB/s, which is what FINDINGS 61.9 predicted to the tenth, and it
encodes in 3.3 s because there is no k-means in it.

px68k's own x68k/gvram.c renders the container's bytes index-exact with the
harness computing no interleave -- the only test that can catch an encoder whose
byte order is wrong, since a container round-trips against its own inverse
either way. Both negative controls fail as they must.

The picture is re-derived against this project's builder rather than PIL's
(34.05 dB against 61.9's 34.08) and the GGGGGRRRRRBBBBBI word is charged for the
first time in this tree: 0.53 dB, on every row, so it moves no comparison.

What the control found is the finding. A packed container on a SCENE palette
lands exactly on the codec's ceiling, so the whole +2.31 dB is the per-frame
palette and nothing else -- and 231 of 256 entries change every frame, which
makes a mismatched paint 12.8 dB worse than the correct pairing, on screen for
roughly half of every frame slot if buffer mode does not blank. So B2 now
decides which packed CONTAINER ships, not only which player. The fallback is
already a flag: --scene-palette --no-palette is 30.79 dB, zero churn, 576.0 KB/s
and still +2.07 dB on the shipping codec.

62.5 is priced and is a wash: palette first 20.32 dB, palette last 20.33.

Claude-Session: https://claude.ai/code/session_01194oWYW8DQXK1SZ2DnChW6
This commit is contained in:
prosolis
2026-08-25 07:31:05 -07:00
parent 07f36c2af9
commit f1007a0dbc
15 changed files with 1189 additions and 16 deletions
+35
View File
@@ -65,6 +65,41 @@ def scene_palette(rgb, colors=256, stride=3, reserve_black=True):
return ref, pal
def frame_palette(rgb1, colors=254):
"""`scene_palette`'s sibling, for the PACKED layout: ONE FRAME, 254 colours.
The codec cannot have this. Every codeword it emits is an index INTO
`scene_palette`, so its palette is shared scene-wide and 31.33 dB is a
ceiling no bitrate crosses (FINDINGS 61.9). A literal frame has no
codebooks, so nothing forces a shared palette on it.
The layout spends TWO entries where `--reserve-black` spends one (47.2):
index 0 is the TRANSPARENCY KEY of the top graphics page and must never
appear in the picture, and black therefore lives at 255 for the letterbox.
So the picture gets 254.
The +1 shift is the whole mechanism, and it is why this does NOT go through
a P-mode reference image the way `scene_palette` does. `palettise` maps
against the FINAL 256-entry table, and that table has (0,0,0) at both 0 and
255 -- a nearest-colour mapper is free to pick either, and there is no way to
forbid the one that must stay unused. Quantising to 254 and shifting keeps
index 0 free BY CONSTRUCTION rather than by hoping the mapper agrees, and it
is still exact: `pal[idx]` reproduces the quantiser's own rendering.
Returns (pal (256,3) uint8, idx (H,W) uint8 in 1..254).
"""
q = Image.fromarray(rgb1).quantize(colors=colors, method=Image.MEDIANCUT,
dither=Image.NONE)
raw = q.getpalette()
if len(raw) < colors * 3:
raise ValueError(f"quantiser returned {len(raw)//3} entries, wanted {colors}")
pal = np.array(raw[:colors * 3], dtype=np.uint8).reshape(-1, 3)
pal = np.vstack([np.zeros((1, 3), np.uint8), pal, np.zeros((1, 3), np.uint8)])
idx = np.asarray(q, dtype=np.uint8) + np.uint8(1)
if idx.min() < 1 or idx.max() > colors:
raise ValueError("index 0 (transparency key) or 255 (black) got used")
return pal, idx
def palettise(rgb, ref):
return [np.asarray(Image.fromarray(r).quantize(palette=ref, dither=Image.NONE),
dtype=np.uint8) for r in rgb]