Route 6 needs no production change and this commit contains none: C3c (529e0e9d) already flipped both hosts' Create paths onto the residence lease, so a dropped item is byte-for-byte route 1's create classification (RuntimePositionEntityKind.Remote, RuntimeCreateResidenceKind.TopLevel, ClassifyCreate -> SetPosition with InitialCreateFlags = Placement | Slide). Route 6 is a SOURCE of route-1 traffic, not a route of its own. Both drop flavours converge on LiveEntityHydrationController.OnCreate -> RegisterEntityWithInitialResidence — the whole-item drop through ItemInteractionController's DropToWorld (no physics, no position; the server decides), and split-to-world through TryRecoverUnknownPosition's call to the identical entry point. Contract: docs/research/2026-08-04-c4-route-6-contract.md. Retires a FALSE PREMISE from the campaign plan (:97-100), which claimed split-recovery creates "need an effect-replay suppression signal". Verified against the decomp instead of assumed: play_default_script @0x005132B0 / @0x00513300 has exactly three call sites in the entire pseudo-C dump — DefaultScriptPartHook::Execute @0x00526c08, DefaultScriptHook::Execute @0x00526c14, and ACCWeenieObject::DoCollision @0x0058c3b4 — and NONE from set_description or CreateObject. Neither client plays a default script at create, so there is nothing to suppress. acdream's only create-time replay is the F754/F755 queue drain keyed by server GUID, which is retail's own HandleCreateObject @0x00454C80 behaviour. The plan's other two clauses were closed at C0 (TryCommitParent/CommitWithdrawal cancellation symmetry; host-visible cancellation receipts); the list now states what actually remains — route 7's child-cell two-writer split and the headless parent-realize gap. Retail split marking recorded for the record: UIAttemptSplitTo3D @0x0058D850 stores only splitStackSize/splitClassID/splitTime and performs no placement; DeclareValid @0x0058E340's recovery action is SetSelectedObject @0x0058E481 — a SELECTION transfer with a 10-second expiry, not effect suppression and not placement. UIAttemptPutIn3D @0x0058D700 records no marker at all. Seven tests over the now-flipped path (whole item, split stack, new-GUID recovery, second drop, unavailable destination, newer Position after the pending identity is consumed, plus the #314 repro), each sabotage-verified: the production path was broken on purpose, the test was confirmed to fail, and the sabotage reverted. R6-c is now settled by assertion rather than argument — BuildSpawn's wholesale clone of Children/Movement/AnimationFrame/ SetupTableId is measured, not reasoned about. FOUND WHILE TESTING — #314, filed not fixed (this route is zero-production by contract). BuildSpawn resets top-level MovementSequence/ServerControlSequence to 0 but its Timestamps `with` block overrides only Position/Teleport/ ForcePosition/Instance, leaving Physics.Timestamps.Movement and .ServerControlledMove at the SOURCE item's values. HasConsistentCreateIdentityAndParent requires the two projections to agree, so a split whose source carries nonzero Movement timestamps — plausible for any item dropped once, picked up, and split again — fails the predicate and throws instead of completing the canonical transaction. Verified in source, not taken on report. Note this is a crash in the exact mechanism the scoping cited as EVIDENCE that drops already converge: code reading said the path converges, driving it said it throws. Fixed in the immediately following commit. Also filed: #313 (DeclareValid's SetSelectedObject port is missing and the container-split flavour records no marker — selection UX, deliberately not implemented inside a placement closure) and #315 (route 4b-3's per-packet runTeleportHook Func<bool> closure at three RunRemoteArmTail call sites; the network packet path, not Slice I's per-frame resolve path — filed now because route 5 adds a fourth site). AP-124 stays open and registered. Test lines are 410 against a 150-250 guidance, accepted: the excess is a real ItemInteractionController harness plus the #314 repro, which is what found the defect. A mock that proved nothing would have been shorter and worthless. Complete Release suite MEASURED at 11,020 passed / 4 skipped / 0 failed (baseline 11,013/4/0 at6dc7ba51; +7 new). Neither known flake fired. Connected gate (user-run) still owed: drop a whole item, split a stack to the ground, drop a second within ~1 m, repeat indoors and after a portal recall, then walk two landblocks away and back. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
124 lines
6.7 KiB
Markdown
124 lines
6.7 KiB
Markdown
# C4 route 6 — drops and split-recovery: pinned contract (2026-08-04)
|
|
|
|
Scoped in
|
|
[`2026-08-04-c4-routes-6-7-scoping.md`](2026-08-04-c4-routes-6-7-scoping.md)
|
|
§6. Pinned after route 4b-3 landed (`6dc7ba51`, gate passed `21cd6e9b`).
|
|
|
|
**Route 6 requires ZERO production lines.** It is a closure, not a slice: C3c
|
|
(`529e0e9d`) already flipped both hosts' Create paths onto the residence lease,
|
|
and a dropped item is byte-for-byte route 1's create classification. The
|
|
deliverable is evidence + coverage tests + one planning-record correction.
|
|
|
|
**If implementing this contract requires a production change, STOP AND REPORT.**
|
|
A production diff here means the scoping's central finding is wrong, and that
|
|
is a finding worth more than the slice.
|
|
|
|
## Why there is nothing to build
|
|
|
|
A dropped item is an ordinary non-local CreateObject:
|
|
`RuntimePositionEntityKind.Remote`, `RuntimeCreateResidenceKind.TopLevel`,
|
|
`ClassifyCreate` → disposition `SetPosition` with
|
|
`InitialCreateFlags = Placement | Slide`. Route 6 is a *source* of route-1
|
|
traffic, not a route of its own.
|
|
|
|
Both drop flavours already converge on the canonical transaction:
|
|
|
|
- **Whole-item drop** — `ItemInteractionController.ExecutePlacementActions`'s
|
|
`DropToWorld` case sends the drop with no physics and no position; the
|
|
server's CreateObject returns through
|
|
`LiveEntityHydrationController.OnCreate` → `RegisterLiveEntity` →
|
|
`RegisterEntityWithInitialResidence`.
|
|
- **Split-to-world** — `InventoryWorldDropProjectionController`'s
|
|
`TryRecoverUnknownPosition` calls **the identical `_hydration.OnCreate`
|
|
entry point**. Same residence lease, same conductor, same placement.
|
|
|
|
So the campaign handoff's route-6 requirement ("`TryRecoverUnknownPosition`
|
|
may create the logical object, but it must enter the same canonical
|
|
create-placement transaction") is already met, by C3c, with zero route-6 code.
|
|
|
|
## The false premise this closure exists to retire
|
|
|
|
`docs/plans/2026-08-02-placement-cutover.md:98-100` states route-6
|
|
split-recovery creates "need an effect-replay suppression signal". **That is
|
|
unsubstantiated and must be corrected in this landing.**
|
|
|
|
acdream's only create-time effect replay is the F754/F755 queue drain keyed by
|
|
server GUID (`EntityEffectController.ReplayPendingForLiveEntity`), written only
|
|
by inbound `PlayPhysicsScript`/`PlayPhysicsScriptType` for that exact GUID. A
|
|
fresh split GUID has nothing queued unless ACE actually sent an effect for it,
|
|
and draining it then is retail's own behaviour
|
|
(`SmartBox::HandlePlayScriptID` @0x00452020 / `HandlePlayScriptType`
|
|
@0x00452070 queue while absent; `HandleCreateObject` @0x00454C80 drains).
|
|
|
|
The one plausible mechanism — a cloned `DefaultScriptType` surviving
|
|
`BuildSpawn` — **does not fire at create in either client.** acdream's
|
|
`PlayDefault` has exactly two callers, both animation hooks
|
|
(`DefaultScriptHook`, `DefaultScriptPartHook`). Retail matches:
|
|
`CPhysicsObj::play_default_script` @0x005132B0 / @0x00513300 is reached only
|
|
from `ACCWeenieObject::DoCollision` @0x0058C3A0 (call @0x0058C3B4) and the
|
|
animation-hook dispatcher (@0x00526C08, @0x00526C14). **Neither client plays a
|
|
default script from `set_description` or CreateObject.**
|
|
|
|
**Verify both retail claims yourself before relying on them.**
|
|
|
|
## Retail truth for split-recovery marking
|
|
|
|
`ACCWeenieObject::UIAttemptSplitTo3D` @0x0058D850 records exactly three fields:
|
|
`splitStackSize` @0x0058D8A2, `splitClassID` @0x0058D8A8, `splitTime`
|
|
@0x0058D8AE. `UIAttemptSplitToContainer` @0x0058D7D0 records the identical
|
|
three. The consumer is `ACCWeenieObject::DeclareValid` @0x0058E340, whose
|
|
recovery action is `SetSelectedObject(this->id, 0)` @0x0058E481 — **a SELECTION
|
|
transfer, with a 10-second expiry @0x0058E49F-@0x0058E4B2. Not effect
|
|
suppression, and nothing placement-related.**
|
|
|
|
`ACCWeenieObject::UIAttemptPutIn3D` @0x0058D700 (whole-item drop) records no
|
|
marker at all and performs no placement.
|
|
|
|
## Deliverables
|
|
|
|
1. **Coverage tests, 150-250 lines**, against the now-flipped path — the list
|
|
the campaign handoff names: whole item, split stack, new GUID, second drop
|
|
position, unavailable destination, newer Position arriving while waiting.
|
|
Note "attached child becoming a world root" from that list is **NOT route
|
|
6's** — it is a cell-less Position on an existing entity, owned by 4b-3.
|
|
2. **R6-c settled by assertion, not by argument.** `BuildSpawn` clones
|
|
`Children` / `Movement` / `AnimationFrame` / `SetupTableId` wholesale from
|
|
the source. Expected inert for a stackable inventory item but never
|
|
measured. Assert it in the tests rather than reasoning about it.
|
|
3. **The plan correction** at `docs/plans/2026-08-02-placement-cutover.md:98-100`.
|
|
All three clauses are stale: the effect-replay premise is unsubstantiated
|
|
(above), and route 7's `TryCommitParent`/`CommitWithdrawal`
|
|
cancellation-symmetry and host-visible cancellation receipts were BOTH
|
|
closed at C0. Replace with what actually remains: route 7's child-cell
|
|
two-writer split and the headless parent-realize gap.
|
|
4. **R6-a filed as an issue, NOT implemented here.** `DeclareValid`'s
|
|
`SetSelectedObject` is not ported and the container-split flavour has no
|
|
marker. That is selection UX, not placement; mixing it into a placement
|
|
closure makes the landing un-reviewable. Add to `docs/ISSUES.md`.
|
|
|
|
## What must remain true
|
|
|
|
- **Zero production lines.** No `src/**` change. See the stop condition above.
|
|
- **Tests must fail against broken behaviour.** No source-text pins, no
|
|
tautologies. The 4b-3 lesson (round-2 finding B1): a test asserting only what
|
|
must NOT happen cannot detect a deleted write — assert the positive half too.
|
|
- AP-124 (the WCID/count approximation in the recovery match) stays open and
|
|
registered; retiring it needs ACE to send CreateObject to the initiator, not
|
|
a client change.
|
|
|
|
## Gates
|
|
|
|
- Complete Release suite. **Baseline 11,013 passed / 4 skipped / 0 failed** at
|
|
`6dc7ba51` — measure and record; do not inherit. Known flakes, do not chase
|
|
and do not conflate: #302 (`PortalProjectionTests`, GC-allocation) and #308
|
|
(`NakEmissionTests.LossSoak_…`, wall-clock, full-suite load only).
|
|
- **Connected gate (user-run), cheap and directly visible:** drop a whole item
|
|
on open ground — it must land at your feet, resting, immediately pickable.
|
|
Split a partial stack to the ground — correct quantity on the pile, remainder
|
|
in inventory. Drop a second item within ~1 m — both remain visible and
|
|
separately pickable. Repeat once indoors and once after a portal recall. Walk
|
|
two landblocks away and back — both piles still there, still pickable.
|
|
**Regressions:** item at world origin or your *previous* position (stale
|
|
pose); invisible but blocking (#184 class); sunk into or floating above the
|
|
floor; not pickable; the split pile never appears (recovery window failed);
|
|
the second drop swallowed by the first.
|