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

29 KiB
Raw Blame History

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 ResolveWithTransitionuntouched 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 PreparePositionForCommitArmConstraintLeashAtCommittedPlacement), 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.