diff --git a/tests/AcDream.Core.Tests/Physics/CameraCornerSealReplayTests.cs b/tests/AcDream.Core.Tests/Physics/CameraCornerSealReplayTests.cs
new file mode 100644
index 00000000..1d94e784
--- /dev/null
+++ b/tests/AcDream.Core.Tests/Physics/CameraCornerSealReplayTests.cs
@@ -0,0 +1,293 @@
+using System;
+using System.Numerics;
+using AcDream.Core.Physics;
+using AcDream.Core.Tests.Conformance;
+using DatReaderWriter;
+using DatReaderWriter.Options;
+using Xunit;
+using Xunit.Abstractions;
+
+namespace AcDream.Core.Tests.Physics;
+
+///
+/// §2b corner camera-seal replay (2026-06-10) — INVESTIGATION RESULT: the
+/// "camera penetrates the wall at corners" hypothesis is REFUTED. Replays the
+/// captured "escape" camera sweeps from corner-seal-capture.log through the
+/// REAL with the camera's exact
+/// call shape ( | PathClipped | FreeRotate |
+/// PerfectClip, single 0.3 m sphere — mirrors
+/// src/AcDream.App/Rendering/PhysicsCameraCollisionProbe.SweepEye).
+///
+/// WHAT THE REPLAY + GEOMETRY MAP PROVED (Diagnostic fact below):
+/// every captured zero-contact traversal runs through a REAL OPENING, not a wall —
+/// the building's exit-door portal (0170 → 0xFFFF) for the viewer-outdoor frames, and
+/// the 0171↔0173↔0172 doorway chain (0173 is a 20 cm threshold cell — two parallel
+/// portal planes at local x=4.10/3.90) for the corner-press frames. The captured eyes
+/// land INSIDE the door openings' rectangles; the containment walk shows no path point
+/// inside solid cell volume. 8,703 of 14,230 indoor sweeps in the same capture DID
+/// collide (pull-in up to 2.77 m) — camera collision works; nothing penetrates.
+/// The user-visible "background at the corner" is therefore NOT a collision bug: it is
+/// the §2a edge-on portal-clip collapse with the eye hovering at the doorway plane
+/// (render-side; see docs/research/2026-06-10 corner-seal handoff).
+///
+/// The assertion fact below is a CHARACTERIZATION pinning the verified-correct
+/// behavior: viewer sweeps along these captured opening paths must pass WITHOUT
+/// collision (a future change that makes doorway air solid would break the camera).
+///
+/// Capture provenance (worktree root, untracked): corner-seal-capture.log
+/// L20104 / L20224 / L20386 (door-exit) + L23035 / L81340 / L87958 (corner press);
+/// pivot = player + (0,0,1.5) per RetailChaseCamera.PivotHeight.
+///
+public class CameraCornerSealReplayTests
+{
+ private readonly ITestOutputHelper _out;
+ public CameraCornerSealReplayTests(ITestOutputHelper output) => _out = output;
+
+ private const float ViewerSphereRadius = 0.3f; // retail viewer_sphere (acclient :93314)
+ private const float PivotHeight = 1.5f; // RetailChaseCamera.PivotHeight
+
+ // Captured corner-escape samples: (label, start cellId, player feet position,
+ // desired eye = the [flap-sweep] in= value). Player from the same frame's
+ // [pv-input]; S1 has both the render-interpolated and raw physics player (1 cm
+ // apart) — both must stop at the wall, so both are exercised.
+ private static readonly (string Label, uint CellId, Vector3 Player, Vector3 Eye)[] Samples =
+ {
+ ("S1-render 0170", 0xA9B40170u, new Vector3(154.934845f, 16.451527f, 94.000000f),
+ new Vector3(154.797028f, 19.320539f, 96.248833f)),
+ ("S1-raw 0170", 0xA9B40170u, new Vector3(154.945374f, 16.451527f, 94.000000f),
+ new Vector3(154.797028f, 19.320539f, 96.248833f)),
+ ("S2 0171", 0xA9B40171u, new Vector3(155.164307f, 14.392493f, 94.000000f),
+ new Vector3(154.804489f, 18.434128f, 96.248833f)),
+ ("S3 0171", 0xA9B40171u, new Vector3(155.475723f, 11.463923f, 94.000000f),
+ new Vector3(154.990143f, 16.249460f, 96.248833f)),
+ };
+
+ private static (PhysicsEngine, PhysicsDataCache,
+ System.Collections.Generic.Dictionary)
+ BuildBuildingEngine(DatCollection dats)
+ {
+ // Same fixture as ThresholdPortalCrossingReplayTests.BuildBuildingEngine:
+ // the full Holtburg building cell range + a stub far-below landblock.
+ var cache = new PhysicsDataCache();
+ var engine = new PhysicsEngine { DataCache = cache };
+ var envCells = new System.Collections.Generic.Dictionary();
+ for (uint low = 0x016Fu; low <= 0x0175u; low++)
+ {
+ uint id = ConformanceDats.HoltburgLandblock | low;
+ envCells[id] = ConformanceDats.LoadEnvCell(dats, cache, id);
+ }
+
+ var heights = new byte[81];
+ var heightTable = new float[256];
+ for (int i = 0; i < 256; i++) heightTable[i] = -1000f;
+ engine.AddLandblock(0xA9B40000u, new TerrainSurface(heights, heightTable),
+ Array.Empty(), Array.Empty(), 0f, 0f);
+ return (engine, cache, envCells);
+ }
+
+ ///
+ /// Mirror of PhysicsCameraCollisionProbe.SweepEye's transition call (the App-layer
+ /// probe is not referencable from Core tests; the call shape is duplicated here and
+ /// kept in sync by the file-header provenance note).
+ ///
+ private static ResolveResult SweepViewer(PhysicsEngine engine, Vector3 pivot, Vector3 desiredEye, uint cellId)
+ {
+ uint startCell = cellId;
+ if ((cellId & 0xFFFFu) >= 0x0100u)
+ {
+ var (pivotCell, found) = engine.AdjustPosition(cellId, pivot);
+ if (found) startCell = pivotCell;
+ }
+
+ // InitPath sphere-center convention: shift down by the radius (ToSpherePath).
+ Vector3 begin = pivot - new Vector3(0f, 0f, ViewerSphereRadius);
+ Vector3 end = desiredEye - new Vector3(0f, 0f, ViewerSphereRadius);
+
+ return engine.ResolveWithTransition(
+ currentPos: begin,
+ targetPos: end,
+ cellId: startCell,
+ sphereRadius: ViewerSphereRadius,
+ sphereHeight: 0f,
+ stepUpHeight: 0f,
+ stepDownHeight: 0f,
+ isOnGround: false,
+ body: null,
+ moverFlags: ObjectInfoState.IsViewer | ObjectInfoState.PathClipped
+ | ObjectInfoState.FreeRotate | ObjectInfoState.PerfectClip,
+ movingEntityId: 0);
+ }
+
+ ///
+ /// Diagnostic (no assertions): per-sweep BSP dispatch trace via the A6.P1
+ /// [push-back] probe family. Compares the LEAKING camera path (S1) against two
+ /// controls into the same north boundary: a horizontal viewer-style sweep at
+ /// chest height and a player-style grounded sweep. Outcome decides whether the
+ /// room's exterior boundary is sealed in the cell physics BSP at all (controls
+ /// collide ⇒ path/angle-specific miss) or genuinely open (nothing collides ⇒
+ /// the building-shell GfxObj is the only enclosure and the live isViewer
+ /// shadow-query path is the half that failed).
+ ///
+ [Fact]
+ public void Diagnostic_DispatchTrace_LeakPath_vs_Controls()
+ {
+ var datDir = ConformanceDats.ResolveDatDir();
+ if (datDir is null) { _out.WriteLine("SKIP: dats unavailable"); return; }
+
+ using var dats = new DatCollection(datDir, DatAccessType.Read);
+ var (engine, cache, envCells) = BuildBuildingEngine(dats);
+
+ var s = Samples[0]; // S1-render 0170
+ Vector3 pivot = s.Player + new Vector3(0f, 0f, PivotHeight);
+
+ RunTraced(engine, "LEAK viewer pivot->eye", () =>
+ SweepViewer(engine, pivot, s.Eye, s.CellId));
+
+ RunTraced(engine, "CTRL-H viewer horizontal +3Y", () =>
+ SweepViewer(engine, pivot, pivot + new Vector3(0f, 3f, 0f), s.CellId));
+
+ RunTraced(engine, "CTRL-P player grounded +3Y", () =>
+ engine.ResolveWithTransition(
+ currentPos: s.Player,
+ targetPos: s.Player + new Vector3(0f, 3f, 0f),
+ cellId: s.CellId,
+ sphereRadius: 0.4f,
+ sphereHeight: 1.2f,
+ stepUpHeight: 0.4f,
+ stepDownHeight: 0.1f,
+ isOnGround: true,
+ body: null,
+ moverFlags: ObjectInfoState.IsPlayer | ObjectInfoState.EdgeSlide,
+ movingEntityId: 0));
+
+ // ── Geometry map: what does the leak path actually cross? ──────────────
+ _out.WriteLine("=== containment along LEAK path (pivot -> eye, 11 steps) ===");
+ for (int i = 0; i <= 10; i++)
+ {
+ Vector3 p = Vector3.Lerp(pivot, s.Eye, i / 10f);
+ string inCells = "";
+ foreach (var kv in envCells)
+ if (kv.Value.PointInCell(p))
+ inCells += $" 0x{kv.Key & 0xFFFFu:X4}";
+ if (inCells.Length == 0) inCells = " (none)";
+ _out.WriteLine(System.FormattableString.Invariant(
+ $" t={i / 10f:F1} ({p.X:F2},{p.Y:F2},{p.Z:F2}) ->{inCells}"));
+ }
+
+ _out.WriteLine("=== full room map: origin + portals (cell-LOCAL polygon bounds) ===");
+ foreach (uint low in new uint[] { 0x016Fu, 0x0170u, 0x0171u, 0x0172u, 0x0173u, 0x0174u, 0x0175u })
+ {
+ var cp = cache.GetCellStruct(ConformanceDats.HoltburgLandblock | low);
+ if (cp is null) { _out.WriteLine($" 0x{low:X4}: no CellPhysics"); continue; }
+ var o = cp.WorldTransform.Translation;
+ _out.WriteLine(System.FormattableString.Invariant(
+ $" 0x{low:X4} origin=({o.X:F2},{o.Y:F2},{o.Z:F2}) portals={cp.Portals.Count}"));
+ foreach (var portal in cp.Portals)
+ {
+ string bounds = "(polygon missing)";
+ if (cp.PortalPolygons is not null
+ && cp.PortalPolygons.TryGetValue(portal.PolygonId, out var poly))
+ {
+ Vector3 min = new(float.MaxValue), max = new(float.MinValue);
+ foreach (var v in poly.Vertices)
+ {
+ min = Vector3.Min(min, v);
+ max = Vector3.Max(max, v);
+ }
+ bounds = System.FormattableString.Invariant(
+ $"x[{min.X:F2},{max.X:F2}] y[{min.Y:F2},{max.Y:F2}] z[{min.Z:F2},{max.Z:F2}]");
+ }
+ _out.WriteLine(System.FormattableString.Invariant(
+ $" -> other=0x{portal.OtherCellId:X4} poly={portal.PolygonId} {bounds}"));
+ }
+ }
+
+ // ── Containment for the live 0172->0171 corner frames (captured eye points
+ // + their pivots). The corner press lived in room 0172; the viewer classified
+ // into 0171 (2,963 frames). Which cell volume actually contains those points?
+ _out.WriteLine("=== containment: corner-press eyes + pivots (player=0172 viewer=0171 frames) ===");
+ var cornerPoints = new (string Label, Vector3 P)[]
+ {
+ ("eyeA (L23035)", new Vector3(154.607880f, 11.154252f, 96.248787f)),
+ ("eyeB (L81340)", new Vector3(157.477249f, 7.912723f, 96.248863f)),
+ ("eyeC (L87958)", new Vector3(157.452667f, 7.914233f, 96.248856f)),
+ ("pivotA", new Vector3(157.976959f, 8.622595f, 95.500000f)),
+ ("pivotB/C", new Vector3(159.936676f, 7.701012f, 95.500000f)),
+ };
+ foreach (var (label, p) in cornerPoints)
+ {
+ string inCells = "";
+ foreach (var kv in envCells)
+ if (kv.Value.PointInCell(p))
+ inCells += $" 0x{kv.Key & 0xFFFFu:X4}";
+ if (inCells.Length == 0) inCells = " (none)";
+ _out.WriteLine(System.FormattableString.Invariant(
+ $" {label} ({p.X:F2},{p.Y:F2},{p.Z:F2}) ->{inCells}"));
+ }
+ }
+
+ private void RunTraced(PhysicsEngine engine, string label, Func sweep)
+ {
+ var sw = new StringWriter();
+ var prev = Console.Out;
+ ResolveResult r;
+ try
+ {
+ Console.SetOut(sw);
+ PhysicsDiagnostics.ProbePushBackEnabled = true;
+ r = sweep();
+ }
+ finally
+ {
+ PhysicsDiagnostics.ProbePushBackEnabled = false;
+ Console.SetOut(prev);
+ }
+
+ var lines = sw.ToString().Split('\n', StringSplitOptions.RemoveEmptyEntries);
+ _out.WriteLine(System.FormattableString.Invariant(
+ $"=== {label}: end=({r.Position.X:F3},{r.Position.Y:F3},{r.Position.Z:F3}) cell=0x{r.CellId:X8} collNorm={r.CollisionNormalValid} ok={r.Ok} probeLines={lines.Length} ==="));
+ for (int i = 0; i < lines.Length && i < 40; i++)
+ _out.WriteLine(" " + lines[i].TrimEnd());
+ if (lines.Length > 40)
+ _out.WriteLine($" ... +{lines.Length - 40} more");
+ }
+
+ [Fact]
+ public void ViewerSweep_ThroughOpenings_PassesWithoutCollision()
+ {
+ var datDir = ConformanceDats.ResolveDatDir();
+ if (datDir is null) { _out.WriteLine("SKIP: dats unavailable"); return; }
+
+ using var dats = new DatCollection(datDir, DatAccessType.Read);
+ var (engine, _, _) = BuildBuildingEngine(dats);
+
+ var failures = new System.Collections.Generic.List();
+ foreach (var s in Samples)
+ {
+ Vector3 pivot = s.Player + new Vector3(0f, 0f, PivotHeight);
+ float desiredBack = Vector3.Distance(pivot, s.Eye);
+
+ var r = SweepViewer(engine, pivot, s.Eye, s.CellId);
+
+ Vector3 eyeOut = r.Position + new Vector3(0f, 0f, ViewerSphereRadius);
+ float eyeBack = Vector3.Distance(pivot, eyeOut);
+ float pulledIn = desiredBack - eyeBack;
+
+ // Characterization (see file header): these captured paths run through
+ // real openings (exit door / doorway-threshold chain). The geometry-map
+ // diagnostic verified no solid is crossed, so the sweep must pass clean —
+ // no collision, full traversal.
+ bool passedClean = !r.CollisionNormalValid && pulledIn < 0.10f && r.Ok;
+
+ _out.WriteLine(System.FormattableString.Invariant(
+ $"{s.Label}: ok={r.Ok} collNorm={r.CollisionNormalValid} desiredBack={desiredBack:F2} eyeBack={eyeBack:F2} pulledIn={pulledIn:F2} endCell=0x{r.CellId:X8} {(passedClean ? "CLEAN" : "OBSTRUCTED")}"));
+
+ if (!passedClean)
+ failures.Add(s.Label);
+ }
+
+ Assert.True(failures.Count == 0,
+ "Viewer sweep through a verified-open doorway path was obstructed or cut short " +
+ "(camera would wrongly pull in at openings) for: " + string.Join(", ", failures));
+ }
+}