Appendix D: Extension Profiles

These extensions provide domain-specific capabilities beyond the Core profile. The Sensing Common module supplies reusable sensing metadata, ROI negotiation structures, and codec/payload descriptors that the specialized sensor profiles build upon. The VIO profile carries raw and fused IMU/magnetometer samples. The Vision profile shares camera metadata, encoded frames, and optional feature tracks for perception pipelines. The SLAM Frontend profile adds features and keyframes for SLAM and SfM pipelines. The Semantics profile allows 2D and 3D object detections to be exchanged for AR, robotics, and analytics use cases. The Radar profile provides detection-centric radar metadata and per-frame detection sets, plus a tensor transport path for raw or processed radar data cubes used in ISAC and ML workloads. The Lidar profile transports compressed point clouds, associated metadata, and optional detections for mapping and perception workloads. The AR+Geo profile adds GeoPose, frame transforms, and geo-anchoring structures, which allow clients to align local coordinate systems with global reference frames and support persistent AR content. The Mapping profile provides map lifecycle metadata, multi-source pose graph edge types, inter-map alignment primitives, and lifecycle events for multi-agent collaborative mapping. The Spatial Events profile provides typed, spatially-scoped events, zone definitions, and zone state summaries for smart infrastructure alerting, anomaly detection, and capacity management.

Common type aliases and geometry primitives are defined once in Appendix A. Extension modules import those shared definitions and MUST NOT re-declare them.

Sensing Common Extension

Shared base types, enums, and ROI negotiation utilities reused by all sensing profiles (radar, lidar, vision).

// SPDX-License-Identifier: MIT
// SpatialDDS Sensing Common 1.5 (Extension module)

#ifndef SPATIAL_CORE_INCLUDED
#define SPATIAL_CORE_INCLUDED
#include "core.idl"
#endif

module spatial { module sensing { module common {

  const string MODULE_ID = "spatial.sensing.common/1.5";

  // --- Standard sizing tiers ---
  // Use these to bound sequences for detections and other per-frame arrays.
  const uint32 SZ_TINY   = 64;
  const uint32 SZ_SMALL  = 256;
  const uint32 SZ_MEDIUM = 2048;
  const uint32 SZ_LARGE  = 8192;
  const uint32 SZ_XL     = 32768;

  // Reuse Core primitives (time, pose, blob references)
  typedef builtin::Time    Time;
  typedef spatial::core::PoseSE3 PoseSE3;
  typedef spatial::core::BlobRef BlobRef;
  typedef spatial::common::FrameRef FrameRef;

  // ---- Axes & Regions (for tensors or scans) ----
  enum AxisEncoding {
    @value(0) AXIS_CENTERS,
    @value(1) AXIS_LINSPACE
  };

  // Compact parametric axis definition
  @extensibility(APPENDABLE) struct Linspace {
    double start;   // first point
    double step;    // spacing (may be negative for descending axes)
    uint32 count;   // number of samples (>=1)
  };

  // Discriminated union: carries only one encoding on wire
  @extensibility(APPENDABLE) union AxisSpec switch (AxisEncoding) {
    case AXIS_CENTERS:  sequence<double, 65535> centers;
    case AXIS_LINSPACE: Linspace lin;
  };

  @extensibility(APPENDABLE) struct Axis {
    string   name;    // "range","azimuth","elevation","doppler","time","channel"
    string   unit;    // "m","deg","m/s","Hz","s",...
    AxisSpec spec;    // encoding of the axis samples (centers or linspace)
  };

  @extensibility(APPENDABLE) struct ROI {
    // Range bounds (meters). When has_range == false, consumers MUST ignore range_min/range_max.
    boolean has_range;
    float   range_min;
    float   range_max;

    // Azimuth bounds (degrees). When has_azimuth == false, azimuth bounds are unspecified.
    boolean has_azimuth;
    float   az_min;
    float   az_max;

    // Elevation bounds (degrees). When has_elevation == false, elevation bounds are unspecified.
    boolean has_elevation;
    float   el_min;
    float   el_max;

    // Doppler bounds (m/s). When has_doppler == false, doppler_min/doppler_max are unspecified.
    boolean has_doppler;
    float   dop_min;
    float   dop_max;

    // Image-plane ROI for vision (pixels). When has_image_roi == false, u/v bounds are unspecified.
    boolean has_image_roi;
    int32   u_min;
    int32   v_min;
    int32   u_max;
    int32   v_max;

    // Indicates this ROI covers the entire valid domain of its axes. When true, all numeric bounds may be ignored.
    boolean global;
  };

  // ---- Codecs / Payload kinds (shared enums) ----
  enum Codec {
    @value(0)  CODEC_NONE,
    @value(1)  LZ4,
    @value(2)  ZSTD,
    @value(3)  GZIP,
    @value(10) DRACO,     // geometry compression
    @value(20) JPEG,
    @value(21) H264,
    @value(22) H265,
    @value(23) AV1,
    @value(40) FP8Q,
    @value(41) FP4Q,
    @value(42) AE_V1
  };

  enum PayloadKind {
    @value(0)  DENSE_TILES,    // tiled dense blocks (e.g., tensor tiles)
    @value(1)  SPARSE_COO,     // sparse indices + values
    @value(2)  LATENT,         // learned latent vectors
    @value(10) BLOB_GEOMETRY,  // PCC/PLY/glTF+Draco
    @value(11) BLOB_RASTER     // JPEG/GOP chunk(s)
  };

  enum SampleType {        // post-decode voxel/point sample type
    @value(0) U8_MAG,
    @value(1) F16_MAG,
    @value(2) CF16,
    @value(3) CF32,
    @value(4) MAGPHASE_S8
  };

  // ---- Stream identity & calibration header shared by sensors ----
  @extensibility(APPENDABLE) struct StreamMeta {
    @key string stream_id;        // stable id for this sensor stream
    FrameRef frame_ref;           // mounting frame (Core frame naming)
    PoseSE3  T_bus_sensor;        // extrinsics (sensor in bus frame)
    double   nominal_rate_hz;     // advertised cadence
    string   schema_version;      // MUST be "spatial.sensing.common/1.5"
  };

  // ---- Frame index header shared by sensors (small, on-bus) ----
  @extensibility(APPENDABLE) struct FrameHeader {
    @key string stream_id;
    uint64 frame_seq;
    Time   t_start;
    Time   t_end;
    // Optional sensor pose at acquisition (moving platforms)
    boolean has_sensor_pose;
    PoseSE3 sensor_pose;
    // data pointers: heavy bytes referenced as blobs
    sequence<BlobRef, SZ_SMALL> blobs;
  };

  // ---- Quality & health (uniform across sensors) ----
  enum Health {
    @value(0) OK,
    @value(1) DEGRADED,
    @value(2) ERROR
  };

  @extensibility(APPENDABLE) struct FrameQuality {
    boolean has_snr_db;
    float   snr_db;          // valid when has_snr_db == true
    float   percent_valid;   // 0..100
    Health health;
    string note;             // short diagnostic
  };

  // ---- ROI request/reply (control-plane pattern) ----
  @extensibility(APPENDABLE) struct ROIRequest {
    @key string stream_id;
    uint64 request_id;
    Time   t_start; Time t_end;
    ROI    roi;
    boolean wants_payload_kind; PayloadKind desired_payload_kind;
    boolean wants_codec;       Codec       desired_codec;
    boolean wants_sample_type; SampleType  desired_sample_type;
    int32  max_bytes;          // -1 for unlimited
  };

  @extensibility(APPENDABLE) struct ROIReply {
    @key string stream_id;
    uint64 request_id;
    // Typically returns new frames whose blobs contain only the ROI
    sequence<spatial::sensing::common::FrameHeader, 64> frames;
  };

}; }; };

Standard Sequence Bounds (Normative)

Payload Recommended Bound Rationale
2D Detections (per frame) SZ_MEDIUM (2048) Typical object detectors
3D Detections (LiDAR) SZ_SMALL (256) Clusters/objects, not raw points
Radar Detections (micro-dets) SZ_XL (32768) Numerous sparse returns per frame
Keypoints/Tracks (per frame) SZ_LARGE (8192) Feature-rich frames

Producers SHOULD choose the smallest tier that covers real workloads; exceeding these bounds requires a new profile minor.

Axis Encoding (Normative)

The Axis struct embeds a discriminated union to ensure only one encoding is transmitted on the wire.

enum AxisEncoding { AXIS_CENTERS = 0, AXIS_LINSPACE = 1 };
@extensibility(APPENDABLE) struct Linspace { double start; double step; uint32 count; };
@extensibility(APPENDABLE) union AxisSpec switch (AxisEncoding) {
  case AXIS_CENTERS:  sequence<double, 65535> centers;
  case AXIS_LINSPACE: Linspace lin;
  default: ;
};
@extensibility(APPENDABLE) struct Axis { string name; string unit; AxisSpec spec; };
  • AXIS_CENTERS — Explicit sample positions carried as double values.
  • AXIS_LINSPACE — Uniform grid defined by start + i * step for i ∈ [0, count‑1].
  • Negative step indicates descending axes.
  • count MUST be ≥ 1 and step * (count – 1) + start yields the last coordinate.

IDL Tooling Notes (Non-Consecutive Enums)

Several enumerations in the SpatialDDS 1.5 profiles use intentionally sparse or non-consecutive numeric values. These enums are designed for forward extensibility (e.g., reserving ranges for future codecs, layouts, or pixel formats). Because of this, certain DDS toolchains (including Cyclone DDS’s idlc) may emit non-fatal warnings such as:

“enum literal values are not consecutive”

These warnings do not indicate a schema error. All affected enums are valid IDL4.x and interoperable on the wire.

The intentionally sparse enums are: - CovarianceType (types.idl) - Codec (common.idl) - PayloadKind (common.idl) - RigRole (vision.idl) - RadSensorType (rad.idl) - RadTensorLayout (rad.idl) - CloudEncoding (lidar.idl) - ColorSpace (vision.idl) - PixFormat (vision.idl)

No changes are required for implementers. These warnings may be safely ignored.

VIO / Inertial Extension

Raw IMU/mag samples, 9-DoF bundles, and fused state outputs.

