---
title: "Changelog"
url: https://recursiveai.org/onephysics/docs/changelog/
description: "What changed for hosts in each preview, newest first. \"Moved results\" lists the changes that alter a world's state after the same inputs (and so its…"
version: "0.4.0-preview.1"
source: https://github.com/recursive-ai-oss/onephysics-csharp/blob/fa8eed5a2021c452ca8051b6099917e09538b4f4/CHANGELOG.md
---

# Changelog

What changed for hosts in each preview, newest first. "Moved results" lists the changes that alter a world's state after the same inputs (and so its snapshot bytes and world hashes).

**0.x previews are not bit-compatible with each other: snapshots, saved games and replays from one preview do not carry to another.**

## [Unreleased]

## [0.4.0-preview.1]

- **Soft bodies complete the 3D feature set for this preview line; the 3D engine stays `[Experimental("OPHYS0002")]`.** Every 3D scene, soft bodies included, is within its stability limits on x64 and arm64 with identical metrics, and results are bit-identical across x64, arm64, WebAssembly, vector widths and thread counts. Not every speed row meets the bar yet (within 1.15x of the fastest stable managed engine, BepuPhysics v2 or Jitter2, per scene and thread count). Measured on a Ryzen 9 7950X3D (ours / reference median step time, t1 / t8 on one core complex / t32):

  | scene | t1 | t8 | t32 |
  |---|---|---|---|
  | large-pile-3d | 1.05 | 1.08 | 1.25 |
  | ragdoll-pile-3d | 2.19 | 1.12 | 1.70 |
  | sleep-wake-3d | 2.51 | 1.51 | 1.40 |
  | queries-3d (settle phase) | 1.27 | 1.26 | 1.19 |
  | cloth-drape-3d (soft body, against Jitter2) | 0.93 | 3.9 | 2.5 |
  | heightfield-terrain-3d | 1.13 | 0.89 | 0.64 |
  | mesh-terrain-3d | 1.08 | 0.89 | 0.85 |
  | joint-grid-3d | 0.10 | 0.16 | 0.09 |

  On an Apple M2 (t1 / t8) every 3D row meets the bar except ragdoll-pile-3d t1 (1.27) and sleep-wake-3d t1 (1.64), and cloth-drape-3d is 0.84 / 1.87. joint-chains-3d and pyramid-3d-30 have no stable managed reference, and the other soft-body scenes (soft-pile-3d, soft-rigid-3d, soft-mesh-3d, soft-skinned-3d) have none either; they are judged on their stability limits, which they meet. Every 2D scene is within the bar at every thread count.

- **3D soft bodies solve about 2.5 times faster on a cloth (indicative, one thread), and large bodies use several threads. Moved results (soft bodies only; rigid worlds and 2D are unchanged).** The step works on float32 positions relative to each body, the constraints are coloured once at creation (edges, bends and tetrahedra solved colour by colour, in `Vector128` lanes where four constraints of a colour are available, with the same bits as the scalar loop), bodies of 1024 particles or more are split across threads by colour, contact detection and the particle pair search run in tasks that are joined in particle order, and particle pairs are searched once per step instead of once per substep. The order in which constraints are solved changes, so soft-body states, snapshot bytes and hashes differ from the previous preview; the snapshot layout is unchanged. Details in `docs/spec/softbody.md` (Solver).

