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

@ -6,6 +6,7 @@ using AcDream.Core.Net.Messages;
using AcDream.Core.Player;
using AcDream.Core.Social;
using AcDream.Core.Spells;
using AcDream.Runtime.Gameplay;
namespace AcDream.Runtime.Session;
@ -29,18 +30,16 @@ public sealed record LiveEnvironmentSessionSink(
public sealed record LiveInventorySessionBindings(
ClientObjectTable Objects,
LocalPlayerState LocalPlayer,
Func<uint> PlayerGuid,
Action<IReadOnlyList<ShortcutEntry>>? OnShortcuts,
Action<uint>? OnUseDone,
ItemManaState? ItemMana,
Action<IReadOnlyList<(uint Id, uint Amount)>>? OnDesiredComponents,
ExternalContainerState? ExternalContainers,
Action<AppraiseInfoParser.Parsed>? OnAppraisal = null);
public sealed record LiveCharacterSessionBindings(
CombatState Combat,
Spellbook Spellbook,
RuntimeCharacterState Character,
Func<uint, IReadOnlyDictionary<uint, uint>, uint>? ResolveSkillFormulaBonus,
Action<int, int>? OnSkillsUpdated,
Action<GameEvents.CharacterConfirmationRequest>? OnConfirmationRequest,
@ -114,7 +113,7 @@ public sealed class LiveSessionEventRouter : ILiveSessionEventRouting
session,
inventory.Objects,
inventory.PlayerGuid,
inventory.LocalPlayer,
character.Character.LocalPlayer,
IsAccepting));
ConstructionCheckpoint();
_subscriptions.Add(CombatStateWiring.Wire(
@ -149,9 +148,9 @@ public sealed class LiveSessionEventRouter : ILiveSessionEventRouting
session.GameEvents,
inventory.Objects,
character.Combat,
character.Spellbook,
character.Character.Spellbook,
social.Chat,
inventory.LocalPlayer,
character.Character.LocalPlayer,
social.TurbineChat,
onSkillsUpdated: character.OnSkillsUpdated,
resolveSkillFormulaBonus: character.ResolveSkillFormulaBonus,
@ -164,7 +163,7 @@ public sealed class LiveSessionEventRouter : ILiveSessionEventRouting
onConfirmationDone: character.OnConfirmationDone,
friends: social.Friends,
squelch: social.Squelch,
onDesiredComponents: inventory.OnDesiredComponents,
onDesiredComponents: null,
onCharacterOptions: character.OnCharacterOptions,
clientTime: character.ClientTime,
externalContainers: inventory.ExternalContainers,
@ -202,7 +201,7 @@ public sealed class LiveSessionEventRouter : ILiveSessionEventRouting
h => session.TurbineChatReceived -= h,
parsed => RouteTurbineChat(social.Chat, parsed));
Subscribe<PrivateUpdateVital.ParsedFull>(h => session.VitalUpdated += h, h => session.VitalUpdated -= h, vital =>
inventory.LocalPlayer.OnVitalUpdate(
character.Character.LocalPlayer.OnVitalUpdate(
vital.VitalId,
vital.Ranks,
vital.Start,
@ -211,7 +210,9 @@ public sealed class LiveSessionEventRouter : ILiveSessionEventRouting
Subscribe<PrivateUpdateVital.ParsedCurrent>(
h => session.VitalCurrentUpdated += h,
h => session.VitalCurrentUpdated -= h,
vital => inventory.LocalPlayer.OnVitalCurrent(vital.VitalId, vital.Current));
vital => character.Character.LocalPlayer.OnVitalCurrent(
vital.VitalId,
vital.Current));
if (Interlocked.CompareExchange(ref _lifecycleState, 2, 1) != 1)
throw new ObjectDisposedException(nameof(LiveSessionEventRouter));
@ -297,10 +298,9 @@ public sealed class LiveSessionEventRouter : ILiveSessionEventRouting
ArgumentNullException.ThrowIfNull(environment.EnvironChanged);
ArgumentNullException.ThrowIfNull(environment.ServerTimeUpdated);
ArgumentNullException.ThrowIfNull(inventory.Objects);
ArgumentNullException.ThrowIfNull(inventory.LocalPlayer);
ArgumentNullException.ThrowIfNull(inventory.PlayerGuid);
ArgumentNullException.ThrowIfNull(character.Combat);
ArgumentNullException.ThrowIfNull(character.Spellbook);
ArgumentNullException.ThrowIfNull(character.Character);
ArgumentNullException.ThrowIfNull(social.Chat);
ArgumentNullException.ThrowIfNull(social.TurbineChat);
}