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 reserveMetaKV.jsonfor 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 typedCovMatrix/ matrix fields the profiles define, never relegated toMetaKVorjson.
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