Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

The FFI contract

The SDK’s binding surface is one frozen UniFFI UDL contract, map_native_core.udl, published verbatim below and as a plain-text download. This file is the single source of truth: the Kotlin, Swift, and React Native bindings are generated from it, and the WebAssembly TypeScript mirror is regression-checked against it.

The contract is addition-only. Existing members keep their signature and meaning, so your compiled bindings stay valid across upgrades; new capability is new members. The API reference documents the same surface as generated rustdoc; the UDL download carries the raw form machine tooling consumes.

The pages below show the contract verbatim, byte-equality checked against the source file on every publish; an edit to the contract propagates to the site through the build or the build fails.

Show the raw UDL contract
// FROZEN (M4, slice 1 — 2026-10-27) — the UniFFI contract of the Maps SDK;
// extended in M4 slice 2 with the fit-bounds constructor (`EdgeInsetsPixels`
// and the named `MapCamera` constructor), in M4 slice 3 with the async
// provider surface (the `MapSdk` object resolving the `geospatial-traits`
// provider contracts one-to-one through the `map-core` `ProviderRegistry`,
// the provider records and enums mirror of `geospatial-types`, and the
// provider failure variants of `SdkError`), in M4 slice 4 with the
// foreign-callback provider injection: the `GeocodingProvider` and
// `RasterTileProvider` `[Trait, WithForeign]` interfaces foreign code
// (Kotlin, Swift) implements and registers on `MapSdk` through the
// `register_*_from_foreign` methods, and in M6 with the navigation wiring:
// the `NavigationStateEventListener` `[Trait, WithForeign]` listener and the
// `MapNavigationController` object driving the `map-navigation` session, with
// the mirror records of the navigation state, the instruction payload and the
// policy records standing in for the native decision objects.
//
// M2's remaining developer-facing sub-item (2026-10-28 tile TTL/budget
// ruling) adds the tile-cache settings surface additively: the
// `TileCacheTerms` record and the `MapSdk::set_raster_tile_cache_time_to_live`
// / `raster_tile_cache_terms` pair over the installed tile cache (byte
// budget stays registration-time through the frozen registration method).
//
// Round 12 (owner ruling of 2026-10-29) adds the provider-failure diagnostic
// seam additively: the `ProviderFailureClassification` enum and the
// `ProviderFailureDiagnostic` record, plus the
// `MapSdk::latest_provider_failure_diagnostics` readback over the bounded
// per-object ring the foreign-callback provider adapters write when a
// foreign-implemented provider contract call fails.
//
// This file is the single source of truth for the native binding surface
// (Kotlin, Swift, and React Native via `uniffi-bindgen-react-native`; PRD §6,
// decision 1). The hand-reviewed wasm surface in `crate map-web-bindings`
// mirrors this contract, and a M4 slice keeps a contract-sync test asserting
// that parity (PRD §4.7).
//
// This slice freezes the camera/projection core of `map-rendering`: the
// anchored `MapCamera`, the free projection helpers, and the version probe,
// with names and semantics aligned one-to-one with the wasm surface.
//
// Records mirror the projection core's value shapes by field name; they are
// plain FFI records, converted on the Rust side to and from the
// `geospatial-types` `GeographicCoordinate`/`BoundingBox` and the
// `map-rendering` viewport/tile types through explicit converters, because
// FFI traits cannot be implemented for foreign-crate types. The error
// carries detail messages and mirrors the `geospatial-types` `SDKError`
// variants these calls can actually produce; camera/projection failures are
// `InvalidCoordinateInput` — the viewport-extent and box-corner failures of
// the core map to the same variant and keep their verbatim message under
// `detail` — while the provider surface maps the six `SDKError` variants the
// registry path can produce. Further variants enter the contract as later
// slices surface real error paths; none are invented here.

namespace map_native_core {
    /// Version of the compiled SDK, equal to the crate's package version.
    string get_sdk_compiled_version();

    /// Maps a coordinate to normalized world coordinates in `[0, 1]`² (x
    /// eastward from the antimeridian, y southward from the north pole);
    /// latitude is clamped to the Mercator cutoffs.
    [Throws=SdkError]
    NormalizedWorldCoordinate coordinate_to_normalized_world(GeographicCoordinate coordinate);

    /// The XYZ address of the tile containing the coordinate at the zoom
    /// level.
    [Throws=SdkError]
    TileKeyRecord tile_key_for_coordinate(GeographicCoordinate coordinate, u8 zoom_level);

    /// Every XYZ tile whose area intersects the spanning box, ordered
    /// row-major from the north.
    [Throws=SdkError]
    sequence<TileKeyRecord> tiles_covering(BoundingBox bounding_box, u8 zoom_level);

    /// The SDK default step-advance policy: the distance-to-end-of-step
    /// condition with the Ferrostar recommended driving defaults.
    StepAdvancePolicy default_step_advance_policy();

    /// The SDK default deviation policy: the distance-threshold detector with
    /// the SDK's default deviation threshold.
    DeviationPolicy default_deviation_policy();

    /// The SDK default reroute-recalculation policy (round 11): the
    /// Ferrostar-recommended hysteresis — a 5 s cooldown and a 50 m movement
    /// threshold.
    ReroutePolicy default_reroute_policy();
};

/// A position on the WGS 84 ellipsoid in decimal degrees.
dictionary GeographicCoordinate {
    /// Latitude in decimal degrees, valid within `[-90, 90]`.
    double latitude;
    /// Longitude in decimal degrees, valid within `[-180, 180]`.
    double longitude;
};

/// Rectangular area between a south-west and a north-east corner; boxes
/// never span the antimeridian.
dictionary BoundingBox {
    /// South-west corner (minimum latitude and longitude).
    GeographicCoordinate south_west;
    /// North-east corner (maximum latitude and longitude).
    GeographicCoordinate north_east;
};

/// Position on the viewport in physical pixels (origin top-left, y down).
dictionary ScreenPoint {
    /// Horizontal position in pixels.
    double x;
    /// Vertical position in pixels.
    double y;
};

/// A world position in normalized world coordinates, both axes within
/// `[0, 1]`.
dictionary NormalizedWorldCoordinate {
    /// Eastward position from the antimeridian.
    double x;
    /// Southward position from the north pole.
    double y;
};

/// One web-mercator XYZ tile address.
dictionary TileKeyRecord {
    /// Tile column, in `0..2^zoom_level`.
    u32 tile_x;
    /// Tile row, counted from the north, in `0..2^zoom_level`.
    u32 tile_y;
    /// Zoom level this tile belongs to.
    u8 zoom_level;
};

/// Viewport dimensions in physical pixels.
dictionary ViewportPixels {
    /// Horizontal extent in pixels.
    double width_pixels;
    /// Vertical extent in pixels.
    double height_pixels;
};

/// Screen-space insets kept clear of map content: distances from each
/// viewport edge in pixels, so UI overlays — route cards, attribution, sheet
/// panels — never cover the content the camera guarantees visible.
dictionary EdgeInsetsPixels {
    /// Pixels kept clear from the left viewport edge.
    double left;
    /// Pixels kept clear from the top viewport edge.
    double top;
    /// Pixels kept clear from the right viewport edge.
    double right;
    /// Pixels kept clear from the bottom viewport edge.
    double bottom;
};

/// Failures thrown across the native binding surface of this slice. The
/// variants mirror the `geospatial-types` `SDKError` subset actually returned
/// by the frozen calls; the detail message is always verbatim.
[Error] interface SdkError {
    /// A coordinate, viewport extent or box corner failed validation.
    InvalidCoordinateInput(string detail);
    /// The network transport failed; carries the provider's verbatim detail.
    NetworkRequestFailed(string detail);
    /// The provider answered with a payload the parser could not use.
    ResponseParsingFailure(string detail);
    /// The provider returned a documented application-level failure status.
    ProviderRequestRejected(string detail);
    /// The token-bucket rate limiter is exhausted; retry after the next refill.
    RateLimitExhausted();
    /// No provider implementation is registered for the requested contract.
    ProviderNotConfigured(string contract_name);
    /// A navigation input (route record or navigation policy) failed native
    /// validation.
    InvalidNavigationInput(string detail);
    /// An overlay descriptor did not describe a usable overlay (wrong kind
    /// payload, an out-of-range color channel, or a non-positive or
    /// non-finite width or radius), or a surface input was structurally
    /// unusable (a null, zero or ambiguous platform surface pointer, or a
    /// non-positive viewport extent). Round 8 (surface slice).
    InvalidRenderInput(string detail);
    /// The live rendering could not proceed on the platform surface: the
    /// platform handle or surface was unusable, the graphics context could
    /// not be acquired, the surface format could not be negotiated or
    /// configured, the frame texture could not be acquired even after the
    /// stale-surface retry, or the frame's render state was busy in another
    /// draw. Round 8 (surface slice).
    RenderingSurfaceUnavailable(string detail);
};

/// Anchored map camera (geographic center, zoom, bearing, pitch) mirroring
/// the native `map-rendering` camera. The ground point under the center
/// always sits at the viewport center; zoom and pitch are clamped and the
/// bearing wraps into `[0, 360)` exactly like the native constructor.
interface MapCamera {
    [Throws=SdkError]
    constructor(GeographicCoordinate center, double zoom_level, double bearing_degrees, double pitch_degrees);

