Commit graph

16 commits

Author SHA1 Message Date
Erik
7b60e71b85 fix(headless,runtime): OP7 review fixes + docs: OP3 re-review REOPEN (narrow)
TWO work products share this commit (a staged-index collision between the
coordinator's docs commit and the OP7 fixer's staged files — content
verified complete and coherent; only this message was wrong before the
amend):

1. OP7 review fixes (all nine findings from
   docs/research/2026-08-11-op7-review.md):
   - M1: HeadlessSessionDescriptor is a record; WithAccount uses 'with' non-destructive record copy,
     so a future property cannot be silently dropped; direct-CLI
     regression test proves CharacterOptions survives --user/--password.
   - M2 root fix: LiveSessionEventRouter skips BOTH Replace and the
     options notification on a trailer-truncated PlayerDescription — a
     truncated re-seed can no longer install zeroed words under an armed
     latch for OP7's automation to flush into 0x01A1.
   - SF1: schema keys validate as ordinal strings against the allowed
     names (numeric / comma-combined aliases rejected). SF2: both-true
     fellowship exclusion rejected at load, naming both keys. SF3: the
     onLoginCompleteSent observer moved after transit.EndTeleport().
     SF4: production-hook coverage for all three LoginComplete sites.
     SF5: test-script OP7 wire expectation corrected (batched ids ride
     only the 0x01A1).

2. docs/research/2026-08-11-op3-rereview.md — OP3 re-review verdict
   REOPEN (narrow): M1 byte-decode independently re-verified (6a 07 at
   all six sites); residuals R1 (gate script promises a timestamp prefix
   acdream doesn't render), R2 (null-controller player-mode still
   refuses), R3 (dormancy pin lacks stimulus) — coordinator fixes follow.

Full Release suite at this tree: 12,956 passed / 4 skipped / 0 failed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-11 03:22:54 +02:00
Erik
09cb548a32 feat(headless): Campaign OP slice OP7 — declared characterOptions with seed-diff sends
Adds an optional, strict `characterOptions` block to the headless bot config
(D8): keys are exactly the lane-B tier-1 (22) + tier-2 (4) bot-declarable
CharacterOptionId enum-member spellings; an unknown/out-of-tier name fails
config load naming the offending key, before it can ever reach the wire.

HeadlessCharacterOptionsSeeder diffs declared-vs-actual once both of ACE's
real preconditions are known true — GameActionLoginComplete sent (the
FirstEnterWorldDone gate SetCharacterOptions 0x01A1 needs) and a real
PlayerDescription has seeded RuntimeCharacterOptionsState
(HasServerSeed) — learned from whichever of two hooks lands second. Every
differing id routes through OP1's shared IRuntimeCharacterCommands seam:
auto-save ids send SetSingleOption (0x0005) immediately; batched ids also
call SetSingleOption (which only dirties the module) followed by exactly
one SaveOptions flush after the whole declared set has been walked.
Idempotent on reconnect by construction — no dedupe latch, the diff simply
finds nothing once the server agrees.

RuntimeLiveEntitySessionController gains a passive onLoginCompleteSent
observation hook (additive only, never changes when/whether it sends) so
the headless host can learn ACE's gate opened from any of its own two
internal send sites; the third site (direct first-entry completion) is
already owned by HeadlessSessionHost itself. All wiring is synchronous
delegate calls on Runtime's one dedicated update thread — no new
async/Task continuation, honoring #368.

Tests: schema (valid parse, unknown/tier-3 name rejected naming the key,
non-bool rejected, empty/absent no-op), the diff engine against a fake
IRuntimeCharacterCommands (nothing-to-send, auto-save-only, batched-with-
flush, mixed ordering, reconnect idempotence), and two wiring integration
tests — one dispatching a real PlayerDescription game event end-to-end to
a captured wire action, one proving the send lands on the same dedicated
thread every Tick runs on. Full solution suite: 12,935 passed / 4 skipped
/ 0 failed (+17 over baseline 12,918/4/0).

No register row: the characterOptions bot-config surface is acdream-
native tooling over retail's own wire mechanisms (both already ported by
OP1), not a retail UI port with a divergence to record.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-11 02:45:08 +02:00
Erik
8bc458fb88 feat(audio): Campaign A slice A3 — the server sound channel (0xF750)
acdream never parsed retail's Sound event, so every server-driven cue was
silent: melee hits and wounds, wield/unwield, pickup/drop, lockpicking,
lifestone bind, spell resist, trap triggers, item mana depletion.

SoundEvent parses the 16-byte message (guid, SoundType, f32 volume) whose
layout three oracles agree on: retail CM_Physics::DispatchSB_SoundEvent
@0x006AC760 reading buf+4/+8/+0xC, ACE's GameMessageSound at declared
length 16, and holtburger's PlaySoundData.

Playback reuses EntityEffectController's existing per-guid queue rather
than adding a second one, because retail routes sounds through the SAME
CObjectMaint blob queue as F754/F755: an event for a guid the client does
not know yet is parked and drained by HandleCreateObject, so a creature
that spawns and immediately grunts still grunts. Dropping it — the
obvious alternative — would silently lose the cue. Sound joins Direct and
Typed as a third PendingEffect kind so one readiness edge releases the
whole mixed stream in order.

AudioHookSink.PlayServerSound reproduces two decoded asymmetries with the
animation-hook path: the sound plays at the WIRE volume and the
SoundTable entry's volume is ignored (the hook path does the opposite),
while the entry's probability still gates it and its priority still
drives eviction. An object with no SoundTable plays nothing, matching
CPhysicsObj::play_sound @0x0050F460's early return.

