From 0a38d934fd7ab41550b433c0e0d56f2643d509a2 Mon Sep 17 00:00:00 2001 From: Erik Date: Tue, 9 Jun 2026 22:07:43 +0200 Subject: [PATCH] =?UTF-8?q?diag(render):=20#105=20round=203=20=E2=80=94=20?= =?UTF-8?q?finalize-replace=20+=20late-register=20tripwires=20on=20EnvCell?= =?UTF-8?q?Renderer?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Live evidence narrowed #105 to the pending->committed instance hand-off: walls missing from BOTH inside and outside views while the same cells'' props draw and collision works = the wall-shell INSTANCES never reach the committed draw set. FinalizeLandblock uses REPLACE semantics (lb.Instances = lb.PendingInstances), and with two-tier streaming a landblock can finalize while a promote job is still registering its cells on the worker thread — the partial pending set commits, the remainder lands in a fresh pending list, and the next finalize REPLACES the committed set with only the remainder. Whoever registered first is silently lost: per-session-random (drain timing), per-building-persistent, from session start. - [finalize-replace] a finalize that DISCARDS already-committed instances - [late-register] a RegisterCell landing after its landblock finalized Both print only on the suspect interleavings. Next occurrence proves or kills the theory; the fix (merge semantics + registration/finalize atomicity) follows the evidence. Co-Authored-By: Claude Fable 5 --- src/AcDream.App/Rendering/Wb/EnvCellRenderer.cs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/AcDream.App/Rendering/Wb/EnvCellRenderer.cs b/src/AcDream.App/Rendering/Wb/EnvCellRenderer.cs index 0e6e47ed..064cb264 100644 --- a/src/AcDream.App/Rendering/Wb/EnvCellRenderer.cs +++ b/src/AcDream.App/Rendering/Wb/EnvCellRenderer.cs @@ -349,6 +349,12 @@ public sealed unsafe class EnvCellRenderer : IDisposable lock (lb.Lock) { + // TEMP diagnostic #105 (strip with fix): a registration landing AFTER + // this landblock was already finalized starts a fresh pending list that + // only commits if ANOTHER finalize arrives — and that one will REPLACE + // (not merge) the committed set. One-shot per landblock per pending list. + if (lb.InstancesReady && lb.PendingInstances is null) + Console.WriteLine($"[late-register] lb=0x{landblockId:X8} cell=0x{envCellId:X8} registered AFTER finalize — starting a new pending list ({(lb.Instances?.Count ?? 0)} already committed)"); lb.PendingInstances ??= new List(capacity: 32); lb.PendingInstances.Add(cellInstance); lb.PendingEnvCellBounds ??= new Dictionary(); @@ -403,6 +409,13 @@ public sealed unsafe class EnvCellRenderer : IDisposable { if (lb.PendingInstances is not null) { + // TEMP diagnostic #105 (strip with fix): REPLACE semantics — if a + // previous finalize already committed instances for this landblock, + // this swap DISCARDS them in favor of the new pending set. A partial + // pending set (finalize racing a still-registering promote job) + // silently loses buildings. + if (lb.Instances is { Count: > 0 }) + Console.WriteLine($"[finalize-replace] lb=0x{landblockId:X8} DISCARDING {lb.Instances.Count} committed instances, replacing with {lb.PendingInstances.Count} pending"); lb.Instances = lb.PendingInstances; lb.PendingInstances = null; }