From 053dae3f74d40f95341755585534c59cc8fd899e Mon Sep 17 00:00:00 2001 From: Nayiem Willems Date: Thu, 9 Jul 2026 13:26:44 -0700 Subject: [PATCH] Fix the connected tile drawer's timeout unit mismatch The pathfinding stall watchdog added TimeSpan ticks, which are fixed 100-nanosecond units, to Stopwatch timestamps, which use platform-specific Stopwatch.Frequency units. The two only coincide on Windows, where the query performance counter runs at 10 MHz. In the browser the runtime reports timestamps in nanoseconds, shrinking the intended 10 millisecond budget to 0.1 milliseconds, so the search aborted early and placed partial connected tile paths. Convert the budget through Stopwatch.Frequency, and give the browser a larger budget to compensate for the interpreted runtime's higher per-node cost and coarser timer granularity. --- .../Mutations/Classes/DrawConnectedTilesMutation.cs | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/TSMapEditor/Mutations/Classes/DrawConnectedTilesMutation.cs b/src/TSMapEditor/Mutations/Classes/DrawConnectedTilesMutation.cs index 610697d92..574c6a1ef 100644 --- a/src/TSMapEditor/Mutations/Classes/DrawConnectedTilesMutation.cs +++ b/src/TSMapEditor/Mutations/Classes/DrawConnectedTilesMutation.cs @@ -39,7 +39,14 @@ private struct ConnectedTileUndoData public byte Level; } - private const int MaxTimeInMilliseconds = 10; + // The stall watchdog resets whenever the search progresses, so a generous budget only + // extends genuinely stuck searches. The browser gets more time to compensate for the + // interpreted runtime's higher per-node cost and coarser timer granularity. + private static readonly int MaxTimeInMilliseconds = OperatingSystem.IsBrowser() ? 100 : 10; + + // Stopwatch timestamps use Stopwatch.Frequency units, which differ per platform + // (they only coincide with TimeSpan's 100ns ticks on Windows). + private static readonly long MaxTimeInStopwatchTicks = Stopwatch.Frequency / 1000 * MaxTimeInMilliseconds; private const float TileSizePenaltyPerFoundationCell = 0.07f; private const int RepeatPenaltyAncestorWindow = 5; private const float RepeatPenaltyPerWeightedUse = 0.75f; @@ -101,7 +108,7 @@ private void FindConnectedTilePath(Point2D start, Point2D end, bool allowInstant lastNode.Destination = end; } - long timeoutTimestamp = Stopwatch.GetTimestamp() + TimeSpan.FromMilliseconds(MaxTimeInMilliseconds).Ticks; + long timeoutTimestamp = Stopwatch.GetTimestamp() + MaxTimeInStopwatchTicks; openSet.Enqueue(lastNode, (GetNodePriorityScore(lastNode), lastNode.Tile?.ExtraPriority ?? 0)); while (openSet.Count > 0) @@ -120,7 +127,7 @@ private void FindConnectedTilePath(Point2D start, Point2D end, bool allowInstant { bestNode = currentNode; bestDistance = currentDistance; - timeoutTimestamp = Stopwatch.GetTimestamp() + TimeSpan.FromMilliseconds(MaxTimeInMilliseconds).Ticks; + timeoutTimestamp = Stopwatch.GetTimestamp() + MaxTimeInStopwatchTicks; } if (bestDistance == 0 || Stopwatch.GetTimestamp() > timeoutTimestamp)