# 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.
