feat(runtime): construct remote bodies at Create per retail set_description
Cutover slice C3b: residence-route remote/creature/projectile Creates now get their canonical PhysicsBody at Create time — retail's order, closing the C3 flip's Finding C (production builds bodies at first motion; retail builds them in ACCObjectMaint::CreateObject). RuntimeRemoteBodyDescription walks set_description 0x00514F40 exactly: the motion-table gate (zero id PASSES — verified at 0051871f/005127ca), the frame-vs-movement branch keyed on retail's movement_buffer != 0 (an empty-buffer movement payload takes the PLACEMENT branch and writes no autonomy — the wire-shape defect the retail review caught), set_state, the byte-certain friction gate (inclusive [0,1]; NaN deliberately skipped per the gates doc's sanctioned deviation), the set_elasticity clamp with retail's unordered-to-zero NaN routing (ACE diverges to 0.1 on that edge), the translucency gate (!= 0.0f, original always recorded), velocity via setter, omega raw, and ctor-defaults for absent wire fields (0.95f/0.05f/0 — the fresh-desc-per- message flow verified at both UnPack call sites). InWorld stays false until submission, the enter_world analog. RuntimeRemoteFirstEntryState sequences mover-prep -> body construction -> placement -> acknowledgement -> Execute with every C3a hardening inherited: exactly-once stages, the shared acknowledge-stage discriminator (extracted to RuntimeFirstEntryAcknowledgement, one body for both conductors), typed Contention against in-flight remote-motion binds, never-clobber body binding through the canonical writer (foreign body fails closed — provably safe coexistence with today's build-at-first-motion path in both directions), automatic convergence through the retirement fan-out, and the construction receipt riding the terminal Advance. Dormant: no production caller; C3c wires both hosts. Reviewed: retail-conformance PASS (the construction order, both gate boundary/NaN semantics, the motion-table and autonomy verdicts all re-derived from the pseudo-C) + architecture/adversarial PASS after one fix round. Runtime 982/982; complete Release solution 10,777 passed / 4 intentional skips. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
d62b99509e
commit
0934a12111
6 changed files with 2083 additions and 41 deletions
649
src/AcDream.Runtime/Entities/RuntimeRemoteFirstEntryState.cs
Normal file
649
src/AcDream.Runtime/Entities/RuntimeRemoteFirstEntryState.cs
Normal file
|
|
@ -0,0 +1,649 @@
|
|||
using AcDream.Content;
|
||||
using AcDream.Core.Physics;
|
||||
using AcDream.Runtime.Physics;
|
||||
|
||||
namespace AcDream.Runtime.Entities;
|
||||
|
||||
/// <summary>
|
||||
/// Typed yields for <see cref="RuntimeRemoteFirstEntryState.Advance"/>.
|
||||
/// Mirrors the C3a conductor's vocabulary
|
||||
/// (<c>RuntimeLocalPlayerFirstEntryStatus</c>) rather than inventing a
|
||||
/// parallel one; the two publication-only statuses have no remote analog.
|
||||
/// </summary>
|
||||
internal enum RuntimeRemoteFirstEntryStatus : byte
|
||||
{
|
||||
/// <summary>
|
||||
/// The underlying <see cref="RuntimeInitialCreateContinuationExecutor.Execute"/>
|
||||
/// call reported <c>Completed</c>: residence consumed, initial tail and
|
||||
/// FIFO drained, ExecutorCompleted receipt dispatched. Terminal.
|
||||
/// </summary>
|
||||
Completed,
|
||||
|
||||
/// <summary>
|
||||
/// The authored-mover Setup read
|
||||
/// (<see cref="RuntimeSetPositionState.TryPrepareAuthoredMover"/>) is not
|
||||
/// yet available. Retry with the same arguments once the prepared-asset
|
||||
/// package lands; no Runtime state changed.
|
||||
/// </summary>
|
||||
AwaitingCollisionSource,
|
||||
|
||||
/// <summary>
|
||||
/// The submitted placement deferred (<c>DeferredCell</c> — destination
|
||||
/// collision generation not ready, or a collision-prefix quiescence held
|
||||
/// it) and its parked operation has not produced an acknowledgeable
|
||||
/// Place projection yet. The wake is internal to
|
||||
/// <see cref="RuntimeSetPositionState"/> (collision-generation commit
|
||||
/// drives <c>RetryDeferred</c>); retry <see cref="Advance"/> after it.
|
||||
/// </summary>
|
||||
AwaitingPlacement,
|
||||
|
||||
/// <summary>
|
||||
/// Our projection exists but could not be acknowledged this call —
|
||||
/// either another entity's receipt sits ahead of ours in the one ordered
|
||||
/// FIFO, or <see cref="RuntimeInitialCreateContinuationExecutor.Execute"/>
|
||||
/// still observed <c>PendingPlacement</c>. Retry the same stage.
|
||||
/// </summary>
|
||||
AwaitingReceiptAcknowledgement,
|
||||
|
||||
/// <summary>
|
||||
/// Passthrough of the executor's own <c>AwaitingContinuationPlacement</c>
|
||||
/// — a later FIFO continuation needs its own authored placement before
|
||||
/// the drain can finish; entirely the executor's concern from here on.
|
||||
/// </summary>
|
||||
AwaitingContinuationPlacement,
|
||||
|
||||
/// <summary>
|
||||
/// A reentrant <see cref="Advance"/> for the SAME entity arrived while an
|
||||
/// outer call for it was still on the stack, or another owner's
|
||||
/// body/remote-motion binding callback is mid-flight on this record.
|
||||
/// Retry once the outer call has returned.
|
||||
/// </summary>
|
||||
Contention,
|
||||
|
||||
/// <summary>
|
||||
/// The residence token matches nothing this conductor can own — including
|
||||
/// a LOCAL-PLAYER lease (<see cref="RuntimeSetPositionOperationKind.InitialLogin"/>),
|
||||
/// which belongs to the C3a conductor, never this one.
|
||||
/// </summary>
|
||||
RejectedToken,
|
||||
|
||||
/// <summary>
|
||||
/// An authority-shaped failure (stale epoch/session/identity, deleted or
|
||||
/// replaced record, a foreign physics body bound out-of-band, a rejected
|
||||
/// or cancelled submission). Abandoned; progress removed. The caller must
|
||||
/// begin a fresh sequence (a new residence lease), never retry this call.
|
||||
/// </summary>
|
||||
RejectedAuthority,
|
||||
}
|
||||
|
||||
internal readonly record struct RuntimeRemoteFirstEntryOwnershipSnapshot(
|
||||
int ActiveCount)
|
||||
{
|
||||
internal bool IsConverged => ActiveCount == 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The dormant, resumable Runtime transaction that dissolves C3's Finding C:
|
||||
/// ordinary remote-creature and projectile Creates classify to
|
||||
/// <c>SetPosition</c>, but no production path constructs their canonical
|
||||
/// <see cref="PhysicsBody"/> at Create time (bodies arrive with first motion
|
||||
/// today), so <see cref="RuntimeSetPositionState.SubmitPreparedPlacement"/>'s
|
||||
/// <c>Record.PhysicsBody</c> requirement rejects the residence route's
|
||||
/// initial placement. This class is the remote analog of the C3a conductor
|
||||
/// (<c>RuntimeLocalPlayerFirstEntryState</c>) WITHOUT the publication chain —
|
||||
/// remotes have no <c>PlayerMovementController</c> — and with retail body
|
||||
/// construction in its place:
|
||||
///
|
||||
/// mover preparation (retail <c>CPhysicsObj::makeObject</c> shaping the
|
||||
/// Setup, which precedes <c>set_description</c> in
|
||||
/// <c>ACCObjectMaint::CreateObject</c> 0x00558870 step 2-vs-6) ->
|
||||
/// body construction per the exact <c>set_description</c> order
|
||||
/// (0x00514F40; <see cref="RuntimeRemoteBodyDescription"/>) bound through the
|
||||
/// canonical <see cref="RuntimePhysicsState.GetOrCreatePhysicsBody"/> writer
|
||||
/// -> ordinary authored submission
|
||||
/// (<see cref="RuntimeSetPositionState.SubmitPreparedPlacement"/> — retail
|
||||
/// <c>enter_world</c>, which <c>SmartBox::HandleCreateObject</c> 0x00454C80
|
||||
/// runs for a top-level object with a nonzero wire cell AFTER CreateObject
|
||||
/// returns) -> Withdraw/Place receipt acknowledgement ->
|
||||
/// <see cref="RuntimeInitialCreateContinuationExecutor.Execute"/> (FIFO
|
||||
/// drain).
|
||||
///
|
||||
/// Unlike the local-player path this class never touches the dormant
|
||||
/// activation family: no operation it drives ever has
|
||||
/// <c>DormantLocalActivation</c> set, so the ordinary submission tail is the
|
||||
/// correct — and only — commit route.
|
||||
///
|
||||
/// Dormant by design: <see cref="RuntimeEntityObjectLifetime"/> fully
|
||||
/// constructs and wires this class (construction, retirement fan-out, bulk
|
||||
/// session-clear cleanup, ownership fold) exactly like the C3a conductor,
|
||||
/// but nothing calls <see cref="Advance"/> in production — C3c wires the
|
||||
/// hosts.
|
||||
/// </summary>
|
||||
internal sealed class RuntimeRemoteFirstEntryState
|
||||
{
|
||||
private enum Stage : byte
|
||||
{
|
||||
/// <summary>No progress yet, or the mover has not been prepared.</summary>
|
||||
AwaitingMoverPreparation,
|
||||
|
||||
/// <summary>Mover command in hand; the body has not been constructed.</summary>
|
||||
MoverPrepared,
|
||||
|
||||
/// <summary>
|
||||
/// The canonical body is constructed and bound; the placement has
|
||||
/// not been submitted.
|
||||
/// </summary>
|
||||
BodyConstructed,
|
||||
|
||||
/// <summary>
|
||||
/// Submission deferred (<c>DeferredCell</c>): the parked operation's
|
||||
/// Withdraw/Place receipts are drained from the projection FIFO as
|
||||
/// they surface; the wake itself is internal to
|
||||
/// <see cref="RuntimeSetPositionState"/>.
|
||||
/// </summary>
|
||||
PlacementSubmitted,
|
||||
|
||||
/// <summary>
|
||||
/// The Place projection token is known but not yet acknowledged.
|
||||
/// </summary>
|
||||
PlacementCommitted,
|
||||
|
||||
/// <summary>
|
||||
/// The Place projection has been acknowledged. Only
|
||||
/// <see cref="RuntimeInitialCreateContinuationExecutor.Execute"/>
|
||||
/// remains; the acknowledgement step is never re-entered.
|
||||
/// </summary>
|
||||
Acknowledged,
|
||||
}
|
||||
|
||||
private sealed class Progress
|
||||
{
|
||||
internal required ulong LeaseId { get; init; }
|
||||
internal Stage Stage { get; set; } = Stage.AwaitingMoverPreparation;
|
||||
internal RuntimeSetPositionCommand PreparedCommand { get; set; }
|
||||
internal PhysicsBody? ConstructedBody { get; set; }
|
||||
internal RuntimeRemoteBodyConstructionReceipt Construction { get; set; }
|
||||
internal RuntimePlacementProjectionToken Projection { get; set; }
|
||||
}
|
||||
|
||||
private readonly RuntimeInitialCreateResidenceState _residences;
|
||||
private readonly RuntimeInitialCreateContinuationExecutor _executor;
|
||||
private readonly RuntimePhysicsState _physics;
|
||||
private readonly Dictionary<RuntimeEntityKey, Progress> _progress = [];
|
||||
private readonly HashSet<RuntimeEntityKey> _executing = [];
|
||||
|
||||
internal RuntimeRemoteFirstEntryState(
|
||||
RuntimeInitialCreateResidenceState residences,
|
||||
RuntimeInitialCreateContinuationExecutor executor,
|
||||
RuntimePhysicsState physics)
|
||||
{
|
||||
_residences = residences
|
||||
?? throw new ArgumentNullException(nameof(residences));
|
||||
_executor = executor
|
||||
?? throw new ArgumentNullException(nameof(executor));
|
||||
_physics = physics
|
||||
?? throw new ArgumentNullException(nameof(physics));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Exposes the body-construction receipt for a still-tracked entry —
|
||||
/// the MID-FLIGHT half of the consumption rule documented on
|
||||
/// <see cref="Advance"/> (C3b review M1): while the sequence is in
|
||||
/// flight this query serves diagnostics/tests; the terminal
|
||||
/// <c>Completed</c> yield delivers the same receipt through Advance's
|
||||
/// own out-param in the call that reaps this entry. Returns false once
|
||||
/// the sequence completed or was abandoned.
|
||||
/// </summary>
|
||||
internal bool TryGetConstruction(
|
||||
RuntimeEntityKey key,
|
||||
out RuntimeRemoteBodyConstructionReceipt construction)
|
||||
{
|
||||
if (_progress.TryGetValue(key, out Progress? progress)
|
||||
&& progress.ConstructedBody is not null)
|
||||
{
|
||||
construction = progress.Construction;
|
||||
return true;
|
||||
}
|
||||
construction = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// One resumable step. Callers pass the SAME arguments on every retry;
|
||||
/// this method re-reads currency from the owning states on every entry
|
||||
/// rather than trusting anything cached beyond its own stage cursor and
|
||||
/// the exact command/token structs the owning methods themselves require.
|
||||
///
|
||||
/// <para><b>Construction-receipt consumption rule (C3b review M1),
|
||||
/// following the C3a/F2 precedent of receipts riding the terminal
|
||||
/// Advance out-params:</b> <paramref name="construction"/> is populated
|
||||
/// ONLY on the <see cref="RuntimeRemoteFirstEntryStatus.Completed"/>
|
||||
/// yield — the same call that delivers the executor
|
||||
/// <paramref name="receipt"/> — because the terminal Advance is a C3c
|
||||
/// host's one natural consumption point and the progress entry (the
|
||||
/// receipt's only retained storage) is reaped in that same call.
|
||||
/// Mid-flight the receipt stays inspectable via
|
||||
/// <see cref="TryGetConstruction"/>; after Completed nothing is
|
||||
/// retained. A lease whose route performs no SetPosition (Parented/
|
||||
/// PickedUp) constructs no body, so its terminal receipt is default.</para>
|
||||
/// </summary>
|
||||
internal RuntimeRemoteFirstEntryStatus Advance(
|
||||
RuntimeEntityRecord record,
|
||||
in RuntimeInitialCreateResidenceToken residenceToken,
|
||||
IPreparedCollisionSource collisionSource,
|
||||
double gameTime,
|
||||
in RuntimeInitialCreateExecutionInputs inputs,
|
||||
out RuntimeInitialCreateExecutionReceipt receipt,
|
||||
out RuntimeRemoteBodyConstructionReceipt construction)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(record);
|
||||
ArgumentNullException.ThrowIfNull(collisionSource);
|
||||
receipt = default;
|
||||
construction = default;
|
||||
if (!residenceToken.IsValid || record.Key is not { } key)
|
||||
return RuntimeRemoteFirstEntryStatus.RejectedToken;
|
||||
|
||||
// Mirrors the executor's and the C3a conductor's _executing guard: a
|
||||
// synchronous reentrant call for the SAME entity fails closed rather
|
||||
// than interleaving two drains of one stage machine.
|
||||
if (!_executing.Add(key))
|
||||
return RuntimeRemoteFirstEntryStatus.Contention;
|
||||
try
|
||||
{
|
||||
return AdvanceCore(
|
||||
record,
|
||||
residenceToken,
|
||||
collisionSource,
|
||||
gameTime,
|
||||
inputs,
|
||||
key,
|
||||
out receipt,
|
||||
out construction);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_executing.Remove(key);
|
||||
}
|
||||
}
|
||||
|
||||
private RuntimeRemoteFirstEntryStatus AdvanceCore(
|
||||
RuntimeEntityRecord record,
|
||||
in RuntimeInitialCreateResidenceToken residenceToken,
|
||||
IPreparedCollisionSource collisionSource,
|
||||
double gameTime,
|
||||
in RuntimeInitialCreateExecutionInputs inputs,
|
||||
RuntimeEntityKey key,
|
||||
out RuntimeInitialCreateExecutionReceipt receipt,
|
||||
out RuntimeRemoteBodyConstructionReceipt construction)
|
||||
{
|
||||
receipt = default;
|
||||
construction = default;
|
||||
|
||||
_progress.TryGetValue(key, out Progress? progress);
|
||||
// ABA/GUID-reuse guard, exactly like the C3a conductor and the
|
||||
// executor's own Progress reconciliation.
|
||||
if (progress is not null && progress.LeaseId != residenceToken.LeaseId)
|
||||
{
|
||||
Discard(key);
|
||||
progress = null;
|
||||
}
|
||||
|
||||
if (progress is null || progress.Stage is Stage.AwaitingMoverPreparation)
|
||||
{
|
||||
if (!_residences.TryGetCurrent(
|
||||
record,
|
||||
out RuntimeInitialCreateResidenceLease lease)
|
||||
|| lease.Token != residenceToken)
|
||||
{
|
||||
if (progress is null)
|
||||
return RuntimeRemoteFirstEntryStatus.RejectedToken;
|
||||
Discard(key);
|
||||
return RuntimeRemoteFirstEntryStatus.RejectedAuthority;
|
||||
}
|
||||
|
||||
// This conductor owns REMOTE and PROJECTILE residence leases
|
||||
// only. A local-player lease (InitialLogin — or the structurally
|
||||
// impossible-at-Create LocalAuthoritative) belongs to the C3a
|
||||
// conductor and its publication chain; refusing it here is
|
||||
// "nothing tracked in this domain", not an abandonment.
|
||||
if (lease.Route.OperationKind
|
||||
is not (RuntimeSetPositionOperationKind.RemoteAuthoritative
|
||||
or RuntimeSetPositionOperationKind.ProjectileAuthoritative))
|
||||
{
|
||||
return RuntimeRemoteFirstEntryStatus.RejectedToken;
|
||||
}
|
||||
|
||||
if (!lease.Route.PerformsSetPosition)
|
||||
{
|
||||
// Parented/PickedUp residence: no SetPosition operation
|
||||
// exists, so there is nothing to place and — matching
|
||||
// today's production behavior for those routes — no body is
|
||||
// constructed at Create (retail constructs one, but a
|
||||
// parented child's placement is driven by later parent/
|
||||
// pickup events; body-at-Create for those routes stays with
|
||||
// the first-motion path until a later slice widens this).
|
||||
// Skip straight to Execute, mirroring the C3a conductor.
|
||||
progress ??= new Progress { LeaseId = residenceToken.LeaseId };
|
||||
progress.Stage = Stage.Acknowledged;
|
||||
_progress[key] = progress;
|
||||
return RunExecute(
|
||||
record,
|
||||
residenceToken,
|
||||
inputs,
|
||||
key,
|
||||
progress,
|
||||
out receipt,
|
||||
out construction);
|
||||
}
|
||||
|
||||
RuntimeSetPositionMoverPreparationStatus moverStatus = _physics
|
||||
.SetPosition.TryPrepareAuthoredMover(
|
||||
record,
|
||||
lease.Placement,
|
||||
lease.Route.OperationKind,
|
||||
lease.Route.SetPositionFlags,
|
||||
collisionSource,
|
||||
gameTime,
|
||||
out RuntimeSetPositionCommand command);
|
||||
if (moverStatus
|
||||
== RuntimeSetPositionMoverPreparationStatus.RetrySetupUnavailable)
|
||||
{
|
||||
return RuntimeRemoteFirstEntryStatus.AwaitingCollisionSource;
|
||||
}
|
||||
if (moverStatus != RuntimeSetPositionMoverPreparationStatus.Prepared)
|
||||
{
|
||||
if (progress is not null)
|
||||
Discard(key);
|
||||
return RuntimeRemoteFirstEntryStatus.RejectedAuthority;
|
||||
}
|
||||
|
||||
progress ??= new Progress { LeaseId = residenceToken.LeaseId };
|
||||
progress.PreparedCommand = command;
|
||||
progress.Stage = Stage.MoverPrepared;
|
||||
_progress[key] = progress;
|
||||
}
|
||||
|
||||
if (progress.Stage is Stage.MoverPrepared)
|
||||
{
|
||||
if (!_residences.TryGetCurrent(
|
||||
record,
|
||||
out RuntimeInitialCreateResidenceLease lease)
|
||||
|| lease.Token != residenceToken)
|
||||
{
|
||||
Discard(key);
|
||||
return RuntimeRemoteFirstEntryStatus.RejectedAuthority;
|
||||
}
|
||||
|
||||
if (record.PhysicsBody is { } existing)
|
||||
{
|
||||
if (ReferenceEquals(progress.ConstructedBody, existing))
|
||||
{
|
||||
// Idempotent retry: our own construction already bound.
|
||||
progress.Stage = Stage.BodyConstructed;
|
||||
}
|
||||
else
|
||||
{
|
||||
// A body this conductor did not construct appeared while
|
||||
// the residence lease was still active — an out-of-band
|
||||
// owner raced Create-time construction. Never clobber an
|
||||
// existing canonical body (the writer map's invariant);
|
||||
// fail closed and let the lease's own retirement path
|
||||
// converge.
|
||||
Discard(key);
|
||||
return RuntimeRemoteFirstEntryStatus.RejectedAuthority;
|
||||
}
|
||||
}
|
||||
else if (record.PhysicsBodyAcquisitionInProgress
|
||||
|| record.RemoteMotionBindingInProgress)
|
||||
{
|
||||
// Another owner's binding callback is mid-flight on this
|
||||
// exact record (only reachable when this Advance itself runs
|
||||
// inside that callback). Typed contention instead of letting
|
||||
// GetOrCreatePhysicsBody throw its structural guard.
|
||||
return RuntimeRemoteFirstEntryStatus.Contention;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Retail order: CreateObject acquires the physics object
|
||||
// from the Setup (makeObject — our mover preparation, stage
|
||||
// 1) and then applies the PhysicsDesc via set_description
|
||||
// (RuntimeRemoteBodyDescription.Construct). Binding runs
|
||||
// through the canonical GetOrCreatePhysicsBody writer: its
|
||||
// post-factory InitializeNewPhysicsBody re-applies
|
||||
// state/velocity/omega from the live snapshot — identical by
|
||||
// value to the frozen-description writes the factory already
|
||||
// made (nothing between admission and this call mutates the
|
||||
// snapshot's physics payload; continuations are queued, not
|
||||
// applied) — and its SynchronizeBodyActiveState aligns the
|
||||
// Active transient bit with the record's object clock.
|
||||
RuntimeRemoteBodyConstructionReceipt built = default;
|
||||
PhysicsBody constructed = _physics.GetOrCreatePhysicsBody(
|
||||
record,
|
||||
r => RuntimeRemoteBodyDescription.Construct(
|
||||
r,
|
||||
lease.InitialCreate.Physics,
|
||||
progress.PreparedCommand,
|
||||
out built));
|
||||
progress.ConstructedBody = constructed;
|
||||
progress.Construction = built;
|
||||
progress.Stage = Stage.BodyConstructed;
|
||||
}
|
||||
}
|
||||
|
||||
if (progress.Stage is Stage.BodyConstructed)
|
||||
{
|
||||
if (!_residences.TryGetCurrent(
|
||||
record,
|
||||
out RuntimeInitialCreateResidenceLease lease)
|
||||
|| lease.Token != residenceToken)
|
||||
{
|
||||
Discard(key);
|
||||
return RuntimeRemoteFirstEntryStatus.RejectedAuthority;
|
||||
}
|
||||
|
||||
RuntimeSetPositionOutcome outcome = _physics.SetPosition
|
||||
.SubmitPreparedPlacement(lease.Placement, progress.PreparedCommand);
|
||||
switch (outcome.Status)
|
||||
{
|
||||
case RuntimeSetPositionStatus.CommittedHostAcknowledgementPending:
|
||||
progress.Projection = outcome.Projection;
|
||||
progress.Stage = Stage.PlacementCommitted;
|
||||
break;
|
||||
case RuntimeSetPositionStatus.DeferredCell:
|
||||
// ParkDeferred published a Withdraw receipt and parked
|
||||
// the operation; the projection FIFO drives everything
|
||||
// from here (drained in the PlacementSubmitted stage
|
||||
// below, this same call).
|
||||
progress.Stage = Stage.PlacementSubmitted;
|
||||
break;
|
||||
default:
|
||||
// Rejected (the SetPosition transaction failed — retail's
|
||||
// enter_world failure leaves the object celless; the
|
||||
// resident-cell-cleanup family owns that destiny, not a
|
||||
// silent retry here) or Cancelled (a reentrant observer
|
||||
// displaced the operation). Fail closed.
|
||||
Discard(key);
|
||||
return RuntimeRemoteFirstEntryStatus.RejectedAuthority;
|
||||
}
|
||||
}
|
||||
|
||||
if (progress.Stage is Stage.PlacementSubmitted)
|
||||
{
|
||||
if (!_residences.TryGetCurrent(
|
||||
record,
|
||||
out RuntimeInitialCreateResidenceLease lease)
|
||||
|| lease.Token != residenceToken)
|
||||
{
|
||||
Discard(key);
|
||||
return RuntimeRemoteFirstEntryStatus.RejectedAuthority;
|
||||
}
|
||||
|
||||
// Drain OUR OWN receipts from the FIFO head as they surface:
|
||||
// Withdraw (the deferred park) must be acknowledged before the
|
||||
// internal collision-generation wake can resubmit; the wake's
|
||||
// commit then publishes the Place this stage is waiting for.
|
||||
while (true)
|
||||
{
|
||||
if (!_physics.SetPosition.TryPeekProjection(
|
||||
out RuntimePlacementProjectionSnapshot head))
|
||||
{
|
||||
// Nothing pending anywhere — the operation is parked
|
||||
// awaiting its cell/collision-generation wake. The
|
||||
// residence currency check above already proved the
|
||||
// placement operation itself is still tracked.
|
||||
return RuntimeRemoteFirstEntryStatus.AwaitingPlacement;
|
||||
}
|
||||
if (head.Token.Entity != key)
|
||||
{
|
||||
// Another entity's receipt sits ahead of ours in the one
|
||||
// ordered FIFO.
|
||||
return RuntimeRemoteFirstEntryStatus
|
||||
.AwaitingReceiptAcknowledgement;
|
||||
}
|
||||
if (head.Kind is RuntimePlacementProjectionKind.Withdraw)
|
||||
{
|
||||
if (!_physics.SetPosition.AcknowledgeProjection(head.Token))
|
||||
{
|
||||
Discard(key);
|
||||
return RuntimeRemoteFirstEntryStatus.RejectedAuthority;
|
||||
}
|
||||
// The withdrawal acknowledgement may have re-armed (or —
|
||||
// when the generation was already ready — synchronously
|
||||
// resubmitted) the parked operation; peek again.
|
||||
continue;
|
||||
}
|
||||
if (head.Kind is RuntimePlacementProjectionKind.Place)
|
||||
{
|
||||
progress.Projection = head.Token;
|
||||
progress.Stage = Stage.PlacementCommitted;
|
||||
break;
|
||||
}
|
||||
// Discard (a delete/cancel rewrote our slot) or any other
|
||||
// kind bearing our key: authority moved. Leave the receipt
|
||||
// for the ordinary host drain — mirroring the C3a
|
||||
// conductor's abandonment, which never consumes a Discard
|
||||
// it did not publish — and fail closed.
|
||||
Discard(key);
|
||||
return RuntimeRemoteFirstEntryStatus.RejectedAuthority;
|
||||
}
|
||||
}
|
||||
|
||||
if (progress.Stage is Stage.PlacementCommitted)
|
||||
{
|
||||
if (!_physics.SetPosition.AcknowledgeProjection(progress.Projection))
|
||||
{
|
||||
// Same re-validation the C3a conductor performs on a failed
|
||||
// acknowledge: only a genuinely-not-our-turn FIFO head stays
|
||||
// retryable; a retired lease or a rewritten/superseded slot
|
||||
// means authority moved.
|
||||
if (!IsAcknowledgementStillPending(
|
||||
record, residenceToken, progress.Projection))
|
||||
{
|
||||
Discard(key);
|
||||
return RuntimeRemoteFirstEntryStatus.RejectedAuthority;
|
||||
}
|
||||
return RuntimeRemoteFirstEntryStatus
|
||||
.AwaitingReceiptAcknowledgement;
|
||||
}
|
||||
progress.Stage = Stage.Acknowledged;
|
||||
}
|
||||
|
||||
return RunExecute(
|
||||
record,
|
||||
residenceToken,
|
||||
inputs,
|
||||
key,
|
||||
progress,
|
||||
out receipt,
|
||||
out construction);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Re-validates authority after a failed acknowledge — the exact C3a
|
||||
/// mechanism, shared verbatim with the local-player conductor via
|
||||
/// <see cref="RuntimeFirstEntryAcknowledgement.IsStillPending"/> (C3b
|
||||
/// review M2: one body, so the abandonment fix cannot regress
|
||||
/// independently in either conductor). Full rationale on the shared
|
||||
/// helper.
|
||||
/// </summary>
|
||||
private bool IsAcknowledgementStillPending(
|
||||
RuntimeEntityRecord record,
|
||||
in RuntimeInitialCreateResidenceToken residenceToken,
|
||||
in RuntimePlacementProjectionToken expected) =>
|
||||
RuntimeFirstEntryAcknowledgement.IsStillPending(
|
||||
_residences,
|
||||
_physics.SetPosition,
|
||||
record,
|
||||
residenceToken,
|
||||
expected);
|
||||
|
||||
private RuntimeRemoteFirstEntryStatus RunExecute(
|
||||
RuntimeEntityRecord record,
|
||||
in RuntimeInitialCreateResidenceToken residenceToken,
|
||||
in RuntimeInitialCreateExecutionInputs inputs,
|
||||
RuntimeEntityKey key,
|
||||
Progress progress,
|
||||
out RuntimeInitialCreateExecutionReceipt receipt,
|
||||
out RuntimeRemoteBodyConstructionReceipt construction)
|
||||
{
|
||||
construction = default;
|
||||
RuntimeInitialCreateExecutionStatus executeStatus = _executor.Execute(
|
||||
record, residenceToken, inputs, out receipt);
|
||||
switch (executeStatus)
|
||||
{
|
||||
case RuntimeInitialCreateExecutionStatus.Completed:
|
||||
// C3b review M1: the terminal Advance is the one natural
|
||||
// consumption point — deliver the construction receipt in
|
||||
// the same call that reaps its only retained storage (this
|
||||
// progress entry). Default (no body constructed) for a
|
||||
// route that performs no SetPosition.
|
||||
construction = progress.Construction;
|
||||
_progress.Remove(key);
|
||||
return RuntimeRemoteFirstEntryStatus.Completed;
|
||||
case RuntimeInitialCreateExecutionStatus.PendingPlacement:
|
||||
return RuntimeRemoteFirstEntryStatus
|
||||
.AwaitingReceiptAcknowledgement;
|
||||
case RuntimeInitialCreateExecutionStatus.AwaitingContinuationPlacement:
|
||||
return RuntimeRemoteFirstEntryStatus
|
||||
.AwaitingContinuationPlacement;
|
||||
case RuntimeInitialCreateExecutionStatus.RejectedToken:
|
||||
_progress.Remove(key);
|
||||
return RuntimeRemoteFirstEntryStatus.RejectedToken;
|
||||
default:
|
||||
_progress.Remove(key);
|
||||
return RuntimeRemoteFirstEntryStatus.RejectedAuthority;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Drops this class's own progress entry for <paramref name="key"/>.
|
||||
/// Unlike the C3a conductor there is no publication candidate/activation
|
||||
/// to discard — the constructed body, once bound through the canonical
|
||||
/// writer, belongs to the record and is torn down by ordinary entity
|
||||
/// teardown (retail has no entry-flow rollback; the C3a carried finding
|
||||
/// applies identically here). The residence and executor own their own
|
||||
/// convergence independently.
|
||||
/// </summary>
|
||||
private void Discard(RuntimeEntityKey key) => _progress.Remove(key);
|
||||
|
||||
/// <summary>
|
||||
/// Cleanup for one key. <see cref="RuntimeEntityObjectLifetime"/> binds
|
||||
/// this into <see cref="RuntimeInitialCreateResidenceState"/>'s multicast
|
||||
/// retirement notification (alongside the executor's
|
||||
/// <c>DiscardProgress</c> and the C3a conductor's <c>Forget</c>), so any
|
||||
/// residence retirement path — delete, reset, generation replacement, a
|
||||
/// host discovering staleness — reaps this class's progress
|
||||
/// automatically, using the exact key the residence tracked internally.
|
||||
/// </summary>
|
||||
internal void Forget(RuntimeEntityKey key) => Discard(key);
|
||||
|
||||
/// <summary>
|
||||
/// Bulk cleanup wired into the same session-clear sequence
|
||||
/// (<see cref="RuntimeEntityObjectLifetime.BeginSessionClear"/>) as the
|
||||
/// executor's and the C3a conductor's own <c>DiscardAll</c> calls.
|
||||
/// </summary>
|
||||
internal void DiscardAll() => _progress.Clear();
|
||||
|
||||
internal RuntimeRemoteFirstEntryOwnershipSnapshot CaptureOwnership() =>
|
||||
new(_progress.Count);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue