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>
This commit is contained in:
Erik 2026-08-12 10:26:19 +02:00
parent 022b1844e1
commit 01fafe7b37
2 changed files with 164 additions and 2 deletions

File diff suppressed because one or more lines are too long

View file

@ -4,7 +4,10 @@
with `ACDREAM_LIVE=1` against the local ACE server (`testaccount` / with `ACDREAM_LIVE=1` against the local ACE server (`testaccount` /
`+Acdream`). Anything marked **INERT** is authored and clickable but `+Acdream`). Anything marked **INERT** is authored and clickable but
deliberately does nothing yet — that is the correct, contracted behavior deliberately does nothing yet — that is the correct, contracted behavior
for this slice (D1), not a bug. 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 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 four pages' empty states, and Friends/Squelch read-only lists. Fellowship
@ -750,3 +753,162 @@ from the steps above, or a pre-existing allegiance)
AD-86). AD-86).
- Swear's missing "target is a player" enable-rule check (register row - 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. 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.