docs(memory): distill CLAUDE.md render+physics banners into claude-memory digests

Collapse ~285 lines of dated render-flap + A6/#98/door/#100/#101 physics saga banners into two pointers to new claude-memory digests (project_render_pipeline_digest + project_physics_collision_digest), each carrying current-truth + a DO-NOT-RETRY table. Add an always-loaded memory pointer to the How-to-operate section: read the digests before domain work; update the digest, don't add new dated banners here. ~380 fewer lines in the always-loaded instruction file; no code change.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Erik 2026-06-09 20:20:17 +02:00
parent a1b12dff40
commit d0bd28543b

404
CLAUDE.md
View file

@ -249,6 +249,19 @@ pursuing live in [`docs/architecture/code-structure.md`](docs/architecture/code-
## How to operate
**Memory — read the digests before domain work.** Durable project knowledge
lives in `claude-memory/` (the auto-loaded index is `MEMORY.md`). Before starting
work in a domain that has a **digest**, read it first: `project_render_pipeline_digest.md`
(indoor render / doorway FLAP) and `project_physics_collision_digest.md`
(physics / collision / #98 / membership). Each digest is current-truth-on-top
plus a DO-NOT-RETRY table — it supersedes the dated banners that used to sprawl
across this file. The memory-handling protocol (distill-don't-journal, the digest
pattern, tags, recall + capture) is in `reference_obsidian_vault.md`. **Do NOT add
new dated banners to this file — update the relevant digest instead.** Obsidian
(auto-started in the main repo via a SessionStart hook) is the live search / graph /
tag lens over `claude-memory/` through the `mcp__obsidian__*` tools when it's
running; the files read and write the same with it closed.
**You are the lead engineer AND architect on this project at all times.**
You own the architecture (`docs/architecture/acdream-architecture.md`),
the execution plan (milestones doc + strategic roadmap), the development
@ -725,389 +738,22 @@ Visual side-by-side passed: Holtburg town, inn interior, dungeon all
render identically to pre-O. Spec:
[`docs/superpowers/specs/2026-05-21-phase-o-dat-path-unification-design.md`](docs/superpowers/specs/2026-05-21-phase-o-dat-path-unification-design.md).
**2026-05-30 — RENDER PIPELINE PIVOT (read this first).** The two-pipe
(inside / outside) render approach is **ABANDONED**. acdream inherited a
WorldBuilder-style split — a normal outdoor draw plus a separate flat
`RenderInsideOut` stencil pass toggled on `cameraInsideBuilding` — and that
split is the root cause of every indoor seam bug (the flap, missing/transparent
walls, terrain bleeding into interiors). Retail has no such split; it renders
through one portal-visibility traversal (`PView`) and is seamless by
construction. We are building **Phase U — a single unified retail-faithful
render pipeline**. This supersedes the A8/A8.F two-pipe arc (issue #103). The
camera-collision work (retail `SmartBox::update_viewer` spring arm) + a
physics viewer-cap fix **SHIPPED this session and are kept** (they're real and
retail-faithful, just not the seam fix). Full decision + scope + next-session
pickup prompt:
[`docs/research/2026-05-30-unified-render-pipeline-decision-and-handoff.md`](docs/research/2026-05-30-unified-render-pipeline-decision-and-handoff.md).
The M1.5 narrative below is history retained for context.
**2026-05-31 — U.4c doorway FLAP FIXED** (`0ee328a`, visual-verified "flap gone").
Root cause (converged on a live `ACDREAM_PROBE_FLAP` capture, after disproving an
H2 `PortalSide` side-test fix and an H1 PVS-grounding hypothesis): indoor visibility
was rooted at the 3rd-person camera **eye**, which drifts out of the player's cell →
`FindCameraCell` returns a STALE cell for its grace frames → the doorway portal is
culled as behind-the-eye → exit cell + terrain + shells drop. Fix: root indoor
visibility (cell resolution + portal-side test) at the **player's cell**
(retail `CellManager::ChangePosition`; matches the existing lighting decision). Eye
still drives projection. **The flap is done; the indoor pipeline is NOT yet seamless**
the visual gate revealed three SEPARATE residuals: (1) **#78** outdoor terrain not gated
inside (now more visible since terrain draws again); (2) **camera collision** needed (the
chase eye is outside the player's cell ~79% of frames → the eye-projected clip
over-includes → transparent outer walls); (3) **U.5** outside-looking-in (deferred).
Camera collision (retail `SmartBox::update_viewer` keeping the eye in the cell) is the
highest-leverage next step. CANONICAL handoff (read first next session):
[`docs/research/2026-05-31-u4c-flap-fixed-and-residuals-handoff.md`](docs/research/2026-05-31-u4c-flap-fixed-and-residuals-handoff.md).
Apparatus `ACDREAM_PROBE_FLAP` + `tools/A8CellAudit` are committed + ready. Do NOT retry
H1 (PVS grounding) or H2 (`PortalSide` side-test) — both evidence-disproven.
**Currently working toward: M1.5 — Indoor world feels right** (resumed
from 2026-05-20 baseline after Phase O ship).
**2026-06-08 (evening) — FLAP ROOT CAUSE SETTLED BY LIVE-RETAIL MEASUREMENT; full retail render
port DECIDED (Option A). READ THIS FIRST — it supersedes EVERY flap banner below, including the
bounded-propagation/churn direction (REFUTED by measurement: `maxPop=1`, 0 churn).** We attached cdb
to the **live 2013 retail client** at the Holtburg doorway + read the decomp. Findings (measured, not
inferred): **retail has ONE render path — `DrawInside(viewer_cell)` every frame, NO inside/outside
branch** (`RenderNormalMode`'s outside branch is dead code; `is_player_outside` only gates
sky/lighting). "Entering a building" is NOT a render event — only the camera sweep resolving a
different `viewer_cell` (outdoor `CLandCell` → indoor `CEnvCell`); same path before/after the
threshold → no seam → no flap. **Retail's eye JITTERS ~36 µm at rest** (so a byte-stable eye is the
WRONG target — my render-position rest-snap fix `cd974b2` failed + regressed, reverted `9b1857a`);
retail's membership is stable anyway because it does **many small per-building floods** (~7/frame,
~2 cells each, via the terrain BSP → `DrawPortal``ConstructView(CBldPortal)`), not one giant
unified flood. **Our 3 divergences:** (D1) we invented an inside/outside branch
(`GameWindow.cs:7498`, `clipRoot = viewerRoot ?? _outdoorNode` :7396); (D2) a synthetic `_outdoorNode`;
(D3) one unified flood. **Decision (user-approved): Option A — rip out the branch + outdoor node, root
always at the real `viewer_cell`, one `DrawInside`, per-building rendering.** DO NOT retry: byte-stable
eye, bounded-propagation/churn, physics rest-jitter, viewer-cell dead-zone, two-pipe split (all
evidence-disproven). **CANONICAL PICKUP (exhaustive — read top-to-bottom before any code):**
[`docs/research/2026-06-08-full-retail-render-port-OPTION-A-handoff.md`](docs/research/2026-06-08-full-retail-render-port-OPTION-A-handoff.md).
Close its §8 open traces (viewer_sought_position write site; ClipPortals/AddViewToPortals; how
`DrawInside` handles an outdoor `CLandCell` root) BEFORE writing the implementation plan.
**Indoor render & the doorway "FLAP" — read the digest, not banners.**
The full current state, the root cause, the DO-NOT-RETRY list, the ⚠️
`ACDREAM_PROBE_FLAP`→white-textures landmine, and the detail-doc pointers are
distilled in **`claude-memory/project_render_pipeline_digest.md`** (auto-loaded
via MEMORY.md). As of 2026-06-09 (HEAD `a1b12df`): governing direction is
**Option A — one `DrawInside(viewer_cell)`, NO inside/outside branch**;
R-A1/R-A2/R-A2b shipped (outside-looking-in flap GONE, seams GONE, portal-flood
churn KILLED); remaining = the visible indoor flap narrowed to §4 (edge-on
doorway grey + corner camera-seal). Render roots at the **VIEWER** cell, not the
player cell. Read the digest before any render/flap work — it supersedes the
dated render banners that used to live here.
**2026-06-05 (PM) — Indoor FLICKER + bluish VOID ROOT CAUSE CONFIRMED (decomp + live cdb); 3-part retail-faithful fix PLANNED (READ THIS FIRST).**
The "core inside render / cellar floor drops" framing below is **SUPERSEDED** by this session's diagnosis.
R1's per-cell `DrawInside` is already built and the cottage/cellar **seals** (user visual-verified). The
residual indoor **flicker (grey↔texture while standing still)** + **stable bluish void** are ONE root cause —
**visibility metastability at cell boundaries:** the 3rd-person camera **boom drifts at rest**
(`desiredBack 3.11→3.07`), walking the eye across a portal plane, and acdream re-resolves the **viewer cell**
fresh each frame with **no hysteresis** → it flips `0170↔0171` → the render (rooted at the viewer cell)
redraws two solves → flicker; and the 2D project-then-clip **degenerates** at close portals (`proj=0`) → grey
void. **Live cdb on retail CONFIRMS** retail's `viewer_cell` is rock-stable here (clean monotonic transitions,
ZERO oscillation across 4,916 samples) — retail holds the boom + uses a **0.2 mm cell dead-zone** + clips
portals **in clip-space**. **FIX (3 parts, retail-faithful, planned):** (1) camera boom stability
[`RetailChaseCamera`; `UpdateCamera` 0x456660] → kills the flicker trigger; (2) viewer-cell ±0.2 mm dead-zone
[`PhysicsCameraCollisionProbe.SweepEye`; `point_inside_cell_bsp` 0x53c1f0]; (3) w-space (w=0) portal clip
[`PortalProjection`/`PortalVisibilityBuilder`; `GetClip` 0x5a4320 / `polyClipFinish` 0x6b6d00]. Two partial
fixes committed: `5f596f2` (NDC side-plane clip — KEEP), `9f95252` (eye-in-portal flood — reassess/revert).
Baseline App 183p / Core 1326p-4f-1s. **CANONICAL PICKUP:**
[`docs/research/2026-06-05-viewer-cell-flicker-rootcause-and-fix-plan-handoff.md`](docs/research/2026-06-05-viewer-cell-flicker-rootcause-and-fix-plan-handoff.md).
**2026-06-05 — Render Residual A (camera collision) SHIPPED + user-kept; next = the CORE INSIDE RENDER (R1 completion) (SUPERSEDED 2026-06-05 PM — see the banner above; the cellar IS sealed, the real bug is the boundary flicker/void).**
Residual A = a verbatim port of retail `SmartBox::update_viewer` (pc:92761): the indoor sweep's start
cell is seated at the head-PIVOT via `AdjustPosition` (pc:280009) → `find_visible_child_cell` (pc:311397),
plus the two fallbacks + cellId==0 snap-to-player. Commits `0ffc3f5` (spec) / `5177b54` (Core primitives)
/ `9e70031` (`ResolveResult.Ok` + `SweepEye` orchestration); TDD, 11 new tests, no regression (Core
1326p/4f/1s, App 179p). **Key finding (live capture):** A's V1 sweep ALREADY contained the eye
(`eyeInRoot=Y` 99.75%, `viewerCell` never 0) — so A is a faithfulness completion, not the fix for the
dominant bluish void. **What A EXPOSED (the bridge to the next phase):** the render roots at the VIEWER
cell (`clipRoot = visibility.CameraCell`, GameWindow.cs:7322, Phase W V1 "one viewpoint"); A made that
cell ACCURATE (the eye's real cell), so when the player is in the cellar but the eye is up in the room,
`clipRoot = room` and the PVS flood from the room does NOT reach the cellar → the cellar floor drops.
The user chose **"Keep it"** (the faithful viewpoint) over revert. **NEXT = the CORE inside render (R1
completion)** — make `DrawInside` flood + seal correctly from the viewer cell so the cottage/cellar
interior is SEALED: no bluish void, no see-through-to-other-buildings BLEED, cellar floor draws. This
(NOT the handoff-era "C / outside-looking-in", which is R2, a later phase) is what fixes the visible
problems; the locked design already exists. **CANONICAL PICKUP:**
[`docs/research/2026-06-05-render-residual-a-shipped-core-inside-render-handoff.md`](docs/research/2026-06-05-render-residual-a-shipped-core-inside-render-handoff.md)
(+ the LOCKED render design [`docs/superpowers/specs/2026-06-02-render-pipeline-redesign-design.md`](docs/superpowers/specs/2026-06-02-render-pipeline-redesign-design.md)).
**2026-06-03 — P1 membership DONE + P2 active (history; cellar-lip now FIXED per the 2026-06-05 banner above).** The verbatim spatial-pipeline
port (master plan
[`docs/superpowers/specs/2026-06-03-verbatim-spatial-pipeline-port-master-plan.md`](docs/superpowers/specs/2026-06-03-verbatim-spatial-pipeline-port-master-plan.md))
is the active effort. **P1 (membership) = DONE** — proven to ALREADY match retail; the believed
doorway "0/11 lag" was a cdb CAPTURE ARTIFACT (`change_cell` logs `m_position` before `set_frame`
writes it). Aligned re-capture → production gate 9/9 with NO code change; live `[cell-transit]` clean.
Merged + pushed to both remotes (HEAD `f0d37d8`). **P2 (door/building-shell collision) = IN PROGRESS,
root cause LOCALIZED** to **BSP Path 5 grounded step-up** — the Path 5 wrappers (`DoStepUp`=retail
`step_up`, `DoStepDown`=retail `step_down`) are verified faithful; the divergence is in the step-up
CLIMB (`find_walkable`/`step_sphere_down` up-adjust). The 5 failing Core tests are P2's target. The
visible doorway seam has moved from physics into the RENDER path (P3 camera-collision + P4 PView seal).
The (a)(d) membership cleanups (remove `ResolveCellId`, unify `find_env_collisions`, intrinsic
building stabs, per-cell collision graph) are **approval-gated refactors of WORKING code**. **CANONICAL
PICKUP:**
[`docs/research/2026-06-03-p2-door-stepup-handoff.md`](docs/research/2026-06-03-p2-door-stepup-handoff.md)
(+ the P1 RESOLVED banner in
[`docs/research/2026-06-03-p1-membership-swept-advance-handoff.md`](docs/research/2026-06-03-p1-membership-swept-advance-handoff.md)
and the visual-gate/render-residuals note
[`docs/research/2026-06-03-p1-visual-gate-render-residuals.md`](docs/research/2026-06-03-p1-visual-gate-render-residuals.md)).
**A6.P1 + A6.P2 + A6.P3
slice 1 SHIPPED 2026-05-21.** **A6.P3 slice 2 v2 SHIPPED 2026-05-22**
(commit `f8d669b`): tried removing the L622 per-tick CP seed
(`892019b` v1) but it broke BSP step_up at the last step of stairs;
reverted + added a benign no-op-if-unchanged guard inside
`CollisionInfo.SetContactPlane`. Slice 2 outcome: **#96 partially
addressed — accepted as documented retail divergence** (the per-tick
seed is load-bearing for `AdjustOffset` slope-projection on sub-step 1
which BSP step_up depends on; matching retail would require deeper
refactor of AdjustOffset). Slice 2 verification surfaced a NEW
M1.5-blocking bug: **user cannot walk UP out of cottage cellar — stuck
at last step due to cell-resolver ping-pong (filed as issue #98,
Finding 3 family).** **A6.P3 slice 3 SHIPPED 2026-05-22** (commits `8898166` v1 +
`3e140cf` v2): cell-resolver stickiness added in `ResolveCellId`'s
indoor branch (point-in check against `fallbackCellId`'s CellBSP
before falling through to FindCellList). Data confirms ping-pong is
FULLY CLOSED — scen4 cellar capture shows 1 cell-transit (login
teleport) vs 20+ pre-fix. **#90 workaround now redundant — deferred
to A6.P4 removal. #98 APPARATUS COMPLETE 2026-05-23 evening**
(commits `35b37df` triage → `f62a873` cell-dump probe → `3f56915`
fixtures → `856aa78` replay harness → `6f666c1` cdb script →
`28c282a` divergence comparison doc). Four sessions of speculative
fixes (10+ variants) shipped the wrong diagnosis each time; this
session shipped the APPARATUS that turns evidence-driven analysis
into a 200ms test loop. 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 at world Z ≈ 92.01
(2.47m lower). Retail's ContactPlane writes during cellar-up are
ONLY flat floors (cellar floor or cottage floor), never the ramp.
Retail's find_crossed_edge fires once in 35K BPs; ours uses it
heavily. **Fix targets (priority): (1) Transition.AdjustOffset
slope projection / DoStepUp WalkInterp handling — ramp climb
doesn't gain Z; (2) cottage-cell candidacy using wrong sphere
reference; (3) find_crossed_edge over-use; (4) ramp polygon normal
divergence (low confidence).** Full divergence reading +
fix-plan pickup prompt at
[`docs/research/2026-05-23-a6-p3-issue98-replay-comparison.md`](docs/research/2026-05-23-a6-p3-issue98-replay-comparison.md).
Current A6 phase:
**A6.P3 — PAUSED 2026-05-23 (full day). Trajectory replay harness shipped
but BLOCKED on a new bug surfaced during commissioning.** Read
[`docs/research/2026-05-23-a6-p3-issue98-harness-handoff.md`](docs/research/2026-05-23-a6-p3-issue98-harness-handoff.md)
as the canonical pickup document — it has the chronological commit list,
the apparatus inventory, the exclusion list (do-not-retry), and three
concrete next-session options ranked by recommendation.
The session shipped further apparatus + first failed fix attempt + revert:
`8a232a3` (`[step-walk-adjust]` probe inside `Transition.AdjustOffset`
revealing branch tokens and per-call zGain), `8daf7e7` (findings note
at [`docs/research/2026-05-23-a6-stepwalkadjust-findings.md`](docs/research/2026-05-23-a6-stepwalkadjust-findings.md)
+ capture snapshot), `0cb4c59` (Shape 1 fix: gate `BSPQuery.AdjustSphereToPlane`'s
two `SetContactPlane` call sites by `Normal.Z >= 0.99`), `402ec10`
(revert — Shape 1 broke OnWalkable tracking, sphere went into falling
state on every sloped surface). **Refined diagnosis:** AdjustOffset is
CORRECT (145/146 calls take `into-plane` branch, +0.045 m mean zGain
per call when offset points into ramp); the climb CAPS at world Z ≈
92.80 because step-up's downward step-down probe finds no walkable
within 0.6 m below the proposed position (cottage floor is ABOVE).
Earlier "Fix targets 14" priority list is OBSOLETE — AdjustOffset
projection is not the problem. The actual bug is in the step-up
validation at the ramp top. **Honest next-session moves**: (1) build
deterministic trajectory replay harness so fix attempts iterate in
<500ms instead of 5-minute live-test cycles; (2) pivot to a less-
coupled M1.5 issue while #98 awaits the harness; (3) targeted decomp
research on `CEnvCell::find_env_collisions``BSPTREE::find_collisions`
indoor CP-setting chain (prior research worked on the outdoor
`CLandCell` path; indoor was never fully traced). Session-end ISSUES.md
entry has the full reading and pickup prompt. **NO further #98 fix
attempts until apparatus or research has converged — six+ failed
attempts in the saga is the signal.**
**Late-day extension (2026-05-23 PM):** trajectory replay harness shipped
(commits `4c9290c``5c6bdbe`). Mechanics work — runs 200 ticks in <100 ms.
Five tests pass. NEW finding: the cellar ramp polygon is in a GfxObj
(static building piece), not the cell's PhysicsPolygons. Harness now
includes `RegisterStairRampGfxObj` for synthetic stair construction
and `AttachSyntheticBsp` to wrap hydrated cells (which have BSP=null)
with a one-leaf BSP that exposes the indoor BSP collision path.
**NEW BLOCKER:** even with full apparatus, sphere goes airborne at
tick 1 with `hit=(0,1,0)` (a +Y wall normal matching no registered
geometry). 6 hypotheses tested via the harness, none isolated root cause.
Per systematic-debugging skill's "question architecture" rule, stop and
reflect. Next session: build a side-by-side comparison harness that
captures live PlayerMovementController state and diffs against the
test harness — evidence-first instead of speculation-first.
Findings doc:
[`docs/research/2026-05-21-a6-cdb-capture-findings.md`](docs/research/2026-05-21-a6-cdb-capture-findings.md).
**Evening extension v2 (2026-05-23 PM late) — apparatus shipped + root
cause identified.** Four commits (`fb5fba6``44614ab``0f2db62`
`f29c9d5`). The side-by-side comparison harness was built and exercised:
- `PhysicsResolveCapture` ships a JSON Lines writer for every player-side
`ResolveWithTransition` call. Off by default; turn on via
`ACDREAM_CAPTURE_RESOLVE=<path>`. Filtered to `IsPlayer` so NPC / remote
DR doesn't pollute.
- Two live captures from a cottage-cellar session (41K + 70K records).
- Three `LiveCompare_*` tests load 3 representative records (spawn,
on-ramp, first-cap). Spawn + on-ramp PASS bit-perfect; the first-cap
test originally FAILED with a clear divergence — and that divergence
pinpoints the root cause.
- **The cap is caused by `obj=0xA9B47900` — a landblock-baked cottage
GfxObj.** Cottage floor polygons live in this GfxObj's polygon table
(registered as a ShadowEntry), NOT in any cottage cell. The harness's
cell fixtures (0xA9B40143/146/147) don't include the cottage GfxObj,
so the harness fails to reproduce the live cn=(0,0,-1) cap.
- User's confirming observation: jumping in the cellar caps at the same
Z — purely vertical motion. This rules out every step-up / AdjustOffset
hypothesis from the prior 6-shape saga. The bug is the head sphere
hitting the cottage floor at Z=94.0 from below (math: foot Z=92.74
+ sphereHeight 1.20 = head center 93.94, head top 94.42, intersects
cottage floor Z=94.0).
- The first-cap test is now in documents-the-bug form (PASSES while
bug exists; FAILS when fix lands). Test baseline maintained at
1178 + 8 (serial run).
- 13 new cell fixtures cover the full 0xA9B4014X neighborhood (272 KB).
Findings doc (canonical pickup):
[`docs/research/2026-05-23-a6-p3-issue98-comparison-harness-findings.md`](docs/research/2026-05-23-a6-p3-issue98-comparison-harness-findings.md).
**Evening v2 follow-on — apparatus convergence SHIPPED 2026-05-23 PM.**
Two commits (`cc3afbc``97fec19`):
- `cc3afbc` adds the GfxObj dump infrastructure (`ACDREAM_DUMP_GFXOBJS`)
mirroring the existing `ACDREAM_DUMP_CELLS` pattern, with new
`GfxObjDump`/`GfxObjDumpSerializer` parallel to `CellDump`. The new
env var triggers `PhysicsDataCache.CacheGfxObj` to write the full
resolved polygon table as JSON when a listed id caches. Closes the
gap that the existing `[resolve-bldg]` probe couldn't fill (the BSP
wire site that populates `LastBspHitPoly` was never wired, so the
probe only emitted GfxObj-level metadata, not per-poly geometry).
- `97fec19` lands the cottage GfxObj fixture (`0x01000A2B`, 74 polygons,
BSP radius 13.989m matching live), the new `RegisterCottageGfxObj`
harness helper, and a minimum-stub landblock so
`TryGetLandblockContext` succeeds at the cellar XY. Harness now
reproduces the live `cn=(0,0,-1)` cap bit-perfect. The full per-field
round-trip uncovers ONE residual: live preserves +0.0266m of +X
motion through the cap (edge-slide along the cottage floor); harness
blocks all motion. Captured in
`LiveCompare_FirstCap_ResidualXMotionDivergence_DocumentsNextInvestigation`
in documents-the-bug form.
- All 21 issue-#98-relevant tests (12 harness + 4 GfxObjDumpRoundTrip +
1 new PhysicsDiagnosticsTests + 4 CellDumpRoundTripTests) pass
deterministically in isolation.
- Pre-existing test suite flakiness observed (819 failures across runs
of the same code, from PhysicsResolveCapture / PhysicsDiagnostics
statics leaking between test classes). INDEPENDENT of A6.P3 — verified
by stashing the cottage helper and reproducing the same flaky range.
Out of scope for this session; tracked as follow-up.
**Evening v3 finding (2026-05-23 PM, even later) — NEW root-cause
hypothesis identified:** the cottage-floor cap is a SYMPTOM. The actual
bug is **stale ramp contact plane causing per-tick Z drift** that makes
the cap reachable in the first place.
Evidence:
- Body's contact plane at cap = ramp's plane (n=(0, 0.7190, 0.6950),
d=-69.5035) from the live capture's `bodyBefore`
- Cellar ramp's actual world XY: X∈[129.7, 131.3], Y∈[10.19, 13.09]
(computed from the cellar cell fixture's vertex data + WorldTransform)
- Player position at cap: world (141.5, 7.22, 92.74) — **10 m away**
from the ramp in cell-local X
- `AdjustOffset` projects requested motion along the contact-plane
perpendicular. Math: dot((0.0266, -0.4022, 0), (0, 0.719, 0.695))
= -0.2892 → projected = (0.0266, -0.1943, +0.2010). **+0.201 m of
Z gain per tick**, applied because the engine believes the player
is on the slope.
- Head sphere top at cap = foot Z + 1.68 = 94.42. Cottage floor at
Z=94.00. **Head sphere exceeds cottage floor by 0.42 m** → cap fires
- If the contact plane refreshed to the flat cellar floor when the
player walked off the ramp, AdjustOffset would produce zero Z gain
(no Z component in requested motion + horizontal-plane perpendicular).
No drift, no cap.
How this question surfaced: user asked "we know how retail OPENs it
from above, how hard can it be to know how to open it from below?" —
that reframing made the question "what's different about our state
when walking up vs down?" The answer: **nothing, actually — the
cottage geometry is the same. But our contact plane is wrong.** The
six prior fix attempts were all investigating the cap-event mechanics
(step-up, slope projection at the cap, edge-slide, SidesType, +X
residual). None questioned why the contact plane was the ramp at all
when the player was 10 m from the ramp.
**Next-session move:** verify the stale-contact-plane hypothesis
chronologically against the live capture (walk the JSONL records, find
the last tick the player was on the actual ramp, quantify Z drift),
then locate the walkable-refresh code path in
`Transition.FindEnvCollisions` / `SpherePath.SetWalkable` that's
supposed to detect a new walkable polygon under the sphere and
overwrite the contact plane. Retail decomp anchor:
`CObjCell::find_env_collisions`. Full pickup prompt at the bottom of
[`docs/research/2026-05-23-a6-p3-issue98-comparison-harness-findings.md`](docs/research/2026-05-23-a6-p3-issue98-comparison-harness-findings.md).
**A6.P4 door bug — `pos_hits_sphere` near-miss recording shipped
2026-05-25 PM** (commit `3253d84`). Single-line ordering fix in
`BSPQuery.PosHitsSphere`: `if (hit) hitPoly = poly;` now precedes the
front-face cull, matching retail's `CPolygon::pos_hits_sphere` at
`acclient_2013_pseudo_c.txt:322974-322993` where `*arg5 = this` fires
on static-overlap BEFORE `dot(N, movement) >= 0 → return 0`. With this
ordering, Path 5's existing `if (hitPoly0 is not null)` near-miss
branch (`BSPQuery.cs:1869`) finally fires — `NegPolyHitDispatch`
sets `path.NegPolyHit`, the outer `transitional_insert` loop dispatches
via `slide_sphere`, and the sphere slides along walls it's touching
instead of squeezing through. The handoff hypothesized swept-sphere +
closest-considered-polygon tracking; reading retail showed both
`pos_hits_sphere` and `polygon_hits_sphere_slow_but_sure` are STATIC
tests using motion only for the cull — the fix is just the ordering.
3 new RED→GREEN unit tests in `BSPQueryTests.FindCollisions_Path5_*`
cover: overlap + parallel motion (RED→GREEN), overlap + away motion
(RED→GREEN), overlap + into motion (regression guard, already passed).
Zero regressions in full Core suite — with-fix failure set is a strict
subset of baseline (14 vs 17, the 14 are pre-existing static-leak
flakiness + 2 stale-capture document-the-bug tests). Issue #98
`LiveCompare_FirstCap_FixClosesCottageFloorCap` regression test
passes. **Needs visual verification at Holtburg cottage door inside-
out off-center ~50 cm scenario** before A6.P4 is marked complete —
sphere should block at the door surface with no squeeze-through. The
"runs a bit into the door" over-penetration symptom is hypothesized
to close together with the squeeze-through (continuous near-miss
recording while approaching a wall means the sphere slides along it
substep-by-substep rather than catastrophically penetrating then
recovering), but separate investigation if the symptom persists.
Original demo scenario (Holtburg Sewer end-to-end) is unreachable: sewer
doesn't exist on this server, and **issue #95** (portal-graph visibility
blowup) blocks any substitute dungeon. Revised M1.5 demo split into
building/cellar half (PARTIALLY ACHIEVABLE post-slice-1; cellar-ascent
blocked on #98) + dungeon half (blocked on #95). Issues in scope: #80,
#81, #83, #88, #90 (workaround removal after slice 3), **#95**
(visibility; not A6 scope), **#96** (L622 seed; retail divergence
accepted), **#97** (phantom collisions; may close as #98 side-effect),
**#98** (cellar-ascent stuck; A6.P3 slice 3 target), L-indoor,
L-spotlight, indoor sling-out (Finding 3 family with #98), and the
`TryFindIndoorWalkablePlane` definition deletion (A6.P4). **M2
("Kill a drudge") is deferred until M1.5 lands.** Full M1.5 writeup at
the corresponding block in `docs/plans/2026-05-12-milestones.md`.
**A6.P8 — Mesh-AABB-fallback phantom suppression for GfxObj-only stabs — SHIPPED 2026-05-25.**
Three commits: `f6305b1` (PhysicsDataCache.IsPhantomGfxObjSource + 3 unit tests),
`5240d65` (GameWindow.cs wire-in at line 6127), `6ca872f` (test-class doc
line-ref sync from code review). Issue #101 CLOSED — the 10 phantom stair
cyls on the Holtburg upper-floor cottage staircase are gone; collision
falls through to entity `0x40B50089` (GfxObj `0x01000C16`, `hasPhys=True`
BSP with walkable inclined polygon at `Normal.Z=0.717`, world ramp from
(111.10, 25.50, 94.00)→(107.50, 27.10, 97.50)). Visual-verified end-to-end
2026-05-25: holding W continuously climbs Z=94→97.5 over the full 45°
ramp; no phantom diagonal slides (`[cyl-test]` count on `obj=0x40B500*`
post-fix = 0 vs 7101 pre-fix). Spec:
[`docs/superpowers/plans/2026-05-25-issue-101-stairs-cyl-phantom.md`](docs/superpowers/plans/2026-05-25-issue-101-stairs-cyl-phantom.md).
**Issue #100 — Transparent ground around buildings — SHIPPED 2026-05-25 (primary acceptance);
visibility-culling follow-up handed off.** Three commits: `f48c74a` (terrain shader Z nudge,
retail `zFightTerrainAdjust = 0.00999999978` applied per-vertex in `terrain_modern.vert`),
`a64e6f2` (removed ~50 LOC of `hiddenTerrainCells` / `BuildingTerrainCells` plumbing across
LandblockMesh / LoadedLandblock / LandblockLoader / GameWindow / GpuWorldState /
LandblockStreamer + 2 dead tests), `84e3b72` (docs SHA stabilization follow-up).
Visual-verified 2026-05-25 PM at Holtburg: 24m × 24m transparent rectangles around
every cottage are GONE; ground reads as continuous cobblestone / grass. Plan:
[`docs/superpowers/plans/2026-05-25-issue-100-terrain-cutout.md`](docs/superpowers/plans/2026-05-25-issue-100-terrain-cutout.md);
predecessor research [`docs/research/2026-05-25-issue-100-terrain-cutout-handoff.md`](docs/research/2026-05-25-issue-100-terrain-cutout-handoff.md).
**Secondary finding from visual verification:** outdoor terrain mesh visible inside
cottage cellars at certain camera angles (clears when camera moves closer; gameplay
unaffected). High-confidence root cause: **indoor-cell visibility culling not gating
outdoor terrain** — same family as filed issue #78 (outdoor stabs visible through inn
floor) and #95 (dungeon portal-graph blowup). Per user direction, NOT filed as a new
issue; treated as additional evidence for #78. Next session investigates + ports
retail's `CEnvCell::find_visible_child_cell` (decomp anchor
`acclient_2013_pseudo_c.txt:311397`) and/or WB's `RenderInsideOut` stencil pipeline.
Full handoff with pickup prompt:
[`docs/research/2026-05-25-issue-100-shipped-and-culling-handoff.md`](docs/research/2026-05-25-issue-100-shipped-and-culling-handoff.md).
**Physics / collision / cell-membership — read the digest, not banners.** The #98 cellar-ascent saga, the A6.P* phase ledger, the door/step-up (P2) work, the phantom-collision fixes (#100/#101), P1 membership, the apparatus inventory, and the full 18-entry DO-NOT-RETRY list are distilled in **`claude-memory/project_physics_collision_digest.md`** (auto-loaded via MEMORY.md). Current state (2026-06-09): #98 CLOSED via the `b3ce505` stopgap (a WORKAROUND — it introduced #99 door run-through, OPEN HIGH); P2 cellar-lip FIXED (`cc4590f`, visual-gated); P1 membership matches retail (no port needed); #100/#101 CLOSED. The open debt is the per-cell shadow architecture (A6.P4) that closes #99. Read the digest before any collision/physics/membership work — it supersedes the dated A6 banners that used to live here.
**Today's pre-M1.5 baseline (2026-05-20).** Five surgical fixes
shipped to close the user-reported "logged in inside the inn, ran