feat(runtime): FA2 -- RuntimeFellowshipState + RuntimeAllegianceState sibling J-owners

Two new sibling Runtime owners under GameRuntime per the Slice-J
pattern (D2): RuntimeFellowshipState is session-scoped (a new
RuntimeGenerationResetStage.Fellowship clears it at every generation
reset, matching the ExternalContainer precedent); RuntimeAllegianceState
survives reconnect behind a HasServerSeed-style one-way latch and
participates in NO reset stage (its data persists like a real
disconnect does not sever allegiance membership).

Fellowship: full-update REPLACE, incremental-fellow UPSERT, self-vs-
other quit/dismiss removal (self clears the whole snapshot), disband
clear, and retail's leader hand-off rule for the Quit button
(RequiresLeaderHandoffBeforeQuit -- the current leader quitting WITHOUT
disbanding must send 0x0290 AssignNewLeader before 0x00A3, lane B
§2.5/§3.6).

Allegiance: seeded by AllegianceUpdate (0x0020, always self) and,
self-gated on TargetGuid == playerGuid(), by AllegianceInfoResponse
(0x027C); wraps the FA1-assembled flat AllegianceMemberRecord list
directly (AllegianceTree was deleted at FA1 -- nothing left to wrap).

Both apply the full 8-edit J-owner template: construction + fault
points + Owner/View properties + CaptureOwnership + a new
GameRuntimeTeardownStage pair (FellowshipDisposed/AllegianceDisposed,
stage count 11->13) + RuntimeGameplayOwnershipSnapshot inclusion +
RuntimeStateCheckpoint/trace fields. IRuntimeFellowshipCommands/
IRuntimeAllegianceCommands added to IGameRuntimeCommands and
implemented on DirectGameRuntimeCommandAdapter. No IRuntimeEventObserver
member added (D2) -- consumers poll Snapshot.Revision.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Erik 2026-08-12 01:31:54 +02:00
parent 1c40104896
commit 369729f06a
19 changed files with 1949 additions and 35 deletions

View file

@ -79,7 +79,13 @@ public sealed record LiveSocialSessionBindings(
// existing caller (including tests that build a bare ChatLog with no
// owning RuntimeCommunicationState) compiles unchanged; GameEventWiring
// falls back to its pre-CH2 chat-only behavior when this is null.
Action<string, RetailLogTextType>? AddText = null);
Action<string, RetailLogTextType>? AddText = null,
// Campaign FA slice FA2 (2026-08-12): the two sibling J-owners.
// Trailing/optional so every existing positional caller (Headless)
// compiles unchanged (docs/research/2026-08-11-fa-acdream-seams.md
// §2.4 — "the established compatibility convention").
RuntimeFellowshipState? Fellowship = null,
RuntimeAllegianceState? Allegiance = null);
/// <summary>
/// Owns every inbound subscription for one exact live session. Domain state
@ -228,7 +234,34 @@ public sealed class LiveSessionEventRouter : ILiveSessionEventRouting
externalContainers: inventory.ExternalContainers,
vendor: inventory.Vendor,
onInterfaceText: social.AddText,
accepting: IsAccepting));
accepting: IsAccepting,
// Campaign FA slice FA2 (2026-08-12): fellowship/allegiance
// sink registration — the ONE inbound registration site
// (docs/research/2026-08-11-fa-acdream-seams.md §2.1),
// reached identically by both hosts since LiveSessionEventRouter
// itself is shared (LiveSessionEventRouter.Attach, K-slice
// unification).
onFellowshipFullUpdate: update =>
social.Fellowship?.ApplyFullUpdate(update),
onFellowshipUpdateFellow: update =>
social.Fellowship?.ApplyUpdateFellow(update),
onFellowshipQuit: quitterGuid =>
social.Fellowship?.ApplyQuit(quitterGuid, inventory.PlayerGuid()),
onFellowshipDismiss: dismissedGuid =>
social.Fellowship?.ApplyDismiss(dismissedGuid, inventory.PlayerGuid()),
onFellowshipDisband: () => social.Fellowship?.ApplyDisband(),
onAllegianceUpdate: update =>
social.Allegiance?.ApplyUpdate(update),
onAllegianceInfoResponseSelf: response =>
social.Allegiance?.ApplyInfoResponseSelf(response),
onAllegianceUpdateDone: weenieError =>
social.Allegiance?.ApplyUpdateDone(weenieError),
onAllegianceUpdateAborted: weenieError =>
social.Allegiance?.ApplyUpdateAborted(weenieError),
onAllegianceLoginNotification: notice =>
social.Allegiance?.ApplyLoginNotification(
notice.CharacterGuid,
notice.IsLoggedIn)));
ConstructionCheckpoint();
// Campaign P Slice P1 (2026-07-30): burden recompute triggers —