Greasy Fork

Greasy Fork is available in English.

Meeland Enhancement Suite

Meeland.io enhancement suite — fly, sprint, teleport, auto-lock, pet sniper with server-hop, anti-knockback, noclip, ghost mode, attack assist, waypoints, native HUD & more. Works on CrazyGames, twoplayergames.org, meeland.io, iogames.onl, sprunki-game.io, gameflare.com and other sites hosting Meeland

当前为 2026-03-27 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         Meeland Enhancement Suite
// @namespace    meeland-script
// @version      8.0.0
// @description  Meeland.io enhancement suite — fly, sprint, teleport, auto-lock, pet sniper with server-hop, anti-knockback, noclip, ghost mode, attack assist, waypoints, native HUD & more. Works on CrazyGames, twoplayergames.org, meeland.io, iogames.onl, sprunki-game.io, gameflare.com and other sites hosting Meeland
// @match        *://*/*
// @run-at       document-end
// @license      MIT
// @grant        none
// ==/UserScript==
  
  (async function () {
      'use strict';

      const W = typeof unsafeWindow !== 'undefined' ? unsafeWindow : window;
      for (let i = 0; i < 150 && !W.pc?.app?.root; i++) await new Promise(r => setTimeout(r, 100));
      if (!W.pc?.app?.root) return;

      const STORE_KEY = 'ml_wp';
      const CFG_KEY   = 'ml_cfg';
      const isMobile  = ('ontouchstart' in window || navigator.maxTouchPoints > 0) && window.matchMedia('(pointer: coarse)').matches;
      let ACCEL          = 6;
      let FLY_MIN_SPEED  = 10;
      let SPEED_CAP      = 100;
      let GRAVITY        = -18;
      let SPEED_DEFAULT  = 7;

      let flyActive   = false;
      let _flyStartTime = 0;
      let _flyEmoteTimer = null;
      const _FLY_EMOTES = ['Waving','Breakdance','Macarena','Shuffle','Smooth Moves','Techno'];
      const slots = new Array(10).fill(null);
      let flyUp       = false;
      let flyDown     = false;
      let flyVelY     = 0;
      let prevTick    = Date.now();
      let sprinting   = false;
      let sprintSpeed = SPEED_DEFAULT;
      let homePos     = null;
      let backPos     = null;
      let _pendingBaseCapture = false;
      let _roomJoinTime = 0;
      let cuddleTarget = null;
      let cuddling    = false;
      let accelEnabled = true;

      let featFly     = true;
      let featSprint  = true;
      let featWaypoints = true;
      let featCuddle  = true;
      let featCuddleFollow = true;
      let featPets    = true;
      let featAutoLock = true;
      let featAntiKnockback = true;
      let featNoclip = false;
      let featFreeMoney = true;
      let featAutoCollect = true;
      let featInvincible = true;
      let featGhostMode = true;
      let featFreeStars = true;
      let featAutoAttack = true;
      let _freeStarsTimer = null;
      let autoCollectInterval = 30;
      let snipeFilter = 'exclusive';
      let snipeActive = false;
      let snipeAutoDrop = true;
      let snipeAutoHop = !!sessionStorage.getItem('ml_snipeAutoHop');
      let _snipeHopNoMatchStart = 0;
      let _snipeCooldown = 0;
      let _snipeWasHolding = false;

      const autoRefresh  = true;
      const refreshInterval = 5;
      let kbListeningRow = null;
      let petFilter = 'wild';
      let petSortCol = 'price';
      let petSortDir = -1;
      let petAutoRefresh = true;
      let petRefreshInterval = 1;

      const KEYBINDS = {
          fly:      'Space',
          flyDown:  'KeyF',
          setHome:  'KeyQ',
          home:     'Backquote',
          back:     'KeyZ',
          cuddle:   'KeyJ',
          settings: 'KeyM',
          pets:     'KeyK',
          slots:    'KeyI',
          help:     'Slash',
          snipe:    'KeyU',
          attack:   'KeyG',
      };
      const DEFAULT_KEYBINDS = { ...KEYBINDS };

      function saveSettings() {
          try {
              localStorage.setItem(CFG_KEY, JSON.stringify({
                  ACCEL, SPEED_CAP, SPEED_DEFAULT, accelEnabled,

                  featFly, featSprint, featWaypoints, featCuddle, featCuddleFollow, featPets, featAutoLock,
                  featAntiKnockback, featNoclip, featFreeMoney, featAutoCollect, featInvincible, featGhostMode,
                  featFreeStars, featAutoAttack,
                  autoCollectInterval,
                  snipeFilter, snipeAutoDrop, snipeActive,
                  keybinds: { ...KEYBINDS },
                  petFilter, petSortCol, petSortDir, petAutoRefresh, petRefreshInterval,
              }));
          } catch (_) {}
      }

      function loadSettings() {
          try {
              const raw = localStorage.getItem(CFG_KEY);
              if (!raw) return;
              const d = JSON.parse(raw);
              const assign = (key, fn) => { if (d[key] !== undefined) fn(d[key]); };
              assign('ACCEL', v => ACCEL = v);
              assign('SPEED_CAP', v => SPEED_CAP = v);
              assign('SPEED_DEFAULT', v => SPEED_DEFAULT = v);
              assign('accelEnabled', v => accelEnabled = v);

              assign('featFly', v => featFly = v);
              assign('featSprint', v => featSprint = v);
              assign('featWaypoints', v => featWaypoints = v);
              assign('featCuddle', v => featCuddle = v); assign('featVisit', v => featCuddle = v);
              assign('featCuddleFollow', v => featCuddleFollow = v); assign('featStalk', v => featCuddleFollow = v);
              assign('featPets', v => featPets = v);
              assign('featAutoLock', v => featAutoLock = v);
              assign('featAntiKnockback', v => featAntiKnockback = v);
              assign('featNoclip', v => featNoclip = v);
              assign('featFreeMoney', v => featFreeMoney = v);
              assign('featAutoCollect', v => featAutoCollect = v);
              assign('featInvincible', v => featInvincible = v);
              assign('featGhostMode', v => featGhostMode = v);
              assign('featFreeStars', v => featFreeStars = v);
              assign('featAutoAttack', v => featAutoAttack = v);
              assign('autoCollectInterval', v => autoCollectInterval = v);
              assign('snipeFilter', v => snipeFilter = v);
              assign('snipeAutoDrop', v => snipeAutoDrop = v);
              assign('snipeActive', v => snipeActive = v);
              assign('petFilter', v => petFilter = v);
              assign('petSortCol', v => petSortCol = v);
              assign('petSortDir', v => petSortDir = v);
              assign('petAutoRefresh', v => petAutoRefresh = v);
              assign('petRefreshInterval', v => petRefreshInterval = v);
              if (d.keybinds) {
                  for (const k of Object.keys(KEYBINDS)) {
                      if (typeof d.keybinds[k] === 'string') KEYBINDS[k] = d.keybinds[k];
                  }
              }
          } catch (_) {}
      }
      loadSettings();

      let _playerCache = null, _playerCacheTime = 0;
      const getPlayer = () => {
          const now = Date.now();
          if (_playerCacheTime && now - _playerCacheTime < 200) return _playerCache;
          _playerCache = W.pc?.app?.root?.findByName('Player') ?? null;
          if (_playerCache) _playerCacheTime = now;
          return _playerCache;
      };
      const getKcc    = p  => p?.script?.kcc ?? null;
      const getPC     = p  => p?.script?.playerController ?? null;
      const $ = id => document.getElementById(id);
      const flash = id => {
          const el = $(id);
          if (el) { el.classList.add('fresh'); setTimeout(() => el.classList.remove('fresh'), 400); }
          if (_nativeHud) {
              const idMap = { 'ml-fly': 'fly', 'ml-spr': 'spr', 'ml-home': 'home', 'ml-go': 'go', 'ml-back': 'back', 'ml-slots': 'slots', 'ml-lock': 'lock' };
              const k = idMap[id];
              if (k && _nativeHud[k]) { _nativeHud[k].classList.add('fresh'); setTimeout(() => _nativeHud[k].classList.remove('fresh'), 400); }
          }
      };
      const syncSlider = (id, valId, v, uom) => { const el = $(id), ve = $(valId); if (el) el.value = v; if (ve) ve.textContent = uom ? v + uom : v; };
      const vec3 = v => ({ x: v.x, y: v.y, z: v.z });
      const getCamera = () => W.pc?.app?.root?.findByName('Camera') ?? null;

      function capturePos(player) {
          const p = player.getPosition();
          const r = player.getEulerAngles();
          const camera = getCamera();
          const cam = camera?.getEulerAngles();
          let se = null;
          for (const inst of (camera?.script?._scripts || [])) {
              if (inst.eulers) { se = vec3(inst.eulers); break; }
          }
          return { x: p.x, y: p.y, z: p.z, rot: vec3(r), cam: cam ? vec3(cam) : null, se };
      }

      function restoreCam(posData) {
          if (!posData?.cam) return;
          const camera = getCamera();
          if (!camera) return;
          let scriptRestored = false;
          for (const inst of (camera.script?._scripts || [])) {
              if ('_yaw' in inst) { inst._yaw = posData.cam.y; scriptRestored = true; }
              if ('_pitch' in inst) { inst._pitch = posData.cam.x; scriptRestored = true; }
              if ('yaw' in inst) { inst.yaw = posData.cam.y; scriptRestored = true; }
              if ('pitch' in inst) { inst.pitch = posData.cam.x; scriptRestored = true; }
              if (inst.eulers && posData.se) {
                  inst.eulers.x = posData.se.x; inst.eulers.y = posData.se.y; inst.eulers.z = posData.se.z;
                  scriptRestored = true;
              }
          }
          if (!scriptRestored) camera.setEulerAngles(posData.cam.x, posData.cam.y, posData.cam.z);
      }

      const FEAT_IDS = ['ml-f-fly','ml-f-sprint','ml-f-waypoints','ml-f-cuddle','ml-f-cuddle-follow','ml-f-pets','ml-f-autolock','ml-f-antiknockback','ml-f-noclip','ml-f-freemoney','ml-f-autocollect','ml-f-invincible','ml-f-ghostmode','ml-f-freestars','ml-f-autoattack'];
      const MOVE_KEYS = new Set(['KeyW','KeyA','KeyS','KeyD','ArrowUp','ArrowDown','ArrowLeft','ArrowRight']);

      function getPlayerState() {
          const nm = W.pc?.app?.root?.findByName('NetworkManager')?.script?.networkManager;
          if (!nm?.room?.state?.players) return null;
          return nm.room.state.players.get(nm.room.sessionId) ?? null;
      }

      const setSpeed = (pc, kcc, v) => {
          if (pc) { pc.currentSpeed = v; pc.speed = v; }
          if (kcc.speed !== undefined) kcc.speed = v;
          const ps = getPlayerState();
          if (ps && ps.movementSpeed !== undefined) ps.movementSpeed = v / SPEED_DEFAULT;
      };

      function teleport(player, pos) {
          player.setPosition(pos.x, pos.y, pos.z);
          player.rigidbody?.teleport(pos.x, pos.y, pos.z);
          if (pos.rot) player.setEulerAngles(pos.rot.x, pos.rot.y, pos.rot.z);
          restoreCam(pos);
      }

      function serializePos(p) {
          if (!p) return null;
          const o = vec3(p);
          if (p.rot) o.rot = vec3(p.rot);
          if (p.cam) o.cam = vec3(p.cam);
          if (p.se) o.se = vec3(p.se);
          return o;
      }

      function saveWaypoints() {
          try {
              localStorage.setItem(STORE_KEY, JSON.stringify({
                  home:  serializePos(homePos),
                  back:  serializePos(backPos),
                  slots: slots.map(s => serializePos(s)),
              }));
          } catch (_) {}
      }

      function loadWaypoints() {
          try {
              const raw = localStorage.getItem(STORE_KEY);
              if (!raw) return;
              const d = JSON.parse(raw);
              if (d.home)  homePos = d.home;
              if (d.back)  backPos = d.back;
              if (Array.isArray(d.slots)) d.slots.forEach((s, i) => { if (s) slots[i] = s; });
          } catch (_) {}
      }

      function flyOn(kcc, hover) {
          flyActive = true;
          _flyStartTime = Date.now();
          kcc.gravity = 0;
          if (hover) {
              flyUp = false;
              flyDown = false;
              flyVelY = 0;
              kcc._velY = 0;
          } else {
              flyUp     = true;
              flyVelY   = FLY_MIN_SPEED;
              kcc._velY = FLY_MIN_SPEED;
          }
          _flyEmoteLoop();
      }

      function flyOff(kcc, resetVel = true) {
          flyActive = false;
          flyUp     = false;
          flyDown   = false;
          flyVelY   = 0;
          kcc.gravity = GRAVITY;
          if (resetVel) kcc._velY = 0;
          _flyEmoteStop();
      }

      function _flyEmoteLoop() {
          clearTimeout(_flyEmoteTimer);
          _flyEmoteTimer = null;
          if (!flyActive) return;
          const app = W.pc?.app;
          if (!app) return;
          const name = _FLY_EMOTES[Math.random() * _FLY_EMOTES.length | 0];
          app.fire('NetworkManager:Send', 'emotePlay', name);
          const asset = app.assets?.find('Emote-' + name + '.glb', 'animation');
          const dur = asset?.resource?.duration || 4;
          _flyEmoteTimer = setTimeout(_flyEmoteLoop, dur * 1000 + 200);
      }

      function _flyEmoteStop() {
          clearTimeout(_flyEmoteTimer);
          _flyEmoteTimer = null;
          const app = W.pc?.app;
          if (app) app.fire('NetworkManager:Send', 'emoteStop');
      }

      const DESCRIPTION = `
  <h1>Meeland Script — Cheat &amp; Enhancement Suite</h1>
  <p>General-purpose cheat script for Meeland.io. Works across all game modes — <strong>Meeland Hub</strong>, <strong>Escape Waves</strong>, <strong>Steal a Pet</strong>, and <strong>Obby Tower</strong> — on all supported platforms.</p>
  <p><strong>Works on PC and mobile.</strong> On PC, use keyboard shortcuts or click the in-game HUD buttons. On mobile, tap the HUD buttons directly — dedicated fly controls (toggle, up, down) appear on the right side of the screen.</p>
  <p><em>This script is 100% free and always will be. If you have a suggestion, bug report, or something you'd like changed — awesome, leave it in a <strong>positive</strong> review and it'll get looked at. Dropping a 1-star review over a misunderstanding or a missing feature is the fastest way to make sure nothing gets done about it. Good reviews keep development alive. ⭐</em></p>
  <h2>Features</h2>
  <ul>
  <li>🚀 <strong>Fly Hack</strong> — Press <kbd>Space</kbd> to activate. Hold <kbd>Space</kbd> to ascend, <kbd>F</kbd> to descend. Throttle-based acceleration with configurable speed cap and accel rate. Auto-lands on ground contact. On mobile, use the dedicated fly controls (hover mode with up/down buttons).</li>
  <li>⚡ <strong>Speed Hack</strong> — Hold <kbd>Shift</kbd> for accelerating speed boost. Configurable cap and base speed.</li>
  <li>📍 <strong>Waypoints</strong> — <kbd>Q</kbd> saves home, <kbd>\`</kbd> teleports to home, <kbd>Z</kbd> toggles back position. 10 save slots via <kbd>Ctrl+Numpad 0-9</kbd>, recall with <kbd>Numpad 0-9</kbd>. Click the SLOTS button in the HUD for a visual slot manager with Go/Set buttons.</li>
  <li>🐾 <strong>Cuddle Panel</strong> — <kbd>J</kbd> opens a player list sorted by distance. Click a player to teleport to them. Continuous cuddle follows the target in real time (cancelled by moving).</li>
  <li>🐕 <strong>Pet Browser</strong> — <kbd>K</kbd> opens a full pet table with sortable columns (name, mutation, rarity, owner, worth, income, distance). Filter pets with a powerful search supporting AND, OR, and brackets. Teleport to any pet or grab wild/others' pets directly from the table. Configurable auto-refresh interval.</li>
  <li>🔒 <strong>Auto-Lock Base</strong> — Automatically locks your base in Steal a Pet when the lockdown timer expires. Shows lock status and countdown in the HUD.</li>
  <li>🎯 <strong>Pet Sniper</strong> — <kbd>U</kbd> opens the sniper panel. Filter pets by name/mutation/rarity and auto-snipe matching targets. In Escape Waves with ghost mode, silently teleports to grab pets and drops them at your position. In Steal a Pet, teleports to filtered pets on enemy bases. Auto-drop option creates an endless stream of sniped pets.</li>
  <li>🛡️ <strong>Pet Height Spoof</strong> — When carrying a stolen pet, position packets report Y+200 to the server so attackers can't reach you (server proximity check fails).</li>
  <li>💀 <strong>Anti-Death</strong> — Immune to falling off the map and wave kills in Escape Waves.</li>
  <li>💪 <strong>Anti-Knockback</strong> — Immune to attacks and knockback from other players.</li>
  <li>⚔️ <strong>Attack Closest Enemy</strong> — <kbd>G</kbd> teleports to the nearest player and attacks them. Saves your position for quick <kbd>Z</kbd> return.</li>
  <li>👻 <strong>Noclip</strong> — Walk through walls and obstacles. Off by default.</li>
  <li>🛡️ <strong>Invincibility</strong> — Immune to wave kills and fall damage.</li>
  <li>👻 <strong>Ghost Mode</strong> — Fades your character model when position is being spoofed (holding a stolen pet or during snipe teleport). Visual indicator that ghost mechanics are active.</li>
  <li>💰 <strong>Free Daily Money</strong> — Instantly collects all free daily coins without watching ads. Runs automatically once the game connects — no interaction needed.</li>
  <li>🐾 <strong>Auto-Collect Pet Earnings</strong> — Automatically claims accumulated income from all your base pets every 30 seconds. No need to walk over the pads.</li>
  <li>⚙️ <strong>Settings Panel</strong> — <kbd>M</kbd> opens settings. Adjust speed cap, accel rate, base speed, cuddle panel refresh interval, and toggle individual features on/off. Reset to defaults with one click.</li>
  <li>🎮 <strong>Rebindable Keybindings</strong> — All hotkeys can be rebound from the settings panel. Changes persist across sessions.</li>
  <li>🖱️ <strong>Draggable &amp; Resizable Panels</strong> — All panels (cuddle, settings, keybinds, pets, help) can be dragged by their headers and resized. Positions reset on close.</li>
  <li>🎨 <strong>Native In-Game HUD</strong> — Buttons render inside the game canvas using PlayCanvas UI, scaling with the game's resolution. All buttons are clickable on both PC and mobile via an HTML overlay system.</li>
  <li>📱 <strong>Mobile Fly Controls</strong> — On mobile devices, three responsive circular buttons appear on the right: FLY toggle (hover mode), ▲ Up, and ▼ Down. Scale automatically with screen size.</li>
  <li>⭐ <strong>Auto-Free Stars</strong> — Automatically sends getFreeStars request every 60s while in Escape Waves mode.</li>
  <li>💃 <strong>Fly Emotes</strong> — Plays random emotes (network-only, invisible to you) while flying so other players see your character dancing in the sky.</li>
  <li>✏️ <strong>Username Change</strong> — Change your username directly from the Settings panel (one free change per account).</li>
  <li>🏠 <strong>Auto Return Home</strong> — After stealing a pet, automatically teleports back to your base after 10 seconds to deposit safely.</li>
  </ul>
  <h2>Keyboard Shortcuts</h2>
  <p><em>All keybindings are rebindable via Settings → Keybindings.</em></p>
  <ul>
  <li><kbd>Space</kbd> — Toggle fly / fly up</li>
  <li><kbd>F</kbd> (hold) — Fly down</li>
  <li><kbd>Shift</kbd> (hold) — Speed hack</li>
  <li><kbd>Q</kbd> — Set home waypoint</li>
  <li><kbd>\`</kbd> — Teleport to home</li>
  <li><kbd>Z</kbd> — Teleport back (toggle)</li>
  <li><kbd>J</kbd> — Cuddle panel (player list)</li>
  <li><kbd>K</kbd> — Pet browser</li>
  <li><kbd>U</kbd> — Pet sniper panel</li>
  <li><kbd>G</kbd> — Attack closest enemy (TP + hit)</li>
  <li><kbd>I</kbd> — Waypoint slots</li>
  <li><kbd>M</kbd> — Settings panel</li>
  <li><kbd>?</kbd> — This help dialog</li>
  <li><kbd>Ctrl+Numpad 0-9</kbd> — Save position to slot</li>
  <li><kbd>Numpad 0-9</kbd> — Recall position from slot</li>
  </ul>
  <h2>Pet Browser</h2>
  <ul>
  <li>Click column headers to sort (name, mutation, rarity, owner, worth, income, distance)</li>
  <li>Use the search bar to filter — words are AND'd, use OR for alternatives, brackets to group</li>
  <li>➜ button teleports to a pet</li>
  <li>✋ button grabs a pet (teleports, buys, returns, drops at your position)</li>
  <li>Grabbed pets show ✔ for 5 seconds</li>
  <li>Toggle auto-refresh and adjust interval (1-10s) in the header</li>
  </ul>
  <h2>Installation</h2>
  <ol>
  <li>Install a userscript manager:
  <ul>
  <li><strong>Chrome/Brave/Edge:</strong> <a href="https://chrome.google.com/webstore/detail/tampermonkey/dhdgffkkebhmkfjojejmpbldmpobfkfo">Tampermonkey</a> or <a href="https://chrome.google.com/webstore/detail/violentmonkey/jinjaccalgkegednnccohejagnlnfdag">Violentmonkey</a></li>
  <li><strong>Firefox:</strong> <a href="https://addons.mozilla.org/firefox/addon/tampermonkey/">Tampermonkey</a> or <a href="https://addons.mozilla.org/firefox/addon/violentmonkey/">Violentmonkey</a></li>
  </ul>
  </li>
  <li>Click Install above</li>
  <li>Load any Meeland game — the script activates automatically</li>
  </ol>
  <h2>Supported Sites (61+)</h2>
  <p style="font-size:11px;line-height:1.4">meeland.io, CrazyGames, Miniplay.com, MiniPlay.io, PlayMiniGames, RocketGames.io, KBH Games, Gamenora, WooGames.io, TwoPlayerGames.org, KizGame, OmiGames, PlayingFunGames, Gameflare.com, Free AI Games, ZapGames.io, Play-Games.com, GoGy Games, iogames.games, Sprunki-Game.io, 1000Games.io, Play-IOGames.com, ArcadeHippo, Sleepy Arcade, HotGames.io, geodashlite.io, YoPlay.io, Geometry-Free.com, Sprunki.org, Gombis.com, Wordle Unlimited, Games-Kids.com, spacewavesgame.io, amongusfree.io, Veck IO, geometrylitegame.org, Geometry Dash Subzero, Dinosaur-Game.io, Crossy-Road.io, Snow Rider 3D, Drive Mad 3, geometrylitegame.io, That's Not My Neighbor, Melon Playground, Capybara Game, FNF Games, 8Games.net, CrazyGames.tools, LoveMoneyGames.com, Zahraj.cz, iogames.onl, 1Games.io, TrendGames.io, StickmanHook2.io, GeometryDashSpam.io, SGameS.org, robbrainrot.io, Animalverse.social, BasketRandom.io, YupGames.io, GameDistribution CDN, + any site embedding meeland.io in an iframe</p>
  <h2>Privacy</h2>
  <ul>
  <li>✅ Client-side only — no data collected. API calls use the official Meeland API only (username)</li>
  </ul>
  <h2>Disclaimer</h2>
  <blockquote>
  <p>For educational and entertainment purposes. Use at your own risk.</p>
  </blockquote>`;

      let _savedPointerLock = false;
      let _intentionalPointerExit = false;
      const anyPanelOpen = () =>
          $('ml-plist')?.classList.contains('open') ||
          $('ml-settings')?.classList.contains('open') ||
          $('ml-pets')?.classList.contains('open') ||
          $('ml-dialog')?.classList.contains('open') ||
          $('ml-slots-pop')?.classList.contains('open');

      function panelPause() {
          _savedPointerLock = _savedPointerLock || !!document.pointerLockElement;
          if (document.pointerLockElement) {
              _intentionalPointerExit = true;
              document.exitPointerLock();
          }
      }
      function panelResume() {
          if (anyPanelOpen()) return;
          if (_savedPointerLock) {
              const app = W.pc?.app;
              if (app) app.fire('GameManager:LockMouse', 'GameManager:GameResumed', null, 'GameManager:GamePause');
          }
          _savedPointerLock = false;
      }

      let _ptCache = null, _ptCacheTime = 0;
      let _etCache = null, _etCacheTime = 0;
      function _getPetTycoon() {
          const now = Date.now();
          if (_ptCacheTime && now - _ptCacheTime < 5000) return _ptCache;
          _ptCacheTime = now;
          const app = W.pc?.app;
          if (!app) return (_ptCache = null);
          const e = app.root?.findByName('PetTycoonRoom')?.script?.petTycoon
              || app.root?.find(e => e.script?.petTycoon)?.[0]?.script?.petTycoon;
          _ptCache = e || null;
          return _ptCache;
      }
      function _getEscapeTsunami() {
          const now = Date.now();
          if (_etCacheTime && now - _etCacheTime < 5000) return _etCache;
          _etCacheTime = now;
          const app = W.pc?.app;
          if (!app) return (_etCache = null);
          const e = app.root?.findByName('EscapeTsunamiRoom')?.script?.escapeTsunamiRoom
              || app.root?.find(e => e.script?.escapeTsunamiRoom)?.[0]?.script?.escapeTsunamiRoom;
          _etCache = e || null;
          return _etCache;
      }
      let _nmCache = null, _nmCacheTime = 0;
      let _bmCache = null, _bmCacheTime = 0;
      function _getNetworkManager() {
          const now = Date.now();
          if (_nmCacheTime && now - _nmCacheTime < 5000) return _nmCache;
          _nmCacheTime = now;
          _nmCache = W.pc?.app?.root?.findByName('NetworkManager')?.script?.networkManager || null;
          return _nmCache;
      }
      function _getBasesManager() {
          const now = Date.now();
          if (_bmCacheTime && now - _bmCacheTime < 5000) return _bmCache;
          _bmCacheTime = now;
          _bmCache = W.pc?.app?.root?.findByName('Bases')?.script?.petTycoonBasesManager || null;
          return _bmCache;
      }
      const _serverLockedSessions = new Set();
      let _lockDataReceived = false;

      function createHUD() {
          if ($('ml-hud')) return;
          const s = document.createElement('style');
          s.textContent = [
              '#ml-hud{display:none}',
              '#ml-dialog{position:fixed;top:0;left:0;right:0;bottom:0;background:rgba(0,0,0,.75);z-index:100000;display:none;overflow:auto;padding:40px 20px;box-sizing:border-box;scrollbar-width:auto;scrollbar-color:rgba(160,120,240,.5) rgba(255,255,255,.06);-webkit-overflow-scrolling:touch;touch-action:pan-y}',
              '#ml-dialog::-webkit-scrollbar{width:8px}',
              '#ml-dialog::-webkit-scrollbar-track{background:rgba(255,255,255,.06);border-radius:4px}',
              '#ml-dialog::-webkit-scrollbar-thumb{background:rgba(160,120,240,.5);border-radius:4px;border:1px solid rgba(255,255,255,.08)}',
              '#ml-dialog::-webkit-scrollbar-thumb:hover{background:rgba(160,120,240,.7)}',
              '#ml-dialog.open{display:block}',
              '#ml-inner{background:#181818;color:#ccc;max-width:580px;margin:0 auto;border-radius:8px;padding:24px 28px;font-family:sans-serif;font-size:14px;line-height:1.65;position:relative}',
              '#ml-inner h1{color:#fff;font-size:1.2em;margin:0 0 10px}',
              '#ml-inner h2{color:#aaa;font-size:.85em;text-transform:uppercase;letter-spacing:.08em;margin:18px 0 6px;border-top:1px solid #333;padding-top:12px}',
              '#ml-inner li{margin:4px 0}',
              '#ml-inner kbd{background:#2a2a2a;border:1px solid #444;border-radius:3px;padding:1px 5px;font-family:monospace;font-size:.9em}',
              '#ml-inner a{color:#4af}',
              '#ml-inner blockquote{border-left:3px solid #333;margin:8px 0 0;padding:6px 12px;color:#888;font-size:.9em}',
              '#ml-close{position:absolute;top:10px;right:14px;cursor:pointer;color:#555;font-size:20px;background:none;border:none;font-family:monospace;line-height:1}',
              '#ml-close:hover{color:#fff}',

              '#ml-plist{position:fixed;top:50%;right:20px;transform:translateY(-50%);width:260px;max-height:70vh;display:none;z-index:100001;pointer-events:auto;overflow:hidden;border-radius:16px;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;background:linear-gradient(170deg,rgba(55,45,80,.92) 0%,rgba(45,38,72,.94) 40%,rgba(40,32,65,.95) 100%);border:1.5px solid rgba(160,120,240,.3);box-shadow:0 8px 32px rgba(0,0,0,.4),0 0 0 1px rgba(80,50,140,.2),0 0 60px rgba(120,80,200,.1);user-select:none}',
              '#ml-plist::before{content:"";position:absolute;top:0;left:0;right:0;height:3px;background:linear-gradient(90deg,#7c3aed,#a855f7,#c084fc,#a855f7,#7c3aed);border-radius:16px 16px 0 0;z-index:1}',
              '#ml-plist.open{display:flex;flex-direction:column}',
              '#ml-plist-head{padding:14px 14px 10px;display:flex;align-items:center;justify-content:space-between}',
              '#ml-plist-title{font-size:14px;font-weight:800;color:rgba(255,250,255,.97);letter-spacing:.02em}',
              '#ml-plist-timer{font-size:10px;color:rgba(190,155,245,.65);font-variant-numeric:tabular-nums;margin-left:6px;font-weight:600}',
              '#ml-plist-refresh{cursor:pointer;background:rgba(140,100,220,.12);border:1px solid rgba(140,100,220,.25);color:rgba(200,170,255,.8);font-size:12px;padding:3px 9px;border-radius:8px;font-family:inherit;transition:all .15s}',
              '#ml-plist-refresh:hover{background:rgba(140,100,220,.22);color:rgba(240,220,255,.95);border-color:rgba(140,100,220,.4)}',
              '#ml-plist-close{cursor:pointer;background:rgba(255,255,255,.08);border:none;color:rgba(220,200,245,.6);font-size:16px;width:28px;height:28px;display:flex;align-items:center;justify-content:center;border-radius:8px;font-family:inherit;line-height:1;transition:all .15s}',
              '#ml-plist-close:hover{color:#ff6b6b;background:rgba(255,80,80,.15)}',
              '#ml-plist-body{overflow-y:auto;flex:1;padding:4px 8px 6px;scrollbar-width:auto;scrollbar-color:rgba(160,120,240,.5) rgba(255,255,255,.06);-webkit-overflow-scrolling:touch;touch-action:pan-y}',
              '#ml-plist-body::-webkit-scrollbar{width:8px}',
              '#ml-plist-body::-webkit-scrollbar-track{background:rgba(255,255,255,.06);border-radius:4px}',
              '#ml-plist-body::-webkit-scrollbar-thumb{background:rgba(160,120,240,.5);border-radius:4px;border:1px solid rgba(255,255,255,.08)}',
              '#ml-plist-body::-webkit-scrollbar-thumb:hover{background:rgba(160,120,240,.7)}',
              '#ml-plist-empty{color:rgba(190,155,245,.5);text-align:center;padding:20px 8px;font-size:12px;font-style:italic}',
              '.ml-prow{display:flex;align-items:center;gap:8px;padding:7px 10px;margin:2px 0;border-radius:10px;cursor:pointer;transition:all .15s;border:1px solid transparent;background:rgba(140,100,220,.04)}',
              '.ml-prow:hover{background:rgba(140,100,220,.1);border-color:rgba(140,100,220,.15)}',
              '.ml-prow:active{background:rgba(140,100,220,.18);transform:scale(.98);border-color:rgba(140,100,220,.25)}',
              '.ml-pnum{font-size:10px;color:rgba(168,130,230,.6);font-weight:700;min-width:16px;text-align:right;font-variant-numeric:tabular-nums}',
              '.ml-pinfo{flex:1;overflow:hidden}',
              '.ml-pname{font-size:12.5px;font-weight:600;color:rgba(255,250,255,.97);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;line-height:1.3}',
              '.ml-pdist{font-size:10px;color:rgba(168,130,230,.55);line-height:1.2}',
              '.ml-parrow{color:rgba(140,100,220,.3);font-size:14px;transition:all .15s}',
              '.ml-prow:hover .ml-parrow{color:rgba(168,130,230,.75);transform:translateX(3px)}',

              '#ml-settings{position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);width:320px;max-height:80vh;display:none;z-index:100002;pointer-events:auto;overflow:hidden;border-radius:16px;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;user-select:none;background:linear-gradient(170deg,rgba(55,45,80,.92) 0%,rgba(45,38,72,.94) 40%,rgba(40,32,65,.95) 100%);border:1.5px solid rgba(160,120,240,.3);box-shadow:0 8px 32px rgba(0,0,0,.4),0 0 0 1px rgba(80,50,140,.2),0 0 60px rgba(120,80,200,.1)}',
              '#ml-settings::before{content:"";position:absolute;top:0;left:0;right:0;height:3px;background:linear-gradient(90deg,#7c3aed,#a855f7,#c084fc,#a855f7,#7c3aed);border-radius:16px 16px 0 0;z-index:1}',
              '#ml-settings.open{display:flex;flex-direction:column}',
              '#ml-settings-head{position:relative;padding:18px 18px 14px;display:flex;align-items:center;justify-content:space-between}',
              '#ml-settings-title{font-size:16px;font-weight:800;color:rgba(255,250,255,.97);letter-spacing:.02em}',
              '#ml-settings-close{cursor:pointer;background:rgba(255,255,255,.08);border:none;color:rgba(220,200,245,.6);font-size:16px;width:30px;height:30px;display:flex;align-items:center;justify-content:center;border-radius:8px;font-family:inherit;line-height:1;transition:all .15s}',
              '#ml-settings-close:hover{color:#ff6b6b;background:rgba(255,80,80,.15)}',
              '#ml-settings-reset{cursor:pointer;background:rgba(255,255,255,.08);border:1px solid rgba(160,120,240,.25);color:rgba(220,200,245,.7);font-size:10px;padding:5px 14px;border-radius:8px;font-family:inherit;font-weight:600;transition:all .15s}',
              '#ml-settings-reset:hover{color:rgba(240,220,255,.9);background:rgba(140,100,220,.15);border-color:rgba(140,100,220,.4)}',
              '#ml-settings-body{position:relative;overflow-y:auto;flex:1;padding:2px 12px 14px;scrollbar-width:auto;scrollbar-color:rgba(160,120,240,.5) rgba(255,255,255,.06);-webkit-overflow-scrolling:touch;touch-action:pan-y}',
              '#ml-settings-body::-webkit-scrollbar{width:8px}',
              '#ml-settings-body::-webkit-scrollbar-track{background:rgba(255,255,255,.06);border-radius:4px}',
              '#ml-settings-body::-webkit-scrollbar-thumb{background:rgba(160,120,240,.5);border-radius:4px;border:1px solid rgba(255,255,255,.08)}',
              '#ml-settings-body::-webkit-scrollbar-thumb:hover{background:rgba(160,120,240,.7)}',
              '.ml-sgroup{margin:0 0 2px}',
              '#ml-tabs{display:flex;gap:4px;padding:0 16px 12px;margin-bottom:2px;border-bottom:1px solid rgba(160,120,240,.15)}',
              '.ml-tab{flex:1;padding:8px 0;font-size:11px;font-weight:700;text-align:center;color:rgba(200,180,240,.5);cursor:pointer;transition:all .2s;border-radius:10px;background:rgba(255,255,255,.05);border:1px solid transparent;font-family:inherit}',
              '.ml-tab:hover{color:rgba(200,180,240,.7);background:rgba(140,100,220,.08)}',
              '.ml-tab.active{color:#fff;background:linear-gradient(135deg,rgba(140,90,230,.45) 0%,rgba(110,70,210,.35) 100%);border-color:rgba(160,110,240,.35);box-shadow:0 2px 12px rgba(130,80,220,.25),inset 0 1px 0 rgba(255,255,255,.1)}',
              '.ml-tab-content{display:none;min-height:380px}',
              '.ml-tab-content.active{display:block}',
              '.ml-srow{display:flex;align-items:center;justify-content:space-between;padding:9px 10px;border-radius:10px;transition:all .12s;margin:1px 0}',
              '.ml-srow:hover{background:rgba(140,100,220,.06)}',
              '.ml-srow-label{font-size:13px;color:rgba(245,238,255,.92);flex:1;font-weight:500}',
              '.ml-srow-value{font-size:11px;color:rgba(190,155,245,.9);font-variant-numeric:tabular-nums;min-width:34px;text-align:right;margin-right:8px;font-weight:700}',
              '.ml-toggle{position:relative;width:40px;height:22px;border-radius:11px;background:rgba(255,255,255,.12);border:1.5px solid rgba(255,255,255,.15);cursor:pointer;transition:all .2s;flex-shrink:0}',
              '.ml-toggle::after{content:"";position:absolute;top:3px;left:3px;width:14px;height:14px;border-radius:7px;background:rgba(255,255,255,.35);transition:all .2s;box-shadow:0 1px 3px rgba(0,0,0,.3)}',
              '.ml-toggle.on{background:linear-gradient(135deg,#7c3aed 0%,#a855f7 100%);border-color:rgba(168,85,247,.5)}',
              '.ml-toggle.on::after{left:21px;background:#fff;box-shadow:0 1px 4px rgba(120,60,220,.4),0 0 8px rgba(168,85,247,.3)}',
              '.ml-slider{-webkit-appearance:none;appearance:none;width:90px;height:6px;border-radius:3px;background:rgba(255,255,255,.12);outline:none;cursor:pointer;border:none}',
              '.ml-slider::-webkit-slider-thumb{-webkit-appearance:none;width:18px;height:18px;border-radius:9px;background:linear-gradient(135deg,#a855f7 0%,#7c3aed 100%);border:2px solid rgba(255,255,255,.25);box-shadow:0 2px 8px rgba(120,60,220,.4);cursor:pointer;transition:all .15s}',
              '.ml-slider::-webkit-slider-thumb:hover{transform:scale(1.1);box-shadow:0 2px 12px rgba(120,60,220,.5)}',
              '.ml-slider::-moz-range-thumb{width:18px;height:18px;border-radius:9px;background:linear-gradient(135deg,#a855f7 0%,#7c3aed 100%);border:2px solid rgba(255,255,255,.25);box-shadow:0 2px 8px rgba(120,60,220,.4);cursor:pointer}',

              '#ml-keybinds-body{padding:0}',
              '.ml-krow{display:flex;align-items:center;justify-content:space-between;padding:9px 10px;border-radius:10px;cursor:pointer;transition:all .12s;margin:1px 0}',
              '.ml-krow:hover{background:rgba(140,100,220,.06)}',
              '.ml-krow.listening{background:rgba(255,180,40,.1);border:1px solid rgba(255,180,40,.35);border-radius:10px}',
              '.ml-krow-action{font-size:13px;color:rgba(245,238,255,.92);font-weight:500}',
              '.ml-krow-key{font-size:11px;color:rgba(190,155,245,.9);font-weight:700;padding:4px 10px;border-radius:8px;background:rgba(140,100,220,.1);border:1px solid rgba(140,100,220,.2);min-width:40px;text-align:center;transition:all .15s}',
              '.ml-krow.listening .ml-krow-key{color:rgba(255,200,80,.9);background:rgba(120,80,0,.15);border-color:rgba(255,180,40,.4)}',

              '#ml-pets{position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);width:680px;max-height:82vh;display:none;z-index:100004;pointer-events:auto;overflow:hidden;border-radius:16px;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;user-select:none;background:linear-gradient(170deg,rgba(55,45,80,.92) 0%,rgba(45,38,72,.94) 40%,rgba(40,32,65,.95) 100%);border:1.5px solid rgba(160,120,240,.3);box-shadow:0 8px 32px rgba(0,0,0,.4),0 0 0 1px rgba(80,50,140,.2),0 0 60px rgba(120,80,200,.1)}',
              '#ml-pets::before{content:"";position:absolute;top:0;left:0;right:0;height:3px;background:linear-gradient(90deg,#7c3aed,#a855f7,#c084fc,#a855f7,#7c3aed);border-radius:16px 16px 0 0;z-index:1}',
              '#ml-pets.open{display:flex;flex-direction:column}',
              '#ml-pets-head{position:relative;padding:16px 18px 12px;display:flex;align-items:center;justify-content:space-between}',
              '#ml-pets-title{font-size:16px;font-weight:800;color:rgba(255,250,255,.97);letter-spacing:.02em}',
              '#ml-pets-count{font-size:10px;color:rgba(190,155,245,.7);margin-left:8px;font-weight:600}',
              '#ml-pets-close{cursor:pointer;background:rgba(255,255,255,.08);border:none;color:rgba(220,200,245,.6);font-size:16px;width:30px;height:30px;display:flex;align-items:center;justify-content:center;border-radius:8px;font-family:inherit;line-height:1;transition:all .15s}',
              '#ml-pets-close:hover{color:#ff6b6b;background:rgba(255,80,80,.15)}',
              '#ml-pets-filter{position:relative;padding:8px 14px;border-bottom:1px solid rgba(160,120,240,.15)}',
              '#ml-pets-search{width:100%;background:rgba(255,255,255,.07);border:1.5px solid rgba(160,120,240,.25);color:rgba(240,230,255,.9);font-size:11px;padding:7px 12px;border-radius:10px;font-family:inherit;outline:none;box-sizing:border-box;transition:all .15s}',
              '#ml-pets-search:focus{border-color:rgba(140,100,220,.45);box-shadow:0 0 8px rgba(120,70,200,.12)}',
              '#ml-pets-search::placeholder{color:rgba(168,130,230,.35)}',
              '#ml-pets-body{position:relative;overflow-y:auto;flex:1;padding:0;scrollbar-width:auto;scrollbar-color:rgba(160,120,240,.5) rgba(255,255,255,.06);-webkit-overflow-scrolling:touch;touch-action:pan-y}',
              '#ml-pets-body::-webkit-scrollbar{width:8px}',
              '#ml-pets-body::-webkit-scrollbar-track{background:rgba(255,255,255,.06);border-radius:4px}',
              '#ml-pets-body::-webkit-scrollbar-thumb{background:rgba(160,120,240,.5);border-radius:4px;border:1px solid rgba(255,255,255,.08)}',
              '#ml-pets-body::-webkit-scrollbar-thumb:hover{background:rgba(160,120,240,.7)}',
              '#ml-ptable{width:100%;border-collapse:collapse;font-size:11px}',
              '#ml-ptable th{position:sticky;top:0;background:linear-gradient(180deg,rgba(55,45,80,.98),rgba(45,38,72,.95));color:rgba(168,130,230,.7);font-size:9.5px;text-transform:uppercase;letter-spacing:.15em;padding:7px 8px;text-align:left;cursor:pointer;border-bottom:1px solid rgba(140,100,220,.15);white-space:nowrap;transition:color .12s;z-index:1;font-weight:700}',
              '#ml-ptable th:hover{color:rgba(200,170,255,.95)}',
              '#ml-ptable th.sort-asc::after{content:" ▲";font-size:8px}',
              '#ml-ptable th.sort-desc::after{content:" ▼";font-size:8px}',
              '#ml-ptable td{padding:5px 8px;border-bottom:1px solid rgba(160,120,240,.08);color:rgba(245,238,255,.85);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:140px}',
              '#ml-ptable tr:hover td{background:rgba(140,100,220,.08)}',
              '#ml-ptable .own-you{color:rgba(80,255,120,.9);font-weight:600}',
              '#ml-ptable .own-wild{color:rgba(255,190,60,.85);font-style:italic}',
              '#ml-ptable .pet-var{color:rgba(180,140,255,.85);font-style:italic}',
              '#ml-ptable .pet-mut-Golden{color:rgba(255,215,0,.95)}',
              '#ml-ptable .pet-mut-Diamond{color:rgba(185,242,255,.95)}',
              '#ml-ptable .pet-mut-Emerald{color:rgba(80,255,120,.95)}',
              '#ml-ptable .pet-mut-Rainbow{color:rgba(255,120,200,.95)}',
              '#ml-ptable .pet-mut-Galaxy{color:rgba(200,140,255,.95)}',
              '#ml-ptable .pet-rar{font-size:10px;letter-spacing:.05em}',
              '.pet-tp{background:rgba(140,100,220,.15);border:1px solid rgba(140,100,220,.3);color:rgba(200,170,255,.8);font-size:11px;padding:3px 8px;border-radius:8px;cursor:pointer;transition:all .12s;line-height:1}',
              '.pet-tp:hover{background:rgba(140,100,220,.3);color:#fff;border-color:rgba(168,120,255,.6)}',
              '.pet-grab{background:rgba(60,200,60,.15);border:1px solid rgba(60,200,60,.3);color:rgba(140,255,140,.8);font-size:11px;padding:3px 8px;border-radius:8px;cursor:pointer;transition:all .12s;line-height:1;margin-left:3px}',
              '.pet-grab:hover{background:rgba(60,200,60,.3);color:#fff;border-color:rgba(80,230,80,.6)}',
              '#ml-pets-empty{color:rgba(190,155,245,.5);text-align:center;padding:30px 8px;font-size:12px;font-style:italic}',

              '#ml-slots-pop{position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);width:300px;max-height:70vh;display:none;z-index:100003;pointer-events:auto;overflow:hidden;border-radius:16px;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;user-select:none;background:linear-gradient(170deg,rgba(55,45,80,.92) 0%,rgba(45,38,72,.94) 40%,rgba(40,32,65,.95) 100%);border:1.5px solid rgba(160,120,240,.3);box-shadow:0 8px 32px rgba(0,0,0,.4),0 0 0 1px rgba(80,50,140,.2),0 0 60px rgba(120,80,200,.1)}',
              '#ml-slots-pop::before{content:"";position:absolute;top:0;left:0;right:0;height:3px;background:linear-gradient(90deg,#7c3aed,#a855f7,#c084fc,#a855f7,#7c3aed);border-radius:16px 16px 0 0;z-index:1}',
              '#ml-slots-pop.open{display:flex;flex-direction:column}',
              '#ml-slots-pop-head{padding:14px 14px 10px;display:flex;align-items:center;justify-content:space-between}',
              '#ml-slots-pop-title{font-size:14px;font-weight:800;color:rgba(255,250,255,.97);letter-spacing:.02em}',
              '#ml-slots-pop-close{cursor:pointer;background:rgba(255,255,255,.08);border:none;color:rgba(220,200,245,.6);font-size:16px;width:28px;height:28px;display:flex;align-items:center;justify-content:center;border-radius:8px;font-family:inherit;line-height:1;transition:all .15s}',
              '#ml-slots-pop-close:hover{color:#ff6b6b;background:rgba(255,80,80,.15)}',
              '#ml-slots-pop-body{overflow-y:auto;flex:1;padding:4px 8px 8px;scrollbar-width:auto;scrollbar-color:rgba(160,120,240,.5) rgba(255,255,255,.06);-webkit-overflow-scrolling:touch;touch-action:pan-y}',
              '#ml-slots-pop-body::-webkit-scrollbar{width:8px}',
              '#ml-slots-pop-body::-webkit-scrollbar-track{background:rgba(255,255,255,.06);border-radius:4px}',
              '#ml-slots-pop-body::-webkit-scrollbar-thumb{background:rgba(160,120,240,.5);border-radius:4px;border:1px solid rgba(255,255,255,.08)}',
              '#ml-slots-pop-body::-webkit-scrollbar-thumb:hover{background:rgba(160,120,240,.7)}',
              '.ml-slot{display:flex;align-items:center;gap:6px;padding:5px 8px;margin:2px 0;border-radius:8px;transition:all .12s;border:1px solid transparent}',
              '.ml-slot:hover{background:rgba(140,100,220,.1);border-color:rgba(140,100,220,.15)}',
              '.ml-slot-num{font-size:10px;color:rgba(168,130,230,.6);font-weight:700;min-width:14px;text-align:right}',
              '.ml-slot-info{flex:1;font-size:10px;color:rgba(200,180,240,.65);overflow:hidden;white-space:nowrap;text-overflow:ellipsis}',
              '.ml-slot-info.filled{color:rgba(200,180,240,.9)}',
              '.ml-slot-go{background:rgba(140,100,220,.15);border:1px solid rgba(140,100,220,.25);color:rgba(200,170,255,.8);font-size:10px;padding:2px 8px;border-radius:6px;cursor:pointer;transition:all .12s}',
              '.ml-slot-go:hover{background:rgba(140,100,220,.3);color:#fff}',
              '.ml-slot-go.empty{opacity:.35;pointer-events:none}',
              '.ml-slot-set{background:rgba(60,180,60,.12);border:1px solid rgba(60,180,60,.25);color:rgba(140,230,140,.8);font-size:10px;padding:2px 8px;border-radius:6px;cursor:pointer;transition:all .12s}',
              '.ml-slot-set:hover{background:rgba(60,180,60,.25);color:#fff}',
              '.ml-slot-clr{background:rgba(220,60,60,.12);border:1px solid rgba(220,60,60,.25);color:rgba(255,140,140,.8);font-size:10px;padding:2px 6px;border-radius:6px;cursor:pointer;transition:all .12s}',
              '.ml-slot-clr:hover{background:rgba(220,60,60,.25);color:#fff}',
              '.ml-slot-clr.empty{opacity:.35;pointer-events:none}',

              '#ml-snipe{position:fixed;top:60px;right:10px;width:360px;max-height:calc(100vh - 20px);display:none;z-index:100005;pointer-events:auto;overflow:hidden auto;border-radius:16px;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;user-select:none;background:linear-gradient(170deg,rgba(55,45,80,.92) 0%,rgba(45,38,72,.94) 40%,rgba(40,32,65,.95) 100%);border:1.5px solid rgba(240,120,120,.3);box-shadow:0 8px 32px rgba(0,0,0,.4),0 0 0 1px rgba(140,50,50,.2),0 0 60px rgba(200,80,80,.1)}',
              '#ml-snipe::before{content:"";position:absolute;top:0;left:0;right:0;height:3px;background:linear-gradient(90deg,#ed3a3a,#f75555,#fc8484,#f75555,#ed3a3a);border-radius:16px 16px 0 0;z-index:1}',
              '#ml-snipe.open{display:flex;flex-direction:column}',
              '#ml-snipe-head{padding:14px 14px 10px;display:flex;align-items:center;justify-content:space-between}',
              '#ml-snipe-title{font-size:14px;font-weight:800;color:rgba(255,250,255,.97);letter-spacing:.02em}',
              '#ml-snipe-close{cursor:pointer;background:rgba(255,255,255,.08);border:none;color:rgba(220,200,245,.6);font-size:16px;width:28px;height:28px;display:flex;align-items:center;justify-content:center;border-radius:8px;font-family:inherit;line-height:1;transition:all .15s}',
              '#ml-snipe-close:hover{color:#ff6b6b;background:rgba(255,80,80,.15)}',
              '#ml-snipe-body{padding:8px 14px 14px}',
              '#ml-snipe-search{width:100%;background:rgba(255,255,255,.07);border:1.5px solid rgba(240,120,120,.25);color:rgba(240,230,255,.9);font-size:11px;padding:7px 12px;border-radius:10px;font-family:inherit;outline:none;box-sizing:border-box;transition:all .15s}',
              '#ml-snipe-search:focus{border-color:rgba(240,120,120,.45);box-shadow:0 0 8px rgba(200,70,70,.12)}',
              '#ml-snipe-search::placeholder{color:rgba(255,170,170,.5)}',
              '#ml-snipe-toggle{width:100%;margin-top:10px;padding:8px 0;border-radius:10px;border:1.5px solid rgba(240,120,120,.3);background:rgba(240,120,120,.12);color:rgba(255,200,200,.95);font-size:12px;font-weight:700;cursor:pointer;transition:all .15s;font-family:inherit}',
              '#ml-snipe-toggle:hover{background:rgba(240,120,120,.25)}',
              '#ml-snipe-toggle.active{background:rgba(60,200,60,.15);border-color:rgba(60,200,60,.3);color:rgba(140,255,140,.95)}',
              '#ml-snipe-status{margin-top:8px;font-size:11px;color:rgba(255,220,220,.85);min-height:14px}',
              '#ml-snipe-types{margin-top:4px;font-size:10px;color:rgba(255,220,210,.9);line-height:1.5;max-height:80px;overflow-y:auto;scrollbar-width:thin;scrollbar-color:rgba(240,120,120,.3) transparent;-webkit-overflow-scrolling:touch;touch-action:pan-y}',
              '.ml-snipe-type-row{padding:1px 0;border-bottom:1px solid rgba(240,120,120,.06)}',
              '#ml-snipe-log{margin-top:6px;max-height:120px;overflow-y:auto;font-size:9.5px;color:rgba(220,200,240,.7);scrollbar-width:thin;scrollbar-color:rgba(160,120,240,.4) transparent;-webkit-overflow-scrolling:touch;touch-action:pan-y}',
              '.ml-snipe-entry{padding:1px 0;border-bottom:1px solid rgba(160,120,240,.08);font-family:monospace;letter-spacing:-.02em}',
              '#ml-carry-timer{position:fixed;bottom:18px;left:18px;display:none;padding:6px 14px;border-radius:10px;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;font-size:14px;font-weight:800;color:rgba(255,220,140,.95);background:rgba(40,32,60,.85);border:1.5px solid rgba(255,180,80,.35);box-shadow:0 4px 16px rgba(0,0,0,.3);z-index:100010;pointer-events:none;letter-spacing:.03em}',
          ].join('');
          document.head.appendChild(s);
          const h = document.createElement('div');
          const carryTimer = document.createElement('div');
          carryTimer.id = 'ml-carry-timer';
          document.body.appendChild(carryTimer);
          h.id = 'ml-hud';
          h.innerHTML = '<span id="ml-fly"></span><span id="ml-spr"></span><span id="ml-home"></span><span id="ml-go"></span><span id="ml-back"></span><span id="ml-slots"></span><span id="ml-lock"></span><span id="ml-tp"></span><span id="ml-pets-btn"></span><span id="ml-snipe-btn"></span><span id="ml-cfg"></span><span id="ml-help"></span>';
          document.body.appendChild(h);
          const d = document.createElement('div');
          d.id = 'ml-dialog';
          d.innerHTML = `<div id="ml-inner"><button id="ml-close">✕</button>${DESCRIPTION}</div>`;
          document.body.appendChild(d);

          const plist = document.createElement('div');
          plist.id = 'ml-plist';
          plist.innerHTML = '<div id="ml-plist-head"><div style="display:flex;align-items:baseline;gap:6px"><span id="ml-plist-title">Players</span><span id="ml-plist-timer">10s</span></div><div style="display:flex;gap:6px;align-items:center"><button id="ml-plist-refresh">↻</button><button id="ml-plist-close">✕</button></div></div><div id="ml-plist-body"><div id="ml-plist-empty">No other players found</div></div>';
          document.body.appendChild(plist);

          const settings = document.createElement('div');
          settings.id = 'ml-settings';
          settings.innerHTML = `<div id="ml-settings-head"><span id="ml-settings-title">Settings</span><div style="display:flex;gap:6px;align-items:center"><button id="ml-settings-reset">Reset</button><button id="ml-settings-close">✕</button></div></div>
  <div id="ml-tabs">
  <div class="ml-tab active" data-tab="move">Move</div>
  <div class="ml-tab" data-tab="features">Features</div>
  <div class="ml-tab" data-tab="auto">Auto</div>
  <div class="ml-tab" data-tab="keys">Keys</div>
  </div>
  <div id="ml-settings-body">
  <div class="ml-tab-content active" data-tab="move">
  <div class="ml-sgroup">
  <div class="ml-srow"><span class="ml-srow-label">Acceleration</span><div class="ml-toggle on" id="ml-s-accel-en"></div></div>
  <div class="ml-srow"><span class="ml-srow-label">Speed Cap</span><span class="ml-srow-value" id="ml-sv-cap">${SPEED_CAP}</span><input type="range" class="ml-slider" id="ml-s-cap" min="0" max="300" value="${SPEED_CAP}"></div>
  <div class="ml-srow"><span class="ml-srow-label">Accel Rate</span><span class="ml-srow-value" id="ml-sv-accel">${ACCEL}</span><input type="range" class="ml-slider" id="ml-s-accel" min="0" max="60" value="${ACCEL}"></div>
  <div class="ml-srow"><span class="ml-srow-label">Base Speed</span><span class="ml-srow-value" id="ml-sv-base">${SPEED_DEFAULT}</span><input type="range" class="ml-slider" id="ml-s-base" min="0" max="20" value="${SPEED_DEFAULT}"></div>
  </div>
  </div>
  <div class="ml-tab-content" data-tab="features">
  <div class="ml-sgroup">
  <div class="ml-srow"><span class="ml-srow-label">Fly Hack</span><div class="ml-toggle on" id="ml-f-fly"></div></div>
  <div class="ml-srow"><span class="ml-srow-label">Speed Hack</span><div class="ml-toggle on" id="ml-f-sprint"></div></div>
  <div class="ml-srow"><span class="ml-srow-label">Waypoints</span><div class="ml-toggle on" id="ml-f-waypoints"></div></div>
  <div class="ml-srow"><span class="ml-srow-label">Anti-Knockback</span><div class="ml-toggle on" id="ml-f-antiknockback"></div></div>
  <div class="ml-srow"><span class="ml-srow-label">Noclip</span><div class="ml-toggle" id="ml-f-noclip"></div></div>
  <div class="ml-srow"><span class="ml-srow-label">Free Daily Money</span><div class="ml-toggle on" id="ml-f-freemoney"></div></div>
  <div class="ml-srow"><span class="ml-srow-label">Invincibility</span><div class="ml-toggle on" id="ml-f-invincible"></div></div>
  <div class="ml-srow"><span class="ml-srow-label">Ghost Mode</span><div class="ml-toggle on" id="ml-f-ghostmode"></div></div>
  </div>
  <div class="ml-sgroup" style="margin-top:8px">
  <div class="ml-srow" style="flex-direction:column;align-items:stretch;gap:6px">
  <div style="display:flex;justify-content:space-between;align-items:center"><span class="ml-srow-label">Username</span><span id="ml-uname-status" style="font-size:10px;color:rgba(180,160,220,.6)"></span></div>
  <div style="display:flex;gap:6px"><input id="ml-uname-input" type="text" maxlength="12" placeholder="New username (3-12 chars)" style="flex:1;background:rgba(255,255,255,.08);border:1px solid rgba(160,120,240,.25);border-radius:8px;color:rgba(240,230,255,.9);padding:5px 10px;font-size:12px;font-family:inherit;outline:none"><button id="ml-uname-btn" style="padding:5px 14px;border-radius:8px;border:1px solid rgba(160,120,240,.3);background:rgba(100,80,180,.25);color:rgba(220,200,245,.8);font-size:11px;font-weight:600;font-family:inherit;cursor:pointer;white-space:nowrap">Change</button></div>
  </div>
  </div>
  </div>
  <div class="ml-tab-content" data-tab="auto">
  <div class="ml-sgroup">
  <div class="ml-srow"><span class="ml-srow-label">Cuddle Panel</span><div class="ml-toggle on" id="ml-f-cuddle"></div></div>
  <div class="ml-srow"><span class="ml-srow-label">Continuous Cuddle</span><div class="ml-toggle on" id="ml-f-cuddle-follow"></div></div>
  <div class="ml-srow"><span class="ml-srow-label">Pet Browser</span><div class="ml-toggle on" id="ml-f-pets"></div></div>
  <div class="ml-srow"><span class="ml-srow-label">Auto-Lock Base</span><div class="ml-toggle on" id="ml-f-autolock"></div></div>

  <div class="ml-srow"><span class="ml-srow-label">Auto-Collect Earnings</span><div class="ml-toggle on" id="ml-f-autocollect"></div></div>
  <div class="ml-srow"><span class="ml-srow-label">Collect Interval</span><span class="ml-srow-value" id="ml-sv-acinterval">${autoCollectInterval}s</span><input type="range" class="ml-slider" id="ml-s-acinterval" min="10" max="120" value="${autoCollectInterval}"></div>
  <div class="ml-srow"><span class="ml-srow-label">Auto-Free Stars</span><div class="ml-toggle on" id="ml-f-freestars"></div></div>
  <div class="ml-srow"><span class="ml-srow-label">Auto-Attack Pet Thieves</span><div class="ml-toggle on" id="ml-f-autoattack"></div></div>
  </div>
  </div>
  <div class="ml-tab-content" data-tab="keys">
  <div class="ml-sgroup" id="ml-keybinds-body"></div>
  </div>
  </div>`;
          document.body.appendChild(settings);

          const KB_LABELS = {
              fly: 'Fly (toggle/up)',
              flyDown: 'Fly Down',
              setHome: 'Set Home',
              home: 'Teleport Home',
              back: 'Back Teleport',
              cuddle: 'Cuddle Panel',
              settings: 'Settings',
              pets: 'Pet Browser',
              slots: 'Waypoint Slots',
              snipe: 'Pet Sniper',
              attack: 'Attack',
              help: 'Help / Description',
          };

          function keyCodeLabel(code) {
              if (code === 'Space') return 'Space';
              if (code === 'Backquote') return '`';
              if (code.startsWith('Key')) return code.slice(3);
              if (code.startsWith('Digit')) return code.slice(5);
              return code;
          }

          function buildKeybindsHTML() {
              let rows = '';
              for (const [action, code] of Object.entries(KEYBINDS)) {
                  rows += '<div class="ml-krow" data-action="' + action + '"><span class="ml-krow-action">' + (KB_LABELS[action] || action) + '</span><span class="ml-krow-key">' + keyCodeLabel(code) + '</span></div>';
              }
              $('ml-keybinds-body').innerHTML = rows;
          }
          buildKeybindsHTML();

          const petsPanel = document.createElement('div');
          petsPanel.id = 'ml-pets';
          petsPanel.innerHTML = '<div id="ml-pets-head"><div style="display:flex;align-items:baseline;gap:6px"><span id="ml-pets-title">Pets</span><span id="ml-pets-count"></span></div><div style="display:flex;gap:8px;align-items:center"><label style="display:flex;align-items:center;gap:4px;cursor:pointer;font-size:10px;color:rgba(168,130,230,.6)"><input type="checkbox" id="ml-pets-auto"> Auto</label><span style="display:flex;align-items:center;gap:3px;font-size:10px;color:rgba(168,130,230,.5)"><input type="range" id="ml-pets-interval" min="1" max="10" value="' + petRefreshInterval + '" style="width:50px;height:3px" class="ml-slider"><span id="ml-pets-iv">' + petRefreshInterval + '</span>s</span><button id="ml-pets-close">✕</button></div></div><div id="ml-pets-filter"><input id="ml-pets-search" type="text" placeholder="e.g. wild kitty, golden OR diamond, (golden OR emerald) wild"><div style="font-size:9px;color:rgba(168,130,230,.35);margin-top:2px">Words are AND\u2019d together. Use OR for alternatives, brackets to group. * or % as wildcard.</div></div><div id="ml-pets-body"></div>';
          document.body.appendChild(petsPanel);

          const slotsPop = document.createElement('div');
          slotsPop.id = 'ml-slots-pop';
          slotsPop.innerHTML = '<div id="ml-slots-pop-head"><span id="ml-slots-pop-title">Waypoint Slots</span><button id="ml-slots-pop-close">✕</button></div><div id="ml-slots-pop-body"></div>';
          document.body.appendChild(slotsPop);
          function buildSlotsHTML() {
              const body = $('ml-slots-pop-body');
              if (!body) return;
              let html = '';
              for (let i = 0; i < 10; i++) {
                  const s = slots[i];
                  const info = s ? `${s.x.toFixed(1)}, ${s.y.toFixed(1)}, ${s.z.toFixed(1)}` : 'empty';
                  html += `<div class="ml-slot"><span class="ml-slot-num">${i}</span><span class="ml-slot-info${s ? ' filled' : ''}">${info}</span><button class="ml-slot-go${s ? '' : ' empty'}" data-slot="${i}">Go</button><button class="ml-slot-set" data-slot="${i}">Set</button><button class="ml-slot-clr${s ? '' : ' empty'}" data-slot="${i}">✕</button></div>`;
              }
              body.innerHTML = html;
          }
          buildSlotsHTML();
          $('ml-slots-pop-close').addEventListener('click', () => toggleSlots(false));

          slotsPop.addEventListener('click', e => {
              const goBtn = e.target.closest('.ml-slot-go');
              const setBtn = e.target.closest('.ml-slot-set');
              const clrBtn = e.target.closest('.ml-slot-clr');
              if (!goBtn && !setBtn && !clrBtn) return;
              const idx = parseInt((goBtn || setBtn || clrBtn).dataset.slot);
              if (isNaN(idx)) return;
              const p = getPlayer();
              if (!p) return;
              if (clrBtn) {
                  slots[idx] = null;
                  saveWaypoints();
                  flash('ml-slots');
                  buildSlotsHTML();
                  console.log(`[slot ${idx}] cleared`);
              } else if (setBtn) {
                  slots[idx] = capturePos(p);
                  saveWaypoints();
                  flash('ml-slots');
                  buildSlotsHTML();
                  console.log(`[slot ${idx}] saved via popout`);
              } else if (goBtn && slots[idx]) {
                  backPos = capturePos(p);
                  teleport(p, slots[idx]);
                  saveWaypoints();
                  flash('ml-slots');
                  toggleSlots(false);
                  console.log(`[slot ${idx}] teleported via popout`);
              }
          });

          slotsPop.addEventListener('ml-rebuild', () => buildSlotsHTML());

          const snipePanel = document.createElement('div');
          snipePanel.id = 'ml-snipe';
          snipePanel.innerHTML = '<div id="ml-snipe-head"><span id="ml-snipe-title">Pet Sniper</span><button id="ml-snipe-close">✕</button></div><div id="ml-snipe-body"><input id="ml-snipe-search" type="text" placeholder="e.g. halo, golden OR diamond, emerald dragon"><div style="font-size:9.5px;color:rgba(255,180,180,.6);margin-top:3px">Words are AND\u2019d. Use OR for alternatives, brackets to group. * or % as wildcard.</div><label id="ml-snipe-drop-row" style="display:flex;align-items:center;gap:8px;margin-top:8px;cursor:pointer;font-size:10.5px;color:rgba(255,210,210,.85)"><input id="ml-snipe-drop" type="checkbox" style="accent-color:#ed3a3a"><span>Auto-drop in Waves <span style="color:rgba(255,180,180,.55)">(creates endless stream of dropped pets)</span></span></label><label id="ml-snipe-hop-row" style="display:flex;align-items:center;gap:8px;margin-top:6px;cursor:pointer;font-size:10.5px;color:rgba(255,210,210,.85)"><input id="ml-snipe-hop" type="checkbox" style="accent-color:#ed3a3a"><span>Auto-hop in Steal <span style="color:rgba(255,180,180,.55)">(switch server when no matches)</span></span></label><button id="ml-snipe-toggle">Start Sniping</button><div id="ml-snipe-status">Idle</div><div id="ml-snipe-types"></div><div id="ml-snipe-log"></div></div>';
          document.body.appendChild(snipePanel);

          [plist, settings, d, petsPanel, slotsPop, snipePanel].forEach(el => {
              ['mousedown','mouseup','click','pointerdown','pointerup','mousemove','mouseover','mouseenter','mouseleave','wheel','contextmenu','pointermove','pointerover','pointerenter'].forEach(evt => {
                  el.addEventListener(evt, e => e.stopPropagation());
              });
              ['keydown','keyup','keypress'].forEach(evt => {
                  el.addEventListener(evt, e => e.stopPropagation());
              });
          });

          function makeDraggable(panel, handle) {
              let dragging = false, ox = 0, oy = 0;
              handle.style.cursor = 'grab';
              panel.style.resize = 'both';
              panel.style.overflow = 'auto';
              function startDrag(cx, cy, tag) {
                  if (tag === 'BUTTON' || tag === 'INPUT' || tag === 'LABEL') return false;
                  dragging = true;
                  handle.style.cursor = 'grabbing';
                  const rect = panel.getBoundingClientRect();
                  panel.style.left = rect.left + 'px';
                  panel.style.top = rect.top + 'px';
                  panel.style.right = 'auto';
                  panel.style.bottom = 'auto';
                  panel.style.transform = 'none';
                  ox = cx - rect.left;
                  oy = cy - rect.top;
                  return true;
              }
              function moveDrag(cx, cy) {
                  if (!dragging) return;
                  const ph = panel.offsetHeight || 40;
                  const pw = panel.offsetWidth || 40;
                  panel.style.left = Math.max(0, Math.min(cx - ox, window.innerWidth - Math.min(pw, 40))) + 'px';
                  panel.style.top = Math.max(0, Math.min(cy - oy, window.innerHeight - Math.min(ph, 40))) + 'px';
              }
              function endDrag() {
                  if (dragging) { dragging = false; handle.style.cursor = 'grab'; }
              }
              handle.addEventListener('mousedown', e => { if (startDrag(e.clientX, e.clientY, e.target.tagName)) e.preventDefault(); });
              window.addEventListener('mousemove', e => moveDrag(e.clientX, e.clientY), true);
              window.addEventListener('mouseup', endDrag, true);
              handle.addEventListener('touchstart', e => {
                  const t = e.touches[0];
                  if (startDrag(t.clientX, t.clientY, e.target.tagName)) { e.preventDefault(); e.stopPropagation(); }
              }, { passive: false });
              window.addEventListener('touchmove', e => {
                  if (!dragging) return;
                  moveDrag(e.touches[0].clientX, e.touches[0].clientY);
                  e.preventDefault();
              }, { passive: false, capture: true });
              window.addEventListener('touchend', endDrag, true);
              panel._resetPos = () => {
                  panel.style.left = '';
                  panel.style.top = '';
                  panel.style.right = '';
                  panel.style.bottom = '';
                  panel.style.transform = '';
                  panel.style.width = '';
                  panel.style.height = '';
              };
          }
          makeDraggable(plist, $('ml-plist-head'));
          makeDraggable(settings, $('ml-settings-head'));
          makeDraggable(petsPanel, $('ml-pets-head'));
          makeDraggable(slotsPop, $('ml-slots-pop-head'));
          makeDraggable(snipePanel, $('ml-snipe-head'));
          makeDraggable(d.querySelector('#ml-inner'), d.querySelector('#ml-inner h1'));

          function syncSettingsUI() {
              syncSlider('ml-s-cap', 'ml-sv-cap', SPEED_CAP);
              syncSlider('ml-s-accel', 'ml-sv-accel', ACCEL);
              syncSlider('ml-s-base', 'ml-sv-base', SPEED_DEFAULT);

              syncSlider('ml-s-acinterval', 'ml-sv-acinterval', autoCollectInterval, 's');
              const ae = $('ml-s-accel-en');
              if (ae) ae.classList.toggle('on', accelEnabled);

              const featVals = [featFly, featSprint, featWaypoints, featCuddle, featCuddleFollow, featPets, featAutoLock, featAntiKnockback, featNoclip, featFreeMoney, featAutoCollect, featInvincible, featGhostMode, featFreeStars, featAutoAttack];
              FEAT_IDS.forEach((id, i) => { const el = $(id); if (el) el.classList.toggle('on', featVals[i]); });
          }
          syncSettingsUI();

          function getPlayers() {
              const holder = W.pc?.app?.root?.findByName('EnemyHolder');
              if (!holder) return [];
              const player = getPlayer();
              const playerPos = player?.getPosition();
              const results = [];
              for (const enemy of holder.children) {
                  const pos = enemy.getPosition();
                  let username = null;
                  try { username = enemy.script?.enemy?.usernameEntity?.element?.text; } catch (_) {}
                  if (!username) {
                      const ue = enemy.findByName?.('Username');
                      if (ue?.element?.text) username = ue.element.text;
                  }
                  if (!username || username === 'Enemy') username = enemy.name !== 'Enemy' ? enemy.name : null;
                  if (!username) username = 'Player';
                  const dist = playerPos ? Math.floor(playerPos.distance(pos)) : '?';
                  results.push({ name: username, dist, pos: vec3(pos), entity: enemy });
              }
              results.sort((a, b) => (typeof a.dist === 'number' && typeof b.dist === 'number') ? a.dist - b.dist : 0);
              return results;
          }

          function refreshPlayerList() {
              const body = $('ml-plist-body');
              if (!body) return;
              const players = getPlayers();
              if (players.length === 0) {
                  body.innerHTML = '<div id="ml-plist-empty">No other players found</div>';
                  return;
              }
              body.innerHTML = '';
              players.forEach((p, i) => {
                  const row = document.createElement('div');
                  row.className = 'ml-prow';
                  row.innerHTML = `<span class="ml-pnum">${i + 1}</span><div class="ml-pinfo"><div class="ml-pname"></div><div class="ml-pdist">${p.dist}m</div></div><span class="ml-parrow">→</span>`;
                  row.querySelector('.ml-pname').textContent = p.name;
                  row.addEventListener('click', () => {
                      const player = getPlayer();
                      if (!player) return;
                      const freshPos = p.entity?.getPosition();
                      const target = freshPos || p.pos;
                      backPos = capturePos(player);
                      teleport(player, vec3(target));
                      saveWaypoints();
                      if (featCuddleFollow) {
                          cuddleTarget = p.entity;
                          cuddling = true;
                          console.log(`[cuddle] locked → ${p.name}`);
                      }
                      flash('ml-back');
                      const app = W.pc?.app;
                      if (app) app.fire('GameManager:GameResumed');
                  });
                  body.appendChild(row);
              });
          }

          function getPetOwnerId(pet) {
              if (!pet) return null;
              const d = pet.owner;
              if (d === false) return false;
              if (d != null) return d;
              return pet?.data?.owner ?? pet?.ownerId ?? pet?.data?.ownerId ?? null;
          }

          let _ownerMapCache = null;
          let _ownerMapTime = 0;
          function buildOwnerMap() {
              const now = Date.now();
              if (_ownerMapCache && now - _ownerMapTime < 2000) return _ownerMapCache;
              const map = {};
              const myId = W.pc?.sessionId;
              if (myId) map[myId] = 'Yours';
              const nm = W.pc?.app?.root?.findByName('NetworkManager')?.script?.networkManager;
              if (nm?.sessionId && !map[nm.sessionId]) map[nm.sessionId] = 'Yours';
              const holder = W.pc?.app?.root?.findByName('EnemyHolder');
              if (holder) {
                  for (const child of holder.children) {
                      let id = child.id || child.sessionId || child.playerId;
                      if (!id && child.script) {
                          for (const sk of Object.keys(child.script._scriptsIndex || {})) {
                              const inst = child.script[sk];
                              id = inst?.id || inst?.sessionId || inst?.playerId;
                              if (id) break;
                          }
                      }
                      if (!id) continue;
                      let name = null;
                      try { name = child.script?.enemy?.usernameEntity?.element?.text; } catch (_) {}
                      if (!name) { const ue = child.findByName?.('Username'); if (ue?.element?.text) name = ue.element.text; }
                      if (name && name !== 'Enemy') map[id] = name;
                  }
              }
              _ownerMapCache = map;
              _ownerMapTime = now;
              return map;
          }

          function getPetOwnerName(pet, ownerMap) {
              const ownerId = getPetOwnerId(pet);
              if (ownerId === false || ownerId == null) return 'Wild';
              if (ownerMap?.[ownerId]) return ownerMap[ownerId];
              return ownerId.length > 10 ? ownerId.substring(0, 8) + '…' : ownerId;
          }

          let _pmCache = null;
          let _pmCacheTime = 0;
          function getPetsManager() {
              const now = Date.now();
              if (_pmCacheTime && now - _pmCacheTime < 5000) return _pmCache;
              const pmEntity = W.pc?.app?.root?.findByName('PetsManager');
              let pm = pmEntity?.script?.petsManager;
              if (!pm && pmEntity?.script) {
                  for (const k of Object.keys(pmEntity.script)) {
                      const s = pmEntity.script[k];
                      if (s?.activePets || s?.basePets) { pm = s; break; }
                  }
              }
              if (!pm) {
                  const root = W.pc?.app?.root;
                  if (!root) return null;
                  root.find(e => {
                      if (!e.script) return false;
                      for (const k of Object.keys(e.script)) {
                          const s = e.script[k];
                          if (s?.activePets || s?.basePets) { pm = s; return true; }
                      }
                      return false;
                  });
              }
              _pmCache = pm || null;
              _pmCacheTime = now;
              return _pmCache;
          }

          function getAllPets() {
              const pm = getPetsManager();
              if (!pm) return [];
              hookPetSpawn();
              const ownerMap = buildOwnerMap();
              const pets = [];
              const scan = (map, type) => {
                  if (!map) return;
                  map.forEach((pet, token) => {
                      const name = pet.name || String(token).substring(0, 8);
                      const sd = petSpawnData.get(token);
                      let mutation = sd?.mutation ?? '';
                      let rarity = sd?.rarity ?? '';
                      let profit = sd?.profit ?? null;
                      if (mutation === 'Default') mutation = '';
                      if (!mutation) {
                          try {
                              if (pet._mlMut) { mutation = pet._mlMut; }
                              else {
                                  const mutNames = ['Golden', 'Diamond', 'Emerald', 'Rainbow', 'Galaxy'];
                                  for (const mn of mutNames) {
                                      if (pet.findByName?.(`Mutation ${mn} Effect`)) { mutation = mn; break; }
                                  }
                                  if (mutation) pet._mlMut = mutation;
                              }
                          } catch (_) {}
                      }
                      if (!rarity) {
                          try {
                              if (pet._mlRar) { rarity = pet._mlRar; }
                              else {
                                  const RARITIES = ['BASIC', 'RARE', 'EPIC', 'LEGENDARY', 'MYTHICAL', 'EXOTIC', 'SECRET', 'EXCLUSIVE'];
                                  for (const r of RARITIES) {
                                      const node = pet.findByName?.(r);
                                      if (node && node.enabled) { rarity = r; break; }
                                  }
                                  if (rarity) pet._mlRar = rarity;
                              }
                          } catch (_) {}
                      }
                      if (!profit) {
                          try {
                              const statsEl = pet.findByName?.('PetStats');
                              if (statsEl) {
                                  const profitEl = statsEl.findByName?.('Profit') || statsEl.findByName?.('PetProfit');
                                  if (profitEl?.element?.text) {
                                      const m = profitEl.element.text.match(/\$\s*([\d,.]+)\s*([kmbtqsxi]*)/);
                                      if (m) {
                                          let v = parseFloat(m[1].replace(/,/g, ''));
                                          const suf = m[2]?.toLowerCase();
                                          if (suf === 'k') v *= 1e3;
                                          else if (suf === 'm') v *= 1e6;
                                          else if (suf === 'b') v *= 1e9;
                                          else if (suf === 't') v *= 1e12;
                                          else if (suf === 'q') v *= 1e15;
                                          profit = v;
                                      }
                                  }
                              }
                          } catch (_) {}
                      }
                      const pos = pet.getPosition ? pet.getPosition() : pet.position;
                      const price = pet.price ?? 0;
                      pets.push({
                          token,
                          name,
                          price,
                          income: profit ?? 0,
                          owner: getPetOwnerName(pet, ownerMap),
                          ownerId: getPetOwnerId(pet),
                          type,
                          mutation,
                          rarity,
                          x: pos?.x ?? 0,
                          y: pos?.y ?? 0,
                          z: pos?.z ?? 0,
                      });
                  });
              };
              scan(pm.activePets, 'active');
              scan(pm.basePets, 'base');
              return pets;
          }

          let lastRenderedPets = [];
          const grabbedTokens = new Map();

          function renderPetTable() {
              const body = $('ml-pets-body');
              const countEl = $('ml-pets-count');
              if (!body) return;
              let pets = getAllPets();
              if (countEl) countEl.textContent = `(${pets.length})`;
              if (pets.length === 0) {
                  lastRenderedPets = [];
                  body.innerHTML = '<div id="ml-pets-empty">No pets found in this room</div>';
                  return;
              }
              if (petFilter) pets = pets.filter(p => petMatchesFilter(p, petFilter));
              const playerPos = getPlayer()?.getPosition();
              if (playerPos) pets.forEach(p => { p._dist = Math.hypot(p.x - playerPos.x, p.y - playerPos.y, p.z - playerPos.z); });
              pets.sort((a, b) => {
                  const key = petSortCol === 'dist' ? '_dist' : petSortCol;
                  let av = a[key], bv = b[key];
                  if (av == null) av = Infinity;
                  if (bv == null) bv = Infinity;
                  if (typeof av === 'string') av = av.toLowerCase();
                  if (typeof bv === 'string') bv = bv.toLowerCase();
                  if (av < bv) return -petSortDir;
                  if (av > bv) return petSortDir;
                  return 0;
              });
              lastRenderedPets = pets;
              const cols = ['name','mutation','rarity','owner','price','income','dist','go'];
              const labels = { name:'Name', mutation:'Mutation', rarity:'Rarity', owner:'Owner', price:'Worth', income:'Income/s', dist:'Dist', go:'' };
              let html = '<table id="ml-ptable"><thead><tr>';
              cols.forEach(c => {
                  const cls = petSortCol === c ? (petSortDir === 1 ? 'sort-asc' : 'sort-desc') : '';
                  html += c === 'go' ? '<th></th>' : `<th data-col="${c}" class="${cls}">${labels[c]}</th>`;
              });
              html += '</tr></thead><tbody>';
              const now = Date.now();
              pets.forEach(p => {
                  const ownerCls = p.owner === 'Yours' ? 'own-you' : p.owner === 'Wild' ? 'own-wild' : '';
                  const mutCls = p.mutation ? `pet-mut-${p.mutation}` : 'pet-var';
                  const tokenStr = String(p.token);
                  const grabbed = grabbedTokens.has(tokenStr) && grabbedTokens.get(tokenStr) > now;
                  const grabBtn = p.owner !== 'Yours' ? `<button class="pet-grab" data-token="${esc(p.token)}"${grabbed ? ' disabled' : ''} title="Pick up">${grabbed ? '\u2714' : '\u270B'}</button>` : '';
                  html += `<tr><td>${esc(p.name)}</td><td class="${mutCls}">${esc(p.mutation) || '\u2014'}</td><td class="pet-rar">${esc(p.rarity) || '\u2014'}</td><td class="${ownerCls}">${esc(p.owner)}</td><td>${numFmt(p.price)}</td><td>${numFmt(p.income)}/s</td><td>${p._dist != null ? numFmt(Math.round(p._dist)) : '\u2014'}</td><td><button class="pet-tp" data-token="${esc(p.token)}" title="Teleport">\u279C</button>${grabBtn}</td></tr>`;
              });
              html += '</tbody></table>';
              body.innerHTML = html;
          }

          function findPetByToken(token) {
              return lastRenderedPets.find(p => String(p.token) === token) || null;
          }

          $('ml-pets-body').addEventListener('click', e => {
              const th = e.target.closest('th[data-col]');
              if (th) {
                  const col = th.dataset.col;
                  if (petSortCol === col) petSortDir *= -1;
                  else { petSortCol = col; petSortDir = col === 'price' || col === 'income' || col === 'dist' ? -1 : 1; }
                  saveSettings();
                  renderPetTable();
                  return;
              }
              const tpBtn = e.target.closest('.pet-tp');
              if (tpBtn) {
                  const p = findPetByToken(tpBtn.dataset.token);
                  if (!p) return;
                  const player = getPlayer();
                  if (!player) return;
                  player.setPosition(p.x, p.y + 1, p.z);
                  togglePetsPanel(false);
                  return;
              }
              const grabBtn = e.target.closest('.pet-grab');
              if (grabBtn) {
                  const p = findPetByToken(grabBtn.dataset.token);
                  if (!p) return;
                  const app = W.pc?.app;
                  if (!app) return;
                  const player = getPlayer();
                  if (!player) return;
                  const tokenStr = String(p.token);
                  const orig = player.getPosition().clone();
                  player.setPosition(p.x, p.y + 0.5, p.z);
                  grabBtn.disabled = true;
                  grabBtn.textContent = '…';
                  setTimeout(() => {
                      app.fire('ModeOverlay:BuyPet', p.token);
                      console.log(`[ml] grab pet: ${p.name} (${p.token})`);
                      setTimeout(() => {
                          player.setPosition(orig.x, orig.y, orig.z);
                          setTimeout(() => {
                              app.fire('NetworkManager:Send', 'dropPet');
                              console.log(`[ml] drop pet at origin`);
                              grabbedTokens.set(tokenStr, Date.now() + 5000);
                              renderPetTable();
                          }, 150);
                      }, 150);
                  }, 100);
              }
          });

          function esc(s) {
              return String(s).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;');
          }
          function numFmt(n) {
              if (n >= 1e15) return (n / 1e15).toFixed(1) + 'q';
              if (n >= 1e12) return (n / 1e12).toFixed(1) + 't';
              if (n >= 1e9) return (n / 1e9).toFixed(1) + 'b';
              if (n >= 1e6) return (n / 1e6).toFixed(1) + 'm';
              if (n >= 1e3) return (n / 1e3).toFixed(1) + 'k';
              return n % 1 === 0 ? String(n) : n.toFixed(1);
          }
          function petMatchesFilter(p, raw) {
              if (!raw) return true;
              const text = [p.name, p.owner, p.mutation, p.rarity].join(' ').toLowerCase();
              const tokens = raw.match(/\(|\)|AND|OR|[^\s()]+/gi) || [];
              function parseOr(i) {
                  let [result, j] = parseAnd(i);
                  while (j < tokens.length && tokens[j]?.toUpperCase() === 'OR') {
                      const [r2, j2] = parseAnd(j + 1);
                      result = result || r2;
                      j = j2;
                  }
                  return [result, j];
              }
              function parseAnd(i) {
                  let [result, j] = parseAtom(i);
                  while (j < tokens.length) {
                      const up = tokens[j]?.toUpperCase();
                      if (up === 'AND') { j++; }
                      else if (up === 'OR' || tokens[j] === ')') break;
                      const [r2, j2] = parseAtom(j);
                      result = result && r2;
                      j = j2;
                  }
                  return [result, j];
              }
              function parseAtom(i) {
                  if (i >= tokens.length) return [false, i];
                  if (tokens[i] === '(') {
                      const [result, j] = parseOr(i + 1);
                      return [result, j < tokens.length && tokens[j] === ')' ? j + 1 : j];
                  }
                  const t = tokens[i].toLowerCase();
                  if (t.includes('*') || t.includes('%')) {
                      const re = new RegExp(t.replace(/[.+^${}()|[\]\\]/g, '\\$&').replace(/[*%]/g, '.*'));
                      return [re.test(text), i + 1];
                  }
                  return [text.includes(t), i + 1];
              }
              try { return parseOr(0)[0]; } catch (_) { return text.includes(raw.toLowerCase()); }
          }

          let petRefreshTimer = null;
          function startPetRefresh() {
              stopPetRefresh();
              petRefreshTimer = setInterval(() => renderPetTable(), petRefreshInterval * 1000);
          }
          function stopPetRefresh() {
              if (petRefreshTimer) { clearInterval(petRefreshTimer); petRefreshTimer = null; }
          }
          function togglePetsPanel(forceOpen) {
              const open = forceOpen !== undefined ? forceOpen : !petsPanel.classList.contains('open');
              petsPanel.classList.toggle('open', open);
              if (open) {
                  $('ml-pets-search').value = petFilter;
                  $('ml-pets-auto').checked = petAutoRefresh;
                  syncSlider('ml-pets-interval', 'ml-pets-iv', petRefreshInterval);
                  renderPetTable();
                  if (petAutoRefresh) startPetRefresh();
                  panelPause();
              } else {
                  stopPetRefresh();
                  petsPanel._resetPos?.();
                  panelResume();
              }
          }

          $('ml-pets-btn').addEventListener('click', () => { togglePetsPanel(); });
          $('ml-pets-close').addEventListener('click', () => togglePetsPanel(false));
          $('ml-pets-search').addEventListener('input', e => {
              petFilter = e.target.value;
              saveSettings();
              renderPetTable();
          });
          $('ml-pets-auto').addEventListener('change', e => {
              petAutoRefresh = e.target.checked;
              saveSettings();
              if (petAutoRefresh && petsPanel.classList.contains('open')) startPetRefresh();
              else stopPetRefresh();
          });
          $('ml-pets-interval').addEventListener('input', e => {
              petRefreshInterval = parseInt(e.target.value);
              const iv = $('ml-pets-iv');
              if (iv) iv.textContent = petRefreshInterval;
              saveSettings();
              if (petAutoRefresh && petsPanel.classList.contains('open')) startPetRefresh();
          });

          let _snipeTimer = null;
          let _snipeDropInProgress = false;

          function isOwnerBaseLocked(ownerId) {
              if (!ownerId) return false;
              if (_serverLockedSessions.has(ownerId)) return true;
              const bm = _getBasesManager();
              if (bm?.activeBases) {
                  for (const bd of bm.activeBases) {
                      if (bd.data?.sessionId !== ownerId) continue;
                      return !!(bd.data.lockdownActive || bd.data.lockdownTimeLeft > 0);
                  }
              }
              return false;
          }

          const _snipedPets = [];
          const SNIPE_HISTORY_CAP = 200;
          function recordSnipe(token) {
              _snipedPets.push({ token: String(token), time: Date.now() });
              while (_snipedPets.length > SNIPE_HISTORY_CAP) _snipedPets.shift();
          }
          function getSnipeTime(token) {
              const t = String(token);
              for (let i = _snipedPets.length - 1; i >= 0; i--) {
                  if (_snipedPets[i].token === t) return _snipedPets[i].time;
              }
              return 0;
          }

          function snipeLog(msg) {
              const log = $('ml-snipe-log');
              if (!log) return;
              const entry = document.createElement('div');
              entry.className = 'ml-snipe-entry';
              entry.textContent = new Date().toLocaleTimeString() + ' ' + msg;
              log.prepend(entry);
              while (log.children.length > 30) log.lastChild.remove();
          }

          function snipeScan() {
              if (!snipeFilter) {
                  $('ml-snipe-status').textContent = 'No filter set';
                  $('ml-snipe-types').textContent = '';
                  return null;
              }
              const app = W.pc?.app;
              const player = getPlayer();
              if (!app || !player) {
                  $('ml-snipe-status').textContent = 'Waiting for game...';
                  return null;
              }
              const nm = _getNetworkManager();
              const myId = nm?.room?.sessionId || W.pc?.sessionId;
              const bm = _getBasesManager();
              const isStealMode = !!(bm?.activeBases);
              const pets = getAllPets();
              const nameMatches = pets.filter(p => {
                  if (p.ownerId === myId || p.owner === 'Yours') return false;
                  return petMatchesFilter(p, snipeFilter);
              });
              let blocked = 0;
              const matches = nameMatches.filter(p => {
                  if (isStealMode) {
                      if (p.owner === 'Wild' || !p.ownerId) { blocked++; return false; }
                      if (p.type === 'active') { blocked++; return false; }
                      if (isOwnerBaseLocked(p.ownerId)) { blocked++; return false; }
                      return true;
                  } else {
                      if (p.owner !== 'Wild' && p.ownerId) { blocked++; return false; }
                      return true;
                  }
              });
              const blockedStr = blocked ? ' (' + blocked + ' locked)' : '';
              $('ml-snipe-status').textContent = (isStealMode ? '[Steal] ' : '[Waves] ') + matches.length + ' target(s) / ' + nameMatches.length + ' matched' + blockedStr;
              const typeCounts = {};
              for (const m of nameMatches) {
                  const variant = [m.rarity, m.mutation, m.name].filter(Boolean).join(' ');
                  const isLocked = isStealMode && m.ownerId && isOwnerBaseLocked(m.ownerId);
                  if (!typeCounts[variant]) typeCounts[variant] = { total: 0, locked: 0 };
                  typeCounts[variant].total++;
                  if (isLocked) typeCounts[variant].locked++;
              }
              const typesEl = $('ml-snipe-types');
              if (typesEl) {
                  const entries = Object.entries(typeCounts).sort((a, b) => b[1].total - a[1].total);
                  typesEl.innerHTML = '';
                  for (const [k, v] of entries) {
                      const row = document.createElement('div');
                      row.className = 'ml-snipe-type-row';
                      let label = k + (v.total > 1 ? ' \u00d7' + v.total : '');
                      if (v.locked > 0) label += ' \ud83d\udd12' + v.locked;
                      row.textContent = label;
                      typesEl.appendChild(row);
                  }
              }
              return { matches, isStealMode, totalPets: pets.length, blocked };
          }

          let _snipeScanTimer = null;

          function snipeTick() {
              if (!snipeActive) return;
              if (_hopInProgress) return;
              const now = Date.now();
              if (now < _snipeCooldown) {
                  $('ml-snipe-status').textContent = 'Cooldown ' + (((_snipeCooldown - now) / 1000) | 0) + 's...';
                  return;
              }
              const app = W.pc?.app;
              const pt = _getPetTycoon();
              const et = _getEscapeTsunami();
              const holding = !!(pt?.isHoldingPet || et?.isHoldingPet);
              if (_snipeWasHolding && !holding) {
                  _snipeCooldown = Date.now() + 10000;
                  console.log('[ml] pet deposited — 10s snipe cooldown');
              }
              _snipeWasHolding = holding;
              if (holding) {
                  $('ml-snipe-status').textContent = 'Holding a pet \u2014 paused';
                  return;
              }
              const scan = snipeScan();
              if (!scan || scan.matches.length === 0) {
                  if (snipeAutoHop && scan && scan.isStealMode && snipeFilter && scan.totalPets > 0 && !scan.blocked) {
                      if (!_snipeHopNoMatchStart) _snipeHopNoMatchStart = Date.now();
                      const elapsed = ((Date.now() - _snipeHopNoMatchStart) / 1000) | 0;
                      if (elapsed >= 5) {
                          snipeLog('No matches (' + scan.totalPets + ' pets) — hopping...');
                          $('ml-snipe-status').textContent = 'Hopping server...';
                          _snipeHopNoMatchStart = 0;
                          performServerHop();
                          return;
                      }
                      $('ml-snipe-status').textContent = 'No matches (' + scan.totalPets + ' pets loaded)';
                  }
                  return;
              }
              _snipeHopNoMatchStart = 0;
              const { matches, isStealMode } = scan;
              const player = getPlayer();
              if (!player) return;

              const pPos = player.getPosition();
              for (const m of matches) {
                  m._dist = Math.hypot(m.x - pPos.x, m.y - pPos.y, m.z - pPos.z);
                  m._snipeTime = getSnipeTime(m.token);
                  m._value = Math.max(m.price || 0, m.income || 0);
              }
              matches.sort((a, b) => {
                  if (!isStealMode) {
                      if (!a._snipeTime && b._snipeTime) return -1;
                      if (a._snipeTime && !b._snipeTime) return 1;
                      if (a._snipeTime && b._snipeTime) return a._snipeTime - b._snipeTime;
                  }
                  if (isStealMode) {
                      if (b.income !== a.income) return b.income - a.income;
                  }
                  if (b._value !== a._value) return b._value - a._value;
                  return a._dist - b._dist;
              });
              const target = matches[0];

              $('ml-snipe-status').textContent = 'Sniping: ' + target.name + '...';
              snipeLog('Sniping ' + target.name + ' (' + (target.owner || 'wild') + ') dist=' + Math.round(target._dist));

              if (isStealMode) {
                  if (!_lockDataReceived) {
                      $('ml-snipe-status').textContent = 'Waiting for lock data...';
                      return;
                  }
                  if (target.ownerId && isOwnerBaseLocked(target.ownerId)) {
                      snipeLog(target.name + ': base locked at last second, skipping');
                      $('ml-snipe-status').textContent = 'Base locked — skipping...';
                      _snipeCooldown = Date.now() + 500;
                      return;
                  }
                  const nm = _getNetworkManager();
                  const room = nm?.room;
                  const sid = room?.sessionId;
                  if (!room || !sid) return;
                  const rawSend = room.__mlOrigSend || room.send.bind(room);
                  _ghostSuppressed = true;
                  player.setPosition(target.x, target.y + 0.5, target.z);
                  const posMsg = { x: target.x, y: target.y + 0.5, z: target.z, w: 0 };
                  rawSend('p', posMsg);
                  recordSnipe(target.token);
                  snipeLog('TP\u2019d to ' + target.name + ' — stealing...');
                  $('ml-snipe-status').textContent = 'Stealing ' + target.name + '...';
                  _snipeCooldown = Date.now() + 8000;
                  for (let i = 1; i <= 4; i++) setTimeout(() => rawSend('p', posMsg), i * 80);
                  setTimeout(() => {
                      _ghostSuppressed = false;
                      room.send('stealPet', target.token);
                      console.log('[ml] snipe steal sent:', target.name, target.token);
                  }, 400);
              } else {
                  const orig = player.getPosition().clone();
                  const nm = _getNetworkManager();
                  const room = nm?.room;
                  const sid = room?.sessionId;
                  if (!room || !sid) return;
                  const rawSend = room.__mlOrigSend || room.send.bind(room);
                  _snipeCooldown = Date.now() + 5000;
                  if (featGhostMode) {
                      _wavesGhostPos = { x: target.x, y: target.y + 0.5, z: target.z };
                  } else {
                      player.setPosition(target.x, target.y + 0.5, target.z);
                  }
                  (async () => {
                      await new Promise(r => setTimeout(r, 150));
                      app.fire('ModeOverlay:BuyPet', target.token);
                      recordSnipe(target.token);
                      console.log('[ml] snipe grab:', target.name, target.token);
                      const grabbed = await new Promise(resolve => {
                          const timer = setTimeout(() => { unsub(); resolve(false); }, 600);
                          const unsub = room.onMessage('petHeld', msg => {
                              if (msg.holderSessionId !== sid) return;
                              clearTimeout(timer); unsub(); resolve(true);
                          });
                      });
                      if (!grabbed) {
                          _wavesGhostPos = null;
                          if (!featGhostMode) player.setPosition(orig.x, orig.y, orig.z);
                          snipeLog(target.name + ': grab timeout');
                          $('ml-snipe-status').textContent = 'Grab failed. Retrying...';
                          _snipeCooldown = Date.now() + 500;
                          return;
                      }
                      _wavesGhostPos = null;
                      if (!featGhostMode) player.setPosition(orig.x, orig.y, orig.z);
                      if (snipeAutoDrop) {
                          _snipeDropInProgress = true;
                          const realPos = player.getPosition();
                          rawSend('p', { x: realPos.x, y: realPos.y, z: realPos.z, w: 0 });
                          let dropped = false;
                          for (let att = 0; att < 4 && !dropped; att++) {
                              await new Promise(r => setTimeout(r, att === 0 ? 200 : 500));
                              const et = _getEscapeTsunami();
                              if (!et?.isHoldingPet) { dropped = true; break; }
                              const dp = new Promise(resolve => {
                                  const t = setTimeout(() => { u(); resolve(false); }, 800);
                                  const u = room.onMessage('escapeTsunamiPetDropped', msg => {
                                      if (msg.holderSessionId !== sid) return;
                                      clearTimeout(t); u(); resolve(true);
                                  });
                              });
                              rawSend('dropPet');
                              if (att > 0) rawSend('p', { x: realPos.x, y: realPos.y, z: realPos.z, w: 0 });
                              dropped = await dp;
                              if (!dropped) console.log('[ml] drop attempt', att + 1, 'failed');
                          }
                          _snipeDropInProgress = false;
                          if (!dropped) console.log('[ml] drop failed after retries — isHolding:', _getEscapeTsunami()?.isHoldingPet);
                          snipeLog('Sniped ' + target.name + (dropped ? ' ✓ dropped' : ' (drop failed!)'));
                          $('ml-snipe-status').textContent = dropped ? 'Dropped ' + target.name : 'Drop failed!';
                          _snipeCooldown = Date.now() + 300;
                      } else {
                          snipeLog('Sniped ' + target.name + ' ✓ — place on base!');
                          $('ml-snipe-status').textContent = 'Holding ' + target.name + '! Place on base.';
                          _snipeCooldown = Date.now() + 1000;
                      }
                  })();
              }
          }

          let _heldPetCleanup = null;
          function heldPetCleanupTick() {
              if (!snipeActive || !snipeAutoDrop || _snipeDropInProgress) return;
              const et = _getEscapeTsunami();
              if (!et?.isHoldingPet) return;
              const nm = _getNetworkManager();
              const room = nm?.room;
              if (!room) return;
              const rawSend = room.__mlOrigSend || room.send.bind(room);
              const pos = getPlayer()?.getPosition();
              if (pos) rawSend('p', { x: pos.x, y: pos.y, z: pos.z, w: 0 });
              rawSend('dropPet');
              console.log('[ml] held-pet cleanup: forced drop');
          }

          function startSnipe() {
              if (_snipeTimer) return;
              _snipeTimer = setInterval(snipeTick, 200);
              if (!_heldPetCleanup) _heldPetCleanup = setInterval(heldPetCleanupTick, 3000);
              snipeLog('Sniping started: ' + snipeFilter);
          }
          function stopSnipe() {
              if (_snipeTimer) { clearInterval(_snipeTimer); _snipeTimer = null; }
              if (_heldPetCleanup) { clearInterval(_heldPetCleanup); _heldPetCleanup = null; }
          }

          function toggleSnipePanel(forceOpen) {
              const wasOpen = snipePanel.classList.contains('open');
              if (wasOpen && forceOpen === undefined && document.pointerLockElement) {
                  panelPause();
                  return;
              }
              const open = forceOpen !== undefined ? forceOpen : !wasOpen;
              snipePanel.classList.toggle('open', open);
              if (open) {
                  if (document.pointerLockElement) panelPause();
                  $('ml-snipe-search').value = snipeFilter;
                  $('ml-snipe-drop').checked = snipeAutoDrop;
                  $('ml-snipe-hop').checked = snipeAutoHop;
                  $('ml-snipe-toggle').textContent = snipeActive ? 'Stop Sniping' : 'Start Sniping';
                  $('ml-snipe-toggle').classList.toggle('active', snipeActive);
                  snipeScan();
                  if (!_snipeScanTimer && !_snipeTimer) _snipeScanTimer = setInterval(snipeScan, 2000);
              } else if (wasOpen) {
                  if (_snipeScanTimer) { clearInterval(_snipeScanTimer); _snipeScanTimer = null; }
                  snipePanel._resetPos?.();
                  panelResume();
              }
          }

          document.addEventListener('mousedown', e => {
              if (!snipePanel.classList.contains('open')) return;
              if (document.pointerLockElement) return;
              if (snipePanel.contains(e.target)) return;
              const app = W.pc?.app;
              const canvas = app?.graphicsDevice?.canvas;
              if (canvas) origRequestPointerLock.call(canvas, {unadjustedMovement: true});
          });

          $('ml-snipe-btn').addEventListener('click', () => { toggleSnipePanel(); });
          $('ml-snipe-close').addEventListener('click', () => toggleSnipePanel(false));
          $('ml-snipe-search').addEventListener('input', e => {
              snipeFilter = e.target.value;
              saveSettings();
          });
          $('ml-snipe-drop').addEventListener('change', e => {
              snipeAutoDrop = e.target.checked;
              saveSettings();
          });
          $('ml-snipe-hop').addEventListener('change', e => {
              snipeAutoHop = e.target.checked;
              if (snipeAutoHop) sessionStorage.setItem('ml_snipeAutoHop', '1');
              else sessionStorage.removeItem('ml_snipeAutoHop');
              _snipeHopNoMatchStart = 0;
          });
          $('ml-snipe-toggle').addEventListener('click', () => {
              snipeActive = !snipeActive;
              saveSettings();
              $('ml-snipe-toggle').textContent = snipeActive ? 'Stop Sniping' : 'Start Sniping';
              $('ml-snipe-toggle').classList.toggle('active', snipeActive);
              if (snipeActive) startSnipe();
              else { stopSnipe(); $('ml-snipe-status').textContent = 'Idle'; }
          });

          if (snipeActive) {
              $('ml-snipe-toggle').textContent = 'Stop Sniping';
              $('ml-snipe-toggle').classList.add('active');
              startSnipe();
          }

          function toggleCuddlePanel(forceOpen) {
              const wasOpen = plist.classList.contains('open');
              const open = forceOpen !== undefined ? forceOpen : !wasOpen;
              plist.classList.toggle('open', open);
              if (open) {
                  refreshPlayerList();
                  panelPause();
                  if (!wasOpen && autoRefresh) startRefreshTimer();
              } else if (wasOpen) {
                  stopRefreshTimer();
                  plist._resetPos?.();
                  panelResume();
              }
          }

          const origRequestPointerLock = Element.prototype.requestPointerLock;
          Element.prototype.requestPointerLock = function() {
              if (anyPanelOpen()) {
                  if (W.pc?.isGamePaused) {
                      for (const id of ['ml-plist', 'ml-settings', 'ml-pets', 'ml-dialog', 'ml-slots-pop', 'ml-snipe']) {
                          $(id)?.classList.remove('open');
                      }
                      _savedPointerLock = false;
                  } else {
                      return;
                  }
              }
              return origRequestPointerLock.apply(this, arguments);
          };

          const origRequestFullscreen = Element.prototype.requestFullscreen;
          Element.prototype.requestFullscreen = function(opts) {
              return origRequestFullscreen.call(document.body, opts);
          };

          document.addEventListener('pointerlockchange', e => {
              const wasIntentional = _intentionalPointerExit;
              _intentionalPointerExit = false;
              if (wasIntentional && !document.pointerLockElement) {
                  e.stopImmediatePropagation();
                  return;
              }
              if (document.pointerLockElement) {
                  const app = W.pc?.app;
                  if (app) app.fire('GameManager:GameResumed');
              }
          }, true);

          document.addEventListener('visibilitychange', () => {
              if (document.hidden) _intentionalPointerExit = false;
          });

          let refreshCountdown = refreshInterval;
          let refreshTimer = null;

          function startRefreshTimer() {
              stopRefreshTimer();
              refreshCountdown = refreshInterval;
              const timerEl = $('ml-plist-timer');
              if (timerEl) timerEl.textContent = refreshCountdown + 's';
              refreshTimer = setInterval(() => {
                  refreshCountdown--;
                  if (timerEl) timerEl.textContent = refreshCountdown + 's';
                  if (refreshCountdown <= 0) {
                      refreshPlayerList();
                      refreshCountdown = refreshInterval;
                      if (timerEl) timerEl.textContent = refreshCountdown + 's';
                  }
              }, 1000);
          }
          function stopRefreshTimer() {
              if (refreshTimer) { clearInterval(refreshTimer); refreshTimer = null; }
          }

          $('ml-tp').addEventListener('click', () => { toggleCuddlePanel(); });
          $('ml-plist-refresh').addEventListener('click', () => {
              refreshPlayerList();
              if (autoRefresh && plist.classList.contains('open')) startRefreshTimer();
          });
          $('ml-plist-close').addEventListener('click', () => toggleCuddlePanel(false));

          function toggleSettings(forceOpen) {
              const open = forceOpen !== undefined ? forceOpen : !settings.classList.contains('open');
              settings.classList.toggle('open', open);
              if (open) { panelPause(); refreshUsernameUI(); }
              else if (!open) { kbListeningRow = null; settings._resetPos?.(); panelResume(); }
          }
          $('ml-cfg').addEventListener('click', () => { toggleSettings(); });
          $('ml-settings-close').addEventListener('click', () => toggleSettings(false));

          document.querySelectorAll('#ml-tabs .ml-tab').forEach(tab => {
              tab.addEventListener('click', () => {
                  const t = tab.dataset.tab;
                  document.querySelectorAll('#ml-tabs .ml-tab').forEach(x => x.classList.toggle('active', x.dataset.tab === t));
                  document.querySelectorAll('#ml-settings-body .ml-tab-content').forEach(x => x.classList.toggle('active', x.dataset.tab === t));
                  if (t === 'keys') buildKeybindsHTML();
                  else { kbListeningRow = null; $('ml-keybinds-body').querySelectorAll('.ml-krow.listening').forEach(r => r.classList.remove('listening')); }
              });
          });

          const DEFAULTS = { ACCEL: 6, SPEED_CAP: 100, SPEED_DEFAULT: 7, petRefreshInterval: 1, petFilter: 'wild', petSortCol: 'price', petSortDir: -1 };
          $('ml-settings-reset').addEventListener('click', () => {
              ACCEL = DEFAULTS.ACCEL;
              SPEED_CAP = DEFAULTS.SPEED_CAP;
              SPEED_DEFAULT = DEFAULTS.SPEED_DEFAULT;
              petRefreshInterval = DEFAULTS.petRefreshInterval;
              petFilter = DEFAULTS.petFilter;
              petSortCol = DEFAULTS.petSortCol;
              petSortDir = DEFAULTS.petSortDir;
              accelEnabled = true;

              featFly = true; featSprint = true; featWaypoints = true; featCuddle = true; featCuddleFollow = true; featPets = true; featAutoLock = true;
              featAntiKnockback = true; featNoclip = false; featFreeMoney = true; featAutoCollect = true; featInvincible = true; featGhostMode = true;
              featFreeStars = true;
              autoCollectInterval = 30;
              Object.assign(KEYBINDS, DEFAULT_KEYBINDS);
              syncSettingsUI();
              buildKeybindsHTML();
              updateHUDBadgeLabels();
              console.log('[settings] reset');
              saveSettings();
          });

          const sliderMap = [
              { id: 'ml-s-cap',     valId: 'ml-sv-cap',      apply: v => { SPEED_CAP = v; } },
              { id: 'ml-s-accel',   valId: 'ml-sv-accel',    apply: v => { ACCEL = v; } },
              { id: 'ml-s-base',    valId: 'ml-sv-base',     apply: v => { SPEED_DEFAULT = v; } },

              { id: 'ml-s-acinterval', valId: 'ml-sv-acinterval', uom: 's', apply: v => { autoCollectInterval = v; } },
          ];
          sliderMap.forEach(s => {
              const el = $(s.id);
              const valEl = $(s.valId);
              if (!el) return;
              el.addEventListener('input', () => {
                  const v = parseInt(el.value);
                  if (valEl) valEl.textContent = s.uom ? v + s.uom : v;
                  s.apply(v);
                  saveSettings();
              });
          });

          const accelToggle = $('ml-s-accel-en');
          if (accelToggle) {
              accelToggle.addEventListener('click', () => {
                  accelEnabled = !accelEnabled;
                  accelToggle.classList.toggle('on', accelEnabled);
                  saveSettings();
              });
          }

          const featToggles = [
              { id: 'ml-f-fly',       get: () => featFly,       set: v => { featFly = v; if (!v) { const p = getPlayer(); const k = getKcc(p); if (k && flyActive) flyOff(k); } } },
              { id: 'ml-f-sprint',    get: () => featSprint,    set: v => { featSprint = v; if (!v) sprinting = false; } },
              { id: 'ml-f-waypoints', get: () => featWaypoints, set: v => { featWaypoints = v; } },
              { id: 'ml-f-cuddle',       get: () => featCuddle,       set: v => { featCuddle = v; if (!v) toggleCuddlePanel(false); } },
              { id: 'ml-f-cuddle-follow', get: () => featCuddleFollow, set: v => { featCuddleFollow = v; if (!v) { cuddling = false; cuddleTarget = null; } } },
              { id: 'ml-f-pets',      get: () => featPets,      set: v => { featPets = v; if (!v) togglePetsPanel(false); } },
              { id: 'ml-f-autolock',  get: () => featAutoLock,  set: v => { featAutoLock = v; } },
              { id: 'ml-f-antiknockback', get: () => featAntiKnockback, set: v => { featAntiKnockback = v; if (!v) antiKnockbackReady = false; } },
              { id: 'ml-f-noclip', get: () => featNoclip, set: v => { featNoclip = v; } },
              { id: 'ml-f-freemoney', get: () => featFreeMoney, set: v => { featFreeMoney = v; if (v && !freeMoneyDone) autoFarmFreeMoney(); } },
              { id: 'ml-f-autocollect', get: () => featAutoCollect, set: v => { featAutoCollect = v; } },
              { id: 'ml-f-invincible', get: () => featInvincible, set: v => { featInvincible = v; } },
              { id: 'ml-f-ghostmode', get: () => featGhostMode, set: v => { featGhostMode = v; if (!v) { _ghostActive = true; updateGhostEffect(); } } },
              { id: 'ml-f-freestars', get: () => featFreeStars, set: v => { featFreeStars = v; if (v) _freeStarsStart(); else _freeStarsStop(); } },
              { id: 'ml-f-autoattack', get: () => featAutoAttack, set: v => { featAutoAttack = v; } },
          ];
          featToggles.forEach(ft => {
              const el = $(ft.id);
              if (!el) return;
              el.addEventListener('click', () => {
                  const nv = !ft.get();
                  ft.set(nv);
                  el.classList.toggle('on', nv);
                  saveSettings();
                  console.log(`[feat] ${ft.id.replace('ml-f-','')} = ${nv}`);
              });
          });

          function refreshUsernameUI() {
              const statusEl = $('ml-uname-status');
              const btnEl = $('ml-uname-btn');
              const inputEl = $('ml-uname-input');
              if (!statusEl) return;
              const nm = _getNetworkManager();
              const me = nm?.room?.state?.players?.get?.(nm?.room?.sessionId);
              const apiMgr = W.pc?.app?.root?.findByName('APIManager')?.script?.apimanager;
              const canChange = !!apiMgr?.accountData?.canChangeName;
              statusEl.textContent = me?.username ? (canChange ? me.username + ' (1 free change)' : me.username + ' (used)') : '…';
              statusEl.style.color = canChange ? 'rgba(140,230,140,.7)' : 'rgba(255,150,150,.6)';
              if (btnEl) btnEl.disabled = !canChange;
              if (btnEl) btnEl.style.opacity = canChange ? '1' : '.4';
              if (inputEl) inputEl.disabled = !canChange;
          }

          $('ml-uname-btn')?.addEventListener('click', () => {
              const input = $('ml-uname-input');
              const name = input?.value?.trim();
              if (!name) return;
              if (name.length < 3 || name.length > 12) {
                  $('ml-uname-status').textContent = 'Must be 3-12 characters';
                  $('ml-uname-status').style.color = 'rgba(255,150,150,.8)';
                  return;
              }
              const token = localStorage.getItem('access_token_v0.01');
              if (!token) { $('ml-uname-status').textContent = 'Not logged in'; return; }
              const btn = $('ml-uname-btn');
              btn.disabled = true;
              btn.textContent = '…';
              fetch('https://api.meeland.io/v1/users/changeName', {
                  method: 'POST',
                  headers: { 'Accept': 'application/json', 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + token },
                  body: JSON.stringify({ name })
              }).then(r => {
                  if (!r.ok) throw new Error(r.status === 400 || r.status === 403 ? 'Change unavailable' : 'Error ' + r.status);
                  return r.text().then(() => null);
              }).then(() => {
                  console.log('[ml] username changed to: ' + name);
                  const app = W.pc?.app;
                  if (app) app.fire('NetworkManager:Send', 'refreshUsername');
                  input.value = '';
                  $('ml-uname-status').textContent = name + ' ✓';
                  $('ml-uname-status').style.color = 'rgba(140,230,140,.8)';
                  btn.textContent = 'Change';
                  btn.disabled = true;
                  btn.style.opacity = '.4';
              }).catch(e => {
                  console.error('[ml] username change failed:', e.message);
                  $('ml-uname-status').textContent = e.message;
                  $('ml-uname-status').style.color = 'rgba(255,150,150,.8)';
                  btn.textContent = 'Change';
                  btn.disabled = false;
              });
          });

          $('ml-keybinds-body').addEventListener('click', e => {
              const row = e.target.closest('.ml-krow');
              if (!row) return;
              $('ml-keybinds-body').querySelectorAll('.ml-krow.listening').forEach(r => r.classList.remove('listening'));
              if (kbListeningRow === row) { kbListeningRow = null; return; }
              row.classList.add('listening');
              row.querySelector('.ml-krow-key').textContent = '...';
              kbListeningRow = row;
          });

          window.addEventListener('keydown', e => {
              if (!kbListeningRow) return;
              e.preventDefault();
              e.stopPropagation();
              e.stopImmediatePropagation();
              const action = kbListeningRow.dataset.action;
              if (!action || !(action in KEYBINDS)) return;
              if (e.code === 'Escape') {
                  kbListeningRow.classList.remove('listening');
                  kbListeningRow.querySelector('.ml-krow-key').textContent = keyCodeLabel(KEYBINDS[action]);
                  kbListeningRow = null;
                  return;
              }
              KEYBINDS[action] = e.code;
              kbListeningRow.querySelector('.ml-krow-key').textContent = keyCodeLabel(e.code);
              kbListeningRow.classList.remove('listening');
              kbListeningRow = null;
              saveSettings();
              updateHUDBadgeLabels();
              console.log('[keybind] ' + action + ' → ' + e.code);
          }, true);

          function updateHUDBadgeLabels() {
              const map = {
                  'ml-fly':  ['FLY', 'fly'],
                  'ml-spr':  ['SPR', null],
                  'ml-home': ['SETHOME', 'setHome'],
                  'ml-go':   ['HOME', 'home'],
                  'ml-back': ['BACK', 'back'],
                  'ml-tp':   ['CUDDLE', 'cuddle'],
                  'ml-pets-btn': ['PETS', 'pets'],
                  'ml-snipe-btn': ['SNIPE', 'snipe'],
                  'ml-cfg':  ['CFG', 'settings'],
              };
              const nativeKeyMap = { 'ml-fly': 'fly', 'ml-spr': 'spr', 'ml-home': 'home', 'ml-go': 'go', 'ml-back': 'back', 'ml-tp': 'tp', 'ml-pets-btn': 'petsBtn', 'ml-snipe-btn': 'snipeBtn', 'ml-cfg': 'cfg' };
              for (const [id, [label, action]] of Object.entries(map)) {
                  if (_nativeHud) {
                      const k = nativeKeyMap[id];
                      const kl = k && _nativeHud[k]?._native?.keyLbl;
                      if (kl?.element) {
                          kl.element.text = !action ? 'SH' : keyCodeLabel(KEYBINDS[action]);
                      }
                  }
              }
          }
          updateHUDBadgeLabels();

          function toggleHelp(forceOpen) {
              const open = forceOpen !== undefined ? forceOpen : !d.classList.contains('open');
              d.classList.toggle('open', open);
              if (open) { panelPause(); }
              else { d.querySelector('#ml-inner')._resetPos?.(); panelResume(); }
          }
          $('ml-help').addEventListener('click', () => { toggleHelp(); });
          $('ml-close').addEventListener('click', () => toggleHelp(false));
          d.addEventListener('click', e => { if (e.target === d) toggleHelp(false); });
          d.addEventListener('wheel', e => { e.stopPropagation(); e.preventDefault(); d.scrollTop += e.deltaY; }, { passive: false });

          /* ── mobile touch-scroll fix ────────────────────────────── */
          function enableTouchScroll(el) {
              el.addEventListener('touchstart', e => e.stopPropagation(), { passive: true });
              el.addEventListener('touchmove',  e => e.stopPropagation(), { passive: true });
              el.addEventListener('touchend',   e => e.stopPropagation(), { passive: true });
          }
          [d, $('ml-plist-body'), $('ml-settings-body'),
           $('ml-pets-body'), $('ml-slots-pop-body'),
           $('ml-snipe-types'), $('ml-snipe-log')].forEach(el => { if (el) enableTouchScroll(el); });

          document.addEventListener('click', e => {
              if (plist.classList.contains('open') && !plist.contains(e.target) && e.target.id !== 'ml-tp') {
                  toggleCuddlePanel(false);
              }
              if (settings.classList.contains('open') && !settings.contains(e.target) && e.target.id !== 'ml-cfg') {
                  toggleSettings(false);
              }
              if (petsPanel.classList.contains('open') && !petsPanel.contains(e.target) && e.target.id !== 'ml-pets-btn') {
                  togglePetsPanel(false);
              }
              if (slotsPop.classList.contains('open') && !slotsPop.contains(e.target) && e.target.id !== 'ml-slots') {
                  toggleSlots(false);
              }
          });
      }

      let _nativeHud = null;
      function createNativeHUD() {
          const app = W.pc?.app;
          const pc = W.pc;
          if (!app || !pc) return null;
          const roundedTex = app.assets.get(236692532);
          const fontAsset = app.assets.get(236690804);
          const gpm = app.root.findByName('GamePlayMenu');
          if (!gpm || !roundedTex || !fontAsset) return null;

          const old = app.root.findByName('ML-HUD');
          if (old) old.destroy();

          const btnSize = 50, spacing = 56, rowGap = 68;
          const COL_NORM = [0.071, 0.071, 0.071];
          const COL_ON = [0, 0.5, 1];
          const COL_FRESH = [0, 0.66, 1];
          const COL_DISABLED = [0.071, 0.071, 0.071];

          const topDefs = [
              ['fly',  'FLY', 'SPC', 'ml-fly'],
              ['spr',  'SPR',   'SH', 'ml-spr'],
              ['home', 'SET',   'Q',   'ml-home'],
              ['go',   'GO',    '~',   'ml-go'],
              ['back', 'BCK',   'Z',   'ml-back'],
              ['slots','0/10',  'I',   'ml-slots'],
              ['lock', 'LCK',   '',    'ml-lock'],
          ];
          const botDefs = [
              ['tp',      'CUD', 'J', 'ml-tp'],
              ['petsBtn', 'PET', 'K', 'ml-pets-btn'],
              ['snipeBtn','SNP', 'U', 'ml-snipe-btn'],
              ['cfg',     'CFG', 'M', 'ml-cfg'],
              ['help',    '?',   '/',  'ml-help'],
          ];

          const mlGroup = new pc.Entity('ML-HUD');
          mlGroup.addComponent('element', { type: 'group', anchor: [0,1,0,1], pivot: [0,1], width: 440, height: 200 });
          mlGroup.setLocalPosition(10, -70, 0);
          mlGroup.enabled = false;

          function makeBtn(id, label, keyText, idx, yBase) {
              const btn = new pc.Entity('ML-' + id);
              btn.addComponent('element', { type: 'group', anchor: [0,1,0,1], pivot: [0,1], width: btnSize + 6, height: btnSize + 30 });
              btn.setLocalPosition(idx * spacing, -yBase, 0);

              const bg = new pc.Entity('BG');
              bg.addComponent('element', {
                  type: 'image', anchor: [.5,.5,.5,.5], pivot: [.5,.5],
                  width: btnSize, height: btnSize,
                  color: new pc.Color(COL_NORM[0], COL_NORM[1], COL_NORM[2]),
                  opacity: 1, textureAsset: roundedTex.id, useInput: true
              });
              btn.addChild(bg);

              const fs = label.length > 3 ? 15 : (label.length > 2 ? 20 : label.length > 1 ? 24 : 30);
              const lbl = new pc.Entity('Label');
              lbl.addComponent('element', {
                  type: 'text', anchor: [.5,.5,.5,.5], pivot: [.5,.5],
                  text: label, fontSize: fs, fontAsset: fontAsset.id,
                  color: new pc.Color(1, 1, 1),
                  outlineThickness: .3, outlineColor: new pc.Color(0, 0, 0),
                  autoWidth: true, autoHeight: true
              });
              bg.addChild(lbl);

              if (keyText) {
                  const isLong = keyText.length > 2, isMed = keyText.length === 2;
                  if (isLong || isMed) {
                      const hg = isLong ? 6 : 3, rw = hg * 2 + 0.5;
                      const dk = new pc.Entity('DesktopKey');
                      dk.addComponent('element', { type:'group', anchor:[.5,.5,.5,.5], pivot:[.5,.5], width:25, height:25 });
                      dk.setLocalPosition(17.5, -17.5, 0);
                      bg.addChild(dk);
                      [[-hg,25,25],[hg,25,25],[0,rw,25]].forEach(([x,w,h],j)=>{
                          const e = new pc.Entity('Image');
                          const c = { type:'image', anchor:[.5,.5,.5,.5], pivot:[.5,.5], width:w, height:h, color:new pc.Color(.388,.388,.388), opacity:1 };
                          if (j < 2) c.textureAsset = roundedTex.id;
                          e.addComponent('element', c); if (x) e.setLocalPosition(x,0,0); dk.addChild(e);
                      });
                      [[-hg,20,20],[hg,20,20],[0,rw,20]].forEach(([x,w,h],j)=>{
                          const e = new pc.Entity('Group');
                          const c = { type:'image', anchor:[.5,.5,.5,.5], pivot:[.5,.5], width:w, height:h, color:new pc.Color(1,1,1), opacity:1 };
                          if (j < 2) c.textureAsset = roundedTex.id;
                          e.addComponent('element', c); if (x) e.setLocalPosition(x,0,0); dk.addChild(e);
                      });
                      const kl = new pc.Entity('Text');
                      kl.addComponent('element', { type:'text', anchor:[.5,.5,.5,.5], pivot:[.5,.5],
                          text:keyText, fontSize:15, fontAsset:fontAsset.id, color:new pc.Color(0,0,0), autoWidth:true, autoHeight:true });
                      kl.setLocalPosition(0, -0.3, 0); dk.addChild(kl);
                  } else {
                      const dk = new pc.Entity('DesktopKey');
                      dk.addComponent('element', { type:'image', anchor:[.5,.5,.5,.5], pivot:[.5,.5],
                          width:25, height:25, color:new pc.Color(.388,.388,.388), opacity:1, textureAsset:roundedTex.id });
                      dk.setLocalPosition(17.5, -17.5, 0);
                      bg.addChild(dk);
                      const inner = new pc.Entity('Group');
                      inner.addComponent('element', { type:'image', anchor:[.5,.5,.5,.5], pivot:[.5,.5],
                          width:20, height:20, color:new pc.Color(1,1,1), opacity:1, textureAsset:roundedTex.id });
                      dk.addChild(inner);
                      const kl = new pc.Entity('Text');
                      kl.addComponent('element', { type:'text', anchor:[.5,.5,.5,.5], pivot:[.5,.5],
                          text:keyText, fontSize:15, fontAsset:fontAsset.id, color:new pc.Color(0,0,0), autoWidth:true, autoHeight:true });
                      kl.setLocalPosition(0.6, 0, 0); dk.addChild(kl);
                  }
              }

              mlGroup.addChild(btn);
              return { bg, lbl, btn, keyLbl: keyText ? btn.findByName('Text') : null };
          }

          function makeProxy(parts, htmlId) {
              const st = {};
              const setCol = (r, g, b) => { if (parts.bg.element) { parts.bg.element.color = new pc.Color(r, g, b); } };
              const update = () => {
                  if (st.disabled) {
                      setCol(COL_DISABLED[0], COL_DISABLED[1], COL_DISABLED[2]);
                      parts.bg.element.opacity = .45;
                      parts.lbl.element.color = new pc.Color(1, .24, .24);
                  } else if (st.fresh) {
                      setCol(COL_FRESH[0], COL_FRESH[1], COL_FRESH[2]);
                      parts.bg.element.opacity = 1;
                      parts.lbl.element.color = new pc.Color(1, 1, 1);
                  } else if (st.on) {
                      setCol(COL_ON[0], COL_ON[1], COL_ON[2]);
                      parts.bg.element.opacity = 1;
                      parts.lbl.element.color = new pc.Color(1, 1, 1);
                  } else {
                      setCol(COL_NORM[0], COL_NORM[1], COL_NORM[2]);
                      parts.bg.element.opacity = 1;
                      parts.lbl.element.color = new pc.Color(1, 1, 1);
                  }
              };
              return {
                  _native: parts,
                  classList: {
                      toggle(cls, force) { st[cls] = force; update(); },
                      add(cls) { st[cls] = true; update(); },
                      remove(cls) { delete st[cls]; update(); },
                      contains(cls) { return !!st[cls]; }
                  },
                  set textContent(t) {
                      const short = t.replace(/^SLOTS\s*/, '').replace(/^\u{1F512}\s*/u, '').replace(/^\u{1F513}\s*/u, '');
                      parts.lbl.element.text = short.length > 5 ? short.substring(0, 5) : short;
                  },
                  get textContent() { return parts.lbl.element.text; },
                  addEventListener() {},
                  click() { const el = document.getElementById(htmlId); if (el) el.click(); }
              };
          }

          const result = {};
          topDefs.forEach(([key, label, keyText, htmlId], i) => {
              result[key] = makeProxy(makeBtn(key, label, keyText, i, 0), htmlId);
          });
          botDefs.forEach(([key, label, keyText, htmlId], i) => {
              result[key] = makeProxy(makeBtn(key, label, keyText, i, rowGap), htmlId);
          });

          const prevOverlay = $('ml-click-overlay');
          if (prevOverlay) prevOverlay.remove();
          const clickOverlay = document.createElement('div');
          clickOverlay.id = 'ml-click-overlay';
          clickOverlay.style.cssText = 'position:fixed;inset:0;z-index:10;pointer-events:none';
          document.body.appendChild(clickOverlay);
          const clickDivs = {};
          for (const key of Object.keys(result)) {
              const div = document.createElement('div');
              div.style.cssText = 'position:absolute;pointer-events:auto;cursor:pointer';
              div.dataset.key = key;
              clickOverlay.appendChild(div);
              clickDivs[key] = div;
          }
          ['mousedown','mouseup','pointerdown','pointerup'].forEach(evt => {
              clickOverlay.addEventListener(evt, e => {
                  if (e.target.dataset?.key) e.stopPropagation();
              });
          });
          clickOverlay.addEventListener('touchstart', e => {
              if (e.target.dataset?.key) { e.stopPropagation(); e.preventDefault(); }
          });
          clickOverlay.addEventListener('touchend', e => {
              const key = e.target.dataset?.key;
              if (key && result[key]) {
                  e.stopPropagation(); e.preventDefault();
                  console.log('[ml] btn-touch ' + key);
                  result[key].click();
              }
          });
          clickOverlay.addEventListener('click', e => {
              const key = e.target.dataset?.key;
              if (!key || !result[key]) return;
              console.log('[ml] btn-click ' + key);
              e.stopPropagation();
              e.preventDefault();
              result[key].click();
          });

          const btnLayout = {};
          topDefs.forEach(([key], i) => { btnLayout[key] = { idx: i, yBase: 0 }; });
          botDefs.forEach(([key], i) => { btnLayout[key] = { idx: i, yBase: rowGap }; });

          let _syncDiag = false;
          let _syncLast = 0;
          function syncOverlay() {
              requestAnimationFrame(syncOverlay);
              const now = performance.now();
              if (now - _syncLast < 500) return;
              _syncLast = now;
              const canvas = app.graphicsDevice.canvas;
              const rect = canvas.getBoundingClientRect();
              const cw = rect.width, ch = rect.height;
              const scr = mlGroup.parent?.screen;
              const refW = scr?.referenceResolution?.x || 1280;
              const refH = scr?.referenceResolution?.y || 720;
              const sx = cw / refW, sy = ch / refH;
              const blend = scr?.scaleBlend ?? 0.5;
              const scale = Math.pow(sx, 1 - blend) * Math.pow(sy, blend);
              const groupY = Math.abs(mlGroup.localPosition.y);
              const groupX = mlGroup.localPosition.x;
              const visible = mlGroup.enabled;
              for (const key of Object.keys(result)) {
                  const lay = btnLayout[key];
                  if (!lay) continue;
                  const bgLeft = groupX + lay.idx * spacing + 3;
                  const bgTop  = groupY + lay.yBase + 15;
                  const div = clickDivs[key];
                  div.style.left   = (rect.left + bgLeft * scale) + 'px';
                  div.style.top    = (rect.top  + bgTop  * scale) + 'px';
                  div.style.width  = (btnSize * scale) + 'px';
                  div.style.height = (btnSize * scale) + 'px';
                  div.style.display = visible ? 'block' : 'none';
              }
              if (!_syncDiag && visible) {
                  _syncDiag = true;
                  console.log('[ml] overlay-sync scale=' + scale.toFixed(3) + ' groupXY=' + groupX + ',' + groupY + ' canvas=' + cw.toFixed(0) + 'x' + ch.toFixed(0) + ' buttons=' + Object.keys(btnLayout).join(','));
              }
          }

          const overlay = gpm.parent;
          overlay.addChild(mlGroup);

          syncOverlay();

          const chatEntity = gpm.findByName('Chat');
          if (chatEntity) {
              const chatY = -(Math.abs(mlGroup.localPosition.y) + rowGap + btnSize + 12);
              chatEntity.setLocalPosition(chatEntity.localPosition.x, chatY, 0);
              console.log('[ml] chat repositioned to y=' + chatY);
          }

          function setPauseLayout() {
              mlGroup.setLocalPosition(10, -115, 0);
          }

          function setGameplayLayout() {
              mlGroup.setLocalPosition(10, -70, 0);
          }

          const pauseEntity = overlay.findByName('Pause');
          if (pauseEntity) {
              const origDesc = Object.getOwnPropertyDescriptor(pc.GraphNode.prototype, 'enabled');
              if (origDesc?.set) {
                  Object.defineProperty(pauseEntity, 'enabled', {
                      get() { return origDesc.get.call(this); },
                      set(v) {
                          origDesc.set.call(this, v);
                          if (v) setPauseLayout(); else setGameplayLayout();
                      },
                      configurable: true
                  });
              }
          }

          const loadingEntity = overlay.findByName('LoadingScreen');
          if (loadingEntity) {
              const origDesc = Object.getOwnPropertyDescriptor(pc.GraphNode.prototype, 'enabled');
              if (origDesc?.set) {
                  Object.defineProperty(loadingEntity, 'enabled', {
                      get() { return origDesc.get.call(this); },
                      set(v) {
                          origDesc.set.call(this, v);
                          mlGroup.enabled = !v;
                          const mf = $('ml-mobile-fly');
                          if (mf) mf.style.display = v ? 'none' : 'flex';
                      },
                      configurable: true
                  });
                  mlGroup.enabled = !loadingEntity.enabled;
                  const mfInit = $('ml-mobile-fly');
                  if (mfInit) mfInit.style.display = loadingEntity.enabled ? 'none' : 'flex';
              } else {
                  mlGroup.enabled = true;
                  const mfOrig = $('ml-mobile-fly');
                  if (mfOrig) mfOrig.style.display = 'flex';
              }
          } else {
              mlGroup.enabled = true;
              const mfFallback = $('ml-mobile-fly');
              if (mfFallback) mfFallback.style.display = 'flex';
          }

          console.log('[ml] native PlayCanvas HUD created');
          return result;
      }

      function getApiManager() {
          const root = W.pc?.app?.root;
          const scripts = root?.findComponents('script') || [];
          for (const s of scripts) {
              for (const name of Object.keys(s)) {
                  const inst = s[name];
                  if (inst?.freeCurrency && inst?.balance !== undefined) return inst;
              }
          }
          return null;
      }

      let freeMoneyDone = false;
      async function autoFarmFreeMoney() {
          if (freeMoneyDone || !featFreeMoney) return;
          const api = getApiManager();
          if (!api) return;
          const nm = W.pc?.app?.root?.findByName('NetworkManager')?.script?.networkManager;
          if (!nm?.room?.sessionId) return;
          freeMoneyDone = true;
          const remaining = (api.freeCurrencyAdLimit || 10) - (api.freeCurrencyAdCounter || 0);
          if (remaining <= 0) { console.log('[ml] free money: daily limit already reached'); return; }
          let total = 0, calls = 0;
          for (let i = 0; i < remaining; i++) {
              const before = api.balance;
              api.freeCurrency();
              await new Promise(r => setTimeout(r, 1500));
              const gained = api.balance - before;
              if (gained <= 0) break;
              total += gained;
              calls++;
          }
          console.log('[ml] free money: +' + total + ' coins (' + calls + ' calls, balance: ' + api.balance + ')');
      }

      function _freeStarsStart() {
          _freeStarsStop();
          if (!featFreeStars) return;
          _freeStarsTick();
      }

      function _freeStarsStop() {
          clearTimeout(_freeStarsTimer);
          _freeStarsTimer = null;
      }

      function _freeStarsTick() {
          _freeStarsStop();
          if (!featFreeStars) return;
          const nm = _getNetworkManager();
          if (!nm?.room || nm.room.name !== 'escapeTsunami') {
              _freeStarsTimer = setTimeout(_freeStarsTick, 5000);
              return;
          }
          const app = W.pc?.app;
          if (!app) { _freeStarsTimer = setTimeout(_freeStarsTick, 5000); return; }
          app.fire('NetworkManager:Send', 'getFreeStars');
          _freeStarsTimer = setTimeout(_freeStarsTick, 60000);
      }

      let lastAutoCollect = 0;
      let _mlBatchCollecting = false;
      let _mlBatchTotal = 0;
      let _mlBatchCount = 0;
      let _mlBatchTimer = null;
      let _mlCachedBalance = null;

      function findPetOverlay() {
          const app = W.pc?.app;
          if (!app) return null;
          let overlay = null;
          app.root.find(e => {
              if (!e.script) return false;
              for (const k of Object.keys(e.script)) {
                  const s = e.script[k];
                  if (s?.coinContainer && s?.onCoinAnimation) { overlay = s; return true; }
              }
              return false;
          });
          return overlay;
      }

      function flushBatch() {
          if (!_mlBatchCollecting) return;
          _mlBatchCollecting = false;
          clearTimeout(_mlBatchTimer);
          if (_mlBatchTotal !== 0) {
              console.log('[ml] auto-collect: batched ' + _mlBatchCount + ' notifications → +$' + _mlBatchTotal);
              const overlay = findPetOverlay();
              const app = W.pc?.app;
              if (overlay) {
                  for (let i = 0; i < 3; i++) {
                      setTimeout(() => overlay.onCoinAnimation(5), i * 200);
                  }
              }
              if (_mlCachedBalance !== null && app?._mlOrigFire) {
                  const bal = _mlCachedBalance;
                  _mlCachedBalance = null;
                  setTimeout(() => {
                      app._mlOrigFire.call(app, 'ModeOverlay:Balance', bal);
                  }, 1500);
              }
          } else if (_mlCachedBalance !== null) {
              const app = W.pc?.app;
              if (app?._mlOrigFire) {
                  app._mlOrigFire.call(app, 'ModeOverlay:Balance', _mlCachedBalance);
              }
              _mlCachedBalance = null;
          }
          _mlBatchTotal = 0;
          _mlBatchCount = 0;
      }

      function autoCollectPetEarnings() {
          if (!featAutoCollect) return;
          const now = Date.now();
          if (now - lastAutoCollect < autoCollectInterval * 1000) return;
          lastAutoCollect = now;

          const app = W.pc?.app;
          if (!app) return;
          const root = app.root;

          if (!app._mlNotifBatched) {
              app._mlNotifBatched = true;
              app._mlOrigFire = app.fire;
          }

          let pm = null;
          const pmEntity = root.findByName('PetsManager');
          if (pmEntity?.script) {
              for (const k of Object.keys(pmEntity.script)) {
                  const s = pmEntity.script[k];
                  if (s?.basePets) { pm = s; break; }
              }
          }
          if (!pm) {
              root.find(e => {
                  if (!e.script) return false;
                  for (const k of Object.keys(e.script)) {
                      const s = e.script[k];
                      if (s?.basePets) { pm = s; return true; }
                  }
                  return false;
              });
          }
          if (!pm?.basePets || pm.basePets.size === 0) return;

          const nm = root.findByName('NetworkManager')?.script?.networkManager;
          if (!nm?.room?.sessionId) return;
          const myId = nm.room.sessionId;

          _mlBatchCollecting = true;
          _mlBatchTotal = 0;
          _mlBatchCount = 0;
          clearTimeout(_mlBatchTimer);

          let claimed = 0;
          pm.basePets.forEach((pet, token) => {
              if (String(pet.owner) === String(myId)) {
                  app.fire('NetworkManager:Send', 'claimPetBalance', token);
                  claimed++;
              }
          });

          if (claimed > 0) {
              console.log('[ml] auto-collect: claimed ' + claimed + ' pets');
              _mlBatchTimer = setTimeout(flushBatch, 5000);
          } else {
              _mlBatchCollecting = false;
          }
      }

      const petSpawnData = new Map();
      const PET_SPAWN_CAP = 2000;
      function hookPetSpawn() {
          const app = W.pc?.app;
          if (!app) return;
          if (app._mlPetSpawnHooked) return;
          app._mlPetSpawnHooked = true;
          const onPetsSpawn = data => {
              const items = Array.isArray(data) ? data : (data && typeof data === 'object' ? Object.values(data) : []);
              if (!items.length) return;
              if (petSpawnData.size > PET_SPAWN_CAP) {
                  const excess = petSpawnData.size - PET_SPAWN_CAP + items.length;
                  const it = petSpawnData.keys();
                  for (let i = 0; i < excess; i++) petSpawnData.delete(it.next().value);
              }
              for (const p of items) {
                  if (!p.token) continue;
                  petSpawnData.set(p.token, {
                      profit: p.profit ?? null,
                      rarity: p.rarity ?? null,
                      mutation: p.mutation ?? null,
                      isEgg: !!p.isEgg,
                  });
              }
          };
          app.on('PetsManager:PetsSpawn', onPetsSpawn);
          app.on('BasesManager:PetsSpawn', onPetsSpawn);
          console.log('[ml] pet spawn hook installed');
      }

      function getMyBase() {
          const basesScript = _getBasesManager();
          if (!basesScript?.activeBases) return null;
          const nm = _getNetworkManager();
          const sessionId = nm?.room?.sessionId || W.pc?.sessionId;
          if (!sessionId) return null;
          for (const bd of basesScript.activeBases) {
              if (bd?.data?.sessionId === sessionId) return basesScript.baseEntities?.[bd.data.id] ?? null;
          }
          return null;
      }

      function getLockBtn() {
          const base = getMyBase();
          if (!base) return null;
          const btn = base.findByName('LockdownButton');
          return btn?.script?.lockdownButton ?? null;
      }

      let _lastLockTriggerLog = 0;
      function triggerLock() {
          const btn = getLockBtn();
          if (!btn || typeof btn.onTriggerEnter !== 'function') return false;
          const player = getPlayer();
          if (!player) return false;
          btn.onTriggerEnter(player);
          const now = Date.now();
          if (_roomJoinTime) {
              console.log('[ml] auto-lock: first lock ' + (now - _roomJoinTime) + 'ms after join');
              _roomJoinTime = 0;
          } else if (now - _lastLockTriggerLog > 10000) {
              _lastLockTriggerLog = now;
              console.log('[ml] auto-lock: triggered');
          }
          return true;
      }

      let lastLockCheck = 0;

      function hookAntiDisconnect() {
          const nm = _getNetworkManager();
          const room = nm?.room;
          if (!room) return;
          if (room.__mlSendHooked) return;
          room.__mlSendHooked = true;
          const origSend = room.send.bind(room);
          room.__mlOrigSend = origSend;
          const HEIGHT_OFFSET = 200;
          let _currentOffset = 0;
          let _wasHolding = false;
          let _earlyGhost = false;
          let _earlyGhostTimer = null;
          room.send = function (type, message) {
              if (type === 'stealPet' && featGhostMode) {
                  _earlyGhost = true;
                  _currentOffset = HEIGHT_OFFSET;
                  _wasHolding = true;
                  if (_earlyGhostTimer) clearTimeout(_earlyGhostTimer);
                  _earlyGhostTimer = setTimeout(() => { _earlyGhost = false; _earlyGhostTimer = null; }, 5000);
                  console.log('[ml] ghost: early activate on stealPet send');
              }
              if (type === 'p' && message && featGhostMode && !_ghostSuppressed) {
                  if (_wavesGhostPos) {
                      return origSend(type, { x: _wavesGhostPos.x, y: _wavesGhostPos.y, z: _wavesGhostPos.z, w: message.w });
                  }
                  const pt = _getPetTycoon();
                  const holding = !!pt?.isHoldingPet;
                  if (holding) {
                      _earlyGhost = false;
                      _currentOffset = HEIGHT_OFFSET;
                      _wasHolding = true;
                  } else if (_earlyGhost) {
                      // keep offset active until server confirms or 5s timeout
                  } else if (_wasHolding && _currentOffset > 0) {
                      _currentOffset = Math.max(0, _currentOffset - 10);
                      if (_currentOffset === 0) _wasHolding = false;
                  }
                  if (_currentOffset > 0) {
                      return origSend(type, { x: message.x, y: message.y + _currentOffset, z: message.z, w: message.w });
                  }
              }
              if (type === 'dropPet') {
                  _earlyGhost = false;
              }
              return origSend(type, message);
          };
          console.log('[ml] ghost position hook installed');
          _playerCache = null; _playerCacheTime = 0;
          _ptCache = null; _ptCacheTime = 0; _etCache = null; _etCacheTime = 0;
          _nmCache = null; _nmCacheTime = 0; _bmCache = null; _bmCacheTime = 0;
          _serverLockedSessions.clear();
          _lockDataReceived = false;
          _pendingBaseCapture = true;
          _roomJoinTime = Date.now();
          _myPetTokens = new Set(); _myPetTokensTime = 0;

          if (featAntiKnockback) {
              room.onMessage('gotAttacked', () => {});
          }

          let _stealReturnTimer = null;

          function _stealReturnHome() {
              if (!featGhostMode) return;
              const rawSend = room.__mlOrigSend || room.send.bind(room);
              if (_stealReturnTimer) {
                  clearTimeout(_stealReturnTimer);
              }
              console.log('[ml] steal return: 10s then TP base');
              _stealReturnTimer = setTimeout(() => {
                  _stealReturnTimer = null;
                  const player = getPlayer();
                  if (!player) return;
                  const pt = _getPetTycoon();
                  const et = _getEscapeTsunami();
                  if (!pt?.isHoldingPet && !et?.isHoldingPet) {
                      console.log('[ml] steal return: not holding pet, skip TP');
                      return;
                  }
                  _wavesGhostPos = null;
                  backPos = capturePos(player);
                  const app = W.pc?.app;
                  if (app) app.fire('Player:TeleportToPersonalSpawn');
                  setTimeout(() => {
                      const pos = player.getPosition();
                      rawSend('p', { x: pos.x, y: pos.y, z: pos.z, w: 0 });
                  }, 100);
                  console.log('[ml] steal return: teleported to base');
              }, 10000);
          }

          room.onMessage('petClaimed', msg => {
              if (msg?.stealerSessionId && msg.stealerSessionId !== room.sessionId) return;
              if (msg?.sessionId && msg.sessionId !== room.sessionId) return;
              _snipeCooldown = Date.now() + 10000;
              console.log('[ml] pet claimed — 10s snipe cooldown');
              _stealReturnHome();
          });

          room.onMessage('petStolen', msg => {
              if (msg?.stealerSessionId && msg.stealerSessionId !== room.sessionId) return;
              if (msg?.sessionId && msg.sessionId !== room.sessionId) return;
              console.log('[ml] pet stolen');
              _stealReturnHome();
          });

          room.onMessage('modeNotification', msg => {
              if (msg === 'PetClaimedSuccessfully' || (typeof msg === 'object' && msg.text === 'PetClaimedSuccessfully')) {
                  console.log('[ml] mode notification: claimed');
                  _stealReturnHome();
              }
          });

          room.onMessage('baseLockdownTimers', msg => {
              _serverLockedSessions.clear();
              if (Array.isArray(msg)) {
                  for (const entry of msg) {
                      if (entry.sessionId) _serverLockedSessions.add(entry.sessionId);
                  }
              }
              _lockDataReceived = true;
          });

          if (typeof room.onLeave === 'function') {
              room.onLeave((code) => {
                  console.log('[ml] room closed code=' + code);
              });
          }
      }

      let _ghostActive = false;
      let _wavesGhostPos = null;
      let _ghostSuppressed = false;
      const GHOST_OPACITY = 0.25;

      function _getCharMeshInstances() {
          const player = getPlayer();
          const ch = player?.findByName('CharacterHolder');
          if (!ch) return [];
          const result = [];
          ch.forEach(child => {
              const mis = child.render?.meshInstances || child.model?.meshInstances || [];
              for (const mi of mis) if (mi.material) result.push(mi);
          });
          return result;
      }

      function updateGhostEffect() {
          const pt = _getPetTycoon();
          const shouldGhost = featGhostMode && (!!pt?.isHoldingPet || !!_wavesGhostPos);
          if (shouldGhost === _ghostActive) return;

          const mis = _getCharMeshInstances();
          if (!mis.length) return;

          for (const mi of mis) {
              if (shouldGhost) {
                  if (!mi.__mlGhostCloned) {
                      mi.material = mi.material.clone();
                      mi.__mlGhostCloned = true;
                  }
                  mi.material.blendType = W.pc.BLEND_NORMAL;
                  mi.material.opacity = GHOST_OPACITY;
                  mi.material.depthWrite = false;
              } else {
                  mi.material.blendType = W.pc.BLEND_NONE;
                  mi.material.opacity = 1.0;
                  mi.material.depthWrite = true;
              }
              mi.material.update();
          }
          _ghostActive = shouldGhost;
          console.log('[ml] ghost:', shouldGhost ? 'faded (holding pet)' : 'restored');
      }

      let antiDeathReady = false;

      function hookAntiDeath() {
          if (antiDeathReady) return;
          const app = W.pc?.app;
          if (!app) return;

          app.root.find(entity => {
              if (!entity.script?._scripts) return false;
              for (const inst of entity.script._scripts) {
                  if (inst && typeof inst.deathYThreshold === 'number' && inst.deathYThreshold > -9999) {
                      inst.deathYThreshold = -99999;
                      console.log('[ml] anti-death: Y threshold → -99999 on %s', entity.name);
                  }
              }
              return false;
          });

          const origFire = app.fire;
          app._mlOrigFire = origFire;
          app.fire = function (ev, a1, a2, a3, a4, a5, a6, a7, a8) {
              switch (ev) {
                  case 'DeathScreen:Trigger': if (featInvincible) return this; break;
                  case 'PlayerController:GotHit': if (featAntiKnockback) return this; break;
                  case 'ModeOverlay:BalanceChange':
                      if (_mlBatchCollecting) {
                          _mlBatchTotal += (typeof a1 === 'number' ? a1 : 0);
                          _mlBatchCount++;
                          clearTimeout(_mlBatchTimer);
                          _mlBatchTimer = setTimeout(flushBatch, 2000);
                          return this;
                      }
                      break;
                  case 'ModeOverlay:Balance':
                      if (_mlBatchCollecting) { _mlCachedBalance = a1; return this; }
                      break;
              }
              return origFire.call(this, ev, a1, a2, a3, a4, a5, a6, a7, a8);
          };
          app._mlNotifBatched = true;

          antiDeathReady = true;
          console.log('[ml] anti-death: ready');
      }

      let antiKnockbackReady = false;

      function hookAntiKnockback() {
          if (antiKnockbackReady || !featAntiKnockback) return;
          const player = getPlayer();
          const pc = getPC(player);
          if (!pc) return;
          pc.shockedTime = 0;
          pc.knockbackDecay = 1.0;
          antiKnockbackReady = true;
      }

      let noclipHooked = false;

      function hookNoclip() {
          if (noclipHooked) return;
          const player = getPlayer();
          const kcc = getKcc(player);
          if (!kcc || !kcc.update) return;
          const origUpdate = kcc.update.bind(kcc);
          kcc.update = function (dt) {
              if (!featNoclip) return origUpdate(dt);
              const pos = this.entity.getPosition();
              const preX = pos.x, preZ = pos.z;
              origUpdate(dt);
              const speed = this.speed || 7;
              const h = this._horizontal || 0, v = this._vertical || 0;
              const newPos = this.entity.getPosition();
              this.entity.setPosition(preX + h * speed * dt, newPos.y, preZ + v * speed * dt);
          };
          noclipHooked = true;
          console.log('[ml] noclip: KCC update hooked');
      }

      let netSpyHooked = false;

      function hookNetSpy() {
          if (netSpyHooked) return;
          const nm = _getNetworkManager();
          if (!nm?.room) return;
          netSpyHooked = true;

          const NOISY = new Set(['positions', 'animSetBooleans', 'animSetTrigger']);

          const origOnMessage = nm.room.onMessage.bind(nm.room);
          nm.room.onMessage = function (type, cb) {
              if (NOISY.has(type)) return origOnMessage(type, cb);
              return origOnMessage(type, function () {
                  console.log('[ml] NET ←', type, arguments.length === 1 ? arguments[0] : Array.from(arguments));
                  return cb.apply(this, arguments);
              });
          };

          const prevSend = nm.room.send;
          nm.room.send = function (type, message) {
              if (type !== 'p' && !NOISY.has(type)) {
                  console.log('[ml] NET →', type, message);
              }
              return prevSend.call(this, type, message);
          };

          console.log('[ml] net-spy: active');
      }

      W.mlSetUsername = function (name) {
          if (!name || typeof name !== 'string') {
              console.log('[ml] usage: mlSetUsername("NewName")');
              return;
          }
          const token = localStorage.getItem('access_token_v0.01');
          if (!token) {
              console.log('[ml] no access token — not logged in');
              return;
          }
          console.log('[ml] changing username to: ' + name);
          fetch('https://api.meeland.io/v1/users/changeName', {
              method: 'POST',
              headers: { 'Accept': 'application/json', 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + token },
              body: JSON.stringify({ name })
          }).then(r => {
              if (!r.ok) throw new Error(r.status === 400 || r.status === 403 ? 'already used free name change' : 'HTTP ' + r.status);
              return r.text().then(() => null);
          }).then(() => {
              console.log('[ml] username changed to: ' + name + ' — syncing to room');
              const app = W.pc?.app;
              if (app) app.fire('NetworkManager:Send', 'refreshUsername');
          }).catch(e => {
              console.error('[ml] username change failed:', e.message);
          });
      };

      W.mlProbeUsername = function () {
          const nm = _getNetworkManager();
          if (!nm) { console.log('[ml] no network manager'); return; }
          const room = nm.room;
          const ps = room?.state?.players;
          const me = ps?.get?.(room.sessionId);
          console.log('[ml] username:', me?.username);
          console.log('[ml] sessionId:', room?.sessionId);
          console.log('[ml] accountId:', me?.accountId);
          console.log('[ml] room:', room?.name, room?.id);
      };

      createHUD();
      loadWaypoints();

      if (isMobile) {
          const mfc = document.createElement('div');
          mfc.id = 'ml-mobile-fly';
          mfc.innerHTML = '<button id="ml-mfly-up" class="ml-mfly">▲</button><button id="ml-mfly-down" class="ml-mfly">▼</button><button id="ml-mfly-toggle" class="ml-mfly">FLY</button><button id="ml-msprint" class="ml-mfly ml-msprint-btn">SPR</button><button id="ml-mfly-collapse" class="ml-mfly ml-mcollapse">⊟</button>';
          const mfcStyle = document.createElement('style');
          mfcStyle.textContent = [
              '#ml-mobile-fly{position:fixed;right:2.5vmin;bottom:3vmin;z-index:15;display:none;flex-direction:column;align-items:flex-end;gap:clamp(6px,1.5vmin,12px);pointer-events:none}',
              '.ml-mfly{pointer-events:auto;width:clamp(52px,12vmin,80px);height:clamp(52px,12vmin,80px);border-radius:50%;border:2px solid rgba(200,170,255,.35);background:rgba(55,45,80,.55);color:rgba(255,250,255,.85);font-size:clamp(14px,3.5vmin,22px);font-weight:700;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;cursor:pointer;user-select:none;-webkit-user-select:none;touch-action:none;display:flex;align-items:center;justify-content:center;backdrop-filter:blur(6px);-webkit-backdrop-filter:blur(6px);transition:background .12s,border-color .12s,transform .08s}',
              '.ml-mfly:active{transform:scale(.92);background:rgba(140,100,220,.4);border-color:rgba(200,170,255,.6)}',
              '.ml-mfly.on{background:rgba(100,180,100,.4);border-color:rgba(140,230,140,.5)}',
              '.ml-mcollapse{width:clamp(36px,8vmin,52px);height:clamp(36px,8vmin,52px);font-size:clamp(16px,4vmin,24px);border-color:rgba(180,180,200,.3);background:rgba(40,40,55,.55)}',
              '.ml-mcollapse:active{background:rgba(100,100,140,.4)}',
              '#ml-mobile-fly.collapsed .ml-mfly:not(.ml-mcollapse){display:none}',
              '.ml-msprint-btn{border-color:rgba(255,180,60,.35);background:rgba(80,55,30,.55);font-size:clamp(11px,2.8vmin,16px)}',
              '.ml-msprint-btn:active{background:rgba(220,160,40,.4);border-color:rgba(255,200,80,.6)}',
              '.ml-msprint-btn.on{background:rgba(220,160,40,.4);border-color:rgba(255,200,80,.5)}',
          ].join('');
          document.head.appendChild(mfcStyle);
          document.body.appendChild(mfc);

          const mflyToggle = $('ml-mfly-toggle');
          const mflyUp = $('ml-mfly-up');
          const mflyDown = $('ml-mfly-down');
          const msprint = $('ml-msprint');
          const mCollapse = $('ml-mfly-collapse');

          let _collapseLock = false;
          const toggleCollapse = () => {
              if (_collapseLock) return;
              _collapseLock = true;
              setTimeout(() => _collapseLock = false, 200);
              const collapsed = mfc.classList.toggle('collapsed');
              mCollapse.textContent = collapsed ? '⊞' : '⊟';
          };
          mCollapse.addEventListener('touchstart', e => { e.preventDefault(); toggleCollapse(); });
          mCollapse.addEventListener('click', toggleCollapse);

          mflyToggle.addEventListener('touchstart', e => {
              e.preventDefault();
              if (!featFly) return;
              const p = getPlayer(), k = getKcc(p);
              if (!k) return;
              if (flyActive) { flyOff(k); mflyToggle.classList.remove('on'); }
              else { flyOn(k, true); mflyToggle.classList.add('on'); }
          });

          msprint.addEventListener('touchstart', e => {
              e.preventDefault();
              if (!featSprint) return;
              sprinting = !sprinting;
              msprint.classList.toggle('on', sprinting);
          });

          mflyUp.addEventListener('touchstart', e => {
              e.preventDefault();
              if (!featFly || !flyActive) return;
              flyUp = true;
          });
          mflyUp.addEventListener('touchend', e => {
              e.preventDefault();
              flyUp = false;
          });
          mflyUp.addEventListener('touchcancel', () => { flyUp = false; });

          mflyDown.addEventListener('touchstart', e => {
              e.preventDefault();
              if (!featFly || !flyActive) return;
              flyDown = true;
          });
          mflyDown.addEventListener('touchend', e => {
              e.preventDefault();
              flyDown = false;
          });
          mflyDown.addEventListener('touchcancel', () => { flyDown = false; });
      }

      let _flyClickTime = 0;
      $('ml-fly').addEventListener('click', () => {
          if (!featFly || isMobile) return;
          const now = Date.now();
          if (now - _flyClickTime < 300) return;
          _flyClickTime = now;
          const p = getPlayer(), k = getKcc(p);
          if (!k) return;
          if (flyActive) flyOff(k); else flyOn(k, true);
      });

      $('ml-spr').addEventListener('click', () => {
          if (!featSprint) return;
          sprinting = !sprinting;
      });

      $('ml-home').addEventListener('click', () => {
          if (!featWaypoints) return;
          const p = getPlayer();
          if (!p) return;
          homePos = capturePos(p);
          saveWaypoints();
          flash('ml-home');
          console.log('[ml] home set via click');
      });

      $('ml-go').addEventListener('click', () => {
          if (!featWaypoints || !homePos) return;
          const p = getPlayer();
          if (!p) return;
          backPos = capturePos(p);
          teleport(p, homePos);
          saveWaypoints();
          flash('ml-go');
      });

      $('ml-back').addEventListener('click', () => {
          if (!featWaypoints || !backPos) return;
          const p = getPlayer();
          if (!p) return;
          const cur = capturePos(p);
          teleport(p, backPos);
          backPos = cur;
          saveWaypoints();
          flash('ml-back');
      });

      $('ml-lock').addEventListener('click', () => {
          if (!featAutoLock) return;
          triggerLock();
          flash('ml-lock');
      });

      function toggleSlots(forceOpen) {
          if (!featWaypoints) return;
          const pop = $('ml-slots-pop');
          if (!pop) return;
          const open = forceOpen !== undefined ? forceOpen : !pop.classList.contains('open');
          pop.classList.toggle('open', open);
          if (open) {
              pop.dispatchEvent(new CustomEvent('ml-rebuild'));
              panelPause();
          } else {
              pop._resetPos?.();
              panelResume();
          }
      }
      $('ml-slots').addEventListener('click', () => { toggleSlots(); });

      function _ensureWeapon() {
          const nm = _getNetworkManager();
          const room = nm?.room;
          if (!room?.connection?.isOpen) return;
          let myItem = null;
          room.state?.players?.forEach?.((p, sid) => {
              if (sid === room.sessionId) myItem = p.selectedItem;
          });
          if (myItem && myItem !== 'Hand') return;
          const rawSend = room.__mlOrigSend || room.send.bind(room);
          rawSend('selectItem', 'Baton');
          console.log('[ml] weapon: equipped Baton');
      }

      function attackClosestEnemy() {
          if (!_getBasesManager()?.activeBases) return;
          _ensureWeapon();
          const player = getPlayer();
          if (!player) return;
          const holder = W.pc?.app?.root?.findByName('EnemyHolder');
          if (!holder?.children?.length) {
              console.log('[ml] attack: no enemies found');
              return;
          }
          const pPos = player.getPosition();
          let closest = null, closestDist = Infinity, closestName = 'Player', closestEntity = null;
          for (const enemy of holder.children) {
              let name = null;
              try { name = enemy.script?.enemy?.usernameEntity?.element?.text; } catch (_) {}
              if (!name) { const ue = enemy.findByName?.('Username'); if (ue?.element?.text) name = ue.element.text; }
              if (name === 'Stishka') continue;
              const ePos = enemy.getPosition();
              const d = pPos.distance(ePos);
              if (d < closestDist) {
                  closestDist = d;
                  closest = ePos;
                  closestEntity = enemy;
                  closestName = name || 'Player';
              }
          }
          if (!closest || !closestEntity) return;
          backPos = capturePos(player);
          const nm = _getNetworkManager();
          const room = nm?.room;
          if (room?.connection?.isOpen) {
              const rawSend = room.__mlOrigSend || room.send.bind(room);
              _ghostSuppressed = true;
              const myPos = player.getPosition();
              const dx = closest.x - myPos.x;
              const dz = closest.z - myPos.z;
              const dir = Math.atan2(dx, dz) * (180 / Math.PI);
              const startTime = performance.now();
              let attacked = false;
              const trackAndAttack = () => {
                  const ep = closestEntity.getPosition();
                  teleport(player, vec3(ep));
                  rawSend('p', { x: ep.x, y: ep.y, z: ep.z, w: dir });
                  if (performance.now() - startTime >= 50 && !attacked) {
                      rawSend('attack', 'Baton');
                      attacked = true;
                  }
                  if (performance.now() - startTime < 150) {
                      requestAnimationFrame(trackAndAttack);
                  } else {
                      if (!attacked) rawSend('attack', 'Baton');
                      console.log('[ml] attack: locked ' + Math.round(performance.now() - startTime) + 'ms');
                      setTimeout(() => { _ghostSuppressed = false; }, 200);
                  }
              };
              trackAndAttack();
          }
          console.log('[ml] attack: TP→' + closestName + ' (' + Math.floor(closestDist) + 'm)');
      }

      let _autoAttackCooldown = 0;
      let _autoAttackTarget = null;
      let _autoAttackRRIdx = 0;
      let _myPetTokens = new Set();
      let _myPetTokensTime = 0;
      function _refreshMyPetTokens() {
          const now = Date.now();
          if (now - _myPetTokensTime < 10000 && _myPetTokens.size > 0) return;
          const nm = _getNetworkManager();
          const myId = nm?.room?.sessionId;
          if (!myId) return;
          const root = W.pc?.app?.root;
          if (!root) return;
          root.find(e => {
              if (!e.script) return false;
              for (const k of Object.keys(e.script)) {
                  const s = e.script[k];
                  if (s?.basePets && s.basePets.size > 0) {
                      s.basePets.forEach((pet, token) => {
                          if (pet?.owner === myId) _myPetTokens.add(token);
                      });
                      _myPetTokensTime = now;
                      return true;
                  }
              }
              return false;
          });
      }
      function autoAttackPetThieves() {
          if (!featAutoAttack) return;
          if (!_getBasesManager()?.activeBases) return;
          const now = Date.now();
          if (now < _autoAttackCooldown) return;
          _refreshMyPetTokens();
          if (_myPetTokens.size === 0) return;
          const nm = _getNetworkManager();
          const room = nm?.room;
          if (!room?.state?.players || !room?.connection?.isOpen) return;
          const myId = room.sessionId;
          const thieves = [];
          room.state.players.forEach((p, sid) => {
              if (sid === myId) return;
              if (p.isHoldingPet && p.holdingPetToken && _myPetTokens.has(p.holdingPetToken)) {
                  thieves.push({ sid, name: p.username || 'Player' });
              }
          });
          if (thieves.length === 0) {
              if (_autoAttackTarget) {
                  console.log('[ml] auto-attack: no thieves — chase ended');
                  _autoAttackTarget = null;
                  _autoAttackRRIdx = 0;
              }
              return;
          }
          if (_autoAttackRRIdx >= thieves.length) _autoAttackRRIdx = 0;
          const thief = thieves[_autoAttackRRIdx];
          _autoAttackRRIdx++;
          const holder = W.pc?.app?.root?.findByName('EnemyHolder');
          if (!holder?.children?.length) return;
          let targetEntity = null;
          for (const enemy of holder.children) {
              let name = null;
              try { name = enemy.script?.enemy?.usernameEntity?.element?.text; } catch (_) {}
              if (!name) { const ue = enemy.findByName?.('Username'); if (ue?.element?.text) name = ue.element.text; }
              if (name === thief.name) { targetEntity = enemy; break; }
          }
          if (!targetEntity) return;
          _ensureWeapon();
          const player = getPlayer();
          if (!player) return;
          if (_autoAttackTarget !== thief.name) {
              backPos = capturePos(player);
              console.log('[ml] auto-attack: → ' + thief.name + ' (' + thieves.length + ' thieves)');
          }
          _autoAttackTarget = thief.name;
          _autoAttackCooldown = now + 1600;
          const rawSend = room.__mlOrigSend || room.send.bind(room);
          _ghostSuppressed = true;
          const myPos = player.getPosition();
          const tPos = targetEntity.getPosition();
          const dx = tPos.x - myPos.x;
          const dz = tPos.z - myPos.z;
          const dir = Math.atan2(dx, dz) * (180 / Math.PI);
          const startTime = performance.now();
          let attacked = false;
          const trackAndAttack = () => {
              const ep = targetEntity.getPosition();
              teleport(player, vec3(ep));
              rawSend('p', { x: ep.x, y: ep.y, z: ep.z, w: dir });
              if (performance.now() - startTime >= 50 && !attacked) {
                  rawSend('attack', 'Baton');
                  attacked = true;
              }
              if (performance.now() - startTime < 150) {
                  requestAnimationFrame(trackAndAttack);
              } else {
                  if (!attacked) rawSend('attack', 'Baton');
                  setTimeout(() => { _ghostSuppressed = false; }, 200);
              }
          };
          trackAndAttack();
      }

      window.addEventListener('keydown', e => {
          const openPanel = $('ml-pets')?.classList.contains('open') ? 'pets'
              : $('ml-settings')?.classList.contains('open') ? 'settings'
              : $('ml-plist')?.classList.contains('open') ? 'plist'
              : $('ml-dialog')?.classList.contains('open') ? 'dialog'
              : $('ml-slots-pop')?.classList.contains('open') ? 'slots'
              : $('ml-snipe')?.classList.contains('open') ? 'snipe'
              : null;
          if (openPanel) {
              if (openPanel === 'snipe') {
                  if (e.code === 'Escape') {
                      e.stopPropagation();
                      e.preventDefault();
                      $('ml-snipe-close')?.click();
                      return;
                  }
                  const active = document.activeElement;
                  if (active && (active.tagName === 'INPUT' || active.tagName === 'TEXTAREA')
                      && $('ml-snipe')?.contains(active)) {
                      e.stopPropagation();
                      return;
                  }
              } else {
                  if (e.code === 'Escape') {
                      e.stopPropagation();
                      e.preventDefault();
                      if (openPanel === 'pets') $('ml-pets-close')?.click();
                      else if (openPanel === 'settings') $('ml-settings-close')?.click();
                      else if (openPanel === 'plist') $('ml-plist-close')?.click();
                      else if (openPanel === 'dialog') $('ml-close')?.click();
                      else if (openPanel === 'slots') $('ml-slots-pop-close')?.click();
                      return;
                  }
                  const active = document.activeElement;
                  if (active?.tagName === 'INPUT' || active?.tagName === 'TEXTAREA') {
                      e.stopPropagation();
                      return;
                  }
                  e.stopPropagation();
                  e.preventDefault();
                  return;
              }
          }

          const active = document.activeElement;
          if (active?.tagName === 'INPUT' || active?.tagName === 'TEXTAREA') return;
          if (kbListeningRow) return;

          if (e.code === KEYBINDS.cuddle) {
              e.preventDefault();
              e.stopPropagation();
              if (featCuddle) $('ml-tp')?.click();
              return;
          }

          if (e.code === KEYBINDS.settings) {
              e.preventDefault();
              e.stopPropagation();
              $('ml-cfg')?.click();
              return;
          }

          if (e.code === KEYBINDS.pets) {
              e.preventDefault();
              e.stopPropagation();
              if (featPets) $('ml-pets-btn')?.click();
              return;
          }

          if (e.code === KEYBINDS.snipe) {
              e.preventDefault();
              e.stopPropagation();
              $('ml-snipe-btn')?.click();
              return;
          }

          if (e.code === KEYBINDS.attack) {
              e.preventDefault();
              e.stopPropagation();
              attackClosestEnemy();
              return;
          }

          if (e.code === KEYBINDS.slots) {
              e.preventDefault();
              e.stopPropagation();
              $('ml-slots')?.click();
              return;
          }

          if (e.code === KEYBINDS.help) {
              e.preventDefault();
              e.stopPropagation();
              $('ml-help')?.click();
              return;
          }

          const player = getPlayer();
          const kcc    = getKcc(player);
          if (!kcc) return;

          if (cuddling && MOVE_KEYS.has(e.code)) {
              cuddling = false;
              cuddleTarget = null;
              console.log('[cuddle] cancelled');
          }

          const numpadMatch = e.code.match(/^Numpad(\d)$/);
          let slotIdx = numpadMatch ? parseInt(numpadMatch[1]) : -1;
          if (slotIdx < 0 && e.location === 3) {
              const alt = { Insert: 0, End: 1, ArrowDown: 2, PageDown: 3, ArrowLeft: 4, Clear: 5, ArrowRight: 6, Home: 7, ArrowUp: 8, PageUp: 9 };
              slotIdx = alt[e.key] ?? -1;
          }
          if (slotIdx >= 0 && featWaypoints) {
              const idx = slotIdx;
              if (e.ctrlKey) {
                  e.preventDefault();
                  e.stopPropagation();
                  slots[idx] = capturePos(player);
                  saveWaypoints();
                  flash('ml-slots');
                  console.log(`[slot ${idx}] saved`, slots[idx]);
                  return;
              } else if (slots[idx]) {
                  e.preventDefault();
                  e.stopPropagation();
                  backPos = capturePos(player);
                  teleport(player, slots[idx]);
                  saveWaypoints();
                  flash('ml-slots');
                  console.log(`[slot ${idx}] teleported to`, slots[idx]);
                  return;
              }
          }

          if (e.code === KEYBINDS.fly) {
              if (!featFly) return;
              e.stopPropagation();
              if (flyActive) {
                  flyUp = true;
              } else {
                  flyOn(kcc);
              }
              return;
          }

          if (e.code === KEYBINDS.flyDown && flyActive) { flyDown = true; return; }

          if (e.key === 'Shift' && !sprinting && featSprint) { sprinting = true; return; }

          if (e.code === KEYBINDS.setHome) {
              e.preventDefault();
              if (!featWaypoints) return;
              homePos = capturePos(player);
              saveWaypoints();
              flash('ml-home');
              return;
          }

          if (e.code === KEYBINDS.home) {
              e.preventDefault();
              if (!featWaypoints) return;
              if (homePos) {
                  backPos = capturePos(player);
                  teleport(player, homePos);
                  saveWaypoints();
                  flash('ml-go');
              }
              return;
          }

          if (e.code === KEYBINDS.back) {
              e.preventDefault();
              if (!featWaypoints) return;
              if (backPos) {
                  const cur = capturePos(player); teleport(player, backPos); backPos = cur;
                  saveWaypoints();
                  flash('ml-back');
              }
          }
      }, true);

      window.addEventListener('keyup', e => {
          if (anyPanelOpen()) {
              e.stopPropagation();
              return;
          }
          if (e.code === KEYBINDS.fly)     flyUp   = false;
          if (e.code === KEYBINDS.flyDown) flyDown = false;
          if (e.key === 'Shift' && sprinting) { sprinting = false; }
      }, true);

      const _htmlHud = { fly: $('ml-fly'), spr: $('ml-spr'), home: $('ml-home'), go: $('ml-go'), back: $('ml-back'), slots: $('ml-slots'), lock: $('ml-lock'), tp: $('ml-tp'), petsBtn: $('ml-pets-btn'), snipeBtn: $('ml-snipe-btn'), cfg: $('ml-cfg'), help: $('ml-help') };
      _nativeHud = createNativeHUD();
      if (!_nativeHud) {
          const retryId = setInterval(() => {
              _nativeHud = createNativeHUD();
              if (_nativeHud) {
                  clearInterval(retryId);
                  Object.assign(hud, _nativeHud);
                  console.log('[ml] native HUD loaded (deferred)');
              }
          }, 500);
      }
      const hud = _nativeHud || _htmlHud;
      const panels = { plist: $('ml-plist'), settings: $('ml-settings'), dialog: $('ml-dialog'), pets: $('ml-pets'), snipe: $('ml-snipe') };

      let _hopResumed = false;
      let _hopInProgress = false;
      function performServerHop() {
          if (_hopInProgress) return;
          _hopInProgress = true;
          const app = W.pc?.app;
          if (!app) { _hopInProgress = false; return; }
          app.fire('NetworkManager:LeaveCurrentRoom');
          console.log('[ml] hop: left room');
          $('ml-snipe-status').textContent = 'Hopping...';
          _nmCache = null; _nmCacheTime = 0;
          _bmCache = null; _bmCacheTime = 0;
          _serverLockedSessions.clear();
          _lockDataReceived = false;
          _myPetTokens = new Set(); _myPetTokensTime = 0;
          _snipeHopNoMatchStart = 0;
          _hopResumed = false;
          setTimeout(() => {
              app.fire('OverlayManager:ClickGameMode', 0);
              console.log('[ml] hop: rejoining Steal a Pet');
              const dismissPlay = () => {
                  const fml = app.root.findByName('FirstMouseLock');
                  if (fml && fml.enabled) {
                      fml.enabled = false;
                      app.fire('GameManager:GameResumed');
                      console.log('[ml] hop: dismissed PLAY screen');
                  }
                  const canvas = app.graphicsDevice?.canvas;
                  if (canvas && !document.pointerLockElement) {
                      try { canvas.requestPointerLock(); } catch (_) {}
                  }
                  _hopInProgress = false;
              };
              let attempts = 0;
              const checkReady = setInterval(() => {
                  attempts++;
                  const nm = _getNetworkManager();
                  const fml = app.root.findByName('FirstMouseLock');
                  if ((nm?.room?.state?.players && fml?.enabled) || attempts > 30) {
                      clearInterval(checkReady);
                      dismissPlay();
                  }
              }, 500);
          }, 3000);
      }
      function tryHopResume() {
          if (_hopResumed || _hopInProgress || !snipeAutoHop || !snipeActive) return;
          const nm = _getNetworkManager();
          if (!nm?.room?.state?.players) return;
          const bm = _getBasesManager();
          if (!bm?.activeBases) return;
          const root = W.pc?.app?.root;
          let petCount = 0;
          if (root) {
              root.find(e => {
                  if (!e.script) return false;
                  for (const k of Object.keys(e.script)) {
                      const s = e.script[k];
                      if (s?.basePets && s.basePets.size > 0) { petCount = s.basePets.size; return true; }
                  }
                  return false;
              });
          }
          if (petCount === 0) return;
          _hopResumed = true;
          _snipeHopNoMatchStart = 0;
          console.log('[ml] hop: new room ready (' + petCount + ' pets)');
          const logEl = $('ml-snipe-log');
          if (logEl) {
              const entry = document.createElement('div');
              entry.className = 'ml-snipe-entry';
              entry.textContent = new Date().toLocaleTimeString() + ' Hopped — ' + petCount + ' pets in new room';
              logEl.prepend(entry);
          }
      }

      let _tickN = 0;
      function _tick() {
          requestAnimationFrame(_tick);
          _tickN++;
          const slow = (_tickN % 12 === 0);

          if (slow) {
              try { hookPetSpawn(); } catch (_) {}
              try { hookAntiDisconnect(); } catch (_) {}
              try { tryHopResume(); } catch (_) {}
              try { hookAntiDeath(); } catch (_) {}
              try { hookAntiKnockback(); } catch (_) {}
              try { hookNoclip(); } catch (_) {}
              try { updateGhostEffect(); } catch (_) {}
              try { hookNetSpy(); } catch (_) {}
              if (!freeMoneyDone && featFreeMoney) autoFarmFreeMoney();
              if (featFreeStars && !_freeStarsTimer) _freeStarsStart();
              autoCollectPetEarnings();
              autoAttackPetThieves();

              const ct = $('ml-carry-timer');
              if (ct) {
                  const pt = _getPetTycoon();
                  const holding = !!(pt?.isHoldingPet);
                  const isSteal = !!_getBasesManager()?.activeBases;
                  if (holding && isSteal) {
                      if (!ct._start) ct._start = Date.now();
                      ct.style.display = '';
                      ct.textContent = '\u23F1 ' + ((Date.now() - ct._start) / 1000).toFixed(1) + 's';
                  } else {
                      if (ct._start) ct._start = 0;
                      ct.style.display = 'none';
                  }
              }
          }

          const player = getPlayer();
          if (!player) return;

          if (slow && _pendingBaseCapture) {
              const myBase = getMyBase();
              if (myBase) {
                  const bp = myBase.getPosition();
                  backPos = { x: bp.x, y: bp.y, z: bp.z };
                  _pendingBaseCapture = false;
                  console.log('[ml] backPos set to home base (' + (_roomJoinTime ? (Date.now() - _roomJoinTime) + 'ms' : '?') + '):', bp.x.toFixed(1), bp.y.toFixed(1), bp.z.toFixed(1));
              }
          }

          const kcc = getKcc(player);
          if (!kcc) return;

          const now = Date.now();
          const dt  = Math.min((now - prevTick) / 1000, 0.1);
          prevTick  = now;

          const pc = getPC(player);
          if (pc?.isDied) { pc.isDied = false; }

          if (sprinting && !isMobile) {
              const kb = W.pc?.app?.keyboard;
              if (kb && !kb.isPressed(W.pc.KEY_SHIFT)) { sprinting = false; }
          }
          if (sprinting) {
              const sprintMin = SPEED_DEFAULT * 3;
              if (sprintSpeed < sprintMin) sprintSpeed = sprintMin;
              if (accelEnabled) sprintSpeed = Math.min(sprintSpeed + ACCEL * dt, SPEED_CAP);
              setSpeed(pc, kcc, sprintSpeed);
              if (pc?.isSlowedSpeed) pc.isSlowedSpeed = false;
          } else if (sprintSpeed !== SPEED_DEFAULT) {
              sprintSpeed = SPEED_DEFAULT;
              setSpeed(pc, kcc, SPEED_DEFAULT);
          }

          if (slow) {
              if (hud.fly)  { hud.fly.classList.toggle('on', flyActive); hud.fly.classList.toggle('disabled', !featFly); }
              const mft = $('ml-mfly-toggle');
              if (mft) mft.classList.toggle('on', flyActive);
              if (hud.spr)  { hud.spr.classList.toggle('on', sprinting); hud.spr.classList.toggle('disabled', !featSprint); }
              const msp = $('ml-msprint');
              if (msp) msp.classList.toggle('on', sprinting);
              if (hud.home) hud.home.classList.toggle('disabled', !featWaypoints);
              if (hud.go)   hud.go.classList.toggle('disabled', !featWaypoints);
              if (hud.back) hud.back.classList.toggle('disabled', !featWaypoints);
              if (hud.slots) { const n = slots.filter(Boolean).length; hud.slots.textContent = `SLOTS ${n}/10`; hud.slots.classList.toggle('disabled', !featWaypoints); }
              if (hud.tp)   { hud.tp.classList.toggle('disabled', !featCuddle); hud.tp.classList.toggle('on', !!panels.plist?.classList.contains('open')); }
              if (hud.petsBtn) { hud.petsBtn.classList.toggle('disabled', !featPets); hud.petsBtn.classList.toggle('on', !!panels.pets?.classList.contains('open')); }
              if (hud.snipeBtn) { hud.snipeBtn.classList.toggle('on', snipeActive || !!panels.snipe?.classList.contains('open')); }
              if (hud.cfg)  hud.cfg.classList.toggle('on', !!panels.settings?.classList.contains('open'));
              if (hud.help) hud.help.classList.toggle('on', !!panels.dialog?.classList.contains('open'));

              if (featAutoLock && now - lastLockCheck > 100) {
                  lastLockCheck = now;
                  const lockBtn = getLockBtn();
                  if (lockBtn) {
                      const timeLeft = lockBtn.lockdownTimeLeft || 0;
                      const isActive = lockBtn.isLockdownActive || false;
                      if (hud.lock) {
                          hud.lock.classList.toggle('on', isActive && timeLeft > 0);
                          hud.lock.textContent = (isActive && timeLeft > 0) ? `\ud83d\udd12 ${Math.ceil(timeLeft)}s` : '\ud83d\udd13 UNLOCKED';
                      }
                      if (!isActive || timeLeft <= 0) triggerLock();
                  } else if (hud.lock) {
                      hud.lock.textContent = '\ud83d\udd12 LOCK';
                      hud.lock.classList.remove('on');
                  }
              }
              if (hud.lock) hud.lock.classList.toggle('disabled', !featAutoLock);
          }

          if (cuddling && cuddleTarget && featCuddleFollow) {
              if (kcc._horizontal || kcc._vertical) {
                  cuddling = false;
                  cuddleTarget = null;
                  console.log('[cuddle] cancelled (movement input)');
              } else {
                  const tPos = cuddleTarget.getPosition?.();
                  if (tPos) {
                      kcc.gravity = 0;
                      kcc._velY = 0;
                      teleport(player, vec3(tPos));
                  } else {
                      cuddling = false;
                      cuddleTarget = null;
                      console.log('[cuddle] target lost');
                  }
              }
          }

          if (!flyActive) return;

          if (kcc._grounded && Date.now() - _flyStartTime > 500) { flyOff(kcc, false); return; }

          if (kcc.gravity !== 0) kcc.gravity = 0;

          flyVelY = kcc._velY;
          const flySpeedCap = sprinting ? sprintSpeed : SPEED_CAP;

          if (flyUp) {
              if (flyVelY < 0) {
                  flyVelY = 0;
              } else if (accelEnabled) {
                  if (flyVelY < FLY_MIN_SPEED) flyVelY = FLY_MIN_SPEED;
                  flyVelY = Math.min(flyVelY + ACCEL * dt, flySpeedCap);
              } else {
                  flyVelY = flySpeedCap;
              }
          } else if (flyDown) {
              if (flyVelY > 0) {
                  flyVelY = 0;
              } else if (accelEnabled) {
                  if (flyVelY > -FLY_MIN_SPEED) flyVelY = -FLY_MIN_SPEED;
                  flyVelY = Math.max(flyVelY - ACCEL * dt, -flySpeedCap);
              } else {
                  flyVelY = -flySpeedCap;
              }
          } else {
              flyVelY = 0;
          }

          kcc._velY = flyVelY;
      }
      requestAnimationFrame(_tick);

  })();