feat(#184): remote-creature de-overlap — placement-snap + shadow-follows-resolved (visual gate passed)

Packed monster remotes interpenetrate in acdream but barely in retail on the same
ACE. Retail de-overlaps them CLIENT-side: it sweeps every remote creature every
tick against neighbours' LIVE resolved positions (the collision shadow == the
resolved m_position, re-registered every moved transition step), with the server
position a gentle catch-up target (CPhysicsObj::MoveOrTeleport 0x00516330), not a
hard-snap. The collision math was already faithful; the bug was the reconciliation
(hard-snap) + the movement model (synth-velocity) + a stale shadow.

A first attempt (reverted) enqueued EVERYTHING and left the shadow at the raw
server position — it gate-failed with invisible monsters (an unplaced body blipped
over a huge distance into the sweep -> garbage pos) and the player stuck on offset
shadows. This redo fixes both root causes, mechanism-proven in a Core test first:

- NPC UpdatePosition routes through MoveOrTeleport with a PLACEMENT-SNAP: the body
  is snapped to the server pos when it is not already near it (first UP, no
  Sequencer to consume the queue, >96 m, or |Body - worldPos| > 4 m); only near DR
  corrections enqueue. This restores the body's placement authority (no invisible
  monsters). Airborne keeps the authoritative hard-snap.
- Grounded movement drives the body from the interp CATCH-UP (ComputeOffset ->
  InterpolationManager::adjust_offset, REPLACE dichotomy) instead of synth-velocity
  (get_state_velocity / SERVERVEL); MovementManager::UseTime runs unconditionally.
- SHADOW-FOLLOWS-RESOLVED: after each tick's sweep the collision shadow is
  re-registered at the resolved body (SyncRemoteShadowToBody), movement-gated
  (|Body - LastShadowSyncPos| > 1 cm). The per-UP :5669 raw-pos sync is now
  PLAYERS-ONLY, so an NPC's shadow is only ever written to its resolved body ->
  neighbours de-overlap against resolved bodies, the spread PERSISTS, and collision
  == render (no stuck-on-nothing). Landing clears the interp queue.

Preserved: airborne path, sticky #171 (gate + StickyManager overwrite of the seeded
frame), omega, the #173 bounce, landing, the node_fail_counter watchdog, and Path A
(player remotes, untouched -- Slice 2 unifies it).

Tests (RemoteDeOverlapMechanismTests): converging pair settles STABLE at 0.86 m
(barely overlapping = the retail look) WITH the shadow-sync vs <0.40 m (full
overlap) WITHOUT it; a third test drives the REAL InterpolationManager loop and
confirms the sweep absorbs the stall-blip (no pop-into-neighbour). 2-lens Opus
review (CONCERNS) addressed: movement-gated re-flood for the town-FPS risk;
players-only :5669; the blip-absorption test.

Register: retires TS-41 (SERVERVEL synth-velocity -> catch-up), narrows TS-44 (NPC
UP unified onto the interp queue; gate kept for orientation), adds AP-86
(shadow-follows-resolved impl) + AP-87 (MoveOrTeleport 4 m/no-Sequencer placement
snap). Known residual: the de-overlap sweep uses the human sphere for the mover, so
large creatures de-overlap at human radii (TS-46; Slice 3 plumbs Setup dims).

Visual gate PASSED (user: monsters visible + spacing much better). Core 2620 /
App 741 green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Erik 2026-07-07 22:36:54 +02:00
parent 7f7a78d3ea
commit 37a94e1fa4
4 changed files with 558 additions and 99 deletions

View file

@ -46,6 +46,51 @@ Copy this block when adding a new issue:
---
## #184 — Remote monsters overlap (arms interpenetrating) in a crowd; retail barely overlaps
**Status:** IN-PROGRESS (redo implemented + reviewed — awaiting the user visual re-gate)
**Severity:** MEDIUM
**Filed:** 2026-07-07
**Component:** physics / remote dead-reckoning
**Description:** Side-by-side vs retail on the SAME ACE, monsters packed around the player
interpenetrate (arms) in acdream where retail keeps them barely overlapping — the "no room to
slide out" feel. This is the REMOTE-creature thread — distinct from #182 (the LOCAL-player wedge).
**Root cause / status:** Retail de-overlaps remotes CLIENT-side: it runs the collision sweep on
every remote every tick against neighbours' LIVE resolved positions (the shadow == the resolved
`m_position`, re-registered every moved step), with the server pos a GENTLE catch-up target
(`MoveOrTeleport` 0x00516330), not a hard-snap. **A first attempt (commit `9c0849dd`) GATE-FAILED
(invisible monsters + player stuck on them) and was REVERTED** — it (a) replaced the NPC UP
hard-snap with enqueue-everything, losing the body's PLACEMENT authority (an unplaced body blipped
over a huge distance into the sweep → garbage pos → invisible), and (b) left the shadow at the raw
server pos, so neighbours de-overlapped against overlapping shadows and the player collided with an
offset shadow. **REDO (this session):** (1) NPC UP `MoveOrTeleport` with a PLACEMENT-SNAP (snap when
the body isn't already near the target — first UP / no-Sequencer / far / >4 m; enqueue only near);
(2) grounded movement = interp catch-up feeding the kept sweep; (3) **shadow-follows-resolved**
the shadow is re-registered at the resolved body every moving tick (`SyncRemoteShadowToBody`,
movement-gated; `:5669` raw sync now players-only). Retires TS-41, narrows TS-44, adds AP-86/AP-87.
Mechanism proven in Core (`RemoteDeOverlapMechanismTests`: with-sync 0.86 m stable / without <0.40 m;
the real-interp loop absorbs the stall-blip). 2-lens Opus review CONCERNS → all addressed
(movement-gate for the town-FPS risk; players-only `:5669`; the blip-absorption test). Core 2620 /
App 741 green.
**Known residual:** the de-overlap sweep uses the fixed human sphere (R 0.48) for the mover, so
large packed creatures de-overlap at human radii (**TS-46**; Setup-derived dims = Slice 3).
**Files:** `src/AcDream.App/Rendering/GameWindow.cs` (NPC `OnLivePositionUpdated` ~:5960 MoveOrTeleport
+ shadow sync; Path B tick catch-up/sweep/shadow-sync; `SyncRemoteShadowToBody`).
**Research:** design `docs/superpowers/specs/2026-07-07-remote-creature-deoverlap-design.md`; handoff
`docs/research/2026-07-07-remote-creature-deoverlap-handoff.md`; digest `claude-memory/project_physics_collision_digest.md` (2026-07-07 top).
**Acceptance:** side-by-side vs retail — packed monsters spread to retail spacing (arms no longer
interpenetrating), monsters VISIBLE (not stuck-on-nothing), no ~3 Hz jitter in a pack, no town-FPS
drop; remotes don't rubber-band/desync; sticky #171 facing unbroken; walk/run/jump/land unchanged.
Then Slice 2 (unify Path A + `RemotePhysicsUpdater` extraction) and Slice 3 (Setup-derived sphere).
---
## #183 — Floating distant scenery: trees from another biome hover in the distance
**Status:** OPEN