Correct ADV History layout addressing
This commit is contained in:
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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]));
|
||||
|
||||
Reference in New Issue
Block a user