Commit graph

182 commits

Author SHA1 Message Date
Erik
81ea3aa41a docs(p0): P1 design nuances — acdream already has FindTransitCellsSphere; test the production ResolveWithTransition path
From reading CEnvCell::find_transit_cells @ pc:309968: P1 is mostly REWIRING
curr_cell advancement (RunCheckOtherCellsAndAdvance/SetCheckPos) to use the
portal-crossing candidate, not FindCellSet's point-in-cell pick. The P1
conformance must replay the golden positions through ResolveWithTransition (a
trajectory incl. outdoor landcell 0031), not bare FindCellList.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 15:09:46 +02:00
Erik
bb4dead0ae test(p0): retail-trace golden captured — membership criterion divergence pinned (P0 GATE MET)
P0 Task 6 complete. Captured live retail membership at the 0031<->0170<->0171
doorway via cdb on CPhysicsObj::change_cell (symbol-driven; offsets verified by
discover-types.cdb; PDB MATCH). 22 transitions, clean monotonic sequence, NO
ping-pong (retail is correct-by-construction). Golden:
Conformance/Fixtures/find-cell-list-threshold.log.

ROOT-CAUSE FINDING (the central P1 work): retail transitions membership at the
PORTAL CROSSING (CEnvCell::find_transit_cells @ 0x52c820 pc:309968 — sphere crosses
the doorway polygon plane), while acdream's FindCellList re-picks by POINT-IN-CELL
containment at the foot. Retail commits room 0171 while the foot is STILL inside
vestibule 0170's BSP (in_0171=0); acdream lags. ALL 22 transitions diverge for this
one criterion mismatch — not a per-cell hysteresis or a building-entry-only split.
This is master-plan §0 'hysteresis gap' confirmed against the real client.

FindCellList_DoorwayThreshold_DivergesFromRetail_PendingP1 (documents-the-bug, GREEN)
+ ThresholdDivergenceDiagnosticTests (per-transition containment print) pin it; both
flip when P1 ports the directed portal crossing. Conformance 59 pass / 1 skip / 0 fail;
full Core 1308 pass / 5 fail (baseline) / 1 skip — no new failures.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 15:04:51 +02:00
Erik
1662da8731 test(p0): threshold-trace golden wiring + PVS scaffold + P1-entry checklist
P0 Tasks 6 (autonomous half) + 7. FindCellList_DoorwayThreshold_MatchesRetailTrace
asserts acdream's pick == each captured retail pick; skips until the capture
fixture lands. PvsConformanceTests scaffolds the render visible-set golden
(skipped; filled in P4). ConformanceDats.FixturesDir resolves fixtures from the
source tree (issue98 pattern). Notes record: existing retail traces are
collision-only (no membership) so the strict P1 gate needs the one live capture;
plus the P1 re-scope finding (Stage-1 membership already on this branch).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 14:29:30 +02:00
Erik
a90f34368f test(p0): dat-backed conformance loader + characterized cottage-doorway topology
P0 (verbatim-spatial-pipeline-port) Tasks 1+2. ConformanceDats loads the
cottage-doorway cells from the real dats with their real ContainmentBsp;
CottageDoorwayCharacterizationTests maps the Holtburg 0140..017F indoor
neighborhood and pins the master-plan threshold building (origin
161.93,7.50,94.00): 0xA9B40170 vestibule (exit portal 0xFFFF + portal to
0171), 0xA9B40171 room. Grid math confirms the outdoor side is landcell
0xA9B40031 -> the 0031<->0170<->0171 ping-pong is verified real. Verified
interior points recorded for the point_in_cell/find_cell_list goldens.

