feat(runtime): bridge executor completion to the placement stream
Cutover slice C0 (docs/plans/2026-08-02-placement-cutover.md): the seam work that lets C3 flip hosts onto a complete receipt stream instead of growing one mid-cutover. The executor's Released exit now publishes an acknowledge-only ExecutorCompleted receipt through the one placement projection stream — registered before observer dispatch, correlated to the full execution receipt, reaped exactly once on acknowledgement/ discard/session-clear, and counted in the convergence ledger. All three production placement sinks acknowledge-and-ignore the new kind via early returns proven behavior-preserving for every existing kind; without them the first such receipt at cutover would permanently wedge the exact-head FIFO behind sinks that return false. Provably inert today: the publisher has no production caller. Execute's live inputs now derive from Runtime's own owners bound at GameRuntime construction: UsePositionFromServer is retail's exact autonomy_level != 2 (CommandInterpreter::UsePositionFromServer 0x006B3B40, startup-only knob), and PlayerDistance uses the live movement controller's position with a null-safe fallback to the caller struct — never a fabricated origin. TryPrepareAndSubmitAuthoredPlacement chains the prepared-collision Setup read through PrepareMover to submission with zero validation-semantics changes. TryCommitParent and CommitWithdrawal gain the sibling cancellation flow (residence + ordinary placement family); TryCommitParent deliberately omits LeaveWorld — retail's set_parent performs its single gated leave_world (0x00515A90) and a second would have no counterpart. Not fully dormant: the two cancellation fixes change Runtime paths production already calls (today as no-op-adjacent hardening, since nothing upstream begins a residence yet); everything else is reachable only by tests. Reviewed: retail-conformance PASS + architecture/ adversarial PASS after one fix round (sink wedge, completion-receipt lifecycle, null-controller distance). Runtime 921/921; complete Release solution 10,716 passed / 4 intentional skips. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
27e05b99e4
commit
67f63e85e5
14 changed files with 1639 additions and 11 deletions
|
|
@ -18,7 +18,9 @@ public readonly record struct RuntimeCharacterOwnershipSnapshot(
|
|||
int PositionCount,
|
||||
int PropertyCount,
|
||||
bool OptionsAreDefaults,
|
||||
bool MovementSkillsAreReset)
|
||||
bool MovementSkillsAreReset,
|
||||
/// <summary>C0-2: <see cref="RuntimeCharacterState.AutonomyLevel"/> is back at retail's default (<see cref="RuntimeCharacterState.FullAutonomyLevel"/>).</summary>
|
||||
bool AutonomyIsDefault = true)
|
||||
{
|
||||
public bool IsConverged =>
|
||||
IsDisposed
|
||||
|
|
@ -33,7 +35,8 @@ public readonly record struct RuntimeCharacterOwnershipSnapshot(
|
|||
&& PositionCount == 0
|
||||
&& PropertyCount == 0
|
||||
&& OptionsAreDefaults
|
||||
&& MovementSkillsAreReset;
|
||||
&& MovementSkillsAreReset
|
||||
&& AutonomyIsDefault;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -47,11 +50,27 @@ public sealed class RuntimeCharacterState : IDisposable
|
|||
public const uint RunSkillId = 24u;
|
||||
/// <summary>ACE Skill enum ordinal for Jump (K-fix7 / pseudocode doc §5).</summary>
|
||||
public const uint JumpSkillId = 22u;
|
||||
/// <summary>
|
||||
/// C0-2/F5(b): retail <c>CommandInterpreter</c>'s own default, set at
|
||||
/// construction (pseudo-C 699752, <c>this->autonomy_level = 2;</c>, a
|
||||
/// direct field write, not a <c>SetAutonomyLevel</c> call) and by the
|
||||
/// command-line-only override at admission
|
||||
/// (<c>command_line_autonomy_level</c>, pseudo-C 1088429, itself
|
||||
/// defaulting to <c>0x2</c>). Exactly ONE retail caller of
|
||||
/// <c>CommandInterpreter::SetAutonomyLevel</c> exists in the named
|
||||
/// retail decomp - the startup construction path at pseudo-C 94102
|
||||
/// (<c>cmdinterp->vtable->SetAutonomyLevel(cmdinterp, command_line_autonomy_level)</c>)
|
||||
/// - it is a startup/debug knob, not a per-play-session gameplay
|
||||
/// toggle, so acdream's own default matches retail's value exactly and
|
||||
/// nothing in ordinary play ever changes it.
|
||||
/// </summary>
|
||||
public const uint FullAutonomyLevel = 2u;
|
||||
|
||||
private bool _disposed;
|
||||
private long _characterRevision;
|
||||
private long _spellbookRevision;
|
||||
private bool _internalSubscriptionsAttached;
|
||||
private uint _autonomyLevel = FullAutonomyLevel;
|
||||
|
||||
/// <summary>
|
||||
/// Campaign P Slice P1 (2026-07-30): the pre-<c>EnchantSkill</c> base
|
||||
|
|
@ -88,6 +107,39 @@ public sealed class RuntimeCharacterState : IDisposable
|
|||
public IRuntimeCharacterView View { get; }
|
||||
public bool IsDisposed => _disposed;
|
||||
|
||||
/// <summary>Retail <c>CommandInterpreter::GetAutonomyLevel</c>.</summary>
|
||||
public uint AutonomyLevel => Volatile.Read(ref _autonomyLevel);
|
||||
|
||||
/// <summary>
|
||||
/// C0-2: retail <c>CommandInterpreter::UsePositionFromServer</c>
|
||||
/// (pseudo-C 699506-699512: <c>result = this->autonomy_level != 2;</c>).
|
||||
/// This is the source
|
||||
/// <see cref="AcDream.Runtime.Entities.RuntimeInitialCreateContinuationExecutor.BindLiveInputs"/>
|
||||
/// binds for <c>RuntimeInitialCreateExecutionInputs.UsePositionFromServer</c>
|
||||
/// - the local-player-only interpolate gate consumed by
|
||||
/// <c>RuntimeAuthoritativePositionRouteClassifier.ClassifyAcceptedPosition</c>.
|
||||
/// </summary>
|
||||
public bool UsePositionFromServer => AutonomyLevel != FullAutonomyLevel;
|
||||
|
||||
/// <summary>
|
||||
/// Retail <c>CommandInterpreter::SetAutonomyLevel</c> (pseudo-C
|
||||
/// 699542-699552): rejects any value above 2, otherwise commits.
|
||||
/// F5(a): retail's own setter ALSO sends <c>SendAutonomyLevelEvent</c>
|
||||
/// (pseudo-C 699550) after committing - this Runtime-only port has no
|
||||
/// outbound wire concept to carry that event today (autonomy level has
|
||||
/// no host caller yet). Any FUTURE host exposure of this setter (e.g. a
|
||||
/// debug/admin command) MUST also send the equivalent outbound event -
|
||||
/// do not port only the field write.
|
||||
/// </summary>
|
||||
public bool TrySetAutonomyLevel(uint level)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
if (level > FullAutonomyLevel)
|
||||
return false;
|
||||
Volatile.Write(ref _autonomyLevel, level);
|
||||
return true;
|
||||
}
|
||||
|
||||
public RuntimeCharacterOwnershipSnapshot CaptureOwnership()
|
||||
{
|
||||
int favoriteCount = 0;
|
||||
|
|
@ -144,7 +196,8 @@ public sealed class RuntimeCharacterState : IDisposable
|
|||
&& MovementSkills.LastPkAttackTimestamp is null
|
||||
&& _runSkillBase == -1
|
||||
&& _jumpSkillBase == -1
|
||||
&& _movementSkillAugmentations == default);
|
||||
&& _movementSkillAugmentations == default,
|
||||
AutonomyLevel == FullAutonomyLevel);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -355,6 +408,7 @@ public sealed class RuntimeCharacterState : IDisposable
|
|||
_runSkillBase = -1;
|
||||
_jumpSkillBase = -1;
|
||||
_movementSkillAugmentations = default;
|
||||
Volatile.Write(ref _autonomyLevel, FullAutonomyLevel);
|
||||
Try(MovementSkills.ResetSession, ref failures);
|
||||
if (failures is not null)
|
||||
{
|
||||
|
|
@ -380,6 +434,7 @@ public sealed class RuntimeCharacterState : IDisposable
|
|||
_runSkillBase = -1;
|
||||
_jumpSkillBase = -1;
|
||||
_movementSkillAugmentations = default;
|
||||
Volatile.Write(ref _autonomyLevel, FullAutonomyLevel);
|
||||
Try(MovementSkills.ResetSession, ref failures);
|
||||
}
|
||||
finally
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue