diff --git a/src/AcDream.Core/Physics/PhysicsDiagnostics.cs b/src/AcDream.Core/Physics/PhysicsDiagnostics.cs
index 43a1a8d..06a3180 100644
--- a/src/AcDream.Core/Physics/PhysicsDiagnostics.cs
+++ b/src/AcDream.Core/Physics/PhysicsDiagnostics.cs
@@ -512,6 +512,56 @@ public static class PhysicsDiagnostics
public static bool ProbeDumpGfxObjsEnabled => ProbeDumpGfxObjIds.Count > 0;
+ ///
+ /// Test-only reset: set every probe flag to false 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.
+ ///
+ ///
+ /// Call from test constructors and IDisposable.Dispose() 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).
+ ///
+ ///
+ ///
+ /// This method is intentionally public so test projects can call
+ /// it without reflection, but it must NEVER be called from production
+ /// code paths.
+ ///
+ ///
+ 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();
+ ProbeDumpGfxObjIds = new System.Collections.Generic.HashSet();
+ }
+
private static IReadOnlySet ParseHexIdList(string? raw)
{
if (string.IsNullOrWhiteSpace(raw))
diff --git a/src/AcDream.Core/Physics/PhysicsResolveCapture.cs b/src/AcDream.Core/Physics/PhysicsResolveCapture.cs
index ad514cd..69f508d 100644
--- a/src/AcDream.Core/Physics/PhysicsResolveCapture.cs
+++ b/src/AcDream.Core/Physics/PhysicsResolveCapture.cs
@@ -148,6 +148,24 @@ public static class PhysicsResolveCapture
public static void ResetTickCounter() =>
Interlocked.Exchange(ref _tickCounter, 0);
+ ///
+ /// Test-only reset: close any open writer, clear ,
+ /// and reset the tick counter to 0. Call from test constructors and
+ /// IDisposable.Dispose() to prevent static state from leaking
+ /// across test-class boundaries.
+ ///
+ ///
+ /// This method is intentionally public so test projects can call it
+ /// without reflection, but it must NEVER be called from production code paths.
+ ///
+ ///
+ public static void ResetForTest()
+ {
+ Close();
+ CapturePath = null;
+ Interlocked.Exchange(ref _tickCounter, 0);
+ }
+
private static void EnsureWriter_NoLock()
{
if (_writer is not null)
diff --git a/tests/AcDream.Core.Tests/AcDream.Core.Tests.csproj b/tests/AcDream.Core.Tests/AcDream.Core.Tests.csproj
index 1c83fc3..f848e8a 100644
--- a/tests/AcDream.Core.Tests/AcDream.Core.Tests.csproj
+++ b/tests/AcDream.Core.Tests/AcDream.Core.Tests.csproj
@@ -18,6 +18,15 @@
+
+
+
+
+
diff --git a/tests/AcDream.Core.Tests/Physics/CellarUpTrajectoryReplayTests.cs b/tests/AcDream.Core.Tests/Physics/CellarUpTrajectoryReplayTests.cs
index a409bb1..0054a82 100644
--- a/tests/AcDream.Core.Tests/Physics/CellarUpTrajectoryReplayTests.cs
+++ b/tests/AcDream.Core.Tests/Physics/CellarUpTrajectoryReplayTests.cs
@@ -80,8 +80,25 @@ namespace AcDream.Core.Tests.Physics;
/// trajectory shape.
///
///
-public class CellarUpTrajectoryReplayTests
+public class CellarUpTrajectoryReplayTests : IDisposable
{
+ // ── T0: constructor + Dispose reset PhysicsResolveCapture static ────────
+ // xUnit creates a fresh instance per test, so the constructor runs before
+ // each test and Dispose runs after. Resetting here guarantees that no
+ // prior test in this class can leave CapturePath set (which would cause
+ // Capture_SkipsNonPlayerCalls to find an unexpected file).
+ public CellarUpTrajectoryReplayTests()
+ {
+ PhysicsResolveCapture.ResetForTest();
+ PhysicsDiagnostics.ResetForTest();
+ }
+
+ public void Dispose()
+ {
+ PhysicsResolveCapture.ResetForTest();
+ PhysicsDiagnostics.ResetForTest();
+ }
+
// ── Cellar / cottage geometry constants ────────────────────────
private const uint CellarId = 0xA9B40147u;
private const uint CottageNeighborA = 0xA9B40143u;
diff --git a/tests/AcDream.Core.Tests/xunit.runner.json b/tests/AcDream.Core.Tests/xunit.runner.json
new file mode 100644
index 0000000..c315589
--- /dev/null
+++ b/tests/AcDream.Core.Tests/xunit.runner.json
@@ -0,0 +1,6 @@
+{
+ "$schema": "https://xunit.net/schema/current/xunit.runner.schema.json",
+ "parallelizeAssembly": false,
+ "parallelizeTestCollections": false,
+ "maxParallelThreads": 1
+}