13 — Script-Engine waehlen und Sandbox haerten #14

Closed
opened 2026-08-11 13:45:06 +00:00 by RDietermann · 1 comment
Owner

Teil der Map #1 · Type: wayfinder:grilling · Blocked by: #2 · graduiert aus #6

Rhai oder mlua — und wie wird die gewaehlte Engine abgedichtet? Haengt bewusst an #2: reden Scripts nur ueber das Effekt-Vokabular, ist die Wahl umkehrbar; reichen sie tiefer in den Game-State, ist sie es nicht.

Volle Fragestellung (kanonisch): .scratch/rustkeep-1.0/issues/13-script-engine-waehlen.md

Teil der Map #1 · **Type: `wayfinder:grilling`** · **Blocked by: #2** · graduiert aus #6 Rhai oder mlua — und wie wird die gewaehlte Engine abgedichtet? Haengt bewusst an #2: reden Scripts nur ueber das Effekt-Vokabular, ist die Wahl umkehrbar; reichen sie tiefer in den Game-State, ist sie es nicht. Volle Fragestellung (kanonisch): `.scratch/rustkeep-1.0/issues/13-script-engine-waehlen.md`
RDietermann added the wayfinder:grilling label 2026-08-11 13:45:06 +00:00
Author
Owner

Aufgeloest in 3804006. Kanonische Fassung: .scratch/rustkeep-1.0/issues/13-script-engine-waehlen.md

Die Wahl ist eine Implementierungsentscheidung

Scripts lesen ueber eine Handvoll Funktionen und schreiben ausschliesslich ueber Aktionen. Weder lesen sie direkt im Game-Baum noch halten sie Zustand ausserhalb des Vokabulars. Damit ist die Schnittstelle klein genug, um sie in jeder Engine nachzubauen — genau die Leitplanke aus #6. Ein Wechsel kostet die Bindungsschicht, nicht die Architektur.

Die strikte Variante (Script bekommt Daten, gibt Aktionen zurueck, liest gar nichts) waere sauberer und macht Scripts sinnlos: was ein Script gegenueber einem Trigger hinzufuegt, ist Rechnen — und dafuer muss es lesen koennen.

Rhai — unbequem begruendet

Fuer ein unveroeffentlichtes Terminal-Roguelike schreibt niemand Mods. Lua gewinnt bei der Vertrautheit deutlich, aber das zahlt sich erst mit einer Modder-Gemeinde aus, und die entsteht nicht durch die Sprachwahl. Bis dahin ist der Modder, den es zu bedienen gilt, der Autor selbst — und die Charter nennt "Rust lernen" als halbe Motivation.

Dazu der schaerfste Befund aus #6: ein Lua-Script liest unter StdLib::ALL_SAFE per io.open echte Dateien. Rhai hat Datei-, Netz- und Prozesszugriff gar nicht erst in der Standard-Engine.

Mods gelten als vertrauenswuerdig — wie bei Factorio, Minecraft, Skyrim und Noita. Eine Sandbox schuetzt den Spieler nicht, sie verschiebt nur, wo er die Vertrauensentscheidung trifft. Damit scheidet Luau samt C++17-Toolchain und Lua-Dialekt aus.

Haertung — gegen Unfaelle, nicht gegen Absicht

engine.set_module_resolver(DummyModuleResolver::new()); // kein ./*.rhai von der Platte
engine.disable_symbol("eval");
engine.set_max_operations(100_000);
engine.set_max_call_levels(32);
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);

Alle Methodennamen gegen die Rhai-Doku geprueft. on_progress bleibt weg — set_max_operations deckt denselben Fall mit einem Wert statt einer Callback-Schleife ab.

set_max_operations gehoert ausdruecklich nicht in [balance] aus #15: ein Mod, der sein eigenes CPU-Limit hochsetzen kann, invertiert die Haertung. Ein Script am Limit ist ein Eintrags-Fehler nach #5 — Warnzeile mit Mod-ID, Script-Name und Zeile, Trigger verworfen, Spiel laeuft weiter.

Was Scripts sehen duerfen

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 an der Bedingungsgrammatik aus #9 gespiegelt — Scripts sehen nichts, was eine Bedingung nicht auch saehe, sie koennen nur damit rechnen. health/stamina sind eigene Funktionen statt attribute("health"), weil #2 scharf zwischen Attribut (max_health) und Zustand trennt.

