acdream/docs/research/2026-08-03-c4-route-4-scoping.md
Erik 40f5721354 docs: C4 route 4 scoping — the stated budget failed, stop and re-plan
After route 2 I pinned a falsifiable bet: routes 4-7 reuse the seam route 2
built, so their marginal cost should be well under 400 production lines, and if
route 4 also cost ~900 the bet was dead. Scoping estimates 1,500-2,500 lines
plus ~1,700 lines of test re-modelling. Honouring the bet: no implementation
pass until the scope is re-planned.

The bet failed for an instructive reason. The seam generalises fine — the
begin/prepare/submit chain has no local-player precondition, the classifier's
remote branches are already retail-exact, and all remote physics state is
already in Runtime. Route 2 was simply not a representative unit: one entity vs
N, one disposition vs four, one execution path vs two (canonical SetPosition
AND the interpolation queue), no teleport hook, no constrain phase, two
duplicate authorities vs six. Picking the simplest route first and then
calibrating everything against it was the error.

Four findings that change the campaign plan, not just route 4:

- Route 4's Create half is already done (C3b/C3c). The remaining work is
  steady-state remote Position plus deletions; the route title misleads.
- AP-131 cannot be retired by route 4. Route 2 did not fix its FORCE_POSITION
  half, and its local ordinary-Apply half is owned by no route in the inventory.
- #277's safety bound breaks: it argues about Creates, while a steady-state
  Position can carry a remote out of the collision window with no Create at all.
  Needs a Position-time service-window guard on both hosts; the graphical host
  has no such predicate today.
- N3 (headless never calls RetryPending) stops being latent the moment route 4
  makes headless remotes produce placement receipts.

Also records three previously unfiled divergences found while scoping: the NPC
airborne hard-snap that ignores the wire IsGrounded bit, ConstrainTo armed
before the operation instead of after, and ConstrainTo never armed on the remote
teleport branch. Route 4 fixes all three by construction, which makes it a
behaviour change to every visible creature rather than a refactor.

Allocation is NOT the blocker the inventory feared: the steady state classifies
to Interpolate, which runs no SetPosition at all.

Recommends splitting route 4 into 4a (near/interpolate + airborne no-op — the
observable win, no park hazard) and 4b (teleport/far/cellless — where the parks,
the service-window guard, N3 and #277 live).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-03 19:46:01 +02:00

9.3 KiB

C4 route 4 — scoping, and a stop-and-re-plan (2026-08-03)

Scoped at HEAD e3b766d9, after route 2 shipped. This is not a contract. The scoping says route 4 is materially larger than the budget it was given, so the correct next action is a user decision, not an implementation pass.

The budget, and how it failed

After route 2 I pinned a falsifiable bet: routes 4-7 reuse the seam route 2 built, so their marginal cost should be far lower — well under 400 production lines. If route 4 also costs ~900, the bet is dead and that is the signal to stop and simplify.

Estimate: 1,500-2,500 lines of new/changed production code, plus ~1,700 lines of test re-modelling. The bet failed before implementation started.

But it failed for a reason worth stating precisely, because it changes what to do about it: the seam generalises fine. Route 2 was just not a representative unit.

route 2 route 4
entities 1 (local player) N (every visible remote)
dispositions 1 (ForcePosition) 4 (SetPosition / SetPositionSimple / Interpolate / NoPositionOperation)
execution paths 1 2 (canonical SetPosition and the interpolation queue)
teleport hook never BeforePositionOperation
constrain phase never AfterPositionOperation
duplicate authorities to delete 2 6
outbound ack owns it none
frequency ~never 5-10 Hz x N

I chose route 2 first because it was simplest to reason about, then used it to calibrate everything after it. That was the error: a one-entity, one-disposition, no-interpolation route is the floor, not the median.

What is genuinely reusable (the good news)

  • The begin/prepare/submit/commit chain is entity-kind agnostic — no local-player precondition anywhere in TryBeginExclusiveAuthoredPlacementTryPrepareAndSubmitAuthoredPlacementSubmitPreparedPlacementCore.
  • The classifier's remote branches are retail-exact already (verified against CPhysicsObj::MoveOrTeleport @0x00516330 on all five behaviours).
  • The placement receipt → presentation path is already entity-agnostic; C3c drives remote Creates through it today.
  • All remote physics state already lives in Runtime (J5.5) — RemoteMotion, EntityPhysicsHost, the interpolation queue. No assembly boundary to cross.
  • RuntimeAcceptedPositionDriveController itself is ~15-20% liftable — the architecture, not the code. It is single-entity and ack-owning by construction (_pending is one slot and RetainPending throws on a second).

Four findings that change the campaign plan, not just route 4

1. Route 4's Create half is already done. C3b/C3c landed RuntimeRemoteFirstEntryState + RuntimeRemoteBodyDescription and flipped both hosts' Creates onto the residence conductor. Route 4's remaining work is steady-state remote Position plus the deletions. The route title is misleading and will send an implementer hunting for a flip that already happened.

2. AP-131 cannot be retired by route 4. The row covers two halves: the !HasAnims gate on SetPlacementFrame, and the FORCE_POSITION branch skipping unset_parent. Route 2 did not fix the second — it consumes an already-merged snapshot, so a ForcePosition still passes installPlacementFrame: true, clearParent: true. Retiring AP-131 needs the unconditional merge gone for every Position: remote (route 4), local ForcePosition (a route-2 residual), and local ordinary-Apply — which no route in the eight-route inventory owns. Either widen route 4 or move AP-131 to C5 with those two as prerequisites. Pinning "route 4 retires AP-131" pins something route 4 alone cannot deliver.

3. #277's bound breaks. Its safety argument is that the collision window is strictly larger than ACE's Create-broadcast group, so the far set is empty. That is a statement about Creates. A steady-state Position can carry a remote out of the window with no Create at all — a creature wandering off, or the >=96 m far-snap branch by definition targeting something distant. Route 4 introduces a second producer of far parks the radius argument never covered. Needs a Position-time service-window guard on both hosts; headless has the predicate, the graphical host has none and one must be built.

4. N3 stops being latent. The route-2 round-2 finding — headless never calls RetryPending after construction, so a declined Place would wedge the ordered stream — was filed as latent because headless remotes produced no placement receipts. Route 4 makes them produce receipts for the first time. Fix N3 in route 4 or the headless stream wedges.

Three unfiled divergences found during scoping

Our production remote path diverges from the retail classifier on five behaviours; three have no register row:

  • NPC airborne hard-snap (LiveEntityNetworkUpdateController.cs:1819-1823) writes Body.Position/Orientation and never consults update.IsGrounded at all — it branches on the client-tracked rmState.Airborne. Retail returns 0 and writes nothing (@0x0051636D).
  • ConstrainTo armed before the operation, unconditionally (:1522-1529), so it arms on the airborne no-op retail skips and anchors to the pre-move position on the far branch. Retail arms it after a nonzero MoveOrTeleport, anchored to the post-move m_position (@0x00454254-@0x00454272).
  • ConstrainTo never armed on the remote teleport branch (the path returns at :1504, before :1522), where retail does re-arm after teleport_hook's UnConstrain.

Route 4 fixes all three by construction — which means route 4 is a behaviour change to every visible creature in the game, not a refactor.

Allocation: not the blocker anyone feared

The inventory's "frame-frequency routes are blocked on placement allocation" warning was written pre-C2 and before anyone checked which branch remotes take. The steady state — nearby, grounded, non-teleporting — classifies to Interpolate, which runs no SetPosition at all. The 944 B/op cost applies only to teleports, cellless first placements, and >=96 m remotes.

Arithmetic (assumptions, not a capture): 40 remotes x 7 Hz with ~10% on a SetPosition branch ~= 26 KB/s. Pathological dense-town worst case ~= 264 KB/s, about 2.4% of the Slice-G5 profile. Not a blocker — provided the near branch stays out of SetPosition. The contract must forbid routing every remote Position through it "for uniformity".

Unmeasured and worth measuring before pinning a budget: the legacy path's actual cost (no gate covers PhysicsEngine.SetPosition), and the real near/far split in live play.

Route 4's one big advantage: it is trivially observable

Route 2 cost five review rounds largely because nothing a user could do would trigger it. Route 4 is the opposite — every remote entity exercises it continuously. Six checks, all within two minutes of ordinary play with a second character:

  1. Near interpolate — second character walks/runs in a circle 5-15 m away. Smooth motion, no per-packet stepping.
  2. Airborne no-op — they jump, and jump off a ledge. Clean parabola, clean landing. Watch for invisible-but-solid, the #184 signature.
  3. Far-snap — they run >96 m and back. No freeze at the boundary, no wrong Z.
  4. Remote teleport — portal/recall out and back, plus an admin @teleto. Arrive grounded, animating, immediately targetable.
  5. The leash — route 4 moves ConstrainTo from before to after, stops arming it on the airborne no-op, starts arming it on teleport. #167 was a leash bug.
  6. Creature blast radius — pull a drudge, let it chase, melee it, let it die.

Weight this gate heavily. All four route-2 states were green and three were defective; route 4 finally has a real oracle.

The decision

Route 4 is worth doing — it fixes three unfiled divergences affecting every creature, retires the largest duplicate-authority cluster in the codebase, and builds the seam route 5 needs (the classifier gives Projectile identical branches to Remote). But it is a 1,500-2,500 line behaviour change, not a wiring job, and it should not start on a budget everyone knows is wrong.

Options, in the order I would rank them:

A. Split route 4 into 4a and 4b. 4a = the interpolation/near path plus the airborne no-op (the steady state, highest visible value, no park hazard, no service-window work). 4b = teleport/far/cellless, which is where the parks, the service-window guard, N3 and #277 all live. Each gets its own contract, gate and review. Roughly halves the blast radius per commit and puts the observable win first.

B. Do route 4 whole against a corrected budget (~2,000 lines, 2-3 implementation passes, one adversarial review per pass). Honest, but it is a large single commit touching every visible creature.

C. Defer route 4; do routes 5-7 first. Rejected — route 5 needs the same seam, so this just moves the cost.

D. Stop the campaign here and simplify instead. The complexity concern that prompted the budget is real, and RuntimeSetPositionState at 5,652 lines is the obvious target. But route 4 is where three live divergences get fixed, so stopping leaves known-wrong behaviour in the client.

Recommendation: A. It respects the budget's intent — keep each landing small enough to review — without abandoning work that fixes real bugs. It also front- loads the part with a clean live gate and defers the part with the known traps.