{
  "version": "1.0.0",
  "synthetic": true,
  "scope": "PC-only teaching data, one-axis plant and decoded messages; no hardware/firmware/SITL validation",
  "imu": {
    "rows_per_case": 1001,
    "period_ms": 10,
    "frame": "body FRD: x forward, y right, z down; positive roll follows right-hand rule",
    "motion": "roll only; no pitch/yaw; initial roll zero; stationary 0-1 s used for bias estimate",
    "g_m_s2": 9.80665,
    "specific_force_without_translation": "ay=-g*sin(roll); az=-g*cos(roll); ax=0",
    "roll_deg": "0 through 2 s; 15*(1-cos(pi*(t-2)/2)) at 2-4 s; 30 at 4-6 s; 15*(1+cos(pi*(t-6)/2)) at 6-8 s; 0 thereafter",
    "gx_deg_s": "analytic roll derivative + 0.5 + 0.04*sin(2*pi*10*t)",
    "ay_noise_m_s2": "0.025*sin(2*pi*7*t)",
    "az_noise_m_s2": "0.02*cos(2*pi*9*t)",
    "motion_case": "+3 m/s^2 specific force on y for 5<=t<=5.5 s",
    "rounding": "raw CSV numeric values rounded to six decimals before replay",
    "filter_tau_s": 0.5,
    "norm_gate_m_s2": 0.3,
    "limitation": "norm near g does not prove absence of translation; this heuristic is not an EKF or yaw estimator"
  },
  "messages": {
    "rows": 12,
    "clock": "same synthetic clock for rx_ms and sample_ms; one boot/session; not actual network clock synchronization",
    "sequence_bits": 16,
    "accept_max_age_ms": 40,
    "half_range": "forward delta must be 1..32767; >=32768 is rejected as old/ambiguous",
    "expected": {"accepted": 7, "duplicate": 1, "out_of_order": 1, "stale": 2, "future_timestamp": 1, "accepted_stream_gaps": 3},
    "gap_meaning": "gaps among accepted messages, not evidence of three physical packet losses"
  },
  "control": {
    "rows_per_case": 601,
    "scenarios": ["healthy", "overload", "saturation", "data-loss"],
    "nominal_period_ms": 10,
    "normal_work_ms": 6,
    "overload_work_ms": 12,
    "overload_ticks": "40,80,...,600; fifteen missed deadlines; sequential jobs; next tick starts after previous finish",
    "sensor": "ideal current state at tick start; zero acquisition latency and no sensor noise",
    "plant": "rate_next=rate+(u-0.8*rate)*dt; angle_next=angle+rate_next*dt; degrees and seconds",
    "pid": {"kp_s_inv2": 4, "ki_s_inv3": 1, "kd_s_inv": 2.5, "integral_limit_deg_s": 20, "output_limit_deg_s2": 60},
    "setpoint": "15 deg from 1 s; saturation case 30 deg; before 1 s zero",
    "data_loss": "no new samples from 3 s; last fresh sample 2.99 s; age reaches 120 ms at 3.11 s",
    "stop_policy": "age>=120 ms latches STOP_SIM; u=0 in model; fresh data never restarts the same run",
    "limitation": "not a cascaded flight controller, ESC command, STM32 timing benchmark, PX4/ArduPilot failsafe or flight result"
  },
  "references": [
    "https://docs.px4.io/v1.16/en/msg_docs/SensorCombined",
    "https://docs.px4.io/v1.16/en/config/flight_controller_orientation",
    "https://docs.px4.io/v1.16/en/flight_stack/controller_diagrams",
    "https://docs.px4.io/v1.16/en/config/safety.html"
  ]
}