// SPDX-License-Identifier: MIT
// SpatialDDS VIO/Inertial 1.5

#ifndef SPATIAL_CORE_INCLUDED
#define SPATIAL_CORE_INCLUDED
#include "core.idl"
#endif

module spatial {
  module vio {

    const string MODULE_ID = "spatial.vio/1.5";

    typedef builtin::Time Time;
    typedef spatial::common::FrameRef FrameRef;

    // IMU calibration
    @extensibility(APPENDABLE) struct ImuInfo {
      @key string imu_id;
      FrameRef frame_ref;
      double accel_noise_density;    // (m/s^2)/√Hz
      double gyro_noise_density;     // (rad/s)/√Hz
      double accel_random_walk;      // (m/s^3)/√Hz
      double gyro_random_walk;       // (rad/s^2)/√Hz
      Time   stamp;
    };

    // Raw IMU sample
    @extensibility(APPENDABLE) struct ImuSample {
      @key string imu_id;
      spatial::common::Vec3 accel;   // m/s^2
      spatial::common::Vec3 gyro;    // rad/s
      Time   stamp;
      string source_id;
      uint64 seq;
    };

    // Magnetometer
    @extensibility(APPENDABLE) struct MagnetometerSample {
      @key string mag_id;
      spatial::common::Vec3 mag;     // microtesla
      Time   stamp;
      FrameRef frame_ref;
      string source_id;
      uint64 seq;
    };

    // Convenience raw 9-DoF bundle
    @extensibility(APPENDABLE) struct SensorFusionSample {
      @key string fusion_id;         // e.g., device id
      spatial::common::Vec3 accel;   // m/s^2
      spatial::common::Vec3 gyro;    // rad/s
      spatial::common::Vec3 mag;     // microtesla
      Time   stamp;
      FrameRef frame_ref;
      string source_id;
      uint64 seq;
    };

    // Fused state (orientation ± position)
    enum FusionMode {
      @value(0) ORIENTATION_3DOF,
      @value(1) ORIENTATION_6DOF,
      @value(2) POSE_6DOF
    };
    enum FusionSourceKind {
      @value(0) EKF,
      @value(1) AHRS,
      @value(2) VIO_FUSED,
      @value(3) IMU_ONLY,
      @value(4) MAG_AIDED,
      @value(5) AR_PLATFORM
    };

    @extensibility(APPENDABLE) struct FusedState {
      @key string fusion_id;
      FusionMode       mode;
      FusionSourceKind source_kind;

      spatial::common::QuaternionXYZW q; // quaternion (x,y,z,w) in GeoPose order
      boolean has_position;
      spatial::common::Vec3 t;       // meters, in frame_ref

      boolean has_gravity;
      spatial::common::Vec3  gravity;    // m/s^2
      boolean has_lin_accel;
      spatial::common::Vec3  lin_accel;  // m/s^2
      boolean has_gyro_bias;
      spatial::common::Vec3  gyro_bias;  // rad/s
      boolean has_accel_bias;
      spatial::common::Vec3  accel_bias; // m/s^2

      boolean has_cov_orient;
      spatial::common::Mat3x3  cov_orient; // 3x3 covariance
      boolean has_cov_pos;
      spatial::common::Mat3x3  cov_pos;    // 3x3 covariance

      Time   stamp;
      FrameRef frame_ref;
      string source_id;
      uint64 seq;
      double quality;                // 0..1
    };

  }; // module vio
};

Vision Extension

Camera intrinsics, video frames, and keypoints/tracks for perception and analytics pipelines. ROI semantics follow §2 Conventions for global normative rules; axes use the Sensing Common AXIS_CENTERS/AXIS_LINSPACE union encoding. See §2 Conventions for global normative rules.

Pre-Rectified Images (Normative)
When images have been rectified (undistorted) before publication, producers MUST set dist = NONE, dist_params to an empty sequence, and model = PINHOLE. Consumers receiving dist = NONE MUST NOT apply any distortion correction.

Image Dimensions (Normative)
CamIntrinsics.width and CamIntrinsics.height are REQUIRED and MUST be populated from the actual image dimensions. A VisionMeta sample with width = 0 or height = 0 is malformed and consumers MAY reject it.

Distortion Model Mapping (Informative)
Vision uses CamModel + Distortion, while SLAM Frontend uses DistortionModelKind. Implementers bridging the two SHOULD map as follows:

Vision SLAM Frontend Notes
Distortion.NONE DistortionModelKind.NONE No distortion
Distortion.RADTAN DistortionModelKind.RADTAN Brown-Conrady
Distortion.KANNALA_BRANDT DistortionModelKind.KANNALA_BRANDT Fisheye
CamModel.FISHEYE_EQUIDISTANT DistortionModelKind.EQUIDISTANT Equivalent naming
// SPDX-License-Identifier: MIT
// SpatialDDS Vision (sensing.vision) 1.5 — Extension profile

#ifndef SPATIAL_CORE_INCLUDED
#define SPATIAL_CORE_INCLUDED
#include "core.idl"
#endif
#ifndef SPATIAL_SENSING_COMMON_INCLUDED
#define SPATIAL_SENSING_COMMON_INCLUDED
#include "common.idl"
#endif

module spatial { module sensing { module vision {

  // Module identifier for discovery and schema registration
  const string MODULE_ID = "spatial.sensing.vision/1.5";

  // Reuse Core + Sensing Common
  typedef builtin::Time                      Time;
  typedef spatial::core::PoseSE3                   PoseSE3;
  typedef spatial::core::BlobRef                   BlobRef;
  typedef spatial::common::FrameRef               FrameRef;

  typedef spatial::sensing::common::Codec          Codec;        // JPEG/H264/H265/AV1, etc.
  typedef spatial::sensing::common::PayloadKind    PayloadKind;  // use BLOB_RASTER for frames/GOPs
  typedef spatial::sensing::common::SampleType     SampleType;
  typedef spatial::sensing::common::Axis           Axis;
  typedef spatial::sensing::common::ROI            ROI;
  typedef spatial::sensing::common::StreamMeta     StreamMeta;
  typedef spatial::sensing::common::FrameHeader    FrameHeader;
  typedef spatial::sensing::common::FrameQuality   FrameQuality;
  typedef spatial::sensing::common::ROIRequest     ROIRequest;
  typedef spatial::sensing::common::ROIReply       ROIReply;

  // ROI bounds follow Sensing Common presence flags.
  // Axis samples are encoded via the Sensing Common union (AXIS_CENTERS or AXIS_LINSPACE).

  // Camera / imaging specifics
  enum CamModel {
    @value(0) PINHOLE,
    @value(1) FISHEYE_EQUIDISTANT,
    @value(2) KB_4,
    @value(3) OMNI
  };
  enum Distortion {
    @value(0) NONE,
    @value(1) RADTAN,
    @value(2) KANNALA_BRANDT
  };
  enum PixFormat {
    @value(0)  UNKNOWN,
    @value(1)  YUV420,
    @value(2)  RGB8,
    @value(3)  BGR8,
    @value(4)  RGBA8,
    @value(5)  DEPTH16,           // 16-bit unsigned depth image (mm)
    @value(10) RAW10,
    @value(12) RAW12,
    @value(16) RAW16
  };
  // DEPTH16 frames carry per-pixel unsigned 16-bit depth values. The default
  // unit is millimeters (range 0-65535 mm ~= 0-65.5 m), consistent with
  // OpenNI, RealSense SDK, and ARKit conventions. A value of 0 denotes no
  // measurement (invalid/missing depth). Publishers using a different unit
  // (e.g., 100 um for sub-millimeter sensors) MUST include a depth_unit key in
  // the corresponding VisionMeta.base.attributes sequence with json value
  // {"unit": "100um"} (or "mm", "m"). When depth_unit is absent, consumers
  // MUST assume millimeters.
  //
  // A depth stream is published as a separate VisionMeta + VisionFrame pair
  // with pix = DEPTH16 and a stream_id distinct from the co-located color
  // stream. The rig_id field links the depth and color streams for spatial
  // alignment; CamIntrinsics carries the depth camera's intrinsic calibration.
  // For sensors with factory-aligned depth and color (e.g., iPhone LiDAR),
  // both streams share the same CamIntrinsics and FrameRef.
  enum ColorSpace {
    @value(0)  SRGB,
    @value(1)  REC709,
    @value(2)  REC2020,
    @value(10) LINEAR
  };
  enum RigRole {
    @value(0) LEFT,
    @value(1) RIGHT,
    @value(2) CENTER,
    @value(3) FRONT,
    @value(4) FRONT_LEFT,
    @value(5) FRONT_RIGHT,
    @value(6) BACK,
    @value(7) BACK_LEFT,
    @value(8) BACK_RIGHT,
    @value(9) AUX,
    @value(10) PANORAMIC,          // stitched panoramic view
    @value(11) EQUIRECTANGULAR     // equirectangular 360° projection
  };

  @extensibility(APPENDABLE) struct CamIntrinsics {
    CamModel model;
    uint16 width;  uint16 height;       // REQUIRED: image dimensions in pixels
    float fx; float fy; float cx; float cy;
    Distortion dist;                    // NONE for pre-rectified images
    sequence<float,16> dist_params;     // empty when dist == NONE
    float shutter_us;                   // exposure time
    float readout_us;                   // rolling-shutter line time (0=global)
    PixFormat pix;  ColorSpace color;
    string calib_version;               // hash or tag
  };

  // Static description — RELIABLE + TRANSIENT_LOCAL (late joiners receive the latest meta)
  @extensibility(APPENDABLE) struct VisionMeta {
    @key string stream_id;
    StreamMeta base;                    // frame_ref, T_bus_sensor, nominal_rate_hz
    CamIntrinsics K;                    // intrinsics
    RigRole role;                       // for stereo/rigs
    string rig_id;                      // shared id across synchronized cameras

    // Default payload (frames ride as blobs)
    Codec codec;                        // JPEG/H264/H265/AV1 or NONE
    PixFormat pix;                      // for RAW payloads
    ColorSpace color;
    string schema_version;              // MUST be "spatial.sensing.vision/1.5"
  };

