Captures post-B.4c state, click-NPC investigation findings (chain already wired via Tell/CommunicationTransientString/etc; verify opportunistically during B.5 visual test), and B.5 scope decisions made in chat before the user requested a session handoff: - Trigger: F-key (SelectionPickUp action, already bound) - Target: requires _selectedGuid (no pick-under-cursor fallback) - Wire opcode 0x0019 (GameAction.PutItemInContainer) - Payload: itemGuid + containerGuid + placement (12 bytes) - Container = _playerServerGuid - Three changes in two existing files (~50 LOC total) Plus carry-overs from B.4c (#61 cycle-boundary flap, #62 PARTSDIAG null-guard), the B.4b ID-translation gotcha pattern to watch for, and the standard ACE session-race tip. Branch `claude/phase-b5-pickup` (renamed from `claude/investigate-npc-click`) is the workspace; the fresh session should start there. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
10 KiB
Phase B.5 — BuildPickUp + ground-item interaction — fresh-session handoff
Date: 2026-05-13 evening (after B.4c ship).
Branch: claude/phase-b5-pickup (renamed from claude/investigate-npc-click).
Worktree: C:\Users\erikn\source\repos\acdream\.claude\worktrees\investigate-npc-click (directory name kept; only the branch was renamed).
Predecessor on main: e7842e0 — Merge branch 'claude/phase-b4c-door-anim' — Phase B.4c door swing animation.
TL;DR
After B.4c shipped (doors visibly swing open/close), three of M1's four
demo targets are met: walk through Holtburg, open the inn door, and
likely click an NPC (per the investigation below; not yet
visual-verified). The remaining target is pick up an item, which
needs a new outbound wire builder + F-key handler in GameWindow.
Phase B.5 is the slice that closes M1's "click + pickup" demo path. Scope: ~50 LOC across two existing files (no new files). Implementation pattern mirrors B.4b's outbound Use chain.
Investigation findings (carry forward)
Before starting B.5 work, the controller agent investigated whether
B.4b's existing BuildUse chain already handles "click an NPC". Code
reading produced this answer: yes, the basic chat-dialogue case
should already work end-to-end with zero new code. Verify
opportunistically during B.5's visual test by clicking an NPC while
in-world.
Specifically:
- ACE's
Creature.ActOnUseatreferences/ACE/Source/ACE.Server/WorldObjects/Creature.cs:334defers tobase.OnActivate → EmoteManager.OnUse(). The emote manager walks the creature's emote table and emitsTell,CommunicationTransientString,Motion, and other game events. - All those events are already wired in
src/AcDream.Core.Net/GameEventWiring.cs:Tell (0x0027)→chat.OnTellReceived(line 78)CommunicationTransientString (0x028B)→chat.OnSystemMessage(line 83)WeenieError / WeenieErrorWithString→chat.OnSystemMessage(lines 139, 144) PlusUpdateMotion (0xF74D)is already routed for creature entities viaOnLiveMotionUpdated.
UseDone (0x01C7)— the completion ack — has a parser atGameEvents.ParseUseDonebut is not registered with the dispatcher. Silent drop. Harmless for the basic demo (the chat events arrive independently), but worth filing as a follow-up if not picked up by B.5.
Conclusion: click-NPC chain is wired; no code change needed for the M1 demo target 3 acceptance. Verify in-world during B.5's launch.
B.5 scope (decisions already made)
| Decision | Value | Rationale |
|---|---|---|
| Trigger | F-key (InputAction.SelectionPickUp) |
Already bound at KeyBindings.cs:172 |
| Target selection | Requires _selectedGuid (B.4b's renamed field) |
Mirrors retail F-key behavior + B.4b's UseSelected pattern. User single-clicks the ground item to select, then F to pickup. |
| Wire opcode | GameAction.PutItemInContainer (0x0019) |
ACE source: references/ACE/Source/ACE.Server/Network/GameAction/Actions/GameActionPutItemInContainer.cs |
| Wire payload | 12 bytes: itemGuid (u32) + containerGuid (u32) + placement (i32) |
Same source |
| Container destination | The player's own server guid (_playerServerGuid) |
Single-bag pickup; bag-specific destinations are M2+ work |
| Placement value | 0 (let server pick slot) | Simplest; placement-control UI is M2+ |
| Visual feedback | Toast + [pickup] log line |
No inventory UI yet; the existing WieldObject / InventoryPutObjInContainer server events already update ItemRepository so the state is correct internally |
| Pick under cursor fallback | NO | Out of scope per user decision. Strict select-first UX. |
Brainstorm explicitly NOT done with the user (interrupted before the design sections were presented). The new session should re-confirm these decisions are still desired before writing the spec — or just proceed if they remain obviously right.
Three changes B.5 needs to land
-
src/AcDream.Core.Net/Messages/InteractRequests.cs— addBuildPickUp(uint gameActionSequence, uint itemGuid, uint containerGuid, int placement). Pattern: same as the existingBuildUseWithTargetbuilder at line 51 of that file. 20-byte total body (0xF7B1 envelope + seq + opcode 0x0019 + 12-byte payload). -
src/AcDream.App/Rendering/GameWindow.cs— add a private helperSendPickUp(uint itemGuid):- Gate on
_liveSession?.CurrentState == InWorld(same pattern as B.4b'sSendUse). seq = _liveSession.NextGameActionSequence().body = InteractRequests.BuildPickUp(seq, itemGuid, _playerServerGuid, 0)._liveSession.SendGameAction(body).- Diagnostic:
Console.WriteLine($"[pickup] item=0x{itemGuid:X8} container=0x{_playerServerGuid:X8} seq={seq}").
- Gate on
-
src/AcDream.App/Rendering/GameWindow.csOnInputActionswitch — addcase InputAction.SelectionPickUp:near the otherSelect*/UseSelectedcases (B.4b added those around line 8633+). Body:if (_selectedGuid is uint sel) SendPickUp(sel); else _debugVm?.AddToast("Nothing selected");.
That's the whole code change. ~50 LOC including diagnostics.
Likely ID-translation gotcha (the L.2g slice 1c pattern)
B.4b's L.2g slice 1c surfaced an ID-space mismatch: the BuildUse
wire builder takes a targetGuid which is entity.ServerGuid, but
ShadowObjectRegistry keys by entity.Id. For BuildPickUp:
itemGuidargument must beentity.ServerGuid(the server's identifier — ACE looks it up in its world). ✅ B.4b's picker returnsServerGuid, so_selectedGuidalready carries the right value.containerGuidargument must be_playerServerGuid(the server's identifier for the player). ✅ Already a ServerGuid inGameWindow.
So B.5 should NOT hit the same ID-mismatch trap L.2g slice 1c did. But re-check at implementation time.
ACE inbound chain (already wired)
After ACE processes a BuildPickUp, it broadcasts:
0x019B InventoryPutObjInContainer— moves the item record into the player's container. Already wired toItemRepository.MoveItem(itemGuid, containerGuid, placement)atGameEventWiring.cs:239.RemoveObjectfor the world-spawned item — already wired (existing despawn path removes the ground item from view).- Possibly
WieldObjectif the item auto-equips — already wired (GameEventWiring.cs:231).
No new inbound wiring needed for the minimum demo. The user will see:
- Click ground item → selection updates.
- Press F → diagnostic logs, packet sent.
- ACE processes; sends inventory + despawn events.
- Item disappears from ground.
- (No inventory UI yet, but item is in
ItemRepository.)
Acceptance criteria
dotnet buildgreen.dotnet testgreen: 1046 / 8 pre-existing-baseline fail (unchanged from main HEAD).- At Holtburg, drop a test item on the ground (via
/dropserver command or have ACE spawn one for the test character), then:- Single-click the item —
_selectedGuidupdates, B.4b's[pick]diagnostic shows the item's guid. - Press F — log shows
[pickup] item=0x... container=0x5000000A seq=N. - Item disappears from the ground.
- No regressions on door interaction (B.4b/B.4c still work).
- Single-click the item —
- Bonus: click-NPC verification. While in-world, single-click an NPC and press F (or double-click). Expected: NPC chat appears in the chat panel. If it does → M1 demo target 3 confirmed met. If not → file the gap.
docs/ISSUES.mdclosure entry for whatever issue (if any) was filed for the pickup gap.- Roadmap + CLAUDE.md updated.
Reproducibility
Same launch recipe as B.4c. Per CLAUDE.md "Logout-before-reconnect", wait 20-45s between client launches to let ACE clear stale sessions.
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"
$env:ACDREAM_PROBE_BUILDING = "1"
dotnet run --project src\AcDream.App\AcDream.App.csproj -c Debug 2>&1 |
Tee-Object -FilePath "launch-b5.log"
Log grep:
Select-String -Path launch-b5.log -Pattern "pickup|\[pick\] guid=|UseDone|\[B.4b\] pick"
Carry-overs from B.4c (don't lose track)
- #61 — AnimationSequencer link→cycle frame-0 flash on door swing. Visible as brief flap at end of swing animation. Low-severity polish.
- #62 — PARTSDIAG null-guard for sequencer-driven entities. Latent; not currently reachable for doors. One-line fix.
- Worktree at
.claude/worktrees/phase-b4c-door-animstill on disk (submodules blockedgit worktree removeper B.4b precedent). Manual cleanup after this session:rm -rfthe directory +git worktree prune+git branch -D claude/phase-b4c-door-anim.
State at handoff
- Branch:
claude/phase-b5-pickup(renamed fromclaude/investigate-npc-click). - Worktree directory:
.claude/worktrees/investigate-npc-click(cosmetic mismatch with branch name; harmless). - Commits ahead of main: 1 after this handoff lands.
- Main HEAD:
e7842e0. - Build state: worktree compiles cleanly (verified via
dotnet build -c Debug). Tests at 1046/8 baseline. - Submodule state:
references/WorldBuilderinitialized.references/ACENOT initialized in this worktree — use the main repo'sreferences/ACEfor ACE source reads, or init viagit submodule update --init --depth=1 references/ACEif extensive reading is needed.
Why a fresh session
This session accumulated ~10 hours of context across L.2g, B.4b, and B.4c — the working set is large enough that starting B.5 cold lets the new session work with a clean context budget and avoids the compaction risk that hit the prior B.4b session.
The prompt for the new session is in the controller's reply that created this handoff (the chat message immediately after this commit).