docs: ticket 14 (kampfformeln) aufloesen, nachtraege an 01 07 08 11, registry-liste und initiative ins glossar

This commit is contained in:
2026-08-12 11:51:25 +02:00
parent 88195d3c9a
commit d51ebb26a1
7 changed files with 270 additions and 1 deletions

View File

@@ -116,6 +116,22 @@ color = "cyan"
`data/` kennt nur `char` und ein Farbnamen-Enum, `ui/theme.rs` übersetzt in `ratatui::Color` — `game/`
und `data/` bleiben damit weiterhin ratatui-frei.
### Nachtrag aus Ticket 14: `damage` skaliert, und `flee` ist die achte Aktion
Die `damage`-Aktion oben kennt nur `amount = 12`, also einen festen Wert. Ein Waffenangriff soll aber
das `damage`-Attribut des Angreifers verwenden — das war gar nicht ausdrückbar. Die Menge ist deshalb
immer:
```
menge = amount + scale * damage-Attribut(Verursacher) # beide optional, Standard 0
```
Eine Formel für alles: Basisangriff `scale = 1.0` · Feuerball `amount = 20` · Wuchtschlag
`scale = 1.5` · Mischformen beides. Keine zwei Modi, kein Sonderfall für `core:attack`.
Dazu **`flee` als achte Aktion** — Ticket 11 hat Flucht zu einer Fähigkeit gemacht, und die braucht
eine Aktion, die sie ausführt. Der Katalog hat damit acht Einträge.
### Bewusst nicht entschieden
Das **Bedingungsvokabular** für `condition`. Es kommt aus Ticket 08, damit Quests und Modifikatoren

View File

@@ -162,6 +162,25 @@ nicht auswertbar — die Funktion wüsste nicht, gegen wen geschlagen wird. Der
Dazu kommt `events: Vec<Event>` als Feld in `Game`: Ticket 08 arbeitet Events als Warteschlange ab,
weil synchrones Feuern über `self.quests` iterieren müsste, während `self.apply` erneut läuft.
### Nachtrag aus Ticket 14
**`enemies` ist der laufende Kampf, nicht die Etage.** Das stand oben nie ausdrücklich da und wäre
sonst falsch geraten worden: Bei Grid-Erkundung mit Menü-Kampf griffe sonst der Skelettkrieger drei
Räume weiter jeden Zug mit an. Leer bedeutet kein Kampf; gefüllt wird die Liste von der Etage (10).
Zwei neue Felder am `Creature`:
- `initiative: i32` — einmal beim Betreten des Kampfes gewürfelt. Bewusst ein Zahlenwert am Wesen und
keine Reihenfolgeliste aus `CreatureRef`: Die wäre eine **zweite** Indexliste mit demselben
Ungültigkeitsproblem beim Tod eines Gegners.
- `cooldowns: HashMap<AbilityId, u32>` — Abklingzeiten in Zügen.
Dazu der RNG-Seed als Feld in `Game`, damit Nachladen nicht neu würfelt.
Und die Antwort auf die Frage, die 07 offen an 14 gereicht hatte: **Tote Gegner werden am Ende von
`Game::execute` entfernt**, nach dem Leerarbeiten der Event-Warteschlange. Innerhalb eines Zuges
bleiben Indizes stabil.
### Auflagen an andere Tickets
- **08 Event- und Trigger-System**: Form von `QuestProgress`; das Bedingungsvokabular, das 01 bereits

View File

@@ -102,6 +102,20 @@ Stufe (c) kann niemand ein eigenes Event *auslösen* — ein namespaced Event w
Kommt Scripting (Ticket 13), wird `emit_event` eine gewöhnliche Aktion und die Liste bekommt eine
`Custom(String)`-Variante. Ein Zusatz, kein Umbau.
### Nachtrag aus Ticket 14: elf Events statt neun
`combat_started` und `combat_ended { outcome: Victory | Fled }` kommen dazu. Ohne sie ist „gewinne drei
Kämpfe" nicht formulierbar, und `combat_ended` **braucht** die Unterscheidung — sonst zählt Weglaufen
als Erfolg.
Beide mit `source = Player`, ohne `target`. Platzierung im Zugablauf aus 14: `combat_started` bei
Schritt 1 neben `turn_started`, `combat_ended` bei Schritt 5 direkt nach dem `retain` — dann ist die
Gegnerliste bereits leer und ein Trigger sieht den Endzustand statt einer halb aufgeräumten
Zwischenstufe.
Keine dritte Variante für den Tod des Helden: `creature_died` mit `source = Player` deckt ihn ab, und
danach gibt es ohnehin keinen Trigger mehr, der etwas davon hätte.
### Warteschlange statt Rekursion
Events landen in `Game.events: Vec<Event>` und werden nach der laufenden Aktion in einer Schleife

View File

@@ -120,6 +120,11 @@ Trank.
`MainMenu` (trägt die Ladewarnungs-Zeile aus 04) · `CharacterCreation` (Registry-Schleife aus 02) ·
`Explore` · `Combat` · `Inventory` · `CharacterSheet` · `QuestLog` · `Shop` · `Dialog` · `Confirm`
**Nachtrag aus Ticket 14: `GameOver` als elfter Screen**, Vollbild. Die lazy Variante — zurück ins
`MainMenu` mit einer Meldungszeile — wurde bewusst verworfen: Ein Abstiegs-Roguelike lebt davon, dass
der Lauf am Ende *erzählt* wird (Etage, Zug, Gold, Todesursache). Das ist der Moment, für den man fünf
Etagen gespielt hat, und kein anderer Screen kann ihn übernehmen.
Kein `ItemDetail`-Popup: Die Inventarliste zeigt Details in einer Seitenspalte, das ist ein Layout und
kein Screen. `Shop` bleibt getrennt von `Dialog`, weil Handel zwei Listen nebeneinander braucht und
der Dialog eine Textfläche mit Antwortoptionen.

View File

@@ -1,7 +1,7 @@
# 14 — Kampfformeln und Rundenablauf
Type: grilling
Status: open
Status: resolved
Blocked by: 01
## Question
@@ -41,3 +41,183 @@ Eine Frage fällt zusätzlich an, die 07 nur benennen konnte: **Wann werden tote
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).
## Answer
Begriffe im [Glossar](../../../CONTEXT.md). **Zahlen gehören nicht hierher** — die Map hält Balancing
getrennt; hier stehen Formeln und Ablauf.
### Rundenablauf
**Ein Zug = Spielerhandlung plus alle Gegnerreaktionen.** Nicht jede Handlung ein eigener Zug: Bei drei
Gegnern tickte ein Gift-Debuff sonst viermal pro Runde, und ein Tastendruck erzeugte vier Züge — das
bräche die Zusage aus 11, die Ticket 12 gerade gemessen hat.
Reihenfolge über **Initiative**, einmal beim Betreten des Kampfes aus `dexterity` plus Wurf gewürfelt
und als `i32` **am Wesen** gehalten. Ausdrücklich *nicht* als Reihenfolgeliste aus `CreatureRef`: Die
wäre eine zweite Indexliste, die beim Tod eines Gegners genauso ungültig wird wie die erste. Ein
Zahlenwert am Wesen verschwindet mit ihm, die Sortierung stimmt weiter. Gleichstand geht an den
Spieler — deterministisch und zu seinen Gunsten.
`dexterity` speist damit Initiative *und* Ausweichen. Absicht, aber 15 sollte es beim Balancing wissen.
```
Game::execute(cmd, reg):
1. turn_started (+ combat_started, falls der Kampf gerade beginnt) in die Warteschlange
2. befristete Modifikatoren ticken, Ausdauer regeneriert, Abklingzeiten sinken
3. Teilnehmer handeln in Initiativreihenfolge, Spieler-Command an seinem Platz
-> Abbruch, sobald der Held tot ist
4. nach JEDER einzelnen Handlung die Warteschlange leerarbeiten (bis leer oder 100)
5. retain auf tote Gegner, danach ggf. combat_ended
6. turn += 1
```
Schritt 4 nach jeder Handlung statt einmal am Zugende: Sonst schlägt ein Gegner zu, den ein
Dornenpanzer eine Handlung zuvor getötet hat. Schritt 5 erst **nach** dem Leeren: Sonst zeigt ein
Trigger auf `creature_died` auf einen bereits entfernten Gegner. Innerhalb eines Zuges müssen Indizes
stabil bleiben — über Zuggrenzen hinweg hält ohnehin niemand ein `CreatureRef`, die UI liest neu.
Genau **eine** Handlung pro Zug, kein Ausdauer-Budget. `max_stamina` ist die Ressource für
Fähigkeiten — das beantwortet die Frage „wozu sonst" aus dem Ticketkörper.
Stirbt der Held mitten im Zug, bricht der Zug ab (laufende Handlung samt Warteschlange noch zu Ende).
Zwei weitere Gegner, die auf eine Leiche einschlagen, erzeugen nur Kampflog und Trigger auf ein Wesen,
das die Runde nicht mehr erlebt.
### Formeln
```
p_treffer = clamp(hit_chance(A, gegen=B) - dexterity(B) * AUSWEICH, 0.05, 0.95)
roh = attribute(damage, A, gegen=B) # 01-Stapelung, bedingte Modifikatoren inklusive
krit = rand() < clamp(critical_chance(A), 0.0, 1.0)
roh = roh * critical_factor(A) falls krit
r = clamp(resistance(B, typ), -1.0, 0.8)
final = max(1, round(roh * (1.0 - r)))
p_flucht = clamp(0.5 + (dexterity(Held) - dexterity(schnellster Gegner)) * FLUCHT, 0.1, 0.9)
```
**Die Frage aus dem Ticketkörper löst sich auf.** Ob `critical_factor` den Rohschaden oder den
geminderten multipliziert, ist mathematisch belanglos: `roh × kf × (1−r)` ist dasselbe wie
`roh × (1−r) × kf`. Festzulegen war nur, dass beide multiplikativ *bleiben*.
Drei Klemmbereiche, jeder mit Grund:
- **Resistenz bei `0.8`** — bei `1.0` wäre ein Wesen immun, und Immunität ist keine Zahl, sondern ein
Spielabbruch. Nach unten bis `-1.0`, damit Verwundbarkeit ausdrückbar bleibt („Untote nehmen
doppelten heiligen Schaden").
- **`max(1, …)`** — ein Treffer macht nie null, sonst gibt es Kämpfe, die rechnerisch nie enden.
- **`0.05 / 0.95` beim Treffen** — sonst wird ein hochgezüchteter Verteidiger unangreifbar und ein
hochgezüchteter Angreifer unfehlbar. Beides Sackgassen, die man erst auf Etage 5 bemerkt.
Keine Schadensstreuung für 1.0: Der Krit liefert die Varianz bereits, Streuung wäre ein zweiter
Zufallsknopf ohne eigene Entscheidung dahinter.
### Zufall
Eigener xorshift64* — acht Zeilen statt einer Dependency. Der Seed liegt als Feld in `Game` und damit
**im Speicherstand**.
Das ist keine Implementierungsfrage, sondern eine Spielentscheidung: **Nachladen würfelt nicht neu.**
Derselbe Angriff führt nach dem Laden zum selben Ergebnis, Save-Scumming läuft ins Leere. Ein
Abstiegs-Roguelike lebt von Entscheidungen mit Konsequenzen.
### Fähigkeiten — die Bestellung aus Ticket 02
```toml
[[abilities]]
id = "core:power_strike"
name = "Wuchtschlag"
symbol = "!"
color = "red"
cost = 15 # Ausdauer
cooldown = 3 # Zuege
condition = [ … ] # optional, Grammatik aus 08
actions = [ { action = "damage", scale = 1.5, damage_type = "blunt", target = "target" } ]
```
**Nur Ausdauer, kein zweiter Ressourcenpool.** Mana wäre ein zweites Attribut, eine zweite
Regeneration, eine zweite Anzeige und eine zweite Sperrregel. Die Decke: Ein Mod, der Mana will,
bräuchte ein Engine-Attribut — ein Nachtrag an 01, kein Problem, das ein Modder selbst lösen kann.
**Abklingzeit in Zügen**, als `cooldowns: HashMap<AbilityId, u32>` am `Creature`. Sie als befristete
Modifikatoren zu tarnen wäre clever und genau die Sorte Cleverness, die man um drei Uhr nachts
entziffert.
**Gesperrt heißt**: Ausdauer < `cost`, oder `cooldown > 0`, oder `condition` unerfüllt. Auslösen ergibt
dann `Outcome::Rejected("Nicht genug Ausdauer")` aus Ticket 12 und **kostet keinen Zug**. Die Regel
steht im Spielkern, nicht in der UI — sonst kennt ein Script sie nicht.
**Ausdauer regeneriert pro Zug** um einen festen Betrag. Das gibt `Wait` aus Ticket 11 seinen Zweck und
macht Ausdauer zu einer Entscheidung statt zu einem Countdown.
Eine misslungene Flucht ist übrigens `TurnPassed`, keine Zurückweisung: Der Versuch hat stattgefunden,
die Gegner haben gehandelt. `Rejected` bleibt dem Fall vorbehalten, dass gar nichts passiert ist.
### Gegner — die achte Registry
Beim Aufschreiben der KI fiel eine Lücke auf, die seit 02 offen lag: **Wesen stehen in keiner
Registry.** 02 hatte fünf, 07 ergänzte Affixe, 08 Quests — woher Gegnerdefinitionen kommen, hat nie
jemand gesagt, obwohl 10 sie in Räume setzen und 16 Beute an sie hängen muss.
```toml
[[creatures]]
id = "core:skeleton"
name = "Skelett"
symbol = "s"
color = "white"
tags = ["core:undead"]
base = { max_health = 30, damage = 6, hit_chance = 0.7, dexterity = 3 }
abilities = ["core:attack", "core:bone_throw"]
ai = [
{ condition = [ { check = "attribute", who = "self", attribute = "health", below = 0.3 } ],
ability = "core:flee" },
{ ability = "core:attack" }, # ohne condition = greift immer
]
```
`ai` ist eine **geordnete Liste**, der erste passende *und verfügbare* Eintrag gewinnt — dieselbe
Bedingungsgrammatik wie überall sonst, kein neues Vokabular. Passt nichts, **setzt der Gegner aus**;
einer, der die Runde blockiert, wäre schlimmer als einer, der nichts tut.
Eine `ai`-Liste **ohne bedingungslosen letzten Eintrag** gibt eine Ladewarnung über die Maschinerie aus
04. Das ist fast immer ein Versehen, und zur Laufzeit erkennt man es nur daran, dass ein Gegner
gelegentlich stillsteht — die teuerste Sorte Fehler.
Boss-Phasen sind damit *nicht* gelöst, aber vorbereitet: Die Liste trägt sie, was fehlt, ist der
Phasenbegriff.
### Balance als Daten — ausdrückliche Ausnahme zu Ticket 03
Fünf Stellknöpfe entstehen hier: `AUSWEICH`, `FLUCHT`, Ausdauer-Regeneration, Resistenz-Deckel und
Treffer-Klemmbereich. Sie liegen als `[balance]`-Tabelle in TOML und verschmelzen **feldweise** über
die Ladereihenfolge.
Das widerspricht der Ganz-oder-gar-nicht-Regel aus 03, und zwar bewusst: Ein Mod, der nur `AUSWEICH`
dreht, müsste sonst alle fünf Werte abschreiben, und ein sechster Knopf in der nächsten Engine-Version
bräche ihn stillschweigend.
Die Ausnahme bleibt eng, weil ihre Begründung eng ist: **`[balance]` hat ein festes, der Engine
bekanntes Schema.** Genau das fehlte bei Inhalt mit offenen Feldern — Modifikator- und Aktionslisten
bräuchten einen Merge-Algorithmus, und das war 03s Argument gegen feldweises Patchen. Hier greift es
nicht.
Standardwerte liegen in der Engine, nicht in `core`. Dann ist ein Mod, der einen Knopf dreht, eine
Zeile lang.
### Vier Nachträge an geschlossene Tickets
| Ticket | Nachtrag |
|---|---|
| **01** | `damage`-Aktion: `menge = amount + scale * damage-Attribut(Verursacher)`, beide Felder optional (Standard 0). Deckt Basisangriff (`scale = 1.0`), Feuerball (`amount = 20`) und Wuchtschlag (`scale = 1.5`) mit *einer* Formel. Dazu `flee` als **achte Aktion** |
| **07** | `initiative: i32` und `cooldowns: HashMap<AbilityId, u32>` am `Creature`; RNG-Seed in `Game`; **`enemies` ist der laufende Kampf**, nicht die Etage |
| **08** | `combat_started` und `combat_ended { outcome: Victory \| Fled }` — die Eventliste wächst auf **elf**. Der Tod des Helden braucht keine dritte Variante, `creature_died` mit `source = Player` deckt ihn ab |
| **11** | **`GameOver`** als elfter Screen, Vollbild. Bewusst nicht die lazy Variante mit einer Meldungszeile im Hauptmenü: Ein Abstiegs-Roguelike lebt davon, dass der Lauf am Ende erzählt wird — Etage, Zug, Gold, Todesursache |
### Auflagen an andere Tickets
- **10 Raum-Prefab-Format**: füllt `Game.enemies`; Kampfbeginn ist ein Prefab-Ereignis.
- **15 Progression**: was bei Sieg passiert (EXP), und die Zahlen hinter allen Formeln inklusive der
fünf `[balance]`-Knöpfe.
- **16 Loot-Tabellen**: Beute hängt an der `creatures`-Registry, die hier entstanden ist.

View File

@@ -69,6 +69,7 @@ auch über Ticket 13.
- [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.
- [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.
- [14 — Kampfformeln und Rundenablauf](issues/14-kampfformeln-und-rundenablauf.md) ([#15](https://gitlab.hive-brain.de/MDietermann/rustkeep/issues/15)) — **Ein Zug = Spielerhandlung plus alle Gegnerreaktionen**, sonst tickte ein Debuff bei drei Gegnern viermal. Initiative als `i32` **am Wesen** statt als Reihenfolgeliste (die waere eine zweite Indexsammlung mit demselben Ungueltigkeitsproblem); tote Gegner erst am Ende von `execute`, nach dem Leeren der Warteschlange. Trefferformel und Schadenskette mit drei Klemmbereichen — `0.8` bei Resistenz, weil `1.0` Immunitaet und damit ein Spielabbruch waere. **Die Krit-Reihenfolge-Frage loest sich auf**: multiplikativ ist kommutativ. Eigener xorshift, **Seed im Save** — Nachladen wuerfelt nicht neu. Faehigkeiten kosten Ausdauer, Abklingzeit in Zuegen, gesperrt ⇒ `Rejected` ohne Zug. **Achte Registry: Wesen** — die Luecke lag seit 02 offen; `ai` als geordnete Bedingungsliste. `[balance]` verschmilzt **feldweise**, ausdrueckliche Ausnahme zu 03 (festes Schema). Vier Nachtraege: `scale` und `flee` an 01, drei Felder an 07, zwei Events an 08, `GameOver` an 11.
## Not yet specified

View File

@@ -11,6 +11,9 @@ Held oder Gegner — im Code derselbe Typ. Träger von [Attributen](#attribut),
Ein Wesen ist dabei nicht in sich abgeschlossen: Eine Aura wohnt beim Träger und wird erst beim
Auslesen des Ziels eingesammelt.
Gegner werden in der [Registry](#registry) `[[creatures]]` definiert — mit Grundwerten, Tags,
Fähigkeiten und einer geordneten `ai`-Liste, deren erster passender und verfügbarer Eintrag gewinnt.
## Attribut
Eine benannte, veränderbare Größe an einem [Wesen](#wesen). Der Katalog ist fest und wird von der Engine
@@ -162,6 +165,37 @@ keine Zeit.
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.
## Initiative
Ein Zahlenwert am [Wesen](#wesen), einmal beim Betreten eines Kampfes aus `dexterity` plus Wurf
gewürfelt. Er bestimmt die Reihenfolge innerhalb eines [Zuges](#zug); bei Gleichstand handelt der Held
zuerst.
Initiative ist bewusst eine Zahl *am Wesen* und keine Reihenfolgeliste — eine Liste wäre eine zweite
Sammlung von Verweisen, die beim Tod eines Gegners ungültig würde.
## Registry
Eine benannte Sammlung moddbarer Definitionen, geschlüsselt nach namespaced ID. Es gibt **acht**, und
das hier ist die kanonische Liste — sie ist über vier Tickets gewachsen und stand sonst nirgends
vollständig:
| Registry | TOML-Tabelle |
|---|---|
| [Gegenstände](#gegenstand) | `[[items]]` |
| [Slots](#slot) | `[[slots]]` |
| [Charakteroption-Kategorien](#charakteroption) | `[[categories]]` |
| [Charakteroptionen](#charakteroption) | `[[options]]` |
| [Fähigkeiten](#fähigkeit) | `[[abilities]]` |
| [Affixe](#affix) | `[[affixes]]` |
| [Quests](#quest) | `[[quests]]` |
| [Wesen](#wesen) | `[[creatures]]` |
Jede dieser Tabellen darf in jeder beliebigen `*.toml` eines [Mods](#mod) stehen.
Die Einstellungstabelle `[balance]` ist **keine** Registry: ein einzelner Block mit festem Schema, der
feldweise verschmilzt, statt als ganzer Eintrag ersetzt zu werden.
## Mod
Ein Ordner mit einer `manifest.toml` und beliebig vielen weiteren TOML-Dateien. Das Manifest gibt dem