Commit graph

146 commits

Author SHA1 Message Date
Erik
4cbfbf98af docs: #100 ship + indoor-cell culling investigation handoff
Session-end documentation for the issue #100 ship and the visibility-
culling investigation handoff for the next session.

Three documents land together:

  - docs/superpowers/plans/2026-05-25-issue-100-terrain-cutout.md
    (the 3-task plan that drove this session's f48c74a / a64e6f2 /
    84e3b72 — never committed by Tasks 1-2)

  - docs/research/2026-05-25-issue-100-terrain-cutout-handoff.md
    (the predecessor session's smoking-gun research that drove the
    #100 fix — never committed by the prior session)

  - docs/research/2026-05-25-issue-100-shipped-and-culling-handoff.md
    (THIS session's handoff: what shipped, what visual-verification
    surfaced, the issue family map for #78 + #95 + the new cellar-
    stairs finding, root-cause hypothesis, retail anchors, WB
    references, do-not-retry list, and pickup prompt for the next
    session's investigation + plan + implementation)

Plus two updates to existing files:

  - CLAUDE.md — adds a ship paragraph for #100 to the M1.5 progress
    block. References the new handoff doc as the next-session pickup
    point.

  - docs/ISSUES.md #78 — broadens scope from "outdoor stabs visible
    through floor" to "outdoor stabs + terrain mesh visible inside
    EnvCells". Adds the 2026-05-25 cellar-stairs evidence (per user
    direction: not filed as new issue; treated as evidence
    reinforcing #78's hypothesis #2). Promotes hypothesis #2 to
    "high confidence as of 2026-05-25" and adds the retail anchor
    (acclient_2013_pseudo_c.txt:311397 CEnvCell::find_visible_child_cell).
    Acceptance criteria broadened to include the cellar-stairs case.

Next session: pickup prompt at the bottom of the new handoff doc
drives a /investigate → writing-plans → subagent-driven-development
pass on indoor-cell visibility culling — the work that closes #78
+ cellar-stairs together, and possibly #95 if the infrastructure
overlaps.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 22:17:51 +02:00
Erik
8795655250 docs: issue #101 — broken stairs cyl phantom (post-A6.P7 finding)
Visual verification of A6.P7 at Holtburg cottage door passed cleanly
(1187 [cyl-skip-bsp] guard fires, 0 [cyl-test] on doors, 30/30
axis-aligned hits, smooth NE/SE slide along door face). While
exploring post-verification, the user discovered a different
staircase in cells 0xA9B40159 + 0xA9B4015A where the sphere cannot
climb at all.

Captured working baseline (stairs-working.jsonl, cottage cellar
stairs in cells 0xA9B40143/146/147 — clean ↔ Z=90.95-94.00 traversal)
and broken scenario (stairs-broken.jsonl, Z stays at 94.00 the entire
4216-record capture).

Root cause is NOT a regression of A6.P7. It's a different bug shape:
the staircase is built as a multi-part EnvCell entity (entityId
0x0040B500, ~150 parts), with 10 of those parts being 0.80m-radius
cylinders forming the steps. Each cyl carries state=0x00000000 — no
HAS_PHYSICS_BSP_PS — so A6.P7's BspOnlyDispatch guard correctly
doesn't fire. Cyl height 0.80m exceeds A6.P6's step-up budget 0.60m
so grounded step-over fails. Falls through to wall-slide which
produces the same diagonal radial phantom A6.P7 closed for the door.

The [resolve-bldg] lines reveal gfxObj=0x0100081A hasPhys=False
bspR=0.00 vAabbR=0.82 — the underlying GfxObj has NO physics BSP;
we appear to be synthesizing a cyl from the visual AABB radius. That
synthesis path is the suspected misregistration.

Filed as issue #101 with severity HIGH. Investigation handoff written
covering 4 retail-research questions (cdb on retail at this stair
location, Setup trace via entity-source probe, ShadowShapeBuilder
vAabbR fallback audit, cell BSP poly dump), do-not-retry list, and 3
candidate fix shapes (don't synthesize cyl from vAabbR / cell BSP for
stairs / cyl-height-tolerant step-over). The handoff explicitly
defers implementation to a later session pending retail evidence.

Files:
- docs/research/2026-05-25-stairs-cyl-investigation-handoff.md (new)
- docs/ISSUES.md — added #101 entry

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 19:03:58 +02:00
Erik
888272aad1 fix(phys): A6.P7 — retail-binary cyl-vs-BSP dispatch (HAS_PHYSICS_BSP_PS gate)
Closes the door-cyl phantom slide where a sphere approaching a closed
cottage door at NE/SE headings could be blocked by the cyl's radial
normal contaminating the slide tangent into the slab face (live
evidence in door-a6p6-v2.utf8.log: 12 resolves with
cn=(0.86,0.51,0) attributed to door entity 0x000F4245).

Retail anchor: CPhysicsObj::FindObjCollisions at
acclient_2013_pseudo_c.txt:276861 dispatches BINARILY between
BSP-only and cyl+sphere based on HAS_PHYSICS_BSP_PS (0x10000 in
acclient.h:2833). For non-PvP, non-missile movers — every M1.5
scope walking-vs-static scenario — an entity with the flag set
tests its BSP exclusively; the foot cyl is never tested. ACE
confirms the truth table at PhysicsObj.cs:412-450 (HasPhysicsBSP,
missileIgnore, exemption).

Our dispatcher iterated every ShadowEntry independently and tested
both the cyl AND the BSP for a closed door. Cyl was registered
first (FromSetup walk order), and its diagonal radial slide normal
"won" attribution at the early-return on first non-OK. Result was
out=in for tangential motion along the door face.

Changes (~15 LOC + 7 unit tests):
- PhysicsStateFlags.HasPhysicsBsp = 0x00010000 (PhysicsBody.cs)
- Transition.BspOnlyDispatch(uint state) static predicate
  (TransitionTypes.cs) — mirrors retail's branch with M1.5 scope
  defaults (ebp_1 and eax_12 treated as false; wire PvP / missile
  refinements when those scopes ship)
- Per-entry guard in FindObjCollisions cyl/sphere branch
  (TransitionTypes.cs:2433) — continue when BspOnlyDispatch fires,
  with [cyl-skip-bsp] diagnostic line gated on ProbeBuildingEnabled
- A6P7DispatchRulesTests (7 tests, all GREEN): flag value + 6
  parameterized predicate cases

Verification: 14-test keep-green list from the 2026-05-25 handoff
passes (5 BSPQueryTests.FindCollisions_Path5_*, 2 CellTransitTests.A6P5_*,
2 DoorCollisionApparatusTests.Apparatus_DeadCenter_*,
5 DoorBugTrajectoryReplayTests, 1
CellarUpTrajectoryReplayTests.LiveCompare_FirstCap_FixClosesCottageFloorCap).
Total: 20/20 pass including the new 7-test predicate suite.

The DocumentsBug test (Apparatus_Grounded_50cmOffCenter) fails
post-fix BUT was already failing pre-fix in the worktree baseline
(verified by stashing the fix and re-running — same failure mode:
sphere blocks at start with floor normal (0,0,1)). Not in the
keep-green list, so this is a known pre-existing condition; the
test's own header comment instructs flipping the assertion when
the fix lands.

Investigation:
docs/research/2026-05-25-a6-door-cyl-retail-dispatch-investigation.md

Needs visual verification at Holtburg cottage door (NE/SE approach
should now slide smoothly along the door face — zero [cyl-test]
log lines attributed to door entity, replaced by [cyl-skip-bsp]).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 16:35:32 +02:00
Erik
b36eff1c10 docs(handoff): A6.P7 door-cyl + slab interaction — retail investigation needed
A6.P5 (cellSet fix, 3b1ae83) + A6.P6 (cyl step-over, 3d4e63f) shipped
and verified. Original phantom radial-push is gone. Residual symptom:
sphere blocked at NE/SE headings approaching closed cottage door
because cyl's radial normal drives slide direction into the slab.

Handoff covers:
  - What landed today (don't redo)
  - Concrete evidence from door-a6p6-v2.utf8.log (12 resolves with
    cn=(0.86,0.51,0) on door entity post-A6.P6)
  - 3 fix options (BSP-first per-entity / per-physobj dispatch port /
    door-cyl-informational)
  - 3 retail investigation questions for next session (state bit
    0x10000 semantics, cdb trace on door cyl in retail, Setup parsing
    comparison)
  - Files to read first + tests to keep green + do-not-retry list
  - Pickup prompt with brainstorming-only discipline

Next session's deliverable: a research report, NOT an implementation.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 14:54:59 +02:00
Erik
2deb539953 docs(handoff): add second door-bug symptom (over-penetration before block) 2026-05-25 10:45:41 +02:00
Erik
fd1548af61 feat(phys): A6.P4 door — cdb-driven NegPolyHit dispatch (incomplete; needs BSP near-miss recording)
cdb attached to retail at a Holtburg cottage door while user walked the
inside-out off-center scenario. Three trace iterations identified that
retail's collision-recording happens via SPHEREPATH::set_neg_poly_hit
(fires hundreds of times during inside-out walk), NOT via the more
obvious-named COLLISIONINFO setters (which fire 0 times). Apparatus
scripts at tools/cdb/door-inside-out-v[1-3].cdb + symbol-probe.cdb.

Our codebase has NegPolyHitDispatch defined but never called. The
downstream TransitionalInsert NegPolyHit handler was a stub. Two-part
fix landed:

1. BSPQuery.FindCollisions Path 5 (Contact branch) restructured —
   distinguishes full hit (hit0 == true → StepSphereUp) from near-miss
   (hit0 == false but hitPoly0 != null → NegPolyHitDispatch). Mirrors
   retail BSPTREE::find_collisions at
   acclient_2013_pseudo_c.txt:0053a630-0053a6fb.

2. Transition.TransitionalInsert NegPolyHit handler — dispatches to
   step_up + step_up_slide (NegStepUp=true) or records collision
   normal + returns Collided (NegStepUp=false). Mirrors retail
   CTransition::transitional_insert at
   acclient_2013_pseudo_c.txt:0050b7af-0050b7e6.

Tests: all 11 fix-relevant + regression tests pass including issue #98.

VISUAL VERIFICATION (user-driven inside-out off-center): still squeezes
through. Diagnostic [neg-poly-dispatch] probe shows ZERO hits in
production. The Path 5 restructuring doesn't surface NegPolyHit
because our SphereIntersectsPolyInternal only sets hitPoly on FULL
hits — retail's sphere_intersects_poly sets var_5c (closest polygon)
even on near-misses via BSP-traversal side effect.

Remaining fix (next session): add near-miss polygon recording to
SphereIntersectsPolyInternal. Once it sets hitPoly on near-miss BSP
traversal, the Path 5 NegPolyHit dispatch (this commit) will fire
and the TransitionalInsert handler (this commit) will block.

Full handoff with cdb trace table + next-step plan:
docs/research/2026-05-25-door-bug-cdb-retail-trace-findings.md

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 10:36:22 +02:00
Erik
a657ca946c test(phys): A6.P4 door — corner-slide hypothesis falsified, bug is state-related
CornerSlide_AlcoveEastToCottageNorth_ShouldBlock test:
- Registers cottage GfxObj 0x01000A2B (contains north exterior walls)
- Registers cell 0xA9B40150 BSP via dat-direct load (alcove walls)
- Places sphere at (132.95, 16.8, 94) inside alcove near east wall
- Walks sphere +Y 50 times at walk speed (0.05 m/tick)

Result: sphere STAYS at (132.95, 16.8) for all 50 ticks with collision
normal cn=(0.71, -0.71, 0) — the average of alcove east wall normal
and cottage north wall normal at their meeting corner. The corner
handling works correctly in the harness.

So production's inside-out walkthrough is NOT a geometric or BSP
collision-detection bug. The geometry exists, the collision detection
fires symmetrically at corners. The discrepancy must be a STATE
difference between harness and production:
- Real walkable polygons with edges (harness uses big quad)
- Real terrain (harness uses Z=-1000 stub)
- Accumulated body state across many prior ticks (harness uses fresh)
- Possibly cell ping-pong between 0x0150 and 0x0029 in production

Cottage GfxObj wall polygons at the doorway area confirmed:
- North exterior wall east of doorway: polys 0x0032, 0x0033
  X=[133.5, 136.3], Y=17.10, Z=[94, 97], normal +Y
- North exterior wall west of doorway: polys 0x0030, 0x0031, 0x0034,
  0x0035 (X<131.6 various ranges)
- Lintel polys above doorway: 0x0037, 0x0038, 0x003A, 0x003B at Z>96.5

Next-session moves (per handoff):
1. Replay captured tick 2586 (where sphere went from cell 0x0150 to
   0x0029 at X=134.022, way past alcove east wall). Inspect engine
   behavior at exactly that tick's body state.
2. cdb attach to retail at Holtburg cottage doorway — verify whether
   retail also lets sphere walk through at off-center, OR blocks
   cleanly. If retail also allows walkthrough, this might be
   retail-faithful behavior we should accept.

Updated handoff: docs/research/2026-05-25-door-bug-inside-out-geometry-gap.md

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 08:37:31 +02:00
Erik
fe29db5691 test(phys): A6.P4 door inside-out — locate cottage wall, identify corner-slide hypothesis
Followed up the geometry-gap diagnosis with a wider polygon search.
Result: the cottage's north exterior wall east of doorway DOES exist
in cottage GfxObj 0x01000A2B (polys 0x0032, 0x0033) at
world X=[133.5, 136.3], Y=17.10, Z=[94, 97], normal +Y. Symmetric
polys cover the west side and above the doorway lintel.

The wall SHOULD block sphere at X=133.655 (sphere west edge at 133.175
overlaps wall X range; sphere south edge at 17.11 aligns with wall
at Y=17.10).

New hypothesis: the bug is sphere-vs-corner collision at the meeting
point of cell 0x0150's east wall (X=133.5, Y=[16.5, 17.1]) and the
cottage's north exterior wall (X=[133.5, 136.3], Y=17.10). Cell
transit data shows sphere going from X=132.859 entering alcove to
X=134.022 leaving alcove — sphere reached X=134.022 INSIDE cottage
geometry somehow. The sliding along the slab east face (cn=(+1,0,0)
in captured tick 3254) gradually pushes sphere east. Eventually it
shifts past X=133.5 — the corner where alcove east wall meets cottage
north wall. The corner-handling in our BSP collision may incorrectly
let the sphere slide past, or the alcove cell's east wall and cottage
GfxObj's north wall don't compose correctly at the corner.

Diagnostic apparatus extensions:
- HoltburgLandblockStatics_DatInspection: dumps LandBlockInfo for
  landblock 0xA9B4. Shows 114 stabs + 12 buildings. The cottage IS
  Building[6] with modelId=0x01000A2B (the GfxObj we already loaded).
- Diagnostic_CottagePolys_NearWalkthroughPosition: widened search
  reveals the cottage's full north exterior wall geometry.
- HoltburgCottage_CellPortals_DatInspection: extended with cell
  PhysicsPolygon world-frame dump (already in prior commit).

Full updated handoff: docs/research/2026-05-25-door-bug-inside-out-geometry-gap.md

Next-session move: add a "sphere walks +Y from inside alcove at
X near 133" test. If harness slides past the corner like production,
investigate BSPQuery's sphere-vs-edge case. If harness blocks at
corner, the bug is elsewhere (cell 0x0150 BSP not queried, or
cottage GfxObj BSP traversal misses the wall poly).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 08:34:52 +02:00
Erik
da798b2071 test(phys): A6.P4 door inside-out — collision-geometry gap diagnosis
Added diagnostic apparatus that pinpoints the inside-out walkthrough
as a collision-geometry GAP, not a collision-detection bug.

New tests in DoorBugTrajectoryReplayTests:
- InsideOut_Tick3254_WithCottageWalls_ShouldBlock: hypothesis test that
  registered cottage GfxObj 0x01000A2B and replayed the captured tick.
  Cottage blocked sphere but with cn=(0,0,1) floor-cap normal, not a
  wall normal — first signal that cottage geometry near the sphere
  isn't a wall.
- Diagnostic_CottagePolys_NearWalkthroughPosition: dumps cottage polys
  near sphere XY=(133.655, 17.59) at any Z. Result: ZERO cottage
  polygons in that area. The cottage GfxObj has no geometry where the
  sphere walks through.

DoorSetupGfxObjInspectionTests.HoltburgCottage_CellPortals_DatInspection
extended to dump cell 0xA9B40150's 4 physics polygons in world frame:
- floor (Z=94), ceiling (Z=96.5), west wall (X=131.6), east wall (X=133.5)
- All walls only span Y=[16.5, 17.1] — the small doorway alcove volume
- North of Y=17.1, no wall

Captured sphere at (133.655, 17.59) is 0.155 m east of cell east wall
AND 0.49 m north of the wall's Y range. No collision geometry exists
at that XY past Y=17.1. The collision representation has a gap that
the visual cottage covers with a wall.

Production capture confirms the diagnosis: cottage GfxObj fires
[bsp-test] 425 times during inside-out walking — visibility IS
correct post-AddAllOutsideCells fix. Door slab fires 245 times. But
the BSP queries find no polygon at (133.655, 17.6+, 94-95.20). The
slab's east face blocks WEST motion (cn=(+1,0,0) as captured), sphere
free to move +Y past it because no wall is there to block.

Three candidates for next-session investigation:
1. Different cottage GfxObj (Holtburg cottages may be multi-piece)
2. Landblock-baked stab static at the cottage exterior wall location
3. Cottage GfxObj's visual polygons wider than physics polygons (dat fact)

Cheapest next step: add LandblockStatics_DatInspection test that
loads LandBlockInfo 0xA9B4FFFE + iterates StaticObjects + prints
every entity at world XY in [131,135] x [16,19]. Reveals what other
entities live at the cottage doorway.

Full handoff: docs/research/2026-05-25-door-bug-inside-out-geometry-gap.md

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 08:27:52 +02:00
Erik
85a164f4a8 fix(test): correct geometric pin test for door slab Z math
The Geometric_DoorSlabZRange_AbovePlayerSphereTop test was computing
slabWorldZBottom as (entity.Z + partFrame.Z) — assuming the slab's
local Z=0 was its bottom. Actually checking the dat shows the slab's
PhysicsPolygons local AABB is min=(-0.954, -0.134, -1.236) max=(0.971,
0.127, 1.255) — the slab's local origin is at its GEOMETRIC CENTER,
not the bottom. With partFrame.Z=1.275 lifting the origin, the slab
world Z is actually [94.139, 96.630], not [95.375, 97.865].

Corrected test now computes both slabLocalZMin and slabLocalZMax from
the polygon vertices and asserts the opposite (correct) geometric fact:
the slab IS at sphere height — overlap from Z=94.139 to Z=95.20 (1.061
m of vertical overlap with the player's sphere). The slab is NOT a
lintel that misses the sphere; it should collide.

Test renamed: Geometric_DoorSlabZRange_AbovePlayerSphereTop →
Geometric_DoorSlabAtSphereHeight_OverlapsInZ.

Handoff doc 2026-05-25-door-bug-partial-fix-shipped.md updated with
the corrected analysis. The "next investigation candidates" list now
points toward cdb attach to retail as the highest-ROI option, since
the BSP collision IS active at sphere height but production still
shows asymmetric walkthrough behavior. The bug is in either the
GetNearbyObjects coverage at primary-cell boundaries, the BSP
polygon partial-overlap handling, or missing cell-BSP collision for
cottage doorway walls.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 08:14:49 +02:00
Erik
c27fded61e test(phys): A6.P4 door — directional + geometric pin tests reframe inside-out bug
Built three new tests to investigate the inside-out asymmetric collision
that persists after the AddAllOutsideCells coord fix:

1. Directional_OutsideIn_SouthApproach_BlocksAtSlabSouthFace — sphere
   south of door moving NORTH; expects block with cn.Y less than -0.5
2. Directional_InsideOut_NorthApproach_BlocksAtSlabNorthFace — sphere
   north of door moving SOUTH; expects block with cn.Y greater than +0.5
3. Geometric_DoorSlabZRange_AbovePlayerSphereTop — pins the slab Z
   range vs sphere top math

BOTH directional tests PASS — collision is symmetric at unit-test level.
The asymmetric production bug therefore comes from something the unit
tests do not capture (multi-tick state, cell-tracking flicker, walkable
polygon edge interactions).

The geometric pin test reveals the real story: Setup 0x020019FF places
the part-0 BSP slab 1.275 m ABOVE the entity origin via
PlacementFrames[Default][0].Origin. With the cottage door entity at
world Z=94.1, the slab world Z range is [95.375, 97.865]. Player sphere
top reaches Z=95.20. The slab BOTTOM is 0.175 m ABOVE the sphere top —
the slab NEVER collides with the player.

The slab is a LINTEL (door frame above the doorway), not a leaf. The
door's only effective collider at sphere height is the 0.10 m radius
foot cylinder. The directional tests pass because the cylinder blocks,
not the BSP.

User-reported inside-out off-center walkthrough is the sphere walking
AROUND the foot cylinder (sphere reach 0.48 + cyl 0.10 = 0.58 m; any
sphere center over 0.58 m from cylinder center passes freely). The
visual "body partially intersects door" is the character model
occupying the visual door volume while the collision sphere passes
beside the cylinder.

Reframed handoff in docs/research/2026-05-25-door-bug-partial-fix-shipped.md
points to three candidate next-step investigations:
- Retail-faithfulness audit on setup.Radius / setup.Height interpretation
- Re-inspect door parts 1+2 (GfxObj 0x010044B6) for missed physics shapes
- Test the cottage cell BSP (cell 0x0150 walls) + door together — the
  COMBINED collision may be what retail relies on

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 08:08:42 +02:00
Erik
28cd97be62 fix(phys): A6.P4 door bug — AddAllOutsideCells coord convention + replay apparatus
CellTransit.AddAllOutsideCells assumed sphere coords were absolute world
coords (subtracting lbXf = 0xA9 * 192 = 32448 from the sphere position).
Production has used landblock-local coords since Phase A.1
(streaming-center landblock at world origin), so the subtraction
produced localX = -32316, gridX = -1346 → out-of-range → early return
→ ZERO outdoor cells added.

For outdoor primary cells the bug was masked by GetNearbyObjects's
radial sweep. For indoor primary cells (where #98 gates the outdoor
sweep), the door's outdoor cell 0xA9B40029 never reached
portalReachableCells, the door's BSP was never queried, and the player
walked through Holtburg cottage doors unimpeded.

Fix: AddAllOutsideCells treats worldSphereCenter as landblock-local
directly. Matches retail CLandCell::add_all_outside_cells which uses
the per-cell 6-byte landblock-relative position struct.

Existing CellTransitAddAllOutsideCellsTests + CellTransitFindCellSetTests
updated to use landblock-local sphere coords (they were the only callers
using the world-coord convention; production never did).

Apparatus shipped:
- DoorBugTrajectoryReplayTests — live-capture-driven replay harness
  that pinpointed the bug per-field at unit-test speed (<500ms iteration)
- AddAllOutsideCells_LandblockLocalSphere_AddsDoorOutdoorCell — direct
  unit test that demonstrates the fix
- FindTransitCellsSphere_IndoorExitPortal_AddsOutsideForCapturedSpherePos
  — verifies cell-portal traversal at the captured sphere position
- DoorSetupGfxObjInspectionTests.HoltburgCottage_CellPortals_DatInspection
  — dat-direct EnvCell + Environment.Cells + portal-poly inspector
- Fixture: tests/AcDream.Core.Tests/Fixtures/door-bug/live-capture.jsonl
  (tick 13558 walkthrough + tick 22760 outdoor block)

Visual verification (user-driven at Holtburg cottage door, ~50cm off-center):
- outside→inside RUN: now BLOCKS (was: walks through)
- outside→inside WALK: presumed blocks (not retested)
- inside→outside RUN: PARTIAL — body intersects door, sphere slides through
- inside→outside WALK: same partial behavior

The remaining inside→outside asymmetry is a SEPARATE bug in BSP
collision response for two-sided polygons. The [bsp-test] probe now
fires 245 times for the door entity from indoor (was 0 pre-fix) —
door IS being queried; the BSP polygon-level collision response is
the new bug. Handoff at
docs/research/2026-05-25-door-bug-partial-fix-shipped.md.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 07:53:34 +02:00
Erik
6a2c432e5a docs(handoff): door collision session end — honest accounting
Replaces the puffed-up framing in the prior task7-shipped handoff.
Honest summary: no user-visible bug fixed this session. Off-center
and inside-out door walk-through still 100% reproducible. The 4
commits shipped real infrastructure (multi-part registration + the
GetNearbyObjects dedup fix that would have silently broken any
future multi-part feature) but no observed behavioral change.

Also explicitly retracts the "step-up is the bug" hypothesis from
the prior handoff doc — ACDREAM_DUMP_STEPUP=1 in the apparatus
produced no stepup: ENTER lines, so DoStepUp wasn't even being
called. That hypothesis was over-reach from an inference I should
not have inflated to a conclusion.

Recommends the apparatus-replay pattern (same one that closed issue
#98 after 6+ speculation rounds): live capture via
ACDREAM_CAPTURE_RESOLVE → harness replay test → first per-field
divergence names the broken assumption.

DO-NOT list for the next session: do not redo the multi-part work,
do not speculate-and-fix, do not relaunch with more probes hoping
for an obvious signal.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 20:52:53 +02:00
Erik
163a1f0d35 diag(phys): [bsp-test] probe + grounded apparatus test + handoff
Visual verification of Task 7 ship: doors block at dead-center (the
small Cylinder catches) but the BSP slab doesn't catch off-center
or inside-walking-out approaches. Probe-instrumented live capture
proves multi-part registration is correct — every door spawns with
shapes=cyl1+bsp1, and the BSP part is visited 135 times for a single
door at player approaches as close as 0.42 m, with cacheHit=True.
But zero [resolve-bldg] attributions for the BSP shape.

Three artifacts added:

1. TransitionTypes.cs — new [bsp-test] probe in the BSP collision
   dispatch, fires BEFORE the cache lookup. Mirrors [cyl-test] on
   the Cylinder branch. Distinguishes "cache miss → silent skip"
   from "queried but no hit" (the latter doesn't show up in
   [resolve-bldg] which only fires on attributed hits).

2. DoorCollisionApparatusTests.cs — new grounded test
   (Apparatus_Grounded_50cmOffCenter_*) attempts to reproduce the
   production bug via a seeded PhysicsBody (Contact + OnWalkable
   + ContactPlane + WalkablePolygon). Currently doesn't reproduce
   because the apparatus's stub-terrain + synthetic-floor setup
   diverges from production's real Holtburg geometry. Captured as
   "documents-the-bug" — flip the assertion shape when the fix
   lands.

3. docs/research/2026-05-24-door-collision-task7-shipped-but-bug-remains.md
   — full session handoff. Identifies the remaining bug as a Path 5
   (Contact branch + StepSphereUp) misbehavior at thin tall
   obstacles, not in the multi-part registration we just shipped.
   Leading hypothesis: DoStepUp's downward probe finds the same
   flat floor on the OTHER side of the door (Holtburg cottages have
   no Z change between exterior and interior floor), declares
   step-up success, BSP collision returns OK, sphere walks through.
   Recommended next move: relaunch with ACDREAM_DUMP_STEPUP=1 to
   verify the hypothesis.

What this commit DOES NOT do: fix the remaining step-up bug. The
A6.P4 multi-part registration foundation is correct and stays.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 19:22:45 +02:00
Erik
e1d94d7094 test(phys): door setup + GfxObj dat-inspection — Hypothesis A falsified
Read-only deterministic test that opens the real client dat and
dumps Setup 0x020019FF + every GfxObj id it references. Bypasses
PhysicsDataCache's four early-return filters so we see WHAT is in
the dat, not what got into the cache. Skips gracefully when the
dat directory isn't present (keeps CI green).

Result reframes the prior session's investigation:

GfxObj 0x010044B5 (part 0 of the door) DOES have a full door-slab
PhysicsBSP — 6 two-sided (SidesType=Landblock) polygons forming a
1.925m × 0.261m × 2.490m collision volume at frame[0] offset
(-0.006, 0.125, 1.275). Bounding sphere radius 1.975. HasPhysics
flag set. So the handoff's Hypothesis A ("0x010044B5 has no
collision-bearing polygons, only visual") is FALSE.

GfxObj 0x010044B6 (parts 1 + 2, the swinging leaves) IS visual-only
by retail design — HasPhysics clear, PhysicsBSP null, 0 PhysicsPolygons,
but 87 visual Polygons. Our ShadowShapeBuilder skipping these matches
retail's CPhysicsPart::find_obj_collisions short-circuit on
physics_bsp==0 (acclient_2013_pseudo_c.txt:275051) — not a bug.

So the door collision bug is in INTEGRATION, not data. The Task 7
experiment last session registered 0x010044B5's BSP but got zero
[resolve-bldg] attributions. With the data confirmed good, the
next apparatus is a deterministic harness that hydrates 0x010044B5
from a dat dump, registers it via RegisterMultiPart, and sweeps a
player sphere into the door to confirm whether BSP collision fires
in isolation.

Pickup prompt + full reading in
docs/research/2026-05-24-door-dat-inspection-findings.md.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 18:34:41 +02:00
Erik
c89df8e4c0 docs(handoff): door collision per-part BSP session handoff (2026-05-24)
Long session that shipped A6.P4 infrastructure (Tasks 1-6 of the
per-part BSP plan) but discovered the specific shapes we register
from door setup 0x020019FF don't catch the player. Per-part BSP at
0x010044B5 produced ZERO collision attributions in 188K+ resolve
lines despite player walking at doors. Cylinder still blocks
center-only.

Task 7 (refactor RegisterLiveEntityCollision) was implemented and
visually tested, but reverted because the new per-part BSP shape
didn't actually fix the door bug. The infrastructure stays — it's
correctly modeling retail's CPhysicsObj-with-parts model — but the
shapes we feed it need to be re-investigated apparatus-first.

Three hypotheses ranked: (A) part BSPs are visual-only, no collision
polys; (B) building BSP has a wide doorway gap our tiny cylinder
doesn't fill; (C) retail uses Setup.Radius/Height directly. Next-
session move: dump GfxObj 0x010044B5's PhysicsBSP first, then cdb
retail at a doorway.

Recommends: stop speculation, build apparatus, decide fix from
evidence.

#99 stays OPEN. Slice 1's "Closes #99" claim was premature; the real
close requires the per-part BSP work + correct shape identification.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 18:16:02 +02:00
Erik
3e3cd77202 docs(handoff): A6.P4 pickup handoff — full session-resume artifact
Self-contained pickup doc for the next session. Combines:
  - State summary (what's done, what's open, where we are in M1.5)
  - Direction (Option B chosen 2026-05-24 — A6.P4 full then #100)
  - Slice 1 pre-flight (Q1 + Q2 to resolve before coding)
  - Slice 1 / 2 / 3 implementation plans with commit shapes
  - #100 follow-up plan
  - Decomp anchors reference card (8 line citations)
  - Apparatus inventory (don't rebuild what's already there)
  - CLAUDE.md rules that apply
  - Copy-paste pickup prompt at the bottom

Cross-references all the canonical artifacts from this saga:
  - docs/superpowers/specs/2026-05-24-phase-a6-p4-retail-shadow-architecture.md
  - docs/research/2026-05-23-a6-p3-issue98-comparison-harness-findings.md
  - docs/ISSUES.md (#98 DONE, #99 OPEN, #100 OPEN)
  - memory: feedback_retail_per_cell_shadow_list.md,
            feedback_apparatus_for_physics_bugs.md
  - commits b3ce505 + b55ae83 (don't redo)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 07:32:58 +02:00
Erik
b55ae831bd docs: A6.P3 #98 resolution + A6.P4 design + #99/#100 filed
Knowledge-preservation pass after the issue #98 cellar-up fix shipped
(`b3ce505`). Closes the saga's documentation loop and plans the next
phase.

Changes:
  - docs/research/2026-05-23-a6-p3-issue98-comparison-harness-findings.md
    Appended "Resolution 2026-05-24" section: v3 hypothesis falsified,
    actual mechanism (head-bump cottage GfxObj floor poly from below)
    confirmed, b3ce505 fix shipped, known door regression flagged.
    Memory artifacts cross-referenced.
  - docs/ISSUES.md
    #98 moved to DONE with full resolution writeup + decomp anchors.
    #99 filed: door regression at building thresholds (caused by
    b3ce505's indoor-primary gate). Closes via A6.P4.
    #100 filed: transparent rectangular patches around houses
    (terrain rendering). Bisect found commit 35b37df introduced the
    hiddenTerrainCells mechanism that collapses 24m outdoor cells
    when buildings sit in them; cottage building only fills part of
    its cell so the rest of the 24m cell shows the sky-bleeding gap.
    Three fix-path options documented.
  - docs/superpowers/specs/2026-05-24-phase-a6-p4-retail-shadow-architecture.md
    Full A6.P4 design doc. Three-slice plan: (1) query-side portal
    expansion to close #99 while preserving #98 fix, (2) port retail's
    BuildShadowCellSet at registration time so per-cell semantics match
    `CObjCell::find_cell_list`, (3) remove b3ce505 stopgap entirely.
    Decomp anchors, file-by-file plan, risk inventory, open questions.

Memory entries written separately (out-of-tree at
~/.claude/projects/.../memory/):
  - feedback_retail_per_cell_shadow_list.md
    The architectural lesson: retail uses per-cell shadow_object_list
    with portal-aware registration; our landblock-wide spatial
    registry diverges at indoor/outdoor seams.
  - feedback_apparatus_for_physics_bugs.md
    The apparatus-first pattern that cracked the saga: live capture +
    fixture dump + replay harness. Template for future physics bugs.
    Quote rule: "when a physics bug is resisting and you catch
    yourself about to ship 'fix attempt N+1 with no new evidence,'
    STOP. Build the apparatus first."
  - MEMORY.md index updated with both new entries.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 07:23:49 +02:00
Erik
bf6d97625c docs: A6.P3 #98 — new root-cause hypothesis (stale ramp contact plane)
Today's evening session ran from "harness still doesn't reproduce the
cap" → "harness reproduces it" → "wait, the cap is only a symptom, the
real cause is upstream Z drift from the contact plane never refreshing."

The breakthrough question, from the user: "we know how retail OPENs it
from above, how hard can it be to know how to open it from below?" —
which reframed the investigation away from cap-event mechanics (where
six prior attempts looked) and toward "what about our STATE is wrong
when the player is in the cellar but not on the ramp?"

The math: player at cap is 10 m away from the cellar ramp in cell-local
X, but body.ContactPlane is still the ramp's slope plane. AdjustOffset
projects forward motion along that stale slope every tick, lifting Z
by +0.201 m per tick. After enough ticks of horizontal walking, the
head sphere reaches Z=94 and bumps the cottage floor. If the contact
plane refreshed to the flat cellar floor when the player walked off
the ramp, the drift would be zero, the cap would never be reachable.

Next session's task (per the pickup prompt at the bottom of the
findings doc): (1) verify the hypothesis chronologically against the
live capture, (2) find the walkable-refresh gap in
Transition.FindEnvCollisions / SpherePath.SetWalkable, (3) cross-ref
retail's CObjCell::find_env_collisions for the per-tick contact-plane
refresh logic.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 06:03:52 +02:00
Erik
7729bdcf98 docs: A6.P3 #98 — record apparatus convergence + residual X-motion
The findings doc gets an evening-v2 follow-on documenting:
  - GfxObj dump infrastructure shipped (cc3afbc)
  - Harness reproduces cap-event collision normal (97fec19)
  - Residual +0.0266m X-motion divergence — the new investigation target
  - Pre-existing test suite flakiness (out of scope, tracked separately)

CLAUDE.md's "Current A6 phase" block points at the residual divergence
as the next concrete move with the test that gives <1s feedback per fix
attempt.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-23 20:47:17 +02:00
Erik
f29c9d5e61 docs(research): A6.P3 #98 — comparison harness findings + neighborhood fixtures
Session-end documentation for the 2026-05-23 evening session in
which:

1. The PhysicsResolveCapture apparatus shipped (committed earlier
   in fb5fba6).
2. A live capture (41K records) drove the first LiveCompare_* tests
   in CellarUpTrajectoryReplayTests, two of which PASS bit-perfect.
3. The failing third test pinpointed the cap-event divergence.
4. A second capture (70K records + 16 cell dumps + per-poly probes)
   identified the cottage GfxObj 0xA9B47900 as the blocker — a
   landblock-baked static building whose floor polygons live in the
   GfxObj's BSP, NOT in any cottage cell.

The findings doc has:
- TL;DR + chronological commits
- Apparatus inventory (PhysicsResolveCapture, comparison tests,
  fixtures, launch scripts)
- The math: head sphere top at Z=foot+1.68 reaches the cottage floor
  at Z=94.0 when foot Z=92.74, matching the observed cap.
- User's confirming observation (cap fires on pure-vertical jump too,
  ruling out every step-up / AdjustOffset hypothesis)
- What's NOT yet known (why retail doesn't have this cap; full
  cottage GfxObj polygon list)
- Next-session pickup with two ranked options

Adds:
- docs/research/2026-05-23-a6-p3-issue98-comparison-harness-findings.md
- launch-a6-issue98-capture.ps1 (capture-only launch)
- launch-a6-issue98-polydump.ps1 (capture + diagnostic probes + 16-cell dump)
- 13 new cell-dump fixtures (0xA9B40140-0xA9B40142, 0xA9B40144,
  0xA9B40145, 0xA9B40148-0xA9B4014F) at 272 KB total. The harness now
  has the full 0xA9B4014X neighborhood available for any future
  comparison test that needs adjacent cell geometry.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-23 20:12:43 +02:00
Erik
ec47159a2e docs(handoff): A6.P3 #98 — full-session handoff doc + CLAUDE.md/ISSUES.md updates
Adds the canonical pickup document
docs/research/2026-05-23-a6-p3-issue98-harness-handoff.md with:
- TL;DR + session arc (10 commits chronological)
- What the trajectory replay harness IS (committed apparatus)
- Bug 1 status: #98 cellar-up freeze (unfixed, 6 fix shapes failed)
- Bug 2 status: airborne-at-tick-1 (new, 6 hypotheses tested, root
  cause not isolated)
- Exclusion list: DO NOT retry any of the 6+6 dead ends
- Apparatus inventory: probes, tests, fixtures, cdb captures
- Recommended next move: side-by-side comparison harness against
  live PlayerMovementController state (evidence-first instead of
  speculation-first)
- Alternative moves: pivot to other M1.5 issues or M2 prep
- Self-contained pickup prompt at the bottom of the handoff doc

Updates CLAUDE.md's "Current A6 phase" block to point at the new
handoff doc as the canonical resume artifact.

Updates ISSUES.md's #98 entry with the late-day extension findings,
the 6-hypothesis exclusion list, and a pointer to the handoff doc.

Test baseline maintained at 1172 + 8 pre-existing failures.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-23 19:09:00 +02:00
Erik
8daf7e7e4d research(phys): A6.P3 #98 — [step-walk-adjust] capture + findings
The diagnostic-first capture revealed the failure mode the plan's
four-branch decision tree (A/B/C/D) did not anticipate. AdjustOffset
is CORRECT: 145/146 calls use the into-plane branch, mean zGain
+0.045 m per call, sphere world Z climbs 90.95 -> 92.80 monotonically.

The climb caps at world Z 92.80 (cottage floor at 94.00 is still
1.20 m above). At the cap, the per-step CP reset at TransitionTypes.cs
723-725 clears ContactPlaneValid as designed; TransitionalInsert
should re-establish CP at the proposed position. Step-up logic fires
because the offset has +Z; step-up calls DoStepDown(stepDownHeight=
0.6, runPlacement=true). The downward probe finds NO walkable surface
within 0.6 m below the proposed position (cottage floor is ABOVE,
not below) -- 101 stepdown-reject hits in this capture vs 1 acceptance.

Conclusion: Target E (new). Three candidate fix shapes named in the
findings note. Each one researched against retail named-decomp before
any code lands. Test baseline 1167 + 8 maintained.

Findings:  docs/research/2026-05-23-a6-stepwalkadjust-findings.md
Capture:   docs/research/2026-05-23-a6-captures/stepwalkadjust/acdream.log

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-23 16:25:04 +02:00
Erik
28c282a563 docs(phys): A6.P3 #98 Step 5 — replay vs retail divergence comparison
Closes the apparatus loop. Side-by-sides acdream's deterministic replay
(commit 856aa78) against retail's cdb capture taken via Step 4's
runner. The divergence target is named; the fix plan is the next plan.

Retail data (cellar_up_capture_1):
- 35,219 BP hits over ~5 seconds of motion
- BPE (set_contact_plane): 161 writes, ALL to one of two flat planes
  (n=(0,0,1) d=-93.9998 = cottage floor @ Z=94, OR d=-90.95 = cellar
  floor @ Z=90.95). Retail NEVER sets ContactPlane to the cellar ramp.
- BPC (find_crossed_edge): 1 hit in 35K. Retail barely uses this
  predicate during cellar-up.
- BPA (find_walkable) sphere position at each cottage-floor
  acceptance: sphere LOCAL Z = +0.48 to +0.63 (resting on top of the
  floor plane). Sphere world Z ≈ 94.48.

acdream replay (Issue98CellarUpReplayTests):
- At the failing-frame sphere (world (141.7, 8.4, 92.0)), the cottage
  cell 0xA9B40143's poly 0x0004 reports insideEdges=false AND
  overlapsSphere=false. Sphere local Z = -0.69 (below the cottage
  floor plane). 0xA9B40146 has no walkable candidate at all. Step-up
  has nothing to step onto → stuck.

Sphere world Z delta: 2.47m. Retail's sphere is 2.5m higher than ours
at the decision point. The fix targets, in priority order:

1. (HIGHEST CONFIDENCE) Step-up + ramp climb doesn't gain enough Z per
   tick. Retail climbs the ramp GRADUALLY across thousands of ticks;
   ours oscillates at world Z ≈ 92 without altitude gain. Look at
   Transition.AdjustOffset (slope projection) and Transition.DoStepUp
   (does it reset WalkInterp like retail's step_sphere_up?).

2. Cottage-cell candidacy uses wrong sphere reference. Check what
   sphere CheckOtherCells passes to BSPQuery.FindCollisions — is it
   the step-lifted sphere or the pre-step sphere?

3. (SECONDARY) find_crossed_edge over-use. Our walkable test calls
   FindCrossedEdge heavily; retail barely uses it. Possibly a
   code-shape mismatch in step-up vs walkable-acceptance flow.

4. (LOW CONFIDENCE) Ramp polygon normal divergence. Verify via test
   after any fix.

The apparatus that gets us here:
- tests/AcDream.Core.Tests/Fixtures/issue98/*.json (real cell geometry)
- Issue98CellarUpReplayTests (7 tests, <1ms each, deterministic bug
  reproduction)
- tools/cdb/issue98-runner.ps1 (reusable for any future capture)
- docs/research/2026-05-23-a6-captures/cellar_up_capture_1/ (this
  capture, checked in for future analyses)

Next plan: pick Target 1 or 2 from the comparison doc and write the
fix plan against it. The replay harness is the test loop; a fix that
doesn't change the failing assertions in Issue98CellarUpReplayTests is
not the fix.
2026-05-23 15:57:12 +02:00
Erik
111aa3e59d docs(handoff): A6.P3 issue #98 — slice 6 failed; pivot to terrain-mesh
Tonight's slice 6 session attempted 6 variations of placement-insert
bypass in Transition.FindEnvCollisions + Transition.DoStepUp. None
unstuck the player at the cellar ramp top despite mechanically firing
the bypass up to 72 times per session. Reverted all variants; nothing
shipped tonight beyond this handoff.

The hard finding: the placement-insert path is a SYMPTOM, not the
cause. Bypassing it (in 6 ways) doesn't make the sphere climb the
cellar ramp. The first-order question — why doesn't the sphere
progress UP the ramp via normal slope-walking? — wasn't addressed.

User's most actionable clue (not yet investigated): "outside ground
covers only the open path down into the cellar" → suggests a missing
hole in the outdoor terrain mesh over the cellar entry. That's a
terrain-generation bug, completely separate from BSPQuery.FindCollisions.

Handoff doc captures:
  - The 3-session diagnosis evolution (each previous session's
    confident diagnosis was wrong)
  - All 6 slice-6 bypass variants tried and why each failed
  - What we KNOW (data-confirmed) vs what we DON'T KNOW (open
    questions)
  - Specific next-step investigation order with terrain-mesh as #1
  - Pickup prompt with strong "don't re-attempt placement-insert
    bypass" guard

Test baseline 1148 + 8 unchanged. Slice 5 probe (cf3deff) remains
committed as the durable diagnostic infrastructure.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-23 08:56:43 +02:00
Erik
cf3deff7c2 feat(phys): A6.P3 slice 5 — [place-fail] probe + sharpened #98 diagnosis
Add ACDREAM_PROBE_PLACEMENT_FAIL gate + LogPlacementFail emitter +
side-channel polygon attribution in PhysicsDiagnostics. Wire into
BSPQuery.FindCollisions Path 1 (Placement/Ethereal) on Collided
returns; wire into Transition.DoStepDown after the placement_insert
TransitionalInsert(1) call; wire into Transition.FindObjCollisions
to emit per-static-object [place-fail-obj] lines.

Run scen4 cellar-up with the probe → 168 [place-fail] events. 80 of
81 BSPQuery Path 1 placement rejections cite polygon 0x0020 in
cellar cell 0xA9B40147's BSP: n=(0,0,-1) d=-0.2, world Z=93.82 —
the cellar ceiling (underside of cottage main floor thickness layer).
0 [place-fail-obj] lines, confirming the failure source is the cell
BSP not a static object.

The probe-driven evidence INVALIDATES the 2026-05-22 morning
handoff's "Path 5 vs Path 6 in BSPQuery.FindCollisions" diagnosis.
Retail's BP4 trace shows every find_collisions hit has collide=0 —
retail enters the same Contact branch we do, no outer-dispatcher
divergence. Retail's BP5 fires 17+ times on the cellar ramp polygon,
not "30 hits all on flat planes" as morning claimed.

The actual divergence is downstream in cell-promotion: retail's
check_cell transitions to cottage cell 0xA9B40146 during the ascent
(BP7 sets ContactPlane to the cottage main floor poly, which lives
in cottage cell's BSP not cellar's). Ours stays at cellar 0xA9B40147,
where the ceiling poly 0x0020 correctly rejects the lifted sphere.

No fix attempted this session per CLAUDE.md discipline check
(3+ failed fixes = handoff). Full slice 5 evidence + concrete
next-session pickup steps at docs/research/2026-05-22-a6-p3-slice5-handoff.md.
ISSUES.md #98 updated with the corrected diagnosis.

Test baseline: 1148 + 8 pre-existing fail. Maintained.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 20:02:15 +02:00
Erik
c479ea68a3 docs(handoff): A6.P3 2026-05-22 EOS handoff + pickup prompt for #98 fix
Comprehensive handoff doc covering today's full A6.P3 work:
  - 13 commits shipped today (slice 2 + slice 3 + slice 4 probes +
    diagnosis)
  - Issue #98 sharply diagnosed via paired retail+acdream cdb captures:
    BSP path-selection bug (Path 5 vs Path 6) at BSPQuery.FindCollisions
    dispatcher
  - All 4 A6.P2 findings status updated (Findings 1, 3 closed; Finding 2
    partially closed + accepted divergence; Finding 4 = issue #95
    separate scope)
  - Failed fix attempts log so next session doesn't re-attempt dead ends
  - Concrete starting steps + file references for the next session
  - Pasteable pickup prompt at the bottom

CLAUDE.md "Currently working toward" block updated to reflect slice 3
ship + #98 sharp diagnosis + handoff doc pointer.

Test suite: 1148 + 8 pre-existing fail (baseline maintained).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 13:32:02 +02:00
Erik
134c9b87f3 capture(research): retail cellar-up trace for issue #98 — SHARP diagnosis
User walked retail UP the same Holtburg cottage cellar that acdream
gets stuck on. cdb captured retail's BSP behavior for paired
comparison against the acdream polydump trace (0b44996).

Retail (successful walk):
  BP1 transitional_insert: 2,651
  BP2 step_up:                29  (incl. 1 hit on the ramp slope, n.z=0.6950)
  BP4 find_collisions:     4,032
  BP5 adjust_sphere:          30  (ALL on FLAT planes; ZERO on the ramp)
  BP6 check_walkable:         25
  BP7 set_contact_plane:      18  (ALL set the SAME flat plane:
                                   (0,0,1) d=-93.9998 = world Z=94 =
                                   cottage main floor)

Acdream (stuck — from scen4_cottage_cellar_polydump):
  cp-write:                  229,300
  push-back:                  ~1000 (270 on the RAMP slope poly 0x0008)
  step_up_slide:                159

THE DIVERGENCE — pinpointed:

Retail's BSP path-selection for the cellar ramp picks Path 6 (find_walkable
land) — the ramp is treated as a walkable floor to LAND ON. Result:
BP7 sets the contact plane to the cottage main floor (Z=94). No push-back
needed on the ramp.

Our BSP picks Path 5 (Contact → step_up → adjust_sphere push-back) for the
SAME ramp polygon. Result: 270 push-backs against the ramp slope; step_up
keeps failing → step_up_slide loop → player stuck.

NEXT STEP (new session): trace why our BSP picks Path 5 instead of Path 6
for the ramp. Likely in BSPQuery.FindCollisions dispatcher's
path-selection logic. The ramp is walkable (N.Z=0.695 > FloorZ=0.6642) so
Path 6 should fire. Maybe a wrong ObjectInfo state flag, or a sub-step
order issue, or the ramp polygon's BSP-side classification is wrong.

This capture + the polydump capture give a complete picture for the next
investigation session. No more guess-fixes today — the data is now sharp.

Test suite: 1148 + 8 (unchanged this commit).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 13:25:57 +02:00
Erik
ceeb06be7d ship(phys): A6.P3 slice 3 — cell-resolver ping-pong CLOSED + #98 re-diagnosed
Slice 3 v2 (3e140cf) added point-in cell-stickiness in
ResolveCellId's indoor branch. User verification + slice3v2 capture
confirms: cell-resolver ping-pong is FULLY CLOSED.

Data:
- scen2_v2 capture (pre-slice-3): 20+ cell-transit events with
  rampant ping-pong (0xA9B4014B ↔ 0xA9B4014A ↔ 0xA9B4013F at the
  cellar boundary, Z stable ~96.4 — same tick re-classification)
- slice3v2 capture (post-fix): 1 cell-transit event (login teleport
  only) — ping-pong fully eliminated

Findings:
- A6.P2 Finding 3 (cell-resolver sling-out family) CLOSED.
- Issue #90 (sphere-overlap stickiness workaround in same function)
  now redundant; can be removed in A6.P4 after broader visual
  verification.
- Issue #97 (phantom collisions + fall-through on 2nd floor) hypothesis
  pending: same instability family, likely closed as side-effect of
  this fix. Re-test on next happy-test session.
- Issue #98 (cellar-up stuck) PERSISTS but with NEW DIAGNOSIS.
  Originally filed as cell-resolver ping-pong (which was true and now
  fixed), but user verification shows the cellar-up symptom remains
  with a DIFFERENT root cause: BSP step-physics at the cellar stair
  TOP. Push-back trace from slice3v2 capture:
    n=(0, -0.719, 0.695) sloped face (walkable per FloorZ=0.664)
    delta=(0, 0, 0.75) step-down probe lifts sphere by 0.75m
    winterp=1.0->0.0 entire walk-interp consumed per tick
  Player progresses up most of the stairs but blocks at top step
  where the cellar transitions to the cottage main floor. #98 issue
  updated with this re-diagnosis.

Includes:
- scen4_cottage_cellar_slice3 acdream.log (slice 3 v1 evidence;
  ping-pong already closed by v1's sphere-overlap stickiness, but
  v1 over-corrected by holding player in cellar during legitimate
  transitions)
- scen4_cottage_cellar_slice3v2 acdream.log (slice 3 v2 evidence;
  point-in stickiness fixes the over-correction; cellar-up reveals
  the deeper BSP step-physics bug)

Docs updated:
- ISSUES.md — #98 re-diagnosed
- docs/plans/2026-04-11-roadmap.md — A6.P3 slice 3 marked SHIPPED;
  slice 4 (or A6.P4) scoped for #98 step-physics investigation
- CLAUDE.md — Currently-working-toward block updated

Test suite: 1148 pass + 8 pre-existing fail (baseline maintained).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 12:11:56 +02:00
Erik
d868946537 ship(phys): A6.P3 slice 2 — L622 seed investigation + #98 filed
Slice 2 v1 (`892019b`) attempted to close issue #96 by removing the
PhysicsEngine.cs L622 per-tick CP seed. v1 build/test green, CP-write
count dropped 91% in scen3 re-capture, BUT user happy-test surfaced
a regression: BSP step_up at the last step of stairs failed because
sub-step 1's AdjustOffset had no ContactPlane to compute the lift
direction.

Slice 2 v2 (`f8d669b`) reverted the seed removal + added a no-op-if-
unchanged guard inside CollisionInfo.SetContactPlane. The guard
early-returns when called with values matching current ci state.

Outcome:
- #96 PARTIALLY ADDRESSED, scope updated in ISSUES.md to "accepted as
  documented retail divergence." The seed is load-bearing for step_up;
  closing #96 fully would require deeper refactor (AdjustOffset
  fallback to body.ContactPlane). Guard is benign improvement.
- Slice 2 v2 verification capture (scen3_inn_2nd_floor_slice2v2/
  acdream.log) committed as evidence — 226,464 cp-writes from L624
  seed confirms guard doesn't trigger for fresh-ci-per-tick pattern.
- Slice 2 v1 verification capture (scen3_inn_2nd_floor_slice2/
  acdream.log) also committed — confirms v1 actually reduced cp-writes
  (2,690 total) but the step_up regression made it unshippable.

NEW M1.5 BLOCKER FILED — issue #98: cellar ascent stuck at last step.
Evidence in slice2v2 capture's cell-transit chain:
  0xA9B4014B → 0xA9B4014A → 0xA9B4013F → 0xA9B4014A → 0xA9B4014B → ...
  (Z stable ~96.4; CellId ping-pongs every tick)
This is Finding 3 family (cell-resolver hysteresis missing) — same
root cause as #90 workaround + scen4 sling-out. Retail oracle:
CObjCell::find_cell_list Position-variant at
acclient_2013_pseudo_c.txt:308742-308783.

NEXT — A6.P3 slice 3:
- Port retail's cell-array hysteresis into ResolveCellId +
  CheckBuildingTransit.
- Closes #98 (cellar-up), possibly #97 (phantom collisions same
  instability family), enables #90 workaround removal.

Documents updated:
- ISSUES.md — #96 scope updated, #98 filed
- docs/plans/2026-04-11-roadmap.md — A6.P3 slice 2 marked SHIPPED,
  slice 3 scope added
- CLAUDE.md — Currently-working-toward block updated to slice 3

Test suite: 1148 pass + 8 pre-existing fail (baseline maintained).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 11:58:05 +02:00
Erik
f04ea90050 ship(phys): A6.P3 slice 1 — Indoor ContactPlane retention COMPLETE
Slice 1 ships the strip-synthesis + Mechanism B (LKCP restore) fix
addressing A6.P2 Finding 2. Includes:

  - scen3_inn_2nd_floor_postfix paired capture (retail.log + decoded
    + acdream.log) as verification artifact. acdream cp-write count
    dropped from 86,748 to 25,082; per-unit-of-activity rate dropped
    63x (164.61 -> 2.60 cp-writes per cell-cache event).

  - Findings doc (docs/research/2026-05-21-a6-cdb-capture-findings.md)
    appended with slice 1 SHIPPED section: commit map, scen2/scen3
    pre/post comparison tables, user happy-test results, status of
    each A6.P2 finding (Finding 1 CLOSED as side-effect, Finding 2
    PARTIALLY CLOSED with remaining 99.3% from L622 seed flagged
    as #96, Finding 3 + #95 still open), slice 2 recommendation.

  - Issue #96 filed: "Per-tick PhysicsEngine.ResolveWithTransition CP
    seed contributes 99.3% of post-slice-1 CP writes." Slice 2 target.
    Sketch options: remove entirely / gate by change-detection / no-op
    guard inside SetContactPlane.

  - Issue #97 filed: "Phantom collisions + occasional fall-through on
    indoor 2nd floor." User-reported during happy-testing. HYPOTHESIS:
    side-effect of #96; falsifiable by re-testing post-slice-2.

  - CLAUDE.md updated: Currently-working-toward block now points at
    A6.P3 slice 2 (#96) as the active phase. M1.5 building/cellar
    demo half is ACHIEVABLE NOW (slice 1 unblocked the physics).

  - Roadmap updated: A6.P3 broken into 3 slices, slice 1 marked
    SHIPPED with commit history.

KEY USER-VISIBLE OUTCOME: stairs + cellar descent now WORK in acdream
(user happy-test confirmed walking up/down inn stairs multiple times,
walking down to cellar). A6.P2 Finding 1 (dispatcher entry frequency
mismatch) confirmed as secondary effect of Finding 2 — closed without
explicit Finding 1 work, as A6.P2 hypothesized.

REMAINING CONCERNS for slice 2 + future:
  - L622 per-tick seed (#96) still firing 24,906 times in scen3 postfix
  - Phantom collisions + fall-through on 2nd floor (#97)
  - See-through-walls indoors (#95, separate scope)
  - Indoor lighting broken (A7 scope)

Test suite: 1148 pass + 8 pre-existing fail (baseline maintained;
T3 IndoorContactPlaneRetentionTests adds the +1).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 10:11:44 +02:00
Erik
066568a711 capture(research): A6.P3 slice 1 — scen2 postfix proves stairs now work
Unexpected slice 1 win: the synthesis-strip + Mechanism B (LKCP
restore) fix didn't just close Finding 2 (CP-write blowup) — it also
unblocked stair-walking, which A6.P2 had categorized as Finding 1+3
territory expected to need separate fixes. User reports walking up
and down the inn stairs multiple times in acdream post-fix.

Shape shift in tag distribution:

  Tag                  Pre-fix (FAIL)  Post-fix (SUCCESS)  Signal
  ----                 -------------   ------------------  ------
  indoor-walkable        859              0                synthesis gone
  push-back-cell        1478            879 (-40%)         multi-cell relaxed
  push-back               51            345 (+577%)        real step-up firing
  push-back-disp        4156           6055 (+46%)         real traversal
  cp-write             33969          57846                L622 seed (slice 2)

Pre-fix: synthesis firing while physics hammers BSP trying to resolve
stair-step (failure mode). Post-fix: real BSP queries succeeding, real
step-up + step-down landing. Same shape as retail's stair-climb
(retail scen2: BP2 step_up=188, push-back-disp dominates).

A6.P2 Finding 1 (dispatcher entry frequency mismatch) hypothesis was
"likely secondary effect of Finding 2 — may close as side effect of
the fix." Confirmed empirically: dispatcher activity now matches
retail-like shape without explicit Finding 1 work.

Remaining (slice 2 territory):
- L622 per-tick PhysicsEngine.ResolveWithTransition seed fires 99.3%
  of remaining cp-writes; retail's equivalent fires zero times on
  flat-floor walks. Gate this seed to close the remaining CP-write
  gap.
- Phantom collisions + occasional fall-through on 2nd floor reported
  by user during happy-testing. New issue to file.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 10:07:35 +02:00
Erik
c6bc2b9980 fix(research): A6.P3 slice 1 T1 — citation corrections + LKCP re-latch note
Code-review feedback on commit 6b4be7f:
  - Section 1: strip stale [309NNN] inline annotations (off by 2-8
    lines from actual file content; the 0052c1xx address comments
    are the reliable anchor); address comments already present in the
    decomp output are now used as inline anchors instead
  - Section 2: validate_transition function header is at file line
    272547 (was: 272538, inside the preceding check_collisions
    function). Address 0050aa70 + LKCP-block range 272565-272583
    were already correct. References section updated to match.
  - Section 5: add note that SetContactPlane re-latches LKCP fields
    (no-op when LKCP is the source, but non-obvious side effect)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 08:20:24 +02:00
Erik
6b4be7f863 docs(research): A6.P3 slice 1 — retail Mechanism B oracle for CP retention
Pre-fix research note grounding the indoor CP-retention refactor in
retail's exact LKCP-restore pattern (acclient_2013_pseudo_c.txt:272565-272582)
and CEnvCell::find_env_collisions tiny shape (line 309573).

Key findings:
- find_env_collisions writes NO ContactPlane — only BSP Path 6 does (Mech A)
- validate_transition Collided/Slid/Adjusted branch calls set_contact_plane
  from LKCP when proximity guard passes (global_curr_center, not global_sphere)
- Our ValidateTransition is missing the SetContactPlane call in that branch
  (sets Contact/OnWalkable flags only) — this is the gap Task 4 closes
- Proximity sphere should be GlobalCurrCenter[0] not GlobalSphere[0]
- Exact insertion point: TransitionTypes.cs ~line 2849, inside the
  'radius + EPSILON > |angle|' proximity-guard branch

Output of this note drives the per-transition Mechanism B insertion
point selection in Task 4 + the slice-1 acceptance shape.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 08:09:40 +02:00
Erik
90fbdc02df docs(roadmap+milestones): mark A6.P1/A6.P2 shipped; update M1.5 demo
A6.P1 (cdb probe spike) + A6.P2 (analysis report) both SHIPPED this
session. Updated:

  docs/plans/2026-04-11-roadmap.md — M1.5 phase block now shows A6.P1
  + A6.P2 SHIPPED with commit refs; A6.P3 entry expanded with the
  Finding-2-first sequencing recommendation from A6.P2; A6.P4 entry
  notes the original "Holtburg Sewer end-to-end" acceptance walk is
  unreachable (sewer doesn't exist).

  docs/plans/2026-05-12-milestones.md — M1.5 demo scenario split into
  building/cellar half (achievable post-A6.P3) + dungeon half (blocked
  on issue #95 visibility blowup; promote to post-M1.5 if #95 isn't
  fixed in scope). Issue list updated: added #95 + indoor sling-out
  (new from scen4); marked stairs/2nd-floor/cellar as characterized by
  A6.P2 Finding 2 family.

  CLAUDE.md — Currently-working-toward block now points at A6.P3 as
  the active phase. A6.P1 + A6.P2 ship noted with the findings doc
  pointer. Demo-scenario note updated to reflect the sewer + #95
  reality. Issues-in-scope updated.

Also includes a 1-line trailing-prompt addition to scen3 + scen4
retail.log files (cdb wrote one more `0:000>` after the kill that
landed after the original capture commits).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 21:17:21 +02:00
Erik
184933d796 docs(research): A6.P2 — capture findings report (5 of 9 scenarios)
Replaces the A6.P1 stub with the analysis pass over 5 paired captures
(scen1-5). Scen6-9 (sewer-specific) cancelled because the Holtburg Sewer
doesn't exist on this ACE server and any substitute dungeon hits issue
#95 (portal-graph visibility blowup) on entry.

Four findings ready for A6.P3 sequencing:

  Finding 1 — Dispatcher entry frequency mismatch (4x to 281x fewer in
              acdream). Likely secondary effect; may close as side-effect
              of Finding 2 fix.

  Finding 2 — ContactPlane resynthesis blowup. 250x to INFINITE more CP
              writes in acdream. Strongest single signal; scen3 shows
              retail wrote CP zero times during a flat 2nd-floor walk
              while acdream wrote 86,748 field updates. Primary M1.5
              root cause. HIGH severity.

  Finding 3 — Indoor cell-resolver sling-out (scen4). Resolver flings
              +Acdream across landblock boundary; CheckBuildingTransit
              fires 5,495 times during the sling while indoor BSP is
              barely queried. Same family as the M1.5 cell-tracking
              ping-pong hypothesis. HIGH severity.

  Finding 4 — Portal-graph visibility blowup (scen5 incidental). Filed
              as issue #95; not strictly A6 scope but documented here so
              A6.P3 sequencing knows about it.

Tables 1+2 (per-site push-back delta + path-frequency diff) deferred to
A6.P1.5: the v4 cdb probe captures function entry only, not exit values.
Adding paired exit BPs is ~1 hour of cdb scripting work but not needed
unless A6.P3 fixes fail to close the symptoms.

Table 3 (CP lifecycle) fully populated — geometric mean CP-write ratio
across 4 finite scenarios is ~1,470x; median ~2,200x.
Table 4 (sub-step state mutations) partially populated with proxy
metrics (per-tag firing rates).

M1.5 symptom coverage matrix: every in-scope physics symptom maps to
at least one finding. Acceptance per spec §4.7 met.

A6.P3 sequencing recommendation: Finding 2 first (highest-confidence
single-cause; may close Finding 1 as side effect), re-run captures, then
Finding 3. Issue #95 handled separately outside A6.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 21:13:07 +02:00
Erik
35d5c58c7b capture(research): A6.P1 scen5 — town network portal entry paired traces
Substituted "Holtburg Sewer" portal with Town Network Portal — no
sewer entry exists in this world (user-verified). Town Network is
also an outdoor->indoor portal transition with the same physics
signature.

Both clients walked to the portal, entered, walked 2 m inside.
Retail: clean traversal. Acdream: also clean (no failure mode).

Retail (decoded, 23,890 raw / 9,769 BP lines):
  BP1 transitional_insert: 13,863
  BP4 find_collisions:      9,552
  BP5 adjust_sphere:           97
  BP6 check_walkable:          55
  BP7 set_contact_plane:       65  (moderate, portal threshold + indoor)
  BP2 step_up:                  1

Acdream (31,914 lines, no failure):
  [cp-write]:        20,956  (vs retail BP7 = 65 — ~322x ratio)
  [cell-cache]:       9,642  (Holtburg landblock streaming)
  [check-bldg]:         740
  [push-back-disp]:      34  (flat-ground walking)
  [push-back]:            1
  [cell-transit]:        12  (CLEAN traversal, no thrashing)

cell-transit event chain — captures the portal entry signature:
  0x00000000 -> 0xA9B30030  (login teleport)
  0xA9B30030 -> 0xA9B40029 -> 0xA9B40021 -> 0xA9B40019 ->
  0xA9B40011 -> 0xA9B40012 -> 0xA9B4000A -> 0xA9B4000B ->
  0xA9B40003  (walked across Holtburg, all reason=resolver)
  0xA9B40003 -> 0x00070143 reason=teleport  (PORTAL ENTRY)

scen5 is the "control" — both clients reached their target, no
visible failure. The CP-write blowup persists as the only A6.P2
divergence. Useful baseline for separating "indoor physics broken
during walking" (scen2, scen3, scen4) from "indoor physics okay
when portal-delivered" (scen5).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 20:59:00 +02:00
Erik
46c6e08ee5 capture(research): A6.P1 scen4 — cottage cellar paired traces
Asymmetric pair (scenario-level, not protocol-failure):
- Retail: user walked UP out of the cellar (ascent of 2 cellar
  steps + exit through doorway) — captures ascent + indoor-to-
  outdoor transition.
- Acdream: user teleported INTO the cellar, walked a few meters,
  the resolver flung +Acdream OUTSIDE the cottage entirely
  (landblock prefix changed A9B4 -> A9B3 mid-walk) — captures
  a real indoor physics failure that's not a stair issue per se.

Both traces are valuable to A6.P2 even though they don't match
walk-for-walk.

Retail (decoded, 22,536 raw / 12,875 decoded BP lines):
  BP1 transitional_insert:  9,402
  BP4 find_collisions:     12,596  (ended in mem-access error
                                    @ hit#12596 - cdb hit a null
                                    transition arg, dropped to
                                    interactive prompt; worth a
                                    note for A6.P2 retail edge)
  BP5 adjust_sphere:          136
  BP6 check_walkable:         128
  BP2 step_up:                 13  (2-step cellar = 13 vs scen2
                                    4-step inn = 188; non-linear)
  BP7 set_contact_plane:        3  (Finding 2 holds)

Acdream (42,001 lines, ended with sling-out):
  [cp-write]:        35,624
  [check-bldg]:       5,495  (CheckBuildingTransit fired
                              constantly trying to re-resolve
                              which building +Acdream was in)
  [cell-cache]:         540
  [push-back-disp]:      82  (very few dispatcher hits)
  [push-back]:            1  (almost no sphere-adjustment)
  [indoor-bsp]:           2  (indoor BSP barely queried!)
  [cell-transit]:         3  (3 transit events captured the sling:
                              0xA9B40148 -> 0xA9B40029 -> 0xA9B30030
                              all reason=resolver)

Sling-out signature: indoor BSP never engaged (only 2 indoor-bsp
hits), but the cell resolver fired 3 transit events crossing a
landblock boundary, with check-bldg thrashing in between. This is
distinct from scen2's stair-attempt pattern (which hammered the
BSP); scen4 shows the resolver pushing the character out of indoor
space entirely without triggering the indoor BSP collision path.

A6.P2 fix surface: investigate why ResolveCellId / CheckBuildingTransit
push a player from indoor cell 0xA9B40148 to outdoor cell 0xA9B30030
through routine walking. Likely the same family as the M1.5 hypothesis:
indoor cell membership isn't sticky (the ping-pong bug from the
2026-05-20 A4 handoff in a different guise).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 20:50:09 +02:00
Erik
4b5aebc61f capture(research): A6.P1 scen3 — inn 2nd floor paired traces
User reached the 2nd floor in acdream via ACE @teleport (stair-physics
unblocked separately by scen2). Retail walked normally to 2nd floor.
Both clients performed the same walk: forward 3 m, sidestep 1 m,
walk back. Flat-floor scenario, no stairs, no transitions.

Retail (decoded, 21,337 lines):
  BP1 transitional_insert: 10,217 hits
  BP4 find_collisions:     10,636 hits
  BP5 adjust_sphere:          113 hits
  BP6 check_walkable:         113 hits  threshold=0.6642
  BP2 step_up:                  0 hits  (no stairs)
  BP3 set_collide:              0 hits  (no walls)
  BP7 set_contact_plane:        0 hits  (KEY: zero CP updates)

Acdream (93,558 lines):
  [cp-write]:        86,748  (vs retail BP7 = 0 — INFINITE ratio)
  [push-back-disp]:   2,752
  [push-back]:          320
  [push-back-cell]:     550
  [other-cells]:        550
  [indoor-bsp]:       1,061
  [indoor-walkable]:    707

KEY FINDING for A6.P2: scen3 is the strongest CP-write blowup
evidence yet. On a flat 2nd-floor walk where retail's
set_contact_plane fires ZERO times across the entire scenario,
acdream rewrites the contact plane 86,748 times. This is the
exact pattern Finding 2 hypothesized (M1.5 design spec §1.2):
acdream resynthesizes CP every frame instead of retaining it
through the documented retention mechanisms (LKCP-restore,
Path-6 land write, post-OK step-down probe).

scen3 pair confirms CP-write blowup isn't stair-specific — it
fires equally for ordinary flat-floor walking inside any indoor
cell. A6.P3 fix surface: same as Finding 2 — stop resynthesizing
CP per frame.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 20:39:46 +02:00
Erik
297d1c54e8 capture(research): A6.P1 scen2 — replace acdream pair with stair-attempt
Original acdream capture (a9a427f) was a doorway-walk because acdream's
indoor stair physics doesn't work. For A6.P2 to characterize the
divergence we need the FAILURE captured, not a substitute walk.

User re-attempted the inn stairs in acdream (whatever it produces:
bumping, sliding, stuck). Failure signature is dramatic vs door-walk:

  Tag             | door-walk | stair-attempt | ratio
  ----------------+-----------+---------------+------
  push-back-disp  | 1,141     | 4,156         | 3.6x
  push-back-cell  | 87        | 1,478         | 17x
  other-cells     | 87        | 1,478         | 17x
  indoor-bsp      | 343       | 1,286         | 3.7x
  indoor-walkable | 227       | 859           | 3.8x
  cp-write        | 70,244    | 33,969        | 0.5x (!)

The 17x explosion on push-back-cell / other-cells says acdream's
CheckOtherCells loop fires constantly when physics can't resolve a
stair-step — the indoor BSP query fails, then the multi-cell
fallback fails, then the next tick repeats. The cp-write DROP
(half the door-walk volume) is the inverse signal: when no ground
plane resolves, no CP gets written. Both are A6.P2 fix-surface
indicators.

Now scen2 pair = retail successfully climbs (BP2 step_up=188) vs
acdream tries and fails (push-back-cell explosion).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 20:26:20 +02:00
Erik
a9a427fff9 capture(research): A6.P1 scen2 — inn stairs paired traces
Walked +Acdream up 4 steps of Holtburg inn, stopped on landing,
shuffled briefly to push past 50K cdb threshold. Acdream walk
exceeded the minimal scenario script (user explored further) —
scen2 dataset is a superset of the protocol; A6.P2 analysis can
window to the comparable section.

Retail (decoded, 110,104 lines):
  BP1 transitional_insert: 60,289 hits  (~5x scen1)
  BP2 step_up:                188 hits  (vs scen1 = 1 — stair signal)
  BP4 find_collisions:     47,783 hits  (~8x scen1)
  BP5 adjust_sphere:          780 hits  (~65x scen1)
  BP6 check_walkable:         677 hits  threshold=0.6642 confirmed
  BP7 set_contact_plane:      136 hits  (~7x scen1)

Acdream (73,937 lines):
  [cp-write]:        70,244 — CP-write blowup persists on stairs
  [push-back-disp]:   1,141
  [push-back]:          101
  [push-back-cell]:      87
  [indoor-bsp]:         343
  [indoor-walkable]:    227

Note: cdb's qd-in-BP-action threshold doesn't actually self-detach
(documented CLAUDE.md cdb watchout) — user closed retail manually.
Three remaining workflow steps work cleanly (decode + acdream pair
+ graceful close).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 20:21:05 +02:00
Erik
2f2b63f8bd docs(handoff): A6.P1 partial-ship — infra DONE, captures 1/9
Pickup-prompt + lessons doc for the A6.P1 capture work. Documents:

- The 16 commits shipping today (infrastructure Tasks 1-14 + cdb
  script v1→v4 iteration + scen1 capture + decoder).
- WHY cdb iterated 4 times: v1 wrong offsets, v2 PowerShell UTF-16,
  v3 cdb %f unreliable with dwo()/@@c++, v4 hex output works.
- Scen1 findings already strong: dispatcher entry frequency mismatch
  (acdream 20× fewer than retail) + ContactPlane write blowup
  (~100-1000× more frequent in acdream) — directly confirms the
  spec's M1.5 hypothesis about per-frame CP resynthesis.
- Per-scenario protocol validated by scen1.
- Pasteable session-start prompt for picking up scenarios 2-9.
- Known issues (kill-cdb-kills-retail, .printf %f unreliable, etc).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 20:05:17 +02:00
Erik
194ed3ef21 feat(cdb): A6.P1 — decode_retail_hex.py hex→float decoder
Python tool that decodes the retail.log hex-bits float fields produced
by a6-probe.cdb v4 into IEEE 754 single-precision values. Required
because cdb's .printf %f doesn't reliably format floats from dwo()
reads — v4 works around this by emitting 32-bit hex, this script
reinterprets via struct.unpack('<f', struct.pack('<I', value)).

Verified against scen1 retail.log:
  BP6 threshold_h=0x3F2A0751 → threshold=0.6642 (= FloorZ exactly)
  BP5 hit#1 Nz_h=0x3F800000 → Nz=1.0 (ground normal)
  9,517 float fields decoded across 9,331 lines.

Output written next to input as .decoded.log. Format matches
acdream-side [push-back] probe (4-decimal floats), so A6.P2
analysis can compare line-for-line.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 20:03:03 +02:00
Erik
8ca718a56d capture(research): A6.P1 scen1 — acdream.log paired with retail.log
Acdream-side capture for the Holtburg inn doorway walk, paired with
the v4 retail capture committed at 180b4a5. 84,130 lines total.

Probe line distribution (~30 sec session, ~2 sec actual walk):
  [push-back] (adjust_sphere): 8 hits   — vs retail BP5 12 hits
  [push-back-disp] (dispatch): 295      — vs retail BP4 5818 (!)
  [push-back-cell] (other_cells): 5     — vs retail's check_other_cells
  [indoor-bsp]: 26
  [cell-transit]: 30 (cell ID changes)
  [cp-write]: 73,304 (per-field writes) — vs retail BP7 18 fn calls (!)
  [cell-cache]: 540

Two major divergences already visible from this single scenario:

1. DISPATCH FREQUENCY: retail's BSPTREE::find_collisions fires 20×
   more than acdream's BSPQuery.FindCollisions. Could reflect either
   different physics tick rate, different sub-step cadence, or
   different call paths into the dispatcher.

2. CONTACTPLANE LIFECYCLE: acdream writes CP fields 73,304 times
   in 30 seconds (~2,400/sec). Retail calls set_contact_plane 18
   times (~0.6/sec). Even with a 6× field-write multiplier per
   set_contact_plane call, that's ~100 actual CP updates in retail
   vs ~12K in acdream — 100-1000× more frequent in acdream. This
   directly confirms the spec's hypothesis that FindEnvCollisions
   indoor branch is rewriting CP every frame (sub-step?) instead
   of retaining it across frames. Same family as the
   TryFindIndoorWalkablePlane workaround.

Per-call shape comparison (BP5 hit#1):
  Retail: plane=(0,0,1) d=-0.0, sphere=(0.0046,10.31,-0.27) r=0.48,
          mvmt=(0,-0,-0.75), winterp=1.0
  Acdream: plane=(0,0,1) d=-0.0, sphere=(-0.43,11.02,0.46) r=0.48,
           mvmt=Z-down, winterp 1.0→0.96 (small adjust applied)
Identical operation SHAPE (ground plane + vertical step-down probe
+ same radius). XY positions differ because walks were independent.

Scenario 1 complete. Remaining 8 scenarios deferred per user
direction. Python hex→float decoder + A6.P1 handoff doc to follow.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 20:02:08 +02:00
Erik
180b4a5010 capture(research): A6.P1 scen1 — retail.log with real hex-bits floats
v4 cdb probe captured paired field data for the Holtburg inn
doorway walk. 13,552 BP hits in ~2 sec of walking. Distribution:
  - BP1 transitional_insert: 7,686 (sub-step loop)
  - BP4 find_collisions:      5,818 (per cell per sub-step)
  - BP5 adjust_sphere_to_plane: 12 (the over-correction suspect)
  - BP6 check_walkable:        12
  - BP7 set_contact_plane:     18

Smoking-gun verification:
  BP6 threshold_h=0x3F2A0751 ≈ 0.664 = PhysicsGlobals.FloorZ
  BP5 plane normal = (0,0,1), movement = (0,-0,-0.75) — classic
       step-down probe against the ground polygon
  BP5 sphere radius = 0x3EF5C28F ≈ 0.480 m — player foot sphere

All hex-bits floats decode cleanly via Python struct.unpack('<f').
Decoder script TBD as part of the handoff.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 19:59:33 +02:00
Erik
2d841cb615 fix(cdb): A6.P1 — a6-probe.cdb v4 hex-bits floats
v3 with @@c++(*(float*)..) STILL produced 0.000000 across the board.
Conclusion: cdb's .printf %f is unreliable for our use case — possibly
doesn't handle the float-to-double promotion in varargs the way C
printf does, or has a deeper limitation we don't have time to debug.

Pivoting to: print all floats as 32-bit hex bits via %08X, reinterpret
in the Python analysis pipeline via struct.unpack('<f', bytes.fromhex(...))
to recover IEEE 754 single-precision values.

This bypasses cdb's float formatting entirely. Integer reads (which
work — substeps, insertType, collide flag, isWater) stay as %d.

The smoking gun: BP6's check_walkable threshold should be 0.0871556997
(cos 85°) per the decomp call site at acclient_2013_pseudo_c.txt:273202.
v4's BP6 should output threshold_h=0x3DB283D7. If it does, the
infrastructure is sound and we can proceed to all 9 scenarios.

v3 capture preserved as retail-v3-cpp-zero-floats.log audit trail.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 19:55:48 +02:00
Erik
1b6d49ea57 fix(cdb): A6.P1 — a6-probe.cdb v3 with C++ float reads
v2 dry-run produced correct hit counts but all %f field values
printed as 0.000000 — including BP6 threshold which the decomp says
must be 0.0871556997f (cos 85°). Root cause: cdb's MASM evaluator
returns dwo(addr) as a 32-bit integer; .printf %f expects a 64-bit
double; passing the integer to %f produces formatted-zero garbage.

Fix: switch all float-reading expressions to @@c++(*(float*)addr).
The C++ evaluator dereferences memory as a float pointer, returning
a proper float that .printf %f formats correctly. Integer reads (%d)
still use MASM dwo() — that works.

For double-indirect (pointer args), the form is
  @@c++(*(float*)(*(unsigned int*)(@esp+N)+offset))
which reads the pointer at [esp+N], adds the offset, and treats the
result as a float pointer.

v2 capture preserved as retail-v2-zero-floats.log audit trail.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 19:50:11 +02:00
Erik
d0c8c54d96 fix(cdb): A6.P1 — v1 dry-run lessons + v2 prep tooling
Dry-run of scenario 1 (retail-v1-broken-offsets.log preserved as
audit trail) surfaced three issues with the v1 cdb script:

1. STACK-ARG OFFSETS WRONG: BP actions used arbitrary registers
   (@edx, @edi) to read function args, but __thiscall puts non-this
   args on the stack ([esp+N] after the return address). All 12 BP5
   "adjust_sphere" hits printed Nx=0.0 Ny=0.0 ... — fields not read.
   Fixed by writing a type dumper (a6-types-dump.cdb + runner) that
   uses cdb's `dt` command against the loaded PDB to get authoritative
   struct offsets. v2 probe script (to be written next) will use
   double-indirect reads (dwo(poi(@esp+N)+offset)) with correct
   offsets from the dump.

2. TEE-OBJECT UTF-16 ENCODING: PowerShell's default Tee-Object writes
   UTF-16 LE with BOM, making logs unparseable by grep without
   conversion. Runner now uses Out-File -Encoding ASCII. Sacrifices
   live console echo; use `Get-Content -Tail 50 -Wait` in a separate
   shell if live monitoring is needed.

3. BP6 SYMBOL NOT FOUND: `acclient!CTransition::validate_walkable`
   doesn't exist in the PDB. Decomp at line 272811 has
   `CTransition::check_walkable` — likely the actual name. To be
   verified + fixed in v2.

The BP hit-count distribution from v1 is still meaningful diagnostic
data (14,318 transitional_insert + 16,558 find_collisions + 40
set_contact_plane + 12 adjust_sphere + 1 step_up + 1 set_collide in
a 2-second walk through the inn doorway). Preserved as a baseline
sanity-check the v2 distribution can be diffed against.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 19:38:31 +02:00
Erik
22e341faf6 docs(research): A6.P1 — refresh PDB verification to MATCH
User swapped in the correct Sept 2013 EoR build acclient.exe.
GUID {9e847e2f-777c-4bd9-886c-22256bb87f32}, linker UTC
2013-09-06T00:17:56 — exact match for refs/acclient.pdb.
T15 captures are unblocked.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 18:51:34 +02:00
Erik
260c60f8f5 docs(research): A6.P1 — capture directory structure + findings stub
Creates the 9 per-scenario capture directories (gitkeep stubs) and
the findings doc stub at docs/research/2026-05-21-a6-cdb-capture-findings.md.
A6.P1 fills the capture log slots (Task 15, user-driven); A6.P2
fills the analysis tables and findings section.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 18:46:56 +02:00