fix(diagnostics): keep UI scripts on game-loop time
Preserve sub-millisecond frame deltas with a command-local compensated clock so uncapped diagnostic routes do not outrun materialization and settling. Keep sleep boundaries inclusive, wait timeouts strict, and handle long-duration and invalid-delta cases deterministically. Record the aligned seven-destination connected route with a full turn at every stop. Release build succeeds and all 6,308 tests pass with five intentional skips. Co-authored-by: OpenAI Codex <codex@openai.com>
This commit is contained in:
parent
3718e341be
commit
b95bb3e21c
4 changed files with 336 additions and 15 deletions
|
|
@ -22,8 +22,8 @@ public sealed class RetailUiAutomationScriptRunner
|
|||
private readonly string? _loadError;
|
||||
private int _index;
|
||||
private int _activeIndex = -1;
|
||||
private long _elapsedMs;
|
||||
private long _commandStartMs;
|
||||
private double _commandElapsedMs;
|
||||
private double _commandElapsedCompensationMs;
|
||||
private bool _started;
|
||||
private bool _completed;
|
||||
|
||||
|
|
@ -65,8 +65,27 @@ public sealed class RetailUiAutomationScriptRunner
|
|||
{
|
||||
if (_completed) return;
|
||||
|
||||
long addMs = Math.Max(1, (long)Math.Round(deltaSeconds * 1000.0));
|
||||
_elapsedMs += addMs;
|
||||
// Preserve sub-millisecond frame deltas. Rounding every uncapped frame
|
||||
// up to one millisecond makes script time run several times faster than
|
||||
// wall/game-loop time in light scenes and portal space.
|
||||
if (double.IsFinite(deltaSeconds) && deltaSeconds > 0d)
|
||||
{
|
||||
double addMs = deltaSeconds * 1000d;
|
||||
double adjustedMs = addMs - _commandElapsedCompensationMs;
|
||||
double nextElapsedMs = _commandElapsedMs + adjustedMs;
|
||||
double nextCompensationMs = (nextElapsedMs - _commandElapsedMs) - adjustedMs;
|
||||
if (_activeIndex == _index &&
|
||||
double.IsFinite(addMs) &&
|
||||
double.IsFinite(nextElapsedMs) &&
|
||||
double.IsFinite(nextCompensationMs))
|
||||
{
|
||||
// Kahan summation keeps long fractional-frame waits on their
|
||||
// exact boundary instead of accumulating cadence-dependent
|
||||
// drift over tens of minutes.
|
||||
_commandElapsedMs = nextElapsedMs;
|
||||
_commandElapsedCompensationMs = nextCompensationMs;
|
||||
}
|
||||
}
|
||||
|
||||
if (!_started)
|
||||
{
|
||||
|
|
@ -96,7 +115,10 @@ public sealed class RetailUiAutomationScriptRunner
|
|||
if (_activeIndex != _index)
|
||||
{
|
||||
_activeIndex = _index;
|
||||
_commandStartMs = _elapsedMs;
|
||||
// Command-local time avoids losing precision when a diagnostic
|
||||
// script has already run for minutes or hours.
|
||||
_commandElapsedMs = 0d;
|
||||
_commandElapsedCompensationMs = 0d;
|
||||
}
|
||||
|
||||
var command = _commands[_index];
|
||||
|
|
@ -233,7 +255,7 @@ public sealed class RetailUiAutomationScriptRunner
|
|||
: p.Length >= 2 ? p[1] : "";
|
||||
if (!TryParseInt(value, out int ms) || ms < 0)
|
||||
return Stop(command, "usage: sleep <milliseconds> | wait ms <milliseconds>");
|
||||
return _elapsedMs - _commandStartMs >= ms;
|
||||
return HasElapsed(ms);
|
||||
}
|
||||
|
||||
private bool DoAssert(ScriptCommand command)
|
||||
|
|
@ -282,10 +304,33 @@ public sealed class RetailUiAutomationScriptRunner
|
|||
|
||||
private bool WaitOrTimeout(ScriptCommand command, int timeoutMs, string label)
|
||||
{
|
||||
if (_elapsedMs - _commandStartMs <= timeoutMs) return false;
|
||||
if (!HasExceeded(timeoutMs)) return false;
|
||||
return Stop(command, $"timed out waiting for {label}");
|
||||
}
|
||||
|
||||
private bool HasElapsed(int durationMs)
|
||||
{
|
||||
double elapsedMs = _commandElapsedMs;
|
||||
if (!double.IsFinite(elapsedMs)) return false;
|
||||
if (elapsedMs >= durationMs) return true;
|
||||
|
||||
// Repeated fractional frame deltas can land a few representable
|
||||
// doubles below an exact integer-millisecond deadline (for example,
|
||||
// sixty 1/60-second frames). Treat only machine-scale drift as equal;
|
||||
// this tolerance is far below any observable script or frame interval.
|
||||
return durationMs - elapsedMs <= TimingToleranceMs(durationMs);
|
||||
}
|
||||
|
||||
private bool HasExceeded(int durationMs)
|
||||
{
|
||||
double elapsedMs = _commandElapsedMs;
|
||||
if (!double.IsFinite(elapsedMs) || elapsedMs <= durationMs) return false;
|
||||
return elapsedMs - durationMs > TimingToleranceMs(durationMs);
|
||||
}
|
||||
|
||||
private static double TimingToleranceMs(int durationMs)
|
||||
=> Math.Max(1e-9d, Math.Abs(durationMs) * 1e-12d);
|
||||
|
||||
private bool Stop(ScriptCommand command, string message)
|
||||
{
|
||||
_log($"line {command.LineNumber}: {message}; command: {command.Text}");
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue