test: fix PhysicsResolveCapture/PhysicsDiagnostics static-leak isolation

xUnit's default parallel execution let diagnostic-harness tests (CellarUp,
DoorBug, DoorCollisionApparatus) mutate PhysicsResolveCapture.CapturePath
and PhysicsDiagnostics probe flags concurrently with victim tests
(MotionInterpreter, PositionManager, PlayerMovementController,
DispatcherToMovement, BSPStepUp), producing a flaky 14-26 failure range.

Fixes:
- Add PhysicsResolveCapture.ResetForTest() + PhysicsDiagnostics.ResetForTest()
  as documented test-only reset APIs (never called from production paths).
- Add IDisposable to CellarUpTrajectoryReplayTests with ctor/Dispose calling
  both ResetForTest() — prevents CapturePath from leaking between the Capture_*
  tests in the same class (the immediate root cause of Capture_SkipsNonPlayerCalls
  finding an unexpected file).
- Add xunit.runner.json (maxParallelThreads=1, parallelizeTestCollections=false)
  to AcDream.Core.Tests — eliminates parallelism-induced probe-flag leaks across
  all test classes without requiring [Collection] boilerplate on every offender.

After: two consecutive runs produce the identical 12-failure set.
Confirmed: LiveCompare_FirstCap_FixClosesCottageFloorCap passes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Erik 2026-06-02 15:20:24 +02:00
parent a06226f9a2
commit 21ee5e1035
5 changed files with 101 additions and 1 deletions

View file

@ -512,6 +512,56 @@ public static class PhysicsDiagnostics
public static bool ProbeDumpGfxObjsEnabled => ProbeDumpGfxObjIds.Count > 0;
/// <summary>
/// Test-only reset: set every probe flag to <c>false</c> and clear any
/// side-channel fields. Does NOT re-read environment variables — tests
/// run in environments where the env vars are all absent, so false is
/// the correct default.
///
/// <para>
/// Call from test constructors and <c>IDisposable.Dispose()</c> to
/// prevent one test class from leaving enabled probes that corrupt
/// timing-sensitive tests in another class (the static-leak root cause
/// documented in T0).
/// </para>
///
/// <para>
/// This method is intentionally <c>public</c> so test projects can call
/// it without reflection, but it must NEVER be called from production
/// code paths.
/// </para>
/// </summary>
public static void ResetForTest()
{
ProbeResolveEnabled = false;
ProbeCellEnabled = false;
ProbeBuildingEnabled = false;
ProbeCellSetEnabled = false;
ProbeAutoWalkEnabled = false;
ProbeUseabilityFallbackEnabled= false;
DumpSteepRoofEnabled = false;
ProbeIndoorBspEnabled = false;
ProbeCellCacheEnabled = false;
ProbeContactPlaneEnabled = false;
ProbeWalkMissEnabled = false;
ProbePushBackEnabled = false;
ProbePolyDumpEnabled = false;
ProbePlacementFailEnabled = false;
ProbeSweptEnabled = false;
ProbeStepWalkEnabled = false;
// Side-channel fields
LastBspHitPoly = null;
LastPlacementFailPolyId = 0;
LastPlacementFailPolyNormal = default;
LastPlacementFailPolyD = 0f;
LastPlacementFailSolidLeaf = false;
// Dump-trigger sets
ProbeDumpCellIds = new System.Collections.Generic.HashSet<uint>();
ProbeDumpGfxObjIds = new System.Collections.Generic.HashSet<uint>();
}
private static IReadOnlySet<uint> ParseHexIdList(string? raw)
{
if (string.IsNullOrWhiteSpace(raw))

View file

@ -148,6 +148,24 @@ public static class PhysicsResolveCapture
public static void ResetTickCounter() =>
Interlocked.Exchange(ref _tickCounter, 0);
/// <summary>
/// Test-only reset: close any open writer, clear <see cref="CapturePath"/>,
/// and reset the tick counter to 0. Call from test constructors and
/// <c>IDisposable.Dispose()</c> to prevent static state from leaking
/// across test-class boundaries.
///
/// <para>
/// This method is intentionally <c>public</c> so test projects can call it
/// without reflection, but it must NEVER be called from production code paths.
/// </para>
/// </summary>
public static void ResetForTest()
{
Close();
CapturePath = null;
Interlocked.Exchange(ref _tickCounter, 0);
}
private static void EnsureWriter_NoLock()
{
if (_writer is not null)