Ports retail's missile Position handling into the canonical Runtime
placement owner instead of the deleted ApplyAuthoritativePosition
short-circuit. The Create/residence-window halves of the projectile
pipeline (RuntimeProjectile binding, TryBind's adopted-body branch,
the collision/shadow registration) were already canonical from prior
slices; this closes the remaining gap — how an ACCEPTED Position for
an in-flight missile is classified, placed, and presented.
Byte-decode (Step 1 hard gate, before any code was written):
CPhysicsObj::MoveOrTeleport @0x00516330-0x00516438 disassembled from
the PDB-paired binary (Capstone, x86 32-bit thiscall). `ret 0x10`
establishes four stack args; [esp+0x7c] (arg5, the velocity pointer)
is never referenced in any of the three branches (teleport/near/far).
The retail reviewer independently reproduced this by searching the
whole function body for the `24 7c` mod/rm+disp8 encoding a
`[esp+0x7c]` read would require and found zero occurrences. This
retired a fabricated `?? Vector3.Zero` fallback in the deleted method
— retail's PositionPack::UnPack initializes an absent velocity to
zero and MoveOrTeleport never installs it; the projectile's Vector
channel (RuntimeProjectilePhysicsUpdater.ApplyAuthoritativeVector)
remains the sole velocity authority for a missile. D-P5 in the
contract; the Runtime seam commits no velocity from the Position
packet at all.
The unbound-missile fix: RuntimeEntityObjectLifetime's
ClassifyRemoteAcceptedPosition now derives ProjectileAuthoritative
from a CONJUNCTIVE predicate — the Missile bit AND a bound
RuntimeProjectile whose Body is the canonical PhysicsBody — never the
bit alone. Retail places every non-player CPhysicsObj unconditionally
(there is no missile-specific placement gate in MoveOrTeleport or its
callers), so an unbindable or not-yet-bound missile taking the
ordinary remote tail is retail-faithful, not a fallback: the earlier
bit-only discriminator would have silently frozen it instead.
AP-141 records this as a deliberate, recorded divergence, not
fidelity. Retail mechanically WOULD arm a missile's ConstrainTo leash
on any nonzero MoveOrTeleport return: HandleReceivedPosition
@0x00453FD0's only kind test is player-vs-not, ConstrainTo
@0x00454272 has no kind test of its own, and CPhysicsObj::ConstrainTo
@0x00510520 creates a PositionManager on demand via
MakePositionManager @0x00510523 if one doesn't exist. acdream
deliberately does not construct that EntityPhysicsHost/
PositionManager/InterpolationManager chain for a ballistic body — the
route-5b split the C4 route 5 contract rejected — so a live missile
never shows an armed leash and never catches up via the near/
UnroutedCatchUp policy. This divergence is safe specifically because
ACE never sends UpdatePosition for a missile
(references/ACE/Source/ACE.Server/WorldObjects/WorldObject_Tick.cs:
333-334, SendUpdatePosition() commented out inside the
PhysicsState.Missile branch at :265) — every half of this row is
deterministic-test-gated only, never exercised against a real server.
AP-141 also records the surviving ConstrainTo re-anchor divergence
under clause (b): for the adopted-body case (TryBind's shared-body
branch — an ordinary remote whose Missile bit is set by a later
State packet, so it still carries a live RemoteMotion), acdream now
ports retail's teleport-branch and far-branch StopInterpolating
action (Interp.Clear()), but never re-arms or re-anchors the
inherited ConstrainTo leash the way retail's HandleReceivedPosition
@0x00454254/@0x00454272 does on every nonzero return. The risk
column's earlier wording — that a stale leash "would drag the body
toward a stale anchor" — was wrong and is retracted in this same
commit: ConstraintManager.ConstraintPos is write-only in both retail
and the port (never read by AdjustOffset), and
ConstraintManager::adjust_offset @0x00556180 only tapers or zeroes an
already-composed per-tick offset while InContact — a leash brakes
motion the interp/sticky chain already produced, it cannot pull
anything toward the anchor. The real residual is one tick of un-reset
brake accumulator, contact-gated, and it cannot move an airborne
far-snapped missile at all (the clamp branch does not run while
airborne).
NO CONNECTED GATE EXISTS for this route, by design: ACE never sends a
missile UpdatePosition (see above), so retail's own server never
exercises this code path in play. Every proof obligation here is
test-gated only — Runtime and App-level fixtures constructing the
packet directly — never a live client/server capture.
Three review rounds closed 8 MAJOR findings before this landed:
round 1 (A1 App discarded the seam's status; A2/R1 silent swallow on
an unbound missile; A3/R2 the adopted-body teleport_hook never
wired; A4/A5 zero Runtime/App test coverage); round 2 (a
ParentCellId regression introduced by round 1's own R6 finding,
which the retail reviewer retracted the following round as factually
wrong — the fix here is the REVERT to record.FullCellId, not the
relocation round 1 shipped; B2 the far-branch StopInterpolating skip
never extended to the adopted-body case; residual App/Runtime store-
path coverage; a per-packet closure contradicting the file's own
#315 cached-delegate pattern). Round 3 closed on coverage alone (no
defect): the Advance() retry arm's projectile branch — added at
round 2, semantically reordered at round 2's B5 fix (skip prediction
invalidation on a re-parked Contention, since it writes nothing) —
had never been executed by any test; two new tests drive it directly
and are sabotage-verified against both the reordering and the
retry-arm's own SyncProjectilePresentation call site. The one
recorded defect this campaign produced (the ParentCellId regression)
was caused by complying with a review finding that its own author
later retracted — the standing lesson recorded for future rounds is
that review findings are evidence to re-verify against the code, not
commands to obey unconditionally.
Complete Release suite: 11,063 passed / 4 skipped / 0 failed
(baseline 11,036 at 30d3d114, +27 new tests across this campaign).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
22 KiB
C4 route 5 — architecture / adversarial DELTA review, round 2 (2026-08-04)
Verdict: FAIL. Three findings: two MAJOR, one MAJOR-coverage. Both MAJORs are narrow and cheap to close (one expression, one call, one assertion, one test), and one of them (B2) is a new defect introduced by the fix round, in the same class as the finding it was fixing.
Round 1 raised 5 MAJOR + 6 MINOR. Eight are closed well, one is closed for the teleport half only (B1), one is closed but with a regression attached (B2), one is declined acceptably (A11). Detailed disposition in §3.
Scope: the uncommitted working tree, 1,626 insertions / 398 deletions across 10
files, against HEAD 30d3d114. Independent gates run for this review: Release
build green (0 warnings / 0 errors); focused Runtime suites 59/59; focused App
suites (ProjectileControllerTests + LiveEntityNetworkOnPositionCollapseMatrixTests)
64/64. A green suite is not evidence — every claim below is traced to source.
1. New MAJOR findings
B1 — A3's fix covers only the teleport branch; the adopted-body FAR snap still leaves the interpolation queue armed, and AP-141 now asserts the opposite as retail-faithful
Severity: MAJOR (half-closed fix + a register row asserting behaviour the code does not have — the defect class the contract lists by name).
LiveEntityNetworkUpdateController.cs:2151-2159 runs the six-action hook only
on SetPosition:
if (route.Disposition
is RuntimeAuthoritativePositionDisposition.SetPosition
&& acceptedPositionCanonical.RemoteMotion is RemoteMotion adoptedRemote)
{
RunRemoteTeleportHook(...);
}
Retail's far branch is not covered by teleport_hook — it has its own
StopInterpolating, guarded on position_manager != 0
(@0x005163C9-@0x005163CB), and the remote far arm ports it exactly
(ApplyAcceptedRemoteFarSnap: if (route.StopInterpolating) remote.Interp.Clear();).
The projectile far arm ports nothing.
For the adopted-body shape — the one A3/R2 established as real and which
this round now has a passing test for
(MissileAdoptedBody_TeleportCommit_UnConstrainsAndClearsInterpQueue) — the
missile does have a PositionManager and a populated Interp queue, so
retail's guard passes and retail would clear it.
Concrete scenario: an ordinary remote with a live Interp queue gets the
Missile bit from a State packet; TryBind's shared-body branch adopts the same
body; the next accepted Position classifies far (>=96 m), not teleport. The
projectile arm places the body at the destination and returns. The surviving
RemoteMotion is still in _spatialRemotes with a stale waypoint, and the
remote stepper drags the freshly-placed missile back toward it — the exact
symptom A3 described, on the branch the fix did not reach. The
MissileAdoptedBody_… test uses teleportSequence: 5 (teleport); there is no
adopted-body far test.
Compounding this, AP-141 (docs/architecture/retail-divergence-register.md)
now asserts the false justification unconditionally:
"The far branch's
StopInterpolatingskip is NOT part of this divergence — retail's ownposition_manager != 0guard @0x005163C9 already skips it for a never-interpolated missile, so acdream's identical skip is retail-faithful by consequence."
That justification is conditioned on "a never-interpolated missile", which the adopted-body case is not. The row therefore claims retail-faithfulness for a shape where the code is not retail-faithful. This is the recurring class the route-5 contract names in its predecessor list ("a register row asserting behaviour the code does not have") and which AP-136 was previously amended for.
Fix direction: on the SetPositionSimple arm, when
record.RemoteMotion is RemoteMotion adopted && route.StopInterpolating, clear
adopted.Interp before the placement — mirroring ApplyAcceptedRemoteFarSnap's
one line and retail's own ordering (@0x005163CB before @0x005163D9). Then narrow
AP-141's far-branch sentence to the bare-missile case it is actually true of.
Add the adopted-body far test alongside the teleport one.
B2 — R6 traded A1's wrong-cell symptom for a new one: on every STORED outcome the render entity is now positioned at the destination but parented to the cell it left
Severity: MAJOR (regression introduced by this fix round).
ProjectileController.SyncPresentationFromResolvedBody:530 changed from
record.FullCellId to:
entity.ParentCellId = runtime.Body.CellPosition.ObjCellId;
Trace the three outcome classes:
| outcome | body.Position after |
body.CellPosition.ObjCellId |
record.FullCellId |
|---|---|---|---|
| Committed | resolved destination | result cell (SnapToCell) |
result cell (CommitCanonical's SetFullCell) |
Stored (Refused/Contention/RejectedPreparation/NotApplicable) |
accepted destination, resolved in the WIRE cell's frame | source cell — StoreAcceptedDestinationPose never writes the cell |
wire cell — the merge's RefreshDerivedState → SetFullCell |
Deferred/RejectedByPlacement |
ack does not run (A1 gate) | — | — |
On a committed outcome the two sources are identical, so R6 is a no-op there —
and MissileTeleportCommit_… asserts both, confirming the equality rather than
discriminating between the sources.
On a stored outcome they diverge, and R6 picked the wrong one.
StoreAcceptedDestinationPose:1123-1132 computes
accepted.PositionX + worldOffset(accepted.LandblockId) — the world point of
the wire cell-local coordinates — and record.FullCellId is that same wire
cell. So record.FullCellId is the cell the stored position belongs to;
body.CellPosition.ObjCellId is the cell the body left.
The inconsistency is visible inside this fix round: on the same stored packet,
Runtime's own SyncProjectilePresentation:1043-1051 publishes the shadow row
with record.FullCellId (wire cell) paired with the new position, while App
publishes the render entity with the source cell paired with the same new
position. Two presentation surfaces, two different cells, one body.
Concrete failure: a missile crosses a landblock boundary at the streaming edge;
the destination landblock is not yet in the collision service window, so
CanAttemptDestination refuses. The body stores to the destination. The render
entity is placed there but entity.ParentCellId is the source cell —
and ParentCellId is precisely what
RetailPViewRenderer.cs:925/945 uses for the indoor/outdoor stage split and
viewcone.SphereVisibleInCell(e.ParentCellId!.Value, …). The arrow is
visibility-tested against a cell it is no longer in: culled, or drawn in the
wrong stage.
The R6 doc comment's own retail citation argues against its choice — it states
that retail's store_position @0x00515CE2 "writes the object's whole
Position including objcell_id". If retail's store wrote the cell, the
body's cell after a store would be the wire cell; record.FullCellId is the
faithful stand-in for that, and the body's stale cell is the acdream residual
(AP-138), not the truth.
Fix direction: revert ParentCellId to record.FullCellId (round 1's
value) and keep the A1 status gate, which is what actually fixed the no-op
case. That pairing is correct on all three outcome classes and agrees with
Runtime's own shadow publication. If the body's stale cell is considered the
truth, then StoreAcceptedDestinationPose must write the cell too — but that is
AP-138 scope and would change the remote arms as well.
B3 — the one branch where SyncProjectilePresentation is the sole writer — spatial+visible on the STORE path — is still unasserted at both layers, and it is exactly where B2 and the surviving A1 sabotage hide
Severity: MAJOR (coverage; one assertion + one test wide).
I independently reproduced the implementer's "2 of 5" trace and confirm it:
| new test | discriminates a gutted SyncProjectilePresentation? |
why |
|---|---|---|
TeleportCommit_…ForceEndsCollision… (shadow assertion) |
No | the shared pipeline's ShadowObjects.CommitSetPosition publishes the same row |
FarCommit_… (shadow assertion) |
No | same |
TeleportCommit_ReenteringWorldReactivatesBody |
No | RuntimeSetPositionState.cs:2983-2985 sets EnteringWorldFromCelllessResidence |= !body.InWorld || FullCellId == 0 at prepare time (before SnapToCell), and :4979-4984 then sets Active + LastUpdateTime itself |
TeleportCommit_HiddenSuspendsShadowStaysInWorld |
Yes | nothing else suspends on Hidden |
Refused_NonSpatialDeactivatesAndSuspends |
Yes | the Refused path never reaches the engine |
The implementer's trace is accurate and was reported honestly. But the table
also shows why only 2 discriminate: on the committed path the shared pipeline
independently produces the same observable, so SyncProjectilePresentation's
spatial+visible branch is only load-bearing on the store path — and no test
asserts it there. Refused_StillAdvancesPoseNoParkPredictionInvalidated is
spatial+visible+Refused and asserts body.Position, prediction,
body.InWorld (vacuous — AttachBody's SnapToCell already set it) and
ObjectClock.IsActive, but not the shadow row.
The same hole exists at the App layer: the collapse matrix now has teleport commit, far commit, near no-op, airborne no-op, null classification, unbound fall-through, and adopted-body teleport — but no store/refused scenario. That is why the reported A1 sabotage survived: with R6 reading the body's own cell, an unconditional ack on a no-op outcome writes nothing observable (position and cell both unchanged), so the gate is invisible on the no-op path. It is visible on the store path — which is untested.
Both gaps are cheap:
- Runtime: add
Assert.Equal(body.Position, shadowEntry.Position)(using the file's existingAllEntriesForDebugpattern) toRefused_StillAdvancesPoseNoParkPredictionInvalidated. Gutting the sync then leaves the shadow at the spawn pose and the test fails. - App: add a
MissileFarRefused_…scenario to the collapse matrix. The fixture already supports it with no new machinery —PublishDestinationCollision()andServiceWindow.Allow(DestinationLandblock)are separate opt-in calls, so simply omittingAllowyieldsRefusedfromCanAttemptDestination. Assertentity.Position == body.Positionandentity.ParentCellIdagainst the wire cell. That single test catches B2 and gives the A1 gate a discriminating home.
2. New MINOR findings
B4 — the App hook call allocates a per-packet closure, contradicting its own #315-pattern claim
LiveEntityNetworkUpdateController.cs:2155-2158 passes
() => _liveEntities.IsCurrentPositionAuthority(acceptedPositionRecord, acceptedPositionAuthorityVersion) —
a fresh display class + delegate on every teleport-classified adopted-body
missile packet. The comment immediately above claims it uses "the SAME ordered
hook seam and per-packet currency check the remote teleport arm already uses
(RunRemoteTeleportHook, #315 pattern)". The remote arm's #315 pattern is
precisely the opposite: _remoteArmCallbacks cached delegates over scratch
fields (:1474-1492), introduced by the collapse's second commit to remove
per-packet closures from this exact method. D-P4 also states the wiring must be
"without per-packet closures". Narrow reach (SetPosition + RemoteMotion
present), so MINOR — but the comment asserts compliance the code does not have.
B5 — the retained-retry arm invalidates prediction on an outcome that writes nothing
RuntimeRemotePlacementDriveController.Advance:1265-1268: the else branch calls
pendingProjectile?.InvalidatePrediction() and then SubmitAndResolve. When
SubmitAndResolve returns Contention (retryable → re-parked into _pending),
Advance performs no StoreAcceptedDestinationPose — so nothing was
written, yet the prediction version advanced. Contract invariant 4 pins
"prediction invalidation accompanies every body write on this route… The no-op
dispositions invalidate nothing." Effect is one aborted in-flight quantum per
re-park; small, but it is the invariant's stated shape violated on one arm only.
The entry point does not have this problem (Contention there always stores).
3. Round-1 findings — disposition
| # | status | verification |
|---|---|---|
| A1 (unconditional ack) | CLOSED, gate correct — but see B2 | :2170-2179 gates on placementStatus is not null and not Deferred and not RejectedByPlacement, an exact set-equality mirror of the Runtime gate (:975-983 plus the null returns). Verified by enumerating every RuntimeRemotePlacementExecutionStatus producer. |
| A2 (Missile bit without a bound projectile) | CLOSED, well | The discriminator became conjunctive at the classifier (RuntimeEntityObjectLifetime.cs:673-677) with the App's null-route fallback mirroring it exactly (:2122-2131). Retail justification is sound and better than my suggested fix: retail places every non-player object unconditionally, so an unbindable missile taking the ordinary remote path is the faithful behaviour, not a fallback. Blast radius enumerated below. MissileUnbound_FallsThroughToRemoteTail_TracksInsteadOfFreezing is discriminating (revert the conjunct and the body never moves). |
| A3 (teleport hook) | PARTIALLY closed — see B1 | Teleport branch wired through the existing RemoteTeleportHook bundle with a real per-packet currency guard; the adopted-body test is genuinely discriminating (removing the call leaves Constraint.IsConstrained true and Interp.IsActive true). Far branch not covered. |
| A4 (sync untested) | PARTIALLY closed — see B3 | 2 of 5 new tests discriminate; the store-path spatial+visible branch remains unasserted. |
| A5 (no App missile coverage) | CLOSED, well | 7 tests on the shared collapse fixture. Fixture-regression check below. |
A6 (wasInWorld read after placement) |
CLOSED (behaviourally inert, correctly so) | wasInWorld is now captured before dispatch at both call sites (entry point :942, retry :1226) and threaded as a parameter. Note it has no observable effect: on commit, EnteringWorldFromCelllessResidence already re-activates from the same pre-SnapToCell condition; on store, body.InWorld is unchanged so before == after. Correct and trap-removing either way. |
A7 (hidden LastUpdateTime) |
CLOSED | Restored at :1055. Clock basis verified equivalent: UpdateFrameOrchestrator.CurrentScriptTime => _runtime.SimulationTimeSeconds, i.e. _physicsScriptGameTime and _clock.SimulationTimeSeconds are the same clock, so no mixed-basis write into body.LastUpdateTime. |
| A8 (silent shadow skip) | CLOSED | else { physics.ThrowIfWorldFrameUnreachable(record.FullCellId); } at :1053. Verified non-throwing during the legitimate pre-Create window (RuntimePhysicsState.cs:596-606 returns early unless the local-player Create was observed with a zero frame). |
| A9 (local player not fenced) | CLOSED | update.Guid != _playerServerGuid && added to the fallback (:2125). |
A10 (OwnsFarSnap doc) |
CLOSED | Doc-only change; the predicate body is byte-identical (verified — the diff adds only comment lines). No effect on the far arm the collapse and 4b-2 gated. |
| A11 (stale cutover inventory) | DECLINED — acceptable | docs/research/2026-08-02-cutover-route-inventory.md is a dated research/planning record, and the project's documentation rules treat those as historical. D-P7's grep was scoped to src/ and docs/architecture/, both of which are clean. Reasonable call; the risk is a future reader treating a dated inventory as current, which the date already signals. |
_lastFiniteGameTime |
CLOSED | SyncPresentationFromResolvedBody:521-522 restores the rebase under double.IsFinite, matching the deleted method's semantics (it also skipped the assignment on a non-finite clock). |
A2's fix — blast radius, enumerated
Everything that now depends on the conjunctive kind derivation, and its status:
RuntimeAcceptedPositionRouteRequests.TryBuild→RuntimeAuthoritativePositionRouteClassifier— re-verified that the classifier's only kind branch pastValidEntityKindisLocalPlayer, soProjectileandRemoteremain disposition-, flag-,StopInterpolating- andConstrainPhase-identical. Kind changes theOperationKindonly.RuntimeRemotePlacementDriveController.OwnsPlacement— admits both; single production reader (TryExecuteAcceptedRemotePosition:640).RuntimeRemoteFarSnapPosition.OwnsFarSnap/RuntimeRemoteTeleportPosition.OwnsTeleportPlacement/ApplyAcceptedRemoteFarSnap/ApplyAcceptedRemoteTeleport/TryArmConstraintAfterOperation— stillRemoteAuthoritative-gated. An unbound missile now legitimately reaches them (it is classifiedRemote), which is the intended pre-route-5 behaviour; a bound one never can. The throwing guards stay unreachable for projectiles.- App
isMissilePacket— derived fromroute.OperationKindwhenever a route exists, so it cannot disagree with the classifier; the null-route arm restates the identical conjunct. - Kind is now derived from mutable binding state, so it can flip between packets without the Missile bit changing. The one place that stores a kind across packets is
_pending[key].Route;Advancere-validatespending.Record.Projectileand body identity before syncing (:1214-1224), andTryPrepareAndSubmitAuthoredPlacementcomparesroute.OperationKindagainst theoperation.Kindstamped at the same Begin. Consistent — this was handled deliberately (R3), not by luck. - First-entry admission, the residence Create half, and the continuation executor all pass explicit kinds and are untouched.
I found no dependent that broke.
A5's fix — did the shared fixture regress for its original dual-guid purpose?
No. Verified line by line:
- The remote construction is moved verbatim into an
elsebranch; theRemoteMotion+Shadows.Register(… Cylinder, cylHeight: 1.835f …)block is byte-identical to the original. baseStateevaluates to exactlyPhysicsStateFlags.ReportCollisionswhenisMissile: false, so bothRawStateandPhysicsStateare unchanged for every pre-existing test (all of which use the defaultisMissile: false).Remotestaysnull!for missile fixtures; no missile test dereferences it. The two tests that do usefixture.Remote(MissileUnbound_…,MissileAdoptedBody_…) both construct withisMissile: false— deliberately, because both scenarios are remote-shaped records that acquire the Missile bit later. That is the right modelling.- The new
Projectileproperty is additive. - All pre-existing dual-guid tests in the file pass in the 64/64 run.
MissileAdoptedBody_… is worth calling out as a genuinely good test: it builds
the adopted-body shape through the production seam (BindProjectile over the
record's canonical body, which SetRemoteMotion had already adopted from the
component), and its two discriminating assertions (Constraint.IsConstrained,
Interp.IsActive) fail if the hook call is removed.
4. The two reported residuals — judgment
Residual 1 — A1's fix has no App-level regression test
Judgment: the stated gap is acceptable; the surrounding gap is not.
The narrow claim is correct and I verified it: Deferred requires a
DeferredCell park (collision-generation quiescence) and RejectedByPlacement
requires the engine's own sweep to refuse, and no sibling remote test in that
file constructs either — the fixture has no machinery for it. The gate is a
literal set-equality mirror of a Runtime gate whose behaviour is exercised by
Refused/no-op/rejected tests, so code review is a defensible verification for
those two statuses specifically.
But the reason the sabotage survived is not that Deferred is unreachable — it
is that R6 made the ack idempotent on the no-op path, so the gate has no
observable effect on any scenario the matrix currently contains. The status the
gate matters for that is trivially constructible is the store path, and the
fixture already supports it (omit ServiceWindow.Allow). That test is required
(B3), and it also catches B2. So: accept the Deferred/RejectedByPlacement
carve-out, reject the absence of any store-path App scenario.
Residual 2 — only 2 of 5 A4 tests discriminate
Judgment: 2 is not sufficient for invariant 2, but the shortfall is one assertion, not a suite.
The trace is accurate (I reproduced all five verdicts independently, including
the non-obvious EnteringWorldFromCelllessResidence mechanism behind the
reactivation test). Keeping the redundant assertions as correct facts is the
right call — they are true, cheap, and they pin the shared pipeline's
contribution.
The shortfall is specific: the spatial+visible branch is redundant only on the
committed path. On the store path SyncProjectilePresentation is the sole
writer of the shadow row, and no test asserts it. Invariant 2 is "#312's layer"
— an entity left rendered a packet behind its body — and the store path is
exactly the outcome class where that can happen without the shared pipeline
noticing. One Assert.Equal(body.Position, shadowEntry.Position) in the
existing Refused_… test converts the third branch from redundant to
discriminating and closes invariant 2 at the Runtime layer. Combined with the
App store scenario from B3, invariant 2 is then covered at both layers by
discriminating assertions.
5. What would make this PASS
- B2:
entity.ParentCellId = record.FullCellId(keep the A1 gate). One expression. - B1: clear the adopted
Interpqueue on theSetPositionSimplearm whenroute.StopInterpolating; narrow AP-141's far-branch sentence to the bare-missile case. One call + one clause + one test. - B3: one shadow assertion in
Refused_StillAdvancesPoseNoParkPredictionInvalidated; oneMissileFarRefused_…App scenario asserting position andParentCellId(which is the B2 regression test). - B4/B5: cached delegate for the hook currency check; move the retry arm's
InvalidatePredictionto the paths that actually write.
Nothing here needs new machinery, a new fixture, or a design change.