acdream/docs/research/2026-07-31-runtime-set-position-collision-reporting.md

8 KiB

Runtime SetPosition collision-report ownership

Scope: placement/streaming Slice 4B2 prerequisite A only. This closes the missing Runtime owner for retail collision tracking and the boolean returned by CPhysicsObj::handle_all_collisions. It does not activate any graphical or headless production SetPosition route.

Named-retail oracle

Primary sources:

  • CPhysicsObj::report_object_collision_end 0x00510A90
  • CPhysicsObj::report_environment_collision 0x00512FC0
  • CPhysicsObj::report_object_collision 0x00513060
  • CPhysicsObj::track_object_collision 0x00513F10
  • CPhysicsObj::report_collision_start 0x00513FD0
  • CPhysicsObj::report_collision_end 0x00514620
  • CPhysicsObj::handle_all_collisions 0x00514780
  • CPhysicsObj::SetPositionInternal(CTransition const*) 0x00515330
  • CPhysicsObj::leave_world 0x005155A0
  • placement failure path in CPhysicsObj::SetPositionInternal 0x00515BD0
  • CPhysicsObj::CollisionRecord, EnvCollisionProfile, ObjCollisionProfile, and AtkCollisionProfile in docs/research/named-retail/acclient.h

The source text is docs/research/named-retail/acclient_2013_pseudo_c.txt. The addresses above are the behavioral authority; the older unnamed chunks remain fallback only.

Environment reporting

report_environment_collision(meInContact):
    reported = false
    if !colliding_with_environment:
        if self.ReportCollisions && self.weenie != null:
            DoCollision(EnvCollisionProfile(self.velocity, meInContact))
            reported = true
        colliding_with_environment = true
        if self.Missile:
            self.state &= ~(Missile | AlignPath | PathClipped)
    return reported

The latch is independent of callback eligibility. An object with no collision callback still latches its environment contact, and a repeated environment hit returns false. Retail has no environment-end callback. leave_world does not clear this latch; the next handle_all_collisions call re-arms it only after a non-environment frame.

Object reporting and tracking

track_object_collision(other, meInContact):
    if other.Static:
        return report_environment_collision(meInContact)

    record = { touched_time = PhysicsTimer.curr_time,
               ethereal = other.Ethereal }
    existed = collision_table.clobber(other.id, record)
    if existed:
        return false
    return report_object_collision(other, meInContact)

The table insert/refresh precedes callbacks. Duplicate contacts refresh their time but never replay a start callback. DAT/static classification and physics state come from the exact shadow object which produced the collision; object-ID presence is not a valid substitute.

report_object_collision first maps ReportAsEnvironment to the environment path. Otherwise:

  • the mover reports only when the other object is not IgnoreCollisions and the mover has ReportCollisions plus a weenie;
  • a mover which had Missile set before the source callback unconditionally masks its current Missile | AlignPath | PathClipped bits after striking a non-ignored object, even when the callback cleared Missile but re-added path bits; when pre-callback Missile was clear, callback-added Missile is retained;
  • the reciprocal report occurs only when the other has ReportCollisions, the mover is not IgnoreCollisions, and the other has a weenie;
  • the return is true when at least one callback is attempted. It is never a collision-presence boolean.

Expiry and end reporting

report_collision_end(force) removes records before dispatching callbacks. This ordering is required for safe reentrancy.

ordinary record: remove when age > 1.0, or force
ethereal record:  remove when age > 0.0, or force

Equality remains alive. A still-resolvable non-ReportAsEnvironment peer may receive reciprocal collision-end callbacks. When the peer no longer resolves, the owner can still receive its self-only end using the stored retail object ID. A later incarnation must never satisfy the old contact record.

handle_all_collisions and SetPosition ordering

handle_all_collisions(info, previousContact, previousOnWalkable):
    reported = false
    for other in info.collidedObjects, in encounter order:
        reported |= track_object_collision(other, previousContact)
    report_collision_end(force = false)

    if environment latch is already set:
        latch = info.collided_with_environment
    else if info.collided_with_environment
         || (!previousOnWalkable && self.OnWalkable):
        reported |= report_environment_collision(previousContact)

    apply retail collision velocity/stationary response
    return reported

Successful SetPositionInternal(CTransition const*) commits the resolved cell/frame, Contact/WaterContact/OnWalkable state, and HitGround/LeaveGround edge before handle_all_collisions; it ignores the returned boolean and only then replaces/refloods shadows. Collision reports observe the old stationary- fall state; the new counter is installed before physical response, while the StationaryFall/Stop/Stuck transient bits are replaced after response and before shadow reflood. The placement failure path calls handle_all_collisions(info, false, false) and maps true to SetPositionError::Collided (4) and false to NoValidPosition (2).

Consequently acdream must keep report/tracking separate from the physical response: failed placement runs both once, while successful Runtime commit runs reporting between the contact/ground commit and shadow reflood without double-applying velocity response.

Runtime ownership contract

The implementation is presentation-free and belongs to the per-session RuntimePhysicsState graph. Its invariants are:

  • owner and peer identities are exact RuntimeEntityKey values, not server GUID or local ID alone;
  • each tracked record retains the peer server GUID, touch time in the Runtime simulation-clock domain, and ethereal-at-touch bit;
  • collided IDs and authored/static ownership are admitted through the exact retained ShadowObjectRegistry registration which produced the collision; every dynamic Static/Ethereal/Ignore/ReportAsEnvironment decision then reads the current canonical PhysicsBody.State, never a stale shadow snapshot;
  • immutable reports preserve encounter order and dispatch through a retained, reentrancy-safe FIFO;
  • callback exceptions are isolated, while the retail report-result boolean is determined by callback eligibility and does not depend on subscribers;
  • every callback boundary revalidates the exact record/body/authority before any later canonical mutation;
  • force-end mutates the complete expired set before publishing ends; exact-key admission guards prevent callback reentry from recreating a leaving owner, and session teardown blocks the whole owner batch before its first callback;
  • one source lifetime token covers a complete precollected end batch, so a callback-accepted delete stops every later peer report even while teardown sidecars remain resolvable;
  • lifetime forget, session reset, and disposal cannot donate state to GUID reuse;
  • terminal ownership diagnostics include contact/report state and converge to zero;
  • graphical and no-window hosts borrow the same Runtime owner. No host owns a second collision table or report-result heuristic.

The warmed steady-contact refresh path allocates zero managed bytes. Expired contact storage is allocated lazily only after the first actual expiry, and session-batch teardown is linear in owner count.

Deliberately deferred

The canonical SetPosition owner remains dormant in production. The following belong to later 4B2 commits and are not part of this checkpoint:

  • exact ordered Setup spheres, authored scale and step-height preparation;
  • the atomic shared local-controller body transaction;
  • presentation-only rebucketing and placement-prefix quiescence;
  • graphical/headless spawn, Position, portal, projectile, drop, pickup, parent, and delete route cutover.

AP-1 and AD-1 therefore remain open, narrowed only by removal of the collision-report prerequisite.