9.2 KiB
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:
- This handoff.
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).docs/research/2026-07-06-137-sliding-normal-lifecycle-audit.md— the #137 sliding-normal lifecycle audit (persist / absorb / clear; the synthetic-normal mechanism).docs/architecture/retail-divergence-register.md— rows TS-4 (steep-tangentSetSlidingNormal), TS-45 (retired —SphereCollisionleak), AP-83/84 (CSphere/ CylSphere family).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=falseon 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; gfxObj0x01000AC5(+ a neighbouring building partgfxObj 0x01000ACA,obj 0xF6821201, right at the seam). - Landblock
0xf682, cell0xF682002C. Login spawn@0xF682002Cis 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 creasecollisionNormal × contactPlane.Normalat 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.03but|result−cur|_xy < 0.4×that); readcollisionNormal,slidingNormal,contactPlane,walkableVertices.
- ResolveResult, filtered to IsPlayer). Analyse: find blocked-while-moving frames
(
ACDREAM_PROBE_BUILDING=1→[resolve-bldg]blocks (gfxObj, hit-poly plane + world verts). ⚠️ GOTCHA: the diagnostic Console output lands in the raw background-task.outputfile (the process stdout captured byrun_in_background), NOT theTee-Objectlog —Teebuffers and shows almost nothing. Grep the task.output. Also enables[bsp-test]/[bldg-channel](very high volume) — grep forresolve-bldg/hitPoly.- Optional:
ACDREAM_PROBE_RESOLVE=1/ACDREAM_PROBE_PUSH_BACK=1for 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 withACDREAM_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
SlideSphereand 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 cause99c22fad(+ filing29e01c38). - 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 port96ae2740.