using Age.Engine.Hosting; using Age.Engine.Model; namespace Age.Engine.Vm; /// /// A persistent global store carried across scenes. Every scene the game runs shares one flat global /// bank (the engine's model); running scenes in isolation with empty state is why our headless VM /// diverges from the real game (the bg/sprite geometry drift, the state-gated EMPTY scenes, Lily's /// form-gated voices are all state divergence — see docs/phase-a-slice-plan.md A2b-Geometry). /// /// Seam rule: references only Model + Hosting (never Sys4). The /// is unchanged — its globals are pre-seeded before Run and merged /// back after, so trace/selftest parity is untouched. Local frames are per-call and correctly do NOT /// persist (they live in the VM, not here). /// public sealed class GameSession { public Dictionary Globals { get; } = new(); public Dictionary GlobalStrings { get; } = new(); public void Seed(int addr, long value) => Globals[addr] = value; public void SeedString(int addr, string value) => GlobalStrings[addr] = value; /// Run one scene: seed a fresh VM from session state, execute, merge final state back. public SceneResult RunScene(Script script, OpcodeTable table, IHost host, VmOptions? options = null) { var vm = new VirtualMachine(script, table, host, options); foreach (var kv in Globals) vm.Globals[kv.Key] = kv.Value; foreach (var kv in GlobalStrings) vm.GlobalStrings[kv.Key] = kv.Value; vm.Run(); // Globals are one flat space; last write wins — the engine's single global bank. foreach (var kv in vm.Globals) Globals[kv.Key] = kv.Value; foreach (var kv in vm.GlobalStrings) GlobalStrings[kv.Key] = kv.Value; return new SceneResult(vm.Emitted.ToList(), vm.HaltReason, vm.Steps); } } /// The observable result of running one scene into a . public sealed record SceneResult(IReadOnlyList<(int Offset, string Text)> Emitted, string? Halt, long Steps);