Merge "Some cleanup of target_mapping"
diff --git a/WORKSPACE b/WORKSPACE
index c603581..0c98e3f 100644
--- a/WORKSPACE
+++ b/WORKSPACE
@@ -251,11 +251,11 @@
 
 load("@com_grail_bazel_toolchain//toolchain:rules.bzl", "llvm", "llvm_toolchain")
 
-llvm_version = "13.0.0"
+llvm_version = "16.0.3"
 
 llvm(
     name = "llvm_k8",
-    distribution = "clang+llvm-%s-x86_64-linux-gnu-ubuntu-16.04.tar.xz" % llvm_version,
+    distribution = "clang+llvm-%s-x86_64-linux-gnu-ubuntu-22.04.tar.xz" % llvm_version,
     llvm_version = llvm_version,
 )
 
diff --git a/aos/aos_dump.cc b/aos/aos_dump.cc
index 1dbcd44..18d2ce1 100644
--- a/aos/aos_dump.cc
+++ b/aos/aos_dump.cc
@@ -51,8 +51,6 @@
     return 0;
   }
 
-  uint64_t message_count = 0;
-
   aos::monotonic_clock::time_point next_send_time =
       aos::monotonic_clock::min_time;
 
@@ -74,7 +72,6 @@
           cli_info.event_loop->MakeRawFetcher(channel);
       if (fetcher->Fetch()) {
         printer.PrintMessage(channel, fetcher->context());
-        ++message_count;
       }
     }
 
diff --git a/aos/events/event_loop.h b/aos/events/event_loop.h
index 1ed4337..827dd46 100644
--- a/aos/events/event_loop.h
+++ b/aos/events/event_loop.h
@@ -113,15 +113,15 @@
  public:
   using SharedSpan = std::shared_ptr<const absl::Span<const uint8_t>>;
 
-  enum class [[nodiscard]] Error{
-      // Represents success and no error
-      kOk,
+  enum class [[nodiscard]] Error {
+    // Represents success and no error
+    kOk,
 
-      // Error for messages on channels being sent faster than their
-      // frequency and channel storage duration allow
-      kMessagesSentTooFast,
-      // Access to Redzone was attempted in Sender Queue
-      kInvalidRedzone,
+    // Error for messages on channels being sent faster than their
+    // frequency and channel storage duration allow
+    kMessagesSentTooFast,
+    // Access to Redzone was attempted in Sender Queue
+    kInvalidRedzone,
   };
 
   RawSender(EventLoop *event_loop, const Channel *channel);
diff --git a/aos/events/event_loop_tmpl.h b/aos/events/event_loop_tmpl.h
index 9a5fe19..132da04 100644
--- a/aos/events/event_loop_tmpl.h
+++ b/aos/events/event_loop_tmpl.h
@@ -408,7 +408,8 @@
   void set_timing_report(timing::Watcher *watcher);
   void ResetReport();
 
-  virtual void Startup(EventLoop *event_loop) = 0;
+  virtual void Construct() = 0;
+  virtual void Startup() = 0;
 
  protected:
   const int channel_index_;
diff --git a/aos/events/logging/BUILD b/aos/events/logging/BUILD
index c33606c..a9c8497 100644
--- a/aos/events/logging/BUILD
+++ b/aos/events/logging/BUILD
@@ -183,18 +183,18 @@
     target_compatible_with = ["@platforms//os:linux"],
     visibility = ["//visibility:public"],
     deps = [
-        ":file_operations",
         ":boot_timestamp",
-        ":snappy_encoder",
         ":buffer_encoder",
-        ":logger_fbs",
+        ":file_operations",
         ":log_backend",
-        "//aos:sha256",
-        "//aos:uuid",
+        ":logger_fbs",
+        ":snappy_encoder",
         "//aos:configuration",
-        "//aos/containers:error_list",
         "//aos:flatbuffer_merge",
         "//aos:flatbuffers",
+        "//aos:sha256",
+        "//aos:uuid",
+        "//aos/containers:error_list",
         "//aos/containers:resizeable_buffer",
         "//aos/events:event_loop",
         "//aos/network:remote_message_fbs",
diff --git a/aos/events/logging/logfile_validator.cc b/aos/events/logging/logfile_validator.cc
index 19d11c8..05c881c 100644
--- a/aos/events/logging/logfile_validator.cc
+++ b/aos/events/logging/logfile_validator.cc
@@ -37,12 +37,12 @@
   multinode_estimator.set_reboot_found(
       [config](distributed_clock::time_point reboot_time,
                const std::vector<logger::BootTimestamp> &node_times) {
-        LOG(INFO) << "Rebooted at distributed " << reboot_time;
+        VLOG(1) << "Rebooted at distributed " << reboot_time;
         size_t node_index = 0;
         for (const logger::BootTimestamp &time : node_times) {
-          LOG(INFO) << "  "
-                    << config->nodes()->Get(node_index)->name()->string_view()
-                    << " " << time;
+          VLOG(1) << "  "
+                  << config->nodes()->Get(node_index)->name()->string_view()
+                  << " " << time;
           ++node_index;
         }
       });
@@ -94,7 +94,7 @@
   // logger on purpose.  It loads in *all* the timestamps in 1 go per node,
   // ignoring memory usage.
   for (const Node *node : configuration::GetNodes(config)) {
-    LOG(INFO) << "Reading all data for " << node->name()->string_view();
+    VLOG(1) << "Reading all data for " << node->name()->string_view();
     const size_t node_index = configuration::GetNodeIndex(config, node);
     TimestampMapper *timestamp_mapper = mappers[node_index].get();
     if (timestamp_mapper == nullptr) {
@@ -117,11 +117,11 @@
   if (!next_timestamp.has_value() || !next_timestamp.value().has_value()) {
     return preempt_destructor(false);
   }
-  LOG(INFO) << "Starting at:";
+  VLOG(1) << "Starting at:";
   for (const Node *node : configuration::GetNodes(config)) {
     const size_t node_index = configuration::GetNodeIndex(config, node);
-    LOG(INFO) << "  " << node->name()->string_view() << " -> "
-              << std::get<1>(*next_timestamp.value().value())[node_index].time;
+    VLOG(1) << "  " << node->name()->string_view() << " -> "
+            << std::get<1>(*next_timestamp.value().value())[node_index].time;
   }
 
   std::vector<monotonic_clock::time_point> just_monotonic(
@@ -148,7 +148,7 @@
         std::get<0>(*next_timestamp.value().value()));
   }
 
-  LOG(INFO) << "Done";
+  VLOG(1) << "Done";
 
   return preempt_destructor(true);
 }
diff --git a/aos/events/logging/multinode_logger_test_lib.cc b/aos/events/logging/multinode_logger_test_lib.cc
index 64dd1cd..9b3e00c 100644
--- a/aos/events/logging/multinode_logger_test_lib.cc
+++ b/aos/events/logging/multinode_logger_test_lib.cc
@@ -544,14 +544,12 @@
   }
 
   std::vector<std::tuple<std::string, std::string, int>> result;
-  int channel = 0;
   for (size_t i = 0; i < counts.size(); ++i) {
     if (counts[i] != 0) {
       const Channel *channel = config->channels()->Get(i);
       result.push_back(std::make_tuple(channel->name()->str(),
                                        channel->type()->str(), counts[i]));
     }
-    ++channel;
   }
 
   return result;
diff --git a/aos/events/shm_event_loop.cc b/aos/events/shm_event_loop.cc
index 3126265..7f06ee0 100644
--- a/aos/events/shm_event_loop.cc
+++ b/aos/events/shm_event_loop.cc
@@ -159,7 +159,7 @@
     }
   }
 
-  bool FetchNext() { return FetchNextIf(std::ref(should_fetch_)); }
+  bool FetchNext() { return FetchNextIf(should_fetch_); }
 
   bool FetchNextIf(std::function<bool(const Context &)> fn) {
     const ipc_lib::LocklessQueueReader::Result read_result =
@@ -192,7 +192,7 @@
     return read_result == ipc_lib::LocklessQueueReader::Result::GOOD;
   }
 
-  bool Fetch() { return FetchIf(std::ref(should_fetch_)); }
+  bool Fetch() { return FetchIf(should_fetch_); }
 
   Context context() const { return context_; }
 
@@ -557,10 +557,14 @@
     event_loop_->RemoveEvent(&event_);
   }
 
-  void Startup(EventLoop *event_loop) override {
+  void Construct() override {
+    event_loop_->CheckCurrentThread();
+    CHECK(RegisterWakeup(event_loop_->runtime_realtime_priority()));
+  }
+
+  void Startup() override {
     event_loop_->CheckCurrentThread();
     simple_shm_fetcher_.PointAtNextQueueIndex();
-    CHECK(RegisterWakeup(event_loop->runtime_realtime_priority()));
   }
 
   // Returns true if there is new data available.
@@ -1048,6 +1052,16 @@
     if (!CPU_EQUAL(&affinity_, &default_affinity)) {
       ::aos::SetCurrentThreadAffinity(affinity_);
     }
+
+    // Construct the watchers, but don't update the next pointer. This also
+    // cleans up any watchers that previously died, and puts the nonrt work
+    // before going realtime.  After this happens, we will start queueing
+    // signals (which may be a bit of extra work to process, but won't cause any
+    // messages to be lost).
+    for (::std::unique_ptr<WatcherState> &watcher : watchers_) {
+      watcher->Construct();
+    }
+
     // Now, all the callbacks are setup.  Lock everything into memory and go RT.
     if (priority_ != 0) {
       ::aos::InitRT();
@@ -1059,9 +1073,10 @@
     set_is_running(true);
 
     // Now that we are realtime (but before the OnRun handlers run), snap the
-    // queue index.
+    // queue index pointer to the newest message. This happens in RT so that we
+    // minimize the risk of losing messages.
     for (::std::unique_ptr<WatcherState> &watcher : watchers_) {
-      watcher->Startup(this);
+      watcher->Startup();
     }
 
     // Now that we are RT, run all the OnRun handlers.
diff --git a/aos/events/shm_event_loop_test.cc b/aos/events/shm_event_loop_test.cc
index 6b40b9d..2382f07 100644
--- a/aos/events/shm_event_loop_test.cc
+++ b/aos/events/shm_event_loop_test.cc
@@ -26,12 +26,12 @@
     }
 
     // Clean up anything left there before.
-    unlink((FLAGS_shm_base + "/test/aos.TestMessage.v5").c_str());
-    unlink((FLAGS_shm_base + "/test1/aos.TestMessage.v5").c_str());
-    unlink((FLAGS_shm_base + "/test2/aos.TestMessage.v5").c_str());
-    unlink((FLAGS_shm_base + "/test2/aos.TestMessage.v5").c_str());
-    unlink((FLAGS_shm_base + "/aos/aos.timing.Report.v5").c_str());
-    unlink((FLAGS_shm_base + "/aos/aos.logging.LogMessageFbs.v5").c_str());
+    unlink((FLAGS_shm_base + "/test/aos.TestMessage.v6").c_str());
+    unlink((FLAGS_shm_base + "/test1/aos.TestMessage.v6").c_str());
+    unlink((FLAGS_shm_base + "/test2/aos.TestMessage.v6").c_str());
+    unlink((FLAGS_shm_base + "/test2/aos.TestMessage.v6").c_str());
+    unlink((FLAGS_shm_base + "/aos/aos.timing.Report.v6").c_str());
+    unlink((FLAGS_shm_base + "/aos/aos.logging.LogMessageFbs.v6").c_str());
   }
 
   ~ShmEventLoopTestFactory() { FLAGS_override_hostname = ""; }
diff --git a/aos/events/simulated_event_loop.cc b/aos/events/simulated_event_loop.cc
index 7b84380..5cb67b4 100644
--- a/aos/events/simulated_event_loop.cc
+++ b/aos/events/simulated_event_loop.cc
@@ -100,7 +100,8 @@
 
   void Handle() noexcept override;
 
-  void Startup(EventLoop * /*event_loop*/) override {}
+  void Construct() override {}
+  void Startup() override {}
 
   void Schedule(std::shared_ptr<SimulatedMessage> message);
 
diff --git a/aos/events/simulated_network_bridge.cc b/aos/events/simulated_network_bridge.cc
index c8a8150..3ed39fa 100644
--- a/aos/events/simulated_network_bridge.cc
+++ b/aos/events/simulated_network_bridge.cc
@@ -234,7 +234,7 @@
       // channel within the same callback.
       LOG(WARNING) << "Not forwarding message on "
                    << configuration::CleanedChannelToString(fetcher_->channel())
-                   << " because we aren't running.  Set at "
+                   << " because we aren't running.  Sent at "
                    << fetcher_->context().monotonic_event_time << " now is "
                    << fetch_node_factory_->monotonic_now();
       sent_ = true;
diff --git a/aos/ipc_lib/BUILD b/aos/ipc_lib/BUILD
index d67616f..75bbd4d 100644
--- a/aos/ipc_lib/BUILD
+++ b/aos/ipc_lib/BUILD
@@ -193,10 +193,12 @@
         "lockless_queue.cc",
         "lockless_queue_memory.h",
         "memory_mapped_queue.cc",
+        "robust_ownership_tracker.cc",
     ],
     hdrs = [
         "lockless_queue.h",
         "memory_mapped_queue.h",
+        "robust_ownership_tracker.h",
     ],
     target_compatible_with = ["@platforms//os:linux"],
     visibility = ["//visibility:public"],
@@ -210,6 +212,7 @@
         "//aos/events:context",
         "//aos/time",
         "//aos/util:compiler_memory_barrier",
+        "//aos/util:top",
         "@com_github_google_glog//:glog",
         "@com_google_absl//absl/strings",
         "@com_google_absl//absl/types:span",
@@ -250,6 +253,16 @@
 )
 
 cc_test(
+    name = "robust_ownership_tracker_test",
+    srcs = ["robust_ownership_tracker_test.cc"],
+    target_compatible_with = ["@platforms//os:linux"],
+    deps = [
+        ":lockless_queue",
+        "//aos/testing:googletest",
+    ],
+)
+
+cc_test(
     name = "lockless_queue_test",
     timeout = "eternal",
     srcs = ["lockless_queue_test.cc"],
diff --git a/aos/ipc_lib/lockless_queue.cc b/aos/ipc_lib/lockless_queue.cc
index fd3a305..f033e60 100644
--- a/aos/ipc_lib/lockless_queue.cc
+++ b/aos/ipc_lib/lockless_queue.cc
@@ -172,9 +172,7 @@
   size_t valid_senders = 0;
   for (size_t i = 0; i < num_senders; ++i) {
     Sender *sender = memory->GetSender(i);
-    const uint32_t tid =
-        __atomic_load_n(&(sender->tid.futex), __ATOMIC_ACQUIRE);
-    if (!(tid & FUTEX_OWNER_DIED)) {
+    if (!sender->ownership_tracker.OwnerIsDefinitelyAbsolutelyDead()) {
       // Not dead.
       ++valid_senders;
       continue;
@@ -227,7 +225,7 @@
       // always be a NOP if it's in 1), verified by a DCHECK.
       memory->GetMessage(scratch_index)->header.queue_index.RelaxedInvalidate();
 
-      __atomic_store_n(&(sender->tid.futex), 0, __ATOMIC_RELEASE);
+      sender->ownership_tracker.ForceClear();
       ++valid_senders;
       continue;
     }
@@ -245,7 +243,7 @@
       aos_compiler_memory_barrier();
 
       // And mark that we succeeded.
-      __atomic_store_n(&(sender->tid.futex), 0, __ATOMIC_RELEASE);
+      sender->ownership_tracker.ForceClear();
       ++valid_senders;
       continue;
     }
@@ -259,13 +257,11 @@
   // read it before it's set.
   for (size_t i = 0; i < num_pinners; ++i) {
     Pinner *const pinner = memory->GetPinner(i);
-    const uint32_t tid =
-        __atomic_load_n(&(pinner->tid.futex), __ATOMIC_ACQUIRE);
-    if (!(tid & FUTEX_OWNER_DIED)) {
+    if (!pinner->ownership_tracker.OwnerIsDefinitelyAbsolutelyDead()) {
       continue;
     }
     pinner->pinned.Invalidate();
-    __atomic_store_n(&(pinner->tid.futex), 0, __ATOMIC_RELEASE);
+    pinner->ownership_tracker.ForceClear();
   }
 
   // If all the senders are (or were made) good, there is no need to do the hard
@@ -284,9 +280,7 @@
     num_missing = 0;
     for (size_t i = 0; i < num_senders; ++i) {
       Sender *const sender = memory->GetSender(i);
-      const uint32_t tid =
-          __atomic_load_n(&(sender->tid.futex), __ATOMIC_ACQUIRE);
-      if (tid & FUTEX_OWNER_DIED) {
+      if (sender->ownership_tracker.OwnerIsDefinitelyAbsolutelyDead()) {
         if (!need_recovery[i]) {
           return false;
         }
@@ -331,9 +325,7 @@
     const size_t starting_num_missing = num_missing;
     for (size_t i = 0; i < num_senders; ++i) {
       Sender *sender = memory->GetSender(i);
-      const uint32_t tid =
-          __atomic_load_n(&(sender->tid.futex), __ATOMIC_ACQUIRE);
-      if (!(tid & FUTEX_OWNER_DIED)) {
+      if (!sender->ownership_tracker.OwnerIsDefinitelyAbsolutelyDead()) {
         CHECK(!need_recovery[i]) << ": Somebody else recovered a sender: " << i;
         continue;
       }
@@ -368,7 +360,7 @@
             ->header.queue_index.RelaxedInvalidate();
 
         // And then mark this sender clean.
-        __atomic_store_n(&(sender->tid.futex), 0, __ATOMIC_RELEASE);
+        sender->ownership_tracker.ForceClear();
         need_recovery[i] = false;
 
         // And account for scratch_index.
@@ -394,7 +386,7 @@
         sender->to_replace.Invalidate();
 
         // And then mark this sender clean.
-        __atomic_store_n(&(sender->tid.futex), 0, __ATOMIC_RELEASE);
+        sender->ownership_tracker.ForceClear();
         need_recovery[i] = false;
 
         // And account for to_replace.
@@ -437,6 +429,14 @@
 
 }  // namespace
 
+bool PretendThatOwnerIsDeadForTesting(aos_mutex *mutex, pid_t tid) {
+  if (static_cast<pid_t>(mutex->futex & FUTEX_TID_MASK) == tid) {
+    mutex->futex = FUTEX_OWNER_DIED;
+    return true;
+  }
+  return false;
+}
+
 size_t LocklessQueueConfiguration::message_size() const {
   // Round up the message size so following data is aligned appropriately.
   // Make sure to leave space to align the message data. It will be aligned
@@ -685,11 +685,10 @@
   CHECK_NE(watcher_index_, -1);
 
   // Make sure we still own the slot we are supposed to.
-  CHECK(
-      death_notification_is_held(&(memory_->GetWatcher(watcher_index_)->tid)));
+  CHECK(memory_->GetWatcher(watcher_index_)->ownership_tracker.IsHeldBySelf());
 
   // The act of unlocking invalidates the entry.  Invalidate it.
-  death_notification_release(&(memory_->GetWatcher(watcher_index_)->tid));
+  memory_->GetWatcher(watcher_index_)->ownership_tracker.Release();
   // And internally forget the slot.
   watcher_index_ = -1;
 
@@ -699,7 +698,7 @@
   // And confirm that nothing is owned by us.
   const int num_watchers = memory_->num_watchers();
   for (int i = 0; i < num_watchers; ++i) {
-    CHECK(!death_notification_is_held(&(memory_->GetWatcher(i)->tid)))
+    CHECK(!memory_->GetWatcher(i)->ownership_tracker.IsHeldBySelf())
         << ": " << i;
   }
 }
@@ -732,14 +731,15 @@
   for (int i = 0; i < num_watchers; ++i) {
     // If we see a slot the kernel has marked as dead, everything we do reusing
     // it needs to happen-after whatever that process did before dying.
-    auto *const futex = &(memory_->GetWatcher(i)->tid.futex);
-    const uint32_t tid = __atomic_load_n(futex, __ATOMIC_ACQUIRE);
-    if (tid == 0 || (tid & FUTEX_OWNER_DIED)) {
+    auto *const ownership_tracker =
+        &(memory_->GetWatcher(i)->ownership_tracker);
+    if (ownership_tracker->LoadAcquire().IsUnclaimed() ||
+        ownership_tracker->OwnerIsDefinitelyAbsolutelyDead()) {
       watcher_index_ = i;
       // Relaxed is OK here because we're the only task going to touch it
       // between here and the write in death_notification_init below (other
       // recovery is blocked by us holding the setup lock).
-      __atomic_store_n(futex, 0, __ATOMIC_RELAXED);
+      ownership_tracker->ForceClear();
       break;
     }
   }
@@ -756,7 +756,7 @@
 
   // Grabbing a mutex is a compiler and memory barrier, so nothing before will
   // get rearranged afterwords.
-  death_notification_init(&(w->tid));
+  w->ownership_tracker.Acquire();
 }
 
 LocklessQueueWakeUpper::LocklessQueueWakeUpper(LocklessQueue queue)
@@ -778,25 +778,25 @@
   // creating a pidfd is likely not RT.
   for (size_t i = 0; i < num_watchers; ++i) {
     const Watcher *w = memory_->GetWatcher(i);
-    watcher_copy_[i].tid = __atomic_load_n(&(w->tid.futex), __ATOMIC_RELAXED);
+    watcher_copy_[i].ownership_snapshot = w->ownership_tracker.LoadRelaxed();
     // Force the load of the TID to come first.
     aos_compiler_memory_barrier();
     watcher_copy_[i].pid = w->pid.load(std::memory_order_relaxed);
     watcher_copy_[i].priority = w->priority.load(std::memory_order_relaxed);
 
     // Use a priority of -1 to mean an invalid entry to make sorting easier.
-    if (watcher_copy_[i].tid & FUTEX_OWNER_DIED || watcher_copy_[i].tid == 0) {
+    if (watcher_copy_[i].ownership_snapshot.OwnerIsDead() ||
+        watcher_copy_[i].ownership_snapshot.IsUnclaimed()) {
       watcher_copy_[i].priority = -1;
     } else {
       // Ensure all of this happens after we're done looking at the pid+priority
       // in shared memory.
       aos_compiler_memory_barrier();
-      if (watcher_copy_[i].tid != static_cast<pid_t>(__atomic_load_n(
-                                      &(w->tid.futex), __ATOMIC_RELAXED))) {
+      if (watcher_copy_[i].ownership_snapshot !=
+          w->ownership_tracker.LoadRelaxed()) {
         // Confirm that the watcher hasn't been re-used and modified while we
         // read it.  If it has, mark it invalid again.
         watcher_copy_[i].priority = -1;
-        watcher_copy_[i].tid = 0;
       }
     }
   }
@@ -835,8 +835,8 @@
       // Send the signal.  Target just the thread that sent it so that we can
       // support multiple watchers in a process (when someone creates multiple
       // event loops in different threads).
-      rt_tgsigqueueinfo(watcher_copy.pid, watcher_copy.tid, kWakeupSignal,
-                        &uinfo);
+      rt_tgsigqueueinfo(watcher_copy.pid, watcher_copy.ownership_snapshot.tid(),
+                        kWakeupSignal, &uinfo);
 
       ++count;
     }
@@ -872,8 +872,7 @@
     // This doesn't need synchronization because we're the only process doing
     // initialization right now, and nobody else will be touching senders which
     // we're interested in.
-    const uint32_t tid = __atomic_load_n(&(s->tid.futex), __ATOMIC_RELAXED);
-    if (tid == 0) {
+    if (s->ownership_tracker.LoadRelaxed().IsUnclaimed()) {
       sender_index_ = i;
       break;
     }
@@ -888,7 +887,7 @@
 
   // Indicate that we are now alive by taking over the slot. If the previous
   // owner died, we still want to do this.
-  death_notification_init(&(sender->tid));
+  sender->ownership_tracker.Acquire();
 
   const Index scratch_index = sender->scratch_index.RelaxedLoad();
   Message *const message = memory_->GetMessage(scratch_index);
@@ -899,7 +898,7 @@
 LocklessQueueSender::~LocklessQueueSender() {
   if (sender_index_ != -1) {
     CHECK(memory_ != nullptr);
-    death_notification_release(&(memory_->GetSender(sender_index_)->tid));
+    memory_->GetSender(sender_index_)->ownership_tracker.Release();
   }
 }
 
@@ -1183,8 +1182,7 @@
     // This doesn't need synchronization because we're the only process doing
     // initialization right now, and nobody else will be touching pinners which
     // we're interested in.
-    const uint32_t tid = __atomic_load_n(&(p->tid.futex), __ATOMIC_RELAXED);
-    if (tid == 0) {
+    if (p->ownership_tracker.LoadRelaxed().IsUnclaimed()) {
       pinner_index_ = i;
       break;
     }
@@ -1200,7 +1198,7 @@
 
   // Indicate that we are now alive by taking over the slot. If the previous
   // owner died, we still want to do this.
-  death_notification_init(&(p->tid));
+  p->ownership_tracker.Acquire();
 }
 
 LocklessQueuePinner::~LocklessQueuePinner() {
@@ -1208,7 +1206,7 @@
     CHECK(memory_ != nullptr);
     memory_->GetPinner(pinner_index_)->pinned.Invalidate();
     aos_compiler_memory_barrier();
-    death_notification_release(&(memory_->GetPinner(pinner_index_)->tid));
+    memory_->GetPinner(pinner_index_)->ownership_tracker.Release();
   }
 }
 
@@ -1622,8 +1620,8 @@
   for (size_t i = 0; i < memory->num_senders(); ++i) {
     const Sender *s = memory->GetSender(i);
     ::std::cout << "    [" << i << "] -> Sender {" << ::std::endl;
-    ::std::cout << "      aos_mutex tid = " << PrintMutex(&s->tid)
-                << ::std::endl;
+    ::std::cout << "      RobustOwnershipTracker ownership_tracker = "
+                << s->ownership_tracker.DebugString() << ::std::endl;
     ::std::cout << "      AtomicIndex scratch_index = "
                 << s->scratch_index.Load().DebugString() << ::std::endl;
     ::std::cout << "      AtomicIndex to_replace = "
@@ -1637,8 +1635,8 @@
   for (size_t i = 0; i < memory->num_pinners(); ++i) {
     const Pinner *p = memory->GetPinner(i);
     ::std::cout << "    [" << i << "] -> Pinner {" << ::std::endl;
-    ::std::cout << "      aos_mutex tid = " << PrintMutex(&p->tid)
-                << ::std::endl;
+    ::std::cout << "      RobustOwnershipTracker ownership_tracker = "
+                << p->ownership_tracker.DebugString() << ::std::endl;
     ::std::cout << "      AtomicIndex scratch_index = "
                 << p->scratch_index.Load().DebugString() << ::std::endl;
     ::std::cout << "      AtomicIndex pinned = "
@@ -1653,8 +1651,8 @@
   for (size_t i = 0; i < memory->num_watchers(); ++i) {
     const Watcher *w = memory->GetWatcher(i);
     ::std::cout << "    [" << i << "] -> Watcher {" << ::std::endl;
-    ::std::cout << "      aos_mutex tid = " << PrintMutex(&w->tid)
-                << ::std::endl;
+    ::std::cout << "      RobustOwnershipTracker ownership_tracker = "
+                << w->ownership_tracker.DebugString() << ::std::endl;
     ::std::cout << "      pid_t pid = " << w->pid << ::std::endl;
     ::std::cout << "      int priority = " << w->priority << ::std::endl;
     ::std::cout << "    }" << ::std::endl;
diff --git a/aos/ipc_lib/lockless_queue.h b/aos/ipc_lib/lockless_queue.h
index 01f2b7c..14a11b6 100644
--- a/aos/ipc_lib/lockless_queue.h
+++ b/aos/ipc_lib/lockless_queue.h
@@ -14,6 +14,7 @@
 #include "aos/ipc_lib/aos_sync.h"
 #include "aos/ipc_lib/data_alignment.h"
 #include "aos/ipc_lib/index.h"
+#include "aos/ipc_lib/robust_ownership_tracker.h"
 #include "aos/time/time.h"
 #include "aos/uuid.h"
 
@@ -29,7 +30,7 @@
   // Note: this is only modified with the queue_setup_lock lock held, but may
   // always be read.
   // Any state modification should happen before the lock is acquired.
-  aos_mutex tid;
+  RobustOwnershipTracker ownership_tracker;
 
   // PID of the watcher.
   std::atomic<pid_t> pid;
@@ -46,7 +47,7 @@
   //
   // Note: this is only modified with the queue_setup_lock lock held, but may
   // always be read.
-  aos_mutex tid;
+  RobustOwnershipTracker ownership_tracker;
 
   // Index of the message we will be filling out.
   AtomicIndex scratch_index;
@@ -59,7 +60,7 @@
 // Structure to hold the state required to pin messages.
 struct Pinner {
   // The same as Sender::tid. See there for docs.
-  aos_mutex tid;
+  RobustOwnershipTracker ownership_tracker;
 
   // Queue index of the message we have pinned, or Invalid if there isn't one.
   AtomicQueueIndex pinned;
@@ -200,6 +201,10 @@
 
 const static unsigned int kWakeupSignal = SIGRTMIN + 2;
 
+// Sets FUTEX_OWNER_DIED if the owner was tid.  This fakes what the kernel does
+// with a robust mutex.
+bool PretendThatOwnerIsDeadForTesting(aos_mutex *mutex, pid_t tid);
+
 // A convenient wrapper for accessing a lockless queue.
 class LocklessQueue {
  public:
@@ -268,7 +273,7 @@
   // up.  This isn't a copy of Watcher since tid is simpler to work with here
   // than the futex above.
   struct WatcherCopy {
-    pid_t tid;
+    ThreadOwnerStatusSnapshot ownership_snapshot;
     pid_t pid;
     int priority;
   };
diff --git a/aos/ipc_lib/lockless_queue_death_test.cc b/aos/ipc_lib/lockless_queue_death_test.cc
index 2a95b31..92ad8a8 100644
--- a/aos/ipc_lib/lockless_queue_death_test.cc
+++ b/aos/ipc_lib/lockless_queue_death_test.cc
@@ -102,12 +102,13 @@
         bool print = false;
 
         // TestShmRobustness doesn't handle robust futexes.  It is happy to just
-        // not crash with them.  We know what they are, and what the tid of the
-        // holder is.  So go pretend to be the kernel and fix it for it.
-        PretendOwnerDied(&memory->queue_setup_lock, tid.Get());
+        // not crash with them.  We know what the futexes are, and what the tid
+        // of the corresponding holder is.  So go pretend to be the kernel and
+        // fix the futex.
+        PretendThatOwnerIsDeadForTesting(&memory->queue_setup_lock, tid.Get());
 
         for (size_t i = 0; i < config.num_senders; ++i) {
-          if (PretendOwnerDied(&memory->GetSender(i)->tid, tid.Get())) {
+          if (memory->GetSender(i)->ownership_tracker.IsHeldBy(tid.Get())) {
             // Print out before and after results if a sender died.  That is the
             // more fun case.
             print = true;
diff --git a/aos/ipc_lib/lockless_queue_stepping.cc b/aos/ipc_lib/lockless_queue_stepping.cc
index 0ec5214..e819795 100644
--- a/aos/ipc_lib/lockless_queue_stepping.cc
+++ b/aos/ipc_lib/lockless_queue_stepping.cc
@@ -458,14 +458,6 @@
 
 pid_t SharedTid::Get() { return *tid_; }
 
-bool PretendOwnerDied(aos_mutex *mutex, pid_t tid) {
-  if ((mutex->futex & FUTEX_TID_MASK) == tid) {
-    mutex->futex = FUTEX_OWNER_DIED;
-    return true;
-  }
-  return false;
-}
-
 }  // namespace testing
 }  // namespace ipc_lib
 }  // namespace aos
diff --git a/aos/ipc_lib/lockless_queue_stepping.h b/aos/ipc_lib/lockless_queue_stepping.h
index b3eb6e3..7aab193 100644
--- a/aos/ipc_lib/lockless_queue_stepping.h
+++ b/aos/ipc_lib/lockless_queue_stepping.h
@@ -140,10 +140,6 @@
   pid_t *tid_;
 };
 
-// Sets FUTEX_OWNER_DIED if the owner was tid.  This fakes what the kernel does
-// with a robust mutex.
-bool PretendOwnerDied(aos_mutex *mutex, pid_t tid);
-
 #endif
 
 }  // namespace testing
diff --git a/aos/ipc_lib/lockless_queue_test.cc b/aos/ipc_lib/lockless_queue_test.cc
index 58e093f..14701e2 100644
--- a/aos/ipc_lib/lockless_queue_test.cc
+++ b/aos/ipc_lib/lockless_queue_test.cc
@@ -538,7 +538,7 @@
         ::aos::ipc_lib::LocklessQueueMemory *const memory =
             reinterpret_cast<::aos::ipc_lib::LocklessQueueMemory *>(raw_memory);
         LocklessQueue queue(memory, memory, config);
-        PretendOwnerDied(&memory->queue_setup_lock, tid.Get());
+        PretendThatOwnerIsDeadForTesting(&memory->queue_setup_lock, tid.Get());
 
         if (VLOG_IS_ON(1)) {
           PrintLocklessQueueMemory(memory);
diff --git a/aos/ipc_lib/memory_mapped_queue.cc b/aos/ipc_lib/memory_mapped_queue.cc
index 79f27d9..594febb 100644
--- a/aos/ipc_lib/memory_mapped_queue.cc
+++ b/aos/ipc_lib/memory_mapped_queue.cc
@@ -17,7 +17,7 @@
 
 std::string ShmPath(std::string_view shm_base, const Channel *channel) {
   CHECK(channel->has_type());
-  return ShmFolder(shm_base, channel) + channel->type()->str() + ".v5";
+  return ShmFolder(shm_base, channel) + channel->type()->str() + ".v6";
 }
 
 void PageFaultDataWrite(char *data, size_t size) {
diff --git a/aos/ipc_lib/robust_ownership_tracker.cc b/aos/ipc_lib/robust_ownership_tracker.cc
new file mode 100644
index 0000000..f106113
--- /dev/null
+++ b/aos/ipc_lib/robust_ownership_tracker.cc
@@ -0,0 +1,25 @@
+#include "aos/ipc_lib/robust_ownership_tracker.h"
+
+#include "aos/ipc_lib/lockless_queue.h"
+
+namespace aos {
+namespace ipc_lib {
+
+::std::string RobustOwnershipTracker::DebugString() const {
+  ::std::stringstream s;
+  s << "{.tid=aos_mutex(" << ::std::hex << mutex_.futex;
+
+  if (mutex_.futex != 0) {
+    s << ":";
+    if (mutex_.futex & FUTEX_OWNER_DIED) {
+      s << "FUTEX_OWNER_DIED|";
+    }
+    s << "tid=" << (mutex_.futex & FUTEX_TID_MASK);
+  }
+
+  s << "),}";
+  return s.str();
+}
+
+}  // namespace ipc_lib
+}  // namespace aos
diff --git a/aos/ipc_lib/robust_ownership_tracker.h b/aos/ipc_lib/robust_ownership_tracker.h
new file mode 100644
index 0000000..a8bbca3
--- /dev/null
+++ b/aos/ipc_lib/robust_ownership_tracker.h
@@ -0,0 +1,177 @@
+#ifndef AOS_IPC_LIB_ROBUST_OWNERSHIP_TRACKER_H_
+#define AOS_IPC_LIB_ROBUST_OWNERSHIP_TRACKER_H_
+
+#include <linux/futex.h>
+#include <sys/syscall.h>
+
+#include <string>
+
+#include "aos/ipc_lib/aos_sync.h"
+#include "aos/util/top.h"
+
+namespace aos::ipc_lib {
+namespace testing {
+class RobustOwnershipTrackerTest;
+}  // namespace testing
+
+// Results of atomically loading the ownership state via RobustOwnershipTracker
+// below. This allows the state to be compared and queried later.
+class ThreadOwnerStatusSnapshot {
+ public:
+  ThreadOwnerStatusSnapshot() : futex_(0) {}
+  ThreadOwnerStatusSnapshot(aos_futex futex) : futex_(futex) {}
+  ThreadOwnerStatusSnapshot(const ThreadOwnerStatusSnapshot &) = default;
+  ThreadOwnerStatusSnapshot &operator=(const ThreadOwnerStatusSnapshot &) =
+      default;
+  ThreadOwnerStatusSnapshot(ThreadOwnerStatusSnapshot &&) = default;
+  ThreadOwnerStatusSnapshot &operator=(ThreadOwnerStatusSnapshot &&) = default;
+
+  // Returns if the owner died as noticed by the robust futex using Acquire
+  // memory ordering.
+  bool OwnerIsDead() const { return (futex_ & FUTEX_OWNER_DIED) != 0; }
+
+  // Returns true if no one has claimed ownership.
+  bool IsUnclaimed() const { return futex_ == 0; }
+
+  // Returns the thread ID (a.k.a. "tid") of the owning thread. Use this when
+  // trying to access the /proc entry that corresponds to the owning thread for
+  // example. Do not use the futex value directly.
+  pid_t tid() const { return futex_ & FUTEX_TID_MASK; }
+
+  bool operator==(const ThreadOwnerStatusSnapshot &other) const {
+    return other.futex_ == futex_;
+  }
+
+ private:
+  aos_futex futex_;
+};
+
+// This object reliably tracks a thread owning a resource. A single thread may
+// possess multiple resources like senders and receivers. Each resource can have
+// its own instance of this class. These instances are responsible for
+// monitoring the thread that owns them. Each resource can use its instance of
+// this class to reliably check whether the owning thread is no longer alive.
+//
+// All methods other than Load* must be accessed under a mutex.
+class RobustOwnershipTracker {
+ public:
+  static constexpr uint64_t kNoStartTimeTicks =
+      std::numeric_limits<uint64_t>::max();
+
+  static uint64_t ReadStartTimeTicks(pid_t tid) {
+    if (tid == 0) {
+      return kNoStartTimeTicks;
+    }
+    std::optional<aos::util::ProcStat> proc_stat = util::ReadProcStat(tid);
+    if (!proc_stat.has_value()) {
+      return kNoStartTimeTicks;
+    }
+    return proc_stat->start_time_ticks;
+  }
+
+  // Loads the realtime-compatible contents of the ownership tracker with
+  // Acquire memory ordering.
+  ThreadOwnerStatusSnapshot LoadAcquire() const {
+    return ThreadOwnerStatusSnapshot(
+        __atomic_load_n(&(mutex_.futex), __ATOMIC_ACQUIRE));
+  }
+
+  // Loads all the realtime-compatible contents of the ownership tracker with
+  // Relaxed memory order.
+  ThreadOwnerStatusSnapshot LoadRelaxed() const {
+    return ThreadOwnerStatusSnapshot(
+        __atomic_load_n(&(mutex_.futex), __ATOMIC_RELAXED));
+  }
+
+  // Checks both the robust futex and dredges through /proc to see if the thread
+  // is alive. As per the class description, this must only be called under a
+  // mutex. This must not be called in a realtime context and it is slow.
+  bool OwnerIsDefinitelyAbsolutelyDead() const {
+    auto loaded = LoadAcquire();
+    if (loaded.OwnerIsDead()) {
+      return true;
+    }
+    if (loaded.IsUnclaimed()) {
+      return false;
+    }
+    const uint64_t proc_start_time_ticks = ReadStartTimeTicks(loaded.tid());
+    if (proc_start_time_ticks == kNoStartTimeTicks) {
+      LOG(ERROR) << "Detected that PID " << loaded.tid() << " died.";
+      return true;
+    }
+
+    if (proc_start_time_ticks != start_time_ticks_) {
+      LOG(ERROR) << "Detected that PID " << loaded.tid()
+                 << " died from a starttime missmatch.";
+      return true;
+    }
+    return false;
+  }
+
+  // Clears all ownership state.
+  //
+  // This should only really be called if you are 100% certain that the owner is
+  // dead. Use `LoadAquire().OwnerIsDead()` to determine this.
+  void ForceClear() {
+    // Must be opposite order of Acquire.
+    // We only deal with the futex here because we don't want to change anything
+    // about the linked list. We just want to release ownership here. We still
+    // want the kernel to know about this element via the linked list the next
+    // time someone takes ownership.
+    __atomic_store_n(&(mutex_.futex), 0, __ATOMIC_RELEASE);
+    start_time_ticks_ = kNoStartTimeTicks;
+  }
+
+  // Returns true if this thread holds ownership.
+  bool IsHeldBySelf() { return death_notification_is_held(&mutex_); }
+
+  // Returns true if the mutex is held by the provided tid.  This is primarily
+  // intended for testing. There should be no need to call this in production
+  // code.
+  bool IsHeldBy(pid_t tid) { return LoadRelaxed().tid() == tid; }
+
+  // Acquires ownership. Other threads will know that this thread holds the
+  // ownership or be notified if this thread dies.
+  void Acquire() {
+    pid_t tid = syscall(SYS_gettid);
+    assert(tid > 0);
+    const uint64_t proc_start_time_ticks = ReadStartTimeTicks(tid);
+    CHECK_NE(proc_start_time_ticks, kNoStartTimeTicks);
+
+    start_time_ticks_ = proc_start_time_ticks;
+    death_notification_init(&mutex_);
+  }
+
+  // Releases ownership.
+  //
+  // This should only be called from the owning thread.
+  void Release() {
+    // Must be opposite order of Acquire.
+    death_notification_release(&mutex_);
+    start_time_ticks_ = kNoStartTimeTicks;
+  }
+
+  // Returns a string representing this object.
+  std::string DebugString() const;
+
+ private:
+  friend class testing::RobustOwnershipTrackerTest;
+
+  // Robust futex to track ownership the normal way. The futex is inside the
+  // mutex here. We use the wrapper mutex because the death_notification_*
+  // functions operate on that instead of the futex directly.
+  //
+  // We use a futex here because:
+  // - futexes are fast.
+  // - The kernel can atomically clean up a dead thread and mark the futex
+  //   appropriately.
+  // - Owners can clean up after dead threads.
+  aos_mutex mutex_;
+
+  // Thread's start time ticks.
+  std::atomic<uint64_t> start_time_ticks_;
+};
+
+}  // namespace aos::ipc_lib
+
+#endif  // AOS_IPC_LIB_ROBUST_OWNERSHIP_TRACKER_H_
diff --git a/aos/ipc_lib/robust_ownership_tracker_test.cc b/aos/ipc_lib/robust_ownership_tracker_test.cc
new file mode 100644
index 0000000..b204886
--- /dev/null
+++ b/aos/ipc_lib/robust_ownership_tracker_test.cc
@@ -0,0 +1,155 @@
+#include "aos/ipc_lib/robust_ownership_tracker.h"
+
+#include <sys/mman.h>
+#include <sys/wait.h>
+
+#include "gtest/gtest.h"
+
+namespace aos::ipc_lib::testing {
+
+// Capture RobustOwnershipTracker in shared memory so it is shared across a
+// fork.
+class SharedRobustOwnershipTracker {
+ public:
+  SharedRobustOwnershipTracker() {
+    tracker_ = static_cast<RobustOwnershipTracker *>(
+        mmap(nullptr, sizeof(RobustOwnershipTracker), PROT_READ | PROT_WRITE,
+             MAP_SHARED | MAP_ANONYMOUS, -1, 0));
+    PCHECK(MAP_FAILED != tracker_);
+  };
+  ~SharedRobustOwnershipTracker() {
+    PCHECK(munmap(tracker_, sizeof(RobustOwnershipTracker)) != -1);
+  }
+
+  // Captures the tid.
+  RobustOwnershipTracker &tracker() const { return *tracker_; }
+
+ private:
+  RobustOwnershipTracker *tracker_;
+};
+
+class RobustOwnershipTrackerTest : public ::testing::Test {
+ public:
+  // Runs a function in a child process, and then exits afterwards.  Waits for
+  // the child to finish before resuming.
+  template <typename T>
+  void RunInChildAndBlockUntilComplete(T fn) {
+    pid_t pid = fork();
+    if (pid == 0) {
+      fn();
+      LOG(INFO) << "Child exiting normally.";
+      exit(0);
+      return;
+    }
+
+    LOG(INFO) << "Child has pid " << pid;
+
+    while (true) {
+      LOG(INFO) << "Waiting for child.";
+      int status;
+      const pid_t waited_on = waitpid(pid, &status, 0);
+      // Check for failure.
+      if (waited_on == -1) {
+        if (errno == EINTR) continue;
+        PLOG(FATAL) << ": waitpid(" << pid << ", " << &status << ", 0) failed";
+      }
+      CHECK_EQ(waited_on, pid)
+          << ": waitpid() got child " << waited_on << " instead of " << pid;
+      CHECK(WIFEXITED(status));
+      LOG(INFO) << "Status " << WEXITSTATUS(status);
+      CHECK(WEXITSTATUS(status) == 0);
+      return;
+    }
+  }
+
+  // Returns the robust mutex.
+  aos_mutex &GetMutex(RobustOwnershipTracker &tracker) {
+    return tracker.mutex_;
+  }
+
+  // Returns the current start time in ticks.
+  uint64_t GetStartTimeTicks(RobustOwnershipTracker &tracker) {
+    return tracker.start_time_ticks_.load();
+  }
+
+  // Sets the current start time in ticks.
+  void SetStartTimeTicks(RobustOwnershipTracker &tracker, uint64_t start_time) {
+    tracker.start_time_ticks_ = start_time;
+  }
+};
+
+// Tests that acquiring the futex doesn't erroneously report the owner (i.e.
+// "us") as dead.
+TEST_F(RobustOwnershipTrackerTest, AcquireWorks) {
+  SharedRobustOwnershipTracker shared_tracker;
+
+  EXPECT_FALSE(shared_tracker.tracker().OwnerIsDefinitelyAbsolutelyDead());
+
+  // Run acquire in the this process, and expect it should not be dead until
+  // after the test finishes.
+  shared_tracker.tracker().Acquire();
+
+  // We have ownership. Since we are alive, the owner should not be marked as
+  // dead. We can use relaxed ordering since we are the only ones touching the
+  // data here.
+  EXPECT_FALSE(shared_tracker.tracker().LoadRelaxed().OwnerIsDead());
+  EXPECT_FALSE(shared_tracker.tracker().OwnerIsDefinitelyAbsolutelyDead());
+}
+
+// Tests that child death without unlocking results in the futex being marked as
+// dead, and the owner being very dead.
+TEST_F(RobustOwnershipTrackerTest, FutexRecovers) {
+  SharedRobustOwnershipTracker shared_tracker;
+
+  RunInChildAndBlockUntilComplete(
+      [&]() { shared_tracker.tracker().Acquire(); });
+
+  // Since the child that took ownership died, we expect that death to be
+  // reported.
+  EXPECT_TRUE(shared_tracker.tracker().LoadRelaxed().OwnerIsDead());
+  EXPECT_TRUE(shared_tracker.tracker().OwnerIsDefinitelyAbsolutelyDead());
+}
+
+// Tests that a PID which doesn't exist results in the process being noticed as
+// dead when we inspect /proc.
+TEST_F(RobustOwnershipTrackerTest, NoMatchingPID) {
+  SharedRobustOwnershipTracker shared_tracker;
+
+  shared_tracker.tracker().Acquire();
+  EXPECT_FALSE(shared_tracker.tracker().LoadRelaxed().OwnerIsDead());
+  EXPECT_FALSE(shared_tracker.tracker().OwnerIsDefinitelyAbsolutelyDead());
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wignored-attributes"
+  GetMutex(shared_tracker.tracker()).futex =
+      std::numeric_limits<aos_futex>::max() & FUTEX_TID_MASK;
+#pragma GCC diagnostic pop
+
+  // Since we're only pretending that the owner died (by changing the TID in the
+  // futex), we only notice that the owner is dead when spending the time
+  // walking through /proc.
+  EXPECT_FALSE(shared_tracker.tracker().LoadRelaxed().OwnerIsDead());
+  EXPECT_TRUE(shared_tracker.tracker().OwnerIsDefinitelyAbsolutelyDead());
+}
+
+// Tests that a mismatched start time results in the process being marked as
+// dead.
+TEST_F(RobustOwnershipTrackerTest, NoMatchingStartTime) {
+  SharedRobustOwnershipTracker shared_tracker;
+
+  shared_tracker.tracker().Acquire();
+  EXPECT_FALSE(shared_tracker.tracker().LoadRelaxed().OwnerIsDead());
+  EXPECT_FALSE(shared_tracker.tracker().OwnerIsDefinitelyAbsolutelyDead());
+
+  EXPECT_NE(GetStartTimeTicks(shared_tracker.tracker()), 0);
+  EXPECT_NE(GetStartTimeTicks(shared_tracker.tracker()),
+            RobustOwnershipTracker::kNoStartTimeTicks);
+  SetStartTimeTicks(shared_tracker.tracker(), 1);
+
+  // Since we're only pretending that the owner died (by changing the tracked
+  // start time ticks in the tracker), we only notice that the owner is dead
+  // when spending the time walking through /proc.
+  EXPECT_FALSE(shared_tracker.tracker().LoadRelaxed().OwnerIsDead());
+  EXPECT_TRUE(shared_tracker.tracker().OwnerIsDefinitelyAbsolutelyDead());
+}
+
+}  // namespace aos::ipc_lib::testing
diff --git a/aos/json_to_flatbuffer_test.cc b/aos/json_to_flatbuffer_test.cc
index d2b88f0..a145364 100644
--- a/aos/json_to_flatbuffer_test.cc
+++ b/aos/json_to_flatbuffer_test.cc
@@ -41,7 +41,11 @@
     printf("Back to string via TypeTable: %s\n", back_typetable.c_str());
     printf("Back to string via reflection: %s\n", back_reflection.c_str());
 
-    return back_typetable == out && back_reflection == out;
+    const bool as_expected = back_typetable == out && back_reflection == out;
+    if (!as_expected) {
+      printf("But expected: %s\n", out.c_str());
+    }
+    return as_expected;
   }
 };
 
@@ -230,9 +234,33 @@
   EXPECT_TRUE(JsonAndBack("{  }"));
 }
 
-// Tests that comments get stripped.
-TEST_F(JsonToFlatbufferTest, Comments) {
-  EXPECT_TRUE(JsonAndBack("{ /* foo */ \"vector_foo_double\": [ 9, 7, 1 ] }",
+// Tests that C style comments get stripped.
+TEST_F(JsonToFlatbufferTest, CStyleComments) {
+  EXPECT_TRUE(JsonAndBack(R"({
+  /* foo */
+  "vector_foo_double": [ 9, 7, 1 ] /* foo */
+} /* foo */)",
+                          "{ \"vector_foo_double\": [ 9.0, 7.0, 1.0 ] }"));
+}
+
+// Tests that C++ style comments get stripped.
+TEST_F(JsonToFlatbufferTest, CppStyleComments) {
+  EXPECT_TRUE(JsonAndBack(R"({
+  // foo
+  "vector_foo_double": [ 9, 7, 1 ] // foo
+} // foo)",
+                          "{ \"vector_foo_double\": [ 9.0, 7.0, 1.0 ] }"));
+}
+
+// Tests that mixed style comments get stripped.
+TEST_F(JsonToFlatbufferTest, MixedStyleComments) {
+  // Weird comments do not throw us off.
+  EXPECT_TRUE(JsonAndBack(R"({
+  // foo /* foo */
+  "vector_foo_double": [ 9, 7, 1 ] /* // foo */
+}
+// foo
+/* foo */)",
                           "{ \"vector_foo_double\": [ 9.0, 7.0, 1.0 ] }"));
 }
 
diff --git a/aos/json_tokenizer.cc b/aos/json_tokenizer.cc
index b3c6620..eab7fcc 100644
--- a/aos/json_tokenizer.cc
+++ b/aos/json_tokenizer.cc
@@ -23,6 +23,19 @@
         }
         ConsumeChar();
       }
+    } else if (Consume("//")) {
+      // C++ style comment.  Keep consuming chars until newline, or until the
+      // end of the file if this is the last line (no newline at end of file).
+      while (true) {
+        ConsumeChar();
+        if (AtEnd()) {
+          return;
+        }
+        if (Char() == '\n') {
+          ++linenumber_;
+          break;
+        }
+      }
     } else {
       // There is no fail.  Once we are out of whitespace (including 0 of it),
       // declare success.
diff --git a/aos/logging/logging.h b/aos/logging/logging.h
index c71e5c4..8d83037 100644
--- a/aos/logging/logging.h
+++ b/aos/logging/logging.h
@@ -51,7 +51,7 @@
 // It's currently using __PRETTY_FUNCTION__ because both GCC and Clang support
 // that and it gives nicer results in C++ than the standard __func__ (which
 // would also work).
-//#define LOG_CURRENT_FUNCTION __PRETTY_FUNCTION__
+// #define LOG_CURRENT_FUNCTION __PRETTY_FUNCTION__
 #define LOG_CURRENT_FUNCTION __func__
 
 #define LOG_SOURCENAME __FILE__
diff --git a/aos/network/BUILD b/aos/network/BUILD
index d1a79d0..e8dd75f 100644
--- a/aos/network/BUILD
+++ b/aos/network/BUILD
@@ -194,6 +194,7 @@
     tags = [
         # Fakeroot is required to enable "net.sctp.auth_enable".
         "requires-fakeroot",
+        "no-remote-exec",
     ],
     target_compatible_with = ["@platforms//cpu:x86_64"],
     deps = [
diff --git a/aos/network/message_bridge_client_lib.cc b/aos/network/message_bridge_client_lib.cc
index 19f3420..34f578e 100644
--- a/aos/network/message_bridge_client_lib.cc
+++ b/aos/network/message_bridge_client_lib.cc
@@ -62,7 +62,6 @@
                                            const Node *my_node,
                                            const Node *other_node) {
   std::vector<bool> stream_reply_with_timestamp;
-  int channel_index = 0;
   for (const Channel *channel : *config->channels()) {
     if (configuration::ChannelIsSendableOnNode(channel, other_node)) {
       const Connection *connection =
@@ -78,7 +77,6 @@
             configuration::ChannelMessageIsLoggedOnNode(channel, my_node));
       }
     }
-    ++channel_index;
   }
 
   return stream_reply_with_timestamp;
diff --git a/aos/network/multinode_timestamp_filter.cc b/aos/network/multinode_timestamp_filter.cc
index 85ee12c..b4ebef4 100644
--- a/aos/network/multinode_timestamp_filter.cc
+++ b/aos/network/multinode_timestamp_filter.cc
@@ -459,7 +459,8 @@
   return result;
 }
 
-Eigen::VectorXd NewtonSolver::Newton(const Problem::Derivatives &derivatives,
+Eigen::VectorXd NewtonSolver::Newton(const Eigen::Ref<const Eigen::VectorXd> y,
+                                     const Problem::Derivatives &derivatives,
                                      size_t iteration) {
   // https://www.cs.purdue.edu/homes/jhonorio/16spring-cs52000-equality.pdf
   // https://web.stanford.edu/~boyd/cvxbook/bv_cvxbook.pdf chapter 10 is good
@@ -479,6 +480,10 @@
   Eigen::VectorXd b = Eigen::VectorXd::Zero(a.rows());
   b.block(0, 0, derivatives.gradient.rows(), 1) = -derivatives.gradient;
   if (derivatives.Axmb.rows()) {
+    const Eigen::Ref<const Eigen::VectorXd> v =
+        y.block(derivatives.hessian.rows(), 0, derivatives.A.rows(), 1);
+    b.block(0, 0, derivatives.gradient.rows(), 1) -=
+        derivatives.A.transpose() * v;
     b.block(derivatives.gradient.rows(), 0, derivatives.Axmb.rows(), 1) =
         -derivatives.Axmb;
   }
@@ -508,6 +513,34 @@
   return step;
 }
 
+double NewtonSolver::RSquaredUnconstrained(
+    const Problem::Derivatives &derivatives,
+    Eigen::Ref<const Eigen::VectorXd> y, Eigen::Ref<const Eigen::VectorXd> step,
+    double t) {
+  // See https://web.stanford.edu/~boyd/cvxbook/bv_cvxbook.pdf section 10.3.1
+  //
+  // Note: all the prints are transposed so they fit on one line instead of
+  // having a ton of massive column vectors being printed all the time.
+  SOLVE_VLOG(my_solve_number_, 2)
+      << "    r_dual: " << std::setprecision(12) << std::fixed
+      << std::setfill(' ')
+      << derivatives.gradient.transpose().format(kHeavyFormat) << " + "
+      << ((y.block(derivatives.gradient.rows(), 0, 1, 1) +
+           t * step.block(derivatives.gradient.rows(), 0, 1, 1))
+              .transpose() *
+          derivatives.A)
+             .format(kHeavyFormat);
+  SOLVE_VLOG(my_solve_number_, 2)
+      << "    r_primal: " << std::setprecision(12) << std::fixed
+      << std::setfill(' ') << derivatives.Axmb.transpose().format(kHeavyFormat);
+  return (derivatives.gradient +
+          derivatives.A.transpose() *
+              (y.block(derivatives.gradient.rows(), 0, 1, 1) +
+               t * step.block(derivatives.gradient.rows(), 0, 1, 1)))
+             .squaredNorm() +
+         derivatives.Axmb.squaredNorm();
+}
+
 std::tuple<Eigen::VectorXd, size_t, size_t> NewtonSolver::SolveNewton(
     Problem *problem, const size_t max_iterations) {
   SOLVE_VLOG(my_solve_number_, 2)
@@ -515,25 +548,35 @@
 
   problem->Prepare(my_solve_number_);
 
-  Eigen::VectorXd data = Eigen::VectorXd::Zero(problem->states());
+  Eigen::VectorXd y = Eigen::VectorXd::Zero(problem->states() + 1);
 
   size_t iteration = 0;
   size_t solution_node;
+
+  // The derivatives and squared norm of r(y).
+  Problem::Derivatives derivatives = problem->ComputeDerivatives(
+      my_solve_number_, y, false, false, absl::Span<const size_t>());
+  double r_orig_squared = RSquaredUnconstrained(derivatives, y, y, 0.0);
   while (true) {
-    const Problem::Derivatives derivatives = problem->ComputeDerivatives(
-        my_solve_number_, data, false, false, absl::Span<const size_t>());
-    const Eigen::VectorXd step = Newton(derivatives, iteration);
+    SOLVE_VLOG(my_solve_number_, 1) << "Starting iteration " << iteration;
+
+    const Eigen::VectorXd step = Newton(y, derivatives, iteration);
     solution_node = derivatives.solution_node;
 
+    SOLVE_VLOG(my_solve_number_, 1)
+        << "  y(" << iteration << ") is " << y.transpose().format(kHeavyFormat);
+    PrintDerivatives(derivatives, y, "", 3);
     SOLVE_VLOG(my_solve_number_, 2)
-        << "Step " << iteration << " -> " << std::setprecision(12) << std::fixed
-        << std::setfill(' ') << step.transpose().format(kHeavyFormat);
+        << "  initial step(" << iteration << ") -> " << std::setprecision(12)
+        << std::fixed << std::setfill(' ')
+        << step.transpose().format(kHeavyFormat);
 
     // We now have a search direction.  Line search and go.
+    double t = 1.0;
 
-    // We got there if the max step is small (this is strongly correlated to the
-    // gradient since the Hessian is constant), and our solution node's time is
-    // also close.
+    // We got there if the max step is small (this is strongly correlated to
+    // the gradient since the Hessian is constant), and our solution node's
+    // time is also close.
 
     // Grad is < epsilon.
     // Dual is < epsilon too.
@@ -543,7 +586,44 @@
       break;
     }
 
-    data += step.block(0, 0, problem->states(), 1);
+    SOLVE_VLOG(my_solve_number_, 2)
+        << "  Not close enough, "
+        << step.block(0, 0, problem->states(), 1).lpNorm<Eigen::Infinity>()
+        << " >= 1e-4 || " << derivatives.Axmb.squaredNorm() << " >= 1e-8";
+
+    // Now, do line search.
+    while (true) {
+      Problem::Derivatives new_derivatives =
+          problem->ComputeDerivatives(my_solve_number_, y + t * step, false,
+                                      false, absl::Span<const size_t>());
+      const double r_squared =
+          RSquaredUnconstrained(new_derivatives, y, step, t);
+      SOLVE_VLOG(my_solve_number_, 2)
+          << std::setprecision(12) << std::setfill(' ') << "   |r| < |r+1| "
+          << r_orig_squared << " < " << r_squared << " for step size " << t;
+      // See Boyd, section 10.3.2, algorithm 10.2
+      if (r_squared <=
+              std::pow(1.0 - kUnconstrainedAlpha * t, 2.0) * r_orig_squared ||
+          t < kLineSearchStopThreshold) {
+        // This is modified because we have seen cases where the way we handle
+        // equality constraints sometimes ends up increasing the cost slightly.
+        // We are better off taking a small step and trying again than shrinking
+        // our step forever.
+        //
+        // Save the derivatives and norm computed for the next iteration to save
+        // CPU.
+        derivatives = std::move(new_derivatives);
+        r_orig_squared = r_squared;
+        SOLVE_VLOG(my_solve_number_, 3)
+            << "  Line search terminated with a step of " << t;
+        break;
+      } else {
+        CHECK_NE(t, 0.0) << ": Failed on solve " << my_solve_number_;
+      }
+      t *= kBeta;
+    }
+
+    y += t * step;
 
     ++iteration;
 
@@ -555,8 +635,8 @@
     // true for the first solution.  Because we control the solver, as we
     // determine that the double is getting too big, we can move that
     // information to the int64 base clock.  Threshold this to not be *too* big
-    // since it makes it hard to debug as the data keeps jumping around.
-    problem->Update(my_solve_number_, data);
+    // since it makes it hard to debug as y keeps jumping around.
+    problem->Update(my_solve_number_, y);
 
     // And finally, don't let us iterate forever.  If it isn't converging,
     // report back.
@@ -571,7 +651,8 @@
 
   SOLVE_VLOG(my_solve_number_, 1)
       << "Took " << iteration << " iterations to solve.";
-  return std::make_tuple(std::move(data), solution_node, iteration);
+  return std::make_tuple(y.block(0, 0, problem->states(), 1), solution_node,
+                         iteration);
 }
 
 Problem::Derivatives NewtonSolver::AddConstraintSlackVariable(
@@ -693,6 +774,10 @@
   const Eigen::Ref<const Eigen::VectorXd> lambda =
       y.block(x.rows(), 0, derivatives.f.rows(), 1);
 
+  CHECK_LT(0, lambda.rows())
+      << ": You are calling the unconstrained Newton solver without inequality "
+         "constraints. This is not supported.";
+
   const Eigen::Ref<const Eigen::VectorXd> v =
       y.block(x.rows() + lambda.rows(), 0, derivatives.A.rows(), 1);
 
@@ -770,25 +855,32 @@
     const Eigen::Ref<const Eigen::VectorXd> v =
         y.block(x.rows() + lambda.rows(), 0, derivatives.A.rows(), 1);
     SOLVE_VLOG(my_solve_number_, verbosity)
-        << "   " << prefix << "x: " << x.transpose().format(heavy);
+        << std::setprecision(12) << "   " << prefix
+        << "x: " << x.transpose().format(heavy);
     SOLVE_VLOG(my_solve_number_, verbosity)
-        << "   " << prefix << "lambda: " << lambda.transpose().format(heavy);
+        << std::setprecision(12) << "   " << prefix
+        << "lambda: " << lambda.transpose().format(heavy);
     SOLVE_VLOG(my_solve_number_, verbosity)
-        << "   " << prefix << "v: " << v.transpose().format(heavy);
+        << std::setprecision(12) << "   " << prefix
+        << "v: " << v.transpose().format(heavy);
     SOLVE_VLOG(my_solve_number_, verbosity)
-        << "  " << prefix
+        << std::setprecision(12) << "  " << prefix
         << "hessian:     " << derivatives.hessian.format(heavy);
     SOLVE_VLOG(my_solve_number_, verbosity)
-        << "  " << prefix
+        << std::setprecision(12) << "  " << prefix
         << "gradient:    " << derivatives.gradient.format(heavy);
     SOLVE_VLOG(my_solve_number_, verbosity)
-        << "  " << prefix << "A:           " << derivatives.A.format(heavy);
+        << std::setprecision(12) << "  " << prefix
+        << "A:           " << derivatives.A.format(heavy);
     SOLVE_VLOG(my_solve_number_, verbosity)
-        << "  " << prefix << "Ax-b:        " << derivatives.Axmb.format(heavy);
+        << std::setprecision(12) << "  " << prefix
+        << "Ax-b:        " << derivatives.Axmb.format(heavy);
     SOLVE_VLOG(my_solve_number_, verbosity)
-        << "  " << prefix << "f:           " << derivatives.f.format(heavy);
+        << std::setprecision(12) << "  " << prefix
+        << "f:           " << derivatives.f.format(heavy);
     SOLVE_VLOG(my_solve_number_, verbosity)
-        << "  " << prefix << "df:          " << derivatives.df.format(heavy);
+        << std::setprecision(12) << "  " << prefix
+        << "df:          " << derivatives.df.format(heavy);
   }
 }
 
@@ -1022,6 +1114,13 @@
 
     PrintDerivatives(derivatives, y, "", 1);
 
+    if (derivatives.f.rows() == 0) {
+      LOG(ERROR) << "No inequality constraints provided in constrained solver. "
+                    "This suggests an inconsistency in the solver code, please "
+                    "investigate.";
+      return std::nullopt;
+    }
+
     // Figure out our descent direction.
     Eigen::VectorXd dy;
     Eigen::VectorXd rt_orig;
@@ -1275,7 +1374,8 @@
       }
 
       SOLVE_VLOG(solve_number, 2)
-          << "    live " << points_[j].time << " vs solution "
+          << std::setprecision(12) << std::fixed << "    live "
+          << points_[j].time << " vs solution "
           << base_clock_[j].time +
                  std::chrono::nanoseconds(static_cast<int64_t>(
                      std::round(data(NodeToFullSolutionIndex(j)))))
@@ -1720,7 +1820,6 @@
   }
 
   if (filter_fps_.size() != 0 && !destructor) {
-    size_t node_a_index = 0;
     for (const auto &filters : filters_per_node_) {
       for (const auto &filter : filters) {
         while (true) {
@@ -1732,7 +1831,6 @@
           WriteFilter(filter.filter, *sample);
         }
       }
-      ++node_a_index;
     }
   }
 
diff --git a/aos/network/multinode_timestamp_filter.h b/aos/network/multinode_timestamp_filter.h
index 959fe85..828c489 100644
--- a/aos/network/multinode_timestamp_filter.h
+++ b/aos/network/multinode_timestamp_filter.h
@@ -84,6 +84,10 @@
   // Ratio to let the cost increase when line searching.  A small increase is
   // fine since we aren't perfectly convex.
   static constexpr double kAlpha = -0.15;
+  // Ratio to require the cost to decrease when line searching.
+  static constexpr double kUnconstrainedAlpha = 0.5;
+  // Unconstrained min line search distance to guarantee forward progress.
+  static constexpr double kLineSearchStopThreshold = 0.4;
   // Line search step parameter.
   static constexpr double kBeta = 0.5;
   static constexpr double kMu = 2.0;
@@ -138,6 +142,14 @@
   Eigen::VectorXd Rt(const Problem::Derivatives &derivatives,
                      Eigen::Ref<const Eigen::VectorXd> y, double t);
 
+  // Returns the squared norm of r for the unconstrained problem.
+  // (10.3.1 in Convex Optimization,
+  //  https://web.stanford.edu/~boyd/cvxbook/bv_cvxbook.pdf)
+  double RSquaredUnconstrained(const Problem::Derivatives &derivatives,
+                               Eigen::Ref<const Eigen::VectorXd> y,
+                               Eigen::Ref<const Eigen::VectorXd> step,
+                               double t);
+
   // Returns the constrained newtons step, t_inverse, and Rt.
   std::tuple<Eigen::VectorXd, double, Eigen::VectorXd> ConstrainedNewton(
       const Eigen::Ref<const Eigen::VectorXd> y,
@@ -152,7 +164,8 @@
   // used for the equality constraint.  The last term is the scalar on the
   // equality constraint.  This needs to be removed from the solution to get the
   // actual newton step.
-  Eigen::VectorXd Newton(const Problem::Derivatives &derivatives,
+  Eigen::VectorXd Newton(const Eigen::Ref<const Eigen::VectorXd> y,
+                         const Problem::Derivatives &derivatives,
                          size_t iteration);
 
   // The solve number for this instance of the problem.
diff --git a/aos/network/rawrtc.h b/aos/network/rawrtc.h
index f43cc5e..7908124 100644
--- a/aos/network/rawrtc.h
+++ b/aos/network/rawrtc.h
@@ -170,9 +170,7 @@
   void Open();
 
   // Returns the connection if Open has been called.
-  struct rawrtc_peer_connection *connection() {
-    return connection_;
-  }
+  struct rawrtc_peer_connection *connection() { return connection_; }
 
  private:
   // Trampolines from C -> C++.
diff --git a/aos/network/sctp_client.h b/aos/network/sctp_client.h
index 06f6b15..9b9265e 100644
--- a/aos/network/sctp_client.h
+++ b/aos/network/sctp_client.h
@@ -44,9 +44,7 @@
   void SetPriorityScheduler(sctp_assoc_t assoc_id);
 
   // Remote to send to.
-  struct sockaddr_storage sockaddr_remote() const {
-    return sockaddr_remote_;
-  }
+  struct sockaddr_storage sockaddr_remote() const { return sockaddr_remote_; }
 
   void LogSctpStatus(sctp_assoc_t assoc_id);
 
diff --git a/aos/network/sctp_lib.cc b/aos/network/sctp_lib.cc
index c3c6e23..0922d6b 100644
--- a/aos/network/sctp_lib.cc
+++ b/aos/network/sctp_lib.cc
@@ -715,8 +715,11 @@
       << "SCTP Authentication key requested, but authentication isn't "
          "enabled... Use `sysctl -w net.sctp.auth_enable=1` to enable";
   // Set up the key with id `1`.
-  std::unique_ptr<sctp_authkey> authkey(
-      (sctp_authkey *)malloc(sizeof(sctp_authkey) + auth_key.size()));
+  // NOTE: `sctp_authkey` is a variable-sized struct which is why it needs
+  // to be heap allocated. Regardless, this object doesn't have to persist past
+  // the `setsockopt` call below.
+  std::unique_ptr<sctp_authkey> authkey(static_cast<sctp_authkey *>(
+      ::operator new(sizeof(sctp_authkey) + auth_key.size())));
 
   authkey->sca_keynumber = 1;
   authkey->sca_keylength = auth_key.size();
diff --git a/aos/network/sctp_test.cc b/aos/network/sctp_test.cc
index 8e332e4..bce5c19 100644
--- a/aos/network/sctp_test.cc
+++ b/aos/network/sctp_test.cc
@@ -11,6 +11,7 @@
 #include "aos/network/sctp_client.h"
 #include "aos/network/sctp_lib.h"
 #include "aos/network/sctp_server.h"
+#include "sctp_lib.h"
 
 DECLARE_bool(disable_ipv6);
 
@@ -28,8 +29,10 @@
 namespace {
 void EnableSctpAuthIfAvailable() {
 #if HAS_SCTP_AUTH
-  CHECK(system("/usr/sbin/sysctl net.sctp.auth_enable=1 || /sbin/sysctl "
-               "net.sctp.auth_enable=1") == 0)
+  // Open an SCTP socket to bring the kernel SCTP module
+  SctpServer server(1, "localhost");
+  CHECK(system("/usr/sbin/sysctl net.sctp.auth_enable=1 || "
+               "/sbin/sysctl net.sctp.auth_enable=1") == 0)
       << "Couldn't enable sctp authentication.";
 #endif
 }
diff --git a/aos/network/timestamp_filter.cc b/aos/network/timestamp_filter.cc
index 4a29dfc..9736679 100644
--- a/aos/network/timestamp_filter.cc
+++ b/aos/network/timestamp_filter.cc
@@ -531,6 +531,13 @@
   if (!use_other) {
     return std::make_pair(pointer, std::make_pair(t0, t1));
   }
+
+  // The invariant of pointer is that other_points is bounded by t0, t1. Confirm
+  // it before we return things depending on it since it is easy.
+  CHECK_GT(std::get<0>(pointer.other_points_[0].second), std::get<0>(t0));
+  CHECK_LT(std::get<0>(
+               pointer.other_points_[pointer.other_points_.size() - 1].second),
+           std::get<0>(t1));
   // We have 2 timestamps bookending everything, and a list of points in the
   // middle.
   //
@@ -660,11 +667,14 @@
 
   const size_t index = std::distance(timestamps_.begin(), it);
 
+  // Now, update the pointer cache.
   pointer.index_ = index - 1;
   t0 = timestamp(index - 1);
   pointer.t0_ = t0;
   t1 = timestamp(index);
   pointer.t1_ = t1;
+  // And clear out the point cache since the points changed.
+  pointer.other_points_.clear();
 
   if (other != nullptr && !other->timestamps_empty()) {
     // Ok, we now need to find all points within our range in the matched
@@ -688,8 +698,6 @@
 
       if (std::get<0>(*other_t0_it) + std::get<1>(*other_t0_it) <
           std::get<0>(pointer.t1_)) {
-        pointer.other_points_.clear();
-
         // Now, we've got a range.  [other_t0_it, other_t1_it).
         for (auto other_it = other_t0_it; other_it != other_t1_it; ++other_it) {
           const std::tuple<monotonic_clock::time_point,
diff --git a/aos/network/timestamp_filter.h b/aos/network/timestamp_filter.h
index 8b597bd..b1aff86 100644
--- a/aos/network/timestamp_filter.h
+++ b/aos/network/timestamp_filter.h
@@ -306,7 +306,12 @@
     std::tuple<monotonic_clock::time_point, std::chrono::nanoseconds> t1_ =
         std::make_tuple(monotonic_clock::min_time, std::chrono::nanoseconds(0));
 
-    // List of points and their associated times going the other way.
+    // List of points and their associated times going the other way.  This lets
+    // us handle when there is a large gap in timestamps, but time drifts in
+    // such a way to create an impossible situation when the points are
+    // connected by straight lines.
+    //
+    // These need to be between t0 and t1.
     std::vector<std::pair<size_t, std::tuple<monotonic_clock::time_point,
                                              std::chrono::nanoseconds>>>
         other_points_;
@@ -869,6 +874,14 @@
     return result;
   }
 
+  // Interpolates between two points for time ta using the provided pointer, t0,
+  // and t1.  If use_other is true, then we use other_points_ inside pointer to
+  // also interpolate with.  This lets us handle cases where we have
+  // observations only going one way, but the filter lines cross because time
+  // drifted.
+  //
+  // Returns the 2 points which define the line we should interpolate along, and
+  // an updated pointer caching what points were actually used.
   static std::pair<
       Pointer,
       std::pair<
diff --git a/aos/starter/BUILD b/aos/starter/BUILD
index 732cfe3..1873166 100644
--- a/aos/starter/BUILD
+++ b/aos/starter/BUILD
@@ -93,6 +93,24 @@
     ],
 )
 
+# Similar to subprocess_test, but here are all the tests that are not flaky.
+cc_test(
+    name = "subprocess_reliable_test",
+    srcs = ["subprocess_reliable_test.cc"],
+    data = [
+        "//aos/events:pingpong_config",
+    ],
+    # The roborio compiler doesn't support <filesystem>.
+    target_compatible_with = ["@platforms//os:linux"],
+    deps = [
+        ":subprocess",
+        "//aos/events:shm_event_loop",
+        "//aos/testing:googletest",
+        "//aos/testing:path",
+        "//aos/testing:tmpdir",
+    ],
+)
+
 cc_test(
     name = "starter_test",
     srcs = ["starter_test.cc"],
diff --git a/aos/starter/subprocess.cc b/aos/starter/subprocess.cc
index 7b5fce6..4d21c61 100644
--- a/aos/starter/subprocess.cc
+++ b/aos/starter/subprocess.cc
@@ -12,6 +12,25 @@
 
 namespace aos::starter {
 
+// Blocks all signals while an instance of this class is in scope.
+class ScopedCompleteSignalBlocker {
+ public:
+  ScopedCompleteSignalBlocker() {
+    sigset_t mask;
+    sigfillset(&mask);
+    // Remember the current mask.
+    PCHECK(sigprocmask(SIG_SETMASK, &mask, &old_mask_) == 0);
+  }
+
+  ~ScopedCompleteSignalBlocker() {
+    // Restore the remembered mask.
+    PCHECK(sigprocmask(SIG_SETMASK, &old_mask_, nullptr) == 0);
+  }
+
+ private:
+  sigset_t old_mask_;
+};
+
 // RAII class to become root and restore back to the original user and group
 // afterwards.
 class Sudo {
@@ -141,11 +160,13 @@
       restart_timer_(event_loop_->AddTimer([this] { DoStart(); })),
       stop_timer_(event_loop_->AddTimer([this] {
         if (kill(pid_, SIGKILL) == 0) {
-          LOG_IF(WARNING, quiet_flag_ == QuietLogging::kNo)
+          LOG_IF(WARNING, quiet_flag_ == QuietLogging::kNo ||
+                              quiet_flag_ == QuietLogging::kNotForDebugging)
               << "Failed to stop, sending SIGKILL to '" << name_
               << "' pid: " << pid_;
         } else {
-          PLOG_IF(WARNING, quiet_flag_ == QuietLogging::kNo)
+          PLOG_IF(WARNING, quiet_flag_ == QuietLogging::kNo ||
+                               quiet_flag_ == QuietLogging::kNotForDebugging)
               << "Failed to send SIGKILL to '" << name_ << "' pid: " << pid_;
           stop_timer_->Schedule(event_loop_->monotonic_now() +
                                 std::chrono::seconds(1));
@@ -209,34 +230,56 @@
   pipe_timer_->Schedule(event_loop_->monotonic_now(),
                         std::chrono::milliseconds(100));
 
-  const pid_t pid = fork();
+  {
+    // Block all signals during the fork() call. Together with the default
+    // signal handler restoration below, This prevents signal handlers from
+    // getting called in the child and accidentally affecting the parent. In
+    // particular, the exit handler for shm_event_loop could be called here if
+    // we don't exec() quickly enough.
+    ScopedCompleteSignalBlocker signal_blocker;
 
-  if (pid != 0) {
-    if (pid == -1) {
-      PLOG_IF(WARNING, quiet_flag_ == QuietLogging::kNo)
-          << "Failed to fork '" << name_ << "'";
-      stop_reason_ = aos::starter::LastStopReason::FORK_ERR;
-      status_ = aos::starter::State::STOPPED;
-    } else {
-      pid_ = pid;
-      id_ = next_id_++;
-      start_time_ = event_loop_->monotonic_now();
-      status_ = aos::starter::State::STARTING;
-      latest_timing_report_version_.reset();
-      LOG_IF(INFO, quiet_flag_ == QuietLogging::kNo)
-          << "Starting '" << name_ << "' pid " << pid_;
+    const pid_t pid = fork();
 
-      // Set up timer which moves application to RUNNING state if it is still
-      // alive in 1 second.
-      start_timer_->Schedule(event_loop_->monotonic_now() +
-                             std::chrono::seconds(1));
-      // Since we are the parent process, clear our write-side of all the pipes.
-      status_pipes_.write.reset();
-      stdout_pipes_.write.reset();
-      stderr_pipes_.write.reset();
+    if (pid != 0) {
+      if (pid == -1) {
+        PLOG_IF(WARNING, quiet_flag_ == QuietLogging::kNo ||
+                             quiet_flag_ == QuietLogging::kNotForDebugging)
+            << "Failed to fork '" << name_ << "'";
+        stop_reason_ = aos::starter::LastStopReason::FORK_ERR;
+        status_ = aos::starter::State::STOPPED;
+      } else {
+        pid_ = pid;
+        id_ = next_id_++;
+        start_time_ = event_loop_->monotonic_now();
+        status_ = aos::starter::State::STARTING;
+        latest_timing_report_version_.reset();
+        LOG_IF(INFO, quiet_flag_ == QuietLogging::kNo)
+            << "Starting '" << name_ << "' pid " << pid_;
+
+        // Set up timer which moves application to RUNNING state if it is still
+        // alive in 1 second.
+        start_timer_->Schedule(event_loop_->monotonic_now() +
+                               std::chrono::seconds(1));
+        // Since we are the parent process, clear our write-side of all the
+        // pipes.
+        status_pipes_.write.reset();
+        stdout_pipes_.write.reset();
+        stderr_pipes_.write.reset();
+      }
+      OnChange();
+      return;
     }
-    OnChange();
-    return;
+
+    // Clear any signal handlers so that they don't accidentally interfere with
+    // the parent process. Is there a better way to iterate over all the
+    // signals? Right now we're just dealing with the most common ones.
+    for (int signal : {SIGINT, SIGHUP, SIGTERM}) {
+      struct sigaction action;
+      sigemptyset(&action.sa_mask);
+      action.sa_flags = 0;
+      action.sa_handler = SIG_DFL;
+      PCHECK(sigaction(signal, &action, nullptr) == 0);
+    }
   }
 
   if (memory_cgroup_) {
@@ -323,7 +366,8 @@
   // If we got here, something went wrong
   status_pipes_.write->Write(
       static_cast<uint32_t>(aos::starter::LastStopReason::EXECV_ERR));
-  PLOG_IF(WARNING, quiet_flag_ == QuietLogging::kNo)
+  PLOG_IF(WARNING, quiet_flag_ == QuietLogging::kNo ||
+                       quiet_flag_ == QuietLogging::kNotForDebugging)
       << "Could not execute " << name_ << " (" << path_ << ')';
 
   _exit(EXIT_FAILURE);
@@ -371,12 +415,18 @@
   switch (status_) {
     case aos::starter::State::STARTING:
     case aos::starter::State::RUNNING: {
-      LOG_IF(INFO, quiet_flag_ == QuietLogging::kNo)
+      LOG_IF(INFO, quiet_flag_ == QuietLogging::kNo ||
+                       quiet_flag_ == QuietLogging::kNotForDebugging)
           << "Stopping '" << name_ << "' pid: " << pid_ << " with signal "
           << SIGINT;
       status_ = aos::starter::State::STOPPING;
 
-      kill(pid_, SIGINT);
+      if (kill(pid_, SIGINT) != 0) {
+        PLOG_IF(INFO, quiet_flag_ == QuietLogging::kNo ||
+                          quiet_flag_ == QuietLogging::kNotForDebugging)
+            << "Failed to send signal " << SIGINT << " to '" << name_
+            << "' pid: " << pid_;
+      }
 
       // Watchdog timer to SIGKILL application if it is still running 1 second
       // after SIGINT
@@ -591,7 +641,8 @@
             << "Application '" << name_ << "' pid " << pid_
             << " exited with status " << exit_code_.value();
       } else {
-        LOG_IF(WARNING, quiet_flag_ == QuietLogging::kNo)
+        LOG_IF(WARNING, quiet_flag_ == QuietLogging::kNo ||
+                            quiet_flag_ == QuietLogging::kNotForDebugging)
             << "Failed to start '" << name_ << "' on pid " << pid_
             << " : Exited with status " << exit_code_.value();
       }
@@ -609,7 +660,8 @@
             << "Application '" << name_ << "' pid " << pid_
             << " exited with status " << exit_code_.value();
       } else {
-        if (quiet_flag_ == QuietLogging::kNo) {
+        if (quiet_flag_ == QuietLogging::kNo ||
+            quiet_flag_ == QuietLogging::kNotForDebugging) {
           std::string version_string =
               latest_timing_report_version_.has_value()
                   ? absl::StrCat("'", latest_timing_report_version_.value(),
diff --git a/aos/starter/subprocess.h b/aos/starter/subprocess.h
index 784c544..9630e00 100644
--- a/aos/starter/subprocess.h
+++ b/aos/starter/subprocess.h
@@ -68,7 +68,14 @@
 // automatically.
 class Application {
  public:
-  enum class QuietLogging { kYes, kNo };
+  enum class QuietLogging {
+    kYes,
+    kNo,
+    // For debugging child processes not behaving as expected. When a child
+    // experiences an event such as exiting with an error code or dying to due a
+    // signal, this option will cause a log statement to be printed.
+    kNotForDebugging,
+  };
   Application(const aos::Application *application, aos::EventLoop *event_loop,
               std::function<void()> on_change,
               QuietLogging quiet_flag = QuietLogging::kNo);
@@ -127,6 +134,8 @@
   bool autorestart() const { return autorestart_; }
   void set_autorestart(bool autorestart) { autorestart_ = autorestart; }
 
+  LastStopReason stop_reason() const { return stop_reason_; }
+
   const std::string &GetStdout();
   const std::string &GetStderr();
   std::optional<int> exit_code() const { return exit_code_; }
diff --git a/aos/starter/subprocess_reliable_test.cc b/aos/starter/subprocess_reliable_test.cc
new file mode 100644
index 0000000..0460bc3
--- /dev/null
+++ b/aos/starter/subprocess_reliable_test.cc
@@ -0,0 +1,127 @@
+#include <signal.h>
+#include <sys/types.h>
+
+#include <filesystem>
+
+#include "gtest/gtest.h"
+
+#include "aos/events/shm_event_loop.h"
+#include "aos/starter/subprocess.h"
+#include "aos/testing/path.h"
+#include "aos/testing/tmpdir.h"
+#include "aos/util/file.h"
+
+namespace aos::starter::testing {
+
+namespace {
+void Wait(pid_t pid) {
+  int status;
+  if (waitpid(pid, &status, 0) != pid) {
+    if (errno != ECHILD) {
+      PLOG(ERROR) << "Failed to wait for PID " << pid << ": " << status;
+      FAIL();
+    }
+  }
+  LOG(INFO) << "Succesfully waited for PID " << pid;
+}
+
+}  // namespace
+
+// Validates that killing a child process right after startup doesn't have any
+// unexpected consequences. The child process should exit even if it hasn't
+// `exec()`d yet.
+TEST(SubprocessTest, KillDuringStartup) {
+  const std::string config_file =
+      ::aos::testing::ArtifactPath("aos/events/pingpong_config.json");
+  aos::FlatbufferDetachedBuffer<aos::Configuration> config =
+      aos::configuration::ReadConfig(config_file);
+  aos::ShmEventLoop event_loop(&config.message());
+
+  // Run an application that takes a really long time to exit. The exact details
+  // here don't matter. We just need to to survive long enough until we can call
+  // Terminate() below.
+  auto application =
+      std::make_unique<Application>("sleep", "sleep", &event_loop, []() {});
+  application->set_args({"100"});
+
+  // Track whether we exit via our own timer callback. We don't want to exit
+  // because of any strange interactions with the child process.
+  bool exited_as_expected = false;
+
+  // Here's the sequence of events that we expect to see:
+  // 1. Start child process.
+  // 2. Stop child process (via `Terminate()`).
+  // 3. Wait 1 second.
+  // 4. Set `exited_as_expected` to `true`.
+  // 5. Exit the event loop.
+  //
+  // At the end, if `exited_as_expected` is `false`, then something unexpected
+  // happened and we failed the test here.
+  aos::TimerHandler *shutdown_timer = event_loop.AddTimer([&]() {
+    exited_as_expected = true;
+    event_loop.Exit();
+  });
+  aos::TimerHandler *trigger_timer = event_loop.AddTimer([&]() {
+    application->Start();
+    application->Terminate();
+    shutdown_timer->Schedule(event_loop.monotonic_now() +
+                             std::chrono::seconds(1));
+  });
+  trigger_timer->Schedule(event_loop.monotonic_now());
+  event_loop.Run();
+  application->Stop();
+  Wait(application->get_pid());
+
+  EXPECT_TRUE(exited_as_expected) << "It looks like we killed ourselves.";
+}
+
+// Validates that the code in subprocess.cc doesn't accidentally block signals
+// in the child process.
+TEST(SubprocessTest, CanKillAfterStartup) {
+  const std::string config_file =
+      ::aos::testing::ArtifactPath("aos/events/pingpong_config.json");
+  aos::FlatbufferDetachedBuffer<aos::Configuration> config =
+      aos::configuration::ReadConfig(config_file);
+  aos::ShmEventLoop event_loop(&config.message());
+
+  // We create a directory in which we create some files so this test here and
+  // the subsequently created child process can "signal" one another. We roughly
+  // expect the following sequence of events:
+  // 1. Start the child process.
+  // 2. Test waits for "startup" file to be created by child.
+  // 3. Child process creates the "startup" file.
+  // 4. Test sees "startup" file being created, sends SIGTERM to child.
+  // 5. Child sees SIGTERM, creates "shutdown" file, and exits.
+  // 6. Test waits for child to exit.
+  // 7. Test validates that the "shutdown" file was created by the child.
+  auto signal_dir = std::filesystem::path(aos::testing::TestTmpDir()) /
+                    "startup_file_signals";
+  ASSERT_TRUE(std::filesystem::create_directory(signal_dir));
+  auto startup_signal_file = signal_dir / "startup";
+  auto shutdown_signal_file = signal_dir / "shutdown";
+
+  auto application = std::make_unique<Application>("/bin/bash", "/bin/bash",
+                                                   &event_loop, []() {});
+  application->set_args(
+      {"-c", absl::StrCat("cleanup() { touch ", shutdown_signal_file.string(),
+                          "; exit 0; }; trap cleanup SIGTERM; touch ",
+                          startup_signal_file.string(),
+                          "; while true; do sleep 0.1; done;")});
+
+  // Wait for the child process to create the "startup" file.
+  ASSERT_FALSE(std::filesystem::exists(startup_signal_file));
+  application->Start();
+  while (!std::filesystem::exists(startup_signal_file)) {
+    std::this_thread::sleep_for(std::chrono::milliseconds(50));
+  }
+  ASSERT_FALSE(std::filesystem::exists(shutdown_signal_file));
+
+  // Manually kill the application here. The Stop() and Terminate() helpers
+  // trigger some timeout behaviour that interferes with the test here. This
+  // should cause the child to exit and create the "shutdown" file.
+  PCHECK(kill(application->get_pid(), SIGTERM) == 0);
+  Wait(application->get_pid());
+  ASSERT_TRUE(std::filesystem::exists(shutdown_signal_file));
+}
+
+}  // namespace aos::starter::testing
diff --git a/aos/util/config_validator_lib.cc b/aos/util/config_validator_lib.cc
index 0f90ed6..e388bb7 100644
--- a/aos/util/config_validator_lib.cc
+++ b/aos/util/config_validator_lib.cc
@@ -177,6 +177,13 @@
   const std::string log_path = aos::testing::TestTmpDir() + "/logs/";
   for (const bool send_data_on_channels : {false, true}) {
     SCOPED_TRACE(send_data_on_channels);
+    // Single nodes (multi-nodes with node count = 1) will not produce readable
+    // logs in the absense of data.
+    if (!send_data_on_channels && (configuration::NodesCount(config) == 1u)) {
+      continue;
+    }
+    // Send timing report when we are sending data.
+    const bool do_skip_timing_report = !send_data_on_channels;
     for (const LoggerNodeSetValidation *logger_set :
          *validation_config->logging()->logger_sets()) {
       SCOPED_TRACE(aos::FlatbufferToJson(logger_set));
@@ -187,9 +194,11 @@
         for (const auto &node : *logger_set->loggers()) {
           logger_nodes.push_back(node->str());
         }
-        loggers = MakeLoggersForNodes(&factory, logger_nodes, log_path);
+        loggers = MakeLoggersForNodes(&factory, logger_nodes, log_path,
+                                      do_skip_timing_report);
       } else {
-        loggers = MakeLoggersForAllNodes(&factory, log_path);
+        loggers =
+            MakeLoggersForAllNodes(&factory, log_path, do_skip_timing_report);
       }
 
       std::vector<std::unique_ptr<EventLoop>> test_loops;
diff --git a/aos/util/simulation_logger.cc b/aos/util/simulation_logger.cc
index 24691ef..513fa65 100644
--- a/aos/util/simulation_logger.cc
+++ b/aos/util/simulation_logger.cc
@@ -4,13 +4,16 @@
 
 namespace aos::util {
 LoggerState::LoggerState(aos::SimulatedEventLoopFactory *factory,
-                         const aos::Node *node, std::string_view output_folder)
+                         const aos::Node *node, std::string_view output_folder,
+                         bool do_skip_timing_report)
     : event_loop_(factory->MakeEventLoop("logger", node)),
       namer_(std::make_unique<aos::logger::MultiNodeFilesLogNamer>(
           absl::StrCat(output_folder, "/", logger::MaybeNodeName(node), "/"),
           event_loop_.get())),
       logger_(std::make_unique<aos::logger::Logger>(event_loop_.get())) {
-  event_loop_->SkipTimingReport();
+  if (do_skip_timing_report) {
+    event_loop_->SkipTimingReport();
+  }
   event_loop_->SkipAosLog();
   event_loop_->OnRun([this]() { logger_->StartLogging(std::move(namer_)); });
 }
@@ -18,23 +21,24 @@
 std::vector<std::unique_ptr<LoggerState>> MakeLoggersForNodes(
     aos::SimulatedEventLoopFactory *factory,
     const std::vector<std::string> &nodes_to_log,
-    std::string_view output_folder) {
+    std::string_view output_folder, bool do_skip_timing_report) {
   std::vector<std::unique_ptr<LoggerState>> loggers;
   for (const std::string &node : nodes_to_log) {
     loggers.emplace_back(std::make_unique<LoggerState>(
         factory, aos::configuration::GetNode(factory->configuration(), node),
-        output_folder));
+        output_folder, do_skip_timing_report));
   }
   return loggers;
 }
 
 std::vector<std::unique_ptr<LoggerState>> MakeLoggersForAllNodes(
-    aos::SimulatedEventLoopFactory *factory, std::string_view output_folder) {
+    aos::SimulatedEventLoopFactory *factory, std::string_view output_folder,
+    bool do_skip_timing_report) {
   std::vector<std::unique_ptr<LoggerState>> loggers;
   for (const aos::Node *node :
        configuration::GetNodes(factory->configuration())) {
-    loggers.emplace_back(
-        std::make_unique<LoggerState>(factory, node, output_folder));
+    loggers.emplace_back(std::make_unique<LoggerState>(
+        factory, node, output_folder, do_skip_timing_report));
   }
   return loggers;
 }
diff --git a/aos/util/simulation_logger.h b/aos/util/simulation_logger.h
index f431b4c..af43d02 100644
--- a/aos/util/simulation_logger.h
+++ b/aos/util/simulation_logger.h
@@ -9,7 +9,8 @@
 class LoggerState {
  public:
   LoggerState(aos::SimulatedEventLoopFactory *factory, const aos::Node *node,
-              std::string_view output_folder);
+              std::string_view output_folder,
+              bool do_skip_timing_report = true);
 
  private:
   std::unique_ptr<aos::EventLoop> event_loop_;
@@ -23,11 +24,12 @@
 std::vector<std::unique_ptr<LoggerState>> MakeLoggersForNodes(
     aos::SimulatedEventLoopFactory *factory,
     const std::vector<std::string> &nodes_to_log,
-    std::string_view output_folder);
+    std::string_view output_folder, bool do_skip_timing_report = true);
 
 // Creates loggers for all of the nodes.
 std::vector<std::unique_ptr<LoggerState>> MakeLoggersForAllNodes(
-    aos::SimulatedEventLoopFactory *factory, std::string_view output_folder);
+    aos::SimulatedEventLoopFactory *factory, std::string_view output_folder,
+    bool do_skip_timing_report = true);
 
 }  // namespace aos::util
 #endif  // AOS_UTIL_SIMULATION_LOGGER_H_
diff --git a/compilers/yocto_orin_rootfs.BUILD b/compilers/yocto_orin_rootfs.BUILD
new file mode 100644
index 0000000..425b63a
--- /dev/null
+++ b/compilers/yocto_orin_rootfs.BUILD
@@ -0,0 +1,41 @@
+filegroup(
+    name = "sysroot_files",
+    srcs = glob(
+        include = [
+            "include/**",
+            "lib/**",
+            "lib64/**",
+            "usr/include/**",
+            "usr/lib/**",
+            "usr/lib64/**",
+        ],
+        exclude = [
+            "usr/share/**",
+        ],
+    ),
+    visibility = ["//visibility:public"],
+)
+
+cc_library(
+    name = "argus",
+    srcs = [
+        "usr/lib/libnvargus_socketclient.so",
+    ],
+    hdrs = glob(
+        include = ["usr/include/Argus/**"],
+    ),
+    includes = ["usr/include/Argus/utils/"],
+    visibility = ["//visibility:public"],
+)
+
+cc_library(
+    name = "eglstream",
+    srcs = [
+        #"usr/lib/libnvargus_socketclient.so",
+    ],
+    hdrs = glob(
+        include = ["usr/include/EGLStream/**"],
+    ),
+    includes = ["usr/include/EGLStream/"],
+    visibility = ["//visibility:public"],
+)
diff --git a/debian/BUILD.zlib.bazel b/debian/BUILD.zlib.bazel
index c0f81fa..c859eda 100644
--- a/debian/BUILD.zlib.bazel
+++ b/debian/BUILD.zlib.bazel
@@ -8,6 +8,8 @@
     copts = [
         "-w",
         "-Dverbose=-1",
+        "-Wno-unused-but-set-variable",
+        "-Wno-implicit-function-declaration",
     ],
     includes = [
         ".",
diff --git a/debian/boringssl.patch b/debian/boringssl.patch
index d12a7db..b7ac2ae 100644
--- a/debian/boringssl.patch
+++ b/debian/boringssl.patch
@@ -49,7 +49,7 @@
  
      # Modern build environments should be able to set this to use atomic
      # operations for reference counting rather than locks. However, it's
-@@ -86,17 +103,26 @@ posix_copts = [
+@@ -86,17 +103,29 @@ posix_copts = [
  boringssl_copts = select({
      ":linux_x86_64": posix_copts,
      ":linux_ppc64le": posix_copts,
@@ -62,7 +62,10 @@
      ],
      "//conditions:default": ["-DOPENSSL_NO_ASM"],
 +}) + compiler_select({
-+    "clang": [],
++    "clang": [
++      "-Wno-unused-but-set-variable",
++      "-Wno-array-parameter",
++    ],
 +    "gcc": [
 +      "-Wno-array-parameter",
 +    ],
diff --git a/debian/clapack.BUILD b/debian/clapack.BUILD
index 9ed7f82..c85e2df 100644
--- a/debian/clapack.BUILD
+++ b/debian/clapack.BUILD
@@ -295,7 +295,9 @@
         "-Wno-unused-but-set-variable",
     ] + compiler_select({
         "clang": [
+            "-Wno-deprecated-non-prototype",
             "-Wno-self-assign",
+            "-Wno-unused-but-set-parameter",
         ],
         "gcc": [
             "-Wno-implicit-fallthrough",
diff --git a/debian/curl.BUILD b/debian/curl.BUILD
index 95edb78..a4f6526 100644
--- a/debian/curl.BUILD
+++ b/debian/curl.BUILD
@@ -175,6 +175,8 @@
     name = "curl",
     srcs = [
         "include/curl_config.h",
+        "lib/altsvc.c",
+        "lib/altsvc.h",
         "lib/amigaos.h",
         "lib/arpa_telnet.h",
         "lib/asyn.h",
@@ -198,6 +200,8 @@
         "lib/curl_endian.h",
         "lib/curl_fnmatch.c",
         "lib/curl_fnmatch.h",
+        "lib/curl_get_line.c",
+        "lib/curl_get_line.h",
         "lib/curl_gethostname.c",
         "lib/curl_gethostname.h",
         "lib/curl_gssapi.h",
@@ -227,6 +231,8 @@
         "lib/curl_threads.h",
         "lib/curlx.h",
         "lib/dict.h",
+        "lib/doh.c",
+        "lib/doh.h",
         "lib/dotdot.c",
         "lib/dotdot.h",
         "lib/easy.c",
@@ -295,16 +301,18 @@
         "lib/nwos.c",
         "lib/parsedate.c",
         "lib/parsedate.h",
-        "lib/pingpong.h",
         "lib/pingpong.c",
+        "lib/pingpong.h",
         "lib/pop3.h",
         "lib/progress.c",
         "lib/progress.h",
+        "lib/psl.c",
+        "lib/psl.h",
         "lib/quic.h",
         "lib/rand.c",
         "lib/rand.h",
-        "lib/rename.h",
         "lib/rename.c",
+        "lib/rename.h",
         "lib/rtsp.c",
         "lib/rtsp.h",
         "lib/security.c",
@@ -325,8 +333,8 @@
         "lib/smb.h",
         "lib/smtp.h",
         "lib/sockaddr.h",
-        "lib/socketpair.h",
         "lib/socketpair.c",
+        "lib/socketpair.h",
         "lib/socks.c",
         "lib/socks.h",
         "lib/speedcheck.c",
@@ -352,6 +360,8 @@
         "lib/transfer.h",
         "lib/url.c",
         "lib/url.h",
+        "lib/urlapi.c",
+        "lib/urlapi-int.h",
         "lib/urldata.h",
         "lib/vauth/cleartext.c",
         "lib/vauth/cram.c",
@@ -367,9 +377,12 @@
         "lib/vtls/gskit.h",
         "lib/vtls/gtls.h",
         "lib/vtls/mbedtls.h",
+        "lib/vtls/mesalink.c",
+        "lib/vtls/mesalink.h",
         "lib/vtls/nssg.h",
         "lib/vtls/openssl.h",
         "lib/vtls/schannel.h",
+        "lib/vtls/sectransp.h",
         "lib/vtls/vtls.c",
         "lib/vtls/vtls.h",
         "lib/vtls/wolfssl.h",
@@ -378,19 +391,6 @@
         "lib/wildcard.c",
         "lib/wildcard.h",
         "lib/x509asn1.h",
-        "lib/psl.h",
-        "lib/psl.c",
-        "lib/vtls/sectransp.h",
-        "lib/vtls/mesalink.h",
-        "lib/vtls/mesalink.c",
-        "lib/curl_get_line.h",
-        "lib/curl_get_line.c",
-        "lib/urlapi-int.h",
-        "lib/urlapi.c",
-        "lib/altsvc.h",
-        "lib/altsvc.c",
-        "lib/doh.h",
-        "lib/doh.c",
     ] + select({
         ":macos": [
             "lib/vtls/sectransp.c",
@@ -426,6 +426,7 @@
             "-Wno-cast-qual",
             "-Wno-format-nonliteral",
             "-Wno-tautological-type-limit-compare",
+            "-Wno-unused-but-set-variable",
         ],
     }) + select({
         ":macos": [
@@ -463,7 +464,7 @@
     visibility = ["//visibility:public"],
     deps = [
         # Use the same version of zlib that gRPC does.
-        "@zlib//:zlib",
+        "@zlib",
         ":define-ca-bundle-location",
     ] + select({
         ":windows": [],
diff --git a/debian/gstreamer.BUILD b/debian/gstreamer.BUILD
index f81f206..a9a28ef 100644
--- a/debian/gstreamer.BUILD
+++ b/debian/gstreamer.BUILD
@@ -262,8 +262,8 @@
         "usr/include",
         "usr/include/glib-2.0",
         "usr/include/gstreamer-1.0",
-        "usr/include/libsoup-2.4",
         "usr/include/json-glib-1.0",
+        "usr/include/libsoup-2.4",
         "usr/include/opencv4",
     ],
     linkopts = [
diff --git a/debian/opencv.BUILD b/debian/opencv.BUILD
index 141a286..d6b4378 100644
--- a/debian/opencv.BUILD
+++ b/debian/opencv.BUILD
@@ -263,10 +263,10 @@
     name = "opencv",
     srcs = select({
         "@platforms//cpu:x86_64": [s % "x86_64-linux-gnu" if "%" in s else s for s in _common_srcs_list] + [
-            "usr/lib/x86_64-linux-gnu/libmfx.so.1",
-            "usr/lib/x86_64-linux-gnu/libquadmath.so.0",
-            "usr/lib/x86_64-linux-gnu/libnuma.so.1",
             "usr/lib/x86_64-linux-gnu/libcrypto.so.1.1",
+            "usr/lib/x86_64-linux-gnu/libmfx.so.1",
+            "usr/lib/x86_64-linux-gnu/libnuma.so.1",
+            "usr/lib/x86_64-linux-gnu/libquadmath.so.0",
             "usr/lib/x86_64-linux-gnu/libssl.so.1.1",
         ],
         "@platforms//cpu:armv7": [s % "arm-linux-gnueabihf" if "%" in s else s for s in _common_srcs_list],
diff --git a/debian/slycot.BUILD b/debian/slycot.BUILD
index 21c0df7..fb3f376 100644
--- a/debian/slycot.BUILD
+++ b/debian/slycot.BUILD
@@ -15,6 +15,7 @@
     ] + compiler_select({
         "clang": [
             "-Wno-unused-but-set-parameter",
+            "-Wno-deprecated-non-prototype",
         ],
         "gcc": [
             "-Wno-discarded-qualifiers",
diff --git a/frc971/analysis/BUILD b/frc971/analysis/BUILD
index 60528a9..442a7de 100644
--- a/frc971/analysis/BUILD
+++ b/frc971/analysis/BUILD
@@ -58,6 +58,7 @@
         "//y2022/control_loops/superstructure:turret_plotter",
         "//y2022/localizer:localizer_plotter",
         "//y2022/vision:vision_plotter",
+        "//y2023/control_loops/superstructure:superstructure_plotter",
         "//y2023/localizer:corrections_plotter",
         "//y2023/localizer:localizer_plotter",
     ],
diff --git a/frc971/analysis/plot_index.ts b/frc971/analysis/plot_index.ts
index 57d5041..1ee85d1 100644
--- a/frc971/analysis/plot_index.ts
+++ b/frc971/analysis/plot_index.ts
@@ -44,6 +44,8 @@
     '../../y2022/control_loops/superstructure/turret_plotter'
 import {plotSuperstructure as plot2022Superstructure} from
     '../../y2022/control_loops/superstructure/superstructure_plotter'
+import {plotSuperstructure as plot2023Superstructure} from
+    '../../y2023/control_loops/superstructure/superstructure_plotter'
 import {plotCatapult as plot2022Catapult} from
     '../../y2022/control_loops/superstructure/catapult_plotter'
 import {plotIntakeFront as plot2022IntakeFront, plotIntakeBack as plot2022IntakeBack} from
@@ -118,6 +120,7 @@
   ['Robot State', new PlotState(plotDiv, plotRobotState)],
   ['2023 Vision', new PlotState(plotDiv, plot2023Corrections)],
   ['2023 Localizer', new PlotState(plotDiv, plot2023Localizer)],
+  ['2023 Superstructure', new PlotState(plotDiv, plot2023Superstructure)],
   ['2020 Finisher', new PlotState(plotDiv, plot2020Finisher)],
   ['2020 Accelerator', new PlotState(plotDiv, plot2020Accelerator)],
   ['2020 Hood', new PlotState(plotDiv, plot2020Hood)],
diff --git a/frc971/constants.h b/frc971/constants.h
index 4f7cb36..cf53287 100644
--- a/frc971/constants.h
+++ b/frc971/constants.h
@@ -70,6 +70,28 @@
 
 struct RelativeEncoderZeroingConstants {};
 
+struct ContinuousAbsoluteEncoderZeroingConstants {
+  // The number of samples in the moving average filter.
+  size_t average_filter_size;
+  // The distance that the absolute encoder needs to complete a full rotation.
+  // It is presumed that this will always be 2 * pi for any subsystem using this
+  // class, unless you have a continuous system that for some reason doesn't
+  // have a logical period of 1 revolution in radians.
+  double one_revolution_distance;
+  // Measured absolute position of the encoder when at zero.
+  double measured_absolute_position;
+
+  // Threshold for deciding if we are moving. moving_buffer_size samples need to
+  // be within this distance of each other before we use the middle one to zero.
+  double zeroing_threshold;
+  // Buffer size for deciding if we are moving.
+  size_t moving_buffer_size;
+
+  // Value between 0 and 1 indicating what fraction of a revolution
+  // it is acceptable for the offset to move.
+  double allowable_encoder_error;
+};
+
 struct AbsoluteEncoderZeroingConstants {
   // The number of samples in the moving average filter.
   size_t average_filter_size;
diff --git a/frc971/control_loops/BUILD b/frc971/control_loops/BUILD
index 3cdb6ab..31b245b 100644
--- a/frc971/control_loops/BUILD
+++ b/frc971/control_loops/BUILD
@@ -416,6 +416,7 @@
         "//aos/util:trapezoid_profile",
         "//frc971/control_loops:control_loop",
         "//frc971/zeroing",
+        "//frc971/zeroing:pot_and_index",
     ],
 )
 
@@ -661,6 +662,9 @@
         ":static_zeroing_single_dof_profiled_subsystem_test_subsystem_output_fbs",
         "//aos/testing:googletest",
         "//frc971/control_loops:control_loop_test",
+        "//frc971/zeroing:absolute_and_absolute_encoder",
+        "//frc971/zeroing:absolute_encoder",
+        "//frc971/zeroing:pot_and_absolute_encoder",
     ],
 )
 
diff --git a/frc971/control_loops/drivetrain/line_follow_drivetrain.cc b/frc971/control_loops/drivetrain/line_follow_drivetrain.cc
index 9823f0c..355710c 100644
--- a/frc971/control_loops/drivetrain/line_follow_drivetrain.cc
+++ b/frc971/control_loops/drivetrain/line_follow_drivetrain.cc
@@ -257,7 +257,7 @@
        relative_pose_.rel_pos().y(), relative_pose_.rel_theta(),
        abs_state(3, 0), abs_state(4, 0))
           .finished();
-  if (velocity_sign_ * goal_velocity_ < 0) {
+  if (velocity_sign_ * goal_velocity_ < -0.1) {
     goal_theta = rel_state(2, 0);
   }
   controls_goal_ << goal_theta, goal_velocity_, 0.0;
diff --git a/frc971/control_loops/drivetrain/robot_state_plotter.ts b/frc971/control_loops/drivetrain/robot_state_plotter.ts
index 8cffd76..7a9a346 100644
--- a/frc971/control_loops/drivetrain/robot_state_plotter.ts
+++ b/frc971/control_loops/drivetrain/robot_state_plotter.ts
@@ -34,6 +34,9 @@
   const brownOut = robotStatePlot.addMessageLine(robotState, ['browned_out']);
   brownOut.setColor(BROWN);
   brownOut.setDrawLine(false);
+  robotStatePlot.addMessageLine(robotState, ['outputs_enabled'])
+      .setColor(CYAN)
+      .setDrawLine(false);
   const enabled = robotStatePlot.addMessageLine(joystickState, ['enabled']);
   enabled.setColor(GREEN);
   enabled.setDrawLine(false);
diff --git a/frc971/control_loops/drivetrain/trajectory.h b/frc971/control_loops/drivetrain/trajectory.h
index e30ca98..b2cecc0 100644
--- a/frc971/control_loops/drivetrain/trajectory.h
+++ b/frc971/control_loops/drivetrain/trajectory.h
@@ -92,8 +92,8 @@
   }
 
   const StateFeedbackLoop<2, 2, 2, double, StateFeedbackHybridPlant<2, 2, 2>,
-                          HybridKalman<2, 2, 2>>
-      &velocity_drivetrain() const {
+                          HybridKalman<2, 2, 2>> &
+  velocity_drivetrain() const {
     return *velocity_drivetrain_;
   }
 
diff --git a/frc971/control_loops/hybrid_state_feedback_loop.h b/frc971/control_loops/hybrid_state_feedback_loop.h
index d8dbe72..79e7da7 100644
--- a/frc971/control_loops/hybrid_state_feedback_loop.h
+++ b/frc971/control_loops/hybrid_state_feedback_loop.h
@@ -120,8 +120,8 @@
     return coefficients().U_max;
   }
   Scalar U_max(int i, int j) const { return U_max()(i, j); }
-  const Eigen::Matrix<Scalar, number_of_inputs, number_of_states>
-      &U_limit_coefficient() const {
+  const Eigen::Matrix<Scalar, number_of_inputs, number_of_states> &
+  U_limit_coefficient() const {
     return coefficients().U_limit_coefficient;
   }
   Scalar U_limit_coefficient(int i, int j) const {
@@ -145,14 +145,14 @@
   Scalar &mutable_Y(int i) { return mutable_Y()(i); }
 
   const StateFeedbackHybridPlantCoefficients<number_of_states, number_of_inputs,
-                                             number_of_outputs, Scalar>
-      &coefficients(int index) const {
+                                             number_of_outputs, Scalar> &
+  coefficients(int index) const {
     return *coefficients_[index];
   }
 
   const StateFeedbackHybridPlantCoefficients<number_of_states, number_of_inputs,
-                                             number_of_outputs, Scalar>
-      &coefficients() const {
+                                             number_of_outputs, Scalar> &
+  coefficients() const {
     return *coefficients_[index_];
   }
 
@@ -398,14 +398,14 @@
   int index() const { return index_; }
 
   const HybridKalmanCoefficients<number_of_states, number_of_inputs,
-                                 number_of_outputs>
-      &coefficients(int index) const {
+                                 number_of_outputs> &
+  coefficients(int index) const {
     return *coefficients_[index];
   }
 
   const HybridKalmanCoefficients<number_of_states, number_of_inputs,
-                                 number_of_outputs>
-      &coefficients() const {
+                                 number_of_outputs> &
+  coefficients() const {
     return *coefficients_[index_];
   }
 
diff --git a/frc971/control_loops/profiled_subsystem.h b/frc971/control_loops/profiled_subsystem.h
index dff5aad..2a15201 100644
--- a/frc971/control_loops/profiled_subsystem.h
+++ b/frc971/control_loops/profiled_subsystem.h
@@ -15,6 +15,7 @@
 #include "frc971/control_loops/profiled_subsystem_generated.h"
 #include "frc971/control_loops/simple_capped_state_feedback_loop.h"
 #include "frc971/control_loops/state_feedback_loop.h"
+#include "frc971/zeroing/pot_and_index.h"
 #include "frc971/zeroing/zeroing.h"
 
 namespace frc971 {
@@ -60,8 +61,9 @@
   }
 
   // Returns the controller.
-  const StateFeedbackLoop<number_of_states, number_of_inputs, number_of_outputs>
-      &controller() const {
+  const StateFeedbackLoop<number_of_states, number_of_inputs,
+                          number_of_outputs> &
+  controller() const {
     return *loop_;
   }
 
diff --git a/frc971/control_loops/state_feedback_loop.h b/frc971/control_loops/state_feedback_loop.h
index 295beca..c5d05c1 100644
--- a/frc971/control_loops/state_feedback_loop.h
+++ b/frc971/control_loops/state_feedback_loop.h
@@ -144,8 +144,8 @@
     return coefficients().U_max;
   }
   Scalar U_max(int i, int j = 0) const { return U_max()(i, j); }
-  const Eigen::Matrix<Scalar, number_of_inputs, number_of_states>
-      &U_limit_coefficient() const {
+  const Eigen::Matrix<Scalar, number_of_inputs, number_of_states> &
+  U_limit_coefficient() const {
     return coefficients().U_limit_coefficient;
   }
   Scalar U_limit_coefficient(int i, int j) const {
@@ -173,14 +173,14 @@
   size_t coefficients_size() const { return coefficients_.size(); }
 
   const StateFeedbackPlantCoefficients<number_of_states, number_of_inputs,
-                                       number_of_outputs, Scalar>
-      &coefficients(int index) const {
+                                       number_of_outputs, Scalar> &
+  coefficients(int index) const {
     return *coefficients_[index];
   }
 
   const StateFeedbackPlantCoefficients<number_of_states, number_of_inputs,
-                                       number_of_outputs, Scalar>
-      &coefficients() const {
+                                       number_of_outputs, Scalar> &
+  coefficients() const {
     return *coefficients_[index_];
   }
 
@@ -326,14 +326,14 @@
   int index() const { return index_; }
 
   const StateFeedbackControllerCoefficients<number_of_states, number_of_inputs,
-                                            number_of_outputs, Scalar>
-      &coefficients(int index) const {
+                                            number_of_outputs, Scalar> &
+  coefficients(int index) const {
     return *coefficients_[index];
   }
 
   const StateFeedbackControllerCoefficients<number_of_states, number_of_inputs,
-                                            number_of_outputs, Scalar>
-      &coefficients() const {
+                                            number_of_outputs, Scalar> &
+  coefficients() const {
     return *coefficients_[index_];
   }
 
@@ -450,14 +450,14 @@
   int index() const { return index_; }
 
   const StateFeedbackObserverCoefficients<number_of_states, number_of_inputs,
-                                          number_of_outputs, Scalar>
-      &coefficients(int index) const {
+                                          number_of_outputs, Scalar> &
+  coefficients(int index) const {
     return *coefficients_[index];
   }
 
   const StateFeedbackObserverCoefficients<number_of_states, number_of_inputs,
-                                          number_of_outputs, Scalar>
-      &coefficients() const {
+                                          number_of_outputs, Scalar> &
+  coefficients() const {
     return *coefficients_[index_];
   }
 
@@ -550,8 +550,8 @@
   PlantType *mutable_plant() { return &plant_; }
 
   const StateFeedbackController<number_of_states, number_of_inputs,
-                                number_of_outputs, Scalar>
-      &controller() const {
+                                number_of_outputs, Scalar> &
+  controller() const {
     return controller_;
   }
 
diff --git a/frc971/control_loops/static_zeroing_single_dof_profiled_subsystem_test.cc b/frc971/control_loops/static_zeroing_single_dof_profiled_subsystem_test.cc
index c886775..ace528e 100644
--- a/frc971/control_loops/static_zeroing_single_dof_profiled_subsystem_test.cc
+++ b/frc971/control_loops/static_zeroing_single_dof_profiled_subsystem_test.cc
@@ -16,6 +16,8 @@
 #include "frc971/control_loops/static_zeroing_single_dof_profiled_subsystem_test_pot_and_absolute_position_generated.h"
 #include "frc971/control_loops/static_zeroing_single_dof_profiled_subsystem_test_subsystem_goal_generated.h"
 #include "frc971/control_loops/static_zeroing_single_dof_profiled_subsystem_test_subsystem_output_generated.h"
+#include "frc971/zeroing/absolute_encoder.h"
+#include "frc971/zeroing/pot_and_absolute_encoder.h"
 #include "frc971/zeroing/zeroing.h"
 
 using ::frc971::control_loops::PositionSensorSimulator;
diff --git a/frc971/orin/build_rootfs.py b/frc971/orin/build_rootfs.py
new file mode 100755
index 0000000..f82bed7
--- /dev/null
+++ b/frc971/orin/build_rootfs.py
@@ -0,0 +1,412 @@
+#!/usr/bin/python3
+
+import contextlib
+import pathlib
+import collections
+import subprocess
+import shlex
+import os
+
+IMAGE = "arm64_bookworm_debian_yocto.img"
+YOCTO = "/home/austin/local/jetpack/robot-yocto/build"
+
+REQUIRED_DEPS = ["debootstrap", "u-boot-tools"]
+
+
+@contextlib.contextmanager
+def scoped_loopback(image):
+    """Mounts an image as a loop back device."""
+    result = subprocess.run(["sudo", "losetup", "--show", "-f", image],
+                            check=True,
+                            stdout=subprocess.PIPE)
+    device = result.stdout.decode('utf-8').strip()
+    print("Mounted", image, "to", repr(device))
+    try:
+        yield device
+    finally:
+        subprocess.run(["sudo", "losetup", "-d", device], check=True)
+
+
+@contextlib.contextmanager
+def scoped_mount(image):
+    """Mounts an image as a partition."""
+    partition = f"{image}.partition"
+    try:
+        os.mkdir(partition)
+    except FileExistsError:
+        pass
+
+    result = subprocess.run(["sudo", "mount", "-o", "loop", image, partition],
+                            check=True)
+
+    try:
+        yield partition
+    finally:
+        subprocess.run(
+            ["sudo", "rm", f"{partition}/usr/bin/qemu-aarch64-static"])
+        subprocess.run(["sudo", "umount", partition], check=True)
+
+
+def check_required_deps(deps):
+    """Checks if the provided list of dependencies is installed."""
+    missing_deps = []
+    for dep in deps:
+        result = subprocess.run(["dpkg-query", "-W", "-f='${Status}'", dep],
+                                check=True,
+                                stdout=subprocess.PIPE)
+
+        if "install ok installed" not in result.stdout.decode('utf-8'):
+            missing_deps.append(dep)
+
+    if len(missing_deps) > 0:
+        print("Missing dependencies, please install:")
+        print("sudo apt-get install", " ".join(missing_deps))
+
+
+def make_image(image):
+    """Makes an image and creates an xfs filesystem on it."""
+    result = subprocess.run([
+        "dd", "if=/dev/zero", f"of={image}", "bs=1", "count=0",
+        "seek=8589934592"
+    ],
+                            check=True)
+
+    with scoped_loopback(image) as loopback:
+        subprocess.run([
+            "sudo", "mkfs.xfs", "-d", "su=128k", "-d", "sw=1", "-L", "rootfs",
+            loopback
+        ],
+                       check=True)
+
+
+def target_unescaped(cmd):
+    """Runs a command as root with bash -c cmd, ie without escaping."""
+    subprocess.run([
+        "sudo", "chroot", "--userspec=0:0", f"{PARTITION}",
+        "qemu-aarch64-static", "/bin/bash", "-c", cmd
+    ],
+                   check=True)
+
+
+def target(cmd):
+    """Runs a command as root with escaping."""
+    target_unescaped(shlex.join([shlex.quote(c) for c in cmd]))
+
+
+def pi_target_unescaped(cmd):
+    """Runs a command as pi with bash -c cmd, ie without escaping."""
+    subprocess.run([
+        "sudo", "chroot", "--userspec=pi:pi", "--groups=pi", f"{PARTITION}",
+        "qemu-aarch64-static", "/bin/bash", "-c", cmd
+    ],
+                   check=True)
+
+
+def pi_target(cmd):
+    """Runs a command as pi with escaping."""
+    pi_target_unescaped(shlex.join([shlex.quote(c) for c in cmd]))
+
+
+def copyfile(owner, permissions, file):
+    """Copies a file from contents/{file} with the provided owner and permissions."""
+    print("copyfile", owner, permissions, file)
+    subprocess.run(["sudo", "cp", f"contents/{file}", f"{PARTITION}/{file}"],
+                   check=True)
+    subprocess.run(["sudo", "chmod", permissions, f"{PARTITION}/{file}"],
+                   check=True)
+    target(["chown", owner, f"/{file}"])
+
+
+def target_mkdir(owner_group, permissions, folder):
+    """Creates a directory recursively with the provided permissions and ownership."""
+    print("target_mkdir", owner_group, permissions, folder)
+    owner, group = owner_group.split('.')
+    target(
+        ["install", "-d", "-m", permissions, "-o", owner, "-g", group, folder])
+
+
+def list_packages():
+    """Lists all installed packages.
+
+    Returns:
+      A dictionary with keys as packages, and values as versions.
+    """
+    result = subprocess.run([
+        "sudo", "chroot", "--userspec=0:0", f"{PARTITION}",
+        "qemu-aarch64-static", "/bin/bash", "-c",
+        "dpkg-query -W -f='${Package} ${Version}\n'"
+    ],
+                            check=True,
+                            stdout=subprocess.PIPE)
+
+    device = result.stdout.decode('utf-8').strip()
+
+    r = {}
+    for line in result.stdout.decode('utf-8').strip().split('\n'):
+        package, version = line.split(' ')
+        r[package] = version
+
+    return r
+
+
+def list_yocto_packages():
+    """Lists all packages in the Yocto folder.
+
+    Returns:
+      list of Package classes.
+    """
+    Package = collections.namedtuple(
+        'Package', ['path', 'name', 'version', 'architecture'])
+    result = []
+    pathlist = pathlib.Path(f"{YOCTO}/tmp/deploy/deb").glob('**/*.deb')
+    for path in pathlist:
+        # Strip off the path, .deb, and split on _ to parse the package info.
+        s = os.path.basename(str(path))[:-4].split('_')
+        result.append(Package(str(path), s[0], s[1], s[2]))
+
+    return result
+
+
+def install_packages(new_packages, existing_packages):
+    """Installs the provided yocto packages, if they are new."""
+    # To install the yocto packages, first copy them into a folder in /tmp, then install them, then clean the folder up.
+    target(["mkdir", "-p", "/tmp/yocto_packages"])
+    try:
+        to_install = []
+        for package in new_packages:
+            if package.name in existing_packages and existing_packages[
+                    package.name] == package.version:
+                print('Skipping', package)
+                continue
+
+            subprocess.run([
+                "sudo", "cp", package.path,
+                f"{PARTITION}/tmp/yocto_packages/{os.path.basename(package.path)}"
+            ],
+                           check=True)
+            to_install.append(package)
+
+        if len(to_install) > 0:
+            target(["dpkg", "-i"] + [
+                f"/tmp/yocto_packages/{os.path.basename(package.path)}"
+                for package in to_install
+            ])
+
+    finally:
+        target(["rm", "-rf", "/tmp/yocto_packages"])
+
+
+def install_virtual_packages(virtual_packages):
+    """Builds and installs the provided virtual packages."""
+    try:
+        target(["mkdir", "-p", "/tmp/yocto_packages"])
+        for virtual_package in virtual_packages:
+            subprocess.run(
+                ["dpkg-deb", "--build", f"virtual_packages/{virtual_package}"],
+                check=True)
+            subprocess.run([
+                "sudo", "cp", f"virtual_packages/{virtual_package}.deb",
+                f"{PARTITION}/tmp/yocto_packages/{virtual_package}.deb"
+            ],
+                           check=True)
+
+        target(["dpkg", "-i"] + [
+            f"/tmp/yocto_packages/{package}.deb"
+            for package in virtual_packages
+        ])
+
+    finally:
+        target(["rm", "-rf", "/tmp/yocto_packages"])
+
+
+def main():
+    check_required_deps(REQUIRED_DEPS)
+
+    new_image = not os.path.exists(IMAGE)
+    if new_image:
+        make_image(IMAGE)
+
+    with scoped_mount(IMAGE) as partition:
+        if new_image:
+            subprocess.run([
+                "sudo", "debootstrap", "--arch=arm64", "--no-check-gpg",
+                "--foreign", "bookworm", partition,
+                "http://deb.debian.org/debian/"
+            ],
+                           check=True)
+
+        subprocess.run([
+            "sudo", "cp", "/usr/bin/qemu-aarch64-static",
+            f"{partition}/usr/bin/"
+        ],
+                       check=True)
+
+        global PARTITION
+        PARTITION = partition
+
+        if new_image:
+            target(["/debootstrap/debootstrap", "--second-stage"])
+
+            target([
+                "useradd", "-m", "-p",
+                '$y$j9T$85lzhdky63CTj.two7Zj20$pVY53UR0VebErMlm8peyrEjmxeiRw/rfXfx..9.xet1',
+                '-s', '/bin/bash', 'pi'
+            ])
+            target(["addgroup", "debug"])
+            target(["addgroup", "crypto"])
+            target(["addgroup", "trusty"])
+
+        if not os.path.exists(
+                f"{partition}/etc/apt/sources.list.d/bullseye-backports.list"):
+            copyfile("root.root", "644",
+                     "etc/apt/sources.list.d/bullseye-backports.list")
+            target(["apt-get", "update"])
+
+        target([
+            "apt-get", "-y", "install", "gnupg", "wget", "systemd",
+            "systemd-resolved", "locales"
+        ])
+
+        target(["localedef", "-i", "en_US", "-f", "UTF-8", "en_US.UTF-8"])
+
+        target_mkdir("root.root", "755", "run/systemd")
+        target_mkdir("systemd-resolve.systemd-resolve", "755",
+                     "run/systemd/resolve")
+        copyfile("systemd-resolve.systemd-resolve", "644",
+                 "run/systemd/resolve/stub-resolv.conf")
+        target(["systemctl", "enable", "systemd-resolved"])
+
+        target([
+            "apt-get", "-y", "install", "bpfcc-tools", "sudo",
+            "openssh-server", "python3", "bash-completion", "git", "v4l-utils",
+            "cpufrequtils", "pmount", "rsync", "vim-nox", "chrony",
+            "libopencv-calib3d406", "libopencv-contrib406",
+            "libopencv-core406", "libopencv-features2d406",
+            "libopencv-flann406", "libopencv-highgui406",
+            "libopencv-imgcodecs406", "libopencv-imgproc406",
+            "libopencv-ml406", "libopencv-objdetect406", "libopencv-photo406",
+            "libopencv-shape406", "libopencv-stitching406",
+            "libopencv-superres406", "libopencv-video406",
+            "libopencv-videoio406", "libopencv-videostab406",
+            "libopencv-viz406", "libnice10", "pmount", "libnice-dev", "feh",
+            "libgstreamer1.0-0", "libgstreamer-plugins-base1.0-0",
+            "libgstreamer-plugins-bad1.0-0", "gstreamer1.0-plugins-base",
+            "gstreamer1.0-plugins-good", "gstreamer1.0-plugins-bad",
+            "gstreamer1.0-plugins-ugly", "gstreamer1.0-nice", "usbutils",
+            "locales", "trace-cmd", "clinfo", "jq", "strace", "sysstat",
+            "lm-sensors", "can-utils", "xfsprogs", "gstreamer1.0-tools",
+            "bridge-utils", "net-tools", "apt-file", "parted", "xxd"
+        ])
+        target(["apt-get", "clean"])
+
+        target(["usermod", "-a", "-G", "sudo", "pi"])
+        target(["usermod", "-a", "-G", "video", "pi"])
+        target(["usermod", "-a", "-G", "systemd-journal", "pi"])
+        target(["usermod", "-a", "-G", "dialout", "pi"])
+
+        virtual_packages = [
+            'libglib-2.0-0', 'libglvnd', 'libgtk-3-0', 'libxcb-glx', 'wayland'
+        ]
+
+        install_virtual_packages(virtual_packages)
+
+        yocto_package_names = [
+            'tegra-argus-daemon', 'tegra-firmware', 'tegra-firmware-tegra234',
+            'tegra-firmware-vic', 'tegra-firmware-xusb',
+            'tegra-libraries-argus-daemon-base', 'tegra-libraries-camera',
+            'tegra-libraries-core', 'tegra-libraries-cuda',
+            'tegra-libraries-eglcore', 'tegra-libraries-glescore',
+            'tegra-libraries-glxcore', 'tegra-libraries-multimedia',
+            'tegra-libraries-multimedia-utils',
+            'tegra-libraries-multimedia-v4l', 'tegra-libraries-nvsci',
+            'tegra-libraries-vulkan', 'tegra-nvphs', 'tegra-nvphs-base',
+            'libnvidia-egl-wayland1'
+        ]
+        yocto_packages = list_yocto_packages()
+        packages = list_packages()
+
+        install_packages([
+            package for package in yocto_packages
+            if package.name in yocto_package_names
+        ], packages)
+
+        # Now, install the kernel and modules after all the normal packages are in.
+        yocto_packages_to_install = [
+            package for package in yocto_packages
+            if (package.name.startswith('kernel-module-') or package.name.
+                startswith('kernel-5.10') or package.name == 'kernel-modules')
+        ]
+
+        packages_to_remove = []
+
+        # Remove kernel-module-* packages + kernel- package.
+        for key in packages:
+            if key.startswith('kernel-module') or key.startswith(
+                    'kernel-5.10'):
+                already_installed = False
+                for index, yocto_package in enumerate(
+                        yocto_packages_to_install):
+                    if key == yocto_package.name and packages[
+                            key] == yocto_package.version:
+                        print('Found already installed package', key,
+                              yocto_package)
+                        already_installed = True
+                        del yocto_packages_to_install[index]
+                        break
+                if not already_installed:
+                    packages_to_remove.append(key)
+
+        print("Removing", packages_to_remove)
+        if len(packages_to_remove) > 0:
+            target(['dpkg', '--purge'] + packages_to_remove)
+        print("Installing",
+              [package.name for package in yocto_packages_to_install])
+
+        install_packages(yocto_packages_to_install, packages)
+
+        target(["systemctl", "enable", "nvargus-daemon.service"])
+
+        copyfile("root.root", "644", "etc/sysctl.d/sctp.conf")
+        copyfile("root.root", "644", "etc/systemd/logind.conf")
+        copyfile("root.root", "555",
+                 "etc/bash_completion.d/aos_dump_autocomplete")
+        copyfile("root.root", "644", "etc/security/limits.d/rt.conf")
+        copyfile("root.root", "644", "etc/systemd/system/usb-mount@.service")
+        copyfile("root.root", "644", "etc/chrony/chrony.conf")
+        target_mkdir("root.root", "700", "root/bin")
+        target_mkdir("pi.pi", "755", "home/pi/.ssh")
+        copyfile("pi.pi", "600", "home/pi/.ssh/authorized_keys")
+        target_mkdir("root.root", "700", "root/bin")
+        copyfile("root.root", "644", "etc/systemd/system/grow-rootfs.service")
+        copyfile("root.root", "500", "root/bin/change_hostname.sh")
+        copyfile("root.root", "700", "root/trace.sh")
+        copyfile("root.root", "440", "etc/sudoers")
+        copyfile("root.root", "644", "etc/fstab")
+        copyfile("root.root", "644",
+                 "var/nvidia/nvcam/settings/camera_overrides.isp")
+
+        target_mkdir("root.root", "755", "etc/systemd/network")
+        copyfile("root.root", "644", "etc/systemd/network/eth0.network")
+        copyfile("root.root", "644", "etc/systemd/network/80-can.network")
+        target(["/root/bin/change_hostname.sh", "pi-971-1"])
+
+        target(["systemctl", "enable", "systemd-networkd"])
+        target(["systemctl", "enable", "grow-rootfs"])
+        target(["apt-file", "update"])
+
+        target(["ldconfig"])
+
+        if not os.path.exists(f"{partition}/home/pi/.dotfiles"):
+            pi_target_unescaped(
+                "cd /home/pi/ && git clone --separate-git-dir=/home/pi/.dotfiles https://github.com/AustinSchuh/.dotfiles.git tmpdotfiles && rsync --recursive --verbose --exclude .git tmpdotfiles/ /home/pi/ && rm -r tmpdotfiles && git --git-dir=/home/pi/.dotfiles/ --work-tree=/home/pi/ config --local status.showUntrackedFiles no"
+            )
+            pi_target(["vim", "-c", "\":qa!\""])
+
+            target_unescaped(
+                "cd /root/ && git clone --separate-git-dir=/root/.dotfiles https://github.com/AustinSchuh/.dotfiles.git tmpdotfiles && rsync --recursive --verbose --exclude .git tmpdotfiles/ /root/ && rm -r tmpdotfiles && git --git-dir=/root/.dotfiles/ --work-tree=/root/ config --local status.showUntrackedFiles no"
+            )
+            target(["vim", "-c", "\":qa!\""])
+
+
+if __name__ == '__main__':
+    main()
diff --git a/frc971/orin/contents/etc/apt/sources.list.d/bullseye-backports.list b/frc971/orin/contents/etc/apt/sources.list.d/bullseye-backports.list
new file mode 100644
index 0000000..6c98bcb
--- /dev/null
+++ b/frc971/orin/contents/etc/apt/sources.list.d/bullseye-backports.list
@@ -0,0 +1,2 @@
+deb http://deb.debian.org/debian bullseye-backports main
+deb http://deb.debian.org/debian bullseye main
diff --git a/frc971/orin/contents/etc/bash_completion.d/aos_dump_autocomplete b/frc971/orin/contents/etc/bash_completion.d/aos_dump_autocomplete
new file mode 100644
index 0000000..1b82c46
--- /dev/null
+++ b/frc971/orin/contents/etc/bash_completion.d/aos_dump_autocomplete
@@ -0,0 +1,5 @@
+#!/bin/bash
+
+if [[ -e "/home/pi/bin/aos_dump_autocomplete.sh" ]]; then
+  . /home/pi/bin/aos_dump_autocomplete.sh
+fi
diff --git a/frc971/orin/contents/etc/chrony/chrony.conf b/frc971/orin/contents/etc/chrony/chrony.conf
new file mode 100644
index 0000000..6e03143
--- /dev/null
+++ b/frc971/orin/contents/etc/chrony/chrony.conf
@@ -0,0 +1,59 @@
+# Welcome to the chrony configuration file. See chrony.conf(5) for more
+# information about usable directives.
+
+# Include configuration files found in /etc/chrony/conf.d.
+confdir /etc/chrony/conf.d
+
+# Use Debian vendor zone.
+pool 2.debian.pool.ntp.org iburst
+
+# Use time sources from DHCP.
+sourcedir /run/chrony-dhcp
+
+# Use NTP sources found in /etc/chrony/sources.d.
+sourcedir /etc/chrony/sources.d
+
+# This directive specify the location of the file containing ID/key pairs for
+# NTP authentication.
+keyfile /etc/chrony/chrony.keys
+
+# This directive specify the file into which chronyd will store the rate
+# information.
+driftfile /var/lib/chrony/chrony.drift
+
+# Save NTS keys and cookies.
+ntsdumpdir /var/lib/chrony
+
+# Uncomment the following line to turn logging on.
+#log tracking measurements statistics
+
+# Log files location.
+logdir /var/log/chrony
+
+# Stop bad estimates upsetting machine clock.
+maxupdateskew 100.0
+
+# This directive enables kernel synchronisation (every 11 minutes) of the
+# real-time clock. Note that it can’t be used along with the 'rtcfile' directive.
+rtcsync
+
+# Always step the time if it's more than 4ms off.
+makestep 0.004 -1
+
+# The next 2 settings are used to control how much slewing of the clock chrony
+# is allowed to do. The total will be the sum of both and we have a hard limit
+# at 500ppm coming from AOS.
+
+# The maximum slewing allowed to correct an offset with the reference clock.
+# Note that this value doesn't include corrections due to frequency errors
+# (drift).
+maxslewrate 200
+
+# Maximum frequency error (drift) of the clock. Chrony will try to compensate
+# for drift and this setting limits how large that correction can be.
+maxdrift 100
+
+# Get TAI-UTC offset and leap seconds from the system tz database.
+# This directive must be commented out when using time sources serving
+# leap-smeared time.
+leapsectz right/UTC
diff --git a/frc971/orin/contents/etc/fstab b/frc971/orin/contents/etc/fstab
new file mode 100644
index 0000000..fd54025
--- /dev/null
+++ b/frc971/orin/contents/etc/fstab
@@ -0,0 +1,8 @@
+# /etc/fstab: static file system information.
+#
+# Use 'blkid' to print the universally unique identifier for a
+# device; this may be used with UUID= as a more robust way to name devices
+# that works even if disks are added and removed. See fstab(5).
+#
+# <file system> <mount point>   <type>  <options>       <dump>  <pass>
+/dev/nvme0n1p1 / xfs rw,noatime,discard 0 0
diff --git a/frc971/orin/contents/etc/security/limits.d/rt.conf b/frc971/orin/contents/etc/security/limits.d/rt.conf
new file mode 100644
index 0000000..ad2c08b
--- /dev/null
+++ b/frc971/orin/contents/etc/security/limits.d/rt.conf
@@ -0,0 +1,3 @@
+pi - nice -20
+pi - rtprio 95
+pi - memlock unlimited
diff --git a/frc971/orin/contents/etc/sudoers b/frc971/orin/contents/etc/sudoers
new file mode 100644
index 0000000..4ce0b30
--- /dev/null
+++ b/frc971/orin/contents/etc/sudoers
@@ -0,0 +1,27 @@
+#
+# This file MUST be edited with the 'visudo' command as root.
+#
+# Please consider adding local content in /etc/sudoers.d/ instead of
+# directly modifying this file.
+#
+# See the man page for details on how to write a sudoers file.
+#
+Defaults        env_reset
+Defaults        mail_badpass
+Defaults        secure_path="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
+
+# Host alias specification
+
+# User alias specification
+
+# Cmnd alias specification
+
+# User privilege specification
+root    ALL=(ALL:ALL) ALL
+
+# Allow members of group sudo to execute any command
+%sudo   ALL=NOPASSWD: ALL
+
+# See sudoers(5) for more information on "@include" directives:
+
+@includedir /etc/sudoers.d
diff --git a/frc971/orin/contents/etc/sysctl.d/sctp.conf b/frc971/orin/contents/etc/sysctl.d/sctp.conf
new file mode 100644
index 0000000..1fcf2ca
--- /dev/null
+++ b/frc971/orin/contents/etc/sysctl.d/sctp.conf
@@ -0,0 +1,4 @@
+# https://wwwx.cs.unc.edu/~sparkst/howto/network_tuning.php
+# Make the buffers big enough for an image
+net.core.wmem_max=8388608
+net.core.rmem_max=8388608
diff --git a/frc971/orin/contents/etc/systemd/logind.conf b/frc971/orin/contents/etc/systemd/logind.conf
new file mode 100644
index 0000000..17ced30
--- /dev/null
+++ b/frc971/orin/contents/etc/systemd/logind.conf
@@ -0,0 +1,36 @@
+#  This file is part of systemd.
+#
+#  systemd is free software; you can redistribute it and/or modify it
+#  under the terms of the GNU Lesser General Public License as published by
+#  the Free Software Foundation; either version 2.1 of the License, or
+#  (at your option) any later version.
+#
+# See logind.conf(5) for details.
+
+[Login]
+#NAutoVTs=6
+#ReserveVT=6
+#KillUserProcesses=no
+#KillOnlyUsers=
+#KillExcludeUsers=root
+#InhibitDelayMaxSec=5
+#HandlePowerKey=poweroff
+#HandleSuspendKey=suspend
+#HandleHibernateKey=hibernate
+#HandleLidSwitch=suspend
+#HandleLidSwitchExternalPower=suspend
+#HandleLidSwitchDocked=ignore
+#PowerKeyIgnoreInhibited=no
+#SuspendKeyIgnoreInhibited=no
+#HibernateKeyIgnoreInhibited=no
+#LidSwitchIgnoreInhibited=yes
+#HoldoffTimeoutSec=30s
+#IdleAction=ignore
+#IdleActionSec=30min
+#RuntimeDirectorySize=10%
+
+# We don't want systemd removing IPC.  This makes it so nothing new can talk to
+# existing channels.
+RemoveIPC=no
+#InhibitorsMax=8192
+#SessionsMax=8192
diff --git a/frc971/orin/contents/etc/systemd/network/80-can.network b/frc971/orin/contents/etc/systemd/network/80-can.network
new file mode 100644
index 0000000..e75db33
--- /dev/null
+++ b/frc971/orin/contents/etc/systemd/network/80-can.network
@@ -0,0 +1,9 @@
+[Match]
+Name=can0
+
+[CAN]
+BitRate=1M
+DataBitRate=8M
+RestartSec=1000ms
+BusErrorReporting=yes
+FDMode=yes
diff --git a/frc971/orin/contents/etc/systemd/network/eth0.network b/frc971/orin/contents/etc/systemd/network/eth0.network
new file mode 100644
index 0000000..8bfaf61
--- /dev/null
+++ b/frc971/orin/contents/etc/systemd/network/eth0.network
@@ -0,0 +1,11 @@
+[Match]
+Name=eth0
+
+[Network]
+Address=10.9.71.20/24
+Gateway=10.9.71.13
+DNS=8.8.8.8
+
+# ipv6 adds an extra 10 seconds to boot, and we don't use it...
+# Disable all the route discovery and stuff
+LinkLocalAddressing=no
diff --git a/frc971/orin/contents/etc/systemd/system/grow-rootfs.service b/frc971/orin/contents/etc/systemd/system/grow-rootfs.service
new file mode 100644
index 0000000..5ab3803
--- /dev/null
+++ b/frc971/orin/contents/etc/systemd/system/grow-rootfs.service
@@ -0,0 +1,19 @@
+[Unit]
+Description=Grow partition and filesystem to fit disk
+DefaultDependencies=no
+Before=local-fs.target
+Before=shutdown.target
+After=-.mount
+BindsTo=-.mount
+
+# Backport of <https://github.com/systemd/systemd/pull/14618>.
+After=systemd-remount-fs.service
+
+[Service]
+Type=oneshot
+ExecStart=/bin/bash -c "/sbin/xfs_growfs $(systemctl show --property What --value -- -.mount)"
+TimeoutSec=0
+RemainAfterExit=yes
+
+[Install]
+WantedBy=multi-user.target
diff --git a/frc971/orin/contents/etc/systemd/system/usb-mount@.service b/frc971/orin/contents/etc/systemd/system/usb-mount@.service
new file mode 100644
index 0000000..934d456
--- /dev/null
+++ b/frc971/orin/contents/etc/systemd/system/usb-mount@.service
@@ -0,0 +1,7 @@
+[Unit]
+Description=Mount USB Drive on %i
+[Service]
+Type=oneshot
+RemainAfterExit=true
+ExecStart=/usr/bin/pmount --umask 000 /dev/%i /media/%i
+ExecStop=/usr/bin/pumount /dev/%i
diff --git a/frc971/orin/contents/home/pi/.ssh/authorized_keys b/frc971/orin/contents/home/pi/.ssh/authorized_keys
new file mode 100644
index 0000000..58b82cc
--- /dev/null
+++ b/frc971/orin/contents/home/pi/.ssh/authorized_keys
@@ -0,0 +1,21 @@
+ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDI002gCm4aRVrIcg2G4/qF4D1oNY74HFFAHjNUIgvrmqSEn+Oy+pxigpJFiZZaJMJpaw4kpd1IEpZxhooZm4DC4/bVV3wAFTw/OJI7D75WgrRrBRHd95TMBwYyNUhoDOcPAoZ69+IL9P0rhmNjgCv6Y+3PG+Rl6IqRPuf3dXX/PT3E/h8B18PRkEnas/3WTW8goov6x10kVAa5I+iQansiyAbPQF7E+Q5mpsnl26V2vpHo1UAk7y+TD7jqifEn13TmLeTkDXmaIOflQeOBMAdErftuqrClPa00VbejP18v02RI/jOIAQ250g0hN3zvKi2eNHUPdAzlMB4cSvZspRrB /home/austin/.ssh/id_rsa
+
+ssh-rsa-cert-v01@openssh.com AAAAHHNzaC1yc2EtY2VydC12MDFAb3BlbnNzaC5jb20AAAAgzaqXNuB589EgR6/ljdYhp5Ca+B8eimCTmmC23oQvNyIAAAADAQABAAABAQDI002gCm4aRVrIcg2G4/qF4D1oNY74HFFAHjNUIgvrmqSEn+Oy+pxigpJFiZZaJMJpaw4kpd1IEpZxhooZm4DC4/bVV3wAFTw/OJI7D75WgrRrBRHd95TMBwYyNUhoDOcPAoZ69+IL9P0rhmNjgCv6Y+3PG+Rl6IqRPuf3dXX/PT3E/h8B18PRkEnas/3WTW8goov6x10kVAa5I+iQansiyAbPQF7E+Q5mpsnl26V2vpHo1UAk7y+TD7jqifEn13TmLeTkDXmaIOflQeOBMAdErftuqrClPa00VbejP18v02RI/jOIAQ250g0hN3zvKi2eNHUPdAzlMB4cSvZspRrBAAAAAAAAAAAAAAABAAAAHmF1c3Rpbi5zY2h1aEBibHVlcml2ZXJ0ZWNoLmNvbQAAAA8AAAADYnJ0AAAABHJvb3QAAAAAWGi3PAAAAABjhSpmAAAAAAAAAIIAAAAVcGVybWl0LVgxMS1mb3J3YXJkaW5nAAAAAAAAABdwZXJtaXQtYWdlbnQtZm9yd2FyZGluZwAAAAAAAAAWcGVybWl0LXBvcnQtZm9yd2FyZGluZwAAAAAAAAAKcGVybWl0LXB0eQAAAAAAAAAOcGVybWl0LXVzZXItcmMAAAAAAAAAAAAAAKwAAAATZWNkc2Etc2hhMi1uaXN0cDUyMQAAAAhuaXN0cDUyMQAAAIUEANvRmN8fXmKOO6xQPsllgbHxX+hvP4sU8/ayxw1K9C2MlGT3OKPgnjWEmvEPgpPR+/YQ6asQnP+jucdgCM8q7+c4ATwFnMO7yl2LCU1UkCKShzoumXflKC9rWNVT6MY4PTbpQXui5XE0gIZrjKrkcfCGjvRouUasM/C5Zro/aGQFkL6XAAAApwAAABNlY2RzYS1zaGEyLW5pc3RwNTIxAAAAjAAAAEIAswnueuP8iT7Qbzr1yBx6tLbNY9jewA6NEkLnFJtu11VzBFFaLWxeXwKPy3ajT0DCzt6EX6YKBHfYngnzdyjP9KkAAABCAWsxaA9D59ToYmbEKT//85dczH397v6As8WeQMAMzKfJYVSJBceHiwt6EbRKd6m+xUsd/Sr4Bj/Eu2VvwplqCpOq /home/austin/.ssh/id_rsa
+
+ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDLyVTxr8s3kjz+PEjEymgwC8o64IZgBRJbtq9HZ16ufEHqxCD6WK21v8XsbAyTo3/vIfiae+SxhZTC9PMA1AQXuXCBTcvH1avHlHNPgnfxOfzNpU5LSZx/hqrx9tJ+ELV6m34XUbAhIhXJSyiPE2Mst8et6XUvXLgQ8hr0vwXZ3jitI0WzdoZE2svQhn/Cw+NnFiIyhVm4VTnw0bo5XVvvCawvZdTWsyXIvYx9P7rJ5Kvr1eJTZB+tDynzEFxJZeC+lnE6kV8NudC/7hLwwn1Uvqon17Z4P8ukxDsaD2Y4a2v0zqqN0FkEAKjhcqRWdyHM2JOeygRJa1sABNzt4gJB austin@ASchuh-T480s
+
+ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDDs88Uby+A7k69WSUXp6wmDrsIIFib5bsJ/0oHdjRWSZc4gHVGVF4cFCIJsjvuiQ3LlQ1vVb4ot7kXDNhPWEENRiuMVN3bovTr0fIjSi+YzhidIUZV44LhIkf2XorjpBjfdKE8YyZgYU0wway6myLijoEy6UnLaYerFjUh0k0p+R/axNtD48Glge82pvihosNt4J4592PGbfoTg7hgPizz4Yp39MtYN0OAqHDSrthU/ceA97prMo9tugozHthDasNAb1u/KiOr86dswLiGhwfM0aWAStIu+jie8fKzFtPFFvCyeEaGTYJ/nKiTq2qX2VNLk2zoqXoP6OPHTztejMtRyuRZxx3+mwhDT1lwUQx/ZsFqMTuIOjGQjpzQg3/Q2Y7rnSeQmgc5LglzaH5SRQ7i3nXJvDm1akdHRFFjarBw9Pb2p8DsDaTmJ6gpoEFqZZa1RM5ZCab8bL9z0pHBdpqhIXcPflDQE7Qi8win+LlWBwFyjhu5PvNnAKFEv6uQC1M= austin@aschuh-3950x
+
+ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDONITvwkh8whp/ac9mhqIk+H4JV7gHIO6OYiOeJfd8mJGAKbox6JNPsnZ2Yewse9oc+MDKZ+dyLqBsfIVG0MUh96hJZHwIYW6wObcZt7Zj6c+JzbAQNavWltsu0IIA7wVrdz2qOXLuH3MhM5URP29IsmF9EUp2H1/iRUMlwz3+1GckYWmiJl+JKNct1DofJb4RdbY2iG34QgS6FvLEKZBxGYPboPD0DLylymdAZAq2TjCcngiq+5BWmy79BLoA2xwJjiz1WPbSILIygfX6e85FGf9ZskylfgHcltAP4xrg3ci7ygJZSw7rd37pV6PfxOz4vGmA7ROT+ocbkXtzkmGk6Qb8HPqT9uRl9Y2Wm4YQMIrREHvjXtGHePCS6R8rrK6og4U4l6Fn4ghicemoRcQXTeX8RJFN+jCpBmks+P6UbYW8k2424kVhNMHz2sHT+BU5Klz9HQvcqlCqupqiPT6HF/gzMZgMHAf2Dp17CpoDbhkhLmCE1TmFOKqNqHP31fs= milindupadhyay@mdu-brt
+
+ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDNUJEb8hoIboZ+FWUp9OX37xRs6s63pXfkF0Hirka+ovIziorNLxR6BixqD7xkVBwXgyMo+YRRy80eHLX2NoItoRLoMfT4uqhu/izdidLcUrpYicAxMIi1pCi1SS5BcKCHbyEqS5FrHzea+0dhIeemb1EP2NXxTDlFUINTni/Aw0z8bB3UI0tY0+K8zypuDKohL9tWOOln6e0VirhPhSCpfs89959jhmeI7Al1DuyZw1vfcaVgQ/7W59rHqP8RNcziMrwwt5hCMQQRxEu4Dd11jBiLklS+wLBI8exP/aEVXGwwc6D/zhg2zma4wQ7HuivZF+sMrP4T1RlLMK3ngbMX james@hp-debian
+
+ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCaElJXdu6YMo7n32jAYXVPW9HKdp2vbE1BcKz+hqZDiIpMtIwmyu+KQD7XHOfkDrDLlsKRRnEvAKAhE+Q5apHdVwnR70h79khBjvRZqXBt+lip3GyGBuymE6+2bAL6zMnZ6oKBvKcPRdIUEbv1jvJV4SfCERGp0/yQxlf/h+yy2pGTEV2iGZerfYBDIyggDzrhjEeiFUL7In4kR2dQId/BPlzZx9AVIdF6CIY7MnfsPBZUkrk9XQrCJOxzN2gGPLdan3DMCp1o2K93dvojl4dQguphanwOdw8wm+wlMiucY/buHgqqbS0EFOz/taQDBFfQ2v7ckht5CFvCY56YpGFaVW7ztf7OYbU6t8LS3dsoN8+rAesBe9fJhJWR/0zFIAM7WW6j95Sppikqh0ZcaQK2/eMKFqn8R7K9zJ20BOI+QYGNnLvIVXlFzRt0/Qy/GczhejpJddwK9zbYxVKJyMNpVz3ZUlr+nRR2UtlcM+lWe7Kbu3HxTPBNk6vSCXPE+Pc= ravago@rjmacAir
+
+ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDSB1Nji8HEfnQX6E2FIfyvolrlihiBKukyLMA5isJhtvdaOXVOrSilpd91wB+ljLOfDy+4XWK4+8p84tvggdfvJdsLRtSBhExJNqe2ViyfUqh0O61lZg4Yvw6DGFEdS+DrqIkZh1v3WQ1Py1Mpt5V18dSu42DttHCigXFnYlpZfx9n4QT6GphkWYE3hLWHQH3uSkujzgkr86WreUi4idRDmq/r21H/MBx4q3uwWuftLS6oX79y7aukpieopeoWN8WUNscwUpUpmAn6rdFe/IHl0u8Zw4wmXoYSIQErFGsOK/rA3nKuK0uvVvUQkVKBZpjMOAAuaLugu9NYsOgqIZf/ filip@Filips-MacBook-Pro.local
+
+ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILZrP0on5xPZHadvlMN2/+iZzEbIeGpS5MT5hXfb+kP0 uid@pop-os
+
+ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC+t3HrUbZb9P/zWw9+qH9i8HBcCBRO6BS0rYZMz3UuXFSxhLendcqmAJQ+cu1lI0sb2HAyIhv4d5CQrjZ2en/ZyN93DThWNRWA+iH+ybkJhpUiRVSa/e1rz06o//kL+PVoZEHRUpyy7iJgEgcRxnVZd90baMkkm51bF0NSVn+iGCIao76VS+PBGwtQFghJyAvTz+w/hLYmFtXnY2DRfPvbw8H3cehvDhUBY9V+MUwucMU38Yfucr/BguPyw/sRFzRJtpIzP+JFQZzy/Bz4DI06vIWBps6NZqGo+ETXsrlEmNRKXgp3YWMZg/VcT6IIi9NijtDt6gHNDLfxX1Q41cYErBIaUpDKWPpGJ9z+/FxtxxBHwhIQPzSvdxqgsoGKyy7rFfWOPqWHzASsAcdx8V0EPIL4VmJQOd461WRfZxAhDdo2hQe37joRjcHW2i1nJcpVSepMf7EJH/AoYcifp+b+JUpMZx79HmKtE1cua2JcfZ6b20w7rQzLIP3M2WvrDak= deepachainani@Yashs-MBP
+
+ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDG+8q7VDZv1bI2s/LDLZvjX4HInQobPF9QfBi8sNJdu98drWzSIlTIRMfOFuN8Vbq2E/wr6eKXOUwq/Brxu4sZ6+po6+sT6IqyE5cd+FffNJXZUc+QxXC6maD20i7k8gNJuo+BRa6VbSR25WarRoGT+pYl03jbhR304Wn8wIYIS5cAfaP2wzwNPOvCZzzwJrba2jeoeVWRUTuqLRAw4NNq5eZskdpv7w/3qw901z7RKzuTfYiAnvsRm8o6E0uRncyujzvXoXD2jyWdKC/boIv/l1Is/XwsSJahq1NIK3y95jZHoUXpDQsq49U0wOByBpKEyQYcI54nhfwYagVsYRY9 jim.o@bluerivert.com
diff --git a/frc971/orin/contents/root/bin/change_hostname.sh b/frc971/orin/contents/root/bin/change_hostname.sh
new file mode 100755
index 0000000..785903e
--- /dev/null
+++ b/frc971/orin/contents/root/bin/change_hostname.sh
@@ -0,0 +1,55 @@
+#!/bin/bash
+
+set -xeuo pipefail
+
+HOSTNAME="$1"
+
+# TODO<Jim>: Should probably add handling for imu hostname, too
+if [[ ! "${HOSTNAME}" =~ ^pi-[0-9]*-[0-9]$ ]]; then
+  echo "Invalid hostname ${HOSTNAME}, needs to be pi-[team#]-[pi#]"
+  exit 1
+fi
+
+TEAM_NUMBER="$(echo ${HOSTNAME} | sed 's/pi-\(.*\)-.*/\1/')"
+PI_NUMBER="$(echo ${HOSTNAME} | sed 's/pi-.*-\(.*\)/\1/')"
+IP_BASE="$(echo ${TEAM_NUMBER} | sed 's/\(.*\)\(..\)/10.\1.\2/')"
+IP="${IP_BASE}.$(( 100 + ${PI_NUMBER}))"
+
+echo "Changing to team number ${TEAM_NUMBER}, IP ${IP}"
+
+sed -i "s/^Address=.*$/Address=${IP}\/24/" /etc/systemd/network/eth0.network
+sed -i "s/^Gateway=.*$/Gateway=${IP_BASE}.13/" /etc/systemd/network/eth0.network
+
+echo "${HOSTNAME}" > /etc/hostname
+
+# Make sure a 127.0.* entry exists to make things looking up localhost happy.
+if grep '^127.0.1.1' /etc/hosts > /dev/null;
+then
+  sed -i "s/\(127\.0\.1\.1\t\).*$/\1${HOSTNAME}/" /etc/hosts
+else
+  echo -e "127.0.1.1\t${HOSTNAME}" >> /etc/hosts
+fi
+
+# Put corret team number in pi's IP addresses, or add them if needed
+if grep '^10\.[0-9]*\.[0-9]*\.[0-9]*\s*pi-[0-9]*-[0-9] pi[0-9]$' /etc/hosts >/dev/null ;
+then
+  sed -i "s/^10\.[0-9]*\.[0-9]*\(\.[0-9]*\s*pi-\)[0-9]*\(-[0-9] pi[0-9]\)\(.*\)$/${IP_BASE}\1${TEAM_NUMBER}\2\3/" /etc/hosts
+else
+  for i in {1..6}; do
+      imu=""
+      # Add imu name to pi6.  Put space in this string, since extra
+      # spaces otherwise will make the above grep fail
+      if [[ ${i} == 6 ]]; then
+          imu=" imu"
+      fi
+    echo -e "${IP_BASE}.$(( i + 100 ))\tpi-${TEAM_NUMBER}-${i} pi${i}${imu}" >> /etc/hosts
+  done
+fi
+
+# Put correct team number in roborio's address, or add it if missing
+if grep '^10\.[0-9]*\.[0-9]*\.2\s*roborio$' /etc/hosts >/dev/null;
+then
+  sed -i "s/^10\.[0-9]*\.[0-9]*\(\.2\s*roborio\)$/${IP_BASE}\1/" /etc/hosts
+else
+  echo -e "${IP_BASE}.2\troborio" >> /etc/hosts
+fi
diff --git a/frc971/orin/contents/root/trace.sh b/frc971/orin/contents/root/trace.sh
new file mode 100644
index 0000000..4b69b4e
--- /dev/null
+++ b/frc971/orin/contents/root/trace.sh
@@ -0,0 +1,19 @@
+#!/bin/sh
+
+echo 1 > /sys/kernel/debug/tracing/tracing_on
+echo 30720 > /sys/kernel/debug/tracing/buffer_size_kb
+echo 1 > /sys/kernel/debug/tracing/events/tegra_rtcpu/enable
+echo 1 > /sys/kernel/debug/tracing/events/freertos/enable
+echo 2 > /sys/kernel/debug/camrtc/log-level
+echo 1 > /sys/kernel/debug/tracing/events/camera_common/enable
+echo > /sys/kernel/debug/tracing/trace
+
+echo file vi2_fops.c +p > /sys/kernel/debug/dynamic_debug/control
+echo file csi2_fops.c +p > /sys/kernel/debug/dynamic_debug/control
+
+echo file vi4_fops.c +p > /sys/kernel/debug/dynamic_debug/control
+echo file csi.c +p > /sys/kernel/debug/dynamic_debug/control
+echo file csi4_fops.c +p > /sys/kernel/debug/dynamic_debug/control
+echo file nvcsi.c +p > /sys/kernel/debug/dynamic_debug/control
+
+cat /sys/kernel/debug/tracing/trace /sys/kernel/debug/tracing/trace_pipe
diff --git a/frc971/orin/contents/run/systemd/resolve/stub-resolv.conf b/frc971/orin/contents/run/systemd/resolve/stub-resolv.conf
new file mode 100644
index 0000000..fa4bebb
--- /dev/null
+++ b/frc971/orin/contents/run/systemd/resolve/stub-resolv.conf
@@ -0,0 +1,3 @@
+domain lan
+search lan
+nameserver 127.0.0.53
diff --git a/frc971/orin/contents/var/nvidia/nvcam/settings/camera_overrides.isp b/frc971/orin/contents/var/nvidia/nvcam/settings/camera_overrides.isp
new file mode 100644
index 0000000..82363b2
--- /dev/null
+++ b/frc971/orin/contents/var/nvidia/nvcam/settings/camera_overrides.isp
@@ -0,0 +1,1761 @@
+# Char-lite Calibration
+# command:     nv_wrapper 0 optical_black_file ..\imx477\outputs\ob.cfg lsc ..\imx477\outputs\lsc.cfg awb ..\imx477\outputs\awb.cfg ccm ..\imx477\outputs\ccm.cfg public_params public_params.cfg out_name ..\imx477\outputs\camera_overrides.isp
+# version:     1.8.0
+# created on: 16-Jul-2020 17:10
+
+#-**************************************************************
+#-* Do not modify
+#-**************************************************************
+#* =============================================================
+#* ISP Configuration Override File
+#* Copyright (c) 2012-2017, NVIDIA CORPORATION.  All rights reserved.
+#* Model:model
+#* Integrator:
+#* Part#:
+#* Sensor:sensor
+#* Size:
+#* Version: version
+#* FileName & Location:/data/nvcam/settings/camera_overrides.isp
+#* Updated:Thu Jul 20 19:15:20 2017 PST
+#* =============================================================
+ap15Function.lensShading = TRUE;
+ae.MeanAlg.HigherTarget = 120;
+ae.MeanAlg.LowerTarget  = 120;
+ae.MeanAlg.HigherBrightness = 10000;
+ae.MeanAlg.LowerBrightness  =   600;
+ae.MeanAlg.SlopFactor = 0.3;
+ae.MeanAlg.MinTailMass = 0.001;
+ae.MeanAlg.CriticalMass = 0.015;
+ae.MeanAlg.ConvergeSpeed = 0.5;
+ae.MaxFstopDeltaPos = 0.4;
+ae.MaxFstopDeltaNeg = 0.5;
+defaults.autoFramerateRange = {7.5, 120.0};
+ae.ApertureStepToFNumberLUT = {2.4};
+ae.ExposureTuningTable.Preview[0] = {2.4, 0.03333, 1.0, 1.0};
+ae.ExposureTuningTable.Preview[1] = {2.4, 0.03333, 2.0, 1.0};
+ae.ExposureTuningTable.Preview[2] = {2.4, 0.03333, 4.0, 1.0};
+ae.ExposureTuningTable.Preview[3] = {2.4, 0.03333, 8.0, 1.0};
+ae.ExposureTuningTable.Preview[4] = {2.4, 0.03333, 8.0, 1.0};
+ae.ExposureTuningTable.Preview[5] = {2.4, 0.06666, 8.0, 1.0};
+ae.ExposureTuningTable.Preview[6] = {2.4, 0.06666, 16.0, 1.0};
+ae.ExposureTuningTable.Preview[6] = {2.4, 0.06666, 22.0, 1.0};
+flicker.ConfidenceThreshold = 32;
+flicker.SuccessFrameCount = 8;
+flicker.FailureFrameCount = 3;
+flicker.CorrectionFreqListEntries = 2;
+flicker.CorrectionFreqList = {100, 120};
+defaults.saturation = 1.0;
+ae.saturation.Preview[0] = {100, 100, 100, 100, 100, 100, 100};
+ae.saturation.Preview[1] = {100, 100, 100, 100, 100, 100, 100};
+ae.saturation.Preview[2] = {100, 100, 100, 100, 100, 100, 100};
+ae.saturation.Preview[3] = {100, 100, 100, 100, 100, 100, 100};
+ae.saturation.Still[0] = {100, 100, 100, 100, 100, 100, 100};
+ae.saturation.Still[1] = {100, 100, 100, 100, 100, 100, 100};
+ae.saturation.Still[2] = {100, 100, 100, 100, 100, 100, 100};
+ae.saturation.Still[3] = {100, 100, 100, 100, 100, 100, 100};
+ae.saturation.Video[0] = {100, 100, 100, 100, 100, 100, 100};
+ae.saturation.Video[1] = {100, 100, 100, 100, 100, 100, 100};
+ae.saturation.Video[2] = {100, 100, 100, 100, 100, 100, 100};
+ae.saturation.Video[3] = {100, 100, 100, 100, 100, 100, 100};
+sharpness.v2.Preview[0] = {3, 3, 5, 5, 5, 5, 5};
+sharpness.v2.Preview[1] = {3, 3, 5, 5, 5, 5, 5};
+sharpness.v2.Preview[2] = {3, 3, 5, 5, 5, 5, 5};
+sharpness.v2.Preview[3] = {3, 3, 5, 5, 5, 5, 5};
+sharpness.v2.Still[0] = {3, 3, 5, 5, 5, 5, 5};
+sharpness.v2.Still[1] = {3, 3, 5, 5, 5, 5, 5};
+sharpness.v2.Still[2] = {3, 3, 5, 5, 5, 5, 5};
+sharpness.v2.Still[3] = {3, 3, 5, 5, 5, 5, 5};
+sharpness.v2.Video[0] = {3, 3, 5, 5, 5, 5, 5};
+sharpness.v2.Video[1] = {3, 3, 5, 5, 5, 5, 5};
+sharpness.v2.Video[2] = {3, 3, 5, 5, 5, 5, 5};
+sharpness.v2.Video[3] = {3, 3, 5, 5, 5, 5, 5};
+tc.Enable=true;
+tc.Version=v2;
+tc.v2.user.presets[0].Gamma.Value=2.4000;
+tc.v2.user.presets[0].UserCurve.Enable=FALSE;
+tc.v2.user.presets[0].UserCurve.Points={ 0.0000, 0.0000, 0.2500, 0.2500, 0.5000, 0.5000, 0.7500, 0.7500, 1.0000, 1.0000 };
+tc.v2.user.presets[0].CurveControl.Enable=TRUE;
+tc.v2.user.presets[0].CurveControl.AdjustHighlights=0.7500;
+tc.v2.user.presets[0].CurveControl.MidtoneBrightness=0.5000;
+tc.v2.user.presets[0].CurveControl.MidtoneContrast=0.5000;
+tc.v2.user.presets[0].CurveControl.AdjustShadows=0.2500;
+tc.v2.user.presets[0].Brightness.Enable=FALSE;
+tc.v2.user.presets[0].Brightness.Value=1.0000;
+tc.v2.user.presets[1].Gamma.Value=2.4000;
+tc.v2.user.presets[1].UserCurve.Enable=FALSE;
+tc.v2.user.presets[1].UserCurve.Points={ 0.0000, 0.0000, 0.2500, 0.2500, 0.5000, 0.5000, 0.7500, 0.7500, 1.0000, 1.0000 };
+tc.v2.user.presets[1].CurveControl.Enable=TRUE;
+tc.v2.user.presets[1].CurveControl.AdjustHighlights=0.7500;
+tc.v2.user.presets[1].CurveControl.MidtoneBrightness=0.5000;
+tc.v2.user.presets[1].CurveControl.MidtoneContrast=0.5000;
+tc.v2.user.presets[1].CurveControl.AdjustShadows=0.2500;
+tc.v2.user.presets[1].Brightness.Enable=FALSE;
+tc.v2.user.presets[1].Brightness.Value=1.0000;
+tc.v2.user.presets[2].Gamma.Value=2.4000;
+tc.v2.user.presets[2].UserCurve.Enable=FALSE;
+tc.v2.user.presets[2].UserCurve.Points={ 0.0000, 0.0000, 0.2500, 0.2500, 0.5000, 0.5000, 0.7500, 0.7500, 1.0000, 1.0000 };
+tc.v2.user.presets[2].CurveControl.Enable=TRUE;
+tc.v2.user.presets[2].CurveControl.AdjustHighlights=0.7500;
+tc.v2.user.presets[2].CurveControl.MidtoneBrightness=0.5000;
+tc.v2.user.presets[2].CurveControl.MidtoneContrast=0.5000;
+tc.v2.user.presets[2].CurveControl.AdjustShadows=0.2500;
+tc.v2.user.presets[2].Brightness.Enable=FALSE;
+tc.v2.user.presets[2].Brightness.Value=1.0000;
+tc.v2.user.presets[3].Gamma.Value=2.4000;
+tc.v2.user.presets[3].UserCurve.Enable=FALSE;
+tc.v2.user.presets[3].UserCurve.Points={ 0.0000, 0.0000, 0.2500, 0.2500, 0.5000, 0.5000, 0.7500, 0.7500, 1.0000, 1.0000 };
+tc.v2.user.presets[3].CurveControl.Enable=TRUE;
+tc.v2.user.presets[3].CurveControl.AdjustHighlights=0.7500;
+tc.v2.user.presets[3].CurveControl.MidtoneBrightness=0.5000;
+tc.v2.user.presets[3].CurveControl.MidtoneContrast=0.5000;
+tc.v2.user.presets[3].CurveControl.AdjustShadows=0.2500;
+tc.v2.user.presets[3].Brightness.Enable=FALSE;
+tc.v2.user.presets[3].Brightness.Value=1.0000;
+tc.v2.user.presets[4].Gamma.Value=2.4000;
+tc.v2.user.presets[4].UserCurve.Enable=FALSE;
+tc.v2.user.presets[4].UserCurve.Points={ 0.0000, 0.0000, 0.2500, 0.2500, 0.5000, 0.5000, 0.7500, 0.7500, 1.0000, 1.0000 };
+tc.v2.user.presets[4].CurveControl.Enable=TRUE;
+tc.v2.user.presets[4].CurveControl.AdjustHighlights=0.7500;
+tc.v2.user.presets[4].CurveControl.MidtoneBrightness=0.5000;
+tc.v2.user.presets[4].CurveControl.MidtoneContrast=0.5000;
+tc.v2.user.presets[4].CurveControl.AdjustShadows=0.2500;
+tc.v2.user.presets[4].Brightness.Enable=FALSE;
+tc.v2.user.presets[4].Brightness.Value=1.0000;
+tc.v2.user.presets[5].Gamma.Value=2.4000;
+tc.v2.user.presets[5].UserCurve.Enable=FALSE;
+tc.v2.user.presets[5].UserCurve.Points={ 0.0000, 0.0000, 0.2500, 0.2500, 0.5000, 0.5000, 0.7500, 0.7500, 1.0000, 1.0000 };
+tc.v2.user.presets[5].CurveControl.Enable=TRUE;
+tc.v2.user.presets[5].CurveControl.AdjustHighlights=0.7500;
+tc.v2.user.presets[5].CurveControl.MidtoneBrightness=0.5000;
+tc.v2.user.presets[5].CurveControl.MidtoneContrast=0.5000;
+tc.v2.user.presets[5].CurveControl.AdjustShadows=0.2500;
+tc.v2.user.presets[5].Brightness.Enable=FALSE;
+tc.v2.user.presets[5].Brightness.Value=1.0000;
+tc.v2.user.presets[6].Gamma.Value=2.4000;
+tc.v2.user.presets[6].UserCurve.Enable=FALSE;
+tc.v2.user.presets[6].UserCurve.Points={ 0.0000, 0.0000, 0.2500, 0.2500, 0.5000, 0.5000, 0.7500, 0.7500, 1.0000, 1.0000 };
+tc.v2.user.presets[6].CurveControl.Enable=TRUE;
+tc.v2.user.presets[6].CurveControl.AdjustHighlights=0.7500;
+tc.v2.user.presets[6].CurveControl.MidtoneBrightness=0.5000;
+tc.v2.user.presets[6].CurveControl.MidtoneContrast=0.5000;
+tc.v2.user.presets[6].CurveControl.AdjustShadows=0.2500;
+tc.v2.user.presets[6].Brightness.Enable=FALSE;
+tc.v2.user.presets[6].Brightness.Value=1.0000;
+tc.v2.user.presets[7].Gamma.Value=2.4000;
+tc.v2.user.presets[7].UserCurve.Enable=FALSE;
+tc.v2.user.presets[7].UserCurve.Points={ 0.0000, 0.0000, 0.2500, 0.2500, 0.5000, 0.5000, 0.7500, 0.7500, 1.0000, 1.0000 };
+tc.v2.user.presets[7].CurveControl.Enable=TRUE;
+tc.v2.user.presets[7].CurveControl.AdjustHighlights=0.7500;
+tc.v2.user.presets[7].CurveControl.MidtoneBrightness=0.5000;
+tc.v2.user.presets[7].CurveControl.MidtoneContrast=0.5000;
+tc.v2.user.presets[7].CurveControl.AdjustShadows=0.2500;
+tc.v2.user.presets[7].Brightness.Enable=FALSE;
+tc.v2.user.presets[7].Brightness.Value=1.0000;
+tc.v2.user.presets[8].Gamma.Value=2.4000;
+tc.v2.user.presets[8].UserCurve.Enable=FALSE;
+tc.v2.user.presets[8].UserCurve.Points={ 0.0000, 0.0000, 0.2500, 0.2500, 0.5000, 0.5000, 0.7500, 0.7500, 1.0000, 1.0000 };
+tc.v2.user.presets[8].CurveControl.Enable=TRUE;
+tc.v2.user.presets[8].CurveControl.AdjustHighlights=0.7500;
+tc.v2.user.presets[8].CurveControl.MidtoneBrightness=0.5000;
+tc.v2.user.presets[8].CurveControl.MidtoneContrast=0.5000;
+tc.v2.user.presets[8].CurveControl.AdjustShadows=0.2500;
+tc.v2.user.presets[8].Brightness.Enable=FALSE;
+tc.v2.user.presets[8].Brightness.Value=1.0000;
+tc.v2.user.UseIndices={ 0, 0, 0, 0, 0, 0, 0, 0, 0 };
+tc.v2.user.FlashUseIndices= { 0, 0, 0, 0, 0, 0, 0, 0, 0 };
+tc.v2.UseAxisX={ 1068.0000, 2136.0000, 9970.0000, 14242.0000 };
+tc.v2.UseAxisY={ 1.0000, 1.0000, 1.0000, 1.0000 };
+tc.v2.FlashUseAxisX={ 1068.0000, 2136.0000, 9970.0000, 14242.0000 };
+tc.v2.FlashUseAxisY={ 1.0000, 1.0000, 1.0000, 1.0000 };
+tc.v2.initStats.brightness=3811;
+tc.v2.initStats.tcdre=1.0;
+flash.IntensityCalibrateEnable = false;
+flash.Led1.FlashLevel[1] = 180;
+flash.Led1.TorchLevel[1] = 100;
+af.module_cal_enable = 0;
+af.settle_time = 30;
+af.inf = 220;
+af.macro = 510;
+af.inf_offset = 0;
+af.macro_offset = 0;
+af.macro_max = 100;
+noiseReduction.v2.MaxValue = 7;
+noiseReduction.v2.Preview[0] = { 1, 2, 3, 5, 6, 7, 7 };
+noiseReduction.v2.Preview[1] = { 1, 2, 3, 5, 6, 7, 7 };
+noiseReduction.v2.Preview[2] = { 1, 2, 3, 5, 6, 7, 7 };
+noiseReduction.v2.Preview[3] = { 1, 2, 3, 5, 6, 7, 7 };
+noiseReduction.v2.Still[0] = { 1, 2, 3, 5, 6, 7, 7 };
+noiseReduction.v2.Still[1] = { 1, 2, 3, 5, 6, 7, 7 };
+noiseReduction.v2.Still[2] = { 1, 2, 3, 5, 6, 7, 7 };
+noiseReduction.v2.Still[3] = { 1, 2, 3, 5, 6, 7, 7 };
+noiseReduction.v2.Video[0] = { 1, 2, 3, 5, 6, 7, 7 };
+noiseReduction.v2.Video[1] = { 1, 2, 3, 5, 6, 7, 7 };
+noiseReduction.v2.Video[2] = { 1, 2, 3, 5, 6, 7, 7 };
+noiseReduction.v2.Video[3] = { 1, 2, 3, 5, 6, 7, 7 };
+noiseReduction.v6.Chroma.Enable = TRUE;
+noiseReduction.v6.Luma.Enable = TRUE;
+noiseReduction.v6.Chroma.GainThreshold = 1.0;
+noiseReduction.v6.Luma.GainThreshold = 2.0;
+noiseReduction.v6.NumSignalConditioningPoints = 7;
+noiseReduction.v6.SignalConditioning[0].Gain = 2;
+noiseReduction.v6.SignalConditioning[0].NoiseBlend = 0.95;
+noiseReduction.v6.SignalConditioning[0].Shaping = 1.0;
+noiseReduction.v6.SignalConditioning[1].Gain = 4;
+noiseReduction.v6.SignalConditioning[1].NoiseBlend = 0.90;
+noiseReduction.v6.SignalConditioning[1].Shaping = 1.0;
+noiseReduction.v6.SignalConditioning[2].Gain = 6;
+noiseReduction.v6.SignalConditioning[2].NoiseBlend = 0.86;
+noiseReduction.v6.SignalConditioning[2].Shaping = 1.0;
+noiseReduction.v6.SignalConditioning[3].Gain = 8;
+noiseReduction.v6.SignalConditioning[3].NoiseBlend = 0.82;
+noiseReduction.v6.SignalConditioning[3].Shaping = 1.0;
+noiseReduction.v6.SignalConditioning[4].Gain = 10;
+noiseReduction.v6.SignalConditioning[4].NoiseBlend = 0.80;
+noiseReduction.v6.SignalConditioning[4].Shaping = 1.0;
+noiseReduction.v6.SignalConditioning[5].Gain = 12;
+noiseReduction.v6.SignalConditioning[5].NoiseBlend = 0.79;
+noiseReduction.v6.SignalConditioning[5].Shaping = 1.0;
+noiseReduction.v6.SignalConditioning[6].Gain = 16;
+noiseReduction.v6.SignalConditioning[6].NoiseBlend = 0.40;
+noiseReduction.v6.SignalConditioning[6].Shaping = 1.0;
+noiseReduction.v6.NumChromaPoints = 7;
+noiseReduction.v6.Chroma[0].Gain = 1;
+noiseReduction.v6.Chroma[0].FilterStrength = 0.03;
+noiseReduction.v6.Chroma[0].Levels = 2;
+noiseReduction.v6.Chroma[0].Scaling = 1.0;
+noiseReduction.v6.Chroma[0].SignalBoost = 1.0;
+noiseReduction.v6.Chroma[1].Gain = 1.1;
+noiseReduction.v6.Chroma[1].FilterStrength = 0.04;
+noiseReduction.v6.Chroma[1].Levels = 2;
+noiseReduction.v6.Chroma[1].Scaling = 1.0;
+noiseReduction.v6.Chroma[1].SignalBoost = 1.0;
+noiseReduction.v6.Chroma[2].Gain = 2;
+noiseReduction.v6.Chroma[2].FilterStrength = 0.06;
+noiseReduction.v6.Chroma[2].Levels = 3;
+noiseReduction.v6.Chroma[2].Scaling = 1.0;
+noiseReduction.v6.Chroma[2].SignalBoost = 1.0;
+noiseReduction.v6.Chroma[3].Gain = 4;
+noiseReduction.v6.Chroma[3].FilterStrength = 0.07;
+noiseReduction.v6.Chroma[3].Levels = 3;
+noiseReduction.v6.Chroma[3].Scaling = 1.0;
+noiseReduction.v6.Chroma[3].SignalBoost = 1.0;
+noiseReduction.v6.Chroma[4].Gain = 6;
+noiseReduction.v6.Chroma[4].FilterStrength = 0.08;
+noiseReduction.v6.Chroma[4].Levels = 3;
+noiseReduction.v6.Chroma[4].Scaling = 1.0;
+noiseReduction.v6.Chroma[4].SignalBoost = 1.0;
+noiseReduction.v6.Chroma[5].Gain = 8;
+noiseReduction.v6.Chroma[5].FilterStrength = 0.09;
+noiseReduction.v6.Chroma[5].Levels = 3;
+noiseReduction.v6.Chroma[5].Scaling = 1.0;
+noiseReduction.v6.Chroma[5].SignalBoost = 1.0;
+noiseReduction.v6.Chroma[6].Gain = 10;
+noiseReduction.v6.Chroma[6].FilterStrength = 0.10;
+noiseReduction.v6.Chroma[6].Levels = 3;
+noiseReduction.v6.Chroma[6].Scaling = 1.0;
+noiseReduction.v6.Chroma[6].SignalBoost = 1.0;
+noiseReduction.v6.NumLumaPoints = 7;
+noiseReduction.v6.Luma[0].Gain = 2;
+noiseReduction.v6.Luma[0].FilterStrength = 0.5;
+noiseReduction.v6.Luma[0].Levels = 1;
+noiseReduction.v6.Luma[0].Scaling = 1.0;
+noiseReduction.v6.Luma[0].SignalBoost = 1.0;
+noiseReduction.v6.Luma[1].Gain = 4;
+noiseReduction.v6.Luma[1].FilterStrength = 0.8;
+noiseReduction.v6.Luma[1].Levels = 1;
+noiseReduction.v6.Luma[1].Scaling = 1.0;
+noiseReduction.v6.Luma[1].SignalBoost = 1.0;
+noiseReduction.v6.Luma[2].Gain = 6;
+noiseReduction.v6.Luma[2].FilterStrength = 1.0;
+noiseReduction.v6.Luma[2].Levels = 1;
+noiseReduction.v6.Luma[2].Scaling = 1.0;
+noiseReduction.v6.Luma[2].SignalBoost = 1.0;
+noiseReduction.v6.Luma[3].Gain = 8;
+noiseReduction.v6.Luma[3].FilterStrength = 1.3;
+noiseReduction.v6.Luma[3].Levels = 1;
+noiseReduction.v6.Luma[3].Scaling = 1.0;
+noiseReduction.v6.Luma[3].SignalBoost = 1.0;
+noiseReduction.v6.Luma[4].Gain = 10;
+noiseReduction.v6.Luma[4].FilterStrength = 1.5;
+noiseReduction.v6.Luma[4].Levels = 1;
+noiseReduction.v6.Luma[4].Scaling = 1.0;
+noiseReduction.v6.Luma[4].SignalBoost = 1.0;
+noiseReduction.v6.Luma[5].Gain = 12;
+noiseReduction.v6.Luma[5].FilterStrength = 1.6;
+noiseReduction.v6.Luma[5].Levels = 1;
+noiseReduction.v6.Luma[5].Scaling = 1.0;
+noiseReduction.v6.Luma[5].SignalBoost = 1.0;
+noiseReduction.v6.Luma[6].Gain = 16;
+noiseReduction.v6.Luma[6].FilterStrength = 0.20;
+noiseReduction.v6.Luma[6].Levels = 1;
+noiseReduction.v6.Luma[6].Scaling = 1.0;
+noiseReduction.v6.Luma[6].SignalBoost = 1.0;
+colorEffects.sepia.param1 = -0.1500;
+colorEffects.sepia.param2 = 0.2000;
+colorEffects.aqua.param1 = 0.3000;
+colorEffects.aqua.param2 = -0.3500;
+colorEffects.solarize.numPoints = 4;
+colorEffects.solarize.point[0] = {0, 0};
+colorEffects.solarize.point[1] = {128, 160};
+colorEffects.solarize.point[2] = {196, 160};
+colorEffects.solarize.point[3] = {256, 0};
+colorEffects.solarize.point[4] = {0, 0};
+colorEffects.solarize.point[5] = {0, 0};
+colorEffects.solarize.point[6] = {0, 0};
+colorEffects.solarize.point[7] = {0, 0};
+colorEffects.solarize.point[8] = {0, 0};
+colorEffects.solarize.point[9] = {0, 0};
+colorEffects.posterize.numPoints = 8;
+colorEffects.posterize.point[0] = {0, 0};
+colorEffects.posterize.point[1] = {54, 0};
+colorEffects.posterize.point[2] = {69, 85};
+colorEffects.posterize.point[3] = {123, 85};
+colorEffects.posterize.point[4] = {138, 170};
+colorEffects.posterize.point[5] = {192, 170};
+colorEffects.posterize.point[6] = {207, 256};
+colorEffects.posterize.point[7] = {256, 256};
+colorEffects.posterize.point[8] = {0, 0};
+colorEffects.posterize.point[9] = {0, 0};
+mwbCCTTint.incandescent    = {2950,  0.0};
+mwbCCTTint.fluorescent     = {4300,  0.0};
+mwbCCTTint.warmfluorescent = {2940,  0.0};
+mwbCCTTint.daylight        = {6100,  0.0};
+mwbCCTTint.cloudy          = {7000,  0.0};
+mwbCCTTint.twilight        = {10000, 0.0};
+mwbCCTTint.shade           = {6550,  0.0};
+lensShading.module_cal_enable = 0;
+awb.module_cal_enable = 0;
+lensShading.falloff_PointsCount = 6;
+lensShading.falloff.Preview[0] = {1.0, 20.0};
+lensShading.falloff.Preview[1] = {2.0, 20.0};
+lensShading.falloff.Preview[2] = {4.0, 20.0};
+lensShading.falloff.Preview[3] = {8.0, 20.0};
+lensShading.falloff.Preview[4] = {16.0, 30.0};
+lensShading.falloff.Preview[5] = {32.0, 30.0};
+lensShading.falloff.Still[0] = {1.0, 20.0};
+lensShading.falloff.Still[1] = {2.0, 20.0};
+lensShading.falloff.Still[2] = {4.0, 20.0};
+lensShading.falloff.Still[3] = {8.0, 20.0};
+lensShading.falloff.Still[4] = {16.0, 30.0};
+lensShading.falloff.Still[5] = {32.0, 30.0};
+lensShading.falloff.Video[0] = {1.0, 20.0};
+lensShading.falloff.Video[1] = {2.0, 20.0};
+lensShading.falloff.Video[2] = {4.0, 20.0};
+lensShading.falloff.Video[3] = {8.0, 20.0};
+lensShading.falloff.Video[4] = {16.0, 30.0};
+lensShading.falloff.Video[5] = {32.0, 30.0};
+#-----------------------------------------------------
+# AWB Parameters 
+#-----------------------------------------------------
+
+# AWB
+awb.NumGrayLineSoftClampPoints	 = 2; 
+awb.GrayLineSoftClamp[0]  	 = {0.9268,  0.9268}; 
+awb.GrayLineSoftClamp[1]  	 = {1.6582,  1.6582}; 
+awb.GrayLineSoftClamp[2]  	 = {0.0000,  0.0000}; 
+awb.GrayLineSoftClamp[3]  	 = {0.0000,  0.0000}; 
+awb.GrayLineSoftClamp[4]  	 = {0.0000,  0.0000}; 
+awb.GrayLineSoftClamp[5]  	 = {0.0000,  0.0000}; 
+awb.GrayLineSoftClamp[6]  	 = {0.0000,  0.0000}; 
+awb.GrayLineSoftClamp[7]  	 = {0.0000,  0.0000}; 
+awb.GrayLineSoftClamp[8]  	 = {0.0000,  0.0000}; 
+awb.GrayLineSoftClamp[9]  	 = {0.0000,  0.0000}; 
+awb.GrayLineSoftClamp[10] 	 = {0.0000,  0.0000}; 
+awb.GrayLineSoftClamp[11] 	 = {0.0000,  0.0000}; 
+awb.GrayLineSoftClamp[12] 	 = {0.0000,  0.0000}; 
+awb.GrayLineSoftClamp[13] 	 = {0.0000,  0.0000}; 
+awb.GrayLineSoftClamp[14] 	 = {0.0000,  0.0000}; 
+awb.GrayLineSoftClamp[15] 	 = {0.0000,  0.0000}; 
+awb.GrayLineSoftClampSlopeBeforeFirstPoint	 = 0.0000; 
+awb.GrayLineSoftClampSlopeAfterLastPoint	 = 0.0000; 
+awb.GrayLineThickness     	 = 0.0088; 
+awb.HighU                 	 = 1.6582; 
+awb.LowU                  	 = 0.9268; 
+awb.v4.NumGrayLineSoftClampPoints	 = 3; 
+awb.v4.GrayLineSoftClamp[0]	 = {0.9268,  0.9268}; 
+awb.v4.GrayLineSoftClamp[1]	 = {0.9268,  0.9268}; 
+awb.v4.GrayLineSoftClamp[2]	 = {1.6582,  1.6582}; 
+awb.v4.GrayLineSoftClamp[3]	 = {0.0000,  0.0000}; 
+awb.v4.GrayLineSoftClamp[4]	 = {0.0000,  0.0000}; 
+awb.v4.GrayLineSoftClamp[5]	 = {0.0000,  0.0000}; 
+awb.v4.GrayLineSoftClamp[6]	 = {0.0000,  0.0000}; 
+awb.v4.GrayLineSoftClamp[7]	 = {0.0000,  0.0000}; 
+awb.v4.GrayLineSoftClamp[8]	 = {0.0000,  0.0000}; 
+awb.v4.GrayLineSoftClamp[9]	 = {0.0000,  0.0000}; 
+awb.v4.GrayLineSoftClamp[10]	 = {0.0000,  0.0000}; 
+awb.v4.GrayLineSoftClamp[11]	 = {0.0000,  0.0000}; 
+awb.v4.GrayLineSoftClamp[12]	 = {0.0000,  0.0000}; 
+awb.v4.GrayLineSoftClamp[13]	 = {0.0000,  0.0000}; 
+awb.v4.GrayLineSoftClamp[14]	 = {0.0000,  0.0000}; 
+awb.v4.GrayLineSoftClamp[15]	 = {0.0000,  0.0000}; 
+awb.v4.GrayLineSoftClampSlopeBeforeFirstPoint	 = 0.0000; 
+awb.v4.GrayLineSoftClampSlopeAfterLastPoint	 = 0.0000; 
+awb.v4.GrayLineThickness  	 = 0.0442; 
+awb.v4.HighU              	 = 1.6582; 
+awb.v4.LowU               	 = 0.9268; 
+
+#Lens shading falloff factor
+lensShading.falloff_factor = 20;
+#-**************************************************************************
+#-* Below parameters are calculated from calibration process.  Do not edit! 
+#-**************************************************************************
+
+# AWB
+awb.GrayLineSlope         	 = -0.9946; 
+awb.GrayLineIntercept     	 = 2.4544; 
+awb.UtoMIRED              	 = {-330.909004284264, 700.071557556732}; 
+awb.MIREDtoU              	 = {-0.003021978813, 2.115601414567}; 
+awb.UtoCCT                	 = {5351.936855019085,-2504.031973940120}; 
+awb.CCTtoU                	 = {0.000186848243,0.467873975679}; 
+awb.NumDiscreteLights     	 = 0; 
+awb.v4.GrayLineSlope      	 = -0.9946; 
+awb.v4.GrayLineIntercept  	 = 2.4544; 
+awb.v4.UtoMIRED           	 = {-330.909004284264, 700.071557556732}; 
+awb.v4.MIREDtoU           	 = {-0.003021978813, 2.115601414567}; 
+awb.v4.UtoCCT             	 = {5147.548412945803,-2189.798723064104}; 
+awb.v4.CCTtoU             	 = {0.000194267236,0.425406144322}; 
+awb.v4.FusionNumLights    	 = 5; 
+awb.v4.FusionLights[0]    	 = {224,384,384,135}; 
+awb.v4.FusionLights[1]    	 = {224,384,384,135}; 
+awb.v4.FusionLights[2]    	 = {249,594,594,281}; 
+awb.v4.FusionLights[3]    	 = {227,555,555,253}; 
+awb.v4.FusionLights[4]    	 = {214,662,662,442}; 
+awb.v4.FusionInitLight    	 = 4; 
+# CCM
+colorCorrection.srgbMatrix[0]	 = {1.53743000,-0.19831000, 0.05253000}; 
+colorCorrection.srgbMatrix[1]	 = {-0.20559000, 1.69073000,-0.45222000}; 
+colorCorrection.srgbMatrix[2]	 = {-0.33184000,-0.49242000, 1.39969000}; 
+# - sensor gain value corresponding to ISO100
+ae.PerChannelGainAdjustment = {1.0, 1.0, 1.0, 1.0}; 
+
+# Optical Black
+opticalBlack.manualBiasR  	 = 64; 
+opticalBlack.manualBiasGR 	 = 64; 
+opticalBlack.manualBiasGB 	 = 64; 
+opticalBlack.manualBiasB  	 = 64; 
+opticalBlack.float.manualBiasR	 = 0.06256109; 
+opticalBlack.float.manualBiasGR	 = 0.06256109; 
+opticalBlack.float.manualBiasGB	 = 0.06256109; 
+opticalBlack.float.manualBiasB	 = 0.06256109; 
+#-----------------------------------------------------
+# Lens Shading Parameters 
+ap15Function.lensShading = TRUE;
+
+#-----------------------------------------------------
+
+# Lens Shading Surfaces
+falloff_srfc.controlPoint[0][0]	 = 0.000000000000; 
+falloff_srfc.controlPoint[0][1]	 = 0.170708874040; 
+falloff_srfc.controlPoint[0][2]	 = 0.340238317593; 
+falloff_srfc.controlPoint[0][3]	 = 0.452085048909; 
+falloff_srfc.controlPoint[0][4]	 = 0.677164760676; 
+falloff_srfc.controlPoint[0][5]	 = 0.677330142506; 
+falloff_srfc.controlPoint[0][6]	 = 0.452002154012; 
+falloff_srfc.controlPoint[0][7]	 = 0.339406304229; 
+falloff_srfc.controlPoint[0][8]	 = 0.170434181672; 
+falloff_srfc.controlPoint[0][9]	 = 0.000000000000; 
+falloff_srfc.controlPoint[1][0]	 = 0.072132534499; 
+falloff_srfc.controlPoint[1][1]	 = 0.296887547258; 
+falloff_srfc.controlPoint[1][2]	 = 0.466416990811; 
+falloff_srfc.controlPoint[1][3]	 = 0.578263722127; 
+falloff_srfc.controlPoint[1][4]	 = 0.803343433894; 
+falloff_srfc.controlPoint[1][5]	 = 0.803508815724; 
+falloff_srfc.controlPoint[1][6]	 = 0.578180827230; 
+falloff_srfc.controlPoint[1][7]	 = 0.465584977447; 
+falloff_srfc.controlPoint[1][8]	 = 0.296612854890; 
+falloff_srfc.controlPoint[1][9]	 = 0.071377962941; 
+falloff_srfc.controlPoint[2][0]	 = 0.167436650479; 
+falloff_srfc.controlPoint[2][1]	 = 0.392191663238; 
+falloff_srfc.controlPoint[2][2]	 = 0.561721106791; 
+falloff_srfc.controlPoint[2][3]	 = 0.673567838107; 
+falloff_srfc.controlPoint[2][4]	 = 0.898647549874; 
+falloff_srfc.controlPoint[2][5]	 = 0.898812931703; 
+falloff_srfc.controlPoint[2][6]	 = 0.673484943209; 
+falloff_srfc.controlPoint[2][7]	 = 0.560889093426; 
+falloff_srfc.controlPoint[2][8]	 = 0.391916970870; 
+falloff_srfc.controlPoint[2][9]	 = 0.166682078921; 
+falloff_srfc.controlPoint[3][0]	 = 0.230173639911; 
+falloff_srfc.controlPoint[3][1]	 = 0.454928652670; 
+falloff_srfc.controlPoint[3][2]	 = 0.624458096222; 
+falloff_srfc.controlPoint[3][3]	 = 0.736304827538; 
+falloff_srfc.controlPoint[3][4]	 = 0.961384539306; 
+falloff_srfc.controlPoint[3][5]	 = 0.961549921135; 
+falloff_srfc.controlPoint[3][6]	 = 0.736221932641; 
+falloff_srfc.controlPoint[3][7]	 = 0.623626082858; 
+falloff_srfc.controlPoint[3][8]	 = 0.454653960301; 
+falloff_srfc.controlPoint[3][9]	 = 0.229419068352; 
+falloff_srfc.controlPoint[4][0]	 = 0.356522103492; 
+falloff_srfc.controlPoint[4][1]	 = 0.581277116251; 
+falloff_srfc.controlPoint[4][2]	 = 0.750806559804; 
+falloff_srfc.controlPoint[4][3]	 = 0.862653291120; 
+falloff_srfc.controlPoint[4][4]	 = 1.087733002887; 
+falloff_srfc.controlPoint[4][5]	 = 1.087898384717; 
+falloff_srfc.controlPoint[4][6]	 = 0.862570396223; 
+falloff_srfc.controlPoint[4][7]	 = 0.749974546440; 
+falloff_srfc.controlPoint[4][8]	 = 0.581002423883; 
+falloff_srfc.controlPoint[4][9]	 = 0.355767531934; 
+falloff_srfc.controlPoint[5][0]	 = 0.356704584387; 
+falloff_srfc.controlPoint[5][1]	 = 0.581459597146; 
+falloff_srfc.controlPoint[5][2]	 = 0.750989040699; 
+falloff_srfc.controlPoint[5][3]	 = 0.862835772015; 
+falloff_srfc.controlPoint[5][4]	 = 1.087915483783; 
+falloff_srfc.controlPoint[5][5]	 = 1.088080865612; 
+falloff_srfc.controlPoint[5][6]	 = 0.862752877118; 
+falloff_srfc.controlPoint[5][7]	 = 0.750157027335; 
+falloff_srfc.controlPoint[5][8]	 = 0.581184904778; 
+falloff_srfc.controlPoint[5][9]	 = 0.355950012829; 
+falloff_srfc.controlPoint[6][0]	 = 0.230082637765; 
+falloff_srfc.controlPoint[6][1]	 = 0.454837650524; 
+falloff_srfc.controlPoint[6][2]	 = 0.624367094077; 
+falloff_srfc.controlPoint[6][3]	 = 0.736213825393; 
+falloff_srfc.controlPoint[6][4]	 = 0.961293537160; 
+falloff_srfc.controlPoint[6][5]	 = 0.961458918989; 
+falloff_srfc.controlPoint[6][6]	 = 0.736130930495; 
+falloff_srfc.controlPoint[6][7]	 = 0.623535080712; 
+falloff_srfc.controlPoint[6][8]	 = 0.454562958156; 
+falloff_srfc.controlPoint[6][9]	 = 0.229328066207; 
+falloff_srfc.controlPoint[7][0]	 = 0.166849337330; 
+falloff_srfc.controlPoint[7][1]	 = 0.391604350089; 
+falloff_srfc.controlPoint[7][2]	 = 0.561133793642; 
+falloff_srfc.controlPoint[7][3]	 = 0.672980524958; 
+falloff_srfc.controlPoint[7][4]	 = 0.898060236726; 
+falloff_srfc.controlPoint[7][5]	 = 0.898225618555; 
+falloff_srfc.controlPoint[7][6]	 = 0.672897630061; 
+falloff_srfc.controlPoint[7][7]	 = 0.560301780278; 
+falloff_srfc.controlPoint[7][8]	 = 0.391329657721; 
+falloff_srfc.controlPoint[7][9]	 = 0.166094765772; 
+falloff_srfc.controlPoint[8][0]	 = 0.071906715933; 
+falloff_srfc.controlPoint[8][1]	 = 0.296661728692; 
+falloff_srfc.controlPoint[8][2]	 = 0.466191172244; 
+falloff_srfc.controlPoint[8][3]	 = 0.578037903561; 
+falloff_srfc.controlPoint[8][4]	 = 0.803117615328; 
+falloff_srfc.controlPoint[8][5]	 = 0.803282997157; 
+falloff_srfc.controlPoint[8][6]	 = 0.577955008663; 
+falloff_srfc.controlPoint[8][7]	 = 0.465359158880; 
+falloff_srfc.controlPoint[8][8]	 = 0.296387036323; 
+falloff_srfc.controlPoint[8][9]	 = 0.071152144374; 
+falloff_srfc.controlPoint[9][0]	 = 0.000000000000; 
+falloff_srfc.controlPoint[9][1]	 = 0.170146049063; 
+falloff_srfc.controlPoint[9][2]	 = 0.339675492615; 
+falloff_srfc.controlPoint[9][3]	 = 0.451522223932; 
+falloff_srfc.controlPoint[9][4]	 = 0.676601935699; 
+falloff_srfc.controlPoint[9][5]	 = 0.676767317528; 
+falloff_srfc.controlPoint[9][6]	 = 0.451439329034; 
+falloff_srfc.controlPoint[9][7]	 = 0.338843479251; 
+falloff_srfc.controlPoint[9][8]	 = 0.169871356694; 
+falloff_srfc.controlPoint[9][9]	 = 0.000000000000; 
+lensShading.correction_type.enableWPC	 = FALSE; 
+lensShading.correction_type.enableLSC	 = TRUE; 
+lensShading.ctrlPointsCount	 = 3; 
+lensShading.imageHeight   	 = 3040; 
+lensShading.imageWidth    	 = 4056; 
+lensShading.leftPatchFactor	 = 0.25000; 
+lensShading.centerPatchFactor	 = 0.50000; 
+lensShading.topPatchFactor	 = 0.25000; 
+lensShading.middlePatchFactor	 = 0.50000; 
+lensShading.ctrlPoints[0].cct	 = 2856; 
+lensShading.ctrlPoints[0].light_family	 = 1; 
+lensShading.ctrlPoints[0].controlPointR[0][0]	 = 1.134521484375; 
+lensShading.ctrlPoints[0].controlPointR[0][1]	 = 0.960693359375; 
+lensShading.ctrlPoints[0].controlPointR[0][2]	 = 1.151367187500; 
+lensShading.ctrlPoints[0].controlPointR[0][3]	 = 1.100463867188; 
+lensShading.ctrlPoints[0].controlPointR[0][4]	 = 1.139526367188; 
+lensShading.ctrlPoints[0].controlPointR[0][5]	 = 1.077514648438; 
+lensShading.ctrlPoints[0].controlPointR[0][6]	 = 1.081542968750; 
+lensShading.ctrlPoints[0].controlPointR[0][7]	 = 1.109619140625; 
+lensShading.ctrlPoints[0].controlPointR[0][8]	 = 0.838378906250; 
+lensShading.ctrlPoints[0].controlPointR[0][9]	 = 1.468627929688; 
+lensShading.ctrlPoints[0].controlPointR[1][0]	 = 1.025634765625; 
+lensShading.ctrlPoints[0].controlPointR[1][1]	 = 1.146606445313; 
+lensShading.ctrlPoints[0].controlPointR[1][2]	 = 1.111816406250; 
+lensShading.ctrlPoints[0].controlPointR[1][3]	 = 1.135498046875; 
+lensShading.ctrlPoints[0].controlPointR[1][4]	 = 1.042846679688; 
+lensShading.ctrlPoints[0].controlPointR[1][5]	 = 1.204956054688; 
+lensShading.ctrlPoints[0].controlPointR[1][6]	 = 1.078491210938; 
+lensShading.ctrlPoints[0].controlPointR[1][7]	 = 1.197753906250; 
+lensShading.ctrlPoints[0].controlPointR[1][8]	 = 0.818115234375; 
+lensShading.ctrlPoints[0].controlPointR[1][9]	 = 1.154174804688; 
+lensShading.ctrlPoints[0].controlPointR[2][0]	 = 1.112792968750; 
+lensShading.ctrlPoints[0].controlPointR[2][1]	 = 1.075439453125; 
+lensShading.ctrlPoints[0].controlPointR[2][2]	 = 1.148925781250; 
+lensShading.ctrlPoints[0].controlPointR[2][3]	 = 1.136962890625; 
+lensShading.ctrlPoints[0].controlPointR[2][4]	 = 1.143798828125; 
+lensShading.ctrlPoints[0].controlPointR[2][5]	 = 1.107055664063; 
+lensShading.ctrlPoints[0].controlPointR[2][6]	 = 1.113891601563; 
+lensShading.ctrlPoints[0].controlPointR[2][7]	 = 1.111083984375; 
+lensShading.ctrlPoints[0].controlPointR[2][8]	 = 1.073852539063; 
+lensShading.ctrlPoints[0].controlPointR[2][9]	 = 1.022583007813; 
+lensShading.ctrlPoints[0].controlPointR[3][0]	 = 1.095703125000; 
+lensShading.ctrlPoints[0].controlPointR[3][1]	 = 1.097290039063; 
+lensShading.ctrlPoints[0].controlPointR[3][2]	 = 1.140258789063; 
+lensShading.ctrlPoints[0].controlPointR[3][3]	 = 1.126342773438; 
+lensShading.ctrlPoints[0].controlPointR[3][4]	 = 1.130981445313; 
+lensShading.ctrlPoints[0].controlPointR[3][5]	 = 1.116210937500; 
+lensShading.ctrlPoints[0].controlPointR[3][6]	 = 1.117797851563; 
+lensShading.ctrlPoints[0].controlPointR[3][7]	 = 1.092651367188; 
+lensShading.ctrlPoints[0].controlPointR[3][8]	 = 1.078857421875; 
+lensShading.ctrlPoints[0].controlPointR[3][9]	 = 1.041381835938; 
+lensShading.ctrlPoints[0].controlPointR[4][0]	 = 1.107177734375; 
+lensShading.ctrlPoints[0].controlPointR[4][1]	 = 1.159790039063; 
+lensShading.ctrlPoints[0].controlPointR[4][2]	 = 1.095092773438; 
+lensShading.ctrlPoints[0].controlPointR[4][3]	 = 1.141723632813; 
+lensShading.ctrlPoints[0].controlPointR[4][4]	 = 1.090087890625; 
+lensShading.ctrlPoints[0].controlPointR[4][5]	 = 1.096801757813; 
+lensShading.ctrlPoints[0].controlPointR[4][6]	 = 1.114868164063; 
+lensShading.ctrlPoints[0].controlPointR[4][7]	 = 1.161254882813; 
+lensShading.ctrlPoints[0].controlPointR[4][8]	 = 1.033935546875; 
+lensShading.ctrlPoints[0].controlPointR[4][9]	 = 1.079833984375; 
+lensShading.ctrlPoints[0].controlPointR[5][0]	 = 1.095092773438; 
+lensShading.ctrlPoints[0].controlPointR[5][1]	 = 1.094238281250; 
+lensShading.ctrlPoints[0].controlPointR[5][2]	 = 1.160766601563; 
+lensShading.ctrlPoints[0].controlPointR[5][3]	 = 1.147216796875; 
+lensShading.ctrlPoints[0].controlPointR[5][4]	 = 1.112060546875; 
+lensShading.ctrlPoints[0].controlPointR[5][5]	 = 1.165771484375; 
+lensShading.ctrlPoints[0].controlPointR[5][6]	 = 1.118530273438; 
+lensShading.ctrlPoints[0].controlPointR[5][7]	 = 1.082763671875; 
+lensShading.ctrlPoints[0].controlPointR[5][8]	 = 1.109252929688; 
+lensShading.ctrlPoints[0].controlPointR[5][9]	 = 1.014770507813; 
+lensShading.ctrlPoints[0].controlPointR[6][0]	 = 1.158203125000; 
+lensShading.ctrlPoints[0].controlPointR[6][1]	 = 1.112304687500; 
+lensShading.ctrlPoints[0].controlPointR[6][2]	 = 1.166992187500; 
+lensShading.ctrlPoints[0].controlPointR[6][3]	 = 1.148315429688; 
+lensShading.ctrlPoints[0].controlPointR[6][4]	 = 1.166870117188; 
+lensShading.ctrlPoints[0].controlPointR[6][5]	 = 1.145141601563; 
+lensShading.ctrlPoints[0].controlPointR[6][6]	 = 1.122192382813; 
+lensShading.ctrlPoints[0].controlPointR[6][7]	 = 1.142456054688; 
+lensShading.ctrlPoints[0].controlPointR[6][8]	 = 1.010253906250; 
+lensShading.ctrlPoints[0].controlPointR[6][9]	 = 1.154907226563; 
+lensShading.ctrlPoints[0].controlPointR[7][0]	 = 1.243652343750; 
+lensShading.ctrlPoints[0].controlPointR[7][1]	 = 1.133300781250; 
+lensShading.ctrlPoints[0].controlPointR[7][2]	 = 1.146850585938; 
+lensShading.ctrlPoints[0].controlPointR[7][3]	 = 1.158447265625; 
+lensShading.ctrlPoints[0].controlPointR[7][4]	 = 1.169311523438; 
+lensShading.ctrlPoints[0].controlPointR[7][5]	 = 1.160888671875; 
+lensShading.ctrlPoints[0].controlPointR[7][6]	 = 1.123657226563; 
+lensShading.ctrlPoints[0].controlPointR[7][7]	 = 1.081054687500; 
+lensShading.ctrlPoints[0].controlPointR[7][8]	 = 1.047363281250; 
+lensShading.ctrlPoints[0].controlPointR[7][9]	 = 1.225341796875; 
+lensShading.ctrlPoints[0].controlPointR[8][0]	 = 1.191650390625; 
+lensShading.ctrlPoints[0].controlPointR[8][1]	 = 1.046508789063; 
+lensShading.ctrlPoints[0].controlPointR[8][2]	 = 1.124511718750; 
+lensShading.ctrlPoints[0].controlPointR[8][3]	 = 1.161865234375; 
+lensShading.ctrlPoints[0].controlPointR[8][4]	 = 1.163696289063; 
+lensShading.ctrlPoints[0].controlPointR[8][5]	 = 1.153076171875; 
+lensShading.ctrlPoints[0].controlPointR[8][6]	 = 1.125976562500; 
+lensShading.ctrlPoints[0].controlPointR[8][7]	 = 1.067016601563; 
+lensShading.ctrlPoints[0].controlPointR[8][8]	 = 1.066406250000; 
+lensShading.ctrlPoints[0].controlPointR[8][9]	 = 1.251098632813; 
+lensShading.ctrlPoints[0].controlPointR[9][0]	 = 1.592041015625; 
+lensShading.ctrlPoints[0].controlPointR[9][1]	 = 1.106567382813; 
+lensShading.ctrlPoints[0].controlPointR[9][2]	 = 1.173828125000; 
+lensShading.ctrlPoints[0].controlPointR[9][3]	 = 1.132080078125; 
+lensShading.ctrlPoints[0].controlPointR[9][4]	 = 1.157226562500; 
+lensShading.ctrlPoints[0].controlPointR[9][5]	 = 1.137695312500; 
+lensShading.ctrlPoints[0].controlPointR[9][6]	 = 1.100219726563; 
+lensShading.ctrlPoints[0].controlPointR[9][7]	 = 1.160766601563; 
+lensShading.ctrlPoints[0].controlPointR[9][8]	 = 1.062622070313; 
+lensShading.ctrlPoints[0].controlPointR[9][9]	 = 1.739868164063; 
+lensShading.ctrlPoints[0].controlPointGR[0][0]	 = 1.100097656250; 
+lensShading.ctrlPoints[0].controlPointGR[0][1]	 = 1.002075195313; 
+lensShading.ctrlPoints[0].controlPointGR[0][2]	 = 1.099365234375; 
+lensShading.ctrlPoints[0].controlPointGR[0][3]	 = 1.088500976563; 
+lensShading.ctrlPoints[0].controlPointGR[0][4]	 = 1.087158203125; 
+lensShading.ctrlPoints[0].controlPointGR[0][5]	 = 1.086181640625; 
+lensShading.ctrlPoints[0].controlPointGR[0][6]	 = 1.064208984375; 
+lensShading.ctrlPoints[0].controlPointGR[0][7]	 = 1.101684570313; 
+lensShading.ctrlPoints[0].controlPointGR[0][8]	 = 0.797363281250; 
+lensShading.ctrlPoints[0].controlPointGR[0][9]	 = 1.501220703125; 
+lensShading.ctrlPoints[0].controlPointGR[1][0]	 = 1.005615234375; 
+lensShading.ctrlPoints[0].controlPointGR[1][1]	 = 1.123046875000; 
+lensShading.ctrlPoints[0].controlPointGR[1][2]	 = 1.123046875000; 
+lensShading.ctrlPoints[0].controlPointGR[1][3]	 = 1.106079101563; 
+lensShading.ctrlPoints[0].controlPointGR[1][4]	 = 1.078369140625; 
+lensShading.ctrlPoints[0].controlPointGR[1][5]	 = 1.133789062500; 
+lensShading.ctrlPoints[0].controlPointGR[1][6]	 = 1.052612304688; 
+lensShading.ctrlPoints[0].controlPointGR[1][7]	 = 1.211547851563; 
+lensShading.ctrlPoints[0].controlPointGR[1][8]	 = 0.774658203125; 
+lensShading.ctrlPoints[0].controlPointGR[1][9]	 = 1.173461914063; 
+lensShading.ctrlPoints[0].controlPointGR[2][0]	 = 1.088745117188; 
+lensShading.ctrlPoints[0].controlPointGR[2][1]	 = 1.061279296875; 
+lensShading.ctrlPoints[0].controlPointGR[2][2]	 = 1.074218750000; 
+lensShading.ctrlPoints[0].controlPointGR[2][3]	 = 1.102661132813; 
+lensShading.ctrlPoints[0].controlPointGR[2][4]	 = 1.085937500000; 
+lensShading.ctrlPoints[0].controlPointGR[2][5]	 = 1.096435546875; 
+lensShading.ctrlPoints[0].controlPointGR[2][6]	 = 1.085571289063; 
+lensShading.ctrlPoints[0].controlPointGR[2][7]	 = 1.080810546875; 
+lensShading.ctrlPoints[0].controlPointGR[2][8]	 = 1.092163085938; 
+lensShading.ctrlPoints[0].controlPointGR[2][9]	 = 0.992431640625; 
+lensShading.ctrlPoints[0].controlPointGR[3][0]	 = 1.065063476563; 
+lensShading.ctrlPoints[0].controlPointGR[3][1]	 = 1.099609375000; 
+lensShading.ctrlPoints[0].controlPointGR[3][2]	 = 1.099365234375; 
+lensShading.ctrlPoints[0].controlPointGR[3][3]	 = 1.106689453125; 
+lensShading.ctrlPoints[0].controlPointGR[3][4]	 = 1.090698242188; 
+lensShading.ctrlPoints[0].controlPointGR[3][5]	 = 1.088623046875; 
+lensShading.ctrlPoints[0].controlPointGR[3][6]	 = 1.085571289063; 
+lensShading.ctrlPoints[0].controlPointGR[3][7]	 = 1.080322265625; 
+lensShading.ctrlPoints[0].controlPointGR[3][8]	 = 1.042724609375; 
+lensShading.ctrlPoints[0].controlPointGR[3][9]	 = 1.036987304688; 
+lensShading.ctrlPoints[0].controlPointGR[4][0]	 = 1.100708007813; 
+lensShading.ctrlPoints[0].controlPointGR[4][1]	 = 1.085083007813; 
+lensShading.ctrlPoints[0].controlPointGR[4][2]	 = 1.118652343750; 
+lensShading.ctrlPoints[0].controlPointGR[4][3]	 = 1.099121093750; 
+lensShading.ctrlPoints[0].controlPointGR[4][4]	 = 1.078613281250; 
+lensShading.ctrlPoints[0].controlPointGR[4][5]	 = 1.072509765625; 
+lensShading.ctrlPoints[0].controlPointGR[4][6]	 = 1.091186523438; 
+lensShading.ctrlPoints[0].controlPointGR[4][7]	 = 1.120605468750; 
+lensShading.ctrlPoints[0].controlPointGR[4][8]	 = 1.055175781250; 
+lensShading.ctrlPoints[0].controlPointGR[4][9]	 = 1.049072265625; 
+lensShading.ctrlPoints[0].controlPointGR[5][0]	 = 1.065551757813; 
+lensShading.ctrlPoints[0].controlPointGR[5][1]	 = 1.103759765625; 
+lensShading.ctrlPoints[0].controlPointGR[5][2]	 = 1.095458984375; 
+lensShading.ctrlPoints[0].controlPointGR[5][3]	 = 1.116943359375; 
+lensShading.ctrlPoints[0].controlPointGR[5][4]	 = 1.092529296875; 
+lensShading.ctrlPoints[0].controlPointGR[5][5]	 = 1.109741210938; 
+lensShading.ctrlPoints[0].controlPointGR[5][6]	 = 1.088989257813; 
+lensShading.ctrlPoints[0].controlPointGR[5][7]	 = 1.098632812500; 
+lensShading.ctrlPoints[0].controlPointGR[5][8]	 = 1.044067382813; 
+lensShading.ctrlPoints[0].controlPointGR[5][9]	 = 1.027587890625; 
+lensShading.ctrlPoints[0].controlPointGR[6][0]	 = 1.129882812500; 
+lensShading.ctrlPoints[0].controlPointGR[6][1]	 = 1.072387695313; 
+lensShading.ctrlPoints[0].controlPointGR[6][2]	 = 1.142822265625; 
+lensShading.ctrlPoints[0].controlPointGR[6][3]	 = 1.111816406250; 
+lensShading.ctrlPoints[0].controlPointGR[6][4]	 = 1.127075195313; 
+lensShading.ctrlPoints[0].controlPointGR[6][5]	 = 1.117553710938; 
+lensShading.ctrlPoints[0].controlPointGR[6][6]	 = 1.098876953125; 
+lensShading.ctrlPoints[0].controlPointGR[6][7]	 = 1.090087890625; 
+lensShading.ctrlPoints[0].controlPointGR[6][8]	 = 1.019653320313; 
+lensShading.ctrlPoints[0].controlPointGR[6][9]	 = 1.123779296875; 
+lensShading.ctrlPoints[0].controlPointGR[7][0]	 = 1.216796875000; 
+lensShading.ctrlPoints[0].controlPointGR[7][1]	 = 1.040893554688; 
+lensShading.ctrlPoints[0].controlPointGR[7][2]	 = 1.096801757813; 
+lensShading.ctrlPoints[0].controlPointGR[7][3]	 = 1.152221679688; 
+lensShading.ctrlPoints[0].controlPointGR[7][4]	 = 1.105346679688; 
+lensShading.ctrlPoints[0].controlPointGR[7][5]	 = 1.128295898438; 
+lensShading.ctrlPoints[0].controlPointGR[7][6]	 = 1.091186523438; 
+lensShading.ctrlPoints[0].controlPointGR[7][7]	 = 1.113647460938; 
+lensShading.ctrlPoints[0].controlPointGR[7][8]	 = 0.949462890625; 
+lensShading.ctrlPoints[0].controlPointGR[7][9]	 = 1.240234375000; 
+lensShading.ctrlPoints[0].controlPointGR[8][0]	 = 1.189575195313; 
+lensShading.ctrlPoints[0].controlPointGR[8][1]	 = 1.028442382813; 
+lensShading.ctrlPoints[0].controlPointGR[8][2]	 = 1.112915039063; 
+lensShading.ctrlPoints[0].controlPointGR[8][3]	 = 1.089843750000; 
+lensShading.ctrlPoints[0].controlPointGR[8][4]	 = 1.161132812500; 
+lensShading.ctrlPoints[0].controlPointGR[8][5]	 = 1.097900390625; 
+lensShading.ctrlPoints[0].controlPointGR[8][6]	 = 1.094238281250; 
+lensShading.ctrlPoints[0].controlPointGR[8][7]	 = 1.018676757813; 
+lensShading.ctrlPoints[0].controlPointGR[8][8]	 = 1.049926757813; 
+lensShading.ctrlPoints[0].controlPointGR[8][9]	 = 1.245361328125; 
+lensShading.ctrlPoints[0].controlPointGR[9][0]	 = 1.515502929688; 
+lensShading.ctrlPoints[0].controlPointGR[9][1]	 = 1.076782226563; 
+lensShading.ctrlPoints[0].controlPointGR[9][2]	 = 1.101684570313; 
+lensShading.ctrlPoints[0].controlPointGR[9][3]	 = 1.114746093750; 
+lensShading.ctrlPoints[0].controlPointGR[9][4]	 = 1.101440429688; 
+lensShading.ctrlPoints[0].controlPointGR[9][5]	 = 1.131469726563; 
+lensShading.ctrlPoints[0].controlPointGR[9][6]	 = 1.059570312500; 
+lensShading.ctrlPoints[0].controlPointGR[9][7]	 = 1.145385742188; 
+lensShading.ctrlPoints[0].controlPointGR[9][8]	 = 1.019897460938; 
+lensShading.ctrlPoints[0].controlPointGR[9][9]	 = 1.733398437500; 
+lensShading.ctrlPoints[0].controlPointGB[0][0]	 = 1.117187500000; 
+lensShading.ctrlPoints[0].controlPointGB[0][1]	 = 1.017211914063; 
+lensShading.ctrlPoints[0].controlPointGB[0][2]	 = 1.104248046875; 
+lensShading.ctrlPoints[0].controlPointGB[0][3]	 = 1.093139648438; 
+lensShading.ctrlPoints[0].controlPointGB[0][4]	 = 1.103027343750; 
+lensShading.ctrlPoints[0].controlPointGB[0][5]	 = 1.083740234375; 
+lensShading.ctrlPoints[0].controlPointGB[0][6]	 = 1.072387695313; 
+lensShading.ctrlPoints[0].controlPointGB[0][7]	 = 1.144775390625; 
+lensShading.ctrlPoints[0].controlPointGB[0][8]	 = 0.770751953125; 
+lensShading.ctrlPoints[0].controlPointGB[0][9]	 = 1.514770507813; 
+lensShading.ctrlPoints[0].controlPointGB[1][0]	 = 1.018310546875; 
+lensShading.ctrlPoints[0].controlPointGB[1][1]	 = 1.127197265625; 
+lensShading.ctrlPoints[0].controlPointGB[1][2]	 = 1.090209960938; 
+lensShading.ctrlPoints[0].controlPointGB[1][3]	 = 1.122802734375; 
+lensShading.ctrlPoints[0].controlPointGB[1][4]	 = 1.068603515625; 
+lensShading.ctrlPoints[0].controlPointGB[1][5]	 = 1.144165039063; 
+lensShading.ctrlPoints[0].controlPointGB[1][6]	 = 1.074829101563; 
+lensShading.ctrlPoints[0].controlPointGB[1][7]	 = 1.159301757813; 
+lensShading.ctrlPoints[0].controlPointGB[1][8]	 = 0.852539062500; 
+lensShading.ctrlPoints[0].controlPointGB[1][9]	 = 1.158813476563; 
+lensShading.ctrlPoints[0].controlPointGB[2][0]	 = 1.115112304688; 
+lensShading.ctrlPoints[0].controlPointGB[2][1]	 = 1.026489257813; 
+lensShading.ctrlPoints[0].controlPointGB[2][2]	 = 1.140136718750; 
+lensShading.ctrlPoints[0].controlPointGB[2][3]	 = 1.093750000000; 
+lensShading.ctrlPoints[0].controlPointGB[2][4]	 = 1.116455078125; 
+lensShading.ctrlPoints[0].controlPointGB[2][5]	 = 1.094360351563; 
+lensShading.ctrlPoints[0].controlPointGB[2][6]	 = 1.088623046875; 
+lensShading.ctrlPoints[0].controlPointGB[2][7]	 = 1.106933593750; 
+lensShading.ctrlPoints[0].controlPointGB[2][8]	 = 1.067504882813; 
+lensShading.ctrlPoints[0].controlPointGB[2][9]	 = 1.041259765625; 
+lensShading.ctrlPoints[0].controlPointGB[3][0]	 = 1.063110351563; 
+lensShading.ctrlPoints[0].controlPointGB[3][1]	 = 1.118896484375; 
+lensShading.ctrlPoints[0].controlPointGB[3][2]	 = 1.090820312500; 
+lensShading.ctrlPoints[0].controlPointGB[3][3]	 = 1.110229492188; 
+lensShading.ctrlPoints[0].controlPointGB[3][4]	 = 1.095947265625; 
+lensShading.ctrlPoints[0].controlPointGB[3][5]	 = 1.096557617188; 
+lensShading.ctrlPoints[0].controlPointGB[3][6]	 = 1.090942382813; 
+lensShading.ctrlPoints[0].controlPointGB[3][7]	 = 1.093261718750; 
+lensShading.ctrlPoints[0].controlPointGB[3][8]	 = 1.048706054688; 
+lensShading.ctrlPoints[0].controlPointGB[3][9]	 = 1.037231445313; 
+lensShading.ctrlPoints[0].controlPointGB[4][0]	 = 1.107788085938; 
+lensShading.ctrlPoints[0].controlPointGB[4][1]	 = 1.068359375000; 
+lensShading.ctrlPoints[0].controlPointGB[4][2]	 = 1.127197265625; 
+lensShading.ctrlPoints[0].controlPointGB[4][3]	 = 1.099121093750; 
+lensShading.ctrlPoints[0].controlPointGB[4][4]	 = 1.084106445313; 
+lensShading.ctrlPoints[0].controlPointGB[4][5]	 = 1.066650390625; 
+lensShading.ctrlPoints[0].controlPointGB[4][6]	 = 1.100585937500; 
+lensShading.ctrlPoints[0].controlPointGB[4][7]	 = 1.133056640625; 
+lensShading.ctrlPoints[0].controlPointGB[4][8]	 = 1.051391601563; 
+lensShading.ctrlPoints[0].controlPointGB[4][9]	 = 1.067993164063; 
+lensShading.ctrlPoints[0].controlPointGB[5][0]	 = 1.061889648438; 
+lensShading.ctrlPoints[0].controlPointGB[5][1]	 = 1.123413085938; 
+lensShading.ctrlPoints[0].controlPointGB[5][2]	 = 1.090698242188; 
+lensShading.ctrlPoints[0].controlPointGB[5][3]	 = 1.124145507813; 
+lensShading.ctrlPoints[0].controlPointGB[5][4]	 = 1.074584960938; 
+lensShading.ctrlPoints[0].controlPointGB[5][5]	 = 1.134033203125; 
+lensShading.ctrlPoints[0].controlPointGB[5][6]	 = 1.088500976563; 
+lensShading.ctrlPoints[0].controlPointGB[5][7]	 = 1.110351562500; 
+lensShading.ctrlPoints[0].controlPointGB[5][8]	 = 1.036376953125; 
+lensShading.ctrlPoints[0].controlPointGB[5][9]	 = 1.032226562500; 
+lensShading.ctrlPoints[0].controlPointGB[6][0]	 = 1.125488281250; 
+lensShading.ctrlPoints[0].controlPointGB[6][1]	 = 1.076782226563; 
+lensShading.ctrlPoints[0].controlPointGB[6][2]	 = 1.135009765625; 
+lensShading.ctrlPoints[0].controlPointGB[6][3]	 = 1.111694335938; 
+lensShading.ctrlPoints[0].controlPointGB[6][4]	 = 1.130371093750; 
+lensShading.ctrlPoints[0].controlPointGB[6][5]	 = 1.120483398438; 
+lensShading.ctrlPoints[0].controlPointGB[6][6]	 = 1.097290039063; 
+lensShading.ctrlPoints[0].controlPointGB[6][7]	 = 1.094482421875; 
+lensShading.ctrlPoints[0].controlPointGB[6][8]	 = 1.017700195313; 
+lensShading.ctrlPoints[0].controlPointGB[6][9]	 = 1.139282226563; 
+lensShading.ctrlPoints[0].controlPointGB[7][0]	 = 1.214599609375; 
+lensShading.ctrlPoints[0].controlPointGB[7][1]	 = 1.060180664063; 
+lensShading.ctrlPoints[0].controlPointGB[7][2]	 = 1.109130859375; 
+lensShading.ctrlPoints[0].controlPointGB[7][3]	 = 1.139404296875; 
+lensShading.ctrlPoints[0].controlPointGB[7][4]	 = 1.105468750000; 
+lensShading.ctrlPoints[0].controlPointGB[7][5]	 = 1.122070312500; 
+lensShading.ctrlPoints[0].controlPointGB[7][6]	 = 1.092529296875; 
+lensShading.ctrlPoints[0].controlPointGB[7][7]	 = 1.104370117188; 
+lensShading.ctrlPoints[0].controlPointGB[7][8]	 = 0.979003906250; 
+lensShading.ctrlPoints[0].controlPointGB[7][9]	 = 1.221557617188; 
+lensShading.ctrlPoints[0].controlPointGB[8][0]	 = 1.171875000000; 
+lensShading.ctrlPoints[0].controlPointGB[8][1]	 = 1.030273437500; 
+lensShading.ctrlPoints[0].controlPointGB[8][2]	 = 1.096435546875; 
+lensShading.ctrlPoints[0].controlPointGB[8][3]	 = 1.103759765625; 
+lensShading.ctrlPoints[0].controlPointGB[8][4]	 = 1.139770507813; 
+lensShading.ctrlPoints[0].controlPointGB[8][5]	 = 1.121459960938; 
+lensShading.ctrlPoints[0].controlPointGB[8][6]	 = 1.088989257813; 
+lensShading.ctrlPoints[0].controlPointGB[8][7]	 = 1.006225585938; 
+lensShading.ctrlPoints[0].controlPointGB[8][8]	 = 1.081665039063; 
+lensShading.ctrlPoints[0].controlPointGB[8][9]	 = 1.246459960938; 
+lensShading.ctrlPoints[0].controlPointGB[9][0]	 = 1.529052734375; 
+lensShading.ctrlPoints[0].controlPointGB[9][1]	 = 1.055419921875; 
+lensShading.ctrlPoints[0].controlPointGB[9][2]	 = 1.115356445313; 
+lensShading.ctrlPoints[0].controlPointGB[9][3]	 = 1.094238281250; 
+lensShading.ctrlPoints[0].controlPointGB[9][4]	 = 1.119750976563; 
+lensShading.ctrlPoints[0].controlPointGB[9][5]	 = 1.108276367188; 
+lensShading.ctrlPoints[0].controlPointGB[9][6]	 = 1.066284179688; 
+lensShading.ctrlPoints[0].controlPointGB[9][7]	 = 1.156982421875; 
+lensShading.ctrlPoints[0].controlPointGB[9][8]	 = 0.994873046875; 
+lensShading.ctrlPoints[0].controlPointGB[9][9]	 = 1.735473632813; 
+lensShading.ctrlPoints[0].controlPointB[0][0]	 = 1.137939453125; 
+lensShading.ctrlPoints[0].controlPointB[0][1]	 = 1.042846679688; 
+lensShading.ctrlPoints[0].controlPointB[0][2]	 = 1.180297851563; 
+lensShading.ctrlPoints[0].controlPointB[0][3]	 = 1.141113281250; 
+lensShading.ctrlPoints[0].controlPointB[0][4]	 = 1.173828125000; 
+lensShading.ctrlPoints[0].controlPointB[0][5]	 = 1.112426757813; 
+lensShading.ctrlPoints[0].controlPointB[0][6]	 = 1.125732421875; 
+lensShading.ctrlPoints[0].controlPointB[0][7]	 = 1.078247070313; 
+lensShading.ctrlPoints[0].controlPointB[0][8]	 = 0.961303710938; 
+lensShading.ctrlPoints[0].controlPointB[0][9]	 = 1.374267578125; 
+lensShading.ctrlPoints[0].controlPointB[1][0]	 = 1.055786132813; 
+lensShading.ctrlPoints[0].controlPointB[1][1]	 = 1.235839843750; 
+lensShading.ctrlPoints[0].controlPointB[1][2]	 = 1.083984375000; 
+lensShading.ctrlPoints[0].controlPointB[1][3]	 = 1.199096679688; 
+lensShading.ctrlPoints[0].controlPointB[1][4]	 = 1.049316406250; 
+lensShading.ctrlPoints[0].controlPointB[1][5]	 = 1.266479492188; 
+lensShading.ctrlPoints[0].controlPointB[1][6]	 = 1.101318359375; 
+lensShading.ctrlPoints[0].controlPointB[1][7]	 = 1.285400390625; 
+lensShading.ctrlPoints[0].controlPointB[1][8]	 = 0.756103515625; 
+lensShading.ctrlPoints[0].controlPointB[1][9]	 = 1.257324218750; 
+lensShading.ctrlPoints[0].controlPointB[2][0]	 = 1.153320312500; 
+lensShading.ctrlPoints[0].controlPointB[2][1]	 = 1.080078125000; 
+lensShading.ctrlPoints[0].controlPointB[2][2]	 = 1.217041015625; 
+lensShading.ctrlPoints[0].controlPointB[2][3]	 = 1.160156250000; 
+lensShading.ctrlPoints[0].controlPointB[2][4]	 = 1.203247070313; 
+lensShading.ctrlPoints[0].controlPointB[2][5]	 = 1.126708984375; 
+lensShading.ctrlPoints[0].controlPointB[2][6]	 = 1.142700195313; 
+lensShading.ctrlPoints[0].controlPointB[2][7]	 = 1.089965820313; 
+lensShading.ctrlPoints[0].controlPointB[2][8]	 = 1.209228515625; 
+lensShading.ctrlPoints[0].controlPointB[2][9]	 = 0.934448242188; 
+lensShading.ctrlPoints[0].controlPointB[3][0]	 = 1.117187500000; 
+lensShading.ctrlPoints[0].controlPointB[3][1]	 = 1.179565429688; 
+lensShading.ctrlPoints[0].controlPointB[3][2]	 = 1.145507812500; 
+lensShading.ctrlPoints[0].controlPointB[3][3]	 = 1.174682617188; 
+lensShading.ctrlPoints[0].controlPointB[3][4]	 = 1.156982421875; 
+lensShading.ctrlPoints[0].controlPointB[3][5]	 = 1.140625000000; 
+lensShading.ctrlPoints[0].controlPointB[3][6]	 = 1.150268554688; 
+lensShading.ctrlPoints[0].controlPointB[3][7]	 = 1.119018554688; 
+lensShading.ctrlPoints[0].controlPointB[3][8]	 = 1.095703125000; 
+lensShading.ctrlPoints[0].controlPointB[3][9]	 = 1.050292968750; 
+lensShading.ctrlPoints[0].controlPointB[4][0]	 = 1.148681640625; 
+lensShading.ctrlPoints[0].controlPointB[4][1]	 = 1.168457031250; 
+lensShading.ctrlPoints[0].controlPointB[4][2]	 = 1.172363281250; 
+lensShading.ctrlPoints[0].controlPointB[4][3]	 = 1.175781250000; 
+lensShading.ctrlPoints[0].controlPointB[4][4]	 = 1.140258789063; 
+lensShading.ctrlPoints[0].controlPointB[4][5]	 = 1.153076171875; 
+lensShading.ctrlPoints[0].controlPointB[4][6]	 = 1.117553710938; 
+lensShading.ctrlPoints[0].controlPointB[4][7]	 = 1.195678710938; 
+lensShading.ctrlPoints[0].controlPointB[4][8]	 = 1.082275390625; 
+lensShading.ctrlPoints[0].controlPointB[4][9]	 = 1.064575195313; 
+lensShading.ctrlPoints[0].controlPointB[5][0]	 = 1.143920898438; 
+lensShading.ctrlPoints[0].controlPointB[5][1]	 = 1.122070312500; 
+lensShading.ctrlPoints[0].controlPointB[5][2]	 = 1.201904296875; 
+lensShading.ctrlPoints[0].controlPointB[5][3]	 = 1.177368164063; 
+lensShading.ctrlPoints[0].controlPointB[5][4]	 = 1.154907226563; 
+lensShading.ctrlPoints[0].controlPointB[5][5]	 = 1.172485351563; 
+lensShading.ctrlPoints[0].controlPointB[5][6]	 = 1.172729492188; 
+lensShading.ctrlPoints[0].controlPointB[5][7]	 = 1.101928710938; 
+lensShading.ctrlPoints[0].controlPointB[5][8]	 = 1.129638671875; 
+lensShading.ctrlPoints[0].controlPointB[5][9]	 = 1.034912109375; 
+lensShading.ctrlPoints[0].controlPointB[6][0]	 = 1.174804687500; 
+lensShading.ctrlPoints[0].controlPointB[6][1]	 = 1.164306640625; 
+lensShading.ctrlPoints[0].controlPointB[6][2]	 = 1.193359375000; 
+lensShading.ctrlPoints[0].controlPointB[6][3]	 = 1.195312500000; 
+lensShading.ctrlPoints[0].controlPointB[6][4]	 = 1.186523437500; 
+lensShading.ctrlPoints[0].controlPointB[6][5]	 = 1.188598632813; 
+lensShading.ctrlPoints[0].controlPointB[6][6]	 = 1.139160156250; 
+lensShading.ctrlPoints[0].controlPointB[6][7]	 = 1.148437500000; 
+lensShading.ctrlPoints[0].controlPointB[6][8]	 = 1.061401367188; 
+lensShading.ctrlPoints[0].controlPointB[6][9]	 = 1.144042968750; 
+lensShading.ctrlPoints[0].controlPointB[7][0]	 = 1.367065429688; 
+lensShading.ctrlPoints[0].controlPointB[7][1]	 = 1.026977539063; 
+lensShading.ctrlPoints[0].controlPointB[7][2]	 = 1.276733398438; 
+lensShading.ctrlPoints[0].controlPointB[7][3]	 = 1.187011718750; 
+lensShading.ctrlPoints[0].controlPointB[7][4]	 = 1.210571289063; 
+lensShading.ctrlPoints[0].controlPointB[7][5]	 = 1.171508789063; 
+lensShading.ctrlPoints[0].controlPointB[7][6]	 = 1.181274414063; 
+lensShading.ctrlPoints[0].controlPointB[7][7]	 = 1.132690429688; 
+lensShading.ctrlPoints[0].controlPointB[7][8]	 = 1.095703125000; 
+lensShading.ctrlPoints[0].controlPointB[7][9]	 = 1.181762695313; 
+lensShading.ctrlPoints[0].controlPointB[8][0]	 = 1.191650390625; 
+lensShading.ctrlPoints[0].controlPointB[8][1]	 = 1.103515625000; 
+lensShading.ctrlPoints[0].controlPointB[8][2]	 = 1.111938476563; 
+lensShading.ctrlPoints[0].controlPointB[8][3]	 = 1.196655273438; 
+lensShading.ctrlPoints[0].controlPointB[8][4]	 = 1.145141601563; 
+lensShading.ctrlPoints[0].controlPointB[8][5]	 = 1.185913085938; 
+lensShading.ctrlPoints[0].controlPointB[8][6]	 = 1.109375000000; 
+lensShading.ctrlPoints[0].controlPointB[8][7]	 = 1.027221679688; 
+lensShading.ctrlPoints[0].controlPointB[8][8]	 = 1.090820312500; 
+lensShading.ctrlPoints[0].controlPointB[8][9]	 = 1.261230468750; 
+lensShading.ctrlPoints[0].controlPointB[9][0]	 = 1.585205078125; 
+lensShading.ctrlPoints[0].controlPointB[9][1]	 = 1.170654296875; 
+lensShading.ctrlPoints[0].controlPointB[9][2]	 = 1.156127929688; 
+lensShading.ctrlPoints[0].controlPointB[9][3]	 = 1.155395507813; 
+lensShading.ctrlPoints[0].controlPointB[9][4]	 = 1.184204101563; 
+lensShading.ctrlPoints[0].controlPointB[9][5]	 = 1.171875000000; 
+lensShading.ctrlPoints[0].controlPointB[9][6]	 = 1.123291015625; 
+lensShading.ctrlPoints[0].controlPointB[9][7]	 = 1.159790039063; 
+lensShading.ctrlPoints[0].controlPointB[9][8]	 = 1.119750976563; 
+lensShading.ctrlPoints[0].controlPointB[9][9]	 = 1.673583984375; 
+lensShading.ctrlPoints[1].cct	 = 4000; 
+lensShading.ctrlPoints[1].light_family	 = 2; 
+lensShading.ctrlPoints[1].controlPointR[0][0]	 = 1.083862304688; 
+lensShading.ctrlPoints[1].controlPointR[0][1]	 = 0.958007812500; 
+lensShading.ctrlPoints[1].controlPointR[0][2]	 = 1.130615234375; 
+lensShading.ctrlPoints[1].controlPointR[0][3]	 = 1.063964843750; 
+lensShading.ctrlPoints[1].controlPointR[0][4]	 = 1.088989257813; 
+lensShading.ctrlPoints[1].controlPointR[0][5]	 = 1.084594726563; 
+lensShading.ctrlPoints[1].controlPointR[0][6]	 = 1.072143554688; 
+lensShading.ctrlPoints[1].controlPointR[0][7]	 = 1.107177734375; 
+lensShading.ctrlPoints[1].controlPointR[0][8]	 = 0.815429687500; 
+lensShading.ctrlPoints[1].controlPointR[0][9]	 = 1.580322265625; 
+lensShading.ctrlPoints[1].controlPointR[1][0]	 = 0.999633789063; 
+lensShading.ctrlPoints[1].controlPointR[1][1]	 = 1.102416992188; 
+lensShading.ctrlPoints[1].controlPointR[1][2]	 = 1.067504882813; 
+lensShading.ctrlPoints[1].controlPointR[1][3]	 = 1.113647460938; 
+lensShading.ctrlPoints[1].controlPointR[1][4]	 = 1.112670898438; 
+lensShading.ctrlPoints[1].controlPointR[1][5]	 = 1.102416992188; 
+lensShading.ctrlPoints[1].controlPointR[1][6]	 = 1.081298828125; 
+lensShading.ctrlPoints[1].controlPointR[1][7]	 = 1.187500000000; 
+lensShading.ctrlPoints[1].controlPointR[1][8]	 = 0.771728515625; 
+lensShading.ctrlPoints[1].controlPointR[1][9]	 = 1.229492187500; 
+lensShading.ctrlPoints[1].controlPointR[2][0]	 = 1.086914062500; 
+lensShading.ctrlPoints[1].controlPointR[2][1]	 = 1.023315429688; 
+lensShading.ctrlPoints[1].controlPointR[2][2]	 = 1.174926757813; 
+lensShading.ctrlPoints[1].controlPointR[2][3]	 = 1.089599609375; 
+lensShading.ctrlPoints[1].controlPointR[2][4]	 = 1.111572265625; 
+lensShading.ctrlPoints[1].controlPointR[2][5]	 = 1.117797851563; 
+lensShading.ctrlPoints[1].controlPointR[2][6]	 = 1.111450195313; 
+lensShading.ctrlPoints[1].controlPointR[2][7]	 = 1.166137695313; 
+lensShading.ctrlPoints[1].controlPointR[2][8]	 = 1.044799804688; 
+lensShading.ctrlPoints[1].controlPointR[2][9]	 = 1.093383789063; 
+lensShading.ctrlPoints[1].controlPointR[3][0]	 = 1.056518554688; 
+lensShading.ctrlPoints[1].controlPointR[3][1]	 = 1.092895507813; 
+lensShading.ctrlPoints[1].controlPointR[3][2]	 = 1.084472656250; 
+lensShading.ctrlPoints[1].controlPointR[3][3]	 = 1.109375000000; 
+lensShading.ctrlPoints[1].controlPointR[3][4]	 = 1.092895507813; 
+lensShading.ctrlPoints[1].controlPointR[3][5]	 = 1.099609375000; 
+lensShading.ctrlPoints[1].controlPointR[3][6]	 = 1.109252929688; 
+lensShading.ctrlPoints[1].controlPointR[3][7]	 = 1.090209960938; 
+lensShading.ctrlPoints[1].controlPointR[3][8]	 = 1.074584960938; 
+lensShading.ctrlPoints[1].controlPointR[3][9]	 = 1.081420898438; 
+lensShading.ctrlPoints[1].controlPointR[4][0]	 = 1.095581054688; 
+lensShading.ctrlPoints[1].controlPointR[4][1]	 = 1.059204101563; 
+lensShading.ctrlPoints[1].controlPointR[4][2]	 = 1.116210937500; 
+lensShading.ctrlPoints[1].controlPointR[4][3]	 = 1.092895507813; 
+lensShading.ctrlPoints[1].controlPointR[4][4]	 = 1.082519531250; 
+lensShading.ctrlPoints[1].controlPointR[4][5]	 = 1.094360351563; 
+lensShading.ctrlPoints[1].controlPointR[4][6]	 = 1.110229492188; 
+lensShading.ctrlPoints[1].controlPointR[4][7]	 = 1.224975585938; 
+lensShading.ctrlPoints[1].controlPointR[4][8]	 = 0.971557617188; 
+lensShading.ctrlPoints[1].controlPointR[4][9]	 = 1.152832031250; 
+lensShading.ctrlPoints[1].controlPointR[5][0]	 = 1.061035156250; 
+lensShading.ctrlPoints[1].controlPointR[5][1]	 = 1.090332031250; 
+lensShading.ctrlPoints[1].controlPointR[5][2]	 = 1.116821289063; 
+lensShading.ctrlPoints[1].controlPointR[5][3]	 = 1.118408203125; 
+lensShading.ctrlPoints[1].controlPointR[5][4]	 = 1.095092773438; 
+lensShading.ctrlPoints[1].controlPointR[5][5]	 = 1.105590820313; 
+lensShading.ctrlPoints[1].controlPointR[5][6]	 = 1.118774414063; 
+lensShading.ctrlPoints[1].controlPointR[5][7]	 = 1.063232421875; 
+lensShading.ctrlPoints[1].controlPointR[5][8]	 = 1.124145507813; 
+lensShading.ctrlPoints[1].controlPointR[5][9]	 = 1.058837890625; 
+lensShading.ctrlPoints[1].controlPointR[6][0]	 = 1.113281250000; 
+lensShading.ctrlPoints[1].controlPointR[6][1]	 = 1.043212890625; 
+lensShading.ctrlPoints[1].controlPointR[6][2]	 = 1.131225585938; 
+lensShading.ctrlPoints[1].controlPointR[6][3]	 = 1.104370117188; 
+lensShading.ctrlPoints[1].controlPointR[6][4]	 = 1.133911132813; 
+lensShading.ctrlPoints[1].controlPointR[6][5]	 = 1.117553710938; 
+lensShading.ctrlPoints[1].controlPointR[6][6]	 = 1.117431640625; 
+lensShading.ctrlPoints[1].controlPointR[6][7]	 = 1.149169921875; 
+lensShading.ctrlPoints[1].controlPointR[6][8]	 = 1.019775390625; 
+lensShading.ctrlPoints[1].controlPointR[6][9]	 = 1.210449218750; 
+lensShading.ctrlPoints[1].controlPointR[7][0]	 = 1.201782226563; 
+lensShading.ctrlPoints[1].controlPointR[7][1]	 = 1.068115234375; 
+lensShading.ctrlPoints[1].controlPointR[7][2]	 = 1.099975585938; 
+lensShading.ctrlPoints[1].controlPointR[7][3]	 = 1.140380859375; 
+lensShading.ctrlPoints[1].controlPointR[7][4]	 = 1.080566406250; 
+lensShading.ctrlPoints[1].controlPointR[7][5]	 = 1.162231445313; 
+lensShading.ctrlPoints[1].controlPointR[7][6]	 = 1.121337890625; 
+lensShading.ctrlPoints[1].controlPointR[7][7]	 = 1.080932617188; 
+lensShading.ctrlPoints[1].controlPointR[7][8]	 = 1.054565429688; 
+lensShading.ctrlPoints[1].controlPointR[7][9]	 = 1.302734375000; 
+lensShading.ctrlPoints[1].controlPointR[8][0]	 = 1.157836914063; 
+lensShading.ctrlPoints[1].controlPointR[8][1]	 = 0.961791992188; 
+lensShading.ctrlPoints[1].controlPointR[8][2]	 = 1.110351562500; 
+lensShading.ctrlPoints[1].controlPointR[8][3]	 = 1.092651367188; 
+lensShading.ctrlPoints[1].controlPointR[8][4]	 = 1.166992187500; 
+lensShading.ctrlPoints[1].controlPointR[8][5]	 = 1.119873046875; 
+lensShading.ctrlPoints[1].controlPointR[8][6]	 = 1.091552734375; 
+lensShading.ctrlPoints[1].controlPointR[8][7]	 = 1.112548828125; 
+lensShading.ctrlPoints[1].controlPointR[8][8]	 = 1.015258789063; 
+lensShading.ctrlPoints[1].controlPointR[8][9]	 = 1.356323242188; 
+lensShading.ctrlPoints[1].controlPointR[9][0]	 = 1.489013671875; 
+lensShading.ctrlPoints[1].controlPointR[9][1]	 = 1.096069335938; 
+lensShading.ctrlPoints[1].controlPointR[9][2]	 = 1.102050781250; 
+lensShading.ctrlPoints[1].controlPointR[9][3]	 = 1.106079101563; 
+lensShading.ctrlPoints[1].controlPointR[9][4]	 = 1.133422851563; 
+lensShading.ctrlPoints[1].controlPointR[9][5]	 = 1.097045898438; 
+lensShading.ctrlPoints[1].controlPointR[9][6]	 = 1.115844726563; 
+lensShading.ctrlPoints[1].controlPointR[9][7]	 = 1.141357421875; 
+lensShading.ctrlPoints[1].controlPointR[9][8]	 = 1.062133789063; 
+lensShading.ctrlPoints[1].controlPointR[9][9]	 = 1.936035156250; 
+lensShading.ctrlPoints[1].controlPointGR[0][0]	 = 1.063476562500; 
+lensShading.ctrlPoints[1].controlPointGR[0][1]	 = 1.007202148438; 
+lensShading.ctrlPoints[1].controlPointGR[0][2]	 = 1.059814453125; 
+lensShading.ctrlPoints[1].controlPointGR[0][3]	 = 1.056518554688; 
+lensShading.ctrlPoints[1].controlPointGR[0][4]	 = 1.074951171875; 
+lensShading.ctrlPoints[1].controlPointGR[0][5]	 = 1.055541992188; 
+lensShading.ctrlPoints[1].controlPointGR[0][6]	 = 1.048339843750; 
+lensShading.ctrlPoints[1].controlPointGR[0][7]	 = 1.097412109375; 
+lensShading.ctrlPoints[1].controlPointGR[0][8]	 = 0.760375976563; 
+lensShading.ctrlPoints[1].controlPointGR[0][9]	 = 1.566162109375; 
+lensShading.ctrlPoints[1].controlPointGR[1][0]	 = 0.996337890625; 
+lensShading.ctrlPoints[1].controlPointGR[1][1]	 = 1.042358398438; 
+lensShading.ctrlPoints[1].controlPointGR[1][2]	 = 1.114990234375; 
+lensShading.ctrlPoints[1].controlPointGR[1][3]	 = 1.078369140625; 
+lensShading.ctrlPoints[1].controlPointGR[1][4]	 = 1.059204101563; 
+lensShading.ctrlPoints[1].controlPointGR[1][5]	 = 1.084594726563; 
+lensShading.ctrlPoints[1].controlPointGR[1][6]	 = 1.051025390625; 
+lensShading.ctrlPoints[1].controlPointGR[1][7]	 = 1.207519531250; 
+lensShading.ctrlPoints[1].controlPointGR[1][8]	 = 0.743041992188; 
+lensShading.ctrlPoints[1].controlPointGR[1][9]	 = 1.232666015625; 
+lensShading.ctrlPoints[1].controlPointGR[2][0]	 = 1.065307617188; 
+lensShading.ctrlPoints[1].controlPointGR[2][1]	 = 1.065429687500; 
+lensShading.ctrlPoints[1].controlPointGR[2][2]	 = 1.051269531250; 
+lensShading.ctrlPoints[1].controlPointGR[2][3]	 = 1.074707031250; 
+lensShading.ctrlPoints[1].controlPointGR[2][4]	 = 1.070678710938; 
+lensShading.ctrlPoints[1].controlPointGR[2][5]	 = 1.080444335938; 
+lensShading.ctrlPoints[1].controlPointGR[2][6]	 = 1.066772460938; 
+lensShading.ctrlPoints[1].controlPointGR[2][7]	 = 1.076904296875; 
+lensShading.ctrlPoints[1].controlPointGR[2][8]	 = 1.064819335938; 
+lensShading.ctrlPoints[1].controlPointGR[2][9]	 = 1.026000976563; 
+lensShading.ctrlPoints[1].controlPointGR[3][0]	 = 1.040161132813; 
+lensShading.ctrlPoints[1].controlPointGR[3][1]	 = 1.072875976563; 
+lensShading.ctrlPoints[1].controlPointGR[3][2]	 = 1.065917968750; 
+lensShading.ctrlPoints[1].controlPointGR[3][3]	 = 1.080688476563; 
+lensShading.ctrlPoints[1].controlPointGR[3][4]	 = 1.057250976563; 
+lensShading.ctrlPoints[1].controlPointGR[3][5]	 = 1.068481445313; 
+lensShading.ctrlPoints[1].controlPointGR[3][6]	 = 1.069458007813; 
+lensShading.ctrlPoints[1].controlPointGR[3][7]	 = 1.082641601563; 
+lensShading.ctrlPoints[1].controlPointGR[3][8]	 = 1.013916015625; 
+lensShading.ctrlPoints[1].controlPointGR[3][9]	 = 1.064819335938; 
+lensShading.ctrlPoints[1].controlPointGR[4][0]	 = 1.075927734375; 
+lensShading.ctrlPoints[1].controlPointGR[4][1]	 = 1.062500000000; 
+lensShading.ctrlPoints[1].controlPointGR[4][2]	 = 1.084594726563; 
+lensShading.ctrlPoints[1].controlPointGR[4][3]	 = 1.075561523438; 
+lensShading.ctrlPoints[1].controlPointGR[4][4]	 = 1.058227539063; 
+lensShading.ctrlPoints[1].controlPointGR[4][5]	 = 1.040161132813; 
+lensShading.ctrlPoints[1].controlPointGR[4][6]	 = 1.084228515625; 
+lensShading.ctrlPoints[1].controlPointGR[4][7]	 = 1.129028320313; 
+lensShading.ctrlPoints[1].controlPointGR[4][8]	 = 1.037841796875; 
+lensShading.ctrlPoints[1].controlPointGR[4][9]	 = 1.075561523438; 
+lensShading.ctrlPoints[1].controlPointGR[5][0]	 = 1.031005859375; 
+lensShading.ctrlPoints[1].controlPointGR[5][1]	 = 1.084228515625; 
+lensShading.ctrlPoints[1].controlPointGR[5][2]	 = 1.067993164063; 
+lensShading.ctrlPoints[1].controlPointGR[5][3]	 = 1.088256835938; 
+lensShading.ctrlPoints[1].controlPointGR[5][4]	 = 1.037109375000; 
+lensShading.ctrlPoints[1].controlPointGR[5][5]	 = 1.110839843750; 
+lensShading.ctrlPoints[1].controlPointGR[5][6]	 = 1.072387695313; 
+lensShading.ctrlPoints[1].controlPointGR[5][7]	 = 1.089355468750; 
+lensShading.ctrlPoints[1].controlPointGR[5][8]	 = 1.032592773438; 
+lensShading.ctrlPoints[1].controlPointGR[5][9]	 = 1.048095703125; 
+lensShading.ctrlPoints[1].controlPointGR[6][0]	 = 1.088867187500; 
+lensShading.ctrlPoints[1].controlPointGR[6][1]	 = 1.049560546875; 
+lensShading.ctrlPoints[1].controlPointGR[6][2]	 = 1.101440429688; 
+lensShading.ctrlPoints[1].controlPointGR[6][3]	 = 1.084838867188; 
+lensShading.ctrlPoints[1].controlPointGR[6][4]	 = 1.105346679688; 
+lensShading.ctrlPoints[1].controlPointGR[6][5]	 = 1.082641601563; 
+lensShading.ctrlPoints[1].controlPointGR[6][6]	 = 1.086303710938; 
+lensShading.ctrlPoints[1].controlPointGR[6][7]	 = 1.095214843750; 
+lensShading.ctrlPoints[1].controlPointGR[6][8]	 = 1.002319335938; 
+lensShading.ctrlPoints[1].controlPointGR[6][9]	 = 1.155517578125; 
+lensShading.ctrlPoints[1].controlPointGR[7][0]	 = 1.180175781250; 
+lensShading.ctrlPoints[1].controlPointGR[7][1]	 = 1.003051757813; 
+lensShading.ctrlPoints[1].controlPointGR[7][2]	 = 1.097290039063; 
+lensShading.ctrlPoints[1].controlPointGR[7][3]	 = 1.098632812500; 
+lensShading.ctrlPoints[1].controlPointGR[7][4]	 = 1.064941406250; 
+lensShading.ctrlPoints[1].controlPointGR[7][5]	 = 1.135131835938; 
+lensShading.ctrlPoints[1].controlPointGR[7][6]	 = 1.071289062500; 
+lensShading.ctrlPoints[1].controlPointGR[7][7]	 = 1.106689453125; 
+lensShading.ctrlPoints[1].controlPointGR[7][8]	 = 0.937988281250; 
+lensShading.ctrlPoints[1].controlPointGR[7][9]	 = 1.290161132813; 
+lensShading.ctrlPoints[1].controlPointGR[8][0]	 = 1.148437500000; 
+lensShading.ctrlPoints[1].controlPointGR[8][1]	 = 0.993652343750; 
+lensShading.ctrlPoints[1].controlPointGR[8][2]	 = 1.059570312500; 
+lensShading.ctrlPoints[1].controlPointGR[8][3]	 = 1.084960937500; 
+lensShading.ctrlPoints[1].controlPointGR[8][4]	 = 1.137329101563; 
+lensShading.ctrlPoints[1].controlPointGR[8][5]	 = 1.063110351563; 
+lensShading.ctrlPoints[1].controlPointGR[8][6]	 = 1.081542968750; 
+lensShading.ctrlPoints[1].controlPointGR[8][7]	 = 1.033203125000; 
+lensShading.ctrlPoints[1].controlPointGR[8][8]	 = 1.046020507813; 
+lensShading.ctrlPoints[1].controlPointGR[8][9]	 = 1.294311523438; 
+lensShading.ctrlPoints[1].controlPointGR[9][0]	 = 1.435180664063; 
+lensShading.ctrlPoints[1].controlPointGR[9][1]	 = 1.056640625000; 
+lensShading.ctrlPoints[1].controlPointGR[9][2]	 = 1.080322265625; 
+lensShading.ctrlPoints[1].controlPointGR[9][3]	 = 1.072509765625; 
+lensShading.ctrlPoints[1].controlPointGR[9][4]	 = 1.072143554688; 
+lensShading.ctrlPoints[1].controlPointGR[9][5]	 = 1.103027343750; 
+lensShading.ctrlPoints[1].controlPointGR[9][6]	 = 1.054809570313; 
+lensShading.ctrlPoints[1].controlPointGR[9][7]	 = 1.144165039063; 
+lensShading.ctrlPoints[1].controlPointGR[9][8]	 = 0.990600585938; 
+lensShading.ctrlPoints[1].controlPointGR[9][9]	 = 1.867187500000; 
+lensShading.ctrlPoints[1].controlPointGB[0][0]	 = 1.058959960938; 
+lensShading.ctrlPoints[1].controlPointGB[0][1]	 = 1.006103515625; 
+lensShading.ctrlPoints[1].controlPointGB[0][2]	 = 1.062500000000; 
+lensShading.ctrlPoints[1].controlPointGB[0][3]	 = 1.068115234375; 
+lensShading.ctrlPoints[1].controlPointGB[0][4]	 = 1.078125000000; 
+lensShading.ctrlPoints[1].controlPointGB[0][5]	 = 1.072998046875; 
+lensShading.ctrlPoints[1].controlPointGB[0][6]	 = 1.060913085938; 
+lensShading.ctrlPoints[1].controlPointGB[0][7]	 = 1.130493164063; 
+lensShading.ctrlPoints[1].controlPointGB[0][8]	 = 0.752685546875; 
+lensShading.ctrlPoints[1].controlPointGB[0][9]	 = 1.575805664063; 
+lensShading.ctrlPoints[1].controlPointGB[1][0]	 = 1.013305664063; 
+lensShading.ctrlPoints[1].controlPointGB[1][1]	 = 1.072143554688; 
+lensShading.ctrlPoints[1].controlPointGB[1][2]	 = 1.126464843750; 
+lensShading.ctrlPoints[1].controlPointGB[1][3]	 = 1.084350585938; 
+lensShading.ctrlPoints[1].controlPointGB[1][4]	 = 1.074462890625; 
+lensShading.ctrlPoints[1].controlPointGB[1][5]	 = 1.066650390625; 
+lensShading.ctrlPoints[1].controlPointGB[1][6]	 = 1.065429687500; 
+lensShading.ctrlPoints[1].controlPointGB[1][7]	 = 1.163574218750; 
+lensShading.ctrlPoints[1].controlPointGB[1][8]	 = 0.819091796875; 
+lensShading.ctrlPoints[1].controlPointGB[1][9]	 = 1.206420898438; 
+lensShading.ctrlPoints[1].controlPointGB[2][0]	 = 1.064575195313; 
+lensShading.ctrlPoints[1].controlPointGB[2][1]	 = 1.018066406250; 
+lensShading.ctrlPoints[1].controlPointGB[2][2]	 = 1.081787109375; 
+lensShading.ctrlPoints[1].controlPointGB[2][3]	 = 1.066894531250; 
+lensShading.ctrlPoints[1].controlPointGB[2][4]	 = 1.082031250000; 
+lensShading.ctrlPoints[1].controlPointGB[2][5]	 = 1.097167968750; 
+lensShading.ctrlPoints[1].controlPointGB[2][6]	 = 1.069946289063; 
+lensShading.ctrlPoints[1].controlPointGB[2][7]	 = 1.097045898438; 
+lensShading.ctrlPoints[1].controlPointGB[2][8]	 = 1.030029296875; 
+lensShading.ctrlPoints[1].controlPointGB[2][9]	 = 1.057250976563; 
+lensShading.ctrlPoints[1].controlPointGB[3][0]	 = 1.042358398438; 
+lensShading.ctrlPoints[1].controlPointGB[3][1]	 = 1.088012695313; 
+lensShading.ctrlPoints[1].controlPointGB[3][2]	 = 1.060546875000; 
+lensShading.ctrlPoints[1].controlPointGB[3][3]	 = 1.079589843750; 
+lensShading.ctrlPoints[1].controlPointGB[3][4]	 = 1.075683593750; 
+lensShading.ctrlPoints[1].controlPointGB[3][5]	 = 1.061523437500; 
+lensShading.ctrlPoints[1].controlPointGB[3][6]	 = 1.079833984375; 
+lensShading.ctrlPoints[1].controlPointGB[3][7]	 = 1.086181640625; 
+lensShading.ctrlPoints[1].controlPointGB[3][8]	 = 1.037353515625; 
+lensShading.ctrlPoints[1].controlPointGB[3][9]	 = 1.059570312500; 
+lensShading.ctrlPoints[1].controlPointGB[4][0]	 = 1.065307617188; 
+lensShading.ctrlPoints[1].controlPointGB[4][1]	 = 1.032104492188; 
+lensShading.ctrlPoints[1].controlPointGB[4][2]	 = 1.093261718750; 
+lensShading.ctrlPoints[1].controlPointGB[4][3]	 = 1.076416015625; 
+lensShading.ctrlPoints[1].controlPointGB[4][4]	 = 1.058837890625; 
+lensShading.ctrlPoints[1].controlPointGB[4][5]	 = 1.047119140625; 
+lensShading.ctrlPoints[1].controlPointGB[4][6]	 = 1.084350585938; 
+lensShading.ctrlPoints[1].controlPointGB[4][7]	 = 1.123535156250; 
+lensShading.ctrlPoints[1].controlPointGB[4][8]	 = 1.023071289063; 
+lensShading.ctrlPoints[1].controlPointGB[4][9]	 = 1.088134765625; 
+lensShading.ctrlPoints[1].controlPointGB[5][0]	 = 1.045410156250; 
+lensShading.ctrlPoints[1].controlPointGB[5][1]	 = 1.088745117188; 
+lensShading.ctrlPoints[1].controlPointGB[5][2]	 = 1.071166992188; 
+lensShading.ctrlPoints[1].controlPointGB[5][3]	 = 1.085571289063; 
+lensShading.ctrlPoints[1].controlPointGB[5][4]	 = 1.048706054688; 
+lensShading.ctrlPoints[1].controlPointGB[5][5]	 = 1.100830078125; 
+lensShading.ctrlPoints[1].controlPointGB[5][6]	 = 1.077026367188; 
+lensShading.ctrlPoints[1].controlPointGB[5][7]	 = 1.108764648438; 
+lensShading.ctrlPoints[1].controlPointGB[5][8]	 = 1.026245117188; 
+lensShading.ctrlPoints[1].controlPointGB[5][9]	 = 1.050537109375; 
+lensShading.ctrlPoints[1].controlPointGB[6][0]	 = 1.083007812500; 
+lensShading.ctrlPoints[1].controlPointGB[6][1]	 = 1.046142578125; 
+lensShading.ctrlPoints[1].controlPointGB[6][2]	 = 1.093139648438; 
+lensShading.ctrlPoints[1].controlPointGB[6][3]	 = 1.083374023438; 
+lensShading.ctrlPoints[1].controlPointGB[6][4]	 = 1.103271484375; 
+lensShading.ctrlPoints[1].controlPointGB[6][5]	 = 1.090087890625; 
+lensShading.ctrlPoints[1].controlPointGB[6][6]	 = 1.084350585938; 
+lensShading.ctrlPoints[1].controlPointGB[6][7]	 = 1.096679687500; 
+lensShading.ctrlPoints[1].controlPointGB[6][8]	 = 0.999877929688; 
+lensShading.ctrlPoints[1].controlPointGB[6][9]	 = 1.158569335938; 
+lensShading.ctrlPoints[1].controlPointGB[7][0]	 = 1.194213867188; 
+lensShading.ctrlPoints[1].controlPointGB[7][1]	 = 0.993652343750; 
+lensShading.ctrlPoints[1].controlPointGB[7][2]	 = 1.113159179688; 
+lensShading.ctrlPoints[1].controlPointGB[7][3]	 = 1.089599609375; 
+lensShading.ctrlPoints[1].controlPointGB[7][4]	 = 1.090087890625; 
+lensShading.ctrlPoints[1].controlPointGB[7][5]	 = 1.096191406250; 
+lensShading.ctrlPoints[1].controlPointGB[7][6]	 = 1.089965820313; 
+lensShading.ctrlPoints[1].controlPointGB[7][7]	 = 1.106689453125; 
+lensShading.ctrlPoints[1].controlPointGB[7][8]	 = 0.935791015625; 
+lensShading.ctrlPoints[1].controlPointGB[7][9]	 = 1.284179687500; 
+lensShading.ctrlPoints[1].controlPointGB[8][0]	 = 1.118652343750; 
+lensShading.ctrlPoints[1].controlPointGB[8][1]	 = 0.987182617188; 
+lensShading.ctrlPoints[1].controlPointGB[8][2]	 = 1.050781250000; 
+lensShading.ctrlPoints[1].controlPointGB[8][3]	 = 1.075561523438; 
+lensShading.ctrlPoints[1].controlPointGB[8][4]	 = 1.114379882813; 
+lensShading.ctrlPoints[1].controlPointGB[8][5]	 = 1.108764648438; 
+lensShading.ctrlPoints[1].controlPointGB[8][6]	 = 1.055419921875; 
+lensShading.ctrlPoints[1].controlPointGB[8][7]	 = 1.031738281250; 
+lensShading.ctrlPoints[1].controlPointGB[8][8]	 = 1.052490234375; 
+lensShading.ctrlPoints[1].controlPointGB[8][9]	 = 1.282348632813; 
+lensShading.ctrlPoints[1].controlPointGB[9][0]	 = 1.449096679688; 
+lensShading.ctrlPoints[1].controlPointGB[9][1]	 = 1.060302734375; 
+lensShading.ctrlPoints[1].controlPointGB[9][2]	 = 1.064453125000; 
+lensShading.ctrlPoints[1].controlPointGB[9][3]	 = 1.069213867188; 
+lensShading.ctrlPoints[1].controlPointGB[9][4]	 = 1.088012695313; 
+lensShading.ctrlPoints[1].controlPointGB[9][5]	 = 1.073852539063; 
+lensShading.ctrlPoints[1].controlPointGB[9][6]	 = 1.069213867188; 
+lensShading.ctrlPoints[1].controlPointGB[9][7]	 = 1.146728515625; 
+lensShading.ctrlPoints[1].controlPointGB[9][8]	 = 0.974243164063; 
+lensShading.ctrlPoints[1].controlPointGB[9][9]	 = 1.875122070313; 
+lensShading.ctrlPoints[1].controlPointB[0][0]	 = 1.054565429688; 
+lensShading.ctrlPoints[1].controlPointB[0][1]	 = 1.022338867188; 
+lensShading.ctrlPoints[1].controlPointB[0][2]	 = 1.072387695313; 
+lensShading.ctrlPoints[1].controlPointB[0][3]	 = 1.086425781250; 
+lensShading.ctrlPoints[1].controlPointB[0][4]	 = 1.060546875000; 
+lensShading.ctrlPoints[1].controlPointB[0][5]	 = 1.094848632813; 
+lensShading.ctrlPoints[1].controlPointB[0][6]	 = 1.073486328125; 
+lensShading.ctrlPoints[1].controlPointB[0][7]	 = 1.129028320313; 
+lensShading.ctrlPoints[1].controlPointB[0][8]	 = 0.770996093750; 
+lensShading.ctrlPoints[1].controlPointB[0][9]	 = 1.599243164063; 
+lensShading.ctrlPoints[1].controlPointB[1][0]	 = 1.010498046875; 
+lensShading.ctrlPoints[1].controlPointB[1][1]	 = 1.098999023438; 
+lensShading.ctrlPoints[1].controlPointB[1][2]	 = 1.100830078125; 
+lensShading.ctrlPoints[1].controlPointB[1][3]	 = 1.086425781250; 
+lensShading.ctrlPoints[1].controlPointB[1][4]	 = 1.076171875000; 
+lensShading.ctrlPoints[1].controlPointB[1][5]	 = 1.110717773438; 
+lensShading.ctrlPoints[1].controlPointB[1][6]	 = 1.059204101563; 
+lensShading.ctrlPoints[1].controlPointB[1][7]	 = 1.228149414063; 
+lensShading.ctrlPoints[1].controlPointB[1][8]	 = 0.770141601563; 
+lensShading.ctrlPoints[1].controlPointB[1][9]	 = 1.236816406250; 
+lensShading.ctrlPoints[1].controlPointB[2][0]	 = 1.067260742188; 
+lensShading.ctrlPoints[1].controlPointB[2][1]	 = 1.054321289063; 
+lensShading.ctrlPoints[1].controlPointB[2][2]	 = 1.088867187500; 
+lensShading.ctrlPoints[1].controlPointB[2][3]	 = 1.076904296875; 
+lensShading.ctrlPoints[1].controlPointB[2][4]	 = 1.115844726563; 
+lensShading.ctrlPoints[1].controlPointB[2][5]	 = 1.054931640625; 
+lensShading.ctrlPoints[1].controlPointB[2][6]	 = 1.097167968750; 
+lensShading.ctrlPoints[1].controlPointB[2][7]	 = 1.081054687500; 
+lensShading.ctrlPoints[1].controlPointB[2][8]	 = 1.086547851563; 
+lensShading.ctrlPoints[1].controlPointB[2][9]	 = 1.056274414063; 
+lensShading.ctrlPoints[1].controlPointB[3][0]	 = 1.047119140625; 
+lensShading.ctrlPoints[1].controlPointB[3][1]	 = 1.085083007813; 
+lensShading.ctrlPoints[1].controlPointB[3][2]	 = 1.082519531250; 
+lensShading.ctrlPoints[1].controlPointB[3][3]	 = 1.091430664063; 
+lensShading.ctrlPoints[1].controlPointB[3][4]	 = 1.065185546875; 
+lensShading.ctrlPoints[1].controlPointB[3][5]	 = 1.085693359375; 
+lensShading.ctrlPoints[1].controlPointB[3][6]	 = 1.086425781250; 
+lensShading.ctrlPoints[1].controlPointB[3][7]	 = 1.124511718750; 
+lensShading.ctrlPoints[1].controlPointB[3][8]	 = 1.034057617188; 
+lensShading.ctrlPoints[1].controlPointB[3][9]	 = 1.084716796875; 
+lensShading.ctrlPoints[1].controlPointB[4][0]	 = 1.101562500000; 
+lensShading.ctrlPoints[1].controlPointB[4][1]	 = 1.040283203125; 
+lensShading.ctrlPoints[1].controlPointB[4][2]	 = 1.105224609375; 
+lensShading.ctrlPoints[1].controlPointB[4][3]	 = 1.083862304688; 
+lensShading.ctrlPoints[1].controlPointB[4][4]	 = 1.076904296875; 
+lensShading.ctrlPoints[1].controlPointB[4][5]	 = 1.046752929688; 
+lensShading.ctrlPoints[1].controlPointB[4][6]	 = 1.095214843750; 
+lensShading.ctrlPoints[1].controlPointB[4][7]	 = 1.146118164063; 
+lensShading.ctrlPoints[1].controlPointB[4][8]	 = 1.032592773438; 
+lensShading.ctrlPoints[1].controlPointB[4][9]	 = 1.117553710938; 
+lensShading.ctrlPoints[1].controlPointB[5][0]	 = 1.028930664063; 
+lensShading.ctrlPoints[1].controlPointB[5][1]	 = 1.111450195313; 
+lensShading.ctrlPoints[1].controlPointB[5][2]	 = 1.084960937500; 
+lensShading.ctrlPoints[1].controlPointB[5][3]	 = 1.096069335938; 
+lensShading.ctrlPoints[1].controlPointB[5][4]	 = 1.064819335938; 
+lensShading.ctrlPoints[1].controlPointB[5][5]	 = 1.103881835938; 
+lensShading.ctrlPoints[1].controlPointB[5][6]	 = 1.091796875000; 
+lensShading.ctrlPoints[1].controlPointB[5][7]	 = 1.100708007813; 
+lensShading.ctrlPoints[1].controlPointB[5][8]	 = 1.072753906250; 
+lensShading.ctrlPoints[1].controlPointB[5][9]	 = 1.058105468750; 
+lensShading.ctrlPoints[1].controlPointB[6][0]	 = 1.100830078125; 
+lensShading.ctrlPoints[1].controlPointB[6][1]	 = 1.057128906250; 
+lensShading.ctrlPoints[1].controlPointB[6][2]	 = 1.108032226563; 
+lensShading.ctrlPoints[1].controlPointB[6][3]	 = 1.095703125000; 
+lensShading.ctrlPoints[1].controlPointB[6][4]	 = 1.102416992188; 
+lensShading.ctrlPoints[1].controlPointB[6][5]	 = 1.102416992188; 
+lensShading.ctrlPoints[1].controlPointB[6][6]	 = 1.101318359375; 
+lensShading.ctrlPoints[1].controlPointB[6][7]	 = 1.127319335938; 
+lensShading.ctrlPoints[1].controlPointB[6][8]	 = 1.002929687500; 
+lensShading.ctrlPoints[1].controlPointB[6][9]	 = 1.188842773438; 
+lensShading.ctrlPoints[1].controlPointB[7][0]	 = 1.173461914063; 
+lensShading.ctrlPoints[1].controlPointB[7][1]	 = 1.100463867188; 
+lensShading.ctrlPoints[1].controlPointB[7][2]	 = 1.071166992188; 
+lensShading.ctrlPoints[1].controlPointB[7][3]	 = 1.115844726563; 
+lensShading.ctrlPoints[1].controlPointB[7][4]	 = 1.096313476563; 
+lensShading.ctrlPoints[1].controlPointB[7][5]	 = 1.147949218750; 
+lensShading.ctrlPoints[1].controlPointB[7][6]	 = 1.094238281250; 
+lensShading.ctrlPoints[1].controlPointB[7][7]	 = 1.166992187500; 
+lensShading.ctrlPoints[1].controlPointB[7][8]	 = 0.900268554688; 
+lensShading.ctrlPoints[1].controlPointB[7][9]	 = 1.378295898438; 
+lensShading.ctrlPoints[1].controlPointB[8][0]	 = 1.127197265625; 
+lensShading.ctrlPoints[1].controlPointB[8][1]	 = 0.955200195313; 
+lensShading.ctrlPoints[1].controlPointB[8][2]	 = 1.065795898438; 
+lensShading.ctrlPoints[1].controlPointB[8][3]	 = 1.076049804688; 
+lensShading.ctrlPoints[1].controlPointB[8][4]	 = 1.104248046875; 
+lensShading.ctrlPoints[1].controlPointB[8][5]	 = 1.072021484375; 
+lensShading.ctrlPoints[1].controlPointB[8][6]	 = 1.081420898438; 
+lensShading.ctrlPoints[1].controlPointB[8][7]	 = 1.028564453125; 
+lensShading.ctrlPoints[1].controlPointB[8][8]	 = 1.075195312500; 
+lensShading.ctrlPoints[1].controlPointB[8][9]	 = 1.251831054688; 
+lensShading.ctrlPoints[1].controlPointB[9][0]	 = 1.462280273438; 
+lensShading.ctrlPoints[1].controlPointB[9][1]	 = 1.096191406250; 
+lensShading.ctrlPoints[1].controlPointB[9][2]	 = 1.081420898438; 
+lensShading.ctrlPoints[1].controlPointB[9][3]	 = 1.086791992188; 
+lensShading.ctrlPoints[1].controlPointB[9][4]	 = 1.099975585938; 
+lensShading.ctrlPoints[1].controlPointB[9][5]	 = 1.113769531250; 
+lensShading.ctrlPoints[1].controlPointB[9][6]	 = 1.082397460938; 
+lensShading.ctrlPoints[1].controlPointB[9][7]	 = 1.170532226563; 
+lensShading.ctrlPoints[1].controlPointB[9][8]	 = 0.970336914063; 
+lensShading.ctrlPoints[1].controlPointB[9][9]	 = 1.959106445313; 
+lensShading.ctrlPoints[2].cct	 = 6500; 
+lensShading.ctrlPoints[2].light_family	 = 1; 
+lensShading.ctrlPoints[2].controlPointR[0][0]	 = 1.082397460938; 
+lensShading.ctrlPoints[2].controlPointR[0][1]	 = 0.986572265625; 
+lensShading.ctrlPoints[2].controlPointR[0][2]	 = 1.100585937500; 
+lensShading.ctrlPoints[2].controlPointR[0][3]	 = 1.100708007813; 
+lensShading.ctrlPoints[2].controlPointR[0][4]	 = 1.096191406250; 
+lensShading.ctrlPoints[2].controlPointR[0][5]	 = 1.100341796875; 
+lensShading.ctrlPoints[2].controlPointR[0][6]	 = 1.104492187500; 
+lensShading.ctrlPoints[2].controlPointR[0][7]	 = 1.140136718750; 
+lensShading.ctrlPoints[2].controlPointR[0][8]	 = 0.767333984375; 
+lensShading.ctrlPoints[2].controlPointR[0][9]	 = 1.623535156250; 
+lensShading.ctrlPoints[2].controlPointR[1][0]	 = 0.982910156250; 
+lensShading.ctrlPoints[2].controlPointR[1][1]	 = 1.103637695313; 
+lensShading.ctrlPoints[2].controlPointR[1][2]	 = 1.125610351563; 
+lensShading.ctrlPoints[2].controlPointR[1][3]	 = 1.096313476563; 
+lensShading.ctrlPoints[2].controlPointR[1][4]	 = 1.091796875000; 
+lensShading.ctrlPoints[2].controlPointR[1][5]	 = 1.183593750000; 
+lensShading.ctrlPoints[2].controlPointR[1][6]	 = 1.061645507813; 
+lensShading.ctrlPoints[2].controlPointR[1][7]	 = 1.266723632813; 
+lensShading.ctrlPoints[2].controlPointR[1][8]	 = 0.764282226563; 
+lensShading.ctrlPoints[2].controlPointR[1][9]	 = 1.251586914063; 
+lensShading.ctrlPoints[2].controlPointR[2][0]	 = 1.108032226563; 
+lensShading.ctrlPoints[2].controlPointR[2][1]	 = 1.014282226563; 
+lensShading.ctrlPoints[2].controlPointR[2][2]	 = 1.171508789063; 
+lensShading.ctrlPoints[2].controlPointR[2][3]	 = 1.122192382813; 
+lensShading.ctrlPoints[2].controlPointR[2][4]	 = 1.146850585938; 
+lensShading.ctrlPoints[2].controlPointR[2][5]	 = 1.096069335938; 
+lensShading.ctrlPoints[2].controlPointR[2][6]	 = 1.154785156250; 
+lensShading.ctrlPoints[2].controlPointR[2][7]	 = 1.110229492188; 
+lensShading.ctrlPoints[2].controlPointR[2][8]	 = 1.091308593750; 
+lensShading.ctrlPoints[2].controlPointR[2][9]	 = 1.099487304688; 
+lensShading.ctrlPoints[2].controlPointR[3][0]	 = 1.066894531250; 
+lensShading.ctrlPoints[2].controlPointR[3][1]	 = 1.099243164063; 
+lensShading.ctrlPoints[2].controlPointR[3][2]	 = 1.107910156250; 
+lensShading.ctrlPoints[2].controlPointR[3][3]	 = 1.119506835938; 
+lensShading.ctrlPoints[2].controlPointR[3][4]	 = 1.123657226563; 
+lensShading.ctrlPoints[2].controlPointR[3][5]	 = 1.114990234375; 
+lensShading.ctrlPoints[2].controlPointR[3][6]	 = 1.124267578125; 
+lensShading.ctrlPoints[2].controlPointR[3][7]	 = 1.120849609375; 
+lensShading.ctrlPoints[2].controlPointR[3][8]	 = 1.092651367188; 
+lensShading.ctrlPoints[2].controlPointR[3][9]	 = 1.100463867188; 
+lensShading.ctrlPoints[2].controlPointR[4][0]	 = 1.096679687500; 
+lensShading.ctrlPoints[2].controlPointR[4][1]	 = 1.090454101563; 
+lensShading.ctrlPoints[2].controlPointR[4][2]	 = 1.130981445313; 
+lensShading.ctrlPoints[2].controlPointR[4][3]	 = 1.119750976563; 
+lensShading.ctrlPoints[2].controlPointR[4][4]	 = 1.127807617188; 
+lensShading.ctrlPoints[2].controlPointR[4][5]	 = 1.086181640625; 
+lensShading.ctrlPoints[2].controlPointR[4][6]	 = 1.149902343750; 
+lensShading.ctrlPoints[2].controlPointR[4][7]	 = 1.204956054688; 
+lensShading.ctrlPoints[2].controlPointR[4][8]	 = 1.016113281250; 
+lensShading.ctrlPoints[2].controlPointR[4][9]	 = 1.176025390625; 
+lensShading.ctrlPoints[2].controlPointR[5][0]	 = 1.069335937500; 
+lensShading.ctrlPoints[2].controlPointR[5][1]	 = 1.100952148438; 
+lensShading.ctrlPoints[2].controlPointR[5][2]	 = 1.120117187500; 
+lensShading.ctrlPoints[2].controlPointR[5][3]	 = 1.127807617188; 
+lensShading.ctrlPoints[2].controlPointR[5][4]	 = 1.097656250000; 
+lensShading.ctrlPoints[2].controlPointR[5][5]	 = 1.150268554688; 
+lensShading.ctrlPoints[2].controlPointR[5][6]	 = 1.127929687500; 
+lensShading.ctrlPoints[2].controlPointR[5][7]	 = 1.130249023438; 
+lensShading.ctrlPoints[2].controlPointR[5][8]	 = 1.116088867188; 
+lensShading.ctrlPoints[2].controlPointR[5][9]	 = 1.090454101563; 
+lensShading.ctrlPoints[2].controlPointR[6][0]	 = 1.109130859375; 
+lensShading.ctrlPoints[2].controlPointR[6][1]	 = 1.076049804688; 
+lensShading.ctrlPoints[2].controlPointR[6][2]	 = 1.141113281250; 
+lensShading.ctrlPoints[2].controlPointR[6][3]	 = 1.131347656250; 
+lensShading.ctrlPoints[2].controlPointR[6][4]	 = 1.144287109375; 
+lensShading.ctrlPoints[2].controlPointR[6][5]	 = 1.161743164063; 
+lensShading.ctrlPoints[2].controlPointR[6][6]	 = 1.140014648438; 
+lensShading.ctrlPoints[2].controlPointR[6][7]	 = 1.165771484375; 
+lensShading.ctrlPoints[2].controlPointR[6][8]	 = 1.043457031250; 
+lensShading.ctrlPoints[2].controlPointR[6][9]	 = 1.234252929688; 
+lensShading.ctrlPoints[2].controlPointR[7][0]	 = 1.234008789063; 
+lensShading.ctrlPoints[2].controlPointR[7][1]	 = 1.033447265625; 
+lensShading.ctrlPoints[2].controlPointR[7][2]	 = 1.155761718750; 
+lensShading.ctrlPoints[2].controlPointR[7][3]	 = 1.151245117188; 
+lensShading.ctrlPoints[2].controlPointR[7][4]	 = 1.151123046875; 
+lensShading.ctrlPoints[2].controlPointR[7][5]	 = 1.177246093750; 
+lensShading.ctrlPoints[2].controlPointR[7][6]	 = 1.147583007813; 
+lensShading.ctrlPoints[2].controlPointR[7][7]	 = 1.153808593750; 
+lensShading.ctrlPoints[2].controlPointR[7][8]	 = 0.965332031250; 
+lensShading.ctrlPoints[2].controlPointR[7][9]	 = 1.398925781250; 
+lensShading.ctrlPoints[2].controlPointR[8][0]	 = 1.171752929688; 
+lensShading.ctrlPoints[2].controlPointR[8][1]	 = 0.985839843750; 
+lensShading.ctrlPoints[2].controlPointR[8][2]	 = 1.111938476563; 
+lensShading.ctrlPoints[2].controlPointR[8][3]	 = 1.116210937500; 
+lensShading.ctrlPoints[2].controlPointR[8][4]	 = 1.182128906250; 
+lensShading.ctrlPoints[2].controlPointR[8][5]	 = 1.095703125000; 
+lensShading.ctrlPoints[2].controlPointR[8][6]	 = 1.146606445313; 
+lensShading.ctrlPoints[2].controlPointR[8][7]	 = 1.030761718750; 
+lensShading.ctrlPoints[2].controlPointR[8][8]	 = 1.168945312500; 
+lensShading.ctrlPoints[2].controlPointR[8][9]	 = 1.306396484375; 
+lensShading.ctrlPoints[2].controlPointR[9][0]	 = 1.507080078125; 
+lensShading.ctrlPoints[2].controlPointR[9][1]	 = 1.070190429688; 
+lensShading.ctrlPoints[2].controlPointR[9][2]	 = 1.147705078125; 
+lensShading.ctrlPoints[2].controlPointR[9][3]	 = 1.104614257813; 
+lensShading.ctrlPoints[2].controlPointR[9][4]	 = 1.175659179688; 
+lensShading.ctrlPoints[2].controlPointR[9][5]	 = 1.151245117188; 
+lensShading.ctrlPoints[2].controlPointR[9][6]	 = 1.116577148438; 
+lensShading.ctrlPoints[2].controlPointR[9][7]	 = 1.212402343750; 
+lensShading.ctrlPoints[2].controlPointR[9][8]	 = 1.031616210938; 
+lensShading.ctrlPoints[2].controlPointR[9][9]	 = 1.976562500000; 
+lensShading.ctrlPoints[2].controlPointGR[0][0]	 = 1.052246093750; 
+lensShading.ctrlPoints[2].controlPointGR[0][1]	 = 0.989624023438; 
+lensShading.ctrlPoints[2].controlPointGR[0][2]	 = 1.065917968750; 
+lensShading.ctrlPoints[2].controlPointGR[0][3]	 = 1.062866210938; 
+lensShading.ctrlPoints[2].controlPointGR[0][4]	 = 1.069213867188; 
+lensShading.ctrlPoints[2].controlPointGR[0][5]	 = 1.055175781250; 
+lensShading.ctrlPoints[2].controlPointGR[0][6]	 = 1.047119140625; 
+lensShading.ctrlPoints[2].controlPointGR[0][7]	 = 1.119018554688; 
+lensShading.ctrlPoints[2].controlPointGR[0][8]	 = 0.749389648438; 
+lensShading.ctrlPoints[2].controlPointGR[0][9]	 = 1.583740234375; 
+lensShading.ctrlPoints[2].controlPointGR[1][0]	 = 0.991088867188; 
+lensShading.ctrlPoints[2].controlPointGR[1][1]	 = 1.075317382813; 
+lensShading.ctrlPoints[2].controlPointGR[1][2]	 = 1.092407226563; 
+lensShading.ctrlPoints[2].controlPointGR[1][3]	 = 1.066162109375; 
+lensShading.ctrlPoints[2].controlPointGR[1][4]	 = 1.073974609375; 
+lensShading.ctrlPoints[2].controlPointGR[1][5]	 = 1.107666015625; 
+lensShading.ctrlPoints[2].controlPointGR[1][6]	 = 1.054321289063; 
+lensShading.ctrlPoints[2].controlPointGR[1][7]	 = 1.211547851563; 
+lensShading.ctrlPoints[2].controlPointGR[1][8]	 = 0.758422851563; 
+lensShading.ctrlPoints[2].controlPointGR[1][9]	 = 1.214477539063; 
+lensShading.ctrlPoints[2].controlPointGR[2][0]	 = 1.067626953125; 
+lensShading.ctrlPoints[2].controlPointGR[2][1]	 = 1.038696289063; 
+lensShading.ctrlPoints[2].controlPointGR[2][2]	 = 1.075683593750; 
+lensShading.ctrlPoints[2].controlPointGR[2][3]	 = 1.082519531250; 
+lensShading.ctrlPoints[2].controlPointGR[2][4]	 = 1.061889648438; 
+lensShading.ctrlPoints[2].controlPointGR[2][5]	 = 1.078247070313; 
+lensShading.ctrlPoints[2].controlPointGR[2][6]	 = 1.071777343750; 
+lensShading.ctrlPoints[2].controlPointGR[2][7]	 = 1.100463867188; 
+lensShading.ctrlPoints[2].controlPointGR[2][8]	 = 1.030883789063; 
+lensShading.ctrlPoints[2].controlPointGR[2][9]	 = 1.070190429688; 
+lensShading.ctrlPoints[2].controlPointGR[3][0]	 = 1.039184570313; 
+lensShading.ctrlPoints[2].controlPointGR[3][1]	 = 1.067260742188; 
+lensShading.ctrlPoints[2].controlPointGR[3][2]	 = 1.070190429688; 
+lensShading.ctrlPoints[2].controlPointGR[3][3]	 = 1.076049804688; 
+lensShading.ctrlPoints[2].controlPointGR[3][4]	 = 1.068115234375; 
+lensShading.ctrlPoints[2].controlPointGR[3][5]	 = 1.064941406250; 
+lensShading.ctrlPoints[2].controlPointGR[3][6]	 = 1.078247070313; 
+lensShading.ctrlPoints[2].controlPointGR[3][7]	 = 1.082641601563; 
+lensShading.ctrlPoints[2].controlPointGR[3][8]	 = 1.037719726563; 
+lensShading.ctrlPoints[2].controlPointGR[3][9]	 = 1.064941406250; 
+lensShading.ctrlPoints[2].controlPointGR[4][0]	 = 1.063842773438; 
+lensShading.ctrlPoints[2].controlPointGR[4][1]	 = 1.074340820313; 
+lensShading.ctrlPoints[2].controlPointGR[4][2]	 = 1.084960937500; 
+lensShading.ctrlPoints[2].controlPointGR[4][3]	 = 1.075195312500; 
+lensShading.ctrlPoints[2].controlPointGR[4][4]	 = 1.049194335938; 
+lensShading.ctrlPoints[2].controlPointGR[4][5]	 = 1.057373046875; 
+lensShading.ctrlPoints[2].controlPointGR[4][6]	 = 1.074951171875; 
+lensShading.ctrlPoints[2].controlPointGR[4][7]	 = 1.162475585938; 
+lensShading.ctrlPoints[2].controlPointGR[4][8]	 = 0.997802734375; 
+lensShading.ctrlPoints[2].controlPointGR[4][9]	 = 1.105102539063; 
+lensShading.ctrlPoints[2].controlPointGR[5][0]	 = 1.037841796875; 
+lensShading.ctrlPoints[2].controlPointGR[5][1]	 = 1.067138671875; 
+lensShading.ctrlPoints[2].controlPointGR[5][2]	 = 1.070190429688; 
+lensShading.ctrlPoints[2].controlPointGR[5][3]	 = 1.090820312500; 
+lensShading.ctrlPoints[2].controlPointGR[5][4]	 = 1.054687500000; 
+lensShading.ctrlPoints[2].controlPointGR[5][5]	 = 1.092773437500; 
+lensShading.ctrlPoints[2].controlPointGR[5][6]	 = 1.088623046875; 
+lensShading.ctrlPoints[2].controlPointGR[5][7]	 = 1.078613281250; 
+lensShading.ctrlPoints[2].controlPointGR[5][8]	 = 1.067016601563; 
+lensShading.ctrlPoints[2].controlPointGR[5][9]	 = 1.041992187500; 
+lensShading.ctrlPoints[2].controlPointGR[6][0]	 = 1.086181640625; 
+lensShading.ctrlPoints[2].controlPointGR[6][1]	 = 1.045654296875; 
+lensShading.ctrlPoints[2].controlPointGR[6][2]	 = 1.097900390625; 
+lensShading.ctrlPoints[2].controlPointGR[6][3]	 = 1.087158203125; 
+lensShading.ctrlPoints[2].controlPointGR[6][4]	 = 1.093383789063; 
+lensShading.ctrlPoints[2].controlPointGR[6][5]	 = 1.099243164063; 
+lensShading.ctrlPoints[2].controlPointGR[6][6]	 = 1.084838867188; 
+lensShading.ctrlPoints[2].controlPointGR[6][7]	 = 1.109863281250; 
+lensShading.ctrlPoints[2].controlPointGR[6][8]	 = 0.992065429688; 
+lensShading.ctrlPoints[2].controlPointGR[6][9]	 = 1.174804687500; 
+lensShading.ctrlPoints[2].controlPointGR[7][0]	 = 1.161254882813; 
+lensShading.ctrlPoints[2].controlPointGR[7][1]	 = 1.059204101563; 
+lensShading.ctrlPoints[2].controlPointGR[7][2]	 = 1.054931640625; 
+lensShading.ctrlPoints[2].controlPointGR[7][3]	 = 1.099487304688; 
+lensShading.ctrlPoints[2].controlPointGR[7][4]	 = 1.091918945313; 
+lensShading.ctrlPoints[2].controlPointGR[7][5]	 = 1.106567382813; 
+lensShading.ctrlPoints[2].controlPointGR[7][6]	 = 1.087890625000; 
+lensShading.ctrlPoints[2].controlPointGR[7][7]	 = 1.078857421875; 
+lensShading.ctrlPoints[2].controlPointGR[7][8]	 = 0.982543945313; 
+lensShading.ctrlPoints[2].controlPointGR[7][9]	 = 1.277221679688; 
+lensShading.ctrlPoints[2].controlPointGR[8][0]	 = 1.147827148438; 
+lensShading.ctrlPoints[2].controlPointGR[8][1]	 = 0.933715820313; 
+lensShading.ctrlPoints[2].controlPointGR[8][2]	 = 1.106079101563; 
+lensShading.ctrlPoints[2].controlPointGR[8][3]	 = 1.079223632813; 
+lensShading.ctrlPoints[2].controlPointGR[8][4]	 = 1.116577148438; 
+lensShading.ctrlPoints[2].controlPointGR[8][5]	 = 1.088745117188; 
+lensShading.ctrlPoints[2].controlPointGR[8][6]	 = 1.073852539063; 
+lensShading.ctrlPoints[2].controlPointGR[8][7]	 = 1.063964843750; 
+lensShading.ctrlPoints[2].controlPointGR[8][8]	 = 1.001953125000; 
+lensShading.ctrlPoints[2].controlPointGR[8][9]	 = 1.307861328125; 
+lensShading.ctrlPoints[2].controlPointGR[9][0]	 = 1.432128906250; 
+lensShading.ctrlPoints[2].controlPointGR[9][1]	 = 1.074584960938; 
+lensShading.ctrlPoints[2].controlPointGR[9][2]	 = 1.054809570313; 
+lensShading.ctrlPoints[2].controlPointGR[9][3]	 = 1.069580078125; 
+lensShading.ctrlPoints[2].controlPointGR[9][4]	 = 1.086669921875; 
+lensShading.ctrlPoints[2].controlPointGR[9][5]	 = 1.098266601563; 
+lensShading.ctrlPoints[2].controlPointGR[9][6]	 = 1.064208984375; 
+lensShading.ctrlPoints[2].controlPointGR[9][7]	 = 1.130493164063; 
+lensShading.ctrlPoints[2].controlPointGR[9][8]	 = 1.020874023438; 
+lensShading.ctrlPoints[2].controlPointGR[9][9]	 = 1.854125976563; 
+lensShading.ctrlPoints[2].controlPointGB[0][0]	 = 1.051025390625; 
+lensShading.ctrlPoints[2].controlPointGB[0][1]	 = 1.005004882813; 
+lensShading.ctrlPoints[2].controlPointGB[0][2]	 = 1.065551757813; 
+lensShading.ctrlPoints[2].controlPointGB[0][3]	 = 1.067382812500; 
+lensShading.ctrlPoints[2].controlPointGB[0][4]	 = 1.087280273438; 
+lensShading.ctrlPoints[2].controlPointGB[0][5]	 = 1.060424804688; 
+lensShading.ctrlPoints[2].controlPointGB[0][6]	 = 1.056884765625; 
+lensShading.ctrlPoints[2].controlPointGB[0][7]	 = 1.148681640625; 
+lensShading.ctrlPoints[2].controlPointGB[0][8]	 = 0.717529296875; 
+lensShading.ctrlPoints[2].controlPointGB[0][9]	 = 1.603759765625; 
+lensShading.ctrlPoints[2].controlPointGB[1][0]	 = 0.990478515625; 
+lensShading.ctrlPoints[2].controlPointGB[1][1]	 = 1.097167968750; 
+lensShading.ctrlPoints[2].controlPointGB[1][2]	 = 1.074096679688; 
+lensShading.ctrlPoints[2].controlPointGB[1][3]	 = 1.083862304688; 
+lensShading.ctrlPoints[2].controlPointGB[1][4]	 = 1.040039062500; 
+lensShading.ctrlPoints[2].controlPointGB[1][5]	 = 1.114501953125; 
+lensShading.ctrlPoints[2].controlPointGB[1][6]	 = 1.069580078125; 
+lensShading.ctrlPoints[2].controlPointGB[1][7]	 = 1.151367187500; 
+lensShading.ctrlPoints[2].controlPointGB[1][8]	 = 0.858032226563; 
+lensShading.ctrlPoints[2].controlPointGB[1][9]	 = 1.179931640625; 
+lensShading.ctrlPoints[2].controlPointGB[2][0]	 = 1.070068359375; 
+lensShading.ctrlPoints[2].controlPointGB[2][1]	 = 0.998046875000; 
+lensShading.ctrlPoints[2].controlPointGB[2][2]	 = 1.112426757813; 
+lensShading.ctrlPoints[2].controlPointGB[2][3]	 = 1.072631835938; 
+lensShading.ctrlPoints[2].controlPointGB[2][4]	 = 1.104736328125; 
+lensShading.ctrlPoints[2].controlPointGB[2][5]	 = 1.074462890625; 
+lensShading.ctrlPoints[2].controlPointGB[2][6]	 = 1.067749023438; 
+lensShading.ctrlPoints[2].controlPointGB[2][7]	 = 1.120361328125; 
+lensShading.ctrlPoints[2].controlPointGB[2][8]	 = 1.015991210938; 
+lensShading.ctrlPoints[2].controlPointGB[2][9]	 = 1.084960937500; 
+lensShading.ctrlPoints[2].controlPointGB[3][0]	 = 1.026855468750; 
+lensShading.ctrlPoints[2].controlPointGB[3][1]	 = 1.093994140625; 
+lensShading.ctrlPoints[2].controlPointGB[3][2]	 = 1.059814453125; 
+lensShading.ctrlPoints[2].controlPointGB[3][3]	 = 1.080932617188; 
+lensShading.ctrlPoints[2].controlPointGB[3][4]	 = 1.064941406250; 
+lensShading.ctrlPoints[2].controlPointGB[3][5]	 = 1.072998046875; 
+lensShading.ctrlPoints[2].controlPointGB[3][6]	 = 1.080810546875; 
+lensShading.ctrlPoints[2].controlPointGB[3][7]	 = 1.083618164063; 
+lensShading.ctrlPoints[2].controlPointGB[3][8]	 = 1.053588867188; 
+lensShading.ctrlPoints[2].controlPointGB[3][9]	 = 1.062133789063; 
+lensShading.ctrlPoints[2].controlPointGB[4][0]	 = 1.076782226563; 
+lensShading.ctrlPoints[2].controlPointGB[4][1]	 = 1.031738281250; 
+lensShading.ctrlPoints[2].controlPointGB[4][2]	 = 1.087158203125; 
+lensShading.ctrlPoints[2].controlPointGB[4][3]	 = 1.073608398438; 
+lensShading.ctrlPoints[2].controlPointGB[4][4]	 = 1.062377929688; 
+lensShading.ctrlPoints[2].controlPointGB[4][5]	 = 1.045654296875; 
+lensShading.ctrlPoints[2].controlPointGB[4][6]	 = 1.080932617188; 
+lensShading.ctrlPoints[2].controlPointGB[4][7]	 = 1.153320312500; 
+lensShading.ctrlPoints[2].controlPointGB[4][8]	 = 0.983642578125; 
+lensShading.ctrlPoints[2].controlPointGB[4][9]	 = 1.118408203125; 
+lensShading.ctrlPoints[2].controlPointGB[5][0]	 = 1.018066406250; 
+lensShading.ctrlPoints[2].controlPointGB[5][1]	 = 1.095703125000; 
+lensShading.ctrlPoints[2].controlPointGB[5][2]	 = 1.065429687500; 
+lensShading.ctrlPoints[2].controlPointGB[5][3]	 = 1.087402343750; 
+lensShading.ctrlPoints[2].controlPointGB[5][4]	 = 1.044067382813; 
+lensShading.ctrlPoints[2].controlPointGB[5][5]	 = 1.110595703125; 
+lensShading.ctrlPoints[2].controlPointGB[5][6]	 = 1.083862304688; 
+lensShading.ctrlPoints[2].controlPointGB[5][7]	 = 1.099487304688; 
+lensShading.ctrlPoints[2].controlPointGB[5][8]	 = 1.058837890625; 
+lensShading.ctrlPoints[2].controlPointGB[5][9]	 = 1.044067382813; 
+lensShading.ctrlPoints[2].controlPointGB[6][0]	 = 1.081665039063; 
+lensShading.ctrlPoints[2].controlPointGB[6][1]	 = 1.040649414063; 
+lensShading.ctrlPoints[2].controlPointGB[6][2]	 = 1.098754882813; 
+lensShading.ctrlPoints[2].controlPointGB[6][3]	 = 1.081787109375; 
+lensShading.ctrlPoints[2].controlPointGB[6][4]	 = 1.099243164063; 
+lensShading.ctrlPoints[2].controlPointGB[6][5]	 = 1.097412109375; 
+lensShading.ctrlPoints[2].controlPointGB[6][6]	 = 1.086425781250; 
+lensShading.ctrlPoints[2].controlPointGB[6][7]	 = 1.107788085938; 
+lensShading.ctrlPoints[2].controlPointGB[6][8]	 = 0.997436523438; 
+lensShading.ctrlPoints[2].controlPointGB[6][9]	 = 1.164062500000; 
+lensShading.ctrlPoints[2].controlPointGB[7][0]	 = 1.151733398438; 
+lensShading.ctrlPoints[2].controlPointGB[7][1]	 = 1.036254882813; 
+lensShading.ctrlPoints[2].controlPointGB[7][2]	 = 1.056152343750; 
+lensShading.ctrlPoints[2].controlPointGB[7][3]	 = 1.100463867188; 
+lensShading.ctrlPoints[2].controlPointGB[7][4]	 = 1.096679687500; 
+lensShading.ctrlPoints[2].controlPointGB[7][5]	 = 1.093383789063; 
+lensShading.ctrlPoints[2].controlPointGB[7][6]	 = 1.085205078125; 
+lensShading.ctrlPoints[2].controlPointGB[7][7]	 = 1.075683593750; 
+lensShading.ctrlPoints[2].controlPointGB[7][8]	 = 0.987182617188; 
+lensShading.ctrlPoints[2].controlPointGB[7][9]	 = 1.268310546875; 
+lensShading.ctrlPoints[2].controlPointGB[8][0]	 = 1.137695312500; 
+lensShading.ctrlPoints[2].controlPointGB[8][1]	 = 0.948608398438; 
+lensShading.ctrlPoints[2].controlPointGB[8][2]	 = 1.092163085938; 
+lensShading.ctrlPoints[2].controlPointGB[8][3]	 = 1.074584960938; 
+lensShading.ctrlPoints[2].controlPointGB[8][4]	 = 1.100097656250; 
+lensShading.ctrlPoints[2].controlPointGB[8][5]	 = 1.108154296875; 
+lensShading.ctrlPoints[2].controlPointGB[8][6]	 = 1.072998046875; 
+lensShading.ctrlPoints[2].controlPointGB[8][7]	 = 1.067626953125; 
+lensShading.ctrlPoints[2].controlPointGB[8][8]	 = 0.983398437500; 
+lensShading.ctrlPoints[2].controlPointGB[8][9]	 = 1.322387695313; 
+lensShading.ctrlPoints[2].controlPointGB[9][0]	 = 1.421752929688; 
+lensShading.ctrlPoints[2].controlPointGB[9][1]	 = 1.078857421875; 
+lensShading.ctrlPoints[2].controlPointGB[9][2]	 = 1.049560546875; 
+lensShading.ctrlPoints[2].controlPointGB[9][3]	 = 1.065185546875; 
+lensShading.ctrlPoints[2].controlPointGB[9][4]	 = 1.099609375000; 
+lensShading.ctrlPoints[2].controlPointGB[9][5]	 = 1.079833984375; 
+lensShading.ctrlPoints[2].controlPointGB[9][6]	 = 1.067749023438; 
+lensShading.ctrlPoints[2].controlPointGB[9][7]	 = 1.132812500000; 
+lensShading.ctrlPoints[2].controlPointGB[9][8]	 = 1.021606445313; 
+lensShading.ctrlPoints[2].controlPointGB[9][9]	 = 1.829223632813; 
+lensShading.ctrlPoints[2].controlPointB[0][0]	 = 1.067626953125; 
+lensShading.ctrlPoints[2].controlPointB[0][1]	 = 0.951171875000; 
+lensShading.ctrlPoints[2].controlPointB[0][2]	 = 1.110473632813; 
+lensShading.ctrlPoints[2].controlPointB[0][3]	 = 1.056518554688; 
+lensShading.ctrlPoints[2].controlPointB[0][4]	 = 1.093994140625; 
+lensShading.ctrlPoints[2].controlPointB[0][5]	 = 1.054687500000; 
+lensShading.ctrlPoints[2].controlPointB[0][6]	 = 1.069213867188; 
+lensShading.ctrlPoints[2].controlPointB[0][7]	 = 1.134399414063; 
+lensShading.ctrlPoints[2].controlPointB[0][8]	 = 0.739135742188; 
+lensShading.ctrlPoints[2].controlPointB[0][9]	 = 1.590576171875; 
+lensShading.ctrlPoints[2].controlPointB[1][0]	 = 0.969238281250; 
+lensShading.ctrlPoints[2].controlPointB[1][1]	 = 1.175659179688; 
+lensShading.ctrlPoints[2].controlPointB[1][2]	 = 1.016113281250; 
+lensShading.ctrlPoints[2].controlPointB[1][3]	 = 1.098632812500; 
+lensShading.ctrlPoints[2].controlPointB[1][4]	 = 1.035156250000; 
+lensShading.ctrlPoints[2].controlPointB[1][5]	 = 1.125122070313; 
+lensShading.ctrlPoints[2].controlPointB[1][6]	 = 1.055908203125; 
+lensShading.ctrlPoints[2].controlPointB[1][7]	 = 1.160644531250; 
+lensShading.ctrlPoints[2].controlPointB[1][8]	 = 0.858886718750; 
+lensShading.ctrlPoints[2].controlPointB[1][9]	 = 1.210815429688; 
+lensShading.ctrlPoints[2].controlPointB[2][0]	 = 1.090698242188; 
+lensShading.ctrlPoints[2].controlPointB[2][1]	 = 0.956787109375; 
+lensShading.ctrlPoints[2].controlPointB[2][2]	 = 1.132934570313; 
+lensShading.ctrlPoints[2].controlPointB[2][3]	 = 1.060791015625; 
+lensShading.ctrlPoints[2].controlPointB[2][4]	 = 1.104858398438; 
+lensShading.ctrlPoints[2].controlPointB[2][5]	 = 1.052490234375; 
+lensShading.ctrlPoints[2].controlPointB[2][6]	 = 1.083129882813; 
+lensShading.ctrlPoints[2].controlPointB[2][7]	 = 1.119140625000; 
+lensShading.ctrlPoints[2].controlPointB[2][8]	 = 1.034790039063; 
+lensShading.ctrlPoints[2].controlPointB[2][9]	 = 1.066894531250; 
+lensShading.ctrlPoints[2].controlPointB[3][0]	 = 1.028808593750; 
+lensShading.ctrlPoints[2].controlPointB[3][1]	 = 1.086791992188; 
+lensShading.ctrlPoints[2].controlPointB[3][2]	 = 1.064331054688; 
+lensShading.ctrlPoints[2].controlPointB[3][3]	 = 1.079223632813; 
+lensShading.ctrlPoints[2].controlPointB[3][4]	 = 1.063842773438; 
+lensShading.ctrlPoints[2].controlPointB[3][5]	 = 1.071533203125; 
+lensShading.ctrlPoints[2].controlPointB[3][6]	 = 1.080810546875; 
+lensShading.ctrlPoints[2].controlPointB[3][7]	 = 1.100219726563; 
+lensShading.ctrlPoints[2].controlPointB[3][8]	 = 1.048217773438; 
+lensShading.ctrlPoints[2].controlPointB[3][9]	 = 1.080810546875; 
+lensShading.ctrlPoints[2].controlPointB[4][0]	 = 1.089111328125; 
+lensShading.ctrlPoints[2].controlPointB[4][1]	 = 1.040405273438; 
+lensShading.ctrlPoints[2].controlPointB[4][2]	 = 1.078857421875; 
+lensShading.ctrlPoints[2].controlPointB[4][3]	 = 1.073730468750; 
+lensShading.ctrlPoints[2].controlPointB[4][4]	 = 1.048706054688; 
+lensShading.ctrlPoints[2].controlPointB[4][5]	 = 1.068847656250; 
+lensShading.ctrlPoints[2].controlPointB[4][6]	 = 1.084960937500; 
+lensShading.ctrlPoints[2].controlPointB[4][7]	 = 1.156372070313; 
+lensShading.ctrlPoints[2].controlPointB[4][8]	 = 1.000610351563; 
+lensShading.ctrlPoints[2].controlPointB[4][9]	 = 1.117797851563; 
+lensShading.ctrlPoints[2].controlPointB[5][0]	 = 1.024047851563; 
+lensShading.ctrlPoints[2].controlPointB[5][1]	 = 1.084228515625; 
+lensShading.ctrlPoints[2].controlPointB[5][2]	 = 1.072387695313; 
+lensShading.ctrlPoints[2].controlPointB[5][3]	 = 1.081665039063; 
+lensShading.ctrlPoints[2].controlPointB[5][4]	 = 1.074829101563; 
+lensShading.ctrlPoints[2].controlPointB[5][5]	 = 1.076416015625; 
+lensShading.ctrlPoints[2].controlPointB[5][6]	 = 1.093750000000; 
+lensShading.ctrlPoints[2].controlPointB[5][7]	 = 1.072509765625; 
+lensShading.ctrlPoints[2].controlPointB[5][8]	 = 1.104492187500; 
+lensShading.ctrlPoints[2].controlPointB[5][9]	 = 1.054077148438; 
+lensShading.ctrlPoints[2].controlPointB[6][0]	 = 1.086547851563; 
+lensShading.ctrlPoints[2].controlPointB[6][1]	 = 1.045410156250; 
+lensShading.ctrlPoints[2].controlPointB[6][2]	 = 1.100219726563; 
+lensShading.ctrlPoints[2].controlPointB[6][3]	 = 1.083251953125; 
+lensShading.ctrlPoints[2].controlPointB[6][4]	 = 1.095581054688; 
+lensShading.ctrlPoints[2].controlPointB[6][5]	 = 1.097167968750; 
+lensShading.ctrlPoints[2].controlPointB[6][6]	 = 1.093872070313; 
+lensShading.ctrlPoints[2].controlPointB[6][7]	 = 1.130249023438; 
+lensShading.ctrlPoints[2].controlPointB[6][8]	 = 0.980346679688; 
+lensShading.ctrlPoints[2].controlPointB[6][9]	 = 1.195190429688; 
+lensShading.ctrlPoints[2].controlPointB[7][0]	 = 1.171020507813; 
+lensShading.ctrlPoints[2].controlPointB[7][1]	 = 1.062500000000; 
+lensShading.ctrlPoints[2].controlPointB[7][2]	 = 1.063598632813; 
+lensShading.ctrlPoints[2].controlPointB[7][3]	 = 1.118530273438; 
+lensShading.ctrlPoints[2].controlPointB[7][4]	 = 1.072143554688; 
+lensShading.ctrlPoints[2].controlPointB[7][5]	 = 1.124389648438; 
+lensShading.ctrlPoints[2].controlPointB[7][6]	 = 1.093627929688; 
+lensShading.ctrlPoints[2].controlPointB[7][7]	 = 1.110961914063; 
+lensShading.ctrlPoints[2].controlPointB[7][8]	 = 0.973266601563; 
+lensShading.ctrlPoints[2].controlPointB[7][9]	 = 1.320312500000; 
+lensShading.ctrlPoints[2].controlPointB[8][0]	 = 1.119140625000; 
+lensShading.ctrlPoints[2].controlPointB[8][1]	 = 0.935791015625; 
+lensShading.ctrlPoints[2].controlPointB[8][2]	 = 1.070434570313; 
+lensShading.ctrlPoints[2].controlPointB[8][3]	 = 1.055541992188; 
+lensShading.ctrlPoints[2].controlPointB[8][4]	 = 1.120483398438; 
+lensShading.ctrlPoints[2].controlPointB[8][5]	 = 1.078979492188; 
+lensShading.ctrlPoints[2].controlPointB[8][6]	 = 1.070922851563; 
+lensShading.ctrlPoints[2].controlPointB[8][7]	 = 1.046630859375; 
+lensShading.ctrlPoints[2].controlPointB[8][8]	 = 1.051147460938; 
+lensShading.ctrlPoints[2].controlPointB[8][9]	 = 1.285156250000; 
+lensShading.ctrlPoints[2].controlPointB[9][0]	 = 1.434814453125; 
+lensShading.ctrlPoints[2].controlPointB[9][1]	 = 1.068481445313; 
+lensShading.ctrlPoints[2].controlPointB[9][2]	 = 1.075805664063; 
+lensShading.ctrlPoints[2].controlPointB[9][3]	 = 1.076904296875; 
+lensShading.ctrlPoints[2].controlPointB[9][4]	 = 1.084106445313; 
+lensShading.ctrlPoints[2].controlPointB[9][5]	 = 1.102050781250; 
+lensShading.ctrlPoints[2].controlPointB[9][6]	 = 1.076049804688; 
+lensShading.ctrlPoints[2].controlPointB[9][7]	 = 1.137207031250; 
+lensShading.ctrlPoints[2].controlPointB[9][8]	 = 1.001098632813; 
+lensShading.ctrlPoints[2].controlPointB[9][9]	 = 1.901000976563; 
diff --git a/frc971/orin/doflash_frc971.sh b/frc971/orin/doflash_frc971.sh
new file mode 100644
index 0000000..ecfa90c
--- /dev/null
+++ b/frc971/orin/doflash_frc971.sh
@@ -0,0 +1,20 @@
+#!/bin/bash
+set -ex
+
+TMPDIR=$(mktemp -d /tmp/yoctoflash.XXXXXXXXXX)
+
+# Cleanup on exit.
+function finish {
+  sudo rm -rf "${TMPDIR}"
+}
+trap finish EXIT
+
+# Assumes that the image has been copied into ./
+tar xf frc971-image-orin-nx-8g.tegraflash.tar.gz -C "${TMPDIR}"
+
+# Replace the rootfs with our new image.
+cp --sparse=always arm64_bookworm_debian_yocto.img "${TMPDIR}/frc971-image.ext4"
+
+cd ${TMPDIR}
+
+sudo ./initrd-flash
diff --git a/frc971/orin/virtual_packages/libglib-2.0-0/DEBIAN/control b/frc971/orin/virtual_packages/libglib-2.0-0/DEBIAN/control
new file mode 100644
index 0000000..1f9e4f0
--- /dev/null
+++ b/frc971/orin/virtual_packages/libglib-2.0-0/DEBIAN/control
@@ -0,0 +1,7 @@
+Package: libglib-2.0-0
+Version: 2.74.6-2
+Architecture: arm64
+Depends: libglib2.0-bin
+Maintainer: Austin Schuh <austin.linux@gmail.com>
+Provides: libglib-2.0-0
+Description: Adapter for libglib2.0-bin
diff --git a/frc971/orin/virtual_packages/libglvnd/DEBIAN/control b/frc971/orin/virtual_packages/libglvnd/DEBIAN/control
new file mode 100644
index 0000000..15ee630
--- /dev/null
+++ b/frc971/orin/virtual_packages/libglvnd/DEBIAN/control
@@ -0,0 +1,7 @@
+Package: libglvnd
+Version: 1.6.0-1
+Architecture: arm64
+Depends: libglvnd0
+Maintainer: Austin Schuh <austin.linux@gmail.com>
+Provides: libglvnd
+Description: Adapter for libglvnd
diff --git a/frc971/orin/virtual_packages/libgtk-3-0/DEBIAN/control b/frc971/orin/virtual_packages/libgtk-3-0/DEBIAN/control
new file mode 100644
index 0000000..8d85196
--- /dev/null
+++ b/frc971/orin/virtual_packages/libgtk-3-0/DEBIAN/control
@@ -0,0 +1,7 @@
+Package: libgtk-3.0
+Version: 3.24.38-2~deb12u1
+Architecture: arm64
+Depends: libgtk-3-0
+Maintainer: Austin Schuh <austin.linux@gmail.com>
+Provides: libgtk-3.0
+Description: Adapter for libgtk-3-0
diff --git a/frc971/orin/virtual_packages/libxcb-glx/DEBIAN/control b/frc971/orin/virtual_packages/libxcb-glx/DEBIAN/control
new file mode 100644
index 0000000..16c3060
--- /dev/null
+++ b/frc971/orin/virtual_packages/libxcb-glx/DEBIAN/control
@@ -0,0 +1,7 @@
+Package: libxcb-glx
+Version: 1.15-1
+Architecture: arm64
+Depends: libxcb-glx0
+Maintainer: Austin Schuh <austin.linux@gmail.com>
+Provides: libxcb-glx
+Description: Adapter for libxcb-glx0
diff --git a/frc971/orin/virtual_packages/wayland/DEBIAN/control b/frc971/orin/virtual_packages/wayland/DEBIAN/control
new file mode 100644
index 0000000..04ac8ca
--- /dev/null
+++ b/frc971/orin/virtual_packages/wayland/DEBIAN/control
@@ -0,0 +1,7 @@
+Package: wayland
+Version: 1.21.0-1
+Architecture: arm64
+Depends: libwayland-client0, libwayland-cursor0, libwayland-egl1, libwayland-server0
+Maintainer: Austin Schuh <austin.linux@gmail.com>
+Provides: wayland
+Description: Adapter for wayland
diff --git a/frc971/wpilib/falcon.cc b/frc971/wpilib/falcon.cc
index 6be83aa..c3ff26d 100644
--- a/frc971/wpilib/falcon.cc
+++ b/frc971/wpilib/falcon.cc
@@ -3,11 +3,12 @@
 using frc971::wpilib::Falcon;
 using frc971::wpilib::kMaxBringupPower;
 
-Falcon::Falcon(int device_id, std::string canbus,
+Falcon::Falcon(int device_id, bool inverted, std::string canbus,
                std::vector<ctre::phoenix6::BaseStatusSignal *> *signals,
                double stator_current_limit, double supply_current_limit)
     : talon_(device_id, canbus),
       device_id_(device_id),
+      inverted_(inverted),
       device_temp_(talon_.GetDeviceTemp()),
       supply_voltage_(talon_.GetSupplyVoltage()),
       supply_current_(talon_.GetSupplyCurrent()),
@@ -37,6 +38,12 @@
   signals->push_back(&duty_cycle_);
 }
 
+Falcon::Falcon(FalconParams params, std::string canbus,
+               std::vector<ctre::phoenix6::BaseStatusSignal *> *signals,
+               double stator_current_limit, double supply_current_limit)
+    : Falcon(params.device_id, params.inverted, canbus, signals,
+             stator_current_limit, supply_current_limit) {}
+
 void Falcon::PrintConfigs() {
   ctre::phoenix6::configs::TalonFXConfiguration configuration;
   ctre::phoenix::StatusCode status =
@@ -48,9 +55,7 @@
   AOS_LOG(INFO, "configuration: %s", configuration.ToString().c_str());
 }
 
-void Falcon::WriteConfigs(ctre::phoenix6::signals::InvertedValue invert) {
-  inverted_ = invert;
-
+void Falcon::WriteConfigs() {
   ctre::phoenix6::configs::CurrentLimitsConfigs current_limits;
   current_limits.StatorCurrentLimit = stator_current_limit_;
   current_limits.StatorCurrentLimitEnable = true;
@@ -94,7 +99,8 @@
   return status;
 }
 
-void Falcon::SerializePosition(flatbuffers::FlatBufferBuilder *fbb) {
+void Falcon::SerializePosition(flatbuffers::FlatBufferBuilder *fbb,
+                               double gear_ratio) {
   control_loops::CANFalcon::Builder builder(*fbb);
   builder.add_id(device_id_);
   builder.add_device_temp(device_temp());
@@ -102,7 +108,7 @@
   builder.add_supply_current(supply_current());
   builder.add_torque_current(torque_current());
   builder.add_duty_cycle(duty_cycle());
-  builder.add_position(position());
+  builder.add_position(position() * gear_ratio);
 
   last_position_offset_ = builder.Finish();
 }
diff --git a/frc971/wpilib/falcon.h b/frc971/wpilib/falcon.h
index 8f0f1f0..6ea8735 100644
--- a/frc971/wpilib/falcon.h
+++ b/frc971/wpilib/falcon.h
@@ -18,24 +18,35 @@
 namespace frc971 {
 namespace wpilib {
 
+struct FalconParams {
+  int device_id;
+  bool inverted;
+};
+
 static constexpr units::frequency::hertz_t kCANUpdateFreqHz = 200_Hz;
 static constexpr double kMaxBringupPower = 12.0;
 
 // Gets info from and writes to falcon motors using the TalonFX controller.
 class Falcon {
  public:
-  Falcon(int device_id, std::string canbus,
+  Falcon(int device_id, bool inverted, std::string canbus,
+         std::vector<ctre::phoenix6::BaseStatusSignal *> *signals,
+         double stator_current_limit, double supply_current_limit);
+
+  Falcon(FalconParams params, std::string canbus,
          std::vector<ctre::phoenix6::BaseStatusSignal *> *signals,
          double stator_current_limit, double supply_current_limit);
 
   void PrintConfigs();
 
-  void WriteConfigs(ctre::phoenix6::signals::InvertedValue invert);
+  void WriteConfigs();
   ctre::phoenix::StatusCode WriteCurrent(double current, double max_voltage);
 
   ctre::phoenix6::hardware::TalonFX *talon() { return &talon_; }
 
-  void SerializePosition(flatbuffers::FlatBufferBuilder *fbb);
+  // The position of the Falcon output shaft is multiplied by gear_ratio
+  void SerializePosition(flatbuffers::FlatBufferBuilder *fbb,
+                         double gear_ratio);
 
   std::optional<flatbuffers::Offset<control_loops::CANFalcon>> TakeOffset();
 
diff --git a/frc971/wpilib/swerve/swerve_drivetrain_writer.cc b/frc971/wpilib/swerve/swerve_drivetrain_writer.cc
index 2b6ef9e..bfc5d73 100644
--- a/frc971/wpilib/swerve/swerve_drivetrain_writer.cc
+++ b/frc971/wpilib/swerve/swerve_drivetrain_writer.cc
@@ -37,8 +37,8 @@
 
 void DrivetrainWriter::WriteConfigs() {
   for (auto module : {front_left_, front_right_, back_left_, back_right_}) {
-    module->rotation->WriteConfigs(false);
-    module->translation->WriteConfigs(false);
+    module->rotation->WriteConfigs();
+    module->translation->WriteConfigs();
   }
 }
 
diff --git a/frc971/wpilib/swerve/swerve_module.h b/frc971/wpilib/swerve/swerve_module.h
index 534f0ce..f449afa 100644
--- a/frc971/wpilib/swerve/swerve_module.h
+++ b/frc971/wpilib/swerve/swerve_module.h
@@ -8,14 +8,15 @@
 namespace swerve {
 
 struct SwerveModule {
-  SwerveModule(int rotation_id, int translation_id, std::string canbus,
+  SwerveModule(FalconParams rotation_params, FalconParams translation_params,
+               std::string canbus,
                std::vector<ctre::phoenix6::BaseStatusSignal *> *signals,
                double stator_current_limit, double supply_current_limit)
-      : rotation(std::make_shared<Falcon>(rotation_id, canbus, signals,
+      : rotation(std::make_shared<Falcon>(rotation_params, canbus, signals,
                                           stator_current_limit,
                                           supply_current_limit)),
-        translation(std::make_shared<Falcon>(translation_id, canbus, signals,
-                                             stator_current_limit,
+        translation(std::make_shared<Falcon>(translation_params, canbus,
+                                             signals, stator_current_limit,
                                              supply_current_limit)) {}
 
   void WriteModule(
diff --git a/frc971/zeroing/BUILD b/frc971/zeroing/BUILD
index d50f181..8ca7f67 100644
--- a/frc971/zeroing/BUILD
+++ b/frc971/zeroing/BUILD
@@ -61,20 +61,8 @@
 cc_library(
     name = "zeroing",
     srcs = [
-        "absolute_and_absolute_encoder.cc",
-        "absolute_encoder.cc",
-        "hall_effect_and_position.cc",
-        "pot_and_absolute_encoder.cc",
-        "pot_and_index.cc",
-        "pulse_index.cc",
     ],
     hdrs = [
-        "absolute_and_absolute_encoder.h",
-        "absolute_encoder.h",
-        "hall_effect_and_position.h",
-        "pot_and_absolute_encoder.h",
-        "pot_and_index.h",
-        "pulse_index.h",
         "zeroing.h",
     ],
     target_compatible_with = ["@platforms//os:linux"],
@@ -88,19 +76,10 @@
     ],
 )
 
-cc_test(
-    name = "zeroing_test",
-    srcs = [
-        "absolute_and_absolute_encoder_test.cc",
-        "absolute_encoder_test.cc",
-        "hall_effect_and_position_test.cc",
-        "pot_and_absolute_encoder_test.cc",
-        "pot_and_index_test.cc",
-        "pulse_index_test.cc",
-        "relative_encoder_test.cc",
-        "zeroing_test.h",
-    ],
-    target_compatible_with = ["@platforms//os:linux"],
+cc_library(
+    name = "zeroing_test_lib",
+    testonly = True,
+    hdrs = ["zeroing_test.h"],
     deps = [
         ":zeroing",
         "//aos/testing:googletest",
@@ -109,6 +88,46 @@
     ],
 )
 
+[
+    (
+        cc_library(
+            name = lib,
+            srcs = [lib + ".cc"],
+            hdrs = [lib + ".h"],
+            deps = [
+                ":wrap",
+                ":zeroing",
+                "//aos/containers:error_list",
+                "//aos/logging",
+                "//frc971:constants",
+                "//frc971/control_loops:control_loops_fbs",
+                "@com_github_google_glog//:glog",
+            ],
+        ),
+        cc_test(
+            name = lib + "_test",
+            srcs = [lib + "_test.cc"],
+            deps = [
+                lib,
+                ":zeroing",
+                ":zeroing_test_lib",
+                "//aos/testing:googletest",
+                "//frc971/control_loops:control_loops_fbs",
+                "//frc971/control_loops:position_sensor_sim",
+            ],
+        ),
+    )
+    for lib in [
+        "absolute_and_absolute_encoder",
+        "absolute_encoder",
+        "continuous_absolute_encoder",
+        "hall_effect_and_position",
+        "pot_and_absolute_encoder",
+        "pot_and_index",
+        "pulse_index",
+    ]
+]
+
 cc_library(
     name = "wrap",
     srcs = [
diff --git a/frc971/zeroing/absolute_encoder.h b/frc971/zeroing/absolute_encoder.h
index df40ec3..9d730f2 100644
--- a/frc971/zeroing/absolute_encoder.h
+++ b/frc971/zeroing/absolute_encoder.h
@@ -5,6 +5,7 @@
 
 #include "flatbuffers/flatbuffers.h"
 
+#include "aos/containers/error_list.h"
 #include "frc971/zeroing/zeroing.h"
 
 namespace frc971 {
diff --git a/frc971/zeroing/continuous_absolute_encoder.cc b/frc971/zeroing/continuous_absolute_encoder.cc
new file mode 100644
index 0000000..a47a491
--- /dev/null
+++ b/frc971/zeroing/continuous_absolute_encoder.cc
@@ -0,0 +1,169 @@
+#include "frc971/zeroing/continuous_absolute_encoder.h"
+
+#include <cmath>
+#include <numeric>
+
+#include "glog/logging.h"
+
+#include "aos/containers/error_list.h"
+#include "frc971/zeroing/wrap.h"
+
+namespace frc971 {
+namespace zeroing {
+
+ContinuousAbsoluteEncoderZeroingEstimator::
+    ContinuousAbsoluteEncoderZeroingEstimator(
+        const constants::ContinuousAbsoluteEncoderZeroingConstants &constants)
+    : constants_(constants), move_detector_(constants_.moving_buffer_size) {
+  relative_to_absolute_offset_samples_.reserve(constants_.average_filter_size);
+  Reset();
+}
+
+void ContinuousAbsoluteEncoderZeroingEstimator::Reset() {
+  zeroed_ = false;
+  error_ = false;
+  first_offset_ = 0.0;
+  offset_ = 0.0;
+  samples_idx_ = 0;
+  position_ = 0.0;
+  nan_samples_ = 0;
+  relative_to_absolute_offset_samples_.clear();
+  move_detector_.Reset();
+}
+
+// The math here is a bit backwards, but I think it'll be less error prone that
+// way and more similar to the version with a pot as well.
+//
+// We start by unwrapping the absolute encoder using the relative encoder.  This
+// puts us in a non-wrapping space and lets us average a bit easier.  From
+// there, we can compute an offset and wrap ourselves back such that we stay
+// close to the middle value.
+//
+// To guard against the robot moving while updating estimates, buffer a number
+// of samples and check that the buffered samples are not different than the
+// zeroing threshold. At any point that the samples differ too much, do not
+// update estimates based on those samples.
+void ContinuousAbsoluteEncoderZeroingEstimator::UpdateEstimate(
+    const AbsolutePosition &info) {
+  // Check for Abs Encoder NaN value that would mess up the rest of the zeroing
+  // code below. NaN values are given when the Absolute Encoder is disconnected.
+  if (::std::isnan(info.absolute_encoder())) {
+    if (zeroed_) {
+      VLOG(1) << "NAN on absolute encoder.";
+      errors_.Set(ZeroingError::LOST_ABSOLUTE_ENCODER);
+      error_ = true;
+    } else {
+      ++nan_samples_;
+      VLOG(1) << "NAN on absolute encoder while zeroing " << nan_samples_;
+      if (nan_samples_ >= constants_.average_filter_size) {
+        errors_.Set(ZeroingError::LOST_ABSOLUTE_ENCODER);
+        error_ = true;
+        zeroed_ = true;
+      }
+    }
+    // Throw some dummy values in for now.
+    filtered_absolute_encoder_ = info.absolute_encoder();
+    position_ = offset_ + info.encoder();
+    return;
+  }
+
+  const bool moving = move_detector_.Update(info, constants_.moving_buffer_size,
+                                            constants_.zeroing_threshold);
+
+  if (!moving) {
+    const PositionStruct &sample = move_detector_.GetSample();
+
+    // adjusted_* numbers are nominally in the desired output frame.
+    const double adjusted_absolute_encoder =
+        sample.absolute_encoder - constants_.measured_absolute_position;
+
+    // Note: If are are near the breakpoint of the absolute encoder, this number
+    // will be jitter between numbers that are ~one_revolution_distance apart.
+    // For that reason, we rewrap it so that we are not near that boundary.
+    const double relative_to_absolute_offset =
+        adjusted_absolute_encoder - sample.encoder;
+
+    // To avoid the aforementioned jitter, choose a base value to use for
+    // wrapping. When we have no prior samples, just use the current offset.
+    // Otherwise, we use an arbitrary prior offset (the stored offsets will all
+    // already be wrapped).
+    const double relative_to_absolute_offset_wrap_base =
+        relative_to_absolute_offset_samples_.size() == 0
+            ? relative_to_absolute_offset
+            : relative_to_absolute_offset_samples_[0];
+
+    const double relative_to_absolute_offset_wrapped =
+        UnWrap(relative_to_absolute_offset_wrap_base,
+               relative_to_absolute_offset, constants_.one_revolution_distance);
+
+    const size_t relative_to_absolute_offset_samples_size =
+        relative_to_absolute_offset_samples_.size();
+    if (relative_to_absolute_offset_samples_size <
+        constants_.average_filter_size) {
+      relative_to_absolute_offset_samples_.push_back(
+          relative_to_absolute_offset_wrapped);
+    } else {
+      relative_to_absolute_offset_samples_[samples_idx_] =
+          relative_to_absolute_offset_wrapped;
+    }
+    samples_idx_ = (samples_idx_ + 1) % constants_.average_filter_size;
+
+    // Compute the average offset between the absolute encoder and relative
+    // encoder. Because we just pushed a value, the size() will never be zero.
+    offset_ =
+        ::std::accumulate(relative_to_absolute_offset_samples_.begin(),
+                          relative_to_absolute_offset_samples_.end(), 0.0) /
+        relative_to_absolute_offset_samples_.size();
+
+    // To provide a value that can be used to estimate the
+    // measured_absolute_position when zeroing, we just need to output the
+    // current absolute encoder value. We could make use of the averaging
+    // implicit in offset_ to reduce the noise on this slightly.
+    filtered_absolute_encoder_ = sample.absolute_encoder;
+
+    if (offset_ready()) {
+      if (!zeroed_) {
+        first_offset_ = offset_;
+      }
+
+      if (::std::abs(first_offset_ - offset_) >
+          constants_.allowable_encoder_error *
+              constants_.one_revolution_distance) {
+        VLOG(1) << "Offset moved too far. Initial: " << first_offset_
+                << ", current " << offset_ << ", allowable change: "
+                << constants_.allowable_encoder_error *
+                       constants_.one_revolution_distance;
+        errors_.Set(ZeroingError::OFFSET_MOVED_TOO_FAR);
+        error_ = true;
+      }
+
+      zeroed_ = true;
+    }
+  }
+
+  // Update the position. Wrap it to reflect the fact that we do not have
+  // sufficient information to disambiguate which revolution we are on (also,
+  // since this value is primarily meant for debugging, this makes it easier to
+  // see that the device is actually at zero without having to divide by 2 *
+  // pi).
+  position_ =
+      Wrap(0.0, offset_ + info.encoder(), constants_.one_revolution_distance);
+}
+
+flatbuffers::Offset<ContinuousAbsoluteEncoderZeroingEstimator::State>
+ContinuousAbsoluteEncoderZeroingEstimator::GetEstimatorState(
+    flatbuffers::FlatBufferBuilder *fbb) const {
+  flatbuffers::Offset<flatbuffers::Vector<ZeroingError>> errors_offset =
+      errors_.ToFlatbuffer(fbb);
+
+  State::Builder builder(*fbb);
+  builder.add_error(error_);
+  builder.add_zeroed(zeroed_);
+  builder.add_position(position_);
+  builder.add_absolute_position(filtered_absolute_encoder_);
+  builder.add_errors(errors_offset);
+  return builder.Finish();
+}
+
+}  // namespace zeroing
+}  // namespace frc971
diff --git a/frc971/zeroing/continuous_absolute_encoder.h b/frc971/zeroing/continuous_absolute_encoder.h
new file mode 100644
index 0000000..e11d866
--- /dev/null
+++ b/frc971/zeroing/continuous_absolute_encoder.h
@@ -0,0 +1,99 @@
+#ifndef FRC971_ZEROING_CONTINUOUS_ABSOLUTE_ENCODER_H_
+#define FRC971_ZEROING_CONTINUOUS_ABSOLUTE_ENCODER_H_
+
+#include <vector>
+
+#include "flatbuffers/flatbuffers.h"
+
+#include "aos/containers/error_list.h"
+#include "frc971/zeroing/zeroing.h"
+
+namespace frc971 {
+namespace zeroing {
+
+// Estimates the position with an absolute encoder which spins continuously. The
+// absolute encoder must have a 1:1 ratio to the output.
+// The provided offset(), when added to incremental encoder, may return a value
+// outside of +/- pi. The user is responsible for handling wrapping.
+class ContinuousAbsoluteEncoderZeroingEstimator
+    : public ZeroingEstimator<
+          AbsolutePosition,
+          constants::ContinuousAbsoluteEncoderZeroingConstants,
+          AbsoluteEncoderEstimatorState> {
+ public:
+  explicit ContinuousAbsoluteEncoderZeroingEstimator(
+      const constants::ContinuousAbsoluteEncoderZeroingConstants &constants);
+
+  // Resets the internal logic so it needs to be re-zeroed.
+  void Reset() override;
+
+  // Updates the sensor values for the zeroing logic.
+  void UpdateEstimate(const AbsolutePosition &info) override;
+
+  void TriggerError() override { error_ = true; }
+
+  bool zeroed() const override { return zeroed_; }
+
+  double offset() const override { return offset_; }
+
+  bool error() const override { return error_; }
+
+  // Returns true if the sample buffer is full.
+  bool offset_ready() const override {
+    return relative_to_absolute_offset_samples_.size() ==
+           constants_.average_filter_size;
+  }
+
+  // Returns information about our current state.
+  virtual flatbuffers::Offset<State> GetEstimatorState(
+      flatbuffers::FlatBufferBuilder *fbb) const override;
+
+ private:
+  struct PositionStruct {
+    PositionStruct(const AbsolutePosition &position_buffer)
+        : absolute_encoder(position_buffer.absolute_encoder()),
+          encoder(position_buffer.encoder()) {}
+    double absolute_encoder;
+    double encoder;
+  };
+
+  // The zeroing constants used to describe the configuration of the system.
+  const constants::ContinuousAbsoluteEncoderZeroingConstants constants_;
+
+  // True if the mechanism is zeroed.
+  bool zeroed_;
+  // Marker to track whether an error has occurred.
+  bool error_;
+  // The first valid offset we recorded. This is only set after zeroed_ first
+  // changes to true.
+  double first_offset_;
+
+  // The filtered absolute encoder.  This is used in the status for calibration.
+  double filtered_absolute_encoder_ = 0.0;
+
+  // Samples of the offset needed to line the relative encoder up with the
+  // absolute encoder.
+  ::std::vector<double> relative_to_absolute_offset_samples_;
+
+  MoveDetector<PositionStruct, AbsolutePosition> move_detector_;
+
+  // Estimated start position of the mechanism
+  double offset_ = 0;
+  // The next position in 'relative_to_absolute_offset_samples_' and
+  // 'encoder_samples_' to be used to store the next sample.
+  int samples_idx_ = 0;
+
+  // Number of NANs we've seen in a row.
+  size_t nan_samples_ = 0;
+
+  // The filtered position.
+  double position_ = 0.0;
+
+  // Marker to track what kind of error has occured.
+  aos::ErrorList<ZeroingError> errors_;
+};
+
+}  // namespace zeroing
+}  // namespace frc971
+
+#endif  // FRC971_ZEROING_CONTINUOUS_ABSOLUTE_ENCODER_H_
diff --git a/frc971/zeroing/continuous_absolute_encoder_test.cc b/frc971/zeroing/continuous_absolute_encoder_test.cc
new file mode 100644
index 0000000..3869393
--- /dev/null
+++ b/frc971/zeroing/continuous_absolute_encoder_test.cc
@@ -0,0 +1,198 @@
+#include "frc971/zeroing/continuous_absolute_encoder.h"
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+
+#include "frc971/zeroing/wrap.h"
+#include "frc971/zeroing/zeroing_test.h"
+
+namespace frc971 {
+namespace zeroing {
+namespace testing {
+
+using constants::ContinuousAbsoluteEncoderZeroingConstants;
+
+class ContinuousAbsoluteEncoderZeroingTest : public ZeroingTest {
+ protected:
+  void MoveTo(PositionSensorSimulator *simulator,
+              ContinuousAbsoluteEncoderZeroingEstimator *estimator,
+              double new_position) {
+    simulator->MoveTo(new_position);
+    flatbuffers::FlatBufferBuilder fbb;
+    estimator->UpdateEstimate(
+        *simulator->FillSensorValues<AbsolutePosition>(&fbb));
+  }
+};
+
+// Makes sure that using an absolute encoder lets us zero without moving.
+TEST_F(ContinuousAbsoluteEncoderZeroingTest,
+       TestContinuousAbsoluteEncoderZeroingWithoutMovement) {
+  const double index_diff = 1.0;
+  PositionSensorSimulator sim(index_diff);
+
+  const double start_pos = 2.1;
+  double measured_absolute_position = 0.3 * index_diff;
+
+  ContinuousAbsoluteEncoderZeroingConstants constants{
+      kSampleSize, index_diff,        measured_absolute_position,
+      0.1,         kMovingBufferSize, kIndexErrorFraction};
+
+  sim.Initialize(start_pos, index_diff / 3.0, 0.0,
+                 constants.measured_absolute_position);
+
+  ContinuousAbsoluteEncoderZeroingEstimator estimator(constants);
+
+  for (size_t i = 0; i < kSampleSize + kMovingBufferSize - 1; ++i) {
+    MoveTo(&sim, &estimator, start_pos);
+    ASSERT_FALSE(estimator.zeroed());
+  }
+
+  MoveTo(&sim, &estimator, start_pos);
+  ASSERT_TRUE(estimator.zeroed());
+  // Because the continuous estimator doesn't care about extra revolutions, it
+  // will have brought the offset down to less than index_diff.
+  EXPECT_NEAR(Wrap(0.0, start_pos, index_diff), estimator.offset(), 1e-12);
+}
+
+// Makes sure that if the underlying mechanism moves by >1 revolution that the
+// continuous zeroing estimator handles it correctly.
+TEST_F(ContinuousAbsoluteEncoderZeroingTest,
+       ContinuousEstimatorZeroesAcrossRevolution) {
+  const double index_diff = 1.0;
+  PositionSensorSimulator sim(index_diff);
+
+  const double start_pos = 2.1;
+  double measured_absolute_position = 0.3 * index_diff;
+
+  ContinuousAbsoluteEncoderZeroingConstants constants{
+      kSampleSize, index_diff,        measured_absolute_position,
+      0.1,         kMovingBufferSize, kIndexErrorFraction};
+
+  sim.Initialize(start_pos, index_diff / 3.0, 0.0,
+                 constants.measured_absolute_position);
+
+  ContinuousAbsoluteEncoderZeroingEstimator estimator(constants);
+
+  for (size_t i = 0; i < kSampleSize + kMovingBufferSize - 1; ++i) {
+    MoveTo(&sim, &estimator, start_pos);
+    ASSERT_FALSE(estimator.zeroed());
+  }
+
+  MoveTo(&sim, &estimator, start_pos);
+  ASSERT_TRUE(estimator.zeroed());
+  // Because the continuous estimator doesn't care about extra revolutions, it
+  // will have brought the offset down to less than index_diff.
+  EXPECT_NEAR(Wrap(0.0, start_pos, index_diff), estimator.offset(), 1e-12);
+
+  // Now, rotate by a full revolution, then stay still. We should stay zeroed.
+  for (size_t i = 0; i < kSampleSize + kMovingBufferSize; ++i) {
+    MoveTo(&sim, &estimator, start_pos + 10 * index_diff);
+  }
+  ASSERT_TRUE(estimator.zeroed());
+  ASSERT_FALSE(estimator.error());
+}
+
+// Makes sure that we ignore a NAN if we get it, but will correctly zero
+// afterwards.
+TEST_F(ContinuousAbsoluteEncoderZeroingTest,
+       TestContinuousAbsoluteEncoderZeroingIgnoresNAN) {
+  const double index_diff = 1.0;
+  PositionSensorSimulator sim(index_diff);
+
+  const double start_pos = 2.1;
+  double measured_absolute_position = 0.3 * index_diff;
+
+  ContinuousAbsoluteEncoderZeroingConstants constants{
+      kSampleSize, index_diff,        measured_absolute_position,
+      0.1,         kMovingBufferSize, kIndexErrorFraction};
+
+  sim.Initialize(start_pos, index_diff / 3.0, 0.0,
+                 constants.measured_absolute_position);
+
+  ContinuousAbsoluteEncoderZeroingEstimator estimator(constants);
+
+  // We tolerate a couple NANs before we start.
+  flatbuffers::FlatBufferBuilder fbb;
+  fbb.Finish(CreateAbsolutePosition(
+      fbb, 0.0, ::std::numeric_limits<double>::quiet_NaN()));
+  const auto sensor_values =
+      flatbuffers::GetRoot<AbsolutePosition>(fbb.GetBufferPointer());
+  for (size_t i = 0; i < kSampleSize - 1; ++i) {
+    estimator.UpdateEstimate(*sensor_values);
+  }
+
+  for (size_t i = 0; i < kSampleSize + kMovingBufferSize - 1; ++i) {
+    MoveTo(&sim, &estimator, start_pos);
+    ASSERT_FALSE(estimator.zeroed());
+  }
+
+  MoveTo(&sim, &estimator, start_pos);
+  ASSERT_TRUE(estimator.zeroed());
+  // Because the continuous estimator doesn't care about extra revolutions, it
+  // will have brought the offset down to less than index_diff.
+  EXPECT_NEAR(Wrap(0.0, start_pos, index_diff), estimator.offset(), 1e-12);
+}
+
+// Makes sure that using an absolute encoder doesn't let us zero while moving.
+TEST_F(ContinuousAbsoluteEncoderZeroingTest,
+       TestContinuousAbsoluteEncoderZeroingWithMovement) {
+  const double index_diff = 1.0;
+  PositionSensorSimulator sim(index_diff);
+
+  const double start_pos = 10 * index_diff;
+  double measured_absolute_position = 0.3 * index_diff;
+
+  ContinuousAbsoluteEncoderZeroingConstants constants{
+      kSampleSize, index_diff,        measured_absolute_position,
+      0.1,         kMovingBufferSize, kIndexErrorFraction};
+
+  sim.Initialize(start_pos, index_diff / 3.0, 0.0,
+                 constants.measured_absolute_position);
+
+  ContinuousAbsoluteEncoderZeroingEstimator estimator(constants);
+
+  for (size_t i = 0; i < kSampleSize + kMovingBufferSize - 1; ++i) {
+    MoveTo(&sim, &estimator, start_pos + i * index_diff);
+    ASSERT_FALSE(estimator.zeroed());
+  }
+  MoveTo(&sim, &estimator, start_pos + 10 * index_diff);
+
+  MoveTo(&sim, &estimator, start_pos);
+  ASSERT_FALSE(estimator.zeroed());
+}
+
+// Makes sure we detect an error if the ZeroingEstimator gets sent a NaN.
+TEST_F(ContinuousAbsoluteEncoderZeroingTest,
+       TestContinuousAbsoluteEncoderZeroingWithNaN) {
+  ContinuousAbsoluteEncoderZeroingConstants constants{
+      kSampleSize, 1, 0.3, 0.1, kMovingBufferSize, kIndexErrorFraction};
+
+  ContinuousAbsoluteEncoderZeroingEstimator estimator(constants);
+
+  flatbuffers::FlatBufferBuilder fbb;
+  fbb.Finish(CreateAbsolutePosition(
+      fbb, 0.0, ::std::numeric_limits<double>::quiet_NaN()));
+  const auto sensor_values =
+      flatbuffers::GetRoot<AbsolutePosition>(fbb.GetBufferPointer());
+  for (size_t i = 0; i < kSampleSize - 1; ++i) {
+    estimator.UpdateEstimate(*sensor_values);
+  }
+  ASSERT_FALSE(estimator.error());
+
+  estimator.UpdateEstimate(*sensor_values);
+  ASSERT_TRUE(estimator.error());
+
+  flatbuffers::FlatBufferBuilder fbb2;
+  fbb2.Finish(estimator.GetEstimatorState(&fbb2));
+
+  const AbsoluteEncoderEstimatorState *state =
+      flatbuffers::GetRoot<AbsoluteEncoderEstimatorState>(
+          fbb2.GetBufferPointer());
+
+  EXPECT_THAT(*state->errors(),
+              ::testing::ElementsAre(ZeroingError::LOST_ABSOLUTE_ENCODER));
+}
+
+}  // namespace testing
+}  // namespace zeroing
+}  // namespace frc971
diff --git a/frc971/zeroing/zeroing.h b/frc971/zeroing/zeroing.h
index f7b52a6..5d5b6eb 100644
--- a/frc971/zeroing/zeroing.h
+++ b/frc971/zeroing/zeroing.h
@@ -172,13 +172,4 @@
 }  // namespace zeroing
 }  // namespace frc971
 
-// TODO(Brian): Actually split these targets apart. Need to convert all the
-// reverse dependencies to #include what they actually need...
-#include "frc971/zeroing/absolute_and_absolute_encoder.h"
-#include "frc971/zeroing/absolute_encoder.h"
-#include "frc971/zeroing/hall_effect_and_position.h"
-#include "frc971/zeroing/pot_and_absolute_encoder.h"
-#include "frc971/zeroing/pot_and_index.h"
-#include "frc971/zeroing/pulse_index.h"
-
 #endif  // FRC971_ZEROING_ZEROING_H_
diff --git a/scouting/www/view/view.ng.html b/scouting/www/view/view.ng.html
index 2273217..ee2c62f 100644
--- a/scouting/www/view/view.ng.html
+++ b/scouting/www/view/view.ng.html
@@ -98,8 +98,8 @@
             </div>
           </th>
           <th scope="col">Team</th>
-          <th scope="col">Set</th>
           <th scope="col">Comp Level</th>
+          <th scope="col">Scout</th>
           <th scope="col"></th>
         </tr>
       </thead>
@@ -107,8 +107,8 @@
         <tr *ngFor="let stat2023 of statList; index as i;">
           <th scope="row">{{stat2023.matchNumber()}}</th>
           <td>{{stat2023.teamNumber()}}</td>
-          <td>{{stat2023.setNumber()}}</td>
           <td>{{COMP_LEVEL_LABELS[stat2023.compLevel()]}}</td>
+          <td>{{stat2023.collectedBy()}}</td>
           <!-- Delete Icon. -->
           <td>
             <button
diff --git a/third_party/apriltag/common/workerpool.c b/third_party/apriltag/common/workerpool.c
index 29eccfc..a23832e 100644
--- a/third_party/apriltag/common/workerpool.c
+++ b/third_party/apriltag/common/workerpool.c
@@ -52,7 +52,7 @@
 {
     workerpool_t *wp = (workerpool_t*) p;
 
-    int cnt = 0;
+    //int cnt = 0;
 
     while (1) {
         struct task *task;
@@ -63,13 +63,13 @@
 //          printf("%"PRId64" thread %d did %d\n", utime_now(), pthread_self(), cnt);
             pthread_cond_broadcast(&wp->endcond);
             pthread_cond_wait(&wp->startcond, &wp->mutex);
-            cnt = 0;
+            //cnt = 0;
 //            printf("%"PRId64" thread %d awake\n", utime_now(), pthread_self());
         }
 
         zarray_get_volatile(wp->tasks, wp->taskspos, &task);
         wp->taskspos++;
-        cnt++;
+        //cnt++;
         pthread_mutex_unlock(&wp->mutex);
 //        pthread_yield();
         sched_yield();
diff --git a/third_party/apriltag/common/zarray.c b/third_party/apriltag/common/zarray.c
index 43e6a7e..fa1f8a2 100644
--- a/third_party/apriltag/common/zarray.c
+++ b/third_party/apriltag/common/zarray.c
@@ -41,7 +41,7 @@
     return strcmp(a,b);
 }
 
-void zarray_vmap(zarray_t *za, void (*f)())
+void zarray_vmap(zarray_t *za, void (*f)(void*))
 {
     assert(za != NULL);
     assert(f != NULL);
diff --git a/third_party/apriltag/common/zhash.c b/third_party/apriltag/common/zhash.c
index 914f530..9414513 100644
--- a/third_party/apriltag/common/zhash.c
+++ b/third_party/apriltag/common/zhash.c
@@ -353,7 +353,7 @@
     zit->last_entry--;
 }
 
-void zhash_map_keys(zhash_t *zh, void (*f)())
+void zhash_map_keys(zhash_t *zh, void (*f)(void*))
 {
     assert(zh != NULL);
     if (f == NULL)
@@ -369,7 +369,7 @@
     }
 }
 
-void zhash_vmap_keys(zhash_t * zh, void (*f)())
+void zhash_vmap_keys(zhash_t * zh, void (*f)(void*))
 {
     assert(zh != NULL);
     if (f == NULL)
@@ -386,7 +386,7 @@
     }
 }
 
-void zhash_map_values(zhash_t * zh, void (*f)())
+void zhash_map_values(zhash_t * zh, void (*f)(void*))
 {
     assert(zh != NULL);
     if (f == NULL)
@@ -401,7 +401,7 @@
     }
 }
 
-void zhash_vmap_values(zhash_t * zh, void (*f)())
+void zhash_vmap_values(zhash_t * zh, void (*f)(void*))
 {
     assert(zh != NULL);
     if (f == NULL)
diff --git a/third_party/apriltag/common/zmaxheap.c b/third_party/apriltag/common/zmaxheap.c
index e04d03e..5cf5292 100644
--- a/third_party/apriltag/common/zmaxheap.c
+++ b/third_party/apriltag/common/zmaxheap.c
@@ -167,7 +167,7 @@
     }
 }
 
-void zmaxheap_vmap(zmaxheap_t *heap, void (*f)())
+void zmaxheap_vmap(zmaxheap_t *heap, void (*f)(void*))
 {
     assert(heap != NULL);
     assert(f != NULL);
diff --git a/third_party/bazel-toolchain/toolchain/cc_toolchain_config.bzl b/third_party/bazel-toolchain/toolchain/cc_toolchain_config.bzl
index 59b542c..b167f6e 100644
--- a/third_party/bazel-toolchain/toolchain/cc_toolchain_config.bzl
+++ b/third_party/bazel-toolchain/toolchain/cc_toolchain_config.bzl
@@ -53,6 +53,12 @@
     target_os_arch_key = _os_arch_pair(target_os, target_arch)
     _check_os_arch_keys([host_os_arch_key, target_os_arch_key])
 
+    ## doesn't seem to have a feature for this.
+    llvm_version_split = llvm_version.split(".")
+    llvm_major_ver = int(llvm_version_split[0]) if len(llvm_version_split) else 0
+
+    llvm_subfolder = llvm_version_split[0] if llvm_major_ver > 14 else llvm_version
+
     # A bunch of variables that get passed straight through to
     # `create_cc_toolchain_config_info`.
     # TODO: What do these values mean, and are they actually all correct?
@@ -129,7 +135,7 @@
 
     resource_dir = [
         "-resource-dir",
-        "{}lib/clang/{}".format(target_toolchain_path_prefix, llvm_version),
+        "{}lib/clang/{}".format(target_toolchain_path_prefix, llvm_subfolder),
     ]
 
     # Default compiler flags:
@@ -278,7 +284,7 @@
             common_include_flags = [
                 "-nostdinc",
                 "-isystem",
-                target_toolchain_path_prefix + "lib/clang/{}/include".format(llvm_version),
+                target_toolchain_path_prefix + "lib/clang/{}/include".format(llvm_subfolder),
                 "-isystem",
                 sysroot_path + "/usr/local/include",
                 "-isystem",
@@ -293,6 +299,7 @@
                 sysroot_path + "/usr/include",
             ]
             compile_not_cxx_flags.extend(common_include_flags)
+
             cxx_flags.extend([
                 "-nostdinc++",
                 "-isystem",
@@ -321,13 +328,12 @@
     coverage_link_flags = ["-fprofile-instr-generate"]
 
     ## NOTE: framework paths is missing here; unix_cc_toolchain_config
-    ## doesn't seem to have a feature for this.
 
     # C++ built-in include directories:
     cxx_builtin_include_directories = [
         target_toolchain_path_prefix + "include/c++/v1",
-        target_toolchain_path_prefix + "lib/clang/{}/include".format(llvm_version),
-        target_toolchain_path_prefix + "lib64/clang/{}/include".format(llvm_version),
+        target_toolchain_path_prefix + "lib/clang/{}/include".format(llvm_subfolder),
+        target_toolchain_path_prefix + "lib64/clang/{}/include".format(llvm_subfolder),
     ]
 
     sysroot_prefix = ""
@@ -354,8 +360,6 @@
 
     # Tool paths:
     # `llvm-strip` was introduced in V7 (https://reviews.llvm.org/D46407):
-    llvm_version = llvm_version.split(".")
-    llvm_major_ver = int(llvm_version[0]) if len(llvm_version) else 0
     strip_binary = (tools_path_prefix + "bin/llvm-strip") if llvm_major_ver >= 7 else _host_tools.get_and_assert(host_tools_info, "strip")
 
     # TODO: The command line formed on darwin does not work with llvm-ar.
diff --git a/third_party/bazel-toolchain/toolchain/internal/llvm_distributions.bzl b/third_party/bazel-toolchain/toolchain/internal/llvm_distributions.bzl
index 4cdbf75..031f4cf 100644
--- a/third_party/bazel-toolchain/toolchain/internal/llvm_distributions.bzl
+++ b/third_party/bazel-toolchain/toolchain/internal/llvm_distributions.bzl
@@ -181,6 +181,102 @@
     "clang+llvm-13.0.0-x86_64-linux-gnu-ubuntu-16.04.tar.xz": "76d0bf002ede7a893f69d9ad2c4e101d15a8f4186fbfe24e74856c8449acd7c1",
     "clang+llvm-13.0.0-x86_64-linux-gnu-ubuntu-20.04.tar.xz": "2c2fb857af97f41a5032e9ecadf7f78d3eff389a5cd3c9ec620d24f134ceb3c8",
     "clang+llvm-13.0.0-aarch64-linux-gnu.tar.xz": "968d65d2593850ee9b37fcda074fb7641529bd45d2f976af6c8197de3c22612f",
+
+    # 13.0.1
+    "clang+llvm-13.0.1-aarch64-linux-gnu.tar.xz": "15ff2db12683e69e552b6668f7ca49edaa01ce32cb1cbc8f8ed2e887ab291069",
+    "clang+llvm-13.0.1-amd64-unknown-freebsd12.tar.xz": "8101c8d3a920bf930b33987ada5373f43537c5de8c194be0ea10530fd0ad5617",
+    "clang+llvm-13.0.1-amd64-unknown-freebsd13.tar.xz": "f1ba8ec77b5e82399af738ad9897a8aafc11c5692ceb331c8373eae77018d428",
+    "clang+llvm-13.0.1-armv7a-linux-gnueabihf.tar.xz": "1215720114538f57acbe2f3b0614c23f4fc551ba2976afa3779a3c01aaaf1221",
+    "clang+llvm-13.0.1-i386-unknown-freebsd12.tar.xz": "e3c921e0f130afa6a6ebac23c31b66b32563a5ec53a2f4ed4676f31a81379f70",
+    "clang+llvm-13.0.1-i386-unknown-freebsd13.tar.xz": "e85c46bd64a0217f3df1f42421a502648d6741ef29fd5d44674b87af119ce25d",
+    "clang+llvm-13.0.1-powerpc64le-linux-rhel-7.9.tar.xz": "ab659c290536182a99c064d4537d2fb1273bb2b1bf8c6a43866f033bf1ece4a8",
+    "clang+llvm-13.0.1-powerpc64le-linux-ubuntu-18.04.5.tar.xz": "7a4be2508aa0b4ee3f72c312af4b62ea14581a5db61aa703ea0822f46e5598cb",
+    "clang+llvm-13.0.1-x86_64-apple-darwin.tar.xz": "dec02d17698514d0fc7ace8869c38937851c542b02adf102c4e898f027145a4d",
+    "clang+llvm-13.0.1-x86_64-linux-gnu-ubuntu-18.04.tar.xz": "84a54c69781ad90615d1b0276a83ff87daaeded99fbc64457c350679df7b4ff0",
+
+    # 14.0.0
+    "clang+llvm-14.0.0-aarch64-linux-gnu.tar.xz": "1792badcd44066c79148ffeb1746058422cc9d838462be07e3cb19a4b724a1ee",
+    "clang+llvm-14.0.0-amd64-pc-solaris2.11.tar.xz": "a708470fdbaadf530d6cfd56f92fde1328cb47ef8439ecf1a2126523e7c94a50",
+    "clang+llvm-14.0.0-amd64-unknown-freebsd12.tar.xz": "7eaff7ee2a32babd795599f41f4a5ffe7f161721ebf5630f48418e626650105e",
+    "clang+llvm-14.0.0-amd64-unknown-freebsd13.tar.xz": "b68d73fd57be385e7f06046a87381f7520c8861f492c294e6301d2843d9a1f57",
+    "clang+llvm-14.0.0-armv7a-linux-gnueabihf.tar.xz": "17d5f60c3d5f9494be7f67b2dc9e6017cd5e8457e53465968a54ec7069923bfe",
+    "clang+llvm-14.0.0-i386-unknown-freebsd12.tar.xz": "5ed9d93a8425132e8117d7061d09c2989ce6b2326f25c46633e2b2dee955bb00",
+    "clang+llvm-14.0.0-i386-unknown-freebsd13.tar.xz": "81f49eb466ce9149335ac8918a5f02fa724d562a94464ed13745db0165b4a220",
+    "clang+llvm-14.0.0-powerpc64-ibm-aix-7.2.tar.xz": "4ad5866de6c69d989cbbc989201b46dfdcd7d2b23a712fcad7baa09c204f10de",
+    "clang+llvm-14.0.0-powerpc64le-linux-rhel-7.9.tar.xz": "7a31de37959fdf3be897b01f284a91c28cd38a2e2fa038ff58121d1b6f6eb087",
+    "clang+llvm-14.0.0-powerpc64le-linux-ubuntu-18.04.tar.xz": "2d504c4920885c86b306358846178bc2232dfac83b47c3b1d05861a8162980e6",
+    "clang+llvm-14.0.0-sparcv9-sun-solaris2.11.tar.xz": "b342cdaaea3b44de5b0f45052e2df49bcdf69dcc8ad0c23ec5afc04668929681",
+    "clang+llvm-14.0.0-x86_64-apple-darwin.tar.xz": "cf5af0f32d78dcf4413ef6966abbfd5b1445fe80bba57f2ff8a08f77e672b9b3",
+    "clang+llvm-14.0.0-x86_64-linux-gnu-ubuntu-18.04.tar.xz": "61582215dafafb7b576ea30cc136be92c877ba1f1c31ddbbd372d6d65622fef5",
+    "clang+llvm-14.0.0-x86_64-linux-sles12.4.tar.xz": "78f70cc94c3b6f562455b15cebb63e75571d50c3d488d53d9aa4cd9dded30627",
+
+    # 15.0.0
+    "clang+llvm-15.0.0-aarch64-linux-gnu.tar.xz": "527ed550784681f95ec7a1be8fbf5a24bd03d7da9bf31afb6523996f45670be3",
+    "clang+llvm-15.0.0-amd64-pc-solaris2.11.tar.xz": "5b9fd6a30ce6941adf74667d2076a49aa047fa040e3690f7af26c264d4ce58e7",
+    "clang+llvm-15.0.0-arm64-apple-darwin21.0.tar.xz": "cfd5c3fa07d7fccea0687f5b4498329a6172b7a15bbc45b547d0ac86bd3452a5",
+    "clang+llvm-15.0.0-armv7a-linux-gnueabihf.tar.xz": "58ce8877642fc1399736ffc81bc8ef6244440fc78d72e097a07475b8b25e2bf1",
+    "clang+llvm-15.0.0-powerpc64-ibm-aix-7.2.tar.xz": "c5f63401fa88ea96ca7110bd81ead1bf1a2575962e9cc84a6713ec29c02b1c10",
+    "clang+llvm-15.0.0-powerpc64le-linux-rhel-8.4.tar.xz": "c94448766b6b92cfc8f35e611308c9680a9ad2177f88d358c2b06e9b108d61bd",
+    "clang+llvm-15.0.0-powerpc64le-linux-ubuntu-18.04.6.tar.xz": "6bcedc3d18552732f219c1d0f8c4b0c917ff5f800400a31dabfe8d040cbf1f02",
+    "clang+llvm-15.0.0-sparc64-unknown-linux-gnu.tar.xz": "b5a8108040d5d5d69d6106fa89a6cffc71a16a3583b74c1f15c42f392a47a3d9",
+    "clang+llvm-15.0.0-sparcv9-sun-solaris2.11.tar.xz": "4354854976355ca6f4ac90231a97121844c4fc9f998c9850527390120c62f01f",
+    "clang+llvm-15.0.0-x86_64-apple-darwin.tar.xz": "8fb11e6ada98b901398b2e7b0378a3a59e88c88c754e95d8f6b54613254d7d65",
+
+    # 15.0.6
+    "clang+llvm-15.0.6-aarch64-linux-gnu.tar.xz": "8ca4d68cf103da8331ca3f35fe23d940c1b78fb7f0d4763c1c059e352f5d1bec",
+    "clang+llvm-15.0.6-arm64-apple-darwin21.0.tar.xz": "32bc7b8eee3d98f72dd4e5651e6da990274ee2d28c5c19a7d8237eb817ce8d91",
+    "clang+llvm-15.0.6-armv7a-linux-gnueabihf.tar.xz": "c12e9298f9a9ed3a96342e9ffb2c02146a0cd7535231fef57c7217bd3a36f53b",
+    "clang+llvm-15.0.6-powerpc64-ibm-aix-7.2.tar.xz": "6bc1c2fcc8069e28773f6a0d16624160cd6de01b8f15aab27652eedad665d462",
+    "clang+llvm-15.0.6-powerpc64le-linux-rhel-8.4.tar.xz": "c26e5563e6ff46a03bc45fe27547c69283b64cba2359ccd3a42f735c995c0511",
+    "clang+llvm-15.0.6-powerpc64le-linux-ubuntu-18.04.tar.xz": "7fc9f07ff0fcf191df93fe4adc1da555e43f62fe1d3ddafb15c943f72b1bda17",
+    "clang+llvm-15.0.6-x86_64-linux-gnu-ubuntu-18.04.tar.xz": "38bc7f5563642e73e69ac5626724e206d6d539fbef653541b34cae0ba9c3f036",
+
+    # 15.0.7
+    "clang+llvm-15.0.7-arm64-apple-darwin22.0.tar.xz": "867c6afd41158c132ef05a8f1ddaecf476a26b91c85def8e124414f9a9ba188d",
+    "clang+llvm-15.0.7-powerpc64-ibm-aix-7.2.tar.xz": "6cbc7c7f4395abb9c1a5bdcab3811bd6b1a6c4d08756ba674bfbbd732e2b23ac",
+    "clang+llvm-15.0.7-powerpc64le-linux-rhel-8.4.tar.xz": "2163cc934437146dc30810a21a46327ba3983f123c3bea19be316a64135b6414",
+    "clang+llvm-15.0.7-powerpc64le-linux-ubuntu-18.04.tar.xz": "19a16d768e15966923b0cbf8fc7dc148c89e316857acd89ad3aff72dcfcd61f4",
+    "clang+llvm-15.0.7-x86_64-apple-darwin21.0.tar.xz": "d16b6d536364c5bec6583d12dd7e6cf841b9f508c4430d9ee886726bd9983f1c",
+
+    # 16.0.0
+    "clang+llvm-16.0.0-aarch64-linux-gnu.tar.xz": "b750ba3120e6153fc5b316092f19b52cf3eb64e19e5f44bd1b962cb54a20cf0a",
+    "clang+llvm-16.0.0-amd64-pc-solaris2.11.tar.xz": "b637b7da383d3417ac4862342911cb467fba2ec00f48f163eb8308f2bbb9b7ad",
+    "clang+llvm-16.0.0-amd64-unknown-freebsd13.tar.xz": "c4fe6293349b3ab7d802793103d1d44f58831884e63ff1b40ce29c3e7408257b",
+    "clang+llvm-16.0.0-arm64-apple-darwin22.0.tar.xz": "2041587b90626a4a87f0de14a5842c14c6c3374f42c8ed12726ef017416409d9",
+    "clang+llvm-16.0.0-powerpc64-ibm-aix-7.2.tar.xz": "e51209eeea3c3db41084d8625ab3357991980831e0b641d633ec23e9d858333f",
+    "clang+llvm-16.0.0-powerpc64le-linux-rhel-8.4.tar.xz": "eb56949af9a83a12754f7cf254886d30c4be8a1da4dd0f27db790a7fcd35a3bf",
+    "clang+llvm-16.0.0-powerpc64le-linux-ubuntu-18.04.tar.xz": "ae34b037cde14f19c3c431de5fc04e06fa43d2cce3f8d44a63659b48afdf1f7a",
+    "clang+llvm-16.0.0-sparc64-unknown-linux-gnu.tar.xz": "a2627fcb6d97405b38c9e4c17ccfdc5d61fdd1bee742dcce0726ed39e2dcd92c",
+    "clang+llvm-16.0.0-sparcv9-sun-solaris2.11.tar.xz": "45c2ac0c10c3876332407a1ea893dccbde77a490f4a9b54a00e4881681a3c5ea",
+    "clang+llvm-16.0.0-x86_64-linux-gnu-ubuntu-18.04.tar.xz": "2b8a69798e8dddeb57a186ecac217a35ea45607cb2b3cf30014431cff4340ad1",
+
+    # 16.0.1
+    "clang+llvm-16.0.1-aarch64-linux-gnu.tar.xz": "83e38451772120b016432687c0a3aab391808442b86f54966ef44c73a26280ac",
+    "clang+llvm-16.0.1-amd64-unknown-freebsd13.tar.xz": "970359de2a1a09a93a9e1cf3405e5758dfe463567b20a168f9156bd72b7f8ac6",
+    "clang+llvm-16.0.1-arm64-apple-darwin22.0.tar.xz": "cb487fa991f047dc79ae36430cbb9ef14621c1262075373955b1d97215c75879",
+    "clang+llvm-16.0.1-powerpc64-ibm-aix-7.2.tar.xz": "c56d9cf643b7f39e40436e55b59b3bd88057ec0fa084bd8e06ac17fb20ea2a21",
+    "clang+llvm-16.0.1-powerpc64le-linux-rhel-8.4.tar.xz": "c89a9af64a35ee58ef4eac7b52c173707140dc7eac6839ff254b656de8eb6c3c",
+    "clang+llvm-16.0.1-powerpc64le-linux-ubuntu-20.04.tar.xz": "08b39f9e6c19086aaf029d155c42a4db96ce662f84d6e89d8c9037d3baeee036",
+
+    # 16.0.2
+    "clang+llvm-16.0.2-aarch64-linux-gnu.tar.xz": "de89d138cfb17e2d81fdaca2f9c5e0c042014beea6bcacde7f27db40b69c0bdc",
+    "clang+llvm-16.0.2-amd64-unknown-freebsd13.tar.xz": "0cd92b6a84e7477aa8070465f01eec8198e0b1e38d1b6da8c61859a633ec9a71",
+    "clang+llvm-16.0.2-arm64-apple-darwin22.0.tar.xz": "539861297b8aa6be8e89bf68268b07d79d7a1fde87f4b98f123709f13933f326",
+    "clang+llvm-16.0.2-powerpc64-ibm-aix-7.2.tar.xz": "8c9cbf29b261f1af905f41032b446fd78bd560b549ab31d05a16d0cc972df23d",
+    "clang+llvm-16.0.2-powerpc64le-linux-rhel-8.4.tar.xz": "fe21023b64d2298d65fea0f4832a27a9948121662b54a8c8ce8a9331c4039c36",
+    "clang+llvm-16.0.2-x86_64-linux-gnu-ubuntu-22.04.tar.xz": "9530eccdffedb9761f23cbd915cf95d861b1d95f340ea36ded68bd6312af912e",
+
+    # 16.0.3
+    "clang+llvm-16.0.3-aarch64-linux-gnu.tar.xz": "315fd821ddb3e4b10c4dfabe7f200d1d17902b6a5ccd5dd665a0cd454bca379f",
+    "clang+llvm-16.0.3-arm64-apple-darwin22.0.tar.xz": "b9068eee1cf1e17848241ea581a2abe6cb4a15d470ec515c100f8b52e4c6a7cb",
+    "clang+llvm-16.0.3-powerpc64-ibm-aix-7.2.tar.xz": "f0372ea5b665ca1b8524b933b84ccbe59e9441537388815b24323aa4aab7db2f",
+    "clang+llvm-16.0.3-powerpc64le-linux-rhel-8.4.tar.xz": "9804721c746d74a85ce935d938509277af728fad1548835f539660ff1380e04d",
+    "clang+llvm-16.0.3-x86_64-linux-gnu-ubuntu-22.04.tar.xz": "638d32fd0032f99bafaab3bae63a406adb771825a02b6b7da119ee7e71af26c6",
+
+    # 16.0.4
+    "clang+llvm-16.0.4-amd64-unknown-freebsd13.tar.xz": "cf9d73bcf05b8749c7f3efbe86654b8fe0209f28993eafe26c27eb85885593f7",
+    "clang+llvm-16.0.4-arm64-apple-darwin22.0.tar.xz": "429b8061d620108fee636313df55a0602ea0d14458c6d3873989e6b130a074bd",
+    "clang+llvm-16.0.4-x86_64-linux-gnu-ubuntu-22.04.tar.xz": "fd464333bd55b482eb7385f2f4e18248eb43129a3cda4c0920ad9ac3c12bdacf",
 }
 
 # Note: Unlike the user-specified llvm_mirror attribute, the URL prefixes in
@@ -201,6 +297,16 @@
     "12.0.0": "https://github.com/llvm/llvm-project/releases/download/llvmorg-",
     "12.0.1": "https://github.com/llvm/llvm-project/releases/download/llvmorg-",
     "13.0.0": "https://github.com/llvm/llvm-project/releases/download/llvmorg-",
+    "13.0.1": "https://github.com/llvm/llvm-project/releases/download/llvmorg-",
+    "14.0.0": "https://github.com/llvm/llvm-project/releases/download/llvmorg-",
+    "15.0.0": "https://github.com/llvm/llvm-project/releases/download/llvmorg-",
+    "15.0.6": "https://github.com/llvm/llvm-project/releases/download/llvmorg-",
+    "15.0.7": "https://github.com/llvm/llvm-project/releases/download/llvmorg-",
+    "16.0.0": "https://github.com/llvm/llvm-project/releases/download/llvmorg-",
+    "16.0.1": "https://github.com/llvm/llvm-project/releases/download/llvmorg-",
+    "16.0.2": "https://github.com/llvm/llvm-project/releases/download/llvmorg-",
+    "16.0.3": "https://github.com/llvm/llvm-project/releases/download/llvmorg-",
+    "16.0.4": "https://github.com/llvm/llvm-project/releases/download/llvmorg-",
 }
 
 def _get_auth(ctx, urls):
diff --git a/third_party/eigen/Eigen/src/SparseCore/TriangularSolver.h b/third_party/eigen/Eigen/src/SparseCore/TriangularSolver.h
index f9c56ba..07c0d88 100644
--- a/third_party/eigen/Eigen/src/SparseCore/TriangularSolver.h
+++ b/third_party/eigen/Eigen/src/SparseCore/TriangularSolver.h
@@ -270,17 +270,12 @@
       }
 
 
-      Index count = 0;
       // FIXME compute a reference value to filter zeros
       for (typename AmbiVector<Scalar,StorageIndex>::Iterator it(tempVector/*,1e-12*/); it; ++it)
       {
-        ++ count;
-//         std::cerr << "fill " << it.index() << ", " << col << "\n";
-//         std::cout << it.value() << "  ";
         // FIXME use insertBack
         res.insert(it.index(), col) = it.value();
       }
-//       std::cout << "tempVector.nonZeros() == " << int(count) << " / " << (other.rows()) << "\n";
     }
     res.finalize();
     other = res.markAsRValue();
diff --git a/third_party/eigen/Eigen/src/SparseLU/SparseLU_heap_relax_snode.h b/third_party/eigen/Eigen/src/SparseLU/SparseLU_heap_relax_snode.h
index 6f75d50..7aecbca 100644
--- a/third_party/eigen/Eigen/src/SparseLU/SparseLU_heap_relax_snode.h
+++ b/third_party/eigen/Eigen/src/SparseLU/SparseLU_heap_relax_snode.h
@@ -75,8 +75,6 @@
   // Identify the relaxed supernodes by postorder traversal of the etree
   Index snode_start; // beginning of a snode 
   StorageIndex k;
-  Index nsuper_et_post = 0; // Number of relaxed snodes in postordered etree 
-  Index nsuper_et = 0; // Number of relaxed snodes in the original etree 
   StorageIndex l; 
   for (j = 0; j < n; )
   {
@@ -88,7 +86,6 @@
       parent = et(j);
     }
     // Found a supernode in postordered etree, j is the last column 
-    ++nsuper_et_post;
     k = StorageIndex(n);
     for (Index i = snode_start; i <= j; ++i)
       k = (std::min)(k, inv_post(i));
@@ -97,7 +94,6 @@
     {
       // This is also a supernode in the original etree
       relax_end(k) = l; // Record last column 
-      ++nsuper_et; 
     }
     else 
     {
@@ -107,7 +103,6 @@
         if (descendants(i) == 0) 
         {
           relax_end(l) = l;
-          ++nsuper_et;
         }
       }
     }
diff --git a/third_party/flatbuffers/src/util.cpp b/third_party/flatbuffers/src/util.cpp
index aabc23a..d3cf0d8 100644
--- a/third_party/flatbuffers/src/util.cpp
+++ b/third_party/flatbuffers/src/util.cpp
@@ -212,9 +212,16 @@
   return g_file_exists_function(name);
 }
 
-bool DirExists(const char *name) {
-  // clang-format off
+#ifdef __clang__
+#define NO_MSAN_ATTRIBUTE __attribute__((no_sanitize("memory")))
+#else
+#define NO_MSAN_ATTRIBUTE
+#endif
 
+// For no obvious reason, clang's sanitizer thinks that the mode bits from
+// stat() are uninitialized in some circumstances.
+bool DirExists(const char *name) NO_MSAN_ATTRIBUTE {
+  // clang-format off
   #ifdef _WIN32
     #define flatbuffers_stat _stat
     #define FLATBUFFERS_S_IFDIR _S_IFDIR
diff --git a/third_party/google-glog/bazel/glog.bzl b/third_party/google-glog/bazel/glog.bzl
index b4825d3..63f40d3 100644
--- a/third_party/google-glog/bazel/glog.bzl
+++ b/third_party/google-glog/bazel/glog.bzl
@@ -90,6 +90,7 @@
     ]
 
     linux_or_darwin_copts = wasm_copts + [
+        "-Wno-unused-but-set-variable",
         "-DGLOG_EXPORT=__attribute__((visibility(\\\"default\\\")))",
         # For src/utilities.cc.
         "-DHAVE_SYS_SYSCALL_H",
diff --git a/third_party/googletest/googletest.patch b/third_party/googletest/googletest.patch
index ad57dc5..25d8cf4 100644
--- a/third_party/googletest/googletest.patch
+++ b/third_party/googletest/googletest.patch
@@ -30,3 +30,17 @@
      linkopts = select({
          "//:qnx": [],
          "//:windows": [],
+diff --git a/googletest/test/BUILD.bazel b/googletest/test/BUILD.bazel
+index 1890b6ff..9bd00bd2 100644
+--- a/googletest/test/BUILD.bazel
++++ b/googletest/test/BUILD.bazel
+@@ -151,6 +151,9 @@ cc_test(
+     name = "gtest_unittest",
+     size = "small",
+     srcs = ["gtest_unittest.cc"],
++    copts = [
++        "-Wno-unused-but-set-variable",
++    ],
+     shard_count = 2,
+     deps = ["//:gtest_main"],
+ )
diff --git a/third_party/pico-sdk/tools/pioasm/BUILD b/third_party/pico-sdk/tools/pioasm/BUILD
index 355834f..1a276c3 100644
--- a/third_party/pico-sdk/tools/pioasm/BUILD
+++ b/third_party/pico-sdk/tools/pioasm/BUILD
@@ -21,6 +21,7 @@
         "-Wno-unused-parameter",
         "-Wno-sign-compare",
         "-fexceptions",
+        "-Wno-unused-but-set-variable",
     ],
     includes = [
         ".",
diff --git a/third_party/rawrtc/usrsctp/BUILD b/third_party/rawrtc/usrsctp/BUILD
index 6d1fa69..2a4118f 100644
--- a/third_party/rawrtc/usrsctp/BUILD
+++ b/third_party/rawrtc/usrsctp/BUILD
@@ -50,6 +50,7 @@
     ] + compiler_select({
         "clang": [
             "-Wno-unused-but-set-variable",
+            "-Wno-deprecated-non-prototype",
         ],
         "gcc": [
             "-Wno-discarded-qualifiers",
diff --git a/tools/build_rules/autocxx.bzl b/tools/build_rules/autocxx.bzl
index d4b6be6..01a9197 100644
--- a/tools/build_rules/autocxx.bzl
+++ b/tools/build_rules/autocxx.bzl
@@ -138,6 +138,7 @@
     gen_rs.add_all(["--outdir", out_rs_json.dirname])
     gen_rs.add("--gen-rs-archive")
     gen_rs.add("--gen-cpp")
+    #gen_rs.add("--auto-allowlist")
 
     gen_rs.add_all(["--generate-exact", ctx.attr.sections_to_generate])
 
diff --git a/y2014/control_loops/drivetrain/drivetrain_base.h b/y2014/control_loops/drivetrain/drivetrain_base.h
index a47ff5c..48143c3 100644
--- a/y2014/control_loops/drivetrain/drivetrain_base.h
+++ b/y2014/control_loops/drivetrain/drivetrain_base.h
@@ -6,8 +6,8 @@
 namespace y2014 {
 namespace control_loops {
 
-const ::frc971::control_loops::drivetrain::DrivetrainConfig<double>
-    &GetDrivetrainConfig();
+const ::frc971::control_loops::drivetrain::DrivetrainConfig<double> &
+GetDrivetrainConfig();
 
 }  // namespace control_loops
 }  // namespace y2014
diff --git a/y2014_bot3/control_loops/drivetrain/drivetrain_base.h b/y2014_bot3/control_loops/drivetrain/drivetrain_base.h
index d41ff99..98cb31d 100644
--- a/y2014_bot3/control_loops/drivetrain/drivetrain_base.h
+++ b/y2014_bot3/control_loops/drivetrain/drivetrain_base.h
@@ -10,8 +10,8 @@
 const double kDrivetrainEncoderRatio =
     (17.0 / 50.0) /*output reduction*/ * (64.0 / 24.0) /*encoder gears*/;
 
-const ::frc971::control_loops::drivetrain::DrivetrainConfig<double>
-    &GetDrivetrainConfig();
+const ::frc971::control_loops::drivetrain::DrivetrainConfig<double> &
+GetDrivetrainConfig();
 
 }  // namespace drivetrain
 }  // namespace control_loops
diff --git a/y2016/control_loops/drivetrain/drivetrain_base.h b/y2016/control_loops/drivetrain/drivetrain_base.h
index 4d5525a..0186a6c 100644
--- a/y2016/control_loops/drivetrain/drivetrain_base.h
+++ b/y2016/control_loops/drivetrain/drivetrain_base.h
@@ -7,8 +7,8 @@
 namespace control_loops {
 namespace drivetrain {
 
-const ::frc971::control_loops::drivetrain::DrivetrainConfig<double>
-    &GetDrivetrainConfig();
+const ::frc971::control_loops::drivetrain::DrivetrainConfig<double> &
+GetDrivetrainConfig();
 
 }  // namespace drivetrain
 }  // namespace control_loops
diff --git a/y2016/dashboard/dashboard.cc b/y2016/dashboard/dashboard.cc
index 56b10d7..0b12b14 100644
--- a/y2016/dashboard/dashboard.cc
+++ b/y2016/dashboard/dashboard.cc
@@ -42,11 +42,11 @@
 
 // Define the following if we want to use a local www directory and feed in
 // dummy data.
-//#define DASHBOARD_TESTING
+// #define DASHBOARD_TESTING
 
 // Define the following if we want to read from the vision queue, which has
 // caused problems in the past when auto aiming that still need to be addressed.
-//#define DASHBOARD_READ_VISION_QUEUE
+// #define DASHBOARD_READ_VISION_QUEUE
 
 DataCollector::DataCollector(::aos::EventLoop *event_loop)
     : event_loop_(event_loop),
diff --git a/y2017/control_loops/drivetrain/drivetrain_base.h b/y2017/control_loops/drivetrain/drivetrain_base.h
index 59ca4a9..c84934f 100644
--- a/y2017/control_loops/drivetrain/drivetrain_base.h
+++ b/y2017/control_loops/drivetrain/drivetrain_base.h
@@ -7,8 +7,8 @@
 namespace control_loops {
 namespace drivetrain {
 
-const ::frc971::control_loops::drivetrain::DrivetrainConfig<double>
-    &GetDrivetrainConfig();
+const ::frc971::control_loops::drivetrain::DrivetrainConfig<double> &
+GetDrivetrainConfig();
 
 }  // namespace drivetrain
 }  // namespace control_loops
diff --git a/y2017/control_loops/superstructure/column/BUILD b/y2017/control_loops/superstructure/column/BUILD
index 2d83482..6e1cc7f 100644
--- a/y2017/control_loops/superstructure/column/BUILD
+++ b/y2017/control_loops/superstructure/column/BUILD
@@ -71,6 +71,8 @@
         "//frc971:constants",
         "//frc971/control_loops:profiled_subsystem_fbs",
         "//frc971/zeroing",
+        "//frc971/zeroing:hall_effect_and_position",
+        "//frc971/zeroing:pulse_index",
         "//frc971/zeroing:wrap",
         "//y2017:constants",
         "//y2017/control_loops/superstructure:superstructure_position_fbs",
diff --git a/y2017/control_loops/superstructure/column/column.cc b/y2017/control_loops/superstructure/column/column.cc
index 9acd190..b8a12f7 100644
--- a/y2017/control_loops/superstructure/column/column.cc
+++ b/y2017/control_loops/superstructure/column/column.cc
@@ -11,6 +11,7 @@
 #include "frc971/constants.h"
 #include "frc971/control_loops/profiled_subsystem.h"
 #include "frc971/control_loops/state_feedback_loop.h"
+#include "frc971/zeroing/pulse_index.h"
 #include "y2017/control_loops/superstructure/column/column_integral_plant.h"
 #include "y2017/control_loops/superstructure/column/stuck_column_integral_plant.h"
 
diff --git a/y2017/control_loops/superstructure/column/column_zeroing.h b/y2017/control_loops/superstructure/column/column_zeroing.h
index a7fe47b..c07a504 100644
--- a/y2017/control_loops/superstructure/column/column_zeroing.h
+++ b/y2017/control_loops/superstructure/column/column_zeroing.h
@@ -2,6 +2,7 @@
 #define Y2017_CONTROL_LOOPS_SUPERSTRUCTURE_COLUMN_H_
 
 #include "frc971/constants.h"
+#include "frc971/zeroing/hall_effect_and_position.h"
 #include "frc971/zeroing/zeroing.h"
 #include "y2017/constants.h"
 #include "y2017/control_loops/superstructure/superstructure_position_generated.h"
diff --git a/y2017/control_loops/superstructure/hood/BUILD b/y2017/control_loops/superstructure/hood/BUILD
index 8f162a7..9edcc8e 100644
--- a/y2017/control_loops/superstructure/hood/BUILD
+++ b/y2017/control_loops/superstructure/hood/BUILD
@@ -43,6 +43,7 @@
     deps = [
         ":hood_plants",
         "//frc971/control_loops:profiled_subsystem",
+        "//frc971/zeroing:pulse_index",
         "//y2017:constants",
         "//y2017/control_loops/superstructure:superstructure_goal_fbs",
         "//y2017/control_loops/superstructure:superstructure_position_fbs",
diff --git a/y2017/control_loops/superstructure/hood/hood.h b/y2017/control_loops/superstructure/hood/hood.h
index 8284e6e..47ace6e 100644
--- a/y2017/control_loops/superstructure/hood/hood.h
+++ b/y2017/control_loops/superstructure/hood/hood.h
@@ -2,6 +2,7 @@
 #define Y2017_CONTROL_LOOPS_SUPERSTRUCTURE_HOOD_HOOD_H_
 
 #include "frc971/control_loops/profiled_subsystem.h"
+#include "frc971/zeroing/pulse_index.h"
 #include "y2017/constants.h"
 #include "y2017/control_loops/superstructure/superstructure_goal_generated.h"
 
diff --git a/y2017/control_loops/superstructure/intake/BUILD b/y2017/control_loops/superstructure/intake/BUILD
index 550c241..00642b0 100644
--- a/y2017/control_loops/superstructure/intake/BUILD
+++ b/y2017/control_loops/superstructure/intake/BUILD
@@ -43,6 +43,7 @@
     deps = [
         ":intake_plants",
         "//frc971/control_loops:profiled_subsystem",
+        "//frc971/zeroing:pot_and_absolute_encoder",
         "//y2017:constants",
         "//y2017/control_loops/superstructure:superstructure_goal_fbs",
     ],
diff --git a/y2017/control_loops/superstructure/intake/intake.h b/y2017/control_loops/superstructure/intake/intake.h
index a91d2f0..29899fb 100644
--- a/y2017/control_loops/superstructure/intake/intake.h
+++ b/y2017/control_loops/superstructure/intake/intake.h
@@ -2,6 +2,7 @@
 #define Y2017_CONTROL_LOOPS_SUPERSTRUCTURE_INTAKE_INTAKE_H_
 
 #include "frc971/control_loops/profiled_subsystem.h"
+#include "frc971/zeroing/pot_and_absolute_encoder.h"
 #include "y2017/constants.h"
 #include "y2017/control_loops/superstructure/superstructure_goal_generated.h"
 
diff --git a/y2017/vision/target_finder.cc b/y2017/vision/target_finder.cc
index f7a958c..e917b2e 100644
--- a/y2017/vision/target_finder.cc
+++ b/y2017/vision/target_finder.cc
@@ -68,14 +68,12 @@
   RangeImage t_img = Transpose(img);
   int total = 0;
   int split = 0;
-  int count = t_img.mini();
   for (const auto &row : t_img) {
     if (row.size() == 1) {
       total++;
     } else if (row.size() == 2) {
       split++;
     }
-    count++;
   }
   return (double)split / total;
 }
diff --git a/y2018/control_loops/drivetrain/drivetrain_base.h b/y2018/control_loops/drivetrain/drivetrain_base.h
index a7ce43f..0592a25 100644
--- a/y2018/control_loops/drivetrain/drivetrain_base.h
+++ b/y2018/control_loops/drivetrain/drivetrain_base.h
@@ -7,8 +7,8 @@
 namespace control_loops {
 namespace drivetrain {
 
-const ::frc971::control_loops::drivetrain::DrivetrainConfig<double>
-    &GetDrivetrainConfig();
+const ::frc971::control_loops::drivetrain::DrivetrainConfig<double> &
+GetDrivetrainConfig();
 
 }  // namespace drivetrain
 }  // namespace control_loops
diff --git a/y2018/control_loops/superstructure/arm/BUILD b/y2018/control_loops/superstructure/arm/BUILD
index 9f969ff..ec18f9d 100644
--- a/y2018/control_loops/superstructure/arm/BUILD
+++ b/y2018/control_loops/superstructure/arm/BUILD
@@ -15,6 +15,7 @@
         "//frc971/control_loops/double_jointed_arm:graph",
         "//frc971/control_loops/double_jointed_arm:trajectory",
         "//frc971/zeroing",
+        "//frc971/zeroing:pot_and_absolute_encoder",
         "//y2018:constants",
         "//y2018/control_loops/superstructure:superstructure_position_fbs",
         "//y2018/control_loops/superstructure:superstructure_status_fbs",
diff --git a/y2018/control_loops/superstructure/arm/arm.h b/y2018/control_loops/superstructure/arm/arm.h
index 0b0a6a4..a9cf614 100644
--- a/y2018/control_loops/superstructure/arm/arm.h
+++ b/y2018/control_loops/superstructure/arm/arm.h
@@ -6,6 +6,7 @@
 #include "frc971/control_loops/double_jointed_arm/ekf.h"
 #include "frc971/control_loops/double_jointed_arm/graph.h"
 #include "frc971/control_loops/double_jointed_arm/trajectory.h"
+#include "frc971/zeroing/pot_and_absolute_encoder.h"
 #include "frc971/zeroing/zeroing.h"
 #include "y2018/constants.h"
 #include "y2018/control_loops/superstructure/arm/generated_graph.h"
diff --git a/y2018/control_loops/superstructure/intake/BUILD b/y2018/control_loops/superstructure/intake/BUILD
index fa4eb7e..50d9989 100644
--- a/y2018/control_loops/superstructure/intake/BUILD
+++ b/y2018/control_loops/superstructure/intake/BUILD
@@ -46,6 +46,7 @@
         "//frc971/control_loops:control_loop",
         "//frc971/control_loops:control_loops_fbs",
         "//frc971/zeroing",
+        "//frc971/zeroing:pot_and_absolute_encoder",
         "//y2018:constants",
         "//y2018/control_loops/superstructure:superstructure_output_fbs",
         "//y2018/control_loops/superstructure:superstructure_position_fbs",
diff --git a/y2018/control_loops/superstructure/intake/intake.h b/y2018/control_loops/superstructure/intake/intake.h
index 09a7e4d..bec5ff6 100644
--- a/y2018/control_loops/superstructure/intake/intake.h
+++ b/y2018/control_loops/superstructure/intake/intake.h
@@ -5,6 +5,7 @@
 
 #include "aos/commonmath.h"
 #include "frc971/control_loops/control_loop.h"
+#include "frc971/zeroing/pot_and_absolute_encoder.h"
 #include "frc971/zeroing/wrap.h"
 #include "frc971/zeroing/zeroing.h"
 #include "y2018/constants.h"
diff --git a/y2019/BUILD b/y2019/BUILD
index 2fba6d6..d388819 100644
--- a/y2019/BUILD
+++ b/y2019/BUILD
@@ -41,6 +41,8 @@
         "//frc971/control_loops:pose",
         "//frc971/control_loops:static_zeroing_single_dof_profiled_subsystem",
         "//frc971/control_loops/drivetrain:camera",
+        "//frc971/zeroing:absolute_encoder",
+        "//frc971/zeroing:pot_and_absolute_encoder",
         "//y2019/control_loops/drivetrain:polydrivetrain_plants",
         "//y2019/control_loops/superstructure/elevator:elevator_plants",
         "//y2019/control_loops/superstructure/intake:intake_plants",
diff --git a/y2019/constants.cc b/y2019/constants.cc
index 10582ed..380899a 100644
--- a/y2019/constants.cc
+++ b/y2019/constants.cc
@@ -12,6 +12,8 @@
 
 #include "aos/network/team_number.h"
 #include "aos/stl_mutex/stl_mutex.h"
+#include "frc971/zeroing/absolute_encoder.h"
+#include "frc971/zeroing/pot_and_absolute_encoder.h"
 #include "y2019/control_loops/superstructure/elevator/integral_elevator_plant.h"
 #include "y2019/control_loops/superstructure/intake/integral_intake_plant.h"
 #include "y2019/control_loops/superstructure/stilts/integral_stilts_plant.h"
diff --git a/y2019/constants.h b/y2019/constants.h
index b6c1b55..a36e4b3 100644
--- a/y2019/constants.h
+++ b/y2019/constants.h
@@ -9,6 +9,8 @@
 #include "frc971/control_loops/drivetrain/camera.h"
 #include "frc971/control_loops/pose.h"
 #include "frc971/control_loops/static_zeroing_single_dof_profiled_subsystem.h"
+#include "frc971/zeroing/absolute_encoder.h"
+#include "frc971/zeroing/pot_and_absolute_encoder.h"
 #include "y2019/control_loops/drivetrain/drivetrain_dog_motor_plant.h"
 #include "y2019/control_loops/superstructure/elevator/elevator_plant.h"
 #include "y2019/control_loops/superstructure/intake/intake_plant.h"
diff --git a/y2019/control_loops/drivetrain/BUILD b/y2019/control_loops/drivetrain/BUILD
index 80e9fe6..2ac689b 100644
--- a/y2019/control_loops/drivetrain/BUILD
+++ b/y2019/control_loops/drivetrain/BUILD
@@ -188,14 +188,14 @@
     shard_count = 8,
     target_compatible_with = ["@platforms//os:linux"],
     deps = [
-        ":localizer",
         ":drivetrain_base",
+        ":localizer",
         "//aos/testing:googletest",
         "//aos/testing:random_seed",
         "//aos/testing:test_shm",
+        "//frc971/control_loops/drivetrain:splinedrivetrain",
         "//frc971/control_loops/drivetrain:trajectory",
         "//y2019:constants",
-        "//frc971/control_loops/drivetrain:splinedrivetrain",
         "@com_github_gflags_gflags//:gflags",
     ] + cpu_select({
         "amd64": [
diff --git a/y2019/control_loops/drivetrain/drivetrain_base.h b/y2019/control_loops/drivetrain/drivetrain_base.h
index 5cee1b5..3a8bd6b 100644
--- a/y2019/control_loops/drivetrain/drivetrain_base.h
+++ b/y2019/control_loops/drivetrain/drivetrain_base.h
@@ -7,8 +7,8 @@
 namespace control_loops {
 namespace drivetrain {
 
-const ::frc971::control_loops::drivetrain::DrivetrainConfig<double>
-    &GetDrivetrainConfig();
+const ::frc971::control_loops::drivetrain::DrivetrainConfig<double> &
+GetDrivetrainConfig();
 
 }  // namespace drivetrain
 }  // namespace control_loops
diff --git a/y2019/control_loops/superstructure/BUILD b/y2019/control_loops/superstructure/BUILD
index 9504c9f..f97c6f0 100644
--- a/y2019/control_loops/superstructure/BUILD
+++ b/y2019/control_loops/superstructure/BUILD
@@ -69,6 +69,8 @@
         "//aos/events:event_loop",
         "//frc971/control_loops:control_loop",
         "//frc971/control_loops/drivetrain:drivetrain_status_fbs",
+        "//frc971/zeroing:absolute_encoder",
+        "//frc971/zeroing:pot_and_absolute_encoder",
         "//y2019:constants",
         "//y2019:status_light_fbs",
     ],
diff --git a/y2019/control_loops/superstructure/superstructure.h b/y2019/control_loops/superstructure/superstructure.h
index f8ad0fd..4d59132 100644
--- a/y2019/control_loops/superstructure/superstructure.h
+++ b/y2019/control_loops/superstructure/superstructure.h
@@ -5,6 +5,8 @@
 #include "frc971/control_loops/control_loop.h"
 #include "frc971/control_loops/drivetrain/drivetrain_status_generated.h"
 #include "frc971/control_loops/static_zeroing_single_dof_profiled_subsystem.h"
+#include "frc971/zeroing/absolute_encoder.h"
+#include "frc971/zeroing/pot_and_absolute_encoder.h"
 #include "y2019/constants.h"
 #include "y2019/control_loops/superstructure/collision_avoidance.h"
 #include "y2019/control_loops/superstructure/superstructure_goal_generated.h"
diff --git a/y2020/BUILD b/y2020/BUILD
index 32d392a..7c04d99 100644
--- a/y2020/BUILD
+++ b/y2020/BUILD
@@ -77,6 +77,9 @@
         "//frc971:constants",
         "//frc971/control_loops:static_zeroing_single_dof_profiled_subsystem",
         "//frc971/shooter_interpolation:interpolation",
+        "//frc971/zeroing:absolute_and_absolute_encoder",
+        "//frc971/zeroing:absolute_encoder",
+        "//frc971/zeroing:pot_and_absolute_encoder",
         "//y2020/control_loops/drivetrain:polydrivetrain_plants",
         "//y2020/control_loops/superstructure/accelerator:accelerator_plants",
         "//y2020/control_loops/superstructure/control_panel:control_panel_plants",
diff --git a/y2020/constants.cc b/y2020/constants.cc
index 4d9d066..6218f9b 100644
--- a/y2020/constants.cc
+++ b/y2020/constants.cc
@@ -12,6 +12,9 @@
 
 #include "aos/network/team_number.h"
 #include "aos/stl_mutex/stl_mutex.h"
+#include "frc971/zeroing/absolute_and_absolute_encoder.h"
+#include "frc971/zeroing/absolute_encoder.h"
+#include "frc971/zeroing/pot_and_absolute_encoder.h"
 #include "y2020/control_loops/superstructure/control_panel/integral_control_panel_plant.h"
 #include "y2020/control_loops/superstructure/hood/integral_hood_plant.h"
 #include "y2020/control_loops/superstructure/intake/integral_intake_plant.h"
diff --git a/y2020/constants.h b/y2020/constants.h
index 83b2cec..7332b4f 100644
--- a/y2020/constants.h
+++ b/y2020/constants.h
@@ -8,6 +8,9 @@
 #include "frc971/constants.h"
 #include "frc971/control_loops/static_zeroing_single_dof_profiled_subsystem.h"
 #include "frc971/shooter_interpolation/interpolation.h"
+#include "frc971/zeroing/absolute_and_absolute_encoder.h"
+#include "frc971/zeroing/absolute_encoder.h"
+#include "frc971/zeroing/pot_and_absolute_encoder.h"
 #include "y2020/control_loops/drivetrain/drivetrain_dog_motor_plant.h"
 #include "y2020/control_loops/superstructure/accelerator/accelerator_plant.h"
 #include "y2020/control_loops/superstructure/control_panel/control_panel_plant.h"
diff --git a/y2020/control_loops/drivetrain/drivetrain_base.h b/y2020/control_loops/drivetrain/drivetrain_base.h
index c220088..e35c2af 100644
--- a/y2020/control_loops/drivetrain/drivetrain_base.h
+++ b/y2020/control_loops/drivetrain/drivetrain_base.h
@@ -7,8 +7,8 @@
 namespace control_loops {
 namespace drivetrain {
 
-const ::frc971::control_loops::drivetrain::DrivetrainConfig<double>
-    &GetDrivetrainConfig();
+const ::frc971::control_loops::drivetrain::DrivetrainConfig<double> &
+GetDrivetrainConfig();
 
 }  // namespace drivetrain
 }  // namespace control_loops
diff --git a/y2020/control_loops/superstructure/BUILD b/y2020/control_loops/superstructure/BUILD
index d1f20a2..83af834 100644
--- a/y2020/control_loops/superstructure/BUILD
+++ b/y2020/control_loops/superstructure/BUILD
@@ -83,6 +83,9 @@
         "//frc971/control_loops:control_loop",
         "//frc971/control_loops:control_loops_fbs",
         "//frc971/control_loops/drivetrain:drivetrain_status_fbs",
+        "//frc971/zeroing:absolute_and_absolute_encoder",
+        "//frc971/zeroing:absolute_encoder",
+        "//frc971/zeroing:pot_and_absolute_encoder",
         "//y2020:constants",
         "//y2020/control_loops/superstructure/hood:hood_encoder_zeroing_estimator",
         "//y2020/control_loops/superstructure/shooter",
diff --git a/y2020/control_loops/superstructure/hood/BUILD b/y2020/control_loops/superstructure/hood/BUILD
index 437c67a..7983e62 100644
--- a/y2020/control_loops/superstructure/hood/BUILD
+++ b/y2020/control_loops/superstructure/hood/BUILD
@@ -44,6 +44,7 @@
     target_compatible_with = ["@platforms//os:linux"],
     deps = [
         "//frc971/zeroing",
+        "//frc971/zeroing:absolute_and_absolute_encoder",
         "//y2020:constants",
     ],
 )
diff --git a/y2020/control_loops/superstructure/hood/hood_encoder_zeroing_estimator.cc b/y2020/control_loops/superstructure/hood/hood_encoder_zeroing_estimator.cc
index dee4461..249dd00 100644
--- a/y2020/control_loops/superstructure/hood/hood_encoder_zeroing_estimator.cc
+++ b/y2020/control_loops/superstructure/hood/hood_encoder_zeroing_estimator.cc
@@ -2,6 +2,8 @@
 
 #include <cmath>
 
+#include "frc971/zeroing/absolute_and_absolute_encoder.h"
+
 namespace y2020::control_loops::superstructure::hood {
 
 HoodEncoderZeroingEstimator::HoodEncoderZeroingEstimator(
diff --git a/y2020/control_loops/superstructure/superstructure.h b/y2020/control_loops/superstructure/superstructure.h
index 5d371fc..8a00bf0 100644
--- a/y2020/control_loops/superstructure/superstructure.h
+++ b/y2020/control_loops/superstructure/superstructure.h
@@ -5,6 +5,9 @@
 #include "frc971/control_loops/control_loop.h"
 #include "frc971/control_loops/drivetrain/drivetrain_status_generated.h"
 #include "frc971/input/joystick_state_generated.h"
+#include "frc971/zeroing/absolute_and_absolute_encoder.h"
+#include "frc971/zeroing/absolute_encoder.h"
+#include "frc971/zeroing/pot_and_absolute_encoder.h"
 #include "y2020/constants.h"
 #include "y2020/control_loops/superstructure/hood/hood_encoder_zeroing_estimator.h"
 #include "y2020/control_loops/superstructure/shooter/shooter.h"
diff --git a/y2020/vision/sift/fast_gaussian_halide_generator.sh b/y2020/vision/sift/fast_gaussian_halide_generator.sh
index 793e56c..cf094b8 100755
--- a/y2020/vision/sift/fast_gaussian_halide_generator.sh
+++ b/y2020/vision/sift/fast_gaussian_halide_generator.sh
@@ -43,12 +43,12 @@
   -isystem"${SYSROOT}/usr/include/c++/10" \
   -isystem"${SYSROOT}/usr/include/${MULTIARCH}/c++/10" \
   -isystem"${SYSROOT}/usr/include/c++/7/backward" \
-  -isystem"${LLVM_TOOLCHAIN}/lib/clang/13.0.0/include" \
+  -isystem"${LLVM_TOOLCHAIN}/lib/clang/16/include" \
   -isystem"${SYSROOT}/usr/include/${MULTIARCH}" \
   -isystem"${SYSROOT}/usr/include" \
   -isystem"${SYSROOT}/include" \
   "--sysroot=${SYSROOT}" \
-  -resource-dir "${LLVM_TOOLCHAIN}/lib/clang/13.0.0" \
+  -resource-dir "${LLVM_TOOLCHAIN}/lib/clang/16" \
   -target "${TARGET}" \
   -fuse-ld=lld \
   -L"${LLVM_TOOLCHAIN}/lib" \
diff --git a/y2021_bot3/control_loops/drivetrain/drivetrain_base.h b/y2021_bot3/control_loops/drivetrain/drivetrain_base.h
index 7250f93..f796d4e 100644
--- a/y2021_bot3/control_loops/drivetrain/drivetrain_base.h
+++ b/y2021_bot3/control_loops/drivetrain/drivetrain_base.h
@@ -7,8 +7,8 @@
 namespace control_loops {
 namespace drivetrain {
 
-const ::frc971::control_loops::drivetrain::DrivetrainConfig<double>
-    &GetDrivetrainConfig();
+const ::frc971::control_loops::drivetrain::DrivetrainConfig<double> &
+GetDrivetrainConfig();
 
 }  // namespace drivetrain
 }  // namespace control_loops
diff --git a/y2022/BUILD b/y2022/BUILD
index f25925e..8d9d901 100644
--- a/y2022/BUILD
+++ b/y2022/BUILD
@@ -224,6 +224,7 @@
         "//frc971/control_loops:static_zeroing_single_dof_profiled_subsystem",
         "//frc971/shooter_interpolation:interpolation",
         "//frc971/wpilib:wpilib_utils",
+        "//frc971/zeroing:pot_and_absolute_encoder",
         "//y2022/control_loops/drivetrain:polydrivetrain_plants",
         "//y2022/control_loops/superstructure/catapult:catapult_plants",
         "//y2022/control_loops/superstructure/climber:climber_plants",
diff --git a/y2022/constants.cc b/y2022/constants.cc
index ff1c738..8ba3367 100644
--- a/y2022/constants.cc
+++ b/y2022/constants.cc
@@ -13,6 +13,7 @@
 #include "aos/mutex/mutex.h"
 #include "aos/network/team_number.h"
 #include "frc971/wpilib/wpilib_utils.h"
+#include "frc971/zeroing/pot_and_absolute_encoder.h"
 #include "y2022/control_loops/superstructure/catapult/integral_catapult_plant.h"
 #include "y2022/control_loops/superstructure/climber/integral_climber_plant.h"
 #include "y2022/control_loops/superstructure/intake/integral_intake_plant.h"
diff --git a/y2022/constants.h b/y2022/constants.h
index 62b9b4a..f27b6cd 100644
--- a/y2022/constants.h
+++ b/y2022/constants.h
@@ -9,6 +9,7 @@
 #include "frc971/control_loops/pose.h"
 #include "frc971/control_loops/static_zeroing_single_dof_profiled_subsystem.h"
 #include "frc971/shooter_interpolation/interpolation.h"
+#include "frc971/zeroing/pot_and_absolute_encoder.h"
 #include "y2022/control_loops/drivetrain/drivetrain_dog_motor_plant.h"
 #include "y2022/control_loops/superstructure/catapult/catapult_plant.h"
 #include "y2022/control_loops/superstructure/climber/climber_plant.h"
diff --git a/y2022/control_loops/drivetrain/drivetrain_base.h b/y2022/control_loops/drivetrain/drivetrain_base.h
index 2983016..1f4cfe4 100644
--- a/y2022/control_loops/drivetrain/drivetrain_base.h
+++ b/y2022/control_loops/drivetrain/drivetrain_base.h
@@ -7,8 +7,8 @@
 namespace control_loops {
 namespace drivetrain {
 
-const ::frc971::control_loops::drivetrain::DrivetrainConfig<double>
-    &GetDrivetrainConfig();
+const ::frc971::control_loops::drivetrain::DrivetrainConfig<double> &
+GetDrivetrainConfig();
 
 }  // namespace drivetrain
 }  // namespace control_loops
diff --git a/y2022/control_loops/superstructure/BUILD b/y2022/control_loops/superstructure/BUILD
index e0db64c..e370f0e 100644
--- a/y2022/control_loops/superstructure/BUILD
+++ b/y2022/control_loops/superstructure/BUILD
@@ -86,6 +86,7 @@
         "//aos/events:event_loop",
         "//frc971/control_loops:control_loop",
         "//frc971/control_loops/drivetrain:drivetrain_status_fbs",
+        "//frc971/zeroing:pot_and_absolute_encoder",
         "//y2022:constants",
         "//y2022/control_loops/superstructure/catapult",
         "//y2022/control_loops/superstructure/turret:aiming",
diff --git a/y2022/control_loops/superstructure/catapult/BUILD b/y2022/control_loops/superstructure/catapult/BUILD
index a43925e..36e34bd 100644
--- a/y2022/control_loops/superstructure/catapult/BUILD
+++ b/y2022/control_loops/superstructure/catapult/BUILD
@@ -41,6 +41,7 @@
     deps = [
         ":catapult_plants",
         "//aos:realtime",
+        "//frc971/zeroing:pot_and_absolute_encoder",
         "//third_party/osqp-cpp",
         "//y2022:constants",
         "//y2022/control_loops/superstructure:superstructure_goal_fbs",
diff --git a/y2022/control_loops/superstructure/catapult/catapult.h b/y2022/control_loops/superstructure/catapult/catapult.h
index a4c82de..d30e8f5 100644
--- a/y2022/control_loops/superstructure/catapult/catapult.h
+++ b/y2022/control_loops/superstructure/catapult/catapult.h
@@ -5,6 +5,7 @@
 #include "glog/logging.h"
 
 #include "frc971/control_loops/state_feedback_loop.h"
+#include "frc971/zeroing/pot_and_absolute_encoder.h"
 #include "osqp++.h"
 #include "y2022/constants.h"
 #include "y2022/control_loops/superstructure/superstructure_goal_generated.h"
diff --git a/y2022/control_loops/superstructure/superstructure.h b/y2022/control_loops/superstructure/superstructure.h
index 5e3415c..36269e9 100644
--- a/y2022/control_loops/superstructure/superstructure.h
+++ b/y2022/control_loops/superstructure/superstructure.h
@@ -4,6 +4,7 @@
 #include "aos/events/event_loop.h"
 #include "frc971/control_loops/control_loop.h"
 #include "frc971/control_loops/drivetrain/drivetrain_status_generated.h"
+#include "frc971/zeroing/pot_and_absolute_encoder.h"
 #include "y2022/constants.h"
 #include "y2022/control_loops/superstructure/catapult/catapult.h"
 #include "y2022/control_loops/superstructure/collision_avoidance.h"
diff --git a/y2022_bot3/BUILD b/y2022_bot3/BUILD
index 29debe7..125ff44 100644
--- a/y2022_bot3/BUILD
+++ b/y2022_bot3/BUILD
@@ -116,6 +116,7 @@
         "//frc971/control_loops:pose",
         "//frc971/control_loops:static_zeroing_single_dof_profiled_subsystem",
         "//frc971/shooter_interpolation:interpolation",
+        "//frc971/zeroing:pot_and_absolute_encoder",
         "//y2022_bot3/control_loops/drivetrain:polydrivetrain_plants",
         "//y2022_bot3/control_loops/superstructure/climber:climber_plants",
         "//y2022_bot3/control_loops/superstructure/intake:intake_plants",
diff --git a/y2022_bot3/constants.cc b/y2022_bot3/constants.cc
index 7ec5187..82982aa 100644
--- a/y2022_bot3/constants.cc
+++ b/y2022_bot3/constants.cc
@@ -12,6 +12,7 @@
 
 #include "aos/mutex/mutex.h"
 #include "aos/network/team_number.h"
+#include "frc971/zeroing/pot_and_absolute_encoder.h"
 #include "y2022_bot3/control_loops/superstructure/climber/integral_climber_plant.h"
 #include "y2022_bot3/control_loops/superstructure/intake/integral_intake_plant.h"
 
diff --git a/y2022_bot3/constants.h b/y2022_bot3/constants.h
index 2ced38d..a87930f 100644
--- a/y2022_bot3/constants.h
+++ b/y2022_bot3/constants.h
@@ -9,6 +9,7 @@
 #include "frc971/control_loops/pose.h"
 #include "frc971/control_loops/static_zeroing_single_dof_profiled_subsystem.h"
 #include "frc971/shooter_interpolation/interpolation.h"
+#include "frc971/zeroing/pot_and_absolute_encoder.h"
 #include "y2022_bot3/control_loops/drivetrain/drivetrain_dog_motor_plant.h"
 #include "y2022_bot3/control_loops/superstructure/climber/climber_plant.h"
 #include "y2022_bot3/control_loops/superstructure/intake/intake_plant.h"
diff --git a/y2022_bot3/control_loops/drivetrain/drivetrain_base.h b/y2022_bot3/control_loops/drivetrain/drivetrain_base.h
index cce197e..04c6c86 100644
--- a/y2022_bot3/control_loops/drivetrain/drivetrain_base.h
+++ b/y2022_bot3/control_loops/drivetrain/drivetrain_base.h
@@ -7,8 +7,8 @@
 namespace control_loops {
 namespace drivetrain {
 
-const ::frc971::control_loops::drivetrain::DrivetrainConfig<double>
-    &GetDrivetrainConfig();
+const ::frc971::control_loops::drivetrain::DrivetrainConfig<double> &
+GetDrivetrainConfig();
 
 }  // namespace drivetrain
 }  // namespace control_loops
diff --git a/y2022_bot3/control_loops/superstructure/BUILD b/y2022_bot3/control_loops/superstructure/BUILD
index 7d89a0a..5b44e2a 100644
--- a/y2022_bot3/control_loops/superstructure/BUILD
+++ b/y2022_bot3/control_loops/superstructure/BUILD
@@ -75,6 +75,7 @@
         "//aos/events:event_loop",
         "//frc971/control_loops:control_loop",
         "//frc971/control_loops/drivetrain:drivetrain_status_fbs",
+        "//frc971/zeroing:pot_and_absolute_encoder",
         "//y2022_bot3:constants",
     ],
 )
diff --git a/y2022_bot3/control_loops/superstructure/superstructure.h b/y2022_bot3/control_loops/superstructure/superstructure.h
index 4f33c3c..13d5dec 100644
--- a/y2022_bot3/control_loops/superstructure/superstructure.h
+++ b/y2022_bot3/control_loops/superstructure/superstructure.h
@@ -4,6 +4,7 @@
 #include "aos/events/event_loop.h"
 #include "frc971/control_loops/control_loop.h"
 #include "frc971/control_loops/drivetrain/drivetrain_status_generated.h"
+#include "frc971/zeroing/pot_and_absolute_encoder.h"
 #include "y2022_bot3/constants.h"
 #include "y2022_bot3/control_loops/superstructure/superstructure_goal_generated.h"
 #include "y2022_bot3/control_loops/superstructure/superstructure_output_generated.h"
diff --git a/y2023/BUILD b/y2023/BUILD
index e200f87..d934927 100644
--- a/y2023/BUILD
+++ b/y2023/BUILD
@@ -254,6 +254,8 @@
         "//frc971/control_loops:pose",
         "//frc971/control_loops:static_zeroing_single_dof_profiled_subsystem",
         "//frc971/shooter_interpolation:interpolation",
+        "//frc971/zeroing:absolute_encoder",
+        "//frc971/zeroing:pot_and_absolute_encoder",
         "//y2023/control_loops/drivetrain:polydrivetrain_plants",
         "//y2023/control_loops/superstructure/arm:arm_constants",
         "//y2023/control_loops/superstructure/roll:roll_plants",
diff --git a/y2023/autonomous/autonomous_actor.cc b/y2023/autonomous/autonomous_actor.cc
index bedbab7..8891223 100644
--- a/y2023/autonomous/autonomous_actor.cc
+++ b/y2023/autonomous/autonomous_actor.cc
@@ -15,6 +15,7 @@
 DEFINE_bool(spline_auto, false, "Run simple test S-spline auto mode.");
 DEFINE_bool(charged_up, true, "If true run charged up autonomous mode");
 DEFINE_bool(charged_up_cable, false, "If true run cable side autonomous mode");
+DEFINE_bool(do_balance, true, "If true run the balance.");
 
 namespace y2023 {
 namespace autonomous {
@@ -364,6 +365,7 @@
   AOS_LOG(
       INFO, "Placed second cube %lf s\n",
       aos::time::DurationInSeconds(aos::monotonic_clock::now() - start_time));
+
   InitializeEncoders();
 
   const ProfileParametersT kDrive = MakeProfileParameters(2.0, 4.0);
@@ -381,6 +383,11 @@
         INFO, "Done backing up %lf s\n",
         aos::time::DurationInSeconds(aos::monotonic_clock::now() - start_time));
 
+    if (!FLAGS_do_balance) {
+      StopSpitting();
+      return;
+    }
+
     const ProfileParametersT kInPlaceTurn = MakeProfileParameters(2.7, 8.0);
     StartDrive(0.0, aos::math::NormalizeAngle(M_PI / 2.0 - Theta()), kDrive,
                kInPlaceTurn);
diff --git a/y2023/autonomous/splines/spline.1.json b/y2023/autonomous/splines/spline.1.json
index 5240ad8..ae80ebe 100644
--- a/y2023/autonomous/splines/spline.1.json
+++ b/y2023/autonomous/splines/spline.1.json
@@ -1 +1 @@
-{"spline_count": 1, "spline_x": [1.609310857796625, 2.5819120488556946, 3.506443404506549, 5.555694235956169, 5.989575501273337, 6.418880858416194], "spline_y": [0.6043502546533336, 0.69141924611354, 1.0213742193777775, 0.38712949808092717, 0.40845524312381665, 0.40845524312381665], "constraints": [{"constraint_type": "LONGITUDINAL_ACCELERATION", "value": 3.5}, {"constraint_type": "LATERAL_ACCELERATION", "value": 2}, {"constraint_type": "VOLTAGE", "value": 12.0}]}
\ No newline at end of file
+{"spline_count": 1, "spline_x": [1.609310857796625, 2.5819120488556946, 3.506443404506549, 5.571740737699863, 6.005622003017031, 6.434927360159888], "spline_y": [0.6043502546533336, 0.69141924611354, 1.0213742193777775, 0.47692519455739807, 0.49825093960028755, 0.49825093960028755], "constraints": [{"constraint_type": "LONGITUDINAL_ACCELERATION", "value": 3.5}, {"constraint_type": "LATERAL_ACCELERATION", "value": 2}, {"constraint_type": "VOLTAGE", "value": 12.0}]}
\ No newline at end of file
diff --git a/y2023/autonomous/splines/spline.2.json b/y2023/autonomous/splines/spline.2.json
index 297966c..4afc161 100644
--- a/y2023/autonomous/splines/spline.2.json
+++ b/y2023/autonomous/splines/spline.2.json
@@ -1 +1 @@
-{"spline_count": 1, "spline_x": [6.418880858416194, 6.02775087044969, 5.275605833281384, 2.8053451665928835, 2.37026593061867, 1.5260719060059573], "spline_y": [0.40845524312381665, 0.40845524312381665, 0.4647376584428905, 1.3287637699876067, -0.026954142728124464, -0.5547144640218522], "constraints": [{"constraint_type": "LONGITUDINAL_ACCELERATION", "value": 3.5}, {"constraint_type": "LATERAL_ACCELERATION", "value": 2.5}, {"constraint_type": "VOLTAGE", "value": 12.0}]}
\ No newline at end of file
+{"spline_count": 1, "spline_x": [6.434927360159888, 6.043797372193384, 5.291652335025078, 2.8053451665928835, 2.37026593061867, 1.5260719060059573], "spline_y": [0.49825093960028755, 0.49825093960028755, 0.5545333549193614, 1.3287637699876067, -0.026954142728124464, -0.5547144640218522], "constraints": [{"constraint_type": "LONGITUDINAL_ACCELERATION", "value": 3.5}, {"constraint_type": "LATERAL_ACCELERATION", "value": 2.5}, {"constraint_type": "VOLTAGE", "value": 12.0}]}
\ No newline at end of file
diff --git a/y2023/constants.cc b/y2023/constants.cc
index 5ab6407..56be337 100644
--- a/y2023/constants.cc
+++ b/y2023/constants.cc
@@ -84,31 +84,34 @@
       break;
 
     case kCompTeamNumber:
-      arm_proximal->zeroing.measured_absolute_position = 0.911194143585562;
+      arm_proximal->zeroing.measured_absolute_position = 0.911747959388894;
       arm_proximal->potentiometer_offset =
           10.5178592988554 + 0.0944609125285876 - 0.00826532984625095 +
-          0.167359305216504 + 0.135144500925909 - 0.214909475332252;
+          0.167359305216504 + 0.135144500925909 - 0.214909475332252 +
+          0.0377032255050543;
 
-      arm_distal->zeroing.measured_absolute_position = 0.295329750530428;
+      arm_distal->zeroing.measured_absolute_position = 0.294291930885304;
       arm_distal->potentiometer_offset =
           7.673132586937 - 0.0799284644472573 - 0.0323574039310657 +
           0.0143810684138064 + 0.00945555248207735 + 0.452446388633863 +
           0.0194863477007102 + 0.235993332670562 + 0.00138417783482921 -
-          1.29562640607084;
+          1.29562640607084 - 0.390356125757262 - 0.267002511437832 -
+          0.611626839639182 + 2.55745730136924 + 0.503121678457021 +
+          0.0440779746883177;
 
       arm_distal->zeroing.one_revolution_distance =
           M_PI * 2.0 * constants::Values::kDistalEncoderRatio() *
           (3.12725165289659 + 0.002) / 3.1485739705977704;
 
-      roll_joint->zeroing.measured_absolute_position = 1.79390317510529;
+      roll_joint->zeroing.measured_absolute_position = 1.82824749141201;
       roll_joint->potentiometer_offset =
           0.624713611895747 + 3.10458504917251 - 0.0966407797407789 +
           0.0257708772364788 - 0.0395076737853459 - 6.87914956118006 -
           0.097581301615046 + 3.3424421683095 - 3.97605190912604 +
-          0.709274294168941;
+          0.709274294168941 - 0.0817908884966825;
 
       wrist->subsystem_params.zeroing_constants.measured_absolute_position =
-          2.97717660361257;
+          0.744036527649413;
 
       break;
 
diff --git a/y2023/constants.h b/y2023/constants.h
index 32ed317..84d6499 100644
--- a/y2023/constants.h
+++ b/y2023/constants.h
@@ -8,6 +8,8 @@
 #include "frc971/constants.h"
 #include "frc971/control_loops/pose.h"
 #include "frc971/control_loops/static_zeroing_single_dof_profiled_subsystem.h"
+#include "frc971/zeroing/absolute_encoder.h"
+#include "frc971/zeroing/pot_and_absolute_encoder.h"
 #include "y2023/control_loops/drivetrain/drivetrain_dog_motor_plant.h"
 #include "y2023/control_loops/superstructure/arm/arm_constants.h"
 #include "y2023/control_loops/superstructure/roll/roll_plant.h"
diff --git a/y2023/constants/971.json b/y2023/constants/971.json
index 3c3ba38..fc21b74 100644
--- a/y2023/constants/971.json
+++ b/y2023/constants/971.json
@@ -4,13 +4,13 @@
       "calibration": {% include 'y2023/vision/calib_files/calibration_pi-971-1_cam-23-09_ext_2023-03-05.json' %}
     },
     {
-      "calibration": {% include 'y2023/vision/calib_files/calibration_pi-971-2_cam-23-10_ext_2023-04-15.json' %}
+      "calibration": {% include 'y2023/vision/calib_files/calibration_pi-971-2_cam-23-10_ext_2023-09-23.json' %}
     },
     {
-      "calibration": {% include 'y2023/vision/calib_files/calibration_pi-971-3_cam-23-11_ext_2023-04-15.json' %}
+      "calibration": {% include 'y2023/vision/calib_files/calibration_pi-971-3_cam-23-11_ext_2023-09-23.json' %}
     },
     {
-      "calibration": {% include 'y2023/vision/calib_files/calibration_pi-971-4_cam-23-12_ext_2023-04-15.json' %}
+      "calibration": {% include 'y2023/vision/calib_files/calibration_pi-971-4_cam-23-12_ext_2023-09-23.json' %}
     }
   ],
   "robot": {
diff --git a/y2023/control_loops/drivetrain/drivetrain_base.h b/y2023/control_loops/drivetrain/drivetrain_base.h
index 6404081..98f984e 100644
--- a/y2023/control_loops/drivetrain/drivetrain_base.h
+++ b/y2023/control_loops/drivetrain/drivetrain_base.h
@@ -7,8 +7,8 @@
 namespace control_loops {
 namespace drivetrain {
 
-const ::frc971::control_loops::drivetrain::DrivetrainConfig<double>
-    &GetDrivetrainConfig();
+const ::frc971::control_loops::drivetrain::DrivetrainConfig<double> &
+GetDrivetrainConfig();
 
 }  // namespace drivetrain
 }  // namespace control_loops
diff --git a/y2023/control_loops/python/graph_paths.py b/y2023/control_loops/python/graph_paths.py
index eb92ec8..52db9d8 100644
--- a/y2023/control_loops/python/graph_paths.py
+++ b/y2023/control_loops/python/graph_paths.py
@@ -231,7 +231,7 @@
     ))
 
 points['GroundPickupBackCube'] = to_theta_with_circular_index_and_roll(
-    -1.102, 0.28, -np.pi / 2.0, circular_index=1)
+    -1.102, 0.30, -np.pi / 2.0, circular_index=1)
 
 named_segments.append(
     ThetaSplineSegment(
@@ -246,7 +246,7 @@
     ))
 
 points['GroundPickupFrontCube'] = to_theta_with_circular_index_and_roll(
-    0.325603, 0.255189, np.pi / 2.0, circular_index=0)
+    0.325603, 0.275189, np.pi / 2.0, circular_index=0)
 
 named_segments.append(
     ThetaSplineSegment(
@@ -261,7 +261,7 @@
     ))
 
 points['ScoreBackMidConeUp'] = to_theta_with_circular_index_and_roll(
-    -1.45013, 1.00354, np.pi / 2.0, circular_index=1)
+    -1.45013, 1.02354, np.pi / 2.0, circular_index=1)
 
 named_segments.append(
     ThetaSplineSegment(
@@ -333,7 +333,7 @@
     ))
 
 points['HPPickupBackConeUp'] = to_theta_with_circular_index_and_roll(
-    -1.1200539, 1.335, np.pi / 2.0, circular_index=0)
+    -1.1200539, 1.330, np.pi / 2.0, circular_index=0)
 
 named_segments.append(
     ThetaSplineSegment(
@@ -346,7 +346,7 @@
     ))
 
 points['HPPickupFrontConeUp'] = np.array(
-    (5.16514378449353, 1.25, -np.pi / 2.0))
+    (5.16514378449353, 1.2461538461538462, -np.pi / 2.0))
 #        to_theta_with_circular_index_and_roll(
 #    0.265749, 1.28332, -np.pi / 2.0, circular_index=1)
 
@@ -476,7 +476,7 @@
     ))
 
 points['ScoreBackLowCube'] = to_theta_with_circular_index_and_roll(
-    -1.102, 0.3712121, -np.pi / 2.0, circular_index=1)
+    -1.102, 0.4012121, -np.pi / 2.0, circular_index=1)
 
 named_segments.append(
     ThetaSplineSegment(
@@ -489,7 +489,7 @@
     ))
 
 points['ScoreBackMidCube'] = to_theta_with_circular_index_and_roll(
-    -1.27896, 0.84, -np.pi / 2.0, circular_index=1)
+    -1.27896, 0.89, -np.pi / 2.0, circular_index=1)
 
 named_segments.append(
     ThetaSplineSegment(
@@ -515,7 +515,7 @@
 #points['ScoreBackHighCube'] = to_theta_with_circular_index_and_roll(
 #    -1.60932, 1.16839, np.pi / 2.0, circular_index=0)
 points['ScoreBackHighCube'] = np.array(
-    (4.77284735761704, -1.19952193130714, -np.pi / 2.0))
+    (4.77284735761704, -1.130291162076371, -np.pi / 2.0))
 
 named_segments.append(
     ThetaSplineSegment(
@@ -538,7 +538,7 @@
     ))
 
 points['GroundPickupFrontConeUp'] = to_theta_with_circular_index_and_roll(
-    0.313099, 0.380, -np.pi / 2.0, circular_index=0)
+    0.313099, 0.39, -np.pi / 2.0, circular_index=0)
 
 named_segments.append(
     ThetaSplineSegment(
diff --git a/y2023/control_loops/superstructure/BUILD b/y2023/control_loops/superstructure/BUILD
index 861bbf8..3d35dae 100644
--- a/y2023/control_loops/superstructure/BUILD
+++ b/y2023/control_loops/superstructure/BUILD
@@ -106,6 +106,8 @@
         "//frc971/control_loops/drivetrain:drivetrain_can_position_fbs",
         "//frc971/control_loops/drivetrain:drivetrain_status_fbs",
         "//frc971/shooter_interpolation:interpolation",
+        "//frc971/zeroing:absolute_encoder",
+        "//frc971/zeroing:pot_and_absolute_encoder",
         "//y2023:constants",
         "//y2023/constants:constants_fbs",
         "//y2023/constants:simulated_constants_sender",
diff --git a/y2023/control_loops/superstructure/arm/BUILD b/y2023/control_loops/superstructure/arm/BUILD
index a742c50..768a54e 100644
--- a/y2023/control_loops/superstructure/arm/BUILD
+++ b/y2023/control_loops/superstructure/arm/BUILD
@@ -16,6 +16,7 @@
         "//frc971/control_loops/double_jointed_arm:graph",
         "//frc971/control_loops/drivetrain:drivetrain_can_position_fbs",
         "//frc971/zeroing",
+        "//frc971/zeroing:pot_and_absolute_encoder",
         "//y2023:constants",
         "//y2023/control_loops/superstructure:superstructure_position_fbs",
         "//y2023/control_loops/superstructure:superstructure_status_fbs",
diff --git a/y2023/control_loops/superstructure/arm/arm.h b/y2023/control_loops/superstructure/arm/arm.h
index 1f97d80..6153400 100644
--- a/y2023/control_loops/superstructure/arm/arm.h
+++ b/y2023/control_loops/superstructure/arm/arm.h
@@ -5,6 +5,7 @@
 #include "frc971/control_loops/double_jointed_arm/dynamics.h"
 #include "frc971/control_loops/double_jointed_arm/ekf.h"
 #include "frc971/control_loops/double_jointed_arm/graph.h"
+#include "frc971/zeroing/pot_and_absolute_encoder.h"
 #include "frc971/zeroing/zeroing.h"
 #include "y2023/constants.h"
 #include "y2023/control_loops/superstructure/arm/generated_graph.h"
diff --git a/y2023/control_loops/superstructure/end_effector.cc b/y2023/control_loops/superstructure/end_effector.cc
index 8628359..43f3d24 100644
--- a/y2023/control_loops/superstructure/end_effector.cc
+++ b/y2023/control_loops/superstructure/end_effector.cc
@@ -36,6 +36,9 @@
         state_ = EndEffectorState::LOADED;
         break;
       case EndEffectorState::LOADED:
+        // In case we thought we had a cube, force it to cone.
+        game_piece_ = vision::Class::CONE_UP;
+        break;
       case EndEffectorState::SPITTING:
         break;
     }
diff --git a/y2023/control_loops/superstructure/superstructure.h b/y2023/control_loops/superstructure/superstructure.h
index fdfef4e..bcee3ea 100644
--- a/y2023/control_loops/superstructure/superstructure.h
+++ b/y2023/control_loops/superstructure/superstructure.h
@@ -7,6 +7,8 @@
 #include "frc971/control_loops/control_loop.h"
 #include "frc971/control_loops/drivetrain/drivetrain_can_position_generated.h"
 #include "frc971/control_loops/drivetrain/drivetrain_status_generated.h"
+#include "frc971/zeroing/absolute_encoder.h"
+#include "frc971/zeroing/pot_and_absolute_encoder.h"
 #include "y2023/constants.h"
 #include "y2023/constants/constants_generated.h"
 #include "y2023/control_loops/superstructure/arm/arm.h"
diff --git a/y2023/control_loops/superstructure/superstructure_lib_test.cc b/y2023/control_loops/superstructure/superstructure_lib_test.cc
index 26b88f3..926846e 100644
--- a/y2023/control_loops/superstructure/superstructure_lib_test.cc
+++ b/y2023/control_loops/superstructure/superstructure_lib_test.cc
@@ -9,6 +9,7 @@
 #include "frc971/control_loops/position_sensor_sim.h"
 #include "frc971/control_loops/subsystem_simulator.h"
 #include "frc971/control_loops/team_number_test_environment.h"
+#include "frc971/zeroing/absolute_encoder.h"
 #include "y2023/constants/simulated_constants_sender.h"
 #include "y2023/control_loops/drivetrain/drivetrain_dog_motor_plant.h"
 #include "y2023/control_loops/superstructure/roll/integral_roll_plant.h"
diff --git a/y2023/control_loops/superstructure/superstructure_plotter.ts b/y2023/control_loops/superstructure/superstructure_plotter.ts
index 2e32445..c7d14fd 100644
--- a/y2023/control_loops/superstructure/superstructure_plotter.ts
+++ b/y2023/control_loops/superstructure/superstructure_plotter.ts
@@ -11,15 +11,15 @@
 
 export function plotSuperstructure(conn: Connection, element: Element): void {
   const aosPlotter = new AosPlotter(conn);
-  const goal = aosPlotter.addMessageSource(
-      '/superstructure', 'y2023.control_loops.superstructure.Goal');
-  const output = aosPlotter.addMessageSource(
-      '/superstructure', 'y2023.control_loops.superstructure.Output');
-  const status = aosPlotter.addMessageSource(
-      '/superstructure', 'y2023.control_loops.superstructure.Status');
+  //const goal = aosPlotter.addMessageSource(
+  //    '/superstructure', 'y2023.control_loops.superstructure.Goal');
+  //const output = aosPlotter.addMessageSource(
+  //    '/superstructure', 'y2023.control_loops.superstructure.Output');
+  //const status = aosPlotter.addMessageSource(
+  //    '/superstructure', 'y2023.control_loops.superstructure.Status');
   const position = aosPlotter.addMessageSource(
       '/superstructure', 'y2023.control_loops.superstructure.Position');
-  const robotState = aosPlotter.addMessageSource('/aos', 'aos.RobotState');
+  //const robotState = aosPlotter.addMessageSource('/aos', 'aos.RobotState');
 
   const positionPlot =
       aosPlotter.addPlot(element, [DEFAULT_WIDTH, DEFAULT_HEIGHT / 2]);
@@ -28,88 +28,13 @@
   positionPlot.plot.getAxisLabels().setYLabel('wonky state units');
   positionPlot.plot.setDefaultYRange([-1.0, 2.0]);
 
-  positionPlot.addMessageLine(position, ['turret_beambreak'])
+  positionPlot.addMessageLine(position, ['arm', 'distal', 'pot'])
       .setColor(RED)
       .setPointSize(4.0);
-  positionPlot.addMessageLine(status, ['state'])
-      .setColor(PINK)
-      .setPointSize(4.0);
-  positionPlot.addMessageLine(status, ['flippers_open'])
-      .setColor(WHITE)
-      .setPointSize(1.0);
-  positionPlot.addMessageLine(status, ['reseating_in_catapult'])
-      .setColor(BLUE)
-      .setPointSize(1.0);
-  positionPlot.addMessageLine(status, ['fire'])
-      .setColor(BROWN)
-      .setPointSize(1.0);
-  positionPlot.addMessageLine(status, ['ready_to_fire'])
-      .setColor(GREEN)
-      .setPointSize(1.0);
-  positionPlot.addMessageLine(status, ['collided'])
-      .setColor(PINK)
-      .setPointSize(1.0);
-
-  const goalPlot =
-      aosPlotter.addPlot(element, [DEFAULT_WIDTH, DEFAULT_HEIGHT / 2]);
-  goalPlot.plot.getAxisLabels().setTitle('Goal');
-  goalPlot.plot.getAxisLabels().setXLabel(TIME);
-  goalPlot.plot.getAxisLabels().setYLabel('value');
-  goalPlot.plot.setDefaultYRange([-1.0, 2.0]);
-  goalPlot.addMessageLine(goal, ['fire']).setColor(RED).setPointSize(1.0);
-  goalPlot.addMessageLine(goal, ['auto_aim']).setColor(BLUE).setPointSize(1.0);
-
-
-  const shotCountPlot =
-      aosPlotter.addPlot(element, [DEFAULT_WIDTH, DEFAULT_HEIGHT / 2]);
-  shotCountPlot.plot.getAxisLabels().setTitle('Shot Count');
-  shotCountPlot.plot.getAxisLabels().setXLabel(TIME);
-  shotCountPlot.plot.getAxisLabels().setYLabel('balls');
-  shotCountPlot.plot.setDefaultYRange([-1.0, 2.0]);
-  shotCountPlot.addMessageLine(status, ['shot_count'])
-      .setColor(RED)
-      .setPointSize(1.0);
-
-  const intakePlot =
-      aosPlotter.addPlot(element, [DEFAULT_WIDTH, DEFAULT_HEIGHT / 2]);
-  intakePlot.plot.getAxisLabels().setTitle('Intake');
-  intakePlot.plot.getAxisLabels().setXLabel(TIME);
-  intakePlot.plot.getAxisLabels().setYLabel('wonky state units');
-  intakePlot.plot.setDefaultYRange([-1.0, 2.0]);
-  intakePlot.addMessageLine(status, ['intake_state'])
-      .setColor(RED)
-      .setPointSize(1.0);
-  intakePlot.addMessageLine(position, ['intake_beambreak_front'])
-      .setColor(GREEN)
-      .setPointSize(4.0);
-  intakePlot.addMessageLine(position, ['intake_beambreak_back'])
-      .setColor(PINK)
-      .setPointSize(1.0);
-  intakePlot.addMessageLine(output, ['transfer_roller_voltage'])
-      .setColor(BROWN)
-      .setPointSize(3.0);
-
-
-  const otherPlot =
-      aosPlotter.addPlot(element, [DEFAULT_WIDTH, DEFAULT_HEIGHT / 2]);
-  otherPlot.plot.getAxisLabels().setTitle('Position');
-  otherPlot.plot.getAxisLabels().setXLabel(TIME);
-  otherPlot.plot.getAxisLabels().setYLabel('rad');
-  otherPlot.plot.setDefaultYRange([-1.0, 2.0]);
-
-  otherPlot.addMessageLine(status, ['catapult', 'position'])
-      .setColor(PINK)
-      .setPointSize(4.0);
-  otherPlot.addMessageLine(status, ['turret', 'position'])
-      .setColor(WHITE)
-      .setPointSize(4.0);
-  otherPlot.addMessageLine(position, ['flipper_arm_left', 'encoder'])
+  positionPlot.addMessageLine(position, ['arm', 'distal', 'absolute_encoder'])
       .setColor(BLUE)
       .setPointSize(4.0);
-  otherPlot.addMessageLine(position, ['flipper_arm_right', 'encoder'])
-      .setColor(CYAN)
-      .setPointSize(4.0);
-  otherPlot.addMessageLine(output, ['flipper_arms_voltage'])
-      .setColor(BROWN)
+  positionPlot.addMessageLine(position, ['arm', 'distal', 'encoder'])
+      .setColor(GREEN)
       .setPointSize(4.0);
 }
diff --git a/y2023/copy_logs.sh b/y2023/copy_logs.sh
new file mode 100755
index 0000000..4d5669c
--- /dev/null
+++ b/y2023/copy_logs.sh
@@ -0,0 +1,29 @@
+#!/bin/bash
+
+# Helper script to copy most recent logs off of the pis
+
+set -e
+
+ROBOT_PREFIX="79" # ..71  (Should be one of 79, 89, 99, or 9)
+PI_LIST="2 3"     # Should be some set of {1,2,3,4,5,6}
+
+LOG_FILE_PATH=/media/sda1/fbs_log-current
+if [[ -z $1 || ! -d $1 ]]; then
+    echo "Please specify the base directory to store the logs ('$1' not found)"
+    exit -1
+fi
+
+# Create output directory based on given directory + a timestamp
+OUTPUT_DIR=$1"/"`date +"%Y-%m-%dT%H-%M-%S"`
+mkdir ${OUTPUT_DIR}
+
+echo "Copying logs from the robot ${ROBOT_PREFIX}71 and pis ${PI_LIST}"
+echo "Storing logs in folder ${OUTPUT_DIR}"
+
+for pi in $PI_LIST; do
+    echo "========================================================"
+    echo "Copying logs from pi-${ROBOT_PREFIX}71-$pi"
+    echo "========================================================"
+    scp -r pi@10.${ROBOT_PREFIX}.71.10${pi}:${LOG_FILE_PATH} ${OUTPUT_DIR}/fbs_log-pi${pi}
+done
+
diff --git a/y2023/joystick_reader.cc b/y2023/joystick_reader.cc
index 874e691..f4d9ea6 100644
--- a/y2023/joystick_reader.cc
+++ b/y2023/joystick_reader.cc
@@ -186,7 +186,7 @@
     },
     {
         .index = arm::ScoreBackMidConeUpIndex(),
-        .wrist_goal = kConeWrist,
+        .wrist_goal = kConeWrist + 0.05,
         .game_piece = GamePiece::CONE_UP,
         .buttons = {{kMidConeScoreRight, SpotSelectionHint::RIGHT},
                     {kMidConeScoreLeft, SpotSelectionHint::LEFT}},
diff --git a/y2023/pi_send_joystick.sh b/y2023/pi_send_joystick.sh
new file mode 100755
index 0000000..9b60e02
--- /dev/null
+++ b/y2023/pi_send_joystick.sh
@@ -0,0 +1,18 @@
+#!/usr/bin/sh
+
+# Helper script to spoof joystick state of robot enabled, to triggger image logging
+
+# Currently, this is going through pi6.  Need to set the right IP address for the bot
+imu_pi6="pi@10.79.71.106"
+
+# TODO(milind): add logger in the future
+echo "Sending Joystick command '$1' to $imu_pi6"
+ssh ${imu_pi6} "bin/aos_send /imu/aos aos.JoystickState '{\"enabled\": $1}'"
+
+if [ $1 = "false" ]
+then
+    # This extra sleep is necessary to make sure the logs rotate to a new file
+    sleep 6
+    echo "Sending Joystick command '$1' to $imu_pi6"
+    ssh ${imu_pi6} "bin/aos_send /imu/aos aos.JoystickState '{\"enabled\": $1}'"
+fi
diff --git a/y2023/vision/calib_files/calibration_pi-971-1_cam-23-09_ext_2023-03-05.json b/y2023/vision/calib_files/calibration_pi-971-1_cam-23-09_ext_2023-03-05.json
index ad8ab23..051897b 100644
--- a/y2023/vision/calib_files/calibration_pi-971-1_cam-23-09_ext_2023-03-05.json
+++ b/y2023/vision/calib_files/calibration_pi-971-1_cam-23-09_ext_2023-03-05.json
@@ -1 +1 @@
-{ "node_name": "pi1", "team_number": 971, "intrinsics": [ 893.617798, 0.0, 612.44397, 0.0, 893.193115, 375.196381, 0.0, 0.0, 1.0 ], "fixed_extrinsics": { "data": [ -0.483961, 0.220781, 0.84678, 0.176109, 0.868846, 0.005849, 0.495048, -0.191149, 0.104344, 0.975306, -0.194656, 0.550508, 0.0, 0.0, 0.0, 1.0 ] }, "dist_coeffs": [ -0.443805, 0.238734, 0.000133, 0.000448, -0.071068 ], "calibration_timestamp": 1358499779650270322, "camera_id": "23-09" }
+{ "node_name": "pi1", "team_number": 971, "intrinsics": [ 893.617798, 0.0, 612.44397, 0.0, 893.193115, 375.196381, 0.0, 0.0, 1.0 ], "fixed_extrinsics": { "data": [ -0.483961, 0.220781, 0.84678, 0.176109, 0.868846, 0.005849, 0.495048, -0.231149, 0.104344, 0.975306, -0.194656, 0.550508, 0.0, 0.0, 0.0, 1.0 ] }, "dist_coeffs": [ -0.443805, 0.238734, 0.000133, 0.000448, -0.071068 ], "calibration_timestamp": 1358499779650270322, "camera_id": "23-09" }
diff --git a/y2023/vision/calib_files/calibration_pi-971-2_cam-23-10_ext_2023-09-23.json b/y2023/vision/calib_files/calibration_pi-971-2_cam-23-10_ext_2023-09-23.json
new file mode 100644
index 0000000..f353533
--- /dev/null
+++ b/y2023/vision/calib_files/calibration_pi-971-2_cam-23-10_ext_2023-09-23.json
@@ -0,0 +1 @@
+{ "node_name": "pi2", "team_number": 971, "intrinsics": [ 894.002502, 0.0, 636.431335, 0.0, 893.723816, 377.069672, 0.0, 0.0, 1.0 ], "fixed_extrinsics": { "data": [ 0.84372, 0.233067, 0.483545, 0.188786, 0.499721,-0.0121191, -0.866102, -0.244606, -0.196001, 0.972385, -0.126693, 0.600451, 0.0, 0.0, 0.0, 1.0 ] }, "dist_coeffs": [ -0.446659, 0.244189, 0.000632, 0.000171, -0.074849 ], "calibration_timestamp": 1358503360377380613, "camera_id": "23-10" }
diff --git a/y2023/vision/calib_files/calibration_pi-971-3_cam-23-11_ext_2023-09-23.json b/y2023/vision/calib_files/calibration_pi-971-3_cam-23-11_ext_2023-09-23.json
new file mode 100644
index 0000000..d7851fa
--- /dev/null
+++ b/y2023/vision/calib_files/calibration_pi-971-3_cam-23-11_ext_2023-09-23.json
@@ -0,0 +1 @@
+{ "node_name": "pi3", "team_number": 971, "intrinsics": [ 891.026001, 0.0, 620.086731, 0.0, 890.566895, 385.035126, 0.0, 0.0, 1.0 ], "fixed_extrinsics": { "data": [   0.484157, -0.154776, -0.861182, -0.0935153, -0.872577, -0.0125032, -0.488317, -0.225918, 0.0648126, 0.987871, -0.141107, 0.623926, 0.0, 0.0, 0.0, 1.0 ] }, "dist_coeffs": [ -0.448299, 0.250123, -0.00042, -0.000127, -0.078433 ], "calibration_timestamp": 1358503290177115986, "camera_id": "23-11" }
diff --git a/y2023/vision/calib_files/calibration_pi-971-4_cam-23-12_ext_2023-09-23.json b/y2023/vision/calib_files/calibration_pi-971-4_cam-23-12_ext_2023-09-23.json
new file mode 100644
index 0000000..e12c22c
--- /dev/null
+++ b/y2023/vision/calib_files/calibration_pi-971-4_cam-23-12_ext_2023-09-23.json
@@ -0,0 +1 @@
+{ "node_name": "pi4", "team_number": 971, "intrinsics": [ 891.127197, 0.0, 640.291321, 0.0, 891.176453, 359.578705, 0.0, 0.0, 1.0 ], "fixed_extrinsics": { "data": [ -0.864114, -0.173035,  -0.472616,  -0.111428, -0.477471, -0.0150962, 0.878518, -0.22396, -0.159149, 0.9848, -0.0695749, 0.696406, 0.0, 0.0, 0.0, 1.0 ] }, "dist_coeffs": [ -0.452948, 0.262567, 0.00088, -0.000253, -0.089368 ], "calibration_timestamp": 1358499579812698894, "camera_id": "23-12" }
diff --git a/y2023_bot3/BUILD b/y2023_bot3/BUILD
new file mode 100644
index 0000000..6025e3f
--- /dev/null
+++ b/y2023_bot3/BUILD
@@ -0,0 +1,240 @@
+load("//frc971:downloader.bzl", "robot_downloader")
+load("//aos:config.bzl", "aos_config")
+load("//aos/util:config_validator_macro.bzl", "config_validator_test")
+
+config_validator_test(
+    name = "config_validator_test",
+    config = "//y2023_bot3:aos_config",
+)
+
+robot_downloader(
+    binaries = [
+        "//aos/network:web_proxy_main",
+        "//aos/events/logging:log_cat",
+        "//y2023_bot3/constants:constants_sender",
+        "//aos/events:aos_timing_report_streamer",
+    ],
+    data = [
+        ":aos_config",
+        "//aos/starter:roborio_irq_config.json",
+        "@ctre_phoenix6_api_cpp_athena//:shared_libraries",
+        "@ctre_phoenix6_tools_athena//:shared_libraries",
+        "@ctre_phoenix_api_cpp_athena//:shared_libraries",
+        "@ctre_phoenix_cci_athena//:shared_libraries",
+    ],
+    dirs = [
+        "//y2023_bot3/www:www_files",
+        "//y2023_bot3/autonomous:splines",
+    ],
+    start_binaries = [
+        "//aos/events/logging:logger_main",
+        "//aos/network:web_proxy_main",
+        "//aos/starter:irq_affinity",
+        "//y2023_bot3/autonomous:binaries",
+        ":joystick_reader",
+        ":wpilib_interface",
+        "//frc971/can_logger",
+        "//aos/network:message_bridge_client",
+        "//aos/network:message_bridge_server",
+        "//y2023_bot3/control_loops/drivetrain:drivetrain",
+        "//y2023_bot3/control_loops/drivetrain:trajectory_generator",
+        "//y2023_bot3/control_loops/superstructure:superstructure",
+    ],
+    target_compatible_with = ["@platforms//os:linux"],
+)
+
+robot_downloader(
+    name = "pi_download",
+    binaries = [
+        "//aos/starter:irq_affinity",
+        "//aos/util:foxglove_websocket",
+        "//aos/events:aos_timing_report_streamer",
+        "//y2023_bot3/constants:constants_sender",
+        "//aos/network:web_proxy_main",
+        "//aos/events/logging:log_cat",
+        "//y2023_bot3/rockpi:imu_main",
+        "//frc971/image_streamer:image_streamer",
+    ],
+    data = [
+        ":aos_config",
+        "//frc971/rockpi:rockpi_config.json",
+        "//y2023_bot3/constants:constants.json",
+        "//y2023_bot3/www:www_files",
+    ],
+    dirs = [
+        "//y2023_bot3/www:www_files",
+        "//frc971/image_streamer/www:www_files",
+    ],
+    start_binaries = [
+        "//aos/network:message_bridge_client",
+        "//aos/network:message_bridge_server",
+        "//aos/network:web_proxy_main",
+        "//aos/starter:irq_affinity",
+        "//aos/events/logging:logger_main",
+    ],
+    target_compatible_with = ["//tools/platforms/hardware:raspberry_pi"],
+    target_type = "pi",
+)
+
+aos_config(
+    name = "aos_config",
+    src = "y2023_bot3.json",
+    flatbuffers = [
+        "//aos/network:message_bridge_client_fbs",
+        "//aos/network:message_bridge_server_fbs",
+        "//aos/network:timestamp_fbs",
+        "//frc971/input:robot_state_fbs",
+    ],
+    target_compatible_with = ["@platforms//os:linux"],
+    visibility = ["//visibility:public"],
+    deps = [
+        ":config_imu",
+        ":config_roborio",
+    ],
+)
+
+aos_config(
+    name = "config_imu",
+    src = "y2023_bot3_imu.json",
+    flatbuffers = [
+        "//aos/network:message_bridge_client_fbs",
+        "//aos/network:message_bridge_server_fbs",
+        "//y2023_bot3/constants:constants_fbs",
+        "//aos/network:timestamp_fbs",
+        "//aos/network:remote_message_fbs",
+        "//frc971/control_loops/drivetrain/localization:localizer_output_fbs",
+    ],
+    target_compatible_with = ["@platforms//os:linux"],
+    visibility = ["//visibility:public"],
+    deps = [
+        "//aos/events:aos_config",
+        "//frc971/control_loops/drivetrain:aos_config",
+    ],
+)
+
+aos_config(
+    name = "config_roborio",
+    src = "y2023_bot3_roborio.json",
+    flatbuffers = [
+        "//aos/network:remote_message_fbs",
+        "//aos/network:message_bridge_client_fbs",
+        "//aos/network:message_bridge_server_fbs",
+        "//y2023_bot3/constants:constants_fbs",
+        "//aos/network:timestamp_fbs",
+        "//y2019/control_loops/drivetrain:target_selector_fbs",
+        "//y2023_bot3/control_loops/superstructure:superstructure_goal_fbs",
+        "//y2023_bot3/control_loops/superstructure:superstructure_output_fbs",
+        "//y2023_bot3/control_loops/superstructure:superstructure_position_fbs",
+        "//y2023_bot3/control_loops/superstructure:superstructure_status_fbs",
+        "//frc971/control_loops/drivetrain:drivetrain_can_position_fbs",
+        "//frc971:can_configuration_fbs",
+        "//frc971/can_logger:can_logging_fbs",
+    ],
+    target_compatible_with = ["@platforms//os:linux"],
+    deps = [
+        "//aos/events:aos_config",
+        "//frc971/autonomous:aos_config",
+        "//frc971/control_loops/drivetrain:aos_config",
+        "//frc971/input:aos_config",
+        "//frc971/wpilib:aos_config",
+    ],
+)
+
+cc_library(
+    name = "constants",
+    srcs = [
+        "constants.cc",
+    ],
+    hdrs = [
+        "constants.h",
+    ],
+    visibility = ["//visibility:public"],
+    deps = [
+        "//aos/mutex",
+        "//aos/network:team_number",
+        "//frc971:constants",
+        "//frc971/control_loops:pose",
+        "//frc971/control_loops:static_zeroing_single_dof_profiled_subsystem",
+        "//frc971/shooter_interpolation:interpolation",
+        "//y2023_bot3/control_loops/drivetrain:polydrivetrain_plants",
+        "@com_github_google_glog//:glog",
+        "@com_google_absl//absl/base",
+    ],
+)
+
+cc_binary(
+    name = "wpilib_interface",
+    srcs = [
+        "wpilib_interface.cc",
+    ],
+    target_compatible_with = ["//tools/platforms/hardware:roborio"],
+    deps = [
+        ":constants",
+        "//aos:init",
+        "//aos:math",
+        "//aos/containers:sized_array",
+        "//aos/events:shm_event_loop",
+        "//aos/logging",
+        "//aos/stl_mutex",
+        "//aos/time",
+        "//aos/util:log_interval",
+        "//aos/util:phased_loop",
+        "//aos/util:wrapping_counter",
+        "//frc971:can_configuration_fbs",
+        "//frc971/autonomous:auto_mode_fbs",
+        "//frc971/control_loops:control_loop",
+        "//frc971/control_loops:control_loops_fbs",
+        "//frc971/control_loops/drivetrain:drivetrain_can_position_fbs",
+        "//frc971/control_loops/drivetrain:drivetrain_position_fbs",
+        "//frc971/input:robot_state_fbs",
+        "//frc971/queues:gyro_fbs",
+        "//frc971/wpilib:ADIS16448",
+        "//frc971/wpilib:buffered_pcm",
+        "//frc971/wpilib:drivetrain_writer",
+        "//frc971/wpilib:encoder_and_potentiometer",
+        "//frc971/wpilib:interrupt_edge_counting",
+        "//frc971/wpilib:joystick_sender",
+        "//frc971/wpilib:logging_fbs",
+        "//frc971/wpilib:loop_output_handler",
+        "//frc971/wpilib:pdp_fetcher",
+        "//frc971/wpilib:sensor_reader",
+        "//frc971/wpilib:wpilib_interface",
+        "//frc971/wpilib:wpilib_robot_base",
+        "//third_party:phoenix",
+        "//third_party:phoenix6",
+        "//third_party:wpilib",
+        "//y2023_bot3/control_loops/superstructure:led_indicator_lib",
+        "//y2023_bot3/control_loops/superstructure:superstructure_output_fbs",
+        "//y2023_bot3/control_loops/superstructure:superstructure_position_fbs",
+    ],
+)
+
+cc_binary(
+    name = "joystick_reader",
+    srcs = [
+        ":joystick_reader.cc",
+    ],
+    deps = [
+        ":constants",
+        "//aos:init",
+        "//aos/actions:action_lib",
+        "//aos/logging",
+        "//frc971/autonomous:auto_fbs",
+        "//frc971/autonomous:base_autonomous_actor",
+        "//frc971/control_loops:profiled_subsystem_fbs",
+        "//frc971/input:action_joystick_input",
+        "//frc971/input:drivetrain_input",
+        "//frc971/input:joystick_input",
+        "//frc971/input:redundant_joystick_data",
+        "//y2023_bot3/control_loops/drivetrain:drivetrain_base",
+        "//y2023_bot3/control_loops/superstructure:superstructure_goal_fbs",
+        "//y2023_bot3/control_loops/superstructure:superstructure_status_fbs",
+    ],
+)
+
+py_library(
+    name = "python_init",
+    srcs = ["__init__.py"],
+    target_compatible_with = ["@platforms//os:linux"],
+    visibility = ["//visibility:public"],
+)
diff --git a/y2023_bot3/__init__.py b/y2023_bot3/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/y2023_bot3/__init__.py
diff --git a/y2023_bot3/autonomous/BUILD b/y2023_bot3/autonomous/BUILD
new file mode 100644
index 0000000..940d591
--- /dev/null
+++ b/y2023_bot3/autonomous/BUILD
@@ -0,0 +1,74 @@
+load("//frc971/downloader:downloader.bzl", "aos_downloader_dir")
+
+filegroup(
+    name = "binaries.stripped",
+    srcs = [
+        ":autonomous_action.stripped",
+    ],
+    visibility = ["//visibility:public"],
+)
+
+filegroup(
+    name = "binaries",
+    srcs = [
+        ":autonomous_action",
+    ],
+    visibility = ["//visibility:public"],
+)
+
+filegroup(
+    name = "spline_jsons",
+    srcs = glob([
+        "splines/*.json",
+    ]),
+    visibility = ["//visibility:public"],
+)
+
+aos_downloader_dir(
+    name = "splines",
+    srcs = [
+        ":spline_jsons",
+    ],
+    dir = "splines",
+    target_compatible_with = ["@platforms//os:linux"],
+    visibility = ["//visibility:public"],
+)
+
+cc_library(
+    name = "autonomous_action_lib",
+    srcs = [
+        "auto_splines.cc",
+        "autonomous_actor.cc",
+    ],
+    hdrs = [
+        "auto_splines.h",
+        "autonomous_actor.h",
+    ],
+    deps = [
+        "//aos/events:event_loop",
+        "//aos/logging",
+        "//aos/util:phased_loop",
+        "//frc971/autonomous:base_autonomous_actor",
+        "//frc971/control_loops:control_loops_fbs",
+        "//frc971/control_loops:profiled_subsystem_fbs",
+        "//frc971/control_loops/drivetrain:drivetrain_config",
+        "//frc971/control_loops/drivetrain:localizer_fbs",
+        "//y2023_bot3:constants",
+        "//y2023_bot3/control_loops/drivetrain:drivetrain_base",
+        "//y2023_bot3/control_loops/superstructure:superstructure_goal_fbs",
+        "//y2023_bot3/control_loops/superstructure:superstructure_status_fbs",
+    ],
+)
+
+cc_binary(
+    name = "autonomous_action",
+    srcs = [
+        "autonomous_actor_main.cc",
+    ],
+    deps = [
+        ":autonomous_action_lib",
+        "//aos:init",
+        "//aos/events:shm_event_loop",
+        "//frc971/autonomous:auto_fbs",
+    ],
+)
diff --git a/y2023_bot3/autonomous/auto_splines.cc b/y2023_bot3/autonomous/auto_splines.cc
new file mode 100644
index 0000000..e2779c4
--- /dev/null
+++ b/y2023_bot3/autonomous/auto_splines.cc
@@ -0,0 +1,126 @@
+#include "y2023_bot3/autonomous/auto_splines.h"
+
+#include "aos/flatbuffer_merge.h"
+#include "frc971/control_loops/control_loops_generated.h"
+
+namespace y2023_bot3 {
+namespace autonomous {
+
+namespace {
+flatbuffers::Offset<frc971::MultiSpline> FixSpline(
+    aos::Sender<frc971::control_loops::drivetrain::SplineGoal>::Builder
+        *builder,
+    flatbuffers::Offset<frc971::MultiSpline> spline_offset,
+    aos::Alliance alliance) {
+  frc971::MultiSpline *spline =
+      GetMutableTemporaryPointer(*builder->fbb(), spline_offset);
+  flatbuffers::Vector<float> *spline_x = spline->mutable_spline_x();
+
+  // For 2023: The field is mirrored across the center line, and is not
+  // rotationally symmetric. As such, we only flip the X coordinates when
+  // changing side of the field.
+  if (alliance == aos::Alliance::kBlue) {
+    for (size_t ii = 0; ii < spline_x->size(); ++ii) {
+      spline_x->Mutate(ii, -spline_x->Get(ii));
+    }
+  }
+  return spline_offset;
+}
+}  // namespace
+
+flatbuffers::Offset<frc971::MultiSpline> AutonomousSplines::BasicSSpline(
+    aos::Sender<frc971::control_loops::drivetrain::SplineGoal>::Builder
+        *builder,
+    aos::Alliance alliance) {
+  flatbuffers::Offset<frc971::Constraint> longitudinal_constraint_offset;
+  flatbuffers::Offset<frc971::Constraint> lateral_constraint_offset;
+  flatbuffers::Offset<frc971::Constraint> voltage_constraint_offset;
+
+  {
+    frc971::Constraint::Builder longitudinal_constraint_builder =
+        builder->MakeBuilder<frc971::Constraint>();
+    longitudinal_constraint_builder.add_constraint_type(
+        frc971::ConstraintType::LONGITUDINAL_ACCELERATION);
+    longitudinal_constraint_builder.add_value(1.0);
+    longitudinal_constraint_offset = longitudinal_constraint_builder.Finish();
+  }
+
+  {
+    frc971::Constraint::Builder lateral_constraint_builder =
+        builder->MakeBuilder<frc971::Constraint>();
+    lateral_constraint_builder.add_constraint_type(
+        frc971::ConstraintType::LATERAL_ACCELERATION);
+    lateral_constraint_builder.add_value(1.0);
+    lateral_constraint_offset = lateral_constraint_builder.Finish();
+  }
+
+  {
+    frc971::Constraint::Builder voltage_constraint_builder =
+        builder->MakeBuilder<frc971::Constraint>();
+    voltage_constraint_builder.add_constraint_type(
+        frc971::ConstraintType::VOLTAGE);
+    voltage_constraint_builder.add_value(6.0);
+    voltage_constraint_offset = voltage_constraint_builder.Finish();
+  }
+
+  flatbuffers::Offset<
+      flatbuffers::Vector<flatbuffers::Offset<frc971::Constraint>>>
+      constraints_offset =
+          builder->fbb()->CreateVector<flatbuffers::Offset<frc971::Constraint>>(
+              {longitudinal_constraint_offset, lateral_constraint_offset,
+               voltage_constraint_offset});
+
+  const float startx = 0.4;
+  const float starty = 3.4;
+  flatbuffers::Offset<flatbuffers::Vector<float>> spline_x_offset =
+      builder->fbb()->CreateVector<float>({0.0f + startx, 0.6f + startx,
+                                           0.6f + startx, 0.4f + startx,
+                                           0.4f + startx, 1.0f + startx});
+  flatbuffers::Offset<flatbuffers::Vector<float>> spline_y_offset =
+      builder->fbb()->CreateVector<float>({starty - 0.0f, starty - 0.0f,
+                                           starty - 0.3f, starty - 0.7f,
+                                           starty - 1.0f, starty - 1.0f});
+
+  frc971::MultiSpline::Builder multispline_builder =
+      builder->MakeBuilder<frc971::MultiSpline>();
+
+  multispline_builder.add_spline_count(1);
+  multispline_builder.add_constraints(constraints_offset);
+  multispline_builder.add_spline_x(spline_x_offset);
+  multispline_builder.add_spline_y(spline_y_offset);
+
+  return FixSpline(builder, multispline_builder.Finish(), alliance);
+}
+
+flatbuffers::Offset<frc971::MultiSpline> AutonomousSplines::TestSpline(
+    aos::Sender<frc971::control_loops::drivetrain::SplineGoal>::Builder
+        *builder,
+    aos::Alliance alliance) {
+  return FixSpline(
+      builder,
+      aos::CopyFlatBuffer<frc971::MultiSpline>(test_spline_, builder->fbb()),
+      alliance);
+}
+
+flatbuffers::Offset<frc971::MultiSpline> AutonomousSplines::StraightLine(
+    aos::Sender<frc971::control_loops::drivetrain::SplineGoal>::Builder
+        *builder,
+    aos::Alliance alliance) {
+  flatbuffers::Offset<flatbuffers::Vector<float>> spline_x_offset =
+      builder->fbb()->CreateVector<float>(
+          {-12.3, -11.9, -11.5, -11.1, -10.6, -10.0});
+  flatbuffers::Offset<flatbuffers::Vector<float>> spline_y_offset =
+      builder->fbb()->CreateVector<float>({1.25, 1.25, 1.25, 1.25, 1.25, 1.25});
+
+  frc971::MultiSpline::Builder multispline_builder =
+      builder->MakeBuilder<frc971::MultiSpline>();
+
+  multispline_builder.add_spline_count(1);
+  multispline_builder.add_spline_x(spline_x_offset);
+  multispline_builder.add_spline_y(spline_y_offset);
+
+  return FixSpline(builder, multispline_builder.Finish(), alliance);
+}
+
+}  // namespace autonomous
+}  // namespace y2023_bot3
diff --git a/y2023_bot3/autonomous/auto_splines.h b/y2023_bot3/autonomous/auto_splines.h
new file mode 100644
index 0000000..0c9d92f
--- /dev/null
+++ b/y2023_bot3/autonomous/auto_splines.h
@@ -0,0 +1,45 @@
+#ifndef Y2023_AUTONOMOUS_AUTO_SPLINES_H_
+#define Y2023_AUTONOMOUS_AUTO_SPLINES_H_
+
+#include "aos/events/event_loop.h"
+#include "aos/flatbuffer_merge.h"
+#include "frc971/control_loops/control_loops_generated.h"
+#include "frc971/control_loops/drivetrain/drivetrain_goal_generated.h"
+#include "frc971/input/joystick_state_generated.h"
+/*
+
+  The cooridinate system for the autonomous splines is the same as the spline
+  python generator and drivetrain spline systems.
+
+*/
+
+namespace y2023_bot3 {
+namespace autonomous {
+
+class AutonomousSplines {
+ public:
+  AutonomousSplines()
+      : test_spline_(aos::JsonFileToFlatbuffer<frc971::MultiSpline>(
+            "splines/test_spline.json")) {}
+  static flatbuffers::Offset<frc971::MultiSpline> BasicSSpline(
+      aos::Sender<frc971::control_loops::drivetrain::SplineGoal>::Builder
+          *builder,
+      aos::Alliance alliance);
+  static flatbuffers::Offset<frc971::MultiSpline> StraightLine(
+      aos::Sender<frc971::control_loops::drivetrain::SplineGoal>::Builder
+          *builder,
+      aos::Alliance alliance);
+
+  flatbuffers::Offset<frc971::MultiSpline> TestSpline(
+      aos::Sender<frc971::control_loops::drivetrain::SplineGoal>::Builder
+          *builder,
+      aos::Alliance alliance);
+
+ private:
+  aos::FlatbufferDetachedBuffer<frc971::MultiSpline> test_spline_;
+};
+
+}  // namespace autonomous
+}  // namespace y2023_bot3
+
+#endif  // Y2023_AUTONOMOUS_AUTO_SPLINES_H_
diff --git a/y2023_bot3/autonomous/autonomous_actor.cc b/y2023_bot3/autonomous/autonomous_actor.cc
new file mode 100644
index 0000000..0aacbd1
--- /dev/null
+++ b/y2023_bot3/autonomous/autonomous_actor.cc
@@ -0,0 +1,198 @@
+#include "y2023_bot3/autonomous/autonomous_actor.h"
+
+#include <chrono>
+#include <cinttypes>
+#include <cmath>
+
+#include "aos/logging/logging.h"
+#include "aos/util/math.h"
+#include "frc971/control_loops/drivetrain/localizer_generated.h"
+#include "y2023_bot3/autonomous/auto_splines.h"
+#include "y2023_bot3/constants.h"
+#include "y2023_bot3/control_loops/drivetrain/drivetrain_base.h"
+
+DEFINE_bool(spline_auto, false, "Run simple test S-spline auto mode.");
+
+namespace y2023_bot3 {
+namespace autonomous {
+
+using ::frc971::ProfileParametersT;
+
+ProfileParametersT MakeProfileParameters(float max_velocity,
+                                         float max_acceleration) {
+  ProfileParametersT result;
+  result.max_velocity = max_velocity;
+  result.max_acceleration = max_acceleration;
+  return result;
+}
+
+using ::aos::monotonic_clock;
+using frc971::CreateProfileParameters;
+using ::frc971::ProfileParametersT;
+using frc971::control_loops::CreateStaticZeroingSingleDOFProfiledSubsystemGoal;
+using frc971::control_loops::StaticZeroingSingleDOFProfiledSubsystemGoal;
+using frc971::control_loops::drivetrain::LocalizerControl;
+namespace chrono = ::std::chrono;
+
+AutonomousActor::AutonomousActor(::aos::EventLoop *event_loop)
+    : frc971::autonomous::BaseAutonomousActor(
+          event_loop, control_loops::drivetrain::GetDrivetrainConfig()),
+      localizer_control_sender_(
+          event_loop->MakeSender<
+              ::frc971::control_loops::drivetrain::LocalizerControl>(
+              "/drivetrain")),
+      joystick_state_fetcher_(
+          event_loop->MakeFetcher<aos::JoystickState>("/aos")),
+      robot_state_fetcher_(event_loop->MakeFetcher<aos::RobotState>("/aos")),
+      auto_splines_(),
+      superstructure_goal_sender_(
+          event_loop
+              ->MakeSender<::y2023_bot3::control_loops::superstructure::Goal>(
+                  "/superstructure")),
+      superstructure_status_fetcher_(
+          event_loop->MakeFetcher<
+              ::y2023_bot3::control_loops::superstructure::Status>(
+              "/superstructure")) {
+  drivetrain_status_fetcher_.Fetch();
+  replan_timer_ = event_loop->AddTimer([this]() { Replan(); });
+
+  event_loop->OnRun([this, event_loop]() {
+    replan_timer_->Schedule(event_loop->monotonic_now());
+    button_poll_->Schedule(event_loop->monotonic_now(),
+                           chrono::milliseconds(50));
+  });
+
+  // TODO(james): Really need to refactor this code since we keep using it.
+  button_poll_ = event_loop->AddTimer([this]() {
+    const aos::monotonic_clock::time_point now =
+        this->event_loop()->context().monotonic_event_time;
+    if (robot_state_fetcher_.Fetch()) {
+      if (robot_state_fetcher_->user_button()) {
+        user_indicated_safe_to_reset_ = true;
+        MaybeSendStartingPosition();
+      }
+    }
+    if (joystick_state_fetcher_.Fetch()) {
+      if (joystick_state_fetcher_->has_alliance() &&
+          (joystick_state_fetcher_->alliance() != alliance_)) {
+        alliance_ = joystick_state_fetcher_->alliance();
+        is_planned_ = false;
+        // Only kick the planning out by 2 seconds. If we end up enabled in
+        // that second, then we will kick it out further based on the code
+        // below.
+        replan_timer_->Schedule(now + std::chrono::seconds(2));
+      }
+      if (joystick_state_fetcher_->enabled()) {
+        if (!is_planned_) {
+          // Only replan once we've been disabled for 5 seconds.
+          replan_timer_->Schedule(now + std::chrono::seconds(5));
+        }
+      }
+    }
+  });
+}
+
+void AutonomousActor::Replan() {
+  if (!drivetrain_status_fetcher_.Fetch()) {
+    replan_timer_->Schedule(event_loop()->monotonic_now() + chrono::seconds(1));
+    AOS_LOG(INFO, "Drivetrain not up, replanning in 1 second");
+    return;
+  }
+
+  if (alliance_ == aos::Alliance::kInvalid) {
+    return;
+  }
+  sent_starting_position_ = false;
+  if (FLAGS_spline_auto) {
+    test_spline_ =
+        PlanSpline(std::bind(&AutonomousSplines::TestSpline, &auto_splines_,
+                             std::placeholders::_1, alliance_),
+                   SplineDirection::kForward);
+
+    starting_position_ = test_spline_->starting_position();
+  }
+  is_planned_ = true;
+
+  MaybeSendStartingPosition();
+}
+
+void AutonomousActor::MaybeSendStartingPosition() {
+  if (is_planned_ && user_indicated_safe_to_reset_ &&
+      !sent_starting_position_) {
+    CHECK(starting_position_);
+    SendStartingPosition(starting_position_.value());
+  }
+}
+
+void AutonomousActor::Reset() {
+  InitializeEncoders();
+  ResetDrivetrain();
+
+  joystick_state_fetcher_.Fetch();
+  CHECK(joystick_state_fetcher_.get() != nullptr)
+      << "Expect at least one JoystickState message before running auto...";
+  alliance_ = joystick_state_fetcher_->alliance();
+
+  preloaded_ = false;
+  SendSuperstructureGoal();
+}
+
+bool AutonomousActor::RunAction(
+    const ::frc971::autonomous::AutonomousActionParams *params) {
+  Reset();
+
+  AOS_LOG(INFO, "Params are %d\n", params->mode());
+
+  if (!user_indicated_safe_to_reset_) {
+    AOS_LOG(WARNING, "Didn't send starting position prior to starting auto.");
+    CHECK(starting_position_);
+    SendStartingPosition(starting_position_.value());
+  }
+  // Clear this so that we don't accidentally resend things as soon as we
+  // replan later.
+  user_indicated_safe_to_reset_ = false;
+  is_planned_ = false;
+  starting_position_.reset();
+
+  AOS_LOG(INFO, "Params are %d\n", params->mode());
+  if (alliance_ == aos::Alliance::kInvalid) {
+    AOS_LOG(INFO, "Aborting autonomous due to invalid alliance selection.");
+    return false;
+  }
+
+  return true;
+}
+
+void AutonomousActor::SendStartingPosition(const Eigen::Vector3d &start) {
+  // Set up the starting position for the blue alliance.
+
+  auto builder = localizer_control_sender_.MakeBuilder();
+
+  LocalizerControl::Builder localizer_control_builder =
+      builder.MakeBuilder<LocalizerControl>();
+  localizer_control_builder.add_x(start(0));
+  localizer_control_builder.add_y(start(1));
+  localizer_control_builder.add_theta(start(2));
+  localizer_control_builder.add_theta_uncertainty(0.00001);
+  AOS_LOG(INFO, "User button pressed, x: %f y: %f theta: %f", start(0),
+          start(1), start(2));
+  if (builder.Send(localizer_control_builder.Finish()) !=
+      aos::RawSender::Error::kOk) {
+    AOS_LOG(ERROR, "Failed to reset localizer.\n");
+  }
+}
+
+void AutonomousActor::SendSuperstructureGoal() {
+  auto builder = superstructure_goal_sender_.MakeBuilder();
+
+  control_loops::superstructure::Goal::Builder superstructure_builder =
+      builder.MakeBuilder<control_loops::superstructure::Goal>();
+
+  if (builder.Send(superstructure_builder.Finish()) !=
+      aos::RawSender::Error::kOk) {
+    AOS_LOG(ERROR, "Sending superstructure goal failed.\n");
+  }
+}
+
+}  // namespace autonomous
+}  // namespace y2023_bot3
diff --git a/y2023_bot3/autonomous/autonomous_actor.h b/y2023_bot3/autonomous/autonomous_actor.h
new file mode 100644
index 0000000..b7978c7
--- /dev/null
+++ b/y2023_bot3/autonomous/autonomous_actor.h
@@ -0,0 +1,64 @@
+#ifndef Y2023_AUTONOMOUS_AUTONOMOUS_ACTOR_H_
+#define Y2023_AUTONOMOUS_AUTONOMOUS_ACTOR_H_
+
+#include "aos/actions/actions.h"
+#include "aos/actions/actor.h"
+#include "frc971/autonomous/base_autonomous_actor.h"
+#include "frc971/control_loops/control_loops_generated.h"
+#include "frc971/control_loops/drivetrain/drivetrain_config.h"
+#include "frc971/control_loops/drivetrain/localizer_generated.h"
+#include "y2023_bot3/autonomous/auto_splines.h"
+#include "y2023_bot3/control_loops/superstructure/superstructure_goal_generated.h"
+#include "y2023_bot3/control_loops/superstructure/superstructure_status_generated.h"
+
+namespace y2023_bot3 {
+namespace autonomous {
+
+class AutonomousActor : public ::frc971::autonomous::BaseAutonomousActor {
+ public:
+  explicit AutonomousActor(::aos::EventLoop *event_loop);
+
+  bool RunAction(
+      const ::frc971::autonomous::AutonomousActionParams *params) override;
+
+ private:
+  void set_preloaded(bool preloaded) { preloaded_ = preloaded; }
+
+  void SendSuperstructureGoal();
+
+  void Reset();
+
+  void SendStartingPosition(const Eigen::Vector3d &start);
+  void MaybeSendStartingPosition();
+  void Replan();
+
+  aos::Sender<frc971::control_loops::drivetrain::LocalizerControl>
+      localizer_control_sender_;
+  aos::Fetcher<aos::JoystickState> joystick_state_fetcher_;
+  aos::Fetcher<aos::RobotState> robot_state_fetcher_;
+
+  aos::TimerHandler *replan_timer_;
+  aos::TimerHandler *button_poll_;
+
+  aos::Alliance alliance_ = aos::Alliance::kInvalid;
+  AutonomousSplines auto_splines_;
+  bool user_indicated_safe_to_reset_ = false;
+  bool sent_starting_position_ = false;
+
+  bool is_planned_ = false;
+
+  std::optional<Eigen::Vector3d> starting_position_;
+
+  bool preloaded_ = false;
+
+  aos::Sender<control_loops::superstructure::Goal> superstructure_goal_sender_;
+  aos::Fetcher<y2023_bot3::control_loops::superstructure::Status>
+      superstructure_status_fetcher_;
+
+  std::optional<SplineHandle> test_spline_;
+};
+
+}  // namespace autonomous
+}  // namespace y2023_bot3
+
+#endif  // Y2023_AUTONOMOUS_AUTONOMOUS_ACTOR_H_
diff --git a/y2023_bot3/autonomous/autonomous_actor_main.cc b/y2023_bot3/autonomous/autonomous_actor_main.cc
new file mode 100644
index 0000000..3947359
--- /dev/null
+++ b/y2023_bot3/autonomous/autonomous_actor_main.cc
@@ -0,0 +1,19 @@
+#include <cstdio>
+
+#include "aos/events/shm_event_loop.h"
+#include "aos/init.h"
+#include "y2023_bot3/autonomous/autonomous_actor.h"
+
+int main(int argc, char *argv[]) {
+  ::aos::InitGoogle(&argc, &argv);
+
+  aos::FlatbufferDetachedBuffer<aos::Configuration> config =
+      aos::configuration::ReadConfig("aos_config.json");
+
+  ::aos::ShmEventLoop event_loop(&config.message());
+  ::y2023_bot3::autonomous::AutonomousActor autonomous(&event_loop);
+
+  event_loop.Run();
+
+  return 0;
+}
diff --git a/y2023_bot3/autonomous/splines/README.md b/y2023_bot3/autonomous/splines/README.md
new file mode 100644
index 0000000..c655416
--- /dev/null
+++ b/y2023_bot3/autonomous/splines/README.md
@@ -0,0 +1,3 @@
+# Spline Descriptions
+This folder contains reference material for what each spline does
+
diff --git a/y2023_bot3/autonomous/splines/test_spline.json b/y2023_bot3/autonomous/splines/test_spline.json
new file mode 100644
index 0000000..733d516
--- /dev/null
+++ b/y2023_bot3/autonomous/splines/test_spline.json
@@ -0,0 +1 @@
+{"spline_count": 1, "spline_x": [0, 0.4, 0.4, 0.6, 0.6, 1.0], "spline_y": [0, 0, 0.05, 0.1, 0.15, 0.15], "constraints": [{"constraint_type": "LONGITUDINAL_ACCELERATION", "value": 1}, {"constraint_type": "LATERAL_ACCELERATION", "value": 1}, {"constraint_type": "VOLTAGE", "value": 2}]}
diff --git a/y2023_bot3/constants.cc b/y2023_bot3/constants.cc
new file mode 100644
index 0000000..909d508
--- /dev/null
+++ b/y2023_bot3/constants.cc
@@ -0,0 +1,41 @@
+#include "y2023_bot3/constants.h"
+
+#include <cinttypes>
+#include <map>
+
+#if __has_feature(address_sanitizer)
+#include "sanitizer/lsan_interface.h"
+#endif
+
+#include "absl/base/call_once.h"
+#include "glog/logging.h"
+
+#include "aos/mutex/mutex.h"
+#include "aos/network/team_number.h"
+
+namespace y2023_bot3 {
+namespace constants {
+
+Values MakeValues(uint16_t team) {
+  LOG(INFO) << "creating a Constants for team: " << team;
+
+  Values r;
+  switch (team) {
+    // A set of constants for tests.
+    case 1:
+      break;
+
+    case kThirdRobotTeamNumber:
+      break;
+
+    default:
+      LOG(FATAL) << "unknown team: " << team;
+  }
+
+  return r;
+}
+
+Values MakeValues() { return MakeValues(aos::network::GetTeamNumber()); }
+
+}  // namespace constants
+}  // namespace y2023_bot3
diff --git a/y2023_bot3/constants.h b/y2023_bot3/constants.h
new file mode 100644
index 0000000..500b836
--- /dev/null
+++ b/y2023_bot3/constants.h
@@ -0,0 +1,73 @@
+#ifndef Y2023_CONSTANTS_H_
+#define Y2023_CONSTANTS_H_
+
+#include <array>
+#include <cmath>
+#include <cstdint>
+
+#include "frc971/constants.h"
+#include "frc971/control_loops/pose.h"
+#include "frc971/control_loops/static_zeroing_single_dof_profiled_subsystem.h"
+#include "y2023_bot3/control_loops/drivetrain/drivetrain_dog_motor_plant.h"
+
+namespace y2023_bot3 {
+namespace constants {
+
+constexpr uint16_t kThirdRobotTeamNumber = 8971;
+
+struct Values {
+  static const int kZeroingSampleSize = 200;
+
+  static const int kSuperstructureCANWriterPriority = 35;
+  static const int kDrivetrainWriterPriority = 35;
+  static const int kDrivetrainTxPriority = 36;
+  static const int kDrivetrainRxPriority = 36;
+
+  // TODO(max): Change these constants based on 3rd drivetrain CAD
+  static constexpr double kDrivetrainCyclesPerRevolution() { return 512.0; }
+  static constexpr double kDrivetrainEncoderCountsPerRevolution() {
+    return kDrivetrainCyclesPerRevolution() * 4;
+  }
+  static constexpr double kDrivetrainEncoderRatio() { return 1.0; }
+  static constexpr double kMaxDrivetrainEncoderPulsesPerSecond() {
+    return control_loops::drivetrain::kFreeSpeed / (2.0 * M_PI) *
+           control_loops::drivetrain::kHighOutputRatio /
+           constants::Values::kDrivetrainEncoderRatio() *
+           kDrivetrainEncoderCountsPerRevolution();
+  }
+
+  static constexpr double kDrivetrainSupplyCurrentLimit() { return 35.0; }
+  static constexpr double kDrivetrainStatorCurrentLimit() { return 60.0; }
+
+  // timeout to ensure code doesn't get stuck after releasing the "intake"
+  // button
+  static constexpr std::chrono::milliseconds kExtraIntakingTime() {
+    return std::chrono::milliseconds{100};
+  }
+
+  static double DrivetrainEncoderToMeters(int32_t in) {
+    return ((static_cast<double>(in) /
+             kDrivetrainEncoderCountsPerRevolution()) *
+            (2.0 * M_PI)) *
+           kDrivetrainEncoderRatio() * control_loops::drivetrain::kWheelRadius;
+  }
+
+  static double DrivetrainCANEncoderToMeters(double rotations) {
+    return (rotations * (2.0 * M_PI)) *
+           control_loops::drivetrain::kHighOutputRatio *
+           control_loops::drivetrain::kWheelRadius;
+  }
+};
+
+// Creates and returns a Values instance for the constants.
+// Should be called before realtime because this allocates memory.
+// Only the first call to either of these will be used.
+Values MakeValues(uint16_t team);
+
+// Calls MakeValues with aos::network::GetTeamNumber()
+Values MakeValues();
+
+}  // namespace constants
+}  // namespace y2023_bot3
+
+#endif  // Y2023_CONSTANTS_H_
diff --git a/y2023_bot3/constants/8971.json b/y2023_bot3/constants/8971.json
new file mode 100644
index 0000000..aea0c6f
--- /dev/null
+++ b/y2023_bot3/constants/8971.json
@@ -0,0 +1,5 @@
+{
+  "robot": {
+  },
+  {% include 'y2023_bot3/constants/common.json' %}
+}
diff --git a/y2023_bot3/constants/BUILD b/y2023_bot3/constants/BUILD
new file mode 100644
index 0000000..40d77be
--- /dev/null
+++ b/y2023_bot3/constants/BUILD
@@ -0,0 +1,65 @@
+load("@com_github_google_flatbuffers//:build_defs.bzl", "flatbuffer_cc_library")
+load("//tools/build_rules:template.bzl", "jinja2_template")
+
+cc_library(
+    name = "simulated_constants_sender",
+    srcs = ["simulated_constants_sender.cc"],
+    hdrs = ["simulated_constants_sender.h"],
+    data = [":test_constants.json"],
+    visibility = ["//y2023_bot3:__subpackages__"],
+    deps = [
+        ":constants_fbs",
+        ":constants_list_fbs",
+        "//aos/events:simulated_event_loop",
+        "//aos/testing:path",
+        "//frc971/constants:constants_sender_lib",
+    ],
+)
+
+jinja2_template(
+    name = "constants.json",
+    src = "constants.jinja2.json",
+    includes = [
+        "8971.json",
+        "common.json",
+    ],
+    parameters = {},
+    visibility = ["//visibility:public"],
+)
+
+jinja2_template(
+    name = "test_constants.json",
+    src = "test_constants.jinja2.json",
+    includes = glob(["test_data/*.json"]),
+    parameters = {},
+    visibility = ["//visibility:public"],
+)
+
+flatbuffer_cc_library(
+    name = "constants_fbs",
+    srcs = ["constants.fbs"],
+    gen_reflections = True,
+    visibility = ["//visibility:public"],
+)
+
+flatbuffer_cc_library(
+    name = "constants_list_fbs",
+    srcs = ["constants_list.fbs"],
+    gen_reflections = True,
+    visibility = ["//visibility:public"],
+    deps = [":constants_fbs"],
+)
+
+cc_binary(
+    name = "constants_sender",
+    srcs = ["constants_sender.cc"],
+    visibility = ["//visibility:public"],
+    deps = [
+        ":constants_fbs",
+        ":constants_list_fbs",
+        "//aos:init",
+        "//aos/events:shm_event_loop",
+        "//aos/testing:path",
+        "//frc971/constants:constants_sender_lib",
+    ],
+)
diff --git a/y2023_bot3/constants/common.json b/y2023_bot3/constants/common.json
new file mode 100644
index 0000000..0967ef4
--- /dev/null
+++ b/y2023_bot3/constants/common.json
@@ -0,0 +1 @@
+{}
diff --git a/y2023_bot3/constants/constants.fbs b/y2023_bot3/constants/constants.fbs
new file mode 100644
index 0000000..08f03be
--- /dev/null
+++ b/y2023_bot3/constants/constants.fbs
@@ -0,0 +1,10 @@
+namespace y2023_bot3;
+
+table RobotConstants {
+}
+
+table Constants {
+  robot:RobotConstants (id: 0);
+}
+
+root_type Constants;
diff --git a/y2023_bot3/constants/constants.jinja2.json b/y2023_bot3/constants/constants.jinja2.json
new file mode 100644
index 0000000..be1ea14
--- /dev/null
+++ b/y2023_bot3/constants/constants.jinja2.json
@@ -0,0 +1,8 @@
+{
+  "constants": [
+    {
+      "team": 8971,
+      "data": {% include 'y2023_bot3/constants/8971.json' %}
+    },
+  ]
+}
diff --git a/y2023_bot3/constants/constants_list.fbs b/y2023_bot3/constants/constants_list.fbs
new file mode 100644
index 0000000..8b132e8
--- /dev/null
+++ b/y2023_bot3/constants/constants_list.fbs
@@ -0,0 +1,14 @@
+include "y2023_bot3/constants/constants.fbs";
+
+namespace y2023_bot3;
+
+table TeamAndConstants {
+  team:long (id: 0);
+  data:Constants (id: 1);
+}
+
+table ConstantsList {
+  constants:[TeamAndConstants] (id: 0);
+}
+
+root_type ConstantsList;
diff --git a/y2023_bot3/constants/constants_sender.cc b/y2023_bot3/constants/constants_sender.cc
new file mode 100644
index 0000000..d6e5c28
--- /dev/null
+++ b/y2023_bot3/constants/constants_sender.cc
@@ -0,0 +1,25 @@
+#include "gflags/gflags.h"
+#include "glog/logging.h"
+
+#include "aos/configuration.h"
+#include "aos/events/shm_event_loop.h"
+#include "aos/init.h"
+#include "aos/json_to_flatbuffer.h"
+#include "frc971/constants/constants_sender_lib.h"
+#include "y2023_bot3/constants/constants_generated.h"
+#include "y2023_bot3/constants/constants_list_generated.h"
+
+DEFINE_string(config, "aos_config.json", "Path to the AOS config.");
+DEFINE_string(constants_path, "constants.json", "Path to the constant file");
+
+int main(int argc, char **argv) {
+  aos::InitGoogle(&argc, &argv);
+  aos::FlatbufferDetachedBuffer<aos::Configuration> config =
+      aos::configuration::ReadConfig(FLAGS_config);
+  aos::ShmEventLoop event_loop(&config.message());
+  frc971::constants::ConstantSender<y2023_bot3::Constants,
+                                    y2023_bot3::ConstantsList>
+      constants_sender(&event_loop, FLAGS_constants_path);
+  // Don't need to call Run().
+  return 0;
+}
diff --git a/y2023_bot3/constants/simulated_constants_sender.cc b/y2023_bot3/constants/simulated_constants_sender.cc
new file mode 100644
index 0000000..36c1891
--- /dev/null
+++ b/y2023_bot3/constants/simulated_constants_sender.cc
@@ -0,0 +1,18 @@
+#include "aos/events/simulated_event_loop.h"
+#include "aos/testing/path.h"
+#include "frc971/constants/constants_sender_lib.h"
+#include "y2023_bot3/constants/constants_generated.h"
+#include "y2023_bot3/constants/constants_list_generated.h"
+
+namespace y2023_bot3 {
+bool SendSimulationConstants(aos::SimulatedEventLoopFactory *factory, int team,
+                             std::string constants_path) {
+  for (const aos::Node *node : factory->nodes()) {
+    std::unique_ptr<aos::EventLoop> event_loop =
+        factory->MakeEventLoop("constants_sender", node);
+    frc971::constants::ConstantSender<Constants, ConstantsList> sender(
+        event_loop.get(), constants_path, team, "/constants");
+  }
+  return true;
+}
+}  // namespace y2023_bot3
diff --git a/y2023_bot3/constants/simulated_constants_sender.h b/y2023_bot3/constants/simulated_constants_sender.h
new file mode 100644
index 0000000..3f1495f
--- /dev/null
+++ b/y2023_bot3/constants/simulated_constants_sender.h
@@ -0,0 +1,16 @@
+#ifndef Y2023_CONSTANTS_SIMULATED_CONFIG_SENDER_H_
+#define Y2023_CONSTANTS_SIMULATED_CONFIG_SENDER_H_
+
+#include "aos/events/simulated_event_loop.h"
+#include "aos/testing/path.h"
+
+namespace y2023_bot3 {
+// Returns true, to allow this to be easily called in the initializer list of a
+// constructor.
+bool SendSimulationConstants(
+    aos::SimulatedEventLoopFactory *factory, int team,
+    std::string constants_path =
+        aos::testing::ArtifactPath("y2023_bot3/constants/test_constants.json"));
+}  // namespace y2023_bot3
+
+#endif  // Y2023_CONSTANTS_SIMULATED_CONFIG_SENDER_H_
diff --git a/y2023_bot3/constants/test_constants.jinja2.json b/y2023_bot3/constants/test_constants.jinja2.json
new file mode 100644
index 0000000..f05194b
--- /dev/null
+++ b/y2023_bot3/constants/test_constants.jinja2.json
@@ -0,0 +1,8 @@
+{
+  "constants": [
+    {
+      "team": 7971,
+      "data": {% include 'y2023_bot3/constants/test_data/test_team.json' %}
+    }
+  ]
+}
diff --git a/y2023_bot3/constants/test_data/scoring_map.json b/y2023_bot3/constants/test_data/scoring_map.json
new file mode 100644
index 0000000..0967ef4
--- /dev/null
+++ b/y2023_bot3/constants/test_data/scoring_map.json
@@ -0,0 +1 @@
+{}
diff --git a/y2023_bot3/constants/test_data/test_team.json b/y2023_bot3/constants/test_data/test_team.json
new file mode 100644
index 0000000..97bab66
--- /dev/null
+++ b/y2023_bot3/constants/test_data/test_team.json
@@ -0,0 +1,3 @@
+{
+    "robot": {}
+}
diff --git a/y2023_bot3/control_loops/BUILD b/y2023_bot3/control_loops/BUILD
new file mode 100644
index 0000000..77007e7
--- /dev/null
+++ b/y2023_bot3/control_loops/BUILD
@@ -0,0 +1,7 @@
+py_library(
+    name = "python_init",
+    srcs = ["__init__.py"],
+    target_compatible_with = ["@platforms//os:linux"],
+    visibility = ["//visibility:public"],
+    deps = ["//y2023_bot3:python_init"],
+)
diff --git a/y2023_bot3/control_loops/__init__.py b/y2023_bot3/control_loops/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/y2023_bot3/control_loops/__init__.py
diff --git a/y2023_bot3/control_loops/drivetrain/BUILD b/y2023_bot3/control_loops/drivetrain/BUILD
new file mode 100644
index 0000000..46654ec
--- /dev/null
+++ b/y2023_bot3/control_loops/drivetrain/BUILD
@@ -0,0 +1,102 @@
+genrule(
+    name = "genrule_drivetrain",
+    outs = [
+        "drivetrain_dog_motor_plant.h",
+        "drivetrain_dog_motor_plant.cc",
+        "kalman_drivetrain_motor_plant.h",
+        "kalman_drivetrain_motor_plant.cc",
+    ],
+    cmd = "$(location //y2023_bot3/control_loops/python:drivetrain) $(OUTS)",
+    target_compatible_with = ["@platforms//os:linux"],
+    tools = [
+        "//y2023_bot3/control_loops/python:drivetrain",
+    ],
+)
+
+genrule(
+    name = "genrule_polydrivetrain",
+    outs = [
+        "polydrivetrain_dog_motor_plant.h",
+        "polydrivetrain_dog_motor_plant.cc",
+        "polydrivetrain_cim_plant.h",
+        "polydrivetrain_cim_plant.cc",
+        "hybrid_velocity_drivetrain.h",
+        "hybrid_velocity_drivetrain.cc",
+    ],
+    cmd = "$(location //y2023_bot3/control_loops/python:polydrivetrain) $(OUTS)",
+    target_compatible_with = ["@platforms//os:linux"],
+    tools = [
+        "//y2023_bot3/control_loops/python:polydrivetrain",
+    ],
+)
+
+cc_library(
+    name = "polydrivetrain_plants",
+    srcs = [
+        "drivetrain_dog_motor_plant.cc",
+        "hybrid_velocity_drivetrain.cc",
+        "kalman_drivetrain_motor_plant.cc",
+        "polydrivetrain_dog_motor_plant.cc",
+    ],
+    hdrs = [
+        "drivetrain_dog_motor_plant.h",
+        "hybrid_velocity_drivetrain.h",
+        "kalman_drivetrain_motor_plant.h",
+        "polydrivetrain_dog_motor_plant.h",
+    ],
+    target_compatible_with = ["@platforms//os:linux"],
+    visibility = ["//visibility:public"],
+    deps = [
+        "//frc971/control_loops:hybrid_state_feedback_loop",
+        "//frc971/control_loops:state_feedback_loop",
+    ],
+)
+
+cc_library(
+    name = "drivetrain_base",
+    srcs = [
+        "drivetrain_base.cc",
+    ],
+    hdrs = [
+        "drivetrain_base.h",
+    ],
+    target_compatible_with = ["@platforms//os:linux"],
+    visibility = ["//visibility:public"],
+    deps = [
+        ":polydrivetrain_plants",
+        "//frc971:shifter_hall_effect",
+        "//frc971/control_loops/drivetrain:drivetrain_config",
+    ],
+)
+
+cc_binary(
+    name = "drivetrain",
+    srcs = [
+        "drivetrain_main.cc",
+    ],
+    target_compatible_with = ["@platforms//os:linux"],
+    visibility = ["//visibility:public"],
+    deps = [
+        ":drivetrain_base",
+        "//aos:init",
+        "//aos/events:shm_event_loop",
+        "//frc971/control_loops/drivetrain:drivetrain_lib",
+        "//frc971/control_loops/drivetrain/localization:puppet_localizer",
+        "//y2023_bot3/constants:constants_sender",
+    ],
+)
+
+cc_binary(
+    name = "trajectory_generator",
+    srcs = [
+        "trajectory_generator_main.cc",
+    ],
+    target_compatible_with = ["@platforms//os:linux"],
+    visibility = ["//visibility:public"],
+    deps = [
+        ":drivetrain_base",
+        "//aos:init",
+        "//aos/events:shm_event_loop",
+        "//frc971/control_loops/drivetrain:trajectory_generator",
+    ],
+)
diff --git a/y2023_bot3/control_loops/drivetrain/drivetrain_base.cc b/y2023_bot3/control_loops/drivetrain/drivetrain_base.cc
new file mode 100644
index 0000000..5b30097
--- /dev/null
+++ b/y2023_bot3/control_loops/drivetrain/drivetrain_base.cc
@@ -0,0 +1,90 @@
+#include "y2023_bot3/control_loops/drivetrain/drivetrain_base.h"
+
+#include <chrono>
+
+#include "frc971/control_loops/drivetrain/drivetrain_config.h"
+#include "frc971/control_loops/state_feedback_loop.h"
+#include "y2023_bot3/control_loops/drivetrain/drivetrain_dog_motor_plant.h"
+#include "y2023_bot3/control_loops/drivetrain/hybrid_velocity_drivetrain.h"
+#include "y2023_bot3/control_loops/drivetrain/kalman_drivetrain_motor_plant.h"
+#include "y2023_bot3/control_loops/drivetrain/polydrivetrain_dog_motor_plant.h"
+
+using ::frc971::control_loops::drivetrain::DownEstimatorConfig;
+using ::frc971::control_loops::drivetrain::DrivetrainConfig;
+using ::frc971::control_loops::drivetrain::LineFollowConfig;
+
+namespace chrono = ::std::chrono;
+
+namespace y2023_bot3 {
+namespace control_loops {
+namespace drivetrain {
+
+using ::frc971::constants::ShifterHallEffect;
+
+const ShifterHallEffect kThreeStateDriveShifter{0.0, 0.0, 0.25, 0.75};
+
+const DrivetrainConfig<double> &GetDrivetrainConfig() {
+  // Yaw of the IMU relative to the robot frame.
+  static constexpr double kImuYaw = 0.0;
+  static DrivetrainConfig<double> kDrivetrainConfig{
+      ::frc971::control_loops::drivetrain::ShifterType::SIMPLE_SHIFTER,
+      ::frc971::control_loops::drivetrain::LoopType::CLOSED_LOOP,
+      ::frc971::control_loops::drivetrain::GyroType::SPARTAN_GYRO,
+      ::frc971::control_loops::drivetrain::IMUType::IMU_FLIPPED_X,
+
+      drivetrain::MakeDrivetrainLoop,
+      drivetrain::MakeVelocityDrivetrainLoop,
+      drivetrain::MakeKFDrivetrainLoop,
+      drivetrain::MakeHybridVelocityDrivetrainLoop,
+
+      chrono::duration_cast<chrono::nanoseconds>(
+          chrono::duration<double>(drivetrain::kDt)),
+      drivetrain::kRobotRadius,
+      drivetrain::kWheelRadius,
+      drivetrain::kV,
+
+      drivetrain::kHighGearRatio,
+      drivetrain::kLowGearRatio,
+      drivetrain::kJ,
+      drivetrain::kMass,
+      kThreeStateDriveShifter,
+      kThreeStateDriveShifter,
+      true /* default_high_gear */,
+      0 /* down_offset if using constants use
+     constants::GetValues().down_error */
+      ,
+      0.7 /* wheel_non_linearity */,
+      1.2 /* quickturn_wheel_multiplier */,
+      1.2 /* wheel_multiplier */,
+      true /*pistol_grip_shift_enables_line_follow*/,
+      (Eigen::Matrix<double, 3, 3>() << std::cos(kImuYaw), -std::sin(kImuYaw),
+       0.0, std::sin(kImuYaw), std::cos(kImuYaw), 0.0, 0.0, 0.0, 1.0)
+          .finished(),
+      false /*is_simulated*/,
+      DownEstimatorConfig{.gravity_threshold = 0.015,
+                          .do_accel_corrections = 1000},
+      LineFollowConfig{
+          .Q = Eigen::Matrix3d((::Eigen::DiagonalMatrix<double, 3>().diagonal()
+                                    << 1.0 / ::std::pow(0.1, 2),
+                                1.0 / ::std::pow(1.0, 2),
+                                1.0 / ::std::pow(1.0, 2))
+                                   .finished()
+                                   .asDiagonal()),
+          .R = Eigen::Matrix2d((::Eigen::DiagonalMatrix<double, 2>().diagonal()
+                                    << 10.0 / ::std::pow(12.0, 2),
+                                10.0 / ::std::pow(12.0, 2))
+                                   .finished()
+                                   .asDiagonal()),
+          .max_controllable_offset = 0.5},
+      frc971::control_loops::drivetrain::PistolTopButtonUse::kNone,
+      frc971::control_loops::drivetrain::PistolSecondButtonUse::kTurn1,
+      frc971::control_loops::drivetrain::PistolBottomButtonUse::
+          kControlLoopDriving,
+  };
+
+  return kDrivetrainConfig;
+};
+
+}  // namespace drivetrain
+}  // namespace control_loops
+}  // namespace y2023_bot3
diff --git a/y2023_bot3/control_loops/drivetrain/drivetrain_base.h b/y2023_bot3/control_loops/drivetrain/drivetrain_base.h
new file mode 100644
index 0000000..6922ea6
--- /dev/null
+++ b/y2023_bot3/control_loops/drivetrain/drivetrain_base.h
@@ -0,0 +1,17 @@
+#ifndef Y2023_CONTROL_LOOPS_DRIVETRAIN_DRIVETRAIN_BASE_H_
+#define Y2023_CONTROL_LOOPS_DRIVETRAIN_DRIVETRAIN_BASE_H_
+
+#include "frc971/control_loops/drivetrain/drivetrain_config.h"
+
+namespace y2023_bot3 {
+namespace control_loops {
+namespace drivetrain {
+
+const ::frc971::control_loops::drivetrain::DrivetrainConfig<double> &
+GetDrivetrainConfig();
+
+}  // namespace drivetrain
+}  // namespace control_loops
+}  // namespace y2023_bot3
+
+#endif  // Y2023_CONTROL_LOOPS_DRIVETRAIN_DRIVETRAIN_BASE_H_
diff --git a/y2023_bot3/control_loops/drivetrain/drivetrain_main.cc b/y2023_bot3/control_loops/drivetrain/drivetrain_main.cc
new file mode 100644
index 0000000..55c51aa
--- /dev/null
+++ b/y2023_bot3/control_loops/drivetrain/drivetrain_main.cc
@@ -0,0 +1,35 @@
+#include <memory>
+
+#include "aos/events/shm_event_loop.h"
+#include "aos/init.h"
+#include "frc971/constants/constants_sender_lib.h"
+#include "frc971/control_loops/drivetrain/drivetrain.h"
+#include "frc971/control_loops/drivetrain/localization/puppet_localizer.h"
+#include "y2023_bot3/constants/constants_generated.h"
+#include "y2023_bot3/control_loops/drivetrain/drivetrain_base.h"
+
+using ::frc971::control_loops::drivetrain::DrivetrainLoop;
+
+int main(int argc, char **argv) {
+  aos::InitGoogle(&argc, &argv);
+
+  aos::FlatbufferDetachedBuffer<aos::Configuration> config =
+      aos::configuration::ReadConfig("aos_config.json");
+
+  frc971::constants::WaitForConstants<y2023_bot3::Constants>(&config.message());
+
+  aos::ShmEventLoop event_loop(&config.message());
+  std::unique_ptr<::frc971::control_loops::drivetrain::PuppetLocalizer>
+      localizer = std::make_unique<
+          ::frc971::control_loops::drivetrain::PuppetLocalizer>(
+          &event_loop,
+          ::y2023_bot3::control_loops::drivetrain::GetDrivetrainConfig());
+  ;
+  std::unique_ptr<DrivetrainLoop> drivetrain = std::make_unique<DrivetrainLoop>(
+      y2023_bot3::control_loops::drivetrain::GetDrivetrainConfig(), &event_loop,
+      localizer.get());
+
+  event_loop.Run();
+
+  return 0;
+}
diff --git a/y2023_bot3/control_loops/drivetrain/trajectory_generator_main.cc b/y2023_bot3/control_loops/drivetrain/trajectory_generator_main.cc
new file mode 100644
index 0000000..7d6d5f2
--- /dev/null
+++ b/y2023_bot3/control_loops/drivetrain/trajectory_generator_main.cc
@@ -0,0 +1,40 @@
+#include <sys/resource.h>
+#include <sys/time.h>
+
+#include "aos/events/shm_event_loop.h"
+#include "aos/init.h"
+#include "frc971/control_loops/drivetrain/trajectory_generator.h"
+#include "y2023_bot3/control_loops/drivetrain/drivetrain_base.h"
+
+using ::frc971::control_loops::drivetrain::TrajectoryGenerator;
+
+DEFINE_bool(skip_renicing, false,
+            "If true, skip renicing the trajectory generator.");
+
+int main(int argc, char *argv[]) {
+  ::aos::InitGoogle(&argc, &argv);
+
+  aos::FlatbufferDetachedBuffer<aos::Configuration> config =
+      aos::configuration::ReadConfig("aos_config.json");
+
+  ::aos::ShmEventLoop event_loop(&config.message());
+  TrajectoryGenerator generator(
+      &event_loop,
+      ::y2023_bot3::control_loops::drivetrain::GetDrivetrainConfig());
+
+  event_loop.OnRun([]() {
+    if (FLAGS_skip_renicing) {
+      LOG(WARNING) << "Ignoring request to renice to -20 due to "
+                      "--skip_renicing.";
+    } else {
+      errno = 0;
+      setpriority(PRIO_PROCESS, 0, -20);
+      PCHECK(errno == 0)
+          << ": Renicing to -20 failed, use --skip_renicing to skip renicing.";
+    }
+  });
+
+  event_loop.Run();
+
+  return 0;
+}
diff --git a/y2023_bot3/control_loops/python/BUILD b/y2023_bot3/control_loops/python/BUILD
new file mode 100644
index 0000000..fe7666f
--- /dev/null
+++ b/y2023_bot3/control_loops/python/BUILD
@@ -0,0 +1,57 @@
+package(default_visibility = ["//y2023_bot3:__subpackages__"])
+
+py_binary(
+    name = "drivetrain",
+    srcs = [
+        "drivetrain.py",
+    ],
+    legacy_create_init = False,
+    target_compatible_with = ["@platforms//cpu:x86_64"],
+    deps = [
+        ":python_init",
+        "//frc971/control_loops/python:drivetrain",
+        "@pip//glog",
+        "@pip//python_gflags",
+    ],
+)
+
+py_binary(
+    name = "polydrivetrain",
+    srcs = [
+        "drivetrain.py",
+        "polydrivetrain.py",
+    ],
+    legacy_create_init = False,
+    target_compatible_with = ["@platforms//cpu:x86_64"],
+    deps = [
+        ":python_init",
+        "//frc971/control_loops/python:polydrivetrain",
+        "@pip//glog",
+        "@pip//python_gflags",
+    ],
+)
+
+py_library(
+    name = "polydrivetrain_lib",
+    srcs = [
+        "drivetrain.py",
+        "polydrivetrain.py",
+    ],
+    target_compatible_with = ["@platforms//cpu:x86_64"],
+    visibility = ["//visibility:public"],
+    deps = [
+        "//frc971/control_loops/python:controls",
+        "//frc971/control_loops/python:drivetrain",
+        "//frc971/control_loops/python:polydrivetrain",
+        "@pip//glog",
+        "@pip//python_gflags",
+    ],
+)
+
+py_library(
+    name = "python_init",
+    srcs = ["__init__.py"],
+    target_compatible_with = ["@platforms//os:linux"],
+    visibility = ["//visibility:public"],
+    deps = ["//y2023_bot3/control_loops:python_init"],
+)
diff --git a/y2023_bot3/control_loops/python/__init__.py b/y2023_bot3/control_loops/python/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/y2023_bot3/control_loops/python/__init__.py
diff --git a/y2023_bot3/control_loops/python/drivetrain.py b/y2023_bot3/control_loops/python/drivetrain.py
new file mode 100644
index 0000000..26cb043
--- /dev/null
+++ b/y2023_bot3/control_loops/python/drivetrain.py
@@ -0,0 +1,49 @@
+#!/usr/bin/python3
+
+from __future__ import print_function
+from frc971.control_loops.python import drivetrain
+from frc971.control_loops.python import control_loop
+import sys
+
+import gflags
+import glog
+
+FLAGS = gflags.FLAGS
+
+gflags.DEFINE_bool('plot', False, 'If true, plot the loop response.')
+
+# TODO(max): Change constants based on robot / CAD
+kDrivetrain = drivetrain.DrivetrainParams(
+    J=2.2,
+    mass=44.7,
+    # TODO(austin): Measure radius a bit better.
+    robot_radius=0.25,
+    wheel_radius=2.5 * 0.0254,
+    motor_type=control_loop.Falcon(),
+    num_motors=2,
+    G=(14.0 / 68.0) * (30.0 / 54.0),
+    q_pos=0.24,
+    q_vel=2.5,
+    efficiency=0.92,
+    has_imu=False,
+    force=True,
+    kf_q_voltage=1.0,
+    controller_poles=[0.82, 0.82])
+
+
+def main(argv):
+    argv = FLAGS(argv)
+    glog.init()
+
+    if FLAGS.plot:
+        drivetrain.PlotDrivetrainMotions(kDrivetrain)
+    elif len(argv) != 5:
+        print("Expected .h file name and .cc file name")
+    else:
+        # Write the generated constants out to a file.
+        drivetrain.WriteDrivetrain(argv[1:3], argv[3:5], 'y2023_bot3',
+                                   kDrivetrain)
+
+
+if __name__ == '__main__':
+    sys.exit(main(sys.argv))
diff --git a/y2023_bot3/control_loops/python/polydrivetrain.py b/y2023_bot3/control_loops/python/polydrivetrain.py
new file mode 100644
index 0000000..80e4f13
--- /dev/null
+++ b/y2023_bot3/control_loops/python/polydrivetrain.py
@@ -0,0 +1,34 @@
+#!/usr/bin/python3
+
+import sys
+from y2023_bot3.control_loops.python import drivetrain
+from frc971.control_loops.python import polydrivetrain
+
+import gflags
+import glog
+
+__author__ = 'Austin Schuh (austin.linux@gmail.com)'
+
+FLAGS = gflags.FLAGS
+
+try:
+    gflags.DEFINE_bool('plot', False, 'If true, plot the loop response.')
+except gflags.DuplicateFlagError:
+    pass
+
+
+def main(argv):
+    if FLAGS.plot:
+        polydrivetrain.PlotPolyDrivetrainMotions(drivetrain.kDrivetrain)
+    elif len(argv) != 7:
+        glog.fatal('Expected .h file name and .cc file name')
+    else:
+        polydrivetrain.WritePolyDrivetrain(argv[1:3], argv[3:5], argv[5:7],
+                                           'y2023_bot3',
+                                           drivetrain.kDrivetrain)
+
+
+if __name__ == '__main__':
+    argv = FLAGS(sys.argv)
+    glog.init()
+    sys.exit(main(argv))
diff --git a/y2023_bot3/control_loops/superstructure/BUILD b/y2023_bot3/control_loops/superstructure/BUILD
new file mode 100644
index 0000000..0330182
--- /dev/null
+++ b/y2023_bot3/control_loops/superstructure/BUILD
@@ -0,0 +1,190 @@
+load("@com_github_google_flatbuffers//:build_defs.bzl", "flatbuffer_cc_library")
+load("@com_github_google_flatbuffers//:typescript.bzl", "flatbuffer_ts_library")
+
+package(default_visibility = ["//visibility:public"])
+
+flatbuffer_cc_library(
+    name = "superstructure_goal_fbs",
+    srcs = [
+        "superstructure_goal.fbs",
+    ],
+    gen_reflections = 1,
+    deps = [
+        "//frc971/control_loops:control_loops_fbs",
+        "//frc971/control_loops:profiled_subsystem_fbs",
+    ],
+)
+
+flatbuffer_cc_library(
+    name = "superstructure_output_fbs",
+    srcs = [
+        "superstructure_output.fbs",
+    ],
+    gen_reflections = 1,
+)
+
+flatbuffer_cc_library(
+    name = "superstructure_status_fbs",
+    srcs = [
+        "superstructure_status.fbs",
+    ],
+    gen_reflections = 1,
+    deps = [
+        "//frc971/control_loops:control_loops_fbs",
+        "//frc971/control_loops:profiled_subsystem_fbs",
+    ],
+)
+
+flatbuffer_ts_library(
+    name = "superstructure_status_ts_fbs",
+    srcs = [
+        "superstructure_status.fbs",
+    ],
+    deps = [
+        "//frc971/control_loops:control_loops_ts_fbs",
+        "//frc971/control_loops:profiled_subsystem_ts_fbs",
+    ],
+)
+
+flatbuffer_cc_library(
+    name = "superstructure_position_fbs",
+    srcs = [
+        "superstructure_position.fbs",
+    ],
+    gen_reflections = 1,
+    deps = [
+        "//frc971:can_configuration_fbs",
+        "//frc971/control_loops:can_falcon_fbs",
+        "//frc971/control_loops:control_loops_fbs",
+        "//frc971/control_loops:profiled_subsystem_fbs",
+    ],
+)
+
+cc_library(
+    name = "end_effector",
+    srcs = [
+        "end_effector.cc",
+    ],
+    hdrs = [
+        "end_effector.h",
+    ],
+    deps = [
+        ":superstructure_goal_fbs",
+        ":superstructure_status_fbs",
+        "//aos/events:event_loop",
+        "//aos/time",
+        "//frc971/control_loops:control_loop",
+        "//y2023_bot3:constants",
+    ],
+)
+
+cc_library(
+    name = "superstructure_lib",
+    srcs = [
+        "superstructure.cc",
+    ],
+    hdrs = [
+        "superstructure.h",
+    ],
+    data = [
+    ],
+    deps = [
+        ":end_effector",
+        ":superstructure_goal_fbs",
+        ":superstructure_output_fbs",
+        ":superstructure_position_fbs",
+        ":superstructure_status_fbs",
+        "//aos:flatbuffer_merge",
+        "//aos/events:event_loop",
+        "//frc971/constants:constants_sender_lib",
+        "//frc971/control_loops:control_loop",
+        "//frc971/control_loops/drivetrain:drivetrain_can_position_fbs",
+        "//frc971/control_loops/drivetrain:drivetrain_status_fbs",
+        "//frc971/shooter_interpolation:interpolation",
+        "//y2023_bot3:constants",
+        "//y2023_bot3/constants:constants_fbs",
+        "//y2023_bot3/constants:simulated_constants_sender",
+    ],
+)
+
+cc_binary(
+    name = "superstructure",
+    srcs = [
+        "superstructure_main.cc",
+    ],
+    deps = [
+        ":superstructure_lib",
+        "//aos:init",
+        "//aos/events:shm_event_loop",
+    ],
+)
+
+cc_test(
+    name = "superstructure_lib_test",
+    srcs = [
+        "superstructure_lib_test.cc",
+    ],
+    data = [
+        "//y2023_bot3:aos_config",
+    ],
+    deps = [
+        ":superstructure_goal_fbs",
+        ":superstructure_lib",
+        ":superstructure_output_fbs",
+        ":superstructure_position_fbs",
+        ":superstructure_status_fbs",
+        "//aos:json_to_flatbuffer",
+        "//aos:math",
+        "//aos/events/logging:log_writer",
+        "//aos/testing:googletest",
+        "//aos/time",
+        "//frc971/control_loops:capped_test_plant",
+        "//frc971/control_loops:control_loop_test",
+        "//frc971/control_loops:position_sensor_sim",
+        "//frc971/control_loops:subsystem_simulator",
+        "//frc971/control_loops:team_number_test_environment",
+        "//frc971/control_loops/drivetrain:drivetrain_status_fbs",
+    ],
+)
+
+cc_library(
+    name = "led_indicator_lib",
+    srcs = ["led_indicator.cc"],
+    hdrs = ["led_indicator.h"],
+    data = [
+        "@ctre_phoenix_api_cpp_athena//:shared_libraries",
+        "@ctre_phoenix_cci_athena//:shared_libraries",
+    ],
+    target_compatible_with = ["//tools/platforms/hardware:roborio"],
+    deps = [
+        ":superstructure_goal_fbs",
+        ":superstructure_output_fbs",
+        ":superstructure_position_fbs",
+        ":superstructure_status_fbs",
+        "//aos/events:event_loop",
+        "//aos/network:message_bridge_client_fbs",
+        "//aos/network:message_bridge_server_fbs",
+        "//frc971/control_loops:control_loop",
+        "//frc971/control_loops:control_loops_fbs",
+        "//frc971/control_loops:profiled_subsystem_fbs",
+        "//frc971/control_loops/drivetrain:drivetrain_output_fbs",
+        "//frc971/control_loops/drivetrain:drivetrain_status_fbs",
+        "//frc971/control_loops/drivetrain/localization:localizer_output_fbs",
+        "//frc971/queues:gyro_fbs",
+        "//third_party:phoenix",
+        "//third_party:wpilib",
+    ],
+)
+
+cc_binary(
+    name = "superstructure_replay",
+    srcs = ["superstructure_replay.cc"],
+    deps = [
+        ":superstructure_lib",
+        "//aos:configuration",
+        "//aos:init",
+        "//aos/events:simulated_event_loop",
+        "//aos/events/logging:log_reader",
+        "//aos/network:team_number",
+    ],
+)
diff --git a/y2023_bot3/control_loops/superstructure/end_effector.cc b/y2023_bot3/control_loops/superstructure/end_effector.cc
new file mode 100644
index 0000000..a98d06f
--- /dev/null
+++ b/y2023_bot3/control_loops/superstructure/end_effector.cc
@@ -0,0 +1,88 @@
+#include "y2023_bot3/control_loops/superstructure/end_effector.h"
+
+#include "aos/events/event_loop.h"
+#include "aos/time/time.h"
+#include "frc971/control_loops/control_loop.h"
+
+namespace y2023_bot3 {
+namespace control_loops {
+namespace superstructure {
+
+using ::aos::monotonic_clock;
+
+EndEffector::EndEffector()
+    : state_(EndEffectorState::IDLE), timer_(aos::monotonic_clock::min_time) {}
+
+void EndEffector::RunIteration(
+    const ::aos::monotonic_clock::time_point timestamp, RollerGoal roller_goal,
+    bool beambreak_status, double *roller_voltage, bool preloaded_with_cube) {
+  *roller_voltage = 0.0;
+
+  if (roller_goal == RollerGoal::SPIT) {
+    state_ = EndEffectorState::SPITTING;
+  }
+
+  if (roller_goal == RollerGoal::SPIT_MID) {
+    state_ = EndEffectorState::SPITTING_MID;
+  }
+
+  // If we started off preloaded, skip to the loaded state.
+  // Make sure we weren't already there just in case.
+  if (preloaded_with_cube) {
+    switch (state_) {
+      case EndEffectorState::IDLE:
+      case EndEffectorState::INTAKING:
+        state_ = EndEffectorState::LOADED;
+        break;
+      case EndEffectorState::LOADED:
+        break;
+      case EndEffectorState::SPITTING:
+        break;
+      case EndEffectorState::SPITTING_MID:
+        break;
+    }
+  }
+
+  switch (state_) {
+    case EndEffectorState::IDLE:
+      // If idle and intake requested, intake
+      if (roller_goal == RollerGoal::INTAKE_CUBE) {
+        state_ = EndEffectorState::INTAKING;
+        timer_ = timestamp;
+      }
+      break;
+    case EndEffectorState::INTAKING:
+      // If intaking and beam break is not triggered, keep intaking
+      if (roller_goal == RollerGoal::INTAKE_CUBE) {
+        timer_ = timestamp;
+      }
+
+      if (beambreak_status) {
+        // Beam has been broken, switch to loaded.
+        state_ = EndEffectorState::LOADED;
+        break;
+      } else if (timestamp > timer_ + constants::Values::kExtraIntakingTime()) {
+        // Intaking failed, waited 1 second with no beambreak
+        state_ = EndEffectorState::IDLE;
+        break;
+      }
+
+      *roller_voltage = kRollerCubeSuckVoltage();
+
+      break;
+    case EndEffectorState::LOADED:
+      timer_ = timestamp;
+      break;
+    case EndEffectorState::SPITTING:
+      *roller_voltage = kRollerCubeSpitVoltage();
+      break;
+    case EndEffectorState::SPITTING_MID:
+      *roller_voltage = kRollerCubeSpitMidVoltage();
+  }
+}
+
+void EndEffector::Reset() { state_ = EndEffectorState::IDLE; }
+
+}  // namespace superstructure
+}  // namespace control_loops
+}  // namespace y2023_bot3
\ No newline at end of file
diff --git a/y2023_bot3/control_loops/superstructure/end_effector.h b/y2023_bot3/control_loops/superstructure/end_effector.h
new file mode 100644
index 0000000..ab4e117
--- /dev/null
+++ b/y2023_bot3/control_loops/superstructure/end_effector.h
@@ -0,0 +1,39 @@
+#ifndef Y2023_BOT3_CONTROL_LOOPS_SUPERSTRUCTURE_END_EFFECTOR_H_
+#define Y2023_BOT3_CONTROL_LOOPS_SUPERSTRUCTURE_END_EFFECTOR_H_
+
+#include "aos/events/event_loop.h"
+#include "aos/time/time.h"
+#include "frc971/control_loops/control_loop.h"
+#include "y2023_bot3/constants.h"
+#include "y2023_bot3/control_loops/superstructure/superstructure_goal_generated.h"
+#include "y2023_bot3/control_loops/superstructure/superstructure_status_generated.h"
+
+namespace y2023_bot3 {
+namespace control_loops {
+namespace superstructure {
+
+class EndEffector {
+ public:
+  static constexpr double kRollerCubeSuckVoltage() { return -7.0; }
+  static constexpr double kRollerCubeSpitVoltage() { return 3.0; }
+  static constexpr double kRollerCubeSpitMidVoltage() { return 5.0; }
+
+  EndEffector();
+  void RunIteration(const ::aos::monotonic_clock::time_point timestamp,
+                    RollerGoal roller_goal, bool beambreak_status,
+                    double *intake_roller_voltage, bool preloaded_with_cube);
+  EndEffectorState state() const { return state_; }
+  void Reset();
+
+ private:
+  EndEffectorState state_;
+
+  // variable which records the last time at which "intake" button was pressed
+  aos::monotonic_clock::time_point timer_;
+};
+
+}  // namespace superstructure
+}  // namespace control_loops
+}  // namespace y2023_bot3
+
+#endif  // Y2023_CONTROL_LOOPS_SUPERSTRUCTURE_END_EFFECTOR_H_
diff --git a/y2023_bot3/control_loops/superstructure/led_indicator.cc b/y2023_bot3/control_loops/superstructure/led_indicator.cc
new file mode 100644
index 0000000..f0a9838
--- /dev/null
+++ b/y2023_bot3/control_loops/superstructure/led_indicator.cc
@@ -0,0 +1,142 @@
+#include "y2023_bot3/control_loops/superstructure/led_indicator.h"
+
+namespace led = ctre::phoenix::led;
+namespace chrono = std::chrono;
+
+namespace y2023_bot3::control_loops::superstructure {
+
+LedIndicator::LedIndicator(aos::EventLoop *event_loop)
+    : event_loop_(event_loop),
+      drivetrain_output_fetcher_(
+          event_loop_->MakeFetcher<frc971::control_loops::drivetrain::Output>(
+              "/drivetrain")),
+      superstructure_status_fetcher_(
+          event_loop_->MakeFetcher<Status>("/superstructure")),
+      superstructure_position_fetcher_(
+          event_loop_->MakeFetcher<Position>("/superstructure")),
+      superstructure_goal_fetcher_(
+          event_loop_->MakeFetcher<Goal>("/superstructure")),
+      server_statistics_fetcher_(
+          event_loop_->MakeFetcher<aos::message_bridge::ServerStatistics>(
+              "/roborio/aos")),
+      client_statistics_fetcher_(
+          event_loop_->MakeFetcher<aos::message_bridge::ClientStatistics>(
+              "/roborio/aos")),
+      localizer_output_fetcher_(
+          event_loop_->MakeFetcher<frc971::controls::LocalizerOutput>(
+              "/localizer")),
+      gyro_reading_fetcher_(
+          event_loop_->MakeFetcher<frc971::sensors::GyroReading>(
+              "/drivetrain")),
+      drivetrain_status_fetcher_(
+          event_loop_->MakeFetcher<frc971::control_loops::drivetrain::Status>(
+              "/drivetrain")) {
+  led::CANdleConfiguration config;
+  config.statusLedOffWhenActive = true;
+  config.disableWhenLOS = false;
+  config.brightnessScalar = 1.0;
+  candle_.ConfigAllSettings(config, 0);
+
+  event_loop_->AddPhasedLoop([this](int) { DecideColor(); },
+                             chrono::milliseconds(20));
+  event_loop_->OnRun(
+      [this]() { startup_time_ = event_loop_->monotonic_now(); });
+}
+
+// This method will be called once per scheduler run
+void LedIndicator::DisplayLed(uint8_t r, uint8_t g, uint8_t b) {
+  candle_.SetLEDs(static_cast<int>(r), static_cast<int>(g),
+                  static_cast<int>(b));
+}
+
+bool DisconnectedIMUPiServer(
+    const aos::message_bridge::ServerStatistics &server_statistics) {
+  for (const auto *node_status : *server_statistics.connections()) {
+    if (node_status->state() == aos::message_bridge::State::DISCONNECTED) {
+      return true;
+    }
+  }
+
+  return false;
+}
+
+bool DisconnectedIMUPiClient(
+    const aos::message_bridge::ClientStatistics &client_statistics) {
+  for (const auto *node_status : *client_statistics.connections()) {
+    if (node_status->state() == aos::message_bridge::State::DISCONNECTED) {
+      return true;
+    }
+  }
+
+  return false;
+}
+
+bool PisDisconnected(
+    const frc971::controls::LocalizerOutput &localizer_output) {
+  if (!localizer_output.all_pis_connected()) {
+    return true;
+  } else {
+    return false;
+  }
+}
+
+void LedIndicator::DecideColor() {
+  superstructure_status_fetcher_.Fetch();
+  superstructure_position_fetcher_.Fetch();
+  server_statistics_fetcher_.Fetch();
+  drivetrain_output_fetcher_.Fetch();
+  drivetrain_status_fetcher_.Fetch();
+  client_statistics_fetcher_.Fetch();
+  superstructure_goal_fetcher_.Fetch();
+  gyro_reading_fetcher_.Fetch();
+  localizer_output_fetcher_.Fetch();
+
+  // Estopped
+  if (superstructure_status_fetcher_.get() &&
+      superstructure_status_fetcher_->estopped()) {
+    DisplayLed(255, 0, 0);
+    return;
+  }
+
+  // Not zeroed
+  if (superstructure_status_fetcher_.get() &&
+      !superstructure_status_fetcher_->zeroed()) {
+    DisplayLed(255, 255, 0);
+    return;
+  }
+
+  // If the imu gyro readings are not being sent/updated recently.  Only do this
+  // after we've been on for a bit.
+  if (event_loop_->context().monotonic_event_time >
+          startup_time_ + chrono::seconds(5) &&
+      (!gyro_reading_fetcher_.get() ||
+       gyro_reading_fetcher_.context().monotonic_event_time +
+               frc971::controls::kLoopFrequency * 10 <
+           event_loop_->monotonic_now())) {
+    if (flash_counter_.Flash()) {
+      DisplayLed(255, 0, 0);
+    } else {
+      DisplayLed(255, 255, 255);
+    }
+    return;
+  }
+
+  if (localizer_output_fetcher_.get() == nullptr ||
+      server_statistics_fetcher_.get() == nullptr ||
+      client_statistics_fetcher_.get() == nullptr ||
+      PisDisconnected(*localizer_output_fetcher_) ||
+      DisconnectedIMUPiServer(*server_statistics_fetcher_) ||
+      DisconnectedIMUPiClient(*client_statistics_fetcher_)) {
+    if (flash_counter_.Flash()) {
+      DisplayLed(255, 0, 0);
+    } else {
+      DisplayLed(0, 255, 0);
+    }
+
+    return;
+  }
+
+  DisplayLed(0, 0, 0);
+}
+
+}  // namespace y2023_bot3::control_loops::superstructure
diff --git a/y2023_bot3/control_loops/superstructure/led_indicator.h b/y2023_bot3/control_loops/superstructure/led_indicator.h
new file mode 100644
index 0000000..d878bfe
--- /dev/null
+++ b/y2023_bot3/control_loops/superstructure/led_indicator.h
@@ -0,0 +1,80 @@
+#ifndef Y2023_CONTROL_LOOPS_SUPERSTRUCTURE_LED_INDICATOR_H_
+#define Y2023_CONTROL_LOOPS_SUPERSTRUCTURE_LED_INDICATOR_H_
+
+#include "ctre/phoenix/led/CANdle.h"
+
+#include "aos/events/event_loop.h"
+#include "aos/network/message_bridge_client_generated.h"
+#include "aos/network/message_bridge_server_generated.h"
+#include "frc971/control_loops/control_loop.h"
+#include "frc971/control_loops/control_loops_generated.h"
+#include "frc971/control_loops/drivetrain/drivetrain_output_generated.h"
+#include "frc971/control_loops/drivetrain/drivetrain_status_generated.h"
+#include "frc971/control_loops/drivetrain/localization/localizer_output_generated.h"
+#include "frc971/control_loops/profiled_subsystem_generated.h"
+#include "frc971/queues/gyro_generated.h"
+#include "y2023_bot3/control_loops/superstructure/superstructure_goal_generated.h"
+#include "y2023_bot3/control_loops/superstructure/superstructure_output_generated.h"
+#include "y2023_bot3/control_loops/superstructure/superstructure_position_generated.h"
+#include "y2023_bot3/control_loops/superstructure/superstructure_status_generated.h"
+
+namespace y2023_bot3::control_loops::superstructure {
+
+class FlashCounter {
+ public:
+  FlashCounter(size_t flash_iterations) : flash_iterations_(flash_iterations) {}
+
+  bool Flash() {
+    if (counter_ % flash_iterations_ == 0) {
+      flash_ = !flash_;
+    }
+    counter_++;
+    return flash_;
+  }
+
+ private:
+  size_t flash_iterations_;
+  size_t counter_ = 0;
+  bool flash_ = false;
+};
+
+class LedIndicator {
+ public:
+  LedIndicator(aos::EventLoop *event_loop);
+
+  void DecideColor();
+
+ private:
+  static constexpr size_t kFlashIterations = 5;
+
+  void DisplayLed(uint8_t r, uint8_t g, uint8_t b);
+
+  ctre::phoenix::led::CANdle candle_{8, "rio"};
+
+  aos::EventLoop *event_loop_;
+  aos::Fetcher<frc971::control_loops::drivetrain::Output>
+      drivetrain_output_fetcher_;
+  aos::Fetcher<Status> superstructure_status_fetcher_;
+  aos::Fetcher<Position> superstructure_position_fetcher_;
+  aos::Fetcher<Goal> superstructure_goal_fetcher_;
+  aos::Fetcher<aos::message_bridge::ServerStatistics>
+      server_statistics_fetcher_;
+  aos::Fetcher<aos::message_bridge::ClientStatistics>
+      client_statistics_fetcher_;
+  aos::Fetcher<frc971::controls::LocalizerOutput> localizer_output_fetcher_;
+  aos::Fetcher<frc971::sensors::GyroReading> gyro_reading_fetcher_;
+  aos::Fetcher<frc971::control_loops::drivetrain::Status>
+      drivetrain_status_fetcher_;
+
+  aos::monotonic_clock::time_point last_accepted_time_ =
+      aos::monotonic_clock::min_time;
+
+  aos::monotonic_clock::time_point startup_time_ =
+      aos::monotonic_clock::min_time;
+
+  FlashCounter flash_counter_{kFlashIterations};
+};
+
+}  // namespace y2023_bot3::control_loops::superstructure
+
+#endif  // Y2023_CONTROL_LOOPS_SUPERSTRUCTURE_LED_INDICATOR_H_
diff --git a/y2023_bot3/control_loops/superstructure/superstructure.cc b/y2023_bot3/control_loops/superstructure/superstructure.cc
new file mode 100644
index 0000000..e9df534
--- /dev/null
+++ b/y2023_bot3/control_loops/superstructure/superstructure.cc
@@ -0,0 +1,61 @@
+#include "y2023_bot3/control_loops/superstructure/superstructure.h"
+
+#include "aos/events/event_loop.h"
+#include "aos/flatbuffer_merge.h"
+#include "aos/network/team_number.h"
+#include "frc971/shooter_interpolation/interpolation.h"
+#include "frc971/zeroing/wrap.h"
+
+DEFINE_bool(ignore_distance, false,
+            "If true, ignore distance when shooting and obay joystick_reader");
+
+namespace y2023_bot3 {
+namespace control_loops {
+namespace superstructure {
+
+using ::aos::monotonic_clock;
+
+using frc971::control_loops::AbsoluteEncoderProfiledJointStatus;
+using frc971::control_loops::PotAndAbsoluteEncoderProfiledJointStatus;
+using frc971::control_loops::RelativeEncoderProfiledJointStatus;
+
+Superstructure::Superstructure(::aos::EventLoop *event_loop,
+                               std::shared_ptr<const constants::Values> values,
+                               const ::std::string &name)
+    : frc971::controls::ControlLoop<Goal, Position, Status, Output>(event_loop,
+                                                                    name),
+      values_(values),
+      constants_fetcher_(event_loop) {
+  event_loop->SetRuntimeRealtimePriority(30);
+}
+
+void Superstructure::RunIteration(const Goal *unsafe_goal,
+                                  const Position *position,
+                                  aos::Sender<Output>::Builder *output,
+                                  aos::Sender<Status>::Builder *status) {
+  (void)unsafe_goal;
+  (void)position;
+
+  const monotonic_clock::time_point timestamp =
+      event_loop()->context().monotonic_event_time;
+  (void)timestamp;
+
+  if (WasReset()) {
+    AOS_LOG(ERROR, "WPILib reset, restarting\n");
+  }
+
+  OutputT output_struct;
+
+  if (output) {
+    output->CheckOk(output->Send(Output::Pack(*output->fbb(), &output_struct)));
+  }
+
+  Status::Builder status_builder = status->MakeBuilder<Status>();
+  status_builder.add_zeroed(true);
+
+  (void)status->Send(status_builder.Finish());
+}
+
+}  // namespace superstructure
+}  // namespace control_loops
+}  // namespace y2023_bot3
diff --git a/y2023_bot3/control_loops/superstructure/superstructure.h b/y2023_bot3/control_loops/superstructure/superstructure.h
new file mode 100644
index 0000000..4b3d488
--- /dev/null
+++ b/y2023_bot3/control_loops/superstructure/superstructure.h
@@ -0,0 +1,56 @@
+#ifndef Y2023_CONTROL_LOOPS_SUPERSTRUCTURE_SUPERSTRUCTURE_H_
+#define Y2023_CONTROL_LOOPS_SUPERSTRUCTURE_SUPERSTRUCTURE_H_
+
+#include "aos/events/event_loop.h"
+#include "aos/json_to_flatbuffer.h"
+#include "frc971/constants/constants_sender_lib.h"
+#include "frc971/control_loops/control_loop.h"
+#include "frc971/control_loops/drivetrain/drivetrain_can_position_generated.h"
+#include "frc971/control_loops/drivetrain/drivetrain_status_generated.h"
+#include "y2023_bot3/constants.h"
+#include "y2023_bot3/constants/constants_generated.h"
+#include "y2023_bot3/control_loops/superstructure/end_effector.h"
+#include "y2023_bot3/control_loops/superstructure/superstructure_goal_generated.h"
+#include "y2023_bot3/control_loops/superstructure/superstructure_output_generated.h"
+#include "y2023_bot3/control_loops/superstructure/superstructure_position_generated.h"
+#include "y2023_bot3/control_loops/superstructure/superstructure_status_generated.h"
+
+namespace y2023_bot3 {
+namespace control_loops {
+namespace superstructure {
+
+class Superstructure
+    : public ::frc971::controls::ControlLoop<Goal, Position, Status, Output> {
+ public:
+  explicit Superstructure(::aos::EventLoop *event_loop,
+                          std::shared_ptr<const constants::Values> values,
+                          const ::std::string &name = "/superstructure");
+
+  double robot_velocity() const;
+
+  inline const EndEffector &end_effector() const { return end_effector_; }
+
+ protected:
+  virtual void RunIteration(const Goal *unsafe_goal, const Position *position,
+                            aos::Sender<Output>::Builder *output,
+                            aos::Sender<Status>::Builder *status) override;
+
+ private:
+  // Returns the Y coordinate of a game piece given the time-of-flight reading.
+  std::optional<double> LateralOffsetForTimeOfFlight(double reading);
+
+  std::shared_ptr<const constants::Values> values_;
+  frc971::constants::ConstantsFetcher<Constants> constants_fetcher_;
+
+  EndEffector end_effector_;
+
+  aos::Alliance alliance_ = aos::Alliance::kInvalid;
+
+  DISALLOW_COPY_AND_ASSIGN(Superstructure);
+};
+
+}  // namespace superstructure
+}  // namespace control_loops
+}  // namespace y2023_bot3
+
+#endif  // Y2023_CONTROL_LOOPS_SUPERSTRUCTURE_SUPERSTRUCTURE_H_
diff --git a/y2023_bot3/control_loops/superstructure/superstructure_goal.fbs b/y2023_bot3/control_loops/superstructure/superstructure_goal.fbs
new file mode 100644
index 0000000..5659e1f
--- /dev/null
+++ b/y2023_bot3/control_loops/superstructure/superstructure_goal.fbs
@@ -0,0 +1,28 @@
+include "frc971/control_loops/profiled_subsystem.fbs";
+
+namespace y2023_bot3.control_loops.superstructure;
+
+enum PivotGoal: ubyte {
+  PICKUP_FRONT = 0,
+  PICKUP_BACK = 1,
+  SCORE_LOW_FRONT = 2,
+  SCORE_LOW_BACK = 3,
+  SCORE_MID_FRONT = 4,
+  SCORE_MID_BACK = 5,
+}
+
+enum RollerGoal: ubyte {
+    IDLE = 0,
+    INTAKE_CUBE = 1,
+    SPIT = 2,
+    SPIT_MID = 3,
+}
+
+table Goal {
+    pivot_goal:PivotGoal (id: 0);
+    roller_goal:RollerGoal (id: 1);
+    preloaded_with_cube:bool (id: 2);
+}
+
+
+root_type Goal;
diff --git a/y2023_bot3/control_loops/superstructure/superstructure_lib_test.cc b/y2023_bot3/control_loops/superstructure/superstructure_lib_test.cc
new file mode 100644
index 0000000..24c4f2a
--- /dev/null
+++ b/y2023_bot3/control_loops/superstructure/superstructure_lib_test.cc
@@ -0,0 +1,248 @@
+#include <chrono>
+#include <memory>
+
+#include "gtest/gtest.h"
+
+#include "aos/events/logging/log_writer.h"
+#include "frc971/control_loops/capped_test_plant.h"
+#include "frc971/control_loops/control_loop_test.h"
+#include "frc971/control_loops/position_sensor_sim.h"
+#include "frc971/control_loops/subsystem_simulator.h"
+#include "frc971/control_loops/team_number_test_environment.h"
+#include "y2023_bot3/constants/simulated_constants_sender.h"
+#include "y2023_bot3/control_loops/drivetrain/drivetrain_dog_motor_plant.h"
+#include "y2023_bot3/control_loops/superstructure/superstructure.h"
+
+DEFINE_string(output_folder, "",
+              "If set, logs all channels to the provided logfile.");
+
+namespace y2023_bot3 {
+namespace control_loops {
+namespace superstructure {
+namespace testing {
+namespace {}  // namespace
+namespace chrono = std::chrono;
+
+using ::aos::monotonic_clock;
+using ::frc971::CreateProfileParameters;
+using ::frc971::control_loops::CappedTestPlant;
+using ::frc971::control_loops::
+    CreateStaticZeroingSingleDOFProfiledSubsystemGoal;
+using ::frc971::control_loops::PositionSensorSimulator;
+using ::frc971::control_loops::StaticZeroingSingleDOFProfiledSubsystemGoal;
+using DrivetrainStatus = ::frc971::control_loops::drivetrain::Status;
+
+// Class which simulates the superstructure and sends out queue messages with
+// the position.
+class SuperstructureSimulation {
+ public:
+  SuperstructureSimulation(::aos::EventLoop *event_loop,
+                           std::shared_ptr<const constants::Values> values,
+                           chrono::nanoseconds dt)
+      : event_loop_(event_loop),
+        superstructure_position_sender_(
+            event_loop_->MakeSender<Position>("/superstructure")),
+        superstructure_status_fetcher_(
+            event_loop_->MakeFetcher<Status>("/superstructure")),
+        superstructure_output_fetcher_(
+            event_loop_->MakeFetcher<Output>("/superstructure")) {
+    (void)values;
+    phased_loop_handle_ = event_loop_->AddPhasedLoop(
+        [this](int) {
+          // Skip this the first time.
+          if (!first_) {
+            EXPECT_TRUE(superstructure_output_fetcher_.Fetch());
+            EXPECT_TRUE(superstructure_status_fetcher_.Fetch());
+          }
+          first_ = false;
+          SendPositionMessage();
+        },
+        dt);
+  }
+
+  // Sends a queue message with the position of the superstructure.
+  void SendPositionMessage() {
+    ::aos::Sender<Position>::Builder builder =
+        superstructure_position_sender_.MakeBuilder();
+
+    Position::Builder position_builder = builder.MakeBuilder<Position>();
+    CHECK_EQ(builder.Send(position_builder.Finish()),
+             aos::RawSender::Error::kOk);
+  }
+
+ private:
+  ::aos::EventLoop *event_loop_;
+  ::aos::PhasedLoopHandler *phased_loop_handle_ = nullptr;
+
+  ::aos::Sender<Position> superstructure_position_sender_;
+  ::aos::Fetcher<Status> superstructure_status_fetcher_;
+  ::aos::Fetcher<Output> superstructure_output_fetcher_;
+
+  bool first_ = true;
+};
+
+class SuperstructureTest : public ::frc971::testing::ControlLoopTest {
+ public:
+  SuperstructureTest()
+      : ::frc971::testing::ControlLoopTest(
+            aos::configuration::ReadConfig("y2023_bot3/aos_config.json"),
+            std::chrono::microseconds(5050)),
+        values_(std::make_shared<constants::Values>(constants::MakeValues())),
+        simulated_constants_dummy_(SendSimulationConstants(
+            event_loop_factory(), 7971,
+            "y2023_bot3/constants/test_constants.json")),
+        roborio_(aos::configuration::GetNode(configuration(), "roborio")),
+        superstructure_event_loop(MakeEventLoop("Superstructure", roborio_)),
+        superstructure_(superstructure_event_loop.get(), values_),
+        test_event_loop_(MakeEventLoop("test", roborio_)),
+        superstructure_goal_fetcher_(
+            test_event_loop_->MakeFetcher<Goal>("/superstructure")),
+        superstructure_goal_sender_(
+            test_event_loop_->MakeSender<Goal>("/superstructure")),
+        superstructure_status_fetcher_(
+            test_event_loop_->MakeFetcher<Status>("/superstructure")),
+        superstructure_output_fetcher_(
+            test_event_loop_->MakeFetcher<Output>("/superstructure")),
+        superstructure_position_fetcher_(
+            test_event_loop_->MakeFetcher<Position>("/superstructure")),
+        superstructure_position_sender_(
+            test_event_loop_->MakeSender<Position>("/superstructure")),
+        drivetrain_status_sender_(
+            test_event_loop_->MakeSender<DrivetrainStatus>("/drivetrain")),
+        superstructure_plant_event_loop_(MakeEventLoop("plant", roborio_)),
+        superstructure_plant_(superstructure_plant_event_loop_.get(), values_,
+                              dt()) {
+    set_team_id(frc971::control_loops::testing::kTeamNumber);
+
+    SetEnabled(true);
+
+    if (!FLAGS_output_folder.empty()) {
+      unlink(FLAGS_output_folder.c_str());
+      logger_event_loop_ = MakeEventLoop("logger", roborio_);
+      logger_ = std::make_unique<aos::logger::Logger>(logger_event_loop_.get());
+      logger_->StartLoggingOnRun(FLAGS_output_folder);
+    }
+  }
+
+  void VerifyNearGoal() {
+    superstructure_goal_fetcher_.Fetch();
+    superstructure_status_fetcher_.Fetch();
+
+    ASSERT_TRUE(superstructure_goal_fetcher_.get() != nullptr) << ": No goal";
+    ASSERT_TRUE(superstructure_status_fetcher_.get() != nullptr)
+        << ": No status";
+  }
+
+  void CheckIfZeroed() {
+    superstructure_status_fetcher_.Fetch();
+    ASSERT_TRUE(superstructure_status_fetcher_.get()->zeroed());
+  }
+
+  void WaitUntilZeroed() {
+    int i = 0;
+    do {
+      i++;
+      RunFor(dt());
+      superstructure_status_fetcher_.Fetch();
+      // 2 Seconds
+      ASSERT_LE(i, 2.0 / ::aos::time::DurationInSeconds(dt()));
+
+      // Since there is a delay when sending running, make sure we have a
+      // status before checking it.
+    } while (superstructure_status_fetcher_.get() == nullptr ||
+             !superstructure_status_fetcher_.get()->zeroed());
+  }
+
+  void SendRobotVelocity(double robot_velocity) {
+    SendDrivetrainStatus(robot_velocity, {0.0, 0.0}, 0.0);
+  }
+
+  void SendDrivetrainStatus(double robot_velocity, Eigen::Vector2d pos,
+                            double theta) {
+    // Send a robot velocity to test compensation
+    auto builder = drivetrain_status_sender_.MakeBuilder();
+    auto drivetrain_status_builder = builder.MakeBuilder<DrivetrainStatus>();
+    drivetrain_status_builder.add_robot_speed(robot_velocity);
+    drivetrain_status_builder.add_estimated_left_velocity(robot_velocity);
+    drivetrain_status_builder.add_estimated_right_velocity(robot_velocity);
+    drivetrain_status_builder.add_x(pos.x());
+    drivetrain_status_builder.add_y(pos.y());
+    drivetrain_status_builder.add_theta(theta);
+    builder.CheckOk(builder.Send(drivetrain_status_builder.Finish()));
+  }
+
+  std::shared_ptr<const constants::Values> values_;
+  const bool simulated_constants_dummy_;
+
+  const aos::Node *const roborio_;
+
+  ::std::unique_ptr<::aos::EventLoop> superstructure_event_loop;
+  ::y2023_bot3::control_loops::superstructure::Superstructure superstructure_;
+  ::std::unique_ptr<::aos::EventLoop> test_event_loop_;
+  ::aos::PhasedLoopHandler *phased_loop_handle_ = nullptr;
+
+  ::aos::Fetcher<Goal> superstructure_goal_fetcher_;
+  ::aos::Sender<Goal> superstructure_goal_sender_;
+  ::aos::Fetcher<Status> superstructure_status_fetcher_;
+  ::aos::Fetcher<Output> superstructure_output_fetcher_;
+  ::aos::Fetcher<Position> superstructure_position_fetcher_;
+  ::aos::Sender<Position> superstructure_position_sender_;
+  ::aos::Sender<DrivetrainStatus> drivetrain_status_sender_;
+
+  ::std::unique_ptr<::aos::EventLoop> superstructure_plant_event_loop_;
+  SuperstructureSimulation superstructure_plant_;
+
+  std::unique_ptr<aos::EventLoop> logger_event_loop_;
+  std::unique_ptr<aos::logger::Logger> logger_;
+};
+
+// Makes sure that the voltage on a motor is properly pulled back after
+// saturation such that we don't get weird or bad (e.g. oscillating)
+// behaviour.
+TEST_F(SuperstructureTest, SaturationTest) {
+  SetEnabled(true);
+
+  // Zero it before we move.
+  WaitUntilZeroed();
+  {
+    auto builder = superstructure_goal_sender_.MakeBuilder();
+
+    Goal::Builder goal_builder = builder.MakeBuilder<Goal>();
+
+    ASSERT_EQ(builder.Send(goal_builder.Finish()), aos::RawSender::Error::kOk);
+  }
+  RunFor(chrono::seconds(20));
+  VerifyNearGoal();
+
+  // Try a low acceleration move with a high max velocity and verify the
+  // acceleration is capped like expected.
+  {
+    auto builder = superstructure_goal_sender_.MakeBuilder();
+
+    Goal::Builder goal_builder = builder.MakeBuilder<Goal>();
+
+    ASSERT_EQ(builder.Send(goal_builder.Finish()), aos::RawSender::Error::kOk);
+  }
+
+  // TODO(Milo): Make this a sane time
+  RunFor(chrono::seconds(20));
+  VerifyNearGoal();
+}
+
+// Tests that the loop zeroes when run for a while without a goal.
+TEST_F(SuperstructureTest, ZeroNoGoal) {
+  SetEnabled(true);
+  WaitUntilZeroed();
+  RunFor(chrono::seconds(2));
+}
+
+// Tests that running disabled works
+TEST_F(SuperstructureTest, DisableTest) {
+  RunFor(chrono::seconds(2));
+  CheckIfZeroed();
+}
+
+}  // namespace testing
+}  // namespace superstructure
+}  // namespace control_loops
+}  // namespace y2023_bot3
diff --git a/y2023_bot3/control_loops/superstructure/superstructure_main.cc b/y2023_bot3/control_loops/superstructure/superstructure_main.cc
new file mode 100644
index 0000000..1abfe88
--- /dev/null
+++ b/y2023_bot3/control_loops/superstructure/superstructure_main.cc
@@ -0,0 +1,25 @@
+#include "aos/events/shm_event_loop.h"
+#include "aos/init.h"
+#include "y2023_bot3/control_loops/superstructure/superstructure.h"
+
+using y2023_bot3::control_loops::superstructure::Superstructure;
+
+int main(int argc, char **argv) {
+  ::aos::InitGoogle(&argc, &argv);
+
+  aos::FlatbufferDetachedBuffer<aos::Configuration> config =
+      aos::configuration::ReadConfig("aos_config.json");
+
+  ::aos::ShmEventLoop event_loop(&config.message());
+
+  frc971::constants::WaitForConstants<y2023_bot3::Constants>(&config.message());
+
+  std::shared_ptr<const y2023_bot3::constants::Values> values =
+      std::make_shared<const y2023_bot3::constants::Values>(
+          y2023_bot3::constants::MakeValues());
+  Superstructure superstructure(&event_loop, values);
+
+  event_loop.Run();
+
+  return 0;
+}
diff --git a/y2023_bot3/control_loops/superstructure/superstructure_output.fbs b/y2023_bot3/control_loops/superstructure/superstructure_output.fbs
new file mode 100644
index 0000000..652d247
--- /dev/null
+++ b/y2023_bot3/control_loops/superstructure/superstructure_output.fbs
@@ -0,0 +1,10 @@
+namespace y2023_bot3.control_loops.superstructure;
+
+table Output {
+  // Pivot joint voltage
+  pivot_joint_voltage:double (id: 0);
+  // Roller voltage on the end effector (positive is spitting, negative is sucking)
+  roller_voltage:double (id: 1);
+}
+
+root_type Output;
diff --git a/y2023_bot3/control_loops/superstructure/superstructure_position.fbs b/y2023_bot3/control_loops/superstructure/superstructure_position.fbs
new file mode 100644
index 0000000..a629e56
--- /dev/null
+++ b/y2023_bot3/control_loops/superstructure/superstructure_position.fbs
@@ -0,0 +1,18 @@
+include "frc971/control_loops/control_loops.fbs";
+include "frc971/can_configuration.fbs";
+include "frc971/control_loops/can_falcon.fbs";
+
+namespace y2023_bot3.control_loops.superstructure;
+
+table Position {
+  // The position of the pivot joint in radians
+  pivot_joint_position:frc971.PotAndAbsolutePosition (id: 0);
+
+  // If this is true, the cube beam break is triggered.
+  end_effector_cube_beam_break:bool (id: 1);
+
+  // Roller falcon data
+  roller_falcon:frc971.control_loops.CANFalcon (id: 2);
+}
+
+root_type Position;
diff --git a/y2023_bot3/control_loops/superstructure/superstructure_replay.cc b/y2023_bot3/control_loops/superstructure/superstructure_replay.cc
new file mode 100644
index 0000000..ed39461
--- /dev/null
+++ b/y2023_bot3/control_loops/superstructure/superstructure_replay.cc
@@ -0,0 +1,74 @@
+// This binary allows us to replay the superstructure code over existing
+// logfile. When you run this code, it generates a new logfile with the data all
+// replayed, so that it can then be run through the plotting tool or analyzed
+// in some other way. The original superstructure status data will be on the
+// /original/superstructure channel.
+#include "gflags/gflags.h"
+
+#include "aos/events/logging/log_reader.h"
+#include "aos/events/logging/log_writer.h"
+#include "aos/events/simulated_event_loop.h"
+#include "aos/init.h"
+#include "aos/json_to_flatbuffer.h"
+#include "aos/logging/log_message_generated.h"
+#include "aos/network/team_number.h"
+#include "y2023_bot3/constants.h"
+#include "y2023_bot3/control_loops/superstructure/superstructure.h"
+
+DEFINE_int32(team, 971, "Team number to use for logfile replay.");
+DEFINE_string(output_folder, "/tmp/superstructure_replay/",
+              "Logs all channels to the provided logfile.");
+
+int main(int argc, char **argv) {
+  aos::InitGoogle(&argc, &argv);
+
+  aos::network::OverrideTeamNumber(FLAGS_team);
+
+  // open logfiles
+  aos::logger::LogReader reader(
+      aos::logger::SortParts(aos::logger::FindLogs(argc, argv)));
+  reader.RemapLoggedChannel("/superstructure",
+                            "y2023_bot3.control_loops.superstructure.Status");
+  reader.RemapLoggedChannel("/superstructure",
+                            "y2023_bot3.control_loops.superstructure.Output");
+
+  aos::SimulatedEventLoopFactory factory(reader.configuration());
+  reader.Register(&factory);
+
+  aos::NodeEventLoopFactory *roborio =
+      factory.GetNodeEventLoopFactory("roborio");
+
+  unlink(FLAGS_output_folder.c_str());
+  std::unique_ptr<aos::EventLoop> logger_event_loop =
+      roborio->MakeEventLoop("logger");
+  auto logger = std::make_unique<aos::logger::Logger>(logger_event_loop.get());
+  logger->StartLoggingOnRun(FLAGS_output_folder);
+
+  roborio->OnStartup([roborio]() {
+    roborio->AlwaysStart<
+        y2023_bot3::control_loops::superstructure::Superstructure>(
+        "superstructure", std::make_shared<y2023_bot3::constants::Values>(
+                              y2023_bot3::constants::MakeValues()));
+  });
+
+  std::unique_ptr<aos::EventLoop> print_loop = roborio->MakeEventLoop("print");
+  print_loop->SkipAosLog();
+  print_loop->MakeWatcher(
+      "/aos", [&print_loop](const aos::logging::LogMessageFbs &msg) {
+        LOG(INFO) << print_loop->context().monotonic_event_time << " "
+                  << aos::FlatbufferToJson(&msg);
+      });
+  print_loop->MakeWatcher(
+      "/superstructure",
+      [&](const y2023_bot3::control_loops::superstructure::Status &status) {
+        if (status.estopped()) {
+          LOG(ERROR) << "Estopped";
+        }
+      });
+
+  factory.Run();
+
+  reader.Deregister();
+
+  return 0;
+}
diff --git a/y2023_bot3/control_loops/superstructure/superstructure_status.fbs b/y2023_bot3/control_loops/superstructure/superstructure_status.fbs
new file mode 100644
index 0000000..f3df83c
--- /dev/null
+++ b/y2023_bot3/control_loops/superstructure/superstructure_status.fbs
@@ -0,0 +1,34 @@
+include "frc971/control_loops/control_loops.fbs";
+include "frc971/control_loops/profiled_subsystem.fbs";
+
+namespace y2023_bot3.control_loops.superstructure;
+
+enum EndEffectorState : ubyte {
+  // Not doing anything
+  IDLE = 0,
+  // Intaking the game object into the end effector
+  INTAKING = 1,
+  // The game object is loaded into the end effector
+  LOADED = 2,
+  // Waiting for the arm to be at shooting goal and then telling the
+  // end effector to spit
+  SPITTING = 3,
+  // Waiting for the arm to be at MID shooting goal and then telling the
+  // end effector to spit mid
+  SPITTING_MID = 4
+}
+
+table Status {
+  // All subsystems know their location.
+  zeroed:bool (id: 0);
+
+  // If true, we have aborted. This is the or of all subsystem estops.
+  estopped:bool (id: 1);
+
+  // The current state of the arm.
+  pivot_joint_state:frc971.control_loops.PotAndAbsoluteEncoderProfiledJointStatus (id: 2);
+
+  end_effector_state:EndEffectorState (id: 3);
+}
+
+root_type Status;
diff --git a/y2023_bot3/joystick_reader.cc b/y2023_bot3/joystick_reader.cc
new file mode 100644
index 0000000..2e874d3
--- /dev/null
+++ b/y2023_bot3/joystick_reader.cc
@@ -0,0 +1,109 @@
+#include <unistd.h>
+
+#include <cmath>
+#include <cstdio>
+#include <cstring>
+
+#include "aos/actions/actions.h"
+#include "aos/init.h"
+#include "aos/logging/logging.h"
+#include "aos/network/team_number.h"
+#include "aos/util/log_interval.h"
+#include "frc971/autonomous/base_autonomous_actor.h"
+#include "frc971/control_loops/drivetrain/localizer_generated.h"
+#include "frc971/control_loops/profiled_subsystem_generated.h"
+#include "frc971/input/action_joystick_input.h"
+#include "frc971/input/driver_station_data.h"
+#include "frc971/input/drivetrain_input.h"
+#include "frc971/input/joystick_input.h"
+#include "frc971/input/redundant_joystick_data.h"
+#include "frc971/zeroing/wrap.h"
+#include "y2023_bot3/constants.h"
+#include "y2023_bot3/control_loops/drivetrain/drivetrain_base.h"
+#include "y2023_bot3/control_loops/superstructure/superstructure_goal_generated.h"
+#include "y2023_bot3/control_loops/superstructure/superstructure_status_generated.h"
+
+using frc971::CreateProfileParameters;
+using frc971::control_loops::CreateStaticZeroingSingleDOFProfiledSubsystemGoal;
+using frc971::control_loops::StaticZeroingSingleDOFProfiledSubsystemGoal;
+using frc971::input::driver_station::ButtonLocation;
+using frc971::input::driver_station::ControlBit;
+using frc971::input::driver_station::JoystickAxis;
+using frc971::input::driver_station::POVLocation;
+using Side = frc971::control_loops::drivetrain::RobotSide;
+
+namespace y2023_bot3 {
+namespace input {
+namespace joysticks {
+
+namespace superstructure = y2023_bot3::control_loops::superstructure;
+
+struct ButtonData {
+  ButtonLocation button;
+};
+
+class Reader : public ::frc971::input::ActionJoystickInput {
+ public:
+  Reader(::aos::EventLoop *event_loop)
+      : ::frc971::input::ActionJoystickInput(
+            event_loop,
+            ::y2023_bot3::control_loops::drivetrain::GetDrivetrainConfig(),
+            ::frc971::input::DrivetrainInputReader::InputType::kPistol,
+            {.use_redundant_joysticks = true}),
+        superstructure_goal_sender_(
+            event_loop->MakeSender<superstructure::Goal>("/superstructure")),
+        superstructure_status_fetcher_(
+            event_loop->MakeFetcher<superstructure::Status>(
+                "/superstructure")) {}
+
+  void AutoEnded() override { AOS_LOG(INFO, "Auto ended.\n"); }
+
+  bool has_scored_ = false;
+
+  void HandleTeleop(
+      const ::frc971::input::driver_station::Data &data) override {
+    (void)data;
+    superstructure_status_fetcher_.Fetch();
+    if (!superstructure_status_fetcher_.get()) {
+      AOS_LOG(ERROR, "Got no superstructure status message.\n");
+      return;
+    }
+
+    std::optional<double> place_index = std::nullopt;
+    (void)place_index;
+
+    {
+      auto builder = superstructure_goal_sender_.MakeBuilder();
+
+      superstructure::Goal::Builder superstructure_goal_builder =
+          builder.MakeBuilder<superstructure::Goal>();
+      if (builder.Send(superstructure_goal_builder.Finish()) !=
+          aos::RawSender::Error::kOk) {
+        AOS_LOG(ERROR, "Sending superstructure goal failed.\n");
+      }
+    }
+  }
+
+ private:
+  ::aos::Sender<superstructure::Goal> superstructure_goal_sender_;
+
+  ::aos::Fetcher<superstructure::Status> superstructure_status_fetcher_;
+};
+
+}  // namespace joysticks
+}  // namespace input
+}  // namespace y2023_bot3
+
+int main(int argc, char **argv) {
+  ::aos::InitGoogle(&argc, &argv);
+
+  aos::FlatbufferDetachedBuffer<aos::Configuration> config =
+      aos::configuration::ReadConfig("aos_config.json");
+
+  ::aos::ShmEventLoop event_loop(&config.message());
+  ::y2023_bot3::input::joysticks::Reader reader(&event_loop);
+
+  event_loop.Run();
+
+  return 0;
+}
diff --git a/y2023_bot3/log_web_proxy.sh b/y2023_bot3/log_web_proxy.sh
new file mode 100755
index 0000000..a2c3acc
--- /dev/null
+++ b/y2023_bot3/log_web_proxy.sh
@@ -0,0 +1 @@
+./aos/network/log_web_proxy_main --data_dir=y2023_bot3/www $@
diff --git a/y2023_bot3/rockpi/BUILD b/y2023_bot3/rockpi/BUILD
new file mode 100644
index 0000000..f3a9d94
--- /dev/null
+++ b/y2023_bot3/rockpi/BUILD
@@ -0,0 +1,12 @@
+cc_binary(
+    name = "imu_main",
+    srcs = ["imu_main.cc"],
+    target_compatible_with = ["@platforms//os:linux"],
+    visibility = ["//visibility:public"],
+    deps = [
+        "//aos:init",
+        "//aos/events:shm_event_loop",
+        "//frc971/imu_reader:imu",
+        "//y2023_bot3:constants",
+    ],
+)
diff --git a/y2023_bot3/rockpi/imu_main.cc b/y2023_bot3/rockpi/imu_main.cc
new file mode 100644
index 0000000..cd443f2
--- /dev/null
+++ b/y2023_bot3/rockpi/imu_main.cc
@@ -0,0 +1,25 @@
+#include "aos/events/shm_event_loop.h"
+#include "aos/init.h"
+#include "aos/realtime.h"
+#include "frc971/imu_reader/imu.h"
+#include "y2023_bot3/constants.h"
+
+DEFINE_string(config, "aos_config.json", "Path to the config file to use.");
+
+int main(int argc, char *argv[]) {
+  aos::InitGoogle(&argc, &argv);
+
+  aos::FlatbufferDetachedBuffer<aos::Configuration> config =
+      aos::configuration::ReadConfig(FLAGS_config);
+
+  aos::ShmEventLoop event_loop(&config.message());
+  frc971::imu::Imu imu(
+      &event_loop, y2023_bot3::constants::Values::DrivetrainEncoderToMeters(1));
+
+  event_loop.SetRuntimeAffinity(aos::MakeCpusetFromCpus({0}));
+  event_loop.SetRuntimeRealtimePriority(55);
+
+  event_loop.Run();
+
+  return 0;
+}
diff --git a/y2023_bot3/wpilib_interface.cc b/y2023_bot3/wpilib_interface.cc
new file mode 100644
index 0000000..2b5a9f4
--- /dev/null
+++ b/y2023_bot3/wpilib_interface.cc
@@ -0,0 +1,784 @@
+#include <unistd.h>
+
+#include <array>
+#include <chrono>
+#include <cinttypes>
+#include <cmath>
+#include <cstdio>
+#include <cstring>
+#include <functional>
+#include <memory>
+#include <mutex>
+#include <thread>
+
+#include "ctre/phoenix/CANifier.h"
+
+#include "frc971/wpilib/ahal/AnalogInput.h"
+#include "frc971/wpilib/ahal/Counter.h"
+#include "frc971/wpilib/ahal/DigitalGlitchFilter.h"
+#include "frc971/wpilib/ahal/DriverStation.h"
+#include "frc971/wpilib/ahal/Encoder.h"
+#include "frc971/wpilib/ahal/Servo.h"
+#include "frc971/wpilib/ahal/TalonFX.h"
+#include "frc971/wpilib/ahal/VictorSP.h"
+#undef ERROR
+
+#include "ctre/phoenix/cci/Diagnostics_CCI.h"
+#include "ctre/phoenix/motorcontrol/can/TalonFX.h"
+#include "ctre/phoenix/motorcontrol/can/TalonSRX.h"
+#include "ctre/phoenix6/TalonFX.hpp"
+
+#include "aos/commonmath.h"
+#include "aos/containers/sized_array.h"
+#include "aos/events/event_loop.h"
+#include "aos/events/shm_event_loop.h"
+#include "aos/init.h"
+#include "aos/logging/logging.h"
+#include "aos/realtime.h"
+#include "aos/time/time.h"
+#include "aos/util/log_interval.h"
+#include "aos/util/phased_loop.h"
+#include "aos/util/wrapping_counter.h"
+#include "frc971/autonomous/auto_mode_generated.h"
+#include "frc971/can_configuration_generated.h"
+#include "frc971/control_loops/drivetrain/drivetrain_can_position_generated.h"
+#include "frc971/control_loops/drivetrain/drivetrain_position_generated.h"
+#include "frc971/input/robot_state_generated.h"
+#include "frc971/queues/gyro_generated.h"
+#include "frc971/wpilib/ADIS16448.h"
+#include "frc971/wpilib/buffered_pcm.h"
+#include "frc971/wpilib/buffered_solenoid.h"
+#include "frc971/wpilib/dma.h"
+#include "frc971/wpilib/drivetrain_writer.h"
+#include "frc971/wpilib/encoder_and_potentiometer.h"
+#include "frc971/wpilib/joystick_sender.h"
+#include "frc971/wpilib/logging_generated.h"
+#include "frc971/wpilib/loop_output_handler.h"
+#include "frc971/wpilib/pdp_fetcher.h"
+#include "frc971/wpilib/sensor_reader.h"
+#include "frc971/wpilib/wpilib_robot_base.h"
+#include "y2023_bot3/constants.h"
+#include "y2023_bot3/control_loops/superstructure/led_indicator.h"
+#include "y2023_bot3/control_loops/superstructure/superstructure_output_generated.h"
+#include "y2023_bot3/control_loops/superstructure/superstructure_position_generated.h"
+
+DEFINE_bool(ctre_diag_server, false,
+            "If true, enable the diagnostics server for interacting with "
+            "devices on the CAN bus using Phoenix Tuner");
+
+using ::aos::monotonic_clock;
+using ::y2023_bot3::constants::Values;
+namespace superstructure = ::y2023_bot3::control_loops::superstructure;
+namespace drivetrain = ::y2023_bot3::control_loops::drivetrain;
+namespace chrono = ::std::chrono;
+using std::make_unique;
+
+namespace y2023_bot3 {
+namespace wpilib {
+namespace {
+
+constexpr double kMaxBringupPower = 12.0;
+
+// TODO(Brian): Fix the interpretation of the result of GetRaw here and in the
+// DMA stuff and then removing the * 2.0 in *_translate.
+// The low bit is direction.
+
+double drivetrain_velocity_translate(double in) {
+  return (((1.0 / in) / Values::kDrivetrainCyclesPerRevolution()) *
+          (2.0 * M_PI)) *
+         Values::kDrivetrainEncoderRatio() *
+         control_loops::drivetrain::kWheelRadius;
+}
+
+constexpr double kMaxFastEncoderPulsesPerSecond = std::max({
+    Values::kMaxDrivetrainEncoderPulsesPerSecond(),
+});
+static_assert(kMaxFastEncoderPulsesPerSecond <= 1300000,
+              "fast encoders are too fast");
+
+}  // namespace
+
+static constexpr int kCANFalconCount = 6;
+static constexpr units::frequency::hertz_t kCANUpdateFreqHz = 200_Hz;
+
+class Falcon {
+ public:
+  Falcon(int device_id, std::string canbus,
+         std::vector<ctre::phoenix6::BaseStatusSignal *> *signals)
+      : talon_(device_id, canbus),
+        device_id_(device_id),
+        device_temp_(talon_.GetDeviceTemp()),
+        supply_voltage_(talon_.GetSupplyVoltage()),
+        supply_current_(talon_.GetSupplyCurrent()),
+        torque_current_(talon_.GetTorqueCurrent()),
+        position_(talon_.GetPosition()),
+        duty_cycle_(talon_.GetDutyCycle()) {
+    // device temp is not timesynced so don't add it to the list of signals
+    device_temp_.SetUpdateFrequency(kCANUpdateFreqHz);
+
+    CHECK_NOTNULL(signals);
+
+    supply_voltage_.SetUpdateFrequency(kCANUpdateFreqHz);
+    signals->push_back(&supply_voltage_);
+
+    supply_current_.SetUpdateFrequency(kCANUpdateFreqHz);
+    signals->push_back(&supply_current_);
+
+    torque_current_.SetUpdateFrequency(kCANUpdateFreqHz);
+    signals->push_back(&torque_current_);
+
+    position_.SetUpdateFrequency(kCANUpdateFreqHz);
+    signals->push_back(&position_);
+
+    duty_cycle_.SetUpdateFrequency(kCANUpdateFreqHz);
+    signals->push_back(&duty_cycle_);
+  }
+
+  void PrintConfigs() {
+    ctre::phoenix6::configs::TalonFXConfiguration configuration;
+    ctre::phoenix::StatusCode status =
+        talon_.GetConfigurator().Refresh(configuration);
+    if (!status.IsOK()) {
+      AOS_LOG(ERROR, "Failed to get falcon configuration: %s: %s",
+              status.GetName(), status.GetDescription());
+    }
+    AOS_LOG(INFO, "configuration: %s", configuration.ToString().c_str());
+  }
+
+  void WriteConfigs(ctre::phoenix6::signals::InvertedValue invert) {
+    inverted_ = invert;
+
+    ctre::phoenix6::configs::CurrentLimitsConfigs current_limits;
+    current_limits.StatorCurrentLimit =
+        constants::Values::kDrivetrainStatorCurrentLimit();
+    current_limits.StatorCurrentLimitEnable = true;
+    current_limits.SupplyCurrentLimit =
+        constants::Values::kDrivetrainSupplyCurrentLimit();
+    current_limits.SupplyCurrentLimitEnable = true;
+
+    ctre::phoenix6::configs::MotorOutputConfigs output_configs;
+    output_configs.NeutralMode =
+        ctre::phoenix6::signals::NeutralModeValue::Brake;
+    output_configs.DutyCycleNeutralDeadband = 0;
+
+    output_configs.Inverted = inverted_;
+
+    ctre::phoenix6::configs::TalonFXConfiguration configuration;
+    configuration.CurrentLimits = current_limits;
+    configuration.MotorOutput = output_configs;
+
+    ctre::phoenix::StatusCode status =
+        talon_.GetConfigurator().Apply(configuration);
+    if (!status.IsOK()) {
+      AOS_LOG(ERROR, "Failed to set falcon configuration: %s: %s",
+              status.GetName(), status.GetDescription());
+    }
+
+    PrintConfigs();
+  }
+
+  ctre::phoenix6::hardware::TalonFX *talon() { return &talon_; }
+
+  flatbuffers::Offset<frc971::control_loops::CANFalcon> WritePosition(
+      flatbuffers::FlatBufferBuilder *fbb) {
+    frc971::control_loops::CANFalcon::Builder builder(*fbb);
+    builder.add_id(device_id_);
+    builder.add_device_temp(device_temp());
+    builder.add_supply_voltage(supply_voltage());
+    builder.add_supply_current(supply_current());
+    builder.add_torque_current(torque_current());
+    builder.add_duty_cycle(duty_cycle());
+
+    double invert =
+        (inverted_ == ctre::phoenix6::signals::InvertedValue::Clockwise_Positive
+             ? 1
+             : -1);
+
+    builder.add_position(
+        constants::Values::DrivetrainCANEncoderToMeters(position()) * invert);
+
+    return builder.Finish();
+  }
+
+  int device_id() const { return device_id_; }
+  float device_temp() const { return device_temp_.GetValue().value(); }
+  float supply_voltage() const { return supply_voltage_.GetValue().value(); }
+  float supply_current() const { return supply_current_.GetValue().value(); }
+  float torque_current() const { return torque_current_.GetValue().value(); }
+  float duty_cycle() const { return duty_cycle_.GetValue().value(); }
+  float position() const { return position_.GetValue().value(); }
+
+  // returns the monotonic timestamp of the latest timesynced reading in the
+  // timebase of the the syncronized CAN bus clock.
+  int64_t GetTimestamp() {
+    std::chrono::nanoseconds latest_timestamp =
+        torque_current_.GetTimestamp().GetTime();
+
+    return latest_timestamp.count();
+  }
+
+  void RefreshNontimesyncedSignals() { device_temp_.Refresh(); };
+
+ private:
+  ctre::phoenix6::hardware::TalonFX talon_;
+  int device_id_;
+
+  ctre::phoenix6::signals::InvertedValue inverted_;
+
+  ctre::phoenix6::StatusSignal<units::temperature::celsius_t> device_temp_;
+  ctre::phoenix6::StatusSignal<units::voltage::volt_t> supply_voltage_;
+  ctre::phoenix6::StatusSignal<units::current::ampere_t> supply_current_,
+      torque_current_;
+  ctre::phoenix6::StatusSignal<units::angle::turn_t> position_;
+  ctre::phoenix6::StatusSignal<units::dimensionless::scalar_t> duty_cycle_;
+};
+
+class CANSensorReader {
+ public:
+  CANSensorReader(
+      aos::EventLoop *event_loop,
+      std::vector<ctre::phoenix6::BaseStatusSignal *> signals_registry)
+      : event_loop_(event_loop),
+        signals_(signals_registry.begin(), signals_registry.end()),
+        can_position_sender_(
+            event_loop
+                ->MakeSender<frc971::control_loops::drivetrain::CANPosition>(
+                    "/drivetrain")) {
+    event_loop->SetRuntimeRealtimePriority(40);
+    event_loop->SetRuntimeAffinity(aos::MakeCpusetFromCpus({1}));
+    timer_handler_ = event_loop->AddTimer([this]() { Loop(); });
+    timer_handler_->set_name("CANSensorReader Loop");
+
+    event_loop->OnRun([this]() {
+      timer_handler_->Schedule(event_loop_->monotonic_now(),
+                               1 / kCANUpdateFreqHz);
+    });
+  }
+
+  void set_falcons(std::shared_ptr<Falcon> right_front,
+                   std::shared_ptr<Falcon> right_back,
+                   std::shared_ptr<Falcon> left_front,
+                   std::shared_ptr<Falcon> left_back,
+                   std::shared_ptr<Falcon> roller_falcon) {
+    right_front_ = std::move(right_front);
+    right_back_ = std::move(right_back);
+    left_front_ = std::move(left_front);
+    left_back_ = std::move(left_back);
+    roller_falcon = std::move(roller_falcon);
+  }
+
+  std::optional<frc971::control_loops::CANFalconT> roller_falcon_data() {
+    std::unique_lock<aos::stl_mutex> lock(roller_mutex_);
+    return roller_falcon_data_;
+  }
+
+ private:
+  void Loop() {
+    ctre::phoenix::StatusCode status =
+        ctre::phoenix6::BaseStatusSignal::WaitForAll(2000_ms, signals_);
+
+    if (!status.IsOK()) {
+      AOS_LOG(ERROR, "Failed to read signals from falcons: %s: %s",
+              status.GetName(), status.GetDescription());
+    }
+
+    auto builder = can_position_sender_.MakeBuilder();
+
+    for (auto falcon : {right_front_, right_back_, left_front_, left_back_}) {
+      falcon->RefreshNontimesyncedSignals();
+    }
+
+    aos::SizedArray<flatbuffers::Offset<frc971::control_loops::CANFalcon>,
+                    kCANFalconCount>
+        falcons;
+
+    for (auto falcon : {right_front_, right_back_, left_front_, left_back_}) {
+      falcons.push_back(falcon->WritePosition(builder.fbb()));
+    }
+
+    auto falcons_list =
+        builder.fbb()
+            ->CreateVector<
+                flatbuffers::Offset<frc971::control_loops::CANFalcon>>(falcons);
+
+    frc971::control_loops::drivetrain::CANPosition::Builder
+        can_position_builder =
+            builder
+                .MakeBuilder<frc971::control_loops::drivetrain::CANPosition>();
+
+    can_position_builder.add_falcons(falcons_list);
+    can_position_builder.add_timestamp(right_front_->GetTimestamp());
+    can_position_builder.add_status(static_cast<int>(status));
+
+    builder.CheckOk(builder.Send(can_position_builder.Finish()));
+  }
+
+  aos::EventLoop *event_loop_;
+
+  const std::vector<ctre::phoenix6::BaseStatusSignal *> signals_;
+  aos::Sender<frc971::control_loops::drivetrain::CANPosition>
+      can_position_sender_;
+
+  std::shared_ptr<Falcon> right_front_, right_back_, left_front_, left_back_,
+      roller_falcon;
+
+  std::optional<frc971::control_loops::CANFalconT> roller_falcon_data_;
+
+  aos::stl_mutex roller_mutex_;
+
+  // Pointer to the timer handler used to modify the wakeup.
+  ::aos::TimerHandler *timer_handler_;
+};
+
+// Class to send position messages with sensor readings to our loops.
+class SensorReader : public ::frc971::wpilib::SensorReader {
+ public:
+  SensorReader(::aos::ShmEventLoop *event_loop,
+               std::shared_ptr<const Values> values,
+               CANSensorReader *can_sensor_reader)
+      : ::frc971::wpilib::SensorReader(event_loop),
+        values_(std::move(values)),
+        auto_mode_sender_(
+            event_loop->MakeSender<::frc971::autonomous::AutonomousMode>(
+                "/autonomous")),
+        superstructure_position_sender_(
+            event_loop->MakeSender<superstructure::Position>(
+                "/superstructure")),
+        drivetrain_position_sender_(
+            event_loop
+                ->MakeSender<::frc971::control_loops::drivetrain::Position>(
+                    "/drivetrain")),
+        gyro_sender_(event_loop->MakeSender<::frc971::sensors::GyroReading>(
+            "/drivetrain")),
+        can_sensor_reader_(can_sensor_reader) {
+    // Set to filter out anything shorter than 1/4 of the minimum pulse width
+    // we should ever see.
+    UpdateFastEncoderFilterHz(kMaxFastEncoderPulsesPerSecond);
+    event_loop->SetRuntimeAffinity(aos::MakeCpusetFromCpus({0}));
+  }
+
+  void Start() override { AddToDMA(&imu_yaw_rate_reader_); }
+
+  // Auto mode switches.
+  void set_autonomous_mode(int i, ::std::unique_ptr<frc::DigitalInput> sensor) {
+    autonomous_modes_.at(i) = ::std::move(sensor);
+  }
+
+  void set_yaw_rate_input(::std::unique_ptr<frc::DigitalInput> sensor) {
+    imu_yaw_rate_input_ = ::std::move(sensor);
+    imu_yaw_rate_reader_.set_input(imu_yaw_rate_input_.get());
+  }
+
+  void RunIteration() override {
+    superstructure_reading_->Set(true);
+    {
+      auto builder = superstructure_position_sender_.MakeBuilder();
+
+      flatbuffers::Offset<frc971::control_loops::CANFalcon>
+          roller_falcon_offset;
+      auto optional_roller_falcon = can_sensor_reader_->roller_falcon_data();
+      if (optional_roller_falcon.has_value()) {
+        roller_falcon_offset = frc971::control_loops::CANFalcon::Pack(
+            *builder.fbb(), &optional_roller_falcon.value());
+      }
+
+      superstructure::Position::Builder position_builder =
+          builder.MakeBuilder<superstructure::Position>();
+      position_builder.add_end_effector_cube_beam_break(
+          end_effector_cube_beam_break_->Get());
+
+      if (!roller_falcon_offset.IsNull()) {
+        position_builder.add_roller_falcon(roller_falcon_offset);
+      }
+      builder.CheckOk(builder.Send(position_builder.Finish()));
+    }
+
+    {
+      auto builder = drivetrain_position_sender_.MakeBuilder();
+      frc971::control_loops::drivetrain::Position::Builder drivetrain_builder =
+          builder.MakeBuilder<frc971::control_loops::drivetrain::Position>();
+      drivetrain_builder.add_left_encoder(
+          constants::Values::DrivetrainEncoderToMeters(
+              drivetrain_left_encoder_->GetRaw()));
+      drivetrain_builder.add_left_speed(
+          drivetrain_velocity_translate(drivetrain_left_encoder_->GetPeriod()));
+
+      drivetrain_builder.add_right_encoder(
+          -constants::Values::DrivetrainEncoderToMeters(
+              drivetrain_right_encoder_->GetRaw()));
+      drivetrain_builder.add_right_speed(-drivetrain_velocity_translate(
+          drivetrain_right_encoder_->GetPeriod()));
+
+      builder.CheckOk(builder.Send(drivetrain_builder.Finish()));
+    }
+
+    {
+      auto builder = gyro_sender_.MakeBuilder();
+      ::frc971::sensors::GyroReading::Builder gyro_reading_builder =
+          builder.MakeBuilder<::frc971::sensors::GyroReading>();
+      // +/- 2000 deg / sec
+      constexpr double kMaxVelocity = 4000;  // degrees / second
+      constexpr double kVelocityRadiansPerSecond =
+          kMaxVelocity / 360 * (2.0 * M_PI);
+
+      // Only part of the full range is used to prevent being 100% on or off.
+      constexpr double kScaledRangeLow = 0.1;
+      constexpr double kScaledRangeHigh = 0.9;
+
+      constexpr double kPWMFrequencyHz = 200;
+      double velocity_duty_cycle =
+          imu_yaw_rate_reader_.last_width() * kPWMFrequencyHz;
+
+      constexpr double kDutyCycleScale =
+          1 / (kScaledRangeHigh - kScaledRangeLow);
+      // scale from 0.1 - 0.9 to 0 - 1
+      double rescaled_velocity_duty_cycle =
+          (velocity_duty_cycle - kScaledRangeLow) * kDutyCycleScale;
+
+      if (!std::isnan(rescaled_velocity_duty_cycle)) {
+        gyro_reading_builder.add_velocity((rescaled_velocity_duty_cycle - 0.5) *
+                                          kVelocityRadiansPerSecond);
+      }
+      builder.CheckOk(builder.Send(gyro_reading_builder.Finish()));
+    }
+
+    {
+      auto builder = auto_mode_sender_.MakeBuilder();
+
+      uint32_t mode = 0;
+      for (size_t i = 0; i < autonomous_modes_.size(); ++i) {
+        if (autonomous_modes_[i] && autonomous_modes_[i]->Get()) {
+          mode |= 1 << i;
+        }
+      }
+
+      auto auto_mode_builder =
+          builder.MakeBuilder<frc971::autonomous::AutonomousMode>();
+
+      auto_mode_builder.add_mode(mode);
+
+      builder.CheckOk(builder.Send(auto_mode_builder.Finish()));
+    }
+  }
+
+  std::shared_ptr<frc::DigitalOutput> superstructure_reading_;
+
+  void set_superstructure_reading(
+      std::shared_ptr<frc::DigitalOutput> superstructure_reading) {
+    superstructure_reading_ = superstructure_reading;
+  }
+
+ private:
+  std::shared_ptr<const Values> values_;
+
+  aos::Sender<frc971::autonomous::AutonomousMode> auto_mode_sender_;
+  aos::Sender<superstructure::Position> superstructure_position_sender_;
+  aos::Sender<frc971::control_loops::drivetrain::Position>
+      drivetrain_position_sender_;
+  ::aos::Sender<::frc971::sensors::GyroReading> gyro_sender_;
+
+  std::array<std::unique_ptr<frc::DigitalInput>, 2> autonomous_modes_;
+
+  std::unique_ptr<frc::DigitalInput> imu_yaw_rate_input_,
+      end_effector_cube_beam_break_;
+
+  frc971::wpilib::DMAPulseWidthReader imu_yaw_rate_reader_;
+
+  CANSensorReader *can_sensor_reader_;
+};
+
+class SuperstructureWriter
+    : public ::frc971::wpilib::LoopOutputHandler<superstructure::Output> {
+ public:
+  SuperstructureWriter(aos::EventLoop *event_loop)
+      : frc971::wpilib::LoopOutputHandler<superstructure::Output>(
+            event_loop, "/superstructure") {
+    event_loop->SetRuntimeRealtimePriority(
+        constants::Values::kDrivetrainWriterPriority);
+  }
+
+  std::shared_ptr<frc::DigitalOutput> superstructure_reading_;
+
+  void set_superstructure_reading(
+      std::shared_ptr<frc::DigitalOutput> superstructure_reading) {
+    superstructure_reading_ = superstructure_reading;
+  }
+
+ private:
+  void Stop() override { AOS_LOG(WARNING, "Superstructure output too old.\n"); }
+
+  void Write(const superstructure::Output &output) override { (void)output; }
+
+  static void WriteCan(const double voltage,
+                       ::ctre::phoenix::motorcontrol::can::TalonFX *falcon) {
+    falcon->Set(
+        ctre::phoenix::motorcontrol::ControlMode::PercentOutput,
+        std::clamp(voltage, -kMaxBringupPower, kMaxBringupPower) / 12.0);
+  }
+
+  template <typename T>
+  static void WritePwm(const double voltage, T *motor) {
+    motor->SetSpeed(std::clamp(voltage, -kMaxBringupPower, kMaxBringupPower) /
+                    12.0);
+  }
+};
+
+class SuperstructureCANWriter
+    : public ::frc971::wpilib::LoopOutputHandler<superstructure::Output> {
+ public:
+  SuperstructureCANWriter(::aos::EventLoop *event_loop)
+      : ::frc971::wpilib::LoopOutputHandler<superstructure::Output>(
+            event_loop, "/superstructure") {
+    event_loop->SetRuntimeRealtimePriority(
+        constants::Values::kSuperstructureCANWriterPriority);
+
+    event_loop->OnRun([this]() { WriteConfigs(); });
+  };
+
+  void HandleCANConfiguration(const frc971::CANConfiguration &configuration) {
+    if (configuration.reapply()) {
+      WriteConfigs();
+    }
+  }
+
+ private:
+  void WriteConfigs() {}
+
+  void Write(const superstructure::Output &output) override { (void)output; }
+
+  void Stop() override {
+    AOS_LOG(WARNING, "Superstructure CAN output too old.\n");
+    ctre::phoenix6::controls::DutyCycleOut stop_command(0.0);
+    stop_command.UpdateFreqHz = 0_Hz;
+    stop_command.EnableFOC = true;
+  }
+
+  double SafeSpeed(double voltage) {
+    return (::aos::Clip(voltage, -kMaxBringupPower, kMaxBringupPower) / 12.0);
+  }
+};
+
+class DrivetrainWriter : public ::frc971::wpilib::LoopOutputHandler<
+                             ::frc971::control_loops::drivetrain::Output> {
+ public:
+  DrivetrainWriter(::aos::EventLoop *event_loop)
+      : ::frc971::wpilib::LoopOutputHandler<
+            ::frc971::control_loops::drivetrain::Output>(event_loop,
+                                                         "/drivetrain") {
+    event_loop->SetRuntimeRealtimePriority(
+        constants::Values::kDrivetrainWriterPriority);
+
+    event_loop->OnRun([this]() { WriteConfigs(); });
+  }
+
+  void set_falcons(std::shared_ptr<Falcon> right_front,
+                   std::shared_ptr<Falcon> right_back,
+                   std::shared_ptr<Falcon> left_front,
+                   std::shared_ptr<Falcon> left_back) {
+    right_front_ = std::move(right_front);
+    right_back_ = std::move(right_back);
+    left_front_ = std::move(left_front);
+    left_back_ = std::move(left_back);
+  }
+
+  void set_right_inverted(ctre::phoenix6::signals::InvertedValue invert) {
+    right_inverted_ = invert;
+  }
+
+  void set_left_inverted(ctre::phoenix6::signals::InvertedValue invert) {
+    left_inverted_ = invert;
+  }
+
+  void HandleCANConfiguration(const frc971::CANConfiguration &configuration) {
+    for (auto falcon : {right_front_, right_back_, left_front_, left_back_}) {
+      falcon->PrintConfigs();
+    }
+    if (configuration.reapply()) {
+      WriteConfigs();
+    }
+  }
+
+ private:
+  void WriteConfigs() {
+    for (auto falcon : {right_front_.get(), right_back_.get()}) {
+      falcon->WriteConfigs(right_inverted_);
+    }
+
+    for (auto falcon : {left_front_.get(), left_back_.get()}) {
+      falcon->WriteConfigs(left_inverted_);
+    }
+  }
+
+  void Write(
+      const ::frc971::control_loops::drivetrain::Output &output) override {
+    ctre::phoenix6::controls::DutyCycleOut left_control(
+        SafeSpeed(output.left_voltage()));
+    left_control.UpdateFreqHz = 0_Hz;
+    left_control.EnableFOC = true;
+
+    ctre::phoenix6::controls::DutyCycleOut right_control(
+        SafeSpeed(output.right_voltage()));
+    right_control.UpdateFreqHz = 0_Hz;
+    right_control.EnableFOC = true;
+
+    for (auto falcon : {left_front_.get(), left_back_.get()}) {
+      ctre::phoenix::StatusCode status =
+          falcon->talon()->SetControl(left_control);
+
+      if (!status.IsOK()) {
+        AOS_LOG(ERROR, "Failed to write control to falcon: %s: %s",
+                status.GetName(), status.GetDescription());
+      }
+    }
+
+    for (auto falcon : {right_front_.get(), right_back_.get()}) {
+      ctre::phoenix::StatusCode status =
+          falcon->talon()->SetControl(right_control);
+
+      if (!status.IsOK()) {
+        AOS_LOG(ERROR, "Failed to write control to falcon: %s: %s",
+                status.GetName(), status.GetDescription());
+      }
+    }
+  }
+
+  void Stop() override {
+    AOS_LOG(WARNING, "drivetrain output too old\n");
+    ctre::phoenix6::controls::DutyCycleOut stop_command(0.0);
+    stop_command.UpdateFreqHz = 0_Hz;
+    stop_command.EnableFOC = true;
+
+    for (auto falcon : {right_front_.get(), right_back_.get(),
+                        left_front_.get(), left_back_.get()}) {
+      falcon->talon()->SetControl(stop_command);
+    }
+  }
+
+  double SafeSpeed(double voltage) {
+    return (::aos::Clip(voltage, -kMaxBringupPower, kMaxBringupPower) / 12.0);
+  }
+
+  ctre::phoenix6::signals::InvertedValue left_inverted_, right_inverted_;
+  std::shared_ptr<Falcon> right_front_, right_back_, left_front_, left_back_;
+};
+
+class WPILibRobot : public ::frc971::wpilib::WPILibRobotBase {
+ public:
+  ::std::unique_ptr<frc::Encoder> make_encoder(int index) {
+    return make_unique<frc::Encoder>(10 + index * 2, 11 + index * 2, false,
+                                     frc::Encoder::k4X);
+  }
+
+  void Run() override {
+    std::shared_ptr<const Values> values =
+        std::make_shared<const Values>(constants::MakeValues());
+
+    aos::FlatbufferDetachedBuffer<aos::Configuration> config =
+        aos::configuration::ReadConfig("aos_config.json");
+
+    // Thread 1.
+    ::aos::ShmEventLoop joystick_sender_event_loop(&config.message());
+    ::frc971::wpilib::JoystickSender joystick_sender(
+        &joystick_sender_event_loop);
+    AddLoop(&joystick_sender_event_loop);
+
+    // Thread 2.
+    ::aos::ShmEventLoop pdp_fetcher_event_loop(&config.message());
+    ::frc971::wpilib::PDPFetcher pdp_fetcher(&pdp_fetcher_event_loop);
+    AddLoop(&pdp_fetcher_event_loop);
+
+    std::shared_ptr<frc::DigitalOutput> superstructure_reading =
+        make_unique<frc::DigitalOutput>(25);
+
+    std::vector<ctre::phoenix6::BaseStatusSignal *> signals_registry;
+    std::shared_ptr<Falcon> right_front =
+        std::make_shared<Falcon>(1, "Drivetrain Bus", &signals_registry);
+    std::shared_ptr<Falcon> right_back =
+        std::make_shared<Falcon>(2, "Drivetrain Bus", &signals_registry);
+    std::shared_ptr<Falcon> left_front =
+        std::make_shared<Falcon>(4, "Drivetrain Bus", &signals_registry);
+    std::shared_ptr<Falcon> left_back =
+        std::make_shared<Falcon>(5, "Drivetrain Bus", &signals_registry);
+    std::shared_ptr<Falcon> roller_falcon =
+        std::make_shared<Falcon>(13, "Drivetrain Bus", &signals_registry);
+
+    // Thread 3.
+    ::aos::ShmEventLoop can_sensor_reader_event_loop(&config.message());
+    can_sensor_reader_event_loop.set_name("CANSensorReader");
+    CANSensorReader can_sensor_reader(&can_sensor_reader_event_loop,
+                                      std::move(signals_registry));
+
+    can_sensor_reader.set_falcons(right_front, right_back, left_front,
+                                  left_back, roller_falcon);
+
+    AddLoop(&can_sensor_reader_event_loop);
+
+    // Thread 4.
+    ::aos::ShmEventLoop sensor_reader_event_loop(&config.message());
+    SensorReader sensor_reader(&sensor_reader_event_loop, values,
+                               &can_sensor_reader);
+    sensor_reader.set_pwm_trigger(true);
+    sensor_reader.set_drivetrain_left_encoder(make_encoder(1));
+    sensor_reader.set_drivetrain_right_encoder(make_encoder(0));
+    sensor_reader.set_superstructure_reading(superstructure_reading);
+    sensor_reader.set_yaw_rate_input(make_unique<frc::DigitalInput>(0));
+
+    AddLoop(&sensor_reader_event_loop);
+
+    // Thread 5.
+    // Set up CAN.
+    if (!FLAGS_ctre_diag_server) {
+      c_Phoenix_Diagnostics_SetSecondsToStart(-1);
+      c_Phoenix_Diagnostics_Dispose();
+    }
+
+    ctre::phoenix::platform::can::CANComm_SetRxSchedPriority(
+        constants::Values::kDrivetrainRxPriority, true, "Drivetrain Bus");
+    ctre::phoenix::platform::can::CANComm_SetTxSchedPriority(
+        constants::Values::kDrivetrainTxPriority, true, "Drivetrain Bus");
+
+    ::aos::ShmEventLoop can_output_event_loop(&config.message());
+    can_output_event_loop.set_name("CANOutputWriter");
+    DrivetrainWriter drivetrain_writer(&can_output_event_loop);
+
+    drivetrain_writer.set_falcons(right_front, right_back, left_front,
+                                  left_back);
+    drivetrain_writer.set_right_inverted(
+        ctre::phoenix6::signals::InvertedValue::Clockwise_Positive);
+    drivetrain_writer.set_left_inverted(
+        ctre::phoenix6::signals::InvertedValue::CounterClockwise_Positive);
+
+    can_output_event_loop.MakeWatcher(
+        "/roborio",
+        [&drivetrain_writer](const frc971::CANConfiguration &configuration) {
+          drivetrain_writer.HandleCANConfiguration(configuration);
+        });
+
+    AddLoop(&can_output_event_loop);
+
+    // Thread 6
+    // Set up superstructure output.
+    ::aos::ShmEventLoop output_event_loop(&config.message());
+    output_event_loop.set_name("PWMOutputWriter");
+    SuperstructureWriter superstructure_writer(&output_event_loop);
+
+    superstructure_writer.set_superstructure_reading(superstructure_reading);
+
+    AddLoop(&output_event_loop);
+
+    // Thread 7
+    // Set up led_indicator.
+    ::aos::ShmEventLoop led_indicator_event_loop(&config.message());
+    led_indicator_event_loop.set_name("LedIndicator");
+    control_loops::superstructure::LedIndicator led_indicator(
+        &led_indicator_event_loop);
+    AddLoop(&led_indicator_event_loop);
+
+    RunLoops();
+  }
+};
+
+}  // namespace wpilib
+}  // namespace y2023_bot3
+
+AOS_ROBOT_CLASS(::y2023_bot3::wpilib::WPILibRobot);
diff --git a/y2023_bot3/www/BUILD b/y2023_bot3/www/BUILD
new file mode 100644
index 0000000..50367ba
--- /dev/null
+++ b/y2023_bot3/www/BUILD
@@ -0,0 +1,22 @@
+load("//frc971/downloader:downloader.bzl", "aos_downloader_dir")
+
+filegroup(
+    name = "files",
+    srcs = glob([
+        "**/*.html",
+        "**/*.css",
+        "**/*.png",
+    ]),
+    visibility = ["//visibility:public"],
+)
+
+aos_downloader_dir(
+    name = "www_files",
+    srcs = [
+        ":files",
+        "//frc971/analysis:plot_index_bundle.min.js",
+    ],
+    dir = "www",
+    target_compatible_with = ["@platforms//os:linux"],
+    visibility = ["//visibility:public"],
+)
diff --git a/y2023_bot3/www/constants.ts b/y2023_bot3/www/constants.ts
new file mode 100644
index 0000000..d6ecfaf
--- /dev/null
+++ b/y2023_bot3/www/constants.ts
@@ -0,0 +1,8 @@
+// Conversion constants to meters
+export const IN_TO_M = 0.0254;
+export const FT_TO_M = 0.3048;
+// Dimensions of the field in meters
+// Numbers are slightly hand-tuned to match the PNG that we are using.
+export const FIELD_WIDTH = 26 * FT_TO_M + 7.25 * IN_TO_M;
+export const FIELD_LENGTH = 54 * FT_TO_M + 5.25 * IN_TO_M;
+
diff --git a/y2023_bot3/www/field.html b/y2023_bot3/www/field.html
new file mode 100644
index 0000000..ee512d3
--- /dev/null
+++ b/y2023_bot3/www/field.html
@@ -0,0 +1,63 @@
+<html>
+  <head>
+    <script src="field_main_bundle.min.js" defer></script>
+    <link rel="stylesheet" href="styles.css">
+  </head>
+  <body>
+    <div id="field"> </div>
+    <div id="legend"> </div>
+    <div id="readouts">
+      <table>
+        <tr>
+          <th colspan="2">Robot State</th>
+        </tr>
+        <tr>
+          <td>X</td>
+          <td id="x"> NA </td>
+        </tr>
+        <tr>
+          <td>Y</td>
+          <td id="y"> NA </td>
+        </tr>
+        <tr>
+          <td>Theta</td>
+          <td id="theta"> NA </td>
+        </tr>
+      </table>
+
+      <table>
+        <tr>
+          <th colspan="2">Superstructure</th>
+        </tr>
+  </table>
+  <table>
+    <tr>
+      <th colspan="2">Game Piece</th>
+    </tr>
+    <tr>
+      <td>Game Piece Held</td>
+      <td id="game_piece"> NA </td>
+    </tr>
+    <tr>
+      <td>Game Piece Position (+ = left, 0 = empty)</td>
+      <td id="game_piece_position"> NA </td>
+    </tr>
+  </table>
+
+  <h3>Zeroing Faults:</h3>
+  <p id="zeroing_faults"> NA </p>
+  </div>
+  <div id="middle_readouts">
+    <div id="vision_readouts">
+    </div>
+    <div id="message_bridge_status">
+      <div>
+        <div>Node</div>
+        <div>Client</div>
+        <div>Server</div>
+      </div>
+    </div>
+  </div>
+  </body>
+</html>
+
diff --git a/y2023_bot3/www/field_handler.ts b/y2023_bot3/www/field_handler.ts
new file mode 100644
index 0000000..809bc5d
--- /dev/null
+++ b/y2023_bot3/www/field_handler.ts
@@ -0,0 +1,185 @@
+import {ByteBuffer} from 'flatbuffers'
+import {ClientStatistics} from '../../aos/network/message_bridge_client_generated'
+import {ServerStatistics, State as ConnectionState} from '../../aos/network/message_bridge_server_generated'
+import {Connection} from '../../aos/network/www/proxy'
+import {ZeroingError} from '../../frc971/control_loops/control_loops_generated'
+import {Status as DrivetrainStatus} from '../../frc971/control_loops/drivetrain/drivetrain_status_generated'
+import {LocalizerOutput} from '../../frc971/control_loops/drivetrain/localization/localizer_output_generated'
+
+import {FIELD_LENGTH, FIELD_WIDTH, FT_TO_M, IN_TO_M} from './constants';
+
+// (0,0) is field center, +X is toward red DS
+const FIELD_SIDE_Y = FIELD_WIDTH / 2;
+const FIELD_EDGE_X = FIELD_LENGTH / 2;
+
+const ROBOT_WIDTH = 25 * IN_TO_M;
+const ROBOT_LENGTH = 32 * IN_TO_M;
+
+export class FieldHandler {
+  private canvas = document.createElement('canvas');
+  private localizerOutput: LocalizerOutput|null = null;
+  private drivetrainStatus: DrivetrainStatus|null = null;
+
+  private handleDrivetrainStatus(data: Uint8Array): void {
+    const fbBuffer = new ByteBuffer(data);
+    this.drivetrainStatus = DrivetrainStatus.getRootAsStatus(fbBuffer);
+  }
+
+  private setCurrentNodeState(element: HTMLElement, state: ConnectionState):
+      void {
+    if (state === ConnectionState.CONNECTED) {
+      element.innerHTML = ConnectionState[state];
+      element.classList.remove('faulted');
+      element.classList.add('connected');
+    } else {
+      element.innerHTML = ConnectionState[state];
+      element.classList.remove('connected');
+      element.classList.add('faulted');
+    }
+  }
+
+  private handleServerStatistics(data: Uint8Array): void {
+    const fbBuffer = new ByteBuffer(data);
+    const serverStatistics =
+        ServerStatistics.getRootAsServerStatistics(fbBuffer);
+
+    for (let ii = 0; ii < serverStatistics.connectionsLength(); ++ii) {
+      const connection = serverStatistics.connections(ii);
+      const nodeName = connection.node().name();
+      if (!this.serverStatuses.has(nodeName)) {
+        this.populateNodeConnections(nodeName);
+      }
+      this.setCurrentNodeState(
+          this.serverStatuses.get(nodeName), connection.state());
+    }
+  }
+
+  private handleClientStatistics(data: Uint8Array): void {
+    const fbBuffer = new ByteBuffer(data);
+    const clientStatistics =
+        ClientStatistics.getRootAsClientStatistics(fbBuffer);
+
+    for (let ii = 0; ii < clientStatistics.connectionsLength(); ++ii) {
+      const connection = clientStatistics.connections(ii);
+      const nodeName = connection.node().name();
+      if (!this.clientStatuses.has(nodeName)) {
+        this.populateNodeConnections(nodeName);
+      }
+      this.setCurrentNodeState(
+          this.clientStatuses.get(nodeName), connection.state());
+    }
+  }
+
+  drawField(): void {
+    const ctx = this.canvas.getContext('2d');
+    ctx.save();
+    ctx.scale(1.0, -1.0);
+    ctx.restore();
+  }
+
+  drawCamera(x: number, y: number, theta: number, color: string = 'blue'):
+      void {
+    const ctx = this.canvas.getContext('2d');
+    ctx.save();
+    ctx.translate(x, y);
+    ctx.rotate(theta);
+    ctx.strokeStyle = color;
+    ctx.beginPath();
+    ctx.moveTo(0.5, 0.5);
+    ctx.lineTo(0, 0);
+    ctx.lineTo(0.5, -0.5);
+    ctx.stroke();
+    ctx.beginPath();
+    ctx.arc(0, 0, 0.25, -Math.PI / 4, Math.PI / 4);
+    ctx.stroke();
+    ctx.restore();
+  }
+
+  drawRobot(
+      x: number, y: number, theta: number, color: string = 'blue',
+      dashed: boolean = false): void {
+    const ctx = this.canvas.getContext('2d');
+    ctx.save();
+    ctx.translate(x, y);
+    ctx.rotate(theta);
+    ctx.strokeStyle = color;
+    ctx.lineWidth = ROBOT_WIDTH / 10.0;
+    if (dashed) {
+      ctx.setLineDash([0.05, 0.05]);
+    } else {
+      // Empty array = solid line.
+      ctx.setLineDash([]);
+    }
+    ctx.rect(-ROBOT_LENGTH / 2, -ROBOT_WIDTH / 2, ROBOT_LENGTH, ROBOT_WIDTH);
+    ctx.stroke();
+
+    // Draw line indicating which direction is forwards on the robot.
+    ctx.beginPath();
+    ctx.moveTo(0, 0);
+    ctx.lineTo(ROBOT_LENGTH / 2.0, 0);
+    ctx.stroke();
+
+    ctx.restore();
+  }
+
+  setZeroing(div: HTMLElement): void {
+    div.innerHTML = 'zeroing';
+    div.classList.remove('faulted');
+    div.classList.add('zeroing');
+    div.classList.remove('near');
+  }
+
+  setEstopped(div: HTMLElement): void {
+    div.innerHTML = 'estopped';
+    div.classList.add('faulted');
+    div.classList.remove('zeroing');
+    div.classList.remove('near');
+  }
+
+  setValue(div: HTMLElement, val: number): void {
+    div.innerHTML = val.toFixed(4);
+    div.classList.remove('faulted');
+    div.classList.remove('zeroing');
+    div.classList.remove('near');
+  }
+
+  draw(): void {
+    this.reset();
+    this.drawField();
+
+    // Draw the matches with debugging information from the localizer.
+    const now = Date.now() / 1000.0;
+
+    if (this.drivetrainStatus && this.drivetrainStatus.trajectoryLogging()) {
+      this.drawRobot(
+          this.drivetrainStatus.trajectoryLogging().x(),
+          this.drivetrainStatus.trajectoryLogging().y(),
+          this.drivetrainStatus.trajectoryLogging().theta(), '#000000FF',
+          false);
+    }
+
+    window.requestAnimationFrame(() => this.draw());
+  }
+
+  reset(): void {
+    const ctx = this.canvas.getContext('2d');
+    ctx.setTransform(1, 0, 0, 1, 0, 0);
+    const size = window.innerHeight * 0.9;
+    ctx.canvas.height = size;
+    const width = size / 2 + 20;
+    ctx.canvas.width = width;
+    ctx.clearRect(0, 0, size, width);
+
+    // Translate to center of display.
+    ctx.translate(width / 2, size / 2);
+    // Coordinate system is:
+    // x -> forward.
+    // y -> to the left.
+    ctx.rotate(-Math.PI / 2);
+    ctx.scale(1, -1);
+
+    const M_TO_PX = (size - 10) / FIELD_LENGTH;
+    ctx.scale(M_TO_PX, M_TO_PX);
+    ctx.lineWidth = 1 / M_TO_PX;
+  }
+}
diff --git a/y2023_bot3/www/field_main.ts b/y2023_bot3/www/field_main.ts
new file mode 100644
index 0000000..d71a45e
--- /dev/null
+++ b/y2023_bot3/www/field_main.ts
@@ -0,0 +1,12 @@
+import {Connection} from '../../aos/network/www/proxy';
+
+import {FieldHandler} from './field_handler';
+
+const conn = new Connection();
+
+conn.connect();
+
+const fieldHandler = new FieldHandler(conn);
+
+fieldHandler.draw();
+
diff --git a/y2023_bot3/www/index.html b/y2023_bot3/www/index.html
new file mode 100644
index 0000000..e4e185e
--- /dev/null
+++ b/y2023_bot3/www/index.html
@@ -0,0 +1,6 @@
+<html>
+  <body>
+    <a href="field.html">Field Visualization</a><br>
+    <a href="plotter.html">Plots</a>
+  </body>
+</html>
diff --git a/y2023_bot3/www/plotter.html b/y2023_bot3/www/plotter.html
new file mode 100644
index 0000000..629ceaa
--- /dev/null
+++ b/y2023_bot3/www/plotter.html
@@ -0,0 +1,7 @@
+<html>
+  <head>
+    <script src="plot_index_bundle.min.js" defer></script>
+  </head>
+  <body>
+  </body>
+</html>
diff --git a/y2023_bot3/www/styles.css b/y2023_bot3/www/styles.css
new file mode 100644
index 0000000..c2c44d2
--- /dev/null
+++ b/y2023_bot3/www/styles.css
@@ -0,0 +1,74 @@
+.channel {
+  display: flex;
+  border-bottom: 1px solid;
+  font-size: 24px;
+}
+#field {
+  display: inline-block
+}
+
+#readouts,
+#middle_readouts
+{
+  display: inline-block;
+  vertical-align: top;
+  float: right;
+}
+
+
+#legend {
+  display: inline-block;
+}
+
+table, th, td {
+  border: 1px solid black;
+  border-collapse: collapse;
+  padding: 5px;
+  margin: 10px;
+}
+
+th, td {
+  text-align: right;
+  width: 70px;
+}
+
+td:first-child {
+  width: 150px;
+}
+
+.connected, .near {
+  background-color: LightGreen;
+  border-radius: 10px;
+}
+
+.zeroing {
+  background-color: yellow;
+  border-radius: 10px;
+}
+
+.faulted {
+  background-color: red;
+  border-radius: 10px;
+}
+
+#vision_readouts > div {
+  display: table-row;
+  padding: 5px;
+}
+
+#vision_readouts > div > div {
+  display: table-cell;
+  padding: 5px;
+  text-align: right;
+}
+
+#message_bridge_status > div {
+  display: table-row;
+  padding: 5px;
+}
+
+#message_bridge_status > div > div {
+  display: table-cell;
+  padding: 5px;
+  text-align: right;
+}
diff --git a/y2023_bot3/y2023_bot3.json b/y2023_bot3/y2023_bot3.json
new file mode 100644
index 0000000..36085de
--- /dev/null
+++ b/y2023_bot3/y2023_bot3.json
@@ -0,0 +1,18 @@
+{
+    "channel_storage_duration": 10000000000,
+    "maps": [
+        {
+            "match": {
+                "name": "/aos",
+                "type": "aos.RobotState"
+            },
+            "rename": {
+                "name": "/roborio/aos"
+            }
+        }
+    ],
+    "imports": [
+        "y2023_bot3_roborio.json",
+        "y2023_bot3_imu.json"
+    ]
+}
diff --git a/y2023_bot3/y2023_bot3_imu.json b/y2023_bot3/y2023_bot3_imu.json
new file mode 100644
index 0000000..5e744a7
--- /dev/null
+++ b/y2023_bot3/y2023_bot3_imu.json
@@ -0,0 +1,270 @@
+{
+  "channels": [
+    {
+      "name": "/imu/aos",
+      "type": "aos.timing.Report",
+      "source_node": "imu",
+      "frequency": 50,
+      "num_senders": 20,
+      "max_size": 4096
+    },
+    {
+      "name": "/imu/aos",
+      "type": "aos.logging.LogMessageFbs",
+      "source_node": "imu",
+      "frequency": 200,
+      "num_senders": 20
+    },
+    {
+      "name": "/imu/aos",
+      "type": "aos.starter.Status",
+      "source_node": "imu",
+      "frequency": 50,
+      "num_senders": 20,
+      "max_size": 2048
+    },
+    {
+      "name": "/imu/aos",
+      "type": "aos.starter.StarterRpc",
+      "source_node": "imu",
+      "frequency": 10,
+      "num_senders": 2
+    },
+    {
+      "name": "/imu/aos",
+      "type": "aos.message_bridge.ServerStatistics",
+      "source_node": "imu",
+      "frequency": 10,
+      "num_senders": 2,
+      "max_size": 1504
+    },
+    {
+      "name": "/imu/aos",
+      "type": "aos.message_bridge.ClientStatistics",
+      "source_node": "imu",
+      "frequency": 20,
+      "num_senders": 2
+    },
+    {
+      "name": "/imu/aos",
+      "type": "aos.logging.DynamicLogCommand",
+      "source_node": "imu",
+      "frequency": 10,
+      "num_senders": 2
+    },
+    {
+      "name": "/imu/aos",
+      "type": "aos.message_bridge.Timestamp",
+      "source_node": "imu",
+      "frequency": 15,
+      "num_senders": 2,
+      "logger": "LOCAL_AND_REMOTE_LOGGER",
+      "logger_nodes": [
+        "roborio",
+      ],
+      "max_size": 400,
+      "destination_nodes": [
+        {
+          "name": "roborio",
+          "priority": 1,
+          "timestamp_logger": "LOCAL_AND_REMOTE_LOGGER",
+          "timestamp_logger_nodes": [
+            "imu"
+          ],
+          "time_to_live": 5000000
+        },
+      ]
+    },
+    {
+      "name": "/imu/aos/remote_timestamps/roborio/imu/aos/aos-message_bridge-Timestamp",
+      "type": "aos.message_bridge.RemoteMessage",
+      "frequency": 20,
+      "source_node": "imu",
+      "max_size": 208
+    },
+    {
+      "name": "/roborio/aos",
+      "type": "aos.starter.StarterRpc",
+      "source_node": "roborio",
+      "logger": "LOCAL_AND_REMOTE_LOGGER",
+      "logger_nodes": [
+        "imu"
+      ],
+      "destination_nodes": [
+        {
+          "name": "imu",
+          "priority": 5,
+          "timestamp_logger": "LOCAL_AND_REMOTE_LOGGER",
+          "timestamp_logger_nodes": [
+            "roborio"
+          ],
+          "time_to_live": 5000000
+        }
+      ]
+    },
+    {
+      "name": "/roborio/aos/remote_timestamps/imu/roborio/aos/aos-starter-StarterRpc",
+      "type": "aos.message_bridge.RemoteMessage",
+      "source_node": "roborio",
+      "logger": "NOT_LOGGED",
+      "frequency": 20,
+      "num_senders": 2,
+      "max_size": 200
+    },
+    {
+      "name": "/localizer",
+      "type": "frc971.IMUValuesBatch",
+      "source_node": "imu",
+      "frequency": 2200,
+      "max_size": 1600,
+      "num_senders": 2
+    },
+    {
+      "name": "/localizer",
+      "type": "frc971.controls.LocalizerOutput",
+      "source_node": "imu",
+      "frequency": 52,
+      "logger": "LOCAL_AND_REMOTE_LOGGER",
+      "logger_nodes": [
+        "roborio"
+      ],
+      "destination_nodes": [
+        {
+          "name": "roborio",
+          "priority": 5,
+          "time_to_live": 5000000,
+          "timestamp_logger": "LOCAL_AND_REMOTE_LOGGER",
+          "timestamp_logger_nodes": [
+            "imu"
+          ]
+        }
+      ]
+    },
+    {
+      "name": "/imu/aos/remote_timestamps/roborio/localizer/frc971-controls-LocalizerOutput",
+      "type": "aos.message_bridge.RemoteMessage",
+      "source_node": "imu",
+      "logger": "NOT_LOGGED",
+      "frequency": 52,
+      "num_senders": 2,
+      "max_size": 200
+    },
+    {
+      "name": "/imu/constants",
+      "type": "y2023_bot3.Constants",
+      "source_node": "imu",
+      "frequency": 1,
+      "num_senders": 2,
+      "max_size": 65536
+    }
+  ],
+  "applications": [
+    {
+      "name": "message_bridge_client",
+      "nodes": [
+        "imu"
+      ]
+    },
+    {
+      "name": "imu",
+      "executable_name": "imu_main",
+      "user": "pi",
+      "nodes": [
+        "imu"
+      ]
+    },
+    {
+      "name": "joystick_republish",
+      "executable_name": "joystick_republish",
+      "user": "pi",
+      "nodes": [
+        "imu"
+      ]
+    },
+    {
+      "name": "message_bridge_server",
+      "executable_name": "message_bridge_server",
+      "user": "pi",
+      "nodes": [
+        "imu"
+      ]
+    },
+    {
+      "name": "localizer_logger",
+      "executable_name": "logger_main",
+      "args": [
+        "--logging_folder",
+        "",
+        "--snappy_compress",
+        "--rotate_every", "30.0"
+      ],
+      "user": "pi",
+      "nodes": [
+        "imu"
+      ]
+    },
+    {
+      "name": "web_proxy",
+      "executable_name": "web_proxy_main",
+      "args": [
+        "--min_ice_port=5800",
+        "--max_ice_port=5810"
+      ],
+      "user": "pi",
+      "nodes": [
+        "imu"
+      ]
+    },
+    {
+      "name": "foxglove_websocket",
+      "user": "pi",
+      "nodes": [
+        "imu"
+      ]
+    },
+    {
+      "name": "constants_sender",
+      "autorestart": false,
+      "user": "pi",
+      "nodes": [
+        "imu"
+      ]
+    }
+  ],
+  "maps": [
+    {
+      "match": {
+        "name": "/constants*",
+        "source_node": "imu"
+      },
+      "rename": {
+        "name": "/imu/constants"
+      }
+    },
+    {
+      "match": {
+        "name": "/aos*",
+        "source_node": "imu"
+      },
+      "rename": {
+        "name": "/imu/aos"
+      }
+    }
+  ],
+  "nodes": [
+    {
+      "name": "imu",
+      "hostname": "pi6",
+      "hostnames": [
+        "pi-971-6",
+        "pi-7971-6",
+        "pi-8971-6",
+        "pi-9971-6"
+      ],
+      "port": 9971
+    },
+    {
+      "name": "roborio"
+    }
+  ]
+}
diff --git a/y2023_bot3/y2023_bot3_roborio.json b/y2023_bot3/y2023_bot3_roborio.json
new file mode 100644
index 0000000..4ae02d6
--- /dev/null
+++ b/y2023_bot3/y2023_bot3_roborio.json
@@ -0,0 +1,465 @@
+{
+  "channels": [
+    {
+      "name": "/roborio/aos",
+      "type": "aos.JoystickState",
+      "source_node": "roborio",
+      "frequency": 100,
+      "logger": "LOCAL_AND_REMOTE_LOGGER",
+      "logger_nodes": [
+        "imu"
+      ],
+      "destination_nodes": [
+        {
+          "name": "imu",
+          "priority": 5,
+          "time_to_live": 50000000
+        }
+      ]
+    },
+    {
+      "name": "/roborio/aos",
+      "type": "aos.RobotState",
+      "source_node": "roborio",
+      "frequency": 250
+    },
+    {
+      "name": "/roborio/aos",
+      "type": "aos.timing.Report",
+      "source_node": "roborio",
+      "frequency": 50,
+      "num_senders": 20,
+      "max_size": 8192
+    },
+    {
+      "name": "/roborio/aos",
+      "type": "aos.logging.LogMessageFbs",
+      "source_node": "roborio",
+      "frequency": 500,
+      "max_size": 1504,
+      "num_senders": 20
+    },
+    {
+      "name": "/roborio/aos",
+      "type": "aos.starter.Status",
+      "source_node": "roborio",
+      "frequency": 50,
+      "num_senders": 20,
+      "max_size": 2000
+    },
+    {
+      "name": "/roborio/aos",
+      "type": "aos.starter.StarterRpc",
+      "source_node": "roborio",
+      "frequency": 10,
+      "max_size": 400,
+      "num_senders": 2
+    },
+    {
+      "name": "/roborio/aos",
+      "type": "aos.message_bridge.ServerStatistics",
+      "source_node": "roborio",
+      "frequency": 10,
+      "num_senders": 2,
+      "max_size": 1504
+    },
+    {
+      "name": "/roborio/aos",
+      "type": "aos.message_bridge.ClientStatistics",
+      "source_node": "roborio",
+      "frequency": 20,
+      "max_size": 2000,
+      "num_senders": 2
+    },
+    {
+      "name": "/roborio/aos",
+      "type": "aos.logging.DynamicLogCommand",
+      "source_node": "roborio",
+      "frequency": 10,
+      "num_senders": 2
+    },
+    {
+      "name": "/roborio/aos/remote_timestamps/imu/roborio/aos/aos-message_bridge-Timestamp",
+      "type": "aos.message_bridge.RemoteMessage",
+      "frequency": 20,
+      "source_node": "roborio",
+      "max_size": 208
+    },
+    {
+      "name": "/roborio/aos",
+      "type": "aos.message_bridge.Timestamp",
+      "source_node": "roborio",
+      "frequency": 15,
+      "num_senders": 2,
+      "max_size": 512,
+      "logger": "LOCAL_AND_REMOTE_LOGGER",
+      "logger_nodes": [
+        "imu"
+      ],
+      "destination_nodes": [
+        {
+          "name": "imu",
+          "priority": 1,
+          "timestamp_logger": "LOCAL_AND_REMOTE_LOGGER",
+          "timestamp_logger_nodes": [
+            "roborio"
+          ],
+          "time_to_live": 5000000
+        }
+      ]
+    },
+    {
+      "name": "/superstructure",
+      "type": "y2023_bot3.control_loops.superstructure.Goal",
+      "source_node": "roborio",
+      "frequency": 250,
+      "max_size": 512
+    },
+    {
+      "name": "/superstructure",
+      "type": "y2023_bot3.control_loops.superstructure.Status",
+      "source_node": "roborio",
+      "frequency": 400,
+      "num_senders": 2
+    },
+    {
+      "name": "/superstructure",
+      "type": "y2023_bot3.control_loops.superstructure.Output",
+      "source_node": "roborio",
+      "frequency": 250,
+      "num_senders": 2,
+      "max_size": 224
+    },
+    {
+      "name": "/superstructure",
+      "type": "y2023_bot3.control_loops.superstructure.Position",
+      "source_node": "roborio",
+      "frequency": 250,
+      "num_senders": 2,
+      "max_size": 448
+    },
+    {
+      "name": "/can",
+      "type": "frc971.can_logger.CanFrame",
+      "source_node": "roborio",
+      "frequency": 6000,
+      "num_senders": 2,
+      "max_size": 200
+    },
+    {
+      "name": "/drivetrain",
+      "type": "frc971.control_loops.drivetrain.CANPosition",
+      "source_node": "roborio",
+      "frequency": 220,
+      "num_senders": 2,
+      "max_size": 400
+    },
+    {
+      "name": "/drivetrain",
+      "type": "frc971.sensors.GyroReading",
+      "source_node": "roborio",
+      "frequency": 250,
+      "num_senders": 2
+    },
+    {
+      "name": "/drivetrain",
+      "type": "frc971.sensors.Uid",
+      "source_node": "roborio",
+      "frequency": 250,
+      "num_senders": 2
+    },
+    {
+      "name": "/drivetrain",
+      "type": "frc971.control_loops.drivetrain.fb.Trajectory",
+      "source_node": "roborio",
+      "max_size": 600000,
+      "frequency": 10,
+      "logger": "NOT_LOGGED",
+      "num_senders": 2,
+      "read_method": "PIN",
+      "num_readers": 10
+    },
+    {
+      "name": "/drivetrain",
+      "type": "frc971.control_loops.drivetrain.SplineGoal",
+      "source_node": "roborio",
+      "frequency": 10
+    },
+    {
+      "name": "/drivetrain",
+      "type": "frc971.control_loops.drivetrain.Goal",
+      "source_node": "roborio",
+      "max_size": 224,
+      "frequency": 250
+    },
+    {
+      "name": "/drivetrain",
+      "type": "frc971.control_loops.drivetrain.Position",
+      "source_node": "roborio",
+      "frequency": 400,
+      "max_size": 112,
+      "num_senders": 2
+    },
+    {
+      "name": "/drivetrain",
+      "type": "frc971.control_loops.drivetrain.Output",
+      "source_node": "roborio",
+      "frequency": 400,
+      "max_size": 80,
+      "num_senders": 2,
+      "logger": "LOCAL_AND_REMOTE_LOGGER",
+      "logger_nodes": [
+        "imu"
+      ],
+      "destination_nodes": [
+        {
+          "name": "imu",
+          "priority": 5,
+          "time_to_live": 5000000
+        }
+      ]
+    },
+    {
+      "name": "/drivetrain",
+      "type": "frc971.control_loops.drivetrain.Status",
+      "source_node": "roborio",
+      "frequency": 400,
+      "max_size": 1616,
+      "num_senders": 2
+    },
+    {
+      "name": "/drivetrain",
+      "type": "frc971.control_loops.drivetrain.LocalizerControl",
+      "source_node": "roborio",
+      "frequency": 250,
+      "max_size": 96,
+      "logger": "LOCAL_AND_REMOTE_LOGGER",
+      "logger_nodes": [
+        "imu"
+      ],
+      "destination_nodes": [
+        {
+          "name": "imu",
+          "priority": 5,
+          "timestamp_logger": "LOCAL_AND_REMOTE_LOGGER",
+          "timestamp_logger_nodes": [
+            "roborio"
+          ],
+          "time_to_live": 0
+        }
+      ]
+    },
+    {
+      "name": "/roborio/aos/remote_timestamps/imu/drivetrain/frc971-control_loops-drivetrain-LocalizerControl",
+      "type": "aos.message_bridge.RemoteMessage",
+      "source_node": "roborio",
+      "logger": "NOT_LOGGED",
+      "frequency": 400,
+      "num_senders": 2,
+      "max_size": 200
+    },
+    {
+      "name": "/autonomous",
+      "type": "aos.common.actions.Status",
+      "source_node": "roborio"
+    },
+    {
+      "name": "/autonomous",
+      "type": "frc971.autonomous.Goal",
+      "source_node": "roborio"
+    },
+    {
+      "name": "/autonomous",
+      "type": "frc971.autonomous.AutonomousMode",
+      "source_node": "roborio",
+      "frequency": 250
+    },
+    {
+      "name": "/roborio/aos",
+      "type": "frc971.PDPValues",
+      "source_node": "roborio",
+      "frequency": 55,
+      "max_size": 368
+    },
+    {
+      "name": "/roborio/aos",
+      "type": "frc971.wpilib.PneumaticsToLog",
+      "source_node": "roborio",
+      "frequency": 50
+    },
+    {
+      "name": "/roborio",
+      "type": "frc971.CANConfiguration",
+      "source_node": "roborio",
+      "frequency": 2
+    },
+    {
+      "name": "/roborio/constants",
+      "type": "y2023_bot3.Constants",
+      "source_node": "roborio",
+      "frequency": 1,
+      "num_senders": 2,
+      "max_size": 65536
+    }
+  ],
+  "applications": [
+    {
+      "name": "drivetrain",
+      "executable_name": "drivetrain",
+      "nodes": [
+        "roborio"
+      ]
+    },
+    {
+      "name": "trajectory_generator",
+      "executable_name": "trajectory_generator",
+      "nodes": [
+        "roborio"
+      ]
+    },
+    {
+      "name": "superstructure",
+      "executable_name": "superstructure",
+      "nodes": [
+        "roborio"
+      ]
+    },
+    {
+      "name": "roborio_irq_affinity",
+      "executable_name": "irq_affinity",
+      "args": [
+        "--irq_config=/home/admin/bin/roborio_irq_config.json"
+      ],
+      "nodes": [
+        "roborio"
+      ]
+    },
+    {
+      "name": "joystick_reader",
+      "executable_name": "joystick_reader",
+      "args": [
+        "--nodie_on_malloc"
+      ],
+      "nodes": [
+        "roborio"
+      ]
+    },
+    {
+      "name": "wpilib_interface",
+      "executable_name": "wpilib_interface",
+      "args": [
+        "--nodie_on_malloc"
+      ],
+      "nodes": [
+        "roborio"
+      ]
+    },
+    {
+      "name": "autonomous_action",
+      "executable_name": "autonomous_action",
+      "args": [
+        "--nodie_on_malloc"
+      ],
+      "autostart": true,
+      "nodes": [
+        "roborio"
+      ]
+    },
+    {
+      "name": "roborio_web_proxy",
+      "executable_name": "web_proxy_main",
+      "args": [
+        "--min_ice_port=5800",
+        "--max_ice_port=5810"
+      ],
+      "nodes": [
+        "roborio"
+      ]
+    },
+    {
+      "name": "roborio_message_bridge_client",
+      "executable_name": "message_bridge_client",
+      "args": [
+        "--rt_priority=16",
+        "--sinit_max_init_timeout=5000"
+      ],
+      "nodes": [
+        "roborio"
+      ]
+    },
+    {
+      "name": "roborio_message_bridge_server",
+      "executable_name": "message_bridge_server",
+      "args": [
+        "--rt_priority=16"
+      ],
+      "nodes": [
+        "roborio"
+      ]
+    },
+    {
+      "name": "logger",
+      "executable_name": "logger_main",
+      "args": [
+        "--snappy_compress",
+        "--logging_folder=/home/admin/logs/",
+        "--rotate_every", "30.0"
+      ],
+      "nodes": [
+        "roborio"
+      ]
+    },
+    {
+      "name": "constants_sender_roborio",
+      "executable_name": "constants_sender",
+      "autorestart": false,
+      "nodes": [
+        "roborio"
+      ]
+    },
+    {
+      "name": "can_logger",
+      "executable_name": "can_logger",
+      "nodes": [
+        "roborio"
+      ]
+    }
+  ],
+  "maps": [
+    {
+      "match": {
+        "name": "/constants*",
+        "source_node": "roborio"
+      },
+      "rename": {
+        "name": "/roborio/constants"
+      }
+    },
+    {
+      "match": {
+        "name": "/aos*",
+        "source_node": "roborio"
+      },
+      "rename": {
+        "name": "/roborio/aos"
+      }
+    }
+  ],
+  "nodes": [
+    {
+      "name": "roborio",
+      "hostname": "roborio",
+      "hostnames": [
+        "roboRIO-971-FRC",
+        "roboRIO-6971-FRC",
+        "roboRIO-7971-FRC",
+        "roboRIO-8971-FRC",
+        "roboRIO-9971-FRC"
+      ],
+      "port": 9971
+    },
+    {
+      "name": "imu"
+    },
+  ]
+}
diff --git a/y2023_bot4/BUILD b/y2023_bot4/BUILD
new file mode 100644
index 0000000..1a4ed64
--- /dev/null
+++ b/y2023_bot4/BUILD
@@ -0,0 +1,234 @@
+load("@com_github_google_flatbuffers//:build_defs.bzl", "flatbuffer_cc_library")
+load("//frc971:downloader.bzl", "robot_downloader")
+load("//aos:config.bzl", "aos_config")
+load("//aos/util:config_validator_macro.bzl", "config_validator_test")
+
+config_validator_test(
+    name = "config_validator_test",
+    config = "//y2023_bot4:aos_config",
+)
+
+robot_downloader(
+    binaries = [
+        "//aos/network:web_proxy_main",
+        "//aos/events/logging:log_cat",
+        "//aos/events:aos_timing_report_streamer",
+    ],
+    data = [
+        ":aos_config",
+        ":swerve_publisher_output_json",
+        "@ctre_phoenix6_api_cpp_athena//:shared_libraries",
+        "@ctre_phoenix6_tools_athena//:shared_libraries",
+        "@ctre_phoenix_api_cpp_athena//:shared_libraries",
+        "@ctre_phoenix_cci_athena//:shared_libraries",
+    ],
+    start_binaries = [
+        "//aos/events/logging:logger_main",
+        "//aos/network:web_proxy_main",
+        "//aos/starter:irq_affinity",
+        ":wpilib_interface",
+        ":swerve_publisher",
+        "//frc971/can_logger",
+        "//aos/network:message_bridge_client",
+        "//aos/network:message_bridge_server",
+    ],
+    target_compatible_with = ["@platforms//os:linux"],
+)
+
+robot_downloader(
+    name = "pi_download",
+    binaries = [
+        "//aos/util:foxglove_websocket",
+        "//aos/events:aos_timing_report_streamer",
+        "//aos/events/logging:log_cat",
+        "//y2023/rockpi:imu_main",
+        "//frc971/image_streamer:image_streamer",
+        "//aos/network:message_bridge_client",
+        "//aos/network:message_bridge_server",
+        "//aos/network:web_proxy_main",
+        "//aos/starter:irq_affinity",
+        "//aos/events/logging:logger_main",
+    ],
+    data = [
+        ":aos_config",
+        "//frc971/rockpi:rockpi_config.json",
+    ],
+    start_binaries = [
+    ],
+    target_compatible_with = ["//tools/platforms/hardware:raspberry_pi"],
+    target_type = "pi",
+)
+
+filegroup(
+    name = "swerve_publisher_output_json",
+    srcs = [
+        "swerve_drivetrain_output.json",
+    ],
+    visibility = ["//y2023_bot4:__subpackages__"],
+)
+
+cc_library(
+    name = "constants",
+    srcs = ["constants.cc"],
+    hdrs = [
+        "constants.h",
+    ],
+    visibility = ["//visibility:public"],
+    deps = [
+        "//aos/network:team_number",
+        "//frc971:constants",
+    ],
+)
+
+flatbuffer_cc_library(
+    name = "drivetrain_position_fbs",
+    srcs = ["drivetrain_position.fbs"],
+    gen_reflections = 1,
+    deps = ["//frc971/control_loops:control_loops_fbs"],
+)
+
+flatbuffer_cc_library(
+    name = "drivetrain_can_position_fbs",
+    srcs = ["drivetrain_can_position.fbs"],
+    gen_reflections = 1,
+    deps = ["//frc971/control_loops:can_falcon_fbs"],
+)
+
+cc_binary(
+    name = "swerve_publisher",
+    srcs = ["swerve_publisher_main.cc"],
+    deps = [
+        ":swerve_publisher_lib",
+        "//aos/events:shm_event_loop",
+        "@com_github_gflags_gflags//:gflags",
+    ],
+)
+
+cc_library(
+    name = "swerve_publisher_lib",
+    srcs = ["swerve_publisher_lib.cc"],
+    hdrs = ["swerve_publisher_lib.h"],
+    deps = [
+        "//aos:init",
+        "//aos/events:event_loop",
+        "//frc971/control_loops/drivetrain/swerve:swerve_drivetrain_output_fbs",
+        "@com_github_google_glog//:glog",
+    ],
+)
+
+cc_test(
+    name = "swerve_publisher_lib_test",
+    srcs = [
+        "swerve_publisher_lib_test.cc",
+    ],
+    data = [
+        ":aos_config",
+        ":swerve_publisher_output_json",
+    ],
+    deps = [
+        ":swerve_publisher_lib",
+        "//aos/events:simulated_event_loop",
+        "//aos/testing:googletest",
+    ],
+)
+
+cc_binary(
+    name = "wpilib_interface",
+    srcs = ["wpilib_interface.cc"],
+    target_compatible_with = ["//tools/platforms/hardware:roborio"],
+    deps = [
+        ":constants",
+        ":drivetrain_can_position_fbs",
+        ":drivetrain_position_fbs",
+        "//aos:init",
+        "//aos/events:shm_event_loop",
+        "//frc971/control_loops:control_loops_fbs",
+        "//frc971/wpilib:can_sensor_reader",
+        "//frc971/wpilib:falcon",
+        "//frc971/wpilib:sensor_reader",
+        "//frc971/wpilib:wpilib_robot_base",
+        "//frc971/wpilib/swerve:swerve_drivetrain_writer",
+    ],
+)
+
+aos_config(
+    name = "aos_config",
+    src = "y2023_bot4.json",
+    flatbuffers = [
+        "//aos/network:message_bridge_client_fbs",
+        "//aos/network:message_bridge_server_fbs",
+        "//aos/network:timestamp_fbs",
+        "//frc971/input:robot_state_fbs",
+    ],
+    target_compatible_with = ["@platforms//os:linux"],
+    visibility = ["//visibility:public"],
+    deps = [
+        ":config_imu",
+        ":config_logger",
+        ":config_roborio",
+    ],
+)
+
+aos_config(
+    name = "config_roborio",
+    src = "y2023_bot4_roborio.json",
+    flatbuffers = [
+        ":drivetrain_position_fbs",
+        ":drivetrain_can_position_fbs",
+        "//frc971:can_configuration_fbs",
+        "//aos/network:remote_message_fbs",
+        "//aos/network:message_bridge_client_fbs",
+        "//aos/network:message_bridge_server_fbs",
+        "//aos/network:timestamp_fbs",
+        "//frc971/control_loops/drivetrain/swerve:swerve_drivetrain_output_fbs",
+        "//frc971/control_loops/drivetrain/swerve:swerve_drivetrain_position_fbs",
+        "//frc971/control_loops/drivetrain:drivetrain_can_position_fbs",
+        "//frc971/can_logger:can_logging_fbs",
+    ],
+    target_compatible_with = ["@platforms//os:linux"],
+    deps = [
+        "//aos/events:aos_config",
+        "//frc971/autonomous:aos_config",
+        "//frc971/control_loops/drivetrain:aos_config",
+        "//frc971/input:aos_config",
+        "//frc971/wpilib:aos_config",
+    ],
+)
+
+aos_config(
+    name = "config_imu",
+    src = "y2023_bot4_imu.json",
+    flatbuffers = [
+        "//aos/network:message_bridge_client_fbs",
+        "//aos/network:message_bridge_server_fbs",
+        "//aos/network:timestamp_fbs",
+        "//aos/network:remote_message_fbs",
+        "//frc971/vision:target_map_fbs",
+    ],
+    target_compatible_with = ["@platforms//os:linux"],
+    visibility = ["//visibility:public"],
+    deps = [
+        "//aos/events:aos_config",
+        "//frc971/control_loops/drivetrain:aos_config",
+    ],
+)
+
+aos_config(
+    name = "config_logger",
+    src = "y2023_bot4_logger.json",
+    flatbuffers = [
+        "//aos/network:message_bridge_client_fbs",
+        "//aos/network:message_bridge_server_fbs",
+        "//aos/network:timestamp_fbs",
+        "//aos/network:remote_message_fbs",
+        "//frc971/vision:calibration_fbs",
+        "//frc971/vision:target_map_fbs",
+    ],
+    target_compatible_with = ["@platforms//os:linux"],
+    visibility = ["//visibility:public"],
+    deps = [
+        "//aos/events:aos_config",
+        "//frc971/control_loops/drivetrain:aos_config",
+        "//frc971/input:aos_config",
+    ],
+)
diff --git a/y2023_bot4/constants.cc b/y2023_bot4/constants.cc
new file mode 100644
index 0000000..aab1935
--- /dev/null
+++ b/y2023_bot4/constants.cc
@@ -0,0 +1,52 @@
+#include "y2023_bot4/constants.h"
+
+#include <cstdint>
+
+#include "glog/logging.h"
+
+#include "aos/network/team_number.h"
+
+namespace y2023_bot4 {
+namespace constants {
+Values MakeValues(uint16_t team) {
+  LOG(INFO) << "creating a Constants for team: " << team;
+  Values r;
+  auto *const front_left_zeroing_constants = &r.front_left_zeroing_constants;
+  auto *const front_right_zeroing_constants = &r.front_right_zeroing_constants;
+  auto *const back_left_zeroing_constants = &r.back_left_zeroing_constants;
+  auto *const back_right_zeroing_constants = &r.back_right_zeroing_constants;
+
+  front_left_zeroing_constants->average_filter_size = 0;
+  front_left_zeroing_constants->one_revolution_distance = 2 * M_PI;
+  front_left_zeroing_constants->measured_absolute_position = 0.76761395509829;
+  front_left_zeroing_constants->zeroing_threshold = 0.0;
+  front_left_zeroing_constants->moving_buffer_size = 0.0;
+  front_left_zeroing_constants->allowable_encoder_error = 0.0;
+
+  front_right_zeroing_constants->average_filter_size = 0;
+  front_right_zeroing_constants->one_revolution_distance = 2 * M_PI;
+  front_right_zeroing_constants->measured_absolute_position = 0.779403958443922;
+  front_right_zeroing_constants->zeroing_threshold = 0.0;
+  front_right_zeroing_constants->moving_buffer_size = 0.0;
+  front_right_zeroing_constants->allowable_encoder_error = 0.0;
+
+  back_left_zeroing_constants->average_filter_size = 0;
+  back_left_zeroing_constants->one_revolution_distance = 2 * M_PI;
+  back_left_zeroing_constants->measured_absolute_position = 0.053439698061417;
+  back_left_zeroing_constants->zeroing_threshold = 0.0;
+  back_left_zeroing_constants->moving_buffer_size = 0.0;
+  back_left_zeroing_constants->allowable_encoder_error = 0.0;
+
+  back_right_zeroing_constants->average_filter_size = 0;
+  back_right_zeroing_constants->one_revolution_distance = 2 * M_PI;
+  back_right_zeroing_constants->measured_absolute_position = 0.719329333121509;
+  back_right_zeroing_constants->zeroing_threshold = 0.0;
+  back_right_zeroing_constants->moving_buffer_size = 0.0;
+  back_right_zeroing_constants->allowable_encoder_error = 0.0;
+
+  return r;
+}
+
+Values MakeValues() { return MakeValues(aos::network::GetTeamNumber()); }
+}  // namespace constants
+}  // namespace y2023_bot4
diff --git a/y2023_bot4/constants.h b/y2023_bot4/constants.h
new file mode 100644
index 0000000..676ae06
--- /dev/null
+++ b/y2023_bot4/constants.h
@@ -0,0 +1,55 @@
+#ifndef Y2023_BOT4_CONSTANTS_H
+#define Y2023_BOT4_CONSTANTS_H
+
+#ifndef M_PI
+#define M_PI 3.14159265358979323846
+#endif
+
+#include <cstdint>
+
+#include "frc971/constants.h"
+
+namespace y2023_bot4 {
+namespace constants {
+struct Values {
+  static const int kZeroingSampleSize = 200;
+
+  static const int kDrivetrainWriterPriority = 35;
+  static const int kDrivetrainTxPriority = 36;
+  static const int kDrivetrainRxPriority = 36;
+
+  // TODO (maxwell): Make this the real value;
+  static constexpr double kDrivetrainCyclesPerRevolution() { return 512.0; }
+  static constexpr double kDrivetrainEncoderRatio() { return 1.0; }
+
+  static constexpr double kDrivetrainStatorCurrentLimit() { return 35.0; }
+  static constexpr double kDrivetrainSupplyCurrentLimit() { return 60.0; }
+
+  // TODO (maxwell): Make this the real value
+  static constexpr double kFollowerWheelCountsPerRevolution() { return 512.0; }
+  static constexpr double kFollowerWheelEncoderRatio() { return 1.0; }
+  static constexpr double kFollowerWheelRadius() { return 3.25 / 2 * 0.0254; }
+  static constexpr double kDrivetrainEncoderCountsPerRevolution() {
+    return 2048.0;
+  }
+
+  static constexpr double kMaxDrivetrainEncoderPulsesPerSecond() {
+    return 1200000;
+  }
+
+  frc971::constants::ContinuousAbsoluteEncoderZeroingConstants
+      front_left_zeroing_constants,
+      front_right_zeroing_constants, back_left_zeroing_constants,
+      back_right_zeroing_constants;
+};
+// Creates and returns a Values instance for the constants.
+// Should be called before realtime because this allocates memory.
+// Only the first call to either of these will be used.
+Values MakeValues(uint16_t team);
+
+// Calls MakeValues with aos::network::GetTeamNumber()
+Values MakeValues();
+}  // namespace constants
+}  // namespace y2023_bot4
+
+#endif  // Y2023_BOT4_CONSTANTS_H
diff --git a/y2023_bot4/drivetrain_can_position.fbs b/y2023_bot4/drivetrain_can_position.fbs
new file mode 100644
index 0000000..e8c1235
--- /dev/null
+++ b/y2023_bot4/drivetrain_can_position.fbs
@@ -0,0 +1,17 @@
+include "frc971/control_loops/can_falcon.fbs";
+namespace y2023_bot4;
+
+table SwerveModuleCANPosition {
+  rotation: frc971.control_loops.CANFalcon (id: 0);
+  translation: frc971.control_loops.CANFalcon (id: 1);
+}
+
+// CAN Readings from the CAN sensor reader loop for each swerve module
+table AbsoluteCANPosition {
+  front_left: SwerveModuleCANPosition (id: 0);
+  front_right: SwerveModuleCANPosition (id: 1);
+  back_left: SwerveModuleCANPosition (id: 2);
+  back_right: SwerveModuleCANPosition (id: 3);
+}
+
+root_type AbsoluteCANPosition;
diff --git a/y2023_bot4/drivetrain_position.fbs b/y2023_bot4/drivetrain_position.fbs
new file mode 100644
index 0000000..e5d0fc3
--- /dev/null
+++ b/y2023_bot4/drivetrain_position.fbs
@@ -0,0 +1,16 @@
+include "frc971/control_loops/control_loops.fbs";
+namespace y2023_bot4;
+
+table AbsoluteDrivetrainPosition {
+  // Position of the follower wheels from the encoders
+  follower_wheel_one_position:double (id: 0);
+  follower_wheel_two_position:double (id: 1);
+
+  // Position from the mag encoder on each module.
+  front_left_position: frc971.AbsolutePosition (id: 2);
+  front_right_position: frc971.AbsolutePosition (id: 3);
+  back_left_position: frc971.AbsolutePosition (id: 4);
+  back_right_position: frc971.AbsolutePosition  (id: 5);
+}
+
+root_type AbsoluteDrivetrainPosition;
diff --git a/y2023_bot4/swerve_drivetrain_output.json b/y2023_bot4/swerve_drivetrain_output.json
new file mode 100644
index 0000000..bc152e2
--- /dev/null
+++ b/y2023_bot4/swerve_drivetrain_output.json
@@ -0,0 +1,18 @@
+{
+    "front_left_output": {
+        "rotation_current": 0.0,
+        "translation_current": 0.0
+    },
+    "front_right_output": {
+        "rotation_current": 0.0,
+        "translation_current": 0.0
+    },
+    "back_left_output": {
+        "rotation_current": 0.0,
+        "translation_current": 0.0
+    },
+    "back_right_output": {
+        "rotation_current": 0.0,
+        "translation_current": 0.0
+    }
+}
diff --git a/y2023_bot4/swerve_publisher_lib.cc b/y2023_bot4/swerve_publisher_lib.cc
new file mode 100644
index 0000000..78a778e
--- /dev/null
+++ b/y2023_bot4/swerve_publisher_lib.cc
@@ -0,0 +1,51 @@
+#include "y2023_bot4/swerve_publisher_lib.h"
+
+y2023_bot4::SwervePublisher::SwervePublisher(aos::EventLoop *event_loop,
+                                             aos::ExitHandle *exit_handle,
+                                             const std::string &filename,
+                                             double duration)
+    : drivetrain_output_sender_(
+          event_loop->MakeSender<drivetrain::swerve::Output>("/drivetrain")) {
+  event_loop
+      ->AddTimer([this, filename]() {
+        auto output_builder = drivetrain_output_sender_.MakeBuilder();
+
+        auto drivetrain_output =
+            aos::JsonFileToFlatbuffer<drivetrain::swerve::Output>(filename);
+
+        auto copied_flatbuffer =
+            aos::CopyFlatBuffer<drivetrain::swerve::Output>(
+                drivetrain_output, output_builder.fbb());
+        CHECK(drivetrain_output.Verify());
+
+        output_builder.CheckOk(output_builder.Send(copied_flatbuffer));
+      })
+      ->Schedule(event_loop->monotonic_now(),
+                 std::chrono::duration_cast<aos::monotonic_clock::duration>(
+                     std::chrono::milliseconds(5)));
+  event_loop
+      ->AddTimer([this, exit_handle]() {
+        auto builder = drivetrain_output_sender_.MakeBuilder();
+        drivetrain::swerve::SwerveModuleOutput::Builder swerve_module_builder =
+            builder.MakeBuilder<drivetrain::swerve::SwerveModuleOutput>();
+
+        swerve_module_builder.add_rotation_current(0.0);
+        swerve_module_builder.add_translation_current(0.0);
+
+        auto swerve_module_offset = swerve_module_builder.Finish();
+
+        drivetrain::swerve::Output::Builder drivetrain_output_builder =
+            builder.MakeBuilder<drivetrain::swerve::Output>();
+
+        drivetrain_output_builder.add_front_left_output(swerve_module_offset);
+        drivetrain_output_builder.add_front_right_output(swerve_module_offset);
+        drivetrain_output_builder.add_back_left_output(swerve_module_offset);
+        drivetrain_output_builder.add_back_right_output(swerve_module_offset);
+
+        builder.CheckOk(builder.Send(drivetrain_output_builder.Finish()));
+
+        exit_handle->Exit();
+      })
+      ->Schedule(event_loop->monotonic_now() +
+                 std::chrono::milliseconds((int)duration));
+}
diff --git a/y2023_bot4/swerve_publisher_lib.h b/y2023_bot4/swerve_publisher_lib.h
new file mode 100644
index 0000000..5969b7b
--- /dev/null
+++ b/y2023_bot4/swerve_publisher_lib.h
@@ -0,0 +1,29 @@
+#ifndef Y2023_BOT4_SWERVE_PUBLISHER_H_
+#define Y2023_BOT4_SWERVE_PUBLISHER_H_
+
+#include "gflags/gflags.h"
+#include "glog/logging.h"
+
+#include "aos/events/event_loop.h"
+#include "aos/flatbuffer_merge.h"
+#include "aos/init.h"
+#include "aos/json_to_flatbuffer.h"
+#include "frc971/control_loops/drivetrain/swerve/swerve_drivetrain_output_generated.h"
+
+namespace y2023_bot4 {
+
+namespace drivetrain = frc971::control_loops::drivetrain;
+
+class SwervePublisher {
+ public:
+  SwervePublisher(aos::EventLoop *event_loop, aos::ExitHandle *exit_handle,
+                  const std::string &filename, double duration);
+
+ private:
+  aos::Sender<frc971::control_loops::drivetrain::swerve::Output>
+      drivetrain_output_sender_;
+};
+
+}  // namespace y2023_bot4
+
+#endif  // Y2023_BOT4_SWERVE_PUBLISHER_H_
diff --git a/y2023_bot4/swerve_publisher_lib_test.cc b/y2023_bot4/swerve_publisher_lib_test.cc
new file mode 100644
index 0000000..817f295
--- /dev/null
+++ b/y2023_bot4/swerve_publisher_lib_test.cc
@@ -0,0 +1,54 @@
+#include "y2023_bot4/swerve_publisher_lib.h"
+
+#include "gtest/gtest.h"
+
+#include "aos/events/simulated_event_loop.h"
+
+namespace y2023_bot4 {
+namespace testing {
+class SwervePublisherTest : public ::testing::Test {
+ public:
+  SwervePublisherTest()
+      : config_(aos::configuration::ReadConfig("y2023_bot4/aos_config.json")),
+        event_loop_factory_(&config_.message()),
+        roborio_(aos::configuration::GetNode(
+            event_loop_factory_.configuration(), "roborio")),
+        event_loop_(
+            event_loop_factory_.MakeEventLoop("swerve_publisher", roborio_)),
+        exit_handle_(event_loop_factory_.MakeExitHandle()),
+        drivetrain_swerve_output_fetcher_(
+            event_loop_->MakeFetcher<
+                frc971::control_loops::drivetrain::swerve::Output>(
+                "/drivetrain")),
+        swerve_publisher_(event_loop_.get(), exit_handle_.get(),
+                          "y2023_bot4/swerve_drivetrain_output.json", 100) {}
+
+  void SendOutput() { event_loop_factory_.Run(); }
+
+  void CheckOutput() {
+    drivetrain_swerve_output_fetcher_.Fetch();
+
+    ASSERT_TRUE(drivetrain_swerve_output_fetcher_.get() != nullptr)
+        << ": No drivetrain output";
+  }
+
+ private:
+  aos::FlatbufferDetachedBuffer<aos::Configuration> config_;
+  aos::SimulatedEventLoopFactory event_loop_factory_;
+  const aos::Node *const roborio_;
+
+  std::unique_ptr<aos::EventLoop> event_loop_;
+  std::unique_ptr<aos::ExitHandle> exit_handle_;
+
+  aos::Fetcher<frc971::control_loops::drivetrain::swerve::Output>
+      drivetrain_swerve_output_fetcher_;
+
+  y2023_bot4::SwervePublisher swerve_publisher_;
+};
+
+TEST_F(SwervePublisherTest, CheckSentFb) {
+  SendOutput();
+  CheckOutput();
+}
+}  // namespace testing
+}  // namespace y2023_bot4
diff --git a/y2023_bot4/swerve_publisher_main.cc b/y2023_bot4/swerve_publisher_main.cc
new file mode 100644
index 0000000..87470d7
--- /dev/null
+++ b/y2023_bot4/swerve_publisher_main.cc
@@ -0,0 +1,24 @@
+#include "aos/events/shm_event_loop.h"
+#include "y2023_bot4/swerve_publisher_lib.h"
+
+DEFINE_double(duration, 100.0, "Length of time in Ms to apply current for");
+DEFINE_string(drivetrain_position, "swerve_drivetrain_output.json",
+              "The path to the json drivetrain position to apply");
+DEFINE_string(config, "aos_config.json", "The path to aos_config.json");
+
+int main(int argc, char **argv) {
+  aos::InitGoogle(&argc, &argv);
+
+  aos::FlatbufferDetachedBuffer<aos::Configuration> config =
+      aos::configuration::ReadConfig(FLAGS_config);
+
+  aos::ShmEventLoop event_loop(&config.message());
+
+  std::unique_ptr<aos::ExitHandle> exit_handle = event_loop.MakeExitHandle();
+
+  y2023_bot4::SwervePublisher publisher(&event_loop, exit_handle.get(),
+                                        FLAGS_drivetrain_position,
+                                        FLAGS_duration);
+
+  event_loop.Run();
+}
diff --git a/y2023_bot4/wpilib_interface.cc b/y2023_bot4/wpilib_interface.cc
new file mode 100644
index 0000000..a744ae0
--- /dev/null
+++ b/y2023_bot4/wpilib_interface.cc
@@ -0,0 +1,325 @@
+#include "ctre/phoenix/cci/Diagnostics_CCI.h"
+#include "ctre/phoenix6/TalonFX.hpp"
+
+#include "aos/events/shm_event_loop.h"
+#include "aos/init.h"
+#include "frc971/control_loops/control_loops_generated.h"
+#include "frc971/wpilib/can_sensor_reader.h"
+#include "frc971/wpilib/falcon.h"
+#include "frc971/wpilib/sensor_reader.h"
+#include "frc971/wpilib/swerve/swerve_drivetrain_writer.h"
+#include "frc971/wpilib/wpilib_robot_base.h"
+#include "y2023_bot4/constants.h"
+#include "y2023_bot4/drivetrain_can_position_generated.h"
+#include "y2023_bot4/drivetrain_position_generated.h"
+
+DEFINE_bool(ctre_diag_server, false,
+            "If true, enable the diagnostics server for interacting with "
+            "devices on the CAN bus using Phoenix Tuner");
+
+using frc971::wpilib::CANSensorReader;
+using frc971::wpilib::Falcon;
+using frc971::wpilib::swerve::DrivetrainWriter;
+using frc971::wpilib::swerve::SwerveModule;
+
+namespace drivetrain = frc971::control_loops::drivetrain;
+
+namespace y2023_bot4 {
+namespace wpilib {
+namespace {
+
+template <class T>
+T value_or_exit(std::optional<T> optional) {
+  CHECK(optional.has_value());
+  return optional.value();
+}
+
+flatbuffers::Offset<frc971::AbsolutePosition> module_offset(
+    frc971::AbsolutePosition::Builder builder,
+    frc971::wpilib::AbsoluteEncoder *module) {
+  builder.add_encoder(module->ReadRelativeEncoder());
+  builder.add_absolute_encoder(module->ReadAbsoluteEncoder());
+
+  return builder.Finish();
+}
+
+flatbuffers::Offset<SwerveModuleCANPosition> can_module_offset(
+    SwerveModuleCANPosition::Builder builder,
+    std::shared_ptr<SwerveModule> module) {
+  std::optional<flatbuffers::Offset<control_loops::CANFalcon>> rotation_offset =
+      module->rotation->TakeOffset();
+  std::optional<flatbuffers::Offset<control_loops::CANFalcon>>
+      translation_offset = module->translation->TakeOffset();
+
+  CHECK(rotation_offset.has_value());
+  CHECK(translation_offset.has_value());
+
+  builder.add_rotation(rotation_offset.value());
+  builder.add_translation(translation_offset.value());
+
+  return builder.Finish();
+}
+
+constexpr double kMaxFastEncoderPulsesPerSecond = std::max({
+    constants::Values::kMaxDrivetrainEncoderPulsesPerSecond(),
+});
+static_assert(kMaxFastEncoderPulsesPerSecond <= 1300000,
+              "fast encoders are too fast");
+}  // namespace
+
+class SensorReader : public ::frc971::wpilib::SensorReader {
+ public:
+  SensorReader(aos::ShmEventLoop *event_loop,
+               std::shared_ptr<const constants::Values> values)
+      : ::frc971::wpilib::SensorReader(event_loop),
+        values_(values),
+        drivetrain_position_sender_(
+            event_loop->MakeSender<AbsoluteDrivetrainPosition>("/drivetrain")) {
+    UpdateFastEncoderFilterHz(kMaxFastEncoderPulsesPerSecond);
+    event_loop->SetRuntimeAffinity(aos::MakeCpusetFromCpus({0}));
+  }
+
+  void RunIteration() override {
+    {
+      auto builder = drivetrain_position_sender_.MakeBuilder();
+
+      auto front_left_offset =
+          module_offset(builder.MakeBuilder<frc971::AbsolutePosition>(),
+                        &front_left_encoder_);
+      auto front_right_offset =
+          module_offset(builder.MakeBuilder<frc971::AbsolutePosition>(),
+                        &front_right_encoder_);
+      auto back_left_offset = module_offset(
+          builder.MakeBuilder<frc971::AbsolutePosition>(), &back_left_encoder_);
+      auto back_right_offset =
+          module_offset(builder.MakeBuilder<frc971::AbsolutePosition>(),
+                        &back_right_encoder_);
+
+      AbsoluteDrivetrainPosition::Builder drivetrain_position_builder =
+          builder.MakeBuilder<AbsoluteDrivetrainPosition>();
+
+      drivetrain_position_builder.add_follower_wheel_one_position(
+          follower_wheel_one_encoder_->GetRaw());
+      drivetrain_position_builder.add_follower_wheel_two_position(
+          follower_wheel_two_encoder_->GetRaw());
+
+      drivetrain_position_builder.add_front_left_position(front_left_offset);
+      drivetrain_position_builder.add_front_right_position(front_right_offset);
+      drivetrain_position_builder.add_back_left_position(back_left_offset);
+      drivetrain_position_builder.add_back_right_position(back_right_offset);
+
+      builder.CheckOk(builder.Send(drivetrain_position_builder.Finish()));
+    }
+  }
+
+  void set_follower_wheel_one_encoder(std::unique_ptr<frc::Encoder> encoder) {
+    fast_encoder_filter_.Add(encoder.get());
+    follower_wheel_one_encoder_ = std::move(encoder);
+    follower_wheel_one_encoder_->SetMaxPeriod(0.005);
+  }
+  void set_follower_wheel_two_encoder(std::unique_ptr<frc::Encoder> encoder) {
+    fast_encoder_filter_.Add(encoder.get());
+    follower_wheel_two_encoder_ = std::move(encoder);
+    follower_wheel_two_encoder_->SetMaxPeriod(0.005);
+  }
+
+  void set_front_left_encoder(std::unique_ptr<frc::Encoder> encoder) {
+    fast_encoder_filter_.Add(encoder.get());
+    front_left_encoder_.set_encoder(std::move(encoder));
+  }
+  void set_front_left_absolute_pwm(
+      std::unique_ptr<frc::DigitalInput> absolute_pwm) {
+    front_left_encoder_.set_absolute_pwm(std::move(absolute_pwm));
+  }
+
+  void set_front_right_encoder(std::unique_ptr<frc::Encoder> encoder) {
+    fast_encoder_filter_.Add(encoder.get());
+    front_right_encoder_.set_encoder(std::move(encoder));
+  }
+  void set_front_right_absolute_pwm(
+      std::unique_ptr<frc::DigitalInput> absolute_pwm) {
+    front_right_encoder_.set_absolute_pwm(std::move(absolute_pwm));
+  }
+
+  void set_back_left_encoder(std::unique_ptr<frc::Encoder> encoder) {
+    fast_encoder_filter_.Add(encoder.get());
+    back_left_encoder_.set_encoder(std::move(encoder));
+  }
+  void set_back_left_absolute_pwm(
+      std::unique_ptr<frc::DigitalInput> absolute_pwm) {
+    back_left_encoder_.set_absolute_pwm(std::move(absolute_pwm));
+  }
+
+  void set_back_right_encoder(std::unique_ptr<frc::Encoder> encoder) {
+    fast_encoder_filter_.Add(encoder.get());
+    back_right_encoder_.set_encoder(std::move(encoder));
+  }
+  void set_back_right_absolute_pwm(
+      std::unique_ptr<frc::DigitalInput> absolute_pwm) {
+    back_right_encoder_.set_absolute_pwm(std::move(absolute_pwm));
+  }
+
+ private:
+  std::shared_ptr<const constants::Values> values_;
+
+  aos::Sender<AbsoluteDrivetrainPosition> drivetrain_position_sender_;
+
+  std::unique_ptr<frc::Encoder> follower_wheel_one_encoder_,
+      follower_wheel_two_encoder_;
+
+  frc971::wpilib::AbsoluteEncoder front_left_encoder_, front_right_encoder_,
+      back_left_encoder_, back_right_encoder_;
+};
+
+class WPILibRobot : public ::frc971::wpilib::WPILibRobotBase {
+ public:
+  ::std::unique_ptr<frc::Encoder> make_encoder(int index) {
+    return std::make_unique<frc::Encoder>(10 + index * 2, 11 + index * 2, false,
+                                          frc::Encoder::k4X);
+  }
+  void Run() override {
+    std::shared_ptr<const constants::Values> values =
+        std::make_shared<const constants::Values>(constants::MakeValues());
+
+    aos::FlatbufferDetachedBuffer<aos::Configuration> config =
+        aos::configuration::ReadConfig("aos_config.json");
+
+    std::vector<ctre::phoenix6::BaseStatusSignal *> signals_registry;
+    std::vector<std::shared_ptr<Falcon>> falcons;
+
+    // TODO(max): Change the CanBus names with TalonFX software.
+    std::shared_ptr<SwerveModule> front_left = std::make_shared<SwerveModule>(
+        frc971::wpilib::FalconParams{6, false},
+        frc971::wpilib::FalconParams{5, false}, "Drivetrain Bus",
+        &signals_registry, constants::Values::kDrivetrainStatorCurrentLimit(),
+        constants::Values::kDrivetrainSupplyCurrentLimit());
+    std::shared_ptr<SwerveModule> front_right = std::make_shared<SwerveModule>(
+        frc971::wpilib::FalconParams{3, false},
+        frc971::wpilib::FalconParams{4, false}, "Drivetrain Bus",
+        &signals_registry, constants::Values::kDrivetrainStatorCurrentLimit(),
+        constants::Values::kDrivetrainSupplyCurrentLimit());
+    std::shared_ptr<SwerveModule> back_left = std::make_shared<SwerveModule>(
+        frc971::wpilib::FalconParams{8, false},
+        frc971::wpilib::FalconParams{7, false}, "Drivetrain Bus",
+        &signals_registry, constants::Values::kDrivetrainStatorCurrentLimit(),
+        constants::Values::kDrivetrainSupplyCurrentLimit());
+    std::shared_ptr<SwerveModule> back_right = std::make_shared<SwerveModule>(
+        frc971::wpilib::FalconParams{2, false},
+        frc971::wpilib::FalconParams{1, false}, "Drivetrain Bus",
+        &signals_registry, constants::Values::kDrivetrainStatorCurrentLimit(),
+        constants::Values::kDrivetrainSupplyCurrentLimit());
+
+    // Thread 1
+    aos::ShmEventLoop can_sensor_reader_event_loop(&config.message());
+    can_sensor_reader_event_loop.set_name("CANSensorReader");
+
+    falcons.push_back(front_left->rotation);
+    falcons.push_back(front_left->translation);
+
+    falcons.push_back(front_right->rotation);
+    falcons.push_back(front_right->translation);
+
+    falcons.push_back(back_left->rotation);
+    falcons.push_back(back_left->translation);
+
+    falcons.push_back(back_right->rotation);
+    falcons.push_back(back_right->translation);
+
+    aos::Sender<AbsoluteCANPosition> can_position_sender =
+        can_sensor_reader_event_loop.MakeSender<AbsoluteCANPosition>(
+            "/drivetrain");
+
+    CANSensorReader can_sensor_reader(
+        &can_sensor_reader_event_loop, std::move(signals_registry), falcons,
+        [this, falcons, front_left, front_right, back_left, back_right,
+         &can_position_sender](ctre::phoenix::StatusCode status) {
+          // TODO(max): use status properly in the flatbuffer.
+          (void)status;
+
+          auto builder = can_position_sender.MakeBuilder();
+
+          for (auto falcon : falcons) {
+            falcon->RefreshNontimesyncedSignals();
+            falcon->SerializePosition(builder.fbb(), 1.0);
+          }
+
+          auto front_left_offset = can_module_offset(
+              builder.MakeBuilder<SwerveModuleCANPosition>(), front_left);
+          auto front_right_offset = can_module_offset(
+              builder.MakeBuilder<SwerveModuleCANPosition>(), front_right);
+          auto back_left_offset = can_module_offset(
+              builder.MakeBuilder<SwerveModuleCANPosition>(), back_left);
+          auto back_right_offset = can_module_offset(
+              builder.MakeBuilder<SwerveModuleCANPosition>(), back_right);
+
+          AbsoluteCANPosition::Builder can_position_builder =
+              builder.MakeBuilder<AbsoluteCANPosition>();
+
+          can_position_builder.add_front_left(front_left_offset);
+          can_position_builder.add_front_right(front_right_offset);
+          can_position_builder.add_back_left(back_left_offset);
+          can_position_builder.add_back_right(back_right_offset);
+
+          builder.CheckOk(builder.Send(can_position_builder.Finish()));
+        });
+
+    AddLoop(&can_sensor_reader_event_loop);
+
+    // Thread 2
+    // Setup CAN
+    if (!FLAGS_ctre_diag_server) {
+      c_Phoenix_Diagnostics_SetSecondsToStart(-1);
+      c_Phoenix_Diagnostics_Dispose();
+    }
+
+    ctre::phoenix::platform::can::CANComm_SetRxSchedPriority(
+        constants::Values::kDrivetrainRxPriority, true, "Drivetrain Bus");
+    ctre::phoenix::platform::can::CANComm_SetTxSchedPriority(
+        constants::Values::kDrivetrainTxPriority, true, "Drivetrain Bus");
+
+    aos::ShmEventLoop drivetrain_writer_event_loop(&config.message());
+    drivetrain_writer_event_loop.set_name("DrivetrainWriter");
+
+    DrivetrainWriter drivetrain_writer(
+        &drivetrain_writer_event_loop,
+        constants::Values::kDrivetrainWriterPriority, 12);
+
+    drivetrain_writer.set_falcons(front_left, front_right, back_left,
+                                  back_right);
+
+    AddLoop(&drivetrain_writer_event_loop);
+
+    // Thread 3
+    aos::ShmEventLoop sensor_reader_event_loop(&config.message());
+    sensor_reader_event_loop.set_name("SensorReader");
+    SensorReader sensor_reader(&sensor_reader_event_loop, values);
+
+    sensor_reader.set_follower_wheel_one_encoder(make_encoder(4));
+    sensor_reader.set_follower_wheel_two_encoder(make_encoder(5));
+
+    sensor_reader.set_front_left_encoder(make_encoder(1));
+    sensor_reader.set_front_left_absolute_pwm(
+        std::make_unique<frc::DigitalInput>(1));
+
+    sensor_reader.set_front_right_encoder(make_encoder(0));
+    sensor_reader.set_front_right_absolute_pwm(
+        std::make_unique<frc::DigitalInput>(0));
+
+    sensor_reader.set_back_left_encoder(make_encoder(2));
+    sensor_reader.set_back_left_absolute_pwm(
+        std::make_unique<frc::DigitalInput>(2));
+
+    sensor_reader.set_back_right_encoder(make_encoder(3));
+    sensor_reader.set_back_right_absolute_pwm(
+        std::make_unique<frc::DigitalInput>(3));
+
+    AddLoop(&sensor_reader_event_loop);
+
+    RunLoops();
+  }
+};
+
+}  // namespace wpilib
+}  // namespace y2023_bot4
+
+AOS_ROBOT_CLASS(::y2023_bot4::wpilib::WPILibRobot)
diff --git a/y2023_bot4/y2023_bot4.json b/y2023_bot4/y2023_bot4.json
new file mode 100644
index 0000000..49db6fc
--- /dev/null
+++ b/y2023_bot4/y2023_bot4.json
@@ -0,0 +1,19 @@
+{
+  "channel_storage_duration": 10000000000,
+  "maps": [
+    {
+      "match": {
+        "name": "/aos",
+        "type": "aos.RobotState"
+      },
+      "rename": {
+        "name": "/roborio/aos"
+      }
+    }
+  ],
+  "imports": [
+    "y2023_bot4_roborio.json",
+    "y2023_bot4_imu.json",
+    "y2023_bot4_logger.json"
+  ]
+}
diff --git a/y2023_bot4/y2023_bot4_imu.json b/y2023_bot4/y2023_bot4_imu.json
new file mode 100644
index 0000000..274b158
--- /dev/null
+++ b/y2023_bot4/y2023_bot4_imu.json
@@ -0,0 +1,212 @@
+{
+  "channels": [
+    {
+      "name": "/imu/aos",
+      "type": "aos.timing.Report",
+      "source_node": "imu",
+      "frequency": 50,
+      "num_senders": 20,
+      "max_size": 4096
+    },
+    {
+      "name": "/imu/aos",
+      "type": "aos.logging.LogMessageFbs",
+      "source_node": "imu",
+      "frequency": 200,
+      "num_senders": 20
+    },
+    {
+      "name": "/imu/aos",
+      "type": "aos.starter.Status",
+      "source_node": "imu",
+      "frequency": 50,
+      "num_senders": 20,
+      "max_size": 2048
+    },
+    {
+      "name": "/imu/aos",
+      "type": "aos.starter.StarterRpc",
+      "source_node": "imu",
+      "frequency": 10,
+      "num_senders": 2
+    },
+    {
+      "name": "/imu/aos",
+      "type": "aos.message_bridge.ServerStatistics",
+      "source_node": "imu",
+      "frequency": 10,
+      "num_senders": 2
+    },
+    {
+      "name": "/imu/aos",
+      "type": "aos.message_bridge.ClientStatistics",
+      "source_node": "imu",
+      "frequency": 20,
+      "num_senders": 2
+    },
+    {
+      "name": "/imu/aos",
+      "type": "aos.logging.DynamicLogCommand",
+      "source_node": "imu",
+      "frequency": 10,
+      "num_senders": 2
+    },
+    {
+      "name": "/imu/aos",
+      "type": "aos.message_bridge.Timestamp",
+      "source_node": "imu",
+      "frequency": 15,
+      "num_senders": 2,
+      "logger": "LOCAL_AND_REMOTE_LOGGER",
+      "logger_nodes": [
+        "roborio",
+        "logger"
+      ],
+      "max_size": 400,
+      "destination_nodes": [
+        {
+          "name": "roborio",
+          "priority": 1,
+          "timestamp_logger": "LOCAL_AND_REMOTE_LOGGER",
+          "timestamp_logger_nodes": [
+            "imu"
+          ],
+          "time_to_live": 5000000
+        },
+        {
+          "name": "logger",
+          "priority": 1,
+          "timestamp_logger": "LOCAL_AND_REMOTE_LOGGER",
+          "timestamp_logger_nodes": [
+            "imu"
+          ],
+          "time_to_live": 5000000
+        }
+      ]
+    },
+    {
+      "name": "/imu/aos/remote_timestamps/roborio/imu/aos/aos-message_bridge-Timestamp",
+      "type": "aos.message_bridge.RemoteMessage",
+      "frequency": 20,
+      "source_node": "imu",
+      "max_size": 208
+    },
+    {
+      "name": "/imu/aos/remote_timestamps/logger/imu/aos/aos-message_bridge-Timestamp",
+      "type": "aos.message_bridge.RemoteMessage",
+      "frequency": 20,
+      "source_node": "imu",
+      "max_size": 208
+    },
+    {
+      "name": "/roborio/aos",
+      "type": "aos.starter.StarterRpc",
+      "source_node": "roborio",
+      "logger": "LOCAL_AND_REMOTE_LOGGER",
+      "logger_nodes": [
+        "imu"
+      ],
+      "destination_nodes": [
+        {
+          "name": "imu",
+          "priority": 5,
+          "timestamp_logger": "LOCAL_AND_REMOTE_LOGGER",
+          "timestamp_logger_nodes": [
+            "roborio"
+          ],
+          "time_to_live": 5000000
+        }
+      ]
+    },
+    {
+      "name": "/roborio/aos/remote_timestamps/imu/roborio/aos/aos-starter-StarterRpc",
+      "type": "aos.message_bridge.RemoteMessage",
+      "source_node": "roborio",
+      "logger": "NOT_LOGGED",
+      "frequency": 20,
+      "num_senders": 2,
+      "max_size": 200
+    },
+    {
+      "name": "/localizer",
+      "type": "frc971.IMUValuesBatch",
+      "source_node": "imu",
+      "frequency": 2200,
+      "max_size": 1600,
+      "num_senders": 2
+    }
+  ],
+  "applications": [
+    {
+      "name": "message_bridge_client",
+      "nodes": [
+        "imu"
+      ]
+    },
+    {
+      "name": "imu",
+      "executable_name": "imu_main",
+      "user": "pi",
+      "nodes": [
+        "imu"
+      ]
+    },
+    {
+      "name": "message_bridge_server",
+      "executable_name": "message_bridge_server",
+      "user": "pi",
+      "nodes": [
+        "imu"
+      ]
+    },
+    {
+      "name": "web_proxy",
+      "executable_name": "web_proxy_main",
+      "args": [
+        "--min_ice_port=5800",
+        "--max_ice_port=5810"
+      ],
+      "user": "pi",
+      "nodes": [
+        "imu"
+      ]
+    },
+    {
+      "name": "foxglove_websocket",
+      "user": "pi",
+      "nodes": [
+        "imu"
+      ]
+    }
+  ],
+  "maps": [
+    {
+      "match": {
+        "name": "/aos*",
+        "source_node": "imu"
+      },
+      "rename": {
+        "name": "/imu/aos"
+      }
+    }
+  ],
+  "nodes": [
+    {
+      "name": "imu",
+      "hostname": "pi6",
+      "hostnames": [
+        "pi-971-6",
+        "pi-7971-6",
+        "pi-8971-6",
+        "pi-9971-6"
+      ],
+      "port": 9971
+    },
+    {
+      "name": "logger"
+    },
+    {
+      "name": "roborio"
+    }
+  ]
+}
diff --git a/y2023_bot4/y2023_bot4_logger.json b/y2023_bot4/y2023_bot4_logger.json
new file mode 100644
index 0000000..5cca31f
--- /dev/null
+++ b/y2023_bot4/y2023_bot4_logger.json
@@ -0,0 +1,190 @@
+{
+  "channels": [
+    {
+      "name": "/roborio/aos",
+      "type": "aos.message_bridge.Timestamp",
+      "source_node": "roborio",
+      "logger": "LOCAL_AND_REMOTE_LOGGER",
+      "logger_nodes": [
+        "logger"
+      ],
+      "destination_nodes": [
+        {
+          "name": "logger",
+          "priority": 1,
+          "time_to_live": 5000000,
+          "timestamp_logger": "LOCAL_AND_REMOTE_LOGGER",
+          "timestamp_logger_nodes": [
+            "roborio"
+          ]
+        }
+      ]
+    },
+    {
+      "name": "/logger/aos",
+      "type": "aos.timing.Report",
+      "source_node": "logger",
+      "frequency": 50,
+      "num_senders": 20,
+      "max_size": 4096
+    },
+    {
+      "name": "/logger/aos",
+      "type": "aos.logging.LogMessageFbs",
+      "source_node": "logger",
+      "frequency": 400,
+      "num_senders": 20
+    },
+    {
+      "name": "/logger/aos",
+      "type": "aos.message_bridge.ServerStatistics",
+      "source_node": "logger",
+      "frequency": 10,
+      "num_senders": 2
+    },
+    {
+      "name": "/logger/aos",
+      "type": "aos.message_bridge.ClientStatistics",
+      "source_node": "logger",
+      "frequency": 20,
+      "max_size": 2000,
+      "num_senders": 2
+    },
+    {
+      "name": "/logger/aos",
+      "type": "aos.logging.DynamicLogCommand",
+      "source_node": "logger",
+      "frequency": 10,
+      "num_senders": 2
+    },
+    {
+      "name": "/logger/aos",
+      "type": "aos.starter.Status",
+      "source_node": "logger",
+      "frequency": 50,
+      "num_senders": 20,
+      "max_size": 2000
+    },
+    {
+      "name": "/logger/aos",
+      "type": "aos.starter.StarterRpc",
+      "source_node": "logger",
+      "frequency": 10,
+      "num_senders": 2
+    },
+    {
+      "name": "/logger/aos",
+      "type": "aos.message_bridge.Timestamp",
+      "source_node": "logger",
+      "frequency": 15,
+      "num_senders": 2,
+      "max_size": 400,
+      "logger": "LOCAL_AND_REMOTE_LOGGER",
+      "logger_nodes": [
+        "imu",
+        "roborio"
+      ],
+      "destination_nodes": [
+        {
+          "name": "imu",
+          "priority": 1,
+          "time_to_live": 5000000,
+          "timestamp_logger": "LOCAL_AND_REMOTE_LOGGER",
+          "timestamp_logger_nodes": [
+            "logger"
+          ]
+        },
+        {
+          "name": "roborio",
+          "priority": 1,
+          "time_to_live": 5000000,
+          "timestamp_logger": "LOCAL_AND_REMOTE_LOGGER",
+          "timestamp_logger_nodes": [
+            "logger"
+          ]
+        }
+      ]
+    },
+    {
+      "name": "/logger/aos/remote_timestamps/imu/logger/aos/aos-message_bridge-Timestamp",
+      "type": "aos.message_bridge.RemoteMessage",
+      "source_node": "logger",
+      "logger": "NOT_LOGGED",
+      "frequency": 20,
+      "num_senders": 2,
+      "max_size": 200
+    },
+    {
+      "name": "/logger/aos/remote_timestamps/roborio/logger/aos/aos-message_bridge-Timestamp",
+      "type": "aos.message_bridge.RemoteMessage",
+      "source_node": "logger",
+      "logger": "NOT_LOGGED",
+      "frequency": 20,
+      "num_senders": 2,
+      "max_size": 200
+    }
+  ],
+    "maps": [
+    {
+      "match": {
+        "name": "/aos*",
+        "source_node": "logger"
+      },
+      "rename": {
+        "name": "/logger/aos"
+      }
+    },
+    {
+      "match": {
+        "name": "/constants*",
+        "source_node": "logger"
+      },
+      "rename": {
+        "name": "/logger/constants"
+      }
+    },
+    {
+      "match": {
+        "name": "/camera*",
+        "source_node": "logger"
+      },
+      "rename": {
+        "name": "/logger/camera"
+      }
+    }
+  ],
+  "applications": [
+    {
+      "name": "message_bridge_client",
+      "nodes": [
+        "logger"
+      ]
+    },
+    {
+      "name": "message_bridge_server",
+      "executable_name": "message_bridge_server",
+      "user": "pi",
+      "nodes": [
+        "logger"
+      ]
+    }
+  ],
+  "nodes": [
+    {
+      "name": "logger",
+      "hostname": "pi5",
+      "hostnames": [
+        "pi-971-5",
+        "pi-9971-5",
+        "pi-7971-5"
+      ],
+      "port": 9971
+    },
+    {
+      "name": "imu"
+    },
+    {
+      "name": "roborio"
+    }
+    ]
+}
diff --git a/y2023_bot4/y2023_bot4_roborio.json b/y2023_bot4/y2023_bot4_roborio.json
new file mode 100644
index 0000000..7ff175c
--- /dev/null
+++ b/y2023_bot4/y2023_bot4_roborio.json
@@ -0,0 +1,350 @@
+{
+    "channels": [
+        {
+            "name": "/roborio/aos",
+            "type": "aos.RobotState",
+            "source_node": "roborio",
+            "frequency": 250
+        },
+        {
+            "name": "/roborio/aos",
+            "type": "aos.timing.Report",
+            "source_node": "roborio",
+            "frequency": 50,
+            "num_senders": 20,
+            "max_size": 8192
+        },
+        {
+            "name": "/roborio/aos",
+            "type": "aos.logging.LogMessageFbs",
+            "source_node": "roborio",
+            "frequency": 500,
+            "max_size": 1000,
+            "num_senders": 20
+        },
+        {
+            "name": "/roborio/aos",
+            "type": "aos.starter.Status",
+            "source_node": "roborio",
+            "frequency": 50,
+            "num_senders": 20,
+            "max_size": 2000
+        },
+        {
+            "name": "/roborio/aos",
+            "type": "aos.starter.StarterRpc",
+            "source_node": "roborio",
+            "frequency": 10,
+            "max_size": 400,
+            "num_senders": 2
+        },
+        {
+            "name": "/roborio/aos",
+            "type": "aos.message_bridge.ServerStatistics",
+            "source_node": "roborio",
+            "frequency": 10,
+            "num_senders": 2
+        },
+        {
+            "name": "/roborio/aos",
+            "type": "aos.message_bridge.ClientStatistics",
+            "source_node": "roborio",
+            "frequency": 20,
+            "max_size": 2000,
+            "num_senders": 2
+        },
+        {
+            "name": "/roborio/aos",
+            "type": "aos.logging.DynamicLogCommand",
+            "source_node": "roborio",
+            "frequency": 10,
+            "num_senders": 2
+        },
+        {
+            "name": "/roborio/aos/remote_timestamps/imu/roborio/aos/aos-message_bridge-Timestamp",
+            "type": "aos.message_bridge.RemoteMessage",
+            "frequency": 20,
+            "source_node": "roborio",
+            "max_size": 208
+        },
+        {
+            "name": "/roborio/aos/remote_timestamps/logger/roborio/aos/aos-message_bridge-Timestamp",
+            "type": "aos.message_bridge.RemoteMessage",
+            "frequency": 300,
+            "source_node": "roborio"
+        },
+        {
+            "name": "/roborio/aos",
+            "type": "aos.message_bridge.Timestamp",
+            "source_node": "roborio",
+            "frequency": 15,
+            "num_senders": 2,
+            "max_size": 512,
+            "logger": "LOCAL_AND_REMOTE_LOGGER",
+            "logger_nodes": [
+                "imu"
+            ],
+            "destination_nodes": [
+                {
+                    "name": "imu",
+                    "priority": 1,
+                    "timestamp_logger": "LOCAL_AND_REMOTE_LOGGER",
+                    "timestamp_logger_nodes": [
+                        "roborio"
+                    ],
+                    "time_to_live": 5000000
+                }
+            ]
+        },
+        {
+            "name": "/drivetrain",
+            "type": "y2023_bot4.AbsoluteDrivetrainPosition",
+            "source_node": "roborio",
+            "frequency": 250,
+            "num_senders": 1,
+            "max_size": 480
+        },
+        {
+            "name": "/drivetrain",
+            "type": "y2023_bot4.AbsoluteCANPosition",
+            "source_node": "roborio",
+            "frequency": 250,
+            "num_senders": 1,
+            "max_size": 480
+        },
+        {
+            "name": "/can",
+            "type": "frc971.can_logger.CanFrame",
+            "source_node": "roborio",
+            "frequency": 6000,
+            "num_senders": 2,
+            "max_size": 200
+        },
+        {
+            "name": "/drivetrain",
+            "type": "frc971.control_loops.drivetrain.CANPosition",
+            "source_node": "roborio",
+            "frequency": 220,
+            "num_senders": 2,
+            "max_size": 400
+        },
+        {
+            "name": "/drivetrain",
+            "type": "frc971.control_loops.drivetrain.SplineGoal",
+            "source_node": "roborio",
+            "frequency": 10
+        },
+        {
+            "name": "/drivetrain",
+            "type": "frc971.control_loops.drivetrain.Goal",
+            "source_node": "roborio",
+            "max_size": 224,
+            "frequency": 250
+        },
+        {
+            "name": "/drivetrain",
+            "type": "frc971.control_loops.drivetrain.swerve.Position",
+            "source_node": "roborio",
+            "frequency": 400,
+            "max_size": 112,
+            "num_senders": 2
+        },
+        {
+            "name": "/drivetrain",
+            "type": "frc971.control_loops.drivetrain.swerve.Output",
+            "source_node": "roborio",
+            "frequency": 400,
+            "max_size": 200,
+            "num_senders": 2,
+            "logger": "LOCAL_AND_REMOTE_LOGGER",
+            "logger_nodes": [
+                "imu"
+            ],
+            "destination_nodes": [
+                {
+                    "name": "imu",
+                    "priority": 5,
+                    "time_to_live": 5000000
+                }
+            ]
+        },
+        {
+            "name": "/drivetrain",
+            "type": "frc971.control_loops.drivetrain.Status",
+            "source_node": "roborio",
+            "frequency": 400,
+            "max_size": 1616,
+            "num_senders": 2
+        },
+        {
+            "name": "/drivetrain",
+            "type": "frc971.control_loops.drivetrain.LocalizerControl",
+            "source_node": "roborio",
+            "frequency": 250,
+            "max_size": 96,
+            "logger": "LOCAL_AND_REMOTE_LOGGER",
+            "logger_nodes": [
+                "imu"
+            ],
+            "destination_nodes": [
+                {
+                    "name": "imu",
+                    "priority": 5,
+                    "timestamp_logger": "LOCAL_AND_REMOTE_LOGGER",
+                    "timestamp_logger_nodes": [
+                        "roborio"
+                    ],
+                    "time_to_live": 0
+                }
+            ]
+        },
+        {
+            "name": "/roborio/aos/remote_timestamps/imu/drivetrain/frc971-control_loops-drivetrain-LocalizerControl",
+            "type": "aos.message_bridge.RemoteMessage",
+            "source_node": "roborio",
+            "logger": "NOT_LOGGED",
+            "frequency": 400,
+            "num_senders": 2,
+            "max_size": 200
+        },
+        {
+            "name": "/autonomous",
+            "type": "aos.common.actions.Status",
+            "source_node": "roborio"
+        },
+        {
+            "name": "/autonomous",
+            "type": "frc971.autonomous.Goal",
+            "source_node": "roborio"
+        },
+        {
+            "name": "/autonomous",
+            "type": "frc971.autonomous.AutonomousMode",
+            "source_node": "roborio",
+            "frequency": 250
+        },
+        {
+            "name": "/roborio/aos",
+            "type": "frc971.PDPValues",
+            "source_node": "roborio",
+            "frequency": 55,
+            "max_size": 368
+        },
+        {
+            "name": "/roborio/aos",
+            "type": "frc971.wpilib.PneumaticsToLog",
+            "source_node": "roborio",
+            "frequency": 50
+        },
+        {
+            "name": "/roborio",
+            "type": "frc971.CANConfiguration",
+            "source_node": "roborio",
+            "frequency": 2
+        }
+    ],
+    "applications": [
+        {
+            "name": "wpilib_interface",
+            "executable_name": "wpilib_interface",
+            "args": [
+                "--nodie_on_malloc",
+                "--ctre_diag_server"
+            ],
+            "nodes": [
+                "roborio"
+            ]
+        },
+        {
+            "name": "swerve_publisher",
+            "executable_name": "swerve_publisher",
+            "autostart": false,
+            "nodes": [
+                "roborio"
+            ]
+        },
+        {
+            "name": "roborio_web_proxy",
+            "executable_name": "web_proxy_main",
+            "args": [
+                "--min_ice_port=5800",
+                "--max_ice_port=5810"
+            ],
+            "nodes": [
+                "roborio"
+            ]
+        },
+        {
+            "name": "roborio_message_bridge_client",
+            "executable_name": "message_bridge_client",
+            "args": [
+                "--rt_priority=16",
+                "--sinit_max_init_timeout=5000"
+            ],
+            "nodes": [
+                "roborio"
+            ]
+        },
+        {
+            "name": "roborio_message_bridge_server",
+            "executable_name": "message_bridge_server",
+            "args": [
+                "--rt_priority=16"
+            ],
+            "nodes": [
+                "roborio"
+            ]
+        },
+        {
+            "name": "logger",
+            "executable_name": "logger_main",
+            "args": [
+                "--snappy_compress",
+                "--logging_folder=/home/admin/logs/",
+                "--rotate_every",
+                "30.0"
+            ],
+            "nodes": [
+                "roborio"
+            ]
+        },
+        {
+            "name": "can_logger",
+            "executable_name": "can_logger",
+            "nodes": [
+                "roborio"
+            ]
+        }
+    ],
+    "maps": [
+        {
+            "match": {
+                "name": "/aos*",
+                "source_node": "roborio"
+            },
+            "rename": {
+                "name": "/roborio/aos"
+            }
+        }
+    ],
+    "nodes": [
+        {
+            "name": "roborio",
+            "hostname": "roborio",
+            "hostnames": [
+                "roboRIO-971-FRC",
+                "roboRIO-6971-FRC",
+                "roboRIO-7971-FRC",
+                "roboRIO-8971-FRC",
+                "roboRIO-9971-FRC"
+            ],
+            "port": 9971
+        },
+        {
+            "name": "imu"
+        },
+        {
+            "name": "logger"
+        }
+    ]
+}