  // Per-frame index — BEST_EFFORT + KEEP_LAST=1 (large payloads referenced via blobs)
  @extensibility(APPENDABLE) struct VisionFrame {
    @key string stream_id;
    uint64 frame_seq;

    FrameHeader hdr;                    // t_start/t_end, optional sensor_pose, blobs[]

    // May override meta per-frame
    Codec codec;
    PixFormat pix;
    ColorSpace color;

    boolean has_line_readout_us;
    float   line_readout_us;            // valid when has_line_readout_us == true
    boolean rectified;                  // true if pre-rectified to pinhole
    boolean is_key_frame;               // true if this frame is a selected keyframe

    FrameQuality quality;               // shared health/SNR notes
  };

  // Optional lightweight derivatives (for VIO/SfM/analytics)
  @extensibility(APPENDABLE) struct Keypoint2D { float u; float v; float score; };
  @extensibility(APPENDABLE) struct Track2D {
    uint64 id;
    sequence<Keypoint2D, spatial::sensing::common::SZ_LARGE> trail;
  };

  // Detections topic — BEST_EFFORT
  @extensibility(APPENDABLE) struct VisionDetections {
    @key string stream_id;
    uint64 frame_seq;
    Time   stamp;
    sequence<Keypoint2D, spatial::sensing::common::SZ_LARGE> keypoints;
    sequence<Track2D, spatial::sensing::common::SZ_MEDIUM>    tracks;
    // Masks/boxes can be added in Semantics profile to keep Vision lean
  };

}; }; };

SLAM Frontend Extension

Per-keyframe features, matches, landmarks, tracks, and camera calibration.

// SPDX-License-Identifier: MIT
// SpatialDDS SLAM Frontend 1.5

#ifndef SPATIAL_CORE_INCLUDED
#define SPATIAL_CORE_INCLUDED
#include "core.idl"
#endif

module spatial {
  module slam_frontend {

    const string MODULE_ID = "spatial.slam_frontend/1.5";

    // Reuse core: Time, etc.
    typedef builtin::Time Time;
    typedef spatial::common::FrameRef FrameRef;

    // Camera calibration
    enum DistortionModelKind {
      @value(0) NONE,
      @value(1) RADTAN,
      @value(2) EQUIDISTANT,
      @value(3) KANNALA_BRANDT
    };

    @extensibility(APPENDABLE) struct CameraInfo {
      @key string camera_id;
      uint32 width;  uint32 height;   // pixels
      double fx; double fy;           // focal (px)
      double cx; double cy;           // principal point (px)
      DistortionModelKind dist_kind;
      sequence<double, 8> dist;       // model params (bounded)
      FrameRef frame_ref;             // camera frame
      Time   stamp;                   // calib time (or 0 if static)
    };

    // 2D features & descriptors per keyframe
    @extensibility(APPENDABLE) struct Feature2D {
      double u; double v;     // pixel coords
      float  scale;           // px
      float  angle;           // rad [0,2π)
      float  score;           // detector response
    };

    @extensibility(APPENDABLE) struct KeyframeFeatures {
      @key string node_id;                  // keyframe id
      string camera_id;
      string desc_type;                     // "ORB32","BRISK64","SPT256Q",...
      uint32 desc_len;                      // bytes per descriptor
      boolean row_major;                    // layout hint
      sequence<Feature2D, 4096> keypoints;  // ≤4096
      sequence<uint8, 1048576> descriptors; // ≤1 MiB packed bytes
      Time   stamp;
      string source_id;
      uint64 seq;
    };

    // Optional cross-frame matches
    @extensibility(APPENDABLE) struct FeatureMatch {
      string node_id_a;  uint32 idx_a;
      string node_id_b;  uint32 idx_b;
      float  score;      // similarity or distance
    };

    @extensibility(APPENDABLE) struct MatchSet {
      @key string match_id;                // e.g., "kf_12<->kf_18"
      sequence<FeatureMatch, 8192> matches;
      Time   stamp;
      string source_id;
    };

    // Sparse 3D landmarks & tracks (optional)
    @extensibility(APPENDABLE) struct Landmark {
      @key string lm_id;
      string map_id;
      spatial::common::Vec3 p;
      boolean has_cov;
      spatial::common::Mat3x3  cov;        // 3x3 pos covariance (row-major)
      sequence<uint8, 4096> desc;          // descriptor bytes
      string desc_type;
      Time   stamp;
      string source_id;
      uint64 seq;
    };

    @extensibility(APPENDABLE) struct TrackObs {
      string node_id;                      // observing keyframe
      double u; double v;                  // pixel coords
    };

    @extensibility(APPENDABLE) struct Tracklet {
      @key string track_id;
      boolean has_lm_id;                   // true when lm_id is populated
      string  lm_id;                       // link to Landmark when present
      sequence<TrackObs, 64> obs;          // ≤64 obs
      string source_id;
      Time   stamp;
    };

  }; // module slam_frontend
};

Semantics / Perception Extension

2D detections tied to keyframes; 3D oriented boxes in world frames (optionally tiled).

Size Convention (Normative)
Detection3D.size is the extent of the oriented bounding box in the object's local frame (center + q):
size[0] = width (local X), size[1] = height (local Z), size[2] = depth (local Y).
All values are in meters and MUST be non-negative. For datasets that use (width, length, height), map as (width, height, length).

// SPDX-License-Identifier: MIT
// SpatialDDS Semantics 1.5

#ifndef SPATIAL_CORE_INCLUDED
#define SPATIAL_CORE_INCLUDED
#include "core.idl"
#endif
#ifndef SPATIAL_SENSING_COMMON_INCLUDED
#define SPATIAL_SENSING_COMMON_INCLUDED
#include "common.idl"
#endif

module spatial {
  module semantics {

    const string MODULE_ID = "spatial.semantics/1.5";

    typedef builtin::Time Time;
    typedef spatial::core::TileKey TileKey;
    typedef spatial::common::FrameRef FrameRef;

    // 2D detections per keyframe (image space)
    @extensibility(APPENDABLE) struct Detection2D {
      @key string det_id;       // unique per publisher
      string node_id;           // keyframe id
      string camera_id;         // camera
      string class_id;          // ontology label
      float  score;             // [0..1]
      spatial::common::BBox2D bbox; // [u_min,v_min,u_max,v_max] (px)
      boolean has_mask;         // if a pixel mask exists
      string  mask_blob_id;     // BlobChunk ref (role="mask")
      Time   stamp;
      string source_id;
    };

    @extensibility(APPENDABLE) struct Detection2DSet {
      @key string set_id;                 // batch id (e.g., node_id + seq)
      string node_id;
      string camera_id;
      sequence<Detection2D, spatial::sensing::common::SZ_SMALL> dets;    // ≤256
      Time   stamp;
      string source_id;
    };

    // 3D detections in world/local frame (scene space)
    @extensibility(APPENDABLE) struct Detection3D {
      @key string det_id;
      FrameRef frame_ref;        // e.g., "map" (pose known elsewhere)
      boolean has_tile;
      TileKey tile_key;          // valid when has_tile = true

      string class_id;           // semantic label
      float  score;              // [0..1]

      // Oriented bounding box in frame_ref
      spatial::common::Vec3 center;      // m
      spatial::common::Vec3 size;        // (width, height, depth) in meters; see Size Convention
      spatial::common::QuaternionXYZW q; // orientation (x,y,z,w) in GeoPose order

      // Uncertainty (optional)
      boolean has_covariance;
      spatial::common::Mat3x3 cov_pos; // 3x3 position covariance (row-major)
      spatial::common::Mat3x3 cov_rot; // 3x3 rotation covariance (row-major)

      // Optional instance tracking
      boolean has_track_id;
      string  track_id;

      Time   stamp;
      string source_id;

      // Optional attribute key-value pairs
      boolean has_attributes;
      sequence<spatial::common::MetaKV, 8> attributes; // valid when has_attributes == true

      // Occlusion / visibility (0.0 = fully occluded, 1.0 = fully visible)
      boolean has_visibility;
      float   visibility;                // valid when has_visibility == true

      // Evidence counts
      boolean has_num_pts;
      uint32  num_lidar_pts;             // valid when has_num_pts == true
      uint32  num_radar_pts;             // valid when has_num_pts == true
    };

    @extensibility(APPENDABLE) struct Detection3DSet {
      @key string set_id;                 // batch id
      FrameRef frame_ref;                 // common frame for the set
      boolean has_tile;
      TileKey tile_key;                   // valid when has_tile = true
      sequence<Detection3D, spatial::sensing::common::SZ_SMALL> dets;    // ≤256
      Time   stamp;
      string source_id;
    };

  }; // module semantics
};

Radar Extension

Radar metadata, per-frame detection sets, and raw/processed tensor transport. The detection-centric path (RadSensorMeta / RadDetectionSet) serves automotive-style point detections. The tensor path (RadTensorMeta / RadTensorFrame) serves raw or processed radar data cubes for ISAC and ML workloads. Deployments may use either or both. ROI semantics follow §2 Conventions for global normative rules. See §2 Conventions for global normative rules.

// SPDX-License-Identifier: MIT
// SpatialDDS Radar (sensing.rad) 1.5 - Extension profile
// Detection-centric radar for automotive, industrial, and robotics sensors.

#ifndef SPATIAL_CORE_INCLUDED
#define SPATIAL_CORE_INCLUDED
#include "core.idl"
#endif
#ifndef SPATIAL_SENSING_COMMON_INCLUDED
#define SPATIAL_SENSING_COMMON_INCLUDED
#include "common.idl"
#endif

module spatial { module sensing { module rad {

  // Module identifier for discovery and schema registration
  const string MODULE_ID = "spatial.sensing.rad/1.5";

  // Reuse Core + Sensing Common types
  typedef builtin::Time                          Time;
  typedef spatial::core::PoseSE3                 PoseSE3;
  typedef spatial::core::BlobRef                 BlobRef;
  typedef spatial::common::FrameRef              FrameRef;

  typedef spatial::sensing::common::Codec        Codec;
  typedef spatial::sensing::common::StreamMeta   StreamMeta;
  typedef spatial::sensing::common::FrameHeader  FrameHeader;
  typedef spatial::sensing::common::FrameQuality FrameQuality;
  typedef spatial::sensing::common::Axis         Axis;
  typedef spatial::sensing::common::SampleType   SampleType;
  typedef spatial::sensing::common::PayloadKind  PayloadKind;
  typedef spatial::sensing::common::ROI          ROI;
  typedef spatial::sensing::common::ROIRequest   ROIRequest;
  typedef spatial::sensing::common::ROIReply     ROIReply;

  // ---- Radar sensor type ----
  enum RadSensorType {
    @value(0) SHORT_RANGE,       // e.g., corner/parking radar, ~30m
    @value(1) MEDIUM_RANGE,      // e.g., blind-spot, ~80m
    @value(2) LONG_RANGE,        // e.g., forward-facing, ~200m+
    @value(3) IMAGING_4D,        // 4D imaging radar (range/az/el/doppler)
    @value(4) SAR,               // synthetic aperture radar
    @value(255) OTHER
  };

  // ---- Dynamic property per detection ----
  enum RadDynProp {
    @value(0) UNKNOWN,
    @value(1) MOVING,
    @value(2) STATIONARY,
    @value(3) ONCOMING,
    @value(4) CROSSING_LEFT,
    @value(5) CROSSING_RIGHT,
    @value(6) STOPPED            // was moving, now stationary
  };

  // ---- Static sensor description ----
  // RELIABLE + TRANSIENT_LOCAL (late joiners receive the latest meta)
  @extensibility(APPENDABLE) struct RadSensorMeta {
    @key string stream_id;               // stable id for this radar stream
    StreamMeta base;                     // frame_ref, T_bus_sensor, nominal_rate_hz

    RadSensorType sensor_type;           // range class of this radar

    // Detection-space limits (from sensor datasheet)
    boolean has_range_limits;
    float   min_range_m;                 // valid when has_range_limits == true
    float   max_range_m;

    boolean has_azimuth_fov;
    float   az_fov_min_deg;              // valid when has_azimuth_fov == true
    float   az_fov_max_deg;

    boolean has_elevation_fov;
    float   el_fov_min_deg;              // valid when has_elevation_fov == true
    float   el_fov_max_deg;

    boolean has_velocity_limits;
    float   v_min_mps;                   // valid when has_velocity_limits == true
    float   v_max_mps;                   // max unambiguous radial velocity

    // Max detections per frame (informative hint for subscriber allocation)
    uint32  max_detections_per_frame;

    // Processing chain description (informative)
    string  proc_chain;                  // e.g., "CFAR -> clustering -> tracking"

    string  schema_version;              // MUST be "spatial.sensing.rad/1.5"
  };

  // ---- Per-detection data ----
  @extensibility(APPENDABLE) struct RadDetection {
    // Position in sensor frame (meters)
    spatial::common::Vec3 xyz_m;

    // Velocity: Cartesian vector preferred; scalar radial as fallback.
    // Producers SHOULD populate velocity_xyz when available.
    // When only radial velocity is known, set has_velocity_xyz = false
    // and use v_r_mps.
    boolean has_velocity_xyz;
    spatial::common::Vec3 velocity_xyz;  // m/s in frame_ref (valid when has_velocity_xyz == true)

    boolean has_v_r_mps;
    double  v_r_mps;                     // scalar radial velocity (valid when has_v_r_mps == true)

    // Ego-motion compensated velocity (optional)
    boolean has_velocity_comp_xyz;
    spatial::common::Vec3 velocity_comp_xyz; // ego-compensated, m/s (valid when has_velocity_comp_xyz == true)

    // Radar cross-section in physical units
    boolean has_rcs_dbm2;
    float   rcs_dbm2;                    // dBm^2 (valid when has_rcs_dbm2 == true)

    // Generic intensity / magnitude (0..1 normalized, for renderers)
    float   intensity;

    // Per-detection quality / confidence (0..1)
    float   quality;

    // Dynamic property classification
    boolean has_dyn_prop;
    RadDynProp dyn_prop;                 // valid when has_dyn_prop == true

    // Per-detection position uncertainty (optional)
    boolean has_pos_rms;
    float   x_rms_m;                     // valid when has_pos_rms == true
    float   y_rms_m;
    float   z_rms_m;

    // Per-detection velocity uncertainty (optional)
    boolean has_vel_rms;
    float   vx_rms_mps;                  // valid when has_vel_rms == true
    float   vy_rms_mps;
    float   vz_rms_mps;

    // Ambiguity state (radar-specific; 0 = unambiguous)
    boolean has_ambig_state;
    uint8   ambig_state;                 // valid when has_ambig_state == true

    // False alarm probability hint (0 = high confidence, higher = less certain)
    boolean has_false_alarm_prob;
    float   false_alarm_prob;            // valid when has_false_alarm_prob == true

    // Optional tracking ID assigned by the radar firmware
    boolean has_sensor_track_id;
    uint32  sensor_track_id;             // valid when has_sensor_track_id == true
  };

  // ---- Detection set (per-frame batch) ----
  // BEST_EFFORT + KEEP_LAST=1
  @extensibility(APPENDABLE) struct RadDetectionSet {
    @key string stream_id;
    uint64 frame_seq;
    FrameRef frame_ref;                  // coordinate frame of xyz_m

    sequence<RadDetection, spatial::sensing::common::SZ_XL> dets;

    Time   stamp;
    string source_id;
    uint64 seq;

    // Processing provenance
    string proc_chain;                   // e.g., "ARS408-CFAR" or "OS-CFAR->cluster"

    // Frame-level quality
    boolean has_quality;
    FrameQuality quality;                // valid when has_quality == true
  };

  // ==============================================================
  // RAD TENSOR TYPES (for raw/processed radar cubes)
  // ==============================================================

  // Layout of the RAD tensor - axis ordering convention
  enum RadTensorLayout {
    @value(0)   RA_D,             // [range, azimuth, doppler]
    @value(1)   R_AZ_EL_D,        // [range, azimuth, elevation, doppler]
    @value(2)   CH_FAST_SLOW,     // [channel/Rx, fast_time, slow_time] - raw FMCW
    @value(3)   CH_R_D,           // [channel, range, doppler] - post range-doppler FFT
    @value(255) CUSTOM            // axes[] defines the order explicitly
  };

  // Static tensor description - RELIABLE + TRANSIENT_LOCAL
  // Publishes once; late joiners receive current state.
  @extensibility(APPENDABLE) struct RadTensorMeta {
    @key string stream_id;                 // stable id for this radar tensor stream
    StreamMeta base;                       // frame_ref, T_bus_sensor, nominal_rate_hz

    RadSensorType sensor_type;             // reuse existing enum (SHORT_RANGE, IMAGING_4D, etc.)

    // Tensor shape description
    RadTensorLayout layout;                // axis ordering convention
    sequence<Axis, 8> axes;                // axis definitions (range/az/el/doppler/channel/time)

    SampleType voxel_type;                 // pre-compression sample type (e.g., CF32)
    string physical_meaning;               // e.g., "raw FMCW I/Q", "post 3D-FFT complex baseband"

    // Antenna configuration (informative, for MIMO systems)
    boolean has_antenna_config;
    uint16  num_tx;                        // valid when has_antenna_config == true
    uint16  num_rx;                        // valid when has_antenna_config == true
    uint16  num_virtual_channels;          // num_tx * num_rx (informative)

    // Waveform parameters (informative)
    boolean has_waveform_params;
    float   bandwidth_hz;                  // valid when has_waveform_params == true
    float   center_freq_hz;                // valid when has_waveform_params == true
    float   chirp_duration_s;              // valid when has_waveform_params == true
    uint32  samples_per_chirp;             // valid when has_waveform_params == true
    uint32  chirps_per_frame;              // valid when has_waveform_params == true

    // Default payload settings for frames
    PayloadKind payload_kind;              // DENSE_TILES, SPARSE_COO, or LATENT
    Codec       codec;                     // LZ4, ZSTD, CODEC_NONE, etc.
    boolean     has_quant_scale;
    float       quant_scale;               // valid when has_quant_scale == true
    uint32      tile_size[4];              // for DENSE_TILES; unused dims = 1

    string  schema_version;                // MUST be "spatial.sensing.rad/1.5"
  };

  // Per-frame tensor index - BEST_EFFORT + KEEP_LAST=1
  // Heavy payload referenced via FrameHeader.blobs[]
  @extensibility(APPENDABLE) struct RadTensorFrame {
    @key string stream_id;
    uint64 frame_seq;

    FrameHeader hdr;                       // t_start/t_end, optional sensor_pose, blobs[]

    // Per-frame overrides (may differ from RadTensorMeta defaults)
    PayloadKind payload_kind;
    Codec       codec;
    SampleType  voxel_type_after_decode;   // post-decode type (e.g., CF32 -> MAG_F16)
    boolean     has_quant_scale;
    float       quant_scale;               // valid when has_quant_scale == true

    FrameQuality quality;                  // SNR/valid%/health note
    string proc_chain;                     // e.g., "raw", "FFT3D->hann", "FFT3D->OS-CFAR"
  };

}; }; };

Lidar Extension

Lidar metadata, compressed point cloud frames, and detections. ROI semantics follow §2 Conventions for global normative rules; axes use the Sensing Common AXIS_CENTERS/AXIS_LINSPACE union encoding. See §2 Conventions for global normative rules.

BIN_INTERLEAVED Encoding (Normative)
BIN_INTERLEAVED indicates raw interleaved binary where each point is a contiguous record of fields defined by the PointLayout enum. There is no header. The ring field is serialized as uint16 per the LidarDetection.ring type.

Layout Fields per point Default byte-width per field
XYZ_I x, y, z, intensity 4 × float32 = 16 bytes
XYZ_I_R x, y, z, intensity, ring 4 × float32 + 1 × uint16 = 18 bytes
XYZ_I_R_N x, y, z, intensity, ring, nx, ny, nz 4 × float32 + 1 × uint16 + 3 × float32 = 30 bytes
XYZ_I_R_T x, y, z, intensity, ring, t 4 × float32 + 1 × uint16 + 1 × float32 = 22 bytes
XYZ_I_R_T_N x, y, z, intensity, ring, t, nx, ny, nz 4 × float32 + 1 × uint16 + 1 × float32 + 3 × float32 = 34 bytes

