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

@ -19,6 +19,8 @@ public sealed class GameRuntimeTests
Assert.Same(runtime.CharacterOwner.View, runtime.Character);
Assert.Same(runtime.CommunicationOwner.View, runtime.Chat);
Assert.Same(runtime.CommunicationOwner.SocialView, runtime.Social);
Assert.Same(runtime.FellowshipOwner.View, runtime.Fellowship);
Assert.Same(runtime.AllegianceOwner.View, runtime.Allegiance);
Assert.Same(runtime.ActionOwner.View, runtime.Actions);
Assert.Same(runtime.MovementOwner.View, runtime.Movement);
Assert.Same(runtime.EnvironmentOwner, runtime.Environment);
@ -110,6 +112,8 @@ public sealed class GameRuntimeTests
[InlineData((int)GameRuntimeConstructionPoint.InventoryCreated)]
[InlineData((int)GameRuntimeConstructionPoint.CharacterCreated)]
[InlineData((int)GameRuntimeConstructionPoint.CommunicationCreated)]
[InlineData((int)GameRuntimeConstructionPoint.FellowshipCreated)]
[InlineData((int)GameRuntimeConstructionPoint.AllegianceCreated)]
[InlineData((int)GameRuntimeConstructionPoint.MovementCreated)]
[InlineData((int)GameRuntimeConstructionPoint.ActionsCreated)]
[InlineData((int)GameRuntimeConstructionPoint.EnvironmentCreated)]
@ -147,6 +151,10 @@ public sealed class GameRuntimeTests
Assert.True(captured.Inventory.CaptureOwnership().IsConverged);
if (captured.Communication is not null)
Assert.True(captured.Communication.CaptureOwnership().IsConverged);
if (captured.Fellowship is not null)
Assert.True(captured.Fellowship.CaptureOwnership().IsConverged);
if (captured.Allegiance is not null)
Assert.True(captured.Allegiance.CaptureOwnership().IsConverged);
if (captured.EntityObjects is not null)
{
Assert.True(captured.EntityObjects.CaptureOwnership().IsConverged);