Commit graph

1 commit

Author SHA1 Message Date
Erik
6bedbc4772 feat(net): FA1 -- S->C parsers for fellowship/allegiance, confirmation triple, allegiance version gates
Campaign FA slice FA1: pure parse functions + typed records only,
UNWIRED (FA2 registers them against the new RuntimeFellowshipState/
RuntimeAllegianceState owners -- see docs/research/2026-08-11-fa-acdream-seams.md
§2).

Fellowship family (GameEvents.cs), field orders from lane B §3.8-§3.13,
guid-first on 0x02C0 per the resolved Chorizite disagreement:
FellowshipFullUpdate (0x02BE), FellowshipUpdateFellow (0x02C0),
FellowshipQuitNotice/FellowshipDismissNotice (S->C 0x00A3/0x00A4),
FellowshipDisband (0x02BF, empty body), and the dead
FellowshipFellowUpdateDone/FellowshipFellowStatsDone (0x01C9/0x01CA,
parse-and-ignore, must never fail per lane B §2.7). ShareLoot is modeled
as a raw uint (D5) -- ACE encodes it two incompatible ways (0x10 in full
updates, <<1 incremental), so `!= 0` is the only safe read, never `== 1`.

Confirmation triple (D6): grepping the tree showed 0x0274/0x0276 already
had typed parsers in Core.Net; 0x0275 (client-authored) already had a
byte-correct builder but no typed representation. Added the
ConfirmationType enum (1 SwearAllegiance, 4 Fellowship, matching retail's
Handle_Character__ConfirmationRequest switch and ACE's enum verbatim) and
ParseConfirmationResponse, completing Core.Net's typed coverage of all
three legs and round-tripping against the existing
ClientCommandRequests.BuildConfirmationResponse byte-for-byte.

Allegiance small events (GameEvents.cs): AllegianceLoginNotification
(0x027A), AllegianceUpdateDone (0x01C8), AllegianceUpdateAborted (0x0003,
declared but never sent by ACE).

The heavyweight AllegianceUpdate (0x0020) extends
ClientCommandResponses.ParseAllegianceInfoResponse (0x027C) rather than a
second parser, per lane C §7.2's explicit reuse verdict -- both messages
now share ReadAllegianceProfileBody, which the discriminating leading u32
(targetGuid vs rank) is read around. That shared reader implements:

- The ELEVEN AllegianceHierarchy::UnPack version gates (lane C §4.2) --
  officers/spokesperson-skip, officer titles, the four broadcast
  counters, motd/motdSetBy, chatRoomId, bind point, allegianceName,
  isLocked, approvedVassal, each behind its own oldVersion threshold.
  AllegianceProfileVersionGateTests.cs pins all eleven with a
  boundary-crossing pair per gate (N-1 OFF vs N ON), including the
  negative proof that version 5 (BannedCharactersAdded) gates nothing
  in UnPack.
- The §4.4 tree-assembly rules: a record whose treeParent is not already
  in the tree (orphan), equals its own id (self-parent), or duplicates an
  id already seen makes AllegianceHierarchy::Add fail, which the whole
  parse now mirrors by returning null for the ENTIRE message -- not a
  partial tree. Sibling order REVERSES on assembly (each new record is
  prepended to its parent's vassal list), so FindVassals now walks
  records in reverse wire order; both rules have dedicated tests.
- AllegianceMemberRecord gained the panel-needed columns lane C §7.2
  names (rank, level, loyalty, leadership, cpCached, cpTithed, gender,
  heritage, MayPassupExperience) with defaulted trailing parameters so
  existing 4-arg positional construction sites keep compiling. Officers/
  officer titles/bind point are read (so every later field lands at the
  right offset) but deliberately left unsurfaced -- ACE always zeroes/
  empties them anyway (lane C §5.1), and bind point is a 32-byte Position
  the retail chat renderer never uses either; a future panel slice can
  extend the record without re-deriving the parse.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-12 00:13:10 +02:00