Multi-step investigation of the airborne-at-tick-1 bug per the
systematic-debugging skill. Several hypotheses tested via the
harness, each producing the same (0,1,0) hit normal at tick 1:
1. WalkablePolygon seeding ADDED to BuildInitialBody (was missing).
PhysicsEngine.cs:665-673 requires body.WalkablePolygonValid +
WalkableVertices to call SpherePath.SetWalkable. With seeded
walkable poly: walkPoly=True survives tick 1 (was False before).
BUT engine still reports hit=(0,1,0) and body goes airborne.
2. Initial Z lift removed (back to 0): same airborne behavior.
3. Synthetic stair GfxObj DISABLED: same (0,1,0) hit. Hit is not
from FindObjCollisions.
4. Stub landblock REMOVED: same (0,1,0). FindObjCollisions early-
returns without landblock context, FindEnvCollisions's outdoor
terrain returns null. Hit is not from terrain.
5. SYNTHETIC BSP attached to cell fixtures (Hydrate sets BSP=null
per its xmldoc; without BSP the indoor branch is skipped, falls
through to outdoor terrain). One-leaf BSP referencing every poly
in cell.Resolved. Indoor BSP path now runs. Same (0,1,0) hit.
Trace timeline at tick 1:
find-start: walkPoly=True, CP valid, oi=0x303 (Contact+OnWalkable)
after-adjust: req=(0,-0.1,0) adj=(0,-0.1,0) — no projection change
before-insert: check=(141.5, 9.4, 91.43)
stepdown-enter (Contact-recovery): stepDown=True, height=0.04
stepdown-after-offset: check=(141.5, 9.4, 91.39) — moved DOWN 0.04
stepdown-after-insert: state=OK, cp=n/a (no walkable found)
stepdown-reject
(second stepdown attempt — same outcome)
after-insert: state=Collided, hit=n/a, walkPoly=False
after-validate: state=OK, hit=(0,1,0), slide=(0,1,0)
oi=0x300 (Contact+OnWalkable CLEARED)
The (0,1,0) hit is set by ValidateTransition between after-insert
and after-validate. ValidateTransition's default-push-up code path
sets UnitZ=(0,0,1), NOT UnitY=(0,1,0). So something INSIDE
TransitionalInsert sets ci.CollisionNormal=(0,1,0) before
ValidateTransition runs (12 SetCollisionNormal call sites in
TransitionTypes.cs — root cause not isolated to one).
Per systematic-debugging skill: 5+ hypotheses tested without
convergence = "question architecture". The bug is hidden deeper
than a single misconfigured init field.
Next session pickup: build a side-by-side instrumentation harness
that mimics PlayerMovementController's EXACT call sequence
(PhysicsBody field state, ResolveWithTransition args, frame
ordering) and compare per-tick divergence against a live capture.
The harness is missing some piece of state production carries
across ticks — find what piece.
Apparatus progress (committed):
- Harness with synthetic stair GfxObj registration (Issue #98 ramp polygon now constructable programmatically)
- Synthetic cell-BSP attachment (AttachSyntheticBsp) — unlocks indoor
BSP collision path for hydrated cell fixtures
- WalkablePolygon seeding in BuildInitialBody (PhysicsBody seeding pattern documented)
- Three diagnostic dump tests for tick-by-tick traces
Test baseline: 1167 + 5 (harness) = 1172 + 8 pre-existing failures.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>