docs(#185): handoff for the outdoor-stairs phantom fix (synthetic sliding-normal, #137/TS-4 family)
This commit is contained in:
parent
99c22fad48
commit
4a067e34a9
1 changed files with 158 additions and 0 deletions
158
docs/research/2026-07-08-185-outdoor-stairs-phantom-handoff.md
Normal file
158
docs/research/2026-07-08-185-outdoor-stairs-phantom-handoff.md
Normal file
|
|
@ -0,0 +1,158 @@
|
||||||
|
# Handoff: #185 — LOCAL player jams half-way up outdoor stairs (house on stilts)
|
||||||
|
|
||||||
|
**Date:** 2026-07-08 · **Status:** ROOT CAUSE PINNED (definitive, probe-confirmed) — fix NOT
|
||||||
|
started. The fix touches the **frozen collision internals** (the #137 / TS-4 sliding-normal
|
||||||
|
family), so per the project workflow it needs a **brainstorming gate BEFORE any code**. This
|
||||||
|
handoff is the investigation; do the brainstorm + fix in the new session.
|
||||||
|
|
||||||
|
**Read these first, in order:**
|
||||||
|
1. This handoff.
|
||||||
|
2. `claude-memory/project_physics_collision_digest.md` — the #137 banners + the DO-NOT-RETRY
|
||||||
|
table + the sliding-normal-provenance lessons (this is the SSOT for the collision arc).
|
||||||
|
3. `docs/research/2026-07-06-137-sliding-normal-lifecycle-audit.md` — the #137 sliding-normal
|
||||||
|
lifecycle audit (persist / absorb / clear; the synthetic-normal mechanism).
|
||||||
|
4. `docs/architecture/retail-divergence-register.md` — rows **TS-4** (steep-tangent
|
||||||
|
`SetSlidingNormal`), **TS-45** (retired — `SphereCollision` leak), **AP-83/84** (CSphere/
|
||||||
|
CylSphere family).
|
||||||
|
5. `docs/ISSUES.md` #185 (the one-paragraph version).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 0. Symptom
|
||||||
|
|
||||||
|
Running the **LOCAL player** up the outside stairs of a house-on-stilts, you hit an invisible
|
||||||
|
block **half-way up** — you shuffle sideways in place but cannot advance up. **A jump clears
|
||||||
|
it** and you continue up the rest of the stairs normally. Pre-existing (seen before the #184
|
||||||
|
work); unrelated to the remote de-overlap thread.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Root cause — DEFINITIVE (probe-confirmed): a SYNTHETIC sliding normal, not geometry
|
||||||
|
|
||||||
|
The stairs are **real, walkable 38.7° tread quads** in the building collision mesh. The player
|
||||||
|
walks straight up them. At **one convex tread edge** (near a building-part seam), the foot
|
||||||
|
sphere straddles the edge, hits the lower tread poly, and the collision **RESPONSE fabricates a
|
||||||
|
perpendicular normal** — the tread normal **rotated 90°** — which is then flattened to a pure
|
||||||
|
horizontal `(0,1,0)` sliding plane. The player's +Y (up-stairs) movement is fully absorbed by
|
||||||
|
that plane → **sideways-only slide** (pinned in Y and Z). The jump clears it because the jump's
|
||||||
|
+Z velocity is not in the horizontal slide plane.
|
||||||
|
|
||||||
|
**It is NOT geometry, NOT a step-height / step-up-budget issue, NOT a missing riser.**
|
||||||
|
|
||||||
|
### The evidence
|
||||||
|
|
||||||
|
**A — `ACDREAM_CAPTURE_RESOLVE` (structured player-resolve JSONL).** At the jam the player is
|
||||||
|
pinned at **Y=77.812, Z=61.404** and only slides in X (~0.10 m of the ~0.68 m requested).
|
||||||
|
Constant across every stuck frame:
|
||||||
|
- `collisionNormal = (0.00, 0.78, 0.62)` — a ~52° face (**NOT** the #137 negated-run-direction).
|
||||||
|
- `slidingNormal = (0, 1, 0)` — that collision normal with **Z zeroed + renormalised** (horizontalised).
|
||||||
|
- `contactPlane / walkablePlane = (0, -0.625, 0.781)` — the 38.7° tread the player stands on.
|
||||||
|
- `result.collisionNormalValid=false` on the zero-move frames (the offset is pre-absorbed → target==current).
|
||||||
|
|
||||||
|
**B — `ACDREAM_PROBE_BUILDING=1` (`[resolve-bldg]` hit-poly log).** The decisive read: at the
|
||||||
|
jam, **ALL 156 building collisions are ONE polygon** — the 38.7° tread:
|
||||||
|
```
|
||||||
|
[resolve-bldg] obj=0xF6822103 entityId=0x00F68221 partIdx=3
|
||||||
|
gfxObj=0x01000AC5 hasPhys=True bspR=1.05 vAabbR=1.15
|
||||||
|
entOrigin_lb=(132.0,77.2,60.4)
|
||||||
|
hitPoly: numVerts=4 plane=(-0.625,0.000,0.781,-0.312)
|
||||||
|
v0_world=(132.75, 77.50, 61.01) v1_world=(131.25, 77.50, 61.01)
|
||||||
|
v2_world=(131.25, 77.00, 60.62) v3_world=(132.75, 77.00, 60.62)
|
||||||
|
```
|
||||||
|
- **No riser / wall polygon exists in the collision mesh** — the only distinct hit-poly plane
|
||||||
|
across all 156 hits is that tread.
|
||||||
|
- Local plane `(-0.625,0,0.781)` maps (building rotation ≈ +90° about Z) to the **world tread
|
||||||
|
normal `(0,-0.625,0.781)`** — matching capture A's contact plane.
|
||||||
|
- The capture's blocking normal `(0,0.78,0.62)` is that **tread normal rotated 90°**
|
||||||
|
(perpendicular: dot ≈ 0). It does **not** exist in the dat → **synthetic**, fabricated by the
|
||||||
|
collision response.
|
||||||
|
|
||||||
|
### Geometry facts (for the repro / test fixture)
|
||||||
|
- Object `0xF6822103` (entity), `entityId=0x00F68221`, `partIdx=3`; **gfxObj `0x01000AC5`**
|
||||||
|
(+ a neighbouring building part `gfxObj 0x01000ACA`, `obj 0xF6821201`, right at the seam).
|
||||||
|
- Landblock `0xf682`, cell `0xF682002C`. **Login spawn `@0xF682002C` is at the house** (the
|
||||||
|
streaming recenters to (246,130) on spawn), so the stairs load immediately — no travel needed.
|
||||||
|
- Jam world position ≈ **(132, 77.8, 61.4)**; the involved tread quad world verts are listed
|
||||||
|
above (X 131.25–132.75, Y 77.00–77.50, Z 60.62–61.01; ~0.4 m rise / 0.5 m run = 38.7°).
|
||||||
|
- The player collides as a **body SPHERE** (human Setup 0x02000001; r≈0.48), so the relevant
|
||||||
|
response is the **CSphere family** (dispatcher `0x00537A80`, `slide_sphere 0x00537440`) — the
|
||||||
|
one the #182 port (`96ae2740`) rebuilt.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Classification + where the fix lives
|
||||||
|
|
||||||
|
**#137 / TS-4 "synthetic sliding-normal at a convex edge/seam" family — stair edition.** Same
|
||||||
|
class as the corridor-phantom work (`a11df5b8`, `e8651b38`) and the seam-shake fix
|
||||||
|
(`d4869154`). The building geometry is fine; our slide/validate response invents the wall.
|
||||||
|
|
||||||
|
**The core open question for the brainstorm:** *where does the 90°-rotated normal get
|
||||||
|
fabricated?* Candidates (trace these):
|
||||||
|
- `CSphere::slide_sphere` (`0x00537440`) — the crease `collisionNormal × contactPlane.Normal`
|
||||||
|
at a convex edge (a cross product IS a 90° rotation; the tread × something could yield the
|
||||||
|
perpendicular normal). **Prime suspect.**
|
||||||
|
- `validate_transition` (`0x0050aa70`) sliding-normal epilogue (the only in-transition
|
||||||
|
sliding-normal writer per the #137 audit) — is it persisting a fabricated normal?
|
||||||
|
- The step-up / neg-poly path at a convex edge (the seam-shake class — a lifted CheckPos lost,
|
||||||
|
re-test grazes the neighbour tread).
|
||||||
|
- The CSphere `collide_with_point` / edge-vertex case at the tread top edge.
|
||||||
|
|
||||||
|
The `(0,1,0)` horizontalisation of `(0,0.78,0.62)` (Z zeroed) is the **absorb** step — the
|
||||||
|
player's forward offset is projected out. Find who WRITES `(0,0.78,0.62)` first.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Apparatus (reusable — RE-CAPTURE in the new session; the temp files won't persist)
|
||||||
|
|
||||||
|
Repro: launch (canonical live command, CLAUDE.md), spawn `@0xF682002C`, run up the outside
|
||||||
|
stairs, hold forward against the jam (~world 132,77.8,61.4) for a couple of seconds.
|
||||||
|
|
||||||
|
- `ACDREAM_CAPTURE_RESOLVE=<path>` → per-player-resolve JSONL (inputs + PhysicsBody before/after
|
||||||
|
+ ResolveResult, filtered to IsPlayer). Analyse: find blocked-while-moving frames
|
||||||
|
(`|target−cur|_xy > 0.03` but `|result−cur|_xy < 0.4×that`); read `collisionNormal`,
|
||||||
|
`slidingNormal`, `contactPlane`, `walkableVertices`.
|
||||||
|
- `ACDREAM_PROBE_BUILDING=1` → `[resolve-bldg]` blocks (gfxObj, hit-poly plane + world verts).
|
||||||
|
**⚠️ GOTCHA:** the diagnostic Console output lands in the **raw background-task `.output`
|
||||||
|
file** (the process stdout captured by `run_in_background`), **NOT** the `Tee-Object` log —
|
||||||
|
`Tee` buffers and shows almost nothing. Grep the task `.output`. Also enables `[bsp-test]` /
|
||||||
|
`[bldg-channel]` (very high volume) — grep for `resolve-bldg` / `hitPoly`.
|
||||||
|
- Optional: `ACDREAM_PROBE_RESOLVE=1` / `ACDREAM_PROBE_PUSH_BACK=1` for the step-up / push-back
|
||||||
|
dispatch internals if the fabrication site needs a finer trace.
|
||||||
|
- Build a **dat-backed replay test** reproducing the tread-edge hit to the millimetre (pattern:
|
||||||
|
`Issue137CorridorSeamReplayTests`) — the fix's red→green pin. Dump the cell/GfxObj with
|
||||||
|
`ACDREAM_DUMP_GFXOBJS=0x01000AC5` (+ `_DIR`) for the fixture.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. DO-NOT-RETRY
|
||||||
|
|
||||||
|
- **Do NOT touch the step-up budget or step-up logic.** There is no riser here — it's a
|
||||||
|
continuous walkable 38.7° ramp of tread quads; the jam is a fabricated normal, not a tall
|
||||||
|
step. (The register/digest already warned: stair sticks are the sliding-normal family; six
|
||||||
|
past step-up attempts were all wrong.)
|
||||||
|
- **Do NOT "fix the geometry" / mesh generation.** All 156 building hits are ONE real, walkable
|
||||||
|
tread poly; the dat is fine. There is no bad 52° face to remove.
|
||||||
|
- **Do NOT read the Tee'd launch log for `[resolve-bldg]`** — it's buffered/empty; read the raw
|
||||||
|
task `.output` (§3 gotcha).
|
||||||
|
- **Do NOT ship a per-frame sliding-normal clear or a small-offset-abort patch** (the #137
|
||||||
|
DO-NOT-RETRY): fix the normal's PROVENANCE, not the symptom. A leaked/fabricated normal +
|
||||||
|
success writeback = an absorbing wedge at empty space — the exact pattern the corridor fix
|
||||||
|
addressed by routing through the faithful `SlideSphere` and returning COLLIDED on the
|
||||||
|
opposing branch.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Acceptance
|
||||||
|
- The LOCAL player **walks up the full outdoor staircase without jamming or jumping**; no
|
||||||
|
sideways-slide pin at the ~Y 77.5 / Z 61.0 tread edge.
|
||||||
|
- **Regression pass** (the whole point — this is the shared slide response): indoor cellar/
|
||||||
|
corridor stairs (#137 repros), other outdoor buildings, ramps, and flat ground — **no new
|
||||||
|
phantom blocks AND no players sliding through walkable ramps/edges.** Un-skip / keep green the
|
||||||
|
`Issue137*` replay suites.
|
||||||
|
- Register bookkeeping in the fix commit (amend TS-4 / add a row as the change dictates).
|
||||||
|
|
||||||
|
## 6. Pointers
|
||||||
|
- ISSUES `#185`; committed root cause `99c22fad` (+ filing `29e01c38`).
|
||||||
|
- Collision digest DO-NOT-RETRY table + the #137 corridor/seam banners.
|
||||||
|
- Register TS-4 / TS-45 / AP-83 / AP-84; #137 fixes `a11df5b8`, `e8651b38`, `d4869154`;
|
||||||
|
CSphere port `96ae2740`.
|
||||||
Loading…
Add table
Add a link
Reference in a new issue