S1: Projekt-Gerüst, Arena, Kamera, Turm #14

Closed
opened 2026-08-10 13:14:32 +00:00 by RDietermann · 1 comment
Owner

Part of #13

Das Gerüst, in dem alles andere hängt. src/main.rs ist noch Hello, world!.

Inhalt

  • Bevy-App mit DefaultPlugins, Fenster, orthographische Kamera.
  • Ein AppState-Enum mit mindestens Run und Meta. Der Zustandswechsel ist der Ort, an dem später Run-Start und Run-Ende hängen — er muss von Anfang an da sein, sonst wird er nachträglich überall eingefädelt.
  • Arena und Tower in der Mitte, Programmer-Art (farbige Sprites, keine Assets).
  • Die Turm-Entity wird beim Betreten von Run gespawnt und beim Verlassen despawnt (ADR-0002 §6). Das ist keine Optimierung, sondern die Grundlage dafür, dass am Run-Ende nichts aufgeräumt werden muss.
  • Modulstruktur anlegen, die den ADRs folgt: balance bleibt frei von Gameplay-Logik, Gameplay liest später nur Res<Balance>.

Akzeptanz (aus Spec Feature 1)

  • Nach Programmstart und Run-Start ist der Turm sichtbar in der Bildmitte, und die gesamte Spawn-Peripherie liegt im sichtbaren Bereich.
  • Zwischen zwei Runs existiert keine Entity mit EffectiveStats — im Skelett genügt: keine Tower-Entity.
  • Ein Wechsel Meta → Run → Meta lässt keine Entity zurück, nachweisbar über einen Zähler oder ein debug_assert.

Nicht in diesem Ticket: Gegner, Schießen, HUD, Wellen. Nur das Gerüst.

Part of #13 Das Gerüst, in dem alles andere hängt. `src/main.rs` ist noch `Hello, world!`. **Inhalt** - Bevy-App mit `DefaultPlugins`, Fenster, orthographische Kamera. - Ein `AppState`-Enum mit mindestens `Run` und `Meta`. Der Zustandswechsel ist der Ort, an dem später Run-Start und Run-Ende hängen — er muss von Anfang an da sein, sonst wird er nachträglich überall eingefädelt. - Arena und `Tower` in der Mitte, Programmer-Art (farbige Sprites, keine Assets). - Die Turm-Entity wird **beim Betreten von `Run` gespawnt und beim Verlassen despawnt** (ADR-0002 §6). Das ist keine Optimierung, sondern die Grundlage dafür, dass am Run-Ende nichts aufgeräumt werden muss. - Modulstruktur anlegen, die den ADRs folgt: `balance` bleibt frei von Gameplay-Logik, Gameplay liest später nur `Res<Balance>`. **Akzeptanz** (aus Spec Feature 1) - Nach Programmstart und Run-Start ist der Turm sichtbar in der Bildmitte, und die gesamte Spawn-Peripherie liegt im sichtbaren Bereich. - Zwischen zwei Runs existiert keine Entity mit `EffectiveStats` — im Skelett genügt: keine `Tower`-Entity. - Ein Wechsel `Meta → Run → Meta` lässt keine Entity zurück, nachweisbar über einen Zähler oder ein `debug_assert`. **Nicht in diesem Ticket**: Gegner, Schießen, HUD, Wellen. Nur das Gerüst.
RDietermann added the wayfinder:task label 2026-08-10 13:14:32 +00:00
RDietermann self-assigned this 2026-08-10 13:22:53 +00:00
Author
Owner

Gebaut. src/main.rs, src/balance/{mod,values}.rs, src/arena.rs, src/tower.rs — 392 Zeilen, keine neuen Dependencies.

Nachgeprüft, nicht geglaubt

  • cargo clean -p infinitower plus vollständiger Rebuild: null Warnungen.
  • cargo test: tower::tests::meta_run_meta_leaves_nothing_behind grün.
  • Der Agent hat den Test gegengeprobt — mit entferntem DespawnOnExit schlägt er fehl (left: 1, right: 0), danach wiederhergestellt und wieder grün. Er ist nicht vakuant.
  • Das Fenster selbst hat der Agent per Screenshot verifiziert, ich nicht: dunkle Arena-Scheibe, gelbes Turm-Quadrat in der Bildmitte, Spawn-Peripherie mit ~8 % Rand sichtbar.

Zwei Dinge, die besser sind als beauftragt

ADR-0001 ist compilerdurchgesetzt, nicht nur per Konvention. Die Konstanten in values.rs sind pub(super); Balance::from_constants() ist der einzige Weg heraus. Die Regel „kein Gameplay liest eine Konstante" lässt sich damit nicht versehentlich brechen, sondern nur absichtlich.

ADR-0002 §6 wird von einem Bevy-Built-in erfüllt. Statt eines Aufräum-Systems trägt die Turm-Entity DespawnOnExit(AppState::Run). Die ADR forderte „kein Aufräum-Code, den man beim nächsten Feature erweitern müsste" — hier gibt es kein System, das man falsch einordnen oder vergessen könnte. Der Test zählt außerdem alle Entities, nicht nur Türme, und fängt damit auch künftige run-gebundene Entities, die jemand ohne Run-Bindung spawnt.

Statt #[allow(dead_code)] steht #[expect(dead_code, reason = "read by the gameplay tickets #16-#18")] am balance-Modul — der Compiler fordert die Zeile zurück, sobald das letzte Feld einen Leser hat.

Platzhalterwerte

Feld Wert Herkunft
arena.radius 500,0 aus perf_spike.rs
enemy.speed 50,0 aus perf_spike.rs
enemy.health 10,0 frei geraten
tower.damage 5,0 frei geraten
tower.attack_interval 0,5 s frei geraten
tower.health 100,0 frei geraten
combat.hit_radius 10,0 aus perf_spike.rs
meta.gain_per_kill 1,0 frei geraten — siehe unten

Alle acht sind im Code als PLACEHOLDER ausgezeichnet.

Vier Befunde des Agenten, zur Kenntnis

  1. AppState::Run ist Default, damit cargo run ohne Tastendruck einen sichtbaren Turm zeigt. Die Alternative — im Startup heimlich NextState setzen — verschluckt das OnEnter(Meta) des ersten Frames und fiele bei #18 still auf die Füße. Mit // ponytail: markiert, Umdrehen ist eine Zeile und gehört zu #18.
  2. Balance.caps ist ein leeres Struct. ADR-0002 §2 verlangt einen Cap je geclamptem Stat — die fünf betroffenen Stats existieren im Skelett noch nicht. Der Bereich ist benannt und erreichbar, nur ohne Felder. Vertagung, kein Widerspruch.
  3. Ticket #15 sagt „Feuerrate", ADR-0002 §3 verbietet genau das. Der Agent ist der ADR gefolgt: tower.attack_interval in Sekunden, weil mult sonst kein Teiler auf eine Dauer sein kann. Das Ticket-Wording ist überholt, nicht der Code — genau die Reihenfolge, die die Karte vorgibt.
  4. type Stat = f32 liegt vorerst in balance. ADR-0002 §4 definiert den Alias, ordnet ihn keinem Modul zu. Wenn die Stat-Pipeline kommt, gehört er vermutlich in ein eigenes stats-Modul.

Der eine Punkt, der weiterwandert

meta.gain_per_kill ist ein flacher Betrag pro Kill — die Spec sagt aber, der Meta-Ertrag ist drop-basiert: Chance je Normalgegner, garantiert je Boss, Menge wellenabhängig. Das ist eine andere Mechanik, nicht bloß eine andere Zahl.

Für ein Skelett ohne Wellen und ohne Bosse ist der flache Wert das einzig Baubare, und der Agent hat die Abweichung am Konstanten-Doc vermerkt statt sie zu verschlucken. Damit sie nicht zur stillschweigenden Mechanikentscheidung gegen die Spec wird, ist sie als ausdrückliche Auflage an #18 gehängt.

