Extract VM surface opcode handler

This commit is contained in:
gamer147
2026-08-02 23:45:15 -04:00
parent ee4a89ca1c
commit 4574ee3797
4 changed files with 103 additions and 61 deletions

View File

@@ -177,7 +177,10 @@ opcode dispatcher. Its partial-class companion `engine/Age.Engine/Vm/VirtualMach
state, BGM restart semantics, and the BGM/voice/SFX/mixer opcode handler; `Step` retains the audio labels and state, BGM restart semantics, and the BGM/voice/SFX/mixer opcode handler; `Step` retains the audio labels and
routes that family into the handler. `engine/Age.Engine/Vm/VirtualMachine.Movie.cs` owns modal/asynchronous/ routes that family into the handler. `engine/Age.Engine/Vm/VirtualMachine.Movie.cs` owns modal/asynchronous/
positioned movie playback, movie surface metadata/activity queries, and movie-mask transition dispatch; `Step` positioned movie playback, movie surface metadata/activity queries, and movie-mask transition dispatch; `Step`
likewise retains and routes the movie labels. likewise retains and routes the movie labels. `engine/Age.Engine/Vm/VirtualMachine.Surface.cs` owns surface
allocation/loading, texture binding and sizing, mutable surface fill/copy, render-target control, and transient
surface release; its labels remain at their existing dispatcher positions around the retained numeric-glyph and
object cases.
The disposable `build/page-map-<SCENE>.jsonl` files are produced by editor/development Godot runs and map The disposable `build/page-map-<SCENE>.jsonl` files are produced by editor/development Godot runs and map
runtime ADV page ordinals to their authoritative script offsets for `tools/locate_page.py`. Packaged exports runtime ADV page ordinals to their authoritative script offsets for `tools/locate_page.py`. Packaged exports

View File

@@ -636,6 +636,12 @@ do not mix mechanical moves with semantic changes.
existing positions and routes them through `StepMovie`; the original instruction remains available for the existing positions and routes them through `StepMovie`; the original instruction remains available for the
two bytecode-offset compatibility paths. Runtime validation remains green. two bytecode-offset compatibility paths. Runtime validation remains green.
The third bounded `VirtualMachine.Step` extraction moved surface allocation/loading, texture binding and
sizing, mutable surface fill/copy, render-target control, and transient-surface release into
`engine/Age.Engine/Vm/VirtualMachine.Surface.cs`. The top-level dispatcher retains these labels at their
existing positions, including the separate routing groups around numeric-glyph and retained-object cases;
public VM behavior and case-body logic remain unchanged. Runtime validation remains green.
**Gate:** no externally visible behavior or command changes; generated artifacts are byte-identical where **Gate:** no externally visible behavior or command changes; generated artifacts are byte-identical where
deterministic, and the corresponding engine, Python, Godot, and corpus validations remain green after deterministic, and the corresponding engine, Python, Godot, and corpus validations remain green after
each domain move. each domain move.
@@ -1007,8 +1013,8 @@ layer's rendering diverges from ADV; save layout.
## 8. Immediate next step ## 8. Immediate next step
Continue step 2 of the **codebase consolidation** maintenance slice: behavior-neutral physical splits backed Continue step 2 of the **codebase consolidation** maintenance slice: behavior-neutral physical splits backed
by the tracked launcher and layered validation driver. With the planned `Main`, `GodotAdvHost`, and `GfxState` by the tracked launcher and layered validation driver. With the planned `Main`, `GodotAdvHost`, and `GfxState`
domains isolated and the first two `VirtualMachine.Step` families routed through domain handlers, extract the domains isolated and the audio, movie, and surface/texture `VirtualMachine.Step` families routed through domain
surface/texture opcode family next without replacing the proven dispatcher or changing public types, commands, handlers, extract the retained-object query/mutation and transform opcode family next without replacing the
and generated output. proven dispatcher or changing public types, commands, and generated output.
Concrete playthrough blockers may still preempt this bounded maintenance work; the consolidation effort does Concrete playthrough blockers may still preempt this bounded maintenance work; the consolidation effort does
not replace Phase B gameplay validation or the open cross-platform gates. not replace Phase B gameplay validation or the open cross-platform gates.

View File

@@ -0,0 +1,78 @@
using Age.Engine.Hosting;
using Age.Engine.Model;
namespace Age.Engine.Vm;
public sealed partial class VirtualMachine
{
private int StepSurface(string label, IReadOnlyList<Operand> a, int pc)
{
switch (label)
{
case "create-texture": // 0x1f8 (slot)(w)(h) — allocate a blank surface at the slot
_host.ReleaseSurface((int)Read(a[0]));
Gfx.CreateSurface((int)Read(a[0]));
_host.CreateTexture((int)Read(a[0]), (int)Read(a[1]), (int)Read(a[2])); return pc + 1;
case "set-texture": // 0x1f9 (resId)(slot)(colorkey) — load a file into the slot's surface
{
long resourceId = Read(a[0]);
if (_diagSetTexture) // AGE_DIAG_SETTEX: log the SLOT operand source (literal vs which global) — grey-BG slot dig
System.Console.Error.WriteLine($"[settex] resId=0x{resourceId:x} slot={(int)Read(a[1])} " +
$"slotOp=(type={a[1].Type} val=0x{a[1].Value:x}){(a[1].Type == 3 ? $" G[0x{a[1].Value:x}]" : "")}");
_host.ReleaseSurface((int)Read(a[1]));
long colorKey = a.Count > 2 ? Read(a[2]) : -1;
Gfx.SetSurface((int)Read(a[1]), resourceId, colorKey);
_host.SetTexture(resourceId, (int)Read(a[1]), colorKey);
return pc + 1; // host still tracks dims for get-texture-size
}
case "u00422E80": // pre-reference compatibility
case "set-tiled-surface-edge-length": // 0x248 (edge pixels)
Gfx.SetTiledSurfaceEdgeLength(Read(a[0]));
return pc + 1;
case "u00422EB0": // pre-reference compatibility
case "load-raw-texture-surface": // 0x249 (packed resource id)(slot)(colorkey)
{
// Native shares 0x1f9's release/load/colorkey path, but constructs its mode-1
// surface subclass. Both texture opcodes receive the same universal packed id;
// the CPU compositor does not need the D3D subclass distinction.
long resourceId = Read(a[0]);
int surfaceSlot = (int)Read(a[1]);
_host.ReleaseSurface(surfaceSlot);
Gfx.SetSurface(surfaceSlot, resourceId, Read(a[2]));
_host.SetTexture(resourceId, surfaceSlot, Read(a[2]));
return pc + 1;
}
case "draw-texture": // 0x1fb (handle)(slot)(srcX)(srcY)(w)(h)(dstX)(dstY) — bind object -> surface + rect + pos
Gfx.BindDraw(Read(a[0]), (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]));
_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; // IHost seam (oracle log; Godot no-ops)
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 "fill-surface-rect": // 0x20b: clipped alpha/RGB fill of a mutable surface
case "u00420D50":
_host.FillSurfaceRect(new SurfaceRectFill(
(int)Read(a[0]), (int)Read(a[1]), (int)Read(a[2]), (int)Read(a[3]), (int)Read(a[4]),
(int)System.Math.Min(Read(a[5]), 255), Read(a[6]) & 0x00ff_ffff));
return pc + 1;
case "copy-surface-rect": // 0x207: paired-clipped source-to-destination surface copy
_host.CopySurfaceRect(new SurfaceRectCopy(
(int)Read(a[0]), (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 "select-render-target": // 0x20d: slot <1000 selects a surface; >=1000 restores backbuffer
Gfx.SelectRenderTarget(Read(a[0])); return pc + 1;
case "clear-render-target": // 0x20e: clear color to black and depth to one
_host.ClearRenderTarget(Gfx.CurrentRenderTargetSlot); return pc + 1;
case "release-transient-surfaces": // 0x23d: native fixed range [42,1000)
Gfx.ReleaseSurfaceRange(42, 1000 - 42);
_host.ReleaseSurfaceRange(42, 1000 - 42); return pc + 1;
default:
throw new InvalidOperationException($"Non-surface opcode routed to surface handler: {label}");
}
}
}

View File

@@ -2449,44 +2449,14 @@ public sealed partial class VirtualMachine
_advTextStyle = _advTextStyle with { FontFace = ReadStr(a[0]) }; return pc + 1; _advTextStyle = _advTextStyle with { FontFace = ReadStr(a[0]) }; return pc + 1;
case "comment": case "display-furigana": case "dev_ukn": case "comment": case "display-furigana": case "dev_ukn":
return pc + 1; return pc + 1;
case "create-texture": // 0x1f8 (slot)(w)(h) — allocate a blank surface at the slot case "create-texture":
_host.ReleaseSurface((int)Read(a[0])); case "set-texture":
Gfx.CreateSurface((int)Read(a[0]));
_host.CreateTexture((int)Read(a[0]), (int)Read(a[1]), (int)Read(a[2])); return pc + 1;
case "set-texture": // 0x1f9 (resId)(slot)(colorkey) — load a file into the slot's surface
{
long resourceId = Read(a[0]);
if (_diagSetTexture) // AGE_DIAG_SETTEX: log the SLOT operand source (literal vs which global) — grey-BG slot dig
System.Console.Error.WriteLine($"[settex] resId=0x{resourceId:x} slot={(int)Read(a[1])} " +
$"slotOp=(type={a[1].Type} val=0x{a[1].Value:x}){(a[1].Type == 3 ? $" G[0x{a[1].Value:x}]" : "")}");
_host.ReleaseSurface((int)Read(a[1]));
long colorKey = a.Count > 2 ? Read(a[2]) : -1;
Gfx.SetSurface((int)Read(a[1]), resourceId, colorKey);
_host.SetTexture(resourceId, (int)Read(a[1]), colorKey);
return pc + 1; // host still tracks dims for get-texture-size
}
case "u00422E80": // pre-reference compatibility case "u00422E80": // pre-reference compatibility
case "set-tiled-surface-edge-length": // 0x248 (edge pixels) case "set-tiled-surface-edge-length": // 0x248 (edge pixels)
Gfx.SetTiledSurfaceEdgeLength(Read(a[0]));
return pc + 1;
case "u00422EB0": // pre-reference compatibility case "u00422EB0": // pre-reference compatibility
case "load-raw-texture-surface": // 0x249 (packed resource id)(slot)(colorkey) case "load-raw-texture-surface": // 0x249 (packed resource id)(slot)(colorkey)
{ case "draw-texture":
// Native shares 0x1f9's release/load/colorkey path, but constructs its mode-1 return StepSurface(label, a, pc);
// surface subclass. Both texture opcodes receive the same universal packed id;
// the CPU compositor does not need the D3D subclass distinction.
long resourceId = Read(a[0]);
int surfaceSlot = (int)Read(a[1]);
_host.ReleaseSurface(surfaceSlot);
Gfx.SetSurface(surfaceSlot, resourceId, Read(a[2]));
_host.SetTexture(resourceId, surfaceSlot, Read(a[2]));
return pc + 1;
}
case "draw-texture": // 0x1fb (handle)(slot)(srcX)(srcY)(w)(h)(dstX)(dstY) — bind object -> surface + rect + pos
Gfx.BindDraw(Read(a[0]), (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]));
_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; // IHost seam (oracle log; Godot no-ops)
case "u0041F3A0": case "u0041F3A0":
case "register-numeric-glyph-style": // 0x13a: (style)(surface)(atlas x/y)(digit w/h) case "register-numeric-glyph-style": // 0x13a: (style)(surface)(atlas x/y)(digit w/h)
{ {
@@ -2515,32 +2485,17 @@ public sealed partial class VirtualMachine
} }
return pc + 1; return pc + 1;
} }
case "get-texture-size": // 0x208 (slot) (out_w) (out_h) case "get-texture-size":
{ case "fill-surface-rect":
var (gw, gh) = _host.GetTextureSize((int)Read(a[0]));
Write(a[1], gw); Write(a[2], gh);
return pc + 1;
}
case "fill-surface-rect": // 0x20b: clipped alpha/RGB fill of a mutable surface
case "u00420D50": case "u00420D50":
_host.FillSurfaceRect(new SurfaceRectFill( case "copy-surface-rect":
(int)Read(a[0]), (int)Read(a[1]), (int)Read(a[2]), (int)Read(a[3]), (int)Read(a[4]), return StepSurface(label, a, pc);
(int)System.Math.Min(Read(a[5]), 255), Read(a[6]) & 0x00ff_ffff));
return pc + 1;
case "copy-surface-rect": // 0x207: paired-clipped source-to-destination surface copy
_host.CopySurfaceRect(new SurfaceRectCopy(
(int)Read(a[0]), (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 "clear-retained-gfx-objects": // 0x1f6: erase object records, but preserve surfaces case "clear-retained-gfx-objects": // 0x1f6: erase object records, but preserve surfaces
Gfx.ClearRetainedObjects(); return pc + 1; Gfx.ClearRetainedObjects(); return pc + 1;
case "select-render-target": // 0x20d: slot <1000 selects a surface; >=1000 restores backbuffer case "select-render-target":
Gfx.SelectRenderTarget(Read(a[0])); return pc + 1; case "clear-render-target":
case "clear-render-target": // 0x20e: clear color to black and depth to one case "release-transient-surfaces":
_host.ClearRenderTarget(Gfx.CurrentRenderTargetSlot); return pc + 1; return StepSurface(label, a, pc);
case "release-transient-surfaces": // 0x23d: native fixed range [42,1000)
Gfx.ReleaseSurfaceRange(42, 1000 - 42);
_host.ReleaseSurfaceRange(42, 1000 - 42); return pc + 1;
case "play-bgm": case "play-bgm":
case "restart-bgm-loop": case "restart-bgm-loop":
case "stop-bgm": case "stop-bgm":