From b2b5e3d54a5b989c8d0d0625ae46a85c346a5313 Mon Sep 17 00:00:00 2001 From: Erik Date: Wed, 29 Jul 2026 10:28:52 +0200 Subject: [PATCH] diag(render): composite-warmup stall probe for the session-3 tunnel hang The 2026-07-29 Coldeve session 3 stuck the player in the portal tunnel forever: generation 2 (Town Network, 0x00070156) published render but composites/collision never became ready, and the reveal latch correctly held the tunnel. The composite warmup queue in WbDrawDispatcher has exactly two permanent-stall shapes - a GfxObj id that never resolves (silent load failure, e.g. custom-server content absent from the baked pak) or an upload budget that never reopens - and they are indistinguishable from the reveal log alone. ACDREAM_PROBE_REVEAL=1 (NetDiagnostics.ProbeReveal) now emits one [composite-warmup] STALL line per second while warmup blocks a reveal: pending count, queue depth, scan state, upload-budget gate, and the first four pending GfxObj ids. Zero cost when off. Co-Authored-By: Claude Fable 5 --- .../Rendering/Wb/WbDrawDispatcher.cs | 45 +++++++++++++++++++ src/AcDream.Core.Net/NetDiagnostics.cs | 12 +++++ 2 files changed, 57 insertions(+) diff --git a/src/AcDream.App/Rendering/Wb/WbDrawDispatcher.cs b/src/AcDream.App/Rendering/Wb/WbDrawDispatcher.cs index e64b042d..e6d1d6e2 100644 --- a/src/AcDream.App/Rendering/Wb/WbDrawDispatcher.cs +++ b/src/AcDream.App/Rendering/Wb/WbDrawDispatcher.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -228,6 +229,50 @@ public sealed partial class WbDrawDispatcher : IDisposable + (_compositeWarmupScanComplete ? 0 : entities.Count - _compositeWarmupScanIndex); CompositeTexturesReady = _compositeWarmupScanComplete && _compositeWarmupQueue.Count == 0; + if (!CompositeTexturesReady + && AcDream.Core.Net.NetDiagnostics.ProbeReveal) + { + ProbeRevealWarmupStall(); + } + } + + // #260 probe: while composite warmup blocks a reveal, name the stall once + // per second. The two permanent-stall shapes — a GfxObj id that never + // resolves (silent load failure, e.g. custom-server content missing from + // the baked pak) versus an upload budget that never reopens — are + // otherwise indistinguishable from the reveal log's composites=False. + private long _probeWarmupLastEmitTs; + + private void ProbeRevealWarmupStall() + { + long now = Stopwatch.GetTimestamp(); + if (_probeWarmupLastEmitTs != 0 + && now - _probeWarmupLastEmitTs < Stopwatch.Frequency) + { + return; + } + _probeWarmupLastEmitTs = now; + + var pendingIds = new System.Text.StringBuilder(); + int listed = 0; + foreach (WorldEntity entity in _compositeWarmupQueue) + { + if (listed >= 4) + break; + ulong gfxObjId = entity.MeshRefs.Count > 0 + ? entity.MeshRefs[0].GfxObjId + : 0; + if (listed > 0) + pendingIds.Append(','); + pendingIds.Append($"0x{gfxObjId:X8}"); + listed++; + } + Console.WriteLine( + $"[composite-warmup] STALL pending={LastCompositeWarmupPendingCount}" + + $" queue={_compositeWarmupQueue.Count}" + + $" scanComplete={_compositeWarmupScanComplete}" + + $" uploadOpen={_textures.CanStartCompositeUpload}" + + $" firstPending=[{pendingIds}]"); } internal static bool RequiresCompositeWarmupRebuild( diff --git a/src/AcDream.Core.Net/NetDiagnostics.cs b/src/AcDream.Core.Net/NetDiagnostics.cs index 7ac800e3..e885dd25 100644 --- a/src/AcDream.Core.Net/NetDiagnostics.cs +++ b/src/AcDream.Core.Net/NetDiagnostics.cs @@ -33,4 +33,16 @@ public static class NetDiagnostics /// public static bool ProbeNet { get; set; } = Environment.GetEnvironmentVariable("ACDREAM_PROBE_NET") == "1"; + + /// + /// ACDREAM_PROBE_REVEAL=1 — #260 reveal-stall probe: while a + /// reveal destination's composite warmup is incomplete, emit one + /// [composite-warmup] line per second naming the pending queue + /// depth, scan state, upload-budget gate, and the first few unresolved + /// GfxObj ids. The two permanent-stall shapes (a mesh id that never + /// resolves vs. an upload budget that never reopens) are otherwise + /// indistinguishable and invisible. + /// + public static bool ProbeReveal { get; set; } = + Environment.GetEnvironmentVariable("ACDREAM_PROBE_REVEAL") == "1"; }