Plan: docs/superpowers/plans/2026-06-03-p0-conformance-apparatus.md
Notes: docs/research/2026-06-03-p0-conformance-apparatus-notes.md

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 14:20:17 +02:00
Erik
a1b49f9b24 docs: wrap session — doorway flap FIXED (membership + blue-hole); A/B/C render residuals next
Canonical handoff: docs/research/2026-06-03-membership-and-bluehole-shipped-handoff.md
(what shipped: membership Stage 1 ordered-CELLARRAY port + the blue-hole render-root
clobbering fix; the full remaining-issues list — A camera-collision, B R1b particles,
C R2 outside-looking-in, Stage 2 membership, #7 stairs, the 5-test baseline; KEEP/
DON'T-REDO; key files + decomp anchors; copy-paste pickup prompt for next session).

- ISSUES.md: recorded the cottage doorway flap DONE (both causes) in Recently closed.
- render design spec §7: R1 + flap marked DONE; A/B/C mapped to the next render phases.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 11:09:57 +02:00
Erik
b44dd147bc feat(physics): Stage 1 — CellArray ordered/deduped cell collection (retail CELLARRAY)
Ports retail CELLARRAY::add_cell (acclient_2013_pseudo_c.txt:701036): ordered list,
dedup by cell_id, append at end. The order is load-bearing for the verbatim
find_cell_list current-cell-first interior-wins pick (next commits) that fixes the
R1 cottage membership flap. Implements ICollection<uint> (helper-facing) +
IReadOnlyCollection<uint> (consumer-facing). 5 unit tests.

Also lands the membership-port pseudocode (workflow step 3) + the Stage-1 plan.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 08:54:45 +02:00
Erik
1438d73a43 docs(physics): handoff reframe — membership is STATE not recomputation (user analysis)
User's own decomp dig (verified): the flap's deepest root is architectural, not the
find_cell_list pick ordering. Retail membership is persistent object STATE (curr_cell
mutated ONLY by change_cell at a portal crossing); acdream RE-DERIVES CellId from
FindCellSet geometry every tick → ping-pong. Plus multi-valued CELLARRAY (retail) vs
single CellId (acdream), uniform vs forked collision (0x0100), intrinsic vs bridge
building entry. Reframed the handoff + prompt: the pick-ordering port (§4.3) is
SUPERSEDED/symptomatic; the job is STAGE 1 = persistent + multi-valued + portal-
crossing membership (change_cell 281192, find_transit_cells, SetPositionInternal),
drop the 5ca2f44 pre-check; STAGE 2 = uniform collision + intrinsic entry. New §4.4
(the 4-point analysis) + §4.5 (staged fix).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 08:20:55 +02:00
Erik
298b3b92b8 docs(physics): handoff — verbatim find_cell_list port (the R1 membership flap fix)
Canonical pickup for a fresh session. R1 (per-cell DrawInside render) shipped + is
correct (cellar seals); it exposed a pre-existing cell-membership ping-pong (the
flap). Root cause: CellTransit.BuildCellSetAndPickContaining picks from an UNORDERED
HashSet, dropping retail find_cell_list's current-cell-first ordering (CELLARRAY
index-0 + interior-wins-break, pc:308742-308825). Next job: verbatim port of that
ordered pick, replacing the HashSet + the 5ca2f44 pre-check approximation. User
authorized breaking any physics to get membership faithful. Full diagnosis, verbatim
retail source, fix plan, KEEP/don't-redo, test baseline, and a copy-paste pickup
prompt in the doc.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-02 22:24:22 +02:00
Erik
21bf97ed35 docs(render): REOPEN the render half — full retail-faithful redesign dossier (handoff + huge plan + 3 research docs)
The Phase W indoor seal did NOT land. The 2026-06-02 visual gate proved the interior render is fundamentally broken (#78: transparent walls, outdoor terrain + scenery entities bleeding in, grey floors, no outside-looking-in). Stage 4 (sky-through-door clip) was real but a top layer on a base that never sealed.

DECISIVE EVIDENCE (committed in the handoff): the PVS computes correctly AND the cell shells render correctly (opaque, textured, complete — the [shell] probe shows zero NOSNAP / zero missing-texture). The failure is the SEAL + three inconsistent gates — concretely the WbDrawDispatcher.cs:1756 ParentCellId==null -> return true bypass draws outdoor scenery indoors, and the indoor path draws the outdoor world then gates it instead of running ONLY DrawInside. Retail, when inside, runs ONE PView flood: visibility IS the cull; the landscape enters only through clipped exit portals + a conditional depth-only clear.

Dossier (per the user's mandate: NO shortcuts/bandaids, port from retail, redesign the whole pipeline if needed, brainstorm first):
- Master handoff (root cause + retail target + reusable-vs-redesign + apparatus + do-not-repeat + copy-paste pickup prompt).
- Huge staged redesign plan R0(brainstorm)->R1(one visibility authority, kill the bleed)->R2(indoor=DrawInside-only)->R3(the seal, DrawCells port)->R4(per-cell object/particle clip)->R5(outside-looking-in)->R6(dungeons)->R7(polish/conformance). Each ends at a user visual gate.
- 3 research docs: full retail render pipeline reference (705 lines, decomp-verified), acdream pipeline inventory + failure map, reference cross-check (WB two-pipe is the wrong model).

#78 promoted to the redesign. The 5 remaining Core test failures are pre-existing physics/collision bugs, none render-related.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-02 18:28:01 +02:00
Erik
55e1b30553 docs(render): Phase W session-2 handoff — membership FIXED + render rewrite (Stage 3 done)
Canonical pickup for the next session. Membership root cause (static :1947 re-derive)
FIXED the retail way (find_cell_list interior-wins pick + swept determination, 59f3a13)
and offline-verified (doorway strobe -> one clean transition). T0 made the suite
deterministic (12 known failures, none Phase-W regressions). Stage 3 (render-root
unification) DONE (6a1fbbd->573c555). Remaining: Stage 4 (the seal: sky/landscape inside
the portal-clip bracket + conditional doorway Z-clear = no blue-hole), Stage 5 (entity/
particle clip), green-tests triage, then the single final visual verification. Render is
wire-and-fill-gaps (PView infra exists). Flags a stash discrepancy (1 of 2 stashes missing
from the shared refs/stash) for the user to check against other worktrees.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-02 15:39:41 +02:00
Erik
840c1b6442 docs(render): Phase W (rev) — 4-model research + transition-membership/PView design
Four independent decomp studies (Opus 4.8 x2, Sonnet 4.6, external Codex)
converge: retail carries the cell through the collision sweep (validate_transition
advances curr_cell only on an accepted move, reverts on a block) and commits it in
SetPositionInternal — it never re-derives membership from a static resting position.
acdream already ports the sweep machinery (sp.CurCellId/CheckCellId, ValidateTransition,
CheckOtherCells) but ResolveWithTransition discards the swept cell and re-derives
statically via ResolveCellId (PhysicsEngine.cs:909/928) — the root of the
0170<->0031 doorway/cellar ping-pong. The do_not_load_cells prune is secondary
(static/cross-cell lists), not the anti-flicker; W2b was doubly misplaced and is reverted.

Render: one PView::ConstructView portal traversal over the same cell graph, rooted at
the physics current cell; seen_outside (not a dungeon flag) gates landscape; the outside
draws through exit portals clipped to the doorway (no blue-hole, no stencil split).
Dungeons/interiors share the machinery; "underground" is emergent.

Design doc lays out the staged, evidence-first rewrite (Stage 0 diagnostic ->
Stage 1 transition-owned membership [visual gate] -> Stage 2 CELLARRAY/prune parity ->
Stages 3-5 render root + PView seal + entity clip). Adds the shared research prompt and
all four study reports as the grounding record.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-02 13:58:51 +02:00
Erik
3622a658fd docs(render): Phase W — W2a shipped+verified + baseline handoff
W2a (render reads physics CurrCell) visually verified: indoor world-from-below fixed (cellar/stairs seal). Baseline scopes the residuals: W2b (doorway ping-pong 0170<->0031, confirmed) + W3 (one-gate seal: roof, entity bleed, openings) + the EnvCellRenderer GL_BLEND fix (transparent walls). Membership (W2) done.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-02 10:55:21 +02:00
Erik
e8c7164ad9 docs(render): Unified Cell Graph pivot — evidence model + Stage 1 spec
Pixel-grounded investigation concluded the indoor 'world from below' is a cell-MEMBERSHIP disagreement between render-side CellVisibility and physics-side ResolveCellId, not any single draw gate (terrain has one gated draw path; it leaks only on render null-root frames). Decision with user: full migration onto one retail CObjCell graph across physics+collision+render+streaming, staged in 5 verify-each cycles. This lands the evidence model + the Stage 1 (ObjCell scaffold) design. No code yet.

- docs/research/2026-06-02-render-cell-membership-evidence.md (the why, from pixels)

- docs/superpowers/specs/2026-06-02-unified-cell-graph-stage1-design.md (Stage 1)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-02 08:27:00 +02:00
Erik
1d7d8b1de4 docs(render): session-2 handoff — stencil attempt reverted, evidence-first pickup prompt
Net code change this session = 0 (stencil-occlusion T1-T4 implemented, regressed,
reverted to baseline 9bff2b0). Documents the honest failure + lessons (patchwork via
flag-based gate routing; the interior-writes-mask rule breaks outdoors; coded before
screenshotting), the still-useful evidence (cottage = IsBuildingShell GfxObjs not cell
shells; two redundant traversals; retail DrawCells outside_view gate; working window
screenshot tooling), the open questions to answer with pixels first, and a refined
evidence-first pickup prompt.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-01 13:51:22 +02:00
Erik
0013819fa1 docs(render): ARCHITECTURE RESET — indoor render is a 3-gate patchwork; handoff + unified-PView target
A week on the indoor render (Phase U.4 → U.4c → 2026-05-31) fixed the flap but
produced NO shippable progress: walls/ceiling don't seal, outdoor terrain is
visible from inside (#78), the enclosure reads grey/transparent. Root cause is
ARCHITECTURAL, not a bug.

Evidence this session (direct, via the new [shell] probe + screenshots) RULED OUT
every subsystem except the gating architecture: the interior cell shells render
fine (geometry/texture/opaque/depth all correct, zh=0 tr=0); the visibility
traversal computes correct sets + non-empty portal clips; cull mode is fine; the
camera/eye thread was a detour. The residual is that OUTDOOR geometry is not gated
to portal openings when indoors, and acdream enforces visibility THREE inconsistent
ways (TerrainClipMode / per-cell shell clip / entity ParentCellId filter with an
outdoor-stab bypass) instead of retail's ONE PView gate.

This commit is the reset handoff + documentation, not a code fix:
- docs/research/2026-05-31-render-architecture-reset-handoff.md — canonical: honest
  state, evidence ledger (ruled-out / do-not-repeat), the mapped 3-gate patchwork,
  the retail PView target (one traversal → one gate for ALL geometry), the reset
  mission, and a copy-paste pickup prompt.
- docs/architecture/acdream-architecture.md — new "Render Pipeline" SSOT section
  (current divergence + unified-PView target + the one rule: compute visibility
  once, enforce it once). (Doc has pre-existing corruption below this section —
  flagged for separate cleanup.)
- Apparatus: ACDREAM_PROBE_SHELL → [shell] (EnvCellRenderer per-cell prepared/drawn
  geometry + flags) added to RenderingDiagnostics + EnvCellRenderer. Throwaway.
- docs/superpowers/specs/2026-05-31-camera-collision-indoor-engagement-design.md —
  spec for e099b4c (camera collision; now parked as orthogonal to the seam).

Next session: STOP point-fixing; do the architecture reset to a single PView gate.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 21:35:55 +02:00
Erik
3066460370 diag(render): camera-collision indoor non-engagement — RED test + diagnosis
Root cause (b): ShadowObjectRegistry.GetNearbyObjects (line 480) returns early
when primaryCellId is an indoor cell, skipping the outdoor radial sweep that
contains the landblock-baked cottage exterior-shell GfxObj. The issue-#98 fix
that prevents the player's head sphere from being capped by the cottage floor
also prevents the IsViewer camera sweep from finding the exterior building shell.
Result: camera passes through exterior walls unimpeded, driving the residual
transparent-walls symptom after the U.4c flap fix.

Evidence: live capture shows eyeInRoot=n ~90% of frames, eye-player distance
3.43m (full chase, no pull-in). RED test deterministically reproduces: synthetic
indoor cell (0xA9B40175) + exterior GfxObj registered at cellScope=0; probe
SweepEye returns pulledIn=0.0000m (full eye distance Y=5.0, wall at Y=4.0).

Fix design: exempt IsViewer from the indoor-primary early-return gate in
GetNearbyObjects — retail's find_obj_collisions (named-retail :308918) has no
indoor/outdoor cell gate; the acdream fix is correct only for IsPlayer.

Apparatus committed:
- tests/AcDream.App.Tests/Rendering/CameraCollisionIndoorTests.cs (RED test)
- docs/research/2026-05-31-camera-collision-indoor-diagnosis.md (findings + design)
- PhysicsCameraCollisionProbe.cs [flap-sweep] diagnostic retained (U.4c spike)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 18:02:37 +02:00
Erik
95b6874c12 docs(render): Phase U.4c — flap fixed + residuals handoff (checkpoint)
Canonical handoff (research note) for the U.4c flap fix + the three residuals the
visual gate revealed (#78 terrain-not-gated-inside, camera-collision need, U.5).
Records the full hypothesis journey (H1/H2 both evidence-disproven) so the next
session doesn't re-walk them. ISSUES.md: flap recorded in Recently-closed; #78
annotated (more visible post-fix). CLAUDE.md: U-phase orientation updated with the
flap-fixed status + the canonical handoff pointer + camera-collision-next.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 17:01:23 +02:00
Erik
f47895cc73 research(render): Phase U.4c — CONVERGED root cause (eye-rooted grace-stale root)
Live ACDREAM_PROBE_FLAP moving capture: flap frames are uniformly res=Grace
eyeInRoot=n terrain=Skip; good frames eyeInRoot=Y terrain=Planes. The 3rd-person
camera EYE drifts out of the player's cell -> FindCameraCell returns the stale cell
for 3 grace frames -> from that stale root the doorway portal is behind the eye
(D=+1.26 CULL) -> exit cell drops -> terrain+shells Skip. Clip math is fine
(clip=5 when eye inside). Fix: (1) root visibility at the PLAYER's cell (retail
CellManager::ChangePosition tracks curr_cell by player; acdream already does this
for lighting at GameWindow:7152); (2) keep a player-reachable cell + exit when the
threshold eye-projection degenerates. Supersedes H2 and the earlier idle-frame
'stale root refuted' note.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 14:32:19 +02:00
Erik
8941d1e6e5 research(render): Phase U.4c — refute eye-crosses-plane; correct stale H2 note
A8CellAudit portals now dumps each cell's local AABB. Real flap cells: 0171 local
y in [-7.65, 1.15], 0170 in [-8.61, -7.65]; the 0171->0170 portal plane is at
y=-7.65 (0171's MIN boundary), no overlap. So an eye genuinely inside 0171 always
has side-test D<=0 -> always traverses 0171->0170; the side test cannot cull 0170
while the eye is in 0171. The flap therefore requires the eye OUTSIDE 0171 while
root is still 0171 (cache/grace/3rd-person camera) -> a camera-cell-resolution
issue, not the side test (H2, disproven) and not the per-frame PVS set (H1, in
doubt). Mechanism still unconfirmed -> needs a live eye-pos capture. Stale H2
conclusion in the characterization note corrected with a banner.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 10:36:30 +02:00
Erik
b5f2bf2b8f research(render): Phase U.4c — DISPROVE the side-test fix (PortalSide port is a no-op)
InitCell decode (PortalFlags.PortalSide=0x2) + a swept-pose A8CellAudit comparison
(O=centroid, A=winding-corrected PortalSide, B=opposite) over the real flap cells.
A is IDENTICAL to O at every pose/every portal — the (Flags&2)==0 boolean convention
makes the dat PortalSide sense equal to our centroid sense, so swapping is a no-op
and cannot fix the flap. B culls true-interior poses (wrong polarity). Conclusion:
the flap is NOT the side-test sense — it's the 3rd-person camera eye crossing an
interior portal plane while FindCameraCell still roots in the cell; ANY plane-side
test culls there. No production code changed (no no-op shipped).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 10:32:52 +02:00
Erik
13d58cae6a research(render): Phase U.4c-1 — characterize the flap on real dat evidence
A8CellAudit portals dump extended to print per-portal plane + centroid-derived
InsideSide vs the dat's authored PortalSide. Real Holtburg cottage cells show:
the flap is a DIRECT 0xA9B40171->0xA9B40170 portal side-test flip (0170 is a
direct neighbour, not multi-hop), and our centroid-derived InsideSide is
anti-correlated with the dat PortalSide that retail InitCell (432896) uses.
Evidence selects H2 (port the side test) over H1 (PVS set-grounding). Camera
cell 0171 seenOutside=Y. Full reading + fix direction + open sign question in
the note.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 10:13:16 +02:00
Erik
a3ecac5369 docs(render): Phase U.4 shipped (indoor rendering verified) + flap handoff
Phase U (U.1-U.4) shipped: the unified retail-faithful render pipeline replacing the
abandoned two-pipe split (#103). Indoor rendering VISUALLY VERIFIED — solid walls, no
terrain bleed, per-cell clip gating works. Two root-caused EnvCellRenderer
self-contained-GL-state fixes landed (uViewProjection stale-matrix; inherited
blend/depth-mask). Residual threshold "flap" (OutsideView instability from the per-frame
view-dependent portal BFS) is precisely root-caused via ACDREAM_PROBE_VIS and scoped to
U.4c (PVS / stab_list grounding, retail-faithful). Handoff captures the [vis] evidence,
the retail anchors, and the next-session pickup. U.5 (outdoor->building peering) + U.6
(dungeon scale) remain.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 09:16:35 +02:00
Erik
75b1df9cc3 docs: abandon two-pipe render approach; scope Phase U (unified retail-faithful pipeline)
Decision (2026-05-30, with user): the WB-inherited two-pipe (inside/outside) render
split is the root cause of the indoor seam bugs (flap, missing/transparent walls,
terrain bleed) and cannot be seamless. Abandon A8/A8.F (#103); build ONE unified
pipeline driven by retail's PView portal visibility — seamless by construction. The
2026-05-30 camera-collision + physics viewer-cap work is kept (retail-faithful, but a
detour from the seam fix). New Phase U scoped; #103 superseded; CLAUDE.md / roadmap /
milestones updated; full decision + scope + next-session pickup prompt in
docs/research/2026-05-30-unified-render-pipeline-decision-and-handoff.md.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-30 11:35:41 +02:00
Erik
9757818e95 docs(render): Phase A8.F — correct camera handoff; retail DOES collide the camera
Correction after the user (who has played retail and observed the camera pull in
at walls) flagged the prior "no camera collision" conclusion. Verified against the
decomp: retail's camera collision lives in SmartBox::update_viewer (0x00453ce0),
NOT CameraManager::UpdateCamera. The earlier research traced only the producer
(UpdateCamera computes the desired/damped eye -> viewer_sought_position) and missed
the consumer (update_viewer), which sweeps a 0.3 m viewer_sphere via
CTransition::find_valid_position from the head-pivot to that eye and uses the
stopped position (fallbacks: AdjustPosition, then snap to player). The player-fade
when super close (CameraSet::UpdateCamera -> SetTranslucencyHierarchical) is a
SEPARATE stage, already ported as RetailChaseCamera.ComputeTranslucency.

Implication: a swept-sphere camera collision is RETAIL-FAITHFUL, not a divergence —
no special sign-off needed, and acdream already owns the Transition swept-sphere
engine. Updated TL;DR, KEY FINDING, the fix section (was "design decision"),
slot-in (collide the damped eye, after RetailChaseCamera.cs:131), open questions,
pickup prompt, and reference index. Memory updated likewise.

Lesson recorded: when the decomp says "no X" but a domain expert says X exists,
trace the CONSUMER of the computed value, not just the producer.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-29 17:51:44 +02:00
Erik
ce909ad0a8 docs(render): Phase A8.F — camera-collision root cause + handoff (session 2)
Root cause of the A8.F flap / missing-walls reframed (with the user's help):
the 3rd-person camera EYE passes through walls, and the A8.F renderer keys its
"am I inside?" (PointInCell) and portal side-tests (CameraOnInteriorSide) off
that eye position (camPos = invView translation, GameWindow.cs:7271). Eye clips
a wall -> those decisions flip frame-to-frame -> the flap.

Key finding from camera research (Opus agent + verified against the decomp):
retail's camera does NOT collide with walls either — it fades the player to
translucent (CameraSet::UpdateCamera @ 0x00458ae0 -> SetTranslucencyHierarchical),
which acdream already ports as RetailChaseCamera.ComputeTranslucency. So a
"spring arm that pulls the eye in on a wall hit" is a deliberate divergence from
retail, not a faithful port — needs user sign-off before coding.

Handoff documents: the eye->visibility coupling + flap mechanism, acdream's
current camera (the ported turn/jump input-lag = damping + velocity ring +
mouse filter; no collision), retail's camera (symbols+addresses), the reusable
swept-sphere collision machinery (BSPQuery.FindCollisions vs CellPhysics.BSP),
3 fix options (lead: modern spring arm), open design questions, apparatus, and a
pickup prompt.

Bug A (cellar terrain flood) already fixed + committed in 9417d3c; the
recursive-clip builder works (the prior "Bug B" framing was wrong).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-29 16:40:41 +02:00
Erik
cf3d49cbd7 docs: Phase A8.F visual-gate failure handoff + issue #103
A8.F (retail portal-frame port) shipped Tasks 0-8 but failed its visual gate:
indoor branch renders broadly wrong at runtime (terrain over walls, transparent/
invisible walls). Default game unaffected (branch gated behind
ACDREAM_A8_INDOOR_BRANCH). Two compounding root causes documented (OutsideView
under-produces; Job-A/B else-branch floods ungated terrain) + apparatus + a
first-fix hypothesis + pickup prompt. Filed #103.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-29 14:43:24 +02:00
Erik
5dc4140c11 feat(render): Phase A8 — indoor visibility + streaming fixes batch
Lands the working A8 indoor-rendering and streaming fixes accumulated this
session. User has verified these visually to some degree (e.g. lifestone /
translucent meshes confirmed fine under the FrontFace flip; bridge / wall /
collision regressions confirmed fixed after travel); not every path has been
exhaustively gated. The cellar-flap defect remains OPEN and will be solved
the retail-faithful way via a dedicated brainstorm (see handoff docs).

Rendering core (reviewed, high confidence):
- EnvCellRenderer SSBO stride fix: upload packed Matrix4x4[] (64B) instead of
  the 80B CPU InstanceData struct the shader never expected — fixes the
  transform/texture "explosion" for any draw with >1 instance (cells that
  dedupe to a shared cellGeomId). Real root cause.
- WB-style global FrontFace(CW) + per-batch CullMode carried through the MDI
  layout (GroupKey + BuildIndirectArrays + DrawIndirectRange split into
  same-cull runs with absolute uDrawIDOffset per run).
- EntitySet partitioning (IndoorPass / OutdoorScenery / LiveDynamic) +
  WorldEntity.BuildingShellAnchorCellId so building shells scope to their
  dat-derived building cell instead of rendering everywhere.
- RenderOutsideInAcdream (look into buildings from outside) +
  CollectVisiblePortalBuildings frustum cull of portal bounds.
- Sky-when-inside-building + per-cell audit probe + GL-state probe.

Streaming / perf (test-covered; not independently code-reviewed this session):
- Near/far priority queues so near work wins over far; PromoteToNear carries
  full landblock + mesh data; LandblockEntriesWithoutAnimatedIndex avoids
  rebuilding the animated-lookup dict in the hot draw path. Fixes the
  bridge-not-appearing / missing-walls / broken-collision-after-travel
  regressions and improves post-transition FPS.

Tooling + docs:
- tools/A8CellAudit: offline dat cell/portal/building dumper (portals +
  buildings modes) — reproduces the cellar-flap investigation with no launch.
- docs/research cellar-flap root-cause + option-2 handoff (the didInsideStencil
  double-duty finding + the WB-recursive design decision + brainstorm prompt),
  entity-taxonomy, replan, issue-78 visibility investigation.

Diagnostics retained on purpose: ACDREAM_A8_DIAG_* gates, portal_stencil.vert
provisional pos.w clamp, and the probe families are kept (env-var gated, zero
cost when off) because the pending option-2 cellar-flap brainstorm needs them.
Strip in the option-2 ship commit.

Indoor branch stays behind ACDREAM_A8_INDOOR_BRANCH=1 (default off = pre-A8
visual). Build green; App tests + Core (streaming/dispatcher/loader) tests pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-29 10:14:50 +02:00
Erik
e415bb3863 docs: Phase A8 — session 2 handoff (pool fix shipped + 4 partial fixes + residuals)
After 5 visual gates, the session shipped 5 commits closing real bugs
(pool aliasing was the catastrophic root cause), but residual symptoms
(transparent floor, texture warping, flickering, distortion) didn't
yield to surgical fixes. Per systematic-debugging skill's >=3-failures
rule, stop and capture state.

Doc covers:
- Pool aliasing root cause + fix (the big win — closes session-1's
  visual chaos).
- Sky-when-building, LiveDynamic, Landblock→None — all real bug closures.
- Apparatus state (GL state probe + per-cell audit + pool diagnostics).
- Three theories for the residual issues (FrontFace=CW global match to
  WB / per-poly Stippling audit / WB side-by-side render).
- Pickup prompt for next session with ranked options.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 20:32:22 +02:00
Erik
9559726960 fix(render): Phase A8 — pool aliasing in EnvCellRenderer (visual chaos root cause)
The post-Wave-5 indoor branch chaos (flickering, missing walls, GPU 100%,
~10 FPS) is caused by two interconnected pool-management bugs in
EnvCellRenderer that line-by-line WB comparison surfaced in 30 minutes.
Neither was found by the five post-Wave-5 speculative fixes because none
of them inspected the pool path.

Bug #1 — GetPooledList missing list.Clear():
The reuse branch returned pool lists with prior-frame data still inside.
PrepareRenderBatches' merge phase pattern `gfxDict[k] = list; list.AddRange(...)`
assumes empty lists. Without Clear(), lists grow unbounded each frame, GPU
draws cumulative instance counts, and per-instance transforms become a stew
of past + present data. Mirrors WB ObjectRenderManagerBase.cs:1221-1233.

Bug #2 — Render uses snapshot.BatchedByCell.Count instead of PostPreparePoolIndex:
The snapshot author dropped WB's PostPreparePoolIndex field calling it
"scenery-only," then "compensated" in Render by setting _poolIndex to the
cell count. The cell count has no relation to the pool — Prepare may have
used 50+ pool lists for an 18-cell scene. Render's filter-path GetPooledList
then returns lists that ARE in snapshot.BatchedByCell, corrupting the snapshot
mid-Render. Restoring PostPreparePoolIndex (WB VisibilitySnapshot.cs:31)
correctly places Render's pool cursor past the snapshot's owned region.

Bug #3 (minor) — PopulateRecursive hardcoded isSetup:false for nested parts:
Setup IDs use high-byte 0x02 (per retail). WB ObjectRenderManagerBase.cs:813
checks `(partId >> 24) == 0x02` to detect nested Setups. Our port always
passed isSetup:false, silently dropping any nested Setup (its TryGetRenderData
returns IsSetup=true, Render's `!IsSetup` guard skips the draw). Probably
rare in EnvCells but fixed for completeness.

Regression coverage:
- GetPooledList_ReusedList_IsClearedBeforeReturn — would have failed pre-fix
- GetPooledList_FreshList_IsAlwaysEmpty — sanity check
- Snapshot_PostPreparePoolIndex_IsInitSettable — compile-time guarantee
- Snapshot_PostPreparePoolIndex_DefaultsToZero — defensive default

86/86 App tests pass. Build green. The fix is the audit's primary
deliverable; the GL state probe option-1 apparatus follows in a separate
commit as defense-in-depth for any unidentified residual issue.

Full audit + WB cross-reference in
docs/research/2026-05-28-a8-env-cell-renderer-audit-findings.md.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 19:08:49 +02:00
Erik
3d0ffaa794 feat(render): Phase A8 — kill-switch ACDREAM_A8_INDOOR_BRANCH (default OFF)
After 5 visual-gate failures with speculative fixes that each addressed
plausible-looking symptoms without resolving the chaos (texture flicker,
missing walls, GPU 100%, ~10 FPS), this commit stops the speculation and
ships a kill-switch that reverts default behavior to pre-A8.

The user's verbatim authorization at session start said "no quickfixes
or fixes that might cause issues down the line ... no band-aids." The
post-Wave-5 fix stream WAS band-aids — each fix was pattern-matched
against possible RR7-era causes without confirming the actual root
cause from evidence. Five failures in a row is the signal to stop.

ACDREAM_A8_INDOOR_BRANCH gate:
- Unset or != "1" (DEFAULT): cameraInsideBuilding forced false. Outdoor
  Draw(All) path runs for indoor cells too. Pre-A8 depth-clear-if-inside
  workaround at line ~7314 is restored. Visual behavior = pre-A8.
- Set to "1": indoor branch (RenderInsideOutAcdream) runs. All A8 code
  exercises. Probes ([envcells]/[stencil]/[draworder]/[buildings]) emit.

All Phase A8 scaffolding (Waves 1-5 + post-Wave-5 fix commits) remains
in tree, accessible for the next-session apparatus to test against.
~1,830 LOC of WB-extracted infrastructure preserved.

Handoff doc at docs/research/2026-05-28-a8-wb-port-shipped-but-broken-handoff.md
captures the full chronicle: which fixes were applied, what each
visual-gate launch reported, the root-cause hypotheses tested and
falsified, the remaining unknowns, and the recommended apparatus
approaches (frame-replay harness / per-step GL state probe / WB-renderer
side-by-side / mesh-data audit).

Next session's mission: NO MORE LIVE LAUNCHES until apparatus is built.
This is the same trap the issue #98 saga fell into before the
trajectory-replay harness shipped.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 16:26:40 +02:00
Erik
3e9ff7accb docs(research): A8 RR7 reverted — full WB-port handoff for next session
Four RR7 variants shipped + reverted in one session (RR7, RR7.1, RR7.2,
RR7.3). The root architectural mismatch: RR7 routed cell-mesh rendering
through ObjectMeshManager / WbDrawDispatcher.Draw(IndoorPass) — a per-
GfxObj batched pipeline. WB uses a separate EnvCellRenderManager (862
LOC) for cells; we never extracted it. Indoor branch fires correctly
after RR7.2 + RR7.3 but interior cell geometry doesn't render.

User direction (verbatim, 2026-05-27): port WB verbatim. No band-aids.
Visual test launch only when fix is ready; probe data verified first.

Handoff captures:
- Session log of all four RR7 attempts + why each failed
- Why WB over retail (modern GL fit + existing Phase N.4/N.5/O
  commitment to WB as rendering base)
- The full WB RenderInsideOut algorithm spec (Steps 1-5, line refs)
- 5-phase next-session plan (extract EnvCellRenderManager + deps,
  wire into landblock load, replicate RenderInsideOut byte-for-byte,
  probe trail mandatory before visual gate, single visual gate)
- Process rules carved from this session's mistakes (no visual gate
  without probe data first, no partial WB ports, no conceptual
  adaptations, trust-but-verify, slow at brainstorm not implement)

RR3-RR6 infrastructure remains shipped + tested in isolation
(Building/Registry/Loader/Dispatcher cellIds overload/Stencil pipeline).
Branch is at pre-A8 visual ("looks good") with infrastructure dormant.

Next session opens cold against the pickup prompt at the bottom of
the handoff doc.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 14:10:33 +02:00
Erik
f44a9bf943 docs(research): Phase A8 RR2 — BuildingInfo data shape + interior-portal walk
Spike findings before RR3 (BuildingLoader impl). Documents:
  - DatReaderWriter.Types.BuildingInfo field shape (verbatim ilspy decomp
    of DRW 2.1.7 — type is BuildingInfo with field BuildingPortal, not the
    plan's tentative BldPortal; same OtherCellId semantics)
  - WB PortalService.GetPortalsByBuilding interior-portal walk algorithm
    (BFS through EnvCell.CellPortals; 0xFFFF == exit-portal sentinel)
  - Holtburg town landblock 0xA9B4FFFF live BuildingInfo dump: 12 buildings,
    1-10 portals each, including the cottage from the #98 cellar saga at
    idx=6 (cells 0xA9B40145/014C/014E/014F/0150)
  - Resolved BuildingLoader algorithm + 2 minor rename corrections vs the
    plan's RR3 pseudocode (BuildingPortal not BldPortal; defensive 0xFFFF
    skip kept matching WB)
  - 6 edge cases (empty portals, shared cells, unloaded interiors, etc.)

Gate decision: data shape compatible — proceed to RR3.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 11:01:39 +02:00
Erik
a5d2244467 docs(handoff): Phase A8 RR1 shipped — pickup prompt for RR2 spike
Session-end handoff capturing:
- RR0 findings + design + plan + RR1 cleanup all shipped (8 commits)
- Working tree at logical R2-baseline + [vis] probe
- RR2 (BuildingInfo spike) is next; ~30-60 min; human-in-the-loop step
  for live-inspect
- Canonical doc-read order for fresh session
- Pickup prompt with state-both-altitudes header

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