diff --git a/src/xrt/auxiliary/util/u_iterator_base.hpp b/src/xrt/auxiliary/util/u_iterator_base.hpp
new file mode 100644
index 000000000..61e5d1b09
--- /dev/null
+++ b/src/xrt/auxiliary/util/u_iterator_base.hpp
@@ -0,0 +1,360 @@
+// Copyright 2021-2022, Collabora, Ltd.
+//
+// SPDX-License-Identifier: BSL-1.0
+/*!
+ * @file
+ *
+ * @brief A template class to serve as the base of iterator and const_iterator
+ * types for things with "random access".
+ * @author Ryan Pavlik <ryan.pavlik@collabora.com>
+ * @ingroup aux_util
+ */
+
+#pragma once
+
+#include <stdexcept>
+#include <limits>
+#include <iterator>
+
+namespace xrt::auxiliary::util {
+/*!
+ * @brief Template for base class used by "random-access" iterators and const_iterators, providing all the functionality
+ * that is independent of element type and const-ness of the iterator.
+ *
+ * Using inheritance instead of composition here to more easily satisfy the C++ standard's requirements for
+ * iterators (e.g. that const iterators and iterators are comparable, etc.)
+ *
+ * All invalid instances will compare as equal, as required by the spec, but they are not all equivalent. You can freely
+ * go "past the end" (they will be invalid, so cannot dereference, but you can get them back to valid), but you can't go
+ * "past the beginning". That is, you can do `*(buf.end() - 1)` successfully if your buffer has at least one element,
+ * even though `buf.end()` is invalid.
+ *
+ * @tparam ContainerOrHelper Your container or some member thereof that provides a size() method. If it's a helper
+ * instead of the actual container, make it const.
+ *
+ */
+template <typename ContainerOrHelper> class RandomAccessIteratorBase
+{
+public:
+	using iterator_category = std::random_access_iterator_tag;
+	using difference_type = std::ptrdiff_t;
+
+
+	//! Is this iterator valid?
+	bool
+	valid() const noexcept;
+
+	//! Is this iterator valid?
+	explicit operator bool() const noexcept
+	{
+		return valid();
+	}
+
+	//! What is the index stored by this iterator?
+	size_t
+	index() const noexcept
+	{
+		return index_;
+	}
+
+	//! Is this iterator pointing "past the end" of the container?
+	bool
+	is_past_the_end() const noexcept
+	{
+		return container_ != nullptr && index_ >= container_->size();
+	}
+
+	/*!
+	 * @brief True if this iterator is "irrecoverably" invalid (aka, cleared/default constructed).
+	 *
+	 * Implies !valid() but is stronger. `buf.end().is_cleared()` is false.
+	 */
+	bool
+	is_cleared() const noexcept
+	{
+		return container_ == nullptr;
+	}
+
+	/*!
+	 * @brief Compute the difference between two iterators.
+	 *
+	 * - If both are cleared, the result is 0.
+	 * - Otherwise the result is the difference in index.
+	 *
+	 * @throws std::logic_error if exactly one of the iterators is cleared
+	 * @throws std::out_of_range if at least one of the iterators has an index larger than the maximum value of
+	 * std::ptrdiff_t.
+	 */
+	std::ptrdiff_t
+	operator-(const RandomAccessIteratorBase<ContainerOrHelper> &other) const;
+
+	/*!
+	 * @brief Increment by an arbitrary value.
+	 */
+	RandomAccessIteratorBase &
+	operator+=(std::ptrdiff_t n);
+
+	/*!
+	 * @brief Decrement by an arbitrary value.
+	 */
+	RandomAccessIteratorBase &
+	operator-=(std::ptrdiff_t n);
+
+	/*!
+	 * @brief Add some arbitrary amount to a copy of this iterator.
+	 */
+	RandomAccessIteratorBase
+	operator+(std::ptrdiff_t n) const;
+
+	/*!
+	 * @brief Subtract some arbitrary amount from a copy of this iterator.
+	 */
+	RandomAccessIteratorBase
+	operator-(std::ptrdiff_t n) const;
+
+	//! Factory function: construct the "begin" iterator
+	static RandomAccessIteratorBase
+	begin(ContainerOrHelper &container)
+	{
+		return RandomAccessIteratorBase(container, 0);
+	}
+
+	//! Factory function: construct the "past the end" iterator that can be decremented safely
+	static RandomAccessIteratorBase
+	end(ContainerOrHelper &container)
+	{
+		return RandomAccessIteratorBase(container, container.size());
+	}
+
+
+	/**
+	 * @brief Default constructor - initializes to "cleared" aka irrecoverably invalid
+	 */
+	RandomAccessIteratorBase() = default;
+
+	/**
+	 * @brief Constructor from a helper/container and index
+	 *
+	 * @param container The helper or container we will iterate through.
+	 * @param index An index - may be out of range.
+	 */
+	explicit RandomAccessIteratorBase(ContainerOrHelper &container, size_t index);
+
+
+	using const_iterator_base = std::conditional_t<std::is_const<ContainerOrHelper>::value,
+	                                               RandomAccessIteratorBase,
+	                                               RandomAccessIteratorBase<std::add_const_t<ContainerOrHelper>>>;
+
+	/**
+	 * @brief Get a const iterator base pointing to the same element as this element.
+	 *
+	 * @return const_iterator_base
+	 */
+	const_iterator_base
+	as_const() const
+	{
+		if (is_cleared()) {
+			return {};
+		}
+
+		return const_iterator_base{*container_, index_};
+	}
+
+protected:
+	//! for internal use
+	RandomAccessIteratorBase(ContainerOrHelper *container, size_t index) : container_(container), index_(index) {}
+
+	//! Increment an arbitrary amount
+	void
+	increment_n(std::size_t n);
+
+	//! Decrement an arbitrary amount
+	void
+	decrement_n(std::size_t n);
+
+	//! Access the container or helper
+	ContainerOrHelper *
+	container() const noexcept
+	{
+		return container_;
+	}
+
+private:
+	/**
+	 * @brief The container or helper we're associated with.
+	 *
+	 * If we were created knowing a container, this pointer is non-null.
+	 * Used to determine if an index is in bounds.
+	 * If this is null, the iterator is irrecoverably invalid.
+	 */
+	ContainerOrHelper *container_{nullptr};
+
+	//! This is the index in the container. May be out-of-range.
+	size_t index_{0};
+};
+
+/*!
+ * @brief Equality comparison operator for @ref RandomAccessIteratorBase
+ *
+ * @relates RandomAccessIteratorBase
+ */
+template <typename ContainerOrHelper>
+static inline bool
+operator==(RandomAccessIteratorBase<ContainerOrHelper> const &lhs,
+           RandomAccessIteratorBase<ContainerOrHelper> const &rhs) noexcept
+{
+	const bool lhs_valid = lhs.valid();
+	const bool rhs_valid = rhs.valid();
+	if (!lhs_valid && !rhs_valid) {
+		// all invalid iterators compare equal.
+		return true;
+	}
+	if (lhs_valid != rhs_valid) {
+		// valid is never equal to invalid.
+		return false;
+	}
+	// OK, so both are valid. Now, we look at the index
+	return lhs.index() == rhs.index();
+}
+
+/*!
+ * @overload
+ */
+template <typename ContainerOrHelper>
+static inline bool
+operator==(RandomAccessIteratorBase<const ContainerOrHelper> const &lhs,
+           RandomAccessIteratorBase<ContainerOrHelper> const &rhs) noexcept
+{
+	return lhs == rhs.as_const();
+}
+/*!
+ * @overload
+ */
+template <typename ContainerOrHelper>
+static inline bool
+operator==(RandomAccessIteratorBase<ContainerOrHelper> const &lhs,
+           RandomAccessIteratorBase<const ContainerOrHelper> const &rhs) noexcept
+{
+	return lhs.as_const() == rhs;
+}
+
+/*!
+ * @brief Inequality comparison operator for @ref RandomAccessIteratorBase
+ *
+ * @relates RandomAccessIteratorBase
+ */
+template <typename ContainerOrHelper>
+static inline bool
+operator!=(RandomAccessIteratorBase<ContainerOrHelper> const &lhs,
+           RandomAccessIteratorBase<ContainerOrHelper> const &rhs) noexcept
+{
+	return !(lhs == rhs);
+}
+
+/*!
+ * @overload
+ */
+template <typename ContainerOrHelper>
+static inline bool
+operator!=(RandomAccessIteratorBase<const ContainerOrHelper> const &lhs,
+           RandomAccessIteratorBase<ContainerOrHelper> const &rhs) noexcept
+{
+	return !(lhs == rhs.as_const());
+}
+
+/*!
+ * @overload
+ */
+template <typename ContainerOrHelper>
+static inline bool
+operator!=(RandomAccessIteratorBase<ContainerOrHelper> const &lhs,
+           RandomAccessIteratorBase<const ContainerOrHelper> const &rhs) noexcept
+{
+	return !(lhs.as_const() == rhs);
+}
+
+template <typename ContainerOrHelper>
+inline bool
+RandomAccessIteratorBase<ContainerOrHelper>::valid() const noexcept
+{
+	return container_ != nullptr && index_ < container_->size();
+}
+
+template <typename ContainerOrHelper>
+inline RandomAccessIteratorBase<ContainerOrHelper> &
+RandomAccessIteratorBase<ContainerOrHelper>::operator+=(std::ptrdiff_t n)
+{
+	if (n < 0) {
+		decrement_n(static_cast<size_t>(-1 * n));
+	} else {
+		increment_n(static_cast<size_t>(n));
+	}
+	return *this;
+}
+
+template <typename ContainerOrHelper>
+inline RandomAccessIteratorBase<ContainerOrHelper> &
+RandomAccessIteratorBase<ContainerOrHelper>::operator-=(std::ptrdiff_t n)
+{
+	if (n < 0) {
+		increment_n(static_cast<size_t>(-1 * n));
+	} else {
+		decrement_n(static_cast<size_t>(n));
+	}
+	return *this;
+}
+
+template <typename ContainerOrHelper>
+inline void
+RandomAccessIteratorBase<ContainerOrHelper>::increment_n(std::size_t n)
+{
+	// being cleared is permanent
+	if (is_cleared())
+		return;
+	index_ += n;
+}
+
+template <typename ContainerOrHelper>
+inline void
+RandomAccessIteratorBase<ContainerOrHelper>::decrement_n(std::size_t n)
+{
+	// being cleared is permanent
+	if (is_cleared())
+		return;
+	if (n > index_) {
+		// would move backward past the beginning which you can't recover from. So, clear it.
+		*this = {};
+		return;
+	}
+	index_ -= n;
+}
+
+template <typename ContainerOrHelper>
+inline std::ptrdiff_t
+RandomAccessIteratorBase<ContainerOrHelper>::operator-(const RandomAccessIteratorBase<ContainerOrHelper> &other) const
+{
+	const bool self_cleared = is_cleared();
+	const bool other_cleared = other.is_cleared();
+	if (self_cleared && other_cleared) {
+		// If both cleared, they're at the same place.
+		return 0;
+	}
+	if (self_cleared || other_cleared) {
+		// If only one is cleared, we can't do this.
+		throw std::logic_error(
+		    "Tried to find the difference between a cleared iterator and a non-cleared iterator.");
+	}
+	constexpr size_t max_ptrdiff = static_cast<size_t>(std::numeric_limits<std::ptrdiff_t>::max());
+	if (index_ > max_ptrdiff || other.index_ > max_ptrdiff) {
+		throw std::out_of_range("An index exceeded the maximum value of the signed version of the index type.");
+	}
+	// Otherwise subtract the index values.
+	return static_cast<std::ptrdiff_t>(index_) - static_cast<std::ptrdiff_t>(other.index_);
+}
+
+template <typename ContainerOrHelper>
+inline RandomAccessIteratorBase<ContainerOrHelper>::RandomAccessIteratorBase(ContainerOrHelper &container, size_t index)
+    : container_(&container), index_(index)
+{}
+
+} // namespace xrt::auxiliary::util
diff --git a/src/xrt/auxiliary/util/u_template_historybuf.hpp b/src/xrt/auxiliary/util/u_template_historybuf.hpp
index 5d1684dbc..bde9bc9de 100644
--- a/src/xrt/auxiliary/util/u_template_historybuf.hpp
+++ b/src/xrt/auxiliary/util/u_template_historybuf.hpp
@@ -1,636 +1,27 @@
-// Copyright 2021, Collabora, Ltd.
+// Copyright 2021-2022, Collabora, Ltd.
+//
 // SPDX-License-Identifier: BSL-1.0
 /*!
  * @file
  * @brief Ringbuffer implementation for keeping track of the past state of things
- * @author Moses Turner <moses@collabora.com>
  * @author Ryan Pavlik <ryan.pavlik@collabora.com>
+ * @author Moses Turner <moses@collabora.com>
  * @ingroup aux_util
  */
 
 #pragma once
 
-#include <algorithm>
-#include <array>
+#include "u_template_historybuf_impl_helpers.hpp"
+#include "u_iterator_base.hpp"
+
 #include <limits>
-#include <stdexcept>
-#include <assert.h>
-
-
-//|  -4   |   -3   |  -2 | -1 | Top | Garbage |
-// OR
-//|  -4   |   -3   |  -2 | -1 | Top | -7 | -6 | -5 |
+#include <array>
 
 namespace xrt::auxiliary::util {
 
-template <typename T, size_t MaxSize> class HistoryBuffer;
-
 namespace detail {
-	//! All the bookkeeping for adapting a fixed-size array to a ring buffer.
-	template <size_t MaxSize> class RingBufferHelper
-	{
-	private:
-		size_t latest_idx_ = 0;
-		size_t length_ = 0;
-
-		//! Get the inner index of the front value: assumes not empty!
-		size_t
-		front_impl_() const noexcept
-		{
-			assert(!empty());
-			// length will not exceed MaxSize, so this will not underflow
-			return (latest_idx_ + MaxSize - length_ + 1) % MaxSize;
-		}
-
-	public:
-		//! Get the inner index for a given age (if possible)
-		bool
-		age_to_inner_index(size_t age, size_t &out_inner_idx) const noexcept
-		{
-
-			if (empty()) {
-				return false;
-			}
-			if (age >= length_) {
-				return false;
-			}
-			// latest_idx_ is the same as latest_idx_ + MaxSize (when modulo MaxSize) so we add it to handle
-			// underflow with unsigned values
-			out_inner_idx = (latest_idx_ + MaxSize - age) % MaxSize;
-			return true;
-		}
-
-		//! Get the inner index for a given index (if possible)
-		bool
-		index_to_inner_index(size_t index, size_t &out_inner_idx) const noexcept
-		{
-
-			if (empty()) {
-				return false;
-			}
-			if (index >= length_) {
-				return false;
-			}
-			// Just add to the front index and take modulo MaxSize
-			out_inner_idx = (front_impl_() + index) % MaxSize;
-			return true;
-		}
-
-		//! Is the buffer empty?
-		bool
-		empty() const noexcept
-		{
-			return length_ == 0;
-		}
-
-		//! How many elements are in the buffer?
-		size_t
-		size() const noexcept
-		{
-			return length_;
-		}
-
-		/*!
-		 * @brief Update internal state for pushing an element to the back, and return the inner index to store
-		 * the element at.
-		 */
-		size_t
-		push_back_location() noexcept
-		{
-			latest_idx_ = (latest_idx_ + 1) % MaxSize;
-			length_ = std::min(length_ + 1, MaxSize);
-			return latest_idx_;
-		}
-
-		/*!
-		 * @brief Record the logical removal of the front element, if any.
-		 *
-		 * Does nothing if the buffer is empty.
-		 */
-		void
-		pop_front() noexcept
-		{
-			if (!empty()) {
-				length_--;
-			}
-		}
-
-		//! Get the inner index of the front value, or MaxSize if empty.
-		size_t
-		front_inner_index() const noexcept
-		{
-			if (empty()) {
-				return MaxSize;
-			}
-			return front_impl_();
-		}
-
-		//! Get the inner index of the back (latest) value, or MaxSize if empty.
-		size_t
-		back_inner_index() const noexcept
-		{
-			if (empty()) {
-				return MaxSize;
-			}
-			return latest_idx_;
-		}
-	};
-
-	/*!
-	 * @brief Base class used by iterators and const_iterators of HistoryBuffer, providing substantial parts of the
-	 * functionality.
-	 *
-	 * Using inheritance instead of composition here to more easily satisfy the C++ standard's requirements for
-	 * iterators (e.g. that const iterators and iterators are comparable, etc.)
-	 *
-	 * All invalid instances will compare as equal, but they are not all equivalent: the "past the end" iterator is
-	 * invalid to dereference, but is allowed to be decremented.
-	 */
-	template <size_t MaxSize> class RingBufferIteratorBase
-	{
-		static_assert(MaxSize < (std::numeric_limits<size_t>::max() >> 1), "Cannot use most significant bit");
-		// for internal use
-		RingBufferIteratorBase(const RingBufferHelper<MaxSize> *helper, size_t index)
-		    : buf_helper_(*helper), index_(index)
-		{}
-
-	public:
-		//! Construct the "past the end" iterator that can be decremented safely
-		static RingBufferIteratorBase
-		end(const RingBufferHelper<MaxSize> &helper)
-		{
-			return RingBufferIteratorBase(helper, helper.size());
-		}
-
-		//! Is this iterator valid?
-		bool
-		valid() const noexcept;
-
-		//! Is this iterator valid?
-		explicit operator bool() const noexcept
-		{
-			return valid();
-		}
-
-		//! What is the index stored by this iterator?
-		size_t
-		index() const noexcept
-		{
-			return index_;
-		}
-
-		/*!
-		 * @brief True if this iterator is "irrecoverably" invalid (aka, cleared/default constructed).
-		 *
-		 * Implies !valid()
-		 */
-		bool
-		is_cleared() const noexcept
-		{
-			return index_ > MaxSize;
-		}
-
-	protected:
-		//! default constructor
-		RingBufferIteratorBase() = default;
-
-		//! Constructor from a helper and index
-		explicit RingBufferIteratorBase(const RingBufferHelper<MaxSize> &helper, size_t index)
-		    : buf_helper_(&helper), index_(index % MaxSize)
-		{}
-
-
-		/*!
-		 * Clear member variables so we are bytewise identical to a default constructed instance, and thus
-		 * irrecoverably invalid.
-		 */
-		void
-		clear() noexcept
-		{
-			*this = {};
-		}
-
-		/*!
-		 * @brief Increment the internal index, given appropriate conditions.
-		 *
-		 * If we were already out of range (invalid), the members are cleared so we are irrecoverably invalid.
-		 * However, you can reach the "past-the-end" element using this function and still decrement away from
-		 * it.
-		 */
-		void
-		increment() noexcept
-		{
-			if (valid()) {
-				index_++;
-			} else {
-				// make sure we can't "back out" of invalid
-				clear();
-			}
-		}
-
-		/*!
-		 * @brief Decrement the internal index, given appropriate conditions.
-		 *
-		 * If our index is not 0 and not our sentinel (MaxSize), we decrement.
-		 * (This is a broader condition than "valid()": we want to be able to decrement() on the "past-the-end"
-		 * iterator to get the last element, etc.)  If we were already out of range (invalid) the members are
-		 * cleared so we are irrecoverably invalid.
-		 */
-		void
-		decrement() noexcept
-		{
-			if (index_ > 0 && !is_cleared()) {
-				index_--;
-			} else {
-				// make sure we can't "back out" of invalid
-				clear();
-			}
-		}
-
-	public:
-		/*!
-		 * @brief Compute the difference between two iterators.
-		 *
-		 * - If both are invalid, the result is 0.
-		 * - If both are either valid or the "past-the-end" iterator, the result is the difference in (logical,
-		 * not inner) index.
-		 *
-		 * @throws std::logic_error if one of the above conditions is not met.
-		 */
-		std::ptrdiff_t
-		operator-(const RingBufferIteratorBase<MaxSize> &other) const;
-
-		/*!
-		 * @brief Increment by an arbitrary value.
-		 */
-		RingBufferIteratorBase &
-		operator+=(std::ptrdiff_t n)
-		{
-			if (is_cleared()) {
-				return *this;
-			}
-
-			size_t n_clean = remap_to_range(n);
-			index_ = (index_ + n_clean) % MaxSize;
-			return *this;
-		}
-
-		/*!
-		 * @brief Decrement by an arbitrary value.
-		 */
-		RingBufferIteratorBase &
-		operator-=(std::ptrdiff_t n)
-		{
-			if (is_cleared()) {
-				return *this;
-			}
-			size_t n_clean = remap_to_range(n);
-			index_ = (index_ + MaxSize - n_clean) % MaxSize;
-			return *this;
-		}
-		/*!
-		 * @brief Add a count.
-		 */
-		RingBufferIteratorBase
-		operator+(std::ptrdiff_t n) const
-		{
-			RingBufferIteratorBase ret = *this;
-			ret += n;
-			return ret;
-		}
-
-		/*!
-		 * @brief Subtract a count.
-		 */
-		RingBufferIteratorBase
-		operator-(std::ptrdiff_t n) const
-		{
-			RingBufferIteratorBase ret = *this;
-			ret -= n;
-			return ret;
-		}
-
-	private:
-		//! Returns a value in [0,  MaxSize)
-		static size_t
-		remap_to_range(std::ptrdiff_t n)
-		{
-			if (n < 0) {
-				size_t ret = MaxSize - (static_cast<size_t>(-1 * n) % MaxSize);
-				assert(ret < MaxSize);
-				return ret;
-			}
-			return static_cast<size_t>(n % MaxSize);
-		}
-		const RingBufferHelper<MaxSize> *buf_helper_{nullptr};
-		size_t index_{MaxSize + 1};
-	};
-
-	/*!
-	 * Equality comparison operator for @ref RingBufferIteratorBase, which is the base of all HistoryBuffer iterator
-	 * types.
-	 */
-	template <size_t MaxSize>
-	static inline bool
-	operator==(RingBufferIteratorBase<MaxSize> const &lhs, RingBufferIteratorBase<MaxSize> const &rhs) noexcept
-	{
-		const bool lhs_valid = lhs.valid();
-		const bool rhs_valid = rhs.valid();
-		if (!lhs_valid && !rhs_valid) {
-			// all invalid iterators compare equal.
-			return true;
-		}
-		if (lhs_valid != rhs_valid) {
-			// valid is never equal to invalid.
-			return false;
-		}
-		// OK, so both are valid. Now, we look at the index
-		return lhs.index() == rhs.index();
-	}
-
-	/*!
-	 * Inequality comparison operator for @ref RingBufferIteratorBase, which is the base of all HistoryBuffer
-	 * iterator types.
-	 */
-	template <size_t MaxSize>
-	static inline bool
-	operator!=(RingBufferIteratorBase<MaxSize> const &lhs, RingBufferIteratorBase<MaxSize> const &rhs) noexcept
-	{
-		return !(lhs == rhs);
-	}
-
+	template <typename T, size_t MaxSize> class HistoryBufConstIterator;
 	template <typename T, size_t MaxSize> class HistoryBufIterator;
-
-	/**
-	 * @brief Class template for const_iterator for HistoryBuffer
-	 *
-	 * @tparam T Container element type - must match HistoryBuffer
-	 * @tparam MaxSize Maximum number of elements - must match HistoryBuffer
-	 */
-	template <typename T, size_t MaxSize> class HistoryBufConstIterator : public RingBufferIteratorBase<MaxSize>
-	{
-		using base = RingBufferIteratorBase<MaxSize>;
-		// for use internally
-		HistoryBufConstIterator(const HistoryBuffer<T, MaxSize> *container,
-		                        RingBufferIteratorBase<MaxSize> &&iter_base)
-		    : base(std::move(iter_base)), container_(container)
-		{}
-
-	public:
-		using iterator_category = std::random_access_iterator_tag;
-		using value_type = const T;
-		using difference_type = std::ptrdiff_t;
-		using pointer = const T *;
-		using reference = const T &;
-		//! Default-construct an (invalid) iterator.
-		HistoryBufConstIterator() = default;
-
-		//! Construct from a container, its helper, and an index: mostly for internal use.
-		HistoryBufConstIterator(const HistoryBuffer<T, MaxSize> &container,
-		                        const RingBufferHelper<MaxSize> &helper,
-		                        size_t index);
-
-		//! Implicit conversion from a non-const iterator
-		HistoryBufConstIterator(const HistoryBufIterator<T, MaxSize> &nonconst);
-
-		//! Construct the "past the end" iterator that can be decremented safely
-		static HistoryBufConstIterator
-		end(const HistoryBuffer<T, MaxSize> &container, const RingBufferHelper<MaxSize> &helper)
-		{
-			return {&container, RingBufferIteratorBase<MaxSize>::end(helper)};
-		}
-
-		//! Is this iterator valid?
-		bool
-		valid() const noexcept
-		{
-			return container_ != nullptr && base::valid();
-		}
-
-		//! Is this iterator valid?
-		explicit operator bool() const noexcept
-		{
-			return valid();
-		}
-
-		//! Dereference operator: throws std::out_of_range if invalid
-		reference
-		operator*() const;
-
-		//! Smart pointer operator: returns nullptr if invalid
-		pointer
-		operator->() const noexcept;
-
-		//! Pre-increment: Advance, then return self.
-		HistoryBufConstIterator &
-		operator++()
-		{
-			base::increment();
-			return *this;
-		}
-
-		//! Post-increment: return a copy of initial state after incrementing self
-		HistoryBufConstIterator
-		operator++(int)
-		{
-			HistoryBufConstIterator tmp = *this;
-			base::increment();
-			return tmp;
-		}
-
-		//! Pre-decrement: Subtract, then return self.
-		HistoryBufConstIterator &
-		operator--()
-		{
-			base::decrement();
-			return *this;
-		}
-
-		//! Post-decrement: return a copy of initial state after decrementing self
-		HistoryBufConstIterator
-		operator--(int)
-		{
-			HistoryBufConstIterator tmp = *this;
-			base::decrement();
-			return tmp;
-		}
-
-
-		using base::operator-;
-
-		HistoryBufConstIterator &
-		operator+=(std::ptrdiff_t n) noexcept
-		{
-			static_cast<base &>(*this) += n;
-
-			return *this;
-		}
-
-		HistoryBufConstIterator &
-		operator-=(std::ptrdiff_t n) noexcept
-		{
-			static_cast<base &>(*this) -= n;
-
-			return *this;
-		}
-
-		//! Increment a copy of the iterator a given number of steps.
-		HistoryBufConstIterator
-		operator+(std::ptrdiff_t n) const noexcept
-		{
-			HistoryBufConstIterator ret(*this);
-			ret += n;
-			return ret;
-		}
-
-		//! Decrement a copy of the iterator a given number of steps.
-		HistoryBufConstIterator
-		operator-(std::ptrdiff_t n) const noexcept
-		{
-			HistoryBufConstIterator ret(*this);
-			ret -= n;
-			return ret;
-		}
-
-	private:
-		const HistoryBuffer<T, MaxSize> *container_{nullptr};
-	};
-
-
-	/**
-	 * @brief Class template for iterator for HistoryBuffer
-	 *
-	 * @see HistoryBufConstIterator for the const_iterator version
-	 *
-	 * @tparam T Container element type - must match HistoryBuffer
-	 * @tparam MaxSize Maximum number of elements - must match HistoryBuffer
-	 */
-	template <typename T, size_t MaxSize> class HistoryBufIterator : public RingBufferIteratorBase<MaxSize>
-	{
-		using base = RingBufferIteratorBase<MaxSize>;
-
-		// for use internally
-		HistoryBufIterator(HistoryBuffer<T, MaxSize> *container, RingBufferIteratorBase<MaxSize> &&iter_base)
-		    : base(std::move(iter_base)), container_(container)
-		{}
-
-	public:
-		using iterator_category = std::random_access_iterator_tag;
-		using value_type = T;
-		using difference_type = std::ptrdiff_t;
-		using pointer = T *;
-		using reference = T &;
-
-		//! Default-construct an (invalid) iterator.
-		HistoryBufIterator() = default;
-
-		//! Construct from a container, its helper, and an index: mostly for internal use.
-		HistoryBufIterator(HistoryBuffer<T, MaxSize> &container,
-		                   const RingBufferHelper<MaxSize> &helper,
-		                   size_t index);
-
-		//! Construct the "past the end" iterator that can be decremented safely
-		static HistoryBufIterator
-		end(HistoryBuffer<T, MaxSize> &container, const RingBufferHelper<MaxSize> &helper)
-		{
-			return {&container, RingBufferIteratorBase<MaxSize>::end(helper)};
-		}
-
-		//! Is this iterator valid?
-		bool
-		valid() const noexcept
-		{
-			return container_ != nullptr && base::valid();
-		}
-
-		//! Is this iterator valid?
-		explicit operator bool() const noexcept
-		{
-			return valid();
-		}
-
-		//! Dereference operator: throws std::out_of_range if invalid
-		reference
-		operator*() const;
-
-		//! Smart pointer operator: returns nullptr if invalid
-		pointer
-		operator->() const noexcept;
-
-		//! Pre-increment: Advance, then return self.
-		HistoryBufIterator &
-		operator++()
-		{
-			base::increment();
-			return *this;
-		}
-
-		//! Post-increment: return a copy of initial state after incrementing self
-		HistoryBufIterator
-		operator++(int)
-		{
-			HistoryBufIterator tmp = *this;
-			base::increment();
-			return tmp;
-		}
-
-		//! Pre-decrement: Subtract, then return self.
-		HistoryBufIterator &
-		operator--()
-		{
-			base::decrement();
-			return *this;
-		}
-
-		//! Post-decrement: return a copy of initial state after decrementing self
-		HistoryBufIterator
-		operator--(int)
-		{
-			HistoryBufIterator tmp = *this;
-			base::decrement();
-			return tmp;
-		}
-		using base::operator-;
-
-		HistoryBufIterator &
-		operator+=(std::ptrdiff_t n) noexcept
-		{
-			static_cast<base &>(*this) += n;
-
-			return *this;
-		}
-
-		HistoryBufIterator &
-		operator-=(std::ptrdiff_t n) noexcept
-		{
-			static_cast<base &>(*this) -= n;
-
-			return *this;
-		}
-
-		//! Increment a copy of the iterator a given number of steps.
-		HistoryBufIterator
-		operator+(std::ptrdiff_t n) const noexcept
-		{
-			HistoryBufIterator ret(*this);
-			ret += n;
-			return ret;
-		}
-
-		//! Decrement a copy of the iterator a given number of steps.
-		HistoryBufIterator
-		operator-(std::ptrdiff_t n) const noexcept
-		{
-			HistoryBufIterator ret(*this);
-			ret -= n;
-			return ret;
-		}
-
-	private:
-		HistoryBuffer<T, MaxSize> *container_{nullptr};
-	};
-
-
 } // namespace detail
 
 /*!
@@ -642,26 +33,14 @@ namespace detail {
  */
 template <typename T, size_t MaxSize> class HistoryBuffer
 {
-private:
-	using container_t = std::array<T, MaxSize>;
-	container_t internalBuffer{};
-	detail::RingBufferHelper<MaxSize> helper_;
-
-	static_assert(MaxSize < (std::numeric_limits<size_t>::max() >> 1), "Cannot use most significant bit");
-
 public:
 	//! Is the buffer empty?
 	bool
-	empty() const noexcept
-	{
-		return helper_.empty();
-	}
+	empty() const noexcept;
+
 	//! How many elements are in the buffer?
 	size_t
-	size() const noexcept
-	{
-		return helper_.size();
-	}
+	size() const noexcept;
 
 
 	/*!
@@ -671,19 +50,7 @@ public:
 	 * expect.
 	 */
 	void
-	push_back(const T &element)
-	{
-		auto inner_index = helper_.push_back_location();
-		internalBuffer[inner_index] = element;
-	}
-
-	//! @overload
-	void
-	push_back(T &&element)
-	{
-		auto inner_index = helper_.push_back_location();
-		internalBuffer[inner_index] = std::move(element);
-	}
+	push_back(const T &element);
 
 	/*!
 	 * @brief Logically remove the oldest element from the buffer.
@@ -693,244 +60,203 @@ public:
 	 * This invalidates iterators. They won't be poisoned, but they will return something you don't expect.
 	 */
 	void
-	pop_front()
+	pop_front() noexcept
 	{
 		helper_.pop_front();
 	}
 
 	/*!
 	 * @brief Access something at a given age, where age 0 is the most recent value, age 1 precedes it, etc.
+	 * (reverse chronological order)
 	 *
 	 * Out of bounds accesses will return nullptr.
 	 */
 	T *
-	get_at_age(size_t age) noexcept
-	{
-		size_t inner_index = 0;
-		if (helper_.age_to_inner_index(age, inner_index)) {
-			return &internalBuffer[inner_index];
-		}
-		return nullptr;
-	}
+	get_at_age(size_t age) noexcept;
 
 
 	//! @overload
 	const T *
-	get_at_age(size_t age) const noexcept
-	{
-		size_t inner_index = 0;
-		if (helper_.age_to_inner_index(age, inner_index)) {
-			return &internalBuffer[inner_index];
-		}
-		return nullptr;
-	}
+	get_at_age(size_t age) const noexcept;
 
 	/*!
 	 * @brief Access something at a given index, where 0 is the least-recent value still stored, index 1 follows it,
-	 * etc.
+	 * etc. (chronological order)
 	 *
 	 * Out of bounds accesses will return nullptr.
 	 */
 	T *
-	get_at_index(size_t index) noexcept
-	{
-		size_t inner_index = 0;
-		if (helper_.index_to_inner_index(index, inner_index)) {
-			return &internalBuffer[inner_index];
-		}
-		return nullptr;
-	}
+	get_at_index(size_t index) noexcept;
 
 	//! @overload
 	const T *
-	get_at_index(size_t index) const noexcept
-	{
-		size_t inner_index = 0;
-		if (helper_.index_to_inner_index(index, inner_index)) {
-			return &internalBuffer[inner_index];
-		}
-		return nullptr;
-	}
-
-
-
-	//! Access the ring buffer helper, mostly for implementation usage only.
-	const detail::RingBufferHelper<MaxSize> &
-	helper() const noexcept
-	{
-		return helper_;
-	}
+	get_at_index(size_t index) const noexcept;
 
 	using iterator = detail::HistoryBufIterator<T, MaxSize>;
 	using const_iterator = detail::HistoryBufConstIterator<T, MaxSize>;
 
-	//! Get an iterator for the least-recent element.
+	//! Get a const iterator for the oldest element.
+	const_iterator
+	cbegin() const noexcept;
+
+	//! Get a "past the end" (past the newest) const iterator
+	const_iterator
+	cend() const noexcept;
+
+	//! Get a const iterator for the oldest element.
+	const_iterator
+	begin() const noexcept;
+
+	//! Get a "past the end" (past the newest) const iterator
+	const_iterator
+	end() const noexcept;
+
+	//! Get an iterator for the oldest element.
 	iterator
-	begin() noexcept
-	{
-		return iterator(*this, helper_, 0);
-	}
+	begin() noexcept;
 
-	//! Get a "past the end" iterator
+	//! Get a "past the end" (past the newest) iterator
 	iterator
-	end() noexcept
-	{
-		return iterator::end(*this, helper_);
-	}
-
-	//! Get a const iterator for the least-recent element.
-	const_iterator
-	cbegin() const noexcept
-	{
-		return const_iterator(*this, helper_, 0);
-	}
-
-	//! Get a "past the end" const iterator
-	const_iterator
-	cend() const noexcept
-	{
-		return const_iterator::end(*this, helper_);
-	}
-
-	//! Get a const iterator for the least-recent element.
-	const_iterator
-	begin() const noexcept
-	{
-		return cbegin();
-	}
-
-	//! Get a "past the end" const iterator
-	const_iterator
-	end() const noexcept
-	{
-		return cend();
-	}
+	end() noexcept;
 
 	/*!
-	 * @brief Gets a reference to the front (least-recent) element in the buffer.
+	 * @brief Gets a reference to the front (oldest) element in the buffer.
 	 * @throws std::logic_error if buffer is empty
 	 */
 	T &
-	front()
-	{
-		if (empty()) {
-			throw std::logic_error("Cannot get the front of an empty buffer");
-		}
-		return internalBuffer[helper_.front_inner_index()];
-	}
+	front();
+
 	//! @overload
 	const T &
-	front() const
-	{
-		if (empty()) {
-			throw std::logic_error("Cannot get the front of an empty buffer");
-		}
-		return internalBuffer[helper_.front_inner_index()];
-	}
+	front() const;
 
 	/*!
-	 * @brief Gets a reference to the back (most-recent) element in the buffer.
+	 * @brief Gets a reference to the back (newest) element in the buffer.
 	 * @throws std::logic_error if buffer is empty
 	 */
 	T &
-	back()
-	{
-		if (empty()) {
-			throw std::logic_error("Cannot get the back of an empty buffer");
-		}
-		return internalBuffer[helper_.back_inner_index()];
-	}
+	back();
 
 	//! @overload
 	const T &
-	back() const
-	{
-		if (empty()) {
-			throw std::logic_error("Cannot get the back of an empty buffer");
-		}
-		return internalBuffer[helper_.back_inner_index()];
-	}
+	back() const;
+
+private:
+	// Make sure all valid indices can be represented in a signed integer of the same size
+	static_assert(MaxSize < (std::numeric_limits<size_t>::max() >> 1), "Cannot use most significant bit");
+
+	using container_t = std::array<T, MaxSize>;
+	container_t internalBuffer{};
+	detail::RingBufferHelper<MaxSize> helper_;
 };
 
-namespace detail {
+template <typename T, size_t MaxSize>
+inline bool
+HistoryBuffer<T, MaxSize>::empty() const noexcept
+{
+	return helper_.empty();
+}
 
-	template <size_t MaxSize>
-	inline bool
-	RingBufferIteratorBase<MaxSize>::valid() const noexcept
-	{
-		return index_ < MaxSize && buf_helper_ != nullptr && index_ < buf_helper_->size();
+template <typename T, size_t MaxSize>
+inline size_t
+HistoryBuffer<T, MaxSize>::size() const noexcept
+{
+	return helper_.size();
+}
+
+template <typename T, size_t MaxSize>
+inline void
+HistoryBuffer<T, MaxSize>::push_back(const T &element)
+{
+	auto inner_index = helper_.push_back_location();
+	internalBuffer[inner_index] = element;
+}
+
+template <typename T, size_t MaxSize>
+inline T *
+HistoryBuffer<T, MaxSize>::get_at_age(size_t age) noexcept
+{
+	size_t inner_index = 0;
+	if (helper_.age_to_inner_index(age, inner_index)) {
+		return &internalBuffer[inner_index];
 	}
-
-	template <size_t MaxSize>
-	inline std::ptrdiff_t
-	detail::RingBufferIteratorBase<MaxSize>::operator-(const RingBufferIteratorBase<MaxSize> &other) const
-	{
-		const bool self_valid = valid();
-		const bool other_valid = other.valid();
-		if (!self_valid && !other_valid) {
-			return 0;
-		}
-		if (index_ < MaxSize && other.index_ < MaxSize) {
-			return static_cast<std::ptrdiff_t>(index_) - static_cast<std::ptrdiff_t>(other.index_);
-		}
-		throw std::logic_error(
-		    "Tried to find the difference between an iterator that has no concrete index, and one that does.");
+	return nullptr;
+}
+template <typename T, size_t MaxSize>
+inline const T *
+HistoryBuffer<T, MaxSize>::get_at_age(size_t age) const noexcept
+{
+	size_t inner_index = 0;
+	if (helper_.age_to_inner_index(age, inner_index)) {
+		return &internalBuffer[inner_index];
 	}
+	return nullptr;
+}
 
-	template <typename T, size_t MaxSize>
-	inline HistoryBufIterator<T, MaxSize>::HistoryBufIterator(HistoryBuffer<T, MaxSize> &container,
-	                                                          const RingBufferHelper<MaxSize> &helper,
-	                                                          size_t index)
-	    : base(helper, index), container_(&container)
-	{}
-
-	template <typename T, size_t MaxSize>
-	inline typename HistoryBufIterator<T, MaxSize>::reference
-	HistoryBufIterator<T, MaxSize>::operator*() const
-	{
-		auto ptr = container_->get_at_index(base::index());
-		if (ptr == nullptr) {
-			throw std::out_of_range("Iterator index out of range");
-		}
-		return *ptr;
+template <typename T, size_t MaxSize>
+inline T *
+HistoryBuffer<T, MaxSize>::get_at_index(size_t index) noexcept
+{
+	size_t inner_index = 0;
+	if (helper_.index_to_inner_index(index, inner_index)) {
+		return &internalBuffer[inner_index];
 	}
+	return nullptr;
+}
 
-	template <typename T, size_t MaxSize>
-	inline typename HistoryBufIterator<T, MaxSize>::pointer
-	HistoryBufIterator<T, MaxSize>::operator->() const noexcept
-	{
-		return container_->get_at_index(base::index());
+template <typename T, size_t MaxSize>
+inline const T *
+HistoryBuffer<T, MaxSize>::get_at_index(size_t index) const noexcept
+{
+	size_t inner_index = 0;
+	if (helper_.index_to_inner_index(index, inner_index)) {
+		return &internalBuffer[inner_index];
 	}
+	return nullptr;
+}
 
-	template <typename T, size_t MaxSize>
-	inline HistoryBufConstIterator<T, MaxSize>::HistoryBufConstIterator(const HistoryBuffer<T, MaxSize> &container,
-	                                                                    const RingBufferHelper<MaxSize> &helper,
-	                                                                    size_t index)
-	    : base(helper, index), container_(&container)
-	{}
-
-	template <typename T, size_t MaxSize>
-	inline detail::HistoryBufConstIterator<T, MaxSize>::HistoryBufConstIterator(
-	    const HistoryBufIterator<T, MaxSize> &nonconst)
-	    : base(nonconst), container_(nonconst.container_)
-	{}
-
-	template <typename T, size_t MaxSize>
-	inline typename HistoryBufConstIterator<T, MaxSize>::reference
-	HistoryBufConstIterator<T, MaxSize>::operator*() const
-	{
-		auto ptr = container_->get_at_index(base::index());
-		if (ptr == nullptr) {
-			throw std::out_of_range("Iterator index out of range");
-		}
-		return *ptr;
+template <typename T, size_t MaxSize>
+inline T &
+HistoryBuffer<T, MaxSize>::front()
+{
+	if (empty()) {
+		throw std::logic_error("Cannot get the front of an empty buffer");
 	}
+	return internalBuffer[helper_.front_inner_index()];
+}
 
-	template <typename T, size_t MaxSize>
-	inline typename HistoryBufConstIterator<T, MaxSize>::pointer
-	HistoryBufConstIterator<T, MaxSize>::operator->() const noexcept
-	{
-		return container_->get_at_index(base::index());
+template <typename T, size_t MaxSize>
+inline const T &
+HistoryBuffer<T, MaxSize>::front() const
+{
+	if (empty()) {
+		throw std::logic_error("Cannot get the front of an empty buffer");
 	}
-} // namespace detail
+	return internalBuffer[helper_.front_inner_index()];
+}
+
+template <typename T, size_t MaxSize>
+inline T &
+HistoryBuffer<T, MaxSize>::back()
+{
+	if (empty()) {
+		throw std::logic_error("Cannot get the back of an empty buffer");
+	}
+	return internalBuffer[helper_.back_inner_index()];
+}
+
+template <typename T, size_t MaxSize>
+inline const T &
+HistoryBuffer<T, MaxSize>::back() const
+{
+	if (empty()) {
+		throw std::logic_error("Cannot get the back of an empty buffer");
+	}
+	return internalBuffer[helper_.back_inner_index()];
+}
+
+
 } // namespace xrt::auxiliary::util
+
+#include "u_template_historybuf_const_iterator.inl"
+#include "u_template_historybuf_iterator.inl"
diff --git a/src/xrt/auxiliary/util/u_template_historybuf_const_iterator.inl b/src/xrt/auxiliary/util/u_template_historybuf_const_iterator.inl
new file mode 100644
index 000000000..cef7d992d
--- /dev/null
+++ b/src/xrt/auxiliary/util/u_template_historybuf_const_iterator.inl
@@ -0,0 +1,257 @@
+// Copyright 2021-2022, Collabora, Ltd.
+//
+// SPDX-License-Identifier: BSL-1.0
+/*!
+ * @file
+ * @brief const_iterator details for ring buffer
+ * @author Ryan Pavlik <ryan.pavlik@collabora.com>
+ * @ingroup aux_util
+ */
+
+#include <stddef.h>
+#include <type_traits>
+
+namespace xrt::auxiliary::util {
+template <typename T, size_t MaxSize> class HistoryBuffer;
+
+namespace detail {
+	/**
+	 * @brief Class template for const_iterator for HistoryBuffer
+	 *
+	 * @tparam T Container element type - must match HistoryBuffer
+	 * @tparam MaxSize Maximum number of elements - must match HistoryBuffer
+	 */
+	template <typename T, size_t MaxSize>
+	class HistoryBufConstIterator : public RandomAccessIteratorBase<const RingBufferHelper<MaxSize>>
+	{
+		using base = RandomAccessIteratorBase<const RingBufferHelper<MaxSize>>;
+		friend class HistoryBuffer<T, MaxSize>;
+		friend class HistoryBufIterator<T, MaxSize>;
+
+	public:
+		using container_type = const HistoryBuffer<T, MaxSize>;
+		using typename base::difference_type;
+		using typename base::iterator_category;
+		using value_type = const T;
+		using pointer = const T *;
+		using reference = const T &;
+
+		//! Default-construct an (invalid) iterator.
+		HistoryBufConstIterator() = default;
+
+		// copy and move as you wish
+		HistoryBufConstIterator(HistoryBufConstIterator const &) = default;
+		HistoryBufConstIterator(HistoryBufConstIterator &&) = default;
+		HistoryBufConstIterator &
+		operator=(HistoryBufConstIterator const &) = default;
+		HistoryBufConstIterator &
+		operator=(HistoryBufConstIterator &&) = default;
+
+		//! Implicit conversion from a non-const iterator
+		HistoryBufConstIterator(const HistoryBufIterator<T, MaxSize> &other);
+
+		//! Is this iterator valid?
+		bool
+		valid() const noexcept
+		{
+			return container_ != nullptr && base::valid();
+		}
+
+		//! Is this iterator valid?
+		explicit operator bool() const noexcept
+		{
+			return valid();
+		}
+
+		//! Access the container: for internal use
+		container_type *
+		container() const noexcept
+		{
+			return container_;
+		}
+
+		//! Dereference operator: throws std::out_of_range if invalid
+		reference
+		operator*() const;
+
+		//! Smart pointer operator: returns nullptr if invalid
+		pointer
+		operator->() const noexcept;
+
+		//! Pre-increment: Advance, then return self.
+		HistoryBufConstIterator &
+		operator++();
+
+		//! Post-increment: return a copy of initial state after incrementing self
+		HistoryBufConstIterator
+		operator++(int);
+
+		//! Pre-decrement: Subtract, then return self.
+		HistoryBufConstIterator &
+		operator--();
+
+		//! Post-decrement: return a copy of initial state after decrementing self
+		HistoryBufConstIterator
+		operator--(int);
+
+		// Use the base class implementation of subtracting one iterator from another
+		using base::operator-;
+
+		//! Increment by an arbitrary amount.
+		HistoryBufConstIterator &
+		operator+=(std::ptrdiff_t n) noexcept;
+
+		//! Decrement by an arbitrary amount.
+		HistoryBufConstIterator &
+		operator-=(std::ptrdiff_t n) noexcept;
+
+		//! Increment a copy of the iterator by an arbitrary amount.
+		HistoryBufConstIterator
+		operator+(std::ptrdiff_t n) const noexcept;
+
+		//! Decrement a copy of the iterator by an arbitrary amount.
+		HistoryBufConstIterator
+		operator-(std::ptrdiff_t n) const noexcept;
+
+	private:
+		//! Factory for a "begin" iterator from a container and its helper: mostly for internal use.
+		static HistoryBufConstIterator
+		begin(container_type &container, const RingBufferHelper<MaxSize> &helper)
+		{
+			return {&container, std::move(base::begin(helper))};
+		}
+
+		//! Construct the "past the end" iterator that can be decremented safely
+		static HistoryBufConstIterator
+		end(container_type &container, const RingBufferHelper<MaxSize> &helper)
+		{
+			return {&container, std::move(base::end(helper))};
+		}
+
+		// for use internally
+		HistoryBufConstIterator(container_type *container, base &&iter_base)
+		    : base(std::move(iter_base)), container_(container)
+		{}
+		container_type *container_{nullptr};
+	};
+
+	template <typename T, size_t MaxSize>
+	inline typename HistoryBufConstIterator<T, MaxSize>::reference
+	HistoryBufConstIterator<T, MaxSize>::operator*() const
+	{
+		auto ptr = container_->get_at_index(base::index());
+		if (ptr == nullptr) {
+			throw std::out_of_range("Iterator index out of range");
+		}
+		return *ptr;
+	}
+
+	template <typename T, size_t MaxSize>
+	inline typename HistoryBufConstIterator<T, MaxSize>::pointer
+	HistoryBufConstIterator<T, MaxSize>::operator->() const noexcept
+	{
+		return container_->get_at_index(base::index());
+	}
+
+	template <typename T, size_t MaxSize>
+	inline HistoryBufConstIterator<T, MaxSize> &
+	HistoryBufConstIterator<T, MaxSize>::operator++()
+	{
+		this->increment_n(1);
+		return *this;
+	}
+
+	template <typename T, size_t MaxSize>
+	inline HistoryBufConstIterator<T, MaxSize>
+	HistoryBufConstIterator<T, MaxSize>::operator++(int)
+	{
+		HistoryBufConstIterator tmp = *this;
+		this->increment_n(1);
+		return tmp;
+	}
+
+	template <typename T, size_t MaxSize>
+	inline HistoryBufConstIterator<T, MaxSize> &
+	HistoryBufConstIterator<T, MaxSize>::operator--()
+	{
+		this->decrement_n(1);
+		return *this;
+	}
+
+	template <typename T, size_t MaxSize>
+	inline HistoryBufConstIterator<T, MaxSize>
+	HistoryBufConstIterator<T, MaxSize>::operator--(int)
+	{
+		HistoryBufConstIterator tmp = *this;
+		this->decrement_n(1);
+		return tmp;
+	}
+
+	template <typename T, size_t MaxSize>
+	inline HistoryBufConstIterator<T, MaxSize> &
+	HistoryBufConstIterator<T, MaxSize>::operator+=(std::ptrdiff_t n) noexcept
+	{
+		static_cast<base &>(*this) += n;
+		return *this;
+	}
+
+	template <typename T, size_t MaxSize>
+	inline HistoryBufConstIterator<T, MaxSize> &
+	HistoryBufConstIterator<T, MaxSize>::operator-=(std::ptrdiff_t n) noexcept
+	{
+		static_cast<base &>(*this) -= n;
+		return *this;
+	}
+
+	template <typename T, size_t MaxSize>
+	inline HistoryBufConstIterator<T, MaxSize>
+	HistoryBufConstIterator<T, MaxSize>::operator+(std::ptrdiff_t n) const noexcept
+	{
+		HistoryBufConstIterator ret(*this);
+		ret += n;
+		return ret;
+	}
+
+	template <typename T, size_t MaxSize>
+	inline HistoryBufConstIterator<T, MaxSize>
+	HistoryBufConstIterator<T, MaxSize>::operator-(std::ptrdiff_t n) const noexcept
+	{
+		HistoryBufConstIterator ret(*this);
+		ret -= n;
+		return ret;
+	}
+} // namespace detail
+
+
+template <typename T, size_t MaxSize>
+inline typename HistoryBuffer<T, MaxSize>::const_iterator
+HistoryBuffer<T, MaxSize>::cbegin() const noexcept
+{
+	static_assert(std::is_same<typename std::iterator_traits<const_iterator>::iterator_category,
+	                           std::random_access_iterator_tag>::value,
+	              "Iterator should be random access");
+	return const_iterator::begin(*this, helper_);
+}
+
+template <typename T, size_t MaxSize>
+inline typename HistoryBuffer<T, MaxSize>::const_iterator
+HistoryBuffer<T, MaxSize>::cend() const noexcept
+{
+	return const_iterator::end(*this, helper_);
+}
+
+template <typename T, size_t MaxSize>
+inline typename HistoryBuffer<T, MaxSize>::const_iterator
+HistoryBuffer<T, MaxSize>::begin() const noexcept
+{
+	return cbegin();
+}
+
+template <typename T, size_t MaxSize>
+inline typename HistoryBuffer<T, MaxSize>::const_iterator
+HistoryBuffer<T, MaxSize>::end() const noexcept
+{
+	return cend();
+}
+
+} // namespace xrt::auxiliary::util
diff --git a/src/xrt/auxiliary/util/u_template_historybuf_impl_helpers.hpp b/src/xrt/auxiliary/util/u_template_historybuf_impl_helpers.hpp
new file mode 100644
index 000000000..39b8b8481
--- /dev/null
+++ b/src/xrt/auxiliary/util/u_template_historybuf_impl_helpers.hpp
@@ -0,0 +1,195 @@
+// Copyright 2021-2022, Collabora, Ltd.
+//
+// SPDX-License-Identifier: BSL-1.0
+/*!
+ * @file
+ * @brief All the "element-type-independent" code (helper objects, base classes) for a ringbuffer implementation on top
+ * of a fixed size array
+ * @author Ryan Pavlik <ryan.pavlik@collabora.com>
+ * @author Moses Turner <moses@collabora.com>
+ * @ingroup aux_util
+ */
+
+#pragma once
+
+#include <algorithm>
+#include <assert.h>
+#include <stdlib.h>
+
+
+//|  -4   |   -3   |  -2 | -1 | Top | Garbage |
+// OR
+//|  -4   |   -3   |  -2 | -1 | Top | -7 | -6 | -5 |
+
+namespace xrt::auxiliary::util::detail {
+/**
+ * @brief All the bookkeeping for adapting a fixed-size array to a ring buffer.
+ *
+ * This is all the guts of the ring buffer except for the actual buffer.
+ * We split it out to
+ * - reduce code size (this can be shared among multiple types)
+ * - separate concerns (keeping track of the indices separate from owning the buffer)
+ * - allow easier implementation of both const iterators and non-const iterators
+ *
+ * There are a few types of "index":
+ *
+ * - just "index": an index where the least-recently-added element still remaining is numbered 0, the next
+ *   oldest is 1, etc. (Chronological)
+ * - "age": Reverse chronological order: 0 means most-recently-added, 1 means the one before it, etc.
+ * - "inner" index: the index in the underlying array/buffer. It's called "inner" because the consumer of the
+ *   ring buffer should not ever deal with this index, it's an implementation detail.
+ */
+template <size_t MaxSize> class RingBufferHelper
+{
+public:
+	//! Get the inner index for a given age (if possible)
+	bool
+	age_to_inner_index(size_t age, size_t &out_inner_idx) const noexcept;
+
+	//! Get the inner index for a given index (if possible)
+	bool
+	index_to_inner_index(size_t index, size_t &out_inner_idx) const noexcept;
+
+	//! Is the buffer empty?
+	bool
+	empty() const noexcept
+	{
+		return length_ == 0;
+	}
+
+	//! How many elements are in the buffer?
+	size_t
+	size() const noexcept
+	{
+		return length_;
+	}
+
+	/*!
+	 * @brief Update internal state for pushing an element to the back, and return the inner index to store
+	 * the element at.
+	 *
+	 * This is the implementation of "push_back" excluding all the messy "actually dealing with the data"
+	 * part ;-)
+	 */
+	size_t
+	push_back_location() noexcept;
+
+	/*!
+	 * @brief Record the logical removal of the front element, if any.
+	 *
+	 * Does nothing if the buffer is empty. Does not actually modify the value stored in the backing array.
+	 */
+	void
+	pop_front() noexcept;
+
+	//! Get the inner index of the front (oldest) value, or MaxSize if empty.
+	size_t
+	front_inner_index() const noexcept;
+
+	//! Get the inner index of the back (newest) value, or MaxSize if empty.
+	size_t
+	back_inner_index() const noexcept;
+
+private:
+	//! The inner index containing the most recently added element, if any
+	size_t latest_inner_idx_ = 0;
+
+	//! The number of elements populated.
+	size_t length_ = 0;
+
+	/**
+	 * @brief Get the inner index of the front (oldest) value: assumes not empty!
+	 *
+	 * For internal use in this class only.
+	 *
+	 * @see front_inner_index() for the "safe" equivalent (that wraps this with error handling)
+	 */
+	size_t
+	front_impl_() const noexcept;
+};
+
+
+template <size_t MaxSize>
+inline size_t
+RingBufferHelper<MaxSize>::front_impl_() const noexcept
+{
+	assert(!empty());
+	// length will not exceed MaxSize, so this will not underflow
+	return (latest_inner_idx_ + MaxSize - length_ + 1) % MaxSize;
+}
+
+template <size_t MaxSize>
+inline bool
+RingBufferHelper<MaxSize>::age_to_inner_index(size_t age, size_t &out_inner_idx) const noexcept
+{
+
+	if (empty()) {
+		return false;
+	}
+	if (age >= length_) {
+		return false;
+	}
+	// latest_inner_idx_ is the same as (latest_inner_idx_ + MaxSize) % MaxSize so we add MaxSize to
+	// prevent underflow with unsigned values
+	out_inner_idx = (latest_inner_idx_ + MaxSize - age) % MaxSize;
+	return true;
+}
+
+template <size_t MaxSize>
+inline bool
+RingBufferHelper<MaxSize>::index_to_inner_index(size_t index, size_t &out_inner_idx) const noexcept
+{
+
+	if (empty()) {
+		return false;
+	}
+	if (index >= length_) {
+		return false;
+	}
+	// Just add to the front (oldest) index and take modulo MaxSize
+	out_inner_idx = (front_impl_() + index) % MaxSize;
+	return true;
+}
+
+template <size_t MaxSize>
+inline size_t
+RingBufferHelper<MaxSize>::push_back_location() noexcept
+{
+	// We always increment the latest inner index modulo MaxSize
+	latest_inner_idx_ = (latest_inner_idx_ + 1) % MaxSize;
+	// Length cannot exceed MaxSize. If it already was MaxSize, that just means we're overwriting something at
+	// latest_inner_idx_
+	length_ = std::min(length_ + 1, MaxSize);
+	return latest_inner_idx_;
+}
+
+template <size_t MaxSize>
+inline void
+RingBufferHelper<MaxSize>::pop_front() noexcept
+{
+	if (!empty()) {
+		length_--;
+	}
+}
+
+template <size_t MaxSize>
+inline size_t
+RingBufferHelper<MaxSize>::front_inner_index() const noexcept
+{
+	if (empty()) {
+		return MaxSize;
+	}
+	return front_impl_();
+}
+
+template <size_t MaxSize>
+inline size_t
+RingBufferHelper<MaxSize>::back_inner_index() const noexcept
+{
+	if (empty()) {
+		return MaxSize;
+	}
+	return latest_inner_idx_;
+}
+
+} // namespace xrt::auxiliary::util::detail
diff --git a/src/xrt/auxiliary/util/u_template_historybuf_iterator.inl b/src/xrt/auxiliary/util/u_template_historybuf_iterator.inl
new file mode 100644
index 000000000..a4737463a
--- /dev/null
+++ b/src/xrt/auxiliary/util/u_template_historybuf_iterator.inl
@@ -0,0 +1,245 @@
+// Copyright 2021-2022, Collabora, Ltd.
+//
+// SPDX-License-Identifier: BSL-1.0
+/*!
+ * @file
+ * @brief iterator details for ring buffer
+ * @author Ryan Pavlik <ryan.pavlik@collabora.com>
+ * @ingroup aux_util
+ */
+
+#include <stddef.h>
+#include <type_traits>
+
+namespace xrt::auxiliary::util {
+template <typename T, size_t MaxSize> class HistoryBuffer;
+
+namespace detail {
+	/**
+	 * @brief Class template for iterator for HistoryBuffer
+	 *
+	 * @tparam T Container element type - must match HistoryBuffer
+	 * @tparam MaxSize Maximum number of elements - must match HistoryBuffer
+	 */
+	template <typename T, size_t MaxSize>
+	class HistoryBufIterator : public RandomAccessIteratorBase<const RingBufferHelper<MaxSize>>
+	{
+		using base = RandomAccessIteratorBase<const RingBufferHelper<MaxSize>>;
+		friend class HistoryBuffer<T, MaxSize>;
+
+	public:
+		using container_type = HistoryBuffer<T, MaxSize>;
+		using typename base::difference_type;
+		using typename base::iterator_category;
+		using value_type = T;
+		using pointer = T *;
+		using reference = T &;
+
+		//! Default-construct an (invalid) iterator.
+		HistoryBufIterator() = default;
+
+		// copy and move as you wish
+		HistoryBufIterator(HistoryBufIterator const &) = default;
+		HistoryBufIterator(HistoryBufIterator &&) = default;
+		HistoryBufIterator &
+		operator=(HistoryBufIterator const &) = default;
+		HistoryBufIterator &
+		operator=(HistoryBufIterator &&) = default;
+
+		//! Is this iterator valid?
+		bool
+		valid() const noexcept
+		{
+			return container_ != nullptr && base::valid();
+		}
+
+		//! Is this iterator valid?
+		explicit operator bool() const noexcept
+		{
+			return valid();
+		}
+
+		//! Access the container: for internal use
+		container_type *
+		container() const noexcept
+		{
+			return container_;
+		}
+
+		//! Dereference operator: throws std::out_of_range if invalid
+		reference
+		operator*() const;
+
+		//! Smart pointer operator: returns nullptr if invalid
+		pointer
+		operator->() const noexcept;
+
+		//! Pre-increment: Advance, then return self.
+		HistoryBufIterator &
+		operator++();
+
+		//! Post-increment: return a copy of initial state after incrementing self
+		HistoryBufIterator
+		operator++(int);
+
+		//! Pre-decrement: Subtract, then return self.
+		HistoryBufIterator &
+		operator--();
+
+		//! Post-decrement: return a copy of initial state after decrementing self
+		HistoryBufIterator
+		operator--(int);
+
+		// Use the base class implementation of subtracting one iterator from another
+		using base::operator-;
+
+		//! Increment by an arbitrary amount.
+		HistoryBufIterator &
+		operator+=(std::ptrdiff_t n) noexcept;
+
+		//! Decrement by an arbitrary amount.
+		HistoryBufIterator &
+		operator-=(std::ptrdiff_t n) noexcept;
+
+		//! Increment a copy of the iterator by an arbitrary amount.
+		HistoryBufIterator
+		operator+(std::ptrdiff_t n) const noexcept;
+
+		//! Decrement a copy of the iterator by an arbitrary amount.
+		HistoryBufIterator
+		operator-(std::ptrdiff_t n) const noexcept;
+
+	private:
+		//! Factory for a "begin" iterator from a container and its helper: mostly for internal use.
+		static HistoryBufIterator
+		begin(container_type &container, const RingBufferHelper<MaxSize> &helper)
+		{
+			return HistoryBufIterator{&container, std::move(base::begin(helper))};
+		}
+
+		//! Construct the "past the end" iterator that can be decremented safely
+		static HistoryBufIterator
+		end(container_type &container, const RingBufferHelper<MaxSize> &helper)
+		{
+			return HistoryBufIterator{&container, std::move(base::end(helper))};
+		}
+
+		// for use internally
+		HistoryBufIterator(container_type *container, base &&iter_base)
+		    : base(std::move(iter_base)), container_(container)
+		{}
+		container_type *container_{nullptr};
+	};
+
+	template <typename T, size_t MaxSize>
+	inline typename HistoryBufIterator<T, MaxSize>::reference
+	HistoryBufIterator<T, MaxSize>::operator*() const
+	{
+		auto ptr = container_->get_at_index(base::index());
+		if (ptr == nullptr) {
+			throw std::out_of_range("Iterator index out of range");
+		}
+		return *ptr;
+	}
+
+	template <typename T, size_t MaxSize>
+	inline typename HistoryBufIterator<T, MaxSize>::pointer
+	HistoryBufIterator<T, MaxSize>::operator->() const noexcept
+	{
+		return container_->get_at_index(base::index());
+	}
+
+	template <typename T, size_t MaxSize>
+	inline HistoryBufIterator<T, MaxSize> &
+	HistoryBufIterator<T, MaxSize>::operator++()
+	{
+		this->increment_n(1);
+		return *this;
+	}
+
+	template <typename T, size_t MaxSize>
+	inline HistoryBufIterator<T, MaxSize>
+	HistoryBufIterator<T, MaxSize>::operator++(int)
+	{
+		HistoryBufIterator tmp = *this;
+		this->increment_n(1);
+		return tmp;
+	}
+
+	template <typename T, size_t MaxSize>
+	inline HistoryBufIterator<T, MaxSize> &
+	HistoryBufIterator<T, MaxSize>::operator--()
+	{
+		this->decrement_n(1);
+		return *this;
+	}
+
+	template <typename T, size_t MaxSize>
+	inline HistoryBufIterator<T, MaxSize>
+	HistoryBufIterator<T, MaxSize>::operator--(int)
+	{
+		HistoryBufIterator tmp = *this;
+		this->decrement_n(1);
+		return tmp;
+	}
+
+	template <typename T, size_t MaxSize>
+	inline HistoryBufIterator<T, MaxSize> &
+	HistoryBufIterator<T, MaxSize>::operator+=(std::ptrdiff_t n) noexcept
+	{
+		static_cast<base &>(*this) += n;
+		return *this;
+	}
+
+	template <typename T, size_t MaxSize>
+	inline HistoryBufIterator<T, MaxSize> &
+	HistoryBufIterator<T, MaxSize>::operator-=(std::ptrdiff_t n) noexcept
+	{
+		static_cast<base &>(*this) -= n;
+		return *this;
+	}
+
+	template <typename T, size_t MaxSize>
+	inline HistoryBufIterator<T, MaxSize>
+	HistoryBufIterator<T, MaxSize>::operator+(std::ptrdiff_t n) const noexcept
+	{
+		HistoryBufIterator ret(*this);
+		ret += n;
+		return ret;
+	}
+
+	template <typename T, size_t MaxSize>
+	inline HistoryBufIterator<T, MaxSize>
+	HistoryBufIterator<T, MaxSize>::operator-(std::ptrdiff_t n) const noexcept
+	{
+		HistoryBufIterator ret(*this);
+		ret -= n;
+		return ret;
+	}
+
+	// Conversion constructor for const iterator
+	template <typename T, size_t MaxSize>
+	inline HistoryBufConstIterator<T, MaxSize>::HistoryBufConstIterator(const HistoryBufIterator<T, MaxSize> &other)
+	    : HistoryBufConstIterator(other.container(), base{other})
+	{}
+} // namespace detail
+
+
+template <typename T, size_t MaxSize>
+inline typename HistoryBuffer<T, MaxSize>::iterator
+HistoryBuffer<T, MaxSize>::begin() noexcept
+{
+	static_assert(std::is_same<typename std::iterator_traits<iterator>::iterator_category,
+	                           std::random_access_iterator_tag>::value,
+	              "Iterator should be random access");
+	return iterator::begin(*this, helper_);
+}
+
+template <typename T, size_t MaxSize>
+inline typename HistoryBuffer<T, MaxSize>::iterator
+HistoryBuffer<T, MaxSize>::end() noexcept
+{
+	return iterator::end(*this, helper_);
+}
+
+} // namespace xrt::auxiliary::util
diff --git a/tests/tests_history_buf.cpp b/tests/tests_history_buf.cpp
index 08ab4cbfc..aaf829d08 100644
--- a/tests/tests_history_buf.cpp
+++ b/tests/tests_history_buf.cpp
@@ -11,9 +11,9 @@
 #include <util/u_template_historybuf.hpp>
 using xrt::auxiliary::util::HistoryBuffer;
 
-template <size_t MaxSize>
+template <typename Container>
 static inline std::ostream &
-operator<<(std::ostream &os, const xrt::auxiliary::util::detail::RingBufferIteratorBase<MaxSize> &iter_base)
+operator<<(std::ostream &os, const xrt::auxiliary::util::RandomAccessIteratorBase<Container> &iter_base)
 {
 	os << "Iterator@[" << iter_base.index() << "]";
 	return os;
@@ -349,10 +349,10 @@ TEST_CASE("IteratorBase")
 	buffer.push_back(2);
 	buffer.push_back(4);
 	using namespace xrt::auxiliary::util;
-	using iterator = typename HistoryBuffer<int, 4>::iterator;
-	iterator default_constructed{};
-	iterator begin_constructed = buffer.begin();
-	iterator end_constructed = buffer.end();
+	using const_iterator = typename HistoryBuffer<int, 4>::const_iterator;
+	const_iterator default_constructed{};
+	const_iterator begin_constructed = buffer.begin();
+	const_iterator end_constructed = buffer.end();
 
 	SECTION("Check default constructed")
 	{
@@ -369,6 +369,7 @@ TEST_CASE("IteratorBase")
 	{
 		CHECK_FALSE(end_constructed.valid());
 		CHECK_FALSE(end_constructed.is_cleared());
-		CHECK((++end_constructed).is_cleared());
+		// if we go past the end, we can go backwards into validity.
+		CHECK_FALSE((++end_constructed).is_cleared());
 	}
 }