Commit graph

365 commits

Author SHA1 Message Date
Erik
ea60d1fb7d docs(spec): Phase A8 — full WB RenderInsideOut + RenderOutsideIn port (design)
Re-brainstormed after RR0 falsification showed R3+R3.5 introduced
Issues A+C (rendering all 16 BFS-reachable cells at full screen extent
caused co-planar Z-fight + grace-state leak from outside view). The
prior design's "WB-faithful restructure" was insufficient — it kept
the BFS-wide cell rendering. Retail and WB both solve indoor visibility
with per-portal recursive culling.

This design ports WB's full pipeline:
  - RenderInsideOut Steps 1-5 (including 3-stencil-bit cross-building)
  - RenderOutsideIn (cottage interiors visible through windows from outside)
  - Per-building cell association (Building + BuildingRegistry, plus
    LoadedCell.BuildingId for O(1) cell→building lookups)
  - Single strict cameraInsideBuilding gate (no grace for render path)
  - Stencil-gated sky inside indoor branch (acdream enhancement)

12 tasks (RR1-RR12), 8-10 sessions estimated. M1.5 indoor scope ships fully.

Supersedes:
  docs/superpowers/specs/2026-05-26-phase-a8-restructure-design.md
  docs/superpowers/plans/2026-05-26-phase-a8-restructure.md
(both will be footer-marked in RR1 cleanup)

Reverts in RR1: R3 (60f07bc), R3.5 v1 (38d5374), R3.5 v2 (2bfeafd).
R1+R2 (data layer + dispatcher partition) stay — orthogonal infrastructure.

RR2 spike resolves the BuildingInfo data shape + interior-portal walk
algorithm against WB PortalRenderManager:518-551 before RR3 implements.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 09:29:14 +02:00
Erik
f9bab501df docs(research): Phase A8 RR0 — Issues A + C caused by R3, NOT pre-existing
Three-branch falsification spike per the design's RR0:

  HEAD (2bfeafd, R3.5 v2):     Issue C YES; Issue A YES (varies by building)
  R3 baseline (60f07bc):        Issue C YES; Issue A YES (same as HEAD)
  main (7034be9, no A8 work):   Issue C NO; Issue A NO flicker; BUT
                                 constant #78 symptom (houses-below-terrain
                                 visible from inside)

Diagnosis: R3 (stencil pipeline wire-in) successfully fixes the original
#78 main symptom but introduces Issues A and C as new transition artifacts.
R3.5 v1+v2 patches didn't help (R3 baseline shows same A+C as HEAD).

Per the design's decision gate (Outcome 2): PAUSE plan; re-brainstorm
via superpowers:brainstorming to address A+C without re-introducing #78
constant leak.

