Two read-only research landings that unblock the last two C4 routes. **Parent-cell propagation (unblocks route 7).** Retail DOES re-cell children when the parent crosses a cell, recursively to unbounded depth. SetPositionInternal @0x00515330 branches on `this->cell == curr_cell` @0x0051536d; the changed branch reaches change_cell @0x00513390, whose delegates leave_cell @0x00510f50 and enter_cell @0x00510ed0 self-recurse over children and write the FULL identity (add_object @0x00510ee2, objcell_id @0x00510f1e, part-array cell id @0x00510f2b, cell pointer @0x00510f35). change_cell itself has no child loop — the recursion is in the delegates. The clincher: update_object @0x00515d10 early-returns on `parent != 0` @0x00515d40, so a child never runs its own physics tick and parent propagation is the ONLY mechanism maintaining its cell. The trap this retires: the depth-1 loop @0x0051539c-0x005153d8 is the SAME-CELL fast path (objcell_id and part-array id only, deliberately not the cell pointer), not the propagation. An implementer finding it first would conclude "depth-1, id-only" and ship equipped items stranded at landblock boundaries — the #184 class. Route 7's planned set_parent-only write would have done exactly that. Settled by READING, not by a debugger trace. The scoping had listed this as needing live cdb evidence, but change_cell/set_cell's child handling had simply never been read; the project's grep -> decompile -> verify order had not been exhausted. One BN field-name gap was closed by walking struct CPhysicsObj in the verbatim acclient.h, so no PE byte-decode was needed either. A breakpoint set is recorded for optional confirmation only. **Route 3 scoping (portal, the last route).** ~225-400 added non-comment production lines, ONE slice, contingent on #280 splitting out. Portalling works today; route 3 removes a duplicate authority (LocalPlayerTeleportPlacement.Place), it does not fix a bug. Eight dated-inventory claims are now false, the most consequential being "the binding machinery is 100% dormant end to end" — the portal authority's CONSUMPTION and validation side is live production code at three layers and is exercised by every placement; only the PRODUCER adapter is missing. That makes route 3 materially smaller than the campaign plan implies. #280 SPLITS from route 3, definitively: it is a reveal-gate/prefetch-window concern (WorldRevealReadinessBarrier's neighbourhood radius versus retail's mid_radius, LScape::PreFetchCells @0x00505660 / SmartBox::SetRegion @0x00453227), mechanically disjoint from the placement cutover — route 3 reads the ready predicate, #280 rewrites it. The campaign plan already sequences #280 separately; only the session handoff said it "rides with" route 3, and the plan is right. Retail's local portal arrival is the GENERIC path for the third route running: SmartBox::TeleportPlayer @0x00453910 is SetPositionSimple(player, dest, 1) with flags 0x1012 — route 2's exact primitive — plus PlayerPositionUpdated. Two rule inversions recorded so route 3's implementer cannot carry the wrong rule forward from the routes just landed: route 2's "never re-arm the leash" INVERTS here (the teleport branch arms ConstrainTo @0x0045418A and zeroes velocity @0x004541B4), and 4b-3's hook-before-placement ordering INVERTS (the local teleport_hook runs AFTER placement, from PlayerPositionUpdated @0x004538AE). The classifier's dormant LocalPlayer-teleport route already encodes both. Two documentation defects found in passing and recorded, not fixed: the 2026-07-16 portal pseudocode attributes portal arrival to enter_world (that is the login path), and a stale comment hides a live second writer — the generic wire-pose write does run for the local player (AP-131/C5 scope). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
43 KiB
C4 route 3 — portal / local-player placement: SCOPING (2026-08-04)
This is scoping, not a pinned contract. Verified at HEAD cff52c44, branch
claude/acdream-physics-divergence-5aa784, clean tree. Line numbers are
as-of-HEAD and will go stale; every citation also names the symbol — trust the
symbol (process rule 6).
Route 3 is NOT a bug fix. Portalling works today — /ls, spell recalls,
portal use, and admin teleports all place correctly and are user-accepted
behaviour. Route 3 removes a duplicate placement authority: the local
player's portal arrival is committed by two host-owned hand copies
(LocalPlayerTeleportPlacement.Place in App,
ResynchronizeLocalPlayerForPortalArrival in Headless) instead of the one
canonical Runtime SetPosition transaction every other C4 route now uses.
Nobody should read this document as "portals are broken".
Route 3 is the LAST C4 route. Routes 1 (C3a-c), 2 (9966b531), 4a, 4b-1/2/3,
5 (36255af0), and 6 (zero production lines, 1b484937) are landed; route 7
is scoped with its research blocker resolved
(2026-08-04-retail-child-cell-ownership.md).
Inputs verified for this scoping: the campaign plan
(docs/plans/2026-08-02-placement-cutover.md), the 2026-08-02 route inventory
(dated — §3 below lists what is now false), the route-5 contract + its
round-3 reviews (the current contract standard), the 4b-3 contract's 13
invariants, claude-memory/project_portal_space.md, the J6.2/J6.3 closeouts,
the six process rules in 2026-08-04-session-handoff-c4-remaining.md, and
direct reads of every file cited below plus the retail pseudo-C.
1. Headline findings
-
The campaign plan overstates what is missing. Plan line ~92-93 says "
RuntimePortalPlacementAuthorityhas zero producing call sites; the adapter fromRuntimeWorldTransitStatedoes not exist." The first half is still true — zero production producers, confirmed by exhaustive read of everyBegin*/TryPrepareAndSubmit*call site (§4). The second half is now HALF-false: the consumption/validation side of the adapter exists and is LIVE production code, exercised (with an empty portal) by every placement in the game:RuntimeWorldTransitState.IsCurrentPlacementAuthority(RuntimeWorldTransitState.cs:258-275) — generation + sequence + cell + host-token + not-superseding check;RuntimePlacementPresentationSink.TryApply(src/AcDream.App/World/RuntimePlacementPresentationSink.cs:100-106) — every graphicalPlacereceipt is already gated on it;HeadlessRuntimePlacementProjectionSink.TryApply(src/AcDream.Headless/Hosting/HeadlessRuntimePlacementProjectionSink.cs:100-121) — the headless twin;LiveEntityRuntime.IsValidPortalPlacementAuthority(src/AcDream.App/World/LiveEntityRuntime.cs:1448-1457) — token-shape validation inside every projection-record lookup;RuntimeSetPositionState.BeginAcceptedPlacementCore:1528-1534— Begin refuses a non-empty portal unless kind isLocalAuthoritativeAND the authority's destination cell equals the record's accepted-position landblock;- the portal authority is threaded through the whole pipeline already:
RuntimeSetPositionCommand.Portal(:128),RuntimeSetPositionMoverPreparation.Portal(RuntimeSetPositionMoverPreparation.cs:121, consistency-checked inTryBuild:146),Operation.Portal(:433), andRuntimePlacementProjectionToken.Portal(:250, a REQUIRED positional on every projection token).
What is actually missing is only the producer: nothing constructs a
RuntimePortalPlacementAuthoritywithPresent: true(grep: zero insrc/; three test constructors), and no portal arrival drives a Runtime placement at all. That makes route 3 materially smaller than the plan's phrasing implies. -
Retail's local portal arrival is the GENERIC path — the pattern from routes 2 and 5 holds a third time (§5).
SmartBox::TeleportPlayer@0x00453910 is two calls:CPhysicsObj::SetPositionSimple(player, dest, 1)— the EXACT primitive route 2 already routed through Runtime'sCommitCanonicalfor ForcePosition, flags 0x1012 — andSmartBox::PlayerPositionUpdated(this, 1, FLT_MAX). There is no dedicated local portal placement path to port; the work is wiring, ordering, and the post-placement suffix. -
The classifier's LocalPlayer+TeleportAdvanced route is fully modeled and steady-state dead (
RuntimeAuthoritativePositionRouteClassifier.cs:349-368):SetPositionSimple+AuthoritativeTeleportFlags+ hookAfterPositionOperation+ constrainAfterPositionOperation+ZeroVelocity: true+SendPositionImmediately: false— matching the retail listing exactly (§5). Its only production caller today is the initial-create continuation executor inside the residence window; route 3 would be its first steady-state producer. -
#280 is mechanically distinct from route 3 and must SPLIT (§7).
2. Site inventory at HEAD (re-verified, not inherited)
2.1 The duplicate authorities to replace (both hosts)
| # | site (symbol) | at HEAD | what it does |
|---|---|---|---|
| D1 | LocalPlayerTeleportPlacement.Place |
src/AcDream.App/Streaming/LocalPlayerTeleportController.cs:214-278 (class :183-290) |
The graphical duplicate authority, intact and essentially as the 2026-08-02 inventory described plus the 2026-07-25 rebucket fix: _physics.Resolve(pos, cell, 0, StepUpHeight) :219 → controller.SetPosition :230 (which runs the whole teleport tail in SetPositionCore, see 2.4) → direct entity.SetPosition/ParentCellId/Rotation :242-244 → RebucketLiveEntity (throws on failure) :254 → _host.Host?.NotifyTeleported() :264 → SetBodyOrientation :265 → camera resets :267-268 → _spatial.Reconcile() :269. No Runtime transaction, no receipt, no portal authority. |
| D2 | HeadlessSessionWorldProjection.ResynchronizeLocalPlayerForPortalArrival |
src/AcDream.Headless/Hosting/HeadlessSessionWorldProjection.cs:788-836 |
The headless duplicate authority — the old SynchronizeLocalPlayer body, SURVIVING ONLY FOR THIS ROUTE (its own doc at :779-787 says "TODO-C4 (route 3)"): CenterOn → Physics.Engine.Resolve(wire, lb, 0, 100f) :810 → Engine.ResolvePlacement(0.48f/1.835f, IsPlayer|EdgeSlide) :816 → controller.SetPosition :831 → SetBodyOrientation :835. Called from PrepareDestination:757-762. |
2.2 The drive machinery (graphical)
| site | at HEAD | note |
|---|---|---|
LocalPlayerTeleportController (drive) |
LocalPlayerTeleportController.cs:385-789 |
OnTeleportStarted:436-459 (F751 → TryQueueTeleportStart); OfferDestination:461-470; Tick:472-570 — readiness = destination + !IsRecenterPending + _worldReveal.Evaluate(cell).IsReady; on TeleportAnimEvent.Place: CanPlacePortalDestination preflight :514 → _placement.Place :521 → ObserveMaterialized :527 (still a rubber-stamp AFTER the mutation — the inventory's structural claim holds); PlayExitSound → RevealWorldViewport; FireLoginComplete → EnterWorld + SendLoginComplete + Complete. |
| destination offer | src/AcDream.App/Physics/LiveEntityNetworkUpdateController.cs:2950-2957 (was :1906-1913) |
Last statement of OnPosition: Apply disposition + local guid → _localPlayerTeleport.OfferDestination(FromAcceptedPosition(update), timestamps.TeleportAdvanced). No placement runs for a local Apply anywhere in OnPosition (route 2's ForcePosition drive at :2014-2071 is disposition-exclusive with it). |
WorldRevealCoordinator |
src/AcDream.App/Streaming/WorldRevealCoordinator.cs (574 lines) |
Holds the current RuntimeWorldHostProjectionToken PRIVATELY in _hostProjections (:40-62, FindCurrentHostProjection:516); TryBeginPortal:117-149 mints generation + registers the host projection. The producer needs read access to this token (or re-derives it via the transit's idempotent TryRegisterHostProjection) — a small exposure decision for the contract. |
| local generic render-pose write | LiveEntityNetworkUpdateController.cs:2284-2289 + :2314 |
Live finding, verified against the code (the comment at :2276 is stale): TryApplyGenericRemoteRenderPose(entity, null, …) gate is OwnsSteadyState(route) (:1037-1052) which is FALSE for the local player's null route — so every accepted local Apply, including the portal DESTINATION Position, writes the raw wire pose onto the local player's WorldEntity and rebuckets to the wire landblock while portal space still covers the viewport. Pre-existing, hidden by the portal viewport, later overwritten by Place. AP-131/#275 territory — see trap T8. |
2.3 The headless portal flow
RuntimeLiveEntitySessionController.TryCompletePortal
(src/AcDream.Runtime/Session/RuntimeLiveEntitySessionController.cs:339-428),
called from OnPositionUpdated:296 and OnTeleportStarted:330:
TryGetAcceptedTeleportDestination → TryBeginPortalReveal →
TryRegisterHostProjection → ack ProjectionRegistered →
PrepareDestination(generation, destination) :368 (this is where D2
runs, plus controller.State = PlayerState.InWorld and the readiness report)
→ AcknowledgeDestinationReadiness → AcknowledgePortalMaterialized → ack
SimulationReleaseProjected → RequireDestinationReservationRelease → ack
DestinationReservationReleased → AcknowledgeWorldViewportVisible +
Complete → ack TerminalProjected → SendGameAction(LoginComplete) →
EndTeleport. The J6 lifecycle itself is canonical and healthy; only the
placement inside PrepareDestination is the duplicate.
2.4 Runtime surfaces route 3 builds against
| site | at HEAD | relevance |
|---|---|---|
RuntimePortalPlacementAuthority |
RuntimeSetPositionState.cs:105-119 |
(bool Present, long RevealGeneration, ushort TeleportSequence, RuntimeWorldHostProjectionToken Projection). The shape is RIGHT for what transit holds — it is exactly the tuple RuntimeWorldTransitState owns per reveal (Snapshot.Generation, ActiveTeleportSequence, the registered host token whose DestinationCell is validated everywhere). No shape change needed. |
RuntimeWorldTransitState |
RuntimeWorldTransitState.cs (1,012 lines) |
Producer inputs all exist: IsTeleportActive:96, ActiveTeleportSequence:97, TryGetAcceptedTeleportDestination:522, CanPlacePortalDestination:534 (read-only preflight), TryRegisterHostProjection:189 (idempotent for the same generation/cell — a legitimate token re-derivation path), AcknowledgePortalMaterialized:595, IsCurrentPlacementAuthority:258. |
RuntimeAcceptedPositionDriveController |
src/AcDream.Runtime/Session/RuntimeAcceptedPositionDriveController.cs (924 lines) |
Route 2's landed local-player accepted-Position seam over TryBeginExclusiveAuthoredPlacement:389 + TryPrepareAndSubmitAuthoredPlacement:677 (resolveWorldOffsetFromRuntimeFrame: true). ForcePosition-only by gate (:340-360); its Pending/ack funnel is ForcePosition-shaped (PositionEventOwed). The natural home for a sibling portal arm — but see trap T7 before inheriting any of its funnel. |
PlayerMovementController teleport tail |
src/AcDream.Runtime/Gameplay/PlayerMovementController.cs:1845-1920 (SetPositionCore) |
Today's controller.SetPosition on the portal path performs, in one method: body snap, render-lerp anchor reset, UpdateCellId publication, Contact|OnWalkable|Active transient set, zero velocity, StopCompletelyAtPhysicsObjectBoundary (retail StopCompletely 0x00527E40), input-edge/mouse state reset, UnStick + UnConstrain + RearmConstraintLeashAtCurrentPosition (retail @0x0045418A analog), and _objectClock.ResetForEnterWorld(). Under a canonical commit the BODY write moves into Runtime's CommitCanonical; every one of these controller-local duties must be re-homed into a teleport analog of CommitCanonicalForcePositionFrame (:1944-1950) — losing any one of them is the 4b-2 round-1 defect class. This is the single largest and riskiest piece of the slice. |
| classifier LocalPlayer teleport branch | RuntimeAuthoritativePositionRouteClassifier.cs:349-368 |
See §1.3. Production classify callers: the drive controller :922 (Force only), ClassifyRemoteAcceptedPosition (remote/projectile), the continuation executor :1928 (residence window). |
SpawnPlacementSettler |
src/AcDream.Core/Physics/SpawnPlacementSettler.cs:22; local-player call site RuntimeLocalPlayerPhysicsPublicationState.cs:812 |
First-entry only (C3c-F5/AD-61). Not invoked on the portal path today, and retail's portal arrival has no settle sweep either — contact resolves inside SetPosition's own transition (SLIDE flags). Do not add it (trap T10). |
| world frame | RuntimePhysicsState.ObserveLocalWorldFrame:550-562; callers RuntimeEntityObjectLifetime.cs:1739/:1794 |
Runtime's world frame rebases on teleportAdvanced at the accepted-Position merge; App's LiveWorldOriginState recenters asynchronously afterwards. See trap T4. |
2.5 What is already canonical and must not regress (J6)
Slice J6.2/J6.3/J6.4 ownership stands: RuntimeWorldTransitState owns the
reveal generation, destination latch, F751 correlation (both packet orders),
materialization/simulation edge, viewport observation, wait cue, completion/
cancellation, and the 4-stage host acknowledgement suffix. Route 3 must not
move ANY of that; it only makes the transit owner additionally gate the
placement mutation it currently rubber-stamps. The lifecycle gate's
transitOwnership zero-at-stable-checkpoint discipline
(tools/run-connected-world-lifecycle-gate.ps1) already covers this state.
3. The dated inventory: claims now false
The 2026-08-02 route inventory predates C3c, route 2, the four route-4 sub-slices, route 5, and the OnPosition collapse. Eight of its claims bearing on routes 3/8 are now materially false, plus pervasive stale line numbers:
- "The binding machinery is real but 100% dormant end to end." — FALSE. The validation half is live at three production layers (§1.1) and runs for every placement (empty-portal case). Only the producer is dormant.
- "Neither host has ever constructed a production
IRuntimePlacementObserverimplementation" (prerequisite D "100% unbuilt on the host side") — FALSE.RuntimePlacementPresentationSink(graphical) andHeadlessRuntimePlacementProjectionSink(headless, wired atHeadlessSessionHost.cs:701) are production consumers of the placement FIFO. - "
BeginAcceptedPlacement/BeginAuthoredPlacement… zero external callers repo-wide" / "a placement transaction … never begun anywhere in production" — FALSE. Three production controllers driveTryBeginExclusiveAuthoredPlacement+TryPrepareAndSubmitAuthoredPlacement(local force, remote, first-entry). (The two named legacy wrappersBeginAcceptedPlacement/BeginAuthoredPlacementthemselves are now test-only entry points.) - "
InboundPhysicsStateController.TryApplyPositionis today's only PRODUCTION Position wire caller; the classifier-based path is test-only" — FALSE since routes 2/4a/4b-2/4b-3/5. - The entire route-2 chain (
LocalForcePositionTransaction, pre-commit ack, generic-tail double write for ForcePosition) — GONE; route 2 landed. - Headless route-8: "
SynchronizeLocalPlayer(566-615) is the duplicate placement authority …CreateController(639-655) constructsPlayerMovementController" — FALSE at HEAD. C3c deleted the route-1/8 initial-entry hand-copy and the controller construction; the ONLY surviving headless duplicate is the renamed portal-arrival re-resolve (D2). - "
BlipLocalPlayer(617-637) direct blip" — FALSE; deleted by route 2 (halves split intoCenterOnAcceptedForcePosition+ the drive controller). - "The executor publishes only generic entity deltas; nothing bridges its
completion to
RuntimePlacementProjectionChannel" — FALSE since C0/C3-1 (ExecutorCompletedreceipts;TryGetInitialCreateCompletion).
Still TRUE from the inventory's route-3 section: the call-chain shape
(F751 → queue → offer → aim → TryBeginPortal → Place at anim event), the
Place-mutates-then-ObserveMaterialized-rubber-stamps sequencing, the
"discard/cancellation semantics structurally sufficient but UNEXERCISED for a
real placement" warning (now with live validators, still zero portal-carrying
operations — the explicit cutover tests it demanded are still owed), and the
executor scope note (portal arrival is a steady-state placement, not a
Create admission — the FIFO executor is not the target).
Stale-but-substantively-true: the offer site (:1906 → :2950), the
validator (:1162 → :1448), PrepareDestination (:539 → :752,
re-resolve retained under a new name), RuntimeWorldTransitState still 1,012
lines.
4. The missing producer, precisely
What route 3 must build (and ALL it must build on the authority side):
- The producer: at the placement moment, construct
new RuntimePortalPlacementAuthority(Present: true, RevealGeneration, ActiveTeleportSequence, hostProjectionToken)from the live transit state. Graphical: the generation is already at hand (_pendingRevealGeneration,LocalPlayerTeleportController:404/694), the sequence is_transit.ActiveTeleportSequence, and the host token needs a small accessor onWorldRevealCoordinator(or re-derivation via the idempotentTryRegisterHostProjection). Headless: all three are in scope insideTryCompletePortal(generation,destination.TeleportSequence,projection). - The placement call: a portal arm on the local-player drive seam that
runs
TryBeginExclusiveAuthoredPlacement(record, version, LocalAuthoritative, portal)+TryPrepareAndSubmitAuthoredPlacement(…, portal, resolveWorldOffsetFromRuntimeFrame: true)with the classifier's LocalPlayer-teleport route, invoked at each host's existing placement edge (graphical: theTeleportAnimEvent.Placehandler afterCanPlacePortalDestination; headless:PrepareDestination). - The controller-local teleport frame commit (§2.4's
SetPositionCorere-homing) invoked on the committed receipt. - Re-sequencing:
AcknowledgePortalMaterializedfires from the committed placement receipt (it then actually gates —IsCurrentPlacementAuthorityrequires the materialization state it sets), camera reset +_spatial.Reconcile()become acknowledge-edge reactions, and D1/D2's direct mutations are deleted.
The authority's shape does not need changing (§2.4). One genuine edge to
design for: BeginAcceptedPlacementCore:1528-1534 validates the authority's
destination cell against record.Snapshot.Physics?.Position ?? record.Snapshot.Position — the LATEST merged Position — while transit pins
the FIRST accepted destination per generation (J6.3). A second local Position
merging between the offer and the Place edge would make Begin refuse
(default token). Today that window is benign (ACE sends one destination per
teleport), but the refusal path must be defined and tested rather than
discovered: on refusal the transit generation must be cancelled or the
placement re-aimed — never a silent wedge in portal space.
5. Retail ground truth (verified in acclient_2013_pseudo_c.txt for this scoping; verify again at contract time)
| claim | address | status |
|---|---|---|
F751 (SmartBox::HandlePlayerTeleport) writes exactly three flags — position_update_complete = 0, has_been_teleported = 0, waiting_for_teleport = 1 — after a wrap-safe TELEPORT_TS check. No position, no cell, no physics. |
@0x00452150, writes @0x00452193-0x004521A7 | ✓ read |
HandleReceivedPosition's local/non-local split is arg2 != this->player @0x0045414D. The LOCAL teleport branch (newer_event(TELEPORT_TS) @0x0045415F): TeleportPlayer @0x00454168 → ConstrainTo(player, &var_48 /* the WIRE destination */, start, max) @0x0045418A → set_velocity(player, 0, 1) @0x004541B4 → return. |
@0x00453FD0 | ✓ read |
SmartBox::TeleportPlayer is TWO calls: CPhysicsObj::SetPositionSimple(player, dest, 1) @0x00453924 and PlayerPositionUpdated(this, 1, FLT_MAX) @0x00453932. The generic placement primitive — the same one route 2 ported (BlipPlayer @0x00453940 is the identical shape with arg 0). |
@0x00453910 | ✓ read |
SetPositionSimple(…, 1) builds flags 0x1012 = SEND_POSITION_EVENT | SLIDE | TELEPORT (acclient.h:6123) → generic CPhysicsObj::SetPosition @0x005160C0. Identical flags to the remote teleport branch. SLIDE is consumed in CheckPositionInternal @0x00511E90 (accept what find_valid_position resolved). |
@0x005162B0 (0x1012 @0x005162C2) | ✓ read |
PlayerPositionUpdated teleport arm (AFTER the placement): position_update_complete = 0, waiting_for_teleport = 0 (the F751 wait ends at PLACEMENT, not at reveal), has_been_teleported = 0, teleport_hook(player) @0x004538AE, cmdinterp->PlayerTeleported() (= SetAutoRun(0,1) + SendMovementEvent, @0x006B32B0), set_viewer(&player->m_position, 1) (camera reset, reset_sought), LScape::update_viewpoint(0) (destroys the terrain draw list), CellManager::ChangePosition(&m_position, 1 /* blocking */). |
@0x00453870 | ✓ read |
The hook ordering FLIPS versus the remote route: local runs teleport_hook @0x00514ED0 AFTER SetPositionSimple returns (from PlayerPositionUpdated); the remote branch runs it BEFORE SetPosition (@0x005163EF). The hook UnConstrains @0x00514F0C, and HandleReceivedPosition re-arms ConstrainTo @0x0045418A after TeleportPlayer returns, so the leash survives. The classifier already encodes this (TeleportHookPhase.AfterPositionOperation local vs BeforePositionOperation remote). |
✓ read | |
CPhysicsObj::enter_world @0x00516170 is NOT on the portal path. Its local-player caller is the initial-login path only (@0x00455095, with store_position + blocking ChangePosition). Portal arrival never re-runs enter_world or CPartArray::HandleEnterWorld/MovementManager::HandleEnterWorld — the cell install happens inside SetPosition itself. 2026-07-16-portal-completion-pseudocode.md §2.1 attributes portal arrival to enter_world — that is the login path and the doc needs a correction note (its conclusion about committing the cell before releasing simulation survives; the cited mechanism is wrong). |
callers of 0x00516170 | ✓ read (agent-verified caller sweep) |
The FORCE_POSITION branch (@0x0045400C → BlipPlayer → SendPositionEvent → return @0x0045409D) never reaches any ConstrainTo — route 2's finding reconfirmed. The local TELEPORT branch DOES arm, wire-anchored, and DOES zero velocity — the inversion trap T1. |
✓ read | |
Prefetch (#280's oracle, re-verified): blocking CellManager::ChangePosition @0x004559B0 releases the old landscape and calls CellManager::PreFetchCells @0x00455820 → outdoor LScape::PreFetchCells @0x00505660 walking ±mid_radius landblocks (DAT-record residency, not GPU upload); indoor CEnvCell::PreFetchCells. mid_radius = Render::m_RenderPrefs.LandscapeDrawDistance via SmartBox::SetRegion @0x00453227 / set_mid_radius @0x00453180 — the prefetch window IS the landscape draw distance, centred on the destination. While blocking_for_cells, SmartBox::UseTime @0x00455410 runs ONLY CheckPrefetchStatus; object/physics/landscape/game-time/ambient all hold. Reveal (SmartBox::Show) is a further TunnelFadeOut second after the readiness edge; LoginComplete a WorldFadeIn second after that. |
✓ read | |
Retail places IMMEDIATELY on the accepted destination Position and blocks SIMULATION on prefetch behind the portal viewport. acdream inverts this: it holds the PLACEMENT until reveal-readiness (ready in LocalPlayerTeleportController.Tick) and then places. This is the accepted portal-presentation architecture (J6/E5, user-gated repeatedly) — route 3 keeps the deferred-Place timing and changes only WHO commits it (trap T3). |
✓ (architecture fact) |
Unverified residuals from the retail pass (mark for the contract, none
load-bearing for scope): the exact second argument of teleport_hook at
@0x004538AE (decompiler-elided, almost certainly 1); CheckPrefetchStatus's
apparent 5.0 s re-check throttle (@0x00455BEE — suspicious, byte-verify if
#280 uses it); where SEND_POSITION_EVENT/TELEPORT flag bits are consumed
below SetPositionInternal (not traced; irrelevant to route 3's seam
choice).
6. Shape sketch (scoping altitude — the contract pins the real design)
- Runtime: a portal arm on
RuntimeAcceptedPositionDriveController(a sibling entry point, e.g.TryExecuteAcceptedPortalArrival(record, route, authority)), sharing Begin/Submit/status handling with the force arm but NOT itsPositionEventOwed/re-issue funnel (trap T7). It consumes the classifier's LocalPlayer-teleport route, appliesZeroVelocity, invokes the controller-local teleport frame commit on the committed receipt, and arms the leash post-operation (the route'sConstrainPhase— today'sSetPositionCorere-arm relocates here). - Graphical:
LocalPlayerTeleportPlacement.Placebecomes acknowledge-only — build the authority, call the Runtime arm, and on the committed receipt run the presentation suffix (camera resets,NotifyTeleported,_spatial.Reconcile()), thenObserveMaterialized. The direct Resolve/SetPosition/entity-write/rebucket bodies are deleted; the render entity moves via the existingRuntimePlacementPresentationSinkPlace projection (whose portal gate finally goes live). - Headless:
PrepareDestinationkeepsCenterOn+ readiness reporting + state writes;ResynchronizeLocalPlayerForPortalArrivalis deleted and the same Runtime arm is driven from the portal completion flow. - Probe:
ACDREAM_PROBE_LOCAL_TELEPORT=1(PhysicsDiagnostics-owned, TEMPORARY family) — one line per portal arrival with cause (portal/recall), placement status, portal generation/sequence, destination cell, and hook/leash confirmation.
Register bookkeeping to expect (contract finalizes): AD-42's headless
portal-arrival-resync citation dies with D2; locate-or-add the row recording
the deferred-place timing adaptation (§5 last row — UNVERIFIED whether an
existing AD row states it; if none exists the route-3 commit must add it —
a deviation without a row is a bug twice over); the 2026-07-16 pseudocode
correction note (§5); the stale :2276 comment (§2.2) corrected or the
finding filed.
7. #280 — definitive recommendation: SPLIT (do not bundle into route 3)
The campaign plan and the session handoff disagree, and the plan is right. The plan lists #280 as its own numbered work item (item 3 of the remaining campaign work), separate from "finish C4's routes 2-7" (item 2). Only the 2026-08-04 session handoff says "#280 rides with [route 3]". They cannot both be followed; this scoping recommends the plan's sequencing.
Why they are mechanically distinct:
- Route 3 changes WHO commits the placement. Its blast radius is
RuntimeSetPositionState/drive-controller/PlayerMovementController/the two host placement bodies. It does not touch readiness evaluation at all — thereadypredicate inLocalPlayerTeleportController.Tick:489-493and everything behindWorldRevealCoordinator.Evaluateis read, not written. - #280 changes WHAT the reveal waits for. Its fix shape (per its own
ISSUES entry, whose retail oracle this scoping re-verified §5): replace
WorldRevealReadinessBarrier.OutdoorNeighborhoodRadius = 1(src/AcDream.App/Streaming/WorldRevealReadinessBarrier.cs:38) with a retail-derived, quality-configured destination prefetch window (mid_radius== landscape draw distance) and hold one generation-scoped reservation across terrain/statics/EnvCells/render publication/composites/collision until the complete visible window is ready. Its blast radius is streaming/reveal/render-resource scheduling —WorldRevealReadinessBarrier,GpuWorldStatereadiness joins, the streaming reservation schedulers. It does not touch placement. - The ONLY shared surface is the transit lifecycle both sit inside — and J6 already owns that; neither change moves it.
- Bundling would put a placement-authority cutover (physics-review skillset, connected teleport gate) and a streaming-window change (render/streaming skillset, view-distance visual gate at multiple quality settings) in one diff, and would push the slice far past the ~500-line budget (#280 alone plausibly rivals route 3 in size). 4b-2's process lesson ("split on discovery; a defect outside scope rides for three review rounds") applies prospectively here.
Sequencing: #280 can land before or after route 3 with no dependency in
either direction — route 3 keeps the Place gated on the SAME ready
predicate, whatever radius that predicate uses. Recommend: route 3 first
(closes C4), #280 immediately after as its own slice with its own visual
gate, before C5's closeout matrix (the campaign plan already sequences it
exactly there).
One honest caveat: bundling advocates would note both changes gate "what happens at the Place edge" and a single connected session could gate both. True but insufficient — a shared test session is not a shared mechanism, and the review lenses are disjoint.
8. Size estimate and split recommendation
Calibration: route 4a 364; 4b-1 230+57; 4b-2 350-500; 4b-3 ~250 net new (400-700 budget) with 739 deleted; route 5 ~131 added, ~180 deleted.
| piece | est. non-comment production lines |
|---|---|
| Runtime portal arm on the drive controller (Begin/Submit/status/receipt handling, ZeroVelocity, leash arm) | 90-150 |
PlayerMovementController teleport frame commit (re-home the SetPositionCore suffix; split shared private helpers) |
40-80 |
Graphical producer + Place rewrite + ObserveMaterialized/camera/reconcile re-sequencing |
60-110 (deletes ~55) |
WorldRevealCoordinator host-token exposure |
10-20 |
Headless: PrepareDestination flip + drive call |
15-30 (deletes ~50 incl. D2) |
| Probe line | ~10 |
| Total added | ~225-400 |
Net roughly +150 to +250 (deletions ~105-120). Tests are the larger share, an estimated 500-900 lines (per-outcome placement tests on the portal arm, supersession/cancellation/stale-generation discard tests — the inventory's still-owed "explicit cutover test" for the discard semantics — presentation assertions at #312's layer, dual-host parity, the Begin cell-mismatch edge of §4).
Recommendation: ONE slice, no split — comfortably under the ~500
threshold PROVIDED #280 splits out (§7). Stop-and-report conditions for the
implementer: added production lines exceed ~500; the design starts needing
changes to RuntimeWorldTransitState's lifecycle semantics, the
TeleportAnimSequencer timings, or the readiness barrier (each is a sign
#280 or a J6 regression is being smuggled in); or the Begin cell-mismatch
edge (§4) turns out to be reachable in ordinary play.
Allocation: a portal placement is once-per-teleport, not frame-frequency — the C2 1,536 B budget is not an activation blocker here (route 2's reasoning applies). Do not spend lines pooling.
9. The connected gate (real and mandatory — unlike route 5)
Portals are trivially reachable and user-visible; a live gate exists and is required. A clean-looking session is NOT a pass (process rule 5; 4b-3's gate was a pass only because 16 probe lines proved the arm executed, and its cell-less half is still honestly recorded as unverified).
Recipe (user-run, graphical): Release build, ACDREAM_RETAIL_UI=1,
ACDREAM_PROBE_LOCAL_TELEPORT=1, live ACE. Exercise, in one session:
- a physical portal (e.g. Holtburg portal) — outdoor destination;
- a dungeon portal (indoor destination, EnvCell readiness path);
/lslifestone recall and one spell recall (the F751 recall family);- an ACE admin teleport of the LOCAL player (
@teleto/@teleloc) — this advances ObjectTeleport and, per the route-2 visual-gate doc, exercises exactly this route; - a same-destination revisit (ACE may omit CreateObject on revisit);
- graceful close.
Pass requires ALL of:
- one probe line per arrival showing the CANONICAL path executed:
placement=Committed, the portal authority's generation/sequence, and the resolved destination cell — zero arrivals through the deleted path (which no longer exists to run) and zeroRefused/Contentionlines in ordinary play; - user visual: purple materialization silhouette without an opaque pop or
late tail (the 2026-07-25 accepted baseline must not regress), camera
reset behind the player at arrival, movement works immediately (walk out
with W held — the input-edge reset half of the teleport frame commit),
idle stance not a run-in-place, no rubber-band or tether after arrival
AND no leash absence (the leash IS armed on this route — a creature-free
way to observe it is beyond a visual check, so add a probe field
leash=armedinstead of inventing a visual); - the exact lifecycle/reconnect gate
(
tools/run-connected-world-lifecycle-gate.ps1) passes with everytransitOwnershipcounter zero at every stable checkpoint — this is the supersession/cancellation convergence evidence; - headless parity: the K-style connected portal route (four-stop portal routing from the K3 closeout) with the probe enabled — same probe-line-per-arrival requirement, proving D2's replacement executed on the no-window host.
Known gap to state up front, 4b-3-style: mid-transit supersession (a second teleport starting before the first materializes) and mid-transit disconnect are hard to provoke against ACE on demand. If the session does not produce them, record the stale-generation discard behaviour as test-verified-only — do not fold it into a blanket "gate passed".
10. Traps — rules from earlier routes that flip or do not transfer
- T1 — route 2's leash rule INVERTS. Route 2 pinned "the constraint
leash is NOT re-armed" because the FORCE_POSITION branch returns at
@0x0045409D before every
ConstrainTo. The local TELEPORT branch is the opposite: retail armsConstrainTo@0x0045418A (anchored at the received destination) AND zeroes velocity @0x004541B4. The classifier already carries both (ConstrainPhase.AfterPositionOperation,ZeroVelocity: true); the drive-controller doc comment (RuntimeAcceptedPositionDriveController.cs:127-132) explaining why the force arm never arms MUST NOT be generalized to the portal arm. Also inverted:SendPositionImmediatelyis FALSE (noAutonomousPositionack on this route — retail's teleport branch returns withoutSendPositionEvent; the outbound tail is LoginComplete, which stays where it is, see T11) andPreserveHeadingis FALSE (the wire orientation applies). - T2 — 4b-3's hook ordering INVERTS. Remote:
teleport_hookBEFORE the placement (@0x005163EF). Local: the hook runs AFTERSetPositionSimple, fromPlayerPositionUpdated@0x004538AE. The classifier encodes the flip (AfterPositionOperationvsBeforePositionOperation). Copying 4b-3's hook-first arm shape onto the local route would be retail-wrong. Also: the local "hook" is notRemoteTeleportHook— its live actions for the local player are the controller-local UnStick/UnConstrain (+ re-arm afterwards),NotifyTeleported(TargetManager pair), the autorun cancel (PlayerTeleported→SetAutoRun(0,1)— verify against the J5.4 autorun latch at contract time), andreport_collision_end. - T3 — do NOT move the placement to packet-accept time. Retail places immediately and blocks simulation on DAT prefetch; acdream's accepted portal architecture defers the placement to the reveal-ready Place edge behind the portal viewport (user-gated repeatedly; J6/E5). Route 3 changes the executor of the Place, never its timing. An implementer "fixing" the timing toward retail would regress the whole accepted presentation and collide with #280's territory. Locate-or-add the register row for this adaptation (§6).
- T4 — two world frames disagree during the teleport window (#283).
Runtime's frame rebases at the accepted teleport Position's merge
(
ObserveLocalWorldFrame(…, teleportAdvanced: true)); App'sLiveWorldOriginStaterecenters asynchronously. TodayPlaceruns only after!IsRecenterPending(insideready), so both frames agree at placement time. The canonical arm must keep that gating and must resolve through Runtime's frame (resolveWorldOffsetFromRuntimeFrame: true, as route 2 does) — the App-translated_pendingPositionvector dies with D1. A placement submitted during the disagreement window is the failure mode. - T5 — the Begin cell-mismatch edge (§4): transit pins the FIRST accepted destination per generation; Begin validates against the LATEST merged snapshot. Define and test the refusal path; never wedge in portal space.
- T6 — hands off J6 and the presentation.
RuntimeWorldTransitState's lifecycle semantics, the 4-stage host acknowledgement, theTeleportAnimSequencerseven-state machine, AD-38's viewport-retire timing, the wait cue, andTeleportViewPlaneControllerare all user-accepted and out of scope. The ONLY transit-adjacent change is thatAcknowledgePortalMaterializedfires from the commit receipt instead of rubber-stamping after a host mutation. - T7 — do not inherit the force arm's funnel.
PositionEventOwed, the_newestForcere-issue observation (AD-62), andSendImmediatePositionare ForcePosition-shaped: ACE never repeats a portal destination, the portal route owes no AutonomousPosition ack, and a dead-watch re-issue of a portal placement would fight the transit generation. The portal arm shares Begin/Submit/receipt handling only. (The mirror of 4b's "do NOT port route 2's re-issue funnel" lesson.) - T8 — the local generic render-pose write is a pre-existing second
writer; do not silently delete OR silently keep it. Every accepted local
Apply — including the portal destination Position — writes the raw wire
pose to the local player's
WorldEntityand rebuckets to the wire landblock (:2284-2289/:2314; the:2276comment claiming otherwise is stale). Hidden by the portal viewport today and overwritten by Place. It is AP-131/#275 (the ordinary local Apply path no route owns) — route 3 must TOLERATE it (the canonical projection overwrites on commit) and the contract must decide explicitly whether the teleport window suppresses it; whichever way, correct the stale comment (process rule 6). - T9 — no allocation work. Once-per-teleport; the C2 budget is not in play.
- T10 — no settle sweep.
SpawnPlacementSettleris first-entry-only (C3c-F5); retail's portal arrival has none — contact resolves inside the SLIDE placement itself. If arrival contact looks wrong in the gate, the bug is in the placement flags/transition, not a missing settle. - T11 — LoginComplete does not move. It stays on
TeleportAnimEvent.FireLoginComplete(retail: ~2 s after reveal; TS-28 is narrowed to initial login only). C3c's175ad6b0moved LOGIN's LoginComplete to the first-placement terminal edge — that reasoning does NOT transfer to the portal route. - T12 — headless keeps its non-placement duties.
PrepareDestination'sCenterOn, readiness report, andPlayerStatewrites stay host-owned; only the D2 re-resolve body flips.BeginTeleport's directPlayerState.PortalSpacewrite is out of scope.
11. What route 3 does NOT do / what remains for C5
- #280 — split out (§7); its own slice with its own visual gate.
- AP-131 / #275 — the shared merge call and the ordinary local Apply path (including T8's generic write) stay for C5.
- AP-1/AD-1 retirement, the legacy-deletion sweep, parity tests, the
final connected matrix — C5. Route 3's deletions are only D1/D2's bodies
and their direct wiring; any surviving dead seams
(
ILocalPlayerTeleportPlacementif it reduces to a trivial adapter, legacyBeginAcceptedPlacement/BeginAuthoredPlacementtest-only wrappers) are C5 sweep candidates, recorded not deleted here. - Route 7 (pickup/parent/delete) — separate, already scoped.
- No headless remote consumer, no reveal-gate changes, no presentation/
anim/viewport changes, no
enter_world/HandleEnterWorldadditions (retail does not run them on this path — §5). - The six
LiveEntityRuntimeTestsfixture failures named in the campaign handoff and the final-binary soak remain campaign-level obligations, not route-3 scope.
12. Open gaps (each with the check that closes it)
- The deferred-place register row — grep the divergence register for the portal place-at-readiness adaptation; if absent, the route-3 commit adds it (T3).
- The autorun cancel — confirm acdream's arrival path cancels autorun
(retail
PlayerTeleported→SetAutoRun(0,1)): read the J5.4RuntimeLocalPlayerMovementStateautorun latch againstSetPositionCore's input reset. WorldRevealCoordinatortoken exposure shape — accessor vs re-registration; decide at contract with a test that a superseded token can never be handed to the producer.- The Begin cell-mismatch refusal path (§4/T5) — design + test.
- Retail residuals from §5's unverified list (teleport_hook arg, prefetch throttle) — byte-verify only if the contract leans on them.
RuntimePlacementPresentationSinkPlace projection for the local player — route 2's B2 recorded that no test drives a local-player Place end-to-end through the sink to a movedWorldEntity; route 3 makes that seam load-bearing for portals and MUST close the coverage gap (an App-layer test asserting the render entity moved from the committed receipt), not inherit it.
Summary for the campaign plan
- Size: ~225-400 added non-comment production lines, net ~+150-250 — ONE slice, no split (contingent on #280 splitting out).
- #280: SPLIT. The handoff's "rides with route 3" is wrong; the campaign plan's own sequencing (separate item) is right. Reveal-window/streaming concern, not placement.
- Eight dated-inventory claims now false (§3), the most consequential being "100% dormant end to end" — the validation half of the portal machinery is live production code; only the producer is missing.
- Contradictions with the campaign plan: (a) plan line ~92-93's "the
adapter … does not exist" overstates the gap — only the producer is
missing; (b) the plan's #280 sequencing contradicts the session handoff's
bundling — this scoping sides with the plan; (c) the inventory's "route
3's target is
RuntimeSetPositionStatedirectly" is refined: the target is a portal arm on route 2'sRuntimeAcceptedPositionDriveControllerseam OVERRuntimeSetPositionState. - Two documentation defects found while verifying: the 2026-07-16 portal
pseudocode attributes portal arrival to
enter_world(that is the LOGIN path — needs a correction note), andLiveEntityNetworkUpdateController.cs:2276's "the local player never reaches this generic-remote code path" is false forTryApplyGenericRemoteRenderPose/RebucketLiveEntity(T8).