18 KiB
Canonical retail SetPosition — placement/streaming Slice 4A
Scope
This note pins the pure physics half of the placement/streaming closeout. Slice 4A lands the retail placement transaction as a separately testable Core mechanism. It deliberately does not replace the production snap-only resolver yet: Runtime lost-cell ownership and the complete inbound-route cutover remain Slice 4B. AP-1 and AD-1 therefore remain active until that cutover is complete.
Named-retail oracle, Sept 2013 EoR:
CPhysicsObj::SetPosition0x005160C0CPhysicsObj::SetPositionInternal0x00515BD0CPhysicsObj::AdjustPosition0x00511D80CPhysicsObj::CheckPositionInternal0x00511E90CTransition::find_valid_position0x0050C310CTransition::find_placement_position0x0050C170CTransition::find_placement_pos0x0050BA50CTransition::validate_placement_transition0x0050ADC0CTransition::validate_placement0x0050B210CPhysicsObj::ForceIntoCell0x00515660CPhysicsObj::handle_all_collisions0x00514780
Retail transaction
SetPosition(request):
transition = makeTransition() // GENERAL_FAILURE if none
init_object(transition, object)
if the PartArray has no spheres:
init_sphere(1, dummy center=(0,0,0.1), radius=0.1, scale=1)
else:
init_sphere(first min(count,2) authored spheres, exact object scale)
if flags & RANDOM_SCATTER (0x200):
return scatter only
result = SetPositionInternal(request)
if result != OK and flags & SCATTER (0x100):
return scatter
return result
SetPositionInternal(request):
AdjustPosition(request frame, first sphere, noCreate=(flags & 0x20))
if no resident cell:
store the adjusted authoritative frame and enter lost-cell lifetime
return OK
if the live weenie is Hook, Storage, or Corpse:
return ForceIntoCell(resident cell, frame)
set do_not_load_cells from flag 0x20
if !CheckPositionInternal(...):
handled = handle_all_collisions(...)
return handled ? COLLIDED : NO_VALID_POSITION
if transition.curr_cell == null:
return NO_CELL
commit the complete transition
return OK
AdjustPosition branches on the claimed cell shape. A direct outdoor claim
runs pure LandDefs::adjust_to_outside normalization before visible-cell
lookup. An indoor claim first resolves the visible cell and child; only a
resident indoor cell marked seen_outside falls back to outdoor
normalization. An absent indoor cell (including the 0xFFFF sentinel) remains
the exact claimed cell/frame. A map-edge outdoor normalization failure stores
cell zero with the otherwise unchanged frame. The old max(terrainZ, z) lift
and nearest-in-Z scan do not occur in this canonical mechanism.
CheckPositionInternal calls the complete placement transition. Without the
slide flag, retail accepts the result only when signed
resolvedX-requestedX <= 0.0500000007, the same signed Y condition holds, and
the cell is unchanged. Z is not part of that predicate. The resolved origin is
accepted while the requested orientation remains intact.
Two validators, not one
The similarly named retail helpers have different contracts and stay separate in the port:
validate_placement_transitionis the innerfind_placement_posvalidator. Any non-OK state fromCOLLIDEDthroughSLID, when sliding is permitted, resetsCOLLISIONINFO; it never retries placement.validate_placementis the outer initial/final validator. OnlyADJUSTEDorSLID, and only while its retry argument is true, performs oneplacement_insert;COLLIDEDneither resets nor retries.
Step-down is disabled only for missiles. For fewer than two spheres retail first clamps the requested height to half the radius when the sphere diameter is less than or equal to that height. It then performs one full probe when the diameter is greater than the resulting height, otherwise two half probes. Equality belongs to the two-half-probe branch.
Modern seam
PhysicsEngine.SetPosition returns one immutable
PhysicsSetPositionResult. SetPositionError retains the header values
(OK=0, GENERAL=1, NO_VALID=2, NO_CELL=3, COLLIDED=4,
INVALID_ARGS=0x100) while PhysicsResidenceDisposition separately reports
Committed, DeferredCell, or Unchanged. Missing content is therefore
successful-but-deferred, never misreported as a placement failure.
The result carries the complete commit packet: root/cell-local frame,
contact/walkable/water state, sliding and collision normals, stationary-fall
counter, a complete immutable COLLISIONINFO snapshot for the real
handle_all_collisions callback (including contact/last-contact, sliding,
collision normal, stationary-fall, environment, adjustment, and object
fields), the callback result, and an explicit shadow action. A PhysicsBSP or changed-cell force commit
requests canonical shadow recalculation; a non-BSP transition replaces its
shadows only when the transition produced a nonempty cell array, otherwise it
preserves the prior list. Unchanged force placement changes only the frame.
Core never creates cells synchronously, so retail flag 0x20
(DoNotCreateCells) has no differential loader branch inside this pure
mechanism. Both flag states can only observe already-published immutable cell
content and otherwise return DeferredCell. Carrying the flag into exact-cell,
generation-scoped async admission is part of the still-open Slice 4B
adaptation tracked with AD-2; this slice does not claim a dead SpherePath field
as exact behavior.
The existing public Resolve compatibility entry remains entirely unchanged
for production movement and zero-delta callers. Its result cannot represent a
successful-but-deferred residence, so hiding DeferredCell inside
ResolveResult.Ok would corrupt the contract. Slice 4B will route every
authoritative placement family through SetPosition, atomically install its
packet, and own exact lost-cell wakeup/commit.
Automated oracle
PhysicsSetPositionTests pins error values, absent/invalid outdoor and indoor
claims, cross-landblock frame normalization, map-edge failure and the 0xFFFF
sentinel, dummy/authored sphere setup, nonpositive scale, Ethereal seeding,
explicit-only PathClipped, missile step-down, exact equality schedules, both validators,
the late compass sample's float bits, signed no-slide behavior, actual
collision-handler mapping, force-class policy, explicit shadow actions, null
current-cell wakeup, ten-record scratch exhaustion, exact scatter ordering,
failed-probe scratch lifetime, and deferred-scatter stop. The legacy public
Resolve fixture remains unchanged until Slice 4B.
Slice 4B1 — Runtime residence owner
Slice 4B1 adds the presentation-independent half of the cutover without
changing a production graphical route yet. RuntimeSetPositionState accepts
an exact entity/position token before graphical DAT preparation, consumes the
immutable Core result, and commits body, contact, full cell, shadows, object
clock, and Runtime spatial worksets before publishing one ordered placement
delta. The delta carries the exact RuntimeEntityKey, session lifetime,
position/spatial/placement versions, adjusted cell, collision generation, and
optional portal-authority shape. A throwing or unavailable host does not roll
simulation back: Runtime republishes the same projection token until the
exact FIFO head is acknowledged. A newer operation changes an already-
published token to Discard and increments its projection revision, so an
acknowledgement of the previously observed Place/Withdraw cannot consume an
unseen Discard. It is never silently forgotten. An unacknowledged lost-cell
Withdraw transfers intact to a replacing accepted operation and remains the
FIFO head before that replacement may publish Place.
The successful missing-cell path owns retail's residence shape:
SetPosition -> OK + DeferredCell
retain adjusted Position and the same PhysicsBody/components
clear only Active and suspend the object clock
withdraw Runtime spatial worksets and shadow rows
retain shadow registration and exact authored mover request
append parentless root to (exact cell, collision generation)
arm independent exact-key 25 s deadlines for root + direct children
publish Withdraw
exact cell generation resident + Withdraw acknowledged
re-run SetPosition with retained authored spheres and CurrentCellId=null
atomically install complete result
publish Place
Lost-cell membership buckets use retail-shaped append plus swap-remove.
Destruction deadlines use an exact-key hash plus a bounded indexed min-heap,
the allocation-bounded modern equivalent of retail's hash +
PQueueArray<double> priority owner. Rearm/removal updates the exact heap node;
there are no stale tombstones. Only committed, current direct children from
the parent-incarnation ordered CHILDLIST participate; unresolved or future
relations cannot inherit a deadline. Parent, pickup, delete, GUID
replacement, newer Position, reset, and disposal cancel the exact incarnation
and use leave-world semantics rather than a wakeable lost entry. The dormant
collision-retirement entry parks non-static parentless indoor roots and, for
complete withdrawal, affected outdoor roots. It performs a complete preflight
and rejects overlap with any active accepted/host-ack-pending placement before
mutating one resident. It then installs every affected canonical lost
residence and operation before publishing the first synchronous Withdraw, so
an observer re-entering for a later root inherits that root's exact pending
Withdraw instead of having its newly accepted placement cancelled by the
retirement loop. 4B2 must quiesce that placement prefix before invoking the
entry in the same transaction that installs host acknowledgements.
Runtime retains the last accepted prepared mover request, including exact
off-center/two-sphere payloads, scale, flags, and step values. A cold resident
with no prepared request still withdraws atomically but remains explicitly in
AwaitingPreparation; it cannot wake through an invented empty-sphere shape.
Preparation is cached only after Core accepts it as Committed or DeferredCell.
A rejected/malformed preparation keeps the same accepted token retryable and
cannot replace the last validated mover used by later collision retirement.
Runtime's host boundary rejects only non-finite consumed frame/shape values;
retail-valid oddities such as nonpositive authored scale remain untouched.
Likewise, a non-deferred wake failure retains the withdrawn body, independent
25-second lifetime, and exact operation: invalid arguments return to
AwaitingPreparation and re-index for the next exact generation, while other
world-placement failures also re-index. The last successful DeferredCell
result and adjusted frame remain canonical across the failed attempt. No
failed wake can leave a live entity withdrawn without a Runtime owner. The
graphical/no-window cutover in 4B2 supplies that exact preparation token.
Retail CPhysicsObj::SetPositionInternal (0x00515BD0) calls
prepare_to_enter_world (0x00511FA0) only when this->cell == 0.
Consequently the physics update_time (PhysicsBody.LastUpdateTime) and
active bit are reset only on the cellless-to-world edge. Ordinary same-cell or
cross-cell in-world SetPosition preserves the already-consumed physics clock;
entering the lost-cell residence also preserves it until the eventual
cellless wake commit. Runtime pins both sides and does not inherit the older
graphical teleport helper's unconditional timer reset.
The wake timestamp is in the Runtime simulation-time domain, never Unix/UTC:
GameRuntime binds its instance GameRuntimeClock through the entity/physics
owner, and RetryDeferred samples SimulationTimeSeconds. Standalone Runtime
fixtures without a bound game clock retain the accepted command time. This is
a Runtime dependency only; no App delegate enters the owner.
The canonical commit also installs every SetPosition-derived body invariant
before host publication: Contact/OnWalkable/WaterContact, the current contact
plane and slope GroundNormal, Sliding plus its normal, and the complete
StationaryFall/Stop/Stuck encoding. Named retail
CPhysicsObj::SetPositionInternal(CTransition const*) (0x00515330) copies
only the transition's current contact plane/water flag, walkability, sliding
normal/valid flag, and collision state (0x005153E5–0x005154FE). It does not
publish last_known_contact_plane or the SpherePath walkable polygon on this
path, so those ordinary-update-only fields are intentionally absent from the
immutable SetPosition result and remain unchanged. A zero expected velocity
version in host preparation preserves the nonzero version captured when the
operation was accepted; an intervening Vector/Movement therefore suppresses
only the stale collision-velocity response. A bodyless cancellation terminates
without fabricating a PhysicsBody or a host Withdraw projection. The two time
domains remain explicit: PhysicsBody.LastUpdateTime consumes the instance
simulation clock, while IRuntimeRemotePlacement.LastServerPositionTime
remains Unix-UTC receipt time because the remote stale-velocity owner ages it
against RuntimePhysicsState.UtcNowSeconds. A deferred wake therefore cannot
make fresh authoritative remote velocity appear years old. Runtime preparation
also applies the existing retail PositionFrameValidation before Core or
prepared-mover caching, and caps synchronous Scatter/RandomScatter work at 64
attempts; this keeps valid authored retail request shapes while rejecting a
hostile uint.MaxValue loop at the authority boundary.
One authority boundary intentionally remains open for 4B2:
RuntimePortalPlacementAuthorityvalidates immutable token shape only; 4B2 must bind it to activeRuntimeWorldTransitStategeneration, teleport sequence, destination, and host acknowledgement before reveal.
The former collision-report boundary is closed by
RuntimeCollisionReportingState. Runtime now owns retail's exact-key object
contact table, environment latch, strict ordinary/ethereal expiry, force-end,
static and ReportAsEnvironment routing, reciprocal callback eligibility,
missile-state clearing, ordered reentrant dispatch, and the report-result
boolean which distinguishes placement Collided from NoValidPosition.
Successful SetPosition commits reporting after Contact/OnWalkable and ground
callbacks but before its single physical response and shadow reflood. See
docs/research/2026-07-31-runtime-set-position-collision-reporting.md.
Slice 4B2 checkpoint 1 — public dormant host seam
The first 4B2 checkpoint exposes the dormant receipt owner through
RuntimePlacementProjectionChannel. Graphical and no-window hosts can observe
the one ordered placement stream, retry the exact immutable pending receipts,
peek the FIFO head, measure pending debt, and acknowledge only the exact head.
Mutation and retry calls require the current RuntimeGenerationToken; a stale
generation, stale revision, reordered token, duplicate acknowledgement, or
reused GUID cannot consume current placement debt. The channel delegates to
RuntimeSetPositionState and RuntimeEntityObjectEventStream; it owns no
second queue, mirror, or rollback path.
Shared local-controller body adoption is deliberately deferred. A reviewed prototype that prepared directly on the canonical body was rejected: a snapshot/rollback lease cannot safely coexist with reentrant SetPosition, remote/projectile binding, deletion/GUID reuse, owner replacement, object-clock epoch changes, or disposal. Correct adoption requires either an exclusive Runtime transaction integrated with every canonical writer, or off-canonical preparation followed by one validated atomic body/controller publication. Either choice belongs to the all-route ownership cutover, not this narrow dormant-seam checkpoint.
This remains a deliberately non-activating checkpoint. Production spawn, Position, projectile, drop/pickup/parent, and portal routes do not submit to the dormant SetPosition owner yet. The cutover remains blocked on exact ordered Setup spheres/scale/step heights/flags/cell-local preparation, presentation-only rebucketing, and placement-prefix quiescence before collision retirement. AP-1 and AD-1 remain open until those prerequisites and every production route land together.
AD-2 remains the explicit async adaptation: collision readiness can publish in a different frame from retail's blocking load. A failed wake is safely re- indexed to the next exact generation instead of inheriting retail's synchronous assumption. When older unbound survivors meet newer entities already indexed into that future generation, Runtime merges them into one bucket with the older survivor order first and retains one bucket-order entry. AP-1 and AD-1 remain open until 4B2 removes the legacy graphical/headless placement paths.
RuntimeSetPositionStateTests pins accepted-before-preparation ownership,
portal-shape rejection, canonical-before-projection ordering, retry and
reentrant discard, cross-landblock adjusted-cell park/wake, same-body
identity, retained contact/water/sliding/velocity, exact generation gating,
authored two-sphere retention, cold preparation, bounded priority-deadline
rearm/cancel, zero-allocation empty ticks, independent ordered direct-child
deadlines, actual collision-admission supersession/invalidation, missing exact
indoor-cell generation rebind/wake, collision demotion/withdrawal preflight,
failed-wake retry, malformed-preparation retry without cache poisoning,
simulation-clock-domain wake, velocity-version preservation, derived body-bit
writeback, immediate remote-velocity survival across the simulation/UTC clock
boundary, invalid cell/frame/quaternion and extreme-scatter rejection before
Core/caching, bodyless cancellation, newer Position/pickup/parent/delete, GUID
reuse, reset, and complete index/node terminal convergence. Existing zero-
allocation collision-generation gates remain unchanged on the no-deferred
fast path.
The warmed immediate commit/ack route currently measures exactly 1,880 managed bytes per operation in the Release Runtime test host (1,000 iterations after 64 warmups); the regression gate caps it at 2,048 bytes. This dormant-path result is an explicit 4B2 activation blocker rather than a claim of allocation-free production readiness: 4B2 must either pool/remove the operation and projection envelopes or record an approved measured budget before routing frame-frequency placement through this owner.