kv::Store von bladeink entkoppeln

Der Kern-KV war HashMap<String, bladeink::ValueType> — jedes Modul, das
eine Spielvariable las, hing damit an der öffentlichen API der Story-Crate.
"Konvertierung gratis" war der Grund, aber der Preis war eine
Dependency-Kante quer durch engine/.

Neuer eigener kv::Value (Bool/Int/Float/Str). Die Umrechnung auf Inks
ValueType passiert jetzt allein in ink.rs (to_ink/from_ink) — die einzige
Stelle im Kern, die bladeink überhaupt kennt. from_ink verwirft
Ink-eigene Typen (Listen, Divert-Referenzen), die im KV nichts verloren
haben.

- format_value → Display für Value
- coerce_to_int → Value::as_int (gleiche Semantik); coerce_to_bool hatte
  außer Tests keinen Aufrufer und entfällt, die Tests prüfen jetzt die
  gespeicherte Variante direkt
- bladeink bleibt Dependency (ink.rs braucht es), nur nicht mehr im
  KV-Vertrag

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0196AM4UyAJEXVtPvp5DdKr8
This commit is contained in:
irrlicht
2026-09-01 22:54:52 +02:00
co-authored by Claude Sonnet 5
parent 247f7ef9fe
commit 236a643d04
4 changed files with 118 additions and 50 deletions
+7 -6
View File
@@ -4,7 +4,7 @@ use crate::engine::game::{Action, Game, ModeTarget};
use crate::engine::ink::StoryState;
use crate::engine::player::{self, Player};
use crate::engine::trigger::Triggers;
use crate::engine::{kv, model, signals, story_ctrl};
use crate::engine::{model, signals, story_ctrl};
/// Maus-Empfindlichkeit der Sicht (Radiant/Pixel).
const LOOK_SENS: f32 = 0.0025;
@@ -393,7 +393,7 @@ impl Session {
let mut keys: Vec<&String> = self.game.kv.keys().collect();
keys.sort();
keys.into_iter()
.map(|k| format!(" {k} = {}", kv::format_value(&self.game.kv[k])))
.map(|k| format!(" {k} = {}", self.game.kv[k]))
.collect()
}
}
@@ -416,6 +416,7 @@ fn help() -> Vec<String> {
#[cfg(test)]
mod tests {
use super::*;
use crate::engine::kv::Value;
// Fehlender Pfad → leere Signal-Table (load_signals schluckt den Fehler),
// also keine Asset-Abhängigkeit für diese Tests. Der Konstruktor startet
@@ -440,7 +441,7 @@ mod tests {
// Unbekanntes Signal fällt auf den Builtin-Pfad durch → `set`.
let r = s.exec("signal set has_key true");
assert!(!r.quit);
assert!(s.game.kv["has_key"].coerce_to_bool().unwrap());
assert!(matches!(s.game.kv["has_key"], Value::Bool(true)));
assert!(matches!(s.mode, Mode::Free));
}
@@ -456,7 +457,7 @@ mod tests {
assert!(matches!(s.mode, Mode::Menu));
// Im Menü laufen Engine-Befehle weiter (gleiche Route wie Play/Free).
s.exec("signal set in_menu true");
assert!(s.game.kv["in_menu"].coerce_to_bool().unwrap());
assert!(matches!(s.game.kv["in_menu"], Value::Bool(true)));
}
#[test]
@@ -571,7 +572,7 @@ mod tests {
// Vorwärts (−Z, yaw=0) in die Zone laufen; irgendwann feuert die
// Flanke genau einmal (danach: drin = still).
for _ in 0..120 { s.tick(&FrameInput { fwd: 1.0, ..Default::default() }, FIXED_DT); }
assert!(s.game.kv["zone_hit"].coerce_to_bool().unwrap(), "Zone sollte gefeuert haben");
assert!(matches!(s.game.kv["zone_hit"], Value::Bool(true)), "Zone sollte gefeuert haben");
// Weiterlaufen in der Zone darf nicht erneut feuern.
s.game.kv.remove("zone_hit");
@@ -586,7 +587,7 @@ mod tests {
s.mode = Mode::FirstPerson;
// Klick-Ziel: feuert den Property-Wert, nicht signal_key("Kiste").
s.exec("use Kiste");
assert!(s.game.kv["kiste_geklickt"].coerce_to_bool().unwrap());
assert!(matches!(s.game.kv["kiste_geklickt"], Value::Bool(true)));
// Ohne Property wie gehabt: Name (suffix-gestrippt) als Signal — hier
// unbekannt und ohne Table ein stilles No-Op, aber kein Fehler.
let r = s.exec("use Irgendwas.003");