    /// Builds the camera that frames a bounding box inside the viewport's
    /// inset region: top-down and north-up by construction (pitch and bearing
    /// zero), zoomed to the largest level keeping the whole box inside the
    /// inset area and clamped to `maximum_zoom_level`; the center is anchored
    /// on the inset region's center. Throws when the insets or the maximum
    /// zoom are not finite usable values, when the insets are negative or
    /// leave no inset area, or when the box spans no positive world area.
    [Throws=SdkError, Name=fit_bounds]
    constructor(BoundingBox bounding_box, ViewportPixels viewport, EdgeInsetsPixels edge_insets, double maximum_zoom_level);

    /// Projects a coordinate to viewport pixels (origin top-left, y down).
    /// Returns null when the point lies behind the camera.
    [Throws=SdkError]
    ScreenPoint? project_to_screen(GeographicCoordinate coordinate, ViewportPixels viewport, double field_of_view_degrees);

    /// Unprojects a viewport pixel back onto the ground plane. Returns null
    /// when the ray misses the ground (typically below the horizon at steep
    /// pitch).
    [Throws=SdkError]
    GeographicCoordinate? screen_to_ground_coordinate(ScreenPoint screen_point, ViewportPixels viewport, double field_of_view_degrees);

    /// Every tile at `tile_zoom_level` whose area intersects the visible
    /// ground, sorted row-major from the north.
    [Throws=SdkError]
    sequence<TileKeyRecord> visible_tile_keys(ViewportPixels viewport, double field_of_view_degrees, u8 tile_zoom_level);

    /// Nearest whole zoom level a tile fetch should use for this camera.
    u8 tile_zoom_level();

    /// Reads the camera's attribute state back verbatim as it was built
    /// (round-9 asks). The getters make `fit_bounds`' chosen framing
    /// readable: the zoom level the constructor actually fitted, its
    /// anchored center, and the constructed bearing and pitch read back
    /// through these members. Read-only: the camera carries no setters —
    /// a new camera pose is a new constructor call. The UniFFI 0.32 UDL
    /// parser supports no `[Attribute]` member syntax, so the getters are
    /// plain methods.
    GeographicCoordinate center();
    double zoom_level();
    double bearing_degrees();
    double pitch_degrees();

    /// Ground meters represented by one screen pixel at the camera's center
    /// (round 10, the scale bar's helper): the equatorial ground meters per
    /// pixel at the camera's zoom level times the cosine of the center
    /// latitude, so the answer shrinks toward the poles exactly like the
    /// projected ground. At any nonzero pitch this is an approximation valid
    /// at the viewport's center column: the helper is flat-centered (no
    /// unprojection fold of pitch into the answer) and documents that
    /// honestly; the platform scale bars fit the meter magnitude through a
    /// labeled ladder rather than reading a bare value.
    [Throws=SdkError]
    double ground_meters_per_screen_pixel(ViewportPixels viewport, double field_of_view_degrees);
};

// ---------------------------------------------------------------------------
// Foreign-callback provider surface (M4 slice 4). The `[Trait, WithForeign]`
// interfaces below are the provider contracts foreign code (Kotlin, Swift)
// implements to serve calls of the matching `MapSdk` method. The plain
// contract name is used — no `Foreign` prefix — because this namespace
// freezes nothing else with those names (the `geospatial-traits` traits of
// the same names live in a different crate and never cross this FFI), and
// the name a Kotlin implementor reads is the contract the SDK documents.
// Per-member `[Async]` only: interface-level `[Async]` is rejected by the
// uniffi 0.32 UDL parser (documented in `docs/udl/contracts-draft.udl`).
// The remaining provider contracts follow this same two-step pattern as
// their first native or foreign implementations demand.
// ---------------------------------------------------------------------------

/// Geocoding contract implemented by foreign code and registered on `MapSdk`
/// through `register_geocoding_provider_from_foreign`.
[Trait, WithForeign]
interface GeocodingProvider {
    /// Resolves a free-form address text into candidate places.
    [Async, Throws=SdkError]
    sequence<PlaceResult> forward_geocode(string address_text);

    /// Resolves a coordinate into candidate places describing that position.
    [Async, Throws=SdkError]
    sequence<PlaceResult> reverse_geocode(GeographicCoordinate coordinate);
};

/// Raster-tile contract implemented by foreign code and registered on
/// `MapSdk` through `register_raster_tile_provider_from_foreign`.
[Trait, WithForeign]
interface RasterTileProvider {
    /// Fetches one web-mercator XYZ raster tile at the requested resolution.
    [Async, Throws=SdkError]
    RasterTileData fetch_raster_tile(
        u32 tile_x,
        u32 tile_y,
        u8 zoom_level,
        RasterImageFormat image_format,
        RasterTileResolution tile_resolution
    );
};

// ---------------------------------------------------------------------------
// Async provider surface (M4 slice 3). The enums and records below mirror the
// `geospatial-types` provider records one-to-one by name and field; `MapSdk`
// resolves every call through the `map-core` `ProviderRegistry`, so a missing
// registration throws `ProviderNotConfigured` with the contract name.
// ---------------------------------------------------------------------------

/// Structural precision a geocoder reached for one resolved location.
enum GeocodeQuality {
    /// Rooftop or feature point.
    "Point",
    /// Street address matched to the building level.
    "StreetAddress",
    /// Street intersection.
    "Intersection",
    /// Street level without house number.
    "Street",
    /// Postal code area.
    "PostalCode",
    /// Settlement level (village, town, city).
    "City",
    /// County or administrative district.
    "County",
    /// First-level administrative division.
    "State",
    /// Sovereign country level.
    "Country",
};

/// Mode of transport used to calculate a route.
enum TransportationMode {
    /// Private car routing.
    "Automobile",
    /// Bicycle routing.
    "Bicycle",
    /// Walking routing.
    "Pedestrian",
};

/// Length and speed conventions used when displaying a route.
enum UnitSystem {
    /// Kilometers and kilometers per hour.
    "Metric",
    /// Miles and miles per hour.
    "Imperial",
};

/// What a routing request optimizes for; the strategy applies to automobile
/// routing only (the current providers have no time-based cost model for the
/// other modes).
enum RouteStrategy {
    /// Minimize expected travel time.
    "Fastest",
    /// Minimize traveled distance.
    "Shortest",
};

/// Class of disruption reported in a traffic incident.
enum TrafficIncidentType {
    /// Road construction.
    "Construction",
    /// Traffic-impacting event.
    "Event",
    /// Collision.
    "Accident",
    /// Weather-related disruption.
    "Weather",
    /// Lane or road closure.
    "Closure",
};

/// Severity ranking of a traffic incident.
enum TrafficIncidentSeverity {
    /// Minimal delay expected.
    "Low",
    /// Noticeable delay expected.
    "Medium",
    /// Significant delay expected.
    "High",
};

/// Congestion classification of a road segment.
enum TrafficFlowCongestionLevel {
    /// At or above free-flow speed.
    "FreeFlow",
    /// Moderately below free-flow speed.
    "Slow",
    /// Stop-and-go traffic.
    "Congested",
    /// Roadway effectively closed.
    "Blocked",
};

/// Encoded image formats a raster tile provider may deliver.
enum RasterImageFormat {
    /// PNG imagery.
    "Png",
    /// JPEG imagery.
    "Jpeg",
};

/// Pixel resolution a raster tile is rendered at; `Standard` is the plain
/// tile form, `Retina` the `@2x` form with twice the texels per edge for the
/// same logical tile span.
enum RasterTileResolution {
    /// One texel per logical tile pixel.
    "Standard",
    /// Twice the texels per logical tile pixel (the `@2x` request form).
    "Retina",
};

/// One resolved location returned by forward or reverse geocoding.
dictionary PlaceResult {
    /// Display string the provider produced for this place.
    string structured_address_text;
    /// Resolved position.
    GeographicCoordinate coordinate;
    /// Structural precision reached for this resolution.
    GeocodeQuality geocode_quality;
};

/// Road features a routing request can ask the provider to avoid; all
/// disabled by default.
dictionary AvoidOptions {
    /// Avoid toll roads.
    boolean toll_roads;
    /// Avoid limited-access highways and motorways.
    boolean limited_access_highways;
    /// Avoid ferry crossings.
    boolean ferries;
    /// Avoid unpaved roads.
    boolean unpaved_roads;
};

/// Options controlling how a route is calculated.
dictionary RouteOptions {
    /// Mode of transport.
    TransportationMode transportation_mode;
    /// Whether routing optimizes travel time or distance.
    RouteStrategy route_strategy;
    /// Display unit convention the provider should annotate.
    UnitSystem unit_system;
    /// Features to avoid.
    AvoidOptions avoid_options;
    /// Departure instant as seconds since the Unix epoch; null for
    /// "depart now".
    u64? departure_timestamp_seconds;
};

/// One maneuver within a route leg.
dictionary RouteStep {
    /// Distance of this step in meters.
    double distance_meters;
    /// Travel time of this step in seconds.
    double duration_seconds;
    /// Maneuver classification as a stable snake-case text label; the
    /// roundabout exit index (one-based) and the turn direction are carried
    /// in the label (`roundabout-2`, `turn-left`); lane guidance is not part
    /// of this surface yet.
    string maneuver_type;
    /// Human-readable instruction in the display language.
    string instruction_display_text;
    /// Instruction tuned for text-to-speech engines.
    string instruction_speech_text;
    /// Step geometry from the previous step end to this maneuver point.
    sequence<GeographicCoordinate> shape;
};

/// Leg between two consecutive waypoints.
dictionary RouteLeg {
    /// Leg distance in meters.
    double distance_meters;
    /// Leg travel time in seconds.
    double duration_seconds;
    /// Maneuver steps in traversal order.
    sequence<RouteStep> steps;
};

/// A complete calculated route.
dictionary RouteData {
    /// Total distance across all legs, in meters.
    double distance_meters;
    /// Total travel time across all legs, in seconds.
    double duration_seconds;
    /// Geographic bounds of the full route geometry.
    BoundingBox bounding_box;
    /// Legs, one per consecutive waypoint pair.
    sequence<RouteLeg> legs;
};

/// One origin-destination cell of a distance matrix.
dictionary DistanceMatrixEntry {
    /// Origin index into the request's origins.
    u32 origin_index;
    /// Destination index into the request's destinations.
    u32 destination_index;
    /// Distance for this cell in meters.
    double distance_meters;
    /// Travel time for this cell in seconds.
    double duration_seconds;
};

/// Travel-time matrix for origins x destinations.
dictionary DistanceMatrix {
    /// Entries in row-major origin-major order.
    sequence<DistanceMatrixEntry> entries;
};

/// One reported disruption inside a queried bounding box.
dictionary TrafficIncident {
    /// Provider-stable incident identifier.
    string identifier;
    /// Class of disruption.
    TrafficIncidentType incident_type;
    /// Severity ranking.
    TrafficIncidentSeverity severity;
    /// Provider display text for this incident.
    string short_description_text;
    /// Position where the incident starts.
    GeographicCoordinate start_coordinate;
    /// Position where the incident ends, if reported.
    GeographicCoordinate? end_coordinate;
};

/// Flow condition for one road segment (or polyline of road segments) inside
/// the queried box, in kilometers per hour when the provider reports speeds
/// and as a congestion classification otherwise.
dictionary TrafficFlowData {
    /// Segment geometry, ordered along the road.
    sequence<GeographicCoordinate> shape_coordinates;
    /// Currently observed speed in kilometers per hour, when reported.
    double? current_speed_kilometers_per_hour;
    /// Unconstrained (free-flow) speed in kilometers per hour, when reported.
    double? free_flow_speed_kilometers_per_hour;
    /// Congestion classification from the speed ratio or provider buckets.
    TrafficFlowCongestionLevel congestion_level;
};

/// One fetched web-mercator XYZ raster tile.
dictionary RasterTileData {
    /// Tile column coordinate at the given zoom level.
    u32 tile_x;
    /// Tile row coordinate at the given zoom level.
    u32 tile_y;
    /// Zoom level this tile belongs to.
    u8 zoom_level;
    /// Encoding of the byte payload.
    RasterImageFormat image_format;
    /// Pixel resolution the payload was rendered at.
    RasterTileResolution tile_resolution;
    /// Encoded image bytes.
    sequence<u8> image_bytes;
};

/// Async provider entry point of the SDK: resolves every call of the
/// implemented `geospatial-traits` contracts through the `map-core`
/// `ProviderRegistry`. Construct the object empty, then the native
/// integrator registers providers through the Rust-side `register_*`
/// methods and foreign integrators through the `register_*_from_foreign`
/// methods (see the `[Trait, WithForeign]` interfaces above); every call
/// into an unregistered contract throws `SdkError::ProviderNotConfigured`
/// with the contract name.
///
/// The methods mirror the provider contracts one-to-one: geocoding (forward
/// and reverse), routing, distance matrix, place search, traffic incidents,
/// traffic flow and raster tiles. Elevation and offline routing follow with
/// their first concrete provider implementations (M6/M7).
interface MapSdk {
    /// Builds the SDK with an unconfigured provider registry; the native
    /// integrator registers providers before the first await call.
    constructor();

    /// Registers the geocoding implementation built by the foreign code on
    /// the `GeocodingProvider` interface; the foreign implementation starts
    /// serving `forward_geocode` and `reverse_geocode` once this returns.
    void register_geocoding_provider_from_foreign(GeocodingProvider geocoding_provider);

    /// Registers the raster-tile implementation built by the foreign code on
    /// the `RasterTileProvider` interface; the foreign implementation starts
    /// serving `fetch_raster_tile` once this returns.
    void register_raster_tile_provider_from_foreign(RasterTileProvider raster_tile_provider);

    /// Resolves a free-form address text into candidate places.
    [Async, Throws=SdkError]
    sequence<PlaceResult> forward_geocode(string address_text);

    /// Resolves a coordinate into candidate places describing that position.
    [Async, Throws=SdkError]
    sequence<PlaceResult> reverse_geocode(GeographicCoordinate coordinate);

    /// Calculates a route through the ordered waypoints with the given
    /// options; the waypoints must all be canonical coordinates.
    [Async, Throws=SdkError]
    RouteData calculate_route(sequence<GeographicCoordinate> waypoints, RouteOptions route_options);

    /// Calculates durations and distances for every origin-destination pair.
    [Async, Throws=SdkError]
    DistanceMatrix calculate_distance_matrix(
        sequence<GeographicCoordinate> origins,
        sequence<GeographicCoordinate> destinations,
        TransportationMode transportation_mode
    );

    /// Returns fast completion candidates while the user types; the boundary
    /// restricts the search when given.
    [Async, Throws=SdkError]
    sequence<PlaceResult> search_autocomplete(string query_text, BoundingBox? search_boundary);

    /// Returns full search results for a settled query; the boundary
    /// restricts the search when given.
    [Async, Throws=SdkError]
    sequence<PlaceResult> search_places(string query_text, BoundingBox? search_boundary);

    /// Fetches incidents intersecting the bounding box.
    [Async, Throws=SdkError]
    sequence<TrafficIncident> fetch_traffic_incidents(BoundingBox bounding_box);

    /// Fetches per-segment flow observations inside the bounding box.
    [Async, Throws=SdkError]
    sequence<TrafficFlowData> fetch_traffic_flow(BoundingBox bounding_box);

    /// Fetches one web-mercator XYZ raster tile at the requested resolution.
    [Async, Throws=SdkError]
    RasterTileData fetch_raster_tile(
        u32 tile_x,
        u32 tile_y,
        u8 zoom_level,
        RasterImageFormat image_format,
        RasterTileResolution tile_resolution
    );

    /// Rounds 8 (surface slice): registers the seven native MapQuest
    /// provider implementations (geocoding, routing, distance matrix, place
    /// search, traffic incidents, traffic flow, raster tiles) onto the
    /// registry, constructed from the MapQuest consumer key with the SDK
    /// defaults for base URL, rate limiting and retries — the foreign
    /// mirror of the native integrator's `register_*` calls, so foreign
    /// code installs the whole provider set with one call and an API key.
    /// Re-registration replaces every slot. Elevation stays unregistered:
    /// the elevation contract has no MapQuest provider, and elevation calls
    /// keep throwing `ProviderNotConfigured`.
    [Throws=SdkError]
    void register_mapquest_providers(string mapquest_api_key);

    /// Round 9 (offline-maps demo): registers the seven native MapQuest
    /// provider implementations from one key exactly as
    /// `register_mapquest_providers`, wrapping the raster-tile contract in
    /// the disk response cache opened (or created) as a SQLite database at
    /// `tile_cache_database_path` under an absolute byte budget of
    /// `tile_cache_absolute_maximum_bytes`. Cached tile responses follow the
    /// tiles category's time-to-live (thirty days by default — the
    /// imagery-provider terms ceiling, PRD §6.7); a cache miss goes to the
    /// network and back-fills the cache, so pre-warmed tiles keep rendering
    /// whenever the network is unavailable. The path must be non-empty and
    /// the byte budget positive; a cache open or quota failure throws with
    /// its verbatim detail. Re-registration replaces the slots (including
    /// the cached raster-tile slot).
    [Throws=SdkError]
    void register_mapquest_providers_with_tile_cache(string mapquest_api_key, string tile_cache_database_path, u64 tile_cache_absolute_maximum_bytes);

    // --- Tile-cache settings surface (M2's remaining developer-facing
    // sub-item; addition-only). The frozen
    // `register_mapquest_providers_with_tile_cache` signature is untouched:
    // the byte budget stays a registration-time fact (the SQLite store's
    // quota is fixed when the store opens, so retuning it means
    // re-registering through the frozen method), while the tile time-to-live
    // is a property of the installed cache and is tuned live through the two
    // members below. A per-integration registration-time alternative (a
    // second registration method carrying the whole frozen parameter set
    // plus one optional TTL field) was rejected: it duplicates the
    // registration surface for one optional value, and the terms readback
    // below already makes the applied value inspectable, so the override
    // rides the installed cache instead. Re-registration replaces the slots
    // and the tuning: a freshly registered cache starts again at the
    // category default, and the integrator re-applies the override after
    // registering.
    //
    // Validation ruling (PRD §6.7's provider-terms-ceiling intent): a
    // requested time-to-live above the installed provider's declared ceiling
    // is ACCEPTED, and the ceiling clamps what the cache stores — the terms
    // ceiling is a compliance bound on what the cache may retain, not a
    // preference, so the SDK must never retain beyond the provider's terms
    // whatever the integrator asks for; refusing the call would instead
    // break a live integration every time the provider's terms move or the
    // integrator reads a stale ceiling. The ceiling and the effective
    // (clamped) time-to-live are both in the `raster_tile_cache_terms`
    // readback, so an integrator sees exactly what went into force. A zero
    // time-to-live is refused: it would expire every entry on capture
    // (a cache that stores nothing), which is almost certainly a unit
    // mistake in the caller.

    /// Tile-cache settings surface (M2): overrides the raster-tile cache's
    /// time-to-live on the installed cache, in seconds. The tile cache must
    /// be installed (registered through
    /// `register_mapquest_providers_with_tile_cache`); otherwise this throws
    /// `ProviderNotConfigured` naming `TileCache` as the unconfigured
    /// backing store. A zero value throws `InvalidCoordinateInput` with a
    /// precise detail (a zero time-to-live expires every entry on capture,
    /// storing nothing). A value above the installed provider's declared
    /// terms ceiling is accepted with the ceiling clamping what the cache
    /// stores (see the ruling above); `raster_tile_cache_terms` reports the
    /// effective value.
    [Throws=SdkError]
    void set_raster_tile_cache_time_to_live(u64 time_to_live_seconds);

    /// Tile-cache settings surface (M2): reads the effective raster-tile
    /// caching terms so a developer can display them (a settings page, or
    /// the offline-maps demo's cache panel). Reports the time-to-live
    /// currently in force on the installed cache (the category default
    /// thirty days, or the last override this object accepted, clamped when
    /// it exceeded the provider's terms ceiling), the installed provider's
    /// declared terms ceiling (null when the provider declares no ceiling,
    /// in which case the requested time-to-live applies verbatim), and the
    /// cache category's default time-to-live. The tile cache must be
    /// installed (registered through
    /// `register_mapquest_providers_with_tile_cache`); otherwise this
    /// throws `ProviderNotConfigured` naming `TileCache` as the
    /// unconfigured backing store. Read-only: the readback never mutates
    /// the cache.
    [Throws=SdkError]
    TileCacheTerms raster_tile_cache_terms();

    /// Round-10 diagnostics (capability 6): subscribes the foreign listener
    /// to the SDK's structured internal log events streamed fire-and-forget
    /// from the crates' bounded ring buffers. Only events at
    /// `minimum_severity` or above are delivered, and coordinates within
    /// event messages are always rounded at the sink (never the API key).
    /// The listener callbacks run on the threads that produce the events and
    /// must never block on the caller; re-subscribing an already subscribed
    /// listener replaces its severity gate.
    void subscribe_to_diagnostic_events(SdkDiagnosticEventListener listener, SdkLogSeverity minimum_severity);

    /// Round-10 diagnostics: drains up to `maximum_count` of the most recent
    /// ring events at `minimum_severity` or above through the listener's
    /// `on_diagnostic_event` (oldest first) and returns how many were
    /// delivered, then (like Organic Maps' and OsmAnd's opt-in capture)
    /// nothing is removed from the ring, so a later flush can redeliver. The
    /// flush is bounded and synchronous: a listener must not block on it.
    u32 flush_recent_diagnostic_events(SdkDiagnosticEventListener listener, SdkLogSeverity minimum_severity, u32 maximum_count);

    /// Round-12 provider-failure diagnostic seam (addition-only): reads the
    /// retained diagnostics of every failed foreign-callback provider call
    /// this object has served — the failures foreign implementations of the
    /// `[Trait, WithForeign]` provider contracts threw, both the typed ones
    /// (the foreign code threw one of the contract's `SdkError` variants) and
    /// the unclassified ones (the foreign code failed without a mapped
    /// error). Returns the ring's retained entries oldest first, in
    /// recency-bounded order: the ring keeps the last
    /// `PROVIDER_FAILURE_DIAGNOSTIC_RING_CAPACITY` failures and drops the
    /// oldest beyond it, and reading is non-destructive. Read-only: the
    /// readback never records anything and never clears the ring. See
    /// `ProviderFailureDiagnostic` for what an entry carries; the frozen
    /// `SdkError` variants are untouched — failures still surface typed at
    /// the throwing call, and this seam only adds where they can be
    /// classified after the fact.
    sequence<ProviderFailureDiagnostic> latest_provider_failure_diagnostics();
};

// ---------------------------------------------------------------------------
// Tile-cache settings surface (M2 — addition-only). The record mirrors the
// effective caching terms of the installed raster-tile cache; the ceiling is
// the provider-specific caching term (PRD §6.7), read only when a cache is
// installed, which is the only state the settings surface addresses.
// ---------------------------------------------------------------------------

/// The effective raster-tile caching terms of the installed cache
/// (`MapSdk::raster_tile_cache_terms`'s return), shaped so a developer can
/// display the caching posture: the default, the provider's ceiling and the
/// value currently in force.
dictionary TileCacheTerms {
    /// Time-to-live currently in force on the installed cache, in seconds:
    /// the category default, or the last accepted override (clamped to the
    /// ceiling when the override exceeded it).
    u64 effective_time_to_live_seconds;
    /// The installed provider's declared caching-terms ceiling in seconds,
    /// or null when the provider declares no ceiling — the requested
    /// time-to-live then applies verbatim. For the MapQuest raster tiles
    /// this is the documented terms ceiling (thirty calendar days).
    u64? provider_terms_ceiling_seconds;
    /// The cache category's default time-to-live for raster tiles, in
    /// seconds (thirty days by the owner ruling of 2026-10-28, PRD §6.7);
    /// the value a freshly registered cache starts at.
    u64 default_time_to_live_seconds;
};

// ---------------------------------------------------------------------------
// Navigation wiring (M6). The `[Trait, WithForeign]`
// `NavigationStateEventListener` is the listener contract foreign code
// implements; its subscriptions register on the `MapNavigationController`
// object, which drives the native `map-navigation` reducer session and its
// per-topic broadcaster. The records and enums below mirror the
// `map-navigation` plain-data types field-for-field; the step-advance and
// deviation policies stand in for the native decision objects (trait
// objects), which cannot cross the FFI, as tagged records. Payload-carrying
// native enums mirror as a kind enum plus nullable fields; the instruction
// payload mirrors without the native active maneuver's lane-guidance list,
// which stays native until the frozen route records carry lane guidance.
// ---------------------------------------------------------------------------

/// State-listener contract implemented by foreign code and subscribed on
/// `MapNavigationController` through the `subscribe_to_*` methods; each
/// registered subscription delivers only its own topic.
[Trait, WithForeign]
interface NavigationStateEventListener {
    /// Receives the composed instruction payload of every dispatched state
    /// change (the instructions topic).
    void on_navigation_instruction(NavigationInstruction instruction);

    /// Receives a deviation-status stream update (the deviation topic):
    /// off-route on entry and on every recorded measurement update while the
    /// deviation persists, on-route when it clears.
    void on_deviation_status_change(DeviationStatusChange deviation_status_change);

    /// Receives one phase transition per dispatched trip-state transition
    /// (the phase topic).
    void on_navigation_phase_change(NavigationPhaseChange navigation_phase_change);

    /// Round 10 (speech pacing, OQ5's SDK-owned triggers): receives one
    /// utterance trigger when the controller's distance-band policy judges
    /// the fix newsworthy (a trigger band crossed this fix) — the verbatim
    /// composed speech text, never rewritten, so the platform owns only
    /// playback. Subscribers of the speech topic receive exactly the
    /// `subscribe_to_speech_instructions` deliveries.
    void on_speech_instruction(SpeechInstruction speech_instruction);
};

/// Handle coupled to exactly one `(topic, listener)` subscription; cancelling
/// is explicit and idempotent, dropping the handle does not unsubscribe.
interface NavigationTopicSubscription {
    /// Cancels this subscription: the listener stops receiving the
    /// subscription's topic from the next dispatch on. Idempotent.
    void cancel();
};

/// The exported navigation controller: drives the native navigation reducer
/// session, broadcasts every state change to the registered listeners, and
/// answers read-only state and instruction queries. Obtain routes from
/// `MapSdk::calculate_route` and start a trip on them with this controller.
///
/// The listener callbacks run synchronously on the thread that drives the
/// controller call; a listener calling back into the controller while a
/// dispatch is still in flight is suppressed by the broadcaster's
/// single-in-flight guard (the state change behind it is already applied,
/// only its broadcast is skipped). The controller never holds its state lock
/// across a listener callback.
interface MapNavigationController {
    /// Builds the controller with no active trip and no subscriptions.
    constructor();

    /// Subscribes the listener to the instructions topic: the composed
    /// instruction payload of every dispatched state change, in registration
    /// order.
    NavigationTopicSubscription subscribe_to_navigation_instructions(NavigationStateEventListener listener);

    /// Subscribes the listener to the deviation-status topic, in registration
    /// order.
    NavigationTopicSubscription subscribe_to_deviation_status(NavigationStateEventListener listener);

    /// Subscribes the listener to the phase-transition topic, in registration
    /// order.
    NavigationTopicSubscription subscribe_to_phase_changes(NavigationStateEventListener listener);

    /// Begins navigating `route` with the given policies, replacing any
    /// active trip. The initial instruction payload is dispatched; no
    /// deviation or phase transitions exist yet. A route without traversable
    /// steps represents an already-arrived trip. Refuses policies with
    /// non-finite or negative thresholds, and route records carrying a step
    /// maneuver label the conversion cannot map.
    [Throws=SdkError]
    void start_trip(RouteData route, UnitSystem unit_system, StepAdvancePolicy step_advance_policy, DeviationPolicy deviation_policy);

    /// Delivers one position fix to the active trip; without one the fix is
    /// dropped. Fixes without an accuracy report never trigger a step
    /// advance and are never judged by the deviation detector.
    void provide_location_update(NavigationLocationUpdate location_update);

    /// Replaces the route of the active trip (a reroute). The replacement
    /// route must be anchored at the trip's current snapped coordinate; the
    /// step advance strategy for the new route is configured by
    /// `step_advance_policy` and the deviation policy persists. A reroute
    /// offered to an arrived trip is ignored (no transition); without an
    /// active trip the event is dropped. Round 11: an in-flight reroute
    /// calculation is settled by this call (applied or ignored alike), so
    /// the request-in-flight gate reopens after the integrator acts.
    [Throws=SdkError]
    void provide_reroute(RouteData route, StepAdvancePolicy step_advance_policy);

    /// Round 11 (Ferrostar B3): applies the reroute-recalculation gates to
    /// one reroute-request signal at the wall clock: true when every gate
    /// passed (no calculation in flight, outside the cooldown window, and
    /// — when both fix references exist — beyond the movement threshold)
    /// and a calculation is now in flight, readable as the trip state's
    /// `is_calculating_new_route`; false when the signal was refused. A
    /// signal without an open, still-navigable trip (no trip, or the trip
    /// has arrived) is refused before the gates are consulted. A passing
    /// caller owes a settle: the eventual `provide_reroute` (applied or
    /// ignored) or `cancel_reroute_calculation`. The initial (trip's first)
    /// attempt always passes the cooldown and movement gates.
    boolean begin_reroute_calculation();

    /// Round 11: settles an in-flight reroute calculation without applying a
    /// route (the integrator abandoned the request): the request-in-flight
    /// gate reopens. The cooldown keeps running from the calculation's
    /// beginning and the movement reference stays where it was. Idempotent.
    void cancel_reroute_calculation();

    /// Round 11: swaps the reroute-recalculation hysteresis policy in force,
    /// keeping the gate's memory as it stands. Both thresholds must be
    /// finite and non-negative (refused with `InvalidNavigationInput`
    /// otherwise, and the policy in force is then untouched).
    [Throws=SdkError]
    void configure_reroute_policy(ReroutePolicy reroute_policy);

    /// The plain state of the active trip, or null without one.
    TripNavigationState? current_trip_state();

    /// The composed turn-by-turn instruction payload of the current trip
    /// state, or null without an active trip; repeated reads of an unchanged
    /// state return the same payload.
    NavigationInstruction? current_instructions();

    /// Round 10 (speech pacing, OQ5): subscribes the listener to the
    /// speech-instruction trigger topic, in registration order. Each fix's
    /// dispatch consults the controller's named-potential policy: the trigger
    /// bands reuse the display phrase's distance bands (so a spoken "in 100
    /// meters" always matches the payload's shown phrase band), each band
    /// announces at most once per step (monotone progress), and a minimum
    /// inter-utterance spacing and repeat distance suppress chatter. The
    /// platform keeps playback (including its fallback speak-latest behavior
    /// when no listener subscribes) and synthesis stays out of scope.
    NavigationTopicSubscription subscribe_to_speech_instructions(NavigationStateEventListener listener);
};

/// High-level phase of a trip that has already been started. Rerouting is not
/// a phase of its own: the machine flows Navigating ⇄ OffRoute within a trip
/// and Navigating → Arrived at its end.
enum NavigationPhase {
    /// Following the route's step sequence.
    "Navigating",
    /// The deviation detector judged the last graded fix off route; progress
    /// is frozen at the last on-route values until a graded fix recovers or a
    /// reroute replaces the route.
    "OffRoute",
    /// The final step has been completed; location updates no longer change
    /// the state.
    "Arrived",
};

/// Which grade of deviation a recorded deviation carries: the two-grade
/// distinction between off the current step yet still on the route line and
/// completely off the route. Only the fully-off grade freezes navigation; the
/// wrong-step grade never freezes on its own.
enum DeviationGradeKind {
    /// The fix projects onto neither the current step nor any future step of
    /// the remaining route line: the freezing grade. The trip's progress is
    /// frozen until a graded fix recovers or a reroute replaces the route.
    "FullyOffRoute",
    /// The fix is off the current step but projects onto a future step of the
    /// remaining route line: "on route, wrong step". The trip keeps
    /// navigating and counting down, and is never frozen by this grade alone.
    "OffStepOnRoute",
};

/// Plain-data description of an active deviation from the route. The grade
/// kind distinguishes the freezing fully-off grade from a merely wrong-step
/// record: a wrong-step record is guidance feedback ("on the route, on a
/// later step") carried while the trip phase stays `Navigating`.
dictionary DeviationInfo {
    /// Which grade of deviation this is (see `DeviationGradeKind`).
    DeviationGradeKind grade_kind;
    /// Distance, in meters, from the raw fix that triggered (or most recently
    /// confirmed) the deviation to the remaining route line.
    double deviation_from_route_meters;
};

/// Location of one route step in traversal order across a route's legs.
dictionary RouteStepPosition {
    /// Index of the leg that owns this step in the route's legs.
    u64 leg_index;
    /// Index of the step within its leg in the leg's steps.
    u64 step_index;
};

/// Progress along the remaining trip.
dictionary TripProgress {
    /// Distance, in meters, from the snapped position to the end of the
    /// current step — the point of the next maneuver.
    double distance_to_next_maneuver_meters;
    /// Total remaining distance, in meters, including the current step's
    /// remainder.
    double distance_remaining_meters;
    /// Total remaining travel time, in seconds, measured the same way as
    /// `distance_remaining_meters`.
    double duration_remaining_seconds;
};

/// The full navigable state of an active trip, read-only for foreign code.
/// The route's steps are carried as traversal-ordered indices, never
/// duplicated; the live step-advance condition and deviation detector are not
/// records and cannot cross the FFI — they are configured through the
/// navigation policies on the controller.
dictionary TripNavigationState {
    /// Current phase of the trip.
    NavigationPhase phase;
    /// The route being navigated.
    RouteData route;
    /// All steps across every leg, flattened in traversal order.
    sequence<RouteStepPosition> step_positions;
    /// Index into `step_positions` of the step currently traversed.
    u64 current_step_position;
    /// Progress along the remaining trip.
    TripProgress progress;
    /// Position snapped onto the current step's shape; null until the first
    /// location update has been processed.
    GeographicCoordinate? snapped_coordinate;
    /// Active deviation, as judged by the deviation detector; a fully-off
    /// grade is set exactly while the phase is `OffRoute`, and a wrong-step
    /// (`OffStepOnRoute`) grade may be set while the phase stays `Navigating`.
    DeviationInfo? deviation;
    /// Round 11 (Ferrostar B3): whether a reroute recalculation is currently
    /// in flight. This is the controller's wall-clock gate fact, not reducer
    /// state — the reducer stays clock-free — so it is read fresh from the
    /// controller on every readback.
    boolean is_calculating_new_route;
};

/// Length unit a distance-phrase magnitude is expressed in.
enum DistanceUnit {
    /// Meters, used by the metric near and moderate bands.
    "Meters",
    /// Kilometers, used by the metric far band.
    "Kilometers",
    /// Feet, used by the imperial near and moderate bands.
    "Feet",
    /// Miles, used by the imperial far band.
    "Miles",
};

/// Which form a distance phrase takes.
enum DistancePhraseKind {
    /// The position is closer than half a near-band increment to the maneuver
    /// point; no distance is phrased and composed text drops the prefix. The
    /// magnitude is null on this kind.
    "Immediate",
    /// A rounded distance magnitude in a specific unit; the magnitude and
    /// unit are set on this kind.
    "Quantity",
    /// No distance is phrased because the freezing (fully-off-route) deviation
    /// grade holds: the projection is frozen, so any countdown composed off it
    /// would be a phantom measurement. The magnitude is null on this kind, and
    /// the composed text channels carry no distance prefix.
    "Suppressed",
};

/// The unit-system-aware phrasing of the distance remaining to a maneuver,
/// rounded into the named distance phrasing bands.
dictionary DistancePhrase {
    /// Which form the phrase takes.
    DistancePhraseKind phrase_kind;
    /// Rounded magnitude of the phrase; null for the immediate and suppressed
    /// forms.
    double? magnitude;
    /// Unit the magnitude is expressed in; null for the immediate and
    /// suppressed forms.
    DistanceUnit? magnitude_unit;
};

/// Which form an instruction text takes.
enum ManeuverInstructionTextKind {
    /// The provider authored instruction text for this maneuver.
    "Composed",
    /// The provider authored no instruction text for this maneuver; nothing
    /// is invented to fill it. All four text fields are null on this kind.
    "Unavailable",
};

/// The composed text channels of one maneuver: provider-authored text is
/// carried verbatim beside the composed strings.
dictionary ManeuverInstructionText {
    /// Which form the text takes.
    ManeuverInstructionTextKind text_kind;
    /// Provider-authored display text, verbatim and never rephrased.
    string? provider_display_text;
    /// Provider-authored speech text, verbatim and never rephrased.
    string? provider_speech_text;
    /// Banner variant: the live distance prefix plus the display variant.
    string? composed_display_text;
    /// Text-to-speech variant: the live distance prefix plus the speech
    /// variant.
    string? composed_speech_text;
};

/// Instruction payload of the maneuver currently being traversed (the banner
/// maneuver: the step the snapped position sits inside, with the distance
/// measured to that step's end — the maneuver point).
dictionary ActiveManeuverInstruction {
    /// Composed display and speech text of the active step.
    ManeuverInstructionText instruction;
    /// Unit-system-aware distance phrasing of the distance to the next
    /// maneuver.
    DistancePhrase distance_phrase;
    /// Structured exit index of a roundabout active step, starting at one.
    u8? roundabout_exit_index;
};

/// Instruction payload of the maneuver after the active one (the "then"
/// instruction), text-carrying only.
dictionary UpcomingManeuverInstruction {
    /// Composed display and speech text of the following step.
    ManeuverInstructionText instruction;
    /// Structured exit index when the following step is a roundabout.
    u8? roundabout_exit_index;
};

/// The emitted turn-by-turn instruction payload for one trip state; the same
/// state yields the same payload on every read.
dictionary NavigationInstruction {
    /// The banner maneuver currently being traversed.
    ActiveManeuverInstruction active_maneuver;
    /// The maneuver after the active one, where the route has a following
    /// step (always null on the route's final step and after arrival).
    UpcomingManeuverInstruction? upcoming_maneuver;
    /// True exactly while the trip has arrived and the remaining progress is
    /// zero.
    boolean is_arrival;
    /// True exactly while the trip is off route: the payload beneath the
    /// marker is the frozen last-on-route projection, whose distance phrase
    /// is suppressed rather than presented, and must not be presented as
    /// current guidance.
    boolean is_rerouting_required;
};

/// Which form a deviation-status update takes.
enum DeviationStatusChangeKind {
    /// The trip is judged deviating; the deviation description is set on this
    /// kind. The description's grade kind distinguishes the freezing
    /// fully-off grade from a merely wrong-step record, which is delivered
    /// while the trip phase stays `Navigating`.
    "OffRoute",
    /// A previously recorded deviation cleared; the trip is judged on route.
    "OnRoute",
};

/// A deviation-topic update: the detector's judgment changed between the
/// previous and the current trip state — entry, measurement drift while the
/// deviation persists, and clearing.
dictionary DeviationStatusChange {
    /// Which form the update takes.
    DeviationStatusChangeKind status_change_kind;
    /// The recorded measurement of the deviation, set exactly on the
    /// off-route kind.
    DeviationInfo? deviation_info;
};

/// A phase-transition event delivered on the phase topic.
enum NavigationPhaseChange {
    /// The trip entered the off-route phase from the navigating phase.
    "OffRouteEntered",
    /// A graded fix back on the route line lifted the deviation and returned
    /// the trip to the navigating phase.
    "OffRouteCleared",
    /// A reroute event replaced the route (delivered only when the event
    /// actually applied — an ignored reroute produces no transition).
    "RerouteApplied",
    /// The final step completed; the trip reached the arrived phase.
    "Arrived",
};

/// One raw position fix delivered to the navigation controller.
dictionary NavigationLocationUpdate {
    /// Raw position on the WGS 84 ellipsoid, before route snapping.
    GeographicCoordinate coordinate;
    /// Radius, in meters, of the 68% horizontal-accuracy envelope of this fix
    /// (null when the source does not report accuracy).
    double? horizontal_accuracy_meters;
    /// Direction of travel in degrees clockwise from true north (null when
    /// the source does not report a course).
    double? course_heading_degrees;
    /// Fix instant in seconds since the Unix epoch (null for untimed
    /// sources).
    double? timestamp_seconds;
};

/// Which step-advance strategy a navigation policy configures.
enum StepAdvancePolicyKind {
    /// Steps change only when driven programmatically (not available on this
    /// surface yet); the trip still tracks progress. The threshold fields are
    /// ignored on this kind.
    "Manual",
    /// Advances when the position fix comes within the threshold of the end
    /// of the current step, eager-advance semantics.
    "DistanceToEndOfStep",
    /// Advances when the position fix moves farther from the current step's
    /// polyline than the threshold, delayed-advance semantics.
    "DistanceFromStep",
};

/// The step-advance policy of a trip, standing in for the native decision
/// object that cannot cross the FFI.
dictionary StepAdvancePolicy {
    /// Which step-advance strategy the policy configures.
    StepAdvancePolicyKind policy_kind;
    /// Distance, in meters, of the advance threshold (the distance to the
    /// step's end, or from the step's polyline, depending on the kind).
    double advance_threshold_meters;
    /// Fixes with a reported horizontal accuracy worse than this, in meters,
    /// never advance.
    double minimum_horizontal_accuracy_meters;
};

/// Which deviation-detection strategy a deviation policy configures.
enum DeviationPolicyKind {
    /// Flags a deviation when the raw fix is farther from the remaining route
    /// line than the configured threshold, widened by the fix's reported
    /// accuracy.
    "DistanceThreshold",
};

/// The deviation-detection policy of a trip, standing in for the native
/// decision object that cannot cross the FFI.
dictionary DeviationPolicy {
    /// Which deviation-detection strategy the policy configures.
    DeviationPolicyKind policy_kind;
    /// Distance, in meters, from the remaining route line beyond which a fix
    /// is judged off route (before any accuracy widening, strictly greater).
    double deviation_threshold_meters;
};

// ---------------------------------------------------------------------------
// Round 11 (Ferrostar adoption batch 2, mechanism adopted from `FerrostarCore`
// under its BSD 3-Clause license; the expressions are our own). The
// reroute-recalculation hysteresis: three gates stand between a fully
// off-route signal and an actual recalculation request — the request-in-flight
// gate, the cooldown and the movement threshold — plus the in-flight
// readback on the trip state, so a jittery off-route bounce cannot spam the
// integrator's routing engine. The gates live at the controller seam with
// the wall clock (exactly like the speech pacing's planner), never in the
// reducer: the reducer stays a pure, clock-free event function. The gates
// close the round-9 contract gap ("no reroute timestamp on the phase
// channel") without an addition there: a reroute timestamp on the phase
// channel is not addition-only (`NavigationPhaseChange` stays a payloadless
// flat enum), so the phase channel stays untouched and the honest closure is
// the gate seam plus the `is_calculating_new_route` readback plus
// `begin_reroute_calculation`'s boolean answer.
// ---------------------------------------------------------------------------

/// The reroute-recalculation hysteresis policy of a navigation controller
/// (round 11, Ferrostar B3's mechanism). Both thresholds must be finite and
/// non-negative; the controller refuses otherwise (`InvalidNavigationInput`),
/// leaving the policy in force untouched.
dictionary ReroutePolicy {
    /// Minimum wall-clock seconds between the beginnings of two reroute
    /// calculations (the SDK default: 5).
    double recalculation_cooldown_seconds;
    /// Minimum displacement, in meters, between the raw position fix current
    /// at a calculation's beginning and the one current at the previous
    /// calculation's beginning (the SDK default: 50).
    double recalculation_movement_meters;
};
// ---------------------------------------------------------------------------
// Native surface views (round 8, slice 0). The additions below mirror the
// wasm canvas' overlay plain-object names — `routePolyline`,
// `filledPolygon`, `pointMarkers`, `trafficFlow` — as a kind enum plus
// nullable fields (the round-7 navigation payload pattern: a field-carrying
// native enum lower as a tag with `null` marking the unused payload
// fields), and expose one `MapSurfaceView` object: the FFI face of the
// `map-rendering` surface renderer, drawn by a platform view the foreign
// code owns (Kotlin `SurfaceView`, Swift `UIView`/`MKMapView`-style view,
// PRD §4.4–4.6). The renderer itself stays native (same `wgpu` machinery as
// the headless capture path); the FFI surface carries the lifecycle
// (attach/resize/detach), the overlay list, and an explicit per-frame draw.
// The platform's own render cadence drives the view — `draw` is per-frame,
// called from a display link or choreographer callback, never an SDK-
// internal loop: no SDK thread this slice, documented below.
// ---------------------------------------------------------------------------

/// Which overlay shape one descriptor configures. The kind tags follow the
/// wasm canvas' plain-object `kind` strings (the wasm seam passes the
/// lowercase camel kinds `routePolyline`/`filledPolygon`/`pointMarkers`/
/// `trafficFlow`; the FFI enum variants are the contract's own CamelCase),
/// mirroring the native `MapOverlay` payloads one to one.
enum MapOverlayKind {
    /// A routed path drawn as a thick line.
    "RoutePolyline",
    /// A filled polygon: one outer ring, ear-clip triangulated.
    "FilledPolygon",
    /// Screen-facing point markers (octagon fans).
    "PointMarkers",
    /// Congestion-colored traffic-flow lines.
    "TrafficFlow",
};

/// Solid RGBA overlay style, every channel in `[0, 1]`. Mirrors the wasm
/// canvas' `color` plain object. Null style fields keep the documented
/// `map-rendering` default style of the matching overlay kind.
dictionary OverlayColor {
    /// Red channel in `[0, 1]`.
    double red;
    /// Green channel in `[0, 1]`.
    double green;
    /// Blue channel in `[0, 1]`.
    double blue;
    /// Alpha channel in `[0, 1]` (the overlay pipeline alpha-blends).
    double alpha;
};

/// One overlay shape drawn over the map surface's tiles in every frame,
/// selected by `kind`. Each kind reads exactly its own payload fields and
/// ignores the others; a descriptor whose kind-required payload is absent
/// (or whose style field is unusable) rejects the whole `set_overlays` call
/// and keeps the previous list in force — the wasm canvas' all-or-nothing
/// semantics. Absent style fields keep the documented map-rendering
/// default style; present style fields must be usable (channels in
/// `[0, 1]`, widths and radii positive and finite). The record mirrors the
/// native `map-rendering` overlay structs field-for-field; the wasm canvas
/// plain objects carry the same names in camel case (`ringCoordinates`,
/// `pointMarkerPositions`, `pointMarkerRadiusPixels`,
/// `trafficFlowSegments`).
dictionary MapOverlayDescriptor {
    /// Which overlay shape this descriptor configures and which payload
    /// fields carry it.
    MapOverlayKind kind;

    /// `RoutePolyline`: the traversal-order polyline; consecutive equal
    /// coordinates skip their degenerate segment. Required for the kind.
    sequence<GeographicCoordinate>? coordinates;
    /// Route polyline, filled polygon and point markers: solid color
    /// override, or null for the kind's documented default.
    OverlayColor? rgba_color;
    /// Route polyline and traffic flow: line width in screen pixels
    /// (positive and finite), or null for the kind's documented default.
    double? width_pixels;

    /// `FilledPolygon`: the outer ring in traversal order without a
    /// repeated closing vertex (a repeated one is tolerated and dropped).
    /// Required for the kind.
    sequence<GeographicCoordinate>? ring_coordinates;

    /// `PointMarkers`: the geographic anchor of every marker. Required for
    /// the kind.
    sequence<GeographicCoordinate>? point_marker_positions;
    /// Point markers: marker radius in screen pixels (positive and finite),
    /// or null for the documented default.
    double? point_marker_radius_pixels;

    /// `TrafficFlow`: the flow observations to paint, one per road segment
    /// (or polyline of segments), colored by `congestion_level` — the same
    /// `TrafficFlowData` records the `fetch_traffic_flow` call returns.
    /// Required for the kind.
    sequence<TrafficFlowData>? traffic_flow_segments;
};

/// The platform window surface handle foreign code hands to
/// `MapSurfaceView::attach`: exactly one platform pointer is set, the rest
/// stay null (a null-or-ambiguous handle is refused with
/// `SdkError::InvalidRenderInput`; a null pointer is `u64 0` or null, never
/// a zeroed pointer passed through). The handle shape is a single record
/// with one nullable pointer per supported platform — one dictionary keeps
/// add-a-platform growth additive and mirrors how uniffi records lower
/// everywhere. The object behind the pointer must outlive the attached
/// view: the raw handles are the caller's validity guarantee (see
/// `wgpu::SurfaceTargetUnsafe`), and detach/drop releases the Rust side
/// first, so dropping the platform view before `detach` is the caller's
/// bug.
dictionary MapSurfaceTargetHandle {
    /// macOS: pointer to the `NSView` the surface renders into (Metal
    /// creates the layer from the view); null on the other platforms.
    u64? appkit_view_pointer;
    /// iOS: pointer to the `UIView` the surface renders into; null on the
    /// other platforms.
    u64? ios_view_pointer;
    /// Android: pointer to the `ANativeWindow` obtained from the platform
    /// `Surface` (`ANativeWindow_fromSurface`); null on the other platforms.
    u64? android_native_window_pointer;
};

/// The native map surface view: renders the camera-driven tiled map plus
/// the overlay list onto a live platform window surface through the same
/// `wgpu` machinery as the headless capture path. Construct with the
/// `MapSdk` whose registered raster-tile provider the attached surface
/// draws from (the provider resolves through the registry at attach time —
/// with no registered provider, `attach` throws
/// `SdkError::ProviderNotConfigured`).
///
/// # Lifecycle
///
/// `attach` wraps the platform handle, acquires the surface-compatible
/// graphics context, negotiates the surface format, configures the initial
/// extent and resolves the tile provider — every step's failure throws
/// (`SdkError::RenderingSurfaceUnavailable` for the GPU/surface path,
/// `InvalidRenderInput` for a bad handle or extent, `ProviderNotConfigured`
/// for the missing provider slot). `resize` (explicit, called from the
/// platform's surface-size callback) reconfigures the surface; a zero
/// extent rejects. `detach` releases the surface state until a re-attach.
/// Overlay state outlives attach/detach cycles: `set_overlays` and
/// `clear_overlays` work detached, and the next attach draws the stored
/// list.
///
/// # Draw model
///
/// Frames are explicit: the foreign platform view calls `draw` once per
/// rendered frame from its own cadence (a display link, a choreographer, a
/// resize-then-once pattern) with the current `MapCamera` value — there is
/// deliberately no SDK-internal render thread or continuous redraw loop in
/// this slice. Each draw lists the camera's visible tiles, fetches the
/// pooled misses from the surface-attached provider (budgeted per frame,
/// parent fallback like the headless path), and renders tiles then
/// overlays alpha-blended, presenting to the surface. A failed frame
/// throws and leaves the previous frame on screen. Draws on one view are
/// serialized: a draw holding the view's render state (the fetch phase
/// spans network await) reports a concurrent `draw` or `resize` with
/// `RenderingSurfaceUnavailable`, the foreign cadence retrying the next
/// frame.
interface MapSurfaceView {
    /// Builds the view over the SDK state it draws from.
    constructor(MapSdk map_sdk);

    /// Attaches the platform surface of the given initial physical-pixel
    /// extent (the extent must be non-zero; a platform view not laid out
    /// yet waits for `resize`). The wrap validates the handle, acquires
    /// the context, negotiates the format, configures and resolves the
    /// raster provider. Attaching an attached view is refused.
    [Throws=SdkError]
    void attach(MapSurfaceTargetHandle surface_handle, u32 width_pixels, u32 height_pixels);

    /// Reconfigures the surface to the new physical-pixel extent (both
    /// non-zero); a mid-draw resize surfaces the busy state and the next
    /// frame's draw or the next resize call picks it up.
    [Throws=SdkError]
    void resize(u32 width_pixels, u32 height_pixels);

    /// Detaches: releases the surface, its renderer and its scheduler.
    /// Overlay state persists; re-attach rebuilds the drawing machinery.
    /// Safe against a draw still in flight (its frame completes and the
    /// state is then dropped without being restored).
    void detach();

    /// Replaces the overlay list drawn over the tiles in every following
    /// `draw`, replacing any earlier list in order (later entries draw
    /// over earlier ones); an empty list clears. Every descriptor must be
    /// valid for its kind: no partial lists are kept.
    [Throws=SdkError]
    void set_overlays(sequence<MapOverlayDescriptor> overlay_descriptors);

    /// Clears the overlay list; following frames draw the tiles alone
    /// until `set_overlays` supplies a new list.
    void clear_overlays();

    /// Draws one frame for the camera: the camera's visible tiles fetched
    /// and rendered, overlaid, presented. Returns the frame's tile and
    /// overlay statistics (round-9 ask) so the platform render loop can
    /// report exactly what a frame drew: drawn pixels this frame come from
    /// the present and fallback tiles, while the deferred and unavailable
    /// counts are where later frames may still change pixels. Throws when
    /// the view is detached, a draw is already in flight, or the frame's
    /// rendering failed; a thrown draw leaves the previous frame on screen.
    [Async, Throws=SdkError]
    TileFrameStatistics draw(MapCamera camera);

    /// Round-10 (renderer-owned location indicator): sets the single
    /// location layer drawn after the tile pass and the overlay pass
    /// (topmost SDK layer), replacing any earlier state — mirroring
    /// `set_overlays`' all-or-nothing semantics, so a state or style that
    /// fails validation throws and keeps the earlier indicator. The state
    /// outlives attach/detach cycles like the overlay list; the styled
    /// visuals resolve to the documented map-rendering defaults for every
    /// null style field.
    [Throws=SdkError]
    void set_location_indicator(LocationIndicatorState state, LocationIndicatorStyle style);

    /// Clears the location layer; following frames draw tiles and overlays
    /// alone until `set_location_indicator` supplies new state.
    void clear_location_indicator();

    /// Round-10 (idle-settled redraw discipline): reads whether the view's
    /// presented state would not change if `draw` ran again right now —
    /// true when this camera value equals the last drawn camera, the overlay
    /// list and the location indicator are unchanged since that draw, and
    /// the last frame's tile statistics resolved no pending pixels (no
    /// fallback, deferred or unavailable tiles). A pure query: it never
    /// draws, fetches or mutates state, so a platform render pump can call
    /// it every display frame and skip `draw` when it answers true, letting
    /// the presented frame stand and resuming on any change or on the next
    /// pending detail's arrival. Throws when the view is detached.
    [Throws=SdkError]
    boolean frame_settled(MapCamera camera);
};

/// One presented frame's tile and overlay accounting, mirroring how the
/// surface renderer resolves its scheduler verdicts. Every requested tile
/// lands in exactly one of the four readiness counts, so
/// `present + fallback + deferred + unavailable` always equals `requested`.
/// The four readiness names mirror the tile scheduler's fetch-state
/// taxonomy (`Present`/`FallsBackTo`/`Deferred`/`Unavailable`).
dictionary TileFrameStatistics {
    /// Tile requests the frame resolved (per request; a request listing
    /// carrying duplicates costs one fetch but reports per request).
    u32 requested_tile_count;
    /// Tiles drawn at their own zoom level.
    u32 present_tile_count;
    /// Tiles drawn from their nearest pooled ancestor.
    u32 fallback_tile_count;
    /// Tiles whose fetch was deferred by the per-frame budget.
    u32 deferred_tile_count;
    /// Tiles the provider rejected this frame.
    u32 unavailable_tile_count;
    /// Clip-space triangles the overlay layer contributed to the frame.
    u32 overlay_triangle_count;
};

// ---------------------------------------------------------------------------
// Round-10 contract additions (addition-only; renderer-owned location
// indicator, SDK-owned speech triggers, structured diagnostics).
// ---------------------------------------------------------------------------

/// Which visual form the renderer-owned location layer draws (OQ3's
/// style-selected single layer).
enum LocationIndicatorVisualKind {
    /// Plain disc (tracking mode: no course known or no route).
    "TrackingDot",
    /// Disc plus course-directed heading wedge (navigation follow mode).
    "GuidedCourseArrow",
};

/// Style of the location layer; null fields keep the documented
/// `map-rendering` defaults (the overlay-style pattern). The disc and wedge
/// are screen-sized pixel constants (a position marker scales with the
/// screen, not the ground); the accuracy ring is ground-sized.
dictionary LocationIndicatorStyle {
    /// Which visual form this layer draws.
    LocationIndicatorVisualKind visual_kind;
    /// Disc radius in screen pixels; null keeps the default.
    double? radius_pixels;
    /// Disc fill color; null keeps the default.
    OverlayColor? rgba_color;
    /// Accuracy ring drawn around the disc (shown only when a fix carries a
    /// reported accuracy); null keeps the default (shown whenever the fix
    /// reports one that reaches the minimum draw size).
    boolean? show_accuracy_ring;
};

/// One frame's location-layer state (the presentable subset of a position
/// fix): position, optional course over ground, optional horizontal accuracy.
dictionary LocationIndicatorState {
    /// Fix position.
    GeographicCoordinate coordinate;
    /// Course over ground in degrees clockwise from true north; null when
    /// the source does not report one (the wedge renders only when set and
    /// the style carries `GuidedCourseArrow`).
    double? course_heading_degrees;
    /// Horizontal accuracy radius in meters; the accuracy ring renders when
    /// set and the style shows rings.
    double? horizontal_accuracy_meters;
};

/// When a spoken-trigger event fires and what carries it (OQ5). The
/// utterance text is the contract's composed speech text verbatim — the SDK
/// owns when a line is newsworthy, the platform owns playback and synthesis.
dictionary SpeechInstruction {
    /// The verbatim speech text (from the contract's composition; never
    /// rewritten by the trigger policy).
    string utterance_speech_text;
    /// The live distance to the maneuver in meters at emission time.
    double trigger_distance_meters;
    /// Round 11 (Ferrostar B4): the utterance identity — a stable monotonic
    /// id composed at emission, strictly increasing over every line the
    /// controller has emitted since construction and never reused.
    /// Identical text re-emitted is a new id; dedup and flush decisions stay
    /// at the platform speaker.
    u64 utterance_id;
    /// What triggered this line (a crossed band, the immediate band, an
    /// off-route recovery, or the trip's arrival).
    SpeechInstructionKind instruction_kind;
};

/// What triggered a `SpeechInstruction`.
enum SpeechInstructionKind {
    /// A trigger band was crossed this fix (the moderate or far threshold).
    "ManeuverApproach",
    /// The immediate band: the maneuver is imminent.
    "ImmediateManeuver",
    /// Guidance just recovered from off-route (guidance resumed).
    "OffRouteRecovery",
    /// The trip arrived.
    "Arrival",
};

/// Severity of an SDK diagnostic event, mirroring the `tracing` levels the
/// crates log under.
enum SdkLogSeverity {
    /// Very fine-grained frame-path instrumentation (GPU-hot path logs live
    /// here, only when a listener subscribed at this severity).
    "Trace",
    /// Fine-grained internal state transitions.
    "Debug",
    /// Coarse-grained progress (the default minimum for a subscription).
    "Info",
    /// A problem this event explains.
    "Warn",
    /// A failure this event explains.
    "Error",
};

/// One structured SDK internal log event, as delivered to a foreign
/// diagnostics listener or read back by a flush. Coordinates in event
/// messages are rounded at the sink (never the API key, and never raw
/// coordinates at full precision).
dictionary DiagnosticLogEvent {
    /// Wall-clock milliseconds since the Unix epoch, as the ring recorded
    /// the event.
    u64 timestamp_milliseconds_since_epoch;
    /// The event's severity.
    SdkLogSeverity severity;
    /// The named per-subsystem target that logged it, e.g. "tile_fetch".
    string subsystem_target;
    /// The one-line already-scrubbed message.
    string message_line;
};

/// Diagnostics contract implemented by foreign code, receiving the SDK's
/// structured internal log events fire-and-forget (the Mozilla
/// application-services ADR 0009 sink pattern): the SDK copies an event
/// across the FFI and moves on immediately without awaiting the listener —
/// a slow listener can never slow the events it observes, and a frame-path
/// event never blocks on a foreign callback.
[Trait, WithForeign]
interface SdkDiagnosticEventListener {
    /// Receives one structured event (severity-gated at the subscription).
    void on_diagnostic_event(DiagnosticLogEvent event);
};

// ---------------------------------------------------------------------------
// Round-12 provider-failure diagnostics (addition-only; the owner ruling of
// 2026-10-29 on unclassified foreign provider failures). Foreign
// implementations of the `[Trait, WithForeign]` provider contracts fail in
// two shapes: the typed shape (the foreign code threw one of the contract's
// `SdkError` variants, which round-trips one-to-one) and the unclassified
// shape (the foreign code failed some other way, which the Rust conversion
// reports under `ProviderRequestRejected` with its verbatim reason — the
// honest but thin detail the seam exists for). Every failed foreign-callback
// provider call is recorded on the seam with the contract and the callback
// method it occurred on, a timestamp, the classification, and the verbatim
// failure text; `MapSdk::latest_provider_failure_diagnostics` reads the
// bounded ring back.
//
// Honest limitation of the classification: the unclassified shape is
// recognized by the unclassified-callback conversion's verbatim detail text
// (one Rust-side constant shared by the conversion and the adapter-side
// classifier), so a foreign implementation that itself throws
// `ProviderRequestRejected` carrying exactly that conversion phrasing is
// indistinguishable and lands as unclassified. Richer typed payloads (a
// provider status code, a retriable flag) would need a channel the frozen
// provider traits cannot carry without a contract change; until one exists
// the foreign side's only input to the seam is the failure text it throws,
// which the entries carry verbatim — no data is parsed or invented.
// ---------------------------------------------------------------------------

/// Which shape of foreign provider failure a diagnostic captured
/// (`ProviderFailureDiagnostic::failure_classification`).
enum ProviderFailureClassification {
    /// The foreign implementation failed without a mapped error (an
    /// exception the contract does not spell): the failure text is the
    /// uniffi callback machinery's verbatim reason for it.
    "UnclassifiedCallbackFailure",
    /// The foreign implementation threw one of the contract's `SdkError`
    /// variants, which round-trips typed through the seam: the failure text
    /// is the thrown binding error's verbatim display form.
    "MappedContractError",
};

/// One failed foreign-callback provider call, as retained in
/// `MapSdk::latest_provider_failure_diagnostics`'s bounded per-object ring.
/// Everything carried here is knowable at the adapter boundary that bridges
/// the foreign implementation onto the native provider registry; no data is
/// invented beyond it.
dictionary ProviderFailureDiagnostic {
    /// Wall-clock milliseconds since the Unix epoch, as the seam recorded
    /// the failure.
    u64 timestamp_milliseconds_since_epoch;
    /// Which failure shape this entry captured.
    ProviderFailureClassification failure_classification;
    /// The provider contract the foreign code implements, e.g.
    /// `GeocodingProvider`.
    string provider_contract_name;
    /// The contract method whose call failed, e.g. `forward_geocode`.
    string provider_method_name;
    /// The verbatim failure text: the uniffi callback machinery's reason for
    /// the unclassified shape, or the thrown binding error's display form
    /// for the mapped shape.
    string failure_detail;
};