diff --git a/tests/AcDream.Core.Tests/Fixtures/issue98/live-capture.jsonl b/tests/AcDream.Core.Tests/Fixtures/issue98/live-capture.jsonl
new file mode 100644
index 0000000..70f1583
--- /dev/null
+++ b/tests/AcDream.Core.Tests/Fixtures/issue98/live-capture.jsonl
@@ -0,0 +1,3 @@
+{"tick":0,"timestampMs":40919993,"input":{"currentPos":{"x":140.93564,"y":7.424385,"z":92.5333},"targetPos":{"x":140.93564,"y":7.424385,"z":92.5333},"cellId":2847146311,"sphereRadius":0.48,"sphereHeight":1.2,"stepUpHeight":0.6,"stepDownHeight":1.5,"isOnGround":true,"moverFlags":768,"movingEntityId":1000000},"bodyBefore":{"position":{"x":140.93564,"y":7.424385,"z":92.5333},"orientation":{"isIdentity":false,"x":-0,"y":-0,"z":-0.027161535,"w":0.99963105},"velocity":{"x":0,"y":0,"z":0},"acceleration":{"x":0,"y":0,"z":0},"omega":{"x":0,"y":0,"z":0},"groundNormal":{"x":0,"y":0,"z":1},"slidingNormal":{"x":0,"y":0,"z":0},"contactPlaneValid":false,"contactPlane":{"normal":{"x":0,"y":0,"z":0},"d":0},"contactPlaneCellId":0,"contactPlaneIsWater":false,"walkablePolygonValid":false,"walkablePlane":{"normal":{"x":0,"y":0,"z":0},"d":0},"walkableVertices":null,"walkableUp":{"x":0,"y":0,"z":1},"elasticity":0.05,"friction":0.95,"state":1040,"transientState":131,"lastUpdateTime":0},"result":{"position":{"x":140.93564,"y":7.424385,"z":92.5333},"cellId":2847146311,"isOnGround":true,"collisionNormalValid":false,"collisionNormal":{"x":0,"y":0,"z":0}},"bodyAfter":{"position":{"x":140.93564,"y":7.424385,"z":92.5333},"orientation":{"isIdentity":false,"x":-0,"y":-0,"z":-0.027161535,"w":0.99963105},"velocity":{"x":0,"y":0,"z":0},"acceleration":{"x":0,"y":0,"z":0},"omega":{"x":0,"y":0,"z":0},"groundNormal":{"x":0,"y":0,"z":1},"slidingNormal":{"x":0,"y":0,"z":0},"contactPlaneValid":false,"contactPlane":{"normal":{"x":0,"y":0,"z":0},"d":0},"contactPlaneCellId":0,"contactPlaneIsWater":false,"walkablePolygonValid":false,"walkablePlane":{"normal":{"x":0,"y":0,"z":0},"d":0},"walkableVertices":null,"walkableUp":{"x":0,"y":0,"z":1},"elasticity":0.05,"friction":0.95,"state":1040,"transientState":131,"lastUpdateTime":0}}
+{"tick":376,"timestampMs":40923854,"input":{"currentPos":{"x":141.02515,"y":8.432315,"z":91.48935},"targetPos":{"x":141.02515,"y":8.432315,"z":91.48935},"cellId":2847146311,"sphereRadius":0.48,"sphereHeight":1.2,"stepUpHeight":0.6,"stepDownHeight":1.5,"isOnGround":true,"moverFlags":768,"movingEntityId":1000000},"bodyBefore":{"position":{"x":141.02515,"y":8.432315,"z":91.48935},"orientation":{"isIdentity":false,"x":-0,"y":-0,"z":-0.027161535,"w":0.99963105},"velocity":{"x":0.64338976,"y":11.830655,"z":0},"acceleration":{"x":0,"y":0,"z":0},"omega":{"x":0,"y":0,"z":0},"groundNormal":{"x":0,"y":0,"z":1},"slidingNormal":{"x":0,"y":0,"z":0},"contactPlaneValid":true,"contactPlane":{"normal":{"x":6.285598E-08,"y":0.7189884,"z":0.69502217},"d":-69.50349},"contactPlaneCellId":2847146311,"contactPlaneIsWater":false,"walkablePolygonValid":true,"walkablePlane":{"normal":{"x":6.285598E-08,"y":0.7189884,"z":0.69502217},"d":-69.50349},"walkableVertices":[{"x":140.5,"y":8.70195,"z":90.999794},{"x":140.5,"y":5.8019505,"z":93.999794},{"x":142.1,"y":5.8019505,"z":93.999794},{"x":142.1,"y":8.70195,"z":90.999794}],"walkableUp":{"x":0,"y":0,"z":1},"elasticity":0.05,"friction":0.95,"state":1040,"transientState":131,"lastUpdateTime":0},"result":{"position":{"x":141.02515,"y":8.432315,"z":91.48935},"cellId":2847146311,"isOnGround":true,"collisionNormalValid":false,"collisionNormal":{"x":0,"y":0,"z":0}},"bodyAfter":{"position":{"x":141.02515,"y":8.432315,"z":91.48935},"orientation":{"isIdentity":false,"x":-0,"y":-0,"z":-0.027161535,"w":0.99963105},"velocity":{"x":0.64338976,"y":11.830655,"z":0},"acceleration":{"x":0,"y":0,"z":0},"omega":{"x":0,"y":0,"z":0},"groundNormal":{"x":0,"y":0,"z":1},"slidingNormal":{"x":0,"y":0,"z":0},"contactPlaneValid":true,"contactPlane":{"normal":{"x":6.285598E-08,"y":0.7189884,"z":0.69502217},"d":-69.50349},"contactPlaneCellId":2847146311,"contactPlaneIsWater":false,"walkablePolygonValid":true,"walkablePlane":{"normal":{"x":6.285598E-08,"y":0.7189884,"z":0.69502217},"d":-69.50349},"walkableVertices":[{"x":140.5,"y":8.70195,"z":90.999794},{"x":140.5,"y":5.8019505,"z":93.999794},{"x":142.1,"y":5.8019505,"z":93.999794},{"x":142.1,"y":8.70195,"z":90.999794}],"walkableUp":{"x":0,"y":0,"z":1},"elasticity":0.05,"friction":0.95,"state":1040,"transientState":131,"lastUpdateTime":0}}
+{"tick":1183,"timestampMs":40927362,"input":{"currentPos":{"x":141.35991,"y":7.224303,"z":92.73901},"targetPos":{"x":141.38649,"y":6.822069,"z":92.73901},"cellId":2847146311,"sphereRadius":0.48,"sphereHeight":1.2,"stepUpHeight":0.6,"stepDownHeight":1.5,"isOnGround":true,"moverFlags":768,"movingEntityId":1000000},"bodyBefore":{"position":{"x":141.38649,"y":6.822069,"z":92.73901},"orientation":{"isIdentity":false,"x":-0,"y":-0,"z":-0.999456,"w":0.032981448},"velocity":{"x":0.7811123,"y":-11.822362,"z":0},"acceleration":{"x":0,"y":0,"z":0},"omega":{"x":0,"y":0,"z":0},"groundNormal":{"x":0,"y":0,"z":1},"slidingNormal":{"x":0,"y":0,"z":0},"contactPlaneValid":true,"contactPlane":{"normal":{"x":6.285598E-08,"y":0.7189884,"z":0.69502217},"d":-69.50349},"contactPlaneCellId":2847146311,"contactPlaneIsWater":false,"walkablePolygonValid":true,"walkablePlane":{"normal":{"x":6.285598E-08,"y":0.7189884,"z":0.69502217},"d":-69.50349},"walkableVertices":[{"x":140.5,"y":8.70195,"z":90.999794},{"x":140.5,"y":5.8019505,"z":93.999794},{"x":142.1,"y":5.8019505,"z":93.999794},{"x":142.1,"y":8.70195,"z":90.999794}],"walkableUp":{"x":0,"y":0,"z":1},"elasticity":0.05,"friction":0.95,"state":1040,"transientState":131,"lastUpdateTime":0},"result":{"position":{"x":141.38649,"y":7.224303,"z":92.73901},"cellId":2847146311,"isOnGround":true,"collisionNormalValid":true,"collisionNormal":{"x":0,"y":0,"z":-1}},"bodyAfter":{"position":{"x":141.38649,"y":6.822069,"z":92.73901},"orientation":{"isIdentity":false,"x":-0,"y":-0,"z":-0.999456,"w":0.032981448},"velocity":{"x":0.7811123,"y":-11.822362,"z":0},"acceleration":{"x":0,"y":0,"z":0},"omega":{"x":0,"y":0,"z":0},"groundNormal":{"x":0,"y":0,"z":1},"slidingNormal":{"x":0,"y":0,"z":0},"contactPlaneValid":true,"contactPlane":{"normal":{"x":6.285598E-08,"y":0.7189884,"z":0.69502217},"d":-69.50349},"contactPlaneCellId":2847146311,"contactPlaneIsWater":false,"walkablePolygonValid":true,"walkablePlane":{"normal":{"x":6.285598E-08,"y":0.7189884,"z":0.69502217},"d":-69.50349},"walkableVertices":[{"x":140.5,"y":8.70195,"z":90.999794},{"x":140.5,"y":5.8019505,"z":93.999794},{"x":142.1,"y":5.8019505,"z":93.999794},{"x":142.1,"y":8.70195,"z":90.999794}],"walkableUp":{"x":0,"y":0,"z":1},"elasticity":0.05,"friction":0.95,"state":1040,"transientState":131,"lastUpdateTime":0}}
diff --git a/tests/AcDream.Core.Tests/Physics/CellarUpTrajectoryReplayTests.cs b/tests/AcDream.Core.Tests/Physics/CellarUpTrajectoryReplayTests.cs
index 346980a..90df2dd 100644
--- a/tests/AcDream.Core.Tests/Physics/CellarUpTrajectoryReplayTests.cs
+++ b/tests/AcDream.Core.Tests/Physics/CellarUpTrajectoryReplayTests.cs
@@ -440,6 +440,331 @@ public class CellarUpTrajectoryReplayTests
PropertyNamingPolicy = System.Text.Json.JsonNamingPolicy.CamelCase,
};
+ // ───────────────────────────────────────────────────────────────
+ // A6.P3 #98 (2026-05-23 PM extension) — live-vs-harness comparison.
+ // Loads the 3-record fixture sampled from a live capture in the
+ // Holtburg cottage cellar and replays each through the harness's
+ // PhysicsEngine. Each test compares one record's outputs (result +
+ // body-after) to what the live engine produced, reporting the FIRST
+ // per-field divergence. The divergence pinpoints what world state
+ // the harness lacks vs production, ending the speculation loop that
+ // burned 6 hypotheses on the airborne-at-tick-1 bug.
+ // ───────────────────────────────────────────────────────────────
+
+ ///
+ /// Tick 0 — spawn/login teleport into the cellar at world Z=92.5333.
+ /// No velocity, no contact-plane seed; currentPos == targetPos (no
+ /// motion). The simplest test case: replay the call and verify the
+ /// harness produces the same ResolveResult + bodyAfter state.
+ ///
+ [Fact]
+ public void LiveCompare_Tick0_Spawn()
+ {
+ var (engine, cache) = BuildEngineWithCellarFixtures();
+ var captured = LoadCapturedRecord(record => record.Tick == 0);
+ AssertCallMatchesCapture(engine, captured);
+ }
+
+ ///
+ /// Tick 376 — player on the cellar ramp at world Z=91.49. Live capture
+ /// has bodyAfter.WalkablePolygon = the ramp polygon (normal ≈
+ /// (0, 0.719, 0.695), z range 90.99→94.00). If the harness reproduces
+ /// the same walkable polygon + ResolveResult, the ramp geometry is
+ /// loaded correctly.
+ ///
+ [Fact]
+ public void LiveCompare_Tick376_OnRamp()
+ {
+ var (engine, _) = BuildEngineWithCellarFixtures();
+ var captured = LoadCapturedRecord(record => record.Tick == 376);
+ AssertCallMatchesCapture(engine, captured);
+ }
+
+ ///
+ /// First-cap event — the failing tick. Live engine reports cn=(0,0,-1),
+ /// a downward-facing collision normal, capping the foot sphere at
+ /// world Z=92.74. Math: head sphere TOP reaches Z=94.0 (the cottage
+ /// floor) when foot Z = 94.0 - sphereHeight = 92.80. So the head is
+ /// bumping the cottage floor from BELOW.
+ ///
+ ///
+ /// This is the actual #98 bug, NOT a step-up / AdjustOffset problem
+ /// — it's a head-sphere collision against a polygon that retail
+ /// doesn't have (cottage floor should be punched-through above the
+ /// ramp). Whether the harness reproduces the cap pinpoints whether
+ /// the cottage-cell floor polygon set is the cause.
+ ///
+ ///
+ [Fact]
+ public void LiveCompare_FirstCap_HeadHitsCottageFloor()
+ {
+ var (engine, _) = BuildEngineWithCellarFixtures();
+ var captured = LoadCapturedRecord(record =>
+ record.Result.CollisionNormalValid
+ && record.Result.CollisionNormal.Z < -0.99f);
+ AssertCallMatchesCapture(engine, captured);
+ }
+
+ ///
+ /// Diagnostic dump: turns on every relevant probe and replays the
+ /// first-cap record, so the captured stdout shows which polygon the
+ /// harness BSP hit when it computed cn=(0,0,+1) — pinpoints the
+ /// missing fixture cell or the wrong-winding-order polygon.
+ /// Always passes; this is a one-shot tool, not a regression.
+ ///
+ [Fact]
+ public void LiveCompare_FirstCap_DiagnosticDump()
+ {
+ PhysicsDiagnostics.ProbeResolveEnabled = true;
+ PhysicsDiagnostics.ProbeIndoorBspEnabled = true;
+ PhysicsDiagnostics.ProbePolyDumpEnabled = true;
+ PhysicsDiagnostics.ProbePushBackEnabled = true;
+ PhysicsDiagnostics.ProbeStepWalkEnabled = true;
+ try
+ {
+ var (engine, cache) = BuildEngineWithCellarFixtures();
+
+ // Dump the cellar cell's polygons so we can see what BSP is
+ // testing against. The harness hit cn=(0,0,+1) — find which
+ // polygon has that normal.
+ DumpCellPolygons(cache, CellarId);
+ DumpCellPolygons(cache, CottageNeighborA);
+ DumpCellPolygons(cache, CottageNeighborB);
+
+ var captured = LoadCapturedRecord(record =>
+ record.Result.CollisionNormalValid
+ && record.Result.CollisionNormal.Z < -0.99f);
+ var body = SeedBodyFromSnapshot(captured.BodyBefore!);
+
+ Console.WriteLine($"=== Replay tick {captured.Tick} ===");
+ var result = engine.ResolveWithTransition(
+ currentPos: captured.Input.CurrentPos,
+ targetPos: captured.Input.TargetPos,
+ cellId: captured.Input.CellId,
+ sphereRadius: captured.Input.SphereRadius,
+ sphereHeight: captured.Input.SphereHeight,
+ stepUpHeight: captured.Input.StepUpHeight,
+ stepDownHeight: captured.Input.StepDownHeight,
+ isOnGround: captured.Input.IsOnGround,
+ body: body,
+ moverFlags: (ObjectInfoState)captured.Input.MoverFlags,
+ movingEntityId: captured.Input.MovingEntityId);
+
+ Console.WriteLine(
+ $"=== Result pos=({result.Position.X:F4},{result.Position.Y:F4},{result.Position.Z:F4}) " +
+ $"cn=({result.CollisionNormal.X:F4},{result.CollisionNormal.Y:F4},{result.CollisionNormal.Z:F4}) " +
+ $"cnValid={result.CollisionNormalValid} onGround={result.IsOnGround}");
+ }
+ finally
+ {
+ PhysicsDiagnostics.ProbeResolveEnabled = false;
+ PhysicsDiagnostics.ProbeIndoorBspEnabled = false;
+ PhysicsDiagnostics.ProbePolyDumpEnabled = false;
+ PhysicsDiagnostics.ProbePushBackEnabled = false;
+ PhysicsDiagnostics.ProbeStepWalkEnabled = false;
+ }
+ }
+
+ private static void DumpCellPolygons(PhysicsDataCache cache, uint cellId)
+ {
+ var cell = cache.GetCellStruct(cellId);
+ if (cell is null)
+ {
+ Console.WriteLine($"[cell-dump] 0x{cellId:X8} NOT IN CACHE");
+ return;
+ }
+ var t = cell.WorldTransform;
+ Console.WriteLine($"[cell-dump] 0x{cellId:X8} resolved-poly-count={cell.Resolved.Count}");
+ Console.WriteLine($" WorldTransform.M14={t.M14:F4} M24={t.M24:F4} M34={t.M34:F4} (origin XYZ?)");
+ Console.WriteLine($" Translation=({t.Translation.X:F4},{t.Translation.Y:F4},{t.Translation.Z:F4})");
+ foreach (var kv in cell.Resolved)
+ {
+ var p = kv.Value;
+ // Show world-frame vertices for the first 2 polys with normal-Z>0.9
+ // (floor candidates) — these are the polygons the head sphere
+ // could hit from below.
+ string vertsWorld = "";
+ if (p.Plane.Normal.Z > 0.9f || p.Plane.Normal.Z < -0.9f)
+ {
+ vertsWorld = " worldVerts=[" + string.Join(",", p.Vertices.Select(v =>
+ {
+ var w = Vector3.Transform(v, cell.WorldTransform);
+ return $"({w.X:F2},{w.Y:F2},{w.Z:F2})";
+ })) + "]";
+ }
+ Console.WriteLine(
+ $" poly id=0x{p.Id:X4} sides={p.SidesType} n=({p.Plane.Normal.X:F4},{p.Plane.Normal.Y:F4},{p.Plane.Normal.Z:F4}) d={p.Plane.D:F4} numV={p.NumPoints}{vertsWorld}");
+ }
+ }
+
+ ///
+ /// Reads the live-capture.jsonl fixture and returns the FIRST record
+ /// matching . Throws with a clear error
+ /// when none match — keeps the test failure attributed to the
+ /// fixture, not to deserialization.
+ ///
+ private static ResolveCaptureRecord LoadCapturedRecord(
+ Func predicate)
+ {
+ var path = Path.Combine(FixtureDir, "live-capture.jsonl");
+ Assert.True(File.Exists(path),
+ $"Live-capture fixture missing: {path}. Re-run live capture " +
+ $"with ACDREAM_CAPTURE_RESOLVE set.");
+
+ foreach (var line in File.ReadLines(path))
+ {
+ if (string.IsNullOrWhiteSpace(line))
+ continue;
+
+ var record = System.Text.Json.JsonSerializer
+ .Deserialize(line, CaptureJsonOptions)!;
+ if (predicate(record))
+ return record;
+ }
+
+ throw new Xunit.Sdk.XunitException(
+ "No captured record matched the predicate. Update the fixture " +
+ "to include a representative record.");
+ }
+
+ ///
+ /// Replays one captured ResolveWithTransition call through the harness
+ /// engine, seeded with the captured body-before state, and compares
+ /// the harness's ResolveResult + body-after vs the captured values.
+ /// Reports the FIRST per-field divergence with both values so the
+ /// missing apparatus state is named.
+ ///
+ private static void AssertCallMatchesCapture(
+ PhysicsEngine engine,
+ ResolveCaptureRecord captured)
+ {
+ Assert.NotNull(captured.BodyBefore);
+ Assert.NotNull(captured.BodyAfter);
+
+ var body = SeedBodyFromSnapshot(captured.BodyBefore);
+
+ var harnessResult = engine.ResolveWithTransition(
+ currentPos: captured.Input.CurrentPos,
+ targetPos: captured.Input.TargetPos,
+ cellId: captured.Input.CellId,
+ sphereRadius: captured.Input.SphereRadius,
+ sphereHeight: captured.Input.SphereHeight,
+ stepUpHeight: captured.Input.StepUpHeight,
+ stepDownHeight: captured.Input.StepDownHeight,
+ isOnGround: captured.Input.IsOnGround,
+ body: body,
+ moverFlags: (ObjectInfoState)captured.Input.MoverFlags,
+ movingEntityId: captured.Input.MovingEntityId);
+
+ // Compare in priority order — most consequential divergence first.
+ var divergences = new List();
+
+ // 1. Result fields
+ AddIfDifferent(divergences, "Result.Position",
+ captured.Result.Position, harnessResult.Position);
+ AddIfDifferent(divergences, "Result.CellId",
+ $"0x{captured.Result.CellId:X8}",
+ $"0x{harnessResult.CellId:X8}");
+ AddIfDifferent(divergences, "Result.IsOnGround",
+ captured.Result.IsOnGround, harnessResult.IsOnGround);
+ AddIfDifferent(divergences, "Result.CollisionNormalValid",
+ captured.Result.CollisionNormalValid,
+ harnessResult.CollisionNormalValid);
+ if (captured.Result.CollisionNormalValid && harnessResult.CollisionNormalValid)
+ {
+ AddIfDifferent(divergences, "Result.CollisionNormal",
+ captured.Result.CollisionNormal,
+ harnessResult.CollisionNormal);
+ }
+
+ // 2. Body-after fields (subset that's most likely to diverge first)
+ AddIfDifferent(divergences, "BodyAfter.Position",
+ captured.BodyAfter.Position, body.Position);
+ AddIfDifferent(divergences, "BodyAfter.ContactPlaneValid",
+ captured.BodyAfter.ContactPlaneValid, body.ContactPlaneValid);
+ if (captured.BodyAfter.ContactPlaneValid && body.ContactPlaneValid)
+ {
+ AddIfDifferent(divergences, "BodyAfter.ContactPlane.Normal",
+ captured.BodyAfter.ContactPlane.Normal,
+ body.ContactPlane.Normal);
+ AddIfDifferent(divergences, "BodyAfter.ContactPlane.D",
+ captured.BodyAfter.ContactPlane.D,
+ body.ContactPlane.D);
+ }
+ AddIfDifferent(divergences, "BodyAfter.WalkablePolygonValid",
+ captured.BodyAfter.WalkablePolygonValid, body.WalkablePolygonValid);
+ AddIfDifferent(divergences, "BodyAfter.TransientState",
+ $"0x{captured.BodyAfter.TransientState:X}",
+ $"0x{(uint)body.TransientState:X}");
+
+ if (divergences.Count > 0)
+ {
+ string summary = string.Join("\n • ", divergences);
+ string header = string.Format(System.Globalization.CultureInfo.InvariantCulture,
+ "Harness replay of captured tick {0} diverges from live engine. " +
+ "Input: currentPos=({1:F4},{2:F4},{3:F4}) targetPos=({4:F4},{5:F4},{6:F4}) " +
+ "cellId=0x{7:X8} isOnGround={8}",
+ captured.Tick,
+ captured.Input.CurrentPos.X, captured.Input.CurrentPos.Y, captured.Input.CurrentPos.Z,
+ captured.Input.TargetPos.X, captured.Input.TargetPos.Y, captured.Input.TargetPos.Z,
+ captured.Input.CellId, captured.Input.IsOnGround);
+ throw new Xunit.Sdk.XunitException(
+ header + "\nDivergences (live → harness):\n • " + summary);
+ }
+ }
+
+ private static PhysicsBody SeedBodyFromSnapshot(PhysicsBodySnapshot snap) => new()
+ {
+ Position = snap.Position,
+ Orientation = snap.Orientation,
+ Velocity = snap.Velocity,
+ Acceleration = snap.Acceleration,
+ Omega = snap.Omega,
+ GroundNormal = snap.GroundNormal,
+ SlidingNormal = snap.SlidingNormal,
+ ContactPlaneValid = snap.ContactPlaneValid,
+ ContactPlane = snap.ContactPlane,
+ ContactPlaneCellId = snap.ContactPlaneCellId,
+ ContactPlaneIsWater = snap.ContactPlaneIsWater,
+ WalkablePolygonValid = snap.WalkablePolygonValid,
+ WalkablePlane = snap.WalkablePlane,
+ WalkableVertices = snap.WalkableVertices,
+ WalkableUp = snap.WalkableUp,
+ Elasticity = snap.Elasticity,
+ Friction = snap.Friction,
+ State = (PhysicsStateFlags)snap.State,
+ TransientState = (TransientStateFlags)snap.TransientState,
+ LastUpdateTime = snap.LastUpdateTime,
+ };
+
+ private static void AddIfDifferent(
+ List divergences, string name, T live, T harness)
+ {
+ if (EqualityComparer.Default.Equals(live, harness))
+ return;
+ divergences.Add(string.Format(System.Globalization.CultureInfo.InvariantCulture,
+ "{0}: live={1} harness={2}", name, live, harness));
+ }
+
+ private static void AddIfDifferent(
+ List divergences, string name, Vector3 live, Vector3 harness)
+ {
+ if (Vector3.DistanceSquared(live, harness) < 1e-6f)
+ return;
+ divergences.Add(string.Format(System.Globalization.CultureInfo.InvariantCulture,
+ "{0}: live=({1:F4},{2:F4},{3:F4}) harness=({4:F4},{5:F4},{6:F4})",
+ name, live.X, live.Y, live.Z, harness.X, harness.Y, harness.Z));
+ }
+
+ private static void AddIfDifferent(
+ List divergences, string name, float live, float harness)
+ {
+ if (MathF.Abs(live - harness) < 1e-3f)
+ return;
+ divergences.Add(string.Format(System.Globalization.CultureInfo.InvariantCulture,
+ "{0}: live={1:F4} harness={2:F4}", name, live, harness));
+ }
+
// ───────────────────────────────────────────────────────────────
// Harness internals
// ───────────────────────────────────────────────────────────────