acdream/docs/research/2026-08-05-c5-scoping.md
Erik 0991182198 research: scope C5 — the deletion is smaller than the plan implies, and half the rows are blocked on a behaviour change
Verified by symbol at HEAD 52175aa1, behind two exhaustive caller censuses
(legacy placement writers; the probe family).

DELETION INVENTORY: 6 deletable symbol groups (~540 production lines), 2 more
deletable only via #275, and 5 that the plan or handoff names as deletable but
are NOT. Deletable now, all with zero production callers: PhysicsEngine.Resolve
(~360 lines — this IS AD-1's legacy demote/lift body) plus its private
HasCellSurface; PhysicsEngine.ResolvePlacement; PlayerMovementController
.SetPosition (both overloads); CommitPreparedPosition; the BeginAcceptedPlacement
/BeginAuthoredPlacement test wrappers. Hazard recorded: production
IsSpawnCellReady sits physically inside the Resolve block.

THE PLAN'S FRAMING IS WRONG FOR HALF THE ROWS. AP-1 and AD-1 are provably
retirable in the deletion commit — both rows' "production still routes through
the legacy resolver" sentences are already false at HEAD. But AP-131 and
AD-60's legacy half are blocked on #275, which is a BEHAVIOUR CHANGE
(classify-then-merge on every steady-state Position, retail Gate A, wire-cell
withhold across a 45+ site residency-predicate blast radius) — not a deletion.
"C5 deletes the paths and the rows retire" holds for two rows and not the
other two.

SEVEN TEST-ONLY-CALLER CASES, each with a disposition, because this is where a
deletion slice silently removes coverage: Resolve's unit tests (behaviour gone
— delete); Issue133DungeonTeleportPrefixTests (behaviour MOVED — re-point, it
is a named-bug regression pin); InitialPlacementOverlapTests and
TransitionScratchDifferentialTests (verify-then-delete / re-point the
differential arm); ~19 files using PlayerMovementController.SetPosition as
fixture setup (mechanical re-point); CommitPreparedPosition's tests (re-point
at the leash-arm replacement); the Begin* wrappers' ~40 sites (keep as a seam).
Also flagged: the #316-preserving test pins a defect verbatim and inverts by
design once measured.

THE CELL-LESS TRIGGER, ESTABLISHED BY READING rather than deferred to a live
session: pickup-then-drop. TryApplyPickup leaves an ACTIVE cell-0 record, and
ACE's drop sends that guid a bare UpdatePosition (Player_Inventory.cs:1443)
with no client-side delete. What remains unknown is only whether a CreateObject
from NotifyPlayers (Landblock.cs:900) precedes it on the ordered stream, which
depends on when ACE populates the fresh item's known-players set — a two-minute
probe run settles it, with route 5's "the gate cannot exist, record that" as
the honest fallback.

SEQUENCING: run the owed gates FIRST as one cheap user session on the current
binary (route-7 thickening, the cell-less falsification, #316's measurement),
then C5a (deletions + #318 + route-2's B2 parity test + retire AP-1/AD-1), then
C5b (#275 + AP-131/AD-60, own contract and dual review), then C5c (closeout
gates after #280 per the plan's own ordering, the ~1,340-line probe strip, the
ledger). The probe strip goes LAST because the owed gates depend on the probes,
with a REMOTE_LANDING carve-out if #316 stays unmeasured.

Seven false or stale plan claims found and listed, including AP-1/AD-1's own
row texts, ILocalPlayerTeleportPlacement being called deletable when it is a
live post-commit suffix, and the 4b "to delete" list being overtaken — the
pre-op ConstrainTo and duplicate constants are already gone and the remainder
is now canonical post-collapse.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-05 09:26:11 +02:00

357 lines
30 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

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

# 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 C0C4. 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-141145 (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, ~150300 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 | ~100200 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` | ~3080 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 350500, 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,5002,000 test lines deleted/re-pointed | Low (all compile-loud), but the §3 dispositions are judgment work |
| #275 unification (§1b/§2) | ~150400 changed on the hottest inbound path | ~300600 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 | ~250500 | 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.