Skip to content

Rapier cluster physics: contact events + per-entity collider shapesΒ #118

@martinjms

Description

@martinjms

Quick Summary

  • 🟑 Status: planned, ready to start. Depends on #117 β€” Rapier cluster physics: minimum integration.
  • Add a sibling trait RapierClusterSimulation (in arcane-infra::rapier_cluster) that takes an extended context with contact events, plus a per-entity collider shape declaration.
  • Without these two, Rapier integration is "all entities are uniform spheres with invisible collisions" β€” fine for tech demo, useless for real games.
  • Single PR; tests for each new capability.

Why This Matters

  • Contact events make Rapier physics observable to game code β€” collision damage, item pickup, area triggers, hit detection.
  • Per-entity collider shapes make Rapier physics real β€” capsules for humans, boxes for crates, rather than uniform 0.5-radius spheres.
  • Together these are the minimum surface to make Rapier-backed nodes usable for typical action games. Either alone is half-built: contact events without varied shapes give artificial sphere-on-sphere contacts; varied shapes without events leave the game blind to what physics produced.

Scope

  • In:
    • New trait RapierClusterSimulation (sibling to ClusterSimulation) with extended context.
    • RapierClusterTickContext<'a> β€” ClusterTickContext fields plus contact_events: &[ContactEvent].
    • Enum RapierColliderShape β€” Ball(f32) | Capsule { half_height, radius } | Cuboid([f32; 3]).
    • Method RapierClusterSimulation::collider_for(&self, entry: &EntityStateEntry) -> RapierColliderShape with default impl returning Ball(config.default_body_radius).
    • Constructor RapierClusterSim::with_rapier_sim(...) accepting the new trait.
    • Rapier ChannelEventCollector wired into step_with_accumulator; contacts mapped from ColliderHandle pairs back to entity_id pairs via the existing handle map.
  • Out (each its own follow-up issue):
    • Physics commands (impulses, forces, teleports).
    • Raycasts.
    • Joints.
    • Kinematic neighbor proxies (cross-cluster physics visibility).
    • World-bounds policy / boundary colliders.

Action Items

  • Define RapierClusterSimulation trait + RapierClusterTickContext in arcane-infra::rapier_cluster.
  • Add RapierColliderShape enum + private conversion to ColliderBuilder.
  • Add collider_for trait method with default impl.
  • Add RapierClusterSim::with_rapier_sim constructor; preserve V1 new constructor.
  • Wire contact event collector; map handle pairs back to (Uuid, Uuid) per tick.
  • Tests:
    • Two spheres collide β†’ contact event surfaces with both ids and started: true.
    • Two non-overlapping capsules don't generate contacts.
    • Box-on-box stack settles under gravity (sanity check for non-sphere shapes).
    • collider_for is honored at first-sight spawn.
    • Shape change after first-sight is ignored (matches first-sight-only contract from V1; documented).
    • Contact events drained correctly across ticks β€” no duplicates, no losses, paired started/stopped.
  • Update module docs with new trait and shape examples.
  • Clippy clean both with and without feature.
Design notes

Why a sibling trait, not extending ClusterSimulation

ClusterSimulation and ClusterTickContext live in arcane-core, which can't depend on Rapier. Extending the existing trait would either pull Rapier into core (no) or make new fields conditional with feature gymnastics that leak into core (no). Sibling trait in arcane-infra::rapier_cluster is clean β€” the wrapper accepts either trait via separate constructors; users opt in by implementing the richer trait.

Why collider shape is a method, not user_data schema

A method gives full programmatic control (per-entity, per-class, per-state) with zero schema overhead. A schema-driven version remains possible later but isn't needed for this surface.

Why first-sight-only for shape

Same reason as V1's first-sight-only spawn position: changing collider shape mid-life requires destroying and re-creating the body in Rapier. Users who need that go through despawn/respawn β€” same escape hatch as V1's teleport story.

Industry terms

  • Physics tick lifecycle hooks β€” Unreal PrePhysicsTick / PostPhysicsTick, Bevy PhysicsSet::*.
  • Contact event listener / observer β€” Box2D b2ContactListener, PhysX PxSimulationEventCallback. Standard names; we use them.

Reference

  • Parent EPIC: #8 β€” Cluster physics backends β€” Unreal (Chaos) first, multi-engine path
  • Strategic context: #33 β€” Engine-specific node types β€” heterogeneous physics tiers
  • Depends on: #117 β€” Rapier cluster physics: minimum integration
  • Architecture doc: docs/architecture/physics-backends-and-unreal.md β€” Β§6 entity/body mapping, Β§9 testing expectations

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions