# Social-panel completion batch — exact wire mechanics (2026-08-13) Read-only research for the user-ordered social-panel completion features: friends add/remove, appear-offline, squelch add, fellowship option operations, the three fellowship/allegiance character-option bits, and world self-selection. Every claim below is cited to primary source; the usual three-way agreement rule (retail decomp + ACE + a third reference) is applied per message. `str16L` = `[u16 byteLen][Windows-1252 bytes][pad to 4-byte multiple counting the 2 length bytes]` throughout (see `2026-08-11-fa-fellowship-wire.md` §3 for the envelope conventions — C→S GameAction body = `[u32 0xF7B1][u32 seq][u32 subOpcode][payload]`, S→C GameEvent body = `[u32 0xF7B0][u32 targetGuid][u32 eventSeq][u32 eventType][payload]`; both already shipped in acdream). **Headline finding:** the entire C→S sender layer, the inbound S→C parser/state layer, AND the generation-gated Runtime command surface for features 1–3 **already exist in acdream** (built for the CH command registry and Campaign FA). The implementation gap is almost purely *panel wiring*: the Friends/Squelch pages are deliberately read-only (FA slice FA3 scope) and no UI affordance drives the existing commands. Feature 4 (self-selection) is the one place acdream *diverges* from retail behavior today. --- ## 1. Add friend / remove friend / appear offline / squelch add ### 1.1 AddFriend — C→S `0x0018` | | | |---|---| | Body | `[u32 0x0018][str16L characterName]` | | Retail sender | `CM_Social::Event_AddFriend @0x006A5C10` (buffer `strPackSize + 0xC`; opcode u32, then `PStringBase::Pack`) | | Retail UI caller | `gmFriendsUI::Request_AddFriend @0x0048D240` — Add button (`idElement 0x10000514`) reads the name edit box in `gmFriendsUI::ListenToElementMessage @0x0048D520`, clears it, dims the Add button (`SetState(0xD)`). Chat-command route: `gmFriendsUI::RecvNotice_ChatCommand_AddFriend @0x0048D470` | | ACE handler | `references/ACE/Source/ACE.Server/Network/GameAction/Actions/GameActionAddFriend.cs:7-12` (`GameActionType.AddFriend = 0x0018`, `GameActionType.cs:12`) → `Player.HandleActionAddFriend`, `ACE.Server/WorldObjects/Player_Character.cs:141-172` | | ACE validation | rejects self-add ("can't be friends with yourself"), unknown name ("That character does not exist"), duplicate ("already in your friends list") — each as a `ChatPacket.SendServerMessage` broadcast text, NOT a WeenieError | | ACE success flow | DB add → **incremental** `GameEventFriendsListUpdate` with `FriendsUpdateTypeFlag.FriendAdded (0x0001)` (`Player_Character.cs:169`) + a "`{name} has been added to your friends list.`" text | | Cross-check | Chorizite `Messages/C2S/Actions/Social_AddFriend.generated.cs:24-26` (str16L only). holtburger: opcode known but **commented out / no sender** (`crates/holtburger-protocol/src/opcodes.rs:427`) | | acdream builder | `src/AcDream.Core.Net/Messages/ClientCommandRequests.cs:132-133` (`BuildAddFriend`, retail address cited in-file) | ### 1.2 RemoveFriend — C→S `0x0017`, RemoveAllFriends — C→S `0x0025` | | | |---|---| | RemoveFriend body | `[u32 0x0017][u32 friendGuid]` — **guid, not name.** The retail Remove button resolves the selected list row's InstanceID attribute (`GetAttribute_InstanceID(…, 0x10000085, …)` at `@0x0048D67D`) and sends `CM_Social::Event_RemoveFriend @0x006A5650` (buffer 0x10). The `/friends remove ` chat route (`RecvNotice_ChatCommand_RemoveFriend @0x0048DAC0`) resolves name→guid client-side first (`@0x0048dbde`). | | RemoveAllFriends body | `[u32 0x0025]` — parameterless. `CM_Social::Event_ClearFriends @0x006A55C0` (buffer 0xC), chat route `RecvNotice_ChatCommand_RemoveAllFriends @0x0048E020`. | | ACE handlers | `GameActionRemoveFriend.cs:6-11` (`ReadUInt32` guid) → `HandleActionRemoveFriend` (`Player_Character.cs:178-198`): not-found → text error; success → incremental `0x0021` with `FriendRemoved (0x0002)` + text. `GameActionRemoveAllFriends.cs` → `HandleActionRemoveAllFriends` (`Player_Character.cs:203-211`): DB clear, **NO S→C list update at all** (the client is expected to clear its own list — retail does, in the `/friends removeall` handler). | | Cross-check | Chorizite `Social_RemoveFriend.generated.cs:18-25` (`ObjectId` u32). | | acdream builders | `ClientCommandRequests.cs:135-139` (`BuildRemoveFriend`, `BuildClearFriends`). | ### 1.3 S→C FriendsListUpdate — `0x0021` (verified; it IS the 0x0021 family) `GameEventType.FriendsListUpdate = 0x0021` (`ACE.Server/Network/GameEvent/GameEventType.cs:9`; Chorizite `Social_FriendsUpdate.generated.cs`). Writer: `GameEventFriendsListUpdate.cs:56-99`: ``` u32 count count × FriendData: u32 friendId u32 online (0/1; forced 0 when the friend has AppearOffline set — :74-79) u32 appearOffline (ACE always writes 0 — :84) str16L friendName u32 numFriendsOfFriend (ACE: always 0 / TODO) [u32 ids…] u32 numFriendOf (ACE: always 0 / TODO) [u32 ids…] u32 updateType — LAST field, after the list: 0=FullList 1=FriendAdded 2=FriendRemoved 4=FriendStatusChanged ``` Retail parser: `CM_Social::DispatchUI_FriendsUpdate @0x006A5DD0` → `FriendData::UnPack @0x005B9D20`; UI application: `gmFriendsUI::ServerSays_AddFriend @0x0048DEF0` / `ServerSays_RemoveFriend @0x0048DFB0` / `ServerSays_UpdateFriend @0x0048DCD0`. The full list arrives unprompted at login (`Player_Networking.cs:117` sends it with the login bundle). **acdream already parses and owns this**: `src/AcDream.Core.Net/Messages/SocialStateMessages.cs:13-43` (`ParseFriendsUpdate`, retail addresses cited in-file) → registered at `GameEventWiring.cs:341-346` → `AcDream.Core/Social/FriendsState.cs` (`Apply` handles Full/Add/Remove/OnlineStatus), owned by J4.1's `RuntimeCommunicationState`. Nothing to build inbound. ### 1.4 Appear offline — a character option, NOT a dedicated GameAction There is **no** standalone appear-offline opcode. `AppearOffline` is `PlayerOption 0x27`, stored in `options2_` bit 12 (mask `0x00001000` — retail `PlayerModule::GetOption` switch `@0x005D3D56-67`; ACE `CharacterOptions2.cs:25`). It is in retail's 21-entry auto-save table (`CPlayerModule::IsAutoSaveOption @0x0059A600`, byte-verified list in `2026-08-10-character-options-map.md` §1.3 — id 0x27 IS auto-save), so setting it sends **`SetSingleCharacterOption (0x0005)` `[u32 0x27][u32 value]` immediately** via `CM_Character::Event_PlayerOptionChangedEvent @0x006A1510`, dispatched from `CPlayerModule::OnChanged @0x0059A8E0` (`@0x0059a99d-9b6`). ACE side (`GameActionSetSingleCharacterOption.cs:16-18`): the 0x0005 handler special-cases `AppearOffline` → `Player.SetAppearOffline` (`Player_Character.cs:221-226`), which flips the option and calls `SendFriendStatusUpdates` (`Player_Networking.cs:274-294`): every online player who has YOU friended receives an incremental **`0x0021` with `FriendStatusChanged (0x0004)` and the forced online flag** plus a "`{Name} has gone offline.`" text — i.e., other players genuinely see you go offline. Login/logout honor it too (`PlayerManager.cs:430,464`), and the full-list writer masks you out of other players' lists (`GameEventFriendsListUpdate.cs:76-79`). ACE deliberately does **not** persist it (`Player_Character.cs:219` comment). **Open retail question (carried, not new):** the 2013 client has NO UI row and no `SetAppearOffline` caller outside the generic `SetOption` switch — how retail's own client ever set 0x27 is **UNKNOWN** (options map §3 row `0x27` + §8 item U4). Any acdream checkbox for it is an acdream-authored affordance driving a fully retail wire path. `HeadlessConfigurationLoader.cs:45` already declares it by name for bots. ### 1.5 Squelch add/remove (by name) — C→S `0x0058` / `0x0059` / `0x005B` | Opcode | Body | Retail sender | |---|---|---| | `0x0058 ModifyCharacterSquelch` | `[u32 add(0/1)][u32 characterGuid][str16L characterName][u32 chatMessageType]` | `CM_Communication::Event_ModifyCharacterSquelch @0x006A42D0` (buffer `strPackSize + 0x18`; field order byte-read from the body: opcode, arg1=add, arg2=guid, Pack(name), arg4=type) | | `0x0059 ModifyAccountSquelch` | `[u32 add][str16L characterName]` | `Event_ModifyAccountSquelch @0x006A41E0` | | `0x005B ModifyGlobalSquelch` | `[u32 add][u32 chatMessageType]` | `Event_ModifyGlobalSquelch @0x006A3D00` | **Add-by-name is native**: retail's squelch panel sends `guid = 0` with the typed name (`gmSquelchUI::ListenToElementMessage @0x0048C860`, add call `@0x0048CA0D`: `Event_ModifyCharacterSquelch(1, 0, &name, 1)` — `chatMessageType 1 = AllChannels`, ACE `ChatMessageType.cs:47`). The account-checkbox variant sends `0x0059` instead (`@0x0048C99C`). The chat-window speaker toggle goes through `gmMainChatUI::ToggleSquelchOnCurrentSpeakableTarget @0x004CD230` (`@0x004CD2F6`). ACE (`GameActionModifyCharacterSquelch.cs:12-17` → `SquelchManager.HandleActionModifyCharacterSquelch`, `ACE.Server/WorldObjects/Managers/SquelchManager.cs:74-124`): - channel legality check (`IsLegalChannel`, :78) → text error if bad; - **guid≠0 looks up by guid; guid==0 falls back to name lookup** (:86-107) — so name-only add works; - self-squelch rejected (:109-113); - on success `UpdateSquelchDB()` + `SendSquelchDB()` (:121-123) — the S→C answer is always a **full `0x01F4 SetSquelchDB` replacement**, never an incremental. Account (`:195-250`) and global (`:255-279`) do the same. S→C `0x01F4 SetSquelchDB` (`GameEventType.cs:51`; writer `GameEventCommunicationSetSquelch.cs` + `Network/Structure/SquelchDB.cs:158-165` + `SquelchInfo.cs:50-65`): ``` PackableHashTable accounts — ALWAYS EMPTY in retail pcaps (ACE writes header 0/0) PackableHashTable characters — account squelches folded in with Account=true SquelchInfo globals SquelchInfo = [u32 filterWordCount][u32 mask × count][str16L playerName][u32 isAccount] ``` ACE writes each character's filter list as the same mask **4×** (`SquelchInfo.cs:21-27` — "if not sent 4x, the checkbox in the chat menu doesn't toggle"; why retail wants 4 words is an ACE-acknowledged UNKNOWN). Chorizite `Communication_SetSquelchDB.generated.cs` agrees on the envelope. acdream already parses it — `SocialStateMessages.ParseSquelchDatabase` (`:45-61`, retail `SquelchDB::UnPack @0x006B1900` cited) → `GameEventWiring.cs:350-355` → `AcDream.Core/Social/SquelchState.cs` (full `Replace` semantics, matching the wire). ### 1.6 acdream seam — feature 1 is wiring-only Already shipped (verify, don't rebuild): - Builders: `ClientCommandRequests.cs:132-188` (all six, retail addresses cited). - Session sends: `WorldSession.cs:2235-2274` (`SendAddFriend`, `SendRemoveFriend`, `SendClearFriends`, `SendModifyCharacterSquelch`, `SendModifyAccountSquelch`, `SendModifyGlobalSquelch`) and `:2202` (`SendSetSingleCharacterOption` for appear-offline). - Runtime commands (generation-gated, host-shared): `src/AcDream.Runtime/GameRuntimeCommands.cs:264-300` (`RuntimeFriendCommand` Add/Remove/Clear/RequestLegacyList, `RuntimeSquelchCommand` Character/Account/Global scopes, `IRuntimeSocialCommands`), executed at `DirectGameRuntimeCommandAdapter.cs:722-800`; graphical route bound at `LiveSessionRuntimeFactory.cs:520-525/595-599`. - Option path: `RuntimeCharacterOptionsState.TrySetOption` (`RuntimeCharacterState.cs:806-838`) auto-save dispatch → `SendSetSingleCharacterOption` (adapter `:677`). To build: Friends-page Add (edit box + button → `RuntimeFriendCommand Add` with the typed name), Remove (selected row's guid → `Remove`), appear-offline checkbox (option id `0x27` through the existing option seam — note U4: no retail UI row to copy), Squelch-page add-by-name (`RuntimeSquelchCommand` Character scope, guid 0, type `1 = AllChannels`, account checkbox → Account scope). The pages (`SocialFriendsPageController.cs`, `SocialSquelchPageController.cs`) are currently read-only by FA-D1 design. --- ## 2. Fellowship option operations Full derivation in `2026-08-11-fa-fellowship-wire.md` (lane B); this section resolves the specific "change after creation" questions. ### 2.1 What CAN change after creation — openness only: `0x0291` `FellowshipChangeOpenness` `[u32 isOpen]` — `CM_Fellowship::Event_ChangeFellowOpeness @0x006A6040`, Open button case 9 in `gmFellowshipUI` (client pre-toggles its local `_open_fellow` optimistically `@0x0049038A`). ACE: `GameActionFellowshipChangeOpenness.cs:7-12` → `Player_Fellowship.cs:33-48` — **leader-only** (`WeenieError.YouMustBeLeaderOfFellowship 0x050F`-family refusal) and refused when locked (`WeenieError.FellowshipIsLocked`). Success → `Fellowship.UpdateOpenness` (`Fellowship.cs:410-415`) → `SendWeenieErrorWithStringAndUpdate(_IsNowOpenFellowship / _IsNowClosedFellowship, name)` — i.e. every member gets the WeenieErrorWithString **plus a full `0x02BE FellowshipFullUpdate`**. acdream: builder `SocialActions.BuildFellowshipChangeOpenness` (`SocialActions.cs:223-231`), Runtime command `IRuntimeFellowshipCommands.SetOpen` (`GameRuntimeCommands.cs:339-341`) — **both already shipped (FA1/FA2)**. ### 2.2 What CANNOT change after creation — ShareXP and ShareLoot - **ShareXP** (retail option `FellowshipShareXP 0x0F`, named `ShareFellowshipExpAndLuminance` in ACE — luminance rides the same flag) is a **create-time-only wire field**: `0x00A2 FellowshipCreate` = `[str16L name][u32 shareXP]` (retail `Event_Create @0x006A67A0` reads the option at create; ACE `Fellowship.cs:51-54` latches `DesiredShareXP`/`ShareXP` in the constructor). There is **no GameAction to change it later** — the seven C→S fellowship opcodes are `0x00A2–0x00A6`, `0x0290`, `0x0291`, period (lane B §3; ACE's `GameActionType.cs` has nothing else fellowship-shaped). Toggling the 0x0F option after creation changes only what your NEXT create sends. Level-spread proportional-vs-even demotion is server-side (`Fellowship.CalculateXPSharing`), reflected to clients as `_share_xp = 0` inside `0x02BE`. - **ShareLoot** is not on the create message at all: ACE reads the LEADER's `ShareFellowshipLoot (0x11)` character option **once, in the Fellowship constructor** (`Fellowship.cs:56-57`), enforces it at `Corpse.cs:192-198`, and echoes it per-fellow as the `shareLoot` u32 in the `Fellow` record. No post-create wire op exists in either ACE or the retail client (lane B master-table row 31). The only lever is flipping option 0x11 (auto-save → immediate `0x0005`) **before** creating. ### 2.3 Panel visibility — `0x00A6 FellowshipUpdateRequest` (not an option, but required) `[u32 panelOpen]` from `gmFellowshipUI::OnVisibilityChanged @0x0048E460`. ACE `GameActionFellowshipUpdateRequest.cs:8-14` → `HandleFellowshipUpdateRequest` (`Player_Fellowship.cs:142-148`): sets `FellowshipPanelOpen`, replies with a full `0x02BE` when open, and the flag **gates the whole `0x02C0` member-vitals stream** (`Fellowship.cs:723`). Already shipped: `BuildFellowshipUpdateRequest` (`SocialActions.cs:190-198`) and `IRuntimeFellowshipCommands.SetPanelOpen` — the fellowship page's show/hide seam must keep calling it. ### 2.4 Auto-accept — server-side, driven by the option bits (see §3) `Fellowship.AddFellowshipMember` (`Fellowship.cs:76-132`): inviter-side recruit → busy check → **`AutomaticallyAcceptFellowshipRequests` on the TARGET short-circuits the confirmation dialog** (`Fellowship.cs:120-123` → `AddConfirmedMember` directly); otherwise a `Confirmation_Fellowship` popup is enqueued. The `IgnoreFellowshipRequests` filter fires earlier, on the inviter's `FellowshipRecruit` (`Player_Fellowship.cs:98-102` — `WeenieError.FellowshipIgnoringRequests` + "not accepting fellowship requests" text). Neither bit generates any wire traffic of its own at recruit time — they are read from the server's stored option words. --- ## 3. The three character-option bits — values, wire class, ACE reads | Option | `PlayerOption` id (0x0005 payload) | Word | Mask | Retail accessor | Auto-save? | Client default | |---|---|---|---|---|---|---| | `IgnoreAllegianceRequests` | `0x01` | options1 | `0x00000004` | (options map §3) | **yes** | false | | `IgnoreFellowshipRequests` | `0x02` | options1 | `0x00000008` | `@0x005D2B30` / `@0x005D2B40` | **yes** | **true** | | `FellowshipAutoAcceptRequests` (ACE: `AutomaticallyAcceptFellowshipRequests`) | `0x12` | options1 | `0x20000000` | `@0x005D3020` / `@0x005D3030` | **yes** | false | | (context) `AppearOffline` | `0x27` | options2 | `0x00001000` | `@0x005D3740` | **yes** | false | Sources: retail bit map `PlayerModule::GetOption @0x005D3AA0` switch; ACE `CharacterOptions1.cs:16-17`, `CharacterOption.cs:18-22,70`, `CharacterOptions2.cs:25`; auto-save membership from the byte-verified `IsAutoSaveOption @0x0059A600` table (`2026-08-10-character-options-map.md` §1.3 — all four ids are in the 21-entry auto-save set). **Wire class:** all three (plus AppearOffline) ride **`0x0005 SetSingleCharacterOption` immediately** — `[u32 optionId][u32 value]` — and never *trigger* the batched `0x01A1` PlayerModule blob. (Their bits still appear inside the blob's options1/options2 words whenever some *other* dirty option flushes it — that is correct and harmless; ACE stores the whole word.) **Mutual exclusion** (`CPlayerModule::OnChanged @0x0059A8E0`, case 2 / case 0x12, decomp lines `@0x0059a971-98d`): setting `IgnoreFellowshipRequests` clears `FellowshipAutoAcceptRequests` and vice versa, each clear going through the real accessor so the CLEARED option's own `0x0005` is emitted first. acdream models this exactly — `RuntimeCharacterState.cs:821-834` (MF-2), and `HeadlessConfigurationLoader.cs:254-266` rejects both-true bot configs. **acdream option table** (`src/AcDream.Runtime/Gameplay/CharacterOptionTable.cs`): - `:112` `IgnoreAllegianceRequests` — options1, `0x00000004`, autoSave, default false - `:113` `IgnoreFellowshipRequests` — options1, `0x00000008`, autoSave, **default true** - `:129` `FellowshipAutoAcceptRequests` — options1, `0x20000000`, autoSave, default false - `:150` `AppearOffline` — options2, `0x00001000`, autoSave, default false Id enum: `CharacterOptionId` (`SocialActions.cs:425-480`). Send seam: `RuntimeCharacterOptionsState.TrySetOption` (`RuntimeCharacterState.cs:806-838`, `entry.IsAutoSave → sendAutoSave(id, value)`) wired to `WorldSession.SendSetSingleCharacterOption` (`WorldSession.cs:2202`) at `DirectGameRuntimeCommandAdapter.cs:677`. Headless bots declare all of them by name (`HeadlessConfigurationLoader.cs:30-45`). **ACE READS each bit server-side (the D6 finding, confirmed with code):** - `IgnoreFellowshipRequests` — `ACE.Server/WorldObjects/Player_Fellowship.cs:98` (`FellowshipRecruit`: `newPlayer.GetCharacterOption(...)` → inviter gets `WeenieError.FellowshipIgnoringRequests`). Pure server-side filter; the target's client never sees the attempt. - `AutomaticallyAcceptFellowshipRequests` — `ACE.Server/Entity/Fellowship.cs:120` (`AddFellowshipMember`: bypasses the `Confirmation_Fellowship` popup, joins immediately). Also settable via ACE's own `/option` player command alias `AutoAcceptFellowRequest` (`PlayerCommands.cs:221`). - `IgnoreAllegianceRequests` — `ACE.Server/WorldObjects/Player_Allegiance.cs:317` (`IsPledgable`, checked during SwearAllegiance: swearer gets `WeenieError.YourOfferOfAllegianceWasIgnored` + "Your offer of allegiance was ignored." text). - `AppearOffline` — `Player_Character.cs:213-226`, `GameEventFriendsListUpdate.cs:76-79`, `PlayerManager.cs:430,464` (see §1.4). The client-side consumer status in the FA fix round stands: these three are **store-only** on the acdream client (`CharacterOptionsPageController.cs:244-249` — D6 correction comments); the server does all the filtering. --- ## 4. World self-selection — retail allows it; acdream blocks it ### 4.1 Retail: no self-exclusion anywhere in the selection pipeline The retail pick is render-driven: `SmartBox::find_object @0x00451C60` arms `Render::set_selection_cursor` + `lookingForObject = 1`; during the next frame every drawn part runs the ray test; `SmartBox::DrawNoBlit @0x00454C20` (`@0x00454C4E-70`) reads the raw winner (`Render::GetMouseSelectionObjectID`) and publishes it via `ECM_UI::SendNotice_SmartBoxObjectFound`. The eligibility chain: 1. `CPhysicsPart::Draw @0x0050D7A0` (`@0x0050D823-35`): sets `Render::check_curr_object = 1` for **any part whose `physobj->id` is nonzero** (or `creature_mode`). No player-id comparison exists. 2. `Render::GfxObjUnderSelectionRay @0x0054C740`: sphere test (`CSphere::sphere_intersects_ray`) then per-polygon (`CPolygon::polygon_hits_ray`), keeping the closest winner in `Render::m_MouseSelectData`. No identity filtering. 3. `UIElement_SmartBoxWrapper::RecvNotice_SmartBoxObjectFound @0x004E5AD0`: for the ordinary reasons — `sr_Select` (left click, set `@0x004E5805`) and `sr_Examine` (right click, `@0x004E58B8`) — it pulses lighting and calls `ACCWeenieObject::SetSelectedObject(foundId, 0)` **unconditionally** (`@0x004E5BAE`); `sr_Examine` additionally calls `ClientUISystem::ExamineObject` with the raw found id (`@0x004E5C0A`). 4. `ACCWeenieObject::SetSelectedObject @0x0058C2E0`: no filter of any kind — it swaps `selectedID`, fires `SendNotice_SelectionChanged`, and notifies plugins. **The only self-related gate in the whole flow** is the double-click-use branch (search reason 4): `if (found->pwd._wielderID != SmartBox::player_id)` before `ItemHolder::UseObject` (`@0x004E5BE9`) — clicking your own **wielded item** selects/flashes but does not Use. acdream already ports that exact gate (`SelectionInteractionController.cs:169-180` comment + `IsWieldedByPlayer`). Conclusion: **retail lets you left-click-select and right-click-examine your OWN character** whenever your model is drawn (third person). In first person the model simply isn't rendered, so it can't win the ray — an emergent property, not a filter. ### 4.2 acdream: the exclusion is ours, and it is deliberate but retail-divergent - Skip mechanism: `src/AcDream.Core/Selection/RetailWorldPicker.cs:29` — `if (part.ServerGuid == 0u || part.ServerGuid == skipServerGuid) continue;` - Fed from `src/AcDream.App/Rendering/Selection/RetailSelectionScene.cs:220-233` (`Pick(..., uint skipServerGuid)`) ← `src/AcDream.App/Interaction/WorldSelectionQuery.cs:145-154` (`PickAt(..., bool includeSelf)` passes `includeSelf ? 0u : _playerGuid()`). - Callers (`src/AcDream.App/Interaction/SelectionInteractionController.cs`): - `:151` `PickAndStoreSelection` — ordinary left click passes `_items.IsAnyTargetModeActive`, i.e. **self is excluded except in spell/item target mode**; - `:199` `PickSelectAndExamine` — right-click examine passes `includeSelf: false` — **self-examine impossible**; - `:101` `PlaceDraggedItem` and `InteractionRetainedUiComposition.cs:497` pass `true`. To match retail, the select (`:151`) and examine (`:199`) paths should pass `includeSelf: true` (target-mode already does). If the exclusion is retained instead, it needs a `retail-divergence-register.md` row — none exists today (the register has no row for self-picking; this is an unregistered deviation). Downstream note for the implementer: `WorldSelectionQuery.PickAt` verifies the winner through `LiveEntityRuntime.TryGetPickEligibleRecord`; confirm the local player's record passes that eligibility check before assuming the one-line flag flip is sufficient. --- ## 5. UNKNOWNs (explicit) 1. **U4 (carried):** how retail's 2013 client sets `AppearOffline 0x27` — no UI row, no non-generic setter call site (`2026-08-10-character-options-map.md` §3 row 0x27, §8 U4). Any acdream checkbox is a new affordance on a retail wire path. 2. **SquelchInfo 4× filter list:** why retail wants the mask repeated four times (`SquelchInfo.cs:21-27` — ACE's own "not sure"). Parse side already tolerant (acdream reads ≤4 words). 3. **Account-squelch key semantics:** ACE is unsure whether the (always empty) top-level accounts table keyed on account name or character name (`SquelchDB.cs:20-28`). Moot for a client — never populated. 4. **Per-channel character squelch:** no retail pcaps exist (`SquelchInfo.cs:24-25`); ACE accepts a `chatMessageType` mask but the panel only ever sends `AllChannels (1)`. 5. **`RemoveAllFriends` S→C silence:** ACE sends no `0x0021` after `0x0025`; retail clears its own list. acdream's `FriendsState` has no "clear on 0x0025 ack" hook — the panel action should clear the local state the way retail's chat handler does, or simply re-request. (Behavior choice for the implementer, flagged, not researched deeper.) 6. **Self-pick eligibility depth (§4.2):** whether `TryGetPickEligibleRecord` admits the local player's record today was not verified — check before shipping the includeSelf flip. ## 6. Ten-line summary 1. AddFriend `0x0018 [str16L name]`, RemoveFriend `0x0017 [u32 guid]`, RemoveAllFriends `0x0025` []; ACE answers with incremental `0x0021 FriendsListUpdate` (updateType LAST: 0/1/2/4) — acdream builders, WorldSession sends, `RuntimeFriendCommand`, and the `0x0021` parser ALL exist; only panel wiring is missing. 2. Appear-offline is CharacterOption `0x27` (options2 `0x1000`), auto-save → immediate `0x0005`; ACE's handler routes it to `SetAppearOffline`, which pushes `0x0021 FriendStatusChanged` to everyone who friended you. No retail UI row exists (U4) — our checkbox is a new affordance on a retail wire path. 3. Squelch add-by-name is native: `0x0058 [u32 add][u32 guid=0][str16L name][u32 type=1 AllChannels]`; ACE falls back to name lookup and always answers with a FULL `0x01F4 SetSquelchDB` replace — sender, Runtime command, and parser all shipped. 4. Fellowship post-create ops: openness is the ONLY one — `0x0291 [u32 isOpen]`, leader-only, refused when locked, answered by WeenieErrorWithString + full `0x02BE`. Builder + `IRuntimeFellowshipCommands.SetOpen` already exist (FA1/FA2). 5. ShareXP is a create-time field of `0x00A2` and ShareLoot is the leader's option `0x11` read once in ACE's Fellowship constructor — NO post-create wire op exists for either; the panel must dim them for an existing fellowship. 6. `0x00A6` is panel VISIBILITY (gates ACE's `0x02C0` vitals stream), not openness — keep `SetPanelOpen` wired to page show/hide. 7. The three bits: IgnoreAllegianceRequests `0x01`/options1 `0x04`, IgnoreFellowshipRequests `0x02`/options1 `0x08` (default TRUE), FellowshipAutoAcceptRequests `0x12`/options1 `0x20000000`; all auto-save → `0x0005` immediate, never triggering `0x01A1`; the 0x02↔0x12 mutual exclusion emits the cleared option's `0x0005` first (already modeled, MF-2). 8. ACE reads all three server-side: recruit filter `Player_Fellowship.cs:98`, auto-accept `Fellowship.cs:120`, swear filter `Player_Allegiance.cs:317` — client stays store-only (D6 stands). 9. Retail has NO self-selection exclusion: any drawn physobj is ray-eligible (`CPhysicsPart::Draw @0x0050D823`), and found ids are selected/examined unconditionally (`@0x004E5BAE/0x004E5C0A`); the only self gate is the wielded-item double-click Use check (`@0x004E5BE9`, already ported). 10. acdream's self-exclusion is an unregistered divergence: `RetailWorldPicker.cs:29` skip fed by `SelectionInteractionController.cs:151/:199` passing includeSelf=false — flip to true (after checking pick-eligibility admits the local player) or add a register row.