- **3D soft bodies follow a skeleton (skinning).** `SoftSkin.Create` builds per-particle joint weights (four influences, linear blend skinning), a maximum distance and an optional back-stop sphere from a skeleton and the mesh; set it as `SoftBodyDef.Skin`. `SoftBody.SetSkeletonPose` (ragdoll-style local rotations) or `SetSkeletonTransforms` (world joint poses) drives it before each step, with `teleport` to place or reset; `GetSkinnedPosition`, `CopySkinnedPositions`, `HasSkin`, `Slot` and `PhysicsWorld3D.GetSoftBody(slot)` (to find the bodies again after `Restore`) are new. A particle with maximum distance 0 is kinematic and equals its linearly blended position bit for bit; the others are held within their distance (and out of the back-stop) after every step, and win against contacts. Snapshot section version 2 is written only while a skinned body exists, so other worlds keep their bytes and hashes; version 1 sections still load. `DebugSoftBody.FirstTargetPoint` carries the targets. New scene `soft-skinned-3d` (a sleeve on a swinging arm) with `skinMaxDistance`, `skinBackstop` and `skinCuffError` metrics. See `docs/spec/softbody.md`.
- **3D: the bounding-box stage takes about half the time, with identical results.** The world box of a sphere, box, capsule or sharp hull is computed straight from the body arrays with only the rotation-matrix entries the shape needs, and a hull's six extremes come from one pass with the three axes as lanes of one 128-bit vector (the same operations in the same order as the scalar code, so every platform and vector width gives the same bits). Profile build, one thread, indicative: large-pile-3d 0.24 to 0.11 ms, ragdoll-pile-3d 0.08 to 0.04, sleep-wake-3d 0.07 to 0.035. No world hash moves. See `docs/spec/world3d.md`, round 35.
- **3D soft bodies (core).** `PhysicsWorld3D.AddCloth`, `AddSoftBox`, `AddSoftBall`, `AddSoftBody(SoftBodyMesh, ...)` and `AddSoftBody(SoftBodyDef)` make cloth, rubber and jelly from a triangle or tetrahedral mesh: extended position-based dynamics with edge, isometric-bend, tetrahedron-volume and pressure constraints, particles that collide with every shape, with each other and with friction, two-way coupling with dynamic rigid bodies, pins, sleeping, debug geometry (`DebugContent.SoftBodies`), `SoftSubsteps` and `SoftIterations` options. The `SoftBody` handle reads particles without allocating. Soft bodies are stored apart from rigid bodies and solved serially after the rigid step, in a new **optional** snapshot section (`0x534F4654`) that is written, hashed and counted only once a soft body exists: worlds without soft bodies keep every byte and hash, and snapshot format version 1 is unchanged. New scenes `cloth-drape-3d`, `soft-pile-3d`, `soft-rigid-3d`, `soft-mesh-3d` with soft-body metrics and a Jitter2 driver; fuzz targets `world3d-softbodies` and `world3d-softbody-snapshot`. `DebugContent.All` is now 1023; it is a constant, so code compiled against an earlier preview keeps `All` = 511 and does not draw soft bodies until it is rebuilt.

## [0.3.0-preview.1]

- **Characters, vehicles, ragdolls and buoyancy are complete for this preview line.** The rigid and virtual 3D characters, wheeled, tracked and motorcycle vehicles, ragdolls with powered pose matching, and buoyancy and drag in fluid volumes are within their stability limits on every scene, with identical results on x64 and arm64: `characters-3d` (every stair climbed, character jitter under 1e-6 m/s, no slope violations), `vehicles-3d` (no rollovers, no wheel through the ground, suspension jitter 0.002), `tracked-3d`, `motorcycle-3d` (none fallen, lean error 0.006), `ragdoll-powered-3d` (95th-percentile pose error 0.058 rad, none fallen) and `buoyancy-3d` (equilibrium depth error 0.0026). No managed .NET engine covers these scenes, so they are judged on their stability limits. They stay under `[Experimental("OPHYS0002")]` with the rest of the 3D engine until every 3D speed row meets the bar.

