docs: ticket 03 (mod-ladepipeline) aufloesen, mod ins glossar, vorgaben an tickets 04 und 10

This commit is contained in:
2026-08-12 09:21:45 +02:00
parent 5c4543da1a
commit d2d5ae6d8c
5 changed files with 175 additions and 3 deletions

View File

@@ -1,7 +1,7 @@
# 03 — Mod-Ladepipeline und Manifest
Type: grilling
Status: open
Status: resolved
Blocked by: 02
## Question
@@ -18,3 +18,146 @@ Das Kernspiel ist `core:` und damit selbst ein Mod. Zu klären:
- Wird beim Start alles geladen, oder gibt es ein Nachladen zur Laufzeit?
Ergebnis: Ordnerlayout, Manifest-Schema und die Phasen der Ladepipeline.
## Answer
Begriffe im [Glossar](../../../CONTEXT.md).
### Ordnerlayout — zwei Suchpfade
`mods/` neben der Binary ist die Auslieferung und enthält `core/`. `data_dir()/rustkeep/mods` im
Benutzerprofil ist der Ort, an dem die Mods des Spielers eine Neuinstallation überleben. Der Pfad
kommt aus der Crate `directories` (6.0.0) — acht handgerollte Zeilen hätten Windows und Linux
bedient, `directories` nimmt macOS mit.
Liegt dieselbe Mod-ID in beiden Pfaden, **gewinnt das Profil**, mit Warnzeile. Das ist die Richtung,
in die der zweite Pfad zeigt: Er existiert, damit die Sachen des Spielers bleiben, und dann darf die
Neuinstallation sie nicht stumm überstimmen. Es gilt auch für `core` — überschreibbar und
protokolliert. Die Warnzeile ist die einzige Diagnose, die je zwischen „mein Mod tut nichts" und
einer vergessenen alten Kopie im Profil unterscheidet.
**`core` ist ein gewöhnlicher Mod-Ordner auf der Platte**, kein privilegierter Sonderfall.
`include_str!` entfällt. Bliebe das Kernspiel einkompiliert, wäre die Mod-Fähigkeit nie getestet und
jeder Bug im Lader träfe ausschließlich die Modder.
`load_order.toml` liegt ebenfalls in `data_dir()`. Die XDG-Trennung von Daten und Konfiguration
kostet einen zweiten Pfad, einen zweiten „existiert nicht"-Fall und einen Ordner, den der Spieler
suchen muss — für eine einzelne Textdatei.
### Manifest — `manifest.toml`, Pflicht
```toml
id = "meinmod" # Namensraum-Präfix und Mod-ID, unabhängig vom Ordnernamen
name = "Mein Mod"
version = "1.0.0"
depends = ["core"] # nur Namen, keine Versionsbereiche
```
TOML, nicht YAML: `serde_yaml` ist seit dem 25.03.2024 archiviert und unmaintained, der
Nachfolge-Fork `serde_yml` ebenfalls — nach einer Unsoundness im Serializer (RUSTSEC-2025-0068).
Übrig bleiben junge Crates (`serde-saphyr`, `noyalib`, `yaml-rust2`). Für den Parser, der bei
*jedem* Mod als erstes läuft, ist das eine schlechtere Basis als das ohnehin vorhandene `toml`.
Dazu käme eine zweite Fehlermeldungssprache, die Ticket 04 mit abfangen müsste. YAMLs Stärke ist
tiefe Verschachtelung; das Manifest hat vier flache Felder.
`id` ist unabhängig vom Ordnernamen — das Manifest ist die Quelle, und Phase 1 liest ohnehin alle
Manifeste, bevor sortiert wird. `depends` trägt keine Versionsbereiche: Semver-Matching ist ein
Parser plus eine Konfliktauflösung für ein Problem, das erst existiert, wenn zwei Versionen desselben
Mods im Umlauf sind. `version` ist bis dahin Anzeige und Save-Header. `description` und `author`
kommen, wenn es einen Mod-Screen gibt, der sie anzeigt.
Ein Ordner ohne Manifest ist kein Mod: Warnzeile, übersprungen, kein Fehler — sonst legt eine
vergessene `screenshots/`-Kopie das Spiel lahm.
### Ladereihenfolge — Spielerliste, nicht Topo-Sort
`load_order.toml` listet Manifest-IDs in Reihenfolge und ist selbstheilend:
| Fall | Verhalten |
|---|---|
| Datei fehlt | wird erzeugt: `core` zuerst, Rest alphabetisch |
| Ordner auf der Platte, nicht in der Liste | hinten angehängt, Datei zurückgeschrieben |
| Eintrag in der Liste, kein Ordner | Warnzeile, übersprungen |
Ein entpackter Mod läuft damit, ohne dass der Spieler von der Existenz der Sortierdatei weiß, und
ein deinstallierter hindert das Spiel nicht am Start. Kein Sortier-Screen für 1.0 — die Datei *ist*
die UI.
**`depends` sortiert nicht, es prüft.** Fehlende Abhängigkeit oder falsche Position sind harte
Startfehler im Klartext: „`meinmod` muss nach `magiemod` geladen werden, steht aber davor". Das ist
der Preis dafür, dass ein Mensch die Reihenfolge pflegt — ohne die Prüfung merkt er den Fehler erst
an einer Meldung über eine unbekannte ID, die auf die Symptomdatei zeigt statt auf die Ursache.
Ein Mod, dessen Voraussetzung fehlt, produziert sonst zehn Folgefehler.
### Dateien im Mod
Jede `*.toml` außer dem Manifest wird eingelesen, und jede Datei darf jede Registry-Tabelle enthalten
(`[[items]]`, `[[slots]]`, `[[categories]]`, `[[options]]`, `[[abilities]]`). Keine festen
Dateinamen — der Modder sortiert selbst, und ein großer Mod muss seine 4000 Gegenstände nicht in
eine Datei zwingen.
Das ist *weniger* Code, nicht mehr: eine Struct mit fünf optionalen `Vec`-Feldern, über jede
gefundene Datei deserialisiert, Ergebnisse angehängt. Feste Namen bräuchten dieselbe Struct plus
eine Namensliste plus Antworten auf „Datei fehlt" und „unbekannte Datei liegt daneben".
### IDs und Überschreiben
Der Lader stellt `manifest.id` automatisch voran: `id = "schwert"` wird zu `meinmod:schwert`.
Überschrieben wird über ein Feld am Eintrag:
```toml
[[items]]
id = "eisenschwert"
overrides = "core:iron_sword"
```
Der Eintrag landet **nur** unter `core:iron_sword`; `meinmod:eisenschwert` wird nicht registriert.
Der Zweck des Überschreibens ist, dass bestehende Verweise weiterfunktionieren — Loottabellen,
`on_create`-Startausrüstung und Questbelohnungen zeigen auf `core:iron_sword` und liefern ab sofort
das neue Schwert. Beide IDs zu registrieren hieße, denselben Gegenstand zweimal in der Welt zu haben:
doppelt im Händlerbestand, doppelt in jeder Registry-Schleife.
Zeigt `overrides` ins Leere — Zielmod nicht installiert oder vertippt —, gibt es eine Warnzeile und
der Eintrag wird verworfen. Ihn ersatzweise unter der eigenen ID zu registrieren wäre schlimmer als
nichts zu tun: Der Modder wollte etwas ersetzen und bekäme stumm einen zusätzlichen Gegenstand
in die Welt gelegt.
Überschreiben zwei Mods dasselbe, entscheidet die Ladereihenfolge — der letzte gewinnt. `overrides`
ist ein Feld an *jedem* Registry-Eintrag, nicht nur an Gegenständen.
### Drei Phasen
1. **Manifeste** — alle Ordner beider Pfade scannen, `manifest.toml` lesen, Reihenfolge aus
`load_order.toml` bestimmen, `depends` prüfen.
2. **Einlesen** — alle Dateien in Ladereihenfolge, Einträge nach ID in die Registries, spätere
überschreiben frühere. Hier wird *nichts* aufgelöst. Dadurch gibt es keine Vorwärtsreferenzen:
Ein Mod darf auf etwas zeigen, das erst ein späterer liefert.
3. **Auflösen** — alle Referenzen prüfen: Gegenstand → Slot, Charakteroption → Fähigkeit,
Aktion → Gegenstand.
**Fehlereinheit ist der einzelne Eintrag**, nicht der Mod und nicht das Spiel. Ein Tippfehler in
einem von 200 Gegenständen darf die anderen 199 nicht mitreißen — beim Kernspiel wäre es der
Totalausfall. Was genau eine kaputte Referenz auslöst, entscheidet Ticket 04.
**Kein Nachladen zur Laufzeit.** Nachladen hieße, dass jede laufende Referenz auf einen
Registry-Eintrag ungültig werden kann, mitten im Kampf — das ist kein Feature, sondern eine
Invalidierungs-Architektur, die den `Game`-Baum aus Ticket 07 mitbestimmt. Neustart ist die
Reload-Taste, und ein Terminalspiel startet in Millisekunden.
### Auflagen an andere Tickets
- **04 Fehlerstrategie**: erbt den Katalog Warnung-vs-Abbruch. Die harten Fälle stehen fest —
`depends` fehlend oder verletzt. Alles andere ist bislang Warnung plus Eintrag verwerfen; 04 muss
das zu einer Regel machen und entscheiden, wo die Warnungen landen, wenn der Bildschirm ratatui
gehört.
- **09 Save-Schema**: Der Mod-Header nutzt `manifest.id` und `version`. Ohne Versionsbereiche in
`depends` ist `version` dort das einzige Signal, dass ein Mod sich geändert hat — und damit
09 zugleich sein *einziger* Leser in 1.0. Fällt der Header, fällt das Feld mit.
- **10 Raum-Prefab-Format**: Prefabs sind eine weitere Registry über dieselbe Pipeline, kein eigener
Lader — sie erben Namensraum, `overrides` und die drei Phasen.
### Folgen für bestehenden Code
`include_str!` und `load_items()` entfallen zugunsten des Mod-Laders. `mods/core/` entsteht,
`weapons.toml` und `armors.toml` ziehen dorthin (verschmolzen laut Ticket 02). `directories` kommt
als Dependency in `Cargo.toml`.

View File

@@ -21,3 +21,14 @@ ist fehlerhaftes TOML der Normalfall, nicht der Ausnahmefall. Zu klären:
- Wie kommt die Herkunft in die Meldung — Datei, Zeile, Mod-ID?
Ergebnis: Fehlerklassifikation, Reaktion je Klasse, und die gewählte Rust-Fehlerbehandlung mit Begründung.
## Vorgabe aus Ticket 03
[Ticket 03](03-mod-ladepipeline.md) klassifiziert seine eigenen Fälle bereits durch: **hart** ist
allein `depends` fehlend oder in der Reihenfolge verletzt, alles Übrige ist Warnung plus Überspringen.
**Fehlereinheit ist der einzelne Eintrag**, nicht der Mod und nicht das Spiel.
Offen bleibt damit: kaputte TOML-Syntax (die Datei ist unlesbar, die Einheit „Eintrag" greift nicht),
fehlende Pflichtfelder, unaufgelöste Referenzen aus Phase 3 — und wohin die Warnungen gehen, wenn der
Bildschirm ratatui gehört. Ein Ladelauf produziert leicht ein Dutzend, und `println!` ist nach dem
TUI-Start keine Option mehr.

View File

@@ -20,3 +20,10 @@ Entschieden ist der Hybrid: Modder bauen Prefabs, der Generator setzt prozedural
- Wie greift ein Mod ein — nur neue Prefabs hinzufügen, oder auch Generator-Parameter ändern?
Ergebnis: Prefab-Schema mit Beispiel plus die Regeln, nach denen der Generator zusammensetzt.
## Vorgabe aus Ticket 03
Prefabs sind eine **weitere Registry über dieselbe Ladepipeline**, kein eigener Lader — das
beantwortet die letzte Frage oben halb: Hinzufügen ist der Normalfall, Ersetzen geht über `overrides`.
Offen bleibt, ob **Generator-Parameter** (Etagen-Tabellen, Gewichtungen, Pflichträume) ebenfalls
Registry-Einträge sind — dann mit derselben Maschine moddbar — oder ein Sonderfall.