The no-window host parses and discards, exactly as it does for F754/F755
— sound is presentation.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-08 22:07:23 +02:00
Erik
3aab05b0cc fix(streaming): derive the portal reveal window from the live streaming radii (#280)
The user watched far terrain visibly assemble after portal space exits.
The reveal gate was NOT missing a hold — Slice E's hold mechanism is
correct and already in place. The hold was measuring the wrong domain:
it opened at a hardcoded 3x3 landblock neighbourhood (~192 m) while the
visible world extends to the fog end (~2,189 m at the shipped High
preset, inside a 2,304 m Far window). An 11.4:1 ratio.

Retail's equivalent ratio is 1:1 BY CONSTRUCTION. `LScape` owns one
`mid_width x mid_width` array of `CLandBlock*` (`LScape::SetMidRadius`
@0x00504C00, `LScape::update_block` @0x005063A0), `mid_radius` is
assigned directly from the user's `Render.LandscapeDrawDistance`
preference (`SmartBox::SetRegion` @0x004531F0; values
`Render_LandscapeDrawDistance_Values` @0x007CA988 = {3,5,8,11,15,25},
default 8 — both byte-verified against the PDB-paired 2013 binary), and
that same square is simultaneously the prefetched set
(`LScape::PreFetchCells` @0x00505660), the drawn set (`block_draw_list`
over the same array), and the set the simulation blocks on
(`CellManager::blocking_for_cells`). There is no retail configuration in
which the client streams farther than it gates, because there is only
one number.

So the fix derives rather than duplicates. Four coupled parts, which is
why this is one commit and not four — D1 without D2 hangs the client and
D2 without D1 is dead code:

D1 `WorldRevealReadinessBarrier` takes a live `Func<StreamingRevealWindow>`
and stops being static: outdoor requires `FarRadius`, indoor still 0
(retail's `CEnvCell::PreFetchCells` @0x0052D1E0 arm). Read per
evaluation, never captured — the radii are runtime mutable through
Settings, and retail's answer to a mid-hold radius change is to reset,
re-radius, and re-arm the blocking prefetch at the NEW value
(`SmartBox::set_mid_radius` @0x00453180). `OutdoorNeighborhoodRadius`
is deleted; there is no constant left to drift.

D2 `StreamingController.IsRenderNeighborhoodResident` becomes tiered,
because acdream's loaded landscape is: inside `NearRadius`,
`IsNearTier && IsRenderReady`; out to `FarRadius`, `IsRenderReady` only.
Without this the fix cannot work at all — nothing outside the Near ring
is ever promoted, so any radius above `NearRadius` was unsatisfiable and
would have held the reveal forever. Proof obligation P1 (a Far-tier
landblock genuinely satisfies `IsRenderReady`) is now a test driven
through the real `PublicationKind.Far` pipeline against a real
`LandblockSpawnAdapter`, not an inference.

D7 `RuntimeWorldTransitState.AcknowledgeDestinationReadiness` re-derived
`indoor ? 0 : 1` and failed `invalid-readiness-shape` on any other
value, so changing the radius alone would have looked like "the fix
hangs the client". It is now a SHAPE invariant (`indoor => 0`,
`outdoor => >= 1`). Runtime does not own the graphical host's streaming
configuration and must not learn it; plumbing App radii into Runtime to
preserve the strict equality is exactly the assert-a-mechanism-that-does-
not-exist failure C5b was built to stop. Both non-graphical producers
keep emitting their centre-ring token and stay legal, annotated in place.

D6 `PhysicsEngine.IsNeighborhoodTerrainResident` rebuilt a full-map
`HashSet` on every call, every frame of every hold. At radius 1 that was
invisible; at radius 12 (625 ring members) it violates Slice I1's
0 B/resolve standard. Now an engine-owned scratch set, cleared in place;
measured at 0 bytes over 1,000 warmed radius-12 queries.

Also: the destination reservation opens at exactly the gate's radius and
reopens on the same generation when the radius changes mid-hold (retail
has one square for both, and no concept of prioritising an inner ring
differently). Composite warmup deliberately stays `NearRadius`-scoped —
the composite domain is entity-scoped and Far builds carry no entities,
so widening it would walk the outer window to warm nothing.
`ACDREAM_PROBE_REVEAL_RADIUS` is a measurement probe in a diagnostic
owner (CLAUDE.md rule 5) so the connected route can be run A/B on one
binary; it is NOT a user-facing prefetch knob, since a low setting would
reintroduce the decoupling this slice exists to close.

Register: AD-2 amended with the derived window, the two-tier split, and
the four new retail anchors. AP-149 FILED for the residual this does not
close — the outer ring accepts terrain-only publication where retail
requires LandBlockInfo and every building EnvCell, so a distant building
can still pop in at Far-ring distances. Do not let a later closeout
claim parity.

Docs: `ACDREAM_STREAM_RADIUS`'s CLAUDE.md description was wrong on every
clause (the default is unset, not 2; it forces `NearRadius`; it is
silently discarded by any Settings save) — corrected, since that is the
file every session reads. `reference_two_tier_streaming.md` corrected in
four ways, including "Far tier = terrain only": Far also publishes
terrain COLLISION, which is precisely what makes this fix viable.
#280's issue text had the right conclusion from a wrong premise (it
names a view-distance setting acdream does not have) — corrected, and
the missing Viewing Distance option filed separately as #326, with #327
(DDD progress readout) and #328 (hardcoded 5000 f far plane vs retail's
byte-verified 4000) filed alongside.

Expect LONGER holds and the "In Portal Space - Please Wait..." cue on
recalls MORE often. That is convergence toward retail, not away from it:
retail emits the byte-identical string for the whole duration of a
blocked prefetch and polls at 5 s intervals. The failure condition is
non-convergence, not duration.

Gates: Release build 0 errors. Complete suite 11,178 passed / 4 skipped
/ 0 failed, against a re-measured 11,142 / 4 / 0 baseline at 9ee9c1a1 —
+36, reconciled exactly as 36 new tests (App +23, Runtime +10, Core +3),
zero deleted, zero newly skipped. Nine discriminating tests
sabotage-verified in both directions. The connected/visual gate is
batched into C5's matrix; its recipe, its three positive artifacts, and
its required recall leg are written into the campaign plan.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-05 23:54:59 +02:00
Erik
9ee9c1a1a6 fix(runtime): close the C5b re-review findings — Gate A narrowing filed, no-window payload gate, bisect hazard recorded
Both C5b re-reviews returned PASS on 02578441..ff100cf3. This lands the
bookkeeping corrections they left, the one gate asymmetry both found
independently, and one wrong retail fact neither of them caught.

1. AP-148 / #325 — Gate A's teleport test, wrong on primary source twice.

The C5b contract stated retail's Gate A teleport term as "TELEPORT_TS
equal" (and, in the trace block, as "must NOT be newer") and blessed
acdream's `teleport == _timestamps[Teleport]` as retail-exact. Disassembly
of the PDB-paired binary at SmartBox::HandleReceivedPosition
0x0045402B-0x00454054 says otherwise: the shortcut is taken iff the wire
stamp is equal OR newer (wrap-safe) — `sbb eax,eax / neg eax` materialises
the carry of the compare and the branch skips Gate A on CF, i.e. only when
the wire stamp is strictly OLDER. It is CPhysicsObj::newer_event
@0x00451B10's identical idiom with the operands swapped. Binary Ninja drops
the flag test and renders it `if (-((eax_7 - eax_7)) == 0)`, always true —
which is why two rounds of reading pseudo-C recorded it backwards.

So acdream's ForcePosition disposition is a strict SUBSET of retail's Gate
A set, and a local ForcePosition carrying a newer teleport stamp is
misrouted into a full Apply: wire heading instead of preserved heading,
unparent, possible placement frame, zeroed velocity, TELEPORT_TS advanced,
and OfferTeleportDestination called for a packet retail never starts
presentation for.

