# CSphere::intersects_sphere — swept-sphere-vs-sphere pseudocode **Date:** 2026-06-24 **Task:** Task 2 — true sphere collision primitive (collision-inclusion phase) --- ## Oracles consulted 1. **Named-retail decomp** `acclient_2013_pseudo_c.txt`: - `CSphere::collides_with_sphere` @ `0x005369E0` — static overlap test - `CSphere::intersects_sphere` (primary) @ `0x00537A80` — the full 6-path dispatcher - `CSphere::intersects_sphere` (Position variant) @ `0x00537FD0` 2. **ACE C# port** `references/ACE/Source/ACE.Server/Physics/Sphere.cs`: - `CollidesWithSphere(Vector3 otherSphere, float radsum)` — static overlap - `FindTimeOfCollision(Vector3 movement, Vector3 spherePos, float radSum)` — swept solve - `IntersectsSphere(Vector3 center, float radius, Transition transition, bool isCreature)` — 6-path dispatcher --- ## CSphere::collides_with_sphere (static overlap test) Retail @ `0x005369E0`: ``` collides_with_sphere(this, disp_vec3, radsum_float): lenSq = disp_vec3.x² + disp_vec3.y² + disp_vec3.z² if radsum² > lenSq: // i.e. lenSq < radsum² return 1 (true — overlapping) return 0 (false) ``` ACE equivalent: `disp.LengthSquared() <= radsum * radsum` Note: retail uses `>` (strictly greater-than radsum²), ACE uses `<=`. These are the same predicate — the retail FPU instruction emits "collides" when radsum² is NOT less than lenSq, which is `lenSq <= radsum²`. --- ## FindTimeOfCollision (swept quadratic, from ACE) ACE `Sphere.FindTimeOfCollision(Vector3 movement, Vector3 spherePos, float radSum)`: Interprets "mover starts at origin, travels by `movement`; target is at `spherePos` relative to mover". ``` distSq = |movement|² // if < EPSILON: no sweep (degenerate), return -1 nonCollide = |spherePos|² - radSum² // if < EPSILON: already overlapping → no forward collision needed, return -1 similar = -dot(spherePos, movement) // projection of separation onto movement direction disc = similar² - nonCollide * distSq // discriminant of quadratic if disc < 0: return -1 // no real intersection cDist = sqrt(disc) if similar - cDist < 0: return -(cDist + similar) / distSq else: return -(similar - cDist) / distSq ``` This returns a time in the range [0, 1] for the first contact. A return of -1 means no hit (miss or already overlapping). Values > 1 mean the sweep doesn't reach the target within the movement step. --- ## SweptSphereHitsSphere — our primitive (pure function) Wraps `FindTimeOfCollision` with a clean bool/out API for the narrow-phase dispatch: ``` SweptSphereHitsSphere(moverCenter, moverRadius, sweepDelta, targetCenter, targetRadius, out float t): movement = sweepDelta // vector the mover travels spherePos = targetCenter - moverCenter // target relative to mover's start radSum = moverRadius + targetRadius t = (float) FindTimeOfCollision(movement, spherePos, radSum) return t > 0 && t <= 1 ``` `t` is the parametric fraction of `sweepDelta` at which surfaces first touch. `t <= 0`: target is behind or already overlapping (use static test separately). `t > 1`: sweep misses (target too far in this step). --- ## Retail dispatch order for Sphere objects From `CSphere::intersects_sphere @ 0x00537A80` — the same 6-path structure as for CylSpheres: 1. `obstruction_ethereal || insert_type == PLACEMENT_INSERT`: Static overlap test only (`collides_with_sphere`). Return Collided or OK. 2. `step_down != 0`: Delegates to `step_sphere_down` (for non-creature movers). 3. `check_walkable != 0`: Static overlap test. Return Collided or OK. 4. `collide == 0`: Sub-dispatch on `object_info.state & 3` (Contact/OnWalkable): - Contact: step_sphere_up or slide_sphere - PathClipped: collide_with_point - Default: land_on_sphere or collide_with_point 5. `collide != 0` + `isCreature`: Return OK (creatures don't block each other via sphere-sphere in this path). 6. `collide != 0` + not creature: Full swept quadratic. Set contact plane, adjust check_pos. For our narrow-phase dispatch in `FindObjCollisionsInCell`, the "narrow-phase Sphere branch" maps directly to ACE's `IntersectsSphere` — which acdream already implements for Cylinder objects via `CylinderCollision`. The sphere primitive just provides the swept check without the cylinder's height clipping. --- ## Acdream adaptation note The `SweptSphereHitsSphere` primitive is PURE (no Transition state). The actual 6-path dispatch (step-up, land-on, slide, etc.) is handled by the existing `CylinderCollision` infrastructure — for Sphere-typed shadow entries we call through the same dispatcher after the overlap check, using 3-D distance for the broad-phase (not XY-only cylinder distance). The primitive's narrow phase: `static overlap` (`CollidesWithSphere`) is the gate; the swept quadratic from `FindTimeOfCollision` resolves the time-of-contact for the walkable landing path. For the initial ship (Task 2), we implement the static overlap test in the dispatch (matching the `obstruction_ethereal`/`check_walkable`/`Contact` paths that don't use the swept form), plus `SweptSphereHitsSphere` for the swept narrow-phase. The full 6-path wiring for sphere objects mirrors the cylinder path already in `CylinderCollision`, extended to use 3-D distance instead of XY-only.