docs(physics): #165 diagnostic pass - rule out (a)/(b), stop at (c)

Campaign P Slice P3 item 4. Per the plan's explicit instruction, this
is diagnose-only: the research's candidate (a)/(b) mechanisms did not
confirm, so no fix lands here.

Built the dat-free/dat-backed fixtures the plan asked for (no live
client) to test the two mechanisms a physics fixture CAN discriminate:

- (b) ruled out by code reading: RuntimeRemotePhysicsUpdater.Tick's
  resolve gate reads RuntimeEntityRecord.FullCellId live. Every
  FullCellId = 0 write site (TryApplyPickup, CommitAcceptedParentCellless,
  CommitWithdrawal in RuntimeEntityObjectLifetime.cs) is a pickup/
  parent-attach/delete path, never reachable for a live, freely moving
  remote mid-session. The "one-frame grace" is genuinely first-spawn-only.

- (a) tested directly and does not reproduce, on two independent
  geometries: InterpolationManager's unclamped stall-fail "tail delta"
  snap (node_fail_counter > 3) can hand ResolveWithTransition an
  arbitrarily large single-tick targetPos. New fixture tests replace a
  proven small-step sweep (many 0.08-0.10 m ticks) with ONE resolve call
  spanning the entire distance, against both a synthetic creature sphere
  and the real Holtburg door BSP slab (Setup 0x020019FF/GfxObj
  0x010044B5, the existing door-apparatus dat fixture) already used by
  DoorCollisionApparatusTests. Both stop at the identical surface
  distance the small-step tests pin, with a valid collision normal --
  the sweep is not distance-limited and does not tunnel on a large
  single-tick delta.

Candidate (c) -- render/interpolation presentation lag on the App side --
is the remaining hypothesis and is out of scope for a physics-fixture
pass (it's a claim about what gets drawn relative to the committed
PhysicsBody.Position, not something a Core fixture observes). #165
stays OPEN with (a)/(b) struck from the candidate list by the evidence
above and (c) named as the next concrete step (an App-layer render-vs-
physics-position diff, or a fresh live ACDREAM_PROBE_RESOLVE capture).

New tests: Issue165RemoteWallPenetrationDiagnosticTests (dat-free,
3 tests) and DoorCollisionApparatusTests.
Apparatus_SingleLargeTickJump_DeadCenter_StillBlocksOnBSP (dat-backed,
1 test, skips gracefully without the local dat directory).

dotnet build + dotnet test (Core.Tests 4012/2 skip, Runtime.Tests
425/0) green.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Erik 2026-07-30 10:01:11 +02:00
parent bb7b899bfe
commit bc3277a8ec
4 changed files with 392 additions and 1 deletions

View file

@ -4244,9 +4244,55 @@ NOTE: capture first — ACDREAM_PROBE_RESOLVE on the remote's guid at a
wall shows whether the resolver reports Collided-but-position-inside or
never sees the wall.
**Campaign P Slice P3 diagnostic pass (2026-07-30, no live client —
dat-free + dat-backed fixtures only, per
`docs/research/2026-07-29-remote-and-world-specials-pseudocode.md` §2.4b):**
research P3.2 re-framed the three candidates as (a) the
`InterpolationManager` unclamped stall-fail "tail delta" snap
(`node_fail_counter > 3`) committing a position on the far side of / inside
a wall in one tick and the same-tick sweep failing to catch a large
delta, (b) the remote resolve gate (`rm.CellId != 0 &&
_physics.Engine.LandblockCount > 0`) skipping the sweep entirely on some
tick other than first-spawn, and (c) render/interpolation presentation lag
on the App side. Both (a) and (b) are now RULED OUT with direct evidence:
- **(b):** code-read every `FullCellId = 0` write site
(`RuntimeEntityObjectLifetime.cs`: `TryApplyPickup`,
`CommitAcceptedParentCellless`, `CommitWithdrawal`) — all three are
pickup/parent-attach/delete paths, never reachable for a live, freely
moving remote mid-session. The gate's "one-frame grace" is genuinely
first-spawn-only.
- **(a):** three new fixture tests drive a SINGLE resolve call spanning an
entire large-tick jump (simulating the unclamped snap) instead of many
small ticks, against both synthetic sphere geometry AND the real
Holtburg door BSP slab (`Setup 0x020019FF`/`GfxObj 0x010044B5`) already
used by the door apparatus tests — both stop at the identical surface
distance the proven small-step tests already pin, with a valid collision
normal. The sweep is not distance-limited and does not tunnel on a large
single-tick delta. See
`Issue165RemoteWallPenetrationDiagnosticTests.SingleLargeTickJumpThroughObstacle_IsStillBlockedAtSurface`
and
`DoorCollisionApparatusTests.Apparatus_SingleLargeTickJump_DeadCenter_StillBlocksOnBSP`.
**Candidate (c) is therefore the remaining hypothesis** and is explicitly
OUT OF SCOPE for a physics-fixture-only pass — it is a claim about the
App-layer render/presentation frame relative to the committed
`PhysicsBody.Position`, not something a dat-free/dat-backed Core fixture
can observe. Per the campaign's own instruction ("diagnose only unless a
candidate confirms cheaply; otherwise stop"), this issue stays OPEN. The
next concrete step for whoever picks this up: an App-layer render-position
vs. physics-position diff across frames for a remote near a wall, or a
fresh `ACDREAM_PROBE_RESOLVE`/`ACDREAM_CAPTURE_RESOLVE` live capture (the
existing diagnostic recommendation in the research doc, still valid) if a
live repro becomes available.
**Where:** GameWindow remote DR tick (`TickAnimations` player-remote
pipeline + queue chase), `PhysicsEngine.ResolveWithTransition` remote
callers.
callers; `src/AcDream.Runtime/Physics/RuntimeRemotePhysicsUpdater.cs`
(resolve gate, candidate (b), ruled out); `src/AcDream.Core/Physics/
InterpolationManager.cs` (unclamped stall-fail snap, candidate (a), ruled
out); the render/presentation path (candidate (c), OPEN, App-layer, not
yet located).
**Acceptance:** a retail mover pressed against a wall shows flush at the
wall from acdream, matching the retail-observer view side-by-side.

View file

@ -462,6 +462,78 @@ start point). `ACDREAM_CAPTURE_RESOLVE=<path>` would let this be replayed
offline against the trajectory-replay harness. Both are existing tools;
no new instrumentation is needed to start.
### 2.4b — P3 diagnostic pass (2026-07-30): (a) and (b) ruled out by dat-free/dat-backed fixtures; (c) is the remaining candidate
Campaign P Slice P3 item 4 built the dat-free/dat-backed fixtures the
plan asked for (no live client) to discriminate the three candidates
directly, without waiting on a fresh live capture. Findings:
**(b) ruled out by code reading.** `RuntimeRemotePhysicsUpdater.Tick`'s
resolve gate is `rm.CellId != 0 && _physics.Engine.LandblockCount > 0`.
`RemoteMotion.CellId` reads `RuntimeEntityRecord.FullCellId` live (bound via
`RuntimePhysicsState`'s `readCell = () => record.FullCellId`). Every write
site for `FullCellId = 0` was traced (`RuntimeEntityObjectLifetime.cs`:
`TryApplyPickup` line ~505, `CommitAcceptedParentCellless` line ~591,
`CommitWithdrawal` line ~833) — all three are pickup/parent-attach/delete
paths, never reachable for a live, freely-moving remote creature or player
mid-session. For such a mover, `FullCellId` is set once by the first
`UpdatePosition` and never returns to 0 until the entity despawns. The
"one-frame grace" this gate names is therefore genuinely first-spawn-only,
as the code's own comment already claimed — not a hidden mid-session
tunneling window.
**(a) tested directly and does NOT reproduce, on two independent
geometries.** The unclamped stall-fail "tail delta" snap
(`InterpolationManager.ComputeStep`, `_failCount > StallFailCountThreshold`)
hands `ResolveWithTransition` a `targetPos` that can be an arbitrarily large
single-tick delta (the full remaining distance to the interpolation queue's
tail node, no speed cap). The concern was that this large delta might not
sweep correctly and could tunnel through solid geometry in one tick.
Tested directly, synthetic-sphere and real-BSP-wall geometry both agree:
- `Issue165RemoteWallPenetrationDiagnosticTests.SingleLargeTickJumpThroughObstacle_IsStillBlockedAtSurface`
(dat-free, synthetic creature sphere): a single resolve call spanning the
ENTIRE 2.4 m approach (the same total distance the pre-existing 30-tick
`SphereCollisionFamilyTests.GroundedSingleCreature_HeadOnPush_BlocksWithoutPenetration`
covers in 0.08 m increments) stops at the identical surface distance
(Y≈10.4810.54) — the sweep is not distance-limited and catches the large
jump exactly as it catches the small-step approach.
- `DoorCollisionApparatusTests.Apparatus_SingleLargeTickJump_DeadCenter_StillBlocksOnBSP`
(dat-backed, the REAL Holtburg door Setup `0x020019FF` + GfxObj
`0x010044B5` BSP slab used by the existing door apparatus tests): the
SAME dead-center front approach the 20-tick
`Apparatus_DeadCenter_FrontApproach_BlocksOnBSP` test covers, driven in
ONE resolve call for the full 2 m instead of 20 × 0.10 m ticks, reports
`CollisionNormalValid=true` and stops at Y=11.4 — before the door's front
face (Y≈11.99), matching the small-step result.
- A third fixture (`SingleLargeTickJumpStartingInsideObstacleOverlap_DoesNotAcceptTunneledCandidate`)
confirms that even a candidate whose START point is already inside a
solid obstacle's overlap zone does not sail through to a far target —
retail's own `validate_transition` restores `curr_pos` on a non-clean
step from inside an overlap (the #184 DO-NOT-RETRY precedent), and
acdream matches.
Both candidates the fixtures could test without a live capture are
therefore RULED OUT as the #165 mechanism: the sweep itself handles large
single-tick position deltas correctly against both object-collision
(ShadowObjectRegistry sphere) and BSP-wall geometry, and the resolve gate
cannot skip mid-session for a live mover.
**Conclusion: candidate (c) — render/interpolation presentation lag on the
App side — is the remaining candidate**, and it is NOT testable with a
physics-fixture-only pass: it is a claim about what gets DRAWN on a given
frame relative to the collision-corrected `PhysicsBody.Position`, which
requires an App-layer render-frame read (comparing the entity's presented
transform against its committed physics position across frames) — outside
Core/Runtime and outside what a dat-free/dat-backed fixture can observe.
Per the campaign plan ("otherwise write the diagnosis... and STOP"), this
item stops here: #165 stays OPEN with (a) and (b) struck from the
candidate list by the evidence above, and (c) named as the next concrete
step for whoever picks this up (an App-layer render-position vs.
physics-position diff across frames, or a live `ACDREAM_PROBE_RESOLVE`
capture if a fresh repro is available — see the diagnostic recommendation
above, still valid for confirming (c) live).
---
## 3. P3.3 — TS-23: PK/PKLite/Impenetrable mover bits