System architecture
Hardware-specific code stays at the system boundary. The device, the simulator, the browser demo and the iPhone run the same no_std application core. Each host supplies storage, sensors, input, and display.
Runtime layers
Dependencies point from the hosts to the shared core. The core does not depend on a host, so new hardware cannot reach into the application.
App is the composition root. The Navigator owns the active route, route matching, guidance, and planner pacing. The UI runtime owns screens, timers, and dirty regions. The host protocol defines bounded commands and events.
The host owns RenderScratch and lends it to each render call. The memory a frame needs is therefore not part of application state.
Four foundation crates carry no policy:
obc-formatsdefines the persistent byte constants and byte I/O.obc-map-sceneseparates map sources from the renderer.obc-elevationreads terrain and applies the shared elevation rules.obc-portsdefines the semantic host interfaces.
Four hosts, one core
obc-fw-nrf54lis the device.obc-simis the desktop simulator.obc-web-demois the browser demo.obc-ios-hostis the iPhone host.
obc-host-core holds the behavior that the three non-device hosts share.
The iPhone host runs the application over one card file. The phone supplies the position, heading, altitude, and battery level. It is a development tool: it tests the user interface, position tracking, route following, and ride recording against real sensors and a real rider. It cannot show device speed, power use, the display driver, or the Bluetooth link. Only the device shows those.
The host supplies bytes through ByteSource, which reads any offset on request. A map therefore does not have to fit in RAM. Every host reads its objects from the same flat store; only the medium differs, which is a card on the device, a sparse file on the simulator, and memory pages in the browser.
A reader pins one object revision while it reads. A replacement object gets a new revision, and the open reader keeps its bytes until it closes. A map or a route can therefore be replaced while the rider looks at it.
Recording is the write path with the same rule. The recorder appends bounded batches, writes periodic checkpoints, and closes a ride with a footer. An interrupted ride restarts from its last checkpoint, because the samples before that point are already durable.
obc-ports defines the sensor, input, settings, and track interfaces. A sensor poll drains a mailbox. It does not start a bus transaction.
The per-frame loop
The application renders on demand. It repaints a region only when the facts that the region draws have changed, so a static screen costs no processor and no panel traffic.
App::run_pass takes what the platform finished, what changed underneath it, and what the rider did. It runs every domain in a fixed order and returns a plan: what to repaint, when to run again, and at most one bounded effect for each domain.
Each effect carries an operation token, and its answer must return that token. A late answer to a cancelled operation is therefore refused instead of applied. Effects carry small identifiers and results; bulk data stays in caller-owned buffers. obc-host-core performs the effects for the frame-stepped hosts. The board performs the same effects with its own asynchronous execution.
On-device routing: the router seam
Navigator owns the planning lifecycle and issues one physical operation at a time. The host executor and the board executor do only the work Navigator asks for. They never start the next step or publish a route on their own.
| Operation | Executor work | Result Navigator waits for |
|---|---|---|
| Acquire | Claim the workspace and hold the map source, and the route source for a detour. | Sources and workspace are held. |
| Step | Run one bounded search, trim, or preview step against those sources. | Progress, a preview, or a failure. |
| Commit | Publish the completed route or the accepted detour. | The new route identifier, or a failure. |
| Release | Finish or cancel pending work and return the workspace. | The workspace is available. |
Planning stays bound to the sources it started with: the exact map object and route revision. A changed source invalidates the result. The executor cannot substitute the current map halfway through a plan, so a preview always describes the route the rider reviewed.
Publication writes a new route object and never overwrites the original. A detour is planned the same way, and the rider sees its cost before it replaces the active route.
The router projects each route endpoint onto stored road geometry and accepts a road within a fixed radius. Sparse lookup anchors make long road edges discoverable. The search is profile-weighted A* with a widening epsilon ladder: a wider bound gives an answer where a tight one runs out of table space. A fixed search table holds the frontier, so the practical range follows the size of that table and not a distance limit. The constants are in nav.rs.
If the map carries terrain, the planner samples it for route elevations, and the shared integrator calculates climb and descent. A map without terrain still plans routes.
A visit is one complete route: a ride to a mapped approach for the selected place, a return or forward connection, and the remaining original route. The rider reviews it before it becomes active, and acceptance is one durable transaction, so a restart can offer to resume it. The shared builder composes the route, and Navigator owns the requests and the phase changes.
Staying responsive: the two planes
The device runs two cooperating execution planes. The high-priority input plane samples the buttons and recognizes gestures. The map plane applies the gestures and owns all rendering and panel output. A bounded channel carries gestures from one to the other.
A map frame takes hundreds of milliseconds. Without the split, a button press during a frame would be seen late or lost. The simulator runs the same InputPlane inline, because gesture recognition depends only on raw input and time.
Source index
- Application and dirty state:
app.rs - Input recognition:
input_plane.rs - Display output:
obc-display - Storage:
obc-storage - Sensor adapters:
obc-platformandobc-sensors - Map formats: data formats
- Rendering: rendering pipeline
- UI: UI system
- Terrain: terrain and elevation