Manual/Reference

Application performance audit, September 2026

This audit covers local rendering, editing, data admin and processing. It preserves full data, picking, animation timing, coordinate precision, raster values, compositing order, undo and export behavior. No new feature-count limits, sampling, image downscaling or frame-rate caps were introduced. Server-side data editing is outside scope.

Measurements

Results below are local CPU workloads on Windows with Bun 1.4.0 unless stated otherwise. They demonstrate the cost removed from particular paths, not whole-application speedups. RSS is sensitive to garbage collection, process warmup and concurrent work; resource identity and allocation counts are stronger evidence than an isolated RSS sample. GPU frame rate and cross-device peak memory need hardware profiling.

Run the checked-in scripts with bun scripts/benchmarks/<name>.ts. Timings are deliberately not CI thresholds. The scripts and tests check exact output, resource counts or identity so faster incorrect behavior cannot pass.

WorkloadBeforeAfterBenchmark
Read 100,000 deck rows on 60 unchanged frames5,062 ms157 ms initial validation, 0.06 ms for 60 warm readsdeck-viz-data.ts
Read object coordinates, 100,000 rows × 205,923 ms validating whole rows repeatedly282 ms reading validated fieldsdeck-viz-accessors.ts
1M LiDAR points, 30 opacity updates696 ms rebuilding buffers0.69 ms reusing prepared chunkslidar-point-cloud-manager.ts
Color 1M LiDAR points, 5 passes216 ms184 mslidar-color-scheme.ts
10,000 3D meshes, 120 frames60.8 ms and 1.2M material version increments1.25 ms and no material version incrementsthree-d-tiles-opacity.ts
Elevated route, 100,000 vertices, 120 updates306 ms for the old path preparation alone108 ms including actual engine/layer constructionroute-animation.ts
1,000 mixed document/native layers, anchor lookup13,562 ms90 ms, including ownership index creationlayer-anchors.ts
1,000 layers and 200 organizer groups1,051 ms1.57 mslayer-organizer.ts
Restore one local source into 100 layers9,315 ms of individual document transitions38 ms for one transitionlocal-layer-restore.ts
Redact 1,000 layers with 100 points each1,489 ms1,088 ms; identical result hashproject-redaction.ts
Save then delete from 50 templates with 1,000 features each237 ms; about 11.5 MB cloned10 ms; 113 KB clonedtemplate-library-persistence.ts
Mosaic, 100,000 assets and 100 tile queries87 ms scanning manifests43 ms index creation, 10 ms queriesraster-mosaic.ts
Reclassify 512×512 raster, 2,000 rules1,600 ms19 msraster-operations.ts
Prepare 2,304 segmentation candidate masks144 MiB cumulative scratch allocation64 KiB invocation-owned scratchsegmentation-candidate.ts
Whitebox inline GeoJSON, 100,000 features594 ms for duplicate schema encoding357 ms for one encodingwhitebox-inline-geojson.ts
Stage a 64 MiB typed array into the WASI filesystem31.6 ms and two copies7 ms and one owned copywasm-tool-input.ts

The route comparison is conservative: its baseline measures only the removed path-building work, while the optimized measurement runs the engine. The 3D material benchmark uses real Three.js meshes and the actual layer’s render method with a fake GL surface; it does not measure shader compilation or GPU rendering time. Template persistence uses an instrumented fake IndexedDB with real structured clones and exact record assertions. Reclassification includes rule preparation and checks exact first-match semantics.

The anchor benchmark retains the original Effect HashSet membership checks. Its dense case, with all 1,000 native layers present, remained approximately level: 23.7 ms before and 22.4 ms after. The mixed case has 100 matching native layers and 900 basemap layers, exposing the repeated searches for missing companions.

Additional allocation workloads are in netcdf-inline-store.ts, plugin-file-bytes.ts, and map-geometry-profile.ts. A 2048² Float32 NetCDF grid avoids a second 16 MiB byte buffer. A full 100 MiB Tauri byte buffer is returned without another allocation; a subview still copies its exact range so unrelated bytes cannot escape.

Rendering and layer lifecycle

