Some checks are pending
Headless portability / portable-headless (ubuntu-latest) (push) Waiting to run
Headless portability / portable-headless (windows-latest) (push) Waiting to run
Headless portability / linux-graphical (push) Waiting to run
Headless portability / linux-vulkan (push) Waiting to run
During a portal transit the two world-frame owners legitimately rebase on different edges (Runtime at TeleportAdvanced, streaming only after old-window retirement), and a spawn projection landing in that window hit the #283 invariant as an unhandled render-path throw — the crash the user hit entering a dungeon. The guard's check is unchanged; only the disagreement RESPONSE is discriminated on the canonical transit authority (RuntimeWorldTransitState.IsTeleportActive, the same field the App layer already reads for portal-in-flight): in transit -> the materializer's existing "not yet" return, parking the projection on its established retry rides (OnLandblockLoaded's re-attempt loop, whose ordering guarantees agreement on retry because the recenter coordinator adopts the new origin BEFORE unblocking new landblock loads — verified at source; plus OnPosition recovery and OnAppearance). Outside transit -> still throws: genuine corruption stays loud. The implementer explicitly ruled out riding the Runtime placement pump, which would have acknowledged-and-discarded the completion receipt and silently dropped the entity forever. Sabotage: removing the discriminator reddened the pre-existing #283 throw tests as well as the new not-in-transit test — the sabotage defeats the original contract, not merely the new coverage. Four new tests cover defer, defer-then-agree-then-succeed (projected exactly once), throw-outside-transit, and the agreeing pass-through. Clean-room suite: 11,261 passed / 6 skipped / 0 failed. #346 filed for a sixth, distinct load-sensitive allocation flake observed during the runs. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
266 lines
9.3 KiB
C#
266 lines
9.3 KiB
C#
using System.Numerics;
|
|
using AcDream.App.World;
|
|
|
|
namespace AcDream.App.Tests.World;
|
|
|
|
public sealed class LiveWorldOriginStateTests
|
|
{
|
|
[Fact]
|
|
public void Placeholder_IsNotAuthoritativeUntilFirstInitialization()
|
|
{
|
|
var state = new LiveWorldOriginState();
|
|
|
|
state.SetPlaceholder(42, 43);
|
|
|
|
Assert.Equal(42, state.CenterX);
|
|
Assert.Equal(43, state.CenterY);
|
|
Assert.False(state.IsKnown);
|
|
Assert.True(state.TryInitialize(10, 11));
|
|
Assert.Equal(10, state.CenterX);
|
|
Assert.Equal(11, state.CenterY);
|
|
Assert.True(state.IsKnown);
|
|
Assert.False(state.TryInitialize(20, 21));
|
|
Assert.Equal(10, state.CenterX);
|
|
Assert.Equal(11, state.CenterY);
|
|
}
|
|
|
|
[Fact]
|
|
public void Recenter_ReplacesKnownOriginAndResetRetainsItAsPlaceholder()
|
|
{
|
|
var state = new LiveWorldOriginState();
|
|
Assert.True(state.TryInitialize(1, 2));
|
|
|
|
state.Recenter(3, 4);
|
|
state.Reset();
|
|
|
|
Assert.Equal(3, state.CenterX);
|
|
Assert.Equal(4, state.CenterY);
|
|
Assert.False(state.IsKnown);
|
|
Assert.True(state.TryInitialize(5, 6));
|
|
}
|
|
|
|
[Fact]
|
|
public void RecenterBeforeInitialization_PreservesUnknownAndResetIsIdempotent()
|
|
{
|
|
var state = new LiveWorldOriginState();
|
|
state.SetPlaceholder(20, 21);
|
|
|
|
state.Recenter(30, 31);
|
|
state.Reset();
|
|
state.Reset();
|
|
|
|
Assert.Equal(30, state.CenterX);
|
|
Assert.Equal(31, state.CenterY);
|
|
Assert.False(state.IsKnown);
|
|
|
|
Assert.True(state.TryInitialize(40, 41));
|
|
state.Recenter(50, 51);
|
|
|
|
Assert.True(state.IsKnown);
|
|
Assert.Equal(50, state.CenterX);
|
|
Assert.Equal(51, state.CenterY);
|
|
}
|
|
|
|
[Fact]
|
|
public void CellLocalForSeed_UsesTheCurrentLandblockOrigin()
|
|
{
|
|
var state = new LiveWorldOriginState();
|
|
state.SetPlaceholder(0x30, 0x32);
|
|
|
|
Vector3 local = state.CellLocalForSeed(
|
|
new Vector3(200f, -300f, 5f),
|
|
0x3130_0001u);
|
|
|
|
Assert.Equal(new Vector3(8f, 84f, 5f), local);
|
|
}
|
|
|
|
// #283: acdream has TWO owners that convert a landblock-local network
|
|
// origin into the streamed world frame - this one for presentation and
|
|
// streaming, RuntimePhysicsState.TryGetWorldFrameOffset for placement.
|
|
// They rebase on different edges (Runtime on the accepted teleport
|
|
// Position; this owner only once StreamingOriginRecenterCoordinator
|
|
// observes old-window retirement), so a one-landblock disagreement places
|
|
// an entity 192 m from the geometry around it - the same failure family
|
|
// as the zero-offset bug 670f307c fixed, with a wrong origin rather than
|
|
// a missing one.
|
|
//
|
|
// A 2026-08-03 connected run with ACDREAM_PROBE_WORLD_FRAME=1 (11 reveals
|
|
// across six landblocks spanning ~45 km) recorded ZERO disagreements:
|
|
// detaching every resident landblock before adopting the new origin
|
|
// serializes the two rebases. These pin the permanent guard.
|
|
|
|
[Fact]
|
|
public void AgreeingWorldFrameOwners_Pass()
|
|
{
|
|
var state = new LiveWorldOriginState();
|
|
Assert.True(state.TryInitialize(0xA9, 0xB6));
|
|
|
|
state.EnsureAgreesWithRuntimeFrame(0xA9B6FFFFu, 0xA9B60001u);
|
|
}
|
|
|
|
[Fact]
|
|
public void BeforeEitherWorldFrameOwnerIsEstablished_ThereIsNothingToAgreeOn()
|
|
{
|
|
var state = new LiveWorldOriginState();
|
|
|
|
// No accepted origin yet; placement is gated separately (#284).
|
|
state.EnsureAgreesWithRuntimeFrame(0xA9B6FFFFu, 0xA9B60001u);
|
|
|
|
// An accepted origin with no Runtime frame yet is equally not a
|
|
// disagreement.
|
|
Assert.True(state.TryInitialize(0xA9, 0xB6));
|
|
state.EnsureAgreesWithRuntimeFrame(0u, 0xA9B60001u);
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(0xAAB6FFFFu, "192m")]
|
|
[InlineData(0xA8B6FFFFu, "-192m")]
|
|
[InlineData(0xA9B7FFFFu, "192m")]
|
|
[InlineData(0xA9B5FFFFu, "-192m")]
|
|
public void DisagreeingWorldFrameOwners_FailLoudlyWithTheOffsetInMetres(
|
|
uint runtimeCenter,
|
|
string expectedOffset)
|
|
{
|
|
var state = new LiveWorldOriginState();
|
|
Assert.True(state.TryInitialize(0xA9, 0xB6));
|
|
|
|
InvalidOperationException error =
|
|
Assert.Throws<InvalidOperationException>(() =>
|
|
state.EnsureAgreesWithRuntimeFrame(
|
|
runtimeCenter,
|
|
0xA9B60001u));
|
|
|
|
Assert.Contains("World-frame owners disagree", error.Message);
|
|
Assert.Contains(expectedOffset, error.Message);
|
|
Assert.Contains("0xA9B60001", error.Message);
|
|
}
|
|
|
|
/// <summary>
|
|
/// The exact case the guard exists for: Runtime has rebased on a portal
|
|
/// destination while the streamed origin still holds the departure
|
|
/// landblock. A cross-world portal makes the offset enormous rather than
|
|
/// subtle - 0xF6 - 0x09 = 237 landblocks east = 45,504 m.
|
|
/// </summary>
|
|
[Fact]
|
|
public void ARuntimeRebaseAheadOfTheStreamedOrigin_IsCaught()
|
|
{
|
|
var state = new LiveWorldOriginState();
|
|
Assert.True(state.TryInitialize(0x09, 0x04));
|
|
|
|
InvalidOperationException error =
|
|
Assert.Throws<InvalidOperationException>(() =>
|
|
state.EnsureAgreesWithRuntimeFrame(
|
|
0xF682FFFFu,
|
|
0xF6820033u));
|
|
|
|
Assert.Contains("45504m", error.Message);
|
|
}
|
|
|
|
[Fact]
|
|
public void AfterBothRebaseToTheSameDestination_TheyAgreeAgain()
|
|
{
|
|
var state = new LiveWorldOriginState();
|
|
Assert.True(state.TryInitialize(0x09, 0x04));
|
|
|
|
// The streamed origin adopts the destination once old-window
|
|
// retirement completes; Runtime is already there.
|
|
state.Recenter(0xF6, 0x82);
|
|
|
|
state.EnsureAgreesWithRuntimeFrame(0xF682FFFFu, 0xF6820033u);
|
|
}
|
|
|
|
// #344: the discriminated overload. During an in-flight portal/teleport
|
|
// transit, Runtime rebasing ahead of this owner is the ordinary shape of
|
|
// the race documented on EnsureAgreesWithRuntimeFrame's doc comment, not
|
|
// corruption — the caller gets back its existing "not yet" outcome (a
|
|
// false return, matching every other "not yet" case in
|
|
// DatLiveEntityProjectionMaterializer.TryMaterialize) instead of a thrown
|
|
// exception. Outside a transit the guard is unchanged: it still throws.
|
|
|
|
[Fact]
|
|
public void DisagreeingWorldFrameOwners_WhileTransitInFlight_DefersInsteadOfThrowing()
|
|
{
|
|
var state = new LiveWorldOriginState();
|
|
Assert.True(state.TryInitialize(0x09, 0x04));
|
|
|
|
bool agree = state.TryEnsureAgreesWithRuntimeFrame(
|
|
0xF682FFFFu,
|
|
0xF6820033u,
|
|
transitInFlight: true);
|
|
|
|
Assert.False(agree);
|
|
// The disagreement did not mutate the streamed origin — only
|
|
// Recenter (driven by StreamingOriginRecenterCoordinator) may do
|
|
// that.
|
|
Assert.Equal(0x09, state.CenterX);
|
|
Assert.Equal(0x04, state.CenterY);
|
|
}
|
|
|
|
/// <summary>
|
|
/// The full #344 round trip: a spawn arrives mid-portal (disagreement,
|
|
/// transit in flight) and parks; streaming then recentres to the same
|
|
/// destination Runtime already rebased to, and the exact same query that
|
|
/// deferred a moment ago now agrees — the caller's existing retry re-
|
|
/// attempts materialization and this is what lets it proceed. Passing
|
|
/// transitInFlight:false on the retry too shows agreement no longer
|
|
/// depends on the discriminator once the origins actually match.
|
|
/// </summary>
|
|
[Fact]
|
|
public void DisagreeingWorldFrameOwners_WhileTransitInFlight_ThenAgreeing_RetrySucceeds()
|
|
{
|
|
var state = new LiveWorldOriginState();
|
|
Assert.True(state.TryInitialize(0x09, 0x04));
|
|
|
|
bool firstAttempt = state.TryEnsureAgreesWithRuntimeFrame(
|
|
0xF682FFFFu,
|
|
0xF6820033u,
|
|
transitInFlight: true);
|
|
Assert.False(firstAttempt);
|
|
|
|
// Streaming recentres: old window fully retires, the streamed
|
|
// origin adopts the destination Runtime already rebased to.
|
|
state.Recenter(0xF6, 0x82);
|
|
|
|
bool retry = state.TryEnsureAgreesWithRuntimeFrame(
|
|
0xF682FFFFu,
|
|
0xF6820033u,
|
|
transitInFlight: false);
|
|
Assert.True(retry);
|
|
}
|
|
|
|
[Fact]
|
|
public void DisagreeingWorldFrameOwners_NotInTransit_StillThrowsWithTheOffsetInMetres()
|
|
{
|
|
var state = new LiveWorldOriginState();
|
|
Assert.True(state.TryInitialize(0x09, 0x04));
|
|
|
|
InvalidOperationException error =
|
|
Assert.Throws<InvalidOperationException>(() =>
|
|
state.TryEnsureAgreesWithRuntimeFrame(
|
|
0xF682FFFFu,
|
|
0xF6820033u,
|
|
transitInFlight: false));
|
|
|
|
Assert.Contains("World-frame owners disagree", error.Message);
|
|
Assert.Contains("45504m", error.Message);
|
|
Assert.Contains("0xF6820033", error.Message);
|
|
}
|
|
|
|
[Fact]
|
|
public void AgreeingWorldFrameOwners_TryVariantPassesRegardlessOfTransitFlag()
|
|
{
|
|
var state = new LiveWorldOriginState();
|
|
Assert.True(state.TryInitialize(0xA9, 0xB6));
|
|
|
|
Assert.True(
|
|
state.TryEnsureAgreesWithRuntimeFrame(
|
|
0xA9B6FFFFu,
|
|
0xA9B60001u,
|
|
transitInFlight: true));
|
|
Assert.True(
|
|
state.TryEnsureAgreesWithRuntimeFrame(
|
|
0xA9B6FFFFu,
|
|
0xA9B60001u,
|
|
transitInFlight: false));
|
|
}
|
|
}
|