- **`GetSnapshotSizeBound()` on `PhysicsWorld2D` and `PhysicsWorld3D`** returns an upper bound on `GetSnapshotSize()` that holds however the world steps while no storage grows, so a host can size rollback buffers once. Contacts and joints, shape pairs, islands, broadphase nodes and events are counted at the capacity that holds them (`ContactCapacity`, `PairCapacity`); what the host creates (bodies, shapes, joints, characters, vehicles, ragdolls, fluids) is counted as it is now. Additive; the snapshot format and every world hash are unchanged.
- **3D box-against-box edge axes are tested without normalizing each axis** (about 9% off the box–box routine on pairs from a sleep-wake pile). Separations can differ from the previous preview in the last bit in some poses, so 3D world hashes and snapshot bytes move.
- **3D: mild joints run on the contact-only schedule.** An island whose joints are all mild (no kinematic end, motor, spring or pose drive; mass ratio at most 50; no more joints than bodies) now takes the 2-substep contact-only schedule with its contacts, instead of the world's 4. ragdoll-pile-3d solve stage 0.67× the time (step about 0.8×, one thread, indicative); stiff joint scenes are unchanged. See `docs/spec/world3d.md`, round 33. Step time on a locked machine: ragdoll-pile-3d 0.80× at one thread, 0.85× at eight, 0.75× at 32; large-pile-3d and the joint scenes within noise.
- **3D steps scale better with threads, with identical results.** The sleep timers run in parallel blocks as in 2D, colouring blocks are 512 constraints instead of 4096, and threads waiting at a stage boundary pause for a few instructions a round and give the core up only after a long wait, instead of backing off and yielding after ten rounds. On a locked machine, steps at eight and 32 threads take 0.94–0.99× the time on piles, ragdolls, sleep-wake and joint grids, in 2D and 3D. No world hash moves.

## [0.2.0-preview.1]

- **The snapshot format is frozen at version 1.** Its layout (header, options, bodies, shapes, constraints, islands and registered sections, `docs/spec/architecture.md`) no longer changes within version 1. A later change is a new format version. Results may still move between previews.
- **Raw tier (`[Experimental("OPHYS0001")]`): `IBox<TSelf>` gains `Translated(ReadOnlySpan<double>)`**, which the exact origin shift uses. A type that implements `IBox<TSelf>` outside the library must add it.
- **The 3D engine stays `[Experimental("OPHYS0002")]`.** It meets the speed bar on most scenes but not all yet: within 1.15x of the fastest stable managed engine (BepuPhysics v2, Jitter2), per scene and thread count. Measured on a Ryzen 9 7950X3D (ours / reference median step time, t1 / t8 on one core complex / t32):

  | scene | t1 | t8 | t32 |
  |---|---|---|---|
  | large-pile-3d | 1.10 | 1.23 | 1.47 |
  | ragdoll-pile-3d | 2.75 | 1.42 | 2.17 |
  | sleep-wake-3d | 2.75 | 1.60 | 1.41 |
  | queries-3d (settle phase) | 1.31 | 1.42 | 1.38 |
  | heightfield-terrain-3d | 1.17 | 0.95 | 0.68 |
  | mesh-terrain-3d | 1.10 | 0.92 | 0.68 |
  | joint-grid-3d | 0.10 | 0.16 | 0.06 |

  joint-chains-3d and pyramid-3d-30 have no stable managed reference and are judged on their stability limits, which they meet. Every 3D scene is within its stability limits, and every 2D scene is within the bar at every thread count. These rows predate the narrowphase entry below, which takes 3-6% off the ragdoll, box-pile and sleep-wake rows.

- **`Warmup` primes more of the process.** Its trial world now drops a pile that lands, settles and falls asleep (150 steps) and runs box and sphere casts and box and capsule overlaps, so those paths are loaded before a host's first step. Worlds, hashes and snapshots are unchanged.

**Moved results:** 3D worlds move (the broadphase keeps tighter boxes, so pairs start and end at different steps), and 2D and 3D worlds move again because positions now advance on a 2^-32 grid (see the origin shift entry).

