diff --git a/docs/ISSUES.md b/docs/ISSUES.md index e76754b5..ab510ba1 100644 --- a/docs/ISSUES.md +++ b/docs/ISSUES.md @@ -24,6 +24,32 @@ What does NOT go here: - Every session: scan OPEN issues at start; promote/close anything we touched during the session before ending. - Promoting to a Phase: mark as `DONE (promoted to Phase X)` + commit SHA where the Phase entry landed. +## #350 — Render-shadow ledger overflow after 2h42m: lifetime int counters in a never-reset accumulator + +**Status:** FIXED IN TREE 2026-08-08 (pending clean-room + landing). +**Evidence:** `vendor-buy-gate.log` ~1606 — checked OverflowException in +`RenderSceneShadowRuntime.Add` from `UpdateFrameOrchestrator.Tick`, exit 82, +2h42m into a SINGLE world generation (login 20:28 -> crash 23:10, one +`[world-reveal] generation=1`, no portal). + +**Mechanism (investigated, vendor coupling REFUTED):** +`_cumulativeApply` sums nine int fields once per tick for the lifetime of a +world generation and is never reset in place (production has zero Clear() +call sites; only generation replacement constructs a fresh runtime). +`SynchronizeActiveSources` feeds per-entity churn from TWO call sites per +frame; at uncapped frame rates a long session's ordinary churn crosses +int.MaxValue. Introduced `0eb66485` (2026-07-24) with the class's OTHER +lifetime counters already ulong/long — only these nine were undersized. +The vendor materializer was exonerated by routing analysis: shop-item +Ingest/Remove reaches inventory deltas only, never the render journal; +the correlation was that buy-gate testing produced the first multi-hour +single-generation soak. + +**Fix:** widen `RenderDeltaApplyResult`'s nine fields to long (the +per-tick builder stays int and widens implicitly); arithmetic stays +checked. No clamp, no reset-behavior change — the counters are +legitimately unbounded lifetime telemetry that was simply undersized. + ## #348 — Render-loop death by Win32 cursor-handle exhaustion: Silk recreates the native cursor on every alternation **Status:** FIX IN TREE (2026-08-08) pending the vendor-gate relaunch. diff --git a/src/AcDream.App/Rendering/Scene/RenderSceneContracts.cs b/src/AcDream.App/Rendering/Scene/RenderSceneContracts.cs index 057d1fe7..e3450ce2 100644 --- a/src/AcDream.App/Rendering/Scene/RenderSceneContracts.cs +++ b/src/AcDream.App/Rendering/Scene/RenderSceneContracts.cs @@ -401,18 +401,23 @@ internal readonly record struct RenderSceneIndexCounts( }; } +// #350: these counters accumulate for the LIFETIME of a world generation +// (RenderSceneShadowRuntime._cumulativeApply is never reset in place), so +// they are 64-bit like the class's other lifetime counters — a 2h42m +// single-generation session overflowed the original int through ordinary +// per-tick churn. The arithmetic stays checked. internal readonly record struct RenderDeltaApplyResult( - int Applied, - int Registered, - int Updated, - int Replaced, - int Unregistered, - int RejectedGeneration, - int RejectedOutOfOrderSequence, - int RejectedStaleIncarnation, - int RejectedMissing) + long Applied, + long Registered, + long Updated, + long Replaced, + long Unregistered, + long RejectedGeneration, + long RejectedOutOfOrderSequence, + long RejectedStaleIncarnation, + long RejectedMissing) { - public int Rejected => + public long Rejected => RejectedGeneration + RejectedOutOfOrderSequence + RejectedStaleIncarnation