Skip to content

Roadmap

What Tetravox does today, and what is still open. The contract is docs/ARCHITECTURE.md; every decision behind a line here is in docs/DECISIONS.md.

What exists

Diffusion tensors (§6.1, §7.3). Explicit six-component FSL/SimNIBS or NIfTI symmetric-matrix interpretation, independent axis convention, direction-coloured ellipsoid slices, glyph spacing and FA filtering. Ordinary volume frames remain the default. See the 2026-09-19 decision and tests/tensors.rs / tensor.spec.ts.

Volume controls (§8). Grouped contrast/visibility with 1–99%, 50–99.9%, 95–99.9% presets; labels use region controls. Intent is recorded in docs/DECISIONS.md; verification is in packages/app/e2e/props-volume.spec.ts.

Formats. NIfTI-1/2 (.nii, .nii.gz, 4D, every dtype but complex and 64-bit ints), Gmsh .msh v2.2 and v4.1 with $NodeData/$ElementData fields and .msh.opt sidecars, Gmsh parsed views (.geo/.pos, e.g. SimNIBS electrode nets), GIfTI, FreeSurfer surfaces / curv / annot, STL, PLY, OBJ. LUT sidecars in FreeSurfer, SimNIBS, ITK-SNAP and generic formats.

Volumes. Slice compositing with N layers per plane, the full Scale model (linear and heat with min/mid/max, negative branch, truncate, inverse), thresholds with a soft edge, 15 colormaps plus user-defined ones, label fill/outline/both over a dense-index palette, per-region show/hide/opacity/recolour, 4D frame stepping, and showIn3D slice planes in the 3D pane. A volume layer carries its own 3D isosurfaces, one per visible region for a label volume.

Surfaces. A triangle-only file (GIfTI, FreeSurfer, STL/PLY/OBJ/OFF, .vtp) is a surface layer with its own model and editor: one colour source — solid, a per-vertex overlay, an atlas annotation — attached from a .annot, morph file or data-only GIfTI; the 2D outline; clip planes. Drawn by the mesh passes.

Meshes. Tagged tissue surfaces, boundary extraction for tri-less tet meshes, node and element field colouring with component selection, up to six clip planes with exact per-element caps and a drag gizmo, element isolation (tags / field range / sphere / box / label volume), masked-barycentric element edges, vector glyphs with four scaling modes, two-phase transparency with depth-resolved sheets (§7.2), and mesh cross-sections in the 2D panes — filled polygons and tissue-boundary contours that sweep with the slice, with or without a volume loaded. Surfaces draw their intersection with each 2D plane as a Freeview-style contour by default.

Views. Linked 3D + sagittal/axial/coronal panes in four layouts, all containing the 3D pane; oblique planes throughout; per-pane zoom about the pointer; Freeview-style mouse handling (left-click sets the crosshair, never pans); ID picking; orientation letters, corner info, RAD/NEU badge, scale bar, orientation cube, colour bars and crosshair as toggleable chrome; light and dark themes.

Coordinates. World RAS, per-volume voxel and FreeSurfer tkr-RAS, MNI through a SimNIBS toMNI/ folder (affine and nonlinear reported separately), surface vertex index, and an fsaverage vertex + coordinate when a sphere.reg and an fsaverage subject are both on disk. Typed entry converts back in any of them.

Tools. A measurement tool (distance and angle in world mm, drawn in every pane that contains its points, saved in the scene), a region panel for label volumes / mesh tags / annots, a histogram widget with draggable window and threshold handles, and a screenshot dialog writing DPI into the PNG.

Extensions. A first-party extension surface (§13): a docked panel slot in the right column with one active extension at a time, a toolbar switcher, a status cell, a key pool that resolves after the §7.5 map, a confirm dialog, per-module scene blocks carried through save and load — including for extensions a build does not have — and an import wall that keeps an extension to ModuleHost. The host is complete and frozen (§12.3 item 6): its scene, tool and file members are the engine's point tool, the engine's bounded local reads and the main-process IO channels. The fixture extension tetravox.hello (?modules=hello) is the worked example; the sEEG contact editor is the first real one.

