From 56d2b5e4a1f1f73369ba0a8abc1b5206b40c80d8 Mon Sep 17 00:00:00 2001 From: Erik Date: Wed, 20 May 2026 14:42:47 +0200 Subject: [PATCH] docs(physics): handoff for 2026-05-21 collision-fix session MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Captures everything that shipped in the session — A1, A1.5, A1.6, A1.7 plus the walk-miss probe spike — and what's still open: - A4 (multi-cell BSP iteration) — the next big architectural fix, closes the "walls walk-through-able in vestibule cells" gap - A2 (PHSP inversion) — small fix, but only meaningful paired with A3 - A3 (synthesis removal) — needs A4 in place first to avoid reverting back to Bug A's free-fall regression - Lighting bugs (indoor lighting + spotlight projection) — M7 polish, separate session Includes per-fix commit SHAs, code anchors, retail decomp anchors, probe + launch reference, anti-patterns, and a fresh-session pickup prompt for boxing into Claude Code. Co-Authored-By: Claude Opus 4.7 (1M context) --- ...6-05-21-collision-fixes-shipped-handoff.md | 390 ++++++++++++++++++ 1 file changed, 390 insertions(+) create mode 100644 docs/research/2026-05-21-collision-fixes-shipped-handoff.md diff --git a/docs/research/2026-05-21-collision-fixes-shipped-handoff.md b/docs/research/2026-05-21-collision-fixes-shipped-handoff.md new file mode 100644 index 0000000..95ba5cb --- /dev/null +++ b/docs/research/2026-05-21-collision-fixes-shipped-handoff.md @@ -0,0 +1,390 @@ +# Collision fixes — session 2026-05-21 shipped handoff + +**Branch:** `claude/lucid-goldberg-1ba520` +**Worktree:** `C:\Users\erikn\source\repos\acdream\.claude\worktrees\lucid-goldberg-1ba520` +**Commits ahead of main:** 8 (probe spike + 4 fixes + docs) + +## TL;DR + +User reported the world feeling buggy — collision in thin air inside +and outside buildings, walls walk-through-able in spots. A two-step +investigation surfaced a foundation-level math bug (`PolygonHitsSpherePrecise` +inverted vs retail) and four discrete registration / cell-tracking +bugs. **Four surgical fixes landed this session** (A1, A1.5, A1.6, +A1.7) plus a `[walk-miss]` / `[floor-polys]` diagnostic probe set that +quantified the bug rates. **What's left is one architectural change +(A4: multi-cell BSP iteration) and three smaller code-correctness +items.** Visual verification at the end of each phase confirmed +forward progress; remaining wall-walkthroughs in vestibule cells are +the A4 gap. + +## What shipped this session + +### Probe spike (3 commits) + +| SHA | What | Why | +|---|---|---| +| `27c7284` | `ProbeWalkMissEnabled` flag + roundtrip test | Diagnostic gate for ISSUES #83 H-disambiguation | +| `31da57c` | `WalkMissDiagnostic` aggregator + 2 logic tests | Pure-function aggregator over `CellPhysics.Resolved` | +| `a2e7a87` | `[walk-miss]` + `[floor-polys]` emission sites | Wire flag + aggregator into `Transition.FindEnvCollisions` MISS branch + `PhysicsDataCache.CacheCellStruct` | +| `bb1e919` | Spec + plan + findings docs | The doc artifacts for the spike | + +The walk-miss probe produced the **smoking-gun analysis** in +[`docs/research/2026-05-21-walk-miss-capture-findings.md`](2026-05-21-walk-miss-capture-findings.md): +0.38 % synthesis HIT rate, with a 2 cm boundary between HIT (`dz≈0.46 m`) +and MISS (`dz≈0.48 m`) at sphere radius 0.480 m. This proved +**`PolygonHitsSpherePrecise` is inverted vs retail's +`polygon_hits_sphere_slow_but_sure`** (BSPQuery.cs:117 vs +acclient_2013_pseudo_c.txt:322509-322517). That's Phase A2, still +pending. + +### Collision fixes (4 commits) + +| Phase | SHA | Fix | +|---|---|---| +| **A1** | `5f2b545` | **Skip mesh-AABB-fallback cylinder for landblock stabs.** Stabs (`entity.Id 0xC0XXYY00+n`) had their per-part BSP shadow correctly registered AND a redundant 1.5 m-clamped invisible cylinder at the mesh origin. The cylinder was the "thin air" collision inside cottages. Gate: `_isLandblockStab = (entity.Id & 0xFF000000u) == 0xC0000000u`. | +| **A1.5** | `4d3bf6f` | **Scope interior cell shadows to ParentCellId.** `ShadowObjectRegistry.Register` assigned every entity to outdoor landcells based on XY. Interior statics (fireplace, furniture in cell `0xA9B40121`) got stamped into the outdoor landcell whose XY they overlapped (e.g., `0xA9B40029`), firing collisions for players walking OUTSIDE the building. New optional `cellScope` parameter, passed `entity.ParentCellId ?? 0u` from all 5 entity-loop call sites. | +| **A1.6** | `700abad` | **Skip Setup CylSphere/Sphere shadows for landblock stabs.** A1 only gated the mesh-AABB-fallback path. Setup-derived registrations (lines 5910-6005 in GameWindow) still fired for stabs whose source is a Setup with CylSpheres. Same `_isLandblockStab` gate, extended to the outer `if (setup is not null)` block. | +| **A1.7** | `4679134` | **Fall through to outdoor cell when indoor BSP doesn't contain player.** `CellTransit.FindCellList` returns `currentCellId` when no candidate cell's `CellBSP` contains the sphere — but this also fired when the player walked OUTSIDE the entire portal-connected indoor graph. The player's CellId was stuck on an old indoor cell whose BSP was geometrically far away; every indoor-bsp query returned OK at the BSP root; no walls blocked. Fix: after `FindCellList`, verify with `PointInsideCellBsp`; if not inside, fall through to the existing outdoor resolution branch. | + +### Visual verification at each phase + +Each fix was visually verified by walking the same buildings before/after: +- **A1**: "thin air" inside cottage GONE. +- **A1.5**: "thin air" outside buildings → 71/97 interior-static-leak hits down to 0. +- **A1.6**: Setup-CylSphere bleed around buildings cleared. +- **A1.7**: cell-id correctly transitions between indoor doorway cell and adjacent outdoor cell on building exit. + +## What's still broken + +Per end-of-session user testing: + +1. **Walls walk-through-able in "vestibule" cells.** Some interior cells (e.g., the Holtburg cell `0xA9B40164`) have very few physics polygons — only 4 polys, BSP bounding sphere of 2 m radius. When the player walks past the doorway, they're geometrically inside a *neighboring* cell's actual walls — but the collision check only queries the cell the player's center is "in." That cell (the vestibule) has no walls there. The neighboring cell's walls (e.g., `0xA9B40157` with 23 polys, 38 % hit rate when the player IS there) are never queried. +2. **Stairs walk-through.** Likely the same multi-cell iteration gap — stairs span cell boundaries. +3. **Lighting indoors broken.** Separate rendering concern; M7 polish. +4. **Items projecting spotlight on walls.** Per-entity light direction bug; M7 polish. +5. **PHSP inversion (A2).** Still pending. The `[walk-miss]` data proved this bug exists but fixing it alone doesn't fix walkable synthesis at the tangent boundary — needs to pair with synthesis removal (A3). +6. **Synthesis architecturally wrong (A3).** Retail's grounded path never re-synthesizes `ContactPlane`; it retains via Mechanisms A/B/C. Our `TryFindIndoorWalkablePlane` runs every frame and is the wrong shape. Removing it is Bug A from the 2026-05-20 session — was tried + reverted because retention had its own gaps. A1.7 closed one of those gaps; A2 + A4 close the others. + +## The architectural picture (plain-English) + +acdream's world is divided into invisible chunks called **cells**. +There are two flavors: + +- **Outdoor cells**: the world is gridded into 24 m × 24 m squares. Each + landblock (the 192 m × 192 m unit of streaming) has 64 such cells in + an 8 × 8 grid. They get cell IDs like `0xA9B40029`. +- **Indoor cells**: each room (or section of room) inside a building + gets its own cell. They're not grid-aligned — they follow the + building's interior partitioning. Cell IDs have the high bit of the + low-16 set, e.g. `0xA9B40157`. + +Each cell carries: +- A **CellBSP** — defines the volume the cell occupies in space (used + for "is this point inside this cell?" lookups during cell-id resolution). +- A **PhysicsBSP** — the collision geometry (walls, floors, stairs) the + player can hit. +- **Portals** — connections to adjacent indoor cells (think doorways). +- **Static objects** — furniture, decoration meshes hydrated as entities. + +The collision system asks two things per frame: +1. **What cell is the player in?** Driven by `PhysicsEngine.ResolveCellId` + → `CellTransit.FindCellList`. Walks the portal graph from the + current cell, picks the cell whose `CellBSP` contains the sphere + center. With **A1.7**, when no indoor cell claims the player, falls + through to outdoor landcell resolution. +2. **Does the player hit anything?** Drives `Transition.FindEnvCollisions`. + Queries the **one cell** the player is "in" — its `PhysicsBSP` for + walls/floor and its shadow-registered statics for furniture. + +**The architectural gap** is step 2 only queries one cell. Retail +queries the **cell_array** — the sphere center's cell plus every +other cell the sphere geometrically overlaps. So if you're in a +vestibule cell with no real walls but your shoulder pokes into the +next room's wall, retail's collision sees the wall. acdream doesn't. + +## Phase A4 — multi-cell iteration (the next big fix) + +This is the gap. Implementation sketch: + +### What to port from retail + +`CTransition::check_other_cells` at `acclient_2013_pseudo_c.txt:272717-272798`. +After the primary cell's `find_collisions` runs, it iterates every +other cell in `this->cell_array` (built from `CObjCell::find_cell_list` +which fills via interior portals + `add_all_outside_cells` for outdoor +neighbors). For each cell: +- Calls the cell's vtable `find_collisions`. +- On Slid (4): clears `contact_plane_valid`, returns. +- On Collided (2) or Adjusted (3): returns immediately. +- On OK: continues to the next cell. + +If the sphere is geometrically outside the original cell, the +fallback (line 272761-272797) sets `check_cell = var_4c` (the cell +containing the final position) and adjusts `check_pos.objcell_id`. + +### What we already have + +Phase 2 portal cell-tracking is shipped (commits `1969c55` → `eb0f772`, +2026-05-19). It gives us: +- `CellTransit.FindCellList` (sphere variant) — top-level driver. +- `CellTransit.FindTransitCellsSphere` — interior portal neighbour expansion. +- `CellTransit.AddAllOutsideCells` — outdoor landcell neighbour expansion. +- `CellPhysics.VisibleCellIds` — pre-computed visible-cell set per cell. + +These currently feed **cell-id resolution** (step 1 above). They are +NOT yet used to drive **collision iteration** (step 2). A4's job is to +wire them into `Transition.FindEnvCollisions`. + +### Implementation outline for A4 + +1. **In `Transition.FindEnvCollisions`** (`src/AcDream.Core/Physics/TransitionTypes.cs:1407-1559`): + - Currently: queries one cell (`engine.DataCache.GetCellStruct(sp.CheckCellId)`) + and runs `BSPQuery.FindCollisions` against its BSP. + - Change to: build the cell_array from the current cell using + `CellTransit.FindCellList` (or a new variant that returns the + full set), then iterate each cell and run BSP collision against + each. Combine results. +2. **Combine semantics** match retail's `check_other_cells`: + - Any cell returning `Collided` (2) or `Adjusted` (3) → return that + immediately (halt iteration). + - Any cell returning `Slid` (4) → record but continue (in case + another cell collides harder). After all cells: return Slid. + - All cells OK → return OK. +3. **Outdoor case**: if the resolved cell is outdoor, iterate adjacent + outdoor landcells via `AddAllOutsideCells` and any indoor cells + accessible via building portals (`CheckBuildingTransit`). Both + already exist as helpers. +4. **Shadow objects (the L.2d `[resolve-bldg]` path)** likely also need + multi-cell awareness — `FindObjCollisions` only checks shadows + keyed to the player's current cell. After A1.5, interior shadows + are scoped to their `ParentCellId`, so multi-cell iteration + automatically picks them up too. +5. **Testing strategy**: + - Unit tests: synthetic two-cell fixture where wall lives in cell B + and player is in cell A's vestibule. Assert collision fires. + - Live capture: walk the Holtburg inn vestibule (`0xA9B40164`) and + verify walls in `0xA9B40157` now block. +6. **Performance**: each cell query is ~50 µs. Multi-cell iteration + visits ~3-7 cells in worst case. ~200-350 µs extra per resolve. + At 30 Hz that's ~10 ms/sec. Acceptable. + +### Risks + +- **R1**: shadow objects in cells visible from multiple positions may + get tested multiple times in one frame. Need dedup via the existing + `_entityToCells` map. +- **R2**: cells in `cell_array` may have stale `CellPhysics` (loaded + for rendering but not for physics). Guard with `cellPhysics?.BSP?.Root is not null`. +- **R3**: the existing `BSPQuery.FindCollisions` mutates `Transition` + state (SpherePath.CheckPos, CollisionInfo). Running it multiple + times per frame requires either save/restore between cells or + letting the first-hit's mutations stand (matching retail). + +## Other pending items + +### Phase A2 — PHSP inversion fix + +`BSPQuery.PolygonHitsSpherePrecise` at `BSPQuery.cs:117` has its +early-return condition inverted vs retail's `polygon_hits_sphere_slow_but_sure` +at `acclient_2013_pseudo_c.txt:322509-322517`. Ours bails when sphere +is FAR from plane; retail bails when sphere is OVERLAPPING plane. + +The actual fix is one line, but it doesn't fix walkable synthesis on +its own (because `AdjustSphereToPlane` still rejects tangent). It DOES +affect wall-collision precision at the tangent boundary. Pair with A3 +(synthesis removal) for the full benefit. + +### Phase A3 — synthesis removal + +Delete `TryFindIndoorWalkablePlane` (TransitionTypes.cs:1294) and rely +on the three retail CP retention mechanisms (Mechanisms A/B/C). The +previous session (2026-05-20) tried this and reverted because +multi-cell iteration was missing, so doorway transitions caused +free-fall. With A1.7 + A4 in place, A3 should work. + +### Lighting bugs + +- **Indoor lighting broken**: probably cell-light association or + visibility culling for lights inside cells. +- **Spotlight projection**: per-entity light direction transform. + +These are M7 polish, separate phase. Not blocking M2 ("kill a drudge"). + +## How to start a fresh session + +Copy the block below into a new Claude Code session in the acdream worktree: + +--- + +``` +Pick up the acdream collision-fix work from the 2026-05-21 session. + +1. Read docs/research/2026-05-21-collision-fixes-shipped-handoff.md + FIRST. It captures everything that shipped (4 fixes A1/A1.5/A1.6/A1.7 + + a probe spike) and what's left (Phase A4 multi-cell iteration is + the next major user-visible win). + +2. Current branch state: claude/lucid-goldberg-1ba520 is 8 commits + ahead of main. All 4 fixes have visual verification + no regression + in the 1129-test baseline. Ready to merge or build A4 on top. + +3. The next phase to design + ship is **A4 (multi-cell BSP iteration)**. + Sketch in §"Phase A4" of the handoff. Reads retail's + CTransition::check_other_cells (acclient_2013_pseudo_c.txt:272717-272798). + Wires the existing CellTransit helpers (FindCellList, + FindTransitCellsSphere, AddAllOutsideCells) into + Transition.FindEnvCollisions so collision is queried against ALL + cells the sphere overlaps, not just the one cell the player's + center is in. + +4. CLAUDE.md rules apply: + - No workarounds. Retail-faithful. + - Probe-first, design-second. Already have [indoor-bsp] + + [cell-transit] + [cell-cache] probes available. + - Use the superpowers:brainstorming skill before writing code. + A4 is a real architectural change deserving its own spec. + - Visual verification at the Holtburg inn (cell 0xA9B40164 + vestibule) is the acceptance test — walls in cell 0xA9B40157 + should block when the player is "in" 0xA9B40164 but their sphere + extends into 0xA9B40157. + +5. M2 ("kill a drudge") is the active milestone. Indoor walking + robustness is on the M2 critical path because dungeons have + drudges. A4 is the last big collision fix needed for M2's + "walkable indoor space" demo target. + +6. Launch command (same as this session): + $env:ACDREAM_DAT_DIR = "$env:USERPROFILE\Documents\Asheron's Call" + $env:ACDREAM_LIVE = "1" + $env:ACDREAM_TEST_HOST = "127.0.0.1" + $env:ACDREAM_TEST_PORT = "9000" + $env:ACDREAM_TEST_USER = "testaccount" + $env:ACDREAM_TEST_PASS = "testpassword" + $env:ACDREAM_DEVTOOLS = "1" + $env:ACDREAM_PROBE_INDOOR_BSP = "1" + $env:ACDREAM_PROBE_CELL = "1" + $env:ACDREAM_PROBE_CELL_CACHE = "1" + dotnet build -c Debug + dotnet run --project src\AcDream.App\AcDream.App.csproj --no-build -c Debug 2>&1 | + Tee-Object -FilePath "launch-a4.log" + + DO NOT set ACDREAM_PROBE_RESOLVE — it lagged the client this + session (400k+ log lines at 30 Hz). + +State the milestone + chosen phase in your first action. +``` + +--- + +## Anti-patterns from this session + +1. **Don't enable `ACDREAM_PROBE_RESOLVE` for live captures.** It + emits one line per resolve call at 30 Hz, producing 400k+ lines + per session and making the client laggy enough that the user + couldn't move. Use the lighter `[indoor-bsp]` + `[cell-transit]` + probes instead. + +2. **Don't assume "walk through wall" means PHSP inversion.** This + session walked through that misconception twice. The actual cause + was different bugs each time (doubled cylinders, interior shadow + bleed, cell-id stuck, missing physics polys in vestibule cells). + Always capture probe data before designing fixes. + +3. **Don't merge A1.5's pattern (`cellScope: entity.ParentCellId`) + without understanding that interior shadows might need MULTI-cell + scope, not just their parent cell.** A1.5 fixed the obvious leak + but introduced "stairs span cells" gaps. The real fix needs A4. + +4. **Don't skip visual verification between fixes.** Each of A1, + A1.5, A1.6, A1.7 was visually confirmed before moving to the + next. The user reported what was still broken at each step, + which guided the next fix. Without that loop, we'd have shipped + a "fix" that broke something else. + +5. **Don't try to fix lighting bugs in the same session as + collision bugs.** Different domain (rendering, not physics). + Defer to its own session. + +## Code anchors + +### This session's fixes (in commit order) + +- [`src/AcDream.Core/Physics/PhysicsDiagnostics.cs:246-277`](src/AcDream.Core/Physics/PhysicsDiagnostics.cs:246) — `ProbeWalkMissEnabled` flag. +- [`src/AcDream.Core/Physics/WalkMissDiagnostic.cs`](src/AcDream.Core/Physics/WalkMissDiagnostic.cs) — pure-function aggregator (full file). +- [`src/AcDream.Core/Physics/TransitionTypes.cs:1543-1586`](src/AcDream.Core/Physics/TransitionTypes.cs:1543) — `[walk-miss]` emission. +- [`src/AcDream.Core/Physics/PhysicsDataCache.cs:222-238`](src/AcDream.Core/Physics/PhysicsDataCache.cs:222) — `[floor-polys]` emission. +- [`src/AcDream.App/Rendering/GameWindow.cs:5830-5839`](src/AcDream.App/Rendering/GameWindow.cs:5830) — `_isLandblockStab` flag (A1). +- [`src/AcDream.App/Rendering/GameWindow.cs:6062-6064`](src/AcDream.App/Rendering/GameWindow.cs:6062) — mesh-AABB-fallback gate (A1). +- [`src/AcDream.Core/Physics/ShadowObjectRegistry.cs:34-92`](src/AcDream.Core/Physics/ShadowObjectRegistry.cs:34) — `cellScope` parameter (A1.5). +- [`src/AcDream.App/Rendering/GameWindow.cs`](src/AcDream.App/Rendering/GameWindow.cs) — 5 call sites pass `entity.ParentCellId ?? 0u` (A1.5). +- [`src/AcDream.App/Rendering/GameWindow.cs:5922-5933`](src/AcDream.App/Rendering/GameWindow.cs:5922) — `setup is not null && !_isLandblockStab` gate (A1.6). +- [`src/AcDream.Core/Physics/PhysicsEngine.cs:259-289`](src/AcDream.Core/Physics/PhysicsEngine.cs:259) — `PointInsideCellBsp` fall-through (A1.7). + +### What A4 will touch + +- [`src/AcDream.Core/Physics/TransitionTypes.cs:1407-1559`](src/AcDream.Core/Physics/TransitionTypes.cs:1407) — `FindEnvCollisions` (extend to iterate cell_array). +- [`src/AcDream.Core/Physics/CellTransit.cs`](src/AcDream.Core/Physics/CellTransit.cs) — already has the helpers; may need a new `EnumerateCells` variant that returns the set rather than picking one. +- [`src/AcDream.Core/Physics/PhysicsEngine.cs`](src/AcDream.Core/Physics/PhysicsEngine.cs) — `FindObjCollisions` may need similar treatment for shadow objects. + +## Retail decomp anchors + +- `acclient_2013_pseudo_c.txt:272717-272798` — `CTransition::check_other_cells` (A4 oracle). +- `:272565-272582` — `validate_transition` Mechanism B (LKCP proximity). +- `:273242-273340` — `transitional_insert` Mechanism C (step-down probe). +- `:322032-322077` — `CPolygon::adjust_sphere_to_plane`. +- `:322403-322500` — `CPolygon::polygon_hits_sphere`. +- `:322504-322593` — `CPolygon::polygon_hits_sphere_slow_but_sure` (A2 oracle — inversion). +- `:322974-322993` — `CPolygon::pos_hits_sphere` (front-face culling). +- `:323725-323939` — `BSPTREE::find_collisions` (full 6-path dispatcher). +- `:326211-326242` — `BSPNODE::find_walkable`. +- `:326706-326727` — `BSPLEAF::sphere_intersects_poly`. +- `:326793-326816` — `BSPLEAF::find_walkable`. + +## Probe + diagnostic reference + +| Env var | Volume | When to use | +|---|---|---| +| `ACDREAM_PROBE_INDOOR_BSP` | Low (indoor cells only) | Wall walk-through investigations. Logs `cell`, `wpos`, `lpos`, `result`, hit poly. | +| `ACDREAM_PROBE_CELL` | Very low (cell change events) | Cell-tracking issues. Logs old → new cell + position. | +| `ACDREAM_PROBE_CELL_CACHE` | One-shot per cell load | When you need cell BSP poly counts + bsphere. Identifies "vestibule" cells with sparse geometry. | +| `ACDREAM_PROBE_WALK_MISS` | High (per-frame MISS) | Walkable synthesis investigations (Phase A2/A3 work). | +| `ACDREAM_PROBE_BUILDING` | Medium | Building-shadow attribution. Multi-line `[resolve-bldg]` per hit. | +| `ACDREAM_PROBE_RESOLVE` | **VERY HIGH — DO NOT USE FOR LIVE PLAY** | Per-resolve attribution. 30 Hz × per-entity = 400k+ lines/session. Lagged the client this session. | +| `ACDREAM_PROBE_CONTACT_PLANE` | Medium | CP retention investigations. Bug B from 2026-05-20 era. | + +### Log analysis recipe + +```powershell +# 1. Convert UTF-16LE to UTF-8 for grep: +Get-Content launch.log -Encoding Unicode | Out-File launch.utf8.log -Encoding utf8 + +# 2. Quick counts: +grep -c '\[indoor-bsp\]' launch.utf8.log +grep -c '\[cell-transit\]' launch.utf8.log + +# 3. Per-cell hit rate: +grep '\[indoor-bsp\] cell=0xA9B40164' launch.utf8.log | grep -oE 'result=[A-Za-z]+' | sort | uniq -c +``` + +## What this is NOT + +This is **NOT** a complete fix for indoor walking. Walls walk-through-able +remain in cells where the PhysicsBSP has sparse coverage (vestibule +cells). A4 closes that gap by querying multiple cells per frame — +which is exactly what retail does. + +This is **NOT** related to the PHSP inversion (A2). A2 fixes per-poly +overlap math precision at the tangent boundary. A4 fixes which cells +get queried. They're orthogonal. + +This is **NOT** related to the lighting bugs the user reported. Those +are rendering-side; ignore in any collision work. + +## References + +- [`docs/research/2026-05-21-walk-miss-capture-findings.md`](2026-05-21-walk-miss-capture-findings.md) — probe spike findings. +- [`docs/superpowers/specs/2026-05-21-indoor-walk-miss-probe-design.md`](../superpowers/specs/2026-05-21-indoor-walk-miss-probe-design.md) — probe spec. +- [`docs/superpowers/specs/2026-05-21-cylinder-fallback-dedup-design.md`](../superpowers/specs/2026-05-21-cylinder-fallback-dedup-design.md) — A1 spec. +- [`docs/research/2026-05-20-indoor-walking-bug-a-handoff.md`](2026-05-20-indoor-walking-bug-a-handoff.md) — previous-session handoff (Bug B shipped, Bug A reverted). +- [`docs/research/2026-05-21-indoor-walking-doorway-investigation-prompt.md`](2026-05-21-indoor-walking-doorway-investigation-prompt.md) — the prompt that started this session.