feat(items): Phase F.2 ItemRepository + AppraiseRequest round-trip
Implements the item-state mirror + appraise round-trip infrastructure
on top of Phase F.1's GameEvent dispatcher.
Core layer (AcDream.Core/Items):
- ItemRepository: ConcurrentDictionary-backed live item state keyed by
server ObjectId. Events: ItemAdded, ItemMoved, ItemRemoved,
ItemPropertiesUpdated. MoveItem handles container / slot / equip
location updates atomically and fires ItemMoved with old+new container
ids. UpdateProperties merges a PropertyBundle patch (for appraise
results) without clobbering existing untouched keys.
Wire layer (AcDream.Core.Net/Messages):
- AppraiseRequest (0x00C8 C→S, inside 0xF7B1 GameAction envelope):
Build(sequence, targetGuid) → 16-byte body ready for SendGameAction.
- GameEvents.ParseIdentifyResponseHeader for 0x00C9 S→C — extracts
(guid, appraiseFlags, success). Full PropertyBundle deserialization
(the 10-flag bitfield-indexed tables) is a future pass; header alone
is enough to route into the repository + surface "appraise complete"
to UI.
- GameEvents.ParseWieldObject (0x0023) — server-driven equip.
- GameEvents.ParsePutObjInContainer (0x0022) — server-driven inventory
move (item, container, placement).
Tests (11 new):
- ItemRepository: add/update fires correct event, move updates fields,
missing-id returns false, remove, properties merge, clear.
- Wire: AppraiseRequest byte-exact encoding, IdentifyResponse header
round-trip, WieldObject round-trip, PutObjInContainer round-trip.
Build green, 532 tests pass (up from 521).
Phase F.2 unblocks the Paperdoll + Inventory UI panels and the
"appraise on right-click" UX. Next pieces: PropertyBundle full
deserializer (AppraiseInfo 10-flag bitfield), outbound move/drop/
pickup actions.
Ref: r06 §1 (ItemType), §2 (EquipMask), §5 (appraise wire), §7 (pack
depth rules).
Ref: ACE GameEventIdentifyObjectResponse.cs for AppraiseInfo format.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>