Appendix A: Core Profile

The Core profile defines the fundamental data structures for SpatialDDS. It includes pose graphs, 3D geometry tiles, anchors, transforms, and generic blob transport. This is the minimal interoperable baseline for exchanging world models across devices and services.

Common Type Aliases (Normative)

// SPDX-License-Identifier: MIT
// SpatialDDS Common Type Aliases 1.8

#ifndef SPATIAL_COMMON_TYPES_INCLUDED
#define SPATIAL_COMMON_TYPES_INCLUDED

module builtin {
  @extensibility(APPENDABLE) struct Time {
    int64 sec;      // seconds since UNIX epoch (UTC), int64 (no year-2038 limit)
    uint32 nanosec; // nanoseconds [0, 1e9)
  };
};

module spatial {
  module common {
    typedef double BBox2D[4];
    // Flat 2D box, [min_x, min_y, max_x, max_y] — the two axis-minima then the two
    // axis-maxima, axes in the field's coordinate order. The order is the type's,
    // not the use-site's: a geographic bbox is [west, south, east, north] (GeoJSON
    // ordering, §2) and an image bbox is [u_min, v_min, u_max, v_max]; both are
    // this same min-min-max-max layout. A transposed pair is a well-formed box in
    // the wrong place that nothing downstream can detect, so the order is fixed here.
    // Flat 3D AABB, [min_x, min_y, min_z, max_x, max_y, max_z], for JSON/HTTP
    // payloads (see §2.10). This is the array form; the on-bus volumetric type
    // is the core::Aabb3 struct (min_xyz/max_xyz), NOT this typedef. The two are
    // distinct despite the near-identical names.
    typedef double Aabb3D[6];
    typedef double Vec3[3];
    typedef double Mat3x3[9];
    typedef double Mat6x6[36];
    typedef double Mat12x12[144];
    typedef double QuaternionXYZW[4];  // GeoPose order (x, y, z, w)
    // Canonical manifest reference: a spatialdds:// URI, empty when absent.
    // Defined here in common so every profile shares one spelling (core's
    // GeoAnchor and disco's announce types both carry it).
    typedef string SpatialUri;

    // 2D point/vector; used by polygon zone rings and crossing-line paths
    // (added 1.8). A struct rather than `typedef double Vec2[2]`, which is
    // what it was in the 1.8 draft until this type met its first independent
    // consumer.
    //
    // APPENDABLE is deliberate, and is the opposite of what a purity argument
    // would pick. Under XCDR2 an APPENDABLE struct carries a 4-byte DHEADER
    // per element; a FINAL one does not, and FINAL would therefore be
    // byte-identical to the `double[2]` this replaced. The reason to delimit
    // anyway is reader compatibility, measured against real messages:
    //
    //   schema says   wire carries          conformant reader   Foxglove
    //   FINAL         no per-element DHEADER        OK            FAIL
    //   APPENDABLE    per-element DHEADER           OK            OK
    //
    // A widely deployed omgidl deserializer requires a delimiter on every
    // nested struct element regardless of its declared extensibility, so a
    // FINAL element is unreadable there while an APPENDABLE one is readable
    // everywhere. That makes APPENDABLE strictly dominant rather than a
    // compromise: it costs four octets per vertex on definitions that are
    // latched and published once, and it buys every reader. Revisit only if
    // that deserializer changes upstream.
    //
    // DRAFTING RULES, from the episode that produced this type:
    //   1. An array typedef MUST NOT be used as a sequence element type.
    //      `sequence<Vec2>` over `typedef double Vec2[2]` is legal IDL that
    //      `idlc` accepts and that omgidl consumers reject outright ("We do
    //      not support composing variable length arrays with typedefs").
    //   2. A struct used as a sequence element SHOULD be APPENDABLE, so its
    //      elements are delimited on the wire, for the reason above.
    // The remaining array typedefs are fixed-size members only, never
    // sequence elements, and are unaffected by either rule.
    @extensibility(APPENDABLE) struct Vec2 {
      double x;
      double y;
    };

    enum CovarianceType {
      @value(0) COV_NONE,
      @value(3) COV_POS3,           // 3x3 position covariance
      @value(6) COV_POSE6,          // 6x6 pose covariance (pos + rot)
      @value(9) COV_ROT3,           // 3x3 orientation-only covariance (added in 1.6)
      @value(12) COV_POSE6_TWIST6   // 12x12 pose + velocity covariance (added in 1.6)
    };

    // Axis convention for poses expressed in this frame (added in 1.6).
    // Determines the meaning of the X, Y, Z axes of a PoseSE3 translation
    // and the body axes of its quaternion. When absent, consumers MUST
    // assume ENU. See §2.12 Coordinate Axis Convention.
    enum CoordConvention {
      @value(0) ENU,       // X-East,  Y-North, Z-Up         (robotics, ROS REP-103, GeoPose)
      @value(1) CV,        // X-Right, Y-Down,  Z-Forward    (OpenCV, colmap, hloc)
      @value(2) GRAPHICS,  // X-Right, Y-Up,    Z-Backward   (WebXR, OpenGL, three.js)
      @value(3) UNITY_LH,  // X-Right, Y-Up,    Z-Forward    (Unity, left-handed)
      @value(4) NED,       // X-North, Y-East,  Z-Down       (aviation, PX4, MAVLink)
      @value(5) OTHER      // Custom — consumer MUST consult MetaKV namespace="frame"
    };

    // Stable, typo-proof frame identity shared across all profiles.
    // Equality is by uuid; fqn is a normalized, human-readable alias.
    // Frame scale status (added 1.8). Parallel to CoordConvention: an unstated
    // frame property that silently corrupts every pose expressed in the frame.
    // SCALE_DECLARED = producer asserts meters_per_unit directly; SCALE_DERIVED =
    // meters_per_unit was recovered (e.g. from a legacy similarity transform);
    // SCALE_UNKNOWN = scale not established, meters_per_unit is not meaningful.
    enum ScaleStatus {
      @value(0) SCALE_UNKNOWN,
      @value(1) SCALE_DECLARED,
      @value(2) SCALE_DERIVED
    };
    // Covariance composition scope (added 1.8 Batch 2). Qualifies an observer
    // covariance: COV_SCOPE_LOCAL means it is stated against the emitting frame
    // and consumers MUST compose observer uncertainty through the frame chain;
    // COV_SCOPE_COMPOSED means the producer has already folded observer
    // uncertainty in and consumers MUST NOT apply it again. Absent means LOCAL.
    enum CovScope {
      @value(0) COV_SCOPE_LOCAL,
      @value(1) COV_SCOPE_COMPOSED
    };
    @extensibility(APPENDABLE) struct FrameRef {
      string uuid;                 // REQUIRED: stable identifier for the frame
      string fqn;                  // REQUIRED: normalized FQN, e.g., "oarc/rig01/cam_front"
      // Coordinate axis convention (added in 1.6). When has_coord_convention
      // is false, consumers MUST assume ENU per §2.12.
      boolean has_coord_convention;
      CoordConvention coord_convention;
      // Metric scale (added 1.8). When has_scale is false, the frame is metric:
      // 1 unit = 1 metre (every pre-1.8 publisher is correct by construction). A
      // producer that has NOT established the frame's scale MUST set has_scale =
      // true with scale_status = SCALE_UNKNOWN — absence is not the unknown state.
      // meters_per_unit is the single source of truth, valid when scale_status is
      // not SCALE_UNKNOWN (1.0 for a native-metric frame). display_unit is
      // presentation only (e.g. "m","mm","ft") and is never used for computation.
      boolean has_scale;
      ScaleStatus scale_status;
      double meters_per_unit;
      string display_unit;
    };

    // Simple typed key/value row for structured extension metadata.
    // Added in 1.7 draft rev so MetaKV can carry typed extensions inline.
    @extensibility(APPENDABLE) struct KV {
      string key;    // SHOULD be namespaced, e.g., "org.key"
      string value;  // scalar or string-valued extension
    };

    // Optional namespaced metadata bag for asset descriptors.
    @extensibility(APPENDABLE) struct MetaKV {
      string namespace;  // e.g., "sensing.vision.features"
      string json;       // JSON object string; producer-defined for this namespace
      // appended in 1.7 draft rev
      sequence<KV, 64> entries;   // typed extension rows; see typed-first rule (§ Appendix A)
    };

    // Uniform contract for asset references, covering fetch + integrity.
    @extensibility(APPENDABLE) struct AssetRef {
      string uri;          // required: how to fetch
      string media_type;   // required: IANA or registry-friendly type (with params)
      string hash;         // required: e.g., "sha256:<hex>"
      sequence<MetaKV, 16> meta;  // optional: zero or more namespaced bags
    };
  };
};

#endif // SPATIAL_COMMON_TYPES_INCLUDED

Typed-first extension rule (Normative). Producers SHOULD carry scalar and string-valued extensions in MetaKV.entries (typed key/value rows) and reserve MetaKV.json for genuinely free-form payloads. Consumers MUST accept either. Keys SHOULD be namespaced (org.key). This keeps extension data on the typed wire and inspectable without JSON parsing. Uncertainty is never free-form: covariance — including the observer covariance added in 1.8 (Detection3D.observer_cov, FusedTrack.observer_cov) — MUST be carried in the typed CovMatrix / matrix fields the profiles define, never relegated to MetaKV or json.

Core Module

// SPDX-License-Identifier: MIT
// SpatialDDS Core 1.8

#ifndef SPATIAL_COMMON_TYPES_INCLUDED
#include "types.idl"
#endif

module spatial {
  module core {

    // Module identity (authoritative string for interop)
    const string MODULE_ID = "spatial.core/1.8";

    // ---------- Utility ----------
    // Expose builtin Time under spatial::core
    typedef builtin::Time Time;
    typedef spatial::common::SpatialUri SpatialUri;

    @extensibility(APPENDABLE) struct PoseSE3 {
      spatial::common::Vec3 t;               // translation (x,y,z)
      spatial::common::QuaternionXYZW q;     // quaternion (x,y,z,w) in GeoPose order
    };

    // On-bus volumetric AABB: min and max corners as Vec3 in the enclosing
    // struct's frame. The flat 6-element array counterpart for JSON/HTTP
    // payloads is common::Aabb3D (§2.10); this struct is the wire form, and the
    // two are distinct despite the near-identical names.
    @extensibility(APPENDABLE) struct Aabb3 {
      spatial::common::Vec3 min_xyz;
      spatial::common::Vec3 max_xyz;
    };

    @extensibility(APPENDABLE) struct TileKey {
      @key uint32 x;     // tile coordinate (quadtree/3D grid)
      @key uint32 y;
      @key uint32 z;     // use 0 for 2D schemes
      @key uint8  level; // LOD level
    };

    // ---------- Geometry ----------
    enum PatchOp {
      @value(0) ADD,
      @value(1) REPLACE,
      @value(2) REMOVE
    };

    @extensibility(APPENDABLE) struct BlobRef {
      string blob_id;   // UUID or content-address
      string role;      // "mesh","attr/normals","pcc/geom","pcc/attr",...
      string checksum;  // SHA-256 (hex)
    };

    typedef spatial::common::FrameRef FrameRef;

    @extensibility(APPENDABLE) struct TileMeta {
      @key TileKey key;              // unique tile key
      boolean has_tile_id_compat;
      string  tile_id_compat;        // optional human-readable id
      Aabb3  aabb;                   // AABB in the local frame
      uint64 version;                // monotonic full-state version
      string encoding;               // "glTF+Draco","MPEG-PCC","V3C","PLY",...
      string checksum;               // checksum of composed tile
      sequence<string, 32> blob_ids; // blobs composing this tile
      // optional geo hints
      boolean has_centroid_llh;
      spatial::common::Vec3  centroid_llh; // lat,lon,alt (deg,deg,m)
      boolean has_radius_m;
      double  radius_m;              // rough extent (m)
      string schema_version;         // MUST be "spatial.core/1.8"
    };

    @extensibility(APPENDABLE) struct TilePatch {
      @key TileKey key;              // which tile
      uint64 revision;               // monotonic per-tile
      PatchOp op;                    // ADD/REPLACE/REMOVE
      string target;                 // submesh/attr/"all"
      sequence<BlobRef, 8> blobs;    // payload refs
      string post_checksum;          // checksum after apply
      Time   stamp;                  // production time
    };

    @extensibility(APPENDABLE) struct BlobChunk {
      // Composite key: (blob_id, index) uniquely identifies a chunk instance.
      @key string blob_id;   // which blob
      @key uint32 index;     // chunk index (0..N-1)
      uint32 total_chunks;   // total number of chunks expected for this blob_id
      uint32 crc32;          // CRC32 checksum over 'data'
      sequence<uint8, 65535> data; // <=65535 bytes per chunk (binding-compatible bound; see §3.2)
    };

    // ---------- Pose Graph (minimal) ----------
    enum EdgeTypeCore {
      @value(0) ODOM,
      @value(1) LOOP
    };
    // NOTE: The mapping extension profile (spatial.mapping/1.8) defines
    // mapping::EdgeType which extends EdgeTypeCore with additional constraint
    // types (INTER_MAP, GPS, ANCHOR, IMU_PREINT, GRAVITY, PLANE, SEMANTIC,
    // MANUAL). Values 0-1 are identical. Core consumers MAY downcast
    // mapping::Edge to core::Edge by treating unknown edge types as LOOP.

    // Discriminated union: exactly one covariance payload (or none) is serialized.
    @extensibility(APPENDABLE) union CovMatrix switch (spatial::common::CovarianceType) {
      case spatial::common::COV_NONE: uint8 none;
      case spatial::common::COV_POS3:  spatial::common::Mat3x3 pos;
      case spatial::common::COV_POSE6: spatial::common::Mat6x6 pose;
      case spatial::common::COV_ROT3:  spatial::common::Mat3x3 rot;          // added in 1.6
      case spatial::common::COV_POSE6_TWIST6: spatial::common::Mat12x12 pose_twist; // added in 1.6
    };

    // A metric pose bundled with its coordinate frame, uncertainty, and
    // timestamp. This is the self-contained local-coordinate counterpart
    // to GeoPose (which uses geographic coordinates). Use FramedPose
    // wherever a "located pose" is needed — it avoids scattering pose,
    // frame_ref, cov, and stamp across sibling fields on the parent struct.
    @extensibility(APPENDABLE) struct FramedPose {
      PoseSE3   pose;       // translation + orientation
      FrameRef  frame_ref;  // which coordinate frame this pose is expressed in
      CovMatrix cov;        // uncertainty (COV_NONE when absent)
      Time      stamp;      // when this pose was valid
    };

    // PlannedTrajectory carries a sequence of future poses representing
    // an agent's intended path. It is the planning counterpart to a
    // perception trajectory: both are sequences of poses, but a
    // PlannedTrajectory extends into the future and is conditioned on
    // intended actions rather than past measurements.
    //
    // Consumers MAY use PlannedTrajectory for:
    // - Cooperative collision avoidance (avoid other agents' plans)
    // - Intent visualization in digital twins
    // - Predictive resource allocation (anticipate coverage changes)
    //
    // Topic: spatialdds/<scene>/plan/<agent_id>/trajectory/v1
    // QoS:   EVENT_RT (RELIABLE, ordered, ~100 ms deadline)
    @extensibility(APPENDABLE) struct PlannedWaypoint {
      PoseSE3   pose;                   // planned pose at this waypoint
      Time      stamp;                  // planned arrival time
      boolean   has_velocity;
      spatial::common::Vec3 velocity;   // planned velocity (m/s)
      boolean   has_uncertainty;
      float     position_uncertainty_m; // 1-sigma position uncertainty
      boolean   has_confidence;
      float     confidence;             // planner confidence [0,1]
    };

    @extensibility(APPENDABLE) struct PlannedTrajectory {
      @key string agent_id;             // who is planning
      string  plan_id;                  // unique plan identifier (changes on replan)
      uint32  plan_revision;            // increments on each replan

      FrameRef frame_ref;               // coordinate frame for all waypoints

      sequence<PlannedWaypoint, 256> waypoints;

      // Planning metadata
      boolean has_goal_pose;
      PoseSE3 goal_pose;                // final destination (may differ from last waypoint)

      boolean has_horizon_sec;
      float   horizon_sec;              // planning horizon in seconds

      boolean has_replan_rate_hz;
      float   replan_rate_hz;           // how often the plan is updated

      Time    stamp;                    // when this plan was computed
      string  schema_version;           // "spatial.core/1.8"
    };

    // EntityBinding correlates messages across different SpatialDDS topics
    // that refer to the same physical entity. It is the minimal primitive
    // for scene graph construction: consumers receive bindings and assemble
    // their own entity hierarchies.
    //
    // Example: a fused track, a geometry tile, and a SpatialZone occupant
    // entry all describe the same vehicle. An EntityBinding links them
    // by their respective keys.
    //
    // Topic: spatialdds/<scene>/entity/binding/v1
    // QoS:   RELIABLE + TRANSIENT_LOCAL (late joiners receive current bindings)
    @extensibility(APPENDABLE) struct ComponentRef {
      string topic;                     // topic name (e.g., "spatialdds/.../fusion/track/v1")
      string key;                       // key value on that topic (e.g., track_id = "fused-42")
    };

    @extensibility(APPENDABLE) struct EntityBinding {
      @key string entity_id;            // stable entity identifier

      string entity_class;              // semantic class (e.g., "vehicle", "pedestrian", "drone")

      sequence<ComponentRef, 16> components; // references to messages on other topics

      // Optional: entity's authoritative pose (convenience — same as the pose
      // on one of the component topics, but saves a lookup).
      boolean    has_pose;
      FramedPose pose;

      Time   stamp;
      string source_id;                 // who published this binding (e.g., fusion service)
      string schema_version;            // "spatial.core/1.8"
    };

    @extensibility(APPENDABLE) struct Node {
      @key string map_id;
      @key string node_id;     // unique keyframe id; instance identity is (map_id, node_id)
      PoseSE3 pose;            // pose in frame_ref
      CovMatrix cov;           // covariance payload (COV_NONE when absent)
      Time    stamp;
      FrameRef frame_ref;      // e.g., "map"
      string  source_id;
      uint64  seq;             // per-source monotonic
      uint64  graph_epoch;     // for major rebases/merges
    };

    @extensibility(APPENDABLE) struct Edge {
      @key string map_id;
      @key string edge_id;     // unique edge id; instance identity is (map_id, edge_id)
      string from_id;          // source node
      string to_id;            // target node
      EdgeTypeCore type;       // ODOM or LOOP
      PoseSE3 T_from_to;       // relative transform from->to
      spatial::common::Mat6x6 information; // 6x6 info matrix (row-major)
      Time   stamp;
      string source_id;
      uint64 seq;
      uint64 graph_epoch;
    };

    // ---------- Geo anchoring ----------
    @extensibility(APPENDABLE) struct GeoPose {
      double lat_deg;
      double lon_deg;
      double alt_m;            // ellipsoidal meters
      // orientation (x,y,z,w) in the local ENU tangent frame at (lat,lon,alt)
      spatial::common::QuaternionXYZW q;
      Time   stamp;
      // Exactly one covariance payload will be present based on the discriminator.
      CovMatrix cov;
    };

    // ---------- GNSS diagnostics ----------
    enum GnssFixType {
      @value(0) NO_FIX,         // unable to determine position
      @value(1) FIX_2D,         // 2D fix (no altitude)
      @value(2) FIX_3D,         // 3D autonomous fix
      @value(3) DGPS,           // differential GPS correction
      @value(4) RTK_FLOAT,      // RTK float solution
      @value(5) RTK_FIXED,      // RTK fixed (integer ambiguity resolved)
      @value(6) SBAS,           // satellite-based augmentation (WAAS/EGNOS)
      @value(7) DEAD_RECKONING, // DR-only (no satellite fix)
      @value(8) UNKNOWN_FIX     // status not yet determined
    };

    module GnssService {
      const uint16 GPS     = 0x0001;
      const uint16 GLONASS = 0x0002;
      const uint16 BEIDOU  = 0x0004;  // includes COMPASS
      const uint16 GALILEO = 0x0008;
      const uint16 QZSS    = 0x0010;
      const uint16 IRNSS   = 0x0020;  // NavIC
      const uint16 SBAS_SV = 0x0040;  // SBAS ranging SVs
    };

    @extensibility(APPENDABLE) struct NavSatStatus {
      @key string gnss_id;      // receiver identifier, matches GeoPose stream

      GnssFixType fix_type;     // current fix quality
      uint16      service;      // GnssService bitmask: which constellations
      uint16      num_satellites; // SVs used in fix

      // Dilution of precision (from GSA sentence)
      boolean has_dop;
      float   pdop;             // position DOP  (valid when has_dop)
      float   hdop;             // horizontal DOP (valid when has_dop)
      float   vdop;             // vertical DOP   (valid when has_dop)

      // Ground velocity (from RMC sentence)
      boolean has_velocity;
      float   speed_mps;        // speed over ground, m/s (valid when has_velocity)
      float   course_deg;       // course over ground, degrees true north (valid when has_velocity)

      // Differential correction metadata (from GGA fields 13-14)
      boolean has_diff_age;
      float   diff_age_s;       // age of differential correction, seconds
      uint16  diff_station_id;  // reference station ID

      Time   stamp;             // should match the associated GeoPose.stamp
      string schema_version;    // MUST be "spatial.core/1.8"
    };

    @extensibility(APPENDABLE) struct GeoAnchor {
      @key string anchor_id;   // e.g., "anchor/4th-and-main"
      string map_id;
      FrameRef frame_ref;      // local frame (e.g., "map")
      GeoPose geopose;         // global pose
      string  method;          // "GNSS","VisualFix","Surveyed","Fusion"
      double  confidence;      // 0..1 scalar summary, retained
      string  checksum;        // integrity/versioning
      // Directional uncertainty (added 1.8). COV_NONE when absent, so existing
      // anchors remain valid. Expressed in the anchor's local ENU tangent frame
      // at the encoded GeoPose position (same convention as the GeoPose
      // orientation rule, §3.2). When both are present, confidence SHOULD be a
      // monotone summary of cov (e.g. from its largest eigenvalue) so the two
      // never contradict. Appended at struct end per APPENDABLE rules.
      CovMatrix cov;
      // Content reference (added in the 1.8 revision; 1.9 candidates entry 2).
      // An anchor MAY name a content manifest, resolvable per §7.5; empty when
      // absent, so existing anchors are unchanged in meaning and on the wire.
      // Mirrors ContentAnnounce.manifest_uri exactly: same SpatialUri type, same
      // name, same empty-is-absent mechanism (no has_ guard). The reference
      // binds content to this anchor's frame and precision — the anchor's method
      // and cov are the claim level the binding inherits. Appended at struct end
      // per APPENDABLE rules.
      SpatialUri manifest_uri;
    };

    @extensibility(APPENDABLE) struct FrameTransform {
      @key string transform_id; // e.g., "map->ENU@lat,lon,alt"
      FrameRef parent_ref;      // global frame (ENU@..., ECEF, ...)
      FrameRef child_ref;       // local frame ("map")
      PoseSE3 T_parent_child;   // transform parent->child
      Time    stamp;
      CovMatrix cov;             // covariance payload (COV_NONE when absent)
    };

    // ---------- Snapshot / Catch-up ----------
    @extensibility(APPENDABLE) struct SnapshotRequest {
      @key TileKey key;        // which tile
      uint64 up_to_revision;   // 0 = latest
    };

    @extensibility(APPENDABLE) struct SnapshotResponse {
      @key TileKey key;                 // tile key
      uint64 revision;                  // snapshot revision served
      sequence<string, 64> blob_ids;    // composing blobs
      string checksum;                  // composed state checksum
    };

  }; // module core
};   // module spatial