# ai.mapmap:core (Android) reference

> Generated from the published **ai.mapmap:core 0.8.0** sources (core-0.8.0-sources.jar on Maven Central) by `website/scripts/sdk-reference-native.mjs`. Do not edit by hand: change the doc comments in the SDK, or the examples in `website/content/sdk-reference/android.examples.md`, and regenerate.

Every public declaration of the Android library at release 0.8.0, from its published sources jar: the hand-written `ai.mapmap` API and the generated `uniffi.mapmap` and `uniffi.ferrostar` bindings to the Rust core. The guide is [Android (Kotlin) in SDKs & installation](/docs/sdks#android-kotlin) and the hand-written [Android API reference](/docs/android). Requires minSdk 26.

Other SDK references: [@mapmap/maps](/docs/maps-reference), [@mapmap/points](/docs/points-reference), [@mapmap/core](/docs/core-reference), [@mapmap/tile](/docs/tile-reference), [@mapmap/react-native](/docs/react-native-reference), [@mapmap/voice](/docs/voice-reference), [MapMapKit](/docs/ios-reference).

316 public declarations across 3 packages: 42 written by hand, 274 in the generated Rust bindings.

## How to read this page

Each entry gives the declaration as written in the release source (in Kotlin without identifier backticks or the `kotlin.` package prefix), its doc comment, and its public members. The **Default** column shows a value only where the source writes one (an initialiser, a parameter's default, or a doc comment that states it); otherwise it is blank. Types and classes written by hand carry a minimal example. The generated bindings are the Rust core's types as UniFFI exposes them; their descriptions are the Rust doc comments.

## Setup used by the examples

```kotlin
// build.gradle.kts
dependencies {
    implementation("ai.mapmap:core:0.8.0")
}
```

## Contents

- [ai.mapmap](#aimapmap): 42 declarations
- [uniffi.mapmap](#uniffimapmap): 106 declarations
- [uniffi.ferrostar](#uniffiferrostar): 168 declarations

## ai.mapmap

### Classes (ai.mapmap)

[`AndroidLocationProvider`](#androidlocationprovider), [`AudioFocusPolicy`](#audiofocuspolicy), [`ChannelSource`](#channelsource), [`ChannelSync.Installed`](#channelsyncinstalled), [`ChannelSync.Updated`](#channelsyncupdated), [`ChannelSync.UpToDate`](#channelsyncuptodate), [`CorpusFormatException`](#corpusformatexception), [`DriveCorpus`](#drivecorpus), [`DriveFix`](#drivefix), [`DriveHeader`](#driveheader), [`HttpChannelFetcher`](#httpchannelfetcher), [`HttpProbeUploader`](#httpprobeuploader), [`LocationSample`](#locationsample), [`NavigationEngine`](#navigationengine), [`NavigationState`](#navigationstate), [`ReplayLocationProvider`](#replaylocationprovider), [`SignedChannelIndex`](#signedchannelindex), [`TerritoryStore`](#territorystore), [`TtsStartup`](#ttsstartup), [`TtsStartup.InitActions`](#ttsstartupinitactions), [`ValhallaMobileRouter`](#valhallamobilerouter), [`VoiceGuidance`](#voiceguidance), [`VoiceProsody.Prosody`](#voiceprosodyprosody)

#### `AndroidLocationProvider`

*Class* · `ai/mapmap/nav/AndroidLocationProvider.kt`

```kotlin
public class AndroidLocationProvider(context: Context, private val minIntervalMs: Long = 1_000L, private val minDistanceM: Float = 0f) : LocationProvider
```

Live [`LocationProvider`](#locationprovider) on the platform location stack, no Google Play
Services dependency.

Uses `LocationManager.FUSED_PROVIDER` (the platform fused provider,
API 31+) and falls back to `LocationManager.GPS_PROVIDER` on older
devices. Requires `ACCESS_FINE_LOCATION` to be granted before the flow
is collected; the SDK manifest deliberately declares no permissions.

Background collection is subject to the platform's background-location
limits and OEM battery management, see android/README.md
("Location permissions and device caveats") before shipping fleet
builds.

@param minIntervalMs minimum interval between fixes; 1 Hz default
  matches vehicular guidance expectations.

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `val locations` | `Flow<LocationSample>` |  |  |

Nested: [`AndroidLocationProvider.Companion`](#androidlocationprovidercompanion).

**Example**

```kotlin
import ai.mapmap.nav.AndroidLocationProvider
import android.content.Context

// Needs ACCESS_FINE_LOCATION granted first.
suspend fun printFixes(context: Context) {
    AndroidLocationProvider(context, minIntervalMs = 1_000L).locations.collect { fix ->
        println("${fix.lat}, ${fix.lon} ±${fix.horizontalAccuracyM}")
    }
}
```

#### `AudioFocusPolicy`

*Class* · `ai/mapmap/nav/VoiceGuidance.kt`

```kotlin
public class AudioFocusPolicy
```

Pure audio-focus bookkeeping, unit-testable on any JVM. Transient
focus is held across the set of outstanding utterances (`TextToSpeech`
queues with `QUEUE_ADD`, so several can be in flight) and abandoned
only when the last one ends. Focus counts as held only when the
platform actually *granted* the request; a denied request is never
"abandoned" later, and the next utterance retries it.

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `var focusHeld` | `Boolean` | `false` | Whether a granted transient focus request is currently held. |
| `val outstandingCount` | `Int` |  | Utterances queued but not yet finished. |

**Initialisers and methods**

- `public fun shouldRequestFocus(): Boolean`: Before speaking: whether a focus request should be made now.
- `public fun onSpeakQueued(utteranceId: String, flushedQueue: Boolean, granted: Boolean?)`: Record `utteranceId` being handed to the engine. `flushedQueue` is true for `QUEUE_FLUSH` prompts (everything previously queued is discarded without callbacks); `granted` is the focus-request result, or null when no request was made because focus is held.
- `public fun onUtteranceEnded(utteranceId: String?): Boolean`: Record an utterance ending (done, error or stop). Returns `true` when focus should be abandoned now, the last outstanding utterance ended while focus was held.
- `public fun reset(): Boolean`: Everything stopped/muted. Returns `true` when focus should be abandoned.

**Example**

```kotlin
import ai.mapmap.nav.AudioFocusPolicy

// The audio-focus bookkeeping VoiceGuidance runs: request once per burst, abandon after the last utterance.
val focus = AudioFocusPolicy()
fun speak(id: String) {
    val request = focus.shouldRequestFocus()
    focus.onSpeakQueued(id, flushedQueue = false, granted = if (request) true else null)
}
fun done(id: String) {
    if (focus.onUtteranceEnded(id)) println("abandon audio focus")
}
```

#### `ChannelSource`

*Class* · `ai/mapmap/territory/ChannelSource.kt`

```kotlin
public data class ChannelSource(val indexUrl: String, val indexSigUrl: String, val layerBaseUrl: String)
```

Where a signed territory channel lives over HTTPS: its index, the index's
detached signature, and the base every channel-relative layer path
(`territories/<id>/<version>/...`) is resolved against.

Nothing fetched from here is trusted: the core verifies the index
signature, the manifest signature and every layer hash against the keys
pinned in the [`TerritoryStore`](#territorystore).

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `val indexUrl` | `String` |  |  |
| `val indexSigUrl` | `String` |  |  |
| `val layerBaseUrl` | `String` |  |  |

Nested: [`ChannelSource.Companion`](#channelsourcecompanion).

#### `ChannelSync.Installed`

*Class* · `ai/mapmap/territory/TerritoryStore.kt`

```kotlin
public data class Installed(val result: ChannelInstallResult) : ChannelSync
```

The territory was not installed; the channel's latest version now is.

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `val result` | `ChannelInstallResult` |  |  |

#### `ChannelSync.Updated`

*Class* · `ai/mapmap/territory/TerritoryStore.kt`

```kotlin
public data class Updated(val result: UpdateResult) : ChannelSync
```

An older version was installed; it was updated in place.

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `val result` | `UpdateResult` |  |  |

#### `ChannelSync.UpToDate`

*Class* · `ai/mapmap/territory/TerritoryStore.kt`

```kotlin
public data class UpToDate(val territory: TerritoryInfo) : ChannelSync
```

Already on the channel's latest version; nothing was downloaded.

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `val territory` | `TerritoryInfo` |  |  |

#### `CorpusFormatException`

*Class* · `ai/mapmap/replay/DriveCorpus.kt`

```kotlin
public class CorpusFormatException(message: String) : IllegalArgumentException(message)
```

A `.drive.jsonl` document violated the corpus format.

#### `DriveCorpus`

*Class* · `ai/mapmap/replay/DriveCorpus.kt`

```kotlin
public data class DriveCorpus(val header: DriveHeader, val fixes: List<DriveFix>)
```

Parser for the `.drive.jsonl` replay corpus format produced by the
workspace's `sn-replay` crate (see `crates/sn-replay/src/corpus.rs`).

Line 1 is a JSON header (`format_version`, `name`, `description`,
`route_fixture`, `seed`, optional `generator`); every following
non-blank line is one GPS fix (`t_ms`, `lat`, `lon`, optional
`speed_mps`, `bearing_deg`, `horizontal_accuracy_m`). The Kotlin parser
enforces the same invariants as the Rust one: supported format version,
non-empty name, at least one fix, strictly increasing `t_ms`, and sane
coordinate/channel ranges.

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `val header` | `DriveHeader` |  |  |
| `val fixes` | `List<DriveFix>` |  |  |

Nested: [`DriveCorpus.Companion`](#drivecorpuscompanion).

#### `DriveFix`

*Class* · `ai/mapmap/replay/DriveCorpus.kt`

```kotlin
public data class DriveFix(val tMs: Long, val lat: Double, val lon: Double, val speedMps: Double?, val bearingDeg: Double?, val horizontalAccuracyM: Double?)
```

One GPS fix line of a corpus. `tMs` is milliseconds since drive start.

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `val tMs` | `Long` |  |  |
| `val lat` | `Double` |  |  |
| `val lon` | `Double` |  |  |
| `val speedMps` | `Double?` |  |  |
| `val bearingDeg` | `Double?` |  |  |
| `val horizontalAccuracyM` | `Double?` |  |  |

#### `DriveHeader`

*Class* · `ai/mapmap/replay/DriveCorpus.kt`

```kotlin
public data class DriveHeader(val formatVersion: Int, val name: String, val description: String, val routeFixture: String, val seed: Long)
```

Header line of a `.drive.jsonl` corpus.

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `val formatVersion` | `Int` |  |  |
| `val name` | `String` |  |  |
| `val description` | `String` |  |  |
| `val routeFixture` | `String` |  |  |
| `val seed` | `Long` |  |  |

#### `HttpChannelFetcher`

*Class* · `ai/mapmap/territory/ChannelSource.kt`

```kotlin
public class HttpChannelFetcher(public val source: ChannelSource, private val apiKey: String? = null, private val cancel: CancelToken? = null, private val onBytes: ((Long) -> Unit)? = null) : LayerFetcher
```

A blocking HTTPS [`LayerFetcher`](#layerfetcher) for a [`ChannelSource`](#channelsource), on
`HttpURLConnection` (no extra dependency). Call it off the main thread;
[`TerritoryStore`](#territorystore) does.

@param apiKey sent as `Authorization: Bearer <apiKey>` (the hosted
  gateway meters layer downloads against the key's allowance); `null`
  for an open mirror.
@param cancel polled between chunks, so a tripped token stops a transfer
  in progress, not only the next one.
@param onBytes called with the running total of bytes received.

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `val source` | `ChannelSource` |  |  |
| `var lastFailedStatus` | `Int?` | `null` | HTTP status of the last failed request, or null. 401/403 auth, 402 allowance, 404 not found. |
| `val bytesReceived` | `Long` |  | Total bytes received by this fetcher so far. |

**Initialisers and methods**

- `public fun fetchIndex(): SignedChannelIndex`: GET the signed index and its signature, exact bytes.
- `override fun fetch(relPath: String, expectedBlake3: String, destPath: String)`

**Example**

```kotlin
import ai.mapmap.territory.ChannelSource
import ai.mapmap.territory.HttpChannelFetcher

// What TerritoryStore.sync(territoryId, source, apiKey) builds for you; use it directly when
// you drive installFromChannel or checkForUpdate yourself. Blocking: call off the main thread.
fun fetchIndex() {
    val fetcher = HttpChannelFetcher(ChannelSource.gateway(), apiKey = "snk_…")
    val index = fetcher.fetchIndex()
    println("${index.json.length} bytes, ${fetcher.bytesReceived} received")
}
```

#### `HttpProbeUploader`

*Class* · `ai/mapmap/nav/ProbeContribution.kt`

```kotlin
public class HttpProbeUploader @JvmOverloads constructor(baseUrl: String, private val apiKey: String, private val maxAttempts: Int = 4, private val backoffMillis: (Int) -> Long = { attempt -> attempt * 1_000L }, private val executor: ExecutorService = Executors.newSingleThreadExecutor { r -> Thread(r, "mapmap-probe-upload").apply { isDaemon = true } }, private val onResult: (ProbeUploadOutcome) -> Unit = {}) : ProbeUploader, AutoCloseable
```

Default [`ProbeUploader`](#probeuploader): POSTs each body to `{baseUrl}/v1/probe` with a
bearer key, on a small daemon executor, using only the JDK HTTP client, the
SDK takes on no third-party HTTP dependency. Transient failures (IO or `5xx`)
are retried with bounded backoff; permanent ones (`400`/`403`/`501`) are
dropped. Close it to release the executor.

@param baseUrl gateway origin, e.g. `https://api.mapmap.ai` (a trailing
  slash is tolerated); @param maxAttempts total tries including the first;
  @param backoffMillis attempt-number → delay before the next try.

**Example**

```kotlin
import ai.mapmap.nav.HttpProbeUploader
import ai.mapmap.nav.NavigationEngine
import ai.mapmap.nav.contributingProbe
import ai.mapmap.nav.LocationProvider
import uniffi.mapmap.ProbeConfig

// Opt-in aggregate probe upload (POST /v1/probe), retried with backoff.
suspend fun driveAndContribute(engine: NavigationEngine, provider: LocationProvider) {
    val uploader = HttpProbeUploader("https://api.mapmap.ai", apiKey = "snk_…")
    engine.navigate(provider)
        .contributingProbe(ProbeConfig(enabled = true), uploader)
        .collect { }
}
```

#### `LocationSample`

*Class* · `ai/mapmap/nav/LocationProvider.kt`

```kotlin
public data class LocationSample(val lat: Double, val lon: Double, val timestampMs: Long, val speedMps: Double? = null, val bearingDeg: Double? = null, val horizontalAccuracyM: Double? = null)
```

One position sample fed into guidance.

@property timestampMs milliseconds since the Unix epoch.
@property speedMps speed over ground, when the source reports it.
@property bearingDeg course over ground, degrees clockwise from true
  north, `[0, 360)`.
@property horizontalAccuracyM estimated 1-sigma horizontal accuracy in
  metres. Pass the platform value whenever available, the core uses it
  to gate step advance and deviation detection.

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `val lat` | `Double` |  |  |
| `val lon` | `Double` |  |  |
| `val timestampMs` | `Long` |  |  |
| `val speedMps` | `Double?` | `null` |  |
| `val bearingDeg` | `Double?` | `null` |  |
| `val horizontalAccuracyM` | `Double?` | `null` |  |

#### `NavigationEngine`

*Class* · `ai/mapmap/nav/NavigationEngine.kt`

```kotlin
public class NavigationEngine(private val routeResponseJson: String, private val config: GuidanceConfig = GuidanceConfig(), private val dispatcher: CoroutineDispatcher = Dispatchers.Default)
```

Turn-by-turn guidance over one computed route.

The Rust core's `GuidanceSession` (Ferrostar-based) is the navigation
state machine; this engine owns its lifecycle and adapts it to
coroutines: stream [`LocationSample`](#locationsample)s in from any [`LocationProvider`](#locationprovider),
collect [`NavigationState`](#navigationstate)s out. The stream completes after `Arrived`.
An engine is bound to a single route, on `OffRoute`, compute a new
route and build a new engine (see `NavigationState.needsReroute`).

Ferrostar UI note: the Stadia Maps `ferrostar` Kotlin artefacts can sit
on top of this state (`uniffi.ferrostar` classes are packaged in this
SDK and served by the same native library), but our `GuidanceSession`
remains the state machine, see android/README.md, "Ferrostar boundary".

**Initialisers and methods**

- `public constructor(route: RouteResult, config: GuidanceConfig = GuidanceConfig(), dispatcher: CoroutineDispatcher = Dispatchers.Default)`: Build an engine from a route computed by `ai.mapmap.routing.OfflineRouting.route`.
- `public fun navigate(provider: LocationProvider, manualAdvance: Flow<Unit> = emptyFlow()): Flow<NavigationState>`: Run guidance: feed every fix from `provider` into a fresh native session and emit the resulting states. The flow is cold, each collection starts a new session at the first route step, and completes after arrival. Guidance maths runs on `dispatcher`. @param manualAdvance emit `Unit` to force-advance to the next step, the tunnel/no-GNSS escape hatch. Ignored before the first fix. @throws uniffi.mapmap.NavCoreException if the route response cannot start a session (e.g. malformed JSON).
- `public fun totalSteps(): UInt`: Total number of steps in the route (including arrival), for progress UIs. Opens a short-lived native session.

**Example**

```kotlin
import ai.mapmap.nav.AndroidLocationProvider
import ai.mapmap.nav.NavigationEngine
import ai.mapmap.nav.VoiceGuidance
import android.content.Context
import kotlinx.coroutines.flow.takeWhile

suspend fun drive(context: Context, routeResponseJson: String) {
    val engine = NavigationEngine(routeResponseJson)
    VoiceGuidance(context, language = "en-GB").use { voice ->
        engine.navigate(AndroidLocationProvider(context))
            .takeWhile { state -> !state.isArrived }
            .collect { state -> voice.handle(state) }
    }
}
```

#### `NavigationState`

*Class* · `ai/mapmap/nav/NavigationState.kt`

```kotlin
public data class NavigationState(val fix: LocationSample, val update: GuidanceUpdate)
```

One guidance emission: the fix that produced it plus the core's verdict.

[`GuidanceUpdate`](#guidanceupdate) is the Rust core's sealed state, `Navigating`,
`Arrived` or `OffRoute`, and the single source of truth. This wrapper
only adds the triggering sample and display conveniences.

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `val fix` | `LocationSample` |  |  |
| `val update` | `GuidanceUpdate` |  |  |
| `val isArrived` | `Boolean` |  | True once the core reports arrival at the destination. |
| `val needsReroute` | `Boolean` |  | True when the user has left the route entirely and the app should request a recalculation (build a new route + [`NavigationEngine`](#navigationengine)). |

#### `ReplayLocationProvider`

*Class* · `ai/mapmap/nav/ReplayLocationProvider.kt`

```kotlin
public class ReplayLocationProvider(private val corpus: DriveCorpus, private val timeScale: Double = 1.0, private val epochStartMs: Long = 0L) : LocationProvider
```

Replays a `.drive.jsonl` corpus (the `sn-replay` QA format) as a
[`LocationProvider`](#locationprovider).

Fix timestamps in the corpus are relative to drive start; they are
rebased onto `epochStartMs` so guidance sees monotonic wall-clock-like
times.

@param corpus a parsed corpus (see `DriveCorpus.parse`).
@param timeScale playback speed: `1.0` = real time, `2.0` = twice as
  fast, `0.0` = emit every fix immediately (unit tests).
@param epochStartMs epoch milliseconds assigned to the first fix.

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `val locations` | `Flow<LocationSample>` |  |  |

Nested: [`ReplayLocationProvider.Companion`](#replaylocationprovidercompanion).

**Example**

```kotlin
import ai.mapmap.nav.NavigationEngine
import ai.mapmap.nav.ReplayLocationProvider
import ai.mapmap.replay.DriveCorpus
import java.io.File

// Replay a recorded drive through guidance, ten times faster than real time.
suspend fun replay(corpusFile: File, routeResponseJson: String) {
    val corpus = DriveCorpus.parse(corpusFile.readText())
    NavigationEngine(routeResponseJson)
        .navigate(ReplayLocationProvider(corpus, timeScale = 10.0))
        .collect { state -> println(state.update) }
}
```

#### `SignedChannelIndex`

*Class* · `ai/mapmap/territory/ChannelSource.kt`

```kotlin
public data class SignedChannelIndex(val json: String, val signatureBase64: String)
```

The exact served bytes of a channel index and its detached signature.

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `val json` | `String` |  |  |
| `val signatureBase64` | `String` |  |  |

#### `TerritoryStore`

*Class* · `ai/mapmap/territory/TerritoryStore.kt`

```kotlin
public class TerritoryStore(private val rootDir: File, private val verifyingKeyHex: String, private val dispatcher: CoroutineDispatcher = Dispatchers.IO, private val regionKeyHex: String? = null) : AutoCloseable
```

Coroutine-friendly store of signed offline territory packages.

Wraps the Rust [`TerritoryManager`](#territorymanager): every package is ed25519-signature and
BLAKE3-layer-hash verified before promotion, and nothing is trusted until
verification passes. All calls hop to `dispatcher` (IO by default), installs read whole archives and must never run on the main thread.

`territories` is a hot `StateFlow` of the installed set; it refreshes
after every mutating call and on `refresh`.

Installs are cancellable. The overloads taking a [`CancelToken`](#canceltoken) (from the
core) can be stopped from any thread by calling `cancel()` on it, and they
also trip the token when the calling coroutine is cancelled, so a plain
`job.cancel()` works the way callers expect. A cancelled install returns
[`InstallOutcome.Cancelled`](#installoutcomecancelled) / [`UpdateOutcome.Cancelled`](#updateoutcomecancelled) rather than
throwing: nothing was installed, staging is removed, the previous version
is untouched. Cancelling after completion changes nothing.

Custom regions (`region-` ids, cut by `POST /v1/jobs/territory`) are
signed by a separate region key. Pass its public half as `regionKeyHex`
(`TerritoryKeys.HOSTED_REGION_KEY_HEX` for the hosted gateway) to accept
them; the core then verifies `region-` ids against that key only and
every other id against the factory key only. Without it every `region-`
package is refused as a bad signature.

@param rootDir directory the store owns; created on first use.
@param verifyingKeyHex the factory signing key's public half, 64 hex
  characters. Baked into the app at build time, treat it like a pin.
@param regionKeyHex the region signing key's public half, 64 hex
  characters, or `null` to refuse custom regions. Pinned like the
  factory key; never read from the network.

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `val acceptsCustomRegions` | `Boolean` |  | Whether this store was given a region key, and so can install custom regions. |
| `val territories` | `StateFlow<List<TerritoryInfo>>` |  | The installed territories, refreshed after every mutating call. |

**Initialisers and methods**

- `public suspend fun refresh(): List<TerritoryInfo>`: Re-read the installed set from disk and emit it on `territories`.
- `public suspend fun install(snpkg: File): TerritoryInfo`: Install a packed `.snpkg` archive. Verify-then-promote: a package that fails verification leaves no trace on disk. @throws NavCoreException.Verification if the signature or any layer hash does not verify.
- `public suspend fun install(snpkg: File, cancel: CancelToken): InstallOutcome`: `install` with a cancellation handle. The core polls `cancel` between archive entries, before verification and before promotion; a tripped token (or a cancelled calling coroutine) yields [`InstallOutcome.Cancelled`](#installoutcomecancelled) with staging removed and nothing installed.
- `public suspend fun installDirectory(packageDir: File): TerritoryInfo`: Install an unpacked package directory (same verification rules).
- `public suspend fun installDirectory(packageDir: File, cancel: CancelToken): InstallOutcome`: `installDirectory` with a cancellation handle; see `install`.
- `public suspend fun installFromChannel(territoryId: String, indexJson: String, indexSigBase64: String, fetcher: LayerFetcher, omit: List<MapLayerKind> = emptyList(), cancel: CancelToken = CancelToken()): ChannelInstallOutcome`: Install `territoryId` **from its channel**: the first-install counterpart of `applyUpdate`. `indexJson` / `indexSigBase64` are the exact served bytes of the channel index and its detached signature; the channel's latest version is installed, fetching only the layers not in `omit` through `fetcher`. Nothing is trusted but the pinned keys: the index signature, the manifest signature and every layer hash are checked, and the assembled package goes through the full verification pass before it is promoted. Interrupted installs resume (verified layers stay in the layer cache). A tripped `cancel` (or a cancelled calling coroutine) yields [`ChannelInstallOutcome.Cancelled`](#channelinstalloutcomecancelled) with nothing installed. @throws NavCoreException.Verification if a signature or hash does not verify, including a `region-` id on a store without a region key.
- `public suspend fun sync(territoryId: String, indexJson: String, indexSigBase64: String, fetcher: LayerFetcher, omit: List<MapLayerKind> = emptyList(), cancel: CancelToken = CancelToken()): ChannelSync`: Bring `territoryId` to the channel's latest version, whichever way that takes: a first install from the channel when it is not installed, a signed differential update when an older version is, nothing when it is already current. Updates go through `checkForUpdate` / `applyUpdate`, so a channel can never move an installed territory backwards.
- `public suspend fun sync(territoryId: String, source: ChannelSource, apiKey: String? = null, omit: List<MapLayerKind> = emptyList(), cancel: CancelToken = CancelToken(), onBytes: ((Long) -> Unit)? = null): ChannelSync`: `sync` over HTTPS: fetch the signed index from `source` and every layer relative to it with an [`HttpChannelFetcher`](#httpchannelfetcher). `cancel` also stops a transfer in progress.
- `public suspend fun installCustomRegion(territoryId: String, indexUrl: String, indexSigUrl: String, apiKey: String? = null, omit: List<MapLayerKind> = emptyList(), cancel: CancelToken = CancelToken(), onBytes: ((Long) -> Unit)? = null): ChannelSync`: Install, or update, a custom region from the `territory_id`, `index_url` and `index_sig_url` of a `POST /v1/jobs/territory` result. A re-cut of the same area after the country is republished is the same id at a newer version, so calling this again is the update. @throws IllegalStateException if this store has no `regionKeyHex`. @throws IllegalArgumentException if `territoryId` is not a `region-` id or `indexUrl` is not an `index.json` URL.
- `public suspend fun verify(territoryId: String): TerritoryInfo`: Re-verify an installed territory from disk: the manifest signature (against the key its id is partitioned to), the hash of every layer it holds and the absence of every layer it omitted. The on-demand integrity check for after a restore or a crash. @throws NavCoreException.Verification when the install must be repaired or reinstalled.
- `public suspend fun setActive(territoryId: String): Unit`: Mark `territoryId` active; persisted atomically by the core.
- `public suspend fun active(): String?`: The active territory id, if one is set and still installed.
- `public suspend fun activeTerritory(): TerritoryInfo?`: The active territory's full info, if any.
- `public suspend fun layerPath(territoryId: String, kind: MapLayerKind): File`: Absolute path of one layer of an installed territory, e.g. [`MapLayerKind.ValhallaTiles`](#maplayerkindvalhallatiles) for the on-device router, [`MapLayerKind.Pmtiles`](#maplayerkindpmtiles) for MapLibre.
- `public suspend fun territoryStyle(territoryId: String, theme: StyleTheme = StyleTheme.LIGHT): String`: A complete MapLibre style JSON for an installed territory with **local** (offline) references, mirroring the web SDK's `buildStyle()`. Load it straight into MapLibre, the territory tile source points at the installed PMTiles layer via a `pmtiles://<absolute path>` URL, so the map renders with radios off. Packages without a baked style layer get the default MapMap theme compiled on the fly.
- `public suspend fun openSearch(territoryId: String): TerritorySearch`: Open the installed territory's bundled geocode index for fully offline on-device search. The returned handle holds the index open (mmap-backed), create it once per territory and reuse it across queries (calls are synchronous and disk-bound; keep them off the main thread): ``kotlin val search = store.openSearch("uk") val places = search.search("watford gap", near = null, limit = 10u) ``
- `public suspend fun remove(territoryId: String): Unit`: Remove an installed territory (clears the active marker if needed).
- `public suspend fun planUpdate(newManifestJson: String): UpdatePlan`: Plan a differential update against a newly served (signed) manifest JSON: which layers must be downloaded, and how many bytes.
- `public suspend fun checkForUpdate(territoryId: String, indexJson: String, indexSigBase64: String, fetcher: LayerFetcher): AvailableUpdate?`: Check the signed update channel for a newer version of an installed territory. Fail-closed: the channel index signature is verified before anything else, `fetcher` is never invoked for a channel that does not verify against this store's pinned key. @param indexJson the exact bytes served as `index.json`. @param indexSigBase64 the content of `index.json.sig`. @param fetcher the app's download transport (OkHttp, an air-gapped mirror, …), the core decides what to fetch and re-verifies all content itself. @return the available update, or `null` when already on the channel's latest version. @throws NavCoreException.Verification if the index or remote manifest signature does not verify.
- `public suspend fun applyUpdate(territoryId: String, newManifestJson: String, newManifestSigBase64: String, fetcher: LayerFetcher): UpdateResult`: Apply a differential update: unchanged layers are hard-linked from the current install, only changed layers are fetched through `fetcher`, the staged package is fully re-verified, and the swap is atomic. Any failure, transport, hash mismatch, bad signature, leaves the installed territory untouched; the active flag survives.
- `public suspend fun applyUpdate(territoryId: String, newManifestJson: String, newManifestSigBase64: String, fetcher: LayerFetcher, cancel: CancelToken): UpdateOutcome`: `applyUpdate` with a cancellation handle. The core polls `cancel` before every layer download, between archive entries while unpacking, before each hash pass and before promotion; a tripped token (or a cancelled calling coroutine) yields [`UpdateOutcome.Cancelled`](#updateoutcomecancelled). The installed version is untouched and staging removed; layers already fetched *and verified* stay in the resumable cache, so a later attempt with the same manifest re-fetches only what is missing. The token cannot interrupt `fetcher` while it is inside a transfer: give the fetcher the same token and have it poll `isCancelled()` between chunks (or abort its request). Whatever the fetcher then throws, a tripped token reports [`UpdateOutcome.Cancelled`](#updateoutcomecancelled) rather than a transport error.
- `override fun close()`

**Example**

```kotlin
import ai.mapmap.territory.ChannelSource
import ai.mapmap.territory.TerritoryKeys
import ai.mapmap.territory.TerritoryStore
import java.io.File

suspend fun installUk(filesDir: File) {
    TerritoryStore(
        rootDir = File(filesDir, "territories"),
        verifyingKeyHex = TerritoryKeys.HOSTED_FACTORY_KEY_HEX,
        regionKeyHex = TerritoryKeys.HOSTED_REGION_KEY_HEX,
    ).use { store ->
        // First install or update, whichever is needed, from the hosted channel.
        val result = store.sync("uk", ChannelSource.gateway(), apiKey = "snk_…")
        store.setActive("uk")
        println(result)
    }
}
```

#### `TtsStartup`

*Class* · `ai/mapmap/nav/VoiceGuidance.kt`

```kotlin
public class TtsStartup<T>(private val languageTag: String?)
```

Pure init-order state machine for the asynchronous `TextToSpeech`
engine, unit-testable on any JVM, like [`VoiceProsody`](#voiceprosody).

The platform silently drops `setLanguage` calls made before the engine
has finished initialising, so the language is applied in the SUCCESS
callback, and *before* any prompt deferred during startup is flushed,
so the first prompt already speaks in the configured voice.

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `var ready` | `Boolean` | `false` | Whether the engine reported `TextToSpeech.SUCCESS` yet. |

**Initialisers and methods**

- `public fun defer(announcement: T): Boolean`: Defer `announcement` until init completes. Returns `true` when it was deferred (engine not ready, the latest prompt wins); `false` when the engine is ready and the caller should speak it now.
- `public fun onInitSuccess(): InitActions<T>`: Handle the engine's SUCCESS callback. Idempotent per prompt.

Nested: [`TtsStartup.InitActions`](#ttsstartupinitactions).

**Example**

```kotlin
import ai.mapmap.nav.TtsStartup

// The start-up queue VoiceGuidance uses while TextToSpeech initialises: the latest prompt wins.
val startup = TtsStartup<String>(languageTag = "en-GB")
fun announce(text: String) {
    if (!startup.defer(text)) println("speak now: $text")
}
fun onTtsReady() {
    val actions = startup.onInitSuccess()
    actions.flush?.let { println("speak deferred: $it") }
}
```

#### `TtsStartup.InitActions`

*Class* · `ai/mapmap/nav/VoiceGuidance.kt`

```kotlin
public data class InitActions<T>(val languageToSet: String?, val flush: T?)
```

What to do on init SUCCESS, in order: set the language, then flush.

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `val languageToSet` | `String?` |  |  |
| `val flush` | `T?` |  |  |

#### `ValhallaMobileRouter`

*Class* · `ai/mapmap/routing/ValhallaMobileRouter.kt`

```kotlin
public class ValhallaMobileRouter(configPath: String) : OfflineRouter, AutoCloseable
```

[`OfflineRouter`](#offlinerouter) backed by the on-device Valhalla engine
(`ai.mapmap:valhalla-engine`: Rallista/valhalla-mobile's wrapper, MIT,
https://github.com/Rallista/valhalla-mobile, compiled against the
Valhalla release and patch series the server builds tiles with; see
tools/mobile-engine/engine.pin).

valhalla-mobile's actor exposes exactly the
`route(requestJson) -> responseJson` shape the Rust core's `LocalRouter`
callback expects; the call crosses into it through
`ValhallaActorBridge` (see that file for why the seam is Java).

The engine is a `compileOnly` dependency of the SDK: apps that use
this class must declare `ai.mapmap:valhalla-engine` themselves (see
android/README.md). Do not use `io.github.rallista:valhalla-mobile`
instead: its releases carry a different Valhalla than the factory tiles
(0.5.1 to 0.6.3: 3.6.3, which answers 171 "No suitable edges" on them).
Routing is CPU-bound native work: call it through
`OfflineRouting.route`, which hops off the main thread.

Holds one native engine for its lifetime; reuse one router across
routes and `close` it when done.

@param configPath absolute path of a `valhalla.json` whose
  `mjolnir.tile_dir` points at the territory's Valhalla tile layer.

**Initialisers and methods**

- `override fun route(requestJson: String): String`
- `override fun close(): Unit`: Release the native engine. Routing after this throws.

Nested: [`ValhallaMobileRouter.Companion`](#valhallamobileroutercompanion).

**Example**

```kotlin
import ai.mapmap.MapMap
import ai.mapmap.routing.OfflineRouting
import ai.mapmap.routing.ValhallaMobileRouter
import ai.mapmap.territory.TerritoryStore
import uniffi.mapmap.CostingModel
import uniffi.mapmap.MapLayerKind
import uniffi.mapmap.RoutePoint
import java.io.File

// On-device routing over an installed territory's Valhalla tiles.
suspend fun routeOffline(store: TerritoryStore, cacheDir: File) {
    val tiles = store.layerPath("uk", MapLayerKind.ValhallaTiles)
    ValhallaMobileRouter.fromTileDir(tiles, scratchDir = cacheDir).use { router ->
        val route = OfflineRouting.route(
            router,
            listOf(RoutePoint(51.5072, -0.1276), RoutePoint(51.4545, -2.5879)),
            costing = CostingModel.TRUCK,
            adr = MapMap.defaultAdrProfile(),
        )
        println(route)
    }
}
```

#### `VoiceGuidance`

*Class* · `ai/mapmap/nav/VoiceGuidance.kt`

```kotlin
public class VoiceGuidance(context: Context, language: String? = null, private val baseRate: Float = 1.0f) : AutoCloseable
```

Spoken turn-by-turn guidance over the platform `TextToSpeech` engine
(offline-capable, no network required with a device voice installed).

Feed every [`NavigationState`](#navigationstate) (or raw [`GuidanceUpdate`](#guidanceupdate)) from
`NavigationEngine.navigate`:

``kotlin
val voice = VoiceGuidance(context)
engine.navigate(provider)
    .onEach { voice.handle(it.update) }
    .collect { render(it) }
// …
voice.close()
``

The core schedules each spoken prompt by its trigger distance and
repeats it on every update until it is superseded, so the speaking rule
is: announce each `utteranceId` exactly once, the first time it appears.
Severity maps to prosody and earcons via [`VoiceProsody`](#voiceprosody); critical
prompts flush the queue. Off-route plays the warning earcon once per
episode. Observe `isSpeaking` / `onSpeakingChanged` to reflect live
speaking state in UI (e.g. a pulsing voice icon). Audio focus is
requested transiently with ducking
(`AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK`) around each
announcement, so music dims rather than stops.

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `var isSpeaking` | `Boolean` | `false` | Whether an utterance is currently being spoken or queued. Mirrors the transitions delivered to `onSpeakingChanged`. Guarded by `synchronized(focusPolicy)` like the underlying bookkeeping. |
| `var onSpeakingChanged` | `((Boolean) -> Unit)?` | `null` | Invoked on every speaking-state transition: `true` when the first utterance of a burst is handed to the engine, `false` once the last outstanding one ends (done, error, stop, mute or `close`). End transitions arrive on the TTS engine's callback thread, hop to the main thread before touching UI. |
| `var muted` | `Boolean` | `false` | Whether announcements are muted. Toggle freely mid-drive. |
| `var volume` | `Float` | `1.0f` | Announcement volume, 0.0–1.0 (applied to speech and earcons). |

**Initialisers and methods**

- `public fun handle(update: GuidanceUpdate)`: Consume one guidance update. Cheap; call it for every emission of `NavigationEngine.navigate`.
- `public fun handle(state: NavigationState): Unit`: Convenience overload for `NavigationEngine.navigate` emissions.
- `override fun close()`: Release the TTS engine, earcon player and audio focus.

**Example**

```kotlin
import ai.mapmap.nav.VoiceGuidance
import android.content.Context

fun voice(context: Context): VoiceGuidance =
    VoiceGuidance(context, language = "en-GB").apply {
        volume = 0.8f
        onSpeakingChanged = { speaking -> println("speaking: $speaking") }
    }
// For every NavigationState from NavigationEngine.navigate: voice.handle(state). Close it when done.
```

#### `VoiceProsody.Prosody`

*Class* · `ai/mapmap/nav/VoiceGuidance.kt`

```kotlin
public data class Prosody(val rate: Float, val pitch: Float, val interrupt: Boolean)
```

Speech parameters for one severity level.

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `val rate` | `Float` |  |  |
| `val pitch` | `Float` |  |  |
| `val interrupt` | `Boolean` |  |  |

### Enums (ai.mapmap)

[`ProbeUploadOutcome`](#probeuploadoutcome)

#### `ProbeUploadOutcome`

*Enum* · `ai/mapmap/nav/ProbeContribution.kt`

```kotlin
public enum class ProbeUploadOutcome
```

The outcome of one upload attempt, surfaced through
[`HttpProbeUploader`](#httpprobeuploader)'s result callback for observability and tests.

| Case | Description |
| --- | --- |
| `ACCEPTED` | `202`, accepted and stored. |
| `REFUSED` | `403`, the key is not opted in; dropped, not retried. |
| `NOT_ENABLED` | `501`, server collection is not enabled (`SN_PROBE_DIR` unset); dropped. |
| `REJECTED` | `400` or any other 4xx, malformed/unacceptable; dropped, not retried. |
| `GAVE_UP` | IO error or `5xx` after exhausting retries; given up on. |

### Interfaces (ai.mapmap)

[`ChannelSync`](#channelsync), [`LocationProvider`](#locationprovider), [`OfflineRouter`](#offlinerouter), [`ProbeUploader`](#probeuploader)

#### `ChannelSync`

*Interface* · `ai/mapmap/territory/TerritoryStore.kt`

```kotlin
public sealed interface ChannelSync
```

What `TerritoryStore.sync` did.

Nested: [`ChannelSync.Installed`](#channelsyncinstalled), [`ChannelSync.Updated`](#channelsyncupdated), [`ChannelSync.UpToDate`](#channelsyncuptodate), [`ChannelSync.Cancelled`](#channelsynccancelled).

#### `LocationProvider`

*Interface* · `ai/mapmap/nav/LocationProvider.kt`

```kotlin
public interface LocationProvider
```

A source of location fixes for [`NavigationEngine`](#navigationengine).

Implementations: [`AndroidLocationProvider`](#androidlocationprovider) (the platform's fused
provider) for live navigation, [`ReplayLocationProvider`](#replaylocationprovider) (a
`.drive.jsonl` corpus) for QA and demos. Any cold `Flow` of samples in
ascending timestamp order qualifies.

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `val locations` | `Flow<LocationSample>` |  | The stream of fixes. Collected once per navigation session. |

#### `OfflineRouter`

*Interface* · `ai/mapmap/routing/OfflineRouter.kt`

```kotlin
public interface OfflineRouter : LocalRouter
```

An on-device routing engine.

This is the Rust core's [`LocalRouter`](#localrouter) callback interface with a Kotlin
name: implementations execute a Valhalla turn-by-turn request JSON
synchronously and return the raw response JSON (either a route response
or a Valhalla error body, the core parses and classifies both).

The production implementation is [`ValhallaMobileRouter`](#valhallamobilerouter); tests inject
fakes that return canned responses.

#### `ProbeUploader`

*Interface* · `ai/mapmap/nav/ProbeContribution.kt`

```kotlin
public interface ProbeUploader
```

Where a completed probe batch goes. Called off the navigation flow's hot
path: `enqueue` must be non-blocking and must never throw.

**Initialisers and methods**

- `public fun enqueue(body: String)`: Hand one probe body, the JSON string from `uniffi.mapmap.ProbeCollector.finishJson`, off to be POSTed to `/v1/probe`. Fire-and-forget.

### Objects (ai.mapmap)

[`AdrCompliance`](#adrcompliance), [`AndroidLocationProvider.Companion`](#androidlocationprovidercompanion), [`ChannelSource.Companion`](#channelsourcecompanion), [`ChannelSync.Cancelled`](#channelsynccancelled), [`DriveCorpus.Companion`](#drivecorpuscompanion), [`GuidanceDisplay`](#guidancedisplay), [`MapMap`](#mapmap), [`OfflineRouting`](#offlinerouting), [`ReplayLocationProvider.Companion`](#replaylocationprovidercompanion), [`TerritoryKeys`](#territorykeys), [`ValhallaConfigWriter`](#valhallaconfigwriter), [`ValhallaMobileRouter.Companion`](#valhallamobileroutercompanion), [`VoiceProsody`](#voiceprosody)

#### `AdrCompliance`

*Object* · `ai/mapmap/adr/AdrCompliance.kt`

```kotlin
public object AdrCompliance
```

On-device ADR 8.6.4 dangerous-goods enforcement.

Two enforcement points, both running the same Rust domain model
(`sn-adr`, conservative ADR 8.6.4 reading):

1. **Pre-route**, pass the [`AdrProfile`](#adrprofile) to
   `ai.mapmap.routing.OfflineRouting.route`; the core
   merges the restrictions into Valhalla truck costing, so forbidden
   tunnels and undersized roads are excluded before a route exists.
2. **En-route**, call [`checkTunnel`](#checktunnel) against a tunnel's category (e.g.
   from map data ahead of the vehicle) to gate entry while driving, and
   [`forbiddenCategories`](#forbiddencategories) to badge the whole profile in the UI.

**Initialisers and methods**

- `public fun checkTunnel(profile: AdrProfile, category: AdrTunnelCategory): AdrDecision`: May the vehicle described by `profile` pass through a tunnel of `category`? Blocked decisions carry a human-readable reason citing ADR 8.6.4.
- `public fun forbiddenCategories(profile: AdrProfile): List<AdrTunnelCategory>`: All tunnel categories the vehicle is forbidden from, ascending. Empty when unrestricted.
- `public fun isTunnelRestricted(profile: AdrProfile): Boolean`: True when the profile is barred from at least one tunnel category.
- `public fun validateProfile(profile: AdrProfile): List<String>`: Validate a profile's physical plausibility before it goes anywhere near routing. Returns a list of human-readable problems; empty means valid. Pure Kotlin, safe to call from previews and unit tests without the native library.

#### `AndroidLocationProvider.Companion`

*Object* · `ai/mapmap/nav/AndroidLocationProvider.kt`

```kotlin
public companion object
```

**Initialisers and methods**

- `public fun Location.toSample(): LocationSample`: Map a platform `Location` onto the SDK's [`LocationSample`](#locationsample).

#### `ChannelSource.Companion`

*Object* · `ai/mapmap/territory/ChannelSource.kt`

```kotlin
public companion object
```

No trailing slash; layer paths are appended after a `/`.

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `val DEFAULT_GATEWAY` | `String` | `"https://api.mapmap.ai"` | The hosted gateway. |

**Initialisers and methods**

- `public fun gateway(baseUrl: String = DEFAULT_GATEWAY): ChannelSource`: A gateway's country channel: `GET /territories` (the signed index) and `GET /territories/index.sig`.
- `public fun customRegion(territoryId: String, indexUrl: String, indexSigUrl: String): ChannelSource`: A custom region's own one-territory channel, from the `index_url` / `index_sig_url` of a `POST /v1/jobs/territory` result. The gateway serves the index at `<base>/v1/territories/<id>/index.json` and the layers under `<base>/territories/<id>/...`, so the layer base is `<base>`. Any other `.../index.json` (a mirror of the region directory, laid out as `SN_REGION_DIR/<id>/`) resolves layers next to the index. @throws IllegalArgumentException if `territoryId` is not a custom-region id or `indexUrl` does not end in `index.json`.
- `public fun isCustomRegionId(territoryId: String): Boolean`: Whether `territoryId` has the shape the gateway gives custom regions.

#### `ChannelSync.Cancelled`

*Object* · `ai/mapmap/territory/TerritoryStore.kt`

```kotlin
public data object Cancelled : ChannelSync
```

The [`CancelToken`](#canceltoken) was tripped; nothing changed on disk.

#### `DriveCorpus.Companion`

*Object* · `ai/mapmap/replay/DriveCorpus.kt`

```kotlin
public companion object
```

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `val FORMAT_VERSION` | `Int` | `1` | The corpus format version this parser accepts. |

**Initialisers and methods**

- `public fun parse(jsonl: String): DriveCorpus`: Parse and validate a corpus from JSON-lines text. @throws CorpusFormatException describing the first violation, with its 1-based line number where applicable.

#### `GuidanceDisplay`

*Object* · `ai/mapmap/nav/NavigationState.kt`

```kotlin
public object GuidanceDisplay
```

Pure display mappers for [`GuidanceUpdate`](#guidanceupdate). Kept free of native calls so
they are unit-testable on any JVM.

**Initialisers and methods**

- `public fun instruction(update: GuidanceUpdate): String?`: The instruction to show for the current update, if any.
- `public fun severity(update: GuidanceUpdate): InstructionSeverity`: How urgently the update should be surfaced (see the core's `InstructionSeverity` for the derivation). Off-route is a `InstructionSeverity.WARNING`, the driver has left the route and a recalculation is due; arrival is routine.
- `public fun formatDistance(metres: Double): String`: Format a distance in metres for guidance display: metres under 1 km (10 m steps under 100 m, 50 m steps above), kilometres with one decimal from 1 km.
- `public fun summarise(update: GuidanceUpdate): String`: One-line status suitable for a debug HUD or notification.

#### `MapMap`

*Object* · `ai/mapmap/MapMap.kt`

```kotlin
public object MapMap
```

Entry point for the MapMap Android SDK.

The SDK is a thin, idiomatic Kotlin layer over the Rust navigation core
(`sn-nav-core`, shipped as `libsn_nav_core.so`). The raw UniFFI bindings
live in `uniffi.mapmap` (and `uniffi.ferrostar`, which is linked
into the same native library); everything under `ai.mapmap`
is the supported surface:

- `ai.mapmap.territory.TerritoryStore`, signed offline
  territory packages (install / verify / activate / update planning).
- `ai.mapmap.routing.OfflineRouting`, on-device Valhalla
  routing with ADR dangerous-goods enforcement merged into every request.
- `ai.mapmap.adr.AdrCompliance`, pre-route and en-route
  ADR 8.6.4 tunnel checks.
- `ai.mapmap.nav.NavigationEngine`, turn-by-turn guidance
  over a computed route, fed by any
  `ai.mapmap.nav.LocationProvider`.

**Initialisers and methods**

- `public fun ensureLoaded()`: Eagerly load the native core and verify the bindings match it. Calling this is optional, the first native call loads the library lazily, but doing it at app start surfaces packaging problems (missing ABI, mismatched binding checksums) as an immediate `UnsatisfiedLinkError` instead of a mid-drive crash.
- `public fun defaultGuidanceConfig(): GuidanceConfig`: A [`GuidanceConfig`](#guidanceconfig) with the core's documented defaults.
- `public fun defaultAdrProfile(): AdrProfile`: An [`AdrProfile`](#adrprofile) with the core's documented defaults: EU maximum authorised dimensions (Council Directive 96/53/EC), no dangerous goods.

#### `OfflineRouting`

*Object* · `ai/mapmap/routing/OfflineRouting.kt`

```kotlin
public object OfflineRouting
```

On-device route computation.

The Rust core builds the Valhalla request (merging ADR dangerous-goods
and dimensional costing options, rejecting conflicts), the injected
[`OfflineRouter`](#offlinerouter) executes it against local tiles, and the core parses the
response into a [`RouteResult`](#routeresult) whose `valhallaResponseJson` feeds
`ai.mapmap.nav.NavigationEngine`.

**Initialisers and methods**

- `public fun requireCompatibleCosting(costing: CostingModel, adr: AdrProfile?)`: ADR profiles ride on Valhalla truck costing options; requesting any other costing model with an ADR profile is a contract violation the Rust core rejects. Checked here too so the failure is a fast, clearly-attributed `IllegalArgumentException` rather than a routing error after the FFI hop.
- `public suspend fun route(router: LocalRouter, waypoints: List<RoutePoint>, costing: CostingModel = CostingModel.AUTO, adr: AdrProfile? = null, dispatcher: CoroutineDispatcher = Dispatchers.Default): RouteResult`: Compute a route on-device. @param router the on-device engine, usually a [`ValhallaMobileRouter`](#valhallamobilerouter). @param waypoints at least two [`RoutePoint`](#routepoint)s, in visit order. @param costing the Valhalla costing model; must be `CostingModel.TRUCK` when `adr` is supplied. @param adr optional dangerous-goods/dimensional profile, when set, the request is built with the restrictions merged in, so illegal roads and tunnels are excluded before the route exists. @throws NavCoreException.Routing when the engine returns a Valhalla error body (e.g. no route, no tiles for the area).
- `public fun buildRequest(waypoints: List<RoutePoint>, costing: CostingModel = CostingModel.AUTO, adr: AdrProfile? = null): String`: Build the raw Valhalla request JSON without executing it, for callers that drive the engine themselves or log requests for audit.
- `public fun parseResponse(responseJson: String): RouteResult`: Parse a raw engine response into a [`RouteResult`](#routeresult).

#### `ReplayLocationProvider.Companion`

*Object* · `ai/mapmap/nav/ReplayLocationProvider.kt`

```kotlin
public companion object
```

**Initialisers and methods**

- `public fun DriveFix.toSample(epochStartMs: Long): LocationSample`: Map one corpus fix to a [`LocationSample`](#locationsample) rebased on `epochStartMs`.

#### `TerritoryKeys`

*Object* · `ai/mapmap/territory/TerritoryKeys.kt`

```kotlin
public object TerritoryKeys
```

The public halves of the keys that sign MapMap's hosted territory
packages, for pinning in an app. Both are ed25519 public keys as 64 hex
characters, the form [`TerritoryStore`](#territorystore) takes.

Pin them at build time; never read either from the network. An app that
runs its own factory or region signer passes its own keys instead.

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `val HOSTED_FACTORY_KEY_HEX` | `String` | `"c2230696626a1ecbc61cf9ac141eabea5a3a1a15dab70d2acc110bec79108bcf"` | The factory key: verifies every country package on the hosted channel (`https://api.mapmap.ai/territories`). Also published at mapmap.ai/docs/territories. |
| `val HOSTED_REGION_KEY_HEX` | `String` | `"6f60f17bc31be2f7696cb957b2188416741e09cb3e7bdec2719fe553213be107"` | The region key: verifies custom regions (`region-` ids) cut by the hosted gateway (`POST /v1/jobs/territory`). The core accepts it for `region-` ids ONLY, and never the factory key for them, so a leaked region key cannot forge a country package. Pass it as `TerritoryStore(regionKeyHex = ...)`. |

#### `ValhallaConfigWriter`

*Object* · `ai/mapmap/routing/ValhallaConfigWriter.kt`

```kotlin
public object ValhallaConfigWriter
```

Writes the `valhalla.json` the on-device Valhalla actor needs.

The document is Valhalla's own complete default config
(`valhalla_build_config`, MIT,
https://github.com/valhalla/valhalla/blob/master/scripts/valhalla_build_config),
bundled as `valhalla-defaults.json` next to this class, with the tile
source and a mobile-sized cache set on top. The actor reads many keys
(every `service_limits` section, `loki`, `thor`, `meili`) with no
compiled-in fallback: a config carrying only `mjolnir` makes every route
fail with "No such node (service_limits.isochrone.max_contours)", which
is what this writer produced before (found routing a custom region on a
device, `CustomRegionDeviceTest`). The iOS writer bundles the same file.

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `val DEFAULT_MAX_CACHE_BYTES` | `Long` | `268_435_456L` | Mobile default for `mjolnir.max_cache_size` (bytes). |

**Initialisers and methods**

- `public fun configJson(tileDir: File, maxCacheBytes: Long = DEFAULT_MAX_CACHE_BYTES): String`: Render the config document for a routing-tile directory (the territory's `uniffi.mapmap.MapLayerKind.ValhallaTiles` layer).

#### `ValhallaMobileRouter.Companion`

*Object* · `ai/mapmap/routing/ValhallaMobileRouter.kt`

```kotlin
public companion object
```

**Initialisers and methods**

- `public fun fromConfig(configFile: File): ValhallaMobileRouter`: Create a router from an existing `valhalla.json` config file.
- `public fun fromTileDir(tileDir: File, scratchDir: File): ValhallaMobileRouter`: Create a router for a territory's Valhalla tile directory, writing a config file under `scratchDir` (e.g. `context.cacheDir`). ``kotlin val tiles = store.layerPath(id, MapLayerKind.ValhallaTiles) val router = ValhallaMobileRouter.fromTileDir(tiles, context.cacheDir) ``

#### `VoiceProsody`

*Object* · `ai/mapmap/nav/VoiceGuidance.kt`

```kotlin
public object VoiceProsody
```

Prosody and earcon policy per [`InstructionSeverity`](#instructionseverity), pure and
unit-testable on any JVM (the platform wiring lives in [`VoiceGuidance`](#voiceguidance)).

The core derives severity (see `InstructionSeverity` in
`crates/sn-nav-core`): `INFO` for routine manoeuvres, `WARNING` when
approaching a restriction (toll, ferry, and off-route), `CRITICAL` for
prohibitions (ADR tunnel block, gate).

**Initialisers and methods**

- `public fun prosody(severity: InstructionSeverity, baseRate: Float = 1.0f): Prosody`: Prosody for `severity` at `baseRate` (default 1.0).
- `public fun earconRes(severity: InstructionSeverity): Int?`: The earcon raw resource for `severity`, or `null` when the severity plays none by default (INFO stays silent so a chime does not precede every routine manoeuvre). Assets are generated by `scripts/generate-earcons.py`.

Nested: [`VoiceProsody.Prosody`](#voiceprosodyprosody).

### Functions (ai.mapmap)

[`Flow<NavigationState>.contributingProbe`](#flownavigationstatecontributingprobe)

#### `Flow<NavigationState>.contributingProbe`

*Function* · `ai/mapmap/nav/ProbeContribution.kt`

```kotlin
public fun Flow<NavigationState>.contributingProbe(config: ProbeConfig, uploader: ProbeUploader, routeRef: String? = null, territory: String? = null, vehicleClass: String? = null, now: () -> Long = System::currentTimeMillis): Flow<NavigationState>
```

Accumulates an aggregate probe batch from a navigation stream and uploads it
once the trip ends. **A transparent pass-through when `config` is disabled**
(the shipped default): no collector is constructed and nothing is uploaded,
so leaving collection off costs nothing.

Wire it right onto the guidance stream:
``
engine.navigate(provider)
    .contributingProbe(
        ProbeConfig(enabled = consentGranted),
        HttpProbeUploader("https://api.mapmap.ai", apiKey),
        routeRef = servedRouteToken,
        territory = "uk",
        vehicleClass = "truck_40t",
    )
    .collect { render(it) }
``

Consent is the caller's responsibility: only pass `enabled = true` for a key
whose operator has set `probe_opt_in` and, for a consumer app, with the
user's opt-in consent. See `docs/PROBE-SDK-INTEGRATION.md`.

Only aggregates leave the device: the collector is architecturally incapable
of emitting a trajectory, and the first/last stretch of every trip is
discarded on-device.

@param routeRef opaque served-route token so the server can resolve segment
  indices; @param vehicleClass a costing class only (never a registration,
  VIN or driver id); @param now injectable clock for deterministic tests.

## uniffi.mapmap

### Classes (uniffi.mapmap)

[`AdrDecision`](#adrdecision), [`AdrDecision.Blocked`](#adrdecisionblocked), [`AdrProfile`](#adrprofile), [`AvailableUpdate`](#availableupdate), [`BannerLine`](#bannerline), [`CancelToken`](#canceltoken), [`ChangedLayer`](#changedlayer), [`ChannelInstallOutcome`](#channelinstalloutcome), [`ChannelInstallOutcome.Installed`](#channelinstalloutcomeinstalled), [`ChannelInstallResult`](#channelinstallresult), [`FasterRouteCandidate`](#fasterroutecandidate), [`FasterRouteConfig`](#fasterrouteconfig), [`FasterRouteRecommendation`](#fasterrouterecommendation), [`FetchException`](#fetchexception), [`FetchException.Transport`](#fetchexceptiontransport), [`GuidanceConfig`](#guidanceconfig), [`GuidanceSession`](#guidancesession), [`GuidanceUpdate`](#guidanceupdate), [`GuidanceUpdate.Navigating`](#guidanceupdatenavigating), [`GuidanceUpdate.OffRoute`](#guidanceupdateoffroute), [`InstallOutcome`](#installoutcome), [`InstallOutcome.Installed`](#installoutcomeinstalled), [`LaneGuidance`](#laneguidance), [`LayerFetcherImpl`](#layerfetcherimpl), [`LocalRouterImpl`](#localrouterimpl), [`MapLayerKind`](#maplayerkind), [`MapLayerKind.Extra`](#maplayerkindextra), [`NavCoreException`](#navcoreexception), [`NavCoreException.Guidance`](#navcoreexceptionguidance), [`NavCoreException.InvalidArgument`](#navcoreexceptioninvalidargument), [`NavCoreException.LayerNotInstalled`](#navcoreexceptionlayernotinstalled), [`NavCoreException.Routing`](#navcoreexceptionrouting), [`NavCoreException.Search`](#navcoreexceptionsearch), [`NavCoreException.Territory`](#navcoreexceptionterritory), [`NavCoreException.Verification`](#navcoreexceptionverification), [`OdRecord`](#odrecord), [`Place`](#place), [`ProbeBatch`](#probebatch), [`ProbeCollector`](#probecollector), [`ProbeConfig`](#probeconfig), [`ProbeEvent`](#probeevent), [`ProbeEvent.EtaOutcome`](#probeeventetaoutcome), [`ProbeEvent.OffRoute`](#probeeventoffroute), [`RerouteAnchor`](#rerouteanchor), [`RerouteConfig`](#rerouteconfig), [`RerouteDecision`](#reroutedecision), [`RerouteReason`](#reroutereason), [`RerouteReason.FasterRoute`](#reroutereasonfasterroute), [`RerouteReason.OffRoute`](#reroutereasonoffroute), [`RerouteState`](#reroutestate), [`RerouteState.OffRouteSuspected`](#reroutestateoffroutesuspected), [`RerouteWaypoint`](#reroutewaypoint), [`RouteManeuver`](#routemaneuver), [`RoutePoint`](#routepoint), [`RouteResult`](#routeresult), [`RouteSummary`](#routesummary), [`SegmentAggregate`](#segmentaggregate), [`SnappedFix`](#snappedfix), [`SpeedLimit`](#speedlimit), [`SpeedLimit.Known`](#speedlimitknown), [`SpokenPrompt`](#spokenprompt), [`TerritoryInfo`](#territoryinfo), [`TerritoryManager`](#territorymanager), [`TerritorySearch`](#territorysearch), [`UpdateOutcome`](#updateoutcome), [`UpdateOutcome.Applied`](#updateoutcomeapplied), [`UpdatePlan`](#updateplan), [`UpdateResult`](#updateresult), [`VisualBanner`](#visualbanner)

#### `AdrDecision`

*Class* · `uniffi/mapmap/mapmap.kt`

```kotlin
sealed class AdrDecision
```

Outcome of a tunnel-entry check, mirroring [`sn_adr::Decision`].

Nested: [`AdrDecision.Allowed`](#adrdecisionallowed), [`AdrDecision.Blocked`](#adrdecisionblocked).

#### `AdrDecision.Blocked`

*Class* · `uniffi/mapmap/mapmap.kt`

```kotlin
data class Blocked(val reason: String) : AdrDecision()
```

Passage is forbidden.

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `val reason` | `String` |  |  |

#### `AdrProfile`

*Class* · `uniffi/mapmap/mapmap.kt`

```kotlin
data class AdrProfile (var heightM: Double = 4.0, var widthM: Double = 2.55, var lengthM: Double = 16.5, var grossWeightT: Double = 40.0, var axleLoadT: Double? = null, var axleCount: UInt? = null, var hazmat: Boolean = false, var tunnelCode: AdrTunnelCode? = null)
```

Vehicle profile for ADR and dimensional enforcement, mirroring
[`sn_adr::AdrVehicleProfile`]. Defaults are the EU maximum authorised
dimensions (Council Directive 96/53/EC) with no dangerous goods.

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `var heightM` | `Double` | `4.0` |  |
| `var widthM` | `Double` | `2.55` |  |
| `var lengthM` | `Double` | `16.5` |  |
| `var grossWeightT` | `Double` | `40.0` |  |
| `var axleLoadT` | `Double?` | `null` |  |
| `var axleCount` | `UInt?` | `null` |  |
| `var hazmat` | `Boolean` | `false` |  |
| `var tunnelCode` | `AdrTunnelCode?` | `null` |  |

#### `AvailableUpdate`

*Class* · `uniffi/mapmap/mapmap.kt`

```kotlin
data class AvailableUpdate (var version: String, var changedLayers: List<ChangedLayer>, var installedBytes: ULong, var manifestJson: String, var manifestSig: String)
```

An update the channel offers for an installed territory.

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `var version` | `String` |  |  |
| `var changedLayers` | `List<ChangedLayer>` |  |  |
| `var installedBytes` | `ULong` |  |  |
| `var manifestJson` | `String` |  |  |
| `var manifestSig` | `String` |  |  |

#### `BannerLine`

*Class* · `uniffi/mapmap/mapmap.kt`

```kotlin
data class BannerLine (var text: String, var maneuverType: String?, var maneuverModifier: String?, var roundaboutExitDegrees: UShort?, var exitNumbers: List<String>, var lanes: List<LaneGuidance>)
```

One line of a visual banner (primary/secondary/sub).

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `var text` | `String` |  |  |
| `var maneuverType` | `String?` |  |  |
| `var maneuverModifier` | `String?` |  |  |
| `var roundaboutExitDegrees` | `UShort?` |  |  |
| `var exitNumbers` | `List<String>` |  |  |
| `var lanes` | `List<LaneGuidance>` |  |  |

#### `CancelToken`

*Class* · `uniffi/mapmap/mapmap.kt`

```kotlin
open class CancelToken: Disposable, AutoCloseable, CancelTokenInterface
```

A cancellation handle for an in-flight territory install or update.

The install calls on [`TerritoryManager`](#territorymanager) block the calling thread for
as long as a multi-gigabyte package takes to download, unpack and
verify. Create a token, pass it to one of the `_cancellable` variants
running on a background thread, and call [`CancelToken::cancel`] from
the UI thread when the user taps Cancel. The install polls it at safe
points and returns [`InstallOutcome::Cancelled`] /
[`UpdateOutcome::Cancelled`] with its staging directory removed: no
partial package is left on disk, the previously installed version is
untouched, and layers that had already been verified stay in the
resumable cache for the next attempt. Cancelling after the call has
returned changes nothing.

The token cannot interrupt a [`LayerFetcher`](#layerfetcher) blocked in a transfer:
give the fetcher the same token and have it poll
[`CancelToken::is_cancelled`] between chunks (or abort its request when
the app cancels). Whatever the fetcher then returns, the install
reports `Cancelled`, not a transport error.

**Initialisers and methods**

- `constructor()`: A fresh, uncancelled token.
- `override fun destroy()`
- `override fun close()`
- `override fun cancel()`: Request cancellation. Idempotent and safe from any thread.
- `override fun isCancelled(): Boolean`: Whether [`Self::cancel`] has been called.

#### `ChangedLayer`

*Class* · `uniffi/mapmap/mapmap.kt`

```kotlin
data class ChangedLayer (var kind: String, var path: String, var bytes: ULong)
```

One layer that a territory update must download.

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `var kind` | `String` |  |  |
| `var path` | `String` |  |  |
| `var bytes` | `ULong` |  |  |

#### `ChannelInstallOutcome`

*Class* · `uniffi/mapmap/mapmap.kt`

```kotlin
sealed class ChannelInstallOutcome
```

How a channel install ended.

Nested: [`ChannelInstallOutcome.Installed`](#channelinstalloutcomeinstalled), [`ChannelInstallOutcome.Cancelled`](#channelinstalloutcomecancelled).

#### `ChannelInstallOutcome.Installed`

*Class* · `uniffi/mapmap/mapmap.kt`

```kotlin
data class Installed(val result: uniffi.mapmap.ChannelInstallResult) : ChannelInstallOutcome()
```

The package verified and was promoted.

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `val result` | `uniffi.mapmap.ChannelInstallResult` |  |  |

#### `ChannelInstallResult`

*Class* · `uniffi/mapmap/mapmap.kt`

```kotlin
data class ChannelInstallResult (var territory: TerritoryInfo, var layersFetched: UInt, var layersReused: UInt, var bytesDownloaded: ULong)
```

What [`TerritoryManager::install_from_channel`] did.

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `var territory` | `TerritoryInfo` |  |  |
| `var layersFetched` | `UInt` |  |  |
| `var layersReused` | `UInt` |  |  |
| `var bytesDownloaded` | `ULong` |  |  |

#### `FasterRouteCandidate`

*Class* · `uniffi/mapmap/mapmap.kt`

```kotlin
data class FasterRouteCandidate (var id: String?, var durationS: Double)
```

One candidate route for the remaining journey, summarised from the
router's response (a primary route or an alternate).

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `var id` | `String?` |  |  |
| `var durationS` | `Double` |  |  |

#### `FasterRouteConfig`

*Class* · `uniffi/mapmap/mapmap.kt`

```kotlin
data class FasterRouteConfig (var checkIntervalS: Double = 120.0, var minSavingS: UInt = 180u, var minSavingFrac: Double = 0.08, var dismissSuppressS: Double = 600.0, var maneuverBlackoutS: Double = 60.0, var enabled: Boolean = true)
```

Faster-route evaluator configuration, flattened for FFI like
[`crate::reroute::RerouteConfig`].

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `var checkIntervalS` | `Double` | `120.0` |  |
| `var minSavingS` | `UInt` | `180u` |  |
| `var minSavingFrac` | `Double` | `0.08` |  |
| `var dismissSuppressS` | `Double` | `600.0` |  |
| `var maneuverBlackoutS` | `Double` | `60.0` |  |
| `var enabled` | `Boolean` | `true` |  |

#### `FasterRouteRecommendation`

*Class* · `uniffi/mapmap/mapmap.kt`

```kotlin
data class FasterRouteRecommendation (var savesS: UInt, var candidateIdx: UInt)
```

A recommendation to switch to a faster candidate route.

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `var savesS` | `UInt` |  |  |
| `var candidateIdx` | `UInt` |  |  |

#### `FetchException`

*Class* · `uniffi/mapmap/mapmap.kt`

```kotlin
sealed class FetchException: Exception()
```

A transport failure thrown by a foreign [`LayerFetcher`](#layerfetcher)
implementation.

Nested: [`FetchException.Transport`](#fetchexceptiontransport), [`FetchException.ErrorHandler`](#fetchexceptionerrorhandler).

#### `FetchException.Transport`

*Class* · `uniffi/mapmap/mapmap.kt`

```kotlin
class Transport(override val message: String) : FetchException()
```

The download failed (network, storage, cancelled, …).

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `val message` | `String` |  |  |

#### `GuidanceConfig`

*Class* · `uniffi/mapmap/mapmap.kt`

```kotlin
data class GuidanceConfig (var stepAdvanceDistanceM: UShort = 20u, var minimumHorizontalAccuracyM: UShort = 25u, var routeDeviationThresholdM: Double = 25.0, var snapCourseToRoute: Boolean = true, var waypointAdvanceRangeM: Double = 100.0)
```

Guidance behaviour configuration, flattened for FFI.

Maps onto Ferrostar's `NavigationControllerConfig`: a
distance-to-end-of-step advance condition, a static-threshold route
deviation tracker and course snapping.

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `var stepAdvanceDistanceM` | `UShort` | `20u` |  |
| `var minimumHorizontalAccuracyM` | `UShort` | `25u` |  |
| `var routeDeviationThresholdM` | `Double` | `25.0` |  |
| `var snapCourseToRoute` | `Boolean` | `true` |  |
| `var waypointAdvanceRangeM` | `Double` | `100.0` |  |

#### `GuidanceSession`

*Class* · `uniffi/mapmap/mapmap.kt`

```kotlin
open class GuidanceSession: Disposable, AutoCloseable, GuidanceSessionInterface
```

A turn-by-turn guidance session (see [`crate::guidance`]).

Thread-safe: the mutable Ferrostar navigation state is guarded by a
mutex, matching UniFFI's shared-object concurrency model.

**Initialisers and methods**

- `constructor(routeResponseJson: String, config: GuidanceConfig)`: Create a session from a Valhalla route response JSON (as returned by `route_offline` in `valhalla_response_json`).
- `override fun destroy()`
- `override fun close()`
- `@Throws(NavCoreException::class) override fun advanceToNextStep(): GuidanceUpdate`: Manually advance to the next step (e.g. in a tunnel with no GNSS).
- `override fun totalSteps(): UInt`: Number of steps in the route, including the arrival step.
- `@Throws(NavCoreException::class) override fun updateLocation(lat: Double, lon: Double, timestampMs: Long, speedMps: Double?, courseDeg: Double?, horizontalAccuracyM: Double?): GuidanceUpdate`: Feed a location fix; returns the updated guidance state. `horizontal_accuracy_m` defaults to `0.0` (trusted exactly) when absent; pass the platform's reported accuracy when available so step advance and deviation gating behave correctly.

#### `GuidanceUpdate`

*Class* · `uniffi/mapmap/mapmap.kt`

```kotlin
sealed class GuidanceUpdate
```

The guidance state reported after each location update, flattened for
FFI from Ferrostar's `TripState`.

The `Navigating` variant is deliberately larger than the others: it is
the steady-state payload and the FFI shape (UniFFI enums cannot box),
and one allocation per location fix is irrelevant next to the fix
cadence.

Nested: [`GuidanceUpdate.Navigating`](#guidanceupdatenavigating), [`GuidanceUpdate.Arrived`](#guidanceupdatearrived), [`GuidanceUpdate.OffRoute`](#guidanceupdateoffroute).

#### `GuidanceUpdate.Navigating`

*Class* · `uniffi/mapmap/mapmap.kt`

```kotlin
data class Navigating(val stepIndex: UInt, val distanceToNextManeuverM: Double, val distanceRemainingM: Double, val durationRemainingS: Double, val currentInstruction: String, val deviationM: Double?, val visual: uniffi.mapmap.VisualBanner?, val spoken: uniffi.mapmap.SpokenPrompt?, val snapped: uniffi.mapmap.SnappedFix?, val severity: uniffi.mapmap.InstructionSeverity, val speedLimit: uniffi.mapmap.SpeedLimit?) : GuidanceUpdate()
```

Actively navigating.

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `val stepIndex` | `UInt` |  |  |
| `val distanceToNextManeuverM` | `Double` |  |  |
| `val distanceRemainingM` | `Double` |  |  |
| `val durationRemainingS` | `Double` |  |  |
| `val currentInstruction` | `String` |  |  |
| `val deviationM` | `Double?` |  |  |
| `val visual` | `uniffi.mapmap.VisualBanner?` |  |  |
| `val spoken` | `uniffi.mapmap.SpokenPrompt?` |  |  |
| `val snapped` | `uniffi.mapmap.SnappedFix?` |  |  |
| `val severity` | `uniffi.mapmap.InstructionSeverity` |  |  |
| `val speedLimit` | `uniffi.mapmap.SpeedLimit?` |  |  |

#### `GuidanceUpdate.OffRoute`

*Class* · `uniffi/mapmap/mapmap.kt`

```kotlin
data class OffRoute(val deviationM: Double) : GuidanceUpdate()
```

The user has left the route entirely (beyond the configured
deviation threshold from every remaining step). The platform layer
should recalculate.

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `val deviationM` | `Double` |  |  |

#### `InstallOutcome`

*Class* · `uniffi/mapmap/mapmap.kt`

```kotlin
sealed class InstallOutcome
```

How a cancellable install ended.

Nested: [`InstallOutcome.Installed`](#installoutcomeinstalled), [`InstallOutcome.Cancelled`](#installoutcomecancelled).

#### `InstallOutcome.Installed`

*Class* · `uniffi/mapmap/mapmap.kt`

```kotlin
data class Installed(val territory: uniffi.mapmap.TerritoryInfo) : InstallOutcome()
```

The package verified and was promoted.

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `val territory` | `uniffi.mapmap.TerritoryInfo` |  |  |

#### `LaneGuidance`

*Class* · `uniffi/mapmap/mapmap.kt`

```kotlin
data class LaneGuidance (var directions: List<String>, var active: Boolean, var activeDirection: String?)
```

One lane at the upcoming maneuver, for lane-diagram UIs.

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `var directions` | `List<String>` |  |  |
| `var active` | `Boolean` |  |  |
| `var activeDirection` | `String?` |  |  |

#### `LayerFetcherImpl`

*Class* · `uniffi/mapmap/mapmap.kt`

```kotlin
open class LayerFetcherImpl: Disposable, AutoCloseable, LayerFetcher
```

The platform's download transport for over-the-air territory updates,
exported as a UniFFI callback interface like
[`crate::routing::LocalRouter`]: the core decides *what* to fetch and
cryptographically verifies everything; Kotlin/Swift decide *how* bytes
move (OkHttp, URLSession, an air-gapped mirror, …).

**Initialisers and methods**

- `override fun destroy()`
- `override fun close()`
- `@Throws(FetchException::class) override fun fetch(relPath: String, expectedBlake3: String, destPath: String)`: Download the channel-relative `rel_path` (e.g. `territories/uk/2.0.0/layers/<addr>/valhalla.tar.zst`) to the local file `dest_path`. `expected_blake3` is advisory (empty for signed metadata files); the core re-verifies all content itself.

#### `LocalRouterImpl`

*Class* · `uniffi/mapmap/mapmap.kt`

```kotlin
open class LocalRouterImpl: Disposable, AutoCloseable, LocalRouter
```

The injection point for the platform's on-device Valhalla binding.

valhalla-mobile (<https://github.com/Rallista/valhalla-mobile>) exposes
a `route(request_json) -> response_json` actor on both Android and iOS;
Kotlin/Swift implement this interface with that actor and pass it to
[`crate::ffi::route_offline`]. Exported as a UniFFI callback interface
(`with_foreign`) so the implementation lives in foreign code.

**Initialisers and methods**

- `override fun destroy()`
- `override fun close()`
- `override fun route(requestJson: String): String`: Execute a Valhalla turn-by-turn request and return the raw response JSON (either a route response or a Valhalla error body).

#### `MapLayerKind`

*Class* · `uniffi/mapmap/mapmap.kt`

```kotlin
sealed class MapLayerKind
```

Map layer kind, mirroring [`sn_package::LayerKind`].

Nested: [`MapLayerKind.ValhallaTiles`](#maplayerkindvalhallatiles), [`MapLayerKind.Pmtiles`](#maplayerkindpmtiles), [`MapLayerKind.GeocodeIndex`](#maplayerkindgeocodeindex), [`MapLayerKind.PoiSidecar`](#maplayerkindpoisidecar), [`MapLayerKind.Extra`](#maplayerkindextra).

#### `MapLayerKind.Extra`

*Class* · `uniffi/mapmap/mapmap.kt`

```kotlin
data class Extra(val name: String) : MapLayerKind()
```

Any other layer kind, by its manifest string.

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `val name` | `String` |  |  |

#### `NavCoreException`

*Class* · `uniffi/mapmap/mapmap.kt`

```kotlin
sealed class NavCoreException: Exception()
```

The flat error type crossing the FFI boundary.

Rich typed errors live on the Rust side (`TerritoryError`,
`RoutingError`, `GuidanceError`); foreign code gets a stable
category plus a human-readable message.

Nested: [`NavCoreException.Territory`](#navcoreexceptionterritory), [`NavCoreException.Verification`](#navcoreexceptionverification), [`NavCoreException.Routing`](#navcoreexceptionrouting), [`NavCoreException.Guidance`](#navcoreexceptionguidance), [`NavCoreException.InvalidArgument`](#navcoreexceptioninvalidargument), [`NavCoreException.Search`](#navcoreexceptionsearch), [`NavCoreException.LayerNotInstalled`](#navcoreexceptionlayernotinstalled), [`NavCoreException.ErrorHandler`](#navcoreexceptionerrorhandler).

#### `NavCoreException.Guidance`

*Class* · `uniffi/mapmap/mapmap.kt`

```kotlin
class Guidance(override val message: String) : NavCoreException()
```

Guidance session failure.

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `val message` | `String` |  |  |

#### `NavCoreException.InvalidArgument`

*Class* · `uniffi/mapmap/mapmap.kt`

```kotlin
class InvalidArgument(override val message: String) : NavCoreException()
```

A caller-supplied argument was invalid.

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `val message` | `String` |  |  |

#### `NavCoreException.LayerNotInstalled`

*Class* · `uniffi/mapmap/mapmap.kt`

```kotlin
class LayerNotInstalled(val territoryId: String, val kind: String, val detail: String) : NavCoreException()
```

The territory has a layer of that kind, but **this install left it
out** (see [`TerritoryManager::install_from_channel`]).

Its own case on purpose: it is not `Territory` (nothing failed),
not a corrupt install and not an unknown territory. The app knows
what it chose not to download, so this is the signal to offer the
download, not to report an error.

The human-readable sentence is `detail`, not `message`: a UniFFI
error variant with several fields, one of them named `message`,
generates a Kotlin class whose constructor property collides with
`Throwable.message` and does not compile. Single-field variants
(every other case here) are fine.

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `val territoryId` | `String` |  |  |
| `val kind` | `String` |  |  |
| `val detail` | `String` |  |  |
| `val message` |  |  | Human-readable description. |

#### `NavCoreException.Routing`

*Class* · `uniffi/mapmap/mapmap.kt`

```kotlin
class Routing(override val message: String) : NavCoreException()
```

Route request building or response parsing failure (including
Valhalla error bodies from the on-device engine).

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `val message` | `String` |  |  |

#### `NavCoreException.Search`

*Class* · `uniffi/mapmap/mapmap.kt`

```kotlin
class Search(override val message: String) : NavCoreException()
```

On-device geocode search failure (index missing, unreadable or of
an unsupported schema version).

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `val message` | `String` |  |  |

#### `NavCoreException.Territory`

*Class* · `uniffi/mapmap/mapmap.kt`

```kotlin
class Territory(override val message: String) : NavCoreException()
```

Territory storage or lifecycle failure.

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `val message` | `String` |  |  |

#### `NavCoreException.Verification`

*Class* · `uniffi/mapmap/mapmap.kt`

```kotlin
class Verification(override val message: String) : NavCoreException()
```

A package failed cryptographic or integrity verification.

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `val message` | `String` |  |  |

#### `OdRecord`

*Class* · `uniffi/mapmap/mapmap.kt`

```kotlin
data class OdRecord (var originCell: String, var destCell: String, var departBucket: String)
```

Coarse origin/destination for a trip. Both cells are geohashes of
*suppressed* (mid-trip) positions, so they are already
[`ProbeConfig::trip_end_suppression_m`] away from the true ends.

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `var originCell` | `String` |  |  |
| `var destCell` | `String` |  |  |
| `var departBucket` | `String` |  |  |

#### `Place`

*Class* · `uniffi/mapmap/mapmap.kt`

```kotlin
data class Place (var id: String, var kind: PlaceKind, var name: String, var housenumber: String?, var street: String?, var locality: String?, var region: String?, var postcode: String?, var countryCode: String, var lat: Double, var lon: Double, var categories: List<String>, var importance: Float, var distanceM: Double?)
```

One on-device search result: the matched document's display and
context fields plus its position.

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `var id` | `String` |  |  |
| `var kind` | `PlaceKind` |  |  |
| `var name` | `String` |  |  |
| `var housenumber` | `String?` |  |  |
| `var street` | `String?` |  |  |
| `var locality` | `String?` |  |  |
| `var region` | `String?` |  |  |
| `var postcode` | `String?` |  |  |
| `var countryCode` | `String` |  |  |
| `var lat` | `Double` |  |  |
| `var lon` | `Double` |  |  |
| `var categories` | `List<String>` |  |  |
| `var importance` | `Float` |  |  |
| `var distanceM` | `Double?` |  |  |

#### `ProbeBatch`

*Class* · `uniffi/mapmap/mapmap.kt`

```kotlin
data class ProbeBatch (var probeFormat: UInt, var territory: String?, var vehicleClass: String?, var routeRef: String?, var segments: List<SegmentAggregate>, var events: List<ProbeEvent>, var od: OdRecord?)
```

A completed trip's aggregates, ready for the platform layer to POST to the
probe ingest endpoint. Contains no trajectory.

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `var probeFormat` | `UInt` |  |  |
| `var territory` | `String?` |  |  |
| `var vehicleClass` | `String?` |  |  |
| `var routeRef` | `String?` |  |  |
| `var segments` | `List<SegmentAggregate>` |  |  |
| `var events` | `List<ProbeEvent>` |  |  |
| `var od` | `OdRecord?` |  |  |

#### `ProbeCollector`

*Class* · `uniffi/mapmap/mapmap.kt`

```kotlin
open class ProbeCollector: Disposable, AutoCloseable, ProbeCollectorInterface
```

On-device probe collector. Feed it every guidance update with the fix's
position, speed and timestamp; call [`finish`](ProbeCollector::finish) at
arrival or trip abandonment to get the [`ProbeBatch`](#probebatch) to upload.

Interior mutability (a `Mutex`) lets the FFI methods take `&self`, matching
the [`crate::guidance::GuidanceSession`] object shape.

**Initialisers and methods**

- `constructor(config: ProbeConfig)`: Create a collector with the given configuration.
- `override fun destroy()`
- `override fun close()`
- `override fun finish(arrived: Boolean, timestampMs: Long): ProbeBatch`: Close the trip and return the batch to upload. `arrived` distinguishes a completed trip from an abandoned one (both apply end suppression).
- `override fun finishJson(arrived: Boolean, timestampMs: Long): String?`: Close the trip and return the exact JSON body to `POST /v1/probe`, or `None` when the batch carries nothing worth uploading. This is the single canonical serialiser for the probe wire format, platform code should upload this verbatim rather than re-encoding the record, so the wire form can never drift from what the gateway ingests.
- `override fun observe(update: GuidanceUpdate, fixLat: Double, fixLon: Double, speedMps: Double?, timestampMs: Long)`: Feed one guidance update and its fix. `speed_mps` is the device's reported ground speed when available (preferred); pass `None` to let the collector estimate it from successive positions.
- `override fun setRouteRef(routeRef: String)`: Set the opaque served-route token so the server can resolve segments.
- `override fun setTerritory(territory: String)`: Set the territory id stamped into the batch.
- `override fun setVehicleClass(vehicleClass: String)`: Set the vehicle costing class stamped into the batch (a class only, never a vehicle or driver identifier).

#### `ProbeConfig`

*Class* · `uniffi/mapmap/mapmap.kt`

```kotlin
data class ProbeConfig (var enabled: Boolean = false, var tripEndSuppressionM: Double = 500.0, var minSegmentObservations: UInt = 1u, var timeBucketMinutes: UInt = 5u, var odCellPrecision: UByte = 5u, var collectOd: Boolean = true)
```

Probe collector configuration, flattened for FFI like
[`crate::guidance::GuidanceConfig`].

Collection is **off by default**: `enabled` must be set explicitly (and, in
deployment, gated on the fleet customer's opt-in). Everything a batch
contains follows from these knobs.

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `var enabled` | `Boolean` | `false` |  |
| `var tripEndSuppressionM` | `Double` | `500.0` |  |
| `var minSegmentObservations` | `UInt` | `1u` |  |
| `var timeBucketMinutes` | `UInt` | `5u` |  |
| `var odCellPrecision` | `UByte` | `5u` |  |
| `var collectOd` | `Boolean` | `true` |  |

#### `ProbeEvent`

*Class* · `uniffi/mapmap/mapmap.kt`

```kotlin
sealed class ProbeEvent
```

A coarse, time-bucketed event within a trip.

Nested: [`ProbeEvent.OffRoute`](#probeeventoffroute), [`ProbeEvent.EtaOutcome`](#probeeventetaoutcome).

#### `ProbeEvent.EtaOutcome`

*Class* · `uniffi/mapmap/mapmap.kt`

```kotlin
data class EtaOutcome(val predictedS: Double, val actualS: Double, val routeLenKmBucket: String) : ProbeEvent()
```

The realised trip duration against the engine's first estimate. Feeds
ETA-accuracy calibration; carries no location.

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `val predictedS` | `Double` |  |  |
| `val actualS` | `Double` |  |  |
| `val routeLenKmBucket` | `String` |  |  |

#### `ProbeEvent.OffRoute`

*Class* · `uniffi/mapmap/mapmap.kt`

```kotlin
data class OffRoute(val todBucket: String, val deviationBandM: UInt) : ProbeEvent()
```

The vehicle left the route (a confirmed off-route condition from
guidance). Carries only the time bucket and a coarse deviation band.

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `val todBucket` | `String` |  |  |
| `val deviationBandM` | `UInt` |  |  |

#### `RerouteAnchor`

*Class* · `uniffi/mapmap/mapmap.kt`

```kotlin
data class RerouteAnchor (var lat: Double, var lon: Double, var courseDeg: Double?)
```

The point and course a recalculated route should depart from.

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `var lat` | `Double` |  |  |
| `var lon` | `Double` |  |  |
| `var courseDeg` | `Double?` |  |  |

#### `RerouteConfig`

*Class* · `uniffi/mapmap/mapmap.kt`

```kotlin
data class RerouteConfig (var minConsecutiveOffrouteFixes: UInt = 3u, var minOffrouteDurationS: Double = 5.0, var minIntervalBetweenReroutesS: Double = 10.0, var enabled: Boolean = true)
```

Reroute controller configuration, flattened for FFI like
[`crate::guidance::GuidanceConfig`].

Both the count gate and the duration gate must pass for a reroute to be
confirmed: a burst of fixes arriving in a fraction of a second should not
out-vote the duration requirement, and a slow trickle of fixes should not
out-vote the count requirement.

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `var minConsecutiveOffrouteFixes` | `UInt` | `3u` |  |
| `var minOffrouteDurationS` | `Double` | `5.0` |  |
| `var minIntervalBetweenReroutesS` | `Double` | `10.0` |  |
| `var enabled` | `Boolean` | `true` |  |

#### `RerouteDecision`

*Class* · `uniffi/mapmap/mapmap.kt`

```kotlin
data class RerouteDecision (var reason: RerouteReason, var anchor: RerouteAnchor, var waypointsRemaining: List<RerouteWaypoint>)
```

A confirmed reroute: where to route from, where to, and why.

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `var reason` | `RerouteReason` |  |  |
| `var anchor` | `RerouteAnchor` |  |  |
| `var waypointsRemaining` | `List<RerouteWaypoint>` |  |  |

#### `RerouteReason`

*Class* · `uniffi/mapmap/mapmap.kt`

```kotlin
sealed class RerouteReason
```

Why a reroute was requested.

Nested: [`RerouteReason.OffRoute`](#reroutereasonoffroute), [`RerouteReason.FasterRoute`](#reroutereasonfasterroute).

#### `RerouteReason.FasterRoute`

*Class* · `uniffi/mapmap/mapmap.kt`

```kotlin
data class FasterRoute(val savesS: UInt) : RerouteReason()
```

A periodic re-evaluation found a faster route for the remaining
journey (see [`crate::faster_route::FasterRouteEvaluator`]); the
driver accepted the switch. `saves_s` is the estimated saving in
seconds versus staying on the current route.

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `val savesS` | `UInt` |  |  |

#### `RerouteReason.OffRoute`

*Class* · `uniffi/mapmap/mapmap.kt`

```kotlin
data class OffRoute(val deviationM: Double) : RerouteReason()
```

The vehicle left the route line; `deviation_m` is the distance from
the route at the confirming fix.

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `val deviationM` | `Double` |  |  |

#### `RerouteState`

*Class* · `uniffi/mapmap/mapmap.kt`

```kotlin
sealed class RerouteState
```

The controller's state after the most recent update, for UI and telemetry.

Nested: [`RerouteState.Monitoring`](#reroutestatemonitoring), [`RerouteState.OffRouteSuspected`](#reroutestateoffroutesuspected), [`RerouteState.RerouteNeeded`](#reroutestatererouteneeded), [`RerouteState.CoolingDown`](#reroutestatecoolingdown).

#### `RerouteState.OffRouteSuspected`

*Class* · `uniffi/mapmap/mapmap.kt`

```kotlin
data class OffRouteSuspected(val count: UInt) : RerouteState()
```

Off route, but not yet confirmed, the count and/or duration gate has
not been met. `count` is the run of consecutive off-route fixes so far.

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `val count` | `UInt` |  |  |

#### `RerouteWaypoint`

*Class* · `uniffi/mapmap/mapmap.kt`

```kotlin
data class RerouteWaypoint (var lat: Double, var lon: Double, var isBreak: Boolean)
```

One onward waypoint of the active route.

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `var lat` | `Double` |  |  |
| `var lon` | `Double` |  |  |
| `var isBreak` | `Boolean` |  |  |

#### `RouteManeuver`

*Class* · `uniffi/mapmap/mapmap.kt`

```kotlin
data class RouteManeuver (var instruction: String, var maneuverType: UInt, var verbalPreTransitionInstruction: String?, var verbalPostTransitionInstruction: String?, var streetNames: List<String>, var distanceM: Double, var durationS: Double, var beginShapeIndex: UInt, var endShapeIndex: UInt, var roundaboutExitCount: UInt?)
```

One maneuver of the primary route, in SI units with shape indices into
[`RouteResult::geometry`]. Parsed once by the core so integrators never
re-parse the raw Valhalla JSON themselves.

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `var instruction` | `String` |  |  |
| `var maneuverType` | `UInt` |  |  |
| `var verbalPreTransitionInstruction` | `String?` |  |  |
| `var verbalPostTransitionInstruction` | `String?` |  |  |
| `var streetNames` | `List<String>` |  |  |
| `var distanceM` | `Double` |  |  |
| `var durationS` | `Double` |  |  |
| `var beginShapeIndex` | `UInt` |  |  |
| `var endShapeIndex` | `UInt` |  |  |
| `var roundaboutExitCount` | `UInt?` |  |  |

#### `RoutePoint`

*Class* · `uniffi/mapmap/mapmap.kt`

```kotlin
data class RoutePoint (var lat: Double, var lon: Double)
```

A route waypoint.

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `var lat` | `Double` |  |  |
| `var lon` | `Double` |  |  |

#### `RouteResult`

*Class* · `uniffi/mapmap/mapmap.kt`

```kotlin
data class RouteResult (var summary: RouteSummary, var geometry: List<RoutePoint> = listOf(), var maneuvers: List<RouteManeuver> = listOf(), var valhallaResponseJson: String, var osrmResponseJson: String)
```

Result of an offline route computation.

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `var summary` | `RouteSummary` |  |  |
| `var geometry` | `List<RoutePoint>` | `listOf()` |  |
| `var maneuvers` | `List<RouteManeuver>` | `listOf()` |  |
| `var valhallaResponseJson` | `String` |  |  |
| `var osrmResponseJson` | `String` |  |  |

#### `RouteSummary`

*Class* · `uniffi/mapmap/mapmap.kt`

```kotlin
data class RouteSummary (var distanceM: Double, var durationS: Double, var hasToll: Boolean, var hasHighway: Boolean, var hasFerry: Boolean)
```

SI summary of a parsed route.

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `var distanceM` | `Double` |  |  |
| `var durationS` | `Double` |  |  |
| `var hasToll` | `Boolean` |  |  |
| `var hasHighway` | `Boolean` |  |  |
| `var hasFerry` | `Boolean` |  |  |

#### `SegmentAggregate`

*Class* · `uniffi/mapmap/mapmap.kt`

```kotlin
data class SegmentAggregate (var stepIndex: UInt, var todBucket: String, var observations: UInt, var meanSpeedKmh: Double, var p85SpeedKmh: Double, var stoppedS: Double, var limitDeltaKmh: Double?)
```

One aggregated road segment: statistics for a `step_index` within the
served route, in one day-of-week × time-of-day bucket.

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `var stepIndex` | `UInt` |  |  |
| `var todBucket` | `String` |  |  |
| `var observations` | `UInt` |  |  |
| `var meanSpeedKmh` | `Double` |  |  |
| `var p85SpeedKmh` | `Double` |  |  |
| `var stoppedS` | `Double` |  |  |
| `var limitDeltaKmh` | `Double?` |  |  |

#### `SnappedFix`

*Class* · `uniffi/mapmap/mapmap.kt`

```kotlin
data class SnappedFix (var lat: Double, var lon: Double, var courseDeg: Double?)
```

The route-snapped position for the current update.

This is Ferrostar's `snapped_user_location`: the raw fix projected
onto the route line, with the course snapped to the route direction
when [`GuidanceConfig::snap_course_to_route`] is on (the default).
Drive follow-cameras (course-up / chase views) from this, never the
raw fix, the raw GPS trace jitters and drifts off the carriageway.

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `var lat` | `Double` |  |  |
| `var lon` | `Double` |  |  |
| `var courseDeg` | `Double?` |  |  |

#### `SpeedLimit`

*Class* · `uniffi/mapmap/mapmap.kt`

```kotlin
sealed class SpeedLimit
```

The posted speed limit for the current edge, for a speed-limit badge and
over-limit warnings.

This is surfaced from the OSRM `annotation.maxspeed` that Ferrostar threads
through its navigation state (`TripState::Navigating.annotation_json`) at
the current geometry index, in the Mapbox shape
(<https://docs.mapbox.com/api/navigation/directions/#route-leg-object>). It
appears only when the route actually carries speed-limit data: on-device
that means the Valhalla graph was built with OSM `maxspeed` (and admin data
for implicit limits) and the engine emitted per-maneuver limits, which the
translator forwarded (see [`sn_valhalla::valhalla::SpeedLimit`]). With
stock data there is no annotation and [`GuidanceUpdate::Navigating`] reports
no limit at all, values are never invented.

Nested: [`SpeedLimit.Known`](#speedlimitknown), [`SpeedLimit.Unlimited`](#speedlimitunlimited), [`SpeedLimit.Unknown`](#speedlimitunknown).

#### `SpeedLimit.Known`

*Class* · `uniffi/mapmap/mapmap.kt`

```kotlin
data class Known(val kmh: Double) : SpeedLimit()
```

A known posted limit, normalised to kilometres per hour regardless of
the unit the route expressed it in.

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `val kmh` | `Double` |  |  |

#### `SpokenPrompt`

*Class* · `uniffi/mapmap/mapmap.kt`

```kotlin
data class SpokenPrompt (var text: String, var ssml: String?, var triggerDistanceM: Double, var utteranceId: String)
```

The spoken prompt to synthesise for the upcoming maneuver.

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `var text` | `String` |  |  |
| `var ssml` | `String?` |  |  |
| `var triggerDistanceM` | `Double` |  |  |
| `var utteranceId` | `String` |  |  |

#### `TerritoryInfo`

*Class* · `uniffi/mapmap/mapmap.kt`

```kotlin
data class TerritoryInfo (var territoryId: String, var displayName: String, var version: String, var dataTimestamp: String, var west: Double, var south: Double, var east: Double, var north: Double, var totalBytes: ULong, var installedBytes: ULong, var omittedKinds: List<MapLayerKind>, var path: String, var active: Boolean)
```

Summary of one installed territory.

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `var territoryId` | `String` |  |  |
| `var displayName` | `String` |  |  |
| `var version` | `String` |  |  |
| `var dataTimestamp` | `String` |  |  |
| `var west` | `Double` |  |  |
| `var south` | `Double` |  |  |
| `var east` | `Double` |  |  |
| `var north` | `Double` |  |  |
| `var totalBytes` | `ULong` |  |  |
| `var installedBytes` | `ULong` |  |  |
| `var omittedKinds` | `List<MapLayerKind>` |  |  |
| `var path` | `String` |  |  |
| `var active` | `Boolean` |  |  |

#### `TerritoryManager`

*Class* · `uniffi/mapmap/mapmap.kt`

```kotlin
open class TerritoryManager: Disposable, AutoCloseable, TerritoryManagerInterface
```

Manages signed offline territory packages (see [`crate::territory`]).

**Initialisers and methods**

- `constructor(rootDir: String, verifyingKeyHex: String)`: Open a manager rooted at `root_dir`, trusting packages signed by the ed25519 public key given as 64 hex characters (the factory signing key's public half, [`sn_package::VerifyingKey::to_hex`]).
- `override fun destroy()`
- `override fun close()`
- `@Throws(NavCoreException::class) override fun active(): String?`: The active territory id, if one is set and still installed.
- `@Throws(NavCoreException::class) override fun applyUpdate(territoryId: String, newManifestJson: String, newManifestSigBase64: String, fetcher: LayerFetcher): UpdateResult`: Apply a differential update: stage, fetch only the changed layers through `fetcher`, verify everything and swap atomically. Any failure leaves the installed territory untouched; the active flag is preserved. Blocks until done and cannot be interrupted; see [`Self::apply_update_cancellable`].
- `@Throws(NavCoreException::class) override fun applyUpdateCancellable(territoryId: String, newManifestJson: String, newManifestSigBase64: String, fetcher: LayerFetcher, cancel: CancelToken): UpdateOutcome`: [`Self::apply_update`] with a [`CancelToken`](#canceltoken). Polled before every layer download, between archive entries while unpacking, before each hash pass and before promotion. A tripped token yields [`UpdateOutcome::Cancelled`]: the installed version is untouched, staging is removed, and layers that had already been fetched and verified stay in the resumable cache, so calling again with the same manifest re-fetches only what is missing. Give `fetcher` the same token so it can stop an in-flight transfer (the token cannot interrupt foreign code on its own); whatever the fetcher then returns, a tripped token reports `Cancelled` rather than a transport error.
- `@Throws(NavCoreException::class) override fun cachedLayerBytes(): ULong`: Bytes parked in the resumable layer cache: downloads a failed install already paid for, which a retry will not pay for again. Surface this next to a "resume" affordance and in storage settings.
- `@Throws(NavCoreException::class) override fun checkForUpdate(territoryId: String, indexJson: String, indexSigBase64: String, fetcher: LayerFetcher): AvailableUpdate?`: Check the signed channel index for an update to an installed territory. `index_json` must be the exact bytes served as `index.json` and `index_sig_base64` the content of `index.json.sig`; the remote manifest is fetched and verified through `fetcher`. Returns `None` when the device is already on the channel's latest version (or the channel does not list the territory).
- `@Throws(NavCoreException::class) override fun clearLayerCache()`: Discard every parked layer. The bytes are lost and a later install re-downloads (and on a metered channel re-pays for) them, so offer this only as a deliberate "free up space" action.
- `@Throws(NavCoreException::class) override fun installDir(packageDir: String): TerritoryInfo`: Install an unpacked package directory. Blocks until done and cannot be interrupted; see [`Self::install_dir_cancellable`].
- `@Throws(NavCoreException::class) override fun installDirCancellable(packageDir: String, cancel: CancelToken): InstallOutcome`: [`Self::install_dir`] with a [`CancelToken`](#canceltoken). Polled between copied files, before verification and before promotion.
- `@Throws(NavCoreException::class) override fun installDirPartial(packageDir: String, omit: List<MapLayerKind>, cancel: CancelToken): InstallOutcome`: Install an unpacked package directory, holding only the layers this app can use: `omit` names the kinds to leave out (e.g. `Pmtiles` and `Extra("style")` for an app with no map view). The install records what it left out, and every later verification requires those layers to be **absent** rather than exempting them from hashing, so the record cannot be forged into admitting tampered bytes. Asking for an omitted layer answers [`NavCoreError::LayerNotInstalled`], which is not an error state: it is "you chose not to download this". The selection is sticky: updates carry it forward, so an app that skipped the render layers never downloads them on an update either.
- `@Throws(NavCoreException::class) override fun installFromChannel(territoryId: String, indexJson: String, indexSigBase64: String, omit: List<MapLayerKind>, fetcher: LayerFetcher, cancel: CancelToken): ChannelInstallOutcome`: Install a territory **from the channel**, downloading only the layers this app can use. The first-install counterpart of [`Self::apply_update`], and the only path on which omitting layers saves bandwidth: a layer in `omit` is never requested, so it is never metered and never stored. `index_json` / `index_sig_base64` are the exact served bytes of `index.json` and `index.json.sig`; the channel's latest version of the territory is installed. Nothing is trusted but the pinned key: the index signature, the manifest signature and every blob's BLAKE3 are checked, and the assembled directory goes through the same full verification pass as any other install before it is promoted. Interrupted installs resume: verified layers stay parked in the layer cache. Calling it again for a territory that is already installed is how a selection is widened later. Layers already on the device at the same content hash are linked, not re-downloaded, so adding the map to a router-only install costs the map alone.
- `@Throws(NavCoreException::class) override fun installSnpkg(archivePath: String): TerritoryInfo`: Install a packed `.snpkg` archive (verify-then-promote; rejected packages leave no trace). Blocks until done and cannot be interrupted; see [`Self::install_snpkg_cancellable`].
- `@Throws(NavCoreException::class) override fun installSnpkgCancellable(archivePath: String, cancel: CancelToken): InstallOutcome`: [`Self::install_snpkg`] with a [`CancelToken`](#canceltoken). Polled between archive entries, before verification and before promotion; a tripped token yields [`InstallOutcome::Cancelled`] with staging removed and nothing installed.
- `@Throws(NavCoreException::class) override fun installSnpkgPartial(archivePath: String, omit: List<MapLayerKind>, cancel: CancelToken): InstallOutcome`: [`Self::install_dir_partial`] from a packed `.snpkg` archive. The archive carries every layer, so this saves storage, not bandwidth: the omitted layers are unpacked and then dropped before verification. For the bandwidth saving use [`Self::install_from_channel`], which never fetches a blob it does not want.
- `@Throws(NavCoreException::class) override fun layerPath(territoryId: String, kind: MapLayerKind): String`: Absolute path of a layer inside an installed territory (e.g. the Valhalla tile tree for valhalla-mobile, the PMTiles file for MapLibre).
- `@Throws(NavCoreException::class) override fun list(): List<TerritoryInfo>`: List installed territories in territory-id order.
- `@Throws(NavCoreException::class) override fun openSearch(territoryId: String): TerritorySearch`: Open the installed territory's bundled geocode index for on-device search. The returned [`TerritorySearch`](#territorysearch) holds the index open (mmap-backed), create it once per territory and reuse it across queries.
- `@Throws(NavCoreException::class) override fun planUpdate(newManifestJson: String): UpdatePlan`: Plan a differential update from the installed manifest to `new_manifest_json` (a signed manifest's JSON body, as served by the update channel).
- `@Throws(NavCoreException::class) override fun remove(territoryId: String)`: Remove an installed territory (clears the active marker if needed).
- `@Throws(NavCoreException::class) override fun setActive(territoryId: String)`: Mark a territory as active (persisted atomically).
- `@Throws(NavCoreException::class) override fun territoryStyle(territoryId: String, theme: StyleTheme): String`: A complete MapLibre style JSON for an installed territory with **local** (offline) references, mirroring the web SDK's `buildStyle()`. Load it straight into MapLibre, the territory tile source points at the installed PMTiles layer via a `pmtiles://<absolute path>` URL, so the map renders with radios off. Packages without a baked style layer get the default MapMap theme compiled on the fly over the package's PMTiles layer. The `glyphs` endpoint stays hosted (MapLibre caches fetched glyph ranges; no offline glyph bundle ships in packages today).
- `@Throws(NavCoreException::class) override fun verifyTerritory(territoryId: String): TerritoryInfo`: Re-verify an installed territory from disk: the manifest signature, the BLAKE3 of every layer this install holds, and the absence of every layer it recorded as omitted. The ordinary calls do not re-hash gigabytes; this is the on-demand integrity check for after a restore or a crash. A failure arrives as [`NavCoreError::Verification`] and means the install must be repaired or reinstalled, not retried.

Nested: [`TerritoryManager.Companion`](#territorymanagercompanion).

#### `TerritorySearch`

*Class* · `uniffi/mapmap/mapmap.kt`

```kotlin
open class TerritorySearch: Disposable, AutoCloseable, TerritorySearchInterface
```

On-device search over an installed territory's bundled geocode index
(see [`TerritoryManager::open_search`]). Fully offline; queries are
synchronous and mmap-backed, call from a background thread/dispatcher
like any other disk-bound SDK call.

**Initialisers and methods**

- `override fun destroy()`
- `override fun close()`
- `@Throws(NavCoreException::class) override fun reverse(lat: Double, lon: Double, limit: UInt): List<Place>`: Reverse geocode: the nearest indexed places to a coordinate, nearest first.
- `@Throws(NavCoreException::class) override fun search(query: String, near: RoutePoint?, limit: UInt): List<Place>`: Free-text search (addresses, streets, places, POIs, UK postcodes; prefix-tokenised, so it works for search-as-you-type). `near` biases ranking towards the given point and fills each result's `distance_m`; `limit` caps the number of hits.

#### `UpdateOutcome`

*Class* · `uniffi/mapmap/mapmap.kt`

```kotlin
sealed class UpdateOutcome
```

How a cancellable update ended.

Nested: [`UpdateOutcome.Applied`](#updateoutcomeapplied), [`UpdateOutcome.Cancelled`](#updateoutcomecancelled).

#### `UpdateOutcome.Applied`

*Class* · `uniffi/mapmap/mapmap.kt`

```kotlin
data class Applied(val result: uniffi.mapmap.UpdateResult) : UpdateOutcome()
```

The update verified and was swapped in.

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `val result` | `uniffi.mapmap.UpdateResult` |  |  |

#### `UpdatePlan`

*Class* · `uniffi/mapmap/mapmap.kt`

```kotlin
data class UpdatePlan (var changed: List<ChangedLayer>, var installedBytes: ULong)
```

A differential update plan.

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `var changed` | `List<ChangedLayer>` |  |  |
| `var installedBytes` | `ULong` |  |  |

#### `UpdateResult`

*Class* · `uniffi/mapmap/mapmap.kt`

```kotlin
data class UpdateResult (var territory: TerritoryInfo, var previousVersion: String, var layersFetched: UInt, var layersReused: UInt, var bytesDownloaded: ULong)
```

What [`TerritoryManager::apply_update`] did.

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `var territory` | `TerritoryInfo` |  |  |
| `var previousVersion` | `String` |  |  |
| `var layersFetched` | `UInt` |  |  |
| `var layersReused` | `UInt` |  |  |
| `var bytesDownloaded` | `ULong` |  |  |

#### `VisualBanner`

*Class* · `uniffi/mapmap/mapmap.kt`

```kotlin
data class VisualBanner (var primary: BannerLine, var secondary: BannerLine?, var sub: BannerLine?, var triggerDistanceM: Double, var drivingSide: String? = null, var roadRef: String? = null, var country: String? = null, var subdivision: String? = null, var roadClass: String? = null)
```

The visual banner to display for the upcoming maneuver.

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `var primary` | `BannerLine` |  |  |
| `var secondary` | `BannerLine?` |  |  |
| `var sub` | `BannerLine?` |  |  |
| `var triggerDistanceM` | `Double` |  |  |
| `var drivingSide` | `String?` | `null` |  |
| `var roadRef` | `String?` | `null` |  |
| `var country` | `String?` | `null` |  |
| `var subdivision` | `String?` | `null` |  |
| `var roadClass` | `String?` | `null` |  |

### Enums (uniffi.mapmap)

[`AdrTunnelCategory`](#adrtunnelcategory), [`AdrTunnelCode`](#adrtunnelcode), [`CostingModel`](#costingmodel), [`InstructionSeverity`](#instructionseverity), [`PlaceKind`](#placekind), [`StyleTheme`](#styletheme)

#### `AdrTunnelCategory`

*Enum* · `uniffi/mapmap/mapmap.kt`

```kotlin
enum class AdrTunnelCategory
```

ADR tunnel category A–E (ascending restrictiveness), mirroring
[`sn_adr::TunnelCategory`].

| Case | Description |
| --- | --- |
| `A` | No dangerous-goods restriction. |
| `B` | Restriction for goods which may lead to a very large explosion. |
| `C` | … or a large explosion or large toxic release. |
| `D` | … or a large fire. |
| `E` | Restriction for nearly all dangerous goods. |

#### `AdrTunnelCode`

*Enum* · `uniffi/mapmap/mapmap.kt`

```kotlin
enum class AdrTunnelCode
```

ADR 8.6.4 tunnel restriction code of a load, mirroring
[`sn_adr::TunnelRestrictionCode`]. `NoCode` is ADR's `(, )`: explicitly
unrestricted.

| Case | Description |
| --- | --- |
| `NO_CODE` | ADR `(, )`, passage permitted through all tunnels. |
| `B` | Code B. |
| `B1000C` | Code B1000C. |
| `BD` | Code B/D. |
| `BE` | Code B/E. |
| `C` | Code C. |
| `C5000D` | Code C5000D. |
| `CD` | Code C/D. |
| `CE` | Code C/E. |
| `D` | Code D. |
| `DE` | Code D/E. |
| `E` | Code E. |

#### `CostingModel`

*Enum* · `uniffi/mapmap/mapmap.kt`

```kotlin
enum class CostingModel
```

Valhalla costing model, mirroring [`sn_valhalla::valhalla::Costing`].

| Case | Description |
| --- | --- |
| `AUTO` | Standard car costing. |
| `TRUCK` | Truck costing (required when an ADR profile is supplied). |
| `BUS` | Bus costing. |
| `MOTOR_SCOOTER` | Motor scooter costing. |
| `MOTORCYCLE` | Motorcycle costing. |
| `BICYCLE` | Bicycle costing. |
| `PEDESTRIAN` | Pedestrian costing. |

#### `InstructionSeverity`

*Enum* · `uniffi/mapmap/mapmap.kt`

```kotlin
enum class InstructionSeverity
```

How urgently a guidance update should be surfaced to the driver.

Voice layers map this to prosody (pitch/rate) and earcons; visual
layers to banner styling. Ordered: `Info < Warning < Critical`.

Derivation (single source of truth for every SDK):

- **Critical**, restriction prohibitions: an ADR tunnel prohibition
([`InstructionSeverity::for_adr_decision`] on a `Blocked` decision,
the hook platform layers use when `check_tunnel` fires off-route)
and manoeuvres crossing a **gate** (a physical barrier the vehicle
must not run).
- **Warning**, approaching a restriction or interruption of the
drive: toll booths and ferry boardings/landings.
- **Info**, every normal manoeuvre.

Off-route updates carry no instruction; platform voice layers treat
[`GuidanceUpdate::OffRoute`] itself as `Warning`.

| Case | Description |
| --- | --- |
| `INFO` | A normal manoeuvre announcement. |
| `WARNING` | Approaching a restriction (toll booth, ferry boarding/landing). |
| `CRITICAL` | A prohibition: ADR tunnel block or a gate across the carriageway. |

#### `PlaceKind`

*Enum* · `uniffi/mapmap/mapmap.kt`

```kotlin
enum class PlaceKind
```

The kind of entity a [`Place`](#place) describes, mirroring
[`sn_geocode::DocKind`].

| Case | Description |
| --- | --- |
| `ADDRESS` | A single addressable building or unit (housenumber + street). |
| `STREET` | A named street or road. |
| `LOCALITY` | A town, city, village or suburb. |
| `POI` | A point of interest (fuel station, truck stop, museum, …). |
| `POSTCODE` | A postcode centroid (full or outward code). |

#### `StyleTheme`

*Enum* · `uniffi/mapmap/mapmap.kt`

```kotlin
enum class StyleTheme
```

Which packaged style variant [`TerritoryManager::territory_style`]
returns, mirroring [`crate::territory::StyleTheme`] (and the web SDK's
`buildStyle({ theme })` names).

| Case | Description |
| --- | --- |
| `LIGHT` | The light (day) style, also the package default. |
| `DARK` | The dark (night) style. |

### Interfaces (uniffi.mapmap)

[`CancelTokenInterface`](#canceltokeninterface), [`GuidanceSessionInterface`](#guidancesessioninterface), [`LayerFetcher`](#layerfetcher), [`LocalRouter`](#localrouter), [`ProbeCollectorInterface`](#probecollectorinterface), [`TerritoryManagerInterface`](#territorymanagerinterface), [`TerritorySearchInterface`](#territorysearchinterface)

#### `CancelTokenInterface`

*Interface* · `uniffi/mapmap/mapmap.kt`

```kotlin
public interface CancelTokenInterface
```

A cancellation handle for an in-flight territory install or update.

The install calls on [`TerritoryManager`](#territorymanager) block the calling thread for
as long as a multi-gigabyte package takes to download, unpack and
verify. Create a token, pass it to one of the `_cancellable` variants
running on a background thread, and call [`CancelToken::cancel`] from
the UI thread when the user taps Cancel. The install polls it at safe
points and returns [`InstallOutcome::Cancelled`] /
[`UpdateOutcome::Cancelled`] with its staging directory removed: no
partial package is left on disk, the previously installed version is
untouched, and layers that had already been verified stay in the
resumable cache for the next attempt. Cancelling after the call has
returned changes nothing.

The token cannot interrupt a [`LayerFetcher`](#layerfetcher) blocked in a transfer:
give the fetcher the same token and have it poll
[`CancelToken::is_cancelled`] between chunks (or abort its request when
the app cancels). Whatever the fetcher then returns, the install
reports `Cancelled`, not a transport error.

**Initialisers and methods**

- `fun cancel()`: Request cancellation. Idempotent and safe from any thread.
- `fun isCancelled(): Boolean`: Whether [`Self::cancel`] has been called.

#### `GuidanceSessionInterface`

*Interface* · `uniffi/mapmap/mapmap.kt`

```kotlin
public interface GuidanceSessionInterface
```

A turn-by-turn guidance session (see [`crate::guidance`]).

Thread-safe: the mutable Ferrostar navigation state is guarded by a
mutex, matching UniFFI's shared-object concurrency model.

**Initialisers and methods**

- `fun advanceToNextStep(): GuidanceUpdate`: Manually advance to the next step (e.g. in a tunnel with no GNSS).
- `fun totalSteps(): UInt`: Number of steps in the route, including the arrival step.
- `fun updateLocation(lat: Double, lon: Double, timestampMs: Long, speedMps: Double?, courseDeg: Double?, horizontalAccuracyM: Double?): GuidanceUpdate`: Feed a location fix; returns the updated guidance state. `horizontal_accuracy_m` defaults to `0.0` (trusted exactly) when absent; pass the platform's reported accuracy when available so step advance and deviation gating behave correctly.

#### `LayerFetcher`

*Interface* · `uniffi/mapmap/mapmap.kt`

```kotlin
public interface LayerFetcher
```

The platform's download transport for over-the-air territory updates,
exported as a UniFFI callback interface like
[`crate::routing::LocalRouter`]: the core decides *what* to fetch and
cryptographically verifies everything; Kotlin/Swift decide *how* bytes
move (OkHttp, URLSession, an air-gapped mirror, …).

**Initialisers and methods**

- `fun fetch(relPath: String, expectedBlake3: String, destPath: String)`: Download the channel-relative `rel_path` (e.g. `territories/uk/2.0.0/layers/<addr>/valhalla.tar.zst`) to the local file `dest_path`. `expected_blake3` is advisory (empty for signed metadata files); the core re-verifies all content itself.

#### `LocalRouter`

*Interface* · `uniffi/mapmap/mapmap.kt`

```kotlin
public interface LocalRouter
```

The injection point for the platform's on-device Valhalla binding.

valhalla-mobile (<https://github.com/Rallista/valhalla-mobile>) exposes
a `route(request_json) -> response_json` actor on both Android and iOS;
Kotlin/Swift implement this interface with that actor and pass it to
[`crate::ffi::route_offline`]. Exported as a UniFFI callback interface
(`with_foreign`) so the implementation lives in foreign code.

**Initialisers and methods**

- `fun route(requestJson: String): String`: Execute a Valhalla turn-by-turn request and return the raw response JSON (either a route response or a Valhalla error body).

#### `ProbeCollectorInterface`

*Interface* · `uniffi/mapmap/mapmap.kt`

```kotlin
public interface ProbeCollectorInterface
```

On-device probe collector. Feed it every guidance update with the fix's
position, speed and timestamp; call [`finish`](ProbeCollector::finish) at
arrival or trip abandonment to get the [`ProbeBatch`](#probebatch) to upload.

Interior mutability (a `Mutex`) lets the FFI methods take `&self`, matching
the [`crate::guidance::GuidanceSession`] object shape.

**Initialisers and methods**

- `fun finish(arrived: Boolean, timestampMs: Long): ProbeBatch`: Close the trip and return the batch to upload. `arrived` distinguishes a completed trip from an abandoned one (both apply end suppression).
- `fun finishJson(arrived: Boolean, timestampMs: Long): String?`: Close the trip and return the exact JSON body to `POST /v1/probe`, or `None` when the batch carries nothing worth uploading. This is the single canonical serialiser for the probe wire format, platform code should upload this verbatim rather than re-encoding the record, so the wire form can never drift from what the gateway ingests.
- `fun observe(update: GuidanceUpdate, fixLat: Double, fixLon: Double, speedMps: Double?, timestampMs: Long)`: Feed one guidance update and its fix. `speed_mps` is the device's reported ground speed when available (preferred); pass `None` to let the collector estimate it from successive positions.
- `fun setRouteRef(routeRef: String)`: Set the opaque served-route token so the server can resolve segments.
- `fun setTerritory(territory: String)`: Set the territory id stamped into the batch.
- `fun setVehicleClass(vehicleClass: String)`: Set the vehicle costing class stamped into the batch (a class only, never a vehicle or driver identifier).

#### `TerritoryManagerInterface`

*Interface* · `uniffi/mapmap/mapmap.kt`

```kotlin
public interface TerritoryManagerInterface
```

Manages signed offline territory packages (see [`crate::territory`]).

**Initialisers and methods**

- `fun active(): String?`: The active territory id, if one is set and still installed.
- `fun applyUpdate(territoryId: String, newManifestJson: String, newManifestSigBase64: String, fetcher: LayerFetcher): UpdateResult`: Apply a differential update: stage, fetch only the changed layers through `fetcher`, verify everything and swap atomically. Any failure leaves the installed territory untouched; the active flag is preserved. Blocks until done and cannot be interrupted; see [`Self::apply_update_cancellable`].
- `fun applyUpdateCancellable(territoryId: String, newManifestJson: String, newManifestSigBase64: String, fetcher: LayerFetcher, cancel: CancelToken): UpdateOutcome`: [`Self::apply_update`] with a [`CancelToken`](#canceltoken). Polled before every layer download, between archive entries while unpacking, before each hash pass and before promotion. A tripped token yields [`UpdateOutcome::Cancelled`]: the installed version is untouched, staging is removed, and layers that had already been fetched and verified stay in the resumable cache, so calling again with the same manifest re-fetches only what is missing. Give `fetcher` the same token so it can stop an in-flight transfer (the token cannot interrupt foreign code on its own); whatever the fetcher then returns, a tripped token reports `Cancelled` rather than a transport error.
- `fun cachedLayerBytes(): ULong`: Bytes parked in the resumable layer cache: downloads a failed install already paid for, which a retry will not pay for again. Surface this next to a "resume" affordance and in storage settings.
- `fun checkForUpdate(territoryId: String, indexJson: String, indexSigBase64: String, fetcher: LayerFetcher): AvailableUpdate?`: Check the signed channel index for an update to an installed territory. `index_json` must be the exact bytes served as `index.json` and `index_sig_base64` the content of `index.json.sig`; the remote manifest is fetched and verified through `fetcher`. Returns `None` when the device is already on the channel's latest version (or the channel does not list the territory).
- `fun clearLayerCache()`: Discard every parked layer. The bytes are lost and a later install re-downloads (and on a metered channel re-pays for) them, so offer this only as a deliberate "free up space" action.
- `fun installDir(packageDir: String): TerritoryInfo`: Install an unpacked package directory. Blocks until done and cannot be interrupted; see [`Self::install_dir_cancellable`].
- `fun installDirCancellable(packageDir: String, cancel: CancelToken): InstallOutcome`: [`Self::install_dir`] with a [`CancelToken`](#canceltoken). Polled between copied files, before verification and before promotion.
- `fun installDirPartial(packageDir: String, omit: List<MapLayerKind>, cancel: CancelToken): InstallOutcome`: Install an unpacked package directory, holding only the layers this app can use: `omit` names the kinds to leave out (e.g. `Pmtiles` and `Extra("style")` for an app with no map view). The install records what it left out, and every later verification requires those layers to be **absent** rather than exempting them from hashing, so the record cannot be forged into admitting tampered bytes. Asking for an omitted layer answers [`NavCoreError::LayerNotInstalled`], which is not an error state: it is "you chose not to download this". The selection is sticky: updates carry it forward, so an app that skipped the render layers never downloads them on an update either.
- `fun installFromChannel(territoryId: String, indexJson: String, indexSigBase64: String, omit: List<MapLayerKind>, fetcher: LayerFetcher, cancel: CancelToken): ChannelInstallOutcome`: Install a territory **from the channel**, downloading only the layers this app can use. The first-install counterpart of [`Self::apply_update`], and the only path on which omitting layers saves bandwidth: a layer in `omit` is never requested, so it is never metered and never stored. `index_json` / `index_sig_base64` are the exact served bytes of `index.json` and `index.json.sig`; the channel's latest version of the territory is installed. Nothing is trusted but the pinned key: the index signature, the manifest signature and every blob's BLAKE3 are checked, and the assembled directory goes through the same full verification pass as any other install before it is promoted. Interrupted installs resume: verified layers stay parked in the layer cache. Calling it again for a territory that is already installed is how a selection is widened later. Layers already on the device at the same content hash are linked, not re-downloaded, so adding the map to a router-only install costs the map alone.
- `fun installSnpkg(archivePath: String): TerritoryInfo`: Install a packed `.snpkg` archive (verify-then-promote; rejected packages leave no trace). Blocks until done and cannot be interrupted; see [`Self::install_snpkg_cancellable`].
- `fun installSnpkgCancellable(archivePath: String, cancel: CancelToken): InstallOutcome`: [`Self::install_snpkg`] with a [`CancelToken`](#canceltoken). Polled between archive entries, before verification and before promotion; a tripped token yields [`InstallOutcome::Cancelled`] with staging removed and nothing installed.
- `fun installSnpkgPartial(archivePath: String, omit: List<MapLayerKind>, cancel: CancelToken): InstallOutcome`: [`Self::install_dir_partial`] from a packed `.snpkg` archive. The archive carries every layer, so this saves storage, not bandwidth: the omitted layers are unpacked and then dropped before verification. For the bandwidth saving use [`Self::install_from_channel`], which never fetches a blob it does not want.
- `fun layerPath(territoryId: String, kind: MapLayerKind): String`: Absolute path of a layer inside an installed territory (e.g. the Valhalla tile tree for valhalla-mobile, the PMTiles file for MapLibre).
- `fun list(): List<TerritoryInfo>`: List installed territories in territory-id order.
- `fun openSearch(territoryId: String): TerritorySearch`: Open the installed territory's bundled geocode index for on-device search. The returned [`TerritorySearch`](#territorysearch) holds the index open (mmap-backed), create it once per territory and reuse it across queries.
- `fun planUpdate(newManifestJson: String): UpdatePlan`: Plan a differential update from the installed manifest to `new_manifest_json` (a signed manifest's JSON body, as served by the update channel).
- `fun remove(territoryId: String)`: Remove an installed territory (clears the active marker if needed).
- `fun setActive(territoryId: String)`: Mark a territory as active (persisted atomically).
- `fun territoryStyle(territoryId: String, theme: StyleTheme): String`: A complete MapLibre style JSON for an installed territory with **local** (offline) references, mirroring the web SDK's `buildStyle()`. Load it straight into MapLibre, the territory tile source points at the installed PMTiles layer via a `pmtiles://<absolute path>` URL, so the map renders with radios off. Packages without a baked style layer get the default MapMap theme compiled on the fly over the package's PMTiles layer. The `glyphs` endpoint stays hosted (MapLibre caches fetched glyph ranges; no offline glyph bundle ships in packages today).
- `fun verifyTerritory(territoryId: String): TerritoryInfo`: Re-verify an installed territory from disk: the manifest signature, the BLAKE3 of every layer this install holds, and the absence of every layer it recorded as omitted. The ordinary calls do not re-hash gigabytes; this is the on-demand integrity check for after a restore or a crash. A failure arrives as [`NavCoreError::Verification`] and means the install must be repaired or reinstalled, not retried.

#### `TerritorySearchInterface`

*Interface* · `uniffi/mapmap/mapmap.kt`

```kotlin
public interface TerritorySearchInterface
```

On-device search over an installed territory's bundled geocode index
(see [`TerritoryManager::open_search`]). Fully offline; queries are
synchronous and mmap-backed, call from a background thread/dispatcher
like any other disk-bound SDK call.

**Initialisers and methods**

- `fun reverse(lat: Double, lon: Double, limit: UInt): List<Place>`: Reverse geocode: the nearest indexed places to a coordinate, nearest first.
- `fun search(query: String, near: RoutePoint?, limit: UInt): List<Place>`: Free-text search (addresses, streets, places, POIs, UK postcodes; prefix-tokenised, so it works for search-as-you-type). `near` biases ranking towards the given point and fills each result's `distance_m`; `limit` caps the number of hits.

### Objects (uniffi.mapmap)

[`AdrDecision.Allowed`](#adrdecisionallowed), [`ChannelInstallOutcome.Cancelled`](#channelinstalloutcomecancelled), [`FetchException.ErrorHandler`](#fetchexceptionerrorhandler), [`GuidanceUpdate.Arrived`](#guidanceupdatearrived), [`InstallOutcome.Cancelled`](#installoutcomecancelled), [`MapLayerKind.GeocodeIndex`](#maplayerkindgeocodeindex), [`MapLayerKind.Pmtiles`](#maplayerkindpmtiles), [`MapLayerKind.PoiSidecar`](#maplayerkindpoisidecar), [`MapLayerKind.ValhallaTiles`](#maplayerkindvalhallatiles), [`NavCoreException.ErrorHandler`](#navcoreexceptionerrorhandler), [`RerouteState.CoolingDown`](#reroutestatecoolingdown), [`RerouteState.Monitoring`](#reroutestatemonitoring), [`RerouteState.RerouteNeeded`](#reroutestatererouteneeded), [`SpeedLimit.Unknown`](#speedlimitunknown), [`SpeedLimit.Unlimited`](#speedlimitunlimited), [`TerritoryManager.Companion`](#territorymanagercompanion), [`UpdateOutcome.Cancelled`](#updateoutcomecancelled)

#### `AdrDecision.Allowed`

*Object* · `uniffi/mapmap/mapmap.kt`

```kotlin
object Allowed : AdrDecision()
```

Passage is permitted.

#### `ChannelInstallOutcome.Cancelled`

*Object* · `uniffi/mapmap/mapmap.kt`

```kotlin
object Cancelled : ChannelInstallOutcome()
```

The [`CancelToken`](#canceltoken) was tripped before promotion. Nothing was
installed; verified layers stay parked for the next attempt.

#### `FetchException.ErrorHandler`

*Object* · `uniffi/mapmap/mapmap.kt`

```kotlin
companion object ErrorHandler : UniffiRustCallStatusErrorHandler<FetchException>
```

Human-readable description of the failure.

#### `GuidanceUpdate.Arrived`

*Object* · `uniffi/mapmap/mapmap.kt`

```kotlin
object Arrived : GuidanceUpdate()
```

The user has arrived at the destination.

#### `InstallOutcome.Cancelled`

*Object* · `uniffi/mapmap/mapmap.kt`

```kotlin
object Cancelled : InstallOutcome()
```

The [`CancelToken`](#canceltoken) was tripped before promotion. Nothing was
installed; staging was removed.

#### `MapLayerKind.GeocodeIndex`

*Object* · `uniffi/mapmap/mapmap.kt`

```kotlin
object GeocodeIndex : MapLayerKind()
```

Tantivy geocoding index.

#### `MapLayerKind.Pmtiles`

*Object* · `uniffi/mapmap/mapmap.kt`

```kotlin
object Pmtiles : MapLayerKind()
```

PMTiles render layer.

#### `MapLayerKind.PoiSidecar`

*Object* · `uniffi/mapmap/mapmap.kt`

```kotlin
object PoiSidecar : MapLayerKind()
```

Proprietary POI sidecar database.

#### `MapLayerKind.ValhallaTiles`

*Object* · `uniffi/mapmap/mapmap.kt`

```kotlin
object ValhallaTiles : MapLayerKind()
```

Valhalla routing tile tree.

#### `NavCoreException.ErrorHandler`

*Object* · `uniffi/mapmap/mapmap.kt`

```kotlin
companion object ErrorHandler : UniffiRustCallStatusErrorHandler<NavCoreException>
```

#### `RerouteState.CoolingDown`

*Object* · `uniffi/mapmap/mapmap.kt`

```kotlin
object CoolingDown : RerouteState()
```

Off route and confirmed again, but suppressed because a reroute fired
within [`RerouteConfig::min_interval_between_reroutes_s`].

#### `RerouteState.Monitoring`

*Object* · `uniffi/mapmap/mapmap.kt`

```kotlin
object Monitoring : RerouteState()
```

On route (or recovered): nothing to do.

#### `RerouteState.RerouteNeeded`

*Object* · `uniffi/mapmap/mapmap.kt`

```kotlin
object RerouteNeeded : RerouteState()
```

A reroute is confirmed for this update; the accompanying
[`RerouteDecision`](#reroutedecision) should be routed. Reported exactly once per
confirmed departure.

#### `SpeedLimit.Unknown`

*Object* · `uniffi/mapmap/mapmap.kt`

```kotlin
object Unknown : SpeedLimit()
```

A limit applies but is unknown for the current segment (Mapbox
`maxspeed` `unknown`), the data source annotates this position but has
no value for it. Distinct from the field being absent entirely.

#### `SpeedLimit.Unlimited`

*Object* · `uniffi/mapmap/mapmap.kt`

```kotlin
object Unlimited : SpeedLimit()
```

The road is de-restricted (Mapbox `maxspeed` `none`; OSM
`maxspeed=none`, e.g. a German autobahn), show a "no limit" badge, do
not warn.

#### `TerritoryManager.Companion`

*Object* · `uniffi/mapmap/mapmap.kt`

```kotlin
companion object
```

**Initialisers and methods**

- `@Throws(NavCoreException::class) fun withRegionKey(rootDir: String, verifyingKeyHex: String, regionKeyHex: String): TerritoryManager`: [`Self::new`], also trusting custom regions (`region-…` ids, cut by `POST /v1/jobs/territory`) signed by `region_key_hex`, the public half of the gateway's region signing key. The keys are partitioned by id: the region key verifies `region-` ids only and the factory key everything else. Pin both in the app; never read either from the network.

#### `UpdateOutcome.Cancelled`

*Object* · `uniffi/mapmap/mapmap.kt`

```kotlin
object Cancelled : UpdateOutcome()
```

The [`CancelToken`](#canceltoken) was tripped before promotion. The installed
version is untouched; verified layers stay parked for a retry.

### Functions (uniffi.mapmap)

[`buildOfflineRouteRequest`](#buildofflinerouterequest), [`checkTunnel`](#checktunnel), [`forbiddenCategories`](#forbiddencategories), [`parseRouteResult`](#parserouteresult), [`routeOffline`](#routeoffline), [`styleForNavigation`](#stylefornavigation), [`styleWithBuildings3d`](#stylewithbuildings3d)

#### `buildOfflineRouteRequest`

*Function* · `uniffi/mapmap/mapmap.kt`

```kotlin
@Throws(NavCoreException::class) fun buildOfflineRouteRequest(locations: List<RoutePoint>, costing: CostingModel, adr: AdrProfile?): String
```

Build the Valhalla request JSON an on-device engine accepts, with ADR
costing options merged (conflicts rejected). Useful when foreign code
wants to drive the engine itself.

#### `checkTunnel`

*Function* · `uniffi/mapmap/mapmap.kt`

```kotlin
fun checkTunnel(profile: AdrProfile, category: AdrTunnelCategory): AdrDecision
```

Decide whether the vehicle may pass through a tunnel of `category`
(conservative ADR 8.6.4 reading; see [`crate::adr::check_tunnel`]).

#### `forbiddenCategories`

*Function* · `uniffi/mapmap/mapmap.kt`

```kotlin
fun forbiddenCategories(profile: AdrProfile): List<AdrTunnelCategory>
```

All tunnel categories the vehicle is forbidden from (ascending). Empty
when unrestricted.

#### `parseRouteResult`

*Function* · `uniffi/mapmap/mapmap.kt`

```kotlin
@Throws(NavCoreException::class) fun parseRouteResult(responseJson: String): RouteResult
```

Parse a raw on-device route response into a [`RouteResult`](#routeresult), including
the typed [`RouteResult::geometry`] and [`RouteResult::maneuvers`].

#### `routeOffline`

*Function* · `uniffi/mapmap/mapmap.kt`

```kotlin
@Throws(NavCoreException::class) fun routeOffline(router: LocalRouter, locations: List<RoutePoint>, costing: CostingModel, adr: AdrProfile?): RouteResult
```

Full offline routing orchestration: build the request, run it through
the injected [`LocalRouter`](#localrouter) (the platform's valhalla-mobile binding)
and parse the response.

#### `styleForNavigation`

*Function* · `uniffi/mapmap/mapmap.kt`

```kotlin
@Throws(NavCoreException::class) fun styleForNavigation(styleJson: String, keepBuildings3d: Boolean = true): String
```

Prepares a style for a turn-by-turn navigation view. Call it on
whatever style the app otherwise uses, right before loading the
navigation map; it is where nav-view safeguards live.

By default (`keep_buildings_3d = true`) the style's 3D building state
is left exactly as the app configured it: MapLibre Native's historical
fill-extrusion memory blow-up (maplibre-native#4107) is fixed by the
instancing rework (#4256; Android >= 13.2.0, iOS >= 6.26.1), and a
navigation-loop soak (`qa/expo-3d-soak`, 17 Jul 2026) measured a
bounded ~170 MB for city-wide 3D at nav zoom, flat, no growth.

Pass `keep_buildings_3d = false` to strip the `building-3d` layer, the pre-fix behaviour, when targeting MapLibre pins older than the
fix or low-memory devices you have not measured.

#### `styleWithBuildings3d`

*Function* · `uniffi/mapmap/mapmap.kt`

```kotlin
@Throws(NavCoreException::class) fun styleWithBuildings3d(styleJson: String, enabled: Boolean): String
```

Toggle 3D building extrusions on a MapMap `style.json` document (a
territory package's baked style or a hosted style body), returning the
adjusted document to load into MapLibre. Idempotent; errors with
[`NavCoreError::InvalidArgument`] on non-style input.

Safe on MapLibre Native releases carrying the fill-extrusion
instancing fix (maplibre-native#4256, merged 2026-05-05: Android
>= 13.2.0, iOS >= 6.26.1), the historical street-zoom memory blow-up
(#4107, 900 MB+ at zoom 17) is fixed there. Measured 17 Jul 2026 on a
Pixel-class device (`qa/expo-3d-soak`): city-wide extrusions at
zoom 16.5/pitch 60 cost a bounded ~170 MB over an identical no-3D
control, flat over the soak. On pins older than the fix, keep this
off outside measured map-browsing views.

## uniffi.ferrostar

### Classes (uniffi.ferrostar)

[`AndAdvanceConditions`](#andadvanceconditions), [`BoundingBox`](#boundingbox), [`Congestion`](#congestion), [`CourseOverGround`](#courseoverground), [`DeviationKind`](#deviationkind), [`DeviationKind.CompletelyOffRoute`](#deviationkindcompletelyoffroute), [`DeviationKind.OffStepOnRoute`](#deviationkindoffsteponroute), [`DistanceEntryAndExitCondition`](#distanceentryandexitcondition), [`DistanceEntryAndSnappedExitCondition`](#distanceentryandsnappedexitcondition), [`DistanceFromStepCondition`](#distancefromstepcondition), [`DistanceToEndOfStepCondition`](#distancetoendofstepcondition), [`GeographicCoordinate`](#geographiccoordinate), [`Heading`](#heading), [`Incident`](#incident), [`InstantiationException`](#instantiationexception), [`InstantiationException.OptionsJsonParseException`](#instantiationexceptionoptionsjsonparseexception), [`LaneInfo`](#laneinfo), [`LocationBias`](#locationbias), [`LocationBias.Left`](#locationbiasleft), [`LocationBias.Random`](#locationbiasrandom), [`LocationBias.Right`](#locationbiasright), [`LocationSimulationState`](#locationsimulationstate), [`ManualStepCondition`](#manualstepcondition), [`ModelException`](#modelexception), [`ModelException.PolylineGenerationException`](#modelexceptionpolylinegenerationexception), [`NavigationCacheImpl`](#navigationcacheimpl), [`NavigationCachingConfig`](#navigationcachingconfig), [`NavigationController`](#navigationcontroller), [`NavigationControllerConfig`](#navigationcontrollerconfig), [`NavigationObserverImpl`](#navigationobserverimpl), [`NavigationRecorder`](#navigationrecorder), [`NavigationRecordingEvent`](#navigationrecordingevent), [`NavigationRecordingEventData`](#navigationrecordingeventdata), [`NavigationRecordingEventData.RouteUpdate`](#navigationrecordingeventdatarouteupdate), [`NavigationRecordingEventData.StateUpdate`](#navigationrecordingeventdatastateupdate), [`NavigationReplay`](#navigationreplay), [`NavigationSession`](#navigationsession), [`NavigationSessionCache`](#navigationsessioncache), [`NavigationSessionSnapshot`](#navigationsessionsnapshot), [`Navigator`](#navigator), [`NavState`](#navstate), [`OrAdvanceConditions`](#oradvanceconditions), [`OsrmWaypointProperties`](#osrmwaypointproperties), [`ParsingException`](#parsingexception), [`ParsingException.InvalidGeometry`](#parsingexceptioninvalidgeometry), [`ParsingException.InvalidRouteObject`](#parsingexceptioninvalidrouteobject), [`ParsingException.InvalidStatusCode`](#parsingexceptioninvalidstatuscode), [`ParsingException.MalformedAnnotations`](#parsingexceptionmalformedannotations), [`ParsingException.UnknownParsingException`](#parsingexceptionunknownparsingexception), [`RecordingException`](#recordingexception), [`RecordingException.DeserializationException`](#recordingexceptiondeserializationexception), [`RecordingException.RecordingNotEnabled`](#recordingexceptionrecordingnotenabled), [`RecordingException.SerializationException`](#recordingexceptionserializationexception), [`Route`](#route), [`RouteAdapter`](#routeadapter), [`RouteDeviation`](#routedeviation), [`RouteDeviation.Deviation`](#routedeviationdeviation), [`RouteDeviationDetectorImpl`](#routedeviationdetectorimpl), [`RouteDeviationTracking`](#routedeviationtracking), [`RouteDeviationTracking.Custom`](#routedeviationtrackingcustom), [`RouteDeviationTracking.StaticThreshold`](#routedeviationtrackingstaticthreshold), [`RouteRequest`](#routerequest), [`RouteRequest.HttpGet`](#routerequesthttpget), [`RouteRequest.HttpPost`](#routerequesthttppost), [`RouteRequestGeneratorImpl`](#routerequestgeneratorimpl), [`RouteResponseParserImpl`](#routeresponseparserimpl), [`RouteStep`](#routestep), [`RoutingRequestGenerationException`](#routingrequestgenerationexception), [`RoutingRequestGenerationException.JsonException`](#routingrequestgenerationexceptionjsonexception), [`RoutingRequestGenerationException.NotEnoughWaypoints`](#routingrequestgenerationexceptionnotenoughwaypoints), [`RoutingRequestGenerationException.UnknownRequestGenerationException`](#routingrequestgenerationexceptionunknownrequestgenerationexception), [`SerializableNavState`](#serializablenavstate), [`SerializableStepAdvanceCondition`](#serializablestepadvancecondition), [`SerializableStepAdvanceCondition.AndAdvanceConditions`](#serializablestepadvanceconditionandadvanceconditions), [`SerializableStepAdvanceCondition.DistanceEntryAndSnappedExit`](#serializablestepadvanceconditiondistanceentryandsnappedexit), [`SerializableStepAdvanceCondition.DistanceEntryExit`](#serializablestepadvanceconditiondistanceentryexit), [`SerializableStepAdvanceCondition.DistanceFromStep`](#serializablestepadvanceconditiondistancefromstep), [`SerializableStepAdvanceCondition.DistanceToEndOfStep`](#serializablestepadvanceconditiondistancetoendofstep), [`SerializableStepAdvanceCondition.OrAdvanceConditions`](#serializablestepadvanceconditionoradvanceconditions), [`SimulationException`](#simulationexception), [`SimulationException.NotEnoughPoints`](#simulationexceptionnotenoughpoints), [`SimulationException.PolylineException`](#simulationexceptionpolylineexception), [`Speed`](#speed), [`SpokenInstruction`](#spokeninstruction), [`StepAdvanceCondition`](#stepadvancecondition), [`StepAdvanceResult`](#stepadvanceresult), [`TripProgress`](#tripprogress), [`TripState`](#tripstate), [`TripState.Complete`](#tripstatecomplete), [`TripState.Idle`](#tripstateidle), [`TripState.Navigating`](#tripstatenavigating), [`TripSummary`](#tripsummary), [`UserLocation`](#userlocation), [`ValhallaLocationSearchFilter`](#valhallalocationsearchfilter), [`ValhallaWaypointProperties`](#valhallawaypointproperties), [`VisualInstruction`](#visualinstruction), [`VisualInstructionContent`](#visualinstructioncontent), [`Waypoint`](#waypoint), [`WaypointAdvanceMode`](#waypointadvancemode), [`WaypointAdvanceMode.WaypointAlongAdvancingStep`](#waypointadvancemodewaypointalongadvancingstep), [`WaypointAdvanceMode.WaypointWithinRange`](#waypointadvancemodewaypointwithinrange), [`WellKnownRouteProvider`](#wellknownrouteprovider), [`WellKnownRouteProvider.GraphHopper`](#wellknownrouteprovidergraphhopper), [`WellKnownRouteProvider.Valhalla`](#wellknownrouteprovidervalhalla)

#### `AndAdvanceConditions`

*Class* · `uniffi/ferrostar/ferrostar.kt`

```kotlin
open class AndAdvanceConditions: Disposable, AutoCloseable, AndAdvanceConditionsInterface
```

Advance if all of the conditions are met (AND).

**Initialisers and methods**

- `override fun destroy()`
- `override fun close()`

#### `BoundingBox`

*Class* · `uniffi/ferrostar/ferrostar.kt`

```kotlin
data class BoundingBox (var sw: GeographicCoordinate, var ne: GeographicCoordinate)
```

A geographic bounding box defined by its corners.

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `var sw` | `GeographicCoordinate` |  |  |
| `var ne` | `GeographicCoordinate` |  |  |

#### `Congestion`

*Class* · `uniffi/ferrostar/ferrostar.kt`

```kotlin
data class Congestion (var value: UByte)
```

Details about congestion for an incident.

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `var value` | `UByte` |  |  |

#### `CourseOverGround`

*Class* · `uniffi/ferrostar/ferrostar.kt`

```kotlin
data class CourseOverGround (var degrees: UShort, var accuracy: UShort?)
```

The direction in which the user/device is observed to be traveling.

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `var degrees` | `UShort` |  |  |
| `var accuracy` | `UShort?` |  |  |

#### `DeviationKind`

*Class* · `uniffi/ferrostar/ferrostar.kt`

```kotlin
sealed class DeviationKind
```

The kind of deviation from the expected route.

Nested: [`DeviationKind.OffStepOnRoute`](#deviationkindoffsteponroute), [`DeviationKind.CompletelyOffRoute`](#deviationkindcompletelyoffroute).

#### `DeviationKind.CompletelyOffRoute`

*Class* · `uniffi/ferrostar/ferrostar.kt`

```kotlin
data class CompletelyOffRoute(val deviationFromRouteLine: Double) : DeviationKind()
```

The user is off the expected route entirely
(not within threshold of any remaining step,
neither the current one nor any future one).

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `val deviationFromRouteLine` | `Double` |  |  |

#### `DeviationKind.OffStepOnRoute`

*Class* · `uniffi/ferrostar/ferrostar.kt`

```kotlin
data class OffStepOnRoute(val deviationFromStepLine: Double) : DeviationKind()
```

The user is off the current step, but still on the route polyline (on a future step).

This can happen when the user takes a shortcut, GPS noise places them on a future step,
or the route self-intersects. Step advance conditions can use this to limit advancement.

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `val deviationFromStepLine` | `Double` |  |  |

#### `DistanceEntryAndExitCondition`

*Class* · `uniffi/ferrostar/ferrostar.kt`

```kotlin
open class DistanceEntryAndExitCondition: Disposable, AutoCloseable, DistanceEntryAndExitConditionInterface
```

A stateful condition that requires the user to reach the end of the step then proceed past it to advance.

**Initialisers and methods**

- `override fun destroy()`
- `override fun close()`

#### `DistanceEntryAndSnappedExitCondition`

*Class* · `uniffi/ferrostar/ferrostar.kt`

```kotlin
open class DistanceEntryAndSnappedExitCondition: Disposable, AutoCloseable, DistanceEntryAndSnappedExitConditionInterface
```

A stateful condition that requires the user to reach the end of the step then proceed past it to advance.

This variant uses route snapping (snapping to the combined current+next step geometry) for the exit check,
making it more robust for pedestrian/hiking navigation where users may walk on the opposite side of the street
or wander around the optimal path. The route-snapped exit check prevents premature advancement while still
allowing natural pedestrian movement patterns.
The exit distance is measured from the current step to the route-snapped position.

**Initialisers and methods**

- `override fun destroy()`
- `override fun close()`

#### `DistanceFromStepCondition`

*Class* · `uniffi/ferrostar/ferrostar.kt`

```kotlin
open class DistanceFromStepCondition: Disposable, AutoCloseable, DistanceFromStepConditionInterface
```

Advances once the user is at least [`Self::distance`] meters from the current step's polyline.

This results in *delayed* advance,
but is more robust to spurious / unwanted step changes in scenarios including
self-intersecting routes (sudden jumps to the next step)
and pauses at intersections (advancing too soon before the maneuver is complete).

NOTE! This may be less robust to things like short steps, out-and-backs, and U-turns,
where this may eagerly exit a current step before the user has traversed it
if the start of the step is within range of the end.

**Initialisers and methods**

- `override fun destroy()`
- `override fun close()`

#### `DistanceToEndOfStepCondition`

*Class* · `uniffi/ferrostar/ferrostar.kt`

```kotlin
open class DistanceToEndOfStepCondition: Disposable, AutoCloseable, DistanceToEndOfStepConditionInterface
```

Automatically advances when the user's location is close enough to the end of the step.

This results in an eager advance where the user will jump to the next step when the
condition is met.

**Initialisers and methods**

- `override fun destroy()`
- `override fun close()`

#### `GeographicCoordinate`

*Class* · `uniffi/ferrostar/ferrostar.kt`

```kotlin
data class GeographicCoordinate (var lat: Double, var lng: Double)
```

A geographic coordinate in WGS84.

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `var lat` | `Double` |  |  |
| `var lng` | `Double` |  |  |

#### `Heading`

*Class* · `uniffi/ferrostar/ferrostar.kt`

```kotlin
data class Heading (var trueHeading: UShort, var accuracy: UShort, var timestamp: java.time.Instant)
```

The heading of the user/device.

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `var trueHeading` | `UShort` |  |  |
| `var accuracy` | `UShort` |  |  |
| `var timestamp` | `java.time.Instant` |  |  |

#### `Incident`

*Class* · `uniffi/ferrostar/ferrostar.kt`

```kotlin
data class Incident (var id: String, var incidentType: IncidentType, var description: String?, var longDescription: String?, var creationTime: UtcDateTime?, var startTime: UtcDateTime?, var endTime: UtcDateTime?, var impact: Impact?, var lanesBlocked: List<BlockedLane>, var congestion: Congestion?, var closed: Boolean?, var geometryIndexStart: ULong, var geometryIndexEnd: ULong?, var subType: String?, var subTypeDescription: String?, var iso31661Alpha2: String?, var iso31661Alpha3: String?, var affectedRoadNames: List<String>, var bbox: BoundingBox?)
```

An incident affecting the free flow of traffic,
such as constructions, accidents, and congestion.

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `var id` | `String` |  |  |
| `var incidentType` | `IncidentType` |  |  |
| `var description` | `String?` |  |  |
| `var longDescription` | `String?` |  |  |
| `var creationTime` | `UtcDateTime?` |  |  |
| `var startTime` | `UtcDateTime?` |  |  |
| `var endTime` | `UtcDateTime?` |  |  |
| `var impact` | `Impact?` |  |  |
| `var lanesBlocked` | `List<BlockedLane>` |  |  |
| `var congestion` | `Congestion?` |  |  |
| `var closed` | `Boolean?` |  |  |
| `var geometryIndexStart` | `ULong` |  |  |
| `var geometryIndexEnd` | `ULong?` |  |  |
| `var subType` | `String?` |  |  |
| `var subTypeDescription` | `String?` |  |  |
| `var iso31661Alpha2` | `String?` |  |  |
| `var iso31661Alpha3` | `String?` |  |  |
| `var affectedRoadNames` | `List<String>` |  |  |
| `var bbox` | `BoundingBox?` |  |  |

#### `InstantiationException`

*Class* · `uniffi/ferrostar/ferrostar.kt`

```kotlin
sealed class InstantiationException: Exception()
```

Nested: [`InstantiationException.OptionsJsonParseException`](#instantiationexceptionoptionsjsonparseexception), [`InstantiationException.ErrorHandler`](#instantiationexceptionerrorhandler).

#### `InstantiationException.OptionsJsonParseException`

*Class* · `uniffi/ferrostar/ferrostar.kt`

```kotlin
class OptionsJsonParseException() : InstantiationException()
```

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `val message` |  |  |  |

#### `LaneInfo`

*Class* · `uniffi/ferrostar/ferrostar.kt`

```kotlin
data class LaneInfo (var active: Boolean, var directions: List<String>, var activeDirection: String?)
```

The content of a visual instruction.

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `var active` | `Boolean` |  |  |
| `var directions` | `List<String>` |  |  |
| `var activeDirection` | `String?` |  |  |

#### `LocationBias`

*Class* · `uniffi/ferrostar/ferrostar.kt`

```kotlin
sealed class LocationBias
```

Controls how simulated locations deviate from the actual route line.
This simulates real-world GPS behavior where readings often have systematic bias.

Nested: [`LocationBias.Left`](#locationbiasleft), [`LocationBias.Right`](#locationbiasright), [`LocationBias.Random`](#locationbiasrandom), [`LocationBias.None`](#locationbiasnone).

#### `LocationBias.Left`

*Class* · `uniffi/ferrostar/ferrostar.kt`

```kotlin
data class Left(val v1: Double) : LocationBias()
```

Simulates GPS bias by offsetting locations to the left of the route direction.
The f64 parameter specifies the offset distance in meters.

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `val v1` | `Double` |  |  |

#### `LocationBias.Random`

*Class* · `uniffi/ferrostar/ferrostar.kt`

```kotlin
data class Random(val v1: Double) : LocationBias()
```

Simulates GPS bias by randomly choosing left or right offset on initialization
and maintaining that bias throughout the route.
The f64 parameter specifies the offset distance in meters.

This mimics real-world GPS behavior where bias direction is random but typically
remains consistent during a trip.

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `val v1` | `Double` |  |  |

#### `LocationBias.Right`

*Class* · `uniffi/ferrostar/ferrostar.kt`

```kotlin
data class Right(val v1: Double) : LocationBias()
```

Simulates GPS bias by offsetting locations to the right of the route direction.
The f64 parameter specifies the offset distance in meters.

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `val v1` | `Double` |  |  |

#### `LocationSimulationState`

*Class* · `uniffi/ferrostar/ferrostar.kt`

```kotlin
data class LocationSimulationState (var currentLocation: UserLocation, var remainingLocations: List<GeographicCoordinate>, var bias: LocationBias)
```

The current state of the simulation.

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `var currentLocation` | `UserLocation` |  |  |
| `var remainingLocations` | `List<GeographicCoordinate>` |  |  |
| `var bias` | `LocationBias` |  |  |

#### `ManualStepCondition`

*Class* · `uniffi/ferrostar/ferrostar.kt`

```kotlin
open class ManualStepCondition: Disposable, AutoCloseable, ManualStepConditionInterface
```

Never advances to the next step automatically;
requires calling [`NavigationController::advance_to_next_step`](super::NavigationController::advance_to_next_step).

You can use this to implement custom behaviors in external code.

**Initialisers and methods**

- `override fun destroy()`
- `override fun close()`

#### `ModelException`

*Class* · `uniffi/ferrostar/ferrostar.kt`

```kotlin
sealed class ModelException: Exception()
```

Nested: [`ModelException.PolylineGenerationException`](#modelexceptionpolylinegenerationexception), [`ModelException.ErrorHandler`](#modelexceptionerrorhandler).

#### `ModelException.PolylineGenerationException`

*Class* · `uniffi/ferrostar/ferrostar.kt`

```kotlin
class PolylineGenerationException(val error: String) : ModelException()
```

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `val error` | `String` |  |  |
| `val message` |  |  |  |

#### `NavigationCacheImpl`

*Class* · `uniffi/ferrostar/ferrostar.kt`

```kotlin
open class NavigationCacheImpl: Disposable, AutoCloseable, NavigationCache
```

**Initialisers and methods**

- `override fun destroy()`
- `override fun close()`
- `override fun save(record: ByteArray)`
- `override fun load(): ByteArray?`
- `override fun delete()`

#### `NavigationCachingConfig`

*Class* · `uniffi/ferrostar/ferrostar.kt`

```kotlin
data class NavigationCachingConfig (var cacheIntervalSeconds: Long, var maxAgeSeconds: Long)
```

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `var cacheIntervalSeconds` | `Long` |  |  |
| `var maxAgeSeconds` | `Long` |  |  |

#### `NavigationController`

*Class* · `uniffi/ferrostar/ferrostar.kt`

```kotlin
open class NavigationController: Disposable, AutoCloseable, NavigationControllerInterface
```

Manages the navigation lifecycle through a route,
returning an updated state given inputs like user location.

Notes for implementing a new platform:
- A controller is bound to a single route; if you want recalculation, create a new instance.
- This is a pure type (no interior mutability), so a core function of your platform code is responsibly managing mutable state.

**Initialisers and methods**

- `constructor(route: Route, config: NavigationControllerConfig)`: Create a navigation controller for a route and configuration.
- `override fun destroy()`
- `override fun close()`

#### `NavigationControllerConfig`

*Class* · `uniffi/ferrostar/ferrostar.kt`

```kotlin
data class NavigationControllerConfig (var waypointAdvance: WaypointAdvanceMode, var stepAdvanceCondition: StepAdvanceCondition, var arrivalStepAdvanceCondition: StepAdvanceCondition, var routeDeviationTracking: RouteDeviationTracking, var snappedLocationCourseFiltering: CourseFiltering): Disposable
```

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `var waypointAdvance` | `WaypointAdvanceMode` |  |  |
| `var stepAdvanceCondition` | `StepAdvanceCondition` |  |  |
| `var arrivalStepAdvanceCondition` | `StepAdvanceCondition` |  |  |
| `var routeDeviationTracking` | `RouteDeviationTracking` |  |  |
| `var snappedLocationCourseFiltering` | `CourseFiltering` |  |  |

#### `NavigationObserverImpl`

*Class* · `uniffi/ferrostar/ferrostar.kt`

```kotlin
open class NavigationObserverImpl: Disposable, AutoCloseable, NavigationObserver
```

**Initialisers and methods**

- `override fun destroy()`
- `override fun close()`
- `override fun onGetInitialState(state: NavState)`
- `override fun onUserLocationUpdate(location: UserLocation, state: NavState)`
- `override fun onAdvanceToNextStep(state: NavState)`
- `override fun onRouteAvailable(route: Route)`

#### `NavigationRecorder`

*Class* · `uniffi/ferrostar/ferrostar.kt`

```kotlin
open class NavigationRecorder: Disposable, AutoCloseable, NavigationRecorderInterface
```

#### `NavigationRecordingEvent`

*Class* · `uniffi/ferrostar/ferrostar.kt`

```kotlin
data class NavigationRecordingEvent (var timestamp: Long, var eventData: NavigationRecordingEventData)
```

An event that occurs during navigation.

This is used for the optional session recording / telemetry.

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `var timestamp` | `Long` |  |  |
| `var eventData` | `NavigationRecordingEventData` |  |  |

#### `NavigationRecordingEventData`

*Class* · `uniffi/ferrostar/ferrostar.kt`

```kotlin
sealed class NavigationRecordingEventData
```

The event type.

For full replayability, we record things like rerouting, and not just location updates.

Nested: [`NavigationRecordingEventData.StateUpdate`](#navigationrecordingeventdatastateupdate), [`NavigationRecordingEventData.RouteUpdate`](#navigationrecordingeventdatarouteupdate).

#### `NavigationRecordingEventData.RouteUpdate`

*Class* · `uniffi/ferrostar/ferrostar.kt`

```kotlin
data class RouteUpdate(val route: uniffi.ferrostar.Route) : NavigationRecordingEventData()
```

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `val route` | `uniffi.ferrostar.Route` |  |  |

#### `NavigationRecordingEventData.StateUpdate`

*Class* · `uniffi/ferrostar/ferrostar.kt`

```kotlin
data class StateUpdate(val tripState: uniffi.ferrostar.TripState, val stepAdvanceCondition: uniffi.ferrostar.SerializableStepAdvanceCondition) : NavigationRecordingEventData()
```

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `val tripState` | `uniffi.ferrostar.TripState` |  |  |
| `val stepAdvanceCondition` | `uniffi.ferrostar.SerializableStepAdvanceCondition` |  |  |

#### `NavigationReplay`

*Class* · `uniffi/ferrostar/ferrostar.kt`

```kotlin
open class NavigationReplay: Disposable, AutoCloseable, NavigationReplayInterface
```

A wrapper around `NavigationRecording` to facilitate replaying the event stream.

**Initialisers and methods**

- `override fun destroy()`
- `override fun close()`

#### `NavigationSession`

*Class* · `uniffi/ferrostar/ferrostar.kt`

```kotlin
open class NavigationSession: Disposable, AutoCloseable, NavigationSessionInterface
```

#### `NavigationSessionCache`

*Class* · `uniffi/ferrostar/ferrostar.kt`

```kotlin
open class NavigationSessionCache: Disposable, AutoCloseable, NavigationSessionCacheInterface
```

#### `NavigationSessionSnapshot`

*Class* · `uniffi/ferrostar/ferrostar.kt`

```kotlin
data class NavigationSessionSnapshot (var savedAt: UtcDateTime, var route: Route, var tripState: TripState?)
```

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `var savedAt` | `UtcDateTime` |  |  |
| `var route` | `Route` |  |  |
| `var tripState` | `TripState?` |  |  |

#### `Navigator`

*Class* · `uniffi/ferrostar/ferrostar.kt`

```kotlin
open class Navigator: Disposable, AutoCloseable, NavigatorInterface
```

Core interface for navigation functionalities.

This trait defines the essential operations for a navigation state manager.
This lets us build additional layers (e.g. event logging)
around [`NavigationController`](#navigationcontroller) in a composable manner.

**Initialisers and methods**

- `override fun destroy()`
- `override fun close()`
- `override fun route(): Route`
- `override fun getInitialState(location: UserLocation): NavState`
- `override fun advanceToNextStep(state: NavState): NavState`
- `override fun updateUserLocation(location: UserLocation, state: NavState): NavState`

#### `NavState`

*Class* · `uniffi/ferrostar/ferrostar.kt`

```kotlin
data class NavState (var tripState: TripState, var stepAdvanceCondition: StepAdvanceCondition): Disposable
```

The navigation state.

This is typically created from an initial trip state
and conditions for advancing navigation to the next step.
Any internal navigation state is packed in here so that
the navigation controller can remain functionally pure.

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `var tripState` | `TripState` |  |  |
| `var stepAdvanceCondition` | `StepAdvanceCondition` |  |  |

#### `OrAdvanceConditions`

*Class* · `uniffi/ferrostar/ferrostar.kt`

```kotlin
open class OrAdvanceConditions: Disposable, AutoCloseable, OrAdvanceConditionsInterface
```

Advance if any of the conditions are met (OR).

This is ideal for short circuit type advance conditions.

E.g. you may have:
1. A short circuit detecting if the user has exceeded a large distance from the current step.
2. A default advance behavior.

**Initialisers and methods**

- `override fun destroy()`
- `override fun close()`

#### `OsrmWaypointProperties`

*Class* · `uniffi/ferrostar/ferrostar.kt`

```kotlin
data class OsrmWaypointProperties (var name: String?, var distance: Double?)
```

Waypoint properties parsed from an OSRM-compatible server response.

NOTE: Some servers (such as Valhalla) may support additional parameters at request time
which are _not_ echoed back in the response time.
This is unfortunate; PRs upstream would likely be welcomed!
Similarly, if your server is OSRM-compatible and returns additional attributes,
feel free to open a PR to include these as optional properties.

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `var name` | `String?` |  |  |
| `var distance` | `Double?` |  |  |

#### `ParsingException`

*Class* · `uniffi/ferrostar/ferrostar.kt`

```kotlin
sealed class ParsingException: Exception()
```

Nested: [`ParsingException.InvalidRouteObject`](#parsingexceptioninvalidrouteobject), [`ParsingException.InvalidGeometry`](#parsingexceptioninvalidgeometry), [`ParsingException.MalformedAnnotations`](#parsingexceptionmalformedannotations), [`ParsingException.InvalidStatusCode`](#parsingexceptioninvalidstatuscode), [`ParsingException.UnknownParsingException`](#parsingexceptionunknownparsingexception), [`ParsingException.ErrorHandler`](#parsingexceptionerrorhandler).

#### `ParsingException.InvalidGeometry`

*Class* · `uniffi/ferrostar/ferrostar.kt`

```kotlin
class InvalidGeometry(val error: String) : ParsingException()
```

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `val error` | `String` |  |  |
| `val message` |  |  |  |

#### `ParsingException.InvalidRouteObject`

*Class* · `uniffi/ferrostar/ferrostar.kt`

```kotlin
class InvalidRouteObject(val error: String) : ParsingException()
```

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `val error` | `String` |  |  |
| `val message` |  |  |  |

#### `ParsingException.InvalidStatusCode`

*Class* · `uniffi/ferrostar/ferrostar.kt`

```kotlin
class InvalidStatusCode(val code: String, val description: String?) : ParsingException()
```

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `val code` | `String` |  |  |
| `val description` | `String?` |  |  |
| `val message` |  |  |  |

#### `ParsingException.MalformedAnnotations`

*Class* · `uniffi/ferrostar/ferrostar.kt`

```kotlin
class MalformedAnnotations(val error: String) : ParsingException()
```

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `val error` | `String` |  |  |
| `val message` |  |  |  |

#### `ParsingException.UnknownParsingException`

*Class* · `uniffi/ferrostar/ferrostar.kt`

```kotlin
class UnknownParsingException() : ParsingException()
```

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `val message` |  |  |  |

#### `RecordingException`

*Class* · `uniffi/ferrostar/ferrostar.kt`

```kotlin
sealed class RecordingException: Exception()
```

A session recording error.

Nested: [`RecordingException.SerializationException`](#recordingexceptionserializationexception), [`RecordingException.RecordingNotEnabled`](#recordingexceptionrecordingnotenabled), [`RecordingException.DeserializationException`](#recordingexceptiondeserializationexception), [`RecordingException.ErrorHandler`](#recordingexceptionerrorhandler).

#### `RecordingException.DeserializationException`

*Class* · `uniffi/ferrostar/ferrostar.kt`

```kotlin
class DeserializationException(val error: String) : RecordingException()
```

Error during deserialization.

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `val error` | `String` |  |  |
| `val message` |  |  |  |

#### `RecordingException.RecordingNotEnabled`

*Class* · `uniffi/ferrostar/ferrostar.kt`

```kotlin
class RecordingNotEnabled() : RecordingException()
```

Recording is not enabled for this controller.

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `val message` |  |  |  |

#### `RecordingException.SerializationException`

*Class* · `uniffi/ferrostar/ferrostar.kt`

```kotlin
class SerializationException(val error: String) : RecordingException()
```

Error during serialization.

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `val error` | `String` |  |  |
| `val message` |  |  |  |

#### `Route`

*Class* · `uniffi/ferrostar/ferrostar.kt`

```kotlin
data class Route (var geometry: List<GeographicCoordinate>, var bbox: BoundingBox, var distance: Double, var waypoints: List<Waypoint>, var steps: List<RouteStep>)
```

Information describing the series of steps needed to travel between two or more points.

NOTE: This type is unstable and is still under active development and should be
considered unstable.

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `var geometry` | `List<GeographicCoordinate>` |  |  |
| `var bbox` | `BoundingBox` |  |  |
| `var distance` | `Double` |  |  |
| `var waypoints` | `List<Waypoint>` |  |  |
| `var steps` | `List<RouteStep>` |  |  |

#### `RouteAdapter`

*Class* · `uniffi/ferrostar/ferrostar.kt`

```kotlin
open class RouteAdapter: Disposable, AutoCloseable, RouteAdapterInterface
```

The route adapter bridges between the common core and a routing backend where interaction takes place
over a generic request/response flow (typically over a network;
local/offline routers **do not use this object** as the interaction patterns are different).

This is essentially the composite of the [`RouteRequestGenerator`](#routerequestgenerator) and [`RouteResponseParser`](#routeresponseparser)
traits, but it provides one further level of abstraction which is helpful to consumers.
As there is no way to signal compatibility between request generators and response parsers,
the [`RouteAdapter`](#routeadapter) provides convenience constructors which take the guesswork out of it,
while still leaving consumers free to implement one or both halves.

In the future, we may provide additional methods or conveniences, and this
indirection leaves the design open to such changes without necessarily breaking source
compatibility.
One such possible extension would be the ability to fetch more detailed attributes in real time.
This is supported by the Valhalla stack, among others.

Ideas  welcome re: how to signal compatibility between request generators and response parsers.
I don't think we can do this in the type system, since one of the reasons for the split design
is modularity, including the possibility of user-provided implementations, and these will not
always be of a "known" type to the Rust side.

**Initialisers and methods**

- `constructor(requestGenerator: RouteRequestGenerator, responseParser: RouteResponseParser)`: Creates a route adapter from any request generator and response parser. This constructor offers unlimited flexibility, but if you're using a major routing vendor API, [`RouteAdapter::from_well_known_route_provider`] may be a more convenient interface.
- `override fun destroy()`
- `override fun close()`
- `@Throws(RoutingRequestGenerationException::class) override fun generateRequest(userLocation: UserLocation, waypoints: List<Waypoint>): RouteRequest`
- `@Throws(ParsingException::class) override fun parseResponse(response: ByteArray): List<Route>`

Nested: [`RouteAdapter.Companion`](#routeadaptercompanion).

#### `RouteDeviation`

*Class* · `uniffi/ferrostar/ferrostar.kt`

```kotlin
sealed class RouteDeviation
```

Status information that describes whether the user is proceeding according to the route or not.

Note that the name is intentionally a bit generic to allow for expansion of other states.
For example, we could conceivably add a "wrong way" status in the future.

Nested: [`RouteDeviation.NoDeviation`](#routedeviationnodeviation), [`RouteDeviation.Deviation`](#routedeviationdeviation).

#### `RouteDeviation.Deviation`

*Class* · `uniffi/ferrostar/ferrostar.kt`

```kotlin
data class Deviation(val kind: uniffi.ferrostar.DeviationKind) : RouteDeviation()
```

The user has deviated from the expected route.

Check the [`DeviationKind`](#deviationkind) to determine if the user is still on the route polyline
(off the current step but on a future step) or off the route entirely.

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `val kind` | `uniffi.ferrostar.DeviationKind` |  |  |

#### `RouteDeviationDetectorImpl`

*Class* · `uniffi/ferrostar/ferrostar.kt`

```kotlin
open class RouteDeviationDetectorImpl: Disposable, AutoCloseable, RouteDeviationDetector
```

A custom deviation detector (for extending the behavior of [`RouteDeviationTracking`](#routedeviationtracking)).

This allows for arbitrarily complex implementations when the provided ones are not enough.
For example, detecting that the user is proceeding the wrong direction by keeping a ring buffer
of recent locations, or perform local map matching.

**Initialisers and methods**

- `override fun destroy()`
- `override fun close()`
- `override fun checkRouteDeviation(route: Route, tripState: TripState): RouteDeviation`: Determines whether the user is following the route correctly or not. NOTE: This function has a single responsibility. Side-effects like whether to recalculate a route are left to higher levels, and implementations should only be concerned with determining the facts. IMPORTANT: If you are short circuiting [`StepAdvanceCondition`](#stepadvancecondition)'s to allow skipping steps, you must always fall back to checking the deviation from the full route line.

#### `RouteDeviationTracking`

*Class* · `uniffi/ferrostar/ferrostar.kt`

```kotlin
sealed class RouteDeviationTracking: Disposable
```

Determines if the user has deviated from the expected route.

**Initialisers and methods**

- `override fun destroy()`

Nested: [`RouteDeviationTracking.None`](#routedeviationtrackingnone), [`RouteDeviationTracking.StaticThreshold`](#routedeviationtrackingstaticthreshold), [`RouteDeviationTracking.Custom`](#routedeviationtrackingcustom).

#### `RouteDeviationTracking.Custom`

*Class* · `uniffi/ferrostar/ferrostar.kt`

```kotlin
data class Custom(val detector: uniffi.ferrostar.RouteDeviationDetector) : RouteDeviationTracking()
```

An arbitrary user-defined implementation.
You decide with your own [`RouteDeviationDetector`](#routedeviationdetector) implementation!

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `val detector` | `uniffi.ferrostar.RouteDeviationDetector` |  |  |

#### `RouteDeviationTracking.StaticThreshold`

*Class* · `uniffi/ferrostar/ferrostar.kt`

```kotlin
data class StaticThreshold(val minimumHorizontalAccuracy: UShort, val maxAcceptableDeviation: Double) : RouteDeviationTracking()
```

Detects deviation from the route using a configurable static distance threshold from the route line.

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `val minimumHorizontalAccuracy` | `UShort` |  |  |
| `val maxAcceptableDeviation` | `Double` |  |  |

#### `RouteRequest`

*Class* · `uniffi/ferrostar/ferrostar.kt`

```kotlin
sealed class RouteRequest
```

A route request generated by a [`RouteRequestGenerator`](#routerequestgenerator).

Nested: [`RouteRequest.HttpPost`](#routerequesthttppost), [`RouteRequest.HttpGet`](#routerequesthttpget).

#### `RouteRequest.HttpGet`

*Class* · `uniffi/ferrostar/ferrostar.kt`

```kotlin
data class HttpGet(val url: String, val headers: Map<String, String>) : RouteRequest()
```

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `val url` | `String` |  |  |
| `val headers` | `Map<String, String>` |  |  |

#### `RouteRequest.HttpPost`

*Class* · `uniffi/ferrostar/ferrostar.kt`

```kotlin
data class HttpPost(val url: String, val headers: Map<String, String>, val body: ByteArray) : RouteRequest()
```

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `val url` | `String` |  |  |
| `val headers` | `Map<String, String>` |  |  |
| `val body` | `ByteArray` |  |  |

#### `RouteRequestGeneratorImpl`

*Class* · `uniffi/ferrostar/ferrostar.kt`

```kotlin
open class RouteRequestGeneratorImpl: Disposable, AutoCloseable, RouteRequestGenerator
```

A trait describing any object capable of generating [`RouteRequest`](#routerequest)s.

The interface is intentionally generic. Every routing backend has its own set of
parameters, including a "profile," max travel speed, units of speed and distance, and more.
It is assumed that these properties will be set at construction time or otherwise configured
before use, so that we can keep the public interface as generic as possible.

Implementations may be either in Rust (most popular engines should eventually have Rust
glue code) or foreign code.

**Initialisers and methods**

- `override fun destroy()`
- `override fun close()`
- `@Throws(RoutingRequestGenerationException::class) override fun generateRequest(userLocation: UserLocation, waypoints: List<Waypoint>): RouteRequest`: Generates a routing backend request given the set of locations. While most implementations will treat the locations as an ordered sequence, this is not guaranteed (ex: an optimized router).

#### `RouteResponseParserImpl`

*Class* · `uniffi/ferrostar/ferrostar.kt`

```kotlin
open class RouteResponseParserImpl: Disposable, AutoCloseable, RouteResponseParser
```

A generic interface describing any object capable of parsing a response from a routing
backend into one or more [`Route`](#route)s.

**Initialisers and methods**

- `override fun destroy()`
- `override fun close()`
- `@Throws(ParsingException::class) override fun parseResponse(response: ByteArray): List<Route>`: Parses a raw response from the routing backend into a route. We use a sequence of octets as a common interchange format. as this works for all currently conceivable formats (JSON, PBF, etc.).

#### `RouteStep`

*Class* · `uniffi/ferrostar/ferrostar.kt`

```kotlin
data class RouteStep (var geometry: List<GeographicCoordinate>, var distance: Double, var duration: Double, var roadName: String?, var exits: List<String>, var instruction: String, var visualInstructions: List<VisualInstruction>, var spokenInstructions: List<SpokenInstruction>, var annotations: List<String>?, var incidents: List<Incident>, var drivingSide: DrivingSide?, var roundaboutExitNumber: UByte?)
```

A maneuver (such as a turn or merge) followed by travel of a certain distance until reaching
the next step.

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `var geometry` | `List<GeographicCoordinate>` |  |  |
| `var distance` | `Double` |  |  |
| `var duration` | `Double` |  |  |
| `var roadName` | `String?` |  |  |
| `var exits` | `List<String>` |  |  |
| `var instruction` | `String` |  |  |
| `var visualInstructions` | `List<VisualInstruction>` |  |  |
| `var spokenInstructions` | `List<SpokenInstruction>` |  |  |
| `var annotations` | `List<String>?` |  |  |
| `var incidents` | `List<Incident>` |  |  |
| `var drivingSide` | `DrivingSide?` |  |  |
| `var roundaboutExitNumber` | `UByte?` |  |  |

#### `RoutingRequestGenerationException`

*Class* · `uniffi/ferrostar/ferrostar.kt`

```kotlin
sealed class RoutingRequestGenerationException: Exception()
```

Nested: [`RoutingRequestGenerationException.NotEnoughWaypoints`](#routingrequestgenerationexceptionnotenoughwaypoints), [`RoutingRequestGenerationException.JsonException`](#routingrequestgenerationexceptionjsonexception), [`RoutingRequestGenerationException.UnknownRequestGenerationException`](#routingrequestgenerationexceptionunknownrequestgenerationexception), [`RoutingRequestGenerationException.ErrorHandler`](#routingrequestgenerationexceptionerrorhandler).

#### `RoutingRequestGenerationException.JsonException`

*Class* · `uniffi/ferrostar/ferrostar.kt`

```kotlin
class JsonException() : RoutingRequestGenerationException()
```

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `val message` |  |  |  |

#### `RoutingRequestGenerationException.NotEnoughWaypoints`

*Class* · `uniffi/ferrostar/ferrostar.kt`

```kotlin
class NotEnoughWaypoints() : RoutingRequestGenerationException()
```

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `val message` |  |  |  |

#### `RoutingRequestGenerationException.UnknownRequestGenerationException`

*Class* · `uniffi/ferrostar/ferrostar.kt`

```kotlin
class UnknownRequestGenerationException() : RoutingRequestGenerationException()
```

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `val message` |  |  |  |

#### `SerializableNavState`

*Class* · `uniffi/ferrostar/ferrostar.kt`

```kotlin
data class SerializableNavState (var tripState: TripState, var stepAdvanceCondition: SerializableStepAdvanceCondition)
```

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `var tripState` | `TripState` |  |  |
| `var stepAdvanceCondition` | `SerializableStepAdvanceCondition` |  |  |

#### `SerializableStepAdvanceCondition`

*Class* · `uniffi/ferrostar/ferrostar.kt`

```kotlin
sealed class SerializableStepAdvanceCondition
```

Nested: [`SerializableStepAdvanceCondition.Manual`](#serializablestepadvanceconditionmanual), [`SerializableStepAdvanceCondition.DistanceToEndOfStep`](#serializablestepadvanceconditiondistancetoendofstep), [`SerializableStepAdvanceCondition.DistanceFromStep`](#serializablestepadvanceconditiondistancefromstep), [`SerializableStepAdvanceCondition.DistanceEntryExit`](#serializablestepadvanceconditiondistanceentryexit), [`SerializableStepAdvanceCondition.DistanceEntryAndSnappedExit`](#serializablestepadvanceconditiondistanceentryandsnappedexit), [`SerializableStepAdvanceCondition.OrAdvanceConditions`](#serializablestepadvanceconditionoradvanceconditions), [`SerializableStepAdvanceCondition.AndAdvanceConditions`](#serializablestepadvanceconditionandadvanceconditions).

#### `SerializableStepAdvanceCondition.AndAdvanceConditions`

*Class* · `uniffi/ferrostar/ferrostar.kt`

```kotlin
data class AndAdvanceConditions(val conditions: List<uniffi.ferrostar.SerializableStepAdvanceCondition>) : SerializableStepAdvanceCondition()
```

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `val conditions` | `List<uniffi.ferrostar.SerializableStepAdvanceCondition>` |  |  |

#### `SerializableStepAdvanceCondition.DistanceEntryAndSnappedExit`

*Class* · `uniffi/ferrostar/ferrostar.kt`

```kotlin
data class DistanceEntryAndSnappedExit(val distanceToEndOfStep: UShort, val distanceAfterEndStep: UShort, val minimumHorizontalAccuracy: UShort, val hasReachedEndOfCurrentStep: Boolean) : SerializableStepAdvanceCondition()
```

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `val distanceToEndOfStep` | `UShort` |  |  |
| `val distanceAfterEndStep` | `UShort` |  |  |
| `val minimumHorizontalAccuracy` | `UShort` |  |  |
| `val hasReachedEndOfCurrentStep` | `Boolean` |  |  |

#### `SerializableStepAdvanceCondition.DistanceEntryExit`

*Class* · `uniffi/ferrostar/ferrostar.kt`

```kotlin
data class DistanceEntryExit(val distanceToEndOfStep: UShort, val distanceAfterEndStep: UShort, val minimumHorizontalAccuracy: UShort, val hasReachedEndOfCurrentStep: Boolean) : SerializableStepAdvanceCondition()
```

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `val distanceToEndOfStep` | `UShort` |  |  |
| `val distanceAfterEndStep` | `UShort` |  |  |
| `val minimumHorizontalAccuracy` | `UShort` |  |  |
| `val hasReachedEndOfCurrentStep` | `Boolean` |  |  |

#### `SerializableStepAdvanceCondition.DistanceFromStep`

*Class* · `uniffi/ferrostar/ferrostar.kt`

```kotlin
data class DistanceFromStep(val distance: UShort, val minimumHorizontalAccuracy: UShort, val calculationPolicy: uniffi.ferrostar.DeviationCalculationPolicy) : SerializableStepAdvanceCondition()
```

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `val distance` | `UShort` |  |  |
| `val minimumHorizontalAccuracy` | `UShort` |  |  |
| `val calculationPolicy` | `uniffi.ferrostar.DeviationCalculationPolicy` |  |  |

#### `SerializableStepAdvanceCondition.DistanceToEndOfStep`

*Class* · `uniffi/ferrostar/ferrostar.kt`

```kotlin
data class DistanceToEndOfStep(val distance: UShort, val minimumHorizontalAccuracy: UShort) : SerializableStepAdvanceCondition()
```

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `val distance` | `UShort` |  |  |
| `val minimumHorizontalAccuracy` | `UShort` |  |  |

#### `SerializableStepAdvanceCondition.OrAdvanceConditions`

*Class* · `uniffi/ferrostar/ferrostar.kt`

```kotlin
data class OrAdvanceConditions(val conditions: List<uniffi.ferrostar.SerializableStepAdvanceCondition>) : SerializableStepAdvanceCondition()
```

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `val conditions` | `List<uniffi.ferrostar.SerializableStepAdvanceCondition>` |  |  |

#### `SimulationException`

*Class* · `uniffi/ferrostar/ferrostar.kt`

```kotlin
sealed class SimulationException: Exception()
```

Nested: [`SimulationException.PolylineException`](#simulationexceptionpolylineexception), [`SimulationException.NotEnoughPoints`](#simulationexceptionnotenoughpoints), [`SimulationException.ErrorHandler`](#simulationexceptionerrorhandler).

#### `SimulationException.NotEnoughPoints`

*Class* · `uniffi/ferrostar/ferrostar.kt`

```kotlin
class NotEnoughPoints() : SimulationException()
```

Not enough points in the input.

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `val message` |  |  |  |

#### `SimulationException.PolylineException`

*Class* · `uniffi/ferrostar/ferrostar.kt`

```kotlin
class PolylineException(val error: String) : SimulationException()
```

Errors decoding the polyline string.

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `val error` | `String` |  |  |
| `val message` |  |  |  |

#### `Speed`

*Class* · `uniffi/ferrostar/ferrostar.kt`

```kotlin
data class Speed (var value: Double, var accuracy: Double?)
```

The speed of the user from the location provider.

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `var value` | `Double` |  |  |
| `var accuracy` | `Double?` |  |  |

#### `SpokenInstruction`

*Class* · `uniffi/ferrostar/ferrostar.kt`

```kotlin
data class SpokenInstruction (var text: String, var ssml: String?, var triggerDistanceBeforeManeuver: Double, var utteranceId: Uuid)
```

An instruction that can be synthesized using a TTS engine to announce an upcoming maneuver.

Note that these do not have any locale information attached.

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `var text` | `String` |  |  |
| `var ssml` | `String?` |  |  |
| `var triggerDistanceBeforeManeuver` | `Double` |  |  |
| `var utteranceId` | `Uuid` |  |  |

#### `StepAdvanceCondition`

*Class* · `uniffi/ferrostar/ferrostar.kt`

```kotlin
open class StepAdvanceCondition: Disposable, AutoCloseable, StepAdvanceConditionInterface
```

When implementing custom step advance logic, this trait allows you to define
whether the condition should advance to the next condition, the next step or not.

At the moment, these must be implemented in Rust.

**Initialisers and methods**

- `override fun destroy()`
- `override fun close()`
- `override fun shouldAdvanceStep(tripState: TripState): StepAdvanceResult`: This callback method is used by a step advance condition to receive step updates. The step advance condition can choose based on its outcome and internal state whether to advance to the next step or not.
- `override fun newInstance(): StepAdvanceCondition`: Creates a clean instance of this condition with the same configuration but reset state. This is used by composite conditions (Or/And) to ensure proper state isolation when any condition triggers advancement. **Implementation Requirements:** - **Stateless conditions**: Return a copy/clone of self - **Stateful conditions**: Return a new instance with initial state but preserve configuration parameters - **Composite conditions**: Recursively create fresh instances of all nested conditions This method prevents the state leakage bugs that can cause rapid step advancement and jumping behavior in navigation.

#### `StepAdvanceResult`

*Class* · `uniffi/ferrostar/ferrostar.kt`

```kotlin
data class StepAdvanceResult (var shouldAdvance: Boolean, var nextIteration: StepAdvanceCondition): Disposable
```

The step advance result is produced on every iteration of the navigation state machine and
used by the navigation to build a new [`NavState`](super::NavState) instance for that update.

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `var shouldAdvance` | `Boolean` |  |  |
| `var nextIteration` | `StepAdvanceCondition` |  |  |

#### `TripProgress`

*Class* · `uniffi/ferrostar/ferrostar.kt`

```kotlin
data class TripProgress (var distanceToNextManeuver: Double, var distanceRemaining: Double, var durationRemaining: Double)
```

High-level state describing progress through a route.

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `var distanceToNextManeuver` | `Double` |  |  |
| `var distanceRemaining` | `Double` |  |  |
| `var durationRemaining` | `Double` |  |  |

#### `TripState`

*Class* · `uniffi/ferrostar/ferrostar.kt`

```kotlin
sealed class TripState
```

The state of a navigation session.

This is produced by [`NavigationController`](super::NavigationController) methods
including [`get_initial_state`](super::NavigationController::get_initial_state)
and [`update_user_location`](super::NavigationController::update_user_location).

Nested: [`TripState.Idle`](#tripstateidle), [`TripState.Navigating`](#tripstatenavigating), [`TripState.Complete`](#tripstatecomplete).

#### `TripState.Complete`

*Class* · `uniffi/ferrostar/ferrostar.kt`

```kotlin
data class Complete(val userLocation: uniffi.ferrostar.UserLocation, val summary: uniffi.ferrostar.TripSummary) : TripState()
```

The navigation controller has reached the end of the trip.

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `val userLocation` | `uniffi.ferrostar.UserLocation` |  |  |
| `val summary` | `uniffi.ferrostar.TripSummary` |  |  |

#### `TripState.Idle`

*Class* · `uniffi/ferrostar/ferrostar.kt`

```kotlin
data class Idle(val userLocation: uniffi.ferrostar.UserLocation?) : TripState()
```

The navigation controller is idle and there is no active trip.

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `val userLocation` | `uniffi.ferrostar.UserLocation?` |  |  |

#### `TripState.Navigating`

*Class* · `uniffi/ferrostar/ferrostar.kt`

```kotlin
data class Navigating(val currentStepGeometryIndex: ULong?, val userLocation: uniffi.ferrostar.UserLocation, val snappedUserLocation: uniffi.ferrostar.UserLocation, val remainingSteps: List<uniffi.ferrostar.RouteStep>, val remainingWaypoints: List<uniffi.ferrostar.Waypoint>, val progress: uniffi.ferrostar.TripProgress, val summary: uniffi.ferrostar.TripSummary, val deviation: uniffi.ferrostar.RouteDeviation, val visualInstruction: uniffi.ferrostar.VisualInstruction?, val spokenInstruction: uniffi.ferrostar.SpokenInstruction?, val annotationJson: String?) : TripState()
```

The navigation controller is actively navigating a trip.

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `val currentStepGeometryIndex` | `ULong?` |  |  |
| `val userLocation` | `uniffi.ferrostar.UserLocation` |  |  |
| `val snappedUserLocation` | `uniffi.ferrostar.UserLocation` |  |  |
| `val remainingSteps` | `List<uniffi.ferrostar.RouteStep>` |  |  |
| `val remainingWaypoints` | `List<uniffi.ferrostar.Waypoint>` |  |  |
| `val progress` | `uniffi.ferrostar.TripProgress` |  |  |
| `val summary` | `uniffi.ferrostar.TripSummary` |  |  |
| `val deviation` | `uniffi.ferrostar.RouteDeviation` |  |  |
| `val visualInstruction` | `uniffi.ferrostar.VisualInstruction?` |  |  |
| `val spokenInstruction` | `uniffi.ferrostar.SpokenInstruction?` |  |  |
| `val annotationJson` | `String?` |  |  |

#### `TripSummary`

*Class* · `uniffi/ferrostar/ferrostar.kt`

```kotlin
data class TripSummary (var distanceTraveled: Double, var snappedDistanceTraveled: Double, var startedAt: UtcDateTime, var endedAt: UtcDateTime?)
```

Information pertaining to the user's full navigation trip. This includes
simple stats like total duration and distance.

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `var distanceTraveled` | `Double` |  |  |
| `var snappedDistanceTraveled` | `Double` |  |  |
| `var startedAt` | `UtcDateTime` |  |  |
| `var endedAt` | `UtcDateTime?` |  |  |

#### `UserLocation`

*Class* · `uniffi/ferrostar/ferrostar.kt`

```kotlin
data class UserLocation (var coordinates: GeographicCoordinate, var horizontalAccuracy: Double, var courseOverGround: CourseOverGround?, var timestamp: java.time.Instant, var speed: Speed?)
```

The location of the user that is navigating.

In addition to coordinates, this includes estimated accuracy and course information,
which can influence navigation logic and UI.

NOTE: Heading is absent on purpose.
Heading updates are not related to a change in the user's location.

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `var coordinates` | `GeographicCoordinate` |  |  |
| `var horizontalAccuracy` | `Double` |  |  |
| `var courseOverGround` | `CourseOverGround?` |  |  |
| `var timestamp` | `java.time.Instant` |  |  |
| `var speed` | `Speed?` |  |  |

#### `ValhallaLocationSearchFilter`

*Class* · `uniffi/ferrostar/ferrostar.kt`

```kotlin
data class ValhallaLocationSearchFilter (var excludeTunnel: Boolean? = null, var excludeBridge: Boolean? = null, var excludeTolls: Boolean? = null, var excludeFerry: Boolean? = null, var excludeRamp: Boolean? = null, var excludeClosures: Boolean? = null, var minRoadClass: ValhallaRoadClass? = null, var maxRoadClass: ValhallaRoadClass? = null, var level: Float? = null)
```

A set of optional filters to exclude candidate edges based on their attributes.

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `var excludeTunnel` | `Boolean?` | `null` |  |
| `var excludeBridge` | `Boolean?` | `null` |  |
| `var excludeTolls` | `Boolean?` | `null` |  |
| `var excludeFerry` | `Boolean?` | `null` |  |
| `var excludeRamp` | `Boolean?` | `null` |  |
| `var excludeClosures` | `Boolean?` | `null` |  |
| `var minRoadClass` | `ValhallaRoadClass?` | `null` |  |
| `var maxRoadClass` | `ValhallaRoadClass?` | `null` |  |
| `var level` | `Float?` | `null` |  |

#### `ValhallaWaypointProperties`

*Class* · `uniffi/ferrostar/ferrostar.kt`

```kotlin
data class ValhallaWaypointProperties (var heading: UShort? = null, var headingTolerance: UShort? = null, var minimumReachability: UShort? = null, var radius: UShort? = null, var preferredSide: ValhallaWaypointPreferredSide? = null, var displayCoordinate: GeographicCoordinate? = null, var searchCutoff: UInt? = null, var nodeSnapTolerance: UShort? = null, var streetSideTolerance: UShort? = null, var streetSideMaxDistance: UShort? = null, var streetSideCutoff: ValhallaRoadClass? = null, var searchFilter: ValhallaLocationSearchFilter? = null, var allowUturns: Boolean? = null, var name: String? = null)
```

Waypoint properties supported by Valhalla servers.

Our docstrings are short here, since Valhalla is the final authority.
Refer to <https://valhalla.github.io/valhalla/api/turn-by-turn/api-reference/#locations>
for more details, including default values.
Other Valhalla-based APIs such as Stadia Maps or Mapbox may have slightly different defaults.
Refer to your vendor's documentation when in doubt.

NOTE: Waypoint properties will NOT currently be echoed back in OSRM format,
so these are sent to the server one time.

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `var heading` | `UShort?` | `null` |  |
| `var headingTolerance` | `UShort?` | `null` |  |
| `var minimumReachability` | `UShort?` | `null` |  |
| `var radius` | `UShort?` | `null` |  |
| `var preferredSide` | `ValhallaWaypointPreferredSide?` | `null` |  |
| `var displayCoordinate` | `GeographicCoordinate?` | `null` |  |
| `var searchCutoff` | `UInt?` | `null` |  |
| `var nodeSnapTolerance` | `UShort?` | `null` |  |
| `var streetSideTolerance` | `UShort?` | `null` |  |
| `var streetSideMaxDistance` | `UShort?` | `null` |  |
| `var streetSideCutoff` | `ValhallaRoadClass?` | `null` |  |
| `var searchFilter` | `ValhallaLocationSearchFilter?` | `null` |  |
| `var allowUturns` | `Boolean?` | `null` |  |
| `var name` | `String?` | `null` |  |

#### `VisualInstruction`

*Class* · `uniffi/ferrostar/ferrostar.kt`

```kotlin
data class VisualInstruction (var primaryContent: VisualInstructionContent, var secondaryContent: VisualInstructionContent?, var subContent: VisualInstructionContent?, var triggerDistanceBeforeManeuver: Double)
```

An instruction for visual display (usually as banners) at a specific point along a [`RouteStep`](#routestep).

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `var primaryContent` | `VisualInstructionContent` |  |  |
| `var secondaryContent` | `VisualInstructionContent?` |  |  |
| `var subContent` | `VisualInstructionContent?` |  |  |
| `var triggerDistanceBeforeManeuver` | `Double` |  |  |

#### `VisualInstructionContent`

*Class* · `uniffi/ferrostar/ferrostar.kt`

```kotlin
data class VisualInstructionContent (var text: String, var maneuverType: ManeuverType?, var maneuverModifier: ManeuverModifier?, var roundaboutExitDegrees: UShort?, var laneInfo: List<LaneInfo>?, var exitNumbers: List<String>)
```

The content of a visual instruction.

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `var text` | `String` |  |  |
| `var maneuverType` | `ManeuverType?` |  |  |
| `var maneuverModifier` | `ManeuverModifier?` |  |  |
| `var roundaboutExitDegrees` | `UShort?` |  |  |
| `var laneInfo` | `List<LaneInfo>?` |  |  |
| `var exitNumbers` | `List<String>` |  |  |

#### `Waypoint`

*Class* · `uniffi/ferrostar/ferrostar.kt`

```kotlin
data class Waypoint (var coordinate: GeographicCoordinate, var kind: WaypointKind, var properties: ByteArray? = null)
```

A waypoint along a route.

Within the context of Ferrostar, a route request consists of exactly one [`UserLocation`](#userlocation)
and at least one [`Waypoint`](#waypoint). The route starts from the user's location (which may
contain other useful information like their current course for the [`crate::routing_adapters::RouteRequestGenerator`]
to use) and proceeds through one or more waypoints.

Waypoints are used during route calculation, are tracked throughout the lifecycle of a trip,
and are used for recalculating when the user deviates from the expected route.

Note that support for properties beyond basic geographic coordinates varies by routing engine.

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `var coordinate` | `GeographicCoordinate` |  |  |
| `var kind` | `WaypointKind` |  |  |
| `var properties` | `ByteArray?` | `null` |  |

#### `WaypointAdvanceMode`

*Class* · `uniffi/ferrostar/ferrostar.kt`

```kotlin
sealed class WaypointAdvanceMode
```

Controls when a waypoint should be marked as complete.

While a route may consist of thousands of points, waypoints are special.
A simple trip will have only one waypoint: the final destination.
A more complex trip may have several intermediate stops.
Just as the navigation state keeps track of which steps remain in the route,
it also tracks which waypoints are still remaining.

Tracking waypoints enables Ferrostar to reroute users when they stray off the route line.
The waypoint advance mode specifies how the framework decides
that a waypoint has been visited (and is removed from the list).

NOTE: Advancing to the next *step* and advancing to the next *waypoint*
are separate processes.
This will not normally cause any issues, but keep in mind that
manually advancing to the next step does not *necessarily* imply
that the waypoint will be marked as complete!

Nested: [`WaypointAdvanceMode.WaypointWithinRange`](#waypointadvancemodewaypointwithinrange), [`WaypointAdvanceMode.WaypointAlongAdvancingStep`](#waypointadvancemodewaypointalongadvancingstep).

#### `WaypointAdvanceMode.WaypointAlongAdvancingStep`

*Class* · `uniffi/ferrostar/ferrostar.kt`

```kotlin
data class WaypointAlongAdvancingStep(val v1: Double) : WaypointAdvanceMode()
```

Advance when a waypoint is within a certain range of meters of any point on the advancing step.

This condition considers the step being advanced, not the user's location. As a result,
it can recover when your step advance conditions allow the user to skip forward on the route.

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `val v1` | `Double` |  |  |

#### `WaypointAdvanceMode.WaypointWithinRange`

*Class* · `uniffi/ferrostar/ferrostar.kt`

```kotlin
data class WaypointWithinRange(val v1: Double) : WaypointAdvanceMode()
```

Advance when the waypoint is within a certain range of meters from the user's location.

This condition is potentially more rigorous, requiring the user to actually visit within
a range of every waypoint regardless of step advance.

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `val v1` | `Double` |  |  |

#### `WellKnownRouteProvider`

*Class* · `uniffi/ferrostar/ferrostar.kt`

```kotlin
sealed class WellKnownRouteProvider
```

Configurations for built-in route providers.

Nested: [`WellKnownRouteProvider.Valhalla`](#wellknownrouteprovidervalhalla), [`WellKnownRouteProvider.GraphHopper`](#wellknownrouteprovidergraphhopper).

#### `WellKnownRouteProvider.GraphHopper`

*Class* · `uniffi/ferrostar/ferrostar.kt`

```kotlin
data class GraphHopper(val endpointUrl: String, val profile: String, val locale: String, val voiceUnits: uniffi.ferrostar.GraphHopperVoiceUnits, val optionsJson: String? = null) : WellKnownRouteProvider()
```

A GraphHopper-based routing API.

[GraphHopper](https://www.graphhopper.com/) offers a commercially supported API.
You can also substitute any other URL to use a self-hosted server.

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `val endpointUrl` | `String` |  |  |
| `val profile` | `String` |  |  |
| `val locale` | `String` |  |  |
| `val voiceUnits` | `uniffi.ferrostar.GraphHopperVoiceUnits` |  |  |
| `val optionsJson` | `String?` | `null` |  |

#### `WellKnownRouteProvider.Valhalla`

*Class* · `uniffi/ferrostar/ferrostar.kt`

```kotlin
data class Valhalla(val endpointUrl: String, val profile: String, val optionsJson: String? = null) : WellKnownRouteProvider()
```

A Valhalla-based routing API.

[Stadia Maps](https://docs.stadiamaps.com/) offers a commercially supported API.
You can also substitute any URL to use a self-hosted server.

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `val endpointUrl` | `String` |  |  |
| `val profile` | `String` |  |  |
| `val optionsJson` | `String?` | `null` |  |

### Enums (uniffi.ferrostar)

[`BlockedLane`](#blockedlane), [`CourseFiltering`](#coursefiltering), [`DeviationCalculationPolicy`](#deviationcalculationpolicy), [`DrivingSide`](#drivingside), [`GraphHopperVoiceUnits`](#graphhoppervoiceunits), [`Impact`](#impact), [`IncidentType`](#incidenttype), [`ManeuverModifier`](#maneuvermodifier), [`ManeuverType`](#maneuvertype), [`ValhallaRoadClass`](#valhallaroadclass), [`ValhallaWaypointPreferredSide`](#valhallawaypointpreferredside), [`WaypointKind`](#waypointkind)

#### `BlockedLane`

*Enum* · `uniffi/ferrostar/ferrostar.kt`

```kotlin
enum class BlockedLane
```

The lane type blocked by the incident.

| Case | Description |
| --- | --- |
| `LEFT` |  |
| `LEFT_CENTER` |  |
| `LEFT_TURN_LANE` |  |
| `CENTER` |  |
| `RIGHT` |  |
| `RIGHT_CENTER` |  |
| `RIGHT_TURN_LANE` |  |
| `HOV` |  |

#### `CourseFiltering`

*Enum* · `uniffi/ferrostar/ferrostar.kt`

```kotlin
enum class CourseFiltering
```

Controls filtering/post-processing of user course by the [`NavigationController`](#navigationcontroller).

| Case | Description |
| --- | --- |
| `SNAP_TO_ROUTE` | Snap the user's course to the current step's linestring using the next index in the step's geometry. |
| `RAW` | Use the raw course as reported by the location provider with no processing. |

#### `DeviationCalculationPolicy`

*Enum* · `uniffi/ferrostar/ferrostar.kt`

```kotlin
enum class DeviationCalculationPolicy
```

Controls when a deviation-aware step-advance condition is allowed to evaluate,
based on the user's current
[`RouteDeviation`](crate::deviation_detection::RouteDeviation) status.

| Case | Description |
| --- | --- |
| `ALWAYS` | Always evaluate, regardless of deviation status. |
| `WHILE_ON_ROUTE` | Evaluate while the user is still somewhere on the route polyline (current step or any future step), but suspend if the user is completely off the route. |
| `WHILE_ON_CURRENT_STEP` | Evaluate only while the user is within an acceptable deviation of the current step's polyline. Suspend on any deviation (whether off-step-but-on-route, or completely off-route). |

#### `DrivingSide`

*Enum* · `uniffi/ferrostar/ferrostar.kt`

```kotlin
enum class DrivingSide
```

Which side of the road traffic drives on.

This is needed by consumers like Android Auto to determine whether
a roundabout should be rendered as clockwise (right-hand traffic)
or counterclockwise (left-hand traffic).

| Case | Description |
| --- | --- |
| `LEFT` |  |
| `RIGHT` |  |

#### `GraphHopperVoiceUnits`

*Enum* · `uniffi/ferrostar/ferrostar.kt`

```kotlin
enum class GraphHopperVoiceUnits
```

| Case | Description |
| --- | --- |
| `METRIC` |  |
| `IMPERIAL` |  |

#### `Impact`

*Enum* · `uniffi/ferrostar/ferrostar.kt`

```kotlin
enum class Impact
```

The impact of the incident that has occurred.

| Case | Description |
| --- | --- |
| `UNKNOWN` |  |
| `CRITICAL` |  |
| `MAJOR` |  |
| `MINOR` |  |
| `LOW` |  |

#### `IncidentType`

*Enum* · `uniffi/ferrostar/ferrostar.kt`

```kotlin
enum class IncidentType
```

The type of incident that has occurred.

| Case | Description |
| --- | --- |
| `ACCIDENT` |  |
| `CONGESTION` |  |
| `CONSTRUCTION` |  |
| `DISABLED_VEHICLE` |  |
| `LANE_RESTRICTION` |  |
| `MASS_TRANSIT` |  |
| `MISCELLANEOUS` |  |
| `OTHER_NEWS` |  |
| `PLANNED_EVENT` |  |
| `ROAD_CLOSURE` |  |
| `ROAD_HAZARD` |  |
| `WEATHER` |  |

#### `ManeuverModifier`

*Enum* · `uniffi/ferrostar/ferrostar.kt`

```kotlin
enum class ManeuverModifier
```

Additional information to further specify a [`ManeuverType`](#maneuvertype).

| Case | Description |
| --- | --- |
| `U_TURN` |  |
| `SHARP_RIGHT` |  |
| `RIGHT` |  |
| `SLIGHT_RIGHT` |  |
| `STRAIGHT` |  |
| `SLIGHT_LEFT` |  |
| `LEFT` |  |
| `SHARP_LEFT` |  |

#### `ManeuverType`

*Enum* · `uniffi/ferrostar/ferrostar.kt`

```kotlin
enum class ManeuverType
```

The broad class of maneuver to perform.

This is usually combined with [`ManeuverModifier`](#maneuvermodifier) in [`VisualInstructionContent`](#visualinstructioncontent).

| Case | Description |
| --- | --- |
| `TURN` |  |
| `NEW_NAME` |  |
| `DEPART` |  |
| `ARRIVE` |  |
| `MERGE` |  |
| `ON_RAMP` |  |
| `OFF_RAMP` |  |
| `FORK` |  |
| `END_OF_ROAD` |  |
| `CONTINUE` |  |
| `ROUNDABOUT` |  |
| `ROTARY` |  |
| `ROUNDABOUT_TURN` |  |
| `NOTIFICATION` |  |
| `EXIT_ROUNDABOUT` |  |
| `EXIT_ROTARY` |  |

#### `ValhallaRoadClass`

*Enum* · `uniffi/ferrostar/ferrostar.kt`

```kotlin
enum class ValhallaRoadClass
```

A road class in the Valhalla taxonomy.

These are ordered from highest (fastest travel speed) to lowest.

| Case | Description |
| --- | --- |
| `MOTORWAY` |  |
| `TRUNK` |  |
| `PRIMARY` |  |
| `SECONDARY` |  |
| `TERTIARY` |  |
| `UNCLASSIFIED` |  |
| `RESIDENTIAL` |  |
| `SERVICE_OTHER` |  |

#### `ValhallaWaypointPreferredSide`

*Enum* · `uniffi/ferrostar/ferrostar.kt`

```kotlin
enum class ValhallaWaypointPreferredSide
```

Specifies a preferred side for departing from / arriving at a location.

Examples:
- Germany drives on the right side of the road. A value of `same` will only allow leaving
or arriving at a location such that it is on your right.
- Australia drives on the left side of the road. Passing a value of `same` will only allow
leaving or arriving at a location such that it is on your left.

| Case | Description |
| --- | --- |
| `SAME` | You must depart from or arrive at the location on the _same_ side as you drive. |
| `OPPOSITE` | You must depart from or arrive at the location on the _opposite_ side as you drive. |
| `EITHER` | No preference; you can depart or arrive from any direction. |

#### `WaypointKind`

*Enum* · `uniffi/ferrostar/ferrostar.kt`

```kotlin
enum class WaypointKind
```

Describes characteristics of the waypoint for routing purposes.

| Case | Description |
| --- | --- |
| `BREAK` | Starts or ends a leg of the trip. Most routing engines will generate arrival and departure instructions. Some routing engines do not support multi-leg routes, so an intermediate break may behave like a [`WaypointKind::Via`]. |
| `VIA` | A waypoint that is simply passed through, but will not have any arrival or departure instructions. |

### Interfaces (uniffi.ferrostar)

[`AndAdvanceConditionsInterface`](#andadvanceconditionsinterface), [`DistanceEntryAndExitConditionInterface`](#distanceentryandexitconditioninterface), [`DistanceEntryAndSnappedExitConditionInterface`](#distanceentryandsnappedexitconditioninterface), [`DistanceFromStepConditionInterface`](#distancefromstepconditioninterface), [`DistanceToEndOfStepConditionInterface`](#distancetoendofstepconditioninterface), [`ManualStepConditionInterface`](#manualstepconditioninterface), [`NavigationCache`](#navigationcache), [`NavigationControllerInterface`](#navigationcontrollerinterface), [`NavigationObserver`](#navigationobserver), [`NavigationRecorderInterface`](#navigationrecorderinterface), [`NavigationReplayInterface`](#navigationreplayinterface), [`NavigationSessionCacheInterface`](#navigationsessioncacheinterface), [`NavigationSessionInterface`](#navigationsessioninterface), [`NavigatorInterface`](#navigatorinterface), [`OrAdvanceConditionsInterface`](#oradvanceconditionsinterface), [`RouteAdapterInterface`](#routeadapterinterface), [`RouteDeviationDetector`](#routedeviationdetector), [`RouteRequestGenerator`](#routerequestgenerator), [`RouteResponseParser`](#routeresponseparser), [`StepAdvanceConditionInterface`](#stepadvanceconditioninterface)

#### `AndAdvanceConditionsInterface`

*Interface* · `uniffi/ferrostar/ferrostar.kt`

```kotlin
public interface AndAdvanceConditionsInterface
```

Advance if all of the conditions are met (AND).

#### `DistanceEntryAndExitConditionInterface`

*Interface* · `uniffi/ferrostar/ferrostar.kt`

```kotlin
public interface DistanceEntryAndExitConditionInterface
```

A stateful condition that requires the user to reach the end of the step then proceed past it to advance.

#### `DistanceEntryAndSnappedExitConditionInterface`

*Interface* · `uniffi/ferrostar/ferrostar.kt`

```kotlin
public interface DistanceEntryAndSnappedExitConditionInterface
```

A stateful condition that requires the user to reach the end of the step then proceed past it to advance.

This variant uses route snapping (snapping to the combined current+next step geometry) for the exit check,
making it more robust for pedestrian/hiking navigation where users may walk on the opposite side of the street
or wander around the optimal path. The route-snapped exit check prevents premature advancement while still
allowing natural pedestrian movement patterns.
The exit distance is measured from the current step to the route-snapped position.

#### `DistanceFromStepConditionInterface`

*Interface* · `uniffi/ferrostar/ferrostar.kt`

```kotlin
public interface DistanceFromStepConditionInterface
```

Advances once the user is at least [`Self::distance`] meters from the current step's polyline.

This results in *delayed* advance,
but is more robust to spurious / unwanted step changes in scenarios including
self-intersecting routes (sudden jumps to the next step)
and pauses at intersections (advancing too soon before the maneuver is complete).

NOTE! This may be less robust to things like short steps, out-and-backs, and U-turns,
where this may eagerly exit a current step before the user has traversed it
if the start of the step is within range of the end.

#### `DistanceToEndOfStepConditionInterface`

*Interface* · `uniffi/ferrostar/ferrostar.kt`

```kotlin
public interface DistanceToEndOfStepConditionInterface
```

Automatically advances when the user's location is close enough to the end of the step.

This results in an eager advance where the user will jump to the next step when the
condition is met.

#### `ManualStepConditionInterface`

*Interface* · `uniffi/ferrostar/ferrostar.kt`

```kotlin
public interface ManualStepConditionInterface
```

Never advances to the next step automatically;
requires calling [`NavigationController::advance_to_next_step`](super::NavigationController::advance_to_next_step).

You can use this to implement custom behaviors in external code.

#### `NavigationCache`

*Interface* · `uniffi/ferrostar/ferrostar.kt`

```kotlin
public interface NavigationCache
```

**Initialisers and methods**

- `fun save(record: ByteArray)`
- `fun load(): ByteArray?`
- `fun delete()`

#### `NavigationControllerInterface`

*Interface* · `uniffi/ferrostar/ferrostar.kt`

```kotlin
public interface NavigationControllerInterface
```

Manages the navigation lifecycle through a route,
returning an updated state given inputs like user location.

Notes for implementing a new platform:
- A controller is bound to a single route; if you want recalculation, create a new instance.
- This is a pure type (no interior mutability), so a core function of your platform code is responsibly managing mutable state.

#### `NavigationObserver`

*Interface* · `uniffi/ferrostar/ferrostar.kt`

```kotlin
public interface NavigationObserver
```

**Initialisers and methods**

- `fun onGetInitialState(state: NavState)`
- `fun onUserLocationUpdate(location: UserLocation, state: NavState)`
- `fun onAdvanceToNextStep(state: NavState)`
- `fun onRouteAvailable(route: Route)`

#### `NavigationRecorderInterface`

*Interface* · `uniffi/ferrostar/ferrostar.kt`

```kotlin
public interface NavigationRecorderInterface
```

**Initialisers and methods**

- `fun getEvents(): List<NavigationRecordingEvent>`
- `fun getRecordingJson(): String`
- `fun onAdvanceToNextStep(state: NavState)`
- `fun onGetInitialState(state: NavState)`
- `fun onRouteAvailable(route: Route)`
- `fun onUserLocationUpdate(location: UserLocation, state: NavState)`

#### `NavigationReplayInterface`

*Interface* · `uniffi/ferrostar/ferrostar.kt`

```kotlin
public interface NavigationReplayInterface
```

A wrapper around `NavigationRecording` to facilitate replaying the event stream.

#### `NavigationSessionCacheInterface`

*Interface* · `uniffi/ferrostar/ferrostar.kt`

```kotlin
public interface NavigationSessionCacheInterface
```

**Initialisers and methods**

- `fun canResume(): Boolean`: Check if the navigation session can be resumed.
- `fun load(): NavigationSessionSnapshot?`: Load the navigation session record from the cache if it exists and is not stale.
- `fun onAdvanceToNextStep(state: NavState)`
- `fun onGetInitialState(state: NavState)`
- `fun onRouteAvailable(route: Route)`
- `fun onUserLocationUpdate(location: UserLocation, state: NavState)`

#### `NavigationSessionInterface`

*Interface* · `uniffi/ferrostar/ferrostar.kt`

```kotlin
public interface NavigationSessionInterface
```

**Initialisers and methods**

- `fun advanceToNextStep(state: NavState): NavState`
- `fun getInitialState(location: UserLocation): NavState`
- `fun route(): Route`
- `fun updateUserLocation(location: UserLocation, state: NavState): NavState`

#### `NavigatorInterface`

*Interface* · `uniffi/ferrostar/ferrostar.kt`

```kotlin
public interface NavigatorInterface
```

Core interface for navigation functionalities.

This trait defines the essential operations for a navigation state manager.
This lets us build additional layers (e.g. event logging)
around [`NavigationController`](#navigationcontroller) in a composable manner.

**Initialisers and methods**

- `fun route(): Route`
- `fun getInitialState(location: UserLocation): NavState`
- `fun advanceToNextStep(state: NavState): NavState`
- `fun updateUserLocation(location: UserLocation, state: NavState): NavState`

#### `OrAdvanceConditionsInterface`

*Interface* · `uniffi/ferrostar/ferrostar.kt`

```kotlin
public interface OrAdvanceConditionsInterface
```

Advance if any of the conditions are met (OR).

This is ideal for short circuit type advance conditions.

E.g. you may have:
1. A short circuit detecting if the user has exceeded a large distance from the current step.
2. A default advance behavior.

#### `RouteAdapterInterface`

*Interface* · `uniffi/ferrostar/ferrostar.kt`

```kotlin
public interface RouteAdapterInterface
```

The route adapter bridges between the common core and a routing backend where interaction takes place
over a generic request/response flow (typically over a network;
local/offline routers **do not use this object** as the interaction patterns are different).

This is essentially the composite of the [`RouteRequestGenerator`](#routerequestgenerator) and [`RouteResponseParser`](#routeresponseparser)
traits, but it provides one further level of abstraction which is helpful to consumers.
As there is no way to signal compatibility between request generators and response parsers,
the [`RouteAdapter`](#routeadapter) provides convenience constructors which take the guesswork out of it,
while still leaving consumers free to implement one or both halves.

In the future, we may provide additional methods or conveniences, and this
indirection leaves the design open to such changes without necessarily breaking source
compatibility.
One such possible extension would be the ability to fetch more detailed attributes in real time.
This is supported by the Valhalla stack, among others.

Ideas  welcome re: how to signal compatibility between request generators and response parsers.
I don't think we can do this in the type system, since one of the reasons for the split design
is modularity, including the possibility of user-provided implementations, and these will not
always be of a "known" type to the Rust side.

**Initialisers and methods**

- `fun generateRequest(userLocation: UserLocation, waypoints: List<Waypoint>): RouteRequest`
- `fun parseResponse(response: ByteArray): List<Route>`

#### `RouteDeviationDetector`

*Interface* · `uniffi/ferrostar/ferrostar.kt`

```kotlin
public interface RouteDeviationDetector
```

A custom deviation detector (for extending the behavior of [`RouteDeviationTracking`](#routedeviationtracking)).

This allows for arbitrarily complex implementations when the provided ones are not enough.
For example, detecting that the user is proceeding the wrong direction by keeping a ring buffer
of recent locations, or perform local map matching.

**Initialisers and methods**

- `fun checkRouteDeviation(route: Route, tripState: TripState): RouteDeviation`: Determines whether the user is following the route correctly or not. NOTE: This function has a single responsibility. Side-effects like whether to recalculate a route are left to higher levels, and implementations should only be concerned with determining the facts. IMPORTANT: If you are short circuiting [`StepAdvanceCondition`](#stepadvancecondition)'s to allow skipping steps, you must always fall back to checking the deviation from the full route line.

#### `RouteRequestGenerator`

*Interface* · `uniffi/ferrostar/ferrostar.kt`

```kotlin
public interface RouteRequestGenerator
```

A trait describing any object capable of generating [`RouteRequest`](#routerequest)s.

The interface is intentionally generic. Every routing backend has its own set of
parameters, including a "profile," max travel speed, units of speed and distance, and more.
It is assumed that these properties will be set at construction time or otherwise configured
before use, so that we can keep the public interface as generic as possible.

Implementations may be either in Rust (most popular engines should eventually have Rust
glue code) or foreign code.

**Initialisers and methods**

- `fun generateRequest(userLocation: UserLocation, waypoints: List<Waypoint>): RouteRequest`: Generates a routing backend request given the set of locations. While most implementations will treat the locations as an ordered sequence, this is not guaranteed (ex: an optimized router).

#### `RouteResponseParser`

*Interface* · `uniffi/ferrostar/ferrostar.kt`

```kotlin
public interface RouteResponseParser
```

A generic interface describing any object capable of parsing a response from a routing
backend into one or more [`Route`](#route)s.

**Initialisers and methods**

- `fun parseResponse(response: ByteArray): List<Route>`: Parses a raw response from the routing backend into a route. We use a sequence of octets as a common interchange format. as this works for all currently conceivable formats (JSON, PBF, etc.).

#### `StepAdvanceConditionInterface`

*Interface* · `uniffi/ferrostar/ferrostar.kt`

```kotlin
public interface StepAdvanceConditionInterface
```

When implementing custom step advance logic, this trait allows you to define
whether the condition should advance to the next condition, the next step or not.

At the moment, these must be implemented in Rust.

**Initialisers and methods**

- `fun shouldAdvanceStep(tripState: TripState): StepAdvanceResult`: This callback method is used by a step advance condition to receive step updates. The step advance condition can choose based on its outcome and internal state whether to advance to the next step or not.
- `fun newInstance(): StepAdvanceCondition`: Creates a clean instance of this condition with the same configuration but reset state. This is used by composite conditions (Or/And) to ensure proper state isolation when any condition triggers advancement. **Implementation Requirements:** - **Stateless conditions**: Return a copy/clone of self - **Stateful conditions**: Return a new instance with initial state but preserve configuration parameters - **Composite conditions**: Recursively create fresh instances of all nested conditions This method prevents the state leakage bugs that can cause rapid step advancement and jumping behavior in navigation.

### Objects (uniffi.ferrostar)

[`InstantiationException.ErrorHandler`](#instantiationexceptionerrorhandler), [`LocationBias.None`](#locationbiasnone), [`ModelException.ErrorHandler`](#modelexceptionerrorhandler), [`ParsingException.ErrorHandler`](#parsingexceptionerrorhandler), [`RecordingException.ErrorHandler`](#recordingexceptionerrorhandler), [`RouteAdapter.Companion`](#routeadaptercompanion), [`RouteDeviation.NoDeviation`](#routedeviationnodeviation), [`RouteDeviationTracking.None`](#routedeviationtrackingnone), [`RoutingRequestGenerationException.ErrorHandler`](#routingrequestgenerationexceptionerrorhandler), [`SerializableStepAdvanceCondition.Manual`](#serializablestepadvanceconditionmanual), [`SimulationException.ErrorHandler`](#simulationexceptionerrorhandler)

#### `InstantiationException.ErrorHandler`

*Object* · `uniffi/ferrostar/ferrostar.kt`

```kotlin
companion object ErrorHandler : UniffiRustCallStatusErrorHandler<InstantiationException>
```

#### `LocationBias.None`

*Object* · `uniffi/ferrostar/ferrostar.kt`

```kotlin
object None : LocationBias()
```

No position bias - locations follow the route line exactly.

This provides "perfect" GPS behavior, useful for testing basic route following
without position uncertainty.

#### `ModelException.ErrorHandler`

*Object* · `uniffi/ferrostar/ferrostar.kt`

```kotlin
companion object ErrorHandler : UniffiRustCallStatusErrorHandler<ModelException>
```

#### `ParsingException.ErrorHandler`

*Object* · `uniffi/ferrostar/ferrostar.kt`

```kotlin
companion object ErrorHandler : UniffiRustCallStatusErrorHandler<ParsingException>
```

#### `RecordingException.ErrorHandler`

*Object* · `uniffi/ferrostar/ferrostar.kt`

```kotlin
companion object ErrorHandler : UniffiRustCallStatusErrorHandler<RecordingException>
```

#### `RouteAdapter.Companion`

*Object* · `uniffi/ferrostar/ferrostar.kt`

```kotlin
companion object
```

**Initialisers and methods**

- `@Throws(InstantiationException::class) fun fromWellKnownRouteProvider(wellKnownRouteProvider: WellKnownRouteProvider): RouteAdapter`: Creates a route adapter from a well-known provider configuration.

#### `RouteDeviation.NoDeviation`

*Object* · `uniffi/ferrostar/ferrostar.kt`

```kotlin
object NoDeviation : RouteDeviation()
```

The user is proceeding on course within the expected tolerances; everything is normal.

#### `RouteDeviationTracking.None`

*Object* · `uniffi/ferrostar/ferrostar.kt`

```kotlin
object None : RouteDeviationTracking()
```

No checks will be done, and we assume the user is always following the route.

#### `RoutingRequestGenerationException.ErrorHandler`

*Object* · `uniffi/ferrostar/ferrostar.kt`

```kotlin
companion object ErrorHandler : UniffiRustCallStatusErrorHandler<RoutingRequestGenerationException>
```

#### `SerializableStepAdvanceCondition.Manual`

*Object* · `uniffi/ferrostar/ferrostar.kt`

```kotlin
object Manual : SerializableStepAdvanceCondition()
```

#### `SimulationException.ErrorHandler`

*Object* · `uniffi/ferrostar/ferrostar.kt`

```kotlin
companion object ErrorHandler : UniffiRustCallStatusErrorHandler<SimulationException>
```

### Functions (uniffi.ferrostar)

[`advanceLocationSimulation`](#advancelocationsimulation), [`createNavigationSession`](#createnavigationsession), [`createNavigator`](#createnavigator), [`createOsrmResponseParser`](#createosrmresponseparser), [`createRouteFromOsrm`](#createroutefromosrm), [`createRouteFromOsrmRoute`](#createroutefromosrmroute), [`createValhallaRequestGenerator`](#createvalhallarequestgenerator), [`createWaypointWithValhallaProperties`](#createwaypointwithvalhallaproperties), [`getRoutePolyline`](#getroutepolyline), [`locationSimulationFromCoordinates`](#locationsimulationfromcoordinates), [`locationSimulationFromPolyline`](#locationsimulationfrompolyline), [`locationSimulationFromRoute`](#locationsimulationfromroute), [`stepAdvanceAnd`](#stepadvanceand), [`stepAdvanceDistanceEntryAndExit`](#stepadvancedistanceentryandexit), [`stepAdvanceDistanceEntryAndSnappedExit`](#stepadvancedistanceentryandsnappedexit), [`stepAdvanceDistanceFromStep`](#stepadvancedistancefromstep), [`stepAdvanceDistanceToEndOfStep`](#stepadvancedistancetoendofstep), [`stepAdvanceManual`](#stepadvancemanual), [`stepAdvanceOr`](#stepadvanceor)

#### `advanceLocationSimulation`

*Function* · `uniffi/ferrostar/ferrostar.kt`

```kotlin
fun advanceLocationSimulation(state: LocationSimulationState): LocationSimulationState
```

Returns the next simulation state based on the desired strategy.
Results of this can be thought of like a stream from a generator function.

This function is intended to be called once/second.
However, the caller may vary speed to purposefully replay at a faster rate
(ex: calling 3x per second will be a triple speed simulation).

When there are now more locations to visit, returns the same state forever.

#### `createNavigationSession`

*Function* · `uniffi/ferrostar/ferrostar.kt`

```kotlin
fun createNavigationSession(route: Route, config: NavigationControllerConfig, observers: List<NavigationObserver>): NavigationSession
```

Creates a new navigation session for the given route and configuration.

It returns an Arc-wrapped trait object implementing `Navigator`.
If `should_record` is true, it creates a controller with event recording enabled.

#### `createNavigator`

*Function* · `uniffi/ferrostar/ferrostar.kt`

```kotlin
fun createNavigator(route: Route, config: NavigationControllerConfig, shouldRecord: Boolean): Navigator
```

Creates a new navigation controller for the given route and configuration.

It returns an Arc-wrapped trait object implementing `Navigator`.
If `should_record` is true, it creates a controller with event recording enabled.

#### `createOsrmResponseParser`

*Function* · `uniffi/ferrostar/ferrostar.kt`

```kotlin
fun createOsrmResponseParser(polylinePrecision: UInt): RouteResponseParser
```

Creates a [`RouteResponseParser`](#routeresponseparser) capable of parsing OSRM responses.

This response parser is designed to be fairly flexible,
supporting both vanilla OSRM and enhanced Valhalla (ex: from Stadia Maps and Mapbox) outputs
which contain richer information like banners and voice instructions for navigation.

#### `createRouteFromOsrm`

*Function* · `uniffi/ferrostar/ferrostar.kt`

```kotlin
@Throws(ParsingException::class) fun createRouteFromOsrm(routeData: ByteArray, waypointData: ByteArray, polylinePrecision: UInt): Route
```

Creates a [`Route`](#route) from OSRM data.

This uses the same logic as the `OsrmResponseParser` and is designed to be fairly flexible,
supporting both vanilla OSRM and enhanced Valhalla (ex: from Stadia Maps and Mapbox) outputs
which contain richer information like banners and voice instructions for navigation.

#### `createRouteFromOsrmRoute`

*Function* · `uniffi/ferrostar/ferrostar.kt`

```kotlin
@Throws(ParsingException::class) fun createRouteFromOsrmRoute(routeData: ByteArray, waypoints: List<Waypoint>, polylinePrecision: UInt): Route
```

Creates a [`Route`](#route) from OSRM route data and ferrostar waypoints.

This uses the same logic as the `OsrmResponseParser` and is designed to be fairly flexible,
supporting both vanilla OSRM and enhanced Valhalla (ex: from Stadia Maps and Mapbox) outputs
which contain richer information like banners and voice instructions for navigation.

#### `createValhallaRequestGenerator`

*Function* · `uniffi/ferrostar/ferrostar.kt`

```kotlin
@Throws(InstantiationException::class) fun createValhallaRequestGenerator(endpointUrl: String, profile: String, optionsJson: String?): RouteRequestGenerator
```

Creates a [`RouteRequestGenerator`](#routerequestgenerator)
which generates requests to an arbitrary Valhalla server (using the OSRM response format).

This is provided as a convenience for use from foreign code when creating your own [`routing_adapters::RouteAdapter`].

#### `createWaypointWithValhallaProperties`

*Function* · `uniffi/ferrostar/ferrostar.kt`

```kotlin
fun createWaypointWithValhallaProperties(coordinate: GeographicCoordinate, kind: WaypointKind, properties: ValhallaWaypointProperties): Waypoint
```

A convenience helper for creating waypoints with Valhalla rich location properties.

Regrettably this must live as a top-level function unless constructors for record types lands
in `UniFFI`:
<https://github.com/mozilla/uniffi-rs/issues/1935>.

#### `getRoutePolyline`

*Function* · `uniffi/ferrostar/ferrostar.kt`

```kotlin
@Throws(ModelException::class) fun getRoutePolyline(route: Route, precision: UInt): String
```

Helper function for getting the route as an encoded polyline.

Mostly used for debugging.

#### `locationSimulationFromCoordinates`

*Function* · `uniffi/ferrostar/ferrostar.kt`

```kotlin
@Throws(SimulationException::class) fun locationSimulationFromCoordinates(coordinates: List<GeographicCoordinate>, resampleDistance: Double?, bias: LocationBias): LocationSimulationState
```

Creates a location simulation from a set of coordinates.

Optionally resamples the input line so that there is a maximum distance between points.

#### `locationSimulationFromPolyline`

*Function* · `uniffi/ferrostar/ferrostar.kt`

```kotlin
@Throws(SimulationException::class) fun locationSimulationFromPolyline(polyline: String, precision: UInt, resampleDistance: Double?, bias: LocationBias): LocationSimulationState
```

Creates a location simulation from a polyline.

Optionally resamples the input line so that there is no more than the specified maximum distance between points.

#### `locationSimulationFromRoute`

*Function* · `uniffi/ferrostar/ferrostar.kt`

```kotlin
@Throws(SimulationException::class) fun locationSimulationFromRoute(route: Route, resampleDistance: Double?, bias: LocationBias): LocationSimulationState
```

Creates a location simulation from a route.

Optionally resamples the route geometry so that there is no more than the specified maximum distance between points.

#### `stepAdvanceAnd`

*Function* · `uniffi/ferrostar/ferrostar.kt`

```kotlin
fun stepAdvanceAnd(conditions: List<StepAdvanceCondition>): StepAdvanceCondition
```

Convenience function for creating an [`AndAdvanceConditions`](#andadvanceconditions).

This composes multiple conditions together and advances to the next step if ALL of them trigger.

#### `stepAdvanceDistanceEntryAndExit`

*Function* · `uniffi/ferrostar/ferrostar.kt`

```kotlin
fun stepAdvanceDistanceEntryAndExit(distanceToEndOfStep: UShort, distanceAfterEndOfStep: UShort, minimumHorizontalAccuracy: UShort): StepAdvanceCondition
```

Convenience function for creating a [`DistanceEntryAndExitCondition`](#distanceentryandexitcondition).

Requires the user to first travel within `distance_to_end_of_step` meters of the end of the step,
and then travel at least `distance_after_end_of_step` meters away from the step geometry.
This ensures the user completes the maneuver before advancing to the next step.

#### `stepAdvanceDistanceEntryAndSnappedExit`

*Function* · `uniffi/ferrostar/ferrostar.kt`

```kotlin
fun stepAdvanceDistanceEntryAndSnappedExit(distanceToEndOfStep: UShort, distanceAfterEndOfStep: UShort, minimumHorizontalAccuracy: UShort): StepAdvanceCondition
```

Convenience function for creating a [`DistanceEntryAndSnappedExitCondition`](#distanceentryandsnappedexitcondition).

This variant uses route snapping for better handling of pedestrian/hiking navigation scenarios
where users may walk on the opposite side of the street or wander around the optimal path.
Requires the user to first travel within `distance_to_end_of_step` meters of the end of the step,
and then the route-snapped position moves `distance_after_end_of_step` meters from the current step.
The snapping to the combined route (current+next steps) prevents premature advancement.

The exit distance is automatically capped to the next step's length to prevent getting stuck on short steps.

Recommended values for pedestrian navigation: entry 20m, exit 2-5m.

#### `stepAdvanceDistanceFromStep`

*Function* · `uniffi/ferrostar/ferrostar.kt`

```kotlin
fun stepAdvanceDistanceFromStep(distance: UShort, minimumHorizontalAccuracy: UShort, calculationPolicy: DeviationCalculationPolicy): StepAdvanceCondition
```

Convenience function for creating a [`DistanceFromStepCondition`](#distancefromstepcondition).

This advances to the next step when the user is at least `distance` meters away from any point
on the current route step geometry.
Does not advance unless the reported location accuracy is `minimum_horizontal_accuracy`
meters or better.

`calculation_policy` controls when this condition is permitted to evaluate
based on the user's current route deviation status.

#### `stepAdvanceDistanceToEndOfStep`

*Function* · `uniffi/ferrostar/ferrostar.kt`

```kotlin
fun stepAdvanceDistanceToEndOfStep(distance: UShort, minimumHorizontalAccuracy: UShort): StepAdvanceCondition
```

Convenience function for creating a [`DistanceToEndOfStepCondition`](#distancetoendofstepcondition).

This advances to the next step when the user is within `distance` meters of the last point in the current route step.
Does not advance unless the reported location accuracy is `minimum_horizontal_accuracy` meters or better.

#### `stepAdvanceManual`

*Function* · `uniffi/ferrostar/ferrostar.kt`

```kotlin
fun stepAdvanceManual(): StepAdvanceCondition
```

Convenience function for creating a [`ManualStepCondition`](#manualstepcondition).

This never advances to the next step automatically.
You must manually advance to the next step programmatically using a `FerrostarCore`
platform wrapper or by calling [`super::Navigator::advance_to_next_step`] manually.

#### `stepAdvanceOr`

*Function* · `uniffi/ferrostar/ferrostar.kt`

```kotlin
fun stepAdvanceOr(conditions: List<StepAdvanceCondition>): StepAdvanceCondition
```

Convenience function for creating an [`OrAdvanceConditions`](#oradvanceconditions).

This composes multiple conditions together and advances to the next step if ANY of them trigger.

### Type aliases (uniffi.ferrostar)

[`UtcDateTime`](#utcdatetime), [`Uuid`](#uuid)

#### `UtcDateTime`

*Type alias* · `uniffi/ferrostar/ferrostar.kt`

```kotlin
public typealias UtcDateTime = java.util.Date
```

Typealias from the type name used in the UDL file to the custom type.  This
is needed because the UDL type name is used in function/method signatures.
It's also what we have an external type that references a custom type.

#### `Uuid`

*Type alias* · `uniffi/ferrostar/ferrostar.kt`

```kotlin
public typealias Uuid = java.util.UUID
```

Typealias from the type name used in the UDL file to the custom type.  This
is needed because the UDL type name is used in function/method signatures.
It's also what we have an external type that references a custom type.
