fix(world): #344 — a mid-teleport world-frame disagreement defers the projection instead of crashing
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
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>
This commit is contained in:
parent
3f2b2dc3ed
commit
52bdf4df71
5 changed files with 205 additions and 12 deletions
|
|
@ -13,6 +13,7 @@ using AcDream.Core.Plugins;
|
|||
using AcDream.Core.World;
|
||||
using AcDream.Runtime.Entities;
|
||||
using AcDream.Runtime.Physics;
|
||||
using AcDream.Runtime.World;
|
||||
using DatReaderWriter;
|
||||
using DatReaderWriter.DBObjs;
|
||||
using DatReaderWriter.Types;
|
||||
|
|
@ -48,6 +49,14 @@ internal sealed class DatLiveEntityProjectionMaterializer
|
|||
private readonly RetailStaticAnimatingObjectScheduler _staticAnimations;
|
||||
private readonly LiveWorldOriginState _origin;
|
||||
private readonly IPhysicsScriptTimeSource _gameTime;
|
||||
/// <summary>
|
||||
/// #344: Runtime's authoritative "is a portal/teleport transit currently
|
||||
/// in flight" signal (the same field App's
|
||||
/// <c>LocalPlayerTeleportController.IsActive</c> already exposes) — the
|
||||
/// discriminator between the ordinary #283 corruption case (throw) and
|
||||
/// the legitimate mid-transit ordering race (defer).
|
||||
/// </summary>
|
||||
private readonly RuntimeWorldTransitState _transit;
|
||||
|
||||
private int _received;
|
||||
private int _hydrated;
|
||||
|
|
@ -79,7 +88,8 @@ internal sealed class DatLiveEntityProjectionMaterializer
|
|||
LiveEntityAnimationPresenter animationPresenter,
|
||||
RetailStaticAnimatingObjectScheduler staticAnimations,
|
||||
LiveWorldOriginState origin,
|
||||
IPhysicsScriptTimeSource gameTime)
|
||||
IPhysicsScriptTimeSource gameTime,
|
||||
RuntimeWorldTransitState transit)
|
||||
{
|
||||
_options = options ?? throw new ArgumentNullException(nameof(options));
|
||||
_dats = dats ?? throw new ArgumentNullException(nameof(dats));
|
||||
|
|
@ -101,6 +111,7 @@ internal sealed class DatLiveEntityProjectionMaterializer
|
|||
_staticAnimations = staticAnimations ?? throw new ArgumentNullException(nameof(staticAnimations));
|
||||
_origin = origin ?? throw new ArgumentNullException(nameof(origin));
|
||||
_gameTime = gameTime ?? throw new ArgumentNullException(nameof(gameTime));
|
||||
_transit = transit ?? throw new ArgumentNullException(nameof(transit));
|
||||
}
|
||||
|
||||
public void ResetSessionState()
|
||||
|
|
@ -164,12 +175,26 @@ internal sealed class DatLiveEntityProjectionMaterializer
|
|||
CreateObject.ServerPosition position = canonicalSpawn.Position.Value;
|
||||
int lbX = (int)((position.LandblockId >> 24) & 0xFFu);
|
||||
int lbY = (int)((position.LandblockId >> 16) & 0xFFu);
|
||||
// #283: permanent invariant - the two world-frame owners must agree
|
||||
// before their conversions can be mixed. Proven unreachable by the
|
||||
// 2026-08-03 probe run; this keeps it that way.
|
||||
_origin.EnsureAgreesWithRuntimeFrame(
|
||||
_runtime.Physics.WorldFrameCenterLandblockId,
|
||||
position.LandblockId);
|
||||
// #283/#344: permanent invariant - the two world-frame owners must
|
||||
// agree before their conversions can be mixed. Outside an in-flight
|
||||
// portal/teleport transit a disagreement has no legitimate
|
||||
// explanation and still throws loudly (proven unreachable there by
|
||||
// the 2026-08-03 probe run; this keeps it that way). DURING a
|
||||
// transit, Runtime can legitimately rebase to the destination before
|
||||
// this owner observes old-window retirement
|
||||
// (LiveWorldOriginState.EnsureAgreesWithRuntimeFrame's doc comment on
|
||||
// the two rebase edges) - that is the #344 ordering race, not
|
||||
// corruption, so this is the existing "not yet" outcome: the
|
||||
// projection parks here and the caller's existing spatial-recovery
|
||||
// retry re-attempts it once streaming recentres and the origins
|
||||
// agree again.
|
||||
if (!_origin.TryEnsureAgreesWithRuntimeFrame(
|
||||
_runtime.Physics.WorldFrameCenterLandblockId,
|
||||
position.LandblockId,
|
||||
transitInFlight: _transit.IsTeleportActive))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (AcDream.Core.Physics.PhysicsDiagnostics.ProbeWorldFrameEnabled)
|
||||
ProbeWorldFrameAgreement(position.LandblockId);
|
||||
var worldOrigin = new Vector3(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue