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

@ -1,5 +1,6 @@
using AcDream.App.UI;
using AcDream.Core.Chat;
using AcDream.Core.Items;
using AcDream.Core.Net.Messages;
using AcDream.Runtime.Session;
using AcDream.UI.Abstractions;
@ -15,8 +16,64 @@ internal sealed record LiveSessionCommandBindings(
Action<string, string> SendTell,
Action<uint, string> SendChannel,
Action<uint, uint, uint, uint, string, uint> SendTurbineChat,
Action<ShortcutEntry> AddShortcut,
Action<uint> RemoveShortcut,
Action<uint, int, int> AddFavorite,
Action<uint, int> RemoveFavorite,
Action<uint> SetSpellbookFilter,
Action<uint> ForgetSpell,
Action<uint, uint> SetDesiredComponent,
Action ClearDesiredComponents,
Action<uint, ulong> RaiseAttribute,
Action<uint, ulong> RaiseVital,
Action<uint, ulong> RaiseSkill,
Action<uint, uint> TrainSkill,
Action<uint> SetCharacterOptions,
Action<string> AddFriend,
Action<uint> RemoveFriend,
Action ClearFriends,
Action RequestLegacyFriends,
Action<bool, uint, string, uint> ModifyCharacterSquelch,
Action<bool, string> ModifyAccountSquelch,
Action<bool, uint> ModifyGlobalSquelch,
Action<string>? Log = null);
internal readonly record struct AddShortcutRuntimeCmd(ShortcutEntry Entry);
internal readonly record struct RemoveShortcutRuntimeCmd(uint Index);
internal readonly record struct AddFavoriteRuntimeCmd(
uint SpellId,
int Position,
int TabIndex);
internal readonly record struct RemoveFavoriteRuntimeCmd(
uint SpellId,
int TabIndex);
internal readonly record struct SetSpellbookFilterRuntimeCmd(uint Filters);
internal readonly record struct ForgetSpellRuntimeCmd(uint SpellId);
internal readonly record struct SetDesiredComponentRuntimeCmd(
uint ComponentId,
uint Amount);
internal readonly record struct ClearDesiredComponentsRuntimeCmd;
internal readonly record struct RaiseAttributeRuntimeCmd(uint StatId, ulong Cost);
internal readonly record struct RaiseVitalRuntimeCmd(uint StatId, ulong Cost);
internal readonly record struct RaiseSkillRuntimeCmd(uint StatId, ulong Cost);
internal readonly record struct TrainSkillRuntimeCmd(uint StatId, uint Cost);
internal readonly record struct SetCharacterOptionsRuntimeCmd(uint Options);
internal readonly record struct AddFriendRuntimeCmd(string Name);
internal readonly record struct RemoveFriendRuntimeCmd(uint CharacterId);
internal readonly record struct ClearFriendsRuntimeCmd;
internal readonly record struct RequestLegacyFriendsRuntimeCmd;
internal readonly record struct ModifyCharacterSquelchRuntimeCmd(
bool Add,
uint CharacterId,
string Name,
uint MessageType);
internal readonly record struct ModifyAccountSquelchRuntimeCmd(
bool Add,
string Name);
internal readonly record struct ModifyGlobalSquelchRuntimeCmd(
bool Add,
uint MessageType);
/// <summary>
/// Owns the command surface for one exact live-session generation. The router
/// itself is the published bus, so a retained reference becomes inert before
@ -52,6 +109,68 @@ internal sealed class LiveSessionCommandRouter : ILiveSessionCommandRouting
SendIfActive(() => bindings.SendTalk(command.Text));
});
commands.Register<SendChatCmd>(command => RouteChat(bindings, command));
commands.Register<AddShortcutRuntimeCmd>(
command => SendIfActive(() => bindings.AddShortcut(command.Entry)));
commands.Register<RemoveShortcutRuntimeCmd>(
command => SendIfActive(() => bindings.RemoveShortcut(command.Index)));
commands.Register<AddFavoriteRuntimeCmd>(
command => SendIfActive(() => bindings.AddFavorite(
command.SpellId,
command.Position,
command.TabIndex)));
commands.Register<RemoveFavoriteRuntimeCmd>(
command => SendIfActive(() => bindings.RemoveFavorite(
command.SpellId,
command.TabIndex)));
commands.Register<SetSpellbookFilterRuntimeCmd>(
command => SendIfActive(() =>
bindings.SetSpellbookFilter(command.Filters)));
commands.Register<ForgetSpellRuntimeCmd>(
command => SendIfActive(() => bindings.ForgetSpell(command.SpellId)));
commands.Register<SetDesiredComponentRuntimeCmd>(
command => SendIfActive(() => bindings.SetDesiredComponent(
command.ComponentId,
command.Amount)));
commands.Register<ClearDesiredComponentsRuntimeCmd>(
_ => SendIfActive(bindings.ClearDesiredComponents));
commands.Register<RaiseAttributeRuntimeCmd>(
command => SendIfActive(() =>
bindings.RaiseAttribute(command.StatId, command.Cost)));
commands.Register<RaiseVitalRuntimeCmd>(
command => SendIfActive(() =>
bindings.RaiseVital(command.StatId, command.Cost)));
commands.Register<RaiseSkillRuntimeCmd>(
command => SendIfActive(() =>
bindings.RaiseSkill(command.StatId, command.Cost)));
commands.Register<TrainSkillRuntimeCmd>(
command => SendIfActive(() =>
bindings.TrainSkill(command.StatId, command.Cost)));
commands.Register<SetCharacterOptionsRuntimeCmd>(
command => SendIfActive(() =>
bindings.SetCharacterOptions(command.Options)));
commands.Register<AddFriendRuntimeCmd>(
command => SendIfActive(() => bindings.AddFriend(command.Name)));
commands.Register<RemoveFriendRuntimeCmd>(
command => SendIfActive(() =>
bindings.RemoveFriend(command.CharacterId)));
commands.Register<ClearFriendsRuntimeCmd>(
_ => SendIfActive(bindings.ClearFriends));
commands.Register<RequestLegacyFriendsRuntimeCmd>(
_ => SendIfActive(bindings.RequestLegacyFriends));
commands.Register<ModifyCharacterSquelchRuntimeCmd>(
command => SendIfActive(() => bindings.ModifyCharacterSquelch(
command.Add,
command.CharacterId,
command.Name,
command.MessageType)));
commands.Register<ModifyAccountSquelchRuntimeCmd>(
command => SendIfActive(() => bindings.ModifyAccountSquelch(
command.Add,
command.Name)));
commands.Register<ModifyGlobalSquelchRuntimeCmd>(
command => SendIfActive(() => bindings.ModifyGlobalSquelch(
command.Add,
command.MessageType)));
_commands = commands;
}

