# 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](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](../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](../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)`: 1. **Parse** — `WorldSession`'s dispatcher routes opcode `0xF74B` into `SetState.TryParse(body)`, which returns `SetState.Parsed(Guid, PhysicsState, InstanceSequence, StateSequence)`. 2. **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. 3. **Event** — `WorldSession.StateUpdated` fires with the parsed value. 4. **Subscribe** — `GameWindow.OnLiveStateUpdated` (added to the live- session attach block alongside `OnLiveVectorUpdated`) calls `_physicsEngine.ShadowObjects.UpdatePhysicsState(parsed.Guid, parsed.PhysicsState)`. 5. **Mutate** — `ShadowObjectRegistry.UpdatePhysicsState` walks every per-cell list the entity occupies and rewrites `list[i] with { State = newState }`. 6. **Per-tick diagnostic** (same probe flag) — emits `[setstate] guid=0x... state=0x... instSeq=... stateSeq=...` for the greppable trail. 7. **Resolver** — next physics tick, `FindObjCollisions` calls `CollisionExemption.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: 1. **`SetState.cs` "total body size" phrasing diverges from `VectorUpdate.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. 2. **`[setstate-hex]` log uses redundant `Math.Min(body.Length, 32)`.** Called twice in the same line; could be hoisted to a local. Harmless for a one-shot diagnostic. 3. **`WorldSession.cs` uses the fully-qualified `AcDream.Core.Physics.PhysicsDiagnostics.ProbeBuildingEnabled`** instead of adding `using AcDream.Core.Physics;` and using the short form. Every other call site in `GameWindow.cs` and `BSPQuery.cs` uses the unqualified form. Style inconsistency. 4. **`[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.SelectDblLeft` in `GameWindow.OnInputAction` switch. - 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.SelectLeft` for 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 `WorldSession` already 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: ```powershell $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: ```powershell 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 Press` when 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-3bf2e3` ready 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.