Correct ADV History layout addressing
This commit is contained in:
@@ -1366,6 +1366,20 @@ page. This is frame-scoped callback state, not a HISTORY name/offset special cas
|
|||||||
existing HIDEWIN scheduler family. A real regression activates x=684 in SC0000, runs unmodified HISTORY,
|
existing HIDEWIN scheduler family. A real regression activates x=684 in SC0000, runs unmodified HISTORY,
|
||||||
selects/closes region 8, observes retained text, and returns to the same single page wait.
|
selects/closes region 8, observes retained text, and returns to the same single page wait.
|
||||||
|
|
||||||
|
Manual comparison exposed a separate typed-address bug in the first display build. `HISTORY.BIN` copies its
|
||||||
|
button x/y tables into local integer cells `0x4` and `0x68`, then op `0x61` takes local pointers to selected
|
||||||
|
elements for `draw-texture`. A local base operand names that local cell; it is not a local value containing a
|
||||||
|
global address. The VM now retains the local/global domain in pointer values, so reads and writes through a
|
||||||
|
local pointer reach the correct bank. This restores the six controls at x=768/y=121..411, the close control
|
||||||
|
at x=768/y=549, and the hovered-row highlight while preserving RECOVER's global-array pointer behavior.
|
||||||
|
The Python A0 oracle uses the same typed-address model.
|
||||||
|
|
||||||
|
The History text batches already carry the native x origin 65 and cursor x 45. Their Godot labels were
|
||||||
|
created with a full-rect anchor preset before being parented, which discarded the intended absolute
|
||||||
|
placement in the live UI and caused a Godot parent/layout diagnostic. Dynamically composited ADV labels now
|
||||||
|
use the default top-left anchors and their explicit position/size, yielding the native text x=110 and
|
||||||
|
avoiding that diagnostic.
|
||||||
|
|
||||||
History's remaining work is stored voice replay through `0x1bd` and the `0xd3/0xd4/0xd5` smooth-scroll
|
History's remaining work is stored voice replay through `0x1bd` and the `0xd3/0xd4/0xd5` smooth-scroll
|
||||||
callback scheduler. None changes backlog ownership or requires choosing a save/profile backend.
|
callback scheduler. None changes backlog ownership or requires choosing a save/profile backend.
|
||||||
|
|
||||||
|
|||||||
@@ -149,6 +149,13 @@
|
|||||||
|
|
||||||
## compute
|
## compute
|
||||||
|
|
||||||
|
### 0x61 `lookup-array` (lookup-array, argc 3)
|
||||||
|
- **summary:** Take a typed reference to base[index], preserving whether the base belongs to local or global storage.
|
||||||
|
- **grounding:** source=investigation, confidence=high
|
||||||
|
- **evidence:** HISTORY.BIN copies x/y tables into local-int cells 0x4 and 0x68, then lookup-array local-ptr <- local-int base supplies every right-side button and hovered-row draw coordinate. Treating the local operand's current value as a global base collapses those draws to (0,0); retaining the local address yields the native x=768/y=121..549 positions. RECOVER.BIN independently exercises the same pointer destination with global bases.
|
||||||
|
|
||||||
|
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).
|
||||||
|
|
||||||
### 0x64 `copy-inline-int-array` (copy-inline-int-array, argc 2)
|
### 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.
|
- **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
|
- **grounding:** source=investigation, confidence=high
|
||||||
@@ -760,10 +767,6 @@ op 0x90 (u0041BEB0, argc 7): `0x90 x y w h tgt_a tgt_b tgt_c`. Kelebek left it "
|
|||||||
- **summary:** —
|
- **summary:** —
|
||||||
- **grounding:** source=kelebek, confidence=low
|
- **grounding:** source=kelebek, confidence=low
|
||||||
|
|
||||||
### 0x61 `lookup-array` (lookup-array, argc 3)
|
|
||||||
- **summary:** —
|
|
||||||
- **grounding:** source=kelebek, confidence=med
|
|
||||||
|
|
||||||
### 0x63 `u00414A60` (u00414A60, argc 2)
|
### 0x63 `u00414A60` (u00414A60, argc 2)
|
||||||
- **summary:** —
|
- **summary:** —
|
||||||
- **grounding:** source=kelebek, confidence=low
|
- **grounding:** source=kelebek, confidence=low
|
||||||
|
|||||||
@@ -1775,3 +1775,20 @@ effectful gaps total 13 instructions.
|
|||||||
|
|
||||||
**Next:** implement stored History voice replay (`0x1bd`) against the existing voice host path. Then take
|
**Next:** implement stored History voice replay (`0x1bd`) against the existing voice host path. Then take
|
||||||
`0xd3/0xd4/0xd5` smooth-scroll interpolation as a separate scheduler/fidelity slice.
|
`0xd3/0xd4/0xd5` smooth-scroll interpolation as a separate scheduler/fidelity slice.
|
||||||
|
|
||||||
|
### ADV History manual layout corrections (2026-07-19)
|
||||||
|
|
||||||
|
Manual original/port comparison found that History's fixed rail was correct, but the lookup-driven buttons
|
||||||
|
were clustered at the top-left, the hovered-row artwork disagreed with its hit region, and rendered text
|
||||||
|
lost its x origin. The common VM cause was op `0x61`: `HISTORY.BIN` uses local-int operands as the bases of
|
||||||
|
its copied coordinate arrays, while the port treated the values in those cells as global addresses. Local
|
||||||
|
pointers now retain their local/global address domain, with matching dereference and write-through behavior
|
||||||
|
in both the C# VM and Python oracle. A real-script regression fixes the seven button coordinates at
|
||||||
|
x=768/y=121..549 and confirms every visible row retains layout origin x=65.
|
||||||
|
|
||||||
|
Godot's dynamically created History/surface labels also no longer request full-rect anchors before they have
|
||||||
|
a parent. They use top-left absolute placement, matching the batch's origin+cursor coordinates and removing
|
||||||
|
the associated parent/layout diagnostic.
|
||||||
|
|
||||||
|
**Next:** manually recheck History layout and hover against the original screenshot. If it matches, proceed
|
||||||
|
with stored History voice replay (`0x1bd`), followed by `0xd3/0xd4/0xd5` smooth-scroll fidelity.
|
||||||
|
|||||||
@@ -5,11 +5,12 @@ using Age.Engine.Vm;
|
|||||||
|
|
||||||
public class HistoryInteractionOpsTests
|
public class HistoryInteractionOpsTests
|
||||||
{
|
{
|
||||||
private const int T_IMM = 0, T_GINT = 3, T_LINT = 9;
|
private const int T_IMM = 0, T_GINT = 3, T_LINT = 9, T_LPTR = 12;
|
||||||
private static readonly OpcodeTable Table = OpcodeTableJson.Load(Paths.OpcodesJson);
|
private static readonly OpcodeTable Table = OpcodeTableJson.Load(Paths.OpcodesJson);
|
||||||
private static Operand I(long value) => new(T_IMM, value);
|
private static Operand I(long value) => new(T_IMM, value);
|
||||||
private static Operand G(int address) => new(T_GINT, address);
|
private static Operand G(int address) => new(T_GINT, address);
|
||||||
private static Operand L(int address) => new(T_LINT, address);
|
private static Operand L(int address) => new(T_LINT, address);
|
||||||
|
private static Operand P(int address) => new(T_LPTR, address);
|
||||||
|
|
||||||
private sealed class StopAfterHistoryReturnsException : Exception { }
|
private sealed class StopAfterHistoryReturnsException : Exception { }
|
||||||
|
|
||||||
@@ -20,6 +21,7 @@ public class HistoryInteractionOpsTests
|
|||||||
private int _modalSleeps;
|
private int _modalSleeps;
|
||||||
public bool HistoryReturned;
|
public bool HistoryReturned;
|
||||||
public bool SawRenderedText;
|
public bool SawRenderedText;
|
||||||
|
public IReadOnlyList<RenderObject> FirstHistoryFrame = Array.Empty<RenderObject>();
|
||||||
public override long InputClockMilliseconds => _now;
|
public override long InputClockMilliseconds => _now;
|
||||||
|
|
||||||
public override void Sleep(long duration)
|
public override void Sleep(long duration)
|
||||||
@@ -30,6 +32,7 @@ public class HistoryInteractionOpsTests
|
|||||||
_modalSleeps++;
|
_modalSleeps++;
|
||||||
if (_modalSleeps == 1)
|
if (_modalSleeps == 1)
|
||||||
{
|
{
|
||||||
|
FirstHistoryFrame = Vm.Gfx.SnapshotVisibleObjects(_now);
|
||||||
Vm.UpdatePointer(790, 570); // HISTORY candidate 8: visible bottom-right close region
|
Vm.UpdatePointer(790, 570); // HISTORY candidate 8: visible bottom-right close region
|
||||||
Vm.UpdateMouseButtonState(0x1, true);
|
Vm.UpdateMouseButtonState(0x1, true);
|
||||||
Vm.QueueInputCallback(4);
|
Vm.QueueInputCallback(4);
|
||||||
@@ -54,6 +57,28 @@ public class HistoryInteractionOpsTests
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void LookupArrayPreservesLocalStorageForLocalBases()
|
||||||
|
{
|
||||||
|
var script = ScriptAssembler.Assemble(Table, "LOCAL_LOOKUP",
|
||||||
|
new List<(int, Operand[])>
|
||||||
|
{
|
||||||
|
(0x55, new[] { L(10), I(768) }),
|
||||||
|
(0x55, new[] { L(11), I(121) }),
|
||||||
|
(0x61, new[] { P(0), L(10), I(1) }),
|
||||||
|
(0x55, new[] { G(0x100), P(0) }),
|
||||||
|
(0x55, new[] { P(0), I(179) }),
|
||||||
|
(0x55, new[] { G(0x101), L(11) }),
|
||||||
|
(0x2, Array.Empty<Operand>()),
|
||||||
|
}, Array.Empty<string>());
|
||||||
|
var vm = new VirtualMachine(script, Table, new RecordingHost());
|
||||||
|
|
||||||
|
vm.Run();
|
||||||
|
|
||||||
|
Assert.Equal(121, vm.Globals[0x100]);
|
||||||
|
Assert.Equal(179, vm.Globals[0x101]);
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void FindHitRectangleScansAfterTheIncomingIndexWithInclusiveEdges()
|
public void FindHitRectangleScansAfterTheIncomingIndexWithInclusiveEdges()
|
||||||
{
|
{
|
||||||
@@ -105,5 +130,24 @@ public class HistoryInteractionOpsTests
|
|||||||
Assert.True(host.SawRenderedText);
|
Assert.True(host.SawRenderedText);
|
||||||
Assert.True(host.HistoryReturned);
|
Assert.True(host.HistoryReturned);
|
||||||
Assert.Equal(1, host.Waits); // the enclosing ADV page was never released or re-entered
|
Assert.Equal(1, host.Waits); // the enclosing ADV page was never released or re-entered
|
||||||
|
|
||||||
|
var historyButtons = host.FirstHistoryFrame
|
||||||
|
.Where(render => render.Handle >= 0xd2fa && render.Handle <= 0xd300)
|
||||||
|
.OrderBy(render => render.Handle)
|
||||||
|
.ToArray();
|
||||||
|
Assert.Equal(new[]
|
||||||
|
{
|
||||||
|
(0xd2faL, 768, 121), (0xd2fbL, 768, 179), (0xd2fcL, 768, 237),
|
||||||
|
(0xd2fdL, 768, 295), (0xd2feL, 768, 353), (0xd2ffL, 768, 411),
|
||||||
|
(0xd300L, 768, 549),
|
||||||
|
}, historyButtons.Select(render => (render.Handle, render.DstX, render.DstY)).ToArray());
|
||||||
|
|
||||||
|
var visibleRows = host.HistoryRenders
|
||||||
|
.Where(render => render.Text.Length > 0)
|
||||||
|
.GroupBy(render => render.LayoutSlot)
|
||||||
|
.Select(group => group.Last())
|
||||||
|
.ToArray();
|
||||||
|
Assert.NotEmpty(visibleRows);
|
||||||
|
Assert.All(visibleRows, render => Assert.Equal(65, render.Layout.OriginX));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,27 @@
|
|||||||
namespace Age.Engine.Vm;
|
namespace Age.Engine.Vm;
|
||||||
|
|
||||||
|
public enum VmAddressSpace
|
||||||
|
{
|
||||||
|
Global,
|
||||||
|
LocalInteger,
|
||||||
|
LocalFloat,
|
||||||
|
LocalString,
|
||||||
|
}
|
||||||
|
|
||||||
|
public readonly record struct VmAddress(VmAddressSpace Space, int Address)
|
||||||
|
{
|
||||||
|
public static VmAddress Global(int address) => new(VmAddressSpace.Global, address);
|
||||||
|
public static VmAddress LocalInteger(int address) => new(VmAddressSpace.LocalInteger, address);
|
||||||
|
public static VmAddress LocalFloat(int address) => new(VmAddressSpace.LocalFloat, address);
|
||||||
|
public static VmAddress LocalString(int address) => new(VmAddressSpace.LocalString, address);
|
||||||
|
public VmAddress Offset(long offset) => new(Space, checked(Address + (int)offset));
|
||||||
|
}
|
||||||
|
|
||||||
public sealed class Frame
|
public sealed class Frame
|
||||||
{
|
{
|
||||||
public Dictionary<int, long> I = new(); // local-int
|
public Dictionary<int, long> I = new(); // local-int
|
||||||
public Dictionary<int, long> F = new(); // local-float (raw)
|
public Dictionary<int, long> F = new(); // local-float (raw)
|
||||||
public Dictionary<int, string> S = new(); // local-string
|
public Dictionary<int, string> S = new(); // local-string
|
||||||
public Dictionary<int, long> P = new(); // local-ptr (holds a global address)
|
public Dictionary<int, VmAddress> P = new(); // local-ptr (retains local/global address domain)
|
||||||
public Dictionary<int, long> SP = new(); // local-string-ptr (holds a global-string address)
|
public Dictionary<int, VmAddress> SP = new(); // local-string-ptr (same, for the string banks)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -167,7 +167,7 @@ public sealed class VirtualMachine
|
|||||||
T_GPTR => Gi(Globals, (int)Gi(Globals, (int)op.Value)),
|
T_GPTR => Gi(Globals, (int)Gi(Globals, (int)op.Value)),
|
||||||
T_LINT => Gi(_cur.Locals.I, (int)op.Value),
|
T_LINT => Gi(_cur.Locals.I, (int)op.Value),
|
||||||
T_LFLOAT => Gi(_cur.Locals.F, (int)op.Value),
|
T_LFLOAT => Gi(_cur.Locals.F, (int)op.Value),
|
||||||
T_LPTR => Gi(Globals, (int)Gi(_cur.Locals.P, (int)op.Value)),
|
T_LPTR => ReadIntCell(Ga(_cur.Locals.P, (int)op.Value)),
|
||||||
_ => op.Value,
|
_ => op.Value,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -179,7 +179,7 @@ public sealed class VirtualMachine
|
|||||||
case T_GPTR: Globals[(int)Gi(Globals, (int)op.Value)] = val; break;
|
case T_GPTR: Globals[(int)Gi(Globals, (int)op.Value)] = val; break;
|
||||||
case T_LINT: _cur.Locals.I[(int)op.Value] = val; break;
|
case T_LINT: _cur.Locals.I[(int)op.Value] = val; break;
|
||||||
case T_LFLOAT: _cur.Locals.F[(int)op.Value] = val; break;
|
case T_LFLOAT: _cur.Locals.F[(int)op.Value] = val; break;
|
||||||
case T_LPTR: Globals[(int)Gi(_cur.Locals.P, (int)op.Value)] = val; break;
|
case T_LPTR: WriteIntCell(Ga(_cur.Locals.P, (int)op.Value), val); break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -189,7 +189,7 @@ public sealed class VirtualMachine
|
|||||||
T_GSTR => Gs(GlobalStrings, (int)op.Value),
|
T_GSTR => Gs(GlobalStrings, (int)op.Value),
|
||||||
T_GSTRPTR => Gs(GlobalStrings, (int)Gi(Globals, (int)op.Value)),
|
T_GSTRPTR => Gs(GlobalStrings, (int)Gi(Globals, (int)op.Value)),
|
||||||
T_LSTR => Gs(_cur.Locals.S, (int)op.Value),
|
T_LSTR => Gs(_cur.Locals.S, (int)op.Value),
|
||||||
T_LSTRPTR => Gs(GlobalStrings, (int)Gi(_cur.Locals.SP, (int)op.Value)),
|
T_LSTRPTR => ReadStringCell(Ga(_cur.Locals.SP, (int)op.Value)),
|
||||||
_ => "",
|
_ => "",
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -200,27 +200,63 @@ public sealed class VirtualMachine
|
|||||||
case T_GSTR: GlobalStrings[(int)op.Value] = val; break;
|
case T_GSTR: GlobalStrings[(int)op.Value] = val; break;
|
||||||
case T_GSTRPTR: GlobalStrings[(int)Gi(Globals, (int)op.Value)] = val; break;
|
case T_GSTRPTR: GlobalStrings[(int)Gi(Globals, (int)op.Value)] = val; break;
|
||||||
case T_LSTR: _cur.Locals.S[(int)op.Value] = val; break;
|
case T_LSTR: _cur.Locals.S[(int)op.Value] = val; break;
|
||||||
case T_LSTRPTR: GlobalStrings[(int)Gi(_cur.Locals.SP, (int)op.Value)] = val; break;
|
case T_LSTRPTR: WriteStringCell(Ga(_cur.Locals.SP, (int)op.Value), val); break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private long BaseAddr(Operand op) => op.Type switch
|
private static VmAddress Ga(Dictionary<int, VmAddress> d, int k)
|
||||||
|
=> d.TryGetValue(k, out var value) ? value : VmAddress.Global(0);
|
||||||
|
|
||||||
|
private long ReadIntCell(VmAddress address) => address.Space switch
|
||||||
{
|
{
|
||||||
T_IMM or T_GINT or T_GFLOAT or T_GSTR or T_GPTR or T_GSTRPTR => op.Value,
|
VmAddressSpace.LocalInteger => Gi(_cur.Locals.I, address.Address),
|
||||||
T_LINT => Gi(_cur.Locals.I, (int)op.Value),
|
VmAddressSpace.LocalFloat => Gi(_cur.Locals.F, address.Address),
|
||||||
T_LPTR => Gi(_cur.Locals.P, (int)op.Value),
|
_ => Gi(Globals, address.Address),
|
||||||
_ => op.Value,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
private void LookupStore(Operand dst, long addr)
|
private void WriteIntCell(VmAddress address, long value)
|
||||||
|
{
|
||||||
|
switch (address.Space)
|
||||||
|
{
|
||||||
|
case VmAddressSpace.LocalInteger: _cur.Locals.I[address.Address] = value; break;
|
||||||
|
case VmAddressSpace.LocalFloat: _cur.Locals.F[address.Address] = value; break;
|
||||||
|
default: Globals[address.Address] = value; break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private string ReadStringCell(VmAddress address)
|
||||||
|
=> address.Space == VmAddressSpace.LocalString
|
||||||
|
? Gs(_cur.Locals.S, address.Address)
|
||||||
|
: Gs(GlobalStrings, address.Address);
|
||||||
|
|
||||||
|
private void WriteStringCell(VmAddress address, string value)
|
||||||
|
{
|
||||||
|
if (address.Space == VmAddressSpace.LocalString) _cur.Locals.S[address.Address] = value;
|
||||||
|
else GlobalStrings[address.Address] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private VmAddress BaseAddr(Operand op) => op.Type switch
|
||||||
|
{
|
||||||
|
T_LINT => VmAddress.LocalInteger((int)op.Value),
|
||||||
|
T_LFLOAT => VmAddress.LocalFloat((int)op.Value),
|
||||||
|
T_LSTR => VmAddress.LocalString((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 void LookupStore(Operand dst, VmAddress addr)
|
||||||
{
|
{
|
||||||
switch (dst.Type)
|
switch (dst.Type)
|
||||||
{
|
{
|
||||||
case T_LPTR: _cur.Locals.P[(int)dst.Value] = addr; break;
|
case T_LPTR: _cur.Locals.P[(int)dst.Value] = addr; break;
|
||||||
case T_LSTRPTR: _cur.Locals.SP[(int)dst.Value] = addr; break;
|
case T_LSTRPTR: _cur.Locals.SP[(int)dst.Value] = addr; break;
|
||||||
case T_GPTR: Globals[(int)dst.Value] = addr; break;
|
case T_GPTR: Globals[(int)dst.Value] = addr.Address; break;
|
||||||
case T_GSTRPTR: Globals[(int)dst.Value] = addr; break;
|
case T_GSTRPTR: Globals[(int)dst.Value] = addr.Address; break;
|
||||||
default: Write(dst, Gi(Globals, (int)addr)); break;
|
default:
|
||||||
|
if (IsStr(dst)) WriteStr(dst, ReadStringCell(addr));
|
||||||
|
else Write(dst, ReadIntCell(addr));
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -233,7 +269,7 @@ public sealed class VirtualMachine
|
|||||||
case T_LINT: _cur.Locals.I[address] = value; break;
|
case T_LINT: _cur.Locals.I[address] = value; break;
|
||||||
case T_LFLOAT: _cur.Locals.F[address] = value; break;
|
case T_LFLOAT: _cur.Locals.F[address] = value; break;
|
||||||
case T_GPTR: Globals[checked((int)Gi(Globals, (int)destination.Value) + index)] = value; break;
|
case T_GPTR: Globals[checked((int)Gi(Globals, (int)destination.Value) + index)] = value; break;
|
||||||
case T_LPTR: Globals[checked((int)Gi(_cur.Locals.P, (int)destination.Value) + index)] = value; break;
|
case T_LPTR: WriteIntCell(Ga(_cur.Locals.P, (int)destination.Value).Offset(index), value); break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -244,21 +280,25 @@ public sealed class VirtualMachine
|
|||||||
T_LINT => Gi(_cur.Locals.I, checked((int)operand.Value + offset)),
|
T_LINT => Gi(_cur.Locals.I, checked((int)operand.Value + offset)),
|
||||||
T_LFLOAT => Gi(_cur.Locals.F, checked((int)operand.Value + offset)),
|
T_LFLOAT => Gi(_cur.Locals.F, checked((int)operand.Value + offset)),
|
||||||
T_GINT or T_GFLOAT => ReadGlobal(checked((int)operand.Value + offset)),
|
T_GINT or T_GFLOAT => ReadGlobal(checked((int)operand.Value + offset)),
|
||||||
T_LPTR => Gi(Globals, checked((int)Gi(_cur.Locals.P, (int)operand.Value) + offset)),
|
T_LPTR => ReadIntCell(Ga(_cur.Locals.P, (int)operand.Value).Offset(offset)),
|
||||||
T_GPTR => Gi(Globals, checked((int)Gi(Globals, (int)operand.Value) + offset)),
|
T_GPTR => Gi(Globals, checked((int)Gi(Globals, (int)operand.Value) + offset)),
|
||||||
_ => Gi(Globals, checked((int)operand.Value + offset)),
|
_ => Gi(Globals, checked((int)operand.Value + offset)),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private (bool IsLocal, int Address) AddressedCellIdentity(Operand operand, int offset)
|
private (VmAddressSpace Space, int Address) AddressedCellIdentity(Operand operand, int offset)
|
||||||
=> operand.Type switch
|
=> operand.Type switch
|
||||||
{
|
{
|
||||||
T_LINT or T_LFLOAT => (true, checked((int)operand.Value + offset)),
|
T_LINT => (VmAddressSpace.LocalInteger, checked((int)operand.Value + offset)),
|
||||||
T_LPTR => (false, checked((int)Gi(_cur.Locals.P, (int)operand.Value) + offset)),
|
T_LFLOAT => (VmAddressSpace.LocalFloat, checked((int)operand.Value + offset)),
|
||||||
T_GPTR => (false, checked((int)Gi(Globals, (int)operand.Value) + offset)),
|
T_LPTR => PointerIdentity(Ga(_cur.Locals.P, (int)operand.Value).Offset(offset)),
|
||||||
_ => (false, checked((int)operand.Value + offset)),
|
T_GPTR => (VmAddressSpace.Global, checked((int)Gi(Globals, (int)operand.Value) + offset)),
|
||||||
|
_ => (VmAddressSpace.Global, checked((int)operand.Value + offset)),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private static (VmAddressSpace Space, int Address) PointerIdentity(VmAddress address)
|
||||||
|
=> (address.Space, address.Address);
|
||||||
|
|
||||||
private string FormatSwitchValue(Operand operand)
|
private string FormatSwitchValue(Operand operand)
|
||||||
=> IsStr(operand)
|
=> IsStr(operand)
|
||||||
? ReadStr(operand)
|
? ReadStr(operand)
|
||||||
@@ -383,9 +423,9 @@ public sealed class VirtualMachine
|
|||||||
else Write(a[0], Read(a[1]));
|
else Write(a[0], Read(a[1]));
|
||||||
return pc + 1;
|
return pc + 1;
|
||||||
case "lookup-array":
|
case "lookup-array":
|
||||||
LookupStore(a[0], BaseAddr(a[1]) + Read(a[2])); return pc + 1;
|
LookupStore(a[0], BaseAddr(a[1]).Offset(Read(a[2]))); return pc + 1;
|
||||||
case "lookup-array-2d":
|
case "lookup-array-2d":
|
||||||
LookupStore(a[0], BaseAddr(a[1]) + Read(a[2]) * Read(a[3]) + Read(a[4])); return pc + 1;
|
LookupStore(a[0], BaseAddr(a[1]).Offset(Read(a[2]) * Read(a[3]) + Read(a[4]))); return pc + 1;
|
||||||
case "copy-inline-int-array": // 0x64: count dword followed by plain file values
|
case "copy-inline-int-array": // 0x64: count dword followed by plain file values
|
||||||
{
|
{
|
||||||
int offset = checked((int)Read(a[1]));
|
int offset = checked((int)Read(a[1]));
|
||||||
|
|||||||
@@ -586,7 +586,6 @@ public partial class Main : Godot.Control
|
|||||||
AutowrapMode = TextServer.AutowrapMode.WordSmart,
|
AutowrapMode = TextServer.AutowrapMode.WordSmart,
|
||||||
ClipText = true,
|
ClipText = true,
|
||||||
};
|
};
|
||||||
label.SetAnchorsAndOffsetsPreset(LayoutPreset.FullRect);
|
|
||||||
label.AddThemeFontOverride("font", _text.GetThemeFont("font"));
|
label.AddThemeFontOverride("font", _text.GetThemeFont("font"));
|
||||||
AddChild(label);
|
AddChild(label);
|
||||||
return label;
|
return label;
|
||||||
|
|||||||
42
tools/vm0.py
42
tools/vm0.py
@@ -13,9 +13,9 @@ Purpose (see docs/phase-a-slice-plan.md):
|
|||||||
Memory model:
|
Memory model:
|
||||||
* one flat GLOBAL bank G (dict addr->int); globals are raw offsets into one space.
|
* one flat GLOBAL bank G (dict addr->int); globals are raw offsets into one space.
|
||||||
* per-call local frame with sparse typed banks (int/float/string/ptr).
|
* per-call local frame with sparse typed banks (int/float/string/ptr).
|
||||||
* a `-ptr` variable holds an ADDRESS into G. lookup-array/2d with a ptr dst stores that
|
* a `-ptr` variable retains an address plus its local/global storage domain. lookup-array/2d
|
||||||
address (take-reference); reading a ptr derefs (G[addr]); writing through a ptr writes G[addr].
|
with a ptr dst stores that reference (take-reference); reads/writes dereference the matching
|
||||||
This is the model RECOVER forces; the unit test is its litmus.
|
bank. RECOVER exercises global references; HISTORY's copied coordinate arrays exercise local ones.
|
||||||
* jcc(cond, tA, tB): cond truthy -> goto tA else tB; 0xFFFFFFFF = fall through (from RECOVER+SCJUMP).
|
* jcc(cond, tA, tB): cond truthy -> goto tA else tB; 0xFFFFFFFF = fall through (from RECOVER+SCJUMP).
|
||||||
* call/ret (0x8F/0x05) are intra-script subroutine calls (shared frame); call-script (0x03) is
|
* call/ret (0x8F/0x05) are intra-script subroutine calls (shared frame); call-script (0x03) is
|
||||||
the inter-script one and is stubbed.
|
the inter-script one and is stubbed.
|
||||||
@@ -53,7 +53,7 @@ class Frame:
|
|||||||
self.i = collections.defaultdict(int) # local-int
|
self.i = collections.defaultdict(int) # local-int
|
||||||
self.f = collections.defaultdict(int) # local-float (stored raw)
|
self.f = collections.defaultdict(int) # local-float (stored raw)
|
||||||
self.s = collections.defaultdict(str) # local-string
|
self.s = collections.defaultdict(str) # local-string
|
||||||
self.p = collections.defaultdict(int) # local-ptr (holds a G address)
|
self.p = collections.defaultdict(lambda: ("g", 0)) # local-ptr: (bank, address)
|
||||||
|
|
||||||
|
|
||||||
class VM:
|
class VM:
|
||||||
@@ -96,7 +96,7 @@ class VM:
|
|||||||
if t == T_LINT: return self.fr.i[v]
|
if t == T_LINT: return self.fr.i[v]
|
||||||
if t == T_LFLOAT: return self.fr.f[v]
|
if t == T_LFLOAT: return self.fr.f[v]
|
||||||
if t == T_LSTR: return self.fr.s[v]
|
if t == T_LSTR: return self.fr.s[v]
|
||||||
if t == T_LPTR: return self.G[self.fr.p[v]] # deref ptr
|
if t == T_LPTR: return self.read_cell(self.fr.p[v]) # deref ptr
|
||||||
self.log[f"read?t{t:#x}"] += 1
|
self.log[f"read?t{t:#x}"] += 1
|
||||||
return v
|
return v
|
||||||
|
|
||||||
@@ -108,23 +108,37 @@ class VM:
|
|||||||
elif t == T_LINT: self.fr.i[v] = val
|
elif t == T_LINT: self.fr.i[v] = val
|
||||||
elif t == T_LFLOAT: self.fr.f[v] = val
|
elif t == T_LFLOAT: self.fr.f[v] = val
|
||||||
elif t == T_LSTR: self.fr.s[v] = val
|
elif t == T_LSTR: self.fr.s[v] = val
|
||||||
elif t == T_LPTR: self.G[self.fr.p[v]] = val # write through
|
elif t == T_LPTR: self.write_cell(self.fr.p[v], val)
|
||||||
else: self.log[f"write?t{t:#x}"] += 1
|
else: self.log[f"write?t{t:#x}"] += 1
|
||||||
|
|
||||||
|
def read_cell(self, address):
|
||||||
|
bank, addr = address
|
||||||
|
if bank == "i": return self.fr.i[addr]
|
||||||
|
if bank == "f": return self.fr.f[addr]
|
||||||
|
return self.G[addr]
|
||||||
|
|
||||||
|
def write_cell(self, address, value):
|
||||||
|
bank, addr = address
|
||||||
|
if bank == "i": self.fr.i[addr] = value
|
||||||
|
elif bank == "f": self.fr.f[addr] = value
|
||||||
|
else: self.G[addr] = value
|
||||||
|
|
||||||
def base_addr(self, op):
|
def base_addr(self, op):
|
||||||
"""The base ADDRESS an operand names, for array lookups."""
|
"""The base ADDRESS an operand names, for array lookups."""
|
||||||
t, v = op
|
t, v = op
|
||||||
if t in (T_IMM, T_GINT, T_GFLOAT, T_GSTR, T_GPTR): return v # global's own offset
|
if t in (T_IMM, T_GINT, T_GFLOAT, T_GSTR, T_GPTR): return ("g", v)
|
||||||
if t == T_LINT: return self.fr.i[v]
|
if t == T_LINT: return ("i", v)
|
||||||
|
if t == T_LFLOAT: return ("f", v)
|
||||||
|
if t == T_LSTR: return ("s", v)
|
||||||
if t == T_LPTR: return self.fr.p[v]
|
if t == T_LPTR: return self.fr.p[v]
|
||||||
return v
|
return ("g", v)
|
||||||
|
|
||||||
def lookup_store(self, dst, addr):
|
def lookup_store(self, dst, addr):
|
||||||
"""lookup result: ptr dst gets the reference (address); non-ptr gets the element value."""
|
"""lookup result: ptr dst gets the reference (address); non-ptr gets the element value."""
|
||||||
t, v = dst
|
t, v = dst
|
||||||
if t == T_LPTR: self.fr.p[v] = addr
|
if t == T_LPTR: self.fr.p[v] = addr
|
||||||
elif t == T_GPTR: self.G[v] = addr
|
elif t == T_GPTR: self.G[v] = addr[1]
|
||||||
else: self.write(dst, self.G[addr])
|
else: self.write(dst, self.read_cell(addr))
|
||||||
|
|
||||||
# ---- execution -----------------------------------------------------------
|
# ---- execution -----------------------------------------------------------
|
||||||
def run(self, entry_off=0, max_steps=2_000_000):
|
def run(self, entry_off=0, max_steps=2_000_000):
|
||||||
@@ -171,10 +185,12 @@ class VM:
|
|||||||
if lbl == "set-string":
|
if lbl == "set-string":
|
||||||
self.write(a[0], self.read(a[1])); return pc + 1
|
self.write(a[0], self.read(a[1])); return pc + 1
|
||||||
if lbl == "lookup-array": # dst = base[idx]
|
if lbl == "lookup-array": # dst = base[idx]
|
||||||
addr = self.base_addr(a[1]) + self.read(a[2])
|
bank, base = self.base_addr(a[1])
|
||||||
|
addr = (bank, base + self.read(a[2]))
|
||||||
self.lookup_store(a[0], addr); return pc + 1
|
self.lookup_store(a[0], addr); return pc + 1
|
||||||
if lbl == "lookup-array-2d": # dst = base[i*stride + col]
|
if lbl == "lookup-array-2d": # dst = base[i*stride + col]
|
||||||
addr = self.base_addr(a[1]) + self.read(a[2]) * self.read(a[3]) + self.read(a[4])
|
bank, base = self.base_addr(a[1])
|
||||||
|
addr = (bank, base + self.read(a[2]) * self.read(a[3]) + self.read(a[4]))
|
||||||
self.lookup_store(a[0], addr); return pc + 1
|
self.lookup_store(a[0], addr); return pc + 1
|
||||||
if lbl == "bit-set":
|
if lbl == "bit-set":
|
||||||
bit = self.read(a[1])
|
bit = self.read(a[1])
|
||||||
|
|||||||
@@ -796,13 +796,14 @@ abi_source = "kelebek+decode-validated"
|
|||||||
|
|
||||||
[opcode.semantics]
|
[opcode.semantics]
|
||||||
name = "lookup-array"
|
name = "lookup-array"
|
||||||
category = "unknown"
|
category = "compute"
|
||||||
summary = ""
|
summary = "Take a typed reference to base[index], preserving whether the base belongs to local or global storage."
|
||||||
noop_headless = false
|
noop_headless = false
|
||||||
source = "kelebek"
|
source = "investigation"
|
||||||
confidence = "med"
|
confidence = "high"
|
||||||
depends_on = []
|
depends_on = []
|
||||||
evidence = ""
|
evidence = "HISTORY.BIN copies x/y tables into local-int cells 0x4 and 0x68, then lookup-array local-ptr <- local-int base supplies every right-side button and hovered-row draw coordinate. Treating the local operand's current value as a global base collapses those draws to (0,0); retaining the local address yields the native x=768/y=121..549 positions. RECOVER.BIN independently exercises the same pointer destination with global bases."
|
||||||
|
details = "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)."
|
||||||
|
|
||||||
[[opcode.semantics.args]]
|
[[opcode.semantics.args]]
|
||||||
i = 1
|
i = 1
|
||||||
|
|||||||
Reference in New Issue
Block a user