View file

@ -36,9 +36,7 @@ namespace AcDream.App.Net;
internal sealed record LiveSessionPlayerRuntime(
LocalPlayerIdentityState Identity,
LocalPlayerControllerSlot Controller,
LocalPlayerSkillState Skills,
LiveWorldOriginState WorldOrigin,
PlayerCharacterOptionsState CharacterOptions);
LiveWorldOriginState WorldOrigin);
internal sealed record LiveSessionDomainRuntime(
RuntimeEntityObjectLifetime EntityObjects,
@ -215,8 +213,8 @@ internal sealed class LiveSessionRuntimeFactory
_domain.Communication.ResetChatIdentity();
EntityVanishProbe.PlayerGuid = 0u;
_interaction.Settings.ResetActiveCharacterKey();
_player.CharacterOptions.Reset();
_player.Skills.ResetSession();
_domain.Character.Options.ResetSession();
_domain.Character.MovementSkills.ResetSession();
_world.NetworkUpdates.ResetSessionState();
_world.Hydration.ResetSessionState();
_domain.Inventory.ResetPlayerSnapshots();
@ -282,27 +280,22 @@ internal sealed class LiveSessionRuntimeFactory
ResolveSkillFormulaBonus: skillCreditResolver.Resolve,
OnSkillsUpdated: (runSkill, jumpSkill) =>
{
_player.Skills.Update(
runSkill,
jumpSkill,
_player.Controller.Controller);
if (_player.Skills.IsComplete
&& _player.Controller.Controller is not null)
if (LocalPlayerSkillProjection.ApplyTo(
_domain.Character.MovementSkills,
_player.Controller.Controller))
{
RuntimeMovementSkillSnapshot snapshot =
_domain.Character.MovementSkills.Snapshot;
_log(
$"player: applied server skills " +
$"run={_player.Skills.RunSkill} " +
$"jump={_player.Skills.JumpSkill}");
$"run={snapshot.RunSkill} " +
$"jump={snapshot.JumpSkill}");
}
},
OnConfirmationRequest: request =>
_ui.RetailUi?.HandleConfirmationRequest(request),
OnConfirmationDone: done =>
_ui.RetailUi?.HandleConfirmationDone(done),
OnCharacterOptions: (options1, _) =>
_player.CharacterOptions.Options =
(Core.Net.Messages.PlayerDescriptionParser.CharacterOptions1)
options1,
ClientTime: ClientTimerNow);
}
@ -391,6 +384,26 @@ internal sealed class LiveSessionRuntimeFactory
senderGuid,
text,
cookie),
AddShortcut: session.SendAddShortcut,
RemoveShortcut: session.SendRemoveShortcut,
AddFavorite: session.SendAddSpellFavorite,
RemoveFavorite: session.SendRemoveSpellFavorite,
SetSpellbookFilter: session.SendSpellbookFilter,
ForgetSpell: session.SendRemoveSpell,
SetDesiredComponent: session.SendSetDesiredComponentLevel,
ClearDesiredComponents: session.SendClearDesiredComponents,
RaiseAttribute: session.SendRaiseAttribute,
RaiseVital: session.SendRaiseVital,
RaiseSkill: session.SendRaiseSkill,
TrainSkill: session.SendTrainSkill,
SetCharacterOptions: session.SendSetCharacterOptions,
AddFriend: session.SendAddFriend,
RemoveFriend: session.SendRemoveFriend,
ClearFriends: session.SendClearFriends,
RequestLegacyFriends: session.SendLegacyFriendsListRequest,
ModifyCharacterSquelch: session.SendModifyCharacterSquelch,
ModifyAccountSquelch: session.SendModifyAccountSquelch,
ModifyGlobalSquelch: session.SendModifyGlobalSquelch,
Log: _log);
private static double ClientTimerNow() =>