Rauser-Fade-Bug (Alpha-Cutout), Musik-Laenge-Bug (real_song_end-Erkennung) und Factor5-Melodie gefixt

This commit is contained in:
ARIA
2026-07-22 21:31:33 +00:00
parent 6d196020db
commit 094aca0e1e
4 changed files with 282 additions and 38 deletions
+129 -25
View File
@@ -11,15 +11,27 @@ Serie vermessen, siehe NOTES.md Abschnitt "Boot-Sequenz vermessen". Kurzfassung:
mit "!") faehrt von OBEN ein, RAUSER2 ("ADVERTAINMENT") steht von Anfang
an fest. Sobald beide Teile ihre Endposition erreicht haben, spielt ein
kurzer Sound-Sting (TITEL2.TFX/.SAM -- das kuerzeste der 3 Musikstuecke,
genau ein "Bling"-Jingle, kein Loop). Danach faedet der WEISSE
Hintergrund kurz zu GRAU um (NICHT weiss -- war ein Fehler in v1!), haelt
kurz, dann schwarze Pause. WICHTIG: alle Rauser-Assets werden nur bei
**halber Pixel-Groesse** (0.5x) ihrer PCC-Nativaufloesung gezeichnet --
bei 1:1 sind sie in der echten DOSBox-Aufnahme klar zu groß (per
Pixel-Vermessung bestaetigt, siehe NOTES.md "Rauser/Titelbild-Korrektur").
ein "Bling"-Jingle von ~2s natuerlicher Laenge, kein Loop). Danach faedet
der WEISSE Hintergrund kurz zu GRAU um (NICHT weiss -- war ein Fehler in
v1!), haelt kurz, dann schwarze Pause. WICHTIG: alle Rauser-Assets werden
nur bei **halber Pixel-Groesse** (0.5x) ihrer PCC-Nativaufloesung
gezeichnet -- bei 1:1 sind sie in der echten DOSBox-Aufnahme klar zu groß
(per Pixel-Vermessung bestaetigt, siehe NOTES.md "Rauser/Titelbild-
Korrektur"). KORREKTUR (22.07.2026 abends): die Assets haben keinen
Alphakanal, ihr papierweisser Hintergrund sass als harter Kasten um die
Box-Grafik und fadete beim Uebergang zu Grau NICHT mit (Stefans Meldung:
"Text bleibt hart umrandet stehen"). Fix: make_border_bg_transparent()
macht per Flood-Fill vom Bildrand aus genau diesen Aussenbereich
transparent, isolierte weisse Buchstaben MITTEN im Motiv bleiben
unangetastet.
2. kurze schwarze Pause
3. Factor5-Studio-Logo (FACTOR5.PCC, STATISCHES Bild -- keine Animation,
das "Geister"-Doppellogo ist Teil des Assets selbst), ~6s
das "Geister"-Doppellogo ist Teil des Assets selbst) mit einer EIGENEN
Melodie (ONGAME2.TFX/.SAM, der bis 22.07.2026 abends noch keinem Screen
zugeordnete dritte TFMX-Track -- Stefans Hinweis: "bei factor 5 gibts
auch noch ne melodie"). Der Screen bleibt exakt so lange stehen wie das
Stueck einmal komplett durchspielt (natuerliche Laenge per render_tfmx
erkannt, ~123s), nicht mehr eine feste Dauer.
4. kurze schwarze Pause
5. Kellogg's-Markenlogo (KELLOGGS.PCC) erscheint SOFORT komplett; darunter
faedet der Text "praesentiert" per Palette-Fade von blass nach saettigt-
@@ -58,6 +70,7 @@ sonst loopt die Sequenz weiter wie im Original-Titelbildschirm)
import sys
import os
import time
from collections import deque
sys.path.insert(0, os.path.dirname(__file__))
from kellogg_formats import DATContainer, load_pcc
@@ -109,6 +122,67 @@ def indices_to_surface(width, height, indices, palette):
return surf.convert()
def make_border_bg_transparent(surface, threshold=235):
'''Fix fuer Stefans Meldung (22.07.2026 abends): "der Rauser-Text fadet
nicht mit aus, bleibt hart umrandet". Ursache: die Rauser-PCC-Assets haben
KEINEN Alphakanal -- ihr papierweisser Hintergrund sitzt als hartes
Rechteck um die eigentliche Box-Grafik (RAUSER-Schriftzug/ADVERTAINMENT/
"!"-Box). Auf weissem Bildhintergrund faellt das nicht auf, aber sobald
die Szene zu Grau ueberblendet, bleibt dieses Rechteck stur weiss stehen
statt mit auszufaden. Ein v1-Versuch mit set_colorkey() wurde verworfen,
weil er auch die WEISSEN BUCHSTABEN in RAUSER1 durchsichtig machte.
Fix: Flood-Fill vom Bildrand aus ueber alle "papierweissen" Pixel (alle
Kanaele >= threshold) -- NUR die vom Rand aus zusammenhaengende Flaeche
wird transparent gemacht. Isolierte weisse Pixel MITTEN im Motiv (z.B.
weisse Buchstaben im RAUSER-Schriftzug) beruehren den Bildrand nicht und
bleiben deshalb unangetastet. Ergebnis: eine Surface mit echtem
Alphakanal, die beim Blit auf einen sich veraendernden Hintergrund
(weiss->grau) sauber durchscheint, statt eine harte Kante zu zeigen.'''
w, h = surface.get_size()
surf = surface.convert_alpha()
px = pygame.PixelArray(surf)
def is_bg(x, y):
color = surf.unmap_rgb(px[x, y])
return color.r >= threshold and color.g >= threshold and color.b >= threshold
seen = bytearray(w * h)
dq = deque()
def consider(x, y):
if not seen[y * w + x] and is_bg(x, y):
seen[y * w + x] = 1
dq.append((x, y))
for x in range(w):
consider(x, 0)
consider(x, h - 1)
for y in range(h):
consider(0, y)
consider(w - 1, y)
while dq:
cx, cy = dq.popleft()
if cx + 1 < w:
consider(cx + 1, cy)
if cx - 1 >= 0:
consider(cx - 1, cy)
if cy + 1 < h:
consider(cx, cy + 1)
if cy - 1 >= 0:
consider(cx, cy - 1)
del px
alpha = pygame.surfarray.pixels_alpha(surf)
for x in range(w):
for y in range(h):
if seen[y * w + x]:
alpha[x, y] = 0
del alpha
return surf
class Assets:
def __init__(self, dat_path):
self.container = DATContainer(dat_path)
@@ -121,10 +195,16 @@ class Assets:
w, h, indices, palette = self.pcc(name)
return indices_to_surface(w, h, indices, palette)
def pcc_surface_scaled(self, name, scale=ASSET_DISPLAY_SCALE):
def pcc_surface_scaled(self, name, scale=ASSET_DISPLAY_SCALE, cut_border_bg=False):
'''Skaliert BEIDE Achsen um scale -- fuer die Rauser-Assets (dort per
Pixelvermessung als gleichmaessige 0.5x-Verkleinerung bestaetigt).'''
Pixelvermessung als gleichmaessige 0.5x-Verkleinerung bestaetigt).
cut_border_bg=True macht den vom Bildrand aus erreichbaren papierweissen
Hintergrund transparent (siehe make_border_bg_transparent) -- fuer die
Rauser-Teile, damit sie beim Fade zu Grau nicht als harte weisse
Kaesten stehen bleiben.'''
surf = self.pcc_surface(name)
if cut_border_bg:
surf = make_border_bg_transparent(surf)
w, h = surf.get_size()
new_size = (max(1, round(w * scale)), max(1, round(h * scale)))
return pygame.transform.smoothscale(surf, new_size)
@@ -213,19 +293,15 @@ def build_rauser_parts(assets):
'''Liefert die drei skalierten Rauser-Teile + ihre Endpositionen auf einer
320x240-Leinwand (RAUSER1 links, RAUSER3 rechts daneben, RAUSER2 darunter
mittig -- alle bei ASSET_DISPLAY_SCALE, siehe Docstring).'''
r1 = assets.pcc_surface_scaled('RAUSER1') # "RAUSER"-Box
r2 = assets.pcc_surface_scaled('RAUSER2') # "ADVERTAINMENT"
r3 = assets.pcc_surface_scaled('RAUSER3') # "!" auf blauer Box
# cut_border_bg=True (Fix 22.07.2026 abends): macht den papierweissen
# Hintergrund um jede Box transparent (nur die vom Bildrand aus
# zusammenhaengende Flaeche, siehe make_border_bg_transparent) -- vorher
# blieb hier ein harter weisser Kasten stehen, der beim Fade zu Grau nicht
# mitgefadet ist (Stefans Meldung "Text bleibt hart umrandet stehen").
r1 = assets.pcc_surface_scaled('RAUSER1', cut_border_bg=True) # "RAUSER"-Box
r2 = assets.pcc_surface_scaled('RAUSER2', cut_border_bg=True) # "ADVERTAINMENT"
r3 = assets.pcc_surface_scaled('RAUSER3', cut_border_bg=True) # "!" auf blauer Box
# BEKANNTE UNSCHOENHEIT (bewusst NICHT "gefixt"): die PCC-Assets haben
# weissen Hintergrund in ihrer eigenen Bounding-Box (kein Alpha-Kanal).
# Auf weissem Grund faellt das nicht auf, nach dem Fade zu Grau sieht man
# einen schwachen weissen Kasten um RAUSER2/RAUSER3. Ein Versuch, Weiss
# per set_colorkey() durchsichtig zu machen, wurde wieder verworfen --
# dadurch wurden auch die WEISSEN BUCHSTABEN in RAUSER1 durchsichtig
# (schlimmer als der Kasten). Sauberer Fix braucht eine echte
# Alpha-Maske pro Asset statt eines einzelnen Colorkeys; low priority,
# siehe "Noch offen" unten.
total_w = r1.get_width() + r3.get_width()
total_h = r1.get_height() + r2.get_height()
x0 = (320 - total_w) // 2
@@ -267,7 +343,12 @@ def run_rauser(screen, clock, assets):
sting = None
try:
if pygame.mixer.get_init():
sting_wav = tfmx_audio.render_wav(assets.container, 'TITEL2', seconds=1.2)
# KEIN fester seconds-Cap mehr (Fix 22.07.2026 abends, siehe
# tfmx_audio.render_wav-Docstring) -- render_tfmx erkennt das
# natuerliche Ende von TITEL2 selbst (~2s Jingle) und schneidet
# dort ab. Der alte harte 1.2s-Cap konnte den Sting theoretisch
# noch VOR seinem echten Ende kappen.
sting_wav = tfmx_audio.render_wav(assets.container, 'TITEL2')
sting = pygame.mixer.Sound(sting_wav)
except Exception as exc:
print(f'[intro_sequence] Rauser-Sound-Sting konnte nicht geladen werden: {exc}', file=sys.stderr)
@@ -417,12 +498,30 @@ def run_once(screen, clock, assets):
if quit_requested:
return True
# 3) Factor5 (statisch)
# 3) Factor5 (statisches Bild, ABER mit eigener Melodie -- ONGAME2, der
# dritte/letzte bisher nicht zugeordnete TFMX-Track, siehe Stefans
# Meldung 22.07.2026 abends: "bei factor 5 gibts auch noch ne melodie
# solange das lied ist factor 5 anzeigen und dann zum kellogs
# praesentiert". Der Screen bleibt jetzt exakt so lange stehen wie das
# Stueck einmal komplett durchspielt (~123s natuerliche Laenge, siehe
# tfmx_audio.render_wav-Docstring), statt einer festen 3.0s-Dauer.
factor5 = build_letterboxed(assets.pcc_surface('FACTOR5'))
factor5_duration = 3.0 # Fallback falls kein Audio verfuegbar
factor5_sound = None
try:
if pygame.mixer.get_init():
factor5_wav = tfmx_audio.render_wav(assets.container, 'ONGAME2')
factor5_sound = pygame.mixer.Sound(factor5_wav)
factor5_duration = factor5_sound.get_length()
factor5_sound.play()
except Exception as exc:
print(f'[intro_sequence] Factor5-Melodie konnte nicht geladen werden: {exc}', file=sys.stderr)
def show_factor5(_t):
blit_scaled_centered(screen, factor5, WINDOW_W, WINDOW_H)
quit_requested = wait_or_skip(clock, 3.0, show_factor5)
quit_requested = wait_or_skip(clock, factor5_duration, show_factor5)
if factor5_sound is not None:
factor5_sound.stop()
if quit_requested:
return True
@@ -436,9 +535,14 @@ def run_once(screen, clock, assets):
# bis zum Ende der Sequenz (Original hat vermutlich durchgehende
# Titelmusik ueber Logo+Titelbild+Karte -- noch nicht per Referenz-
# Sichtung bestaetigt, aber plausibelste Annahme fuers erste v1).
# KEIN fester seconds-Cap mehr (Fix 22.07.2026 abends): TITEL ist
# tatsaechlich ~109s lang -- der alte 60s-Cap (plus ein liegen
# gebliebener 8s-Debug-Cache) hat das Stueck weit vor seinem echten Ende
# abgeschnitten. Das war Stefans "Musik ist zu kurz, da fehlt der
# Rest"-Bug. render_tfmx erkennt den natuerlichen Loop-Punkt jetzt selbst.
try:
if pygame.mixer.get_init():
titel_wav = tfmx_audio.render_wav(assets.container, 'TITEL', seconds=60)
titel_wav = tfmx_audio.render_wav(assets.container, 'TITEL')
pygame.mixer.music.load(titel_wav)
pygame.mixer.music.play(-1)
except Exception as exc: