From a29bd52045c3e5a43fa9b1434334f1139a1a0e5e Mon Sep 17 00:00:00 2001 From: gamer147 Date: Tue, 21 Jul 2026 09:23:18 -0400 Subject: [PATCH] Implement startup string and block copy opcodes --- docs/engine-re.md | 32 +++++- docs/opcode-reference.md | 13 +-- docs/phase-b-framework.md | 15 ++- .../NaturalBootIntegrationTests.cs | 10 +- .../PointerAndBlockCopyOpsTests.cs | 87 +++++++++++++++++ .../StringComparisonOpsTests.cs | 97 +++++++++++++++++++ engine/Age.Engine/Vm/VirtualMachine.cs | 43 +++++++- vm-map/opcodes.toml | 22 ++--- 8 files changed, 288 insertions(+), 31 deletions(-) create mode 100644 engine/Age.Engine.Tests/PointerAndBlockCopyOpsTests.cs create mode 100644 engine/Age.Engine.Tests/StringComparisonOpsTests.cs diff --git a/docs/engine-re.md b/docs/engine-re.md index 309d3b3..de0ecca 100644 --- a/docs/engine-re.md +++ b/docs/engine-re.md @@ -2197,11 +2197,33 @@ It is therefore an equality predicate, not a string assignment. The release corp conditional branches; `GAMESTART@0x134c` compares `INPUTNAME` with `"?"`, while INIT2 also compares `INPUTNAME` with an empty string during default-name initialization. -`op_0x1b0_copy_dwords@0x427060` fetches operand 3 as a cell count, resolves operands 1 and 2 as source and -destination pointers, and calls `memcpy(destination, source, count * 4)`. The capture reached it three times -inside `UNITECH`/`CALCCC`, immediately after opcode `0x63`. The copy itself is proven; the pointer-producing -semantics of `0x63` remain unresolved, so the pair should be implemented only after that companion handler -is understood. +The port now executes `0x194` through the common string resolver and ordinal equality, so every operand +form accepted by that resolver shares one contract: inline literal, global string, local string, global +string pointer, and local string pointer. Focused tests cover equality and inequality across those forms +plus the release GAMESTART compare-then-`jcc` shape. The full traced SYSTEM4-to-SC0000 regression reaches +GAMESTART without emitting a `0x194` fallback. + +`op_0x63_take_address@0x426ac0` is the companion address operation. It passes operand 2 and unsubscripted +indices `-1/-1` to `vm_operand_resolve_address@0x425a50`, then stores the returned address in operand 1 +through `vm_pointer_operand_write@0x416090`. The resolver returns the backing-cell address for direct +global/local integer or string operands; for pointer operands it returns the target already held by the +pointer, not the address of the pointer slot. Thus `0x63(dst_ptr, source)` is typed address aliasing. All 92 +release-corpus sites use a local integer-pointer destination; sources are local pointers 81 times, local +integers seven times, and global integers four times. + +`op_0x1b0_copy_dwords@0x427060` fetches operand 3 as a cell count, resolves addressable operands 1 and 2 as +source and destination, and calls `memcpy(destination, source, count * 4)`. The corpus has 65 calls: direct +global/local spans as well as local pointers, with 43 immediately preceded by `0x63`. The boot capture +reached the pair in `UNITECH`/`CALCCC`; those scripts use it to copy record-shaped arrays between per-entity +tables and working buffers. + +The port now maps both operations onto its domain-preserving `VmAddress` model. Direct local/global cells +retain their bank, aliasing an existing pointer retains its target bank, and `0x1b0` copies consecutive +32-bit integer cells through the resolved endpoints. Focused tests cover local-to-global, global-to-local, +an alias of a `lookup-array` result, direct spans, and the native string-pointer destination form of +`0x63`. The step-traced SYSTEM4-to-SC0000 regression reaches both operations without fallback. Static +coverage is consequently 31/31 handled for UNITECH and 14/15 for CALCCC; CALCCC's only remaining gap is +the deliberately deferred shared-profile write `0x1a2`. --- diff --git a/docs/opcode-reference.md b/docs/opcode-reference.md index 458f93c..6ccabda 100644 --- a/docs/opcode-reference.md +++ b/docs/opcode-reference.md @@ -186,6 +186,11 @@ Operand 2 names the base cell itself: a global-bank operand produces a global reference and a local-bank operand produces a local reference. Operand 3 is added as the element offset. Pointer destinations retain that address domain; reading or writing the pointer dereferences the corresponding bank. Non-pointer destinations receive the addressed value. The same domain-preserving address model applies to lookup-array-2d (0x12c). +### 0x63 `take-address` (take-address, argc 2) +- **summary:** (destination_pointer)(source) - store the underlying typed storage address of source in destination_pointer; a pointer source aliases its existing target. +- **grounding:** source=investigation, confidence=high +- **evidence:** Ghidra /v2 op_0x63_take_address@0x426ac0 calls vm_operand_resolve_address@0x425a50 for operand 2 with unsubscripted indices -1/-1, then vm_pointer_operand_write@0x416090 for operand 1. The resolver returns a backing-cell address for direct global/local integer or string operands and the already-stored target for pointer operands. Corpus: 92 calls; destination is always local-ptr, while sources are local-ptr x81, local-int x7, and global-int x4. The C# VM preserves the local/global address domain and supports the native string-pointer destination forms; the traced natural boot reaches the UNITECH/CALCCC pair without fallback. + ### 0x64 `copy-inline-int-array` (copy-inline-int-array, argc 2) - **summary:** (destination)(inline_blob_offset) - decode the count-prefixed integer literal blob at codebase + offset*4 and copy its values to consecutive VM integer cells beginning at destination. - **grounding:** source=investigation, confidence=high @@ -209,12 +214,12 @@ Operand 2 names the base cell itself: a global-bank operand produces a global re ### 0x194 `string-equals` (string-equals, argc 3) - **summary:** (out)(left)(right) - compare two complete SYS4 strings and write 1 when equal, otherwise 0. - **grounding:** source=investigation, confidence=high -- **evidence:** Ghidra /v2 op_0x194_string_equals@0x426e20 fetches operands 2 and 3 through the string resolver, compares their byte ranges through FUN_004017a0, and writes compare_result==0 to integer operand 1. INIT2 and GAMESTART use it as a branch predicate for INPUTNAME/default-name handling; the natural Game Start diagnostic reached one GAMESTART call at 0x134c. +- **evidence:** Ghidra /v2 op_0x194_string_equals@0x426e20 fetches operands 2 and 3 through the string resolver, compares their byte ranges through FUN_004017a0, and writes compare_result==0 to integer operand 1. INIT2 and GAMESTART use it as a branch predicate for INPUTNAME/default-name handling; the natural Game Start diagnostic reached one GAMESTART call at 0x134c. The C# VM implements ordinal equality through the shared string resolver, covering literal, global, local, global-string-pointer, and local-string-pointer operands; the traced natural-boot regression proves the reached GAMESTART call no longer falls back. ### 0x1b0 `copy-dwords` (copy-dwords, argc 3) - **summary:** (source)(destination)(count) - copy count consecutive 32-bit cells from source to destination. - **grounding:** source=investigation, confidence=high -- **evidence:** Ghidra /v2 op_0x1b0_copy_dwords@0x427060 fetches operand 3, resolves pointer operands 1 and 2, and calls memcpy(destination, source, count*4). The natural Game Start diagnostic reached it three times in UNITECH/CALCCC initialization, paired with unresolved pointer-preparation opcode 0x63. +- **evidence:** Ghidra /v2 op_0x1b0_copy_dwords@0x427060 fetches operand 3, resolves addressable operands 1 and 2 through vm_operand_resolve_address@0x425a50, and calls memcpy(destination, source, count*4). Corpus: 65 calls across direct global/local spans and local pointers; 43 are immediately preceded by take-address 0x63. The C# VM copies resolved integer-cell spans while retaining local/global address domains; focused tests cover direct spans and aliased pointers, and the traced natural boot reaches the UNITECH/CALCCC pair without fallback. ## control @@ -918,10 +923,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=med -### 0x63 `u00414A60` (u00414A60, argc 2) -- **summary:** — -- **grounding:** source=kelebek, confidence=low - ### 0x6e `show-text` (show-text, argc 2) - **summary:** — - **grounding:** source=kelebek, confidence=med diff --git a/docs/phase-b-framework.md b/docs/phase-b-framework.md index 4bc1822..ec5307f 100644 --- a/docs/phase-b-framework.md +++ b/docs/phase-b-framework.md @@ -272,10 +272,17 @@ The 137 fallback events are not a single boot blocker. Most are declaration/stat proven safe, or deliberately deferred profile/read-text operations (`0x1a2`, `0x1a3`, `0x1cb`). The reached effectful unknowns divide into SYSTEM4 layout setup and unit-data initialization. Native follow-up identifies `0x194` as a string-equality predicate reached in `INIT2` and `GAMESTART`, and `0x1b0` as a dword-block copy -paired with still-unresolved pointer-preparation opcode `0x63` in `UNITECH`/`CALCCC`. This makes `0x194` the -smallest directly boot-relevant implementation slice; the `0x63`/`0x1b0` pair is the larger subsequent -backend-data slice. Closing Godot currently releases a parked ADV wait before process teardown, so the page -map may contain one trailing shutdown-only page; the final timeline `input-wait` is the authoritative stop. +paired with still-unresolved pointer-preparation opcode `0x63` in `UNITECH`/`CALCCC`. `0x194` is now +implemented for every supported SYS4 string operand form, with focused equality/inequality and +compare-then-branch regressions. A step-traced natural-boot test reaches SC0000 without a `0x194` fallback; +static coverage now reports 10/12 INIT2 opcodes and 44/47 GAMESTART opcodes handled, with the remaining +GAMESTART profile operations still deliberately deferred. The reached `0x63`/`0x1b0` unit-data pair is +also implemented: native `0x63` aliases a typed backing-cell address into a pointer, while `0x1b0` copies a +counted dword span through direct or pointer endpoints. The traced natural boot reaches both without +fallback; UNITECH is now 31/31 handled and CALCCC 14/15, with only deferred profile op `0x1a2` remaining. +The next reached effectful cluster to investigate is SYSTEM4's paired `0x79`/`0x1c1` setup. Closing Godot +currently releases a parked ADV wait before process teardown, so the page map may contain one trailing +shutdown-only page; the final timeline `input-wait` is the authoritative stop. ## Stage B2 — Faithful full boot diff --git a/engine/Age.Engine.Tests/NaturalBootIntegrationTests.cs b/engine/Age.Engine.Tests/NaturalBootIntegrationTests.cs index 87b89d3..88ab2b0 100644 --- a/engine/Age.Engine.Tests/NaturalBootIntegrationTests.cs +++ b/engine/Age.Engine.Tests/NaturalBootIntegrationTests.cs @@ -10,11 +10,17 @@ public class NaturalBootIntegrationTests private sealed class StopAtSc0000Sink : ITraceSink { public readonly List Entered = new(); + public bool SawStringEqualsStub; + public bool SawUnitDataCopyStub; public Action? OnEnter; - public bool TracingSteps => false; + public bool TracingSteps => true; public void Emit(in TraceEvent e) { + if (e.Kind == TraceEventKind.Stub && e.Opcode == 0x194) + SawStringEqualsStub = true; + if (e.Kind == TraceEventKind.Stub && e.Opcode is 0x63 or 0x1b0) + SawUnitDataCopyStub = true; if (e.Kind == TraceEventKind.FrameEnter && e.Name != null) { Entered.Add(e.Name); @@ -114,5 +120,7 @@ public class NaturalBootIntegrationTests Assert.Equal(1, vm.Globals.GetValueOrDefault(0)); Assert.Equal(0x22, vm.Globals.GetValueOrDefault(0x699)); Assert.Equal(1, vm.Globals.GetValueOrDefault(0x6c1)); + Assert.False(sink.SawStringEqualsStub); + Assert.False(sink.SawUnitDataCopyStub); } } diff --git a/engine/Age.Engine.Tests/PointerAndBlockCopyOpsTests.cs b/engine/Age.Engine.Tests/PointerAndBlockCopyOpsTests.cs new file mode 100644 index 0000000..931c308 --- /dev/null +++ b/engine/Age.Engine.Tests/PointerAndBlockCopyOpsTests.cs @@ -0,0 +1,87 @@ +using System; +using System.Collections.Generic; +using Age.Engine.Model; +using Age.Engine.Sys4; +using Age.Engine.Vm; +using Xunit; + +public class PointerAndBlockCopyOpsTests +{ + private const int Immediate = 0, InlineString = 2, GlobalInt = 3, GlobalString = 5, + LocalInt = 9, LocalPointer = 12, LocalStringPointer = 14; + + [Fact] + public void TakeAddressAndCopyDwords_PreserveLocalAndGlobalAddressDomains() + { + var table = OpcodeTableJson.Load(Paths.OpcodesJson); + int move = table.ByLabel("mov")!.Value; + int lookup = table.ByLabel("lookup-array")!.Value; + var script = ScriptAssembler.Assemble(table, "POINTER_BLOCK_COPY", new List<(int, Operand[])> + { + (move, new[] { new Operand(LocalInt, 0), new Operand(Immediate, 11) }), + (move, new[] { new Operand(LocalInt, 1), new Operand(Immediate, 22) }), + (move, new[] { new Operand(LocalInt, 2), new Operand(Immediate, 33) }), + (0x63, new[] { new Operand(LocalPointer, 0), new Operand(LocalInt, 0) }), + (0x63, new[] { new Operand(LocalPointer, 1), new Operand(GlobalInt, 0x700) }), + (0x1b0, new[] + { + new Operand(LocalPointer, 0), new Operand(LocalPointer, 1), new Operand(Immediate, 3), + }), + + // A pointer source is aliased to its target, rather than to the pointer slot itself. + (lookup, new[] + { + new Operand(LocalPointer, 2), new Operand(GlobalInt, 0x710), new Operand(Immediate, 2), + }), + (0x63, new[] { new Operand(LocalPointer, 3), new Operand(LocalPointer, 2) }), + (0x1b0, new[] + { + new Operand(LocalPointer, 3), new Operand(GlobalInt, 0x720), new Operand(Immediate, 2), + }), + + // Direct operands resolve as the starts of consecutive cell spans. + (0x1b0, new[] + { + new Operand(GlobalInt, 0x700), new Operand(LocalInt, 10), new Operand(Immediate, 3), + }), + (move, new[] { new Operand(GlobalInt, 0x730), new Operand(LocalInt, 10) }), + (move, new[] { new Operand(GlobalInt, 0x731), new Operand(LocalInt, 11) }), + (move, new[] { new Operand(GlobalInt, 0x732), new Operand(LocalInt, 12) }), + (0x2, Array.Empty()), + }, Array.Empty()); + var vm = new VirtualMachine(script, table, new RecordingHost()); + vm.Globals[0x712] = 44; + vm.Globals[0x713] = 55; + + vm.Run(); + + Assert.Equal(new long[] { 11, 22, 33 }, new[] + { + vm.Globals[0x700], vm.Globals[0x701], vm.Globals[0x702], + }); + Assert.Equal(new long[] { 44, 55 }, new[] { vm.Globals[0x720], vm.Globals[0x721] }); + Assert.Equal(new long[] { 11, 22, 33 }, new[] + { + vm.Globals[0x730], vm.Globals[0x731], vm.Globals[0x732], + }); + } + + [Fact] + public void TakeAddress_SupportsTheNativeStringPointerDestination() + { + var table = OpcodeTableJson.Load(Paths.OpcodesJson); + int move = table.ByLabel("mov")!.Value; + var script = ScriptAssembler.Assemble(table, "STRING_ADDRESS", new List<(int, Operand[])> + { + (0x63, new[] { new Operand(LocalStringPointer, 0), new Operand(GlobalString, 0x740) }), + (move, new[] { new Operand(LocalStringPointer, 0), new Operand(InlineString, 0) }), + (0x2, Array.Empty()), + }, new[] { "aliased" }); + var vm = new VirtualMachine(script, table, new RecordingHost()); + vm.GlobalStrings[0x740] = "before"; + + vm.Run(); + + Assert.Equal("aliased", vm.GlobalStrings[0x740]); + } +} diff --git a/engine/Age.Engine.Tests/StringComparisonOpsTests.cs b/engine/Age.Engine.Tests/StringComparisonOpsTests.cs new file mode 100644 index 0000000..e6473d4 --- /dev/null +++ b/engine/Age.Engine.Tests/StringComparisonOpsTests.cs @@ -0,0 +1,97 @@ +using System; +using System.Collections.Generic; +using Age.Engine.Model; +using Age.Engine.Sys4; +using Age.Engine.Vm; +using Xunit; + +public class StringComparisonOpsTests +{ + private const int Immediate = 0, InlineString = 2, GlobalInt = 3, GlobalString = 5, + GlobalStringPointer = 8, LocalInt = 9, LocalString = 11, + LocalStringPointer = 14; + + [Fact] + public void StringEquals_HandlesLiteralGlobalLocalAndPointerOperands() + { + var table = OpcodeTableJson.Load(Paths.OpcodesJson); + int lookup = table.ByLabel("lookup-array")!.Value; + int move = table.ByLabel("mov")!.Value; + var script = ScriptAssembler.Assemble(table, "STRING_EQUALS", new List<(int, Operand[])> + { + (move, new[] { new Operand(LocalString, 0), new Operand(InlineString, 0) }), + (lookup, new[] + { + new Operand(LocalStringPointer, 0), new Operand(GlobalString, 0x300), + new Operand(Immediate, 2), + }), + (0x194, new[] + { + new Operand(LocalInt, 0), new Operand(InlineString, 0), new Operand(InlineString, 1), + }), + (0x194, new[] + { + new Operand(LocalInt, 1), new Operand(GlobalString, 0x301), new Operand(InlineString, 0), + }), + (0x194, new[] + { + new Operand(LocalInt, 2), new Operand(LocalString, 0), new Operand(InlineString, 0), + }), + (0x194, new[] + { + new Operand(LocalInt, 3), new Operand(LocalStringPointer, 0), new Operand(InlineString, 0), + }), + (0x194, new[] + { + new Operand(LocalInt, 4), new Operand(GlobalStringPointer, 0x400), + new Operand(InlineString, 0), + }), + (move, new[] { new Operand(GlobalInt, 0x500), new Operand(LocalInt, 0) }), + (move, new[] { new Operand(GlobalInt, 0x501), new Operand(LocalInt, 1) }), + (move, new[] { new Operand(GlobalInt, 0x502), new Operand(LocalInt, 2) }), + (move, new[] { new Operand(GlobalInt, 0x503), new Operand(LocalInt, 3) }), + (move, new[] { new Operand(GlobalInt, 0x504), new Operand(LocalInt, 4) }), + (0x2, Array.Empty()), + }, new[] { "姫狩り", "姫狩り", "姫狩り違い" }); + var vm = new VirtualMachine(script, table, new RecordingHost()); + vm.GlobalStrings[0x301] = "姫狩り違い"; + vm.GlobalStrings[0x302] = "姫狩り"; + vm.Globals[0x400] = 0x302; + + vm.Run(); + + Assert.Equal(new long[] { 1, 0, 1, 1, 1 }, new[] + { + vm.Globals[0x500], vm.Globals[0x501], vm.Globals[0x502], + vm.Globals[0x503], vm.Globals[0x504], + }); + } + + [Fact] + public void StringEquals_DrivesAGameStartShapedConditionalBranch() + { + var table = OpcodeTableJson.Load(Paths.OpcodesJson); + var script = ScriptAssembler.Assemble(table, "GAMESTART_STRING_BRANCH", new List<(int, Operand[])> + { + // These offsets mirror the release idiom: compare INPUTNAME with a literal, then jcc. + (0x194, new[] + { + new Operand(LocalInt, 0), new Operand(GlobalString, 0x52d), new Operand(InlineString, 0), + }), + (0xa0, new[] + { + new Operand(LocalInt, 0), new Operand(Immediate, 19), + new Operand(Immediate, unchecked((long)0xffffffff)), + }), + (0x55, new[] { new Operand(GlobalInt, 0x600), new Operand(Immediate, 99) }), + (0x2, Array.Empty()), + }, new[] { "?" }); + var vm = new VirtualMachine(script, table, new RecordingHost()); + vm.GlobalStrings[0x52d] = "?"; + vm.Globals[0x600] = 1; + + vm.Run(); + + Assert.Equal(1, vm.Globals[0x600]); + } +} diff --git a/engine/Age.Engine/Vm/VirtualMachine.cs b/engine/Age.Engine/Vm/VirtualMachine.cs index 78029f2..91791f7 100644 --- a/engine/Age.Engine/Vm/VirtualMachine.cs +++ b/engine/Age.Engine/Vm/VirtualMachine.cs @@ -289,19 +289,28 @@ public sealed class VirtualMachine T_LINT => VmAddress.LocalInteger((int)op.Value), T_LFLOAT => VmAddress.LocalFloat((int)op.Value), T_LSTR => VmAddress.LocalString((int)op.Value), + T_GPTR or T_GSTRPTR => VmAddress.Global((int)Gi(Globals, (int)op.Value)), T_LPTR => Ga(_cur.Locals.P, (int)op.Value), T_LSTRPTR => Ga(_cur.Locals.SP, (int)op.Value), _ => VmAddress.Global((int)op.Value), }; + private bool TryStoreAddress(Operand destination, VmAddress address) + { + switch (destination.Type) + { + case T_LPTR: _cur.Locals.P[(int)destination.Value] = address; return true; + case T_LSTRPTR: _cur.Locals.SP[(int)destination.Value] = address; return true; + case T_GPTR: case T_GSTRPTR: Globals[(int)destination.Value] = address.Address; return true; + default: return false; + } + } + private void LookupStore(Operand dst, VmAddress addr) { + if (TryStoreAddress(dst, addr)) return; switch (dst.Type) { - case T_LPTR: _cur.Locals.P[(int)dst.Value] = addr; break; - case T_LSTRPTR: _cur.Locals.SP[(int)dst.Value] = addr; break; - case T_GPTR: Globals[(int)dst.Value] = addr.Address; break; - case T_GSTRPTR: Globals[(int)dst.Value] = addr.Address; break; default: if (IsStr(dst)) WriteStr(dst, ReadStringCell(addr)); else Write(dst, ReadIntCell(addr)); @@ -572,6 +581,9 @@ public sealed class VirtualMachine case "shl": Write(a[0], Read(a[1]) << (int)(Read(a[2]) & 31)); return pc + 1; case "eq": Write(a[0], Read(a[1]) == Read(a[2]) ? 1 : 0); return pc + 1; case "ne": Write(a[0], Read(a[1]) != Read(a[2]) ? 1 : 0); return pc + 1; + case "string-equals": + Write(a[0], string.Equals(ReadStr(a[1]), ReadStr(a[2]), StringComparison.Ordinal) ? 1 : 0); + return pc + 1; case "lt": Write(a[0], Read(a[1]) < Read(a[2]) ? 1 : 0); return pc + 1; case "lte": Write(a[0], Read(a[1]) <= Read(a[2]) ? 1 : 0); return pc + 1; case "gr": Write(a[0], Read(a[1]) > Read(a[2]) ? 1 : 0); return pc + 1; @@ -585,6 +597,13 @@ public sealed class VirtualMachine LookupStore(a[0], BaseAddr(a[1]).Offset(Read(a[2]))); return pc + 1; case "lookup-array-2d": LookupStore(a[0], BaseAddr(a[1]).Offset(Read(a[2]) * Read(a[3]) + Read(a[4]))); return pc + 1; + case "take-address": // 0x63: pointer destination <- underlying address of operand 2 + if (!TryStoreAddress(a[0], BaseAddr(a[1]))) + { + HaltReason ??= $"take-address-destination-type:{a[0].Type}"; + return HALT; + } + return pc + 1; case "copy-inline-int-array": // 0x64: count dword followed by plain file values { int offset = checked((int)Read(a[1])); @@ -605,6 +624,22 @@ public sealed class VirtualMachine WriteConsecutive(a[0], i, unchecked((int)_cur.Script.BodyDwords[offset + 1 + i])); return pc + 1; } + case "copy-dwords": // 0x1b0: memcpy(count * 4) across resolved integer-cell spans + { + int count = checked((int)Read(a[2])); + if (count < 0) + { + HaltReason ??= $"copy-dwords-negative-count:{count}"; + return HALT; + } + VmAddress source = BaseAddr(a[0]); + VmAddress destination = BaseAddr(a[1]); + var values = new long[count]; + for (int i = 0; i < count; i++) + values[i] = unchecked((int)ReadIntCell(source.Offset(i))); + for (int i = 0; i < count; i++) WriteIntCell(destination.Offset(i), values[i]); + return pc + 1; + } case "find-hit-rectangle": // 0x12e: inclusive rectangle intersection over addressed arrays case "u0041E940": { diff --git a/vm-map/opcodes.toml b/vm-map/opcodes.toml index 7a22c5f..6dc1463 100644 --- a/vm-map/opcodes.toml +++ b/vm-map/opcodes.toml @@ -824,28 +824,28 @@ observed_types = ["g-int", "l-int", "l-ptr"] [[opcode]] op = 0x63 -label = "u00414A60" +label = "take-address" argc = 2 abi_source = "kelebek+decode-validated" [opcode.semantics] -name = "u00414A60" -category = "unknown" -summary = "" +name = "take-address" +category = "compute" +summary = "(destination_pointer)(source) - store the underlying typed storage address of source in destination_pointer; a pointer source aliases its existing target." noop_headless = false -source = "kelebek" -confidence = "low" +source = "investigation" +confidence = "high" depends_on = [] -evidence = "" +evidence = "Ghidra /v2 op_0x63_take_address@0x426ac0 calls vm_operand_resolve_address@0x425a50 for operand 2 with unsubscripted indices -1/-1, then vm_pointer_operand_write@0x416090 for operand 1. The resolver returns a backing-cell address for direct global/local integer or string operands and the already-stored target for pointer operands. Corpus: 92 calls; destination is always local-ptr, while sources are local-ptr x81, local-int x7, and global-int x4. The C# VM preserves the local/global address domain and supports the native string-pointer destination forms; the traced natural boot reaches the UNITECH/CALCCC pair without fallback." [[opcode.semantics.args]] i = 1 -role = "" +role = "destination pointer" observed_types = ["l-ptr"] [[opcode.semantics.args]] i = 2 -role = "" +role = "source addressable cell or pointer target" observed_types = ["g-int", "l-int", "l-ptr"] [[opcode]] @@ -3121,7 +3121,7 @@ noop_headless = false source = "investigation" confidence = "high" depends_on = [] -evidence = "Ghidra /v2 op_0x194_string_equals@0x426e20 fetches operands 2 and 3 through the string resolver, compares their byte ranges through FUN_004017a0, and writes compare_result==0 to integer operand 1. INIT2 and GAMESTART use it as a branch predicate for INPUTNAME/default-name handling; the natural Game Start diagnostic reached one GAMESTART call at 0x134c." +evidence = "Ghidra /v2 op_0x194_string_equals@0x426e20 fetches operands 2 and 3 through the string resolver, compares their byte ranges through FUN_004017a0, and writes compare_result==0 to integer operand 1. INIT2 and GAMESTART use it as a branch predicate for INPUTNAME/default-name handling; the natural Game Start diagnostic reached one GAMESTART call at 0x134c. The C# VM implements ordinal equality through the shared string resolver, covering literal, global, local, global-string-pointer, and local-string-pointer operands; the traced natural-boot regression proves the reached GAMESTART call no longer falls back." [[opcode.semantics.args]] i = 1 @@ -3805,7 +3805,7 @@ noop_headless = false source = "investigation" confidence = "high" depends_on = [] -evidence = "Ghidra /v2 op_0x1b0_copy_dwords@0x427060 fetches operand 3, resolves pointer operands 1 and 2, and calls memcpy(destination, source, count*4). The natural Game Start diagnostic reached it three times in UNITECH/CALCCC initialization, paired with unresolved pointer-preparation opcode 0x63." +evidence = "Ghidra /v2 op_0x1b0_copy_dwords@0x427060 fetches operand 3, resolves addressable operands 1 and 2 through vm_operand_resolve_address@0x425a50, and calls memcpy(destination, source, count*4). Corpus: 65 calls across direct global/local spans and local pointers; 43 are immediately preceded by take-address 0x63. The C# VM copies resolved integer-cell spans while retaining local/global address domains; focused tests cover direct spans and aliased pointers, and the traced natural boot reaches the UNITECH/CALCCC pair without fallback." [[opcode.semantics.args]] i = 1