Correct ADV History metadata ordering
This commit is contained in:
@@ -1292,7 +1292,7 @@ The write path is part of ordinary ADV execution:
|
||||
- op `0x6e`'s glyph builder appends normal text chunks with the active geometry/font/color state.
|
||||
- voice op `0xc4` appends a `0x40000000` record containing `{voice_id,0}`; History replay op `0x1bd`
|
||||
uses the same writer with `{voice_id,1}` when recording is enabled.
|
||||
- op `0x1d2` appends a `0x20000000` record with operand 1 as the value and operand 2 as the metadata type.
|
||||
- op `0x1d2` appends a `0x20000000` record with operand 1 as the metadata type and operand 2 as its value.
|
||||
It was previously misclassified as a safe statement marker; 17,323 corpus uses make this a foundational
|
||||
correction.
|
||||
- op `0x1bb(0)` writes suppression bit `0x80000000` at `ctx+0x55110`; op `0x1bb(1)` clears it. HISTORY uses
|
||||
@@ -1409,6 +1409,13 @@ History's surrounding `0x1bb(0)` suppression prevents the replay from recording
|
||||
playback. Its precise audible meaning remains unproven, so the Godot host retains it through its queue and
|
||||
timeline rather than inventing different playback behavior.
|
||||
|
||||
A live replay trace exposed a corrected prerequisite on that path. The clicked SC0000 row found voice pair
|
||||
`{0x24,0}` but returned at `HISTORY.BIN@0x9ee` because its required type-2 metadata lookup failed. Native
|
||||
`op_0x1d2_append_text_history_metadata@0x41f9c0` fetches operand 2 and then operand 1 before passing them as
|
||||
`(type,value)` to `text_history_append_typed_metadata@0x455f00`; the helper stores value at record `+0x14`
|
||||
and type at `+0x18`. Thus SC0000's `0x1d2(2,0x11)` means type 2, character/value `0x11`, not the reverse.
|
||||
The port now writes that pair in the native order, allowing HISTORY's type-2 gate to reach voice playback.
|
||||
|
||||
History's remaining work is the `0xd3/0xd4/0xd5` smooth-scroll callback scheduler and its small supporting
|
||||
gaps. None changes backlog ownership or requires choosing a save/profile backend.
|
||||
|
||||
|
||||
@@ -86,9 +86,9 @@
|
||||
- **evidence:** Ghidra /v2: op_0x1d1_render_text_history@0x41f950 forwards all five operands plus the text manager at ctx+0x14508 to text_history_render_records@0x4526c0. That helper walks 0x48-byte retained text records, applies record flags/colors/font state, measures strings, and rasterizes/binds them to the chosen layout. HISTORY.BIN uses it to draw each visible backlog line.
|
||||
|
||||
### 0x1d2 `append-text-history-metadata` (append-text-history-metadata, argc 2)
|
||||
- **summary:** (value)(metadata_type) - append a typed metadata record to the current retained ADV message group when history recording is enabled.
|
||||
- **summary:** (metadata_type)(value) - append a typed metadata record to the current retained ADV message group when history recording is enabled.
|
||||
- **grounding:** source=investigation, confidence=high
|
||||
- **evidence:** Ghidra /v2: op_0x1d2_handler@0x41f9c0 tests ctx+0x55110 and, when recording is enabled, calls text_history_append_typed_metadata@0x455f00. That appends a 0x48-byte record with flag 0x20000000, operand 1 at +0x14, and operand 2 at +0x18, consuming the pending group-start flag if set. The corpus has 17,323 uses; HISTORY.BIN later queries metadata types 1 and 2 through op 0x1d3. This opcode is effectful, not a statement marker.
|
||||
- **evidence:** Ghidra /v2: op_0x1d2_append_text_history_metadata@0x41f9c0 tests ctx+0x55110, fetches operand 2 followed by operand 1, and calls text_history_append_typed_metadata@0x455f00 as (layout=0, type=operand1, value=operand2). The helper appends a 0x48-byte record with flag 0x20000000, value at +0x14, and type at +0x18, consuming the pending group-start flag if set. SC0000's voiced page at 0x96d writes (type=2, value=0x11), and HISTORY.BIN later queries type 2 before replay. The corpus has 17,323 uses. This opcode is effectful, not a statement marker.
|
||||
|
||||
### 0x1d3 `find-text-history-value` (find-text-history-value, argc 5)
|
||||
- **summary:** (out_found)(out_value)(direction)(record_index)(value_type) - find typed metadata within one retained ADV message group.
|
||||
|
||||
@@ -1835,3 +1835,17 @@ indexed voice service and is saved.
|
||||
|
||||
**Next:** investigate and implement the `0xd3/0xd4/0xd5` smooth-scroll callback/interpolation family as a
|
||||
separate fidelity slice, including the remaining scheduler support it depends upon.
|
||||
|
||||
### ADV History live voice gate correction (2026-07-19)
|
||||
|
||||
Manual replay remained silent, and the synchronized live trace localized the failure before the audio host:
|
||||
the clicked row reached `HISTORY.BIN@0x9c7`, found retained voice `{0x24,0}`, then failed its type-2 metadata
|
||||
query at `0x9e3/0x9ee` and returned without executing `0xc4` or `0x1bd`. The retained writer had reversed
|
||||
op `0x1d2`'s operands. Native handler/helper dataflow and SC0000's concrete `0x1d2(2,0x11)` site prove the
|
||||
ABI is `(metadata_type,value)`; the helper stores value at record `+0x14` and type at `+0x18`.
|
||||
|
||||
The VM now records operand 2 as the value and operand 1 as the type. The real-script regression clears the
|
||||
voice request emitted by the original page, clicks the actual visible voiced History row, and requires a new
|
||||
request for SC0000 `0x24`/`MAN999.OGG`; this prevents a pre-History voice from producing a false positive.
|
||||
The `/v2` handler and helper comments are corrected and saved. Manual replay of that same retained row now
|
||||
plays audibly, confirming the full click-to-Godot path.
|
||||
|
||||
@@ -18,7 +18,7 @@ public class AdvTextHistoryTests
|
||||
(0x75, new[] { I(24) }),
|
||||
(0x76, new[] { I(0xf0e0d0) }),
|
||||
(0x7a, new[] { I(0), I(12), I(34) }),
|
||||
(0x1d2, new[] { I(123), I(1) }),
|
||||
(0x1d2, new[] { I(1), I(123) }),
|
||||
(0xc4, new[] { I(77) }),
|
||||
(0x6e, new[] { I(0), S(0) }),
|
||||
(0x71, new[] { I(1) }),
|
||||
@@ -76,7 +76,7 @@ public class AdvTextHistoryTests
|
||||
{
|
||||
(0x1bb, new[] { I(0) }),
|
||||
(0x70, new[] { I(2), I(500), I(100), I(10), I(20) }),
|
||||
(0x1d2, new[] { I(9), I(2) }),
|
||||
(0x1d2, new[] { I(2), I(9) }),
|
||||
(0xc4, new[] { I(88) }),
|
||||
(0x6e, new[] { I(0), S(0) }),
|
||||
(0x1bb, new[] { I(1) }),
|
||||
|
||||
@@ -13,6 +13,7 @@ public class HistoryInteractionOpsTests
|
||||
private static Operand P(int address) => new(T_LPTR, address);
|
||||
|
||||
private sealed class StopAfterHistoryReturnsException : Exception { }
|
||||
private sealed class StopAfterHistoryVoiceException : Exception { }
|
||||
|
||||
private static void SeedSystem4AdvLayouts(Sys4ScriptProvider scripts, AdvTextHistory history)
|
||||
=> Assert.Equal(9, AdvTextLayoutBootstrap.ApplyLeadingDefinitionsAndResets(
|
||||
@@ -65,6 +66,55 @@ public class HistoryInteractionOpsTests
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class Sc0000HistoryVoiceHost : RecordingHost
|
||||
{
|
||||
public VirtualMachine Vm = null!;
|
||||
private long _now;
|
||||
private int _modalSleeps;
|
||||
private bool _pressedVoiceRow;
|
||||
public AdvTextHistoryRenderBatch? ClickedBatch;
|
||||
public override long InputClockMilliseconds => _now;
|
||||
|
||||
public override void Sleep(long duration)
|
||||
{
|
||||
base.Sleep(duration);
|
||||
_now += System.Math.Max(16, duration);
|
||||
if (!Vm.IsRawInputCallbackActive) return;
|
||||
_modalSleeps++;
|
||||
if (!_pressedVoiceRow)
|
||||
{
|
||||
ClickedBatch = HistoryRenders.LastOrDefault(batch => batch.Layout.OriginY < 600
|
||||
&& Vm.TextHistory.TryFindVoicePair(batch.FirstRecordIndex, out _, out _));
|
||||
if (ClickedBatch == null) return;
|
||||
_pressedVoiceRow = true;
|
||||
Vm.UpdatePointer(200, ClickedBatch.Layout.OriginY + 50);
|
||||
Vm.UpdateMouseButtonState(0x1, true);
|
||||
Vm.QueueInputCallback(4);
|
||||
return;
|
||||
}
|
||||
if (_modalSleeps == 3)
|
||||
{
|
||||
Vm.UpdateMouseButtonState(0x1, false);
|
||||
Vm.QueueInputCallback(10);
|
||||
}
|
||||
if (VoiceRequests.Count > 0) throw new StopAfterHistoryVoiceException();
|
||||
}
|
||||
|
||||
public override void WaitForInput(int layoutSlot, Func<bool> serviceInputCallback)
|
||||
{
|
||||
Waits++;
|
||||
bool hasRecentVoice = Vm.TextHistory.Entries.TakeLast(5)
|
||||
.Any(entry => Vm.TextHistory.TryFindVoicePair(entry.FirstRecordIndex, out _, out _));
|
||||
if (!hasRecentVoice) return;
|
||||
Voices.Clear();
|
||||
VoiceRequests.Clear();
|
||||
Vm.UpdatePointer(684, 572);
|
||||
while (serviceInputCallback()) { }
|
||||
Assert.True(Vm.TryActivatePointer(684, 572));
|
||||
while (serviceInputCallback()) { }
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RealHistoryRendersMultipleRowsAfterSeveralSc0000Messages()
|
||||
{
|
||||
@@ -189,4 +239,29 @@ public class HistoryInteractionOpsTests
|
||||
Assert.NotEmpty(visibleRows);
|
||||
Assert.All(visibleRows, render => Assert.Equal(65, render.Layout.OriginX));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RealHistoryVoicedRowDispatchesItsRetainedVoicePair()
|
||||
{
|
||||
var scripts = Sys4ScriptProvider.Load(Table);
|
||||
var host = new Sc0000HistoryVoiceHost();
|
||||
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<StopAfterHistoryVoiceException>(() => vm.Run());
|
||||
|
||||
var batch = Assert.IsType<AdvTextHistoryRenderBatch>(host.ClickedBatch);
|
||||
Assert.True(vm.TextHistory.TryFindVoicePair(batch.FirstRecordIndex,
|
||||
out long expectedVoice, out long expectedVariant));
|
||||
Assert.Equal((0x24L, 0L), (expectedVoice, expectedVariant));
|
||||
Assert.Equal(new[] { (expectedVoice, checked((int)expectedVariant)) }, host.VoiceRequests);
|
||||
|
||||
var resources = ResourceMap.Load();
|
||||
var voice = Assert.IsType<AssetEntry>(resources.Resolve("SC0000", expectedVoice));
|
||||
Assert.Equal("MAN999.OGG", voice.Name);
|
||||
Assert.NotEmpty(resources.ReadAudio(voice).Bytes);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -779,8 +779,8 @@ public sealed class VirtualMachine
|
||||
if (enabled) _host.EndTextHistoryPresentation();
|
||||
}
|
||||
return pc + 1;
|
||||
case "append-text-history-metadata": // 0x1d2: typed value attached to the current group
|
||||
TextHistory.AppendMetadata(Read(a[0]), Read(a[1]), _advTextStyle); return pc + 1;
|
||||
case "append-text-history-metadata": // 0x1d2: (metadata type, value)
|
||||
TextHistory.AppendMetadata(Read(a[1]), Read(a[0]), _advTextStyle); return pc + 1;
|
||||
case "step-text-history": // 0x1d0: cumulative delta from the latest retained boundary
|
||||
if (TextHistory.TryStepGroup((int)Read(a[2]), out var historyEntry))
|
||||
{
|
||||
|
||||
@@ -4349,21 +4349,21 @@ abi_source = "kelebek+decode-validated"
|
||||
[opcode.semantics]
|
||||
name = "append-text-history-metadata"
|
||||
category = "adv"
|
||||
summary = "(value)(metadata_type) - append a typed metadata record to the current retained ADV message group when history recording is enabled."
|
||||
summary = "(metadata_type)(value) - append a typed metadata record to the current retained ADV message group when history recording is enabled."
|
||||
noop_headless = false
|
||||
source = "investigation"
|
||||
confidence = "high"
|
||||
depends_on = []
|
||||
evidence = "Ghidra /v2: op_0x1d2_handler@0x41f9c0 tests ctx+0x55110 and, when recording is enabled, calls text_history_append_typed_metadata@0x455f00. That appends a 0x48-byte record with flag 0x20000000, operand 1 at +0x14, and operand 2 at +0x18, consuming the pending group-start flag if set. The corpus has 17,323 uses; HISTORY.BIN later queries metadata types 1 and 2 through op 0x1d3. This opcode is effectful, not a statement marker."
|
||||
evidence = "Ghidra /v2: op_0x1d2_append_text_history_metadata@0x41f9c0 tests ctx+0x55110, fetches operand 2 followed by operand 1, and calls text_history_append_typed_metadata@0x455f00 as (layout=0, type=operand1, value=operand2). The helper appends a 0x48-byte record with flag 0x20000000, value at +0x14, and type at +0x18, consuming the pending group-start flag if set. SC0000's voiced page at 0x96d writes (type=2, value=0x11), and HISTORY.BIN later queries type 2 before replay. The corpus has 17,323 uses. This opcode is effectful, not a statement marker."
|
||||
|
||||
[[opcode.semantics.args]]
|
||||
i = 1
|
||||
role = "metadata value"
|
||||
role = "metadata type"
|
||||
observed_types = ["imm"]
|
||||
|
||||
[[opcode.semantics.args]]
|
||||
i = 2
|
||||
role = "metadata type"
|
||||
role = "metadata value"
|
||||
observed_types = ["imm"]
|
||||
|
||||
[[opcode]]
|
||||
|
||||
Reference in New Issue
Block a user