diff --git a/engine/Age.Engine.Tests/CallScriptIntegrationTests.cs.bak b/engine/Age.Engine.Tests/CallScriptIntegrationTests.cs.bak deleted file mode 100644 index 593337b..0000000 --- a/engine/Age.Engine.Tests/CallScriptIntegrationTests.cs.bak +++ /dev/null @@ -1,49 +0,0 @@ -using Age.Engine.Hosting; -using Age.Engine.Sys4; -using Age.Engine.Vm; -using Xunit; - -public class CallScriptIntegrationTests -{ - private sealed class NullHost : IHost - { - public void ShowText(int o, string t) { } - public void WaitForInput() { } - public void CreateTexture(int s, int w, int h) { } - public void SetTexture(long r, int s) { } - public void DrawTexture(int s, int sx, int sy, int w, int h, int dx, int dy) { } - public (int Width, int Height) GetTextureSize(int s) => (0, 0); - public void PlayBgm(long id) { } - public void PlayVoice(long id) { } - } - - [Fact] - public void RealScriptExecutesRealSubroutinesAndReturns() - { - // ADDILL.BIN unconditionally call-scripts ADDILLSUB then CALCREVISE at entry, then exits. - // With execution on, both subroutines load, run, and return, so ADDILL reaches its own exit. - var t = OpcodeTableJson.Load(Paths.OpcodesJson); - var provider = Sys4ScriptProvider.Load(t); - var script = Sys4Loader.Load(Paths.Scripts()["ADDILL.BIN"], t); - var host = new NullHost(); - var vm = new VirtualMachine(script, t, host, null, provider); - vm.Run(); - Assert.Equal(2, vm.CallScriptDispatches); // ADDILLSUB + CALCREVISE both dispatched - Assert.Equal("exit", vm.HaltReason); // subroutines returned; ADDILL reached its own exit - } - - [Fact] - public void BunkiTopLevelRetReturnsCleanlyAsSubroutine() - { - // BUNKI.BIN ends with a top-level `ret` (empty intra-call stack). Called as a subroutine it - // must return to the caller, not underflow-halt. Drive it directly. - var t = OpcodeTableJson.Load(Paths.OpcodesJson); - var provider = Sys4ScriptProvider.Load(t); - var bunki = provider.GetById(0x143); // BUNKI.BIN - Assert.NotNull(bunki); - var vm = new VirtualMachine(bunki!, t, new NullHost(), null, provider); - vm.Run(); - // Reaching a frame-return at the top = clean "exit"; never "ret-underflow". - Assert.NotEqual("ret-underflow", vm.HaltReason); - } -} diff --git a/engine/Age.Engine.Tests/CallScriptTests.cs.bak b/engine/Age.Engine.Tests/CallScriptTests.cs.bak deleted file mode 100644 index b3a83b6..0000000 --- a/engine/Age.Engine.Tests/CallScriptTests.cs.bak +++ /dev/null @@ -1,106 +0,0 @@ -using System.Collections.Generic; -using Age.Engine.Hosting; -using Age.Engine.Model; -using Age.Engine.Sys4; -using Age.Engine.Vm; -using Xunit; - -public class CallScriptTests -{ - // Opcodes (from build/opcodes.json): exit=0x2, call-script=0x3(argc1), mov=0x55(argc2). - // Operand types: imm=0, global-int=3, local-int=9. - private const uint OP_EXIT = 0x2, OP_CALLSCRIPT = 0x3, OP_MOV = 0x55; - - private sealed class NullHost : IHost - { - public void ShowText(int o, string t) { } - public void WaitForInput() { } - public void CreateTexture(int s, int w, int h) { } - public void SetTexture(long r, int s) { } - public void DrawTexture(int s, int sx, int sy, int w, int h, int dx, int dy) { } - public (int Width, int Height) GetTextureSize(int s) => (0, 0); - public void PlayBgm(long id) { } - public void PlayVoice(long id) { } - } - - private sealed class MapProvider : IScriptProvider - { - private readonly Dictionary _m; - public MapProvider(Dictionary m) => _m = m; - public Script? GetById(long id) => _m.TryGetValue(id, out var s) ? s : null; - } - - // Build a Script from raw dwords via the real loader (guarantees identical decode). - private static Script Asm(OpcodeTable t, string name, params uint[] body) - { - var bytes = new byte[0x3C + body.Length * 4]; - System.Text.Encoding.ASCII.GetBytes("SYS4422 ").CopyTo(bytes, 0); - // fields[8] (F8 = code end) at header offset 8 + 8*4 = 0x28; set to body length (all code). - System.BitConverter.GetBytes(body.Length).CopyTo(bytes, 8 + 8 * 4); - for (int i = 0; i < body.Length; i++) System.BitConverter.GetBytes(body[i]).CopyTo(bytes, 0x3C + i * 4); - return Sys4Loader.Parse(bytes, t, name); - } - - private static OpcodeTable Table() => OpcodeTableJson.Load(Paths.OpcodesJson); - - [Fact] - public void CalleeRunsAndControlResumesAfterTheCall() - { - var t = Table(); - // Callee (id 5): mov g[0x10] = 7, then exit. - var callee = Asm(t, "CALLEE", OP_MOV, 3, 0x10, 0, 7, OP_EXIT); - // Caller: call-script 5 ; mov g[0x11] = g[0x10] ; exit. - var caller = Asm(t, "CALLER", - OP_CALLSCRIPT, 0, 5, - OP_MOV, 3, 0x11, 3, 0x10, - OP_EXIT); - var host = new NullHost(); - var sink = new RecordingTraceSink(); - var vm = new VirtualMachine(caller, t, host, null, new MapProvider(new() { [5] = callee }), sink); - vm.Run(); - Assert.Equal(7, vm.Globals[0x10]); // callee wrote a shared global - Assert.Equal(7, vm.Globals[0x11]); // caller read it AFTER the call returned - Assert.Equal("exit", vm.HaltReason); // top-level exit - Assert.Contains(5L, sink.CallScriptIds); // dispatch observed via the trace sink - } - - [Fact] - public void MissingProviderFallsBackToStub() - { - var t = Table(); - var caller = Asm(t, "CALLER", OP_CALLSCRIPT, 0, 5, OP_EXIT); - var host = new NullHost(); - var sink = new RecordingTraceSink(); - var vm = new VirtualMachine(caller, t, host, null, null, sink); // no provider - vm.Run(); - Assert.Equal("exit", vm.HaltReason); // did not halt on the call; stub + continue - Assert.Contains(5L, sink.CallScriptIds); - } - - [Fact] - public void UnresolvedIdHalts() - { - var t = Table(); - var caller = Asm(t, "CALLER", OP_CALLSCRIPT, 0, 99, OP_EXIT); - var vm = new VirtualMachine(caller, t, new NullHost(), null, new MapProvider(new())); - vm.Run(); - Assert.StartsWith("callscript-unresolved", vm.HaltReason); - } - - [Fact] - public void LocalsDoNotLeakBetweenCallerAndCallee() - { - var t = Table(); - // Callee writes LOCAL-int 0 = 42 (mov to local-int, type 9), then exit. - var callee = Asm(t, "CALLEE", OP_MOV, 9, 0, 0, 42, OP_EXIT); - // Caller sets local-int 0 = 1, calls, then copies its own local-int 0 to global 0x20. - var caller = Asm(t, "CALLER", - OP_MOV, 9, 0, 0, 1, // l[0] = 1 - OP_CALLSCRIPT, 0, 5, // call-script 5 (callee sets ITS local 0 = 42) - OP_MOV, 3, 0x20, 9, 0, // g[0x20] = l[0] - OP_EXIT); - var vm = new VirtualMachine(caller, t, new NullHost(), null, new MapProvider(new() { [5] = callee })); - vm.Run(); - Assert.Equal(1, vm.Globals[0x20]); // caller's local 0 unchanged by callee's local 0 - } -} diff --git a/engine/Age.Engine.Tests/GameSessionTests.cs.bak b/engine/Age.Engine.Tests/GameSessionTests.cs.bak deleted file mode 100644 index bfd440e..0000000 --- a/engine/Age.Engine.Tests/GameSessionTests.cs.bak +++ /dev/null @@ -1,132 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using Age.Engine.Hosting; -using Age.Engine.Model; -using Age.Engine.Sys4; -using Age.Engine.Vm; -using Xunit; - -public class GameSessionTests -{ - private const int T_IMM = 0, T_GINT = 3; - private static readonly OpcodeTable Table = OpcodeTableJson.Load(Paths.OpcodesJson); - - // A minimal one-instruction script: `mov (global-int dst) src`. Halts pc-out-of-range after. - private static Script MovScript(int dst, int srcType, long srcVal) => new() - { - Header = new ScriptHeader(0, 0, 0, 0, 0, 0), - Instructions = new[] - { - new Instruction(0, 0x55, new[] { new Operand(T_GINT, dst), new Operand(srcType, srcVal) }), - }, - IndexByOffset = new Dictionary { { 0, 0 } }, - Strings = new Dictionary(), - }; - - private sealed class VoiceCountHost : IHost - { - public int Voices; - public List Emitted = new(); - public void ShowText(int o, string t) => Emitted.Add(o); - public void WaitForInput() { } - public void CreateTexture(int s, int w, int h) { } - public void SetTexture(long r, int s) { } - public void DrawTexture(int s, int sx, int sy, int w, int h, int dx, int dy) { } - public (int Width, int Height) GetTextureSize(int slot) => (0, 0); - public void PlayBgm(long id) { } - public void PlayVoice(long id) => Voices++; - } - - [Fact] - public void StatePersistsAcrossScenes() - { - var session = new GameSession(); - // Scene A writes G[0x5000] = 42. - session.RunScene(MovScript(0x5000, T_IMM, 0x2a), Table, new CaptureHost()); - Assert.Equal(0x2a, session.Globals[0x5000]); - // Scene B copies G[0x5000] -> G[0x5001]; it must SEE A's write. - session.RunScene(MovScript(0x5001, T_GINT, 0x5000), Table, new CaptureHost()); - Assert.Equal(0x2a, session.Globals[0x5001]); - } - - [Fact] - public void SeedIsVisibleToTheScene() - { - var session = new GameSession(); - session.Seed(0x5000, 99); - session.RunScene(MovScript(0x5001, T_GINT, 0x5000), Table, new CaptureHost()); - Assert.Equal(99, session.Globals[0x5001]); - } - - [Fact] - public void SC0000ViaSessionMatchesSingleRun() - { - var script = Sys4Loader.Load(Paths.Scripts()["SC0000.BIN"], Table); - // single run - var host1 = new CaptureHost(); - new VirtualMachine(script, Table, host1).Run(); - var single = host1.Emitted.Select(e => e.Offset).ToList(); - // via session - var host2 = new CaptureHost(); - var r = new GameSession().RunScene(Sys4Loader.Load(Paths.Scripts()["SC0000.BIN"], Table), Table, host2); - var viaSession = r.Emitted.Select(e => e.Offset).ToList(); - Assert.Equal(single, viaSession); - Assert.Equal("exit", r.Halt); - } - - [Fact] - public void BootingSkinitPopulatesDataTableGlobals() - { - // Running the SKINIT data script through the session populates the real skill table into the - // global bank (the game's boot behavior). Cross-check vs the static extraction (build/data/SKINIT.json): - // skill 0 = "飛行" at global-string 0x23a3, field G[0xa6e5b] = 30. - var session = new GameSession(); - var r = session.RunScene(Sys4Loader.Load(Paths.Scripts()["SKINIT.BIN"], Table), Table, new CaptureHost()); - Assert.Equal("exit", r.Halt); - Assert.Equal("飛行", session.GlobalStrings[0x23a3]); - Assert.Equal(30, session.Globals[0xa6e5b]); - Assert.True(session.Globals.Count > 1000, $"SKINIT should populate the skill table (got {session.Globals.Count})"); - } - - [Fact] - public void SnapshotRoundTripsState() - { - var s = new GameSession(); - s.Seed(0x10, 42); s.Seed(0x20, -7); s.SeedString(0x30, "リリィ"); - var t = GameSession.FromJson(s.ToJson()); - Assert.Equal(42, t.Globals[0x10]); - Assert.Equal(-7, t.Globals[0x20]); - Assert.Equal("リリィ", t.GlobalStrings[0x30]); - Assert.Equal(s.Globals.Count, t.Globals.Count); - } - - [Fact] - public void BootedStateSurvivesSnapshot() - { - var s = new GameSession(); - s.RunScene(Sys4Loader.Load(Paths.Scripts()["SKINIT.BIN"], Table), Table, new CaptureHost()); - var t = GameSession.FromJson(s.ToJson()); // snapshot the expensive booted state, rebuild - Assert.Equal("飛行", t.GlobalStrings[0x23a3]); - Assert.Equal(30, t.Globals[0xa6e5b]); - Assert.Equal(s.Globals.Count, t.Globals.Count); - Assert.Equal(s.GlobalStrings.Count, t.GlobalStrings.Count); - } - - [Fact] - public void SeedingFormFlagChangesBehavior() - { - // Lily's lines are gated on form flags G[0xa57/8/9]; unseeded => all skipped (0 voices on her lines). - // Seeding form A (0xa57=1) makes her lines execute -> more play-voice calls. Proven finding (audio). - var scriptPath = Paths.Scripts()["SC0000.BIN"]; - var unseeded = new VoiceCountHost(); - new GameSession().RunScene(Sys4Loader.Load(scriptPath, Table), Table, unseeded); - - var s = new GameSession(); - s.Seed(0xa57, 1); - var seeded = new VoiceCountHost(); - s.RunScene(Sys4Loader.Load(scriptPath, Table), Table, seeded); - - Assert.True(seeded.Voices > unseeded.Voices, - $"seeding form flag should fire more voices: unseeded={unseeded.Voices} seeded={seeded.Voices}"); - } -} diff --git a/engine/Age.Engine.Tests/TextureGeometryTests.cs.bak b/engine/Age.Engine.Tests/TextureGeometryTests.cs.bak deleted file mode 100644 index 2ee9eba..0000000 --- a/engine/Age.Engine.Tests/TextureGeometryTests.cs.bak +++ /dev/null @@ -1,44 +0,0 @@ -using System.Collections.Generic; -using Age.Engine.Hosting; -using Age.Engine.Model; -using Age.Engine.Sys4; -using Age.Engine.Vm; -using Xunit; - -public class TextureGeometryTests -{ - private sealed class FakeSizeHost : IHost - { - public void ShowText(int o, string t) { } - public void WaitForInput() { } - public void CreateTexture(int slot, int w, int h) { } - public void SetTexture(long resId, int slot) { } - public void DrawTexture(int slot, int sx, int sy, int w, int h, int dx, int dy) { } - public void PlayBgm(long id) { } - public void PlayVoice(long id) { } - public (int Width, int Height) GetTextureSize(int slot) => (0x140, 0xC8); - } - - [Fact] - public void GetTextureSizeWritesHostDimsIntoOutputGlobals() - { - var table = OpcodeTableJson.Load(Paths.OpcodesJson); - // 0x208 (global-int 50)(global-int 60)(global-int 61): slot=50, out_w=G[60], out_h=G[61] - const int T_GINT = 3; - var ins = new Instruction(0, 0x208, new[] - { - new Operand(T_GINT, 50), new Operand(T_GINT, 60), new Operand(T_GINT, 61), - }); - var script = new Script - { - Header = new ScriptHeader(0, 0, 0, 0, 0, 0), - Instructions = new[] { ins }, - IndexByOffset = new Dictionary { { 0, 0 } }, - Strings = new Dictionary(), - }; - var vm = new VirtualMachine(script, table, new FakeSizeHost()); - vm.Run(); - Assert.Equal(0x140, vm.Globals[60]); - Assert.Equal(0xC8, vm.Globals[61]); - } -} diff --git a/engine/Age.Engine.Tests/TextureOpsTests.cs.bak b/engine/Age.Engine.Tests/TextureOpsTests.cs.bak deleted file mode 100644 index b2dbb92..0000000 --- a/engine/Age.Engine.Tests/TextureOpsTests.cs.bak +++ /dev/null @@ -1,36 +0,0 @@ -using System.Collections.Generic; -using Age.Engine.Hosting; -using Age.Engine.Sys4; -using Age.Engine.Vm; -using Xunit; - -public class TextureOpsTests -{ - private sealed class RecHost : IHost - { - public List<(long resId, int slot)> Sets = new(); - public List<(int slot, int w, int h)> Draws = new(); - public int Creates; - public void ShowText(int o, string t) { } - public void WaitForInput() { } - public void CreateTexture(int slot, int w, int h) => Creates++; - public void SetTexture(long resId, int slot) => Sets.Add((resId, slot)); - public void DrawTexture(int slot, int srcX, int srcY, int w, int h, int dstX, int dstY) => Draws.Add((slot, w, h)); - public (int Width, int Height) GetTextureSize(int slot) => (0, 0); - public void PlayBgm(long id) { } - public void PlayVoice(long id) { } - } - - [Fact] - public void SC0000FiresTextureOpsWithSlot0FullScreenSlideshow() - { - var table = OpcodeTableJson.Load(Paths.OpcodesJson); - var script = Sys4Loader.Load(Paths.Scripts()["SC0000.BIN"], table); - var host = new RecHost(); - new VirtualMachine(script, table, host).Run(); - Assert.True(host.Creates > 0, "create-texture should fire"); - // The intro loads a sequence of full-screen images into slot 0; res 0x23 is the first bg. - Assert.Contains(host.Sets, s => s.resId == 0x23 && s.slot == 0); - Assert.Contains(host.Draws, d => d.slot == 0 && d.w == 0x320 && d.h == 0x258); - } -}