# ADR 013: Browser Simulation with CesiumJS and C++ WebAssembly

- HTML version: https://robbiepalmer.me/projects/autonomic-satellite-swarm/adrs/013-browser-simulation-with-cesiumjs-and-webassembly
- Project: Autonomic Satellite Swarm (https://robbiepalmer.me/projects/autonomic-satellite-swarm.md)
- Status: Accepted
- Date: 2026-09-12

# Context

The host simulation proves the three-node negotiation path, but its terminal output does not show
where the agents are, which messages changed their state, or why one candidate won. A reader must
understand the paper and source code before the behavior makes sense.

The portfolio needs an interactive Earth view that runs the coordination code rather than copying
its decisions into TypeScript. [ADR 007](/projects/autonomic-satellite-swarm/adrs/007-portable-core-and-hardware-adapters)
already keeps that code independent of Arduino and operating-system APIs. The Uno, ESP32, and host
builds differ at their adapter boundaries, so a browser adapter fits the existing design.

A globe alone does not make the simulation physically credible. The controller scores an
adapter-supplied `SatelliteSnapshot`, and `HistoricalOrbitalScorer` remains a documented heuristic.
The proposed [orbit-propagation experiment](/projects/autonomic-satellite-swarm/adrs/012-qrptracker-embedded-orbit-propagation)
may later provide changing positions, but it has not passed its acceptance criteria.

[CesiumJS](https://cesium.com/platform/cesiumjs/) is an Apache-2.0 browser library with a WGS84
globe, altitude-aware entities, glTF models, and time-dynamic visualization. Its
[offline guide](https://github.com/CesiumGS/cesium/blob/main/Documentation/OfflineGuide/README.md)
documents bundled Natural Earth II imagery, so the first version does not need a hosted map account
or runtime token. [Emscripten](https://emscripten.org/docs/compiling/Building-Projects.html) can use
the existing CMake project to compile C++ into WebAssembly.

# Decision

Build the browser simulation as one C++ WebAssembly module rendered by CesiumJS.

Link the existing `satellite_swarm` coordination library into an Emscripten target. The target adds
a browser simulation adapter for deterministic time, in-memory message delivery, health inputs,
and simulated environmental state. It does not compile the infrared or ESP-NOW adapters because a
browser has neither device. The same coordination, score, message, and codec sources used by the
firmware remain authoritative.

Run the module in a short-lived Web Worker. Each request supplies a mission objective and named
deterministic network scenario. C++ advances the simulation in fixed steps and returns one complete,
ordered replay before the worker terminates. React may replay the returned snapshots at display
speed. It must not reimplement candidate selection, health transitions, retry behavior, or message
handling.

Define a versioned `SimulationTrace` as the replay contract. Each ordered input frame contains
the exact `uint32_t now_ms` passed to `SwarmController`, followed by its health updates,
environmental snapshot updates, fault transitions, and inbound messages in delivery order. The
trace also fixes initial node configuration and mission commands. Future stochastic adapter
policies, such as simulated packet loss or delivery delay, must take an explicit seed and materialize
their choices into the trace. Neither the controller nor renderer may draw additional randomness.
Native and WebAssembly builds construct the trace from the same versioned scenario inputs and
compare their serialized outputs byte for byte.

Use CesiumJS only for presentation and interaction. It draws the Earth, satellite positions,
orbit or scenario tracks, mission objectives, communication events, and selected agent. React owns
the surrounding controls and event log. Keep both CesiumJS and the WebAssembly worker out of the
initial route. Load them only after explicit user activation, use CesiumJS request-render mode, and
terminate each worker after its run completes.

Self-host CesiumJS's workers, widgets, and other required static files with the site's static export.
Configure the viewer with a `TileMapServiceImageryProvider` pointed at the locally bundled Natural
Earth II tiles and an `EllipsoidTerrainProvider` or another self-hosted terrain provider. Disable
the base-layer picker and geocoder, or replace them with exact local equivalents. Do not rely on
CesiumJS provider defaults, an ion token, Google imagery, or another hosted service. A later ADR may
adopt hosted imagery under its usage and attribution terms.

The browser adapter refreshes each node's `SatelliteSnapshot` before displaying movement. One source
of simulated position feeds both candidacy scoring and Cesium. The first scenario uses deterministic
scripted inputs and labels them as simulation data. Do not describe a trajectory as propagated or
physically validated until the orbit-propagation experiment supplies that evidence.

# Alternatives

## Reimplement the demonstration in TypeScript

This would integrate with React quickly, but the visible result could drift from the embedded state
machine. It would weaken the main reason for building the demonstration, which is to let a reader
interact with the same coordination code used by the firmware.

## Use react-globe.gl

[`react-globe.gl`](https://github.com/vasturiano/react-globe.gl) offers a smaller React-facing API
for paths, altitude, and custom Three.js objects. It is a good choice for a decorative or generic
spherical visualization. CesiumJS fits this project better because positions, reference frames,
simulation time, and later orbital analysis are part of the domain rather than visual effects.

## Extend Leaflet or adopt MapLibre globe mode

Leaflet fits projected surface maps. It does not provide an Earth-centered scene for orbiting
objects. [MapLibre's globe](https://maplibre.org/maplibre-gl-js/docs/examples/display-a-globe-with-an-atmosphere/)
can place data above the surface, but satellite models and tracks would require custom rendering and
projection code that CesiumJS already owns.

## Use hosted photorealistic Earth data

Google 3D Maps and Cesium ion can provide richer terrain and imagery. They require accounts, tokens,
quotas, or billing, and detailed ground geometry adds little while viewing low-Earth orbit. The
self-hosted globe keeps the first version free and available with the rest of the static site.

# Acceptance evidence

The browser target links the portable `satellite_swarm` library used by the host and firmware
builds. Its C++ adapter generates the state changes, scores, messages, and ordered replay. The worker
only validates requests and responses. The parity task first regenerates the native fixture, then
requires the Wasm result to match it byte for byte. The project check also builds the Uno and ESP32
firmware, runs native tests, sanitizers, static analysis, and coverage.

The shipped nominal trace contains changing scripted positions at 0, 10, 100, and 120 ms. The same
`SatelliteSnapshot` supplied to the C++ scorer is serialized for Cesium, so the globe and decision
logic cannot use different coordinates. The interface calls these scenario paths and states that
they are not orbit propagation. ADR 012 is therefore not a dependency for this decision.

Cesium renders three moving satellites, their tracks, the mission target, and communication pulses.
The React interface also exposes replay controls, a state table, and an event log. Reduced-motion
users retain manual stepping, and a WebGL failure replaces only the globe with an accessible
fallback. Natural Earth II imagery, widgets, and workers are self-hosted; terrain uses the local
ellipsoid provider.

The reference audit used headless Chromium 153.0.8010.12, disabled cache, a local static export, and
software WebGL. Times start when the reader selects **Load simulation**. The mobile profile used a
390 by 844 viewport, device pixel ratio 3, and 4x CPU throttling. The figures describe this repeatable
test environment, not every device.

| Profile              |  JavaScript |      Wasm |   Imagery | First globe | Replay p95 | Longest frame / frames over 50 ms |
| -------------------- | ----------: | --------: | --------: | ----------: | ---------: | --------------------------------: |
| Desktop, 1440 by 900 | 6,645,086 B | 202,394 B | 110,064 B |      2.11 s |    33.4 ms |                      183.3 ms / 1 |
| Throttled mobile     | 6,645,086 B | 202,394 B |  26,122 B |      3.44 s |    33.4 ms |                      100.0 ms / 3 |

The JavaScript payload is large enough that the page now requires explicit activation. The audit
also verifies that no Wasm or imagery loads before activation, every measured resource succeeds,
the activated interactive makes no cross-origin HTTP request, the replay reaches its final frame,
and the exact 40-character build revision appears in the interface. UI CI runs this audit against
the production static export. The page keeps the heuristic-scoring, scripted-position, and
flight-readiness caveats visible.

# Consequences

Readers can observe the real coordination state machine instead of trusting a transcript or a
JavaScript reconstruction. The simulation remains deterministic and can grow into packet-loss,
partition, health, and replay experiments without placing those concerns in the firmware core.

The site gains a large client-only renderer, a second C++ build target, a worker protocol, and static
Cesium assets. Explicit activation, lifecycle cleanup, and a non-WebGL fallback are required parts of
the feature.

WebAssembly proves that the browser and firmware compile common source. It does not prove identical
machine code, hardware timing, radio behavior, orbital validity, or flight readiness. CesiumJS also
draws supplied positions; it does not validate them. Those limits must remain visible in the demo.

---

Markdown index of this site: https://robbiepalmer.me/llms.txt
