Developing
Developing¶
simscope is a Python package and a browser player. Python records, stores, and serves rollouts. The browser plays them. Both sides read the same files. The writer is src/simscope/io/. The reader is web/src/core/format.js.
Pull requests, commit messages, and releases are in CONTRIBUTING.md. Design choices are in Design. Change a decision there before changing the code that depends on it.
Layout¶
| Path | What it is |
|---|---|
src/simscope/ |
The Python package. library.py and recorder.py write rollouts. io/ is the format. mujoco.py and isaaclab.py are the simulator adapters. server/ is simscope serve. |
web/src/core/ |
The player. The app and the <simscope-player> element both use it. index.js is the public surface, typed by index.d.ts. |
web/src/element/ |
The <simscope-player> element used by a lean export and by an mkdeck slide. |
web/src/app/ |
The React app for simscope serve and for a full export. |
src/simscope/_assets/ |
The built player and app. The package ships these files, so a user does not need Node. |
Checks¶
make install
make check
make test
make docs-test
cd web && npm ci && npm test && npm run build
make install installs the tools and the git hooks. make check lints, formats, and type-checks. Node 26 is what CI uses. After a change under web/src/, commit the rebuilt files in src/simscope/_assets/ and web/src/core/decode_worker.generated.js. The build is byte for byte the same on every machine, and CI fails if the commit differs.
Do not add tests/io/__init__.py. Pytest would then treat io as a top-level package and shadow the standard library.
Files an extension has to respect¶
Coordinates are Z-up, right-handed, in metres. Time is in seconds. A pose is [px, py, pz, qx, qy, qz, qw], with the quaternion in xyzw order. Within a stream, flip q to -q when the dot product with the previous frame is negative, so the signs stay continuous.
A library is a folder:
<library>/
.simscope/index.sqlite cache; deleting it loses nothing
.simscope/event_types.json event vocabulary
.simscope/groups.json ordered groups
assets/<ab>/<sha256> meshes and textures, stored once
scenes/<ab>/<sha256>.json static scenes, stored once
runs/<name>/rollout.json manifest; rollout.json.partial while recording
runs/<name>/<stream>.blk frame data
runs/<name>/annotations.json notes, ratings, labels; absent means none
A rollout name matches ^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$. body_pose is required. Frame i is at time i * dt. env_origins is the world offset the viewer adds to each env. Use zeros when the simulator already reports world poses.
A scene's geoms use these kinds. Convert anything else, and turn an unsupported shape into a mesh.
kind |
size |
Shape |
|---|---|---|
box |
[hx, hy, hz] |
Half-extents, centred on the origin |
sphere |
[r, 0, 0] |
Sphere |
capsule |
[r, h, 0] |
Half-length h along local +z, plus caps |
cylinder |
[r, h, 0] |
Half-length h along local +z |
ellipsoid |
[rx, ry, rz] |
Radii |
plane |
[hx, hy, spacing] |
z = 0, facing +z. A zero hx or hy is infinite |
mesh |
[0, 0, 0] |
The referenced mesh |
role is visual or collision. Geom pos and quat are in the parent body's frame.
A stream is one array of shape [n_frames, n_envs, *item_shape].
kind |
item_shape |
Drawn as |
|---|---|---|
pose |
[B, 7] |
Body poses. body_pose uses this |
scalar |
[] |
One plot value |
vector |
[K] |
K plot values, named by labels |
arrows |
[K, 6] |
[ox, oy, oz, vx, vy, vz] in the world frame |
points |
[K, 3] |
World points |
polyline |
[K, 3] |
One polyline |
Name an arrows stream contacts to record contact forces. Each row is the contact point in metres, then the force on the body in newtons. Pad unused slots with zeros. Set units to N.
Curation does not rewrite rollout.json. Notes, ratings, labels, and the group live in annotations.json. A label is an event with an empty type. Highlights are a separate derived file, not part of the sidecar. A rollout belongs to at most one group, named by marks.group. If .simscope/event_types.json is missing, the library uses four types: fall, slip, success, and note.
A reader rejects a major version it does not know, and an unknown codec. It accepts a newer minor version and ignores unknown JSON fields. A byte change goes in src/simscope/io/ and in web/src/core/format.js. A compatible addition bumps the minor version. Anything a current reader would misread bumps the major version.
Extend¶
A simulator. Follow mujoco.py or isaaclab.py. Provide a scene, body poses, and, when the simulator has them, contacts. There is no adapter registry. Add an adapter when a project needs that simulator.
A marker. Register a detector with highlights.register, as in Custom markers. The built-in kinds are contact force and center-of-mass acceleration. A label placed by hand goes through annotations.
An importer. Add it in importers.py and write through Library.record, so meshes are stored once. The current importers read .rbundle files and Brax HTML pages.
The player. web/README.md describes index.js. Tests for the player live in web/test/ and run with node --test.