acdream/docs/research/2026-08-05-c5a-contract.md
Erik f8e55ba5e4 fix(physics): route local-player shadow presentation through SyncPose (#318, AP-145)
RuntimePlacementPresentationSink.TryPublishPlace previously published the
local player's collision-shadow pose with a direct LocalPlayerShadowState.Set
call — a plain cache write that never touched PhysicsEngine.ShadowObjects.
Because LocalPlayerShadowSynchronizer.SyncPose's own dedup check compares
against that same cache, the direct write could pre-seed the cache with the
destination pose and cause the next real SyncPose call to see "nothing
changed" and skip its own ShadowObjects publish — leaving the real collision
shadow at the pre-teleport position until an unrelated movement tick forced
a real publish.

Fix: TryPublishPlace now calls _localPlayerShadowSync.SyncPose(...,
force: true), the same publisher ordinary per-tick movement uses, so Place
always drives a real ShadowObjects write before the cache updates.
TryPublishWithdrawal carried the exact mirror asymmetry (a bare
LocalPlayerShadowState.Clear with no ShadowObjects.Suspend, leaving a live
phantom shadow row at the park's source cell for the whole park window — the
#184 shape) and is fixed in the same commit, same one-call shape:
_localPlayerShadowSync.Suspend(entity). The sink no longer holds a direct
LocalPlayerShadowState reference; both halves route exclusively through the
one synchronizer, which owns the cache internally.

The single LocalPlayerShadowSynchronizer instance is now constructed in
LivePresentationComposition (before the sink) and threaded through
LivePresentationResult to SessionPlayerComposition, which no longer builds
its own — this guarantees the sink's Place/Withdraw edge and ordinary
per-tick movement publish through the exact same publisher and cache rather
than two independent instances that could drift out of sync with each other.

TryPublishPlace's xmldoc now states the behavioural nuance directly: routing
through SyncPose means Place inherits SyncPose's own admission guard
(IsHidden, cellId == 0, not-current-visible-projection), which the old
direct .Set() call never consulted. Under those conditions SyncPose now
calls Suspend instead of publishing — correct and symmetric, but new
behaviour worth flagging at the call site, not just in a test comment.

RuntimePlacementShadowCompositionTests.cs (#318) proves four facts against
the real ShadowObjects registry, not the cache: a bare Place publishes a
real row at the destination cell with the source cell's row gone; a
subsequent ordinary per-tick Sync is then a correct no-op; a Place for a
registered non-local-player entity leaves its row at the source cell
untouched and never touches the player's cache (route 7 P4 — the fix lives
entirely inside the pre-existing player-only gate); and Withdraw suspends
the real registry row, not just the cache, with the retained
(suspendable) registration surviving for a later restore. All four were
sabotage-verified in both directions.

RuntimeForcePositionRenderCommitTests.cs (B2) drives a real end-to-end
accepted ForcePosition through RuntimeEntityObjectLifetime.TryApplyPosition
and RuntimeAcceptedPositionDriveController.TryExecuteAcceptedLocalPosition
against a live HostFixture, asserting both the committed render position
AND a cell change that deliberately crosses out of the spawn's outdoor grid
cell, so the cell assertion is independently falsifiable rather than riding
along with the position assertion.

Retires AP-145 (this fix) in docs/architecture/retail-divergence-register.md.
AP-1 and AD-1 are untouched by this commit — they retire separately in the
deletion-sweep commit that follows.

Evidence chain: docs/research/2026-08-05-c5a-contract.md (the governing C5a
slice contract), docs/research/2026-08-05-c5a-architecture-review.md (round
1, FAIL — three MAJORs: vacuous route-7 P4 test, unfixed Withdraw-side
mirror asymmetry, non-driving B2 test), docs/research/2026-08-05-c5a-architecture-review-round2.md
(round 2, PASS with two MINORs — an unfalsifiable B2 cell assertion and the
undocumented SyncPose guard nuance, both fixed here).

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

470 lines
29 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.

# C5a contract — legacy deletion sweep + carried parity tests (pinned 2026-08-05)
Pinned at HEAD **`392c1e22`** (branch `claude/acdream-physics-divergence-5aa784`),
i.e. AFTER #319 landed. Every symbol, caller census, and line number below was
**re-verified against this HEAD by grep/read**, not inherited from the C5
scoping (`2026-08-05-c5-scoping.md`, written at `52175aa1`) — §9 lists every
place the scoping's picture moved. Baseline: complete Release suite
**11,112 passed / 4 skipped / 0 failed**, measured at `392c1e22` (the #319
commit message records the measurement; re-measure at implementation start,
never inherit — process rule (c)).
**Scope, stated negatively first:**
- **NOT #275.** The steady-state inbound-Position merge
(`InboundPhysicsStateController.TryApplyPosition`, the simple overload) and
`RuntimeEntityObjectLifetime`'s wire-derived `FullCellId` refresh (the
`refreshPosition: acceptedPosition` call, **now at `:1926`** post-#319) are
the C5b behaviour change with its own contract. C5a must not modify either
file's executable code (one test-file doc-comment correction is the only
permitted touch near this surface, §1 D7).
- **NOT the probe strip.** All six `ACDREAM_PROBE_*` temporary flags stay
(C5c); they are env-gated and inert to everything here.
- **NOT AP-131, NOT AD-60's legacy half, NOT AP-145's seam** (except the
pre-authorized red branch in §5.1). Those rows stay in the register
untouched.
**Scope, positively:** the six deletion groups in §1 (~490 production lines),
the seven test-caller dispositions in §3, retirement of register rows **AP-1**
and **AD-1** in the same commit as the deletions, and the two carried parity
tests in §5 (#318 composition; route-2 B2).
---
## 1. Deletion inventory — re-verified at `392c1e22` by symbol
Caller censuses below are exhaustive over `src/` (all `*.cs`). Method: for
`Resolve`, every `.Resolve(` receiver in `src/` was enumerated and typed — 38
distinct receiver/site classes, **none** a `PhysicsEngine` (see the grep-hygiene
note in §3.8: two of them are #319's NEW `ParentAttachmentState.Resolve`, a
name collision that did not exist when the scoping ran its census). For the
others, direct symbol grep over `src/` and `tests/`.
| # | Symbol | Location at HEAD | Production callers | Test callers | ~Lines |
|---|---|---|---|---|---|
| D1 | `PhysicsEngine.Resolve(Vector3, uint, Vector3, float)` | `src/AcDream.Core/Physics/PhysicsEngine.cs:1863``~2200` (body ends before `ResolveWithTransition`'s xmldoc; the live method at `:2223` is a **different member** and stays) | **ZERO** | `PhysicsEngineTests.cs` ×12 (`:41,:48,:66,:88,:111,:150,:186,:211,:391,:434,:446,:460`); `Issue133DungeonTeleportPrefixTests.cs:58` | ~360 |
| D2 | `PhysicsEngine.HasCellSurface` | `PhysicsEngine.cs:1767``~1789` | only `Resolve` itself (`:1887`) — deletes with D1 | none | ~23 |
| D3 | `PhysicsEngine.ResolvePlacement` | `PhysicsEngine.cs:2748``~2815` | **ZERO** (sole non-test mention is the already-recorded retirement comment at `HeadlessSessionWorldProjection.cs:794`) | `InitialPlacementOverlapTests.cs:42`; `TransitionScratchDifferentialTests.cs:185,:194,:208,:217` | ~70 |
| D4 | `PlayerMovementController.SetPosition` (both overloads) | `src/AcDream.Runtime/Gameplay/PlayerMovementController.cs:1746,:1760` | **ZERO** — every `.SetPosition(` in `src/` outside `PhysicsEngine.cs` is `WorldEntity.SetPosition` (receivers `entity.`/`child.`) or Core `PhysicsEngine.SetPosition` via `_physics.Engine.` from `RuntimeSetPositionState` (`:2028,:3125,:4789`), the canonical path | 19 test files, ~80 sites (44 in `PlayerMovementControllerTests.cs` alone) — **fixture setup**, not subject (§3.5) | ~30 gross; ~15 net after the retained seed (§3.5) |
| D5 | `PlayerMovementController.CommitPreparedPosition` | `PlayerMovementController.cs:1789` | **ZERO** — production replacement is `ArmConstraintLeashAtCommittedPlacement` (`:1815`), called from `RuntimeLocalPlayerPhysicsPublicationState.cs:774`; `PreparePositionForCommit` (`:1776`) remains production via `RuntimeLocalPlayerPhysicsPublicationState.cs:219` | `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`; production reaches the core via `Apply` (`:1304`) and the authored sequence | **39 sites across 9 Runtime test files** (scoping said ~40/10; re-censused) | **0 — KEEP as documented test seam** (§3.7) |
| D7 | Doc hygiene | stale `BlipPosition`/`PlayerMovementController.SetPosition` doc refs at `src/AcDream.Core/Physics/Motion/ConstraintManager.cs:25` (note: the scoping's path lacked `Motion/`) and `src/AcDream.Core/Physics/PhysicsBody.cs:442`; the stale `TryApplyPickup (:1116)` citation in `RuntimeAcceptedPositionDriveControllerTests.cs` (~`:221` region; the method lives in `RuntimeEntityObjectLifetime`, currently ~`:1258+`) — **cite by symbol, not line**, in the correction | — | — | ~20 comment lines |
Net production deletion: **~490 lines** (the scoping's ~540 minus D6's kept
~25 and D4's retained seed). All deletions are compile-loud.
**Confirmed unchanged from the scoping's §1c (NOT deletable, re-spot-checked):**
`ILocalPlayerTeleportPlacement`/`LocalPlayerTeleportPlacement.Place` (live
post-commit presentation suffix, `LocalPlayerTeleportController.cs:243` still
calls `entity.SetPosition(controller.Position)` inside it);
`PlayerMovementController.SetPositionCore` (`:1845`, production via
`PreparePositionForCommit`); the route-4 leftovers; AP-135's bookkeeping;
`RuntimeLiveEntitySessionController.cs:141`'s pre-flip path (its file WAS
touched by #319, but the C4/C5-revisit comment's unblock condition is still
unmet).
---
## 2. The survivor hazard — TWO production members inside the deletion region, not one
The scoping named one. Re-verification at HEAD finds **two**:
1. **`IsSpawnCellReady` (`PhysicsEngine.cs:1807`)** — production callers
`RuntimeSetPositionState.cs:2169,:4378` and
`SessionPlayerComposition.cs:374`. Sits between `HasCellSurface` (delete)
and `Resolve` (delete).
2. **`AdjustPosition` (`PhysicsEngine.cs:1813`)** — **the scoping never
dispositioned it.** It is production: `PhysicsCameraCollisionProbe.cs:38,:100`
(the camera collision probe), plus six-plus test files
(`PhysicsEngineAdjustPositionTests`, the camera replay suites,
`Issue177StairDescentCameraFloodTests`, ...). It sits between
`IsSpawnCellReady` and `Resolve` — dead centre of the physical block.
**Pinned survival rule:** the deletion is **member-wise, never region-wise**.
Delete exactly the bodies of `HasCellSurface`, `Resolve`, and
`ResolvePlacement`; `IsSpawnCellReady` (`:1807``:1811`) and `AdjustPosition`
(`:1813``:1861`) remain byte-identical in executable code.
**Xmldoc fallout (same commit):** `IsSpawnCellReady`'s summary contains
`<see cref="Resolve"/>` (in the `:1791``:1806` block) and `HasCellSurface`'s
summary names "the Resolve safety net"; the second dies with its method, the
first must be rewritten (the "loud outdoor-demote safety net" sentence
describes machinery this commit deletes — rewrite the paragraph to describe
the canonical `SetPosition` reality, do not leave a cref to a deleted symbol).
Sweep `PhysicsEngine.cs` for any other `cref="Resolve"`/`cref="ResolvePlacement"`
after the deletion; the build must be warning-clean on missing crefs.
---
## 3. The seven test-only-caller dispositions — re-verified, each carried forward
Framing (from the scoping, still correct): deleting a method whose callers are
tests is compile-loud. The silent hazard is the **disposition of the tests
afterward** — deleting a test that pinned MOVED behaviour loses the pin with a
green build. Each case below is a binding disposition; any deviation must be
argued in the commit message.
### 3.1 `PhysicsEngine.Resolve` unit tests — behaviour GONE → DELETE, after a one-pass audit
`PhysicsEngineTests.cs` (565 lines, 12 `engine.Resolve(` sites). The
outdoor-demote / legacy floor-snap / terrain-lift semantics die with the
method; canonical replacements have their own suites (`PhysicsSetPositionTests`,
`RuntimeSetPositionStateTests`). **Audit before deleting:** any individual
assertion that actually pins canonical-owned behaviour — specifically
`AdjustPosition` semantics, which SURVIVE — is re-pointed at `AdjustPosition`
directly (note `PhysicsEngineAdjustPositionTests.cs` already exists; a
re-point may land there). The audit's outcome (N assertions re-pointed, M
deleted) goes in the commit message.
### 3.2 `Issue133DungeonTeleportPrefixTests` — behaviour MOVED → RE-POINT (named-bug regression pin)
Verified at HEAD: the defect mechanism this test pins — the `lbPrefix`
resident-landblock scan that re-stamped a validated dungeon claim with a
neighbour's prefix — lives **entirely inside `Resolve`'s body**
(`PhysicsEngine.cs:1928,:1937,:1984-1995,:2193`). The canonical
`PhysicsSetPosition` path has **no lbPrefix scan** — the defect class cannot
recur there by construction. That is precisely why the pin must be
**re-pointed, not deleted**: #133 is a closed named bug, and the invariant
(a validated dungeon claim's landblock prefix is authoritative; a committed
cell never gets re-stamped from a neighbouring resident block) must stay
assertable against whatever path owns placement now.
**Re-point spec:** drive a teleport-classified canonical placement
(`RuntimeSetPositionState`, or Core `PhysicsEngine.SetPosition` if the fixture
cost is lower) with the test's exact geometry — dungeon claim `0x00070143`,
dungeon block world-offset so its local Y is negative, resident neighbour
block at origin containing the same XY — and assert the committed cell keeps
the `0x0007` prefix. Alternative accepted by this contract: PROVE an existing
canonical test already pins prefix authority for an off-bounds dungeon claim
and record the proof (test name + assertion) in the deleting commit. Silent
deletion is a contract violation.
### 3.3 `InitialPlacementOverlapTests` — behaviour MOVED → VERIFY-THEN-DELETE
The ring-search half of enter-world placement is ported inside canonical
`SetPosition` (`TransitionTypes.cs:1596` `FindPlacementPosition`, retail
0x0050C170; `PhysicsSetPositionTests.cs` header cites it and has
placement-probe scenarios, e.g. the `placementPasses >= 2` retry arm at
`:1152-1194`). **The audit criterion:** confirm `PhysicsSetPositionTests`
covers the **other-entity-occupancy** ring search this test pins (a relogging
player overlapping a registered creature sphere searches outward to the
nearest clear ring) — occupancy-driven, not merely BSP-failure-driven. If
covered: delete, citing the covering test by name. If not: re-point this
test's scenario through canonical `SetPosition` with a placement class that
reaches `FindPlacementPosition` (~50100 lines), then delete the
`ResolvePlacement` call.
### 3.4 `TransitionScratchDifferentialTests` — differential arm → RE-POINT OR DROP EXPLICITLY
Verified at HEAD: the spec-based sequence arms (`ResolveSpec.Resolve` at
`:618`) call `ResolveWithTransition`**untouched by this slice**. Only
`ReusedScratch_MatchesFreshPlacementSearch` (`:180``:227`, four
`ResolvePlacement` sites) is affected. It is a Slice-I zero-alloc scratch-reuse
differential over the placement search (including the hostile-identity
leak check). **Disposition:** re-point the arm at the canonical entry that
reaches `FindPlacementPos` (Core `SetPosition` with the appropriate placement
class), preserving both the bitwise fresh-vs-reused comparison and the
second-identity leak check. If re-pointing is disproportionate, the arm may be
dropped ONLY with an explicit commit-message decision naming what coverage the
I-slice differential loses — never silently.
### 3.5 `PlayerMovementController.SetPosition` fixture usage — NEITHER gone nor moved → RETAINED SEED + mechanical re-point
Census at HEAD: **19 test files** reference `PlayerMovementController` and
call `.SetPosition(`; ~80 sites total; 44 in `PlayerMovementControllerTests.cs`,
8 in `LocalPlayerTeleportControllerTests.cs`, 5 in `HeadlessSessionHostTests.cs`,
the rest 14 each. (Per-site care: a file can reference the controller and
still call `WorldEntity.SetPosition` — type each site during the re-point,
don't regex-replace blind.)
**Pinned design (the scoping's "cheaper and honest" option, adopted):** keep
**ONE** internal, explicitly-named test seed on the controller — rename the
3-arg overload to `SeedPlacementForTest(Vector3 pos, uint cellId, Vector3 cellLocal)`
(internal; xmldoc states it exists ONLY to seed fixtures and that production
placement flows through `PreparePositionForCommit`
`ArmConstraintLeashAtCommittedPlacement`), delete the 2-arg overload, and
mechanically re-point all ~80 sites. Semantics are reproduced by construction:
the seed calls the SAME `SetPositionCore` (which stays production), so the
grounded, zero-velocity start (the AD-61 force-seed) that dozens of movement
tests assume is unchanged. This is the one production-file signature change
in the slice; its body is untouched.
### 3.6 `CommitPreparedPosition` tests — behaviour MOVED → RE-POINT at the arm/commit replacement
Three sites, each audited individually:
- `PlayerMovementPlacementTransactionTests.cs:42` (100-line file): the
prepared-position transaction assertions run against the production pair
(`PreparePositionForCommit` + `ArmConstraintLeashAtCommittedPlacement`) —
rewrite the test against that pair, or delete it if
`RuntimeLocalPlayerPhysicsPublicationStateTests` provably covers the same
transaction shape (cite which test).
- `PlayerMovementControllerTests.cs:1158` and
`RuntimeLocalPlayerPhysicsPublicationStateTests.cs:3007` both assert
`Throws<InvalidOperationException>` on the uncommitted/displaced state.
Audit whether the replacement arm carries an equivalent guard; if yes,
re-point the throw assertion at it; if the guard died with the method,
delete the assertion WITH a commit-message note (guard semantics gone, not
overlooked).
### 3.7 `BeginAcceptedPlacement`/`BeginAuthoredPlacement` — NEITHER → KEEP AS DOCUMENTED SEAM
39 sites across 9 Runtime test files at HEAD. The wrappers are pure
pass-throughs to the production core (`BeginAcceptedPlacementCore`); deleting
them buys zero behaviour and costs broad mechanical churn across the Runtime
suite. **Disposition: keep, with an xmldoc sentence on each wrapper naming it
a test seam** (so a future sweep does not re-litigate this). This is a
recorded deliberate exception to "delete every superseded legacy path": the
wrappers are not a legacy PATH — the core they call IS the canonical path.
### 3.8 Landmines and grep hygiene
- **The #316-preserving pair** (`LiveEntityNetworkOnPositionCollapseMatrixTests.cs:131,:180`)
pins a defect **preserved verbatim**. C5a must not touch it; it inverts only
with #316's measured fix (C5-gate session / later).
- **#319 introduced `ParentAttachmentState.Resolve`** (`ParentAttachmentState.cs:432`),
called at `EquippedChildRenderController.cs:920` and
`RuntimeLiveEntitySessionController.cs:390`. A mechanical grep for
`.Resolve(` now hits relation resolution — neither site is `PhysicsEngine`.
Any "prove zero callers" re-run during implementation must type receivers,
not count matches.
- The affected-file overlap between #319 and this slice is **empty**: #319
touched `EquippedChildRenderController`, `LiveEntityHydrationController`,
`LiveEntityPresentationController`, `ParentAttachmentState`,
`RuntimeEntityObjectLifetime`, `RuntimeLiveEntitySessionController` — none
contains a C5a deletion target. Verified.
---
## 4. Register retirements — AP-1 and AD-1, with the code evidence; four rows explicitly untouchable
A row retires because the code proves its condition met. Both retirements ride
**in the same commit as the D1D5 deletions** (register rule 1).
### AP-1 — RETIRE. Evidence at `392c1e22`:
Row text: "Production zero-delta routes deliberately remain on the legacy
resolver until 4B2..." — **false at HEAD**:
1. The "legacy resolver" is `PhysicsEngine.Resolve`/`ResolvePlacement`. The
exhaustive receiver census (§1) shows **zero** `PhysicsEngine.Resolve` or
`.ResolvePlacement` call sites in `src/`.
2. Every production placement writer reaches Core `PhysicsEngine.SetPosition`
**only** through `RuntimeSetPositionState` (`:2028,:3125,:4789` — the only
three `_physics.Engine.SetPosition` sites in `src/`).
3. The row's named prerequisites (authored mover, rebucketing,
prefix-quiescence, body publication, atomic route cutover) landed across
C0C4; the local controller's body adoption landed at C3c.
4. Deleting D1D5 makes the retirement **structural**: the resolver-shaped
entry points cease to exist, so no future caller can re-open the row's
condition.
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 — deleting AP-1 does not orphan them.
### AD-1 — RETIRE. Evidence at `392c1e22`:
Row text: "Production authoritative placement still routes through the legacy
recoverable outdoor demote and outdoor-restore `max(terrainZ, z)` lift" —
**false at HEAD**: that code is `Resolve`'s body (demote at
`PhysicsEngine.cs:~1890-1910`, the outdoor `max(terrain, z)` lift inside the
snap block ~`:2160-2175`) and `Resolve` has zero production callers. The
lost-cell stand-in the row describes is unreachable from production. Deleting
D1/D2 removes the divergent mechanism outright.
### Must NOT be touched (each blocked on work outside this slice):
- **AP-131** — retires only with **#275** (C5b): the legacy
`TryApplyPosition` unconditional `installPlacementFrame: true, clearParent: true`
is still the ONLY steady-state production Position merge at HEAD.
- **AD-60's legacy half** — same gate (#275): the
`RefreshSnapshot(..., refreshPosition: acceptedPosition)` site — **`:1926`
at HEAD** (the scoping's `:1918` and the register's `:1338` are both stale;
cite the symbol) — still derives `FullCellId` from bare wire acceptance.
- **AP-145** — retires with **#318's fix**, never with its test. The §5.1
composition test makes the asymmetry falsifiable; only the pre-authorized
red branch may touch the seam, and then AP-145 retires in THAT commit.
- **AD-61 / AD-62 / AP-135 / AP-141146** — all carry their own retirement
conditions; none is met by anything in this slice. (AP-146 and the AP-132
amendment are #319's, three days old — do not disturb.)
---
## 5. The two carried parity tests
Both are test-only against HEAD's production code, land BEFORE the deletion
commit (they are independent of it and de-risk the slice's review), and both
follow process rule (e): sabotage-verified, with the WHICH-assertion-fails
check, both directions for dual-layer assertions.
### 5.1 #318 composition test (~150300 lines, App.Tests)
Drive a real portal arrival through the canonical drive controller + the
**REAL** `RuntimePlacementPresentationSink` + the **REAL** `PhysicsEngine`
(fixture patterns exist: `RuntimePlacementPresentationSinkTests.cs`,
`RuntimeFirstEntryHostIntegrationTests.cs`). The discriminating assertion:
> **`PhysicsEngine.ShadowObjects` (`PhysicsEngine.cs:147`) holds a row at the
> destination cell/position** — NEVER merely `LocalPlayerShadowState`'s dedup
> cache. AP-145's bypass (`RuntimePlacementPresentationSink.cs:243`
> `_localPlayerShadow.Set(...)` skipping `LocalPlayerShadowSynchronizer.SyncPose`'s
> publish) both skips the publish AND pre-seeds `SyncPose`'s dedup — a
> cache-only assertion is satisfied by the bug.
Plus the T8 write-ordering assertion from route 3 §8. Sabotage: perturb
`TryPublishPlace` to the cache-only shape and confirm the `ShadowObjects`
assertion (not an incidental one) fails; separately confirm a cache-only
assertion would pass under the same sabotage — proving the discriminator
discriminates.
**Pre-authorized red branch:** this test may legitimately FAIL at HEAD — the
composition drives placement with no subsequent movement tick, which is
exactly the window AP-145 says is unpublished. If red: **C5a's deletion work
does not absorb the fix.** The seam fix (routing the placement's shadow update
through the real publish) is a production behaviour change on a
narrow, low-frequency path; it lands as its **own reviewed commit** together
with the now-green test, retires **AP-145**, and closes **#318** — and the
composition test itself is its designed gate (the C4 handoff explicitly ruled
the connected route out as #318 coverage). If green: land as-is; #318 closes;
AP-145's row is then re-argued (its "why not observed" column may become its
retirement argument) — but only with the green evidence cited.
### 5.2 Route-2 B2 parity test (~100200 lines, App.Tests)
The campaign plan's recorded acceptance gap (plan §C4 route 2, recorded unmet
since 2026-08-03): an App-layer test driving an **accepted ForcePosition end
to end** through `RuntimePlacementPresentationSink` /
`TryApplyRuntimePlacementPlace` and asserting **the render entity's
position/cell came from the committed placement receipt** — closing the
"canonical body moves, render entity stays put" silent seam. Expected green
at HEAD (route 2 landed; the seam is merely uncovered). If red, the same
stop-and-report protocol as 5.1: a red parity test is a found defect, not a
test problem; it gets its own investigation before any deletion lands.
Sabotage: sever the receipt→render write and confirm the position/cell
assertion is the one that fails.
---
## 6. What must REMAIN true — the slice's invariants
1. **Zero production behaviour change.** The production diff consists of:
member deletions with zero callers (D1D5), comment/xmldoc edits (D7, §2),
and exactly one signature change with an untouched body (§3.5's seed
rename). No executable production statement is added or modified —
**except** in the pre-authorized 5.1 red-branch commit, which is its own
reviewed landing with its own register action.
2. **The two survivors survive.** `IsSpawnCellReady` and `AdjustPosition`
keep their exact executable bodies and all production callers
(`RuntimeSetPositionState.cs:2169,:4378`; `SessionPlayerComposition.cs:374`;
`PhysicsCameraCollisionProbe.cs:38,:100`).
3. **Every deleted symbol's absence is proven** by the compiler (all deletions
are compile-loud) AND every test caller has an explicit §3 disposition
executed in the same commit — no test deleted whose pinned behaviour moved
without its re-point landing alongside.
4. **AP-1 and AD-1 retire in the SAME commit as the D1D5 deletions** — never
before (the code proof is the deletion), never after (register rule 1).
5. **The DO-NOT-TOUCH set holds:** AP-131, AD-60, AP-145 (modulo 5.1 red
branch), AD-61/62, AP-135, AP-141146; the #275 surface files' executable
code; the six probe flags; the #316-preserving test pair.
6. **No skips.** The suite ends at 0 failed with the same 4 skips as
baseline — a new skip is a contract violation (process rule (d)).
7. **Counts are measured and reconciled.** The final suite total will move
(deleted legacy tests down, re-points and two parity tests up); the commit
message reconciles the net against baseline 11,112 explicitly (N deleted,
M added, expected total), never hand-waves it.
---
## 7. Gates
- **Complete Release suite** (`dotnet test AcDream.slnx -c Release -m:1` with
`ACDREAM_PAK_PATH` set), baseline **11,112 passed / 4 skipped / 0 failed**
at `392c1e22` — re-measured at slice start AND at each commit. Known flakes,
never conflated (they have been conflated twice): **#302**
(`PortalProjectionTests.ClipToRegion_FrameOwnedStore_…`, GC-allocation,
App.Tests — the `WarmedSteadyContactRefreshDoesNotAllocate` look-alike is
this class) and **#308** (`NakEmissionTests.LossSoak_…`, wall-clock,
Core.Net.Tests, full-suite load only).
- **NO connected gate for C5a — argued, not assumed.** Process rule (g): a
gate must be able to see the defect it gates. C5a's reachable defect
classes are (i) a compile break — seen by the build; (ii) silent coverage
loss — seen only by §3's dispositions and the review, invisible to any live
session; (iii) a behavioural regression — **structurally excluded** by
invariant 1: the production binary's reachable code is byte-equivalent, so
a connected session would exercise identical behaviour and measure nothing.
Precedents: route 5 recorded "no live gate can exist" rather than inventing
one; route 6 was a zero-production-line closure. The 5.1 red-branch commit,
if taken, ALSO needs no connected gate: #318's evidence channel is the
composition test **by design** — the C4 handoff explicitly refused to score
the connected route against it.
- **The review IS the coverage gate.** One dual review over the combined
slice diff (deletions + dispositions + parity tests), reviewers on Opus per
the standing audit rule, with §3's table as the review checklist: for each
of the seven cases, the reviewer confirms the disposition was executed as
pinned or the deviation argued.
---
## 8. Size, commit plan, and the split call
Calibration: campaign landings ran ~127 (route 7) to ~418 (route 3) to
~500 (4b-2) production lines each under full discipline.
| Piece | Production lines | Test lines | Risk |
|---|---|---|---|
| D1D5+D7 deletions + §2 xmldoc | ~490 deleted, ~0 added | ~1,5002,000 deleted/re-pointed across ~25 files | Low — compile-loud; the §3 dispositions are the judgment work |
| §5.1 #318 composition test | 0 (green) / ~1040 (red branch, own commit) | ~150300 | Low; red branch is a decision point, pre-planned |
| §5.2 route-2 B2 test | 0 | ~100200 | Low |
**Call: C5a HOLDS as one slice, in two (possibly three) ordered commits under
this single contract:**
1. **Commit 1 — the two parity tests** (test-only). Lands first: independent
of the deletions, de-risks review, and settles 5.1's green/red question
before the sweep. If 5.1 is red, its fix is **commit 1b** (own review
round, retires AP-145, closes #318) before proceeding.
2. **Commit 2 — the deletion sweep**: D1D5, D7, §2 xmldoc, all §3
dispositions, AP-1 + AD-1 row deletions. One diff, reviewable as one unit.
The ~490-line figure is at the top of the campaign's calibration band, but a
deletion of caller-free code is a different risk class from route 3's ~418
changed lines — the compiler proves most of it. What justifies keeping it
whole rather than splitting D1/D2/D3 (Core) from D4/D5 (Runtime): AP-1's
retirement evidence spans BOTH groups ("the last resolver-shaped entry points"
includes D4/D5), so splitting would either retire AP-1 on a half-proof or
leave the register straddling two commits — both worse than one larger
reviewable deletion. Do NOT fold in: #276's remainder, #317's audit, any
probe change (including the gate-4 `cause=` label improvement — C5c), or any
#275-adjacent edit.
---
## 9. What moved between the scoping (`09911821`, at `52175aa1`) and this contract (`392c1e22`)
1. **`AdjustPosition` is a second production survivor inside the deletion
region** (`PhysicsCameraCollisionProbe.cs:38,:100`) — the scoping's hazard
note named only `IsSpawnCellReady`. A region-wise delete would have taken
the camera collision probe's cell resolver with it. §2 pins member-wise
deletion.
2. **#319 created a `.Resolve(` name collision**: `ParentAttachmentState.Resolve`
(`:432`), called from two files. The scoping's census predates it. Callers
must be typed, not counted (§3.8).
3. **AD-60's legacy-half site moved to `RuntimeEntityObjectLifetime.cs:1926`**
(scoping said `:1918`; the register row still says `:1338`). C5a doesn't
touch it, but C5b's contract must cite by symbol.
4. **Begin* wrapper census: 39 sites / 9 files** (scoping: ~40 / 10).
Immaterial to the disposition.
5. **The scoping's D7 path `ConstraintManager.cs` is actually
`Motion/ConstraintManager.cs`** (`src/AcDream.Core/Physics/Motion/`).
6. **Everything else in the scoping's §1a/§1c/§2/§3 holds exactly at
`392c1e22`**: zero production callers re-proven for all six groups; #319
added no caller to any deletable symbol; the #319-touched file set is
disjoint from every deletion target; the seven dispositions carry forward
unchanged in substance.
7. **New since the scoping, absorbed here:** gate 4 closed 2026-08-05 as a
probe-label artifact (`af828a8a`) — the cell-less falsification is no
longer C5-gate-session work; and route 7's gate criterion was corrected to
the positive child-cell-equals-parent assertion (`2687d893`), whose
still-owed connected run belongs to the C4/#319 ledger, not to C5a.