When BIN_INTERLEAVED is used, consumers MUST interpret the blob as N × record_size bytes where N = blob_size / record_size.

Per-Point Timestamps (Normative)
Layouts XYZ_I_R_T and XYZ_I_R_T_N include a per-point relative timestamp field t serialized as float32, representing seconds elapsed since FrameHeader.t_start. Consumers performing motion compensation SHOULD use t_start + point.t as the acquisition time for each point.

Computing t_end for Spinning Lidars (Informative)
When a source provides only t_start, producers SHOULD compute t_end as t_start + (1.0 / nominal_rate_hz) for spinning lidars, or as t_start + max(point.t) when per-point timestamps are available. Producers MUST populate t_end rather than leaving it as zero.

// SPDX-License-Identifier: MIT
// SpatialDDS LiDAR (sensing.lidar) 1.5 — Extension profile

#ifndef SPATIAL_CORE_INCLUDED
#define SPATIAL_CORE_INCLUDED
#include "core.idl"
#endif
#ifndef SPATIAL_SENSING_COMMON_INCLUDED
#define SPATIAL_SENSING_COMMON_INCLUDED
#include "common.idl"
#endif

module spatial { module sensing { module lidar {

  // Module identifier for discovery and schema registration
  const string MODULE_ID = "spatial.sensing.lidar/1.5";

  // Reuse Core + Sensing Common
  typedef builtin::Time                      Time;
  typedef spatial::core::PoseSE3                   PoseSE3;
  typedef spatial::core::BlobRef                   BlobRef;
  typedef spatial::common::FrameRef               FrameRef;

  typedef spatial::sensing::common::Codec          Codec;
  typedef spatial::sensing::common::PayloadKind    PayloadKind; // use BLOB_GEOMETRY for clouds
  typedef spatial::sensing::common::SampleType     SampleType;  // optional for per-point extras
  typedef spatial::sensing::common::Axis           Axis;
  typedef spatial::sensing::common::ROI            ROI;
  typedef spatial::sensing::common::StreamMeta     StreamMeta;
  typedef spatial::sensing::common::FrameHeader    FrameHeader;
  typedef spatial::sensing::common::FrameQuality   FrameQuality;
  typedef spatial::sensing::common::ROIRequest     ROIRequest;
  typedef spatial::sensing::common::ROIReply       ROIReply;

  // ROI bounds follow Sensing Common presence flags.
  // Axis samples are encoded via the Sensing Common union (AXIS_CENTERS or AXIS_LINSPACE).

  // Device + data model
  enum LidarType {
    @value(0) SPINNING_2D,
    @value(1) MULTI_BEAM_3D,
    @value(2) SOLID_STATE
  };
  enum CloudEncoding {
    @value(0)   PCD,
    @value(1)   PLY,
    @value(2)   LAS,
    @value(3)   LAZ,
    @value(4)   BIN_INTERLEAVED,   // raw interleaved binary (fields by PointLayout)
    @value(10)  GLTF_DRACO,
    @value(20)  MPEG_PCC,
    @value(255) CUSTOM_BIN
  };
  enum PointLayout { // intensity, ring, normal
    @value(0) XYZ_I,
    @value(1) XYZ_I_R,
    @value(2) XYZ_I_R_N,
    @value(3) XYZ_I_R_T,      // with per-point relative timestamp
    @value(4) XYZ_I_R_T_N     // with per-point timestamp + normals
  };

  // Static description — RELIABLE + TRANSIENT_LOCAL (late joiners receive the latest meta)
  @extensibility(APPENDABLE) struct LidarMeta {
    @key string stream_id;
    StreamMeta base;                  // frame_ref, T_bus_sensor, nominal_rate_hz
    LidarType     type;
    uint16        n_rings;            // 0 if N/A
    boolean       has_range_limits;
    float         min_range_m;        // valid when has_range_limits == true
    float         max_range_m;
    boolean       has_horiz_fov;
    float         horiz_fov_deg_min;  // valid when has_horiz_fov == true
    float         horiz_fov_deg_max;
    boolean       has_vert_fov;
    float         vert_fov_deg_min;   // valid when has_vert_fov == true
    float         vert_fov_deg_max;

    // Sensor wavelength (informative; for eye-safety and atmospheric classification)
    boolean       has_wavelength;
    float         wavelength_nm;      // valid when has_wavelength == true; e.g., 865, 905, 1550

    // Default payload for frames (clouds ride as blobs)
    CloudEncoding encoding;           // PCD/PLY/LAS/LAZ/etc.
    Codec         codec;              // ZSTD/LZ4/DRACO/…
    PointLayout   layout;             // expected fields when decoded
    string schema_version;            // MUST be "spatial.sensing.lidar/1.5"
  };

  // Per-frame index — BEST_EFFORT + KEEP_LAST=1 (large payloads referenced via blobs)
  @extensibility(APPENDABLE) struct LidarFrame {
    @key string stream_id;
    uint64 frame_seq;

    FrameHeader  hdr;                 // t_start/t_end, optional sensor_pose, blobs[]
    CloudEncoding encoding;           // may override meta
    Codec         codec;              // may override meta
    PointLayout   layout;             // may override meta
    boolean       has_per_point_timestamps; // true when blob contains per-point t

    // Optional quick hints (for health/telemetry)
    boolean      has_average_range_m;
    float        average_range_m;     // valid when has_average_range_m == true
    boolean      has_percent_valid;
    float        percent_valid;       // valid when has_percent_valid == true (0..100)
    boolean      has_quality;
    FrameQuality quality;             // valid when has_quality == true
  };

  // Lightweight derivative for immediate fusion/tracking (optional)
  @extensibility(APPENDABLE) struct LidarDetection {
    spatial::common::Vec3 xyz_m;
    float  intensity;
    uint16 ring;
    float  quality;                   // 0..1
  };

  // Detections topic — BEST_EFFORT
  @extensibility(APPENDABLE) struct LidarDetectionSet {
    @key string stream_id;
    uint64 frame_seq;
    FrameRef frame_ref;               // coordinate frame of xyz_m
    sequence<LidarDetection, spatial::sensing::common::SZ_SMALL> dets;
    Time   stamp;
  };

}; }; };

AR + Geo Extension

Multi-frame geo-referenced nodes for AR clients, VPS services, and multi-agent alignment.

NodeGeo extends core::Node with an array of metric poses in different coordinate frames and an optional geographic anchor. A VPS service localizing a client against multiple overlapping maps returns one NodeGeo carrying poses in each map's frame. In hierarchical spaces (building → floor → room → table), the same message carries poses at every level of the hierarchy. Consumers select the frame they need; producers include only the frames they can compute.

The poses array uses core::FramedPose — each entry is self-contained with its own frame reference and covariance. This replaces the previous pattern of a single bare PoseSE3 with frame_ref and cov as sibling fields, which could only express one local pose and left the relationship between the top-level cov and the geopose's cov ambiguous.

// SPDX-License-Identifier: MIT
// SpatialDDS AR+Geo 1.5

#ifndef SPATIAL_CORE_INCLUDED
#define SPATIAL_CORE_INCLUDED
#include "core.idl"
#endif

module spatial {
  module argeo {

    const string MODULE_ID = "spatial.argeo/1.5";

    typedef builtin::Time Time;
    typedef spatial::core::PoseSE3    PoseSE3;
    typedef spatial::core::FramedPose FramedPose;
    typedef spatial::core::GeoPose    GeoPose;
    typedef spatial::core::CovMatrix  CovMatrix;
    typedef spatial::common::FrameRef FrameRef;

    // A pose-graph node with one or more metric-frame poses and an
    // optional geographic anchor.
    //
    // Keyed by node_id (same key as core::Node). Published alongside
    // core::Node when geo-referencing is available.
    //
    // poses[] carries the node's position in one or more local/metric
    // coordinate frames. Each FramedPose is self-contained (pose +
    // frame_ref + cov + stamp). Typical entries:
    //   - SLAM map frame (always present)
    //   - ENU frame anchored to a surveyed point
    //   - Building / floor / room frames in hierarchical spaces
    //   - Alternative map frames when multiple maps overlap
    //
    // geopose provides the WGS84 anchor (lat/lon/alt) when known.
    // It remains a separate field because geographic coordinates use
    // degrees, not meters — they cannot share the FramedPose type.
    @extensibility(APPENDABLE) struct NodeGeo {
      string map_id;
      @key string node_id;               // same id as core::Node

      // One or more metric poses in different frames.
      // The first entry SHOULD be the primary SLAM map frame.
      // Additional entries provide the same physical pose expressed
      // in alternative coordinate frames (other maps, hierarchical
      // spaces, ENU anchors). Consumers select by frame_ref.
      sequence<FramedPose, 8> poses;

      // Geographic anchor (optional — absent for indoor-only maps)
      boolean has_geopose;
      GeoPose geopose;

      string  source_id;
      uint64  seq;                       // per-source monotonic
      uint64  graph_epoch;               // increments on major rebases/merges
    };

  }; // module argeo
};

Usage scenarios (informative):

Multi-map localization: A VPS service localizes a client against three overlapping maps and returns:

{
  "node_id": "vps-fix/client-42/0017",
  "map_id": "mall-west",
  "poses": [
    {
      "pose": { "t": [12.3, -4.1, 1.5], "q": [0, 0, 0, 1] },
      "frame_ref": { "uuid": "aaa-...", "fqn": "mall-west/lidar-map" },
      "cov": { "type": "COV_POSE6", "pose": [ ... ] },
      "stamp": { "sec": 1714071000, "nanosec": 0 }
    },
    {
      "pose": { "t": [12.1, -4.3, 1.5], "q": [0, 0, 0.01, 1] },
      "frame_ref": { "uuid": "bbb-...", "fqn": "mall-west/photo-map" },
      "cov": { "type": "COV_POSE6", "pose": [ ... ] },
      "stamp": { "sec": 1714071000, "nanosec": 0 }
    }
  ],
  "has_geopose": true,
  "geopose": {
    "lat_deg": 37.7749, "lon_deg": -122.4194, "alt_m": 15.0,
    "q": [0, 0, 0, 1], "frame_kind": "ENU",
    "frame_ref": { "uuid": "ccc-...", "fqn": "earth-fixed/enu" },
    "stamp": { "sec": 1714071000, "nanosec": 0 },
    "cov": { "type": "COV_POS3", "pos": [ ... ] }
  },
  "source_id": "vps/mall-west-service",
  "seq": 17,
  "graph_epoch": 3
}

Hierarchical spaces: A localization service returns poses in building, floor, and room frames:

{
  "node_id": "vps-fix/headset-07/0042",
  "map_id": "building-west",
  "poses": [
    {
      "pose": { "t": [45.2, 22.1, 9.3], "q": [0, 0, 0, 1] },
      "frame_ref": { "uuid": "bld-...", "fqn": "building-west/enu" },
      "cov": { "type": "COV_POS3", "pos": [ ... ] },
      "stamp": { "sec": 1714071000, "nanosec": 0 }
    },
    {
      "pose": { "t": [15.2, 8.1, 0.3], "q": [0, 0, 0, 1] },
      "frame_ref": { "uuid": "fl3-...", "fqn": "building-west/floor-3" },
      "cov": { "type": "COV_POS3", "pos": [ ... ] },
      "stamp": { "sec": 1714071000, "nanosec": 0 }
    },
    {
      "pose": { "t": [3.2, 2.1, 0.3], "q": [0, 0, 0, 1] },
      "frame_ref": { "uuid": "rmB-...", "fqn": "building-west/floor-3/room-B" },
      "cov": { "type": "COV_POS3", "pos": [ ... ] },
      "stamp": { "sec": 1714071000, "nanosec": 0 }
    }
  ],
  "has_geopose": true,
  "geopose": { "lat_deg": 37.7750, "lon_deg": -122.4190, "alt_m": 24.3, "..." : "..." },
  "source_id": "vps/building-west-indoor",
  "seq": 42,
  "graph_epoch": 1
}

Mapping Extension

Map lifecycle metadata, multi-source edge types, inter-map alignment, and lifecycle events for multi-agent collaborative mapping.

The Core profile provides the mechanical primitives for map data: Node/Edge for pose graphs, TileMeta/TilePatch/BlobRef for geometry transport, FrameTransform for frame alignment, and SnapshotRequest/SnapshotResponse for tile catch-up. The SLAM Frontend profile carries feature-level data for re-localization. The Anchors profile handles durable landmarks with incremental sync.

This extension adds the map lifecycle layer — the metadata and coordination types that let multiple independent SLAM agents discover, align, merge, version, and qualify each other's maps without prior arrangement:

  • MapMeta — top-level map descriptor: what exists, what it covers, its quality and lifecycle state.
  • mapping::Edge — extends core::Edge with richer constraint types for multi-source pose graphs (cross-map loop closures, GPS, anchor, IMU, semantic constraints).
  • MapAlignment — the inter-map transform with provenance, uncertainty, and evidence references.
  • MapEvent — lightweight lifecycle notifications so subscribers react to map state changes without polling.

Design note — no new map formats. This profile does not add occupancy grid, TSDF, or voxel map types. Those are representation-specific formats expressed as TileMeta.encoding values (e.g., "occupancy_grid/uint8", "tsdf/f32", "voxel_hash/f32"). The mapping profile addresses coordinating and aligning maps, not inventing new map formats.

Topic Layout

Type Topic QoS Notes
MapMeta spatialdds/<scene>/mapping/meta/v1 RELIABLE + TRANSIENT_LOCAL, KEEP_LAST(1) per key One sample per (map_id, source_id). Late joiners get current state.
mapping::Edge spatialdds/<scene>/mapping/edge/v1 RELIABLE, KEEP_ALL Superset of core::Edge for multi-source constraints.
MapAlignment spatialdds/<scene>/mapping/alignment/v1 RELIABLE + TRANSIENT_LOCAL, KEEP_LAST(1) per key Durable inter-map transforms.
MapEvent spatialdds/<scene>/mapping/event/v1 RELIABLE, KEEP_LAST(32) Lightweight lifecycle notifications.

Core Node and Edge topics remain unchanged. Agents that produce cross-map constraints publish on the mapping/edge topic; agents that only produce intra-map odometry/loop closures continue using core topics. Consumers that need cross-map awareness subscribe to both.

Range-only constraints: When type == RANGE, the edge carries a scalar distance measurement between from_id and to_id in the range_m / range_std_m fields. The T_from_to and information fields SHOULD be set to identity / zero respectively. Pose graph optimizers that encounter a RANGE edge SHOULD treat it as a distance-only factor: ||pos(from_id) - pos(to_id)|| = range_m. Common sources include UWB inter-robot ranging, acoustic ranging (underwater), and BLE RSSI-derived distances. Range edges may reference nodes in different maps (with has_from_map_id / has_to_map_id populated), enabling range-assisted inter-map alignment.

// SPDX-License-Identifier: MIT
// SpatialDDS Mapping Extension 1.5
//
// Map lifecycle metadata, multi-source edge types, and inter-map
// alignment primitives for multi-agent collaborative mapping.

#ifndef SPATIAL_CORE_INCLUDED
#define SPATIAL_CORE_INCLUDED
#include "core.idl"
#endif

module spatial {
  module mapping {

    const string MODULE_ID = "spatial.mapping/1.5";

    typedef builtin::Time Time;
    typedef spatial::core::PoseSE3    PoseSE3;
    typedef spatial::core::FrameRef   FrameRef;
    typedef spatial::core::CovMatrix  CovMatrix;
    typedef spatial::core::BlobRef    BlobRef;
    typedef spatial::common::MetaKV   MetaKV;
    typedef spatial::core::GeoPose   GeoPose;


    // ================================================================
    // 1. MAP METADATA
    // ================================================================

    // Enums are placed in a nested module to avoid literal collisions
    // at the module scope in IDL compilers.
    module enums {
      module map_kind {
        // Representation kind — what type of spatial map this describes.
        // The enum identifies the high-level representation; the actual
        // encoding and codec live in TileMeta.encoding as today.
        enum MapKind {
          @value(0) POSE_GRAPH,       // sparse keyframe graph (Node + Edge)
          @value(1) OCCUPANCY_GRID,   // 2D or 2.5D grid (nav planning)
          @value(2) POINT_CLOUD,      // dense 3D point cloud
          @value(3) MESH,             // triangle mesh / surface
          @value(4) TSDF,             // truncated signed distance field
          @value(5) VOXEL,            // volumetric voxel grid
          @value(6) NEURAL_FIELD,     // NeRF, 3DGS, neural SDF (see neural profile)
          @value(7) FEATURE_MAP,      // visual place recognition / bag-of-words
          @value(8) SEMANTIC,         // semantic / panoptic map layer
          @value(9) OTHER
        };
      };

      module map_status {
        // Map status — lifecycle state.
        enum MapStatus {
          @value(0) BUILDING,         // actively being constructed (SLAM running)
          @value(1) OPTIMIZING,       // global optimization / bundle adjustment in progress
          @value(2) STABLE,           // optimized and not actively changing
          @value(3) FROZEN,           // immutable reference map (no further updates)
          @value(4) DEPRECATED        // superseded by a newer map; consumers should migrate
        };
      };

      module edge_type {
        // Extends core::EdgeTypeCore (ODOM=0, LOOP=1) with constraint types
        // needed for multi-agent, multi-sensor pose graph optimization.
        //
        // Values 0-1 are identical to EdgeTypeCore. Core consumers that
        // only understand ODOM/LOOP can safely downcast by treating
        // unknown values as LOOP.
        enum EdgeType {
          @value(0)  ODOM,            // odometry (sequential)
          @value(1)  LOOP,            // intra-map loop closure
          @value(2)  INTER_MAP,       // cross-map loop closure (between two agents' maps)
          @value(3)  GPS,             // absolute pose from GNSS
          @value(4)  ANCHOR,          // constraint from recognizing a shared anchor
          @value(5)  IMU_PREINT,      // IMU pre-integration factor
          @value(6)  GRAVITY,         // gravity direction prior
          @value(7)  PLANE,           // planar constraint (e.g., ground plane)
          @value(8)  SEMANTIC,        // semantic co-observation ("both see the same door")
          @value(9)  MANUAL,          // human-provided alignment
          @value(10) RANGE,           // range-only distance constraint (UWB, acoustic, BLE)
          @value(11) OTHER
        };
      };

      module alignment_method {
        // How an alignment was established.
        enum AlignmentMethod {
          @value(0) VISUAL_LOOP,      // feature-based visual closure
          @value(1) LIDAR_ICP,        // point cloud registration (ICP / NDT)
          @value(2) ANCHOR_MATCH,     // shared anchor recognition
          @value(3) GPS_COARSE,       // GPS-derived coarse alignment
          @value(4) SEMANTIC_MATCH,   // semantic landmark co-observation
          @value(5) MANUAL,           // operator-provided ground truth
          @value(6) MULTI_METHOD,     // combination of methods
          @value(7) RANGE_COARSE,     // range-only (UWB, acoustic) coarse alignment
          @value(8) OTHER
        };
      };

      module map_event_kind {
        // Lightweight event published when a map undergoes a significant
        // lifecycle transition. Subscribers (fleet managers, UI dashboards,
        // data pipelines) can react without polling MapMeta.
        enum MapEventKind {
          @value(0) CREATED,          // new map started
          @value(1) EPOCH_ADVANCE,    // graph_epoch incremented (rebase / merge)
          @value(2) STATUS_CHANGE,    // status field changed (e.g., BUILDING → STABLE)
          @value(3) ALIGNMENT_NEW,    // new MapAlignment published involving this map
          @value(4) ALIGNMENT_UPDATE, // existing MapAlignment revised
          @value(5) DEPRECATED,       // map marked deprecated
          @value(6) DELETED           // map data removed from bus
        };
      };
    };

    typedef enums::map_kind::MapKind MapKind;
    typedef enums::map_status::MapStatus MapStatus;
    typedef enums::edge_type::EdgeType EdgeType;
    typedef enums::alignment_method::AlignmentMethod AlignmentMethod;
    typedef enums::map_event_kind::MapEventKind MapEventKind;

