docs: ticket 13 (script-engine) aufloesen, rhai gewaehlt, stufe c hinter 1.0, nachtrag an 01

This commit is contained in:
2026-08-12 13:00:50 +02:00
parent bed9ef5762
commit 380400611e
4 changed files with 170 additions and 6 deletions

View File

@@ -130,7 +130,18 @@ Eine Formel für alles: Basisangriff `scale = 1.0` · Feuerball `amount = 20` ·
`scale = 1.5` · Mischformen beides. Keine zwei Modi, kein Sonderfall für `core:attack`. `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 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. eine Aktion, die sie ausführt.
### Nachtrag aus Ticket 13: `run_script`
`{ action = "run_script", script = "meinmod:feuerregen" }` als **neunte Aktion**. Ein Script ist damit
überall verwendbar, wo eine Aktion stehen darf, und braucht keine eigenen Platzierungsregeln. Es gibt
Aktionen zurück, die in die Warteschlange aus 08 wandern — der Schreibpfad bleibt also genau einer.
### Der Katalog
`damage` · `change_attribute` · `apply_modifier` · `remove_modifier` · `give_item` · `remove_item` ·
`start_quest` (08) · `flee` (14) · `run_script` (13) — **neun**.
### Bewusst nicht entschieden ### Bewusst nicht entschieden

View File

@@ -1,7 +1,7 @@
# 13 — Script-Engine wählen und Sandbox härten # 13 — Script-Engine wählen und Sandbox härten
Type: grilling Type: grilling
Status: open Status: resolved
Blocked by: 01 Blocked by: 01
## Question ## Question
@@ -27,3 +27,146 @@ reichen sie tiefer in den Game-State, ist sie es nicht. Zu klären:
- **Wann fällt die Entscheidung endgültig** — jetzt, oder erst wenn Mod-Stufe (c) gebaut wird? - **Wann fällt die Entscheidung endgültig** — jetzt, oder erst wenn Mod-Stufe (c) gebaut wird?
Ergebnis: gewählte Engine mit Begründung, Härtungs-Checkliste, und die Regel, was Scripts sehen dürfen. Ergebnis: gewählte Engine mit Begründung, Härtungs-Checkliste, und die Regel, was Scripts sehen dürfen.
## Answer
Begriffe im [Glossar](../../../CONTEXT.md).
### Die Wahl ist eine Implementierungsentscheidung — deshalb fällt sie leicht
Scripts lesen über **eine Handvoll Funktionen** und schreiben ausschließlich über **Aktionen**. Weder
lesen sie direkt im `Game`-Baum noch halten sie Zustand außerhalb des Vokabulars.
Damit ist die Schnittstelle klein genug, um sie in jeder Engine nachzubauen — genau die Leitplanke aus
Ticket 05. Ein Engine-Wechsel kostet die Bindungsschicht, nicht die Architektur.
Die strikte Variante — Script bekommt Daten, gibt Aktionen zurück, liest gar nichts — wäre sauberer
gewesen und macht Scripts sinnlos: Was ein Script gegenüber einem Trigger *hinzufügt*, ist Rechnen
(„Schaden skaliert mit der Zahl der Untoten im Raum"), und dafür muss es lesen können.
### Rhai — unbequem begründet
**Für ein unveröffentlichtes Terminal-Roguelike schreibt niemand Mods.** Lua gewinnt bei der
Vertrautheit deutlich (Factorio, GMod, WoW, Noita, Don't Starve), aber das zahlt sich erst aus, wenn es
eine Modder-Gemeinde gibt — und die entsteht nicht durch die Sprachwahl, sondern durch ein Spiel, das
Leute spielen wollen. Bis dahin ist der Modder, den es zu bedienen gilt, der Autor selbst, und die
Charter nennt „Rust lernen" als halbe Motivation.
Dazu der schärfste Einzelbefund aus 05: Ein Lua-Script hat unter `StdLib::ALL_SAFE` per `io.open` eine
echte Datei gelesen — „safe" heißt dort speichersicher, nicht fähigkeitsbeschränkt. Rhai hat Datei-,
Netz- und Prozesszugriff gar nicht erst in der Standard-Engine.
**Mods gelten als vertrauenswürdig.** Kein Spiel dieser Größenordnung behandelt sie anders — Factorio,
Minecraft, Skyrim und Noita vertrauen alle. Der Spieler entpackt einen Ordner in sein Profil, dieselbe
Vertrauensentscheidung wie bei jeder heruntergeladenen Software; eine Sandbox schützt ihn nicht davor,
sondern verschiebt nur, wo er sie trifft. Damit scheidet Luau samt C++17-Toolchain und Lua-*Dialekt*
aus — der Dialekt, den die vertrauten Lua-Modder gerade nicht können.
### Härtung — gegen Unfälle, nicht gegen Absicht
```rust
engine.set_module_resolver(DummyModuleResolver::new()); // kein ./*.rhai von der Platte
engine.disable_symbol("eval"); // kein Code aus Strings
engine.set_max_operations(100_000); // CPU pro Aufruf
engine.set_max_call_levels(32); // Rekursion
engine.set_max_string_size(8_000);
engine.set_max_array_size(1_000);
engine.set_max_map_size(1_000);
engine.set_max_expr_depths(64, 32); // Parser-Tiefe
```
Alle Methodennamen gegen die Rhai-Doku geprüft. `on_progress` bleibt weg — es ist der Abbruchhaken für
lang laufende Scripts, und `set_max_operations` deckt denselben Fall mit einem Wert statt mit einer
Callback-Schleife ab.
**`set_max_operations` gehört ausdrücklich *nicht* in `[balance]`** aus Ticket 14: Ein Mod, der sein
eigenes CPU-Limit hochsetzen kann, invertiert die Härtung.
Ein Script, das an ein Limit stößt, ist ein **Eintrags-Fehler** nach der Regel aus 04: Warnzeile mit
Mod-ID, Script-Name und Zeile, Trigger verworfen, Spiel läuft weiter. Rhais Fehlermeldungen mit Zeile
*und* Spalte sind laut 05 genau dafür da.
### Die Schnittstelle — was Scripts sehen dürfen
```
attribute(wer, name) -> f64 has_tag(wer, tag) -> bool
health(wer) -> f64 has_item(wer, id) -> i64
stamina(wer) -> f64 floor() -> i64
rand() -> f64
```
Alle Leseoperationen sind an der Bedingungsgrammatik aus 08 gespiegelt — **Scripts sehen nichts, was
eine Bedingung nicht auch sähe**, sie können nur damit rechnen. Das ist die Regel; die Liste selbst
wird endgültig, wenn Stufe (c) gebaut wird.
Bewusst gestrichen: `turn()` und `enemy_count()`. Für beide gibt es kein benanntes Beispiel, und
`enemy_count()` deckt sein eigenes Motivbeispiel („Schaden skaliert mit der Zahl der Untoten im Raum")
nicht einmal ab — dafür bräuchte es Iteration. Die Umkehrbarkeit aus Q1 hängt daran, dass diese
Schnittstelle klein bleibt; sie auf Vorrat wachsen zu lassen, während die Stufe verschoben ist, wäre
das Gegenteil.
`health` und `stamina` sind **eigene** Funktionen statt `attribute("health")`, weil Ticket 01 scharf
trennt: `max_health` ist ein Attribut, der Lebensstand ist Zustand. Diese Trennung darf im Script-API
nicht verwaschen.
**`rand()` läuft durch den Generator aus `Game`**, und Rhais eigenes `rand`-Paket wird nicht
registriert. Sonst gilt „Nachladen würfelt nicht neu" aus Ticket 14 für alles außer für Scripts — und
ausgerechnet die interessanten Effekte würfelten beim Laden neu.
Geschrieben wird ausschließlich über Aktionen: Ein Script **gibt Aktionen zurück**, die in die
Warteschlange aus 08 wandern. Damit deckelt der 100-Events-Zähler auch ein Script, das sich selbst
aufruft.
### Scripts sind die elfte Registry
```toml
{ action = "run_script", script = "meinmod:feuerregen" }
```
`run_script` als **neunte Aktion**. Dadurch entstehen keine neuen Platzierungsregeln: Ein Script ist
überall verwendbar, wo eine Aktion stehen darf — an einem Gegenstand, einer Fähigkeit, einer
Questbelohnung, einem `ai`-Eintrag, einem Raum-Trigger. Dieselbe Bewegung wie `core:attack` in Ticket
11 und `flee` in 14: Was wie Engine-Logik aussieht, ist Inhalt.
Die Alternative — Scripts registrieren sich selbst auf Events — wäre genau die Variante, bei der ein
Script Zustand außerhalb des Vokabulars hält, und die macht die Engine-Wahl unumkehrbar.
### Risiken: beobachtet, mit benanntem Ausstiegssignal
„Beobachten" ohne Kriterium ist ein Wort ohne Folge, deshalb ausgeschrieben:
| Signal | Reaktion |
|---|---|
| Keine Rhai-Release in zwölf Monaten | wechseln — Stand 1.25.1 vom 29.05.2026, frisch, 3,4 Mio. Downloads in 90 Tagen |
| Unsoundness in einem Pfad, den wir tatsächlich nutzen | wechseln — Issue #894 ist das bisher nicht |
| `smartstring` unmaintained (RUSTSEC-2026-0249) | **kein** Ausstiegsgrund |
Zu `smartstring`: Das Repo wurde am 03.05.2026 archiviert, die Advisory kam am 10.08.2026 und ist ein
INFO-Eintrag über fehlende Wartung, keine Verwundbarkeit (genannte Alternativen: `compact_str`,
`smol_str`). Rhai 1.25.1 erschien danach ohne Wechsel. `cargo audit` wird das melden, und das ist die
angemessene Menge Ärger dafür.
### Stufe (c) wandert hinter 1.0
Die **Entscheidung** steht jetzt, die **Umsetzung** nicht.
Stufen (a) und (b) sind durch 01–03 vollständig beschrieben und liefern bereits zehn Registries,
Trigger, Bedingungen, Affixe und `[balance]` — sehr viel Moddbarkeit ohne eine Zeile Script. Stufe (c)
ist die einzige, die eine neue Dependency, eine Bindungsschicht und eine Härtungsliste braucht.
Ticket 02 hat die Regel selbst formuliert: „Braucht ein Gegenstand etwas, das Modifikator, Aktion oder
Fähigkeit nicht ausdrücken können, ist das eine Lücke im Vokabular." Eine Script-Engine einzubauen,
*bevor* jemand an diese Lücke gestoßen ist, baut die Antwort vor der Frage — und jede Lücke, die ein
Script stopfen würde, ist zuerst ein Hinweis, dass das Vokabular sie stopfen sollte.
Die Entscheidung jetzt zu treffen kostet dagegen nichts und legt fest, **wogegen** das Vokabular gebaut
wird. Genau das ist die Leitplanke aus 05.
**Folge: Die Destination der Map ändert sich** — 1.0 liefert Mod-Stufen (a) und (b), Stufe (c) kommt
danach.
### Nachtrag an Ticket 01
`run_script` als **neunte Aktion**. Der Katalog hat damit: `damage`, `change_attribute`,
`apply_modifier`, `remove_modifier`, `give_item`, `remove_item`, `start_quest` (08), `flee` (14),
`run_script` (13).

View File

@@ -21,7 +21,11 @@ dort als Konvention im Body. Zuordnung Ticket → Gitea-Issue:
Alle Architektur- und Design-Entscheidungen für rustkeep 1.0 sind getroffen — nicht gebaut, entschieden. Alle Architektur- und Design-Entscheidungen für rustkeep 1.0 sind getroffen — nicht gebaut, entschieden.
1.0 heißt: 5 Etagen mit Boss, Solo-Held mit Rasse und Klasse, Grid-Erkundung mit Menü-Kampf, 1.0 heißt: 5 Etagen mit Boss, Solo-Held mit Rasse und Klasse, Grid-Erkundung mit Menü-Kampf,
Inventar und Ausrüstung, Quests, NPCs, Handel, Speichern/Laden, Mod-Support auf drei Stufen. Inventar und Ausrüstung, Quests, NPCs, Handel, Speichern/Laden, **Mod-Support auf den Stufen (a) und
(b)**.
Stufe (c) — Scripting — ist entschieden, aber nicht Teil von 1.0: Engine (Rhai) und Schnittstelle
stehen, gebaut wird sie, wenn ein Modder an eine Lücke im Vokabular stößt (13).
## Notes ## Notes
@@ -44,7 +48,7 @@ Feststehende Rahmenentscheidungen aus der Charter-Session:
| Etagen | Hybrid — Raum-Prefabs in TOML, prozedural zusammengesetzt | | Etagen | Hybrid — Raum-Prefabs in TOML, prozedural zusammengesetzt |
| Daten | Alles TOML, zur Laufzeit geladen. `include_str!` und `enum ItemType` fallen | | Daten | Alles TOML, zur Laufzeit geladen. `include_str!` und `enum ItemType` fallen |
| Mod-IDs | Namespaced (`core:iron_sword`), Präfix aus dem Manifest und automatisch vorangestellt. Ladereihenfolge aus einer Spielerliste, nicht aus dem Manifest (03). Das Kernspiel ist selbst ein Mod | | Mod-IDs | Namespaced (`core:iron_sword`), Präfix aus dem Manifest und automatisch vorangestellt. Ladereihenfolge aus einer Spielerliste, nicht aus dem Manifest (03). Das Kernspiel ist selbst ein Mod |
| 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. *13: Rhai gewählt, Umsetzung aber hinter 1.0 — gebaut wird sie, wenn das Vokabular an eine Grenze stößt* |
| 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 `String`-IDs (keine Indizes, 07). `Registries` liegt daneben, nicht darin. Kein ECS | | Weltzustand | `Game`-Ownership-Baum, Verweise über `String`-IDs (keine Indizes, 07). `Registries` liegt daneben, nicht darin. Kein ECS |
@@ -71,6 +75,7 @@ auch über Ticket 13.
- [10 — Raum-Prefab-Format](issues/10-raum-prefab-format.md) ([#11](https://gitlab.hive-brain.de/MDietermann/rustkeep/issues/11)) — Freie Platzierung mit L-Korridoren; verbunden wird **Raum `i` mit Raum `i−1`**, weil eine Kette Erreichbarkeit *strukturell* garantiert statt sie nachtraeglich zu pruefen. Prefab als ASCII-Block mit Markern im Block, **Tueren explizit** (sonst stanzt der Generator Loecher in gezeichnete Waende); Rotation und Spiegelung erlaubt, `rotate = false` als Opt-out. **Ein `e` sagt „hier steht ein Gegner", nicht welcher** — das entscheidet die Etage, sonst waere jedes Prefab an eine Schwierigkeitsstufe gebunden. `[[prefabs]]` und `[[floors]]` sind die neunte und zehnte Registry; Auswahl durchgehend ueber **Tags**, Pflichtraeume zuerst platziert (das *ist* die Garantie), Flaechenpruefung beim Laden. **Kampf beginnt bei `room_entered`**, Gegner gehoeren zum Raum — das erspart Pfadsuche, Sichtlinien und Verfolgung. `Floor` mit `rooms`, `player_room: Option<usize>` (Korridor ist ein echter Zustand). **Siegbedingung erstmals definiert**: kein `[[floors]]` mit der naechsten Nummer = gewonnen, die Zahl „5" steht nirgends im Code. Nachtraege: `room_entered` an 08 (zwoelf Events), `GameOver { outcome }` an 14. - [10 — Raum-Prefab-Format](issues/10-raum-prefab-format.md) ([#11](https://gitlab.hive-brain.de/MDietermann/rustkeep/issues/11)) — Freie Platzierung mit L-Korridoren; verbunden wird **Raum `i` mit Raum `i−1`**, weil eine Kette Erreichbarkeit *strukturell* garantiert statt sie nachtraeglich zu pruefen. Prefab als ASCII-Block mit Markern im Block, **Tueren explizit** (sonst stanzt der Generator Loecher in gezeichnete Waende); Rotation und Spiegelung erlaubt, `rotate = false` als Opt-out. **Ein `e` sagt „hier steht ein Gegner", nicht welcher** — das entscheidet die Etage, sonst waere jedes Prefab an eine Schwierigkeitsstufe gebunden. `[[prefabs]]` und `[[floors]]` sind die neunte und zehnte Registry; Auswahl durchgehend ueber **Tags**, Pflichtraeume zuerst platziert (das *ist* die Garantie), Flaechenpruefung beim Laden. **Kampf beginnt bei `room_entered`**, Gegner gehoeren zum Raum — das erspart Pfadsuche, Sichtlinien und Verfolgung. `Floor` mit `rooms`, `player_room: Option<usize>` (Korridor ist ein echter Zustand). **Siegbedingung erstmals definiert**: kein `[[floors]]` mit der naechsten Nummer = gewonnen, die Zahl „5" steht nirgends im Code. Nachtraege: `room_entered` an 08 (zwoelf Events), `GameOver { outcome }` an 14.
- [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. - [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. - [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.
- [13 — Script-Engine wählen](issues/13-script-engine-waehlen.md) ([#14](https://gitlab.hive-brain.de/MDietermann/rustkeep/issues/14)) — **Rhai**, aber die Wahl ist dank der Leitplanke aus 05 nur noch eine *Implementierungs*-Entscheidung: Scripts lesen ueber eine Handvoll Funktionen (gespiegelt an der Bedingungsgrammatik aus 08 — sie sehen nichts, was eine Bedingung nicht auch saehe) und schreiben ausschliesslich ueber Aktionen. Begruendung unbequem, aber ehrlich — **fuer ein unveroeffentlichtes Terminal-Roguelike schreibt niemand Mods**, der Lua-Vorsprung zahlt sich erst mit einer Gemeinde aus. Mods gelten als **vertrauenswuerdig** (wie bei Factorio, Minecraft, Skyrim), gehaertet wird gegen Unfaelle: acht Engine-Schalter, gegen die Doku geprueft; `set_max_operations` ausdruecklich **nicht** in `[balance]`, sonst hebt ein Mod sein eigenes Limit auf. `rand()` laeuft durch den Seed aus 14, sonst wuerfelten ausgerechnet Scripts beim Laden neu. `run_script` als neunte Aktion, Scripts als **elfte Registry**. Risiken beobachtet mit benanntem Ausstiegssignal; `smartstring` ist ein INFO-Advisory, kein Ausstiegsgrund. **Stufe (c) wandert hinter 1.0** — eine Script-Engine zu bauen, bevor jemand an eine Vokabular-Luecke gestossen ist, baut die Antwort vor der Frage.
- [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. - [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 ## Not yet specified

View File

@@ -210,8 +210,8 @@ Speicherstand abgelehnt.
## Registry ## Registry
Eine benannte Sammlung moddbarer Definitionen, geschlüsselt nach namespaced ID. Es gibt **zehn**, und Eine benannte Sammlung moddbarer Definitionen, geschlüsselt nach namespaced ID. Es gibt **elf**, und
das hier ist die kanonische Liste — sie ist über fünf Tickets gewachsen und stand sonst nirgends das hier ist die kanonische Liste — sie ist über sechs Tickets gewachsen und stand sonst nirgends
vollständig: vollständig:
| Registry | TOML-Tabelle | | Registry | TOML-Tabelle |
@@ -226,12 +226,17 @@ vollständig:
| [Wesen](#wesen) | `[[creatures]]` | | [Wesen](#wesen) | `[[creatures]]` |
| [Prefabs](#prefab) | `[[prefabs]]` | | [Prefabs](#prefab) | `[[prefabs]]` |
| [Etagen](#etage) | `[[floors]]` | | [Etagen](#etage) | `[[floors]]` |
| Scripts | `[[scripts]]` |
Jede dieser Tabellen darf in jeder beliebigen `*.toml` eines [Mods](#mod) stehen. 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 Die Einstellungstabelle `[balance]` ist **keine** Registry: ein einzelner Block mit festem Schema, der
feldweise verschmilzt, statt als ganzer Eintrag ersetzt zu werden. feldweise verschmilzt, statt als ganzer Eintrag ersetzt zu werden.
`[[scripts]]` gehört zu Mod-Stufe (c) und ist entschieden, aber noch nicht gebaut — Scripts lesen über
neun Funktionen und schreiben ausschließlich über [Aktionen](#aktion), damit die Engine austauschbar
bleibt.
## Mod ## Mod
Ein Ordner mit einer `manifest.toml` und beliebig vielen weiteren TOML-Dateien. Das Manifest gibt dem Ein Ordner mit einer `manifest.toml` und beliebig vielen weiteren TOML-Dateien. Das Manifest gibt dem