diff --git a/static/css/scoring_panel.css b/static/css/scoring_panel.css
index e0240f91..fc71bb58 100644
--- a/static/css/scoring_panel.css
+++ b/static/css/scoring_panel.css
@@ -16,6 +16,7 @@ body {
 .container {
   padding-top: 1vw;
   width: 100%;
+  max-width: none;
   height: 100%;
   display: flex;
   flex-direction: column;
@@ -38,6 +39,7 @@ body {
   background-color: #223;
 }
 #matchName {
+  margin-bottom: 0.5vw;
   font-size: 2vw;
 }
 .scoring-section {
@@ -73,98 +75,70 @@ body {
 .boolean[data-value="true"] {
   background-color: #263;
 }
-.endgame-status[data-value="0"] {
-  background-color: #333;
-}
-.endgame-status[data-value="1"] {
-  background-color: #652;
-}
-.endgame-status[data-value="2"] {
-  background-color: #263;
-}
-.charge-station-level[data-value="false"] {
-  background-color: #622;
+#postMatchMessage {
+  height: 5vw;
+  display: none;
+  align-items: center;
+  font-size: 1.5vw;
+  color: #c90;
 }
-.charge-station-level[data-value="true"] {
-  background-color: #263;
+#commitMatchScore {
+  height: 5vw;
+  display: none;
+  align-items: center;
 }
-#grid {
-  width: 100%;
-  margin-top: 1.5vw;
-  margin-bottom: 1.5vw;
-  display: flex;
-  flex-direction: column;
+#commitMatchScore>button {
+  font-size: 1vw;
 }
-.grid-row {
-  width: 100%;
+#stage {
+  position: relative;
   display: flex;
   justify-content: center;
-}
-.grid-node {
-  width: 10vw;
-  display: flex;
-  flex-direction: column;
   align-items: center;
-  border: 1px solid #ccc;
-  padding: 0.5vw;
-  margin-top: -1px;
-  margin-left: -1px;
 }
-.grid-node[data-node="2"], .grid-node[data-node="5"] {
-  margin-right: 1vw;
+#stageGraphic {
+  margin-top: 9vw;
+  height: 30vw;
+  color: #999;
 }
-.grid-node-auto {
-  width: 4vw;
-  height: 2vw;
-  margin-bottom: 0.5vw;
+.stage-side {
+  position: absolute;
+  top: 0;
+  left: 0;
   display: flex;
-  justify-content: center;
-  align-items: center;
-  border-radius: 0.5vw;
-  background-color: #333;
-  color: #ccc;
-}
-.grid-node-auto[data-value="true"] {
-  background-color: #263;
+  width: 16vw;
+  height: 8vw;
+  border: 0.5px solid #111;
 }
-.grid-node-states {
+.stage-side-col {
   display: flex;
-  flex-wrap: wrap;
-  justify-content: space-around;
+  flex-direction: column;
+  flex-grow: 1;
+  height: 100%;
 }
-.grid-node-button {
-  width: 4vw;
-  height: 4vw;
+.stage-side-col div {
   display: flex;
   justify-content: center;
   align-items: center;
-  opacity: 0.4;
-}
-.grid-node-button[data-value="true"] {
-  border: 2px solid #aaa;
-  border-radius: 0.5vw;
-  opacity: 1;
-}
-.grid-node-button img {
-  width: 90%;
+  flex-grow: 1;
+  font-size: 1.5vw;
+  background-color: #333;
+  border: 0.5px solid #111;
 }
-#chargeStation {
-  width: 70vw;
-  justify-content: space-between;
-  align-items: center;
+#stageLeft {
+  top: 20vw;
+  left: -8.5vw;
 }
-#postMatchMessage {
-  height: 5vw;
-  display: none;
-  align-items: center;
-  font-size: 1.5vw;
-  color: #c90;
+#centerStage {
+  top: 0.5vw;
+  left: 9.3vw;
 }
-#commitMatchScore {
-  height: 5vw;
-  display: none;
-  align-items: center;
+#stageRight {
+  top: 20vw;
+  left: 25.9vw;
 }
-#commitMatchScore>button {
-  font-size: 1vw;
+#park {
+  top: 13vw;
+  left: 12.2vw;
+  width: 9vw;
 }
diff --git a/static/js/scoring_panel.js b/static/js/scoring_panel.js
index fc5f6065..9aa62992 100644
--- a/static/js/scoring_panel.js
+++ b/static/js/scoring_panel.js
@@ -10,13 +10,13 @@ let alliance;
 const handleMatchLoad = function(data) {
   $("#matchName").text(data.Match.LongName);
   if (alliance === "red") {
-    $("#team1").text(data.Match.Red1);
-    $("#team2").text(data.Match.Red2);
-    $("#team3").text(data.Match.Red3);
+    $(".team-1").text(data.Match.Red1);
+    $(".team-2").text(data.Match.Red2);
+    $(".team-3").text(data.Match.Red3);
   } else {
-    $("#team1").text(data.Match.Blue1);
-    $("#team2").text(data.Match.Blue2);
-    $("#team3").text(data.Match.Blue3);
+    $(".team-1").text(data.Match.Blue1);
+    $(".team-2").text(data.Match.Blue2);
+    $(".team-3").text(data.Match.Blue3);
   }
 };
 
@@ -50,33 +50,20 @@ const handleRealtimeScore = function(data) {
 
   for (let i = 0; i < 3; i++) {
     const i1 = i + 1;
-    $(`#mobilityStatus${i1}>.value`).text(score.MobilityStatuses[i] ? "Yes" : "No");
-    $("#mobilityStatus" + i1).attr("data-value", score.MobilityStatuses[i]);
-    $("#autoDockStatus" + i1 + ">.value").text(score.AutoDockStatuses[i] ? "Yes" : "No");
-    $("#autoDockStatus" + i1).attr("data-value", score.AutoDockStatuses[i]);
-    $("#endgameStatus" + i1 + ">.value").text(getEndgameStatusText(score.EndgameStatuses[i]));
-    $("#endgameStatus" + i1).attr("data-value", score.EndgameStatuses[i]);
-  }
-
-  $("#autoChargeStationLevel>.value").text(score.AutoChargeStationLevel ? "Level" : "Not Level");
-  $("#autoChargeStationLevel").attr("data-value", score.AutoChargeStationLevel);
-  $("#endgameChargeStationLevel>.value").text(score.EndgameChargeStationLevel ? "Level" : "Not Level");
-  $("#endgameChargeStationLevel").attr("data-value", score.EndgameChargeStationLevel);
-
-  for (let i = 0; i < 3; i++) {
-    for (let j = 0; j < 9; j++) {
-      $(`#gridAutoScoringRow${i}Node${j}`).attr("data-value", score.Grid.AutoScoring[i][j]);
-      $(`#gridNodeStatesRow${i}Node${j}`).children().each(function() {
-        const element = $(this);
-        element.attr("data-value", element.attr("data-node-state") === score.Grid.Nodes[i][j].toString());
-      });
-    }
+    $(`#leaveStatus${i1}>.value`).text(score.LeaveStatuses[i] ? "Yes" : "No");
+    $(`#leaveStatus${i1}`).attr("data-value", score.LeaveStatuses[i]);
+    $(`#parkTeam${i1}`).attr("data-value", score.EndgameStatuses[i] === 1);
+    $(`#stageSide0Team${i1}`).attr("data-value", score.EndgameStatuses[i] === 2);
+    $(`#stageSide1Team${i1}`).attr("data-value", score.EndgameStatuses[i] === 3);
+    $(`#stageSide2Team${i1}`).attr("data-value", score.EndgameStatuses[i] === 4);
+    $(`#stageSide${i}Microphone`).attr("data-value", score.MicrophoneStatuses[i]);
+    $(`#stageSide${i}Trap`).attr("data-value", score.TrapStatuses[i]);
   }
 };
 
 // Handles an element click and sends the appropriate websocket message.
-const handleClick = function(command, teamPosition = 0, gridRow = 0, gridNode = 0, nodeState = 0) {
-  websocket.send(command, {TeamPosition: teamPosition, GridRow: gridRow, GridNode: gridNode, NodeState: nodeState});
+const handleClick = function(command, teamPosition = 0, stageIndex = 0) {
+  websocket.send(command, {TeamPosition: teamPosition, StageIndex: stageIndex});
 };
 
 // Sends a websocket message to indicate that the score for this alliance is ready.
@@ -86,18 +73,6 @@ const commitMatchScore = function() {
   $("#commitMatchScore").hide();
 };
 
-// Returns the display text corresponding to the given integer endgame status value.
-const getEndgameStatusText = function(level) {
-  switch (level) {
-    case 1:
-      return "Park";
-    case 2:
-      return "Dock";
-    default:
-      return "None";
-  }
-};
-
 $(function() {
   alliance = window.location.href.split("/").slice(-1)[0];
   $("#alliance").attr("data-alliance", alliance);
diff --git a/templates/scoring_panel.html b/templates/scoring_panel.html
index b6cfb429..a7d805f0 100644
--- a/templates/scoring_panel.html
+++ b/templates/scoring_panel.html
@@ -11,44 +11,41 @@
   <div class="scoring-section">
     <div class="scoring-header">
       <div>&nbsp;</div>
-      <div>Mobility</div>
-      <div>Auto Dock</div>
-      <div>Endgame</div>
+      <div>Leave</div>
     </div>
     {{range $i := seq 3}}
       <div>
-        <div id="team{{$i}}" class="team robot-field"></div>
-        <div id="mobilityStatus{{$i}}" class="boolean robot-field" onclick="handleClick('mobilityStatus', {{$i}});">
-          <div class="value"></div>
-        </div>
-        <div id="autoDockStatus{{$i}}" class="boolean robot-field" onclick="handleClick('autoDockStatus', {{$i}});">
-          <div class="value"></div>
-        </div>
-        <div id="endgameStatus{{$i}}" class="endgame-status robot-field"
-          onclick="handleClick('endgameStatus', {{$i}});">
+        <div class="team team-{{$i}} robot-field"></div>
+        <div id="leaveStatus{{$i}}" class="boolean robot-field" onclick="handleClick('leave', {{$i}});">
           <div class="value"></div>
         </div>
       </div>
     {{end}}
   </div>
-  <div id="chargeStation" class="scoring-section">
-    <div id="autoChargeStationLevel" class="charge-station-level robot-field"
-      onclick="handleClick('autoChargeStationLevel');">
-      <div class="value"></div>
-    </div>
-    <div>Auto</div>
-    <div>Charge Station</div>
-    <div>Endgame</div>
-    <div id="endgameChargeStationLevel" class="charge-station-level robot-field"
-      onclick="handleClick('endgameChargeStationLevel');">
-      <div class="value"></div>
+  <div id="stage">
+    <svg id="stageGraphic" viewBox="0 4 100 90">
+      <polygon points="5,5 95,5 50,78" fill="none" stroke="currentColor" stroke-width="0.2" />
+      <text x="50" y="7.5" text-anchor="middle" font-size="2" fill="currentColor">Center</text>
+      <text x="50" y="9.5" text-anchor="middle" font-size="2" fill="currentColor">Stage</text>
+      <text x="30" y="38" text-anchor="middle" font-size="2" fill="currentColor">Stage</text>
+      <text x="30" y="40" text-anchor="middle" font-size="2" fill="currentColor">Left</text>
+      <text x="69" y="38" text-anchor="middle" font-size="2" fill="currentColor">Stage</text>
+      <text x="69" y="40" text-anchor="middle" font-size="2" fill="currentColor">Right</text>
+      <text x="50" y="15" text-anchor="middle" font-size="2" fill="currentColor">Park</text>
+      <line x1="5" y1="85" x2="95" y2="85" stroke="currentColor" stroke-width="0.2" />
+      <text x="50" y="87.5" text-anchor="middle" font-size="2" fill="currentColor">Alliance Wall</text>
+    </svg>
+    <div id="stageLeft" class="stage-side">{{template "stageSide" dict "index" 0}}</div>
+    <div id="centerStage" class="stage-side">{{template "stageSide" dict "index" 1}}</div>
+    <div id="stageRight" class="stage-side">{{template "stageSide" dict "index" 2}}</div>
+    <div id="park" class="stage-side">
+      <div class="stage-side-col">
+        <div id="parkTeam1" class="team-1 boolean" onclick="handleClick('park', 1);"></div>
+        <div id="parkTeam2" class="team-2 boolean" onclick="handleClick('park', 2);"></div>
+        <div id="parkTeam3" class="team-3 boolean" onclick="handleClick('park', 3);"></div>
+      </div>
     </div>
   </div>
-  <div id="grid">
-    {{template "gridRow" dict "rowIndex" 2 "validNodeStates" (index .ValidGridNodeStates 2)}}
-    {{template "gridRow" dict "rowIndex" 1 "validNodeStates" (index .ValidGridNodeStates 1)}}
-    {{template "gridRow" dict "rowIndex" 0 "validNodeStates" (index .ValidGridNodeStates 0)}}
-  </div>
 </div>
 <div id="commitMatchScore">
   <button type="button" class="btn btn-primary" onclick="commitMatchScore();">
@@ -66,31 +63,14 @@
 <script src="/static/js/match_timing.js"></script>
 <script src="/static/js/scoring_panel.js"></script>
 {{end}}
-{{define "gridRow"}}
-<div class="grid-row">
-  {{range $i, $validStates := .validNodeStates}}
-    {{template "gridNode" dict "rowIndex" $.rowIndex "nodeIndex" $i "validStates" $validStates}}
-  {{end}}
+{{define "stageSide"}}
+<div class="stage-side-col">
+  <div id="stageSide{{.index}}Team1" class="team-1 boolean" onclick="handleClick('onStage', 1, {{.index}});"></div>
+  <div id="stageSide{{.index}}Team2" class="team-2 boolean" onclick="handleClick('onStage', 2, {{.index}});"></div>
+  <div id="stageSide{{.index}}Team3" class="team-3 boolean" onclick="handleClick('onStage', 3, {{.index}});"></div>
 </div>
-{{end}}
-{{define "gridNode"}}
-<div class="grid-node" data-node="{{.nodeIndex}}">
-  <div id="gridAutoScoringRow{{$.rowIndex}}Node{{$.nodeIndex}}" class="grid-node-auto"
-    onclick="handleClick('gridAutoScoring', 0, {{$.rowIndex}}, {{$.nodeIndex}})">
-    Auto
-  </div>
-  <div id="gridNodeStatesRow{{$.rowIndex}}Node{{$.nodeIndex}}" class="grid-node-states">
-    {{range $i, $state := .validStates}}
-      {{if ne $state "Empty"}}
-        {{template "gridNodeButton" dict "i" $i "state" $state "rowIndex" $.rowIndex "nodeIndex" $.nodeIndex}}
-      {{end}}
-    {{end}}
-  </div>
-</div>
-{{end}}
-{{define "gridNodeButton"}}
-<div class="grid-node-button" data-node-state="{{nodeStateToInt .i}}"
-  onclick="handleClick('gridNode', 0, {{.rowIndex}}, {{.nodeIndex}}, {{nodeStateToInt .i}})">
-  <img src="/static/img/node_states/{{.state}}.svg" />
+<div class="stage-side-col">
+  <div id="stageSide{{.index}}Microphone" class="boolean" onclick="handleClick('microphone', 0, {{.index}});">Mic</div>
+  <div id="stageSide{{.index}}Trap" class="boolean" onclick="handleClick('trap', 0, {{.index}});">Trap</div>
 </div>
 {{end}}
diff --git a/web/scoring_panel.go b/web/scoring_panel.go
index ca8136f1..c0f9e86d 100644
--- a/web/scoring_panel.go
+++ b/web/scoring_panel.go
@@ -8,8 +8,10 @@ package web
 import (
 	"fmt"
 	"github.com/Team254/cheesy-arena/field"
+	"github.com/Team254/cheesy-arena/game"
 	"github.com/Team254/cheesy-arena/model"
 	"github.com/Team254/cheesy-arena/websocket"
+	"github.com/mitchellh/mapstructure"
 	"io"
 	"log"
 	"net/http"
@@ -56,13 +58,12 @@ func (web *Web) scoringPanelWebsocketHandler(w http.ResponseWriter, r *http.Requ
 		return
 	}
 
-	// TODO(pat): Update for 2024.
-	//var realtimeScore **field.RealtimeScore
-	//if alliance == "red" {
-	//	realtimeScore = &web.arena.RedRealtimeScore
-	//} else {
-	//	realtimeScore = &web.arena.BlueRealtimeScore
-	//}
+	var realtimeScore **field.RealtimeScore
+	if alliance == "red" {
+		realtimeScore = &web.arena.RedRealtimeScore
+	} else {
+		realtimeScore = &web.arena.BlueRealtimeScore
+	}
 
 	ws, err := websocket.NewWebsocket(w, r)
 	if err != nil {
@@ -81,9 +82,7 @@ func (web *Web) scoringPanelWebsocketHandler(w http.ResponseWriter, r *http.Requ
 
 	// Loop, waiting for commands and responding to them, until the client closes the connection.
 	for {
-		// TODO(pat): Update for 2024.
-		//command, data, err := ws.Read()
-		command, _, err := ws.Read()
+		command, data, err := ws.Read()
 		if err != nil {
 			if err == io.EOF {
 				// Client has closed the connection; nothing to do here.
@@ -92,8 +91,7 @@ func (web *Web) scoringPanelWebsocketHandler(w http.ResponseWriter, r *http.Requ
 			log.Println(err)
 			return
 		}
-		// TODO(pat): Update for 2024.
-		//score := &(*realtimeScore).CurrentScore
+		score := &(*realtimeScore).CurrentScore
 		scoreChanged := false
 
 		if command == "commitMatch" {
@@ -105,67 +103,52 @@ func (web *Web) scoringPanelWebsocketHandler(w http.ResponseWriter, r *http.Requ
 			web.arena.ScoringPanelRegistry.SetScoreCommitted(alliance, ws)
 			web.arena.ScoringStatusNotifier.Notify()
 		} else {
-			// TODO(pat): Update for 2024.
-			//args := struct {
-			//	TeamPosition int
-			//	GridRow      int
-			//	GridNode     int
-			//	NodeState    game.NodeState
-			//}{}
-			//err = mapstructure.Decode(data, &args)
-			//if err != nil {
-			//	ws.WriteError(err.Error())
-			//	continue
-			//}
-			//
-			//switch command {
-			//case "mobilityStatus":
-			//	if args.TeamPosition >= 1 && args.TeamPosition <= 3 {
-			//		score.LeaveStatuses[args.TeamPosition-1] = !score.LeaveStatuses[args.TeamPosition-1]
-			//		scoreChanged = true
-			//	}
-			//case "autoDockStatus":
-			//	if args.TeamPosition >= 1 && args.TeamPosition <= 3 {
-			//		score.AutoDockStatuses[args.TeamPosition-1] = !score.AutoDockStatuses[args.TeamPosition-1]
-			//		scoreChanged = true
-			//	}
-			//case "endgameStatus":
-			//	if args.TeamPosition >= 1 && args.TeamPosition <= 3 {
-			//		score.EndgameStatuses[args.TeamPosition-1]++
-			//		if score.EndgameStatuses[args.TeamPosition-1] > 2 {
-			//			score.EndgameStatuses[args.TeamPosition-1] = 0
-			//		}
-			//		scoreChanged = true
-			//	}
-			//case "autoChargeStationLevel":
-			//	score.AutoChargeStationLevel = !score.AutoChargeStationLevel
-			//	scoreChanged = true
-			//case "endgameChargeStationLevel":
-			//	score.EndgameChargeStationLevel = !score.EndgameChargeStationLevel
-			//	scoreChanged = true
-			//case "gridAutoScoring":
-			//	if args.GridRow >= 0 && args.GridRow <= 2 && args.GridNode >= 0 && args.GridNode <= 8 {
-			//		score.Grid.AutoScoring[args.GridRow][args.GridNode] =
-			//			!score.Grid.AutoScoring[args.GridRow][args.GridNode]
-			//		scoreChanged = true
-			//	}
-			//case "gridNode":
-			//	if args.GridRow >= 0 && args.GridRow <= 2 && args.GridNode >= 0 && args.GridNode <= 8 {
-			//		currentState := score.Grid.Nodes[args.GridRow][args.GridNode]
-			//		if currentState == args.NodeState {
-			//			score.Grid.Nodes[args.GridRow][args.GridNode] = game.Empty
-			//			if web.arena.MatchState == field.AutoPeriod || web.arena.MatchState == field.PausePeriod {
-			//				score.Grid.AutoScoring[args.GridRow][args.GridNode] = false
-			//			}
-			//		} else {
-			//			score.Grid.Nodes[args.GridRow][args.GridNode] = args.NodeState
-			//			if web.arena.MatchState == field.AutoPeriod || web.arena.MatchState == field.PausePeriod {
-			//				score.Grid.AutoScoring[args.GridRow][args.GridNode] = true
-			//			}
-			//		}
-			//		scoreChanged = true
-			//	}
-			//}
+			args := struct {
+				TeamPosition int
+				StageIndex   int
+			}{}
+			err = mapstructure.Decode(data, &args)
+			if err != nil {
+				ws.WriteError(err.Error())
+				continue
+			}
+
+			switch command {
+			case "leave":
+				if args.TeamPosition >= 1 && args.TeamPosition <= 3 {
+					score.LeaveStatuses[args.TeamPosition-1] = !score.LeaveStatuses[args.TeamPosition-1]
+					scoreChanged = true
+				}
+			case "onStage":
+				if args.TeamPosition >= 1 && args.TeamPosition <= 3 && args.StageIndex >= 0 && args.StageIndex <= 2 {
+					endgameStatus := game.EndgameStatus(args.StageIndex + 2)
+					if score.EndgameStatuses[args.TeamPosition-1] == endgameStatus {
+						score.EndgameStatuses[args.TeamPosition-1] = game.EndgameNone
+					} else {
+						score.EndgameStatuses[args.TeamPosition-1] = endgameStatus
+					}
+					scoreChanged = true
+				}
+			case "park":
+				if args.TeamPosition >= 1 && args.TeamPosition <= 3 {
+					if score.EndgameStatuses[args.TeamPosition-1] == game.EndgameParked {
+						score.EndgameStatuses[args.TeamPosition-1] = game.EndgameNone
+					} else {
+						score.EndgameStatuses[args.TeamPosition-1] = game.EndgameParked
+					}
+					scoreChanged = true
+				}
+			case "microphone":
+				if args.StageIndex >= 0 && args.StageIndex <= 2 {
+					score.MicrophoneStatuses[args.StageIndex] = !score.MicrophoneStatuses[args.StageIndex]
+					scoreChanged = true
+				}
+			case "trap":
+				if args.StageIndex >= 0 && args.StageIndex <= 2 {
+					score.TrapStatuses[args.StageIndex] = !score.TrapStatuses[args.StageIndex]
+					scoreChanged = true
+				}
+			}
 
 			if scoreChanged {
 				web.arena.RealtimeScoreNotifier.Notify()
diff --git a/web/scoring_panel_test.go b/web/scoring_panel_test.go
index 83c320ae..ea4fa426 100644
--- a/web/scoring_panel_test.go
+++ b/web/scoring_panel_test.go
@@ -5,6 +5,7 @@ package web
 
 import (
 	"github.com/Team254/cheesy-arena/field"
+	"github.com/Team254/cheesy-arena/game"
 	"github.com/Team254/cheesy-arena/websocket"
 	gorillawebsocket "github.com/gorilla/websocket"
 	"github.com/stretchr/testify/assert"
@@ -18,12 +19,11 @@ func TestScoringPanel(t *testing.T) {
 	recorder := web.getHttpResponse("/panels/scoring/invalidalliance")
 	assert.Equal(t, 500, recorder.Code)
 	assert.Contains(t, recorder.Body.String(), "Invalid alliance")
-	// TODO(pat): Update for 2024.
-	//recorder = web.getHttpResponse("/panels/scoring/red")
-	//assert.Equal(t, 200, recorder.Code)
-	//recorder = web.getHttpResponse("/panels/scoring/blue")
-	//assert.Equal(t, 200, recorder.Code)
-	//assert.Contains(t, recorder.Body.String(), "Scoring Panel - Untitled Event - Cheesy Arena")
+	recorder = web.getHttpResponse("/panels/scoring/red")
+	assert.Equal(t, 200, recorder.Code)
+	recorder = web.getHttpResponse("/panels/scoring/blue")
+	assert.Equal(t, 200, recorder.Code)
+	assert.Contains(t, recorder.Body.String(), "Scoring Panel - Untitled Event - Cheesy Arena")
 }
 
 func TestScoringPanelWebsocket(t *testing.T) {
@@ -55,63 +55,90 @@ func TestScoringPanelWebsocket(t *testing.T) {
 	readWebsocketType(t, blueWs, "realtimeScore")
 
 	// Send some autonomous period scoring commands.
-	// TODO(pat): Update for 2024.
-	//assert.Equal(t, [3]bool{false, false, false}, web.arena.RedRealtimeScore.CurrentScore.LeaveStatuses)
-	//scoringData := struct {
-	//	TeamPosition int
-	//	GridRow      int
-	//	GridNode     int
-	//	NodeState    game.NodeState
-	//}{}
-	//web.arena.MatchState = field.AutoPeriod
-	//scoringData.TeamPosition = 1
-	//redWs.Write("mobilityStatus", scoringData)
-	//scoringData.TeamPosition = 3
-	//redWs.Write("mobilityStatus", scoringData)
-	//scoringData.TeamPosition = 2
-	//redWs.Write("autoDockStatus", scoringData)
-	//redWs.Write("autoChargeStationLevel", scoringData)
-	//scoringData.GridRow = 2
-	//scoringData.GridNode = 7
-	//scoringData.NodeState = game.ConeThenCube
-	//redWs.Write("gridNode", scoringData)
-	//for i := 0; i < 5; i++ {
-	//	readWebsocketType(t, redWs, "realtimeScore")
-	//	readWebsocketType(t, blueWs, "realtimeScore")
-	//}
-	//assert.Equal(t, [3]bool{true, false, true}, web.arena.RedRealtimeScore.CurrentScore.LeaveStatuses)
-	//assert.Equal(t, [3]bool{false, true, false}, web.arena.RedRealtimeScore.CurrentScore.AutoDockStatuses)
-	//assert.Equal(t, true, web.arena.RedRealtimeScore.CurrentScore.AutoChargeStationLevel)
-	//assert.Equal(t, true, web.arena.RedRealtimeScore.CurrentScore.Grid.AutoScoring[2][7])
-	//assert.Equal(t, game.ConeThenCube, web.arena.RedRealtimeScore.CurrentScore.Grid.Nodes[2][7])
-	//
-	//// Send some teleoperated period scoring commands.
-	//web.arena.MatchState = field.TeleopPeriod
-	//scoringData.GridRow = 0
-	//scoringData.GridNode = 1
-	//scoringData.NodeState = game.TwoCubes
-	//blueWs.Write("gridNode", scoringData)
-	//scoringData.GridRow = 2
-	//blueWs.Write("gridAutoScoring", scoringData)
-	//scoringData.TeamPosition = 2
-	//blueWs.Write("endgameStatus", scoringData)
-	//scoringData.TeamPosition = 3
-	//blueWs.Write("endgameStatus", scoringData)
-	//blueWs.Write("endgameStatus", scoringData)
-	//blueWs.Write("endgameChargeStationLevel", scoringData)
-	//for i := 0; i < 6; i++ {
-	//	readWebsocketType(t, redWs, "realtimeScore")
-	//	readWebsocketType(t, blueWs, "realtimeScore")
-	//}
-	//assert.Equal(t, false, web.arena.BlueRealtimeScore.CurrentScore.Grid.AutoScoring[0][1])
-	//assert.Equal(t, game.TwoCubes, web.arena.BlueRealtimeScore.CurrentScore.Grid.Nodes[0][1])
-	//assert.Equal(t, true, web.arena.BlueRealtimeScore.CurrentScore.Grid.AutoScoring[2][1])
-	//assert.Equal(
-	//	t,
-	//	[3]game.EndgameStatus{game.EndgameNone, game.EndgameParked, game.EndgameDocked},
-	//	web.arena.BlueRealtimeScore.CurrentScore.EndgameStatuses,
-	//)
-	//assert.Equal(t, true, web.arena.BlueRealtimeScore.CurrentScore.EndgameChargeStationLevel)
+	assert.Equal(t, [3]bool{false, false, false}, web.arena.RedRealtimeScore.CurrentScore.LeaveStatuses)
+	scoringData := struct {
+		TeamPosition int
+		StageIndex   int
+	}{}
+	web.arena.MatchState = field.AutoPeriod
+	scoringData.TeamPosition = 1
+	redWs.Write("leave", scoringData)
+	scoringData.TeamPosition = 3
+	redWs.Write("leave", scoringData)
+	for i := 0; i < 2; i++ {
+		readWebsocketType(t, redWs, "realtimeScore")
+		readWebsocketType(t, blueWs, "realtimeScore")
+	}
+	assert.Equal(t, [3]bool{true, false, true}, web.arena.RedRealtimeScore.CurrentScore.LeaveStatuses)
+	redWs.Write("leave", scoringData)
+	readWebsocketType(t, redWs, "realtimeScore")
+	readWebsocketType(t, blueWs, "realtimeScore")
+	assert.Equal(t, [3]bool{true, false, false}, web.arena.RedRealtimeScore.CurrentScore.LeaveStatuses)
+
+	// Send some teleoperated period scoring commands.
+	web.arena.MatchState = field.TeleopPeriod
+	scoringData.TeamPosition = 1
+	scoringData.StageIndex = 0
+	blueWs.Write("onStage", scoringData)
+	scoringData.TeamPosition = 2
+	scoringData.StageIndex = 1
+	blueWs.Write("onStage", scoringData)
+	scoringData.TeamPosition = 3
+	scoringData.StageIndex = 2
+	redWs.Write("onStage", scoringData)
+	redWs.Write("microphone", scoringData)
+	scoringData.StageIndex = 0
+	redWs.Write("trap", scoringData)
+	for i := 0; i < 5; i++ {
+		readWebsocketType(t, redWs, "realtimeScore")
+		readWebsocketType(t, blueWs, "realtimeScore")
+	}
+	assert.Equal(
+		t,
+		[3]game.EndgameStatus{game.EndgameStageLeft, game.EndgameCenterStage, game.EndgameNone},
+		web.arena.BlueRealtimeScore.CurrentScore.EndgameStatuses,
+	)
+	assert.Equal(t, [3]bool{false, false, false}, web.arena.BlueRealtimeScore.CurrentScore.MicrophoneStatuses)
+	assert.Equal(t, [3]bool{false, false, false}, web.arena.BlueRealtimeScore.CurrentScore.TrapStatuses)
+	assert.Equal(
+		t,
+		[3]game.EndgameStatus{game.EndgameNone, game.EndgameNone, game.EndgameStageRight},
+		web.arena.RedRealtimeScore.CurrentScore.EndgameStatuses,
+	)
+	assert.Equal(t, [3]bool{false, false, true}, web.arena.RedRealtimeScore.CurrentScore.MicrophoneStatuses)
+	assert.Equal(t, [3]bool{true, false, false}, web.arena.RedRealtimeScore.CurrentScore.TrapStatuses)
+	scoringData.StageIndex = 1
+	redWs.Write("trap", scoringData)
+	scoringData.StageIndex = 0
+	redWs.Write("trap", scoringData)
+	scoringData.StageIndex = 2
+	redWs.Write("microphone", scoringData)
+	scoringData.TeamPosition = 1
+	blueWs.Write("park", scoringData)
+	scoringData.TeamPosition = 2
+	scoringData.StageIndex = 1
+	blueWs.Write("onStage", scoringData)
+	for i := 0; i < 5; i++ {
+		readWebsocketType(t, redWs, "realtimeScore")
+		readWebsocketType(t, blueWs, "realtimeScore")
+	}
+	assert.Equal(
+		t,
+		[3]game.EndgameStatus{game.EndgameParked, game.EndgameNone, game.EndgameNone},
+		web.arena.BlueRealtimeScore.CurrentScore.EndgameStatuses,
+	)
+	assert.Equal(t, [3]bool{false, false, false}, web.arena.RedRealtimeScore.CurrentScore.MicrophoneStatuses)
+	assert.Equal(t, [3]bool{false, true, false}, web.arena.RedRealtimeScore.CurrentScore.TrapStatuses)
+
+	// Test that some invalid commands do nothing and don't result in score change notifications.
+	redWs.Write("invalid", nil)
+	scoringData.TeamPosition = 0
+	redWs.Write("leave", scoringData)
+	scoringData.TeamPosition = 4
+	redWs.Write("onStage", scoringData)
+	scoringData.TeamPosition = 1
+	scoringData.StageIndex = 3
+	blueWs.Write("onStage", scoringData)
 
 	// Test committing logic.
 	redWs.Write("commitMatch", nil)