docs: ticket 12 (durchstich) aufloesen, korrekturen an 11 und 06, zug im glossar geschaerft

This commit is contained in:
2026-08-12 11:27:19 +02:00
parent 2c19cb2aaf
commit 88195d3c9a
5 changed files with 94 additions and 4 deletions

View File

@@ -52,3 +52,21 @@ Vollständiger Bericht: [`research/06-ratatui-architektur.md`](../research/06-ra
Sieben Punkte sind im Bericht ausdrücklich als nicht verifiziert markiert. Sechs Anschlussfragen bleiben
offen und sind zu Ticket 11 und 12 graduiert.
## Nachtrag aus Ticket 12: der Windows-Fallstrick ist gemessen
Der `KeyEventKind::Press`-Filter stand hier als Faktum, aber ungeprüft. Der Durchstich zählt
Tasten-Events ungefiltert **und** gefiltert mit. Unter Windows 11:
```
Tasten 14 → Press 7 → Züge 7
```
Jeder Tastendruck erzeugt zwei `Event::Key` — Press und Release. Ohne den Filter kostete **jeder
Schritt zwei Züge**, und weil ein Zug befristete Modifikatoren tickt, wäre das kein kosmetischer
Fehler, sondern doppelte Spielzeit.
Ebenfalls im Durchstich bestätigt: `ratatui::run`, der Buffer-Zugriff über `buf[(x, y)].set_symbol(…)`
für ein eigenes Grid-Widget, `Clear` auf ein Sub-`Rect` für Popups, und dass `ratatui` die Crate
`crossterm` re-exportiert — ein eigener `Cargo.toml`-Eintrag ist tatsächlich unnötig. Gebaut gegen
ratatui 0.30.2 mit Rust 1.96.

View File

@@ -64,8 +64,22 @@ Weder Angriff noch Flucht sind **eigene Varianten** — beides sind Fähigkeiten
Registry festgelegt, also gehören sie dorthin, und ein Mod kann beide ersetzen: eine Klasse, die
besser flieht, ist damit eine Datenänderung statt eines Engine-Sonderfalls.
Auch Handel kostet einen Zug. Ein `fn costs_turn(&self) -> bool` wäre eine Ausnahmeliste, die mit
jeder neuen Variante gepflegt werden muss und bei der irgendwann jemand falsch rät. Zehn Züge beim
**Korrektur aus dem Durchstich (Ticket 12): „alle Commands" heißt „alle *ausgeführten*".** Gegen eine
Wand zu laufen darf keinen Zug kosten — sonst tickt ein Gift-Debuff, weil jemand danebengetippt hat.
```rust
pub enum Outcome { TurnPassed, Rejected(&'static str) }
fn execute(&mut self, cmd: Command) -> Outcome
```
Der Zug endet nur bei `TurnPassed`, und der Grund reist mit zurück in die Statuszeile — sonst tut das
Spiel schweigend nichts. Das betrifft mehr als Wände: kaufen ohne Gold, eine Fähigkeit ohne Ausdauer,
absteigen ohne Treppe. Overlays schließen sich trotzdem, auch bei `Rejected`: Die Rückfrage *wurde*
beantwortet.
Davon unberührt: Auch Handel kostet einen Zug. Ein `fn costs_turn(&self) -> bool` wäre eine
Ausnahmeliste, die mit jeder neuen Variante gepflegt werden muss und bei der irgendwann jemand falsch
rät. Zehn Züge beim
Einkauf stören nur, wenn im Laden gekämpft wird; dagegen hilft, dass `Buy(item, 10)` bereits *ein*
Command ist.
@@ -95,7 +109,9 @@ ohne Command hat den Spielzustand also nachweislich nicht angefasst.
**Ein `Response::Command` schließt alle Overlay-Screens.** Das Flag existiert bereits fürs Rendern und
beantwortet damit auch „schließt sich nach einer Rückfrage von selbst" — eine Zeile in der Schleife,
keine zusätzliche `Response`-Variante und keine Antwortliste. `Confirm { text, on_yes: Command }` wird
dadurch für jede Rückfrage wiederverwendbar, ohne eigene Variante je Anlass. Dass `Inventory` als
dadurch für jede Rückfrage wiederverwendbar, ohne eigene Variante je Anlass — **`Command` muss dafür
`Clone` sein**, sonst kommt das Kommando nicht aus dem Screen heraus, ohne ihn zu zerstören
(Befund aus Ticket 12). Dass `Inventory` als
Vollbild-Screen nach `UseItem` offen bleibt, fällt aus derselben Regel — man trinkt selten genau einen
Trank.

View File

@@ -1,7 +1,7 @@
# 12 — Prototyp: ratatui-Durchstich
Type: prototype
Status: open
Status: resolved
Blocked by: 11
## Question
@@ -21,3 +21,56 @@ Der Umbau von `print_box` auf ratatui berührt jedes bestehende Modul (`items.rs
Bewusst nicht im Prototyp: Kampf, Inventar, Shop, Speichern, Mods.
Ergebnis: lauffähiger Durchstich plus Urteil, ob das Modell trägt oder Ticket 11 nachgebessert wird.
## Answer
**Urteil: Das Modell trägt.** Ticket 11 wird an genau einer Stelle nachgebessert, und die hatte nichts
mit dem Screen-Stack zu tun.
Der Prototyp liegt als Primärquelle auf dem Branch **`prototyp/12-ratatui-durchstich`**
(`examples/durchstich.rs`, 342 Zeilen, `cargo run --example durchstich`). Er bleibt dort und kommt
nicht nach `main` — Wegwerfcode, der eine Frage beantwortet hat.
### Die fünf Zusagen
| Zusage | Befund |
|---|---|
| `mod game` bleibt ratatui-frei | **hält.** Null Treffer, und zwar ohne dass irgendwer aufpassen musste — die Trennung ergibt sich, wenn `Command` und Spiellogik zusammenliegen |
| Dungeon-Grid als eigenes `impl Widget` | **trägt.** `buf[(x, y)].set_symbol(…)` ist der ganze Trick; kein fertiges Widget wurde vermisst |
| `Vec<Screen>` für Screens *und* Popups | **trägt.** Overlay-Flag entscheidet Rendern *und* Auto-Schließen, wie in 11 abgeleitet |
| `Response` statt Stack-Mutation im Screen | **trägt.** Kein Borrow-Konflikt, weil der Screen den Stack nie sieht |
| `KeyEventKind::Press`-Filter | **gemessen statt geglaubt** — siehe unten |
Popup über Grid, `x` dann `j`: Der Zug zählt hoch und das Popup schließt sich in derselben Iteration.
Die Regel „ein `Response::Command` schließt alle Overlays" ist damit real und kostet die eine Zeile,
die in 11 dafür veranschlagt war.
### Der Windows-Fallstrick ist jetzt eine Zahl
Der Bericht aus 06 führte ihn als Faktum, aber ungemessen. Der Prototyp zählt Tasten-Events
ungefiltert **und** gefiltert mit. Ergebnis unter Windows 11:
```
Tasten 14 → Press 7 → Züge 7
```
Jeder Tastendruck erzeugt zwei `Event::Key` (Press und Release). **Ohne den Filter würde jeder Schritt
zwei Züge kosten** — und weil ein Zug befristete Modifikatoren tickt, wäre das kein kosmetischer
Fehler, sondern doppelte Spielzeit. Der Filter arbeitet, `Press` und `Züge` laufen synchron.
### Zwei Befunde, beide nach Ticket 11 zurückgetragen
**Gegen eine Wand zu laufen kostete einen Zug.** Aufgefallen beim Spielen, nicht beim Nachdenken: Der
Zugzähler lief hoch, obwohl sich nichts bewegte — ein Gift-Debuff hätte getickt, weil jemand
danebengetippt hat. Die Regel und `Outcome { TurnPassed, Rejected(&str) }` stehen jetzt in
[Ticket 11](11-ui-geruest-screen-modell.md).
**`Command` muss `Clone` sein**, sonst kommt das Kommando nicht aus `Confirm { on_yes }` heraus, ohne
den Screen zu zerstören — im Prototyp brauchte das ein `std::mem::replace`.
### Was der Prototyp nicht beantwortet
Kampf, Inventar, Shop, Speichern, Mods — bewusst ausgelassen. Ebenso ungetestet: die
`Registries`-neben-`Game`-Entscheidung aus 07, weil der Durchstich gar keine Registries lädt. Der
Borrow-Konflikt, den sie vermeiden soll, tritt erst auf, wenn `Game::execute` beides gleichzeitig
braucht — das prüft erst die Implementierung.