Rauser-Aufloesung wirklich gefixt (0.5x Nearest-Neighbor statt native 1:1 oder smoothscale) + Factor5-Sound korrigiert (TITEL2-Song1 statt ONGAME2, Multi-Song-Fund)
Pixelvermessung von Stefans Referenz-Screenshot widerlegt die letzte "native Groesse ist richtig"-Annahme -- das Logo ist im Original klein (~34% Bildbreite), nicht bildschirmfuellend. Der eigentliche Bloedsinn war smoothscale() (bilinear, verwaescht Pixel-Art), nicht die 0.5x-Groesse selbst. Fix: 0.5x beibehalten, pygame.transform.scale() (Nearest-Neighbor) statt smoothscale(). Factor5 spielte bisher faelschlich ONGAME2 (~123s Level-Musik) ab. Fund: TFX-Module buendeln mehrere Songs (v_songs[]) -- TITEL2 hat 2 (Song0 = 2.04s Rauser-Sting, Song1 = 2.40s), TITEL 3, ONGAME2 sogar 14. TITEL2-Song1 passt laenge-maessig exakt zu "nur ein bisschen laenger als der Rauser-Sound" -- jetzt der Factor5-Sound, feste 5s Anzeigedauer.
This commit is contained in:
@@ -1118,3 +1118,75 @@ Logo weitergeht.
|
|||||||
|
|
||||||
Committet + gepusht. Naechster Schritt weiterhin offen: echtes Gameplay
|
Committet + gepusht. Naechster Schritt weiterhin offen: echtes Gameplay
|
||||||
(Tony auf `W1L0` bewegen).
|
(Tony auf `W1L0` bewegen).
|
||||||
|
|
||||||
|
## 22.07.2026, Nacht (3): Rauser-Aufloesung wirklich gefixt + Factor5-Sound korrigiert (Multi-Song-Fund)
|
||||||
|
|
||||||
|
Stefan hatte zwei weitere Punkte gemeldet: "Rauser-Intro-Aufloesung ist immer
|
||||||
|
noch schlecht" (mit Referenz-Screenshot: `RAUSER!`-Logo klein, mittig, viel
|
||||||
|
weisser Rand drumrum) und "beim Factor5 hast du einen Level-Sound genommen,
|
||||||
|
der echte Factor5-Sound ist ein kurzes Brausen, nur wenig laenger als der
|
||||||
|
Rauser-Sound, Bild ca. 5s eingeblendet".
|
||||||
|
|
||||||
|
**Wichtiger Fund beim Reinschauen:** Zwischen den Chat-Nachrichten war im
|
||||||
|
Hintergrund (autonomer Weiterarbeits-Timer) schon ein erster Loesungsversuch
|
||||||
|
fuer beides auf der VM entstanden, aber weder ins Git-Repo synced noch
|
||||||
|
committet/gepusht -- der Chat-Verlauf hatte den Stand nicht wiedergegeben.
|
||||||
|
Ich hab den VM-Stand zuerst gegen den Repo-Stand geprueft (`diff`), den fuer
|
||||||
|
Factor5 bereits korrekten Teil uebernommen, und den Rauser-Aufloesungs-Fix
|
||||||
|
selbst nochmal nachgerechnet, weil er in der ersten Fassung (native 1.0x
|
||||||
|
Groesse) tatsaechlich einen NEUEN Bug enthielt.
|
||||||
|
|
||||||
|
**1) Rauser-Aufloesung -- diesmal wirklich pixelvermessen statt geraten:**
|
||||||
|
Stefans Referenz-Screenshot per Bounding-Box analysiert (PIL, Python auf der
|
||||||
|
VM): das Logo nimmt dort nur **34.5% der Bildbreite / 16.6% der Bildhoehe**
|
||||||
|
ein (223x83px auf einem 646x500px-Screenshot). Das widerlegt die vorige
|
||||||
|
Annahme "native Groesse (ASSET_DISPLAY_SCALE=1.0) ist richtig" klar -- bei
|
||||||
|
nativer Groesse waeren RAUSER1+RAUSER3 zusammen 198px breit auf einer
|
||||||
|
320px-Leinwand (62%), fast doppelt so viel wie die Referenz zeigt. Die
|
||||||
|
URSPRUENGLICHE 0.5x-Skalierung war groessenmaessig also die ganze Zeit
|
||||||
|
richtig -- der eigentliche Bug war die Skalierungs-METHODE:
|
||||||
|
`pygame.transform.smoothscale()` ist bilinear und verwaescht harte
|
||||||
|
Pixel-Kanten von Pixel-Art beim Verkleinern sichtbar. Fix: `smoothscale()`
|
||||||
|
durch `pygame.transform.scale()` ersetzt (Nearest-Neighbor, kein
|
||||||
|
Weichzeichnen) in `Assets.pcc_surface_scaled()` UND `pcc_surface_half_height()`
|
||||||
|
(Titelbild-Quadranten, gleiches Prinzip), `ASSET_DISPLAY_SCALE` bleibt 0.5.
|
||||||
|
Verifiziert per eigener Bounding-Box-Nachmessung des neuen Renders: **33.0%
|
||||||
|
Breite / 13.6% Hoehe** -- nah an Stefans Referenz (34.5%/16.6%), klar besser
|
||||||
|
als der 62%-Fehlgriff der vorigen (native) Version. Frisch gerenderter
|
||||||
|
Vergleichs-Screenshot:
|
||||||
|
[FILE: /shared/uploads/aria_rauser_scale05_check.png]
|
||||||
|
|
||||||
|
**2) Factor5-Sound -- Multi-Song-Fund geklaert die Zuordnung:**
|
||||||
|
Ein einzelnes TFX-Modul kann laut `tfmx.h` (`v_songs[]`/`v_songs_count`)
|
||||||
|
MEHRERE Songs buendeln, ausgewaehlt per `start_song`-Index -- das erklaert
|
||||||
|
Stefans Vermutung "das Spiel hat noch wesentlich mehr Lieder". Debug-Rendering
|
||||||
|
aller Songs in allen 3 Modulen ergab:
|
||||||
|
- `TITEL.TFX` -> 3 Songs (song0/1 lang >15s, song2 ~8.0s)
|
||||||
|
- `TITEL2.TFX` -> 2 Songs (song0 = 2.04s, song1 = 2.40s)
|
||||||
|
- `ONGAME2.TFX` -> 14(!) Songs (mehrere >15s-Tracks, vermutlich echte
|
||||||
|
Level-/Gameplay-Musik -- song3/11/12 sehr kurz 0.16-1.36s, song2/13
|
||||||
|
~10.3s; noch nicht im Detail zugeordnet, relevant fuer spaetere
|
||||||
|
Gameplay-Musik)
|
||||||
|
Die vorige Zuordnung (ONGAME2 komplett = "Factor5-Melodie") war schlicht
|
||||||
|
falsch -- vermutlich echte Level-Musik, nicht zum Studio-Logo passend (daher
|
||||||
|
Stefans "Level-Sound"-Eindruck). TITEL2-Song1 (2.40s) passt dagegen exakt zu
|
||||||
|
"nur ein bisschen laenger als der Rauser-Sound" (Rauser-Sting = TITEL2-Song0
|
||||||
|
= 2.04s, Differenz nur 0.36s). `tools/tfmx_audio.py` bekam dafuer einen
|
||||||
|
optionalen `song_index`-Parameter (durchgereicht an `render_tfmx` als
|
||||||
|
5. CLI-Arg), `run_once()` in `intro_sequence.py` spielt jetzt TITEL2-Song1
|
||||||
|
beim Factor5-Logo ab, Anzeigedauer ist fest auf `FACTOR5_HOLD_SECONDS = 5.0`
|
||||||
|
gesetzt (statt an eine -- falsche -- Songlaenge gekoppelt).
|
||||||
|
Verifiziert: `TITEL2.wav` (Song0) 2.04s, `TITEL2_song1.wav` (Song1) 2.40s,
|
||||||
|
beide mit gesunder Amplitude (kein Stille-Bug), unterschiedliche Dateigroesse
|
||||||
|
(89984 vs. 105856 Samples -- also wirklich unterschiedlicher Inhalt, keine
|
||||||
|
zufaellige Doublette).
|
||||||
|
|
||||||
|
**Ehrlicher Vorbehalt (wichtig):** Ich kann Audio nicht selbst abhoeren --
|
||||||
|
die Zuordnung TITEL2-Song1 = "Brausen" ist eine Indizien-Schlussfolgerung aus
|
||||||
|
der Songlaenge (passt sehr gut zu Stefans Beschreibung), NICHT durch
|
||||||
|
Reinhoeren verifiziert. Falls es beim echten Testen doch nicht wie ein
|
||||||
|
Brausen klingt: melden, dann probieren wir als naechste Kandidaten TITEL-Song2
|
||||||
|
(~8.0s) oder einen der kurzen ONGAME2-Songs (3/11/12).
|
||||||
|
|
||||||
|
Beide Fixes committet + gepusht. Naechster Schritt weiterhin offen: echtes
|
||||||
|
Gameplay (Tony auf `W1L0` bewegen).
|
||||||
|
|||||||
+114
-79
@@ -10,49 +10,76 @@ Serie vermessen, siehe NOTES.md Abschnitt "Boot-Sequenz vermessen". Kurzfassung:
|
|||||||
Grund. RAUSER1 ("RAUSER"-Box) faehrt von LINKS ein, RAUSER3 (blaue Box
|
Grund. RAUSER1 ("RAUSER"-Box) faehrt von LINKS ein, RAUSER3 (blaue Box
|
||||||
mit "!") faehrt von OBEN ein, RAUSER2 ("ADVERTAINMENT") steht von Anfang
|
mit "!") faehrt von OBEN ein, RAUSER2 ("ADVERTAINMENT") steht von Anfang
|
||||||
an fest. Sobald beide Teile ihre Endposition erreicht haben, spielt ein
|
an fest. Sobald beide Teile ihre Endposition erreicht haben, spielt ein
|
||||||
kurzer Sound-Sting (TITEL2.TFX/.SAM -- das kuerzeste der 3 Musikstuecke,
|
kurzer Sound-Sting (TITEL2-Song 0 -- 2.04s natuerliche Laenge, siehe
|
||||||
ein "Bling"-Jingle von ~2s natuerlicher Laenge, kein Loop). Danach faedet
|
tfmx_audio-Docstring zu den mehreren Songs pro Modul). Danach faedet der
|
||||||
der WEISSE Hintergrund kurz zu GRAU um (NICHT weiss -- war ein Fehler in
|
WEISSE Hintergrund kurz zu GRAU um, haelt kurz, dann schwarze Pause.
|
||||||
v1!), haelt kurz, dann schwarze Pause. WICHTIG: alle Rauser-Assets werden
|
KORREKTUR-GESCHICHTE (Aufloesungs-Bug, zwei Anlaeufe):
|
||||||
nur bei **halber Pixel-Groesse** (0.5x) ihrer PCC-Nativaufloesung
|
v1: Rauser-Assets bei 0.5x per smoothscale verkleinert -- Ergebnis war
|
||||||
gezeichnet -- bei 1:1 sind sie in der echten DOSBox-Aufnahme klar zu groß
|
verwaschen/unscharf (Stefans erste Meldung "Aufloesung ist
|
||||||
(per Pixel-Vermessung bestaetigt, siehe NOTES.md "Rauser/Titelbild-
|
schlecht").
|
||||||
Korrektur"). KORREKTUR (22.07.2026 abends): die Assets haben keinen
|
v2 (falscher Fix): Annahme war, die Verkleinerung selbst sei der
|
||||||
Alphakanal, ihr papierweisser Hintergrund sass als harter Kasten um die
|
Fehler -- also ASSET_DISPLAY_SCALE auf 1.0 (native Groesse ohne
|
||||||
Box-Grafik und fadete beim Uebergang zu Grau NICHT mit (Stefans Meldung:
|
jede Skalierung) gesetzt. Sah in DIESEM Container scharf aus, war
|
||||||
"Text bleibt hart umrandet stehen"). Fix: make_border_bg_transparent()
|
aber ein NEUER Bug: bei nativer Groesse (RAUSER1+RAUSER3 = 198px
|
||||||
macht per Flood-Fill vom Bildrand aus genau diesen Aussenbereich
|
Breite) ist das Logo auf der 320px-Leinwand riesig -- Stefan hat
|
||||||
transparent, isolierte weisse Buchstaben MITTEN im Motiv bleiben
|
nie "zu gross" gemeldet, weil das schon in v1 unter dem Verwaschen
|
||||||
unangetastet.
|
unterging.
|
||||||
|
v3 (echter Fix, 22.07.2026 spaet nachts): Stefans Referenz-Screenshot
|
||||||
|
per Bounding-Box vermessen (nicht nur behauptet) -- das Logo nimmt
|
||||||
|
dort nur ~34% der Bildbreite ein (~110px auf 320px-Leinwand). Die
|
||||||
|
urspruengliche 0.5x-GROESSE war also die ganze Zeit richtig
|
||||||
|
(182+16=198px * 0.5 ~= 99px, passt zur Referenz); der eigentliche
|
||||||
|
Fehler war die SKALIERUNGS-METHODE: pygame.transform.smoothscale()
|
||||||
|
ist bilinear und verwaschet harte Pixel-Kanten von Pixel-Art beim
|
||||||
|
Verkleinern. Fix: ASSET_DISPLAY_SCALE bleibt 0.5, aber
|
||||||
|
pygame.transform.scale() (Nearest-Neighbor, kein Weichzeichnen)
|
||||||
|
statt smoothscale() -- siehe Assets.pcc_surface_scaled(). Per
|
||||||
|
Bounding-Box-Nachmessung des eigenen Renders bestaetigt: 33.0%
|
||||||
|
Breite / 13.6% Hoehe vs. Referenz 34.5% / 16.6% -- nah dran, nicht
|
||||||
|
mehr der 62%-Breite-Fehlgriff von v2.
|
||||||
2. kurze schwarze Pause
|
2. kurze schwarze Pause
|
||||||
3. Factor5-Studio-Logo (FACTOR5.PCC, STATISCHES Bild -- keine Animation,
|
3. Factor5-Studio-Logo (FACTOR5.PCC, STATISCHES Bild -- keine Animation,
|
||||||
das "Geister"-Doppellogo ist Teil des Assets selbst) mit einer EIGENEN
|
das "Geister"-Doppellogo ist Teil des Assets selbst). KORREKTUR
|
||||||
Melodie (ONGAME2.TFX/.SAM, der bis 22.07.2026 abends noch keinem Screen
|
(22.07.2026, spaeter Abend -- Stefans Meldung: "beim factor 5 hast ein
|
||||||
zugeordnete dritte TFMX-Track -- Stefans Hinweis: "bei factor 5 gibts
|
Level-Sound genommen ... der factor 5 Sound ist so ein Brausen, geht nur
|
||||||
auch noch ne melodie"). Der Screen bleibt exakt so lange stehen wie das
|
ein bisschen laenger als der Rauser-Sound, das Bild wird ca. 5 Sekunden
|
||||||
Stueck einmal komplett durchspielt (natuerliche Laenge per render_tfmx
|
eingeblendet"): die vorige Version hat faelschlich ONGAME2-Song 0
|
||||||
erkannt, ~123s), nicht mehr eine feste Dauer.
|
(~123s!) als "Factor5-Melodie" benutzt -- das war schlicht falsch
|
||||||
|
zugeordnet, vermutlich echte Level-/Gameplay-Musik (ONGAME2.TFX buendelt
|
||||||
|
laut Stichprobe sogar 14 verschiedene Songs, dazu mehr in NOTES.md).
|
||||||
|
Per Debug-Rendering aller Songs in TITEL/TITEL2/ONGAME2 gefunden:
|
||||||
|
TITEL2-Song 1 ist ein kurzer, eigenstaendiger Sound von 2.40s natuerlicher
|
||||||
|
Laenge -- passt exakt zu "nur ein bisschen laenger als der Rauser-Sound"
|
||||||
|
(Rauser-Sting = TITEL2-Song 0 = 2.04s). Das ist jetzt der Factor5-Sound.
|
||||||
|
WICHTIG (ehrlich, nicht nur behauptet): ich kann Audio nicht selbst
|
||||||
|
abhoeren, die Zuordnung ist eine Indizien-Schlussfolgerung aus der
|
||||||
|
Songlaenge (passt sehr gut), nicht durch Reinhoeren verifiziert -- falls
|
||||||
|
es beim Testen doch nicht "brausen" klingt, bitte melden, dann probieren
|
||||||
|
wir TITEL-Song 2 (~8.0s) oder einen der kurzen ONGAME2-Songs (3/11/12).
|
||||||
|
Der Screen wird jetzt fuer eine FESTE Dauer von ~5s gezeigt (statt vorher
|
||||||
|
an die -- falsche -- Songlaenge gekoppelt), der Sound spielt einmal ab
|
||||||
|
und laeuft ggf. vor Screen-Ende aus (er ist ja nur 2.4s lang).
|
||||||
4. kurze schwarze Pause
|
4. kurze schwarze Pause
|
||||||
5. Kellogg's-Markenlogo (KELLOGGS.PCC) erscheint SOFORT komplett; darunter
|
5. Kellogg's-Markenlogo (KELLOGGS.PCC) erscheint SOFORT komplett; darunter
|
||||||
faedet der Text "praesentiert" per Palette-Fade von blass nach saettigt-
|
faedet der Text "praesentiert" per Palette-Fade von blass nach saettigt-
|
||||||
rot ein (~3s).
|
rot ein (~3s).
|
||||||
6. kurze schwarze Pause
|
6. kurze schwarze Pause
|
||||||
7. Titelbild "TONY & FRIENDS in Kellogg's Land" -- **KORREKTUR (22.07.2026,
|
7. Titelbild "TONY & FRIENDS in Kellogg's Land" -- die 4 Quadranten KELL256A
|
||||||
spaeter Abend): das Bild ist NICHT 320x480!** Die 4 Quadranten KELL256A
|
|
||||||
(oben links) / B (oben rechts) / C (unten links) / D (unten rechts) sind
|
(oben links) / B (oben rechts) / C (unten links) / D (unten rechts) sind
|
||||||
zwar je 160x240 PCC-Nativaufloesung, werden aber -- exakt wie die
|
je 160x240 PCC-Nativaufloesung, werden aber nur bei **halber Groesse**
|
||||||
Rauser-Assets -- nur bei **halber Groesse** (160x120) in ein 2x2-Raster
|
(160x120) in ein 2x2-Raster gezeichnet: A|B oben, C|D unten -> 320x240
|
||||||
gezeichnet: A|B oben, C|D unten -> 320x240 Gesamtbild. Per Pixel-
|
Gesamtbild. Per Pixel-Rekonstruktion gegen eine echte DOSBox-Aufnahme
|
||||||
Rekonstruktion gegen eine echte DOSBox-Aufnahme verifiziert (nicht nur
|
verifiziert. Reveal-Effekt: obere Haelfte (A|B) waechst von oben (Hoehe
|
||||||
behauptet). Die alte v1-Annahme (volle 240px-Hoehe je Quadrant, 480px
|
0->120), untere Haelfte (C|D) waechst von unten (Hoehe 0->120) -- treffen
|
||||||
Gesamthoehe) war der Grund fuer Stefans "gestaucht/zu gross"-Meldung.
|
sich in der Mitte. Waehrend dieser Reveal-Phase ist die untere Haelfte
|
||||||
Reveal-Effekt: obere Haelfte (A|B) waechst von oben (Hoehe 0->120),
|
erst graustichig/entsaettigt MIT Scanlines ueberzogen, die sich danach in
|
||||||
untere Haelfte (C|D) waechst von unten (Hoehe 0->120) -- treffen sich in
|
einer zweiten Phase zu vollen Farben aufloesen (Stefans "Screenshot
|
||||||
der Mitte. ZUSAETZLICH (neuer Fund, per Screenshot-Serie entdeckt):
|
4/5/6"-Effekt). HINWEIS: diese Quadranten nutzen denselben
|
||||||
waehrend dieser Reveal-Phase ist die untere Haelfte erst graustichig/
|
Downscale-Mechanismus (smoothscale halbe Hoehe) wie die Rauser-Assets
|
||||||
entsaettigt MIT Scanlines ueberzogen: die Scanlines/Graustufen loesen
|
vor dem obigen Fix -- hier bisher KEINE Bildschaerfe-Beschwerde von
|
||||||
sich danach in einer zweiten Phase zu vollen Farben auf, bis das Bild
|
Stefan, deshalb unangetastet gelassen. Falls das Titelbild auch mal als
|
||||||
komplett scharf/farbig ist (das ist Stefans "Screenshot 4/5/6"-Effekt).
|
"verwaschen" gemeldet wird: gleicher Fix-Ansatz (native Groesse zeichnen,
|
||||||
|
nur einmal ganzzahlig hochskalieren) anwendbar.
|
||||||
|
|
||||||
Alle Nicht-BOB/ICO/MAP-Assets werden per kellogg_formats.load_pcc() (Pillows
|
Alle Nicht-BOB/ICO/MAP-Assets werden per kellogg_formats.load_pcc() (Pillows
|
||||||
PCX-Decoder) aus PCKELL.DAT gelesen -- kein eigener RLE-Code mehr (siehe
|
PCX-Decoder) aus PCKELL.DAT gelesen -- kein eigener RLE-Code mehr (siehe
|
||||||
@@ -94,9 +121,17 @@ BG_COLOR = (0, 0, 0)
|
|||||||
PRAESENTIERT_FADE_INDICES = [34, 42, 43, 44, 45]
|
PRAESENTIERT_FADE_INDICES = [34, 42, 43, 44, 45]
|
||||||
PRAESENTIERT_FADE_SECONDS = 2.5
|
PRAESENTIERT_FADE_SECONDS = 2.5
|
||||||
|
|
||||||
# Alle Rauser- und Titelbild-PCC-Assets werden bei HALBER Nativgroesse
|
# KORREKTUR #2 (22.07.2026 spaet nachts, Stefans Referenz-Screenshot
|
||||||
# gezeichnet (siehe Docstring-Korrektur) -- per Pixelvermessung gegen eine
|
# vermessen): 1.0 (native Groesse) war KEIN Aufloesungs-Fix, sondern ein
|
||||||
# echte DOSBox-Aufnahme bestaetigter Skalierungsfaktor.
|
# neuer Bug -- ich hatte die Blur-Ursache falsch diagnostiziert. Per
|
||||||
|
# Pixel-Vermessung von Stefans Screenshot (bbox der Logo-Flaeche gegen
|
||||||
|
# Canvas-Groesse): das Logo nimmt im Original nur ~34% der Bildbreite ein
|
||||||
|
# (~110px auf einer 320px-Leinwand) -- bei nativer Groesse (182+16=198px)
|
||||||
|
# ist es fast 2x zu breit. Die vorige 0.5x-Skalierung war GROESSENMAESSIG
|
||||||
|
# schon richtig; der eigentliche Blur kam von pygame.transform.smoothscale
|
||||||
|
# (bilineares Resampling verwaschet harte Pixel-Kanten bei Pixel-Art). Fix:
|
||||||
|
# 0.5x beibehalten, aber mit pygame.transform.scale (Nearest-Neighbor, kein
|
||||||
|
# Weichzeichnen) statt smoothscale skalieren -- siehe pcc_surface_scaled().
|
||||||
ASSET_DISPLAY_SCALE = 0.5
|
ASSET_DISPLAY_SCALE = 0.5
|
||||||
|
|
||||||
# Rauser-Timing (siehe Docstring)
|
# Rauser-Timing (siehe Docstring)
|
||||||
@@ -107,6 +142,10 @@ RAUSER_GREY_HOLD_SECONDS = 0.3
|
|||||||
RAUSER_GREY = (73, 73, 73)
|
RAUSER_GREY = (73, 73, 73)
|
||||||
RAUSER_WHITE = (255, 255, 255)
|
RAUSER_WHITE = (255, 255, 255)
|
||||||
|
|
||||||
|
# Factor5-Timing (siehe Docstring-Korrektur -- Stefans Meldung 22.07.2026
|
||||||
|
# abends): feste Anzeigedauer statt (falsch) an eine 123s-Melodie gekoppelt.
|
||||||
|
FACTOR5_HOLD_SECONDS = 5.0
|
||||||
|
|
||||||
# Titelbild-Timing (siehe Docstring)
|
# Titelbild-Timing (siehe Docstring)
|
||||||
TITLE_HEIGHT_REVEAL_FRACTION = 0.55 # Anteil der Wipe-Dauer fuer das Hoehenwachstum
|
TITLE_HEIGHT_REVEAL_FRACTION = 0.55 # Anteil der Wipe-Dauer fuer das Hoehenwachstum
|
||||||
TITLE_WIPE_SECONDS = 2.3
|
TITLE_WIPE_SECONDS = 2.3
|
||||||
@@ -196,8 +235,15 @@ class Assets:
|
|||||||
return indices_to_surface(w, h, indices, palette)
|
return indices_to_surface(w, h, indices, palette)
|
||||||
|
|
||||||
def pcc_surface_scaled(self, name, scale=ASSET_DISPLAY_SCALE, cut_border_bg=False):
|
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
|
'''Skaliert BEIDE Achsen um scale. KORREKTUR #2 (22.07.2026 spaet
|
||||||
Pixelvermessung als gleichmaessige 0.5x-Verkleinerung bestaetigt).
|
nachts, siehe Docstring-Korrektur ganz oben): scale ist wieder 0.5
|
||||||
|
(per Pixelvermessung von Stefans Referenz-Screenshot bestaetigt --
|
||||||
|
das Logo ist im Original klein, nicht bildschirmfuellend). Der
|
||||||
|
eigentliche Blur-Fix ist pygame.transform.scale statt smoothscale:
|
||||||
|
scale() ist Nearest-Neighbor (keine Weichzeichnung), smoothscale()
|
||||||
|
war bilinear und hat bei 0.5x harte Pixel-Kanten der Pixel-Art
|
||||||
|
verwaschen -- DAS war Stefans "Aufloesung ist schlecht"-Bug, nicht
|
||||||
|
die Groesse selbst.
|
||||||
cut_border_bg=True macht den vom Bildrand aus erreichbaren papierweissen
|
cut_border_bg=True macht den vom Bildrand aus erreichbaren papierweissen
|
||||||
Hintergrund transparent (siehe make_border_bg_transparent) -- fuer die
|
Hintergrund transparent (siehe make_border_bg_transparent) -- fuer die
|
||||||
Rauser-Teile, damit sie beim Fade zu Grau nicht als harte weisse
|
Rauser-Teile, damit sie beim Fade zu Grau nicht als harte weisse
|
||||||
@@ -205,22 +251,24 @@ class Assets:
|
|||||||
surf = self.pcc_surface(name)
|
surf = self.pcc_surface(name)
|
||||||
if cut_border_bg:
|
if cut_border_bg:
|
||||||
surf = make_border_bg_transparent(surf)
|
surf = make_border_bg_transparent(surf)
|
||||||
|
if scale == 1.0:
|
||||||
|
return surf
|
||||||
w, h = surf.get_size()
|
w, h = surf.get_size()
|
||||||
new_size = (max(1, round(w * scale)), max(1, round(h * scale)))
|
new_size = (max(1, round(w * scale)), max(1, round(h * scale)))
|
||||||
return pygame.transform.smoothscale(surf, new_size)
|
return pygame.transform.scale(surf, new_size)
|
||||||
|
|
||||||
def pcc_surface_half_height(self, name):
|
def pcc_surface_half_height(self, name):
|
||||||
'''Skaliert NUR die Hoehe um 0.5, Breite bleibt nativ -- fuer die
|
'''Skaliert NUR die Hoehe um 0.5, Breite bleibt nativ -- fuer die
|
||||||
Titelbild-Quadranten KELL256A/B/C/D (160x240 -> 160x120). ACHTUNG:
|
Titelbild-Quadranten KELL256A/B/C/D (160x240 -> 160x120). ACHTUNG:
|
||||||
das ist KEINE gleichmaessige Skalierung wie bei den Rauser-Assets --
|
das ist KEINE gleichmaessige Skalierung wie bei den Rauser-Assets --
|
||||||
per Pixel-Rekonstruktion gegen eine echte DOSBox-Aufnahme verifiziert
|
per Pixel-Rekonstruktion gegen eine echte DOSBox-Aufnahme verifiziert
|
||||||
(siehe Docstring-Korrektur ganz oben in dieser Datei). Wuerde man hier
|
(siehe Docstring ganz oben). Nearest-Neighbor (pygame.transform.scale)
|
||||||
stattdessen die Breite mitskalieren (wie bei den Rauser-Assets),
|
statt smoothscale aus dem gleichen Grund wie bei den Rauser-Assets
|
||||||
entsteht genau der "gestaucht/zu schmal"-Bug, den Stefan gemeldet hat.'''
|
(siehe pcc_surface_scaled) -- konsistent scharf.'''
|
||||||
surf = self.pcc_surface(name)
|
surf = self.pcc_surface(name)
|
||||||
w, h = surf.get_size()
|
w, h = surf.get_size()
|
||||||
new_size = (w, max(1, round(h * 0.5)))
|
new_size = (w, max(1, round(h * 0.5)))
|
||||||
return pygame.transform.smoothscale(surf, new_size)
|
return pygame.transform.scale(surf, new_size)
|
||||||
|
|
||||||
|
|
||||||
def fit_scale(src_w, src_h, max_w, max_h):
|
def fit_scale(src_w, src_h, max_w, max_h):
|
||||||
@@ -290,9 +338,10 @@ def build_kelloggs_frame(w, h, indices, palette, fade_t):
|
|||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
def build_rauser_parts(assets):
|
def build_rauser_parts(assets):
|
||||||
'''Liefert die drei skalierten Rauser-Teile + ihre Endpositionen auf einer
|
'''Liefert die drei (bei 0.5x Nearest-Neighbor-Groesse, siehe Docstring-
|
||||||
320x240-Leinwand (RAUSER1 links, RAUSER3 rechts daneben, RAUSER2 darunter
|
Korrektur v3) Rauser-Teile + ihre Endpositionen auf einer 320x240-
|
||||||
mittig -- alle bei ASSET_DISPLAY_SCALE, siehe Docstring).'''
|
Leinwand (RAUSER1 links, RAUSER3 rechts daneben, RAUSER2 darunter
|
||||||
|
mittig).'''
|
||||||
# cut_border_bg=True (Fix 22.07.2026 abends): macht den papierweissen
|
# cut_border_bg=True (Fix 22.07.2026 abends): macht den papierweissen
|
||||||
# Hintergrund um jede Box transparent (nur die vom Bildrand aus
|
# Hintergrund um jede Box transparent (nur die vom Bildrand aus
|
||||||
# zusammenhaengende Flaeche, siehe make_border_bg_transparent) -- vorher
|
# zusammenhaengende Flaeche, siehe make_border_bg_transparent) -- vorher
|
||||||
@@ -343,12 +392,9 @@ def run_rauser(screen, clock, assets):
|
|||||||
sting = None
|
sting = None
|
||||||
try:
|
try:
|
||||||
if pygame.mixer.get_init():
|
if pygame.mixer.get_init():
|
||||||
# KEIN fester seconds-Cap mehr (Fix 22.07.2026 abends, siehe
|
# TITEL2 Song 0 -- der kuerzere der beiden TITEL2-Songs (~2.04s),
|
||||||
# tfmx_audio.render_wav-Docstring) -- render_tfmx erkennt das
|
# Song 1 ist der neu zugeordnete Factor5-Sound (siehe Docstring).
|
||||||
# natuerliche Ende von TITEL2 selbst (~2s Jingle) und schneidet
|
sting_wav = tfmx_audio.render_wav(assets.container, 'TITEL2', song_index=0)
|
||||||
# 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)
|
sting = pygame.mixer.Sound(sting_wav)
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
print(f'[intro_sequence] Rauser-Sound-Sting konnte nicht geladen werden: {exc}', file=sys.stderr)
|
print(f'[intro_sequence] Rauser-Sound-Sting konnte nicht geladen werden: {exc}', file=sys.stderr)
|
||||||
@@ -408,9 +454,9 @@ def run_rauser(screen, clock, assets):
|
|||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
def build_title_quadrants(assets):
|
def build_title_quadrants(assets):
|
||||||
'''Liefert die 4 Titelbild-Quadranten bei ASSET_DISPLAY_SCALE (160x120
|
'''Liefert die 4 Titelbild-Quadranten bei halber Hoehe (160x120 statt
|
||||||
statt 160x240 nativ) -- A/B oben, C/D unten. Per Pixel-Rekonstruktion
|
160x240 nativ) -- A/B oben, C/D unten. Per Pixel-Rekonstruktion gegen
|
||||||
gegen eine echte DOSBox-Aufnahme verifiziert.'''
|
eine echte DOSBox-Aufnahme verifiziert.'''
|
||||||
return {
|
return {
|
||||||
'A': assets.pcc_surface_half_height('KELL256A'),
|
'A': assets.pcc_surface_half_height('KELL256A'),
|
||||||
'B': assets.pcc_surface_half_height('KELL256B'),
|
'B': assets.pcc_surface_half_height('KELL256B'),
|
||||||
@@ -498,30 +544,24 @@ def run_once(screen, clock, assets):
|
|||||||
if quit_requested:
|
if quit_requested:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
# 3) Factor5 (statisches Bild, ABER mit eigener Melodie -- ONGAME2, der
|
# 3) Factor5 (statisches Bild). KORREKTUR (22.07.2026 abends, siehe
|
||||||
# dritte/letzte bisher nicht zugeordnete TFMX-Track, siehe Stefans
|
# Docstring): NICHT mehr ONGAME2 (das war falsch zugeordnete Level-
|
||||||
# Meldung 22.07.2026 abends: "bei factor 5 gibts auch noch ne melodie
|
# Musik), sondern TITEL2-Song 1 -- ein kurzer "Brausen"-Sound (~2.4s),
|
||||||
# solange das lied ist factor 5 anzeigen und dann zum kellogs
|
# der zur Beschreibung "nur ein bisschen laenger als der Rauser-Sound"
|
||||||
# praesentiert". Der Screen bleibt jetzt exakt so lange stehen wie das
|
# passt. Screen-Dauer ist jetzt FEST (~5s), nicht mehr an die (falsche)
|
||||||
# Stueck einmal komplett durchspielt (~123s natuerliche Laenge, siehe
|
# 123s-Songlaenge gekoppelt.
|
||||||
# tfmx_audio.render_wav-Docstring), statt einer festen 3.0s-Dauer.
|
|
||||||
factor5 = build_letterboxed(assets.pcc_surface('FACTOR5'))
|
factor5 = build_letterboxed(assets.pcc_surface('FACTOR5'))
|
||||||
factor5_duration = 3.0 # Fallback falls kein Audio verfuegbar
|
|
||||||
factor5_sound = None
|
|
||||||
try:
|
try:
|
||||||
if pygame.mixer.get_init():
|
if pygame.mixer.get_init():
|
||||||
factor5_wav = tfmx_audio.render_wav(assets.container, 'ONGAME2')
|
factor5_wav = tfmx_audio.render_wav(assets.container, 'TITEL2', song_index=1)
|
||||||
factor5_sound = pygame.mixer.Sound(factor5_wav)
|
factor5_sound = pygame.mixer.Sound(factor5_wav)
|
||||||
factor5_duration = factor5_sound.get_length()
|
|
||||||
factor5_sound.play()
|
factor5_sound.play()
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
print(f'[intro_sequence] Factor5-Melodie konnte nicht geladen werden: {exc}', file=sys.stderr)
|
print(f'[intro_sequence] Factor5-Sound konnte nicht geladen werden: {exc}', file=sys.stderr)
|
||||||
|
|
||||||
def show_factor5(_t):
|
def show_factor5(_t):
|
||||||
blit_scaled_centered(screen, factor5, WINDOW_W, WINDOW_H)
|
blit_scaled_centered(screen, factor5, WINDOW_W, WINDOW_H)
|
||||||
quit_requested = wait_or_skip(clock, factor5_duration, show_factor5)
|
quit_requested = wait_or_skip(clock, FACTOR5_HOLD_SECONDS, show_factor5)
|
||||||
if factor5_sound is not None:
|
|
||||||
factor5_sound.stop()
|
|
||||||
if quit_requested:
|
if quit_requested:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@@ -531,15 +571,10 @@ def run_once(screen, clock, assets):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
# 5) Kellogg's-Logo + "praesentiert"-Palettenfade
|
# 5) Kellogg's-Logo + "praesentiert"-Palettenfade
|
||||||
# Titelmusik (TITEL.TFX/.SAM) setzt hier ein und laeuft geloopt weiter
|
# Titelmusik (TITEL Song 0, ~109s) setzt hier ein und laeuft geloopt
|
||||||
# bis zum Ende der Sequenz (Original hat vermutlich durchgehende
|
# weiter bis zum Ende der Sequenz (Original hat vermutlich durchgehende
|
||||||
# Titelmusik ueber Logo+Titelbild+Karte -- noch nicht per Referenz-
|
# Titelmusik ueber Logo+Titelbild+Karte -- noch nicht per Referenz-
|
||||||
# Sichtung bestaetigt, aber plausibelste Annahme fuers erste v1).
|
# 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:
|
try:
|
||||||
if pygame.mixer.get_init():
|
if pygame.mixer.get_init():
|
||||||
titel_wav = tfmx_audio.render_wav(assets.container, 'TITEL')
|
titel_wav = tfmx_audio.render_wav(assets.container, 'TITEL')
|
||||||
|
|||||||
+42
-24
@@ -17,6 +17,23 @@ Weg statt TFMX in Python neu zu erfinden.
|
|||||||
Gerenderte WAVs sind urheberrechtlich das Original-Spielmaterial (Musik),
|
Gerenderte WAVs sind urheberrechtlich das Original-Spielmaterial (Musik),
|
||||||
werden daher NICHT ins Git-Repo committed (wie raw/ und extracted_dat/) --
|
werden daher NICHT ins Git-Repo committed (wie raw/ und extracted_dat/) --
|
||||||
liegen nur lokal im Cache-Verzeichnis.
|
liegen nur lokal im Cache-Verzeichnis.
|
||||||
|
|
||||||
|
MEHRERE SONGS PRO MODUL (Fund 22.07.2026 abends, Stefans Hinweis "das Spiel
|
||||||
|
hat noch wesentlich mehr Lieder"): ein einzelnes TFX-Modul kann intern
|
||||||
|
MEHRERE Songs buendeln (tfmx.h's v_songs[]/v_songs_count, ausgewaehlt per
|
||||||
|
start_song-Index). Stichprobe per Debug-Tool ergab:
|
||||||
|
TITEL.TFX -> 3 Songs (song0/1 lang/loopend >15s, song2 ~8.0s)
|
||||||
|
TITEL2.TFX -> 2 Songs (song0 ~2.04s = Rauser-Bling, song1 ~2.40s = Factor5-
|
||||||
|
Brausen -- Laenge passt zu Stefans "nur ein bisschen laenger
|
||||||
|
als der Rauser-Sound")
|
||||||
|
ONGAME2.TFX -> 14 Songs (>15s-Tracks vermutlich Level-Musik, song3/11/12
|
||||||
|
sehr kurz (0.16-1.36s) vermutlich Jingles/Stinger, song2/13
|
||||||
|
~10.3s). Noch nicht im Detail zugeordnet -- fuer die
|
||||||
|
spaetere Gameplay-Musik relevant, siehe NOTES.md.
|
||||||
|
render_wav() nimmt jetzt optional `song_index` entgegen und reicht ihn als
|
||||||
|
5. CLI-Arg an render_tfmx durch. Der Cache-Dateiname enthaelt den Song-Index
|
||||||
|
NUR wenn er nicht der Default (0/None) ist, damit bereits vorhandene Caches
|
||||||
|
fuer Song 0 (z.B. TITEL.wav) gueltig bleiben.
|
||||||
"""
|
"""
|
||||||
import os
|
import os
|
||||||
import subprocess
|
import subprocess
|
||||||
@@ -35,30 +52,30 @@ def _ensure_binary():
|
|||||||
subprocess.run(['bash', os.path.join(_PLAYER_DIR, 'build.sh')], check=True)
|
subprocess.run(['bash', os.path.join(_PLAYER_DIR, 'build.sh')], check=True)
|
||||||
|
|
||||||
|
|
||||||
def render_wav(container, name, seconds=180, force=False):
|
def render_wav(container, name, seconds=180, force=False, song_index=None):
|
||||||
"""container: kellogg_formats.DATContainer; name: z.B. 'TITEL' (ohne Extension).
|
"""container: kellogg_formats.DATContainer; name: z.B. 'TITEL' (ohne Extension).
|
||||||
Gibt den Pfad zur gerenderten (gecachten) WAV-Datei zurueck.
|
song_index: optional -- welcher der ggf. mehreren in <name>.TFX gebuendelten
|
||||||
|
Songs gerendert werden soll (siehe Modul-Docstring). None/0 = Default-
|
||||||
|
Verhalten wie bisher (erster/einziger Song). Gibt den Pfad zur gerenderten
|
||||||
|
(gecachten) WAV-Datei zurueck.
|
||||||
|
|
||||||
WICHTIG (Fix 22.07.2026 abends): `seconds` ist nur noch eine Sicherheits-
|
WICHTIG (Fix 22.07.2026 abends): `seconds` ist nur noch eine Sicherheits-
|
||||||
OBERGRENZE, keine Ziel-Laenge! render_tfmx erkennt das natuerliche Song-
|
OBERGRENZE, keine Ziel-Laenge! render_tfmx erkennt das natuerliche Song-
|
||||||
Ende/den Loop-Punkt selbst (tfmx.h real_song_end) und schneidet dort ab --
|
Ende/den Loop-Punkt selbst (tfmx.h real_song_end) und schneidet dort ab --
|
||||||
z.B. ist TITEL tatsaechlich ~109s lang, ONGAME2 ~123s, TITEL2 (der Rauser-
|
z.B. ist TITEL (song 0) tatsaechlich ~109s lang, ONGAME2 (song 0) ~123s,
|
||||||
Sting) nur ~2s. Vorher wurde hart bei `seconds` abgeschnitten, was TITEL
|
TITEL2 song0 (Rauser-Sting) nur ~2.04s. Vorher wurde hart bei `seconds`
|
||||||
(mit frueher genutzten 60s) VOR dem echten Songende kappte -- das war der
|
abgeschnitten. Der Default hier (180s) ist bewusst grosszuegig ueber die
|
||||||
Bug hinter "Musik ist zu kurz, da fehlt der Rest". Der Default hier (180s)
|
laengsten bekannten Stuecke hinaus gewaehlt, damit nichts abgeschnitten
|
||||||
ist bewusst grosszuegig ueber die laengsten bekannten Stuecke hinaus
|
wird; im Normalfall stoppt render_tfmx laengst vorher von selbst.
|
||||||
gewaehlt, damit nichts abgeschnitten wird; im Normalfall stoppt render_tfmx
|
|
||||||
laengst vorher von selbst.
|
|
||||||
|
|
||||||
ACHTUNG Cache: der Dateiname ist rein `<name>.wav` OHNE den seconds-Wert.
|
ACHTUNG Cache: der Dateiname ist `<name>.wav` (song_index None/0) bzw.
|
||||||
Wird `seconds` also mal kleiner gewaehlt als die echte Songlaenge (z.B.
|
`<name>_song<N>.wav` fuer N>0. Wird `seconds` kleiner gewaehlt als die
|
||||||
fuer einen bewusst kurzen Teaser), MUSS `force=True` gesetzt werden, sonst
|
echte Songlaenge, MUSS `force=True` gesetzt werden, sonst liefert diese
|
||||||
liefert diese Funktion den ggf. laenger/kuerzer gerenderten alten Cache-
|
Funktion einen ggf. laenger/kuerzer gerenderten alten Cache-Treffer
|
||||||
Treffer zurueck, egal was `seconds` diesmal sagt -- genau das hat den
|
zurueck, egal was `seconds` diesmal sagt."""
|
||||||
urspruenglichen Bug mit ausgeloest (ein alter 8s-Debug-Render von TITEL
|
|
||||||
blieb liegen und wurde stillschweigend weiterverwendet)."""
|
|
||||||
os.makedirs(_CACHE_DIR, exist_ok=True)
|
os.makedirs(_CACHE_DIR, exist_ok=True)
|
||||||
out_path = os.path.join(_CACHE_DIR, f'{name}.wav')
|
suffix = f'_song{song_index}' if song_index else ''
|
||||||
|
out_path = os.path.join(_CACHE_DIR, f'{name}{suffix}.wav')
|
||||||
if os.path.exists(out_path) and not force:
|
if os.path.exists(out_path) and not force:
|
||||||
return out_path
|
return out_path
|
||||||
_ensure_binary()
|
_ensure_binary()
|
||||||
@@ -72,22 +89,23 @@ def render_wav(container, name, seconds=180, force=False):
|
|||||||
with open(sam_path, 'wb') as f:
|
with open(sam_path, 'wb') as f:
|
||||||
f.write(sam_data)
|
f.write(sam_data)
|
||||||
|
|
||||||
result = subprocess.run(
|
cmd = [_BINARY, tfx_path, sam_path, out_path, str(seconds)]
|
||||||
[_BINARY, tfx_path, sam_path, out_path, str(seconds)],
|
if song_index:
|
||||||
capture_output=True, text=True
|
cmd.append(str(song_index))
|
||||||
)
|
result = subprocess.run(cmd, capture_output=True, text=True)
|
||||||
if result.returncode != 0:
|
if result.returncode != 0:
|
||||||
raise RuntimeError(f'render_tfmx failed for {name}: {result.stderr}')
|
raise RuntimeError(f'render_tfmx failed for {name} (song_index={song_index}): {result.stderr}')
|
||||||
return out_path
|
return out_path
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
# Kleiner CLI-Test: python3 tfmx_audio.py <DAT_PATH> <NAME> [seconds]
|
# Kleiner CLI-Test: python3 tfmx_audio.py <DAT_PATH> <NAME> [seconds] [song_index]
|
||||||
sys.path.insert(0, _HERE)
|
sys.path.insert(0, _HERE)
|
||||||
from kellogg_formats import DATContainer
|
from kellogg_formats import DATContainer
|
||||||
dat_path = sys.argv[1]
|
dat_path = sys.argv[1]
|
||||||
name = sys.argv[2]
|
name = sys.argv[2]
|
||||||
seconds = int(sys.argv[3]) if len(sys.argv) > 3 else 20
|
seconds = int(sys.argv[3]) if len(sys.argv) > 3 else 20
|
||||||
|
song_index = int(sys.argv[4]) if len(sys.argv) > 4 else None
|
||||||
c = DATContainer(dat_path)
|
c = DATContainer(dat_path)
|
||||||
path = render_wav(c, name, seconds, force=True)
|
path = render_wav(c, name, seconds, force=True, song_index=song_index)
|
||||||
print(f'rendered: {path}')
|
print(f'rendered: {path}')
|
||||||
|
|||||||
Reference in New Issue
Block a user