Skip to main content

alice_physics/
world_participant.rs

1//! The contract a law signs to take part in [`crate::solver::PhysicsWorld`]'s
2//! substep loop: [`Participant`] and the small types around it.
3//!
4//! A participant owns its own state (a particle set, a grid, a pedestrian
5//! crowd, a force field) and advances it once per world substep. It reads the
6//! rigid bodies as they were at the start of the substep and hands its effect
7//! on them back as forces and torques in a [`ForceAccumulator`]; it never
8//! writes a body's position or velocity. Snapshots carry its state as an
9//! opaque payload tagged with its [`ParticipantKind`].
10//!
11//! **Unstable.** The types and rules of this module may still change before
12//! the participant API is declared stable, and such changes may ship in a
13//! minor release.
14//!
15//! # Contract
16//!
17//! | rule | how it is enforced |
18//! |---|---|
19//! | participants run in registration order, once per substep (before the rigid integration of that substep) | world side |
20//! | [`Participant::substep`] returning `Err` leaves the participant unchanged | participant side, checked by the conformance tests |
21//! | forces a participant staged in a substep that returned `Err` never reach the bodies | world side (staged per participant, merged on `Ok`) |
22//! | a participant cannot write a body | type: [`SubstepCtx`] only hands out `&[RigidBody]` and the accumulator |
23//! | [`Participant::check_state`] accepts every payload [`Participant::write_state`] produced, and [`Participant::read_state`] then restores the participant bit for bit | participant side, checked by the conformance tests |
24//! | a restore checks the target world holds the same number of participants, of the same kinds, in the same order | world side, [`ParticipantMismatch::classify`] |
25//! | participants run in the order [`execution_order`] gives for their [`Participant::ports`]: a writer of a port before its readers, registration order otherwise; with no ports declared that is registration order | world side calls [`execution_order`] at registration |
26//! | a loop of ports (a cycle) is refused at registration, never ordered silently | type: [`execution_order`] returns [`OrderError::Cycle`], the world returns it as [`RegisterError::Order`] |
27//! | the value behind a port that is not a shared field is state of the participant that holds it, so it is in that participant's [`Participant::write_state`] payload | participant side |
28//! | the summed force and torque reach a body through `v += F·h·inv_mass`, `ω += I⁻¹·τ·h`; a product out of range is a fault ([`WorldFault::ForceOutOfRange`]), never clamped | world side |
29//! | the rigid integration going out of range ([`WorldFault::RigidOverflow`]) is a fault like a participant's | world side |
30//! | a parked (sleeping) dynamic body is woken (and unparked) by participant forces whenever the change the world applies for the forces and torques of all participants, `Δv = F·inv_mass·h` or `Δω = I⁻¹τ·h`, is non-zero in any component; there is no threshold, so a participant force never leaves a sleeping body unchanged while the participant books its reaction; when both changes round to exactly zero the body stays asleep ([`wakes_parked_body`]; this replaces the earlier rule, which left a body asleep below the sleep thresholds) | world side calls [`wakes_parked_body`] |
31//! | a shared field ([`FieldBoard`]) is owned by the world alone; a participant reads the value committed before the substep began and writes only into its own [`FieldStage`] | type: [`SubstepCtx::field`] hands out `&[Fix128]` of the board, [`SubstepCtx::stage_field`] a buffer of the stage |
32//! | staged field writes are committed at the end of the substep, in [`execution_order`], all fields at once; a commit that would leave the range of [`Fix128`] changes no field and is a fault ([`WorldFault::FieldOutOfRange`]) | [`run_substep`] |
33//! | a participant that returned `Err` has its staged field writes dropped with its forces | [`run_substep`] |
34//! | a [`FieldMode::Replace`] field has at most one writer, a plain [`PortAccess::Read`] of a field and a [`PortAccess::ReadCommitted`] of a port that is not a field are refused at registration | [`check_field_ports`], returned as [`RegisterError::Field`] |
35//! | moving an extensive amount between layouts keeps its total exactly | [`remap_conserving`], [`deposit_bodies`] |
36//! | a snapshot holds the committed field values once, in the world's section, never in a participant payload | world side, bytes from [`FieldBoard::write_values`] |
37//! | participants can be registered only in builds with the `std` feature; a `no_std` world has no participants (the world-side participant API requires `std`) | world side |
38//!
39//! # Participant kinds
40//!
41//! A participant type in this crate takes as its [`ParticipantKind`] four
42//! ASCII bytes read big endian (`u32::from_be_bytes(*b"THRM")`), declared as
43//! a `pub const` next to the type. An assigned value never changes: a restore
44//! matches the participants of a snapshot to those of the world by kind
45//! value, so changing one makes every earlier snapshot refuse to load. Values
46//! below `0x0100_0000` (small integers) are left for tests and user code and
47//! are never assigned here.
48//!
49//! | code | value | type |
50//! |---|---|---|
51//! | `THRM` | `0x5448_524d` | `thermal::ThermalModifier` |
52//! | `PHAS` | `0x5048_4153` | `phase_change::PhaseChangeModifier` |
53//! | `PRES` | `0x5052_4553` | `pressure::PressureModifier` |
54//! | `FRAC` | `0x4652_4143` | `fracture::FractureModifier` |
55//! | `EROS` | `0x4552_4f53` | `erosion::ErosionModifier` |
56//! | `CRWD` | `0x4352_5744` | `crowd_force::CrowdParticipant` |
57//! | `MDVV` | `0x4d44_5656` | `molecular_dynamics::MdParticipant` |
58//! | `MEDM` | `0x4d45_444d` | `coupling_medium::DragMedium` |
59//!
60//! # Coupling through ports
61//!
62//! A participant declares the values it reads and writes as [`Port`]s
63//! ([`Participant::ports`], empty by default). A port is named by a
64//! [`PortId`], which, like [`ParticipantKind`], keeps its value across
65//! snapshots and versions. Within a substep, a participant that reads a port
66//! runs after every participant that writes it, so it sees the value of this
67//! substep. Participants that do not depend on each other keep their
68//! registration order. A participant that reads and writes the same port has
69//! no ordering constraint with itself.
70//!
71//! When the ports form a loop (A writes what B reads and B writes what A
72//! reads), no order lets every read see the value of this substep. The v1
73//! rule for such a loop is that its reads see the previous substep's values
74//! (explicit coupling); [`execution_order`] does not pick an order for it
75//! and returns [`OrderError::Cycle`] instead, so a loop is never coupled
76//! without being declared as one. How a participant declares a lagged read is
77//! not part of this version ([`PortAccess`] is `#[non_exhaustive]`). Implicit
78//! iteration between coupled participants is not part of this module either.
79//!
80//! # Shared fields
81//!
82//! A port whose value several participants exchange (a temperature on a grid,
83//! a heat source per body) is a *field* on the world's [`FieldBoard`]. A field
84//! is named by the same [`PortId`]: the id is what participants declare in
85//! [`Participant::ports`], so the execution order, the registration checks and
86//! the snapshot all speak of one name, and a port is either held by its
87//! participant (no field with that id) or held by the world (a field). The
88//! board, not a participant, owns the value, so a snapshot carries each field
89//! once however many participants use it.
90//!
91//! * **Values.** A field is an array of [`Fix128`] on a [`FieldLayout`]: one
92//!   sample per rigid body ([`FieldLayout::PerBody`], the way to hand a body a
93//!   quantity other than a force: heat, charge, deposited mass) or a uniform
94//!   grid of cubic cells ([`FieldLayout::Grid`]). A vector quantity is three
95//!   fields, one per component, so every rule below (sums, conservation,
96//!   snapshot) is the scalar one.
97//! * **Reads.** [`SubstepCtx::field`] returns the value committed at the end of
98//!   the previous substep, whoever runs first. A field read is declared as
99//!   [`Port::reads_committed`], which adds no ordering constraint, so two
100//!   participants may each read what the other writes (thermal ⇄ structure)
101//!   without forming a refused loop. The coupling through a field is explicit:
102//!   one substep of lag, the same as through the rigid bodies.
103//! * **Writes.** [`SubstepCtx::stage_field`] hands out the participant's own
104//!   staged buffer. Staged buffers are committed at the end of the substep in
105//!   [`execution_order`], all fields at once ([`run_substep`]); a participant
106//!   that returned `Err` has its staged writes dropped, as its forces are.
107//! * **Several writers.** A [`FieldMode::Replace`] field (a state some
108//!   participant integrates, e.g. a temperature) has one writer, checked at
109//!   registration; its staged buffer starts as the committed value and
110//!   replaces it. A [`FieldMode::Sum`] field (a load or source, e.g. Joule
111//!   heat) takes any number of writers; each staged buffer starts at zero and
112//!   the committed value is the sum of this substep's buffers, partial sums
113//!   taken in execution order and checked, so the first writer that leaves the
114//!   range is named the same way on every run. Nothing written in a substep
115//!   gives zero, like the force accumulator.
116//! * **Layouts.** Moving an extensive amount (heat in J per cell, not a
117//!   temperature) between nested grids or from bodies into a grid keeps the
118//!   total exactly ([`remap_conserving`], [`deposit_bodies`]); converting a
119//!   density to an amount is the participant's job.
120//! * **Snapshot.** Proposed section of snapshot format version 2, after
121//!   `participants` and `fault`: `fields` = [`FieldBoard::write_values`]
122//!   (`count: u64`, then per field in ascending id: `id: u32`, `mode: u8`,
123//!   `layout: u8` and its parameters, the values as `hi: i64`, `lo: u64`
124//!   little endian). Staged buffers are empty between steps and are not
125//!   stored. A restore checks the layouts against the target world's board
126//!   ([`FieldBoard::check_values`]) before reading any value, next to the
127//!   participant checks. The `fault` section codes, with the field fault
128//!   added: `0` none, `1` [`WorldFault::Participant`] (`index: u64`,
129//!   `kind: u32`, fault tag `u8`), `2` [`WorldFault::RigidOverflow`], `3`
130//!   [`WorldFault::ForceOutOfRange`] (`body: u64`), `4`
131//!   [`WorldFault::FieldOutOfRange`] (`field: u32`, `index: u64`).
132//!
133//! A participant cannot write a body because [`SubstepCtx`] has no way to
134//! reach one mutably:
135//!
136//! ```compile_fail
137//! use alice_physics::world_participant::{ForceAccumulator, SubstepCtx};
138//! use alice_physics::{Fix128, RigidBody, Vec3Fix};
139//!
140//! let bodies = [RigidBody::new_dynamic(Vec3Fix::ZERO, Fix128::ONE)];
141//! let mut forces = ForceAccumulator::new(1);
142//! let ctx = SubstepCtx::new(&bodies, &mut forces, 0, 1, Fix128::ONE).unwrap();
143//! ctx.bodies()[0].velocity = Vec3Fix::ZERO; // `bodies()` is `&[RigidBody]`
144//! ```
145
146use crate::math::{Fix128, Vec3Fix};
147use crate::sleeping::SleepConfig;
148use crate::solver::RigidBody;
149
150#[cfg(not(feature = "std"))]
151use alloc::{boxed::Box, vec, vec::Vec};
152
153/// Stable tag of a participant type, written in front of its snapshot
154/// payload. A type keeps its tag across versions.
155#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
156pub struct ParticipantKind(u32);
157
158impl ParticipantKind {
159    /// The tag `raw`.
160    #[must_use]
161    pub const fn new(raw: u32) -> Self {
162        Self(raw)
163    }
164
165    /// The raw tag.
166    #[must_use]
167    pub const fn get(self) -> u32 {
168        self.0
169    }
170}
171
172/// Stable name of a value participants exchange (a field, a set of loads).
173/// A port keeps its id across snapshots and versions.
174#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
175pub struct PortId(u32);
176
177impl PortId {
178    /// The port `raw`.
179    #[must_use]
180    pub const fn new(raw: u32) -> Self {
181        Self(raw)
182    }
183
184    /// The raw id.
185    #[must_use]
186    pub const fn get(self) -> u32 {
187        self.0
188    }
189}
190
191/// Whether a participant reads or writes a port.
192#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
193#[non_exhaustive]
194pub enum PortAccess {
195    /// Reads the value of the current substep (runs after every writer).
196    Read,
197    /// Writes the value (runs before every reader).
198    Write,
199    /// Reads a shared field's value as committed at the end of the previous
200    /// substep. Adds no ordering constraint; the port must be a field of the
201    /// world's [`FieldBoard`].
202    ReadCommitted,
203}
204
205/// One declared access of a participant to a port.
206#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
207pub struct Port {
208    id: PortId,
209    access: PortAccess,
210}
211
212impl Port {
213    /// A read of `id`.
214    #[must_use]
215    pub const fn reads(id: PortId) -> Self {
216        Self {
217            id,
218            access: PortAccess::Read,
219        }
220    }
221
222    /// A read of the committed value of the shared field `id`.
223    #[must_use]
224    pub const fn reads_committed(id: PortId) -> Self {
225        Self {
226            id,
227            access: PortAccess::ReadCommitted,
228        }
229    }
230
231    /// A write of `id`.
232    #[must_use]
233    pub const fn writes(id: PortId) -> Self {
234        Self {
235            id,
236            access: PortAccess::Write,
237        }
238    }
239
240    /// The port.
241    #[must_use]
242    pub const fn id(self) -> PortId {
243        self.id
244    }
245
246    /// Read or write.
247    #[must_use]
248    pub const fn access(self) -> PortAccess {
249        self.access
250    }
251}
252
253/// Why the participants' ports admit no execution order.
254#[derive(Clone, Debug, PartialEq, Eq)]
255#[non_exhaustive]
256pub enum OrderError {
257    /// The ports form a loop within one substep.
258    Cycle {
259        /// Registration indices of every participant on a loop, ascending.
260        /// Participants that only depend on a loop are not listed.
261        members: Vec<usize>,
262    },
263}
264
265/// The order participants run in within a substep, from their declared
266/// ports: `ports[i]` is what [`Participant::ports`] returns for the
267/// participant registered at index `i`, and the result lists registration
268/// indices in run order.
269///
270/// A participant that writes a port runs before every other participant that
271/// reads it. Among participants free to run, the one registered first runs
272/// first, so with no ports at all the order is `0, 1, …, n-1`, and the result
273/// depends only on `ports`. Two writers of one port are not ordered against
274/// each other by that port.
275///
276/// # Errors
277///
278/// [`OrderError::Cycle`] when the dependencies form a loop; no order is
279/// chosen.
280pub fn execution_order(ports: &[&[Port]]) -> Result<Vec<usize>, OrderError> {
281    let n = ports.len();
282    // succ[w] = readers that must run after writer w
283    let mut succ: Vec<Vec<usize>> = vec![Vec::new(); n];
284    for (w, wp) in ports.iter().enumerate() {
285        for p in wp.iter().filter(|p| p.access == PortAccess::Write) {
286            for (r, rp) in ports.iter().enumerate() {
287                if r != w
288                    && rp
289                        .iter()
290                        .any(|q| q.access == PortAccess::Read && q.id == p.id)
291                {
292                    succ[w].push(r);
293                }
294            }
295        }
296    }
297    let mut indegree = vec![0_usize; n];
298    for s in &mut succ {
299        s.sort_unstable();
300        s.dedup();
301        for &r in s.iter() {
302            indegree[r] += 1;
303        }
304    }
305    let mut done = vec![false; n];
306    let mut order = Vec::with_capacity(n);
307    while let Some(next) = (0..n).find(|&i| !done[i] && indegree[i] == 0) {
308        done[next] = true;
309        order.push(next);
310        for &r in &succ[next] {
311            indegree[r] -= 1;
312        }
313    }
314    if order.len() == n {
315        return Ok(order);
316    }
317    // A participant left over is on a loop iff it reaches itself through
318    // other left-over participants.
319    let members = (0..n)
320        .filter(|&v| !done[v] && reaches_itself(v, &succ, &done))
321        .collect();
322    Err(OrderError::Cycle { members })
323}
324
325fn reaches_itself(start: usize, succ: &[Vec<usize>], done: &[bool]) -> bool {
326    let mut seen = vec![false; succ.len()];
327    let mut stack: Vec<usize> = succ[start].clone();
328    while let Some(v) = stack.pop() {
329        if v == start {
330            return true;
331        }
332        if done[v] || seen[v] {
333            continue;
334        }
335        seen[v] = true;
336        stack.extend_from_slice(&succ[v]);
337    }
338    false
339}
340
341/// How a participant's own time step relates to the world substep `h`.
342#[derive(Clone, Copy, Debug, PartialEq, Eq)]
343#[non_exhaustive]
344pub enum StepRule {
345    /// One call per world substep, advancing by `h`.
346    FollowSubstep,
347    /// The participant advances by a fixed `Δt` of its own; registration is
348    /// refused unless `h = n·Δt` for an integer `n ≥ 1` (exactly, in
349    /// [`Fix128`]). The world still calls it once per substep with `h`; the
350    /// participant takes `n` internal steps.
351    Fixed(Fix128),
352    /// One call per world substep with `h`; the participant splits `h`
353    /// itself, deterministically from its own state (e.g. a CFL limit).
354    Subcycle,
355}
356
357impl StepRule {
358    /// Internal steps per world substep of width `h`, or why this rule cannot
359    /// follow `h`. `FollowSubstep` and `Subcycle` give 1 (the participant is
360    /// called once); `Fixed(Δt)` gives `n` with `h = n·Δt`.
361    ///
362    /// # Errors
363    ///
364    /// [`RegisterError::NonPositiveStep`] when `h ≤ 0` or `Δt ≤ 0`;
365    /// [`RegisterError::StepRuleMismatch`] when `h` is not an integer multiple
366    /// of `Δt`.
367    pub fn steps_per_substep(self, h: Fix128) -> Result<u64, RegisterError> {
368        if h <= Fix128::ZERO {
369            return Err(RegisterError::NonPositiveStep);
370        }
371        match self {
372            Self::FollowSubstep | Self::Subcycle => Ok(1),
373            Self::Fixed(step) => {
374                if step <= Fix128::ZERO {
375                    return Err(RegisterError::NonPositiveStep);
376                }
377                let n = h / step;
378                let whole = n.lo == 0 && n.hi >= 1;
379                if whole && Fix128::from_int(n.hi) * step == h {
380                    Ok(n.hi.unsigned_abs())
381                } else {
382                    Err(RegisterError::StepRuleMismatch { substep: h, step })
383                }
384            }
385        }
386    }
387}
388
389/// Why a participant could not be registered.
390#[derive(Clone, Debug, PartialEq, Eq)]
391#[non_exhaustive]
392pub enum RegisterError {
393    /// The world substep or the participant's own step is not positive.
394    NonPositiveStep,
395    /// `StepRule::Fixed(step)` does not divide the world substep.
396    StepRuleMismatch {
397        /// World substep `h`.
398        substep: Fix128,
399        /// The participant's fixed step.
400        step: Fix128,
401    },
402    /// With the new participant the declared ports form a loop
403    /// ([`execution_order`]); the participant was not registered.
404    Order(OrderError),
405    /// The declared ports do not fit the world's shared fields
406    /// ([`check_field_ports`]); the participant was not registered.
407    Field(FieldPortError),
408}
409
410/// Why a participant's substep failed. The participant is unchanged and its
411/// forces of that substep are dropped; the world records the fault.
412#[derive(Clone, Copy, Debug, PartialEq, Eq)]
413#[non_exhaustive]
414pub enum ParticipantFault {
415    /// A value left the representable or valid range.
416    OutOfRange,
417    /// The participant found its own state inconsistent.
418    InvalidState,
419}
420
421impl ParticipantFault {
422    /// Snapshot tag of this fault.
423    #[must_use]
424    pub const fn tag(self) -> u8 {
425        match self {
426            Self::OutOfRange => 1,
427            Self::InvalidState => 2,
428        }
429    }
430
431    /// The fault with snapshot tag `tag`, if any.
432    #[must_use]
433    pub const fn from_tag(tag: u8) -> Option<Self> {
434        match tag {
435            1 => Some(Self::OutOfRange),
436            2 => Some(Self::InvalidState),
437            _ => None,
438        }
439    }
440}
441
442/// The first fault the world recorded (sticky until cleared).
443#[derive(Clone, Copy, Debug, PartialEq, Eq)]
444#[non_exhaustive]
445pub enum WorldFault {
446    /// A participant's substep returned `Err`.
447    Participant {
448        /// Registration index of the participant that failed.
449        index: usize,
450        /// Its kind.
451        kind: ParticipantKind,
452        /// What failed.
453        fault: ParticipantFault,
454    },
455    /// The rigid integration (position prediction, velocity from positions)
456    /// left the range of [`Fix128`]; the world's overflow flag in fault form.
457    /// It names no body because the flag does not.
458    ///
459    /// The flag covers only part of the products today: the XPBD position
460    /// prediction and the velocity derived from jointed positions. The TGS
461    /// path does not raise it, and a wrapping addition of a position (XPBD or
462    /// TGS) is not detected.
463    RigidOverflow,
464    /// Applying the summed participant force or torque to body `body`
465    /// (`F·h·inv_mass`, `I⁻¹·τ·h`) left the range of [`Fix128`]. Nothing is
466    /// clamped.
467    ForceOutOfRange {
468        /// Index of the body.
469        body: usize,
470    },
471    /// Committing the staged writes of a [`FieldMode::Sum`] field left the
472    /// range of [`Fix128`] when the writes of participant `index` were added.
473    /// No field changed in that substep.
474    FieldOutOfRange {
475        /// The field.
476        field: PortId,
477        /// Registration index of the participant whose write left the range.
478        index: usize,
479    },
480}
481
482/// Why a checked world step did not run normally.
483#[derive(Clone, Debug, PartialEq, Eq)]
484#[non_exhaustive]
485pub enum StepError {
486    /// A fault was already recorded; the world was not touched.
487    Faulted(WorldFault),
488    /// A participant failed during this step. The step ran to the end (the
489    /// failed participant was frozen at its state before the failing call and
490    /// its forces of that substep were dropped); the fault is now recorded.
491    FaultRaised(WorldFault),
492    /// The participant at `index` has a [`StepRule::Fixed`] step that does not
493    /// divide this frame's substep width, the one the running path hands its
494    /// participants (`dt / substeps`, or on `PhysicsWorld::try_step` under
495    /// `SolverBackend::Tgs` the width its solve uses); nothing ran. The step is
496    /// only known when the world steps, so the check runs at the start of
497    /// every step rather than at registration.
498    Rule {
499        /// Registration index of the participant.
500        index: usize,
501        /// Why its rule cannot follow the substep.
502        error: RegisterError,
503    },
504    /// The [`FieldLayout::PerBody`] field `field` has `samples` samples but
505    /// the world holds `bodies` bodies (a body was added or removed after the
506    /// field was declared): the step is refused and the world is left
507    /// unchanged, nothing ran. The world-side form of
508    /// [`ExchangeError::BodyCount`], checked at the start of every step like
509    /// [`Self::Rule`].
510    BodyCount {
511        /// The field.
512        field: PortId,
513        /// Samples of the field.
514        samples: usize,
515        /// Bodies in the world.
516        bodies: usize,
517    },
518}
519
520/// Why a participant rejected a snapshot payload.
521#[derive(Clone, Copy, Debug, PartialEq, Eq)]
522#[non_exhaustive]
523pub enum StateError {
524    /// The payload has a different length than the participant needs.
525    Length {
526        /// Bytes needed.
527        expected: usize,
528        /// Bytes given.
529        found: usize,
530    },
531    /// A value in the payload is not valid for the participant.
532    InvalidValue,
533}
534
535/// Why a restore refused the participants of the target world.
536#[derive(Clone, Copy, Debug, PartialEq, Eq)]
537#[non_exhaustive]
538pub enum ParticipantMismatch {
539    /// Different numbers of participants.
540    Count {
541        /// Participants in the snapshot.
542        snapshot: usize,
543        /// Participants in the target world.
544        world: usize,
545    },
546    /// The same number, but not the same kinds (as multisets).
547    Kind {
548        /// First index whose kind differs.
549        index: usize,
550        /// Kind in the snapshot.
551        snapshot: ParticipantKind,
552        /// Kind in the target world.
553        world: ParticipantKind,
554    },
555    /// The same kinds, in another order.
556    Order {
557        /// First index whose kind differs.
558        index: usize,
559        /// Kind in the snapshot.
560        snapshot: ParticipantKind,
561        /// Kind in the target world.
562        world: ParticipantKind,
563    },
564}
565
566impl ParticipantMismatch {
567    /// Compare the kinds stored in a snapshot with those registered in the
568    /// target world. `None` when they agree (same count, same kinds, same
569    /// order).
570    #[must_use]
571    pub fn classify(snapshot: &[ParticipantKind], world: &[ParticipantKind]) -> Option<Self> {
572        if snapshot.len() != world.len() {
573            return Some(Self::Count {
574                snapshot: snapshot.len(),
575                world: world.len(),
576            });
577        }
578        let index = snapshot.iter().zip(world).position(|(s, w)| s != w)?;
579        let (s, w) = (snapshot[index], world[index]);
580        let mut a: Vec<ParticipantKind> = snapshot.to_vec();
581        let mut b: Vec<ParticipantKind> = world.to_vec();
582        a.sort_unstable();
583        b.sort_unstable();
584        Some(if a == b {
585            Self::Order {
586                index,
587                snapshot: s,
588                world: w,
589            }
590        } else {
591            Self::Kind {
592                index,
593                snapshot: s,
594                world: w,
595            }
596        })
597    }
598}
599
600/// Why a force could not be staged.
601#[derive(Clone, Copy, Debug, PartialEq, Eq)]
602#[non_exhaustive]
603pub enum AccumulateError {
604    /// The body index is past the end of the body list.
605    BodyOutOfRange {
606        /// The index.
607        index: usize,
608        /// Number of bodies.
609        len: usize,
610    },
611    /// The accumulator does not have one slot per body.
612    LengthMismatch {
613        /// Slots in the accumulator.
614        slots: usize,
615        /// Bodies.
616        bodies: usize,
617    },
618}
619
620/// Per-body sums of force and torque (world frame, [`Fix128`]) for one
621/// substep.
622///
623/// The sums use the wrapping addition of [`Fix128`], which is associative and
624/// commutative, so the totals do not depend on the order the forces were
625/// added in. The world still fixes the order (registration order, then each
626/// participant's own order) so that any later checked accumulation stays
627/// deterministic.
628#[derive(Clone, Debug, PartialEq, Eq)]
629pub struct ForceAccumulator {
630    force: Vec<Vec3Fix>,
631    torque: Vec<Vec3Fix>,
632}
633
634impl ForceAccumulator {
635    /// Zero force and torque for `bodies` bodies.
636    #[must_use]
637    pub fn new(bodies: usize) -> Self {
638        Self {
639            force: vec![Vec3Fix::ZERO; bodies],
640            torque: vec![Vec3Fix::ZERO; bodies],
641        }
642    }
643
644    /// Number of body slots.
645    #[must_use]
646    pub fn len(&self) -> usize {
647        self.force.len()
648    }
649
650    /// Whether there are no body slots.
651    #[must_use]
652    pub fn is_empty(&self) -> bool {
653        self.force.is_empty()
654    }
655
656    fn slot(&self, body: usize) -> Result<usize, AccumulateError> {
657        if body < self.force.len() {
658            Ok(body)
659        } else {
660            Err(AccumulateError::BodyOutOfRange {
661                index: body,
662                len: self.force.len(),
663            })
664        }
665    }
666
667    /// Add `force` to body `body`.
668    ///
669    /// # Errors
670    ///
671    /// [`AccumulateError::BodyOutOfRange`] when `body` has no slot (nothing
672    /// is added).
673    pub fn add_force(&mut self, body: usize, force: Vec3Fix) -> Result<(), AccumulateError> {
674        let i = self.slot(body)?;
675        self.force[i] = self.force[i] + force;
676        Ok(())
677    }
678
679    /// Add `torque` to body `body`.
680    ///
681    /// # Errors
682    ///
683    /// [`AccumulateError::BodyOutOfRange`] when `body` has no slot (nothing
684    /// is added).
685    pub fn add_torque(&mut self, body: usize, torque: Vec3Fix) -> Result<(), AccumulateError> {
686        let i = self.slot(body)?;
687        self.torque[i] = self.torque[i] + torque;
688        Ok(())
689    }
690
691    /// Summed force on `body` (`None` past the end).
692    #[must_use]
693    pub fn force(&self, body: usize) -> Option<Vec3Fix> {
694        self.force.get(body).copied()
695    }
696
697    /// Summed torque on `body` (`None` past the end).
698    #[must_use]
699    pub fn torque(&self, body: usize) -> Option<Vec3Fix> {
700        self.torque.get(body).copied()
701    }
702
703    /// Add every slot of `other` into `self`, slot by slot.
704    ///
705    /// # Errors
706    ///
707    /// [`AccumulateError::LengthMismatch`] when the slot counts differ
708    /// (`self` is unchanged).
709    pub fn merge(&mut self, other: &Self) -> Result<(), AccumulateError> {
710        if other.len() != self.len() {
711            return Err(AccumulateError::LengthMismatch {
712                slots: other.len(),
713                bodies: self.len(),
714            });
715        }
716        for i in 0..self.len() {
717            self.force[i] = self.force[i] + other.force[i];
718            self.torque[i] = self.torque[i] + other.torque[i];
719        }
720        Ok(())
721    }
722
723    /// Reset every slot to zero, keeping the slot count.
724    pub fn clear(&mut self) {
725        self.force.fill(Vec3Fix::ZERO);
726        self.torque.fill(Vec3Fix::ZERO);
727    }
728}
729
730/// What a participant sees during one substep: the bodies as they were at the
731/// start of the substep (read only) and an accumulator for its forces.
732#[derive(Debug)]
733pub struct SubstepCtx<'a> {
734    bodies: &'a [RigidBody],
735    forces: &'a mut ForceAccumulator,
736    substep_index: usize,
737    substeps: usize,
738    h: Fix128,
739    board: Option<&'a FieldBoard>,
740    stage: Option<&'a mut FieldStage>,
741    ports: &'a [Port],
742    sdf_contacts: &'a [crate::sdf_collider::SdfContact],
743}
744
745impl<'a> SubstepCtx<'a> {
746    /// A context over `bodies`, staging into `forces` (one slot per body),
747    /// for substep `substep_index` of `substeps` with width `h`.
748    ///
749    /// # Errors
750    ///
751    /// [`AccumulateError::LengthMismatch`] when `forces` does not have one
752    /// slot per body.
753    pub fn new(
754        bodies: &'a [RigidBody],
755        forces: &'a mut ForceAccumulator,
756        substep_index: usize,
757        substeps: usize,
758        h: Fix128,
759    ) -> Result<Self, AccumulateError> {
760        if forces.len() != bodies.len() {
761            return Err(AccumulateError::LengthMismatch {
762                slots: forces.len(),
763                bodies: bodies.len(),
764            });
765        }
766        Ok(Self {
767            bodies,
768            forces,
769            substep_index,
770            substeps,
771            h,
772            board: None,
773            stage: None,
774            ports: &[],
775            sdf_contacts: &[],
776        })
777    }
778
779    /// The same context with the world's shared fields: reads come from
780    /// `board` (committed values), writes go to `stage`, and only the ports in
781    /// `ports` (what the participant declared) may be read or written.
782    #[must_use]
783    pub fn with_fields(
784        mut self,
785        board: &'a FieldBoard,
786        stage: &'a mut FieldStage,
787        ports: &'a [Port],
788    ) -> Self {
789        self.board = Some(board);
790        self.stage = Some(stage);
791        self.ports = ports;
792        self
793    }
794
795    /// The same context with the SDF contacts the world resolved so far in
796    /// the current step ([`Self::sdf_contacts`]).
797    #[must_use]
798    pub fn with_sdf_contacts(mut self, contacts: &'a [crate::sdf_collider::SdfContact]) -> Self {
799        self.sdf_contacts = contacts;
800        self
801    }
802
803    fn declared(&self, id: PortId, access: PortAccess) -> Result<(), FieldAccessError> {
804        if self.ports.iter().any(|p| p.id == id && p.access == access) {
805            Ok(())
806        } else {
807            Err(FieldAccessError::NotDeclared { field: id, access })
808        }
809    }
810
811    /// The value of field `id` committed before this substep began.
812    ///
813    /// # Errors
814    ///
815    /// [`FieldAccessError::Unknown`] when there is no such field (or no
816    /// board); [`FieldAccessError::NotDeclared`] when the participant did not
817    /// declare [`Port::reads_committed`] for it.
818    pub fn field(&self, id: PortId) -> Result<&'a [Fix128], FieldAccessError> {
819        let board = self.board.ok_or(FieldAccessError::Unknown(id))?;
820        let slot = board.slot(id).ok_or(FieldAccessError::Unknown(id))?;
821        self.declared(id, PortAccess::ReadCommitted)?;
822        Ok(&slot.value)
823    }
824
825    /// The participant's staged buffer for field `id`, created on first use:
826    /// a copy of the committed value for a [`FieldMode::Replace`] field,
827    /// zeros for a [`FieldMode::Sum`] field. What it holds when the substep
828    /// returns `Ok` is committed at the end of the substep.
829    ///
830    /// # Errors
831    ///
832    /// [`FieldAccessError::Unknown`] when there is no such field (or no
833    /// board); [`FieldAccessError::NotDeclared`] when the participant did not
834    /// declare [`Port::writes`] for it.
835    pub fn stage_field(&mut self, id: PortId) -> Result<&mut [Fix128], FieldAccessError> {
836        let board = self.board.ok_or(FieldAccessError::Unknown(id))?;
837        let slot = board.slot(id).ok_or(FieldAccessError::Unknown(id))?;
838        self.declared(id, PortAccess::Write)?;
839        let stage = self
840            .stage
841            .as_deref_mut()
842            .ok_or(FieldAccessError::Unknown(id))?;
843        Ok(stage.buffer(id, || match slot.mode {
844            FieldMode::Replace => slot.value.clone(),
845            FieldMode::Sum => vec![Fix128::ZERO; slot.value.len()],
846        }))
847    }
848
849    /// The bodies at the start of this substep. With TGS (one detection per
850    /// tick) as well as XPBD, positions and velocities are those at the head
851    /// of the substep.
852    #[must_use]
853    pub fn bodies(&self) -> &[RigidBody] {
854        self.bodies
855    }
856
857    /// The SDF contacts the world resolved in the current step before this
858    /// participant runs, in the order they were applied (see
859    /// [`crate::PhysicsWorld::last_step_sdf_contacts`]).
860    ///
861    /// Participants run at the head of each substep, before its SDF
862    /// resolution, so with the XPBD paths (`step`, `step_parallel`,
863    /// `step_with_bridge`) substep `i` sees the contacts of substeps
864    /// `0..i` of this step (none in substep 0). The TGS backend resolves SDF
865    /// overlap once, before its substeps, so every substep sees all of the
866    /// step's contacts. Empty for a context built by [`Self::new`] alone.
867    #[must_use]
868    pub fn sdf_contacts(&self) -> &'a [crate::sdf_collider::SdfContact] {
869        self.sdf_contacts
870    }
871
872    /// Index of this substep within the frame (`0..substeps`).
873    #[must_use]
874    pub fn substep_index(&self) -> usize {
875        self.substep_index
876    }
877
878    /// Substeps in the frame.
879    #[must_use]
880    pub fn substeps(&self) -> usize {
881        self.substeps
882    }
883
884    /// Width of this substep.
885    #[must_use]
886    pub fn h(&self) -> Fix128 {
887        self.h
888    }
889
890    /// Stage `force` on body `body` for this substep.
891    ///
892    /// # Errors
893    ///
894    /// [`AccumulateError::BodyOutOfRange`] for a body that does not exist.
895    pub fn add_force(&mut self, body: usize, force: Vec3Fix) -> Result<(), AccumulateError> {
896        self.forces.add_force(body, force)
897    }
898
899    /// Stage `torque` on body `body` for this substep.
900    ///
901    /// # Errors
902    ///
903    /// [`AccumulateError::BodyOutOfRange`] for a body that does not exist.
904    pub fn add_torque(&mut self, body: usize, torque: Vec3Fix) -> Result<(), AccumulateError> {
905        self.forces.add_torque(body, torque)
906    }
907}
908
909/// A value read from the world: exact, or undecided because a fault (a value
910/// out of range) was recorded.
911#[derive(Clone, Copy, Debug, PartialEq, Eq)]
912pub enum Observed<T> {
913    /// The exact value.
914    Exact(T),
915    /// No value can be stated: the world recorded a fault.
916    Undecided,
917}
918
919/// Three-valued outcome of a predicate over an [`Observed`] value.
920#[derive(Clone, Copy, Debug, PartialEq, Eq)]
921pub enum Verdict {
922    /// The value is exact and the predicate holds.
923    Holds,
924    /// The value is exact and the predicate does not hold.
925    Violated,
926    /// The value is undecided.
927    Undecided,
928}
929
930impl<T> Observed<T> {
931    /// Evaluate `predicate` on the exact value; [`Verdict::Undecided`] when
932    /// there is none.
933    pub fn verdict(&self, predicate: impl FnOnce(&T) -> bool) -> Verdict {
934        match self {
935            Self::Exact(v) if predicate(v) => Verdict::Holds,
936            Self::Exact(_) => Verdict::Violated,
937            Self::Undecided => Verdict::Undecided,
938        }
939    }
940}
941
942/// Channels a participant reports from [`Participant::observe`], in the order
943/// it pushed them.
944#[derive(Clone, Debug, Default, PartialEq, Eq)]
945pub struct ObservationSink {
946    values: Vec<(u32, Fix128)>,
947}
948
949impl ObservationSink {
950    /// An empty sink.
951    #[must_use]
952    pub fn new() -> Self {
953        Self::default()
954    }
955
956    /// Report `value` on `channel`.
957    pub fn push(&mut self, channel: u32, value: Fix128) {
958        self.values.push((channel, value));
959    }
960
961    /// Every reported `(channel, value)`, in push order.
962    #[must_use]
963    pub fn values(&self) -> &[(u32, Fix128)] {
964        &self.values
965    }
966}
967
968/// A law that advances its own state inside the world's substep loop.
969///
970/// See the module documentation for the contract.
971pub trait Participant: Send {
972    /// Snapshot tag of this participant type.
973    fn kind(&self) -> ParticipantKind;
974
975    /// How this participant's time step relates to the world substep.
976    fn step_rule(&self) -> StepRule {
977        StepRule::FollowSubstep
978    }
979
980    /// The ports this participant reads and writes, empty by default. The
981    /// world reads them once, at registration, to fix the execution order
982    /// ([`execution_order`]); they must not change while the participant is
983    /// registered.
984    fn ports(&self) -> &[Port] {
985        &[]
986    }
987
988    /// Advance by one world substep of width `h` (`ctx.h()`). Read bodies
989    /// from `ctx`, stage forces on them through `ctx`.
990    ///
991    /// # Errors
992    ///
993    /// A [`ParticipantFault`]; the participant must then be exactly as it
994    /// was before the call (the forces it staged are dropped by the world).
995    fn substep(&mut self, ctx: &mut SubstepCtx<'_>, h: Fix128) -> Result<(), ParticipantFault>;
996
997    /// Report observable values. The world reports them as
998    /// [`Observed::Undecided`] while a fault is recorded.
999    fn observe(&self, out: &mut ObservationSink);
1000
1001    /// Append the whole state to `out`.
1002    fn write_state(&self, out: &mut Vec<u8>);
1003
1004    /// Check that `bytes` is a payload [`Self::read_state`] can apply. Must
1005    /// not change `self`.
1006    ///
1007    /// # Errors
1008    ///
1009    /// A [`StateError`] saying why the payload is unusable.
1010    fn check_state(&self, bytes: &[u8]) -> Result<(), StateError>;
1011
1012    /// Replace the state with `bytes`. Only called with a payload
1013    /// [`Self::check_state`] accepted.
1014    fn read_state(&mut self, bytes: &[u8]);
1015}
1016
1017// ============================================================================
1018// Shared fields
1019// ============================================================================
1020
1021/// Where the samples of a shared field sit.
1022#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1023#[non_exhaustive]
1024pub enum FieldLayout {
1025    /// One sample per rigid body, indexed like the world's body list.
1026    PerBody {
1027        /// Number of bodies.
1028        bodies: usize,
1029    },
1030    /// A uniform grid of cubic cells. Cell `(ix, iy, iz)` covers
1031    /// `origin + [ix, ix+1)·cell` (and so on per axis); samples are stored row
1032    /// major with `ix` fastest, `index = (iz·ny + iy)·nx + ix`.
1033    Grid {
1034        /// Lower corner of cell `(0, 0, 0)`.
1035        origin: Vec3Fix,
1036        /// Edge length of a cell (positive).
1037        cell: Fix128,
1038        /// Cells per axis `[nx, ny, nz]` (each at least 1).
1039        dims: [usize; 3],
1040    },
1041}
1042
1043impl FieldLayout {
1044    /// Number of samples, or `None` for a grid with a non-positive cell, an
1045    /// axis of 0 cells, or more samples than `usize` holds.
1046    #[must_use]
1047    pub fn samples(&self) -> Option<usize> {
1048        match *self {
1049            Self::PerBody { bodies } => Some(bodies),
1050            Self::Grid { cell, dims, .. } => {
1051                if cell <= Fix128::ZERO || dims.contains(&0) {
1052                    return None;
1053                }
1054                dims[0].checked_mul(dims[1])?.checked_mul(dims[2])
1055            }
1056        }
1057    }
1058}
1059
1060/// How the staged writes of one substep become the field's value.
1061#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1062#[non_exhaustive]
1063pub enum FieldMode {
1064    /// At most one writer; its staged buffer (starting as the committed value)
1065    /// replaces the value. Without a write in a substep the value stays.
1066    Replace,
1067    /// Any number of writers; each staged buffer starts at zero and the value
1068    /// becomes the sum of this substep's buffers (zero when nobody wrote).
1069    Sum,
1070}
1071
1072impl FieldMode {
1073    const fn tag(self) -> u8 {
1074        match self {
1075            Self::Replace => 0,
1076            Self::Sum => 1,
1077        }
1078    }
1079}
1080
1081/// Why a field could not be declared or set.
1082#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1083#[non_exhaustive]
1084pub enum FieldError {
1085    /// A field with this id is already declared.
1086    Duplicate(PortId),
1087    /// The layout has no valid sample count ([`FieldLayout::samples`]).
1088    InvalidLayout,
1089    /// No field with this id.
1090    Unknown(PortId),
1091    /// The values do not have one entry per sample.
1092    Length {
1093        /// Samples of the field.
1094        expected: usize,
1095        /// Values given.
1096        found: usize,
1097    },
1098}
1099
1100/// Why the declared ports do not fit the shared fields.
1101#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1102#[non_exhaustive]
1103pub enum FieldPortError {
1104    /// A [`FieldMode::Replace`] field declared written by two participants.
1105    SecondWriter {
1106        /// The field.
1107        field: PortId,
1108        /// Registration index of the first writer.
1109        first: usize,
1110        /// Registration index of the second writer.
1111        second: usize,
1112    },
1113    /// A plain [`PortAccess::Read`] of a field: a field read sees the
1114    /// committed value and must be declared [`PortAccess::ReadCommitted`].
1115    ReadOfField {
1116        /// Registration index of the participant.
1117        participant: usize,
1118        /// The field.
1119        field: PortId,
1120    },
1121    /// [`PortAccess::ReadCommitted`] of a port that is not a field.
1122    UnknownField {
1123        /// Registration index of the participant.
1124        participant: usize,
1125        /// The port.
1126        field: PortId,
1127    },
1128}
1129
1130/// Why a participant could not read or stage a field.
1131#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1132#[non_exhaustive]
1133pub enum FieldAccessError {
1134    /// No field with this id in the context.
1135    Unknown(PortId),
1136    /// The participant did not declare this access in [`Participant::ports`].
1137    NotDeclared {
1138        /// The field.
1139        field: PortId,
1140        /// The access it would need.
1141        access: PortAccess,
1142    },
1143}
1144
1145#[derive(Clone, Debug, PartialEq, Eq)]
1146struct FieldSlot {
1147    id: PortId,
1148    layout: FieldLayout,
1149    mode: FieldMode,
1150    value: Vec<Fix128>,
1151}
1152
1153/// The world's shared fields: committed values, owned by the world alone.
1154///
1155/// See the module documentation (*Shared fields*) for the rules.
1156#[derive(Clone, Debug, Default, PartialEq, Eq)]
1157pub struct FieldBoard {
1158    // ascending id
1159    fields: Vec<FieldSlot>,
1160}
1161
1162impl FieldBoard {
1163    /// A board without fields.
1164    #[must_use]
1165    pub fn new() -> Self {
1166        Self::default()
1167    }
1168
1169    fn slot(&self, id: PortId) -> Option<&FieldSlot> {
1170        self.fields
1171            .binary_search_by_key(&id, |f| f.id)
1172            .ok()
1173            .map(|i| &self.fields[i])
1174    }
1175
1176    /// Declare field `id`, all samples zero.
1177    ///
1178    /// # Errors
1179    ///
1180    /// [`FieldError::Duplicate`] for an id already declared,
1181    /// [`FieldError::InvalidLayout`] for a layout without a sample count; the
1182    /// board is unchanged.
1183    pub fn declare(
1184        &mut self,
1185        id: PortId,
1186        layout: FieldLayout,
1187        mode: FieldMode,
1188    ) -> Result<(), FieldError> {
1189        let n = layout.samples().ok_or(FieldError::InvalidLayout)?;
1190        match self.fields.binary_search_by_key(&id, |f| f.id) {
1191            Ok(_) => Err(FieldError::Duplicate(id)),
1192            Err(at) => {
1193                self.fields.insert(
1194                    at,
1195                    FieldSlot {
1196                        id,
1197                        layout,
1198                        mode,
1199                        value: vec![Fix128::ZERO; n],
1200                    },
1201                );
1202                Ok(())
1203            }
1204        }
1205    }
1206
1207    /// Set the committed value of field `id` (an initial condition, set
1208    /// between steps).
1209    ///
1210    /// # Errors
1211    ///
1212    /// [`FieldError::Unknown`] or [`FieldError::Length`]; the board is
1213    /// unchanged.
1214    pub fn set(&mut self, id: PortId, values: &[Fix128]) -> Result<(), FieldError> {
1215        let i = self
1216            .fields
1217            .binary_search_by_key(&id, |f| f.id)
1218            .map_err(|_| FieldError::Unknown(id))?;
1219        let slot = &mut self.fields[i];
1220        if values.len() != slot.value.len() {
1221            return Err(FieldError::Length {
1222                expected: slot.value.len(),
1223                found: values.len(),
1224            });
1225        }
1226        slot.value.copy_from_slice(values);
1227        Ok(())
1228    }
1229
1230    /// The committed value of field `id`.
1231    #[must_use]
1232    pub fn value(&self, id: PortId) -> Option<&[Fix128]> {
1233        self.slot(id).map(|f| f.value.as_slice())
1234    }
1235
1236    /// The layout of field `id`.
1237    #[must_use]
1238    pub fn layout(&self, id: PortId) -> Option<FieldLayout> {
1239        self.slot(id).map(|f| f.layout)
1240    }
1241
1242    /// The mode of field `id`.
1243    #[must_use]
1244    pub fn mode(&self, id: PortId) -> Option<FieldMode> {
1245        self.slot(id).map(|f| f.mode)
1246    }
1247
1248    /// Every declared field id, ascending.
1249    #[must_use]
1250    pub fn ids(&self) -> Vec<PortId> {
1251        self.fields.iter().map(|f| f.id).collect()
1252    }
1253
1254    /// Commit `stages` (registration index, stage) in run order. Either every
1255    /// field takes its new value or, on error, none changes.
1256    fn commit(&mut self, stages: &[(usize, FieldStage)]) -> Result<(), WorldFault> {
1257        let mut next: Vec<Vec<Fix128>> = Vec::with_capacity(self.fields.len());
1258        for slot in &self.fields {
1259            let mut v = match slot.mode {
1260                FieldMode::Replace => slot.value.clone(),
1261                FieldMode::Sum => vec![Fix128::ZERO; slot.value.len()],
1262            };
1263            for (index, stage) in stages {
1264                let Some(staged) = stage.staged(slot.id) else {
1265                    continue;
1266                };
1267                match slot.mode {
1268                    FieldMode::Replace => v.copy_from_slice(staged),
1269                    FieldMode::Sum => {
1270                        for (acc, add) in v.iter_mut().zip(staged) {
1271                            *acc = checked_add(*acc, *add).ok_or(WorldFault::FieldOutOfRange {
1272                                field: slot.id,
1273                                index: *index,
1274                            })?;
1275                        }
1276                    }
1277                }
1278            }
1279            next.push(v);
1280        }
1281        for (slot, v) in self.fields.iter_mut().zip(next) {
1282            slot.value = v;
1283        }
1284        Ok(())
1285    }
1286
1287    fn write_descriptor(slot: &FieldSlot, out: &mut Vec<u8>) {
1288        out.extend_from_slice(&slot.id.get().to_le_bytes());
1289        out.push(slot.mode.tag());
1290        match slot.layout {
1291            FieldLayout::PerBody { bodies } => {
1292                out.push(0);
1293                out.extend_from_slice(&(bodies as u64).to_le_bytes());
1294            }
1295            FieldLayout::Grid { origin, cell, dims } => {
1296                out.push(1);
1297                for f in [origin.x, origin.y, origin.z, cell] {
1298                    put_fix(out, f);
1299                }
1300                for d in dims {
1301                    out.extend_from_slice(&(d as u64).to_le_bytes());
1302                }
1303            }
1304        }
1305    }
1306
1307    /// Append every field (descriptor and committed values) to `out`, the
1308    /// bytes of the snapshot section `fields`.
1309    pub fn write_values(&self, out: &mut Vec<u8>) {
1310        out.extend_from_slice(&(self.fields.len() as u64).to_le_bytes());
1311        for slot in &self.fields {
1312            Self::write_descriptor(slot, out);
1313            for &f in &slot.value {
1314                put_fix(out, f);
1315            }
1316        }
1317    }
1318
1319    /// Check that `bytes` (from [`Self::write_values`]) describes exactly the
1320    /// fields of this board (ids, modes, layouts); the values are not checked.
1321    /// Does not change the board.
1322    ///
1323    /// # Errors
1324    ///
1325    /// [`StateError::Length`] when the byte count differs from what this board
1326    /// needs, [`StateError::InvalidValue`] when a descriptor differs.
1327    pub fn check_values(&self, bytes: &[u8]) -> Result<(), StateError> {
1328        let mut own = Vec::new();
1329        self.write_values(&mut own);
1330        if own.len() != bytes.len() {
1331            return Err(StateError::Length {
1332                expected: own.len(),
1333                found: bytes.len(),
1334            });
1335        }
1336        let mut at = 8;
1337        if own[..at] != bytes[..at] {
1338            return Err(StateError::InvalidValue);
1339        }
1340        for slot in &self.fields {
1341            let mut d = Vec::new();
1342            Self::write_descriptor(slot, &mut d);
1343            if bytes[at..at + d.len()] != d[..] {
1344                return Err(StateError::InvalidValue);
1345            }
1346            at += d.len() + 16 * slot.value.len();
1347        }
1348        Ok(())
1349    }
1350
1351    /// Replace every committed value with those in `bytes`. Only called with
1352    /// bytes [`Self::check_values`] accepted.
1353    pub fn read_values(&mut self, bytes: &[u8]) {
1354        let mut at = 8;
1355        for slot in &mut self.fields {
1356            let mut d = Vec::new();
1357            Self::write_descriptor(slot, &mut d);
1358            at += d.len();
1359            for f in &mut slot.value {
1360                *f = get_fix(&bytes[at..at + 16]);
1361                at += 16;
1362            }
1363        }
1364    }
1365}
1366
1367fn put_fix(out: &mut Vec<u8>, f: Fix128) {
1368    out.extend_from_slice(&f.hi.to_le_bytes());
1369    out.extend_from_slice(&f.lo.to_le_bytes());
1370}
1371
1372fn get_fix(b: &[u8]) -> Fix128 {
1373    let mut hi = [0_u8; 8];
1374    let mut lo = [0_u8; 8];
1375    hi.copy_from_slice(&b[0..8]);
1376    lo.copy_from_slice(&b[8..16]);
1377    Fix128 {
1378        hi: i64::from_le_bytes(hi),
1379        lo: u64::from_le_bytes(lo),
1380    }
1381}
1382
1383/// `a + b`, or `None` when the sum leaves the range of [`Fix128`].
1384fn checked_add(a: Fix128, b: Fix128) -> Option<Fix128> {
1385    let wide = |f: Fix128| (i128::from(f.hi) << 64) | i128::from(f.lo);
1386    let s = wide(a).checked_add(wide(b))?;
1387    Some(Fix128 {
1388        hi: (s >> 64) as i64,
1389        lo: s as u64,
1390    })
1391}
1392
1393/// One participant's staged field writes for one substep.
1394#[derive(Clone, Debug, Default, PartialEq, Eq)]
1395pub struct FieldStage {
1396    // ascending id
1397    staged: Vec<(PortId, Vec<Fix128>)>,
1398}
1399
1400impl FieldStage {
1401    /// An empty stage.
1402    #[must_use]
1403    pub fn new() -> Self {
1404        Self::default()
1405    }
1406
1407    /// Whether nothing was staged.
1408    #[must_use]
1409    pub fn is_empty(&self) -> bool {
1410        self.staged.is_empty()
1411    }
1412
1413    /// The staged buffer of field `id`, if one was created.
1414    #[must_use]
1415    pub fn staged(&self, id: PortId) -> Option<&[Fix128]> {
1416        self.staged
1417            .binary_search_by_key(&id, |s| s.0)
1418            .ok()
1419            .map(|i| self.staged[i].1.as_slice())
1420    }
1421
1422    fn buffer(&mut self, id: PortId, init: impl FnOnce() -> Vec<Fix128>) -> &mut [Fix128] {
1423        let i = match self.staged.binary_search_by_key(&id, |s| s.0) {
1424            Ok(i) => i,
1425            Err(at) => {
1426                self.staged.insert(at, (id, init()));
1427                at
1428            }
1429        };
1430        &mut self.staged[i].1
1431    }
1432}
1433
1434/// Check the declared ports of every participant (`ports[i]` for the
1435/// participant registered at `i`) against the fields of `board`.
1436///
1437/// # Errors
1438///
1439/// The first [`FieldPortError`] in registration order, then port order.
1440pub fn check_field_ports(board: &FieldBoard, ports: &[&[Port]]) -> Result<(), FieldPortError> {
1441    let mut writers: Vec<(PortId, usize)> = Vec::new();
1442    for (i, ps) in ports.iter().enumerate() {
1443        for p in ps.iter() {
1444            let field = board.slot(p.id);
1445            match (p.access, field) {
1446                (PortAccess::Read, Some(_)) => {
1447                    return Err(FieldPortError::ReadOfField {
1448                        participant: i,
1449                        field: p.id,
1450                    })
1451                }
1452                (PortAccess::ReadCommitted, None) => {
1453                    return Err(FieldPortError::UnknownField {
1454                        participant: i,
1455                        field: p.id,
1456                    })
1457                }
1458                (PortAccess::Write, Some(slot)) if slot.mode == FieldMode::Replace => {
1459                    match writers.iter().find(|w| w.0 == p.id) {
1460                        Some(&(_, first)) if first != i => {
1461                            return Err(FieldPortError::SecondWriter {
1462                                field: p.id,
1463                                first,
1464                                second: i,
1465                            })
1466                        }
1467                        Some(_) => {}
1468                        None => writers.push((p.id, i)),
1469                    }
1470                }
1471                _ => {}
1472            }
1473        }
1474    }
1475    Ok(())
1476}
1477
1478/// What the world fixes at registration for a list of participants: the run
1479/// order within a substep and each participant's declared ports.
1480#[derive(Clone, Debug, PartialEq, Eq)]
1481pub struct ParticipantPlan {
1482    order: Vec<usize>,
1483    ports: Vec<Vec<Port>>,
1484}
1485
1486impl ParticipantPlan {
1487    /// The plan for `participants` (registration order) over the fields of
1488    /// `board`. The world builds it again whenever a participant or a field is
1489    /// added.
1490    ///
1491    /// # Errors
1492    ///
1493    /// [`RegisterError::Order`] for a loop of ports, [`RegisterError::Field`]
1494    /// when the ports do not fit the fields.
1495    pub fn new(
1496        participants: &[Box<dyn Participant>],
1497        board: &FieldBoard,
1498    ) -> Result<Self, RegisterError> {
1499        let ports: Vec<Vec<Port>> = participants.iter().map(|p| p.ports().to_vec()).collect();
1500        let views: Vec<&[Port]> = ports.iter().map(Vec::as_slice).collect();
1501        check_field_ports(board, &views).map_err(RegisterError::Field)?;
1502        let order = execution_order(&views).map_err(RegisterError::Order)?;
1503        Ok(Self { order, ports })
1504    }
1505
1506    /// Registration indices in run order.
1507    #[must_use]
1508    pub fn order(&self) -> &[usize] {
1509        &self.order
1510    }
1511}
1512
1513/// Position and width of one substep.
1514#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1515pub struct SubstepTime {
1516    /// Index of the substep within the frame.
1517    pub index: usize,
1518    /// Substeps in the frame.
1519    pub count: usize,
1520    /// Width of the substep.
1521    pub h: Fix128,
1522}
1523
1524/// Why [`run_substep`] did not run (nothing was called or changed).
1525#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1526#[non_exhaustive]
1527pub enum ExchangeError {
1528    /// The plan was built for another number of participants.
1529    Plan {
1530        /// Participants given.
1531        participants: usize,
1532        /// Participants in the plan.
1533        planned: usize,
1534    },
1535    /// `frozen` does not have one flag per participant.
1536    Frozen {
1537        /// Flags given.
1538        flags: usize,
1539        /// Participants given.
1540        participants: usize,
1541    },
1542    /// The force accumulator does not have one slot per body.
1543    Accumulate(AccumulateError),
1544    /// A [`FieldLayout::PerBody`] field does not have one sample per body.
1545    BodyCount {
1546        /// The field.
1547        field: PortId,
1548        /// Samples of the field.
1549        samples: usize,
1550        /// Bodies.
1551        bodies: usize,
1552    },
1553}
1554
1555/// One substep of every participant, the part of the world step that runs the
1556/// participants and exchanges their results.
1557///
1558/// Participants that are not `frozen` run in the plan's order. Each gets the
1559/// bodies at the start of the substep, the fields as committed before the
1560/// substep, and its own empty force accumulator and field stage. On `Ok` its
1561/// forces are merged into `forces`; on `Err` its forces and staged writes are
1562/// dropped, it is marked frozen and a [`WorldFault::Participant`] is
1563/// reported. After every participant ran, the staged writes of those that
1564/// returned `Ok` are committed in run order ([`FieldBoard`] rules); a commit
1565/// out of range changes no field and is reported as
1566/// [`WorldFault::FieldOutOfRange`].
1567///
1568/// Returns the faults of this substep in the order they happened.
1569///
1570/// # Errors
1571///
1572/// An [`ExchangeError`] when the inputs do not fit together; nothing ran.
1573pub fn run_substep(
1574    participants: &mut [Box<dyn Participant>],
1575    plan: &ParticipantPlan,
1576    frozen: &mut [bool],
1577    bodies: &[RigidBody],
1578    board: &mut FieldBoard,
1579    forces: &mut ForceAccumulator,
1580    time: SubstepTime,
1581) -> Result<Vec<WorldFault>, ExchangeError> {
1582    run_substep_with_sdf_contacts(participants, plan, frozen, bodies, board, forces, time, &[])
1583}
1584
1585/// [`run_substep`] with the SDF contacts each participant reads through
1586/// [`SubstepCtx::sdf_contacts`]; `run_substep` passes none.
1587///
1588/// # Errors
1589///
1590/// As [`run_substep`].
1591#[allow(clippy::too_many_arguments)]
1592pub fn run_substep_with_sdf_contacts(
1593    participants: &mut [Box<dyn Participant>],
1594    plan: &ParticipantPlan,
1595    frozen: &mut [bool],
1596    bodies: &[RigidBody],
1597    board: &mut FieldBoard,
1598    forces: &mut ForceAccumulator,
1599    time: SubstepTime,
1600    sdf_contacts: &[crate::sdf_collider::SdfContact],
1601) -> Result<Vec<WorldFault>, ExchangeError> {
1602    let n = participants.len();
1603    if plan.order.len() != n {
1604        return Err(ExchangeError::Plan {
1605            participants: n,
1606            planned: plan.order.len(),
1607        });
1608    }
1609    if frozen.len() != n {
1610        return Err(ExchangeError::Frozen {
1611            flags: frozen.len(),
1612            participants: n,
1613        });
1614    }
1615    if forces.len() != bodies.len() {
1616        return Err(ExchangeError::Accumulate(AccumulateError::LengthMismatch {
1617            slots: forces.len(),
1618            bodies: bodies.len(),
1619        }));
1620    }
1621    for slot in &board.fields {
1622        if let FieldLayout::PerBody { .. } = slot.layout {
1623            if slot.value.len() != bodies.len() {
1624                return Err(ExchangeError::BodyCount {
1625                    field: slot.id,
1626                    samples: slot.value.len(),
1627                    bodies: bodies.len(),
1628                });
1629            }
1630        }
1631    }
1632    let mut faults = Vec::new();
1633    let mut kept: Vec<(usize, FieldStage)> = Vec::with_capacity(n);
1634    for &i in &plan.order {
1635        if frozen[i] {
1636            continue;
1637        }
1638        let mut staged_forces = ForceAccumulator::new(bodies.len());
1639        let mut stage = FieldStage::new();
1640        let result = {
1641            let ctx = SubstepCtx::new(bodies, &mut staged_forces, time.index, time.count, time.h)
1642                .map_err(ExchangeError::Accumulate)?;
1643            let mut ctx = ctx
1644                .with_fields(board, &mut stage, &plan.ports[i])
1645                .with_sdf_contacts(sdf_contacts);
1646            participants[i].substep(&mut ctx, time.h)
1647        };
1648        match result {
1649            Ok(()) => {
1650                forces
1651                    .merge(&staged_forces)
1652                    .map_err(ExchangeError::Accumulate)?;
1653                kept.push((i, stage));
1654            }
1655            Err(fault) => {
1656                frozen[i] = true;
1657                faults.push(WorldFault::Participant {
1658                    index: i,
1659                    kind: participants[i].kind(),
1660                    fault,
1661                });
1662            }
1663        }
1664    }
1665    if let Err(fault) = board.commit(&kept) {
1666        faults.push(fault);
1667    }
1668    Ok(faults)
1669}
1670
1671/// Why an amount could not be moved between layouts.
1672#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1673#[non_exhaustive]
1674pub enum RemapError {
1675    /// The values do not have one entry per source sample.
1676    Length {
1677        /// Samples of the source.
1678        expected: usize,
1679        /// Values given.
1680        found: usize,
1681    },
1682    /// A layout is not a valid grid ([`FieldLayout::samples`]).
1683    InvalidLayout,
1684    /// The grids are not nested: not the same origin, or cell sizes not an
1685    /// integer ratio, or not covering the same box.
1686    NotNested,
1687    /// A sum left the range of [`Fix128`].
1688    OutOfRange,
1689    /// Body `body` lies outside the grid.
1690    OutsideGrid {
1691        /// Index of the body.
1692        body: usize,
1693    },
1694}
1695
1696/// `big = n·small` exactly with an integer `n ≥ 1`.
1697fn integer_ratio(big: Fix128, small: Fix128) -> Option<usize> {
1698    let n = big / small;
1699    if n.lo == 0 && n.hi >= 1 && Fix128::from_int(n.hi) * small == big {
1700        usize::try_from(n.hi).ok()
1701    } else {
1702        None
1703    }
1704}
1705
1706fn grid_of(layout: &FieldLayout) -> Result<(Vec3Fix, Fix128, [usize; 3]), RemapError> {
1707    match *layout {
1708        FieldLayout::Grid { origin, cell, dims } if layout.samples().is_some() => {
1709            Ok((origin, cell, dims))
1710        }
1711        _ => Err(RemapError::InvalidLayout),
1712    }
1713}
1714
1715/// Move an extensive amount (per cell, not a density) from grid `src` to grid
1716/// `dst`, keeping the total exactly.
1717///
1718/// The grids must be nested: the same origin, cell sizes in an integer ratio
1719/// `r`, and the same box. Coarsening sums the `r³` fine cells of each coarse
1720/// cell. Refining gives each fine cell `q / r³` of its coarse cell's amount
1721/// `q`, and the first fine cell (lowest index) also the remainder
1722/// `q − r³·(q / r³)`, so the fine cells add up to `q` exactly. Equal grids
1723/// copy.
1724///
1725/// # Errors
1726///
1727/// [`RemapError::InvalidLayout`] when a layout is not a grid,
1728/// [`RemapError::Length`] for `values` of the wrong length,
1729/// [`RemapError::NotNested`] for grids that are not nested,
1730/// [`RemapError::OutOfRange`] when a coarse sum leaves the range.
1731pub fn remap_conserving(
1732    src: &FieldLayout,
1733    values: &[Fix128],
1734    dst: &FieldLayout,
1735) -> Result<Vec<Fix128>, RemapError> {
1736    let (so, sc, sd) = grid_of(src)?;
1737    let (d_o, dc, dd) = grid_of(dst)?;
1738    let n_src = src.samples().ok_or(RemapError::InvalidLayout)?;
1739    let n_dst = dst.samples().ok_or(RemapError::InvalidLayout)?;
1740    if values.len() != n_src {
1741        return Err(RemapError::Length {
1742            expected: n_src,
1743            found: values.len(),
1744        });
1745    }
1746    if so != d_o {
1747        return Err(RemapError::NotNested);
1748    }
1749    let coarsen = dc >= sc;
1750    let r = if coarsen {
1751        integer_ratio(dc, sc)
1752    } else {
1753        integer_ratio(sc, dc)
1754    }
1755    .ok_or(RemapError::NotNested)?;
1756    let (fine, coarse) = if coarsen { (sd, dd) } else { (dd, sd) };
1757    for a in 0..3 {
1758        if coarse[a].checked_mul(r) != Some(fine[a]) {
1759            return Err(RemapError::NotNested);
1760        }
1761    }
1762    let parent =
1763        |ix: usize, iy: usize, iz: usize| ((iz / r) * coarse[1] + iy / r) * coarse[0] + ix / r;
1764    if coarsen {
1765        let mut out = vec![Fix128::ZERO; n_dst];
1766        for iz in 0..fine[2] {
1767            for iy in 0..fine[1] {
1768                for ix in 0..fine[0] {
1769                    let f = (iz * fine[1] + iy) * fine[0] + ix;
1770                    let c = parent(ix, iy, iz);
1771                    out[c] = checked_add(out[c], values[f]).ok_or(RemapError::OutOfRange)?;
1772                }
1773            }
1774        }
1775        Ok(out)
1776    } else {
1777        let k = r
1778            .checked_mul(r)
1779            .and_then(|v| v.checked_mul(r))
1780            .and_then(|v| i64::try_from(v).ok())
1781            .ok_or(RemapError::NotNested)?;
1782        let kf = Fix128::from_int(k);
1783        let mut out = vec![Fix128::ZERO; n_dst];
1784        for iz in 0..fine[2] {
1785            for iy in 0..fine[1] {
1786                for ix in 0..fine[0] {
1787                    let q = values[parent(ix, iy, iz)];
1788                    let base = q / kf;
1789                    let first = ix % r == 0 && iy % r == 0 && iz % r == 0;
1790                    out[(iz * fine[1] + iy) * fine[0] + ix] =
1791                        if first { base + (q - base * kf) } else { base };
1792                }
1793            }
1794        }
1795        Ok(out)
1796    }
1797}
1798
1799/// Deposit an extensive amount per body (`amounts[i]` for `bodies[i]`) into
1800/// the grid cell that holds each body's position, keeping the total exactly.
1801///
1802/// # Errors
1803///
1804/// [`RemapError::InvalidLayout`] when `dst` is not a grid,
1805/// [`RemapError::Length`] when `amounts` is not one per body,
1806/// [`RemapError::OutsideGrid`] for a body outside the grid (nothing is
1807/// dropped silently), [`RemapError::OutOfRange`] when a cell sum leaves the
1808/// range.
1809pub fn deposit_bodies(
1810    bodies: &[RigidBody],
1811    amounts: &[Fix128],
1812    dst: &FieldLayout,
1813) -> Result<Vec<Fix128>, RemapError> {
1814    let (origin, cell, dims) = grid_of(dst)?;
1815    let n = dst.samples().ok_or(RemapError::InvalidLayout)?;
1816    if amounts.len() != bodies.len() {
1817        return Err(RemapError::Length {
1818            expected: bodies.len(),
1819            found: amounts.len(),
1820        });
1821    }
1822    let mut out = vec![Fix128::ZERO; n];
1823    for (b, (body, &q)) in bodies.iter().zip(amounts).enumerate() {
1824        let p = body.position;
1825        let mut idx = [0_usize; 3];
1826        for (a, (x, o)) in [(p.x, origin.x), (p.y, origin.y), (p.z, origin.z)]
1827            .into_iter()
1828            .enumerate()
1829        {
1830            let t = (x - o) / cell;
1831            idx[a] = usize::try_from(t.hi)
1832                .ok()
1833                .filter(|&i| i < dims[a])
1834                .ok_or(RemapError::OutsideGrid { body: b })?;
1835        }
1836        let c = (idx[2] * dims[1] + idx[1]) * dims[0] + idx[0];
1837        out[c] = checked_add(out[c], q).ok_or(RemapError::OutOfRange)?;
1838    }
1839    Ok(out)
1840}
1841
1842// ============================================================================
1843// Waking a parked body
1844// ============================================================================
1845
1846/// Whether a change is non-zero in a component; a change too large to
1847/// represent (`None`) counts as non-zero.
1848fn changes(d: Option<Vec3Fix>) -> bool {
1849    d.is_none_or(|d| !(d.x.is_zero() && d.y.is_zero() && d.z.is_zero()))
1850}
1851
1852/// Whether participant force `force` and torque `torque` (summed over every
1853/// participant for this substep) wake the parked or sleeping body `body`: the
1854/// change the world would apply in one substep of width `h`,
1855/// `Δv = (F·inv_mass)·h` or `Δω = (I⁻¹τ)·h` (`I⁻¹` in the world frame, the
1856/// same [`Fix128`] products the world uses), is non-zero in any component.
1857/// There is no threshold: a participant force that changes the body at all
1858/// wakes it, so the momentum a participant books for its force always
1859/// reaches the body. When both changes round to exactly zero the body stays
1860/// asleep (a force of a few raw units does not keep bodies awake). A change
1861/// too large to represent wakes the body (the world then reports it as
1862/// [`WorldFault::ForceOutOfRange`]). A static body never wakes.
1863///
1864/// This replaces the earlier rule for participant forces, which compared the
1865/// change with the sleep thresholds of `sleep` and left a body asleep below
1866/// them (the force then had no effect on it). `sleep` is no longer read; it
1867/// stays in the signature of this unstable function. Wakes from contacts,
1868/// impulses and other sources keep their own thresholds, and waking spreads
1869/// through an island by the existing rules.
1870#[must_use]
1871pub fn wakes_parked_body(
1872    body: &RigidBody,
1873    force: Vec3Fix,
1874    torque: Vec3Fix,
1875    h: Fix128,
1876    _sleep: &SleepConfig,
1877) -> bool {
1878    if body.is_static() {
1879        return false;
1880    }
1881    let dv = force
1882        .checked_scale(body.inv_mass)
1883        .and_then(|a| a.checked_scale(h));
1884    let dw = body
1885        .checked_world_inv_inertia_apply(torque)
1886        .and_then(|a| a.checked_scale(h));
1887    changes(dv) || changes(dw)
1888}