Boot-Sequenz vermessen + Intro-Player gebaut, echten PCC-Decoder-Regressionsbug gefixt

- Regression gefunden+gefixt: pcc_to_png.py war durch einen frueheren Commit
  versehentlich auf den kaputten Hand-RLE-Decoder zurueckgefallen (der
  ImageMagick-Fix ging verloren). Jetzt: kellogg_formats.load_pcc() nutzt
  Pillows PCX-Decoder direkt, kein eigener RLE-Code mehr im Projekt. Alle
  77 PCC neu gerendert, AE=0 (pixelidentisch) zu den alten guten PNGs.
- Boot-Sequenz per echter DOSBox-Screenshot-Serie (0.3s-Intervall) vermessen:
  Rauser-Karte -> Factor5-Logo (statisch, keine Animation) -> Kellogg's-Logo
  mit Palette-Fade des "praesentiert"-Texts (Indizes 34/42/43/44/45) ->
  Titelbild "Tony & Friends in Kellogg's Land" mit echtem Vorhang-Wipe-Effekt
  (320x480 aus 4 Quadranten KELL256A-D, obere Haelfte faehrt runter, untere
  hoch, treffen sich in der Mitte). Korrigiert eine fruehere Fehlannahme:
  der Wipe sitzt auf dem TITELBILD, nicht auf dem Kellogg's-Markenlogo.
- tools/intro_sequence.py: erster spielbarer pygame-Meilenstein, baut die
  komplette Sequenz nach (Asset-Loading, Palette-Rendering, Timing, echter
  Palette-Fade, Wipe-Effekt). Headless getestet, visuell verifiziert.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
ARIA
2026-07-22 16:13:32 +00:00
co-authored by Claude Sonnet 5
parent 3622bd0a1a
commit ea1f5b951e
4 changed files with 456 additions and 89 deletions
+41
View File
@@ -61,6 +61,47 @@ def get_pcx_palette(pcc_bytes):
return [tuple(pal_bytes[i:i+3]) for i in range(0, 768, 3)]
def load_pcc(pcc_bytes):
'''Decode a .PCC (= plain PCX v5, 8bpp, RLE, own trailing 256-color
palette) via Pillow's battle-tested PCX decoder instead of a hand-rolled
RLE parser -- see git history 2026-07-22 (commits 5efba4b/62af15e/3410a53)
for why: TWO from-scratch RLE implementations silently produced visibly
wrong pixels (shifted/duplicated content) for full-screen 320x200 images
(KARTE.PCC, KELLOGGS.PCC) despite being "verified" -- Pillow avoids that
whole class of bug entirely.
Returns (width, height, indices, palette):
indices -- flat list[int] of palette indices, len width*height, row-major
palette -- list of 256 (r,g,b) tuples (this file's OWN embedded palette,
not any global one)
'''
from PIL import Image
import io
img = Image.open(io.BytesIO(pcc_bytes))
img.load()
if img.mode != 'P':
raise ValueError(f'expected palette (P) mode PCX, got {img.mode}')
width, height = img.size
indices = list(img.getdata())
raw_pal = img.getpalette() # flat [r,g,b, r,g,b, ...], may be shorter than 768
raw_pal = (raw_pal + [0] * 768)[:768]
palette = [tuple(raw_pal[i:i+3]) for i in range(0, 768, 3)]
return width, height, indices, palette
def pcc_to_rgba(pcc_bytes, transparent_index=None):
'''Convenience wrapper around load_pcc(): returns (width, height, rgba_bytes)
ready for e.g. pygame.image.frombuffer(rgba, (w,h), 'RGBA') or PIL Image.frombytes.
If transparent_index is given, that palette index becomes alpha=0.'''
width, height, indices, palette = load_pcc(pcc_bytes)
out = bytearray(width * height * 4)
for i, idx in enumerate(indices):
r, g, b = palette[idx & 0xFF]
a = 0 if (transparent_index is not None and idx == transparent_index) else 255
out[i*4:i*4+4] = bytes([r, g, b, a])
return width, height, bytes(out)
def get_bob_palette(entries, name):
'''name = basename without extension, e.g. 'TONY' for TONY.BOB.
Implements the palette-selection rules from Form1.cs (BOB case).'''