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