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:
2026-08-12 10:08:57 +02:00
parent 145b82bc08
commit f25f393880
8 changed files with 222 additions and 4 deletions

View File

@@ -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.

View File

@@ -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.

View File

@@ -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.

View File

@@ -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

View File

@@ -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).

View File

@@ -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.

View File

@@ -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.

View File

@@ -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.