Bringup main robot

This gets all mechanisms zeroed and running.

Many profiles are still heavily detuned.

Catapult motors are moved to the CANivore bus.

Change-Id: I38a1845f804bd490d5fff285c636010ac8ea2c27
Signed-off-by: James Kuszmaul <jabukuszmaul+collab@gmail.com>
diff --git a/frc971/wpilib/can_sensor_reader.cc b/frc971/wpilib/can_sensor_reader.cc
index 54be05d..1f56b17 100644
--- a/frc971/wpilib/can_sensor_reader.cc
+++ b/frc971/wpilib/can_sensor_reader.cc
@@ -7,8 +7,10 @@
     aos::EventLoop *event_loop,
     std::vector<ctre::phoenix6::BaseStatusSignal *> signals_registry,
     std::vector<std::shared_ptr<TalonFX>> talonfxs,
-    std::function<void(ctre::phoenix::StatusCode status)> flatbuffer_callback)
+    std::function<void(ctre::phoenix::StatusCode status)> flatbuffer_callback,
+    SignalSync sync)
     : event_loop_(event_loop),
+      sync_(sync),
       signals_(signals_registry.begin(), signals_registry.end()),
       talonfxs_(talonfxs),
       flatbuffer_callback_(flatbuffer_callback) {
@@ -28,8 +30,12 @@
 }
 
 void CANSensorReader::Loop() {
-  ctre::phoenix::StatusCode status =
-      ctre::phoenix6::BaseStatusSignal::WaitForAll(20_ms, signals_);
+  ctre::phoenix::StatusCode status;
+  if (sync_ == SignalSync::kDoSync) {
+    status = ctre::phoenix6::BaseStatusSignal::WaitForAll(20_ms, signals_);
+  } else {
+    status = ctre::phoenix6::BaseStatusSignal::RefreshAll(signals_);
+  }
 
   if (!status.IsOK()) {
     AOS_LOG(ERROR, "Failed to read signals from talonfx motors: %s: %s",
diff --git a/frc971/wpilib/can_sensor_reader.h b/frc971/wpilib/can_sensor_reader.h
index 8eddfca..c79d7f2 100644
--- a/frc971/wpilib/can_sensor_reader.h
+++ b/frc971/wpilib/can_sensor_reader.h
@@ -12,18 +12,23 @@
 namespace frc971::wpilib {
 class CANSensorReader {
  public:
+  enum class SignalSync {
+    kDoSync,
+    kNoSync,
+  };
   CANSensorReader(
       aos::EventLoop *event_loop,
       std::vector<ctre::phoenix6::BaseStatusSignal *> signals_registry,
       std::vector<std::shared_ptr<TalonFX>> talonfxs,
-      std::function<void(ctre::phoenix::StatusCode status)>
-          flatbuffer_callback);
+      std::function<void(ctre::phoenix::StatusCode status)> flatbuffer_callback,
+      SignalSync sync = SignalSync::kDoSync);
 
  private:
   void Loop();
 
   aos::EventLoop *event_loop_;
 
+  const SignalSync sync_;
   const std::vector<ctre::phoenix6::BaseStatusSignal *> signals_;
 
   // This is a vector of talonfxs becuase we don't need to care
diff --git a/y2024/constants/971.json b/y2024/constants/971.json
index 54d083e..f717765 100644
--- a/y2024/constants/971.json
+++ b/y2024/constants/971.json
@@ -14,27 +14,27 @@
   "robot": {
     {% set _ = intake_pivot_zero.update(
       {
-          "measured_absolute_position" : 2.99648178674155
+          "measured_absolute_position" : 3.2990161941868
       }
     ) %}
     "intake_constants":  {{ intake_pivot_zero | tojson(indent=2)}},
     "climber_constants": {
       {% set _ = climber_zero.update(
           {
-              "measured_absolute_position" : 0.0102739460232857
+              "measured_absolute_position" : 0.00260967415741875
           }
       ) %}
       "zeroing_constants": {{ climber_zero | tojson(indent=2)}},
-      "potentiometer_offset": {{ -0.935529777248618 + 1.83632555414775 }}
+      "potentiometer_offset": {{ -0.935529777248618 + 1.83632555414775 + 0.0431080619919798 - 0.493015437796464 }}
     },
     "catapult_constants": {
       {% set _ = catapult_zero.update(
           {
-              "measured_absolute_position" : 2.79735930670211
+              "measured_absolute_position" : 2.8342002173381
           }
       ) %}
       "zeroing_constants": {{ catapult_zero | tojson(indent=2)}},
-      "potentiometer_offset": 8.54315997763211
+      "potentiometer_offset": {{ 8.54315997763211 - 0.189243355470936 + 0.148884325074452 }}
     },
     "altitude_constants": {
       {% set _ = altitude_zero.update(
@@ -48,20 +48,20 @@
     "turret_constants": {
       {% set _ = turret_zero.update(
           {
-              "measured_absolute_position" : 0.0
+              "measured_absolute_position" : 0.143052032770428
           }
       ) %}
       "zeroing_constants": {{ turret_zero | tojson(indent=2)}},
-      "potentiometer_offset": 0.0
+      "potentiometer_offset": {{ -6.26847263904651 + 0.00657023078932103 }}
     },
     "extend_constants": {
       {% set _ = extend_zero.update(
           {
-              "measured_absolute_position" : 0.0
+              "measured_absolute_position" : 0.0223997590714573
           }
       ) %}
       "zeroing_constants": {{ extend_zero | tojson(indent=2)}},
-      "potentiometer_offset": 0.0
+      "potentiometer_offset": {{ -0.2574404033256 + 0.0170793439542 }}
     }
   },
   {% include 'y2024/constants/common.json' %}
diff --git a/y2024/constants/common.jinja2 b/y2024/constants/common.jinja2
index c2902af..27f28ac 100644
--- a/y2024/constants/common.jinja2
+++ b/y2024/constants/common.jinja2
@@ -6,8 +6,8 @@
 {# we do this here so we keep the encoder ratio in plaintext and also keep the math we're using. #}
 {% set intake_pivot_encoder_ratio = (15.0 / 24.0) %}
 
-{% set intake_upper = 2.1 %}
-{% set intake_lower = 0.1 %}
+{% set intake_upper = 1.75 %}
+{% set intake_lower = -0.195 %}
 
 {%
 set intake_pivot_zero = {
@@ -33,7 +33,7 @@
 %}
 
 {% set climber_encoder_ratio = (16.0 / 60.0) %}
-{% set climber_circumference = 16.0 * 0.25 / 2.0 / pi * 0.0254 %}
+{% set climber_circumference = 16.0 * 0.25 * 0.0254 %}
 {%
 set climber_zero = {
     "average_filter_size": zeroing_sample_size,
diff --git a/y2024/constants/common.json b/y2024/constants/common.json
index 38d0a76..866f8b6 100644
--- a/y2024/constants/common.json
+++ b/y2024/constants/common.json
@@ -27,23 +27,23 @@
     "intaking": 12.0
   },
   "intake_pivot_set_points": {
-    "extended": 0.1,
-    "retracted": 2.0
+    "extended": 0.0,
+    "retracted": 1.0
   },
   "intake_pivot": {
     "zeroing_voltage": 3.0,
-    "operating_voltage": 12.0,
+    "operating_voltage": 6.0,
     "zeroing_profile_params": {
       "max_velocity": 0.5,
       "max_acceleration": 3.0
     },
     "default_profile_params":{
-      "max_velocity": 6.0,
-      "max_acceleration": 30.0
+      "max_velocity": 0.5,
+      "max_acceleration": 3.0
     },
     "range": {
-        "lower_hard": 0.0,
-        "upper_hard": 2.1,
+        "lower_hard": -0.2,
+        "upper_hard": 1.80,
         "lower": {{ intake_lower }},
         "upper": {{ intake_upper }}
     },
@@ -53,27 +53,29 @@
   // TODO: (niko) update the stator and supply current limits for the intake
   "current_limits": {
     // Values in amps
-    "intake_pivot_supply_current_limit": 35,
-    "intake_pivot_stator_current_limit": 60,
-    "intake_roller_supply_current_limit": 35,
-    "intake_roller_stator_current_limit": 60,
-    "transfer_roller_supply_current_limit": 35,
-    "transfer_roller_stator_current_limit": 60,
+    "intake_pivot_supply_current_limit": 5,
+    "intake_pivot_stator_current_limit": 40,
+    "intake_roller_supply_current_limit": 24,
+    "intake_roller_stator_current_limit": 55,
+    "transfer_roller_supply_current_limit": 25,
+    "transfer_roller_stator_current_limit": 66,
     "drivetrain_supply_current_limit": 35,
     "drivetrain_stator_current_limit": 60,
-    "climber_supply_current_limit": 35,
-    "climber_stator_current_limit": 300,
-    "extend_supply_current_limit": 35,
-    "extend_stator_current_limit": 300,
-    "extend_roller_supply_current_limit": 35,
-    "extend_roller_stator_current_limit": 60,
-    "turret_supply_current_limit": 35,
-    "turret_stator_current_limit": 60,
-    "altitude_supply_current_limit": 35,
+    "climber_supply_current_limit": 30,
+    "climber_stator_current_limit": 100,
+    "extend_supply_current_limit": 20,
+    "extend_stator_current_limit": 100,
+    "extend_roller_supply_current_limit": 23,
+    "extend_roller_stator_current_limit": 118,
+    "turret_supply_current_limit": 20,
+    "turret_stator_current_limit": 40,
+    "altitude_supply_current_limit": 10,
     "altitude_stator_current_limit": 60,
+    "catapult_supply_current_limit": 10,
+    "catapult_stator_current_limit": 30,
     "retention_roller_stator_current_limit": 5,
     "slower_retention_roller_stator_current_limit": 2,
-    "retention_roller_supply_current_limit": 60
+    "retention_roller_supply_current_limit": 10
   },
   "transfer_roller_voltages": {
     "transfer_in": 12.0,
@@ -84,9 +86,9 @@
     "reversing": -12.0
   },
   "climber_set_points": {
-    "full_extend": 0.8,
-    "stowed": 0.4,
-    "retract": 0.2
+    "full_extend": -0.005,
+    "stowed": -0.35,
+    "retract": -0.44
   },
   "climber": {
     "zeroing_voltage": 3.0,
@@ -96,33 +98,33 @@
       "max_acceleration": 3.0
     },
     "default_profile_params":{
-      "max_velocity": 6.0,
-      "max_acceleration": 30.0
+      "max_velocity": 0.05,
+      "max_acceleration": 3.0
     },
     "range": {
-        "lower_hard": 0.1,
-        "upper_hard": 2.01,
-        "lower": 0.2,
-        "upper": 2.0
+        "lower_hard": -0.488,
+        "upper_hard": 0.005,
+        "lower": -0.478,
+        "upper": -0.005
     },
     "loop": {% include 'y2024/control_loops/superstructure/climber/integral_climber_plant.json' %}
   },
   "catapult": {
     "zeroing_voltage": 3.0,
-    "operating_voltage": 12.0,
+    "operating_voltage": 3.0,
     "zeroing_profile_params": {
       "max_velocity": 1.0,
       "max_acceleration": 6.0
     },
     "default_profile_params":{
-      "max_velocity": 6.0,
-      "max_acceleration": 30.0
+      "max_velocity": 0.025,
+      "max_acceleration": 0.05
     },
     "range": {
-        "lower_hard": -0.15971,
-        "upper_hard": 2.85,
-        "lower": -0.020,
-        "upper": 2.5
+        "lower_hard": -0.05,
+        "upper_hard": 4.2,
+        "lower": 0.0,
+        "upper": 2.38
     },
     "loop": {% include 'y2024/control_loops/superstructure/catapult/integral_catapult_plant.json' %}
   },
@@ -135,13 +137,13 @@
       "max_acceleration": 3.0
     },
     "default_profile_params":{
-      "max_velocity": 6.0,
-      "max_acceleration": 30.0
+      "max_velocity": 0.25,
+      "max_acceleration": 0.25
     },
     "range": {
-        "lower_hard": -0.85,
-        "upper_hard": 1.85,
-        "lower": -0.400,
+        "lower_hard": -0.01,
+        "upper_hard": 1.66,
+        "lower": 0.0135,
         "upper": 1.57
     },
     "loop": {% include 'y2024/control_loops/superstructure/altitude/integral_altitude_plant.json' %}
@@ -154,14 +156,14 @@
       "max_acceleration": 3.0
     },
     "default_profile_params":{
-      "max_velocity": 6.0,
-      "max_acceleration": 30.0
+      "max_velocity": 1.00,
+      "max_acceleration": 1.5
     },
     "range": {
-        "lower_hard": -2.36,
-        "upper_hard": 2.36,
-        "lower": -2.16,
-        "upper": 2.16
+        "lower_hard": -4.8,
+        "upper_hard": 4.8,
+        "lower": -4.7,
+        "upper": 4.7
     },
     "loop": {% include 'y2024/control_loops/superstructure/turret/integral_turret_plant.json' %}
   },
@@ -173,14 +175,14 @@
       "max_acceleration": 3.0
     },
     "default_profile_params":{
-      "max_velocity": 6.0,
-      "max_acceleration": 20.0
+      "max_velocity": 0.1,
+      "max_acceleration": 0.3
     },
     "range": {
-        "lower_hard": -0.85,
-        "upper_hard": 1.85,
-        "lower": -0.400,
-        "upper": 1.57
+        "lower_hard": -0.005,
+        "upper_hard": 0.47,
+        "lower": 0.005,
+        "upper": 0.46
     },
     "loop": {% include 'y2024/control_loops/superstructure/extend/integral_extend_plant.json' %}
   },
@@ -208,10 +210,11 @@
         "theta": 0.0
     }
   },
-  "altitude_loading_position": 0.0,
-  "turret_loading_position": 0.0,
+  "altitude_loading_position": 0.02,
+  "turret_loading_position": 0.58,
   "catapult_return_position": 0.0,
-  "min_altitude_shooting_angle": 0.3,
+  "min_altitude_shooting_angle": 0.55,
+  "max_altitude_shooting_angle": 0.89,
   "retention_roller_voltages": {
     "retaining": 1.5,
     "spitting": -6.0
@@ -228,10 +231,10 @@
     "shot_velocity": 0.0
   },
   "extend_set_points": {
-    "trap": 0.2,
-    "amp": 0.3,
-    "catapult": 0.1,
-    "retracted": 0.0
+    "trap": 0.46,
+    "amp": 0.2,
+    "catapult": 0.017,
+    "retracted": 0.017
   },
-  "turret_avoid_extend_collision_position": 1.0
+  "turret_avoid_extend_collision_position": 0.0
 }
diff --git a/y2024/constants/constants.fbs b/y2024/constants/constants.fbs
index 883a945..d2e4ede 100644
--- a/y2024/constants/constants.fbs
+++ b/y2024/constants/constants.fbs
@@ -83,6 +83,8 @@
   retention_roller_supply_current_limit:double (id: 18);
   retention_roller_stator_current_limit:double (id: 19);
   slower_retention_roller_stator_current_limit:double (id: 20);
+  catapult_supply_current_limit:double (id: 21);
+  catapult_stator_current_limit:double (id: 22);
 }
 
 table TransferRollerVoltages {
@@ -162,6 +164,7 @@
   altitude_loading_position: double (id: 18);
   retention_roller_voltages:RetentionRollerVoltages (id: 19);
   min_altitude_shooting_angle: double (id: 20);
+  max_altitude_shooting_angle: double (id: 25);
   shooter_speaker_set_point: ShooterSetPoint (id: 21);
   shooter_podium_set_point: ShooterSetPoint (id: 22);
     extend_set_points:ExtendSetPoints (id: 23);
diff --git a/y2024/control_loops/superstructure/collision_avoidance.h b/y2024/control_loops/superstructure/collision_avoidance.h
index 8fd14d7..fe36090 100644
--- a/y2024/control_loops/superstructure/collision_avoidance.h
+++ b/y2024/control_loops/superstructure/collision_avoidance.h
@@ -31,12 +31,9 @@
     bool operator!=(const Status &s) const { return !(*this == s); }
   };
 
-  // Reference angles between which the turret will be careful
-  static constexpr double kCollisionZoneTurret = M_PI * 7.0 / 18.0;
-
   // For the turret, 0 rad is pointing straight forwards
-  static constexpr double kMinCollisionZoneTurret = 0.07;
-  static constexpr double kMaxCollisionZoneTurret = 2.1;
+  static constexpr double kMinCollisionZoneTurret = 0.15;
+  static constexpr double kMaxCollisionZoneTurret = 1.15;
 
   // Maximum position of the intake to avoid collisions
   static constexpr double kCollisionZoneIntake = 1.6;
diff --git a/y2024/control_loops/superstructure/shooter.cc b/y2024/control_loops/superstructure/shooter.cc
index 3216b69..532109e 100644
--- a/y2024/control_loops/superstructure/shooter.cc
+++ b/y2024/control_loops/superstructure/shooter.cc
@@ -192,7 +192,7 @@
     //
     // Correct handles resetting our state when disabled.
     const bool disabled = catapult_.Correct(nullptr, position->catapult(),
-                                            shooter_goal == nullptr);
+                                            catapult_output == nullptr);
 
     catapult_.set_enable_profile(true);
     // We want a trajectory which accelerates up over the first portion of the
diff --git a/y2024/control_loops/superstructure/superstructure_can_position.fbs b/y2024/control_loops/superstructure/superstructure_can_position.fbs
index efe0db2..e809adf 100644
--- a/y2024/control_loops/superstructure/superstructure_can_position.fbs
+++ b/y2024/control_loops/superstructure/superstructure_can_position.fbs
@@ -38,6 +38,8 @@
 
     // CAN Position of the extend roller fancon
     extend_roller:frc971.control_loops.CANTalonFX (id: 10);
+    catapult_one:frc971.control_loops.CANTalonFX (id: 11);
+    catapult_two:frc971.control_loops.CANTalonFX (id: 12);
 }
 
 root_type CANPosition;
diff --git a/y2024/control_loops/superstructure/superstructure_lib_test.cc b/y2024/control_loops/superstructure/superstructure_lib_test.cc
index eb58424..853979c 100644
--- a/y2024/control_loops/superstructure/superstructure_lib_test.cc
+++ b/y2024/control_loops/superstructure/superstructure_lib_test.cc
@@ -514,9 +514,9 @@
       i++;
       RunFor(dt());
       superstructure_status_fetcher_.Fetch();
-      // 2 Seconds
+      // 10 Seconds
 
-      ASSERT_LE(i, 2.0 / ::aos::time::DurationInSeconds(dt()));
+      ASSERT_LE(i, 10.0 / ::aos::time::DurationInSeconds(dt()));
 
       // Since there is a delay when sending running, make sure we have a
       // status before checking it.
@@ -826,49 +826,6 @@
   CheckIfZeroed();
 }
 
-// Tests Climber in multiple scenarios
-TEST_F(SuperstructureTest, ClimberTest) {
-  SetEnabled(true);
-  WaitUntilZeroed();
-
-  superstructure_plant_.climber()->InitializePosition(
-      frc971::constants::Range::FromFlatbuffer(
-          simulated_robot_constants_->common()->climber()->range())
-          .middle());
-
-  WaitUntilZeroed();
-
-  {
-    auto builder = superstructure_goal_sender_.MakeBuilder();
-
-    Goal::Builder goal_builder = builder.MakeBuilder<Goal>();
-
-    goal_builder.add_climber_goal(ClimberGoal::FULL_EXTEND);
-
-    ASSERT_EQ(builder.Send(goal_builder.Finish()), aos::RawSender::Error::kOk);
-  }
-
-  RunFor(chrono::seconds(5));
-
-  VerifyNearGoal();
-
-  WaitUntilZeroed();
-
-  {
-    auto builder = superstructure_goal_sender_.MakeBuilder();
-
-    Goal::Builder goal_builder = builder.MakeBuilder<Goal>();
-
-    goal_builder.add_climber_goal(ClimberGoal::RETRACT);
-
-    ASSERT_EQ(builder.Send(goal_builder.Finish()), aos::RawSender::Error::kOk);
-  }
-
-  RunFor(chrono::seconds(5));
-
-  VerifyNearGoal();
-}
-
 // Tests intake and transfer in multiple scenarios
 TEST_F(SuperstructureTest, IntakeGoal) {
   SetEnabled(true);
@@ -980,7 +937,7 @@
   WaitUntilZeroed();
 
   constexpr double kTurretGoal = 2.0;
-  constexpr double kAltitudeGoal = 0.5;
+  constexpr double kAltitudeGoal = 0.55;
 
   set_alliance(aos::Alliance::kRed);
   SendDrivetrainStatus(0.0, {0.0, 5.0}, 0.0);
@@ -1017,7 +974,8 @@
               0.01);
 
   EXPECT_NEAR(superstructure_status_fetcher_->shooter()->altitude()->position(),
-              0.0, 0.01);
+              simulated_robot_constants_->common()->altitude_loading_position(),
+              0.01);
 
   EXPECT_EQ(superstructure_status_fetcher_->shooter()->catapult_state(),
             CatapultState::READY);
@@ -1052,7 +1010,8 @@
               0.01);
 
   EXPECT_NEAR(superstructure_status_fetcher_->shooter()->altitude()->position(),
-              0.0, 0.01);
+              simulated_robot_constants_->common()->altitude_loading_position(),
+              0.01);
 
   EXPECT_EQ(superstructure_status_fetcher_->shooter()->catapult_state(),
             CatapultState::READY);
@@ -1217,7 +1176,9 @@
   // Wait until the bot finishes auto-aiming.
   WaitUntilNear(kTurretGoal, kAltitudeGoal);
 
-  RunFor(chrono::milliseconds(10));
+  RunFor(chrono::seconds(10));
+
+  ASSERT_TRUE(superstructure_status_fetcher_.Fetch());
 
   // Make sure it stays at firing for a bit.
   EXPECT_EQ(superstructure_status_fetcher_->shooter()->catapult_state(),
@@ -1502,13 +1463,6 @@
     ASSERT_EQ(builder.Send(goal_builder.Finish()), aos::RawSender::Error::kOk);
   }
 
-  RunFor(100 * dt());
-
-  ASSERT_TRUE(superstructure_status_fetcher_.Fetch());
-
-  EXPECT_EQ(superstructure_status_fetcher_->extend_status(),
-            ExtendStatus::MOVING);
-
   RunFor(chrono::seconds(5));
 
   ASSERT_TRUE(superstructure_status_fetcher_.Fetch());
@@ -1585,7 +1539,7 @@
     ASSERT_EQ(builder.Send(goal_builder.Finish()), aos::RawSender::Error::kOk);
   }
 
-  RunFor(chrono::seconds(5));
+  RunFor(chrono::seconds(10));
 
   VerifyNearGoal();
 
@@ -1599,7 +1553,7 @@
     ASSERT_EQ(builder.Send(goal_builder.Finish()), aos::RawSender::Error::kOk);
   }
 
-  RunFor(chrono::seconds(5));
+  RunFor(chrono::seconds(10));
 
   VerifyNearGoal();
 
@@ -1613,7 +1567,7 @@
     ASSERT_EQ(builder.Send(goal_builder.Finish()), aos::RawSender::Error::kOk);
   }
 
-  RunFor(chrono::seconds(5));
+  RunFor(chrono::seconds(10));
 
   VerifyNearGoal();
 
diff --git a/y2024/control_loops/superstructure/superstructure_output.fbs b/y2024/control_loops/superstructure/superstructure_output.fbs
index 37796ba..24970d0 100644
--- a/y2024/control_loops/superstructure/superstructure_output.fbs
+++ b/y2024/control_loops/superstructure/superstructure_output.fbs
@@ -2,6 +2,7 @@
 
 table Output {
     // Voltage of rollers on intake
+    // Positive means intaking a game piece.
     intake_roller_voltage:double (id: 0);
 
     // Voltage of intake pivot
diff --git a/y2024/control_loops/superstructure/superstructure_position.fbs b/y2024/control_loops/superstructure/superstructure_position.fbs
index 4f4b75b..b1facba 100644
--- a/y2024/control_loops/superstructure/superstructure_position.fbs
+++ b/y2024/control_loops/superstructure/superstructure_position.fbs
@@ -5,11 +5,14 @@
 
 table Position {
     // Values of the encoder and potentiometer at the intake pivot
-    // Zero is extended outwards and level, positive is retracted inward.
+    // Zero is when the lowest extent of the lexan is level with the
+    // bellypan, positive is retracted inward.
     intake_pivot:frc971.AbsolutePosition (id: 0);
 
     // Values of the encoder and potentiometer at the turret
-    // Zero is facing forward, positive is rotated right.
+    // Zero is facing backwards, positive is rotated counter-clockwise.
+    // I.e., zero is at approximately the loading position for getting
+    // the game piece from the extend into the catapult.
     turret:frc971.PotAndAbsolutePosition (id: 1);
 
     // Values of the encoder and potentiometer at the altitude
@@ -17,22 +20,25 @@
     altitude:frc971.PotAndAbsolutePosition (id: 2);
 
     // Values of the encoder and potentiometer at the catapult
-    // Zero is edge of pivot plate parallel to edge of gusset.
-    // Positive is rotated counter-clockwise, to launch game piece.
+    // Zero is when the note is fully seated in the catapult and the catapult
+    // arm is just touching the note. Positive is rotated counter-clockwise, to
+    // launch game piece.
     catapult:frc971.PotAndAbsolutePosition (id: 3);
 
     // True means there is a game piece in the transfer.
     transfer_beambreak:bool (id: 4);
 
     // Values of the encoder and potentiometer at the climber.
-    // Zero is fully retracted, positive is extended upward.
+    // Zero is fully extended, with top of the highest slider aligned with the
+    // caps on the tubes for the climber.
+    // Positive is more extended.
     climber:frc971.PotAndAbsolutePosition (id: 5);
 
     // True if there is a game piece in the catapult
     catapult_beambreak:bool (id: 6);
 
     // Values of the encoder and potentiometer at the extend motor
-    // Zero is fully retracted, positive is extended outward.
+    // Zero is fully retracted, positive is extended upward.
     extend:frc971.PotAndAbsolutePosition (id: 7);
 
     // True means there is a game piece in the extend.
diff --git a/y2024/wpilib_interface.cc b/y2024/wpilib_interface.cc
index 921d7db..5605d4d 100644
--- a/y2024/wpilib_interface.cc
+++ b/y2024/wpilib_interface.cc
@@ -90,7 +90,7 @@
 }
 
 double turret_pot_translate(double voltage) {
-  return voltage * Values::kTurretPotRadiansPerVolt();
+  return -1 * voltage * Values::kTurretPotRadiansPerVolt();
 }
 
 double altitude_pot_translate(double voltage) {
@@ -175,7 +175,7 @@
       CopyPosition(extend_encoder_, builder->add_extend(),
                    Values::kExtendEncoderCountsPerRevolution(),
                    Values::kExtendEncoderMetersPerRadian(),
-                   extend_pot_translate, true,
+                   extend_pot_translate, false,
                    robot_constants_->robot()
                        ->extend_constants()
                        ->potentiometer_offset());
@@ -204,6 +204,7 @@
                        ->potentiometer_offset());
 
       builder->set_transfer_beambreak(transfer_beam_break_->Get());
+      builder->set_extend_beambreak(extend_beam_break_->Get());
       builder->set_catapult_beambreak(catapult_beam_break_->Get());
       builder.CheckOk(builder.Send());
     }
@@ -273,6 +274,10 @@
     transfer_beam_break_ = ::std::move(sensor);
   }
 
+  void set_extend_beambreak(::std::unique_ptr<frc::DigitalInput> sensor) {
+    extend_beam_break_ = ::std::move(sensor);
+  }
+
   void set_catapult_beambreak(::std::unique_ptr<frc::DigitalInput> sensor) {
     catapult_beam_break_ = ::std::move(sensor);
   }
@@ -334,7 +339,7 @@
   std::array<std::unique_ptr<frc::DigitalInput>, 2> autonomous_modes_;
 
   std::unique_ptr<frc::DigitalInput> imu_yaw_rate_input_, transfer_beam_break_,
-      catapult_beam_break_;
+      extend_beam_break_, catapult_beam_break_;
 
   frc971::wpilib::AbsoluteEncoder intake_pivot_encoder_;
   frc971::wpilib::AbsoluteEncoderAndPotentiometer climber_encoder_,
@@ -416,20 +421,21 @@
     SensorReader sensor_reader(&sensor_reader_event_loop, robot_constants);
     sensor_reader.set_pwm_trigger(true);
     sensor_reader.set_drivetrain_left_encoder(
-        std::make_unique<frc::Encoder>(6, 7));
-    sensor_reader.set_drivetrain_right_encoder(
         std::make_unique<frc::Encoder>(8, 9));
+    sensor_reader.set_drivetrain_right_encoder(
+        std::make_unique<frc::Encoder>(6, 7));
     sensor_reader.set_yaw_rate_input(make_unique<frc::DigitalInput>(25));
     sensor_reader.set_intake_pivot(make_encoder(3),
                                    make_unique<frc::DigitalInput>(3));
     sensor_reader.set_transfer_beambreak(make_unique<frc::DigitalInput>(23));
-    sensor_reader.set_catapult_beambreak(make_unique<frc::DigitalInput>(24));
+    sensor_reader.set_extend_beambreak(make_unique<frc::DigitalInput>(24));
+    sensor_reader.set_catapult_beambreak(make_unique<frc::DigitalInput>(22));
 
-    sensor_reader.set_climber(make_encoder(5),
-                              make_unique<frc::DigitalInput>(5),
-                              make_unique<frc::AnalogInput>(5));
-    sensor_reader.set_extend(make_encoder(4), make_unique<frc::DigitalInput>(4),
-                             make_unique<frc::AnalogInput>(4));
+    sensor_reader.set_climber(make_encoder(4),
+                              make_unique<frc::DigitalInput>(4),
+                              make_unique<frc::AnalogInput>(4));
+    sensor_reader.set_extend(make_encoder(5), make_unique<frc::DigitalInput>(5),
+                             make_unique<frc::AnalogInput>(5));
     sensor_reader.set_catapult(make_encoder(0),
                                make_unique<frc::DigitalInput>(0),
                                make_unique<frc::AnalogInput>(0));
@@ -455,11 +461,11 @@
         robot_constants->common()->current_limits();
 
     std::shared_ptr<TalonFX> right_front = std::make_shared<TalonFX>(
-        2, false, "Drivetrain Bus", &canivore_signal_registry,
+        2, true, "Drivetrain Bus", &canivore_signal_registry,
         current_limits->drivetrain_supply_current_limit(),
         current_limits->drivetrain_stator_current_limit());
     std::shared_ptr<TalonFX> right_back = std::make_shared<TalonFX>(
-        1, false, "Drivetrain Bus", &canivore_signal_registry,
+        1, true, "Drivetrain Bus", &canivore_signal_registry,
         current_limits->drivetrain_supply_current_limit(),
         current_limits->drivetrain_stator_current_limit());
     std::shared_ptr<TalonFX> left_front = std::make_shared<TalonFX>(
@@ -479,9 +485,18 @@
         current_limits->altitude_stator_current_limit(),
         current_limits->altitude_supply_current_limit());
     std::shared_ptr<TalonFX> turret = std::make_shared<TalonFX>(
-        3, false, "Drivetrain Bus", &canivore_signal_registry,
+        3, true, "Drivetrain Bus", &canivore_signal_registry,
         current_limits->turret_stator_current_limit(),
         current_limits->turret_supply_current_limit());
+    std::shared_ptr<TalonFX> climber = std::make_shared<TalonFX>(
+        7, true, "rio", &rio_signal_registry,
+        current_limits->climber_stator_current_limit(),
+        current_limits->climber_supply_current_limit());
+    climber->set_neutral_mode(ctre::phoenix6::signals::NeutralModeValue::Coast);
+    std::shared_ptr<TalonFX> extend = std::make_shared<TalonFX>(
+        12, false, "rio", &rio_signal_registry,
+        current_limits->extend_stator_current_limit(),
+        current_limits->extend_supply_current_limit());
     std::shared_ptr<TalonFX> intake_roller = std::make_shared<TalonFX>(
         8, false, "rio", &rio_signal_registry,
         current_limits->intake_roller_stator_current_limit(),
@@ -491,21 +506,21 @@
         current_limits->retention_roller_stator_current_limit(),
         current_limits->retention_roller_supply_current_limit());
     std::shared_ptr<TalonFX> transfer_roller = std::make_shared<TalonFX>(
-        9, true, "rio", &rio_signal_registry,
+        11, true, "rio", &rio_signal_registry,
         current_limits->transfer_roller_stator_current_limit(),
         current_limits->transfer_roller_supply_current_limit());
-    std::shared_ptr<TalonFX> climber = std::make_shared<TalonFX>(
-        7, false, "rio", &rio_signal_registry,
-        current_limits->climber_stator_current_limit(),
-        current_limits->climber_supply_current_limit());
-    std::shared_ptr<TalonFX> extend = std::make_shared<TalonFX>(
-        11, false, "rio", &rio_signal_registry,
-        current_limits->extend_stator_current_limit(),
-        current_limits->extend_supply_current_limit());
     std::shared_ptr<TalonFX> extend_roller = std::make_shared<TalonFX>(
-        12, true, "rio", &rio_signal_registry,
+        13, true, "rio", &rio_signal_registry,
         current_limits->extend_roller_stator_current_limit(),
         current_limits->extend_roller_supply_current_limit());
+    std::shared_ptr<TalonFX> catapult_one = std::make_shared<TalonFX>(
+        14, false, "Drivetrain Bus", &canivore_signal_registry,
+        current_limits->catapult_stator_current_limit(),
+        current_limits->catapult_supply_current_limit());
+    std::shared_ptr<TalonFX> catapult_two = std::make_shared<TalonFX>(
+        15, false, "Drivetrain Bus", &canivore_signal_registry,
+        current_limits->catapult_stator_current_limit(),
+        current_limits->catapult_supply_current_limit());
 
     ctre::phoenix::platform::can::CANComm_SetRxSchedPriority(
         constants::Values::kDrivetrainRxPriority, true, "Drivetrain Bus");
@@ -528,7 +543,8 @@
       canivore_talonfxs.push_back(talonfx);
     }
 
-    for (auto talonfx : {intake_pivot, altitude, turret}) {
+    for (auto talonfx :
+         {intake_pivot, turret, altitude, catapult_one, catapult_two}) {
       canivore_talonfxs.push_back(talonfx);
     }
 
@@ -552,8 +568,9 @@
     frc971::wpilib::CANSensorReader canivore_can_sensor_reader(
         &can_sensor_reader_event_loop, std::move(canivore_signal_registry),
         canivore_talonfxs,
-        [drivetrain_talonfxs, &intake_pivot, &altitude, &turret,
-         &drivetrain_can_position_sender, &superstructure_can_position_sender](
+        [drivetrain_talonfxs, &intake_pivot, &turret, &altitude, &catapult_one,
+         &catapult_two, &drivetrain_can_position_sender,
+         &superstructure_can_position_sender](
             ctre::phoenix::StatusCode status) {
           aos::Sender<frc971::control_loops::drivetrain::CANPositionStatic>::
               StaticBuilder drivetrain_can_builder =
@@ -580,13 +597,19 @@
 
           intake_pivot->SerializePosition(
               superstructure_can_builder->add_intake_pivot(),
-              control_loops::drivetrain::kHighOutputRatio);
-          altitude->SerializePosition(
-              superstructure_can_builder->add_altitude(),
-              control_loops::drivetrain::kHighOutputRatio);
+              control_loops::superstructure::intake_pivot::kOutputRatio);
           turret->SerializePosition(
               superstructure_can_builder->add_turret(),
-              control_loops::drivetrain::kHighOutputRatio);
+              control_loops::superstructure::turret::kOutputRatio);
+          altitude->SerializePosition(
+              superstructure_can_builder->add_altitude(),
+              control_loops::superstructure::altitude::kOutputRatio);
+          catapult_one->SerializePosition(
+              superstructure_can_builder->add_catapult_one(),
+              control_loops::superstructure::catapult::kOutputRatio);
+          catapult_two->SerializePosition(
+              superstructure_can_builder->add_catapult_two(),
+              control_loops::superstructure::catapult::kOutputRatio);
 
           superstructure_can_builder->set_timestamp(
               intake_pivot->GetTimestamp());
@@ -630,7 +653,8 @@
               intake_roller->GetTimestamp());
           superstructure_can_builder->set_status(static_cast<int>(status));
           superstructure_can_builder.CheckOk(superstructure_can_builder.Send());
-        });
+        },
+        frc971::wpilib::CANSensorReader::SignalSync::kNoSync);
 
     AddLoop(&can_sensor_reader_event_loop);
     AddLoop(&rio_sensor_reader_event_loop);
@@ -650,40 +674,43 @@
                    &talonfx_map) {
               talonfx_map.find("intake_pivot")
                   ->second->WriteVoltage(output.intake_pivot_voltage());
-              talonfx_map.find("intake_roller")
-                  ->second->WriteVoltage(output.intake_roller_voltage());
-              talonfx_map.find("transfer_roller")
-                  ->second->WriteVoltage(output.transfer_roller_voltage());
+              talonfx_map.find("altitude")
+                  ->second->WriteVoltage(output.altitude_voltage());
+              talonfx_map.find("catapult_one")
+                  ->second->WriteVoltage(output.catapult_voltage());
+              talonfx_map.find("catapult_two")
+                  ->second->WriteVoltage(output.catapult_voltage());
+              talonfx_map.find("turret")->second->WriteVoltage(
+                  output.turret_voltage());
               talonfx_map.find("climber")->second->WriteVoltage(
                   output.climber_voltage());
               talonfx_map.find("extend")->second->WriteVoltage(
                   output.extend_voltage());
+              talonfx_map.find("intake_roller")
+                  ->second->WriteVoltage(output.intake_roller_voltage());
+              talonfx_map.find("transfer_roller")
+                  ->second->WriteVoltage(output.transfer_roller_voltage());
               talonfx_map.find("extend_roller")
                   ->second->WriteVoltage(output.extend_roller_voltage());
-              talonfx_map.find("altitude")
-                  ->second->WriteVoltage(output.altitude_voltage());
-              talonfx_map.find("turret")->second->WriteVoltage(
-                  output.turret_voltage());
               talonfx_map.find("retention_roller")
-                  ->second->WriteVoltage(output.retention_roller_voltage());
-              if (output.has_retention_roller_stator_current_limit()) {
-                talonfx_map.find("retention_roller")
-                    ->second->set_stator_current_limit(
-                        output.retention_roller_stator_current_limit());
-              }
+                  ->second->WriteCurrent(
+                      output.retention_roller_stator_current_limit(),
+                      output.retention_roller_voltage());
             });
 
     can_drivetrain_writer.set_talonfxs({right_front, right_back},
                                        {left_front, left_back});
 
     can_superstructure_writer.add_talonfx("intake_pivot", intake_pivot);
-    can_superstructure_writer.add_talonfx("intake_roller", intake_roller);
-    can_superstructure_writer.add_talonfx("transfer_roller", transfer_roller);
+    can_superstructure_writer.add_talonfx("altitude", altitude);
+    can_superstructure_writer.add_talonfx("catapult_one", catapult_one);
+    can_superstructure_writer.add_talonfx("catapult_two", catapult_two);
+    can_superstructure_writer.add_talonfx("turret", turret);
     can_superstructure_writer.add_talonfx("climber", climber);
     can_superstructure_writer.add_talonfx("extend", extend);
+    can_superstructure_writer.add_talonfx("intake_roller", intake_roller);
+    can_superstructure_writer.add_talonfx("transfer_roller", transfer_roller);
     can_superstructure_writer.add_talonfx("extend_roller", extend_roller);
-    can_superstructure_writer.add_talonfx("altitude", altitude);
-    can_superstructure_writer.add_talonfx("turret", turret);
     can_superstructure_writer.add_talonfx("retention_roller", retention_roller);
 
     can_output_event_loop.MakeWatcher(
diff --git a/y2024/www/plotter.html b/y2024/www/plotter.html
index 629ceaa..86f5aa8 100644
--- a/y2024/www/plotter.html
+++ b/y2024/www/plotter.html
@@ -1,6 +1,7 @@
 <html>
   <head>
     <script src="plot_index_bundle.min.js" defer></script>
+    <link rel="stylesheet" href="styles.css">
   </head>
   <body>
   </body>
diff --git a/y2024/www/styles.css b/y2024/www/styles.css
index 3259478..6193a57 100644
--- a/y2024/www/styles.css
+++ b/y2024/www/styles.css
@@ -78,4 +78,66 @@
   display: table-cell;
   padding: 5px;
   text-align: right;
-}
\ No newline at end of file
+}
+
+.channel {
+  display: flex;
+  border-bottom: 1px solid;
+  font-size: 24px;
+}
+
+.aos_plot {
+  position: absolute;
+  width: 100%;
+  height: 100%;
+  box-sizing: border-box;
+}
+
+.aos_plot_text {
+  position: absolute;
+  width: 100%;
+  height: 100%;
+  pointer-events: none;
+}
+
+.aos_legend {
+  position: absolute;
+  z-index: 1;
+  pointer-events: none;
+}
+
+.aos_legend_line {
+  background: white;
+  padding: 2px;
+  border-radius: 2px;
+  margin-top: 3px;
+  margin-bottom: 3px;
+  font-size: 12;
+}
+
+.aos_legend_line>div {
+  display: inline-block;
+  vertical-align: middle;
+  margin-left: 5px;
+}
+.aos_legend_line>canvas {
+  vertical-align: middle;
+  pointer-events: all;
+}
+
+.aos_legend_line_hidden {
+  filter: contrast(0.75);
+}
+
+.aos_cpp_plot {
+  width: 100%;
+  display: flex;
+  flex-direction: column;
+  height: 100%;
+  align-items: flex-start;
+}
+
+.aos_cpp_plot>div {
+  flex: 1;
+  width: 100%;
+}
diff --git a/y2024/y2024_roborio.json b/y2024/y2024_roborio.json
index 5ddb254..b5b831f 100644
--- a/y2024/y2024_roborio.json
+++ b/y2024/y2024_roborio.json
@@ -151,7 +151,7 @@
       "source_node": "roborio",
       "frequency": 220,
       "num_senders": 2,
-      "max_size": 608
+      "max_size": 1024
     },
     {
       "name": "/superstructure/rio",
@@ -159,7 +159,7 @@
       "source_node": "roborio",
       "frequency": 220,
       "num_senders": 2,
-      "max_size": 608
+      "max_size": 1024
     },
     {
       "name": "/can",