fix #176: light pool tracked the camera via flood scoping - collect residents, anchor at player
The seam-floor purple flicker was NOT a draw z-fight. The in-engine
[seam-*] probe (ACDREAM_PROBE_SEAMDRAW - built because RenderDoc cannot
capture this pipeline: it hides GL_ARB_bindless_texture and the
mandatory-modern startup gate throws; AMD GPU rules out Nsight) killed
every double-draw suspect: ONE shell instance per seam cell at the
lifted z, no floor-coincident entity (portal entities sit at z=-12.05),
zero portal depth fans in the sealed Hub. What it caught instead: the
corridor floor's applied light set flipping wholesale with the flood.
Root cause: c500912b scoped BuildPointLightSnapshot by the per-frame
portal flood, on the research doc's gloss of CEnvCell::visible_cell_table
as "the portal-flood visible set". The named decomp refutes the gloss:
add_visible_cell (0x0052de40) DBObj-LOADS absent cells and inserts them;
a cell activation adds itself + its whole dat visible-cell list
(0x0052e228/0x0052e24a); entries leave only via the flush machinery.
It is the RESIDENT-cell registry - gaze can never remove a cell.
add_dynamic_lights (0x0052d410) walks the WHOLE table per frame
(caller 0x00452d30), and insert_light (0x0054d1b0) caps the pool by
distance to Render::player_pos (0x0054d1dd). Retail's pool is a function
of player position only. Ours followed the camera: turning changed the
flood (probe: 8..41 cells across one turn), the six intensity-100
under-room portal purples entered/left the pool, and the wedge blinked.
Fix: BuildPointLightSnapshot(playerWorldPos) collects ALL registered
(=resident) lit lights; over cap keeps dynamics FIRST (retail's separate
7-slot dynamic pool never competes with statics) then nearest-the-player;
the RebuildScopedLights callback is deleted. Live-verified with the probe:
full-circle turn, flood churning 8..41, the floor set held the same 8
identities on every post-spawn frame. The purple wedge SHAPE stays - it
is cdb-proven retail-faithful.
Residual deviation (AP-85 rewritten): single 128 pool vs retail's
7-dynamic/40-static degrade-scaled dual pools - the Hub now shows
7 purples + viewer where retail's cdb showed 4 + viewer + fixture slots;
if the gate reads the wedge as too purple, the A7 dual-pool cap is the
faithful trim.
Pins: PointSnapshot_HubScaleLightCount_ObjectSelectionIsCameraInvariant
(rewritten to the corrected model),
PointSnapshot_OverCap_DynamicsNeverEvictedByNearerStatics,
PointSnapshot_OverCap_KeepsNearestThePlayer,
PointSnapshot_ResidentCollection_CellTagDoesNotFilter.
Suites: Core 2599+2skip / App 726+2skip / UI 425 / Net 385.
The [seam-*] probes stay until the visual gate passes, then strip.
Correction banner added to 2026-07-06-a7-per-cell-lighting-pseudocode.md;
outcome banner on the z-fight handoff; ISSUES #176 updated.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
8cb3176daa
commit
d8984e877f
12 changed files with 525 additions and 194 deletions
|
|
@ -176,27 +176,25 @@ public sealed class LightManager
|
|||
public const int MaxLightsPerObject = 8;
|
||||
|
||||
/// <summary>Hard cap on the per-frame global point-light snapshot the shader
|
||||
/// indexes. ⚠️ LOAD-BEARING STOPGAP — read before touching (#176/#177,
|
||||
/// 2026-07-06): this cap BITES in the Facility Hub (366 registered fixtures →
|
||||
/// 238 camera-distance evictions/frame), and the eviction is the CONFIRMED
|
||||
/// mechanism of the #176 purple seam flash + the #177 stair-room light
|
||||
/// pop-in — an in-range torch of a visible cell that ranks past the cap
|
||||
/// drops out of that cell's 8-set, so per-cell Gouraud lighting pops as the
|
||||
/// camera moves. Retail's <c>minimize_object_lighting</c> (0x0054d480) has
|
||||
/// NO global camera-nearest cap. HOWEVER: raising the cap to 1024 was
|
||||
/// live-tested 2026-07-06 and REVERTED — with the full pool active, three
|
||||
/// unported retail lighting semantics dominate the frame: (a) lights reach
|
||||
/// THROUGH solid floors/walls (retail registers lights per-CELL via
|
||||
/// <c>insert_light</c> 0x0054d1b0 — a portal's purple light below never
|
||||
/// touches the corridor above; our flat sphere-overlap selection has no
|
||||
/// reach/occlusion notion), (b) stationary weenie fixtures ride the DYNAMIC
|
||||
/// 1/d falloff (~9× stronger at 3 m than retail's static 1/d³ bake curve),
|
||||
/// (c) an unexplained striped z-fight-like artifact on lit floor regions
|
||||
/// (user screenshot, launch-176-texflush session). The proper fix is the
|
||||
/// A7 dungeon-lighting arc: per-cell light registration + the static curve
|
||||
/// for fixtures + the stripe hunt, THEN uncap. Register row AP-85; desired
|
||||
/// end-state pin (currently Skip):
|
||||
/// LightManagerTests.PointSnapshot_HubScaleLightCount_ObjectSelectionIsCameraInvariant.</summary>
|
||||
/// indexes. #176 root-cause history (2026-07-06, corrected): retail's pool is
|
||||
/// collected from ALL RESIDENT EnvCells (<c>CEnvCell::add_dynamic_lights</c>
|
||||
/// 0x0052d410 walks the static <c>CEnvCell::visible_cell_table</c> — the
|
||||
/// loaded-cell registry that <c>add_visible_cell</c> 0x0052de40 fills from each
|
||||
/// activated cell + its dat visible-cell list; NOT the per-frame portal flood)
|
||||
/// and capped nearest-THE-PLAYER (<c>Render::insert_light</c> 0x0054d1b0 sorts
|
||||
/// by distance to <c>Render::player_pos</c>) with small caps (7 dynamic + 40
|
||||
/// static, <c>0x0081ec94/98</c>). Two prior acdream models both flickered
|
||||
/// because their pool was CAMERA-coupled: (1) nearest-CAMERA-128 over all
|
||||
/// registered lights (chase-boom swing churned the eviction boundary), then
|
||||
/// (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>
|
||||
public const int MaxGlobalLights = 128;
|
||||
|
||||
private readonly List<LightSource> _pointSnapshot = new();
|
||||
|
|
@ -209,55 +207,76 @@ public sealed class LightManager
|
|||
/// </summary>
|
||||
public IReadOnlyList<LightSource> PointSnapshot => _pointSnapshot;
|
||||
|
||||
/// <summary>
|
||||
/// Rebuild <see cref="PointSnapshot"/> from the registered lit point/spot
|
||||
/// lights. The sun and unlit lights are excluded (the sun is global ambient-
|
||||
/// path; unlit torches contribute nothing). When more than
|
||||
/// <see cref="MaxGlobalLights"/> qualify, keeps the nearest the camera so the
|
||||
/// most relevant lights survive the cap. Call once per frame before
|
||||
/// per-object selection.
|
||||
/// </summary>
|
||||
public void BuildPointLightSnapshot(Vector3 cameraWorldPos)
|
||||
=> BuildPointLightSnapshot(cameraWorldPos, visibleCells: null);
|
||||
// Pool-sort state for BuildPointLightSnapshot: the comparison delegate is
|
||||
// cached (allocated once) and reads the anchor from a field so the per-frame
|
||||
// over-cap sort allocates nothing beyond List.Sort's own wrapper — the same
|
||||
// profile as the previous static-lambda sort (MP-Alloc discipline).
|
||||
private Vector3 _poolAnchor;
|
||||
private Comparison<LightSource>? _poolComparison;
|
||||
|
||||
/// <summary>
|
||||
/// Visible-cell-scoped snapshot build — the faithful port of retail's per-frame
|
||||
/// light collection (<c>CObjCell::add_*_to_global_lights</c> 0x0052b350/0x0052b390
|
||||
/// walked over <c>CEnvCell::visible_cell_table</c> 0x0052d410). When
|
||||
/// <paramref name="visibleCells"/> is non-null (an indoor root with a portal
|
||||
/// flood), a cell-tagged light is a candidate ONLY when its <see cref="LightSource.CellId"/>
|
||||
/// is in the visible set — a cell-less light (<c>CellId == 0</c>: the viewer fill,
|
||||
/// global lights) is always included. This is what (a) stops an under-room light
|
||||
/// washing THROUGH a solid floor (its cell isn't visibly flooded → excluded) and
|
||||
/// (b) bounds the pool to the handful of visible cells so <see cref="MaxGlobalLights"/>
|
||||
/// never evicts a visible cell's in-range light (the #176/#177 mechanism). When
|
||||
/// <paramref name="visibleCells"/> is null (outdoor root / no flood) the behaviour
|
||||
/// is unchanged from the legacy full-pool path.
|
||||
/// Rebuild <see cref="PointSnapshot"/> from ALL registered lit point/spot
|
||||
/// lights — retail's per-frame collection over the RESIDENT-cell registry.
|
||||
/// The sun and unlit lights are excluded (the sun is global ambient-path;
|
||||
/// unlit torches contribute nothing).
|
||||
/// <para>
|
||||
/// Retail anchors (#176 corrected reading, 2026-07-06):
|
||||
/// <c>CEnvCell::add_dynamic_lights</c> (0x0052d410) walks the WHOLE static
|
||||
/// <c>CEnvCell::visible_cell_table</c> — the resident-EnvCell registry that
|
||||
/// <c>CEnvCell::add_visible_cell</c> (0x0052de40) populates from each activated
|
||||
/// cell plus its dat visible-cell list (it <c>DBObj::Get</c>-loads absent cells;
|
||||
/// entries leave only via the flush machinery). It is NOT the per-frame portal
|
||||
/// flood: camera gaze cannot remove a cell from it. acdream's <c>_all</c>
|
||||
/// (register at hydration, unregister at unload) is that resident set, so the
|
||||
/// collection is simply every registered lit light. The under-room portal
|
||||
/// purples reaching the corridor's pool is retail-correct (cdb: retail applies
|
||||
/// them to every Hub cell) — the faceted purple wedge is faithful.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// When more than <see cref="MaxGlobalLights"/> qualify, DYNAMICS are kept
|
||||
/// first (retail's dynamic lights live in their own 7-slot pool —
|
||||
/// <c>Render::add_dynamic_light</c> 0x0054d420 — and never compete with
|
||||
/// 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.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public void BuildPointLightSnapshot(Vector3 cameraWorldPos, IReadOnlySet<uint>? visibleCells)
|
||||
/// <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)
|
||||
{
|
||||
_pointSnapshot.Clear();
|
||||
foreach (var light in _all)
|
||||
{
|
||||
if (!light.IsLit || light.Kind == LightKind.Directional) continue;
|
||||
// Visible-cell scoping (retail add_*_lights over visible_cell_table). A
|
||||
// cell-less light (CellId == 0: viewer fill / global) is always a candidate;
|
||||
// a cell-tagged light joins the pool ONLY when its cell is visibly flooded.
|
||||
if (visibleCells is not null && light.CellId != 0 && !visibleCells.Contains(light.CellId))
|
||||
continue;
|
||||
light.DistSq = (light.WorldPosition - cameraWorldPos).LengthSquared();
|
||||
_pointSnapshot.Add(light);
|
||||
}
|
||||
if (_pointSnapshot.Count > MaxGlobalLights)
|
||||
{
|
||||
_pointSnapshot.Sort(static (a, b) => a.DistSq.CompareTo(b.DistSq));
|
||||
_poolAnchor = playerWorldPos;
|
||||
_poolComparison ??= (a, b) =>
|
||||
{
|
||||
// Dynamics-first mirrors retail's separate dynamic pool; ties by
|
||||
// player distance mirror insert_light's player-nearest sort.
|
||||
if (a.IsDynamic != b.IsDynamic) return a.IsDynamic ? -1 : 1;
|
||||
float da = (a.WorldPosition - _poolAnchor).LengthSquared();
|
||||
float db = (b.WorldPosition - _poolAnchor).LengthSquared();
|
||||
return da.CompareTo(db);
|
||||
};
|
||||
_pointSnapshot.Sort(_poolComparison);
|
||||
_pointSnapshot.RemoveRange(MaxGlobalLights, _pointSnapshot.Count - MaxGlobalLights);
|
||||
}
|
||||
|
||||
// A7.L1 SET-COMPOSITION probe — only meaningful on the scoped (indoor) path.
|
||||
// Inert unless ACDREAM_PROBE_INDOOR_LIGHT=1; the flag check keeps it zero-cost off.
|
||||
if (visibleCells is not null && AcDream.Core.Rendering.RenderingDiagnostics.ProbeIndoorLightEnabled)
|
||||
AcDream.Core.Rendering.RenderingDiagnostics.EmitIndoorLight(visibleCells.Count, _all, _pointSnapshot);
|
||||
// A7.L1 SET-COMPOSITION probe. Inert unless ACDREAM_PROBE_INDOOR_LIGHT=1;
|
||||
// the flag check keeps it zero-cost off.
|
||||
if (AcDream.Core.Rendering.RenderingDiagnostics.ProbeIndoorLightEnabled)
|
||||
AcDream.Core.Rendering.RenderingDiagnostics.EmitIndoorLight(_all, _pointSnapshot);
|
||||
}
|
||||
|
||||
// ── Viewer light — retail SmartBox::set_viewer (0x00452c40) ──────────────
|
||||
|
|
|
|||
|
|
@ -47,11 +47,11 @@ public sealed class LightSource
|
|||
public float ConeAngle = 0f; // radians, Spot only
|
||||
public uint OwnerId; // attached entity id; 0 = world-global
|
||||
public uint CellId; // owning cell id (0xLLLLNNNN); 0 = cell-less/global (viewer fill, sun).
|
||||
// Retail carries this on the RenderLight (insert_light arg6, +0x6c) so the
|
||||
// per-frame pool can be built from only the VISIBLE cells' lights
|
||||
// (CObjCell::add_*_to_global_lights over CEnvCell::visible_cell_table).
|
||||
// acdream uses it to scope BuildPointLightSnapshot — a cell-tagged light is
|
||||
// only a candidate when its cell is visibly flooded (#176/#177 A7 fix).
|
||||
// Retail carries this on the RenderLight (insert_light arg6, +0x6c) for the
|
||||
// cross-cell block-offset distance math. #176 correction (2026-07-06): it is
|
||||
// NOT a pool filter — retail collects from ALL resident cells
|
||||
// (CEnvCell::visible_cell_table = the loaded-cell registry, not the flood);
|
||||
// acdream keeps the tag for probes ([indoor-light]/[seam-*]) + future parity.
|
||||
public bool IsLit = true; // SetLightHook latch
|
||||
public bool IsDynamic; // #143: true = D3D hardware path (1/d att, range×1.5);
|
||||
// false = static dat-baked bake (1/d³, range×1.3)
|
||||
|
|
|
|||
|
|
@ -272,32 +272,91 @@ public static class RenderingDiagnostics
|
|||
Environment.GetEnvironmentVariable("ACDREAM_PROBE_LIGHT") == "1";
|
||||
|
||||
/// <summary>
|
||||
/// A7.L1 (2026-07-06) per-cell light SET-COMPOSITION probe — the apparatus the
|
||||
/// A7.L1 (2026-07-06) light-pool SET-COMPOSITION probe — the apparatus the
|
||||
/// <c>[light]</c> counts could not provide (the #176/#177 discriminator: the bug
|
||||
/// lived in set MEMBERSHIP, not counts). When true, the scoped
|
||||
/// lived in set MEMBERSHIP, not counts). When true,
|
||||
/// <c>LightManager.BuildPointLightSnapshot</c> emits ONE rate-limited
|
||||
/// <c>[indoor-light]</c> line describing the visible-cell-scoped point-light pool
|
||||
/// <c>[indoor-light]</c> line describing the point-light pool
|
||||
/// (see <see cref="EmitIndoorLight"/>):
|
||||
/// <code>
|
||||
/// [indoor-light] visibleCells=<N> pool=<M> cellLess=<K> registered=<R>
|
||||
/// droppedNonVisible=<R-M> byCell=[0x<id>:<count>,...]
|
||||
/// [indoor-light] pool=<M> cellLess=<K> registered=<R> capped=<R-M>
|
||||
/// byCell=[0x<id>:<count>,...]
|
||||
/// </code>
|
||||
/// This validates the A7 fix's load-bearing assumption end-to-end:
|
||||
/// <list type="bullet">
|
||||
/// <item><description><c>cellLess==pool</c> (every pool light is CellId 0) ⇒
|
||||
/// cell tagging FAILED (ParentCellId not flowing) — scoping is a silent no-op.</description></item>
|
||||
/// <item><description><c>pool==cellLess</c> while <c>registered</c> is large in a
|
||||
/// LIT room ⇒ tagged CellIds never match the visible set (wrong id form) — the
|
||||
/// room would go dark.</description></item>
|
||||
/// <item><description><c>droppedNonVisible>0</c> with <c>byCell</c> tracking the
|
||||
/// visible rooms ⇒ scoping WORKING (the under-room/through-floor lights are the
|
||||
/// dropped ones).</description></item>
|
||||
/// </list>
|
||||
/// #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.
|
||||
/// Output-only, inert when off. Initial state from <c>ACDREAM_PROBE_INDOOR_LIGHT=1</c>.
|
||||
/// </summary>
|
||||
public static bool ProbeIndoorLightEnabled { get; set; } =
|
||||
Environment.GetEnvironmentVariable("ACDREAM_PROBE_INDOOR_LIGHT") == "1";
|
||||
|
||||
/// <summary>
|
||||
/// #176 seam-floor flicker decisive probe (2026-07-06). RenderDoc pixel-history
|
||||
/// is infeasible on this pipeline (RenderDoc does not support
|
||||
/// GL_ARB_bindless_texture and hides it from the app → our mandatory-modern
|
||||
/// startup gate throws), so this is the in-engine equivalent at draw
|
||||
/// granularity: every draw route that can put geometry at the corridor seam
|
||||
/// floor reports itself, plus the per-cell light sets ACTUALLY applied.
|
||||
/// When set, four line families emit (all change-deduped, Console):
|
||||
/// <list type="bullet">
|
||||
/// <item><description><c>[seam-cell]</c> — <c>EnvCellRenderer.Render</c>
|
||||
/// (opaque pass): per target cell — in-filter flag, per-gfx instance count +
|
||||
/// transform translation (shows the +0.02 shell lift), per-batch
|
||||
/// cull/translucency, and the cell's 8-light set RESOLVED to identities
|
||||
/// (owner cell + intensity — raw snapshot indices shuffle when the pool
|
||||
/// rebuilds, so identities are the stable signature). Two instances of one
|
||||
/// (cell,gfx) = the runtime double-draw; light identities flipping with the
|
||||
/// flood = the snapshot-scope mechanism.</description></item>
|
||||
/// <item><description><c>[seam-snap]</c> — the point-light snapshot's HOT
|
||||
/// subset (intensity ≥ 50: the portal purples; fixtures are ~1–2, the viewer
|
||||
/// fill 2.25) with owner cells, emitted with the block.</description></item>
|
||||
/// <item><description><c>[seam-ent]</c> — <c>WbDrawDispatcher</c>: any entity
|
||||
/// parented to a target cell — position, culled/slot, resolved light set. A
|
||||
/// floor-coincident entity (plate/static) would be the z-fight's second draw;
|
||||
/// the player entity doubles as the probe's positive control.</description></item>
|
||||
/// <item><description><c>[seam-mask]</c> — <c>GameWindow.DrawRetailPViewPortalDepthWrite</c>:
|
||||
/// every portal depth fan drawn in a target cell. A sealed dungeon must show
|
||||
/// ZERO (seals fire only for OtherCellId==0xFFFF) — any line is a finding.</description></item>
|
||||
/// </list>
|
||||
/// Value <c>1</c> = the default Facility Hub target set (corridor 0x8A020164 +
|
||||
/// seam neighbors 0165/016E/017A + under-hall 011E + portal-light cells
|
||||
/// 0118/0119); a comma-separated hex cell-id list overrides it. Throwaway
|
||||
/// apparatus — strip when #176 closes. Initial state from
|
||||
/// <c>ACDREAM_PROBE_SEAMDRAW</c>.
|
||||
/// </summary>
|
||||
public static bool ProbeSeamDrawEnabled { get; set; } =
|
||||
!string.IsNullOrWhiteSpace(Environment.GetEnvironmentVariable("ACDREAM_PROBE_SEAMDRAW"));
|
||||
|
||||
/// <summary>Target cell ids for the #176 seam-draw probe (see
|
||||
/// <see cref="ProbeSeamDrawEnabled"/>). Full 32-bit cell ids.</summary>
|
||||
public static IReadOnlySet<uint> SeamDrawTargetCells { get; } =
|
||||
ParseSeamDrawTargets(Environment.GetEnvironmentVariable("ACDREAM_PROBE_SEAMDRAW"));
|
||||
|
||||
/// <summary>Parse ACDREAM_PROBE_SEAMDRAW: "1"/"true"/empty → the default #176
|
||||
/// Facility Hub set; otherwise a comma-separated hex cell-id list (same forgiving
|
||||
/// grammar as <see cref="ParseDumpEntityIds"/>). Internal for unit tests.</summary>
|
||||
internal static IReadOnlySet<uint> ParseSeamDrawTargets(string? raw)
|
||||
{
|
||||
// Default: the corridor + the coplanar-sweep seed neighbors + the under-hall
|
||||
// + the two portal-weenie cells whose intensity-100 purple lights are the
|
||||
// wedge's source (handoff 2026-07-06-176-seam-floor-zfight-handoff.md).
|
||||
var defaults = new HashSet<uint>
|
||||
{
|
||||
0x8A020164u, 0x8A020165u, 0x8A02016Eu, 0x8A02017Au,
|
||||
0x8A02011Eu, 0x8A020118u, 0x8A020119u,
|
||||
};
|
||||
if (string.IsNullOrWhiteSpace(raw)) return defaults;
|
||||
var trimmed = raw.Trim();
|
||||
if (trimmed == "1" || trimmed.Equals("true", StringComparison.OrdinalIgnoreCase))
|
||||
return defaults;
|
||||
var parsed = ParseDumpEntityIds(raw);
|
||||
return parsed.Count > 0 ? parsed : defaults;
|
||||
}
|
||||
|
||||
// Cell-change gate for EmitVis. The probe fires once per distinct root cell
|
||||
// so launch.log stays readable under motion (the per-frame call is a no-op
|
||||
// when the root is unchanged). Sentinel 0 = "no root yet" — the first real
|
||||
|
|
@ -484,15 +543,15 @@ public static class RenderingDiagnostics
|
|||
|
||||
/// <summary>
|
||||
/// A7.L1 — emit ONE rate-limited <c>[indoor-light]</c> line describing the
|
||||
/// visible-cell-scoped 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 the scoped
|
||||
/// <c>LightManager.BuildPointLightSnapshot</c> (visibleCells != null path).
|
||||
/// 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).
|
||||
/// </summary>
|
||||
/// <param name="visibleCellCount">Size of the portal-flood visible-cell set this frame.</param>
|
||||
/// <param name="allRegistered">Every registered light (<c>LightManager._all</c>).</param>
|
||||
/// <param name="scopedSnapshot">The visible-cell-scoped point-light pool just built.</param>
|
||||
public static void EmitIndoorLight(int visibleCellCount,
|
||||
/// <param name="scopedSnapshot">The point-light pool just built.</param>
|
||||
public static void EmitIndoorLight(
|
||||
IReadOnlyList<AcDream.Core.Lighting.LightSource> allRegistered,
|
||||
IReadOnlyList<AcDream.Core.Lighting.LightSource> scopedSnapshot)
|
||||
{
|
||||
|
|
@ -518,13 +577,12 @@ public static class RenderingDiagnostics
|
|||
}
|
||||
|
||||
var sb = new StringBuilder(220);
|
||||
sb.Append("[indoor-light] visibleCells=").Append(visibleCellCount);
|
||||
sb.Append(" pool=").Append(pool);
|
||||
sb.Append("[indoor-light] pool=").Append(pool);
|
||||
sb.Append(" cellLess=").Append(cellLess);
|
||||
sb.Append(" registered=").Append(registeredLitPoints);
|
||||
// Lights excluded by visibility scoping (retail: cells not in visible_cell_table
|
||||
// contribute nothing) — the through-floor/under-room lights kept out of the pool.
|
||||
sb.Append(" droppedNonVisible=").Append(registeredLitPoints - pool);
|
||||
// Lights dropped by the MaxGlobalLights nearest-player cap (0 in Hub-scale
|
||||
// rooms for dynamics — statics beyond the 128th-nearest are out of range).
|
||||
sb.Append(" capped=").Append(registeredLitPoints - pool);
|
||||
sb.Append(" byCell=[");
|
||||
const int MaxCells = 12;
|
||||
int shown = 0;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue