refactor(runtime): close canonical gameplay ownership

Unify the toolbar shortcut manager with Runtime inventory state, route retail-ordered shortcut and spellbook command effects through the canonical owners, and make retained controllers borrow those exact instances. Remove the item-interaction transaction fallback and add graphical/no-window parity plus failure-safe terminal ownership-ledger coverage.

Co-authored-by: Codex <codex@openai.com>
This commit is contained in:
Erik 2026-07-26 09:48:51 +02:00
parent ce6fae7b38
commit 89e6b207f8
36 changed files with 1433 additions and 251 deletions

View file

@ -71,6 +71,8 @@ internal sealed class CurrentGameRuntimeAdapter
sessionHost,
commands,
_view,
inventory,
character,
selectionState,
selection,
gameplayInput,

View file

@ -5,6 +5,7 @@ using AcDream.App.Net;
using AcDream.Core.Selection;
using AcDream.Core.Items;
using AcDream.Runtime;
using AcDream.Runtime.Gameplay;
using AcDream.Runtime.Session;
using AcDream.UI.Abstractions;
using AcDream.UI.Abstractions.Input;
@ -31,6 +32,8 @@ internal sealed class CurrentGameRuntimeCommandAdapter
private readonly LiveSessionHost _sessionHost;
private readonly ICommandBus _commands;
private readonly CurrentGameRuntimeViewAdapter _view;
private readonly RuntimeInventoryState _inventory;
private readonly RuntimeCharacterState _character;
private readonly SelectionState _selectionState;
private readonly SelectionInteractionController _selection;
private readonly GameplayInputFrameController _gameplayInput;
@ -42,6 +45,8 @@ internal sealed class CurrentGameRuntimeCommandAdapter
LiveSessionHost sessionHost,
ICommandBus commands,
CurrentGameRuntimeViewAdapter view,
RuntimeInventoryState inventory,
RuntimeCharacterState character,
SelectionState selectionState,
SelectionInteractionController selection,
GameplayInputFrameController gameplayInput,
@ -52,6 +57,8 @@ internal sealed class CurrentGameRuntimeCommandAdapter
_sessionHost = sessionHost ?? throw new ArgumentNullException(nameof(sessionHost));
_commands = commands ?? throw new ArgumentNullException(nameof(commands));
_view = view ?? throw new ArgumentNullException(nameof(view));
_inventory = inventory ?? throw new ArgumentNullException(nameof(inventory));
_character = character ?? throw new ArgumentNullException(nameof(character));
_selectionState = selectionState
?? throw new ArgumentNullException(nameof(selectionState));
_selection = selection ?? throw new ArgumentNullException(nameof(selection));
@ -288,8 +295,13 @@ internal sealed class CurrentGameRuntimeCommandAdapter
RuntimeCommandStatus gate = Validate(expectedGeneration, requireWorld: true);
if (gate != RuntimeCommandStatus.Accepted)
return Result(gate);
if (command.Index < 0
|| (command.ObjectId == 0u && command.SpellId == 0u))
var entry = new ShortcutEntry(
command.Index,
command.ObjectId,
command.SpellId);
if (!_inventory.TryAddShortcut(
entry,
() => _commands.Publish(new AddShortcutRuntimeCmd(entry))))
{
return EmitResult(
RuntimeCommandDomain.InventoryState,
@ -298,10 +310,6 @@ internal sealed class CurrentGameRuntimeCommandAdapter
command.ObjectId);
}
_commands.Publish(new AddShortcutRuntimeCmd(new ShortcutEntry(
command.Index,
command.ObjectId,
command.SpellId)));
return EmitResult(
RuntimeCommandDomain.InventoryState,
operation: 0,
@ -316,7 +324,10 @@ internal sealed class CurrentGameRuntimeCommandAdapter
RuntimeCommandStatus gate = Validate(expectedGeneration, requireWorld: true);
if (gate != RuntimeCommandStatus.Accepted)
return Result(gate);
if (index < 0)
if (!_inventory.TryRemoveShortcut(
index,
() => _commands.Publish(
new RemoveShortcutRuntimeCmd((uint)index))))
{
return EmitResult(
RuntimeCommandDomain.InventoryState,
@ -324,7 +335,6 @@ internal sealed class CurrentGameRuntimeCommandAdapter
RuntimeCommandStatus.Rejected);
}
_commands.Publish(new RemoveShortcutRuntimeCmd((uint)index));
return EmitResult(
RuntimeCommandDomain.InventoryState,
operation: 1,
@ -340,9 +350,14 @@ internal sealed class CurrentGameRuntimeCommandAdapter
RuntimeCommandStatus gate = Validate(expectedGeneration, requireWorld: true);
if (gate != RuntimeCommandStatus.Accepted)
return Result(gate);
if ((uint)tabIndex >= 8u
|| position < 0
|| spellId == 0u)
if (!_character.TryAddFavorite(
tabIndex,
position,
spellId,
() => _commands.Publish(new AddFavoriteRuntimeCmd(
spellId,
position,
tabIndex))))
{
return EmitResult(
RuntimeCommandDomain.Spellbook,
@ -351,10 +366,6 @@ internal sealed class CurrentGameRuntimeCommandAdapter
spellId);
}
_commands.Publish(new AddFavoriteRuntimeCmd(
spellId,
position,
tabIndex));
return EmitResult(
RuntimeCommandDomain.Spellbook,
operation: 0,
@ -370,7 +381,11 @@ internal sealed class CurrentGameRuntimeCommandAdapter
RuntimeCommandStatus gate = Validate(expectedGeneration, requireWorld: true);
if (gate != RuntimeCommandStatus.Accepted)
return Result(gate);
if ((uint)tabIndex >= 8u || spellId == 0u)
if (!_character.TryRemoveFavorite(
tabIndex,
spellId,
() => _commands.Publish(
new RemoveFavoriteRuntimeCmd(spellId, tabIndex))))
{
return EmitResult(
RuntimeCommandDomain.Spellbook,
@ -379,7 +394,6 @@ internal sealed class CurrentGameRuntimeCommandAdapter
spellId);
}
_commands.Publish(new RemoveFavoriteRuntimeCmd(spellId, tabIndex));
return EmitResult(
RuntimeCommandDomain.Spellbook,
operation: 1,
@ -394,7 +408,10 @@ internal sealed class CurrentGameRuntimeCommandAdapter
RuntimeCommandStatus gate = Validate(expectedGeneration, requireWorld: true);
if (gate != RuntimeCommandStatus.Accepted)
return Result(gate);
_commands.Publish(new SetSpellbookFilterRuntimeCmd(filters));
_character.SetSpellbookFilter(
filters,
() => _commands.Publish(
new SetSpellbookFilterRuntimeCmd(filters)));
return EmitResult(
RuntimeCommandDomain.Spellbook,
operation: 2,
@ -433,7 +450,12 @@ internal sealed class CurrentGameRuntimeCommandAdapter
RuntimeCommandStatus gate = Validate(expectedGeneration, requireWorld: true);
if (gate != RuntimeCommandStatus.Accepted)
return Result(gate);
if (componentId == 0u)
if (!_character.TrySetDesiredComponent(
componentId,
amount,
() => _commands.Publish(new SetDesiredComponentRuntimeCmd(
componentId,
amount))))
{
return EmitResult(
RuntimeCommandDomain.Spellbook,
@ -442,9 +464,6 @@ internal sealed class CurrentGameRuntimeCommandAdapter
componentId);
}
_commands.Publish(new SetDesiredComponentRuntimeCmd(
componentId,
amount));
return EmitResult(
RuntimeCommandDomain.Spellbook,
operation: 4,
@ -458,7 +477,9 @@ internal sealed class CurrentGameRuntimeCommandAdapter
RuntimeCommandStatus gate = Validate(expectedGeneration, requireWorld: true);
if (gate != RuntimeCommandStatus.Accepted)
return Result(gate);
_commands.Publish(new ClearDesiredComponentsRuntimeCmd());
_character.ClearDesiredComponents(
() => _commands.Publish(
new ClearDesiredComponentsRuntimeCmd()));
return EmitResult(
RuntimeCommandDomain.Spellbook,
operation: 5,