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:
parent
b26f84cc69
commit
7b7ffcd278
36 changed files with 3884 additions and 761 deletions
|
|
@ -12,7 +12,7 @@ namespace AcDream.UI.Abstractions.Tests.Input;
|
|||
/// </summary>
|
||||
public sealed class MmbMouseLookTests
|
||||
{
|
||||
private sealed class YawSink
|
||||
private sealed class AdjustmentSink
|
||||
{
|
||||
public float Total;
|
||||
public int ApplyCount;
|
||||
|
|
@ -22,7 +22,7 @@ public sealed class MmbMouseLookTests
|
|||
[Fact]
|
||||
public void Press_ActivatesAndCapturesCursorPosition()
|
||||
{
|
||||
var sink = new YawSink();
|
||||
var sink = new AdjustmentSink();
|
||||
var ml = new MouseLookState(sink.Apply);
|
||||
|
||||
ml.Press(cursorX: 320f, cursorY: 240f, wantCaptureMouse: false);
|
||||
|
|
@ -35,7 +35,7 @@ public sealed class MmbMouseLookTests
|
|||
[Fact]
|
||||
public void Release_DeactivatesWhenActive()
|
||||
{
|
||||
var sink = new YawSink();
|
||||
var sink = new AdjustmentSink();
|
||||
var ml = new MouseLookState(sink.Apply);
|
||||
ml.Press(0f, 0f, wantCaptureMouse: false);
|
||||
|
||||
|
|
@ -50,7 +50,7 @@ public sealed class MmbMouseLookTests
|
|||
// Defense in depth — the dispatcher already filters on
|
||||
// WantCaptureMouse, but if a binding ever fires through the
|
||||
// state machine itself must not turn on.
|
||||
var sink = new YawSink();
|
||||
var sink = new AdjustmentSink();
|
||||
var ml = new MouseLookState(sink.Apply);
|
||||
|
||||
ml.Press(0f, 0f, wantCaptureMouse: true);
|
||||
|
|
@ -61,7 +61,7 @@ public sealed class MmbMouseLookTests
|
|||
[Fact]
|
||||
public void OnWantCaptureMouseChanged_TrueWhileActive_Deactivates()
|
||||
{
|
||||
var sink = new YawSink();
|
||||
var sink = new AdjustmentSink();
|
||||
var ml = new MouseLookState(sink.Apply);
|
||||
ml.Press(0f, 0f, wantCaptureMouse: false);
|
||||
Assert.True(ml.Active);
|
||||
|
|
@ -79,7 +79,7 @@ public sealed class MmbMouseLookTests
|
|||
{
|
||||
// Going from "ImGui captures" to "ImGui doesn't capture" must
|
||||
// NOT auto-reactivate — only an explicit Press does.
|
||||
var sink = new YawSink();
|
||||
var sink = new AdjustmentSink();
|
||||
var ml = new MouseLookState(sink.Apply);
|
||||
|
||||
ml.OnWantCaptureMouseChanged(wantCaptureMouse: false);
|
||||
|
|
@ -88,17 +88,17 @@ public sealed class MmbMouseLookTests
|
|||
}
|
||||
|
||||
[Fact]
|
||||
public void ApplyDelta_WhileActive_DrivesYawSink()
|
||||
public void ApplyDelta_WhileActive_DrivesMovementAdjustmentSink()
|
||||
{
|
||||
var sink = new YawSink();
|
||||
var ml = new MouseLookState(sink.Apply) { SensitivityRadiansPerPixel = 0.01f };
|
||||
var sink = new AdjustmentSink();
|
||||
var ml = new MouseLookState(sink.Apply) { SensitivityPerPixel = 0.01f };
|
||||
ml.Press(0f, 0f, wantCaptureMouse: false);
|
||||
|
||||
ml.ApplyDelta(dx: 10f, extraSensitivity: 1.0f);
|
||||
for (int i = 0; i < 6; i++)
|
||||
ml.ApplyDelta(dx: 10f, extraSensitivity: 1.0f);
|
||||
|
||||
// Sign: dragging right (positive dx) yaws the character right
|
||||
// — by the acdream Yaw convention that's negative yaw delta.
|
||||
// Magnitude: 10 * 0.01 * 1.0 = 0.1 rad.
|
||||
// Sign: dragging right (positive dx) requests TurnRight, represented
|
||||
// by a negative adjustment. Magnitude: 10 * 0.01 * 1.0 = 0.1.
|
||||
Assert.Equal(1, sink.ApplyCount);
|
||||
Assert.Equal(-0.1f, sink.Total, 5);
|
||||
}
|
||||
|
|
@ -106,7 +106,7 @@ public sealed class MmbMouseLookTests
|
|||
[Fact]
|
||||
public void ApplyDelta_WhileInactive_DoesNothing()
|
||||
{
|
||||
var sink = new YawSink();
|
||||
var sink = new AdjustmentSink();
|
||||
var ml = new MouseLookState(sink.Apply);
|
||||
// Never pressed.
|
||||
|
||||
|
|
@ -119,7 +119,7 @@ public sealed class MmbMouseLookTests
|
|||
[Fact]
|
||||
public void ApplyDelta_AfterRelease_DoesNothing()
|
||||
{
|
||||
var sink = new YawSink();
|
||||
var sink = new AdjustmentSink();
|
||||
var ml = new MouseLookState(sink.Apply);
|
||||
ml.Press(0f, 0f, wantCaptureMouse: false);
|
||||
ml.Release();
|
||||
|
|
@ -135,7 +135,7 @@ public sealed class MmbMouseLookTests
|
|||
// Repeated Press calls must not blow away the originally
|
||||
// captured cursor position — the cursor-restore-on-release
|
||||
// path needs the original anchor.
|
||||
var sink = new YawSink();
|
||||
var sink = new AdjustmentSink();
|
||||
var ml = new MouseLookState(sink.Apply);
|
||||
ml.Press(100f, 200f, wantCaptureMouse: false);
|
||||
|
||||
|
|
@ -148,19 +148,82 @@ public sealed class MmbMouseLookTests
|
|||
[Fact]
|
||||
public void ApplyDelta_DriverDrivesCharacterAndCameraInOneSink()
|
||||
{
|
||||
// Combined drive: the test only exposes a single yaw mutator,
|
||||
// and that's the design — the yaw mutator GameWindow passes
|
||||
// updates _playerController.Yaw, and the chase camera reads
|
||||
// the same yaw via ChaseCamera.Update(pos, playerYaw). So a
|
||||
// single sink in this test models both behaviors.
|
||||
var sink = new YawSink();
|
||||
var ml = new MouseLookState(sink.Apply) { SensitivityRadiansPerPixel = 0.005f };
|
||||
// The input state owns only filtering/scaling. GameWindow's one sink
|
||||
// routes the result to PlayerMovementController, whose turn motion
|
||||
// updates both the character heading and chase-camera follow source.
|
||||
var sink = new AdjustmentSink();
|
||||
var ml = new MouseLookState(sink.Apply) { SensitivityPerPixel = 0.005f };
|
||||
ml.Press(0f, 0f, wantCaptureMouse: false);
|
||||
|
||||
for (int i = 0; i < 5; i++)
|
||||
ml.ApplyDelta(dx: 4f, extraSensitivity: 0.5f);
|
||||
ml.ApplyDelta(dx: 4f, extraSensitivity: 0.5f); // -0.01
|
||||
ml.ApplyDelta(dx: -2f, extraSensitivity: 0.5f); // +0.005
|
||||
|
||||
Assert.Equal(2, sink.ApplyCount);
|
||||
Assert.Equal(-0.005f, sink.Total, 5);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ApplyDelta_ZeroSampleResetsRetailHorizontalExtent()
|
||||
{
|
||||
var sink = new AdjustmentSink();
|
||||
var ml = new MouseLookState(sink.Apply) { SensitivityPerPixel = 0.01f };
|
||||
ml.Press(0f, 0f, wantCaptureMouse: false);
|
||||
|
||||
for (int i = 0; i < 5; i++)
|
||||
ml.ApplyDelta(dx: 2f, extraSensitivity: 1f);
|
||||
ml.ApplyDelta(dx: 0f, extraSensitivity: 1f);
|
||||
for (int i = 0; i < 5; i++)
|
||||
ml.ApplyDelta(dx: 2f, extraSensitivity: 1f);
|
||||
|
||||
Assert.Equal(0, sink.ApplyCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TryTakeRawSample_IdleZeroUsesStrictRetailDelayAndResetsExtent()
|
||||
{
|
||||
var sink = new AdjustmentSink();
|
||||
var ml = new MouseLookState(sink.Apply) { SensitivityPerPixel = 0.01f };
|
||||
ml.Press(0f, 0f, wantCaptureMouse: false, nowSeconds: 1f);
|
||||
|
||||
for (int i = 0; i < 5; i++)
|
||||
{
|
||||
ml.QueueDelta(2f);
|
||||
float now = 1.01f + (i * 0.01f);
|
||||
Assert.True(ml.TryTakeRawSample(now, out float dx, out float dy));
|
||||
Assert.Equal(0f, dy);
|
||||
ml.ApplyDelta(dx, extraSensitivity: 1f);
|
||||
}
|
||||
|
||||
// Retail uses a strict > 0.2-second boundary; eventless render frames
|
||||
// before then do not synthesize input at all.
|
||||
Assert.False(ml.TryTakeRawSample(1.25f, out _, out _));
|
||||
Assert.True(ml.TryTakeRawSample(1.2501f, out float idleDx, out float idleDy));
|
||||
Assert.Equal(0f, idleDx);
|
||||
Assert.Equal(0f, idleDy);
|
||||
ml.ApplyDelta(idleDx, extraSensitivity: 1f);
|
||||
|
||||
ml.QueueDelta(2f);
|
||||
Assert.True(ml.TryTakeRawSample(1.26f, out float resumedDx, out _));
|
||||
ml.ApplyDelta(resumedDx, extraSensitivity: 1f);
|
||||
|
||||
Assert.Equal(0, sink.ApplyCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TryTakeRawSample_AccumulatesNativeEventsIntoOneFrameSample()
|
||||
{
|
||||
var sink = new AdjustmentSink();
|
||||
var ml = new MouseLookState(sink.Apply);
|
||||
ml.Press(0f, 0f, wantCaptureMouse: false, nowSeconds: 2f);
|
||||
|
||||
ml.QueueDelta(2f, 1f);
|
||||
ml.QueueDelta(3f, -0.25f);
|
||||
|
||||
Assert.True(ml.TryTakeRawSample(2.01f, out float dx, out float dy));
|
||||
Assert.Equal(5f, dx);
|
||||
Assert.Equal(0.75f, dy);
|
||||
Assert.False(ml.TryTakeRawSample(2.02f, out _, out _));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue