A deterministic vehicle mission simulator built with React, TanStack Start, Tailwind CSS, and shadcn/ui.
The simulator provides:
- a vehicle moving counterclockwise around a 100-unit circle centered at
(0, 0) - display-refresh-rate vehicle animation and a 60-second lap at
1× - play, pause, reset, and
0.5×/1×/2×/4×speed controls - selectable, draggable Activity nodes with detailed name, color, latitude, longitude, radius-slider, and route-lock configuration
- position events with a configurable
50–60000ms interval, independent of movement and playback speed - a server-side NATS relay with separate position and geofence subjects
- a searchable, filterable, paginated TanStack Table event stream with a configurable
100000-event default retention limit and NATS connection status
Install dependencies and start the TanStack Start development server:
pnpm install
pnpm devThe app runs on http://localhost:3010. A NATS server must be reachable from the TanStack Start server process; authentication is not supported in this iteration.
The NATS server URL and subjects can be changed on screen. Their initial values come from server-side environment variables:
| Variable | Fallback |
|---|---|
NATS_URL |
nats://localhost:4222 |
NATS_POSITION_SUBJECT |
mission.position |
NATS_GEOFENCE_SUBJECT |
mission.geofence |
Example:
NATS_URL=nats://jetson.local:4222 \
NATS_POSITION_SUBJECT=mission.position \
NATS_GEOFENCE_SUBJECT=mission.geofence \
pnpm devThe browser posts validated event batches to /api/nats. The server route owns and reuses the native NATS TCP connection. Publish targets accept nats:// and tls:// URLs without embedded credentials.
Position events use type: "position.updated". Activity node transitions use type: "node.entered" or type: "node.exited". Every event includes:
- a deterministic event ID and monotonically increasing sequence number
- mission elapsed time and wall-clock occurrence time
- exact latitude/longitude coordinates and heading
- playback speed
- an Activity node snapshot for geofence transitions
Vehicle position is rendered continuously with requestAnimationFrame. Position events use a separate wall-clock timer, so changing the interval between updates never changes movement speed or smoothness. Geofence transitions follow vehicle movement rather than waiting for a position event.
src/features/simulator/model.ts contains a pure command-driven state transition model. Runtime state is separate from the versioned, JSON-serializable SimulatorConfiguration, which contains route geometry, telemetry intervals and retention limits, typed nodes, mission identity, and NATS settings.
src/features/simulator/simulator-store.ts owns the per-session TanStack Store and exposes the command controller as its only mutation boundary. React components subscribe with granular useSelector calls, so animation-frame position updates rerender the mission map without rerendering configuration controls.
This boundary is intentional preparation for future configuration persistence and external control clients. Saving configurations and exposing agent control endpoints are not implemented in this iteration.
pnpm test # deterministic simulator model contracts
pnpm check # Biome formatting and lint checks
pnpm build # production client and server bundles