From ea0f1ddc7a58174464a31f5793673a82361ede87 Mon Sep 17 00:00:00 2001 From: gamer147 Date: Mon, 6 Jul 2026 22:48:10 -0400 Subject: [PATCH] feat(a2b): implement 0x208 get-texture-size (geometry keystone) Promote 0x208 from stub to a real VM op that writes the loaded texture's width/height into its two output globals, via new IHost.GetTextureSize. This is the single native primitive the CG-load subroutine (SC0000 label_12649) needs; all downstream centering/anchor geometry is already computed in bytecode. Non-Godot hosts return (0,0) so trace/selftest parity holds (engine 9/9 incl. TraceDiffTests). Godot host gets a temporary (0,0) stub; real impl in the compositor task. Co-Authored-By: Claude Opus 4.8 (1M context) --- docs/opcode-reference.md | 9 ++-- engine/Age.Cli/Program.cs | 1 + .../Age.Engine.Tests/TextureGeometryTests.cs | 46 +++++++++++++++++++ engine/Age.Engine.Tests/TextureOpsTests.cs | 1 + engine/Age.Engine.Tests/WaitForInputTests.cs | 1 + engine/Age.Engine/Hosting/CaptureHost.cs | 1 + engine/Age.Engine/Hosting/IHost.cs | 1 + engine/Age.Engine/Vm/VirtualMachine.cs | 6 +++ godot/GodotAdvHost.cs | 3 ++ vm-map/opcodes.toml | 20 ++++---- 10 files changed, 75 insertions(+), 14 deletions(-) create mode 100644 engine/Age.Engine.Tests/TextureGeometryTests.cs diff --git a/docs/opcode-reference.md b/docs/opcode-reference.md index 4d90953..1817b1a 100644 --- a/docs/opcode-reference.md +++ b/docs/opcode-reference.md @@ -81,6 +81,11 @@ - **grounding:** source=inference, confidence=med - **evidence:** confirm via frida +### 0x208 `get-texture-size` (get-texture-size, argc 3) +- **summary:** 0x208 (slot)(out_w)(out_h) — writes the loaded texture's width/height into two output globals; keystone for bytecode-computed sprite/bg geometry (SC0000 label_12649) +- **grounding:** source=inference, confidence=med +- **evidence:** SC0000 label_12649: set-texture(resId,slot) then 0x208(slot)->w,h feeds w/2 horizontal-center + foot-anchor subtraction into draw-texture dst; stubbing yields 0x0 sizes / off-center draws + ### 0x217 `gfx-geom?` (u004211E0, argc 4) - **summary:** 4 global-ints; part of a 0x217/0x218/0x21a geometry chain - **grounding:** source=inference, confidence=low @@ -840,10 +845,6 @@ op 0x90 (u0041BEB0, argc 7): `0x90 x y w h tgt_a tgt_b tgt_c`. Kelebek left it " - **summary:** — - **grounding:** source=kelebek, confidence=low -### 0x208 `u00420BF0` (u00420BF0, argc 3) -- **summary:** — -- **grounding:** source=kelebek, confidence=low - ### 0x20a `u00420CE0` (u00420CE0, argc 1) - **summary:** — - **grounding:** source=kelebek, confidence=low diff --git a/engine/Age.Cli/Program.cs b/engine/Age.Cli/Program.cs index 16d9e83..aa9f1a0 100644 --- a/engine/Age.Cli/Program.cs +++ b/engine/Age.Cli/Program.cs @@ -84,4 +84,5 @@ sealed class AudioTraceHost : IHost public void CreateTexture(int slot, int width, int height) { } public void SetTexture(long resourceId, int slot) { } public void DrawTexture(int slot, int srcX, int srcY, int width, int height, int dstX, int dstY) { } + public (int Width, int Height) GetTextureSize(int slot) => (0, 0); } diff --git a/engine/Age.Engine.Tests/TextureGeometryTests.cs b/engine/Age.Engine.Tests/TextureGeometryTests.cs new file mode 100644 index 0000000..9118fdc --- /dev/null +++ b/engine/Age.Engine.Tests/TextureGeometryTests.cs @@ -0,0 +1,46 @@ +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 CallScript(long id) { } + public void OnStub(int op) { } + 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 b/engine/Age.Engine.Tests/TextureOpsTests.cs index 4dddd1d..1d31ad0 100644 --- a/engine/Age.Engine.Tests/TextureOpsTests.cs +++ b/engine/Age.Engine.Tests/TextureOpsTests.cs @@ -18,6 +18,7 @@ public class TextureOpsTests 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) { } } diff --git a/engine/Age.Engine.Tests/WaitForInputTests.cs b/engine/Age.Engine.Tests/WaitForInputTests.cs index e423163..f176cb3 100644 --- a/engine/Age.Engine.Tests/WaitForInputTests.cs +++ b/engine/Age.Engine.Tests/WaitForInputTests.cs @@ -17,6 +17,7 @@ public class WaitForInputTests public void CreateTexture(int slot, int w, int h) { } public void SetTexture(long resId, int slot) { } public void DrawTexture(int slot, int srcX, int srcY, int w, int h, int dstX, int dstY) { } + public (int Width, int Height) GetTextureSize(int slot) => (0, 0); public void PlayBgm(long id) { } public void PlayVoice(long id) { } } diff --git a/engine/Age.Engine/Hosting/CaptureHost.cs b/engine/Age.Engine/Hosting/CaptureHost.cs index a58d801..81619e6 100644 --- a/engine/Age.Engine/Hosting/CaptureHost.cs +++ b/engine/Age.Engine/Hosting/CaptureHost.cs @@ -11,6 +11,7 @@ public sealed class CaptureHost : IHost public void CreateTexture(int slot, int width, int height) { } public void SetTexture(long resourceId, int slot) { } public void DrawTexture(int slot, int srcX, int srcY, int width, int height, int dstX, int dstY) { } + public (int Width, int Height) GetTextureSize(int slot) => (0, 0); public void PlayBgm(long id) { } public void PlayVoice(long id) { } } diff --git a/engine/Age.Engine/Hosting/IHost.cs b/engine/Age.Engine/Hosting/IHost.cs index ed85b41..f1d4ce9 100644 --- a/engine/Age.Engine/Hosting/IHost.cs +++ b/engine/Age.Engine/Hosting/IHost.cs @@ -8,6 +8,7 @@ public interface IHost void CreateTexture(int slot, int width, int height); void SetTexture(long resourceId, int slot); void DrawTexture(int slot, int srcX, int srcY, int width, int height, int dstX, int dstY); + (int Width, int Height) GetTextureSize(int slot); void PlayBgm(long id); void PlayVoice(long id); } diff --git a/engine/Age.Engine/Vm/VirtualMachine.cs b/engine/Age.Engine/Vm/VirtualMachine.cs index 617d604..9f3e3fd 100644 --- a/engine/Age.Engine/Vm/VirtualMachine.cs +++ b/engine/Age.Engine/Vm/VirtualMachine.cs @@ -175,6 +175,12 @@ public sealed class VirtualMachine case "draw-texture": // (handle, slot, srcX, srcY, w, h, dstX, dstY) _host.DrawTexture((int)Read(a[1]), (int)Read(a[2]), (int)Read(a[3]), (int)Read(a[4]), (int)Read(a[5]), (int)Read(a[6]), (int)Read(a[7])); return pc + 1; + case "get-texture-size": // 0x208 (slot) (out_w) (out_h) + { + var (gw, gh) = _host.GetTextureSize((int)Read(a[0])); + Write(a[1], gw); Write(a[2], gh); + return pc + 1; + } case "play-bgm": _host.PlayBgm(Read(a[0])); return pc + 1; case "play-voice": _host.PlayVoice(Read(a[0])); return pc + 1; default: diff --git a/godot/GodotAdvHost.cs b/godot/GodotAdvHost.cs index a42be03..fdc4feb 100644 --- a/godot/GodotAdvHost.cs +++ b/godot/GodotAdvHost.cs @@ -54,6 +54,9 @@ public sealed class GodotAdvHost : IHost _main.CallDeferred("DrawSlot", slot, bmp, dstX, dstY, width, height); } + // Temporary stub — replaced by the real BMP-header-backed impl in Task 3 (blit compositor). + public (int Width, int Height) GetTextureSize(int slot) => (0, 0); + // ---- audio ops (OGG plays natively in Godot) ---- // BGM: addressed by direct name (BGM{id:D3}.OGG), NOT the manifest. Voice: via the per-scene manifest. public void PlayBgm(long id) diff --git a/vm-map/opcodes.toml b/vm-map/opcodes.toml index 53ca7af..3ebe58b 100644 --- a/vm-map/opcodes.toml +++ b/vm-map/opcodes.toml @@ -4978,33 +4978,33 @@ observed_types = ["imm", "l-int"] [[opcode]] op = 0x208 -label = "u00420BF0" +label = "get-texture-size" argc = 3 abi_source = "kelebek+decode-validated" [opcode.semantics] -name = "u00420BF0" -category = "unknown" -summary = "" +name = "get-texture-size" +category = "draw" +summary = "0x208 (slot)(out_w)(out_h) — writes the loaded texture's width/height into two output globals; keystone for bytecode-computed sprite/bg geometry (SC0000 label_12649)" noop_headless = false -source = "kelebek" -confidence = "low" +source = "inference" +confidence = "med" depends_on = [] -evidence = "" +evidence = "SC0000 label_12649: set-texture(resId,slot) then 0x208(slot)->w,h feeds w/2 horizontal-center + foot-anchor subtraction into draw-texture dst; stubbing yields 0x0 sizes / off-center draws" [[opcode.semantics.args]] i = 1 -role = "" +role = "slot" observed_types = ["imm", "g-int", "l-int"] [[opcode.semantics.args]] i = 2 -role = "" +role = "out_width" observed_types = ["g-int", "l-int"] [[opcode.semantics.args]] i = 3 -role = "" +role = "out_height" observed_types = ["g-int", "l-int"] [[opcode]]