diff --git a/docs/ISSUES.md b/docs/ISSUES.md
index a7b1e78a..76a3e3d4 100644
--- a/docs/ISSUES.md
+++ b/docs/ISSUES.md
@@ -46,6 +46,57 @@ Copy this block when adding a new issue:
---
+## #175 — Door collision registers the Setup PLACEMENT pose, not the motion-table CLOSED pose (phantom slab behind the visual door)
+
+**Status:** OPEN — mechanism dat-confirmed (`Issue175HubDoorPoseInspectionTests`); fix not started
+**Severity:** MEDIUM-HIGH (embed into doors from one side; phantom wall on the other — can push the player out of use radius)
+**Filed:** 2026-07-05
+**Component:** physics — server-entity collision registration (door part poses)
+
+**Description (user, Facility Hub door guid 0x78A020C7 / Setup 0x02000C9D):**
+running at the door embeds the player INTO the visual panel (deep enough to
+camera-clip to the other side); the actual blocking plane sits displaced to
+the FAR side, and approaching from that side there's a phantom wall in front
+of the visual door — far enough that the door can be out of use range.
+
+**Mechanism (dat-confirmed, 2026-07-05):** the hub door is a DOUBLE door —
+Setup 0x02000C9D has 3 parts; panels part[0]/part[1] (GfxObj 0x01002936,
+physics slab 1.66×0.29×2.95 m) pose in the Setup's `Default` PLACEMENT
+frames at yaw **−150° / −30°** with origin **(±0.88, −0.44, 1.37)** — an
+AJAR pose displaced 0.44 m behind the doorway plane. The RENDERED door poses
+its panels from the motion table's default (closed) state via the sequencer
+(the setup itself has no DefaultMotionTable; the wire spawn supplies it).
+Collision registers via `ShadowShapeBuilder.FromSetup`, which reads the
+PLACEMENT frames (`Resting|Default|first`) — so the physical slabs sit at
+the ajar placement pose while the visuals show closed panels: the exact
+offset the user walked into. Retail tests each part's LIVE pose
+(`CPhysicsPart` — see the #150 notes: for a CLOSED door the live pose IS
+the motion-table closed pose; the open swing never matters because ETHEREAL
+bypasses collision entirely).
+
+**Fix shape (retail-faithful, next session):** the BSP shadow shapes for
+server entities with a sequencer must use the SEQUENCER's part transforms
+(the motion-table default/closed pose) instead of the raw placement frames —
+either sample at registration (the sequencer exists by then — verify spawn
+wiring order) or re-register via `ShadowObjectRegistry.UpdatePosition`-style
+refresh after the sequencer's first advance. Parts without animation data
+keep the placement-frame fallback. Watch: entScale composition, multi-part
+dedup ([[feedback_dedup_keys_after_cardinality_change]]), and the Holtburg
+single-door apparatus must stay green (its placement pose ≈ closed pose,
+which is why #99/#150 never surfaced this).
+
+**Files:** `src/AcDream.Core/Physics/ShadowShapeBuilder.cs` (placement-frame
+read), `src/AcDream.App/Rendering/GameWindow.cs`
+(`RegisterServerEntityCollision` ~4130), inspection
+`tests/AcDream.Core.Tests/Physics/Issue175HubDoorPoseInspectionTests.cs`.
+
+**Acceptance:** at the Facility Hub double door: closed door blocks AT the
+visual panels (no embed, no phantom wall on either side); open door fully
+passable; use radius reachable from both sides. Holtburg cottage door
+unregressed (door apparatus green).
+
+---
+
## #174 — Door Use dies after the first jump: the RemoveLinkAnimations seam stripped animations without retail's queue drain
**Status:** 🟡 FIX SHIPPED 2026-07-05 (same session) — pending user gate (jump around, then use the Facility Hub door from close AND from ~3 m).
diff --git a/tests/AcDream.Core.Tests/Physics/Issue175HubDoorPoseInspectionTests.cs b/tests/AcDream.Core.Tests/Physics/Issue175HubDoorPoseInspectionTests.cs
new file mode 100644
index 00000000..6c023e7c
--- /dev/null
+++ b/tests/AcDream.Core.Tests/Physics/Issue175HubDoorPoseInspectionTests.cs
@@ -0,0 +1,148 @@
+using System;
+using System.IO;
+using System.Linq;
+using DatReaderWriter;
+using DatReaderWriter.DBObjs;
+using DatReaderWriter.Options;
+using Xunit;
+using Xunit.Abstractions;
+using Env = System.Environment;
+
+namespace AcDream.Core.Tests.Physics;
+
+///
+/// #175 (2026-07-05) — read-only dat inspection for the Facility Hub door
+/// (Setup 0x02000C9D, guid 0x78A020C7 in the live session). User report:
+/// the door's COLLISION sits displaced to the far side of the VISUAL panel
+/// (embed from one side deep enough to camera-clip; a phantom wall on the
+/// other side that can push the player out of use radius).
+///
+/// Hypothesis under test: collision registers from the Setup's
+/// PlacementFrames (ShadowShapeBuilder.FromSetup — Resting|Default|first)
+/// while the rendered panel poses from the motion table's default/closed
+/// state through the sequencer; retail tests the part's LIVE pose
+/// (CPhysicsPart), so a door whose placement frame differs from its
+/// motion-table closed pose shows exactly this offset. This test dumps both
+/// poses so the divergence (or its absence) is a dat fact, not a theory.
+///
+/// SKIP when the dat directory is absent (CI); local runs have it.
+///
+public class Issue175HubDoorPoseInspectionTests
+{
+ private readonly ITestOutputHelper _out;
+ public Issue175HubDoorPoseInspectionTests(ITestOutputHelper output) => _out = output;
+
+ private const uint HubDoorSetupId = 0x02000C9Du;
+
+ [Fact]
+ public void HubDoorSetup_PlacementVsMotionPose_DatInspection()
+ {
+ var datDir = Env.GetEnvironmentVariable("ACDREAM_DAT_DIR")
+ ?? Path.Combine(Env.GetFolderPath(Env.SpecialFolder.UserProfile),
+ "Documents", "Asheron's Call");
+ if (!Directory.Exists(datDir))
+ {
+ _out.WriteLine($"SKIP: dat directory not found at {datDir}");
+ return;
+ }
+
+ using var dats = new DatCollection(datDir, DatAccessType.Read);
+
+ var setup = dats.Get(HubDoorSetupId);
+ Assert.NotNull(setup);
+
+ _out.WriteLine($"=== Setup 0x{HubDoorSetupId:X8} ===");
+ _out.WriteLine($" Flags = {setup!.Flags} (0x{(uint)setup.Flags:X8})");
+ _out.WriteLine($" Parts = {setup.Parts.Count}");
+ for (int i = 0; i < setup.Parts.Count; i++)
+ _out.WriteLine($" [{i}] gfxObj=0x{setup.Parts[i]:X8}");
+ _out.WriteLine($" DefaultAnimation = 0x{setup.DefaultAnimation:X8}");
+ _out.WriteLine($" DefaultScript = 0x{setup.DefaultScript:X8}");
+ _out.WriteLine($" DefaultMotionTable = 0x{setup.DefaultMotionTable:X8}");
+ _out.WriteLine($" CylSpheres={setup.CylSpheres.Count} Spheres={setup.Spheres.Count} Radius={setup.Radius:F3}");
+ foreach (var c in setup.CylSpheres)
+ _out.WriteLine($" cyl r={c.Radius:F3} h={c.Height:F3} origin=({c.Origin.X:F3},{c.Origin.Y:F3},{c.Origin.Z:F3})");
+
+ _out.WriteLine($" PlacementFrames = {setup.PlacementFrames.Count}");
+ foreach (var kv in setup.PlacementFrames)
+ {
+ _out.WriteLine($" [{kv.Key}] frames={kv.Value.Frames.Count}");
+ for (int i = 0; i < kv.Value.Frames.Count; i++)
+ {
+ var f = kv.Value.Frames[i];
+ _out.WriteLine(
+ $" part[{i}] pos=({f.Origin.X:F3},{f.Origin.Y:F3},{f.Origin.Z:F3}) " +
+ $"rot=({f.Orientation.X:F3},{f.Orientation.Y:F3},{f.Orientation.Z:F3},{f.Orientation.W:F3})");
+ }
+ }
+
+ // Part 0's physics BSP bounds — where the slab actually is in
+ // PART-LOCAL space (composed with the poses above for world).
+ foreach (uint gfxId in setup.Parts.Distinct())
+ {
+ var gfx = dats.Get(gfxId);
+ _out.WriteLine($"=== GfxObj 0x{gfxId:X8} ===");
+ if (gfx is null) { _out.WriteLine(" NULL"); continue; }
+ var root = gfx.PhysicsBSP?.Root;
+ _out.WriteLine($" PhysicsBSP.Root = {(root is null ? "NULL" : "non-null")}");
+ if (root?.BoundingSphere is { } bs)
+ _out.WriteLine($" BSP bounds = ({bs.Origin.X:F3},{bs.Origin.Y:F3},{bs.Origin.Z:F3}) r={bs.Radius:F3}");
+ if (gfx.PhysicsPolygons is { } pp && gfx.VertexArray?.Vertices is { } verts)
+ {
+ float minX = float.MaxValue, maxX = float.MinValue;
+ float minY = float.MaxValue, maxY = float.MinValue;
+ float minZ = float.MaxValue, maxZ = float.MinValue;
+ foreach (var poly in pp.Values)
+ foreach (var vid in poly.VertexIds)
+ {
+ if (!verts.TryGetValue((ushort)vid, out var sv)) continue;
+ minX = Math.Min(minX, sv.Origin.X); maxX = Math.Max(maxX, sv.Origin.X);
+ minY = Math.Min(minY, sv.Origin.Y); maxY = Math.Max(maxY, sv.Origin.Y);
+ minZ = Math.Min(minZ, sv.Origin.Z); maxZ = Math.Max(maxZ, sv.Origin.Z);
+ }
+ _out.WriteLine($" Physics AABB (part-local) = X[{minX:F3},{maxX:F3}] Y[{minY:F3},{maxY:F3}] Z[{minZ:F3},{maxZ:F3}]");
+ }
+ }
+
+ // The motion-table default (closed) pose, if the setup names one:
+ // frame 0 of the default style's default cycle — what the sequencer
+ // renders for an idle closed door.
+ if (setup.DefaultMotionTable != 0)
+ {
+ var mt = dats.Get(setup.DefaultMotionTable);
+ _out.WriteLine($"=== MotionTable 0x{setup.DefaultMotionTable:X8} ===");
+ if (mt is null) { _out.WriteLine(" NULL"); return; }
+ _out.WriteLine($" DefaultStyle = 0x{(uint)mt.DefaultStyle:X8}");
+ if (mt.Cycles.TryGetValue((int)mt.DefaultStyle, out var defCycle)
+ && defCycle.Anims.Count > 0)
+ {
+ var animRef = defCycle.Anims[0];
+ _out.WriteLine($" default cycle anim[0] id=0x{animRef.AnimId:X8} lo={animRef.LowFrame} hi={animRef.HighFrame}");
+ var anim = dats.Get(animRef.AnimId);
+ if (anim is not null && anim.PartFrames.Count > 0)
+ {
+ var f0 = anim.PartFrames[Math.Clamp((int)animRef.LowFrame, 0, anim.PartFrames.Count - 1)];
+ for (int i = 0; i < f0.Frames.Count; i++)
+ {
+ var f = f0.Frames[i];
+ _out.WriteLine(
+ $" anim frame0 part[{i}] pos=({f.Origin.X:F3},{f.Origin.Y:F3},{f.Origin.Z:F3}) " +
+ $"rot=({f.Orientation.X:F3},{f.Orientation.Y:F3},{f.Orientation.Z:F3},{f.Orientation.W:F3})");
+ }
+ }
+ else
+ {
+ _out.WriteLine(" anim NULL or no PartFrames");
+ }
+ }
+ else
+ {
+ _out.WriteLine(" no default-style cycle");
+ }
+ }
+ else
+ {
+ _out.WriteLine("=== no DefaultMotionTable on the setup ===");
+ }
+ }
+}