Measured while building #16: at 10 000 enemies the grid rebuild costs 0.057 ms against 0.021 ms for move_enemies — 2.7x more, not less. ADR-0004 §3 counted arithmetic and missed what dominates: move_enemies is a linear table walk, the rebuild writes into ~7000 scattered buckets. The decision does not change. It never rested on the comparison but on the absolute bound of 0.10 ms, and 0.057 ms is 0.34 % of the frame budget; an incremental update would pay the same scattered write on every cell crossing. Revision trigger 1 and the acceptance criterion of spec feature 3 are restated on that bound, which is what the runnable check now asserts. Also records the second deviation the build produced: a cell holds (Entity, Vec2) rather than Entity, because soft separation writes Transform while reading its neighbours' positions.
691 lines
50 KiB
Markdown
691 lines
50 KiB
Markdown
# Vertical-Slice-Spec: InfiniTower
|
||
|
||
- **Status:** Angenommen
|
||
- **Datum:** 2026-08-10
|
||
- **Betrifft:** Den vollständigen Funktionsumfang des Vertical Slice — was gebaut wird und woran
|
||
wir erkennen, dass es fertig ist
|
||
- **Baut auf:** [ADR-0001](adr/0001-balancing-daten-als-rust-konstanten.md) (Balancing-Daten),
|
||
[ADR-0002](adr/0002-stat-und-modifier-pipeline.md) (Stat- und Modifier-Pipeline),
|
||
[ADR-0003](adr/0003-save-format-ort-und-migration.md) (Save-Format),
|
||
[ADR-0004](adr/0004-spatial-grid-fuer-gegner-nachbarschaft.md) (Spatial Grid und weiche
|
||
Separation), `CONTEXT.md` (Glossar und Slice-Scope), Karte in Issue #1
|
||
- **Vokabular:** verbindlich aus `CONTEXT.md`. Diese Spec führt keine neuen Begriffe ein, außer den
|
||
beiden in Abschnitt „Zwei Grenzwerte, zwei Begriffe" ausgewiesenen.
|
||
|
||
Diese Spec sagt, **was** gebaut wird. Die ADRs sagen, **warum es so** gebaut wird — ihre Argumente
|
||
werden hier nicht wiederholt, sondern verlinkt. Wo eine Zahl noch nicht feststeht, steht sie als
|
||
‹Platzhalter›; sie lebt dann in `Balance` (ADR-0001) und wird durch Spielen gefunden, nicht hier.
|
||
|
||
---
|
||
|
||
## Der Loop
|
||
|
||
Der Spieler startet einen Run. Der Turm steht in der Mitte der Arena und schießt von selbst auf
|
||
Gegner, die in Wellen radial von außen anlaufen; jeder Kill gibt Coins, und aus Coins kauft der
|
||
Spieler jederzeit In-Run-Upgrades, die den Turm für diesen Run stärker machen. Die Wellen wachsen
|
||
schneller als das, was er sich leisten kann — nach sechs bis neun Minuten überrennen sie ihn, der
|
||
Turm stirbt, und alle In-Run-Upgrades verfallen mit ihm. Was bleibt, ist die Meta-Währung, die die
|
||
Gegner unterwegs gedroppt haben, und was der Boss auf Welle 10 fallen gelassen hat. Damit kauft er
|
||
zwischen den Runs permanente Workshop-Upgrades und rüstet gedroppte Cards aus. Der nächste Run
|
||
beginnt mit einem stärkeren Turm, kommt tiefer, wirft mehr ab — und endet wieder. Das ist das
|
||
Spiel: sterben ist der Fortschrittsmechanismus, nicht sein Gegenteil.
|
||
|
||
---
|
||
|
||
## Zwei Grenzwerte, zwei Begriffe
|
||
|
||
Die Karte nennt beides „Cap", meint aber zwei verschiedene Dinge. Diese Spec trennt sie, und der
|
||
Code tut es auch:
|
||
|
||
| Begriff | Code | Bedeutung |
|
||
|---|---|---|
|
||
| **Harte Obergrenze** | `HARD_ENEMY_CAP` | 10 000 gleichzeitige Gegner. Ein Perf-Grenzwert aus #4, kein Design-Werkzeug. Wird nie überschritten, von nichts. |
|
||
| **Soll-Population** | `target_population(wave)` | Die für die aktuelle Welle angestrebte Zahl lebender Gegner. Wächst mit dem Wellenzähler und ist selbst Teil der Schwierigkeitskurve. Bindet ständig — sie ist die Design-Zielgröße. |
|
||
|
||
Ein Effekt, der „mehr Gegner" verspricht, hebt die **Soll-Population** an, nie eine Spawn-Zahl
|
||
(Begründung: Cap-Analyse in Issue #1 — bei bindender Soll-Population wäre ein Spawn-Multiplikator
|
||
exakt wirkungslos). Die harte Obergrenze bleibt davon unberührt.
|
||
|
||
---
|
||
|
||
## Feststehende Zahlen
|
||
|
||
Alles hier ist entschieden. Alles andere ist ‹Platzhalter› oder steht unter „Offen".
|
||
|
||
| Größe | Wert | Quelle |
|
||
|---|---|---|
|
||
| Wellen-Takt | ‹26 s› Spawn-Phase + ‹9 s› Cooldown-Phase = ‹35 s› bei Normalgeschwindigkeit | Primärtext, als Startwert übernommen (#8) |
|
||
| Wellen pro Spielzeit | 10 min ≈ 17 Wellen · 20 min ≈ 34 · 30 min ≈ 51 | Folge des Takts (#8) |
|
||
| Boss-Welle | jede 10. Welle | Primärtext (#3), übernommen |
|
||
| Ziel für Run 1 | Tod zwischen **Welle 10 und 15** — genau ein Boss gesehen, kurz danach tot; 6–9 Minuten | #8 |
|
||
| Wachstumsform | konstant exponentiell pro Welle, plus ein Multiplikator auf Boss-Wellen | #8 |
|
||
| Kosten- vs. HP-Verlauf | Kosten wachsen anfangs langsamer als Gegner-HP, ab ca. zwei Dritteln der Runlänge überholt die HP-Kurve. Entsteht von selbst, wenn der Kosten-Exponent das Einkommenswachstum übersteigt — **kein separater Knick** | #8 |
|
||
| Meta-Ertrag | Drop-basiert: Chance je Normalgegner, **garantiert je Boss**; gedroppte Menge wellenabhängig | #8 |
|
||
| Workshop-Kosten | exponentiell pro Level | #8 |
|
||
| Tuning-Zielgröße im Run | Zeit zwischen zwei bezahlbaren Käufen: früh ‹10–20 s›, später seltener | #8 |
|
||
| Harte Gegner-Obergrenze | 10 000 gleichzeitig (gemessener Bruch bei ~70 000, ohne Texturen/UI/Audio) | #4, Issue #1 |
|
||
| Perf-Budget bei 10 000 | Gameplay-Logik 0,114 ms von 16,67 ms; Rendering 96–98 % | #4 |
|
||
| In-Run-Shop-Struktur | vier Tabs: **Attack Shop · Defence Shop · Utility Shop · Ultimates** | HUD-Mockup, #10 |
|
||
| Shop-Kapazität | ~8 Zeilen je Tab ohne Scrollen → Größenordnung **~24 In-Run-Upgrades** | HUD-Mockup, #10 |
|
||
| Kauf-Modus | Buy x1, Kosten pro Kauf, **jederzeit** im Run — kein Wellenende, keine Freigabe | Issue #1, #8 |
|
||
| Card-Seltenheiten | drei: **Common / Rare / Epic** | #11 |
|
||
| Card-Slots | Start **1**, Wachstum auf **5**; die Slot-Zahl ist selbst Progression | #11 |
|
||
| Card-Quelle | Drop aus Fortschritt (Boss-Kill, neuer Wellen-Rekord). Kein Kauf, keine dritte Währung | #11 |
|
||
| Card-Randbedingungen | Slot 2 fällt **früh** (erste paar Runs); **mindestens zwei Epics** | #11 |
|
||
| Slot-Quelle | **Kauf aus der Meta-Währung im Workshop**, exponentiell wachsende Kosten wie bei den Stat-Upgrades. Kein zweiter Freischalt-Mechanismus, keine Meilenstein-Schwelle | diese Spec (löst Widerspruch 2) |
|
||
| Utility-Tab | **Ökonomie-Upgrades, keine Kampf-Stats** — vier Zeilen, siehe Feature 6 | #3-Kalibrierung, `docs/WikiData.md`; diese Spec (löst Widerspruch 5) |
|
||
| Gegner-gegen-Gegner | **Spatial Grid mit weicher Separation**; dasselbe Grid trägt Zielauswahl, Reichweiten-Scan und Projektil-Kollision | ADR-0004 (löst Lücke 9) |
|
||
| Ultimate | genau eine: **Death Wave**, radialer Sweep, automatisch ausgelöst | #9 |
|
||
| Auslöse-Regel | **Cooldown UND ein Weltzustand**, je Fähigkeit fest verdrahtet, nicht einstellbar; feuern mehrere gleichzeitig, feuern alle | #12 |
|
||
| Währungen | zwei: `Coins` (In-Run, verfällt) und die Meta-Währung (Workshop). Nicht ineinander konvertierbar | Issue #1, #11 |
|
||
| Turm-Stats | 16, siehe Tabelle in ADR-0002 §3 samt Nachtrag vom 2026-08-10 | ADR-0002 |
|
||
|
||
**Prüfkriterium für die fertige Kurve** (#8, gilt für die vier freien Zahlen):
|
||
|
||
1. Run 1 endet zwischen Welle 10 und 15.
|
||
2. Jeder Run trägt mindestens ein Workshop-Level ein.
|
||
3. Die Run-Tiefe wächst über die ersten zehn Runs monoton, ohne dass ein Run mehr als etwa das
|
||
Doppelte des vorigen erreicht.
|
||
|
||
---
|
||
|
||
## Was im Slice ist
|
||
|
||
### 1. Arena, Turm und Kamera
|
||
|
||
2D, `bevy_sprite`, orthographische Kamera. Ein `Tower` fest in der Mitte, Programmer-Art. Kein
|
||
Scrollen, kein Zoom, keine Turm-Platzierung.
|
||
|
||
**Akzeptanz**
|
||
- Nach Programmstart und Run-Start ist der Turm sichtbar in der Bildmitte des Gameplay-Fensters, und
|
||
die gesamte Spawn-Peripherie liegt innerhalb des sichtbaren Bereichs.
|
||
- Die Turm-Entity wird beim Run-Start gespawnt und beim Run-Ende despawnt (ADR-0002 §6); zwischen
|
||
zwei Runs existiert keine Entity mit `EffectiveStats`.
|
||
|
||
### 2. Wellen-Takt
|
||
|
||
Eine `Wave` besteht aus zwei separat getakteten Phasen. Die nächste Welle beginnt, wenn **beide**
|
||
Timer abgelaufen sind — nicht wenn alle Gegner tot sind. Eine einzige endlose Kurve, keine Tiers.
|
||
|
||
**Akzeptanz**
|
||
- Während der Spawn-Phase erscheinen Gegner; nach ‹26 s› hören Spawns auf, während bereits
|
||
vorhandene Gegner weiter laufen und angreifen; nach weiteren ‹9 s› beginnt Welle N+1.
|
||
- Wird Welle N+1 gestartet, während noch Gegner aus Welle N leben, laufen diese unverändert weiter
|
||
und werden nicht entfernt.
|
||
- Der aktuelle Wellenzähler ist im HUD ablesbar und stimmt mit dem Telemetrie-Log überein.
|
||
- Auf jeder Welle mit `wave % 10 == 0` spawnt genau ein Boss.
|
||
|
||
### 3. Gegner und Population
|
||
|
||
Drei bis vier Normal-Archetypen plus ein Boss (#3): ein Tempo/HP-Tradeoff-Paar, ein Fernkämpfer und
|
||
**eine** Sondermechanik, die Ziel-Priorisierung erzwingt (Splitter oder Schutz-Aura). Der Boss ist
|
||
ein einzelner schwerer, langsamer Meilenstein-Gegner. Keine Elite-Klasse, keine Fleet-Klasse, keine
|
||
Immunitätsmatrix.
|
||
|
||
Der Spawner ist **cap-getrieben, nicht plan-getrieben**: er füllt bis zur `target_population(wave)`
|
||
auf, statt N Gegner nach Zeitplan zu setzen (Cap-Analyse, Issue #1). Gegner laufen radial von außen
|
||
auf den Turm zu und greifen ihn im Nahbereich an.
|
||
|
||
Gegner **stapeln nicht frei übereinander**: Sie drücken sich weich auseinander, nach
|
||
[ADR-0004](adr/0004-spatial-grid-fuer-gegner-nachbarschaft.md). Das ist keine Kollisionsauflösung —
|
||
Überlappung bleibt erlaubt, verboten ist nur, dass tausende Sprites am Turmring deckungsgleich
|
||
liegen und die Soll-Population unlesbar machen. Der Versatz ist auf einen Bruchteil des
|
||
Bewegungsschritts geklemmt und kann den radialen Anlauf deshalb nie umkehren. Die Nachbarschaft
|
||
kommt aus dem `SpatialGrid`, das einmal pro Frame neu gebaut wird und außerdem Feature 4, das
|
||
Auslöse-System und die Projektil-Kollision bedient.
|
||
|
||
**Akzeptanz**
|
||
- Bei laufender Spawn-Phase pendelt die Zahl lebender Gegner um `target_population(wave)`, mit einer
|
||
Abweichung von höchstens ‹±10 %› über ein 5-Sekunden-Fenster.
|
||
- Die Zahl lebender Gegner überschreitet **nie** `HARD_ENEMY_CAP` — auch nicht bei einem
|
||
Sprung-zu-Welle auf die höchste im Debug-Panel erreichbare Welle.
|
||
- Bei 10 000 gleichzeitigen Gegnern bleibt die Frametime unter 16,67 ms (Nachmessung mit dem Aufbau
|
||
aus `examples/perf_spike.rs`, nach dem Wechsel auf echte Sprites zu wiederholen).
|
||
- Der Rebuild des `SpatialGrid` kostet bei 10 000 Gegnern **höchstens 0,10 ms** (ADR-0004 §3 — die
|
||
Entscheidung gegen ein inkrementelles Update steht auf dieser Schranke; reißt sie, ist die ADR
|
||
fällig, nicht die Zahl). *Bis 2026-08-11 stand hier „nicht mehr als `move_enemies` im selben
|
||
Frame"; die Messung in #16 hat diesen Maßstab widerlegt — gemessen 0,057 ms gegen 0,021 ms —, ohne
|
||
die Schranke zu reißen. Siehe den Nachtrag zu ADR-0004 §3.*
|
||
- Kein Gegner bewegt sich in einem Frame vom Turm **weg**, solange ihn keine Fähigkeit dorthin
|
||
drückt — die Separationsklemmung macht das konstruktiv unmöglich, und der Turmring pulsiert
|
||
sichtbar nicht.
|
||
- Jeder Archetyp ist an Sprite-Farbe oder -Form ohne Text unterscheidbar.
|
||
|
||
### 4. Turm-Dauerfeuer und geteilter Reichweiten-Scan
|
||
|
||
Der Turm schießt selbstständig auf Gegner in Reichweite. Genau **ein** System ermittelt pro Frame
|
||
den Reichweiten-Zustand; `tower_fire` und **alle** Auslöse-Bedingungen (Feature 7) lesen dessen
|
||
Ergebnis. Das ist keine Optimierung für später, sondern die Bedingung, unter der die
|
||
Auslöse-Entscheidung aus #12 tragfähig ist.
|
||
|
||
Der Reichweiten-Zustand und die Zielauswahl sind **Abfragen an das `SpatialGrid`**, kein linearer
|
||
Durchlauf über die Gegner (ADR-0004). #12 hat die siebenfache Ausführung desselben Scans beseitigt;
|
||
ADR-0004 beseitigt, dass der eine verbleibende Scan noch O(n) ist — die Zielauswahl läuft
|
||
zellenweise von der Turmmitte nach außen und bricht beim ersten Treffer ab. Damit gilt eine
|
||
schärfere Regel als bisher: **Genau ein System durchläuft die Gegnermenge, und das ist der
|
||
Grid-Rebuild.**
|
||
|
||
**Akzeptanz**
|
||
- Bei mindestens einem Gegner in Reichweite feuert der Turm ohne jede Eingabe innerhalb von
|
||
`EffectiveStats.AttackInterval` Sekunden.
|
||
- Ohne Gegner in Reichweite feuert er nicht.
|
||
- Es existiert genau ein System, das die Gegnermenge in Turm-Reichweite bestimmt, und es fragt das
|
||
`SpatialGrid`; ein `grep` nach einem zweiten Reichweiten-Scan über die Gegner-Query findet keinen.
|
||
- **Außer `rebuild_spatial_grid` iteriert kein System die Gegner-Query, um Nachbarn zu finden** —
|
||
weder die Separation, noch `tower_fire`, noch das Auslöse-System, noch die Projektil-Kollision
|
||
(ADR-0004 §6).
|
||
|
||
### 5. Stat- und Modifier-Pipeline
|
||
|
||
Vollständig nach [ADR-0002](adr/0002-stat-und-modifier-pipeline.md): drei Akkumulatoren (`flat`,
|
||
`points`, `mult`), ein Modus-Feld je Stat mit den drei Varianten `Direct` / `Interval` /
|
||
`Fraction`, Clamp einmal auf die Prozentpunkt-Summe, `EffectiveStats` als `Component` an der
|
||
Turm-Entity, Neuberechnung bei Änderung. `type Stat = f32`. Alle Zahlen aus `Res<Balance>`
|
||
(ADR-0001).
|
||
|
||
**Akzeptanz**
|
||
- Das durchgerechnete Beispiel aus ADR-0002 („Durchgerechnetes Beispiel") ist als Test hinterlegt
|
||
und grün — mit den dort genannten Platzhalterwerten, nicht mit den späteren echten. Das schließt
|
||
den **Crit-Chance-Fall** ein: Drei Quellen mit zusammen 37 pp ergeben 37 %, nicht 0.
|
||
- Kein Stat im Modus `Direct` hat `base = 0` und `points` als einzige erwartete Quelle
|
||
(ADR-0002 §1, Prüfregel 3) — prüfbar an der Stat-Tabelle, ohne das Spiel zu starten.
|
||
- Nach einer Änderung an `Workshop`, `EquippedCards` oder `RunUpgrades` läuft die Neuberechnung
|
||
genau einmal; ohne Änderung läuft sie nicht (nachweisbar über einen Zähler im Debug-Panel).
|
||
- `sync_tower_abilities` ist der einzige Schreiber von Fähigkeits-Components; sein `debug_assert`
|
||
am Ende des Abgleichs ist vorhanden und greift im Debug-Build.
|
||
- Jeder Modifikator, der in den Slice aufgenommen wird, hat die Sichtbarkeitsanforderung aus
|
||
ADR-0002 §11 vor dem Merge durchlaufen — inklusive Punkt 3 (beobachtbare Folge in der
|
||
Run-Telemetrie).
|
||
|
||
### 6. Coins und In-Run-Upgrades
|
||
|
||
`Coins` fallen bei jedem Kill an und werden **beim Tod gutgeschrieben** — kein einsammelbares Objekt,
|
||
keine Entity (Cap-Analyse, Issue #1: bei tausenden Kills pro Welle wäre das ein Entity-Sturm). Damit
|
||
kauft der Spieler In-Run-Upgrades in drei Tabs (Attack / Defence / Utility), jederzeit, einzeln
|
||
(Buy x1). Alle verfallen am Run-Ende, weil sie als `RunUpgrades`-Component an der Turm-Entity hängen
|
||
(ADR-0002 §6).
|
||
|
||
**Der Utility-Tab trägt Ökonomie-Upgrades, keine Kampf-Stats.** Das ist keine Verlegenheitslösung,
|
||
sondern das, was Utility im Referenzspiel ist: ein Ökonomie-Multiplikator (Kalibrierungsbericht in
|
||
#3; `docs/WikiData.md`, Workshop-Abschnitt, listet dort Cash Bonus, Cash/Wave, Coins/Kill Bonus und
|
||
Free Upgrades). Vier Zeilen, jede mit ihrem Grund:
|
||
|
||
| Zeile | Stat | Warum sie da ist |
|
||
|---|---|---|
|
||
| Coin-Ertrag pro Kill | `CoinGain` | der Grundkanal — Einkommen skaliert linear mit Kills und damit mit der Soll-Population; ohne ihn hat der Tab keinen Anker |
|
||
| Coins am Wellenende | `CoinsPerWave` | der einzige Ertrag, der **nicht** mit der Gegnerdichte wächst. Die Cap-Analyse (#1) verlangt, die Economy gegen *Kills pro Welle* zu normieren statt gegen Wellen — ein wellengebundener Sockel ist der Gegenpol dazu und trägt früh, wenn die Population noch klein ist |
|
||
| Coin-Bonus auf Crit-Kills | `CritCoinGain` | koppelt Utility an Attack: Er ist wertlos, solange `CritChance` bei 0 steht, und wird stark, sobald der Attack-Tab bedient wurde. Genau das macht die Kaufreihenfolge zu einer Entscheidung statt zu einer Sortierung (#8, siehe Akzeptanz unten) |
|
||
| Chance auf ein kostenloses Upgrade | `FreeUpgradeChance` | der einzige Utility-Effekt, der in die anderen Tabs zurückwirkt: Er kauft nicht Coins, sondern **Zeit**, und wirkt damit direkt auf die Tuning-Zielgröße „Zeit zwischen zwei bezahlbaren Käufen". Eine Auswertung je Wellenende, ein kostenloses Level in einem zufällig gewählten Tab — das Original hat drei getrennte Stats dafür, #3 empfiehlt für Utility ausdrücklich „stark vereinfachen" |
|
||
|
||
Die vier Stats stehen in ADR-0002 §3 (Nachtrag). **`MetaGain` gehört ausdrücklich nicht in diesen
|
||
Tab**: Ein aus `Coins` gekaufter Multiplikator auf die Meta-Währung wäre ein Konvertierungspfad
|
||
zwischen den beiden Währungen, und den schließt Issue #1 aus. `MetaGain` ist ein Workshop-Upgrade
|
||
(Feature 10).
|
||
|
||
Damit stehen im Panel 5 Attack-, 4 Defence- und 4 Utility-Zeilen — **13 von den ~24 Zeilen, die das
|
||
Layout hergibt.** Die Folgerung des HUD-Kommentars „der Spieler kann in einem Run nicht alles
|
||
kaufen" trägt deshalb nicht mehr aus dem Layout; sie muss aus den Kosten kommen oder gar nicht
|
||
(siehe „Widersprüche", Punkt 5).
|
||
|
||
**Akzeptanz**
|
||
- Ein Kauf ist zu jedem Zeitpunkt eines laufenden Runs möglich, auch mitten in der Spawn-Phase, ohne
|
||
dass die Simulation anhält.
|
||
- Ein Klick auf „Buy x1" zieht genau die angezeigten Kosten ab, erhöht das Upgrade um eine Stufe,
|
||
und der betroffene Wert in `EffectiveStats` ändert sich im selben Frame ablesbar.
|
||
- Ist der Coin-Stand kleiner als die Kosten, ist die Zeile nicht klickbar und als solche erkennbar.
|
||
- Nach einem Run-Ende und Run-Start stehen alle In-Run-Upgrades wieder auf Stufe 0, ohne dass
|
||
irgendein System sie zurücksetzt.
|
||
- Kosten und Wirkung unterscheiden sich zwischen den drei Tabs merklich genug, dass die
|
||
Telemetrie über zehn Runs **nicht** dieselbe Kaufreihenfolge zeigt (#8: sonst ist die
|
||
Kaufreihenfolge eine Sortierung, keine Entscheidung).
|
||
- Der Utility-Tab enthält **keinen** Kauf, der die Meta-Währung, ihre Drop-Chance oder ihre Menge
|
||
verändert — kein Pfad im Run wandelt `Coins` in Meta-Fortschritt.
|
||
- Jede der vier Utility-Zeilen hat die Sichtbarkeitsanforderung aus ADR-0002 §11 durchlaufen; für
|
||
`CritCoinGain` liegt die dort zu nennende Fortschritts-Stelle **hinter** dem ersten Crit-Kauf.
|
||
|
||
### 7. Auslöse-System für Fähigkeiten
|
||
|
||
**Ein** System wertet die Auslöse-Bedingungen aller aktiven Fähigkeiten aus — der Ultimate und jeder
|
||
Fähigkeits-Card. Eine Bedingung ist **Cooldown UND ein Weltzustand** (z. B. „‹N› Gegner in
|
||
Reichweite"), je Fähigkeit fest verdrahtet und nicht vom Spieler einstellbar. Die Werte kommen wie
|
||
alle Zahlen aus `Balance`. Feuern mehrere im selben Frame, feuern alle — keine Sperre, keine
|
||
Priorität.
|
||
|
||
**Akzeptanz**
|
||
- Eine Fähigkeit feuert erst, wenn beide Teile der Bedingung erfüllt sind; ein abgelaufener Cooldown
|
||
allein löst nachweislich nicht aus (prüfbar, indem man die Arena leerräumt und wartet).
|
||
- Sind zwei Fähigkeiten im selben Frame auslösebereit, feuern beide im selben Frame.
|
||
- Die Bedingungswerte stehen in `Balance` und sind über das Debug-Panel zur Laufzeit änderbar; keine
|
||
Schwelle steht als Literal im Gameplay-Code (ADR-0001 §4).
|
||
- Das System liest den Reichweiten-Zustand aus Feature 4 und führt keinen eigenen Scan aus — weder
|
||
über die Gegner-Query noch als eigene Abfrage am `SpatialGrid` (ADR-0004 §6).
|
||
|
||
### 8. Ultimate: Death Wave
|
||
|
||
Ein radialer Sweep von der Turmmitte nach außen, automatisch ausgelöst über Feature 7. Kein
|
||
Zielsystem, kein Knopf, kein Eingabepfad. Die Systemanforderung, die sie aufdeckt, ist der
|
||
**gebatchte Massentod**: ein Sweep kann in einem Tick tausende Gegner treffen.
|
||
|
||
**Akzeptanz**
|
||
- Bei erfüllter Bedingung startet der Sweep ohne Eingabe; ein sichtbarer Ring wandert von der
|
||
Turmmitte nach außen und trifft Gegner in der Reihenfolge ihres Abstands.
|
||
- Ein Sweep, der ‹5 000› Gegner gleichzeitig tötet, erzeugt keinen Frame über 16,67 ms — der
|
||
Massentod wird gebatcht, nicht als tausende Einzelereignisse abgearbeitet.
|
||
- Der verbleibende Cooldown ist im Ultimates-Tab ablesbar.
|
||
- Flächenwirkung ist über ein **Ziel-Limit pro Auslösung** gedeckelt, nicht über einen schrumpfenden
|
||
Radius (Cap-Analyse, Issue #1).
|
||
|
||
### 9. Cards, Slots und Loadout
|
||
|
||
Cards fallen als Drop aus Fortschritt: **Boss-Kill** oder **neuer Wellen-Rekord**. Sie sind nicht
|
||
kaufbar und haben keine eigene Währung. Eine Card ist entweder ein Stat-Modifikator oder eine
|
||
**Fähigkeit** (`CardEffect::Modifier` / `CardEffect::Ability`, ADR-0002 §8) — Fähigkeits-Cards feuern
|
||
über dasselbe System wie die Ultimate. Drei Seltenheiten, Slots von 1 auf 5.
|
||
|
||
**Slots werden im Workshop aus der Meta-Währung gekauft** (Feature 10) — sie stehen dort neben den
|
||
Stat-Upgrades, mit derselben exponentiellen Kostenform. Es gibt keinen zweiten Freischalt-Weg: keine
|
||
Wellen-Meilensteine, keine Boss-Zahl, keinen Drop. Cards selbst bleiben unverkäuflich und fallen
|
||
weiter als Drop; gekauft wird nur die **Breite** des Loadouts. Damit ist die eigentliche
|
||
Meta-Entscheidung des Slice ausgesprochen: Breite (ein Slot mehr) gegen Stärke (ein Stat-Level
|
||
mehr), aus demselben Topf.
|
||
|
||
Das **Loadout steht vor dem Run fest**. Im laufenden Run sind Cards über den Pause-Button nur
|
||
einsehbar, nicht wechselbar (Issue #1). Damit wird im laufenden Run nie eine Fähigkeits-Component
|
||
entfernt (ADR-0002 §10, Nachtrag zu #6).
|
||
|
||
**Akzeptanz**
|
||
- Nach einem Boss-Kill oder einem neuen Wellen-Rekord ist die Card-Ausbeute des Runs auf dem
|
||
Meta-Screen sichtbar, spätestens beim ersten Betreten nach dem Run-Ende.
|
||
- Der Spieler kann vor dem Run so viele Cards ausrüsten, wie er Slots hat, und keine mehr.
|
||
- Eine ausgerüstete Fähigkeits-Card erzeugt beim Run-Start genau eine Fähigkeits-Component am Turm;
|
||
eine abgelegte erzeugt keine.
|
||
- Während eines Runs ändert sich `EquippedCards` nicht — kein Pfad in der UI erlaubt es.
|
||
- Der Card-Pool enthält mindestens zwei Epics, und Slot 2 ist innerhalb der ersten ‹drei› Runs
|
||
**bezahlbar** (#11; beide Bedingungen sind an das Abnahmekriterium des Slice gebunden).
|
||
- Es gibt genau einen Pfad, der `card_slots` erhöht, und der ist ein Workshop-Kauf; kein Boss-Kill,
|
||
kein Wellen-Rekord und kein Drop verändert die Slot-Zahl.
|
||
|
||
### 10. Meta-Währung und Workshop
|
||
|
||
Die zweite Währung fällt aus denselben Kill-Events wie `Coins`, ist aber nicht in sie konvertierbar.
|
||
Drop-Chance je Normalgegner, garantiert je Boss, Menge wellenabhängig. Gutschrift beim Tod des
|
||
Gegners, kein Pickup. Der `Workshop` kauft davon permanente Stat-Upgrades mit exponentiell
|
||
wachsenden Kosten. Unter den Stats liegt auch `MetaGain` — der Multiplikator auf die gedroppte
|
||
Meta-Menge, der aus demselben Grund **nicht** im In-Run-Shop steht (Feature 6).
|
||
|
||
**Der Workshop verkauft außerdem Card-Slots.** Sie stehen als eigene Zeile neben den Stat-Upgrades,
|
||
in derselben Währung, mit derselben exponentiellen Kostenform — vier Käufe insgesamt, von Slot 1 auf
|
||
Slot 5. Der Spieler entscheidet damit selbst zwischen Breite und Stärke; das ist die eine
|
||
Meta-Entscheidung, die der Slice zu zeigen hat. Ein zusätzliches persistiertes Feld braucht es
|
||
nicht: `card_slots: u8` aus ADR-0003 §5 ist zugleich Zustand und Kaufstufe.
|
||
|
||
**Der erste Slot-Preis ist keine freie Zahl.** #11 bindet Slot 2 an das Abnahmekriterium — er muss
|
||
innerhalb der ersten paar Runs fallen. Die Kosten für Slot 2 sind damit an den Meta-Ertrag der
|
||
ersten ‹drei› Runs gebunden, nicht an die Kostenkurve der Stat-Upgrades; erst ab Slot 3 ist der
|
||
Exponent frei. Beide Werte bleiben ‹Platzhalter› und leben in `Balance`.
|
||
|
||
**Akzeptanz**
|
||
- Eine Ausgabe von `Coins` im Run verringert den Meta-Währungs-Stand nicht, und umgekehrt.
|
||
- Ein Boss-Kill schreibt garantiert Meta-Währung gut; die gutgeschriebene Menge auf Welle 20 ist
|
||
höher als auf Welle 10.
|
||
- Ein Workshop-Kauf erhöht den `flat`- bzw. `mult`-Beitrag des betroffenen Stats sofort und
|
||
überlebt ein Run-Ende sowie einen Programm-Neustart.
|
||
- Über die ersten zehn Runs trägt jeder Run mindestens ein Workshop-Level ein (#8, Prüfkriterium 2).
|
||
- Slot 2 ist im Workshop nach höchstens ‹drei› Runs bezahlbar, ohne dass der Spieler auf
|
||
Stat-Upgrades verzichtet hat — die Prüfung ist ein gespielter Durchlauf, keine Rechnung.
|
||
- Ein gekaufter Slot erhöht `card_slots` um genau 1 und überlebt einen Programm-Neustart.
|
||
|
||
### 11. Run-Ende und Meta-Übergang
|
||
|
||
Fällt die Turm-HP auf 0, endet der Run. **Genau ein System** überführt am Run-Ende die aus dem Run
|
||
abgeleiteten Meta-Werte (`highest_wave`, verdiente Meta-Währung, Card-Ausbeute) in die persistierten
|
||
Resources (ADR-0003 §6). Danach wird die Turm-Entity despawnt; alles Run-gebundene verschwindet mit
|
||
ihr, ohne Aufräum-Code.
|
||
|
||
**Akzeptanz**
|
||
- Bei HP ≤ 0 endet der Run innerhalb eines Frames; danach existiert keine Entity mit
|
||
`EffectiveStats`, `RunUpgrades` oder einer Fähigkeits-Component mehr.
|
||
- `highest_wave` wächst nur, wenn die erreichte Welle den bisherigen Stand übersteigt, und wird
|
||
genau einmal pro Run geschrieben.
|
||
- Kein anderes System als dieses eine schreibt aus Run-Zustand in eine persistierte `Resource`
|
||
(prüfbar über die Schreibstellen der `SettingsGroup`-Resources).
|
||
- Der Spieler landet nach dem Run-Ende auf dem Meta-Screen, ohne Zwischenschritt und ohne dass ein
|
||
weiterer Run automatisch startet.
|
||
|
||
### 12. In-Run-HUD und Pause
|
||
|
||
Links das Gameplay-Fenster, rechts ein vertikales Panel über die volle Höhe. Das Panel trägt oben
|
||
den Namen des aktiven Tabs, darunter bis zu ~8 Zeilen im Schema `<Stat Name>` / `<Stat Value>` links
|
||
und `Buy x1` / `<Cost>` rechts. Belegt sind davon 5 (Attack), 4 (Defence) und 4 (Utility) — das
|
||
Panel füllt seine Zeilen also **nicht** aus, und das ist eine Folge der Stat-Liste, kein
|
||
Zwischenstand (Feature 6). Unten vier Tabs: **Attack Shop · Defence Shop · Utility Shop ·
|
||
Ultimates**. Der Ultimates-Tab trägt die Cooldown-Anzeige (#12). Ein Pause-Button friert die
|
||
**gesamte** Simulation ein und gibt Einsicht in die ausgerüsteten Cards.
|
||
|
||
Das Mockup (`docs/design/hud-in-run.png`) legt Panel und Tabs fest, zeigt aber weder Turm-HP,
|
||
Wellenzähler, Währungsstände noch den Pause-Button. Diese vier gehören ins Gameplay-Fenster oder
|
||
dessen Rand; ihre Platzierung ist frei, ihre Existenz nicht.
|
||
|
||
**Akzeptanz**
|
||
- Die vier Tabs sind vorhanden und schaltbar; ein Tab-Wechsel hält die Simulation nicht an.
|
||
- Turm-HP, aktuelle Welle, `Coins`-Stand und Meta-Währungs-Stand sind während des Runs jederzeit
|
||
ohne Tab-Wechsel ablesbar.
|
||
- Der Pause-Button friert Gegnerbewegung, Turmfeuer, Wellen-Timer und Fähigkeits-Cooldowns
|
||
gleichzeitig ein; nach dem Fortsetzen laufen alle vier von genau ihrem Stand weiter.
|
||
- Im Pause-Zustand sind die ausgerüsteten Cards einsehbar und **nicht** wechselbar.
|
||
- Es gibt **keine** „Gespeichert"-Bestätigung — das Programm kann sie nicht belegen (ADR-0003 §8).
|
||
|
||
### 13. Meta-Screen zwischen den Runs
|
||
|
||
Der Zustand zwischen zwei Runs. Er trägt Workshop-Käufe, das Card-Loadout, den Slot-Fortschritt und
|
||
den Start-Knopf für den nächsten Run. **Sein Aufbau ist offen** (siehe „Offen"); was hier steht, ist
|
||
seine Funktion, nicht sein Layout.
|
||
|
||
Der Slot-Fortschritt ist dabei **kein eigener Bildschirmteil**: Slots werden im Workshop gekauft
|
||
(Feature 10) und sind dort eine Zeile neben den Stat-Upgrades. Der Meta-Screen zeigt sie im
|
||
Card-Bereich nur an, er verkauft sie nicht an zweiter Stelle.
|
||
|
||
**Akzeptanz**
|
||
- Ein neuer Run ist nur von hier aus startbar.
|
||
- Workshop-Käufe, Card-Loadout und Slot-Stand sind von hier aus erreichbar und änderbar.
|
||
- Der Slot-Kauf ist genau einmal erreichbar, nämlich im Workshop.
|
||
- Beim Betreten nach einem Run zeigt der Screen, was dieser Run eingebracht hat: erreichte Welle,
|
||
verdiente Meta-Währung, gedroppte Cards.
|
||
- Steht `version` nach dem Laden auf `0`, erscheint hier die Meldung aus ADR-0003 §8 — und bis zur
|
||
ersten bewussten Aktion des Spielers wird nicht geschrieben.
|
||
|
||
### 14. Save/Load
|
||
|
||
Vollständig nach [ADR-0003](adr/0003-save-format-ort-und-migration.md): `progress.toml` über
|
||
`bevy_settings`, `version` als Sentinel, Feldnamen als Vertrag, namensbasierte Ablage. Persistiert
|
||
werden nur Quellen, nie berechnete Werte (ADR-0002 §5). Kein laufender Run, kein `Balance`, kein
|
||
Zeitstempel.
|
||
|
||
**Akzeptanz**
|
||
- Nach einem Programmneustart stehen Workshop-Level, Card-Bestand, Loadout, Slot-Zahl,
|
||
Meta-Währungsstand und `highest_wave` unverändert da.
|
||
- Ein von Hand beschädigtes `progress.toml` führt zur Meldung aus ADR-0003 §8 und **nicht** zu einem
|
||
Schreibvorgang, bevor der Spieler etwas tut. Die beschädigte Datei liegt danach unverändert da.
|
||
- Ein `progress.toml` mit `version` größer als `CURRENT` sperrt alle Schreibvorgänge für die Sitzung
|
||
und lässt die Datei unangetastet.
|
||
- Ein Umbenennen eines Feldes lässt den restlichen Spielstand intakt und setzt genau einen Wert auf
|
||
`Default`.
|
||
- Die Datei ist in einem Texteditor lesbar und von Hand editierbar — Schummeln ist ausdrücklich
|
||
akzeptiert, nicht übersehen.
|
||
|
||
### 15. Debug-Panel und Run-Telemetrie
|
||
|
||
**Kein Nice-to-have.** ADR-0001 und #8 benennen dieses Werkzeug als den eigentlichen Hebel, um die
|
||
vier freien Zahlen überhaupt finden zu können: ohne Sprung-zu-Welle kostet jeder Datenpunkt einen
|
||
Run von Welle 1 an, bei ‹35 s› pro Welle also Minuten. Das Panel mutiert `Balance` im laufenden
|
||
Prozess und nutzt dieselbe Naht wie der spätere Asset-Ladepfad (ADR-0001 §3).
|
||
|
||
Umfang: Sprung zu einer beliebigen Welle · Live-Mutation der `Balance`-Werte · Anzeige von
|
||
`EffectiveStats` samt der **ungeclampten** Summen (ADR-0002 §2) · Herkunfts-Aufschlüsselung pro Stat
|
||
(ADR-0002 §6) · Run-Telemetrie: Zeit bis Tod, erreichte Welle, Coins pro Minute, Kills pro Welle,
|
||
DPS-Verlauf, Zeit zwischen zwei bezahlbaren Käufen.
|
||
|
||
**Akzeptanz**
|
||
- Ein Sprung zu Welle N versetzt den laufenden Run in Welle N mit der für N vorgesehenen
|
||
Soll-Population und Gegner-Skalierung, ohne Programmneustart, in unter ‹2 s›.
|
||
- Eine Änderung an einem `Balance`-Wert im Panel wirkt ohne Neustart auf die nächste Neuberechnung
|
||
von `EffectiveStats`.
|
||
- Am Run-Ende liegt ein Telemetrie-Datensatz vor, der alle oben genannten Größen enthält und sich
|
||
maschinenlesbar exportieren lässt.
|
||
- **Ein Run, in dem das Debug-Panel benutzt wurde, schreibt nichts in `progress.toml`.** Weder
|
||
`highest_wave` noch Meta-Währung noch Card-Drops. Ein Sprung zu Welle 500 darf keinen
|
||
Wellen-Rekord und keinen Card-Drop erzeugen.
|
||
- Das Panel ist im Release-Build nicht erreichbar, ohne dass ein Schalter dafür umgelegt wird.
|
||
|
||
---
|
||
|
||
## Was ausdrücklich nicht drin ist
|
||
|
||
| Nicht im Slice | Warum |
|
||
|---|---|
|
||
| Pfadfindung, Turm-Platzierung, Maze-Building | die Arena ist radial und hat genau einen Turm — es gibt nichts zu platzieren und keinen Pfad zu finden. |
|
||
| Tiers | wir haben uns bewusst für **eine** endlose Kurve entschieden; Tier-Resets sind das Langzeit-Muster des Originals, nicht unser Slice-Problem. |
|
||
| Labs, Modules, Perks | dritte, vierte und fünfte Meta-Achse neben Workshop und Cards — der Slice soll zeigen, dass **eine** Progressionsschleife trägt, nicht dass fünf sich addieren. |
|
||
| Offline-Progress | braucht einen Zeitstempel im Save, und ein Feld ohne Leser ist genau der Fehler, den ADR-0002 §5 verbietet. |
|
||
| Elite- und Fleet-Klasse | ihre Mechanik im Original ist eine Anti-Cheese-Antwort auf ein Fähigkeiten-Arsenal, das wir gar nicht haben (#3). |
|
||
| Immunitätsmatrix | dieselbe Begründung — sie schützt vor Fähigkeiten, die der Slice nicht kennt. |
|
||
| Wall als zweiter HP-Pool | ein zweiter HP-Pool verdoppelt die Schadenslogik, ohne eine neue Frage zu beantworten (#3). |
|
||
| Coin- und Card-Pickups als Entities | bei tausenden Kills pro Welle ist ein einsammelbares Objekt pro Drop ein Entity-Sturm (Cap-Analyse #1). |
|
||
| Coin Decay, Heat-up, Enemy-Mass-Wachstum | Coin Decay und Mass sind dichte-neutral, aber ohne Nutzen im Slice; Heat-up ist bei 13-facher Angreiferzahl schlicht nicht übertragbar und müsste neu erfunden statt übernommen werden (Cap-Analyse #1). |
|
||
| Gacha-Kopien, Card-Mastery, Pity-System | das Original kauft Cards mit einer Währung, die es bei uns nicht gibt (Ads, IAP) — Drop-Verteilung ersetzt beides ersatzlos (#11). |
|
||
| Manuelle Ultimate-Auslösung | ein Knopf wäre der einzige Moment aktiver Kontrolle im ganzen Spiel und würde das Kern-Gefühl „Idle" brechen statt bestätigen (#9). |
|
||
| Einstellbare Auslöse-Bedingungen | die Bedingungen liegen bereits als Daten in `Balance`; sie einstellbar zu machen ist reine UI-Arbeit und kauft im Slice nichts (#12). |
|
||
| Buy x10 / Buy Max | das HUD-Schema lässt Platz dafür, ohne das Layout zu ändern — der Slice braucht es nicht (#10). |
|
||
| Spielgeschwindigkeits-Multiplikator | nicht entschieden und nicht im HUD; das Debug-Panel deckt den Zeitraffer-Bedarf beim Tunen ab (siehe „Lücken"). |
|
||
| Bots, Wave Skip, Recovery Packages | Systeme, die eine bereits laufende Schleife garnieren — der Slice muss erst zeigen, dass die Schleife läuft. |
|
||
| Guilds, Tournaments, Cloud-Saves, Accounts, IAP, Leaderboards | alles Server-seitige ist out of scope; ein Vergleichswert, der das Gerät verlässt, würde außerdem ADR-0003 §1 (Klartext-Save) neu aufmachen. |
|
||
| Mobile- und Web-Port | `bevy_settings` persistiert auf Android und iOS **nichts** und meldet es nur als `warn!` — der Port braucht eigene Arbeit (ADR-0003, Revisions-Auslöser 2). |
|
||
| Art-Direction, Audio | Programmer-Art ist eine Entscheidung, kein Zwischenstand; ein Art-Strang beantwortet keine der offenen Fragen. |
|
||
| Hot-Reload für Balancing-Daten | der Edit-to-see-Loop kostet gemessene ~11 s, und der teure Teil eines Tuning-Zyklus ist das Spielen (ADR-0001). |
|
||
| „Gespeichert"-Bestätigung | das Programm kann sie nicht belegen — es gibt in Bevy 0.19 keinen Rückkanal (ADR-0003 §8). |
|
||
|
||
---
|
||
|
||
## Abnahmekriterium für den Slice
|
||
|
||
Genau eines, überprüfbar, ohne Debug-Panel:
|
||
|
||
> **In einer ununterbrochenen Sitzung von höchstens 60 Minuten auf einem frisch angelegten
|
||
> Spielstand spielt ein Spieler Runs, bis er einen zweiten Card-Slot besitzt und zwischen
|
||
> mindestens zwei Cards wählen kann, startet danach einen weiteren Run und erreicht darin eine
|
||
> höhere Welle als in Run 1; nach einem Neustart des Programms steht derselbe Meta-Stand wieder da.**
|
||
|
||
Warum genau dieses: Es prüft alle vier Takte des Loops in einer Beobachtung — Run starten, sterben,
|
||
aufwerten, stärker starten — und bindet zusätzlich die Randbedingung aus #11 ein, dass der Slice das
|
||
Card-System nicht nur enthalten, sondern **zeigen** muss. Ein Slice, in dem Slot 2 ein Fernziel
|
||
bleibt, hat Cards gebaut und nie vorgeführt. Der Neustart am Ende prüft Feature 14 dort, wo es zählt.
|
||
|
||
---
|
||
|
||
## Offen — bewusst nicht entschieden
|
||
|
||
Für keinen dieser Punkte wird hier eine Antwort erfunden.
|
||
|
||
| Punkt | Wer entscheidet | Wann |
|
||
|---|---|---|
|
||
| **Die vier freien Zahlen**: Wachstumsfaktor pro Welle, Boss-Multiplikator, Drop-Chance mal Wellenwert, Kosten-Exponent des Workshops | Der Entwickler beim Tunen. Form und Prüfkriterien stehen in #8, die Werte findet nur Spielen | Nach Feature 15 (Debug-Panel), vor dem Abnahmekriterium |
|
||
| **Name der Meta-Währung** | `CONTEXT.md` — der Glossar-Eintrag ist der Vertrag, der Save-Feldname folgt ihm | Bevor der erste Workshop-Preis geschrieben wird. `Coins` ist an die In-Run-Währung vergeben; ADR-0003 lässt das Feld unbenannt, weil ein vorläufiger Name nach der Migrationsregel (§4) teuer zu korrigieren wäre |
|
||
| **Aufbau des Meta-Screens** (Workshop, Cards, Slot-Fortschritt) | Ein eigenes Design-Ticket, analog zum HUD-Mockup | Vor Feature 13. Nur das In-Run-HUD ist entworfen |
|
||
| **`MaxHealth` mitten im Run erhöht** — wächst die aktuelle HP mit, behält sie ihren Anteil, oder bleibt sie unverändert? | Das Health-System, nicht die Pipeline (ADR-0002, „Offene Punkte") | **Vor dem ersten Defence-Shop-Upgrade.** Die ADR-0002-Stat-Tabelle weist `MaxHealth` ausdrücklich `flat` aus In-Run-Upgrades zu — der Fall tritt bei jedem Health-Kauf ein, nicht theoretisch |
|
||
| **`SlowAura`** — modifiziert Gegner statt den Turm und ist deshalb kein Stat | Fällt mit der Card-Liste; ADR-0002 §9 legt die Form fest (Component am Turm, das das Gegner-Bewegungssystem liest), nicht den Inhalt | Mit der Card-Liste |
|
||
| **Zusammensetzung der Card-Liste**: Anzahl, Wirkungen, Verteilung auf die drei Seltenheiten, Drop-Wahrscheinlichkeiten je Stufe, Stufenzahl je Card | Ein Card-Inhalts-Ticket, gebunden an #8 (Zahlen) und #11 (Randbedingungen) | Vor Feature 9. **Zwei Randbedingungen sind bereits verbindlich**: Slot 2 muss früh fallen, und es braucht mindestens zwei Epics — sonst ist die Epic-Stufe eine Behauptung statt einer Mechanik |
|
||
| **Slot-Preise**: was ein Card-Slot kostet | Der Entwickler beim Tunen. Die **Mechanik** ist entschieden — Kauf aus der Meta-Währung im Workshop, siehe Feature 10 und „Widersprüche", Punkt 2. Offen ist nur noch der Preis, und der ist für Slot 2 **nicht frei**: er ist an den Meta-Ertrag der ersten ‹drei› Runs gebunden | Zusammen mit den vier freien Zahlen, nach Feature 15 |
|
||
| **Zellgröße, Separationsradius, Nachbar-Budget ‹k› und Klemmfaktor ‹f›** | Der Entwickler beim Tunen; Form und Revisions-Auslöser stehen in ADR-0004 | Mit Feature 3. Der Beleg ist ein Blick auf den Turmring, keine Zahl |
|
||
|
||
---
|
||
|
||
## Widersprüche und Lücken
|
||
|
||
Beim Zusammentragen aller Entscheidungen sind Stellen aufgefallen, an denen zwei Quellen sich
|
||
widersprechen oder an denen eine Frage niemandem gehört. Sie stehen hier, statt geglättet zu werden.
|
||
|
||
Aufgelöste Punkte werden **nicht gelöscht**, sondern als *entschieden, siehe X* markiert und
|
||
behalten ihren Befund. Der Weg zu einer Entscheidung ist so lange wertvoll, wie jemand sie in Frage
|
||
stellen könnte. Aufgelöst sind bisher die Punkte **2**, **5**, **9** und **14**.
|
||
|
||
### Widersprüche
|
||
|
||
1. **Card-Drops überqueren die Run-Grenze, aber ADR-0003 kennt sie nicht.** ADR-0003 §6 nennt
|
||
ausdrücklich **genau eine** Ausnahme von der Regel „während eines Runs ändert sich keine
|
||
persistierte Quelle": `highest_wave` und die verdiente Meta-Währung, überführt genau einmal am
|
||
Run-Ende. `card_levels` steht nicht in dieser Ausnahme — obwohl #11 (entschieden 12:48, also vor
|
||
ADR-0003 um 13:46) Cards aus **Boss-Kills und neuen Wellen-Rekorden** droppen lässt, beides
|
||
Ereignisse *im* Run. Die Spec löst das oben nach ADR-0003 auf: Card-Drops werden im Run gepuffert
|
||
und zusammen mit den übrigen Meta-Werten am Run-Ende überführt. Wer das anders bauen will, muss
|
||
zuerst die Ausnahmeliste in ADR-0003 §6 erweitern — nicht stillschweigend ein zweites System
|
||
schreiben lassen.
|
||
|
||
2. **Die Slot-Kurve gehört niemandem, ist aber Abnahmebedingung.** — **Entschieden, siehe Feature 10
|
||
und Feature 9.** *Der Befund bleibt stehen, weil er zeigt, wo die Lücke herkam.* #11 entscheidet
|
||
„Start bei 1, wächst auf 5, die Slot-Zahl ist selbst Progression" und bindet Slot 2 ausdrücklich
|
||
ans Abnahmekriterium des Slice. **Wodurch** ein Slot freigeschaltet wird, sagte niemand: nicht
|
||
#11 (schiebt es an #8/#10), nicht #8 (die vier freien Zahlen enthalten keine Slot-Kurve), nicht
|
||
die Karte (der Fog nennt nur den Meta-Screen-Aufbau). `card_slots: u8` ist in ADR-0003
|
||
persistiert, aber es gab keinen Mechanismus, der ihn erhöht.
|
||
**Auflösung:** Slots werden **aus der Meta-Währung im Workshop gekauft**, neben den
|
||
Stat-Upgrades, mit derselben exponentiellen Kostenform. Kein zweiter Freischalt-Mechanismus,
|
||
keine Meilenstein-Schwelle. Der Grund ist nicht Sparsamkeit, sondern dass daraus die
|
||
Meta-Entscheidung des Slice wird: Breite gegen Stärke aus einem Topf. Die Randbedingung aus #11
|
||
gilt unverändert weiter und macht den ersten Slot-Preis zu einer gebundenen, nicht zu einer
|
||
freien Zahl — siehe „Offen". Was diese Auflösung neu aufwirft, steht unten als Punkt 16.
|
||
|
||
3. **Die Ticket-Überschrift von #8 verspricht 10–20 Minuten, die Entscheidung liefert 6–9.** #8
|
||
heißt „Wellen- und Economy-Kurve für einen **10–20-Minuten-Run**", sein erster Kommentar
|
||
verlangt Tod „um Welle 20–40", und die Entscheidung im zweiten Kommentar setzt Run 1 auf **Welle
|
||
10–15 = 6–9 Minuten**. Die Karte hat die letzte Fassung übernommen, die frühere steht unwidersprochen
|
||
daneben. Wahrscheinlich gemeint: 10–20 Minuten gilt für den **eingespielten** Run nach einigen
|
||
Workshop-Leveln, 6–9 Minuten für Run 1. Das steht so aber nirgends, und die Kurve wird gegen die
|
||
Zahl getunt, die man liest. Diese Spec folgt der Entscheidung (Welle 10–15) und hält fest, dass
|
||
für Run *n* keine Ziel-Runlänge existiert.
|
||
|
||
4. **Die Cap-Analyse balanciert Sub-Caps für Gegnerklassen, die #3 abgeschafft hat.** Der
|
||
#1-Kommentar teilt die Sub-Caps sorgfältig in Modifikator-Caps (Protector 8, **Elite pro Typ 8**)
|
||
und Event-Caps (Boss 10, **Elite gesamt 20**). #3 wirft Elite und Fleet aber komplett aus dem
|
||
Slice. Damit ist die Hälfte der Analyse gegenstandslos: übrig bleiben genau **ein** mitskalierendes
|
||
Modifikator-Cap (die eine Sondermechanik-Einheit, Protector-artig) und **ein** absolutes
|
||
Event-Cap (Boss). Die Regel bleibt richtig, ihr Anwendungsbereich ist ein Viertel so groß wie
|
||
beschrieben — wer sie liest, baut sonst eine Klassifizierung für Klassen, die es nicht gibt.
|
||
|
||
5. **~24 Shop-Zeilen stehen gegen 12 Stats.** — **Entschieden, siehe Feature 6 und den Nachtrag zu
|
||
ADR-0002 §3.** *Der Befund bleibt stehen.* Der HUD-Kommentar in #10 leitet aus 8 Zeilen × 3 Tabs
|
||
„etwa 24 In-Run-Upgrades" ab und macht daraus eine Design-Aussage („der Spieler kann in einem Run
|
||
nicht alles kaufen"). ADR-0002 §3 kannte damals aber insgesamt **12** Stats, und davon gehören
|
||
`UltimateCooldown` und `WaveCooldown` nicht in die drei Shop-Tabs. Die Aufteilung ergibt grob
|
||
Attack 5 (`Damage`, `AttackInterval`, `Range`, `CritChance`, `CritFactor`), Defence 4
|
||
(`MaxHealth`, `DefensePercent`, `DefenseFlat`, `HealthRegen`) und **Utility 1** (`CoinGain`). Ein
|
||
Utility-Tab mit einer Zeile ist kein Tab. Entweder das Panel füllt nur die Hälfte der Zeilen —
|
||
dann trägt die Folgerung „kann nicht alles kaufen" nicht mehr —, oder es braucht In-Run-Upgrades,
|
||
die keine Stats aus ADR-0002 sind, und dann ist ADR-0002 §3 unvollständig. Beides ist eine
|
||
Entscheidung, keine Detailfrage.
|
||
Verschärfend: #3 empfiehlt für Utility ausdrücklich „stark vereinfachen", weil das Utility-System
|
||
des Originals an Labs und Perks hängt — beides im Nebel. Der Utility-Tab war damit der schwächste
|
||
Teil der HUD-Struktur.
|
||
**Auflösung:** Der zweite Ast wird gewählt — ADR-0002 §3 **war** unvollständig und ist um vier
|
||
Utility-Stats gewachsen (`CoinsPerWave`, `CritCoinGain`, `FreeUpgradeChance`, `MetaGain`; der
|
||
Nachtrag ist in der ADR als solcher markiert). Utility ist damit das, was #3 im Referenzspiel
|
||
gefunden hat: ein Ökonomie-Multiplikator, kein Kampf-Stat. Der erste Ast trägt trotzdem mit:
|
||
Bei 5 + 4 + 4 = 13 belegten von ~24 möglichen Zeilen füllt das Panel seine Zeilen nicht aus, und
|
||
**die Folgerung „der Spieler kann in einem Run nicht alles kaufen" folgt nicht mehr aus dem
|
||
Layout.** Sie muss aus der Kostenkurve kommen (#8, Tuning-Zielgröße „Zeit zwischen zwei
|
||
bezahlbaren Käufen") oder fallen. Punkt 6 unten hatte für dieselbe Kette schon festgehalten, dass
|
||
von der HUD-Rechnung nur noch die Zahl 24 übrig ist — jetzt ist auch die keine Design-Aussage
|
||
mehr, sondern eine Layout-Obergrenze.
|
||
|
||
6. **Die „12–17 Kaufmomente" sind zweimal überholt, stehen aber noch in der Kette.** Der
|
||
HUD-Kommentar (13:38) rechnet mit 12–17 Kaufmomenten aus einer 20-Minuten-Session, #8 (13:51)
|
||
verwirft die Rechnung ausdrücklich („die trägt nicht mehr", weil jederzeit gekauft wird) **und**
|
||
verkürzt Run 1 auf 10–15 Wellen. Was aus dem HUD-Kommentar überlebt, ist allein die Zahl 24 —
|
||
sie kommt aus dem Layout, nicht aus der Session-Rechnung. Die Tuning-Zielgröße ist die **Zeit
|
||
zwischen zwei bezahlbaren Käufen** (‹10–20 s› früh), nicht eine Kaufzahl pro Run.
|
||
|
||
### Lücken, die niemandem gehören
|
||
|
||
7. **Das Debug-Panel kann den echten Spielstand zerstören, und niemand hat es benannt.** Feature 15
|
||
springt zu Welle N. Feature 11 schreibt am Run-Ende `highest_wave` und die Meta-Währung in die
|
||
persistierte Resource, und #11 macht „neuer Wellen-Rekord" zu einer **Card-Drop-Quelle**. Ein
|
||
Sprung zu Welle 500 beendet einen Run also mit `highest_wave = 500`, einem gewaltigen Card-Drop
|
||
und einem Meta-Währungsstand, der den ganzen Slice trivialisiert. Weder ADR-0001 noch #8 noch
|
||
ADR-0003 erwähnen das. Diese Spec zieht die Konsequenz oben als Akzeptanzkriterium ein: ein Run
|
||
mit benutztem Debug-Panel schreibt **nichts**. Wer das Panel baut, muss die Schreibsperre
|
||
zuerst bauen.
|
||
|
||
8. **Die Card-Nachschubquelle „neuer Wellen-Rekord" versiegt eingebaut.** Beide Card-Quellen hängen
|
||
an Fortschritt: Boss-Kill und neuer Wellen-Rekord. Solange die Run-Tiefe monoton wächst (#8,
|
||
Prüfkriterium 3, gilt für die **ersten zehn** Runs), fließt Nachschub. Danach ist unbestimmt, was
|
||
passiert — ein Spieler, der zehn Runs lang auf derselben Welle hängen bleibt, bekommt keine neue
|
||
Card mehr und hat gleichzeitig keinen anderen Hebel, tiefer zu kommen als den Workshop. Ob das
|
||
ein Feature ist (Cards folgen dem Fortschritt) oder eine Sackgasse (Fortschritt braucht Cards),
|
||
hat niemand entschieden. Bei einem Run 1, der auf Welle 10–15 endet, ist außerdem der
|
||
**eine** Boss-Kill pro Run in der Frühphase der einzige garantierte Drop überhaupt — er trägt
|
||
Meta-Währung und Card-Chance gleichzeitig.
|
||
|
||
9. **Gegner-gegen-Gegner-Kollision ist seit #4 offen und steht auf keiner Liste.** —
|
||
**Entschieden, siehe [ADR-0004](adr/0004-spatial-grid-fuer-gegner-nachbarschaft.md).** *Der
|
||
Befund bleibt stehen.* #4 und die Cap-Analyse nannten sie als „offene Architekturfrage": Stapeln
|
||
zulassen oder Spatial Grid mit weicher Separation. Bei tausenden radial konvergierenden Körpern
|
||
kommen O(n²) und ein Stau am Turmring zusammen, den keine DPS-Menge auflöst. Sie stand **nicht**
|
||
im Fog der Karte, in keiner ADR und in keinem der offenen Punkte, die diese Spec zugewiesen
|
||
bekommen hat.
|
||
*Der Vorschlag dieser Spec war „Stapeln zulassen" — er ist verworfen worden.* Nicht wegen der
|
||
Separation, sondern weil das Grid **drei weitere Konsumenten** hat, die bisher jeder für sich
|
||
über die Gegnermenge laufen wollten: die Zielauswahl in `tower_fire` (laut #4 der nächste Hotspot
|
||
nach dem Rendering), der geteilte Reichweiten-Scan aus #12 und die Projektil-Kollision. Genau der
|
||
Scan, dessen Siebenfach-Ausführung #12 als Bruchstelle benannt hat, wird damit zu einer
|
||
Grid-Abfrage. Der Rebuild ist gegen die Messungen aus #4 gerechnet, nicht geschätzt: ≤0,10 ms bei
|
||
`HARD_ENEMY_CAP`, also ≤0,6 % des Frame-Budgets.
|
||
|
||
10. **Ziel-Limit für Flächeneffekte und gebatchter Massentod haben keine ADR.** Die Cap-Analyse
|
||
ordnet beides ausdrücklich „der Architektur" zu, aber ADR-0002 regelt Stats und
|
||
Fähigkeits-Components, nicht Massenwirkung. Death Wave (#9) trifft beide Punkte sofort. Diese
|
||
Spec hat sie deshalb als Akzeptanzkriterien in Feature 8 aufgenommen — das ersetzt keine ADR,
|
||
macht sie aber wenigstens prüfbar.
|
||
|
||
11. **Ein Spielgeschwindigkeits-Multiplikator ist weder entschieden noch verworfen.** Die
|
||
Cap-Analyse wirft ihn auf („ein Idle-Spiel mit 35-Sekunden-Wellen will fast sicher einen") und
|
||
entschärft ihn nur perf-seitig; das Referenzspiel hat ihn; das HUD-Mockup zeigt ihn nicht.
|
||
Diese Spec listet ihn oben unter „nicht drin", weil das Debug-Panel den Zeitraffer-Bedarf beim
|
||
Tunen abdeckt — aber das ist eine Entscheidung, die hier zum ersten Mal getroffen wird und
|
||
jederzeit umkehrbar ist.
|
||
|
||
12. **`WaveCooldown` hat einen Cap, aber im Slice keine Quelle.** ADR-0002 §3 führt den Stat samt
|
||
`caps.wave_cooldown_reduction`; im Original liefert ihn die Epic-Card „Wave Accelerator". Enthält
|
||
die noch offene Card-Liste keine entsprechende Card, sind Stat und Cap toter Code. Kein Fehler,
|
||
aber ein Punkt, der bei der Card-Liste mitentschieden werden sollte.
|
||
|
||
13. **Die Fehlermeldung aus ADR-0003 hat keinen Ort.** ADR-0003 §3 und §8 verlangen bei `version == 0`
|
||
und bei `version > CURRENT` eine Meldung an den Spieler, bevor irgendetwas geschrieben wird. Der
|
||
Slice hat nur das In-Run-HUD (entworfen) und den Meta-Screen (offen). Diese Spec hängt die
|
||
Meldung an den Meta-Screen — womit der offene Meta-Screen-Entwurf eine Anforderung erbt, die
|
||
aus einer ganz anderen Ecke kommt.
|
||
|
||
### Neu aufgefallen beim Einarbeiten der drei Entscheidungen (2026-08-10)
|
||
|
||
14. **Die `points`-Stats mit Basis 0 rechnen sich in ADR-0002 auf null.** — **Behoben, siehe
|
||
ADR-0002 §1.** *Der Befund bleibt stehen, weil er ein ausgelieferter Defekt gewesen wäre, kein
|
||
Beobachtungsposten.* Die `Direct`-Formel aus ADR-0002 §1 lautete
|
||
`(base + flat) × (1 + clamp(points)) × mult`. Die Stat-Tabelle in §3 wies `CritChance` und
|
||
`DefensePercent` aber `base = 0` **und** `points` als einzigen erwarteten Akkumulator zu.
|
||
Eingesetzt: `(0 + 0) × (1 + 0,30) × 1 = 0`. Crit Chance hätte im Spiel **nie über 0 % steigen
|
||
können**, egal wie viele Workshop-Level, Cards und In-Run-Upgrades zusammenkommen — und weder
|
||
Compiler noch ein Formeltest hätten es angezeigt. Es ist exakt dieselbe Falle, die die ADR zwei
|
||
Absätze weiter unten für `HealthRegen` und `DefenseFlat` benennt und begründet, nur dass sie dort
|
||
für `mult` gezogen wurde und für `points` nicht. Aufgefallen ist sie beim Einordnen des neuen
|
||
`FreeUpgradeChance`, der sich korrekt an die Form von `CritChance` gehalten und den Fehler
|
||
deshalb geerbt hat.
|
||
**Behebung:** ADR-0002 §1 hat einen dritten Anwendungsmodus **`Fraction`** bekommen —
|
||
`clamp((base + flat + points) × mult)` — für Stats, die selbst ein Anteil sind. `CritChance`,
|
||
`DefensePercent` und `FreeUpgradeChance` stehen jetzt dort; an ihren `base`-, „Erwartet"- und
|
||
`Clamp`-Spalten hat sich nichts geändert. Es ist ein dritter Wert desselben Feldes, kein zweites
|
||
Bit; drei billigere Wege sind in der ADR geprüft und mit Begründung verworfen. Eine Prüfregel und
|
||
ein fünfter Revisions-Auslöser verhindern die Wiederkehr, das durchgerechnete Beispiel der ADR
|
||
trägt den Fall jetzt als Rechnung, und `Interval` ist nicht betroffen.
|
||
|
||
15. **Nicht-Konvertierbarkeit ist bisher nur in eine Richtung ausgesprochen.** Issue #1 und #11
|
||
begründen die zwei Währungen damit, „dass eine In-Run-Ausgabe keinen Meta-Fortschritt kostet" —
|
||
also `Meta → Coins`. Die Gegenrichtung hat nie jemand benannt: Ein aus `Coins` gekauftes
|
||
In-Run-Upgrade, das die Meta-Drop-Chance oder die Meta-Menge erhöht, wandelt `Coins` in
|
||
Meta-Fortschritt und hebelt die Trennung genauso aus, nur unauffälliger. Der Fall wird real,
|
||
sobald der Utility-Tab Ökonomie-Upgrades trägt (Feature 6) — er ist genau der Tab, in den so ein
|
||
Upgrade thematisch gehören *würde*. Diese Spec zieht die Konsequenz oben ein (`MetaGain` ist
|
||
Workshop-only, mit einer Akzeptanzzeile in Feature 6 und einer Zeilen-Anmerkung in ADR-0002 §3),
|
||
aber die **Regel selbst** steht damit an einer Stat-Tabelle statt in der Entscheidung, aus der
|
||
sie folgt. Wer die Zwei-Währungs-Entscheidung liest, findet die zweite Richtung dort nicht.
|
||
|
||
16. **Slots aus der Meta-Währung erzeugen genau die Konkurrenz, die #11 für Cards ausgeschlossen
|
||
hat.** #11 verwirft eine gemeinsame Währung für Cards mit dem Argument: „beide Achsen
|
||
konkurrieren dann linear um denselben Topf, und niemand kauft Cards, bis der Workshop ausgereizt
|
||
ist — das Original verhindert das ausschließlich über Nicht-Fungibilität, nicht über Preise."
|
||
Die Slot-Entscheidung (Punkt 2) tut fast das: Sie stellt die Card-Achse — nicht die Karten,
|
||
aber ihre Breite — in dieselbe Kaufwarteschlange wie die Stat-Upgrades. Zwei Unterschiede halten
|
||
sie tragfähig: Cards selbst fallen weiterhin gratis, die Achse ist also nie hinter dem Shop
|
||
verschlossen; und Slot 2 muss laut #11 früh bezahlbar sein, was das Versagensmuster „niemand
|
||
kauft, bis der Workshop ausgereizt ist" für den einen Kauf ausschließt, der im Abnahmekriterium
|
||
steht. Für Slot 3 bis 5 gilt dieser Schutz **nicht** — dort ist das Argument aus #11 unverändert
|
||
gültig, und ob die Slots 3–5 im Slice je gekauft werden, ist eine offene Frage an die
|
||
Kostenkurve, keine an die Mechanik.
|