rand() laeuft durch den Generator aus Game, Rhais eigenes rand-Paket wird nicht registriert — sonst gilt "Nachladen wuerfelt nicht neu" aus #15 fuer alles ausser fuer Scripts.

Gestrichen wurden turn() und enemy_count(): kein benanntes Beispiel braucht sie, und enemy_count() deckt sein eigenes Motivbeispiel ("Zahl der Untoten im Raum") nicht einmal ab. Die Umkehrbarkeit haengt daran, dass diese Schnittstelle klein bleibt.

Scripts sind die elfte Registry

{ action = "run_script", script = "meinmod:feuerregen" } — run_script als neunte Aktion. Dadurch entstehen keine neuen Platzierungsregeln: ein Script ist ueberall verwendbar, wo eine Aktion stehen darf. Es gibt Aktionen zurueck, die in die Warteschlange aus #9 wandern, also bleibt der Schreibpfad genau einer.

Risiken: beobachtet, mit benanntem Ausstiegssignal

Signal Reaktion
Keine Rhai-Release in zwoelf Monaten wechseln — Stand 1.25.1 vom 29.05.2026, 3,4 Mio. Downloads/90 Tage
Unsoundness in einem genutzten Pfad wechseln — #894 ist das bisher nicht
smartstring unmaintained kein Ausstiegsgrund

smartstring wurde am 03.05.2026 archiviert, RUSTSEC-2026-0249 kam am 10.08. und ist ein INFO-Eintrag ueber fehlende Wartung, keine Verwundbarkeit. cargo audit meckert, und das ist die angemessene Menge Aerger.

Stufe (c) wandert hinter 1.0

Die Entscheidung steht jetzt, die Umsetzung nicht. Stufen (a) und (b) liefern bereits elf Registries, Trigger, Bedingungen, Affixe und [balance] — sehr viel Moddbarkeit ohne eine Zeile Script.

#3 hat die Regel selbst formuliert: "Braucht ein Gegenstand etwas, das Modifikator, Aktion oder Faehigkeit nicht ausdruecken koennen, ist das eine Luecke im Vokabular." Eine Script-Engine einzubauen, bevor jemand an diese Luecke gestossen ist, baut die Antwort vor der Frage.

Die Destination der Map wurde entsprechend geaendert: 1.0 liefert Mod-Stufen (a) und (b).

Nachtrag an #2 (01)

run_script als neunte Aktion. Katalog jetzt: damage, change_attribute, apply_modifier, remove_modifier, give_item, remove_item, start_quest, flee, run_script.

