feat(A7.L1): scope the point-light pool by last frame's visible cells (#79/#93/#176/#177)
The Town Network hub (498 registered fixtures) starved the player's own
room: BuildPointLightSnapshot's player-nearest-128 cap sorts by raw
Euclidean distance, which isn't a reliable proxy for "same room" in a
dense maze — a fixture on the other side of a wall can be geometrically
closer than the room's own torches and win the cap. LightSource.CellId
tagging and the [indoor-light] membership probe already existed from the
c500912b/#176 arc; the missing piece was a candidacy filter.
BuildPointLightSnapshot(playerWorldPos, visibleCells) now narrows
candidates to the frame's actual visible cells before the existing
dynamics-first player-nearest cap runs (cell-less lights, e.g. the viewer
fill, always included). GameWindow feeds LAST FRAME's already-rendered
RetailPViewFrameResult.DrawableCells — one frame of latency instead of
re-threading a mid-DrawInside callback, which was the exact mechanism
(c500912b) that caused the earlier #176 seam-floor flicker regression.
The distance-sort anchor stays the player, unchanged.
AP-85 updated in place (third revision) rather than adding a new row —
same underlying divergence, now with the render-visibility approximation
of retail's true DBObj-load/flush-bounded resident registry documented
alongside its residual risk (one unscoped frame on portal re-entry).
Core 2652+2skip / App 741+2skip / UI 425 / Net 385 green. Pending: user
visual gate at the Town Network fountain, and a #176 corridor-seam
non-regression recheck (Facility Hub, different landblock).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
parent
388f3ed307
commit
d275ed554e
5 changed files with 166 additions and 23 deletions
|
|
@ -189,12 +189,15 @@ public sealed class LightManager
|
|||
/// (2) frame-FLOOD scoping `c500912b` (gaze-dependent: the under-room portal
|
||||
/// purples entered/left the pool as the camera turned — the seam-floor
|
||||
/// blink; probe: [seam-blk]/[seam-snap]). Current model: all registered
|
||||
/// (=resident) lit lights, dynamics-first then nearest-player, capped here.
|
||||
/// 128 is wider than retail's 40+7 — a documented backstop that in Hub-scale
|
||||
/// rooms only ever evicts far-out-of-range statics; adopting retail's exact
|
||||
/// dual-pool caps + degrade levels is A7-arc work. The 1024 uncap remains
|
||||
/// refuted (striped-floor artifact + the unported static 1/d³ fixture curve,
|
||||
/// A7 fix #2). Register row AP-85.</summary>
|
||||
/// (=resident) lit lights optionally FILTERED by last frame's rendered
|
||||
/// visible-cell set (A7.L1, 2026-07-09 — <see cref="BuildPointLightSnapshot"/>'s
|
||||
/// <c>visibleCells</c> param; fixes Town Network starvation without
|
||||
/// reproducing c500912b — see that method's doc), then dynamics-first nearest-
|
||||
/// player, capped here. 128 is wider than retail's 40+7 — a documented backstop
|
||||
/// that in a properly cell-scoped room only ever evicts far-out-of-range
|
||||
/// statics; adopting retail's exact dual-pool caps + degrade levels is A7-arc
|
||||
/// work. The 1024 uncap remains refuted (striped-floor artifact + the unported
|
||||
/// static 1/d³ fixture curve, A7 fix #2). Register row AP-85.</summary>
|
||||
public const int MaxGlobalLights = 128;
|
||||
|
||||
private readonly List<LightSource> _pointSnapshot = new();
|
||||
|
|
@ -239,22 +242,46 @@ public sealed class LightManager
|
|||
/// statics), then the nearest THE PLAYER (<c>Render::insert_light</c>
|
||||
/// 0x0054d1b0 insertion-sorts by squared distance to <c>Render::player_pos</c>,
|
||||
/// set from <c>player->m_position</c>, SmartBox 0x00453d3a, with the
|
||||
/// viewer-cell fallback 0x00455ab6). The pool is therefore a function of
|
||||
/// PLAYER position and light registration ONLY — camera rotation/position
|
||||
/// cannot change it. Both prior camera-coupled pools (nearest-camera cap;
|
||||
/// frame-flood scoping <c>c500912b</c>) produced the #176 seam-floor purple
|
||||
/// blink. Call once per frame before per-object selection.
|
||||
/// viewer-cell fallback 0x00455ab6). The distance SORT is therefore a function
|
||||
/// of PLAYER position and light registration ONLY — camera rotation/position
|
||||
/// cannot change it (both prior camera-ANCHORED pools — nearest-camera cap;
|
||||
/// <c>c500912b</c>'s camera-seeded re-flood — produced the #176 seam-floor
|
||||
/// purple blink by making the SORT itself camera-dependent). The optional
|
||||
/// <paramref name="visibleCells"/> candidacy FILTER (A7.L1) does not change
|
||||
/// this: it narrows the input set before the player-anchored sort runs, using
|
||||
/// a value the caller captured from last frame's already-rendered draw list,
|
||||
/// not a fresh camera-seeded computation performed here. Call once per frame
|
||||
/// before per-object selection.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
/// <param name="playerWorldPos">The player's world position (render position;
|
||||
/// callers pass the camera position only when no player exists — retail's
|
||||
/// player/viewer branch).</param>
|
||||
public void BuildPointLightSnapshot(Vector3 playerWorldPos)
|
||||
/// <param name="visibleCells">
|
||||
/// A7.L1 (2026-07-09) — optional visible-cell scoping. When non-null, a light
|
||||
/// is a candidate only if it is cell-less (<c>CellId == 0</c> — the viewer fill,
|
||||
/// always in scope) or its <c>CellId</c> is in this set. Fixes the Town Network
|
||||
/// starvation case (463 registered fixtures): the player-nearest cap sorts by
|
||||
/// raw Euclidean distance, which is not a reliable proxy for "same room" in a
|
||||
/// dense, maze-like hub — a fixture on the other side of a wall can be
|
||||
/// geometrically closer than the player's own room's torches and win the cap,
|
||||
/// leaving the visible room dark. Scoping candidacy to the frame's actual
|
||||
/// visible cells (the render already computes this — callers pass last frame's
|
||||
/// <c>RetailPViewFrameResult.DrawableCells</c>, one frame of latency, to avoid
|
||||
/// re-threading a mid-render callback) removes those from contention before the
|
||||
/// cap ever applies. The distance-sort anchor stays the PLAYER either way — this
|
||||
/// parameter only narrows candidacy, it does not change the sort (the #176
|
||||
/// correction: CAMERA anchoring, not cell scoping itself, caused the earlier
|
||||
/// seam-floor flicker regression, c500912b). Null (the default) preserves the
|
||||
/// legacy unscoped behavior — outdoor / no-clipRoot callers pass null.
|
||||
/// </param>
|
||||
public void BuildPointLightSnapshot(Vector3 playerWorldPos, IReadOnlySet<uint>? visibleCells = null)
|
||||
{
|
||||
_pointSnapshot.Clear();
|
||||
foreach (var light in _all)
|
||||
{
|
||||
if (!light.IsLit || light.Kind == LightKind.Directional) continue;
|
||||
if (visibleCells is not null && light.CellId != 0 && !visibleCells.Contains(light.CellId)) continue;
|
||||
_pointSnapshot.Add(light);
|
||||
}
|
||||
if (_pointSnapshot.Count > MaxGlobalLights)
|
||||
|
|
|
|||
|
|
@ -282,13 +282,20 @@ public static class RenderingDiagnostics
|
|||
/// [indoor-light] pool=<M> cellLess=<K> registered=<R> capped=<R-M>
|
||||
/// byCell=[0x<id>:<count>,...]
|
||||
/// </code>
|
||||
/// #176 correction (2026-07-06, same day): the pool is now retail's
|
||||
/// RESIDENT-cell collection capped nearest-the-PLAYER — visible-cell scoping
|
||||
/// (the gaze-coupled pool) was the #176 flicker mechanism and is deleted, so
|
||||
/// the line no longer carries <c>visibleCells/droppedNonVisible</c>. The
|
||||
/// diagnostic value that remains: <c>cellLess==pool</c> in a fixture-rich room
|
||||
/// still means cell tagging FAILED (ParentCellId not flowing), and
|
||||
/// <c>byCell</c> shows which cells' lights are pooled.
|
||||
/// #176 correction (2026-07-06): the pool became retail's RESIDENT-cell
|
||||
/// collection capped nearest-the-PLAYER — the earlier gaze-coupled scoping
|
||||
/// (rebuilding the pool from a freshly re-flooded CAMERA-seeded set,
|
||||
/// <c>c500912b</c>) was the #176 flicker mechanism and was deleted.
|
||||
/// A7.L1 (2026-07-09): visible-cell scoping is BACK, but sourced differently —
|
||||
/// <c>LightManager.BuildPointLightSnapshot</c> now takes an optional
|
||||
/// <c>visibleCells</c> filter that <c>GameWindow</c> feeds from LAST FRAME's
|
||||
/// already-rendered <c>RetailPViewFrameResult.DrawableCells</c> (one frame of
|
||||
/// latency, no independent re-flood, no callback threaded into DrawInside) —
|
||||
/// fixes the Town Network starvation case (463 fixtures, a wall-adjacent
|
||||
/// corridor's fixtures out-ranking the player's own room in raw Euclidean
|
||||
/// distance) without reproducing the #176 mechanism. <c>byCell</c> shows which
|
||||
/// cells' lights are pooled; <c>cellLess==pool</c> in a fixture-rich room still
|
||||
/// means cell tagging FAILED (ParentCellId not flowing).
|
||||
/// Output-only, inert when off. Initial state from <c>ACDREAM_PROBE_INDOOR_LIGHT=1</c>.
|
||||
/// </summary>
|
||||
public static bool ProbeIndoorLightEnabled { get; set; } =
|
||||
|
|
@ -576,8 +583,8 @@ public static class RenderingDiagnostics
|
|||
/// point-light pool: the SET COMPOSITION the <c>[light]</c> counts can't show.
|
||||
/// Cheap no-op when <see cref="ProbeIndoorLightEnabled"/> is false; otherwise
|
||||
/// fires at most once per second. Called from
|
||||
/// <c>LightManager.BuildPointLightSnapshot</c> (the resident collection —
|
||||
/// #176 correction removed the visible-cell scoping).
|
||||
/// <c>LightManager.BuildPointLightSnapshot</c> — <paramref name="scopedSnapshot"/>
|
||||
/// already reflects any last-frame visible-cell scoping (A7.L1, 2026-07-09).
|
||||
/// </summary>
|
||||
/// <param name="allRegistered">Every registered light (<c>LightManager._all</c>).</param>
|
||||
/// <param name="scopedSnapshot">The point-light pool just built.</param>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue