Extract VM input opcode handler
This commit is contained in:
@@ -195,6 +195,9 @@ retained history rendering, metadata/voice lookup, and history backlog clearing.
|
|||||||
`engine/Age.Engine/Vm/VirtualMachine.AdvServices.cs` owns persistent/active message-skip control, read-skip
|
`engine/Age.Engine/Vm/VirtualMachine.AdvServices.cs` owns persistent/active message-skip control, read-skip
|
||||||
settings and queries, auto-message state/timing, and per-message voice/skip reset opcode dispatch; shared state
|
settings and queries, auto-message state/timing, and per-message voice/skip reset opcode dispatch; shared state
|
||||||
and refresh helpers remain in the VM coordinator because live text and input paths also consume them.
|
and refresh helpers remain in the VM coordinator because live text and input paths also consume them.
|
||||||
|
`engine/Age.Engine/Vm/VirtualMachine.Input.cs` owns blocking ADV waits, hotspot registration/arming, cursor
|
||||||
|
resources and virtual position, raw mouse/joystick callback registration and dispatch, action polling, and
|
||||||
|
physical-input mapping; public host-thread input entry points and shared synchronization remain in the coordinator.
|
||||||
|
|
||||||
The disposable `build/page-map-<SCENE>.jsonl` files are produced by editor/development Godot runs and map
|
The disposable `build/page-map-<SCENE>.jsonl` files are produced by editor/development Godot runs and map
|
||||||
runtime ADV page ordinals to their authoritative script offsets for `tools/locate_page.py`. Packaged exports
|
runtime ADV page ordinals to their authoritative script offsets for `tools/locate_page.py`. Packaged exports
|
||||||
|
|||||||
@@ -681,6 +681,13 @@ do not mix mechanical moves with semantic changes.
|
|||||||
remain in the VM coordinator because live-text and input paths also consume them. Runtime validation remains
|
remain in the VM coordinator because live-text and input paths also consume them. Runtime validation remains
|
||||||
green.
|
green.
|
||||||
|
|
||||||
|
The tenth bounded `VirtualMachine.Step` extraction moved blocking ADV waits, hotspot registration/arming,
|
||||||
|
cursor resources and virtual position, raw mouse/joystick callback registration and dispatch, action polling,
|
||||||
|
and physical-input mapping into `engine/Age.Engine/Vm/VirtualMachine.Input.cs`. The top-level dispatcher
|
||||||
|
retains all labels and aliases at their existing positions and routes them through guarded `StepInput`; public
|
||||||
|
host-thread input entry points, shared synchronization, and callback service helpers remain in the coordinator.
|
||||||
|
Runtime validation remains green.
|
||||||
|
|
||||||
**Gate:** no externally visible behavior or command changes; generated artifacts are byte-identical where
|
**Gate:** no externally visible behavior or command changes; generated artifacts are byte-identical where
|
||||||
deterministic, and the corresponding engine, Python, Godot, and corpus validations remain green after
|
deterministic, and the corresponding engine, Python, Godot, and corpus validations remain green after
|
||||||
each domain move.
|
each domain move.
|
||||||
@@ -1054,7 +1061,8 @@ Continue step 2 of the **codebase consolidation** maintenance slice: behavior-ne
|
|||||||
by the tracked launcher and layered validation driver. With the planned `Main`, `GodotAdvHost`, and `GfxState`
|
by the tracked launcher and layered validation driver. With the planned `Main`, `GodotAdvHost`, and `GfxState`
|
||||||
domains isolated and the audio, movie, surface/texture, retained-object, animation, presentation, ADV-text, and
|
domains isolated and the audio, movie, surface/texture, retained-object, animation, presentation, ADV-text, and
|
||||||
text-history `VirtualMachine.Step` families routed through domain handlers, with ADV skip/auto-message services
|
text-history `VirtualMachine.Step` families routed through domain handlers, with ADV skip/auto-message services
|
||||||
now isolated as well, extract the interactive input/hotspot/cursor callback opcode family next without replacing
|
and interactive input/hotspot/cursor callbacks now isolated as well, extract the sleep/timed-callback sequencing
|
||||||
the proven dispatcher or changing public types, commands, and generated output.
|
opcode family next without replacing the proven dispatcher or changing public types, commands, and generated
|
||||||
|
output.
|
||||||
Concrete playthrough blockers may still preempt this bounded maintenance work; the consolidation effort does
|
Concrete playthrough blockers may still preempt this bounded maintenance work; the consolidation effort does
|
||||||
not replace Phase B gameplay validation or the open cross-platform gates.
|
not replace Phase B gameplay validation or the open cross-platform gates.
|
||||||
|
|||||||
184
engine/Age.Engine/Vm/VirtualMachine.Input.cs
Normal file
184
engine/Age.Engine/Vm/VirtualMachine.Input.cs
Normal file
@@ -0,0 +1,184 @@
|
|||||||
|
using Age.Engine.Hosting;
|
||||||
|
using Age.Engine.Model;
|
||||||
|
|
||||||
|
namespace Age.Engine.Vm;
|
||||||
|
|
||||||
|
public sealed partial class VirtualMachine
|
||||||
|
{
|
||||||
|
private int StepInput(string label, IReadOnlyList<Operand> a, int pc)
|
||||||
|
{
|
||||||
|
switch (label)
|
||||||
|
{
|
||||||
|
case "wait-for-input":
|
||||||
|
RefreshAdvReadSkipState();
|
||||||
|
// Faithful headless: no player => halt here rather than plow past every prompt (see VmOptions).
|
||||||
|
if (_o.HaltAtWaitForInput) { HaltReason ??= "wait-for-input"; return HALT; }
|
||||||
|
// The native ADV chrome is a coroutine: after an earlier 0x93 cancellation its shared
|
||||||
|
// registration pass runs again before a stable message wait. Our blocking host models that
|
||||||
|
// scheduler boundary by re-arming the frame's retained definitions here.
|
||||||
|
bool wakeAtWait = false;
|
||||||
|
lock (_interactiveLock)
|
||||||
|
{
|
||||||
|
if (_cur.Hotspots.HasDefinitions && !_cur.Hotspots.Armed)
|
||||||
|
{
|
||||||
|
_interactiveFrame = _cur;
|
||||||
|
wakeAtWait = _cur.Hotspots.Arm(_pointerX, _pointerY);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (wakeAtWait) _host.WakeInputCallbackService();
|
||||||
|
_host.WaitForInput((int)Read(a[0]), ServiceHotspotCallback,
|
||||||
|
() => new AdvAutoWaitState(_autoMessageEnabled, _autoVoicePending,
|
||||||
|
_autoMessageTime0Ms, _autoMessageTime1Ms));
|
||||||
|
_sharedProfile.ReadText.QueueMessage(
|
||||||
|
_cur.Script.PackedId, CurrentReadMessageIndex(),
|
||||||
|
_cur.Script.ReadMessageOffsets.Count);
|
||||||
|
return pc + 1;
|
||||||
|
case "u0041BEB0":
|
||||||
|
case "register-hotspot-callbacks": // 0x90: inclusive rect + enter/leave/activate local callbacks
|
||||||
|
lock (_interactiveLock)
|
||||||
|
_cur.Hotspots.Register((int)Read(a[0]), (int)Read(a[1]), (int)Read(a[2]), (int)Read(a[3]),
|
||||||
|
(int)Read(a[4]), (int)Read(a[5]), (int)Read(a[6]));
|
||||||
|
return pc + 1;
|
||||||
|
case "u00415040":
|
||||||
|
case "cancel-hotspot-wait": // 0x93
|
||||||
|
lock (_interactiveLock)
|
||||||
|
{
|
||||||
|
_cur.Hotspots.Reset();
|
||||||
|
if (ReferenceEquals(_interactiveFrame, _cur)) _interactiveFrame = null;
|
||||||
|
}
|
||||||
|
return pc + 1;
|
||||||
|
case "u00415090":
|
||||||
|
case "arm-hotspot-wait": // 0x94
|
||||||
|
{
|
||||||
|
bool wake;
|
||||||
|
lock (_interactiveLock)
|
||||||
|
{
|
||||||
|
_interactiveFrame = _cur;
|
||||||
|
wake = _cur.Hotspots.Arm(_pointerX, _pointerY);
|
||||||
|
}
|
||||||
|
if (wake) _host.WakeInputCallbackService();
|
||||||
|
return pc + 1;
|
||||||
|
}
|
||||||
|
case "u0041C150":
|
||||||
|
case "bind-hotspot-key": // 0x97: bind a configured logical action to this record
|
||||||
|
lock (_interactiveLock)
|
||||||
|
_cur.Hotspots.BindKey((int)Read(a[0]), (int)Read(a[1]), (int)Read(a[2]),
|
||||||
|
(int)Read(a[3]), (int)Read(a[4]));
|
||||||
|
return pc + 1;
|
||||||
|
case "u0041B210":
|
||||||
|
case "set-cursor-resource": // 0x86: raw indexed .CUR resource
|
||||||
|
_host.SetCursorResource(Read(a[0])); return pc + 1;
|
||||||
|
case "u00414D10":
|
||||||
|
case "clear-cursor-resource": // 0x87
|
||||||
|
_host.ClearCursorResource(); return pc + 1;
|
||||||
|
case "mouse_callback":
|
||||||
|
case "register-mouse-callback": // 0xcc (poll interval ms, local target dword offset)
|
||||||
|
_cur.MouseCallbackIntervalMs = System.Math.Max(0, Read(a[0]));
|
||||||
|
_cur.MouseCallbackTarget = (int)Read(a[1]);
|
||||||
|
_cur.MouseCallbackNextAtMs = _host.InputClockMilliseconds + _cur.MouseCallbackIntervalMs;
|
||||||
|
lock (_interactiveLock) _rawInputFrame = _cur;
|
||||||
|
return pc + 1;
|
||||||
|
case "get-input-type":
|
||||||
|
case "dispatch-mouse-callback": // 0xcd
|
||||||
|
{
|
||||||
|
long now = _host.InputClockMilliseconds;
|
||||||
|
if (_cur.MouseCallbackTarget < 0 || now < _cur.MouseCallbackNextAtMs) return pc + 1;
|
||||||
|
_cur.MouseCallbackNextAtMs = now + _cur.MouseCallbackIntervalMs;
|
||||||
|
if (!_cur.Script.IndexByOffset.TryGetValue(_cur.MouseCallbackTarget, out int target))
|
||||||
|
return pc + 1;
|
||||||
|
_cur.CallStack.Add(pc + 1);
|
||||||
|
return target;
|
||||||
|
}
|
||||||
|
case "joy_callback":
|
||||||
|
case "register-joy-callback": // 0xfb (input index, local target dword offset)
|
||||||
|
{
|
||||||
|
int index = (int)Read(a[0]);
|
||||||
|
if ((uint)index < 32) _cur.InputCallbackTargets[index] = (int)Read(a[1]);
|
||||||
|
return pc + 1;
|
||||||
|
}
|
||||||
|
case "u0041E360":
|
||||||
|
case "set-input-action-count": // 0xfe: actions [0,count), no-input callback at count
|
||||||
|
{
|
||||||
|
int count = unchecked((int)Read(a[0]));
|
||||||
|
if (!InputBindings.SetActionCount(count))
|
||||||
|
{
|
||||||
|
HaltReason ??= $"input-action-count-out-of-range:{count}";
|
||||||
|
return HALT;
|
||||||
|
}
|
||||||
|
return pc + 1;
|
||||||
|
}
|
||||||
|
case "u00415A10":
|
||||||
|
case "poll-joy-callback-input": // 0xff
|
||||||
|
_cur.PendingInputCallbackMask = InputBindings.PollActionMask()
|
||||||
|
| Volatile.Read(ref _heldInputCallbackMask)
|
||||||
|
| Interlocked.Exchange(ref _queuedInputCallbackMask, 0);
|
||||||
|
_cur.InputCallbackScanIndex = 0;
|
||||||
|
return pc + 1;
|
||||||
|
case "u00415A60":
|
||||||
|
case "dispatch-joy-callbacks": // 0x100
|
||||||
|
{
|
||||||
|
int actionCount = InputBindings.ActionCount;
|
||||||
|
if (_cur.PendingInputCallbackMask == 0)
|
||||||
|
{
|
||||||
|
int idleTarget = _cur.InputCallbackTargets[actionCount];
|
||||||
|
if (idleTarget < 0 || !_cur.Script.IndexByOffset.TryGetValue(idleTarget, out int target))
|
||||||
|
return pc + 1;
|
||||||
|
_cur.CallStack.Add(pc + 1);
|
||||||
|
return target;
|
||||||
|
}
|
||||||
|
while (_cur.InputCallbackScanIndex < actionCount)
|
||||||
|
{
|
||||||
|
int index = _cur.InputCallbackScanIndex++;
|
||||||
|
if ((_cur.PendingInputCallbackMask & (1 << index)) == 0) continue;
|
||||||
|
int targetOffset = _cur.InputCallbackTargets[index];
|
||||||
|
if (targetOffset < 0 || !_cur.Script.IndexByOffset.TryGetValue(targetOffset, out int target))
|
||||||
|
continue;
|
||||||
|
// Resume on op 0x100 so another simultaneously active input can dispatch.
|
||||||
|
_cur.CallStack.Add(pc);
|
||||||
|
return target;
|
||||||
|
}
|
||||||
|
return pc + 1;
|
||||||
|
}
|
||||||
|
case "u0041E500":
|
||||||
|
case "map-joystick-button": // 0x107: button slot N emits action N+4
|
||||||
|
InputBindings.MapJoystickButton(unchecked((int)Read(a[0])), unchecked((int)Read(a[1])));
|
||||||
|
return pc + 1;
|
||||||
|
case "u00415E70":
|
||||||
|
case "get-mouse-button-state": // 0x108
|
||||||
|
Write(a[0], Volatile.Read(ref _mouseButtonState)); return pc + 1;
|
||||||
|
case "u00415F10":
|
||||||
|
case "consume-mouse-wheel-delta": // 0x10d
|
||||||
|
Write(a[0], Interlocked.Exchange(ref _mouseWheelDelta, 0)); return pc + 1;
|
||||||
|
case "u00415EC0":
|
||||||
|
case "get-cursor-virtual": // 0x109
|
||||||
|
{
|
||||||
|
int x, y;
|
||||||
|
lock (_interactiveLock) { x = _pointerX; y = _pointerY; }
|
||||||
|
Write(a[0], x == int.MinValue ? 0 : x);
|
||||||
|
Write(a[1], y == int.MinValue ? 0 : y);
|
||||||
|
return pc + 1;
|
||||||
|
}
|
||||||
|
case "u0041E540":
|
||||||
|
case "set-cursor-virtual": // 0x10a; retain the virtual position even without OS warping
|
||||||
|
UpdatePointer((int)Read(a[0]), (int)Read(a[1])); return pc + 1;
|
||||||
|
case "u0041E5A0":
|
||||||
|
case "map-mouse-button": // 0x10b: physical button -> slot, polled action is slot+4
|
||||||
|
InputBindings.MapMouseButton(unchecked((int)Read(a[0])), unchecked((int)Read(a[1])));
|
||||||
|
return pc + 1;
|
||||||
|
case "u0041E5E0":
|
||||||
|
case "map-keyboard-scancode": // 0x10c: logical action <- DIK translated through native VK table
|
||||||
|
{
|
||||||
|
int action = unchecked((int)Read(a[0]));
|
||||||
|
int dik = unchecked((int)Read(a[1]));
|
||||||
|
if (!InputBindings.MapKeyboardScanCode(action, dik))
|
||||||
|
{
|
||||||
|
HaltReason ??= $"keyboard-action-out-of-range:{action}";
|
||||||
|
return HALT;
|
||||||
|
}
|
||||||
|
return pc + 1;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
throw new InvalidOperationException($"Non-input opcode routed to input handler: {label}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1978,173 +1978,47 @@ public sealed partial class VirtualMachine
|
|||||||
case "draw-formatted-integer": // 0x205 (surface slot, x, y, value, field width, flags)
|
case "draw-formatted-integer": // 0x205 (surface slot, x, y, value, field width, flags)
|
||||||
return StepAdvText(label, ins, pc);
|
return StepAdvText(label, ins, pc);
|
||||||
case "wait-for-input":
|
case "wait-for-input":
|
||||||
RefreshAdvReadSkipState();
|
|
||||||
// Faithful headless: no player => halt here rather than plow past every prompt (see VmOptions).
|
|
||||||
if (_o.HaltAtWaitForInput) { HaltReason ??= "wait-for-input"; return HALT; }
|
|
||||||
// The native ADV chrome is a coroutine: after an earlier 0x93 cancellation its shared
|
|
||||||
// registration pass runs again before a stable message wait. Our blocking host models that
|
|
||||||
// scheduler boundary by re-arming the frame's retained definitions here.
|
|
||||||
bool wakeAtWait = false;
|
|
||||||
lock (_interactiveLock)
|
|
||||||
{
|
|
||||||
if (_cur.Hotspots.HasDefinitions && !_cur.Hotspots.Armed)
|
|
||||||
{
|
|
||||||
_interactiveFrame = _cur;
|
|
||||||
wakeAtWait = _cur.Hotspots.Arm(_pointerX, _pointerY);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (wakeAtWait) _host.WakeInputCallbackService();
|
|
||||||
_host.WaitForInput((int)Read(a[0]), ServiceHotspotCallback,
|
|
||||||
() => new AdvAutoWaitState(_autoMessageEnabled, _autoVoicePending,
|
|
||||||
_autoMessageTime0Ms, _autoMessageTime1Ms));
|
|
||||||
_sharedProfile.ReadText.QueueMessage(
|
|
||||||
_cur.Script.PackedId, CurrentReadMessageIndex(),
|
|
||||||
_cur.Script.ReadMessageOffsets.Count);
|
|
||||||
return pc + 1;
|
|
||||||
case "u0041BEB0":
|
case "u0041BEB0":
|
||||||
case "register-hotspot-callbacks": // 0x90: inclusive rect + enter/leave/activate local callbacks
|
case "register-hotspot-callbacks": // 0x90: inclusive rect + enter/leave/activate local callbacks
|
||||||
lock (_interactiveLock)
|
|
||||||
_cur.Hotspots.Register((int)Read(a[0]), (int)Read(a[1]), (int)Read(a[2]), (int)Read(a[3]),
|
|
||||||
(int)Read(a[4]), (int)Read(a[5]), (int)Read(a[6]));
|
|
||||||
return pc + 1;
|
|
||||||
case "u00415040":
|
case "u00415040":
|
||||||
case "cancel-hotspot-wait": // 0x93
|
case "cancel-hotspot-wait": // 0x93
|
||||||
lock (_interactiveLock)
|
|
||||||
{
|
|
||||||
_cur.Hotspots.Reset();
|
|
||||||
if (ReferenceEquals(_interactiveFrame, _cur)) _interactiveFrame = null;
|
|
||||||
}
|
|
||||||
return pc + 1;
|
|
||||||
case "u00415090":
|
case "u00415090":
|
||||||
case "arm-hotspot-wait": // 0x94
|
case "arm-hotspot-wait": // 0x94
|
||||||
{
|
|
||||||
bool wake;
|
|
||||||
lock (_interactiveLock)
|
|
||||||
{
|
|
||||||
_interactiveFrame = _cur;
|
|
||||||
wake = _cur.Hotspots.Arm(_pointerX, _pointerY);
|
|
||||||
}
|
|
||||||
if (wake) _host.WakeInputCallbackService();
|
|
||||||
return pc + 1;
|
|
||||||
}
|
|
||||||
case "u0041C150":
|
case "u0041C150":
|
||||||
case "bind-hotspot-key": // 0x97: bind a configured logical action to this record
|
case "bind-hotspot-key": // 0x97: bind a configured logical action to this record
|
||||||
lock (_interactiveLock)
|
|
||||||
_cur.Hotspots.BindKey((int)Read(a[0]), (int)Read(a[1]), (int)Read(a[2]),
|
|
||||||
(int)Read(a[3]), (int)Read(a[4]));
|
|
||||||
return pc + 1;
|
|
||||||
case "u0041B210":
|
case "u0041B210":
|
||||||
case "set-cursor-resource": // 0x86: raw indexed .CUR resource
|
case "set-cursor-resource": // 0x86: raw indexed .CUR resource
|
||||||
_host.SetCursorResource(Read(a[0])); return pc + 1;
|
|
||||||
case "u00414D10":
|
case "u00414D10":
|
||||||
case "clear-cursor-resource": // 0x87
|
case "clear-cursor-resource": // 0x87
|
||||||
_host.ClearCursorResource(); return pc + 1;
|
return StepInput(label, a, pc);
|
||||||
case "mouse_callback":
|
case "mouse_callback":
|
||||||
case "register-mouse-callback": // 0xcc (poll interval ms, local target dword offset)
|
case "register-mouse-callback": // 0xcc (poll interval ms, local target dword offset)
|
||||||
_cur.MouseCallbackIntervalMs = System.Math.Max(0, Read(a[0]));
|
|
||||||
_cur.MouseCallbackTarget = (int)Read(a[1]);
|
|
||||||
_cur.MouseCallbackNextAtMs = _host.InputClockMilliseconds + _cur.MouseCallbackIntervalMs;
|
|
||||||
lock (_interactiveLock) _rawInputFrame = _cur;
|
|
||||||
return pc + 1;
|
|
||||||
case "get-input-type":
|
case "get-input-type":
|
||||||
case "dispatch-mouse-callback": // 0xcd
|
case "dispatch-mouse-callback": // 0xcd
|
||||||
{
|
|
||||||
long now = _host.InputClockMilliseconds;
|
|
||||||
if (_cur.MouseCallbackTarget < 0 || now < _cur.MouseCallbackNextAtMs) return pc + 1;
|
|
||||||
_cur.MouseCallbackNextAtMs = now + _cur.MouseCallbackIntervalMs;
|
|
||||||
if (!_cur.Script.IndexByOffset.TryGetValue(_cur.MouseCallbackTarget, out int target))
|
|
||||||
return pc + 1;
|
|
||||||
_cur.CallStack.Add(pc + 1);
|
|
||||||
return target;
|
|
||||||
}
|
|
||||||
case "joy_callback":
|
case "joy_callback":
|
||||||
case "register-joy-callback": // 0xfb (input index, local target dword offset)
|
case "register-joy-callback": // 0xfb (input index, local target dword offset)
|
||||||
{
|
|
||||||
int index = (int)Read(a[0]);
|
|
||||||
if ((uint)index < 32) _cur.InputCallbackTargets[index] = (int)Read(a[1]);
|
|
||||||
return pc + 1;
|
|
||||||
}
|
|
||||||
case "u0041E360":
|
case "u0041E360":
|
||||||
case "set-input-action-count": // 0xfe: actions [0,count), no-input callback at count
|
case "set-input-action-count": // 0xfe: actions [0,count), no-input callback at count
|
||||||
{
|
|
||||||
int count = unchecked((int)Read(a[0]));
|
|
||||||
if (!InputBindings.SetActionCount(count))
|
|
||||||
{
|
|
||||||
HaltReason ??= $"input-action-count-out-of-range:{count}";
|
|
||||||
return HALT;
|
|
||||||
}
|
|
||||||
return pc + 1;
|
|
||||||
}
|
|
||||||
case "u00415A10":
|
case "u00415A10":
|
||||||
case "poll-joy-callback-input": // 0xff
|
case "poll-joy-callback-input": // 0xff
|
||||||
_cur.PendingInputCallbackMask = InputBindings.PollActionMask()
|
|
||||||
| Volatile.Read(ref _heldInputCallbackMask)
|
|
||||||
| Interlocked.Exchange(ref _queuedInputCallbackMask, 0);
|
|
||||||
_cur.InputCallbackScanIndex = 0;
|
|
||||||
return pc + 1;
|
|
||||||
case "u00415A60":
|
case "u00415A60":
|
||||||
case "dispatch-joy-callbacks": // 0x100
|
case "dispatch-joy-callbacks": // 0x100
|
||||||
{
|
return StepInput(label, a, pc);
|
||||||
int actionCount = InputBindings.ActionCount;
|
|
||||||
if (_cur.PendingInputCallbackMask == 0)
|
|
||||||
{
|
|
||||||
int idleTarget = _cur.InputCallbackTargets[actionCount];
|
|
||||||
if (idleTarget < 0 || !_cur.Script.IndexByOffset.TryGetValue(idleTarget, out int target))
|
|
||||||
return pc + 1;
|
|
||||||
_cur.CallStack.Add(pc + 1);
|
|
||||||
return target;
|
|
||||||
}
|
|
||||||
while (_cur.InputCallbackScanIndex < actionCount)
|
|
||||||
{
|
|
||||||
int index = _cur.InputCallbackScanIndex++;
|
|
||||||
if ((_cur.PendingInputCallbackMask & (1 << index)) == 0) continue;
|
|
||||||
int targetOffset = _cur.InputCallbackTargets[index];
|
|
||||||
if (targetOffset < 0 || !_cur.Script.IndexByOffset.TryGetValue(targetOffset, out int target))
|
|
||||||
continue;
|
|
||||||
// Resume on op 0x100 so another simultaneously active input can dispatch.
|
|
||||||
_cur.CallStack.Add(pc);
|
|
||||||
return target;
|
|
||||||
}
|
|
||||||
return pc + 1;
|
|
||||||
}
|
|
||||||
case "u0041E500":
|
case "u0041E500":
|
||||||
case "map-joystick-button": // 0x107: button slot N emits action N+4
|
case "map-joystick-button": // 0x107: button slot N emits action N+4
|
||||||
InputBindings.MapJoystickButton(unchecked((int)Read(a[0])), unchecked((int)Read(a[1])));
|
|
||||||
return pc + 1;
|
|
||||||
case "u00415E70":
|
case "u00415E70":
|
||||||
case "get-mouse-button-state": // 0x108
|
case "get-mouse-button-state": // 0x108
|
||||||
Write(a[0], Volatile.Read(ref _mouseButtonState)); return pc + 1;
|
|
||||||
case "u00415F10":
|
case "u00415F10":
|
||||||
case "consume-mouse-wheel-delta": // 0x10d
|
case "consume-mouse-wheel-delta": // 0x10d
|
||||||
Write(a[0], Interlocked.Exchange(ref _mouseWheelDelta, 0)); return pc + 1;
|
|
||||||
case "u00415EC0":
|
case "u00415EC0":
|
||||||
case "get-cursor-virtual": // 0x109
|
case "get-cursor-virtual": // 0x109
|
||||||
{
|
|
||||||
int x, y;
|
|
||||||
lock (_interactiveLock) { x = _pointerX; y = _pointerY; }
|
|
||||||
Write(a[0], x == int.MinValue ? 0 : x);
|
|
||||||
Write(a[1], y == int.MinValue ? 0 : y);
|
|
||||||
return pc + 1;
|
|
||||||
}
|
|
||||||
case "u0041E540":
|
case "u0041E540":
|
||||||
case "set-cursor-virtual": // 0x10a; retain the virtual position even without OS warping
|
case "set-cursor-virtual": // 0x10a; retain the virtual position even without OS warping
|
||||||
UpdatePointer((int)Read(a[0]), (int)Read(a[1])); return pc + 1;
|
|
||||||
case "u0041E5A0":
|
case "u0041E5A0":
|
||||||
case "map-mouse-button": // 0x10b: physical button -> slot, polled action is slot+4
|
case "map-mouse-button": // 0x10b: physical button -> slot, polled action is slot+4
|
||||||
InputBindings.MapMouseButton(unchecked((int)Read(a[0])), unchecked((int)Read(a[1])));
|
|
||||||
return pc + 1;
|
|
||||||
case "u0041E5E0":
|
case "u0041E5E0":
|
||||||
case "map-keyboard-scancode": // 0x10c: logical action <- DIK translated through native VK table
|
case "map-keyboard-scancode": // 0x10c: logical action <- DIK translated through native VK table
|
||||||
{
|
return StepInput(label, a, pc);
|
||||||
int action = unchecked((int)Read(a[0]));
|
|
||||||
int dik = unchecked((int)Read(a[1]));
|
|
||||||
if (!InputBindings.MapKeyboardScanCode(action, dik))
|
|
||||||
{
|
|
||||||
HaltReason ??= $"keyboard-action-out-of-range:{action}";
|
|
||||||
return HALT;
|
|
||||||
}
|
|
||||||
return pc + 1;
|
|
||||||
}
|
|
||||||
case "sleep": // 0xc8 (duration) — pause the host duration ms; headless hosts no-op (parity). Frame pacing.
|
case "sleep": // 0xc8 (duration) — pause the host duration ms; headless hosts no-op (parity). Frame pacing.
|
||||||
_host.Sleep(Read(a[0])); return pc + 1;
|
_host.Sleep(Read(a[0])); return pc + 1;
|
||||||
case "u00425960":
|
case "u00425960":
|
||||||
|
|||||||
Reference in New Issue
Block a user