diff --git a/no_trig_rotation.cpp b/no_trig_rotation.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..f0aa1df15336e04d316a406bbcd0c47c00a2dea0
--- /dev/null
+++ b/no_trig_rotation.cpp
@@ -0,0 +1,241 @@
+#include "common/sketchbook.hpp"
+
+// you know what projection is right? you better do cause i sure don't
+constexpr
+float2 project(float2 x, float2 surface)
+{
+	return surface * surface(x)
+		/ surface.magnitude(); // can skip this, then angles will also scale (like complex numbers)
+}
+
+// rejection is the other part
+// projection + rejection = original vector
+constexpr
+float2 reject(float2 x, float2 surface)
+{
+	return x - project(x,surface);
+}
+
+// reflection simply negates the rejection
+constexpr
+float2 reflect(float2 x, float2 surface)
+{
+	return project(x,surface) - reject(x,surface);
+}
+
+// rotation is just two reflections
+constexpr
+float2 rotate(float2 x, float2 half_angle)
+{
+	return reflect( reflect(x, float2::i()), half_angle );
+}
+
+constexpr
+float2 normalize(float2 x)
+{
+	// return x / x.quadrance(); // fun!
+	return x / support::root2(x.quadrance());
+}
+
+// the tricky part is specifying angles as vectors, so here are some approximations that can help
+template <size_t Exponent = 3, typename Value = float>
+class protractor
+{
+	public:
+	using vector = geom::vector<Value,2>;
+
+	// one approach is to approximate the circle with a regular polygon
+	// and the linear interpolate on that polygon to get angles
+	//
+	// we actually just need to approximate a half circle, since
+	// our rotation doubles angles
+
+	// we'll generate the half circle polygon
+	// by starting with a flat(tau/2) angle and repeatedly bisecting it
+
+	// the number of section will always be a power of two
+	// since bisection is essentially multiplying them by two
+	// so out number of iteration/precision parameter is the exponent
+	using array = std::array<vector, (size_t(1)<<Exponent) + 1>;
+
+	static constexpr array circle = []()
+	{
+		using support::midpoint;
+
+		array points{};
+
+		// end points of half polygon/circle
+		points.front() = vector::i();
+		points.back() = -vector::i();
+
+		auto bisect = [](auto begin, auto end,
+		auto& self) // ugly recursive lambda trick -_-
+		{
+			// can't squeeze a new value between two adjacent elements,
+			// so the array must be full already
+			if(end - begin <= 2)
+				return;
+
+			// otherwise
+
+			// we're going to put the new bisector in the middle of provided range
+			auto middle = midpoint(begin, end);
+			// the bisector itself lies on the midpoint between the two points,
+			// since both are on circle.
+			// need to normalize it to stay on the circle for subsequent bisections
+			*(middle) = normalize(midpoint(*begin, *(end - 1)));
+
+			// do the same for the two bisected parts
+			self(begin, middle + 1,
+			self);
+			self(middle, end,
+			self);
+		};
+
+		if(Exponent > 0)
+		{
+			// have to handle the case of first bisection separately,
+			// since can't normalize vector 0 :/
+			auto middle = midpoint(points.begin(), points.end());
+			*middle = vector::j();
+
+			bisect(points.begin(), middle + 1,
+			bisect);
+			bisect(middle, points.end(),
+			bisect);
+		}
+
+		return points;
+	}();
+
+
+	// fraction of full circle
+	static constexpr vector tau(Value factor)
+	{
+		assert(Value{0} <= factor && factor < Value{1});
+		Value index = factor * (protractor::circle.size() - 1);
+		int whole = index;
+		Value fraction = index - whole;
+		return lerp(circle[whole], circle[whole+1], fraction);
+		// linear interpolation more or less works thanks to normalizing/dividing projection
+		// otherwise this method is no good
+	}
+
+	// full circle
+	static constexpr vector tau()
+	{
+		return -vector::i();
+	}
+
+	// small angle in radians
+	static constexpr vector small_radian(Value value)
+	{
+		return {support::root2(Value{1} - value*value), value};
+		// cause sine of small angle is pretty much equal to that angle
+	}
+
+	// any angle in radians, not constexpr -_-
+	static vector radian(Value angle)
+	{
+		// using trigonometric function, very optional
+		return {std::cos(angle), std::sin(angle)};
+	}
+
+	// decides for you weather to use tau() or small_radian()
+	// is not necessarily smart
+	static vector angle(Value tau_factor)
+	{
+		const static auto tau = std::acos(-1) * 2;
+		if(tau_factor < (Value{0.5f}/(circle.size()-1))/10 )
+			return small_radian(tau_factor * tau);
+		else
+			return protractor::tau(tau_factor);
+	}
+
+};
+
+bool request_draw = false;
+float2 angle = float2::one(1.f);
+float regular_angle = 0.f;
+
+void start(Program& program)
+{
+	program.draw_loop = [&](auto frame, auto delta)
+	{
+		frame.begin_sketch()
+			.rectangle(rect{frame.size})
+			.fill(rgb::white(0))
+		;
+
+		constexpr auto half = float2::one(0.5);
+
+		// the the outline of expected path (circle)
+		frame.begin_sketch()
+			.ellipse(anchored_rect2f{
+				float2::one(300), frame.size/2, half
+			})
+			.outline(0xff00ff_rgb)
+		;
+
+		// draw rotated point (should follow the circle)
+		frame.begin_sketch()
+			.ellipse(anchored_rect2f{
+				float2::one(10),
+				frame.size/2 + rotate(float2::j(150), angle),
+				float2::one(0.5f)
+			})
+			.fill(0xff00ff_rgb);
+		;
+
+		// draw the angle itself and a unit circle
+		frame.begin_sketch()
+			.line(frame.size/2, frame.size/2 + angle * 50)
+			.ellipse(anchored_rect2f{
+				float2::one(100), frame.size/2, half
+			})
+			.outline(0xffff00_rgb)
+		;
+
+		// draw the protractor polygon
+		// smaller radius than unit circle just to not overlap
+		{ auto sketch = frame.begin_sketch();
+			for(size_t i = 1; i < protractor<>::circle.size(); ++i)
+			{
+				sketch.line(frame.size/2 + protractor<>::circle[i-1]*45, frame.size/2 + protractor<>::circle[i] * 45);
+			}
+			sketch.outline(0x00ffff_rgb);
+		}
+
+		// ijkl just free-form move the endpoint of the angle
+		if(pressed(scancode::j))
+			angle.x() -= 1.f * delta.count();
+		if(pressed(scancode::l))
+			angle.x() += 1.f * delta.count();
+		if(pressed(scancode::k))
+			angle.y() += 1.f * delta.count();
+		if(pressed(scancode::i))
+			angle.y() -= 1.f * delta.count();
+
+		// left and right set the angle using the elaborate polygon-lerp scheme
+		auto move_regular_angle = [&](float direction)
+		{
+			using geom::vector;
+			using support::wrap;
+			regular_angle += direction * delta.count() * 0.1f;
+			regular_angle = wrap(regular_angle,1.f);
+			angle = protractor<>::tau(regular_angle);
+		};
+		if(pressed(scancode::right))
+			move_regular_angle(1);
+		if(pressed(scancode::left))
+			move_regular_angle(-1);
+
+		// a and d use small radian approximation
+		// we rotate the angle itself by another small angle
+		if(pressed(scancode::a))
+			angle = rotate(angle, protractor<>::small_radian(delta.count() * 0.1f));
+		if(pressed(scancode::d))
+			angle = rotate(angle, protractor<>::small_radian(delta.count() * -0.1f));
+
+	};
+}