- **3D narrowphase is cheaper in ragdoll and box piles, with identical results** (ragdoll-pile-3d about 5%, sleep-wake-3d about 3%, large-pile-3d about 4% per step, one thread, indicative): a capsule against a box skips box edges that cannot be nearest, box edge axes are rejected before they are normalized, separated boxes nearest on an edge pair skip GJK, and pairs that need a narrowphase run in their own method. No world hash moves.
- **3D ray casts and sphere casts are faster (ray 0.59x, sphere cast 0.79x, `OverlapSphereAll` 0.84x the time on a 3000-body pile, one thread, indicative).** Spheres, capsules, boxes and hulls are tested in closed form, and the GJK route only runs for starts inside, grazing hits, edge regions and other shape kinds. Which shape is hit and the hit counts do not change; the last bits of `Fraction`, `Distance`, `Point` and `Normal` of ray and sphere-cast hits do (within the old route's tolerance), so worlds that cast (vehicle wheel rays) move.
- **3D: a substep schedule per island.** Islands without a joint (and not touching a kinematic body or holding a vehicle) now run 2 substeps of 1 solve and 2 relax passes (`WorldOptions3D.ContactSubsteps`, `ContactVelocityIterations`, `ContactRelaxIterations`; `ContactSubsteps = 0` restores the world-wide schedule); islands with joints keep `Substeps`. Piles and terrain step about 1.15-1.35x faster and worlds with those islands move; adding a joint changes only the island that receives it. The options are saved in snapshots and covered by the world hash.
- **3D broadphase defaults:** `BroadphaseMargin` is 0.03 (was 0.1), boxes stretch over four steps of motion, and the tree is refitted between rebuilds. About half as many pairs are kept alive in piles; sleep-wake scenes step about 1.2× faster and large piles at eight threads about 1.15×. Stability is unchanged on every 3D scene.
- A fast body still inside the supported range no longer fails the step with an argument error when its stretched box would cross the range limit; the stretch stops at the limit.

- **`ShiftOrigin` is exact.** A shifted world now steps through exactly the states of the unshifted world, translated (positions differ by the offset; velocities, rotations and contact data are bit-identical), instead of drifting from it. The shift moves the stored broadphase boxes in place instead of rebuilding them. To make that true, a body's position increment each step is rounded to a multiple of 2^-32 (2.3e-10 m; a body slower than about 1.4e-8 m/s at 60 Hz does not move), shape bounds are rounded outward to the same grid, and the broadphase margin is rounded to it. Positions handed in as 31.32 fixed-point values are already on the grid.
- **3D box-against-box face contacts are built without loading polyhedra.** The reference face is now the one the separating axis test chose (no second choice by area), so manifolds and the worlds that contain boxes move. The box-box manifold costs about 0.78× as much per fresh manifold in a sleep-wake pile (845 against 1090 ns).

## [0.1.0-preview.6]

- **Fix: 0.1.0-preview.5 could refuse a snapshot of a valid 3D world.** Its `Restore` rejected saved contact impulses above a fixed plausibility limit, but the solver can legitimately produce larger ones (a very heavy body on a light one, or a tight pile, gave impulses in the trillions). A world that had stepped normally could therefore write a snapshot that the same library then refused with `SnapshotFormatException`, which breaks a host's rollback. This release removes that check, and the wheel and engine speed limits added with it: a snapshot the library wrote always restores, and saved impulses are checked only for being finite. A damaged snapshot that carries an enormous impulse restores, and the next step reports the flung body with `BodyOutOfBoundsException` (the world unchanged), as before. No step result moves: world hashes and snapshot bytes are the same as in preview.5 for the same inputs.
- **3D `Restore` refuses a dynamic body whose saved extents do not match its shapes** (`SnapshotFormatException`, world unchanged). A snapshot the library wrote always matches.
- **3D steps are faster, with identical results.** Pairs whose contacts are unchanged skip the serial write-back; the tree update tests boxes in parallel; height-field collision visits only the cells a box covers; a convex shape stays loaded across the triangles of a mesh; overlap counts no longer collect hits; solver batches precompute their contents once per step. Height-field worlds step about 1.1–1.2× faster and large piles at eight threads about 1.1×. No result moves.
- **Vehicles: a step can no longer write state its own snapshot reader refuses.** Fuzzing found a differential ratio of about 3e37 (finite, so accepted) that overflowed the drivetrain into NaN in one step. Vehicle definitions now bound their ratios (at most 1000), inertias (0.001 to 1e6), engine torque, clutch strength, tyre factors and low speed, and `AddVehicle` and `Restore` refuse values outside the bounds with the usual argument and snapshot errors. The step also keeps engine and wheel speeds finite, and `Restore` checks them against what a step can write. Valid worlds are unchanged.
- Tests now check, after every step of every fuzz scenario (2D and 3D), that the world's snapshot restores into a fresh world and re-serializes to the same bytes.

## [0.1.0-preview.5]

First public release. The 2D engine and the shared core are the supported surface; the 3D engine ships as a preview marked `[Experimental("OPHYS0002")]`. A user guide is in `docs/guide/`.

**Moved results:** 2D worlds are bit-identical to preview.4 for the same inputs. 3D worlds move: an exact capsule-against-box closest pair replaces the general path when the shapes are apart, and the joint solver computes each joint's block inverse once per substep. Convex hulls built from inputs that were near-coplanar or noisy can differ (see below). Snapshot layout is unchanged.

- **Allocation-free from the first step, also under load and with several threads.** `Warmup()` now prepares every pool thread (including threads an earlier world started) for the narrowphase and batch queries, runs the pool's parallel paths of a throwaway world whatever their size, and stocks the island body-list pool for the world's body count. Before, a pile that kept merging and splitting islands allocated a few hundred bytes at about one step in a few hundred, and the first step of a multi-threaded 3D world allocated on the calling thread. `ComputeHash()` is documented as streaming (it always was: no snapshot copy is made).
- **Breaking: `CharacterDef.SlopeLimit` and `Character.SlopeLimit` are `SlopeLimitRadians`** (2D and 3D); the unit is in the name. The value and its default are unchanged.
- **`world.BodyCount`, `world.Bodies`, `world.CharacterCount`, `world.Characters`** (2D and 3D, identical): count and `foreach` over the live bodies and characters in slot order with a struct enumerator that allocates nothing.
- **The 3D engine is `[Experimental("OPHYS0002")]`:** every public type in `RecursiveAI.OnePhysics.Physics3D`. Using it raises the diagnostic until you suppress it (`#pragma warning disable OPHYS0002` or `<NoWarn>`); its API and its results may change between previews. The 2D engine and the shared types are not marked.
- The package now carries the README.
- **Convex hulls contain their input points.** `ConvexHull3D.Build` now decides face visibility with exact predicates, rescans every input point once the hull is built, and checks each merged face against every point, undoing a merge that would leave a point outside. Every input point is inside every face within the documented tolerance, or the build is refused with `HullBuildException`; before, some near-coplanar inputs gave a hull that left a point outside by up to a few centimetres. Building is about 20% slower on hostile inputs.
- **3D `Restore` refuses damaged contact and vehicle state, and a step reports a runaway sleeping body.** A snapshot holding a saved contact impulse no solver step could have produced (it would launch the bodies out of the world in one step), or a vehicle wheel with an absurd sprung mass or wheel or engine speed, is refused with `SnapshotFormatException` naming the value, and the world is unchanged. A sleeping body far from the origin is now checked at the start of every step (`BodyOutOfBoundsException`, world unchanged), and a vehicle wheel whose top lies just outside the supported range finds no ground instead of failing the step. Valid worlds are bit-identical.
- **3D, faster at 8 threads:** solve stages split into one task per constraint batch and the worker count follows the average stage width (terrain scenes about 1.5–1.8× faster at 8 threads); results do not depend on the thread count, as before.
- **3D integration runs lane-wide** with a bit-identical scalar twin.
- Package metadata, symbols (`.snupkg`) and Source Link for the public repository.

## [0.1.0-preview.4]

**Breaking API changes.** No result moves: world hashes, snapshot bytes and event order are the same as in preview.3 for the same inputs. Migration is by namespace, definition and exception contracts only.

- **Definitions are valid at their zero value.** `default(BodyDef)`, `default(ShapeDef)`, `default(CharacterDef)` and every joint definition (2D and 3D) now mean the documented defaults, exactly like `new XDef()` (a dynamic body, density 1, friction 0.6, collides with everything, unbreakable joints, `JointFrame3D` at the identity). A class field, an array element or `default` no longer falls through the ground or breaks a joint at once. Migration: none needed; code that wrote `new XDef()` keeps working and `default` now agrees with it. `BodyType` keeps its numeric values, so snapshots are unchanged. Reference-type definitions (`FluidDef`, `RagdollDef`, `VehicleDef` and friends) have no zero value and are unchanged.
- **Namespaces.** `RecursiveAI.OnePhysics.Core` is gone. Types game code names now live in `RecursiveAI.OnePhysics`: `BodyType`, `EventKind`, `EventMask`, `PhysicsEvent`, `BodyHandle`, `ShapeHandle`, `JointHandle`, `MaterialLimits`, the exceptions (`PhysicsException`, `InvalidHandleException`, `StaleHandleException`, `ForeignHandleException`, `SnapshotFormatException`, `BodyTypeException`, `InvalidJointException`, `InvalidShapeException`, `HullBuildException`, `MeshBuildException`, `StaticOnlyShapeException`, `ShapeCompositionException` and their error enums) and the shapes the query methods take (`Shape2D`, `Vec2`, `Rot2`, `Transform2`, `Aabb2`, `Chain2D`, `MassData2D`, `RayCastOutput2D`, `ConvexShape3D`, `ConvexHull3D` and its option and face types). Migration: replace `using RecursiveAI.OnePhysics.Core;` with `using RecursiveAI.OnePhysics;`.
- **The raw tier is `RecursiveAI.OnePhysics.LowLevel` and `[Experimental("OPHYS0001")]`.** Stores, the scheduler and coloring, islands, the broadphase trees, GJK/EPA and manifold routines, snapshot readers and writers, and the 2D collision functions moved there from `Core`, `Broadphase`, `Collision2D` and `Collision3D`. Using them needs `<NoWarn>$(NoWarn);OPHYS0001</NoWarn>`. Migration: replace the `using` lines of those four namespaces with `using RecursiveAI.OnePhysics.LowLevel;`. `PhysicsWorld2D.Core` and `PhysicsWorld3D.Core` are marked experimental too. Test fixtures (`ParticleScene`, `SplitMix64`, `CollisionHash2D`, `StageProbe`, `ManifoldLayout`) are internal.
- **Exception contracts.** Argument errors are `ArgumentException`s whose `ParamName` is the caller's parameter (`at`, `size`, `radius`, `layerA`, `force`, `def`, and the second body of an `AddHinge`-style call), with messages in the caller's terms (`AddBox` reports `size`, not half extents). `InvalidJointException` now derives from `ArgumentException` instead of `PhysicsException` (catch it by its own type, or as `ArgumentException`). `InvalidShapeException.Reason` is now `Error`, like `HullBuildException`, `MeshBuildException` and `ShapeCompositionException`. `ShapeError` has a neutral zero value, `None`. `BodyOutOfBoundsException.Body` is a `Body` (2D and 3D), so recovery is `e.Body.Destroy()`. Failed `AddBox`, `AddCircle`, `AddCapsule` and `AddSegment` no longer leave a body behind.
- The README quick start compiles (it was missing `using RecursiveAI.OnePhysics;`); a test now builds and runs the README's code blocks.

**Breaking API changes, round 2.** No result moves here either: hashes, snapshot bytes and event order are unchanged. See `docs/guide/api-names.md` for the conventions.

- **Handles in results are fields.** `Collision`, `Trigger`, `QueryHit2D/3D`, `ControllerColliderHit` and `JointBreak` expose `Body`, `Other`, `Shape`, `OtherShape`, `Collider`, `Joint`, `BodyA` and `BodyB` as readonly fields, so `hit.Other.LinearVelocity = v` compiles. Migration: none for reads; code that took the property as a method group or through reflection changes. New `Body.SetLinearVelocity`, `SetAngularVelocity` and `SetPosition` for places a property cannot be assigned (list elements).
- **Full sizes.** 2D `CreateBox(body, halfWidth, halfHeight, ...)` is now `CreateBox(body, Vector2F size, ...)`; 3D `CreateBox` and `ShapeSpec3D.Box(halfExtents)` take full `size`. Migration: double the old values. `RagdollPartDef.ShapeSize` is `ShapeHalfSize` (the value is unchanged; the name now says what it is). Capsule parameters are `halfLength` in both dimensions (`ShapeSpec3D.Capsule`/`TaperedCapsule` and the 3D capsule queries were `halfHeight`); cylinders keep `halfHeight`.
- **Radians.** `CharacterDef.SlopeLimit` and `Character.SlopeLimit` are radians (they were degrees); the defaults mean the same 45 and 40 degrees. Migration: multiply degrees by pi/180. 2D `angle` parameters are named `angleRadians` (creators, queries, `Body.SetTransform`); rename named arguments.
- **One name per joint concept.** The general `Joint` uses the definition names: `LimitEnabled` is `EnableLimit`, `MotorEnabled` is `EnableMotor`, `SpringEnabled` is `EnableSpring`, `LowerLimit`/`UpperLimit` are `LowerAngle`/`UpperAngle` (revolute), `LowerTranslation`/`UpperTranslation` (prismatic, wheel) or `MinLength`/`MaxLength` (distance). `MaxMotorForce` (linear) and `MaxMotorTorque` (rotational) are separate and asking the wrong one throws `InvalidJointException` naming the right one.
- **Typed joint views.** `Add*` and `CreateJoint(ref XDef)` return `HingeJoint`, `SliderJoint`, `FixedJoint`, `WheelJoint`, `DistanceJoint`, `TargetJoint`, `RelativeJoint` (2D) and `FixedJoint`, `PointJoint`, `DistanceJoint`, `HingeJoint`, `SliderJoint`, `ConeJoint`, `SwingTwistJoint`, `SixDofJoint`, `PathJoint`, `GearJoint`, `RackAndPinionJoint`, `PulleyJoint` (3D) instead of `Joint`. Each converts implicitly to `Joint` (explicit back, checking the kind). Migration: `var` and `Joint` variables keep compiling; code that held the result in a `Joint` field is unchanged.
- **Characters, ragdolls, vehicles.** Presets moved from `Character` to `CharacterDef` (`CharacterDef.Humanoid`, `Child`, `Giant`, 3D `RigidHumanoid`). `Character.Index`/`Generation` are replaced by `Character.RawHandle` (`CharacterHandle`); `Ragdoll.RawHandle` and `Vehicle.RawHandle` are new (`RagdollHandle`, `VehicleHandle`). `GetCharacter(index, generation)` and `IsCharacterAlive(index, generation)` are now `GetCharacter(handle)` and `IsCharacterAlive(handle)`, with the ragdoll and vehicle forms in 3D.
- **`Restore` returns `OptionChanges`** (it returned nothing): the construction options the snapshot replaced. Ignore it if you do not care.
- **Rollback and handlers.** `using (world.SuppressCallbacks())` mutes body, joint and character handlers (polled events still work; it allocates nothing and nests). Per-body handlers now stay registered when a restore brings the same handle back: the world keeps the handlers of the last 1024 destroyed handles per kind once it has taken a snapshot, and a world that never snapshots releases handlers at destroy as before.
- `ToString` on `PhysicsEvent`, `Collision`, `Trigger`, `JointBreak`, `ControllerColliderHit` and `QueryHit2D/3D`.

## [0.1.0-preview.3]

- **OneMath 1.0.0.** The package now depends on `RecursiveAI.OneMath` 1.0.0 (from 0.1.0-preview.5). Its results are bit-identical to the preview, so this change alone moves no world.
- **Input limits.** Inputs that would break the world later are refused with typed errors: material coefficients beyond the limit, shapes with unrepresentable mass or inertia, convex shapes thinner than 2^-20 of their length, non-finite body state at the start of a step, invalid wheel dimensions in a snapshot. See `docs/guide/limits.md`.
- **Restore exactness.** The test that skips pairs still apart is now bounded by both shapes' reach, so a restored world containing kinematic or static bodies continues exactly like the original. Host-style hash scenarios with restores in the middle of a run now cover this.
- `ShiftOrigin` validates the shift before changing anything: a shift that would move a body out of range is refused with a typed error, and the world is unchanged.
- **Moved results: continuous collision against compounds.** Compound sweeps are now clipped to the compound's range, and short ray and normal edge cases in the 3D narrowphase are fixed, so some worlds with fast bodies or compounds differ from preview.2.

- **Moved results: sensors.** A sensor overlap is no longer a constraint. It is a mark on the pair, so a sensor never takes a constraint slot, a pair slot or a color that a contact could have had. Adding, moving or removing a sensor of any body type (static, kinematic, dynamic) leaves every solid body bit-identical. Worlds that contain sensors give different results and different snapshot bytes than preview.2 (in preview.2 sensors paired with kinematic bodies and characters reordered contact solving). Worlds without sensors are unchanged by this change.
- Sensor enter and exit events are unchanged in kind and order; an exit is now also reported when a body or shape that was inside a sensor is destroyed.
- Snapshot layout: the free-pair list of both engines now also carries the slots freed by sensor pairs (as complements, after the contact slots), and a 3D sensor overlap is written as a one-entry pair chain holding -2. Worlds without sensor pairs write the same bytes as before.
- New capacity options on `WorldOptions2D` and `WorldOptions3D` (`PairCapacity`, `ContactCapacity`, `IslandCapacity`, `QueryScratchCapacity`) and `Warmup()` on both worlds, so a host can reach an allocation-free steady state from the first step. Capacity options never change a result and are not saved in snapshots.
- `ComputeHash()` streams over the world state in snapshot order and allocates nothing (as since preview.2); this changelog now records it.
- Allocation: a deep-contact path in the 3D narrowphase allocated 96 bytes per call and no longer does; a restore of a larger world now allocates nothing after `Warmup()`.
- **Moved results: speed rounds and fixes since preview.2.** These change the state after the same inputs, for the scenes named: the 2D pair stage (refit between tree rebuilds) moves 2D worlds with many moving bodies; the 3D solver kernels move worlds with joints and ragdolls; the character and services fixes for stale displacement move 2D worlds with characters or services.

## [0.1.0-preview.2]

- Host hooks: `TrySnapshot`, `GetSnapshotSize` and `Restore(ReadOnlySpan<byte>)` without per-call copies; allocation-free `ComputeHash`; handle liveness queries; character index and generation; typed errors for velocity and bullet settings on static bodies.
- **Moved results: sensors for kinematic bodies and characters.** Sensors now pair with kinematic bodies and characters, so those worlds' events, constraint order and results differ from preview.1.
- Speed rounds between preview.1 and preview.2 (pair stage, solver glue, 3D contact preparation) left the world hashes of the project's scenes unchanged; the sensor change above is what moved results in preview.2.

## [0.1.0-preview.1]

First preview: the 2D engine, the shared core (handles, deterministic scheduler and coloring, islands and sleeping, layers and filters, events and sensors, queries, materials, snapshot and restore, world hashes, debug geometry), and the 3D rigid core with characters, vehicles, ragdolls and buoyancy in progress.

[0.1.0-preview.5]: https://github.com/recursive-ai-oss/onephysics-csharp/releases/tag/v0.1.0-preview.5
[0.1.0-preview.6]: https://github.com/recursive-ai-oss/onephysics-csharp/releases/tag/v0.1.0-preview.6
