docs: ticket 07 (game-state-baum) aufloesen, wesen und affix ins glossar, vorgaben an tickets 08 09 10 14 16
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
# 07 — Game-State-Baum
|
# 07 — Game-State-Baum
|
||||||
|
|
||||||
Type: grilling
|
Type: grilling
|
||||||
Status: open
|
Status: resolved
|
||||||
Blocked by: 01, 02
|
Blocked by: 01, 02
|
||||||
|
|
||||||
## Question
|
## Question
|
||||||
@@ -19,3 +19,148 @@ Entschieden ist: ein `Game`-Ownership-Baum mit Verweisen über IDs oder Indizes,
|
|||||||
`Game` hängen), und wie wird das aufgelöst?
|
`Game` hängen), und wie wird das aufgelöst?
|
||||||
|
|
||||||
Ergebnis: Skizze der Structs mit Besitzverhältnissen und Referenzform.
|
Ergebnis: Skizze der Structs mit Besitzverhältnissen und Referenzform.
|
||||||
|
|
||||||
|
## Answer
|
||||||
|
|
||||||
|
Begriffe im [Glossar](../../../CONTEXT.md). Dieses Ticket entscheidet **Besitz und Verweisform** —
|
||||||
|
nicht die vollständige Feldliste, die kommt aus 14, 15 und 16.
|
||||||
|
|
||||||
|
### Definitionen und Zustand sind getrennt
|
||||||
|
|
||||||
|
`Registries` (aus Mods geladen, unveränderlich) lebt **neben** `Game`, nicht darin. Signaturen werden
|
||||||
|
durchgängig `fn irgendwas(&mut self, reg: &Registries)`.
|
||||||
|
|
||||||
|
Der Grund ist ein Rust-Fakt, kein Geschmack: Läge `registries` als Feld in `Game`, kollidierte
|
||||||
|
`game.player.angreifen(&game.registries)` — ein `&mut`-Borrow auf ein Feld plus ein `&`-Borrow auf ein
|
||||||
|
anderes geht innerhalb einer Funktion, aber nicht über einen Methodenaufruf auf `&mut self` hinweg.
|
||||||
|
Das ist kein Randfall, sondern jeder zweite Aufruf im Kampf. Ein Screen bekommt entsprechend
|
||||||
|
`(&mut Game, &Registries)`, wie 06 es vorzeichnet.
|
||||||
|
|
||||||
|
Laufzeitobjekte **verweisen** per `String`-ID, sie kopieren nicht. Kopien würden bedeuten, dass ein
|
||||||
|
`overrides` aus Ticket 03 an bereits aufgesammelten Gegenständen vorbeigeht und der Save jede
|
||||||
|
Definition mitschreiben müsste.
|
||||||
|
|
||||||
|
Keine `usize`-Indizes und keine internierten Handles: Bei ein paar hundert Registry-Einträgen und
|
||||||
|
*einer* Handlung pro Tastendruck kaufen sie Geschwindigkeit, die niemand messen kann, und bezahlen sie
|
||||||
|
mit einer Auflösungsschicht in beide Richtungen plus einer neuen Fehlerklasse — Indizes hängen an der
|
||||||
|
Ladereihenfolge, und die ändert sich bei jedem installierten Mod. Die Decke: Taucht je eine heiße
|
||||||
|
Schleife auf, wird an *dieser* Stelle interniert, nicht global vorsorglich.
|
||||||
|
|
||||||
|
### Der Baum
|
||||||
|
|
||||||
|
```rust
|
||||||
|
struct Game {
|
||||||
|
player: Creature,
|
||||||
|
enemies: Vec<Creature>,
|
||||||
|
floor: Floor, // Innenbau gehoert Ticket 10
|
||||||
|
floor_number: u8,
|
||||||
|
quests: Vec<QuestProgress>, // Form gehoert Ticket 08
|
||||||
|
turn: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Creature {
|
||||||
|
name: String,
|
||||||
|
base: Attributes, // Katalog aus Ticket 01
|
||||||
|
health: f32, stamina: f32, // Zustand, getrennt von den Attributen
|
||||||
|
options: Vec<OptionId>, // Rasse, Klasse
|
||||||
|
inventory: Vec<ItemInstance>,
|
||||||
|
equipment: HashMap<SlotId, ItemInstance>,
|
||||||
|
tags: Vec<String>,
|
||||||
|
modifiers: Vec<ActiveModifier>, // nur befristete
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ItemInstance { def: ItemId, affixes: Vec<AffixId> }
|
||||||
|
|
||||||
|
enum CreatureRef { Player, Enemy(usize) }
|
||||||
|
```
|
||||||
|
|
||||||
|
**Ein `Creature` für Held und Gegner.** Das Glossar spricht durchgehend von Wesen; zwei Typen hießen,
|
||||||
|
die Kampfformeln aus 14 zweimal zu schreiben oder ein `trait Combatant` mit genau zwei
|
||||||
|
Implementierungen einzuziehen — die Abstraktion, die Ticket 02 beim `ItemType` schon abgeräumt hat.
|
||||||
|
|
||||||
|
**Held und Gegner sind zwei Felder, keine gemeinsame `Vec`.** Der Grund ist die Charter: Solo-Held,
|
||||||
|
kein Party-System. „Gegner greift Gegner an" existiert nicht, also kauft die gemeinsame `Vec` eine
|
||||||
|
Symmetrie, die das Spiel nie braucht, und bezahlt sie mit `split_at_mut` oder Herauskopieren in jeder
|
||||||
|
Kampfroutine. Zwei Felder sind ein disjunkter Feld-Borrow, den Rust erlaubt. Fällt die
|
||||||
|
Solo-Entscheidung je, ist das ohnehin ein Umbau der Kampfarchitektur — sie steht unter „Out of scope".
|
||||||
|
|
||||||
|
**Ein `Floor`, kein `Vec<Floor>`.** Abstiegs-Roguelike ohne Rückkehr: hält den Save klein und erspart
|
||||||
|
dem Generator aus 10 die Garantie, dass Rückwege begehbar bleiben. Die Folge wird bewusst in Kauf
|
||||||
|
genommen — der Spieler kann nicht zum Händler auf Etage 2 zurück, Händler müssen also als Prefab auf
|
||||||
|
jeder Etage auftauchen können.
|
||||||
|
|
||||||
|
`options` ist kein Beiwerk: Ohne das Feld hat der Held nach dem Charakterbau keine Rasse mehr, weil
|
||||||
|
Rassen- und Klassenmodifikatoren sonst nirgends herkommen.
|
||||||
|
|
||||||
|
### Instanzzustand und die sechste Registry
|
||||||
|
|
||||||
|
Zwei Exemplare desselben Gegenstands dürfen sich unterscheiden. Eine Instanz trägt aber keine frei
|
||||||
|
zusammengesetzten Modifikatoren, sondern **Affix-IDs** — und Affixe sind eine neue, moddbare Registry
|
||||||
|
neben den fünf aus Ticket 02:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[[affixes]]
|
||||||
|
id = "core:flaming"
|
||||||
|
name = "Flammend" # -> "Flammendes Eisenschwert"
|
||||||
|
price_factor = 1.5
|
||||||
|
modifiers = [ { attribute = "damage", flat = 4 } ]
|
||||||
|
```
|
||||||
|
|
||||||
|
Instanzzustand kostet an drei Stellen, und der Verweis auf Affixe macht zwei davon klein: Der **Save**
|
||||||
|
bleibt eine Liste von IDs (`{ id = "core:iron_sword", affixes = ["core:flaming"] }`) statt
|
||||||
|
serialisierter Modifikator-Strukturen, und die **Preisbildung** ist `base_price × Π price_factor`.
|
||||||
|
Dazu sind Affixe Inhalt — ein Modder erfindet „Vampirisch", ohne dass die Engine davon weiß.
|
||||||
|
Strukturell ist ein Affix dasselbe wie eine Charakteroption: benannte Modifikatorliste plus Metadaten.
|
||||||
|
|
||||||
|
Mit frei zusammengesetzten Modifikatoren je Exemplar könnte ein zufällig erzeugtes Schwert theoretisch
|
||||||
|
alles, praktisch aber nichts, das ein Modder benennen oder ein Händler bepreisen könnte — und die
|
||||||
|
Anzeige hätte keinen Namen für das, was da liegt.
|
||||||
|
|
||||||
|
**Stapelregel:** `stackable` **und** gleiche `def` **und** gleiche `affixes`. Ein Vergleich zweier
|
||||||
|
`Vec<String>`, eine Zeile, verbietet nichts. Affixe an ausrüstbare Gegenstände zu binden wäre eine
|
||||||
|
Regel, die dem Modder erklärt, was er darf — Bevormundung, die 02 abgeräumt hat. Dass ein Trank mit
|
||||||
|
Zufallsaffix nicht mehr stapelt, ist die logische Folge und das Problem dessen, der ihn so baut.
|
||||||
|
|
||||||
|
### Ein Eingang, ein Ausgang
|
||||||
|
|
||||||
|
```rust
|
||||||
|
Game::apply(&mut self, action: &Action, source: CreatureRef, target: CreatureRef, reg: &Registries)
|
||||||
|
Game::attribute(&self, who: CreatureRef, attr: Attribute, reg: &Registries) -> f32
|
||||||
|
```
|
||||||
|
|
||||||
|
`CreatureRef` fällt direkt aus der Zwei-Felder-Entscheidung: Es gibt keinen gemeinsamen Index mehr,
|
||||||
|
also braucht eine Aktion eine Adresse.
|
||||||
|
|
||||||
|
Der **Lesezugang liegt auf `Game`, nicht auf `Creature`**. Ticket 01 hat entschieden, dass ein
|
||||||
|
Modifikator mit `target = "all_enemies"` beim Träger wohnt, damit die Aura mit ihm stirbt. Folglich
|
||||||
|
braucht das Lesen eines Gegner-Attributs Zugriff auf die Modifikatoren des Helden — ein `Creature`
|
||||||
|
kann seine eigenen Attribute gar nicht ausrechnen. Zwei Rechenwege für dieselbe Formel aus 01 wären
|
||||||
|
ein Bug, der sich als Balancing-Problem tarnt: Wer `creature.attribute()` direkt aufruft, weil es
|
||||||
|
bequemer aussieht, vergisst die Aura.
|
||||||
|
|
||||||
|
Ein Attributzugriff läuft die vier Quellen jedes Mal ab: `base` → `options` → `equipment` samt Affixen
|
||||||
|
→ befristete `modifiers`. **Kein Cache.** Das passiert pro Tastendruck, nicht pro Frame. Die Decke:
|
||||||
|
Liest 14 dasselbe Attribut in einer Schleife zwanzigmal, wird *dort* einmal vorberechnet, statt hier
|
||||||
|
vorsorglich ein Invalidierungsproblem zu bauen.
|
||||||
|
|
||||||
|
Modifikatoren werden ausdrücklich **nicht** beim Ausrüsten in `modifiers` hineinkopiert. Das machte
|
||||||
|
das Lesen billiger und das Schreiben gefährlich: Jedes Ablegen müsste exakt die richtigen Einträge
|
||||||
|
wieder herausoperieren, und ein Affix-Wechsel ginge an der Kopie vorbei.
|
||||||
|
|
||||||
|
### Auflagen an andere Tickets
|
||||||
|
|
||||||
|
- **08 Event- und Trigger-System**: Form von `QuestProgress`; das Bedingungsvokabular, das 01 bereits
|
||||||
|
angefordert hat.
|
||||||
|
- **09 Save-Schema**: serialisiert `Game`. `ItemInstance` schreibt sich als `{ id, affixes }`.
|
||||||
|
- **10 Raum-Prefab-Format**: Innenbau von `Floor` — Grid, Räume, Bodenloot, Ausgänge. Dazu der
|
||||||
|
Händlerraum als Prefab, das auf jeder Etage auftauchen kann.
|
||||||
|
- **14 Kampfformeln und Rundenablauf**: Aktionskatalog und Ziel-Auflösung — und der **Aufräumzeitpunkt
|
||||||
|
für tote Gegner**. Ein `CreatureRef::Enemy(i)` überlebt das Entfernen aus der `Vec` nicht; wann
|
||||||
|
aufgeräumt wird, entscheidet der Rundenablauf.
|
||||||
|
- **16 Loot-Tabellen**: wer Affixe beim Loot vergibt und mit welcher Gewichtung.
|
||||||
|
|
||||||
|
### Folgen für bestehenden Code
|
||||||
|
|
||||||
|
`Stats` zerfällt in `Attributes` (Katalog aus 01) und Zustandsfelder — heute stehen `max_health` und
|
||||||
|
`current_health` nebeneinander in derselben Struktur. `Player` wird zu `Creature`. Neben `Item` als
|
||||||
|
Definition entsteht `ItemInstance` als Laufzeitform.
|
||||||
|
|||||||
@@ -21,3 +21,12 @@ Zu klären:
|
|||||||
- Ist das synchrone Aufrufe (Handler direkt ausführen) oder eine Warteschlange?
|
- Ist das synchrone Aufrufe (Handler direkt ausführen) oder eine Warteschlange?
|
||||||
|
|
||||||
Ergebnis: Eventliste mit Nutzlast, Bedingungs-Grammatik, TOML-Beispiel für eine Quest.
|
Ergebnis: Eventliste mit Nutzlast, Bedingungs-Grammatik, TOML-Beispiel für eine Quest.
|
||||||
|
|
||||||
|
## Vorgaben aus anderen Tickets
|
||||||
|
|
||||||
|
- **01**: Das **Bedingungsvokabular** für `condition` wurde hierher delegiert, damit Quests und
|
||||||
|
Modifikatoren dieselbe Grammatik benutzen statt zweier, die sich subtil unterscheiden.
|
||||||
|
- **07**: `Game` hält `quests: Vec<QuestProgress>` — die Form von `QuestProgress` entscheidet dieses
|
||||||
|
Ticket. Wesen werden über `enum CreatureRef { Player, Enemy(usize) }` adressiert, Mutationen laufen
|
||||||
|
durch den einen Eingang `Game::apply(action, source, target, reg)`. Ein Event-Handler, der etwas
|
||||||
|
bewirkt, geht durch dieselbe Tür.
|
||||||
|
|||||||
@@ -21,3 +21,15 @@ Zu klären:
|
|||||||
- Wie viele Speicherstände, Autosave, und speichert man mitten im Kampf?
|
- Wie viele Speicherstände, Autosave, und speichert man mitten im Kampf?
|
||||||
|
|
||||||
Ergebnis: Save-Schema als kommentiertes TOML-Beispiel plus Regeln für fehlende Mods.
|
Ergebnis: Save-Schema als kommentiertes TOML-Beispiel plus Regeln für fehlende Mods.
|
||||||
|
|
||||||
|
## Vorgaben aus anderen Tickets
|
||||||
|
|
||||||
|
- **03**: Der Mod-Header nutzt `manifest.id` und `version`. Da `depends` keine Versionsbereiche trägt,
|
||||||
|
ist `version` das einzige Signal, dass ein Mod sich geändert hat — und dieses Ticket ist sein
|
||||||
|
**einziger Leser** in 1.0. Fällt der Header, fällt das Feld.
|
||||||
|
- **07**: Serialisiert wird der `Game`-Baum. Alle Verweise sind ohnehin `String`-IDs, es gibt also
|
||||||
|
nichts zurückzuübersetzen. `ItemInstance` schreibt sich als `{ id, affixes }` — Affixe sind
|
||||||
|
Registry-Einträge, keine eingebetteten Modifikatoren, der Save bleibt damit eine ID-Liste. Es gibt
|
||||||
|
**eine** Etage, keine besuchten Vorgänger. `Creature.options` (Rasse, Klasse) muss mit, sonst hat der
|
||||||
|
Held nach dem Laden keine Herkunft mehr — womit die Frage „was, wenn die Klasse fehlt?" oben scharf
|
||||||
|
wird.
|
||||||
|
|||||||
@@ -21,6 +21,14 @@ Entschieden ist der Hybrid: Modder bauen Prefabs, der Generator setzt prozedural
|
|||||||
|
|
||||||
Ergebnis: Prefab-Schema mit Beispiel plus die Regeln, nach denen der Generator zusammensetzt.
|
Ergebnis: Prefab-Schema mit Beispiel plus die Regeln, nach denen der Generator zusammensetzt.
|
||||||
|
|
||||||
|
## Vorgabe aus Ticket 07
|
||||||
|
|
||||||
|
`Game` besitzt genau **einen** `Floor`, dessen Innenbau — Grid, Räume, Bodenloot, Ausgänge — dieses
|
||||||
|
Ticket entscheidet. Besuchte Etagen werden verworfen, es gibt kein Backtracking: Der Generator muss
|
||||||
|
also keine begehbaren Rückwege garantieren, aber **ein Händlerraum muss auf jeder Etage auftauchen
|
||||||
|
können**, sonst ist Handel nach dem Abstieg unerreichbar. Bodenloot ist `ItemInstance`, also
|
||||||
|
`{ def, affixes }`, nicht bloß eine ID.
|
||||||
|
|
||||||
## Vorgabe aus Ticket 03
|
## Vorgabe aus Ticket 03
|
||||||
|
|
||||||
Prefabs sind eine **weitere Registry über dieselbe Ladepipeline**, kein eigener Lader — das
|
Prefabs sind eine **weitere Registry über dieselbe Ladepipeline**, kein eigener Lader — das
|
||||||
|
|||||||
@@ -29,3 +29,15 @@ Der Attributkatalog aus 01 liefert die Zutaten (`damage`, `hit_chance`, `critica
|
|||||||
Seed im Speicherstand?
|
Seed im Speicherstand?
|
||||||
|
|
||||||
Ergebnis: die Formeln als Pseudocode plus der Ablauf eines Zuges von Eingabe bis Ergebnis.
|
Ergebnis: die Formeln als Pseudocode plus der Ablauf eines Zuges von Eingabe bis Ergebnis.
|
||||||
|
|
||||||
|
## Vorgabe aus Ticket 07
|
||||||
|
|
||||||
|
Der Aktionskatalog und die Ziel-Auflösung (`self` / `target` / `all_enemies` aus 01) gehören hierher.
|
||||||
|
Alles läuft durch `Game::apply(action, source: CreatureRef, target: CreatureRef, reg)`, gelesen wird
|
||||||
|
über `Game::attribute(who, attr, reg)`.
|
||||||
|
|
||||||
|
Eine Frage fällt zusätzlich an, die 07 nur benennen konnte: **Wann werden tote Gegner aufgeräumt?**
|
||||||
|
`CreatureRef::Enemy(i)` ist ein Index in `Game.enemies` und überlebt das Entfernen aus der `Vec`
|
||||||
|
nicht — eine Aktion, die mitten im Zug einen Gegner tötet, verschiebt die Adressen aller
|
||||||
|
nachfolgenden. Der Rundenablauf muss festlegen, ob am Zugende, am Rundenende oder gar nicht
|
||||||
|
kompaktiert wird (Alternative: Tote bleiben mit einem Flag stehen).
|
||||||
|
|||||||
@@ -25,3 +25,13 @@ Die Gegenstands-Registry aus 02 steht, `luck` ist ein Attribut aus 01. Offen ist
|
|||||||
- **Zufall**: derselbe Generator wie im Kampf (Ticket 14), und gehört sein Zustand in den Speicherstand?
|
- **Zufall**: derselbe Generator wie im Kampf (Ticket 14), und gehört sein Zustand in den Speicherstand?
|
||||||
|
|
||||||
Ergebnis: Tabellenschema mit TOML-Beispiel, Gewichtungsregel, `luck`-Formel und die Mod-Einhängepunkte.
|
Ergebnis: Tabellenschema mit TOML-Beispiel, Gewichtungsregel, `luck`-Formel und die Mod-Einhängepunkte.
|
||||||
|
|
||||||
|
## Vorgabe aus Ticket 07
|
||||||
|
|
||||||
|
Beute ist `ItemInstance { def, affixes }`, nicht bloß eine Gegenstands-ID. Damit kommt eine Frage
|
||||||
|
dazu, die es vor 07 nicht gab: **Wer vergibt Affixe beim Loot, und mit welcher Gewichtung?** Hängen
|
||||||
|
sie an der Tabelle, am Eintrag, an der Etage — und wirkt `luck` auf die Affix-Zahl, auf deren
|
||||||
|
Seltenheit, oder gar nicht?
|
||||||
|
|
||||||
|
Affixe sind eine eigene Registry (die sechste), also gilt für sie derselbe Mod-Einhängepunkt wie für
|
||||||
|
Gegenstände.
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ Feststehende Rahmenentscheidungen aus der Charter-Session:
|
|||||||
| Mod-Stufen | Daten → Typen → Scripting, in dieser Reihenfolge. Scripting setzt auf der Typ-Registry auf |
|
| Mod-Stufen | Daten → Typen → Scripting, in dieser Reihenfolge. Scripting setzt auf der Typ-Registry auf |
|
||||||
| Effekte | Ein generisches Effekt-System mit Wirkung auf Stats. Rasse, Klasse, Items, Statuseffekte, Quest-Belohnungen und später Scripts nutzen dasselbe Vokabular — dieses Vokabular *ist* die Mod-API |
|
| Effekte | Ein generisches Effekt-System mit Wirkung auf Stats. Rasse, Klasse, Items, Statuseffekte, Quest-Belohnungen und später Scripts nutzen dasselbe Vokabular — dieses Vokabular *ist* die Mod-API |
|
||||||
| Quests | Bedingung + Belohnung als Daten, generisches Event-/Trigger-System |
|
| Quests | Bedingung + Belohnung als Daten, generisches Event-/Trigger-System |
|
||||||
| Weltzustand | `Game`-Ownership-Baum, Verweise über IDs/Indizes. Kein ECS |
|
| Weltzustand | `Game`-Ownership-Baum, Verweise über `String`-IDs (keine Indizes, 07). `Registries` liegt daneben, nicht darin. Kein ECS |
|
||||||
| Fehler | `Result` statt `panic!` — kaputte Mod-TOMLs sind der Normalfall, nicht der Ausnahmefall |
|
| Fehler | `Result` statt `panic!` — kaputte Mod-TOMLs sind der Normalfall, nicht der Ausnahmefall |
|
||||||
| Save | TOML, Mod-Liste im Header, fehlender Mod = Warnung und Verwerfen, kein harter Abbruch |
|
| Save | TOML, Mod-Liste im Header, fehlender Mod = Warnung und Verwerfen, kein harter Abbruch |
|
||||||
|
|
||||||
@@ -65,12 +65,13 @@ auch über Ticket 13.
|
|||||||
- [04 — Fehlerstrategie für kaputte Mods](issues/04-fehlerstrategie.md) ([#5](https://gitlab.hive-brain.de/MDietermann/rustkeep/issues/5)) — **Syntax ist ein Datei-Fehler, alles Semantische ein Eintrags-Fehler**; damit 03s Versprechen hält, wird zweistufig deserialisiert (`Vec<Spanned<toml::Value>>`, dann Eintrag für Eintrag), sonst reißt ein Tippfehler die ganze `items.toml` mit. Genau drei harte Fehler: `depends` fehlend, `depends` verletzt, `core` nicht gefunden — stderr und `exit(1)`. `core` ist sonst kein Sonderfall; abgesichert wird es durch einen `#[test]`, der `mods/core/` mit null Warnungen lädt. Warnungen sammeln sich in `Vec<String>` und landen in `logs/load-<zeitstempel>.log` (letzte zehn), plus eine Zeile auf dem Startbildschirm — kein Report-Screen. Meldung `meinmod/items.toml:42: core:iron_sword — <message>`, Mod-ID vorne, keine Spalte. Rust: handgeschriebenes `enum LoadError` mit `impl Display` statt `thiserror`/`anyhow`, `load_mods() -> Result<(Registries, Vec<String>), LoadError>`, Kette endet vor `main` mit `eprintln!` und `exit(1)`. **Panic-Grenze ist die Datenherkunft**: TOML ist `Result`, eigener Code darf panicken.
|
- [04 — Fehlerstrategie für kaputte Mods](issues/04-fehlerstrategie.md) ([#5](https://gitlab.hive-brain.de/MDietermann/rustkeep/issues/5)) — **Syntax ist ein Datei-Fehler, alles Semantische ein Eintrags-Fehler**; damit 03s Versprechen hält, wird zweistufig deserialisiert (`Vec<Spanned<toml::Value>>`, dann Eintrag für Eintrag), sonst reißt ein Tippfehler die ganze `items.toml` mit. Genau drei harte Fehler: `depends` fehlend, `depends` verletzt, `core` nicht gefunden — stderr und `exit(1)`. `core` ist sonst kein Sonderfall; abgesichert wird es durch einen `#[test]`, der `mods/core/` mit null Warnungen lädt. Warnungen sammeln sich in `Vec<String>` und landen in `logs/load-<zeitstempel>.log` (letzte zehn), plus eine Zeile auf dem Startbildschirm — kein Report-Screen. Meldung `meinmod/items.toml:42: core:iron_sword — <message>`, Mod-ID vorne, keine Spalte. Rust: handgeschriebenes `enum LoadError` mit `impl Display` statt `thiserror`/`anyhow`, `load_mods() -> Result<(Registries, Vec<String>), LoadError>`, Kette endet vor `main` mit `eprintln!` und `exit(1)`. **Panic-Grenze ist die Datenherkunft**: TOML ist `Result`, eigener Code darf panicken.
|
||||||
- [05 — Research: Script-Engine](issues/05-research-script-engine.md) ([#6](https://gitlab.hive-brain.de/MDietermann/rustkeep/issues/6)) — Empfehlung Rhai wegen Fehlermeldungen mit Zeile und Spalte, billiger Typ-Brücke, AST-Hot-Reload und Build ohne `cc`. Gemessen: mlua liest unter `StdLib::ALL_SAFE` per `io.open` echte Dateien — „safe" heißt dort speichersicher, nicht fähigkeitsbeschränkt. Lua gewinnt bei der Modder-Vertrautheit; WASM scheidet für 1.0 aus. **Kernfolge: reden Scripts nur über das Effekt-Vokabular, ist die Engine-Wahl umkehrbar.** Entscheidung offen in Ticket 13.
|
- [05 — Research: Script-Engine](issues/05-research-script-engine.md) ([#6](https://gitlab.hive-brain.de/MDietermann/rustkeep/issues/6)) — Empfehlung Rhai wegen Fehlermeldungen mit Zeile und Spalte, billiger Typ-Brücke, AST-Hot-Reload und Build ohne `cc`. Gemessen: mlua liest unter `StdLib::ALL_SAFE` per `io.open` echte Dateien — „safe" heißt dort speichersicher, nicht fähigkeitsbeschränkt. Lua gewinnt bei der Modder-Vertrautheit; WASM scheidet für 1.0 aus. **Kernfolge: reden Scripts nur über das Effekt-Vokabular, ist die Engine-Wahl umkehrbar.** Entscheidung offen in Ticket 13.
|
||||||
- [06 — Research: ratatui-App-Architektur](issues/06-research-ratatui-architektur.md) ([#7](https://gitlab.hive-brain.de/MDietermann/rustkeep/issues/7)) — Screen-Stack über zentralem `Game`-State statt Elm oder Component-Trait; `game/` bleibt ratatui-frei; Popups per `Clear` auf Sub-`Rect` statt Widget-Stack; blockierendes `event::read()` ohne Tick; Dungeon-Grid ist Eigenbau, es gibt kein Widget dafür; ratatui 0.30.2 mit MSRV 1.88; `KeyEventKind::Press`-Filter unter Windows Pflicht. Voller Bericht in [research/06](research/06-ratatui-architektur.md).
|
- [06 — Research: ratatui-App-Architektur](issues/06-research-ratatui-architektur.md) ([#7](https://gitlab.hive-brain.de/MDietermann/rustkeep/issues/7)) — Screen-Stack über zentralem `Game`-State statt Elm oder Component-Trait; `game/` bleibt ratatui-frei; Popups per `Clear` auf Sub-`Rect` statt Widget-Stack; blockierendes `event::read()` ohne Tick; Dungeon-Grid ist Eigenbau, es gibt kein Widget dafür; ratatui 0.30.2 mit MSRV 1.88; `KeyEventKind::Press`-Filter unter Windows Pflicht. Voller Bericht in [research/06](research/06-ratatui-architektur.md).
|
||||||
|
- [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.
|
||||||
|
|
||||||
## Not yet specified
|
## Not yet specified
|
||||||
|
|
||||||
- **Balancing** — konkrete Zahlen für Attribute, Schaden, Preise und Erfahrung über fünf Etagen. Wartet auf Kampfformeln (14), Progression (15) und Loot (16); vorher gibt es nichts zu balancieren.
|
- **Balancing** — konkrete Zahlen für Attribute, Schaden, Preise und Erfahrung über fünf Etagen. Wartet auf Kampfformeln (14), Progression (15) und Loot (16); vorher gibt es nichts zu balancieren.
|
||||||
- **Boss-Design** — ob Bosse nur Gegner mit besseren Werten sind oder eigene Mechanik brauchen (Phasen, Skripte). Berührt Stufe (c) des Mod-Supports.
|
- **Boss-Design** — ob Bosse nur Gegner mit besseren Werten sind oder eigene Mechanik brauchen (Phasen, Skripte). Berührt Stufe (c) des Mod-Supports.
|
||||||
- **NPC- und Handelsmodell** — Warenbestand, Preisbildung über `charisma`, Dialogdaten. Wartet auf das Event-System (08).
|
- **NPC- und Handelsmodell** — Warenbestand, Dialogdaten. Wartet auf das Event-System (08). Die Preisformel steht bereits in 07; weil 07 Backtracking ausgeschlossen hat, muss ein Händler auf **jeder** Etage erreichbar sein.
|
||||||
- **Inventar-UX** — Slots, Gewicht oder Kapazität, Sortierung, Vergleichsansicht. Wartet jetzt auf das Screen-Modell (11), nicht mehr auf 06.
|
- **Inventar-UX** — Slots, Gewicht oder Kapazität, Sortierung, Vergleichsansicht. Wartet jetzt auf das Screen-Modell (11), nicht mehr auf 06.
|
||||||
- **Mod-Verteilung** — durch 03 auf „Ordner ins Profil entpacken" geschrumpft, ohne Versionsprüfung. Offen ist nur noch, ob 1.0 mehr braucht.
|
- **Mod-Verteilung** — durch 03 auf „Ordner ins Profil entpacken" geschrumpft, ohne Versionsprüfung. Offen ist nur noch, ob 1.0 mehr braucht.
|
||||||
|
|
||||||
|
|||||||
23
CONTEXT.md
23
CONTEXT.md
@@ -2,9 +2,18 @@
|
|||||||
|
|
||||||
Kanonische Begriffe für rustkeep. Nur Sprache, keine Implementierung.
|
Kanonische Begriffe für rustkeep. Nur Sprache, keine Implementierung.
|
||||||
|
|
||||||
|
## Wesen
|
||||||
|
|
||||||
|
Held oder Gegner — im Code derselbe Typ. Träger von [Attributen](#attribut), [Zustand](#zustand),
|
||||||
|
[Tags](#tag), Inventar, Ausrüstung, gewählten [Charakteroptionen](#charakteroption) und den befristeten
|
||||||
|
[Modifikatoren](#modifikator), die gerade auf ihm liegen.
|
||||||
|
|
||||||
|
Ein Wesen ist dabei nicht in sich abgeschlossen: Eine Aura wohnt beim Träger und wird erst beim
|
||||||
|
Auslesen des Ziels eingesammelt.
|
||||||
|
|
||||||
## Attribut
|
## Attribut
|
||||||
|
|
||||||
Eine benannte, veränderbare Größe an einem Wesen. Der Katalog ist fest und wird von der Engine
|
Eine benannte, veränderbare Größe an einem [Wesen](#wesen). Der Katalog ist fest und wird von der Engine
|
||||||
vorgegeben — er umfasst die Grundwerte (Stärke, Geschicklichkeit, Konstitution, Charisma, Glück,
|
vorgegeben — er umfasst die Grundwerte (Stärke, Geschicklichkeit, Konstitution, Charisma, Glück,
|
||||||
Leben, Ausdauer, Level) *und* die abgeleiteten Kampfgrößen (Schaden, Rüstung, Trefferchance,
|
Leben, Ausdauer, Level) *und* die abgeleiteten Kampfgrößen (Schaden, Rüstung, Trefferchance,
|
||||||
Kritische Chance, Kritischer Faktor, Preis-Modifikator, Tragfähigkeit).
|
Kritische Chance, Kritischer Faktor, Preis-Modifikator, Tragfähigkeit).
|
||||||
@@ -54,6 +63,18 @@ Modifikator auf `damage`, mehr nicht.
|
|||||||
Braucht ein Gegenstand etwas, das Modifikator, Aktion oder Fähigkeit nicht ausdrücken können, ist das
|
Braucht ein Gegenstand etwas, das Modifikator, Aktion oder Fähigkeit nicht ausdrücken können, ist das
|
||||||
eine Lücke im Vokabular — sie wird dort geschlossen, nicht mit einem Sonderfeld umgangen.
|
eine Lücke im Vokabular — sie wird dort geschlossen, nicht mit einem Sonderfeld umgangen.
|
||||||
|
|
||||||
|
## Affix
|
||||||
|
|
||||||
|
Eine benannte [Modifikator](#modifikator)-Liste mit Anzeigename und Preisfaktor, die an einem
|
||||||
|
*einzelnen Exemplar* eines [Gegenstands](#gegenstand) hängt — „Flammendes Eisenschwert". Moddbarer,
|
||||||
|
namespaced Inhalt (`core:flaming`).
|
||||||
|
|
||||||
|
Der Affix ist der Grund, warum ein Gegenstand im Spiel nicht dasselbe ist wie sein Katalogeintrag: Die
|
||||||
|
Definition liegt in der Registry, das Exemplar trägt zusätzlich seine Affixe.
|
||||||
|
|
||||||
|
Strukturell ist ein Affix dasselbe wie eine [Charakteroption](#charakteroption) — benannte
|
||||||
|
Modifikatorliste plus Metadaten. Der Unterschied ist, woran er hängt.
|
||||||
|
|
||||||
## Slot
|
## Slot
|
||||||
|
|
||||||
Eine benannte Stelle am Charakter, an der genau ein [Gegenstand](#gegenstand) getragen werden kann.
|
Eine benannte Stelle am Charakter, an der genau ein [Gegenstand](#gegenstand) getragen werden kann.
|
||||||
|
|||||||
Reference in New Issue
Block a user