#120: arm the propagation tripwire for self-attribution + two convergence regression pins
Investigation: retail's growth propagation RECURSES natively too (AddViewToPortals -> FixCellList -> AdjustCellView -> AddViewToPortals, Ghidra 0x005a52d0/0x005a5250/0x005a5770, no depth guard) - the in-place recursion shape is faithful; retail's safety is fast convergence. Our depth-128 firing means slow/non-saturating growth (each lap of a portal cycle nests one recursion level), not necessarily a true infinite loop. Two dat-backed sweeps over the corner-building cell set could NOT reproduce the T5 firing: - PortalPlaneCrossings_InPlacePropagationConverges: +/-6cm eye sweep across every portal plane, seeded from both sides. - InCellDirectionSweep_InPlacePropagationConverges: 3024 builds, in-cell eye grid x 8 yaw x 3 pitch (the walking-and-turning regime). Both pass with 0 firings -> production-only ingredients suspected (full lookup graph - one T5 firing was 0x0162, another building - and/or the real camera path). Armed: PortalVisibilityBuilder.ConvergenceTripwireCount (test observable, both Build + look-in sites) + DumpPropagationChain - on the next firing the log carries root cell, eye, per-cell frequency summary, and the 24-entry chain tail, so the cycle's structure (A<->B ping-pong vs 3-cycle laps) reads directly off the output. Both sweeps stay as regression pins. App tests: 227 green (was 225; +2 pins). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
af5d424df0
commit
2d15084243
3 changed files with 206 additions and 3 deletions
|
|
@ -61,6 +61,42 @@ public static class PortalVisibilityBuilder
|
|||
Environment.GetEnvironmentVariable("ACDREAM_A8_DUMP_PV") == "1";
|
||||
private static readonly Dictionary<uint, int> s_pvDumpCount = new();
|
||||
|
||||
/// <summary>
|
||||
/// #120 observable: total convergence-tripwire firings across both the
|
||||
/// interior <see cref="Build"/> and the exterior look-in propagation.
|
||||
/// The tripwire firing means the in-place growth's fixpoint invariant
|
||||
/// broke (T2/BR-4) — tests reset this and assert it stays 0.
|
||||
/// </summary>
|
||||
public static int ConvergenceTripwireCount;
|
||||
|
||||
/// <summary>
|
||||
/// #120 self-attribution dump: the growth-recursion path that exceeded
|
||||
/// the tripwire, as a per-cell frequency summary plus the chain tail —
|
||||
/// the cycle's structure (e.g. 0174↔0175 ping-pong vs a 3-cycle lap)
|
||||
/// reads directly off the output.
|
||||
/// </summary>
|
||||
private static void DumpPropagationChain(uint[] chain, int depth, uint rootCellId, Vector3 eye)
|
||||
{
|
||||
int n = Math.Min(depth, chain.Length);
|
||||
var freq = new Dictionary<uint, int>();
|
||||
for (int i = 0; i < n; i++)
|
||||
{
|
||||
freq.TryGetValue(chain[i], out int c);
|
||||
freq[chain[i]] = c + 1;
|
||||
}
|
||||
var summary = new System.Text.StringBuilder(256);
|
||||
foreach (var kvp in freq)
|
||||
summary.Append(System.FormattableString.Invariant($" 0x{kvp.Key:X8}x{kvp.Value}"));
|
||||
|
||||
var tail = new System.Text.StringBuilder(256);
|
||||
for (int i = Math.Max(0, n - 24); i < n; i++)
|
||||
tail.Append(System.FormattableString.Invariant($" 0x{chain[i] & 0xFFFFu:X4}"));
|
||||
|
||||
Console.WriteLine(System.FormattableString.Invariant(
|
||||
$"[pv-ERROR] chain root=0x{rootCellId:X8} eye=({eye.X:F3},{eye.Y:F3},{eye.Z:F3}) cells:{summary}"));
|
||||
Console.WriteLine($"[pv-ERROR] chain tail(24):{tail}");
|
||||
}
|
||||
|
||||
/// <param name="lookup">Resolve a full cell id to its LoadedCell, or null if not loaded.</param>
|
||||
/// <param name="buildingMembership">Optional: true if a cell id is in the camera building's cell
|
||||
/// set. When provided, a neighbour OUTSIDE the set routes to CrossBuildingViews instead of
|
||||
|
|
@ -167,14 +203,24 @@ public static class PortalVisibilityBuilder
|
|||
// is a loud failsafe, not control flow: it firing means the convergence
|
||||
// invariant broke and must be fixed, not tuned.
|
||||
const int RecursionTripwire = 128;
|
||||
// #120 self-attribution: the recursion path (cell id per depth), so a
|
||||
// tripwire firing names the growth CYCLE instead of just the tip.
|
||||
// Harness sweeps (CornerFloodReplayTests *Converges tests) could not
|
||||
// reproduce the T5 firing — production-only ingredients (full lookup
|
||||
// graph / real camera path) are suspected; this dump pins them on the
|
||||
// next natural occurrence.
|
||||
var propagationChain = new uint[RecursionTripwire];
|
||||
|
||||
void ProcessCellPortals(LoadedCell cell, int depth)
|
||||
{
|
||||
if (depth >= RecursionTripwire)
|
||||
{
|
||||
System.Threading.Interlocked.Increment(ref ConvergenceTripwireCount);
|
||||
Console.WriteLine($"[pv-ERROR] in-place propagation tripwire at depth {depth} on cell=0x{cell.CellId:X8} — convergence invariant broken, investigate");
|
||||
DumpPropagationChain(propagationChain, depth, cameraCell.CellId, cameraPos);
|
||||
return;
|
||||
}
|
||||
propagationChain[depth] = cell.CellId;
|
||||
if (!frame.CellViews.TryGetValue(cell.CellId, out var currentView) || currentView.IsEmpty)
|
||||
{
|
||||
trace?.Add($"proc cell=0x{cell.CellId:X8} skip=no-view");
|
||||
|
|
@ -496,14 +542,18 @@ public static class PortalVisibilityBuilder
|
|||
// re-enqueue + MaxReprocessPerCell cap and the eye-in-opening rescues
|
||||
// are deleted (empty clip culls, period).
|
||||
const int RecursionTripwire = 128;
|
||||
var propagationChain = new uint[RecursionTripwire]; // #120 self-attribution — see Build()
|
||||
|
||||
void ProcessCellPortals(LoadedCell cell, int depth)
|
||||
{
|
||||
if (depth >= RecursionTripwire)
|
||||
{
|
||||
System.Threading.Interlocked.Increment(ref ConvergenceTripwireCount);
|
||||
Console.WriteLine($"[pv-ERROR] look-in in-place propagation tripwire at depth {depth} on cell=0x{cell.CellId:X8} — convergence invariant broken, investigate");
|
||||
DumpPropagationChain(propagationChain, depth, 0u, cameraPos);
|
||||
return;
|
||||
}
|
||||
propagationChain[depth] = cell.CellId;
|
||||
if (!frame.CellViews.TryGetValue(cell.CellId, out var currentView) || currentView.IsEmpty)
|
||||
return;
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue