Merge "Update extrinsics calib to properly traverse nodes"
diff --git a/.bazelrc b/.bazelrc
index 6359ba3..a69ccad 100644
--- a/.bazelrc
+++ b/.bazelrc
@@ -96,6 +96,7 @@
 
 build --spawn_strategy=sandboxed
 build --sandbox_default_allow_network=false
+build --incompatible_exclusive_test_sandboxed
 
 build --strategy=TsProject=sandboxed
 build --strategy=CopyFile=standalone
diff --git a/scouting/webserver/requests/requests.go b/scouting/webserver/requests/requests.go
index a9919af..76d632d 100644
--- a/scouting/webserver/requests/requests.go
+++ b/scouting/webserver/requests/requests.go
@@ -1123,7 +1123,7 @@
 		return
 	}
 
-	log.Println("Got actions for match", request.MatchNumber(), "team", request.TeamNumber(), "from", username)
+	log.Println("Got actions for match", request.MatchNumber(), "team", string(request.TeamNumber()), "from", username)
 
 	for i := 0; i < request.ActionsListLength(); i++ {
 
@@ -1194,7 +1194,7 @@
 		return
 	}
 
-	log.Println("Got actions for match", request.MatchNumber(), "team", request.TeamNumber(), "from", username)
+	log.Println("Got actions for match", request.MatchNumber(), "team", string(request.TeamNumber()), "from", username)
 
 	for i := 0; i < request.ActionsListLength(); i++ {
 
diff --git a/scouting/www/app/app.ts b/scouting/www/app/app.ts
index 011c94f..597e5c5 100644
--- a/scouting/www/app/app.ts
+++ b/scouting/www/app/app.ts
@@ -10,7 +10,7 @@
   | 'Pit';
 
 // Ignore the guard for tabs that don't require the user to enter any data.
-const unguardedTabs: Tab[] = ['MatchList'];
+const unguardedTabs: Tab[] = ['MatchList', 'View'];
 
 type TeamInMatch = {
   teamNumber: string;
diff --git a/y2024/control_loops/superstructure/superstructure.cc b/y2024/control_loops/superstructure/superstructure.cc
index ad91614..6b262c1 100644
--- a/y2024/control_loops/superstructure/superstructure.cc
+++ b/y2024/control_loops/superstructure/superstructure.cc
@@ -22,6 +22,11 @@
 constexpr std::chrono::milliseconds kExtraIntakingTime =
     std::chrono::milliseconds(500);
 
+// Exit catapult loading state after this much time if we never
+// trigger any beambreaks.
+constexpr std::chrono::milliseconds kMaxCatapultLoadingTime =
+    std::chrono::milliseconds(3000);
+
 namespace y2024::control_loops::superstructure {
 
 using ::aos::monotonic_clock;
@@ -262,6 +267,7 @@
           extend_goal_location = ExtendStatus::CATAPULT;
           if (extend_ready_for_catapult_transfer && turret_ready_for_load &&
               altitude_ready_for_load) {
+            loading_catapult_start_time_ = timestamp;
             state_ = SuperstructureState::LOADING_CATAPULT;
           }
           break;
@@ -292,6 +298,13 @@
       extend_goal_location = ExtendStatus::CATAPULT;
       extend_roller_status = ExtendRollerStatus::TRANSFERING_TO_CATAPULT;
 
+      // If we lost the game piece, reset state to idle.
+      if (((timestamp - loading_catapult_start_time_) >
+           kMaxCatapultLoadingTime) &&
+          !position->catapult_beambreak() && !position->extend_beambreak()) {
+        state_ = SuperstructureState::IDLE;
+      }
+
       // Switch to READY state when the catapult beambreak is triggered
       if (position->catapult_beambreak()) {
         state_ = SuperstructureState::READY;
diff --git a/y2024/control_loops/superstructure/superstructure.h b/y2024/control_loops/superstructure/superstructure.h
index e1856a3..1c2d119 100644
--- a/y2024/control_loops/superstructure/superstructure.h
+++ b/y2024/control_loops/superstructure/superstructure.h
@@ -73,10 +73,10 @@
 
   NoteGoal requested_note_goal_ = NoteGoal::NONE;
 
-  aos::monotonic_clock::time_point transfer_start_time_ =
+  aos::monotonic_clock::time_point intake_end_time_ =
       aos::monotonic_clock::time_point::min();
 
-  aos::monotonic_clock::time_point intake_end_time_ =
+  aos::monotonic_clock::time_point loading_catapult_start_time_ =
       aos::monotonic_clock::time_point::min();
 
   AbsoluteEncoderSubsystem intake_pivot_;