From f4ef2b2a2a711cacc58bd2c102a5c6fffe0572c7 Mon Sep 17 00:00:00 2001 From: Erik Date: Sun, 2 Aug 2026 18:12:57 +0200 Subject: [PATCH] docs(physics): record cutover slice C3c completion + closeout C3c COMPLETE at 529e0e9d in the placement-cutover plan (five fix slices, R1 dual-review round, final gates). New closeout research note. ISSUES #276 (settle-CellId discard), #277 (route-1 far-Create radius bound), #278 (user-session triage bundle). Register AD-60/AD-61 numeric order. The next slice before C5 is the 6b28ff99 O(changed) collision clone (soak convergence); C4 resumes after. Co-Authored-By: Claude Fable 5 --- docs/ISSUES.md | 38 +++++++ .../retail-divergence-register.md | 2 +- docs/plans/2026-08-02-placement-cutover.md | 36 +++++- .../2026-08-02-c3c-cutover-closeout.md | 104 ++++++++++++++++++ 4 files changed, 174 insertions(+), 6 deletions(-) create mode 100644 docs/research/2026-08-02-c3c-cutover-closeout.md diff --git a/docs/ISSUES.md b/docs/ISSUES.md index 43677b55..bb2ae387 100644 --- a/docs/ISSUES.md +++ b/docs/ISSUES.md @@ -24,6 +24,44 @@ What does NOT go here: - Every session: scan OPEN issues at start; promote/close anything we touched during the session before ending. - Promoting to a Phase: mark as `DONE (promoted to Phase X)` + commit SHA where the Phase entry landed. +## C3c placement cutover — 2026-08-02 + +- **#276 — OPEN — SpawnPlacementSettler discards the settle's resolved + cell.** `SpawnPlacementSettler.TrySettle` + (src/AcDream.Core/Physics/SpawnPlacementSettler.cs:61) commits + `settle.Position` but never reads `settle.CellId`: a compressed + first-gravity-frame settle whose few-cm sweep crosses a cell boundary + (outdoor/EnvCell seam, stacked EnvCells) leaves the body's cell at the + placement cell until the next resolve corrects it. Inherited #270 + semantics — shared by the remote spawn seed and the C3c local + first-entry settle (register row AD-61). Fix shape: commit the + settle's resolved cell through the same body/cell channel the per-tick + resolve writeback uses; needs a conformance test placing a body above + a floor whose containing cell differs from the wire cell. Found by C3c + review round 1 (retail minor M2). +- **#277 — OPEN — route-1 far-Create relies on a practical radius bound, + not an invariant.** A graphical-host wire Create for a landblock the + streaming window never reaches keeps its residence + one drive-pending + entry for the session (re-Advanced per frame). Bounded today because + the collision-publication window (5×5, two-tier N₁=4) is strictly + larger than ACE's Create-broadcast group, so the far set is empty; if + C4 changes either radius, route 1 needs the F7-style + service-window/celless conversion + (`HeadlessSessionWorldProjection.cs:557-570` is the template). Wake + and despawn-reap paths are verified correct (C3c adversarial delta + review). Related narrowing: a remote whose landblock leaves the + headless service window between ProjectSpawn and placement commit + still parks (route 8, rarer than the pre-F7 leak). +- **#278 — OPEN — post-C3c user-session triage bundle (2026-08-02 + observations).** (a) purple materialization haze re-fires while + standing still — traced to the Hidden/UnHide script re-firing on a + visibility edge; most plausible driver is the pre-existing `6b28ff99` + streaming-convergence regression (its dedicated slice precedes C5) — + re-observe after that slice; (b) no lateral glide when walking against + impassable slopes — verify against open #269 (Campaign P slope-slide + residual) in the session before treating as new; (c) `/ls` command + reported non-working — identify which command surface at the session. + ## Current queue — 2026-07-27 - **Structural handoff:** all eight `GameWindow` decomposition slices and the diff --git a/docs/architecture/retail-divergence-register.md b/docs/architecture/retail-divergence-register.md index f73acf81..2b8dcaf7 100644 --- a/docs/architecture/retail-divergence-register.md +++ b/docs/architecture/retail-divergence-register.md @@ -145,8 +145,8 @@ readiness/requeue adaptation. See | AD-57 | **Re-argued from TS-24 at Campaign P P7 (2026-07-30).** Outbound `RawMotionState.Actions` is always empty at runtime. The packer emits `num_actions` + per-action pairs (L.2b, `RawMotionState::Pack` 0x0051ed10) and the R3-W1 action FIFO capability exists (`AddAction`/`RemoveAction`/`ApplyMotion`/`RemoveMotion`); no production input path ENQUEUES autonomous actions yet because the emote/autonomous-motion feature surface is unimplemented. An empty list is byte-identical to retail's own no-pending-actions state, so this is a feature gap, not a divergence of existing behavior. | packer `src/AcDream.Core.Net/Messages/RawMotionStatePacker.cs`; FIFO `src/AcDream.Core/Physics/RawMotionState.cs` | Every currently-shipped movement packet matches retail byte-shape; the gap only manifests when emote-class autonomous actions are implemented. | When emotes land, forgetting to route them through the FIFO would silently drop them from the wire. | `RawMotionState::Pack` 0x0051ed10 | | AD-58 | **Re-argued from TS-40 at Campaign P P7 (2026-07-30).** Retail's `physics_obj->cell` null test ("placed in the world") is proxied by the explicit `PhysicsBody.InWorld` flag — set by `SnapToCell` and `RemoteMotion` construction, consumed by `CMotionInterp`'s detached-object link-strip guards. Equivalence: every acdream body that would have a null retail cell pointer has `InWorld == false` (bodies exist only for world entities; the flag flips exactly at placement/withdrawal), so the guards fire on the same population. A structural adaptation of retail's pointer-as-state idiom to acdream's explicit-flag idiom, not scheduled debt. | `src/AcDream.Core/Physics/PhysicsBody.cs` (`InWorld`); `src/AcDream.Core/Physics/MotionInterpreter.cs` (3 guard sites) | If a future path creates a body before world placement without clearing `InWorld`, the link-strip guards misfire where retail's null-cell test would not. | `CMotionInterp` link-strip guards raw @305xxx | | AD-59 | **Filed 2026-08-02 (physics campaign, continuation-executor slice).** The `SameIncarnationCreate` envelope buffers one publish per committed stage and flushes them ALL, in stage order, only after the LAST stage commits (constant-true per-field predicate, `IsCurrent`-checked at flush - the per-field closure variant was invalidated by WeenieDescription's six-field `AdvanceCreateAuthority`). A subscriber sees N back-to-back events with no interleaved observation point, each carrying the FINAL merged post-envelope record state, not per-stage state. | `src/AcDream.Runtime/Entities/RuntimeInitialCreateContinuationExecutor.cs` (`ApplyEnvelope` buffered-publish tail; `Publish`/`PublishNow`) | Retail's own tail is one synchronous critical section, and retail emits ONE notice per Create (`ECM_Physics::SendNotice_CreateObject`, fired whenever a weenie exists, independent of the physics-registration outcome) - never N per-internal-step notices. The buffered flush is closer to retail's one-signal model than per-step publication would be, though not a literal 1:1 match. | A subscriber diffing consecutive `Updated` events from the SAME envelope to isolate one stage's delta gets every stage's cumulative state on each event - silently wrong incremental-diff logic, not a crash. | `SmartBox::HandleCreateObject` 0x00454C80 same-incarnation tail (one synchronous critical section); `ACCObjectMaint::CreateObject` 0x00558870 step 11 (`ECM_Physics::SendNotice_CreateObject`) | -| AD-61 | **Filed 2026-08-02 (C3c review round 1).** The #270 settle-timing compression now covers the LOCAL player: `RuntimeLocalPlayerPhysicsPublicationState.SettleFirstEntryGroundContact` runs the shared `SpawnPlacementSettler` exactly once after the dormant activation's final commit (suffix-current authority only), compressing retail's first post-`enter_world` gravity frame — which grants CONTACT/ON_WALKABLE from a real touch — into the placement transaction. The legacy App-era force-seed (`Contact\|OnWalkable\|Active` in `PlayerMovementController.SetPositionCore`) still RUNS during publication-candidate preparation and is then OVERWRITTEN by the faithful activation commit + settle (it was never deleted). Caveat (review minor M2): the settler commits `settle.Position` but discards `settle.CellId` — a settle whose few-cm sweep crosses a cell boundary keeps the placement cell until the next resolve corrects it (inherited #270 semantics; ISSUES entry filed) | `src/AcDream.Runtime/Gameplay/RuntimeLocalPlayerPhysicsPublicationState.cs` (`SettleFirstEntryGroundContact`); `src/AcDream.Core/Physics/SpawnPlacementSettler.cs` (`TrySettle`); overwritten seed `src/AcDream.Runtime/Gameplay/PlayerMovementController.cs` (`SetPositionCore`) | Timing compression only: contact comes exclusively from the sweep's real touch (no caller-bool seeding, no forced transients), an airborne spawn stays genuinely airborne, and the overwritten force-seed leaves no observable residue past the activation commit — the committed state is exactly what retail's first gravity frame produces | A settle crossing a cell boundary reports the stale placement cell for the frames before the next resolve; a future reader trusting `SetPositionCore`'s "treat as grounded" seed comment could reintroduce the Contact-without-plane state the landing family calls unrepresentable | `CPhysicsObj::enter_world` 0x00516170; `SmartBox::HandleCreateObject` 0x00454C80 | | AD-60 | **Filed 2026-08-02 (physics campaign, continuation-executor slice).** Executor Position-continuation merges never directly commit residency: `ApplyPositionAction` refreshes `canonical.Snapshot.Position` with the retained wire pose but withholds the derived `FullCellId` (`RefreshSnapshot(..., refreshPosition: false)`); only a Runtime `SetPosition` commit (the continuation's own classified placement) or a later simulation full-cell commit may change residency. The LEGACY immediate-apply path's `RefreshSnapshot(canonical, snapshot, refreshPosition: acceptedPosition)` (`RuntimeEntityObjectLifetime.cs:1338`) still derives `FullCellId` from bare wire acceptance - that coarser rule is part of the AP-1 divergence this campaign is removing, not something this row blesses. | `src/AcDream.Runtime/Entities/RuntimeInitialCreateContinuationExecutor.cs` (`ApplyPositionAction`, the CANONICAL CELL SEMANTICS comment) | Matches retail exactly: `HandleReceivedPosition` never writes a resident cell - `enter_world`/`MoveOrTeleport`'s placement commit and `SetPosition` do; also matches the classifier's documented cellless rule. | If a future change passes `refreshPosition: true` here, a wire Position would make a cellless canonical body resident without any placement/collision commit - the classic AP-1-shaped bug this campaign exists to close. | `SmartBox::HandleReceivedPosition` 0x00453FD0; `RuntimeAuthoritativePositionRouteClassifier.ClassifyAcceptedPosition` comment | +| AD-61 | **Filed 2026-08-02 (C3c review round 1).** The #270 settle-timing compression now covers the LOCAL player: `RuntimeLocalPlayerPhysicsPublicationState.SettleFirstEntryGroundContact` runs the shared `SpawnPlacementSettler` exactly once after the dormant activation's final commit (suffix-current authority only), compressing retail's first post-`enter_world` gravity frame — which grants CONTACT/ON_WALKABLE from a real touch — into the placement transaction. The legacy App-era force-seed (`Contact\|OnWalkable\|Active` in `PlayerMovementController.SetPositionCore`) still RUNS during publication-candidate preparation and is then OVERWRITTEN by the faithful activation commit + settle (it was never deleted). Caveat (review minor M2): the settler commits `settle.Position` but discards `settle.CellId` — a settle whose few-cm sweep crosses a cell boundary keeps the placement cell until the next resolve corrects it (inherited #270 semantics; ISSUES entry filed) | `src/AcDream.Runtime/Gameplay/RuntimeLocalPlayerPhysicsPublicationState.cs` (`SettleFirstEntryGroundContact`); `src/AcDream.Core/Physics/SpawnPlacementSettler.cs` (`TrySettle`); overwritten seed `src/AcDream.Runtime/Gameplay/PlayerMovementController.cs` (`SetPositionCore`) | Timing compression only: contact comes exclusively from the sweep's real touch (no caller-bool seeding, no forced transients), an airborne spawn stays genuinely airborne, and the overwritten force-seed leaves no observable residue past the activation commit — the committed state is exactly what retail's first gravity frame produces | A settle crossing a cell boundary reports the stale placement cell for the frames before the next resolve; a future reader trusting `SetPositionCore`'s "treat as grounded" seed comment could reintroduce the Contact-without-plane state the landing family calls unrepresentable | `CPhysicsObj::enter_world` 0x00516170; `SmartBox::HandleCreateObject` 0x00454C80 | --- diff --git a/docs/plans/2026-08-02-placement-cutover.md b/docs/plans/2026-08-02-placement-cutover.md index b992bb71..1884bb81 100644 --- a/docs/plans/2026-08-02-placement-cutover.md +++ b/docs/plans/2026-08-02-placement-cutover.md @@ -162,11 +162,37 @@ same commit) → docs/handoff commit. No workarounds; no fused slices. never-clobber coexistence with the build-at-first-motion production path. The acknowledge discriminator is one shared body (`RuntimeFirstEntryAcknowledgement`) for both conductors. Dormant. - - **C3c — the host flips (production):** both hosts onto the complete - machinery; seal the Controller setter; presentation-only - rebucketing; the connected lifecycle/reconnect + nine-stop gates - (harnesses: `tools/run-connected-world-lifecycle-gate.ps1`, - `tools/run-connected-r6-soak.ps1`). + - **C3c — the host flips (production) — COMPLETE at `529e0e9d` + (2026-08-02, dual Opus reviews: initial FAIL 2+2 MAJOR → R1 fix + round → delta PASS both).** Both hosts register initial Creates + through residence + conductors via the shared + `RuntimeFirstEntryDriveController`; Controller setter sealed; + rebucketing presentation-only strictly while the residence is + ACTIVE (post-residence entities take the full legacy path including + the `prepare_to_enter_world` clock edges); content-less headless + keeps pre-flip direct registration. Five fix slices landed inside + the cutover, each connected-gated: F1 (Runtime ownership seam for + movement stats/server physics — the post-logout retired-controller + crash), F2 (the login activation wedge: admission-prefix gate + factored from the seal, rearm generation identity, auto-entry + requires the published controller), F3 (landblock-prefix 0-sentinel + → explicit absent-id; corner landblocks legal), F4 (diagnosis only: + the nine-stop soak's convergence failure is pre-existing `6b28ff99` + whole-world collision-clone throughput — its fix is the next slice + before C5), F5 (local-player first-entry ground contact via the + shared `SpawnPlacementSettler` at `FinalizeActivation`; the + standing-cast airborne rejections are gone; register AD-61). R1 + additionally armed the login constraint leash at the committed + placement (`HandleReceivedPosition` 0x00453FD0 analog) and + refreshed AD-42. Final gates: complete solution 10,816/0/4 skips; + lifecycle/reconnect gate PASS (`connected-world-gate-20260802- + 175401`). Closeout: + [`2026-08-02-c3c-cutover-closeout.md`](../research/2026-08-02-c3c-cutover-closeout.md). + **Carried to C4/C5:** route-1 far-Create service-window conversion + if either streaming/broadcast radius changes (#276); the + window-departure park narrowing; `NotifyRetirement`-on-active-entry + subscriber invariant; the reachable equip-mid-conductor fail-fast; + settle-CellId discard (#276-adjacent, see ISSUES). - **C4 — remaining routes: 2 (ForcePosition), 3 (portal, with the `RuntimeWorldTransitState` → `RuntimePortalPlacementAuthority` adapter), 4 (remote Create/Position; delete `RemoteTeleportController`/`Placement` diff --git a/docs/research/2026-08-02-c3c-cutover-closeout.md b/docs/research/2026-08-02-c3c-cutover-closeout.md new file mode 100644 index 00000000..efd53d9f --- /dev/null +++ b/docs/research/2026-08-02-c3c-cutover-closeout.md @@ -0,0 +1,104 @@ +# C3c production placement cutover — closeout (2026-08-02) + +Behavior commit: `529e0e9d` (68 files, +5,979/−833, register rows AD-61 + +AD-42 refresh in-commit). Plan: +[`2026-08-02-placement-cutover.md`](../plans/2026-08-02-placement-cutover.md). +Session evidence trail: the campaign scratchpad's `implementer-progress.md` +sections `Continuation 1-4`, `C3c-F1`..`C3c-F5`, `C3c-R1` (not committed; +summarized here). + +## What shipped + +Both production hosts (graphical + headless) register every initial +wire Create through the C0-C3b residence/executor/conductor machinery. +One shared `RuntimeFirstEntryDriveController` pumps the local-player and +remote conductors from the placement-receipt flow (per-frame graphical, +per-tick headless). `MaterializeProjection`/`RebucketLiveEntity` are +presentation-only strictly while the initial-create residence is ACTIVE +(exact-token check; `ExecutorCompleted` is the presentation-binding +receipt); post-residence entities take the full legacy path including +retail's `prepare_to_enter_world` (0x00511FA0) clock rebase. +`RuntimeLocalPlayerMovementState.Controller`'s setter is sealed; every +controller mutation flows through the publication lifecycle. Content-less +headless sessions (validated-legal config) keep the pre-flip direct +registration until C4/C5 revisit. + +## The five fix slices (each connected-gated inside the cutover) + +- **F1** — live movement-stat + server-physics application moved behind + Runtime ownership (`RuntimeMovementStatsApplication`, + `ApplyServerPhysicsState`); the post-logout ingest crash on the + retired controller is eliminated; `RuntimeMovementSkillProjection` + deleted. +- **F2** — the login activation wedge (world never revealed): the + collision-admission prefix gate factored out of the seal (reentrant + commit could yield terminal `RejectedAuthority`), the rearm's + generation identity corrected (parked G vs post-retirement G+1), and + `PlayerModeAutoEntry` now requires the Runtime-published controller + (`IsPlayerControllerReady` was a constant `true` — one early attempt + permanently sealed the reveal). +- **F3** — landblock-prefix `0`-sentinel replaced by explicit absent-id + representation; map-corner landblocks (grid row/col 0, e.g. + `0x0000FFFF`) are legal through admission, park/rearm/retire, + quiescence, and outdoor shadow seeds. +- **F4** — diagnosis only: the nine-stop soak's convergence failure + (pendingPublications=1, farBacklog nonzero, landblock/mesh dimensions) + is **pre-existing `6b28ff99`** (2026-07-31, "make collision activation + starvation-free"): every far publication clones the complete collision + world (median ~19.7k leaves / 3.64 ms), so the queue drains ~10 + landblocks/s and never catches its window. Fix requires an O(changed) + clone (structural sharing or per-landblock atomic unit) — a semantics + change to that slice's asserted one-leaf-per-step invariant; scheduled + as its own slice BEFORE C5 (whose gate matrix includes the soak). +- **F5** — local-player first-entry ground contact: retail seeds contact + from the first gravity frame's transition touch (`enter_world` + 0x00516170 carries no seed; local player and remotes share the + mechanism via `HandleCreateObject` 0x00454C80). The shared + `SpawnPlacementSettler` (moved App→Core) runs at `FinalizeActivation` + exactly once; genuinely airborne spawns stay airborne; the outbound + contact bit chain is asserted end-to-end. The legacy path's + unconditional `Contact|OnWalkable|Active` force-seed (non-retail, no + plane) still runs during candidate preparation and is OVERWRITTEN by + the faithful settle (register AD-61). Fixes the user-observed + standing-cast "You can't do that while in the air!" rejections. + +## Review round R1 (dual Opus: initial FAIL 2+2 MAJOR → delta PASS both) + +Retail MAJORs: the login constraint leash (deleted with the legacy +resolve path; re-armed at the committed placement in +`FinalizeActivation` — `HandleReceivedPosition` 0x00453FD0 arms on every +accepted position) and the post-residence rebucket scope (fixed to +exact-token active-residence). Adversarial MAJORs: content-less headless +(no drive → legacy registration) and the register rows. Nine minors +fixed (owner conversion API with active-residence throw, wire-landblock +guards, drive-pending ledger in `IsConverged`, route attach/detach +latch, celless conversion for far headless remotes, doc-comment truth, +per-incarnation cylinder cache, executor-drain drift model documented + +source-pinned); two tracked (#276, #277 in ISSUES). + +## Final gates + +Runtime 1,003; App 4,039/3 skips; Headless 79; complete solution +**10,816 / 0 failed / 4 skips** (Release, `-m:1`). Connected +lifecycle/reconnect gate **PASS** (`connected-world-gate-20260802-175401`; +graceful exits, world-visible, zero airborne-rejection strings; run +`-174811` failed on user-interference fingerprint — +`activeTeleportCount=1` at the stable checkpoint — and is attributed, +not counted). The soak stays red for the pre-existing F4 attribution. + +## Process lessons (carried to memory) + +1. **Report artifacts over marker logs** — three wrong classifications + this campaign came from reading route/marker logs instead of + `report.json` (the soak "clean route" was Passed=false with 37 + convergence failures). +2. **Log lifetime before absence claims** — a 26-second, 67-line log's + silence about a defect proves nothing (the 122749 misread inverted a + root-cause classification twice). +3. **User observation is the cheapest gate** — the standing-cast + airborne rejections and the black-screen reveal were both + user-spotted minutes before harness detection. +4. **The seal finds the bypasses** — sealing the controller setter + surfaced a runtime-mutation bypass (F1) the compile-break audit could + not see; expect the same class when sealing any long-lived escape + hatch.