Fix ADV transition and nested input lifecycles

This commit is contained in:
gamer147
2026-07-29 18:16:09 -04:00
parent 09d765d89d
commit da7e0e25ce
13 changed files with 251 additions and 78 deletions

View File

@@ -94,6 +94,7 @@ public sealed class GodotAdvHost : IHost
private volatile bool _modalMovieWaiting;
private volatile bool _modalMovieCancelled;
private GfxState? _foregroundGfx;
private volatile bool _foregroundTransitionWaitBypassed;
private readonly object _screenTransitionLock = new();
private readonly Dictionary<int, IReadOnlyList<RenderObject>> _renderTargetSnapshots = new();
private readonly object _backbufferRangeLock = new();
@@ -662,13 +663,16 @@ public sealed class GodotAdvHost : IHost
{
if (IsTransitionWaiting && _foregroundGfx != null)
{
int completed = _foregroundGfx.CompleteClickSkippableTimedPresentation(_clock.NowMs);
if (completed > 0)
bool accepted = _foregroundGfx.TryCompleteClickSkippableTimedPresentation(
_clock.NowMs, out int completed);
if (accepted)
{
_timeline?.State("transition-forced-complete", new()
_foregroundTransitionWaitBypassed = true;
_timeline?.State("transition-skip-accepted", new()
{
["count"] = completed,
["endpoints_completed"] = completed,
["source"] = source,
["wait_bypassed"] = true,
});
_frameSignal.Set();
return true;
@@ -781,6 +785,7 @@ public sealed class GodotAdvHost : IHost
public void WaitForForegroundTransition(GfxState gfx)
{
_foregroundTransitionWaitBypassed = false;
int started = gfx.StartForegroundTransitions(_clock.NowMs);
bool hasActivePresentation =
gfx.HasActiveTimedPresentation(_clock.NowMs) || HasActiveMoviePresentation();
@@ -790,6 +795,10 @@ public sealed class GodotAdvHost : IHost
System.Threading.Interlocked.Exchange(ref _transitionStartedAtMs, _clock.NowMs);
IsTransitionWaiting = true;
_timeline?.State("transition-start", new() { ["count"] = started });
// Skip may already have been active before this service boundary. Native ADV script usually
// selects 0x243+0x20c in that case, but applying it here also covers transition helpers that
// enter run-state 0x400 without repeating the script-side query.
if (_messageSkipActive) TryForceActiveTransition("message-skip");
}
bool scriptSuspended = SuspendScriptForPresentation();
try
@@ -797,7 +806,9 @@ public sealed class GodotAdvHost : IHost
RequestSynchronizedPresentation();
if (!hasActivePresentation) return;
int lastBucket = -1;
while ((gfx.HasActiveTimedPresentation(_clock.NowMs) || HasActiveMoviePresentation()) && !_stopping)
while (!_foregroundTransitionWaitBypassed
&& (gfx.HasActiveTimedPresentation(_clock.NowMs) || HasActiveMoviePresentation())
&& !_stopping)
{
var active = gfx.SnapshotForegroundTransitions(_clock.NowMs);
int bucket = active.Count == 0 ? 100 : (int)System.Math.Floor(active[0].Progress * 10);
@@ -812,8 +823,8 @@ public sealed class GodotAdvHost : IHost
}
_frameSignal.WaitOne(50);
}
// The active query becomes false at the exact transition/movie endpoint. Publish that terminal sample
// once so the last visible frame cannot remain fractionally incomplete.
// Publish the natural terminal sample or the forced service-resume sample once before the
// following script burst mutates/releases its retained inputs.
RequestSynchronizedPresentation();
}
finally
@@ -821,6 +832,7 @@ public sealed class GodotAdvHost : IHost
ResumeScriptAfterPresentation(scriptSuspended);
}
IsTransitionWaiting = false;
_foregroundTransitionWaitBypassed = false;
System.Threading.Interlocked.Exchange(ref _transitionStartedAtMs, -1);
_foregroundGfx = null;
_timeline?.State("running", new() { ["transition_complete"] = true });
@@ -1102,13 +1114,10 @@ public sealed class GodotAdvHost : IHost
_frameSignal.Set();
}
// Ordinary opcode bursts run to the next service boundary without frame pacing. Persistent message
// Skip removes most of those boundaries, but native adv_interpreter_tick still executes one opcode per
// engine tick; retain that cadence here so Skip advances quickly instead of free-running whole scenes.
public void FrameYield()
{
if (_messageSkipActive && !_stopping) _frameSignal.WaitOne(50);
}
// Native dispatch remains burst-fast between explicit presentation/sleep/input services even when
// message Skip removes those services. Per-op frame pacing turns ordinary skipped setup/cleanup bursts
// into multi-second invisible stalls.
public void FrameYield() { }
// op 0xc8: block the VM background thread while the main-thread compositor keeps presenting retained state.
// Time-based sibling of WaitForInput's suspend. The native op arms a non-blocking main-loop-polled timer;

View File

@@ -265,6 +265,7 @@ public partial class Main : Godot.Control
double sleepScale = 1.0; // --sleep-scale <f>: scale explicit op-0xc8 holds
double speed = 1.0; // --speed <f>: sleeps + retained presentation clocks
long transitionClickMs = -1; // --transition-click-ms <n>: force active transitions after n virtual ms
bool holdMessageSkip = false; // --hold-message-skip: hold native logical action 6 for diagnostics
string? histFile = null; // --trace-histogram <file>: op/call-site execution counts of the REAL run
string? pageMapPath = null; // --page-map <jsonl>: override default build/page-map-SCxxxx.jsonl
for (int i = 0; i < userArgs.Length; i++)
@@ -289,6 +290,7 @@ public partial class Main : Godot.Control
if (userArgs[i] == "--sleep-scale" && i + 1 < userArgs.Length) double.TryParse(userArgs[i + 1], out sleepScale);
if (userArgs[i] == "--speed" && i + 1 < userArgs.Length) double.TryParse(userArgs[i + 1], out speed);
if (userArgs[i] == "--transition-click-ms" && i + 1 < userArgs.Length) long.TryParse(userArgs[i + 1], out transitionClickMs);
if (userArgs[i] == "--hold-message-skip") holdMessageSkip = true;
if (userArgs[i] == "--trace-histogram" && i + 1 < userArgs.Length) histFile = userArgs[i + 1];
if (userArgs[i] == "--page-map" && i + 1 < userArgs.Length) pageMapPath = userArgs[i + 1];
if (userArgs[i] == "--locator-hud") _locatorHudVisible = true;
@@ -457,6 +459,8 @@ public partial class Main : Godot.Control
if (!_selftest && scene.Equals("SC0000", System.StringComparison.OrdinalIgnoreCase))
_vm.ExternalGlobals[0x6242d] = 4;
foreach (var (addr, val) in seeds) _vm.Globals[addr] = val; // --seed overrides boot state
if (holdMessageSkip)
_vm.UpdateKeyboardVirtualKeyState(0x11, true); // Ctrl; bindings resolve it to logical action 6.
_vmTask = Task.Run(() =>
{
try { _vm.Run(); }