fix(gfx): 0x1f7 is a registry ERASE, not a create

RE correction: op 0x1f7's worker gfx_registry_erase_range (@0x47d8b0) loops
gfx_registry_erase over [handle, handle+count) — a teardown, not a create.
Objects are created lazily by the geometry SET ops (gfx_object_get_or_create).
GfxState.EraseRange + VM case + tests; opcodes.toml renamed gfx-elem-create ->
gfx-elem-erase; Ghidra handler + workers annotated. Booted SC0000 CG geometry
unchanged for the working CGs (no regression). Engine 40 green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
gamer147
2026-07-07 19:05:31 -04:00
parent 7feb282657
commit fa23b78f26
5 changed files with 40 additions and 9 deletions

View File

@@ -38,4 +38,25 @@ public class GfxStateTests
[Fact]
public void PackColorPacksArgb()
=> Assert.Equal(0x80_112233L, GfxState.PackColor(0x80, 0x112233));
[Fact]
public void EraseRangeRemovesHandlesInRange()
{
var g = new GfxState();
g.GetOrCreate(0x10); g.GetOrCreate(0x11); g.GetOrCreate(0x12); g.GetOrCreate(0x20);
g.EraseRange(0x10, 3); // count>1 → erase [0x10, 0x13)
Assert.Equal(-1, g.QuerySlot(0x10));
Assert.Equal(-1, g.QuerySlot(0x12));
Assert.NotEqual(-1, g.QuerySlot(0x20)); // outside the range, kept
}
[Fact]
public void EraseRangeCountLeOneErasesSingleHandle()
{
var g = new GfxState();
g.GetOrCreate(0x10); g.GetOrCreate(0x11);
g.EraseRange(0x10, 1); // count<=1 → single handle
Assert.Equal(-1, g.QuerySlot(0x10));
Assert.NotEqual(-1, g.QuerySlot(0x11));
}
}

View File

@@ -54,6 +54,15 @@ public sealed class GfxState
if (_objects.TryGetValue(handle, out var o)) { if (o.Slot >= 0) _free.Add(o.Slot); _objects.Remove(handle); }
}
/// <summary>Op 0x1f7 semantics (native gfx_registry_erase_range @0x47d8b0): erase handles in
/// [handle, handle+count) when count>1, else just <paramref name="handle"/>. It is a teardown/erase,
/// NOT a create — objects are created lazily by the geometry SET ops (gfx_object_get_or_create).</summary>
public void EraseRange(long handle, long count)
{
if (count > 1) for (long i = handle; i < handle + count; i++) Release(i);
else Release(handle);
}
/// <summary>Pack (alpha, rgb) → 0xAARRGGBB, matching op 0x202/0x203's handler bit-manipulation for the
/// common (non-negative-sentinel) case. The alpha&lt;0 / color&lt;0 native-fetch path is deferred.</summary>
public static long PackColor(long alpha, long color)

View File

@@ -253,9 +253,10 @@ public sealed class VirtualMachine
{
var o = Gfx.GetOrCreate(Read(a[0])); o.Field68 = Read(a[1]); o.Field6c = Read(a[2]); return pc + 1;
}
case "gfx-cmd-register": // 0x1a2 (val) — register/select
case "gfx-elem-create": // 0x1f7 (handle)(count) — ensure object + slot
case "gfx-cmd-register": // 0x1a2 (val) — register/insert
Gfx.GetOrCreate(Read(a[0])); return pc + 1;
case "gfx-elem-erase": // 0x1f7 (handle)(count) — erase registry range (teardown, NOT create)
Gfx.EraseRange(Read(a[0]), Read(a[1])); return pc + 1;
case "gfx-elem-release": // 0x1fa (handle)
Gfx.Release(Read(a[0])); return pc + 1;
case "gfx-blit-color": // 0x202 (handle)(x)(y)(alpha)(color) — blend deferred