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>
515 lines
26 KiB
Markdown
515 lines
26 KiB
Markdown
# C4 route 3 — retail-conformance review (2026-08-04)
|
|
|
|
**Verdict: FAIL.**
|
|
|
|
Reviewer scope: the uncommitted working-tree diff at HEAD `cd3129e9`
|
|
(`git diff HEAD` + untracked), branch
|
|
`claude/acdream-physics-divergence-5aa784`. Review only — no edits made.
|
|
|
|
The retail *reading* in this slice is excellent. Every §1 claim in the
|
|
pinned contract reproduces line-for-line in
|
|
`docs/research/named-retail/acclient_2013_pseudo_c.txt` (§A below), both
|
|
inversions are implemented in the right direction, the D-T3 duty map is
|
|
complete, and the implementer's P1 finding on TransientState is not just
|
|
correct — it retires a real pre-existing divergence.
|
|
|
|
The slice fails on **what happens when the placement does not commit**.
|
|
`TeleportAnimEvent.Place` is one-shot, so every non-`Committed` outcome
|
|
silently skips the placement, the presentation suffix, and the
|
|
materialization acknowledgement while the animation stream marches on to
|
|
reveal the world and fire LoginComplete anyway. The headless host has the
|
|
same hole with the extra property that it *asserts a materialization that
|
|
did not happen*. Neither is covered by a test, because the App-layer
|
|
presentation suite the contract made mandatory (§8 items 8/9/10, closing
|
|
route 2's B2 gap) was not written — the existing App assertions were
|
|
weakened instead. And the one probe field the connected gate keys on
|
|
(`leash=armed`) can never be true as coded.
|
|
|
|
---
|
|
|
|
## Findings
|
|
|
|
### R1 — MAJOR — `TeleportAnimEvent.Place` is one-shot; there is no re-attempt driver, and the reveal completes anyway
|
|
|
|
`src/AcDream.Core/World/TeleportAnimSequencer.cs:136-141`
|
|
|
|
```csharp
|
|
case TeleportAnimState.Tunnel:
|
|
if (worldReady)
|
|
{
|
|
evts.Add(TeleportAnimEvent.Place);
|
|
Advance(TeleportAnimState.TunnelContinue, enterTunnel: false);
|
|
```
|
|
|
|
The state advances in the **same tick** the event is emitted. `Place`
|
|
never fires again for that reveal.
|
|
|
|
`src/AcDream.App/Streaming/LocalPlayerTeleportController.cs:505-522`
|
|
|
|
```csharp
|
|
case TeleportAnimEvent.Place:
|
|
if (!_worldReveal.CanPlacePortalDestination(...)) return;
|
|
if (!TryExecuteCanonicalPortalPlacement(sequence)) return; // :513
|
|
...
|
|
_placement.Place(_pendingRotation); // :517
|
|
...
|
|
_worldReveal.ObserveMaterialized(...); // :520
|
|
```
|
|
|
|
`TryExecuteCanonicalPortalPlacement` returns `true` **only** on
|
|
`RuntimeAcceptedPositionExecutionStatus.Committed`
|
|
(`LocalPlayerTeleportController.cs:597-598`). Every other status —
|
|
`Contention`, `Rejected`, `NotApplicable`, and notably `DeferredCell` —
|
|
returns `false` and the `Tick` returns.
|
|
|
|
Consequences, all reachable:
|
|
|
|
- `_placement.Place` never runs → no `entity.SetPosition` /
|
|
`ParentCellId` / `RebucketLiveEntity`, no `NotifyTeleported()`, no
|
|
camera reset, no `_spatial.Reconcile()`.
|
|
- `_worldReveal.ObserveMaterialized` never runs →
|
|
`RuntimeWorldTransitState.AcknowledgePortalMaterialized` never fires →
|
|
`Materialized` stays false.
|
|
- The **next** tick still advances the sequencer:
|
|
`TunnelContinue` → `TunnelFadeOut` → `PlayExitSound`
|
|
(`RevealWorldViewport`) → `WorldFadeIn` → `FireLoginComplete`
|
|
(`_mode.EnterWorld()` + `SendLoginComplete()` + `_worldReveal.Complete()`
|
|
+ `ResetTransit`).
|
|
- `RuntimeWorldTransitState.Complete` then trips
|
|
`FailInvariant("portal-complete-before-materialized")`
|
|
(`RuntimeWorldTransitState.cs:701-706`) and returns `false`;
|
|
`WorldRevealCoordinator.Complete()` (`:268-276`) **discards** that
|
|
`false`. `ResetTransit(clearSession:false)` then calls
|
|
`_transit.EndTeleport()` + `_worldReveal.Cancel()`, so the ledger
|
|
converges — but the reveal is recorded cancelled, not completed, with
|
|
one invariant failure logged.
|
|
|
|
User-visible outcome on `Contention`/`Rejected`: the player is revealed
|
|
into the destination world **standing at the origin position**, with
|
|
LoginComplete sent. On `DeferredCell`: the body commits later at the
|
|
collision-generation wake (`Advance` → `ReconcileAndAcknowledgePortal`),
|
|
but the presentation suffix, camera reset, rebucket, and materialization
|
|
ack are gone forever.
|
|
|
|
This is exactly what contract §4 items 4 and 5 forbid ("a refused
|
|
placement must NOT … must not advance the anim-event stream's terminal
|
|
events"; "never a half-state", "never a silent wedge in portal space")
|
|
and it is the D-T5/P4 obligation the contract flagged in advance: *"if
|
|
the Place anim event is one-shot, the re-attempt must be driven by the
|
|
same Tick predicate that produced it, and THAT mechanism must be stated
|
|
in the commit."* It is one-shot, and no mechanism was added.
|
|
|
|
The comment shipped in its place is false. `LocalPlayerTeleportController.cs:566-572`:
|
|
|
|
> "On any refusal this returns `false` without mutating anything — the
|
|
> D-T5 refusal shape: … the transit's own cancellation/supersession
|
|
> machinery is the authority on what happens next."
|
|
|
|
The transit is not the authority on what happens next. The animation
|
|
sequencer is, and it does not wait.
|
|
|
|
**Correct behaviour:** either re-drive the Place edge from the same
|
|
`ready`-gated Tick predicate until it commits (holding the sequencer in
|
|
`Tunnel` — which is what retail's `blocking_for_cells` hold is), or
|
|
cancel the reveal explicitly on refusal so the player is never revealed
|
|
without a committed placement. Retail has no third option: it places
|
|
unconditionally and immediately (`SmartBox::TeleportPlayer` @0x00453910)
|
|
and only the *simulation* waits on prefetch.
|
|
|
|
---
|
|
|
|
### R2 — MAJOR — headless discards the arm's status and acknowledges a materialization that did not happen
|
|
|
|
`src/AcDream.Headless/Hosting/HeadlessSessionWorldProjection.cs:775`
|
|
|
|
```csharp
|
|
_ = _acceptedPositionDrive.TryExecuteAcceptedPortalArrival(
|
|
destination,
|
|
authority);
|
|
```
|
|
|
|
The status is dropped on the floor. `PrepareDestination` then
|
|
unconditionally returns a ready readiness report, and
|
|
`RuntimeLiveEntitySessionController.TryCompletePortal` continues its
|
|
fully-synchronous suffix: `AcknowledgeDestinationReadiness` →
|
|
`AcknowledgePortalMaterialized` → `SimulationReleaseProjected` →
|
|
`Complete` → `SendGameAction(LoginComplete)` → `EndTeleport`.
|
|
|
|
So on any refusal or park, the headless host **fires
|
|
`AcknowledgePortalMaterialized` for a placement that never committed** —
|
|
contract §4 item 5 and D-T5 rows 1/2 both state in terms that the
|
|
materialization ack must fire only from the committed outcome. The bot
|
|
reports a completed teleport while standing where it started, with no
|
|
log line of any kind (see R8).
|
|
|
|
Retail contradiction is indirect but real: retail's
|
|
`SmartBox::PlayerPositionUpdated` @0x00453870 clears
|
|
`waiting_for_teleport` **inside the same call that performed
|
|
`SetPositionSimple`** (@0x00453924 → @0x0045389A). The "wait is over"
|
|
edge is downstream of the placement in retail; here it can precede a
|
|
placement that never occurred.
|
|
|
|
---
|
|
|
|
### R3 — MAJOR — the D-T8 probe's `leash` field can never read `armed`; the connected gate as pinned is unpassable
|
|
|
|
`src/AcDream.Runtime/Session/RuntimeAcceptedPositionDriveController.cs:694`
|
|
|
|
```csharp
|
|
leashArmed: controller.PositionManager?.IsFullyConstrained() ?? false,
|
|
```
|
|
|
|
Retail `ConstraintManager::IsFullyConstrained` @0x005560D0 is
|
|
`constraint_distance_max * 0.9 < constraint_pos_offset` — "has strained
|
|
past 90 % of the leash", the predicate `jump_is_allowed` reads. It is not
|
|
"is the leash armed". The acdream port says so explicitly
|
|
(`src/AcDream.Core/Physics/Motion/ConstraintManager.cs:79-89`).
|
|
|
|
Retail `ConstraintManager::ConstrainTo` @0x00556240 (pseudo-C
|
|
:353528-353537) ends with
|
|
`constraint_pos_offset = Position::distance(anchor, physics_obj->m_position)`;
|
|
acdream mirrors it at `ConstraintManager.cs:65-71`. Because
|
|
`RearmConstraintLeashAtCurrentPosition`
|
|
(`PlayerMovementController.cs:1836-1845`) anchors at the body's **own**
|
|
`CellPosition`, that distance is 0. `max * 0.9 < 0` is false.
|
|
|
|
Therefore every committed portal arrival prints `leash=unarmed`. The
|
|
contract's §9 pass criterion — *"Pass requires ALL of … `leash=armed`"* —
|
|
cannot be met, and a future reader hitting `leash=unarmed` would chase a
|
|
phantom missing leash (the exact 4b-3 A1 defect class the contract
|
|
warned about, inverted).
|
|
|
|
The correct observable is `ConstraintManager.IsConstrained`, which the
|
|
Runtime test itself uses
|
|
(`RuntimeAcceptedPositionDriveControllerTests`,
|
|
`Assert.True(controller.PositionManager.Constraint!.IsConstrained)`).
|
|
`PositionManager` does not currently surface it; it needs to.
|
|
|
|
---
|
|
|
|
### R4 — MAJOR — the mandatory App-layer presentation suite is missing, and the existing App assertions were weakened
|
|
|
|
`tests/AcDream.App.Tests/Streaming/LocalPlayerTeleportControllerTests.cs`
|
|
gained **zero** new `[Fact]`s. All five new tests in the diff are in
|
|
`tests/AcDream.Runtime.Tests/Session/RuntimeAcceptedPositionDriveControllerTests.cs`.
|
|
|
|
What changed in the App file is assertion *strength*, downward:
|
|
|
|
```diff
|
|
- Assert.Equal(new Vector3(7f, 8f, 9f), harness.Placement.Position);
|
|
+ Assert.True(harness.Placement.Called);
|
|
```
|
|
|
|
(and the same substitution at eight further sites). The interface change
|
|
makes the literal old assertion impossible, which is fine — but the
|
|
contract required the replacement coverage and named it as load-bearing:
|
|
|
|
- §8 item 8: "after a committed portal placement through the REAL sink +
|
|
suffix, the render `WorldEntity` position/rotation/`ParentCellId` equal
|
|
the resolved body, the draw bucket moved, the local-player shadow
|
|
agrees, and the sink's Place receipt was consumed with a VALID portal
|
|
authority … **route 2's B2 coverage gap … becomes load-bearing here and
|
|
MUST close**."
|
|
- §8 item 9: the T8 overwrite ordering (wire pose then resolved pose).
|
|
- §8 item 10: refused Place edge presentation.
|
|
|
|
None exist. Since `RuntimePlacementPresentationSink.TryPublishPlace`
|
|
writes no pose (contract §3.4, re-confirmed), the suffix in
|
|
`LocalPlayerTeleportPlacement.Place` is now the render entity's **only**
|
|
mover — and nothing asserts it moves the entity to the resolved pose.
|
|
Combined with R1 (where that suffix is skipped entirely on refusal), this
|
|
is the #312 shape verbatim: process rule 4, "tests must assert the layer
|
|
that broke."
|
|
|
|
---
|
|
|
|
### R5 — MAJOR — headless dual-host parity (§8 item 6, D-T6) has no coverage, self-declared open
|
|
|
|
`tests/AcDream.Headless.Tests/HeadlessSessionHostTests.cs:413-422`
|
|
(added in this diff):
|
|
|
|
> "no accepted-position drive controller is wired into this fixture's
|
|
> projection … so the canonical portal arm this method now calls is a
|
|
> no-op here by construction (`_acceptedPositionDrive` is null) … a
|
|
> headless-host-specific committed-portal test is an open item, not
|
|
> attempted here given this session's time budget."
|
|
|
|
D2 (`ResynchronizeLocalPlayerForPortalArrival`, ~40 non-comment lines and
|
|
AD-42's last citation) was deleted and its replacement has **zero**
|
|
headless test coverage. D-T6 pinned this: *"dual-host parity is a test
|
|
obligation, not an aspiration."* The honest disclosure is appreciated and
|
|
does not change the finding.
|
|
|
|
Related, and unremarked in the diff: the deleted method also performed
|
|
`controller.LocalEntityId = record.LocalEntityId ?? 0u`. Verified safe —
|
|
`RuntimeLocalPlayerPhysicsPublicationState.cs:214` sets it at publication
|
|
and the entity key is stable across a portal — but the drop deserves a
|
|
line in the commit message.
|
|
|
|
---
|
|
|
|
### R6 — MEDIUM — the `enter_world` caller-sweep correction mis-states the retail record it is correcting
|
|
|
|
`docs/research/2026-07-16-portal-completion-pseudocode.md` §2.1 banner:
|
|
|
|
> "a caller sweep … shows both `CPhysicsObj::enter_world` call sites
|
|
> living inside `CObjectMaint::CreateObject`'s player branch
|
|
> (`SmartBox::init_player` + `CellManager::ChangePosition` immediately
|
|
> precede it)"
|
|
|
|
Verified independently. The two call sites are pseudo-C :93797
|
|
(@0x004550EC) and :93824 (@0x00455095). Both live inside
|
|
**`SmartBox::HandleCreateObject` @0x00454C80**, not
|
|
`CObjectMaint::CreateObject` — the latter is merely a *callee* at
|
|
@0x00454FD8 inside that same function. And they are **not both in the
|
|
player branch**: @0x004550EC is in the `if (arg3 != this->player_id)`
|
|
NON-player branch (`PhysicsDesc::get_position` → `enter_world(var_bc, …)`
|
|
for a newly created remote object); only @0x00455095 sits in the player
|
|
branch after `init_player` + `ChangePosition`.
|
|
|
|
The load-bearing NEGATIVE is **CONFIRMED**: the `Position*` overload
|
|
@0x00516310 has exactly those two callers, the `int` overload @0x00516170
|
|
is reached only from @0x00516327, and neither is on the portal path.
|
|
`SmartBox::TeleportPlayer` → `SetPositionSimple` is correct.
|
|
|
|
But this banner is explicitly a correction to the retail record that
|
|
future sessions will cite, and it is wrong in two of its three factual
|
|
clauses. Same defect class as "a register row asserting behaviour the
|
|
code does not have," applied to a research doc — the exact reason the
|
|
contract ordered the correction in-slice.
|
|
|
|
---
|
|
|
|
### R7 — MEDIUM — retail's post-teleport movement refresh is autonomy-gated; acdream's is not
|
|
|
|
Contract open question (c), answered.
|
|
`CommandInterpreter::SendMovementEvent` @0x006B4680 (pseudo-C
|
|
:700274-700313):
|
|
|
|
```
|
|
if ((player != 0 && this->smartbox != 0) && CPhysicsObj::InqRawMotionState(player) != 0)
|
|
if (this->autonomy_level != 0)
|
|
MoveToStatePack::MoveToStatePack(...)
|
|
SendMoveToStateEvent(...)
|
|
```
|
|
|
|
Two gates: a non-null raw motion state, and **`autonomy_level != 0`**.
|
|
Under server control retail sends nothing.
|
|
|
|
`RuntimeAcceptedPositionDriveController.cs:678-681` calls
|
|
`_localPlayerOutbound.TrySendMovement(...)` unconditionally;
|
|
`LocalPlayerOutboundController.TrySendMovement:187-229` gates only on a
|
|
resolvable outbound position. The controller already holds
|
|
`UsePositionFromServer` (retail's `UsePositionFromServer()`), and
|
|
`_usePositionFromServer` is already a field on this very class — the
|
|
autonomy fact is in scope.
|
|
|
|
Everything else about the port checks out: the message family is
|
|
`MoveToState` (retail packs `InqRawMotionState` into `MoveToStatePack`),
|
|
the contact byte is `Contact && OnWalkable` in both, exactly one is sent,
|
|
and no `AutonomousPosition` goes out (retail's teleport branch returns
|
|
before `SendPositionEvent` — verified at @0x004541C0). Order is right:
|
|
`CommitCanonicalTeleportFrame` (hook tail) → `CancelAutoRun` →
|
|
movement send, matching @0x004538AE → @0x004538B3 → tail-jump.
|
|
|
|
Either add the autonomy gate or file the delta as a register row.
|
|
|
|
---
|
|
|
|
### R8 — MEDIUM — D-T8 emits one line per *committed* arrival, not per attempt; refusals are invisible under the pinned gate environment
|
|
|
|
`PhysicsDiagnostics.LogLocalTeleportArrival` is called from exactly one
|
|
site, `ReconcileAndAcknowledgePortal`
|
|
(`RuntimeAcceptedPositionDriveController.cs:687-696`), reached only on
|
|
`CommittedHostAcknowledgementPending`. Its `placementStatus` argument is
|
|
the literal `"Committed"`.
|
|
|
|
The graphical refusal path logs via `PhysicsDiagnostics.LogTeleport`
|
|
(`LocalPlayerTeleportController.cs:582-583`, `:598-599`), which is gated
|
|
by **`ACDREAM_PROBE_TELEPORT`** (`PhysicsDiagnostics.cs:1160-1161`) — a
|
|
different env var from the `ACDREAM_PROBE_LOCAL_TELEPORT` the gate pins.
|
|
The headless refusal path logs nothing at all (R2).
|
|
|
|
Net: with the contract's pinned gate environment, a refusal produces zero
|
|
output on either host. D-T8 specified "One line per portal-arrival
|
|
attempt: cause … placement status", and §9 requires "zero
|
|
`Refused`/`Contention` lines in ordinary play" — unobservable as built.
|
|
Given R1, an unobserved refusal is precisely the failure that would ship.
|
|
|
|
---
|
|
|
|
### R9 — LOW — stale directional reference in a comment added by this diff
|
|
|
|
`src/AcDream.App/Streaming/LocalPlayerTeleportController.cs:775`:
|
|
"TryBeginPortal (below) drives …". `_worldReveal.TryBeginPortal` is
|
|
called **above** this comment, at `:741`, in the same method. Process
|
|
rule 6.
|
|
|
|
### R10 — LOW — `AD-131` does not exist
|
|
|
|
`src/AcDream.App/Physics/LiveEntityNetworkUpdateController.cs:2292`
|
|
cites "AD-2/AD-131/#275". The AD section has 48 rows. The row is
|
|
**AP-131** (`docs/architecture/retail-divergence-register.md:283`), which
|
|
is what the contract itself says. Introduced by this diff, in the very
|
|
comment the slice rewrote to fix a stale comment.
|
|
|
|
### R11 — LOW — the probe asserts more than it observes
|
|
|
|
`RuntimeAcceptedPositionDriveController.cs:693-695` hardcodes
|
|
`hookTailRan: true` and `autorunCancelled: true`.
|
|
`RuntimeLocalPlayerMovementState.CancelAutoRun():226-234` returns `false`
|
|
when autorun was already off (correctly mirroring retail's
|
|
`SetAutoRun` @0x006B4850, which acts only on a state *change* at
|
|
@0x006B4871). The field reports the action ran, not the state changed —
|
|
report the returned bool.
|
|
|
|
---
|
|
|
|
## §A — retail claims verified independently (do not re-derive)
|
|
|
|
All against `docs/research/named-retail/acclient_2013_pseudo_c.txt`.
|
|
|
|
| claim | where | result |
|
|
|---|---|---|
|
|
| `SmartBox::TeleportPlayer` @0x00453910 = `SetPositionSimple(player, dest, 1)` @0x00453924 + `PlayerPositionUpdated(this, 1, FLT_MAX)` @0x00453932, nothing else | :92514-92523 | **CONFIRMED** — the generic path, route 2's exact primitive, third route running |
|
|
| `PlayerPositionUpdated` teleport arm order: `position_update_complete=0` @0x00453890, `waiting_for_teleport=0` @0x0045389A, `has_been_teleported=0` @0x004538A4, `teleport_hook` @0x004538AE, `cmdinterp->PlayerTeleported()` @0x004538B3, `set_viewer` @0x004538D5, `LScape::update_viewpoint` @0x004538E2, `CellManager::ChangePosition` @0x00453903 | :92469-92509 | **CONFIRMED**, exactly the contract's order |
|
|
| `CommandInterpreter::PlayerTeleported` @0x006B32B0 = `SetAutoRun(0,1)` + tail-jump `SendMovementEvent` | :699036-699041 | **CONFIRMED**. New: `SetAutoRun` @0x006B4850 only acts when `(arg2==0) != (auto_run==0)` (@0x006B4871) — acdream's `CancelAutoRun` early-return matches |
|
|
| **Inversion A** — local TELEPORT branch @0x0045415F: `TeleportPlayer(&var_48)` @0x00454168 → `ConstrainTo(arg2, &var_48, start, max)` @0x0045418A → `set_velocity(player, {0,0,0}, 1)` @0x004541B4 → return | :93013-93023 | **CONFIRMED**, including the WIRE-destination anchor |
|
|
| FORCE_POSITION branch returns @0x0045409D before every `ConstrainTo` | :92925-92933 | **CONFIRMED** — route 2's no-re-arm rule intact and correctly left force-scoped |
|
|
| **Inversion B** — the local hook runs AFTER the placement (from `PlayerPositionUpdated`), opposite to the remote arm's @0x005163EF | :92497 vs 4b-3's citation | **CONFIRMED** |
|
|
| `enter_world` is NOT on the portal path | :93797, :93824 | **NEGATIVE CONFIRMED** — but the attribution in the new correction banner is wrong; see R6 |
|
|
| **P1 (TransientState not re-seeded)** — retail `CPhysicsObj::SetPositionInternal(CTransition*)` @0x00515330 derives Contact from `collision_info.contact_plane_valid` @0x00515430, WaterContact from `contact_plane_is_water` @0x00515453, OnWalkable from `set_on_walkable(contact_plane.N.z vs floor_z)` @0x00515467-0x0051548E, Sliding from `sliding_normal_valid` @0x005154E1. **No unconditional `Contact\|OnWalkable` seed anywhere.** | :283484-283519 | **THE FINDING IS CORRECT AND IS A FIDELITY GAIN.** `PhysicsObjUpdate.CommitSetPositionContactPrefix` (`src/AcDream.Core/Physics/PhysicsObjUpdate.cs:154-176`) is that exact port, and runs inside the canonical commit (`RuntimeSetPositionState.cs:5038`). The old `SetPositionCore` seed (`PlayerMovementController.cs:1859-1862`) was the divergence; dropping it is right. The `Active` argument also holds — `PlayerMovementController.cs:2069` and `:2449` re-set it every frame. |
|
|
| `ConstraintManager::ConstrainTo` @0x00556240 initializes `constraint_pos_offset = distance(anchor, m_position)` | :353528-353537 | **CONFIRMED** — acdream matches; also the basis for R3 |
|
|
| `CommandInterpreter::SendMovementEvent` @0x006B4680 is autonomy-gated | :700274-700313 | **CONFIRMED** — see R7 |
|
|
|
|
## §B — implementation facts verified correct
|
|
|
|
- **D-T3 duty map (P1) is complete.** All nine `SetPositionCore` duties
|
|
land in `CommitCanonicalTeleportFrame`
|
|
(`PlayerMovementController.cs:1993-2041`) or the canonical commit, in
|
|
`SetPositionCore`'s own order (StopCompletely → input/mouse resets →
|
|
UnStick/UnConstrain/re-arm → edge resets → clock reset). Nothing
|
|
silently dropped except the TransientState seed, which is correct
|
|
(§A).
|
|
- **Inversions implemented in the right directions.** The portal arm
|
|
consumes the classifier's dormant LocalPlayer-teleport branch
|
|
(`RuntimeAuthoritativePositionRouteClassifier.cs:336-356`) unchanged;
|
|
`ConstrainPhase.AfterPositionOperation` + `ZeroVelocity: true` +
|
|
`SendPositionImmediately: false` + `TeleportHookPhase.AfterPositionOperation`
|
|
all flow through. The hook tail runs only from the committed receipt.
|
|
- **The force arm is untouched.** The only route-2 edits are one
|
|
force-scoping doc sentence (`:133-137`) and a non-`required`
|
|
`Portal { get; init; }` on `Pending` that defaults empty. Zero route-2
|
|
test expectation changes — §4 item 8's tripwire is clean.
|
|
- **The synthetic `priorTeleport = accepted - 1`** in
|
|
`ClassifyPortalArrival` is sound: `TeleportAdvanced` reads only the
|
|
boolean `PhysicsTimestampGate.IsNewer(prev, accepted)`, the branch's
|
|
resulting route does not depend on the previous stamp's magnitude, and
|
|
wrap is safe at `accepted == 0`.
|
|
- **AD-42 deleted** (row gone, header 49 → 48 rows), **AD-2 amended in
|
|
place** with the deferred-place timing, the T8 tolerated-overwrite
|
|
note, and the leash-anchor nuance. Plan gap-line correction present.
|
|
Register rules satisfied.
|
|
- **Release build green.** Focused
|
|
`RuntimeAcceptedPositionDriveControllerTests`: 22 passed / 0 failed /
|
|
**0 skipped**. No `Skip` attribute remains in any of the four touched
|
|
test files, and no Runtime test was weakened — the five new ones are
|
|
strong (the happy path pre-arms the leash at a *stale* anchor so a
|
|
missing re-arm fails the `ConstraintPos` assertion; the refusal tests
|
|
assert positive "nothing moved, transit still active, no packets"
|
|
facts).
|
|
|
|
## §C — the production bug fix: correct, complete, no stale-destination hazard
|
|
|
|
Verified. `RuntimeWorldTransitState.TryBeginPortalReveal:159-182` sets
|
|
`_hasAcceptedDestination = false` and `_acceptedDestination = default`
|
|
the instant it claims the generation, and
|
|
`TryGetAcceptedTeleportDestination:522-527` returns
|
|
`_teleportActive && _hasAcceptedDestination`. So the pre-fix Place-time
|
|
re-read was **guaranteed** to fail — the canonical portal arm was 100 %
|
|
dead code, refusing with `cause=host-token-unavailable` before ever
|
|
reaching Runtime. The diagnosis is right and this was a real production
|
|
bug, not a fixture artifact.
|
|
|
|
The fix is the right lifetime:
|
|
|
|
- `_pendingDestination` is written in `AimDestination:781-784`, in the
|
|
same statement block as `_pendingCell`/`_pendingRotation`, only after
|
|
`TryBeginPortal` succeeded (`:741-748`) — so the four Aim-time
|
|
snapshots are mutually consistent by construction.
|
|
- **Cancellation:** `ResetTransit:801-804` clears all four; every
|
|
cancellation path funnels through it.
|
|
- **Supersession:** a second accepted destination re-enters
|
|
`TryAimAcceptedDestination` → `AimDestination` →
|
|
`WorldRevealCoordinator.TryBeginPortal` → `WithdrawHostForReplacement`
|
|
+ a **new** generation, overwriting all four snapshots together. A
|
|
superseded destination cannot survive.
|
|
- **Staleness at Place:** three independent gates still validate —
|
|
`CanPlacePortalDestination(_pendingRevealGeneration, sequence, _pendingCell)`
|
|
(`:507-511`), the idempotent
|
|
`TryRegisterHostProjection` re-derivation (generation ==
|
|
`_snapshot.Generation`, cell == `_snapshot.DestinationCell`,
|
|
`!Cancelled`, `!Completed` — `RuntimeWorldTransitState.cs:189-227`),
|
|
and `BeginAcceptedPlacementCore`'s own
|
|
`portal.Projection.DestinationCell == acceptedPosition.LandblockId`
|
|
against the **latest merged** snapshot
|
|
(`RuntimeSetPositionState.cs:1528-1534`).
|
|
|
|
The re-read was protecting nothing. `WorldRevealCoordinator.BeginHostLifetime`
|
|
throws if the Aim-time registration fails, so the Place-time
|
|
re-derivation is genuinely idempotent and can never mint a second host
|
|
projection in production.
|
|
|
|
## §D — contract open questions, answered
|
|
|
|
**(b) leash anchor — keep the resolved anchor as shipped.** Retail's
|
|
`constraint_pos` is write-only (never read by `adjust_offset`, confirmed
|
|
in the port's own doc at `ConstraintManager.cs:41-44` and against ACE);
|
|
the only downstream consumer of `ConstrainTo`'s inputs is
|
|
`constraint_pos_offset = distance(anchor, m_position)`, which is the
|
|
placement adjustment (centimetres) in retail and exactly 0 in acdream.
|
|
Both are orders of magnitude inside the `0.9 * max` band, so no behaviour
|
|
in the leash's brake taper can distinguish them. The AD-2 note is the
|
|
right disposition; do **not** switch anchors.
|
|
|
|
**(c) `SendMovementEvent` shape — see R7.** Message family, contact
|
|
derivation, count, and ordering are all correct; the missing
|
|
`autonomy_level` gate is the one delta.
|
|
|
|
## §E — errors in the contract itself
|
|
|
|
1. **§4 item 3 / D-T5's re-attempt reasoning is the proximate cause of
|
|
R1.** D-T5 offered "the anim event re-fires while `ready` holds" as
|
|
the leading case and demoted the one-shot case to a parenthetical
|
|
verify-and-state. It is one-shot. The contract should have read the
|
|
sequencer before writing the row and pinned the driver. This is
|
|
process rule 1 ("the contract causes the defect") recurring for the
|
|
third documented time.
|
|
2. **§9's `leash=armed` criterion is unachievable** with any
|
|
`IsFullyConstrained`-shaped observable; the contract should have named
|
|
`ConstraintManager.IsConstrained`. See R3.
|
|
3. **§3.5 overstates the change:** "`AcknowledgePortalMaterialized` fires
|
|
from the committed placement receipt instead of rubber-stamping after
|
|
a host mutation." As built it still fires from the host Place edge,
|
|
merely gated on a committed status. Substantively equivalent on the
|
|
commit path; wording should be corrected so a future reader does not
|
|
look for a receipt-driven ack that does not exist.
|
|
4. **§1's `enter_world` row** carries the same wrong caller attribution
|
|
the banner does (R6) — it says "its local-player caller is the initial
|
|
login path only (@0x00455095)", which is right about that site but
|
|
silently drops the *other* site @0x004550EC and mis-names the
|
|
enclosing function in the derived correction.
|
|
|
|
## What must land before this can pass
|
|
|
|
1. A re-attempt (or explicit-cancel) mechanism for a non-`Committed`
|
|
Place edge, with the driver named and tested — R1.
|
|
2. Headless must consume the arm's status and must not acknowledge a
|
|
materialization for a placement that did not commit — R2.
|
|
3. Fix the probe's `leash` observable (and emit a line per *attempt*,
|
|
under the gate's own env var) — R3, R8.
|
|
4. Write the App-layer presentation suite (§8 items 8/9/10) closing route
|
|
2's B2 gap, plus one headless committed-portal test — R4, R5.
|
|
5. Correct the `enter_world` caller-sweep banner — R6.
|
|
6. Gate the movement refresh on autonomy, or file the register row — R7.
|
|
7. Comment/citation cleanups — R9, R10, R11.
|