render() schrieb pro Nicht-Letzte-Zeile exakt self.cols Zeichen → Cursor
landet in Pending-Wrap-Zustand. In iTerm2 löscht ein Cursor-Positionierungs-
befehl (_go) diesen Zustand NICHT: das erste Zeichen der nächsten Zeile
triggert den Wrap → Zeile r+1 landet auf r+2 usw. → Kaskade.
Folgen: Zeilen ohne Überschreibung zeigen alte Zeichen (Fragmente oberhalb
der Wasserlinie), Unterwasserinhalt verschiebt sich aus dem sichtbaren
Bereich (nichts unter der Wasserlinie sichtbar).
Fix: letztes Zeichen jeder Zeile grundsätzlich auslassen (col_limit =
cols - 1, für alle Zeilen). Pending-Wrap tritt nie auf. Letzte Spalte
bleibt leer, was für die Animation völlig akzeptabel ist.
Alle 12 Frames normalisiert auf 8 Zeilen (3 Fontäne + 1 Separator + 4 Körper).
Vorher variierten die Frame-Höhen (6–8 Zeilen), sodass der Körper zwischen
Zeilen 2/3/4 sprang. Maske hatte 7 Zeilen → bei 8-Zeilen-Frames um 1 Zeile
versetzt → falsche Körperfarben.
- mask_r/l: leere Trennzeile zwischen Fontänen-Farben und Körper-Farben
- Fontänen-Zeilen auf genau 3 aufgefüllt (oben mit Leerstrings)
- Frames direkt als Listen gebaut statt über _p()-String-Konkatenation
Außerdem aus vorheriger Session:
- add_all_fish: Anzahl relativ zu wl (statt hardcodiert)
- main: Terminalgröße erst nach ALT_ON lesen (Split-Pane-Fix)
- --debug Flag
WRAP_OFF (ESC[?7l) verhinderte Scrollback, blockierte aber in iTerm2
die Darstellung der unteren Bildschirmhälfte.
Stattdessen: die allerletzte Zelle (Zeile rows-1, Spalte cols-1)
wird nicht beschrieben – ncurses-Standardansatz. Kein Auto-Wrap,
kein Scrollback, keine Darstellungsprobleme.
Hardcodiertes wl=5 ließ bei kleinen Terminals (~10 Zeilen) nur
1-2 Zeilen Unterwasserraum. Wie im Perl-Original: wl=rows//3.
- add_environment: y=wl+i statt y=i+5
- Alle Spawn-y für Fische/Haie/Taucher usw.: wl+4 als Minimum
- Delfine: start_ys relativ zu wl
- Enten: y=wl statt y=5
- Castle nur bei rows>=20 (passt sonst nicht ins Terminal)
ESC[2J schiebt in iTerm2 den Bildschirminhalt bei jedem Aufruf
in den Scrollback-Puffer – 10x/Sekunde. Da render() ohnehin
jede Zelle überschreibt, genügt ESC[H (Cursor-Home).
Beim Beschreiben der letzten Spalte der untersten Terminal-Zeile
löst automatischer Zeilenumbruch ein Scroll-Event aus. Bei 10 fps
füllt sich der Scrollback-Puffer kontinuierlich.
WRAP_OFF (ESC[?7l) beim Start, WRAP_ON (ESC[?7h) beim Beenden.
start_ys=[8,2,5] ließ Delfine nur zwischen y=1 und y=8 pendeln –
genau an der Wasseroberfläche (Wellen bei y=5-8), nie darunter.
Neuer Bogen [10,4,7]: Amplitude ±7, Zentrum y=10.
Delfine spannen y=3 (über den Wellen) bis y=10 (unter Wasser)
und durchqueren die Oberfläche in beiden Phasen.
- canvas.render() startet jetzt mit \033[2J um das gesamte Terminal zu löschen,
nicht nur die Canvas-Breite (verhindert Trails bei breiten Terminals)
- add_all_fish(): min 8 Fische statt 1, dichtere Formel (//200 statt //350)
- Krabben-Zeichen von '°' (non-ASCII, wide-char-Problem) auf '.' umgestellt
- steps_out 14→22: Krabbe läuft weiter aus dem Schloss heraus
- add_diver(): Scuba-Taucher mit 2-Frame-Animation, Sauerstoffblasen beim Tod
- CLI-Parameter via argparse: --speed (TICK-basiert), --no-shark, --no-ship,
--bloody (Hai frisst Fische mit Splat-Animation), --vegan (keine Räuber),
--any-key (beliebige Taste beendet)
- Config-Dataclass: wird durch Aquarium durchgereicht, steuert random_fns
- CrabEntity: Krabbe läuft aus Schlosstor heraus und kehrt zurück
- Portcullis-Animation: Falltor öffnet/schließt sich vor/nach der Krabbe
- Neue Tiere: add_seahorse (Seepferdchen), add_squid (Tintenfisch)
- entity_type für Fish und Shark für Kollisionserkennung
Whale body/spout-alignment logic was inverted (not going_right → wrong
direction). Initial fish population now starts distributed on-screen
instead of all off-screen edges, so the aquarium is immediately filled.
Same sprites, same colors, same entity timing as the Perl source.
Replaces Curses with direct ANSI truecolor escapes using the same
RGB values as our Homebrew fix (xterm-256 slots 16-231).