docs(#185): close — REAL root cause = shadow part-id uint32 overflow (07c5b832)

Live gate passed. ISSUES #185 moved to DONE with the corrected root cause
(registration overflow, not the collision response). The handoff's convex-edge
theory and design-v1's grounding-retention theory are both recorded as superseded.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Erik 2026-07-08 10:17:08 +02:00
parent 07c5b832cf
commit 5a9df00aa9

View file

@ -46,56 +46,45 @@ Copy this block when adding a new issue:
---
## #185 — LOCAL player jams half-way up outdoor stairs (house on stilts); a jump clears it
## #185 [DONE 2026-07-08 · `07c5b832`] LOCAL player jams half-way up outdoor stairs (house on stilts); a jump clears it
**Status:** OPEN (investigated, root cause characterised — NOT yet fixed; a fix touches the frozen collision internals → brainstorm gate first)
**Severity:** MEDIUM
**Filed:** 2026-07-08
**Component:** physics / collision (building/stair BSP + step-up vs slide classification)
**Status:** DONE (live gate PASSED — "OK works"). Root cause was NOT the collision response.
**Severity:** MEDIUM · **Filed:** 2026-07-08 · **Component:** physics / collision-registration (landblock shadow objects)
**Description:** Running the LOCAL player up the outside stairs of a house-on-stilts, you hit
an invisible block **half-way up** that you cannot walk through — you shuffle sideways but
never advance. **Jumping clears it** and you continue up normally. Pre-existing (observed
before the #184 work); unrelated to the remote de-overlap thread. Building object
`0xf6822103`, landblock `0xf682`, jam at world ≈ (132, 77.8, 61.4), cell `0xF682002C`.
**Description:** Running up the outside stairs of a house-on-stilts you hit an invisible wall
"in the middle of the stairs" (steps look unbroken) — you shuffle sideways, never advance; a
jump clears it. Landblock `0xf682`, jam ≈ world (132, 77.9, 61.5), cell `0xF682002C`.
**Root cause / status (from `ACDREAM_CAPTURE_RESOLVE` at the jam):** The staircase is a
fairly uniform ~0.4 m-rise / **38.7°** flight of tilted tread faces (`n=(0,-0.62,0.78)`),
which is under the ~49° walkable cutoff, so the player *glides* straight up it (long
no-collision stretches; the wireframe shows discrete stacked step faces). At **one junction
(~Z 61.4)** there is an anomalous **~52° face** (`collisionNormal=(0,0.78,0.62)`, constant
across every stuck frame → a real stable face, NOT the #137 synthetic-negated-run-direction
normal). 52° is just past the walkable cutoff, so the sweep treats it as a **wall**: it
**flattens that normal to a pure-horizontal `(0,1,0)` sliding normal** (Z zeroed) and slides
the player sideways in X, pinning Y+Z — instead of recognising it as a normal ~0.4 m **step**
and stepping the player up onto the next tread. The jump clears it because the jump's +Z
velocity is not in the horizontal `(0,1,0)` slide plane. The staircase continues climbable
above the jam (more 38.7° treads → a flat porch at Z 63.6) — so it is ONE localised spot,
not a general stair failure. Classic **#137 / building-collision-mesh junction-anomaly family**
(TS-4 sliding-normal-provenance), stair edition.
**Root cause (REAL — a uint32 overflow in the shadow-registry part-id, `07c5b832`):**
`GameWindow.cs` registered each landblock BSP part with a synthetic id `entity.Id * 256u +
partIndex`. That `<< 8` **overflows uint32** for class-prefixed landblock ids
(`0x40`/`0x80`/`0xC0`…) and drops the prefix byte, so different-class entities sharing the low
24 bits **collide on one shadow part-id**; `Register`'s deregister-then-insert silently
overwrites one entity's collision (`0xF6822100 ← {0x40F68221, 0xC0F68221}` — 23 such collisions
in landblock `0xF682` alone). Three mid-staircase steps therefore **rendered but had NO
collision**; the player floats into the hole and the (retail-faithful) `PrecipiceSlide` wedge
fires at the walkable edge = the "invisible wall." **The wedge was a symptom, not the cause.**
The two earlier theories in this session — the handoff's "convex-tread-edge synthetic normal"
and design-v1's "grounding-retention at a coplanar seam" — were both SUPERSEDED by the live
`[entity-source]`/`[bsp-test]` capture (#3) that mapped the collision hole + the 23 id
collisions.
**RESOLVED (artifact, not geometry) — `ACDREAM_PROBE_BUILDING=1` hit-poly capture:** at the
jam, ALL 156 building collisions are ONE polygon — a real, walkable **38.7° tread quad**
(`gfxObj=0x01000AC5`, `obj=0xF6822103`, `partIdx=3`, local plane `(-0.625,0,0.781)`; world
verts X 131.25132.75, Y 77.0077.50, Z 60.6261.01). **There is NO riser / wall polygon in
the collision mesh at all.** The 52° "wall" normal from the resolve capture (`(0,0.78,0.62)`)
is that tread normal **rotated exactly 90°** (perpendicular; dot ≈ 0) — it does not exist in
the dat. So the block is a **synthetic sliding normal fabricated by our collision RESPONSE**,
not real geometry and not a step-height/budget issue: the foot sphere straddles a convex tread
edge (near a building-part seam — `partIdx=3`, neighbouring `gfxObj=0x01000ACA` present), hits
the lower tread poly, and the slide/validate chain invents a perpendicular normal, flattens it
to horizontal `(0,1,0)`, and slides the player sideways. **Confirmed #137 / TS-4
sliding-normal-provenance family** (phantom wall at a convex edge/seam), stair edition. Fix
lives in that family (where the perpendicular normal is fabricated / how a convex walkable
tread edge is handled) — touches the frozen collision internals → brainstorm gate first.
**Fix (Option A, retail-faithful):** register each multi-part landblock entity via
`ShadowObjectRegistry.RegisterMultiPart` under its **unique 32-bit `entity.Id`** (retail
`CPhysicsObj::add_shadows_to_cells``CPartArray::AddPartsShadow` — one object, a part array;
no synthetic per-part id). New builder `ShadowShapeBuilder.FromLandblockBspParts`. Setup
cyl/sphere path unchanged (runs only when `entityBsp==0`, retail BSP-xor-cyl). Despawn is
landblock-scoped so the id change is safe. Does NOT touch the frozen collision internals.
**Files:** `src/AcDream.Core/Physics/TransitionTypes.cs` (step-up / slide classification, the
sliding-normal horizontalisation); building-collision mesh generation (BSP faces for
landblock stabs). Capture apparatus: `ACDREAM_CAPTURE_RESOLVE`.
**Files:** `src/AcDream.Core/Physics/ShadowShapeBuilder.cs` (`FromLandblockBspParts`),
`src/AcDream.App/Rendering/GameWindow.cs` (~7898 registration block).
**Tests:** `ShadowRegistrationOverflowTests` (overflow arithmetic; old scheme drops one;
`RegisterMultiPart` keeps both; builder), `Issue185OutdoorStairsSeamReplayTests` (dat-free
clean-climb pin). Core 2629 / App 741 green. Design: `docs/superpowers/specs/2026-07-08-185-outdoor-stairs-fix-design.md` (v2).
**Acceptance:** the LOCAL player walks up the full outdoor staircase of the house-on-stilts
without jumping; no sideways-slide pin at the ~Z 61.4 junction; regression pass on other
stairs/ramps (indoor + outdoor) — no new phantom blocks.
**Fallout note:** shared registration path → this was silently dropping collision on OTHER
landblock objects too (23 id collisions in one landblock); likely fixed a class of
"walked-through-a-thing-that's-clearly-there" bugs, not just these stairs.
---