fix(render): #350 — the shadow ledger's lifetime counters are 64-bit; the 2h42m overflow is closed

RenderDeltaApplyResult's nine counters accumulate for the lifetime of
a world generation (the cumulative sum is never reset in place) yet
were int where every sibling lifetime counter in the same class was
already ulong/long. A 2h42m single-generation session (login to crash
with zero portals) overflowed one through ordinary per-tick churn from
SynchronizeActiveSources' two call sites per frame. Introduced
0eb66485 (2026-07-24); first reached by the vendor buy gate because
parking at a shop for hours produced the project's first multi-hour
unbroken generation. The vendor materializer was exonerated by routing
analysis (shop-item Ingest/Remove reaches inventory deltas only, never
the render journal).

Widened to long; the per-tick builder stays int and widens implicitly;
the arithmetic stays checked. All 68 render-shadow tests green.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Erik 2026-08-08 10:29:39 +02:00
parent 3c9fc57adb
commit 58c8de264e
2 changed files with 41 additions and 10 deletions

View file

@ -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.