The original restructure design assumed A+C might be pre-existing and
could be filed as separate out-of-A8-scope issues. RR0 invalidates that.
The restructure must address them OR the brainstorm needs a third option
between "stencil-gate everything" (causes A+C) and "no stencil work"
(causes #78).

Open questions for the re-brainstorm captured in the findings doc.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 08:33:32 +02:00
Erik
769a003138 docs(plan): Phase A8 — render-frame restructure implementation plan
Six-task plan implementing the approved design:
  RR0 — pre-restructure falsification spike (3-branch repro of A + C)
  RR1 — revert R3.5 v1 + v2 (38d5374 + 2bfeafd)
  RR2 — restructure render frame to WB-faithful order
  RR3 — verify SkyRenderer doesn't toggle stencil state
  RR4 — visual verification matrix (4 buildings + transitions + sky)
  RR5 — ship docs (close #78, file new follow-ups, update CLAUDE.md)

Bite-sized steps (2-5 min each) with exact code snippets, commands,
and expected outcomes. TDD where applicable; GL integration tasks are
visual-verification-only by nature.

Each task has explicit decision gates:
  RR0-S5 — outcome 2 (A8-caused) triggers re-brainstorm
  RR3-S1 — dirty SkyRenderer triggers wrapper variant
  RR4-S9 — building-type failure triggers /investigate

Design: docs/superpowers/specs/2026-05-26-phase-a8-restructure-design.md.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-26 22:48:46 +02:00
Erik
732f766d1b docs(spec): Phase A8 — render-frame restructure to WB-faithful order (design)
Brainstorm-approved design for the A8 R3.5 → restructure pivot. Replaces
the R3.5 v1+v2 frankenstein (terrain twice + depth-clear workaround) with
WB's RenderInsideOut order verbatim: skip initial sky+terrain when inside,
delete the depth-clear, add a stencil-gated sky step inside the indoor
branch so windows show real sky (closes R4 Issue B).

Unifies the two-flag asymmetry (cameraInsideCell lenient + cameraReallyInside
strict) into a single strict cameraInside flag via PointInCell. Grace
mechanism in CellVisibility stays alive for non-render consumers.

Six tasks ahead, in order:
  RR0 — pre-restructure falsification spike (Issues A + C on main?)
  RR1 — revert R3.5 v1+v2 (38d5374 + 2bfeafd)
  RR2 — restructure render frame to WB-faithful order
  RR3 — verify SkyRenderer doesn't toggle stencil state
  RR4 — visual verification matrix (cottage/cellar/inn/dungeon + transitions)
  RR5 — ship docs (close #78; file new follow-ups if pre-existing on main)

Next: superpowers:writing-plans to produce the per-task plan.

Note: the design references two predecessor docs that are currently
untracked in this worktree (entity-taxonomy + phase-a8-replan). Their
contents are read-stable on disk; committing them is a separate concern
(they belong to the prior session's work). The handoff doc this design
continues from is at f90fa2f.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-26 21:49:18 +02:00
Erik
f90fa2f863 docs(handoff): Phase A8 R3.5 paused for restructure — fresh-session handoff
R1 + R2 + R3 + R3.5 v1 + R3.5 v2 all shipped this session (ed727042bfeafd). Primary #78 fix works (cottage walls solid from inside). Three
transition / sky issues remain that resist symptom-level patching:

  A — Exit indoor→outdoor: "objects through ground + building parts missing"
  B — Inside through window: "sky doesn't render"
  C — Entry outdoor→indoor: "floor transparent showing cellar + wrong texture"

Root cause: architectural mismatch with WB's RenderInsideOut reference.
We draw initial terrain unconditionally + depth-clear-if-inside as a
workaround; WB skips initial terrain when inside and renders terrain
ONLY at the stencil-gated step. The R3.5 v1+v2 patches were symptom
fixes that kept producing new edge cases — the exact "patching symptoms"
anti-pattern the predecessor revert handoff called out.

Handoff doc captures: what shipped, what works, what doesn't (with
verbatim user reports), the architectural diagnosis (WB vs our pipeline),
the recommended next-session approach (brainstorm → write-plan → execute
with the full superpowers workflow), and a self-contained pickup prompt.

No code changes in this commit — handoff is doc-only. The 5 implementation
commits (ed727042bfeafd) remain at HEAD; next session decides whether
to revert R3.5 v1+v2 for a cleaner diff vs the R3 baseline, or layer
the restructure on top.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-26 20:52:46 +02:00
Erik
d2db8d5b22 docs: Phase A8 REVERT handoff — full session story + pickup prompt
Documents the 3-round visual verification failure of the original
A8 plan, the architectural taxonomy gap that surfaced (cottage walls
are landblock-baked stabs with ParentCellId == null, not cell mesh,
so the binary IndoorOnly/OutdoorOnly partition mis-classifies them),
and what the re-plan must consider.

Bottom line: the WB stencil approach is correct in principle and the
infrastructure (Tasks 1-6: PortalPolygons field, RenderingDiagnostics
flag, portal_stencil shaders, IndoorCellStencilPipeline,
PortalMeshBuilder, EntitySet enum) is correct and tested. The
integration (Task 7) made a wrong architectural assumption about
entity classification. Reverted by fef6c61, 96f8bd2, c897a17.

Includes detailed pickup prompt for the re-plan session: re-investigate
entity taxonomy (6 distinct classes documented), spike distinguisher
options (AABB-encloses-camera heuristic recommended for first ship),
re-plan Task 7 with MarkAndPunch-first GL order + separate live-entity
pass + 3-building visual verification requirement.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-26 09:42:53 +02:00
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
84e3b72b27 docs: #100 — stabilize Task 2 SHA reference in ISSUES.md
Task 2's commit landed at 64518d59, then an amend (to fix the original
placeholder SHA in the same file) produced the new HEAD a64e6f2 with
identical content. The in-file SHA still pointed at the pre-amend
64518d59 — reachable today only via reflog, unreachable after the next
git gc. Switch to a64e6f2 which is on the branch and survives gc.

This is a follow-up commit (not an amend) so the canonical SHA is
itself stable on the branch from this commit forward.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 21:47:37 +02:00
Erik
a64e6f20da refactor: #100 — remove hiddenTerrainCells / BuildingTerrainCells plumbing
Retired in favour of Task 1's retail-faithful terrain shader Z nudge.
Pure removal — ~50 LOC of dead surface area across:

  - src/AcDream.Core/Terrain/LandblockMesh.cs (drop parameter +
    cell-collapse block)
  - src/AcDream.Core/World/LoadedLandblock.cs (drop field)
  - src/AcDream.Core/World/LandblockLoader.cs (drop method + call)
  - src/AcDream.App/Rendering/GameWindow.cs (3 sites)
  - src/AcDream.App/Streaming/GpuWorldState.cs (6 ctor sites)
  - src/AcDream.App/Streaming/LandblockStreamer.cs (1 ctor site)
  - tests/AcDream.Core.Tests/World/LandblockLoaderTests.cs (drop test)
  - tests/AcDream.Core.Tests/Terrain/LandblockMeshTests.cs (drop test)

No retail anchor — the deleted mechanism never had one; this commit
rolls our code back to the actual retail behaviour established in
the prior commit's shader nudge.

ISSUES.md #100 moved to Recently closed.

Cross-ref:
  docs/research/2026-05-25-issue-100-terrain-cutout-handoff.md
  docs/superpowers/plans/2026-05-25-issue-100-terrain-cutout.md

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 21:37:53 +02:00
Erik
2fc312eac3 docs: #101 — fix fabricated content in Recently closed entry
Code-review found four Important findings in the ISSUES.md entry
that landed in 381561f:
- broken relative link to the plan (../superpowers/... vs
  superpowers/...)
- wrong test class name (PhysicsDataCacheTests, the actual
  class is PhysicsDataCachePhantomSourceTests)
- wrong predicate description (referenced PhysicsRadius and
  vAabbR; the predicate only checks the high byte and the
  cached BSP root)
- fabricated method names (GameWindow.RegisterGfxObjShadow and
  ShadowShapeBuilder.FromGfxObj — neither exists)

This commit corrects all four. The verification evidence in
the entry was accurate and is preserved.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 20:43:02 +02:00
Erik
381561f5cf docs: #101 — A6.P8 ship + ISSUES.md update (Outcome A)
Records the A6.P8 mesh-aabb-fallback suppression ship outcome
in CLAUDE.md and moves issue #101 to Recently closed. Visual
verification confirmed end-to-end ramp climb on GfxObj 0x01000C16's
BSP (walkable inclined polygon, Normal.Z=0.717).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 20:31:30 +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
8d4f14c173 docs(phys): implementation plan — per-part BSP for server-spawned entities
10-task TDD implementation plan for the design in
docs/superpowers/specs/2026-05-24-door-collision-per-part-bsp-design.md
(commit d71ceab). Each task is bite-sized (write failing test → run
→ implement → run → commit), with complete code in every step per
the writing-plans skill's "no placeholders" rule.

Map: Task 1-2 = ShadowShape + ShadowShapeBuilder; Task 3-6 =
ShadowObjectRegistry multi-part extensions (ShadowEntry fields,
RegisterMultiPart, multi-part UpdatePosition, Deregister cleanup);
Task 7 = RegisterLiveEntityCollision refactor (closes door bug);
Task 8 = landblock-static refactor (unifies paths); Task 9 = live-
capture regression pin; Task 10 = strip investigation diagnostics +
ship docs.

Visual verification gates after Task 7 (door fix surface) and Task 8
(static-collision regression check). 40+ test green-gate at every
commit boundary.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 15:05:03 +02:00
Erik
d71ceaba9c docs(phys): design spec — per-part BSP collision for server-spawned entities
Captures the brainstorm session 2026-05-24 evening after A6.P4 slice 1
(b49ed90) shipped without closing #99. Investigation surfaced the actual
root cause: doors register as a single 14cm × 20cm bounding-cylinder
approximation derived from Setup.Radius/Height fallback. Their real
collision-bearing geometry lives in per-part GfxObj BSPs (3 parts for
Setup 0x020019FF), including the threshold polygon spanning the doorway.

Retail-faithful design: every server-spawned entity registers N shadow
entries (one per CylSphere + one per Sphere + one per Part-with-BSP),
all sharing the same EntityId. UpdatePhysicsState propagates ETHEREAL
flips to all entries via the existing EntityId-iteration path. Unifies
the live-entity and landblock-static registration code paths under one
ShadowShapeBuilder.

Retail anchor: CObjCell::find_obj_collisions → CPhysicsObj::FindObjCollisions
→ CPartArray::FindObjCollisions → CPhysicsPart::find_obj_collisions →
CGfxObj::find_obj_collisions. One PhysicsObj per entity, parts iterated
internally for collision (acclient_2013_pseudo_c.txt:276776-275055).

Five-commit migration sequence; tests at three layers (builder unit tests,
registry behavior tests, live-capture regression pin). Approach A approved
by user 2026-05-24.

Spec stands on its own as M1.5 work; not formally assigned a phase letter
per CLAUDE.md's "don't invent phase numbers on the fly" rule.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 14:21:07 +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
5f3b64c548 docs(handoff): A6.P3 #98 — session paused after Shape 1 attempted + reverted
Updates ISSUES.md and CLAUDE.md to reflect the actual state of #98
after two days of work:

- The new [step-walk-adjust] probe (8a232a3) + capture + findings
  (8daf7e7) prove AdjustOffset's slope projection is CORRECT. Sphere
  Z climbs monotonically 90.95 -> 92.80 across the ramp at +0.045 m
  mean zGain per call. The earlier "Fix targets 1-4" priority list
  is OBSOLETE — AdjustOffset is not the bug.
- The climb caps at world Z ~= 92.80 because step-up's downward
  step-down probe finds no walkable within stepDownHeight=0.6 m
  below the proposed position. Cottage floor at Z=94 is ABOVE, not
  below. 101 stepdown-reject hits in the capture vs 1 acceptance.
- Shape 1 fix attempted (0cb4c59): gated BSPQuery.AdjustSphereToPlane's
  two SetContactPlane call sites by Normal.Z >= 0.99 to match retail's
  cdb-observed flat-CP-only pattern. Reverted (402ec10) — gate broke
  OnWalkable tracking. 74% of new capture in falling state. User
  report: "can't get up the first step, jumped, stuck in falling
  animation." Either retail synthesizes a flat CP from sloped
  contacts (step_sphere_down:321203 path, unclear from BN decomp)
  or our OnWalkable tracking is over-coupled to ContactPlaneValid.

Apparatus state: probe, findings, replay harness, plan, retail
cdb capture all committed and ready for next session.

Honest next-session moves (in order):
1. Build deterministic trajectory replay harness — 200ms inner loop
   instead of 5-minute live test. Issue98 cell fixtures are half of
   this already.
2. Pivot to less-coupled M1.5 issue while #98 awaits the harness.
3. Deeper named-decomp research on CEnvCell::find_env_collisions ->
   BSPTREE::find_collisions indoor CP-setting chain. Prior passes
   worked on the outdoor CLandCell path; indoor was never traced.

NO further #98 fix attempts until apparatus or research has
converged. Five+ failed attempts in the saga is the signal.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-23 16:59:43 +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
8a232a3e6e diag(phys): A6.P3 #98 — [step-walk-adjust] probe inside AdjustOffset
Adds one log line per AdjustOffset call (gated by ACDREAM_PROBE_STEP_WALK)
naming the branch taken (no-cp / no-cp-slide / slide-degenerate /
slide-crease / into-plane / away-plane, optionally +safety-push) plus
zGain = output.Z - input.Z.

No math or control-flow changes — pure observability so the next capture
can disambiguate the three failure-mode hypotheses for the cellar-ramp
climb cap. Re-reading the existing capture (a6-issue98-negpoly-...log)
showed the sphere DOES climb 90.00 -> 92.79 (2.79 m gain), then caps,
contradicting the divergence comparison's "no altitude gain" framing.
The real question is what stops the climb at world Z ~= 92.79 with the
cottage floor still 1.21 m higher. Existing [step-walk] probes wrap
AdjustOffset; this new probe reveals which branch the projection takes.

Fix plan with the four-branch decision tree at
docs/superpowers/plans/2026-05-23-a6-p3-issue98-cellar-up-fix.md.

Test baseline maintained: 1167 + 8.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-23 16:16:42 +02:00
Erik
67005e21f1 docs(handoff): A6.P3 #98 Step 6 — issue + claude.md handoff
Final step of the apparatus plan. Updates ISSUES.md issue #98 and
CLAUDE.md's M1.5 status to reflect:

- The apparatus completed (Steps 1-5 land in commits 35b37df28c282a).
- The real divergence: retail's sphere is at world Z ≈ 94.48 (resting
  on cottage floor) when find_walkable accepts; acdream's failing-
  frame sphere is 2.47m lower at world Z ≈ 92.01.
- The four fix targets, in priority order. Fix plan is the NEXT plan,
  scoped to Target 1 (step-up + ramp climb Z gain) or Target 2
  (cottage-cell sphere reference).
- The replay harness (Issue98CellarUpReplayTests) is the test loop —
  any fix that doesn't change the failing assertions is not the fix.

Today's commit graph on top of slice 5 (cf3deff):
  35b37df  triage — revert neg-poly + bldg-check experiments
  f62a873  Step 2 — cell-dump probe + roundtrip test
  3f56915  Step 2 capture — 3 real-geometry cell fixtures
  856aa78  Step 3 — deterministic replay harness (7 tests)
  6f666c1  Step 4 — retail cdb find_walkable capture script
  28c282a  Step 5 — replay vs retail divergence comparison
  (this)   Step 6 — ISSUES.md + CLAUDE.md handoff

Test baseline: 1167 + 8 (8 pre-existing failures, +19 new passing
tests across the apparatus). Build green throughout.

A6.P3 #98 is now in evidence-driven mode. Fix plan starts from the
divergence doc at
docs/research/2026-05-23-a6-p3-issue98-replay-comparison.md.

Pickup prompt for the fix-plan session is in §"Pickup prompt for the
fix plan" of that doc.
2026-05-23 15:58:52 +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
efb5f2c3b8 docs(issues): #98 sharpened diagnosis — Path 5 vs Path 6 path-selection bug
Updates issue #98 with the sharp diagnosis from the retail cellar cdb
trace (commit 134c9b8):

The bug isn't cell-resolver, isn't walk_interp, isn't dat-fidelity.
It's BSP path-selection: our dispatcher picks Path 5 (Contact step_up)
for the cellar ramp polygon when retail picks Path 6 (find_walkable
land). The ramp is walkable (N.Z=0.695 > FloorZ=0.6642) so Path 6 is
the correct choice. Investigation continues in next session at
BSPQuery.FindCollisions path-selection logic.

Also documents failed fix attempts this session as informational so
next session doesn't re-attempt the same dead ends.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 13:26:26 +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