Quick Summary
- 🟡 Status: ready to start, not blocked. Builds on
#117 / #118. No external epic dependencies — all work is internal to arcane-infra::rapier_cluster.
- Add per-entity spawn-time customization hooks to
RapierClusterSimulation: body_kind_for, material_for, collision_groups_for, is_sensor. Currently every spawned entity is a Dynamic body with default friction / restitution / density and no filtering.
- Closes the gap between "Rapier integration exists" and "Rapier integration can model the entities a real game has" — without these hooks, every entity is identical at the physics level.
- Aligns with the unified-entity model documented in entity-model.md: structures = entities with
body_kind = Fixed, players = Dynamic, kinematic platforms = KinematicPositionBased, etc.
- ⚠️ Scope-split call-out: introducing the
Fixed body-kind variant here does not solve the spatial-binding clustering question. Until the (unfiled) clustering-binding epic lands, Fixed entities still migrate by PGP affinity — physics-side they behave correctly (solver-skipped, only AABB tracked); clustering-side they're not yet pinned to chunk ownership. Do not bolt spatial-binding logic into this PR.
Why This Matters
The existing Rapier integration treats every entity as a default-tuned Dynamic body. For a real game, this means:
- A wall built by a player would fall to the floor under gravity (it's Dynamic, not Fixed).
- An ice surface and a rubber surface have the same friction / restitution.
- A player projectile collides with the player who fired it (no collision filtering).
- A "trigger zone" pickup volume physically pushes anything that enters it (no sensor mode).
These are not edge cases — they're the basic vocabulary of physics-based gameplay. Without the hooks, the integration is correct for tech demos and unusable for games.
Scope
- In:
- Trait method
body_kind_for(&self, entry: &EntityStateEntry, config: &RapierConfig) -> RapierBodyKind with default Dynamic.
- Trait method
material_for(&self, entry: &EntityStateEntry, config: &RapierConfig) -> RapierMaterial with default zero-friction / zero-restitution / unit-density.
- Trait method
collision_groups_for(&self, entry: &EntityStateEntry, config: &RapierConfig) -> RapierCollisionGroups with default "everything collides with everything" — concretely RapierCollisionGroups { memberships: Group::ALL, filter: Group::ALL }, equivalent to Rapier's InteractionGroups::all().
- Trait method
is_sensor(&self, entry: &EntityStateEntry, config: &RapierConfig) -> bool with default false.
- New public types:
RapierBodyKind { Dynamic | KinematicPositionBased | KinematicVelocityBased | Fixed }, RapierMaterial { friction, restitution, density }, RapierCollisionGroups { memberships, filter }.
- All hooks called exactly once per entity at first-sight spawn (same lifecycle contract as
collider_for).
- Tests demonstrating each hook's effect via direct Rapier-state inspection.
- Out (separate issues):
- In-tick imperative ops (impulses, raycasts) — separate issue (
#121).
- Cluster-binding-aware migration for Fixed entities — clustering-binding epic (not yet filed). This PR introduces the variant only; physics-side semantics work, clustering-side semantics will be wired by the binding epic.
- Map / terrain loading —
#119.
Action Items
Reference
- Parent EPIC:
#8 — Cluster physics backends
- Builds on:
#117 (minimum integration), #118 (contact events + colliders)
- Architecture doc:
docs/architecture/entity-model.md — body kinds (§4), subclass vs property-value polymorphism (§5), affinity-bound vs spatial-bound binding (§6)
- Downstream:
brainy-bots/arcane-demos#6 (clustering visualization demo) needs Fixed for items, sensor for trigger volumes, collision groups for projectiles
- Unblocks: real-game-shape entities (walls as Fixed, projectiles as Dynamic with collision filtering, trigger zones as sensors)
Quick Summary
#117/#118. No external epic dependencies — all work is internal toarcane-infra::rapier_cluster.RapierClusterSimulation:body_kind_for,material_for,collision_groups_for,is_sensor. Currently every spawned entity is aDynamicbody with default friction / restitution / density and no filtering.body_kind = Fixed, players =Dynamic, kinematic platforms =KinematicPositionBased, etc.Fixedbody-kind variant here does not solve the spatial-binding clustering question. Until the (unfiled) clustering-binding epic lands,Fixedentities still migrate by PGP affinity — physics-side they behave correctly (solver-skipped, only AABB tracked); clustering-side they're not yet pinned to chunk ownership. Do not bolt spatial-binding logic into this PR.Why This Matters
The existing Rapier integration treats every entity as a default-tuned Dynamic body. For a real game, this means:
These are not edge cases — they're the basic vocabulary of physics-based gameplay. Without the hooks, the integration is correct for tech demos and unusable for games.
Scope
body_kind_for(&self, entry: &EntityStateEntry, config: &RapierConfig) -> RapierBodyKindwith defaultDynamic.material_for(&self, entry: &EntityStateEntry, config: &RapierConfig) -> RapierMaterialwith default zero-friction / zero-restitution / unit-density.collision_groups_for(&self, entry: &EntityStateEntry, config: &RapierConfig) -> RapierCollisionGroupswith default "everything collides with everything" — concretelyRapierCollisionGroups { memberships: Group::ALL, filter: Group::ALL }, equivalent to Rapier'sInteractionGroups::all().is_sensor(&self, entry: &EntityStateEntry, config: &RapierConfig) -> boolwith defaultfalse.RapierBodyKind { Dynamic | KinematicPositionBased | KinematicVelocityBased | Fixed },RapierMaterial { friction, restitution, density },RapierCollisionGroups { memberships, filter }.collider_for).#121).#119.Action Items
RapierBodyKind,RapierMaterial,RapierCollisionGroupspublic types (with#[non_exhaustive]).body_kind_forhook with default impl.material_forhook with default impl.collision_groups_forhook with default impl returningRapierCollisionGroups { memberships: Group::ALL, filter: Group::ALL }.is_sensorhook with default impl.RapierState::spawnto consult all four hooks at first-sight.Fixedentity stays put under gravity (verify body kind honored). Test must set non-default gravity, e.g.RapierConfig { gravity: [0.0, -9.81, 0.0], ..Default::default() }— the crate's default gravity is[0,0,0]for benchmark parity.KinematicPositionBasedentity ignores forces; user-set position takes effect.collider_for).# Exampleextended. Add a short note that perentity-model.md§5, users may organize theirRapierClusterSimulationeither as a property-value-style impl that matches onentry.user_datato return the right hook values, or as a subclass-style set of impls dispatched by the user's own per-entity routing. The hook signatures support both — both should be documented.arcane_infracrate root (lib.rs— append to the existingrapier_cluster::{...}re-export block).Reference
#8— Cluster physics backends#117(minimum integration),#118(contact events + colliders)docs/architecture/entity-model.md— body kinds (§4), subclass vs property-value polymorphism (§5), affinity-bound vs spatial-bound binding (§6)brainy-bots/arcane-demos#6(clustering visualization demo) needs Fixed for items, sensor for trigger volumes, collision groups for projectiles