    // Quality metrics — optional per-map health indicators.
    // All fields are optional via has_* flags to avoid mandating
    // metrics that not every SLAM system produces.
    @extensibility(APPENDABLE) struct MapQuality {
      boolean has_loop_closure_count;
      uint32  loop_closure_count;       // total loop closures accepted

      boolean has_mean_residual;
      double  mean_residual;            // mean constraint residual after optimization (meters)

      boolean has_max_drift_m;
      double  max_drift_m;              // estimated worst-case drift (meters)

      boolean has_coverage_pct;
      float   coverage_pct;             // fraction of declared extent actually mapped [0..1]

      boolean has_keyframe_count;
      uint32  keyframe_count;           // number of keyframes / nodes

      boolean has_landmark_count;
      uint32  landmark_count;           // number of 3D landmarks
    };

    // Top-level map descriptor. Published with RELIABLE + TRANSIENT_LOCAL
    // so late joiners discover all active maps immediately.
    //
    // One MapMeta per (map_id, source_id) — a single physical map may have
    // multiple representations (e.g., pose graph + occupancy grid + mesh),
    // each published as a separate MapMeta with the same map_id but
    // different kind and source_id.
    @extensibility(APPENDABLE) struct MapMeta {
      @key string map_id;               // unique map identifier
      @key string source_id;            // producing agent / SLAM system

      MapKind   kind;                   // representation type
      MapStatus status;                 // lifecycle state
      string    algorithm;              // e.g., "ORB-SLAM3", "Cartographer", "RTAB-Map", "LIO-SAM"
      FrameRef  frame_ref;              // map's canonical coordinate frame

      // Spatial extent (axis-aligned in frame_ref)
      boolean has_extent;
      spatial::core::Aabb3 extent;      // bounding box of mapped region

      // Geo-anchor: where this map sits on Earth (when known)
      boolean has_geopose;
      GeoPose geopose;                  // map origin in WGS84

      // Versioning — aligns with core Node/Edge graph_epoch
      uint64  graph_epoch;              // increments on major rebases / merges
      uint64  revision;                 // monotonic within an epoch (fine-grained updates)

      // Quality
      boolean has_quality;
      MapQuality quality;

      // Timing
      Time    created;                  // map creation time
      Time    stamp;                    // last update time

      // Content references — how to get the map data
      // For pose graphs: subscribe to Node/Edge on the standard topic with this map_id
      // For dense maps: these blob_ids reference the backing TileMeta/BlobChunk data
      sequence<BlobRef, 32> blob_refs;  // optional: pre-built map artifacts

      // Extensible metadata (encoding details, sensor suite, etc.)
      sequence<MetaKV, 32> attributes;

      string schema_version;            // MUST be "spatial.mapping/1.5"
    };


    // ================================================================
    // 2. EXTENDED EDGE TYPES
    // ================================================================

    // (EdgeType enum is defined in enums submodule; typedef above)

    // Extended edge that carries the richer EdgeType plus provenance.
    // Supplements core::Edge — publishers that produce multi-source
    // constraints publish mapping::Edge; the fields are a superset
    // of core::Edge.
    @extensibility(APPENDABLE) struct Edge {
      string map_id;
      @key string edge_id;
      string from_id;                   // source node (may be in a different map_id)
      string to_id;                     // target node
      EdgeType type;                    // extended type enum
      PoseSE3 T_from_to;               // relative pose: from_id → to_id
      spatial::common::Mat6x6 information; // 6x6 info matrix (row-major)
      Time   stamp;
      string source_id;                 // who produced this constraint
      uint64 seq;
      uint64 graph_epoch;

      // Cross-map provenance (populated when type == INTER_MAP)
      boolean has_from_map_id;
      string  from_map_id;              // map_id of from_id's origin
      boolean has_to_map_id;
      string  to_map_id;                // map_id of to_id's origin

      // Match quality for loop closures and cross-map edges
      boolean has_match_score;
      float   match_score;              // similarity / inlier ratio [0..1]
      boolean has_inlier_count;
      uint32  inlier_count;             // feature inliers supporting this edge

      // Range-only constraint (populated when type == RANGE)
      // For range-only edges, T_from_to and information are unused (set to
      // identity/zero); the scalar range_m is the primary payload.
      // The optimizer treats this as a distance-only factor between from_id
      // and to_id: ||pos(from_id) - pos(to_id)|| = range_m ± range_std_m.
      boolean has_range_m;
      float   range_m;                  // measured distance (meters)
      boolean has_range_std_m;
      float   range_std_m;              // 1-sigma distance uncertainty (meters)
    };


    // ================================================================
    // 3. MAP ALIGNMENT
    // ================================================================

    // (AlignmentMethod enum is defined in enums submodule; typedef above)

    // Inter-map transform: aligns map_id_from's frame to map_id_to's frame,
    // with provenance and quality metadata.
    //
    // This is the merge primitive. When a multi-robot SLAM system determines
    // that two maps overlap, it publishes a MapAlignment. Downstream consumers
    // (planning, visualization, fleet coordination) use this to reason across
    // maps without waiting for a full graph merge.
    @extensibility(APPENDABLE) struct MapAlignment {
      @key string alignment_id;         // unique alignment identifier

      string map_id_from;               // source map
      string map_id_to;                 // target map (reference)
      PoseSE3 T_from_to;               // transform: map_id_from frame → map_id_to frame
      CovMatrix cov;                    // uncertainty of the alignment

      AlignmentMethod method;           // how the alignment was computed
      Time   stamp;                     // when the alignment was computed
      string source_id;                 // who computed it

      // Quality evidence
      boolean has_match_score;
      float   match_score;              // overall alignment quality [0..1]
      boolean has_overlap_pct;
      float   overlap_pct;              // estimated spatial overlap [0..1]
      boolean has_supporting_edges;
      uint32  supporting_edges;         // number of cross-map edges backing this alignment

      // Versioning — alignment may be refined as more evidence accumulates
      uint64  revision;                 // monotonic; newer revision supersedes older

      // Optional: list of cross-map edge_ids that support this alignment
      sequence<string, 64> evidence_edge_ids;

      string schema_version;            // MUST be "spatial.mapping/1.5"
    };


    // ================================================================
    // 4. MAP LIFECYCLE EVENTS
    // ================================================================

    // (MapEventKind enum is defined in enums submodule; typedef above)

    @extensibility(APPENDABLE) struct MapEvent {
      @key string map_id;
      MapEventKind event;
      string source_id;
      Time   stamp;

      // Context (populated per event kind)
      boolean has_new_status;
      MapStatus new_status;             // for STATUS_CHANGE

      boolean has_new_epoch;
      uint64  new_epoch;                // for EPOCH_ADVANCE

      boolean has_alignment_id;
      string  alignment_id;             // for ALIGNMENT_NEW / ALIGNMENT_UPDATE

      // Human-readable reason
      boolean has_reason;
      string  reason;                   // e.g., "merged with map/robot-B after 42 loop closures"
    };

  }; // module mapping
};   // module spatial

Spatial Events Extension

Typed, spatially-scoped events for zone monitoring, anomaly detection, and smart infrastructure alerting. Bridges perception streams (Detection3DSet) and application logic (fleet management, building automation, safety systems).

The Semantics profile provides spatial facts — "what is where." This extension adds spatial interpretations — "something happened that matters." Events are derived from perception streams plus zone definitions plus temporal rules (dwell thresholds, speed limits, capacity caps). They are typed, severity-graded, tied to triggering detections, and scoped to named spatial zones.

The profile defines three types:

  • SpatialZone — named spatial regions with rules (restricted, speed-limited, capacity-capped). Published latched so all participants know the zone layout.
  • SpatialEvent — typed event tied to a zone, triggering detection, optional media evidence, and severity.
  • ZoneState — periodic zone occupancy and status snapshot for dashboards and capacity management.

Integration with Discovery: Zone publishers announce via disco::Announce with kind: OTHER (or a future ZONE_MANAGER kind) and coverage matching the zone's spatial extent. Consumers use CoverageQuery filtered by module_id_in: ["spatial.events/1.5"] to discover event sources in a region. SpatialZone geometry reuses the same Aabb3 and FrameRef primitives as CoverageElement, ensuring consistent spatial reasoning.

Topic Layout

Type Topic QoS Notes
SpatialZone spatialdds/<scene>/events/zone/v1 RELIABLE + TRANSIENT_LOCAL, KEEP_LAST(1) per key Latched zone definitions. Late joiners get full zone set.
SpatialEvent spatialdds/<scene>/events/event/v1 RELIABLE, KEEP_LAST(64) Event stream. Consumers filter by zone_id, severity, event type.
ZoneState spatialdds/<scene>/events/zone\_state/v1 BEST_EFFORT, KEEP_LAST(1) per key Periodic zone status snapshots.
// SPDX-License-Identifier: MIT
// SpatialDDS Spatial Events Extension 1.5
//
// Typed, spatially-scoped events for zone monitoring, anomaly detection,
// and smart infrastructure alerting.

#ifndef SPATIAL_CORE_INCLUDED
#define SPATIAL_CORE_INCLUDED
#include "core.idl"
#endif

module spatial {
  module events {

    const string MODULE_ID = "spatial.events/1.5";

    typedef builtin::Time Time;
    typedef spatial::core::PoseSE3   PoseSE3;
    typedef spatial::core::FrameRef  FrameRef;
    typedef spatial::core::Aabb3     Aabb3;
    typedef spatial::core::BlobRef   BlobRef;
    typedef spatial::core::GeoPose   GeoPose;
    typedef spatial::common::MetaKV  MetaKV;

    // ================================================================
    // ENUMS (scoped to avoid literal collisions in IDL compilers)
    // ================================================================

