acdream/docs/superpowers/plans/2026-05-21-cylinder-fallback-dedup.md
Erik 5f2b545979 fix(physics): skip mesh-AABB-fallback cylinder for landblock stabs
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>
2026-05-20 11:42:13 +02:00

7.8 KiB

Cylinder fallback dedupe (Phase A1) — 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: Skip the mesh-AABB-fallback cylinder registration for landblock stabs (entity.Id & 0xFF000000u == 0xC0000000u). Eliminates "thin air" collisions inside cottages and around building exteriors.

Architecture: One-line bool definition near existing _isOutdoorMesh declaration + one additional clause in the existing if gate at the fallback's entry. No new files, no math changes, no new tests beyond visual verification.

Tech Stack: C# .NET 10. Single source-file change.

Spec: docs/superpowers/specs/2026-05-21-cylinder-fallback-dedup-design.md


File Structure

File Action Responsibility
src/AcDream.App/Rendering/GameWindow.cs Modify Add _isLandblockStab flag + AND it into the mesh-AABB-fallback gate. ~3 lines added.

No new tests. The fix is verified by a live capture (no [entity-source] mesh-aabb-fallback emissions for 0xC0xxxxxx entities) + visual verification (no thin-air collisions inside cottages).


Task 1: Gate mesh-AABB-fallback on "not a landblock stab"

Files:

  • Modify: src/AcDream.App/Rendering/GameWindow.cs

    • Insertion site 1: immediately after the existing _isOutdoorMesh / _isScenery declarations at lines 5826-5829.
    • Insertion site 2: add a clause to the existing if at line 6050-6052 (the gate around the mesh-AABB-fallback path).
  • Step 1: Add _isLandblockStab flag near _isOutdoorMesh

Find this code at lines 5825-5830 in GameWindow.cs:

            uint _srcPrefix = entity.SourceGfxObjOrSetupId & 0xFF000000u;
            bool _isOutdoorMesh = ((entity.Id & 0x80000000u) != 0)  // scenery
                || ((entity.Id < 0x40000000u)                        // stab
                    && (_srcPrefix == 0x01000000u || _srcPrefix == 0x02000000u));
            bool _isScenery = _isOutdoorMesh;
            if (_isScenery) scTried++;

Insert a new line immediately after bool _isScenery = _isOutdoorMesh; (but before the if (_isScenery) scTried++;), so the block becomes:

            uint _srcPrefix = entity.SourceGfxObjOrSetupId & 0xFF000000u;
            bool _isOutdoorMesh = ((entity.Id & 0x80000000u) != 0)  // scenery
                || ((entity.Id < 0x40000000u)                        // stab
                    && (_srcPrefix == 0x01000000u || _srcPrefix == 0x02000000u));
            bool _isScenery = _isOutdoorMesh;
            // ISSUES #83 / Phase A1 (2026-05-21): landblock stabs
            // (LandBlockInfo.Objects + Buildings) use entity.Id with the
            // 0xC0XXYY00+n layout per LandblockLoader.cs:55. Their BSP
            // collision covers the whole structure; the mesh-AABB-fallback
            // path below is for canopy-only-BSP procedural scenery
            // (0x80XXYY00+n) and produces a redundant 1.5m-clamped
            // invisible disc at the stab's mesh origin — the user-reported
            // "thin air" collision inside cottages. Gate the fallback to
            // exclude stabs. Spec:
            // docs/superpowers/specs/2026-05-21-cylinder-fallback-dedup-design.md.
            bool _isLandblockStab = (entity.Id & 0xFF000000u) == 0xC0000000u;
            if (_isScenery) scTried++;
  • Step 2: AND !_isLandblockStab into the existing fallback gate

Find this code at lines 6050-6052:

            if (!isPhantomSetup
                && (_isOutdoorMesh || (entityBsp == 0 && entityCyl == 0))
                && entity.MeshRefs.Count > 0)

Modify it to:

            if (!isPhantomSetup
                && !_isLandblockStab
                && (_isOutdoorMesh || (entityBsp == 0 && entityCyl == 0))
                && entity.MeshRefs.Count > 0)

(One new line, && !_isLandblockStab, inserted as the second clause.)

  • Step 3: Build the project

Run: dotnet build

Expected: PASS — no compile errors. No warnings other than the pre-existing baseline.

  • Step 4: Run the full Core test suite

Run: dotnet test tests/AcDream.Core.Tests/AcDream.Core.Tests.csproj

Expected: PASS — same 8 pre-existing failures as baseline, no new failures, no new passes.

  • Step 5: Commit
git add src/AcDream.App/Rendering/GameWindow.cs
git commit -m "$(cat <<'EOF'
fix(physics): skip mesh-AABB-fallback cylinder for landblock stabs

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>
EOF
)"

Verification (manual, post-commit)

After the commit lands, re-run the thin-air capture:

$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_BUILDING       = "1"
$env:ACDREAM_PROBE_RESOLVE        = "1"
dotnet build -c Debug
dotnet run --project src\AcDream.App\AcDream.App.csproj --no-build -c Debug 2>&1 |
    Tee-Object -FilePath "launch-a1-verify.log"

User walks inside a Holtburg cottage and around outside. Expected:

  1. Visual: can walk freely inside the cottage room — no invisible wall in the middle of the room.
  2. Log: convert to UTF-8 and check:
Get-Content launch-a1-verify.log -Encoding Unicode |
    Out-File launch-a1-verify.utf8.log -Encoding utf8
Get-Content launch-a1-verify.utf8.log |
    Where-Object { $_ -match '\[entity-source\].*note=mesh-aabb-fallback.*entityId=0xC0' } |
    Measure-Object | Select-Object -ExpandProperty Count

Expected: 0 (no 0xC0 entities now register a mesh-AABB-fallback).

Also expected: no [resolve] hits attribute to 0xC0XXYY79-style cylinder IDs.


Self-review checklist

  • Spec coverage: spec components 1+2 (the bool + the gate clause) both implemented in Task 1 steps 1+2.
  • No placeholders: every step has the exact line numbers and code blocks.
  • Type consistency: _isLandblockStab is bool, referenced in the gate as !_isLandblockStab. Consistent.
  • Atomic commit: one commit, ~3 lines added, single file.
  • Acceptance: visual verification (no thin-air collision) + grep verification (no 0xC0 fallback registrations).
  • No new tests: justified — the fix is in GameWindow.cs (app layer), which doesn't have natural unit tests, and the behavior is verified by live capture per the spec.