Merge branch 'main' into claude/eloquent-hugle-42119e

# Conflicts:
#	.gitignore
This commit is contained in:
Erik 2026-07-06 00:47:09 +02:00
commit 093cdb6d57
38 changed files with 5852 additions and 216 deletions

View file

@ -271,6 +271,33 @@ public static class RenderingDiagnostics
public static bool ProbeLightEnabled { get; set; } =
Environment.GetEnvironmentVariable("ACDREAM_PROBE_LIGHT") == "1";
/// <summary>
/// A7.L1 (2026-07-06) per-cell light 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
/// <c>LightManager.BuildPointLightSnapshot</c> emits ONE rate-limited
/// <c>[indoor-light]</c> line describing the visible-cell-scoped point-light pool
/// (see <see cref="EmitIndoorLight"/>):
/// <code>
/// [indoor-light] visibleCells=&lt;N&gt; pool=&lt;M&gt; cellLess=&lt;K&gt; registered=&lt;R&gt;
/// droppedNonVisible=&lt;R-M&gt; byCell=[0x&lt;id&gt;:&lt;count&gt;,...]
/// </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&gt;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>
/// 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";
// 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
@ -451,6 +478,66 @@ public static class RenderingDiagnostics
}
}
// Wall-clock rate-limit gate for EmitIndoorLight (shares the 1 s interval).
private static long _lastIndoorLightEmitTicks;
/// <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).
/// </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,
IReadOnlyList<AcDream.Core.Lighting.LightSource> allRegistered,
IReadOnlyList<AcDream.Core.Lighting.LightSource> scopedSnapshot)
{
if (!ProbeIndoorLightEnabled) return;
long now = DateTime.UtcNow.Ticks;
if (_lastIndoorLightEmitTicks != 0 && (now - _lastIndoorLightEmitTicks) < LightEmitIntervalTicks)
return;
_lastIndoorLightEmitTicks = now;
int registeredLitPoints = 0;
foreach (var l in allRegistered)
if (l.IsLit && l.Kind != AcDream.Core.Lighting.LightKind.Directional) registeredLitPoints++;
int pool = scopedSnapshot.Count;
int cellLess = 0;
var hist = new Dictionary<uint, int>();
foreach (var l in scopedSnapshot)
{
if (l.CellId == 0) cellLess++;
hist.TryGetValue(l.CellId, out var c);
hist[l.CellId] = c + 1;
}
var sb = new StringBuilder(220);
sb.Append("[indoor-light] visibleCells=").Append(visibleCellCount);
sb.Append(" 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);
sb.Append(" byCell=[");
const int MaxCells = 12;
int shown = 0;
foreach (var kv in hist)
{
if (shown >= MaxCells) { sb.Append(",..."); break; }
if (shown > 0) sb.Append(',');
sb.Append("0x").Append(kv.Key.ToString("X8")).Append(':').Append(kv.Value);
shown++;
}
sb.Append(']');
Console.WriteLine(sb.ToString());
}
private static bool _probeEnvCellEnabled =
Environment.GetEnvironmentVariable("ACDREAM_PROBE_ENVCELL") == "1";