fix(physics): restore presentation when a park is cancelled (#312)
Regression from 7f1c1f5a (C4 route 4b-2). A remote player who recalled in,
arrived, and stood still was permanently absent from the world render AND the
radar while remaining fully simulated — 71 healthy physics ticks with contact
and walkable, interpolation enqueues, equipment attached, chat visible.
Route 4b-2 is the first commit that lets an ordinary remote UpdatePosition open
a canonical SetPosition. A park publishes a synchronous Withdraw that tears down
presentation registrations; only TryPublishPlace restores them.
RestoreParkWithdrawal — added in the same slice — restores InWorld, the object
clock, and canonical residency, i.e. the Runtime half only. Eight Opus reviews
verified those three fields and the tests asserted exactly them, so the suite
stayed green while the entity was invisible.
Why it is intermittent: the presentation half IS restored incidentally by the
per-packet prologue rebucket for a MOVING remote. It only sticks when the
entity parks on its FINAL accepted Position and then goes idle, because ACE
stops broadcasting for a stationary entity, so no later packet arrives to
re-publish it and nothing else re-drives.
The fix publishes a RuntimePlacementProjectionKind.WithdrawalRestored receipt on
the one ordered placement stream, acknowledge-only in Runtime (the parked
operation is already retired by CancelCoreDeferred), which the App sink maps to
the exact inverse of its own TryPublishWithdrawal: the projection half (bucket,
IsSpatiallyProjected, IsSpatiallyVisible, spatial indexes, RefreshPresentation)
plus the publish half (_worldState, _worldEvents, _effectPoses,
_localPlayerShadow, visibility sinks). Applied with commitPose: false, because
the withdrawal never moved the sidecar; a test feeds a deliberately wrong
position to pin that.
Two alternatives were refuted on measurement, not preference. Routing the
restore's SetFullCell through CommitCanonicalCell cannot fire on the shipped
remote path at all — the prologue rebucket has already recommitted a non-zero
FullCellId before the merge cancels the park, so no cell edge remains — and it
never touches the publish half regardless. Extending RestoreParkWithdrawal
directly reduces to the same receipt, since Runtime must not reach behind the
host sink.
Gated on the entity ending the rollback canonically whole (FullCellId != 0 &&
InWorld) rather than on residencyRestored, which is false on the shipped remote
path and would have made the fix a no-op. AP-136's quiescing-prefix refusal arm
is preserved: no receipt, entity stays withdrawn.
Corrects my own framing of the defect: _worldState/_worldEvents/_effectPoses are
lost but are NOT what kills render and radar (_worldState is the plugin
IGameState; _effectPoses is the pose registry, not entity.MeshRefs). The
load-bearing casualties are the visibility sinks and the
IsSpatiallyProjected/IsSpatiallyVisible + bucket removal that gates the radar.
Register: AD-63 filed (selection deliberately not restored — user intent),
AP-136 amended (its "restored visible" claim covered only the canonical half;
the gap was a defect, not a divergence). ShadowObjectRegistry.Suspend stays
out of scope per AP-136.
Seven-revert discrimination table including one that proves the test is not
merely re-checking the bucket. Suite 11,023 passed / 4 skipped / 0 failed.
Live gate is user-run and folds into #309: two clients, ACDREAM_PROBE_PARK=1,
recall a remote in and let it stand still; acceptance is
[park-restore] ... presentation=True for that guid plus a visible model and a
radar blip.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
204d0ae047
commit
b1f914d508
10 changed files with 1086 additions and 12 deletions
|
|
@ -1181,6 +1181,23 @@ internal sealed class LiveEntityNetworkUpdateController
|
|||
/// </para>
|
||||
///
|
||||
/// <para>
|
||||
/// <b>Completion of the "already there" justification for the park case
|
||||
/// (2026-08-04).</b> The no-op argument above is a claim about the four
|
||||
/// cell-resolving-nothing outcomes only, and it does NOT extend to
|
||||
/// <c>Deferred</c>. A park runs <c>WithdrawCanonical</c>, which ZEROES
|
||||
/// <c>record.FullCellId</c>; <c>CommitCanonicalCell</c> early-returns only
|
||||
/// on equality, so nothing about "the value is already there" survives a
|
||||
/// park. <c>Deferred</c> is nevertheless suppressed correctly, but for the
|
||||
/// FIRST reason in this doc rather than the second: the park snapped the
|
||||
/// body to a resolved cell that need not be the wire cell, and
|
||||
/// <c>RestoreParkWithdrawal</c> re-commits residency from that body cell
|
||||
/// (or, in this controller's shipped order, leaves in place the full cell
|
||||
/// the per-UP <c>RebucketLiveEntity</c> above committed before routing).
|
||||
/// Adopting the wire cell into <c>RemoteMotion.CellId</c> afterwards would
|
||||
/// contradict whichever of those two the entity actually holds.
|
||||
/// </para>
|
||||
///
|
||||
/// <para>
|
||||
/// Returns true when the wire cell was adopted.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -1192,8 +1192,10 @@ public sealed class LiveEntityRuntime : ILiveEntityRadarSource
|
|||
// sidecar yet, destination backend not loaded), and a false
|
||||
// return here wedges the whole ordered placement stream at the
|
||||
// FIFO head (RuntimePlacementProjectionSubscription's contract).
|
||||
// Provably inert today: PublishExecutorCompletion has zero
|
||||
// production callers.
|
||||
// NOT inert: RuntimeInitialCreateContinuationExecutor's Released
|
||||
// arm calls PublishExecutorCompletion in production, and
|
||||
// RuntimePlacementPresentationSink binds presentation off that
|
||||
// receipt (C3c) rather than ignoring it the way this method does.
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -1205,6 +1207,12 @@ public sealed class LiveEntityRuntime : ILiveEntityRadarSource
|
|||
out LiveEntityRecord? record)
|
||||
|| record.WorldEntity is not { } entity)
|
||||
{
|
||||
// A WithdrawalRestored receipt lands here for an entity that is
|
||||
// gone, displaced by a newer incarnation, or not materialized:
|
||||
// there is no prior projection left to restore, so this reports
|
||||
// "nothing restored". Its ONLY caller acknowledges the receipt
|
||||
// regardless rather than wedging the ordered stream - see
|
||||
// RuntimePlacementPresentationSink.TryApplyWithdrawalRestoration.
|
||||
return false;
|
||||
}
|
||||
if (projection.Kind is RuntimePlacementProjectionKind.Place
|
||||
|
|
@ -1215,6 +1223,13 @@ public sealed class LiveEntityRuntime : ILiveEntityRadarSource
|
|||
// pending graphical bucket. Keep the FIFO head unacknowledged
|
||||
// until the destination backend exists, otherwise GpuWorldState's
|
||||
// later pending-drain edge escapes this exact receipt transaction.
|
||||
//
|
||||
// WithdrawalRestored deliberately does NOT take this gate: it
|
||||
// re-installs a bucket the matching Withdraw removed rather than
|
||||
// creating a new one, the legacy per-packet rebucket already
|
||||
// permits a pending bucket for this same entity, and holding the
|
||||
// FIFO head behind a streaming edge would wedge every later
|
||||
// receipt for every entity.
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -1224,21 +1239,40 @@ public sealed class LiveEntityRuntime : ILiveEntityRadarSource
|
|||
TryApplyRuntimePlacementPlace(in projection, record, entity),
|
||||
RuntimePlacementProjectionKind.Withdraw =>
|
||||
TryApplyRuntimePlacementWithdrawal(token, record, entity),
|
||||
RuntimePlacementProjectionKind.WithdrawalRestored =>
|
||||
TryApplyRuntimePlacementPlace(
|
||||
in projection,
|
||||
record,
|
||||
entity,
|
||||
commitPose: false),
|
||||
_ => false,
|
||||
};
|
||||
}
|
||||
|
||||
/// <param name="commitPose">
|
||||
/// False for a <see cref="RuntimePlacementProjectionKind.WithdrawalRestored"/>
|
||||
/// receipt. That receipt is the EXACT inverse of
|
||||
/// <see cref="TryApplyRuntimePlacementWithdrawal"/>, which removed the
|
||||
/// graphical projection without touching the sidecar's world pose - so the
|
||||
/// rollback re-installs the projection without touching it either. The
|
||||
/// entity's pose stays owned by the per-frame remote publication path that
|
||||
/// kept running throughout the park.
|
||||
/// </param>
|
||||
private bool TryApplyRuntimePlacementPlace(
|
||||
in RuntimePlacementProjectionSnapshot projection,
|
||||
LiveEntityRecord record,
|
||||
WorldEntity entity)
|
||||
WorldEntity entity,
|
||||
bool commitPose = true)
|
||||
{
|
||||
RuntimePlacementProjectionToken token = projection.Token;
|
||||
RuntimeEntityKey key = token.Entity;
|
||||
ulong projectionOperation = ++record.ProjectionMutationVersion;
|
||||
|
||||
entity.SetPosition(projection.WorldPosition);
|
||||
entity.Rotation = projection.Orientation;
|
||||
if (commitPose)
|
||||
{
|
||||
entity.SetPosition(projection.WorldPosition);
|
||||
entity.Rotation = projection.Orientation;
|
||||
}
|
||||
// #282: live entities carry ParentCellId only; EffectCellId is the
|
||||
// outdoor dat stab / building-shell field (LandblockLoader:80,97).
|
||||
entity.ParentCellId = token.ExactCellId;
|
||||
|
|
|
|||
|
|
@ -71,10 +71,18 @@ internal sealed class RuntimePlacementPresentationSink
|
|||
// C3c: the initial-Create completion receipt is the graphical
|
||||
// binding point for a residence-driven placement (the F1
|
||||
// acknowledge-and-ignore behavior applied only while
|
||||
// PublishExecutorCompletion had zero production callers).
|
||||
// PublishExecutorCompletion had zero production callers;
|
||||
// RuntimeInitialCreateContinuationExecutor's Released arm is one
|
||||
// today).
|
||||
return TryApplyInitialCreateCompletion(in projection);
|
||||
}
|
||||
|
||||
if (projection.Kind
|
||||
is RuntimePlacementProjectionKind.WithdrawalRestored)
|
||||
{
|
||||
return TryApplyWithdrawalRestoration(in projection);
|
||||
}
|
||||
|
||||
if (projection.Kind is RuntimePlacementProjectionKind.Place
|
||||
or RuntimePlacementProjectionKind.Withdraw
|
||||
&& _liveEntities.HasActiveInitialCreateResidence(
|
||||
|
|
@ -159,6 +167,46 @@ internal sealed class RuntimePlacementPresentationSink
|
|||
return TryPublishPlace(record, entity);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Rolls back a <see cref="RuntimePlacementProjectionKind.Withdraw"/> this
|
||||
/// sink already applied, after Runtime restored the canonical half of the
|
||||
/// park it belonged to. Every registration
|
||||
/// <see cref="TryPublishWithdrawal"/> dropped is re-installed by its exact
|
||||
/// mirror image <see cref="TryPublishPlace"/> - the graphical bucket and
|
||||
/// projection visibility through
|
||||
/// <c>LiveEntityRuntime.TryApplyRuntimePlacementProjection</c>, then plugin
|
||||
/// world state, the world-event stream, the effect-pose registry, the
|
||||
/// local-player shadow, and the presentation visibility sinks here.
|
||||
///
|
||||
/// <para><b>Always acknowledges.</b> Like Discard and ExecutorCompleted
|
||||
/// this receipt is not Operation-backed, and its caller
|
||||
/// (<c>RuntimePlacementProjectionSubscription</c>) treats a false return as
|
||||
/// "leave at the FIFO head" - which would wedge the entire ordered stream
|
||||
/// for every entity. Every way the restore below can decline is an
|
||||
/// entity that is gone, displaced by a newer incarnation, or not
|
||||
/// materialized, i.e. one with no prior projection left to restore; the
|
||||
/// replacement projects itself through its own receipts.</para>
|
||||
///
|
||||
/// <para><b>Selection is deliberately not re-established.</b> The
|
||||
/// withdrawal's <c>_clearSelectionForUnavailableEntity</c> is a
|
||||
/// user-intent mutation, not a projection registration; re-selecting an
|
||||
/// object on the player's behalf would invent input. Recorded as AD-63 in
|
||||
/// the divergence register.</para>
|
||||
/// </summary>
|
||||
private bool TryApplyWithdrawalRestoration(
|
||||
in RuntimePlacementProjectionSnapshot projection)
|
||||
{
|
||||
if (_liveEntities.TryApplyRuntimePlacementProjection(in projection)
|
||||
&& _liveEntities.TryGetRecord(
|
||||
projection.Token.Entity,
|
||||
out LiveEntityRecord record)
|
||||
&& record.WorldEntity is { } entity)
|
||||
{
|
||||
_ = TryPublishPlace(record, entity);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool TryPublishPlace(LiveEntityRecord record, WorldEntity entity)
|
||||
{
|
||||
if (!IsCurrent(record, entity))
|
||||
|
|
|
|||
|
|
@ -46,6 +46,18 @@ internal sealed class HeadlessRuntimePlacementProjectionSink
|
|||
return true;
|
||||
}
|
||||
|
||||
if (projection.Kind
|
||||
is RuntimePlacementProjectionKind.WithdrawalRestored)
|
||||
{
|
||||
// Acknowledge-and-ignore for the same reason: the receipt rolls
|
||||
// back the PRESENTATION half of a cancelled park's withdrawal, and
|
||||
// a headless host has no graphical sidecar, plugin world state,
|
||||
// effect poses, or visibility sinks to restore - Runtime already
|
||||
// restored every canonical fact before publishing it. Refusing it
|
||||
// would wedge the whole ordered stream.
|
||||
return true;
|
||||
}
|
||||
|
||||
RuntimePlacementProjectionToken token = projection.Token;
|
||||
RuntimeEntityDirectory directory = _runtime.EntityObjects.Entities;
|
||||
if (projection.Kind is RuntimePlacementProjectionKind.Place
|
||||
|
|
|
|||
|
|
@ -72,6 +72,34 @@ public enum RuntimePlacementProjectionKind
|
|||
/// see AcknowledgeProjection's dedicated branch.
|
||||
/// </summary>
|
||||
ExecutorCompleted,
|
||||
/// <summary>
|
||||
/// The <see cref="Withdraw"/> receipt a restorable park published has
|
||||
/// been ROLLED BACK by <c>RestoreParkWithdrawal</c>: canonical residency,
|
||||
/// <c>InWorld</c>, the transient bits, and the object clock are whole
|
||||
/// again at the entity's committed cell, so every presentation
|
||||
/// registration that <see cref="Withdraw"/> tore down must be re-installed
|
||||
/// exactly as it stood.
|
||||
///
|
||||
/// <para>This exists because <c>RestoreParkWithdrawal</c> can only reach
|
||||
/// CANONICAL state. The presentation half of a withdrawal (the graphical
|
||||
/// bucket, the projection-visibility sinks, plugin world state/events, the
|
||||
/// effect-pose registry, the local-player shadow) lives behind the host
|
||||
/// sink, and its only mirror image is that sink's Place publication. Before
|
||||
/// this receipt existed the rollback depended on a LATER <see cref="Place"/>
|
||||
/// that a remote which stops moving never receives - ACE stops broadcasting
|
||||
/// Positions for a stationary entity - leaving it simulated, collidable and
|
||||
/// audible but invisible in the world and absent from the radar for the
|
||||
/// rest of the session.</para>
|
||||
///
|
||||
/// <para>Acknowledge-only in Runtime, exactly like <see cref="Discard"/>
|
||||
/// and <see cref="ExecutorCompleted"/>: it is published after the parked
|
||||
/// operation has already been cancelled and retired, so there is no
|
||||
/// operation to resume or commit against - see AcknowledgeProjection's
|
||||
/// dedicated branch. A host must NEVER refuse it: a false return leaves it
|
||||
/// at the FIFO head and wedges the whole ordered stream, which is strictly
|
||||
/// worse than the invisibility it repairs.</para>
|
||||
/// </summary>
|
||||
WithdrawalRestored,
|
||||
}
|
||||
|
||||
public readonly record struct RuntimePortalPlacementAuthority(
|
||||
|
|
@ -1163,6 +1191,81 @@ internal sealed class RuntimeSetPositionState : IDisposable
|
|||
return token;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Publishes the exact inverse of the <see cref="ParkDeferred"/>
|
||||
/// withdrawal receipt whose CANONICAL half
|
||||
/// <see cref="RestoreParkWithdrawal"/> just rolled back, on the SAME
|
||||
/// ordered stream every Place/Withdraw/Discard receipt uses.
|
||||
///
|
||||
/// <para><b>Why a receipt rather than a wider rollback.</b>
|
||||
/// <see cref="ParkDeferred"/>'s withdrawal has two halves. The canonical
|
||||
/// half (<c>InWorld</c>, transient bits, object clock, residency, spatial
|
||||
/// root) is Runtime-owned and <see cref="RestoreParkWithdrawal"/> restores
|
||||
/// it directly. The PRESENTATION half was performed by the host sink that
|
||||
/// consumed the Withdraw receipt - the graphical bucket, the projection
|
||||
/// visibility sinks, plugin world state and events, the effect-pose
|
||||
/// registry, the local-player shadow - and Runtime cannot and must not
|
||||
/// reach any of it. Its one existing mirror image is the sink's own Place
|
||||
/// publication, so the rollback names it the same way the teardown was
|
||||
/// named: with an ordered receipt.</para>
|
||||
///
|
||||
/// <para><b>Why not simply route the restore's SetFullCell through
|
||||
/// CommitCanonicalCell</b> so the graphical <c>CellCommitted</c> ->
|
||||
/// <c>RebucketLiveEntity</c> recovery fires: measured, that recovery
|
||||
/// restores the bucket, <c>IsSpatiallyProjected</c>,
|
||||
/// <c>IsSpatiallyVisible</c> and the projection-visibility observers, but
|
||||
/// it never touches the plugin world state, the world-event stream, or the
|
||||
/// effect-pose registry - only the sink's Place publication does. It also
|
||||
/// cannot fire at all in the shipped remote path, where the per-packet
|
||||
/// prologue rebucket has already recommitted a NON-ZERO
|
||||
/// <c>record.FullCellId</c> before the merge cancels the park, so the
|
||||
/// restore's residency arm is skipped and there is no cell edge to
|
||||
/// commit.</para>
|
||||
///
|
||||
/// <para>NOT Operation-backed: the parked operation was removed and
|
||||
/// retired by <c>CancelCoreDeferred</c> before this runs, so the token is
|
||||
/// assembled from the canonical record's current facts exactly the way
|
||||
/// <see cref="PublishExecutorCompletion"/> assembles one, and
|
||||
/// <see cref="AcknowledgeProjection"/> consumes it through the same
|
||||
/// acknowledge-only branch.</para>
|
||||
///
|
||||
/// <para>Ordering is the stream's, not ours: the cancelled park's own
|
||||
/// Discard still sits at a LOWER sequence when this publishes, so the
|
||||
/// synchronous dispatch below is a no-op and the host's per-frame
|
||||
/// <c>RetryPending</c> pump delivers this receipt immediately after that
|
||||
/// Discard drains. If the same packet then commits or re-parks the entity,
|
||||
/// its Place/Withdraw lands at a HIGHER sequence and supersedes this
|
||||
/// restoration in canonical order.</para>
|
||||
/// </summary>
|
||||
private void PublishWithdrawalRestoration(RuntimeEntityRecord record)
|
||||
{
|
||||
if (record.Key is not { } key)
|
||||
return;
|
||||
PhysicsBody? body = record.PhysicsBody;
|
||||
ulong sequence = checked(++_nextProjectionSequence);
|
||||
var token = new RuntimePlacementProjectionToken(
|
||||
sequence,
|
||||
Revision: 1UL,
|
||||
key,
|
||||
record.PositionAuthorityVersion,
|
||||
record.SpatialAuthorityVersion,
|
||||
record.PlacementCommitVersion,
|
||||
_entities.SessionLifetimeVersion,
|
||||
record.FullCellId,
|
||||
_physics.ExpectedCollisionGeneration(record.FullCellId),
|
||||
Portal: default);
|
||||
var snapshot = new RuntimePlacementProjectionSnapshot(
|
||||
token,
|
||||
RuntimePlacementProjectionKind.WithdrawalRestored,
|
||||
body?.Position ?? Vector3.Zero,
|
||||
body?.Orientation ?? Quaternion.Identity,
|
||||
body?.CellPosition.Frame.Origin ?? Vector3.Zero,
|
||||
body?.InContact ?? false,
|
||||
body?.OnWalkable ?? false);
|
||||
_pendingProjection.Add(sequence, snapshot);
|
||||
PublishPlacement(snapshot);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// F2: binds the ONE notification fired when a Kind ExecutorCompleted
|
||||
/// receipt is acknowledged (mirrors
|
||||
|
|
@ -3185,11 +3288,16 @@ internal sealed class RuntimeSetPositionState : IDisposable
|
|||
return false;
|
||||
}
|
||||
if (pending.Kind is RuntimePlacementProjectionKind.Discard
|
||||
or RuntimePlacementProjectionKind.ExecutorCompleted)
|
||||
or RuntimePlacementProjectionKind.ExecutorCompleted
|
||||
or RuntimePlacementProjectionKind.WithdrawalRestored)
|
||||
{
|
||||
// C0-1: an ExecutorCompleted receipt is never Operation-backed
|
||||
// (see PublishExecutorCompletion) - there is nothing to resume or
|
||||
// commit against, exactly like Discard.
|
||||
// commit against, exactly like Discard. A WithdrawalRestored
|
||||
// receipt is published from RestoreParkWithdrawal, AFTER
|
||||
// CancelCoreDeferred already removed and retired the parked
|
||||
// operation (see PublishWithdrawalRestoration), so it is never
|
||||
// Operation-backed either.
|
||||
_pendingProjection.Remove(token.Sequence);
|
||||
RetireQuiescenceProjectionSequence(token.Sequence);
|
||||
if (pending.Kind is RuntimePlacementProjectionKind.ExecutorCompleted)
|
||||
|
|
@ -3450,6 +3558,20 @@ internal sealed class RuntimeSetPositionState : IDisposable
|
|||
/// cancelled park cannot leave the entity invisible and intangible with
|
||||
/// nothing able to wake it.
|
||||
///
|
||||
/// <para><b>Presentation is rolled back too, through
|
||||
/// <see cref="PublishWithdrawalRestoration"/>.</b> The canonical half
|
||||
/// below is only one half of what <see cref="ParkDeferred"/>'s Withdraw
|
||||
/// receipt removed; the host sink that consumed that receipt also dropped
|
||||
/// the graphical bucket, the projection-visibility sinks, plugin world
|
||||
/// state/events, the effect-pose registry and the local-player shadow, and
|
||||
/// nothing but that sink's Place publication re-installs them. Relying on
|
||||
/// a LATER Place was the defect: a remote that parks on its final Position
|
||||
/// and then stops moving never receives one (ACE stops broadcasting for a
|
||||
/// stationary entity), so it stayed simulated, collidable, audible - and
|
||||
/// invisible in both the world and the radar for the rest of the session.
|
||||
/// The restoration receipt is published exactly when the entity ends this
|
||||
/// method canonically whole, so the two halves can never disagree.</para>
|
||||
///
|
||||
/// <para>Residency specifically is re-tested against the live quiescence
|
||||
/// map HERE as well as at park time, because this runs on a later packet
|
||||
/// for a retained park - see the inline comment for the window. The
|
||||
|
|
@ -3542,12 +3664,27 @@ internal sealed class RuntimeSetPositionState : IDisposable
|
|||
_physics.AcknowledgeSpatialProjection(record, spatial: true);
|
||||
residencyRestored = true;
|
||||
}
|
||||
// The presentation rollback is gated on the entity actually ENDING
|
||||
// this method canonically whole, not on `residencyRestored` alone.
|
||||
// Those are different facts: in the shipped graphical remote path the
|
||||
// per-packet prologue rebucket (LiveEntityNetworkUpdateController ->
|
||||
// LiveEntityRuntime.RebucketLiveEntity) has already recommitted a
|
||||
// non-zero FullCellId and re-acknowledged the spatial root BEFORE the
|
||||
// merge cancels the park, so the arm above is correctly skipped while
|
||||
// the entity is nonetheless whole and must be shown again. The
|
||||
// converse - a quiescing prefix refusing residency (AP-136) - leaves
|
||||
// FullCellId at zero, and the entity stays presentation-withdrawn to
|
||||
// match, exactly as it stays canonically withdrawn.
|
||||
bool canonicallyWhole = record.FullCellId != 0u
|
||||
&& record.PhysicsBody is { InWorld: true };
|
||||
if (canonicallyWhole)
|
||||
PublishWithdrawalRestoration(record);
|
||||
// Issue #309's connected-gate confirmation signal; see the [park]
|
||||
// line's own comment in ParkDeferred.
|
||||
if (PhysicsDiagnostics.ProbeParkEnabled)
|
||||
{
|
||||
Console.WriteLine(FormattableString.Invariant(
|
||||
$"[park-restore] guid=0x{record.ServerGuid:X8} restoreCell=0x{residentCellId:X8} inWorld={withdrawal.InWorld} residency={residencyRestored}"));
|
||||
$"[park-restore] guid=0x{record.ServerGuid:X8} restoreCell=0x{residentCellId:X8} inWorld={withdrawal.InWorld} residency={residencyRestored} presentation={canonicallyWhole}"));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue