docs(issues): file #146/#147 (post-portal + far-town wall collision) + record #138-B avatar-vanish fix

Issue A ("no collision after death/portal") investigated capture-first and
confirmed to be TWO distinct bugs (user-corroborated + ACDREAM_CAPTURE_RESOLVE
data, 255,832 player resolves):

- #147 (HIGH) far-town (Arwic): grounded only 3% of resolves vs 100% at
  Holtburg/dungeon; city/perimeter walls never block even on a fresh login —
  the #145 streaming-relative-frame family. Scope as a brainstormed #145
  sub-phase, not a one-commit fix.
- #146 (MEDIUM) Holtburg: building/house-wall collision works on fresh login
  but is lost after portaling in. The [bldg-channel] probe fires post-portal
  (building is cached + reached) yet result=OK as the foot-sphere walks into
  the wall — the building WorldTransform is baked from _liveCenter at cache
  time and CacheBuilding is idempotent, so the recenter leaves a stale offset.

Also recorded against #138 that symptom B (avatar vanish) was root-caused and
fixed in afd5f2a (RelocateEntity-during-PortalSpace), not just the pending-
bucket rescue candidate.

No guess-patches applied to the collision code (DO-NOT-RETRY area).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Erik 2026-06-24 13:11:42 +02:00
parent afd5f2a012
commit 5b3e946b8b

View file

@ -46,6 +46,46 @@ Copy this block when adding a new issue:
--- ---
## #147 — Far-town (Arwic) collision broken at login: terrain barely grounds + city/perimeter walls never block
**Status:** OPEN
**Severity:** HIGH (collision largely absent in far towns; #145-frame family)
**Filed:** 2026-06-24
**Component:** physics — far-town streaming-relative collision frame (#145 family)
**Description (user-observed 2026-06-24):** In Arwic (a far town reached via the Town Network), the city/perimeter walls have NO collision even on a FRESH login there — not portal-churn, it's fundamental. Buildings' own collision channel does fire (`[bldg-channel]` emits at Arwic), but the player walks through the city walls and the terrain barely grounds. Distinct from #146 (which is portal-in-only at Holtburg).
**Root cause (capture-confirmed symptom; #145-family root):** The `ACDREAM_CAPTURE_RESOLVE` capture (`a-wall-resolve.jsonl`, 255,832 player resolves) shows the player is grounded only **3% of resolves at Arwic (0xC6A9)** vs **100% at Holtburg/dungeon** — the terrain collision query misses the ground almost entirely (the player doesn't fall only because zero-move resolves return the "no terrain loaded → pass-through" branch instead of a contact plane). This is the streaming-relative-frame misalignment at a far town: positions are stored relative to `_liveCenter`, the far-town frame/origin is wrong, so terrain + static-wall collision queries hit the wrong place. The physics digest already names the fix as **#145 Option B** — port retail's cell-relative `Position` frame and make `_liveCenter` render-only (multi-commit, brainstorm-gated). The city-perimeter-wall static class may also need its own collision-registration check.
**Files:** `src/AcDream.Core/Physics/CellTransit.cs` (outdoor pick + `TryGetTerrainOrigin` frame), `src/AcDream.Core/Physics/PhysicsEngine.cs` (terrain sample/frame), `src/AcDream.App/Rendering/GameWindow.cs` (`_liveCenter` placement seam).
**Research:** capture `a-wall-resolve.jsonl` (Arwic 3% grounded vs Holtburg 100%); `claude-memory/project_physics_collision_digest.md` #145 banner; `docs/research/2026-06-21-145-cell-relative-physics-frame-handoff.md`. **SCOPE AS A BRAINSTORMED #145 SUB-PHASE — not a one-commit fix; no guess-patches (DO-NOT-RETRY collision area).**
**Acceptance:** in a far town (Arwic), terrain grounds like Holtburg (~100% of resolves) and city/perimeter walls block — matching retail.
---
## #146 — Building/house-wall collision lost after portaling INTO a town (works on fresh login)
**Status:** OPEN
**Severity:** MEDIUM (clip through house/building walls after every portal-in; doors still block; terrain solid)
**Filed:** 2026-06-24
**Component:** physics — building collision channel + streaming-relative frame (teleport recenter)
**Description (user-observed 2026-06-24):** Static building/house walls block normally on a FRESH login to a town (Holtburg confirmed), but after logging in elsewhere and PORTALING into the town, the same walls no longer block — you clip straight through. Server-spawned DOORS still block (they register their own collision via CreateObject), and terrain stays solid — so the symptom is specifically dat-static building-wall collision after a portal-in.
**Root cause (capture-narrowed, one probe from confirmed):** The building collision channel (`TransitionTypes.FindBuildingCollisions``PhysicsDataCache.GetBuilding(cellId)` → BSP test vs `BuildingPhysics.WorldTransform`) IS reached after a portal-in — the `[bldg-channel]` probe fires at Holtburg post-portal (`cell=0xA9B40022 model=0x01000C17`) — but every test returns `result=OK` (no penetration) as the foot-sphere walks into the wall (`arwic-to-holtburg-bldg-capture.log`). The building's `WorldTransform` is computed from `_liveCenter` AT CACHE TIME (`GameWindow.cs` ~6969: `building.Frame.Origin + origin`, `origin = (lb _liveCenter)·192`) and `CacheBuilding` is IDEMPOTENT (`PhysicsDataCache.cs:444` `if (_buildings.ContainsKey) return;` — never re-cached, never cleared on unload). A teleport recenters `_liveCenter`, so the cached shell BSP can sit at a stale world offset → the sphere never penetrates → no block. Same streaming-relative-frame family as #145/#147.
**Next step (one probe before fixing):** add `building.WorldTransform.Translation` to the `[bldg-channel]` line; if it's offset from the visual building / player position after a portal-in, the stale-transform root is confirmed. Fix = invalidate `_buildings[landcell]` on `RemoveLandblock` so it re-caches with the current frame on reload (targeted), or store the transform cell-relative (the #145 frame). **No guess-patch (DO-NOT-RETRY collision area).**
**Files:** `src/AcDream.App/Rendering/GameWindow.cs` (~69447003 building cache loop), `src/AcDream.Core/Physics/PhysicsDataCache.cs` (`CacheBuilding`/`GetBuilding`/`_buildings`, ~441464), `src/AcDream.Core/Physics/TransitionTypes.cs` (`FindBuildingCollisions` 2805, `[bldg-channel]` probe 2874).
**Research:** captures `a-wall-resolve.jsonl` (Holtburg 100% grounded, 876/882 wall-moves no block, doors collide), `arwic-to-holtburg-bldg-capture.log` (post-portal `result=OK`), `holtburg-bldg-capture.log` (fresh-login channel fires). Relates to `claude-memory/project_physics_collision_digest.md` + #145/#147.
**Acceptance:** after portaling into Holtburg from elsewhere, building/house walls block exactly as on a fresh login.
---
## #145 — Portals only work once per session (can't run in, run out, re-enter) ## #145 — Portals only work once per session (can't run in, run out, re-enter)
**Status:** 🟡 PARTIALLY FIXED — REOPENED 2026-06-21 (a NARROWER residual). The cell-relative carried-anchor (Option B, Slices 13+7, `438bb68``403a338`) **fixes the cascade for a STREAMED-terrain arrival** (live-verified: `+Je` ran through ~10 far-town landblocks across 2 sessions, zero march). BUT a third live session found the cascade **RECURS when a teleport arrives onto a NOT-YET-STREAMED landblock NEAR AN EDGE.** Evidence (`launch5.log`): arrival `[snap] claim=0xC98C0028 pos=(113.666,190.259,22.010) branch=NO-LANDBLOCK (lbs=0) -> verbatim` (local Y=190.3, **1.7 m from the 192 boundary**) → cell marches `0xC98C(lbY 0x8C) → 0xC9FE(lbY 0xFE)` +2/tick while Z free-falls 22→19; the outbound wire then sends the marched cell `C9FE0031` with compensating garbage `localY=21684` → ACE `MOVEMENT SPEED` / `failed transition` → player stuck. Contrast same session: hub `0x00070133` VALIDATED→fine; far town `0x977B000C` arrived unstreamed but **mid-block** (Y=73.8) → Z free-fall only, NO march. **ROOT (hypothesis, NOT yet apparatus-confirmed):** outdoor teleport places immediately but the destination streams a few ticks later (`streaming: dungeon EXIT-expand -> (201,140)` logs just after placement); during that gap the resolve runs against an empty world, and at an edge the player crosses into the unstreamed neighbour where the carried anchor (which computes to `(0,0,0)` at the recentered center) gives no protection. Same root as the Z free-fall (`#135`/`#138` placed-but-unstreamed gap). **DO NOT guess-patch** (original #145 burned 5 attempts) — needs a capture + an anchor/guard diagnostic at the crossing FIRST. Likely fix shape: a streaming-gap HOLD (freeze the per-tick resolve — no march, no fall — until the player's landblock loads; the async equivalent of retail's synchronous load), which would fix both symptoms. Suites still green; `TeleportFarTownRunawayTests` covers the STREAMED case only. [Prior "fixed" banner was premature — 2 clean sessions wasn't enough for a streaming-timing bug.] **Status:** 🟡 PARTIALLY FIXED — REOPENED 2026-06-21 (a NARROWER residual). The cell-relative carried-anchor (Option B, Slices 13+7, `438bb68``403a338`) **fixes the cascade for a STREAMED-terrain arrival** (live-verified: `+Je` ran through ~10 far-town landblocks across 2 sessions, zero march). BUT a third live session found the cascade **RECURS when a teleport arrives onto a NOT-YET-STREAMED landblock NEAR AN EDGE.** Evidence (`launch5.log`): arrival `[snap] claim=0xC98C0028 pos=(113.666,190.259,22.010) branch=NO-LANDBLOCK (lbs=0) -> verbatim` (local Y=190.3, **1.7 m from the 192 boundary**) → cell marches `0xC98C(lbY 0x8C) → 0xC9FE(lbY 0xFE)` +2/tick while Z free-falls 22→19; the outbound wire then sends the marched cell `C9FE0031` with compensating garbage `localY=21684` → ACE `MOVEMENT SPEED` / `failed transition` → player stuck. Contrast same session: hub `0x00070133` VALIDATED→fine; far town `0x977B000C` arrived unstreamed but **mid-block** (Y=73.8) → Z free-fall only, NO march. **ROOT (hypothesis, NOT yet apparatus-confirmed):** outdoor teleport places immediately but the destination streams a few ticks later (`streaming: dungeon EXIT-expand -> (201,140)` logs just after placement); during that gap the resolve runs against an empty world, and at an edge the player crosses into the unstreamed neighbour where the carried anchor (which computes to `(0,0,0)` at the recentered center) gives no protection. Same root as the Z free-fall (`#135`/`#138` placed-but-unstreamed gap). **DO NOT guess-patch** (original #145 burned 5 attempts) — needs a capture + an anchor/guard diagnostic at the crossing FIRST. Likely fix shape: a streaming-gap HOLD (freeze the per-tick resolve — no march, no fall — until the player's landblock loads; the async equivalent of retail's synchronous load), which would fix both symptoms. Suites still green; `TeleportFarTownRunawayTests` covers the STREAMED case only. [Prior "fixed" banner was premature — 2 clean sessions wasn't enough for a streaming-timing bug.]
@ -311,6 +351,8 @@ Both are the **entity-lifecycle/render path across a teleport**, NOT the streami
**Implementation (B — player vanish, candidate):** `GpuWorldState.RemoveLandblock` rescued persistent entities only from `_loaded`, silently dropping a persistent entity sitting in the **pending bucket** (the player is re-injected via `AppendLiveEntity` every frame; right after a teleport its landblock hasn't streamed yet → pending; if that landblock is then unloaded mid-churn the player was dropped → "vanishes after a couple round-trips"). Fix: rescue persistent pending entries too. Tests `RemoveLandblock_RescuesPersistentEntity_FromPendingBucket` (+ negative). This is a provable correctness fix for the "persistent ⇒ survives unload" invariant; it is the leading candidate for symptom B but needs user re-verification to confirm it is the complete cause. **Implementation (B — player vanish, candidate):** `GpuWorldState.RemoveLandblock` rescued persistent entities only from `_loaded`, silently dropping a persistent entity sitting in the **pending bucket** (the player is re-injected via `AppendLiveEntity` every frame; right after a teleport its landblock hasn't streamed yet → pending; if that landblock is then unloaded mid-churn the player was dropped → "vanishes after a couple round-trips"). Fix: rescue persistent pending entries too. Tests `RemoveLandblock_RescuesPersistentEntity_FromPendingBucket` (+ negative). This is a provable correctness fix for the "persistent ⇒ survives unload" invariant; it is the leading candidate for symptom B but needs user re-verification to confirm it is the complete cause.
**UPDATE 2026-06-24 — symptom B (avatar vanish) ACTUAL root found + FIXED (`afd5f2a`, user-confirmed + trace-verified):** the pending-bucket rescue above was necessary but NOT the whole cause. The real culprit: the per-frame avatar-sync (`GameWindow` ~:8018) calls `GpuWorldState.RelocateEntity` using the player controller's cell, which stays the **FROZEN SOURCE cell** until `PlaceTeleportArrival` materializes the destination. Mid-transit it dragged the avatar — which the teleport's `DrainRescued` re-inject had correctly placed at the destination center — back into the now-UNLOADED source landblock's pending bucket, where nothing recovers it (`RelocateEntity` only scans `_loaded`). Fix: skip the per-frame relocate while `_playerController.State == PortalSpace`. Verified by a new env-gated avatar-lifecycle probe (`ACDREAM_PROBE_ENT` / `EntityVanishProbe`): pre-fix `[ent] APPEND lb=0x0007FFFF(source) -> PENDING -> DRAWSET ABSENT` never recovered; post-fix every teleport goes `RESCUE -> ABSENT -> APPEND(destination) -> PRESENT` and stays drawn. **The remaining #138 collision residual split into #146 (Holtburg portal-in building walls) + #147 (Arwic far-town frame).**
**Acceptance:** portal out of the 0x0007 dungeon → full outdoor world streams (trees/scenery present), **server objects (doors/NPCs/portals) render**, **own avatar renders across repeated round-trips**, collision works, position tracks (no avatar-vs-camera desync). **Acceptance:** portal out of the 0x0007 dungeon → full outdoor world streams (trees/scenery present), **server objects (doors/NPCs/portals) render**, **own avatar renders across repeated round-trips**, collision works, position tracks (no avatar-vs-camera desync).
--- ---