Restore multi-row ADV History display
This commit is contained in:
@@ -1374,11 +1374,24 @@ local pointer reach the correct bank. This restores the six controls at x=768/y=
|
|||||||
at x=768/y=549, and the hovered-row highlight while preserving RECOVER's global-array pointer behavior.
|
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 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
|
The History text batches already carry the native x origin 65 and cursor x 45. Their Godot labels were first
|
||||||
created with a full-rect anchor preset before being parented, which discarded the intended absolute
|
created with a full-rect anchor preset before being parented, which discarded the intended absolute placement
|
||||||
placement in the live UI and caused a Godot parent/layout diagnostic. Dynamically composited ADV labels now
|
and caused a Godot parent/layout diagnostic. Dynamically composited ADV labels now use top-left anchors, and
|
||||||
use the default top-left anchors and their explicit position/size, yielding the native text x=110 and
|
all root Controls apply their presets only after parenting.
|
||||||
avoiding that diagnostic.
|
|
||||||
|
A second manual check separated the apparent bottom row from the real History batches: it was the ordinary
|
||||||
|
ADV dialogue Label leaking above HISTORY's full-screen retained surface. The real batches contained several
|
||||||
|
non-empty retained groups, but their target layouts had width/height zero and Godot clipped each to one pixel.
|
||||||
|
`HISTORY.BIN` deliberately only resets and repositions layouts 2..6; `SYSTEM4.BIN@0x7..0x82` defines and
|
||||||
|
resets all nine shared layouts before scene dispatch. In particular, slots 2..6 are 650x150 at x=65 and
|
||||||
|
y=0/150/300/450/600. The Phase-A single-scene bootstrap now carries forward that exact nine-layout prefix,
|
||||||
|
in the same category as its inherited SO000/SO001 state, until full SYSTEM4 replay replaces the bridge.
|
||||||
|
The multi-message regression proves that at least two non-empty rows retain 650x150 geometry.
|
||||||
|
|
||||||
|
While a nested timed raw-input frame owns the screen, Godot now hides the enclosing page's ordinary dialogue
|
||||||
|
Label and independently animated wait marker. Native rendering gets this layering naturally because both are
|
||||||
|
part of retained composition; the port must state it explicitly because those two elements are separate
|
||||||
|
Godot overlays. They reappear with the same parked page after the modal frame returns.
|
||||||
|
|
||||||
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.
|
||||||
|
|||||||
@@ -1790,5 +1790,18 @@ Godot's dynamically created History/surface labels also no longer request full-r
|
|||||||
a parent. They use top-left absolute placement, matching the batch's origin+cursor coordinates and removing
|
a parent. They use top-left absolute placement, matching the batch's origin+cursor coordinates and removing
|
||||||
the associated parent/layout diagnostic.
|
the associated parent/layout diagnostic.
|
||||||
|
|
||||||
**Next:** manually recheck History layout and hover against the original screenshot. If it matches, proceed
|
The next manual run showed that the sole bottom line was actually the enclosing ADV dialogue overlay, while
|
||||||
|
the genuine retained batches were clipped to one pixel. The backlog itself is healthy: a new real SC0000
|
||||||
|
six-message regression produces multiple non-empty History rows. The missing state was the exact
|
||||||
|
`SYSTEM4.BIN` prefix that defines nine shared text layouts before dispatching any scene; HISTORY only resets
|
||||||
|
and repositions slots 2..6. The single-scene Godot bootstrap now carries forward all nine definitions, with
|
||||||
|
History rows sized 650x150 at x=65 and y increments of 150, until full SYSTEM4 replay replaces this existing
|
||||||
|
class of inherited-state bridges.
|
||||||
|
|
||||||
|
Godot also suppresses the parked page's ordinary dialogue Label and wait indicator while any nested timed
|
||||||
|
raw-input frame owns the modal screen, then restores them on return. Root Control anchor presets are now
|
||||||
|
applied after parenting, removing the remaining occurrence of the associated runtime layout diagnostic.
|
||||||
|
Validation: engine 198/198, zero-warning Godot build, threaded `SELFTEST OK`, and vm0 RECOVER clean.
|
||||||
|
|
||||||
|
**Next:** manually recheck the five History rows and overlay cleanup against the original. If they match, proceed
|
||||||
with stored History voice replay (`0x1bd`), followed by `0xd3/0xd4/0xd5` smooth-scroll fidelity.
|
with stored History voice replay (`0x1bd`), followed by `0xd3/0xd4/0xd5` smooth-scroll fidelity.
|
||||||
|
|||||||
@@ -14,9 +14,14 @@ public class HistoryInteractionOpsTests
|
|||||||
|
|
||||||
private sealed class StopAfterHistoryReturnsException : Exception { }
|
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
|
private sealed class Sc0000HistoryCloseHost : RecordingHost
|
||||||
{
|
{
|
||||||
public VirtualMachine Vm = null!;
|
public VirtualMachine Vm = null!;
|
||||||
|
private readonly int _openAtWait;
|
||||||
private long _now;
|
private long _now;
|
||||||
private int _modalSleeps;
|
private int _modalSleeps;
|
||||||
public bool HistoryReturned;
|
public bool HistoryReturned;
|
||||||
@@ -24,6 +29,8 @@ public class HistoryInteractionOpsTests
|
|||||||
public IReadOnlyList<RenderObject> FirstHistoryFrame = Array.Empty<RenderObject>();
|
public IReadOnlyList<RenderObject> FirstHistoryFrame = Array.Empty<RenderObject>();
|
||||||
public override long InputClockMilliseconds => _now;
|
public override long InputClockMilliseconds => _now;
|
||||||
|
|
||||||
|
public Sc0000HistoryCloseHost(int openAtWait = 1) => _openAtWait = openAtWait;
|
||||||
|
|
||||||
public override void Sleep(long duration)
|
public override void Sleep(long duration)
|
||||||
{
|
{
|
||||||
base.Sleep(duration);
|
base.Sleep(duration);
|
||||||
@@ -47,6 +54,7 @@ public class HistoryInteractionOpsTests
|
|||||||
public override void WaitForInput(int layoutSlot, Func<bool> serviceInputCallback)
|
public override void WaitForInput(int layoutSlot, Func<bool> serviceInputCallback)
|
||||||
{
|
{
|
||||||
Waits++;
|
Waits++;
|
||||||
|
if (Waits < _openAtWait) return;
|
||||||
Vm.UpdatePointer(684, 572);
|
Vm.UpdatePointer(684, 572);
|
||||||
while (serviceInputCallback()) { }
|
while (serviceInputCallback()) { }
|
||||||
Assert.True(Vm.TryActivatePointer(684, 572));
|
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]
|
[Fact]
|
||||||
public void LookupArrayPreservesLocalStorageForLocalBases()
|
public void LookupArrayPreservesLocalStorageForLocalBases()
|
||||||
{
|
{
|
||||||
@@ -123,6 +159,7 @@ public class HistoryInteractionOpsTests
|
|||||||
var vm = new VirtualMachine(scripts.RequireByName("SC0000.BIN"), Table, host,
|
var vm = new VirtualMachine(scripts.RequireByName("SC0000.BIN"), Table, host,
|
||||||
new VmOptions(MaxSteps: 2_000_000), scripts);
|
new VmOptions(MaxSteps: 2_000_000), scripts);
|
||||||
host.Vm = vm;
|
host.Vm = vm;
|
||||||
|
SeedSystem4AdvLayouts(scripts, vm.TextHistory);
|
||||||
vm.Globals[0x6c1] = 1;
|
vm.Globals[0x6c1] = 1;
|
||||||
|
|
||||||
Assert.Throws<StopAfterHistoryReturnsException>(() => vm.Run());
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -84,8 +84,8 @@ public partial class Main : Godot.Control
|
|||||||
StretchMode = TextureRect.StretchModeEnum.Scale,
|
StretchMode = TextureRect.StretchModeEnum.Scale,
|
||||||
MouseFilter = MouseFilterEnum.Ignore,
|
MouseFilter = MouseFilterEnum.Ignore,
|
||||||
};
|
};
|
||||||
_screenView.SetAnchorsAndOffsetsPreset(LayoutPreset.FullRect);
|
|
||||||
AddChild(_screenView); // added first -> draws behind the text/status labels
|
AddChild(_screenView); // added first -> draws behind the text/status labels
|
||||||
|
_screenView.SetAnchorsAndOffsetsPreset(LayoutPreset.FullRect);
|
||||||
|
|
||||||
// Native ADV wait marker: a tiny independently animated atlas region. Keeping it separate from the
|
// Native ADV wait marker: a tiny independently animated atlas region. Keeping it separate from the
|
||||||
// 800x600 software backbuffer avoids recompositing the entire retained scene throughout static waits.
|
// 800x600 software backbuffer avoids recompositing the entire retained scene throughout static waits.
|
||||||
@@ -99,16 +99,16 @@ public partial class Main : Godot.Control
|
|||||||
AddChild(_waitIndicator);
|
AddChild(_waitIndicator);
|
||||||
|
|
||||||
_text = new Label { AutowrapMode = TextServer.AutowrapMode.WordSmart, MouseFilter = MouseFilterEnum.Ignore };
|
_text = new Label { AutowrapMode = TextServer.AutowrapMode.WordSmart, MouseFilter = MouseFilterEnum.Ignore };
|
||||||
_text.SetAnchorsAndOffsetsPreset(LayoutPreset.FullRect);
|
|
||||||
AddChild(_text);
|
AddChild(_text);
|
||||||
|
_text.SetAnchorsAndOffsetsPreset(LayoutPreset.FullRect);
|
||||||
_speaker = new Label { MouseFilter = MouseFilterEnum.Ignore, Visible = false };
|
_speaker = new Label { MouseFilter = MouseFilterEnum.Ignore, Visible = false };
|
||||||
_speaker.SetAnchorsAndOffsetsPreset(LayoutPreset.FullRect);
|
|
||||||
AddChild(_speaker);
|
AddChild(_speaker);
|
||||||
|
_speaker.SetAnchorsAndOffsetsPreset(LayoutPreset.FullRect);
|
||||||
_surfaceTextLabels.Add(_speaker);
|
_surfaceTextLabels.Add(_speaker);
|
||||||
_status = new Label();
|
_status = new Label();
|
||||||
|
AddChild(_status);
|
||||||
_status.SetAnchorsAndOffsetsPreset(LayoutPreset.BottomWide);
|
_status.SetAnchorsAndOffsetsPreset(LayoutPreset.BottomWide);
|
||||||
_status.OffsetLeft = 40; _status.OffsetTop = -60;
|
_status.OffsetLeft = 40; _status.OffsetTop = -60;
|
||||||
AddChild(_status);
|
|
||||||
_locatorHud = new Label { Visible = false, MouseFilter = MouseFilterEnum.Ignore };
|
_locatorHud = new Label { Visible = false, MouseFilter = MouseFilterEnum.Ignore };
|
||||||
_locatorHud.Position = new Vector2(8, 8);
|
_locatorHud.Position = new Vector2(8, 8);
|
||||||
AddChild(_locatorHud);
|
AddChild(_locatorHud);
|
||||||
@@ -212,6 +212,13 @@ public partial class Main : Godot.Control
|
|||||||
if (histFile != null) { _hist = new Age.Engine.Diagnostics.HistogramTraceSink();
|
if (histFile != null) { _hist = new Age.Engine.Diagnostics.HistogramTraceSink();
|
||||||
sink = new Age.Engine.Diagnostics.CompositeTraceSink(_trace, _hist); }
|
sink = new Age.Engine.Diagnostics.CompositeTraceSink(_trace, _hist); }
|
||||||
_vm = new VirtualMachine(script, table, _host, new VmOptions(MaxSteps: 20_000_000), provider, sink);
|
_vm = new VirtualMachine(script, table, _host, new VmOptions(MaxSteps: 20_000_000), provider, sink);
|
||||||
|
// SYSTEM4.BIN defines these nine shared ADV text layouts before dispatching any scene. The
|
||||||
|
// single-scene harness starts after that prefix, so carry forward its exact script-owned state
|
||||||
|
// alongside the inherited SO000/SO001 state below. Full Phase-B SYSTEM4 replay will replace this
|
||||||
|
// bootstrap as one unit; HISTORY depends on the 650x150 dimensions of layouts 2..6 for clipping.
|
||||||
|
if (!_selftest)
|
||||||
|
AdvTextLayoutBootstrap.ApplyLeadingDefinitionsAndResets(
|
||||||
|
scripts!.RequireByName("SYSTEM4.BIN"), table, _vm.TextHistory);
|
||||||
// SYSTEM4 loads the shared SO001 chrome sheet into surface slot 17 before any scene runs.
|
// SYSTEM4 loads the shared SO001 chrome sheet into surface slot 17 before any scene runs.
|
||||||
// Seed that inherited retained-surface state without replaying the entrypoint's unrelated UI flow.
|
// Seed that inherited retained-surface state without replaying the entrypoint's unrelated UI flow.
|
||||||
if (!_selftest && resources.ResolveName("SO001.AGF") is { } systemChrome)
|
if (!_selftest && resources.ResolveName("SO001.AGF") is { } systemChrome)
|
||||||
@@ -541,7 +548,10 @@ public partial class Main : Godot.Control
|
|||||||
|
|
||||||
private void UpdateAdvTextPresentation()
|
private void UpdateAdvTextPresentation()
|
||||||
{
|
{
|
||||||
_text.Visible = !_host.IsAdvPagePresentationSuspended;
|
// Modal callback scripts composite their own full-screen UI while the enclosing ADV wait remains
|
||||||
|
// parked. The ordinary dialogue Label is a Godot overlay rather than part of the retained surface,
|
||||||
|
// so hide it while that nested input owner is active or it leaks above HISTORY's background.
|
||||||
|
_text.Visible = !_host.IsAdvPagePresentationSuspended && !_vm.IsRawInputCallbackActive;
|
||||||
if (!_text.Visible) return;
|
if (!_text.Visible) return;
|
||||||
var t = _host.SnapshotAdvText();
|
var t = _host.SnapshotAdvText();
|
||||||
_text.Position = new Vector2(t.X, 430 + t.Y);
|
_text.Position = new Vector2(t.X, 430 + t.Y);
|
||||||
@@ -621,6 +631,13 @@ public partial class Main : Godot.Control
|
|||||||
|
|
||||||
private void UpdateAdvWaitIndicatorPresentation()
|
private void UpdateAdvWaitIndicatorPresentation()
|
||||||
{
|
{
|
||||||
|
// As with the ordinary dialogue Label, the enclosing page's separately animated marker must sit
|
||||||
|
// out while HISTORY/HIDEWIN owns raw input; native retained composition naturally covers it.
|
||||||
|
if (_vm.IsRawInputCallbackActive)
|
||||||
|
{
|
||||||
|
_waitIndicator.Visible = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
var snapshot = _host.SnapshotAdvWaitIndicator();
|
var snapshot = _host.SnapshotAdvWaitIndicator();
|
||||||
if (snapshot == null)
|
if (snapshot == null)
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user