{
  "schema": "botlandscape-aerial-native-protocol-v1",
  "source": {
    "repository": "learnsyslab/gym-pybullet-drones",
    "commit": "7ebad1ecabd28a7000add2d05f888aa2e837c2cc",
    "package_version": "2.2.0",
    "entrypoint": "gym_pybullet_drones/examples/pid.py",
    "controller": "gym_pybullet_drones.control.DSLPIDControl.DSLPIDControl",
    "environment": "gym_pybullet_drones.envs.CtrlAviary.CtrlAviary",
    "model": "gym_pybullet_drones/assets/cf2x.urdf",
    "original_bytes_unchanged": true
  },
  "arguments": {
    "drone": "cf2x",
    "num_drones": 1,
    "physics": "pyb",
    "gui": false,
    "record_video": false,
    "plot": false,
    "user_debug_gui": false,
    "obstacles": false,
    "simulation_freq_hz": 240,
    "control_freq_hz": 48,
    "duration_sec": 12,
    "colab": false
  },
  "argument_changes_from_example_defaults": [
    "num_drones: 3 to 1",
    "gui: true to false",
    "plot: true to false",
    "obstacles: true to false",
    "output_folder: fresh task-owned log directory"
  ],
  "initialization": {
    "python_random_seed": 0,
    "numpy_seed": 0,
    "source_H_m": 0.1,
    "source_R_m": 0.3,
    "source_period_s": 10,
    "waypoints": 480,
    "first_action_rpm": [
      0,
      0,
      0,
      0
    ],
    "physics_options": "Native source defaults; actual engine parameters recorded after construction. No reset to recorded states."
  },
  "clocks": {
    "physics_hz": 240,
    "control_hz": 48,
    "physics_steps_per_control": 5,
    "control_steps": 576,
    "physics_steps": 2880,
    "state_samples": 577,
    "camera_frames": 577,
    "state_hz": 48,
    "camera_hz": 48,
    "native_elapsed_s": 12,
    "encoded_duration_s": 12.020833333333334,
    "sample_zero": "Native setup before the first issued action",
    "later_samples": "After the supplied action and all five native physics steps, before computing the next action",
    "controller_alignment": "Original source computes next action after env.step. Step 1 has zero RPM and no target. Applied control step k>=2 uses waypoint (k-2) modulo 480. Original logger timestamps and advanced waypoint labels are retained separately, not used as aligned state."
  },
  "camera": {
    "renderer": "PyBullet ER_TINY_RENDERER",
    "dimensions": [
      640,
      480
    ],
    "shadow": 1,
    "flags": "ER_SEGMENTATION_MASK_OBJECT_AND_LINKINDEX",
    "distance_m": 1.25,
    "yaw_deg": -30,
    "pitch_deg": -35,
    "roll_deg": 0,
    "target_position_m": [
      0,
      -0.3,
      0.15
    ],
    "up_axis_index": 2,
    "vertical_fov_deg": 50,
    "near_m": 0.01,
    "far_m": 10,
    "authored_observer": true,
    "native_geometry_unchanged": true,
    "frame_boundary": "One RGB image after setup and after each control step, matching state sample i and media PTS i/48. Source record_video remains false; its optional 24 fps pre-step recorder is not used."
  },
  "controls": {
    "action": "Four motor RPMs in native prop0..prop3 order",
    "position_target_units": "m",
    "target_rpy_units": "radian",
    "preprocessing": "Native CtrlAviary clipping to [0, MAX_RPM] unchanged; retain supplied and clipped RPM separately",
    "force_model": "Original Physics.PYB applies KF*RPM^2 at prop links and signed KM*RPM^2 yaw torque at center_of_mass_link; no downwash or replacement controller"
  },
  "state": {
    "native_quaternion_order": "xyzw",
    "native_linear_velocity_frame": "world",
    "native_angular_velocity_frame": "world",
    "native_observation_width": 20,
    "retain": [
      "native observation",
      "base inertial world pose and velocity",
      "link frames as PyBullet has them cached, read without recomputing forward kinematics",
      "fixed link frames relative to the base, recorded once at setup",
      "supplied and clipped motor RPM",
      "actual controller targets and returned RPM",
      "every-physics-step drone contact records and altitude"
    ],
    "canonical_quaternion_order": "wxyz, reorder only; no axis change or renormalization"
  },
  "outcome": {
    "kind": "authored-protocol",
    "declared_before_first_trial": true,
    "tracking_samples_inclusive": [
      49,
      576
    ],
    "tracking_reference": "Position target associated with the RPMs applied during this interval",
    "tracking_rmse_limit_m": 0.05,
    "tracking_maximum_error_limit_m": 0.1,
    "minimum_base_altitude_m": 0.03,
    "altitude_scope": "Every physics step 1..2880",
    "maximum_drone_contact_count": 0,
    "contact_scope": "All getContactPoints involving the drone after every physics step 1..2880",
    "required_control_steps": 576,
    "required_physics_steps": 2880,
    "all_state_values_finite": true,
    "upstream_reward": -1,
    "upstream_terminated": false,
    "upstream_truncated": false,
    "upstream_success_signal": null,
    "ending": "The 12-second source loop is a recording boundary; constant dummy reward is not a performance score."
  },
  "replay": {
    "fresh_process": true,
    "evaluate_controller": false,
    "restore_recorded_states": false,
    "initial_state_absolute_tolerance": 1e-12,
    "trajectory_absolute_tolerance": 1e-09,
    "motor_rpm_absolute_tolerance": 0,
    "contact_discrete_fields_must_match": true,
    "outcome_must_pass_again": true
  },
  "resource_limits": {
    "external_capture_timeout_s": 600,
    "external_replay_timeout_s": 300,
    "compiler_workers": 2,
    "parallel_native_browser_codec_experiments": 1
  },
  "trial": 2,
  "feedforward": {
    "argument": "target_vel of DSLPIDControl.computeControlFromState",
    "value": "Time derivative of the declared circle at the waypoint passed as target_pos: R*(2*pi/period)*[-sin(angle), cos(angle), 0] m/s, with angle = waypoint/480*2*pi + pi/2",
    "speed_m_s": 0.18849555921538758,
    "added_by": "The capture harness, at the source example's own controller call. examples/pid.py passes no target_vel, so the controller's velocity term pulls toward zero velocity while the target moves.",
    "controller_gains_changed": false,
    "source_bytes_changed": false,
    "selection": "Chosen on three tuning trajectories that are not this circle (tuning.json, stage tune). The protocol trajectory is flown with it once, by record.py."
  },
  "observation": {
    "link_query": "getLinkState with computeForwardKinematics=0",
    "reason": "With computeForwardKinematics=1 PyBullet rewrites its cached link frames. The source applies each rotor force in those frames, so the query changes the rest of the flight. The first trial used it at every sample.",
    "unobserved_reference": "After the capture the same flight is flown again with no camera and no state query. Every command and every returned observation must be identical.",
    "required_maximum_difference": 0,
    "camera_alignment": "The drone silhouette centre in frame i must move with the projected base position of sample i, not of a neighbouring sample."
  },
  "trials": [
    {
      "trial": 1,
      "protocol_sha256": "a1d022f586b996cf158ea001e359778c119c0a35ef03b2df7e0d22e148c40b85",
      "pretrial_protocol_commit": "561ac7ab5760cb13524df777ca71bd7282f60039",
      "change": "None. The source example as selected, without target_vel.",
      "tracking_rmse_m": 0.08936118720247047,
      "tracking_maximum_error_m": 0.09936062408686526,
      "passed": false,
      "evidence": "first-trial.verified.json",
      "observer_effect": "Its capture refreshed forward kinematics at every sample. The same example with no observer query measures 0.09097668588691611 m RMSE and 0.10618539749477653 m maximum error (tuning.json run 1), which fails both limits."
    },
    {
      "trial": 2,
      "change": "Reference velocity supplied to the unchanged controller; passive observation.",
      "decision": "Coordinator decision on issue #265, row #247: retune and re-run before publishing. Limit, metric, trajectory, window, seed, horizon, gains and source bytes stay as declared for trial 1.",
      "frozen_sections_sha256": {
        "source": "05073ccf6024fe677f74d475d1ebde749c9073acf4d563e7b9eb2956da97ac44",
        "arguments": "6581cf05755587e67fa6c0538dfb6df679edd0d0aa101abdbf7873827793f9c1",
        "initialization": "5fa14c1f29feb748bee0455524396c96d3df7065499620139dbbd98c68adbab2",
        "clocks": "7e2310680f10726fc99f519beda8b6df812e506ab3d79692b85999878c722965",
        "camera": "6f9f789624eb5a3a82923e13d1b3473e79044ff4dac6592b80612af7ae2f22a2",
        "controls": "2acda02536ec72065c62044ed709be5d6e1d12c14fc80c596e65a721e73bbd30",
        "outcome": "dd79448f36325d983a346324ebddb964fa464dd3d6073ef2f462a09b0a5fe806",
        "replay": "fd83fdec205db5353303609d558ec4881d5e34edcb236dede49a239a7f9318ab"
      }
    }
  ],
  "qualification_policy": "Keep every trial and report failures. Trial 1 failed and stays published as failed. Do not change seed, controller gains, horizon, trajectory, criteria or source bytes to select a passing outcome. Trial 2 changes one controller input, chosen on other trajectories, and is flown on this trajectory once; whatever it measures is published."
}
