Merge "Create camera_monitor to restart camera_reader when stuck"
diff --git a/frc971/autonomous/base_autonomous_actor.cc b/frc971/autonomous/base_autonomous_actor.cc
index 90c1454..22909be 100644
--- a/frc971/autonomous/base_autonomous_actor.cc
+++ b/frc971/autonomous/base_autonomous_actor.cc
@@ -450,6 +450,35 @@
   return false;
 }
 
+bool BaseAutonomousActor::SplineHandle::SplineDistanceTraveled(
+    double distance) {
+  base_autonomous_actor_->drivetrain_status_fetcher_.Fetch();
+  if (base_autonomous_actor_->drivetrain_status_fetcher_.get()) {
+    // Confirm that:
+    // (a) The spline has started executiong (is_executing remains true even
+    //     when we reach the end of the spline).
+    // (b) The spline that we are executing is the correct one.
+    // (c) There is less than distance distance remaining.
+    if (base_autonomous_actor_->drivetrain_status_fetcher_->trajectory_logging()
+            ->goal_spline_handle() != spline_handle_) {
+      // Never done if we aren't the active spline.
+      return false;
+    }
+
+    if (base_autonomous_actor_->drivetrain_status_fetcher_->trajectory_logging()
+            ->is_executed()) {
+      return true;
+    }
+    return base_autonomous_actor_->drivetrain_status_fetcher_
+               ->trajectory_logging()
+               ->is_executing() &&
+           base_autonomous_actor_->drivetrain_status_fetcher_
+                   ->trajectory_logging()
+                   ->distance_traveled() > distance;
+  }
+  return false;
+}
+
 bool BaseAutonomousActor::SplineHandle::WaitForSplineDistanceRemaining(
     double distance) {
   ::aos::time::PhasedLoop phased_loop(
@@ -467,6 +496,23 @@
   }
 }
 
+bool BaseAutonomousActor::SplineHandle::WaitForSplineDistanceTraveled(
+    double distance) {
+  ::aos::time::PhasedLoop phased_loop(
+      frc971::controls::kLoopFrequency,
+      base_autonomous_actor_->event_loop()->monotonic_now(),
+      ActorBase::kLoopOffset);
+  while (true) {
+    if (base_autonomous_actor_->ShouldCancel()) {
+      return false;
+    }
+    phased_loop.SleepUntilNext();
+    if (SplineDistanceTraveled(distance)) {
+      return true;
+    }
+  }
+}
+
 void BaseAutonomousActor::LineFollowAtVelocity(
     double velocity, y2019::control_loops::drivetrain::SelectionHint hint) {
   {
diff --git a/frc971/autonomous/base_autonomous_actor.h b/frc971/autonomous/base_autonomous_actor.h
index 5562dde..403854f 100644
--- a/frc971/autonomous/base_autonomous_actor.h
+++ b/frc971/autonomous/base_autonomous_actor.h
@@ -41,7 +41,9 @@
     // Whether there is less than a certain distance, in meters, remaining in
     // the current spline.
     bool SplineDistanceRemaining(double distance);
+    bool SplineDistanceTraveled(double distance);
     bool WaitForSplineDistanceRemaining(double distance);
+    bool WaitForSplineDistanceTraveled(double distance);
 
     // Returns [x, y, theta] position of the start.
     const Eigen::Vector3d &starting_position() const { return spline_start_; }
diff --git a/frc971/control_loops/drivetrain/drivetrain_status.fbs b/frc971/control_loops/drivetrain/drivetrain_status.fbs
index 89dc3d5..8158e75 100644
--- a/frc971/control_loops/drivetrain/drivetrain_status.fbs
+++ b/frc971/control_loops/drivetrain/drivetrain_status.fbs
@@ -77,6 +77,7 @@
   left_velocity:float (id: 9);
   right_velocity:float (id: 10);
   distance_remaining:float (id: 11);
+  distance_traveled:float (id: 13);
 
   // Splines that we have full plans for.
   available_splines:[int] (id: 12);
diff --git a/frc971/control_loops/drivetrain/splinedrivetrain.cc b/frc971/control_loops/drivetrain/splinedrivetrain.cc
index 0f9418c..ddcf63d 100644
--- a/frc971/control_loops/drivetrain/splinedrivetrain.cc
+++ b/frc971/control_loops/drivetrain/splinedrivetrain.cc
@@ -256,6 +256,8 @@
           ? CHECK_NOTNULL(current_trajectory())->length() - current_xva_.x()
           : 0.0);
   trajectory_logging_builder.add_available_splines(handles_vector);
+  trajectory_logging_builder.add_distance_traveled(
+      executing_spline_ ? current_xva_.x() : 0.0);
 
   return trajectory_logging_builder.Finish();
 }
diff --git a/frc971/vision/BUILD b/frc971/vision/BUILD
index 133281b..84f3be7 100644
--- a/frc971/vision/BUILD
+++ b/frc971/vision/BUILD
@@ -166,6 +166,7 @@
         ":target_map_fbs",
         "//aos/events:simulated_event_loop",
         "//frc971/control_loops:control_loop",
+        "//frc971/vision:visualize_robot",
         "//frc971/vision/ceres:pose_graph_3d_lib",
         "//third_party:opencv",
         "@com_google_ceres_solver//:ceres",
diff --git a/frc971/vision/target_mapper.cc b/frc971/vision/target_mapper.cc
index 3eba919..0d6b9e9 100644
--- a/frc971/vision/target_mapper.cc
+++ b/frc971/vision/target_mapper.cc
@@ -10,9 +10,12 @@
 DEFINE_double(distortion_noise_scalar, 1.0,
               "Scale the target pose distortion factor by this when computing "
               "the noise.");
-DEFINE_uint64(
+DEFINE_int32(
     frozen_target_id, 1,
     "Freeze the pose of this target so the map can have one fixed point.");
+DEFINE_int32(min_target_id, 1, "Minimum target id to solve for");
+DEFINE_int32(max_target_id, 8, "Maximum target id to solve for");
+DEFINE_bool(visualize_solver, false, "If true, visualize the solving process.");
 
 namespace frc971::vision {
 Eigen::Affine3d PoseUtils::Pose3dToAffine3d(
@@ -211,19 +214,28 @@
 TargetMapper::TargetMapper(
     std::string_view target_poses_path,
     const ceres::examples::VectorOfConstraints &target_constraints)
-    : target_constraints_(target_constraints) {
+    : target_constraints_(target_constraints),
+      T_frozen_actual_(Eigen::Vector3d::Zero()),
+      R_frozen_actual_(Eigen::Quaterniond::Identity()),
+      vis_robot_(cv::Size(1280, 1000)) {
   aos::FlatbufferDetachedBuffer<TargetMap> target_map =
       aos::JsonFileToFlatbuffer<TargetMap>(target_poses_path);
   for (const auto *target_pose_fbs : *target_map.message().target_poses()) {
-    target_poses_[target_pose_fbs->id()] =
+    ideal_target_poses_[target_pose_fbs->id()] =
         PoseUtils::TargetPoseFromFbs(*target_pose_fbs).pose;
   }
+  target_poses_ = ideal_target_poses_;
 }
 
 TargetMapper::TargetMapper(
     const ceres::examples::MapOfPoses &target_poses,
     const ceres::examples::VectorOfConstraints &target_constraints)
-    : target_poses_(target_poses), target_constraints_(target_constraints) {}
+    : ideal_target_poses_(target_poses),
+      target_poses_(ideal_target_poses_),
+      target_constraints_(target_constraints),
+      T_frozen_actual_(Eigen::Vector3d::Zero()),
+      R_frozen_actual_(Eigen::Quaterniond::Identity()),
+      vis_robot_(cv::Size(1280, 1000)) {}
 
 std::optional<TargetMapper::TargetPose> TargetMapper::GetTargetPoseById(
     std::vector<TargetMapper::TargetPose> target_poses, TargetId target_id) {
@@ -248,7 +260,7 @@
 // Taken from ceres/examples/slam/pose_graph_3d/pose_graph_3d.cc
 // Constructs the nonlinear least squares optimization problem from the pose
 // graph constraints.
-void TargetMapper::BuildOptimizationProblem(
+void TargetMapper::BuildTargetPoseOptimizationProblem(
     const ceres::examples::VectorOfConstraints &constraints,
     ceres::examples::MapOfPoses *poses, ceres::Problem *problem) {
   CHECK(poses != nullptr);
@@ -311,6 +323,33 @@
   problem->SetParameterBlockConstant(pose_start_iter->second.q.coeffs().data());
 }
 
+void TargetMapper::BuildMapFittingOptimizationProblem(ceres::Problem *problem) {
+  // Setup robot visualization
+  vis_robot_.ClearImage();
+  constexpr int kImageWidth = 1280;
+  constexpr double kFocalLength = 500.0;
+  vis_robot_.SetDefaultViewpoint(kImageWidth, kFocalLength);
+
+  const size_t num_targets = FLAGS_max_target_id - FLAGS_min_target_id;
+  // Translation and rotation error for each target
+  const size_t num_residuals = num_targets * 6;
+  // Set up the only cost function (also known as residual). This uses
+  // auto-differentiation to obtain the derivative (jacobian).
+  ceres::CostFunction *cost_function =
+      new ceres::AutoDiffCostFunction<TargetMapper, ceres::DYNAMIC, 3, 4>(
+          this, num_residuals, ceres::DO_NOT_TAKE_OWNERSHIP);
+
+  ceres::LossFunction *loss_function = new ceres::HuberLoss(2.0);
+  ceres::LocalParameterization *quaternion_local_parameterization =
+      new ceres::EigenQuaternionParameterization;
+
+  problem->AddResidualBlock(cost_function, loss_function,
+                            T_frozen_actual_.vector().data(),
+                            R_frozen_actual_.coeffs().data());
+  problem->SetParameterization(R_frozen_actual_.coeffs().data(),
+                               quaternion_local_parameterization);
+}
+
 // Taken from ceres/examples/slam/pose_graph_3d/pose_graph_3d.cc
 bool TargetMapper::SolveOptimizationProblem(ceres::Problem *problem) {
   CHECK_NOTNULL(problem);
@@ -330,11 +369,39 @@
 
 void TargetMapper::Solve(std::string_view field_name,
                          std::optional<std::string_view> output_dir) {
-  ceres::Problem problem;
-  BuildOptimizationProblem(target_constraints_, &target_poses_, &problem);
+  ceres::Problem target_pose_problem;
+  BuildTargetPoseOptimizationProblem(target_constraints_, &target_poses_,
+                                     &target_pose_problem);
+  CHECK(SolveOptimizationProblem(&target_pose_problem))
+      << "The target pose solve was not successful, exiting.";
 
-  CHECK(SolveOptimizationProblem(&problem))
-      << "The solve was not successful, exiting.";
+  ceres::Problem map_fitting_problem;
+  BuildMapFittingOptimizationProblem(&map_fitting_problem);
+  CHECK(SolveOptimizationProblem(&map_fitting_problem))
+      << "The map fitting solve was not successful, exiting.";
+
+  Eigen::Affine3d H_frozen_actual = T_frozen_actual_ * R_frozen_actual_;
+  LOG(INFO) << "H_frozen_actual: "
+            << PoseUtils::Affine3dToPose3d(H_frozen_actual);
+
+  auto H_world_frozen =
+      PoseUtils::Pose3dToAffine3d(target_poses_[FLAGS_frozen_target_id]);
+  auto H_world_frozenactual = H_world_frozen * H_frozen_actual;
+
+  // Offset the solved poses to become the actual ones
+  for (auto &[id, pose] : target_poses_) {
+    // Don't offset targets we didn't solve for
+    if (id < FLAGS_min_target_id || id > FLAGS_max_target_id) {
+      continue;
+    }
+
+    // Take the delta between the frozen target and the solved target, and put
+    // that on top of the actual pose of the frozen target
+    auto H_world_solved = PoseUtils::Pose3dToAffine3d(pose);
+    auto H_frozen_solved = H_world_frozen.inverse() * H_world_solved;
+    auto H_world_actual = H_world_frozenactual * H_frozen_solved;
+    pose = PoseUtils::Affine3dToPose3d(H_world_actual);
+  }
 
   auto map_json = MapToJson(field_name);
   VLOG(1) << "Solved target poses: " << map_json;
@@ -366,6 +433,110 @@
       {.multi_line = true});
 }
 
+namespace {
+
+// Hacks to extract a double from a scalar, which is either a ceres jet or a
+// double. Only used for debugging and displaying.
+template <typename S>
+double ScalarToDouble(S s) {
+  const double *ptr = reinterpret_cast<double *>(&s);
+  return *ptr;
+}
+
+template <typename S>
+Eigen::Affine3d ScalarAffineToDouble(Eigen::Transform<S, 3, Eigen::Affine> H) {
+  Eigen::Affine3d H_double;
+  for (size_t i = 0; i < H.rows(); i++) {
+    for (size_t j = 0; j < H.cols(); j++) {
+      H_double(i, j) = ScalarToDouble(H(i, j));
+    }
+  }
+  return H_double;
+}
+
+}  // namespace
+
+template <typename S>
+bool TargetMapper::operator()(const S *const translation,
+                              const S *const rotation, S *residual) const {
+  using Affine3s = Eigen::Transform<S, 3, Eigen::Affine>;
+  Eigen::Quaternion<S> R_frozen_actual(rotation[3], rotation[1], rotation[2],
+                                       rotation[0]);
+  Eigen::Translation<S, 3> T_frozen_actual(translation[0], translation[1],
+                                           translation[2]);
+  // Actual target pose in the frame of the fixed pose.
+  Affine3s H_frozen_actual = T_frozen_actual * R_frozen_actual;
+  VLOG(2) << "H_frozen_actual: "
+          << PoseUtils::Affine3dToPose3d(ScalarAffineToDouble(H_frozen_actual));
+
+  Affine3s H_world_frozen =
+      PoseUtils::Pose3dToAffine3d(target_poses_.at(FLAGS_frozen_target_id))
+          .cast<S>();
+  Affine3s H_world_frozenactual = H_world_frozen * H_frozen_actual;
+
+  size_t residual_index = 0;
+  if (FLAGS_visualize_solver) {
+    vis_robot_.ClearImage();
+  }
+
+  for (const auto &[id, solved_pose] : target_poses_) {
+    if (id < FLAGS_min_target_id || id > FLAGS_max_target_id) {
+      continue;
+    }
+
+    Affine3s H_world_ideal =
+        PoseUtils::Pose3dToAffine3d(ideal_target_poses_.at(id)).cast<S>();
+    Affine3s H_world_solved =
+        PoseUtils::Pose3dToAffine3d(solved_pose).cast<S>();
+    // Take the delta between the frozen target and the solved target, and put
+    // that on top of the actual pose of the frozen target
+    auto H_frozen_solved = H_world_frozen.inverse() * H_world_solved;
+    auto H_world_actual = H_world_frozenactual * H_frozen_solved;
+    VLOG(2) << id << ": " << H_world_actual.translation();
+    Affine3s H_ideal_actual = H_world_ideal.inverse() * H_world_actual;
+    auto T_ideal_actual = H_ideal_actual.translation();
+    VLOG(2) << "T_ideal_actual: " << T_ideal_actual;
+    VLOG(2);
+    auto R_ideal_actual = Eigen::AngleAxis<S>(H_ideal_actual.rotation());
+
+    constexpr double kTranslationScalar = 100.0;
+    constexpr double kRotationScalar = 1000.0;
+
+    // Penalize based on how much our actual poses matches the ideal
+    // ones. We've already solved for the relative poses, now figure out
+    // where all of them fit in the world.
+    residual[residual_index++] = kTranslationScalar * T_ideal_actual(0);
+    residual[residual_index++] = kTranslationScalar * T_ideal_actual(1);
+    residual[residual_index++] = kTranslationScalar * T_ideal_actual(2);
+    residual[residual_index++] =
+        kRotationScalar * R_ideal_actual.angle() * R_ideal_actual.axis().x();
+    residual[residual_index++] =
+        kRotationScalar * R_ideal_actual.angle() * R_ideal_actual.axis().y();
+    residual[residual_index++] =
+        kRotationScalar * R_ideal_actual.angle() * R_ideal_actual.axis().z();
+
+    if (FLAGS_visualize_solver) {
+      vis_robot_.DrawFrameAxes(ScalarAffineToDouble(H_world_actual),
+                               std::to_string(id), cv::Scalar(0, 255, 0));
+      vis_robot_.DrawFrameAxes(ScalarAffineToDouble(H_world_ideal),
+                               std::to_string(id), cv::Scalar(255, 255, 255));
+    }
+  }
+  if (FLAGS_visualize_solver) {
+    cv::imshow("Target maps", vis_robot_.image_);
+    cv::waitKey(0);
+  }
+
+  // Ceres can't handle residual values of exactly zero
+  for (size_t i = 0; i < residual_index; i++) {
+    if (residual[i] == S(0)) {
+      residual[i] = S(1e-9);
+    }
+  }
+
+  return true;
+}
+
 }  // namespace frc971::vision
 
 std::ostream &operator<<(std::ostream &os, ceres::examples::Pose3d pose) {
diff --git a/frc971/vision/target_mapper.h b/frc971/vision/target_mapper.h
index c782992..ca36866 100644
--- a/frc971/vision/target_mapper.h
+++ b/frc971/vision/target_mapper.h
@@ -7,6 +7,7 @@
 #include "ceres/ceres.h"
 #include "frc971/vision/ceres/types.h"
 #include "frc971/vision/target_map_generated.h"
+#include "frc971/vision/visualize_robot.h"
 
 namespace frc971::vision {
 
@@ -51,18 +52,36 @@
 
   ceres::examples::MapOfPoses target_poses() { return target_poses_; }
 
+  // Cost function for the secondary solver finding out where the whole map fits
+  // in the world
+  template <typename S>
+  bool operator()(const S *const translation, const S *const rotation,
+                  S *residual) const;
+
  private:
   // Constructs the nonlinear least squares optimization problem from the
   // pose graph constraints.
-  void BuildOptimizationProblem(
+  void BuildTargetPoseOptimizationProblem(
       const ceres::examples::VectorOfConstraints &constraints,
       ceres::examples::MapOfPoses *poses, ceres::Problem *problem);
 
+  // Constructs the nonlinear least squares optimization problem for the solved
+  // -> actual pose solver.
+  void BuildMapFittingOptimizationProblem(ceres::Problem *problem);
+
   // Returns true if the solve was successful.
   bool SolveOptimizationProblem(ceres::Problem *problem);
 
+  ceres::examples::MapOfPoses ideal_target_poses_;
   ceres::examples::MapOfPoses target_poses_;
   ceres::examples::VectorOfConstraints target_constraints_;
+
+  // Transformation moving the target map we solved for to where it actually
+  // should be in the world
+  Eigen::Translation3d T_frozen_actual_;
+  Eigen::Quaterniond R_frozen_actual_;
+
+  mutable VisualizeRobot vis_robot_;
 };
 
 // Utility functions for dealing with ceres::examples::Pose3d structs
diff --git a/frc971/vision/target_mapper_test.cc b/frc971/vision/target_mapper_test.cc
index cd7d18b..1371f89 100644
--- a/frc971/vision/target_mapper_test.cc
+++ b/frc971/vision/target_mapper_test.cc
@@ -9,6 +9,9 @@
 #include "glog/logging.h"
 #include "gtest/gtest.h"
 
+DECLARE_int32(min_target_id);
+DECLARE_int32(max_target_id);
+
 namespace frc971::vision {
 
 namespace {
@@ -338,6 +341,9 @@
 }
 
 TEST(TargetMapperTest, TwoTargetsOneConstraint) {
+  FLAGS_min_target_id = 0;
+  FLAGS_max_target_id = 1;
+
   ceres::examples::MapOfPoses target_poses;
   target_poses[0] = Make2dPose(5.0, 0.0, M_PI);
   target_poses[1] = Make2dPose(-5.0, 0.0, 0.0);
@@ -361,6 +367,9 @@
 }
 
 TEST(TargetMapperTest, TwoTargetsTwoConstraints) {
+  FLAGS_min_target_id = 0;
+  FLAGS_max_target_id = 1;
+
   ceres::examples::MapOfPoses target_poses;
   target_poses[0] = Make2dPose(5.0, 0.0, M_PI);
   target_poses[1] = Make2dPose(-5.0, 0.0, -M_PI_2);
@@ -390,6 +399,9 @@
 }
 
 TEST(TargetMapperTest, TwoTargetsOneNoisyConstraint) {
+  FLAGS_min_target_id = 0;
+  FLAGS_max_target_id = 1;
+
   ceres::examples::MapOfPoses target_poses;
   target_poses[0] = Make2dPose(5.0, 0.0, M_PI);
   target_poses[1] = Make2dPose(-5.0, 0.0, 0.0);
@@ -549,33 +561,6 @@
             .value();
     EXPECT_POSE_NEAR(mapper_target_pose, actual_target_pose.pose);
   }
-
-  //
-  // See what happens when we don't start with the
-  // correct values
-  //
-  for (auto [target_id, target_pose] : target_poses) {
-    // Skip first pose, since that needs to be correct
-    // and is fixed in the solver
-    if (target_id != 1) {
-      ceres::examples::Pose3d bad_pose{
-          Eigen::Vector3d::Zero(),
-          PoseUtils::EulerAnglesToQuaternion(Eigen::Vector3d::Zero())};
-      target_poses[target_id] = bad_pose;
-    }
-  }
-
-  frc971::vision::TargetMapper mapper_bad_poses(target_poses,
-                                                target_constraints);
-  mapper_bad_poses.Solve(kFieldName);
-
-  for (auto [target_pose_id, mapper_target_pose] :
-       mapper_bad_poses.target_poses()) {
-    TargetMapper::TargetPose actual_target_pose =
-        TargetMapper::GetTargetPoseById(actual_target_poses, target_pose_id)
-            .value();
-    EXPECT_POSE_NEAR(mapper_target_pose, actual_target_pose.pose);
-  }
 }
 
 }  // namespace frc971::vision
diff --git a/scouting/db/db.go b/scouting/db/db.go
index ca1af9e..34f2dac 100644
--- a/scouting/db/db.go
+++ b/scouting/db/db.go
@@ -37,6 +37,7 @@
 	LowConesAuto, MiddleConesAuto, HighConesAuto, ConesDroppedAuto int32
 	LowCubes, MiddleCubes, HighCubes, CubesDropped                 int32
 	LowCones, MiddleCones, HighCones, ConesDropped                 int32
+	SuperchargedPieces                                             int32
 	AvgCycle                                                       int64
 	DockedAuto, EngagedAuto, BalanceAttemptAuto                    bool
 	Docked, Engaged, BalanceAttempt                                bool
@@ -64,7 +65,7 @@
 	Notes          string
 	GoodDriving    bool
 	BadDriving     bool
-	SolidPickup    bool
+	SolidPlacing   bool
 	SketchyPlacing bool
 	GoodDefense    bool
 	BadDefense     bool
@@ -308,7 +309,7 @@
 		Notes:          data.Notes,
 		GoodDriving:    data.GoodDriving,
 		BadDriving:     data.BadDriving,
-		SolidPickup:    data.SolidPickup,
+		SolidPlacing:   data.SolidPlacing,
 		SketchyPlacing: data.SketchyPlacing,
 		GoodDefense:    data.GoodDefense,
 		BadDefense:     data.BadDefense,
diff --git a/scouting/db/db_test.go b/scouting/db/db_test.go
index b0d34d8..9c48cba 100644
--- a/scouting/db/db_test.go
+++ b/scouting/db/db_test.go
@@ -150,7 +150,7 @@
 			LowConesAuto: 1, MiddleConesAuto: 0, HighConesAuto: 2,
 			ConesDroppedAuto: 0, LowCubes: 1, MiddleCubes: 2,
 			HighCubes: 1, CubesDropped: 0, LowCones: 0,
-			MiddleCones: 2, HighCones: 1, ConesDropped: 1,
+			MiddleCones: 2, HighCones: 1, ConesDropped: 1, SuperchargedPieces: 0,
 			AvgCycle: 0, DockedAuto: true, EngagedAuto: false,
 			BalanceAttemptAuto: false, Docked: false, Engaged: false,
 			BalanceAttempt: false, CollectedBy: "emma",
@@ -162,7 +162,7 @@
 			LowConesAuto: 2, MiddleConesAuto: 0, HighConesAuto: 0,
 			ConesDroppedAuto: 1, LowCubes: 1, MiddleCubes: 0,
 			HighCubes: 0, CubesDropped: 1, LowCones: 0,
-			MiddleCones: 0, HighCones: 1, ConesDropped: 0,
+			MiddleCones: 0, HighCones: 1, ConesDropped: 0, SuperchargedPieces: 1,
 			AvgCycle: 0, DockedAuto: false, EngagedAuto: false,
 			BalanceAttemptAuto: true, Docked: true, Engaged: true,
 			BalanceAttempt: false, CollectedBy: "tyler",
@@ -174,7 +174,7 @@
 			LowConesAuto: 0, MiddleConesAuto: 2, HighConesAuto: 1,
 			ConesDroppedAuto: 1, LowCubes: 0, MiddleCubes: 0,
 			HighCubes: 2, CubesDropped: 1, LowCones: 1,
-			MiddleCones: 1, HighCones: 0, ConesDropped: 1,
+			MiddleCones: 1, HighCones: 0, ConesDropped: 1, SuperchargedPieces: 0,
 			AvgCycle: 0, DockedAuto: false, EngagedAuto: false,
 			BalanceAttemptAuto: false, Docked: false, Engaged: false,
 			BalanceAttempt: true, CollectedBy: "isaac",
@@ -186,7 +186,7 @@
 			LowConesAuto: 1, MiddleConesAuto: 0, HighConesAuto: 0,
 			ConesDroppedAuto: 0, LowCubes: 0, MiddleCubes: 1,
 			HighCubes: 2, CubesDropped: 1, LowCones: 0,
-			MiddleCones: 1, HighCones: 0, ConesDropped: 0,
+			MiddleCones: 1, HighCones: 0, ConesDropped: 0, SuperchargedPieces: 0,
 			AvgCycle: 0, DockedAuto: true, EngagedAuto: true,
 			BalanceAttemptAuto: true, Docked: false, Engaged: false,
 			BalanceAttempt: true, CollectedBy: "will",
@@ -198,7 +198,7 @@
 			LowConesAuto: 0, MiddleConesAuto: 1, HighConesAuto: 1,
 			ConesDroppedAuto: 1, LowCubes: 1, MiddleCubes: 0,
 			HighCubes: 0, CubesDropped: 2, LowCones: 1,
-			MiddleCones: 1, HighCones: 0, ConesDropped: 1,
+			MiddleCones: 1, HighCones: 0, ConesDropped: 1, SuperchargedPieces: 0,
 			AvgCycle: 0, DockedAuto: true, EngagedAuto: false,
 			BalanceAttemptAuto: false, Docked: true, Engaged: false,
 			BalanceAttempt: false, CollectedBy: "unkown",
@@ -248,7 +248,7 @@
 			LowConesAuto: 1, MiddleConesAuto: 0, HighConesAuto: 2,
 			ConesDroppedAuto: 0, LowCubes: 1, MiddleCubes: 2,
 			HighCubes: 1, CubesDropped: 0, LowCones: 0,
-			MiddleCones: 2, HighCones: 1, ConesDropped: 1,
+			MiddleCones: 2, HighCones: 1, ConesDropped: 1, SuperchargedPieces: 0,
 			AvgCycle: 0, DockedAuto: true, EngagedAuto: false,
 			BalanceAttemptAuto: false, Docked: false, Engaged: false,
 			BalanceAttempt: false, CollectedBy: "emma",
@@ -260,7 +260,7 @@
 			LowConesAuto: 2, MiddleConesAuto: 0, HighConesAuto: 0,
 			ConesDroppedAuto: 1, LowCubes: 1, MiddleCubes: 0,
 			HighCubes: 0, CubesDropped: 1, LowCones: 0,
-			MiddleCones: 0, HighCones: 1, ConesDropped: 0,
+			MiddleCones: 0, HighCones: 1, ConesDropped: 0, SuperchargedPieces: 1,
 			AvgCycle: 0, DockedAuto: true, EngagedAuto: true,
 			BalanceAttemptAuto: true, Docked: false, Engaged: false,
 			BalanceAttempt: false, CollectedBy: "tyler",
@@ -272,7 +272,7 @@
 			LowConesAuto: 1, MiddleConesAuto: 0, HighConesAuto: 2,
 			ConesDroppedAuto: 0, LowCubes: 1, MiddleCubes: 2,
 			HighCubes: 1, CubesDropped: 0, LowCones: 0,
-			MiddleCones: 2, HighCones: 1, ConesDropped: 1,
+			MiddleCones: 2, HighCones: 1, ConesDropped: 1, SuperchargedPieces: 0,
 			AvgCycle: 0, DockedAuto: true, EngagedAuto: false,
 			BalanceAttemptAuto: false, Docked: true, Engaged: false,
 			BalanceAttempt: true, CollectedBy: "emma",
@@ -329,7 +329,7 @@
 			LowConesAuto: 0, MiddleConesAuto: 2, HighConesAuto: 0,
 			ConesDroppedAuto: 1, LowCubes: 0, MiddleCubes: 1,
 			HighCubes: 2, CubesDropped: 1, LowCones: 1,
-			MiddleCones: 0, HighCones: 1, ConesDropped: 2,
+			MiddleCones: 0, HighCones: 1, ConesDropped: 2, SuperchargedPieces: 0,
 			AvgCycle: 58, DockedAuto: false, EngagedAuto: false,
 			BalanceAttemptAuto: true, Docked: true, Engaged: true,
 			BalanceAttempt: false, CollectedBy: "unknown",
@@ -341,7 +341,7 @@
 			LowConesAuto: 0, MiddleConesAuto: 1, HighConesAuto: 0,
 			ConesDroppedAuto: 0, LowCubes: 2, MiddleCubes: 0,
 			HighCubes: 1, CubesDropped: 0, LowCones: 0,
-			MiddleCones: 2, HighCones: 1, ConesDropped: 0,
+			MiddleCones: 2, HighCones: 1, ConesDropped: 0, SuperchargedPieces: 1,
 			AvgCycle: 34, DockedAuto: true, EngagedAuto: true,
 			BalanceAttemptAuto: false, Docked: true, Engaged: false,
 			BalanceAttempt: false, CollectedBy: "simon",
@@ -353,7 +353,7 @@
 			LowConesAuto: 1, MiddleConesAuto: 0, HighConesAuto: 0,
 			ConesDroppedAuto: 1, LowCubes: 0, MiddleCubes: 2,
 			HighCubes: 0, CubesDropped: 0, LowCones: 2,
-			MiddleCones: 0, HighCones: 1, ConesDropped: 1,
+			MiddleCones: 0, HighCones: 1, ConesDropped: 1, SuperchargedPieces: 0,
 			AvgCycle: 50, DockedAuto: false, EngagedAuto: false,
 			BalanceAttemptAuto: true, Docked: false, Engaged: false,
 			BalanceAttempt: false, CollectedBy: "eliza",
@@ -365,7 +365,7 @@
 			LowConesAuto: 0, MiddleConesAuto: 2, HighConesAuto: 0,
 			ConesDroppedAuto: 1, LowCubes: 0, MiddleCubes: 1,
 			HighCubes: 2, CubesDropped: 1, LowCones: 0,
-			MiddleCones: 2, HighCones: 1, ConesDropped: 1,
+			MiddleCones: 2, HighCones: 1, ConesDropped: 1, SuperchargedPieces: 0,
 			AvgCycle: 49, DockedAuto: true, EngagedAuto: false,
 			Docked: false, Engaged: false, CollectedBy: "isaac",
 		},
@@ -376,7 +376,7 @@
 			LowConesAuto: 1, MiddleConesAuto: 1, HighConesAuto: 0,
 			ConesDroppedAuto: 1, LowCubes: 1, MiddleCubes: 2,
 			HighCubes: 0, CubesDropped: 0, LowCones: 1,
-			MiddleCones: 1, HighCones: 1, ConesDropped: 0,
+			MiddleCones: 1, HighCones: 1, ConesDropped: 0, SuperchargedPieces: 0,
 			AvgCycle: 70, DockedAuto: true, EngagedAuto: true,
 			BalanceAttemptAuto: false, Docked: false, Engaged: false,
 			BalanceAttempt: true, CollectedBy: "sam",
@@ -391,7 +391,7 @@
 			LowConesAuto: 1, MiddleConesAuto: 0, HighConesAuto: 0,
 			ConesDroppedAuto: 1, LowCubes: 0, MiddleCubes: 2,
 			HighCubes: 0, CubesDropped: 0, LowCones: 2,
-			MiddleCones: 0, HighCones: 1, ConesDropped: 1,
+			MiddleCones: 0, HighCones: 1, ConesDropped: 1, SuperchargedPieces: 0,
 			AvgCycle: 50, DockedAuto: false, EngagedAuto: false,
 			BalanceAttemptAuto: true, Docked: false, Engaged: false,
 			BalanceAttempt: false, CollectedBy: "eliza",
@@ -403,7 +403,7 @@
 			LowConesAuto: 1, MiddleConesAuto: 1, HighConesAuto: 0,
 			ConesDroppedAuto: 1, LowCubes: 1, MiddleCubes: 2,
 			HighCubes: 0, CubesDropped: 0, LowCones: 1,
-			MiddleCones: 1, HighCones: 1, ConesDropped: 0,
+			MiddleCones: 1, HighCones: 1, ConesDropped: 0, SuperchargedPieces: 0,
 			AvgCycle: 70, DockedAuto: true, EngagedAuto: true,
 			BalanceAttemptAuto: false, Docked: false, Engaged: false,
 			BalanceAttempt: true, CollectedBy: "sam",
@@ -683,7 +683,7 @@
 			LowConesAuto: 0, MiddleConesAuto: 0, HighConesAuto: 2,
 			ConesDroppedAuto: 1, LowCubes: 1, MiddleCubes: 2,
 			HighCubes: 1, CubesDropped: 0, LowCones: 2,
-			MiddleCones: 0, HighCones: 2, ConesDropped: 1,
+			MiddleCones: 0, HighCones: 2, ConesDropped: 1, SuperchargedPieces: 0,
 			AvgCycle: 51, DockedAuto: true, EngagedAuto: true,
 			BalanceAttemptAuto: false, Docked: false, Engaged: false,
 			BalanceAttempt: true, CollectedBy: "isaac",
@@ -695,7 +695,7 @@
 			LowConesAuto: 1, MiddleConesAuto: 1, HighConesAuto: 0,
 			ConesDroppedAuto: 0, LowCubes: 2, MiddleCubes: 2,
 			HighCubes: 1, CubesDropped: 0, LowCones: 1,
-			MiddleCones: 0, HighCones: 2, ConesDropped: 1,
+			MiddleCones: 0, HighCones: 2, ConesDropped: 1, SuperchargedPieces: 1,
 			AvgCycle: 39, DockedAuto: false, EngagedAuto: false,
 			BalanceAttemptAuto: true, Docked: false, Engaged: false,
 			BalanceAttempt: false, CollectedBy: "jack",
@@ -707,7 +707,7 @@
 			LowConesAuto: 2, MiddleConesAuto: 0, HighConesAuto: 0,
 			ConesDroppedAuto: 1, LowCubes: 2, MiddleCubes: 2,
 			HighCubes: 0, CubesDropped: 0, LowCones: 1,
-			MiddleCones: 2, HighCones: 1, ConesDropped: 1,
+			MiddleCones: 2, HighCones: 1, ConesDropped: 1, SuperchargedPieces: 0,
 			AvgCycle: 45, DockedAuto: true, EngagedAuto: false,
 			BalanceAttemptAuto: true, Docked: false, Engaged: false,
 			BalanceAttempt: true, CollectedBy: "martin",
@@ -719,7 +719,7 @@
 			LowConesAuto: 2, MiddleConesAuto: 0, HighConesAuto: 0,
 			ConesDroppedAuto: 1, LowCubes: 2, MiddleCubes: 2,
 			HighCubes: 0, CubesDropped: 0, LowCones: 2,
-			MiddleCones: 2, HighCones: 1, ConesDropped: 1,
+			MiddleCones: 2, HighCones: 1, ConesDropped: 1, SuperchargedPieces: 0,
 			AvgCycle: 34, DockedAuto: true, EngagedAuto: false,
 			BalanceAttemptAuto: false, Docked: true, Engaged: false,
 			BalanceAttempt: false, CollectedBy: "unknown",
@@ -875,11 +875,11 @@
 
 	expected := []string{"Note 1", "Note 3"}
 
-	err := fixture.db.AddNotes(NotesData{TeamNumber: 1234, Notes: "Note 1", GoodDriving: true, BadDriving: false, SolidPickup: false, SketchyPlacing: true, GoodDefense: false, BadDefense: true, EasilyDefended: true})
+	err := fixture.db.AddNotes(NotesData{TeamNumber: 1234, Notes: "Note 1", GoodDriving: true, BadDriving: false, SolidPlacing: false, SketchyPlacing: true, GoodDefense: false, BadDefense: true, EasilyDefended: true})
 	check(t, err, "Failed to add Note")
-	err = fixture.db.AddNotes(NotesData{TeamNumber: 1235, Notes: "Note 2", GoodDriving: false, BadDriving: true, SolidPickup: false, SketchyPlacing: true, GoodDefense: false, BadDefense: false, EasilyDefended: false})
+	err = fixture.db.AddNotes(NotesData{TeamNumber: 1235, Notes: "Note 2", GoodDriving: false, BadDriving: true, SolidPlacing: false, SketchyPlacing: true, GoodDefense: false, BadDefense: false, EasilyDefended: false})
 	check(t, err, "Failed to add Note")
-	err = fixture.db.AddNotes(NotesData{TeamNumber: 1234, Notes: "Note 3", GoodDriving: true, BadDriving: false, SolidPickup: false, SketchyPlacing: true, GoodDefense: true, BadDefense: false, EasilyDefended: true})
+	err = fixture.db.AddNotes(NotesData{TeamNumber: 1234, Notes: "Note 3", GoodDriving: true, BadDriving: false, SolidPlacing: false, SketchyPlacing: true, GoodDefense: true, BadDefense: false, EasilyDefended: true})
 	check(t, err, "Failed to add Note")
 
 	actual, err := fixture.db.QueryNotes(1234)
diff --git a/scouting/scouting_test.cy.js b/scouting/scouting_test.cy.js
index 8ac1880..1d7620a 100644
--- a/scouting/scouting_test.cy.js
+++ b/scouting/scouting_test.cy.js
@@ -20,6 +20,17 @@
   cy.get(fieldSelector).type('{selectAll}' + value);
 }
 
+// Click on a random team in the Match list. The exact details here are not
+// important, but we need to know what they are. This could as well be any
+// other team from any other match.
+function clickSemiFinal2Match3Team5254() {
+  // On the 87th row of matches (index 86) click on the second team
+  // (index 1) which resolves to team 5254 in semi final 2 match 3.
+  cy.get('button.match-item')
+    .eq(86 * 6 + 1)
+    .click();
+}
+
 // Moves the nth slider left or right. A positive "adjustBy" value moves the
 // slider to the right. A negative value moves the slider to the left.
 //
@@ -88,29 +99,26 @@
     cy.get('.badge').eq(89).contains('Final 1 Match 3');
   });
 
-  it('should: prefill the match information.', () => {
-    headerShouldBe('Matches');
+  it('should: be let users enter match information manually.', () => {
+    switchToTab('Entry');
+    headerShouldBe(' Team Selection ');
 
-    // On the 87th row of matches (index 86) click on the second team
-    // (index 1) which resolves to team 5254 in semi final 2 match 3.
-    cy.get('button.match-item')
-      .eq(86 * 6 + 1)
-      .click();
+    setInputTo('#match_number', '3');
+    setInputTo('#team_number', '5254');
+    setInputTo('#set_number', '2');
+    setInputTo('#comp_level', '3: sf');
 
-    headerShouldBe('Team Selection');
-    cy.get('#match_number').should('have.value', '3');
-    cy.get('#team_number').should('have.value', '5254');
-    cy.get('#set_number').should('have.value', '2');
-    cy.get('#comp_level').should('have.value', '3: sf');
+    clickButton('Next');
+
+    headerShouldBe('5254 Init ');
   });
 
   //TODO(FILIP): Verify last action when the last action header gets added.
   it('should: be able to submit data scouting.', () => {
-    switchToTab('Data Entry');
-    headerShouldBe('Team Selection');
-    clickButton('Next');
+    clickSemiFinal2Match3Team5254();
 
     // Select Starting Position.
+    headerShouldBe('5254 Init ');
     cy.get('[type="radio"]').first().check();
     clickButton('Start Match');
 
@@ -132,16 +140,14 @@
     cy.get('[type="checkbox"]').check();
 
     clickButton('End Match');
-    headerShouldBe('Review and Submit');
+    headerShouldBe('5254 Review and Submit ');
 
     clickButton('Submit');
-    headerShouldBe('Success');
+    headerShouldBe('5254 Success ');
   });
 
   it('should: be able to return to correct screen with undo for pick and place.', () => {
-    switchToTab('Data Entry');
-    headerShouldBe('Team Selection');
-    clickButton('Next');
+    clickSemiFinal2Match3Team5254();
 
     // Select Starting Position.
     cy.get('[type="radio"]').first().check();
@@ -154,13 +160,13 @@
     clickButton('UNDO');
 
     // User should be back on pickup screen.
-    headerShouldBe('Pickup');
+    headerShouldBe('5254 Pickup ');
 
     // Check the same thing but for undoing place.
     clickButton('CUBE');
     clickButton('MID');
     clickButton('UNDO');
-    headerShouldBe('Place');
+    headerShouldBe('5254 Place ');
   });
 
   it('should: submit note scouting for multiple teams', () => {
diff --git a/scouting/webserver/requests/debug/cli/cli_test.py b/scouting/webserver/requests/debug/cli/cli_test.py
index 0c2beab..b944564 100644
--- a/scouting/webserver/requests/debug/cli/cli_test.py
+++ b/scouting/webserver/requests/debug/cli/cli_test.py
@@ -105,7 +105,7 @@
             "notes": "A very inspiring and useful comment",
             "good_driving": True,
             "bad_driving": False,
-            "solid_pickup": False,
+            "solid_placing": False,
             "sketchy_placing": True,
             "good_defense": False,
             "bad_defense": False,
@@ -128,7 +128,7 @@
             Notes: (string) (len=35) "A very inspiring and useful comment",
             GoodDriving: (bool) true,
             BadDriving: (bool) false,
-            SolidPickup: (bool) false,
+            SolidPlacing: (bool) false,
             SketchyPlacing: (bool) true,
             GoodDefense: (bool) false,
             BadDefense: (bool) false,
diff --git a/scouting/webserver/requests/messages/request_2023_data_scouting_response.fbs b/scouting/webserver/requests/messages/request_2023_data_scouting_response.fbs
index b472c58..341b012 100644
--- a/scouting/webserver/requests/messages/request_2023_data_scouting_response.fbs
+++ b/scouting/webserver/requests/messages/request_2023_data_scouting_response.fbs
@@ -24,6 +24,7 @@
   middle_cones:int (id:16);
   high_cones:int (id:17);
   cones_dropped:int (id:18);
+  supercharged_pieces:int (id:29);
   // Time in nanoseconds.
   avg_cycle: int64 (id:19);
   docked_auto: bool (id:20);
@@ -40,4 +41,4 @@
     stats_list:[Stats2023] (id:0);
 }
 
-root_type Request2023DataScoutingResponse;
+root_type Request2023DataScoutingResponse;
\ No newline at end of file
diff --git a/scouting/webserver/requests/messages/request_all_notes_response.fbs b/scouting/webserver/requests/messages/request_all_notes_response.fbs
index 983ff62..70504d3 100644
--- a/scouting/webserver/requests/messages/request_all_notes_response.fbs
+++ b/scouting/webserver/requests/messages/request_all_notes_response.fbs
@@ -5,7 +5,7 @@
     notes:string (id: 1);
     good_driving:bool (id: 2);
     bad_driving:bool (id: 3);
-    solid_pickup:bool (id: 4);
+    solid_placing:bool (id: 4);
     sketchy_placing:bool (id: 5);
     good_defense:bool (id: 6);
     bad_defense:bool (id: 7);
diff --git a/scouting/webserver/requests/messages/submit_actions.fbs b/scouting/webserver/requests/messages/submit_actions.fbs
index d8aa98d..d269788 100644
--- a/scouting/webserver/requests/messages/submit_actions.fbs
+++ b/scouting/webserver/requests/messages/submit_actions.fbs
@@ -12,7 +12,8 @@
 enum ScoreLevel: short {
     kLow,
     kMiddle,
-    kHigh
+    kHigh,
+    kSupercharged,
 }
 
 table AutoBalanceAction {
diff --git a/scouting/webserver/requests/messages/submit_notes.fbs b/scouting/webserver/requests/messages/submit_notes.fbs
index cee4dee..845a601 100644
--- a/scouting/webserver/requests/messages/submit_notes.fbs
+++ b/scouting/webserver/requests/messages/submit_notes.fbs
@@ -5,7 +5,7 @@
     notes:string (id: 1);
     good_driving:bool (id: 2);
     bad_driving:bool (id: 3);
-    solid_pickup:bool (id: 4);
+    solid_placing:bool (id: 4);
     sketchy_placing:bool (id: 5);
     good_defense:bool (id: 6);
     bad_defense:bool (id: 7);
diff --git a/scouting/webserver/requests/requests.go b/scouting/webserver/requests/requests.go
index 8646a30..a6ee44c 100644
--- a/scouting/webserver/requests/requests.go
+++ b/scouting/webserver/requests/requests.go
@@ -355,7 +355,7 @@
 		Notes:          string(request.Notes()),
 		GoodDriving:    bool(request.GoodDriving()),
 		BadDriving:     bool(request.BadDriving()),
-		SolidPickup:    bool(request.SolidPickup()),
+		SolidPlacing:   bool(request.SolidPlacing()),
 		SketchyPlacing: bool(request.SketchyPlacing()),
 		GoodDefense:    bool(request.GoodDefense()),
 		BadDefense:     bool(request.BadDefense()),
@@ -380,7 +380,7 @@
 	stat := db.Stats2023{TeamNumber: string(submitActions.TeamNumber()), MatchNumber: submitActions.MatchNumber(), SetNumber: submitActions.SetNumber(), CompLevel: string(submitActions.CompLevel()),
 		StartingQuadrant: 0, LowCubesAuto: 0, MiddleCubesAuto: 0, HighCubesAuto: 0, CubesDroppedAuto: 0,
 		LowConesAuto: 0, MiddleConesAuto: 0, HighConesAuto: 0, ConesDroppedAuto: 0, LowCubes: 0, MiddleCubes: 0, HighCubes: 0,
-		CubesDropped: 0, LowCones: 0, MiddleCones: 0, HighCones: 0, ConesDropped: 0, AvgCycle: 0, CollectedBy: string(submitActions.CollectedBy()),
+		CubesDropped: 0, LowCones: 0, MiddleCones: 0, HighCones: 0, ConesDropped: 0, SuperchargedPieces: 0, AvgCycle: 0, CollectedBy: string(submitActions.CollectedBy()),
 	}
 	// Loop over all actions.
 	for i := 0; i < submitActions.ActionsListLength(); i++ {
@@ -460,6 +460,8 @@
 				stat.HighConesAuto += 1
 			} else if object == 1 && level == 2 && auto == false {
 				stat.HighCones += 1
+			} else if level == 3 {
+				stat.SuperchargedPieces += 1
 			} else {
 				return db.Stats2023{}, errors.New(fmt.Sprintf("Got unknown ObjectType/ScoreLevel/Auto combination"))
 			}
@@ -541,6 +543,7 @@
 			MiddleCones:        stat.MiddleCones,
 			HighCones:          stat.HighCones,
 			ConesDropped:       stat.ConesDropped,
+			SuperchargedPieces: stat.SuperchargedPieces,
 			AvgCycle:           stat.AvgCycle,
 			DockedAuto:         stat.DockedAuto,
 			EngagedAuto:        stat.EngagedAuto,
@@ -737,7 +740,7 @@
 			Notes:          note.Notes,
 			GoodDriving:    note.GoodDriving,
 			BadDriving:     note.BadDriving,
-			SolidPickup:    note.SolidPickup,
+			SolidPlacing:   note.SolidPlacing,
 			SketchyPlacing: note.SketchyPlacing,
 			GoodDefense:    note.GoodDefense,
 			BadDefense:     note.BadDefense,
diff --git a/scouting/webserver/requests/requests_test.go b/scouting/webserver/requests/requests_test.go
index ac644ea..b6f09fc 100644
--- a/scouting/webserver/requests/requests_test.go
+++ b/scouting/webserver/requests/requests_test.go
@@ -129,7 +129,7 @@
 				LowConesAuto: 1, MiddleConesAuto: 2, HighConesAuto: 1,
 				ConesDroppedAuto: 0, LowCubes: 1, MiddleCubes: 1,
 				HighCubes: 2, CubesDropped: 1, LowCones: 1,
-				MiddleCones: 2, HighCones: 0, ConesDropped: 1,
+				MiddleCones: 2, HighCones: 0, ConesDropped: 1, SuperchargedPieces: 0,
 				AvgCycle: 34, DockedAuto: true, EngagedAuto: true,
 				BalanceAttemptAuto: false, Docked: false, Engaged: false,
 				BalanceAttempt: false, CollectedBy: "alex",
@@ -141,7 +141,7 @@
 				LowConesAuto: 0, MiddleConesAuto: 0, HighConesAuto: 0,
 				ConesDroppedAuto: 1, LowCubes: 0, MiddleCubes: 0,
 				HighCubes: 1, CubesDropped: 0, LowCones: 0,
-				MiddleCones: 2, HighCones: 1, ConesDropped: 1,
+				MiddleCones: 2, HighCones: 1, ConesDropped: 1, SuperchargedPieces: 0,
 				AvgCycle: 53, DockedAuto: true, EngagedAuto: false,
 				BalanceAttemptAuto: false, Docked: false, Engaged: false,
 				BalanceAttempt: true, CollectedBy: "bob",
@@ -214,7 +214,7 @@
 				LowConesAuto: 1, MiddleConesAuto: 2, HighConesAuto: 1,
 				ConesDroppedAuto: 0, LowCubes: 1, MiddleCubes: 1,
 				HighCubes: 2, CubesDropped: 1, LowCones: 1,
-				MiddleCones: 2, HighCones: 0, ConesDropped: 1,
+				MiddleCones: 2, HighCones: 0, ConesDropped: 1, SuperchargedPieces: 0,
 				AvgCycle: 34, DockedAuto: true, EngagedAuto: false,
 				BalanceAttemptAuto: false, Docked: false, Engaged: false,
 				BalanceAttempt: true, CollectedBy: "isaac",
@@ -226,7 +226,7 @@
 				LowConesAuto: 0, MiddleConesAuto: 0, HighConesAuto: 0,
 				ConesDroppedAuto: 1, LowCubes: 0, MiddleCubes: 0,
 				HighCubes: 1, CubesDropped: 0, LowCones: 0,
-				MiddleCones: 2, HighCones: 1, ConesDropped: 1,
+				MiddleCones: 2, HighCones: 1, ConesDropped: 1, SuperchargedPieces: 0,
 				AvgCycle: 53, DockedAuto: false, EngagedAuto: false,
 				BalanceAttemptAuto: true, Docked: false, Engaged: false,
 				BalanceAttempt: true, CollectedBy: "unknown",
@@ -255,7 +255,7 @@
 				LowConesAuto: 1, MiddleConesAuto: 2, HighConesAuto: 1,
 				ConesDroppedAuto: 0, LowCubes: 1, MiddleCubes: 1,
 				HighCubes: 2, CubesDropped: 1, LowCones: 1,
-				MiddleCones: 2, HighCones: 0, ConesDropped: 1,
+				MiddleCones: 2, HighCones: 0, ConesDropped: 1, SuperchargedPieces: 0,
 				AvgCycle: 34, DockedAuto: true, EngagedAuto: false,
 				BalanceAttemptAuto: false, Docked: false, Engaged: false,
 				BalanceAttempt: true, CollectedBy: "isaac",
@@ -267,7 +267,7 @@
 				LowConesAuto: 0, MiddleConesAuto: 0, HighConesAuto: 0,
 				ConesDroppedAuto: 1, LowCubes: 0, MiddleCubes: 0,
 				HighCubes: 1, CubesDropped: 0, LowCones: 0,
-				MiddleCones: 2, HighCones: 1, ConesDropped: 1,
+				MiddleCones: 2, HighCones: 1, ConesDropped: 1, SuperchargedPieces: 0,
 				AvgCycle: 53, DockedAuto: false, EngagedAuto: false,
 				BalanceAttemptAuto: true, Docked: false, Engaged: false,
 				BalanceAttempt: true, CollectedBy: "unknown",
@@ -368,6 +368,27 @@
 			},
 			{
 				ActionTaken: &submit_actions.ActionTypeT{
+					Type: submit_actions.ActionTypePickupObjectAction,
+					Value: &submit_actions.PickupObjectActionT{
+						ObjectType: submit_actions.ObjectTypekCube,
+						Auto:       false,
+					},
+				},
+				Timestamp: 3500,
+			},
+			{
+				ActionTaken: &submit_actions.ActionTypeT{
+					Type: submit_actions.ActionTypePlaceObjectAction,
+					Value: &submit_actions.PlaceObjectActionT{
+						ObjectType: submit_actions.ObjectTypekCube,
+						ScoreLevel: submit_actions.ScoreLevelkSupercharged,
+						Auto:       false,
+					},
+				},
+				Timestamp: 3900,
+			},
+			{
+				ActionTaken: &submit_actions.ActionTypeT{
 					Type: submit_actions.ActionTypeEndMatchAction,
 					Value: &submit_actions.EndMatchActionT{
 						Docked:         true,
@@ -375,7 +396,7 @@
 						BalanceAttempt: true,
 					},
 				},
-				Timestamp: 4000,
+				Timestamp: 4200,
 			},
 		},
 	}).Pack(builder))
@@ -394,8 +415,8 @@
 		LowConesAuto: 0, MiddleConesAuto: 0, HighConesAuto: 0,
 		ConesDroppedAuto: 0, LowCubes: 0, MiddleCubes: 0,
 		HighCubes: 0, CubesDropped: 0, LowCones: 0,
-		MiddleCones: 0, HighCones: 1, ConesDropped: 0,
-		AvgCycle: 1100, DockedAuto: true, EngagedAuto: true,
+		MiddleCones: 0, HighCones: 1, ConesDropped: 0, SuperchargedPieces: 1,
+		AvgCycle: 950, DockedAuto: true, EngagedAuto: true,
 		BalanceAttemptAuto: false, Docked: true, Engaged: false,
 		BalanceAttempt: true, CollectedBy: "katie",
 	}
@@ -418,7 +439,7 @@
 		Notes:          "Notes",
 		GoodDriving:    true,
 		BadDriving:     false,
-		SolidPickup:    true,
+		SolidPlacing:   true,
 		SketchyPlacing: false,
 		GoodDefense:    true,
 		BadDefense:     false,
@@ -436,7 +457,7 @@
 			Notes:          "Notes",
 			GoodDriving:    true,
 			BadDriving:     false,
-			SolidPickup:    true,
+			SolidPlacing:   true,
 			SketchyPlacing: false,
 			GoodDefense:    true,
 			BadDefense:     false,
@@ -456,7 +477,7 @@
 			Notes:          "Notes",
 			GoodDriving:    true,
 			BadDriving:     false,
-			SolidPickup:    true,
+			SolidPlacing:   true,
 			SketchyPlacing: false,
 			GoodDefense:    true,
 			BadDefense:     false,
@@ -684,7 +705,7 @@
 				Notes:          "Notes",
 				GoodDriving:    true,
 				BadDriving:     false,
-				SolidPickup:    true,
+				SolidPlacing:   true,
 				SketchyPlacing: false,
 				GoodDefense:    true,
 				BadDefense:     false,
@@ -695,7 +716,7 @@
 				Notes:          "More Notes",
 				GoodDriving:    false,
 				BadDriving:     false,
-				SolidPickup:    false,
+				SolidPlacing:   false,
 				SketchyPlacing: true,
 				GoodDefense:    false,
 				BadDefense:     true,
@@ -723,7 +744,7 @@
 				Notes:          "Notes",
 				GoodDriving:    true,
 				BadDriving:     false,
-				SolidPickup:    true,
+				SolidPlacing:   true,
 				SketchyPlacing: false,
 				GoodDefense:    true,
 				BadDefense:     false,
@@ -734,7 +755,7 @@
 				Notes:          "More Notes",
 				GoodDriving:    false,
 				BadDriving:     false,
-				SolidPickup:    false,
+				SolidPlacing:   false,
 				SketchyPlacing: true,
 				GoodDefense:    false,
 				BadDefense:     true,
diff --git a/scouting/www/app/app.ng.html b/scouting/www/app/app.ng.html
index 526cd61..b7f1873 100644
--- a/scouting/www/app/app.ng.html
+++ b/scouting/www/app/app.ng.html
@@ -72,18 +72,15 @@
     *ngSwitchCase="'MatchList'"
   ></app-match-list>
   <app-entry
-    (switchTabsEvent)="switchTabTo($event)"
     [teamNumber]="selectedTeamInMatch.teamNumber"
     [matchNumber]="selectedTeamInMatch.matchNumber"
     [setNumber]="selectedTeamInMatch.setNumber"
     [compLevel]="selectedTeamInMatch.compLevel"
+    [skipTeamSelection]="navigatedFromMatchList"
     *ngSwitchCase="'Entry'"
   ></app-entry>
   <frc971-notes *ngSwitchCase="'Notes'"></frc971-notes>
   <app-driver-ranking *ngSwitchCase="'DriverRanking'"></app-driver-ranking>
   <shift-schedule *ngSwitchCase="'ShiftSchedule'"></shift-schedule>
-  <app-view
-    (switchTabsEvent)="switchTabTo($event)"
-    *ngSwitchCase="'View'"
-  ></app-view>
+  <app-view *ngSwitchCase="'View'"></app-view>
 </ng-container>
diff --git a/scouting/www/app/app.ts b/scouting/www/app/app.ts
index f7d2770..c19895a 100644
--- a/scouting/www/app/app.ts
+++ b/scouting/www/app/app.ts
@@ -30,6 +30,9 @@
     setNumber: 1,
     compLevel: 'qm',
   };
+  // Keep track of the match list automatically navigating the user to the
+  // Entry tab.
+  navigatedFromMatchList: boolean = false;
   tab: Tab = 'MatchList';
 
   @ViewChild('block_alerts') block_alerts: ElementRef;
@@ -54,7 +57,8 @@
 
   selectTeamInMatch(teamInMatch: TeamInMatch) {
     this.selectedTeamInMatch = teamInMatch;
-    this.switchTabTo('Entry');
+    this.navigatedFromMatchList = true;
+    this.switchTabTo('Entry', false);
   }
 
   switchTabToGuarded(tab: Tab) {
@@ -69,11 +73,16 @@
       }
     }
     if (shouldSwitch) {
-      this.switchTabTo(tab);
+      this.switchTabTo(tab, true);
     }
   }
 
-  private switchTabTo(tab: Tab) {
+  private switchTabTo(tab: Tab, wasGuarded: boolean) {
+    if (wasGuarded) {
+      // When the user navigated between tabs manually, we want to reset some
+      // state.
+      this.navigatedFromMatchList = false;
+    }
     this.tab = tab;
   }
 }
diff --git a/scouting/www/entry/entry.component.ts b/scouting/www/entry/entry.component.ts
index 71c8de2..b56948a 100644
--- a/scouting/www/entry/entry.component.ts
+++ b/scouting/www/entry/entry.component.ts
@@ -97,7 +97,7 @@
   templateUrl: './entry.ng.html',
   styleUrls: ['../app/common.css', './entry.component.css'],
 })
-export class EntryComponent {
+export class EntryComponent implements OnInit {
   // Re-export the type here so that we can use it in the `[value]` attribute
   // of radio buttons.
   readonly COMP_LEVELS = COMP_LEVELS;
@@ -106,11 +106,11 @@
   readonly ScoreLevel = ScoreLevel;
 
   section: Section = 'Team Selection';
-  @Output() switchTabsEvent = new EventEmitter<string>();
   @Input() matchNumber: number = 1;
   @Input() teamNumber: number = 1;
   @Input() setNumber: number = 1;
   @Input() compLevel: CompLevel = 'qm';
+  @Input() skipTeamSelection = false;
 
   actionList: ActionT[] = [];
   errorMessage: string = '';
@@ -119,6 +119,12 @@
 
   matchStartTimestamp: number = 0;
 
+  ngOnInit() {
+    // When the user navigated from the match list, we can skip the team
+    // selection. I.e. we trust that the user clicked the correct button.
+    this.section = this.skipTeamSelection ? 'Init' : 'Team Selection';
+  }
+
   addAction(action: ActionT): void {
     if (action.type == 'startMatchAction') {
       // Unix nanosecond timestamp.
diff --git a/scouting/www/entry/entry.ng.html b/scouting/www/entry/entry.ng.html
index e304f2a..56056fc 100644
--- a/scouting/www/entry/entry.ng.html
+++ b/scouting/www/entry/entry.ng.html
@@ -1,5 +1,8 @@
 <div class="header" #header>
-  <h2>{{section}}</h2>
+  <h2>
+    <span *ngIf="section != 'Team Selection'">{{teamNumber}}</span>
+    {{section}}
+  </h2>
 </div>
 <ng-container [ngSwitch]="section">
   <div
@@ -89,7 +92,13 @@
     <h6 class="text-muted">
       Last Action: {{actionList[actionList.length - 1].type}}
     </h6>
-    <div class="d-grid gap-5">
+    <!-- 
+      Decrease distance between buttons during auto to make space for auto balancing
+      selection and keep all buttons visible without scrolling on most devices.
+    -->
+    <div
+      [ngClass]="{'d-grid': true, 'gap-3': autoPhase === true, 'gap-5': autoPhase === false}"
+    >
       <button class="btn btn-secondary" (click)="undoLastAction()">UNDO</button>
       <button
         class="btn btn-danger"
@@ -133,7 +142,7 @@
       </div>
       <button
         *ngIf="autoPhase"
-        class="btn btn-info"
+        class="btn btn-dark"
         (click)="autoPhase = false; addAction({type: 'endAutoPhase'});"
       >
         Start Teleop
@@ -151,7 +160,13 @@
     <h6 class="text-muted">
       Last Action: {{actionList[actionList.length - 1].type}}
     </h6>
-    <div class="d-grid gap-5">
+    <!-- 
+      Decrease distance between buttons during auto to make space for auto balancing
+      selection and keep all buttons visible without scrolling on most devices.
+    -->
+    <div
+      [ngClass]="{'d-grid': true, 'gap-3': autoPhase === true, 'gap-5': autoPhase === false}"
+    >
       <button class="btn btn-secondary" (click)="undoLastAction()">UNDO</button>
       <button
         class="btn btn-danger"
@@ -177,6 +192,15 @@
       >
         LOW
       </button>
+      <!-- Impossible to place supercharged pieces in auto. -->
+      <div *ngIf="autoPhase == false" class="d-grid gap-2">
+        <button
+          class="btn btn-dark"
+          (click)="changeSectionTo('Pickup'); addAction({type: 'placeObjectAction', scoreLevel: ScoreLevel.kSupercharged});"
+        >
+          SUPERCHARGED
+        </button>
+      </div>
       <!-- 'Balancing' during auto. -->
       <div *ngIf="autoPhase" class="d-grid gap-2">
         <label>
@@ -201,7 +225,7 @@
       </div>
       <button
         *ngIf="autoPhase"
-        class="btn btn-info"
+        class="btn btn-dark"
         (click)="autoPhase = false; addAction({type: 'endAutoPhase'});"
       >
         Start Teleop
diff --git a/scouting/www/notes/notes.component.ts b/scouting/www/notes/notes.component.ts
index 5d1c4a2..2786717 100644
--- a/scouting/www/notes/notes.component.ts
+++ b/scouting/www/notes/notes.component.ts
@@ -40,7 +40,7 @@
 interface Keywords {
   goodDriving: boolean;
   badDriving: boolean;
-  solidPickup: boolean;
+  solidPlacing: boolean;
   sketchyPlacing: boolean;
   goodDefense: boolean;
   badDefense: boolean;
@@ -56,7 +56,7 @@
 const KEYWORD_CHECKBOX_LABELS = {
   goodDriving: 'Good Driving',
   badDriving: 'Bad Driving',
-  solidPickup: 'Solid Pickup',
+  solidPlacing: 'Solid Placing',
   sketchyPlacing: 'Sketchy Placing',
   goodDefense: 'Good Defense',
   badDefense: 'Bad Defense',
@@ -115,7 +115,7 @@
       keywordsData: {
         goodDriving: false,
         badDriving: false,
-        solidPickup: false,
+        solidPlacing: false,
         sketchyPlacing: false,
         goodDefense: false,
         badDefense: false,
@@ -152,7 +152,7 @@
           dataFb,
           this.newData[i].keywordsData.goodDriving,
           this.newData[i].keywordsData.badDriving,
-          this.newData[i].keywordsData.solidPickup,
+          this.newData[i].keywordsData.solidPlacing,
           this.newData[i].keywordsData.sketchyPlacing,
           this.newData[i].keywordsData.goodDefense,
           this.newData[i].keywordsData.badDefense,
diff --git a/scouting/www/view/view.component.ts b/scouting/www/view/view.component.ts
index f7d6ce8..561ae08 100644
--- a/scouting/www/view/view.component.ts
+++ b/scouting/www/view/view.component.ts
@@ -171,8 +171,8 @@
     if (entry.badDriving()) {
       parsedKeywords += 'Bad Driving ';
     }
-    if (entry.solidPickup()) {
-      parsedKeywords += 'Solid Pickup ';
+    if (entry.solidPlacing()) {
+      parsedKeywords += 'Solid Placing ';
     }
     if (entry.sketchyPlacing()) {
       parsedKeywords += 'Sketchy Placing ';
diff --git a/y2023/autonomous/autonomous_actor.cc b/y2023/autonomous/autonomous_actor.cc
index 58c31d0..cd4d4f1 100644
--- a/y2023/autonomous/autonomous_actor.cc
+++ b/y2023/autonomous/autonomous_actor.cc
@@ -475,7 +475,13 @@
   splines[1].Start();
 
   std::this_thread::sleep_for(chrono::milliseconds(300));
+  Neutral();
+
+  if (!splines[1].WaitForSplineDistanceTraveled(3.2)) return;
   HighCubeScore();
+  AOS_LOG(
+      INFO, "Extending arm %lf s\n",
+      aos::time::DurationInSeconds(aos::monotonic_clock::now() - start_time));
 
   if (!splines[1].WaitForSplineDistanceRemaining(0.08)) return;
   AOS_LOG(
@@ -521,10 +527,17 @@
   if (!splines[3].WaitForPlan()) return;
   splines[3].Start();
 
+  std::this_thread::sleep_for(chrono::milliseconds(400));
+  Neutral();
+
   AOS_LOG(
       INFO, "Driving back %lf s\n",
       aos::time::DurationInSeconds(aos::monotonic_clock::now() - start_time));
 
+  if (!splines[3].WaitForSplineDistanceTraveled(3.5)) return;
+  AOS_LOG(
+      INFO, "Extending arm %lf s\n",
+      aos::time::DurationInSeconds(aos::monotonic_clock::now() - start_time));
   MidCubeScore();
 
   if (!splines[3].WaitForSplineDistanceRemaining(0.07)) return;
diff --git a/y2023/autonomous/splines/splinecable.0.json b/y2023/autonomous/splines/splinecable.0.json
index 3fdcfbe..3a8a1c8 100644
--- a/y2023/autonomous/splines/splinecable.0.json
+++ b/y2023/autonomous/splines/splinecable.0.json
@@ -1 +1 @@
-{"spline_count": 1, "spline_x": [-6.468141183035714, -6.142335156250001, -5.834629464285714, -2.6851712053571433, -2.2145624999999995, -1.7620541294642855], "spline_y": [-3.493364620535714, -3.493364620535714, -3.4752642857142853, -3.0951572544642856, -3.0770569196428568, -3.0770569196428568], "constraints": [{"constraint_type": "LONGITUDINAL_ACCELERATION", "value": 3.0}, {"constraint_type": "LATERAL_ACCELERATION", "value": 2.0}, {"constraint_type": "VOLTAGE", "value": 10.0}]}
\ No newline at end of file
+{"spline_count": 2, "spline_x": [6.468141183035714, 6.156227508178545, 5.627928372772197, 5.618126908868124, 4.926607785226736, 4.384253622459038, 3.8418994596913407, 3.448710257797334, 2.6702357572801336, 1.9707990587626902, 1.441511105732058], "spline_y": [-3.493364620535714, -3.4921188608837532, -3.431437306632174, -3.364982889102878, -3.381212722774612, -3.3815433034902798, -3.3818738842059477, -3.3663052119655514, -3.293883383936489, -3.2050645805145246, -3.125670625960137], "constraints": [{"constraint_type": "LONGITUDINAL_ACCELERATION", "value": 3.5}, {"constraint_type": "LATERAL_ACCELERATION", "value": 2.0}, {"constraint_type": "VOLTAGE", "value": 12.0}, {"constraint_type": "VELOCITY", "value": 1.5, "start_distance": 1.6, "end_distance": 2.75}]}
\ No newline at end of file
diff --git a/y2023/autonomous/splines/splinecable.1.json b/y2023/autonomous/splines/splinecable.1.json
index b9b4fdd..bf6838d 100644
--- a/y2023/autonomous/splines/splinecable.1.json
+++ b/y2023/autonomous/splines/splinecable.1.json
@@ -1 +1 @@
-{"spline_count": 1, "spline_x": [-1.7620541294642855, -2.2145624999999995, -2.6851712053571433, -5.8165291294642865, -6.124234821428573, -6.450040848214286], "spline_y": [-3.0770569196428568, -3.0770569196428568, -3.0951572544642856, -2.9141539062499993, -2.932254241071428, -2.932254241071428], "constraints": [{"constraint_type": "LONGITUDINAL_ACCELERATION", "value": 3.0}, {"constraint_type": "LATERAL_ACCELERATION", "value": 2.0}, {"constraint_type": "VOLTAGE", "value": 10.0}]}
\ No newline at end of file
+{"spline_count": 2, "spline_x": [1.441511105732058, 2.158787794162399, 2.644327791138681, 3.0017835551344163, 3.810734918966552, 4.3709130304548385, 4.931091141943125, 5.242496001087563, 5.544694674297983, 5.965729060465805, 6.444059695844368], "spline_y": [-3.125670625960137, -3.233263161324085, -3.2028042659537914, -3.139500351375334, -3.125566969972238, -3.126688937308022, -3.127810904643806, -3.143988220718469, -2.9893341041515376, -2.943814113563144, -2.9458417329239843], "constraints": [{"constraint_type": "LONGITUDINAL_ACCELERATION", "value": 3.0}, {"constraint_type": "LATERAL_ACCELERATION", "value": 2.0}, {"constraint_type": "VOLTAGE", "value": 12.0}, {"constraint_type": "VELOCITY", "value": 1.5, "start_distance": 1.9, "end_distance": 10.0}]}
\ No newline at end of file
diff --git a/y2023/autonomous/splines/splinecable.2.json b/y2023/autonomous/splines/splinecable.2.json
index cef0375..1caf288 100644
--- a/y2023/autonomous/splines/splinecable.2.json
+++ b/y2023/autonomous/splines/splinecable.2.json
@@ -1 +1 @@
-{"spline_count": 1, "spline_x": [-6.450040848214286, -6.124234821428573, -5.8165291294642865, -2.9023752232142854, -2.1059604910714285, -1.6353517857142856], "spline_y": [-2.932254241071428, -2.932254241071428, -2.9141539062499993, -3.2218595982142855, -2.6607492187499995, -2.244441517857142], "constraints": [{"constraint_type": "LONGITUDINAL_ACCELERATION", "value": 3.0}, {"constraint_type": "LATERAL_ACCELERATION", "value": 2.0}, {"constraint_type": "VOLTAGE", "value": 10.0}]}
\ No newline at end of file
+{"spline_count": 2, "spline_x": [6.444059695844368, 5.977202894617023, 5.571532532121525, 5.64631894401731, 5.012590627058541, 4.364949107965309, 3.7173075888720764, 3.0557528676443795, 2.3031869947159427, 1.8583292479008144, 1.389061484581065], "spline_y": [-2.9458417329239843, -2.943862750565559, -2.96219050239874, -3.128181973537357, -3.0803143268366417, -3.079651639654858, -3.0789889524730745, -3.1255312248102225, -2.6948472310294784, -2.234117571800885, -1.9039073778940578], "constraints": [{"constraint_type": "LONGITUDINAL_ACCELERATION", "value": 3.0}, {"constraint_type": "LATERAL_ACCELERATION", "value": 2.0}, {"constraint_type": "VOLTAGE", "value": 12.0}, {"constraint_type": "VELOCITY", "value": 1.5, "start_distance": 1.6, "end_distance": 2.55}]}
\ No newline at end of file
diff --git a/y2023/autonomous/splines/splinecable.3.json b/y2023/autonomous/splines/splinecable.3.json
index 60821fe..c49c1e8 100644
--- a/y2023/autonomous/splines/splinecable.3.json
+++ b/y2023/autonomous/splines/splinecable.3.json
@@ -1 +1 @@
-{"spline_count": 1, "spline_x": [-1.6353517857142856, -2.1059604910714285, -2.9023752232142854, -5.8165291294642865, -6.124234821428573, -6.450040848214286], "spline_y": [-2.244441517857142, -2.6607492187499995, -3.2218595982142855, -2.9141539062499993, -2.932254241071428, -2.932254241071428], "constraints": [{"constraint_type": "LONGITUDINAL_ACCELERATION", "value": 3.0}, {"constraint_type": "LATERAL_ACCELERATION", "value": 2.0}, {"constraint_type": "VOLTAGE", "value": 10.0}]}
\ No newline at end of file
+{"spline_count": 2, "spline_x": [1.389061484581065, 2.1138941612201396, 2.2865982233539297, 3.1216159825232483, 3.763030179317507, 4.301019167021117, 4.839008154724727, 5.2735719333376885, 5.59798451182913, 5.734162859875811, 6.443454541551164], "spline_y": [-1.9039073778940578, -2.4139512321954397, -2.680729209318427, -3.1198721507158496, -3.0738965122418085, -3.0726645678919953, -3.071432623542182, -3.1149443733165967, -2.966418143397661, -2.9578217694545557, -2.957336170980663], "constraints": [{"constraint_type": "LONGITUDINAL_ACCELERATION", "value": 3.0}, {"constraint_type": "LATERAL_ACCELERATION", "value": 2.0}, {"constraint_type": "VOLTAGE", "value": 12.0}, {"constraint_type": "VELOCITY", "value": 1.5, "start_distance": 2.5, "end_distance": 3.6}]}
\ No newline at end of file
diff --git a/y2023/constants.cc b/y2023/constants.cc
index b64ac3d..3d3dfc2 100644
--- a/y2023/constants.cc
+++ b/y2023/constants.cc
@@ -95,7 +95,8 @@
           0.0220711555235029 - 0.0162945074111813 + 0.00630344935527365 -
           0.0164398318919943 - 0.145833494945215 + 0.234878799868491 +
           0.125924230298394 + 0.147136306208754 - 0.69167546169753 -
-          0.308761538844425 + 0.610386472488493 + 0.08384162885249 + 0.0262274735196811;
+          0.308761538844425 + 0.610386472488493 + 0.08384162885249 +
+          0.0262274735196811 + 0.5153995156153;
 
       arm_distal->zeroing.one_revolution_distance =
           M_PI * 2.0 * constants::Values::kDistalEncoderRatio();
diff --git a/y2023/control_loops/python/graph_edit.py b/y2023/control_loops/python/graph_edit.py
index bbc7a1b..99b09af 100644
--- a/y2023/control_loops/python/graph_edit.py
+++ b/y2023/control_loops/python/graph_edit.py
@@ -244,6 +244,7 @@
         self.set_size_request(ARM_AREA_WIDTH, ARM_AREA_HEIGHT)
         self.center = (0, 0)
         self.shape = (ARM_AREA_WIDTH, ARM_AREA_HEIGHT)
+        self.window_shape = (ARM_AREA_WIDTH, ARM_AREA_HEIGHT)
         self.theta_version = False
 
         self.init_extents()
@@ -326,9 +327,6 @@
     def on_draw(self, widget, event):
         cr = self.get_window().cairo_create()
 
-        self.window_shape = (self.get_window().get_geometry().width,
-                             self.get_window().get_geometry().height)
-
         cr.save()
         cr.set_font_size(20)
         cr.translate(self.window_shape[0] / 2, self.window_shape[1] / 2)
diff --git a/y2023/control_loops/python/graph_paths.py b/y2023/control_loops/python/graph_paths.py
index 93134c7..1efcd93 100644
--- a/y2023/control_loops/python/graph_paths.py
+++ b/y2023/control_loops/python/graph_paths.py
@@ -329,7 +329,7 @@
     ))
 
 points['HPPickupBackConeUp'] = to_theta_with_circular_index_and_roll(
-    -1.1200539, 1.330, np.pi / 2.0, circular_index=0)
+    -1.1200539, 1.325, np.pi / 2.0, circular_index=0)
 
 named_segments.append(
     ThetaSplineSegment(
diff --git a/y2023/control_loops/superstructure/arm/trajectory.cc b/y2023/control_loops/superstructure/arm/trajectory.cc
index d216467..88e3d1b 100644
--- a/y2023/control_loops/superstructure/arm/trajectory.cc
+++ b/y2023/control_loops/superstructure/arm/trajectory.cc
@@ -974,8 +974,8 @@
     }
 
     // Pull us back to the previous point until we aren't saturated anymore.
-    double saturation_goal_velocity;
-    double saturation_goal_acceleration;
+    double saturation_goal_velocity = 0.0;
+    double saturation_goal_acceleration = 0.0;
     while (step_size > 0.01) {
       USaturationSearch(goal_(0), last_goal_(0), goal_(1), last_goal_(1),
                         saturation_fraction_along_path_, arm_K, X, *trajectory_,
diff --git a/y2023/control_loops/superstructure/arm/trajectory_plot.cc b/y2023/control_loops/superstructure/arm/trajectory_plot.cc
index c56ced9..24bc8dd 100644
--- a/y2023/control_loops/superstructure/arm/trajectory_plot.cc
+++ b/y2023/control_loops/superstructure/arm/trajectory_plot.cc
@@ -15,10 +15,10 @@
 DEFINE_bool(plot, true, "If true, plot");
 DEFINE_bool(plot_thetas, true, "If true, plot the angles");
 
-DEFINE_double(alpha0_max, 20.0, "Max acceleration on joint 0.");
-DEFINE_double(alpha1_max, 30.0, "Max acceleration on joint 1.");
-DEFINE_double(alpha2_max, 60.0, "Max acceleration on joint 2.");
-DEFINE_double(vmax_plan, 10.0, "Max voltage to plan.");
+DEFINE_double(alpha0_max, 15.0, "Max acceleration on joint 0.");
+DEFINE_double(alpha1_max, 10.0, "Max acceleration on joint 1.");
+DEFINE_double(alpha2_max, 90.0, "Max acceleration on joint 2.");
+DEFINE_double(vmax_plan, 9.5, "Max voltage to plan.");
 DEFINE_double(vmax_battery, 12.0, "Max battery voltage.");
 DEFINE_double(time, 2.0, "Simulation time.");
 
@@ -36,12 +36,20 @@
 
   Eigen::Matrix<double, 2, 4> spline_params;
 
-  spline_params << 0.30426338, 0.42813912, 0.64902386, 0.55127045, -1.73611082,
-      -1.64478944, -1.04763868, -0.82624244;
+  spline_params << 3.227752818257, 3.032002509469, 3.131082488348,
+      3.141592653590, 0.914286433787, 0.436747899287, 0.235917057271,
+      0.000000000000;
   LOG(INFO) << "Spline " << spline_params;
   NSpline<4, 2> spline(spline_params);
   CosSpline cos_spline(spline,
-                       {{0.0, 0.1}, {0.3, 0.1}, {0.7, 0.2}, {1.0, 0.2}});
+                       {
+                           CosSpline::AlphaTheta{.alpha = 0.000000000000,
+                                                 .theta = 1.570796326795},
+                           CosSpline::AlphaTheta{.alpha = 0.050000000000,
+                                                 .theta = 1.570796326795},
+                           CosSpline::AlphaTheta{.alpha = 1.000000000000,
+                                                 .theta = 0.000000000000},
+                       });
   Path distance_spline(cos_spline, 100);
 
   Trajectory trajectory(&dynamics, &hybrid_roll.plant(),
@@ -300,10 +308,14 @@
         (::Eigen::Matrix<double, 2, 1>() << arm_X(0), arm_X(2)).finished(),
         sim_dt);
     roll.Correct((::Eigen::Matrix<double, 1, 1>() << roll_X(0)).finished());
+    bool disabled = false;
+    if (t > 0.40 && t < 0.46) {
+      disabled = true;
+    }
     follower.Update(
         (Eigen::Matrix<double, 9, 1>() << arm_ekf.X_hat(), roll.X_hat())
             .finished(),
-        false, sim_dt, FLAGS_vmax_plan, FLAGS_vmax_battery);
+        disabled, sim_dt, FLAGS_vmax_plan, FLAGS_vmax_battery);
 
     const ::Eigen::Matrix<double, 3, 1> theta_t =
         trajectory.ThetaT(follower.goal()(0));
diff --git a/y2023/vision/target_mapping.cc b/y2023/vision/target_mapping.cc
index ba5f5d9..daba90c 100644
--- a/y2023/vision/target_mapping.cc
+++ b/y2023/vision/target_mapping.cc
@@ -5,7 +5,6 @@
 #include "aos/util/mcap_logger.h"
 #include "frc971/control_loops/pose.h"
 #include "frc971/vision/calibration_generated.h"
-#include "frc971/vision/charuco_lib.h"
 #include "frc971/vision/target_mapper.h"
 #include "frc971/vision/visualize_robot.h"
 #include "opencv2/aruco.hpp"
@@ -49,7 +48,10 @@
 DEFINE_bool(solve, true, "Whether to solve for the field's target map.");
 DEFINE_string(dump_constraints_to, "/tmp/constraints.txt",
               "Write the target constraints to this path");
-DECLARE_uint64(frozen_target_id);
+DECLARE_int32(frozen_target_id);
+DECLARE_int32(min_target_id);
+DECLARE_int32(max_target_id);
+DECLARE_bool(visualize_solver);
 
 namespace y2023 {
 namespace vision {
@@ -61,9 +63,6 @@
 using frc971::vision::VisualizeRobot;
 namespace calibration = frc971::vision::calibration;
 
-constexpr TargetMapper::TargetId kMinTargetId = 1;
-constexpr TargetMapper::TargetId kMaxTargetId = 8;
-
 // Class to handle reading target poses from a replayed log,
 // displaying various debug info, and passing the poses to
 // frc971::vision::TargetMapper for field mapping.
@@ -203,7 +202,7 @@
         });
   }
 
-  if (FLAGS_visualize) {
+  if (FLAGS_visualize_solver) {
     vis_robot_.ClearImage();
     const double kFocalLength = 500.0;
     vis_robot_.SetDefaultViewpoint(kImageWidth, kFocalLength);
@@ -229,22 +228,23 @@
 
   for (const auto *target_pose_fbs : *map.target_poses()) {
     // Skip detections with invalid ids
-    if (target_pose_fbs->id() < kMinTargetId ||
-        target_pose_fbs->id() > kMaxTargetId) {
-      LOG(WARNING) << "Skipping tag with invalid id of "
-                   << target_pose_fbs->id();
+    if (static_cast<TargetMapper::TargetId>(target_pose_fbs->id()) <
+            FLAGS_min_target_id ||
+        static_cast<TargetMapper::TargetId>(target_pose_fbs->id()) >
+            FLAGS_max_target_id) {
+      VLOG(1) << "Skipping tag with invalid id of " << target_pose_fbs->id();
       continue;
     }
 
     // Skip detections with high pose errors
     if (target_pose_fbs->pose_error() > FLAGS_max_pose_error) {
-      VLOG(1) << " Skipping tag " << target_pose_fbs->id()
+      VLOG(1) << "Skipping tag " << target_pose_fbs->id()
               << " due to pose error of " << target_pose_fbs->pose_error();
       continue;
     }
     // Skip detections with high pose error ratios
     if (target_pose_fbs->pose_error_ratio() > FLAGS_max_pose_error_ratio) {
-      VLOG(1) << " Skipping tag " << target_pose_fbs->id()
+      VLOG(1) << "Skipping tag " << target_pose_fbs->id()
               << " due to pose error ratio of "
               << target_pose_fbs->pose_error_ratio();
       continue;
@@ -274,7 +274,7 @@
             .distortion_factor = distortion_factor,
             .id = static_cast<TargetMapper::TargetId>(target_pose.id)});
 
-    if (FLAGS_visualize) {
+    if (FLAGS_visualize_solver) {
       // If we've already drawn in the current image,
       // display it before clearing and adding the new poses
       if (drawn_nodes_.count(node_name) != 0) {
@@ -419,10 +419,13 @@
                 .pose),
         .distance_from_camera = kSeedDistanceFromCamera,
         .distortion_factor = kSeedDistortionFactor,
-        .id = kMinTargetId};
+        .id = FLAGS_frozen_target_id};
 
-    for (TargetMapper::TargetId id = kMinTargetId; id <= kMaxTargetId; id++) {
-      if (id == static_cast<TargetMapper::TargetId>(FLAGS_frozen_target_id)) {
+    constexpr TargetMapper::TargetId kAbsMinTargetId = 1;
+    constexpr TargetMapper::TargetId kAbsMaxTargetId = 8;
+    for (TargetMapper::TargetId id = kAbsMinTargetId; id <= kAbsMaxTargetId;
+         id++) {
+      if (id == FLAGS_frozen_target_id) {
         continue;
       }