MapLibre and shared deck.gl

  • Geometry profiles are cached by immutable feature-collection identity with weak keys. Style changes reuse the profile, while replacement data is rescanned.
  • Anchor resolution indexes document ownership once and always reads the current native order. It handles missing native layers and shared companion IDs without repeatedly scanning the whole style for each subsequent document layer. Explicit anchors and selection-overlay fallback remain unchanged.
  • Deck data is validated once per immutable source. Rendering retains the original rows rather than a second decoded object graph. Nested Trips paths use the canonical row schema.
  • Static deck layers and accessor functions survive animation frames. Trips frames update time while preserving data identity. Timestamp bounds include the selected timestamp mapping in their cache key.
  • Repeated identical shared-overlay submissions do not call setProps. Draw readiness uses deck.gl’s onAfterRender instead of repeatedly cancelling two animation-frame timers. Device listeners receive initialization once per subscription or device generation.
  • Route animation reuses fixed elevated vertex tuples, publishes an immutable path with the exact interpolated endpoint, and clears unused flat GeoJSON sources once per source lifetime.
  • Deactivation and project disposal remove animation callbacks, subscriptions and retained layer data. Late lazy imports cannot revive a deactivated visualization.

Cesium

Cesium 1.145 is configured for explicit rendering with requestRenderMode. Camera movement, async layer changes and actual visibility/style mutations still request draws. The clock and maximumRenderTimeChange retain their defaults so time-dependent content keeps updating.

Terrain.fromWorldTerrain() is passed into Viewer through the supported asynchronous terrain API. Camera and layer synchronization no longer wait for World Terrain to load. Cesium owns the pending terrain operation and ellipsoid fallback.

cesium-rendering.ts runs an isolated Chromium page with software WebGL. In the observed windows, continuous versus explicit rendering produced 21 versus 5 idle draws, 12 versus 8 camera draws, and 24 versus 24 clock-animation draws. These small software-rendering windows verify behavior; they are not a GPU FPS claim.

A blanket migration from GeoJSON entities to GeoJsonPrimitive was rejected: point clamping, marker appearance and interaction do not have equivalent behavior. Likewise, forcing an infinite clock-render threshold would change animated content.

Three.js, 3D Tiles, LiDAR and splats

  • 3D Tiles opacity is applied to new model subtrees and actual opacity changes. Unchanged frames do not traverse every mesh or mark its material dirty. Imported material opacity remains intact until explicitly overridden.
  • DRACO and KTX2 decoder loaders have explicit ownership. Removal disposes initialized resources and handles decoder initialization that finishes after removal without starting unused decoders.
  • CESIUM_RTC handling now registers at the owned GLTF loader boundary. The private-field/prototype adapter is removed. Finite center/scene-position validation and additive XYZ behavior remain.
  • LiDAR preparation is separate from display props. Point size, opacity and picking changes reuse chunk buffers; filtering, color, elevation and explicit data updates invalidate them. A multi-chunk change is submitted once, with all points and original hover indices retained.
  • LiDAR color generation writes directly into the output buffer and only extracts a Z plane for percentile calculations.
  • MapScene and Gaussian splat ownership includes exact event callbacks, native custom layers, material texture slots, renderers and pending model loads. Cleanup never forces loss of MapLibre’s shared WebGL context.

The Android blob-texture workaround remains: there is no evidence that all supported WebViews no longer need it. Three.js peer versions were not forcibly overridden without shader/runtime compatibility evidence.

Raster and terrain

  • Raster textures are released on tile eviction, source retirement, hiding, zoom exclusion, engine change and manager destruction. Late tile results cannot allocate resources for a retired owner. Failed partial allocations are cleaned up.
  • Ordinary visible style changes retain the same tile owner. Changing source or requested bands retires it. Mosaic child-source textures have separate lifetimes.
  • This owner cleanup is necessary because the installed deck tile cache’s finalize() clears its cache without calling onTileUnload.
  • Manifest indexing uses inclusive bounding-box intersection and restores manifest order before compositing. Nonfinite, inverted and antimeridian boxes retain the historical predicate. Large fallback lists do not use argument spreading.
  • Mosaic URL caches keep shared sources until their last active owner disappears. Unchanged manifest identities skip repeated URL-set construction. Late source opens cannot repopulate a retired cache.
  • The old 512-asset compositing truncation is removed; the existing decode concurrency remains. Dense overlap can no longer silently omit imagery.
  • Geographic DEM remapping computes row-dependent latitude/Mercator values once per row. Regression tests compare the resulting sample bytes against the original per-pixel algorithm.
  • Float32 raster input and interleaved-band extraction avoid redundant full-sized copies while retaining NaN, negative zero, normalization and exact sample ordering.
  • Raster reclassification uses a compiled interval index that preserves ordered first-match rules, half-open intervals, gaps, infinity, invalid rules and nodata. Focal operations reuse window storage and preserve the existing reduction order and median behavior.
  • Raster shader-pipeline descriptions are reused by immutable tile identity within the current settings closure; settings and statistics changes rebuild that closure.
  • GeoTIFF colorization writes RGBA bytes directly and reuses immutable color ramps. Layer synchronization skips identical snapshots and indexes managed layers, while late imports and image decoding cannot repopulate a disposed overlay. raster-geotiff.ts records the remaining full-image cost: about 245–274 ms for 2048² pixels, with a required 16 MiB RGBA output. This is an absolute workload measurement, not a before/after speedup claim.

Project admin, persistence and workers

  • Layer organizer derivation builds parent/group indexes once. Visibility changes are one authored action and one undo transition.
  • Local source restoration applies ordered patches in one document command, including the same join/virtual-field refresh and dependent propagation as serial edits.
  • Project save preparation no longer serializes eagerly. Export callers serialize at their actual boundary. Credential redaction avoids intermediate copies but still returns a detached, validated, fail-closed project.
  • Recovery checks reject clean/unrelated sessions before serializing. Startup restore is interrupted when its project session is disposed.
  • Template save and delete use their existing targeted IndexedDB transactions. The redundant full-library rewrite is removed, and hydration cannot reapply a stale saved array over a subsequent deletion.
  • Inline Whitebox data is encoded once. NetCDF byte views avoid an intermediate copy before base64 encoding. WebCrypto hashes its fresh encoded bytes directly.
  • The hand-written WASI wrapper passes typed arrays directly to the filesystem’s File constructor, which already copies them. ArrayBuffer inputs keep their required defensive ownership conversion, and COG/PMTiles materialization is unchanged. A real WASI tool test checks caller mutation and subview boundaries.
  • Segmentation reuses one private mask scratch buffer across serial candidates. Rejected masks cannot contaminate subsequent candidates. Full owned model buffers pass through without slicing; subviews and shared buffers retain explicit copies.
  • ONNX sessions and ImageBitmaps already have release/close lifetimes. Interpreter persistence and model caches must preserve their user-visible state; unconditional teardown or speculative global worker pools were not introduced.

Validation and limits

Focused tests cover exact numerical/geometry output, mapping changes, joins, undo, source replacement, tile eviction, partial allocation failure, cancellation, late initialization, scene removal/recreation, duplicate subscriptions and template-write races. Existing controller tests cover companion ordering and selection visibility.

The final application and five affected package TypeScript checks pass with --noEmit. All 244 tests across the audit’s 34 changed test files pass together after the final repairs. Scoped formatting, lint checks and git diff --check pass; unchanged vendored source lint diagnostics remain baseline. The embedded-build check and MapLibre compatibility check also pass. The daily preview and production build output are not used for this audit.

The final full frontend run completed with 8,768 passes, one skip and zero failures across 1,004 files. Bun 1.4’s per-file isolation prevents earlier imports without a DOM from permanently caching Base UI’s layout effect as a no-op in the place-search tests. Both frontend scripts now use isolation. The segmentation buffer checks have independently owned cases for full buffers, subviews and shared buffers, preserving all assertions without increasing the timeout.

The backend suite passed 420 tests with 20 skips, and the Python widget suite passed all 336 tests. The final application and edge-service type checks pass. A precise ambient signal overload restores the inherited process.off signature hidden by Bun 1.4’s memoryPressure overload, with no runtime change.

The final pre-commit formatting and file-integrity hooks pass. The broader changed-file Oxlint run is not green: it reports 233 diagnostics, including type assertions, test style and deprecated APIs. Of these, 47 point to added or rewritten lines; these are not all baseline findings. This later check supersedes the earlier scoped lint result above. No lint rule or assertion was weakened to hide the findings. The full repository lint run was stopped after the changed-file run established these failures. Production builds, coverage gates, browser E2E and Rust checks were not rerun for this commit batch.

The benchmark TypeScript project still has existing configuration/import errors concerning Bun’s fetch.preconnect, CSS/PNG declarations and older vector benchmark imports. None of this audit’s new benchmark files has a reported TypeScript error. These existing diagnostics were not hidden with weakened compiler settings.

Some costs are inherent to the current public contracts: returning an exact standalone subview ArrayBuffer requires copying; full-image GeoTIFF and ML preparation still materialize their requested images; a stateful Python console retains its interpreter. Worker startup reuse and a direct RGBA inference path require evidence that ownership and observable behavior are preserved before replacing those contracts. GLTF texture cleanup releases owned GPU resources but does not close potentially shared ImageBitmaps without proof of exclusive ownership. An audit cannot establish a universal RAM ceiling or guarantee frame rate for every dataset and graphics driver.