Final whole-branch code review (Opus) surfaced two Important post-merge follow-ups + a one-word inaccuracy in the handoff doc: - #59: tighten WorldPicker per-entity Setup.Radius (M1-deferred; the ServerGuid==0 invariant is load-bearing and worth documenting before L.2d's CBuildingObj port lands). - #60: port retail's obstruction_ethereal downstream path so combat-HUD contact reporting works for ethereal creatures (M2-combat). - handoff: corrected "Added a _entitiesByServerGuid reverse-lookup" to "Used the pre-existing _entitiesByServerGuid" — the dict has existed since Phase 6.6/6.7; slice 1c used it, didn't add it. Review verdict: branch ready to merge to main. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
417 lines
20 KiB
Markdown
417 lines
20 KiB
Markdown
# Phase B.4b shipped — handoff (visual-verified 2026-05-13)
|
|
|
|
**Date:** 2026-05-13.
|
|
**Branch:** `claude/compassionate-wilson-23ff99` (ready to merge to main; do NOT merge here — controller handles that after code review).
|
|
**Predecessors:**
|
|
- [docs/research/2026-05-12-l2g-slice1-shipped-handoff.md](2026-05-12-l2g-slice1-shipped-handoff.md) — L.2g slice 1 ship handoff that discovered the B.4 handler gap and deferred the Holtburg visual test to B.4b.
|
|
- [docs/superpowers/specs/2026-05-13-phase-b4b-design.md](../superpowers/specs/2026-05-13-phase-b4b-design.md) — B.4b design spec.
|
|
- [docs/superpowers/plans/2026-05-13-phase-b4b-plan.md](../superpowers/plans/2026-05-13-phase-b4b-plan.md) — B.4b implementation plan (6 tasks; Tasks 1-4 per plan + 2 bonus sets beyond the plan).
|
|
|
|
---
|
|
|
|
## TL;DR
|
|
|
|
Phase B.4b **shipped end-to-end and is visual-verified 2026-05-13.** The M1
|
|
demo target *"open the inn door"* is met. 9 commits on this branch implement
|
|
and fix the complete round-trip: double-click door → `WorldPicker.Pick` →
|
|
`InteractRequests.BuildUse` → ACE broadcasts `SetState (0xF74B)` with
|
|
`ETHEREAL` bit → `ShadowObjectRegistry.UpdatePhysicsState` (L.2g slice 1)
|
|
mutates cached state → `CollisionExemption.ShouldSkip` exempts the door →
|
|
player walks through.
|
|
|
|
The plan estimated "30-50 LOC, 1-2 subagent dispatches, ~30 min."
|
|
Visual testing surfaced **four bonus discoveries** beyond the plan's
|
|
Tasks 1-4:
|
|
|
|
1. `InputDispatcher` had no double-click detection (the `SelectDblLeft`
|
|
binding was dead code — the dispatcher never produced `DoubleClick`
|
|
activations).
|
|
2. `OnInputAction`'s early-return gate discarded `DoubleClick` activations
|
|
before the switch reached the `SelectDblLeft` case.
|
|
3. L.2g `CollisionExemption.ShouldSkip` required **both** `ETHEREAL` +
|
|
`IGNORE_COLLISIONS` bits, but ACE's `Door.Open()` sends only `ETHEREAL`
|
|
(`state=0x0001000C`).
|
|
4. `OnLiveStateUpdated` passed a server GUID to `ShadowObjectRegistry` which
|
|
is keyed by local entity ID — the registry lookup always missed → no-op
|
|
→ the door never became passable. **This was the actual blocker the user
|
|
reported.**
|
|
|
|
Fixes 1-4 were shipped as bonus commits 5-9 beyond the plan's Tasks 1-4.
|
|
L.2g slice 1 and B.4b are now both fully verified by the same visual test.
|
|
Issue #57 is closed. Issue #58 (door swing animation) is filed as M1-deferred
|
|
polish.
|
|
|
|
---
|
|
|
|
## What shipped on this branch
|
|
|
|
| # | Commit | Subject | Task |
|
|
|---|---|---|---|
|
|
| 1 | `f0b3bd9` | `feat(B.4b): WorldPicker.BuildRay — mouse-to-world ray unprojection` | Task 1 |
|
|
| 2 | `221b641` | `feat(B.4b): WorldPicker.Pick — ray-sphere entity pick` | Task 2 |
|
|
| 3 | `5821bdc` | `fix(B.4b): WorldPicker.Pick — handle inside-sphere origin + document normalize contract` | Task 2 review fix |
|
|
| 4 | `7b4aff2` | `refactor(B.4b): unify _selectedTargetGuid -> _selectedGuid` | Task 3 |
|
|
| 5 | `89d82e1` | `feat(B.4b): GameWindow wires Select/Use handlers via WorldPicker` | Task 4 |
|
|
| 6 | `242ce70` | `feat(B.4b): InputDispatcher detects double-clicks` | Bonus: Task 4b |
|
|
| 7 | `58b95bc` | `fix(B.4b): let DoubleClick activation pass the OnInputAction gate` | Bonus: Task 4c |
|
|
| 8 | `a6e4b57` | `fix(phys L.2g slice 1b): widen CollisionExemption to ETHEREAL alone` | L.2g slice 1b |
|
|
| 9 | `08be296` | `fix(phys L.2g slice 1c): translate ServerGuid -> entity.Id for ShadowObjectRegistry` | L.2g slice 1c |
|
|
|
|
Plus plan/spec commits earlier in the branch session:
|
|
- `4a1c594` — B.4b design spec.
|
|
- `ffa404d` — corrected file paths in spec (WorldPicker is in `AcDream.Core.Selection`, not `AcDream.App/Rendering`).
|
|
- `179e441` — B.4b implementation plan (6 tasks).
|
|
|
|
**Build:** clean. **Tests:** 4 new double-click detection tests (commit `242ce70`, all pass). Full suite: builds green, no regressions. L.2g slice 1's 6 tests continue to pass.
|
|
|
|
---
|
|
|
|
## What the code does end-to-end
|
|
|
|
When the user double-left-clicks a door entity in the Holtburg inn doorway,
|
|
the following chain fires:
|
|
|
|
1. **Double-click detection** — `InputDispatcher.OnMouseDown` checks the
|
|
elapsed time since the previous `MouseLeft` press. If ≤500ms, the
|
|
activation kind is `DoubleClick`; otherwise `Press`. This is new as
|
|
of commit `242ce70`; prior to this the `SelectDblLeft` binding was dead
|
|
code (the dispatcher never produced `DoubleClick` activations).
|
|
|
|
2. **Action dispatch** — `InputDispatcher` resolves the chord
|
|
`[MouseLeft, DoubleClick]` → `InputAction.SelectDblLeft` + activation
|
|
`DoubleClick`. The multicast `InputAction` event fires, logged as:
|
|
`[input] SelectDblLeft DoubleClick`.
|
|
|
|
3. **OnInputAction gate** — `GameWindow.OnInputAction` receives the event.
|
|
Prior to commit `58b95bc`, an early-return guard (`if (activation != Press) return;`)
|
|
discarded all `DoubleClick` events. The fix widens the gate to
|
|
`if (activation != Press && activation != DoubleClick) return;`.
|
|
The switch now reaches the `SelectDblLeft` case.
|
|
|
|
4. **Ray construction** — `WorldPicker.BuildRay(mousePos, viewport, viewMatrix, projMatrix)`
|
|
unprojects the cursor pixel into a world-space ray origin + direction,
|
|
using standard NDC→view→world unprojection. Numerically: the mouse pixel
|
|
is mapped to `[-1,+1]` NDC, transformed through `inverse(proj)` to get
|
|
a view-space direction, then through `inverse(view)` for world-space.
|
|
|
|
5. **Entity pick** — `WorldPicker.Pick(ray, entities, maxDist=50m)` iterates
|
|
all entities in `_gpuWorldState.GetAllEntities()`, tests each against a
|
|
ray-sphere intersection with the entity's bounding radius, and returns
|
|
the closest hit. A special-case inside-sphere origin guard (commit `5821bdc`)
|
|
ensures the pick works even when the cursor origin is already inside an
|
|
entity's bounding sphere (common for large portals or doors at close range).
|
|
`[B.4b] pick guid=0x7A9B4015 name=Door` logged on hit.
|
|
|
|
6. **Use message** — `GameWindow` stores `_selectedGuid = picked.Guid` and
|
|
calls `InteractRequests.BuildUse(seq, guid)`. The resulting `0xF7B1 / 0x0036`
|
|
game message is sent to ACE via `_liveSession.SendGameMessage(body)`.
|
|
`[B.4b] use guid=0x7A9B4015 seq=N` logged.
|
|
|
|
7. **ACE processes the Use** — ACE's `Door.Open()` flips the door's physics
|
|
flags to `ETHEREAL | ...` and broadcasts `SetState (0xF74B)` with the
|
|
new state value.
|
|
|
|
8. **SetState arrives** — `WorldSession.OnSetState` parses the 12-byte
|
|
payload (Guid + PhysicsState + InstanceSeq + StateSeq) and fires
|
|
`WorldSession.StateUpdated`. `GameWindow.OnLiveStateUpdated` handles it.
|
|
**New as of commit `08be296` (slice 1c):** the handler translates
|
|
`parsed.Guid` (server GUID `0x7A9B4015`) to `entity.Id` (local entity ID
|
|
`0x000F4245`) via `_entitiesByServerGuid` before calling
|
|
`ShadowObjectRegistry.UpdatePhysicsState`. Without this translation the
|
|
registry lookup always returned "not found" — a silent no-op.
|
|
Log: `[setstate] guid=0x7A9B4015 entityId=0x000F4245 state=0x0001000C`.
|
|
|
|
9. **Collision exemption** — next physics tick, `FindObjCollisions` calls
|
|
`CollisionExemption.ShouldSkip(entry.State, entry.Flags, moverState)`.
|
|
**New as of commit `a6e4b57` (slice 1b):** the check fires on
|
|
`(state & ETHEREAL_PS) != 0` alone (widened from the original `ETHEREAL &&
|
|
IGNORE_COLLISIONS` conjunction). Because ACE broadcasts only `ETHEREAL`
|
|
in the low bits (`state=0x0001000C`), the original conjunction never fired;
|
|
the door stayed solid.
|
|
|
|
10. **Player walks through** — the resolver produces no wall-contact response
|
|
for the door's collision geometry. User confirms: "Now I can walk through."
|
|
|
|
### Observed log evidence
|
|
|
|
```
|
|
[input] SelectDblLeft DoubleClick
|
|
[B.4b] pick guid=0x7A9B4015 name=Door
|
|
[B.4b] use guid=0x7A9B4015 seq=N
|
|
[setstate] guid=0x7A9B4015 entityId=0x000F4245 state=0x0001000C
|
|
```
|
|
|
|
Player walks through the closed door after the `setstate` line.
|
|
|
|
---
|
|
|
|
## The four bonus discoveries
|
|
|
|
### 1. InputDispatcher had no double-click detection (`242ce70`)
|
|
|
|
**Root cause:** `InputDispatcher.OnMouseDown` only looked up `Press` and
|
|
`Hold` activations in the binding table. The `SelectDblLeft` binding was
|
|
wired to the chord `[MouseLeft, DoubleClick]` in `KeyBindings.cs:300-320`
|
|
(shipped in B.4, 2026-04-28), but the dispatcher's mouse-down handler
|
|
never set activation to `DoubleClick` — it always produced `Press`.
|
|
So `SelectDblLeft` was literally unreachable: the chord required
|
|
`DoubleClick` to match, but the dispatcher never generated it.
|
|
|
|
**Fix:** Added a `_lastMouseDownTime` (and `_lastMouseDownButton`) tracker
|
|
to `InputDispatcher`. In `OnMouseDown`, if the same button fires within
|
|
500ms of its last press, activation is `DoubleClick`; otherwise `Press`.
|
|
500ms matches the standard Windows/macOS double-click threshold.
|
|
|
|
**Rationale:** The fix is minimal and correct. A more faithful retail
|
|
implementation might read the OS's configured double-click interval, but
|
|
500ms is the retail default and was the right call for now. 4 new unit
|
|
tests cover the timing logic: first click = Press, second click within
|
|
500ms = DoubleClick, third click = Press again (resets the window), and
|
|
button mismatch = Press.
|
|
|
|
### 2. OnInputAction gate discarded DoubleClick activations (`58b95bc`)
|
|
|
|
**Root cause:** Even after discovery #1 was fixed and `SelectDblLeft DoubleClick`
|
|
fired from the dispatcher, the event handler had an early-return guard at
|
|
the top of `GameWindow.OnInputAction`:
|
|
|
|
```csharp
|
|
if (activation != InputActivation.Press) return;
|
|
```
|
|
|
|
This guard was introduced to prevent `Hold` repetition from triggering
|
|
switch cases intended for one-shot actions. It correctly blocked `Hold`
|
|
but also blocked `DoubleClick` — so the `SelectDblLeft` case was still
|
|
unreachable even after the dispatcher started generating `DoubleClick`.
|
|
|
|
**Fix:** Widened the guard to let both `Press` and `DoubleClick` through:
|
|
|
|
```csharp
|
|
if (activation != InputActivation.Press && activation != InputActivation.DoubleClick) return;
|
|
```
|
|
|
|
**Rationale:** `DoubleClick` is semantically a one-shot activation (fires
|
|
once per double-click gesture), so it belongs in the same pass-through
|
|
group as `Press`. `Hold` repetition remains blocked.
|
|
|
|
### 3. CollisionExemption required both ETHEREAL + IGNORE_COLLISIONS (`a6e4b57`)
|
|
|
|
**Root cause:** The original `CollisionExemption.ShouldSkip` check was
|
|
ported faithfully from `acclient_2013_pseudo_c.txt:276782`, which requires
|
|
**both** `ETHEREAL_PS (0x4)` and `IGNORE_COLLISIONS_PS (0x10)` to be set
|
|
simultaneously before short-circuiting collision detection. Retail servers
|
|
send both bits when opening a door, so retail clients see `state ≥ 0x14`.
|
|
|
|
However, ACE's `Door.Open()` broadcasts only the `ETHEREAL` bit in the
|
|
low portion of the state word. The observed wire value was
|
|
`state=0x0001000C`: bit `0x4` (ETHEREAL) is set, bit `0x10`
|
|
(IGNORE_COLLISIONS) is not. The `&&` conjunction in `ShouldSkip` evaluated
|
|
to false → door stayed solid even after the registry update.
|
|
|
|
This was the exact scenario the L.2g slice 1 Important review note warned
|
|
about (see L.2g handoff §"One Important review note"): *"ACE's
|
|
`PhysicsObj.cs:787-791` may set both bits... but this is not verified by
|
|
the test suite. The B.4b visual test will settle this definitively."*
|
|
It settled as: ACE sends `0x4` alone, not `0x14`.
|
|
|
|
**Fix:** Widened the short-circuit to fire on `ETHEREAL` alone:
|
|
|
|
```csharp
|
|
// Widened from (ETHEREAL && IGNORE_COLLISIONS) — ACE Door.Open() sends
|
|
// ETHEREAL alone (state=0x0001000C); retail servers send both.
|
|
// Pragmatic choice: exempt on ETHEREAL-bit-alone until full retail
|
|
// obstruction_ethereal flag path is ported.
|
|
if ((state & ETHEREAL_PS) != 0) return true;
|
|
```
|
|
|
|
**Rationale:** The deeper retail path (pseudo-C line 276795 sets
|
|
`obstruction_ethereal=1` and routes through downstream movement handling)
|
|
was not ported — that's a more invasive change requiring more testing. The
|
|
pragmatic widening to ETHEREAL alone is correct for ACE's Door behavior and
|
|
matches the spirit of the retail check (ETHEREAL means "pass through me").
|
|
If a future retail-server emulator sends both bits, the widened check still
|
|
fires (ETHEREAL is a subset of ETHEREAL+IGNORE_COLLISIONS).
|
|
|
|
### 4. ServerGuid → entity.Id translation missing in OnLiveStateUpdated (`08be296`) — THE actual blocker
|
|
|
|
**Root cause:** `ShadowObjectRegistry` is keyed by local `entity.Id` (the
|
|
per-session integer ID assigned by `GpuWorldState` at entity registration,
|
|
e.g. `0x000F4245`). The `GameWindow.OnLiveStateUpdated` handler was passing
|
|
`parsed.Guid` — the **server GUID** broadcasted in the `SetState` packet
|
|
(e.g. `0x7A9B4015`) — directly to `UpdatePhysicsState`. Because the registry
|
|
has no entry keyed by server GUID, the lookup always returned "not found"
|
|
and the state mutation was silently dropped. The registry stayed at
|
|
`state=0x00000000` (closed, solid) regardless of how many times the door
|
|
was clicked.
|
|
|
|
This is why discoveries 1-3 alone were insufficient: even with double-click
|
|
detection working, the correct gate firing, and `CollisionExemption`
|
|
widened, the registry still held the stale closed state and the door
|
|
stayed solid.
|
|
|
|
**Fix:** Used the pre-existing `_entitiesByServerGuid` reverse-lookup
|
|
dictionary on `GameWindow` (populated at entity registration in
|
|
`OnLiveCreateObject` since Phase 6.6/6.7). `OnLiveStateUpdated` now does:
|
|
|
|
```csharp
|
|
if (_entitiesByServerGuid.TryGetValue(parsed.Guid, out var entity))
|
|
_physicsEngine.ShadowObjects.UpdatePhysicsState(entity.Id, parsed.PhysicsState);
|
|
```
|
|
|
|
The `entityId=` field was added to the `[setstate]` diagnostic log line
|
|
specifically to make this translation visible and greppable:
|
|
`[setstate] guid=0x7A9B4015 entityId=0x000F4245 state=0x0001000C`.
|
|
|
|
**Why this was missed:** L.2g slice 1's unit tests operated at the
|
|
`ShadowObjectRegistry` level directly, calling `UpdatePhysicsState` with
|
|
an `entity.Id` (not a server GUID). The integration was never exercised
|
|
end-to-end before B.4b's visual test. The two tests `UpdatePhysicsState_FlipsEthereal_*`
|
|
were correct in isolation; the broken layer was one level above them
|
|
(the handler → registry call site).
|
|
|
|
**Why the "multiple doors" misdiagnosis occurred:** Before slice 1c was
|
|
identified, the `[resolve]` probes showed wall hits attributed to
|
|
`obj=0x000F4245` while the clicked door's ServerGuid was `0x7A9B4015`.
|
|
Initial read: "these are two different entities blocking the threshold."
|
|
Slice 1c clarified: both IDs refer to the same door — `0x000F4245` is
|
|
the local entity ID, `0x7A9B4015` is the server GUID for the same entity.
|
|
The ID-space mismatch was the cause of both the collision-not-clearing
|
|
AND the "different object" misread.
|
|
|
|
---
|
|
|
|
## Open notes / follow-ups
|
|
|
|
### Door swing animation (#58)
|
|
|
|
When ACE opens a door it broadcasts **two** packets, not one:
|
|
|
|
1. `SetState (0xF74B)` — the collision-bit flip. **Handled by L.2g slice 1.**
|
|
2. `UpdateMotion (0xF74D)` with stance/command `(NonCombat, On)` — the
|
|
swing animation cycle. **NOT handled.**
|
|
|
|
acdream's `UpdateMotion` pipeline is currently scoped to player + creature
|
|
animation (Phase L.3). Non-creature entities like doors do not receive
|
|
cycle commands. The door therefore opens (becomes passable) but has no
|
|
visible swing animation.
|
|
|
|
Filed as **issue #58**. Scope is unknown — routing `UpdateMotion` to
|
|
non-creature `WorldEntity` instances could be quick (few lines), or the
|
|
`AnimationSequencer` may have creature-specific assumptions that require
|
|
audit first. Filed as M1-deferred polish; it does not block the demo
|
|
scenario.
|
|
|
|
### Door toggle behavior
|
|
|
|
ACE doors toggle on each Use: first double-click opens, subsequent
|
|
double-click closes (re-sends `SetState` with `state=0x00000000`, restoring
|
|
collision). This is correct ACE behavior and matches retail. No issue to file.
|
|
|
|
Rapid double-clicks (faster than ACE's server-tick processing) will open
|
|
then close in quick succession — each Use lands as a distinct game action.
|
|
Expected behavior; no fix needed.
|
|
|
|
### Multiple-door misdiagnosis (historical note)
|
|
|
|
While slice 1c was still unidentified, the `[resolve]` diagnostic showed:
|
|
|
|
```
|
|
[resolve] ... obj=0x000F4245 wall hit
|
|
[B.4b] use guid=0x7A9B4015 ...
|
|
[setstate] guid=0x7A9B4015 state=0x0001000C
|
|
[resolve] ... obj=0x000F4245 wall hit (unchanged!)
|
|
```
|
|
|
|
Initial misdiagnosis: there must be a *different* door entity (`0x000F4245`)
|
|
blocking the threshold whose state was never updated. Slice 1c revealed:
|
|
both IDs refer to the same door — one is the server GUID (network space),
|
|
the other is the local entity ID (registry space). The registry update was
|
|
targeting the server GUID (which missed), so the local-ID-keyed entry
|
|
stayed solid.
|
|
|
|
### Selection HUD / hover-highlight / brackets
|
|
|
|
Out of B.4b scope per design spec §Non-goals. The `_selectedGuid` field on
|
|
`GameWindow` is populated (stores the last-picked entity's server GUID), but
|
|
nothing renders a selection bracket, hover highlight, or target nameplate.
|
|
That is M2/M3 HUD work (Phase D.6).
|
|
|
|
### BuildPickUp (F key) + UseWithTarget UX
|
|
|
|
`InteractRequests.BuildPickUp` exists (as an alias of `BuildUse`). The
|
|
`SelectionPickUp` input action and the F-key binding exist. But
|
|
`OnInputAction` has no case for `SelectionPickUp` — pick-up-by-F-key is
|
|
still unimplemented. Same for `UseWithTarget` (requires a secondary target
|
|
selection UX). Both deferred to a follow-up phase; not M1-blocking.
|
|
|
|
---
|
|
|
|
## Next session
|
|
|
|
**M1 demo progress as of this branch:**
|
|
- ✅ "walk through Holtburg without getting stuck" — Phase L.2 in progress (outdoor collision works; CBuildingObj interior still deferred to L.2d).
|
|
- ✅ "open the inn door" — **done** (B.4b, this branch).
|
|
- ⬜ "click an NPC" — pick + Use wiring exists now; depends on ACE NPC handler responding to Use.
|
|
- ⬜ "pick up an item" — `BuildPickUp` + F-key wiring not yet in `OnInputAction`.
|
|
|
|
**Recommended next steps (in M1 critical-path order):**
|
|
|
|
1. **Door swing animation (#58)** — cosmetic M1 polish. Route
|
|
`UpdateMotion (0xF74D)` to non-creature entities so the door visually
|
|
swings. Could be quick (30 min) or moderate (2 hrs with AnimationSequencer
|
|
audit). Worth a spike before committing to an estimate.
|
|
|
|
2. **Chronic open-issue triage** — #2 (lightning), #4 (horizon-glow), #28
|
|
(aurora), #29 (cloud thinness), #37 (humanoid coat), #41 (remote-motion
|
|
blips) have been deferred since April/early-May. Link each to a future
|
|
phase or downgrade. ~1 hour. Not M1-blocking but surfaces the real backlog.
|
|
|
|
3. **More Phase C visual-fidelity** — C.2 (dynamic point lights), C.3
|
|
(palette tuning), C.4 (double-sided translucent polys). World still reads
|
|
"old" without local lighting on fireplaces/lamps.
|
|
|
|
---
|
|
|
|
## Reproducibility
|
|
|
|
Same launch recipe as before. For reproducing the visual test:
|
|
|
|
```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-b4b.log"
|
|
```
|
|
|
|
Walk to the Holtburg inn doorway. Double-left-click the closed Door. Walk
|
|
through. Subsequent double-clicks will close and re-open (ACE toggle).
|
|
|
|
After closing the client, grep for:
|
|
|
|
```powershell
|
|
Select-String -Path launch-b4b.log -Pattern "SelectDblLeft|pick guid|use guid|setstate.*entityId"
|
|
```
|
|
|
|
Expected:
|
|
- `[input] SelectDblLeft DoubleClick` — dispatcher fires on second click within 500ms.
|
|
- `[B.4b] pick guid=0x7A9B4015 name=Door` — ray hits the door.
|
|
- `[B.4b] use guid=0x7A9B4015 seq=N` — Use message sent.
|
|
- `[setstate] guid=0x7A9B4015 entityId=0x000F4245 state=0x0001000C` — ACE reply processed, translation confirmed.
|
|
|
|
---
|
|
|
|
## Worktree state at handoff
|
|
|
|
- Branch `claude/compassionate-wilson-23ff99`.
|
|
- 9 implementation commits + 3 plan/spec commits ahead of `eea9b4d`
|
|
(the L.2g slice 1 merge from the previous session).
|
|
- Controller should run a code review, then merge to main.
|
|
- Do NOT rebase or squash — each commit tells a diagnostic story that
|
|
the next phase's debugging may need.
|