From d132fcccfbf8a2181be8b9a28d0985638829036b Mon Sep 17 00:00:00 2001 From: Erik Date: Thu, 14 May 2026 16:23:20 +0200 Subject: [PATCH] docs(B.5): ship handoff + roadmap/CLAUDE update + file #63 #64 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Phase B.5 (ground-item pickup, close-range path) shipped and visual-verified 2026-05-14 at Holtburg. M1 demo target 4/4 ("pick up an item") met. New ship-handoff doc captures the 5-commit history including the post-visual-test PickupEvent (0xF74A) wire-handler fix that closes the local-despawn gap. Roadmap and CLAUDE.md updated to reflect the ship + the new follow-up issues: - #63 (MEDIUM) — server-initiated MoveToObject auto-walk not honored; blocks double-click pickup + out-of-range F. Filed as candidate Phase B.6. holtburger has the reference implementation. - #64 (LOW) — local-player pickup animation does not render (retail observers see it correctly). Likely a self-echo filter dropping UpdateMotion(Pickup) on the local player. Carry-overs from B.4c (#61 link-cycle flash, #62 PARTSDIAG null-guard) unchanged. --- CLAUDE.md | 45 ++-- docs/ISSUES.md | 83 ++++++ docs/plans/2026-04-11-roadmap.md | 1 + .../research/2026-05-14-b5-shipped-handoff.md | 252 ++++++++++++++++++ 4 files changed, 364 insertions(+), 17 deletions(-) create mode 100644 docs/research/2026-05-14-b5-shipped-handoff.md diff --git a/CLAUDE.md b/CLAUDE.md index a7e4ec7..f32642b 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -619,19 +619,26 @@ acdream's plan lives in two files committed to the repo: **Currently in Phase L.2 (Movement & Collision Conformance).** L.2a slices 1+2+3 + L.2d slice 1+1.5 + L.2g slice 1 + L.2g slice 1b + L.2g slice 1c + -**Phase B.4b** + **Phase B.4c** all shipped and visual-verified 2026-05-13. -The M1 demo target *"open the inn door"* is met **with full visual feedback**: -double-click a door in the Holtburg inn doorway → `WorldPicker.Pick` finds -the door entity → `BuildUse` sends `0xF7B1/0x0036` to ACE → ACE broadcasts -`SetState (0xF74B)` with `ETHEREAL` bit → `ShadowObjectRegistry.UpdatePhysicsState` -(L.2g slice 1) mutates the cached state (via fixed ServerGuid→entity.Id -translation, L.2g slice 1c) → `CollisionExemption.ShouldSkip` exempts on -ETHEREAL-alone (L.2g slice 1b) → player walks through → door swing animation -plays (B.4c: spawn-time `AnimationSequencer` registration + `OnLiveMotionUpdated` -routing for door entities). Issue #57 (B.4 handler gap) is closed. Issue #58 -(door swing animation) is closed by B.4c. Issues #61 (link→cycle boundary -flash) and #62 (PARTSDIAG null-guard) are filed as M1-deferred polish. +**Phase B.4b** + **Phase B.4c** all shipped and visual-verified 2026-05-13; +**Phase B.5** (ground-item pickup, F-key) shipped and visual-verified +2026-05-14. The M1 demo target *"pick up an item"* is met for the +close-range path — single-click a ground item to select, walk within +~0.6 m of it, press F, and the item is removed from the world and added +to the player's inventory. Wire chain: `InteractRequests.BuildPickUp` +sends `PutItemInContainer (0xF7B1/0x0019)`; ACE despawns the item with +`GameMessagePickupEvent (0xF74A)` (NOT `0xF747 DeleteObject` — the +distinction surfaced during visual testing and is fixed by the new +`PickupEvent.cs` parser routed through the shared `EntityDeleted` +event). The M1 demo target *"open the inn door"* remains met from B.4b ++ B.4c. Issue #57 (B.4 handler gap) is closed. Issue #58 (door swing +animation) is closed by B.4c. Issues #61 (link→cycle boundary flash), +#62 (PARTSDIAG null-guard), **#63 (server-initiated MoveToObject +auto-walk not honored — blocks out-of-range pickup / Use)**, and **#64 +(local-player pickup animation does not render)** are filed as +M1-deferred follow-up. +**B.5 ship handoff:** [`docs/research/2026-05-14-b5-shipped-handoff.md`](docs/research/2026-05-14-b5-shipped-handoff.md) +— full evidence for the 5 commits across InteractRequests / GameWindow / WorldSession + the bonus `PickupEvent (0xF74A)` wire-handler fix that closes the despawn gap. **B.4c ship handoff:** [`docs/research/2026-05-13-b4c-shipped-handoff.md`](docs/research/2026-05-13-b4c-shipped-handoff.md) — full evidence for the 4 commits + 2 bonus discoveries (stance-value wrong `0x01` vs `0x3D` causing underground doors; link→cycle boundary flash). @@ -725,11 +732,15 @@ project. packets, wire the handlers; if it is silent, investigate ACE's NPC handler configuration. ~30 min spike, outcome determines whether NPC interaction needs a full phase or is a one-commit fix. -- **Phase B.5 — Ground item pickup (F key) (M1 critical path).** The - `SelectionPickUp` input action + F-key binding exist in `KeyBindings` but - `OnInputAction` has no case for it. `BuildUse` IS `BuildPickUp` (same wire - format). One-commit addition: add `SelectionPickUp` case to `GameWindow. - OnInputAction` → call `InteractRequests.BuildPickUp(seq, _selectedGuid)`. ~30 min. +- **Phase B.6 — Client-side MoveToObject auto-walk handling (closes #63).** + ACE auto-walks the player to out-of-range Use / Pickup targets via + `CreateMoveToChain` + `EnqueueBroadcastMotion(MoveToObject)`, but our client + doesn't honor the inbound motion broadcast — character drifts toward the + target and snaps back, ACE's chain times out. Reference implementation + exists in `references/holtburger/crates/holtburger-core/src/client/simulation.rs` + (the `approximate_move_to_object_projection_target` + `MoveToObject` case). + Unlocks double-click pickup, F-key pickup from any distance, Use on + out-of-range NPCs / corpses. Probably 1-2 commits + visual verification. - **Triage the chronic open-issue list** in `docs/ISSUES.md` — #2 (lightning), #4 (sky horizon-glow), #28 (aurora), #29 (cloud thinness), #37 (humanoid coat), #41 (remote-motion blips) have been open since April/early-May and diff --git a/docs/ISSUES.md b/docs/ISSUES.md index 966f401..b8265ca 100644 --- a/docs/ISSUES.md +++ b/docs/ISSUES.md @@ -46,6 +46,89 @@ Copy this block when adding a new issue: # Active issues +## #64 — Local-player pickup animation does not render + +**Status:** OPEN +**Severity:** LOW (visual feedback only — pickup completes correctly) +**Filed:** 2026-05-14 (B.5 visual verification) +**Component:** motion / animation routing for local player + +**Description:** When `+Acdream` picks up an item (B.5 close-range +path), retail observers see the character play the pickup animation +correctly, but the local view shows no pickup animation. The item +despawns, the inventory updates, but the character's own +bend-down-and-grab animation is missing. + +**Root cause / hypothesis:** ACE broadcasts `Motion(MotionCommand.Pickup)` +via `Player_Inventory.AddPickupChainToMoveToChain` (line 711–713, +`EnqueueBroadcastMotion(motion)`), which arrives as a normal +`UpdateMotion (0xF74D)` packet. Retail observers route it through +their remote-creature animation pipeline and render the pickup. For +the local player, our `OnLiveMotionUpdated` likely filters self-echoes +(local player drives its own motion via prediction, not server +echoes) and drops the pickup motion. The pickup is a one-shot +animation initiated by the server, so the prediction path has no +trigger — and the echo path is filtered. + +**Acceptance:** When `+Acdream` picks up an item, the local view shows +the same pickup animation retail observers see. Probably resolved by +either (a) admitting server-initiated one-shot motions through the +local-player motion filter, or (b) generating the pickup animation +locally on send (mirroring retail's client behavior). + +**Files:** `src/AcDream.App/Rendering/GameWindow.cs` `OnLiveMotionUpdated` +(motion routing); the self-echo filter is somewhere along this path. + +**Estimated scope:** Small-to-medium. Mostly investigation + +1–2 commits. + +--- + +## #63 — Server-initiated auto-walk (MoveToObject) not honored + +**Status:** OPEN +**Severity:** MEDIUM (blocks out-of-range Use + Pickup; close-range +works fine) +**Filed:** 2026-05-14 (B.5 visual verification) +**Component:** motion / inbound MoveToObject handling + +**Description:** When the player triggers a Use or PutItemInContainer +on a target outside ACE's `WithinUseRadius` (default 0.6 m), ACE +runs server-side auto-walk via `CreateMoveToChain` → +`PhysicsObj.MoveToObject` + `EnqueueBroadcastMotion(Motion(MoveToObject, target))`. +Our client receives the `UpdateMotion(MoveToObject)` broadcast for +the player but doesn't honor it: the character either visually +drifts a bit toward the target and snaps back, or just stands still. +ACE's MoveToChain then times out, the `success: false` path +broadcasts `InventoryServerSaveFailed (ActionCancelled)`, and the +pickup/use never completes. + +**User-visible symptom:** Double-click a ground item from any +distance, or F-key it from > 0.6 m: character partially walks toward +the item, then flips back to original position. No pickup. + +**Reference:** [holtburger simulation.rs:33–41 + 178–191](references/holtburger/crates/holtburger-core/src/client/simulation.rs) +already implements client-side `MoveToObject` motion projection + +auto-walk handling. That's the shape of the fix. + +**Root cause:** Our `OnLiveMotionUpdated` has no handler for the +`MoveToObject` motion type; the broadcast is silently dropped. + +**Acceptance:** Double-click a ground item from 2–5 m away. Character +auto-walks to within use radius, ACE's MoveToChain confirms success, +pickup completes (including the existing PickupEvent despawn). Same +behavior for Use on out-of-range NPCs. + +**Files:** `src/AcDream.App/Rendering/GameWindow.cs` `OnLiveMotionUpdated` +(routing); likely a new `MoveToObjectMotion` handler in the motion / +prediction layer + a server-acked position-update echo so ACE sees the +player has reached the target. + +**Estimated scope:** Medium. Probably its own phase (B.6 or similar); +not a one-commit fix. Compose from holtburger's pattern. + +--- + ## #62 — PARTSDIAG null-guard for sequencer-driven entities **Status:** OPEN diff --git a/docs/plans/2026-04-11-roadmap.md b/docs/plans/2026-04-11-roadmap.md index 233e18f..c2e403c 100644 --- a/docs/plans/2026-04-11-roadmap.md +++ b/docs/plans/2026-04-11-roadmap.md @@ -67,6 +67,7 @@ | C.1.5a | Portal PES wiring — server-spawned `WorldEntity` entities now fire their `Setup.DefaultScript` through the already-shipped `PhysicsScriptRunner` on enter-world. New ~70-line [`EntityScriptActivator`](../../src/AcDream.App/Rendering/Vfx/EntityScriptActivator.cs) class wires into `GpuWorldState`'s spawn lifecycle (`AppendLiveEntity` → `OnCreate`, `RemoveEntityByServerGuid` → `OnRemove`). Resolver lambda in `GameWindow` hits `_dats.Get(...)?.DefaultScript.DataId` with defensive try/catch returning `0u` on miss. Activator also seeds `_particleSink.SetEntityRotation` so hook offsets transform from entity-local to world space correctly. **Verified at the Holtburg Town network portal**: 10-hook portal script fires end-to-end with correct color, persistence, orientation, multi-emitter dispatch. **Known limitation surfaced and filed as issue #56**: `ParticleHookSink` ignores `CreateParticleHook.PartIndex`, so the 10 emitters collapse to one root position instead of distributing across the portal Setup's parts — visually produces a compressed, partly-ground-buried swirl. Mechanism is correct; per-part transform handling is the next vfx-pipeline work (blocks slice 2 visual delight; affects every multi-emitter PES). Spec: [`docs/superpowers/specs/2026-05-12-phase-c1.5a-portals-design.md`](../superpowers/specs/2026-05-12-phase-c1.5a-portals-design.md). Plan: [`docs/superpowers/plans/2026-05-12-phase-c1.5a-portals.md`](../superpowers/plans/2026-05-12-phase-c1.5a-portals.md). | Live ✓ (with #56) | | B.4b | Outbound Use handler wiring + 4 bonus fixes (L.2g slices 1b+1c, double-click detection, DoubleClick gate fix). Shipped 2026-05-13 (branch `claude/compassionate-wilson-23ff99`, merge pending). Closes #57. Files #58 (door swing animation, M1-deferred). `WorldPicker.BuildRay` + `Pick` (ray-sphere entity pick with inside-sphere guard); `GameWindow.OnInputAction` switch cases for `SelectLeft` / `SelectDblLeft` / `UseSelected`; `_entitiesByServerGuid` reverse-lookup dict + ServerGuid→entity.Id translation in `OnLiveStateUpdated` (L.2g slice 1c — THE actual blocker); `InputDispatcher` double-click detection 500ms threshold (binding was dead code without it); `CollisionExemption.ShouldSkip` widened to ETHEREAL-alone (ACE Door.Open() sends `state=0x0001000C`, not `0x14`). M1 demo target "open the inn door" verified at Holtburg inn doorway. Plan: [`docs/superpowers/plans/2026-05-13-phase-b4b-plan.md`](../superpowers/plans/2026-05-13-phase-b4b-plan.md). Handoff: [`docs/research/2026-05-13-b4b-shipped-handoff.md`](../research/2026-05-13-b4b-shipped-handoff.md). | Live ✓ | | B.4c | Door swing animation. Shipped 2026-05-13 (branch `claude/phase-b4c-door-anim`, merge pending). Closes #58. Files #61 (AnimationSequencer link→cycle boundary flash; low-severity polish) + #62 (PARTSDIAG null-guard; latent). Spawn-time `AnimationSequencer` registration for door entities in `GameWindow.OnLiveEntitySpawnedLocked`: initial cycle seeded from `spawn.PhysicsState` (Off for closed, On for open). Shared `IsDoorName` / `IsDoorSpawn` helpers. `[door-cycle]` diagnostic in `OnLiveMotionUpdated` (gated on `ACDREAM_PROBE_BUILDING`). Bonus stance-value fix: `NonCombat = 0x3D` not `0x01` (wrong value caused doors to render halfway underground via empty sequencer frames). Visual-verified 2026-05-13 at Holtburg inn doorway: swing-open + swing-close cycles both play. M1 demo target "open the inn door" now has full visual feedback. Plan: [`docs/superpowers/plans/2026-05-13-phase-b4c-plan.md`](../superpowers/plans/2026-05-13-phase-b4c-plan.md). Handoff: [`docs/research/2026-05-13-b4c-shipped-handoff.md`](../research/2026-05-13-b4c-shipped-handoff.md). | Live ✓ | +| B.5 | Ground-item pickup (F-key, close-range path). Shipped 2026-05-14 (branch `claude/phase-b5-pickup`, merge pending). Closes M1 demo target 4/4 *"pick up an item"*. New `InteractRequests.BuildPickUp(seq, itemGuid, containerGuid, placement)` builds the 24-byte `PutItemInContainer (0xF7B1/0x0019)` wire body verified against `references/ACE/Source/ACE.Server/Network/GameAction/Actions/GameActionPutItemInContainer.cs`. New private `GameWindow.SendPickUp(uint itemGuid)` helper mirrors `SendUse`'s gate-on-InWorld pattern; `case InputAction.SelectionPickUp` in `OnInputAction` switch routes the F-key through `_selectedGuid`. **Bonus wire-handler fix (Task 2b):** ACE despawns picked-up items via `GameMessagePickupEvent (0xF74A)`, not the `GameMessageDeleteObject (0xF747)` we already handled — surfaced during visual testing (item kept rendering on ground after successful server-side pickup). New `PickupEvent.cs` parser + `WorldSession` dispatch branch adapt to `DeleteObject.Parsed` and reuse the existing `EntityDeleted → OnLiveEntityDeleted → RemoveLiveEntityByServerGuid` chain. Files #63 (server-initiated `MoveToObject` auto-walk not honored — out-of-range pickup / double-click fails server-side timeout) + #64 (local-player pickup animation does not render). Visual-verified 2026-05-14 at Holtburg: 3 successful close-range pickups (Pink Taper + Violet Tapers), item despawns locally as ACE acks. Plan: [`docs/superpowers/plans/2026-05-14-phase-b5-pickup.md`](../superpowers/plans/2026-05-14-phase-b5-pickup.md). Handoff: [`docs/research/2026-05-14-b5-shipped-handoff.md`](../research/2026-05-14-b5-shipped-handoff.md). | Live ✓ | | C.1.5b | Per-part PES transforms + dat-hydrated entity DefaultScript dispatch. Closes issue #56. Shipped 2026-05-12 across 5 commits (`1e3c33b` docs+plan, `f3bc15e` SetupPartTransforms helper, `11521f4` ParticleHookSink applies `CreateParticleHook.PartIndex`, `5ca5827` activator refactor + GameWindow resolver lambda, `8735c39` GpuWorldState 4 new fire-sites). **Slice A** — new [`SetupPartTransforms.Compute(setup)`](../../src/AcDream.Core/Meshing/SetupPartTransforms.cs) walks `PlacementFrames[Resting]` → `[Default]` → first-available (mirrors `SetupMesh.Flatten` priority) and returns `Matrix4x4` per part; new `ParticleHookSink.SetEntityPartTransforms(entityId, partTransforms)` mirrors the existing `_rotationByEntity` pattern; `SpawnFromHook` now transforms hook offset through `partTransforms[partIndex]` before applying entity rotation. **Slice B** — activator's `ServerGuid==0` guard relaxed: keys by `entity.ServerGuid` when non-zero, else `entity.Id` (collision-free with server guids in the `0x40xxxxxx` interior / `0x80xxxxxx` scenery / `0xC0xxxxxx` ranges). Resolver delegate refactored to return `ScriptActivationInfo(ScriptId, PartTransforms)` so one dat lookup yields both pieces. `GpuWorldState` fires the activator from 4 new sites: `AddLandblock` + `AddEntitiesToExistingLandblock` (Far→Near promotion) for OnCreate, `RemoveLandblock` + `RemoveEntitiesFromLandblock` (Near→Far demotion) for OnRemove. ServerGuid==0 filter on AddLandblock avoids double-firing pending-bucket merges. **Reality discovery folded into spec §3**: EnvCell `StaticObjects` are already hydrated as `WorldEntity` instances by `GameWindow.BuildInteriorEntitiesForStreaming` (with stable `entity.Id` in `0x40xxxxxx`) — no synthetic-ID scheme or separate walker class needed (handoff §4 Q1/Q2 mooted). **Visual verification 2026-05-12**: Holtburg Town network portal swirl distributes across the arch (no ground-burial), Inn fireplace flames render over the firebox, cottage chimney smoke columns render, spell-cast animation-hook particles all match retail. 18 new + 4 updated tests, all Vfx/Meshing/Streaming/Activator green. Spec: [`docs/superpowers/specs/2026-05-13-phase-c1.5b-design.md`](../superpowers/specs/2026-05-13-phase-c1.5b-design.md). Plan: [`docs/superpowers/plans/2026-05-13-phase-c1.5b.md`](../superpowers/plans/2026-05-13-phase-c1.5b.md). | Live ✓ | Plus polish that doesn't get its own phase number: diff --git a/docs/research/2026-05-14-b5-shipped-handoff.md b/docs/research/2026-05-14-b5-shipped-handoff.md new file mode 100644 index 0000000..eca0c2d --- /dev/null +++ b/docs/research/2026-05-14-b5-shipped-handoff.md @@ -0,0 +1,252 @@ +# Phase B.5 shipped — handoff (visual-verified 2026-05-14) + +**Date:** 2026-05-14. +**Branch:** `claude/phase-b5-pickup` (ready to merge to main; controller handles the merge after this doc lands). +**Predecessors:** +- [docs/research/2026-05-13-b4c-shipped-handoff.md](2026-05-13-b4c-shipped-handoff.md) — B.4c (door swing) shipped immediately before. +- [docs/research/2026-05-13-b5-pickup-handoff.md](2026-05-13-b5-pickup-handoff.md) — fresh-session handoff that scoped this phase. +- [docs/superpowers/plans/2026-05-14-phase-b5-pickup.md](../superpowers/plans/2026-05-14-phase-b5-pickup.md) — implementation plan (2 tasks). + +--- + +## TL;DR + +Phase B.5 **shipped end-to-end and is visual-verified 2026-05-14.** The +M1 demo target *"pick up an item"* is met for the close-range path — +single-click a ground item to select, walk within ~0.6 m of it, press +F, and the item is removed from the world and added to the player's +inventory. + +The plan budgeted 2 implementation tasks (~50 LOC). Visual testing +surfaced **one wire-handler gap** that became Task 2b: ACE despawns +picked-up items via `GameMessagePickupEvent (0xF74A)`, not the +`GameMessageDeleteObject (0xF747)` we already handled — without that +fix the pickup succeeded server-side but the item kept rendering on +the ground locally. Caught and fixed in the same session. + +Two known gaps remain, filed as issues for follow-up: +- **#63 (MEDIUM)** — Server-initiated auto-walk for out-of-range Use / + PickUp not honored. Double-click a ground item from > 0.6 m and the + character partially walks then snaps back; ACE's `MoveToChain` times + out. This is a separate motion-handling phase, not a B.5 regression. +- **#64 (LOW)** — Local-player pickup animation doesn't render + (retail observers see it correctly; local view is silent). Likely a + self-echo filter dropping `UpdateMotion(Pickup)` on the local player. + +--- + +## What shipped on this branch + +| # | Commit | Subject | Task | +|---|---|---|---| +| 1 | `e8a20f2` | `feat(B.5): InteractRequests.BuildPickUp — PutItemInContainer 0x0019` | Task 1 | +| 2 | `ced1b85` | `test(B.5): exercise i32 sign-correctness for BuildPickUp.placement` | Task 1 code-review fix | +| 3 | `54d9bb9` | `feat(B.5): SendPickUp helper + F-key SelectionPickUp wiring` | Task 2 | +| 4 | `5c24f6c` | `docs(B.5): implementation plan from writing-plans skill` | Plan doc | +| 5 | `f7636a9` | `fix(B.5): handle PickupEvent 0xF74A so picked-up items despawn locally` | Task 2b (post-visual-test fix) | + +Plus the predecessor handoff (`86440ff`) that started the branch. + +**Build:** clean. +**Tests:** `dotnet test -c Debug` shows AcDream.Core.Net.Tests 290/290 +passing (was 287 at branch start; +3 from Task 2b's PickupEvent tests; +the two BuildPickUp tests landed inside the same project's existing +file). Failure count unchanged at 8 pre-existing baseline in +AcDream.Core.Tests. + +--- + +## What the code does end-to-end + +**Outbound (Tasks 1 & 2):** + +1. User single-clicks a ground item near `+Acdream`. + `case InputAction.SelectLeft → PickAndStoreSelection(useImmediately: false)` + runs B.4b's `WorldPicker.Pick`, finds the item, sets `_selectedGuid`. + Log: `[B.4b] pick guid=0x… name=…`. +2. User presses F. + `case InputAction.SelectionPickUp → SendPickUp(_selectedGuid)` builds + the wire body via `InteractRequests.BuildPickUp(seq, itemGuid, + _playerServerGuid, placement: 0)` and posts it through + `_liveSession.SendGameAction`. Log: `[B.5] pickup item=… container=… seq=…`. +3. Wire layout (24 bytes): `0xF7B1 envelope | seq | 0x0019 opcode | + itemGuid u32 | containerGuid u32 | placement i32`. Verified against + `references/ACE/Source/ACE.Server/Network/GameAction/Actions/GameActionPutItemInContainer.cs`. + +**Inbound (Task 2b — surfaced during visual test):** + +4. ACE runs `HandleActionPutItemInContainer`. If the player is within + `WithinUseRadius` (~0.6 m), the close-range branch in + `CreateMoveToChain` skips the auto-walk and runs the pickup chain + directly: server-side `Landblock.RemoveWorldObject(item.Guid, + adjacencyMove: false, fromPickup: true)` → per-player + `Player_Tracking.RemoveTrackedObject(wo, fromPickup: true)` → + broadcast `GameMessagePickupEvent (0xF74A)` to all observers. +5. Our `WorldSession.Dispatch` now routes `0xF74A` (in addition to + `0xF747 DeleteObject`) through the shared `EntityDeleted` event, + adapting the `PickupEvent.Parsed` to a `DeleteObject.Parsed` so + `OnLiveEntityDeleted → RemoveLiveEntityByServerGuid` runs unchanged. + The item disappears from the local view. + +--- + +## Wire-handler gap (Task 2b) + +ACE distinguishes two despawn opcodes: +- `0xF747 GameMessageDeleteObject` — "object is gone" (timeout / death / + out-of-LOS). Our existing handler. +- `0xF74A GameMessagePickupEvent` — "object was picked up by a player." + Sent by `Player_Tracking.RemoveTrackedObject(wo, fromPickup: true)`. + +Both are functionally identical from the client's view (remove the +entity from the world), but only one was handled. Wire format adds +one `u16 objectPositionSequence` field over DeleteObject's layout, so +`PickupEvent.cs` is its own parser; the dispatcher adapts to +`DeleteObject.Parsed` for the downstream consumer. + +This is exactly the kind of trap CLAUDE.md's reference-repo discipline +exists to prevent — the handoff spec said "the existing despawn path +removes the ground item from view," which was *almost* true. Took one +visual-verification round-trip to surface, ten minutes to fix with a +clean wire parser + 3 new unit tests. + +--- + +## Visual verification — what was observed + +**Test scenario:** ACE dropped a Pink Taper, then a Violet Taper, then +two more tapers near `+Acdream` at Holtburg. Player walked up close, +single-clicked, pressed F. Three pickups completed in the post-fix +log: items `0x80000725`, `0x8000072A`, `0x80000729`. + +**Before Task 2b:** Server-side pickup succeeded — `[B.5] pickup … +seq=46` in log; retail observer saw item disappear from world. Local +view still rendered the item on the ground. + +**After Task 2b:** Item disappears locally as soon as ACE acks the +pickup. Three successful close-range pickups recorded in the log. + +**Door-interaction regression check (B.4c carry-forward):** Not +explicitly re-tested this session; no code path touched by B.5 +affects door interaction. + +**Click-NPC bonus (M1 demo target 3 verification):** Not visually +verified this session — log shows `[B.4b] use guid=… name=Novedion +the Gem Seller seq=…` from B.4c testing but ACE response not +re-confirmed here. Carry-forward to next session. + +--- + +## What did NOT work (and why it's not B.5's bug) + +1. **Double-click on a ground item from any distance, or F from > 0.6 m.** + ACE auto-walks the player toward the item (`CreateMoveToChain` → + `PhysicsObj.MoveToObject` + `EnqueueBroadcastMotion(MoveToObject)`), + but our client doesn't handle inbound `MoveToObject` motion broadcasts. + ACE's MoveToChain times out, the chain's `success: false` path sends + `InventoryServerSaveFailed (ActionCancelled)`, and the pickup never + completes. Visible as "character drifts toward item then flips back." + **Filed as #63.** Out of B.5's stated scope (which was: select-first + + F-key wire chain). holtburger's `simulation.rs` has the reference + implementation; would be its own phase (B.6 or similar). + +2. **Local-player pickup animation doesn't render.** Retail observers + see `+Acdream` play the bend-down-and-grab animation; our local view + shows nothing. ACE broadcasts `Motion(MotionCommand.Pickup)` via + `EnqueueBroadcastMotion`, our motion routing probably filters + self-echoes for the local player (motion is normally predicted + locally, not echoed from server). Server-initiated one-shot motions + like Pickup have no local prediction trigger, so they're dropped. + **Filed as #64.** Visual feedback gap only; pickup completes + correctly. + +Both are well-defined follow-up work; neither blocks M1. + +--- + +## Carry-overs from B.4c + +Both pre-existed B.5; neither was touched. + +- **#61** — AnimationSequencer link→cycle boundary frame-0 flash on + door swing. Low severity polish. +- **#62** — PARTSDIAG null-guard for sequencer-driven entities. + Latent; not currently reachable for doors. + +--- + +## M1 status after B.5 + +Demo targets: +1. Walk through Holtburg — met (L.2a-d + L.2g shipped earlier) +2. Open the inn door — met (B.4b + B.4c shipped 2026-05-13) +3. Click an NPC — chain wired (B.4b), not visually re-verified this + session +4. Pick up an item — met, close-range path (this phase) + +Outstanding work for the M1 demo recording: +- Optionally re-verify target 3 (NPC click) once and either confirm + met or file a gap. +- Optionally resolve #63 if the demo wants to show double-click / + out-of-range pickup. The close-range path is sufficient for the + scripted demo scenario. +- Carry-overs #61, #62, #64 are polish; do before recording if + visible on tape. + +--- + +## Reproducibility + +```powershell +Get-Process -Name AcDream.App -ErrorAction SilentlyContinue | Stop-Process -Force +Start-Sleep -Seconds 20 + +$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" +dotnet run --project src\AcDream.App\AcDream.App.csproj -c Debug 2>&1 | + Tee-Object -FilePath "launch-b5.log" +``` + +Log evidence: + +```powershell +Get-Content launch-b5.log -Encoding Unicode | + Select-String -Pattern "\[B\.5\] pickup|\[B\.4b\] pick" +``` + +Expected: a `[B.5] pickup item=… container=0x5000000A seq=…` line for +each successful F-press, preceded by `[B.4b] pick guid=…` from the +single-click that set the selection. + +--- + +## Files touched this session + +- New: `src/AcDream.Core.Net/Messages/PickupEvent.cs` +- New: `tests/AcDream.Core.Net.Tests/Messages/PickupEventTests.cs` +- New: `docs/superpowers/plans/2026-05-14-phase-b5-pickup.md` +- New: `docs/research/2026-05-14-b5-shipped-handoff.md` (this file) +- Modified: `src/AcDream.Core.Net/Messages/InteractRequests.cs` +- Modified: `src/AcDream.Core.Net/WorldSession.cs` +- Modified: `src/AcDream.App/Rendering/GameWindow.cs` +- Modified: `tests/AcDream.Core.Net.Tests/Messages/InteractRequestsTests.cs` +- Modified: `docs/ISSUES.md` (added #63, #64) + +--- + +## State at handoff + +- **Branch:** `claude/phase-b5-pickup`, 6 commits ahead of `main` + (predecessor handoff + 5 implementation commits + this docs commit + land in the same merge). +- **Main HEAD before merge:** `e7842e0` — Merge B.4c. +- **Build state:** worktree compiles cleanly under `dotnet build -c Debug`. +- **Tests:** baseline + 3 new (PickupEvent) + 2 new (BuildPickUp + + sign-correctness) — failure count unchanged. + +Ready for non-fast-forward merge into `main`.