acdream/docs/research/2026-08-12-campaign-fa-test-script.md
Erik 01fafe7b37 docs: FA6 — ledger row + gate script §FA6 (fellowship PASSED live, allegiance deferred)
Plan ledger: fellowship two-session automated gate PASSED live 2026-08-12
(five of six runs reproduced the decisive cross-session assertion); the
allegiance bot gate is DEFERRED behind AllegianceGateEnabled=false pending
docs/ISSUES.md #384, with commit citations for every fix this slice landed
(confirmation relay, name-matched proximity, the fellowship-only
finalization).

Gate script §FA6: the fellowship automated-gate recipe + actual PASSED
result (the two-session config, the six proof points per stage, the
literal decisive-assertion log lines), the allegiance deferral writeup,
and a new [TWO-CLIENT] manual step (25) the user's own connected gate can
run to help disambiguate #384 (ACE-side rule vs wire-builder defect vs
harness-specific drop) using two real graphical clients instead of the
testaccount/testaccount2 pair.

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

914 lines
50 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Campaign FA connected-gate test script
**Status: FA3, FA4, and FA5 (below) all owe their connected gates.** Launch
with `ACDREAM_LIVE=1` against the local ACE server (`testaccount` /
`+Acdream`). Anything marked **INERT** is authored and clickable but
deliberately does nothing yet — that is the correct, contracted behavior
for this slice (D1), not a bug. **FA6's bot-vs-ACE fellowship gate PASSED
live 2026-08-12 (§FA6 below) and needs no connected re-gate; its
allegiance half is DEFERRED (`docs/ISSUES.md` #384) with one
`[TWO-CLIENT]` manual step in §FA6 that can help settle it.**
FA3 is the panel SHELL only: mount, F3/F4 open paths, tab switching, all
four pages' empty states, and Friends/Squelch read-only lists. Fellowship
roster rows, the create-fellowship dialog, live vitals, and every
Allegiance swear/break/kick action are FA4/FA5 scope — do not report their
absence here.
**FA4 makes the Fellowship page fully live** (§FA4 below): roster rows,
the `0x00A6` panel-open/vitals-stream gate, the create flow, member
actions + confirmations, the four option checkboxes, and the open/close
caption swap. **Honest limitation up front: several FA4 steps need a
SECOND character** (recruit, the inbound invite dialog, watching another
member's vitals update). If a second ACE account/character is not
available for this gate, run every step marked **[SOLO]** and defer the
steps marked **[TWO-CLIENT]** to Campaign FA slice FA6's bot-vs-ACE gate
(`docs/plans/2026-08-11-fellowship-allegiance-campaign.md`, D8) — do not
treat an unrun two-client step as a failure.
---
## FA3 — the social panel shell
### Opening the panel — F3/F4, keyboard only
1. **Press F3.** The social panel opens on its **Allegiance** tab (the
authored default tab — Allegiance is ALSO the left-most tab drawn on
the tab strip, at x=0; the default tab and the left-most tab are the
SAME tab, verified by re-deriving the authored tab table directly from
the fixture, the live-mount probe, and each page's own `P0x57`
action-map id — see `SocialPanelController`'s class doc for the full
citation. **[FA3 fix-round correction, mechanism MF-1]** an earlier
draft of this script claimed Friends was drawn left-most; that was
wrong). No toolbar button opens this panel — retail authors none
(lane A §6.1) — so there is nothing to click for this step besides the
keybind itself.
2. **Press F3 again** while the panel is open and already on the
Allegiance tab. The panel closes — same toggle-closes-on-repeat-press
shape as every other `Toggle*Panel` action (e.g. F11/Options,
Spellbook).
3. **Press F4.** The social panel opens directly on its **Fellowship**
tab (F4 switches AND opens in one press, even if the panel was
closed).
4. **With the panel open on Fellowship, press F3.** The panel does NOT
close — it switches to the Allegiance tab (F3 is scoped to its own
tab, not a blanket close). Symmetrically, pressing F4 while open on
Allegiance switches to Fellowship rather than closing.
5. **Click the panel's own close (X) button**, top-right of the window
chrome. The panel closes — same action as a repeat F3/F4 press on the
already-active tab.
6. **Drag the window by its border/chrome; resize it from the BOTTOM
edge only.** Same shared `gmPanelUI` geometry policy as every sibling
main panel (Options, Character, Inventory, Spellbook, the
indicator-detail panels): draggable, resizable from the bottom edge
only, remembers its height across a close/reopen within the session.
### Exclusivity vs sibling panels
7. **Open Character Info (or Inventory, or Options), then press F3.**
The other panel closes and the social panel opens on Allegiance — the
SAME retail `gmPanelUI` one-active-panel-at-a-time behavior every
sibling panel already has (`RetailPanelUiController.RegisterMainPanel`
shares one geometry rectangle across all ten registered main panels).
8. **With the social panel open, open Character Info.** The social panel
closes. Confirms the exclusivity is bidirectional, not just
"opening the social panel closes others."
### Tab switching
9. **Click each of the four tabs** (left-to-right authored order:
**Allegiance, Fellowship, Friends, Squelch** — only ONE tab is
highlighted "open" at a time. **[FA3 fix-round correction, mechanism
MF-1]** an earlier draft of this script had the order as
Friends/Allegiance/Fellowship/Squelch; that was wrong — re-derived
directly from the fixture's authored `0x2E` tab table). Each switches
the visible page; exactly one page is visible at a time. No crash, no
stuck state, switching back and forth repeatedly is safe.
10. **Note the tab captions read correctly, in this left-to-right order**
— "Allegiance", "Fellowship", "Friends", "Squelch" — not blank. A
blank caption on any tab button would be the #375 missing-string-
resolver class of bug; report it immediately if seen.
### Fellowship page — empty state
11. **On a character with NO fellowship** (the default state), open the
Fellowship tab. Expect: an editable fellowship-name text field, a
**Create Fellowship** button, and FOUR checkboxes (Ignore Fellowship
Requests / Auto-Accept Requests / Share XP / Share Loot — **[FA3
fix-round correction, mechanism SF-4]** an earlier draft hedged this
as "three... a fourth may also be present"; the fixture and the
decomp both settle it at four). No member list, no
leader/quit/open/recruit/dismiss/disband buttons visible — those
belong to the OTHER (in-fellowship) frame, which is hidden. **[FA4
correction]** the Create Fellowship button, the name field, and the
four checkboxes are now LIVE, not inert — see §FA4 below for their
full behavior; this step's own scope is only the frame/control
PRESENCE, still correct as written.
12. **If the test character IS currently in a fellowship** (uncommon for
`+Acdream`'s default state, but possible if a prior session left one
active, or if you ran §FA4's Create step below), open the Fellowship
tab instead expecting: a fellowship name display, a member roster
ListBox, and six buttons (Leader/Quit/Open/Recruit/Dismiss/Disband).
**[FA4 correction]** all six buttons are now LIVE — see §FA4.
### Allegiance page — empty state
13. **On a character with NO allegiance profile ever received this
session** (the default state — nothing has queried allegiance data
yet), open the Allegiance tab. Expect: the monarch block and patron
block BOTH HIDDEN (no visible "Monarch:" / "Patron:" labels or
name lines), while your own character line (name/followers/rank) and
the vassal list area remain visible per their authored layout. The
Swear/Break/Kick buttons and the "Ignore Allegiance Requests"
checkbox are present and **INERT**.
14. **This state does NOT change for the rest of this gate — that is the
correct, contracted FA3 behavior, not a bug. [FA3 fix-round
correction, mechanism MF-2]** an earlier draft of this script sent
you to `@allegiance info` expecting it to make the blocks visible,
and told you to report it if that didn't happen. That trigger CANNOT
fire and the instruction was wrong: FA3 never sends the `0x001F
AllegianceUpdateRequest` subscription (FA5 scope, per the plan's
corrected D6/CF-1 note), and `0x0020 AllegianceUpdate` — the ONLY
inbound message that can populate this panel's data — is never
provoked by anything FA3 does. Running `@allegiance info` in chat
still works exactly as before FA3 (it is an independent, already-
shipped command) and WILL print your monarch/patron/vassal data as
chat TEXT — but its response (`0x027C`) stopped seeding this panel's
profile back in FA2 (`4272ad0e`), so it has zero effect on this page.
**The correct, expected combination for this entire gate is:**
`@allegiance info` prints real data to the chat window (if you have
an allegiance), AND the Allegiance page's monarch/patron blocks stay
hidden throughout — report NEITHER half as a bug. The panel blocks
becoming visible at any point during this gate (see "What to report"
below) is the actual anomaly to watch for.
### Friends page — read-only list
15. **Open the Friends tab.** If the test account has any friends
server-side, their NAMES appear as rows in the list (no online/
offline styling, no icons — names only, this slice's explicit
scope). If the account has none, the list is empty (no placeholder
text is invented).
16. **The three action-shaped buttons and the "Appear Offline"-shaped
checkbox are INERT** — clicking/toggling them does nothing (register
row AD-79). This is CONTRACTED for FA3; do not report it.
### Squelch page — read-only list
17. **Open the Squelch tab.** If the test account has any squelched
characters or accounts, their names appear as rows (characters and
account-level squelches both listed, names only). Empty otherwise.
18. **The three action-shaped buttons are INERT** (register row AD-79,
same as Friends). CONTRACTED, do not report.
### Scrolling a long roster
**[FA3 fix-round addition, blast N-8/MF-1]** the two ListBoxes' own
scrollbars were unwired until this fix round — completely unreachable
past the visible extent, not merely awkward to use. This step exercises
the fix directly.
19. **If the test account's Friends or Squelch roster is longer than
fits in the panel's visible area** (the Friends box is 270×400 but
the panel itself is only ~300×362, so as few as a dozen or so
entries can already overflow — check either tab), **drag the
scrollbar thumb (or click the track/arrows) and confirm the rows
scroll and every name remains reachable**, all the way to the last
row. This is a **soft check** if the test account's roster is
genuinely too short to overflow — do not manufacture a long roster
just to exercise this step, but if the roster IS long enough, a
scrollbar that does not move the list, or a list that will not
scroll at all, IS a bug — report it.
### Live update while the panel is closed
20. **This is a soft check, not required to pass/fail the gate:** if
Friends/Squelch state changes server-side while the social panel is
CLOSED (e.g. via `@friend` chat commands, if any are wired), then
reopening the panel afterward should show the up-to-date list
immediately (the read-only binding polls every frame WHILE THE PANEL
IS VISIBLE — **[FA3 fix-round correction, blast SF-2]** an earlier
draft of this script said the poll runs "regardless of which tab or
window is visible"; that changed in the fix round — the rebuild is
now gated on the social panel's own visibility so it does no DAT
work while closed, but it still catches up in full the moment the
panel is shown again, so the user-visible outcome described here is
unchanged. See `SocialFriendsPageController`/
`SocialSquelchPageController`'s own doc comments). Report if the
list is stale on reopen.
### Window restore-open behavior across relaunch
**[FA3 fix-round addition, blast N-8]** stated honestly rather than left
untested: the social panel is NOT in acdream's short list of windows
whose OPEN/CLOSED state is force-reset every session
(`stateManagedVisibilityWindows` — Combat/JumpPowerbar/
ExternalContainer/Vendor). This is a deliberate, documented choice
(the FA seams doc), and it matches the EXACT SAME convention every
sibling main panel already uses — Options, Spellbook, Character,
Inventory, and Vitae all restore their own last open/closed state too.
21. **Open the social panel (F3 or F4), then close the client and
relaunch (or simply log out and back in without closing the
window).** The EXPECTED, non-bug behavior is that the social panel
reopens automatically in whatever tab/open state it was left in —
the SAME restore-open behavior every other main panel already has.
This is not something to report; it is only listed here so the
behavior is understood in advance rather than surprising anyone
mid-gate.
---
### What to report
- Any tab caption that renders blank instead of its retail name.
- The panel opening on the wrong DEFAULT tab (should be Allegiance, and
Allegiance is also the left-most tab) on a fresh F3 press with the
panel previously closed.
- F3/F4 failing to switch tabs while the panel is already open, or
closing the panel when it should only switch tabs.
- The panel NOT closing/opening exclusively with sibling `gmPanelUI`
panels (Character/Inventory/Options/etc.).
- The Fellowship page showing the WRONG frame for the character's actual
fellowship state (e.g. showing the roster/six-button frame while
`+Acdream` has no fellowship).
- The Allegiance page's monarch/patron blocks becoming VISIBLE at ANY
point during this gate — there is no code path in FA3 that can make
that happen (see step 14); if it happens, that is a real bug (an
accidental wire connection), not the false-defect trigger an earlier
draft of this script pointed at.
- Any INERT button/checkbox producing a VISIBLE effect (a Create
Fellowship, Swear, Friends, or Squelch action that unexpectedly does
something) — this would mean either an accidental wire connection or
a stale INERT claim in this script.
- A Friends/Squelch row showing anything other than a plain name (an
exception, a blank row, garbled text).
- A Friends/Squelch scrollbar that does not move the list, or a
longer-than-visible roster with no way to reach its later rows (step
19) — only when the test account's roster is actually long enough to
overflow.
- Any crash, hang, or exception in the log while opening/closing/
switching tabs repeatedly.
### Explicitly NOT in scope for this gate
- Allegiance monarch/patron/vassal LIVE population, swear/break/kick
wire sends, and their confirmation dialogs (FA5).
- Friends/Squelch add/remove/appear-offline/clear wire sends (D1 — out
of campaign scope entirely, register row AD-79).
- The bot-vs-ACE two-session fellowship gate (FA6).
---
## FA4 — the Fellowship page fully live
**Prerequisite:** open the Fellowship tab (F4, or click it from the
already-open panel). All step numbers below restart at 1 for this
section's own numbering.
### Create (name + shareXP) — [SOLO]
1. **With no fellowship, type a name into the fellowship-name field and
watch the Create Fellowship button.** It is DISABLED while the field
is empty/whitespace-only and ENABLES the instant you type a
non-blank character — this IS retail's refusal mechanism (lane B
§2.2: the button itself is the guard; there is no separate error
message to expect).
2. **Before clicking Create, toggle the "Share Fellowship Experience and
Luminance" checkbox** (on or off — your choice) and note its state.
3. **Click Create Fellowship.** The panel switches from the empty-state
frame to the populated frame: your fellowship's name appears, you
appear as the sole roster row (with your own name, level, and
vitals bars), and the six member-action buttons appear. **[fix-round
SF-7 correction: the previous wording here contradicted itself —
"only Quit" followed by "Disband and Open should ALSO be enabled" —
which would prime a tester to report correct behavior as a defect.]**
Quit, Disband, AND Open should ALL be enabled (you're the leader of a
real fellowship now); Leader and Dismiss stay DISABLED because no
other member is selected yet (see "button states" below).
4. **Check the ACE server confirms `ShareXP` matches what the checkbox
said** at the moment you clicked Create (a chat command like
`@fellow` status, or simply trusting the wire — this is a soft
check, not required to fail the gate over).
### Roster with a second character — [TWO-CLIENT] (bot alternative: FA6)
5. **Recruit a second character into the fellowship**: log a second
ACE account/character into a separate acdream (or retail) client,
select them in the world (click them), then click **Recruit** on the
first client. If accepted (see the invite steps below), the second
character appears as a NEW roster row on the first client —
confirm their name, level, and vitals bars render (not blank, not a
crash).
6. **With two members in the fellowship, scroll the roster if it
overflows the visible area** and confirm both rows remain reachable
(same scrollbar mechanism FA3 already gated, `UiTemplateListBox`).
### Vitals updating while the panel is open AND the frozen-roster
behavior when closed — [TWO-CLIENT], the `0x00A6` gate made observable
7. **With the Fellowship tab open and a second member in the
fellowship, have that second character take damage or use
stamina/mana** (attack a monster, cast a spell, etc.). Their
health/stamina/mana bars on the FIRST client's roster should update
within a second or two — this is the vitals stream D4 turns on
(`0x00A6` panel-open declaration gates ACE's `0x02C0` sends, lane B
§4.5).
8. **Close the social panel (or switch to a different tab), have the
second character take more damage, then reopen the Fellowship tab.**
EXPECTED: the roster shows the LAST vitals it had before the panel
closed (frozen), then updates live again within a second or two of
reopening — this is the direct, observable consequence of `0x00A6`
being sent `false` on hide and `true` on show. A roster that keeps
updating in real time WHILE THE PANEL IS CLOSED would mean D4 is not
actually gating anything — report that as a bug.
9. **[fix-round MUST-FIX 3, new step] Reconnect while the Fellowship page
is left open.** With the Fellowship tab open and a fellowship active,
force a disconnect/reconnect (kill the ACE connection, or use
whatever local reconnect trigger is available) WITHOUT closing the
social panel or switching tabs. Once the client re-enters the world
on the new session, confirm fellow vitals resume updating (repeat
step 7's damage-and-watch check). EXPECTED: vitals update normally on
the new session, exactly as before the reconnect. BUG if vitals stay
frozen for the rest of the new session — that means `0x00A6` was
never re-declared after the generation reset. (Re-review re-fix
2026-08-12: the re-declaration fires from the post-world
`EnteredWorld` seam via `SocialPanelController.RedeclareAfterWorldEntry`,
NOT the pre-world `ResetSessionDeclaration`, which only clears the
latch — declaring before world entry was silently dropped by the
world-gated command, the original defect.)
10. **Your OWN row's vitals should always update** (your own vitals are
driven by the existing player-vitals pipeline, not the fellowship
`0x02C0` stream) — this is expected and not a sign that `0x00A6` is
failing to gate the OTHER member's stream.
### Recruit/dismiss/quit/disband/leader flows with their confirmations
11. **[SOLO, self only]** With a solo (1-member) fellowship you lead,
click **Quit**. You leave the fellowship (no confirmation dialog for
Quit — retail has none, lane B §2.5) and the panel reverts to the
empty-state frame.
12. **[SOLO, self only]** Create another fellowship, then click
**Disband** instead. Same visible outcome (empty-state frame) via
the wire's `disband=true` flag — no confirmation dialog either.
13. **[TWO-CLIENT]** With two members, select the OTHER member's roster
row (click their name) and click **Dismiss**. They are removed from
the roster; their own client sees themselves leave the fellowship.
14. **[TWO-CLIENT]** With two members, select the other member's row
and click **Leader** (Assign Leadership). The `LeaderGuid` changes —
confirm via the leader's name tinting gold in the roster (this
controller's own visual cue — lane A's row template has no
dedicated leader marker, see `SocialFellowshipPageController`'s
class doc, register row AD-82) — and confirm the enable/disable
states flip: the OLD leader's Disband/Open buttons should now be
DISABLED, the NEW leader's (on their own client) should now be
ENABLED.
15. **[TWO-CLIENT, the leader-handoff rule]** As the CURRENT leader of a
2+-member fellowship, click **Quit** (not Disband). EXPECTED:
leadership transfers to the other member FIRST (their client should
briefly show themselves as leader), THEN you leave — this is
retail's pre-quit leader hand-off (lane B §2.5/§3.6, already ported
in `RuntimeFellowshipState.RequiresLeaderHandoffBeforeQuit`). Report
if the fellowship is left leaderless or the WRONG member becomes
leader.
16. **[fix-round MUST-FIX 4, new step] Select a fellow in the WORLD
(click their 3D model), not their panel row.** EXPECTED: Dismiss and
Assign-Leader enable exactly as if you had clicked their panel row,
and their row shows the panel's own selection highlight — retail's
`gmFellowshipUI::UpdateFellowSelection` reverse-selection arm (world
click selects the panel row too), reproduced against acdream's own
guid-keyed row tracking rather than a ported generic ListBox
selection model (register row AD-82).
### The open-toggle caption swap — [SOLO]
17. **With a fellowship active, note the Open/Close button's caption.**
A CLOSED fellowship shows **"Open"**; click it and it should flip to
show **"Close"** IMMEDIATELY on click, not after a delay (retail's
button reads as the ACTION available, not the current state, and
pre-toggles its own state before the server echo — lane A §4.1,
fix-round NIT N-0). Click again to flip back.
18. **The Open/Close button is enabled only when you are the leader**
confirm it is disabled (greyed / unclickable) if you are a member
but not the leader (needs a second client to observe from the
non-leader side).
### The invite dialog — [TWO-CLIENT]
**[fix-round MUST-FIX 2 correction, 2026-08-12: the OLD steps 18/19 here
tested whether acdream's CLIENT gates the invite dialog on the
Ignore/AutoAccept option bits. That mechanism never existed in retail and
has been deleted from acdream (D6's correction) — retail's client shows
the confirmation dialog UNCONDITIONALLY for every request it receives;
ACE alone decides whether to send one at all, by filtering
server-side. The steps below test the CORRECTED behavior: the dialog
always shows regardless of the SECOND client's own checkbox state. If
your local ACE build implements the server-side filter (lane B: refuses
the recruiter outright when the target has Ignore set; auto-joins
without a confirmation when the target has AutoAccept set), you will
observe the FIRST client's Recruit failing/auto-succeeding with NO
dialog ever reaching the second client at all — that is ACE's behavior,
not a client bug, and is out of scope for this gate either way.]**
19. **Baseline: on the SECOND client, ensure both "Ignore Fellowship
Requests" and "Automatically Accept Fellowship Requests" are
UNCHECKED** (Character tab or this page's own checkboxes — either
surface, they're the same value). From the FIRST client (in a
fellowship, as leader or with Recruit rights), select the second
character in the world and click **Recruit**. The SECOND client
should show a confirmation dialog asking to join the fellowship,
with the server's message text rendered VERBATIM (no added
"Continue?" suffix — type 4 is not in the 2/3/5/6 suffix set).
**Accept it** — the second character joins, appears on the first
client's roster.
20. **Repeat with "Ignore Fellowship Requests" CHECKED on the second
client (the FIX ROUND's regression check — this is the DEFAULT
state for a character who has never touched the option).** EXPECTED:
the dialog STILL shows on the second client exactly as in step 19
(assuming your local ACE does not itself refuse the recruit
server-side first) — the second character's OWN checkbox state must
NOT silently swallow the invite with no dialog and no chat line. If
it does, that is the exact regression MUST-FIX 2 exists to prevent.
21. **Repeat with "Automatically Accept Fellowship Requests" CHECKED on
the second client** (and Ignore unchecked — the two are mutually
exclusive; checking one should auto-uncheck the other, confirm that
too). Same expectation as step 20: the dialog still shows on the
client (acdream no longer auto-responds on its behalf); if your
local ACE implements the auto-accept-without-confirmation
server-side behavior, you may instead see the character join
immediately with no dialog — that is ACE's mechanism, not this
client's.
22. **Reject an invite** (baseline state, dialog showing): click Reject
instead of Accept. The second character does NOT join; no crash or
stuck dialog state on either client.
### Share-column expectations vs ACE — [SOLO, low member counts]
23. **With a solo (1-member) fellowship, Share XP checked, the stats
text should read a percentage of 100%** (the even-split table's
first entry, lane B §7.2). With 2+ members (if a second client is
available) sharing evenly, the percentage should drop per the
table (75% at 2, 60% at 3, …). **This is a SOFT / informational
check, not a fail-the-gate item:** if you happen to reach exactly 9
members, the panel will show **31%** (retail's own 0.3111111) while
ACE's actual XP grant math currently uses .3 — a KNOWN, filed
divergence between ACE and retail, not an acdream bug (register row
AD-80). Do not report a 9-member mismatch between the panel's
percentage and the XP you actually received.
24. **[fix-round MUST-FIX 1, new step — a HARD check, not soft like step
23's ACE note] At exactly 6 and 8 fellows (even split, Share XP
checked), the panel must show 44% and 34% respectively — NOT 45% or
35%.** Retail TRUNCATES the percentage (`_ftol2`), it does not
round; both stored float constants (`0.44999998807907104` at 6,
`0.3499999940395355` at 8) sit just BELOW the nice decimal, so
retail's own truncation lands one point lower than naive rounding
would produce. This is a genuine acdream-vs-retail bug if it shows
45%/35% instead of 44%/34% — unlike step 23's 9-member note, this is
NOT an ACE-divergence exemption.
25. **With Share XP UNCHECKED, the stats text should read "0%"** for
every member (retail's own literal `pct = 0.0f` branch, not a gap).
### What to report (FA4-specific — in addition to the FA3 list above)
- The Create button staying enabled with an empty/whitespace-only name
field, or staying disabled once real text is typed.
- Any roster row showing a blank name, a frozen level, or a vitals bar
that never updates AT ALL while the panel is open and a second member
is taking damage (contrast with step 8's EXPECTED freeze while the
panel is CLOSED — that one is correct, not a bug).
- Fellow vitals staying frozen for the REST OF A NEW SESSION after a
reconnect (step 9) — distinct from step 8's expected freeze, which
clears within a second or two of reopening the tab.
- A membership change (join/leave) resetting your scroll position to the
top of a long roster — the whole point of the FA3 carry-forward this
slice closed (`UiTemplateListBox.FlushPreservingScroll`).
- Quit/Disband/Dismiss/AssignLeader/SetOpen not reaching the server (no
visible effect on either client), or reaching it with the WRONG guid
(e.g. Dismiss removing the wrong member).
- The leader hand-off (step 15) leaving the fellowship leaderless, or
transferring leadership to the wrong member.
- Selecting a fellow in the WORLD (step 16) NOT enabling Dismiss/Leader
or NOT highlighting their row.
- An invite dialog NOT appearing on the second client regardless of that
client's own Ignore/Auto-Accept checkbox state (steps 19-21) — unless
your local ACE build itself refuses/auto-joins the recruit
server-side first, in which case no request ever reaches the second
client and that is expected, not a bug.
- The two option checkboxes NOT staying mutually exclusive (both ending
up checked at once).
- A 6- or 8-fellow percentage reading 45%/35% instead of 44%/34% (step
24 — a real acdream bug, not an ACE-divergence exemption).
- Any crash, hang, or exception in the log during create/recruit/
dismiss/quit/disband/leader/open actions, during a reconnect with the
panel left open, or while the invite dialog is open.
### Explicitly NOT in scope for this gate (FA4)
- Allegiance monarch/patron/vassal LIVE population, swear/break/kick
wire sends, and their confirmation dialogs (FA5).
- Friends/Squelch add/remove/appear-offline/clear wire sends (D1 — out
of campaign scope entirely, register row AD-79).
- The bot-vs-ACE two-session fellowship gate (FA6) — every step marked
**[TWO-CLIENT]** above may be deferred there if a second account is
not available for this connected gate.
- Retail's exact `StringInfo`-templated row/stats sentence and
`ACCharGenData::FormatName` name canonicalization — acdream renders
plain numeric composites and the raw typed name instead (register row
AD-81); do not report "the text doesn't read like a full sentence" or
"my typed name wasn't auto-capitalized" as bugs.
---
## FA5 — the Allegiance page fully live
**Prerequisite:** open the Allegiance tab (F3, or click it — it is the
authored DEFAULT tab, so simply pressing F3 from a cold start lands here).
All step numbers below restart at 1 for this section's own numbering.
**Honest limitation up front, same shape as FA4's:** swearing allegiance
genuinely needs a SECOND character (you cannot swear to yourself, and
retail requires the target to already be online and selected in the
world). If a second ACE account/character is not available, run every
step marked **[SOLO]** and defer the steps marked **[TWO-CLIENT]** to
Campaign FA slice FA6's bot-vs-ACE gate
(`docs/plans/2026-08-11-fellowship-allegiance-campaign.md`, D8) — do not
treat an unrun two-client step as a failure.
### The data subscription (CF-1) — profile populates on SHOW, not on
`@allegiance info` — [SOLO]
**[FA2 re-review CF-1 correction, carried into this slice's own contract:
the panel's data comes from `0x001F AllegianceUpdateRequest``0x0020
AllegianceUpdate`, NOT from the `0x027B`/`0x027C` `@allegiance info`
chat-command pair, which is text-only and does not feed the panel (FA2
MF-2's fix). Do not use `@allegiance info` as a trigger for anything in
this section.]**
1. **With NO allegiance (a fresh character), open the Allegiance tab.**
The self line (your name/rank/follower count) renders at the top;
both the monarch and patron blocks are HIDDEN (no allegiance means
neither relationship exists) — the empty state FA3 already gated, now
reachable live.
2. **If you already have an allegiance (monarch and/or patron), the
blocks should populate WITHIN A SECOND OR TWO of opening the tab** —
this is the `0x001F(1)``0x0020` round trip. If the blocks stay
empty indefinitely after opening the tab, that is the CF-1 subscribe
failing to reach the server — a real bug, not a display gap.
3. **Close the panel (or switch to Fellowship), then reopen the
Allegiance tab.** The data should still be there (or repopulate
quickly) — closing/reopening exercises the visible-branch toggle
(`0x001F(0)` on hide, `0x001F(1)` on show) without losing the profile.
4. **[reconnect, the FA4 MF-3-REOPEN analogue] Force a disconnect/
reconnect while ANY allegiance data was previously visible.** Once
the client re-enters the world on the new session, open (or confirm
already-open) the Allegiance tab. EXPECTED: the profile repopulates
on the new session within a second or two — this is
`RedeclareAfterWorldEntry`'s UNCONDITIONAL post-world `0x001F(1)`
resend, which fires regardless of whether the panel happens to be
open at that exact moment (retail's own `RecvNotice_PlayerDescReceived`
arm does not check panel visibility either — see the class doc's CF-1
section). BUG if the profile stays empty/stale for the rest of the new
session — that would mean the exact bug class MF-3-REOPEN caught for
Fellowship's `0x00A6` recurred here for `0x001F`.
### Monarch / patron / self blocks — [SOLO if you already have an
allegiance, otherwise needs a TWO-CLIENT swear first]
5. **Your own line (top of the page) always shows your allegiance's
name, your follower count, and your rank as plain numbers** — retail's
exact sentence wording is NOT reproduced (the unported `StringInfo`
substitution engine, register row AD-85, same class as FA4's AD-81);
do not report "it just shows a number, not a sentence" as a bug.
6. **If you are a VASSAL (you have a patron who is not the monarch)**,
the patron block shows their name and a follower-adjacent
"experience passed up" number. If your patron IS the monarch
(single-tier allegiance), the patron block should be HIDDEN and the
monarch block should show an ADDITIONAL sub-line (the
"experience passed up" number) that does not appear for a
multi-tier allegiance — this is retail's `PatronSlashMonarchLabel`
swap (fix-round SF-7, the coarser FA3 gate's own owed correction).
7. **If you ARE the monarch of your allegiance**, the monarch block
should be HIDDEN (retail hides it when the monarch IS the viewer,
not just when there is no monarch at all) — confirm this specifically
if you can arrange to be a monarch; it is easy to miss since "no
monarch block" looks identical whether you have no allegiance at all
or you're sitting at the top of one.
8. **A logged-out monarch or patron's block should render visually
dimmed** (their name still shows, but the block itself looks
"greyed"/disabled) — retail's `SetState(1)`/`SetState(0xD)` per-block
online/offline toggle. This needs a second client to log out while
you're watching, so mark it **[TWO-CLIENT]** if you can't arrange it
solo.
### Vassal list — [TWO-CLIENT for a populated list; SOLO to confirm the
empty case]
9. **With no vassals, the vassal list box is simply empty** (no
placeholder text, matching retail — there is no "you have no vassals"
frame the way Fellowship has an empty-state frame swap).
10. **With one or more vassals (needs a second character sworn to you —
see the swear steps below), each row shows the vassal's name and an
"experience passed up" number** (their OWN tithed amount, not
yours — distinct from the monarch/patron block's number, which is
YOUR OWN tithed amount).
11. **An OFFLINE vassal's row should visually distinguish itself**
(acdream tints the name grey rather than porting retail's dedicated
offline-marker element visually — confirm SOME visible difference
between an online and offline vassal row).
12. **With several vassals, scroll the list if it overflows the visible
area** and confirm every row remains reachable via the scrollbar
(same `UiTemplateListBox` mechanism FA3/FA4 already gated).
13. **Sibling order — a SOFT/informational check, not a fail-the-gate
item.** If you can arrange three or more vassals sworn to the same
patron in a KNOWN order, the list should render them in REVERSE
swear order (the last one to swear appears FIRST) — this is retail's
own tree-assembly rule (lane C §4.4 point 3), not a display bug. Hard
to verify without careful setup; report what you observe but do not
treat a mismatch as an automatic fail without double-checking the
actual swear order first.
### Swear — [TWO-CLIENT]
14. **On the SECOND client (the would-be vassal, with NO current
patron), select the FIRST client's character in the world** (click
their 3D model) and click **Swear** on the Allegiance page.
EXPECTED: a confirmation dialog appears on the SECOND client asking
to confirm swearing allegiance — the message is whatever retail's
template resolves to VERBATIM, or the target's bare name if the
template did not resolve (register row AD-85 — do not report "the
dialog doesn't read like a full sentence" as a bug).
15. **Accept the dialog on the second client.** EXPECTED: the FIRST
client (the new patron) receives ITS OWN confirmation dialog asking
to accept the new vassal — this is the SERVER-DRIVEN
`ConfirmationType` 1 round trip (`GameplayConfirmationController`,
generic, no allegiance-specific code), showing ACE's own message
(typically just the vassal's bare name, lane C §6.4 — again, not a
full sentence; not a bug).
16. **Accept the dialog on the first client too.** Both clients' Allegiance
pages should update: the second character now shows a patron block
naming the first character; the first character now shows the second
character as a new row in their vassal list.
17. **Swear button enable rule** — with NO current patron and a player
selected in the world who is not already in your allegiance, Swear
should be ENABLED; with a patron already sworn, or nothing selected,
or yourself selected, it should be DISABLED. **[register row AD-84]**
acdream does not check "is the selection a player" the way retail
does — selecting a non-player object (a monster, an item) may leave
Swear lit when retail would grey it out; the server refuses the send
either way, so this is a cosmetic gap, not a wire-behavior bug — do
not fail the gate over it, but do note it if you observe it.
18. **Reject the swear-target dialog (step 14) instead of accepting.** No
dialog reaches the first client; nothing changes on either side.
19. **Reject the accept-swear dialog (step 15) instead of accepting.** The
second character does NOT become a vassal; both clients' allegiance
state stays as it was before step 14.
### Break and Kick — [TWO-CLIENT] (needs an existing swear relationship
from the steps above, or a pre-existing allegiance)
20. **As the VASSAL, click Break.** A confirmation dialog appears asking
to break allegiance FROM your CURRENT patron (target latched at
click time, matching retail — changing your world selection after
clicking Break should NOT change who you break from, since Break's
target is your patron, not a world selection at all). Accept it:
your patron block clears/hides; the patron's vassal list loses your
row.
21. **As the PATRON, select a vassal row (click their name in the vassal
list — NOT the world) and click Kick.** A confirmation dialog
appears asking to kick that SPECIFIC vassal. Accept it: they are
removed from your vassal list; their own client's patron block
clears/hides.
22. **Kick button enable rule** — with no vassal row selected, Kick
should be DISABLED; select any vassal row and it should ENABLE.
23. **Break button enable rule** — with no patron, Break should be
DISABLED; with any patron, it should be ENABLED.
24. **Reject either dialog** (Break or Kick) instead of accepting — no
change on either client, no crash or stuck dialog state.
### The `IgnoreAllegianceRequests` checkbox — [SOLO]
25. **The checkbox on this page and the SAME-named row on the Options →
Character tab are the SAME value** — toggling one should reflect on
the other (open both surfaces side by side, or toggle-then-reopen to
confirm). This bit is a pure server-side filter with no client-side
consumer in retail either (lane C §1.6) — checking it does not change
any OTHER client-visible behavior; do not report "checking it doesn't
seem to do anything" as a bug, that is correct.
### ACE-zeroed fields — honest, not a bug
26. **You will never see officer names, a message-of-the-day, a lock
indicator, or an "allegiance age" anywhere on this page.** Retail's
OWN `gmAllegianceUI` has no widgets for any of these either (they are
chat-verb-only in the 2013 client — `@allegiance officer`, `@allegiance
motd`, etc., all out of this campaign's scope) — do not report their
absence as a bug. Separately, even the CHAT-VERB versions of these
features (if you try them) will show blank/default values against
the currently-targeted ACE build, because ACE deliberately zeroes
these fields on the wire regardless of the allegiance's real state
(register row AD-86) — that is an ACE-vs-retail divergence, not an
acdream bug either way.
### What to report (FA5-specific — in addition to the FA3/FA4 lists above)
- The Allegiance tab NOT being the panel's default (step 1 of §FA3 already
covers the tab itself; this section is about its DATA) — the profile
never populating after opening the tab (step 2), or staying stale
after a reconnect (step 4).
- The monarch block staying VISIBLE while you are the monarch yourself
(step 7), or the patron block staying visible while your patron is the
monarch (step 6) — both are the exact SF-7 per-relationship gate this
slice fixed; a coarse "both blocks show whenever I have ANY profile"
regression would be the FA3 bug resurfacing.
- The monarch/patron "experience passed up" sub-line failing to appear
under the correct block per step 6's single-tier-vs-multi-tier
distinction.
- A vassal row not appearing at all after a successful swear (step 16),
or an online/offline vassal being visually indistinguishable (step 11).
- Swear/Break/Kick sending the WRONG target guid — e.g. Break targeting
something other than your actual current patron, or Kick targeting a
vassal other than the one you clicked.
- Either confirmation-dialog direction of Swear (steps 14/15) not
appearing, or accepting one but not sending the matching wire command.
- The `IgnoreAllegianceRequests` checkbox NOT staying in sync between
this page and the Options → Character tab (step 25).
- Any crash, hang, or exception in the log during swear/break/kick,
during a reconnect with the Allegiance tab open, or while a
confirmation dialog is open.
### Explicitly NOT in scope for this gate (FA5)
- The bot-vs-ACE two-session swear gate (FA6) — every step marked
**[TWO-CLIENT]** above may be deferred there if a second account is
not available for this connected gate.
- Retail's exact `StringInfo`-templated sentences for followers/rank/
experience-passed-up and the three confirmation dialogs — acdream
renders plain numbers and, for dialogs, either retail's unsubstituted
template text or the bare target name (register row AD-85); do not
report "it just shows a number" or "the dialog doesn't read like a
full sentence" as bugs.
- Officer/MOTD/lock/ban/hometown management — chat-verb-only in retail,
out of this campaign's scope entirely (§4 of the plan), and unreliable
against the currently-targeted ACE build regardless (register row
AD-86).
- Swear's missing "target is a player" enable-rule check (register row
AD-84) — a cosmetic superset-of-retail gap, not a wire-behavior bug.
---
## FA6 — the bot-vs-ACE two-session gate
Unlike FA3-FA5, this is not a graphical-client gate: no window is
launched. The coordinator runs `acdream-headless run` with a config
declaring TWO sessions — `testaccount`/`+Acdream` as the fellowship
LEADER/allegiance PATRON, `testaccount2` as the fellowship RECRUIT/
allegiance VASSAL — both selecting policy id `fellowship-allegiance-gate`
with `role: "leader"`/`role: "recruit"`. Both sessions run in the SAME
process, ticked sequentially on one dedicated update thread.
### Automated fellowship gate — the shipped, passing part
**Recipe:**
```json
{
"version": 1,
"sessions": [
{
"id": "fa6-bot-a-leader",
"endpoint": { "host": "127.0.0.1", "port": 9000 },
"account": "testaccount",
"character": { "name": "+Acdream" },
"policy": { "id": "fellowship-allegiance-gate", "role": "leader" },
"credential": { "provider": "environment", "reference": "FA6_BOT_A_PASSWORD" }
},
{
"id": "fa6-bot-b-recruit",
"endpoint": { "host": "127.0.0.1", "port": 9000 },
"account": "testaccount2",
"character": { "index": 0 },
"policy": { "id": "fellowship-allegiance-gate", "role": "recruit" },
"credential": { "provider": "environment", "reference": "FA6_BOT_B_PASSWORD" },
"characterOptions": { "FellowshipAutoAcceptRequests": true }
}
]
}
```
The Recruit session's `character` selector deliberately does NOT hard-code
a name — `{"index": 0}` picks the first available character on
`testaccount2`, and the Leader bot discovers that character's real name
live (`FellowshipAllegianceGateCoordinator.RecruitCharacterName`, set by
the Recruit bot's own `HeadlessSessionHost` the instant CharacterList
selection resolves it) rather than trusting a config-file guess. Run it:
`acdream-headless run --config fa6-gate.json` with `FA6_BOT_A_PASSWORD`/
`FA6_BOT_B_PASSWORD` set in the environment. The process is
self-terminating — every policy stage carries its own timeout, and the
run ends (gracefully, with ACE-confirmed per-character logout) on its own
once both bots reach `Done` or fault. Do not `timeout`/Ctrl+C the process
mid-run: an external kill during an in-world session leaves ACE holding a
stale session for ~3 minutes (CLAUDE.md's documented logout-before-
reconnect discipline; one of this slice's OWN runs discovered this
firsthand from wrapping a probe run in bash's `timeout`, not from the
gate's own code path).
**What the fellowship gate proves, per stage:**
1. **Proximity.** The Leader sends retail's admin `@teleallto` (no target
= "teleport everyone online to me") on a 5 s retry, then polls
`RuntimeFriendlyTargetQuery.FindPlayerByName` for the Recruit bot's
discovered name specifically — NOT the ambiguous "nearest any player"
query, which a live run found picks up a stray third character on a
shared ACE dev instance if one happens to be online nearby.
2. **Create + recruit.** `Fellowship.Create` then `Fellowship.Recruit`
against the name-matched guid.
3. **The decisive assertion.** The Recruit bot's OWN
`RuntimeFellowshipState` — a completely separate process's canonical
Runtime owner, not the Leader's local echo — flips
`IsInFellowship=true`, `MemberCount=2`, `LeaderGuid=<Leader's guid>`.
This is the proof that `0x02BE`/`0x02C0` inbound reached a real second
client, not just that the Leader's own send succeeded.
4. **D4 panel-open + vitals.** The Leader declares `SetPanelOpen(true)`
(`0x00A6`) and asserts the Recruit's own fellowship member row already
carries nonzero vitals (`MaxHealth`) — headless bots have no panel to
freeze/unfreeze, so this proves the declaration sends and that vitals
data is present, not the freeze-while-closed half of D4 (that remains
a graphical-client-only observation, FA4's own owed connected gate).
5. **Mid-flow reconnect.** Both bots reconnect
(`commands.Session.Reconnect`) after establishing, then assert the
fellowship state re-seeds from scratch (a fresh generation clears
`RuntimeFellowshipState` per FA2 D2) — proving reconnect-idempotence
over the real wire, with a genuine second account watching.
6. **Teardown.** The Leader disbands (`Fellowship.Quit(disband: true)`);
both bots assert their own `IsInFellowship` clears.
**Result — PASSED, 2026-08-12 (five of six live runs; the sixth, run 2,
hit a since-fixed wrong-target bug before the decisive stage was even
reached):**
```
[fa6-recruit] DECISIVE ASSERTION PASSED: own RuntimeFellowshipState
flipped IsInFellowship=true, MemberCount=2, LeaderGuid=0x5000000A —
recruit inbound path reached THIS process's own Runtime owner
[fa6-leader] panel-open declared; recruit vitals name='+Horan'
maxHealth=201
```
Two real defects surfaced and were fixed IN this slice, both now covered
by conformance tests: headless hosts silently dropped every server-driven
confirmation (`HeadlessSessionHost.OnConfirmationRequest` was `null`); and
`RuntimeFriendlyTargetQuery.FindClosestOtherPlayer`'s "nearest any player"
query picked up a stray third character (`+Je`, `0x50000001`) instead of
the actual Recruit bot after `@teleallto`, fixed by the name-matched
`FindPlayerByName`.
### Allegiance bot gate — DEFERRED (docs/ISSUES.md #384)
**Status: not shipped as an automated gate.** The allegiance half of the
SAME policy classes (Leader's `WaitForVassal`; Recruit's `Swear`/
`WaitSwornSeed`/`Break`/`WaitBrokenSeed`) is fully written and wired —
proximity, the `0x001D` swear send, a confirmation-relay seam on the
Leader (the would-be patron, per `docs/research/2026-08-11-fa-allegiance-
wire.md` §1.3's "Target (would-be patron)" steps 4-7), the decisive
`TotalVassals`/`TryGetPatron` assertions, break, and reconnect-idempotence
— but is gated OFF by default (`AllegianceGateEnabled = false` in both
policy classes) because it has never actually completed over the wire.
Six live runs against local ACE all showed the same symptom: ACE returns
**nothing** to the swear — no `0x0274` confirmation, no `0x0020` tree
update, no WeenieError — confirmed at 0.005 m separation between the two
bots (ruling out retail's 2.0 m swear-distance gate). See `docs/ISSUES.md`
#384 for the full evidence trail and register row AD-87 for the
divergence this creates (the allegiance wire/state code is unit-tested
and locally-echoed but not wire-verified end-to-end).
**[TWO-CLIENT] Manual counterpart for the user's connected gate** — this
is the step that can settle #384 outside the headless harness:
25. With TWO graphical clients logged into different characters near each
other (the existing FA5 `[TWO-CLIENT]` swear steps 14-16 already
exercise this UI path), have character A select character B and click
Swear. **Report specifically:** does B's client show the incoming
"accept swear" confirmation dialog at all? If B accepts, does A's
Allegiance page then show B in A's vassal list, and does B's page show
A as patron? If the SAME silent non-response reproduces in the
graphical client (no dialog ever appears on B's side, no error on
A's), that points at an ACE-side rule or a genuine defect in acdream's
`0x001D` wire builder rather than anything specific to the headless
harness — the two clients are real user accounts, not `testaccount`/
`testaccount2`, which also rules out any GM-flag-specific explanation.
If it works normally in the graphical client, the symptom is scoped
to `testaccount`/`testaccount2` specifically (a GM flag, a rank/self-
allegiance precondition, or state left over from this session's
testing) — re-run the headless gate with a different account pair
before reopening #384 as a real wire bug.
### Explicitly NOT in scope for this gate
- A fully verified allegiance bot gate — deferred to #384; do not report
its absence as a regression, and do not re-run the headless allegiance
path repeatedly hoping for a different result without new information
(six identical live results is enough evidence, not bad luck).
- Kick — no test exercises it (would need a THIRD account to have a
vassal to kick that isn't also the tester).
- Kick/kick-confirmation UI — FA5's own `[TWO-CLIENT]` scope, unrelated
to this bot gate.