Code review feedback on Task 3 commit 91b29d1:
- TryFindIndoorWalkablePlane: comment explaining why FindWalkableSphere's
adjustedCenter out param is intentionally discarded (ValidateWalkable
recomputes contact geometry from plane + foot position, consistent
with the outdoor terrain path).
- IndoorWalkablePlaneTests: new TryFindIndoorWalkablePlane_WallPolyInBsp_ReturnsFalse
restores integration-level coverage that the renamed NoBsp_ReturnsFalse
lost. Verifies WalkableAllowance gate rejects a wall polygon in the
cell BSP. Steep-poly rejection is also covered at the BSPQuery layer
by FindWalkableSphere_SteepPoly_RejectedByWalkableAllowance.
No behavior change. Build clean; all related tests pass; same 8
pre-existing failures.
Spec: docs/superpowers/specs/2026-05-19-indoor-walkable-plane-bsp-port-design.md
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
TryFindIndoorWalkablePlane (Phase 2 commit eb0f772) used a linear
first-match XY scan of cellPhysics.Resolved with no Z-proximity test.
For any cell with two walkable polys overlapping in XY at different Z
(cellars, 2nd floors, balconies, stairs spanning floors), it returned
whichever polygon came first in dictionary order — typically the upper
floor when descending, causing the player to be reported below the
synthesized plane → ValidateWalkable fails → falling-stuck. Symptoms
reported by user 2026-05-19: cannot descend into cellar; cannot walk
on 2nd floor; "invisible obstacles at certain spots" (suspected
cascade from wrong-Z ContactPlane misrouting the resolver state).
Fix: route through BSPQuery.FindWalkableSphere (added previous commit),
which wraps the existing retail-faithful FindWalkableInternal
(BSPNODE::find_walkable + BSPLEAF::find_walkable port). Adds a
sphereRadius parameter to TryFindIndoorWalkablePlane so the foot
sphere is built with the actual entity radius rather than a guess.
WalkableAllowance is save/restored via try/finally so the slope
threshold used by walkable_hits_sphere doesn't leak back to the
resolver. Method becomes an instance method (was static) to access
this.SpherePath.
Deletes the now-dead PointInPolygonXY helper.
Updates IndoorWalkablePlaneTests.cs: all TryFindIndoorWalkablePlane
test fixtures now include a PhysicsBSPTree leaf node (required by
the new routing path), calls pass sphereRadius, and the PointInPolygonXY
tests are removed (method deleted). Adds TransitionTypesTests.cs with
an integration test covering two-overlapping-floors selection AND
WalkableAllowance preservation.
Closes (pending visual verification): ISSUES #83.
Spec: docs/superpowers/specs/2026-05-19-indoor-walkable-plane-bsp-port-design.md
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
When the indoor cell-BSP query returns OK (no wall collision), the player
is standing on a floor poly inside the cell. Previously the code fell
through to outdoor terrain (SampleTerrainWalkable + ValidateWalkable),
which used the OUTDOOR terrain plane — below the indoor floor due to the
+0.02f Z-bump applied for render z-fight prevention. ValidateWalkable
saw the player 0.5m above the outdoor plane → marked them as airborne
→ walkable=False → falling animation, never recovers.
Adds TryFindIndoorWalkablePlane (internal static for testability): scans
the cell's resolved physics polys for a walkable floor poly (normal.Z >=
0.6664, walkable-slope threshold matching retail) under the player's XY,
transforms its plane + vertices to world space via WorldTransform, and
calls ValidateWalkable with the indoor plane. Adds PointInPolygonXY
(ray-casting even-odd rule, ignores Z). Both are wired just after the
BSP OK branch in FindEnvCollisions; outdoor terrain remains a defensive
backstop if no floor poly is found under the player indoors (rare).
Matches retail's CEnvCell::find_env_collisions behavior: no fall-through
to terrain when the cell BSP successfully completes a query.
Evidence: launch-phase2-verify5.log captured 12,141 walkable=False
events during an indoor session where the player never managed to walk
back outdoor through a door — they got stuck against the indoor wall
and the resolver never re-established a walkable contact plane.
Adds 13 unit tests in IndoorWalkablePlaneTests.cs covering:
- player over floor poly (returns true, plane normal up, plane at correct Z)
- player outside poly XY (returns false)
- no walkable polys (returns false)
- empty Resolved dict (returns false)
- cell with world translation (plane + vertices in world space)
- PointInPolygonXY cases (centre, near corner, on boundary, outside, Z ignored)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>