    module enums {
      module zone_kind_enum {
        // Zone classification — what kind of spatial region this is.
        enum ZoneKind {
          @value(0) RESTRICTED,       // entry prohibited or requires authorization
          @value(1) SPEED_LIMITED,    // maximum speed enforced
          @value(2) CAPACITY_LIMITED, // maximum occupancy enforced
          @value(3) ONE_WAY,          // directional traffic constraint
          @value(4) LOADING,          // loading/unloading area with dwell rules
          @value(5) HAZARD,           // known hazard zone (chemical, height, machinery)
          @value(6) MONITORING,       // general observation zone (no specific constraint)
          @value(7) GEOFENCE,         // boundary-crossing detection only
          @value(8) OTHER
        };
      };

      module event_type_enum {
        // Event type — what happened.
        enum EventType {
          @value(0)  ZONE_ENTRY,      // object entered the zone
          @value(1)  ZONE_EXIT,       // object exited the zone
          @value(2)  DWELL_TIMEOUT,   // object exceeded dwell time limit
          @value(3)  SPEED_VIOLATION, // object exceeded speed limit
          @value(4)  CAPACITY_BREACH, // zone occupancy exceeded capacity
          @value(5)  WRONG_WAY,       // object traveling against one-way direction
          @value(6)  PROXIMITY_ALERT, // two tracked objects closer than safe distance
          @value(7)  UNATTENDED,      // object stationary without associated person
          @value(8)  ANOMALY,         // general anomaly (ML-detected, pattern deviation)
          @value(9)  LINE_CROSS,      // object crossed a defined trip line
          @value(10) LOITERING,       // person/object lingering beyond threshold
          @value(11) TAILGATING,      // unauthorized entry following authorized person
          @value(12) OTHER
        };
      };

      module severity_enum {
        // Severity level.
        enum Severity {
          @value(0) INFO,             // informational (logging, analytics)
          @value(1) WARNING,          // advisory — may require attention
          @value(2) ALERT,            // actionable — requires human review
          @value(3) CRITICAL          // immediate intervention required
        };
      };

      module event_state_enum {
        // Event lifecycle state.
        enum EventState {
          @value(0) ACTIVE,           // event is ongoing
          @value(1) RESOLVED,         // condition cleared (e.g., person left zone)
          @value(2) ACKNOWLEDGED,     // human acknowledged the event
          @value(3) SUPPRESSED        // suppressed by rule or operator
        };
      };
    };

    typedef enums::zone_kind_enum::ZoneKind ZoneKind;
    typedef enums::event_type_enum::EventType EventType;
    typedef enums::severity_enum::Severity Severity;
    typedef enums::event_state_enum::EventState EventState;

    // ================================================================
    // 1. SPATIAL ZONES
    // ================================================================

    // (ZoneKind enum is defined in enums submodule; typedef above)

    // Named spatial region with associated rules.
    // Published with RELIABLE + TRANSIENT_LOCAL so late joiners
    // receive the full zone layout.
    @extensibility(APPENDABLE) struct SpatialZone {
      @key string zone_id;              // unique zone identifier

      string name;                      // human-readable name (e.g., "Loading Bay 3")
      ZoneKind kind;                    // zone classification
      FrameRef frame_ref;               // coordinate frame for geometry

      // Zone geometry (axis-aligned in frame_ref)
      boolean has_bounds;
      Aabb3 bounds;                     // 3D bounding box (valid when has_bounds == true)

      // Optional geo-anchor for earth-fixed zones
      boolean has_geopose;
      GeoPose geopose;                  // zone center in WGS84

      // Zone rules (optional per kind)
      boolean has_speed_limit_mps;
      float   speed_limit_mps;          // max speed in m/s (for SPEED_LIMITED)

      boolean has_capacity;
      uint32  capacity;                 // max occupancy count (for CAPACITY_LIMITED)

      boolean has_dwell_limit_sec;
      float   dwell_limit_sec;          // max dwell time in seconds (for LOADING, RESTRICTED)

      // Applicable object classes — which detection class_ids trigger events.
      // Empty means all classes.
      sequence<string, 32> class_filter;

      // Schedule (optional) — zone is only active during specified hours.
      // Format: ISO 8601 recurring interval or cron-like string in attributes.
      boolean has_schedule;
      string  schedule;                 // e.g., "R/2024-01-01T06:00:00/PT12H" or deployment-specific

      // Owner / authority
      string provider_id;               // who defines this zone
      Time   stamp;                     // last update time

      // Extensible metadata
      sequence<MetaKV, 16> attributes;

      string schema_version;            // MUST be "spatial.events/1.5"
    };


    // ================================================================
    // 2. SPATIAL EVENTS
    // ================================================================

    // (EventType, Severity, EventState enums are defined in enums submodule; typedefs above)

    // A spatially-grounded event.
    //
    // Keyed by event_id. Publishers update the same event_id as state
    // changes (ACTIVE → RESOLVED). Subscribers use KEEP_LAST per key
    // to see the latest state of each event.
    @extensibility(APPENDABLE) struct SpatialEvent {
      @key string event_id;             // unique event identifier

      EventType  type;                  // what happened
      Severity   severity;              // how urgent
      EventState state;                 // lifecycle state

      // Where — zone reference (optional; not all events are zone-scoped)
      boolean has_zone_id;
      string  zone_id;                  // references SpatialZone.zone_id

      // Where — 3D position of the event (in the zone's or scene's frame_ref)
      boolean has_position;
      spatial::common::Vec3 position;   // event location (meters)
      FrameRef frame_ref;               // coordinate frame for position

      // What — triggering detection(s)
      boolean has_trigger_det_id;
      string  trigger_det_id;           // primary triggering Detection3D.det_id

      boolean has_trigger_track_id;
      string  trigger_track_id;         // tracked object ID (from Detection3D.track_id)

      string  trigger_class_id;         // class of triggering object (e.g., "person", "forklift")

      // Who — secondary object for relational events (PROXIMITY_ALERT, TAILGATING)
      boolean has_secondary_det_id;
      string  secondary_det_id;

      // Measured values (populated per event type)
      boolean has_measured_speed_mps;
      float   measured_speed_mps;       // for SPEED_VIOLATION

      boolean has_measured_dwell_sec;
      float   measured_dwell_sec;       // for DWELL_TIMEOUT, LOITERING, UNATTENDED

      boolean has_measured_distance_m;
      float   measured_distance_m;      // for PROXIMITY_ALERT

      boolean has_zone_occupancy;
      uint32  zone_occupancy;           // current count for CAPACITY_BREACH

      // Confidence
      float   confidence;               // [0..1] — event detection confidence

      // Evidence — optional media snapshot or clip
      boolean has_evidence;
      BlobRef evidence;                 // reference to snapshot image or video clip

      // Narrative — optional human-readable description
      boolean has_description;
      string  description;              // e.g., "Forklift stopped in pedestrian corridor
                                        //  near anchor loading-bay-3 for 4 minutes"

      // Timing
      Time   event_start;               // when the event condition began
      Time   stamp;                     // latest update time (may differ from event_start)

      // Producer
      string source_id;                 // who detected this event

      // Extensible metadata
      sequence<MetaKV, 8> attributes;

      string schema_version;            // MUST be "spatial.events/1.5"
    };


    // ================================================================
    // 3. ZONE STATE (periodic summary)
    // ================================================================

    // Lightweight periodic snapshot of a zone's current status.
    // Enables dashboards and capacity management without maintaining
    // event counters client-side.
    @extensibility(APPENDABLE) struct ZoneState {
      @key string zone_id;              // references SpatialZone.zone_id

      uint32 current_occupancy;         // number of tracked objects currently in zone
      boolean has_capacity;
      uint32  capacity;                 // echoed from SpatialZone for convenience

      // Active alert count by severity
      uint32 active_info;
      uint32 active_warning;
      uint32 active_alert;
      uint32 active_critical;

      // Class breakdown (optional — top N classes present)
      sequence<MetaKV, 8> class_counts; // namespace = class_id, json = {"count": N}

      Time   stamp;
      string source_id;
      // schema_version intentionally omitted: ZoneState is a lightweight
      // summary; version is inferred from the accompanying SpatialZone.
    };

  }; // module events
};   // module spatial

Example JSON (Informative)

Zone Definition:

{
  "zone_id": "zone/facility-west/pedestrian-corridor-B",
  "name": "Pedestrian Corridor B",
  "kind": "RESTRICTED",
  "frame_ref": { "uuid": "f1a2b3c4-...", "fqn": "facility-west/enu" },
  "bounds": { "min_xyz": [10.0, 0.0, 0.0], "max_xyz": [25.0, 5.0, 3.0] },
  "has_geopose": false,
  "has_speed_limit_mps": false,
  "has_capacity": false,
  "has_dwell_limit_sec": true,
  "dwell_limit_sec": 120.0,
  "class_filter": ["forklift", "agv", "pallet_truck"],
  "has_schedule": true,
  "schedule": "R/2024-01-01T06:00:00/PT14H",
  "provider_id": "safety/zone-manager",
  "stamp": { "sec": 1714070400, "nanosec": 0 },
  "schema_version": "spatial.events/1.5"
}

Event:

{
  "event_id": "evt/facility-west/2024-04-26T12:05:00Z/001",
  "type": "DWELL_TIMEOUT",
  "severity": "ALERT",
  "state": "ACTIVE",
  "has_zone_id": true,
  "zone_id": "zone/facility-west/pedestrian-corridor-B",
  "has_position": true,
  "position": [17.2, 2.1, 0.5],
  "frame_ref": { "uuid": "f1a2b3c4-...", "fqn": "facility-west/enu" },
  "has_trigger_det_id": true,
  "trigger_det_id": "det/fused/forklift-07",
  "has_trigger_track_id": true,
  "trigger_track_id": "track/forklift-07",
  "trigger_class_id": "forklift",
  "has_measured_dwell_sec": true,
  "measured_dwell_sec": 247.0,
  "confidence": 0.94,
  "has_evidence": true,
  "evidence": { "blob_id": "snap-20240426-120500-cam3", "role": "evidence/jpeg", "checksum": "sha256:ab12..." },
  "has_description": true,
  "description": "Forklift stopped in pedestrian corridor B near loading-bay-3 for 4 min 7 sec",
  "event_start": { "sec": 1714131653, "nanosec": 0 },
  "stamp": { "sec": 1714131900, "nanosec": 0 },
  "source_id": "analytics/zone-monitor",
  "schema_version": "spatial.events/1.5"
}