diff --git a/.scratch/rustkeep-1.0/issues/05-research-script-engine.md b/.scratch/rustkeep-1.0/issues/05-research-script-engine.md index 84f2bdd..027b50c 100644 --- a/.scratch/rustkeep-1.0/issues/05-research-script-engine.md +++ b/.scratch/rustkeep-1.0/issues/05-research-script-engine.md @@ -1,7 +1,7 @@ # 05 — Research: Script-Engine für Mod-Stufe (c) Type: research -Status: open +Status: resolved Blocked by: — ## Question @@ -19,3 +19,33 @@ Fakten sammeln, keine Entscheidung treffen. Zu klären: - Reifegrad: Wartungsstand, Release-Frequenz, Größe des Ökosystems, Beispiele aus echten Spielen. Ergebnis: Vergleichstabelle plus Empfehlung mit Begründung; die Entscheidung fällt der Mensch später. + +## Answer + +Vollständiger Bericht: [`research/05-script-engine.md`](../research/05-script-engine.md) +(209 Zeilen, geprüft am 2026-08-11). **Empfehlung: Rhai** — begründet, nicht entschieden. + +- **Fehlermeldungen**: Rhai liefert Zeile *und* Spalte plus Call-Chain. Passt direkt zur + `Result`-Fehlerstrategie aus Ticket 04. +- **Typ-Brücke**: `#[derive(CustomType)]`, nur `Clone` nötig — gegen `impl UserData` pro Feld bei mlua. +- **Hot-Reload**: AST-Tausch bei überlebendem `Scope`. +- **Build**: kein `cc`, kein `libc` im Baum. Gemessen 44 Dependencies und 45,5 s gegen 166/304 Deps und + 262 s/356 s bei den WASM-Kandidaten. +- **Sandbox, der schärfste Einzelbefund**: Ein Lua-Script hat auf dieser Maschine unter `Lua::new()` + **und** unter `StdLib::ALL_SAFE` per `io.open` eine echte Datei gelesen. `ALL_SAFE` ist `(1<<30)-1` + und meint *speichersicher*, nicht *fähigkeitsbeschränkt*. Rhai hat Datei-, Netz- und Prozesszugriff + gar nicht erst in der Standard-Engine. Beide brauchen Härtung, bei Rhai kostet ein vergessener + Handgriff weniger. Rhais Defaults sind aber auch nicht harmlos: `FileModuleResolver` lädt `./*.rhai` + von der Platte, `eval()` ist an, CPU- und Memory-Limits sind aus. +- **Gegenseite, ehrlich**: Lua gewinnt bei der Vertrautheit deutlich (Factorio, GMod, WoW, Teardown, + Noita, Don't Starve, Luanti, Dota 2). Ist das Ziel „möglichst viele fremde Mods" statt „gut moddbar + für einen kleinen Kreis", kippt die Empfehlung. +- **Rhai-Risiken**: Bus-Faktor 1; Unsoundness-Issue #894 offen seit 2024; Pflicht-Dependency + `smartstring` seit 2026-08-10 unmaintained (RUSTSEC-2026-0249); VM-Rewrite im Gang. +- **WASM scheidet für 1.0 aus** — nicht technisch (beste Sandbox), sondern wegen Compile-Pflicht für + Modder, Fehlermeldungen ohne Quellzeile und Build-Overhead. Falls später doch: `wasmi`, nicht wasmtime. +- **Nicht verifizierbar und so gekennzeichnet**: kein ausgeliefertes Spiel mit Rhai-Modding, kein + Rust-Spiel mit mlua, kein kommerzielles Spiel mit WASM-Modding auffindbar. + +**Folge für die Map**: Reden Scripts ausschließlich über das Effekt-Vokabular, ist die Engine-Wahl +weit umkehrbarer als sie aussieht. Die Entscheidung selbst ist zu Ticket 13 graduiert und hängt an 01. diff --git a/.scratch/rustkeep-1.0/issues/13-script-engine-waehlen.md b/.scratch/rustkeep-1.0/issues/13-script-engine-waehlen.md new file mode 100644 index 0000000..88d1372 --- /dev/null +++ b/.scratch/rustkeep-1.0/issues/13-script-engine-waehlen.md @@ -0,0 +1,29 @@ +# 13 — Script-Engine wählen und Sandbox härten + +Type: grilling +Status: open +Blocked by: 01 + +## Question + +Rhai oder mlua — und wie wird die gewählte Engine abgedichtet? + +Ticket 05 hat die Fakten geliefert und Rhai empfohlen. Die Entscheidung steht noch aus, und sie hängt +bewusst an 01: Reden Scripts ausschließlich über das Effekt-Vokabular, ist die Wahl später umkehrbar; +reichen sie tiefer in den Game-State, ist sie es nicht. Zu klären: + +- **Ist das Effekt-Vokabular die einzige Schnittstelle für Scripts?** Wenn ja, ist die Engine-Wahl eine + Implementierungsentscheidung statt einer Architekturentscheidung. +- **Zielgruppe**: „gut moddbar für einen kleinen Kreis" spricht für Rhai, „möglichst viele fremde Mods" + für Lua. Das ist die Frage, an der die Empfehlung kippt. +- **Vertraust du fremden Mods?** Bei feindlichen Mods ist Luau der einzige echte Sandbox — zum Preis + einer C++17-Toolchain und eines Lua-*Dialekts*. +- **Härtungs-Checkliste** für die gewählte Engine festlegen. Bei Rhai mindestens: `FileModuleResolver` + abschalten, `eval()` deaktivieren, CPU- und Memory-Limits setzen. Bei mlua: niemals `Lua::new()`, + sondern `new_with` mit ausgewählten Libs, `load`/`loadstring`/`dofile`/`require` entfernen, + `ChunkMode::Text` erzwingen. +- **Wie geht das Projekt mit den Rhai-Risiken um** — Bus-Faktor 1, Unsoundness-Issue #894 seit 2024, + `smartstring` unmaintained (RUSTSEC-2026-0249)? Akzeptiert, beobachtet, oder Ausschlusskriterium? +- **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. diff --git a/.scratch/rustkeep-1.0/map.md b/.scratch/rustkeep-1.0/map.md index 60f588b..fc41903 100644 --- a/.scratch/rustkeep-1.0/map.md +++ b/.scratch/rustkeep-1.0/map.md @@ -14,6 +14,7 @@ dort als Konvention im Body. Zuordnung Ticket → Gitea-Issue: | 04 Fehlerstrategie | [#5](https://gitlab.hive-brain.de/MDietermann/rustkeep/issues/5) | 09 Save-Schema | [#10](https://gitlab.hive-brain.de/MDietermann/rustkeep/issues/10) | | 05 Research Script-Engine | [#6](https://gitlab.hive-brain.de/MDietermann/rustkeep/issues/6) | 10 Raum-Prefab-Format | [#11](https://gitlab.hive-brain.de/MDietermann/rustkeep/issues/11) | | 11 UI-Gerüst | [#12](https://gitlab.hive-brain.de/MDietermann/rustkeep/issues/12) | 12 Prototyp ratatui | [#13](https://gitlab.hive-brain.de/MDietermann/rustkeep/issues/13) | +| 13 Script-Engine wählen | [#14](https://gitlab.hive-brain.de/MDietermann/rustkeep/issues/14) | | | ## Destination @@ -46,10 +47,15 @@ Feststehende Rahmenentscheidungen aus der Charter-Session: | 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 | +**Leitplanke aus Ticket 05**: Scripts reden ausschließlich über das Effekt-Vokabular, nie direkt mit dem +Game-State. Solange das gilt, ist die Script-Engine austauschbar — Ticket 01 entscheidet damit indirekt +auch über Ticket 13. + ## Decisions so far +- [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). ## Not yet specified diff --git a/.scratch/rustkeep-1.0/research/05-script-engine.md b/.scratch/rustkeep-1.0/research/05-script-engine.md new file mode 100644 index 0000000..b1e603b --- /dev/null +++ b/.scratch/rustkeep-1.0/research/05-script-engine.md @@ -0,0 +1,251 @@ +# 05 — Research: Script-Engine für Mod-Stufe (c) + +Stand: 2026-08-11. Antwort auf `issues/05-research-script-engine.md`. +**Keine Entscheidung — Fakten plus begründete Empfehlung.** + +## Methodik + +Zwei Arten von Aussagen, überall getrennt gekennzeichnet: + +- **[gemessen]** — auf dieser Maschine ausgeführt: `cargo 1.96.0` / `rustc 1.96.0`, + Host `x86_64-pc-windows-msvc`, LLVM 22.1.2. Versionen live über `cargo generate-lockfile` + gegen crates.io aufgelöst, Dependency-Zahlen aus `Cargo.lock` (`name =`-Einträge, gleiche + Methode für alle Kandidaten), Build-Zeiten mit `Measure-Command` über `cargo build` (debug, + leerer `fn main(){}`, eigener `CARGO_TARGET_DIR`). +- **[Quelle]** — aus Primärquelle zitiert, URL jeweils direkt dabei und in der Quellenliste. + +Was nicht verifiziert werden konnte, steht explizit unter „Nicht verifiziert". + +## Aufgelöste Versionen [gemessen, 2026-08-11] + +| Crate | Aufgelöste Version | Release-Datum [Quelle] | +|---|---|---| +| `rhai` | **1.25.1** | 2026-05-29 — crates.io API | +| `mlua` (`lua54`, `vendored`) | **0.12.0** | 2026-07-05 — crates.io API | +| `wasmtime` | **47.0.3** | 2026-07-31 — crates.io API | +| `wasmi` | **1.1.0** | 2026-06-12 — crates.io API | +| `extism` | **1.30.0** | 2026-06-04 — crates.io API | + +--- + +## Vergleichstabelle + +| Kriterium | **Rhai 1.25.1** | **Lua 5.4 via mlua 0.12** | **WASM (wasmtime 47 / extism 1.30 / wasmi 1.1)** | +|---|---|---|---| +| **C-Toolchain unter Windows** | Keine. Kein `cc`, `libc`, `bindgen` oder `pkg-config` im Baum — reines Rust. | Ja, aber faktisch gratis: `vendored` kompiliert die Lua-C-Quellen über `cc`. [gemessen] hat `mlua-sys` hier eine echte `lua5.4.lib` erzeugt, **ohne dass `cl.exe` im PATH lag** — das `cc`-Crate findet MSVC über `vswhere` (VS 18.6 installiert). Der MSVC-Linker ist für den `*-windows-msvc`-Rust-Toolchain ohnehin Pflicht. | `wasmtime` hat `cc` als Build-Dep, praktisch aber irrelevant (msvc = Tier 1). `wasmi` ist reines Rust und `no_std`-fähig. | +| **Dependencies (Cargo.lock)** [gemessen] | **44** | **31** | `wasmi` **29** · `wasmtime` **166** · `extism` **304** | +| **Cold-Build debug** [gemessen] | **45,5 s** | **17,2 s** (erster Lauf mit kaltem Registry-Cache: 29,1 s) | `wasmi` **24,5 s** · `wasmtime` **262,5 s** · `extism` **356,1 s** (letztere zwei mit kaltem Registry-Cache, enthalten Download) | +| **MSRV** | 1.66 [Quelle: lib.rs] | 1.88 [Quelle: lib.rs] | `wasmi` 1.86 · `wasmtime` **1.95** (Edition 2024) — aggressiv, erzwingt Rust-Upgrades | +| **Typ-Brücke zu Rust-Structs** | `#[derive(CustomType)]` oder `CustomType`+`TypeBuilder`. **Einzige Anforderung: `Clone`** (mit `sync`-Feature zusätzlich `Send+Sync`). Mutable Game-State über `Rc>`-Closures (Control-Layer-Pattern) oder `&mut`-Erstargument. | `impl UserData` mit `add_field_method_get/set`, `add_method`, `add_method_mut`, `add_meta_method`. Mehr Zeremonie als Rhai, dafür sehr ausgereift. Für `!Send`/nicht-`'static`-Typen braucht es `Lua::scope`. Serde-Brücke über Feature `serialize`. | Am teuersten. Core-WASM kennt nur i32/i64/f32/f64 + Linear Memory — **jeder Struct muss serialisiert werden**. Extism: `#[plugin_fn] fn f(Json(x): Json) -> FnResult>` (wenig Boilerplate, JSON-Kosten pro Call). wasmtime: WIT-IDL + `wit-bindgen` + `Guest`-Impls, Aufwand ähnlich protobuf. Kein Shared Memory — Daten werden kopiert. | +| **Sandbox — Default** | **Gut, aber zwei Fallen.** Keine Datei-/Netz-/Prozess-Funktionen in irgendeinem der 15 Built-in-Packages; Dateizugriff ist ein separates Opt-in-Crate (`rhai-fs`). ABER: (1) der Default-`FileModuleResolver` **lädt `./*.rhai` von der Platte** — `import "evil"` funktioniert auf einer blanken `Engine::new()`; (2) `eval()` ist eingebaut und aktiv. | **Schlecht.** [gemessen] Auf `Lua::new()` **und** auf `StdLib::ALL_SAFE` sind `io`, `os`, `package`, `require`, `dofile`, `load` vorhanden; `io.open` hat hier eine echte Datei gelesen, `os.execute` ist erreichbar. `ALL_SAFE` = `(1<<30)-1` und schließt nur `debug` und `ffi` aus — es bedeutet *speichersicher*, **nicht** *fähigkeitsbeschränkt*. | **Am stärksten.** „no raw access to system calls or other forms of I/O; the only thing a WebAssembly instance can do is what is available through interfaces it has been explicitly linked with." Ein Modul ohne Imports kann nichts. WASI ist Opt-in und default-deny. | +| **Sandbox — abschaltbar / härtbar** | `set_module_resolver(DummyModuleResolver::new())`, `disable_symbol("eval")`, `set_max_operations`, `on_progress`, `set_max_string_size/array_size/map_size`, `set_strict_variables`, Features `no_module`/`no_function`/`no_closure`. **CPU- und Memory-Limits sind per Default 0 = unbegrenzt.** | `Lua::new_with(StdLib::STRING\|TABLE\|MATH, …)` — [gemessen] danach sind `io`, `os`, `package`, `require`, `debug` weg (`load` und `print` bleiben, Base-Lib ist immer geladen). `set_memory_limit` [gemessen: greift, „memory error: not enough memory"], `set_hook(every_nth_instruction)` [gemessen: stoppt `while true do end`]. Luau-Backend bietet zusätzlich `Lua::sandbox()`. | `Config::consume_fuel` (deterministisch, inkompatibel mit Winch) oder Epoch-Interruption („cannot be avoided by malicious wasm code"), `StoreLimits` für Speicher. Caveat wörtlich: „Epochs (and fuel) do not assist in handling WebAssembly code blocked in a call to the host." | +| **Sandbox — bekannte Löcher** | GHSA-67fv-9r7g-432h (High, 2024, ≤1.18.0, gefixt). **Issue #894 „Rhai is unsound (use after free)" seit 2024-07-03 offen** — Host-API-Footgun, kein Script-Escape. | mlua-README selbst: „mlua does not provide absolute safety even without using `unsafe`. This library contains a huge amount of unsafe code." | **46 publizierte Advisories** bei wasmtime, davon **4 critical / 4 high**; zwei Criticals von 2026-04-09 sind echte Sandbox-Escapes (aarch64-Cranelift-Miscompile, Winch-Memory-Access). Der JIT ist in der TCB. Monatlicher Patch-Treadmill. | +| **Fehler mit Zeilennummer** | **Ja, Zeile + Spalte**, auch in verschachtelten Funktionen mit Call-Chain: `Array index 99 out of bounds: only 3 elements in array (line 3, position 11)`. API: `Position::line()` / `Position::position()`. ⚠️ 16-Bit-Auflösung, max. 65 535 Zeilen. | **Ja, Chunk-Name + Zeile + Traceback.** [gemessen]:
`[string "core/mods/evil.lua"]:2: attempt to index a nil value (local 't')`
`stack traceback:`
` [C]: in metamethod 'index'`
` [string "core/mods/evil.lua"]:2: in main chunk`
Syntaxfehler ebenso mit Zeile. | **Deutlich schlechter.** `WasmBacktrace` liefert Funktionsname + `module_offset`/`func_offset`. Quell-Zeile nur, wenn der Modder mit DWARF-Debuginfo gebaut hat *und* du `FrameInfo::symbols()` auswertest. Realistisch: Funktionsname + Byte-Offset. | +| **Hot-Reload** | **Trivial.** Engine ist zustandslos gegenüber dem `AST`; `AST` in `Rc>` neu kompilieren und tauschen. `Scope`-State überlebt den Reload. Chirurgisch möglich über `retain_functions` + `+=`. | **Ja.** [gemessen] Chunk im selben `Lua`-State neu ausführen → `tick()` liefert `v1 -> v2`. Alternativ neuen `Lua`-State bauen. | Möglich (neu kompilieren + frischer `Store`+`Instance`), aber Kompilierung ist teuer. `Module::deserialize` ist **`unsafe`** — „Arbitrary input could […] replace valid compiled code with any other valid compiled code, meaning that this can trivially be used to execute arbitrary code otherwise." Also niemals von Mods gelieferte `.cwasm` laden. | +| **Vertrautheit für Hobby-Modder** | Gering. Rhai-ähnliche Syntax (Rust/JS-nah), aber ein Modder muss sie neu lernen. Kein Spiel bekannt, das Rhai für Modding einsetzt (siehe „Nicht verifiziert"). | **Klar überlegen.** Factorio (Lua 5.2), Garry's Mod (5.1), Teardown (5.1), Luanti, Dota 2 VScript, WoW, Noita, Don't Starve, DFHack, Widelands, Balatro (via LÖVE). Alle mit offizieller oder de-facto Lua-Modding-API. | **Am schlechtesten.** Es gibt kein „Textdatei editieren und neu laden": Extism-Doku sagt, jedes Plugin „must be compiled to WebAssembly […] before it can be run in an Extism Host". Der Modder braucht Rust/Go/Zig/AssemblyScript-Toolchain. Die JS-Ausweichroute (Extism JS PDK = QuickJS-in-WASM, Javy-Fork) braucht zusätzlich Binaryen und kann „nur pure JS", ohne Node-/Browser-APIs. | +| **Reifegrad / Wartung** | Sehr aktiv, aber **Ein-Mann-Projekt** (Stephen Chung). ~4 Minor-Releases/Jahr seit 2 Jahren, letzter Commit 2026-08-11. 5 596 Stars, 316 Reverse-Deps. Hauptnutzer sind Config-/Policy-Engines (handlebars, casbin, apollo-router), nicht Spiele. Ein Bytecode-VM-Rewrite scheint im Gang → Verhaltens-Churn erwartbar. | Aktiv, breit genutzt. 2 826 Stars, letzter Push 2026-07-28, 438 direkte Dependents. Referenz auf Windows: **WezTerm** baut mit exakt `features = ["vendored","lua54",…]`. Produktions-Track-Record liegt in Tooling (WezTerm, Yazi, Vector/VRL, LibAFL), nicht in Spielen. | wasmtime: Bytecode Alliance, **fester Monatsrelease am 20.**, LTS nur alle 12 Versionen (36, 48) — Nicht-LTS-Releases fallen nach **2 Monaten** aus dem Support. extism (Dylibso): unregelmäßig, hängt an wasmtime **43** (bereits außerhalb des Supportfensters). wasmi: von der Stellar Development Foundation gesponsert, 2.0 seit Monaten in Beta. `cargo component` wird offiziell deprecated; Component Model ist **nicht 1.0**. | +| **Performance** | AST-Walking-Interpreter, „No bytecodes/JIT". Offiziell: „In general, Rhai is roughly 2x slower than Python 3." Für rundenbasierte Logik irrelevant. | Lua 5.4 ist eine der schnellsten Skriptsprachen; hier ebenfalls irrelevant. | JIT (Cranelift) am schnellsten, aber Pulley-Interpreter „~10x order-of-magnitude slowdown". Irrelevant für ein Terminalspiel. | + +--- + +## Empfehlung + +**Rhai** — mit klar benannten Gegenargumenten, weil die Entscheidung nicht eindeutig ist. + +### Warum Rhai + +1. **Es passt zur Fehlerstrategie aus der Map.** „`Result` statt `panic!` — kaputte Mod-TOMLs + sind der Normalfall." Rhai liefert Parse- *und* Laufzeitfehler mit Zeile und Spalte, inklusive + Call-Chain, als `EvalAltResult` — das fügt sich ohne Übersetzungsschicht in die bestehende + Fehlerbehandlung ein. WASM fällt an genau dieser Stelle durch: ein Modder bekäme Funktionsname + und Byte-Offset statt einer Zeilennummer. + +2. **Die Typ-Brücke ist die billigste.** `#[derive(CustomType)]` und „the only requirement of a + type to work with Rhai is `Clone`" gegen mlua's `impl UserData` mit `add_field_method_get`/ + `add_method_mut` pro Feld. Da Stufe (c) laut Map auf der Typ-Registry aufsetzt und das + Effekt-Vokabular ohnehin die Mod-API *ist*, ist die Zahl der zu brückenden Typen klein — aber + jeder gesparte Handgriff zählt bei einem Solo-Projekt. + +3. **Der Default ist sicherer.** Das ist der schärfste Einzelunterschied: [gemessen] hat ein + Lua-Script auf `Lua::new()` *und* auf `StdLib::ALL_SAFE` eine echte Datei über `io.open` + gelesen; `os.execute` war erreichbar. Bei Rhai gibt es diese Funktionen in der Standard-Engine + überhaupt nicht — Dateizugriff ist ein separates Crate. Beide brauchen Härtung, aber ein + vergessener Handgriff kostet bei Rhai weniger. + +4. **Kein C im Build, kleinste Toolchain-Angriffsfläche.** Rhai zieht kein `cc`, `libc` oder + `bindgen`. Praktisch ist das unter Windows-MSVC ein schwaches Argument — [gemessen] baute + `mlua` mit `vendored` hier problemlos und sogar schneller (17,2 s gegen 45,5 s), weil der + MSVC-Compiler für den Rust-Toolchain ohnehin installiert sein muss. Das Argument zählt erst + bei `x86_64-pc-windows-gnu` oder bei CI-Images ohne Build-Tools. + +5. **Lernziel Rust.** Die Map nennt „Rust lernen **und** Spiel bauen" als Motivation. Rhais API + ist durchgehend Rust-idiomatisch (Traits, Derive-Makros, `Result`); mlua zwingt zusätzlich zum + Verständnis der Lua-C-API-Semantik (Stack, `longjmp`-Verhalten, `Lua::scope`-Lifetimes). + +6. **Hot-Reload ist ein Zweizeiler** und `Scope`-State überlebt den Tausch — genau das Verhalten, + das man beim Iterieren an einem Mod will. + +### Was gegen Rhai spricht (und für Lua) + +- **Vertrautheit ist Lua's stärkstes Argument, und es ist ein starkes.** Factorio, Garry's Mod, + WoW, Teardown, Noita, Don't Starve, Luanti, Dota 2 — ein Hobby-Modder, der schon einmal etwas + gemoddet hat, kann mit hoher Wahrscheinlichkeit Lua. Für Rhai muss er neu anfangen. Wenn das + Ziel „möglichst viele fremde Mods" ist statt „gut moddbar für mich und ein paar Leute", + kippt die Empfehlung zu Lua. +- **Bus-Faktor 1.** Rhai wird im Wesentlichen von einer Person entwickelt. +- **Offene Unsoundness (#894, seit 2024-07-03)** und die Pflicht-Dependency **`smartstring` ist + seit 2026-08-10 als unmaintained gemeldet** (RUSTSEC-2026-0249, Repo am 2026-05-03 archiviert). + `cargo deny` schlägt damit heute an. +- **Ein Bytecode-VM-Rewrite scheint im Gang** (offene Issues „VM: Fuzz errors detected", + „VM: Modify switch statement processing") → Verhaltensänderungen in kommenden Releases. +- Kein einziges verifizierbares ausgeliefertes Spiel nutzt Rhai fürs Modding. + +### Warum WASM für 1.0 ausscheidet + +Nicht wegen der Technik — die Sandbox ist objektiv die beste der drei und die einzige, die +gegen wirklich bösartigen Code hält. Sondern wegen drei Dingen, die direkt gegen die Ziele +dieses Projekts laufen: + +1. **Der Compile-Schritt tötet die Zielgruppe.** Ein Modder, der heute 30 Zeilen Lua schreiben + würde, müsste eine Rust-/TinyGo-/AssemblyScript-Toolchain installieren und ein Binary + ausliefern. Selbst der „einfache" JS-Weg (Extism JS PDK) braucht Binaryen und einen Build. +2. **Fehlermeldungen ohne Zeilennummer** widersprechen der Fehlerstrategie der Map. +3. **Der Build-Overhead ist eine Größenordnung höher** — [gemessen] 166 bzw. 304 Dependencies und + 262 s bzw. 356 s Cold-Build gegen 44/45,5 s bei Rhai. Dazu wasmtimes MSRV 1.95 und ein + Support-Fenster von 2 Monaten für Nicht-LTS-Releases. + +Falls WASM später doch relevant wird (z. B. um Mods aus dem Netz zu laden, wo echte Isolation +zählt): **`wasmi`** ist der interessantere Kandidat als wasmtime — reines Rust, `no_std`, 29 +Dependencies, MSRV 1.86, mit Fuel und `StoreLimits`. Für ein Terminalspiel braucht es keinen JIT. + +### Härtungs-Checkliste, falls Rhai gewählt wird + +Alle vier sind nötig, weil die Defaults sie nicht mitbringen: + +1. `engine.set_module_resolver(DummyModuleResolver::new())` — sonst liest ein Script `.rhai`-Dateien + vom Dateisystem (empirisch nachgewiesen). +2. `engine.disable_symbol("eval")`. +3. `set_max_operations(...)` und/oder `on_progress(...)` — CPU-Limit ist per Default **aus**. +4. `set_max_string_size` / `set_max_array_size` / `set_max_map_size` — Memory-Limits sind per + Default **aus**. +5. Niemals das Feature `unchecked` aktivieren. +6. Für ein Single-Thread-Terminalspiel das Feature `sync` **weglassen** und `Rc>` + nutzen — `sync` erzwingt Locking bei jedem Funktionsaufruf. + +Analog für mlua, falls es doch Lua wird: `Lua::new_with(StdLib::STRING|TABLE|MATH, …)` +statt `ALL_SAFE`, plus `set_memory_limit` und `set_hook(every_nth_instruction)`. Factorio +veröffentlicht seine eigene Sandbox-Liste (`loadfile`, `dofile`, `coroutine`, `io`, `os` +entfernt, `debug` auf `getinfo`/`traceback` reduziert, `require` auf das Mod-Verzeichnis +gepatcht) — das ist die am besten erprobte Vorlage, die es gibt. + +### Anmerkung zur Umkehrbarkeit + +Laut Map ist das Effekt-Vokabular die Mod-API und Scripting setzt auf der Typ-Registry auf. +Wenn Scripts ausschließlich über dieses Vokabular mit dem Spiel reden — also Effekte +zurückgeben statt `Game` direkt zu manipulieren — ist die Engine-Wahl **deutlich umkehrbarer, +als sie aussieht**. Die Brücke wäre dann eine dünne Adapterschicht, kein Fundament. Das spricht +dafür, die Entscheidung nicht zu überdenken und Stufe (c) bewusst schmal zu halten. + +--- + +## Nicht verifiziert + +Explizit offen geblieben — nicht geraten: + +- **Kein ausgeliefertes kommerzielles Spiel mit Rhai-Modding gefunden.** `github.com/rhaiscript/awesome-rhai` + gibt 404, weder rhai.rs noch das README führen eine „Who uses Rhai"-Liste. Nächste Treffer sind + HEMTT (Arma-Modding-Toolchain, nutzt Rhai für Hooks) und Bevy-Integrationen (`bevy_mod_scripting`, + `bevy_scriptum`). +- **Kein ausgeliefertes Rust-Spiel mit mlua gefunden.** Der Track-Record liegt in Tooling + (WezTerm, Yazi, Vector). WezTerm ist aber ein belastbarer Windows-`vendored`-Präzedenzfall. +- **Kein ausgeliefertes kommerzielles Spiel mit WASM-Modding gefunden.** Veloren hat ein + WASM-Plugin-System, warnt aber selbst: „we currently make no stability guarantees about APIs, + tooling, etc." Ob Veloren inzwischen auf WASI 0.2 / wasmtime umgestellt hat, ließ sich aus + Veloren-Primärquellen **nicht** bestätigen. +- **Build-Zeiten sind Einzelmessungen auf einer Maschine.** Der Registry-Cache-Zustand war nicht + überall gleich (`wasmtime`/`extism` liefen mit kaltem Cache, enthalten also Downloadzeit). Die + Dependency-Zahlen sind dagegen exakt und methodisch identisch erhoben. +- **Rhai-Zahlen im Detail:** Die parallele Erhebung zählte über `cargo tree -e normal` 24 + Dependencies und maß 77 s Cold-Debug-Build; die Tabelle oben nennt 44 (Cargo.lock, inkl. + plattform-gegateter Einträge) und 45,5 s (warmer Registry-Cache). Beide Methoden sind korrekt, + sie messen Unterschiedliches. Innerhalb der Tabelle ist durchgängig dieselbe Methode verwendet. +- **Rhai-Hot-Reload-Randfall:** ob Closures, die aus einem alten `AST` stammen und Rust-seitig + noch gehalten werden, nach dem Verwerfen dieses `AST` korrekt funktionieren, sagt das Buch nicht. +- **Lua-Bytecode als Escape-Vektor:** die einschlägige Warnung im Lua-Referenzhandbuch (maliziös + konstruierte Binary-Chunks können den Interpreter zum Absturz bringen) ließ sich über die + abrufbaren Manual-Abschnitte nicht wörtlich belegen. Praktische Konsequenz bleibt unabhängig + davon: Mod-Scripts nur im Textmodus laden, nie vorkompilierte Chunks. + +--- + +## Quellen + +**crates.io / Registry** +- https://crates.io/api/v1/crates/rhai +- https://crates.io/api/v1/crates/rhai/1.25.1/dependencies +- https://crates.io/api/v1/crates/rhai/reverse_dependencies +- https://crates.io/api/v1/crates/mlua +- https://crates.io/api/v1/crates/wasmtime +- https://crates.io/api/v1/crates/wasmi +- https://crates.io/api/v1/crates/extism +- https://lib.rs/crates/rhai +- https://lib.rs/crates/mlua + +**Rhai** +- https://rhai.rs/book/rust/custom-types.html — „The only requirement of a type to work with Rhai is `Clone`." +- https://rhai.rs/book/rust/build-type.html — `CustomType` / `TypeBuilder` +- https://rhai.rs/book/plugins/module.html — `#[export_module]` +- https://rhai.rs/book/patterns/control.html — Control-Layer-Pattern (`Rc>`) +- https://rhai.rs/book/safety/sandbox.html — „Rhai is sand-boxed so a script can never read from outside its own environment" +- https://rhai.rs/book/safety/index.html — Don't-Panic-Garantie, Warnung zu `unchecked` +- https://rhai.rs/book/safety/max-operations.html · https://rhai.rs/book/safety/progress.html +- https://rhai.rs/book/rust/modules/resolvers/index.html · https://docs.rs/rhai/latest/rhai/module_resolvers/struct.FileModuleResolver.html +- https://rhai.rs/book/language/eval.html — „eval is evil" +- https://rhai.rs/book/engine/strict-var.html · https://rhai.rs/book/engine/scope.html · https://rhai.rs/book/engine/ast.html +- https://rhai.rs/book/patterns/hot-reload.html +- https://rhai.rs/book/patterns/multi-threading.html — Kosten des `sync`-Features +- https://rhai.rs/book/about/benchmarks.html — „roughly 2x slower than Python 3" +- https://rhai.rs/book/about/non-design.html — „No bytecodes/JIT" +- https://rhai.rs/book/rust/packages/builtin.html · https://rhai.rs/book/start/features.html +- https://docs.rs/rhai/latest/rhai/struct.Position.html · https://docs.rs/rhai/latest/rhai/enum.EvalAltResult.html · https://docs.rs/rhai/latest/rhai/struct.Engine.html +- https://github.com/rhaiscript/rhai — Repo; Issue #894 „Rhai is unsound (use after free)", offen seit 2024-07-03 +- https://github.com/rhaiscript/rhai-fs — separates Opt-in-Crate für Dateizugriff +- https://rustsec.org/advisories/RUSTSEC-2026-0249.html — `smartstring` unmaintained, 2026-08-10 +- https://hemtt.dev/rhai/index.html — Arma-Modding-Toolchain mit Rhai +- https://docs.rs/crate/bevy_mod_scripting/latest · https://github.com/jarkonik/bevy_scriptum + +**mlua / Lua** +- https://github.com/mlua-rs/mlua — README: `vendored`, Plattformen, „mlua does not provide absolute safety…" +- https://raw.githubusercontent.com/mlua-rs/mlua/main/src/stdlib.rs — `ALL_SAFE = StdLib((1 << 30) - 1)` +- https://docs.rs/mlua/latest/mlua/struct.Lua.html — `new_with`, `sandbox`, `set_memory_limit`, `set_hook`, `scope`, `load` +- https://docs.rs/mlua/latest/mlua/struct.StdLib.html +- https://docs.rs/mlua/latest/mlua/enum.Error.html +- https://docs.rs/mlua/latest/mlua/trait.UserData.html +- https://raw.githubusercontent.com/wezterm/wezterm/main/config/Cargo.toml — `features = ["vendored","lua54","async","send","serialize"]` +- https://lua-api.factorio.com/latest/auxiliary/libraries.html — Factorios Sandbox-Liste (Lua 5.2) +- https://wiki.facepunch.com/gmod/ (Lua 5.1) · https://www.teardowngame.com/modding/api.html (Lua 5.1) +- https://api.luanti.org/ · https://developer.valvesoftware.com/wiki/Dota_2_Workshop_Tools/Scripting +- https://docs.dfhack.org/en/stable/docs/Lua%20API.html · https://www.widelands.org/documentation/lua_index/ +- https://noita.wiki.gg/wiki/Modding:_Lua_API · https://luau.org/ + +**WASM** +- https://docs.wasmtime.dev/security.html — „no raw access to system calls or other forms of I/O…" +- https://docs.wasmtime.dev/stability-release.html — Monatsrelease am 20., LTS alle 12 Versionen, 2 Monate Support sonst +- https://docs.wasmtime.dev/stability-tiers.html · https://docs.wasmtime.dev/stability-platform-support.html +- https://docs.wasmtime.dev/examples-pulley.html — „~10x order-of-magnitude slowdown" +- https://docs.wasmtime.dev/examples-debugging-native-debugger.html +- https://docs.rs/wasmtime/latest/wasmtime/struct.Config.html — Fuel, Epoch, deren Grenzen +- https://docs.rs/wasmtime/latest/wasmtime/struct.Module.html — `deserialize` ist `unsafe` +- https://docs.rs/wasmtime/latest/wasmtime/struct.WasmBacktrace.html · https://docs.rs/wasmtime/latest/wasmtime/struct.FrameInfo.html +- https://docs.rs/wasmtime-wasi/latest/wasmtime_wasi/struct.WasiCtxBuilder.html — default-deny +- https://github.com/bytecodealliance/wasmtime/security/advisories — 46 Advisories, 4 critical / 4 high +- https://extism.org/docs/concepts/pdk — „must be compiled to WebAssembly … before it can be run" +- https://extism.org/docs/concepts/plug-in-system · https://docs.rs/extism/latest/extism/ +- https://github.com/extism/js-pdk — QuickJS-in-WASM, Javy-Fork, braucht Binaryen +- https://github.com/bytecodealliance/javy +- https://component-model.bytecodealliance.org/language-support/building-a-simple-component/rust.html — `cargo component` wird deprecated +- https://bytecodealliance.org/articles/the-road-to-component-model-1-0 — Component Model ist nicht 1.0 +- https://bytecodealliance.org/articles/WASI-0.3 +- https://github.com/wasmi-labs/wasmi · https://docs.rs/wasmi/latest/wasmi/ +- https://book.veloren.net/contributors/modders/writing-a-plugin.html — „we currently make no stability guarantees" +- https://tartanllama.xyz/posts/wasm-plugins/ — durchgearbeitetes WASM-Plugin-Beispiel (Demo, kein Produkt)