Implement ADV layout cursor and bounds

This commit is contained in:
gamer147
2026-07-21 09:48:25 -04:00
parent a29bd52045
commit d0b2d31d2e
12 changed files with 301 additions and 46 deletions

View File

@@ -40,7 +40,9 @@ public readonly record struct AdvTextLayoutSnapshot(
int OriginX,
int OriginY,
int CursorX,
int CursorY);
int CursorY,
int Right,
int Bottom);
/// <summary>
/// One semantic counterpart of AGE's 0x48-byte retained text record. Metadata uses
@@ -80,8 +82,12 @@ public sealed class AdvTextHistory
public int Height;
public int OriginX;
public int OriginY;
public int ResetCursorX;
public int ResetCursorY;
public int CursorX;
public int CursorY;
public int Right;
public int Bottom;
}
private readonly List<AdvTextHistoryRecord> _records = new();
@@ -105,6 +111,8 @@ public sealed class AdvTextHistory
layout.Height = height;
layout.OriginX = originX;
layout.OriginY = originY;
layout.Right = width;
layout.Bottom = height;
AppendBoundary(slot);
}
@@ -112,11 +120,27 @@ public sealed class AdvTextHistory
{
int slot = SelectLayout(requestedSlot);
var layout = GetOrCreateLayout(slot);
layout.CursorX = 0;
layout.CursorY = 0;
layout.CursorX = layout.ResetCursorX;
layout.CursorY = layout.ResetCursorY;
AppendBoundary(slot);
}
public void SetResetCursor(int requestedSlot, int x, int y)
{
int slot = ResolveLayout(requestedSlot);
var layout = GetOrCreateLayout(slot);
layout.ResetCursorX = x;
layout.ResetCursorY = y;
}
public void SetBounds(int requestedSlot, int right, int bottom)
{
int slot = ResolveLayout(requestedSlot);
var layout = GetOrCreateLayout(slot);
layout.Right = right;
layout.Bottom = bottom;
}
public void SetCursor(int requestedSlot, int x, int y)
{
int slot = ResolveLayout(requestedSlot);
@@ -133,6 +157,9 @@ public sealed class AdvTextHistory
layout.OriginY = y;
}
public AdvTextLayoutSnapshot GetLayoutSnapshot(int requestedSlot)
=> SnapshotLayout(ResolveLayout(requestedSlot));
public void AppendText(int requestedSlot, int sourceOffset, string text, AdvTextStyle style,
AdvTextHistoryRecordFlags flags = AdvTextHistoryRecordFlags.None)
{
@@ -291,7 +318,8 @@ public sealed class AdvTextHistory
{
var layout = GetOrCreateLayout(slot);
return new AdvTextLayoutSnapshot(slot, layout.Width, layout.Height,
layout.OriginX, layout.OriginY, layout.CursorX, layout.CursorY);
layout.OriginX, layout.OriginY, layout.CursorX, layout.CursorY,
layout.Right, layout.Bottom);
}
private void AppendBoundary(int slot)
@@ -325,7 +353,8 @@ public sealed class AdvTextHistory
var layout = GetOrCreateLayout(slot);
var snapshot = new AdvTextLayoutSnapshot(slot, layout.Width, layout.Height,
layout.OriginX, layout.OriginY, layout.CursorX, layout.CursorY);
layout.OriginX, layout.OriginY, layout.CursorX, layout.CursorY,
layout.Right, layout.Bottom);
_records.Add(new AdvTextHistoryRecord(kind, flags, snapshot, style, text, value, auxValue, sourceOffset));
}
}

View File

