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>
550 lines
29 KiB
Markdown
550 lines
29 KiB
Markdown
# C4 route 3 — architecture / adversarial review (2026-08-04)
|
|
|
|
**Verdict: FAIL.**
|
|
|
|
Reviewed: the uncommitted working-tree diff (`git diff HEAD` + untracked) on
|
|
`claude/acdream-physics-divergence-5aa784` at HEAD **`cd3129e9`** (route 7's
|
|
commit, "child cell propagation moves from a render tick into Runtime"). 16
|
|
files, +1,573/-216.
|
|
|
|
Reference documents read in full: the route-3 contract
|
|
(`2026-08-04-c4-route-3-contract.md`), the route-3 scoping, the route-2
|
|
contract, and the route-5/route-7 review defect classes.
|
|
|
|
Independent verification performed against source, not against the
|
|
implementer's summary: `RuntimeWorldTransitState`, `RuntimeSetPositionState`'s
|
|
commit tail, `RuntimeEntityObjectEventStream`/
|
|
`RuntimePlacementProjectionSubscription` (publication synchronicity),
|
|
`RuntimePlacementPresentationSink` + `LiveEntityRuntime
|
|
.TryApplyRuntimePlacementPlace`, `LocalPlayerShadowSynchronizer`,
|
|
`LocalPlayerProjectionController`, `TeleportAnimSequencer`, and both hosts'
|
|
`Advance()` pump sites. `dotnet build AcDream.slnx -c Debug` exits 0.
|
|
|
|
**The `_pendingDestination` fix — the item flagged as highest risk — is
|
|
correct, and is not the reason for the FAIL.** See §"Judgment on the
|
|
`_pendingDestination` lifetime" at the end.
|
|
|
|
The FAIL rests on A1 and A2: the slice adds five new ways for the Place edge
|
|
to refuse, and the refusal path it hands them to does not stop the teleport
|
|
animation stream. One of those five (`DeferredCell`) additionally commits the
|
|
placement out of band after the transit has ended, which both splits the body
|
|
from presentation and leaves a placement receipt nothing can consume — proof
|
|
obligations **P3** (graphical half) and **P4** are undischarged, and D-T2.4's
|
|
"the wake path must re-validate the portal authority before committing" is not
|
|
implemented at all.
|
|
|
|
---
|
|
|
|
## MAJOR
|
|
|
|
### A1 — A refused canonical Place does not stop the teleport animation; the player is released into the world without ever having been placed
|
|
|
|
**Severity: MAJOR (FAIL basis).**
|
|
|
|
`src/AcDream.Core/World/TeleportAnimSequencer.cs:134-142`:
|
|
|
|
```csharp
|
|
case TeleportAnimState.Tunnel:
|
|
if (worldReady)
|
|
{
|
|
evts.Add(TeleportAnimEvent.Place);
|
|
Advance(TeleportAnimState.TunnelContinue, enterTunnel: false);
|
|
_continueElapsed = 0f;
|
|
}
|
|
break;
|
|
```
|
|
|
|
`TeleportAnimEvent.Place` is emitted **exactly once**, and the sequencer
|
|
advances to `TunnelContinue` in the same statement block, unconditionally and
|
|
with no knowledge of whether the consumer's handler succeeded. There is no
|
|
path back to `Tunnel`.
|
|
|
|
`src/AcDream.App/Streaming/LocalPlayerTeleportController.cs:505-526`:
|
|
|
|
```csharp
|
|
case TeleportAnimEvent.Place:
|
|
if (!_worldReveal.CanPlacePortalDestination(...)) return;
|
|
if (!TryExecuteCanonicalPortalPlacement(sequence))
|
|
return; // <- new in this slice
|
|
...
|
|
_placement.Place(_pendingRotation);
|
|
...
|
|
_worldReveal.ObserveMaterialized(...);
|
|
```
|
|
|
|
The `return` exits `Tick`, but the sequencer has already left `Tunnel`. Every
|
|
subsequent `Tick` therefore runs the rest of the stream with **no placement
|
|
and no materialization**:
|
|
|
|
| next event | what runs |
|
|
|---|---|
|
|
| `TunnelContinue` → `TunnelFadeOut` | — |
|
|
| `PlayExitSound` (`:532-542`) | `_worldReveal.RevealWorldViewport()` + `_presentation.ExitTunnel()` |
|
|
| `FireLoginComplete` (`:543-554`) | `_mode.EnterWorld()`, `_session.SendLoginComplete()`, `_worldReveal.Complete()`, `ResetTransit(clearSession: false)` |
|
|
|
|
`WorldRevealCoordinator.RevealWorldViewport` only needs a live host projection
|
|
(`WorldRevealCoordinator.cs:258-266`) — present. `Complete()` reaches
|
|
`RuntimeWorldTransitState.Complete` (`:701-706`), which hits
|
|
`FailInvariant("portal-complete-before-materialized")` and returns `false`;
|
|
`FailInvariant` (`:934-946`) only increments a counter and logs — it does not
|
|
throw and does not stop the caller. `ResetTransit` then runs `EndTeleport()` +
|
|
`_worldReveal.Cancel()`.
|
|
|
|
**Concrete failure scenario.** A `Contention` outcome (route 2's force arm, a
|
|
route-7 parent drive, or an earlier park still owns the entity's placement
|
|
token at the Place edge) makes `TryExecuteAcceptedPortalArrival` return
|
|
`Contention` at `RuntimeAcceptedPositionDriveController.cs:492-501`. The
|
|
player watches the portal tunnel finish normally, the world viewport is
|
|
revealed, `LoginComplete` is sent to ACE — and the player is standing at the
|
|
**pre-teleport position** in the **pre-teleport cell**. ACE has them at the
|
|
destination. Every subsequent server broadcast fights the client. Nothing in
|
|
the client logs above `[world-reveal] event=invariant-failure` (a `SafeLog`
|
|
line), and the D-T8 probe never emits because
|
|
`ReconcileAndAcknowledgePortal` never ran.
|
|
|
|
**Why this is the slice's problem and not inherited.** The pre-existing
|
|
`CanPlacePortalDestination` early return (`:506-512`) has the same shape, but
|
|
it fires only when the transit no longer owns this reveal — a case where
|
|
marching on is at worst redundant, because a newer transit owns the world.
|
|
This slice adds **five new refusal causes that all fire while the transit is
|
|
perfectly healthy**: `host-token-unavailable`, `NotApplicable`, `Rejected`,
|
|
`Contention`, and `DeferredCell` (`LocalPlayerTeleportController.cs:596-625`,
|
|
which treats everything except `Committed` as a refusal at `:619`).
|
|
|
|
**Contract obligations violated.** §4 item 4 ("on every refusal … the transit
|
|
remains coherent … never a half-state … no path leaves the player permanently
|
|
in portal space with a dead operation"); §4 item 5 ("a refused placement must
|
|
NOT … `RevealWorldViewport`, must NOT advance the anim-event stream's terminal
|
|
events"); D-T5's Begin-refusal row ("the anim stream stays where it is, so the
|
|
NEXT Tick re-attempts the Place edge … 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"); and proof obligation **P4**
|
|
verbatim. P4 was to be discharged by reading `TeleportAnimSequencer`. It is
|
|
one-shot. No re-attempt driver exists.
|
|
|
|
**Fix direction.** Two shapes are available without touching the sequencer
|
|
(stop condition 2 forbids sequencer timing changes):
|
|
|
|
1. Make the Place edge idempotent-and-latched at the controller: keep a
|
|
`_placementCommitted` flag; on a refusal, do NOT let the stream reach
|
|
`PlayExitSound`/`FireLoginComplete` — gate those two cases on the latch and
|
|
drive a bounded re-attempt from the same `ready` predicate that produced
|
|
the Place event (the contract's stated fallback). A refusal that never
|
|
converges must then take the existing transit cancellation
|
|
(`ResetTransit(clearSession: false)` — which already cancels the reveal and
|
|
restores presentation) rather than a silent world release.
|
|
2. Or treat a refusal as an immediate transit cancellation and let the
|
|
existing supersession path own recovery. Louder, smaller, and it satisfies
|
|
D-T5's "never a silent wedge" — but it needs the user's eyes because it is
|
|
user-visible (the portal fails and the player stays put) rather than a
|
|
silent desync.
|
|
|
|
Either way this needs a test: *refused arm → the anim stream does not reach
|
|
`RevealWorldViewport`/`FireLoginComplete` with an unplaced body.*
|
|
|
|
---
|
|
|
|
### A2 — A `DeferredCell` portal park splits the body from presentation and leaves a placement receipt nothing can consume (P3 undischarged on the graphical host; D-T2.4's re-validation missing)
|
|
|
|
**Severity: MAJOR.**
|
|
|
|
`RuntimeAcceptedPositionDriveController.SubmitAndResolvePortal:614-645` parks a
|
|
`DeferredCell` outcome into `_pending` carrying the portal authority.
|
|
`LocalPlayerTeleportController.TryExecuteCanonicalPortalPlacement:619` returns
|
|
`false` for it, so A1's march runs: the stream reaches `FireLoginComplete`,
|
|
`ResetTransit` calls `_transit.EndTeleport()` and `_worldReveal.Cancel()`.
|
|
|
|
The park is still live. `Advance()` is pumped by the graphical host at
|
|
`src/AcDream.App/Net/GraphicalSessionEventRoute.cs:117` and
|
|
`src/AcDream.App/World/LiveEntityHydrationController.cs:416`. When the
|
|
destination landblock's collision generation eventually commits:
|
|
|
|
- `Advance:763-771` runs `ReconcileAndAcknowledgePortal` — the body moves, the
|
|
leash re-arms, autorun cancels, and **one outbound movement event is sent** —
|
|
seconds after the player was already released into the world at the old
|
|
position. Presentation is never told: `_placement.Place` and
|
|
`ObserveMaterialized` are unreachable (the anim event is one-shot, A1).
|
|
- The commit publishes a `Place` receipt whose `Token.Portal` still names the
|
|
ended reveal. `RuntimeWorldTransitState.IsCurrentPlacementAuthority:258-275`
|
|
requires `IsCurrentPortalDestination` (`:870-882`), which requires
|
|
`_teleportActive` — cleared by `EndTeleport` (`:547-556`). It returns false
|
|
**forever**.
|
|
- `RuntimePlacementPresentationSink.TryApply:100-106` therefore returns
|
|
`false`; `RuntimePlacementProjectionSubscription.OnPlacement:134-136` leaves
|
|
the receipt at the FIFO head. Every later placement receipt **for every
|
|
entity** is blocked behind it, and `AcceptedPositionDrivePendingCount` never
|
|
returns to zero, so `GameWindowLifetime.DisposeGameRuntime` throws on
|
|
shutdown.
|
|
|
|
That is precisely the failure mode P3 exists to rule out ("A receipt nothing
|
|
can ever consume or retire is a FIFO wedge"). P3 was discharged only for the
|
|
headless *happy path*; the graphical park was not walked.
|
|
|
|
Independently, **D-T2.4's explicit requirement is not implemented**: "its wake
|
|
path must re-validate the portal authority before committing". Neither
|
|
`Advance`'s `AwaitingCommitWake` branch (`:763-771`) nor its
|
|
`IsPlacementCurrent` re-submit branch (`:813-819`) re-checks the portal
|
|
authority — they pass `pending.Portal` straight through.
|
|
|
|
**Fix direction.** (a) Re-validate the portal authority at both wake points
|
|
(`_entityObjects` has no transit handle today — the drive needs a
|
|
`Func<bool>`-style currency predicate or the authority passed back through the
|
|
transit owner); on failure take `AbandonPending`'s exact shape
|
|
(`restoreCancelledPark: true` + `PublishCancellation`) so the park is retired
|
|
rather than committed. (b) Independently, A1's fix must prevent the transit
|
|
from ending while a portal park is outstanding.
|
|
|
|
---
|
|
|
|
## MEDIUM
|
|
|
|
### A3 — Headless discards the arm's status entirely; a failed placement is silent
|
|
|
|
`src/AcDream.Headless/Hosting/HeadlessSessionWorldProjection.cs:768-780`:
|
|
|
|
```csharp
|
|
if (_acceptedPositionDrive is not null)
|
|
{
|
|
var authority = new RuntimePortalPlacementAuthority(...);
|
|
_ = _acceptedPositionDrive.TryExecuteAcceptedPortalArrival(destination, authority);
|
|
}
|
|
```
|
|
|
|
Two problems. First, the status is discarded: any non-`Committed` outcome
|
|
leaves the body unmoved while `TryCompletePortal`
|
|
(`RuntimeLiveEntitySessionController.cs:530-585`) proceeds through
|
|
`AcknowledgeDestinationReadiness` → `AcknowledgePortalMaterialized` →
|
|
`Complete` → `TerminalProjected` → `LoginComplete` → `EndTeleport` — asserting
|
|
a materialization that did not happen (§4 item 5) and telling ACE the login
|
|
completed. The deleted `ResynchronizeLocalPlayerForPortalArrival` was
|
|
unconditional and could not fail this way. Second, the `is not null` guard
|
|
means a composition regression that fails to wire the drive silently disables
|
|
headless portal placement with no signal at all; the previous code had no such
|
|
mode.
|
|
|
|
**Fix direction.** Treat a non-`Committed` status as a hard failure on this
|
|
path (`TryCompletePortal` already throws on every other Runtime refusal —
|
|
match that), and make the drive a required constructor dependency for the
|
|
production projection.
|
|
|
|
### A4 — Both inversions are hardcoded; the classifier route facts that encode them are never read
|
|
|
|
`ReconcileAndAcknowledgePortal(RuntimeEntityRecord, in RuntimeAuthoritativePositionRoute route, in RuntimePortalPlacementAuthority)`
|
|
(`RuntimeAcceptedPositionDriveController.cs:667-698`) **never references
|
|
`route`**. `PlayerMovementController.CommitCanonicalTeleportFrame:1987-2035`
|
|
unconditionally zeroes velocity, runs `StopCompletelyAtPhysicsObjectBoundary`,
|
|
`UnStick`/`UnConstrain`, and `RearmConstraintLeashAtCurrentPosition`.
|
|
|
|
So `route.ZeroVelocity`, `route.ConstrainPhase`, and `route.TeleportHookPhase`
|
|
are recorded-not-consumed, even though D-T2.3 pinned "`route.ZeroVelocity` is
|
|
honored at the commit". The visible behaviour is correct today only because
|
|
the classifier's LocalPlayer-teleport branch happens to agree with the
|
|
hardcoded method.
|
|
|
|
Consequence for test quality: the contract's own §8 item 11 sabotage —
|
|
"hardcode the force route onto the portal arm (`ConstrainPhase.None`) → test
|
|
5a fails" — **cannot fail**, because no code path reads `ConstrainPhase`. No
|
|
test discriminates the classifier from the executor. A future classifier edit
|
|
(the classifier is a shared surface routes 2/4b-2/4b-3 also consume) diverges
|
|
from behaviour silently.
|
|
|
|
**Fix direction.** Either consume the route facts in the frame commit (branch
|
|
on `ZeroVelocity`/`ConstrainPhase`) and add the discriminating test, or delete
|
|
the unused `route` parameter and state explicitly, in the class doc and in
|
|
AD-2, that the inversions are enforced by `CommitCanonicalTeleportFrame` and
|
|
NOT by the classifier — so the next reader does not trust a route fact that
|
|
nothing reads.
|
|
|
|
### A5 — Contract §8 item 8's committed-receipt presentation suite is missing; invariant 6's "the local-player collision shadow agrees" is asserted nowhere
|
|
|
|
No test in this diff drives a committed portal placement through the **real**
|
|
`RuntimePlacementPresentationSink` plus the suffix.
|
|
`tests/AcDream.App.Tests/World/RuntimePlacementPresentationSinkTests.cs` is
|
|
untouched; its only portal test
|
|
(`PortalPlace_RequiresExactCurrentTransitHostAndSequence:467`) drives a
|
|
synthetic token, not a local-player placement. The App teleport tests assert
|
|
`harness.Placement.Called` against a *fake* placement.
|
|
|
|
That missing suite would have surfaced the following latent inconsistency,
|
|
which route 3 newly makes reachable on the portal path:
|
|
|
|
- `RuntimePlacementPresentationSink.TryPublishPlace:226-232` writes
|
|
`_localPlayerShadow.Set(entity.Position, entity.Rotation, record.FullCellId)`.
|
|
`LocalPlayerShadowState.Set` updates **only the cache** — it never touches
|
|
`PhysicsEngine.ShadowObjects`.
|
|
- `LocalPlayerShadowSynchronizer.SyncPose:59-70` dedups against that same
|
|
cache (`cellId` equal AND position within 1 cm AND orientation within
|
|
tolerance ⇒ return without publishing).
|
|
- `LocalPlayerProjectionController.Project:102` early-returns for
|
|
`PlayerState.PortalSpace`, so the sink's cache write is the last word until
|
|
the player re-enters the world.
|
|
|
|
Net: the first post-arrival `SyncShadow` sees a cache that already claims the
|
|
resolved pose and resolved cell, skips, and the player's collision shadow row
|
|
is not published at the destination. It self-heals the first time the player
|
|
moves more than ~1 cm, so the window is short — but during it, other entities
|
|
have no collider for the player at the destination. (The shape is pre-existing
|
|
from route 2's force arm, where `Project` runs every frame so the window is
|
|
one frame; route 3's portal-space skip widens it.)
|
|
|
|
**Fix direction.** Write the §8 item 8 suite. Independently, either have the
|
|
suffix re-publish the shadow through `LocalPlayerShadowSynchronizer`
|
|
(`force: true`) after writing the resolved entity pose, or stop the sink from
|
|
writing a "last published" cache entry it did not publish.
|
|
|
|
### A6 — The one test that discriminated *which* destination gets placed lost its discriminator
|
|
|
|
`tests/AcDream.App.Tests/Streaming/LocalPlayerTeleportControllerTests.cs`,
|
|
superseded-teleport scenario:
|
|
|
|
```diff
|
|
- Assert.Equal(new Vector3(2f), harness.Placement.Position);
|
|
+ Assert.True(harness.Placement.Called);
|
|
```
|
|
|
|
The old assertion proved the **second** destination `(2,2,2)` was placed and
|
|
not the first `(1,1,1)`. `Assert.True(Called)` cannot distinguish them. That is
|
|
exactly the property the `_pendingDestination` caching change puts at risk
|
|
(caching an Aim-time value instead of re-reading). Two sibling tests lose the
|
|
same class of assertion, and one now contradicts its own name:
|
|
`SameLandblockDestination_DoesNotRecenterAndKeepsTranslatedPosition` no longer
|
|
asserts any position or cell.
|
|
|
|
This is the route-5 "tests that assert only negatives"/weakened-successor
|
|
defect class. The replacement assertion is **available**: the harness now owns
|
|
a real Runtime `RuntimeLocalPlayerMovementState`, so
|
|
`Assert.Equal(expected, movement.Controller!.Position)` and `.CellId` are
|
|
reachable; the harness simply does not expose `movement`.
|
|
|
|
**Fix direction.** Expose the Runtime controller on the harness and restore a
|
|
positive position/cell assertion in each of the three tests, at minimum in the
|
|
superseded-teleport one.
|
|
|
|
### A7 — Dual-host parity (§8 item 6) is not met; the headless flip is untested
|
|
|
|
`tests/AcDream.Headless.Tests/HeadlessSessionHostTests.cs:413-424` now
|
|
documents that the fixture's projection is built without a drive controller,
|
|
so "the canonical portal arm this method now calls is a no-op here by
|
|
construction", and defers the headless committed-portal test as "an open item,
|
|
not attempted here given this session's time budget".
|
|
|
|
The contract states plainly: "The headless arm reuses the identical Runtime
|
|
entry — dual-host parity is a test obligation, not an aspiration (§8)."
|
|
Combined with A3, the entire headless production placement flip — the deletion
|
|
of `ResynchronizeLocalPlayerForPortalArrival`, the new `portal` parameter, the
|
|
new call — has zero behavioural coverage. The only surviving assertion change
|
|
is `CenterCount` 3→2.
|
|
|
|
---
|
|
|
|
## MINOR
|
|
|
|
### A8 — Portal-test cleanup runs at the end of the test body, so the first assertion failure is masked by a teardown throw
|
|
|
|
`ConvergePortalHost` is invoked as the last statement of each portal test
|
|
(`RuntimeAcceptedPositionDriveControllerTests.cs:1273/1380/1423/1519`). If any
|
|
earlier `Assert` throws, cleanup is skipped, `StartedRuntime.Dispose()` throws
|
|
during unwinding, and C# `using`/`try…finally` lets the finally-exception
|
|
**replace** the in-flight one.
|
|
|
|
The implementer diagnosed their own instance of this correctly (claim 1 —
|
|
verified: the mechanism is real and the two corrected assertions were genuine
|
|
test bugs, since an accepted `TeleportAdvanced` merge rebases the world frame
|
|
onto the destination per #283, and `ReconcileAndAcknowledgePortal` does
|
|
legitimately send one movement event). But the pattern remains in the shipped
|
|
tests, and it is exactly how a genuine host-projection leak would also present
|
|
— which is why it is worth removing rather than remembering.
|
|
|
|
**Fix direction.** `try { … } finally { ConvergePortalHost(…); }`, or make
|
|
`StartedRuntime.Dispose` record non-convergence and assert it explicitly.
|
|
|
|
`ConvergePortalHost` itself is otherwise sound: it cannot double-release —
|
|
`AcknowledgeHostProjection`'s `TerminalProjected` branch removes the record
|
|
(`RuntimeWorldTransitState.cs:343-355`) and a second call returns false — and
|
|
it cannot leak, because a forgotten call throws at Dispose.
|
|
|
|
### A9 — Two sources for one teleport sequence
|
|
|
|
`LocalPlayerTeleportController.cs:613` builds the authority's
|
|
`TeleportSequence` from `_transit.ActiveTeleportSequence` (passed in as
|
|
`sequence`), while `ClassifyPortalArrival`
|
|
(`RuntimeAcceptedPositionDriveController.cs:514-535`) derives its
|
|
accepted/prior pair from `destination.TeleportSequence`. They agree today —
|
|
`OfferTeleportDestination:490-497` refuses a second destination for an
|
|
already-accepted active sequence — but two sources for one fact is the
|
|
campaign's "mapping written against one caller's reachable set" shape.
|
|
|
|
**Fix direction.** Use `destination.TeleportSequence` in both, or assert
|
|
equality at the producer.
|
|
|
|
### A10 — Two documentation statements assert behaviour the code does not have
|
|
|
|
(a) The new class doc on `LocalPlayerTeleportPlacement`
|
|
(`LocalPlayerTeleportController.cs:188-193`) says the sink "snapshots whatever
|
|
the entity already holds and writes no pose itself — this is the render
|
|
entity's mover". `TryPublishPlace` writes no pose, but the sink's own upstream
|
|
call chain does: `RuntimePlacementPresentationSink.TryApply:108` →
|
|
`LiveEntityRuntime.TryApplyRuntimePlacementProjection:1301` →
|
|
`TryApplyRuntimePlacementPlace:1386-1420`, which performs
|
|
`entity.SetPosition(projection.WorldPosition)`, `entity.Rotation = …`,
|
|
`entity.ParentCellId = token.ExactCellId`, **and** `RebucketLiveEntity` — all
|
|
before `TryPublishPlace` snapshots. The suffix's entity writes and rebucket are
|
|
therefore redundant repeats of a mutation the canonical receipt already made.
|
|
|
|
This is harmless at runtime today, but it is the "a doc asserting behaviour
|
|
the code does not have" class — and the contract carries the same misreading
|
|
(§3.4, D-T4, §12.5(b)), so correcting the code comment alone is not enough.
|
|
|
|
(b) The plan correction in `docs/plans/2026-08-02-placement-cutover.md` says
|
|
"`RuntimeAcceptedPositionDriveController`'s portal arm reading it — was
|
|
already live before route 3 (from route 2's shared drive controller)". The
|
|
portal arm was added by **this** slice. A correction that itself asserts a
|
|
false fact is worse than the line it corrects.
|
|
|
|
### A11 — `PhysicsDiagnostics.LocalTeleportHostKind` is a process-global mutable set from a host
|
|
|
|
`src/AcDream.Core/Physics/PhysicsDiagnostics.cs` + `HeadlessSessionHost.cs:625`.
|
|
Correct under K3/K4 (all sessions in a headless process are headless), and the
|
|
doc comment says so — noted only so it is not later mistaken for per-session
|
|
state. No action required this slice.
|
|
|
|
### A12 — `AddSyntheticIndoorCell` is representative enough to pass the gate, and no more
|
|
|
|
The helper registers a `CellPhysics` with an empty `Resolved` polygon
|
|
dictionary, one `PortalInfo(0,0,0)`, and a leaf-only BSP root. It is not shaped
|
|
to make a specific assertion pass — it mirrors
|
|
`RuntimeSetPositionStateTests.AddSyntheticCell` and its only effect is to make
|
|
`PhysicsEngine.IsSpawnCellReady` return true for an indoor cell, which is a
|
|
genuine fixture gap (a bare `AddLandblock` passes an empty `CellSurface` list,
|
|
so indoor destinations parked `DeferredCell` forever). Accepted.
|
|
|
|
The caveat: because the destination cell has no geometry, the App-layer tests
|
|
prove "the arm returned `Committed`", not "the destination resolved somewhere
|
|
sane". That makes A6's missing position assertions more load-bearing, not
|
|
less.
|
|
|
|
---
|
|
|
|
## Verified correct (checked against source, not taken on report)
|
|
|
|
- **P1 (the D-T3 duty map).** `RuntimeSetPositionState.cs:5036-5058` calls
|
|
`PhysicsObjUpdate.CommitSetPositionContactTransition` unconditionally inside
|
|
the canonical commit, and `CommitSetPositionContactPrefix`
|
|
(`PhysicsObjUpdate.cs:153-175`) derives `Contact`/`OnWalkable`/
|
|
`WaterContact` from the placement result's own `InContact`/`OnWalkable`. Not
|
|
re-seeding `TransientState` in `CommitCanonicalTeleportFrame` is correct and
|
|
is more faithful than `SetPositionCore`'s unconditional
|
|
`Contact|OnWalkable|Active` overwrite, exactly as claimed.
|
|
- **P3, happy path, both hosts.** `RuntimeEntityObjectEventStream
|
|
.PublishPlacement:164-171` → `EnqueueAndDrain` →
|
|
`RuntimePlacementProjectionSubscription.OnPlacement:122-150` is
|
|
**synchronous**, inside `CommitCanonical`. Headless's placement therefore
|
|
commits and its receipt is consumed inside `PrepareDestination`, strictly
|
|
before `AcknowledgePortalMaterialized`/`Complete`/`EndTeleport`. The
|
|
receipt-past-`EndTeleport` hazard is discharged for the committed path. (The
|
|
residual is A2's park.)
|
|
- **Trap T7 / route-2 blast radius.** No portal pending reaches
|
|
`SettlePending` or `_newestForce`: the three `pending.Portal.Present` guards
|
|
at `:763`, `:794`, and `:820` fence every terminal path, and
|
|
`SubmitAndResolvePortal` is a genuine sibling of `SubmitAndResolve` rather
|
|
than an overload of it. `git diff` shows **zero** expectation changes in any
|
|
force-arm test — the contract's §4 item 8 tripwire is clean.
|
|
- **Implementer claim 1 (the teardown throw was a test bug).** Mechanism
|
|
verified. Both corrected assertions were genuinely wrong for the stated
|
|
reasons, and the `using`-finally exception-replacement is real. See A8 for
|
|
the residual.
|
|
- **Sabotage B's asymmetry.** Verified structurally:
|
|
`PortalProducerInvalidAuthority_ArmDoesNotRunAndNothingMutates` builds an
|
|
authority with `RevealGeneration: 0`, which fails
|
|
`RuntimePortalPlacementAuthority.IsValid` at
|
|
`TryExecuteAcceptedPortalArrival:459` — **before** `ClassifyPortalArrival` is
|
|
reached. Forcing the classifier to reject cannot change that test's outcome,
|
|
so the 4-of-5 asymmetry is exactly what the code shape predicts. Good
|
|
evidence.
|
|
- **Register bookkeeping.** AD-42's deletion is justified (its last citation
|
|
was D2's two-call `Resolve`+`ResolvePlacement`, which is gone); AD-2's
|
|
amendment states the deferred-place adaptation, the T8 tolerance, and the
|
|
leash-anchor nuance as D-T9 required; the `:2276` stale comment correction
|
|
landed; the 2026-07-16 pseudocode `enter_world` correction landed. Row count
|
|
49→48 is consistent.
|
|
- **Build.** `dotnet build AcDream.slnx -c Debug` exit 0 at the reviewed tree.
|
|
|
|
---
|
|
|
|
## Judgment on the `_pendingDestination` lifetime
|
|
|
|
**The cached destination's lifetime is correct. I found no way to Place
|
|
against a superseded destination, and the Place-time re-read it replaced
|
|
protected against nothing.**
|
|
|
|
The reasoning, checked against source:
|
|
|
|
1. **The bug was real and total.** `RuntimeWorldTransitState
|
|
.TryBeginPortalReveal:159-183` clears `_hasAcceptedDestination` and
|
|
`_acceptedDestination` at `:180-181` on success.
|
|
`TryGetAcceptedTeleportDestination:522-527` returns `_teleportActive &&
|
|
_hasAcceptedDestination`. Since `AimDestination` drives
|
|
`TryBeginPortalReveal` through `WorldRevealCoordinator.TryBeginPortal`
|
|
(`:742`), the slot is empty at every Place edge. The old re-read could
|
|
never succeed — every real portal placement would have refused with
|
|
`cause=host-token-unavailable`. Not a stale-destination guard; a hard
|
|
failure.
|
|
|
|
2. **Write/clear is in exact lockstep with `_pendingCell`.**
|
|
`_pendingRotation`/`_pendingCell`/`_pendingDestination`/
|
|
`_hasPendingDestination` are written together at `:780-783` and cleared
|
|
together at `:801-805` in `ResetTransit`. `_pendingCell != 0u` is itself the
|
|
`haveDestination` predicate (`:481`), so the two cannot diverge.
|
|
`_pendingDestination.Position.ObjCellId` **is** `_pendingCell` by
|
|
construction (`Position position = destination.Position;` at `:715`).
|
|
|
|
3. **A second Aim cannot produce a mismatched pair.** Supersession by a new
|
|
F751 goes through `OnTeleportStarted` → `ResetTransit(clearSession: false)`,
|
|
which clears all four fields and bumps `_lifetimeGeneration`. Supersession
|
|
by a second destination on the *same* sequence is impossible:
|
|
`OfferTeleportDestination:490-497` returns `false` once `_destinationAccepted`
|
|
is set, and `TryBeginPortalReveal` clears only `_hasAcceptedDestination`,
|
|
leaving `_destinationAccepted` latched for the life of the reveal. So the
|
|
destination is pinned from Aim to terminal, by the transit itself.
|
|
|
|
4. **The one torn window fails closed.** `_pendingRevealGeneration` is written
|
|
at `:749`, before the `IsCurrentLifetime`/recenter guards at `:750`,
|
|
`:757`, `:764`, while the other three are written at `:780-783`. A `false`
|
|
return from any of those guards leaves a NEW generation paired with an OLD
|
|
cell/destination. Both Place-edge gates then refuse:
|
|
`CanPlacePortalDestination(newGen, seq, oldCell)` fails
|
|
`IsCurrentPortalDestination`'s `destinationCell == _snapshot.DestinationCell`
|
|
check, and `TryRegisterHostProjection(newGen, oldCell)` fails the same
|
|
comparison at `RuntimeWorldTransitState.cs:197-209`. Neither can commit a
|
|
stale pair. (This tearing predates the slice — `_pendingCell` already had
|
|
it; `_pendingDestination` does not worsen it.)
|
|
|
|
5. **Terminal clearing.** Commit → `FireLoginComplete` → `ResetTransit`.
|
|
Session reset / generation reset → `ResetSession` / `ResetGenerationPresentation`
|
|
→ `ResetTransit`. Cancellation through `ResetTransit(clearSession: false)`.
|
|
A *refused* Place leaves the fields set — but so does `_pendingCell`, and
|
|
both Place-edge gates are keyed on the reveal generation, so a retained
|
|
value is inert until a new Aim overwrites it or a reset clears it.
|
|
|
|
The one thing the caching genuinely costs is test coverage, not correctness:
|
|
A6 removed the only assertion that could have distinguished a stale cached
|
|
destination from a fresh one. Restore it.
|
|
|
|
---
|
|
|
|
## Summary
|
|
|
|
| # | Severity | Finding |
|
|
|---|---|---|
|
|
| A1 | MAJOR | Refused Place does not stop the anim stream; player released into the world unplaced. P4 undischarged. |
|
|
| A2 | MAJOR | `DeferredCell` park commits after `EndTeleport`: body/presentation split + unconsumable Place receipt (P3 graphical half, D-T2.4 re-validation missing). |
|
|
| A3 | MEDIUM | Headless discards the arm's status; `is not null` guard silently disables placement. |
|
|
| A4 | MEDIUM | `route.ZeroVelocity`/`ConstrainPhase`/`TeleportHookPhase` never read; inversions hardcoded; contract sabotage 3 cannot fail. |
|
|
| A5 | MEDIUM | §8 item 8 presentation suite missing; local-player shadow invariant unasserted, and a real cache/publish desync sits behind it. |
|
|
| A6 | MEDIUM | Superseded-teleport test lost its destination discriminator; two sibling tests weakened, one now contradicts its name. |
|
|
| A7 | MEDIUM | Dual-host parity test obligation (§8 item 6) not met; headless flip untested. |
|
|
| A8 | MINOR | Portal-test cleanup outside `finally` masks the first failure behind a Dispose throw. |
|
|
| A9 | MINOR | Two sources for the teleport sequence (transit vs destination). |
|
|
| A10 | MINOR | Class doc and plan correction each assert behaviour the code does not have. |
|
|
| A11 | MINOR | Process-global `LocalTeleportHostKind` (accepted, noted). |
|
|
| A12 | MINOR | Synthetic indoor cell is geometry-free — fine as a gate, weak as a placement oracle. |
|