Correct ADV History layout addressing

This commit is contained in:
gamer147
2026-07-19 10:43:52 -04:00
parent 1fdedfb41f
commit 280705d69c
9 changed files with 200 additions and 48 deletions

View File

@@ -5,11 +5,12 @@ using Age.Engine.Vm;
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 Operand I(long value) => new(T_IMM, value);
private static Operand G(int address) => new(T_GINT, 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 { }
@@ -20,6 +21,7 @@ public class HistoryInteractionOpsTests
private int _modalSleeps;
public bool HistoryReturned;
public bool SawRenderedText;
public IReadOnlyList<RenderObject> FirstHistoryFrame = Array.Empty<RenderObject>();
public override long InputClockMilliseconds => _now;
public override void Sleep(long duration)
@@ -30,6 +32,7 @@ public class HistoryInteractionOpsTests
_modalSleeps++;
if (_modalSleeps == 1)
{
FirstHistoryFrame = Vm.Gfx.SnapshotVisibleObjects(_now);
Vm.UpdatePointer(790, 570); // HISTORY candidate 8: visible bottom-right close region
Vm.UpdateMouseButtonState(0x1, true);
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]
public void FindHitRectangleScansAfterTheIncomingIndexWithInclusiveEdges()
{
@@ -105,5 +130,24 @@ public class HistoryInteractionOpsTests
Assert.True(host.SawRenderedText);
Assert.True(host.HistoryReturned);
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));
}
}

View File

@@ -1,9 +1,27 @@
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 Dictionary<int, long> I = new(); // local-int
public Dictionary<int, long> F = new(); // local-float (raw)
public Dictionary<int, string> S = new(); // local-string
public Dictionary<int, long> P = new(); // local-ptr (holds a global address)
public Dictionary<int, long> SP = new(); // local-string-ptr (holds a global-string address)
public Dictionary<int, VmAddress> P = new(); // local-ptr (retains local/global address domain)
public Dictionary<int, VmAddress> SP = new(); // local-string-ptr (same, for the string banks)
}

View File

@@ -167,7 +167,7 @@ public sealed class VirtualMachine
T_GPTR => Gi(Globals, (int)Gi(Globals, (int)op.Value)),
T_LINT => Gi(_cur.Locals.I, (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,
};
@@ -179,7 +179,7 @@ public sealed class VirtualMachine
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_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_GSTRPTR => Gs(GlobalStrings, (int)Gi(Globals, (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_GSTRPTR: GlobalStrings[(int)Gi(Globals, (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,
T_LINT => Gi(_cur.Locals.I, (int)op.Value),
T_LPTR => Gi(_cur.Locals.P, (int)op.Value),
_ => op.Value,
VmAddressSpace.LocalInteger => Gi(_cur.Locals.I, address.Address),
VmAddressSpace.LocalFloat => Gi(_cur.Locals.F, address.Address),
_ => Gi(Globals, address.Address),
};
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)
{
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; break;
case T_GSTRPTR: Globals[(int)dst.Value] = addr; break;
default: Write(dst, Gi(Globals, (int)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));
break;
}
}
@@ -233,7 +269,7 @@ public sealed class VirtualMachine
case T_LINT: _cur.Locals.I[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_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_LFLOAT => Gi(_cur.Locals.F, 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)),
_ => 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
{
T_LINT or T_LFLOAT => (true, checked((int)operand.Value + offset)),
T_LPTR => (false, checked((int)Gi(_cur.Locals.P, (int)operand.Value) + offset)),
T_GPTR => (false, checked((int)Gi(Globals, (int)operand.Value) + offset)),
_ => (false, checked((int)operand.Value + offset)),
T_LINT => (VmAddressSpace.LocalInteger, checked((int)operand.Value + offset)),
T_LFLOAT => (VmAddressSpace.LocalFloat, checked((int)operand.Value + offset)),
T_LPTR => PointerIdentity(Ga(_cur.Locals.P, (int)operand.Value).Offset(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)
=> IsStr(operand)
? ReadStr(operand)
@@ -383,9 +423,9 @@ public sealed class VirtualMachine
else Write(a[0], Read(a[1]));
return pc + 1;
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":
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
{
int offset = checked((int)Read(a[1]));