diff --git a/src/Myrian/Helper.tsx b/src/Myrian/Helper.tsx
index 9665d5175..6fe3af051 100644
--- a/src/Myrian/Helper.tsx
+++ b/src/Myrian/Helper.tsx
@@ -1,6 +1,8 @@
 import { DeviceType, Component, Lock, Glitch } from "@nsdefs";
-import { Myrian, findDevice } from "./Myrian";
+import { Myrian, findDevice, inMyrianBounds } from "./Myrian";
 import { getNextISocketRequest } from "./formulas/formulas";
+import { decodeBase64 } from "bcryptjs";
+import { roamingTime } from "./formulas/glitches";
 
 export const myrianSize = 12;
 
@@ -178,4 +180,72 @@ setInterval(() => {
   }
 }, 30000);
 
+const clamp = (min: number, v: number, max: number) => Math.min(Math.max(v, min), max);
+let globalOffset = 0;
+const dirDiff = (v: number): number => {
+  globalOffset++;
+  const r = Math.random();
+  const d = v - (myrianSize - 1) / 2;
+  const h = d > 0 ? -1 : 1;
+
+  const dv = Math.floor(r * 3 + h * Math.random() * Math.sin(globalOffset * 0.05) * Math.abs(d)) - 1;
+  return clamp(-1, dv, 1);
+};
+
+const isEmpty = (x: number, y: number) => {
+  if (!inMyrianBounds(x, y)) return false;
+  return !findDevice([x, y]);
+};
+
+const dirs = [
+  [0, 1],
+  [0, -1],
+  [1, 0],
+  [-1, 0],
+];
+
+const applyRoaming = () => {
+  const roaming = myrian.glitches[Glitch.Roaming];
+  setTimeout(applyRoaming, roamingTime(roaming));
+  if (roaming === 0) return;
+  myrian.devices.forEach((device) => {
+    if (device.type !== DeviceType.OSocket && device.type !== DeviceType.ISocket) return;
+    if (device.isBusy) return;
+    let canMove = false;
+    for (const dir of dirs) {
+      const [dx, dy] = dir;
+      if (isEmpty(device.x + dx, device.y + dy)) {
+        canMove = true;
+        break;
+      }
+    }
+    let x = -1;
+    let y = -1;
+    if (canMove) {
+      let dx = dirDiff(device.x);
+      let dy = dirDiff(device.y);
+
+      if (dx !== 0 && dy !== 0) {
+        if (Math.random() > 0.5) {
+          dx = 0;
+        } else {
+          dy = 0;
+        }
+      }
+      x = device.x + dx;
+      y = device.y + dy;
+    } else {
+      x = Math.floor(Math.random() * myrianSize);
+      y = Math.floor(Math.random() * myrianSize);
+    }
+
+    if (findDevice([x, y])) return;
+    if (!inMyrianBounds(x, y)) return;
+    device.x = x;
+    device.y = y;
+  });
+};
+
+setTimeout(applyRoaming, 0);
+
 resetMyrian();
diff --git a/src/Myrian/formulas/glitches.ts b/src/Myrian/formulas/glitches.ts
index a71b8d5d9..bf85463ad 100644
--- a/src/Myrian/formulas/glitches.ts
+++ b/src/Myrian/formulas/glitches.ts
@@ -40,3 +40,5 @@ export const jammingMult = (lvl: number) => Math.pow(1.3, lvl);
 
 // energy loss
 export const magnetismLoss = (lvl: number) => lvl;
+
+export const roamingTime = (lvl: number) => 30000 * Math.pow(0.7, lvl);
diff --git a/src/Myrian/ui/MyrianRoot.tsx b/src/Myrian/ui/MyrianRoot.tsx
index 02f9402c4..37b5e60fb 100644
--- a/src/Myrian/ui/MyrianRoot.tsx
+++ b/src/Myrian/ui/MyrianRoot.tsx
@@ -79,7 +79,7 @@ const YHeader = () => {
 interface IProps {}
 
 export const MyrianRoot = (__props: IProps): React.ReactElement => {
-  useRerender(200);
+  useRerender(50);
   return (
     <Container maxWidth="lg" disableGutters sx={{ mx: 0 }}>
       <Typography variant="h4">Myrian OS</Typography>