Changelog
Changelog
All notable changes to this project are documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
Unreleased
5.0.0 - 2026-09-21
A cleanup release: no new features, roughly a thousand lines less code, two Python
dependencies and two vendored JavaScript libraries gone. The scene JSON wire format
and the Python authoring API (SimulationScene, SimViewBody, BodyTrajectory,
SimViewModel) are unchanged — a scene written by 4.x loads in 5.0.0 and vice
versa, and authoring code needs no edits.
Removed
- The PNG-sequence recording format. The viewer's recording dropdown offered
WEBM / MP4 / PNG; PNG captured each frame as a PNG blob and packed the run into a
.tar, which was the only reasontar.jsanddownload.jswere vendored. Both libraries are gone with it. WEBM and MP4 are unaffected, as is the single-frame PNG screenshot button, which is a separate feature. - The
jinja2dependency. It rendered sixurl_for('static', ...)calls that all resolved to the constant/static/prefix;index.htmlnow hardcodes them. einopsfrom theauthoringextra. Its fourrearrange()calls are expressible in plaintorch, whichmodel.pyalready requires. If your own code importseinopswhile relying onsimview[authoring]to install it, declare it yourself.
Changed
- The CLI now uses real argparse subcommands. Every documented invocation is
unchanged —
simview info <file>,simview diff <file> --batches 0 1,simview terrain <file> --area --layer all --json— and output, CSV/JSON rendering and the0/1/2exit codes are byte-for-byte identical. Per-subcommand--helpis now scoped, so flags no longer have to explain which subcommand they belong to. Breaking: a flag written before its subcommand (simview --json info x.json) is no longer accepted. Write it after the file, as the documentation has always shown:simview info x.json --json. - The
grayscale,heatmapandterraincolor maps now come from the bundled matplotlib maps (Greys_r,jet,terrain) instead of hand-rolled ramps. Scenes using those three names by name will look slightly different; every other map, including the defaultmagma, is unchanged. /blobRange handling now honours onlybytes=start-end, the one form the viewer sends. Suffix (bytes=-N), open-ended (bytes=N-) and multi-range requests serve the whole blob with200instead of a partial response, which RFC 9110 permits for a range a server declines. A well-formed but out-of-range request still returns416.
Internal
- The stdlib-only inspection tools (
info,diff,terrain) no longer hand-copy the same blob decoding, body-name resolution and scene loading; the shared helpers live incolumnar.pyandutils.py.terrain.py's cross-batch queries now call their single-batch counterparts instead of duplicating them. - 441 lines of CSS-in-JS moved from six UI panels into
controls.css, and the three uPlot panels share one chart-setup helper. - Dead frontend config removed (
GROUND_CONFIG,POINT_VECTOR_CONFIG,CONTACT_CONFIG,createArrows, and sixCONTROLS_CONFIGkeys that either restated three.js defaults or overwrote an OrbitControls method with a boolean).
4.2.1 - 2026-09-02
Added
- "Show Point Clouds" toggle in Body Options, for scenes that contain point-cloud bodies. Point clouds are no longer governed by Body Visualization Mode (see below), so this is how they are hidden and shown.
Fixed
- Mesh bodies no longer disappear once a run travels away from where it started. Bodies are drawn as instanced meshes positioned per instance, and THREE caches an instanced mesh's bounding sphere the first time it is frustum-tested and never invalidates it when the instances move — so every mesh body was culled wholesale as soon as its starting position left the view. Long recordings (a robot driving a few hundred metres) lost their bodies mid-playback while the terrain kept rendering.
- Selecting the "points" visualization mode no longer blanks out every mesh body.
A point-cloud body is the object itself rather than a way of drawing a body, so it no
longer contributes "points" to the mode list, and Body Visualization Mode —
noneincluded — leaves point clouds alone. In a mixed scene (a robot plus a lidar cloud) the mode offered onlypoints, which no mesh body could render. - Static point clouds are no longer listed in the Body states panel, where they showed a permanently-zero Position/Rotation. A point cloud that does carry per-frame data stays listed like any other body.
- The Analysis panel's scalar chart now actually draws. It had been painting
nothing at all: its x scale was never pinned (uPlot treats a scale's
min/maxas outputs —rangeis the pin — so the initial autoscale over empty data nulled it and no later update revisited it), the render loop redrew without rebuilding paths, so the chart kept redrawing whatever data it first saw, and the y axis offered a single tick increment that could not fit the panel's height, so uPlot drew no ticks or labels. An end-to-end test now asserts the chart paints its data and its axis rather than merely that the panel opens. - The camera's far plane and orbit limit now follow the terrain extent instead of being fixed at 500 m, so terrain and bodies on scenes hundreds of metres across no longer clip away, and the whole scene can be framed.
4.2.0 - 2026-08-31
Added
- Merging a subset of a file's batches. Append
#<batches>to any input file to contribute only some of its batches to the merged scene, e.g.simview gt.json method_a.json#1 method_b.json#1— so several files that each carry their own copy of a shared ground truth can be compared without merging that ground truth once per file. The selector takes indices (#1), comma-separated lists (#0,2), inclusive ranges (#1-3), negative indices (#-1) and the file's ownbatchNames(#ours); merged batch names keep the source file's index (run[2]). Works with--save-merged, with remote inputs, and on a single file (to view only some of its batches).merge_simulation_files(paths, selections=...)is the Python equivalent. See CLI. create_terrain(property_bounds=...)pins the color-scale range of a named terrain property explicitly, e.g.property_bounds={"friction": (0.0, 1.0)}, instead of always deriving it from that map's own min/max — so the same scale (and legend) stays comparable across scenes. Properties left out keep the data-range default, and cells outside an explicit range saturate at the end colors.- Each Error Metrics readout is prefixed with a swatch in its plot series color, so the readout doubles as a legend for the curves below it.
Fixed
- Nested fields in the Scene Info panel now start collapsed. A scene with rich metadata used to expand every nested key at once, burying the top-level entries.
4.1.0 - 2026-08-19
Added
- Remote scene files. Anywhere the CLI takes an input file (view,
info,diff,terrain,render, and multi-file merges) it now also accepts an scp-stylehost:path, fetched over the systemsshand cached locally. Transfers are compressed (gzipped on the remote, orssh -Cwhen it has nogzip), and the cache entry carries the remote file's mtime, so freshness is a plainstatcomparison. Adds--refresh/--offline, andsimview clearknows about the new cache. See CLI. - The columnar ("v4") states layout is now an on-disk format, not just a
server-side repack:
SimulationScene.save()writes it by default, producing substantially smaller files that the viewer can load without any repacking. Passcolumnar=Falsefor the previous per-frame layout, orcolumnar=Trueto raise instead of falling back when a scene is too irregular to pack. See the JSON Format Specification. simview inforeports whichstateslayout a file uses.- Episodes. An episodic (e.g. RL) recording can mark its resets with
SimulationScene.mark_episode()/LiveViewer.mark_episode(), serialized as an optionalmodel.episodesarray. The viewer ticks episode boundaries on the playback bar, adds |◀ / ▶| navigation ([/]), and overlays per-episode aggregates (including the episode return) on the scalar plots. See Episodes. - Focused-batch rendering. Above 32 batches the viewer now draws only the focused batch by default, and builds each batch's per-batch scene objects (axes, arrows, point clouds, contact points) the first time it's rendered rather than all up front — so a scene with hundreds of parallel envs loads and runs like a single-env one. Toggle with "Render All Batches" in Body Options.
- Windowed state loading. The
/blobendpoint now supports HTTP Range requests, and the viewer uses them to stream the large current-frame-only fields (velocity,angularVelocity,force,torque) in ~1 MB windows around the playhead instead of materializing whole(T, B, k)runs.bodyTransformand the scalars stay fully resident, since trails, error metrics, the terrain profile and the scalar plots all walk the entire trajectory.
Changed
SimulationScene.save()writes columnarstatesby default. Files stay readable byload,merge_simulation_files,simview info/diff/terrainand the viewer either way, but a third-party tool that parsesstatesas a JSON array will need to handle the object form (or be passedcolumnar=False).- A fully shared (singleton) terrain now ships exactly one copy of its height, normals, properties and embedding data instead of one identical copy per batch — a real saving for many-batch (e.g. RL-scale) scenes. Readers resolve the layout from the data length, so files written by older versions (which broadcast the singleton) keep working; a mixed terrain still broadcasts its shared fields.
Fixed
LiveViewer.push_stateno longer blocks the simulation loop on a slow or hung viewer tab (up to 5 s per frame before). Frames go through a bounded queue drained by a sender thread, dropping the oldest pending frame when it fills so the live view keeps tracking the simulation; dropped frames still land inscene.statesand the catch-up buffer, andstop()flushes the backlog.- The live frame buffer is now bounded (
frame_buffer_size, 10k frames by default) instead of growing for the length of a run, and a viewer connecting mid-run replays the recent window in 500-frame slices rather than onejson.dumpsof the whole history. It is registered for live broadcasts only once the replay has caught up, so a new frame can no longer overtake the history it follows. - Trails are now appended to in place instead of being rebuilt per loaded chunk, which in live mode cost O(T²) geometry reallocation over a run.
simview diffresolves parent chains and compares world poses, like the viewer's Error Metrics panel does — the two used to report different numbers for the same scene. Rigidly-attached bodies (a constantlocalTransform, absent fromstates) are now diffable and addressable via--body.- Mesh bodies authored from tensors crashed the viewer on load:
createGeometrycalled.flat()on vertices that always arrive blob-decoded as flatFloat32Arrays. Meshes are also indexed withUint32Arraynow, so more than 65535 vertices no longer wrap. - Serving an in-memory scene (
show(),LiveViewer,SimViewLauncher) no longer rewrites the caller's model in place, which left a latersave()writing dead/blob/...URLs and a secondshow()serving stale blob references. merge_simulation_filescarries terrainembeddingData(the features color mode was silently lost on merged scenes) and each model'smetadata(namespaced undermetadata.sources) through the merge, groups b64-decoded terrain normals back into per-vertex vec3s, and rejects inputs whose terrain x/y bounds differ instead of merging them into spatially misaligned batches.add_statenormalizes a 0-dim scalar tensor/array to the per-batch list every other frame stores, whichmerge_simulation_filesused to raise on.SimulationScene's internal-data cleanup frees the terrain's per-cellembedding_datatoo, potentially the largest of its arrays.- A blob fetch that returns an HTTP error now fails loudly on the load-error splash
instead of being decoded as float32 garbage, and inline
__b64__state fields decode endian-safely like the standalone blob path already did. setActiveBatchno longer forwards an out-of-range batch index to the body state window, scalar plotter and batch legend right after warning that it rejected it.- Dropped
allow_credentialsfrom the CORS config: combined with the any-localhost-port origin regex it let any other local dev server read scene data with the user's credentials. The API uses no cookies or auth headers, so nothing needed it.
Removed
- The ctrl+drag box-selection code path, which never worked — it crashed on mouseup, raycast against an always-empty list, and checked event keys that don't exist. Click handling, the data probe tooltip and shift+arrow batch switching are unaffected.
4.0.0 - 2026-08-04
⚠ Breaking changes
- Scene JSON files saved by older versions of simview will not load. Terrain's
frictionData/stiffnessDatafields and theirbounds.minFriction/maxFriction/minStiffness/maxStiffnessentries are replaced by a genericterrain.propertiesobject ({name: {data, min, max}}— see the JSON Format Specification). Re-save any existing scene file with the current version ofsimview(orSimulationScene.load()+save()) to pick up the new format; there is no automatic migration. scene.create_terrain()/SimViewModel.create_terrain()no longer acceptfriction_map=/stiffness_map=; passproperties={"friction": ..., "stiffness": ...}instead (or any other named per-cell scalar map — see below).- The batch-names sidecar file (
.<scene>.<hash>.batchnames.json) written before staleness-fingerprinting was added (pre-3.x) is no longer read; a freshPOST /batch-namesregenerates it in the current format. - The model JSON's long-superseded
batchSizefield (renamed tosimBatchesseveral releases ago) is no longer read as a fallback.
Added
- Terrain scalar properties (friction, stiffness, or any other per-cell field) are
now a fully generic, arbitrarily-named mechanism end to end — Python
(
SimViewTerrain.properties), the CLI (simview terrain --layer <name>,simview info,simview merge), and the viewer (color mode dropdown, Legend, hover/probe tooltip, Terrain Profile tab) all support any property name supplied at authoring time, with zero code changes required to add a new one. scene.create_pointcloud()now accepts optionalcolor(static per-point RGB) andembedding(per-point feature vector) tensors;scene.create_terrain()gains a matchingembedding_map(per-cell feature vector). When present, clicking a point or terrain cell recolors the whole body/grid by cosine similarity to the clicked location, computed client-side — a new "similarity" Point Color Mode for point clouds and "features" terrain color mode, both with a matching colormap legend.
Fixed
- Clicking now only recolors a point cloud/terrain by similarity when the matching mode is already selected from its dropdown; otherwise it's an ordinary selection, and a click does nothing at all unless "Data Probe" or similarity mode is active.
- The Analysis panel's "Terrain" tab no longer appears for bodies with no trajectory (e.g. a static point cloud), and now plots the whole trajectory up front instead of only revealing it progressively during playback.
- "Scene Info" now shows full metadata keys/values instead of truncating them.
3.6 - 2026-07-29
Added
SimulationScene/SimViewModelnow accept an optional free-formmetadatadict (e.g. engine name, checkpoint path, git commit, CLI args) carried through to the saved JSON,simview info, and a read-only "Scene Info" panel in the browser, so a scene stays self-describing long after it was generated.scene.create_terrain()now auto-computes normals from the heightmap gradients ifnormalsis omitted.scene.create_terrain()now acceptsgrid_resto auto-infer spatialx_lim/y_limconstraints instead of requiring manual definition.simview terrain <file> --along-body BODY: sample terrain layer(s) bilinear-interpolated at a body's per-frame (x, y) position — "what terrain is under the robot's driven path". With--batches A B, both batches' terrains are sampled along batch A's (reference, typically ground-truth) trajectory and reported asvalue_a/value_b/deltaper layer, so the delta reflects property differences under the path rather than trajectory divergence. Honors--layer/--everyand the usual--json/--csvoutput modes.- "Terrain" tab in the browser Analysis panel: plots a terrain layer
(height/friction/stiffness) sampled under a body's path over time, one
series per batch, with layer/body pickers, a path picker ("own path"
per batch, or every batch's terrain along one reference batch's path —
e.g. ground truth's), playback-synced reveal, click-to-seek, and CSV
export. The browser-side counterpart of
simview terrain --along-body. --fail-on-exceedflag forsimview diff: exits with code 2 (after printing the normal report) when any diffed body's trajectory exceeds--pos-threshold/--rot-threshold-deg, and 0 when within them -- distinct from the usage/parse-error exit 1, so scripts and CI can use an exported scene as a regression tripwire.
Fixed
- Focusing a batch while a Scalars plot was open threw
s.stroke is not a functionon the next redraw (uPlot expectsseries.stroketo stay a function; the focus handler was overwriting it with a color string).
3.5 - 2026-07-28
Added
simview render <file> --output frame.png: headless PNG screenshot via a real (headless) browser driving a realSimViewServerinstance, with--view/--width/--heightoptions. Ships as a new optionalrenderextra rather than a hard dependency.- Terrain diff color overlay: a "diff" color mode (diverging colormap centered on zero) plus Diff Layer/Batch A/Batch B pickers in Terrain Options, with a matching diverging colorbar in the Legend.
- The terrain data probe now shows every batch's height/friction/stiffness at the hovered cell, plus each one's delta from a reference batch, instead of just the hovered batch.
--per-axisflag forsimview diff, reporting signederr_x/err_y/err_z(batch A minus batch B) per frame in--json/--csvoutput.- Mean/min
|delta|stats (alongside the existing max) insimview terrain --batches --areaoutput. - Full documentation site (MkDocs + mkdocs-material + mkdocstrings) covering
usage, the CLI, the JSON format specification, the API reference, and a
developer guide, published to GitHub Pages.
README.mdis trimmed to a landing page that links to it.
Changed
- The Error Metrics panel auto-selects a sensible Batch A/B default from batch names (e.g. ground-truth vs. post-adaptation) instead of always defaulting to indices 0/1, falling back to 0/1 when no batch name matches.
3.4 - 2026-07-27
Added
simview info <file>: a structural summary of a scene JSON (model/terrain/ body/state breakdown, columnar-repack eligibility, consistency warnings) in human-readable text or--json.simview terrain <file> --point/--area: numeric height/friction/stiffness queries (bilinear-interpolated at a point, or a raw grid over an area), plus--batches A Bto compare two batches (value_a/value_b/deltaper layer).simview diff <file> --batches A B: per-frame position/orientation divergence between two batches' trajectories, with--body/--every/--pos-threshold/--rot-threshold-degoptions.--csvoutput forsimview diffandsimview terrain, alongside the existing--json.
Changed
- Only emit uvicorn's access log when running in debug mode.
3.3 - 2026-07-17
Added
- Expose the installed package version as
simview.__version__. - Test against Python 3.14 in CI and advertise it in the package classifiers.
- Dependabot configuration for GitHub Actions, npm, and Python dependencies.
Changed
- Bump the PyPI development-status classifier to
5 - Production/Stable. - Use uvicorn's modern sansio websocket implementation for the live server when
available, silencing the
websockets.legacydeprecation warning. - Raise the CI coverage floor from 80% to 83%.
Removed
- Unused
collapsedMode/focusedModeplaceholder flags fromBatchManager.
3.2 - 2026-07-15
Added
- Shareable view links: the current camera/playback state is encoded in the URL hash so a view can be restored or handed off.
- Single-frame PNG screenshot export.
Changed
- Prepare packaging for PyPI publishing (metadata, build, publish workflow).
- Replace CCapture with the browser-native
MediaRecorderfor video recording, covered by an e2e test.
Fixed
- Unblock CI: guard the optional
numpyimport and add--no-launchtoexample.py. - Plot visualization fixes.
3.1 - 2026-07-14
Added
- Live streaming mode:
LiveViewerpushes states to connected browser tabs over WebSocket as a simulation runs. - Non-blocking
scene.show()with Jupyter iframe support (_repr_html_). - Smooth interpolated playback (position lerp + quaternion slerp) with a toggle.
- Error-metric summary stats and CSV export in the analysis panel.
Changed
- Vendor three.js and chroma-js locally so the viewer works fully offline.
- Serve states as per-body whole-trajectory binary columns ("v4" columnar
repack), backed by a
Float32ArrayStateStore, for much cheaper playback of long recordings.
Fixed
- Binary-search seek for non-uniform timelines, parallel blob fetches, and versioned immutable blob URLs.
Testing / infrastructure
- Add vitest + Playwright frontend tests, pyright type checking, and a CI coverage floor.
3.0 - 2026-07-13
Baseline release. Highlights of the surface established by this version:
- Authoring API —
SimulationScenewith incremental model building,add_state/add_trajectory(batched, binary-encoded), gzip support, and JSON save/load. - Wire format — HTTP-served
model/states, binary-encoded numeric fields, parent-relative (rigid and articulated) body transforms, grouped body names. - Frontend — vanilla-JS/THREE.js viewer with batched split-screen comparison, camera tracking, trajectory trails, terrain data probe, a unified Analysis panel (Scalars + Error Metrics, plotted with uPlot), and synchronized timeline scrubbing.
- Tooling — CLI (
simviewview /clear/--save-merged), multi-file merge pipeline, CORS-hardened server with cache headers,py.typed, and CI across Python 3.12/3.13 with a base-install-only check.