refactor(runtime): own inventory transaction state

Move the retail one-request-at-a-time gate, shared use busy references, external-container state, item mana, shortcuts, and desired-component snapshots into one Runtime-owned graph over J3's exact ClientObjectTable. Retained UI and session routing now borrow that owner; reset and shutdown preserve the existing order while failure/reentrancy tests protect the transaction boundary.

Co-authored-by: Codex <codex@openai.com>
This commit is contained in:
Erik 2026-07-26 08:20:32 +02:00
parent 595d5e6b01
commit 011efbeaa7
18 changed files with 1337 additions and 406 deletions

View file

@ -335,22 +335,19 @@ public sealed class GameWindow :
public AcDream.Core.Social.SquelchState Squelch =>
_runtimeCommunication.Squelch;
public readonly AcDream.Core.Combat.CombatState Combat = new();
public readonly AcDream.Core.Items.ItemManaState ItemMana = new();
private readonly DesiredComponentSnapshotState _desiredComponents = new();
private readonly RuntimeEntityObjectLifetime _runtimeEntityObjects = new();
private readonly RuntimeInventoryState _runtimeInventory;
public AcDream.Core.Items.ItemManaState ItemMana =>
_runtimeInventory.ItemMana;
public IReadOnlyList<(uint Id, uint Amount)> DesiredComponents =>
_desiredComponents.Items;
_runtimeInventory.DesiredComponents.Items;
// Retail resolves learned IDs through portal.dat's CSpellTable. MagicCatalog
// installs that immutable table once DatCollection opens in OnLoad.
public AcDream.Core.Spells.SpellTable SpellTable => SpellBook.Metadata;
public readonly AcDream.Core.Spells.Spellbook SpellBook = null!;
private readonly RuntimeEntityObjectLifetime _runtimeEntityObjects = new();
/// <summary>Persisted hotbar shortcuts from the last PlayerDescription (D.5.1 toolbar source).</summary>
private readonly ShortcutSnapshotState _shortcutSnapshots = new();
public IReadOnlyList<AcDream.Core.Items.ShortcutEntry> Shortcuts
{
get => _shortcutSnapshots.Items;
private set => _shortcutSnapshots.Items = value;
}
public IReadOnlyList<AcDream.Core.Items.ShortcutEntry> Shortcuts =>
_runtimeInventory.Shortcuts.Items;
// Issue #5 — caches CreatureProfile.{Stamina, Mana, *Max} from
// PlayerDescription so the Vitals HUD can render those bars.
// Issue #6 — wired to SpellBook so GetMaxApprox folds enchantment
@ -377,7 +374,6 @@ public sealed class GameWindow :
private AcDream.App.Combat.CombatAttackController? _combatAttackController;
private AcDream.App.Combat.CombatTargetController? _combatTargetController;
private AcDream.App.UI.ItemInteractionController? _itemInteractionController;
private readonly AcDream.Core.Items.ExternalContainerState _externalContainers = new();
private AcDream.App.World.ExternalContainerLifecycleController? _externalContainerLifecycle;
private AcDream.App.Spells.MagicRuntime? _magicRuntime;
private AcDream.App.Spells.MagicCatalog? _magicCatalog;
@ -565,6 +561,7 @@ public sealed class GameWindow :
AcDream.App.Plugins.BufferedUiRegistry? uiRegistry = null)
{
_options = options ?? throw new System.ArgumentNullException(nameof(options));
_runtimeInventory = new RuntimeInventoryState(_runtimeEntityObjects);
var alphaScratchBudgets =
AcDream.App.Rendering.Residency.AlphaScratchBudgetProfile.Create(
_options.ResidencyBudgets.AlphaScratchBytes);
@ -1262,13 +1259,11 @@ public sealed class GameWindow :
Combat,
_combatAttackOperations,
_selection,
_externalContainers,
_runtimeEntityObjects.Objects,
_runtimeInventory,
contentEffectsAudio.MagicCatalog,
SpellBook,
_runtimeCommunication,
LocalPlayer,
ItemMana,
_stackSplitQuantity,
_uiRegistry,
_liveCombatModeCommands,
@ -1276,7 +1271,6 @@ public sealed class GameWindow :
_playerControllerSlot,
_localPlayerMode,
_characterOptions,
_shortcutSnapshots,
viewPlane => new SelectionCameraSource(
hostInputCamera.CameraController,
_window!,
@ -1398,8 +1392,7 @@ public sealed class GameWindow :
_viewportAspect,
_playerApproachCompletions,
_characterOptions,
_shortcutSnapshots,
_desiredComponents,
_runtimeInventory,
_pointerPosition,
_movementInput,
_inputCapture,
@ -1414,8 +1407,6 @@ public sealed class GameWindow :
_runtimeCommunication,
LocalPlayer,
SpellBook,
ItemMana,
_externalContainers,
_portalTunnelFallback,
Console.WriteLine),
this).Compose(
@ -1590,6 +1581,7 @@ public sealed class GameWindow :
_equippedChildRenderer,
_liveEntities,
_runtimeEntityObjects,
_runtimeInventory,
_runtimeCommunication,
_renderSceneShadow,
_livePresentationBindings,

View file

@ -84,6 +84,7 @@ internal sealed record LiveShutdownRoots(
EquippedChildRenderController? EquippedChildren,
LiveEntityRuntime? LiveEntities,
RuntimeEntityObjectLifetime EntityObjects,
RuntimeInventoryState Inventory,
RuntimeCommunicationState Communication,
RenderSceneShadowRuntime? RenderSceneShadow,
LivePresentationRuntimeBindings? PresentationBindings,
@ -390,6 +391,9 @@ internal static class GameWindowShutdownManifest
]),
new ResourceShutdownStage("runtime entity/object stream",
[
Hard(
"runtime inventory state",
live.Inventory.Dispose),
Hard(
"runtime entity/object lifetime",
live.EntityObjects.Dispose),