acdream/docs/research/2026-07-31-canonical-set-position.md

6.6 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::SetPosition 0x005160C0
  • CPhysicsObj::SetPositionInternal 0x00515BD0
  • CPhysicsObj::AdjustPosition 0x00511D80
  • CPhysicsObj::CheckPositionInternal 0x00511E90
  • CTransition::find_valid_position 0x0050C310
  • CTransition::find_placement_position 0x0050C170
  • CTransition::find_placement_pos 0x0050BA50
  • CTransition::validate_placement_transition 0x0050ADC0
  • CTransition::validate_placement 0x0050B210
  • CPhysicsObj::ForceIntoCell 0x00515660
  • CPhysicsObj::handle_all_collisions 0x00514780

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_transition is the inner find_placement_pos validator. Any non-OK state from COLLIDED through SLID, when sliding is permitted, resets COLLISIONINFO; it never retries placement.
  • validate_placement is the outer initial/final validator. Only ADJUSTED or SLID, and only while its retry argument is true, performs one placement_insert; COLLIDED neither 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.