**Aufgeloest** in 3804006. Kanonische Fassung: `.scratch/rustkeep-1.0/issues/13-script-engine-waehlen.md` ### Die Wahl ist eine Implementierungsentscheidung Scripts lesen ueber eine Handvoll Funktionen und schreiben ausschliesslich ueber **Aktionen**. Weder lesen sie direkt im `Game`-Baum noch halten sie Zustand ausserhalb des Vokabulars. Damit ist die Schnittstelle klein genug, um sie in jeder Engine nachzubauen — genau die Leitplanke aus #6. Ein Wechsel kostet die Bindungsschicht, nicht die Architektur. Die strikte Variante (Script bekommt Daten, gibt Aktionen zurueck, liest gar nichts) waere sauberer und macht Scripts sinnlos: was ein Script gegenueber einem Trigger *hinzufuegt*, ist Rechnen — und dafuer muss es lesen koennen. ### Rhai — unbequem begruendet **Fuer ein unveroeffentlichtes Terminal-Roguelike schreibt niemand Mods.** Lua gewinnt bei der Vertrautheit deutlich, aber das zahlt sich erst mit einer Modder-Gemeinde aus, und die entsteht nicht durch die Sprachwahl. Bis dahin ist der Modder, den es zu bedienen gilt, der Autor selbst — und die Charter nennt "Rust lernen" als halbe Motivation. Dazu der schaerfste Befund aus #6: ein Lua-Script liest unter `StdLib::ALL_SAFE` per `io.open` echte Dateien. Rhai hat Datei-, Netz- und Prozesszugriff gar nicht erst in der Standard-Engine. **Mods gelten als vertrauenswuerdig** — wie bei Factorio, Minecraft, Skyrim und Noita. Eine Sandbox schuetzt den Spieler nicht, sie verschiebt nur, wo er die Vertrauensentscheidung trifft. Damit scheidet Luau samt C++17-Toolchain und Lua-*Dialekt* aus. ### Haertung — gegen Unfaelle, nicht gegen Absicht ```rust engine.set_module_resolver(DummyModuleResolver::new()); // kein ./*.rhai von der Platte engine.disable_symbol("eval"); engine.set_max_operations(100_000); engine.set_max_call_levels(32); 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); ``` Alle Methodennamen gegen die Rhai-Doku geprueft. `on_progress` bleibt weg — `set_max_operations` deckt denselben Fall mit einem Wert statt einer Callback-Schleife ab. **`set_max_operations` gehoert ausdruecklich nicht in `[balance]`** aus #15: ein Mod, der sein eigenes CPU-Limit hochsetzen kann, invertiert die Haertung. Ein Script am Limit ist ein Eintrags-Fehler nach #5 — Warnzeile mit Mod-ID, Script-Name und Zeile, Trigger verworfen, Spiel laeuft weiter. ### Was Scripts sehen duerfen ``` 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 an der Bedingungsgrammatik aus #9 gespiegelt — **Scripts sehen nichts, was eine Bedingung nicht auch saehe**, sie koennen nur damit rechnen. `health`/`stamina` sind eigene Funktionen statt `attribute("health")`, weil #2 scharf zwischen Attribut (`max_health`) und Zustand trennt. **`rand()` laeuft durch den Generator aus `Game`**, Rhais eigenes `rand`-Paket wird nicht registriert — sonst gilt "Nachladen wuerfelt nicht neu" aus #15 fuer alles ausser fuer Scripts. Gestrichen wurden `turn()` und `enemy_count()`: kein benanntes Beispiel braucht sie, und `enemy_count()` deckt sein eigenes Motivbeispiel ("Zahl der Untoten im Raum") nicht einmal ab. Die Umkehrbarkeit haengt daran, dass diese Schnittstelle klein bleibt. ### Scripts sind die elfte Registry `{ action = "run_script", script = "meinmod:feuerregen" }` — **`run_script` als neunte Aktion**. Dadurch entstehen keine neuen Platzierungsregeln: ein Script ist ueberall verwendbar, wo eine Aktion stehen darf. Es gibt Aktionen zurueck, die in die Warteschlange aus #9 wandern, also bleibt der Schreibpfad genau einer. ### Risiken: beobachtet, mit benanntem Ausstiegssignal | Signal | Reaktion | |---|---| | Keine Rhai-Release in zwoelf Monaten | wechseln — Stand 1.25.1 vom 29.05.2026, 3,4 Mio. Downloads/90 Tage | | Unsoundness in einem genutzten Pfad | wechseln — #894 ist das bisher nicht | | `smartstring` unmaintained | **kein** Ausstiegsgrund | `smartstring` wurde am 03.05.2026 archiviert, RUSTSEC-2026-0249 kam am 10.08. und ist ein INFO-Eintrag ueber fehlende Wartung, keine Verwundbarkeit. `cargo audit` meckert, und das ist die angemessene Menge Aerger. ### Stufe (c) wandert hinter 1.0 Die **Entscheidung** steht jetzt, die **Umsetzung** nicht. Stufen (a) und (b) liefern bereits elf Registries, Trigger, Bedingungen, Affixe und `[balance]` — sehr viel Moddbarkeit ohne eine Zeile Script. #3 hat die Regel selbst formuliert: "Braucht ein Gegenstand etwas, das Modifikator, Aktion oder Faehigkeit nicht ausdruecken koennen, ist das eine Luecke im Vokabular." Eine Script-Engine einzubauen, *bevor* jemand an diese Luecke gestossen ist, baut die Antwort vor der Frage. **Die Destination der Map wurde entsprechend geaendert**: 1.0 liefert Mod-Stufen (a) und (b). ### Nachtrag an #2 (01) `run_script` als neunte Aktion. Katalog jetzt: `damage`, `change_attribute`, `apply_modifier`, `remove_modifier`, `give_item`, `remove_item`, `start_quest`, `flee`, `run_script`.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: RDietermann/rustkeep#14