acdream/docs/research/2026-08-04-c4-route-3-retail-review-round2.md
Erik e0f96a55bf 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>
2026-08-05 03:57:37 +02:00

434 lines
22 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# C4 route 3 — retail-conformance review, round 2 (delta) — 2026-08-05
**Verdict: FAIL — but a near miss.** All three round-1 MAJOR retail
findings (R1, R3, R8) are genuinely fixed, and the R1 fix is the right
shape for the right retail reason. What blocks is small and cheap: one
unsound "the park committed" inference that reopens R1's failure mode on
a narrow path (**N1**), one approximation shipped with a code comment
instead of the register row the project's binding rule requires
(**R7**), and one transient condition converted into a fatal exception on
the headless endurance path (**N3**).
**R4/A5 (the presentation suite) explicitly does NOT block** — see §D.
That call changed from round 1 because the A10 correction is true: I
verified the canonical Place receipt, not the suffix, is the render
entity's mover, and both halves of that path already have tests.
Scope: delta against my round-1 report
(`2026-08-04-c4-route-3-retail-review.md`). Same working tree, HEAD
`cd3129e9`, uncommitted. Review only.
---
## §A — round-1 findings: disposition
| # | round-1 finding | round-2 status |
|---|---|---|
| R1 | `Place` one-shot; no re-attempt driver; reveal completes with an unplaced body | **FIXED** — §B, and the fix is retail-correct |
| R2 | headless discards the arm's status, acks a materialization that never happened | **FIXED** — §B.3 |
| R3 | probe's `leash` field can never read `armed` | **FIXED**`Constraint?.IsConstrained` at `RuntimeAcceptedPositionDriveController.cs:838` |
| R4 | App presentation suite missing; assertions weakened | **PARTIALLY closed, does not block** — §D |
| R5 | headless dual-host parity untested | **FIXED**`HeadlessPortalPrepareDestinationParksThenCommitsOnCollisionGenerationWake` |
| R6 | `enter_world` caller-sweep correction mis-stated the retail record | **FIXED and independently re-verified** — §C.4 |
| R7 | retail's movement refresh is autonomy-gated; acdream's was not | **PARTIALLY fixed — BLOCKS on register discipline** — §C.3 |
| R8 | probe fired only on commit; refusals invisible under the pinned env var | **MOSTLY fixed** — two App-side causes remain invisible, §E.2 |
| R9 | stale "TryBeginPortal (below)" | **FIXED** (`:751` now reads "above") |
| R10 | `AD-131` does not exist | **FIXED**`AP-131` |
| R11 | probe hardcoded `autorunCancelled: true` | **FIXED** — reads `CancelAutoRun()`'s bool |
---
## §B — the R1 fix: correct, and correct for the right retail reason
`LocalPlayerTeleportController.cs:527`
```csharp
bool placementReady = dataReady && TryAdvancePortalCommit(sequence);
...
var (_, events) = _presentation.Tick(deltaSeconds, placementReady);
```
Inverting the readiness feed instead of touching the sequencer is the
right call, for a reason worth recording: **it is retail's own shape.**
Retail holds the player in portal space on `CellManager::blocking_for_cells`
and `SmartBox::UseTime` @0x00455410 runs only `CheckPrefetchStatus` until
the destination is usable — the hold lives in the *readiness predicate*,
not in the animation. Feeding the sequencer "the canonical commit has
already happened" rather than "the data is ready" reproduces that hold
without changing a single sequencer timing (stop condition 2 respected).
`TeleportAnimSequencer.cs` is untouched — confirmed by `git diff`.
### B.1 — arrival ORDER is preserved; Inversion B intact
Verified by tracing the single tick on which the commit succeeds:
1. `TryAdvancePortalCommit``TryExecuteAcceptedPortalArrival`
`TryPrepareAndSubmitAuthoredPlacement`**canonical body commit**
(`CommitCanonical`, retail `SetPositionSimple` @0x005162B0).
2. `ReconcileAndAcknowledgePortal``CommitCanonicalTeleportFrame`
(UnStick @0x00514EEE / UnConstrain @0x00514F02 / re-arm @0x0045418A,
velocity zero @0x004541B4, StopCompletely) — **after** the placement.
3. `CancelAutoRun()` + movement refresh — the `PlayerTeleported`
@0x006B32B0 port, **after** the hook tail.
4. Only then does the sequencer see `worldReady=true`, emit `Place`, and
run the presentation suffix (`NotifyTeleported`, camera reset,
reconcile) and `ObserveMaterialized`.
Retail: `SetPositionSimple` @0x00453924`PlayerPositionUpdated`
@0x00453932`teleport_hook` @0x004538AE`PlayerTeleported`
@0x004538B3`set_viewer` @0x004538D5. **Same order.** Inversion B
(local hook AFTER placement) holds; the gating did not move it.
Two sub-order deltas versus retail, both traced and both **unobservable**
— stated so a future reader does not re-derive them:
- Retail's `ConstrainTo` @0x0045418A and `set_velocity` @0x004541B4 run
in `HandleReceivedPosition` *after* `PlayerPositionUpdated` returns,
i.e. after `SendMovementEvent` and `set_viewer`. acdream runs both
inside `CommitCanonicalTeleportFrame`, before them. Nothing reads the
leash between those points, and `MoveToStatePack` @0x006B4720 packs
`InqRawMotionState` + `m_position` + contact + longjump + timestamps —
**no velocity** — so the outbound bytes are identical either way.
- `NotifyTeleported()` (teleport_hook's TargetManager teardown) now runs
in the presentation suffix, i.e. after `PlayerTeleported`, where retail
runs the whole hook before it. Purely local; no interaction with the
outbound send.
### B.2 — the refusal path is now genuinely held, and tested with teeth
`RefusedPlace_HoldsTheStreamAndConvergesOnlyAfterContentionClears`
(App.Tests) forces a real Runtime `Contention` by taking the entity's
placement token, drives **100 ticks at 0.1 s** — 10 s, roughly 3× the
tunnel's own 25 s timing, well past where the pre-fix code fired
`FireLoginComplete` — and asserts the positive facts: body unmoved,
`IsActive`, `Snapshot.Completed == false`, `LoginCompleteCount == 0`.
It then releases the competing operation and asserts the very next tick
commits at the offered destination. That is a real discriminator, not a
negative-only assertion, and it directly kills R1.
A permanent refusal now holds in the tunnel showing retail's centered
wait cue (`_holdSeconds` accumulates on `!placementReady`) until
supersession or session reset — which is exactly what contract D-T5
pinned and §4 item 4 requires.
### B.3 — headless
`PrepareDestination` now throws if no drive is wired, gates the attempt
on `_collision.IsReady(destination.CellId)`, and returns
`IsCollisionReady: committed`;
`RuntimeLiveEntitySessionController.TryAdvancePortalCompletion` returns
early on `!IsCollisionReady`, retried by `PumpPortalCompletion` from
`HeadlessSessionHost.Tick`. The materialization ack can no longer
precede a placement. R2 closed.
### B.4 — P3 (no wedgeable portal receipt) is now ESTABLISHED
Neither review had closed this. `RuntimePlacementProjectionSubscription.OnPlacement`
(`:122-150`) calls `_sink.TryApply(in head)` **synchronously on publish**,
so a portal-carrying Place receipt is applied and acknowledged inside the
commit call, while the transit is still active and
`IsCurrentPlacementAuthority` still holds. This matters most on headless,
where `TryAdvancePortalCompletion` runs `Complete` + `EndTeleport`
synchronously right after the commit and the FIFO is only republished at
the end of the same `Tick` — had delivery been deferred, that receipt
would have failed the portal gate forever and wedged the placement FIFO
for every entity. It does not. **P3 satisfied on both hosts.**
---
## §C — retail verification of the round-2 changes
### C.1 — A4: the route fields carry retail semantics (with one granularity defect)
Verified in `RuntimeAuthoritativePositionRouteClassifier.cs:164-171`:
- `ConstrainAfterRouting => ConstrainPhase is AfterPositionOperation`
**correct and discriminating.** It separates the three real retail
cases: force (`None`, early return @0x0045409D), local teleport
(`After`, `ConstrainTo` @0x0045418A), local non-teleport (`Before`,
`ConstrainTo` @0x004541EC). The sabotage the contract's §8 item 11
demanded can now fail as designed.
- `ZeroVelocity` — read directly; retail `set_velocity` @0x004541B4.
**N4 (LOW) — `RunsTeleportHook` gates too much.**
`RuntimeAcceptedPositionDriveController.cs:795-801` uses
`route.RunsTeleportHook` (`TeleportHookPhase is not None`) to gate the
**entire** `CommitCanonicalTeleportFrame` call. But that method does far
more than retail's `teleport_hook` @0x00514ED0 (CancelMoveTo, UnStick,
StopInterpolating, UnConstrain, TargetManager, report_collision_end): it
also resets the render-lerp anchors, publishes `UpdateCellId`, runs
StopCompletely, resets the input edges, and resets the object clock —
none of which retail conditions on the hook. Retail's `SetPositionInternal`
@0x00515330 does the frame/cell work unconditionally. Today the portal
route always sets the phase, so there is no live effect; but a future
`TeleportHookPhase.None` would silently skip the render-root cell publish
— the doorway-FLAP class. Gate only the `UnStick`/`UnConstrain`/re-arm
block on the hook phase.
`RunsTeleportHook` also collapses `Before` and `After` into one boolean.
Harmless here because the call site is unconditionally post-commit, but
it means the field cannot express Inversion B by itself.
### C.2 — the "sequence" plumbing
`TryExecuteCanonicalPortalPlacementCore` now takes the authority's
`TeleportSequence` from `_pendingDestination` and `Debug.Assert`s it
equals the transit's `ActiveTeleportSequence`. Sound: the two can only
diverge through a bug, and `RuntimeWorldTransitState.OfferTeleportDestination:495-503`
accepts **exactly one** destination per active sequence
(`if (_destinationAccepted) return false;`), so the Aim-time snapshot is
unique per sequence by construction. (This also disposes of a hazard I
went looking for: a second destination cannot supersede within a
sequence, and a new sequence routes through `OnTeleportStarted`
`ResetTransit`, which clears `_placementCommitted`/`_awaitingDeferredWake`
at `:932`.)
### C.3 — R7: the autonomy gate — **the approximation is real and needs its register row (BLOCKING)**
The retail reading is now exactly right, and I verified both halves:
- `CommandInterpreter::UsePositionFromServer` @0x006B3B40 (pseudo-C
`:699506-699510`) is literally `return this->autonomy_level != 2`.
acdream's `RuntimeCharacterState.UsePositionFromServer` (`:122`) is
`AutonomyLevel != FullAutonomyLevel(2)`**an exact port.**
- `CommandInterpreter::SendMovementEvent` @0x006B4680 gates on
`this->autonomy_level != 0` (@0x006B46BB, pseudo-C `:700283`).
So `!UsePositionFromServer` sends at level 2 only; retail sends at levels
1 **and** 2. The implementer's characterisation is accurate, and the
divergence is currently unreachable — `TrySetAutonomyLevel` has **zero
production callers** (only `RuntimeCharacterStateTests`), so
`AutonomyLevel` is always 2 and the two gates agree. Retail's own default
is 2 (`command_line_autonomy_level = 0x2`, pseudo-C `:1088429`).
That is precisely what an approximation is: correct today, wrong the day
someone wires level 1. CLAUDE.md's register rule is binding and admits no
implementer discretion — *"Any commit that introduces a deviation (an
adaptation, an approximation, a stopgap, a 'retail does X but we…') adds
its register row IN THE SAME COMMIT. … A deviation found without a row is
a bug twice over."* The shipped disposition is a code comment at
`RuntimeAcceptedPositionDriveController.cs:812-824` saying *"not
register-worthy on its own (no user-visible#-labeled symptom yet)"*. A
symptom is not the threshold; a deviation is. Either thread the raw
`AutonomyLevel` through (the exact port, and the constructor already
takes two optional funcs so the marginal cost is one more) or add the
row. A comment is not the register.
### C.4 — R6: the corrected `enter_world` banner is now accurate
Re-verified independently against the pseudo-C, not merely re-read:
- `:93797` @0x004550EC and `:93824` @0x00455095 both sit inside
**`SmartBox::HandleCreateObject` @0x00454C80**. ✓
- `CObjectMaint::CreateObject` is invoked at @0x00454FD8 *inside* that
function — a callee, not the enclosing scope. ✓
- @0x004550EC is in the `if (arg3 != this->player_id)` **non-player**
branch; only @0x00455095 follows `SmartBox::init_player` +
`CellManager::ChangePosition` in the player branch. ✓
- The load-bearing negative still holds: the `Position*` overload
@0x00516310 has exactly those two callers and the `int` overload
@0x00516170 is reached only from @0x00516327`enter_world` is not on
the portal path.
The banner now states all four facts correctly. This citation is safe for
future sessions to cite.
---
## §D — R4/A5: still open, and it does NOT block. Here is why the call changed.
Round 1 rated this MAJOR on the premise (taken from contract §3.4) that
the sink *"writes no pose"*, making the suffix the render entity's only
mover. **That premise was wrong, and the A10 correction is right.** I
verified `LiveEntityRuntime.TryApplyRuntimePlacementPlace`
(`LiveEntityRuntime.cs:1386-1420`): a `Place` receipt calls
`entity.SetPosition(projection.WorldPosition)`, sets `entity.Rotation`,
sets `entity.ParentCellId = token.ExactCellId`, and calls
`_spatial.RebucketLiveEntity``commitPose` defaults true and is passed
`false` only for `WithdrawalRestored` (`:1364-1372`). The canonical
receipt IS the mover; the suffix's writes are redundant repeats, exactly
as the rewritten class comment now says. The comment is verified true.
With that established, the coverage picture is materially different from
round 1:
- the sink's Place-receipt render-entity reframe + rebucket —
**covered** (`RuntimePlacementPresentationSinkTests.Place_ReframesAndRebucketsExactSidecarWithoutMutatingRuntimePhysics`);
- the sink's **portal gate discriminating****covered**
(`PortalPlace_RequiresExactCurrentTransitHostAndSequence`, which drives
a valid authority and a mismatched sequence);
- the suffix's own entity write + destination bucket ordering —
**covered** (the two `ConcretePlacement_*` tests, adapted to the new
signature against a real `LiveEntityRuntime`/`WorldEntity`/`GpuWorldState`);
- the **canonical body** pose and cell after an App-driven portal commit
**now covered**: the four weakened assertions were restored against
`harness.Movement.Controller.Position`/`.CellId`, including A6's
superseded-destination discriminator, and the App harness now runs a
real Runtime rather than a fake placement.
What remains uncovered is narrower than "the presentation suite was not
built": no single test composes teleport-controller → canonical commit →
real sink → suffix; the **local-player collision shadow** after a portal
commit (#312's own layer) is unasserted; the T8 overwrite ordering is
unasserted; and the refused-Place test asserts body/LoginComplete/IsActive
but not the render entity.
That is a genuine gap and it is #312-adjacent, so it must be recorded and
closed in C5 — the implementer flagged it explicitly rather than claiming
closure, which is the right behaviour. But it is no longer a
"nothing asserts the only mover" hole, and it does not block this slice.
---
## §E — new findings
### N1 — MEDIUM — `PendingCount == 0` does not mean "the portal committed"; R1's failure mode survives on a narrow path
`LocalPlayerTeleportController.cs:653-657`
```csharp
if (_awaitingDeferredWake)
{
if (_acceptedPositionDrive.PendingCount != 0)
return false;
_awaitingDeferredWake = false;
_placementCommitted = true; // ← inference
return true;
}
```
and identically `HeadlessSessionWorldProjection.cs:818`
(`committed = _acceptedPositionDrive.PendingCount == 0;`).
`PendingCount` is `_pending is null ? 0 : 1`
(`RuntimeAcceptedPositionDriveController.cs:330`) — a shared, arm-agnostic
counter. Three `Advance` paths clear a **portal** pending, and only one
of them commits:
| `Advance` site | commits? | logs? |
|---|---|---|
| `:918` completed-wake, authority current | yes → `ReconcileAndAcknowledgePortal` | yes |
| `:918` completed-wake, authority stale | body committed by `RetryDeferred`, **suffix skipped** | `AbandonedAtWake` |
| `:973` **watch died** — "most likely a subsequent accepted Position's merge-time Forget" | **no** | **no line at all** |
| `:1020` `!IsPlacementCurrent` on the prepare-retry | **no** | **no line at all** |
On the last two the caller latches `_placementCommitted = true` for a
placement that never happened: the sequencer is released, `Place` fires,
`ObserveMaterialized` acks a materialization that did not occur, and the
player is revealed at the **origin**. That is exactly R1's shape,
re-entered through the deferred door.
Reachability is genuinely low — both hosts now gate the attempt behind
collision readiness (`_worldReveal.Evaluate(...).IsReady` graphically,
`_collision.IsReady(...)` headless), so a park is rare — and the
Runtime-layer test `PortalDeferredCell_WakeAbandonsInsteadOfReconcilingWhenAuthorityWentStale`
proves at least one non-committing convergence exists
(`Assert.Equal(0, drive.PendingCount)` with no reconcile). Nothing tests
the caller's inference against it.
The fix is small: have the drive report a portal-specific terminal
outcome (it already distinguishes them well enough to log
`AbandonedAtWake`) instead of the caller inferring commit from a shared
counter. Also give the two silent branches a probe line — under D-T8 they
are portal-arrival attempts that ended.
### N2 — LOW/MEDIUM — headless `IsUnhydratable` is now hardcoded false
`HeadlessSessionWorldProjection.cs:874` reports `IsUnhydratable: false`
unconditionally, where it previously reported `!ready`. The old value was
itself a conflation (not-yet-resident ≠ unhydratable), so this is not a
regression in meaning — but headless can now never report an
unhydratable destination, so a genuinely unhydratable claim spins in
`PumpPortalCompletion` forever instead of taking AD-2's stated "loud
unhydratable-placement path". Either derive the real predicate or state
in the method's doc that headless does not model it.
### N3 — MEDIUM — a transient headless condition is now fatal
`HeadlessSessionWorldProjection.cs:854` throws `InvalidOperationException`
on the `default:` arm, which covers **`NotApplicable`** as well as
`Rejected`. `NotApplicable` is returned by
`TryExecuteAcceptedPortalArrival` for `record.PhysicsBody is null` and
for an active initial-Create residence
(`RuntimeAcceptedPositionDriveController.cs:243-251`) — hydration-race
conditions, not "the reveal is stale". The exception message asserts a
diagnosis ("the reveal itself is stale or the local player has no
canonical body, neither recoverable by waiting") that is true for
`Rejected` and not established for `NotApplicable`.
Refusing to fake success is right; converting a possibly-transient
condition into a process-killing throw on the host that must survive
K4's 30-session / two-hour endurance profile is the wrong end of that
trade. Split the arm: throw on `Rejected`, treat `NotApplicable` as a
retryable wait with a bounded attempt budget (or a loud log plus
`IsCollisionReady: false`).
### N4 — LOW — `RunsTeleportHook` over-gates the frame commit (§C.1)
### N5 — LOW — the plan-doc correction contradicts itself
`docs/plans/2026-08-02-placement-cutover.md` now opens *"This line was
accurate for both halves at the time it was written"*, explains that
route 3 added the consumer and producer together, and then closes with
*"'Zero producing call sites; the adapter does not exist' was accurate
only for the producer half"* — which contradicts the opening sentence.
The substance is right (the type, `IsValid`, `Pending.Portal`, the sinks'
gates and `BeginAcceptedPlacementCore`'s gate pre-dated route 3; the arm
did not); the paragraph needs one pass so a future reader can cite it.
---
## §E.2 — R8 residual
`LogPortalArrivalAttempt` now fires on every Runtime-side exit under the
gate's own `ACDREAM_PROBE_LOCAL_TELEPORT`, and a live `Contention`
refusal was observed — R8's substance is closed and **§9's gate is now
passable as specified**: `leash=armed` reads from
`ConstraintManager.IsConstrained`, which `ConstrainTo` @0x00556240 sets
unconditionally, so a committed arrival prints `armed`.
Two App-side refusal causes still never reach Runtime and therefore emit
no `[local-tp]` line: `cause=stale-reveal`
(`LocalPlayerTeleportController.cs:666`, the `CanPlacePortalDestination`
preflight) and `cause=host-token-unavailable` (`:737`). Both log through
`PhysicsDiagnostics.LogTeleport`, gated by the *different*
`ACDREAM_PROBE_TELEPORT`. Under the pinned gate environment these are
invisible. Either route them through `LogLocalTeleportArrival` or add
`ACDREAM_PROBE_TELEPORT=1` to §9's environment line.
---
## §F — gate evidence
- **Release build**: green, 0 warnings / 0 errors.
- **`AcDream.Runtime.Tests`**: 1,164 passed / 0 failed / **0 skipped**.
- **`AcDream.Headless.Tests`**: 85 passed / 0 failed / **0 skipped**.
- **`LocalPlayerTeleportControllerTests`**: 21 passed / 0 failed / **0 skipped**.
- No `Skip` attribute in any touched test file. No test weakened: the
four round-1 weakenings were restored with stronger targets (the real
canonical body rather than the deleted fake's captured argument), and
three genuinely new discriminators were added (`RefusedPlace_…`,
`PortalCommitted_UnderServerControlSendsNoMovementEvent`,
`PortalDeferredCell_WakeAbandonsInsteadOfReconcilingWhenAuthorityWentStale`,
plus the headless park test).
- Per process rule 3, none of this is evidence of correctness — it is
evidence that nothing regressed while the above holes remain.
---
## §G — what must land to pass
1. **N1** — stop inferring commit from `PendingCount`; report a
portal-specific terminal outcome, and log the two silent
non-committing `Advance` branches.
2. **R7** — add the AD register row for the autonomy approximation, or
thread the raw `AutonomyLevel` through and make it exact. Binding
project rule; not an implementer judgment call.
3. **N3** — do not throw on `NotApplicable`; split it from `Rejected`.
Cheap follow-ups, non-blocking: N2, N4 (gate only the UnStick/UnConstrain/
re-arm block on `RunsTeleportHook`), N5, and R8's two App-side causes.
Record as a dated, named C5 item: R4/A5's residual — the end-to-end
composition test, the local-player collision shadow after a portal
commit, and the T8 overwrite ordering.