feat(teleport C): TAS-driven fade transit + retire TeleportArrivalController
Wires the dormant TeleportAnimSequencer as the transit driver: on PlayerTeleport the player holds in PortalSpace behind a full-screen fade (FadeOverlay) until the destination terrain is resident (TeleportWorldReady, gated on the priority-applied landblock), then materializes (Place), and after the world fades back in regains control + acks the server (FireLoginComplete). No movement resolves against the empty world, so the outbound cell frame can't corrupt. Outdoor changes from place-immediately back to hold-until-resident (now fast, not a band-aid). - FadeOverlay: fullscreen NDC black quad, alpha = ShowTunnel ? 1 : FadeAlpha. - Retires TeleportArrivalController + its 2 tests (TAS subsumes the driver role). - Divergence register: AD-2 updated to the new mechanism; AD-31 (fade vs swirl). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
9ac719424c
commit
3ce1fae332
7 changed files with 237 additions and 407 deletions
|
|
@ -1,147 +0,0 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Numerics;
|
||||
using AcDream.App.World;
|
||||
using Xunit;
|
||||
|
||||
namespace AcDream.App.Tests.World;
|
||||
|
||||
public class TeleportArrivalControllerTests
|
||||
{
|
||||
// Records each Place(destPos, destCell, forced) call.
|
||||
private sealed record PlaceCall(Vector3 Pos, uint Cell, bool Forced);
|
||||
|
||||
private static TeleportArrivalController Make(
|
||||
ArrivalReadiness verdict,
|
||||
List<PlaceCall> placed,
|
||||
int maxHoldFrames = TeleportArrivalController.DefaultMaxHoldFrames)
|
||||
=> new(
|
||||
readiness: (_, _) => verdict,
|
||||
place: (pos, cell, forced) => placed.Add(new PlaceCall(pos, cell, forced)),
|
||||
maxHoldFrames: maxHoldFrames);
|
||||
|
||||
[Fact]
|
||||
public void BeginArrival_EntersHolding()
|
||||
{
|
||||
var placed = new List<PlaceCall>();
|
||||
var c = Make(ArrivalReadiness.NotReady, placed);
|
||||
|
||||
c.BeginArrival(new Vector3(1, 2, 3), 0x01250126u);
|
||||
|
||||
Assert.Equal(TeleportArrivalPhase.Holding, c.Phase);
|
||||
Assert.Empty(placed);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Tick_WhenIdle_IsNoOp()
|
||||
{
|
||||
var placed = new List<PlaceCall>();
|
||||
var c = Make(ArrivalReadiness.Ready, placed);
|
||||
|
||||
c.Tick(); // never began
|
||||
|
||||
Assert.Equal(TeleportArrivalPhase.Idle, c.Phase);
|
||||
Assert.Empty(placed);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Tick_NotReady_KeepsHolding_DoesNotPlace()
|
||||
{
|
||||
var placed = new List<PlaceCall>();
|
||||
var c = Make(ArrivalReadiness.NotReady, placed);
|
||||
c.BeginArrival(new Vector3(1, 2, 3), 0x01250126u);
|
||||
|
||||
c.Tick();
|
||||
c.Tick();
|
||||
|
||||
Assert.Equal(TeleportArrivalPhase.Holding, c.Phase);
|
||||
Assert.Empty(placed);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Tick_Ready_PlacesUnforced_AndIdles()
|
||||
{
|
||||
var placed = new List<PlaceCall>();
|
||||
var c = Make(ArrivalReadiness.Ready, placed);
|
||||
c.BeginArrival(new Vector3(30, -60, 6.005f), 0x01250126u);
|
||||
|
||||
c.Tick();
|
||||
|
||||
Assert.Equal(TeleportArrivalPhase.Idle, c.Phase);
|
||||
var call = Assert.Single(placed);
|
||||
Assert.False(call.Forced);
|
||||
Assert.Equal(0x01250126u, call.Cell);
|
||||
Assert.Equal(new Vector3(30, -60, 6.005f), call.Pos);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Tick_Impossible_PlacesForced_AndIdles()
|
||||
{
|
||||
var placed = new List<PlaceCall>();
|
||||
var c = Make(ArrivalReadiness.Impossible, placed);
|
||||
c.BeginArrival(new Vector3(1, 2, 3), 0x0125FF00u);
|
||||
|
||||
c.Tick();
|
||||
|
||||
Assert.Equal(TeleportArrivalPhase.Idle, c.Phase);
|
||||
var call = Assert.Single(placed);
|
||||
Assert.True(call.Forced);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Tick_Timeout_PlacesForced_AfterMaxHoldFrames()
|
||||
{
|
||||
var placed = new List<PlaceCall>();
|
||||
var c = Make(ArrivalReadiness.NotReady, placed, maxHoldFrames: 3);
|
||||
c.BeginArrival(new Vector3(1, 2, 3), 0x01250126u);
|
||||
|
||||
c.Tick(); // 1
|
||||
c.Tick(); // 2
|
||||
Assert.Empty(placed);
|
||||
Assert.Equal(TeleportArrivalPhase.Holding, c.Phase);
|
||||
|
||||
c.Tick(); // 3 -> timeout
|
||||
|
||||
var call = Assert.Single(placed);
|
||||
Assert.True(call.Forced);
|
||||
Assert.Equal(TeleportArrivalPhase.Idle, c.Phase);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BeginArrival_AfterPlace_ReArms()
|
||||
{
|
||||
var placed = new List<PlaceCall>();
|
||||
var c = Make(ArrivalReadiness.Ready, placed);
|
||||
|
||||
c.BeginArrival(new Vector3(1, 0, 0), 0x01250126u);
|
||||
c.Tick(); // places #1, idle
|
||||
c.BeginArrival(new Vector3(2, 0, 0), 0x01250127u);
|
||||
c.Tick(); // places #2, idle
|
||||
|
||||
Assert.Equal(2, placed.Count);
|
||||
Assert.Equal(0x01250127u, placed[1].Cell);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BeginArrival_DuringHold_ResetsTimeoutCounter()
|
||||
{
|
||||
var placed = new List<PlaceCall>();
|
||||
var c = Make(ArrivalReadiness.NotReady, placed, maxHoldFrames: 3);
|
||||
|
||||
c.BeginArrival(new Vector3(1, 0, 0), 0x01250126u);
|
||||
c.Tick(); // held=1
|
||||
c.Tick(); // held=2 (one short of the timeout)
|
||||
|
||||
// Re-arm mid-hold with a fresh destination: the counter must restart.
|
||||
c.BeginArrival(new Vector3(2, 0, 0), 0x01250199u);
|
||||
c.Tick(); // held=1 again (NOT 3 -> no placement yet)
|
||||
c.Tick(); // held=2
|
||||
Assert.Empty(placed);
|
||||
Assert.Equal(TeleportArrivalPhase.Holding, c.Phase);
|
||||
|
||||
c.Tick(); // held=3 -> timeout, forced place of the SECOND destination
|
||||
var call = Assert.Single(placed);
|
||||
Assert.True(call.Forced);
|
||||
Assert.Equal(0x01250199u, call.Cell);
|
||||
Assert.Equal(new Vector3(2, 0, 0), call.Pos);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,52 +0,0 @@
|
|||
using AcDream.App.World;
|
||||
using Xunit;
|
||||
|
||||
namespace AcDream.App.Tests.World;
|
||||
|
||||
/// <summary>
|
||||
/// The teleport-arrival readiness decision (#145/#138).
|
||||
/// <list type="bullet">
|
||||
/// <item>Unhydratable claim → place immediately via the caller's safety-net (Impossible).</item>
|
||||
/// <item>Indoor (sealed dungeon / building interior) → hold until the EnvCell floor hydrates.</item>
|
||||
/// <item>Outdoor → place IMMEDIATELY on the server-authoritative position. Holding is futile —
|
||||
/// streaming does not progress while the player is held in PortalSpace, so the
|
||||
/// destination can't stream in during a hold. The stale source landblock is dropped at
|
||||
/// recenter (GameWindow.OnLivePositionUpdated), so the resolve falls through to the
|
||||
/// server cell (Resolve NO-LANDBLOCK verbatim) until the destination streams in.</item>
|
||||
/// </list>
|
||||
/// </summary>
|
||||
public class TeleportArrivalRulesTests
|
||||
{
|
||||
[Fact]
|
||||
public void Unhydratable_IsImpossible()
|
||||
{
|
||||
Assert.Equal(ArrivalReadiness.Impossible,
|
||||
TeleportArrivalRules.Decide(claimUnhydratable: true, indoor: false, indoorCellReady: false));
|
||||
// …even for an indoor claim.
|
||||
Assert.Equal(ArrivalReadiness.Impossible,
|
||||
TeleportArrivalRules.Decide(claimUnhydratable: true, indoor: true, indoorCellReady: false));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Indoor_CellReady_IsReady()
|
||||
{
|
||||
Assert.Equal(ArrivalReadiness.Ready,
|
||||
TeleportArrivalRules.Decide(claimUnhydratable: false, indoor: true, indoorCellReady: true));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Indoor_CellNotReady_HoldsNotReady()
|
||||
{
|
||||
Assert.Equal(ArrivalReadiness.NotReady,
|
||||
TeleportArrivalRules.Decide(claimUnhydratable: false, indoor: true, indoorCellReady: false));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Outdoor_PlacesImmediately()
|
||||
{
|
||||
// #145: never hold an outdoor arrival — place at once on the server position. The
|
||||
// indoorCellReady flag is irrelevant for an outdoor destination.
|
||||
Assert.Equal(ArrivalReadiness.Ready,
|
||||
TeleportArrivalRules.Decide(claimUnhydratable: false, indoor: false, indoorCellReady: false));
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue