docs: ticket 12 (durchstich) aufloesen, korrekturen an 11 und 06, zug im glossar geschaerft
This commit is contained in:
@@ -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
|
Sieben Punkte sind im Bericht ausdrücklich als nicht verifiziert markiert. Sechs Anschlussfragen bleiben
|
||||||
offen und sind zu Ticket 11 und 12 graduiert.
|
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.
|
||||||
|
|||||||
@@ -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
|
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.
|
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
|
**Korrektur aus dem Durchstich (Ticket 12): „alle Commands" heißt „alle *ausgeführten*".** Gegen eine
|
||||||
jeder neuen Variante gepflegt werden muss und bei der irgendwann jemand falsch rät. Zehn Züge beim
|
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*
|
Einkauf stören nur, wenn im Laden gekämpft wird; dagegen hilft, dass `Buy(item, 10)` bereits *ein*
|
||||||
Command ist.
|
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
|
**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,
|
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
|
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
|
Vollbild-Screen nach `UseItem` offen bleibt, fällt aus derselben Regel — man trinkt selten genau einen
|
||||||
Trank.
|
Trank.
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# 12 — Prototyp: ratatui-Durchstich
|
# 12 — Prototyp: ratatui-Durchstich
|
||||||
|
|
||||||
Type: prototype
|
Type: prototype
|
||||||
Status: open
|
Status: resolved
|
||||||
Blocked by: 11
|
Blocked by: 11
|
||||||
|
|
||||||
## Question
|
## 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.
|
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.
|
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.
|
||||||
|
|||||||
@@ -68,6 +68,7 @@ auch über Ticket 13.
|
|||||||
- [07 — Game-State-Baum](issues/07-game-state-baum.md) ([#8](https://gitlab.hive-brain.de/MDietermann/rustkeep/issues/8)) — `Registries` lebt **neben** `Game`, nicht darin: als Feld kollidierte `&mut game.player` mit `&game.registries` bei jedem Methodenaufruf. Verweise per `String`-ID, keine Indizes (die hängen an der Ladereihenfolge). Ein `Creature` für Held und Gegner, aber **zwei Felder** `player` und `enemies` statt einer `Vec` — Solo-Held heißt, „Gegner greift Gegner an" existiert nicht, und zwei Felder sind ein disjunkter Borrow. Ein `Floor`, kein Backtracking; Händler müssen deshalb auf jeder Etage vorkommen können. **Instanzzustand kommt**: `ItemInstance { def, affixes }`, und **Affixe sind die sechste Registry** — dadurch bleibt der Save eine ID-Liste und der Preis `base_price × Π price_factor`. Stapeln bei gleicher `def` und gleicher `affixes`. `enum CreatureRef`, dazu je ein Eingang `Game::apply` und `Game::attribute` — Letzteres auf `Game`, weil Auren beim Träger wohnen und ein `Creature` seine Attribute allein gar nicht ausrechnen kann. Kein Attribut-Cache.
|
- [07 — Game-State-Baum](issues/07-game-state-baum.md) ([#8](https://gitlab.hive-brain.de/MDietermann/rustkeep/issues/8)) — `Registries` lebt **neben** `Game`, nicht darin: als Feld kollidierte `&mut game.player` mit `&game.registries` bei jedem Methodenaufruf. Verweise per `String`-ID, keine Indizes (die hängen an der Ladereihenfolge). Ein `Creature` für Held und Gegner, aber **zwei Felder** `player` und `enemies` statt einer `Vec` — Solo-Held heißt, „Gegner greift Gegner an" existiert nicht, und zwei Felder sind ein disjunkter Borrow. Ein `Floor`, kein Backtracking; Händler müssen deshalb auf jeder Etage vorkommen können. **Instanzzustand kommt**: `ItemInstance { def, affixes }`, und **Affixe sind die sechste Registry** — dadurch bleibt der Save eine ID-Liste und der Preis `base_price × Π price_factor`. Stapeln bei gleicher `def` und gleicher `affixes`. `enum CreatureRef`, dazu je ein Eingang `Game::apply` und `Game::attribute` — Letzteres auf `Game`, weil Auren beim Träger wohnen und ein `Creature` seine Attribute allein gar nicht ausrechnen kann. Kein Attribut-Cache.
|
||||||
- [08 — Event- und Trigger-System](issues/08-event-trigger-system.md) ([#9](https://gitlab.hive-brain.de/MDietermann/rustkeep/issues/9)) — **Der Trigger ist die vierte Wirkungsform** neben Modifikator, Aktion und Fähigkeit: `{ on, condition, actions }` darf überall stehen, wo `modifiers` und `actions` stehen — sonst entstünde dieselbe Maschine ein zweites Mal in 14 als `on_hit`. Eine Bedingungsgrammatik für Trigger *und* Modifikatoren: getaggtes Enum, Liste ist UND, `not` je Eintrag, **kein ODER** (zwei Einträge oder ein gemeinsames Tag). Neun feste Events ohne Namensraum, jedes mit `source`/`target`; `attack_hit` und `damage_taken` bleiben getrennt, weil der eine beim Angreifer und der andere beim Getroffenen hängt. Im Trigger ist `self` immer der Träger. **Warteschlange statt synchron** — synchrones Feuern wäre ein Borrow-Fehler (über `self.quests` iterieren, während `self.apply` läuft) — mit Zähler bei 100 Events pro Zug statt Zyklengraph. Trigger werden gesucht, nicht indiziert. **Quest = Trigger mit `count`**, siebte Registry, erledigte bleiben markiert stehen. Zwei Nachträge an geschlossene Tickets: `start_quest` als siebte Aktion in 01, und `against: Option<CreatureRef>` plus `events: Vec<Event>` in 07.
|
- [08 — Event- und Trigger-System](issues/08-event-trigger-system.md) ([#9](https://gitlab.hive-brain.de/MDietermann/rustkeep/issues/9)) — **Der Trigger ist die vierte Wirkungsform** neben Modifikator, Aktion und Fähigkeit: `{ on, condition, actions }` darf überall stehen, wo `modifiers` und `actions` stehen — sonst entstünde dieselbe Maschine ein zweites Mal in 14 als `on_hit`. Eine Bedingungsgrammatik für Trigger *und* Modifikatoren: getaggtes Enum, Liste ist UND, `not` je Eintrag, **kein ODER** (zwei Einträge oder ein gemeinsames Tag). Neun feste Events ohne Namensraum, jedes mit `source`/`target`; `attack_hit` und `damage_taken` bleiben getrennt, weil der eine beim Angreifer und der andere beim Getroffenen hängt. Im Trigger ist `self` immer der Träger. **Warteschlange statt synchron** — synchrones Feuern wäre ein Borrow-Fehler (über `self.quests` iterieren, während `self.apply` läuft) — mit Zähler bei 100 Events pro Zug statt Zyklengraph. Trigger werden gesucht, nicht indiziert. **Quest = Trigger mit `count`**, siebte Registry, erledigte bleiben markiert stehen. Zwei Nachträge an geschlossene Tickets: `start_quest` als siebte Aktion in 01, und `against: Option<CreatureRef>` plus `events: Vec<Event>` in 07.
|
||||||
- [11 — UI-Gerüst und Screen-Modell](issues/11-ui-geruest-screen-modell.md) ([#12](https://gitlab.hive-brain.de/MDietermann/rustkeep/issues/12)) — **„Aktion" hiess zwei Dinge**: `Action` bleibt das TOML-Effektvokabular aus 01, `Command` wird das UI-Kommando, Eingang `Game::execute(cmd, reg)` — die eine Stelle, an der ein Zug endet (Modifikatoren ticken, Event-Warteschlange leerarbeiten). Zwölf Commands, **alle kosten einen Zug** (ein `costs_turn`-Flag waere eine Ausnahmeliste); Angriff und Flucht sind keine Varianten, sondern `UseAbility("core:attack")` und `UseAbility("core:flee")` — damit moddbar. Ein gemeinsamer `Vec<Screen>` fuer Screens und Popups; Screens **geben `Response` zurueck statt selbst zu schieben**, sonst haelt ein Screen ein `&mut` auf die `Vec`, in der er liegt. Ein `Response::Command` schliesst alle Overlays — dasselbe Flag, das schon fuers Rendern existiert. Zehn Screens, `ListState` in der Variante (der Stack erhaelt den Cursor gratis, solange der Screen liegt). Nur `KeyEventKind::Press`: bei `Repeat` bestimmte die Tastaturwiederholrate die Spielzeit; gegen zaehes Laufen hilft ein Renn-Kommando in `game/`. Tastenbelegung fest, Pfeile und `hjkl`. ratatui-Freiheit von `game/` prueft ein `#[test]` statt eines Workspace. Nachtrag an 01: `symbol` traegt das Zeichen und einen Farbnamen selbst — `ui/theme.rs` ist Code und damit fuer Modder unerreichbar.
|
- [11 — UI-Gerüst und Screen-Modell](issues/11-ui-geruest-screen-modell.md) ([#12](https://gitlab.hive-brain.de/MDietermann/rustkeep/issues/12)) — **„Aktion" hiess zwei Dinge**: `Action` bleibt das TOML-Effektvokabular aus 01, `Command` wird das UI-Kommando, Eingang `Game::execute(cmd, reg)` — die eine Stelle, an der ein Zug endet (Modifikatoren ticken, Event-Warteschlange leerarbeiten). Zwölf Commands, **alle kosten einen Zug** (ein `costs_turn`-Flag waere eine Ausnahmeliste); Angriff und Flucht sind keine Varianten, sondern `UseAbility("core:attack")` und `UseAbility("core:flee")` — damit moddbar. Ein gemeinsamer `Vec<Screen>` fuer Screens und Popups; Screens **geben `Response` zurueck statt selbst zu schieben**, sonst haelt ein Screen ein `&mut` auf die `Vec`, in der er liegt. Ein `Response::Command` schliesst alle Overlays — dasselbe Flag, das schon fuers Rendern existiert. Zehn Screens, `ListState` in der Variante (der Stack erhaelt den Cursor gratis, solange der Screen liegt). Nur `KeyEventKind::Press`: bei `Repeat` bestimmte die Tastaturwiederholrate die Spielzeit; gegen zaehes Laufen hilft ein Renn-Kommando in `game/`. Tastenbelegung fest, Pfeile und `hjkl`. ratatui-Freiheit von `game/` prueft ein `#[test]` statt eines Workspace. Nachtrag an 01: `symbol` traegt das Zeichen und einen Farbnamen selbst — `ui/theme.rs` ist Code und damit fuer Modder unerreichbar.
|
||||||
|
- [12 — Prototyp: ratatui-Durchstich](issues/12-prototyp-ratatui-durchstich.md) ([#13](https://gitlab.hive-brain.de/MDietermann/rustkeep/issues/13)) — **Das Modell aus 11 traegt.** Primaerquelle: Branch `prototyp/12-ratatui-durchstich`, `cargo run --example durchstich`. Bestaetigt: `mod game` bleibt ratatui-frei ohne Disziplin, Grid als eigenes `impl Widget` ueber `buf[(x,y)].set_symbol(…)`, `Vec<Screen>` mit Overlay-Flag fuer Rendern *und* Auto-Schliessen, `Response` statt Stack-Mutation (kein Borrow-Aerger). **Der Windows-Fallstrick ist jetzt eine Zahl**: `Tasten 14 → Press 7 → Zuege 7` — ohne Filter kostete jeder Schritt zwei Zuege und damit doppelte Spielzeit. Zwei Korrekturen: 11 sagte „alle Commands kosten einen Zug", richtig ist **alle ausgefuehrten** (`Outcome { TurnPassed, Rejected(&str) }`, Grund reist mit zurueck); und `Command` muss `Clone` sein, damit `Confirm` es herausgeben kann. Ungetestet blieb `Registries` neben `Game` — der Durchstich laedt keine.
|
||||||
|
|
||||||
## Not yet specified
|
## Not yet specified
|
||||||
|
|
||||||
|
|||||||
@@ -157,6 +157,8 @@ auf welcher Seite er steht.
|
|||||||
## Zug
|
## Zug
|
||||||
|
|
||||||
Die einzige Zeiteinheit des Spiels. Ein Feld weitergehen ist ein Zug, eine Kampfaktion ist ein Zug.
|
Die einzige Zeiteinheit des Spiels. Ein Feld weitergehen ist ein Zug, eine Kampfaktion ist ein Zug.
|
||||||
|
Ein **abgewiesenes** [Command](#command) kostet dagegen keinen — gegen eine Wand zu laufen verbraucht
|
||||||
|
keine Zeit.
|
||||||
Es gibt keine getrennte Kampfrunden-Uhr: Befristete [Modifikatoren](#modifikator) ticken auch beim
|
Es gibt keine getrennte Kampfrunden-Uhr: Befristete [Modifikatoren](#modifikator) ticken auch beim
|
||||||
Erkunden, damit „ich warte den Debuff draußen ab" eine Entscheidung mit Kosten bleibt.
|
Erkunden, damit „ich warte den Debuff draußen ab" eine Entscheidung mit Kosten bleibt.
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user