feat(runtime): own SetPosition collision reports
This commit is contained in:
parent
ec627c13a2
commit
237d1184d2
19 changed files with 3744 additions and 103 deletions
|
|
@ -0,0 +1,182 @@
|
|||
# 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
|
||||
|
||||
```text
|
||||
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
|
||||
|
||||
```text
|
||||
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.
|
||||
|
||||
```text
|
||||
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
|
||||
|
||||
```text
|
||||
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.
|
||||
Loading…
Add table
Add a link
Reference in a new issue