refactor(runtime): expose canonical gameplay state

Move character options and movement skills into the Runtime-owned character graph, expose borrowed inventory, character, and social views, and route retained UI state commands through generation-gated typed Runtime contracts. Preserve the existing synchronous wire path while deleting the App-owned option and skill mirrors and extending normalized parity checkpoints.

Co-authored-by: Codex <codex@openai.com>
This commit is contained in:
Erik 2026-07-26 09:12:30 +02:00
parent 9d0d9b07e0
commit dcb61efb5a
34 changed files with 2076 additions and 103 deletions

View file

@ -3,6 +3,7 @@ using AcDream.App.Input;
using AcDream.App.Interaction;
using AcDream.App.Net;
using AcDream.Core.Selection;
using AcDream.Core.Items;
using AcDream.Runtime;
using AcDream.Runtime.Session;
using AcDream.UI.Abstractions;
@ -20,7 +21,11 @@ internal sealed class CurrentGameRuntimeCommandAdapter
IRuntimeCombatCommands,
IRuntimeMovementCommands,
IRuntimeChatCommands,
IRuntimePortalCommands
IRuntimePortalCommands,
IRuntimeInventoryStateCommands,
IRuntimeSpellbookCommands,
IRuntimeCharacterCommands,
IRuntimeSocialCommands
{
private readonly LiveSessionController _session;
private readonly LiveSessionHost _sessionHost;
@ -276,6 +281,340 @@ internal sealed class CurrentGameRuntimeCommandAdapter
return Result(RuntimeCommandStatus.Accepted);
}
public RuntimeCommandResult AddShortcut(
RuntimeGenerationToken expectedGeneration,
in RuntimeShortcutCommand command)
{
RuntimeCommandStatus gate = Validate(expectedGeneration, requireWorld: true);
if (gate != RuntimeCommandStatus.Accepted)
return Result(gate);
if (command.Index < 0
|| (command.ObjectId == 0u && command.SpellId == 0u))
{
return EmitResult(
RuntimeCommandDomain.InventoryState,
operation: 0,
RuntimeCommandStatus.Rejected,
command.ObjectId);
}
_commands.Publish(new AddShortcutRuntimeCmd(new ShortcutEntry(
command.Index,
command.ObjectId,
command.SpellId)));
return EmitResult(
RuntimeCommandDomain.InventoryState,
operation: 0,
RuntimeCommandStatus.Accepted,
command.ObjectId);
}
public RuntimeCommandResult RemoveShortcut(
RuntimeGenerationToken expectedGeneration,
int index)
{
RuntimeCommandStatus gate = Validate(expectedGeneration, requireWorld: true);
if (gate != RuntimeCommandStatus.Accepted)
return Result(gate);
if (index < 0)
{
return EmitResult(
RuntimeCommandDomain.InventoryState,
operation: 1,
RuntimeCommandStatus.Rejected);
}
_commands.Publish(new RemoveShortcutRuntimeCmd((uint)index));
return EmitResult(
RuntimeCommandDomain.InventoryState,
operation: 1,
RuntimeCommandStatus.Accepted);
}
public RuntimeCommandResult AddFavorite(
RuntimeGenerationToken expectedGeneration,
int tabIndex,
int position,
uint spellId)
{
RuntimeCommandStatus gate = Validate(expectedGeneration, requireWorld: true);
if (gate != RuntimeCommandStatus.Accepted)
return Result(gate);
if ((uint)tabIndex >= 8u
|| position < 0
|| spellId == 0u)
{
return EmitResult(
RuntimeCommandDomain.Spellbook,
operation: 0,
RuntimeCommandStatus.Rejected,
spellId);
}
_commands.Publish(new AddFavoriteRuntimeCmd(
spellId,
position,
tabIndex));
return EmitResult(
RuntimeCommandDomain.Spellbook,
operation: 0,
RuntimeCommandStatus.Accepted,
spellId);
}
public RuntimeCommandResult RemoveFavorite(
RuntimeGenerationToken expectedGeneration,
int tabIndex,
uint spellId)
{
RuntimeCommandStatus gate = Validate(expectedGeneration, requireWorld: true);
if (gate != RuntimeCommandStatus.Accepted)
return Result(gate);
if ((uint)tabIndex >= 8u || spellId == 0u)
{
return EmitResult(
RuntimeCommandDomain.Spellbook,
operation: 1,
RuntimeCommandStatus.Rejected,
spellId);
}
_commands.Publish(new RemoveFavoriteRuntimeCmd(spellId, tabIndex));
return EmitResult(
RuntimeCommandDomain.Spellbook,
operation: 1,
RuntimeCommandStatus.Accepted,
spellId);
}
public RuntimeCommandResult SetFilter(
RuntimeGenerationToken expectedGeneration,
uint filters)
{
RuntimeCommandStatus gate = Validate(expectedGeneration, requireWorld: true);
if (gate != RuntimeCommandStatus.Accepted)
return Result(gate);
_commands.Publish(new SetSpellbookFilterRuntimeCmd(filters));
return EmitResult(
RuntimeCommandDomain.Spellbook,
operation: 2,
RuntimeCommandStatus.Accepted);
}
public RuntimeCommandResult ForgetSpell(
RuntimeGenerationToken expectedGeneration,
uint spellId)
{
RuntimeCommandStatus gate = Validate(expectedGeneration, requireWorld: true);
if (gate != RuntimeCommandStatus.Accepted)
return Result(gate);
if (spellId == 0u)
{
return EmitResult(
RuntimeCommandDomain.Spellbook,
operation: 3,
RuntimeCommandStatus.Rejected,
spellId);
}
_commands.Publish(new ForgetSpellRuntimeCmd(spellId));
return EmitResult(
RuntimeCommandDomain.Spellbook,
operation: 3,
RuntimeCommandStatus.Accepted,
spellId);
}
public RuntimeCommandResult SetDesiredComponent(
RuntimeGenerationToken expectedGeneration,
uint componentId,
uint amount)
{
RuntimeCommandStatus gate = Validate(expectedGeneration, requireWorld: true);
if (gate != RuntimeCommandStatus.Accepted)
return Result(gate);
if (componentId == 0u)
{
return EmitResult(
RuntimeCommandDomain.Spellbook,
operation: 4,
RuntimeCommandStatus.Rejected,
componentId);
}
_commands.Publish(new SetDesiredComponentRuntimeCmd(
componentId,
amount));
return EmitResult(
RuntimeCommandDomain.Spellbook,
operation: 4,
RuntimeCommandStatus.Accepted,
componentId);
}
public RuntimeCommandResult ClearDesiredComponents(
RuntimeGenerationToken expectedGeneration)
{
RuntimeCommandStatus gate = Validate(expectedGeneration, requireWorld: true);
if (gate != RuntimeCommandStatus.Accepted)
return Result(gate);
_commands.Publish(new ClearDesiredComponentsRuntimeCmd());
return EmitResult(
RuntimeCommandDomain.Spellbook,
operation: 5,
RuntimeCommandStatus.Accepted);
}
public RuntimeCommandResult Advance(
RuntimeGenerationToken expectedGeneration,
in RuntimeAdvancementCommand command)
{
RuntimeCommandStatus gate = Validate(expectedGeneration, requireWorld: true);
if (gate != RuntimeCommandStatus.Accepted)
return Result(gate);
if (command.StatId == 0u
|| command.Cost == 0u)
{
return EmitResult(
RuntimeCommandDomain.Character,
(int)command.Kind,
RuntimeCommandStatus.Rejected,
command.StatId);
}
switch (command.Kind)
{
case RuntimeAdvancementKind.Attribute:
_commands.Publish(new RaiseAttributeRuntimeCmd(
command.StatId,
command.Cost));
break;
case RuntimeAdvancementKind.Vital:
_commands.Publish(new RaiseVitalRuntimeCmd(
command.StatId,
command.Cost));
break;
case RuntimeAdvancementKind.Skill:
_commands.Publish(new RaiseSkillRuntimeCmd(
command.StatId,
command.Cost));
break;
case RuntimeAdvancementKind.TrainSkill
when command.Cost <= uint.MaxValue:
_commands.Publish(new TrainSkillRuntimeCmd(
command.StatId,
(uint)command.Cost));
break;
default:
return EmitResult(
RuntimeCommandDomain.Character,
(int)command.Kind,
RuntimeCommandStatus.Rejected,
command.StatId);
}
return EmitResult(
RuntimeCommandDomain.Character,
(int)command.Kind,
RuntimeCommandStatus.Accepted,
command.StatId);
}
public RuntimeCommandResult SetOptions1(
RuntimeGenerationToken expectedGeneration,
uint options)
{
RuntimeCommandStatus gate = Validate(expectedGeneration, requireWorld: true);
if (gate != RuntimeCommandStatus.Accepted)
return Result(gate);
_commands.Publish(new SetCharacterOptionsRuntimeCmd(options));
return EmitResult(
RuntimeCommandDomain.Character,
operation: 4,
RuntimeCommandStatus.Accepted);
}
public RuntimeCommandResult Execute(
RuntimeGenerationToken expectedGeneration,
in RuntimeFriendCommand command)
{
RuntimeCommandStatus gate = Validate(expectedGeneration, requireWorld: true);
if (gate != RuntimeCommandStatus.Accepted)
return Result(gate);
RuntimeCommandStatus status = RuntimeCommandStatus.Accepted;
switch (command.Kind)
{
case RuntimeFriendCommandKind.Add
when !string.IsNullOrWhiteSpace(command.Name):
_commands.Publish(new AddFriendRuntimeCmd(command.Name));
break;
case RuntimeFriendCommandKind.Remove
when command.CharacterId != 0u:
_commands.Publish(new RemoveFriendRuntimeCmd(
command.CharacterId));
break;
case RuntimeFriendCommandKind.Clear:
_commands.Publish(new ClearFriendsRuntimeCmd());
break;
case RuntimeFriendCommandKind.RequestLegacyList:
_commands.Publish(new RequestLegacyFriendsRuntimeCmd());
break;
default:
status = RuntimeCommandStatus.Rejected;
break;
}
return EmitResult(
RuntimeCommandDomain.Social,
(int)command.Kind,
status,
command.CharacterId,
command.Name);
}
public RuntimeCommandResult Execute(
RuntimeGenerationToken expectedGeneration,
in RuntimeSquelchCommand command)
{
RuntimeCommandStatus gate = Validate(expectedGeneration, requireWorld: true);
if (gate != RuntimeCommandStatus.Accepted)
return Result(gate);
RuntimeCommandStatus status = RuntimeCommandStatus.Accepted;
switch (command.Scope)
{
case RuntimeSquelchScope.Character
when command.CharacterId != 0u
&& !string.IsNullOrWhiteSpace(command.Name):
_commands.Publish(new ModifyCharacterSquelchRuntimeCmd(
command.Add,
command.CharacterId,
command.Name,
command.MessageType));
break;
case RuntimeSquelchScope.Account
when !string.IsNullOrWhiteSpace(command.Name):
_commands.Publish(new ModifyAccountSquelchRuntimeCmd(
command.Add,
command.Name));
break;
case RuntimeSquelchScope.Global:
_commands.Publish(new ModifyGlobalSquelchRuntimeCmd(
command.Add,
command.MessageType));
break;
default:
status = RuntimeCommandStatus.Rejected;
break;
}
return EmitResult(
RuntimeCommandDomain.Social,
operation: 4 + (int)command.Scope,
status,
command.CharacterId,
command.Name);
}
private RuntimeCommandStatus Validate(
RuntimeGenerationToken expectedGeneration,
bool requireWorld)
@ -294,6 +633,17 @@ internal sealed class CurrentGameRuntimeCommandAdapter
uint objectId = 0u) =>
new(status, _view.Generation, objectId);
private RuntimeCommandResult EmitResult(
RuntimeCommandDomain domain,
int operation,
RuntimeCommandStatus status,
uint objectId = 0u,
string? text = null)
{
_events.EmitCommand(domain, operation, status, objectId, text);
return Result(status, objectId);
}
private RuntimeSessionStartResult RejectedStart(RuntimeCommandStatus status) =>
new(
status == RuntimeCommandStatus.StaleGeneration