Add hidden title debug mode and pace menu polls
This commit is contained in:
@@ -302,7 +302,10 @@ TITLE happens to contain a developer menu immediately after its `0x1`, including
|
|||||||
former unknown-op fallback did expose that menu when selecting TITLE's fifth action, providing a useful
|
former unknown-op fallback did expose that menu when selecting TITLE's fifth action, providing a useful
|
||||||
visual confirmation of the static mapping but not a legitimate retail route. Opcode `0x1` now propagates a
|
visual confirmation of the static mapping but not a legitimate retail route. Opcode `0x1` now propagates a
|
||||||
process-exit request through hotspot callbacks and nested script frames and ends the VM session without
|
process-exit request through hotspot callbacks and nested script frames and ends the VM session without
|
||||||
executing the following bytecode. End-to-end visual validation of the native
|
executing the following bytecode. An explicit Godot `--native-debug-menu` diagnostic can deliberately
|
||||||
|
restore the old fall-through for developer archaeology; it maps to `VmOptions.IgnoreExitRequests`, defaults
|
||||||
|
off, and is not part of the native compatibility path. Because it changes the opcode globally, it also
|
||||||
|
suppresses SYSTEM4's invalid-execution-mode exit site during that run. End-to-end visual validation of the native
|
||||||
`SYSTEM4 -> TITLE -> child -> 0x9 -> SYSTEM4 -> TITLE` history remains deferred until the frontend
|
`SYSTEM4 -> TITLE -> child -> 0x9 -> SYSTEM4 -> TITLE` history remains deferred until the frontend
|
||||||
exit/return-to-title boundary or a natural game-over/completion route exists.
|
exit/return-to-title boundary or a natural game-over/completion route exists.
|
||||||
|
|
||||||
@@ -778,6 +781,11 @@ native presentation trace proved that back-to-back retained mutations execute wi
|
|||||||
first published at the following `0x20c`/`0x21c`, sleep, or stable input boundary. An earlier claim that the
|
first published at the following `0x20c`/`0x21c`, sleep, or stable input boundary. An earlier claim that the
|
||||||
opening was generally sleep-paced was inherited without execution evidence and is superseded.
|
opening was generally sleep-paced was inherited without execution evidence and is superseded.
|
||||||
|
|
||||||
|
The native timer arm clamps every duration to at least one millisecond. This is semantically important for
|
||||||
|
menu scripts: ROOM's steady input loop uses `sleep 0` as a one-engine-tick yield. The Godot host formerly
|
||||||
|
allowed a zero deadline, turning that loop into a free-running burst that exhausted the VM step guard and
|
||||||
|
showed `-end-`; it now applies the native minimum after the debug speed multiplier.
|
||||||
|
|
||||||
**Related — `present-frame` (op `0x20c`):** dispatch `param_1[0x26e9f] = gfx_op_0x20c_present_frame` →
|
**Related — `present-frame` (op `0x20c`):** dispatch `param_1[0x26e9f] = gfx_op_0x20c_present_frame` →
|
||||||
`gfx_render_frame`@`0x4820b0` (buffer flip). The port treats `0x20c` as an explicit retained-state publication
|
`gfx_render_frame`@`0x4820b0` (buffer flip). The port treats `0x20c` as an explicit retained-state publication
|
||||||
boundary in the interactive host while keeping it `noop_headless=true`; the Kelebek label `u00416200` was
|
boundary in the interactive host while keeping it `noop_headless=true`; the Kelebek label `u00416200` was
|
||||||
|
|||||||
@@ -273,7 +273,7 @@ Implemented as a whole-stack root-reload boundary in the persistent VM. A reques
|
|||||||
- **grounding:** source=investigation, confidence=high
|
- **grounding:** source=investigation, confidence=high
|
||||||
- **evidence:** Ghidra: dispatch ctx[0x26c93+0xc8]=0x420ec0; sleep_op_0xc8 + sleep_timer_arm decoded/annotated 2026-07-08. docs/engine-re.md sleep section.
|
- **evidence:** Ghidra: dispatch ctx[0x26c93+0xc8]=0x420ec0; sleep_op_0xc8 + sleep_timer_arm decoded/annotated 2026-07-08. docs/engine-re.md sleep section.
|
||||||
|
|
||||||
Native handler sleep_op_0xc8 @0x420ec0 is NON-BLOCKING: it arms a timer (sleep_timer_arm @0x44cff0 at ctx+0x5f304 = active flag + start tick + duration) that the engine main loop polls, resuming the script when elapsed. Operand UNIT = MILLISECONDS (start = ms tick source DAT_0056f3d4, timeGetTime/GetTickCount class). duration<10 fast-paths via [0x56f0b8]; all real scene sleeps (100/750/1000) are >=10. The handler also records its generic 3-dword instruction length and runs anti-tamper checks, neither needed host-side. Port equivalent: the Godot host parks the VM thread for duration ms while the presentation compositor continues. Sleep is one proven presentation-capable service boundary; ordinary AE setup runs burst-fast to 0x21c and is not paced per opcode. Headless hosts no-op it (parity).
|
Native handler sleep_op_0xc8 @0x420ec0 is NON-BLOCKING: it arms a timer (sleep_timer_arm @0x44cff0 at ctx+0x5f304 = active flag + start tick + duration) that the engine main loop polls, resuming the script when elapsed. Operand UNIT = MILLISECONDS (start = ms tick source DAT_0056f3d4, timeGetTime/GetTickCount class), and the native arm helper clamps duration to a minimum of 1 ms. ROOM's input loop deliberately uses sleep(0), making it a one-engine-tick yield rather than a no-op. The handler also records its generic 3-dword instruction length and runs anti-tamper checks, neither needed host-side. Port equivalent: the Godot host parks the VM thread for max(1, scaled duration) ms while the presentation compositor continues. Sleep is one proven presentation-capable service boundary; ordinary AE setup runs burst-fast to 0x21c and is not paced per opcode. Headless hosts no-op it (parity).
|
||||||
|
|
||||||
### 0xd3 `begin-timed-callback-sequence` (u00425960, argc 0)
|
### 0xd3 `begin-timed-callback-sequence` (u00425960, argc 0)
|
||||||
- **summary:** Clear and initialize the current script frame's timed local-callback sequence.
|
- **summary:** Clear and initialize the current script frame's timed local-callback sequence.
|
||||||
|
|||||||
@@ -150,7 +150,8 @@ route through the first `0x9` remains deferred: Himegari's readily accessible re
|
|||||||
to the still-unimplemented frontend exit-request policy, while the other known natural paths require later
|
to the still-unimplemented frontend exit-request policy, while the other known natural paths require later
|
||||||
gameplay, game over, or completion. Do not use TITLE's post-`0x1` developer menu as evidence; native `0x1`
|
gameplay, game over, or completion. Do not use TITLE's post-`0x1` developer menu as evidence; native `0x1`
|
||||||
is non-returning, and the port now propagates that exit request instead of falling through into the hidden
|
is non-returning, and the port now propagates that exit request instead of falling through into the hidden
|
||||||
bytecode. See `docs/engine-re.md`.
|
bytecode. The opt-in `--native-debug-menu` diagnostic deliberately restores fall-through for exploring that
|
||||||
|
retained developer UI, but does not qualify as native lifecycle validation. See `docs/engine-re.md`.
|
||||||
|
|
||||||
**Godot debug scene launcher (2026-07-20; implemented and manually validated).** The first version
|
**Godot debug scene launcher (2026-07-20; implemented and manually validated).** The first version
|
||||||
is deliberately narrower than arbitrary hot swapping:
|
is deliberately narrower than arbitrary hot swapping:
|
||||||
@@ -236,6 +237,13 @@ were presented, its terminal op `0x9` at `0x1fb` ran, and SYSTEM4 reconstructed
|
|||||||
launcher/session-lifecycle discrepancy was observed. DEBUG-specific content oddities are not acceptance
|
launcher/session-lifecycle discrepancy was observed. DEBUG-specific content oddities are not acceptance
|
||||||
failures for this developer route and remain out of scope unless they reproduce in a normal game script.
|
failures for this developer route and remain out of scope unless they reproduce in a normal game script.
|
||||||
|
|
||||||
|
**TITLE Extra Room entry (`ROOM.BIN`, 2026-07-20).** The title dispatch itself is correct. The first manual
|
||||||
|
entry exposed a scheduler discrepancy: ROOM's stable input poll uses `sleep 0`, which native `0xc8` clamps
|
||||||
|
to a one-millisecond timer. Godot previously treated it as a zero-duration no-op, letting the script consume
|
||||||
|
the 20-million-step guard and display `-end-`. The interactive host now preserves the native one-tick yield;
|
||||||
|
Godot also prints the VM halt reason and step count before its generic end marker so future terminations are
|
||||||
|
distinguishable. Manual ROOM presentation and its genuine op-`0x9` return to TITLE remain to be rechecked.
|
||||||
|
|
||||||
## Stage B2 — Faithful full boot
|
## Stage B2 — Faithful full boot
|
||||||
|
|
||||||
Replace `--boot`'s diagnostic seeding and separately injected inherited surfaces with normal boot execution.
|
Replace `--boot`'s diagnostic seeding and separately injected inherited surfaces with normal boot execution.
|
||||||
|
|||||||
@@ -139,13 +139,19 @@ at 2.5M lines). All observe-only → parity preserved; all on `run`/`play`/`swee
|
|||||||
Plays the real bytecode with call-script execution on (subroutines run live). A no-argument launch starts
|
Plays the real bytecode with call-script execution on (subroutines run live). A no-argument launch starts
|
||||||
the persistent `SYSTEM4.BIN` root and reaches TITLE naturally. The local `run-godot.ps1`/`.cmd` launchers
|
the persistent `SYSTEM4.BIN` root and reaches TITLE naturally. The local `run-godot.ps1`/`.cmd` launchers
|
||||||
make that route explicit with `--scene SYSTEM4` and pass neither `--boot` nor SC0000 seeds. `--headless` can't
|
make that route explicit with `--scene SYSTEM4` and pass neither `--boot` nor SC0000 seeds. `--headless` can't
|
||||||
render texture ops (no GPU context) — run windowed for real scenes. User args (after `--`):
|
render texture ops (no GPU context) — run windowed for real scenes. On VM termination the console prints
|
||||||
|
the exact halt reason and step count before the generic on-screen `-end-` marker. User args (after `--`):
|
||||||
- `--scene <NAME>` — override the default `SYSTEM4` root with a direct diagnostic scene, e.g. `--scene SC0240` (executes 29 nested subroutines).
|
- `--scene <NAME>` — override the default `SYSTEM4` root with a direct diagnostic scene, e.g. `--scene SC0240` (executes 29 nested subroutines).
|
||||||
- `--selftest` — headless; runs a **synthesized** scene through the thread/suspend/`CallDeferred` plumbing and asserts it matches a live headless run (full handling; no vm0/frozen golden). Exits.
|
- `--selftest` — headless; runs a **synthesized** scene through the thread/suspend/`CallDeferred` plumbing and asserts it matches a live headless run (full handling; no vm0/frozen golden). Exits.
|
||||||
- `--seed 0xADDR=VAL` (repeatable) — seed initial global state, e.g. `--seed 0xa57=1` unlocks Lily's form-A voiced dialogue.
|
- `--seed 0xADDR=VAL` (repeatable) — seed initial global state, e.g. `--seed 0xa57=1` unlocks Lily's form-A voiced dialogue.
|
||||||
- `--boot` — direct-scene diagnostic only: with `--scene <non-SYSTEM4>`, run the old
|
- `--boot` — direct-scene diagnostic only: with `--scene <non-SYSTEM4>`, run the old
|
||||||
`INITCONFIG/INIT2/INIT` state prefix before that isolated scene. The normal SYSTEM4-rooted launch neither
|
`INITCONFIG/INIT2/INIT` state prefix before that isolated scene. The normal SYSTEM4-rooted launch neither
|
||||||
needs nor applies it. e.g. `godot --path godot -- --scene SC0000 --boot`.
|
needs nor applies it. e.g. `godot --path godot -- --scene SC0000 --boot`.
|
||||||
|
- `--native-debug-menu` — intentionally treat exit-request opcode `0x1` as a no-op, exposing TITLE's
|
||||||
|
otherwise unreachable shipped developer menu. This is a non-native debug divergence and also suppresses
|
||||||
|
SYSTEM4's invalid-execution-mode exit request for that run. Direct Godot and PowerShell launches leave it
|
||||||
|
disabled unless explicitly requested. The local `run-godot.cmd` convenience launcher enables it by
|
||||||
|
default; use `run-godot.ps1` without `-NativeDebugMenu` for a native-faithful launch.
|
||||||
- `--shot <png> [--shot-page N]` — capture page N to a PNG then quit (dev screenshot). At scene end it also prints the call-scripts executed as nested frames.
|
- `--shot <png> [--shot-page N]` — capture page N to a PNG then quit (dev screenshot). At scene end it also prints the call-scripts executed as nested frames.
|
||||||
- `--shot-sequence <dir> [--frames N]` — dump one PNG per rendered frame (`frame_0000.png…`, default N=180 ≈ 3s @60fps) then quit, auto-advancing past input waits. Verifies time-based retained effects and publication boundaries as distinct frames, which a single `--shot` cannot. CPU/IO-heavy by design (a PNG every frame); a dev diagnostic, not a normal run. e.g. `godot --path godot -- --scene SC0000 --boot --shot-sequence out/seq --frames 300`.
|
- `--shot-sequence <dir> [--frames N]` — dump one PNG per rendered frame (`frame_0000.png…`, default N=180 ≈ 3s @60fps) then quit, auto-advancing past input waits. Verifies time-based retained effects and publication boundaries as distinct frames, which a single `--shot` cannot. CPU/IO-heavy by design (a PNG every frame); a dev diagnostic, not a normal run. e.g. `godot --path godot -- --scene SC0000 --boot --shot-sequence out/seq --frames 300`.
|
||||||
- `--sleep-scale <f>` — multiply every explicit `sleep` (op 0xc8) duration by `f` (default 1.0). This stretches only script-authored sleep holds; it does not slow ordinary opcode bursts or replace `0x20c`/`0x21c` presentation pacing. Debug-only; leave at 1.0 for real playback.
|
- `--sleep-scale <f>` — multiply every explicit `sleep` (op 0xc8) duration by `f` (default 1.0). This stretches only script-authored sleep holds; it does not slow ordinary opcode bursts or replace `0x20c`/`0x21c` presentation pacing. Debug-only; leave at 1.0 for real playback.
|
||||||
|
|||||||
@@ -50,4 +50,23 @@ public class ExitRequestTests
|
|||||||
Assert.False(vm.Globals.ContainsKey(0x7001));
|
Assert.False(vm.Globals.ContainsKey(0x7001));
|
||||||
Assert.False(vm.Globals.ContainsKey(0x7002));
|
Assert.False(vm.Globals.ContainsKey(0x7002));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void DebugIgnoreExitRequestFallsThroughToFollowingBytecode()
|
||||||
|
{
|
||||||
|
var table = OpcodeTableJson.Load(Paths.OpcodesJson);
|
||||||
|
var script = ScriptAssembler.Assemble(table, "TITLE.BIN", new List<(int, Operand[])>
|
||||||
|
{
|
||||||
|
(0x1, Array.Empty<Operand>()),
|
||||||
|
(0x55, new[] { G(0x7000), I(1) }),
|
||||||
|
(0x2, Array.Empty<Operand>()),
|
||||||
|
}, Array.Empty<string>());
|
||||||
|
var vm = new VirtualMachine(script, table, new RecordingHost(),
|
||||||
|
new VmOptions(IgnoreExitRequests: true));
|
||||||
|
|
||||||
|
vm.Run();
|
||||||
|
|
||||||
|
Assert.Equal("exit", vm.HaltReason);
|
||||||
|
Assert.Equal(1, vm.Globals[0x7000]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -724,6 +724,7 @@ public sealed class VirtualMachine
|
|||||||
return pc + 1;
|
return pc + 1;
|
||||||
}
|
}
|
||||||
case "throw-exit-request":
|
case "throw-exit-request":
|
||||||
|
if (_o.IgnoreExitRequests) return pc + 1;
|
||||||
// Native op 0x1 throws Command_Exit_Exception through callbacks and nested script
|
// Native op 0x1 throws Command_Exit_Exception through callbacks and nested script
|
||||||
// frames. The outer engine loop catches it and exits without advancing frame_pc.
|
// frames. The outer engine loop catches it and exits without advancing frame_pc.
|
||||||
throw new ProcessExitRequestedException();
|
throw new ProcessExitRequestedException();
|
||||||
|
|||||||
@@ -6,5 +6,7 @@ namespace Age.Engine.Vm;
|
|||||||
/// through every prompt into code no real playthrough reaches (which is what made a headless SC0000 spin
|
/// through every prompt into code no real playthrough reaches (which is what made a headless SC0000 spin
|
||||||
/// 493k× in the name-entry poll loop). Leave false for interactive frontends that really block on input
|
/// 493k× in the name-entry poll loop). Leave false for interactive frontends that really block on input
|
||||||
/// (Godot) and for the dialogue-coverage sweep that deliberately walks every page.</param>
|
/// (Godot) and for the dialogue-coverage sweep that deliberately walks every page.</param>
|
||||||
|
/// <param name="IgnoreExitRequests">Debug-only divergence: treat op 0x1 as a no-op so unreachable
|
||||||
|
/// post-exit bytecode can be explored. Leave false for native-faithful execution.</param>
|
||||||
public sealed record VmOptions(int EmitCap = 2, long MaxSteps = 2_000_000, int CallDepthCap = 64,
|
public sealed record VmOptions(int EmitCap = 2, long MaxSteps = 2_000_000, int CallDepthCap = 64,
|
||||||
bool HaltAtWaitForInput = false);
|
bool HaltAtWaitForInput = false, bool IgnoreExitRequests = false);
|
||||||
|
|||||||
@@ -547,9 +547,14 @@ public sealed class GodotAdvHost : IHost
|
|||||||
public double SleepScale = 1.0; // --sleep-scale <f>: debug multiplier for explicit op-0xc8 holds only
|
public double SleepScale = 1.0; // --sleep-scale <f>: debug multiplier for explicit op-0xc8 holds only
|
||||||
// Wait on the unified FrameClock timebase (not Thread.Sleep) so the Speed multiplier scales
|
// Wait on the unified FrameClock timebase (not Thread.Sleep) so the Speed multiplier scales
|
||||||
// sleeps together with retained presentation clocks. Main._Process advances the clock + pulses each frame.
|
// sleeps together with retained presentation clocks. Main._Process advances the clock + pulses each frame.
|
||||||
|
internal static long NormalizeSleepMilliseconds(long duration, double scale)
|
||||||
|
=> (long)System.Math.Clamp(duration * scale, 1, 60_000);
|
||||||
|
|
||||||
public void Sleep(long duration)
|
public void Sleep(long duration)
|
||||||
{
|
{
|
||||||
long ms = (long)System.Math.Clamp(duration * SleepScale, 0, 60_000); // cap so a pathological script can't hang the window
|
// Native sleep_timer_arm clamps the duration to at least 1 ms. In menu poll loops, sleep(0)
|
||||||
|
// therefore yields to the next engine tick instead of becoming a free-running no-op.
|
||||||
|
long ms = NormalizeSleepMilliseconds(duration, SleepScale);
|
||||||
long deadline = _clock.NowMs + ms;
|
long deadline = _clock.NowMs + ms;
|
||||||
_timeline?.State("sleep", new() { ["duration_ms"] = ms, ["deadline_ms"] = deadline });
|
_timeline?.State("sleep", new() { ["duration_ms"] = ms, ["deadline_ms"] = deadline });
|
||||||
// A sleep is a service boundary: make preceding retained writes visible once even when no animation
|
// A sleep is a service boundary: make preceding retained writes visible once even when no animation
|
||||||
|
|||||||
@@ -159,6 +159,9 @@ public partial class Main : Godot.Control
|
|||||||
var userArgs = OS.GetCmdlineUserArgs();
|
var userArgs = OS.GetCmdlineUserArgs();
|
||||||
_selftest = System.Array.IndexOf(userArgs, "--selftest") >= 0;
|
_selftest = System.Array.IndexOf(userArgs, "--selftest") >= 0;
|
||||||
bool boot = System.Array.IndexOf(userArgs, "--boot") >= 0; // diagnostic prefix for direct-scene runs
|
bool boot = System.Array.IndexOf(userArgs, "--boot") >= 0; // diagnostic prefix for direct-scene runs
|
||||||
|
bool nativeDebugMenu = System.Array.IndexOf(userArgs, "--native-debug-menu") >= 0;
|
||||||
|
if (nativeDebugMenu)
|
||||||
|
GD.Print("[debug] native exit requests disabled; post-0x1 bytecode may execute");
|
||||||
string scene = "SYSTEM4"; // natural persistent root; --scene keeps direct diagnostics
|
string scene = "SYSTEM4"; // natural persistent root; --scene keeps direct diagnostics
|
||||||
var seeds = new List<(int Addr, long Val)>(); // --seed 0xADDR=VAL (repeatable) — initial global state
|
var seeds = new List<(int Addr, long Val)>(); // --seed 0xADDR=VAL (repeatable) — initial global state
|
||||||
double sleepScale = 1.0; // --sleep-scale <f>: scale explicit op-0xc8 holds
|
double sleepScale = 1.0; // --sleep-scale <f>: scale explicit op-0xc8 holds
|
||||||
@@ -223,7 +226,8 @@ public partial class Main : Godot.Control
|
|||||||
Age.Engine.Diagnostics.ITraceSink sink = _trace;
|
Age.Engine.Diagnostics.ITraceSink sink = _trace;
|
||||||
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, IgnoreExitRequests: nativeDebugMenu), provider, sink);
|
||||||
if (scripts != null)
|
if (scripts != null)
|
||||||
{
|
{
|
||||||
_debugSceneEntries = DebugSceneCatalog.Build(scripts.Catalog);
|
_debugSceneEntries = DebugSceneCatalog.Build(scripts.Catalog);
|
||||||
@@ -348,6 +352,7 @@ public partial class Main : Godot.Control
|
|||||||
{
|
{
|
||||||
_ended = true;
|
_ended = true;
|
||||||
DumpHistogram();
|
DumpHistogram();
|
||||||
|
GD.Print($"[vm] ended: {_vm!.HaltReason ?? "unknown"} after {_vm.Steps} steps");
|
||||||
ReportSubroutines();
|
ReportSubroutines();
|
||||||
ShowEnd();
|
ShowEnd();
|
||||||
if (_selftest) RunSelfTest();
|
if (_selftest) RunSelfTest();
|
||||||
@@ -1188,11 +1193,13 @@ public partial class Main : Godot.Control
|
|||||||
launcherSmoke.Open(debugEntries, "SYSTEM4.BIN > TITLE.BIN");
|
launcherSmoke.Open(debugEntries, "SYSTEM4.BIN > TITLE.BIN");
|
||||||
launcherSmoke.Hide();
|
launcherSmoke.Hide();
|
||||||
launcherSmoke.QueueFree();
|
launcherSmoke.QueueFree();
|
||||||
ok &= launcherOk;
|
bool sleepMinimumOk = GodotAdvHost.NormalizeSleepMilliseconds(0, 1.0) == 1
|
||||||
|
&& GodotAdvHost.NormalizeSleepMilliseconds(100, 1.0) == 100;
|
||||||
|
ok &= launcherOk && sleepMinimumOk;
|
||||||
if (ok) GD.Print($"SELFTEST OK: threaded host matches headless ({actual.Count} lines, full handling); " +
|
if (ok) GD.Print($"SELFTEST OK: threaded host matches headless ({actual.Count} lines, full handling); " +
|
||||||
$"debug launcher catalog/UI smoke ({debugEntries.Count} packed scripts)");
|
$"debug launcher catalog/UI smoke ({debugEntries.Count} packed scripts); sleep-min=1ms");
|
||||||
else GD.Print($"SELFTEST FAIL: threaded={actual.Count} vs headless={expected.Count}; " +
|
else GD.Print($"SELFTEST FAIL: threaded={actual.Count} vs headless={expected.Count}; " +
|
||||||
$"debug-launcher={launcherOk}");
|
$"debug-launcher={launcherOk}; sleep-min={sleepMinimumOk}");
|
||||||
GetTree().Quit(ok ? 0 : 1);
|
GetTree().Quit(ok ? 0 : 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2047,7 +2047,7 @@ abi_source = "kelebek+decode-validated"
|
|||||||
name = "sleep"
|
name = "sleep"
|
||||||
category = "control"
|
category = "control"
|
||||||
summary = "Pause the current script for <duration> milliseconds while retained presentation continues."
|
summary = "Pause the current script for <duration> milliseconds while retained presentation continues."
|
||||||
details = "Native handler sleep_op_0xc8 @0x420ec0 is NON-BLOCKING: it arms a timer (sleep_timer_arm @0x44cff0 at ctx+0x5f304 = active flag + start tick + duration) that the engine main loop polls, resuming the script when elapsed. Operand UNIT = MILLISECONDS (start = ms tick source DAT_0056f3d4, timeGetTime/GetTickCount class). duration<10 fast-paths via [0x56f0b8]; all real scene sleeps (100/750/1000) are >=10. The handler also records its generic 3-dword instruction length and runs anti-tamper checks, neither needed host-side. Port equivalent: the Godot host parks the VM thread for duration ms while the presentation compositor continues. Sleep is one proven presentation-capable service boundary; ordinary AE setup runs burst-fast to 0x21c and is not paced per opcode. Headless hosts no-op it (parity)."
|
details = "Native handler sleep_op_0xc8 @0x420ec0 is NON-BLOCKING: it arms a timer (sleep_timer_arm @0x44cff0 at ctx+0x5f304 = active flag + start tick + duration) that the engine main loop polls, resuming the script when elapsed. Operand UNIT = MILLISECONDS (start = ms tick source DAT_0056f3d4, timeGetTime/GetTickCount class), and the native arm helper clamps duration to a minimum of 1 ms. ROOM's input loop deliberately uses sleep(0), making it a one-engine-tick yield rather than a no-op. The handler also records its generic 3-dword instruction length and runs anti-tamper checks, neither needed host-side. Port equivalent: the Godot host parks the VM thread for max(1, scaled duration) ms while the presentation compositor continues. Sleep is one proven presentation-capable service boundary; ordinary AE setup runs burst-fast to 0x21c and is not paced per opcode. Headless hosts no-op it (parity)."
|
||||||
noop_headless = false
|
noop_headless = false
|
||||||
source = "investigation"
|
source = "investigation"
|
||||||
confidence = "high"
|
confidence = "high"
|
||||||
|
|||||||
Reference in New Issue
Block a user