diag #181: root-cause chain complete - flapping cell 0x0181, wall-press excitation, and the REAL defect: camera-flood-scoped light application
Issue181VisFlapReplayTests: headless flood replay at the live parked pose names cell 0x8A020181 - +-0.5mm eye perturbations flip its admission (at yaw15/pitch-20 for every direction). Issue181CameraParkStabilityTests: the camera loop parks BIT-EXACT with static inputs (0.00um over 2000 frames) - the live ~1mm/frame wander is the wall-press equilibrium (sought steps a*gap ~4mm into the wall per frame, the clip lands within adjust_to_plane's parametric 0.02 window; player physics bit-frozen per [resolve]). That wobble is retail-class. The defect is OURS: the A7 adaptation scopes light application to the camera flood's per-frame admission, so a sliver cell flapping flips whole lit regions; retail computes light reach once at registration (Render::add_static_light -> CObjCell::add_lights), camera-independent. Fix direction: registration-time per-light reach sets via the portal graph (pseudocode docs/research/2026-07-06-a7-per-cell-lighting-pseudocode.md). ISSUES #181 updated with the full chain. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
d9a4ca2355
commit
864e4d9e75
3 changed files with 233 additions and 7 deletions
|
|
@ -0,0 +1,100 @@
|
|||
using System;
|
||||
using System.Numerics;
|
||||
using AcDream.App.Rendering;
|
||||
using AcDream.Core.Rendering;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
namespace AcDream.App.Tests.Rendering;
|
||||
|
||||
/// <summary>
|
||||
/// #181 excitation probe — the live parked camera wanders ~0.9 mm/frame
|
||||
/// (launch-176-leakfix.log [flap-sweep]: 19,889 distinct sought values in 20k
|
||||
/// parked sweeps), which keeps the portal flood's knife-edge admissions
|
||||
/// flapping. Retail's parked viewer is a bit-exact fixed point (UpdateCamera
|
||||
/// dead-band `return viewer`).
|
||||
///
|
||||
/// This test drives RetailChaseCamera.Update with BIT-IDENTICAL inputs at the
|
||||
/// live frame rate: if the camera parks bit-stable here, the live wobble comes
|
||||
/// from its INPUTS (player position/yaw jitter out of GameWindow); if it
|
||||
/// wobbles here, the camera loop itself fails to reach the fixed point.
|
||||
/// </summary>
|
||||
public class Issue181CameraParkStabilityTests
|
||||
{
|
||||
private readonly ITestOutputHelper _out;
|
||||
public Issue181CameraParkStabilityTests(ITestOutputHelper output) => _out = output;
|
||||
|
||||
private sealed class PassthroughProbe : ICameraCollisionProbe
|
||||
{
|
||||
public CameraSweepResult SweepEye(Vector3 pivot, Vector3 desiredEye, uint cellId, uint selfEntityId, Vector3 playerPos)
|
||||
=> new(desiredEye, cellId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParkedCamera_StaticInputs_ReachesBitStableFixedPoint()
|
||||
{
|
||||
bool savedAlign = CameraDiagnostics.AlignToSlope;
|
||||
bool savedColl = CameraDiagnostics.CollideCamera;
|
||||
float savedT = CameraDiagnostics.TranslationStiffness;
|
||||
float savedR = CameraDiagnostics.RotationStiffness;
|
||||
try
|
||||
{
|
||||
CameraDiagnostics.AlignToSlope = true; // production default
|
||||
CameraDiagnostics.CollideCamera = true;
|
||||
CameraDiagnostics.TranslationStiffness = 0.45f;
|
||||
CameraDiagnostics.RotationStiffness = 0.45f;
|
||||
|
||||
var cam = new RetailChaseCamera { CollisionProbe = new PassthroughProbe() };
|
||||
|
||||
// Live-like parked pose: static player, static yaw, zero velocity,
|
||||
// grounded on a flat contact plane, ~1500 fps.
|
||||
var playerPos = new Vector3(49.5f, -39.9f, -5.9f);
|
||||
float yaw = 1.83f;
|
||||
float dt = 1f / 1500f;
|
||||
|
||||
void Step() => cam.Update(
|
||||
playerPosition: playerPos,
|
||||
playerYaw: yaw,
|
||||
playerVelocity: Vector3.Zero,
|
||||
isOnGround: true,
|
||||
contactPlaneNormal: Vector3.UnitZ,
|
||||
dt: dt,
|
||||
cellId: 0x8A020142u,
|
||||
selfEntityId: 0x5);
|
||||
|
||||
// Converge: at α≈0.003/frame the boom needs a few thousand frames
|
||||
// to settle from the init pose into the dead-band.
|
||||
for (int i = 0; i < 20000; i++) Step();
|
||||
|
||||
Vector3 a = cam.Position;
|
||||
var fwdA = cam.View; // full view matrix — includes the forward half
|
||||
|
||||
// 2000 further frames with bit-identical inputs: every one must be
|
||||
// the exact fixed point (retail parks verbatim).
|
||||
float maxDelta = 0f;
|
||||
Vector3 prev = a;
|
||||
bool viewChanged = false;
|
||||
for (int i = 0; i < 2000; i++)
|
||||
{
|
||||
Step();
|
||||
maxDelta = MathF.Max(maxDelta, Vector3.Distance(cam.Position, prev));
|
||||
prev = cam.Position;
|
||||
if (cam.View != fwdA) viewChanged = true;
|
||||
}
|
||||
|
||||
_out.WriteLine(FormattableString.Invariant(
|
||||
$"post-convergence maxConsecDelta={maxDelta * 1e6f:F2}um viewChanged={viewChanged} pos=({cam.Position.X:F7},{cam.Position.Y:F7},{cam.Position.Z:F7})"));
|
||||
|
||||
Assert.True(maxDelta == 0f,
|
||||
$"parked camera must be a bit-exact fixed point, wandered up to {maxDelta * 1e6f:F1}um/frame");
|
||||
Assert.False(viewChanged, "view matrix must be frozen at park");
|
||||
}
|
||||
finally
|
||||
{
|
||||
CameraDiagnostics.AlignToSlope = savedAlign;
|
||||
CameraDiagnostics.CollideCamera = savedColl;
|
||||
CameraDiagnostics.TranslationStiffness = savedT;
|
||||
CameraDiagnostics.RotationStiffness = savedR;
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue