docs: ticket 03 (mod-ladepipeline) aufloesen, mod ins glossar, vorgaben an tickets 04 und 10
This commit is contained in:
@@ -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`.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user