diff --git a/applications/gui2/src/main.cpp b/applications/gui2/src/main.cpp
index 2367bd03f151f0b48a7c11b05675d154838972c6..ee07557d2848599e4c2af85034bfb0fc887f4bd3 100644
--- a/applications/gui2/src/main.cpp
+++ b/applications/gui2/src/main.cpp
@@ -153,6 +153,8 @@ void FTLGui::mainloop() {
 	// Process events once more
 	glfwPollEvents();
 
+	ftl::config::save();
+
 	// Stop everything before deleting feed etc
 	LOG(INFO) << "Stopping...";
 	ftl::timer::stop(true);
diff --git a/applications/gui2/src/modules/camera.cpp b/applications/gui2/src/modules/camera.cpp
index f527a4cc1db01603120e1c0d6f6537409aeed002..72d91779c45f46071defbbc5227ca692e0ae8588 100644
--- a/applications/gui2/src/modules/camera.cpp
+++ b/applications/gui2/src/modules/camera.cpp
@@ -8,6 +8,8 @@
 #include <ftl/utility/matrix_conversion.hpp>
 #include <ftl/calibration/structures.hpp>
 #include <ftl/calibration/parameters.hpp>
+#include <ftl/codecs/shapes.hpp>
+#include <ftl/operators/poser.hpp>
 
 #include <opencv2/imgproc.hpp>
 #include <opencv2/imgcodecs.hpp>
@@ -427,7 +429,11 @@ bool Camera::hasFrame() {
 	return false;
 }
 
-Eigen::Matrix4d Camera::cursor() const {
+const Eigen::Matrix4d &Camera::cursor() const {
+	return cursor_;
+}
+
+Eigen::Matrix4d Camera::_cursor() const {
 	if (cursor_normal_.norm() > 0.0f) return nanogui::lookAt(cursor_pos_, cursor_target_, cursor_normal_).cast<double>();
 
 	Eigen::Matrix4d ident;
@@ -573,17 +579,19 @@ void Camera::setCursor(int x, int y) {
 			cursor_target_[2] = CP.z;
 		}
 	}
+
+	cursor_ = _cursor();
 }
 
 void Camera::setOriginToCursor() {
 	using ftl::calibration::transform::inverse;
 	
 	// Check for valid cursor
-	if (cursor_normal_.norm() == 0.0f) return;
+	/*if (cursor_normal_.norm() == 0.0f) return;
 	float cursor_length = (cursor_target_ - cursor_pos_).norm();
 	float cursor_dist = cursor_pos_.norm();
 	if (cursor_length < 0.01f || cursor_length > 5.0f) return;
-	if (cursor_dist > 10.0f) return;
+	if (cursor_dist > 10.0f) return;*/
 
 	if (movable_) {
 		auto *rend = io->feed()->getRenderer(frame_id_);
@@ -612,12 +620,14 @@ void Camera::setOriginToCursor() {
 	cursor_target_ = Eigen::Vector3f(0.0f,0.0f,0.0f); 
 	cursor_pos_ = Eigen::Vector3f(0.0f,0.0f,0.0f);
 	cursor_normal_ = Eigen::Vector3f(0.0f,0.0f,0.0f); 
+	cursor_ = _cursor();
 }
 
 void Camera::resetOrigin() {
 	cursor_target_ = Eigen::Vector3f(0.0f,0.0f,0.0f); 
 	cursor_pos_ = Eigen::Vector3f(0.0f,0.0f,0.0f);
-	cursor_normal_ = Eigen::Vector3f(0.0f,0.0f,0.0f); 
+	cursor_normal_ = Eigen::Vector3f(0.0f,0.0f,0.0f);
+	cursor_ = _cursor(); 
 
 	if (movable_) {
 		auto *rend = io->feed()->getRenderer(frame_id_);
@@ -642,3 +652,45 @@ void Camera::resetOrigin() {
 		}
 	}
 }
+
+void Camera::saveCursorToPoser() {
+	ftl::codecs::Shape3D shape;
+	shape.type = ftl::codecs::Shape3DType::CURSOR;
+	shape.id = cursor_save_id_++;
+	shape.label = std::string("Cursor") + std::to_string(shape.id);
+	shape.pose = cursor().inverse().cast<float>();
+	shape.size = Eigen::Vector3f(0.1f,0.1f,0.1f);
+
+	ftl::operators::Poser::add(shape, frame_id_);
+}
+
+Eigen::Matrix4d Camera::getActivePose() {
+	return cursor(); //.inverse();
+}
+
+nanogui::Vector2i Camera::getActivePoseScreenCoord() {
+	Eigen::Matrix4d pose = getActivePose().inverse();
+
+	auto ptr = std::atomic_load(&latest_);
+	if (ptr) {
+		const auto &frame = ptr->frames[frame_idx].cast<ftl::rgbd::Frame>();
+		auto campose = frame.getPose().inverse() * pose;
+		float3 campos;
+		campos.x = campose(0,3);
+		campos.y = campose(1,3);
+		campos.z = campose(2,3);
+
+		int2 spos = frame.getLeft().camToScreen<int2>(campos);
+		return nanogui::Vector2i(spos.x, spos.y);
+	}
+
+	return nanogui::Vector2i(-1,-1);
+}
+
+void Camera::transformActivePose(const Eigen::Matrix4d &pose) {
+	cursor_ = pose * cursor_;
+}
+
+void Camera::setActivePose(const Eigen::Matrix4d &pose) {
+	cursor_ = pose; //.inverse();
+}
diff --git a/applications/gui2/src/modules/camera.hpp b/applications/gui2/src/modules/camera.hpp
index d314f0a7a8ba9873ed27c7cf6bf6913c9c46d457..21ed70dc34a986b034a28e246897fbcd55e4e384 100644
--- a/applications/gui2/src/modules/camera.hpp
+++ b/applications/gui2/src/modules/camera.hpp
@@ -71,17 +71,23 @@ public:
 
 	void snapshot(const std::string &filename);
 
-	Eigen::Matrix4d cursor() const;
+	const Eigen::Matrix4d &cursor() const;
 
-	void setCursorPosition(const Eigen::Vector3f &pos) { cursor_pos_ = pos; }
-	void setCursorNormal(const Eigen::Vector3f &norm) { cursor_normal_ = norm; }
-	void setCursorTarget(const Eigen::Vector3f &targ) { cursor_target_ = targ; }
+	void setCursorPosition(const Eigen::Vector3f &pos) { cursor_pos_ = pos; cursor_ = _cursor(); }
+	void setCursorNormal(const Eigen::Vector3f &norm) { cursor_normal_ = norm; cursor_ = _cursor(); }
+	void setCursorTarget(const Eigen::Vector3f &targ) { cursor_target_ = targ; cursor_ = _cursor(); }
 	void setCursor(int x, int y);
 
 	const Eigen::Vector3f getCursorPosition() const { return cursor_pos_; }
 
 	void setOriginToCursor();
 	void resetOrigin();
+	void saveCursorToPoser();
+
+	Eigen::Matrix4d getActivePose();
+	nanogui::Vector2i getActivePoseScreenCoord();
+	void transformActivePose(const Eigen::Matrix4d &pose);
+	void setActivePose(const Eigen::Matrix4d &pose);
 
 private:
 	int frame_idx = -1;
@@ -102,6 +108,8 @@ private:
 	Eigen::Vector3f cursor_pos_;
 	Eigen::Vector3f cursor_target_;
 	Eigen::Vector3f cursor_normal_;
+	int cursor_save_id_=0;
+	Eigen::Matrix4d cursor_;
 
 	ftl::data::FrameSetPtr current_fs_;
 	ftl::data::FrameSetPtr latest_;
@@ -120,6 +128,7 @@ private:
 
 	void initiate_(ftl::data::Frame &frame);
 	void _updateCapabilities(ftl::data::Frame &frame);
+	Eigen::Matrix4d _cursor() const;
 };
 
 }
diff --git a/applications/gui2/src/modules/camera_tools.hpp b/applications/gui2/src/modules/camera_tools.hpp
index 8e665b8421fa07d32f39bd1d43928cb5cb9a66e8..ebe8386f100fe6b7a624055f61262f03f9752967 100644
--- a/applications/gui2/src/modules/camera_tools.hpp
+++ b/applications/gui2/src/modules/camera_tools.hpp
@@ -24,6 +24,9 @@ enum class Tools {
 	ROTATE_X,
 	ROTATE_Y,
 	ROTATE_Z,
+	TRANSLATE_X,
+	TRANSLATE_Y,
+	TRANSLATE_Z,
 	INSPECT_POINT
 };
 
diff --git a/applications/gui2/src/screen.cpp b/applications/gui2/src/screen.cpp
index 6fbd703b84bcb6784d5de302640fd05b6cdd6e4a..6925df289e3259f6236416a0fc55f942ad167f3a 100644
--- a/applications/gui2/src/screen.cpp
+++ b/applications/gui2/src/screen.cpp
@@ -25,7 +25,7 @@ using Eigen::Vector2i;
 using ftl::gui2::Screen;
 
 static const int toolbar_w = 50;
-static const Vector2i wsize(1280+toolbar_w,720);
+static const Vector2i wsize(1280,720);
 
 Screen::Screen() :
 		nanogui::Screen(wsize, "FT-Lab Remote Presence"),
@@ -40,6 +40,7 @@ Screen::Screen() :
 	toolbar_->setPosition({0, 0});
 	toolbar_->setWidth(toolbar_w);
 	toolbar_->setHeight(height());
+	toolbar_->setTheme(getTheme("media"));
 
 	setResizeCallback([this](const Vector2i &s) {
 		toolbar_->setFixedSize({toolbar_->width(), s[1]});
@@ -98,7 +99,7 @@ void Screen::redraw() {
 }
 
 nanogui::Vector2i Screen::viewSize(const nanogui::Vector2i &ws) {
-	return {ws.x() - toolbar_->width(), ws.y()};
+	return {ws.x(), ws.y()};
 }
 
 nanogui::Vector2i Screen::viewSize() {
@@ -119,7 +120,7 @@ void Screen::showError(const std::string&title, const std::string& msg) {
 
 void Screen::setView(ftl::gui2::View *view) {
 
-	view->setPosition({toolbar_->width(), 0});
+	view->setPosition({0, 0});
 
 	view->setTheme(getTheme("view"));
 	view->setVisible(true);
diff --git a/applications/gui2/src/views/camera.cpp b/applications/gui2/src/views/camera.cpp
index 451a032ee9f4aa450547594aeb8929c8b9252afb..016ce424c9c6849620be0ed7a57637645f638da7 100644
--- a/applications/gui2/src/views/camera.cpp
+++ b/applications/gui2/src/views/camera.cpp
@@ -397,7 +397,13 @@ ToolPanel::ToolPanel(nanogui::Widget *parent, ftl::gui2::Camera* ctrl, CameraVie
 		Tools::PAN,
 		Tools::INSPECT_POINT,
 		Tools::ZOOM_IN,
-		Tools::ZOOM_OUT
+		Tools::ZOOM_OUT,
+		Tools::ROTATE_X,
+		Tools::ROTATE_Y,
+		Tools::ROTATE_Z,
+		Tools::TRANSLATE_X,
+		Tools::TRANSLATE_Y,
+		Tools::TRANSLATE_Z
 	});
 	_addButton(mouse_group, Tools::SELECT_POINT, ENTYPO_ICON_MOUSE_POINTER, "Select Point");
 	_addButton(mouse_group, Tools::MOVEMENT, ENTYPO_ICON_MAN, "First Person Camera");
@@ -406,6 +412,17 @@ ToolPanel::ToolPanel(nanogui::Widget *parent, ftl::gui2::Camera* ctrl, CameraVie
 	_addButton(mouse_group, Tools::INSPECT_POINT, ENTYPO_ICON_MAGNIFYING_GLASS, "Inspect Point");
 	_addButton(mouse_group, Tools::ZOOM_IN, ENTYPO_ICON_CIRCLE_WITH_PLUS, "Zoom In (+)");
 	_addButton(mouse_group, Tools::ZOOM_OUT, ENTYPO_ICON_CIRCLE_WITH_MINUS, "Zoom Out (-)");
+	auto *trans_but = _addButton(mouse_group, {
+		Tools::ROTATE_X,
+		Tools::ROTATE_Y,
+		Tools::ROTATE_Z
+	}, ENTYPO_ICON_CYCLE, "Transform Pose");
+	_addButton(trans_but, Tools::ROTATE_X, "Rotate X");
+	_addButton(trans_but, Tools::ROTATE_Y, "Rotate Y");
+	_addButton(trans_but, Tools::ROTATE_Z, "Rotate Z");
+	_addButton(trans_but, Tools::TRANSLATE_X, "Translate X");
+	_addButton(trans_but, Tools::TRANSLATE_Y, "Translate Y");
+	_addButton(trans_but, Tools::TRANSLATE_Z, "Translate Z");
 
 	auto *view2d_group = _addGroup(ToolGroup::VIEW_2D_ACTIONS, Button::Flags::NormalButton, {
 		Tools::CENTRE_VIEW,
diff --git a/applications/gui2/src/views/camera3d.cpp b/applications/gui2/src/views/camera3d.cpp
index 29bb0ae4d82b0599dfa0ddd44bee6bb0ac51d676..599fc88a9ae66c246efd319e0c9b3e5ad167651a 100644
--- a/applications/gui2/src/views/camera3d.cpp
+++ b/applications/gui2/src/views/camera3d.cpp
@@ -44,7 +44,13 @@ CameraView3D::CameraView3D(ftl::gui2::Screen *parent, ftl::gui2::Camera *ctrl) :
 		Tools::MOVE_CURSOR,
 		Tools::ORIGIN_TO_CURSOR,
 		Tools::RESET_ORIGIN,
-		Tools::SAVE_CURSOR
+		Tools::SAVE_CURSOR,
+		Tools::ROTATE_X,
+		Tools::ROTATE_Y,
+		Tools::ROTATE_Z,
+		Tools::TRANSLATE_X,
+		Tools::TRANSLATE_Y,
+		Tools::TRANSLATE_Z
 	});
 
 	setZoom(false);
@@ -61,6 +67,15 @@ CameraView3D::CameraView3D(ftl::gui2::Screen *parent, ftl::gui2::Camera *ctrl) :
 			ctrl_->resetOrigin();
 			tools_->setTool(Tools::MOVEMENT);
 			return true;
+		} else if (tool == Tools::SAVE_CURSOR) {
+			ctrl_->saveCursorToPoser();
+			tools_->setTool(Tools::MOVEMENT);
+			return true;
+		} else if (tool == Tools::ROTATE_X || tool == Tools::ROTATE_Y || tool == Tools::ROTATE_Z ||
+					tool == Tools::TRANSLATE_X || tool == Tools::TRANSLATE_Y || tool == Tools::TRANSLATE_Z) {
+			LOG(INFO) << "Loading cache pose";
+			cache_pose_ = ctrl_->getActivePose();
+			cache_screen_ = ctrl_->getActivePoseScreenCoord();
 		}
 		return false;
 	});
@@ -103,6 +118,9 @@ bool CameraView3D::mouseButtonEvent(const Eigen::Vector2i &p, int button, bool d
 			return true;
 		} else if (tools_->isActive(Tools::ROTATE_CURSOR)) {
 			tools_->setTool(Tools::MOVEMENT);
+		} else if (tools_->isActive(Tools::ROTATE_X) || tools_->isActive(Tools::ROTATE_Y) || tools_->isActive(Tools::ROTATE_Z) ||
+					tools_->isActive(Tools::TRANSLATE_X) || tools_->isActive(Tools::TRANSLATE_Y) || tools_->isActive(Tools::TRANSLATE_Z)) {
+			tools_->setTool(Tools::MOVEMENT);
 		}
 	}
 
@@ -126,6 +144,45 @@ bool CameraView3D::mouseMotionEvent(const Eigen::Vector2i &p, const Eigen::Vecto
 		Eigen::Vector3f world = ctrl_->worldAt(pos.x(), pos.y());
 		ctrl_->setCursorTarget(world);
 		return true;
+	} else if (tools_->isActive(Tools::ROTATE_X)) {
+		auto screen_origin = ctrl_->getActivePoseScreenCoord();
+		double angle = atan2(float(screen_origin[1] - p[1]), float(screen_origin[0] - p[0]));
+		Eigen::Affine3d rx = Eigen::Affine3d(Eigen::AngleAxisd(angle, Eigen::Vector3d(1, 0, 0)));
+		ctrl_->setActivePose(rx.matrix() * cache_pose_);
+	} else if (tools_->isActive(Tools::ROTATE_Y)) {
+		auto screen_origin = ctrl_->getActivePoseScreenCoord();
+		double angle = -atan2(float(screen_origin[1] - p[1]), float(screen_origin[0] - p[0]));
+		Eigen::Affine3d ry = Eigen::Affine3d(Eigen::AngleAxisd(angle, Eigen::Vector3d(0, 1, 0)));
+		ctrl_->setActivePose(ry.matrix() * cache_pose_);
+	} else if (tools_->isActive(Tools::ROTATE_Z)) {
+		auto screen_origin = ctrl_->getActivePoseScreenCoord();
+		double angle = atan2(float(screen_origin[1] - p[1]), float(screen_origin[0] - p[0]));
+		Eigen::Affine3d rz = Eigen::Affine3d(Eigen::AngleAxisd(angle, Eigen::Vector3d(0, 0, 1)));
+		ctrl_->setActivePose(rz.matrix() * cache_pose_);
+	} else if (tools_->isActive(Tools::TRANSLATE_X)) {
+		auto mouse = screen()->mousePos();
+		auto pos = imview_->imageCoordinateAt((mouse - mPos).cast<float>());
+		double dx = pos[0] - double(cache_screen_[0]);
+		//double dy = pos[1] - double(cache_screen_[1]);
+		double dist = dx; //(std::abs(dx) > std::abs(dy)) ? dx : dy;
+		Eigen::Affine3d rx = Eigen::Affine3d(Eigen::Translation3d(dist*0.001, 0.0, 0.0));
+		ctrl_->setActivePose(rx.matrix() * cache_pose_);
+	} else if (tools_->isActive(Tools::TRANSLATE_Y)) {
+		auto mouse = screen()->mousePos();
+		auto pos = imview_->imageCoordinateAt((mouse - mPos).cast<float>());
+		double dx = pos[0] - double(cache_screen_[0]);
+		//double dy = pos[1] - double(cache_screen_[1]);
+		double dist = dx; //(std::abs(dx) > std::abs(dy)) ? dx : dy;
+		Eigen::Affine3d rx = Eigen::Affine3d(Eigen::Translation3d(0.0, dist*0.001, 0.0));
+		ctrl_->setActivePose(rx.matrix() * cache_pose_);
+	} else if (tools_->isActive(Tools::TRANSLATE_Z)) {
+		auto mouse = screen()->mousePos();
+		auto pos = imview_->imageCoordinateAt((mouse - mPos).cast<float>());
+		double dx = pos[0] - double(cache_screen_[0]);
+		//double dy = pos[1] - double(cache_screen_[1]);
+		double dist = dx; //(std::abs(dx) > std::abs(dy)) ? dx : dy;
+		Eigen::Affine3d rx = Eigen::Affine3d(Eigen::Translation3d(0.0, 0.0, dist*0.001));
+		ctrl_->setActivePose(rx.matrix() * cache_pose_);
 	}
 
 	//LOG(INFO) << "New pose: \n" << getUpdatedPose();
diff --git a/applications/gui2/src/views/camera3d.hpp b/applications/gui2/src/views/camera3d.hpp
index b5dda23c2efec1f9ba78a1ffdf6ed021b8a250bd..1d0771ca6e5b9bbb8dc0b26410dceca9f46814bd 100644
--- a/applications/gui2/src/views/camera3d.hpp
+++ b/applications/gui2/src/views/camera3d.hpp
@@ -28,6 +28,8 @@ protected:
 	// current
 	Eigen::Vector3d eye_;
 	Eigen::Matrix4d rotmat_;
+	Eigen::Matrix4d cache_pose_;
+	Eigen::Vector2i cache_screen_;
 
 	// updates from mouse
 	double rx_;
diff --git a/applications/reconstruct2/src/main.cpp b/applications/reconstruct2/src/main.cpp
index 7aa75b51f90ca8ecee8a0b5705cb84aebc5287e4..07a0c258cc9ae248234c081e5927f77aea14c553 100644
--- a/applications/reconstruct2/src/main.cpp
+++ b/applications/reconstruct2/src/main.cpp
@@ -129,6 +129,8 @@ static void run(ftl::Configurable *root) {
 	feed->stopRecording();
 	feed->removeFilter(filter);
 
+	ftl::config::save();
+
 	net->shutdown();
 	LOG(INFO) << "Stopping...";
 	ftl::timer::stop(true);
diff --git a/applications/vision/src/main.cpp b/applications/vision/src/main.cpp
index f74fb46daa7b0a7b2cae4a3217c27a4fbcc5b668..9c829d1cbfb2767580328202e793ffd547b102c3 100644
--- a/applications/vision/src/main.cpp
+++ b/applications/vision/src/main.cpp
@@ -164,6 +164,8 @@ static void run(ftl::Configurable *root) {
 
 	// Send channels on flush
 	auto flushhandle = pool.onFlushSet([sender,&encodable](ftl::data::FrameSet &fs, ftl::codecs::Channel c) {
+		if (!fs.test(ftl::data::FSFlag::AUTO_SEND)) return true;
+
 		// Always send data channels
 		if ((int)c >= 32) sender->post(fs, c);
 		else {
@@ -212,19 +214,18 @@ static void run(ftl::Configurable *root) {
 		if (encodable != previous_encodable) sender->resetEncoders(fs->frameset());
 		previous_encodable = encodable;
 
-		// Must touch the depth channel to indicate it should be waited for
-		//for (auto &f : fs->frames) f.touch(Channel::Depth);
+		if (busy.test_and_set()) {
+			LOG(WARNING) << "Depth pipeline drop: " << fs->timestamp();
+			fs->firstFrame().message(ftl::data::Message::Warning_PIPELINE_DROP, "Depth pipeline drop");
+			return true;
+		}
+
+		fs->set(ftl::data::FSFlag::AUTO_SEND);
 
 		// Do all processing in another thread...
 		ftl::pool.push([sender,&stats_count,&latency,&frames,&stats_time,pipeline,&busy,fs](int id) mutable {
-			// Do pipeline here... if not still busy doing it
-			if (busy.test_and_set()) {
-				LOG(WARNING) << "Depth pipeline drop: " << fs->timestamp();
-				fs->firstFrame().message(ftl::data::Message::Warning_PIPELINE_DROP, "Depth pipeline drop");
-				return;
-			}
+			
 			pipeline->apply(*fs, *fs);
-			busy.clear();
 
 			++frames;
 			latency += float(ftl::timer::get_time() - fs->timestamp());
@@ -232,6 +233,7 @@ static void run(ftl::Configurable *root) {
 			// Destruct frameset as soon as possible to send the data...
 			if (fs->hasAnyChanged(Channel::Depth)) fs->flush(Channel::Depth);
 			const_cast<ftl::data::FrameSetPtr&>(fs).reset();
+			busy.clear();
 
 			if (!quiet && --stats_count <= 0) {
 				latency /= float(frames);
@@ -266,6 +268,8 @@ static void run(ftl::Configurable *root) {
 	ftl::timer::start(true);  // Blocks
 	LOG(INFO) << "Stopping...";
 	ctrl.stop();
+
+	ftl::config::save();
 	
 	net->shutdown();
 
diff --git a/components/codecs/include/ftl/codecs/shapes.hpp b/components/codecs/include/ftl/codecs/shapes.hpp
index 313381887bee18c8b70eec45127c4fd7c662352b..2368660e526a294a468e19bcd8ebf105a4fa9263 100644
--- a/components/codecs/include/ftl/codecs/shapes.hpp
+++ b/components/codecs/include/ftl/codecs/shapes.hpp
@@ -17,7 +17,8 @@ enum class Shape3DType {
 	CLIPPING,
 	CAMERA,
 	FEATURE,
-	ARUCO
+	ARUCO,
+	CURSOR
 };
 
 struct Shape3D {
diff --git a/components/common/cpp/include/ftl/configuration.hpp b/components/common/cpp/include/ftl/configuration.hpp
index d555d6dc5eecfa9eeb56137d56661f7affc5763d..3f9118947a003c722c66e0023e8ec20c6424c1e5 100644
--- a/components/common/cpp/include/ftl/configuration.hpp
+++ b/components/common/cpp/include/ftl/configuration.hpp
@@ -48,6 +48,8 @@ nlohmann::json &getDefault(const std::string &key);
 
 void cleanup();
 
+void save();
+
 void removeConfigurable(Configurable *cfg);
 
 /**
diff --git a/components/common/cpp/src/configuration.cpp b/components/common/cpp/src/configuration.cpp
index f756d1d571927d9c8eb9e070e8b39761b007b962..0e327b539c284381d30834572814e6523b7ae992 100644
--- a/components/common/cpp/src/configuration.cpp
+++ b/components/common/cpp/src/configuration.cpp
@@ -685,6 +685,15 @@ static void stripJSON(nlohmann::json &j) {
 	}
 }
 
+void ftl::config::save() {
+	for (auto &f : config_instance) {
+		//LOG(INFO) << "Saving: " << f.second->getID();
+		f.second->save();
+	}
+	stripJSON(config_restore);
+	ftl::saveJSON(std::string(FTL_LOCAL_CONFIG_ROOT "/")+cfg_root_str+std::string("_session.json"), config_restore);
+}
+
 static std::atomic_bool doing_cleanup = false;
 void ftl::config::cleanup() {
 	if (doing_cleanup) return;
@@ -702,8 +711,7 @@ void ftl::config::cleanup() {
 	}
 	config_instance.clear();
 
-	stripJSON(config_restore);
-	ftl::saveJSON(std::string(FTL_LOCAL_CONFIG_ROOT "/")+cfg_root_str+std::string("_session.json"), config_restore);
+	ftl::config::save();
 
 	doing_cleanup = false;
 }
diff --git a/components/net/cpp/src/universe.cpp b/components/net/cpp/src/universe.cpp
index 08e4820ff090d4cdf0b765c542a2cd6a2b61fcb8..84633c833ca937f897a26dd8381f593c5a02a99b 100644
--- a/components/net/cpp/src/universe.cpp
+++ b/components/net/cpp/src/universe.cpp
@@ -41,8 +41,8 @@ struct NetImplDetail {
 //#define TCP_SEND_BUFFER_SIZE	(512*1024)
 //#define TCP_RECEIVE_BUFFER_SIZE	(1024*1024*1)
 
-#define TCP_SEND_BUFFER_SIZE	(256*1024)
-#define TCP_RECEIVE_BUFFER_SIZE	(256*1024)
+#define TCP_SEND_BUFFER_SIZE	(52*1024)  // Was 256
+#define TCP_RECEIVE_BUFFER_SIZE	(52*1024)  // Was 256
 
 callback_t ftl::net::Universe::cbid__ = 0;
 
diff --git a/components/operators/include/ftl/operators/poser.hpp b/components/operators/include/ftl/operators/poser.hpp
index 732ee47ed90caf3fb9bc1ed3bcc889626edf4885..9c642e47730e3f6833087b1a484e1f16476e3017 100644
--- a/components/operators/include/ftl/operators/poser.hpp
+++ b/components/operators/include/ftl/operators/poser.hpp
@@ -22,19 +22,24 @@ class Poser : public ftl::operators::Operator {
 
 	bool apply(ftl::rgbd::FrameSet &in, ftl::rgbd::FrameSet &out, cudaStream_t stream) override;
 
+	bool apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, cudaStream_t stream) override;
+
 	static bool get(const std::string &name, Eigen::Matrix4d &pose);
-	static bool set(const std::string &name, const Eigen::Matrix4d &pose);
+	static bool get(const std::string &name, ftl::codecs::Shape3D &shape);
+	static std::list<ftl::codecs::Shape3D*> getAll(int32_t fsid);
+
+	//static bool set(const ftl::codecs::Shape3D &shape);
+
+	static void add(const ftl::codecs::Shape3D &t, ftl::data::FrameID id);
 
     private:
 	struct PoseState {
-		Eigen::Matrix4d pose;
+		ftl::codecs::Shape3D shape;
 		bool locked;
 	};
 
     static std::unordered_map<std::string,PoseState> pose_db__;
-
-	void add(const ftl::codecs::Shape3D &t, int frameset, int frame);
-
+	static std::unordered_map<int,std::list<ftl::codecs::Shape3D*>> fs_shapes__;
 };
 
 }
diff --git a/components/operators/src/poser.cpp b/components/operators/src/poser.cpp
index 2f8a166cff6c67979acf42d7ab375bad928b9b44..a03682767163c03b089ec985342ca1f846e19996 100644
--- a/components/operators/src/poser.cpp
+++ b/components/operators/src/poser.cpp
@@ -8,7 +8,9 @@ using ftl::codecs::Channel;
 using ftl::codecs::Shape3DType;
 using std::string;
 
+static SHARED_MUTEX smtx;
 std::unordered_map<std::string,ftl::operators::Poser::PoseState> Poser::pose_db__;
+std::unordered_map<int,std::list<ftl::codecs::Shape3D*>> Poser::fs_shapes__;
 
 Poser::Poser(ftl::Configurable *cfg) : ftl::operators::Operator(cfg) {
 
@@ -18,36 +20,48 @@ Poser::~Poser() {
 
 }
 
-void Poser::add(const ftl::codecs::Shape3D &t, int frameset, int frame) {
+void Poser::add(const ftl::codecs::Shape3D &t, ftl::data::FrameID id) {
 	std::string idstr;
 	switch(t.type) {
 	case Shape3DType::ARUCO         : idstr = "aruco-"; break;
 	case Shape3DType::CAMERA		: idstr = "camera-"; break;
+	case Shape3DType::CURSOR		: idstr = "cursor-"; break;
 	default                         : idstr = "unk-"; break;
 	}
 
-	idstr += std::to_string(frameset) + string("-") + std::to_string(frame) + string("-") + std::to_string(t.id);
+	idstr += std::to_string(id.frameset()) + string("-") + std::to_string(id.source()) + string("-") + std::to_string(t.id);
 
-	auto pose = t.pose.cast<double>();  // f.getPose() * 
+	//auto pose = t.pose.cast<double>();  // f.getPose() * 
 
+	UNIQUE_LOCK(smtx, lk);
 	auto p = pose_db__.find(idstr);
 	if (p == pose_db__.end()) {
 		ftl::operators::Poser::PoseState ps;
-		ps.pose = pose;
+		ps.shape = t;
 		ps.locked = false;
 		pose_db__.emplace(std::make_pair(idstr,ps));
 		LOG(INFO) << "POSE ID: " << idstr;
+		fs_shapes__[id.frameset()].push_back(&pose_db__[idstr].shape);
 	} else {
 		// TODO: Merge poses
-		if (!(*p).second.locked) (*p).second.pose = pose;
+		if (!(*p).second.locked) (*p).second.shape = t;
 		//LOG(INFO) << "POSE ID: " << idstr;
 	}
 }
 
+std::list<ftl::codecs::Shape3D*> Poser::getAll(int32_t fsid) {
+	SHARED_LOCK(smtx, lk);
+	if (fs_shapes__.count(fsid)) {
+		return fs_shapes__[fsid];
+	}
+	return {};
+}
+
 bool Poser::get(const std::string &name, Eigen::Matrix4d &pose) {
+	SHARED_LOCK(smtx, lk);
 	auto p = pose_db__.find(name);
 	if (p != pose_db__.end()) {
-		pose = (*p).second.pose;
+		pose = (*p).second.shape.pose.cast<double>();
 		return true;
 	} else {
 		LOG(WARNING) << "Pose not found: " << name;
@@ -55,22 +69,6 @@ bool Poser::get(const std::string &name, Eigen::Matrix4d &pose) {
 	}
 }
 
-bool Poser::set(const std::string &name, const Eigen::Matrix4d &pose) {
-	auto p = pose_db__.find(name);
-	if (p == pose_db__.end()) {
-		ftl::operators::Poser::PoseState ps;
-		ps.pose = pose;
-		ps.locked = false;
-		pose_db__.emplace(std::make_pair(name,ps));
-		LOG(INFO) << "POSE ID: " << name;
-	} else {
-		// TODO: Merge poses
-		if (!(*p).second.locked) (*p).second.pose = pose;
-		//LOG(INFO) << "POSE ID: " << idstr;
-	}
-	return true;
-}
-
 bool Poser::apply(ftl::rgbd::FrameSet &in, ftl::rgbd::FrameSet &out, cudaStream_t stream) {
     if (in.hasChannel(Channel::Shapes3D)) {
         const auto &transforms = in.get<std::list<ftl::codecs::Shape3D>>(Channel::Shapes3D);
@@ -79,7 +77,7 @@ bool Poser::apply(ftl::rgbd::FrameSet &in, ftl::rgbd::FrameSet &out, cudaStream_
 
         for (auto &t : transforms) {
         //    LOG(INFO) << "Have FS transform: " << t.label;
-			add(t, in.frameset(), 255);
+			add(t, in.id());
         }
     }
 
@@ -93,7 +91,7 @@ bool Poser::apply(ftl::rgbd::FrameSet &in, ftl::rgbd::FrameSet &out, cudaStream_
 				//LOG(INFO) << "Found shapes 3D: " << (int)transforms.size();
 
                 for (auto &t : transforms) {
-                    add(t, in.frameset(), i);
+                    add(t, f.id());
                 }
             }
 
@@ -103,21 +101,46 @@ bool Poser::apply(ftl::rgbd::FrameSet &in, ftl::rgbd::FrameSet &out, cudaStream_
 				cam.label = f.name();
 				cam.pose = f.getPose().cast<float>();
 				cam.type = ftl::codecs::Shape3DType::CAMERA;
-				add(cam, in.frameset(), i);
+				add(cam, f.id());
 			}
         //}
     }
 
+	SHARED_LOCK(smtx, lk);
     string pose_ident = config()->value("pose_ident",string("default"));
     if (pose_ident != "default") {
         auto p = pose_db__.find(pose_ident);
         if (p != pose_db__.end()) {
 			(*p).second.locked = config()->value("locked",false);
-            in.cast<ftl::rgbd::Frame>().setPose() = (config()->value("inverse",false)) ? (*p).second.pose.inverse() : (*p).second.pose;
+
+			Eigen::Matrix4d pose = (*p).second.shape.pose.cast<double>();
+            in.cast<ftl::rgbd::Frame>().setPose() = (config()->value("inverse",false)) ? pose.inverse() : pose;
         } else {
             LOG(WARNING) << "Pose not found: " << pose_ident;
         }
     }
 
+	return true;
+}
+
+bool Poser::apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, cudaStream_t stream) {
+	auto &f = in;
+
+	if (f.hasChannel(Channel::Shapes3D)) {
+		const auto &transforms = f.get<std::list<ftl::codecs::Shape3D>>(Channel::Shapes3D);
+
+		for (auto &t : transforms) {
+			add(t, f.id());
+		}
+	}
+
+	if (f.hasChannel(Channel::Pose)) {
+		ftl::codecs::Shape3D cam;
+		cam.id = 0;
+		cam.label = f.name();
+		cam.pose = f.getPose().cast<float>();
+		cam.type = ftl::codecs::Shape3DType::CAMERA;
+		add(cam, f.id());
+	}
 	return true;
 }
\ No newline at end of file
diff --git a/components/renderers/cpp/src/overlay.cpp b/components/renderers/cpp/src/overlay.cpp
index 611e9737c2a5157d5ab1ff21174436d2e763d5c7..7562ffbf9272c72e90e409c03401c0f6f65072a6 100644
--- a/components/renderers/cpp/src/overlay.cpp
+++ b/components/renderers/cpp/src/overlay.cpp
@@ -5,6 +5,7 @@
 #include <opencv2/imgproc.hpp>
 
 #include <ftl/codecs/shapes.hpp>
+#include <ftl/operators/poser.hpp>
 
 #define LOGURU_REPLACE_GLOG 1
 #include <loguru.hpp>
@@ -365,40 +366,39 @@ void Overlay::draw(NVGcontext *ctx, ftl::data::FrameSet &fs, ftl::rgbd::Frame &f
 			}
 		}*/
 
-		for (size_t i=0; i<fs.frames.size(); ++i) {
-			if (fs.frames[i].hasChannel(Channel::Shapes3D)) {
-				const auto &shapes = fs.frames[i].get<std::list<ftl::codecs::Shape3D>>(Channel::Shapes3D);
+		auto shapes = ftl::operators::Poser::getAll(fs.frameset());
 
-				for (auto &s : shapes) {
-					auto pose = s.pose.cast<double>(); //.inverse() * state.getPose();
-					//Eigen::Vector4d pos = pose.inverse() * Eigen::Vector4d(0,0,0,1);
-					//pos /= pos[3];
+		for (auto *ps : shapes) {
+			auto &s = *ps;
 
-					Eigen::Vector3f scale(s.size[0]/2.0f, s.size[1]/2.0f, s.size[2]/2.0f);
+			auto pose = s.pose.cast<double>(); //.inverse() * state.getPose();
+			//Eigen::Vector4d pos = pose.inverse() * Eigen::Vector4d(0,0,0,1);
+			//pos /= pos[3];
 
-					auto tpose = frame.getPose().inverse() * pose;
+			Eigen::Vector3f scale(s.size[0]/2.0f, s.size[1]/2.0f, s.size[2]/2.0f);
 
-					switch (s.type) {
-					case ftl::codecs::Shape3DType::CAMERA: _drawOutlinedShape(Shape::CAMERA, tpose, scale, make_uchar4(255,0,0,80), make_uchar4(255,0,0,255)); break;
-					case ftl::codecs::Shape3DType::CLIPPING: _drawOutlinedShape(Shape::BOX, tpose, scale, make_uchar4(255,0,255,80), make_uchar4(255,0,255,255)); break;
-					case ftl::codecs::Shape3DType::ARUCO: _drawAxis(tpose, Eigen::Vector3f(0.2f, 0.2f, 0.2f)); break;
-					default: break;
-					}
+			auto tpose = frame.getPose().inverse() * pose;
 
-					if (s.label.size() > 0) {
-						float3 textpos;
-						textpos.x = tpose(0,3);
-						textpos.y = tpose(1,3);
-						textpos.z = tpose(2,3);
+			switch (s.type) {
+			case ftl::codecs::Shape3DType::CAMERA: _drawOutlinedShape(Shape::CAMERA, tpose, scale, make_uchar4(255,0,0,80), make_uchar4(255,0,0,255)); break;
+			case ftl::codecs::Shape3DType::CLIPPING: _drawOutlinedShape(Shape::BOX, tpose, scale, make_uchar4(255,0,255,80), make_uchar4(255,0,255,255)); break;
+			case ftl::codecs::Shape3DType::ARUCO: _drawAxis(tpose, Eigen::Vector3f(0.2f, 0.2f, 0.2f)); break;
+			case ftl::codecs::Shape3DType::CURSOR: _drawAxis(tpose, Eigen::Vector3f(0.2f, 0.2f, 0.2f)); break;
+			default: break;
+			}
 
-						float2 textscreen = frame.getLeft().camToScreen<float2>(textpos);
-						if (textpos.z > 0.1f) nvgText(ctx, textscreen.x, textscreen.y, s.label.c_str(), nullptr);
-					}
+			if (s.label.size() > 0) {
+				float3 textpos;
+				textpos.x = tpose(0,3);
+				textpos.y = tpose(1,3);
+				textpos.z = tpose(2,3);
 
-					//ftl::overlay::drawBox(state.getLeft(), out, over_depth_, pose, cv::Scalar(0,0,255,100), s.size.cast<double>());
-					//ftl::overlay::drawText(state.getLeft(), out, over_depth_, s.label, pos, 0.5, cv::Scalar(0,0,255,100));
-				}
+				float2 textscreen = frame.getLeft().camToScreen<float2>(textpos);
+				if (textpos.z > 0.1f) nvgText(ctx, textscreen.x, textscreen.y, s.label.c_str(), nullptr);
 			}
+
+			//ftl::overlay::drawBox(state.getLeft(), out, over_depth_, pose, cv::Scalar(0,0,255,100), s.size.cast<double>());
+			//ftl::overlay::drawText(state.getLeft(), out, over_depth_, s.label, pos, 0.5, cv::Scalar(0,0,255,100));
 		}
 	}
 
diff --git a/components/streams/src/builder.cpp b/components/streams/src/builder.cpp
index 33d19b9460f7f96e1da8e45f44c3e3600b99047f..58f2dd3c92b7e1ce1ce0f802dae91935ae465393 100644
--- a/components/streams/src/builder.cpp
+++ b/components/streams/src/builder.cpp
@@ -394,7 +394,7 @@ std::shared_ptr<ftl::data::FrameSet> ForeignBuilder::_getFrameset() {
 			ftl::data::FrameSetPtr &f2 = framesets_.back();
 			if (f2.get() == f.get()) break;
 
-			LOG(WARNING) << "FrameSet discarded: " << f2->timestamp();
+			LOG(WARNING) << "FrameSet discarded: " << f2->timestamp() << " (" << f->timestamp() << ")";
 			f2->set(ftl::data::FSFlag::DISCARD);
 			{
 				// Ensure frame processing is finished first
diff --git a/components/streams/src/receiver.cpp b/components/streams/src/receiver.cpp
index 9db6ee7929025c881f79eb5600db2e503d0a728e..aee169927ace489075499ec5738fd2fa246c6da1 100644
--- a/components/streams/src/receiver.cpp
+++ b/components/streams/src/receiver.cpp
@@ -319,6 +319,7 @@ void Receiver::_finishPacket(ftl::streams::LockedFrameSet &fs, size_t fix) {
 
 	if (frame.packet_tx > 0 && frame.packet_tx == frame.packet_rx) {
 		fs->completed(fix);
+		if (fs->isComplete()) timestamp_ = fs->timestamp();
 		frame.packet_tx = 0;
 		frame.packet_rx = 0;
 	}
@@ -329,7 +330,6 @@ void Receiver::processPackets(const StreamPacket &spkt, const Packet &pkt) {
 
 	if (spkt.channel == Channel::EndFrame) {
 		auto fs = builder(spkt.streamID).get(spkt.timestamp, spkt.frame_number+pkt.frame_count-1);
-		timestamp_ = spkt.timestamp;
 		fs->frames[spkt.frame_number].packet_tx = static_cast<int>(pkt.packet_count);
 		_finishPacket(fs, spkt.frame_number);
 		return;
diff --git a/components/streams/src/renderers/screen_render.cpp b/components/streams/src/renderers/screen_render.cpp
index 3df19e7a387d0aa48fd5a7abe5d07e61cc7819de..7b74511b9fd379a47548e562bbc22f303cf33c29 100644
--- a/components/streams/src/renderers/screen_render.cpp
+++ b/components/streams/src/renderers/screen_render.cpp
@@ -4,6 +4,7 @@
 #include <ftl/rgbd/capabilities.hpp>
 #include <ftl/operators/antialiasing.hpp>
 #include <ftl/operators/gt_analysis.hpp>
+#include <ftl/operators/poser.hpp>
 #include <ftl/utility/matrix_conversion.hpp>
 #include <ftl/codecs/shapes.hpp>
 
@@ -213,6 +214,7 @@ bool ScreenRender::retrieve(ftl::data::Frame &frame_out) {
 		if (!data_only) {
 			if (!post_pipe_) {
 				post_pipe_ = ftl::config::create<ftl::operators::Graph>(host(), "post_filters");
+				post_pipe_->append<ftl::operators::Poser>("poser");
 				post_pipe_->append<ftl::operators::FXAA>("fxaa");
 				post_pipe_->append<ftl::operators::GTAnalysis>("gtanalyse");
 			}
diff --git a/components/streams/src/sender.cpp b/components/streams/src/sender.cpp
index 62e48bd0b34a62b8dcf7a1e445ff95c0eaea1df3..5e153f82ee5d145148c5d63d0b61fed5efc6f735 100644
--- a/components/streams/src/sender.cpp
+++ b/components/streams/src/sender.cpp
@@ -164,6 +164,11 @@ void Sender::post(ftl::data::FrameSet &fs, ftl::codecs::Channel c, bool noencode
 
 	// Send quick message for this special channel.
 	if (c == Channel::EndFrame) {
+		if (timestamp_ >= fs.timestamp()) {
+			LOG(WARNING) << "Sending old frame! " << fs.timestamp() << " vs " << timestamp_ << " (size = " << fs.frames[0].packet_tx+1 << ")"; 
+		}
+		timestamp_ = fs.timestamp();
+
 		StreamPacket spkt;
 		spkt.version = 5;
 		spkt.timestamp = fs.timestamp();
diff --git a/components/structures/include/ftl/data/new_frameset.hpp b/components/structures/include/ftl/data/new_frameset.hpp
index c471eb5a04cfffd6cd2e235069c35d9151dc93ca..90be7f7bee9c9f646ea3eb7c8c4cfb78e55d532b 100644
--- a/components/structures/include/ftl/data/new_frameset.hpp
+++ b/components/structures/include/ftl/data/new_frameset.hpp
@@ -20,7 +20,8 @@ static const size_t kMaxFramesInSet = 32;
 enum class FSFlag : int {
 	STALE = 0,
 	PARTIAL = 1,
-	DISCARD = 4
+	DISCARD = 4,
+	AUTO_SEND = 8
 };
 
 /**
@@ -77,7 +78,7 @@ class FrameSet : public ftl::data::Frame {
 	/**
 	 * Are all frames complete within this frameset?
 	 */
-	inline bool isComplete() { return ftl::popcount(mask) == frames.size(); }
+	inline bool isComplete() { return mask != 0 && ftl::popcount(mask) == frames.size(); }
 
 	/**
 	 * Check that a given frame is valid in this frameset.
diff --git a/components/structures/src/frameset.cpp b/components/structures/src/frameset.cpp
index f6c9ff4587491ca356820e856198b5ce33dcded8..81848289e21eca9b57688c107caf0c194a007d71 100644
--- a/components/structures/src/frameset.cpp
+++ b/components/structures/src/frameset.cpp
@@ -12,6 +12,7 @@ FrameSet::FrameSet(Pool *ppool, FrameID pid, int64_t ts, size_t psize) :
 FrameSet::~FrameSet() {
 	if (status() == ftl::data::FrameStatus::CREATED) store();
 	if (status() == ftl::data::FrameStatus::STORED) flush();
+	pool()->flush_fs_.trigger(*this, ftl::codecs::Channel::EndFrame);
 }
 
 void ftl::data::FrameSet::completed(size_t ix) {
@@ -103,7 +104,6 @@ void FrameSet::flush() {
 	for (auto c : unflushed) {
 		pool()->flush_fs_.trigger(*this, c);
 	}
-	pool()->flush_fs_.trigger(*this, ftl::codecs::Channel::EndFrame);
 }
 
 void FrameSet::flush(ftl::codecs::Channel c) {