Commit graph

402 commits

Author SHA1 Message Date
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
fdeede8796 docs(render): Phase U.4c — annotate Task 3 with U.4c-1 evidence (H2 selected)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 10:14:22 +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
211350b8a6 docs(render): Phase U.4c — implementation plan (stabilize portal visibility)
Five tasks: (1) RED apparatus reproducing the doorway flap on a synthetic
C0->C1->C2(exit) chain; (2) Layer 1 LoadedCell.VisibleCells + SeenOutside
plumbing; (3) oracle-ported PVS grounding of set membership (the fix, gated by
task 1); (4) seen_outside invariants (sealed=empty, threshold=stable); (5)
live [vis] + visual gate. Task 3 is a faithful port (add_views 433382 /
InitCell 432896 / ClipPortals 433572), pseudocode-first, not fabricated.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 09:53:38 +02:00
Erik
31f265d8ec docs(render): Phase U.4c — design spec (stabilize portal visibility / fix the flap)
Grounds the visible-cell SET in the stable per-cell PVS (stab_list) + seen_outside,
refreshed on cell entry, the way retail does (grab_visible_cells 311878, add_views
433382, DrawInside 433793). Our PortalVisibilityBuilder rebuilds the set per-frame
from a pose-brittle CameraOnInteriorSide walk, so a flipped side-test drops the exit
cell, empties OutsideView, and TerrainMode.Skip flaps terrain/shells off at the
doorway. Both stable inputs already live in-process (envCell.VisibleCells,
envCell.Flags & SeenOutside); U.4c is plumbing + grounding, not new dat parsing.

Apparatus-first: characterize the flap on a live ACDREAM_PROBE_VIS capture + port the
add_views/ClipPortals/AddToCell semantics to pseudocode before implementing; the
builder is not declared correct until a live [vis] shows non-empty + narrowing
OutsideView. No hysteresis band-aid (forbidden). Indoor rendering untouched.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 09:44:14 +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
65781f5768 fix(render): Phase U.2b — resolve reciprocal portal by other_portal_id (retail 433557)
Code review caught a CRITICAL under-inclusion: ApplyReciprocalClip scanned for the
first OtherCellId match, so a cell with two portals to the same neighbour clipped both
near-side openings against the FIRST reciprocal polygon — hiding geometry through the
second opening (real on Holtburg cellar cells 0x148<->0x149). Plumb the dat's
OtherPortalId back-link through CellPortalInfo + BuildLoadedCell and index the reciprocal
directly (retail arg2->other_portal_id, 433557). Skip (degrade to over-include) when the
index is unresolvable — never clip against a guessed polygon. Adds a disjoint two-back-
portal regression test.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-30 16:56:00 +02:00
Erik
3916b2b23e feat(render): Phase U.2b — reciprocal OtherPortalClip (retail 433524)
Clip the portal opening against the neighbour's matching back-portal polygon
before propagating, so a cell's clip region is the intersection of the opening
seen from both sides. Closes the M-4 stub in ISSUES #102. Can only tighten,
never under-include; degrades to prior behavior when no back-portal is found.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-30 16:37:14 +02:00
Erik
306cdb069c docs(render): Phase U.2a review fixups — LIFO-on-ties comment + ISSUES #102
Code-review minor follow-ups: correct the CellTodoList comments (ties are LIFO,
not FIFO — an equal-distance newcomer lands at the tail and pops first, matching
retail's break-on-first-not-greater + pop-from-tail). Update ISSUES #102 to record
that U.2a closes I-1/I-2 (under-count + duplicate accumulation) via the enqueue-once
gate, narrowing the residual to diamond-topology clip-completeness (AddToCell onward
re-propagation, tracked under U.6).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-30 16:30:41 +02:00
Erik
0f7b395be1 docs(render): Phase U — implementation plan (U.1-U.4 detailed, U.5/U.6 stubbed)
Ten bite-sized tasks to the first visual gate: U.1 delete two-pipe; U.2 GL-free
core (builder ordering+fixpoint, OtherPortalClip, ClipPlaneSet, ACDREAM_PROBE_VIS);
U.3 GPU gate (gl_ClipDistance in mesh_modern/terrain_modern + clip SSBO/UBO upload);
U.4 unified gated draw (EnvCellRenderer cell shells + WbDrawDispatcher All +
gated terrain; live-dynamic unclipped per retail) + per-instance slot assignment +
probe validation. U.5 outdoor-peering / U.6 dungeon-scale detailed after the gate.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-30 15:48:17 +02:00
Erik
8601137330 docs(render): Phase U — unified retail-faithful render pipeline design spec
One PView-faithful portal-visibility pass replacing the abandoned two-pipe
(inside/outside) split (#103). Settled in brainstorm 2026-05-30:
- Full Phase U in one spec (indoor BFS + outdoor building-peering + dungeon
  fixpoint + distance-priority ordering + reciprocal OtherPortalClip).
- Per-cell gate = hardware clip planes (gl_ClipDistance) + scissor pre-check
  (retail's two-level model); structurally immune to the #103 global-mask flood.
- Terrain stays its own path, gated to OutsideView (retail-faithful; NOT the
  handoff's "terrain as cells" sketch).
- Salvage = reuse the clip math (PortalView/ScreenPolygonClip/PortalProjection,
  ~36 tests), rework the builder (PortalViewBuilder), delete the stencil pipeline
  + GameWindow two-pipe orchestration. Audited keep-list preserves the real
  EnvCellRenderer / BuildingId / camera-collision fixes.

Staged U.1-U.6 with three visual gates. Retail anchors + acdream file:line
injection points catalogued in the spec.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-30 15:38:09 +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
05161399de docs(render): Phase A8.F — sync plan Task 2 moverFlags to shipped 0x5c
The plan's Task 2 code block still showed moverFlags: ObjectInfoState.None; the
shipped code (fcea05f) and spec §5.1 use IsViewer|PathClipped|FreeRotate|
PerfectClip (retail init_object(player, 0x5c)). Update the stale snippet so the
plan matches reality (this stale block was the likely source of a re-report).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-29 21:42:03 +02:00
Erik
53634b5089 docs(render): Phase A8.F — supersede the old "no camera collision" note
The 2026-05-18 retail-chase-camera spec scoped collision out citing "retail
doesn't raycast." Phase A8.F falsified that (SmartBox::update_viewer DOES sweep
viewer_sphere); mark the note superseded and point to the A8.F spec.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-29 19:46:53 +02:00
Erik
fcea05f808 fix(render): Phase A8.F — camera sweep uses retail moverFlags 0x5c (PathClipped hard-stop)
Code review found the probe passed ObjectInfoState.None; retail's
SmartBox::update_viewer calls init_object(player, 0x5c) =
IsViewer|PathClipped|FreeRotate|PerfectClip (pseudo-C :92864). PathClipped makes
the sweep hard-stop at first contact (TransitionTypes.cs:811) instead of
edge-sliding around corners (which would re-trigger the A8.F camera-cell
instability); IsViewer lets the eye pass through creatures, colliding only with
world geometry. Resolves the spec's slide-vs-stop open question. Also reset
CollideCamera in the Defaults_AreRetailValues baseline test (review: maintenance
trap). Spec §5.1/§11.1 synced.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-29 19:11:53 +02:00
Erik
77a6331ecd docs(render): Phase A8.F — camera-collision implementation plan
Bite-sized TDD plan for the swept-sphere camera collision: CollideCamera flag,
ICameraCollisionProbe + PhysicsCameraCollisionProbe (reuses ResolveWithTransition),
RetailChaseCamera slot-in, GameWindow wiring, Camera-menu toggle, visual
acceptance. Also refines the spec from planning findings: the InitPath +radius
sphere-center offset (ToSpherePath/FromSpherePath z-shift) and the deterministic
probe test scope (z-offset round-trip + cellId==0 guard; collision correctness
rides the existing sweep suite + visual).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-29 18:45:58 +02:00
Erik
9bdd50287b docs(render): Phase A8.F — swept-sphere camera collision design spec
Design for porting retail's stage-2 camera collision (SmartBox::update_viewer):
sweep a 0.3 m sphere from the head-pivot to the damped eye via the existing
ResolveWithTransition engine (collides both indoor cell walls and GfxObj
building shells, e.g. the cottage cellar per #98/#101), publish the stopped
position as the eye. Fixes the A8.F flap by keeping the eye out of walls so the
camera-cell + portal side-tests stay stable. Self-skip via LocalEntityId; gated
by CameraDiagnostics.CollideCamera (default ON). Corrects the prior
retail-chase-camera spec's "no camera collision" note.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-29 18:07:56 +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
270c21f263 refactor(render): Phase A8.F — Task 4 review follow-up (honest cap comment, cycle guard test, file fixpoint fast-follow)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-29 12:16:11 +02:00
Erik
c665f3eef3 docs: A8.F plan — record Task 3 near-clip correction + Task 4 winding requirement 2026-05-29 11:59:04 +02:00
Erik
612400f998 docs: Phase A8.F implementation plan — retail portal-frame visibility port
10 tasks (0-9): strip diag flags; GL-free CPU layer (ViewPolygon/CellView,
ScreenPolygonClip, PortalProjection, PortalVisibilityBuilder) with TDD;
stencil NDC entry; RenderInsideOut rewrite + Job-A/B decouple + three
wire-ins; visual gate. Bite-sized steps, complete code, retail anchors.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-29 10:58:00 +02:00
Erik
ca62d745fb docs: A8.F spec — correct Step 0 (A8 batch already committed in 5dc4140)
The 2026-05-28 handoff's "uncommitted A8 batch" is stale: 5dc4140 landed
the batch after the handoff. Step 0 reduces to stripping the leftover
ACDREAM_A8_DIAG_* flags (still present in RuntimeOptions + GameWindow).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-29 10:46:36 +02:00
Erik
d9d0809549 docs: Phase A8.F design — retail portal-frame visibility port (cellar-flap fix)
Faithful port of retail PView recursive portal-clip visibility
(ConstructView/ClipPortals/GetClip) to fix the residual A8 cellar flap.
Key finding: WB has no per-portal recursion — the flat-stencil algorithm
cannot express the fix; the recursion is retail-only. Builder ports as
GL-free CPU math producing a recursively-clipped OutsideView; enforcement
maps onto the existing A8 stencil pipeline. Builds on (does not supersede)
the A8 WB full-port baseline.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-29 10:44:44 +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
95f0d5267b docs(plan): Phase A8 WB RenderInsideOut port — implementation plan
Replaces the four reverted RR7 variants from 2026-05-27 with a
verbatim port of WB VisibilityManager.RenderInsideOut.

Plan covers 10 tasks across 5 dependency waves:
- Wave 1 (tasks 1-4, 7): extract WbRenderPass, WbFrustum,
  EnvCellSceneryInstance/EnvCellLandblock, EnvCellVisibilitySnapshot;
  add IndoorCellStencilPipeline.RenderBuildingStencilMask
- Wave 2 (task 5): build EnvCellRenderer with inline RenderModernMDI
- Wave 3 (task 6): wire EnvCellRenderer into landblock streaming
- Wave 4 (task 8): port RenderInsideOutAcdream byte-for-byte
- Wave 5 (task 9): probe trail [envcells]/[stencil]/[draworder]/[buildings]
- Wave 6 (task 10): probe-gated visual verification launch

Process rules carved from RR7 saga:
- No visual gate without probe data first
- No partial WB ports (Steps 1-5 ship together)
- No conceptual adaptations
- Trust-but-verify after every subagent

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 14:41:54 +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
29e306b0f6 docs: Phase A8 — mark prior restructure design+plan as SUPERSEDED
Both documents retained for historical reference. The new full-WB-port
design + plan (2026-05-26-phase-a8-wb-full-port-design.md + plan, ea60d1f +
651e7e2) replace them.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 10:08:48 +02:00
Erik
651e7e22fb docs(plan): Phase A8 — full WB RenderInsideOut + RenderOutsideIn port plan
12-task implementation plan (RR1-RR12, 94 step checkboxes total):
  RR1  — Cleanup: commit [vis] probe; revert R3+R3.5 v1+v2; supersede old docs
  RR2  — Spike: confirm BuildingInfo shape + WB interior-portal walk algorithm
  RR3  — Implement Building + BuildingRegistry + BuildingLoader (TDD, 10 tests)
  RR4  — Wire registry into landblock load + LoadedCell.BuildingId
  RR5  — WbDrawDispatcher.Draw(cellIds:) overload (TDD)
  RR6  — IndoorCellStencilPipeline 3-bit + occlusion-query helpers
  RR7  — Render frame: WB Steps 1-4 + outdoor branch + stencil-gated sky
  RR8  — Visual verification gate: Steps 1-4 close #78 + Issues A+C
  RR9  — Step 5 (3-stencil-bit cross-building + occlusion queries)
  RR10 — Visual verification gate: Step 5
  RR11 — RenderOutsideIn (cottage interiors through windows from outside)
  RR12 — Final visual matrix + ship docs (close #78, #102; update CLAUDE.md)

Each task: bite-sized 2-5 min steps; exact code snippets; commit per task.
Visual gates at RR8, RR10, RR12 ensure each layer works before adding the
next. Risk register handles RR2 data-shape uncertainty + RR9/RR11 frustum
API adaptation.

Estimated 8-10 sessions (~1.5-2 weeks calendar). Closes M1.5 indoor world
acceptance scope.

Design: docs/superpowers/specs/2026-05-26-phase-a8-wb-full-port-design.md.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 09:57:45 +02:00
Erik
ea60d1fb7d docs(spec): Phase A8 — full WB RenderInsideOut + RenderOutsideIn port (design)
Re-brainstormed after RR0 falsification showed R3+R3.5 introduced
Issues A+C (rendering all 16 BFS-reachable cells at full screen extent
caused co-planar Z-fight + grace-state leak from outside view). The
prior design's "WB-faithful restructure" was insufficient — it kept
the BFS-wide cell rendering. Retail and WB both solve indoor visibility
with per-portal recursive culling.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-26 09:42:53 +02:00
Erik
4cbfbf98af docs: #100 ship + indoor-cell culling investigation handoff
Session-end documentation for the issue #100 ship and the visibility-
culling investigation handoff for the next session.

Three documents land together:

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

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

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

Plus two updates to existing files:

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

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

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

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

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

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

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

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

ISSUES.md #100 moved to Recently closed.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 16:35:32 +02:00