Umbrella for the #92 / #233 family. The hook-site guards
(IsSafeLightRange + SEH) are symptom treatment — this issue tracks
whether the cause is reachable.
The actual cause
The engine caches/pools BSRenderPass objects whose sceneLights[]
is a raw BSLight**. A cached pass can outlive its lights; the
engine then derefs freed memory (GeometrySetupConstantDirectionalLight
reads sceneLights[0]->light->fade with zero checks). This is an
engine-native lifetime hazard — but vanilla rarely trips it because
vanilla light lifetime is stable. Shadow Light Fix / SCM
deliberately churns BSLights (promoting NiLights to shadow-casting
lights and back), multiplying free/alloc cycles and widening the
stale window. That is why this family arrived with SLF.
Root-fix options assessed
- BSLight graveyard (deferred destruction) — tractable.
Intercept BSLight release at SCM's conversion/free points, park in
a bounded N-frame list, free after rendering. Kills within-frame
and short-window staleness (the BookMenu case is likely this
class). Cannot cover long-lived cached passes (cross-cell) without
unbounded retention, so guards remain as backstop — but the warn
lines should become rare, which is itself the measurement.
- Scrub engine pass pools on light free — the true fix, heavy
RE. Requires reversing the BSRenderPass allocator/pool layout and
patching sceneLights entries on destruction; enumeration is racy
against the render thread. Prior art says caution: SCM ref-holding
was already tried and was insufficient for cross-cell staleness.
- Ref-holding in sceneLights — not possible; engine struct is a
raw pointer array written engine-side.
Proposal: implement (1) behind SCM, keep the (now-unified) guards as
defense-in-depth, and treat guard warn-line frequency in logs as the
regression metric. If warns persist for long-lived passes, that is
the evidence needed to justify the RE cost of (2).
Related: #92, #233, PR #234 (unified guards + per-frame probe cache).
🤖 Generated with Claude Code
Umbrella for the #92 / #233 family. The hook-site guards
(
IsSafeLightRange+ SEH) are symptom treatment — this issue trackswhether the cause is reachable.
The actual cause
The engine caches/pools
BSRenderPassobjects whosesceneLights[]is a raw
BSLight**. A cached pass can outlive its lights; theengine then derefs freed memory (
GeometrySetupConstantDirectionalLightreads
sceneLights[0]->light->fadewith zero checks). This is anengine-native lifetime hazard — but vanilla rarely trips it because
vanilla light lifetime is stable. Shadow Light Fix / SCM
deliberately churns BSLights (promoting NiLights to shadow-casting
lights and back), multiplying free/alloc cycles and widening the
stale window. That is why this family arrived with SLF.
Root-fix options assessed
Intercept BSLight release at SCM's conversion/free points, park in
a bounded N-frame list, free after rendering. Kills within-frame
and short-window staleness (the BookMenu case is likely this
class). Cannot cover long-lived cached passes (cross-cell) without
unbounded retention, so guards remain as backstop — but the warn
lines should become rare, which is itself the measurement.
RE. Requires reversing the BSRenderPass allocator/pool layout and
patching sceneLights entries on destruction; enumeration is racy
against the render thread. Prior art says caution: SCM ref-holding
was already tried and was insufficient for cross-cell staleness.
raw pointer array written engine-side.
Proposal: implement (1) behind SCM, keep the (now-unified) guards as
defense-in-depth, and treat guard warn-line frequency in logs as the
regression metric. If warns persist for long-lived passes, that is
the evidence needed to justify the RE cost of (2).
Related: #92, #233, PR #234 (unified guards + per-frame probe cache).
🤖 Generated with Claude Code