fix(inventory): reset a split result's movement timestamps so recovery cannot throw (#314)
Found by C4 route 6's integration tests (1b484937) and split out of that zero-production closure per the standing split-on-discovery rule. PendingSplitToWorldProjection.BuildSpawn zeroes the top-level MovementSequence/ServerControlSequence, but its Physics.Timestamps `with` block overrode only Position/Teleport/ForcePosition/Instance — leaving Timestamps.Movement and .ServerControlledMove at the SOURCE item's values. RuntimeEntityObjectLifetime.HasConsistentCreateIdentityAndParent requires the PhysicsDesc timestamps and their flattened projections to agree, so the synthetic spawn failed the predicate and TryRecoverUnknownPosition threw `CreateObject 0x… has inconsistent instance or parent projections` instead of completing the canonical create-placement transaction. Reachable in ordinary play: retail's per-object update_times channels are monotonic and do not reset when an item re-enters a container, so any item that ever had world presence — dropped once, picked back up, then split — carries nonzero values in exactly those two fields. The split pile then never appears. Fix is the honest value, not a placation of the predicate: a fresh split GUID has no movement history by construction, so both channels are zero in both projections. Deliberately NOT fixed by loosening HasConsistentCreateIdentityAndParent — the predicate was right and the producer was wrong. The route-6 test that documented the throw (SplitSourceWithRetainedMovementTimestamps_ThrowsInsteadOfRecovering) is renamed to …_StillRecovers and now pins the fix. It asserts more than "no throw": the result's movement channels must be ZERO in both projections, so the test cannot pass against a lenient-predicate workaround. Sabotage-verified in both directions — restoring the old BuildSpawn reproduces the exact original InvalidOperationException. Notable for the campaign record: this is a crash in the precise mechanism route 6's scoping cited as EVIDENCE that drops already converge on the canonical transaction. Reading the code said the path converges; driving it said it throws. The zero-production route was still correct — and building its tests anyway is what found this. Complete Release suite 11,020 passed / 4 skipped / 0 failed, unchanged from1b484937(the test flipped its assertion rather than being added). Neither known flake fired. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
1b484937b6
commit
daef7c9835
2 changed files with 59 additions and 16 deletions
|
|
@ -179,12 +179,28 @@ internal sealed class PendingSplitToWorldProjection
|
|||
Position = update.Position,
|
||||
Parent = null,
|
||||
Velocity = update.Velocity,
|
||||
// #314: every timestamp this record projects to the flattened
|
||||
// spawn fields below MUST be reset here in the same breath.
|
||||
// HasConsistentCreateIdentityAndParent
|
||||
// (RuntimeEntityObjectLifetime) rejects a create whose
|
||||
// PhysicsDesc timestamps disagree with their flattened
|
||||
// projections, and the `source with { … }` below zeroes
|
||||
// MovementSequence/ServerControlSequence. Omitting the
|
||||
// matching Movement/ServerControlledMove resets here left a
|
||||
// split whose SOURCE carried nonzero movement stamps — any
|
||||
// item dropped once, picked back up, and split again —
|
||||
// failing that predicate and throwing instead of completing
|
||||
// the canonical create-placement transaction. A fresh split
|
||||
// GUID has no movement history by construction, so zero is
|
||||
// the honest value, not a placation of the predicate.
|
||||
Timestamps = sourcePhysics.Timestamps with
|
||||
{
|
||||
Position = update.PositionSequence,
|
||||
Teleport = update.TeleportSequence,
|
||||
ForcePosition = update.ForcePositionSequence,
|
||||
Instance = update.InstanceSequence,
|
||||
Movement = 0,
|
||||
ServerControlledMove = 0,
|
||||
},
|
||||
}
|
||||
: null;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue