acdream/docs/research/2026-05-13-l2d-slice1-shipped-handoff.md
Erik 34b7f1faa1 docs(phys L.2d): slice 1 + 1.5 shipped handoff + 3rd plan-of-record reframe
L.2d as scoped is essentially closed at the Holtburg site. The slice-1.5
trace settled the question: the "I can't walk through doorways" symptom
is a closed Door entity (Setup 0x020019FF named "Door") at the building
threshold, not a building-BSP-collision issue. Building BSP is healthy.

The two prior framings turned out wrong:
- L.2a handoff (2026-05-12): "per-cell walkability missing" — based on
  hit attribution pointing at the building, missed the Door cylinder
  also colliding per tick.
- L.2d slice 1 spec (2026-05-13 morning): "BSP shape fidelity, three
  hypotheses X/Y/Z" — ruled out by the trace once the probe labeling
  bug was fixed in slice 1.5.

Handoff doc captures full evidence, side findings (building double-
registration latent bug, missing PhysicsState in entity-source log),
and a candidates list for the next-session ordering discussion.

Plan-of-record L.2d sub-direction paragraph updated to match: "watch-
and-wait" mode, no more slices until a new shape-fidelity bug is
observed at a different site. Door-state handling becomes its own
sub-phase, scope deferred to project-ordering discussion.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 19:46:45 +02:00

10 KiB

L.2d slice 1 + 1.5 shipped — handoff

Date: 2026-05-13 evening, immediately after slice 1.5 + Holtburg verification. Branch: claude/sharp-chatelet-023dda (ready to merge to main). Predecessor: 2026-05-12-l2a-shipped-l2d-handoff.md.


TL;DR

The "I can't walk through Holtburg doorways" symptom is a closed Door entity blocking the threshold, not a building-collision-mesh bug. Building BSP collision is healthy. The L.2a handoff's framing ("per-cell walkability missing") was wrong, the L.2d-slice-1 spec's reframe ("BSP shape fidelity, three hypotheses X/Y/Z") was also wrong, and the actual answer fell out of one capture once the probe labeling was fixed (slice 1.5). L.2d as scoped is essentially closed. The remaining work is door-state handling — a different sub-phase entirely.


What shipped on this branch

Commit What
92cd723 docs(phys L.2d): design spec for slice 1 BSP-hit diagnostic + L.2d reframe
66dc23e feat(phys L.2d slice 1): BSP-hit diagnostic probe + plan-of-record correction
8bacef0 fix(phys L.2d slice 1.5): probe captures hit poly under StepSphereUp recursion

What slice 1 + 1.5 give the next agent:

  • ACDREAM_PROBE_BUILDING=1 env var + DebugPanel checkbox: one multi-line [resolve-bldg] entry per attributed BSP shadow-entry hit (partIdx, hasPhys, bspR vs vAabbR, world-space entOrigin_lb, actual hit polygon vertices in both local and world coords). Reliable under StepSphereUp recursion after the slice 1.5 fix.
  • [entity-source] one-time log line per ShadowObjects.Register call, gated on the same flag. Makes entityId=0xA9B479 in a probe line greppable to its WorldEntity source.
  • PhysicsDiagnostics.LastBspHitPoly — diagnostic side-channel for any future "what poly did BSPQuery hit" question.
  • The two synthetic tests in PhysicsDiagnosticsTests.cs pin the side-channel API contract.

What the trace actually showed

After slice 1.5, walking acdream into a Holtburg town doorway captured 242 real BSP hit polys + 122 cylinder n/a. Definitive finding:

live: spawn guid=0x7A9B4015 name="Door" setup=0x020019FF
      pos=(132.6,17.1,94.1)@0xA9B40029 itemType=0x00000080
[entity-source] id=0x000F4244 entityId=0x000F4244 src=0x020019FF
      gfxObj=0x020019FF lb=0xA9B40029 type=Cylinder note=server-spawn-root

The blocker is a Door entity — Setup 0x020019FF named "Door" — server-spawned by ACE at the threshold of each Holtburg town building. Five Doors appear across Holtburg (landblock cells 0xA9B40029, 0xA9B40154, 0xA9B40155); same Setup DID reused. ItemType 0x00000080 = Misc category in AC's ItemType flags.

Each Door's Cylinder collision blocks the player. The building BSP also fires (the L.2a evidence the original handoff pointed at), but the BSP hits were the player already pushed back by the Door cylinder then grazing the doorframe — they look like wall collision but are a side effect of the Door cylinder push. Slice 1.5's per-tick multi-entity probe revealed this by showing nObj=3 on every hit resolve: one Door + two sphere checks against the building BSP.

The L.2a slice 2 handoff's expectation that doors would be in the 0xCC0Cxxxx range was wrong; doors are in 0x000Fxxxx (server- spawn-root range) because they're hydrated through the live CreateObject stream like NPCs, not the static landblock pipeline.


What this means for L.2d

L.2d as originally scoped ("Shape Fidelity: Sphere / CylSphere / Building Objects") is essentially closed at this site:

  • Building BSP is loaded, parsed, queried correctly. bspR=13.99m for GfxObj 0x01000A2B, real triangles in real positions.
  • Setup.CylSpheres for Door (0x020019FF) is also loaded correctly — the cylinder is firing the cylinder collision path with sensible world-space radius.
  • No actual shape-fidelity bug observed at this test site.

The remaining work is door state handling, which is a different class of problem entirely — it touches network (CreateObject PhysicsState bits), interaction (Use action on door entity), animation (door open/close animation state), and collision-state-toggle (ETHEREAL during open animation). That doesn't fit under L.2d's shape-fidelity umbrella.

Recommend reframing L.2d as "watch-and-wait": keep the probes for future shape-fidelity work at other sites (dungeon walls, stairs, roofs), but don't plan more slices until a NEW shape-fidelity bug is observed with the probe-armed client.


Side findings (latent bugs to file, not block this slice)

1. Building double-registration

The trace shows the same WorldEntity registered TWICE in ShadowObjectRegistry:

[entity-source] id=0xA9B47900 entityId=0xC0A9B479 ... type=BSP note=partIdx=0 hasPhys=true
[entity-source] id=0xC0A9B479 entityId=0xC0A9B479 ... type=Cylinder note=mesh-aabb-fallback

GameWindow.cs:5625 gates the mesh-AABB-fallback on entityBsp == 0, but the BSP registration at line 5530 DOES increment entityBsp. So the fallback shouldn't fire when BSP parts exist. Either entityBsp isn't being checked in the right scope, or there's a second mesh-AABB-fallback site that doesn't gate on entityBsp. Worth a short investigation + one-line fix.

Filing as ISSUE candidate. Doesn't break anything observable yet (cylinder is too far from player to fire at this Holtburg site), but will cause confusion in any future "why does entity X have two ShadowEntries" trace.

2. PhysicsState / EntityCollisionFlags not in entity-source log

The slice 1 [entity-source] log captures id, entityId, src, gfxObj, lb, type, note, hasPhys but not state (PhysicsState bits) or flags (EntityCollisionFlags). For any future ethereal-handling / IGNORE_COLLISIONS work — including the door state handling above — these would be required.

Tiny slice 1.6 if the next agent needs them: add state=0x{...:X8} flags={...} to the format string. ~5 LOC, gated on the same ProbeBuilding flag.


What the next session probably should NOT do

  • Re-investigate Holtburg doorways with the same setup. The evidence is conclusive; we're not going to find new information by re-running the probe at the same site.
  • Port CBuildingObj or per-cell walkability infrastructure. That was based on the original (wrong) hypothesis. ACE's find_building_collisions is six lines and doesn't use per-cell walkability; our equivalent is already in place implicitly.
  • Start L.2d slice 2 as scoped in the design spec. Hypotheses X / Y / Z don't apply — the trace ruled them all out. Update or close the spec.

What the next session COULD do (in rough preference order)

These are NOT prescribed; they're candidates for the project-level ordering discussion the user wants to have.

  1. Door state handling sub-phase. New phase (call it L.2g or nest under B.4). Touches: Use action → server door toggle, PhysicsState ETHEREAL bit honor, door open/close animation, collision-shape suppression during open animation. Probably 2-3 commits.

  2. Fix the building double-registration latent bug (side finding #1). One-liner, no real impact today but cleaner trace later.

  3. Capture slice 1.6 (state + flags in entity-source log) if any future ethereal-related work is on the immediate horizon. Otherwise defer.

  4. Move to a different L.2 sub-phase entirely — L.2e (cell ownership / find_cell_list / outdoor seam updates) or L.2f (real-DAT + retail-observer conformance). Both are scoped in the L.2 plan-of-record.

  5. Triage the 8 pre-existing test failures that have shadowed the last few sessions. Some are in physics modules that L.2d slice 2 (if it ever happens) would touch — fixing them first gives a cleaner baseline.

  6. Pick from CLAUDE.md's "Next phase candidates" list — non-L.2 work like Phase C visual fidelity, N.6 slice 2, or perf tiers 2/3. The session-level "I don't know what to do" feeling is often easier to resolve by shipping something in a different area for a session.


Reproducibility

Same recipe as L.2a + L.2d slice 1:

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

Walk acdream toward any Holtburg building threshold. Hit Ctrl+F2 to toggle collision wireframes — you'll see the Door cylinder right at the threshold. The name="Door" line appears in the log at startup during the CreateObject stream replay.


Open questions / unresolved

  • What PhysicsState bits is ACE sending for the Door entity? Not captured in current logs. Slice 1.6 would answer this.
  • Are these doors supposed to be open by default in retail? If yes, ACE config issue. If no, retail clients see the same blocker and players had to open them manually.
  • What does ACE's door-state state machine look like? Probably documented in references/ACE/Source/ACE.Server/Entity/Door.cs or similar.

These are doors-and-ACE-side questions; defer to the door-state sub-phase when (if) it gets scoped.


Worktree state at handoff

  • All three slice 1 / 1.5 commits ready to merge to main.
  • WorldBuilder submodule initialized + 6 directory junctions in place for the gitignored peer reference dirs (created during slice 1 prep). Worktree builds clean.
  • Three test artifacts (launch-l2d-slice1.log, launch-l2d-slice1b.log, launch-l2d-slice1c.log) are in working tree but not committed — they're large and ephemeral. Delete or preserve at the merge author's discretion.