@@ -14,6 +14,9 @@ public static class AdvTextLayoutBootstrap
{
int? defineOpcode = table.ByLabel("define-adv-text-layout");
int? resetOpcode = table.ByLabel("reset-adv-text-layout");
int? resetCursorOpcode = table.ByLabel("set-adv-text-reset-cursor");
int? boundsOpcode = table.ByLabel("set-adv-text-bounds");
int? addOpcode = table.ByLabel("add");
if (defineOpcode == null || resetOpcode == null) return 0;
bool foundDefinition = false;
@@ -46,6 +49,81 @@ public static class AdvTextLayoutBootstrap
}
break;
}
// SYSTEM4 configures the cursor restored by future resets and the right/bottom overflow bounds
// after the initial define/reset run. Most operands are immediate; slot 1 computes its bounds with
// two constant add instructions. Interpret only that small, data-only expression vocabulary rather
// than entering SYSTEM4's later menu/session flow.
if (resetCursorOpcode != null && boundsOpcode != null)
{
var localInts = new Dictionary<int, long>();
bool inLayoutBlock = false;
int cursorConfigurations = 0;
int boundConfigurations = 0;
foreach (var instruction in systemScript.Instructions)
{
if (!inLayoutBlock)
{
if (instruction.Opcode != defineOpcode.Value) continue;
inLayoutBlock = true;
}
if (addOpcode != null && instruction.Opcode == addOpcode.Value)
{
if (instruction.Args.Count == 3 && instruction.Args[0].Type == 9
&& TryResolveConstant(instruction.Args[1], localInts, out long left)
&& TryResolveConstant(instruction.Args[2], localInts, out long right))
localInts[checked((int)instruction.Args[0].Value)] = left + right;
continue;
}
if (instruction.Opcode == resetCursorOpcode.Value)
{
var values = ResolveConfiguration(instruction, localInts, systemScript.Name);
history.SetResetCursor(values[0], values[1], values[2]);
cursorConfigurations++;
}
else if (instruction.Opcode == boundsOpcode.Value)
{
var values = ResolveConfiguration(instruction, localInts, systemScript.Name);
history.SetBounds(values[0], values[1], values[2]);
boundConfigurations++;
}
if (cursorConfigurations >= definitions && boundConfigurations >= definitions) break;
}
}
return definitions;
}
private static int[] ResolveConfiguration(
Instruction instruction, IReadOnlyDictionary<int, long> localInts, string scriptName)
{
if (instruction.Args.Count != 3)
throw new InvalidDataException(
$"{scriptName}@0x{instruction.Offset:x}: ADV layout configuration must use three operands");
var values = new int[3];
for (int i = 0; i < values.Length; i++)
{
if (!TryResolveConstant(instruction.Args[i], localInts, out long value))
throw new InvalidDataException(
$"{scriptName}@0x{instruction.Offset:x}: ADV layout configuration operand {i + 1} " +
"is not an immediate or constant local integer");
values[i] = checked((int)value);
}
return values;
}
private static bool TryResolveConstant(
Operand operand, IReadOnlyDictionary<int, long> localInts, out long value)
{
if (operand.Type == 0)
{
value = operand.Value;
return true;
}
if (operand.Type == 9 && localInts.TryGetValue(checked((int)operand.Value), out value)) return true;
value = 0;
return false;
}
}

View File

@@ -827,12 +827,23 @@ public sealed class VirtualMachine
(int)Read(a[3]), (int)Read(a[4]));
return pc + 1;
case "reset-adv-text-layout": // 0x71: reset layout and begin the next logical retained group
TextHistory.ResetLayout((int)Read(a[0]));
_host.ClearRenderedAdvTextLayout((int)Read(a[0]));
{
int requestedSlot = (int)Read(a[0]);
TextHistory.ResetLayout(requestedSlot);
var layout = TextHistory.GetLayoutSnapshot(requestedSlot);
_host.SetAdvTextCursor(layout.Slot, layout.CursorX, layout.CursorY);
_host.ClearRenderedAdvTextLayout(layout.Slot);
return pc + 1;
}
case "set-adv-text-reset-cursor": // 0x79: configure cursor restored by a later 0x71
TextHistory.SetResetCursor((int)Read(a[0]), (int)Read(a[1]), (int)Read(a[2]));
return pc + 1;
case "set-adv-text-cursor": // 0x7a (layout slot, x, y); slot 0 means current natively
TextHistory.SetCursor((int)Read(a[0]), (int)Read(a[1]), (int)Read(a[2]));
_host.SetAdvTextCursor((int)Read(a[0]), (int)Read(a[1]), (int)Read(a[2])); return pc + 1;
case "set-adv-text-bounds": // 0x1c1: layout-local right/bottom overflow boundaries
TextHistory.SetBounds((int)Read(a[0]), (int)Read(a[1]), (int)Read(a[2]));
return pc + 1;
case "configure-adv-wait-indicator": // 0x73: per-layout animated input-wait marker
_host.ConfigureAdvWaitIndicator(new AdvWaitIndicatorConfig(
(int)Read(a[0]), (int)Read(a[1]), (int)Read(a[2]), (int)Read(a[3]),