diff --git a/CLC-qthing/SiliconTorch/FxCyanRGB8.cpp b/CLC-qthing/SiliconTorch/FxCyanRGB8.cpp
index 640001de726ce737c3b2ec656432b814d65c61ea..760aa0014e5f26b4643b96515dbf2311eae06491 100644
--- a/CLC-qthing/SiliconTorch/FxCyanRGB8.cpp
+++ b/CLC-qthing/SiliconTorch/FxCyanRGB8.cpp
@@ -2,7 +2,9 @@
 
 // C++ system level
 #include <string>
+#include <cstring>  // memset
 #include <cinttypes>
+#include <algorithm>
 #include <functional>
 
 // ESP32 specific
@@ -21,10 +23,90 @@ namespace SiliconTorch {
 
   namespace FxCyanRGB8 {
 
+    const std::string HEADER("fxCyanRGB8");
 
-    FxCyanRGB8::FxCyanRGB8() {
+
+    FxCyanRGB8::FxCyanRGB8(uint16_t leds, uint16_t startIdx) : leds(leds), startIdx(startIdx) {
+
+      buffer0 = new Pixel[leds];
+      buffer1 = new Pixel[leds];
+
+      std::memset(buffer0, 0x00, leds * sizeof(Pixel));
+      std::memset(buffer1, 0x00, leds * sizeof(Pixel));
+
+
+
+    }
+
+    void FxCyanRGB8::handlePacket(uint8_t* buffer, std::size_t length) {
+
+      if (length < 3) {
+        ESP_LOGE(HEADER.c_str(), "Wrong |buffer|[ %d ]: Must be greater than 2!", length);
+        return;
+      }
+
+      if ((length - 2) % 3 > 0) {
+        ESP_LOGE(HEADER.c_str(), "Wrong |buffer|[ %d ]: (Length - 2) must be divisible by 3!", length);
+        return;
+      }
+
+
+      Pixel* packetPx = reinterpret_cast<Pixel*>(buffer+2);
+      uint16_t packetPxs = (length - 2) / 3;
+      uint16_t packetStartIdx = (buffer[0] << 8) | buffer[1];
+
+
+      if (packetStartIdx == startIdx) {
+
+        ESP_LOGW("Mapping[ X -> X ]", "startIdx[ %d ]   packetIdx[ %d ]   mapping: localPx[ 0 ] to packetPx[ 0 ]", startIdx, packetStartIdx);
+
+        for (uint16_t px = 0; px < std::min(leds, packetPxs); px++)
+          buffer1[px] = packetPx[px];
+
+      } else if(packetStartIdx > startIdx) {
+
+        ESP_LOGW("Mapping[ P > W ]", "startIdx[ %d ]   packetIdx[ %d ]   mapping: packetPx[ 0 ] to localPx[ %d ]", startIdx, packetStartIdx, packetStartIdx-startIdx);
+
+      } else if (packetStartIdx < startIdx) {
+
+        // ESP_LOGW("Mapping[ P < W ]", "startIdx[ %d ]   packetIdx[ %d ]   mapping: localPx[ 0 ] to packetPx[ 0 ]", startIdx, packetStartIdx, );
+
+      }
+    }
+
+    void FxCyanRGB8::registerUDPHandler() {
+      if (!udpHandlerRegistered) {
+        udpHandlerRegistered = true;
+
+        uint8_t headerLength = HEADER.length();
+
+        qthing::addUDPPacketCallback(HEADER, [&, headerLength](qthing::udpPacket packet) {
+          this->handlePacket((uint8_t*)(packet.payload + headerLength), packet.length - headerLength);
+        });
+      }
+    }
+
+    // qthing::Animator
+    qthing::RGB FxCyanRGB8::render(uint16_t addr) {
+      qthing::RGB out;
+
+      return out;
     }
 
+    // qthing::Animator
+    void FxCyanRGB8::step() {
+      if (swapRequested) swapBuffers();
+    }
+
+    void FxCyanRGB8::requestBufferSwap() {
+      swapRequested = true;
+    }
 
+    void FxCyanRGB8::swapBuffers() {
+      swapRequested = false;
+      Pixel* tmp = buffer0;
+      buffer0 = buffer1;
+      buffer1 = tmp;
+    }
   }
 }
diff --git a/CLC-qthing/SiliconTorch/FxCyanRGB8.hpp b/CLC-qthing/SiliconTorch/FxCyanRGB8.hpp
index 691604f48cd862d9bae82a4d66998a36f818297c..457aba448750b5fbd39a82a8676f5fa3a635c147 100644
--- a/CLC-qthing/SiliconTorch/FxCyanRGB8.hpp
+++ b/CLC-qthing/SiliconTorch/FxCyanRGB8.hpp
@@ -13,7 +13,7 @@
 // #include "LambdaTask.hpp"
 
 // qthing stuff
-// #include ""
+#include <qthing>
 
 
 
@@ -21,13 +21,46 @@ namespace SiliconTorch {
 
   namespace FxCyanRGB8 {
 
+    typedef struct __attribute__((__packed__)) {
+      uint8_t r;
+      uint8_t g;
+      uint8_t b;
+    } Pixel;
 
-    class FxCyanRGB8 {
+    extern const std::string HEADER;
+
+    class FxCyanRGB8 : public qthing::Animation<qthing::RGB> {
       public:
-        FxCyanRGB8();
+        FxCyanRGB8(uint16_t leds, uint16_t startIdx = 0);
+
+        void getAnimator();
+
+        void requestBufferSwap();
+
+        // Register qthing UDP packet handler
+        // mainly used for testing purposes
+        void registerUDPHandler();
+
+        // Strip the header before calling this function!
+        void handlePacket(uint8_t* buffer, std::size_t length);
+
+        // qthing::Animator
+        void step();
+        qthing::RGB render(uint16_t addr);
 
       private:
 
+        Pixel* buffer0;
+        Pixel* buffer1;
+
+        bool swapRequested = false;
+        bool udpHandlerRegistered = false;
+
+        uint16_t leds;
+        uint16_t startIdx;
+
+
+        void swapBuffers();  // TODO: visibility…?
 
 
     };
diff --git a/CLC-qthing/device_main.cpp b/CLC-qthing/device_main.cpp
index 3ca3499c8b02ac2e71045a99c3f87031c5d2ee7b..6c88c6acf95d323d1631974ee2a3ac653d13a0fa 100644
--- a/CLC-qthing/device_main.cpp
+++ b/CLC-qthing/device_main.cpp
@@ -16,12 +16,16 @@
 // #include "SpiderLib/Util.hpp"
 #include "SiliconTorch/CyanBusCRC.hpp"
 #include "SiliconTorch/CyanBus.hpp"
+
+#include "SiliconTorch/FxCyanRGB8.hpp"
+
 // ###     END LIBS     ###
 
 
 
 SiliconTorch::CyanBus::CyanBus* cyanBus = NULL;
 
+SiliconTorch::FxCyanRGB8::FxCyanRGB8* cyanRGB = NULL;
 
 qthing::Config cfg;
 
@@ -33,15 +37,31 @@ void device_main() {
   // Needed for packet parsing, animation rendering and stuff
   qthing::power_managment_max_power();
 
-
   cyanBus = new SiliconTorch::CyanBus::CyanBus(13, 14, 12, 15, 2000000);  // Pinout of CyanStripe
 
 
-  cyanBus->packetCallback += [](SiliconTorch::CyanBus::PacketData& data) {
+
+  cyanRGB = new SiliconTorch::FxCyanRGB8::FxCyanRGB8(50);
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+  cyanBus->packetCallback += [](const SiliconTorch::CyanBus::PacketData& data) {
     ESP_LOGW("PacketDataTestCallback", "|buffer|[ %d ]   buffer[ %s ]", data.length, data.buffer);
   };
 
-  cyanBus->metricsCallback += [](SiliconTorch::CyanBus::PacketMetrics& metrics) {
+  cyanBus->metricsCallback += [](const SiliconTorch::CyanBus::PacketMetrics& metrics) {
     ESP_LOGW("PacketMetricsTestCallback", "|buffer|[ %d ]   crc⏻[ %s ]", metrics.payloadLEN, metrics.crcOK ? "✔" : "✘");  // TODO: better use ✅ / ❌  ??
   };
 
diff --git a/devtools/FxCyanRGB8.py b/devtools/FxCyanRGB8.py
new file mode 100755
index 0000000000000000000000000000000000000000..c516a20dca6ed2cdcf861cfb9a5d478da42f5f81
--- /dev/null
+++ b/devtools/FxCyanRGB8.py
@@ -0,0 +1,64 @@
+#!/usr/bin/env nix-shell
+#!nix-shell -i python -p python3
+
+
+import sys
+import time
+import struct
+from socket import *
+
+
+if len(sys.argv) < 2:
+  print(f'Usage: {sys.argv[0]} HOST')
+  exit(1)
+
+
+port = 4213
+host = sys.argv[1]
+print(f'Using host[ {host} ]')
+
+
+def sendRaw(data: bytes, _socket = socket(AF_INET, SOCK_DGRAM)):
+  try:
+    _socket.sendto(data, (host, port))
+  except:
+    pass
+
+
+class RGB:
+  def __init__(self, r: int = 0, g: int = 0, b: int = 0):
+    self.r = r
+    self.g = g
+    self.b = b
+
+  def toBytes(self):
+    return struct.pack('BBB', self.r, self.g, self.b)
+
+
+
+def sendPacket(startIdx: int = 0, pixel: list = [], header: bytes = b'fxCyanRGB8'):
+  data = header + struct.pack('>H', startIdx)
+
+  for px in pixel:
+    data += px.toBytes()
+
+  print(f'Sending |pixel|[ {len(pixel)} ]…')
+  sendRaw(data=data)
+
+
+if __name__ == '__main__':
+  white = RGB(255, 255, 255)
+  black = RGB(  0,   0,   0)
+  red   = RGB(255,   0,   0)
+  green = RGB(  0, 255,   0)
+  blue  = RGB(  0,   0, 255)
+
+  pixels = [
+    white,
+    red, green, blue,
+    white
+  ]
+
+  while True:
+    sendPacket(pixel=pixels)
+    time.sleep(1)