fix(world): remove non-retail portal exit fade
This commit is contained in:
parent
842bd89c16
commit
dded9e6b17
21 changed files with 919 additions and 347 deletions
|
|
@ -53,6 +53,21 @@ public sealed class PortalTunnelAssetTests
|
|||
Assert.Equal(DatReaderWriter.Enums.AnimationHookType.SoundTweaked, hook.HookType);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RequiredAssets_MissingSetupOrAnimationFailsWithActionableIds()
|
||||
{
|
||||
var error = Assert.Throws<InvalidOperationException>(() =>
|
||||
PortalTunnelPresentation.EnsureRequiredAssets(
|
||||
setupDid: 0u,
|
||||
setupLoaded: false,
|
||||
animationDid: 0x030005ACu,
|
||||
animationLoaded: true));
|
||||
|
||||
Assert.Contains("required retail DAT assets unavailable", error.Message);
|
||||
Assert.Contains("setup=0x00000000 (missing)", error.Message);
|
||||
Assert.Contains("animation=0x030005AC (ok)", error.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PortalTunnelCamera_RollsAroundForwardAxisWithoutLeavingPassage()
|
||||
{
|
||||
|
|
@ -96,7 +111,7 @@ public sealed class PortalTunnelAssetTests
|
|||
|
||||
controller.Update(new TeleportAnimSnapshot(
|
||||
TeleportAnimState.TunnelFadeOut,
|
||||
FadeAlpha: 0.5f,
|
||||
ViewPlaneBlend: 0.5f,
|
||||
ShowTunnel: true,
|
||||
ShowPleaseWait: false));
|
||||
System.Numerics.Matrix4x4 projection = controller.Apply(baseProjection);
|
||||
|
|
@ -126,7 +141,7 @@ public sealed class PortalTunnelAssetTests
|
|||
|
||||
controller.Update(new TeleportAnimSnapshot(
|
||||
TeleportAnimState.WorldFadeIn,
|
||||
FadeAlpha: 1f,
|
||||
ViewPlaneBlend: 1f,
|
||||
ShowTunnel: false,
|
||||
ShowPleaseWait: false));
|
||||
System.Numerics.Matrix4x4 wideProjection = controller.Apply(baseProjection);
|
||||
|
|
@ -139,7 +154,7 @@ public sealed class PortalTunnelAssetTests
|
|||
|
||||
controller.Update(new TeleportAnimSnapshot(
|
||||
TeleportAnimState.Off,
|
||||
FadeAlpha: 0f,
|
||||
ViewPlaneBlend: 0f,
|
||||
ShowTunnel: false,
|
||||
ShowPleaseWait: false));
|
||||
|
||||
|
|
@ -147,6 +162,101 @@ public sealed class PortalTunnelAssetTests
|
|||
Assert.Equal(baseProjection, controller.Apply(baseProjection));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TeleportViewPlane_ApplyToSuppliesOverrideToEveryCameraConsumer()
|
||||
{
|
||||
var source = new PortalTunnelCamera
|
||||
{
|
||||
Aspect = 16f / 9f,
|
||||
FovRadians = MathF.PI / 3f,
|
||||
Near = 0.1f,
|
||||
Far = 5000f,
|
||||
};
|
||||
var controller = new TeleportViewPlaneController();
|
||||
controller.Begin(source.Projection);
|
||||
controller.Update(new TeleportAnimSnapshot(
|
||||
TeleportAnimState.WorldFadeIn,
|
||||
ViewPlaneBlend: 1f,
|
||||
ShowTunnel: false,
|
||||
ShowPleaseWait: false));
|
||||
|
||||
ICamera renderCamera = controller.ApplyTo(source);
|
||||
|
||||
Assert.Equal(source.View, renderCamera.View);
|
||||
Assert.Equal(
|
||||
TeleportViewPlaneController.TransitionViewPlaneDistance,
|
||||
renderCamera.Projection.M22,
|
||||
precision: 5);
|
||||
renderCamera.Aspect = 4f / 3f;
|
||||
Assert.Equal(4f / 3f, source.Aspect, precision: 5);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TeleportViewPlane_TunnelPreservesLogoutOverrideUntilTunnelContinue()
|
||||
{
|
||||
var capturedProjection = System.Numerics.Matrix4x4.CreatePerspectiveFieldOfView(
|
||||
MathF.PI / 3f,
|
||||
16f / 9f,
|
||||
0.1f,
|
||||
5000f);
|
||||
var changedProjection = System.Numerics.Matrix4x4.CreatePerspectiveFieldOfView(
|
||||
MathF.PI / 2f,
|
||||
16f / 9f,
|
||||
0.1f,
|
||||
5000f);
|
||||
var controller = new TeleportViewPlaneController();
|
||||
controller.Begin(capturedProjection);
|
||||
|
||||
controller.Update(new TeleportAnimSnapshot(
|
||||
TeleportAnimState.WorldFadeOut,
|
||||
ViewPlaneBlend: 1f,
|
||||
ShowTunnel: false,
|
||||
ShowPleaseWait: false));
|
||||
controller.Update(new TeleportAnimSnapshot(
|
||||
TeleportAnimState.TunnelFadeIn,
|
||||
ViewPlaneBlend: 0f,
|
||||
ShowTunnel: true,
|
||||
ShowPleaseWait: false));
|
||||
controller.Update(new TeleportAnimSnapshot(
|
||||
TeleportAnimState.Tunnel,
|
||||
ViewPlaneBlend: 0f,
|
||||
ShowTunnel: true,
|
||||
ShowPleaseWait: false));
|
||||
|
||||
Assert.True(controller.Enabled);
|
||||
Assert.Equal(capturedProjection.M22, controller.Apply(changedProjection).M22, precision: 5);
|
||||
|
||||
controller.Update(new TeleportAnimSnapshot(
|
||||
TeleportAnimState.TunnelContinue,
|
||||
ViewPlaneBlend: 0f,
|
||||
ShowTunnel: true,
|
||||
ShowPleaseWait: false));
|
||||
|
||||
Assert.False(controller.Enabled);
|
||||
Assert.Equal(changedProjection, controller.Apply(changedProjection));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TeleportViewPlane_NormalPortalTunnelDoesNotEnableOverride()
|
||||
{
|
||||
var baseProjection = System.Numerics.Matrix4x4.CreatePerspectiveFieldOfView(
|
||||
MathF.PI / 3f,
|
||||
16f / 9f,
|
||||
0.1f,
|
||||
5000f);
|
||||
var controller = new TeleportViewPlaneController();
|
||||
controller.Begin(baseProjection);
|
||||
|
||||
controller.Update(new TeleportAnimSnapshot(
|
||||
TeleportAnimState.Tunnel,
|
||||
ViewPlaneBlend: 0f,
|
||||
ShowTunnel: true,
|
||||
ShowPleaseWait: false));
|
||||
|
||||
Assert.False(controller.Enabled);
|
||||
Assert.Equal(baseProjection, controller.Apply(baseProjection));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(45f)]
|
||||
[InlineData(60f)]
|
||||
|
|
|
|||
|
|
@ -60,4 +60,18 @@ public sealed class TeleportLandblockTransitionTests
|
|||
Assert.False(transition.CrossesLandblock);
|
||||
Assert.Equal(0x8C04FFFFu, transition.SourceLandblockId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ReplacementBackToPlayerLandblock_StillRecentersAbandonedStreamingOrigin()
|
||||
{
|
||||
var transition = TeleportLandblockTransition.Classify(
|
||||
sourceCellId: 0x8C0401ADu,
|
||||
destinationCellId: 0x8C040145u,
|
||||
currentStreamingCenterLandblockId: 0xA9B4FFFFu);
|
||||
|
||||
Assert.False(transition.CrossesLandblock);
|
||||
Assert.True(transition.ChangesStreamingCenter);
|
||||
Assert.Equal(0x8C04FFFFu, transition.DestinationLandblockId);
|
||||
Assert.Equal(0xA9B4FFFFu, transition.StreamingCenterLandblockId);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,133 @@
|
|||
using AcDream.App.Streaming;
|
||||
|
||||
namespace AcDream.App.Tests.Streaming;
|
||||
|
||||
public sealed class TeleportTransitCoordinatorTests
|
||||
{
|
||||
[Fact]
|
||||
public void ReplacementRejectsDelayedPriorDestinationAndAcceptsCurrentAdvance()
|
||||
{
|
||||
var transit = new TeleportTransitCoordinator<int>();
|
||||
Assert.True(transit.QueueStart(10));
|
||||
Assert.False(transit.Activate(out _));
|
||||
transit.EndActive();
|
||||
Assert.True(transit.QueueStart(11));
|
||||
Assert.False(transit.Activate(out _));
|
||||
|
||||
Assert.False(transit.OfferDestination(10, true, 100, out _));
|
||||
Assert.True(transit.OfferDestination(11, true, 110, out int accepted));
|
||||
Assert.Equal(110, accepted);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PositionBeforeF751IsBufferedAndEqualFollowupCannotReplaceIt()
|
||||
{
|
||||
var transit = new TeleportTransitCoordinator<int>();
|
||||
|
||||
Assert.False(transit.OfferDestination(11, true, 110, out _));
|
||||
Assert.True(transit.QueueStart(11));
|
||||
Assert.True(transit.Activate(out int buffered));
|
||||
Assert.Equal(110, buffered);
|
||||
Assert.False(transit.OfferDestination(11, false, 111, out _));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InactiveOrdinaryPositionIsNeverBuffered()
|
||||
{
|
||||
var transit = new TeleportTransitCoordinator<int>();
|
||||
|
||||
Assert.False(transit.OfferDestination(11, false, 110, out _));
|
||||
Assert.True(transit.QueueStart(11));
|
||||
Assert.False(transit.Activate(out _));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DuplicateStartIsRejectedButSequenceWrapIsFresh()
|
||||
{
|
||||
var transit = new TeleportTransitCoordinator<int>();
|
||||
Assert.True(transit.QueueStart(ushort.MaxValue));
|
||||
Assert.False(transit.Activate(out _));
|
||||
transit.EndActive();
|
||||
|
||||
Assert.False(transit.CanBegin(ushort.MaxValue));
|
||||
Assert.False(transit.QueueStart(ushort.MaxValue));
|
||||
Assert.True(transit.CanBegin(0));
|
||||
Assert.True(transit.QueueStart(0));
|
||||
Assert.True(transit.HasPendingStart);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ExactHalfRangeUsesRetailLowerStampTieBreak()
|
||||
{
|
||||
var transit = new TeleportTransitCoordinator<int>();
|
||||
Assert.True(transit.QueueStart(0x8000));
|
||||
Assert.False(transit.Activate(out _));
|
||||
transit.EndActive();
|
||||
|
||||
Assert.True(transit.CanBegin(0x0000));
|
||||
Assert.True(transit.QueueStart(0x0000));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ClearSessionAllowsSameSequenceInNewSession()
|
||||
{
|
||||
var transit = new TeleportTransitCoordinator<int>();
|
||||
Assert.True(transit.QueueStart(7));
|
||||
Assert.False(transit.Activate(out _));
|
||||
|
||||
transit.ClearSession();
|
||||
|
||||
Assert.False(transit.IsActive);
|
||||
Assert.True(transit.CanBegin(7));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PendingStartAcceptsEqualPositionUntilPresentationCanActivate()
|
||||
{
|
||||
var transit = new TeleportTransitCoordinator<int>();
|
||||
Assert.True(transit.QueueStart(11));
|
||||
|
||||
Assert.False(transit.OfferDestination(11, false, 110, out _));
|
||||
Assert.True(transit.Activate(out int buffered));
|
||||
|
||||
Assert.Equal(110, buffered);
|
||||
Assert.True(transit.IsActive);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AdvancingPositionPrunesSkippedDestination()
|
||||
{
|
||||
var transit = new TeleportTransitCoordinator<int>();
|
||||
|
||||
Assert.False(transit.OfferDestination(11, true, 110, out _));
|
||||
Assert.False(transit.OfferDestination(12, true, 120, out _));
|
||||
Assert.True(transit.QueueStart(12));
|
||||
Assert.True(transit.Activate(out int buffered));
|
||||
|
||||
Assert.Equal(120, buffered);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ObsoleteDestinationCannotSurviveSequenceWrap()
|
||||
{
|
||||
var transit = new TeleportTransitCoordinator<int>();
|
||||
|
||||
Assert.False(transit.OfferDestination(ushort.MaxValue, true, 999, out _));
|
||||
Assert.False(transit.OfferDestination(0, true, 0, out _));
|
||||
Assert.True(transit.QueueStart(0));
|
||||
Assert.True(transit.Activate(out int wrapped));
|
||||
Assert.Equal(0, wrapped);
|
||||
transit.EndActive();
|
||||
|
||||
// Advance in legal retail-newer hops until 0xFFFF is current again.
|
||||
Assert.True(transit.QueueStart(0x7FFF));
|
||||
Assert.False(transit.Activate(out _));
|
||||
transit.EndActive();
|
||||
Assert.True(transit.QueueStart(0xFFFE));
|
||||
Assert.False(transit.Activate(out _));
|
||||
transit.EndActive();
|
||||
Assert.True(transit.QueueStart(ushort.MaxValue));
|
||||
|
||||
Assert.False(transit.Activate(out _));
|
||||
}
|
||||
}
|
||||
|
|
@ -31,16 +31,32 @@ public sealed class TeleportAnimSequencerTests
|
|||
}
|
||||
|
||||
[Fact]
|
||||
public void Snapshot_DefaultsAreOff_ClearAlpha()
|
||||
public void Snapshot_DefaultsAreOff_WithGameViewPlane()
|
||||
{
|
||||
var snap = new TeleportAnimSnapshot(
|
||||
TeleportAnimState.Off, FadeAlpha: 0f, ShowTunnel: false, ShowPleaseWait: false);
|
||||
TeleportAnimState.Off, ViewPlaneBlend: 0f, ShowTunnel: false, ShowPleaseWait: false);
|
||||
Assert.Equal(TeleportAnimState.Off, snap.State);
|
||||
Assert.Equal(0f, snap.FadeAlpha);
|
||||
Assert.Equal(0f, snap.ViewPlaneBlend);
|
||||
Assert.False(snap.ShowTunnel);
|
||||
Assert.False(snap.ShowPleaseWait);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Reset_CancelsTransitionAndDiscardsPendingEdges()
|
||||
{
|
||||
var seq = new TeleportAnimSequencer();
|
||||
seq.Begin(TeleportEntryKind.Portal);
|
||||
|
||||
seq.Reset();
|
||||
var (snap, events) = seq.Tick(0f, worldReady: true);
|
||||
|
||||
Assert.False(seq.IsActive);
|
||||
Assert.Equal(TeleportAnimState.Off, snap.State);
|
||||
Assert.Equal(0f, snap.ViewPlaneBlend);
|
||||
Assert.False(snap.ShowTunnel);
|
||||
Assert.Empty(events);
|
||||
}
|
||||
|
||||
// --- Task 1.2: Begin(), IsActive, initial state ---
|
||||
|
||||
[Theory]
|
||||
|
|
@ -285,20 +301,20 @@ public sealed class TeleportAnimSequencerTests
|
|||
Assert.Contains(TeleportAnimEvent.FireLoginComplete, evts);
|
||||
}
|
||||
|
||||
// --- Task 1.4: FadeAlpha endpoints and monotonicity ---
|
||||
// --- Retail view-plane blend endpoints and monotonicity ---
|
||||
|
||||
[Fact]
|
||||
public void FadeAlpha_IsZeroAtStart_OfWorldFadeOut()
|
||||
public void ViewPlaneBlend_IsZeroAtStart_OfWorldFadeOut()
|
||||
{
|
||||
var seq = new TeleportAnimSequencer();
|
||||
seq.Begin(TeleportEntryKind.Logout);
|
||||
var (snap, _) = seq.Tick(dt: 0f, worldReady: false);
|
||||
// At elapsed=0 in WorldFadeOut: retail animation level 0 => fully visible.
|
||||
Assert.Equal(0f, snap.FadeAlpha, precision: 4);
|
||||
// At elapsed=0 the override still equals the ordinary game distance.
|
||||
Assert.Equal(0f, snap.ViewPlaneBlend, precision: 4);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FadeAlpha_IsOneAtEnd_OfWorldFadeOut()
|
||||
public void ViewPlaneBlend_IsOneAtEnd_OfWorldFadeOut()
|
||||
{
|
||||
var seq = new TeleportAnimSequencer();
|
||||
seq.Begin(TeleportEntryKind.Logout);
|
||||
|
|
@ -309,12 +325,14 @@ public sealed class TeleportAnimSequencerTests
|
|||
Assert.Equal(TeleportAnimState.WorldFadeOut, seq.State);
|
||||
|
||||
var (snap, _) = seq.Tick(0f, worldReady: false);
|
||||
// The retail animation table should be close to fully opaque here.
|
||||
Assert.True(snap.FadeAlpha > 0.95f, $"Expected FadeAlpha near 1, got {snap.FadeAlpha}");
|
||||
// The retail animation table should be close to the transition distance here.
|
||||
Assert.True(
|
||||
snap.ViewPlaneBlend > 0.95f,
|
||||
$"Expected ViewPlaneBlend near 1, got {snap.ViewPlaneBlend}");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FadeAlpha_IsMonotonicallyIncreasing_DuringWorldFadeOut()
|
||||
public void ViewPlaneBlend_IsMonotonicallyIncreasing_DuringWorldFadeOut()
|
||||
{
|
||||
var seq = new TeleportAnimSequencer();
|
||||
seq.Begin(TeleportEntryKind.Logout);
|
||||
|
|
@ -327,23 +345,23 @@ public sealed class TeleportAnimSequencerTests
|
|||
{
|
||||
var (snap, _) = seq.Tick(step, worldReady: false);
|
||||
if (seq.State != TeleportAnimState.WorldFadeOut) break;
|
||||
Assert.True(snap.FadeAlpha >= prev - 0.001f,
|
||||
$"Alpha decreased: {prev} -> {snap.FadeAlpha} at step {i}");
|
||||
prev = snap.FadeAlpha;
|
||||
Assert.True(snap.ViewPlaneBlend >= prev - 0.001f,
|
||||
$"View-plane blend decreased: {prev} -> {snap.ViewPlaneBlend} at step {i}");
|
||||
prev = snap.ViewPlaneBlend;
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FadeAlpha_IsZero_DuringTunnelStates()
|
||||
public void ViewPlaneBlend_IsZero_DuringStableTunnelStates()
|
||||
{
|
||||
var seq = new TeleportAnimSequencer();
|
||||
seq.Begin(TeleportEntryKind.Portal);
|
||||
seq.Tick(0f, worldReady: false);
|
||||
|
||||
// In Tunnel state, overlay alpha should be 0 (the tunnel viewport is shown; no alpha quad needed)
|
||||
// Stable tunnel space uses the ordinary game projection.
|
||||
var (snap, _) = seq.Tick(0.016f, worldReady: false);
|
||||
Assert.Equal(TeleportAnimState.Tunnel, seq.State);
|
||||
Assert.Equal(0f, snap.FadeAlpha);
|
||||
Assert.Equal(0f, snap.ViewPlaneBlend);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -372,6 +390,30 @@ public sealed class TeleportAnimSequencerTests
|
|||
Assert.True(snap3.ShowTunnel);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Logout_EntersPortalViewportOnFirstTunnelFadeInTick()
|
||||
{
|
||||
var seq = new TeleportAnimSequencer();
|
||||
seq.Begin(TeleportEntryKind.Logout);
|
||||
|
||||
// Retail's visibility block runs before the transition block, so the
|
||||
// WorldFadeOut crossing queues the viewport edge for the next tick.
|
||||
var (crossing, crossingEvents) = seq.Tick(
|
||||
TeleportAnimSequencer.FadeTime,
|
||||
worldReady: false);
|
||||
Assert.Equal(TeleportAnimState.TunnelFadeIn, crossing.State);
|
||||
Assert.True(crossing.ShowTunnel);
|
||||
Assert.DoesNotContain(TeleportAnimEvent.EnterTunnel, crossingEvents);
|
||||
|
||||
var (firstFadeInFrame, firstFadeInEvents) = seq.Tick(0f, worldReady: false);
|
||||
Assert.Equal(TeleportAnimState.TunnelFadeIn, firstFadeInFrame.State);
|
||||
Assert.Contains(TeleportAnimEvent.EnterTunnel, firstFadeInEvents);
|
||||
|
||||
var (_, tunnelEvents) = seq.Tick(TeleportAnimSequencer.FadeTime, worldReady: false);
|
||||
Assert.Equal(TeleportAnimState.Tunnel, seq.State);
|
||||
Assert.DoesNotContain(TeleportAnimEvent.EnterTunnel, tunnelEvents);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShowPleaseWait_TrueOnlyInTunnelContinue()
|
||||
{
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue