// SPDX-License-Identifier: Apache-2.0
// Copyright © 2025 Au-Zone Technologies. All Rights Reserved.

//! Golden CDR tests for cross-language validation.
//!
//! Each test reads a `.cdr` file generated by Python's pycdr2 library
//! (`scripts/generate_cdr_testdata.py`) and validates three phases:
//!
//! 1. **Decode** — deserialize the golden bytes and verify every field.
//! 2. **Construct** — build the same message from scratch and assert
//!    byte-identical CDR output.
//! 3. **Modify** — mutate a field and verify the roundtrip.
//!
//! If any phase fails, the Rust and Python CDR implementations disagree
//! on layout, alignment, or field encoding.

// Golden test values are exact wire-format constants from Python pycdr2, not approximations.
#![allow(clippy::approx_constant)]

use std::path::PathBuf;

use edgefirst_schemas::builtin_interfaces::{Duration, Time};
use edgefirst_schemas::cdr::{decode_fixed, encode_fixed};
use edgefirst_schemas::edgefirst_msgs::{self, Date, DetectBoxView, MaskView};
use edgefirst_schemas::foxglove_msgs::{
    self, FoxgloveCircleAnnotations, FoxgloveColor, FoxglovePoint2, FoxglovePointAnnotationView,
    FoxgloveTextAnnotationView,
};
use edgefirst_schemas::geometry_msgs::{
    self, Accel, Inertia, Point, Point32, Pose, Pose2D, Quaternion, Transform, Twist, Vector3,
};
use edgefirst_schemas::rosgraph_msgs::Clock;
use edgefirst_schemas::sensor_msgs::{self, NavSatStatus, PointFieldView, RegionOfInterest};
use edgefirst_schemas::std_msgs::{self, ColorRGBA};

// ── Helpers ────────────────────────────────────────────────────────────────

fn read_golden(namespace: &str, type_name: &str) -> Vec<u8> {
    let path = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
        .join("testdata/cdr")
        .join(namespace)
        .join(format!("{type_name}.cdr"));
    std::fs::read(&path).unwrap_or_else(|e| panic!("read {}: {e}", path.display()))
}

/// Shared timestamp used across all golden files.
const STAMP: Time = Time {
    sec: 1234567890,
    nanosec: 123456789,
};
const FRAME_ID: &str = "test_frame";

// ── builtin_interfaces ─────────────────────────────────────────────────────

#[test]
fn golden_builtin_interfaces_time() {
    let golden = read_golden("builtin_interfaces", "Time");
    // Phase 1: decode
    let t: Time = decode_fixed(&golden).unwrap();
    assert_eq!(t.sec, 1234567890);
    assert_eq!(t.nanosec, 123456789);
    // Phase 2: construct
    let built = Time::new(1234567890, 123456789);
    assert_eq!(encode_fixed(&built).unwrap(), golden);
    // Phase 3: modify
    let mut m = t;
    m.sec = 42;
    let re: Time = decode_fixed(&encode_fixed(&m).unwrap()).unwrap();
    assert_eq!(re.sec, 42);
    assert_eq!(re.nanosec, t.nanosec);
}

#[test]
fn golden_builtin_interfaces_duration() {
    let golden = read_golden("builtin_interfaces", "Duration");
    let d: Duration = decode_fixed(&golden).unwrap();
    assert_eq!(d.sec, 60);
    assert_eq!(d.nanosec, 500000000);
    let built = Duration::new(60, 500000000);
    assert_eq!(encode_fixed(&built).unwrap(), golden);
    let mut m = d;
    m.sec = 120;
    let re: Duration = decode_fixed(&encode_fixed(&m).unwrap()).unwrap();
    assert_eq!(re.sec, 120);
    assert_eq!(re.nanosec, d.nanosec);
}

// ── geometry_msgs (CdrFixed) ───────────────────────────────────────────────

#[test]
fn golden_geometry_msgs_vector3() {
    let golden = read_golden("geometry_msgs", "Vector3");
    let v: Vector3 = decode_fixed(&golden).unwrap();
    assert_eq!(v.x, 1.5);
    assert_eq!(v.y, -2.5);
    assert_eq!(v.z, 3.0);
    let built = Vector3 {
        x: 1.5,
        y: -2.5,
        z: 3.0,
    };
    assert_eq!(encode_fixed(&built).unwrap(), golden);
    let mut m = v;
    m.x = 42.0;
    let re: Vector3 = decode_fixed(&encode_fixed(&m).unwrap()).unwrap();
    assert_eq!(re.x, 42.0);
    assert_eq!(re.y, v.y);
    assert_eq!(re.z, v.z);
}

#[test]
fn golden_geometry_msgs_point() {
    let golden = read_golden("geometry_msgs", "Point");
    let p: Point = decode_fixed(&golden).unwrap();
    assert_eq!(p.x, 1.5);
    assert_eq!(p.y, -2.5);
    assert_eq!(p.z, 3.0);
    let built = Point {
        x: 1.5,
        y: -2.5,
        z: 3.0,
    };
    assert_eq!(encode_fixed(&built).unwrap(), golden);
    let mut m = p;
    m.z = 99.0;
    let re: Point = decode_fixed(&encode_fixed(&m).unwrap()).unwrap();
    assert_eq!(re.z, 99.0);
    assert_eq!(re.x, p.x);
}

#[test]
fn golden_geometry_msgs_point32() {
    let golden = read_golden("geometry_msgs", "Point32");
    let p: Point32 = decode_fixed(&golden).unwrap();
    assert_eq!(p.x, 1.5);
    assert_eq!(p.y, -2.5);
    assert_eq!(p.z, 3.0);
    let built = Point32 {
        x: 1.5,
        y: -2.5,
        z: 3.0,
    };
    assert_eq!(encode_fixed(&built).unwrap(), golden);
    let mut m = p;
    m.y = 7.0;
    let re: Point32 = decode_fixed(&encode_fixed(&m).unwrap()).unwrap();
    assert_eq!(re.y, 7.0);
    assert_eq!(re.x, p.x);
}

#[test]
fn golden_geometry_msgs_quaternion() {
    let golden = read_golden("geometry_msgs", "Quaternion");
    let q: Quaternion = decode_fixed(&golden).unwrap();
    assert_eq!(q.x, 0.0);
    assert_eq!(q.y, 0.0);
    assert_eq!(q.z, 0.0);
    assert_eq!(q.w, 1.0);
    let built = Quaternion {
        x: 0.0,
        y: 0.0,
        z: 0.0,
        w: 1.0,
    };
    assert_eq!(encode_fixed(&built).unwrap(), golden);
    let mut m = q;
    m.w = 0.707;
    m.z = 0.707;
    let re: Quaternion = decode_fixed(&encode_fixed(&m).unwrap()).unwrap();
    assert_eq!(re.w, 0.707);
    assert_eq!(re.z, 0.707);
    assert_eq!(re.x, q.x);
}

#[test]
fn golden_geometry_msgs_pose() {
    let golden = read_golden("geometry_msgs", "Pose");
    let p: Pose = decode_fixed(&golden).unwrap();
    assert_eq!(p.position.x, 1.5);
    assert_eq!(p.position.y, -2.5);
    assert_eq!(p.position.z, 3.0);
    assert_eq!(p.orientation.w, 1.0);
    let built = Pose {
        position: Point {
            x: 1.5,
            y: -2.5,
            z: 3.0,
        },
        orientation: Quaternion {
            x: 0.0,
            y: 0.0,
            z: 0.0,
            w: 1.0,
        },
    };
    assert_eq!(encode_fixed(&built).unwrap(), golden);
    let mut m = p;
    m.position.x = 99.0;
    let re: Pose = decode_fixed(&encode_fixed(&m).unwrap()).unwrap();
    assert_eq!(re.position.x, 99.0);
    assert_eq!(re.orientation.w, p.orientation.w);
}

#[test]
fn golden_geometry_msgs_pose2d() {
    let golden = read_golden("geometry_msgs", "Pose2D");
    let p: Pose2D = decode_fixed(&golden).unwrap();
    assert_eq!(p.x, 1.5);
    assert_eq!(p.y, -2.5);
    assert_eq!(p.theta, 0.785398163);
    let built = Pose2D {
        x: 1.5,
        y: -2.5,
        theta: 0.785398163,
    };
    assert_eq!(encode_fixed(&built).unwrap(), golden);
    let mut m = p;
    m.theta = 3.14;
    let re: Pose2D = decode_fixed(&encode_fixed(&m).unwrap()).unwrap();
    assert_eq!(re.theta, 3.14);
    assert_eq!(re.x, p.x);
}

#[test]
fn golden_geometry_msgs_transform() {
    let golden = read_golden("geometry_msgs", "Transform");
    let t: Transform = decode_fixed(&golden).unwrap();
    assert_eq!(t.translation.x, 1.5);
    assert_eq!(t.translation.y, -2.5);
    assert_eq!(t.translation.z, 3.0);
    assert_eq!(t.rotation.w, 1.0);
    let built = Transform {
        translation: Vector3 {
            x: 1.5,
            y: -2.5,
            z: 3.0,
        },
        rotation: Quaternion {
            x: 0.0,
            y: 0.0,
            z: 0.0,
            w: 1.0,
        },
    };
    assert_eq!(encode_fixed(&built).unwrap(), golden);
    let mut m = t;
    m.translation.z = 99.0;
    let re: Transform = decode_fixed(&encode_fixed(&m).unwrap()).unwrap();
    assert_eq!(re.translation.z, 99.0);
    assert_eq!(re.rotation.w, t.rotation.w);
}

#[test]
fn golden_geometry_msgs_accel() {
    let golden = read_golden("geometry_msgs", "Accel");
    let a: Accel = decode_fixed(&golden).unwrap();
    assert_eq!(a.linear.x, 1.0);
    assert_eq!(a.linear.y, 2.0);
    assert_eq!(a.linear.z, 3.0);
    assert_eq!(a.angular.x, 0.1);
    assert_eq!(a.angular.y, 0.2);
    assert_eq!(a.angular.z, 0.3);
    let built = Accel {
        linear: Vector3 {
            x: 1.0,
            y: 2.0,
            z: 3.0,
        },
        angular: Vector3 {
            x: 0.1,
            y: 0.2,
            z: 0.3,
        },
    };
    assert_eq!(encode_fixed(&built).unwrap(), golden);
    let mut m = a;
    m.linear.x = 99.0;
    let re: Accel = decode_fixed(&encode_fixed(&m).unwrap()).unwrap();
    assert_eq!(re.linear.x, 99.0);
    assert_eq!(re.angular.z, a.angular.z);
}

#[test]
fn golden_geometry_msgs_twist() {
    let golden = read_golden("geometry_msgs", "Twist");
    let t: Twist = decode_fixed(&golden).unwrap();
    assert_eq!(t.linear.x, 1.0);
    assert_eq!(t.angular.z, 0.3);
    let built = Twist {
        linear: Vector3 {
            x: 1.0,
            y: 2.0,
            z: 3.0,
        },
        angular: Vector3 {
            x: 0.1,
            y: 0.2,
            z: 0.3,
        },
    };
    assert_eq!(encode_fixed(&built).unwrap(), golden);
    let mut m = t;
    m.angular.z = 1.0;
    let re: Twist = decode_fixed(&encode_fixed(&m).unwrap()).unwrap();
    assert_eq!(re.angular.z, 1.0);
    assert_eq!(re.linear.x, t.linear.x);
}

#[test]
fn golden_geometry_msgs_inertia() {
    let golden = read_golden("geometry_msgs", "Inertia");
    let i: Inertia = decode_fixed(&golden).unwrap();
    assert_eq!(i.m, 10.0);
    assert_eq!(i.com.x, 0.1);
    assert_eq!(i.com.y, 0.2);
    assert_eq!(i.com.z, 0.3);
    assert_eq!(i.ixx, 1.0);
    assert_eq!(i.ixy, 0.1);
    assert_eq!(i.ixz, 0.2);
    assert_eq!(i.iyy, 2.0);
    assert_eq!(i.iyz, 0.3);
    assert_eq!(i.izz, 3.0);
    let built = Inertia {
        m: 10.0,
        com: Vector3 {
            x: 0.1,
            y: 0.2,
            z: 0.3,
        },
        ixx: 1.0,
        ixy: 0.1,
        ixz: 0.2,
        iyy: 2.0,
        iyz: 0.3,
        izz: 3.0,
    };
    assert_eq!(encode_fixed(&built).unwrap(), golden);
    let mut m = i;
    m.m = 99.0;
    let re: Inertia = decode_fixed(&encode_fixed(&m).unwrap()).unwrap();
    assert_eq!(re.m, 99.0);
    assert_eq!(re.izz, i.izz);
}

// ── std_msgs ───────────────────────────────────────────────────────────────

#[test]
fn golden_std_msgs_color_rgba() {
    let golden = read_golden("std_msgs", "ColorRGBA");
    let c: ColorRGBA = decode_fixed(&golden).unwrap();
    assert_eq!(c.r, 1.0);
    assert_eq!(c.g, 0.5);
    assert_eq!(c.b, 0.0);
    assert_eq!(c.a, 1.0);
    let built = ColorRGBA {
        r: 1.0,
        g: 0.5,
        b: 0.0,
        a: 1.0,
    };
    assert_eq!(encode_fixed(&built).unwrap(), golden);
    let mut m = c;
    m.r = 0.0;
    let re: ColorRGBA = decode_fixed(&encode_fixed(&m).unwrap()).unwrap();
    assert_eq!(re.r, 0.0);
    assert_eq!(re.g, c.g);
}

#[test]
fn golden_std_msgs_header() {
    let golden = read_golden("std_msgs", "Header");
    // Phase 1: decode
    let view = std_msgs::Header::from_cdr(&golden[..]).unwrap();
    assert_eq!(view.stamp(), STAMP);
    assert_eq!(view.frame_id(), FRAME_ID);
    // Phase 2: construct
    let built = std_msgs::Header::new(STAMP, FRAME_ID).unwrap();
    assert_eq!(built.to_cdr(), golden);
    // Phase 3: modify stamp
    let mut h = std_msgs::Header::from_cdr(golden.clone()).unwrap();
    h.set_stamp(Time::new(42, 0)).unwrap();
    assert_eq!(h.stamp(), Time::new(42, 0));
    assert_eq!(h.frame_id(), FRAME_ID);
}

// ── sensor_msgs (CdrFixed) ────────────────────────────────────────────────

#[test]
fn golden_sensor_msgs_navsat_status() {
    let golden = read_golden("sensor_msgs", "NavSatStatus");
    let s: NavSatStatus = decode_fixed(&golden).unwrap();
    assert_eq!(s.status, 0);
    assert_eq!(s.service, 1);
    let built = NavSatStatus {
        status: 0,
        service: 1,
    };
    assert_eq!(encode_fixed(&built).unwrap(), golden);
    let mut m = s;
    m.status = 1;
    let re: NavSatStatus = decode_fixed(&encode_fixed(&m).unwrap()).unwrap();
    assert_eq!(re.status, 1);
    assert_eq!(re.service, s.service);
}

#[test]
fn golden_sensor_msgs_region_of_interest() {
    let golden = read_golden("sensor_msgs", "RegionOfInterest");
    let r: RegionOfInterest = decode_fixed(&golden).unwrap();
    assert_eq!(r.x_offset, 10);
    assert_eq!(r.y_offset, 20);
    assert_eq!(r.height, 100);
    assert_eq!(r.width, 200);
    assert!(r.do_rectify);
    let built = RegionOfInterest {
        x_offset: 10,
        y_offset: 20,
        height: 100,
        width: 200,
        do_rectify: true,
    };
    assert_eq!(encode_fixed(&built).unwrap(), golden);
    let mut m = r;
    m.do_rectify = false;
    let re: RegionOfInterest = decode_fixed(&encode_fixed(&m).unwrap()).unwrap();
    assert!(!re.do_rectify);
    assert_eq!(re.x_offset, r.x_offset);
}

// ── sensor_msgs (buffer-backed) ───────────────────────────────────────────

#[test]
fn golden_sensor_msgs_compressed_image() {
    let golden = read_golden("sensor_msgs", "CompressedImage");
    // Phase 1
    let view = sensor_msgs::CompressedImage::from_cdr(&golden[..]).unwrap();
    assert_eq!(view.stamp(), STAMP);
    assert_eq!(view.frame_id(), FRAME_ID);
    assert_eq!(view.format(), "jpeg");
    assert_eq!(view.data().len(), 16);
    let expected_data: Vec<u8> = (0..16).collect();
    assert_eq!(view.data(), expected_data.as_slice());
    // Phase 2
    let built = sensor_msgs::CompressedImage::new(STAMP, FRAME_ID, "jpeg", &expected_data).unwrap();
    assert_eq!(built.to_cdr(), golden);
    // Phase 3: modify stamp
    let mut img = sensor_msgs::CompressedImage::from_cdr(golden.clone()).unwrap();
    img.set_stamp(Time::new(42, 0)).unwrap();
    assert_eq!(img.stamp(), Time::new(42, 0));
    assert_eq!(img.format(), "jpeg"); // unchanged
}

#[test]
fn golden_sensor_msgs_image() {
    let golden = read_golden("sensor_msgs", "Image");
    // Phase 1
    let view = sensor_msgs::Image::from_cdr(&golden[..]).unwrap();
    assert_eq!(view.stamp(), STAMP);
    assert_eq!(view.frame_id(), FRAME_ID);
    assert_eq!(view.height(), 2);
    assert_eq!(view.width(), 4);
    assert_eq!(view.encoding(), "rgb8");
    assert_eq!(view.is_bigendian(), 0);
    assert_eq!(view.step(), 12);
    let pixels: Vec<u8> = (0..24).collect();
    assert_eq!(view.data(), pixels.as_slice());
    // Phase 2
    let built = sensor_msgs::Image::new(STAMP, FRAME_ID, 2, 4, "rgb8", 0, 12, &pixels).unwrap();
    assert_eq!(built.to_cdr(), golden);
    // Phase 3: modify height
    let mut img = sensor_msgs::Image::from_cdr(golden.clone()).unwrap();
    img.set_height(999).unwrap();
    assert_eq!(img.height(), 999);
    assert_eq!(img.width(), 4); // unchanged
}

#[test]
fn golden_sensor_msgs_imu() {
    let golden = read_golden("sensor_msgs", "Imu");
    // Phase 1
    let view = sensor_msgs::Imu::from_cdr(&golden[..]).unwrap();
    assert_eq!(view.stamp(), STAMP);
    assert_eq!(view.frame_id(), FRAME_ID);
    let q = view.orientation();
    assert_eq!(q.x, 0.0);
    assert_eq!(q.y, 0.0);
    assert_eq!(q.z, 0.0);
    assert_eq!(q.w, 1.0);
    assert_eq!(view.orientation_covariance(), [0.0; 9]);
    let av = view.angular_velocity();
    assert_eq!(av.x, 0.01);
    assert_eq!(av.y, 0.02);
    assert_eq!(av.z, 0.03);
    assert_eq!(view.angular_velocity_covariance(), [0.0; 9]);
    let la = view.linear_acceleration();
    assert_eq!(la.x, 0.0);
    assert_eq!(la.y, 0.0);
    assert_eq!(la.z, 9.81);
    assert_eq!(view.linear_acceleration_covariance(), [0.0; 9]);
    // Phase 2
    let built = sensor_msgs::Imu::new(
        STAMP,
        FRAME_ID,
        Quaternion {
            x: 0.0,
            y: 0.0,
            z: 0.0,
            w: 1.0,
        },
        [0.0; 9],
        Vector3 {
            x: 0.01,
            y: 0.02,
            z: 0.03,
        },
        [0.0; 9],
        Vector3 {
            x: 0.0,
            y: 0.0,
            z: 9.81,
        },
        [0.0; 9],
    )
    .unwrap();
    assert_eq!(built.to_cdr(), golden);
    // Phase 3: construct variant with different orientation
    let imu2 = sensor_msgs::Imu::new(
        STAMP,
        FRAME_ID,
        Quaternion {
            x: 0.0,
            y: 0.0,
            z: 0.707,
            w: 0.707,
        },
        [0.0; 9],
        Vector3 {
            x: 0.01,
            y: 0.02,
            z: 0.03,
        },
        [0.0; 9],
        Vector3 {
            x: 0.0,
            y: 0.0,
            z: 9.81,
        },
        [0.0; 9],
    )
    .unwrap();
    assert_ne!(imu2.to_cdr(), golden);
}

#[test]
fn golden_sensor_msgs_navsatfix() {
    let golden = read_golden("sensor_msgs", "NavSatFix");
    let view = sensor_msgs::NavSatFix::from_cdr(&golden[..]).unwrap();
    assert_eq!(view.stamp(), STAMP);
    assert_eq!(view.frame_id(), FRAME_ID);
    assert_eq!(view.status().status, 0);
    assert_eq!(view.status().service, 1);
    assert_eq!(view.latitude(), 45.5017);
    assert_eq!(view.longitude(), -73.5673);
    assert_eq!(view.altitude(), 100.0);
    let cov = view.position_covariance();
    assert_eq!(cov[0], 1.0);
    assert_eq!(cov[4], 1.0);
    assert_eq!(cov[8], 1.0);
    assert_eq!(view.position_covariance_type(), 2);
    // Phase 2
    let built = sensor_msgs::NavSatFix::new(
        STAMP,
        FRAME_ID,
        NavSatStatus {
            status: 0,
            service: 1,
        },
        45.5017,
        -73.5673,
        100.0,
        [1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0],
        2,
    )
    .unwrap();
    assert_eq!(built.to_cdr(), golden);
    // Phase 3: construct variant
    let nsf2 = sensor_msgs::NavSatFix::new(
        STAMP,
        FRAME_ID,
        NavSatStatus {
            status: 0,
            service: 1,
        },
        45.5017,
        -73.5673,
        200.0, // different altitude
        [1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0],
        2,
    )
    .unwrap();
    assert_ne!(nsf2.to_cdr(), golden);
}

#[test]
fn golden_sensor_msgs_pointcloud2() {
    let golden = read_golden("sensor_msgs", "PointCloud2");
    let view = sensor_msgs::PointCloud2::from_cdr(&golden[..]).unwrap();
    assert_eq!(view.stamp(), STAMP);
    assert_eq!(view.frame_id(), FRAME_ID);
    assert_eq!(view.height(), 1);
    assert_eq!(view.width(), 4);
    assert_eq!(view.fields_len(), 3);
    let fields = view.fields();
    assert_eq!(fields[0].name, "x");
    assert_eq!(fields[0].offset, 0);
    assert_eq!(fields[0].datatype, 7); // FLOAT32
    assert_eq!(fields[1].name, "y");
    assert_eq!(fields[2].name, "z");
    assert!(!view.is_bigendian());
    assert_eq!(view.point_step(), 12);
    assert_eq!(view.row_step(), 48);
    assert_eq!(view.data().len(), 48);
    assert!(view.is_dense());
    // Phase 2
    let pf = [
        PointFieldView {
            name: "x",
            offset: 0,
            datatype: 7,
            count: 1,
        },
        PointFieldView {
            name: "y",
            offset: 4,
            datatype: 7,
            count: 1,
        },
        PointFieldView {
            name: "z",
            offset: 8,
            datatype: 7,
            count: 1,
        },
    ];
    let mut pc_data = Vec::new();
    for i in 0u32..4 {
        pc_data.extend_from_slice(&(i as f32).to_le_bytes());
        pc_data.extend_from_slice(&((i + 1) as f32).to_le_bytes());
        pc_data.extend_from_slice(&((i + 2) as f32).to_le_bytes());
    }
    let built =
        sensor_msgs::PointCloud2::new(STAMP, FRAME_ID, 1, 4, &pf, false, 12, 48, &pc_data, true)
            .unwrap();
    assert_eq!(built.to_cdr(), golden);
    // Phase 3
    let pc2 = sensor_msgs::PointCloud2::new(
        STAMP, FRAME_ID, 1, 4, &pf, false, 12, 48, &pc_data, false, // not dense
    )
    .unwrap();
    assert_ne!(pc2.to_cdr(), golden);
}

#[test]
fn golden_sensor_msgs_camera_info() {
    let golden = read_golden("sensor_msgs", "CameraInfo");
    let view = sensor_msgs::CameraInfo::from_cdr(&golden[..]).unwrap();
    assert_eq!(view.stamp(), STAMP);
    assert_eq!(view.frame_id(), FRAME_ID);
    assert_eq!(view.height(), 480);
    assert_eq!(view.width(), 640);
    assert_eq!(view.distortion_model(), "plumb_bob");
    assert_eq!(view.d_len(), 5);
    assert_eq!(view.d_get(0), 0.1);
    assert_eq!(view.d_get(1), -0.2);
    let k = view.k();
    assert_eq!(k[0], 500.0);
    assert_eq!(k[4], 500.0);
    let r = view.r();
    assert_eq!(r[0], 1.0);
    assert_eq!(r[4], 1.0);
    let p = view.p();
    assert_eq!(p[0], 500.0);
    assert_eq!(p[5], 500.0);
    assert_eq!(view.binning_x(), 1);
    assert_eq!(view.binning_y(), 1);
    let roi = view.roi();
    assert_eq!(roi.height, 480);
    assert_eq!(roi.width, 640);
    assert!(!roi.do_rectify);
    // Phase 2
    let d_coeffs = [0.1, -0.2, 0.001, 0.002, 0.0];
    let k_matrix = [500.0, 0.0, 320.0, 0.0, 500.0, 240.0, 0.0, 0.0, 1.0];
    let r_matrix = [1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0];
    let p_matrix = [
        500.0, 0.0, 320.0, 0.0, 0.0, 500.0, 240.0, 0.0, 0.0, 0.0, 1.0, 0.0,
    ];
    let roi_val = RegionOfInterest {
        x_offset: 0,
        y_offset: 0,
        height: 480,
        width: 640,
        do_rectify: false,
    };
    let built = sensor_msgs::CameraInfo::new(
        STAMP,
        FRAME_ID,
        480,
        640,
        "plumb_bob",
        &d_coeffs,
        k_matrix,
        r_matrix,
        p_matrix,
        1,
        1,
        roi_val,
    )
    .unwrap();
    assert_eq!(built.to_cdr(), golden);
    // Phase 3
    let ci2 = sensor_msgs::CameraInfo::new(
        STAMP,
        FRAME_ID,
        480,
        640,
        "plumb_bob",
        &d_coeffs,
        k_matrix,
        r_matrix,
        p_matrix,
        2,
        2, // different binning
        RegionOfInterest {
            x_offset: 0,
            y_offset: 0,
            height: 480,
            width: 640,
            do_rectify: false,
        },
    )
    .unwrap();
    assert_ne!(ci2.to_cdr(), golden);
}

// ── geometry_msgs (buffer-backed) ─────────────────────────────────────────

#[test]
fn golden_geometry_msgs_accel_stamped() {
    let golden = read_golden("geometry_msgs", "AccelStamped");
    let view = geometry_msgs::AccelStamped::from_cdr(&golden[..]).unwrap();
    assert_eq!(view.stamp(), STAMP);
    assert_eq!(view.frame_id(), FRAME_ID);
    let a = view.accel();
    assert_eq!(a.linear.x, 1.0);
    assert_eq!(a.linear.y, 2.0);
    assert_eq!(a.linear.z, 3.0);
    assert_eq!(a.angular.x, 0.1);
    assert_eq!(a.angular.y, 0.2);
    assert_eq!(a.angular.z, 0.3);
    // Phase 2
    let built = geometry_msgs::AccelStamped::new(
        STAMP,
        FRAME_ID,
        Accel {
            linear: Vector3 {
                x: 1.0,
                y: 2.0,
                z: 3.0,
            },
            angular: Vector3 {
                x: 0.1,
                y: 0.2,
                z: 0.3,
            },
        },
    )
    .unwrap();
    assert_eq!(built.to_cdr(), golden);
    // Phase 3: construct variant
    let as2 = geometry_msgs::AccelStamped::new(
        STAMP,
        FRAME_ID,
        Accel {
            linear: Vector3 {
                x: 99.0,
                y: 2.0,
                z: 3.0,
            },
            angular: Vector3 {
                x: 0.1,
                y: 0.2,
                z: 0.3,
            },
        },
    )
    .unwrap();
    assert_ne!(as2.to_cdr(), golden);
}

#[test]
fn golden_geometry_msgs_twist_stamped() {
    let golden = read_golden("geometry_msgs", "TwistStamped");
    let view = geometry_msgs::TwistStamped::from_cdr(&golden[..]).unwrap();
    assert_eq!(view.stamp(), STAMP);
    assert_eq!(view.frame_id(), FRAME_ID);
    let t = view.twist();
    assert_eq!(t.linear.x, 1.0);
    assert_eq!(t.angular.z, 0.3);
    let built = geometry_msgs::TwistStamped::new(
        STAMP,
        FRAME_ID,
        Twist {
            linear: Vector3 {
                x: 1.0,
                y: 2.0,
                z: 3.0,
            },
            angular: Vector3 {
                x: 0.1,
                y: 0.2,
                z: 0.3,
            },
        },
    )
    .unwrap();
    assert_eq!(built.to_cdr(), golden);
    let ts2 = geometry_msgs::TwistStamped::new(
        STAMP,
        FRAME_ID,
        Twist {
            linear: Vector3 {
                x: 1.0,
                y: 2.0,
                z: 3.0,
            },
            angular: Vector3 {
                x: 0.1,
                y: 0.2,
                z: 99.0,
            },
        },
    )
    .unwrap();
    assert_ne!(ts2.to_cdr(), golden);
}

#[test]
fn golden_geometry_msgs_inertia_stamped() {
    let golden = read_golden("geometry_msgs", "InertiaStamped");
    let view = geometry_msgs::InertiaStamped::from_cdr(&golden[..]).unwrap();
    assert_eq!(view.stamp(), STAMP);
    assert_eq!(view.frame_id(), FRAME_ID);
    let i = view.inertia();
    assert_eq!(i.m, 10.0);
    assert_eq!(i.izz, 3.0);
    let built = geometry_msgs::InertiaStamped::new(
        STAMP,
        FRAME_ID,
        Inertia {
            m: 10.0,
            com: Vector3 {
                x: 0.1,
                y: 0.2,
                z: 0.3,
            },
            ixx: 1.0,
            ixy: 0.1,
            ixz: 0.2,
            iyy: 2.0,
            iyz: 0.3,
            izz: 3.0,
        },
    )
    .unwrap();
    assert_eq!(built.to_cdr(), golden);
    let is2 = geometry_msgs::InertiaStamped::new(
        STAMP,
        FRAME_ID,
        Inertia {
            m: 99.0,
            com: Vector3 {
                x: 0.1,
                y: 0.2,
                z: 0.3,
            },
            ixx: 1.0,
            ixy: 0.1,
            ixz: 0.2,
            iyy: 2.0,
            iyz: 0.3,
            izz: 3.0,
        },
    )
    .unwrap();
    assert_ne!(is2.to_cdr(), golden);
}

#[test]
fn golden_geometry_msgs_point_stamped() {
    let golden = read_golden("geometry_msgs", "PointStamped");
    let view = geometry_msgs::PointStamped::from_cdr(&golden[..]).unwrap();
    assert_eq!(view.stamp(), STAMP);
    assert_eq!(view.frame_id(), FRAME_ID);
    let p = view.point();
    assert_eq!(p.x, 1.5);
    assert_eq!(p.y, -2.5);
    assert_eq!(p.z, 3.0);
    let built = geometry_msgs::PointStamped::new(
        STAMP,
        FRAME_ID,
        Point {
            x: 1.5,
            y: -2.5,
            z: 3.0,
        },
    )
    .unwrap();
    assert_eq!(built.to_cdr(), golden);
    let ps2 = geometry_msgs::PointStamped::new(
        STAMP,
        FRAME_ID,
        Point {
            x: 99.0,
            y: -2.5,
            z: 3.0,
        },
    )
    .unwrap();
    assert_ne!(ps2.to_cdr(), golden);
}

#[test]
fn golden_geometry_msgs_transform_stamped() {
    let golden = read_golden("geometry_msgs", "TransformStamped");
    let view = geometry_msgs::TransformStamped::from_cdr(&golden[..]).unwrap();
    assert_eq!(view.stamp(), STAMP);
    assert_eq!(view.frame_id(), FRAME_ID);
    assert_eq!(view.child_frame_id(), "child_frame");
    let tf = view.transform();
    assert_eq!(tf.translation.x, 1.5);
    assert_eq!(tf.rotation.w, 1.0);
    let built = geometry_msgs::TransformStamped::new(
        STAMP,
        FRAME_ID,
        "child_frame",
        Transform {
            translation: Vector3 {
                x: 1.5,
                y: -2.5,
                z: 3.0,
            },
            rotation: Quaternion {
                x: 0.0,
                y: 0.0,
                z: 0.0,
                w: 1.0,
            },
        },
    )
    .unwrap();
    assert_eq!(built.to_cdr(), golden);
    let ts2 = geometry_msgs::TransformStamped::new(
        STAMP,
        FRAME_ID,
        "other_child",
        Transform {
            translation: Vector3 {
                x: 1.5,
                y: -2.5,
                z: 3.0,
            },
            rotation: Quaternion {
                x: 0.0,
                y: 0.0,
                z: 0.0,
                w: 1.0,
            },
        },
    )
    .unwrap();
    assert_ne!(ts2.to_cdr(), golden);
}

// ── edgefirst_msgs ─────────────────────────────────────────────────────────

#[test]
fn golden_edgefirst_msgs_date() {
    let golden = read_golden("edgefirst_msgs", "Date");
    let d: Date = decode_fixed(&golden).unwrap();
    assert_eq!(d.year, 2025);
    assert_eq!(d.month, 6);
    assert_eq!(d.day, 15);
    let built = Date {
        year: 2025,
        month: 6,
        day: 15,
    };
    assert_eq!(encode_fixed(&built).unwrap(), golden);
    let mut m = d;
    m.year = 2030;
    let re: Date = decode_fixed(&encode_fixed(&m).unwrap()).unwrap();
    assert_eq!(re.year, 2030);
    assert_eq!(re.month, d.month);
}

#[test]
fn golden_edgefirst_msgs_mask() {
    let golden = read_golden("edgefirst_msgs", "Mask");
    let view = edgefirst_msgs::Mask::from_cdr(&golden[..]).unwrap();
    assert_eq!(view.height(), 2);
    assert_eq!(view.width(), 4);
    assert_eq!(view.length(), 0);
    assert_eq!(view.encoding(), "");
    let mask_data: Vec<u8> = (0..8).collect();
    assert_eq!(view.mask_data(), mask_data.as_slice());
    assert!(!view.boxed());
    // Phase 2
    let built = edgefirst_msgs::Mask::new(2, 4, 0, "", &mask_data, false).unwrap();
    assert_eq!(built.to_cdr(), golden);
    // Phase 3: modify height
    let mut mask = edgefirst_msgs::Mask::from_cdr(golden.clone()).unwrap();
    mask.set_height(999).unwrap();
    assert_eq!(mask.height(), 999);
    assert_eq!(mask.width(), 4); // unchanged
}

#[test]
fn golden_edgefirst_msgs_dma_buffer() {
    let golden = read_golden("edgefirst_msgs", "DmaBuffer");
    let view = edgefirst_msgs::DmaBuffer::from_cdr(&golden[..]).unwrap();
    assert_eq!(view.stamp(), STAMP);
    assert_eq!(view.frame_id(), FRAME_ID);
    assert_eq!(view.pid(), 12345);
    assert_eq!(view.fd(), 42);
    assert_eq!(view.width(), 1920);
    assert_eq!(view.height(), 1080);
    assert_eq!(view.stride(), 5760);
    assert_eq!(view.fourcc(), 0x34325247);
    assert_eq!(view.length(), 1920 * 1080 * 3);
    // Phase 2
    let built = edgefirst_msgs::DmaBuffer::new(
        STAMP,
        FRAME_ID,
        12345,
        42,
        1920,
        1080,
        5760,
        0x34325247,
        1920 * 1080 * 3,
    )
    .unwrap();
    assert_eq!(built.to_cdr(), golden);
    // Phase 3
    let dma2 = edgefirst_msgs::DmaBuffer::new(
        STAMP,
        FRAME_ID,
        12345,
        99,
        1920,
        1080,
        5760,
        0x34325247,
        1920 * 1080 * 3,
    )
    .unwrap();
    assert_ne!(dma2.to_cdr(), golden);
}

#[test]
fn golden_edgefirst_msgs_local_time() {
    let golden = read_golden("edgefirst_msgs", "LocalTime");
    let view = edgefirst_msgs::LocalTime::from_cdr(&golden[..]).unwrap();
    assert_eq!(view.stamp(), STAMP);
    assert_eq!(view.frame_id(), FRAME_ID);
    let date = view.date();
    assert_eq!(date.year, 2025);
    assert_eq!(date.month, 6);
    assert_eq!(date.day, 15);
    let time = view.time();
    assert_eq!(time.sec, 43200);
    assert_eq!(time.nanosec, 0);
    assert_eq!(view.timezone(), -300);
    // Phase 2
    let built = edgefirst_msgs::LocalTime::new(
        STAMP,
        FRAME_ID,
        Date {
            year: 2025,
            month: 6,
            day: 15,
        },
        Time::new(43200, 0),
        -300,
    )
    .unwrap();
    assert_eq!(built.to_cdr(), golden);
    // Phase 3
    let lt2 = edgefirst_msgs::LocalTime::new(
        STAMP,
        FRAME_ID,
        Date {
            year: 2025,
            month: 6,
            day: 15,
        },
        Time::new(43200, 0),
        60, // different timezone
    )
    .unwrap();
    assert_ne!(lt2.to_cdr(), golden);
}

#[test]
fn golden_edgefirst_msgs_radar_cube() {
    let golden = read_golden("edgefirst_msgs", "RadarCube");
    let view = edgefirst_msgs::RadarCube::from_cdr(&golden[..]).unwrap();
    assert_eq!(view.stamp(), STAMP);
    assert_eq!(view.frame_id(), FRAME_ID);
    assert_eq!(view.timestamp(), 1234567890123456);
    assert_eq!(view.layout(), &[6, 1, 5, 2]);
    assert_eq!(view.shape(), &[2, 4, 2, 2]);
    assert_eq!(view.scales(), &[1.0f32, 2.5, 1.0, 0.5]);
    let total = 2 * 4 * 2 * 2;
    assert_eq!(view.cube().len(), total);
    let expected_cube: Vec<i16> = (0..total as i16).map(|i| i * 100).collect();
    assert_eq!(view.cube(), expected_cube.as_slice());
    assert!(!view.is_complex());
    // Phase 2
    let built = edgefirst_msgs::RadarCube::new(
        STAMP,
        FRAME_ID,
        1234567890123456,
        &[6, 1, 5, 2],
        &[2, 4, 2, 2],
        &[1.0, 2.5, 1.0, 0.5],
        &expected_cube,
        false,
    )
    .unwrap();
    assert_eq!(built.to_cdr(), golden);
    // Phase 3
    let rc2 = edgefirst_msgs::RadarCube::new(
        STAMP,
        FRAME_ID,
        1234567890123456,
        &[6, 1, 5, 2],
        &[2, 4, 2, 2],
        &[1.0, 2.5, 1.0, 0.5],
        &expected_cube,
        true, // complex = true
    )
    .unwrap();
    assert_ne!(rc2.to_cdr(), golden);
}

#[test]
fn golden_edgefirst_msgs_radar_info() {
    let golden = read_golden("edgefirst_msgs", "RadarInfo");
    let view = edgefirst_msgs::RadarInfo::from_cdr(&golden[..]).unwrap();
    assert_eq!(view.stamp(), STAMP);
    assert_eq!(view.frame_id(), FRAME_ID);
    assert_eq!(view.center_frequency(), "77GHz");
    assert_eq!(view.frequency_sweep(), "wide");
    assert_eq!(view.range_toggle(), "off");
    assert_eq!(view.detection_sensitivity(), "high");
    assert!(view.cube());
    // Phase 2
    let built =
        edgefirst_msgs::RadarInfo::new(STAMP, FRAME_ID, "77GHz", "wide", "off", "high", true)
            .unwrap();
    assert_eq!(built.to_cdr(), golden);
    // Phase 3
    let ri2 =
        edgefirst_msgs::RadarInfo::new(STAMP, FRAME_ID, "77GHz", "wide", "off", "high", false)
            .unwrap();
    assert_ne!(ri2.to_cdr(), golden);
}

#[test]
fn golden_edgefirst_msgs_track() {
    let golden = read_golden("edgefirst_msgs", "Track");
    let view = edgefirst_msgs::Track::from_cdr(&golden[..]).unwrap();
    assert_eq!(view.id(), "t1");
    assert_eq!(view.lifetime(), 5);
    assert_eq!(view.created(), Time::new(95, 0));
    // Phase 2
    let built = edgefirst_msgs::Track::new("t1", 5, Time::new(95, 0)).unwrap();
    assert_eq!(built.to_cdr(), golden);
    // Phase 3
    let t2 = edgefirst_msgs::Track::new("t2", 5, Time::new(95, 0)).unwrap();
    assert_ne!(t2.to_cdr(), golden);
}

#[test]
fn golden_edgefirst_msgs_detect_box() {
    let golden = read_golden("edgefirst_msgs", "Box");
    let view = edgefirst_msgs::DetectBox::from_cdr(&golden[..]).unwrap();
    assert_eq!(view.center_x(), 0.5);
    assert_eq!(view.center_y(), 0.5);
    assert_eq!(view.width(), 0.1f32);
    assert_eq!(view.height(), 0.2f32);
    assert_eq!(view.label(), "car");
    assert_eq!(view.score(), 0.98f32);
    assert_eq!(view.distance(), 10.0);
    assert_eq!(view.speed(), 5.0);
    assert_eq!(view.track_id(), "t1");
    assert_eq!(view.track_lifetime(), 5);
    assert_eq!(view.track_created(), Time::new(95, 0));
    // Phase 2
    let built = edgefirst_msgs::DetectBox::new(
        0.5,
        0.5,
        0.1,
        0.2,
        "car",
        0.98,
        10.0,
        5.0,
        "t1",
        5,
        Time::new(95, 0),
    )
    .unwrap();
    assert_eq!(built.to_cdr(), golden);
    // Phase 3
    let db2 = edgefirst_msgs::DetectBox::new(
        0.5,
        0.5,
        0.1,
        0.2,
        "truck",
        0.98,
        10.0,
        5.0,
        "t1",
        5,
        Time::new(95, 0),
    )
    .unwrap();
    assert_ne!(db2.to_cdr(), golden);
}

#[test]
fn golden_edgefirst_msgs_detect() {
    let golden = read_golden("edgefirst_msgs", "Detect");
    let view = edgefirst_msgs::Detect::from_cdr(&golden[..]).unwrap();
    assert_eq!(view.stamp(), STAMP);
    assert_eq!(view.frame_id(), FRAME_ID);
    assert_eq!(view.input_timestamp(), STAMP);
    assert_eq!(view.model_time(), Time::new(0, 1000000));
    assert_eq!(view.output_time(), Time::new(0, 2000000));
    assert_eq!(view.boxes_len(), 1);
    let boxes = view.boxes();
    assert_eq!(boxes[0].label, "car");
    assert_eq!(boxes[0].score, 0.98f32);
    assert_eq!(boxes[0].track_id, "t1");
    // Phase 2
    let box_view = DetectBoxView {
        center_x: 0.5,
        center_y: 0.5,
        width: 0.1,
        height: 0.2,
        label: "car",
        score: 0.98,
        distance: 10.0,
        speed: 5.0,
        track_id: "t1",
        track_lifetime: 5,
        track_created: Time::new(95, 0),
    };
    let built = edgefirst_msgs::Detect::new(
        STAMP,
        FRAME_ID,
        STAMP,
        Time::new(0, 1000000),
        Time::new(0, 2000000),
        &[box_view],
    )
    .unwrap();
    assert_eq!(built.to_cdr(), golden);
    // Phase 3
    let box_view2 = DetectBoxView {
        center_x: 0.5,
        center_y: 0.5,
        width: 0.1,
        height: 0.2,
        label: "truck",
        score: 0.98,
        distance: 10.0,
        speed: 5.0,
        track_id: "t1",
        track_lifetime: 5,
        track_created: Time::new(95, 0),
    };
    let det2 = edgefirst_msgs::Detect::new(
        STAMP,
        FRAME_ID,
        STAMP,
        Time::new(0, 1000000),
        Time::new(0, 2000000),
        &[box_view2],
    )
    .unwrap();
    assert_ne!(det2.to_cdr(), golden);
}

#[test]
fn golden_edgefirst_msgs_detect_multi() {
    let golden = read_golden("edgefirst_msgs", "Detect_multi");
    let view = edgefirst_msgs::Detect::from_cdr(&golden[..]).unwrap();
    assert_eq!(view.stamp(), STAMP);
    assert_eq!(view.frame_id(), FRAME_ID);
    assert_eq!(view.boxes_len(), 3);
    let boxes = view.boxes();
    assert_eq!(boxes[0].label, "a");
    assert_eq!(boxes[0].track_id, "t");
    assert_eq!(boxes[0].score, 0.95f32);
    assert_eq!(boxes[1].label, "person");
    assert_eq!(boxes[1].track_id, "track_long_id");
    assert_eq!(boxes[1].score, 0.87f32);
    assert_eq!(boxes[1].track_lifetime, 10);
    assert_eq!(boxes[2].label, "ab");
    assert_eq!(boxes[2].track_id, "abc");
    assert_eq!(boxes[2].score, 0.50f32);
    // Phase 2: rebuild and compare
    let built = edgefirst_msgs::Detect::new(
        STAMP,
        FRAME_ID,
        STAMP,
        Time::new(0, 1000000),
        Time::new(0, 2000000),
        &[
            DetectBoxView {
                center_x: 0.1,
                center_y: 0.2,
                width: 0.5,
                height: 0.6,
                label: "a",
                score: 0.95,
                distance: 5.0,
                speed: 1.0,
                track_id: "t",
                track_lifetime: 1,
                track_created: Time::new(1, 0),
            },
            DetectBoxView {
                center_x: 0.3,
                center_y: 0.4,
                width: 0.2,
                height: 0.3,
                label: "person",
                score: 0.87,
                distance: 12.0,
                speed: 3.0,
                track_id: "track_long_id",
                track_lifetime: 10,
                track_created: Time::new(2, 0),
            },
            DetectBoxView {
                center_x: 0.7,
                center_y: 0.8,
                width: 0.1,
                height: 0.1,
                label: "ab",
                score: 0.50,
                distance: 0.0,
                speed: 0.0,
                track_id: "abc",
                track_lifetime: 0,
                track_created: Time::new(0, 0),
            },
        ],
    )
    .unwrap();
    assert_eq!(built.to_cdr(), golden);
}

#[test]
fn golden_edgefirst_msgs_model() {
    let golden = read_golden("edgefirst_msgs", "Model");
    let view = edgefirst_msgs::Model::from_cdr(&golden[..]).unwrap();
    assert_eq!(view.stamp(), STAMP);
    assert_eq!(view.frame_id(), FRAME_ID);
    assert_eq!(view.input_time(), Duration::new(0, 1000000));
    assert_eq!(view.model_time(), Duration::new(0, 5000000));
    assert_eq!(view.output_time(), Duration::new(0, 500000));
    assert_eq!(view.decode_time(), Duration::new(0, 200000));
    assert_eq!(view.boxes_len(), 1);
    let boxes = view.boxes();
    assert_eq!(boxes[0].label, "car");
    assert_eq!(view.masks_len(), 1);
    let masks = view.masks();
    assert_eq!(masks[0].height, 2);
    assert_eq!(masks[0].width, 4);
    assert!(masks[0].boxed);
    // Phase 2
    let box_view = DetectBoxView {
        center_x: 0.5,
        center_y: 0.5,
        width: 0.1,
        height: 0.2,
        label: "car",
        score: 0.98,
        distance: 10.0,
        speed: 5.0,
        track_id: "t1",
        track_lifetime: 5,
        track_created: Time::new(95, 0),
    };
    let mask_data: Vec<u8> = (0..8).collect();
    let mask_view = MaskView {
        height: 2,
        width: 4,
        length: 0,
        encoding: "",
        mask: &mask_data,
        boxed: true,
    };
    let built = edgefirst_msgs::Model::new(
        STAMP,
        FRAME_ID,
        Duration::new(0, 1000000),
        Duration::new(0, 5000000),
        Duration::new(0, 500000),
        Duration::new(0, 200000),
        &[box_view],
        &[mask_view],
    )
    .unwrap();
    assert_eq!(built.to_cdr(), golden);
    // Phase 3
    let built2 = edgefirst_msgs::Model::new(
        STAMP,
        FRAME_ID,
        Duration::new(0, 9999999),
        Duration::new(0, 5000000),
        Duration::new(0, 500000),
        Duration::new(0, 200000),
        &[DetectBoxView {
            center_x: 0.5,
            center_y: 0.5,
            width: 0.1,
            height: 0.2,
            label: "car",
            score: 0.98,
            distance: 10.0,
            speed: 5.0,
            track_id: "t1",
            track_lifetime: 5,
            track_created: Time::new(95, 0),
        }],
        &[MaskView {
            height: 2,
            width: 4,
            length: 0,
            encoding: "",
            mask: &mask_data,
            boxed: true,
        }],
    )
    .unwrap();
    assert_ne!(built2.to_cdr(), golden);
}

#[test]
fn golden_edgefirst_msgs_model_info() {
    let golden = read_golden("edgefirst_msgs", "ModelInfo");
    let view = edgefirst_msgs::ModelInfo::from_cdr(&golden[..]).unwrap();
    assert_eq!(view.stamp(), STAMP);
    assert_eq!(view.frame_id(), FRAME_ID);
    assert_eq!(view.input_shape(), &[1, 3, 640, 640]);
    assert_eq!(view.input_type(), 8);
    assert_eq!(view.output_shape(), &[1, 84, 8400]);
    assert_eq!(view.output_type(), 8);
    assert_eq!(view.labels(), vec!["person", "car", "bicycle"]);
    assert_eq!(view.model_type(), "object_detection");
    assert_eq!(view.model_format(), "DeepViewRT");
    assert_eq!(view.model_name(), "yolov8n");
    // Phase 2
    let built = edgefirst_msgs::ModelInfo::new(
        STAMP,
        FRAME_ID,
        &[1, 3, 640, 640],
        8,
        &[1, 84, 8400],
        8,
        &["person", "car", "bicycle"],
        "object_detection",
        "DeepViewRT",
        "yolov8n",
    )
    .unwrap();
    assert_eq!(built.to_cdr(), golden);
    // Phase 3
    let mi2 = edgefirst_msgs::ModelInfo::new(
        STAMP,
        FRAME_ID,
        &[1, 3, 640, 640],
        8,
        &[1, 84, 8400],
        8,
        &["person", "car", "bicycle"],
        "object_detection",
        "DeepViewRT",
        "yolov9n", // different name
    )
    .unwrap();
    assert_ne!(mi2.to_cdr(), golden);
}

#[test]
fn golden_edgefirst_msgs_model_info_labels() {
    let golden = read_golden("edgefirst_msgs", "ModelInfo_labels");
    let view = edgefirst_msgs::ModelInfo::from_cdr(&golden[..]).unwrap();
    assert_eq!(view.stamp(), STAMP);
    assert_eq!(view.frame_id(), FRAME_ID);
    assert_eq!(view.input_shape(), &[1, 3, 320, 320]);
    assert_eq!(view.output_shape(), &[1, 100, 6]);
    assert_eq!(view.labels(), vec!["a", "ab", "abc", "abcd", "abcde"]);
    assert_eq!(view.model_type(), "object_detection");
    assert_eq!(view.model_format(), "DeepViewRT");
    assert_eq!(view.model_name(), "yolov8n");
    // Phase 2: rebuild and compare
    let built = edgefirst_msgs::ModelInfo::new(
        STAMP,
        FRAME_ID,
        &[1, 3, 320, 320],
        8,
        &[1, 100, 6],
        8,
        &["a", "ab", "abc", "abcd", "abcde"],
        "object_detection",
        "DeepViewRT",
        "yolov8n",
    )
    .unwrap();
    assert_eq!(built.to_cdr(), golden);
}

#[test]
fn golden_edgefirst_msgs_model_info_empty() {
    let golden = read_golden("edgefirst_msgs", "ModelInfo_empty");
    let view = edgefirst_msgs::ModelInfo::from_cdr(&golden[..]).unwrap();
    assert_eq!(view.stamp(), STAMP);
    assert_eq!(view.frame_id(), FRAME_ID);
    assert_eq!(view.input_shape(), &[1, 3, 224, 224]);
    assert_eq!(view.output_shape(), &[1, 10]);
    assert_eq!(view.labels(), Vec::<&str>::new());
    assert_eq!(view.model_type(), "classifier");
    assert_eq!(view.model_format(), "onnx");
    assert_eq!(view.model_name(), "mobilenet");
    // Phase 2: rebuild and compare
    let built = edgefirst_msgs::ModelInfo::new(
        STAMP,
        FRAME_ID,
        &[1, 3, 224, 224],
        8,
        &[1, 10],
        8,
        &[],
        "classifier",
        "onnx",
        "mobilenet",
    )
    .unwrap();
    assert_eq!(built.to_cdr(), golden);
}

// ── foxglove_msgs (CdrFixed) ──────────────────────────────────────────────

#[test]
fn golden_foxglove_msgs_point2() {
    let golden = read_golden("foxglove_msgs", "Point2");
    let p: FoxglovePoint2 = decode_fixed(&golden).unwrap();
    assert_eq!(p.x, 10.5);
    assert_eq!(p.y, 20.5);
    let built = FoxglovePoint2 { x: 10.5, y: 20.5 };
    assert_eq!(encode_fixed(&built).unwrap(), golden);
    let mut m = p;
    m.x = 42.0;
    let re: FoxglovePoint2 = decode_fixed(&encode_fixed(&m).unwrap()).unwrap();
    assert_eq!(re.x, 42.0);
    assert_eq!(re.y, p.y);
}

#[test]
fn golden_foxglove_msgs_color() {
    let golden = read_golden("foxglove_msgs", "Color");
    let c: FoxgloveColor = decode_fixed(&golden).unwrap();
    assert_eq!(c.r, 1.0);
    assert_eq!(c.g, 0.5);
    assert_eq!(c.b, 0.0);
    assert_eq!(c.a, 1.0);
    let built = FoxgloveColor {
        r: 1.0,
        g: 0.5,
        b: 0.0,
        a: 1.0,
    };
    assert_eq!(encode_fixed(&built).unwrap(), golden);
    let mut m = c;
    m.g = 0.0;
    let re: FoxgloveColor = decode_fixed(&encode_fixed(&m).unwrap()).unwrap();
    assert_eq!(re.g, 0.0);
    assert_eq!(re.r, c.r);
}

#[test]
fn golden_foxglove_msgs_circle_annotation() {
    let golden = read_golden("foxglove_msgs", "CircleAnnotation");
    let c: FoxgloveCircleAnnotations = decode_fixed(&golden).unwrap();
    assert_eq!(c.timestamp, STAMP);
    assert_eq!(c.position.x, 100.0);
    assert_eq!(c.position.y, 200.0);
    assert_eq!(c.diameter, 50.0);
    assert_eq!(c.thickness, 2.0);
    assert_eq!(c.fill_color.r, 1.0);
    assert_eq!(c.fill_color.a, 0.5);
    assert_eq!(c.outline_color.g, 1.0);
    let built = FoxgloveCircleAnnotations {
        timestamp: STAMP,
        position: FoxglovePoint2 { x: 100.0, y: 200.0 },
        diameter: 50.0,
        thickness: 2.0,
        fill_color: FoxgloveColor {
            r: 1.0,
            g: 0.0,
            b: 0.0,
            a: 0.5,
        },
        outline_color: FoxgloveColor {
            r: 0.0,
            g: 1.0,
            b: 0.0,
            a: 1.0,
        },
    };
    assert_eq!(encode_fixed(&built).unwrap(), golden);
    let mut m = c;
    m.diameter = 99.0;
    let re: FoxgloveCircleAnnotations = decode_fixed(&encode_fixed(&m).unwrap()).unwrap();
    assert_eq!(re.diameter, 99.0);
    assert_eq!(re.thickness, c.thickness);
}

// ── foxglove_msgs (buffer-backed) ─────────────────────────────────────────

#[test]
fn golden_foxglove_msgs_compressed_video() {
    let golden = read_golden("foxglove_msgs", "CompressedVideo");
    let view = foxglove_msgs::FoxgloveCompressedVideo::from_cdr(&golden[..]).unwrap();
    assert_eq!(view.stamp(), STAMP);
    assert_eq!(view.frame_id(), "camera");
    let expected_data: Vec<u8> = (0..32).collect();
    assert_eq!(view.data(), expected_data.as_slice());
    assert_eq!(view.format(), "h264");
    // Phase 2
    let built =
        foxglove_msgs::FoxgloveCompressedVideo::new(STAMP, "camera", &expected_data, "h264")
            .unwrap();
    assert_eq!(built.to_cdr(), golden);
    // Phase 3: modify stamp
    let mut cv = foxglove_msgs::FoxgloveCompressedVideo::from_cdr(golden.clone()).unwrap();
    cv.set_stamp(Time::new(42, 0)).unwrap();
    assert_eq!(cv.stamp(), Time::new(42, 0));
    assert_eq!(cv.format(), "h264"); // unchanged
}

#[test]
fn golden_foxglove_msgs_text_annotation() {
    let golden = read_golden("foxglove_msgs", "TextAnnotation");
    let view = foxglove_msgs::FoxgloveTextAnnotation::from_cdr(&golden[..]).unwrap();
    assert_eq!(view.timestamp(), STAMP);
    assert_eq!(view.position().x, 50.0);
    assert_eq!(view.position().y, 100.0);
    assert_eq!(view.text(), "hello");
    assert_eq!(view.font_size(), 14.0);
    let tc = view.text_color();
    assert_eq!(tc.r, 1.0);
    assert_eq!(tc.g, 1.0);
    assert_eq!(tc.b, 1.0);
    assert_eq!(tc.a, 1.0);
    let bg = view.background_color();
    assert_eq!(bg.r, 0.0);
    assert_eq!(bg.a, 0.5);
    // Phase 2
    let built = foxglove_msgs::FoxgloveTextAnnotation::new(
        STAMP,
        FoxglovePoint2 { x: 50.0, y: 100.0 },
        "hello",
        14.0,
        FoxgloveColor {
            r: 1.0,
            g: 1.0,
            b: 1.0,
            a: 1.0,
        },
        FoxgloveColor {
            r: 0.0,
            g: 0.0,
            b: 0.0,
            a: 0.5,
        },
    )
    .unwrap();
    assert_eq!(built.to_cdr(), golden);
    // Phase 3
    let ta2 = foxglove_msgs::FoxgloveTextAnnotation::new(
        STAMP,
        FoxglovePoint2 { x: 50.0, y: 100.0 },
        "world",
        14.0,
        FoxgloveColor {
            r: 1.0,
            g: 1.0,
            b: 1.0,
            a: 1.0,
        },
        FoxgloveColor {
            r: 0.0,
            g: 0.0,
            b: 0.0,
            a: 0.5,
        },
    )
    .unwrap();
    assert_ne!(ta2.to_cdr(), golden);
}

#[test]
fn golden_foxglove_msgs_point_annotation() {
    let golden = read_golden("foxglove_msgs", "PointsAnnotation");
    let view = foxglove_msgs::FoxglovePointAnnotation::from_cdr(&golden[..]).unwrap();
    assert_eq!(view.timestamp(), STAMP);
    assert_eq!(view.type_(), 1); // POINTS
    let pts = view.points();
    assert_eq!(pts.len(), 3);
    assert_eq!(pts[0].x, 0.0);
    assert_eq!(pts[0].y, 0.0);
    assert_eq!(pts[1].x, 10.0);
    assert_eq!(pts[1].y, 20.0);
    assert_eq!(pts[2].x, 20.0);
    assert_eq!(pts[2].y, 40.0);
    let oc = view.outline_color();
    assert_eq!(oc.g, 1.0);
    assert_eq!(view.outline_colors().len(), 0);
    let fc = view.fill_color();
    assert_eq!(fc.b, 1.0);
    assert_eq!(fc.a, 0.5);
    assert_eq!(view.thickness(), 3.0);
    // Phase 2
    let pts_arr = [
        FoxglovePoint2 { x: 0.0, y: 0.0 },
        FoxglovePoint2 { x: 10.0, y: 20.0 },
        FoxglovePoint2 { x: 20.0, y: 40.0 },
    ];
    let built = foxglove_msgs::FoxglovePointAnnotation::new(
        STAMP,
        1,
        &pts_arr,
        FoxgloveColor {
            r: 0.0,
            g: 1.0,
            b: 0.0,
            a: 1.0,
        },
        &[],
        FoxgloveColor {
            r: 0.0,
            g: 0.0,
            b: 1.0,
            a: 0.5,
        },
        3.0,
    )
    .unwrap();
    assert_eq!(built.to_cdr(), golden);
    // Phase 3
    let pa2 = foxglove_msgs::FoxglovePointAnnotation::new(
        STAMP,
        2,
        &pts_arr, // LINE_LOOP
        FoxgloveColor {
            r: 0.0,
            g: 1.0,
            b: 0.0,
            a: 1.0,
        },
        &[],
        FoxgloveColor {
            r: 0.0,
            g: 0.0,
            b: 1.0,
            a: 0.5,
        },
        3.0,
    )
    .unwrap();
    assert_ne!(pa2.to_cdr(), golden);
}

#[test]
fn golden_foxglove_msgs_image_annotation() {
    let golden = read_golden("foxglove_msgs", "ImageAnnotations");
    let view = foxglove_msgs::FoxgloveImageAnnotation::from_cdr(&golden[..]).unwrap();
    // Phase 1: verify sub-elements
    let circles = view.circles();
    assert_eq!(circles.len(), 1);
    assert_eq!(circles[0].diameter, 50.0);
    assert_eq!(circles[0].position.x, 100.0);
    let points = view.points();
    assert_eq!(points.len(), 1);
    assert_eq!(points[0].type_, 1);
    assert_eq!(points[0].points.len(), 3);
    let texts = view.texts();
    assert_eq!(texts.len(), 1);
    assert_eq!(texts[0].text, "hello");
    assert_eq!(texts[0].font_size, 14.0);
    // Phase 2
    let circle = FoxgloveCircleAnnotations {
        timestamp: STAMP,
        position: FoxglovePoint2 { x: 100.0, y: 200.0 },
        diameter: 50.0,
        thickness: 2.0,
        fill_color: FoxgloveColor {
            r: 1.0,
            g: 0.0,
            b: 0.0,
            a: 0.5,
        },
        outline_color: FoxgloveColor {
            r: 0.0,
            g: 1.0,
            b: 0.0,
            a: 1.0,
        },
    };
    let pts_arr = vec![
        FoxglovePoint2 { x: 0.0, y: 0.0 },
        FoxglovePoint2 { x: 10.0, y: 20.0 },
        FoxglovePoint2 { x: 20.0, y: 40.0 },
    ];
    let pts_ann = FoxglovePointAnnotationView {
        timestamp: STAMP,
        type_: 1,
        points: pts_arr,
        outline_color: FoxgloveColor {
            r: 0.0,
            g: 1.0,
            b: 0.0,
            a: 1.0,
        },
        outline_colors: vec![],
        fill_color: FoxgloveColor {
            r: 0.0,
            g: 0.0,
            b: 1.0,
            a: 0.5,
        },
        thickness: 3.0,
    };
    let text_ann = FoxgloveTextAnnotationView {
        timestamp: STAMP,
        position: FoxglovePoint2 { x: 50.0, y: 100.0 },
        text: "hello",
        font_size: 14.0,
        text_color: FoxgloveColor {
            r: 1.0,
            g: 1.0,
            b: 1.0,
            a: 1.0,
        },
        background_color: FoxgloveColor {
            r: 0.0,
            g: 0.0,
            b: 0.0,
            a: 0.5,
        },
    };
    let built =
        foxglove_msgs::FoxgloveImageAnnotation::new(&[circle], &[pts_ann], &[text_ann]).unwrap();
    assert_eq!(built.to_cdr(), golden);
    // Phase 3
    let built2 = foxglove_msgs::FoxgloveImageAnnotation::new(
        &[],
        &[],
        &[], // empty
    )
    .unwrap();
    assert_ne!(built2.to_cdr(), golden);
}

// ── rosgraph_msgs ─────────────────────────────────────────────────────────

#[test]
fn golden_rosgraph_msgs_clock() {
    let golden = read_golden("rosgraph_msgs", "Clock");
    let c: Clock = decode_fixed(&golden).unwrap();
    assert_eq!(c.clock.sec, 1234567890);
    assert_eq!(c.clock.nanosec, 123456789);
    let built = Clock { clock: STAMP };
    assert_eq!(encode_fixed(&built).unwrap(), golden);
    let mut m = c;
    m.clock.sec = 0;
    let re: Clock = decode_fixed(&encode_fixed(&m).unwrap()).unwrap();
    assert_eq!(re.clock.sec, 0);
    assert_eq!(re.clock.nanosec, c.clock.nanosec);
}
