Wires LogPushBackDispatch into the modern FindCollisions overload
at the entry block (after path/collisions/obj locals + movement
computed). Legacy overload at line ~1895 delegates to modern, so
single instrumentation site covers all dispatches.
returnState=-1 sentinel marks "entry log" — A6.P2 analysis pairs
each entry with subsequent [push-back] adjust-sphere lines and
the eventual return state.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
One-line per-call emission helper for the FindCollisions dispatcher
instrumentation site. Captures path-selection state (collide flag,
insertType, objState) + walk-interp + return state for direct
comparison to retail's BSPTREE::find_collisions breakpoint.
Output uses the [push-back-disp] tag to disambiguate from
[push-back] adjust-sphere events.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Wires the LogPushBackAdjust helper into all three return paths
of AdjustSphereToPlane (early-return on no-movement, early-return
on interp out-of-window, and the applied path). Probe is gated by
ProbePushBackEnabled so it's zero-cost when off.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
One-line per-call emission helper for the AdjustSphereToPlane
instrumentation site. Direct field-for-field paired comparison to
retail's CPolygon::adjust_sphere_to_plane breakpoint.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
BSPQuery.AdjustSphereToPlane is private; <see cref> from outside the
class can't resolve and emits CS1574. Switched to <c>...</c> code
span. Other two cross-refs (FindCollisions public, CheckOtherCells
internal-same-assembly) keep their <see cref> form.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
New PhysicsDiagnostics flag gates the [push-back] probe shipping
in subsequent tasks. Env-var ACDREAM_PROBE_PUSH_BACK=1 + DebugVM
mirror, matching the existing probe-toggle pattern.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
15-task TDD plan covering the three pieces of A6.P1:
Phase A — Build the [push-back] acdream probe (Tasks 1-9):
toggle + 3 helpers + 3 emission sites in BSPQuery/Transition,
DebugVM mirror, CLAUDE.md env-var docs.
Phase B — Build the cdb infrastructure (Tasks 10-12):
7-BP cdb script, PowerShell runner, README.
Phase C — Execute 9 captures + findings stub (Tasks 13-15):
PDB-match verify, capture dir + findings stub, scenario captures.
API surface verified against current code: ResolvedPolygon has no
Id property (probe omits poly attribution; cross-ref via time-
adjacent [push-back-cell] line). CheckOtherCells locals are
sp.CheckCellId + cellId + result (verified at TransitionTypes.cs
lines 1418-1473). SpherePath has Collide/InsertType/WalkInterp,
ObjectInfo has State (verified).
Spec: docs/superpowers/specs/2026-05-21-phase-a6-indoor-physics-fidelity-design.md
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Brainstormed + approved 2026-05-21 for M1.5 milestone work. Designs
the cdb probe spike methodology (7 retail breakpoints + new
[push-back] probe) to capture retail's per-tick BSP collision
response state at 9 indoor scenarios (4 buildings + 5 dungeon sites)
and compare against acdream. Working hypothesis: BSPQuery.AdjustSphereToPlane
or its callers over-correct vs retail, producing the family of
indoor symptoms (walls walk through, ping-pong, vibration, multi-Z
falling) plus driving the existing #90 + TryFindIndoorWalkablePlane
workarounds. A6 ships in 4 slices: P1 probe spike, P2 analysis,
P3 surgical fixes, P4 workaround removal + acceptance.
Phase O (DatPath Unification) pre-empted M1.5 and shipped 2026-05-21;
A6 resumes from Phase O state. Phase O only touched rendering/dat
code; indoor physics design is unchanged.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
ONE thing touches the DATs. WB code lives in our repo:
- src/AcDream.Core/Rendering/Wb/ — pure helpers (5 files, ~782 LOC)
- src/AcDream.App/Rendering/Wb/ — GL infra + mesh pipeline (~27 files, ~7K LOC)
Project references to WorldBuilder.Shared + Chorizite.OpenGLSDLBackend
dropped from AcDream.App.csproj and AcDream.Core.csproj.
references/WorldBuilder/ remains in-tree as read-reference only.
DefaultDatReaderWriter eliminated; DatCollection is the only dat reader.
WbMeshAdapter consumes our DatCollection via DatCollectionAdapter
(O-D7 fallback adapter; ObjectMeshManager has 26 _dats.X call sites,
exceeding the 20 refactor threshold).
Visual side-by-side passed: Holtburg town, inn interior, dungeon all
render identically to pre-O.
Doc updates:
- CLAUDE.md: rewrote WB integration cribs to point at extracted code.
Code Structure Rules rule 2 updated to remove stale seam names.
"Currently working toward" flipped from Phase O to M1.5 resumption.
- docs/architecture/worldbuilder-inventory.md: Phase O banner added.
Status/integration model updated to post-O ownership. Workflow
section updated to reference our extracted tree, not WB project ref.
- docs/plans/2026-04-11-roadmap.md: Phase O moved to shipped table.
Phase O "ahead" block collapsed to SHIPPED note. M1.5 block updated
to ACTIVE (Phase O shipped; resuming from 2026-05-20 baseline).
- docs/plans/2026-05-12-milestones.md: M1.5 heading updated to ACTIVE;
Phase O ship writeup prepended to the M1.5 block.
Phase O ship closes Tasks O-T1..O-T7 shipped across this session.
Specs + audit + plan: docs/superpowers/{specs,plans}/2026-05-21-phase-o-*
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The previous T7 commit (dc722e7) and the housekeeping commit (3e6f6ec)
together left the file in the tree with a <Compile Remove> guard in
the csproj. Per spec O-T7 and CLAUDE.md "no workarounds without
approval" the file was supposed to be git-rm'd outright.
This commit:
- git rm tests/AcDream.Core.Tests/Terrain/SplitFormulaDivergenceTest.cs
(the one-time N.5b data-collection sweep — job done at Phase N.5b ship)
- Removes the now-unneeded <Compile Remove> guard from
tests/AcDream.Core.Tests/AcDream.Core.Tests.csproj
Build green; tests green (1146 + 8 pre-existing failures baseline
maintained).
Spec: docs/superpowers/specs/2026-05-21-phase-o-dat-path-unification-design.md
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Five small post-cleanup items from T7 code review:
I1: Removed dead `datDir` parameter from WbMeshAdapter ctor (parameter
was unused after _wbDats removal; ArgumentNullException.ThrowIfNull
was misleading). Updated call sites in GameWindow.cs and
WbMeshAdapterTests.cs.
I2: Updated stale GameWindow.cs comment that still described
WbMeshAdapter as opening its own dat handles. Now reflects Phase O
state: shared DatCollection via DatCollectionAdapter.
I3: Documented thread-safety contract on RenderStateCache (render-thread
only — required for the mutable-static GL sentinel pattern).
M1: Added comment on IDatReaderWriter's write-path methods noting they
are preserved for verbatim compatibility but unused in acdream.
M3: Added comment on Chorizite.Core PackageReference in Core.csproj
explaining the previously-transitive dependency.
Also excluded SplitFormulaDivergenceTest.cs from the test build via
<Compile Remove>: this N.5b one-time data-collection test referenced
WorldBuilder.Shared types directly; after Phase O-T7 dropped that
project reference it no longer compiles. The sweep data it produced
already informed the N.5b Path-C decision and the file is retained
in the tree for historical reference.
Build green; tests green (1146 + 8 pre-existing failures baseline
maintained).
Spec: docs/superpowers/specs/2026-05-21-phase-o-dat-path-unification-design.md
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
End of Phase O extraction. Final cleanup:
- Dropped <ProjectReference> entries to WorldBuilder.Shared and
Chorizite.OpenGLSDLBackend from both AcDream.App.csproj and
AcDream.Core.csproj.
- Added Chorizite.Core NuGet PackageReference to AcDream.Core.csproj
(needed by Core.Rendering.Wb.TextureHelpers for TextureFormat enum;
previously transitive through the WB project ref).
- Added BCnEncoder.Net.ImageSharp (1.1.2) + SixLabors.ImageSharp (3.1.12)
as direct PackageReferences to AcDream.App.csproj — previously transitive
via Chorizite.OpenGLSDLBackend project; used directly by ObjectMeshManager.
Item A (BaseObjectRenderManager static fields):
- Inlined CurrentAtlas/CurrentVAO/CurrentIBO into a new RenderStateCache.cs
static class (AcDream.App.Rendering.Wb namespace) — the 4 consumers
(ManagedGLIndexBuffer, ManagedGLTexture, ManagedGLTextureArray, ParticleBatcher)
all reference RenderStateCache.* instead of BaseObjectRenderManager.*.
- Dropped using Chorizite.OpenGLSDLBackend.Lib from all 4 consumers and from
WbDrawDispatcher (which had it only as a dead import).
Item B (ActiveParticleEmitter.ObjectLandblock):
- ObjectLandblock? erased to object?; WorldBuilder.Shared.Models.ObjectId? erased
to ulong? — both fields are stored but never read by any consumer in our codebase.
- Dropped both WB using directives from ActiveParticleEmitter.cs.
Item C (IDatReaderWriter / IDatDatabase):
- Verbatim copy of both interfaces into IDatReaderWriter.cs in
AcDream.App.Rendering.Wb namespace — DatCollectionAdapter and ObjectMeshManager
already live in that namespace, so no using changes needed.
- Dropped using WorldBuilder.Shared.Services from DatCollectionAdapter.cs and
ObjectMeshManager.cs.
Additional extractions required by the reference drop:
- GeometryUtils.cs: verbatim copy of WorldBuilder.Shared.Lib.GeometryUtils
(float-precision overloads only; Vector3d double-precision overloads omitted —
ObjectMeshManager uses only the float versions).
- Dropped using WorldBuilder.Shared.Lib from ObjectMeshManager.cs.
WbMeshAdapter.cs cleanup (spec O-D12):
- Deleted _wbDats (DefaultDatReaderWriter) field + ctor init + Dispose call.
- Deleted the [indoor-upload] NULL_RESULT diagnostic block (lines ~205-262) —
its Phase 2 cell-resolution investigation is complete; its _wbDats.ResolveId
dependency goes with this commit.
- Deleted _pendingEnvCellRequests field + isPendingEnvCell tracking in Tick().
- Simplified Tick() to a clean drain loop.
Deleted SplitFormulaDivergenceTest.cs — one-time N.5b data-collection sweep;
job done.
Verified acceptance criteria:
- Zero <ProjectReference> to WorldBuilder.* / Chorizite.OpenGLSDLBackend.* in any csproj.
- Zero 'using WorldBuilder.*' / 'using Chorizite.OpenGLSDLBackend.*' in src/.
- DefaultDatReaderWriter referenced in zero places in src/ (comments only).
Build green (0 warnings, 0 errors).
Tests: 1154 total (-1 from deleted SplitFormulaDivergenceTest), 1146 pass,
8 pre-existing failures (unchanged from baseline — physics/input tests
unrelated to this change).
Spec: docs/superpowers/specs/2026-05-21-phase-o-dat-path-unification-design.md
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Code-review findings on T4:
1. Added lock(_lock) around _db.TryGet and TryGetFileBytes in
DatDatabaseWrapper, matching WB's DefaultDatDatabase pattern.
ObjectMeshManager.PrepareMeshDataAsync runs on the thread pool, so
concurrent dat access through the adapter must be serialized — our
underlying DatCollection is not documented as thread-safe.
2. Removed unused `using WorldBuilder.Shared.Models;` from WbMeshAdapter.cs
(its only purpose was TerrainEntry, which moved to AcDream.Core in T2).
Build green; tests green (1147 passing, 8 pre-existing failures baseline).
Spec: docs/superpowers/specs/2026-05-21-phase-o-dat-path-unification-design.md
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Four fixes from T4 spec review:
1. Extracted InstanceData.cs (14-line struct) verbatim to
src/AcDream.App/Rendering/Wb/InstanceData.cs (per O-D1).
2. ObjectMeshManager.cs: replaced `using Chorizite.OpenGLSDLBackend.Lib;`
with `using AcDream.Core.Rendering.Wb;` (TextureHelpers comes from
our T2 Core extraction; InstanceData comes from new T4 cleanup).
3. EmbeddedResourceReader.GetEmbeddedResource promoted from `internal`
to `public` per O-D9 intent (the type promotion only changed the
class signature in T3; this finishes the spec).
4. OpenGLGraphicsDevice.cs: removed stale T3 interim comment at
lines 142-145 — T4 resolved the ParticleBatcher construction
via post-ctor assignment in WbMeshAdapter.cs:78.
Build green; tests green (1147 passing, 8 pre-existing failures
baseline maintained).
Spec: docs/superpowers/specs/2026-05-21-phase-o-dat-path-unification-design.md
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Phase O Task 4: extract the WB mesh pipeline (ObjectMeshManager + 7 support files)
from references/WorldBuilder into src/AcDream.App/Rendering/Wb/ and bridge dat I/O
through our DatCollection via a thin DatCollectionAdapter.
O-D7 adapter path taken: ObjectMeshManager has 26 _dats.X call sites (threshold 20),
so a DatCollectionAdapter : IDatReaderWriter is introduced rather than refactoring
ObjectMeshManager's internal dat access directly.
Files added (verbatim copies, namespace-only changes):
- ObjectMeshManager.cs — mesh pipeline hub; IDatReaderWriter field satisfied by adapter
- GlobalMeshBuffer.cs — single global VAO/VBO/IBO manager
- EdgeLineBuilder.cs — wireframe edge geometry from CellStruct polygons
- ModernRenderData.cs — ModernBatchData + LandblockMdiCommand structs
- TextureAtlasManager.cs — texture array grouping by (Width, Height, Format)
- ParticleBatcher.cs — GPU particle batching; T4 interim uses BaseObjectRenderManager
static fields from Chorizite.OpenGLSDLBackend.Lib (stays until T7)
- ParticleEmitterRenderer.cs — per-emitter particle lifecycle + rendering
- ActiveParticleEmitter.cs — wrapper holding renderer + part index + local offset
- DatCollectionAdapter.cs — NEW: bridges DatCollection → IDatReaderWriter; implements
ResolveId() via DatDatabase.TypeFromId + Tree.TryGetFile in HighRes→Portal→Language→Cell
order matching DefaultDatReaderWriter; DatDatabaseWrapper wraps DatDatabase as IDatDatabase
WbMeshAdapter.cs changes (T4 Step 6):
- _graphicsDevice switched from Chorizite.OpenGLSDLBackend.OpenGLGraphicsDevice to
extracted AcDream.App.Rendering.Wb.OpenGLGraphicsDevice
- ParticleBatcher = new ParticleBatcher(_graphicsDevice) restored (T3 had null! placeholder)
- ObjectMeshManager now constructed with new DatCollectionAdapter(dats) instead of _wbDats
- _wbDats field + its construction + disposal + [indoor-upload] NULL_RESULT diagnostic block
left intact — T7 cleanup removes these once WorldBuilder project ref is dropped
EmbeddedResourceReader.cs: replaced assembly manifest lookup (wrong prefix for our assembly)
with disk-based lookup mapping "Shaders.Particle.vert" → Rendering/Shaders/wb_particle.vert;
consistent with all other acdream shaders.
wb_particle.vert / wb_particle.frag: WB particle shaders copied verbatim with wb_ prefix
to distinguish from acdream's own particle.vert.
OpenGLGraphicsDevice.cs: ParticleBatcher property type updated to extracted ParticleBatcher;
setter changed from private to internal so WbMeshAdapter (same assembly) can assign post-ctor.
Build: green (0 errors, 0 warnings in AcDream.App).
Tests: 1147+8 baseline maintained (8 pre-existing failures unchanged).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Phase O Task 3 — verbatim-copy GL infra from Chorizite.OpenGLSDLBackend
into src/AcDream.App/Rendering/Wb/ (namespace AcDream.App.Rendering.Wb).
18 files extracted (all namespace-changed; no algorithm changes):
OpenGLGraphicsDevice, ManagedGLTexture, ManagedGLTextureArray,
ManagedGLVertexBuffer, ManagedGLIndexBuffer, ManagedGLVertexArray,
ManagedGLFrameBuffer, ManagedGLUniformBuffer, GLSLShader, GLHelpers,
GLStateScope, GpuMemoryTracker, SceneData, DebugRenderSettings,
TextureParameters, TextureFormatExtensions, BufferUsageExtensions,
EmbeddedResourceReader.
3 internals promoted to public (O-D9):
EmbeddedResourceReader, TextureFormatExtensions, BufferUsageExtensions.
SixLabors.ImageSharp not reachable: TextureHelpers was placed in
AcDream.Core (no GL/ImageSharp dep); only the GL types went to App.
TextureHelpers.GetCompressedLayerSize added to AcDream.Core.Rendering.Wb
(was in Chorizite.OpenGLSDLBackend.Lib.TextureHelpers; uses
Chorizite.Core.Render.Enums.TextureFormat which Core gets transitively
via the still-present WB project refs).
T3/T4 boundary interims:
- WbMeshAdapter._graphicsDevice stays Chorizite.OpenGLSDLBackend.OpenGLGraphicsDevice
(T4 will swap it when ObjectMeshManager is extracted).
- OpenGLGraphicsDevice.ParticleBatcher deferred to null! (T4 extracts
ParticleBatcher alongside ObjectMeshManager; can't pass `this` of our
new type to the WB-original ctor before T4).
- ManagedGLTextureArray uses our TextureHelpers via explicit alias.
- IUniformBuffer is in Chorizite.Core.dll under Chorizite.OpenGLSDLBackend
namespace (unusual packaging); resolved via type alias.
- AcDream.App.csproj gets explicit Chorizite.Core 0.0.18 PackageReference
(IUniformBuffer + other Chorizite.Core types now used directly in App).
Build green. Test baseline 1147+8 maintained (1902 passing, 8 pre-existing
MotionInterpreterTests failures unrelated to T3).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Verbatim copy of 5 WorldBuilder files into src/AcDream.Core/Rendering/Wb/:
- TextureHelpers.cs (pixel-format decoders, Chorizite Lib)
- SceneryHelpers.cs (scenery transforms, Chorizite Lib)
- TerrainUtils.cs, TerrainEntry.cs, CellSplitDirection.cs (WB.Shared Landscape)
Namespace migrated from WorldBuilder.* / Chorizite.OpenGLSDLBackend.Lib
to AcDream.Core.Rendering.Wb per O-D11. [MemoryPackable] stripped from
TerrainEntry per O-D10 (we don't serialize the struct).
Updated 3 source files + 1 test file to import from the new namespace.
Verbatim discipline (O-D1): only namespace + MemoryPack attribute changed.
All algorithm bodies byte-identical to upstream.
Note: TextureHelpers omits IsAlphaFormat() and GetCompressedLayerSize()
because those reference Chorizite.Core.Render.Enums.TextureFormat, a type
that has no path into AcDream.Core without adding an unwanted NuGet dep.
Neither method is called from Core or the test suite; the omission is safe.
Verified on main checkout: dotnet build green (0 errors), dotnet test
green — Failed: 8, Passed: 1147, Skipped: 0, Total: 1155 (baseline maintained).
TextureDecodeConformanceTests (9/9) pass byte-for-byte after namespace swap.
AcDream.Core project alone builds green in this worktree (App-layer failures
are pre-existing, blocked by empty WB submodule, addressed in Tasks 3+4).
Spec: docs/superpowers/specs/2026-05-21-phase-o-dat-path-unification-design.md
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Phase O setup: extracted-WB code home + MIT attribution per O-D5.
Spec: docs/superpowers/specs/2026-05-21-phase-o-dat-path-unification-design.md
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Plan: 7 tasks decomposing spec T2..T9 with bite-sized TDD-style steps,
exact file paths, commit-message templates, and a T4 safety-check
branch (refactor in place if ObjectMeshManager._dats call sites <=20;
fall back to thin adapter otherwise).
Spec fix: §4.1 mesh-pipeline files now correctly placed under
src/AcDream.App/Rendering/Wb/ instead of Core (ObjectMeshManager uses
Silk.NET.OpenGL types from Managed* wrappers, and CLAUDE.md forbids
Core depending on GL). §4.2's layer split (TextureHelpers in Core,
rest in App) was already correct.
Plan task order: T2 (setup) -> T5 (Core helpers, lowest risk) ->
T3 (App GL infra) -> T4 (App mesh pipeline + dat-shim) -> T7 (drop
refs + cleanup) -> T8 (visual verification) -> T9 (ship). T5 moved
earlier than spec order to validate the namespace migration flow on
small-blast-radius files before the load-bearing T4.
Self-review: all 12 spec decisions (O-D1..O-D12) mapped to plan tasks;
placeholders intentional + explained (MIT license body fetched at T2
step 4; commit-message parameters filled at task close).
Spec: docs/superpowers/specs/2026-05-21-phase-o-dat-path-unification-design.md
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
O-T1 audit (REPORT-ONLY) maps acdream's transitive closure on WorldBuilder:
33 files / ~7.7K LOC across Chorizite.OpenGLSDLBackend (28 files) and
WorldBuilder.Shared (5 files). Verdict on O-Q1 (thread-model): SAFE —
adapters run render-thread only; no worker-thread access to WB code.
Spec amendments incorporated via brainstorm:
- O-D7: Refactor ObjectMeshManager to take DatCollection directly (not
via adapter). T4 safety check — fall back to thin adapter if call-site
count >20.
- O-D8: Drop LandSurfaceManager, EnvCellRenderManager, PortalRenderManager,
TerrainRenderManager from the extract list — audit confirmed not reachable
(we have our own ports or never used them).
- O-D9: Promote 3 internal types in Chorizite to public on extraction
(EmbeddedResourceReader, TextureFormatExtensions, BufferUsageExtensions).
- O-D10: Strip [MemoryPackable] from TerrainEntry (we don't serialize).
- O-D11: Namespace AcDream.Core.Rendering.Wb.* for extracted code.
- O-D12: Drop ResolveId + [indoor-upload] NULL_RESULT diagnostic block.
Task breakdown: T6 (EnvCell/portal) eliminated; T5 (stateless helpers)
shrinks to 0.5d; T4 (mesh + refactor) grows to 2.5d. Net effort estimate
holds at ~7.75d.
All originally-open spec questions are now closed (Q1/Q2/Q3/Q4) or
deferred to T3 with an explicit verify step (Q5: SixLabors.ImageSharp
reachability).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Self-contained prompt for a fresh Claude Code session. The next session
reads it once, has all the context it needs, produces the WB-usage
closure audit at docs/research/2026-05-21-phase-o-t1-wb-audit.md,
and stops before any extraction. Investigation-only (the /investigate
skill applies). User reviews the audit before T2 begins.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Phase O extracts the WB pieces we actually use (mesh pipeline, texture
decode, GL state, scenery, terrain blending, EnvCell/portal decode —
roughly 3-5K LOC) into src/AcDream.Core/Rendering/Wb/, swaps their
dat dependency from DefaultDatReaderWriter to our DatCollection, and
drops the WorldBuilder.Shared + Chorizite.OpenGLSDLBackend project
references. WB stays in references/ as a read reference, not as a
project dependency. MIT attribution in NOTICE.md.
Tagline: ONE thing touches the DATs.
Discipline: verbatim copy first, no "improvements" while extracting.
Refactors land in follow-up phases. Out of scope: re-porting from
retail decomp; perf optimization; API cleanup.
User direction 2026-05-21: pre-empts M1.5. M1.5 paused at its
2026-05-20 baseline; A6/A7 don't touch dat infrastructure so no
rework needed when it resumes.
Files:
- docs/superpowers/specs/2026-05-21-phase-o-dat-path-unification-design.md (new, full spec)
- docs/plans/2026-04-11-roadmap.md (Phase O block inserted before M1.5; M1.5 marked PAUSED)
- CLAUDE.md (Currently-working-toward line updated; M1.5 block marked paused)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Continued indoor testing through 2026-05-20 surfaced a deep family
of physics + lighting bugs that span buildings AND dungeons. Today's
session shipped 5 surgical fixes (A4 + #89 + #90 + #91 + #92) that
close the user-visible "walls walk through at Holtburg inn" symptom,
but #90 specifically is a CLAUDE.md-rules workaround (sphere-overlap
stickiness on top of point-only cell containment) added without prior
approval. The underlying issue (BSP push-back distance probably
diverges from retail) hasn't been measured. Plus the umbrella #83
(indoor multi-Z walking) has been open since 2026-05-19 with multiple
aborted fix attempts; plus indoor lighting (#80 + #81 + new #93 +
#94) has been deferred as "M7 polish" but is actually part of the
same indoor-experience problem.
Promoting to a milestone of its own forces the work to be central,
retail-anchored, and complete — not another whack-a-mole patch.
Milestone M1.5 — "Indoor world feels right":
Demo: enter Holtburg Sewer through the in-town portal, navigate
through 5-7 rooms with stairs + a multi-Z chamber, exit back to
town. Walls block. Stairs work. Items block. Lighting reads
correctly. Cell transitions smooth.
Phases:
A6 — Indoor physics fidelity (cdb-driven)
A7 — Indoor lighting fidelity (RenderDoc + retail-decomp driven)
Issues in scope: #80, #81, #83, #88, #90 (workaround removal),
#93 (new lighting umbrella), #94 (held-item spotlight),
+ TryFindIndoorWalkablePlane synthesis removal.
M2 ("Kill a drudge") deferred until M1.5 lands.
This commit updates:
- docs/plans/2026-05-12-milestones.md (M1.5 block inserted, M2 moved
to deferred status)
- docs/plans/2026-04-11-roadmap.md (A6 + A7 sub-pieces detailed)
- CLAUDE.md (Currently working toward updated to M1.5, M2 paragraph
marked deferred, M1.5 baseline shipped paragraph added)
- docs/ISSUES.md (#80, #81, #83, #88, #90 tagged M1.5 scope;
new #93 indoor lighting umbrella + #94 held-item spotlight filed)
- docs/research/2026-05-21-open-items-pickup-prompt.md (landscape
table reorganized around M1.5 phases)
A6 + A7 specs to be drafted in the next session(s).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
EnterPlayerModeNow computed the initial cellId from landblock prefix
+ hardcoded low byte 0x0001 (outdoor sentinel) and passed only the
low 16 bits to PhysicsEngine.Resolve. When the server places the
player INSIDE a building (spawn cell id e.g. 0xA9B4015A indoor), the
sentinel forced the outdoor seed branch — for the first several ticks
CheckBuildingTransit hadn't yet picked up the interior cell (it
depends on the sphere overlapping the destination cell's BSP), the
player was classified outdoor, indoor BSP queries didn't run, and
exterior walls were passable until enough inward motion finally
promoted them.
User-visible symptom: "logged in inside the inn, ran out through the
exterior wall; ran back in and the walls now block."
Fix: use spawn.Position.LandblockId (the server's authoritative full
cell id with landblock prefix) when available; fall back to the old
sentinel only if the spawn record is missing (defensive — shouldn't
fire in live play since OnLiveEntitySpawnedLocked writes _lastSpawnByGuid
before EnterPlayerModeNow can possibly run).
1147 + 8 baseline maintained.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Outdoor→indoor entry path used PointInsideCellBsp (point-only) for the
building-portal containment test. When the player logs in INSIDE a
building and the foot-sphere center is just past the destination cell's
CellBSP boundary, the point-only check failed → CellId stuck as
outdoor → indoor BSP queries never ran → walls passable. User-reported
symptom: "logged in in the inn, at start ran through exterior walls,
ran back in and they block now."
Fix: swap PointInsideCellBsp for SphereIntersectsCellBsp (the radius-
aware port from #90). Promotes CellId to the interior cell the moment
ANY part of the foot-sphere crosses the destination cell boundary —
matches retail's CCellStruct::sphere_intersects_cell timing at
acclient_2013_pseudo_c.txt:317666 exactly.
The sphereRadius parameter was already plumbed through CheckBuildingTransit
per #89's documented "future upgrade" note from 2026-05-19 (which is
exactly today's symptom). Closes#89.
1147 + 8 baseline maintained.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Interior items (fireplaces, tables, chests) registered via A1.5's
ShadowObjectRegistry.Register `cellScope` parameter (commit 4d3bf6f)
are stored under their ParentCellId key (e.g. 0xA9B40121). But
GetNearbyObjects's broad-phase only iterates outdoor 24m landcell
keys (0xA9B40029 etc) and never looks up indoor cell keys, so
interior shadows were registered but unreachable. User-visible
symptom: tables/boxes/fireplaces don't block movement, while walls
DO block (the indoor BSP path is separate).
Fix: GetNearbyObjects accepts an optional indoorCellIds parameter
and additionally queries _cells[indoorCellId] for each entry with
low-byte >= 0x0100u. FindObjCollisions computes the set via
CellTransit.FindCellSet (same set A4 uses for multi-cell BSP
iteration) and passes it through. Outdoor seeds typically produce
sets containing only outdoor land-cells which the new branch filters
out, so the outdoor-only behavior is preserved.
1147 + 8 baseline maintained. Closes the user-reported regression
"walls block now correct but interior items such as tables and boxes
or fireplaces do not block."
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
ResolveCellId's indoor-seed fall-through was point-only: when the indoor
BSP push-back moved the foot-sphere CENTER a few cm outside the indoor
CellBSP volume, the resolver flipped CellId back to outdoor. Next tick
re-promoted via CheckBuildingTransit. The ping-pong caused most ticks
to be classified outdoor, bypassing indoor BSP wall checks entirely
and producing the user-reported "walls walk through everywhere in the
inn" symptom.
Fix: port retail's BSPTREE::sphere_intersects_cell_bsp
(acclient_2013_pseudo_c.txt:323267 → BSPNODE variant at :325546) as
BSPQuery.SphereIntersectsCellBsp(node, center, radius). Replace the
point-only check at PhysicsEngine.ResolveCellId:285 with the radius-
aware overlap test. Player stays classified indoor as long as ANY
part of the foot-sphere still overlaps the indoor cell volume; only
flips to outdoor when the sphere is FULLY outside.
Retail uses a 0.01 m epsilon on the radius (acclient :325551); ported
verbatim. 8 new unit tests cover null/leaf/inside/on-plane/straddling/
fully-outside/tangent-boundary cases plus a regression-anchor test
that proves the old PointInsideCellBsp would have returned false for
the same straddling input.
1147 + 8 baseline maintained (was 1139 + 8 before #90 fix). Closes#90.
A4 multi-cell iteration (shipped earlier today) should now actually
exercise in production since the player can stably remain in indoor
cells.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Phase A4 (multi-cell BSP iteration) ships in three commits (e6369e2,
493c5e5, 691493e — with revert 3add110 + reapply during visual
verification that proved A4 is not the cause of the issue surfaced).
1139 + 8 baseline maintained. 10 new unit tests pass. Wires retail's
CTransition::check_other_cells (acclient_2013_pseudo_c.txt:272717-272798)
into Transition.FindEnvCollisions.
Visual verification at the Holtburg inn vestibule surfaced a separate,
pre-existing M2 blocker (filed as #90): CellId ping-pongs between
outdoor 0xA9B40022 and indoor 0xA9B40164 on every wall push-back
because the push-back exits the indoor CellBSP volume, causing the
resolver to flip back to outdoor and bypass walls on outdoor ticks.
Indoor BSP results (Collided/Adjusted/Slid all firing) prove walls ARE
detected when the player is indoor; the aggregate "walls walk through"
appearance comes from CellId classification instability, not from
collision detection.
Bug reproduces fully with A4 reverted (launch-revert2.log captured 18
cell-id flips between 0xA9B40022 ↔ 0xA9B40164, 11 inside=True
building-transit events, 61 indoor-bsp queries firing the full
result distribution). A4 is correct and tested but dormant in
practice until #90 is fixed.
Updates:
- docs/research/2026-05-20-phase-a4-shipped-cell-pingpong-finding.md (new)
- docs/plans/2026-04-11-roadmap.md (A4 shipped row added)
- CLAUDE.md (Indoor walking Phase A4 paragraph + next-step pointer
to #90 with retail oracle anchor at acclient_2013_pseudo_c.txt:308742-308783)
- docs/ISSUES.md (#90 filed, HIGH severity, M2-blocker)
- docs/research/2026-05-21-open-items-pickup-prompt.md (landscape
table updated — A4 closed, #90 promoted to top blocker)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
After the primary cell's BSP returns OK, query every other cell the
foot-sphere overlaps via CellTransit.FindCellSet + Transition.CheckOtherCells.
Closes the Holtburg inn vestibule wall walk-through: the vestibule
(cell 0xA9B40164) has only 4 BSP polys; walls live in the adjacent
interior cell (0xA9B40157). Without A4 the adjacent cell's BSP was
never queried.
End-to-end test reduces the real Holtburg bug to a minimal synthetic
two-cell fixture: empty vestibule BSP + interior cell with the
existing BSPStepUpFixtures.TallWall (the same fixture B2 uses to
prove a grounded mover can't scale a 5m wall). Pre-A4: returns OK
(walks through). Post-A4: returns Slid (the wall halts the
transition).
FindEnvCollisions visibility tightened from private → internal so
the integration test can call it directly without going through
FindTransitionalPosition's sub-step iteration.
Retail oracle: acclient_2013_pseudo_c.txt:272717-272798
(CTransition::check_other_cells).
Spec: docs/superpowers/specs/2026-05-20-phase-a4-multi-cell-bsp-design.md
Plan: docs/superpowers/plans/2026-05-20-phase-a4-multi-cell-bsp.md
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Port of retail's CTransition::check_other_cells at
acclient_2013_pseudo_c.txt:272717-272798. Iterates every non-primary
cell in a candidate set, runs BSPQuery.FindCollisions per cell with
that cell's WorldTransform-derived rotation + origin, halts on first
Collided/Adjusted/Slid.
ApplyOtherCellResult is the combine-semantics helper extracted for
unit testability — it pins the retail switch:
- Collided/Adjusted → CollidedWithEnvironment = true (gated on
!Contact), halt.
- Slid → ContactPlaneValid + ContactPlaneIsWater = false,
halt.
- OK → continue.
Not yet wired into FindEnvCollisions — see next commit. Probe gated
on PhysicsDiagnostics.ProbeIndoorBspEnabled (ACDREAM_PROBE_INDOOR_BSP).
Six new unit tests: five against the pure combine helper for each halt
case + one direct CheckOtherCells call exercising the null-BSP guard.
Spec: docs/superpowers/specs/2026-05-20-phase-a4-multi-cell-bsp-design.md
Plan: docs/superpowers/plans/2026-05-20-phase-a4-multi-cell-bsp.md
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Refactors FindCellList to delegate to a private helper
(BuildCellSetAndPickContaining) that returns BOTH the containing cell
id AND the full candidate HashSet. Public surface gains a new
FindCellSet overload; existing FindCellList behavior is unchanged.
Used by the upcoming Transition.CheckOtherCells (Phase A4) to iterate
every cell the sphere overlaps for per-cell BSP collision. Mirrors
retail's CObjCell::find_cell_list filling both cell_array AND var_4c
at acclient_2013_pseudo_c.txt:272725.
Three new unit tests cover sphere-fully-inside-primary,
sphere-straddling-portal, and outdoor-seed-neighbour-landcells cases.
Spec: docs/superpowers/specs/2026-05-20-phase-a4-multi-cell-bsp-design.md
Plan: docs/superpowers/plans/2026-05-20-phase-a4-multi-cell-bsp.md
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Port retail's CTransition::check_other_cells (acclient_2013_pseudo_c.txt
:272717-272798) into Transition.FindEnvCollisions so the foot-sphere
sees walls in EVERY cell it overlaps, not just the one cell the player's
center is in. Closes the Holtburg inn vestibule wall walk-through
(cell 0xA9B40164 has only 4 polys; adjacent 0xA9B40157 has 23 walls
that are never queried today).
Architecture: new CellTransit.FindCellSet overload (preserves the
candidate HashSet that FindCellList currently discards), new private
Transition.CheckOtherCells method (direct port of the retail loop),
one wire-up in FindEnvCollisions between the existing primary-cell BSP
return and the synthesis fall-through. ~380 LOC total.
Out of scope: FindObjCollisions (already landblock-radius broadphased),
synthesis multi-cell search (A3's job), var_4c re-target (defer).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
After the 2026-05-21 session merged A1/A1.5/A1.6/A1.7 to main, six
discrete items remain. This doc maps them as a landscape rather than
single-phase:
- Collision (M2 critical path): A4 multi-cell BSP iteration → verify
stairs → A2 PHSP inversion → A3 synthesis removal
- Rendering (M7 polish): indoor lighting + spotlight-projection bugs
The recommended order is A4 first (biggest user payoff, unblocks A3),
then stairs verification, A2 + A3 paired, lighting in a separate
session. A3 must NOT ship before A4 — that's the Bug A regression
from 2026-05-20.
Includes a pasteable session-start prompt that the user can box into
a fresh Claude Code session.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
After merging the 2026-05-21 session into main (56d2b5e), update the
pickup prompt so the next session starts from main and creates its
own worktree for the A4 work — not the now-merged lucid-goldberg
branch.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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) <noreply@anthropic.com>
ISSUES #83 Phase A1.7. CellTransit.FindCellList returns currentCellId
when no candidate cell's CellBSP contains the sphere center — but
this also fires when the player has walked OUTSIDE the entire
portal-connected indoor graph (e.g., breached a missing wall poly,
walked through a doorway gap). The player's CellId stays stuck on
the old indoor cell whose BSP is now far away, NodeIntersects fails
at the BSP root for every collision query, and no walls block in
their actual location.
Probe evidence (launch-stairs.utf8.log, A1.6 verify):
- Cell 0xA9B40164: 646 indoor-bsp queries, 644 returned OK (99.7%).
No walls firing.
- Player local positions in cell 0xA9B40164 ranged X[-0.66..33.18],
Y[10.68..63.53] — a 34x53m envelope. The player was geometrically
~62m from the cell's world origin while CellId never updated.
Compare to adjacent cells where collision works:
- Cell 0xA9B4015A: 230 queries, 32% hit rate
- Cell 0xA9B40157: 131 queries, 38% hit rate
Fix: after CellTransit.FindCellList returns, verify the resolved
cell's CellBSP actually contains the sphere center via
BSPQuery.PointInsideCellBsp. If not, fall through to the existing
outdoor cell resolution branch (terrain grid + CheckBuildingTransit
for re-entry into a different building).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
ISSUES #83 Phase A1.6. Phase A1 gated the mesh-AABB-fallback path on
!_isLandblockStab, but Setup-derived CylSphere/Sphere/Radius-fallback
registrations (lines 5910-6005) still ran for stabs. A landblock stab
whose source is a Setup (0x02xxxxxx) with defined CylSpheres got BOTH
per-part BSP shadows AND a CylSphere shadow with id=entity.Id,
producing an invisible collision cylinder at the stab origin
alongside the correct BSP walls. User reported this as "thin air" hits
outside specific Holtburg buildings.
Retail's CBuildingObj uses BSP exclusively. Setup CylSphere/Sphere
data is for scenery (trees with trunk cylinders) and creatures.
Fix: extend A1's _isLandblockStab gate to wrap the Setup-derived
registration block (cylsphere, sphere, radius-fallback). One AND
clause on the outer `if (setup is not null)`.
Probe evidence (launch-a15-verify.utf8.log):
- 0xC0A9B45D (6 hits in outdoor cell 0xA9B40029) — Setup CylSphere on
stab. src=0x020002FC. Pre-A1.5 it would also have been registered
in adjacent landcells.
- 0xC0A9B463 (2 hits) — Setup Sphere on stab. src=0x020000E6.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
ISSUES #83 Phase A1.5. ShadowObjectRegistry.Register() assigned each
entity to the outdoor landcell grid (8x8 cells, 24m square) based on
its XY position. For interior EnvCell statics (fireplace, furniture,
sign) hydrated by BuildInteriorEntitiesForStreaming with
ParentCellId = envCellId (a high-cellId interior cell like
0xA9B40121), this meant the shadow got stamped into the OUTDOOR
landcell whose XY they overlapped (e.g., 0xA9B40029).
When the player was OUTSIDE the building in 0xA9B40029, the indoor
chair/fireplace shadow fired collisions in "thin air" outdoors. The
user reported this on Holtburg cottage exteriors after the Phase A1
landblock-stab fallback fix.
Fix: add optional cellScope parameter to Register(). When non-zero
(passed as entity.ParentCellId ?? 0u from the 5 entity-loop call
sites in GameWindow), skip the XY-based landcell loop and register
the shadow ONLY in that cell. Live server-spawn registration at
GameWindow.cs:3137 keeps the XY-based behavior (live entities move
between cells).
Probe evidence (launch-a1-verify.utf8.log, post-A1 capture):
- 71 hits on 0x40B50054 (interior static) in OUTDOOR cell 0xA9B40029.
- 47 hits on 0xA9B47C00 (other Holtburg cottage BSP — legitimate).
- 31 hits on 0x40B50048 / 15 on 0x40B50018 (interior statics).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
ISSUES #83 Phase A1. Landblock stabs (entity.Id 0xC0XXYY00+n per
LandblockLoader.cs:55) were being registered with TWO collision
shadows: the correct per-part BSP at `entity.Id*256 + partIdx`, AND a
redundant mesh-AABB-fallback cylinder at `entity.Id`. The fallback
clamped to 1.5m radius, centered at the building's mesh origin,
producing user-reported "thin air" collisions inside cottages and
within 2m of building exteriors.
The fallback was originally designed for canopy-only-BSP procedural
scenery (0x80XXYY00+n) — trees whose BSP covers the canopy but not
the trunk. Landblock stabs have full BSP coverage and don't need it.
Probe evidence (launch-thinair capture):
- 0xC0A9B479 cylinder fallback (Holtburg cottage): 104 hits in a
short capture session, all inside the cottage main room
(cell=0xA9B4013F), ~2m from the building's mesh origin.
- 0xA9B47900 BSP (the actual cottage walls): 52 legitimate hits.
Fix: one new bool _isLandblockStab + one clause in the existing
mesh-AABB-fallback gate.
Spec: docs/superpowers/specs/2026-05-21-cylinder-fallback-dedup-design.md.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Three docs from the indoor walk-miss probe spike landed in commits
27c7284..a2e7a87:
- Spec: design of the [walk-miss] + [floor-polys] diagnostic emissions
with the H1/H2/H3 disambiguation matrix.
- Plan: 3-task TDD implementation plan (flag, aggregator, emissions).
- Findings: live-capture analysis showing H3 (walkable_hits_sphere /
adjust_sphere_to_plane synthesis rejection) is the dominant defect.
817 of 876 ground-contact misses (93%) cluster at dz~0.48 m, while
the 7 HITs all sit at dz~0.46 m — a 2 cm boundary between working
and broken that points at the sphere-overlap math, not the probe
distance. H1 (multi-cell iteration missing) is real but only 3%
of misses, secondary. H2 (probe distance) ruled out.
Next step: line-by-line decomp comparison of FindWalkableInternal /
walkable_hits_sphere / adjust_sphere_to_plane against retail at
acclient_2013_pseudo_c.txt:322032 / :323006 / :326793, then design
the fix in a follow-up session.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Wires the WalkMissDiagnostic aggregator + flag into the two emission
sites per docs/superpowers/specs/2026-05-21-indoor-walk-miss-probe-design.md.
- [walk-miss] (per-frame, MISS branch of TryFindIndoorWalkablePlane):
foot world+local position, nearest walkable poly with XY-containment
flag and vertical gap, and LandCell terrain probe at the same XY.
- [floor-polys] (one-shot per cell at cache time): walkable poly id,
normal Z, local-XY bbox, plane Z at bbox center.
Both gated on ACDREAM_PROBE_WALK_MISS=1. No physics behavior changes.
The live capture at the Holtburg cottage doorway + inn 2nd floor +
cellar descent disambiguates H1 (multi-cell iteration), H2 (probe
distance), H3 (poly absent / walkable_hits_sphere rejection) for
ISSUES #83.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Pure-function aggregator that, given a CellPhysics.Resolved dict and
a foot local position, picks the nearest walkable-eligible polygon
(normal Z >= FloorZ) and reports XY-containment + signed vertical gap.
Also enumerates walkable polys with local-XY bboxes for the one-shot
[floor-polys] cell-load dump.
Pure-function, no behavior change. Wiring to emission sites lands in
the next commit.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds a new diagnostic flag for the indoor-walking walk-miss probe
spike per docs/superpowers/specs/2026-05-21-indoor-walk-miss-probe-design.md.
Env var ACDREAM_PROBE_WALK_MISS=1, runtime-toggleable via property.
No DebugPanel mirror — spike-only. Following commits wire the
[walk-miss] and [floor-polys] emissions to this flag.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Companion to the Bug A wrong-scope handoff (35c266a). Provides the
boxed copy-paste prompt for a fresh session + quick reference for the
user and the helper:
- Branch state + KEEP/REMOVE recommendation
- Anti-patterns to avoid (don't repeat Bug A, validate risks with
probe data, stop at three failed verifications)
- Code anchors for Mechanisms A/B/C in our code
- Retail decomp anchors for the doorway investigation
- Probe + diagnostic env var menu
- 5-scenario visual verification list
- Launch command with UTF-8 conversion step
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Bug B (indoor BSP world-origin fix) shipped today at de8ffde.
Bug A (delete per-frame walkable-plane synthesis) attempted and
reverted at 0a7ce8f. Real bug is deeper than scoped:
Indoor cell floor polys don't cover the player's full XY range when
crossing thresholds (doorways). Step-down probes miss past the floor
edge, Mechanism C (post-OK step-down) can't catch the player,
ContactPlane invalidates, gravity pulls them through the void.
We have all three retail CP retention mechanisms (A, B, C). The
defect is geometry, not retention. Either dat-decoder missing some
floor polys, or cell-transition timing too late, or some retail
mechanism we haven't traced.
Handoff includes:
- State of every commit on this branch + KEEP/REMOVE recommendation
- Bug B evidence and recommendation to ship to main
- Bug A failure analysis with probe data
- Mechanisms A/B/C location in our code vs retail decomp anchors
- 5 prioritized investigation targets for fresh session
- Anti-patterns to avoid (don't repeat Bug A approach)
- Lessons learned (probe-first discipline, risk-as-falsification,
3-fails-in-a-session stop signal, Matrix4x4.Decompose idiom,
binary-timestamp paranoia)
Recommendation: merge Bug B alone, leave the rest for fresh session.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>