Implement ADV skip lifecycle

This commit is contained in:
gamer147
2026-07-20 16:41:44 -04:00
parent 6aede1e525
commit 7d438a7574
9 changed files with 176 additions and 34 deletions

View File

@@ -302,6 +302,53 @@ public class HotspotInputTests
Assert.Equal(new[] { true, false }, host.MessageSkipChanges);
}
[Fact]
public void AdvSkipService_SuspendsAndRestoresPersistentMessageSkip()
{
var table = OpcodeTableJson.Load(Paths.OpcodesJson);
var script = ScriptAssembler.Assemble(table, "MESSAGE_SKIP_LIFECYCLE", new List<(int, Operand[])>
{
(0x88, new[] { I(1) }),
(0x19b, Array.Empty<Operand>()),
(0x19a, new[] { G(0x144) }),
(0x1c7, new[] { G(0x145) }),
(0x19c, Array.Empty<Operand>()),
(0x1c7, new[] { G(0x146) }),
(0x2, Array.Empty<Operand>()),
}, Array.Empty<string>());
var host = new RecordingHost();
var vm = new VirtualMachine(script, table, host);
vm.Run();
Assert.Equal(1, vm.Globals.GetValueOrDefault(0x144));
Assert.Equal(0, vm.Globals.GetValueOrDefault(0x145));
Assert.Equal(1, vm.Globals.GetValueOrDefault(0x146));
Assert.True(vm.MessageSkipEnabled);
Assert.Equal(new[] { true, false, true }, host.MessageSkipChanges);
}
[Fact]
public void AdvSkipService_RestoreIncludesLiveReadSkipChannel()
{
var table = OpcodeTableJson.Load(Paths.OpcodesJson);
var script = ScriptAssembler.Assemble(table, "READ_SKIP_LIFECYCLE", new List<(int, Operand[])>
{
(0x19b, Array.Empty<Operand>()),
(0x19c, Array.Empty<Operand>()),
(0x1c7, new[] { G(0x147) }),
(0x2, Array.Empty<Operand>()),
}, Array.Empty<string>());
var host = new RecordingHost { AdvReadSkip = true };
var vm = new VirtualMachine(script, table, host);
vm.Run();
Assert.Equal(1, vm.Globals.GetValueOrDefault(0x147));
Assert.False(vm.MessageSkipEnabled);
Assert.Equal(new[] { false, true }, host.MessageSkipChanges);
}
[Fact]
public void Sc0000MessageSkipButton_EnablesPersistentServiceState()
{

View File

@@ -36,6 +36,7 @@ public sealed class VirtualMachine
private long _autoMessageTime1Ms = 2000;
private bool _autoVoicePending;
private volatile bool _messageSkipEnabled;
private volatile bool _messageSkipServiceActive;
private AdvTextStyle _advTextStyle = AdvTextStyle.Default;
private readonly Dictionary<string, int> _valueSwitchTargets = new(StringComparer.Ordinal);
public long CallScriptDispatches { get; private set; }
@@ -819,14 +820,25 @@ public sealed class VirtualMachine
case "u0041B290":
case "set-message-skip": // 0x88: persistent all-message fast-forward service state
_messageSkipEnabled = Read(a[0]) != 0;
_host.SetMessageSkipActive(_messageSkipEnabled);
_messageSkipServiceActive = _messageSkipEnabled;
_host.SetMessageSkipActive(_messageSkipServiceActive);
return pc + 1;
case "u00414E50": // 0x19a: persistent state used by the SO001 active overlay
Write(a[0], _messageSkipEnabled ? 1 : 0); return pc + 1;
case "u00414E80":
case "suspend-adv-skip-service": // 0x19b: preserve the toggle while leaving ADV presentation
_messageSkipServiceActive = false;
_host.SetMessageSkipActive(false);
return pc + 1;
case "u00414EC0":
case "resume-adv-skip-service": // 0x19c: recompute active fast-forward on ADV entry
_messageSkipServiceActive = _messageSkipEnabled || _host.IsAdvReadSkipActive;
_host.SetMessageSkipActive(_messageSkipServiceActive);
return pc + 1;
case "get-message-skip": // 0x1c7: persistent Skip or host-supplied Ctrl fast-forward
// The native per-op tick continually re-arms the transient run-state bit while op 0x88's
// persistent flag is set. The host channel carries the physically held Ctrl/input source.
Write(a[0], _messageSkipEnabled || _host.IsMessageSkipActive ? 1 : 0); return pc + 1;
// The native per-op tick continually re-arms the transient run-state bit while the
// ADV service is enabled. The host channel also carries physical fast-forward input.
Write(a[0], _messageSkipServiceActive || _host.IsMessageSkipActive ? 1 : 0); return pc + 1;
case "get-adv-read-skip-state": // 0x1cc: per-message read/click skip service state
case "get-adv-service-state": // compatibility with pre-recovery generated tables
Write(a[0], _host.IsAdvReadSkipActive ? 1 : 0); return pc + 1;