fix(gameplay): reconcile wield ownership and target facing

Preserve PlayerDescription inventory/equipment ownership across authoritative manifest replacement, make weapon switching and combat/UI consumers read the same canonical object state, and carry the complete outbound player position frame across landblocks.

Route target-facing and mouse-look through the shared MovementManager and MotionInterpreter completion owner. Match retail input aggregation, toggle ordering, turn/sidestep remapping, per-axis hold keys, and synchronous movement publication without render-only heading state.

Initialize the live streaming origin from the first accepted canonical player Position, defer other projections until that origin exists, and retain logical entity identity through hydration.

Advance the project ledger from completed M2 to active M3, synchronize CLAUDE.md/AGENTS.md and durable memory, and record the next cast-lifecycle, spellbook/enchantment, and two-client portal gates.

Co-Authored-By: Codex <noreply@openai.com>
This commit is contained in:
Erik 2026-07-15 08:19:23 +02:00
parent b26f84cc69
commit 7b7ffcd278
36 changed files with 3884 additions and 761 deletions

View file

@ -27,6 +27,7 @@ public sealed class CombatAttackController : IDisposable
private readonly CombatState _combat;
private readonly Func<bool> _canStartAttack;
private readonly Action _prepareAttackRequest;
private readonly Func<AttackHeight, float, bool> _sendAttack;
private readonly Action _sendCancelAttack;
private readonly Func<bool> _isDualWield;
@ -50,6 +51,7 @@ public sealed class CombatAttackController : IDisposable
CombatState combat,
Func<bool> canStartAttack,
Func<AttackHeight, float, bool> sendAttack,
Action? prepareAttackRequest = null,
Action? sendCancelAttack = null,
Func<bool>? isDualWield = null,
Func<bool>? playerReadyForAttack = null,
@ -58,6 +60,7 @@ public sealed class CombatAttackController : IDisposable
{
_combat = combat ?? throw new ArgumentNullException(nameof(combat));
_canStartAttack = canStartAttack ?? throw new ArgumentNullException(nameof(canStartAttack));
_prepareAttackRequest = prepareAttackRequest ?? (() => { });
_sendAttack = sendAttack ?? throw new ArgumentNullException(nameof(sendAttack));
_sendCancelAttack = sendCancelAttack ?? (() => { });
_isDualWield = isDualWield ?? (() => false);
@ -73,6 +76,7 @@ public sealed class CombatAttackController : IDisposable
public AttackHeight RequestedHeight { get; private set; } = AttackHeight.Medium;
public float DesiredPower { get; private set; } = InitialDesiredPower;
public bool AttackRequestInProgress => _attackRequestInProgress;
public float RequestedAttackPower => _requestedAttackPower;
public bool BuildInProgress => _buildInProgress;
/// <summary>The level retail publishes to the embedded combat meter.</summary>
@ -257,8 +261,13 @@ public sealed class CombatAttackController : IDisposable
|| !_canStartAttack())
return;
// Retail StartAttackRequest (0x0056C040) stores request-in-progress
// and requested power first, then FinishJump and
// CommandInterpreter::MaybeStopCompletely. The host callback owns the
// player movement object and must run before any later attack send.
_attackRequestInProgress = true;
_requestedAttackPower = 1f;
_prepareAttackRequest();
_currentBuildIsAutomatic = false;
AttemptStartBuildingAttack();
}