diff --git a/src/AcDream.App/Rendering/GameWindow.cs b/src/AcDream.App/Rendering/GameWindow.cs index 4320fbb8..9e9478c9 100644 --- a/src/AcDream.App/Rendering/GameWindow.cs +++ b/src/AcDream.App/Rendering/GameWindow.cs @@ -5472,6 +5472,9 @@ public sealed class GameWindow : IDisposable EnsureTeleportArrivalController(); _pendingTeleportRot = rot; _teleportArrival!.BeginArrival(newWorldPos, p.LandblockId); + AcDream.Core.Physics.PhysicsDiagnostics.LogTeleport( + "AIM", p.LandblockId, + $"lb={lbX},{lbY} indoor={((p.LandblockId & 0xFFFFu) >= 0x0100u)} diffLb={differentLandblock}"); } } @@ -5561,6 +5564,8 @@ public sealed class GameWindow : IDisposable dt: 1f / 60f); _playerController.State = AcDream.App.Input.PlayerState.InWorld; + AcDream.Core.Physics.PhysicsDiagnostics.LogTeleport( + "PLACED", resolved.CellId, $"forced={forced}"); Console.WriteLine($"live: teleport complete — snapped to {snappedPos} cell=0x{resolved.CellId:X8}"); // Tell the server the client finished loading the new landblock (holtburger @@ -5813,6 +5818,25 @@ public sealed class GameWindow : IDisposable // case), which blocks the render thread for at most that duration. // This is the minimum correct behavior; a future pass can reduce // contention by pre-building render-thread work on the worker. + // tp-probe (2026-06-22, REMOVABLE): measure lock-WAIT (the _datLock + // contention signal — large only when the render thread is hammering the + // lock during a CreateObject flood) AND lock-HOLD (the intrinsic build + // cost). Identical work in both branches; the probe branch only adds the + // stopwatch + log. No behavior change when ProbeTeleportEnabled is false. + if (AcDream.Core.Physics.PhysicsDiagnostics.ProbeTeleportEnabled) + { + var sw = System.Diagnostics.Stopwatch.StartNew(); + lock (_datLock) + { + long waitedMs = sw.ElapsedMilliseconds; + sw.Restart(); + var built = BuildLandblockForStreamingLocked(landblockId, kind); + AcDream.Core.Physics.PhysicsDiagnostics.LogTeleport( + "BUILD", landblockId, + $"waited={waitedMs}ms held={sw.ElapsedMilliseconds}ms kind={kind}"); + return built; + } + } lock (_datLock) { return BuildLandblockForStreamingLocked(landblockId, kind); @@ -6414,6 +6438,7 @@ public sealed class GameWindow : IDisposable AcDream.Core.Terrain.LandblockMeshData meshData) { if (_terrain is null || _dats is null) return; + AcDream.Core.Physics.PhysicsDiagnostics.LogTeleport("APPLY", lb.LandblockId); // Phase A.1 hotfix: render-thread path also takes the dat lock so it // doesn't race with BuildLandblockForStreaming on the worker thread. diff --git a/src/AcDream.App/Streaming/LandblockStreamer.cs b/src/AcDream.App/Streaming/LandblockStreamer.cs index ffaa6de7..f5602b12 100644 --- a/src/AcDream.App/Streaming/LandblockStreamer.cs +++ b/src/AcDream.App/Streaming/LandblockStreamer.cs @@ -127,6 +127,7 @@ public sealed class LandblockStreamer : IDisposable { if (System.Threading.Volatile.Read(ref _disposed) != 0) throw new ObjectDisposedException(nameof(LandblockStreamer)); + AcDream.Core.Physics.PhysicsDiagnostics.LogTeleport("ENQ", landblockId, $"kind={kind}"); _inbox.Writer.TryWrite(new LandblockStreamJob.Load(landblockId, kind)); } diff --git a/src/AcDream.Core/Physics/PhysicsDiagnostics.cs b/src/AcDream.Core/Physics/PhysicsDiagnostics.cs index 06a3180c..1412249c 100644 --- a/src/AcDream.Core/Physics/PhysicsDiagnostics.cs +++ b/src/AcDream.Core/Physics/PhysicsDiagnostics.cs @@ -423,6 +423,31 @@ public static class PhysicsDiagnostics public static bool ProbeSweptEnabled { get; set; } = Environment.GetEnvironmentVariable("ACDREAM_PROBE_SWEPT") == "1"; + /// + /// Teleport-foundation timing probe (2026-06-22 — REMOVABLE diagnostic). + /// Emits one [tp-probe] line per teleport-pipeline event with a + /// cross-thread monotonic timestamp () + /// so the offline reader can order AIM / ENQ / BUILD / APPLY / PLACED across + /// the render thread and the streamer worker. Disambiguates the three + /// candidate roots for "destination not resident fast": apply-THROTTLE + /// (APPLY lands before PLACED), _datLock CONTENTION (BUILD waited= + /// large), and a streaming-command GATE (ENQ never fires for the dest). + /// Initial state from ACDREAM_PROBE_TELEPORT=1. Strip after capture. + /// + public static bool ProbeTeleportEnabled { get; set; } = + Environment.GetEnvironmentVariable("ACDREAM_PROBE_TELEPORT") == "1"; + + /// + /// One [tp-probe] line. Self-guards on , + /// so callers need not pre-check (the cost when off is a single bool read). + /// + public static void LogTeleport(string point, uint id, string extra = "") + { + if (!ProbeTeleportEnabled) return; + Console.WriteLine(System.FormattableString.Invariant( + $"[tp-probe] {point,-6} id=0x{id:X8} t={Environment.TickCount64} {extra}")); + } + /// /// A6.P3 issue #98 step-walk investigation (2026-05-23). When true, /// emits one [step-walk] line at selected points in the transition @@ -549,6 +574,7 @@ public static class PhysicsDiagnostics ProbePlacementFailEnabled = false; ProbeSweptEnabled = false; ProbeStepWalkEnabled = false; + ProbeTeleportEnabled = false; // Side-channel fields LastBspHitPoly = null;