The point-set substrate. Per-point identity (id / group / ordinal), off-plane ghosting, a selection ring, sampleVoxelBox / peakCentroid, and a 2D point tool with place, select and drag — the engine half every later point-set tool inherits.

Extension file IO. A read-text channel capped at 1 MiB over the existing allow-list, a Save sheet that admits the manifest's same-directory siblings into a per-module write list, a main-side timestamped .bak and a temp+rename write, and the codebase's first BrowserWindow 'close' guard on unsaved extension edits.

Downloadable extensions (§13.8). An extension can now arrive from outside the build: a third tetravox:// host serving a verified-file map rather than a directory, a content-addressed install store with the sample data's own hashing discipline, a per-version consent record whose permission list is derived from the manifest, File ▸ Extensions… on the Sample Data dialog's shape, a one-variable dynamic import with a build-output guard, and @tetravox/module-sdk reaching the host's React through one global. Nothing is bundled into a release (2026-08-31): the catalogue is the one route in. An enabled extension is trusted renderer code; the trust boundary is the consent sheet and the registry's review, and the CSP only stops unconsented script.

Scenes. *.tetravox.json — every layer setting, region edit, measurement, camera, layout and the theme. ⌘S / Save As / Open Recent, drop-to-open, file association, relative paths with a fingerprint-keyed relocate dialog, and an optional reopen-on-launch.

Automation. Tetravox --job job.json --out DIR runs the app offscreen and executes set / screenshot / sweep / orbit / tween actions into PNGs, GIFs and MP4s. A stdlib-only Python client wraps it. See /automation.

Native integration. External applications open local scene files through the packaged executable. The retired browser package is no longer built or tested. Managed launches leave updates to their installer.

Verification. 235 Rust tests, 1,128 vitest tests, 66 Playwright specs and 40 goldens; analytic pixel assertions on synthetic fixtures plus a pure-Python reference renderer for pane-scale slice diffs; real-data tests against sub-ernie gated on TETRAVOX_TESTDATA. CI on ubuntu-24.04 (the golden authority) and macOS, with the packaging matrix carried in the workflow.

What is next

Correctness and coverage

  • §11's Transparency (ii) — a CPU per-fragment-sorted reference render of a mesh scene. The Python reference renderer covers volume slices only; §5 keeps a mesh's triangles out of a spec's reach, so this needs its own vehicle rather than a weaker test wearing the name.
  • §9.1 rows 15, 16 and 17b have no measurement yet.

Rendering

  • Transparency upgrade: dual depth peeling preferred over WBOIT — measured depth complexity is 4–6 median / 8–10 p90, so 6 peels covers p90, WebGL2 has core occlusion queries for adaptive early-out, and 6 × 413 k tris is inside the 8 ms budget. If WBOIT is kept as the > 8-layer fallback, its depth weight must be re-parameterised against scene bounds rather than copied from the paper.
  • Progressive refinement: 8–16 jittered frames accumulated into RGBA16F while the camera is still (§7.0.7), shared by the screenshot path. This is also what unblocks the msaa quality knob.
  • Label minification AA. Nearest-sampled label volumes drop thin structure when a screen pixel covers more than one voxel — 3 of 28 labels present in a view are never sampled at 1 mm/px. It affects fill and outline identically, so it is a sampler item, not an outline-formula one. Integer 3D textures cannot be mipmapped, so the options are (a) a 2×2 rotated-grid supersample of the centre label sample when pxInVoxels > 1, or (b) a worker-precomputed filterable coverage volume. Prefer (a) first and measure.
  • Volume raycasting (MIP + composite with a transfer function) for 3D voxel rendering.
  • Clip planes on isosurfaces — §7.2's iso draw disables clip distances today.
  • capDecimation as a live quality knob: plane_cut must be able to emit fewer cap triangles.
  • mosaic layout ({plane, startMm, stepMm, rows, cols}).

Performance

  • OpArgs['cut'] has no wantEdges / wantBoundary, so the worker builds and ships the 2D overlay's edge and boundary segments even on the 3D clip path, which discards both. Frozen interface — an additive change.
  • Typed-array pooling, oct-encoded normals (85 → 57 MB for ernie's de-indexed variant), instanced points.

Extensions (§13)

  • The sEEG contact editor: BIDS electrodes.tsv in and out, intensity-weighted snap, shaft re-fit, renumbering, an editlog beside the table and a .bak of what it replaced.
  • Job-file operations ({"type":"module", …}) and their Python wrappers, so every panel action is reachable headlessly.
  • A resizable right aside. The column is 320 px and the slot lives in it; there is no splitter primitive in the shell, and an extension with a long list wants one. A wide manifest hint was considered and dropped — widening the aside shrinks the toolbar's centre column below what its controls need, which wraps the row.
  • host.scene.sampleVolume as a §6.5 op (2026-09-03). It is bounded at 2,000,000 points and chunked so it never blocks the UI thread, but it still reads VolumeDataset.data on that thread; the volume is already in the dataset's wasm worker, so the honest home for it is a Rust op beside labelCentroids. The host member is a promise precisely so that move changes nothing an extension can see.
  • Stage 3, the sandboxed tier (§13.9): an extension in a Worker with no DOM, a JSON-only host bridge, permissions with enforcement rather than only disclosure, a Restricted Mode for a scene naming an unknown extension, and a security review. 8–9 days and a different threat model; the import wall exists so that day is a port, not a rewrite. It is the tier that would let an extension be untrusted — §13.8's are not.
  • The extension registry and its website page: idossha/tetravox-extensions' index.json, the review policy that is the trust gate, and a /extensions page built from the same index the app ships.
  • A live catalogue fetch. The dialog reads the index the build shipped, which is why it is correct offline; fetching the live one with a timeout and a silent fallback is wanted, and is the app's first outbound request that is not a user-initiated download.
  • Tighten allowPath itself (§5 rules 9–11). tetravox:allow-path admits any existing absolute path the renderer names, with no gesture, and every other admission in the app sits on top of it. Both write lists are therefore only as strong as the renderer: the scene carve-out is now minted where main hands the path over, and an extension's admissions are revoked when its editing session ends (2026-08-30), but each of those scopes an accident — a compromised renderer simply never sends the revocation. The real fix is an allowPath that admits only what a gesture, a sidecar rule or an opened dataset's own directory justifies. It is a change to the oldest security surface in the app, and it is wanted before §13.9's sandboxed tier. It would not contain a malicious extension — one runs in the renderer with the whole bridge — but it is what would make allowPath the last line rather than the first.
  • An engine command stack, so undo is the engine's rather than each extension keeping snapshots.
  • A Compute panel: an extension that shells out to an external tool with a streamed log. It needs the first async subprocess in main, argv validated token by token, a scrubbed environment and a notarisation check.

Packaging

  • The §12.1 package legs green end to end: .dmg arm64 + x64, .AppImage, .deb, each opening ernie.msh and passing the artefact smoke test.
  • macOS signing is live in release.yml; in-app updates (ARCHITECTURE §12.4) ride on it since 2026-08-31.

2026-09-04 — View controls and screenshot layout (§7.5, §8)

  • Direct anatomical slice buttons; 3D+1 retired from the UI with legacy-scene migration to 1+3.
  • Compact capture dialog checked by packages/app/e2e/view-controls.spec.ts at the minimum window size.
  • Reset / Home checked against the real engine's shared cursor as well as the coordinate readout.

Verification: pnpm exec vitest run — 1849 passed, 83 skipped; app build, lint and app typechecks passed. TETRAVOX_TESTDATA=… pnpm --filter @tetravox/app exec playwright test e2e/view-controls.spec.ts e2e/shell.spec.ts e2e/shell-phase2.spec.ts e2e/reset-all.spec.ts --project=dev — 37 passed, including the Ernie T1 case. The quiet audit could not certify screen ownership because pre-existing Tetravox windows were visible and the user changed focus; test launches remained offscreen.

MIT licensed. macOS and Linux first; a Windows build is published too.