acdream/docs/plans/2026-08-11-fellowship-allegiance-campaign.md
Erik 35c40a9b56 fix(ui): allegiance page — hoist per-frame LinesProvider allocation, document the coarser empty-state gate
Mechanism SHOULD-FIX 2 / blast SHOULD-FIX 4 (same finding, both reviews):
SocialAllegiancePageController.Tick() closed over a local `lines` on
every call — `() => lines` allocated a display class plus a delegate on
EVERY frame, unconditionally, at ~2,000 allocations/second at the
profile's measured FPS, in a hot loop the Modern Runtime slices spent
whole commits driving to 0 B/frame. Hoisted two static readonly
Func<IReadOnlyList<UiText.Line>> providers (BlankLineProvider/
NoLinesProvider); Tick() now assigns the cached delegate reference —
zero allocation while idle or active.

Mechanism SHOULD-FIX 7: FA3's empty-state gate is coarser than the
retail mechanism it is contracted against — gmAllegianceUI::UpdateMonarchData
@0x00491B40 hides the monarch/patron blocks per-relationship (monarch
block also hides when the monarch IS the viewer; patron block on the
analogous test), while this shell gates both blocks on the single
HasProfile flag. Not a MUST-FIX for FA3 (MF-2 in the same review means
HasProfile is effectively always false for the whole FA3 gate, so
nothing wrong is visible during this slice's own gate) — recorded
instead as an explicit FA5 acceptance line in the plan's FA5 row so the
gap cannot be silently lost, plus a class-doc note that FA5's real
monarch/patron population must also change Tick()'s unconditional
LinesProvider reassignment in the same commit or its content will be
overwritten the next frame.

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

27 KiB
Raw Blame History

Campaign FA — the retail social panel: Fellowship & Allegiance

For agentic workers: slices are executed by ONE Sonnet implementer at a time against this contract, then dual-lens Opus-reviewed, per §7/§8. The four research docs in §1 are the spec's data appendix — implementers MUST read the cited sections before coding; every table this plan references is committed there in full.

Status: ACTIVE — planned 2026-08-11. FA1 CLOSED 2026-08-12; FA2 CLOSED 2026-08-12 (dual APPROVE-WITH-FIXES → fix round 4272ad0e/ded23067/ ed8b3ec9 → narrow re-review CLOSED cc1a319c, no reopen; CF-1 folded into the corrected FA5 slice row). FA3 implementation complete 2026-08-12 (0a9ca2f1/74c3d85d), owes its §7 dual-lens Opus review and the user's connected gate (docs/research/2026-08-12-campaign-fa-test-script.md).

Goal: retail's social panel — the four-tab gmPanelUI member at host slot 0x1000018F (panel id 12): Friends / Allegiance / Fellowship / Squelch — with the Fellowship and Allegiance pages fully live retail-faithful end-to-end (authored LayoutDescs, real wire, Runtime-owned state, live consumers), the Friends and Squelch pages bound read-only to the state Runtime already owns, and automated bot-vs-ACE gates for the two-session flows. Campaign directive: the 2026-08-11 /goal (Campaign FA).

Architecture: one panel mounted exactly like Campaign OP's Options panel (the OP3 recipe: LayoutImporter.ImportInfos(dats, 0x2100006E, 0x1000018F), Type-8 tab host + ActivateTabBehavior, per-page scoped controllers, RetailPanelCatalog id 12, F3/F4 keybinds). Fellowship and allegiance state live in TWO new sibling Runtime owners under GameRuntime per the Slice-J pattern. (different lifetimes: fellowship is session-scoped; allegiance survives reconnect behind a seed latch) [FA2 fix-round addendum, 2026-08-12: corrected — both owners are session-scoped and clear at every generation reset; see D2's addendum below for the full three-way evidence citation.] Wire parsers/builders live in AcDream.Core.Net beside the H.2 scaffolding they connect, repair, or replace. Both graphical and headless hosts are served by the single inbound wiring site.


