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:
parent
3c9fc57adb
commit
58c8de264e
2 changed files with 41 additions and 10 deletions
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue