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:
+26
-89
@@ -1,105 +1,42 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Finaler PCC-Decoder fuer Kellogg's Tony and Friends (2026-07-22, ARIA).
|
||||
"""PCC-Decoder fuer Kellogg's Tony and Friends -- nutzt Pillows PCX-Decoder.
|
||||
|
||||
Format (verifiziert gegen MENU.PCC/RAUSER1-3/FACTOR5, visuell + byte-exakt):
|
||||
- Byte 0-15: echter PCX-Header-Anfang (Manufacturer=0x0A, Version=5,
|
||||
Encoding=1/RLE, BPP=8, dann Xmin/Ymin/Xmax/Ymax als LE16 bei Offset 4-11).
|
||||
WICHTIG (Bugfix 2026-07-22): Width/Height MUESSEN aus Xmax-Xmin+1 /
|
||||
Ymax-Ymin+1 berechnet werden. Die 2 LE16-Werte bei Offset 12-15 sehen fuer
|
||||
Vollbild-Screens (320x200) zufaellig identisch aus und wurden erst dafuer
|
||||
gehalten -- sind aber tatsaechlich NICHT die Bilddimensionen (vermutlich
|
||||
ein DPI/Reserved-Feld wie im echten 128-Byte-PCX-Header), sondern ein
|
||||
Konstantwert der bei kleinen Sprites/Logos (z.B. RAUSER1.PCC: 182x46,
|
||||
FAC0.PCC: 16x16) komplett falsch war und zu kaputten/leeren Bildern fuehrte.
|
||||
Mit dem Xmax/Ymax-Fix konsumiert z.B. FACTOR5.PCC exakt 100% der
|
||||
komprimierten Bytes (51609/51609) statt vorher mit falscher Hoehe (200
|
||||
statt echten 199) zu ueberlaufen.
|
||||
- Byte 16 .. (len-769): RLE-komprimierte Pixel-Indexdaten, SCANLINE-weise
|
||||
decodiert (pro Zeile wird bis width Pixel gefuellt, ueberschuessige Pixel
|
||||
eines Runs am Zeilenende werden verworfen -- klassisches PCX-Verhalten).
|
||||
RLE-Tupel: Byte mit oberen 2 Bits gesetzt (0xC0-0xFF) = Lauflaenge (&0x3F),
|
||||
gefolgt von einem Wert-Byte. Sonst literaler Pixel.
|
||||
- Letzte 769 Bytes: 0x0C-Marker + 768 Byte (256 x RGB) eingebettete Palette
|
||||
(klassische PCX-v5-256-Farben-Erweiterung). PRO DATEI eigene Palette,
|
||||
keine globale Palette noetig.
|
||||
.PCC ist stinknormales PCX v5 (8bpp, RLE, eigene 256-Farb-Palette am
|
||||
Dateiende) -- bestaetigt gegen https://github.com/movAX13h/tony-and-friends-in-kelloggs-land.
|
||||
|
||||
BEKANNTER OFFENER BUG (Stand 2026-07-22, noch nicht geloest):
|
||||
KELLOGGS.PCC und KARTE.PCC (volle 320x200-Screens) zeigen weiterhin einen
|
||||
lokalen Deko-Fehler (ein "Kerbe"/Notch-Artefakt rechts neben dem Kellogg's-
|
||||
Schriftzug, roter/gelber Fleck der nicht ins Referenz-Screenshot passt).
|
||||
Ausgeschlossen als Ursache: (a) horizontales Rollen/Verschieben des ganzen
|
||||
Bilds -- getestet mit 18 Shift-Kandidaten, Artefakt bleibt IMMER an
|
||||
derselben Position relativ zum Bildinhalt, nicht zur Leinwand -> kein
|
||||
Rotations-/Scroll-Bug. (b) Zeilenweises Verwerfen von RLE-Overshoot --
|
||||
zeilenbasierte und "kontinuierliche" (ohne Row-Reset) Decodierung liefern
|
||||
BYTE-IDENTISCHE Ergebnisse fuer diese Dateien. Vermutung: entweder eine
|
||||
Palette-Fehlzuordnung fuer einzelne Indizes, oder eine RLE-Ambiguitaet bei
|
||||
literalen Pixelwerten >= 0xC0 (die durch (b&0xC0)==0xC0 faelschlich als
|
||||
Lauflaengen-Token statt als literaler Indexwert interpretiert werden
|
||||
koennten) -- noch nicht verifiziert. FACTOR5.PCC's "Geister"-Doppellogo
|
||||
ist dagegen vermutlich KEIN Bug, sondern ein Reflexions-/Schatten-
|
||||
Designelement (Byte-Konsum ist bei diesem File exakt 100%).
|
||||
REGRESSIONS-WARNUNG (gefunden 2026-07-22 abends): Commit 62af15e hatte hier
|
||||
schon mal korrekt auf ImageMagick (`convert pcx:...`) umgestellt, nachdem
|
||||
ZWEI eigene Hand-RLE-Parser nachweislich falsche Pixel lieferten (Bild
|
||||
verschoben/dupliziert bei Vollbild-Screens wie KARTE.PCC/KELLOGGS.PCC).
|
||||
Der naechste Commit (3410a53, eigentlich nur fuer BOB/ICO/MAP gedacht) hat
|
||||
diese Datei aber versehentlich durch eine AELTERE Version mit dem kaputten
|
||||
Hand-Decoder ersetzt -- die bereits committeten PNGs in png_out/ waren davon
|
||||
zum Glueck nicht betroffen (die sind noch von der guten ImageMagick-Version),
|
||||
aber ein Rerun dieses Skripts haette wieder kaputte Bilder erzeugt. Fix jetzt:
|
||||
Pillow statt Hand-RLE oder externem `convert`-Subprocess -- kein Format-
|
||||
Parsing mehr in eigenem Code, nutzt tools/kellogg_formats.load_pcc().
|
||||
|
||||
Nutzung: python3 pcc_to_png.py <input.PCC> <output.png>
|
||||
Schreibt ein PPM und konvertiert via ImageMagick `convert` zu PNG.
|
||||
"""
|
||||
import sys, struct, subprocess, os
|
||||
import sys, os
|
||||
sys.path.insert(0, os.path.dirname(__file__))
|
||||
from kellogg_formats import load_pcc
|
||||
|
||||
def decode_pcc(data):
|
||||
manuf, version, encoding, bpp = data[0], data[1], data[2], data[3]
|
||||
xmin, ymin, xmax, ymax = struct.unpack('<HHHH', data[4:12])
|
||||
width, height = xmax - xmin + 1, ymax - ymin + 1
|
||||
pal_start = len(data) - 769
|
||||
marker = data[pal_start]
|
||||
pixel_region = data[16:pal_start]
|
||||
pal_bytes = data[pal_start+1:pal_start+1+768]
|
||||
palette = [(pal_bytes[i], pal_bytes[i+1], pal_bytes[i+2]) for i in range(0, 768, 3)]
|
||||
|
||||
out = bytearray()
|
||||
i = 0
|
||||
n = len(pixel_region)
|
||||
for row in range(height):
|
||||
row_out = bytearray()
|
||||
while len(row_out) < width and i < n:
|
||||
b = pixel_region[i]; i += 1
|
||||
if (b & 0xC0) == 0xC0:
|
||||
count = b & 0x3F
|
||||
if i >= n:
|
||||
break
|
||||
val = pixel_region[i]; i += 1
|
||||
row_out.extend([val] * count)
|
||||
else:
|
||||
row_out.append(b)
|
||||
row_out = row_out[:width]
|
||||
if len(row_out) < width:
|
||||
row_out.extend([0] * (width - len(row_out)))
|
||||
out.extend(row_out)
|
||||
|
||||
return dict(width=width, height=height, marker_ok=(marker == 0x0C),
|
||||
pixels=bytes(out), palette=palette,
|
||||
consumed=i, pixel_region_len=n)
|
||||
|
||||
def write_png(pixels, palette, width, height, out_png):
|
||||
ppm = out_png + '.ppm'
|
||||
with open(ppm, 'wb') as f:
|
||||
f.write(f'P6\n{width} {height}\n255\n'.encode())
|
||||
buf = bytearray()
|
||||
for px in pixels[:width*height]:
|
||||
r, g, b = palette[px]
|
||||
buf.extend([r, g, b])
|
||||
f.write(bytes(buf))
|
||||
subprocess.run(['convert', ppm, out_png], check=True)
|
||||
os.remove(ppm)
|
||||
|
||||
def main():
|
||||
inp, outp = sys.argv[1], sys.argv[2]
|
||||
with open(inp, 'rb') as f:
|
||||
data = f.read()
|
||||
res = decode_pcc(data)
|
||||
print(f"{inp}: {res['width']}x{res['height']} marker_ok={res['marker_ok']} "
|
||||
f"consumed={res['consumed']}/{res['pixel_region_len']}")
|
||||
write_png(res['pixels'], res['palette'], res['width'], res['height'], outp)
|
||||
width, height, indices, palette = load_pcc(data)
|
||||
print(f"{inp}: {width}x{height}")
|
||||
|
||||
from PIL import Image
|
||||
img = Image.new('P', (width, height))
|
||||
img.putpalette([c for rgb in palette for c in rgb])
|
||||
img.putdata(indices)
|
||||
img.convert('RGB').save(outp)
|
||||
print(f"-> {outp}")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
||||
Reference in New Issue
Block a user