fix(physics): C4 route 3 — portal placement authority (local player)
Removes a duplicate placement authority for local-player portal arrival. Portalling worked before this change and works after it — this is not a bug fix, EXCEPT that it found and fixed one dead-code production bug. THE PRODUCTION BUG: TryExecuteCanonicalPortalPlacement re-read the accepted destination at Place time, but TryBeginPortalReveal already consumes that slot at Aim time — so the arm was 100% dead code and every real portal Place refused with host-token-unavailable. Found only because we refused to accept 7 skipped tests instead of chasing the count to zero. RETAIL IS THE GENERIC PATH FOR THE THIRD ROUTE RUNNING: SmartBox::TeleportPlayer @0x00453910 = SetPositionSimple(dest, 1) with flags 0x1012, followed by PlayerPositionUpdated. BOTH INVERSIONS, WITH THEIR ANCHORS: unlike route 2, the leash IS armed here (ConstrainTo @0x0045418A) and velocity is zeroed (set_velocity @0x004541B4); unlike route 4b-3, the local teleport_hook runs AFTER placement (@0x004538AE). THE THREE-ROUND DEFECT CHAIN, HONESTLY: - Round 1 released the player at the pre-teleport position while the anim stream marched on — the contract wrongly assumed Place re-fires (process rule 1's third occurrence this campaign). - Round 2's fix inferred commit from a global PendingCount, which three non-committing paths also clear — making the SAME bug complete cleanly and silently. Strictly worse than round 1: round 1 at least tripped portal-complete-before-materialized. - Round 3 latches the commit where it actually happens (ReconcileAndAcknowledgePortal), keyed on reveal generation and teleport sequence, via TryConsumePortalCommit. Two of the three required regression tests landed and are sabotage-verified on both hosts (ParkedPlace_ForgottenByOrdinaryMergeDoesNotLatchAsCommitted / HeadlessPortalPrepareDestinationForgottenByOrdinaryMergeDoesNotLatchAsCommitted). The third (force-arm-takes-the-slot) was judged unnecessary on review: with the inference gone, PendingCount is only a "don't ask yet" guard at both gates, so a force operation occupying or vacating the slot no longer changes an input the commit decision reads — the case collapses into what the landed test already discriminates. THE B2/P3 RESOLUTION: both round-2 reviews were right about different branches of the same synchronous call. RuntimePlacementProjectionSubscription .OnPlacement acknowledges the FIFO head only when TryApply returns true; a Place whose portal authority went stale (transit ended/superseded while parked) used to return false, wedging every later entity's placement receipt behind it forever. Both sinks (RuntimePlacementPresentationSink, HeadlessRuntimePlacementProjectionSink) now acknowledge-and-ignore a stale-authority Place instead of refusing it. The regression test (RuntimePlacementPresentationSinkTests .PortalPlace_StaleTransitHostOrSequenceIsAcknowledgedAndIgnored) had been asserting the old, wrong `false` behaviour; it now asserts and sabotage-verifies the fix. Also lands: AP-144 (register discipline — the portal movement-event send reuses the stricter UsePositionFromServer gate where retail's SendMovementEvent is the looser autonomy_level != 0 test, diverging only at level 1, currently unreachable), AP-145 + issue #318 (the local-player collision-shadow presentation write bypasses its own publisher's ShadowObjects write via a direct cache .Set(), self-healing only once dedup diverges — filed, not fixed, pending a composition test), AD-42 deleted (its last citation retired by the canonical portal arm), AD-2 updated (the wait-cue's trigger predicate now covers a second cause), and two documentation corrections: the enter_world misattribution (both call sites are in SmartBox::HandleCreateObject, only one in the player branch — portal arrival is TeleportPlayer, not enter_world) and the stale "local player never reaches this path" comment on the generic-remote-render-pose write. Suite: 11,090 passed / 4 skipped / 0 failed. No new skips, nothing weakened. STILL OWED: the connected two-client gate, with ACDREAM_PROBE_LOCAL_TELEPORT=1, scored only if [local-tp] lines actually appear in the capture — and explicitly NOT scored as covering issue #318 (no composition test yet asserts PhysicsEngine.ShadowObjects directly). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
cd3129e9d6
commit
e0f96a55bf
24 changed files with 5261 additions and 243 deletions
|
|
@ -206,25 +206,56 @@ which confirms that worker completion alone is not draw readiness.
|
|||
|
||||
### 2.1. Destination placement enters the spatial cell before simulation resumes
|
||||
|
||||
> **2026-08-04 correction (C4 route 3, D-T9), itself corrected 2026-08-05
|
||||
> (R6 retail review):** the listing below attributes portal arrival to
|
||||
> `player.enter_world(destination)`. That is wrong — a caller sweep of the
|
||||
> named retail decomp
|
||||
> (`docs/research/named-retail/acclient_2013_pseudo_c.txt:93770-93828`) shows
|
||||
> both `CPhysicsObj::enter_world` call sites (pseudo-C `:93797` @0x004550EC
|
||||
> and `:93824` @0x00455095) living inside **`SmartBox::HandleCreateObject`
|
||||
> @0x00454C80** — `CObjectMaint::CreateObject` @0x00454FD8 is merely a
|
||||
> *callee* it invokes partway through, not the enclosing function the first
|
||||
> correction pass named. The two call sites are also **not both in the
|
||||
> player branch**: @0x004550EC sits in the `if (arg3 != this->player_id)`
|
||||
> NON-player branch (`PhysicsDesc::get_position` → `enter_world` for a
|
||||
> newly-created REMOTE object); only @0x00455095 sits in the player branch,
|
||||
> after `SmartBox::init_player` + `CellManager::ChangePosition`. Both sites
|
||||
> are the LOGIN/CreateObject path that creates a physics object for the
|
||||
> first time — neither is portal arrival. Portal arrival is
|
||||
> `SmartBox::TeleportPlayer` (`0x00453910`) → `CPhysicsObj::SetPositionSimple`
|
||||
> (`0x00453924`/`0x005162B0`) — confirmed by C4 route 3's own §1 citations
|
||||
> and grep at `acclient_2013_pseudo_c.txt:92514-92521`. The conclusion below
|
||||
> (commit the cell before releasing simulation) is unaffected —
|
||||
> `SetPositionSimple` reaches the identical `change_cell`/`update_object`
|
||||
> machinery this section describes — only the entry-point name and
|
||||
> pseudocode's `enter_world` call are wrong; read `SetPositionSimple(destination)`
|
||||
> wherever this section says `enter_world(destination)`.
|
||||
>
|
||||
> This routing is `SmartBox::TeleportPlayer` → `SetPositionSimple`
|
||||
> everywhere; nothing in the passages below distinguishes retail's specific
|
||||
> Recall/Lifestone/GM-teleport CAUSES, since they all funnel through the same
|
||||
> accepted-destination Position at this layer.
|
||||
|
||||
Named retail references:
|
||||
|
||||
- `CPhysicsObj::change_cell` at `0x00513390`
|
||||
- `CPhysicsObj::update_object` at `0x00515D10`
|
||||
- `CPhysicsObj::enter_world` at `0x00516170`
|
||||
- `SmartBox::TeleportPlayer` at `0x00453910`
|
||||
- `CPhysicsObj::SetPositionSimple` at `0x005162B0`
|
||||
- `CPhysicsObj::prepare_to_enter_world` at `0x00511FA0`
|
||||
- `CPhysicsObj::set_hidden` at `0x00514C60`
|
||||
|
||||
Retail does not separate an accepted destination Position from the object's
|
||||
live cell pointer. `enter_world` runs `SetPosition`, which installs the object
|
||||
in its destination `CObjCell`, before the PartArray and MovementManager
|
||||
enter-world boundaries complete. `update_object` then rejects only a parented
|
||||
object, a null `cell`, or a Frozen object; Hidden is not a reason to skip the
|
||||
live cell pointer. `SetPositionSimple` installs the object in its destination
|
||||
`CObjCell`, before the PartArray and MovementManager enter-world boundaries
|
||||
complete. `update_object` then rejects only a parented object, a null `cell`,
|
||||
or a Frozen object; Hidden is not a reason to skip the
|
||||
ScriptManager/ParticleManager tail.
|
||||
|
||||
```text
|
||||
accepted portal destination becomes ready:
|
||||
player.enter_world(destination)
|
||||
SetPosition(destination)
|
||||
SmartBox.TeleportPlayer(destination)
|
||||
SetPositionSimple(destination)
|
||||
change_cell(destination CObjCell)
|
||||
PartArray.HandleEnterWorld()
|
||||
MovementManager.HandleEnterWorld()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue