Eight-task plan to close A6.P2 Finding 2 (ContactPlane resynthesis
blowup, ~1,470x more CP writes than retail). Strategy: strip the
synthesis path inside Transition.FindEnvCollisions indoor branch +
add per-transition Mechanism B (LKCP restore) so cross-frame CP
retention flows via the existing retail mechanisms instead of
per-frame TryFindIndoorWalkablePlane synthesis.
Plan structure:
T1 — Research note (retail Mechanism B oracle) — mandatory before code.
T2 — Add ContactPlaneWriteCount probe (test instrumentation).
T3 — Write failing IndoorContactPlaneRetentionTests regression.
T4 — Add Mechanism B (LKCP restore) per-transition.
T5 — Strip indoor walkable synthesis from FindEnvCollisions.
T6 — Re-capture scen3 + verify cp-write ratio drops to ≤200.
T7 — Re-capture scen1 + scen5 for full slice 1 sign-off.
T8 — Bookkeeping (findings doc, roadmap, CLAUDE.md).
Out of scope (deferred to slice 2 or A6.P4):
- Mechanism C (frames_stationary_fall flat-CP synthesis); add only
if slice 1 visual verification shows first-frame fall-through.
- Finding 3 (cell-resolver sling-out); independent fix surface.
- TryFindIndoorWalkablePlane definition deletion (A6.P4).
- Issue #95 (visibility blowup; outside A6 scope).
Acceptance: scen3 cp-write ≤ 200 (vs current 86,748); scen1/5 ratio
≤ 10x; visual verification at Holtburg inn 2nd floor passes;
1147+8 baseline maintained.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
35 KiB
A6.P3 Slice 1 — Indoor ContactPlane retention (Finding 2 fix) Implementation Plan
For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (
- [ ]) syntax for tracking.
Goal: Stop acdream's indoor-physics ContactPlane resynthesis blowup (A6.P2 Finding 2 — ~1,470× more CP writes than retail). Strip the per-frame synthesis path inside Transition.FindEnvCollisions indoor branch and rely on the existing Mechanism A (Path-6 land write) + Mechanism B (LKCP restore) for ContactPlane state.
Architecture: Make our indoor branch of FindEnvCollisions match retail's tiny CEnvCell::find_env_collisions (10 lines: just call BSPTREE::find_collisions and return state). The current branch calls TryFindIndoorWalkablePlane (a synthesis workaround) + ValidateWalkable (which writes CP) EVERY frame — that's the blowup. The body-level LKCP-restore already exists in PhysicsEngine.RunTransitionResolve (lines 668-674) and handles the cross-frame retention. We add per-transition Mechanism B (LKCP restore into ci.ContactPlane inside the transition resolver) so the indoor branch can return OK without writing CP and downstream consumers still see a valid CP.
Tech Stack: C# .NET 10, existing physics code in src/AcDream.Core/Physics/. Unit tests use xUnit (already wired). Integration verification uses the A6.P1 cdb probe infrastructure (already shipped).
Spec: docs/superpowers/specs/2026-05-21-phase-a6-indoor-physics-fidelity-design.md §1.2 (hypothesis), §5 (A6.P3 fix surface).
Findings: docs/research/2026-05-21-a6-cdb-capture-findings.md Finding 2.
Retail oracle: docs/research/named-retail/acclient_2013_pseudo_c.txt:
CEnvCell::find_env_collisionsline 309573 — the 10-line indoor branch shape.COLLISIONINFO::set_contact_planeline 271925 — the CP setter.- LKCP restore inside
validate_transitionfamily — line 272565-272582 (restore CP fromlast_known_contact_planewhen sphere is geometrically close). frames_stationary_fallflat-CP synthesis — line 272622+ (Mechanism C; deferred to A6.P3 slice 2).
Out of scope (deferred to A6.P3 slice 2):
- Mechanism C (
frames_stationary_fallcounter + flat CP synthesis after 2+ stationary-falling frames). Add only if slice 1 visual verification shows first-frame fall-through after teleport / cell entry. - Finding 3 (cell-resolver sling-out). Independent fix surface. Separate plan.
- Issue #95 (visibility blowup). Outside A6 scope.
Acceptance for slice 1:
- scen3 re-capture: acdream cp-write count drops from 86,748 to ≤ 200 (≤ retail BP7 + small idle buffer).
- scen1, scen5 re-captures: CP-write ratio drops from 1,000+× to ≤ 10×.
- Visual verification at Holtburg inn 2nd floor: walking feels solid (no falling, no jitter, no fall-through to outdoor terrain).
dotnet build+dotnet testgreen (1147+8 baseline maintained).
File Structure
| Path | Purpose | Change |
|---|---|---|
src/AcDream.Core/Physics/TransitionTypes.cs |
Transition class — indoor BSP branch of FindEnvCollisions is the blowup site (lines 1514-1777). TryFindIndoorWalkablePlane (lines 1294-1380) is the synthesis workaround. |
Modify — strip synthesis from FindEnvCollisions indoor branch; leave TryFindIndoorWalkablePlane definition in place (deleted in A6.P4) |
src/AcDream.Core/Physics/PhysicsEngine.cs |
RunTransitionResolve already has cross-frame LKCP restore (lines 668-674). Verify per-tick Mechanism B (LKCP restore into ci.ContactPlane) is wired or add it. |
Read; modify if needed |
tests/AcDream.Core.Tests/Physics/IndoorContactPlaneRetentionTests.cs |
New regression test asserting CP-write count stays low across multiple FindTransitionalPosition calls on the same flat plane. |
Create |
docs/research/2026-05-21-a6-p3-slice1-retail-mech-b-research.md |
Short research note grounding the fix in retail's exact LKCP-restore pattern. Mandatory before code changes. | Create |
docs/research/2026-05-21-a6-captures/scen1-recap/ etc |
Re-capture directories for verification. | Create dirs (already part of capture protocol) |
Task Decomposition
Task 1: Research note — retail Mechanism B + how FindEnvCollisions returns OK without writing CP
This is non-negotiable. The current synthesis path was added to fix a real bug (fall-through to outdoor terrain at the inn doorway). Removing it without understanding retail's equivalent retention pattern will re-introduce that bug. Read retail's flow and document the exact path before changing code.
Files:
-
Create:
docs/research/2026-05-21-a6-p3-slice1-retail-mech-b-research.md -
Step 1: Read
CEnvCell::find_env_collisionsin retail decomp
Run:
sed -n '309570,309600p' docs/research/named-retail/acclient_2013_pseudo_c.txt
Expected: see the 10-line function (already inspected in plan-write phase). Confirm: returns OK after BSPTREE::find_collisions returns OK; no set_contact_plane call inside find_env_collisions.
- Step 2: Read retail's
validate_transitionLKCP-restore block
Run:
sed -n '272540,272620p' docs/research/named-retail/acclient_2013_pseudo_c.txt
Expected: see the block where last_known_contact_plane_valid != 0 triggers COLLISIONINFO::set_contact_plane(&this->collision_info, &last_known_contact_plane, ...). This is the per-transition restore that closes the gap.
- Step 3: Find which retail function contains the LKCP-restore
Run:
awk 'NR<=272540 && /void __thiscall.*::/ {f=$0; ln=NR} NR>272540 {print ln, f; exit}' docs/research/named-retail/acclient_2013_pseudo_c.txt
Expected: print the most recent function header before line 272540. This identifies whether the restore is inside validate_transition, find_obj_collisions, transitional_insert, or another function.
- Step 4: Find the equivalent in our code
Search for our equivalent of that retail function name. Try grep -n for the C++ method name without the class prefix:
Run:
grep -rn "TransitionalInsert\|TransitionalInsertGround\|FindTransitionalPosition\|ValidateTransition" src/AcDream.Core/Physics/
Expected: identifies the C# method that should contain Mechanism B. Likely Transition.FindTransitionalPosition or Transition.CheckTransition.
- Step 5: Write the research note
# Open and write the file with the answers
Write docs/research/2026-05-21-a6-p3-slice1-retail-mech-b-research.md with these sections:
CEnvCell::find_env_collisionsshape (paste the 10-line function with our line annotations).- Retail Mechanism B location (the function name + line number from Step 3).
- Retail Mechanism B trigger condition (the geometric proximity check at line 272569 —
|dot(global_curr_center, LKCP.N) + LKCP.d| <= radius + 0.0002f). - Our equivalent function (from Step 4).
- Decision: where to add Mechanism B in our code — either inside
Transition.FindEnvCollisionsitself (if our equivalent isn't called per-transition) OR inside the per-transition resolver (if it is). Note the choice. - Risk: first-frame fall-through — what happens when LKCP is invalid AND BSP returns OK on the first frame in a cell (post-teleport or post-cell-cross). Either accept the risk (slice 2 adds Mechanism C) or document a slice-1 mitigation.
- Step 6: Commit the research note
git add docs/research/2026-05-21-a6-p3-slice1-retail-mech-b-research.md
git commit -m "docs(research): A6.P3 slice 1 — retail Mechanism B oracle for CP retention
Pre-fix research note grounding the indoor CP-retention refactor in
retail's exact LKCP-restore pattern (acclient_2013_pseudo_c.txt:272565-272582)
and CEnvCell::find_env_collisions tiny shape (line 309573).
Output of this note drives the per-transition Mechanism B insertion
point selection in Task 4 + the slice-1 acceptance shape.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>"
Task 2: Add CP-write count probe assertion infrastructure
We need a deterministic way to count CP writes from a unit test. Look for an existing counter; if none, add one inside CollisionInfo.SetContactPlane.
Files:
-
Read:
src/AcDream.Core/Physics/TransitionTypes.cs:251-270(theSetContactPlanesetter) -
Modify (if needed):
src/AcDream.Core/Physics/TransitionTypes.cs(add a static counter for tests) -
Test:
tests/AcDream.Core.Tests/Physics/IndoorContactPlaneRetentionTests.cs(created in Task 3) -
Step 1: Read the current
SetContactPlanesetter
sed -n '245,275p' src/AcDream.Core/Physics/TransitionTypes.cs
Expected: see the method signature public void SetContactPlane(Plane plane, uint cellId, bool isWater = false) setting ContactPlane, ContactPlaneCellId, ContactPlaneIsWater, plus updating LKCP fields.
- Step 2: Check if a CP-write counter already exists
grep -rn "ContactPlaneWriteCount\|CpWriteCount\|TotalContactPlaneWrites" src/AcDream.Core/Physics/
Expected: probably no result (counter doesn't exist). If a result appears, use that counter and skip step 3.
- Step 3: Add a test-only counter to
CollisionInfo
In src/AcDream.Core/Physics/TransitionTypes.cs, inside the CollisionInfo class (around line 245), add:
/// <summary>
/// Test-only counter for ContactPlane writes. Incremented by every
/// call to <see cref="SetContactPlane"/>. Used by
/// IndoorContactPlaneRetentionTests to assert that CP retention is
/// working (A6.P3 slice 1, 2026-05-21).
/// </summary>
internal int ContactPlaneWriteCount { get; private set; }
Then modify SetContactPlane (the existing method around line 251) to increment the counter. Find the existing line:
public void SetContactPlane(Plane plane, uint cellId, bool isWater = false)
{
ContactPlane = plane;
Insert before ContactPlane = plane;:
ContactPlaneWriteCount++;
ContactPlane = plane;
- Step 4: Build and verify
dotnet build src/AcDream.Core/AcDream.Core.csproj -c Debug
Expected: Build succeeded. 0 Warning(s). 0 Error(s).
- Step 5: Commit
git add src/AcDream.Core/Physics/TransitionTypes.cs
git commit -m "test(phys): A6.P3 slice 1 — add CollisionInfo.ContactPlaneWriteCount
Internal test-only counter incremented by SetContactPlane. Required
by IndoorContactPlaneRetentionTests to assert CP retention works
post-Finding-2 fix (A6.P2).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>"
Task 3: Write the failing regression test
TDD step. Encode the expected post-fix behavior as a test that FAILS today and will PASS after the fix.
Files:
-
Create:
tests/AcDream.Core.Tests/Physics/IndoorContactPlaneRetentionTests.cs -
Step 1: Identify the test fixture pattern in the existing test project
ls tests/AcDream.Core.Tests/Physics/ | head -20
Look for an existing test file using Transition directly — likely a TransitionTests.cs or BSPQueryTests.cs. Read it to learn the setup pattern (how to construct Transition, SpherePath, CollisionInfo, mock PhysicsEngine).
grep -l "new Transition\|Transition.*FindTransitionalPosition\|Transition.*FindEnvCollisions" tests/AcDream.Core.Tests/Physics/
Expected: one or more files; pick the most-similar test file as the template.
- Step 2: Write the test file
Create tests/AcDream.Core.Tests/Physics/IndoorContactPlaneRetentionTests.cs:
using System.Numerics;
using AcDream.Core.Physics;
using Xunit;
// Plus any using statements identified from the template file in Step 1.
namespace AcDream.Core.Tests.Physics;
/// <summary>
/// A6.P3 slice 1 (2026-05-21). Regression tests for Finding 2:
/// ContactPlane resynthesis blowup. Asserts that running multiple
/// FindEnvCollisions calls on the same indoor flat-floor configuration
/// does NOT cause an unbounded CP-write count.
/// </summary>
public class IndoorContactPlaneRetentionTests
{
[Fact]
public void IndoorFlatFloorWalk_DoesNotResynthesizeContactPlanePerFrame()
{
// Arrange: build a minimal indoor scenario.
// - Create a Transition with a SpherePath positioned inside an
// indoor cell (CellId low 16 bits >= 0x0100).
// - Mock a cell with a flat floor poly at Z=0.
// - Seed the CollisionInfo's ContactPlane + LastKnownContactPlane
// with the floor plane (simulating "we've already touched the floor").
// - Reset ContactPlaneWriteCount to 0 just before the test loop.
//
// Setup the Transition + SpherePath + CollisionInfo per the
// template-test pattern identified in Step 1.
var transition = /* ... build per template ... */;
var ci = transition.CollisionInfo;
// Seed: simulate "already on the floor"
var floorPlane = new System.Numerics.Plane(new Vector3(0, 0, 1), 0f);
ci.SetContactPlane(floorPlane, cellId: 0xA9B40166, isWater: false);
// Reset write counter after seeding.
int seededWrites = ci.ContactPlaneWriteCount;
Assert.Equal(1, seededWrites);
// Act: simulate 60 frames of flat-floor walking by calling
// FindEnvCollisions 60 times with positions that stay on the same
// flat plane (small horizontal deltas, identical Z).
var engine = /* ... mock PhysicsEngine ... */;
for (int frame = 0; frame < 60; frame++)
{
transition.SpherePath.SetCheckPos(
new Position(/* same Z, small XY delta */),
cellId: 0xA9B40166);
var state = transition.FindEnvCollisions(engine);
Assert.Equal(TransitionState.OK, state);
}
// Assert: after 60 frames of identical flat-floor walking, the
// ContactPlane should have been written AT MOST a small constant
// number of additional times (ideally 0; allow a small budget for
// legitimate Mechanism A re-lands by BSP path-6).
int totalWrites = ci.ContactPlaneWriteCount;
int additionalWrites = totalWrites - seededWrites;
// Threshold: 60 frames should produce at most ~5 additional
// CP writes (ratio ≤ 0.1 writes/frame). Today's broken code
// produces ~60-180 (1-3 writes/frame from per-frame synthesis).
Assert.True(additionalWrites <= 5,
$"Expected ≤5 additional CP writes across 60 flat-floor frames, "
+ $"got {additionalWrites}. Finding 2 fix not complete.");
}
}
Note: The exact Transition construction depends on the template test from Step 1. If the template requires concrete CellPhysics + DataCache, replicate that setup. If the construction is too painful (requires mocking many fields), simplify the test by directly calling Transition.FindEnvCollisions and verifying ContactPlaneWriteCount stays low — even if the test setup is artificial, the assertion's job is to prove "we don't write CP on every frame."
- Step 3: Build the test project
dotnet build tests/AcDream.Core.Tests/AcDream.Core.Tests.csproj -c Debug
Expected: build succeeds.
- Step 4: Run the test — expect FAIL
dotnet test tests/AcDream.Core.Tests/AcDream.Core.Tests.csproj --filter "FullyQualifiedName~IndoorContactPlaneRetentionTests" --no-build
Expected: test FAILS with message like "Expected ≤5 additional CP writes ... got 60+." This proves the test correctly captures the bug.
If the test PASSES today (no extra writes), the test setup doesn't exercise the indoor synthesis path. Revisit: either the SpherePath cell isn't in the indoor range (low 16 bits must be ≥ 0x0100), or the engine.DataCache mock isn't returning a cell with a BSP — meaning the indoor branch isn't entered.
- Step 5: Commit the failing test
git add tests/AcDream.Core.Tests/Physics/IndoorContactPlaneRetentionTests.cs
git commit -m "test(phys): A6.P3 slice 1 — failing regression for Finding 2 CP blowup
Test asserts 60 frames of indoor flat-floor walking should produce
≤5 ContactPlane writes. Fails today (broken code: ~60-180 writes).
Will pass after Task 4 + Task 5 strip the synthesis path.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>"
Task 4: Add Mechanism B (LKCP restore) per-transition
Based on Task 1's research note, add the LKCP-restore step in the correct location (per Step 5 of Task 1's research note). The shape should match retail's validate_transition lines 272565-272582 — when LKCP is valid AND the sphere is close to the LKCP plane, restore CP from LKCP.
Files:
-
Modify:
src/AcDream.Core/Physics/TransitionTypes.csORsrc/AcDream.Core/Physics/PhysicsEngine.cs(location determined by Task 1 Step 5) -
Step 1: Re-read Task 1's research note
cat docs/research/2026-05-21-a6-p3-slice1-retail-mech-b-research.md
Locate Section 5: "Decision: where to add Mechanism B in our code."
- Step 2: Read the target function (the file + line range identified in Task 1 Step 5)
Read the function in full to identify the correct insertion point. Mechanism B should fire AFTER the transition's sub-step loop completes with OK_TS but BEFORE the body persist.
- Step 3: Write the Mechanism B insertion
Add at the determined insertion point (replace <INSERTION_POINT> with the exact location from Step 2):
// ── Mechanism B — restore CP from LKCP when geometrically close ──
// A6.P3 slice 1 (2026-05-21). Retail oracle:
// acclient_2013_pseudo_c.txt:272565-272582 (validate_transition's
// LKCP-restore block). When the player is moving across a flat
// indoor floor and FindEnvCollisions returns OK without writing
// a fresh CP (per the now-stripped synthesis path), restore CP
// from LastKnownContactPlane if the sphere is close to the LKCP
// plane geometrically.
if (ci.LastKnownContactPlaneValid && !ci.ContactPlaneValid)
{
var sphereCenter = SpherePath.GlobalCurrCenter[0].Origin;
var lkcp = ci.LastKnownContactPlane;
float distToLKCP = MathF.Abs(
Vector3.Dot(sphereCenter, lkcp.Normal) + lkcp.D);
float threshold = SpherePath.GlobalSphere[0].Radius + 0.0002f;
if (distToLKCP <= threshold)
{
ci.SetContactPlane(
lkcp,
ci.LastKnownContactPlaneCellId,
ci.LastKnownContactPlaneIsWater);
}
}
- Step 4: Build
dotnet build src/AcDream.Core/AcDream.Core.csproj -c Debug
Expected: build succeeds.
- Step 5: Run the full Core test suite — should be 1147+ green
dotnet test tests/AcDream.Core.Tests/AcDream.Core.Tests.csproj --no-build
Expected: still 1147+ pass. Mechanism B by itself should not break anything (it only fires when ContactPlane is invalid but LKCP is valid — a state that's rare in the current code, but harmless if it does happen).
If a previously-green test now fails: the Mechanism B logic is firing where it shouldn't. Inspect the failing test's setup to understand what state assumption it makes, then either narrow the Mechanism B condition or fix the test.
- Step 6: Commit Mechanism B alone
git add src/AcDream.Core/Physics/<file_modified>
git commit -m "feat(phys): A6.P3 slice 1 step 1 — add Mechanism B (LKCP restore)
Restores CollisionInfo.ContactPlane from LastKnownContactPlane when:
- LKCP is valid
- ContactPlane is currently invalid
- sphere is geometrically close to the LKCP plane
(|dot(center, N) + d| <= radius + 0.0002)
Matches retail's validate_transition LKCP-restore at
acclient_2013_pseudo_c.txt:272565-272582. Slice 1 step 1 of the
A6.P3 indoor CP retention fix. Step 2 (Task 5) strips the
TryFindIndoorWalkablePlane synthesis from FindEnvCollisions.
Tests pass: 1147+ green.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>"
Task 5: Strip the synthesis path from FindEnvCollisions indoor branch
The main fix. Match retail's CEnvCell::find_env_collisions tiny shape.
Files:
-
Modify:
src/AcDream.Core/Physics/TransitionTypes.cs:1623-1737(the synthesis block after BSP returns OK) -
Step 1: Re-read the current indoor branch
sed -n '1623,1745p' src/AcDream.Core/Physics/TransitionTypes.cs
Expected: see the block that calls CheckOtherCells, then TryFindIndoorWalkablePlane, then ValidateWalkable on the synthesized plane.
- Step 2: Identify what to KEEP vs DELETE
KEEP:
- The
cellState != TransitionState.OKearly return (lines 1623-1628). Retail's tiny version has this. - The Phase A4
CheckOtherCellscall (lines 1638-1642). This is multi-cell collision iteration — separate concern from CP retention; keep. - All probe diagnostics (
[indoor-bsp],[indoor-walkable]lines). These print only when the env var is set; keep for A6.P3 verification re-captures.
DELETE:
- The
TryFindIndoorWalkablePlanecall (lines 1658-1662). - The
if (walkableHit)block callingValidateWalkable(lines 1727-1737). - The defensive comment about "fall through to outdoor terrain" — replaced by Mechanism B.
- The
[walk-miss]diagnostic block (lines 1682-1725). This diagnostic was tied to the synthesis MISS case; with synthesis stripped, the diagnostic is meaningless. Move out of scope OR delete.
REPLACE with: return TransitionState.OK; immediately after CheckOtherCells returns OK.
- Step 3: Apply the edit
Replace the block (lines 1645-1743 approximately) with:
// ── Indoor walkable handling — A6.P3 slice 1 (2026-05-21) ─
// Retail's CEnvCell::find_env_collisions (decomp
// acclient_2013_pseudo_c.txt:309573) returns OK after
// BSPTREE::find_collisions returns OK — NO call to
// set_contact_plane or any synthesis. ContactPlane is
// either:
// - Already valid from a previous frame's Path-6 land
// write inside BSPQuery.FindCollisions (Mechanism A).
// - Restored from LKCP by the per-transition Mechanism B
// in <function> (see Task 4).
//
// The old TryFindIndoorWalkablePlane synthesis path is
// removed here; the function definition is retained for
// now and is deleted in A6.P4 along with the #90
// workaround.
//
// If subsequent visual verification shows first-frame
// fall-through (LKCP invalid AND no Path-6 land happens
// for a flat-walk-only scenario), A6.P3 slice 2 adds
// Mechanism C (retail's frames_stationary_fall flat-CP
// synthesis at acclient_2013_pseudo_c.txt:272622+).
return TransitionState.OK;
}
}
// ── Outdoor terrain collision ────────────────────────────────────
(Keep everything BEFORE // ── Synthesize indoor walkable contact plane ── and everything AFTER // ── Outdoor terrain collision ──.)
- Step 4: Build
dotnet build src/AcDream.Core/AcDream.Core.csproj -c Debug
Expected: build succeeds. If walkableHit, indoorPlane, indoorVertices, hitPolyId, INDOOR_WALKABLE_PROBE_DISTANCE, WalkMissDiagnostic are referenced by other code OUTSIDE this block, build will fail with "unused variable" warnings — those are now unused and the code outside this block should also not reference them; reach for git grep.
- Step 5: Run the IndoorContactPlaneRetentionTests test — expect PASS now
dotnet test tests/AcDream.Core.Tests/AcDream.Core.Tests.csproj --filter "FullyQualifiedName~IndoorContactPlaneRetentionTests" --no-build
Expected: test PASSES. The 60-frame flat-walk no longer writes CP per-frame because synthesis is gone, and Mechanism B (Task 4) keeps ci.ContactPlane valid via LKCP restore.
If test still fails: Mechanism B isn't firing in the test scenario. Debug by adding a Console.WriteLine inside the Mechanism B block to print whether it fires. Re-run test, inspect output.
- Step 6: Run the FULL test suite — should be 1147+8 green
dotnet test --no-build
Expected: full pass. Tests that touched the old indoor-walkable path (if any) may need updating; investigate failures individually. Common causes:
-
A test that expected
[walk-miss]diagnostic to fire — re-scope or remove. -
A test that called
TryFindIndoorWalkablePlanedirectly — those tests are testing the deleted-in-A6.P4 workaround; remove them or skip. -
Step 7: Commit the strip
git add src/AcDream.Core/Physics/TransitionTypes.cs
# Any other modified test files from Step 6
git commit -m "fix(phys): A6.P3 slice 1 step 2 — strip indoor walkable synthesis
Closes A6.P2 Finding 2 (ContactPlane resynthesis blowup, 250x to ∞x
more CP writes than retail). Indoor branch of Transition.FindEnvCollisions
now matches retail's CEnvCell::find_env_collisions tiny shape (decomp
line 309573): call BSPTREE::find_collisions, return state. No
synthesis, no per-frame ValidateWalkable call, no per-frame
ContactPlane write.
Cross-frame CP retention now flows via:
- Mechanism A: BSPQuery.FindCollisions Path-6 land write (already
present, unchanged).
- Mechanism B: per-transition LKCP restore (added in prior commit).
- PhysicsEngine.RunTransitionResolve body persist (unchanged).
TryFindIndoorWalkablePlane definition retained for now; deleted in
A6.P4 alongside the #90 sphere-overlap workaround.
Verification:
- IndoorContactPlaneRetentionTests now passes.
- Full suite 1147+8 green maintained.
- Re-capture verification deferred to Task 6.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>"
Task 6: Re-capture scen3 and verify CP-write ratio drops
Integration verification. Re-run the A6.P1 capture protocol for scen3 (flat 2nd-floor walk, the cleanest CP-write-blowup signal) and confirm the ratio drops from ~86,748 to ≤200.
Files:
-
Create:
docs/research/2026-05-21-a6-captures/scen3_inn_2nd_floor_postfix/directory -
The acdream.log is the artifact; no code changes in this task.
-
Step 1: Confirm build is green
dotnet build -c Debug 2>&1 | tail -5
Expected: Build succeeded. 0 Warning(s). 0 Error(s).
- Step 2: User confirms retail is running, character on Holtburg inn 2nd floor (via stairs in retail), ready to walk
Interactive coordination — wait for the user to confirm position.
- Step 3: Run cdb capture for retail-postfix (optional — gives a fresh paired baseline)
.\tools\cdb\a6-probe-runner.ps1 -ScenarioTag "scen3_inn_2nd_floor_postfix"
Wait for "a6-probe v4 armed:" in docs/research/2026-05-21-a6-captures/scen3_inn_2nd_floor_postfix/retail.log.
User walks: forward 3 m, sidestep 1 m, walk back. Shuffle a bit at the end. Then close retail to release cdb.
- Step 4: Decode retail.log
py tools/cdb/decode_retail_hex.py docs/research/2026-05-21-a6-captures/scen3_inn_2nd_floor_postfix/retail.log
- Step 5: Launch acdream with probe env vars + walk same scenario
$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_PUSH_BACK = "1"
$env:ACDREAM_PROBE_INDOOR_BSP = "1"
$env:ACDREAM_PROBE_CELL = "1"
$env:ACDREAM_PROBE_CELL_CACHE = "1"
$env:ACDREAM_PROBE_CONTACT_PLANE = "1"
dotnet run --project src\AcDream.App\AcDream.App.csproj --no-build -c Debug 2>&1 |
Out-File -FilePath "docs\research\2026-05-21-a6-captures\scen3_inn_2nd_floor_postfix\acdream.log" -Encoding ASCII
User teleports +Acdream to the inn 2nd floor (via @teleport per the original scen3 capture protocol), walks the same scenario, closes gracefully.
- Step 6: Compare CP-write counts
D="docs/research/2026-05-21-a6-captures/scen3_inn_2nd_floor_postfix"
echo "--- retail BP7 (set_contact_plane) ---"
grep -c "^\[BP7\]" "$D/retail.decoded.log"
echo "--- acdream cp-write ---"
grep -c "^\[cp-write\]" "$D/acdream.log"
echo "--- prefix vs postfix acdream ---"
echo "scen3 prefix: 86,748 cp-writes (committed at 4b5aebc)"
echo "scen3 postfix: $(grep -c "^\[cp-write\]" "$D/acdream.log") cp-writes"
Expected: acdream cp-write count ≤ 200 (the success threshold). If still in the thousands, Finding 2 fix is INCOMPLETE — diagnose:
-
Is Mechanism B firing? Add a probe inside the Mechanism B
ifblock, re-launch, check. -
Is some OTHER write site still firing? Grep
git log -p src/AcDream.Core/Physics/forSetContactPlanecalls not yet audited. -
Step 7: Visual verification with user
User walks +Acdream on the inn 2nd floor for ~30 seconds in various patterns:
- Walk back and forth.
- Stand still for a few seconds.
- Walk into a wall.
- Walk into furniture.
User reports: does the player feel solid (no falling, no jitter, no fall-through to outdoor terrain)?
If user reports a regression: ROLLBACK the commit from Task 5 and either:
-
Add Mechanism C (frames_stationary_fall synthesis) per slice 2, then retry; OR
-
Re-scope: keep TryFindIndoorWalkablePlane gated on
!ci.LastKnownContactPlaneValid(synthesis only on first frame in cell), keep the rest of the fix. -
Step 8: Commit the verification capture
git add docs/research/2026-05-21-a6-captures/scen3_inn_2nd_floor_postfix/
git commit -m "capture(research): A6.P3 slice 1 — scen3 post-fix verification
Re-capture of scen3 (Holtburg inn 2nd floor, flat-floor walk) after
the A6.P3 slice 1 fix. CP-write ratio:
scen3 pre-fix (4b5aebc): retail BP7 = 0, acdream cp-write = 86,748
scen3 post-fix: retail BP7 = N, acdream cp-write = M
[Fill in N + M from Step 6 output.]
Visual verification with user at the inn 2nd floor: [PASS/FAIL/notes].
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>"
Task 7: Re-capture scen1 and scen5 for full slice 1 sign-off
Two more re-captures to ensure the fix generalizes (no regressions in scenarios where retail DID write CP). scen2 stair-fail + scen4 sling-out are intentionally OUT of slice 1 scope (those are Finding 3 territory).
Files:
-
Create:
docs/research/2026-05-21-a6-captures/scen1_inn_doorway_postfix/ -
Create:
docs/research/2026-05-21-a6-captures/scen5_sewer_entry_postfix/ -
Step 1: Re-capture scen1 (doorway walk-through)
Apply the same protocol as Task 6 Steps 2-6, with -ScenarioTag "scen1_inn_doorway_postfix". Walk script: walk forward through inn front door, stop just inside.
Expected: cp-write count drops from 73,304 to a small multiple of retail's BP7 (18 hits) — target ≤ ~200.
- Step 2: Re-capture scen5 (Town Network portal entry)
Apply the same protocol with -ScenarioTag "scen5_sewer_entry_postfix". Walk script: walk to Town Network Portal, enter, walk 2m inside.
Expected: cp-write count drops from 20,956 to a small multiple of retail's BP7 (65 hits) — target ≤ ~500 (portal threshold + indoor hub walking is naturally more CP-active than flat 2nd-floor walking).
- Step 3: Commit both verifications
git add docs/research/2026-05-21-a6-captures/scen1_inn_doorway_postfix/
git add docs/research/2026-05-21-a6-captures/scen5_sewer_entry_postfix/
git commit -m "capture(research): A6.P3 slice 1 — scen1 + scen5 post-fix verification
scen1 pre-fix vs post-fix CP-write ratio:
retail BP7: 18
acdream cp-write: 73,304 -> N (ratio ~4072x -> ~Nx)
scen5 pre-fix vs post-fix CP-write ratio:
retail BP7: 65
acdream cp-write: 20,956 -> M (ratio ~322x -> ~Mx)
[Fill in N + M from re-capture decodes.]
A6.P3 slice 1 acceptance threshold (CP-write ratio ≤ ~10x) met
across all three flat-floor + portal-walk scenarios.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>"
Task 8: Update findings doc + roadmap; mark A6.P3 slice 1 SHIPPED
Bookkeeping. Updates the A6.P2 findings doc with the post-fix data and the roadmap with the slice-1 ship.
Files:
-
Modify:
docs/research/2026-05-21-a6-cdb-capture-findings.md -
Modify:
docs/plans/2026-04-11-roadmap.md -
Modify:
CLAUDE.md(Currently-working-toward block) -
Step 1: Append a "post-fix" section to the findings doc
In docs/research/2026-05-21-a6-cdb-capture-findings.md, add a new section after the existing "Findings" section:
## A6.P3 slice 1 — Finding 2 closed (2026-MM-DD)
[Update date when this lands.]
Strip + Mechanism B fix shipped at commits [list]. Re-capture verification:
| Scenario | Pre-fix cp-write | Post-fix cp-write | Pre-fix ratio | Post-fix ratio |
|---|---:|---:|---:|---:|
| scen1 inn doorway | 73,304 | N | 4,072x | Nx |
| scen3 inn 2nd floor | 86,748 | M | ∞ | Mx |
| scen5 town network | 20,956 | K | 322x | Kx |
[Fill in N, M, K from Task 6 + Task 7 outputs.]
Finding 2 closed. Finding 1 dispatcher entry frequency mismatch
[status — closed as side effect / still wide / TBD per re-capture].
Next: Finding 3 (cell-resolver sling-out from scen4). Separate plan
when A6.P3 slice 2 is scoped.
- Step 2: Update the roadmap A6.P3 entry
In docs/plans/2026-04-11-roadmap.md find the - **A6.P3 — Fix the BSP correction paths** line and update to show slice 1 SHIPPED with the commits.
- Step 3: Update CLAUDE.md Currently-working-toward block
In CLAUDE.md find the M1.5 + A6.P3 block. Update the "Current phase" line to reflect slice 1 ship + next slice (slice 2 = Finding 3 OR slice 2 = Mechanism C if needed).
- Step 4: Commit the bookkeeping
git add docs/research/2026-05-21-a6-cdb-capture-findings.md
git add docs/plans/2026-04-11-roadmap.md
git add CLAUDE.md
git commit -m "docs(roadmap+findings): A6.P3 slice 1 — SHIPPED
CP-write resynthesis blowup (Finding 2) closed. scen1/3/5 re-captures
confirm ratio drop from 250-∞x to ≤10x. Strip-synthesis + Mechanism B
land. TryFindIndoorWalkablePlane retained pending A6.P4 deletion.
Next: assess Finding 1 (dispatcher entry frequency) post-fix; if
still wide, scope as A6.P3 slice 2. Otherwise proceed to Finding 3
(cell-resolver sling-out) as slice 2.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>"
Self-review checklist (for the implementer, before declaring slice 1 done)
- Task 1's research note exists and is committed.
- CollisionInfo.ContactPlaneWriteCount counter exists and is incremented by SetContactPlane.
- IndoorContactPlaneRetentionTests exists, fails pre-fix, passes post-fix.
- Mechanism B (LKCP restore) is wired at the correct per-transition site.
- FindEnvCollisions indoor branch is stripped to match retail's CEnvCell::find_env_collisions shape.
- TryFindIndoorWalkablePlane definition is NOT deleted (deferred to A6.P4).
- Build green; full test suite 1147+8 green.
- scen3 re-capture cp-write count ≤ 200.
- scen1 + scen5 re-capture cp-write ratios ≤ 10x.
- Visual verification at Holtburg inn 2nd floor passes (no fall-through, no jitter).
- Findings doc + roadmap + CLAUDE.md updated.
- If visual verification revealed a regression: rolled back and either added Mechanism C (slice 2 promotion) or applied a narrower gating fix.
If all 12 items check ✅, slice 1 is shipped. Next: either Finding 3 (separate plan) or Mechanism C if first-frame fall-through was reported.