refactor(runtime): own magic and player state

Move the coupled Spellbook and LocalPlayerState into one Runtime-owned character graph, route content, live-session, retained UI, reset, and shutdown through that exact owner, and delete the duplicate desired-component snapshot from inventory state. Preserve synchronous retail update and reset ordering while adding independent-instance and retryable-failure coverage.

Co-authored-by: Codex <codex@openai.com>
This commit is contained in:
Erik 2026-07-26 08:39:02 +02:00
parent d362172620
commit d02a12ceac
16 changed files with 418 additions and 96 deletions

View file

@ -48,9 +48,8 @@ internal sealed record InteractionRetainedUiDependencies(
SelectionState Selection,
RuntimeInventoryState Inventory,
MagicCatalog MagicCatalog,
Spellbook Spellbook,
RuntimeCharacterState Character,
RuntimeCommunicationState Communication,
LocalPlayerState LocalPlayer,
StackSplitQuantityState StackSplitQuantity,
BufferedUiRegistry? UiRegistry,
LiveCombatModeCommandSlot CombatModeCommands,
@ -350,7 +349,7 @@ internal sealed class RetailInteractionRetainedUiCompositionFactory
try
{
VitalsVM vitals = d.ExistingVitals
?? new VitalsVM(d.Combat, d.LocalPlayer);
?? new VitalsVM(d.Combat, d.Character.LocalPlayer);
UiHost host = lease.AcquireHost(
() => new UiHost(
d.Gl,
@ -372,7 +371,7 @@ internal sealed class RetailInteractionRetainedUiCompositionFactory
var characterSheet = new CharacterSheetProvider(
d.Inventory.Objects,
d.LocalPlayer,
d.Character.LocalPlayer,
playerGuid: () => d.PlayerIdentity.ServerGuid,
activeToonName: () => d.Settings.ActiveToonKey,
fallbackSheet: Studio.SampleData.SampleCharacter,
@ -389,7 +388,7 @@ internal sealed class RetailInteractionRetainedUiCompositionFactory
MagicRuntime magic = MagicRuntime.Create(
d.MagicCatalog,
d.Spellbook,
d.Character.Spellbook,
d.Inventory.Objects,
selectedObject: () => d.Selection.SelectedObjectId,
localPlayerId: () => d.PlayerIdentity.ServerGuid,
@ -421,7 +420,7 @@ internal sealed class RetailInteractionRetainedUiCompositionFactory
uint skillId = SkillId(school);
if (skillId != 0u)
return d.LocalPlayer.GetSkill(skillId)?.CurrentLevel ?? 0u;
return d.Character.LocalPlayer.GetSkill(skillId)?.CurrentLevel ?? 0u;
uint highest = 0u;
foreach (MagicSchool candidate in new[]
@ -435,7 +434,8 @@ internal sealed class RetailInteractionRetainedUiCompositionFactory
{
highest = Math.Max(
highest,
d.LocalPlayer.GetSkill(SkillId(candidate))?.CurrentLevel ?? 0u);
d.Character.LocalPlayer.GetSkill(
SkillId(candidate))?.CurrentLevel ?? 0u);
}
return highest;
}
@ -527,7 +527,7 @@ internal sealed class RetailInteractionRetainedUiCompositionFactory
() => d.Settings.Gameplay,
d.Settings.SetCombatGameplay),
Magic: new MagicRuntimeBindings(
d.Spellbook,
d.Character.Spellbook,
magic.Casting,
d.Inventory.Objects,
() => d.PlayerIdentity.ServerGuid,
@ -569,10 +569,10 @@ internal sealed class RetailInteractionRetainedUiCompositionFactory
late.Selection.ResolveVividTargetInfo,
late.SelectionCamera.UiSnapshot),
Indicators: new IndicatorRuntimeBindings(
d.Spellbook,
d.Character.Spellbook,
d.Inventory.Objects,
() => d.PlayerIdentity.ServerGuid,
() => d.LocalPlayer.GetAttribute(
() => d.Character.LocalPlayer.GetAttribute(
LocalPlayerState.AttributeKind.Strength)
is { } strength ? (int?)strength.Current : null,
() => late.Session.LinkStatus,
@ -614,7 +614,7 @@ internal sealed class RetailInteractionRetainedUiCompositionFactory
() => d.PlayerIdentity.ServerGuid,
iconComposer.GetIcon,
iconComposer.GetDragIcon,
() => d.LocalPlayer.GetAttribute(
() => d.Character.LocalPlayer.GetAttribute(
LocalPlayerState.AttributeKind.Strength)
is { } strength ? (int?)strength.Current : null,
guid => late.Session.CurrentSession?.SendUse(guid),