Backend Capability Contract¶
Backend differences are contract boundaries, not script-level special cases. The play/render decision is recorded in ADR-0002 Backend Capability Boundary For Play And Snapshot.
Stable Backend Interface¶
All env-facing backend calls should go through SimBackend in
unisim.backend.base. The interface includes base state, DOF state,
body state in world and baselink frames, named sensors, state reset, physics
stepping, domain-randomization hooks, and optional playback/render methods.
Optional capabilities are explicit:
PlayCapabilitiesreports native interactive rendering, physics-state playback, and native video capture support.HeightScannerandbind_height_scanner(...)expose terrain scan support through a reusable backend-owned object.SensorViewandbind_sensor_data(...)validate ordered named sensors on the cold path and retain a backend-owned reader for finite, shape-stable NumPy batches. Manager hot paths do not inspect XML or model metadata.Domain randomization support is surfaced through
get_dr_capabilities()and the init, reset, and interval randomization methods.Unsupported optional methods raise
NotImplementedErrorfrom the base class.
Rules For New Capability¶
If shared env logic needs a new backend operation, add it to
SimBackendfirst, with a defaultNotImplementedErrorif not every backend can support it immediately.Keep MuJoCo/Motrix differences in backend implementations, env adapters, and owner YAMLs. Do not add hot-path probes of backend private methods in env code.
Asset/XML/model metadata access belongs to cold paths such as scene materialization, backend init, or cache creation.
Physical entities and selected reset¶
The M2 consumer in issue #1599 uses UniSim physical entity declarations separately from UniLab logical selectors. SceneCfg materializes typed entity/variant values, calls the parent contract validation, and the asset factory collects physical and catalog source paths. EntityCfg.physical_entity binds a logical facade explicitly; primary_entity selects the scene’s primary root without encoding a task name in the backend.
A mapped logical root must name its physical entity’s declared root exactly. Binding a descendant body as the root is rejected during initialization, because root queries, defaults and reset writes must reference the same object. Reset staging maps selected rows in linear time and stores only requested fields; a current-state snapshot is needed only to fill missing columns when merging different joint position/velocity selections. The transaction still validates before its single public backend commit.
The existing ResetStateTransaction stages one public SceneResetRequest for mapped scenes. Missing fields and unselected entities/environments remain unchanged. Per-environment defaults come from get_entity_default_state, and restore_default_controls restores keyframe controls in the same commit; controls need not equal joint positions. No engine-private tensors or asset parsing enter manager terms. Scalar hinge/slide joints and a common selected environment set per transaction are the current consumer boundary; unsupported mixed DR/mocap or row patterns fail explicitly.
For tensor-capable scalar resets, TensorLifecycleCapabilities.packed_reset is a required capability for the Manager-Based runtime, not an optional fast path: ManagerBasedRlEnv fails closed during initialization when the backend does not declare it, and ResetStateTransaction.declare_packed_reset_device raises NotImplementedError when the capability or SimBackend.bind_packed_reset() is missing. The Manager composes every reset-mode state, model-field, and mocap write in UniSim’s public packed reset transaction, which owns full-width device state staging, selected-column merges, committed model-field baselines, selected mocap staging when packed_reset_mocap is declared, and the single public commit boundary; the composer is cached per declared device and its baseline persists across resets. The Manager keeps only term-facing validation and row-domain ownership. There is no non-packed fallback: UniLab no longer consumes SimBackend.set_state_tensor_mask(), and tensor mocap writes fail closed on backends without the mocap declaration.
Owner-subset command lifecycles retain the fixed-width bool mask as their input carrier, record the union of masks actually written, and ask the packed composer to commit only those rows; active rows without a write preserve their backend state.
tests/envs/test_multi_entity_consumer.py registers one primitive task with the same pickleable EnvFactory for MuJoCo and IsaacSim. It checks observation/action dimensions, passive joints, selected resets, variants and a kinematic mirror. The portable-profile fixture also combines a robot, passive object, table and collision-free mirror with the non-round-robin N5/K2 assignment [1,1,0,1,0]. The native IsaacSim cases require UNILAB_TEST_M2_ISAACSIM=1. The consumer contract requires released unisim-core>=1.7.3. The default and ROCm lock profiles resolve the published registry package. See UniSim roadmap #154 and UniSim contract #155 for implementation and verification scope.
Evidence In Repo¶
Configuration and asset preparation:
src/unilab/base/scene.py,src/unilab/base/backend_factory.py.Public state/reset bindings:
src/unilab/base/entity.py,src/unilab/base/reset_state.py.Registered runtime tests:
tests/base/test_entity_scene_consumer.py,tests/envs/test_multi_entity_consumer.py.Backend interface and play capabilities:
unisim.backend.baseBackend factory:
src/unilab/base/backend_factory.pyMuJoCo backend:
unisim.backend.mujoco.backendMotrix backend:
unisim.backend.motrix.backendBackend contract tests:
tests/base/test_backend_sensor_view.py,tests/base/test_backend_conformance.py,tests/base/test_sim_backend_smoke.py,tests/base/test_backend_imports.py,tests/base/test_motrix_backend_options.py