fix(runtime): align portal and movement presentation
Port retail portal viewport projection and reveal behavior, preserve outbound combat style, drive remote and local grounded movement from authored CSequence root frames, and reuse the local prepared pose so animation hooks advance once. User-verified portal, observer movement, combat stance, and short-tap locomotion gates. Release build passed with 5,767 tests and five intentional skips. Co-authored-by: OpenAI Codex <codex@openai.com>
This commit is contained in:
parent
e95f55f25b
commit
124e046976
30 changed files with 1362 additions and 348 deletions
|
|
@ -0,0 +1,51 @@
|
|||
using System.Buffers.Binary;
|
||||
using AcDream.App.Input;
|
||||
using AcDream.App.Rendering;
|
||||
using AcDream.Core.Net.Messages;
|
||||
using AcDream.Core.Net.Packets;
|
||||
using AcDream.Core.Physics;
|
||||
|
||||
namespace AcDream.App.Tests.Input;
|
||||
|
||||
public sealed class LocalPlayerOutboundCombatStyleTests
|
||||
{
|
||||
[Theory]
|
||||
[InlineData(0x8000003Cu)] // HandCombat
|
||||
[InlineData(0x8000003Eu)] // Magic
|
||||
[InlineData(0x8000003Fu)] // BowCombat
|
||||
[InlineData(0x80000041u)] // CrossbowCombat
|
||||
public void CaptureAndBuild_PreservesCanonicalRawCombatStyle(uint combatStyle)
|
||||
{
|
||||
var controller = new PlayerMovementController(new PhysicsEngine());
|
||||
controller.Motion.RawState.CurrentStyle = combatStyle;
|
||||
|
||||
MovementResult movement = controller.CaptureMovementResult(
|
||||
mouseLookEvent: false);
|
||||
RawMotionState outbound = GameWindow.BuildOutboundRawMotionState(
|
||||
movement);
|
||||
|
||||
Assert.Equal(combatStyle, movement.CurrentStyle);
|
||||
Assert.Equal(combatStyle, outbound.CurrentStyle);
|
||||
|
||||
var writer = new PacketWriter(16);
|
||||
RawMotionStatePacker.Pack(writer, outbound);
|
||||
byte[] bytes = writer.ToArray();
|
||||
|
||||
uint flags = BinaryPrimitives.ReadUInt32LittleEndian(bytes);
|
||||
Assert.NotEqual(0u, flags & 0x2u);
|
||||
Assert.Equal(
|
||||
combatStyle,
|
||||
BinaryPrimitives.ReadUInt32LittleEndian(bytes.AsSpan(4)));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CaptureAndBuild_NonCombatRetainsRetailDefault()
|
||||
{
|
||||
var controller = new PlayerMovementController(new PhysicsEngine());
|
||||
|
||||
RawMotionState outbound = GameWindow.BuildOutboundRawMotionState(
|
||||
controller.CaptureMovementResult(mouseLookEvent: false));
|
||||
|
||||
Assert.Equal(RawMotionState.Default.CurrentStyle, outbound.CurrentStyle);
|
||||
}
|
||||
}
|
||||
|
|
@ -12,6 +12,17 @@ namespace AcDream.App.Tests.Rendering;
|
|||
|
||||
public sealed class PortalTunnelAssetTests
|
||||
{
|
||||
[Fact]
|
||||
public void PortalViewport_PreservesColorWhileClearingDepthLikeRetail()
|
||||
{
|
||||
Assert.Equal(
|
||||
Silk.NET.OpenGL.ClearBufferMask.DepthBufferBit,
|
||||
PortalTunnelPresentation.RetailViewportClearMask);
|
||||
Assert.False(
|
||||
(PortalTunnelPresentation.RetailViewportClearMask
|
||||
& Silk.NET.OpenGL.ClearBufferMask.ColorBufferBit) != 0);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InstalledDat_ResolvesRetailPortalSetupAndAnimation()
|
||||
{
|
||||
|
|
@ -261,7 +272,7 @@ public sealed class PortalTunnelAssetTests
|
|||
[InlineData(45f)]
|
||||
[InlineData(60f)]
|
||||
[InlineData(90f)]
|
||||
public void PortalTunnelCamera_UsesActiveSmartBoxFieldOfViewAndNearPlane(float degrees)
|
||||
public void PortalTunnelCamera_UsesActiveSmartBoxProjectionClipRange(float degrees)
|
||||
{
|
||||
float expected = degrees * (MathF.PI / 180f);
|
||||
var smartBoxProjection = System.Numerics.Matrix4x4.CreatePerspectiveFieldOfView(
|
||||
|
|
@ -275,6 +286,11 @@ public sealed class PortalTunnelAssetTests
|
|||
|
||||
Assert.Equal(expected, camera.FovRadians, precision: 5);
|
||||
Assert.Equal(0.35f, camera.Near, precision: 5);
|
||||
// Recovering a far plane from a float projection matrix loses a
|
||||
// small amount of precision because M33 is extremely close to -1.
|
||||
// The presentation must nevertheless inherit the active SmartBox
|
||||
// range rather than fall back to its former private 50 m clip.
|
||||
Assert.InRange(camera.Far, 4995f, 5005f);
|
||||
}
|
||||
|
||||
private static string? ResolveDatDir()
|
||||
|
|
|
|||
52
tests/AcDream.App.Tests/Rendering/SkyProjectionTests.cs
Normal file
52
tests/AcDream.App.Tests/Rendering/SkyProjectionTests.cs
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
using System.Numerics;
|
||||
using AcDream.App.Rendering.Sky;
|
||||
|
||||
namespace AcDream.App.Tests.Rendering;
|
||||
|
||||
public sealed class SkyProjectionTests
|
||||
{
|
||||
[Theory]
|
||||
[InlineData(60f)]
|
||||
[InlineData(120f)]
|
||||
[InlineData(179.885f)]
|
||||
public void WithDepthRange_PreservesActiveFieldOfView(float degrees)
|
||||
{
|
||||
Matrix4x4 active = Matrix4x4.CreatePerspectiveFieldOfView(
|
||||
degrees * MathF.PI / 180f,
|
||||
16f / 9f,
|
||||
0.1f,
|
||||
5_000f);
|
||||
|
||||
Matrix4x4 sky = SkyProjection.WithDepthRange(active, 0.1f, 1_000_000f);
|
||||
|
||||
Assert.Equal(active.M11, sky.M11);
|
||||
Assert.Equal(active.M22, sky.M22);
|
||||
Assert.Equal(active.M34, sky.M34);
|
||||
Assert.Equal(active.M44, sky.M44);
|
||||
Assert.Equal(1_000_000f / (0.1f - 1_000_000f), sky.M33, precision: 6);
|
||||
Assert.Equal(0.1f * 1_000_000f / (0.1f - 1_000_000f), sky.M43, precision: 6);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WithDepthRange_PreservesOffCenterProjectionTerms()
|
||||
{
|
||||
Matrix4x4 active = Matrix4x4.CreatePerspectiveOffCenter(
|
||||
-0.8f, 1.2f, -0.5f, 0.7f, 0.1f, 5_000f);
|
||||
|
||||
Matrix4x4 sky = SkyProjection.WithDepthRange(active, 1f, 10_000f);
|
||||
|
||||
Assert.Equal(active.M11, sky.M11);
|
||||
Assert.Equal(active.M22, sky.M22);
|
||||
Assert.Equal(active.M31, sky.M31);
|
||||
Assert.Equal(active.M32, sky.M32);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WithDepthRange_RejectsNonPerspectiveProjection()
|
||||
{
|
||||
Matrix4x4 orthographic = Matrix4x4.CreateOrthographic(10f, 10f, 0.1f, 100f);
|
||||
|
||||
Assert.Throws<ArgumentException>(() =>
|
||||
SkyProjection.WithDepthRange(orthographic, 0.1f, 1_000f));
|
||||
}
|
||||
}
|
||||
|
|
@ -176,4 +176,32 @@ public sealed class RetailUiAutomationProbeTests
|
|||
File.Delete(path);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ScriptRunner_command_routesThroughInjectedClientSubmitPath()
|
||||
{
|
||||
var (root, _, _, _, objects) = RootWithTwoItemLists();
|
||||
var probe = new RetailUiAutomationProbe(root, objects);
|
||||
var submitted = new List<string>();
|
||||
string path = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName() + ".ui-probe.txt");
|
||||
File.WriteAllText(path, "command /ls");
|
||||
|
||||
try
|
||||
{
|
||||
var runner = new RetailUiAutomationScriptRunner(
|
||||
probe,
|
||||
path,
|
||||
dumpOnStart: false,
|
||||
submitCommand: submitted.Add);
|
||||
|
||||
runner.Tick(0.016);
|
||||
|
||||
Assert.Equal(["/ls"], submitted);
|
||||
Assert.True(runner.Completed);
|
||||
}
|
||||
finally
|
||||
{
|
||||
File.Delete(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue