diff --git a/docs/research/2026-08-05-c5-scoping.md b/docs/research/2026-08-05-c5-scoping.md new file mode 100644 index 00000000..a109cba5 --- /dev/null +++ b/docs/research/2026-08-05-c5-scoping.md @@ -0,0 +1,357 @@ +# C5 scoping — legacy deletion + closeout gates (2026-08-05) + +Scoped at HEAD `52175aa1` (branch `claude/acdream-physics-divergence-5aa784`, +identical to `main` — C4 merged and pushed today). Every claim below was +verified **by symbol at this HEAD**, not inherited from a planning document; +where a planning document turned out wrong, §8 says so explicitly. Inputs: +the C4 closeout handoff (`2026-08-05-c4-closeout-handoff.md`), the campaign +plan (`2026-08-02-placement-cutover.md`), register rows AP-1 / AD-1 / AP-131 / +AD-60 / AP-141..145, and two exhaustive caller censuses run against this +worktree (legacy placement writers; the temporary probe family). + +**One-paragraph verdict:** C5 is three different kinds of work wearing one +slice name. (1) A **deletion sweep** that is smaller than the plan implies — +most of the big deletions already landed inside C4's routes; what is left is +~500 production lines, all with test-only callers, plus large mechanical test +churn. (2) **#275**, which is NOT a deletion: it is a behaviour change on the +steady-state inbound-Position merge (the hottest wire path), and it is the +*only* thing that can retire AP-131 and AD-60's legacy half. (3) The +**closeout gates + probe strip + ledger**. These must not ride in one landing; +the split is drawn in §7. + +--- + +## 1. The deletion inventory, verified at HEAD by symbol + +### 1a. Deletable now — zero production callers, test callers only + +Every entry below was grepped across all of `src/`; the caller lists are +exhaustive at `52175aa1`. + +| # | Symbol | Where | Production callers | Test callers | ~Lines | +|---|---|---|---|---|---| +| D1 | `PhysicsEngine.Resolve` | `src/AcDream.Core/Physics/PhysicsEngine.cs:1863-2222` | **ZERO** (the live per-tick method is `ResolveWithTransition`, a different member) | `tests/AcDream.Core.Tests/Physics/PhysicsEngineTests.cs` (12 sites) + `Issue133DungeonTeleportPrefixTests.cs:58` | ~360 | +| D2 | `PhysicsEngine.HasCellSurface` | `PhysicsEngine.cs:1767-1783` | only `Resolve` itself (`:1887`) — deletes with D1 | none | ~17 | +| D3 | `PhysicsEngine.ResolvePlacement` | `PhysicsEngine.cs:2748-~2810` | **ZERO** (its retirement is already recorded as done in `HeadlessSessionWorldProjection.cs:794`) | `InitialPlacementOverlapTests.cs:42`; `TransitionScratchDifferentialTests.cs:185,194,208,217` | ~65 | +| D4 | `PlayerMovementController.SetPosition` (both overloads) | `src/AcDream.Runtime/Gameplay/PlayerMovementController.cs:1746,1760` | **ZERO** (only stale doc-comment mentions at `ConstraintManager.cs:25`, `PhysicsBody.cs:442` — both also still name the long-deleted `BlipPosition`) | ~19 test files, used as **fixture setup**, not as subject (see §3) | ~30 | +| D5 | `PlayerMovementController.CommitPreparedPosition` | `PlayerMovementController.cs:1789` | **ZERO** (production replacement is `ArmConstraintLeashAtCommittedPlacement:1815`, called from `RuntimeLocalPlayerPhysicsPublicationState.cs:774`) | `PlayerMovementPlacementTransactionTests.cs:42`; `PlayerMovementControllerTests.cs:1158`; `RuntimeLocalPlayerPhysicsPublicationStateTests.cs:3007` | ~25 | +| D6 | `RuntimeSetPositionState.BeginAcceptedPlacement` / `BeginAuthoredPlacement` | `src/AcDream.Runtime/Physics/RuntimeSetPositionState.cs:1321,1333` | **ZERO** — pure pass-throughs to `BeginAcceptedPlacementCore`, which production reaches via `Apply` (`:1304`) and the authored sequence (`:1466`) | ~40 call sites across 10 Runtime test files | ~25 | +| D7 | Doc hygiene | tombstone comment `LiveEntityNetworkUpdateController.cs:2469-2478`; stale `BlipPosition` refs above; stale `TryApplyPickup (:1116)` citation in `RuntimeAcceptedPositionDriveControllerTests.cs:221` (the method is now at `RuntimeEntityObjectLifetime.cs:1258`) | — | — | ~20 | + +**Deletion hazard inside D1/D2's block:** `IsSpawnCellReady` +(`PhysicsEngine.cs:1807`) sits physically BETWEEN `HasCellSurface` and +`Resolve` and is **production** (`RuntimeSetPositionState.cs:2169,4378`; +`SessionPlayerComposition.cs:374`). A region-wise delete takes it by accident. + +Production-line total for 1a: **~540 lines**, all compile-loud (see §3 for +why that matters). + +### 1b. Deleted only BY #275 — cannot be deleted standalone + +These two sites are the code bodies of AP-131 and AD-60's legacy half. They +have a **live production caller** — the steady-state Position merge — so +deleting them is not a sweep item; it is the #275 behaviour change (§2). + +- `InboundPhysicsStateController.TryApplyPosition` (the simple overload, + `src/AcDream.Runtime/Entities/InboundPhysicsStateController.cs:610`, which + hardcodes `installPlacementFrame: true, clearParent: true` at `:662-663`). + Production path at HEAD: `LiveEntityInboundAuthorityGate.cs:161` → + `LiveEntityRuntime.cs:2550` → `RuntimeEntityObjectLifetime.TryApplyPosition` + `:1781` → `RuntimeEntityDirectory.cs:654` → this overload. The + route-flag-threaded overload (`:681`) already exists and is what the + continuation executor uses (`RuntimeInitialCreateContinuationExecutor.cs:1991-1992`). +- `RuntimeEntityObjectLifetime.cs:1918` — `RefreshSnapshot(canonical, snapshot, + refreshPosition: acceptedPosition)`: the wire-acceptance-derived `FullCellId` + write AD-60 names as "part of the AP-1 divergence this campaign is removing". + The executor's equivalent already withholds it (`refreshPosition: false`, + executor `:1996`). + +### 1c. Named by the plan/handoff as sweep material but NOT deletable — with reasons + +This is where the plan is most wrong at HEAD. Five items: + +1. **`ILocalPlayerTeleportPlacement` / `LocalPlayerTeleportPlacement.Place`** + (`src/AcDream.App/Streaming/LocalPlayerTeleportController.cs:176,207-284`). + The handoff lists it as a "sweep candidate (now a thin acknowledge seam)". + It IS thin, but it is **live production**: route 3 kept it as the + post-commit presentation suffix (target-watcher `NotifyTeleported`, camera + reset, spatial reconcile) and the A10 review fix explicitly decided its + redundant `WorldEntity` pose writes are "kept because they cost nothing". + Disposition: **no deletion**. Optional zero-risk simplification (inline the + interface) is not worth review cost; recommend leaving it and striking it + from the sweep list. +2. **`PlayerMovementController.SetPositionCore`** (`:1845-1920`) — STILL + PRODUCTION via `PreparePositionForCommit` (`:1782`) ← + `RuntimeLocalPlayerPhysicsPublicationState.cs:219` (the first-entry + publication candidate build). The AD-61 force-seed + (`Contact|OnWalkable|Active` at `:1860-1863`) is still inside it — filed, + argued inert (overwritten by the faithful activation commit + settle), and + **not** a C5 retirement target. Note the tension the census surfaced: the + route-3 comment at `PlayerMovementController.cs:1975-1985` repudiates this + exact overwrite for the teleport path while first-entry still runs it — + that is AD-61's documented state, not new debt. +3. **The route-4 leftovers the 4b plan listed as "4b's to delete"** — the plan + text is overtaken. At HEAD: the pre-operation unconditional `ConstrainTo` + **is already deleted** (tombstone at `LiveEntityNetworkUpdateController.cs:2469-2478`; + the only arming site is the post-operation one at `:2853`); the duplicate + `96f`/`4f` constants **are already deleted** (zero hits in the file); the + `!update.IsGrounded` early return (`:2622-2627`) is **no longer a + player-arm carve-out** — since the OnPosition collapse it is the guid-blind + D2 wire-airborne return-0 shape (retail `MoveOrTeleport @0x0051636D`), i.e. + canonical, not legacy; and `ApplyRemoteContactRouting` (`:1175-1348`) is + the canonical five-arm post-collapse routing with exactly one production + caller. **None of these is C5 deletion material.** +4. **AP-135's bookkeeping** (`RemoteMotion.CellId` writers at `:1392,:1622`; + `LastServerPos`/`LastServerPosTime` at `:1393-1394,:2895-2896,:786`) — + deliberately retained per the row's own retirement condition (retires with + the free-fall sweep gate, which C5 does not touch). Keep. +5. **`RuntimeLiveEntitySessionController.cs:141`** — the content-less headless + host's pre-flip direct `RegisterEntity` path, the one forward-looking + "C4/C5 revisit" comment in all of `src/`. Its stated unblock condition + ("once the direct-host conductor drive no longer requires prepared + content") is not met at HEAD. **Carry, do not delete.** + +Also checked and confirmed already-deleted (comment references only): +`RemoteTeleportController`, `RemoteTeleportPlacement`, +`LocalForcePositionTransaction`, `HeadlessSessionWorldProjection.BlipLocalPlayer`, +`ResynchronizeLocalPlayerForPortalArrival`, `LocalPlayerTeleportPlacement`'s +old duplicate `Place` authority, the `ClassifyLeaveWorld` family, +`BlipPosition`. `LiveEntityRuntime` has **no** legacy CreateObject position +seed; `HeadlessSessionWorldProjection` has **no** remaining direct placement +writer; `PhysicsEngine.SetPosition` (Core) is called **only** from +`RuntimeSetPositionState` (`:2028,:3125,:4789`) in production. `PhysicsBody.SnapToCell`'s +eleven src callers are three canonical-owner sites, two per-tick simulation +sites, and five projectile/static/first-entry body seeds that are live by +design (AP-141 pinned the projectile no-machinery decision) — none deletable. + +--- + +## 2. Register rows: provable retirements vs blocked + +| Row | Verdict | Evidence | +|---|---|---| +| **AD-1** | **RETIRABLE in the deletion commit.** Its divergence sentence — "Production authoritative placement still routes through the legacy recoverable outdoor demote and outdoor-restore `max(terrainZ, z)` lift" — is **already false at HEAD**: that code is `PhysicsEngine.Resolve` (`:1863-2222`; demote at `:1906`, lift at `:2168-2173`) and it has zero production callers. Its listed prerequisites (authored-mover, rebucketing, prefix-quiescence, body-publication, route cutover) all landed C0–C4. Deleting D1/D2 makes the retirement structural rather than merely observational. | census §2; `PhysicsEngine.cs:1863` | +| **AP-1** | **RETIRABLE in the deletion commit.** "Production zero-delta routes deliberately remain on the legacy resolver until 4B2" is stale: every production placement writer now reaches Core `SetPosition` only through `RuntimeSetPositionState`; the local controller's body adoption / sealed setter landed at C3c; deleting D1/D3/D4/D5 removes the last resolver-shaped entry points (all already caller-free). The narrower survivors (#276 settle-cell discard, AD-61 force-seed, AD-62 non-commit outcomes) are separately filed rows/issues and do NOT block AP-1's own condition. | census §§2,3,4,7 | +| **AP-131** | **BLOCKED — retires only with #275, which is a behaviour change, not a deletion.** The legacy caller AP-131 says "is deleted at the production cutover" is still the ONLY steady-state production Position merge at HEAD (chain in §1b), still passing unconditional `true/true`. Retiring it means computing the classified `ApplyPlacementFrameBeforeRouting` / `UnparentBeforeRouting` flags (plus retail's FORCE_POSITION Gate A skip) BEFORE the merge on the steady-state path, exactly as the executor already does for residence-pending entities. That changes what every ordinary inbound Position does to animated and parented entities — retail-correcting, but a live-path behaviour change requiring the full contract + dual-review discipline. | `InboundPhysicsStateController.cs:662-663`; executor `:1991-1992` | +| **AD-60 legacy half** | **BLOCKED — same gate, #275.** `RuntimeEntityObjectLifetime.cs:1918` still derives `FullCellId` from bare wire acceptance on every accepted steady-state Position (the 4b-3 D1 comment at `:1886-1899` states it plainly). Withholding it (executor's `refreshPosition: false` rule) moves residency changes onto placement/simulation commits only — the retail rule — but `FullCellId` is the residency predicate at 45+ sites (AP-142's own count), so the blast radius is real and this must not be smuggled into a sweep. | `RuntimeEntityObjectLifetime.cs:1915-1918` | + +**Rows adjacent but NOT C5 targets:** AD-61 (force-seed, deliberate), AD-62 +(non-commit force outcomes), AP-135 (retained bookkeeping), AP-141–145 (all +filed with their own conditions; AP-145 retires with #318's *fix*, not its +test — the test only makes the asymmetry assertable). + +--- + +## 3. THE DANGEROUS CASE — every test-only-caller path, with disposition + +First a sharpening the scoping question deserves: deleting a method whose +callers are tests is **compile-loud** (the test project breaks), same as a +production caller. The genuinely silent case is the *disposition of the tests +afterward*: deleting a test that pinned behaviour which MOVED (instead of +re-pointing it) is the silent coverage loss. Per item: + +| Path | Test callers | Behaviour gone or moved? | Disposition | +|---|---|---|---| +| `PhysicsEngine.Resolve` | `PhysicsEngineTests.cs` ×12 | **Gone** — the outdoor-demote/floor-snap/terrain-lift semantics are legacy-only; the canonical replacements have their own suites (`PhysicsSetPositionTests`, `RuntimeSetPositionStateTests`) | **Delete tests with the path**, after a one-pass audit that no individual assertion pins something canonical-owned (e.g. `AdjustPosition` behaviour, which survives — tests touching it re-point at `AdjustPosition` directly) | +| `PhysicsEngine.Resolve` | `Issue133DungeonTeleportPrefixTests.cs:58` | **Moved** — #133's invariant (landblock-prefix correctness on dungeon teleport claims) is now the canonical portal/teleport placement's job | **Re-point** at the canonical path (drive a teleport-classified `RuntimeSetPositionState` placement with a prefixed dungeon claim and assert the committed cell), or prove an existing canonical test already pins it and record that in the deleting commit. Do NOT silently delete — this is a regression pin for a named historical bug | +| `PhysicsEngine.ResolvePlacement` | `InitialPlacementOverlapTests.cs:42` | **Moved** — placement-ring occupancy search is ported inside canonical SetPosition (`find_placement_position`, covered by `PhysicsSetPositionTests`) | **Verify-then-delete**: confirm `PhysicsSetPositionTests` covers the occupied-spawn ring-search case this test pins; if not, re-point it at `Transition.FindPlacementPos` via the canonical entry before deleting | +| `PhysicsEngine.ResolvePlacement` | `TransitionScratchDifferentialTests.cs:185-217` | **Moved** — these are Slice-I zero-alloc differential arms | **Re-point or drop the arm explicitly**: if `ResolvePlacement` was one of the differential's measured entry points, replace with the surviving canonical entry; a silent drop would shrink the I-slice differential's coverage without anyone deciding it | +| `PlayerMovementController.SetPosition` | ~19 test files (~45 sites in `PlayerMovementControllerTests` alone) | **Neither** — these tests USE it as fixture setup (position the player), they do not test it | **Mechanical re-point** through one shared test helper that drives the production seeding path (publication `PreparePositionForCommit` → activation), or — cheaper and honest — keep ONE explicitly-named internal test-seed method with a doc comment stating it exists for fixtures only. Either way the fixture semantics (grounded start via the force-seed) must be reproduced deliberately, since dozens of movement tests assume a grounded, zero-velocity start | +| `PlayerMovementController.CommitPreparedPosition` | 3 sites | **Moved** — replaced by `ArmConstraintLeashAtCommittedPlacement` | **Re-point**: the prepared-position transaction assertions should run against the production arm/commit pair; `RuntimeLocalPlayerPhysicsPublicationStateTests:3007` likely already half-covers it — audit before deciding delete-vs-rewrite per test | +| `BeginAcceptedPlacement`/`BeginAuthoredPlacement` wrappers | ~40 sites, 10 files | **Neither** — the CORE (`BeginAcceptedPlacementCore`) is production; the wrappers are test conveniences | Two defensible calls: (a) keep the wrappers, documented as test seams (zero churn, zero coverage change); (b) delete and mechanically re-point every site at `Apply`/the authored sequence. The ~40-site churn buys nothing behavioural; **recommend (a)** unless the campaign wants the sweep to be absolute — in which case budget the churn (it is mechanical but touches the Runtime suite broadly) | +| `InboundPhysicsStateController.TryApplyPosition` (true/true overload) — #275's deletion | its unit tests assert the UNCONDITIONAL flags | **Deliberately changed** — the old assertions describe the divergence being removed | **Rewrite, never delete-only**: each unconditional-flag assertion becomes a classified-flag assertion (animated entity keeps its placement frame; ForcePosition on a parented entity does NOT unparent — retail Gate A), sabotage-verified both directions per process rule (e) | + +One more test-shaped landmine, inherited not created: the #316-preserving +pair (`LandingPacket_PlayerGuid_QueueClearedNoShadowPublish_316Preserved` / +`..._CreatureGuid_ShadowPublishedQueueNotCleared`) pins a **defect preserved +verbatim**. If C5's connected session measures #316 and the fix lands, that +test inverts by design — do not "fix" the test without the measurement. + +--- + +## 4. The C4 inheritance, sized + +| Item | What it is | Size / shape | +|---|---|---| +| **#318 composition test** | Drive a real portal arrival through the canonical drive controller + REAL `RuntimePlacementPresentationSink` + REAL `PhysicsEngine`; assert **`PhysicsEngine.ShadowObjects` holds a row at the destination** (never just `LocalPlayerShadowState`'s dedup cache — AP-145's `TryPublishPlace` → `.Set()` bypass both skips the publish AND pre-seeds `SyncPose`'s dedup so the next tick can skip too; a cache-only assertion is satisfied by the bug) plus the T8 write ordering | Test-only, ~150–300 lines, App.Tests. The wiring cost is composition (real sink + real engine in one fixture), not assertion count. Retires nothing by itself; it makes AP-145 falsifiable | +| **AP-145 asymmetry** | The cache-without-publish seam itself | NOT a C5 fix unless the composition test proves a live miss; the row argues why it self-heals in practice. Land the test first, decide after | +| **Route-2 B2 parity test** | The plan's recorded acceptance gap (plan §C4 route 2): no test drives an accepted ForcePosition end-to-end through the presentation sink and asserts the render entity moved from the committed receipt | ~100–200 test lines. **Flag: this deliverable is in the PLAN but absent from the handoff's "What C5 inherits" list** — carry it explicitly so it doesn't fall between documents | +| **#276 remainder** | `SpawnPlacementSettler.TrySettle` discards `settle.CellId` | ~30–80 production lines + a conformance test; NOT named a C5 deliverable by the plan — carry unless cheap to fold; does not block AP-1 (separately filed) | +| **#277** | far-Create service-window conversion | Trigger-conditioned (no radius changed); **zero C5 work** | +| **#313** | `DeclareValid` selection transfer | Selection UX, outside placement; carry | +| **#316** | player-arm landing never publishes the shadow — **measure before fixing**; the named instrument is `ACDREAM_PROBE_REMOTE_LANDING` | The measurement is a two-client landing observation — fold it into the early gate session (§5); the fix (if the #184 class) is its own later commit | +| **#317** | velocity-chain retail audit | Research-only (decomp), no code; carry unless the session has slack | +| **#309 narrow half** | `GotoLostCell` hidden-until-`reenter_visibility` | Carried, re-scope before running; not C5 | +| **#280** | portal destination prefetch | Its own slice with its own visual gate. **Ordering note the handoff does not restate:** the campaign plan's numbered order (items 3→4→5) puts #280 BEFORE C5's final-binary soak/matrix — sensible, since #280 changes reveal behaviour the matrix would exercise. C5's implementation slices are independent of it and can proceed; the **final** closeout gates should run after #280 lands, or the matrix will need re-running | +| **Probe strip** | six TEMPORARY flags | **~1,340 lines** total: REMOTE_SLIDE ~700, REMOTE_LANDING ~300, LOCAL_TELEPORT ~215, REMOTE_TELEPORT ~50, PARK ~41, CHILD_CELL ~33. All env-only (no DebugPanel mirrors), **zero test references** — the strip is test-free. Hazards recorded by the census: keep `ParseHexIdList` (shared with permanent `ACDREAM_DUMP_*`), keep the DISTINCT older `ACDREAM_PROBE_TELEPORT`/`LogTeleport` block that sits adjacent to `LogLocalTeleportArrival` (two flags, documented distinct at `RuntimeAcceptedPositionDriveController.cs:769-770`), keep the probe-aliased production members (`BodySnapThreshold`, `InterpolationManager` queue state, `bodyOnWalkableAtTickStart`), and trim `ResetForTest` line-wise, not wholesale | + +--- + +## 5. Gate sequencing — and the cell-less trigger, answered by reading + +**State at HEAD:** three of C4's four gates were run and user-passed on +2026-08-05 (route 3 unambiguous incl. the autorun-cancel line; route 6 visual; +route 7 pass but THIN — one `cause=propagate`). Owed: (a) route 7 thickening +(several equipped landblock crossings, expect double-digit `propagate`), and +(b) gate 4, `cause=cellless`, whose recorded recipe route 7 invalidated. + +### The cell-less trigger — what reading establishes + +The classifier's cell-less arm needs `PreMergeCommittedCellId == 0`, which +requires an **ACTIVE canonical record whose `FullCellId` is 0** at merge time +(`RuntimeEntityObjectLifetime.TryApplyPosition:1898` — a fully-withdrawn +record yields `null`, "no opinion", not 0). The production population of such +records exists and is well-defined: + +1. **Post-pickup items**: `TryApplyPickup` (`:1258`) commits + `SetFullCell(canonical, 0u, 0u)` at `:1314` and leaves the record ACTIVE + (withdrawn presentation, suspended clock, but in the directory). +2. Children zeroed by `WithdrawCommittedChildrenToCellless` on parent delete — + but ACE deletes tracked equipment alongside its wielder + (`Player_Tracking.cs:108-112`), so these tombstone rather than linger. +3. Unwield — **no longer cell-less** (route 7 D1/D2: committed child carries + the parent's cell), which is exactly why the old recipe died. + +The one ACE message that delivers a bare `UpdatePosition` to population (1): +**dropping the item**. `HandleActionDropItem` +(`references/ACE/.../Player_Inventory.cs:1437-1443`) sends the dropper +`GameMessageUpdatePosition(item)` directly after `TryDropItem` — and for +`RemoveFromInventoryAction.DropItem` no client-side delete precedes it (no +`InventoryRemoveObject`, no `DeleteObject`; `:222-247`), so the client-side +record is still the active cell-0 one from the pickup. + +**The deciding nuance, which reading could not fully settle:** inside +`TryDropItem`, `Landblock.AddWorldObjectInternal` calls `wo.NotifyPlayers()` +(`Landblock.cs:900`) which sends `CreateObject` to every player in the item's +freshly-created `PhysicsObj.ObjMaint` known-players set — all enqueued on the +same ordered reliable stream BEFORE the `UpdatePosition` at `:1443`. If the +dropper is in that set at that instant (populated synchronously during +`AddPhysicsObj`), the Create precedes the Position, the Position lands in the +initial-create residence branch, and it classifies against the Create's own +resolved cell — **not** cell-less. If known-player registration is deferred to +the player's next visibility tick, the `UpdatePosition` arrives first and +**IS the live cell-less trigger**. + +**Recommendation:** a bounded two-minute falsification run, not an open +investigation: with `ACDREAM_PROBE_REMOTE_TELEPORT=1`, pick up a ground item, +drop it, repeat a few times (outdoors + once indoors), and read the +`[remote-teleport]` lines. Either `cause=cellless` appears (gate 4 closes with +a recorded recipe: *pickup-then-drop*), or it provably takes the Create path — +in which case record the cell-less arm as **synthetic-fixture-covered by +design with no reachable live trigger against ACE**, exactly the route-5 +precedent ("when a gate cannot exist, record that, never a substitute"), +process rule (g). Do not leave gate 4 on file as pending indefinitely. + +### Sequencing + +**Run the owed gates FIRST, before any C5 implementation**, as one cheap +user session on the current binary: route-7 thickening (PROBE_CHILD_CELL) + +the cell-less falsification (PROBE_REMOTE_TELEPORT) + **#316's measurement** +(PROBE_REMOTE_LANDING; a second client jumping/landing while observed). +Rationale: (a) C4's ledger closes on evidence from the binary family its +routes were gated on; (b) if thickening or the cell-less run surfaces a +defect, C5's deletion inventory could change; (c) all three consume probes the +strip will later delete. C5 absorbs the *bookkeeping* of these gates (ledger +updates), not their execution debt. + +--- + +## 6. Probe-strip ordering + +The strip is **last — after every connected gate in this campaign has +consumed its probe evidence**, as its own commit: + +1. Owed-gate session (§5) consumes CHILD_CELL, REMOTE_TELEPORT, + REMOTE_LANDING. +2. C5 implementation slices land (deletions, #275, tests) — probes untouched; + they are env-gated and production-inert. +3. C5 closeout gates run on the final implementation binary — the + lifecycle/reconnect route re-exercises portals, so LOCAL_TELEPORT evidence + remains readable if anything regresses; keep the probes through this. +4. **Strip commit** (~1,340 lines, hazards per §4), then full Release suite + + one short lifecycle route as the strip's own gate. The strip is + behaviour-neutral by construction (env-gated emission only), so the + final-binary soak from step 3 remains valid evidence; the post-strip + re-run is the cheap proof of neutrality. + +**Conditional carve-out:** if #316's measurement does NOT happen in the §5 +session, `ACDREAM_PROBE_REMOTE_LANDING` must be **excluded from the strip** +(it is the issue's named instrument) and the family stripped five-wide with a +note — do not let a hygiene pass delete an open issue's measurement apparatus. + +--- + +## 7. Size and split recommendation + +Calibration: this campaign's landings ran ~127–~500 production lines each +(4a 364, 4b-2 350–500, 4b-3 ~250, r5 ~131, r7 ~127, r3 ~418), each with full +contract/dual-review discipline. + +| Piece | Production lines | Test lines | Risk | +|---|---|---|---| +| Deletion sweep (§1a) | ~540 deleted | ~25 files touched; ~1,500–2,000 test lines deleted/re-pointed | Low (all compile-loud), but the §3 dispositions are judgment work | +| #275 unification (§1b/§2) | ~150–400 changed on the hottest inbound path | ~300–600 rewritten/added | **High** — behaviour change on every steady-state Position; 45+ residency-predicate blast radius for the AD-60 half | +| #318 + route-2 B2 tests | 0 | ~250–500 | Low | +| Probe strip | ~1,340 deleted | 0 | Low, hazard-listed | +| Gates + ledger/docs | 0 | 0 | User time | + +Combined this is far past the ~500-line calibration, and #275 is a different +RISK CLASS from everything else. **Split into three landings plus the +pre-session:** + +- **C5-gate session (first, user-run):** route-7 thickening + cell-less + falsification + #316 measurement. Closes C4's ledger. +- **C5a — deletion sweep + tests:** §1a deletions, §3 dispositions, #318 + composition test, route-2 B2 parity test, **retire AP-1 and AD-1 in the + same commit as the deletions** (register rule 1). Deletion-only + test-only; + reviewable as one diff. +- **C5b — #275:** pinned contract (the executor's classify-then-merge order + is the template; retail Gate A is the FORCE_POSITION skip), single + implementer, dual reviews, sabotage-verified flag tests both directions. + **Retires AP-131 and AD-60's legacy half in its own commit.** This slice is + where process finding (a) — "verify the load-bearing premise in code at + implementation start" — applies to the classifier-flag semantics before + building on them. +- **C5c — closeout:** final-binary complete suite, lifecycle/reconnect route, + canonical nine-stop soak, two-client observation, user visual matrix + (sequenced after #280 per the plan's own ordering, §4), then the probe + strip + its neutrality gate, then register/roadmap/milestones/memory + the + successor handoff, close #275, close the campaign ledger. + +--- + +## 8. Plan claims found false or stale at HEAD (the seventh-stale-doc guard) + +1. **Campaign plan / handoff framing that C5's register retirements are a + deletion product:** AP-131 and AD-60's legacy half **cannot** retire by + deletion — they require #275, a live-path behaviour change (the plan does + name "close #275", so this is a framing error, not a missing item — but a + session that read only "delete every superseded legacy path" would ship + the sweep and wrongly retire the rows). +2. **AD-1's and AP-1's own row texts are stale in the conservative + direction:** the "production still routes through the legacy resolver" + sentences are already false at HEAD (`PhysicsEngine.Resolve` / + `ResolvePlacement` have zero production callers). They over-claim a + divergence, not a completed port — retire with the deletion commit. +3. **The handoff's sweep candidate `ILocalPlayerTeleportPlacement` is not + deletable** — it is the live post-commit presentation suffix with a + production caller (§1c item 1). +4. **The 4b plan's "every legacy fallback 4a left is 4b's to delete" list is + overtaken:** at HEAD the pre-op `ConstrainTo` and duplicate constants are + already gone, and the `!IsGrounded` return + `ApplyRemoteContactRouting` + are the canonical post-collapse retail shapes, not deletable debt (§1c + item 3). +5. **Stale line citations:** the handoff's `TryApplyPickup (:1116)` (now + `:1258`; the same stale `:1116` lives on in + `RuntimeAcceptedPositionDriveControllerTests.cs:221`'s doc comment); + `ConstraintManager.cs:25` and `PhysicsBody.cs:442` still document the + deleted `BlipPosition`/`SetPosition` pair. +6. **The handoff's "What C5 inherits" list omits the route-2 B2 parity test** + that the campaign plan itself records as "carry it into C5's parity tests" + — carried here (§4). +7. **The cell-less trigger is no longer "UNESTABLISHED needing its own + investigation" in the open-ended sense:** reading pins the candidate + population, the single candidate wire path (pickup-then-drop, + `Player_Inventory.cs:1443`), and the one ACE ordering fact a two-minute + probe run settles (§5). The honest outcomes are a recorded recipe or a + recorded cannot-exist — not a standing investigation.