# Rendering Performance

## Performance Rules

v_ase keeps the lightweight viewer path independent from optional editing and
rendering costs:

- visualization mode does not attach the fallback calculator or invoke
  interactive edit paths;
- changing to Edit materializes lazy trajectory frames once, with a visible
  busy state, before ASE-backed editing is enabled;
- Modeling lighting creates no shadow map;
- renderer updates are demand-driven;
- inactive document tabs suspend rendering and playback.

The viewport uses GPU instancing for atoms, bonds, selections, and supercell
replicas. Large scenes reduce device pixel ratio adaptively, reuse geometry and
materials, and avoid one JavaScript/Three.js object per visible atom.

Displacement analysis is opt-in. When hidden, it performs no request and
allocates no geometry. When enabled, all vectors share one instanced shaft
mesh and one instanced head mesh. Changing arrow style, scale, thickness, or
color updates the cached GPU batches without repeating the ASE/MIC calculation.
Backend mapping runs off the event loop and shows a delayed busy state only
when it is not effectively instantaneous.

Volumetric analysis is also opt-in. Scalar grids remain bounded FP32 or FP64
arrays in the backend under `V_ASE_MAX_VOLUMETRIC_POINTS`; FP64 intentionally
uses twice the grid memory. The browser receives only descriptors and the
currently requested indexed float32 mesh, never the complete grid. Marching
cubes and RDF run through worker threads instead of blocking the FastAPI event
loop. Surface `stepSize` 2 or 4 reduces preview geometry explicitly; final
scientific extraction retains step 1. Optional field smearing retains only
the latest filtered grid per dataset and is reused by both signs of a signed
surface. Mesh fairing uses sparse vertex adjacency and does no work at zero
passes. Hidden surfaces allocate no Three.js mesh.

Planar sections cache the cell inverse once per dataset and build one compact
`3 x height x width` fractional-coordinate buffer directly, avoiding full
Cartesian point and meshgrid copies. Identical requests reuse the cached
raster. Live `G/R` previews request only selected planes at reduced resolution;
settling restores only those planes at their configured resolution. Displayed
supercells resample the periodic 2D section rather than repeating the full 3D
grid.

RDF uses one matscipy periodic neighbor-list pass at the requested cutoff.
The search enumerates every contributing periodic shift, so a long cutoff is
not truncated to a prebuilt supercell. Total and selected partial histograms
share that pass. The adapter preserves the physical periodic lattice vectors
and expands only finite search directions for partial-PBC data, preventing a
finite axis from being wrapped without returning to a Python all-pairs scan.
Plotly is loaded from the local Python installation and the
drawer is created only when analysis is requested. After the first trajectory RDF
request, ordinary trajectories precompute every frame with two concurrent
backend workers. Large frame/bin/curve products use a bounded circular cache
covering up to eight frames behind and 32 ahead of the displayed frame; the
window contracts further when many bins or partial curves would exceed its
value budget, and entries outside the new window are evicted. Playback
swaps cached results without purging Plotly; on a miss, the previous graph
stays visible until the requested frame finishes. Structure, label, pair-mode,
cutoff, or bin changes invalidate the cache. CSV reuses the same calculation
function rather than scraping or resampling the plotted curve.

Video interpolation is also opt-in. `1x` follows the existing one-render-per-
source-frame path. Higher multipliers retain only two flattened endpoint frames
at a time, generate one temporary subframe buffer, and reuse the existing atom,
bond, selection, constraint, and supercell batches. No interpolated frame is
stored in the backend trajectory.
Canvas capture uses manual frame requests when the browser supports them.
Transcoding enforces the selected FPS and expected output count, so a 60 Hz
display cannot silently multiply identical video frames. The final transcode
rebuilds constant timestamps as `frame_index / FPS`; variable browser render
latency therefore cannot shorten or stutter the movie.

## Output Encoding

The render canvas is always created at the user-selected width and height.
Storage optimization occurs after rendering:

- PNG is the user-facing image default;
- lossless WebP remains available for compact exact-RGBA storage;
- PNG concatenates and validates the browser IDAT stream, then performs a
  higher-effort lossless DEFLATE pass while preserving all non-IDAT chunks;
- PNG falls back to the original browser bytes when recompression is larger;
- JPEG uses optimized progressive 4:4:4 encoding and an opaque white
  background;
- PDF embeds one full-resolution raster page at the requested pixel size;
- MOV uses H.264 with a slow preset and quality-based rate control;
- AVI uses MPEG-4 quality-based rate control.

None of these paths resize the canvas, lower the atom sphere quality, or alter
the selected renderer. Browser recording retains a high-quality intermediate
bitrate; only the final ffmpeg pass performs storage compression. The browser
sends binary image blobs instead of base64 strings so large captures do not
require an additional 33% text copy.

Reference storage check on the development Mac:

| 1920 x 1080 workload | Previous/output bytes | Optimized bytes | Reduction |
| --- | ---: | ---: | ---: |
| Lossless README overview image | 532,460 PNG | 340,158 WebP | 36.1% |
| 3 s, 30 FPS MOV transcode | 3,699,245 | 3,035,709 | 17.9% |

The image comparison decoded to byte-identical RGBA. The video comparison kept
all 90 frames at 1920 x 1080; it changes only the final H.264 encoding profile.

## Large LAMMPS Pipeline

Numeric LAMMPS text dumps use:

1. memory-mapped frame boundary indexing;
2. one ASE template from the first requested frame;
3. byte-range numeric parsing through NumPy;
4. one contiguous float32 trajectory payload for browser playback;
5. translation-only instance-matrix updates for each frame.

The initial browser JSON never embeds every trajectory frame. Manual frame
scrubbing can read one indexed frame and synchronize Python; active playback
uses the browser's binary cache with no per-frame HTTP or JSON.

The local Uvicorn server is readiness-polled instead of using a fixed startup
sleep. `ASEEditor.close()` and blocking session finalization stop and join the
owned server, so repeated test and API sessions do not leave background
threads.

## Bond Pipeline

Automatic and pair-specification bonds use a spatial cell list above the
small-scene threshold. For repeated cells, one periodic candidate search
supplies both base-cell topology and internal supercell bridge records. Manual
pairs bypass neighbor inference.

Large scenes cache a `maximum cutoff + skin` neighbor candidate list. Actual
distances and pair maximum cutoffs are still evaluated every frame, so
bonds form and break live. The candidate list is rebuilt when an atom moves
more than half the skin or when labels, visibility, cutoffs, cell, PBC,
periodic policy, or constraints change. Cylinder instance matrices are written
directly to the GPU buffer; unchanged topology reuses the existing instanced
bond batches.

The Metal preset creates one shared 192 x 96 studio environment and converts it
to a PMREM texture on first use. It is reused by all metal atom groups.
Standard/rubber-only scenes skip this work, and metal atoms remain instanced by
material group.

Bond controls use a dedicated animation-frame-coalesced update path. Editing a
pair range or bond material does not reparse the Appearance table, supercell
inputs, transform controls, or export settings.

## Add Atoms Benchmark

Random insertion generates coordinates in one vectorized NumPy allocation.
Triclinic unit-cell mode maps a uniform fractional array through the complete
cell matrix. Multi-region mode partitions Cartesian space only at region faces,
marks Allow/Reject membership vectorially, and evaluates only triclinic-cell
boundary partitions as convex-polyhedron intersections. The resulting volume
is exact rather than voxelized. Sampling uses batched vectorized membership;
its cost scales with requested count and accessible-volume fraction. Periodic
region images are generated only for nonzero lattice translations that
intersect the primary cell. The renderer keeps one intact source cuboid per
region, batches all deduplicated wrapped edges for that region into one
instanced draw, and creates no periodic geometry while region MIC is off.
Pairwise placement uses ASE's periodic
neighbor list rather than an all-pairs distance matrix, and only tag-3 mobile
atoms receive forces when the host-fixing option is enabled.

Reference result on the project development Mac with Python 3.13:

| Check | Result |
| --- | ---: |
| 100,000 triclinic unit-cell samples | 0.0026 s |
| 100,000 Cartesian-box samples, 79.6% accepted | 0.0271 s |
| 15,000 host + 100 mobile MIC force evaluation | 0.989 s |
| Nonzero host force entries while fixed | 0 |

The scientific regressions separately verify deterministic seeds, fractional
voxel occupancy, Cartesian-box containment, MIC across a periodic boundary,
multi-Allow-minus-multi-Reject analytic volumes against an independent
half-space/ConvexHull reference, lattice-translation invariance for triclinic
wrapped regions, exact molecule density conversion, and exact host restoration
during both normal completion and cancellation, including cancellation while
an optimizer commit is in flight. Additional regressions cover primitive-ratio
reduction, strict integer counts, active-session MIC synchronization, and the
shortest periodic confinement force at orthogonal and triclinic boundaries.

## Browser Benchmark

Run:

```bash
python scripts/benchmark_large_trajectory.py
python scripts/benchmark_large_trajectory.py --benchmark-bonds
```

The default workload is a deterministic 15,000-atom, 16-frame numeric LAMMPS
dump. The benchmark starts a fresh local server and Chromium page at
1280 x 720, waits for all atom instances and the first rendered canvas frame,
loads the binary trajectory cache, verifies idle rendering, and updates all
frames.

Reference result for the 0.0.92 working tree on the project development Mac:

| Check | Result |
| --- | ---: |
| Input size | 8,719,654 bytes |
| Backend input + server ready | 0.340 s |
| Browser navigation + first render | 0.264 s |
| Fully ready total | 0.604 s |
| Displayed atoms | 15,000 |
| Trajectory frames | 16 |
| Browser trajectory cache | 2,880,000 bytes |
| 16-frame modeling update sweep | 19.8 ms |
| Mean modeling position update | 1.24 ms/frame |
| Mean Studio position update | 0.98 ms/frame |
| Mean Sun + Soft Shadow update | 0.77 ms/frame |
| Extra render frames during 0.9 s idle | 0 |

A separate single-frame 15,000-atom material regression measured 0.392 s from
navigation to all atom instances being available. Standard-to-Metal label
switching retained one instanced atom group for all 15,000 atoms.

With automatic bonds enabled, the same synthetic scene contains 73,952 logical
bonds. Cached topology inference measured 10.5 ms, direct geometry-buffer
update 13.8 ms, and a four-frame sweep that includes candidate-list rebuilds
averaged 30.53 ms/frame.

Browser, GPU, storage, trajectory columns, bond density, and machine load
affect absolute timing; these values are regression references, not universal
guarantees. Background browser tabs may throttle timer frequency, so playback
FPS is not inferred from a headless hidden-tab timer.

## Blender Benchmark

`tests/test_blender_performance.py` generates a 15,000-atom, two-label periodic
scene. Optimized export writes one point mesh per visual label and instances
smooth spheres through Geometry Nodes. Trajectory frames become point-mesh
shape keys; bonds are grouped by material; the unit cell is one multi-spline
curve.

The regression verifies:

- all 15,000 atoms are retained;
- atom data is held in a small number of editable point groups;
- total scene object count remains small;
- Blender executes the generated script and saves a native `.blend`;
- the runtime remains below a conservative machine-independent ceiling.

`tests/test_blender_runtime.py` separately renders a colored bonded scene and
checks smooth atoms, split bond materials, camera, cell, trajectory animation,
and exact directional Sun source/target/intensity.

## Remote Cluster Validation

The 0.0.95 candidate wheel was installed into an isolated environment on an
x86_64 Linux physics cluster with Python 3.13.9 and ASE 3.27.0. From the
development Mac, an existing 18-atom periodic CIF on that host was opened with
the public one-command workflow:

```bash
v_ase gui physics:/remote/path/cod_9001665.cif
```

No port was supplied or reserved. v_ase selected both loopback endpoints,
started the remote backend, established the SSH forwarding connection, and
opened the local browser. The test verified version 0.0.95, all 18 atoms,
`TTT` PBC, the unit cell, and a nonblank rendered scene. Closing the browser
page released the local launcher, tunnel, and remote process.

A separate two-frame LAMMPS dump validated the remote trajectory contract. The
initial response reported `trajectory_streaming=true`, contained no inline
trajectory coordinates, and advertised no whole-trajectory binary cache.
Requesting frame 2 returned only its six float32 coordinates. This policy is
enabled for every remote trajectory regardless of source-file size: the source
file, reader, ASE state, and scientific operations remain on the server while
the browser receives only the data needed for the displayed frame.

## Regression Coverage

Performance-sensitive static contracts are locked by
`tests/test_frontend_regressions.py`. Real browser tests cover:

- zero idle render loop;
- binary trajectory initialization and frame changes;
- live bond formation/breaking;
- supercell atom and bond instancing in skewed cells;
- label visibility and appearance updates;
- visualization-mode replica selection;
- frame-persistent selection and displacement-vector instancing;
- visual translation after supercell instancing without coordinate copies;
- supercell-repeated displacement vectors with shared physical values;
- signed isosurface construction, display repetition, translation, and
  physical grid repetition/reset/undo, including PBC-aware field smearing and
  boundary-preserving mesh smoothing;
- RDF calculation at multiple cutoffs, including image spans beyond a fixed
  `2 x 2 x 2` repetition, partial-curve normalization,
  Plotly drawer rendering, and CSV parity;
- frame-specific cell/PBC handling for wrap, translation, and supercells;
- output preview/capture parity;
- inactive multi-document suspension.
