Restore multi-row ADV History display
This commit is contained in:
@@ -14,9 +14,14 @@ public class HistoryInteractionOpsTests
|
||||
|
||||
private sealed class StopAfterHistoryReturnsException : Exception { }
|
||||
|
||||
private static void SeedSystem4AdvLayouts(Sys4ScriptProvider scripts, AdvTextHistory history)
|
||||
=> Assert.Equal(9, AdvTextLayoutBootstrap.ApplyLeadingDefinitionsAndResets(
|
||||
scripts.RequireByName("SYSTEM4.BIN"), Table, history));
|
||||
|
||||
private sealed class Sc0000HistoryCloseHost : RecordingHost
|
||||
{
|
||||
public VirtualMachine Vm = null!;
|
||||
private readonly int _openAtWait;
|
||||
private long _now;
|
||||
private int _modalSleeps;
|
||||
public bool HistoryReturned;
|
||||
@@ -24,6 +29,8 @@ public class HistoryInteractionOpsTests
|
||||
public IReadOnlyList<RenderObject> FirstHistoryFrame = Array.Empty<RenderObject>();
|
||||
public override long InputClockMilliseconds => _now;
|
||||
|
||||
public Sc0000HistoryCloseHost(int openAtWait = 1) => _openAtWait = openAtWait;
|
||||
|
||||
public override void Sleep(long duration)
|
||||
{
|
||||
base.Sleep(duration);
|
||||
@@ -47,6 +54,7 @@ public class HistoryInteractionOpsTests
|
||||
public override void WaitForInput(int layoutSlot, Func<bool> serviceInputCallback)
|
||||
{
|
||||
Waits++;
|
||||
if (Waits < _openAtWait) return;
|
||||
Vm.UpdatePointer(684, 572);
|
||||
while (serviceInputCallback()) { }
|
||||
Assert.True(Vm.TryActivatePointer(684, 572));
|
||||
@@ -57,6 +65,34 @@ public class HistoryInteractionOpsTests
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RealHistoryRendersMultipleRowsAfterSeveralSc0000Messages()
|
||||
{
|
||||
var scripts = Sys4ScriptProvider.Load(Table);
|
||||
var host = new Sc0000HistoryCloseHost(openAtWait: 6);
|
||||
var vm = new VirtualMachine(scripts.RequireByName("SC0000.BIN"), Table, host,
|
||||
new VmOptions(MaxSteps: 2_000_000), scripts);
|
||||
host.Vm = vm;
|
||||
SeedSystem4AdvLayouts(scripts, vm.TextHistory);
|
||||
vm.Globals[0x6c1] = 1;
|
||||
|
||||
Assert.Throws<StopAfterHistoryReturnsException>(() => vm.Run());
|
||||
|
||||
var visibleRows = host.HistoryRenders
|
||||
.Where(render => render.Text.Length > 0)
|
||||
.GroupBy(render => render.LayoutSlot)
|
||||
.Select(group => group.Last())
|
||||
.ToArray();
|
||||
Assert.True(visibleRows.Length >= 2,
|
||||
$"Expected multiple retained History rows after six messages, got {visibleRows.Length}: "
|
||||
+ string.Join(" | ", visibleRows.Select(render => render.Text)));
|
||||
Assert.All(visibleRows, render =>
|
||||
{
|
||||
Assert.Equal(650, render.Layout.Width);
|
||||
Assert.Equal(150, render.Layout.Height);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void LookupArrayPreservesLocalStorageForLocalBases()
|
||||
{
|
||||
@@ -123,6 +159,7 @@ public class HistoryInteractionOpsTests
|
||||
var vm = new VirtualMachine(scripts.RequireByName("SC0000.BIN"), Table, host,
|
||||
new VmOptions(MaxSteps: 2_000_000), scripts);
|
||||
host.Vm = vm;
|
||||
SeedSystem4AdvLayouts(scripts, vm.TextHistory);
|
||||
vm.Globals[0x6c1] = 1;
|
||||
|
||||
Assert.Throws<StopAfterHistoryReturnsException>(() => vm.Run());
|
||||
|
||||
51
engine/Age.Engine/Vm/AdvTextLayoutBootstrap.cs
Normal file
51
engine/Age.Engine/Vm/AdvTextLayoutBootstrap.cs
Normal file
@@ -0,0 +1,51 @@
|
||||
using Age.Engine.Model;
|
||||
|
||||
namespace Age.Engine.Vm;
|
||||
|
||||
/// <summary>
|
||||
/// Replays the data-only leading ADV layout block from an engine system script. This lets a bounded
|
||||
/// single-scene runner inherit script-owned layout state without hard-coding one game's coordinates or
|
||||
/// entering the system script's later menu/session control flow.
|
||||
/// </summary>
|
||||
public static class AdvTextLayoutBootstrap
|
||||
{
|
||||
public static int ApplyLeadingDefinitionsAndResets(
|
||||
Script systemScript, OpcodeTable table, AdvTextHistory history)
|
||||
{
|
||||
int? defineOpcode = table.ByLabel("define-adv-text-layout");
|
||||
int? resetOpcode = table.ByLabel("reset-adv-text-layout");
|
||||
if (defineOpcode == null || resetOpcode == null) return 0;
|
||||
|
||||
bool foundDefinition = false;
|
||||
bool resetPhase = false;
|
||||
int definitions = 0;
|
||||
foreach (var instruction in systemScript.Instructions)
|
||||
{
|
||||
if (!foundDefinition && instruction.Opcode != defineOpcode.Value) continue;
|
||||
if (instruction.Opcode == defineOpcode.Value && !resetPhase)
|
||||
{
|
||||
if (instruction.Args.Count != 5 || instruction.Args.Any(arg => arg.Type != 0))
|
||||
throw new InvalidDataException(
|
||||
$"{systemScript.Name}@0x{instruction.Offset:x}: leading ADV layout must use five immediates");
|
||||
history.DefineLayout(
|
||||
checked((int)instruction.Args[0].Value), checked((int)instruction.Args[1].Value),
|
||||
checked((int)instruction.Args[2].Value), checked((int)instruction.Args[3].Value),
|
||||
checked((int)instruction.Args[4].Value));
|
||||
foundDefinition = true;
|
||||
definitions++;
|
||||
continue;
|
||||
}
|
||||
if (foundDefinition && instruction.Opcode == resetOpcode.Value)
|
||||
{
|
||||
if (instruction.Args.Count != 1 || instruction.Args[0].Type != 0)
|
||||
throw new InvalidDataException(
|
||||
$"{systemScript.Name}@0x{instruction.Offset:x}: leading ADV reset must use one immediate");
|
||||
resetPhase = true;
|
||||
history.ResetLayout(checked((int)instruction.Args[0].Value));
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
return definitions;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user