Step 4 summary-emit adapted from the plan: the plan's Invariant($"a" + $"b" + sb) form
passes a string to FormattableString.Invariant (which requires a FormattableString) and
does not compile; merged the two interpolated fragments into one literal and appended the
already-invariant-formatted reciprocal detail outside the Invariant call. Same output.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The 2026-06-08 AM "physics rest micro-jitter" diagnosis is refuted with primary
evidence (door-recheck 216K standstill records: 0 position re-snaps; player
byte-stable during the flap). Two adversarial verification sub-agents confirmed:
- Retail roots the render at the camera viewer_cell (swept from the player via
SmartBox::update_viewer 0x453ce0; DrawInside(viewer_cell) 0x453aa0) and toggles
DrawInside / LScape::draw -- so acdream's eye-cell rooting + inside/outside
toggle are RETAIL-FAITHFUL. The locked-design "root at player cell" is wrong.
- The flap is render membership instability, eye-motion-driven: the visible-cell
set oscillates (8<->3) as the eye sweeps monotonically. Root = the
re-enqueue-on-growth DRIFT (PortalVisibilityBuilder.cs:322, MaxReprocessPerCell
=16) re-clipping each grown cell every round -> sub-cm eye jitter flips membership.
Fix (spec, not yet implemented): verbatim port of retail's enqueue-once flood
(ConstructView + AddViewToPortals): enqueue once on first discovery, clip each
cell's portals once, union late growth in place (AddToCell) + draw-reorder
(FixCellList), never re-enqueue. Kills the drift; rooting/camera/seal untouched.
This commit lands VERIFIED GROUNDWORK + design only:
- spec: docs/superpowers/specs/2026-06-08-portal-flood-enqueue-once-port-design.md
- findings: docs/research/2026-06-08-flap-physics-diagnosis-REFUTED-its-render-membership.md
- [pv-input] probe gains rawPlayer + yaw (disambiguates the varying input)
- 4 GREEN physics rest-stability tests (prove rest is bit-stable -> flap not physics)
- apparatus: launch-flap-capture.ps1, analyze_flap_live.py, find_burst.py
- captured fixtures: tests/.../Fixtures/flap-doorway/0xA9B4017{0..5}.json
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Apparatus + handoff for the indoor flap. Confirmed (primary evidence): the flap is the
portal-flood clip being µm-sensitive at the threshold, driven by a ~1-8µm jitter in the
player RenderPosition (physics resting position not bit-stable; Lerp surfaces it). REFUTES
the 2026-06-07 see-through/EnvCell/outdoor-node diagnosis (ModelId GfxObj 0x01000A2B IS the
solid exterior) AND an enqueue-once attempt (retail propagates late slices via AddToCell;
the existing PropagatesNewSlicesToExit test caught it; reverted). Adds: Build determinism
test, A8CellAudit gfxobj dump, [pv-input] 6dp probe + [render-sig] outRoot/bshell fields.
No functional fix shipped. Next: higher-precision physics rest trace -> port retail
kill_velocity/contact rest-stability. Canonical: docs/research/2026-06-08-flap-rootcause-physics-rest-handoff.md
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Cutover-flip follow-up: see-through buildings from outside. When the outdoor-node flood reaches a building, each interior cell is meant to draw clipped to its doorway aperture. But DrawEnvCellShells falls back to the no-clip slot 0 (full-screen) when a cell's aperture degenerates — screen-covering when you get close, or edge-on. Indoors that fallback is load-bearing (it seals the room the camera stands in; near walls hide the over-draw). From OUTSIDE it paints the building interior across the whole screen, depth-tested, so it shows wherever the solid exterior does not cover — the see-through walls, appearing 'past a threshold' exactly where the aperture degenerates.
Fix: for the outdoor-node root only, skip a flooded interior cell with no real plane-clip slot (HasRealClipSlot). From outside, 'no real aperture' means 'do not paint this interior', not 'paint it everywhere'. Interior roots keep the seal-everything slot-0 fallback unchanged. Applied to DrawEnvCellShells AND DrawCellObjectLists so a skipped cell shows neither walls nor furniture; the dead DrawPortal exterior look-in gets the same gate.
Root cause traced over the WB EnvCell render path: CellMesh.cs is physics-only; ObjectMeshManager.PrepareCellStructMeshData builds double-sided walls, so this was never a culling bug. App 216/0, build green. Visual gate pending.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Cutover-flip follow-up (issues 1+2 from the visual gate). After the flip an outdoor-node frame ran DrawInside with a full-screen OutsideView slice (Step A) whose ClearDepthSlice wiped the ENTIRE depth buffer AFTER terrain/exteriors/player drew — so the flooded building interior shells (cellars) drew over everything: the cellar painted in front of the player from outside, and distant building interiors showed through the ground in the open field.
The depth clear is a doorway look-in trick (clear a small door region so the cell seen through it draws over the terrain drawn through it). It is wrong for the full-screen base terrain of the outdoor root. Skip it there (ClearDepthSlice=null when clipRoot is the outdoor node); interiors now depth-test against terrain + exteriors and appear only through real door openings. Interior roots keep the doorway clear unchanged.
App 216/0, build green. Visual gate pending (player must be outdoors to exercise the outdoor root).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The CUTOVER FLIP that kills the indoor FLAP. Replace the two-branch gate (clipRoot = playerIndoorGate && viewerRoot ? viewerRoot : null) with clipRoot = viewerRoot ?? _outdoorNode, so EVERY frame routes through the one RetailPViewRenderer.DrawInside path rooted at the viewer cell — interior EnvCell when the eye is indoors, the synthetic outdoor node when outdoors. There is no inside/outside branch to toggle as the 3rd-person eye crosses the doorway, so the flap (textures battling at every transition) dies by construction. Matches retail SmartBox::RenderNormalMode -> DrawInside(viewer_cell) (0x453aa0 -> 0x5a5860).
clipRoot is null only pre-spawn/login (viewerCellId==0 -> _outdoorNode null), so the outdoor LScape block still runs as the safety path and login keeps its live sky. playerIndoorGate stays computed for the [render-sig] probe.
Preserve the LiveDynamic entity draw (server entities with no resolved ParentCellId — the transient unpositioned case) for the outdoor-node root: the old outdoor branch drew it; DrawInside does not, so re-issue it after DrawInside to keep the spec section 10 byte-identical-outdoor guarantee (no live entity blinks out).
The old outdoor else block + DrawPortal/BuildFromExterior are now dead when clipRoot is non-null but are LEFT IN PLACE for the user visual gate (handoff section 4 Step D deletes them only after the user confirms). App 216/0, build green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Render unification cutover, Step A (additive, behavior-neutral until Step B). When PortalVisibilityBuilder.Build roots at the synthetic outdoor node, seed OutsideView with the full-screen NDC quad so ClipFrameAssembler yields a full-screen OutsideView slice and DrawInside's DrawLandscapeThroughOutsideView draws terrain/sky/scenery/weather as the node's shell — the same callback that already draws the doorway slice for an interior root looking out.
Keyed on a new explicit LoadedCell.IsOutdoorNode flag (set by OutdoorCellNode.Build), NOT a cell-id heuristic: production EnvCell ids are >= 0x100 but test fixtures use low interior ids, so an id test misfired on 4 existing PortalVisibilityBuilderTests.
Nothing roots at the node until Step B, so this is behavior-neutral. Tests: App 216/0 (2 new UnifiedFloodTests incl. the spec section 10 pure-outdoor regression guard + 2 OutdoorCellNode flag assertions).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Builds the synthetic outdoor cell node (OutdoorCellNode.Build) every outdoor frame
from the nearby building-entrance portals (Chebyshev <=1 landblocks), stored in
_outdoorNode. NOT yet rooted — clipRoot/viewerRoot unchanged, so behaviour is
identical this commit. [outdoor-node] probe (ACDREAM_PROBE_FLAP) reports the live
portal count so the next (cutover) step can confirm real building entrances were
found before flipping the render root. App.Tests 214/214, build green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Purely additive: creates the synthetic outdoor cell node that will serve as the
flood-graph root for the unified render pipeline. Each nearby building's exit
portal (OtherCellId==0xFFFF) is reversed into a portal pointing back into the
building, with its polygon transformed to world space and InsideSide flipped so
the outdoor half-space is "inside" the node. WorldTransform=Identity (portals in
world space). Mirrors retail's outdoor landcell that DrawInside(viewer_cell)
roots at (SmartBox::RenderNormalMode, decomp pc:92635). Nothing consumes this
yet — consumer wiring is Task 2.
2 new tests, 212 total passing, 0 regressions.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Checkpoint of the unified retail-faithful indoor render. The two-week HANG/grey is fixed and the
interior seals (live-verified by the user). Commits the session render-rewrite foundation together
with the fixes that made it functional.
- HANG fix: PortalVisibilityBuilder.Build portal flood did not terminate (the faithful ProjectToClip
near-side clip drifts per round, defeating the CellView dedup; the BFS had no bound after U.2a removed
MaxReprocessPerCell). Fix = drift-tolerant snapped/canonical CellView.Add dedup (PortalView.cs) plus
restored MaxReprocessPerCell=16 bounded re-enqueue (PortalVisibilityBuilder.cs). Re-enqueue is kept
(load-bearing for late-slice propagation, Build_ViewGrowthAfterDoneCell_PropagatesNewSlicesToExit);
only its count is capped. CellViewDedupTests added.
- Seal (DrawCells Task 2): RetailPViewRenderer.DrawEnvCellShells draws EVERY visible cell via
IndoorDrawPlan.ShellPass (was gated on the ClipFrameAssembler slot filter, leaving slot-less cells grey).
- Look-in FPS: GameWindow exterior look-in candidates limited to the player landblock +-1 (was all ~81
loaded LBs iterated every outdoor frame). No behaviour change (far cells were >48m, already culled).
Remaining dominant issue = the FLAP at transitions: viewer-cell metastability (render roots at the
camera-eye cell, which oscillates outdoor-indoor as the 3rd-person boom drifts across the doorway,
confirmed in render-sig). SEPARATE fix, NOT the DrawCells port. Full handoff + flap fix plan + tracked
follow-ups (#78 terrain, look-in-from-inside, look-in FPS, L-spotlight):
docs/research/2026-06-07-indoor-render-session-handoff.md.
Baselines: build 0 err; App.Tests 210/210; Core.Tests 1331 pass / 4 fail (pre-existing) / 1 skip.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Pure port of retail DrawCells membership: reverse cell_draw_list, per-slice. Pins the
grey regression — a cell in OrderedVisibleCells is never dropped from the shell pass.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Retail SmartBox::RenderNormalMode (0x453aa0:92665) branches DrawInside vs the
outdoor LScape::draw on is_player_outside (the PLAYER's cell, 0x451e80), then
roots DrawInside at the VIEWER cell. acdream keyed the whole branch off the
camera cell (clipRoot = visibility.CameraCell), so a 3rd-person chase camera
lagging in a doorway AFTER the player stepped outside took the DrawInside path
rooted at the threshold cell, where the exit-portal flood degenerates: grey
world + entities-through-walls. Now ShouldRenderIndoor(playerCellId,
viewerCellResolved) gates the branch on the player; the DrawInside root stays
the viewer cell (handoff invariant preserved).
SCOPE / HONESTY: this REDUCES the player-OUTSIDE doorway grey (visual-confirmed
reduced a lot) but does NOT fix the deeper symptom: when the player is in one
interior cell (cellar 0174) and the camera is in another (room 0171), the flood
roots at the camera cell and does NOT seal the player's cell, so the cellar
floor / interior walls drop to grey. That is the KNOWN R1-completion problem
(2026-06-05 Residual A handoff + 2026-06-02 design doc section 3: a
SHELL-SEALING / wrong-flood-root bug), not this branch.
Tests: Core 1331p / 4f (documented) / 1s, App 187p, build green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Port retail CameraManager::UpdateCamera's convergence snap (0x00456fcd):
once the per-frame lerp step is below 0.0004 m AND the rotation within
0.000199999995, freeze the damped eye at an exact fixed point instead of
Vector3.Lerp's endless sub-mm asymptote. The drift was walking the 3rd-person
eye across the vestibule/room portal plane at rest, flipping the per-frame
viewer-cell resolve 0170<->0171 -> the indoor grey/texture flicker. The
collided-eye firewall (separate publishedEye local) is already present.
Adds ApplyConvergenceSnap static (TDD: 3 unit tests + 1 integration freeze
test) + SnapEpsilon/RotCloseEpsilon. App suite 183 -> 187, all green.
Plan: docs/superpowers/plans/2026-06-05-indoor-viewer-cell-flicker-fix.md
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
When the chase camera roots in a thin doorway cell and the eye stands in an interior portal opening (live capture: vestibule->room portal D=0.16m, proj=0), the 2D projection degenerates and the neighbour was culled (cells=1) -> only the thin cell drew -> bluish void / transparent ceiling. Retail's 3D clip imposes no constraint for a portal the eye is inside, so the neighbour is fully visible. When the clipped region is empty but the eye stands in the opening (EyeInsidePortalOpening: within 0.5m of the portal plane AND point-in-opening), flood the neighbour with the current view. Guarded so an off-screen degenerate portal stays culled (no #95 blowup; over-include is mesh-frustum-culled at draw). Visual-verified: cellar ceiling now solid.
Band-aid for thin-cell-root coverage; likely superseded by the boom-stability + viewer-cell dead-zone + w=0 near-plane clip fix next session (reassess / maybe revert). 2 RED->GREEN tests; cyclic/hub termination guards unchanged.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
ProjectToNdc clipped only the eye half-space (w>MinW, a 2026-06-03 workaround) and left the 4 frustum side planes to the 2D ScreenPolygonClip. When the eye is within a portal's near plane, small-w verts explode under the perspective divide (probe saw NDC (10.2,-67.4)); the 2D clip then collapses to empty -> OutsideView empty -> terrain Skip -> the bluish doorway void. Clip the eye plane + 4 side planes (homogeneous Sutherland-Hodgman) before the divide so NDC is bounded to the screen by construction, matching retail GetClip -> polyClipFinish (clip in clip-space before the divide; pc:432344).
Partial: NOT the full flicker fix. The dominant cause (camera boom drift + viewer-cell flip at boundaries + missing w=0 near-plane clip) is identified and deferred to the next session per the handoff. 2 RED->GREEN tests.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Complete Render Residual A's faithful port: PhysicsCameraCollisionProbe.SweepEye
now mirrors SmartBox::update_viewer (acclient_2013_pseudo_c.txt:92761) end-to-end:
- Start cell (pc:92824-92844): indoor (>=0x100) seats the sweep at the head-PIVOT
via PhysicsEngine.AdjustPosition (the cellar-lip case — feet in the low connector,
head up at floor level); outdoor keeps the player cell.
- Sweep pivot -> sought-eye from the seated start cell (unchanged 0x5c viewer flags).
- Success (pc:92870): set_viewer(curr_pos), viewer_cell = curr_cell.
- Fallback 1 (pc:92878): AdjustPosition(sought_eye).
- Fallback 2 / no-cell (pc:92775, 92886): snap to player, viewer_cell = null. This
also makes cellId==0 faithful (was returning the desired eye; retail snaps to
player_pos) and adds the playerPos arg to ICameraCollisionProbe.SweepEye.
Supporting: ResolveResult.Ok surfaces FindTransitionalPosition's return (retail
find_valid_position != 0, pc:273898) so SweepEye knows when to fall back.
TDD: 11 new tests (FindVisibleChildCell 4, AdjustPosition 3, ResolveResult.Ok 2,
SweepEye orchestration 2). The seating test's RED proved the sweep does NOT auto-
advance feet->room, so the pivot-seated start cell is genuinely decisive. Core
1326 pass / 4 documented-fail / 1 skip; App 179 pass / 0 fail. No regression.
Per the live-capture finding, the visible payoff is the cellar-corner (point 3);
the cottage-room bluish void stays for residual C. Spec:
docs/superpowers/specs/2026-06-05-residual-a-camera-collision-design.md.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The two Core physics primitives retail's SmartBox::update_viewer calls down into,
ported verbatim (TDD, 7 new tests):
- CellTransit.FindVisibleChildCell (CEnvCell::find_visible_child_cell, pc:311397):
return the cell whose cell-BSP point_in_cell contains a world point — start cell
first, then (stab-list mode) the start's VisibleCellIds or (portal mode) its
direct portals. Sibling of FindCellList. Mirrors FindCellList's null-CellBSP skip
(CellTransit.cs:518) so a cell lacking hydrated CellBSP doesn't spuriously claim
every point via PointInsideCellBsp's null-node "inside" default.
- PhysicsEngine.AdjustPosition (CPhysicsObj::AdjustPosition, pc:280009): resolve a
point's cell from a seed. Indoor (>=0x100) → FindVisibleChildCell(stab-list);
outdoor → landcell snap (same grid lookup as ResolveCellId). The seen_outside
sub-fallback is deferred (off the cottage/cellar path; spec §6).
Both are unwired into any production path — they land the machinery update_viewer's
start-cell + fallback 1 need (and that residual C also needs). The App SweepEye
orchestration that calls them lands next.
Decomp-faithful per the live-capture finding: A's V1 sweep already contains the eye
(eyeInRoot=Y 99.75%, never void); this completes A as a verbatim port. Spec:
docs/superpowers/specs/2026-06-05-residual-a-camera-collision-design.md.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The stale-footCenter fix (cc4590f) is visually confirmed: cellar ascent is
smooth, inn door still blocks, generic step-up still climbs. The residual
9/29 (0,-1,0)-sliding-normal records did NOT manifest in live play —
confirming they were buggy-trajectory artifacts.
Remove the temporary investigation scaffolding added for this trace:
- [fc-dispatch] probe in BSPQuery.FindCollisions
- [step-sphere-down] probe in BSPQuery.StepSphereDown
- CellarLipWedgeTests.Diagnostic_TraceRecordByIndex [Theory]
Kept: the fix, the Fix_StaleFootCenter_* regression guards, and the
DocumentsResidualWedge_* documents-the-bug test. Core suite 1317 pass /
4 fail (documented baseline) / 1 skip.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Root cause of the "blocked at the last cellar step" wedge (the primary,
ramp-climb family — 20/29 captured records). The prior session's pinned
"find_walkable is never called during the step-down" was a probe artifact:
a fresh [fc-dispatch]/[step-sphere-down] trace proves Path-3 StepSphereDown
IS reached for both the carried cell and the iterated other-cell.
The real divergence is in Transition.CheckOtherCells. Retail's
check_other_cells (acclient_2013_pseudo_c.txt:272735 → (*cell+0x88)(this))
re-collides the OTHER cells against the LIVE sphere_path.global_sphere — the
position AFTER the primary insert_into_cell ran. The primary collide can MOVE
the sphere: a Path-5 full-hit dispatches step_sphere_up, and a successful
step-up climbs the foot onto the cottage floor yet still returns OK. acdream
instead reused a footCenter snapshot captured BEFORE the primary collide, so
once the lip-riser step-up climbed the foot onto the floor, check_other_cells
still queried 0171 at the pre-climb (sunk ~0.25 m below the floor) position →
the foot spuriously near-missed the very floor it had climbed onto →
neg_step_up → a doomed second step_up vs the floor normal (0,0,1) whose
step_up_slide unwound the climb → validate_transition reverted → 0% advance.
Fix: re-read footCenter = sp.GlobalSphere[0].Origin at the top of
RunCheckOtherCellsAndAdvance (one line). Pre-fix 0/29 wedge records advanced;
post-fix 20/29 climb onto Z≈94.
No regression: full Core suite 1321 pass / 4 fail (the documented baseline:
Apparatus_Grounded_50cmOffCenter, 2× DoorBugTrajectoryReplay LiveCompare_*,
BSPStepUpTests.D4) / 1 skip. The 2 door LiveCompare divergences are
byte-identical with/without the fix (the door's step_up FAILS → sphere
restored → position unchanged → footCenter == live).
Tests: CellarLipWedgeTests.Fix_StaleFootCenter_RampRecordClimbsCottageFloor +
Fix_StaleFootCenter_MajorityOfWedgeRecordsAdvance (new, GREEN).
DocumentsResidualWedge_LiveFloorCp_SlidingNormalKillsPlusY documents the
remaining 9/29 (0,-1,0)-sliding-normal +Y-kill family (slide territory,
deferred to the visual gate).
Apparatus retained (gated on ACDREAM_PROBE_INDOOR_BSP): [fc-dispatch] in
BSPQuery.FindCollisions + [step-sphere-down] in BSPQuery.StepSphereDown +
CellarLipWedgeTests.Diagnostic_TraceRecordByIndex — strip once the residual
is resolved.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
P2 / M1.5 "blocked at the last step" cellar-lip wedge. This session built a faithful
deterministic reproduction and peeled the cause through six evidence-disproven framings
to one bounded question. NO fix landed — the last layers were each disproven by evidence,
and guessing at the load-bearing collision code is the saga's failure mode.
Apparatus:
- CellarLipWedgeTests.cs + Fixtures/cellar-lip/ (3 real cell dumps + wedge-records.jsonl =
29 captured ACDREAM_CAPTURE_RESOLVE wedge calls). Replays the exact calls + body-before
through the lip-cell engine: all 29 reproduce at 0% advance in <200 ms. Tests are
documents-the-bug / diagnostics (GREEN while the wedge exists).
- TEMP probes ([path5-wall]/[fw-enter]/[find-walkable] in BSPQuery; [neg-poly]/[stepsphereup]/
[stepdown-decide]/CheckOtherCells cn/sn/negHit in TransitionTypes), gated on
ACDREAM_PROBE_INDOOR_BSP, marked STRIP. TransitionTypes neg-poly shortcut has a reverted-fix
comment (slide attempt didn't clear the wedge).
- tools/cdb/retail-*-trace.cdb (retail cdb traces).
Findings (handoff: docs/research/2026-06-04-p2-cellar-lip-flatfloor-cp-handoff.md, see the
"NEXT-SESSION KICKOFF" at top):
- Flat-floor contact plane is retail-faithful (v1 trace, full-file correlation). NOT the bug.
- PosHitsSphere cull sign is retail-faithful (cdb -z verified; the Binary Ninja `test ah,N; jp`
parity-jump reads inverted — caught + reverted a wrong fix from that mis-read).
- Sphere radius correct (0.48 player / 0.30 camera probe).
- Retail connector cell 0xA9B40175 never blocks (CEnvCell::find_collisions trace: 0 Collided/Slid).
- PINNED: during the step-up's step-down, BSPQuery.FindWalkableInternal is never called for cell
0171, so the cottage floor (poly 0x0023, Z=94) is never tested as walkable -> no contact plane
-> step-up fails -> StepUpSlide=Collided -> wedge.
Next: trace FindEnvCollisions -> FindCollisions path dispatch for 0171 during StepDown=true (why
StepSphereDown/find_walkable is skipped), port retail, validate via CellarLipWedgeTests, regress
DoorBugTrajectoryReplayTests + visual gate.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replace the A6.P4 Collided shortcut in transitional_insert's neg_step_up==0
branch with the faithful CSphere::slide_sphere. Retail
(acclient_2013_pseudo_c.txt:273350-273351, CTransition::transitional_insert):
neg_step_up==0 (HEAD-sphere near-miss) -> slide_sphere -> continue the insert
loop; neg_step_up==1 (foot) -> step_up_slide. The acdream foot branch already
did that; only the head branch took the shortcut (SetCollisionNormal + return
Collided = dead hard-stop). The slide itself is the existing
SlideSphereInternal (Sphere.SlideSphere port): it strips the into-wall
component and keeps the tangential crease (collisionNormal x contactPlane.N).
Surfaced by the B1 near-miss-gate fix (abbd761): once the grounded mover climbs
onto the cottage floor, its head sphere brushes the cellar stairwell walls and
the old hard-stop wedged it (2026-06-04 live capture: 274 (0,-1,0) + 78
(1,0,0) hits, out==current, dead oscillation). Post-fix capture shows 96
hit-and-advanced frames (the body slides along the walls).
Visual-verified 2026-06-04: closed cottage door still BLOCKS (no walkthrough --
drifts sideways along it, retail-faithful); cellar ascent now works (was always
stuck). An intermittent corner-wedge (slide into the -Y/+X wall corner) remains
-- separate finer issue, under investigation.
Core 1310 pass / 4 fail (pre-existing: 3 door documents-the-bug + D4 airborne).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The 2026-06-03 handoff localized the failing Core tests to the BSP Path 5
step-up CLIMB (find_walkable/step_sphere_down). An ITestOutputHelper capture
of B1 disproved that: the climb code is correct (matches ACE
Polygon.adjust_sphere_to_plane / BSPTree.step_sphere_down exactly). The real
bug is the A6.P4 near-miss dispatch in FindCollisions' Path 5 (Contact
branch), which diverged from retail three ways:
1. Recorded a near-miss NegPolyHit UNCONDITIONALLY. Retail gates both
set_neg_poly_hit calls behind `if (num_sphere > 1)`
(acclient_2013_pseudo_c.txt:323852).
2. Checked the foot sphere's near-miss before the head's. Retail checks
the head (sphere1) first.
3. Mapped foot->neg_step_up=false / head->true. Retail maps head(index 0)
->false (slide), foot(index 1)->true (step-up), per
SPHEREPATH::set_neg_poly_hit (:323279, neg_step_up = arg2).
For B1's single foot sphere, the spurious near-miss -> outer loop
`!NegStepUp -> SetCollisionNormal + Collided` -> revert: the grounded mover
wedged at x=0.1 and never advanced to the wall to step up. With the verbatim
gate, a single-sphere near-miss records nothing, the sphere advances,
full-hits the wall, and step_sphere_up climbs the 0.25 m step (verified via
probe capture: foot ends at (0.6, 0, 0.25)).
The Holtburg cottage door still blocks faithfully (door slab (0,-1,0) normal,
stops in front of the door) when the scenario has a real floor — confirmed
this change does not regress the door.
The two BSPQueryTests Path5 near-miss tests used a single sphere (the very
non-retail assumption that caused this wedge); converted to the production
2-sphere shape where the head sphere records the near-miss, matching retail.
Core 1312 pass / 4 fail (the 4 pre-existing: 3 door documents-the-bug + D4
airborne, none regressed here); App 177 green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The cottage doorway 'void' (dark cell + floating entities while the chase camera looks
through the opening): PortalProjection.ProjectToNdc clipped portals on w+z>=0 — the GL
[-1,1] near-plane test — but acdream's camera builds its projection with D3D-convention
Matrix4x4.CreatePerspectiveFieldOfView and a 1.0 m near plane (RetailChaseCamera). Against
that matrix w+z>=0 discards everything within ~0.5 m of the eye, so when the camera orbits
to ~0.1 m from a doorway portal the near edge is clipped, the far edge projects off-screen
([flap] showed p->0171 D=0.10 proj=4 clip=0 ndc Y=-3.5..-6.6), the room behind is culled
(vis=1) and only the tiny vestibule shell draws -> dark void. Rotating away moved the eye
off the portal -> vis=5 -> room rendered.
Fix: clip against the EYE (w > MinW, MinW=0.05 m), near-INDEPENDENT — a portal you're
standing in still projects (covers the screen) so the cell behind stays visible. We only
use the projected x/y for the visibility clip region, so keeping vertices in front of the
near plane is correct. Matches retail PView::GetClip near-clipping the portal before project.
RED->GREEN regression test (doorway 0.1 m from a near=1.0 eye); 177 App tests green; the
existing straddling ±50 bound still holds.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Phase W single-viewpoint V1 (un-split). The render mode decision, indoor root, and portal
side-test now key on the collided-camera viewer cell + eye (RetailChaseCamera.ViewerCellId +
camPos) — retail RenderNormalMode -> DrawInside(viewer_cell) @92675; InitCell side-test vs
viewer.viewpoint @432991. Lighting / seen_outside / playerInsideCell stay on the PLAYER cell
(CurrCell), retail CellManager::ChangePosition @4559B0. The old per-render player-root +
eye-projection split (U.4c) is removed; the flap is avoided by the robust graph-tracked viewer
cell (no AABB, no grace). [flap-cam] probe extended with viewerCell vs playerCell. CurrCell
stays player-only (blue-hole fix intact). App 176 green; Core 1295/5 baseline (no new fails).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Update() now always sets ViewerCellId: the camera-collision sweep's swept cell when collision
is on (retail viewer_cell = sphere_path.curr_cell), else the passed player cell. This is the
robust, per-frame, graph-tracked 'which cell is the camera in?' answer that V1 roots the render
on — no AABB, no grace frames (the U.4c flap source). 176 App tests green (2 new).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The camera spring-arm sweep already resolves the collided eye's cell (ResolveResult.CellId
= sp.CurCellId = retail viewer_cell = sphere_path.curr_cell, update_viewer pc:92871).
Return it from SweepEye so the render can root on the viewer cell (Phase W single-viewpoint
V1, Task 1). Pure plumbing — behavior unchanged; callers extract .Eye. 174 App tests green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
THE doorway flap root cause, found via [flap-cam]/[shell]/[cell-transit] (2026-06-03):
the player spawned + stood still in the room (cell 0171, NO [cell-transit] after teleport),
yet the render rooted at the vestibule (0170) for all 77,951 frames — drawing only 0170's
~8-triangle shell, the rest = GL clear color = the bluish void.
CellGraph.CurrCell IS "the player's cell" (the render root), but it was written by
SetCurrAndReturn inside the PER-ENTITY ResolveWithTransition + ResolveCellId — so EVERY NPC
wrote it. A Holtburg NPC (0x000F4240) jump-looping near the doorway clobbered the player's
render root every tick. Standing still (player makes no resolve calls) the NPC's write wins
→ stuck blue void; moving, player/NPC writes fight → the flap. This is why the membership
pick fix (correct, kept) didn't change the visual — the render root was clobbered regardless.
Fix: CurrCell is now written ONLY by the player. New PhysicsEngine.UpdatePlayerCurrCell is
called from PlayerMovementController.UpdateCellId — the single player-only chokepoint for
CellId (teleport / server snap @ SetPosition + per-frame resolver). Removed the CurrCell
write from SetCurrAndReturn (inlined the 2 resolve call sites to sp.CurCellId) and the 4
ResolveCellId sites. NPCs no longer touch the render root. Teleport→UpdateCellId also covers
spawn/standing-still (CurrCell = the player's spawn cell immediately).
CellGraphMembershipTests rewritten to the new contract (3 tests): UpdatePlayerCurrCell writes
the render root; ResolveCellId does NOT (the blue-hole guard); stale-beats-null preserved.
Full Core suite: 1295 pass / 5 fail = the documented §10 baseline, zero new breakage.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The ordered pick alone did not stop the cottage doorway flap: the live [cell-transit]
log showed the cell faithfully following a position that cleanly oscillated between two
values at constant Z — the signature of a bistable membership<->collision feedback loop
(user's §4.4 #3, the forked collision).
Root cause: FindEnvCollisions RE-PICKED the cell from the TARGET position (old line 1958)
BEFORE running the primary collision, so the collision geometry (which cell BSP / terrain)
swapped the instant the pick flipped -> position shifts -> pick flips back.
Retail does NOT do this. CEnvCell::find_env_collisions (acclient_2013_pseudo_c.txt:309573)
collides against the cell it was called ON (sphere_path.check_cell, the carried seed) and
picks the NEW containing cell AFTER, in CTransition::check_other_cells (272717->272761:
check_cell = var_4c). Collide-then-pick.
This commit ports that order:
- remove the pre-pick (production); collide against the carried cell (indoor BSP block /
terrain block unchanged);
- new shared RunCheckOtherCellsAndAdvance() runs the ordered FindCellSet pick +
multi-valued CheckOtherCells + the carried-cell advance AFTER the primary collision, for
BOTH indoor and outdoor seeds;
- the outdoor-seed post-step replaces the removed pre-pick's outdoor->indoor re-entry
promotion (CheckBuildingTransit interior cell + its wall collision on the entry frame).
Cache-null unit-test fallback (ResolveCellId) kept. Full Core suite: 1293 pass / 5 fail =
the documented §10 baseline exactly (2 step-up + 3 door-collision), zero new breakage.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Port CObjCell::find_cell_list (acclient_2013_pseudo_c.txt:308742) faithfully:
- build candidates into an ordered CellArray with the CURRENT cell at index 0
(add_cell @308766);
- EXPAND via a single forward walk over the growing array, mirroring retail's
for(i=0;i<num_cells;i++) cells[i].find_transit_cells loop (308775-308785),
replacing the order-losing Queue/visited BFS;
- PICK in array order with interior-wins-break (308788-308825): current cell at
index 0 wins a boundary straddle, so membership no longer ping-pongs.
Deletes the 5ca2f44 current-first pre-check (the ordered array subsumes it for every
seed). Keeps its guard test (TwoOverlappingCells_CurrentCellWinsTheStraddle) + adds
two conformance tests (current-cell-first ordering; interior-wins over outdoor
fallback). Membership net: 45 pass. Decomp finding: retail stability is emergent from
the ordered pick + carried seed, not a separate portal-crossing detector — see
docs/research/2026-06-03-cell-membership-ordered-cellarray-pseudocode.md.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Non-behavioral: lets BuildCellSetAndPickContaining pass an ordered CellArray (next
commit) while existing HashSet-passing test callers compile unchanged. HashSet<uint>
and CellArray both implement ICollection<uint>. Core builds; 9 helper tests green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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>
The flap R1 exposed is a cell-membership ping-pong: the find_cell_list containing-
cell pick (CellTransit.BuildCellSetAndPickContaining) iterated an UNORDERED HashSet
and returned the first interior cell whose BSP contains the sphere center, with no
preference for the current cell. Retail CObjCell::find_cell_list adds the current
cell at index 0 (add_cell, pc:308766) and iterates current-first with interior-wins-
break (pc:308791-308819) — you STAY in your current cell until the center genuinely
leaves it. acdream's HashSet dropped that ordering; once the candidate set churns at
a boundary the enumeration can surface a neighbour before the current cell → the
ping-pong. Restore the explicit, deterministic current-cell-first test (retail's
index-0 hysteresis). + a two-direction regression guard (current cell wins the
straddle).
Diagnosed from the existing [cell-transit] walk log (no new probing): room flips are
the pick non-determinism; stairs flips additionally show the foot Z oscillating
~0.2m/tick (a separate stairs-physics residual, #98 family, to verify after this).
The 2 DoorBugTrajectoryReplay failures are PRE-EXISTING (verified: they fail without
this change too) — 2 of the handoff's '3 door-collision apparatus / A6.P5'.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
EntityPassesVisibleCellGate no longer returns true unconditionally for outdoor
scenery under a cell filter (was the headline #78 bleed). Outdoor scenery now
draws only via the unfiltered bucket (visibleCellIds: null) + ResolveEntitySlot's
OutsideView routing. The outdoor-root global Draw passes visibleCellIds: null
(no portal-cell scoping outdoors; retires VisibleCellIds as a render gate — peering
into buildings is R5). Updated the EntityClipTests case that pinned the old bypass
(Included -> Excluded). 174/174 App tests green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
GameWindow.OnRender: when clipRoot != null, run only InteriorRenderer.DrawInside
(per-cell shells + per-cell objects + live-dynamics); the global entity pass +
global shell pass are no longer issued indoors. Outdoor scenery drawn clipped to
the doorway (after terrain, before the Z-clear). Outdoor root path unchanged.
pvFrame hoisted so the splice reads OrderedVisibleCells; per-frame 3-bucket
partition built on the indoor root. Retail RenderNormalMode @ 0x453aa0.
InteriorRenderer amended with a DrawableCells membership filter (an IsNothingVisible
cell can be in OrderedVisibleCells but absent from CellIdToSlot — iterate for ORDER,
filter for membership; matches the old envCellShellFilter set exactly).
Build green, 174/174 App tests pass.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Per-cell flood: closest-first over OrderedVisibleCells, per cell draws the closed
shell (EnvCellRenderer.Render(pass,{cellId})) + that cell's objects, then live-
dynamics unclipped, then transparent shells. Reuses the existing dispatcher Draw
per cell (safe to call N x/frame; only diagnostic GPU-timing miscounts). Caller
owns the landscape-through-door + Z-clear. Not yet wired (Task 3).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Pure helper splitting a frame's entities into live-dynamic / per-cell statics /
outdoor scenery, by the same precedence as WbDrawDispatcher.ResolveEntitySlot
(serverGuid first — live entities have no ParentCellId). Feeds the per-cell
DrawInside loop. 3 unit tests, GL-free.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Opus adversarial review caught a real gap: the sky/weather MESH bled full-screen indoors in TerrainClipMode.Scissor (a multi-exit interior, or an OutsideView with >8 edges). The assembler only sets the binding=2 clip-plane UBO in Planes mode; in Scissor mode it leaves count==0, so sky.vert's gl_ClipDistance writes all +1 (no clip) and the mesh draws — which had NO scissor wrapper, only the no-op planes — covered the whole screen. The terrain and particle passes were already scissored; the sky/weather mesh was the one unguarded path.
Fix: scissor the WHOLE sky pre-scene + weather post-scene blocks (mesh + particles) to the OutsideView AABB when indoors. In Planes mode the scissor is a harmless over-include (the per-vertex clip planes are tighter and do the exact doorway clip); in Scissor mode it is the sole confinement, mirroring the terrain Scissor path; outdoors it is skipped (full-screen, bit-identical). Also hoisted the scissor-disable out of the particle null-check (cleaner, leak-free on the no-particle path) and corrected a stale 'weather does not write gl_ClipDistance' comment at the world-bracket close.
The single-convex-doorway case (Holtburg cottage) was already correct (Planes mode); this seals the multi-opening case. Build 0/0; App tests 171/171.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
T4.4: annotate EnvCellRenderer.RegisterCell to document that ceilings are present by
construction. PrepareCellStructMeshData iterates ALL CellStruct.Polygons (floor + walls +
ceiling) with no surface filter; retail PView::DrawCells draws the same closed-box
drawing_bsp. No ceiling filtering confirmed.
T4.2: annotate TerrainModernRenderer.Draw to document that terrain projects from the
passed-in ICamera (uView + uProjection derive from the same camera as all other
renderers). No separate landscape viewpoint exists that could desync from the eye.
T4.5, T5.1, T5.2: pure verification — no code changes (see report).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The sky + weather (rain cylinder) are retail's LScape — 'the outside seen through the exit portal.' Retail PView::DrawCells (pseudo_c:432709) draws LScape clipped to the OutsideView when outside_view.view_count>0, then does a conditional Z-buffer-ONLY clear (432731) before the indoor cells. acdream now does the same:
- sky.vert writes gl_ClipDistance against the SAME binding=2 TerrainClip UBO the terrain reads. The OutsideView planes are screen-space (NDC) half-spaces encoded as clip-space planes (nx,ny,0,dw); the test dot(plane,gl_Position)>=0 reduces after perspective divide to nx*ndcX+ny*ndcY+dw>=0 — projection-INDEPENDENT — so the same plane set clips the sky EXACTLY despite its separate dome projection. count==0 (outdoor) → all distances +1 → full-screen, bit-identical. Lighting/fog math untouched.
- GameWindow: relocated the sky pre-scene + weather post-scene draws to their retail LScape positions, each in a local 8-plane clip bracket so sky.vert confines them to the doorway indoors / full-screen outdoors. Added the conditional doorway depth-ONLY Z-clear (no color → no blue hole), scissored to the OutsideView AABB. drawSkyThisFrame = seen_outside policy AND (outdoor OR exit-portal-in-view) — a sealed interior with no exit portal in view draws no sky (kills the full-screen-sky interim regression). Sky pre/post particle passes (particle.vert has no gl_ClipDistance) scissored to the doorway bbox.
- ClipFrameAssembly gains HasOutsideView + OutsideViewNdcAabb (the doorway NDC AABB, computed for BOTH Planes and Scissor terrain modes — unlike TerrainScissorNdcAabb which is Scissor-only).
- The pre-login goto SkipWorldGeometry moved BELOW the sky draw so the live sky still renders during the EnterWorld handshake (clipAssembly is null/no-clip pre-login → full-screen).
Build green; App tests 160/160. Stage 4 tests + verify-annotations follow.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Port CEnvCell::find_visible_child_cell @ 0x0052dc50 (pseudo_c:311397): walk
rootId's StabList + the root itself, return the first EnvCell whose
PointInCell is true for worldPoint. Used to resolve the camera cell in
3rd-person from the physics cell graph rather than a fresh AABB reclassification.
Root is always the player cell (preserves U.4c flap fix). Returns null when
no stab-list cell or root itself contains the query point.
Confirmed (T3.3 Step 1): no production call site uses FindCameraCell for the
camera projection — the only AABB camera resolver is now deleted (T3.1).
FindVisibleChildCell is wired implicitly via Stage 4 (camera-outside-door scenario);
no GameWindow call site needed in Stage 3.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
ComputeVisibilityFromRoot(null, …) now returns null (outdoor root) instead of
calling FindCameraCell(fallbackPos). Retail CellManager::ChangePosition
(0x004559B0) reads the transition-owned curr_cell — it does NOT re-derive from
a static position. W2a guarantees CurrCell is set from the first tick, so the
AABB fallback is dead. Deleted: FindCameraCell (389–446), _lastCameraCell,
_cellSwitchGraceFrames, CellSwitchGraceFrameCount. GetVisibleCells retains a
brute-force AABB scan for test-compat; ComputeVisibility stays for the same
reason. Updated 3 null-root tests in CellVisibilityFromRootTests to assert the
new null-returns-null behavior.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
xUnit's default parallel execution let diagnostic-harness tests (CellarUp,
DoorBug, DoorCollisionApparatus) mutate PhysicsResolveCapture.CapturePath
and PhysicsDiagnostics probe flags concurrently with victim tests
(MotionInterpreter, PositionManager, PlayerMovementController,
DispatcherToMovement, BSPStepUp), producing a flaky 14-26 failure range.
Fixes:
- Add PhysicsResolveCapture.ResetForTest() + PhysicsDiagnostics.ResetForTest()
as documented test-only reset APIs (never called from production paths).
- Add IDisposable to CellarUpTrajectoryReplayTests with ctor/Dispose calling
both ResetForTest() — prevents CapturePath from leaking between the Capture_*
tests in the same class (the immediate root cause of Capture_SkipsNonPlayerCalls
finding an unexpected file).
- Add xunit.runner.json (maxParallelThreads=1, parallelizeTestCollections=false)
to AcDream.Core.Tests — eliminates parallelism-induced probe-flag leaks across
all test classes without requiring [Collection] boilerplate on every offender.
After: two consecutive runs produce the identical 12-failure set.
Confirmed: LiveCompare_FirstCap_FixClosesCottageFloorCap passes.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Change A (TransitionTypes.FindEnvCollisions:~1947): replace the unconditional
static ResolveCellId re-derive with the SWEPT find_cell_list pick via
CellTransit.FindCellSet. When DataCache is available (always in production),
the swept pick runs and resolves the containing cell from the portal-graph
candidate set. When DataCache is null (test engines without a cell registry),
the old ResolveCellId fallback is preserved to keep PhysicsEngineTests green.
Change B (CellTransit.BuildCellSetAndPickContaining): replace the containment
loop that silently skipped all outdoor candidates (CellBSP=null) with the
retail CObjCell::find_cell_list interior-wins pick (pseudo_c:308788-308819):
interior EnvCells win first; if no interior cell contains the center, fall
to the outdoor XY-grid column (CLandCell::point_in_cell equivalent). This is
the missing half of find_cell_list that caused the 0xA9B40170↔0xA9B40031
doorway cell-strobe — the swept pick previously always returned currentCellId
for outdoor candidates, letting the static re-derive at :1947 strobe on every
tick from a different result.
DoorwayMembershipReplayTests: two facts, loads doorway-capture.jsonl (364K records,
strobing live run), filters to Y∈[15.5,17.5] seam zone (57 records), verifies
FindCellSet produces exactly 1 transition (enter indoor → stay outdoors) with
zero A→B→A ping-pong across the full window. Second test verifies outdoor-seed
records round-trip correctly via the XY-grid formula. Both pass.
LiveCompare_FirstCap_FixClosesCottageFloorCap: still passes (issue #98 gate intact).
Full Core suite: 15 failures (within documented flaky baseline of 14–19;
all 15 are pre-existing static-leak/document-the-bug tests, zero new regressions
in cell/transit/BSP/physics classes).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replace ResolveCellId(sp.GlobalSphere[0].Origin, ...) with SetCurrAndReturn(sp.CurCellId)
in both the OK and partial paths of ResolveWithTransition. Retail's
SetPositionInternal reads sphere_path.curr_cell which ValidateTransition
advances only on accepted moves and reverts on blocks — so a push-back or
standing-still tick cannot flip the cell. The static re-derive from the
resting origin strobes between outdoor 0031 and indoor 0170 at doorway
boundaries because the origin lands just outside the indoor BSP volume
after push-back; the swept cell doesn't.
SetCurrAndReturn is kept in both paths so the W2a CellGraph.CurrCell write
that the render root consumes still fires. ResolveCellId is NOT deleted —
it still has one caller at TransitionTypes.cs:1947 (AddAllOutsideCells).
partialCellId is kept as the degenerate fallback when sp.CurCellId==0
(teleport / physics reset before any transition has run).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add ProbeSweptEnabled (ACDREAM_PROBE_SWEPT=1) to PhysicsDiagnostics mirroring
ProbeCellEnabled. Emits one [cell-swept] line per ResolveWithTransition call —
sp.CurCellId and sp.CheckCellId (the transition's swept cells) alongside the
incoming cellId so a doorway capture shows whether the swept cell is stable
where ResolveCellId strobes. No ResolveCellId call in the probe — avoids the
CellGraph.CurrCell side effect. No behavior change.
TDD: ProbeSweptEnabled_DefaultsToFalse RED→GREEN in PhysicsDiagnosticsTests.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>