**Gebaut.** `src/main.rs`, `src/balance/{mod,values}.rs`, `src/arena.rs`, `src/tower.rs` — 392 Zeilen, keine neuen Dependencies. ## Nachgeprüft, nicht geglaubt - `cargo clean -p infinitower` plus vollständiger Rebuild: **null Warnungen**. - `cargo test`: `tower::tests::meta_run_meta_leaves_nothing_behind` grün. - Der Agent hat den Test gegengeprobt — mit entferntem `DespawnOnExit` schlägt er fehl (`left: 1, right: 0`), danach wiederhergestellt und wieder grün. Er ist nicht vakuant. - Das Fenster selbst hat der Agent per Screenshot verifiziert, ich nicht: dunkle Arena-Scheibe, gelbes Turm-Quadrat in der Bildmitte, Spawn-Peripherie mit ~8 % Rand sichtbar. ## Zwei Dinge, die besser sind als beauftragt **ADR-0001 ist compilerdurchgesetzt, nicht nur per Konvention.** Die Konstanten in `values.rs` sind `pub(super)`; `Balance::from_constants()` ist der einzige Weg heraus. Die Regel „kein Gameplay liest eine Konstante" lässt sich damit nicht versehentlich brechen, sondern nur absichtlich. **ADR-0002 §6 wird von einem Bevy-Built-in erfüllt.** Statt eines Aufräum-Systems trägt die Turm-Entity `DespawnOnExit(AppState::Run)`. Die ADR forderte „kein Aufräum-Code, den man beim nächsten Feature erweitern müsste" — hier gibt es kein System, das man falsch einordnen oder vergessen könnte. Der Test zählt außerdem **alle** Entities, nicht nur Türme, und fängt damit auch künftige run-gebundene Entities, die jemand ohne Run-Bindung spawnt. Statt `#[allow(dead_code)]` steht `#[expect(dead_code, reason = "read by the gameplay tickets #16-#18")]` am `balance`-Modul — der Compiler fordert die Zeile zurück, sobald das letzte Feld einen Leser hat. ## Platzhalterwerte | Feld | Wert | Herkunft | |---|---|---| | `arena.radius` | 500,0 | aus `perf_spike.rs` | | `enemy.speed` | 50,0 | aus `perf_spike.rs` | | `enemy.health` | 10,0 | frei geraten | | `tower.damage` | 5,0 | frei geraten | | `tower.attack_interval` | 0,5 s | frei geraten | | `tower.health` | 100,0 | frei geraten | | `combat.hit_radius` | 10,0 | aus `perf_spike.rs` | | `meta.gain_per_kill` | 1,0 | frei geraten — **siehe unten** | Alle acht sind im Code als `PLACEHOLDER` ausgezeichnet. ## Vier Befunde des Agenten, zur Kenntnis 1. **`AppState::Run` ist Default**, damit `cargo run` ohne Tastendruck einen sichtbaren Turm zeigt. Die Alternative — im `Startup` heimlich `NextState` setzen — verschluckt das `OnEnter(Meta)` des ersten Frames und fiele bei #18 still auf die Füße. Mit `// ponytail:` markiert, Umdrehen ist eine Zeile und gehört zu #18. 2. **`Balance.caps` ist ein leeres Struct.** ADR-0002 §2 verlangt einen Cap je geclamptem Stat — die fünf betroffenen Stats existieren im Skelett noch nicht. Der Bereich ist benannt und erreichbar, nur ohne Felder. Vertagung, kein Widerspruch. 3. **Ticket #15 sagt „Feuerrate", ADR-0002 §3 verbietet genau das.** Der Agent ist der ADR gefolgt: `tower.attack_interval` in Sekunden, weil `mult` sonst kein Teiler auf eine Dauer sein kann. Das Ticket-Wording ist überholt, nicht der Code — genau die Reihenfolge, die die Karte vorgibt. 4. **`type Stat = f32` liegt vorerst in `balance`.** ADR-0002 §4 definiert den Alias, ordnet ihn keinem Modul zu. Wenn die Stat-Pipeline kommt, gehört er vermutlich in ein eigenes `stats`-Modul. ## Der eine Punkt, der weiterwandert `meta.gain_per_kill` ist ein **flacher Betrag pro Kill** — die Spec sagt aber, der Meta-Ertrag ist **drop-basiert**: Chance je Normalgegner, garantiert je Boss, Menge wellenabhängig. Das ist eine andere Mechanik, nicht bloß eine andere Zahl. Für ein Skelett ohne Wellen und ohne Bosse ist der flache Wert das einzig Baubare, und der Agent hat die Abweichung am Konstanten-Doc vermerkt statt sie zu verschlucken. Damit sie nicht zur stillschweigenden Mechanikentscheidung gegen die Spec wird, ist sie als ausdrückliche Auflage an #18 gehängt.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: RDietermann/InfiniTower#14