From 87cddce24ad5f8f6bafd3aafd67d3c026aada30a Mon Sep 17 00:00:00 2001 From: Erik Date: Mon, 6 Jul 2026 18:47:33 +0200 Subject: [PATCH] fix #176 site-A: idempotent static light registration on landblock re-apply - the stacking wash leak ApplyLoadedTerrainLocked registered every static Setup light per apply, and RegisterOwnedLight appends unconditionally. A landblock can RE-apply without an unload in between (#168 ForceReloadWindow, Far->Near promotion) and static entity ids are deterministic, so each re-apply stacked another copy of the same lights under the same owner - the [seam-ent] x2->x4 growth, invisible to the identity-keyed [seam-blk] probe (stacked copies share one owner identity while intensity multiplies). The wash intensifies over the session and the stacked exact-tie copies destabilize the 128-cap pool selection sort. Fix: UnregisterOwner(entity.Id) before the per-entity register loop - re-apply idempotent, first apply a no-op. The live-spawn path keeps its existing guid-dedup teardown (GameWindow:3134 -> :4679). Suites green. Co-Authored-By: Claude Fable 5 --- src/AcDream.App/Rendering/GameWindow.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/AcDream.App/Rendering/GameWindow.cs b/src/AcDream.App/Rendering/GameWindow.cs index 79a333b1..6bb93d10 100644 --- a/src/AcDream.App/Rendering/GameWindow.cs +++ b/src/AcDream.App/Rendering/GameWindow.cs @@ -7724,6 +7724,15 @@ public sealed class GameWindow : IDisposable datBundle.Setups.TryGetValue(src, out var datSetup); if (datSetup is not null && datSetup.Lights.Count > 0) { + // #176 site-A light-stacking leak: a landblock can RE-apply + // without an unload in between (#168 ForceReloadWindow, Far→Near + // promotion), and static entity ids are deterministic — so each + // re-apply appended another copy of the same lights under the + // same owner (observed ×2→×4 per session; the wash intensifies + // over time, and the stacked ties destabilize the 128-cap pool + // sort). Clear the owner's previous registration first: the + // re-apply becomes idempotent, and a first apply is a no-op. + _lightingSink.UnregisterOwner(entity.Id); var loaded = AcDream.Core.Lighting.LightInfoLoader.Load( datSetup, ownerId: entity.Id,