Road To 1.2.0
# Roadmap: Abyss Empire – Der Schrecken der Tiefe
## Status-Legende
- [ ] offen · [~] in Arbeit · [x] abgeschlossen
---
Rueckmeldungen aus dem 1.1.0-Betrieb: Das 6-Typen-Bau-Menue (2 Reihen,
260 px Panel-Hoehe) ist im Spielalltag zu unuebersichtlich und frisst
Spielflaeche. Das Menue wird zu einem einklappbaren Panel mit
Icon-Aufruf umgebaut.
## Aufgaben 1.1.1
- [x] **B1 - Bau-Menue-Icon generieren + links unten platzieren:**
Programmatisches Icon im UiIcons-Stil (z. B. Hammer/Winde oder
stilisiertes Fundament) als fester Icon-Button unten links
(ueber dem SharkMenu-Bereich bzw. an dessen unterem Rand -
Position weiten-relativ, kein Kollidieren mit Rudel-Buttons).
Zustands-Anzeige: bei ausgeklapptem Menue hervorgehoben
(Akzent-Farbe), eingeklappt neutral.
- [x] **B2 - Ausklappen/Einklappen per Icon-Tap (Toggle):**
Tap auf das Icon klappt das Bau-Menue aus (2x3-Panel wie 1.1.0),
erneuter Tap klappt es ein (Anforderung 4). Der Spielbereich
hinter dem Panel bleibt voll bedienbar; placementType-Auswahl
bleibt beim Einklappen NICHT erhalten (siehe B3).
- [x] **B3 - Auswahl klappt das Menue ein:**
Nach der Auswahl eines Gebaeudetyps (placementType gesetzt)
klappt das Menue automatisch ein - mehr Spielflaeche zum
Platzieren (Anforderung 3). Der Ghost folgt dem Finger wie
gehabt; ein erneuter Ausklapp-Tap erlaubt die Auswahl eines
anderen Typs (togglePlacement-Verhalten analog 1.0.2/U3: gleicher
Typ abwaehlen). Abbrechen: Auswahl-Typ im Menue erneut tippen
oder Icon-Tap oeffnet Menue erneut.
- [x] **B4 - Auto-Einklappen nach 30 s:**
Bleibt das Menue 30 s unbenutzt offen (kein Tap auf einen
Bau-Button), klappt es selbst ein (Anforderung 5). Timer
resetted bei jedem Ausklappen; pausierte Spiele frieren den
Timer ein (paused => kein Ablauf).
- [x] **B5 - Layout-/State-Tests + Abnahme:**
GameConfig-Konstanten (AUTO_COLLAPSE_SECONDS = 30); Unit-Tests:
Icon-Tap oeffnet/schliesst (Toggle), Typ-Auswahl klappt ein und
setzt placementType, Auto-Einklappen nach 30 s (update-basiert,
deterministisch), Pause friert Timer, SharkMenu/HUD-Kollisions-
Freiheit (UiLayoutTest-Erweiterung). Layout pruefen auf 3
Landschafts-Formaten. Emulator-Abnahme: Ausklappen, Auswahl
(Menue klappt ein), 30-s-Auto-Einklappen (Logcat-Timing),
Spielflaeche dahinter nutzbar.
## Abnahme-Kriterium 1.1.1
**Fertig wenn:**
1. Bau-Menue ist standardmaessig EINGEKLAPPT; Icon links unten
sichtbar; Tap klappt aus, erneuter Tap ein (Emulator-Pixel-Check
des Panel-Bereichs).
2. Auswahl eines Gebaeudetyps klappt das Menue ein und erlaubt
Platzierung auf der freigewordenen Flaeche (live gebaut).
3. Auto-Einklappen nach 30 s ohne Bau-Aktion (Logcat-Timing),
Pause haelt den Zustand eingefroren.
4. Alle Unit-Tests gruen plus neue (Toggle, Auswahl-Einklappen,
Auto-Timer, Pause-Freeze, Layout); 234+ Suite unveraendert gruen.
5. Version 1.1.1 (versionCode 8), signiert, APK-Groesse dokumentiert.
## Aufwand 1.1.1
| Aufgabe | Aufwand |
|---------|---------|
| B1 (Icon + Platzierung) | 0,5 |
| B2/B3 (Toggle + Auswahl) | 0,5 |
| B4 (Auto-Einklappen) | 0,5 |
| B5 + Abnahme + Tests | 0,5 |
**Status 1.1.1: Abgenommen** (Release-Build 1.1.1, Emulator API 37,
2400x1080):
1. Standard eingeklappt: Icon links unten sichtbar (Pixel-Check:
729 helle Pixel in der Icon-Region), Panel-Region leer (0 helle
Pixel - kein Menue). Bau-Buttons rutschen in BuildMenu.resize an
einer Icon-Spalte (x ab 128) vorbei - keine Ueberlappung.
2. Icon-Tap klappt auf ("Bau-Menue aufgeklappt", Panel-Region 495
helle Pixel = Buttons sichtbar), Typ-Auswahl klappt ein:
"Bau-Menue eingeklappt (Typ ausgewaehlt)" - Panel danach leer
(48 Restpixel = Icon/SharkMenu-Rand), Ghost mit Kostentext bleibt
aktiv (26 weisse Text-Pixel im Zentrum); Platzier-Tap auf freier
Flaeche funktioniert (live: Korrektur-Ablehnung auf belegtem
Platz geprueft, echte Platzierung unit-getestet und im
1.1.0-Ablauf mehrfach live gebaut).
3. Auto-Einklappen live: "Bau-Menue automatisch eingeklappt" exakt
~30 s nach dem Oeffnen im Logcat; Pause friert den Timer ein
(unit-getestet: 2x30 s pausiert bleibt offen).
4. 244 Unit-Tests (10 neu: Start-Zustand, Toggle, Auswahl-Einklappen
mit Platzierung, geschlossene Buttons nicht klickbar, Typ-Wechsel
ueber Wieder-Oeffnen, 30-s-Timer + Pause-Freeze, Kollisions-
Freiheit Icon vs. Rudel-Menue/Bau-Buttons auf 3 Formaten,
Aufraeumen bei Hauptmenue-Rueckkehr), 0 Fehler.
5. Perf: ~10 min mit Menue-Zyklen: avg 0,59 ms/Frame (62 Fenster),
overBudget 1 (Start-Ruckeln), 0 FATAL/ANR, PID konstant.
6. Version 1.1.1 (versionCode 8), gleicher Keystore, APK 121.890
Bytes (1.1.0: 121.106; +0,8 KB durch Icon + Toggle-Logik).
---
# Version 1.1.2 – Gebaeude-Design-Ueberarbeitung (nach 1.1.1)
Die Gebaeude-Sprites (V4, M7-Stil) sollen besser erkennbar und
unterscheidbarer werden - insbesondere das Korallenriff soll wie ein
Korallenriff aussehen statt eines Kreis-Musters.
## Aufgaben 1.1.2
- [x] **D1 - Korallenriff neu rendern:**
Sprites.building(CORAL_REEF) ueberarbeiten: verzweigte
Korallen-Aeste (mehrere Farbtupfer auf Ast-Enden: rosa/orange),
unregelmassige Nubs statt Kreisring, dunklerer Felskern;
Stufen bleiben ablesbar (St.1 Basisriff, St.2 +Nistplaetze als
kleine Hoehlungen, St.3 Prachtriff mit goldenem Kranz wie
bisher). Kreis-Fallback (drawPrimitive, CoralReef-Nub-Cache)
bleibt funktional identisch - nur der Sprite-Renderer wird
neu gezeichnet.
Umsetzung: dunkler Felskern, 4+Stufe verzweigte Korallenaste
(Line-Struktur vom Kern nach aussen, je Ast-Ende ein Farbtupfer
aus 7er-Palette rosa/orange/gruen/cyan), St.2 Nisthoehlungen
(dunkle Kreise), St.3 Goldkranz beibehalten.
- [x] **D2 - Deko-Hammerhaie um die Gilde:**
Um jede Hammerhai-Gilde schwimmen dekorative Hammerhaie: 1
Hammerhai je Stufe (St.1: einer, St.2: zwei). Reine Dekoration -
KEINE Auswirkung auf Spielgeschehen (kein Kollisions-/Kampf-
Verhalten). Umsetzung: rotierende Orbit-Bewegung um das
Gebaeude (Winkel laeuft in draw/update mit, deterministisch
ueber die Spielzeit), Sprite im Stil des UiIcons-Hais mit
Hammerkopf, Skalierung kleiner als der Spieler-Hai. Andere
Gebaeude-Typen optional nachbessern (Fressgrund, Trench,
Kaserne, Anemone, Handelsplatz), wenn die Vergleichsbilder
(Screenshot) Verbesserungsbedarf zeigen.
Umsetzung: Sprites.hammerhead (44-px-Silhouette mit Hammerkopf,
vorgerendert), DecoClock (eigene Spielzeit-Uhr fuer Deko, Game
tickt sie im PLAYING-Update), HammerheadGuild.draw-Override
(Building.draw open): 1/2 Haie je Stufe im Orbit radius*1,7,
0,6 rad/s, 90-Grad-Versatz je Hai, Kopfrichtung tangential
(canvas.save/rotate). Andere Gebaeude-Typen bleiben
unveraendert (kein Verbesserungsbedarf im Screenshot-Vergleich).
- [x] **D3 - Screenshot-Abgleich + Tests:**
Emulator-Screenshots vor/nach je Gebaeudetyp dokumentieren
(Pixel-Diff als Nachweis der Veraenderung). Unit-Tests: Gilde
rendert N Hammerhaie je Stufe (Logik des Orbit-Parameters
testbar), Dekoration beeinflusst weder placement noch combat
(deck: isValidPlacement unveraendert, updateCombat kennt die
Deko nicht - reine Draw-Sache, kein Update-Pfad). Perf-Check:
Sprite-Vorgerenderung bleibt einmalig, Orbit-Kosten trivial
(Positionsberechnung je Frame, keine Allokation).
Umsetzung: BuildingDecorationTest (5 Tests): Deko-Anzahl je
Stufe (1/2), Orbit deterministisch+indiziert (90-Grad-Versatz),
Deko verursacht keinen Schaden/kein Placement-Blocking
(isValidPlacement unveraendert), Riff-Stufen-Datenbasis.
Found bei der Abnahme: Guild-Deko-Orbit-Radius (radius*1,7)
blockiert KEINE Platzierung - der Platzierungs-Test musste
seinen Kandidaten weiter vom Riff wegziehen (Test-Fix, kein
Produktionscode betroffen).
## Abnahme-Kriterium 1.1.2
**Fertig wenn:**
1. Korallenriff-Sprite auf dem Emulator deutlich als Riff erkennbar
(vorher/nachher-Screenshot), Stufen optisch unterscheidbar.
2. Hammerhai-Gilde zeigt 1/2 dekorative Hammerhaie in Orbit
(St.1/St.2, Pixel-Check), ohne jede Spiel-Auswirkung
(Platzierung/Kampf unveraendert - per Unit-Test).
3. Alle Unit-Tests gruen (Suite unveraendert plus ggf. kleine
Deko-Tests); Perf-Soak 10 min <= 0,7 ms/Frame avg, 0 FATAL/ANR.
4. Version 1.1.2 (versionCode 9), signiert, APK-Groesse dokumentiert.
## Aufwand 1.1.2
| Aufgabe | Aufwand |
|---------|---------|
| D1 (Korallenriff-Sprite) | 1 |
| D2 (Deko-Hammerhaie) | 1 |
| D3 + Abnahme + Screenshots | 0,5 |
**Status: Abgenommen** (Release-Build 1.1.2, Emulator API 37,
2400x1080, Altsave mit 6 Gebaeuden):
1. Korallenriff live per Pixel-Analyse verifiziert: Riff-Blob
212x292 px (vorher flacher Kreis-Mixer ~180 px Breite), 960
gesaettigte Farbtupfer in 3 Farb-Familien (rosa/rot 776, gruen
134, cyan 50) auf den Ast-Enden - verzweigte Aeste erkennbar,
St.2-Nisthoehlungen/St.3-Goldkranz im Renderer, Datenbasis je
Stufe per Unit-Test.
2. Hammerhai-Gilde live gebaut (Bau-Menue-Icon -> Gilde ->
platzieren; der 1.1.1-Auto-Collapse wurde dabei erneut
verifiziert: "Bau-Menue eingeklappt (Typ ausgewaehlt)"). St.1
zeigt 1 Deko-Hammerhai im Orbit, per Pixel-Diff zweier
Screenshots (3 s Abstand) belegt: Alle 33 veraenderten Pixel
liegen im Ring 60-130 px um die Gilde (Orbit-Radius radius*1,7
≈ 95 px, 0,6 rad/s), sonst bewegt sich in der Region nichts.
Keine Spiel-Auswirkung: isValidPlacement/updateCombat laut
Unit-Tests unveraendert.
3. 249 Unit-Tests (5 neu: BuildingDecorationTest - Anzahl je
Stufe, Orbit deterministisch+indiziert, kein Schaden, kein
Placement-Blocking, Riff-Stufen-Datenbasis), 0 Fehler.
Perf-Soak 10,2 min: avg 0,57 ms/Frame (Ziel <= 0,7), 120/121
Fenster overBudget=0; das eine Ausnahme-Fenster (4 Frames
ueber 16,7 ms, 33,65-ms-Spike) fiel ohne Spielereignis mit
Emulator-Systemlast zusammen (bekanntes Muster seit 1.0.1).
0 FATAL / 0 ANR, PID konstant.
4. Version 1.1.2 (versionCode 9), gleicher Keystore, APK 123.066
Bytes (1.1.1: 121.890; +1,2 KB durch Riff-Renderer +
Hammerhead-Sprite + DecoClock).
---
# Version 1.1.3 – Performance- & Testabdeckungs-Analyse (nach 1.1.2)
Analysiert den 1.1.x-Stand (3 neue Gebaeude, Soeldner-Entity,
einklappbares Bau-Menue, Deko-Orbits) auf Optimierungspotenzial und
prueft die Testabdeckung auf Luecken. Vorbild: M5.75 und 1.0.1 -
Analyse mit Messbasis, Umsetzung nur wo messbarer Gewinn.
## Aufgaben 1.1.3
- [x] **E1 - Performance-Analyse (Messbasis + Code-Verifizierung):**
Analyse des Update-/Draw-Pfads gegen den 1.1.x-Zuwachs:
Söldner (Bewegung + Ziel-Suche je Frame: nearestAttackerTo
iteriert alle Angreifer je Söldner - O(Söldner x Angreifer),
bei 4+8 Angreifern trivial, bei 20+ Söldnern bewerten),
Anemonen-Schaden (je Anemone x je Angreifer je Frame),
Tausch-Panel/Bau-Menue-Zeichnung (nur wenn offen), Deko-Orbits
(Winkelberechnung je Frame), Auto-Collapse-Timer. 10-Min-Soak
mit vollem Ausbau (Kaserne St.2, Anemone, Wellen) als Basis;
Flag ob avg deutlich ueber 0,6 ms steigt. Nur bei messbarem
Gewinn optimieren (z. B. Ziel-Cache, Distanz-Quadrate statt
Wurzel), sonst als "kein Handlungsbedarf" dokumentieren
(vgl. Kategorie D in 1.0.1).
Ergebnis (Code-Verifizierung gegen den 1.1.2-Stand):
a) ECHTER FUND: Vector2.distanceTo allokiert je Aufruf einen
Vector2 (minus-Operator) + sqrt - nearestAttackerTo
(Soeldner-Zielwahl) rief es je Soeldner x Angreifer je
Frame (skaliert mit Rudelgroesse und Wellenstaerke).
Umsetzung: Vector2.squaredDistanceTo (neu, ohne Allok/sqrt,
Monotonie per Test gesichert), nearestAttackerTo
vergleicht Quadrat-Distanzen.
b) ECHTER FUND: SectorMap.ownedSectors ist eine berechnete
Property (filter-Allokation je Zugriff) und stand 3x im
Per-Frame-Pfad: Produktions-Loop (je Frame, immer aktiv),
Anemonen-Schaden (je Frame, zusaetzlich buildings.filter je
eigenem Sektor je Frame - auch bei 0 Angreifern),
hasTrenchLevel2 (je Frame, solange die Instinkt-Szene offen
ist). Umsetzung: Inline-Status-Checks (HOME/CONQUERED)
direkt ueber sectors; zusaetzlich Guard _attackers.isNotEmpty()
um den Anemonen-Block (ohne Welle laeuft dort nichts) und um
den Gefallenen-Block (filter/removeAll nur noch im Kampf).
c) Kein Handlungsbedarf (dokumentiert): Anemonen x Angreifer-
Nestloop (max. 8 Sektoren x 1 Anemone x ~10 Angreifer,
reine Float-Checks), Deko-Orbits (cos/sin je Deko-Hai, keine
Allokation), Auto-Collapse-Timer (ein Float-Vergleich),
Bau-Menue/Tausch-Panel (zeichnen nur wenn offen/geoeffnet,
Kosten-Texte gecacht), Mercenary.drawRect bereits Member
(1.0.1 C1).
- [x] **E2 - Testabdeckungs-Analyse:**
Systematische Luecken-Pruefung: (a) neue 1.1.x-Features
(Soeldner-Orbit/Zeichnung, Anemonen-Upgrade-Pfad, Tausch-
Panel-Layout, Auto-Collapse-Interaktion mit Pause/GameOver),
(b) Randfaelle (0 Biomasse + Söldner + Welle gleichzeitig,
Riff-Tod waehrend Söldner kaempfen, Menue-Return mit offenem
Bau-Menue), (c) Querschnitt (alle onPress-Zustands-
kombinationen je GameState x Scene). Ergaenzungen nur fuer
echte Luecken; Dubletten vermeiden. Ergebnis dokumentieren:
welche Tests ergaenzt wurden und warum (oder explizit: keine
Luecke).
Ergebnis (Bewertung gegen die bestehende Suite, 249 Tests):
KEINE LUECKE (bereits abgedeckt): Soeldner-Streik mit
gleichzeitig aktiver Welle (mercenariesStrikeWithoutBiomass
haelt einen Angreifer bereit), Soeldner-Unterhalt, Kill-XP,
Soeldner-Rebuild aus dem Spielstand nach Kaltstart (im
Save-Roundtrip aller 7 Typen enthalten), Auto-Collapse
unter Pause und Menue-Return, 1 Anemone je Sektor,
Tausch-Routen ohne Arbitrage/Zirkelschluss, Szenen-Back-
Routing (MAP/INSTINCTS -> WORLD), Layout der Kern-Menues.
GESCHLOSSENE LUECKEN (7 neue Tests):
1. Anemonen-Upgrade-Pfad: anemoneUpgradeRaisesAreaDamage-
PerSecond (dps St.2 > St.1, Panel-Upgrade-Flow, Schaden
exakt anemoneDps(2)).
2. Riff-Tod waehrend Söldner kaempfen: gameOverWhile-
MercenariesFightLeavesNoCrashAndRestartResumesCombat
(kein Crash, Söldner nach Neustart sofort wieder kaempfbereit).
3. Neustart schliesst offenes Bau-Menue (restartRoundCloses-
AnOpenBuildMenu) - Fix: restartRound setzte buildMenuOpen
nicht zurueck (Inkonsistenz zum Start-Runden-Pfad).
4. ECHTER BUG (live auf dem Emulator gefunden, Regressionstest
gameOverInMapSceneTapRestartsRound): Stirbt das Riff, waehrend
Karte oder Instinkte offen ist, verschluckten die Szenen-
Guards jeden Tap - die Runde war NICHT mehr neustartbar
(nur App-Beenden half). Fix: Game Over wird in onPress vor
den Szenen-Guards abgefangen (Neustart szenenunabhaengig,
passend zum szenenunabhaengigen Draw des Panels).
5. ECHTER BUG (vom neuen Layout-Test gefunden, tradeRoute-
ButtonsStayOnScreenOnAllFormats): Das Handelsplatz-Panel
(4 Tausch-Buttons, dynamisch nach unten wachsend) ragte bei
1080er-Hoehen 24 px ins Bau-Menue-Band (Panel-Unterkante
844 px vs. Band ab 820 px). Fix: TRADE_PANEL_RAISE (44 px)
in GameConfig, resize + drawBuildingPanel konsistent.
6./7. Vector2.squaredDistanceTo (Wert = distanceTo^2,
symmetrisch; Ranking identisch zu distanceTo - Basis fuer
die E1-Zielwahl ohne sqrt).
- [x] **E3 - Bericht + ggf. Umsetzung:**
Ergebnis festhalten (Roadmap-Status wie M5.75/1.0.1): Tabellen
vorher/nachher fuer umgesetzte Optimierungen; fuer E2 die
Liste der ergaenzten Tests. Kein Release-Build noetig, wenn
nur Tests ergaenzt wurden - bei Code-Aenderung: 10-Min-Soak +
signierter Build (Version 1.1.3, versionCode 10).
## Abnahme-Kriterium 1.1.3
**Fertig wenn:**
1. Analyse mit Messwerten (Soak-Perf) und Code-Befunden (je
Pruefpunkt: optimiert / kein Handlungsbedarf mit Begruendung)
in der Roadmap dokumentiert.
2. Testabdeckung bewertet: gefundene Luecken geschlossen, Suite
komplett gruen; dokumentiert, welche Tests neu sind und warum
(oder warum keine noetig sind).
3. Falls Code-Aenderungen: 10-Min-Soak ohne Regression (avg <= 0,7
ms, 0 FATAL/ANR); sonst nur Test-Commit.
## Aufwand 1.1.3
| Aufgabe | Aufwand |
|---------|---------|
| E1 (Performance-Analyse) | 0,5-1 |
| E2 (Testabdeckung) | 0,5-1 |
| E3 (Bericht/Umsetzung) | 0,5 |
**Status: Abgenommen** (Release-Build 1.1.3, versionCode 10,
Emulator API 37, 2400x1080; Code-Aenderungen waren noetig ->
Soak + signierter Build nachgezogen):
1. Perf-Soak 10 min (Altsave mit 7 Gebaeuden inkl. Kaserne,
2 Soeldner in jeder Welle im Kampf, 10 Wellen mit Siegen,
2x Game Over mit Neustart im Fenster): avg 0,61 ms/Frame
(1.1.2-Basis mit Soeldnern: 0,57; Ziel <= 0,7), 116/119
Fenster overBudget=0. Die 3 Ausnahme-Fenster fielen mit
Welt-Reset-/Wellen-Spawn-Frames zusammen (maxWorkMs 29,4 -
gleiche Groessenordnung wie die bekannten Spawn-Spikes),
0 FATAL / 0 ANR, PID konstant. Ehrliche Abweichung vom
geforderten "vollen Ausbau": Kaserne-St.2-Upgrade und
Anemonen-Neubau waren im Altsave nicht mehr bezahlbar
(Ressourcen durch wiederholte Game-Over-Halbierungen
erschöpft; Bau-/Upgrade-Versuche schlugen mit "Zu teuer"
fehl). Die perf-relevanten Pfade liefen trotzdem: Soeldner-
Zielwahl in jeder Welle aktiv, der neue Anemonen-Block
(Guard + Sektor-Iteration) laeuft in jeder Welle; alle
Veraenderungen sind zusaetzlich deterministisch per Unit-
Test gesichert (Anemonen-Schaden, Soeldner-Targeting,
Guards).
2. Testabdeckung: 256 Tests (7 neu), 0 Fehler. Zwei echte
Bugs gefunden und geschlossen (Game-Over-Stuck in Karte/
Instinkt-Szene - live auf dem Emulator reproduziert,
Neustart jetzt szenenunabhaengig; Handelsplatz-Panel ragte
ins Bau-Menue-Band - vom neuen 3-Format-Layout-Test
gefunden, TRADE_PANEL_RAISE-Fix).
3. Version 1.1.3 (versionCode 10), gleicher Keystore, APK
123.214 Bytes (1.1.2: 123.066; +148 Bytes: Guard/Inline-
Iteration + Panel-Anhebung, kein neuer Code-Block).
---
# Version 1.1.4 – Bau-Menue: Button-Layout (Text-Overflow-Fix) (nach 1.1.3)
Bei den Gebaeude-Buttons im Bau-Menue liegt die Schrift ueber die
Button-Grenze: Der Kostentext wird bei top+98 gezeichnet, die Buttons
(2-Reihen-Layout, 1.1.0/G4) sind aber nur 88 px hoch - die letzte
Zeile ragt 10 px ueber den unteren Rand, lange Namen laufen breit
ueber die Seitenraender. Ziel: Das Gebaeude-Bild wandert auf die
LINKE Seite des Buttons, der Textblock daneben bekommt mehr vertikalen
Platz - Name und Kosten bleiben komplett innerhalb der Button-Grenze.
## Aufgaben 1.1.4
- [x] **K1 - Ist-Analyse + Layout-Umbau (Icon links):**
BuildMenu.draw: Icon statt zentriert-ueber-Label (32 px,
top+6..38) links vertikal zentriert (eigene Icon-Spalte mit
Rand), Textblock rechts daneben mit Links-/Zentriert-Ausrichtung
und mehrzeiligem Platz: Name (22 px) + Kosten (18 px) so
positioniert, dass beide Zeilen innerhalb des Buttons bleiben
(Bottom-Rand >= 8 px Abstand). Text-Alignment im Textblock
konsistent setzen (textAlign LEFT mit fester Text-x statt
CENTER auf Button-Mitte). resize() unveraendert (Button-Rechtecke
bleiben), nur draw() und die Icon-/Text-Positionierung aendern
sich; Fallback ohne Icon (UiIcons null, JVM-Tests) muss
denselben Text-Block nutzen.
Umsetzung: Icon-Spalte 10 px Rand + 32 px Icon (vertikal
zentriert), Textblock LEFT ab left+52; Baselines Name top+40,
Kosten top+70 (18 px Bottom-Rand bei 88-px-Buttons - vorher
top+64/+98, Kosten 10 px unter der Grenze). Die Layout-
Konstanten sind im companion freigelegt (ICON_SIZE,
ICON_LEFT_MARGIN, TEXT_LEFT_OFFSET, NAME_BASELINE,
COST_BASELINE, TEXT_RIGHT_MARGIN, MAX_CHAR_WIDTH_FACTOR).
Der Icon-null-Fallback nutzt denselben Textblock (Positionen
unabhaengig vom Icon-Vorhandensein, kein Doppel-Layout mehr).
- [x] **K2 - Layout-Garantie ueber alle Formate:**
UiLayoutTest erweitern: je Landschafts-Format (2400x1080,
1920x1080, 2560x1440) muss der Textblock geometrisch in den
Button-Rechtecken liegen (ableitbar aus den fixen Positionen:
textTop + Zeilenabstand <= button.bottom - 8). Zusaetzlich
Laengenpruefung: laengster displayName + laengster Kosten-Text
(Konfig-Konstanten) bestimmen die noetige Textbreite - Button-
Breite muss die Textbreite bei 22/18 px Schrift messbar
hergeben (Paint.measureText im Test-Rig).
Umsetzung: buildMenuTextStaysInsideButtonBoundsOnAllFormats.
Hinweis zur Abweichung vom Plan: Paint.measureText ist im
JVM-Rig nicht messbar (Android-Stubs) - die Breiten-Garantie
nutzt stattdessen die konservative Konstante MAX_CHAR_WIDTH_
FACTOR (0,75 je Zeichengroesse) gegen den laengsten displayName
("Drueckerfisch-Kaserne", 21 Zeichen) und laengsten Kosten-Text
(26 Zeichen). Vertikal laufen die freigelegten Baseline-
Konstanten gegen die echte Button-Hoehe (88 px, -8 px Rand).
- [x] **K3 - Abnahme auf dem Emulator:**
Pixel-Check je Format: unterhalb der Button-Unterkante darf
sich kein Schrift-Pixel befinden (Diff Bild mit/ohne geoeffnetes
Menue in der Button-Zeile); Icon-Spalte links sichtbar, Text
frei im Button. Suite gruen, Perf unveraendert (Texte bleiben
gecacht, eine drawBitmap je Button wie bisher).
## Abnahme-Kriterium 1.1.4
**Fertig wenn:**
1. Auf 3 Landschafts-Formaten: Name UND Kosten je Gebaeude-Button
vollstaendig innerhalb der Button-Grenzen (Pixel-Check), Icon
links statt oben-mittig.
2. Alle Unit-Tests gruen inkl. neuer Layout-Tests (K2); bestehende
BuildMenuCollapse-/UiLayout-Tests unveraendert gueltig.
3. Perf-Disziplin gehalten: 10-Min-Soak avg <= 0,7 ms/Frame,
overBudget ~0, 0 FATAL/ANR.
4. Version 1.1.4 (versionCode 11), signiert mit gleichem Keystore,
APK-Groesse dokumentiert.
## Aufwand 1.1.4
| Aufgabe | Aufwand |
|---------|---------|
| K1 (Icon links + Textblock) | 0,5 |
| K2 (Layout-Tests) | 0,5 |
| K3 + Abnahme | 0,5 |
**Status: Abgenommen** (Release-Build 1.1.4, Emulator API 37):
1. Pixel-Check auf 2 Formaten live (2400x1080 nativ und
1920x1080 per wm size, Menue blieb ueber den Formatwechsel
hinweg erhalten): 0 helle Pixel in den Spalten unterhalb der
Button-Unterkanten (vorher ragte die Kostenzeile 10 px unter
die 88-px-Grenze), Icon-Spalte links je Button belegt (331
Icon-Sampler je Format), Name + Kosten vollstaendig im
Button-Inneren (350 Text-Sampler). Das dritte Format
(2560x1440) ist per Layout-Test gesichert (K2).
2. 257 Unit-Tests (1 neu: buildMenuTextStaysInsideButtonBounds-
OnAllFormats auf 3 Formaten), 0 Fehler. Bestehende Flow- und
Collapse-Tests unveraendert gueltig (Button-Rechtecke und
Hit-Test unangetastet).
3. Perf-Soak 9,9 min (Wellen 1-3 mit Siegen, Menue-Wechsel im
Fenster): avg 0,60 ms/Frame gesamt, Regelbetrieb ohne das
Resize-Fenster 0,59 ms. Einziges overBudget-Fenster (7 Frames,
48,1-ms-Spike) ist das erste Messfenster exakt auf dem
wm-size-reset (Surface-Neuaufbau) - kein Spielereignis,
gleiche Kategorie wie die bekannten Welt-Reset-Spikes;
117/118 Fenster overBudget=0, 0 FATAL / 0 ANR, PID konstant.
4. Version 1.1.4 (versionCode 11), gleicher Keystore, APK
123.170 Bytes (1.1.3: 123.214; -44 Bytes - der Icon-vor/nach-
Layout-Zweig entfiel zugunsten eines einzigen Textblocks).
---
# Version 1.1.5 – Feind-Variationen (rein kosmetisch) (nach 1.1.4)
Die Angreifer sehen aktuell alle gleich aus (eine Silhouette je
Normal-/Alpha-Typ). Sie sollen weitere Designs erhalten - Varianten
anderer Meeresbewohner (z. B. andere Fisch-/Qualle-/Krebs-Silhouetten
und Farbstiche), rein kosmetisch: KEINE Unterschiede in Schaden,
Lebenspunkten, Tempo oder Verhalten. Die Variante wird dem Angreifer
deterministisch zugewiesen (stabil pro Entitaet, kein Flackern beim
Neustart des Frames).
## Aufgaben 1.1.5
- [x] **F1 - Varianten-Sprites (programmatisch, M7-Stil):**
Sprites.attacker um 2-3 zusaetzliche Silhouetten erweitern
(vorgerenderte Bitmaps im bekannten Stil, keine Assets) -
z. B. Stachelfisch, Qualle, Krebs; Alpha (Riesenkrake) bleibt
exklusiv. Farbliche Unterscheidung dezent (Tiefsee-Palette),
Lesbarkeit im Kampf erhalten (Umriss heller als Hintergrund).
Umsetzung: 3 Varianten - 0 Rochen (unveraendert), 1 Leucht-
qualle (warmorange Glocke mit 4 Tentakeln, 255,112,67), 2
Panzerkrebs (rotbrauner Carapax mit Scheren/Beinen,
216,67,21); alle 64 px vorgerendert in Sprites.ensure(),
Auswahl ueber Sprites.attackerVariant(index). Kreis-Fallback
je Variante mit eigener Farbe (reine ARGB-Konstanten - die
erste Version mit Color.rgb im companion sprengte die
Klassen-Initialisierung im JVM-Rig, per Test gefunden und
behoben).
- [x] **F2 - Deterministische Zuweisung:**
Attacker bekommt ein variant-Feld (Index), zugeweisen beim
Wellen-Spawn (deterministisch, z. B. abwechselnd/aus Hash des
Spawns - dokumentierte Regel), Persistenz nicht noetig (Varianten
sind rein visuell, kein Save-Format-Bump: Angreifer sind nicht
save-wuerdig, vgl. M4.5). draw() waehlt das Sprite nach variant.
Umsetzung: Attacker.variant (Konstruktor-Parameter, Default 0);
dokumentierte Regel: abwechselnd je Angreifer-Index innerhalb
der Welle (i % ATTACKER_VARIANT_COUNT, jede Welle startet bei
0). Krake und Eroberungs-Gruppen unberuehrt (Alpha exklusiv,
Eroberungs-Angreifer bleiben Rochen).
- [x] **F3 - Kosmetik-Garantie + Tests:**
Unit-Tests: Kampf-Werte (HP, DPS, Panzerung, Tempo) unabhaengig
von variant; Wellen-Statistik (Anzahl/HP-Skalierung) unveraendert
egal welche Varianten fallen; Zuweisungs-Regel deterministisch
(gleicher Spawn -> gleiche Variante, alle Varianten kommen vor).
Perf: eine drawBitmap je Angreifer wie bisher, Vorgerenderung
einmalig (Sprites.ensure).
Umsetzung: 3 Tests in AttackerTest - combatValuesAreIdentical-
AcrossVariants, waveVariantRotationIsDeterministicAndComplete
(Wellen 1-4 inkl. Kraken-Welle 3: Rotation startet je Welle
bei 0, Welle 4 deckt 0/1/2 ab), variantDoesNotChangeReefDamage
(identischer Riff-Schaden je Variante).
## Abnahme-Kriterium 1.1.5
**Fertig wenn:**
1. Emulator: in einer Welle mit >= 5 Angreifern sind >= 2 deutlich
unterscheidbare Designs erkennbar (Pixel-Check auf Farbwerte der
Silhouetten), Alpha-Krake unveraendert.
2. Alle Unit-Tests gruen inkl. F3-Tests; Kampfbalance nachweislich
unveraendert (gleiche Wellen-HP/DPS wie vor 1.1.5).
3. Perf-Soak 10 min ohne Regression (avg <= 0,7 ms, 0 FATAL/ANR).
4. Version 1.1.5 (versionCode 12), signiert, APK-Groesse dokumentiert.
## Aufwand 1.1.5
| Aufgabe | Aufwand |
|---------|---------|
| F1 (Sprites) | 1 |
| F2 (Zuweisung) | 0,5 |
| F3 + Tests + Abnahme | 0,5 |
**Status: Abgenommen** (Release-Build 1.1.5, Emulator API 37):
1. Live verifiziert (Welle 4, 3 Angreifer, Pixel-Diff zweier
Screenshots im 2,5-s-Abstand gegen die Bewegungs-Farben): drei
getrennte bewegte Farb-Cluster - Qualle-Orange (255,112,67,
16 Sampler, eindeutig getrennt vom Spektrum der Rot-Toene),
Rochen-Rot (68) und Krebs-Rotbraun (72); die Rot-Filter
ueberlappen teils, die Qualle ist der saubere Trennbeweis.
Die Zuweisung (Rotation 0/1/2 je Angreifer-Index) ist per
Unit-Test deterministisch gesichert (Welle 4 deckt alle 3 ab,
jede Welle startet bei 0, Krake unberuehrt).
2. 260 Unit-Tests (3 neu), 0 Fehler. Kampfbalance unveraendert:
Riff-Schaden je Variante identisch, Wellen-HP/-DPS-Skalierung
unberuehrt (per Test), Wellen-Siege im Soak zeitlich un-
veraendert.
3. Perf-Soak 9,8 min: avg 0,62 ms/Frame, overBudget 0 Fenster
(bestwertig), worst Einzelframe 11,6 ms (Wellen-Fenster),
0 FATAL / 0 ANR, PID konstant. Eine drawBitmap je Angreifer
wie bisher, Sprites einmalig vorgerendert.
4. Version 1.1.5 (versionCode 12), gleicher Keystore, APK
123.862 Bytes (1.1.4: 123.170; +0,7 KB durch zwei zusaetzliche
vorgerenderte 64-px-Sprites).
---
# Version 1.1.6 – Game-Over: Weiterspielen oder Neustart (nach 1.1.5)
Der Game-Over-Screen bietet aktuell nur einen impliziten Neustart
(Tap irgendwo -> Neustart-Runde mit Teil-Ressourcen). Der Spieler
soll bewusst entscheiden koennen: mit dem AKTUELLEN Stand weiter-
spielen (heutiges restartRound: Gebaeude und Rudel bleiben, Res-
sourcen halbiert) ODER komplett von Anfang an (gelten als Neues
Spiel: gebaute Gebaeude und gesammelte Ressourcen werden zuerueck-
gesetzt, Kampagne startet frisch).
## Aufgaben 1.1.6
- [x] **W1 - Game-Over-Panel mit zwei Wegen:**
drawGameOverPanel um zwei Buttons erweitern: "Weiterspielen
(Teil-Ressourcen)" -> restartRound() und "Neues Spiel" ->
Bestaetigungs-Overlay (gleiche Sicherheit wie das Menue-
Pendant 1.0.2/U4) -> resetToNewGame() + Rundenstart. Die
Statistik (Wellen, Gebaeude, Rudel, XP, 1.0.2/U5) bleibt.
Der freie Tap-auf-dem-Bildschirm-Neustart (heutiges Verhalten,
auch szenenunabhaengig seit 1.1.3) wird durch die Button-
Steuerung ersetzt - Taps ausserhalb der Buttons machen nichts
(Fehl-Tap-Schutz, vgl. 1.0.2/U3-Disziplin).
Umsetzung: gameOverContinueButton (hoehe*0,72) / gameOverNew-
GameButton (0,84) wie Menue-Buttons (resize); handleGameOver-
Press vor den Szenen-Guards (1.1.3-Stelle bleibt); gameOver-
Confirm-Flag mit eigenem Overlay-Aufruf; Statistik + Kurz-
hinweis "Weiterspielen haelt Gebaeude und Rudel ..." beibehalten.
Das Bestaetigungs-Overlay ist aus drawTitleOverlay extrahiert
(drawNewGameConfirmOverlay, Menue + Game Over nutzen dieselbe
Flaeche/Buttons).
- [x] **W2 - resetToNewGame als Game-Over-Pfad:**
Der Reset nutzt die bestehende Neues-Spiel-Logik (resetToNewGame:
kompletter Reset inkl. Sektor-Karte, Nebel, Rudel, Instinkten,
Tutorial, sofortiger Rundenstart + Save). Pruefen: nach dem
Reset muss das Save konsistent sein (frischer Stand ueberlebt
Kaltstart), Instinkt-Punkte und XP auf 0, Standard-Rudel aktiv.
Umsetzung: resetToNewGame raeumt zusaetzlich gameOverConfirm;
gameOver() startet ohne offene Bestaetigung. Reset-Invarianten
(Startwerte, leere Gebaeude-Liste, Standard-Rudel, Welle 0) per
Unit-Test gesichert.
- [x] **W3 - Tests + Abnahme:**
Unit-Tests: Tap "Weiterspielen" -> restartRound-Pfad (halbierter
Ressourcen, Gebaeude bleiben), Tap "Neues Spiel" + Bestaetigung
-> Startwerte + Standard-Rudel + leere Karte, Tap ausserhalb
aendert nichts, Game Over in Karte/Instinkt-Szene (1.1.3-Fix)
zeigt das Panel ebenfalls mit beiden Buttons. Emulator-Abnahme:
beide Pfade live (Logcat + Save-Pruefung), Layout auf 3 Formaten
(UiLayoutTest).
Umsetzung: 4 neue Tests in NewBuildingsTest (Continue haelt
Gebaeude/halbiert Ressourcen; Neues Spiel nur mit Bestaetigung
und komplett frisch; Fehl-Tap macht nichts; Abbrechen kehrt zur
Auswahl zurueck und Weiterspielen funktioniert danach). 5 Alte-
Tests auf den Weiterspielen-Button umgestellt (Combat-, Sector-
Game-, NewBuildings-), da der freie Tap-Neustart bewusst ent-
fiel.
## Abnahme-Kriterium 1.1.6
**Fertig wenn:**
1. Game-Over-Screen bietet beide Wege mit klar beschrifteten Buttons;
beide live auf dem Emulator verifiziert (Weiterspielen haelt
Gebaeude, Neues Spiel resettet komplett - Spielstand nach
Kaltstart geprueft).
2. Alle Unit-Tests gruen inkl. W3-Tests (beide Pfade, Fehl-Tap-
Schutz, Szenen-Fall).
3. Perf-Disziplin gehalten (Panel zeichnet nur im GAME_OVER,
Buttons statisch): 10-Min-Soak ohne Regression, 0 FATAL/ANR.
4. Version 1.1.6 (versionCode 13), signiert, APK-Groesse dokumentiert.
## Aufwand 1.1.6
| Aufgabe | Aufwand |
|---------|---------|
| W1 (Panel + Buttons) | 0,5 |
| W2 (Reset-Pfad) | 0,5 |
| W3 + Tests + Abnahme | 1 |
**Status: Abgenommen** (Release-Build 1.1.6, Emulator API 37):
1. Beide Pfade live verifiziert: Game Over (Welle 1 der frischen
Kampagne nach Menu-Reset) -> Panel zeigt WEITERSPIELEN +
NEUES SPIEL (Pixel-Check: 147 Akzent-Button-Sampler in den
beiden Button-Baendern). Tap ausserhalb: kein Neustart-Log,
Zustand unveraendert (Fehl-Tap-Schutz). NEUES SPIEL -> dunkles
Bestaetigungs-Overlay mit JA/ABBRECHEN (Pixel-Check) -> JA:
"Neues Spiel: Spielstand zurueckgesetzt" (kompletter Reset aus
dem Game-Over-Pfad). Zweiter Game Over -> WEITERSPIELEN:
"Neustart-Runde: Teil-Ressourcen, Welle zurueckgesetzt".
Der ABBRECHEN-Weg ist per Unit-Test gesichert (der Live-Pixel-
Check konnte das halbtransparente Overlay nicht sicher vom
dunklen Tiefsee-Grund trennen - ehrliche Grenze dokumentiert).
Regression: der Menue-Pendant (Neues Spiel aus dem Hauptmenue)
funktioniert nach der Overlay-Extraktion unveraendert (live:
Reset um 14:42 Uhr).
2. 265 Unit-Tests (5 neu: 4 Auswahl-Flows + 1 Layout-Test auf
3 Formaten; 5 Alt-Tests auf den Button umgestellt),
0 Fehler. Szenen-Fall (Game Over in der Karten-Szene, Weiter-
spielen-Button) im Test aktualisiert und gruen.
3. Perf-Soak 9,9 min (inkl. 1 Game Over mit Panel-Draw): avg
0,59 ms/Frame, overBudget 0 Fenster, worst Einzelframe 8,6 ms,
0 FATAL / 0 ANR, PID konstant.
4. Version 1.1.6 (versionCode 13), gleicher Keystore, APK
124.090 Bytes (1.1.5: 123.862; +0,2 KB durch Auswahl-Panel +
extrahiertes Overlay).
---
# Version 1.1.7 – Rotation verhindern (nach 1.1.6)
Das Spiel soll nicht rotierbar sein: Das Spielfeld bleibt immer in
Landschaft, keine Drehung des Bildes - weder per System-Rotation in
Portrait noch zwischen Landschafts-Varianten (Sensor/180-Grad). Das
Manifest ist bereits landscape-locked (screenOrientation="landscape",
Portrait blockiert); verbleibende Luecken sind: die Activity kann bei
Konfigurations-Ereignissen (Rotationssystemdrehung, Foldables, Display-
Wechsel) neu erzeugt werden und der zustandslose Neuaufbau ist nur
durch das robuste Save abgefedert. Die Layouts bleiben weiten-relativ
(keine feste Aufloesung - der Letterbox-Teil wurde aus dieser Version
entfernt).
## Aufgaben 1.1.7
- [x] **R1 - Rotationssperre verhärten:**
Manifest: screenOrientation="landscape" beibehalten (kein
sensorLandscape - keine Sensor-Drehung zwischen 90/270) und
die Activity gegen Konfigurations-Ereignisse unempfindlich
machen: android:configChanges="orientation|screenSize|
screenLayout|smallestScreenSize|keyboardHidden" ergaenzen,
damit Rotationssystem-Ereignisse die Activity nicht neu
erzeugen (Game-Zustand bleibt im Speicher, kein Save/Load-
Zyklus noetig). Pruefen: onBackPressed/OnBackInvokedCallback
(1.0.2/U2) und Predictive Back funktionieren nach der
configChanges-Aenderung unveraendert.
Umsetzung: configChanges war bereits teilweise gesetzt
(orientation|screenSize|keyboardHidden) - ergaenzt um
screenLayout|smallestScreenSize (Foldables/Display-Wechsel).
Live-Regression: Back oeffnet die Pause (FORTSETZEN-Akzent per
Pixel-Check), zweiter Back resumed - OnBackInvokedCallback
unbeeinflusst (keine Recreation mehr, Callback bleibt
registriert).
- [x] **R2 - Rotationstest auf dem Emulator:**
Live-Verifikation: System-Rotation aktivieren (settings put
system accelerometer_rotation 1) und Portrait-/Reverse-
Anforderung per wm user-rotation senden - das Spielfeld darf
sich nicht drehen (Screenshot-Vergleich: Layout bleibt
Landschaft). Activity-Recreation ausschliessen (Game-Logger
zeigt keinen erneuten Welt-Aufbau/PID bleibt). Danach Rotation
zuruecksetzen. Unit-Tests: keine Rotation verfaelscht Game-
Zustand (Game.resize bleibt die einzige Layout-Quelle; rein
JVM-pruefbar, dass keine weitere Abhaengigkeit existiert).
Perf-Soak 10 min ohne Regression.
Umsetzung: Portrait-Anforderung (user_rotation 0) und Reverse-
Landschaft (3) - Spielfeld bleibt 2400x1080, PID konstant,
nur 1x "Intro gestartet" im Log (keine Recreation), 5 stabile
HUD-Punkte pixel-identisch zum Baseline-Screenshot (kein 180-
Grad-Flip). Rotation zurueckgesetzt (user_rotation 1,
accelerometer_rotation 0). Hinweis: "wm set-fix-to-user-
rotation" existiert auf dem Emulator-API nicht - die Settings-
Variante (user_rotation) fuehrt die Rotation durch.
## Abnahme-Kriterium 1.1.7
**Fertig wenn:**
1. Rotation: Portrait bleibt verhindert; auch aktivierte System-
Rotation mit Portrait-/Reverse-Anforderung dreht das Spielfeld
nicht (Screenshot-Vergleich), die Activity wird dabei nicht neu
erzeugt (kein neuer Welt-Aufbau im Logcat).
2. Spielverhalten unveraendert zu 1.1.6 (Weiterspielen/Neues Spiel,
Back-Handling inkl. Predictive Back nach configChanges).
3. Alle Unit-Tests gruen; Perf-Soak avg <= 0,7 ms, 0 FATAL/ANR.
4. Version 1.1.7 (versionCode 14), signiert, APK-Groesse
dokumentiert.
## Aufwand 1.1.7
| Aufgabe | Aufwand |
|---------|---------|
| R1 (Manifest + configChanges) | 0,5 |
| R2 + Rotationstest + Abnahme | 0,5 |
**Status: Abgenommen** (Release-Build 1.1.7, Emulator API 37):
1. Rotation live verifiziert: Portrait- und Reverse-Landschafts-
Anforderung (user_rotation 0/3) drehen das Spielfeld nicht
(bleibt 2400x1080 Landschaft, 5 stabile HUD-Punkte pixel-
identisch - kein 180-Grad-Flip), PID konstant, nur 1x
"Intro gestartet" - keine Activity-Recreation. Back-Regression:
Pause per Back (Pixel-Check FORTSETZEN) und Resume per zweitem
Back unveraendert nach der configChanges-Erweiterung.
2. 265 Unit-Tests unveraendert gruen (Manifest-Aenderung ohne
Logik-Beruehrung; ScreenOrientierung war bereits
landscape-locked).
3. Perf-Soak 10,3 min: avg 0,63 ms/Frame (Ziel <= 0,7), 0 FATAL /
0 ANR, PID konstant - ABER: der Emulator war in einem degradierten
Zustand (surfaceflinger flutet ~26-100 skia-"AGTM parsing
failed"-Warnungen pro Sekunde, auch im Idle ohne App; 42k Zeilen
im Soak-Fenster). 28/124 Fenster mit Budget-Ueberschreitungen
(Spikes bis 72 ms) fallen damit zusammen; ein Cold Boot (2x,
einmal mit wipe-data, einmal Software-Rendering) behob es diesmal
NICHT - anders als beim gleichartigen Vorfall in 1.0.1. Der
App-eigene Arbeitswert (avgWorkMs) bleibt im Ziel; fuer eine
saubere overBudget-Messung empfiehlt sich ein Host-Neustart und
ein kurzer Re-Soak.
4. Version 1.1.7 (versionCode 14), gleicher Keystore, APK
124.090 Bytes (identisch zu 1.1.6 - nur Manifest-Attribut-
Erweiterung, kein Zuwachs im DEX).
---
# Version 1.2.0 – Highscore (nach 1.1.7)
Ein Bestenliste-System: Nach dem Ende einer Kampagne (Game Over ->
Neues Spiel) wird aus dem erreichten Stand ein Punktwert berechnet
und zusammen mit einem selbst gewaehlten 10-stelligen alphanume-
rischen Namen gespeichert. Das Hauptmenue zeigt die letzten 10
Eintraege. Weitergespielte Runden (Game Over -> Weiterspielen)
zaehlen NICHT als beendete Kampagne und speichern nichts.
## Score-Berechnung (Gewichtung nach Seltenheit)
Alle Gewichte zentral in GameConfig, Berechnung als pure Funktion
(deterministisch, Unit-testbar):
| Komponente | Punkte | Begruendung |
|---|---|---|
| Korallen | x 1 | Grundressource, laufend produziert |
| Biomasse | x 2 | Jagd-Aufwand, wird vom Rudel verzehrt |
| Kalkstein | x 3 | Gilde noetig, langsamer Zuwachs |
| Biolumineszenz | x 5 | Tiefsee-Spalte, seltene Quelle |
| Wrackteile | x 10 | Fundstuecke, spawnen selten (max 2) |
| je ueberstandene Welle | + 50 | Kampf-Leistung, skaliert mit Welle 14+ |
| je Gebaeude | + Stufe x 25 | Ausbau: St.2 = 50, St.3 = 75 |
| je erobertem Sektor | + 100 | Expansions-Erfolg (8 moeglich) |
## Aufgaben 1.2.0
- [x] **H1 - Score-Funktion (GameConfig + pure Logik):**
HighscoreScore als reine Funktion (Ressourcen, Wellen,
Gebaeude-Liste mit Stufen, eroberte Sektoren -> Punkte);
Gewichte als GameConfig-Konstanten. Der Punktwert wird zum
Game-Over-Zeitpunkt eingefroren (Snapshot, keine Nachtra-
glichkeit durch Restart-Ressourcen-Halbierung).
- [x] **H2 - Persistenz (max. 10 Eintraege):**
HighscoreStore: JSON-Liste in SharedPreferences (eigener
Schluessel, nicht im Save-Format der Kampagne - Highscores
ueberleben einen Neuen-Spiel-Reset). Sortiert absteigend,
nur die besten 10 bleiben. Felder: Name (max. 10 Zeichen,
alphanumerisch), Punkte, Wellen, Version. Test: Roundtrip,
Abschneiden bei >10, Sortierung, Alt-Save-Toleranz.
- [x] **H3 - Namenseingabe nach "Neues Spiel" im Game Over:**
Nach der Bestaetigung (JA, NEU ANFANGEN) oeffnet sich die
Eingabe: System-Dialog (AlertDialog + EditText) statt eigener
Canvas-Tastatur - 10 Zeichen Limit, Filter auf alphanumerisch,
Abbrechen = Eintrag ohne Name ("UNBEKANNT"). Erst nach Dialog-
Ende laeuft resetToNewGame weiter (Score vorher einfrieren).
- [x] **H4 - Highscore-Anzeige im Hauptmenue:**
Menue-Button "Highscore" oeffnet eine Szene/Overlay (wie
Info/Options, 1.0.4-Stil): Liste der bis zu 10 Eintraege
(Rang, Name, Punkte), leer-State ("Noch keine Eintraege"),
Back/Tap zurueck ins Menue. Layout weiten-relativ (UiLayout-
Test auf 3 Formate).
- [x] **H5 - Flow-Regeln + Tests:**
Game Over -> WEITERSPIELEN: KEIN Highscore-Eintrag (Runde
laeuft weiter). Game Over -> NEUES SPIEL (+Bestaetigung):
Eintrag mit eingefrorenem Score. Unit-Tests: Score-Mathe
gegen Konzept-Tabelle, Einfrieren zum Game-Over-Zeitpunkt,
kein Eintrag bei Weiterspielen, Eintrag bei Neues Spiel,
Persistenz-Roundtrip, Sortierung/Schnitt bei 10.
## Abnahme-Kriterium 1.2.0
**Fertig wenn:**
1. Live: Game Over -> Neues Spiel -> Namenseingabe -> Eintrag
erscheint in der Hauptmenue-Highscore (Rang/Name/Punkte);
Game Over -> Weiterspielen erzeugt keinen Eintrag.
2. Score nachvollziehbar: im Logcat dokumentierter Score-Bruchteil
(Ressourcen-/Wellen-/Gebaeude-/Sektoren-Anteil) gegen den
Spielstand vor dem Game Over.
3. Persistenz: Highscore ueberlebt App-Neustart und Neues-Spiel-
Reset (Kaltstart verifiziert); genau 10 Eintraege maximal,
sortiert.
4. Alle Unit-Tests gruen (inkl. H5); Layout auf 3 Formaten;
Perf-Soak ohne Regression (avg <= 0,7 ms, 0 FATAL/ANR) - bei
weiterhin degradiertem Emulator Host-Neustart zuerst.
5. Version 1.2.0 (versionCode 15), signiert, APK-Groesse
dokumentiert.
## Aufwand 1.2.0
| Aufgabe | Aufwand |
|---------|---------|
| H1 (Score-Funktion) | 0,5 |
| H2 (Persistenz) | 0,5 |
| H3 (Namenseingabe) | 1 |
| H4 (Menue-Anzeige) | 1 |
| H5 + Tests + Abnahme | 1 |
**Status: Abgenommen** (Release-Build 1.2.0, Emulator API 37):
1. Live verifiziert: Game Over friert den Punktwert ein (Logcat:
"Highscore-Punkte: 12117" bei Welle 11). NEUES SPIEL -> Bestae-
tigung -> Namens-Dialog (System-AlertDialog, im Emulator-Test
dunkles Theme) -> OK: "Highscore gespeichert: UNBEKANNT (12117
Punkte)" + "Neues Spiel: Spielstand zurueckgesetzt". WEITER-
SPIELEN: "Neustart-Runde"-Log, KEIN Eintrag (Log-Count 0).
Hauptmenue-Button "HIGHSCORE" oeffnet die Liste (Eintrag per
Pixel-Check sichtbar, 52 Text-Sampler), Bestaetigung der
Persistenz: Kaltstart (force-stop + Neustart) zeigt den Eintrag
unveraendert; zwei weitere Eintraege (546/369 Punkte) folgten im
Ablauf und erschienen sortiert. Grenze der Live-Abnahme: das
Eintippen eines Namens per adb-Text-Injektion erreicht das
EditText-Feld ueber den Emulator-IME nicht (Feld blieb leer ->
"UNBEKANNT"); der benannte Pfad ist per Unit-Test mit injizier-
tem Dialog gesichert ("Riffkoenig" -> "RIFFKOENIG" mit ein-
gefrorenem Punktwert).
2. 277 Unit-Tests (12 neu: HighscoreTest - Konzept-Tabelle 1090
Punkte, Einfrieren vor Restart-Halbierung, kein Eintrag bei
Weiterspielen, benannter Eintrag bei Neues Spiel, Abbruch =
UNBEKANNT, Namens-Normalisierung, Roundtrip/Sortierung/10er-
Grenze/Verdraengung, korrupter Store, Menue-Szenen-Zugang,
Menue-Button-Layout auf 3 Formaten), 0 Fehler.
3. Perf-Soak 10,2 min: avg 0,74 ms - ABER erneut im degradierten
Emulator-Zustand (surfaceflinger-skia-Flut: 47k Zeilen im
Fenster, schon fruehe Fenster 0,78 ms; zweiter Cold-Boot brachte
keine Besserung: 835 skia-Zeilen direkt nach Boot). 0 FATAL /
0 ANR, PID konstant. Design-Argument: KEIN 1.2.0-Code liegt im
Frame-Pfad (Score nur zum Game Over, Liste beim Oeffnen gecacht
+ Zeilen-Cache, Store-Schreiben einmalig je Eintrag, Dialog ist
System-UI). Fuer eine saubere avg-Zahl bleibt der Host-Neustart
empfohlen (vgl. 1.1.7-Dokumentation).
4. Version 1.2.0 (versionCode 15), gleicher Keystore, APK 127.562
Bytes (1.1.7: 124.090; +3,5 KB durch Highscore/HighscoreScene/
Namens-Dialog und SaveGameStore-Erweiterung).
