Solver Internal Data Migration Policy¶
Target convention for new or migrated device-oriented solver scratch under
novaphy/include/dynamics/. This is not a description of every current
solver: CPU and legacy paths still contain per-element std::vector and AoS
caches, including Semi-Implicit, Featherstone, and MuJoCo-style internals.
Their existence is not evidence that the migration is complete.
The design rationale is retained in the repository-only
docs/superpowers/specs/2026-05-21-solver-internal-data-pipeline-design.md
artifact, which is intentionally excluded from the public site.
Target rules for migrated scratch¶
Apply these rules when introducing GPU-capable scratch or explicitly migrating an existing cache:
- Container. Use
DeviceArray<T>for per-element scratch that must follow the model device. CPU-only solver caches may remainstd::vector<T>until their owning path is migrated. - Layout. Prefer struct-of-arrays for kernel-facing scratch — one
DeviceArray<scalar/Vec3f/Quatf/...>per field. - Device. Migrated scratch should satisfy
scratch.device() == model.device(). - Allocation. Allocate at the top of
step()(or in aprepare_*/ensure_*helper called fromstep()), viaDeviceArray<T>::zeros(N, model.device())orDeviceArray<T>::empty_like(model.x). The constructor allocates onlySettingsand trivially-copyable scalar/counter members. - Variable-size scratch. Capacity comes from
Contacts::rigid_contact_maxor aModel::*_count. Active count iscontacts->rigid_count(). Solvers mustassert(active_count <= capacity). - Expected host-side exceptions. Build-time CPU-only
assets (
Mesh::vertices_,Mesh::indices_), tree-shaped ownership (std::unique_ptr<MultiBody>), and short-lived function-local temporaries may remain host containers. POD scalars and counters stay plain members (e.g.int active_contact_count_,float last_dt_). - Private helper signatures. A helper on a migrated path should preserve
DeviceArray<T>&/const DeviceArray<T>&through the kernel boundary.
Performance note¶
ensure_*_scratch_ helpers should reuse an existing allocation when its
device and capacity already match the requested layout. This avoids per-step
allocation overhead without changing the caller-visible solver contract.