1. Research base (committed; the spec's data appendix)

Doc What it pins
docs/research/2026-08-11-fa-panel-structure.md (lane A + coordinator addendum §10) The social-panel mount (slot 0x1000018F, id 12, four pages with RegisterElementClass identities), both element inventories, the row-template mechanism, the five confirmation dialogs, empty states, F3/F4 open path, the 16-slot host table, unknowns U3U10
docs/research/2026-08-11-fa-fellowship-wire.md (lane B) 31-feature master table; per-message field order (golden-vector source); the byte-decoded IsFull >= 9 and the x87 XP-share table (1.0…0.28, 2.8× cap); four pinned ACE divergences (share .3, 900 s vs 600 s lock, dual shareLoot encodings, zeroed cp/lum); the two latent acdream builder defects; the 0x00A6-gates-vitals prerequisite; accept/decline = the shared confirmation triple (type 4); dead 0x01C9/0x01CA; 8 missing WeenieError strings
docs/research/2026-08-11-fa-allegiance-wire.md (lane C) 27 C→S + 5 S→C binary-verified messages; AllegianceProfile/AllegianceHierarchy layout with ELEVEN version gates; tree-assembly rules (orphan record ⇒ whole-message discard; sibling order REVERSES); ACE's deliberately-zeroed profile fields; the reuse verdict on ParseAllegianceInfoResponse and the DELETE verdict on Core/Allegiance/AllegianceTree.cs; the retail-faithful permanent dimming of the two allegiance notification bits
docs/research/2026-08-11-fa-acdream-seams.md (lane D) The H.2 scaffolding inventory (11 event ids, 5 fellowship builders, swear/break, LogTextTypes, F3/F4 actions — all unreachable); the two-sibling-J-owner recommendation with the 8-edit template; the ONE inbound registration site; the six dimmed rows + their pinning tests; the panel-mount template; bot-gate requirements (second ACE account, role-discriminated policy, 7 commands, 7 assertions); 0x027C already has a handler to share

2. Design decisions (stated per the campaign directive; reactable at gates)

  • D1 — one social panel, all four tabs ship. The DAT authors ONE four-page panel; mounting only two pages would be an invented divergence. Fellowship + Allegiance pages are this campaign's core (fully live). Friends + Squelch pages bind READ-ONLY to RuntimeCommunicationState's existing friends/squelch owners (J4.1) for display; their mutation actions (add/remove friend, squelch edit) are wired only if their wire is already served by ACE and trivially pinned in-slice — otherwise the action buttons are honest INERT with register rows (the OP3 precedent), completed post-campaign.
  • D2 — two sibling Runtime owners. RuntimeFellowshipState (session-scoped, cleared at reset like external-container) and RuntimeAllegianceState (survives reconnect behind a HasServerSeed- style latch). [FA2 fix-round addendum, 2026-08-12: shipped the OPPOSITE finding for RuntimeAllegianceState — the "survives reconnect" half is proven wrong by three-way evidence (docs/research/2026-08-12-fa2-review-mechanism.md MUST-FIX 1): (1) the retail hookClientAllegianceSystem:: OnEndCharacterSession @0x00569FA0 tail-calls AllegianceProfile::Clear at exactly the per-character-session boundary this owner's reset would run at, mirroring the sibling ClientFellowshipSystem:: OnEndCharacterSession @0x005690A0 Fellowship already honored — FA2's fellowship half was byte-faithful, the allegiance half was the exact inverse of retail's behavior at the same hook; (2) the cited precedent's actual behaviorRuntimeCharacterOptionsState.ResetSession (src/AcDream.Runtime/Gameplay/RuntimeCharacterState.cs:1007-1017), the very HasServerSeed-style latch this decision named as its justification, CLEARS and re-latches on every reset; it does not persist. Its own doc comment names the exact hazard this row then walked into verbatim: "a stale seed surviving a session boundary could let a flush ship the PRIOR character's words over the new one's"; (3) the no-character-selector connect pathSessionPlayerComposition.cs:1127-1132 constructs LiveSessionConnectOptions with no character field, so LiveSessionController.StartCore falls through to CharacterList.TrySelectFirstAvailable: which character enters world at generation N+1 is resolved fresh from a server-supplied list, and nothing in RuntimeAllegianceState keyed on character identity. The process model does NOT preclude a cross-character reset on the graphical host, so a stale allegiance tree surviving reset could present as the WRONG character's monarch/rank/vassal list. Corrected semantics: RuntimeAllegianceState is now ALSO a RuntimeGenerationReset stage (RuntimeGenerationResetStage.Allegiance) with the identical clear-and-relatch shape as RuntimeFellowshipState and the RuntimeCharacterOptionsState precedent — the profile clears AND HasServerSeed drops to false at every generation reset, not just at terminal Dispose. The HasServerSeed latch's remaining job is exactly what its name says: distinguishing "no profile has arrived THIS generation" from "genuinely no allegiance" WITHIN a session — it is a within-session rendering gate, not a cross-reconnect persistence mechanism. The two owners remain separate classes (not merged into one) because fellowship and allegiance are independent retail systems with independent wire families, not because their lifetimes differ anymore.] Lane D's 8-edit template per owner; consumers poll via Snapshot.Revision — no IRuntimeEventObserver member is added (that would break all five bot policies + the trace recorder).
  • D3 — the H.2 scaffolding is connected, repaired, or deleted — never trusted. Connect: the 11 GameEventType ids, AllegianceRequests swear/break, both LogTextTypes, F3/F4 actions. REPAIR: the fellowship builders (lane B: BuildFellowshipCreate invents an openness field ACE silently misreads as inverted shareXP; BuildFellowshipUpdate mislabels 0x00A6; the real openness action 0x0291 and leader-quit 0x0290 are missing) with their wrong-shape tests re-pinned to the verified field order. DELETE: Core/Allegiance/AllegianceTree.cs + its tests (passup formula ~1000× wrong and locked in by test; tree model contradicts the wire). REUSE: ClientCommandResponses. ParseAllegianceInfoResponse extended with the eleven version gates — no second profile parser.
  • D4 — 0x00A6 (panel-open declaration) is wired to real panel visibility. ACE streams fellow vitals ONLY while the panel is declared open; without it the roster freezes at join. Sent on the fellowship page's show/hide through the page-visibility seam OP3 already exposes.
  • D5 — parse defensively where ACE is internally inconsistent; display retail's own numbers. shareLoot is read as raw uint != 0 (ACE encodes it two incompatible ways). The XP-share percentage column uses retail's byte-decoded table (1.0/.75/.6/.55/.5/.45/.4/.35/.3111111/.28, default 0.0) — one register row records the knowing display divergence vs ACE's .3-at-nine-fellows server math.
  • D6 — confirmations ride the existing dialog seam. Swear/accept- swear/break/kick/fellow-invite are plain Confirmation dialogs (0x0274/0x0275/0x0276, type 1 = allegiance, 4 = fellowship); acdream's RetailDialogFactory/GameplayConfirmationController already match the constants and its own comment names these types as awaiting FA. The missing third message of the triple is completed in FA1. The invite-receive path consumes IgnoreFellowshipRequests / FellowshipAutoAcceptRequests (auto-decline / auto-accept before the dialog), giving those two dimmed rows their real consumers.
  • D7 — dimming resolves per lane-C/D verdicts. Un-dim (with the AD-78 conformance-test flow) exactly the rows that gain consumers: IgnoreFellowshipRequests, FellowshipAutoAcceptRequests (D6), and the fellowship share rows consumed by the create flow/panel display. IgnoreAllegianceRequests and DisplayAllegianceLogonNotifications stay dimmed PERMANENTLY and faithfully — retail's own client has no consumer for either (pure server-side filters); AD-78's register row gains an addendum saying so.
  • D8 — bot-vs-ACE gates are first-class. A second ACE account/character (user-provided when FA6 arrives) + a role-discriminated headless policy let one bot recruit/swear at another, with the decisive assertion on the RECRUITED bot's own snapshot. All six FA option names are already tier-1 allow-listed.
  • D9 — the 8 missing fellowship WeenieError strings are added in FA1 (two are on ACE's live send paths today; all resolve from the DAT string tables, never invented). [FA1 fix-round addendum, 2026-08-12: shipped the OPPOSITE finding — 5f9aa16f verified from primary source (case-label walk + else if chain sweep + whole-file sweep + decimal forms, docs/research/2026-08-12-fa1-review-mechanism.md §2(a)) that retail's Sept-2013 client has NO display text for any of the 8 ids. Inventing English for them would have been the exact failure mode the WeenieError table's no-default-case rule exists to prevent. acdream's existing silence for the two ACE actually sends (0x0417 FellowshipIgnoringRequests, 0x04DB FellowshipDeclined) is already retail-faithful; no strings were added, and none are owed. The conformance [Theory] at WeenieErrorMessagesTests.cs:390-393 (8 InlineData rows, each asserting Resolve() returns null) is the correct artifact in place of the string additions this decision originally called for. No register row is owed either — the register tracks acdream-vs-retail deviation, and this finding is that acdream already matches retail.]

3. Slice map

Dependencies: FA1 → FA2 → (FA3 → FA4/FA5) and FA2 → FA6; FA6 also needs FA4 (recruit) + FA5 (swear). FA7 closes. Default execution order: FA1, FA2, FA3, FA4, FA5, FA6, FA7.

Slice Contract (summary) Gate
FA1 Core.Net truth: repair fellowship builders + re-pin golden vectors; add 0x0290/0x0291/0x00A6/0x001F; the allegiance action set the panel needs (swear/break/kick/info at minimum; the rest of the 27 as builders only where ACE serves them); parsers for the 11 S→C events incl. the profile version gates (extending ParseAllegianceInfoResponse) and the tree discard/reversal rules; complete the confirmation triple; add the 8 WeenieError strings [FA1 fix-round addendum, 2026-08-12: shipped as CONFIRM-ABSENT instead — see D9's addendum. The 8 ids have no retail display text; acdream's silence is already faithful and a conformance test pins it.]; DELETE AllegianceTree automated only
FA2 RuntimeFellowshipState + RuntimeAllegianceState (lane D's 8-edit template each); single-site inbound wiring serving BOTH hosts; typed commands/views; snapshot revisions; reset/reconnect semantics (session-scoped vs seed-latched [FA2 fix-round: both session-scoped — see D2's addendum]); bot event surface via polling automated only
FA3 The social panel shell: mount slot 0x1000018F (catalog id 12), F3/F4 handlers, fixture dump of the slot (closes U3/U4/U6/U7/U10), tab activation, all four pages' empty states, Friends/Squelch read-only binding to J4.1 state user (connected)
FA4 Fellowship page live: roster rows (adds UiTemplateListBox Flush/selection/row-instance-id — lane A sized this), the 0x00A6 show/hide declaration + vitals stream, create dialog (inline name field, shareXP), recruit/dismiss/quit/disband/leader + confirmations, share display per D5, option-row un-dims per D7 user (connected) + bot
FA5 Allegiance page live: profile parse on show (0x027B) [FA2 re-review CF-1 correction, 2026-08-12: the DATA subscription is 0x001F AllegianceUpdateRequest(on) — retail arms it at PostInit, RecvNotice_PlayerDescReceived, and the panel's visible branch, and 0x0020 AllegianceUpdate is the sole owner-seeder after the FA2 MF-2 fix; 0x027B's response is text-only chat. FA5 wires 0x001F(on) at those lifecycle points (off on hide per retail's visible branch) or the panel opens with chat text and no data]; monarch/patron/self blocks, flat vassal list with the reversal rule honored, swear/break/kick + confirmations, ACE zeroed-field presentation scoped honestly (register row). [FA3 fix-round addendum, 2026-08-12 (mechanism SF-7): FA3 shipped a COARSER empty-state gate than retail's own — both the monarch and patron blocks hide/show together on the single RuntimeAllegianceSnapshot.HasProfile flag. Retail's gmAllegianceUI::UpdateMonarchData @0x00491B40 gates PER-RELATIONSHIP: the monarch block additionally hides when the monarch IS the viewer, and the patron block hides on the analogous test. FA5 MUST widen SocialPanelController.Callbacks.AllegianceSnapshot (today Func<RuntimeAllegianceSnapshot>) to reach the per-relationship data IRuntimeAllegianceView.TryGetMonarch/TryGetPatron already expose (GameRuntimeGameplayViews.cs:200-204) and implement the real gate — a monarch character must NOT see an empty, visible monarch block, and a patron-of-the-monarch character must NOT see an empty, visible patron block. SocialAllegiancePageController.Tick also reassigns LinesProvider UNCONDITIONALLY every frame today — FA5's real name population must change this method in the same commit or its content is overwritten the next frame.] user (connected) + bot
FA6 Bot-vs-ACE automated gates: second-account config (USER PREREQUISITE), role-discriminated policy, the 7 Runtime commands + 7 named assertions (decisive: the recruited/sworn bot's own snapshot flips), reconnect-idempotence automated + bot-vs-ACE run
FA7 Closeout: register reconciliation, ISSUES sweep, CLAUDE.md Current-state paragraph, memory digest, the connected-gate test script (the campaign's stop condition) user's final connected matrix

4. What is explicitly OUT of scope

  • Friends/Squelch mutation wire beyond D1's trivially-pinnable bar (post-campaign completion; register rows).
  • Allegiance officer/MOTD/banlist management UI — ACE zeroes or ignores most of it (lane C §ACE-caveats); builders may exist from FA1 but no panel surface beyond what the authored layout carries.
  • XP passup FORMULAS client-side — _cp_tithed arrives pre-computed; the deleted AllegianceTree transcription is not replaced.
  • The 0x01C9/0x01CA dead opcodes (COMDAT-folded no-ops in retail).

5. Verification discipline

Per commit: dotnet build -c Release + FULL Release suite green (baseline at plan time: 13,103 / 4 skips / 0 failures at 28bef4e0+the gate-4 fixes). Golden byte vectors for every wire builder against the lane-B/C field-order sections. Conformance pins for every panel inventory and the tab table. The live-DAT probe harness (ACDREAM_PROBE_LIVE_MOUNT=1) extended per slice — fixture-green alone is NOT acceptance for anything mounted (the #372/#375/#378 lesson). Register rows in the same commit as the deviation. No user-visible invented English ever.

6. Campaign OP lessons imported as binding rules

From claude-memory/project_settings_options_digest.md's DO-NOT-RETRY table and the gate rounds: pass the string resolver to EVERY LayoutImporter.Build; same-layout template-list prototypes are skipped by the importer (verify for this layout's templates); activate the Type-8 host or pages stack; seed lazily-created fill-anchored children with their parent extent; scoped FindDescendant for every id that repeats across pages (lane A: 0x10000492 twice INSIDE the allegiance page); popups get pointer priority via the UiRoot popup registration; straddling rows clip (never whole-cull); BN literal-0 operands are byte-verified before use.

7. Review protocol

Dual-lens Opus review per slice (mechanism-faithfulness × regression/blast-radius), fixes applied by the implementer; REJECT → focused re-review; TWO failures → Fable fixes directly. Findings persisted to a committed doc BEFORE any fixer/re-reviewer dispatch.

8. Process rules (binding, inherited from Campaign OP)

Max 34 agents in parallel INCLUDING children; every agent prompt carries an explicit no-subagent clause; ONE builder/tester on the tree at a time (read-only research may overlap); agents never launch the graphical client (headless bot-vs-ACE runs are allowed); the user runs connected gates; ledger placeholders anchored per-row; decomp claims byte-verified against the PDB-paired binary; stalled agents resumed via SendMessage before any redo; agent claims spot-verified at the seams before anything builds on them.

9. Ledger

Slice Status Commit(s) Review Gate
FA1 CLOSED 2026-08-12 — narrow re-review verdict CLOSED, no reopen (96df892d, §7 of the mechanism findings doc: all 7 mechanism dispositions re-derived in the diffs, all 6 blast dispositions spot-verified, suite claim corroborated on post-fix binaries); re-review carry-forward CF-1 (two further stale AllegianceTree citations in the seam map's FA2 guidance, :128/:214) closed by coordinator addenda in the same commit as this ledger update — FA2 was contracted not to start before that 7be86f47 (builders), 6bedbc47 (parsers), 5f9aa16f (WeenieError), 4281750b (delete AllegianceTree); fix-round: ed308087 (mechanism+blast MUST/SHOULD-FIX code+tests), this commit (register/plan/seams doc corrections) mechanism docs/research/2026-08-12-fa1-review-mechanism.md (2 MUST-FIX, 5 SHOULD-FIX, all applied); blast docs/research/2026-08-12-fa1-review-blast.md (4 MUST-FIX, 5 SHOULD-FIX, all applied). Live-surface note (blast SF-1): FA1 changed the observable output of the ALREADY-LIVE @allegiance info command in two retail-faithful ways — vassal print order reversed (now pinned by a 3-vassal test through FormatAllegianceInfoLines) and a malformed tree now prints nothing instead of a partial roster (now pinned at the GameEventWiring layer) — not a "purely unwired" slice. automated: Release build + full suite green throughout. Reconciled totals (blast MF-4): the ledger's own prior figure (13,149/4/0) is CONFIRMED correct by direct measurement at the pre-fix-round tip bc693728 (13,153 total = 4+916+1559+15+130+119+4856+877+4677 across all 9 test projects); the campaign-start baseline in §5 is 13,103/4/0, and the diff-verified FA1 delta is +58 added / 9 deleted (deleted AllegianceTreeTests.cs) = net +49, i.e. 13,103+49=13,152 — one test of drift against the directly-measured 13,153/13,149 baseline, attributed to the §5 baseline being captured at a different point in git history than the FA1 diff's actual parent, not a further miscount. This fix round adds a further +9 tests (2 golden vectors for the new 0x001F builder, 1 D5 <<1 pin at the 0x02C0 site, 4 MF-1/SF-1 boundary tests, 2 blast SF-1 live-surface pins) — final: 13,158 passed / 4 skipped / 0 failed (13,162 total), directly measured.
FA2 FIX-ROUND CLOSED 2026-08-12 — both reviews' MUST-FIX/SHOULD-FIX findings applied; automated gate only, per contract RuntimeFellowshipState/RuntimeAllegianceState (2 new sibling J-owners, src/AcDream.Runtime/Gameplay/), the full 8-edit template applied twice (GameRuntime.cs, RuntimeGenerationReset.cs, RuntimeGameplayOwnership.cs/RuntimeSimulationOwnership.cs, GameRuntimeGameplayViews.cs, GameRuntimeCommands.cs, GameRuntimeEvents.cs, GameRuntimeViews.cs), 12 new WorldSession.Send* wrappers, 15 new GameEventWiring.WireAll delegate holes [FA2 fix-round addendum, 2026-08-12 (docs/research/2026-08-12-fa2-review-blast.md SHOULD-FIX 3): both counts were wrong. WorldSession.cs:2318-2404 adds 11 new Send* wrappers (7 fellowship + 4 allegiance) — SendAllegianceInfoRequest pre-dates FA2; the 12 figure belongs to a different count, cced83b4's *RuntimeCmd records / LiveSessionCommandBindings send delegates. GameEventWiring.cs:107-120 adds 10 delegate holes (9 new + the 0x027C fold), not 15 — matching the "11 S→C events" the seam doc's §2.3 table names, of which 2 (0x01C9/0x01CA) are correctly left unregistered (dead COMDAT-fold no-ops) and 1 (0x027C) was already registered pre-FA2.] registered at the single site (LiveSessionEventRouter.cs), both LiveSocialSessionBindings construction sites updated (LiveSessionRuntimeFactory.cs, HeadlessSessionHost.cs), IRuntimeFellowshipCommands/IRuntimeAllegianceCommands implemented on both host command adapters (DirectGameRuntimeCommandAdapter, CurrentGameRuntimeCommandAdapter + its LiveSessionCommandRouter/LiveSessionCommandBindings App-bus plumbing), divergence register rows TS-81 (filed) + TS-80 (narrowed); fix-round: 4272ad0e (mechanism MUST-FIX 1/2 + blast MUST-FIX 1/2 + blast SF-1 + mechanism SF-2 — allegiance reset semantics, 0x027C stops seeding, teardown-ledger off-by-one, conditional delegate holes, disposed-checks-inside-lock), ded23067 (mechanism SF-3/4/5/6 + blast SF-4/5/7 — RecalculateEvenXPSplitting, locked/departed admission gate, AllegianceProfileLookups reuse, non-null checkpoint defaults, router self/other-quit test, ResetSession disposed-guard parity, GetVassals allocation doc), this commit (register/plan/seams doc corrections) mechanism docs/research/2026-08-12-fa2-review-mechanism.md (2 MUST-FIX, 6 SHOULD-FIX, all applied); blast docs/research/2026-08-12-fa2-review-blast.md (2 MUST-FIX, 7 SHOULD-FIX, all applied). Allegiance register-row re-evaluation (blast SF-6): the design blast SF-6 asked to be either dropped or given a register row is now fully retired by MF-1's fix — RuntimeAllegianceState clears at every generation reset exactly like RuntimeFellowshipState and exactly like retail's OnEndCharacterSession hook, so there is no remaining acdream-vs-retail deviation for allegiance persistence to name a row for. No register row added; conclusion recorded here per the fix-round contract. automated: Release build + full suite green throughout. Pre-fix-round baseline 13,201/4/0 (13,205 total, measured at 12053e61 by the blast review) → fix-round 13,215/4/0 (13,219 total), +14 tests, arithmetic exact: RuntimeAllegianceStateTests.cs net +2 (1 deleted ApplyInfoResponseSelf_..., +3 new ResetSession_*), RuntimeFellowshipStateTests.cs +10 (1 ResetSession_AfterDispose_... + 3 [Theory] cases for RecalculateEvenXPSplitting + 1 ShareXp-off case + 1 full-update-never-recomputes case + 4 locked/departed admission-gate cases), GameRuntimeTests.cs +1 (CompletedTeardownStagesAccumulatesExactlyOneFlagPerStage), Session/LiveSessionEventRouterTests.cs +1 (FellowshipQuit_RoutesSelfGuidToClearAndOtherGuidToRemove); RuntimeGenerationResetTests.cs and GameEventWiringTests.cs each renamed one test in place (net 0); GameRuntimeContractTests.cs gained two trailing constructor arguments at its sole positional RuntimeStateCheckpoint site (compile-fix only, no new test).
FA3 IMPLEMENTATION COMPLETE 2026-08-12 — owes §7 dual-lens Opus review and the user's connected gate (script: docs/research/2026-08-12-campaign-fa-test-script.md). 0a9ca2f1 (SocialPanelController + 4 per-page controllers + SocialPanelRowText, UiTemplateListBox.Flush, RetailPanelCatalog.SocialPanel/WindowNames.SocialPanel, RetailUiRuntime Mount/Tick/F3/F4 wiring, InteractionRetainedUiComposition's SocialRuntimeBindings, register row AD-79); 74c3d85d (fixture generator entry + committed social_panel_2100006E_1000018F.json, SocialPanelLiveMountProbeTests, SocialPanelControllerTests, RetailPanelCatalogTests additions, FixtureLoader additions); this commit (gate script + ledger). Not yet reviewed. automated: Release build green; full solution suite 13,233 passed / 4 skipped / 0 failed (13,237 total), +18 over FA2's closing 13,215/4/0 (13,219 total) — exact arithmetic: 13 SocialPanelControllerTests + 4 RetailPanelCatalogTests additions + 1 SocialPanelLiveMountProbeTests (env-gated, trivially passes without ACDREAM_PROBE_LIVE_MOUNT=1) = 18. Live-DAT finding (corrects the coordinator addendum, §10): the real authored 0x2E tab table pairs button 0x1000028C ("Allegiance" caption) with page 0x10000291 as the DEFAULT entry — NOT Friends, which the addendum's x-order guess implied; each page's own P0x57 independently corroborates (Allegiance page P0x57=0x1000000E == ToggleAllegiancePanel/F3, Fellowship page P0x57=0x1000000F == ToggleFellowshipPanel/F4). See SocialPanelController's class doc for the full corrected table. Unrelated fixture drift caught and reverted: the ACDREAM_REGENERATE_UI_FIXTURES=1 run used to produce the new fixture also silently regenerated keyboard_config_21000009.json and options_2100002B.json with large diffs against this machine's currently-installed DAT (pre-existing environment drift, not FA3-caused) — both were git checkout'd back to HEAD before committing; only the new fixture is included. Friends/Squelch action buttons are honest INERT per D1 (register row AD-79, one row covering both pages' seven controls, not one row per button).
FA4
FA5
FA6
FA7