feat(headless): FA6 — role-discriminated policy + the fellowship/allegiance

two-bot gate

Adds the infrastructure docs/plans/2026-08-11-fellowship-allegiance-campaign.md
D8 and docs/research/2026-08-11-fa-acdream-seams.md §6.2 call for:

- HeadlessBotPolicyDescriptor gains an optional typed Role
  (HeadlessBotPolicyRole.Leader/Recruit) so two sessions selecting the SAME
  policy id run different scripts — fellowship leader/allegiance patron vs
  fellowship recruit/allegiance vassal.
- HeadlessBotPolicyFactory.Create widens from Create(string id) to
  Create(HeadlessBotPolicyDescriptor, GameRuntime) — the gate policies need
  RuntimeFriendlyTargetQuery, which (like its RuntimeHostileTargetQuery
  sibling) takes the concrete GameRuntime rather than the narrower
  IGameRuntimeView a policy's own Tick receives (IRuntimeEntityView's
  snapshot carries no name/PWD-bitfield). The single call site
  (HeadlessSessionHost.cs) already has the constructed runtime in scope, so
  no new constructor parameter or cross-session coordinator was needed.
- FellowshipAllegianceLeaderBotPolicy / FellowshipAllegianceRecruitBotPolicy:
  a full stage-machine pair covering proximity (retail's admin @teleallto —
  "teleport everyone online to me" — needs no cross-session name sharing,
  unlike @teleto <name>; D8's proximity requirement is load-bearing, recruit
  fails without it), fellowship create+recruit, the D4 0x00A6 panel-open
  declaration with a vitals-presence assertion, the decisive two-session
  assertions (the RECRUIT bot's own RuntimeFellowshipState/
  RuntimeAllegianceState flipping — not the Leader's local echo), a
  mid-flow reconnect on both bots proving FA2's reset-and-reseed semantics
  over the real wire, and teardown (disband / break) with matching
  decisive-clear assertions.

Every pre-FA6 policy (idle, lifecycle-smoke, observer-movement,
portal-route-smoke, jump-probe) is unaffected; the widened factory
signature is the only touch point.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Erik 2026-08-12 09:39:55 +02:00
parent 6b8e29cde6
commit 2825589035
3 changed files with 850 additions and 4 deletions

View file

@ -94,6 +94,31 @@ internal sealed class HeadlessBotPolicyDescriptor
{
[JsonRequired]
public string Id { get; init; } = string.Empty;
/// <summary>
/// Campaign FA slice FA6: optional role discriminator for a policy that
/// coordinates two bots run from the SAME process config (e.g. the
/// fellowship/allegiance gate's Leader — fellowship leader AND
/// allegiance patron — vs Recruit — fellowship recruit AND allegiance
/// vassal, docs/research/2026-08-11-fa-acdream-seams.md §6.2). Ignored
/// by every policy id that doesn't need it (all five pre-FA6 policies);
/// <see cref="AcDream.Headless.Policies.HeadlessBotPolicyFactory.Create"/>
/// rejects a missing role for a policy id that requires one.
/// </summary>
public HeadlessBotPolicyRole? Role { get; init; }
}
/// <summary>See <see cref="HeadlessBotPolicyDescriptor.Role"/>.</summary>
[JsonConverter(typeof(JsonStringEnumConverter<HeadlessBotPolicyRole>))]
internal enum HeadlessBotPolicyRole
{
/// <summary>Fellowship leader / allegiance patron — creates the
/// fellowship, recruits the Recruit bot, and receives its oath.</summary>
Leader,
/// <summary>Fellowship recruit / allegiance vassal — gets recruited and
/// swears to the Leader bot.</summary>
Recruit,
}
[JsonConverter(typeof(JsonStringEnumConverter<HeadlessCredentialProviderKind>))]