PhysicsTimestampGate.cs is NOT changed. The predicate exists twice (also
ValidAcceptedAuthority's PreviousTeleport == AcceptedTeleport), and the fix
has to decide TELEPORT_TS's disposition on a Gate A path that has never
seen a stale-but-equal pair. #325 records all of it and says explicitly
that it is not a one-line comparison swap. C5b made this marginally
better, not worse: clearParent was unconditionally true before C5b and is
unchanged; installPlacementFrame moved toward retail's HasAnims gate.

2. Retail F2 / architecture L-A — the no-window route had no pre-merge
payload validation. Root fix, not a documented asymmetry.

The graphical route validates before the merge (OnPosition's payloadIsValid
-> LiveEntityInboundAuthorityGate's !payloadIsValid return); despite its
name CanAcceptPositionPayload is not projectile-scoped. The no-window route
had no equivalent, and since D1 fed an unvalidated LandblockId into
CommitWireCellRebucket — where 0 is the withdrawal shape, silently
de-residencing the entity in the field every bot reads as CellId.

RuntimeLiveEntitySessionController.OnPositionUpdated now applies the same
rule at the same point, reusing
RuntimeAuthoritativePositionRouteClassifier.IsValidCreateWirePosition plus
the finite-velocity term — the exact pair TryApplyPosition already applies
on its initial-residence branch. Chosen over documenting it because the fix
is five lines and leaving it would have left two written claims falsified
by the code. It is a behaviour change: headless now drops packets it
merged. Against ACE the set is empty, and the graphical host has carried
this gate since it was written; the argument is recorded in the contract's
§15.2 rather than gated.

Two test fixtures carried cell ids retail's own inbound_valid_cellid
rejects (low words 0x41 and 0x51, above the 0x40 landcell ceiling). Their
constants were corrected; their assertions were not.

New test sabotage-verified in both directions: gate removed -> red at the
withdrawal-shape assertion; gate moved to guard only the cell commit ->
red at the pose assertion, which is what makes it a before-the-MERGE test
rather than a before-the-commit test.

3. Register and doc corrections.

- AD-64: "deliberately absent" was presented as the complete difference
  list and was not. Adds (a) the residence gate is weaker than the merge's
  own — both hosts' commits use TryGetCurrent while TryApplyPosition's FIFO
  branch uses TryGetTransaction, so the wire cell can commit ahead of the
  continuation that will replay it; (b) the two missile gates are two
  different expressions that agree today; (c) the payload gate, now
  present. Risk column records that (a) and (b) have no discriminating test
  on either side.
- AP-147: amended for D1 — pre-D1 the no-window host published [Updated]
  alone and lost the Rebucketed, so a headless event log is now a real
  instance of the "consumer that snapshots a delta" the row warns about.
- AD-60: "Matches retail exactly" scoped to the withhold, since the row's
  body documents two channels that do not.
- CommitWireCellRebucket: notes the unreachable ThrowIfNull /
  EnsureNotDisposed precedence inversion.
- TryCommitAcceptedWireCell: the discarded commit bool is explained rather
  than left bare — false means IsCurrent went stale, unreachable three
  statements after a synchronous TryGetActive.

4. Bisect hazard recorded in the C4 closeout handoff (the doc CLAUDE.md
sends readers to before any C5 work) and in the contract's §15.3: commits
735f0a72..23aa62f2 contain a live headless defect — every remote's
FullCellId frozen for the session — introduced by 735f0a72 and fixed only
at ff100cf3. Nothing throws and no test in the range fails.

Gates: Release build 0 errors/0 warnings. Complete suite 11,142 passed /
4 skipped / 0 failed against the 11,141 / 4 / 0 baseline — net +1, exactly
the one new test. No flake appeared (#302, #308, #321 all green).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-05 23:10:24 +02:00
Erik
ff100cf33f fix(runtime): give the no-window host a post-merge canonical cell commit (D1, AD-60/AD-64, AP-146/#320)
C5b (735f0a72) made the steady-state accepted-Position merge stop writing
residency. That is retail-correct — HandleReceivedPosition @0x00453FD0 reads
the wire objcell_id into a local and never assigns the object's cell — and it
stays. What C5b did not account for is that its replacement writers both live
in AcDream.App: the OnPosition prologue rebucket (AD-60's W2) and the
post-routing wire-cell adopt (W3, AP-135).

The two hosts run parallel, non-shared inbound routes. LiveEntitySessionController
-> LiveEntityNetworkUpdateController.OnPosition is graphical-only;
RuntimeLiveEntitySessionController.OnPositionUpdated is the no-window route and
is constructed only at HeadlessSessionHost.cs:682. So AcDream.Headless had NO
post-merge cell writer at all. Every remote's FullCellId was written at
create/placement and then frozen for the session — and RuntimeEntityObjectViews
.Snapshot projects exactly that field as RuntimeEntitySnapshot.CellId, i.e. every
bot's entire world view. The local player lost one of AP-146's three refresh
edges, which matters beyond cosmetics: RuntimeSetPositionState
.IsAffectedCollisionResident reads FullCellId to pick which bodies a landblock
retirement parks, so a bot running A->B without teleporting would have retired A
while parking a body physically in B.

The fix, in three parts:

1. RuntimeEntityObjectLifetime.CommitWireCellRebucket — a new Runtime owner for
   the committed VALUE, extracted verbatim from LiveEntityRuntime
   .RebucketLiveEntity. This is also the root-cause fix for the layering
   inversion the review found: AD-60 was documenting its own correctness by
   naming an App class the Runtime assembly cannot reference. Behaviour on the
   graphical side is unchanged — record.FullCellId is a proxy for
   record.Canonical.FullCellId, which is the record the callee reads, and the
   commit is still CommitRebucket. Verified load-bearing for BOTH hosts:
   sabotaging the preserve branch reddens the graphical
   LiveEntityRuntimeTests.CanonicalOnlyRebucket_DoesNotOverwriteAuthoritativeFullCell
   as well as the new headless assertion.

2. RuntimeLiveEntitySessionController.TryCommitAcceptedWireCell — the no-window
   W2, under the same reachability rules the graphical route applies: Rejected
   writes nothing (the shape the App authority gate produces by returning false);
   a bound-projectile packet writes nothing (routed by the graphical host through
   the canonical projectile placement owner, which returns before W2); an active
   initial-create residence writes nothing (RebucketLiveEntity's own early
   return — while the lease is live the SetPosition conductor is the sole cell
   authority); a local ForcePosition writes only when the accepted-Position drive
   declined it (NotApplicable), because a handled force is
   placement-receipt-authoritative. W2/W3 themselves are untouched.

3. On the committed value (the landblock-vs-cell trap). RebucketLiveEntity's
   preserve branch fires on a LANDBLOCK-shaped id — low 16 bits 0xFFFF — and
   exists for LocalPlayerProjectionController.Project, the per-frame local
   movement caller that emits exactly that shape. An inbound wire objcell_id is
   never landblock-shaped, so on the accepted-Position route the branch is not
   taken and the exact wire cell is committed. That is what W2 commits today and
   what this now commits; the no-window host has no per-frame caller at all.

Ordering is matched, not improved on: the force drive submits its placement
before the commit, so its first submit still reads the pre-commit FullCellId —
AP-138's amended route-2 CurrentCellId measurement.

Bookkeeping in this commit:
- AD-60 corrected. Its surviving-channel enumeration presented "the local force
  path, the missile arm" as exhaustive; the entire no-window host belonged in it.
  23aa62f2's W2/W3-redundancy measurement is preserved verbatim.
- AP-146 and #320 amended the same way — their three-edge list was written from
  the graphical host and silently assumed both hosts shared it. The no-window
  host had two of three; it now has all three.
- AD-64 filed: the reachability decision is now expressed once per host. The
  value is single-sourced; the gate set is not.
- #324 filed: unifying the two session controllers is the genuinely correct fix
  and is campaign-sized (presentation recovery, hydration, the equipped-child
  renderer, and the remote/projectile routing arms only one host has). Not
  attempted here, per the fix brief.

Gates. Release build 0 errors. Complete suite 11,141 passed / 4 skipped /
0 failed, against the 11,134 / 4 / 0 baseline at 23aa62f2 — net +7, exactly the
7 tests added. Eight sabotages verified, each red on at least one discriminating
test and green when reverted: remote commit removed (2 Runtime + the end-to-end
Headless test); local ordinary commit removed; local NotApplicable-force commit
removed; force commit made unconditional; residence gate removed; missile gate
removed; Rejected gate removed; preserve branch broken (red on both hosts).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-05 22:36:31 +02:00
Erik
392c1e22c1 fix(physics): bind a parented child to the parent's live incarnation (#319)
A player-parented child never received a canonical cell. Its FullCellId stayed
0 for its whole attached lifetime, so it could not follow the player across a
boundary. Scope was wider than the local player: every REMOTE player's
equipment too.

ROOT CAUSE. EquippedChildRenderController hardcoded ParentInstanceSequence: 0
for a parented CreateObject. Correct for creatures and statics, which really
are sequence 0; wrong for players, whose ObjectInstance is Character.TotalLogins
(ACE Player_Networking.cs:37). The relation filed under (playerGuid, 0) while
the record carried TotalLogins, so both route-7 write sites — D1's attach
re-cell and D2's propagation lookup — keyed on an incarnation that never
matched. TryCommitParent did not validate the sequence, so the attach
succeeded and printed normally. Silent.

A ROUTE 7 REGRESSION (cd3129e9) that un-masked a latent bug: the TickChild call
route 7 deleted was keyed on the child guid alone and was structurally immune
to a wrong parent key.

THE FIX IS TO STOP TREATING PLAYERS DIFFERENTLY, not to special-case them.
Retail's attach path is guid-only end to end — PhysicsDesc::get_parent_id
@0x00558a18 -> CObjectMaint::GetObjectA @0x00558a2d -> set_parent @0x00558a3e,
with SetChildren @0x00509370 hash-walking by guid — and neither set_parent
overload (@0x00515A90, @0x00515B50) nor enter_cell @0x00510ED0 contains any
player test or instance-sequence read. Our player/non-player split was purely
an artifact of keying relations by (guid, incarnation) against a wire message
that carries no parent incarnation. Late-binding to whoever currently holds the
guid is retail's own semantics. Fixed at BOTH producers: OnSpawn and
OnCreateParentAccepted, the second carrying the byte-identical defect and not
named in the contract's scope line.

THE INVARIANT IS EQUALITY, NOT FRESHNESS. The contract rejected both framings I
offered: every one of the 45 FullCellId liveness predicates excludes a
committed child on a NON-cell clause first, so the child inherits only the
parent record's existing staleness, which is already present today with no
symptom. The key fix alone restores child-equals-parent for every parent class.

TWO SITES GATED, inert only because the cell was zero and would have woken
wrongly: the hydration candidate loop (a nonzero-cell child would take the
legacy RebucketLiveEntity -> CommitRebucket, a second canonical writer — route
7's exact defect class) and RestoreShadow (would install a broadphase row for
the weapon, the #184 shape, contradicting route 7's P4). Retail anchor:
update_object's parent != 0 early-out @0x00515D40 — children are never
independently re-placed.

THREE MAJORS WERE FIXED BY DELETION. The first pass added a deferral queue for
an unaddressable parent, carrying a missing child-freshness gate (A2), a
sentinel-0 collision with the generation filters (A3), and unbounded
accumulation (A5). Both reviewers then proved the deferred branch unreachable
for BOTH producers — RegisterEntityCore defers the entire CreateObject one
layer above, reading the same ?? chain, and CreateParentUpdate is produced only
inside AcceptCreateCore, after that gate passes. The machinery was deleted
rather than repaired, and the diff SHRANK to 76 added / 13 removed from 91/24
while gaining the A1 fix. Retail confirmed the deletion does not diverge:
acdream's real port of retail's per-guid replay (QueueBlobForObject) is a
different, untouched layer, and the deleted queue was a third redundant one
downstream of it.

THE GUARD MUST NOT TEAR WHAT IT PROTECTS. The first pass threw
InvalidOperationException AFTER the canonical half had committed, so the one
time it fired it left the child parented with no committed relation and a
staged one blocking Resolve — a torn transaction, the exact outcome the
contract pinned against. Now a pure CanCommitIncarnation precondition checked
BEFORE the commit at both sites, with a logged refusal instead of a throw.
Route 3's N3 principle (do not make a transient fatal on a host that must
survive 30 sessions x 2 hours) reinforces it, but the tearing argument stands
alone.

TEST QUALITY, the recurring lesson in its most refined form. The A1 test
initially passed sabotage FOR THE WRONG REASON: a mismatched ChildPositionSequence
meant TryCommitParent's own gate refused in either ordering, so the three
assertions carrying A1's meaning passed both ways and only an incidental
staging assertion failed. It failed on stranding, not tearing. Corrected, the
sabotage now names line 925 — Assert.Null(snapshot.ParentGuid), with the
parent's guid in it — proving the canonical mutation happened before the catch.
"Fails under sabotage" is necessary, not sufficient; WHICH assertion fails is
the real question.

The dual parent-class matrix (player 0x5… incarnation > 1 vs creature 0x8…
incarnation 0, identical outcomes, sabotage-verified in both directions) is the
structural fix for how this survived a full dual review and two connected
sessions: every prior test and both captured gate logs used sequence-0 parents.

Register: AP-142 clause (f); AP-132 amended to distinguish the two producers;
new row AP-146 for the local player's coarse canonical cell (retail writes it
per tick at SetPositionInternal @0x00515330 — which, per the retail review, ALSO
walks this->children writing each child's objcell_id @0x005153AE-@0x005153D8,
so retail's per-tick child propagation lives in the same function). That
divergence had no row at all, a standing rule-1 violation now corrected.
Follow-up #320 filed for making the player's cell track ordinary movement —
deliberately excluded here: it touches the landblock-preserve contract, the
Rebucketed cadence, route-2/4b-3 classification inputs AP-136/AP-138 spent four
review rounds pinning, and the portal-space frozen-source-cell race.

Two dual review rounds; 6 architecture MAJORs and 2 retail MAJORs closed.
Diagnostic refusals are latched per child guid and the latch clears on
Clear()/RemoveChild, so a recycled guid's next incarnation still logs rather
than being silently suppressed.

Complete Release suite MEASURED at 11,112 passed / 4 skipped / 0 failed
(baseline 11,090 at 52175aa1, +22). Neither known flake fired.

STILL OWED: the connected gate, with the CORRECTED positive criterion — assert
the equipped child's FullCellId EQUALS the parent's after a crossing (a zero is
a failure, not a silence), run with BOTH a player and a creature parent, plus
the new step carrying an armed creature across a landblock unload/reload.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-05 11:56:31 +02:00
Erik
e0f96a55bf fix(physics): C4 route 3 — portal placement authority (local player)
Removes a duplicate placement authority for local-player portal arrival.
Portalling worked before this change and works after it — this is not a
bug fix, EXCEPT that it found and fixed one dead-code production bug.

THE PRODUCTION BUG: TryExecuteCanonicalPortalPlacement re-read the
accepted destination at Place time, but TryBeginPortalReveal already
consumes that slot at Aim time — so the arm was 100% dead code and every
real portal Place refused with host-token-unavailable. Found only
because we refused to accept 7 skipped tests instead of chasing the
count to zero.

RETAIL IS THE GENERIC PATH FOR THE THIRD ROUTE RUNNING:
SmartBox::TeleportPlayer @0x00453910 = SetPositionSimple(dest, 1) with
flags 0x1012, followed by PlayerPositionUpdated.

BOTH INVERSIONS, WITH THEIR ANCHORS: unlike route 2, the leash IS armed
here (ConstrainTo @0x0045418A) and velocity is zeroed
(set_velocity @0x004541B4); unlike route 4b-3, the local teleport_hook
runs AFTER placement (@0x004538AE).

THE THREE-ROUND DEFECT CHAIN, HONESTLY:
- Round 1 released the player at the pre-teleport position while the
  anim stream marched on — the contract wrongly assumed Place re-fires
  (process rule 1's third occurrence this campaign).
- Round 2's fix inferred commit from a global PendingCount, which three
  non-committing paths also clear — making the SAME bug complete
  cleanly and silently. Strictly worse than round 1: round 1 at least
  tripped portal-complete-before-materialized.
- Round 3 latches the commit where it actually happens
  (ReconcileAndAcknowledgePortal), keyed on reveal generation and
  teleport sequence, via TryConsumePortalCommit. Two of the three
  required regression tests landed and are sabotage-verified on both
  hosts (ParkedPlace_ForgottenByOrdinaryMergeDoesNotLatchAsCommitted /
  HeadlessPortalPrepareDestinationForgottenByOrdinaryMergeDoesNotLatchAsCommitted).
  The third (force-arm-takes-the-slot) was judged unnecessary on review:
  with the inference gone, PendingCount is only a "don't ask yet" guard
  at both gates, so a force operation occupying or vacating the slot no
  longer changes an input the commit decision reads — the case collapses
  into what the landed test already discriminates.

THE B2/P3 RESOLUTION: both round-2 reviews were right about different
branches of the same synchronous call. RuntimePlacementProjectionSubscription
.OnPlacement acknowledges the FIFO head only when TryApply returns true;
a Place whose portal authority went stale (transit ended/superseded
while parked) used to return false, wedging every later entity's
placement receipt behind it forever. Both sinks
(RuntimePlacementPresentationSink, HeadlessRuntimePlacementProjectionSink)
now acknowledge-and-ignore a stale-authority Place instead of refusing
it. The regression test (RuntimePlacementPresentationSinkTests
.PortalPlace_StaleTransitHostOrSequenceIsAcknowledgedAndIgnored) had
been asserting the old, wrong `false` behaviour; it now asserts and
sabotage-verifies the fix.

Also lands: AP-144 (register discipline — the portal movement-event
send reuses the stricter UsePositionFromServer gate where retail's
SendMovementEvent is the looser autonomy_level != 0 test, diverging
only at level 1, currently unreachable), AP-145 + issue #318 (the
local-player collision-shadow presentation write bypasses its own
publisher's ShadowObjects write via a direct cache .Set(), self-healing
only once dedup diverges — filed, not fixed, pending a composition
test), AD-42 deleted (its last citation retired by the canonical portal
arm), AD-2 updated (the wait-cue's trigger predicate now covers a
second cause), and two documentation corrections: the enter_world
misattribution (both call sites are in SmartBox::HandleCreateObject,
only one in the player branch — portal arrival is TeleportPlayer, not
enter_world) and the stale "local player never reaches this path"
comment on the generic-remote-render-pose write.

Suite: 11,090 passed / 4 skipped / 0 failed. No new skips, nothing
weakened.

STILL OWED: the connected two-client gate, with
ACDREAM_PROBE_LOCAL_TELEPORT=1, scored only if [local-tp] lines
actually appear in the capture — and explicitly NOT scored as covering
issue #318 (no composition test yet asserts PhysicsEngine.ShadowObjects
directly).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-05 03:57:37 +02:00
Erik
cd3129e9d6 fix(physics): C4 route 7 — child cell propagation moves from a render tick into Runtime
Retail re-cells children when their parent crosses a cell, recursively, to
unbounded depth. acdream did it from a RENDER tick, so headless parented
children were cell-less forever and the canonical cell had two writers. This
slice makes Runtime the sole authority and demotes App's tick to
presentation-only. Contract:
docs/research/2026-08-04-c4-route-7-contract.md; the research that unblocked
it is docs/research/2026-08-04-retail-parent-cell-propagation.md (ca96ea5e).

Retail: SetPositionInternal @0x00515330 branches on `this->cell == curr_cell`
@0x0051536d; the changed branch reaches change_cell @0x00513390, whose
delegates leave_cell @0x00510f50 and enter_cell @0x00510ed0 self-recurse over
children and write the FULL identity (add_object @0x00510ee2, objcell_id
@0x00510f1e, part-array cell id @0x00510f2b, cell pointer @0x00510f35).
change_cell itself has no child loop.

THE TRAP, recorded because it nearly shipped: the depth-1 loop
@0x0051539c-0x005153d8 is the SAME-CELL fast path (objcell_id and part-array
id only, deliberately not the cell pointer), NOT the propagation. An
implementer who finds it first concludes "depth-1, id-only" and strands every
equipped item at a landblock boundary — the #184 class. The clincher against
that reading: update_object @0x00515d10 early-returns on `parent != 0`
@0x00515d40, so a child never runs its own physics tick and parent
propagation is the ONLY mechanism maintaining its cell.

Route 7 performs NO placement (DoPickupEvent @0x00452240 = unset_parent +
leave_world; DoParentEvent @0x00452290 = set_parent + SetPlacementFrame), so
it arms ConstrainTo nowhere — the leash rule INVERTS relative to routes
2/4/5, and both reviewers confirmed nothing arms.

Propagation is an ITERATIVE WORKLIST, not recursion. The first implementation
recursed with a depth-64 cap; both reviews independently found the cap left a
truncated tail at a stale NON-ZERO cell — permanently unrecoverable, logged
only under a probe flag, and on the withdraw path exactly the #184 shape
AP-142 clause (a) exists to reject. Shipping a fresh #184 instance inside the
slice that fixes stranded children was not acceptable, so the cap was removed
rather than tuned. The worklist retires the cap, the constant, its register
clause, and the failure mode together. Termination: every record on the stack
is already at the target pair, so nothing can be pushed twice and a hostile
A->B->A cycle collapses without a visited set.

The child write deliberately bypasses the public RuntimeEntityDirectory
.SetFullCell and calls the record method directly. This is LOAD-BEARING:
the public method re-enters PropagateFullCellToChildren, which opens with
_propagationWorklist.Clear() — routing children through it mid-drain would
wipe the shared stack and silently drop every unprocessed sibling. Any future
side effect added to the public SetFullCell must be mirrored by hand at that
call site.

Deliberate divergence, recorded not disguised: retail's removal path leaves
children with a null cell pointer but a STALE nonzero objcell_id @0x005133c1.
acdream does not reproduce it, because FullCellId != 0 is the liveness
predicate at 45+ sites — faithful porting would mark dead children live.
AP-142 records this; clause (d) records that acdream cannot gate propagation
on HasPartArray the way enter_cell gates on part_array @0x00510ed8, because
the flag's only writers are graphical and headless never sets it — the reason
is Slice J LAYERING, not a semantic difference (retail's part_array is itself
a mesh-construction product, single assignment site makeAnimObject
@0x0050e930 -> CPartArray::CreateSetup @0x0050e93e).

D7 adopts retail's unset_parent-before-leave_world order @0x0045227f ->
@0x00452286, applied to BOTH pickup paths including the dormant executor
replay. Its inertness was verified by reverting it and finding all 12
propagation tests still green — reported honestly rather than papered over
with a manufactured test, and independently confirmed by both reviewers.

ClassifyLeaveWorld and its request/cause types are DELETED: retail has no
classification here, and method-per-cause IS the retail dispatch shape.
Wiring it would have forced a vacuous teleport-sequence predicate with the
#307 shape.

Two review rounds plus a coordinator-required third pass; 5 MAJORs. One was a
handoff failure worth recording: enter_cell's part_array guard was correctly
identified as load-bearing by the research, dropped by the contract when it
enumerated the writes, and inherited as an omission by the code — a right
finding that evaporated across two handoffs with nobody re-reading the source.
Another was a test that survived deleting the entire behaviour it claimed to
pin, because its assertion read a field written unconditionally one line
earlier.

NoProjection is structurally unreachable from TickChild (TryResolveExactAttachment
performs a strictly stronger form of the same guard one call earlier). Kept as
a fail-safe, unit-tested directly, and documented in two places rather than
wrapped in a fabricated end-to-end test.

Headless regression test — the direct gate for this defect, which FAILED
before this work because no code path existed:
RuntimeLiveEntitySessionControllerTests
.DirectSink_D5_StandaloneParentEventCommitsChildToParentsExactCell.

Probe: ACDREAM_PROBE_CHILD_CELL=1 emits [child-cell] lines at all four write
sites (attach / headless-attach / propagate / withdraw / delete). TEMPORARY.

Complete Release suite MEASURED at 11,079 passed / 4 skipped / 0 failed
(baseline 11,063 at cff52c44, +16). An allocation flake appeared once under
load and was proven NOT this slice by reachability — RuntimeCollisionReportingState
contains zero SetFullCell and zero ParentAttachments references.

STILL OWED: the two-client connected gate (equip/unequip, carry across
landblock boundaries, pickup, loot, reconnect) with ACDREAM_PROBE_CHILD_CELL=1,
and a session counts only if [child-cell] cause=propagate lines appear.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-04 23:53:05 +02:00
Erik
6dc7ba51ee feat(physics): C4 route 4b-3 — remote teleport + cell-less through the canonical placement
Flips the last remote classification (SetPosition: teleport-advanced and
cell-less) onto 4b-1's RuntimeRemotePlacementDriveController, runs retail's
teleport_hook before the placement, and deletes the legacy remote-teleport
machinery. Contract: docs/research/2026-08-04-c4-route-4b-3-contract.md.

Retail: MoveOrTeleport @0x00516330's branch @0x00516386 -> teleport_hook
@0x005163EF -> SetFlags(0x1012) @0x00516414 -> SetPosition @0x00516420 ->
return 1 @0x00516438. The hook @0x00514ED0 runs BEFORE the placement and
regardless of its outcome. Retail places this branch unconditionally, at any
distance and any contact state (arg4 is read only @0x0051638E, after the
branch) — which is what retires AP-137's cell-less enqueue-vs-place delta.

D1 — the classifier's cell-less input is now the PRE-merge committed cell.
Retail's predicate is `this_1->cell == 0`, the BODY's own cell at
MoveOrTeleport entry (this_1 is assigned from this @0x00516334). acdream fed
the POST-merge canonical.FullCellId, which RefreshSnapshot ->
RefreshDerivedState -> SetFullCell has already stamped with the accepted wire
cell; a zero wire cell fails validation into RejectedData first. The shipped
remote cell-less predicate was therefore dead code, not merely different from
remotePlacementRequired. Threaded via a builder overload; route 1's overload
is untouched. The graphical !IsSpatiallyVisible arm of
projectionRequiresTeleportHook is deleted — a presentation predicate with no
retail analogue that fired the teleport machinery on a routine hot path.

Deleted: RemoteTeleportController (605), RemoteTeleportPlacement (85),
RemoteShadowPlacementSynchronizer (49), their 1,709 lines of tests, the
remotePlacementRequired predicate, the TeleportHookRequired plumbing, the
legacy pre-operation ConstrainTo fallback, and the player arm's legacy
!IsGrounded fallback. Net -2,030 lines.

Structural fix (two independent Opus reviews, round 1 FAIL/FAIL): three of the
four MAJORs were one defect — OnPosition carried two parallel inline copies of
the routing tail (player-guid, NPC-guid) that had drifted. Extracted
RunRemoteArmTail (3 call sites) and ApplyWireAirborneLeftoverBookkeeping (2),
both branches now share one implementation.

  A1  ToConstraintArm mapped AirborneSnap -> AirborneNoOperation, so the NPC
      arm armed ConstrainTo ZERO times for an out-of-contact wire-grounded
      creature — a regression this slice introduced while closing a
      structurally identical hole. Now maps to NearInterpolate; switch made
      total with a throwing default proven unreachable.
  R1  D2's write-nothing shape existed on the player arm only; NPC packets
      fell through and wrote the body. Retail makes no player/NPC distinction.
  R2  report_collision_end(this,1) @0x00514F31 was bound to
      ShadowObjects.Suspend, a port of a DIFFERENT retail function
      (remove_shadows_from_cells) that teleport_hook never calls. Now routes
      to RuntimeCollisionReportingState.LeaveWorld, which wraps the private
      ForceEnd in an admission-blocking transaction so a DoCollisionEnd
      callback cannot recreate the contact table.
  R3/A2 A teleported NPC synthesized ServerVelocity from the teleport distance
      (~1,000+ m/s) and planned a run cycle from it. Both the install and
      RemoteServerControlledVelocityCycle.Apply now gate on !isTeleportRoute.

BISECT HAZARD — A1's fix is correct only BECAUSE R1 landed. AirborneSnap is
reachable wire-airborne on the NPC arm only while D2's shape is missing there.
Reverting R1 alone silently inverts A1 into the opposite divergence: arming
where retail returns 0. Revert both or neither.

Also in the velocity hunk: the NPC block's two !IsPlayerGuid(update.Guid)
guards were dropped when it was wrapped in `if (!isTeleportRoute)`. Safe — all
five exit paths of the enclosing IsPlayerGuid block return, so the predicate is
unconditionally false below it — but it was unremarked by both reviews.

Register: AP-137 REWRITTEN (not deleted) to the surviving acdream-only
divergences — null classification during the login window and Rejected*
through UnroutedCatchUp keep a row. AD-42's RemoteTeleportController citation
retired; AP-136/AP-138 writer lists corrected to the two surviving non-Position
rebucket writers; AP-138 gains the teleport arm as a second producer of the
visible-without-collision residual (retirement path remains #309). AP-135 is
untouched and its two airborne bookkeeping writes are preserved on both arms.
AP-131 does not retire; #276 does not close.

Proof obligation 1: ParkCollisionResidents' overlap throw stays unreachable —
the teleport arm adds packets to the same TryBeginExclusiveAuthoredPlacement
one-operation-per-key machinery the far arm uses, opens no new operation shape,
and every DeferredCell outcome cancels synchronously with
restoreCancelledPark: true. The guarded property remains
HasOldPrefixPlacementDebt's stall, not a throw (4b-1's B2 caveat stands).

Correction to an earlier claim: LiveEntityPresentationController's
_activePlacementOwners was NOT write-never at HEAD —
remotePlacementRequired -> BeginPlacement -> Begin -> BeginAuthoritativePlacement
was a live writer chain. It becomes write-never BECAUSE this slice deletes that
chain, which is why deleting the dead half is behaviour-preserving.

Probe: ACDREAM_PROBE_REMOTE_TELEPORT=1 emits one [remote-teleport] line per
routed arm (guid, cause, hook-ran, placement status). TEMPORARY, strip with the
probe family.

Carried, disclosed not fixed: no dedicated bidirectional collision-partner test
for R2 (the wiring, not LeaveWorld itself, is what lacks coverage); the
stress test's teleport step drives hand-written field assignments rather than
the canonical arm; the per-packet runTeleportHook closure allocation (network
path, not the resolve path Slice I's 0 B discipline governs — file before
route 5 adds a fourth call site). B2: IRuntimeCollisionReportObserver has zero
production implementations, so retail's bidirectional DoCollisionEnd half still
reaches no gameplay consumer — this fix closes the wrong-function binding, not
that nobody listens.

Complete Release suite MEASURED at 11,013 passed / 4 skipped / 0 failed
(baseline 11,027/4/0; net -14 = ~33 deleted test cases against ~19 added).
Neither known flake fired (#302 PortalProjectionTests GC-allocation, #308
NakEmissionTests wall-clock).

STILL OWED: the two-client connected gate, which MUST use an NPC/creature
teleport target. Both round-1 MAJORs lived on the NPC arm and the velocity
cycle early-returns for 0x50xxxxxx guids, so a player target structurally
cannot observe A1, A2, or R3.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-04 16:00:10 +02:00
Erik
9966b53174 feat(physics): C4 route 2 — ForcePosition through the canonical placement
A local-player ForcePosition had TWO independent writers for one accepted
packet: LocalForcePositionTransaction snapped the physics body
(PlayerMovementController.BlipPosition, a raw SnapToCell with no collision
resolve), while LiveEntityNetworkUpdateController's generic tail separately
wrote position/cell/rotation to the render WorldEntity from the raw wire and
rebucketed it. Two stores, one packet — the divergence class 670f307c fixed on
the remote path. The outbound AutonomousPosition ack also fired BEFORE any
canonical commit existed: we told ACE "got it, I'm here" before deciding where
"here" was, and the trailing isCurrent() could only suppress the continuation,
never recall the packet.

RuntimeAcceptedPositionDriveController is now the one Runtime-owned seam. Both
hosts call the identical TryExecuteAcceptedLocalPosition; App and headless
project the committed result through the existing placement projection sink
(LiveEntityRuntime.TryApplyRuntimePlacementPlace already performed the same
four writes, from committed state rather than a wire guess).

Retail: SmartBox::HandleReceivedPosition @0x00453FD0's FORCE_POSITION branch is
get_heading -> Frame::set_heading -> SmartBox::BlipPlayer @0x00453940 -> stamp
POSITION_TS -> SendPositionEvent @0x00454091 -> return @0x0045409D. BlipPlayer
is CPhysicsObj::SetPositionSimple @0x005162B0 with flags 0x1012
(Teleport|Slide|SendPositionEvent) — a real collision-resolving SetPosition,
not a snap. The pinned classifier already encoded this exactly.

Named behaviour changes:

* The ack is now an OUTPUT of the committed route, fired strictly after the
  canonical commit and exactly once per accepted force packet.
* The ForcePosition route no longer re-arms the constraint leash. The force
  branch returns at 0x0045409D, ahead of all three ConstrainTo sites
  (0x00454272, 0x0045418A, 0x004541EC); the old re-arm cited retail's "Player,
  normal" branch, which BlipPlayer is not on. The teleport, CommitPreparedPosition
  and first-entry callers legitimately still constrain and are untouched.
* A force correction that terminates WITHOUT committing still sends its
  position event and is not retried — retail's BlipPlayer discards
  SetPositionSimple's SetPositionError return and acks unconditionally.

A single _pending funnel owns the in-flight placement, deciding on the token's
PositionAuthorityVersion against the record's: equal -> clear; advanced with the
newest accepted event still a force -> re-issue, re-classified; advanced to an
ordinary Apply -> clear, since newer server truth owns that pose. This closes a
double-apply/double-ack and a silently-dropped correction that two earlier
iterations of this slice each introduced.

AD-62 records the residual: a ForcePosition our async collision publication
cannot carry to a committed placement is not re-applied. Retail has no park —
its world is fully resident and its placement synchronous — so the state is
unreachable there. AP-131 is NOT retired; its legacy Position caller is route 4.

Deleted: LocalForcePositionTransaction, PlayerMovementController.BlipPosition,
HeadlessSessionWorldProjection.BlipLocalPlayer.

Gates: complete Release solution 10,858 passed / 4 skipped / 0 failed (baseline
10,844/4/0). Two independent Opus reviews (retail-conformance and
architecture/adversarial) PASS on the final diff after three FAIL rounds; every
intermediate state was fully green, so the suite caught none of the four real
defects. Connected acceptance is NOT run: nothing a user can do makes ACE emit
a ForcePosition without retail's @pklite, which acdream does not implement — see
docs/research/2026-08-03-c4-route-2-visual-gate.md.

Known gap, recorded not claimed: the plan's acceptance item 2 is unmet. The App
double-write check is a source pin, and "the committed projection moves the
render entity" is uncovered at any layer (#292). Filed alongside: #286-#291,
#293-#296.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-03 18:46:36 +02:00
Erik
175ad6b0d0 fix(session): acknowledge login after first placement
ACE intentionally creates the local player Hidden and releases that materialization state on LoginComplete. Sending LoginComplete from raw F746 receipt raced canonical placement and left the login haze visible. Route one one-shot completion callback from Runtime's local first-entry terminal edge to graphical and prepared headless hosts; retain a guarded accepted-Create edge only for content-less headless sessions. Focused Runtime login tests, all 79 Headless tests, the connected user gate, and the Release build pass.
2026-08-03 12:10:42 +02:00
Erik
529e0e9d88 feat(runtime): C3c - production placement cutover: both hosts on the residence conductors (routes 1+8)
Campaign P remaining-physics-divergence, placement cutover slice C3c
(docs/plans/2026-08-02-placement-cutover.md). Both production hosts now
register every initial Create through the residence + continuation-
executor + first-entry-conductor machinery (C0-C3b):

- Graphical (route 1): RegisterEntityWithInitialResidence at Create; the
  shared RuntimeFirstEntryDriveController pumps both conductors from the
  placement-receipt flow; MaterializeProjection and RebucketLiveEntity
  are presentation-only while a residence is ACTIVE (ExecutorCompleted is
  the presentation-binding receipt); post-residence entities take the
  full legacy path including the prepare_to_enter_world clock edges.
  PlayerModeController attaches presentation to the Runtime-published
  controller; its legacy resolve/step-heights/host-construction path is
  deleted; presentation-only rollback (retail has no entry-flow rollback).
- Headless (route 8): OnSpawned registers with residence when a drive
  exists; content-less sessions keep the pre-flip direct registration;
  SynchronizeLocalPlayer/CreateController/ApplySetupStepHeights deleted;
  prepared-collision read failure is a typed AwaitingCollisionSource
  retry; far remotes outside the service window complete celless.
- RuntimeLocalPlayerMovementState.Controller setter sealed internal; all
  controller mutation flows through the publication lifecycle.

Fix slices landed within this cutover, each dual-gated:
- F1: live movement-stat/server-physics application routed through the
  Runtime ownership seam (post-logout ingest crash on the retired
  controller eliminated; RuntimeMovementSkillProjection deleted).
- F2: login activation wedge - collision-admission prefix gate factored
  out of the seal (reentrant-commit RejectedAuthority), rearm generation
  identity corrected, PlayerModeAutoEntry requires the Runtime-published
  controller (world reveal can no longer seal unmaterialized).
- F3: landblock-prefix 0-sentinel replaced by explicit absent-id guards;
  map-corner landblocks (grid row/col 0) fully legal through admission,
  park/rearm/retire, quiescence, and outdoor shadow seeds.
- F5: local-player first-entry ground contact seeded by the shared
  SpawnPlacementSettler (moved App->Core) at FinalizeActivation - the
  retail first-gravity-frame touch (enter_world 0x00516170 carries no
  seed); the legacy unconditional force-seed is overwritten by a real
  floor-found contact; airborne spawns stay airborne; outbound contact
  bit verified end-to-end. Fixes the standing-cast 'You can't do that
  while in the air!' rejections.
- R1 (dual-review round): login constraint leash armed at the committed
  placement (HandleReceivedPosition 0x00453FD0 analog); register rows
  AD-61 (settle-timing compression now covering the local player) and
  AD-42 (repointed off the deleted resolve split) in this commit;
  residence-conversion owner API; wire-landblock guards; drive-pending
  ledger in IsConverged; route attach/detach latch; executor-drain drift
  model documented + source-pinned.

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 (logs/connected-world-gate-20260802-175401; graceful exits,
world-visible, zero airborne rejections). The nine-stop soak remains red
for the pre-existing 6b28ff99 whole-world collision-clone throughput
regression (attributed with evidence; scheduled as its own slice before
C5). Dual Opus reviews (retail-conformance + adversarial): delta PASS.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-02 18:10:33 +02:00
Erik
3f3401257c fix(headless): complete connected movement gate 2026-07-27 10:26:44 +02:00
Erik
b6547ff38c feat(headless): hydrate isolated collision worlds 2026-07-27 09:25:58 +02:00
Erik
f8cb840fb1 feat(headless): complete portable single-session host 2026-07-27 07:36:53 +02:00