L.2g slice 1 is CODE-COMPLETE: parser + registry mutator + WorldSession dispatcher + GameWindow subscriber (4 commits:2459f28,d538915,536a608,108e386). Build clean, 6 new tests pass, baseline-stable across the full suite. Per-commit + final integration code reviews all approved. Visual verification deferred: while running the Holtburg-doorway test, Phase B.4's outbound Use handler turned out to be unwired. The wire builders (InteractRequests.BuildUse), classes (SelectionState, WorldPicker), input-action enums, and keybindings all exist — but GameWindow.OnInputAction has no case for SelectDblLeft, so the click silently does nothing. The inbound L.2g chain we just landed can't fire until something sends an outbound Use. This commit captures the handoff + reframes next-session work: * docs/research/2026-05-12-l2g-slice1-shipped-handoff.md (NEW) Full evidence: 4 shipped commits, end-to-end code flow, B.4 discovery explanation, 4 minor + 1 Important review notes (the Important one is a test-coverage gap that the B.4b visual test will settle automatically), reproducibility recipe, next-session pick. * CLAUDE.md "Currently in Phase L.2" paragraph: L.2g slice 1 code shipped; visual test deferred to B.4b. Next-phase-candidates list: L.2g slice 1 (now done) replaced with the B.4b candidate pointing at the slice scope. * docs/plans/2026-04-29-movement-collision-conformance.md L.2g section gains a "Current shipped slice (2026-05-12):" table listing the 4 commits. * docs/plans/2026-05-12-milestones.md M1 phase-list updated: L.2g slice 1 (code) shipped; B.4 renamed "B.4 / B.4b" with the gap-discovery note + B.4b shape. * docs/ISSUES.md New issue #57 (HIGH) for the B.4 interaction-handler gap. Promoted to Phase B.4b; will close as DONE (promoted to Phase B.4b) when B.4b's design spec lands. * Memory file project_interaction_pipeline.md (in personal memory dir, not in this commit) updated to reflect reality. Next session: Phase B.4b (~30-50 LOC, 1-2 subagent dispatches, ~30 min). Subscribe SelectDblLeft -> WorldPicker.Pick -> InteractRequests.BuildUse -> _liveSession.SendGameMessage. Same Holtburg-doorway visual test verifies both L.2g slice 1 and B.4b in one pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
11 KiB
L.2g slice 1 shipped — handoff (code-complete; visual test deferred)
Date: 2026-05-12 evening.
Branch: claude/gallant-mestorf-3bf2e3 (ready to merge to main).
Predecessors:
- 2026-05-13-l2d-slice1-shipped-handoff.md — the L.2d trace that identified Door entities as the Holtburg doorway blocker, motivating L.2g.
- docs/superpowers/specs/2026-05-12-l2g-dynamic-physicsstate-design.md — the L.2g design spec (commit
2c10dd4). - docs/superpowers/plans/2026-05-12-phase-l2g-slice1.md — the L.2g slice 1 implementation plan (commit
869677b).
TL;DR
L.2g slice 1 code is complete and unit-tested. The four commits land
the full inbound SetState (0xF74B) pipeline: parser → WorldSession
event → GameWindow handler → ShadowObjectRegistry.UpdatePhysicsState.
After this slice, the existing CollisionExemption.ShouldSkip
short-circuit (cited at acclient_2013_pseudo_c.txt:276782) honors
runtime ETHEREAL flips without any resolver-path edit.
The visual verification at Holtburg's inn doorway is deferred to the
next session. Cause: Phase B.4's outbound Use handler turns out to be
unwired — clicking on a door silently does nothing because no
production code subscribes to the SelectLeft / SelectDblLeft input
actions. Without the outbound Use, the server never sees a "open the
door" request, so the inbound SetState we just ported never fires.
L.2g slice 1 is the inbound half of the round-trip. Phase B.4b (a small ~30-50 LOC slice) is the outbound half. Both halves are required for the M1 demo target "open the inn door." B.4b is the next session's work.
What shipped on this branch
| Commit | Subject |
|---|---|
2459f28 |
feat(phys L.2g slice 1): inbound SetState (0xF74B) parser |
d538915 |
feat(phys L.2g slice 1): ShadowObjectRegistry.UpdatePhysicsState |
536a608 |
feat(phys L.2g slice 1): WorldSession dispatches SetState (0xF74B) + hex probe |
108e386 |
feat(phys L.2g slice 1): GameWindow routes SetState + extends [entity-source] log |
Plus docs/scaffolding earlier in the session:
2c10dd4— L.2g design spec + L.2 plan-of-record + milestones + CLAUDE.md updates.869677b— L.2g slice 1 implementation plan (this doc's companion).
Build: clean. Tests: 6 new tests pass (3 for parser, 3 for registry mutator). Full suite: 1037 pass / 8 pre-existing-baseline fail. No regressions. Per-commit + final integration code reviews all approved.
What the code now does end-to-end
When the server broadcasts a SetState (0xF74B):
- Parse —
WorldSession's dispatcher routes opcode0xF74BintoSetState.TryParse(body), which returnsSetState.Parsed(Guid, PhysicsState, InstanceSequence, StateSequence). - Probe (gated on
ACDREAM_PROBE_BUILDING=1) — one-shot per session, dumps the first message's body bytes as[setstate-hex] body.len=N first-N-bytes: 4B F7 ...for wire-format confidence. - Event —
WorldSession.StateUpdatedfires with the parsed value. - Subscribe —
GameWindow.OnLiveStateUpdated(added to the live- session attach block alongsideOnLiveVectorUpdated) calls_physicsEngine.ShadowObjects.UpdatePhysicsState(parsed.Guid, parsed.PhysicsState). - Mutate —
ShadowObjectRegistry.UpdatePhysicsStatewalks every per-cell list the entity occupies and rewriteslist[i] with { State = newState }. - Per-tick diagnostic (same probe flag) — emits
[setstate] guid=0x... state=0x... instSeq=... stateSeq=...for the greppable trail. - Resolver — next physics tick,
FindObjCollisionscallsCollisionExemption.ShouldSkip(entry.State, entry.Flags, moverState)on the entity. The check is unchanged from L.2d slice 1; it short-circuits when(state & ETHEREAL_PS) != 0 && (state & IGNORE_COLLISIONS_PS) != 0.
Slice 0.5 freebie folded in: all 6 [entity-source] probe-log
sites in GameWindow.cs now emit state=0x{state:X8} flags={flags}
so ETHEREAL flips are greppable end-to-end from spawn through state
change.
Why the visual test is deferred — the B.4 discovery
Before launching the visual test, the user reported that right-click in-client was bound to camera orbit (correctly), and asked whether left-click should open a door. Investigation produced this finding:
| Component | State |
|---|---|
InteractRequests.BuildUse(seq, guid) wire builder |
✅ implemented + tested |
SelectionState, WorldPicker classes |
✅ exist in source |
InputAction.SelectLeft / SelectDblLeft / SelectRight enum |
✅ defined |
KeyBindings: LMB → SelectLeft, LMB-dblclick → SelectDblLeft, RMB → SelectRight |
✅ wired in KeyBindings.cs:300-320 |
GameWindow.OnInputAction switch case for Select* |
❌ missing |
Any production caller of SelectionState, WorldPicker, InteractRequests.BuildUse |
❌ none in src/ |
The diagnostic line [input] SelectLeft Press fires on LMB-click — the
dispatcher knows the action — but nothing downstream listens. The
click silently does nothing. The R hotkey similarly does nothing
because the corresponding UseSelected case is also absent from the
switch.
So the M1 outbound Use path is half-shipped: every component below the handler exists, but the handler that ties them together was never landed (despite a 2026-04-28 memory entry claiming "B.4 shipped"). Phase B.4b is the slice that fixes this.
This is not an L.2g defect. L.2g's code path is correct and unit- tested; it just can't be exercised at runtime until the outbound Use sends a SetState-triggering request to the server.
Open notes from reviews (minor — defer to next polish pass)
The per-commit and final integration code reviews approved every commit. Four observations flagged as Minor that are worth folding into a future polish pass:
SetState.cs"total body size" phrasing diverges fromVectorUpdate.cs. New form:"Total body size: 16 bytes (4-byte opcode + 12-byte payload)". Sibling form:"Total body size after opcode: 32 bytes". The new form is more self-documenting, but the spec asked to align with the sibling. Cosmetic.[setstate-hex]log uses redundantMath.Min(body.Length, 32). Called twice in the same line; could be hoisted to a local. Harmless for a one-shot diagnostic.WorldSession.csuses the fully-qualifiedAcDream.Core.Physics.PhysicsDiagnostics.ProbeBuildingEnabledinstead of addingusing AcDream.Core.Physics;and using the short form. Every other call site inGameWindow.csandBSPQuery.csuses the unqualified form. Style inconsistency.[setstate]diagnostic emits guid + state as hex but instSeq + stateSeq as decimal. Cosmetic.
One Important review note (worth following up explicitly)
The final integration reviewer flagged: the test
UpdatePhysicsState_FlipsEthereal_NextLookupSeesNewBits asserts the
cached state changes to 0x4 but does not verify the chain
through CollisionExemption.ShouldSkip. That short-circuit requires
both ETHEREAL_PS (0x4) AND IGNORE_COLLISIONS_PS (0x10) to be
set simultaneously ((state & 0x4) && (state & 0x10)). A state of
0x4 alone does NOT exempt collision. Per the reviewer, ACE's
PhysicsObj.cs:787-791 may set both bits when doors open (broadcast
value 0x14 or higher) — but this is not verified by the test suite.
The B.4b visual test will settle this definitively: the slice-1
hex-dump probe will capture the real state=0x???????? wire value the
first time a door opens. If ACE sends 0x14 or higher, the existing
chain works as-is. If ACE sends 0x4 only, we need a tiny adjustment
to CollisionExemption.cs (the && would become ||, OR we make the
collision exemption fire on ETHEREAL alone, OR we widen the test).
Action for B.4b session: after the door-open visual test, grep the
launch log for [setstate-hex] and the [setstate] line that fires on
the Use → confirm the state bits ACE actually sends. If 0x4 only,
file a tiny L.2g slice 1b to widen CollisionExemption.ShouldSkip or
the test's assertion.
Next session
Pick: Phase B.4b — finish the outbound Use handler wiring.
Concretely:
- Subscribe
InputAction.SelectDblLeftinGameWindow.OnInputActionswitch. - Build a world ray from current mouse position
(
WorldPicker.BuildRay(mouse, vp, view, proj)). - Pick the closest entity (
WorldPicker.Pick(ray, entities, cache, skipGuid, maxDist)). - Store result in
_selection(SelectionState.Set(guid)). - Call
InteractRequests.BuildUse(seq, guid)+_liveSession.SendGameMessage(body). - Probably also subscribe
InputAction.SelectLeftfor select-without- use (single-click selects; double-click selects + uses). - Optionally subscribe
InputAction.UseSelected(R hotkey) to send Use on the already-selected guid. - Sequence-number management — there's a game-action sequence counter
on
WorldSessionalready used by the outbound chat path; reuse it.
Estimate: 30-50 LOC, 1-2 subagent-driven implementations + reviews, ~30 min.
Once B.4b lands, immediately re-run the Holtburg inn doorway visual
test with ACDREAM_PROBE_BUILDING=1. Both L.2g slice 1 + B.4b are
verified by the same scenario; no separate L.2g visual test needed.
Reproducibility
Same launch recipe as L.2d slice 1 (see CLAUDE.md "Running the client against the live server"). For visual test once B.4b lands:
$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 run --project src\AcDream.App\AcDream.App.csproj -c Debug 2>&1 |
Tee-Object -FilePath "launch-l2g+b4b.log"
Then walk into the Holtburg inn doorway, double-left-click the door, wait for the swing animation, walk through. After 30s, watch the auto-close fire.
After closing the client:
Select-String -Path launch-l2g+b4b.log -Pattern "setstate-hex|setstate.*guid|entity-source.*Door|input.*SelectDblLeft"
Expected matches:
- One
[setstate-hex] body.len=16 ...line (confirms holtburger's 12-byte payload). - One
[entity-source] name=Door ... state=0x00000000 flags=None ...at spawn. - An
[input] SelectDblLeft Presswhen you double-click. - A
[setstate] guid=0x000F... state=0x????????after the door opens. - A second
[setstate] guid=0x000F... state=0x00000000~30s later when auto-close fires.
Worktree state at handoff
- Branch
claude/gallant-mestorf-3bf2e3ready to merge to main. - 6 commits ahead of main:
2c10dd4(spec + docs),869677b(plan),2459f28/d538915/536a608/108e386(L.2g slice 1 code). - One launch.log artifact (
launch-l2g-slice1.log) in the working tree from the attempted visual test — not committed (gitignored or transient). Safe to discard; B.4b will produce a fresh log.
User wants to start a fresh session for B.4b.