diff --git a/include/fggl/math/fmath.hpp b/include/fggl/math/fmath.hpp
index 35cfe88d259d7ddae3bb654a0c356b82f7ec9202..9f16a9bd890985cc754904a49a15df50d7d5ee90 100644
--- a/include/fggl/math/fmath.hpp
+++ b/include/fggl/math/fmath.hpp
@@ -16,28 +16,73 @@
 #define FGGL_MATH_FMATH_HPP
 
 #include <cmath>
+#include "fggl/math/vector.hpp"
 
 namespace fggl::math {
 
-	// wrap value in range [0, max)
+	/**
+	 * A 4D floating-point vector.
+	 */
+	using vec4f = glm::vec4;
+
+	/**
+	 * A 3D floating-point vector.
+	 */
+	using vec3f = glm::vec3;
+
+	/**
+	 * A 2D floating-point vector.
+	 */
+	using vec2f = glm::vec2;
+
+	constexpr static const math::vec2f VEC2_ZERO {0.0F, 0.0F};
+	constexpr static const math::vec3f VEC3_ZERO {0.0F, 0.0F, 0.0F};
+	constexpr static const math::vec3f VEC3_ONES {1.0F, 1.0F, 1.0F};
+
+	/**
+	 * return the remainder (modulo) of value / maximum.
+	 *
+	 * This will return a value in the range [0, maximum), the result will always be positive, even if passed a negative
+	 * input.
+	 *
+	 * @param value the value to wrap.
+	 * @param max the maximum value that it can take.
+	 * @return the wrapped value
+	 */
 	inline float wrap(float value, float max) {
-		return fmod(max + fmod(value, max), max);
+		return fmodf(max + fmodf(value, max), max);
 	}
 
-	// wrap value in range [min, max)
+	/**
+	 * wrap value in range [min, max)
+	 *
+	 * @param value the value to be tested.
+	 * @param min the minimum allowable value
+	 * @param max the maximum allowable value
+	 */
 	inline float wrap(float value, float min, float max) {
 		if ( min > max ){ std::swap(min, max); };
 		value -= min;
 		float rangeSize = max - min;
-		return value - (rangeSize* std::floor(value/rangeSize)) + min;
+		return value - (rangeSize * std::floor(value/rangeSize)) + min;
 	}
 
-	// if the value is out of bounds, return that bound
-	inline float clamp(float value, float min, float max) {
-		const float t = value < min ? min : value;
-		return t > max ? max : t;
+	/**
+	 * Ensure that value is wrapped in the range [min, max].
+	 * if the value is larger than max, return max.
+	 * if the value is smaller than min, return min.
+	 *
+	 * @param value the value to be tested.
+	 * @param min the minimum allowable value
+	 * @param max the maximum allowable value
+	 * @return value, if it is in the range [min, max], otherwise the bound that it is outside of.
+	 */
+	constexpr float clamp(float value, float min, float max) {
+		const float valueOrMin = value < min ? min : value;
+		return valueOrMin > max ? max : valueOrMin;
 	}
 
 } // namespace fggl::math
 
+
 #endif //FGGL_MATH_FMATH_HPP
diff --git a/include/fggl/math/imath.hpp b/include/fggl/math/imath.hpp
index e42658571f7b7e2e130d12f5f01e6b98f63acc68..7f533f87305538edef3d926f232e79bb886e9555 100644
--- a/include/fggl/math/imath.hpp
+++ b/include/fggl/math/imath.hpp
@@ -23,7 +23,7 @@ namespace fggl::math {
 
 	// wrap value in range [0, max)
 	inline int wrap(int value, int max) {
-		if ( value < 0 ) return (n-1)-(-1-value) % n;
+		if ( value < 0 ) return (value-1)-(-1-value) % value;
 		if ( value >= max ) return value % max;
 		return value;
 	}
diff --git a/include/fggl/math/types.hpp b/include/fggl/math/types.hpp
index 14cc4782f477c618751e12607eba05ca434dac83..a649056fa717dd4e1d228a836090c156baff0661 100644
--- a/include/fggl/math/types.hpp
+++ b/include/fggl/math/types.hpp
@@ -18,13 +18,14 @@
 #include <tuple>
 #include <iostream>
 
-#include <glm/glm.hpp>
+#include "fggl/math/vector.hpp"
 #include <glm/ext/matrix_transform.hpp>
 #include <glm/gtc/quaternion.hpp>
 #include <glm/gtx/transform.hpp>
 #include <glm/gtx/euler_angles.hpp>
 #include <glm/gtx/quaternion.hpp>
 
+#include "fggl/math/fmath.hpp"
 #include "fggl/util/guid.hpp"
 
 #ifndef M_PI
@@ -44,29 +45,72 @@ namespace fggl::math {
 	using uint8 = std::uint8_t;
 
 	// math types (aliased for ease of use)
+
+	/**
+	 * A 4D floating-point vector.
+	 */
 	using vec4 = glm::vec4;
-	using vec4f = glm::vec4;
+
+
+	/**
+	 * A 4D signed integer vector.
+	 */
 	using vec4i = glm::ivec4;
+
+	/**
+	 * A 4D unsigned integer vector.
+	 */
 	using vec4ui = glm::ivec4;
 
+	/**
+	 * A 3D floating-point vector.
+	 */
 	using vec3 = glm::vec3;
+
+	/**
+	 * A 3D integer vector.
+	 */
 	using vec3i = glm::ivec3;
-	using vec3f = glm::vec3;
+
+	/**
+	 * A 3D unsigned integer vector.
+	 */
 	using vec3ui = glm::ivec3;
 
+	/**
+	 * A 2D floating-point vector
+	 */
 	using vec2 = glm::vec2;
-	using vec2f = glm::vec2;
+
+	/**
+	 * A 2D integer vector
+	 */
 	using vec2i = glm::ivec2;
+
+	/**
+	 * a 2D unsigned integer vector
+	 */
 	using vec2ui = glm::ivec2;
 
+	/**
+	 * A 2x2 floating-point matrix.
+	 */
 	using mat2 = glm::mat2;
+
+	/**
+	 * A 3x3 floating-point matrix.
+	 */
 	using mat3 = glm::mat3;
+
+	/**
+	 * A 4x4 floating-point matrix.
+	 */
 	using mat4 = glm::mat4;
-	using quat = glm::quat;
 
-	constexpr static const math::vec2 VEC2_ZERO {0.0F, 0.0F};
-	constexpr static const math::vec3 VEC3_ZERO {0.0F, 0.0F, 0.0F};
-	constexpr static const math::vec3 VEC3_ONES {1.0F, 1.0F, 1.0F};
+	/**
+	 * A quaternion.
+	 */
+	using quat = glm::quat;
 
 	constexpr static const math::mat4 IDENTITY_M4 {1.0F};
 	constexpr static const math::quat IDENTITY_Q {1.0F, 0.0, 0.0, 0.0};
@@ -81,23 +125,57 @@ namespace fggl::math {
 		return x < xi ? xi - 1 : xi;
 	}
 
-	inline float rescale_norm(float value, float min, float max) {
+	/**
+	 * Rescale a value between [min, max] into [0, 1].
+	 *
+	 * @param value the value to rescale
+	 * @param min the minimum value of the original range
+	 * @param max the maximum value of the original range
+	 * @return the rescaled value, [0, 1]
+	 */
+	constexpr float rescale_norm(float value, float min, float max) {
 		return (value - min) / (max - min);
 	}
 
-	inline float rescale_norm(float value, float min, float max, float newMin, float newMax) {
+	/**
+	 * Rescale a value between [min, max] into [newMin, newMax].
+	 *
+	 * @param value the value to rescale
+	 * @param min the minimum value of the original range
+	 * @param max the maximum value of the original range
+	 * @param newMin the new minimum value
+	 * @param newMax the new maximum value
+	 * @return the rescaled value, [newMin, newMax]
+	 */
+	constexpr float rescale_norm(float value, float min, float max, float newMin, float newMax) {
 		return newMin + ((value - min) * (newMax - newMin)) / (max - min);
 	}
 
-	inline float rescale_ndc(float value, float newMin, float newMax){
+	/**
+	 * Rescale a normalised device-coordinate value [-1, 1] into another range.
+	 *
+	 * @param value the value to rescale, [-1.0, 1.0]
+	 * @param newMin the new minimum value
+	 * @param newMax the new maximum value
+	 * @return the rescaled value, [newMin, newMax]
+	 */
+	constexpr float rescale_ndc(float value, float newMin, float newMax){
 		return rescale_norm(value, -1, 1, newMin, newMax);
 	}
 
-	inline float rescale_01(float value, float newMin, float newMax){
+	/**
+	 * Rescale a normalised value [0, 1] into another range.
+	 *
+	 * @param value the value to rescale
+	 * @param newMin the new minimum value
+	 * @param newMax the new maximum value
+	 * @return the rescaled value, [newMin, newMax]
+	 */
+	constexpr float rescale_01(float value, float newMin, float newMax){
 		return rescale_norm(value, 0, 1, newMin, newMax);
 	}
 
-	inline float recale_mean(float value, float avg, float max, float min) {
+	constexpr float recale_mean(float value, float avg, float max, float min) {
 		return (value - avg) / (max - min);
 	}
 
@@ -115,6 +193,7 @@ namespace fggl::math {
 		return modelMatrix(offset, glm::quat(eulerAngles));
 	}
 
+	// FIXME: we have multiple definitions of rays in the codebase!
 	struct Ray {
 		vec3 origin;
 		vec3 direction;
@@ -235,12 +314,4 @@ namespace fggl::math {
 
 }
 
-// feels a bit strange to be doing this...
-namespace glm {
-	inline bool operator<(const vec3 &lhs, const vec3 &rhs) {
-		return std::tie(lhs.x, lhs.y, lhs.z)
-			< std::tie(rhs.x, rhs.y, rhs.z);
-	}
-}
-
 #endif
diff --git a/include/fggl/math/vector.hpp b/include/fggl/math/vector.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..29b117b70470a46f4973ba9cf590b0df35891842
--- /dev/null
+++ b/include/fggl/math/vector.hpp
@@ -0,0 +1,60 @@
+/*
+ * This file is part of FGGL.
+ *
+ * FGGL 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 3 of the License, or (at your option) any
+ * later version.
+ *
+ * FGGL is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License along with FGGL.
+ * If not, see <https://www.gnu.org/licenses/>.
+ */
+
+//
+// Created by webpigeon on 20/08/22.
+//
+
+#ifndef FGGL_MATH_VECTOR_HPP
+#define FGGL_MATH_VECTOR_HPP
+
+#include <ostream>
+
+#define GLM_FORCE_MESSAGES
+#include <glm/glm.hpp>
+
+namespace glm {
+	inline bool operator<(const vec2 &lhs, const vec2 &rhs) {
+		return std::tie(lhs.x, lhs.y)
+			< std::tie(rhs.x, rhs.y);
+	}
+
+	inline bool operator<(const vec3 &lhs, const vec3 &rhs) {
+		return std::tie(lhs.x, lhs.y, lhs.z)
+			< std::tie(rhs.x, rhs.y, rhs.z);
+	}
+
+	inline bool operator<(const vec4 &lhs, const vec4 &rhs) {
+		return std::tie(lhs.x, lhs.y, lhs.z, lhs.w)
+			< std::tie(rhs.x, rhs.y, rhs.z, rhs.w);
+	}
+
+	// output stream operators
+	inline std::ostream& operator<<(std::ostream& os, const vec2& v) {
+		os << "(" << v.x << ", " << v.y << ")";
+		return os;
+	}
+
+	inline std::ostream& operator<<(std::ostream& os, const vec3& v) {
+		os << "(" << v.x << ", " << v.y  << "," << v.z << ")";
+		return os;
+	}
+
+	inline std::ostream& operator<<(std::ostream& os, const vec4& v) {
+		os << "(" << v.x << ", " << v.y  << "," << v.z << "," << v.w << ")";
+		return os;
+	}
+}
+
+#endif //FGGL_MATH_VECTOR_HPP