♟Super-chess-Bot

Super chess Bot is a tournament level bullet bot

설치하기 전에, Greasy Fork는 이 스크립트에 사용자가 아닌 스크립트 작성자의 이익을 위한 기능인 역기능이 포함되어 있음을 알려드립니다.

그룹 가입, 채널 구독, 페이지 '좋아요' 등 특정 활동을 완료해야 스크립트의 모든 기능을 사용할 수 있습니다.

이 스크립트를 설치하려면 Tampermonkey, Greasemonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램을 설치해야 합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Userscripts와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 유저 스크립트 관리자 확장 프로그램이 필요합니다.

(이미 유저 스크립트 관리자가 설치되어 있습니다. 설치를 진행합니다!)

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

(이미 유저 스타일 관리자가 설치되어 있습니다. 설치를 진행합니다!)

// ==UserScript==
// @name          ♟Super-chess-Bot
// @namespace     http://tampermonkey.net/
// @version       9.0.0
// @description   Super chess Bot is a tournament level bullet bot
// @author        quantavil
// @match         https://www.chess.com/play/computer*
// @match         https://www.chess.com/game/*
// @match         https://www.chess.com/play/online*
// @license       MIT
// @icon          https://www.google.com/s2/favicons?sz=64&domain=chess.com
// @grant         GM_xmlhttpRequest
// @connect       stockfish.online
// @antifeature   membership
// ==/UserScript==


(() => {
  // src/utils.js
  function debounce(fn, wait = 150) {
    let t = null;
    return (...args) => {
      clearTimeout(t);
      t = setTimeout(() => fn(...args), wait);
    };
  }
  function getRandomDepth(botPower) {
    const minDepth = 5;
    const maxDepth = Math.max(botPower || 10, minDepth);
    return Math.floor(Math.random() * (maxDepth - minDepth + 1)) + minDepth;
  }
  function getHumanDelay(baseDelay, randomDelay) {
    return baseDelay + Math.floor(Math.random() * randomDelay);
  }
  var sleep = (ms) => new Promise((r) => setTimeout(r, ms));
  var qs = (sel, root = document) => root.querySelector(sel);
  var qsa = (sel, root = document) => Array.from(root.querySelectorAll(sel));
  async function waitForElement(selector, timeout = 15e3) {
    return new Promise((resolve, reject) => {
      const existing = qs(selector);
      if (existing)
        return resolve(existing);
      let timeoutId;
      const obs = new MutationObserver(() => {
        const el = qs(selector);
        if (el) {
          clearTimeout(timeoutId);
          obs.disconnect();
          resolve(el);
        }
      });
      obs.observe(document.body, { childList: true, subtree: true });
      timeoutId = setTimeout(() => {
        obs.disconnect();
        reject(new Error(`Element ${selector} not found within ${timeout}ms`));
      }, timeout);
    });
  }
  function scoreFrom(obj) {
    if (!obj)
      return {};
    if (typeof obj === "object") {
      if ("mate" in obj && obj.mate !== 0)
        return { mate: parseInt(obj.mate, 10) };
      if ("cp" in obj)
        return { cp: parseInt(obj.cp, 10) };
    }
    if (typeof obj === "string") {
      if (obj.toUpperCase().includes("M")) {
        const m = parseInt(obj.replace(/[^-0-9]/g, ""), 10);
        if (!isNaN(m))
          return { mate: m };
      }
      const cpFloat = parseFloat(obj);
      if (!isNaN(cpFloat))
        return { cp: Math.round(cpFloat * 100) };
    }
    if (typeof obj === "number")
      return { cp: Math.round(obj * 100) };
    return {};
  }
  function scoreToDisplay(score) {
    if (score && typeof score.mate === "number" && score.mate !== 0)
      return `M${score.mate}`;
    if (score && typeof score.cp === "number")
      return (score.cp / 100).toFixed(2);
    return "-";
  }
  function scoreNumeric(s) {
    if (!s)
      return -Infinity;
    if (typeof s.mate === "number")
      return s.mate > 0 ? 1e5 - s.mate : -1e5 - s.mate;
    if (typeof s.cp === "number")
      return s.cp;
    return -Infinity;
  }
  function fenCharAtSquare(fen, square) {
    if (!fen || !square)
      return null;
    const placement = fen.split(" ")[0];
    const ranks = placement.split("/");
    const file = "abcdefgh".indexOf(square[0]);
    const rankNum = parseInt(square[1], 10);
    if (file < 0 || rankNum < 1 || rankNum > 8 || ranks.length !== 8)
      return null;
    const row = 8 - rankNum;
    const rowStr = ranks[row];
    let col = 0;
    for (const ch of rowStr) {
      if (/\d/.test(ch)) {
        col += parseInt(ch, 10);
        if (col > file)
          return null;
      } else {
        if (col === file)
          return ch;
        col++;
      }
    }
    return null;
  }
  function pieceFromFenChar(ch) {
    if (!ch)
      return null;
    const isUpper = ch === ch.toUpperCase();
    return { color: isUpper ? "w" : "b", type: ch.toLowerCase() };
  }

  // src/config.js
  var API_URL = "https://stockfish.online/api/s/v2.php";
  var MULTIPV = 1;
  var ANALYZE_TIMEOUT_MS = 1e3;
  var AUTO_MOVE_BASE = 200;
  var AUTO_MOVE_STEP = 20;
  var RANDOM_JITTER_MIN = 0;
  var GAME_CACHE_TTL = 100;

  // src/state.js
  var BotState = {
    hackEnabled: 0,
    botPower: 8,
    updateSpeed: 10,
    autoMove: 1,
    autoMoveSpeed: 10,
    randomDelay: 50,
    currentEvaluation: "-",
    bestMove: "-",
    principalVariation: "-",
    statusInfo: "Ready",
    premoveEnabled: 0,
    premoveMode: "every",
    premovePieces: { q: 1, r: 1, b: 1, n: 1, k: 1, p: 1 },
    premoveChance: 85,
    currentPremoveReasons: "",
    autoRematch: 0
  };
  var LRUCache = class {
    constructor(limit = 2e3) {
      this.limit = limit;
      this.cache = /* @__PURE__ */ new Map();
    }
    get(key) {
      if (!this.cache.has(key))
        return void 0;
      const val = this.cache.get(key);
      this.cache.delete(key);
      this.cache.set(key, val);
      return val;
    }
    set(key, value) {
      if (this.cache.has(key))
        this.cache.delete(key);
      else if (this.cache.size >= this.limit) {
        this.cache.delete(this.cache.keys().next().value);
      }
      this.cache.set(key, value);
    }
    clear() {
      this.cache.clear();
    }
  };
  var PositionCache = new LRUCache(2e3);
  var Settings = {
    save: debounce(() => {
      try {
        const menuWrap = qs("#menuWrap");
        const settings = {
          hackEnabled: BotState.hackEnabled,
          botPower: BotState.botPower,
          updateSpeed: BotState.updateSpeed,
          autoMove: BotState.autoMove,
          autoMoveSpeed: BotState.autoMoveSpeed,
          randomDelay: Math.max(RANDOM_JITTER_MIN, BotState.randomDelay),
          premoveEnabled: BotState.premoveEnabled,
          premoveMode: BotState.premoveMode,
          premovePieces: BotState.premovePieces,
          autoRematch: BotState.autoRematch,
          menuPosition: menuWrap ? { top: menuWrap.style.top, left: menuWrap.style.left } : null
        };
        localStorage.setItem("gabibot_settings", JSON.stringify(settings));
      } catch (e) {
        console.warn("Failed to save settings:", e);
      }
    }, 200),
    load() {
      try {
        const saved = localStorage.getItem("gabibot_settings");
        if (!saved)
          return null;
        const s = JSON.parse(saved);
        BotState.hackEnabled = s.hackEnabled ?? 0;
        BotState.botPower = s.botPower ?? 8;
        BotState.updateSpeed = s.updateSpeed ?? 10;
        BotState.autoMove = s.autoMove ?? 1;
        BotState.autoMoveSpeed = s.autoMoveSpeed ?? 8;
        BotState.randomDelay = Math.max(RANDOM_JITTER_MIN, s.randomDelay ?? 300);
        BotState.premoveEnabled = s.premoveEnabled ?? 0;
        BotState.premoveMode = s.premoveMode ?? "every";
        BotState.premovePieces = s.premovePieces ?? { q: 1, r: 1, b: 1, n: 1, k: 1, p: 1 };
        BotState.autoRematch = s.autoRematch ?? 0;
        return s;
      } catch (e) {
        console.error("Failed to load settings:", e);
        return null;
      }
    }
  };
  var cachedGame = null;
  var cachedGameTimestamp = 0;
  var cachedBoardFlipped = false;
  var cachedFlipTimestamp = 0;
  var getBoard = () => qs("chess-board") || qs(".board") || qs('[class*="board"]');
  var getGame = () => {
    const now = Date.now();
    if (cachedGame && now - cachedGameTimestamp < GAME_CACHE_TTL) {
      return cachedGame;
    }
    cachedGame = getBoard()?.game || null;
    cachedGameTimestamp = now;
    return cachedGame;
  };
  var getFen = (g) => {
    try {
      return g?.getFEN ? g.getFEN() : null;
    } catch {
      return null;
    }
  };
  var getPlayerColor = (g) => {
    try {
      const v = g?.getPlayingAs?.();
      return v === 2 ? "b" : "w";
    } catch {
      return "w";
    }
  };
  var getSideToMove = (g) => {
    const fen = getFen(g);
    return fen ? fen.split(" ")[1] || null : null;
  };
  var isPlayersTurn = (g) => {
    const me = getPlayerColor(g), stm = getSideToMove(g);
    return !!me && !!stm && me === stm;
  };
  var pa = () => getGame()?.getPlayingAs ? getGame().getPlayingAs() : 1;
  function isBoardFlipped() {
    const now = Date.now();
    if (now - cachedFlipTimestamp < 1e3)
      return cachedBoardFlipped;
    const el = getBoard();
    let flipped = false;
    try {
      const attr = el?.getAttribute?.("orientation");
      if (attr === "black")
        flipped = true;
      else if (attr === "white")
        flipped = false;
      else if (el?.classList?.contains("flipped"))
        flipped = true;
      else if (getGame()?.getPlayingAs?.() === 2)
        flipped = true;
    } catch {
    }
    cachedBoardFlipped = flipped;
    cachedFlipTimestamp = now;
    return flipped;
  }
  function invalidateGameCache() {
    cachedGame = null;
    cachedGameTimestamp = 0;
  }

  // src/board.js
  var boardCtx = null;
  var domObserver = null;
  var pendingMoveTimeoutId = null;
  function attachToBoard(boardEl) {
    invalidateGameCache();
    detachFromBoard();
    if (!boardEl) {
      console.warn("GabiBot: No board element to attach.");
      return;
    }
    if (getComputedStyle(boardEl).position === "static")
      boardEl.style.position = "relative";
    const drawingBoard = document.createElement("canvas");
    drawingBoard.id = "arrowCanvas";
    drawingBoard.style.cssText = "position:absolute;top:0;left:0;pointer-events:none;z-index:100;";
    const ctx = drawingBoard.getContext("2d");
    const evalBarWrap = document.createElement("div");
    evalBarWrap.id = "evaluationBarWrap";
    const whiteBar = document.createElement("div");
    whiteBar.id = "evaluationBarWhite";
    const blackBar = document.createElement("div");
    blackBar.id = "evaluationBarBlack";
    evalBarWrap.appendChild(whiteBar);
    evalBarWrap.appendChild(blackBar);
    boardEl.appendChild(evalBarWrap);
    boardEl.appendChild(drawingBoard);
    const resizeCanvas = () => {
      const rect = boardEl.getBoundingClientRect();
      drawingBoard.width = rect.width;
      drawingBoard.height = rect.height;
    };
    resizeCanvas();
    const ro = new ResizeObserver(resizeCanvas);
    ro.observe(boardEl);
    const cancelPendingOnUserAction = () => {
      if (pendingMoveTimeoutId) {
        clearTimeout(pendingMoveTimeoutId);
        pendingMoveTimeoutId = null;
        BotState.statusInfo = "Manual move in progress...";
        if (BotState.onUpdateDisplay)
          BotState.onUpdateDisplay(pa());
      }
    };
    const touchOpts = { passive: true, capture: true };
    boardEl.addEventListener("mousedown", cancelPendingOnUserAction, true);
    boardEl.addEventListener("touchstart", cancelPendingOnUserAction, touchOpts);
    boardCtx = {
      boardEl,
      drawingBoard,
      ctx,
      evalBarWrap,
      resizeObserver: ro,
      cancelPendingOnUserAction,
      touchOpts,
      detachListeners() {
        try {
          boardEl.removeEventListener("mousedown", cancelPendingOnUserAction, true);
        } catch {
        }
        try {
          boardEl.removeEventListener("touchstart", cancelPendingOnUserAction, touchOpts);
        } catch {
        }
        try {
          ro.disconnect();
        } catch {
        }
        try {
          drawingBoard.remove();
        } catch {
        }
        try {
          evalBarWrap.remove();
        } catch {
        }
      }
    };
    if (BotState.onUpdateDisplay)
      BotState.onUpdateDisplay(pa());
  }
  function detachFromBoard() {
    if (!boardCtx)
      return;
    try {
      boardCtx.detachListeners();
    } catch {
    }
    boardCtx = null;
  }
  function startDomBoardWatcher() {
    if (domObserver)
      try {
        domObserver.disconnect();
      } catch {
      }
    domObserver = new MutationObserver(debounce(() => {
      const newBoard = qs("chess-board") || qs(".board") || qs('[class*="board"]');
      if (!newBoard)
        return;
      if (!boardCtx || boardCtx.boardEl !== newBoard) {
        console.log("GabiBot: Board element changed, re-attaching.");
        attachToBoard(newBoard);
      }
    }, 200));
    domObserver.observe(document.body, { childList: true, subtree: true });
  }
  function clearArrows() {
    if (!boardCtx)
      return;
    const { drawingBoard, ctx } = boardCtx;
    ctx.clearRect(0, 0, drawingBoard.width, drawingBoard.height);
  }
  function getSquareCenterClientXY(square) {
    if (!boardCtx || !square || square.length < 2)
      return null;
    const file = "abcdefgh".indexOf(square[0]);
    const rank = parseInt(square[1], 10);
    if (file < 0 || isNaN(rank))
      return null;
    const el = boardCtx.boardEl;
    const rect = el.getBoundingClientRect();
    const size = Math.min(rect.width, rect.height);
    const tile = size / 8;
    const offsetX = rect.left + (rect.width - size) / 2;
    const offsetY = rect.top + (rect.height - size) / 2;
    let x = file, y = 8 - rank;
    if (isBoardFlipped()) {
      x = 7 - x;
      y = 7 - y;
    }
    return { x: offsetX + (x + 0.5) * tile, y: offsetY + (y + 0.5) * tile };
  }
  function getSquareCenterCanvasXY(square) {
    if (!boardCtx || !square || square.length < 2)
      return null;
    const p = getSquareCenterClientXY(square);
    if (!p)
      return null;
    const rect = boardCtx.boardEl.getBoundingClientRect();
    return { x: p.x - rect.left, y: p.y - rect.top };
  }
  function drawArrow(uciFrom, uciTo, color, thickness) {
    if (!boardCtx || !uciFrom || !uciTo || uciFrom.length < 2 || uciTo.length < 2)
      return;
    const { drawingBoard, ctx } = boardCtx;
    const a = getSquareCenterCanvasXY(uciFrom);
    const b = getSquareCenterCanvasXY(uciTo);
    if (!a || !b)
      return;
    const size = Math.min(drawingBoard.width, drawingBoard.height);
    const tile = size / 8;
    ctx.beginPath();
    ctx.moveTo(a.x, a.y);
    ctx.lineTo(b.x, b.y);
    ctx.lineWidth = thickness;
    ctx.strokeStyle = color;
    ctx.lineCap = "round";
    ctx.stroke();
    ctx.beginPath();
    ctx.arc(a.x, a.y, tile / 7, 0, 2 * Math.PI);
    ctx.fillStyle = color.replace("0.7", "0.3");
    ctx.fill();
    ctx.strokeStyle = color;
    ctx.lineWidth = 2;
    ctx.stroke();
    ctx.beginPath();
    ctx.arc(b.x, b.y, tile / 5, 0, 2 * Math.PI);
    ctx.fillStyle = color.replace("0.7", "0.5");
    ctx.fill();
    ctx.strokeStyle = color;
    ctx.lineWidth = 2;
    ctx.stroke();
  }
  function dispatchPointerOrMouse(el, type, opts, usePointer) {
    if (!el)
      return;
    if (usePointer) {
      try {
        el.dispatchEvent(new PointerEvent(type, { bubbles: true, cancelable: true, composed: true, ...opts }));
        return;
      } catch {
      }
    }
    el.dispatchEvent(new MouseEvent(type.replace("pointer", "mouse"), { bubbles: true, cancelable: true, composed: true, ...opts }));
  }
  function getTargetAt(x, y) {
    return document.elementFromPoint(x, y) || boardCtx?.boardEl || document.body;
  }
  async function simulateClickMove(from, to) {
    const a = getSquareCenterClientXY(from), b = getSquareCenterClientXY(to);
    if (!a || !b)
      return false;
    const usePointer = !!window.PointerEvent;
    const startEl = getTargetAt(a.x, a.y);
    const endEl = getTargetAt(b.x, b.y);
    const downStart = { clientX: a.x, clientY: a.y, pointerId: 1, pointerType: "mouse", isPrimary: true, buttons: 1 };
    const upStart = { clientX: a.x, clientY: a.y, pointerId: 1, pointerType: "mouse", isPrimary: true, buttons: 0 };
    const downEnd = { clientX: b.x, clientY: b.y, pointerId: 1, pointerType: "mouse", isPrimary: true, buttons: 1 };
    const upEnd = { clientX: b.x, clientY: b.y, pointerId: 1, pointerType: "mouse", isPrimary: true, buttons: 0 };
    dispatchPointerOrMouse(startEl, usePointer ? "pointerdown" : "mousedown", downStart, usePointer);
    await sleep(2);
    dispatchPointerOrMouse(startEl, usePointer ? "pointerup" : "mouseup", upStart, usePointer);
    startEl.dispatchEvent(new MouseEvent("click", { bubbles: true, cancelable: true, composed: true, clientX: a.x, clientY: a.y }));
    await sleep(4);
    dispatchPointerOrMouse(endEl, usePointer ? "pointerdown" : "mousedown", downEnd, usePointer);
    await sleep(2);
    dispatchPointerOrMouse(endEl, usePointer ? "pointerup" : "mouseup", upEnd, usePointer);
    endEl.dispatchEvent(new MouseEvent("click", { bubbles: true, cancelable: true, composed: true, clientX: b.x, clientY: b.y }));
    return true;
  }
  async function waitForFenChange(prevFen, timeout = 1e3) {
    return new Promise((resolve) => {
      const start = performance.now();
      const check = () => {
        const g = getGame();
        const fen = g?.getFEN ? g.getFEN() : null;
        if (fen && fen !== prevFen)
          return resolve(true);
        if (performance.now() - start > timeout)
          return resolve(false);
        requestAnimationFrame(check);
      };
      requestAnimationFrame(check);
    });
  }
  async function maybeSelectPromotion(prefer = "q") {
    const preferList = (prefer ? [prefer] : ["q", "r", "b", "n"]).map((c) => c.toLowerCase());
    const getCandidates = () => qsa('[data-test-element*="promotion"], [class*="promotion"] [class*="piece"], [class*="promotion"] button, .promotion-piece');
    const tryClick = (el) => {
      try {
        el.click?.();
        el.dispatchEvent(new MouseEvent("mousedown", { bubbles: true, cancelable: true }));
        el.dispatchEvent(new MouseEvent("mouseup", { bubbles: true, cancelable: true }));
        el.dispatchEvent(new MouseEvent("click", { bubbles: true, cancelable: true }));
        return true;
      } catch {
        return false;
      }
    };
    const start = Date.now();
    while (Date.now() - start < 1e3) {
      const nodes = getCandidates();
      if (nodes.length) {
        for (const pref of preferList) {
          const match = nodes.find(
            (n) => (n.dataset?.piece?.toLowerCase?.() || "") === pref || (n.getAttribute?.("data-piece") || "").toLowerCase() === pref || (n.getAttribute?.("aria-label") || "").toLowerCase().includes(pref) || (n.className || "").toLowerCase().includes(pref) || (n.textContent || "").toLowerCase().includes(pref)
          );
          if (match && tryClick(match))
            return true;
        }
        if (tryClick(nodes[0]))
          return true;
      }
      await sleep(60);
    }
    return false;
  }
  function cancelPendingMove() {
    if (pendingMoveTimeoutId) {
      clearTimeout(pendingMoveTimeoutId);
      pendingMoveTimeoutId = null;
    }
  }
  async function makeMove(from, to, expectedFen, promotionChar) {
    const game = getGame();
    if (!game || !BotState.autoMove)
      return false;
    const beforeFen = getFen(game);
    if (!beforeFen || beforeFen !== expectedFen || !isPlayersTurn(game))
      return false;
    await simulateClickMove(from, to);
    if (promotionChar)
      await maybeSelectPromotion(String(promotionChar).toLowerCase());
    const changed = await waitForFenChange(beforeFen, 400);
    return !!changed;
  }
  function executeMove(from, to, analysisFen, promotionChar, tickCallback) {
    if (BotState.hackEnabled && BotState.autoMove) {
      const game = getGame();
      if (!game || !isPlayersTurn(game)) {
        BotState.statusInfo = "Waiting for opponent...";
        if (BotState.onUpdateDisplay)
          BotState.onUpdateDisplay(pa());
        return;
      }
      cancelPendingMove();
      const baseDelay = Math.max(0, AUTO_MOVE_BASE - BotState.autoMoveSpeed * AUTO_MOVE_STEP);
      const totalDelay = getHumanDelay(baseDelay, BotState.randomDelay);
      console.log(`GabiBot: Delay ${totalDelay}ms`);
      BotState.statusInfo = `Moving in ${(totalDelay / 1e3).toFixed(1)}s`;
      if (BotState.onUpdateDisplay)
        BotState.onUpdateDisplay(pa());
      pendingMoveTimeoutId = setTimeout(async () => {
        const g = getGame();
        if (!g)
          return;
        if (!isPlayersTurn(g)) {
          BotState.statusInfo = "Move canceled (not our turn)";
          if (BotState.onUpdateDisplay)
            BotState.onUpdateDisplay(pa());
          return;
        }
        if (getFen(g) !== analysisFen) {
          BotState.statusInfo = "Move canceled (position changed)";
          if (BotState.onUpdateDisplay)
            BotState.onUpdateDisplay(pa());
          return;
        }
        BotState.statusInfo = "Making move...";
        if (BotState.onUpdateDisplay)
          BotState.onUpdateDisplay(pa());
        const success = await makeMove(from, to, analysisFen, promotionChar);
        BotState.statusInfo = success ? "\u2713 Move made!" : "\u274C Move failed";
        if (BotState.onUpdateDisplay)
          BotState.onUpdateDisplay(pa());
        if (!success) {
          setTimeout(() => {
            if (BotState.hackEnabled && isPlayersTurn(getGame())) {
              if (tickCallback)
                tickCallback();
            }
          }, 250);
        }
      }, totalDelay);
    } else {
      BotState.statusInfo = "Ready (manual)";
      if (BotState.onUpdateDisplay)
        BotState.onUpdateDisplay(pa());
    }
  }
  function updateEvaluationBar(evaluation, playingAs) {
    if (!boardCtx)
      return;
    const whiteBar = boardCtx.evalBarWrap.querySelector("#evaluationBarWhite");
    const blackBar = boardCtx.evalBarWrap.querySelector("#evaluationBarBlack");
    if (!whiteBar || !blackBar)
      return;
    let score = 0;
    if (typeof evaluation === "string") {
      if (evaluation === "-" || evaluation === "Error") {
        whiteBar.style.height = "50%";
        blackBar.style.height = "50%";
        return;
      }
      if (evaluation.includes("M")) {
        const m = parseInt(evaluation.replace("M", "").replace("+", ""), 10);
        score = m > 0 ? 10 : -10;
      } else {
        score = parseFloat(evaluation);
      }
    } else {
      score = parseFloat(evaluation);
    }
    if (isNaN(score)) {
      whiteBar.style.height = "50%";
      blackBar.style.height = "50%";
      return;
    }
    const maxScore = 5;
    const clampedScore = Math.max(-maxScore, Math.min(maxScore, score));
    const whitePercent = 50 + clampedScore / maxScore * 50;
    const blackPercent = 100 - whitePercent;
    whiteBar.style.height = `${whitePercent}%`;
    blackBar.style.height = `${blackPercent}%`;
    const ourColor = getPlayerColor(getGame());
    const ourEval = ourColor === "w" ? score : -score;
    if (ourEval < -2) {
      boardCtx.evalBarWrap.style.borderColor = "rgba(255, 100, 100, 0.5)";
    } else if (ourEval > 2) {
      boardCtx.evalBarWrap.style.borderColor = "rgba(100, 255, 100, 0.5)";
    } else {
      boardCtx.evalBarWrap.style.borderColor = "rgba(255, 255, 255, 0.2)";
    }
  }

  // src/ui.js
  var ui = {
    menuWrap: null,
    setText(name, value, title) {
      if (!this.menuWrap)
        return;
      const el = this.menuWrap.querySelector(`[name="${name}"]`);
      if (!el)
        return;
      const state = el.querySelector(".itemState") || el.children[el.children.length - 1];
      if (state) {
        state.textContent = value ?? "-";
        if (title)
          state.title = title;
      }
    },
    updateDisplay(playingAs) {
      this.setText("currentEvaluation", BotState.currentEvaluation);
      this.setText("bestMove", BotState.bestMove);
      this.setText("pvDisplay", BotState.principalVariation, BotState.principalVariation);
      this.setText("statusInfo", BotState.statusInfo);
      const chanceEl = this.menuWrap?.querySelector('[name="premoveChance"] .itemState');
      if (chanceEl && BotState.currentPremoveChance !== void 0) {
        const pct = `${Math.round(BotState.currentPremoveChance)}%`;
        const reasons = BotState.currentPremoveReasons;
        chanceEl.textContent = reasons ? `${pct} (${reasons})` : pct;
        chanceEl.title = reasons || "Premove confidence";
      }
      updateEvaluationBar(BotState.currentEvaluation, playingAs);
    },
    Settings
  };
  function buildUI() {
    const menuWrap = document.createElement("div");
    menuWrap.id = "menuWrap";
    const menuWrapStyle = document.createElement("style");
    menuWrap.innerHTML = `
  <div id="topText">
    <a id="modTitle">\u265F GabiBot</a>
    <button id="minimizeBtn" title="Minimize (Ctrl+B)">\u2500</button>
  </div>
  <div id="itemsList">
    <div name="enableHack" class="listItem">
      <input class="checkboxMod" type="checkbox">
      <a class="itemDescription">Enable Bot</a>
      <a class="itemState">Off</a>
    </div>
    <div name="autoMove" class="listItem">
      <input class="checkboxMod" type="checkbox">
      <a class="itemDescription">Auto Move</a>
      <a class="itemState">Off</a>
    </div>

    <div class="divider"></div>

    <div name="premoveEnabled" class="listItem">
      <input class="checkboxMod" type="checkbox">
      <a class="itemDescription">Premove System</a>
      <a class="itemState">Off</a>
    </div>
    <div name="premoveMode" class="listItem select-row">
      <a class="itemDescription">Premove Mode</a>
      <select class="selectMod">
        <option value="every">Every next move</option>
        <option value="capture">Only if capture</option>
        <option value="filter">By piece filters</option>
      </select>
    </div>
    <div name="premoveChance" class="listItem info-item">
      <a class="itemDescription">Premove Chance:</a>
      <a class="itemState">0%</a>
    </div>
    <div name="premovePieces" class="listItem">
      <div class="pieceFilters">
        <label class="chip"><input type="checkbox" data-piece="q" checked><span>Q</span></label>
        <label class="chip"><input type="checkbox" data-piece="r" checked><span>R</span></label>
        <label class="chip"><input type="checkbox" data-piece="b" checked><span>B</span></label>
        <label class="chip"><input type="checkbox" data-piece="n" checked><span>N</span></label>
        <label class="chip"><input type="checkbox" data-piece="k" checked><span>K</span></label>
        <label class="chip"><input type="checkbox" data-piece="p" checked><span>P</span></label>
      </div>
      <a class="itemDescription">Pieces</a>
      <a class="itemState">-</a>
    </div>

    <div class="divider"></div>

    <div name="autoRematch" class="listItem">
      <input class="checkboxMod" type="checkbox">
      <a class="itemDescription">Auto Rematch</a>
      <a class="itemState">Off</a>
    </div>

    <div class="divider"></div>

    <div name="botPower" class="listItem">
      <input min="1" max="15" value="10" class="rangeSlider" type="range">
      <a class="itemDescription">Depth</a>
      <a class="itemState">12</a>
    </div>
    <div name="autoMoveSpeed" class="listItem">
      <input min="1" max="10" value="8" class="rangeSlider" type="range">
      <a class="itemDescription">Move Speed</a>
      <a class="itemState">4</a>
    </div>
    <div name="randomDelay" class="listItem">
      <input min="120" max="2000" value="300" class="rangeSlider" type="range">
      <a class="itemDescription">Random Delay (ms)</a>
      <a class="itemState">1000</a>
    </div>
    <div name="updateSpeed" class="listItem">
      <input min="1" max="10" value="8" class="rangeSlider" type="range">
      <a class="itemDescription">Update Rate</a>
      <a class="itemState">8</a>
    </div>

    <div class="divider"></div>

    <div name="currentEvaluation" class="listItem info-item">
      <a class="itemDescription">Eval:</a>
      <a class="itemState eval-value">-</a>
    </div>
    <div name="bestMove" class="listItem info-item">
      <a class="itemDescription">Best:</a>
      <a class="itemState">-</a>
    </div>
    <div name="pvDisplay" class="listItem info-item">
      <a class="itemDescription">PV:</a>
      <a class="itemState pv-text-state" title="Principal Variation">-</a>
    </div>
    <div name="statusInfo" class="listItem info-item">
      <a class="itemDescription">Status:</a>
      <a class="itemState status-text">Ready</a>
    </div>
  </div>
`;
    menuWrapStyle.innerHTML = `
  #menuWrap {
    font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
    border-radius: 8px;
    z-index: 9999999;
    display: grid;
    grid-template-rows: auto 1fr;
    width: 300px; max-height: 550px;
    position: fixed;
    border: 1px solid rgba(255, 255, 255, 0.1);
    background: rgba(20, 20, 20, 0.95);
    backdrop-filter: blur(10px);
    box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4);
    user-select: none;
    top: 20px; right: 20px;
    transition: opacity 0.3s ease, transform 0.3s ease;
  }
  #menuWrap.minimized { grid-template-rows: auto 0fr; max-height: 50px; }
  #menuWrap.minimized #itemsList { overflow: hidden; opacity: 0; }
  #menuWrap.grabbing { cursor: grabbing !important; opacity: 0.9; }
  .divider { height: 1px; background: rgba(255, 255, 255, 0.1); margin: 10px 0; }
  .pv-text-state { color: #aaa !important; font-size: 11px; max-width: 150px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .eval-value { font-weight: bold; font-size: 14px; }
  .status-text { color: #4CAF50 !important; font-size: 11px; }
  .info-item { opacity: 0.8; margin-bottom: 8px !important; }
  #evaluationBarWrap {
    position: absolute;
    height: 100%;
    width: 20px;
    left: -28px;
    top: 0;
    background: #000;
    z-index: 50;
    border-radius: 6px;
    overflow: hidden;
    border: 1px solid rgba(255, 255, 255, 0.2);
  }
  #evaluationBarWhite { position: absolute; top: 0; left: 0; right: 0; background: #f0d9b5; transition: height 0.3s ease; }
  #evaluationBarBlack { position: absolute; bottom: 0; left: 0; right: 0; background: #000; transition: height 0.3s ease; }
  #topText { display: flex; justify-content: space-between; align-items: center; padding: 12px 16px;
    background: rgba(255, 255, 255, 0.05); border-bottom: 1px solid rgba(255, 255, 255, 0.1); cursor: move; }
  #modTitle { color: #fff; font-size: 16px; font-weight: 600; letter-spacing: 0.5px; }
  #minimizeBtn { background: rgba(255, 255, 255, 0.1); border: none; color: #fff; width: 24px; height: 24px;
    border-radius: 4px; cursor: pointer; font-size: 14px; transition: background 0.2s; }
  #minimizeBtn:hover { background: rgba(255, 255, 255, 0.2); }
  #itemsList { overflow-y: auto; overflow-x: hidden; padding: 12px 16px; transition: opacity 0.3s ease; }
  ::-webkit-scrollbar { width: 6px; }
  ::-webkit-scrollbar-track { background: rgba(255, 255, 255, 0.05); }
  ::-webkit-scrollbar-thumb { background: rgba(255, 255, 255, 0.2); border-radius: 3px; }
  ::-webkit-scrollbar-thumb:hover { background: rgba(255, 255, 255, 0.3); }
  .listItem { display: flex; align-items: center; margin-bottom: 12px; gap: 8px; }
  .listItem.select-row { display: grid; grid-template-columns: 1fr 1.2fr; gap: 10px; align-items: center; }
  .listItem.select-row .itemDescription { color: rgba(255, 255, 255, 0.85); font-weight: 500; }
  .checkboxMod { appearance: none; width: 18px; height: 18px; border: 2px solid rgba(255, 255, 255, 0.3); border-radius: 4px;
    background: rgba(255, 255, 255, 0.05); cursor: pointer; position: relative; transition: all 0.2s; flex-shrink: 0; }
  .checkboxMod:checked { background: #4CAF50; border-color: #4CAF50; }
  .checkboxMod:checked::after { content: "\u2713"; position: absolute; color: white; font-size: 12px; top: 50%; left: 50%; transform: translate(-50%, -50%); }
  .rangeSlider { -webkit-appearance: none; flex: 1; height: 4px; border-radius: 2px; background: rgba(255, 255, 255, 0.1); outline: none; }
  .rangeSlider::-webkit-slider-thumb { -webkit-appearance: none; width: 14px; height: 14px; border-radius: 50%; background: #4CAF50; cursor: pointer; transition: transform 0.2s; }
  .rangeSlider::-webkit-slider-thumb:hover { transform: scale(1.2); }
  .itemDescription { color: rgba(255, 255, 255, 0.7); font-size: 12px; flex: 1; }
  .itemState { color: #fff; font-size: 12px; min-width: 35px; text-align: right; font-weight: 500; }
  #arrowCanvas { position: absolute !important; top: 0 !important; left: 0 !important; width: 100% !important; height: 100% !important; pointer-events: none !important; z-index: 100 !important; }
  .selectMod {
    appearance: none;
    background: rgba(255,255,255,0.08);
    border: 1px solid rgba(255,255,255,0.2);
    color: #fff;
    border-radius: 6px;
    padding: 6px 28px 6px 10px;
    flex: 1;
    outline: none;
    cursor: pointer;
    font-size: 12px;
    font-family: inherit;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%23fff' d='M6 9L1 4h10z'/%3E%3C/svg%3E");
    background-repeat: no-repeat;
    background-position: right 8px center;
    transition: all 0.2s ease;
  }
  .selectMod:hover { background-color: rgba(255,255,255,0.12); border-color: rgba(255,255,255,0.3); }
  .selectMod:focus { background-color: rgba(255,255,255,0.1); border-color: #4CAF50; box-shadow: 0 0 0 2px rgba(76, 175, 80, 0.2); }
  .selectMod option { background: #1a1a1a; color: #fff; padding: 8px; }
  .pieceFilters { display: flex; flex-wrap: wrap; gap: 6px; }
  .pieceFilters .chip {
    user-select: none; display: inline-flex; align-items: center; gap: 6px;
    padding: 5px 10px; background: rgba(255,255,255,0.08);
    border: 1px solid rgba(255,255,255,0.2); border-radius: 999px; cursor: pointer;
    color: rgba(255,255,255,0.7); transition: all 0.2s ease;
    font-size: 11px; font-weight: 500;
  }
  .pieceFilters .chip:hover { background: rgba(255,255,255,0.12); border-color: rgba(255,255,255,0.3); }
  .pieceFilters .chip input { appearance: none; width: 14px; height: 14px; border-radius: 3px; border: 2px solid rgba(255,255,255,0.4); background: rgba(255,255,255,0.05); transition: all 0.2s ease; }
  .pieceFilters .chip input:checked { background: #4CAF50; border-color: #4CAF50; }
  .pieceFilters .chip input:checked::after { content: "\u2713"; color: white; font-size: 9px; display: flex; align-items: center; justify-content: center; height: 100%; }
  .pieceFilters .chip input:checked + span { color: #fff; font-weight: 600; }
`;
    document.body.appendChild(menuWrap);
    document.body.appendChild(menuWrapStyle);
    ui.menuWrap = menuWrap;
    const saved = Settings.load();
    if (saved?.menuPosition) {
      menuWrap.style.top = saved.menuPosition.top || "20px";
      menuWrap.style.left = saved.menuPosition.left || "";
      menuWrap.style.right = saved.menuPosition.left ? "auto" : "20px";
    }
    const getElementByName = (name, el) => el.querySelector(`[name="${name}"]`);
    const getInputElement = (el) => el.children[0];
    const getStateElement = (el) => el.children[el.children.length - 1];
    function bindControl(name, type, variable) {
      const modElement = getElementByName(name, menuWrap);
      if (!modElement)
        return;
      const modState = getStateElement(modElement);
      const modInput = getInputElement(modElement);
      const key = variable.replace("BotState.", "");
      if (type === "checkbox") {
        modInput.checked = !!BotState[key];
        modState.textContent = BotState[key] ? "On" : "Off";
        modInput.addEventListener("input", () => {
          BotState[key] = modInput.checked ? 1 : 0;
          modState.textContent = BotState[key] ? "On" : "Off";
          Settings.save();
        });
      } else if (type === "range") {
        modInput.value = BotState[key];
        modState.textContent = BotState[key];
        modInput.addEventListener("input", () => {
          let value = parseInt(modInput.value, 10);
          const min = parseInt(modInput.min, 10);
          const max = parseInt(modInput.max, 10);
          value = Math.max(min, Math.min(max, value));
          BotState[key] = value;
          modInput.value = value;
          modState.textContent = value;
          Settings.save();
        });
      }
    }
    function bindSelect(name, variable) {
      const el = getElementByName(name, menuWrap);
      if (!el)
        return;
      const select = el.querySelector("select");
      const key = variable.replace("BotState.", "");
      select.value = BotState[key];
      select.addEventListener("change", () => {
        BotState[key] = select.value;
        refreshPremoveUIVisibility();
        Settings.save();
      });
    }
    function bindPieceFilters() {
      const el = getElementByName("premovePieces", menuWrap);
      if (!el)
        return;
      const checks = qsa('.pieceFilters input[type="checkbox"]', el);
      checks.forEach((chk) => {
        const p = String(chk.dataset.piece || "").toLowerCase();
        chk.checked = !!BotState.premovePieces[p];
      });
      checks.forEach((chk) => {
        chk.addEventListener("input", () => {
          const p = String(chk.dataset.piece || "").toLowerCase();
          BotState.premovePieces[p] = chk.checked ? 1 : 0;
          Settings.save();
        });
      });
    }
    function refreshPremoveUIVisibility() {
      const row = getElementByName("premovePieces", menuWrap);
      if (row)
        row.style.display = BotState.premoveMode === "filter" ? "flex" : "none";
    }
    bindControl("enableHack", "checkbox", "BotState.hackEnabled");
    bindControl("autoMove", "checkbox", "BotState.autoMove");
    bindControl("botPower", "range", "BotState.botPower");
    bindControl("autoMoveSpeed", "range", "BotState.autoMoveSpeed");
    bindControl("updateSpeed", "range", "BotState.updateSpeed");
    bindControl("randomDelay", "range", "BotState.randomDelay");
    bindControl("premoveEnabled", "checkbox", "BotState.premoveEnabled");
    bindSelect("premoveMode", "BotState.premoveMode");
    bindPieceFilters();
    refreshPremoveUIVisibility();
    bindControl("autoRematch", "checkbox", "BotState.autoRematch");
    makePanelDraggable(menuWrap);
    document.getElementById("minimizeBtn").addEventListener("click", () => menuWrap.classList.toggle("minimized"));
    document.addEventListener("keydown", (e) => {
      if (e.key === "b" && e.ctrlKey) {
        e.preventDefault();
        menuWrap.classList.toggle("minimized");
      }
    });
  }
  function makePanelDraggable(panel) {
    function clampToViewport() {
      const rect = panel.getBoundingClientRect();
      const vw = window.innerWidth;
      const vh = window.innerHeight;
      const margin = 8;
      panel.style.right = "auto";
      let left = parseFloat(panel.style.left || rect.left);
      let top = parseFloat(panel.style.top || rect.top);
      left = Math.max(margin, Math.min(left, vw - rect.width - margin));
      top = Math.max(margin, Math.min(top, vh - rect.height - margin));
      panel.style.left = left + "px";
      panel.style.top = top + "px";
    }
    function allowDragFromTarget(target, e) {
      if (e.altKey)
        return true;
      const rect = panel.getBoundingClientRect();
      const m = 14;
      const nearEdge = e.clientX <= rect.left + m || e.clientX >= rect.right - m || e.clientY <= rect.top + m || e.clientY >= rect.bottom - m;
      if (nearEdge)
        return true;
      if (target.closest("input, select, textarea, button, label, a"))
        return false;
      return true;
    }
    function startDrag(e) {
      e.preventDefault();
      const startRect = panel.getBoundingClientRect();
      panel.classList.add("grabbing");
      panel.style.right = "auto";
      panel.style.left = startRect.left + "px";
      panel.style.top = startRect.top + "px";
      const startX = e.clientX;
      const startY = e.clientY;
      const move = (ev) => {
        const dx = ev.clientX - startX;
        const dy = ev.clientY - startY;
        const vw = window.innerWidth;
        const vh = window.innerHeight;
        let newLeft = startRect.left + dx;
        let newTop = startRect.top + dy;
        const margin = 8;
        const maxLeft = Math.max(margin, vw - startRect.width - margin);
        const maxTop = Math.max(margin, vh - startRect.height - margin);
        newLeft = Math.min(Math.max(newLeft, margin), maxLeft);
        newTop = Math.min(Math.max(newTop, margin), maxTop);
        panel.style.left = newLeft + "px";
        panel.style.top = newTop + "px";
      };
      const up = () => {
        document.removeEventListener("mousemove", move);
        document.removeEventListener("mouseup", up);
        panel.classList.remove("grabbing");
        try {
          Settings.save();
        } catch {
        }
      };
      document.addEventListener("mousemove", move);
      document.addEventListener("mouseup", up);
    }
    panel.addEventListener("mousedown", (e) => {
      if (e.button !== 0)
        return;
      if (!allowDragFromTarget(e.target, e))
        return;
      startDrag(e);
    });
    window.addEventListener("resize", clampToViewport);
    setTimeout(clampToViewport, 50);
  }

  // src/engine.js
  var currentAnalysisId = 0;
  var currentAbortController = null;
  var analysisRunning = false;
  var lastFenProcessedMain = "";
  var lastFenProcessedPremove = "";
  var lastPremoveFen = "";
  var lastPremoveUci = "";
  function getLastFenProcessedMain() {
    return lastFenProcessedMain;
  }
  function setLastFenProcessedMain(fen) {
    lastFenProcessedMain = fen;
  }
  function getLastFenProcessedPremove() {
    return lastFenProcessedPremove;
  }
  function setLastFenProcessedPremove(fen) {
    lastFenProcessedPremove = fen;
  }
  function getLastPremoveFen() {
    return lastPremoveFen;
  }
  function setLastPremoveFen(fen) {
    lastPremoveFen = fen;
  }
  function getLastPremoveUci() {
    return lastPremoveUci;
  }
  function setLastPremoveUci(uci) {
    lastPremoveUci = uci;
  }
  var WP = 1;
  var WN = 2;
  var WB = 3;
  var WR = 4;
  var WQ = 5;
  var WK = 6;
  var BP = -1;
  var BN = -2;
  var BB = -3;
  var BR = -4;
  var BQ = -5;
  var BK = -6;
  var EMPTY = 0;
  var FLAG_NONE = 0;
  var FLAG_EP = 1;
  var FLAG_CASTLE = 2;
  var FLAG_PROMO = 4;
  var MATE_SCORE = 3e4;
  var TT_EXACT = 0;
  var TT_ALPHA = 1;
  var TT_BETA = 2;
  var TT_SIZE = 65536;
  var PST_PAWN = [
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    // rank 1
    5,
    10,
    10,
    -20,
    -20,
    10,
    10,
    5,
    // rank 2
    5,
    -5,
    -10,
    0,
    0,
    -10,
    -5,
    5,
    // rank 3
    0,
    0,
    0,
    20,
    20,
    0,
    0,
    0,
    // rank 4
    5,
    5,
    10,
    25,
    25,
    10,
    5,
    5,
    // rank 5
    10,
    10,
    20,
    30,
    30,
    20,
    10,
    10,
    // rank 6
    50,
    50,
    50,
    50,
    50,
    50,
    50,
    50,
    // rank 7
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0
    // rank 8 (never has pawns)
  ];
  var PST_KNIGHT = [
    -50,
    -40,
    -30,
    -30,
    -30,
    -30,
    -40,
    -50,
    -40,
    -20,
    0,
    5,
    5,
    0,
    -20,
    -40,
    -30,
    5,
    10,
    15,
    15,
    10,
    5,
    -30,
    -30,
    0,
    15,
    20,
    20,
    15,
    0,
    -30,
    -30,
    5,
    15,
    20,
    20,
    15,
    5,
    -30,
    -30,
    0,
    10,
    15,
    15,
    10,
    0,
    -30,
    -40,
    -20,
    0,
    0,
    0,
    0,
    -20,
    -40,
    -50,
    -40,
    -30,
    -30,
    -30,
    -30,
    -40,
    -50
  ];
  var PST_BISHOP = [
    -20,
    -10,
    -10,
    -10,
    -10,
    -10,
    -10,
    -20,
    -10,
    5,
    0,
    0,
    0,
    0,
    5,
    -10,
    -10,
    10,
    10,
    10,
    10,
    10,
    10,
    -10,
    -10,
    0,
    10,
    10,
    10,
    10,
    0,
    -10,
    -10,
    5,
    5,
    10,
    10,
    5,
    5,
    -10,
    -10,
    0,
    5,
    10,
    10,
    5,
    0,
    -10,
    -10,
    0,
    0,
    0,
    0,
    0,
    0,
    -10,
    -20,
    -10,
    -10,
    -10,
    -10,
    -10,
    -10,
    -20
  ];
  var PST_ROOK = [
    0,
    0,
    0,
    5,
    5,
    0,
    0,
    0,
    -5,
    0,
    0,
    0,
    0,
    0,
    0,
    -5,
    -5,
    0,
    0,
    0,
    0,
    0,
    0,
    -5,
    -5,
    0,
    0,
    0,
    0,
    0,
    0,
    -5,
    -5,
    0,
    0,
    0,
    0,
    0,
    0,
    -5,
    -5,
    0,
    0,
    0,
    0,
    0,
    0,
    -5,
    5,
    10,
    10,
    10,
    10,
    10,
    10,
    5,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0
  ];
  var PST_QUEEN = [
    -20,
    -10,
    -10,
    -5,
    -5,
    -10,
    -10,
    -20,
    -10,
    0,
    5,
    0,
    0,
    0,
    0,
    -10,
    -10,
    5,
    5,
    5,
    5,
    5,
    0,
    -10,
    0,
    0,
    5,
    5,
    5,
    5,
    0,
    -5,
    -5,
    0,
    5,
    5,
    5,
    5,
    0,
    -5,
    -10,
    0,
    5,
    5,
    5,
    5,
    0,
    -10,
    -10,
    0,
    0,
    0,
    0,
    0,
    0,
    -10,
    -20,
    -10,
    -10,
    -5,
    -5,
    -10,
    -10,
    -20
  ];
  var PST_KING_MG = [
    20,
    30,
    10,
    0,
    0,
    10,
    30,
    20,
    20,
    20,
    0,
    0,
    0,
    0,
    20,
    20,
    -10,
    -20,
    -20,
    -20,
    -20,
    -20,
    -20,
    -10,
    -20,
    -30,
    -30,
    -40,
    -40,
    -30,
    -30,
    -20,
    -30,
    -40,
    -40,
    -50,
    -50,
    -40,
    -40,
    -30,
    -30,
    -40,
    -40,
    -50,
    -50,
    -40,
    -40,
    -30,
    -30,
    -40,
    -40,
    -50,
    -50,
    -40,
    -40,
    -30,
    -30,
    -40,
    -40,
    -50,
    -50,
    -40,
    -40,
    -30
  ];
  var PST_KING_EG = [
    -50,
    -30,
    -30,
    -30,
    -30,
    -30,
    -30,
    -50,
    -30,
    -30,
    0,
    0,
    0,
    0,
    -30,
    -30,
    -30,
    -10,
    20,
    30,
    30,
    20,
    -10,
    -30,
    -30,
    -10,
    30,
    40,
    40,
    30,
    -10,
    -30,
    -30,
    -10,
    30,
    40,
    40,
    30,
    -10,
    -30,
    -30,
    -10,
    20,
    30,
    30,
    20,
    -10,
    -30,
    -30,
    -20,
    -10,
    0,
    0,
    -10,
    -20,
    -30,
    -50,
    -40,
    -30,
    -20,
    -20,
    -30,
    -40,
    -50
  ];
  var PST = {
    [WP]: PST_PAWN,
    [WN]: PST_KNIGHT,
    [WB]: PST_BISHOP,
    [WR]: PST_ROOK,
    [WQ]: PST_QUEEN
  };
  var PIECE_VAL = { 1: 100, 2: 320, 3: 330, 4: 500, 5: 900, 6: 0 };
  function mirrorSq(sq) {
    return (7 - (sq >> 3)) * 8 + (sq & 7);
  }
  function sqFile(sq) {
    return sq & 7;
  }
  function sqRank(sq) {
    return sq >> 3;
  }
  function sqName(sq) {
    return "abcdefgh"[sqFile(sq)] + (sqRank(sq) + 1);
  }
  function nameToSq(s) {
    return s.charCodeAt(0) - 97 + (s.charCodeAt(1) - 49) * 8;
  }
  var LocalEngine = class {
    constructor() {
      this.board = new Array(64).fill(EMPTY);
      this.side = 1;
      this.castling = 0;
      this.epSquare = -1;
      this.halfmove = 0;
      this.fullmove = 1;
      this.wKingSq = 4;
      this.bKingSq = 60;
      this.stateStack = [];
      this.nodes = 0;
      this.timeLimit = 0;
      this.startTime = 0;
      this.stopped = false;
      this.pvTable = [];
      this.killers = [];
      this.history = new Int32Array(64 * 64);
      this.tt = /* @__PURE__ */ new Map();
    }
    loadFen(fen) {
      this.board.fill(EMPTY);
      const parts = fen.split(" ");
      const rows = parts[0].split("/");
      const pieceMap = { p: BP, n: BN, b: BB, r: BR, q: BQ, k: BK, P: WP, N: WN, B: WB, R: WR, Q: WQ, K: WK };
      for (let r = 0; r < 8; r++) {
        let f = 0;
        for (const ch of rows[7 - r]) {
          if (ch >= "1" && ch <= "8") {
            f += parseInt(ch);
          } else {
            const piece = pieceMap[ch] || EMPTY;
            this.board[r * 8 + f] = piece;
            if (piece === WK)
              this.wKingSq = r * 8 + f;
            else if (piece === BK)
              this.bKingSq = r * 8 + f;
            f++;
          }
        }
      }
      this.side = (parts[1] || "w") === "w" ? 1 : -1;
      const c = parts[2] || "-";
      this.castling = (c.includes("K") ? 1 : 0) | (c.includes("Q") ? 2 : 0) | (c.includes("k") ? 4 : 0) | (c.includes("q") ? 8 : 0);
      this.epSquare = parts[3] && parts[3] !== "-" ? nameToSq(parts[3]) : -1;
      this.halfmove = parseInt(parts[4]) || 0;
      this.fullmove = parseInt(parts[5]) || 1;
      this.stateStack = [];
    }
    toFen() {
      let fen = "";
      for (let r = 7; r >= 0; r--) {
        let empty = 0;
        for (let f = 0; f < 8; f++) {
          const p = this.board[r * 8 + f];
          if (p === EMPTY) {
            empty++;
          } else {
            if (empty) {
              fen += empty;
              empty = 0;
            }
            const abs = Math.abs(p);
            const ch = "xpnbrqk"[abs];
            fen += p > 0 ? ch.toUpperCase() : ch;
          }
        }
        if (empty)
          fen += empty;
        if (r > 0)
          fen += "/";
      }
      let c = "";
      if (this.castling & 1)
        c += "K";
      if (this.castling & 2)
        c += "Q";
      if (this.castling & 4)
        c += "k";
      if (this.castling & 8)
        c += "q";
      if (!c)
        c = "-";
      const ep = this.epSquare >= 0 ? sqName(this.epSquare) : "-";
      return `${fen} ${this.side === 1 ? "w" : "b"} ${c} ${ep} ${this.halfmove} ${this.fullmove}`;
    }
    createMove(from, to, flags = FLAG_NONE, promo = EMPTY) {
      return {
        from,
        to,
        flags,
        piece: this.board[from],
        captured: flags & FLAG_EP ? -this.side : this.board[to],
        promo
      };
    }
    makeMove(mv) {
      this.stateStack.push({
        castling: this.castling,
        epSquare: this.epSquare,
        halfmove: this.halfmove,
        fullmove: this.fullmove,
        wKingSq: this.wKingSq,
        bKingSq: this.bKingSq
      });
      const { from, to, flags, piece, promo } = mv;
      const abs = Math.abs(piece);
      this.board[from] = EMPTY;
      this.board[to] = flags & FLAG_PROMO ? promo : piece;
      if (abs === 6) {
        if (this.side === 1)
          this.wKingSq = to;
        else
          this.bKingSq = to;
      }
      if (flags & FLAG_EP) {
        this.board[to - this.side * 8] = EMPTY;
      }
      if (flags & FLAG_CASTLE) {
        if (to > from) {
          this.board[from + 3] = EMPTY;
          this.board[from + 1] = this.side * WR;
        } else {
          this.board[from - 4] = EMPTY;
          this.board[from - 1] = this.side * WR;
        }
      }
      if (abs === 1 && Math.abs(to - from) === 16) {
        this.epSquare = from + to >> 1;
      } else {
        this.epSquare = -1;
      }
      if (abs === 6) {
        if (this.side === 1)
          this.castling &= ~3;
        else
          this.castling &= ~12;
      }
      if (from === 0 || to === 0)
        this.castling &= ~2;
      if (from === 7 || to === 7)
        this.castling &= ~1;
      if (from === 56 || to === 56)
        this.castling &= ~8;
      if (from === 63 || to === 63)
        this.castling &= ~4;
      this.halfmove = abs === 1 || mv.captured !== EMPTY ? 0 : this.halfmove + 1;
      if (this.side === -1)
        this.fullmove++;
      this.side = -this.side;
    }
    unmakeMove(mv) {
      this.side = -this.side;
      const st = this.stateStack.pop();
      this.castling = st.castling;
      this.epSquare = st.epSquare;
      this.halfmove = st.halfmove;
      this.fullmove = st.fullmove;
      this.wKingSq = st.wKingSq;
      this.bKingSq = st.bKingSq;
      const { from, to, flags, piece, captured, promo } = mv;
      this.board[from] = piece;
      this.board[to] = flags & FLAG_EP ? EMPTY : captured;
      if (flags & FLAG_EP) {
        this.board[to - this.side * 8] = -this.side;
      }
      if (flags & FLAG_CASTLE) {
        if (to > from) {
          this.board[from + 1] = EMPTY;
          this.board[from + 3] = this.side * WR;
        } else {
          this.board[from - 1] = EMPTY;
          this.board[from - 4] = this.side * WR;
        }
      }
    }
    findKingSq(side) {
      return side === 1 ? this.wKingSq : this.bKingSq;
    }
    isAttacked(sq, bySide) {
      const pawnDir = bySide === 1 ? 1 : -1;
      const pawnRank = sqRank(sq) - pawnDir;
      if (pawnRank >= 0 && pawnRank <= 7) {
        const pf = sqFile(sq);
        if (pf > 0 && this.board[pawnRank * 8 + pf - 1] === bySide * WP)
          return true;
        if (pf < 7 && this.board[pawnRank * 8 + pf + 1] === bySide * WP)
          return true;
      }
      const kn = bySide * WN;
      const knightOffsets = [-17, -15, -10, -6, 6, 10, 15, 17];
      for (const off of knightOffsets) {
        const t = sq + off;
        if (t < 0 || t > 63)
          continue;
        if (Math.abs(sqFile(t) - sqFile(sq)) > 2)
          continue;
        if (this.board[t] === kn)
          return true;
      }
      const kg = bySide * WK;
      for (let dr = -1; dr <= 1; dr++) {
        for (let df = -1; df <= 1; df++) {
          if (!dr && !df)
            continue;
          const t = sq + dr * 8 + df;
          if (t < 0 || t > 63 || Math.abs(sqFile(t) - sqFile(sq)) > 1)
            continue;
          if (this.board[t] === kg)
            return true;
        }
      }
      const sideR = bySide * WR, sideQ = bySide * WQ, sideB = bySide * WB;
      const straightDirs = [8, -8, 1, -1];
      const diagDirs = [9, 7, -9, -7];
      for (const dir of straightDirs) {
        let t = sq + dir;
        while (t >= 0 && t <= 63) {
          if (dir === 1 || dir === -1) {
            if (sqRank(t) !== sqRank(t - dir))
              break;
          }
          const p = this.board[t];
          if (p !== EMPTY) {
            if (p === sideR || p === sideQ)
              return true;
            break;
          }
          t += dir;
        }
      }
      for (const dir of diagDirs) {
        let t = sq + dir;
        while (t >= 0 && t <= 63) {
          if (Math.abs(sqFile(t) - sqFile(t - dir)) !== 1)
            break;
          const p = this.board[t];
          if (p !== EMPTY) {
            if (p === sideB || p === sideQ)
              return true;
            break;
          }
          t += dir;
        }
      }
      return false;
    }
    inCheck(side) {
      const ksq = this.findKingSq(side);
      return ksq >= 0 && this.isAttacked(ksq, -side);
    }
    generateMoves(capturesOnly = false) {
      const moves = [];
      const s = this.side;
      const opp = -s;
      for (let sq = 0; sq < 64; sq++) {
        const p = this.board[sq];
        if (p === EMPTY || Math.sign(p) !== s)
          continue;
        const abs = Math.abs(p);
        const file = sqFile(sq);
        const rank = sqRank(sq);
        if (abs === 1) {
          const dir = s;
          const promoRank = s === 1 ? 7 : 0;
          const startRank = s === 1 ? 1 : 6;
          const fwd = sq + dir * 8;
          if (fwd >= 0 && fwd <= 63 && this.board[fwd] === EMPTY) {
            if (sqRank(fwd) === promoRank) {
              for (const pr of [WQ, WR, WB, WN])
                moves.push(this.createMove(sq, fwd, FLAG_PROMO, s * pr));
            } else if (!capturesOnly) {
              moves.push(this.createMove(sq, fwd));
              const fwd2 = fwd + dir * 8;
              if (rank === startRank && fwd2 >= 0 && fwd2 <= 63 && this.board[fwd2] === EMPTY) {
                moves.push(this.createMove(sq, fwd2));
              }
            }
          }
          for (const df of [-1, 1]) {
            const cf = file + df;
            if (cf < 0 || cf > 7)
              continue;
            const csq = fwd + df;
            if (csq < 0 || csq > 63)
              continue;
            if (this.board[csq] !== EMPTY && Math.sign(this.board[csq]) === opp) {
              if (sqRank(csq) === promoRank) {
                for (const pr of [WQ, WR, WB, WN])
                  moves.push(this.createMove(sq, csq, FLAG_PROMO, s * pr));
              } else {
                moves.push(this.createMove(sq, csq));
              }
            } else if (csq === this.epSquare) {
              moves.push(this.createMove(sq, csq, FLAG_EP));
            }
          }
        } else if (abs === 2) {
          for (const off of [-17, -15, -10, -6, 6, 10, 15, 17]) {
            const t = sq + off;
            if (t < 0 || t > 63 || Math.abs(sqFile(t) - file) > 2)
              continue;
            const tp = this.board[t];
            if (tp !== EMPTY && Math.sign(tp) === s)
              continue;
            if (capturesOnly && tp === EMPTY)
              continue;
            moves.push(this.createMove(sq, t));
          }
        } else if (abs === 6) {
          for (let dr = -1; dr <= 1; dr++) {
            for (let df = -1; df <= 1; df++) {
              if (!dr && !df)
                continue;
              const t = sq + dr * 8 + df;
              if (t < 0 || t > 63 || Math.abs(sqFile(t) - file) > 1)
                continue;
              const tp = this.board[t];
              if (tp !== EMPTY && Math.sign(tp) === s)
                continue;
              if (capturesOnly && tp === EMPTY)
                continue;
              moves.push(this.createMove(sq, t));
            }
          }
          if (!capturesOnly && !this.inCheck(s)) {
            if (s === 1) {
              if (this.castling & 1 && sq === 4 && this.board[5] === EMPTY && this.board[6] === EMPTY && !this.isAttacked(5, -1) && !this.isAttacked(6, -1)) {
                moves.push(this.createMove(4, 6, FLAG_CASTLE));
              }
              if (this.castling & 2 && sq === 4 && this.board[3] === EMPTY && this.board[2] === EMPTY && this.board[1] === EMPTY && !this.isAttacked(3, -1) && !this.isAttacked(2, -1)) {
                moves.push(this.createMove(4, 2, FLAG_CASTLE));
              }
            } else {
              if (this.castling & 4 && sq === 60 && this.board[61] === EMPTY && this.board[62] === EMPTY && !this.isAttacked(61, 1) && !this.isAttacked(62, 1)) {
                moves.push(this.createMove(60, 62, FLAG_CASTLE));
              }
              if (this.castling & 8 && sq === 60 && this.board[59] === EMPTY && this.board[58] === EMPTY && this.board[57] === EMPTY && !this.isAttacked(59, 1) && !this.isAttacked(58, 1)) {
                moves.push(this.createMove(60, 58, FLAG_CASTLE));
              }
            }
          }
        } else {
          const dirs = abs === 3 ? [9, 7, -9, -7] : abs === 4 ? [8, -8, 1, -1] : [9, 7, -9, -7, 8, -8, 1, -1];
          for (const dir of dirs) {
            let t = sq + dir;
            while (t >= 0 && t <= 63) {
              const fdiff = Math.abs(sqFile(t) - sqFile(t - dir));
              if ((dir === 1 || dir === -1) && fdiff !== 1)
                break;
              if ((Math.abs(dir) === 7 || Math.abs(dir) === 9) && fdiff !== 1)
                break;
              const tp = this.board[t];
              if (tp !== EMPTY && Math.sign(tp) === s)
                break;
              if (!capturesOnly || tp !== EMPTY)
                moves.push(this.createMove(sq, t));
              if (tp !== EMPTY)
                break;
              t += dir;
            }
          }
        }
      }
      return moves;
    }
    generateLegalMoves(capturesOnly = false) {
      const pseudo = this.generateMoves(capturesOnly);
      const legal = [];
      for (const mv of pseudo) {
        this.makeMove(mv);
        if (!this.inCheck(-this.side))
          legal.push(mv);
        this.unmakeMove(mv);
      }
      return legal;
    }
    moveToUci(mv) {
      let s = sqName(mv.from) + sqName(mv.to);
      if (mv.flags & FLAG_PROMO) {
        s += "nbrq"[Math.abs(mv.promo) - 2];
      }
      return s;
    }
    // ---- Evaluation ----
    evaluate() {
      let mgScore = 0, egScore = 0, phase = 0;
      let wBishops = 0, bBishops = 0;
      const phaseVal = { 2: 1, 3: 1, 4: 2, 5: 4 };
      for (let sq = 0; sq < 64; sq++) {
        const p2 = this.board[sq];
        if (p2 === EMPTY)
          continue;
        const abs = Math.abs(p2);
        const side = Math.sign(p2);
        const val = PIECE_VAL[abs];
        const pstSq = side === 1 ? sq : mirrorSq(sq);
        let pstVal = 0;
        if (abs <= 5 && PST[abs])
          pstVal = PST[abs][pstSq];
        let mgKing = 0, egKing = 0;
        if (abs === 6) {
          mgKing = PST_KING_MG[pstSq];
          egKing = PST_KING_EG[pstSq];
        }
        if (abs === 3) {
          if (side === 1)
            wBishops++;
          else
            bBishops++;
        }
        if (abs >= 2 && abs <= 5)
          phase += phaseVal[abs] || 0;
        const material = val * side;
        mgScore += material + (abs === 6 ? mgKing * side : pstVal * side);
        egScore += material + (abs === 6 ? egKing * side : pstVal * side);
      }
      if (wBishops >= 2) {
        mgScore += 30;
        egScore += 50;
      }
      if (bBishops >= 2) {
        mgScore -= 30;
        egScore -= 50;
      }
      for (let f = 0; f < 8; f++) {
        let wPawnsOnFile = 0, bPawnsOnFile = 0;
        for (let r = 0; r < 8; r++) {
          const p2 = this.board[r * 8 + f];
          if (p2 === WP)
            wPawnsOnFile++;
          if (p2 === BP)
            bPawnsOnFile++;
        }
        if (wPawnsOnFile > 1) {
          mgScore -= 10 * (wPawnsOnFile - 1);
          egScore -= 20 * (wPawnsOnFile - 1);
        }
        if (bPawnsOnFile > 1) {
          mgScore += 10 * (bPawnsOnFile - 1);
          egScore += 20 * (bPawnsOnFile - 1);
        }
      }
      for (let sq = 0; sq < 64; sq++) {
        const p2 = this.board[sq];
        if (Math.abs(p2) !== 4)
          continue;
        const f = sqFile(sq);
        let hasFriendlyPawn = false, hasEnemyPawn = false;
        for (let r = 0; r < 8; r++) {
          const pp = this.board[r * 8 + f];
          if (pp === Math.sign(p2) * WP)
            hasFriendlyPawn = true;
          if (pp === -Math.sign(p2) * WP)
            hasEnemyPawn = true;
        }
        if (!hasFriendlyPawn && !hasEnemyPawn) {
          mgScore += 20 * Math.sign(p2);
          egScore += 20 * Math.sign(p2);
        } else if (!hasFriendlyPawn) {
          mgScore += 10 * Math.sign(p2);
          egScore += 10 * Math.sign(p2);
        }
      }
      for (const side of [1, -1]) {
        const ksq = this.findKingSq(side);
        if (ksq < 0)
          continue;
        const kf = sqFile(ksq);
        const kr = sqRank(ksq);
        const shieldRank = kr + side;
        if (shieldRank >= 0 && shieldRank <= 7) {
          let shield = 0;
          for (let df = -1; df <= 1; df++) {
            const sf = kf + df;
            if (sf < 0 || sf > 7)
              continue;
            if (this.board[shieldRank * 8 + sf] === side * WP)
              shield++;
          }
          mgScore += shield * 15 * side;
        }
      }
      const maxPhase = 24;
      const p = Math.min(phase, maxPhase);
      const score = Math.round((mgScore * p + egScore * (maxPhase - p)) / maxPhase);
      return score * this.side;
    }
    // ---- Search ----
    scoreMoves(moves, ply, ttMove) {
      const scores = new Int32Array(moves.length);
      for (let i = 0; i < moves.length; i++) {
        const mv = moves[i];
        let s = 0;
        if (ttMove && mv.from === ttMove.from && mv.to === ttMove.to) {
          s = 1e5;
        } else if (mv.captured !== EMPTY) {
          s = 1e4 + PIECE_VAL[Math.abs(mv.captured)] * 10 - PIECE_VAL[Math.abs(mv.piece)];
        }
        if (mv.flags & FLAG_PROMO)
          s += 8e3 + PIECE_VAL[Math.abs(mv.promo)];
        if (this.killers[ply] && this.killers[ply].includes(mv.from * 64 + mv.to))
          s += 5e3;
        s += this.history[mv.from * 64 + mv.to];
        scores[i] = s;
      }
      return scores;
    }
    // Lazy selection sort: pick best move for position i, swap it in place
    pickMove(moves, scores, startIdx) {
      let bestIdx = startIdx;
      let bestScore = scores[startIdx];
      for (let j = startIdx + 1; j < moves.length; j++) {
        if (scores[j] > bestScore) {
          bestScore = scores[j];
          bestIdx = j;
        }
      }
      if (bestIdx !== startIdx) {
        const tmpMv = moves[startIdx];
        moves[startIdx] = moves[bestIdx];
        moves[bestIdx] = tmpMv;
        const tmpSc = scores[startIdx];
        scores[startIdx] = scores[bestIdx];
        scores[bestIdx] = tmpSc;
      }
    }
    // --- Transposition Table ---
    ttKey() {
      let key = "";
      for (let i = 0; i < 64; i++)
        key += this.board[i] + ",";
      key += this.side + "," + this.castling + "," + this.epSquare;
      return key;
    }
    ttProbe(depth, alpha, beta) {
      const entry = this.tt.get(this.ttKey());
      if (!entry || entry.depth < depth)
        return null;
      if (entry.flag === TT_EXACT)
        return { score: entry.score, move: entry.move };
      if (entry.flag === TT_ALPHA && entry.score <= alpha)
        return { score: alpha, move: entry.move };
      if (entry.flag === TT_BETA && entry.score >= beta)
        return { score: beta, move: entry.move };
      return { score: null, move: entry.move };
    }
    ttStore(depth, score, flag, move) {
      const key = this.ttKey();
      const existing = this.tt.get(key);
      if (!existing || existing.depth <= depth) {
        this.tt.set(key, { depth, score, flag, move });
        if (this.tt.size > TT_SIZE) {
          const firstKey = this.tt.keys().next().value;
          this.tt.delete(firstKey);
        }
      }
    }
    quiesce(alpha, beta, ply) {
      this.nodes++;
      if (this.nodes % 4096 === 0 && performance.now() - this.startTime > this.timeLimit) {
        this.stopped = true;
        return 0;
      }
      const inChk = this.inCheck(this.side);
      if (!inChk) {
        const standPat = this.evaluate();
        if (standPat >= beta)
          return beta;
        if (standPat > alpha)
          alpha = standPat;
      }
      const moves = this.generateLegalMoves(!inChk);
      if (inChk && moves.length === 0)
        return -(MATE_SCORE - ply);
      const standPatForDelta = inChk ? -MATE_SCORE : alpha;
      const scores = this.scoreMoves(moves, ply, null);
      for (let i = 0; i < moves.length; i++) {
        this.pickMove(moves, scores, i);
        const mv = moves[i];
        if (!inChk && mv.captured !== EMPTY) {
          const delta = PIECE_VAL[Math.abs(mv.captured)] + 200;
          if (standPatForDelta + delta < alpha)
            continue;
        }
        this.makeMove(mv);
        const score = -this.quiesce(-beta, -alpha, ply + 1);
        this.unmakeMove(mv);
        if (this.stopped)
          return 0;
        if (score >= beta)
          return beta;
        if (score > alpha)
          alpha = score;
      }
      return alpha;
    }
    negamax(depth, alpha, beta, ply, pvLine) {
      this.nodes++;
      if (this.stopped)
        return 0;
      if (this.nodes % 4096 === 0 && performance.now() - this.startTime > this.timeLimit) {
        this.stopped = true;
        return 0;
      }
      if (depth <= 0)
        return this.quiesce(alpha, beta, ply);
      const inChk = this.inCheck(this.side);
      if (inChk && ply < 20 && this.extensions < 6) {
        depth++;
        this.extensions++;
      }
      if (this.halfmove >= 100)
        return 0;
      let ttMove = null;
      const ttEntry = this.ttProbe(depth, alpha, beta);
      if (ttEntry) {
        ttMove = ttEntry.move;
        if (ttEntry.score !== null)
          return ttEntry.score;
      }
      const moves = this.generateLegalMoves();
      if (moves.length === 0) {
        return inChk ? -(MATE_SCORE - ply) : 0;
      }
      if (!inChk && depth >= 3 && ply > 0) {
        this.stateStack.push({
          castling: this.castling,
          epSquare: this.epSquare,
          halfmove: this.halfmove,
          fullmove: this.fullmove,
          wKingSq: this.wKingSq,
          bKingSq: this.bKingSq
        });
        this.epSquare = -1;
        this.side = -this.side;
        const nullScore = -this.negamax(depth - 3, -beta, -beta + 1, ply + 1, []);
        this.side = -this.side;
        const st = this.stateStack.pop();
        this.castling = st.castling;
        this.epSquare = st.epSquare;
        this.halfmove = st.halfmove;
        this.fullmove = st.fullmove;
        this.wKingSq = st.wKingSq;
        this.bKingSq = st.bKingSq;
        if (this.stopped)
          return 0;
        if (nullScore >= beta)
          return beta;
      }
      const scores = this.scoreMoves(moves, ply, ttMove);
      const childPv = [];
      let bestMoveInNode = null;
      let origAlpha = alpha;
      for (let i = 0; i < moves.length; i++) {
        this.pickMove(moves, scores, i);
        const mv = moves[i];
        this.makeMove(mv);
        childPv.length = 0;
        const score = -this.negamax(depth - 1, -beta, -alpha, ply + 1, childPv);
        this.unmakeMove(mv);
        if (this.stopped)
          return 0;
        if (score >= beta) {
          if (mv.captured === EMPTY) {
            if (!this.killers[ply])
              this.killers[ply] = [];
            const key = mv.from * 64 + mv.to;
            if (!this.killers[ply].includes(key)) {
              this.killers[ply].unshift(key);
              if (this.killers[ply].length > 2)
                this.killers[ply].pop();
            }
            this.history[mv.from * 64 + mv.to] += depth * depth;
          }
          this.ttStore(depth, beta, TT_BETA, mv);
          return beta;
        }
        if (score > alpha) {
          alpha = score;
          bestMoveInNode = mv;
          pvLine.length = 0;
          pvLine.push(mv);
          pvLine.push(...childPv);
        }
      }
      const flag = alpha > origAlpha ? TT_EXACT : TT_ALPHA;
      this.ttStore(depth, alpha, flag, bestMoveInNode || moves[0]);
      return alpha;
    }
    searchRoot(maxDepth, timeLimitMs) {
      this.nodes = 0;
      this.startTime = performance.now();
      this.timeLimit = timeLimitMs;
      this.stopped = false;
      this.killers = [];
      this.history.fill(0);
      this.tt.clear();
      let bestMove = null;
      let bestScore = 0;
      let bestPv = [];
      let completedDepth = 0;
      for (let d = 1; d <= maxDepth; d++) {
        this.extensions = 0;
        if (d > 1) {
          for (let i = 0; i < this.history.length; i++) {
            this.history[i] >>= 1;
          }
        }
        const pvLine = [];
        const score = this.negamax(d, -MATE_SCORE - 1, MATE_SCORE + 1, 0, pvLine);
        if (this.stopped && d > 1)
          break;
        if (pvLine.length > 0) {
          bestMove = pvLine[0];
          bestScore = score;
          bestPv = pvLine.slice();
          completedDepth = d;
        }
        if (Math.abs(score) > MATE_SCORE - 100)
          break;
      }
      const whiteScore = bestScore * this.side;
      return { move: bestMove, score: whiteScore, pv: bestPv, depth: completedDepth, nodes: this.nodes };
    }
    analyze(fen, depth) {
      this.loadFen(fen);
      const timeMs = Math.min(depth * 150, 500);
      const searchDepth = Math.min(depth, 6);
      const result = this.searchRoot(searchDepth, timeMs);
      if (!result.move) {
        return { success: false, bestmove: "(none)", evaluation: 0 };
      }
      const uci = this.moveToUci(result.move);
      const pvStr = result.pv.map((m) => this.moveToUci(m)).join(" ");
      let scoreObj;
      if (Math.abs(result.score) > MATE_SCORE - 200) {
        const mateIn = Math.ceil((MATE_SCORE - Math.abs(result.score)) / 2);
        scoreObj = { mate: result.score > 0 ? mateIn : -mateIn };
      } else {
        scoreObj = { cp: result.score };
      }
      return {
        success: true,
        bestmove: uci,
        evaluation: result.score / 100,
        analysis: [{ uci, pv: pvStr, score: scoreObj }],
        depth: result.depth,
        nodes: result.nodes,
        source: "local"
      };
    }
  };
  var localEngine = new LocalEngine();
  function analyzeLocally(fen, depth) {
    console.log(`GabiBot: \u{1F9E0} Local engine analyzing FEN: ${fen.substring(0, 20)}... | Depth: ${depth}`);
    const start = performance.now();
    const result = localEngine.analyze(fen, depth);
    const elapsed = performance.now() - start;
    console.log(`GabiBot: \u{1F9E0} Local engine done in ${elapsed.toFixed(0)}ms | ${result.nodes} nodes | Depth: ${result.depth} | Best: ${result.bestmove}`);
    return result;
  }
  async function fetchEngineData(fen, depth, signal) {
    const startTime = performance.now();
    console.log(`GabiBot: \u{1F4E1} API request for FEN: ${fen.substring(0, 20)}... | Depth: ${depth}`);
    const call = async (params) => {
      const url = `${API_URL}?fen=${encodeURIComponent(fen)}&depth=${depth}&${params}`;
      return new Promise((resolve, reject) => {
        const abortHandler = () => reject(new DOMException("Aborted", "AbortError"));
        if (signal?.aborted)
          return reject(new DOMException("Aborted", "AbortError"));
        signal?.addEventListener("abort", abortHandler, { once: true });
        const timeoutId = setTimeout(() => {
          signal?.removeEventListener("abort", abortHandler);
          reject(new Error("timeout"));
        }, ANALYZE_TIMEOUT_MS);
        if (typeof GM_xmlhttpRequest !== "undefined") {
          const req = GM_xmlhttpRequest({
            method: "GET",
            url,
            headers: { Accept: "application/json" },
            onload: (r) => {
              clearTimeout(timeoutId);
              signal?.removeEventListener("abort", abortHandler);
              if (r.status >= 200 && r.status < 300) {
                try {
                  const data = JSON.parse(r.responseText);
                  if (data.success === false)
                    reject(new Error("API success=false"));
                  else {
                    console.log(`GabiBot: \u2705 API ok in ${(performance.now() - startTime).toFixed(0)}ms`);
                    resolve(data);
                  }
                } catch {
                  reject(new Error("Invalid JSON"));
                }
              } else
                reject(new Error(`API error ${r.status}`));
            },
            onerror: () => {
              clearTimeout(timeoutId);
              signal?.removeEventListener("abort", abortHandler);
              reject(new Error("Network error"));
            },
            ontimeout: () => {
              clearTimeout(timeoutId);
              signal?.removeEventListener("abort", abortHandler);
              reject(new Error("timeout"));
            }
          });
          signal?.addEventListener("abort", () => req.abort(), { once: true });
        } else {
          fetch(url, { method: "GET", headers: { Accept: "application/json" }, signal }).then(async (res) => {
            clearTimeout(timeoutId);
            if (!res.ok)
              throw new Error(`API error ${res.status}`);
            const data = await res.json();
            if (data.success === false)
              throw new Error("API success=false");
            console.log(`GabiBot: \u2705 API ok in ${(performance.now() - startTime).toFixed(0)}ms`);
            resolve(data);
          }).catch((err) => {
            clearTimeout(timeoutId);
            signal?.removeEventListener("abort", abortHandler);
            reject(err);
          });
        }
      });
    };
    try {
      return await call(`multipv=${MULTIPV}&mode=bestmove`);
    } catch (e) {
      if (e.name === "AbortError")
        throw e;
      throw e;
    }
  }
  async function fetchAnalysis(fen, depth, signal) {
    const cached = PositionCache.get(fen);
    if (cached) {
      console.log("GabiBot: \u{1F5C3}\uFE0F Using cached analysis");
      return cached;
    }
    if (signal?.aborted || !BotState.hackEnabled)
      throw new DOMException("Aborted", "AbortError");
    const apiPromise = fetchEngineData(fen, depth, signal).then((data) => ({ ok: true, data })).catch((err) => ({ ok: false, error: err }));
    BotState.statusInfo = "\u{1F9E0} Analyzing...";
    const localResult = analyzeLocally(fen, depth);
    const apiSettled = await Promise.race([
      apiPromise,
      new Promise((r) => setTimeout(r, 10)).then(() => null)
    ]);
    if (apiSettled?.ok) {
      console.log("GabiBot: \u2705 API beat local engine");
      PositionCache.set(fen, apiSettled.data);
      return apiSettled.data;
    }
    if (localResult.success) {
      PositionCache.set(fen, localResult);
      apiPromise.then((r) => {
        if (r.ok) {
          console.log("GabiBot: \u{1F4E1} API result arrived, cache upgraded");
          PositionCache.set(fen, r.data);
        }
      });
      return localResult;
    }
    const apiResult = await apiPromise;
    if (apiResult.ok) {
      PositionCache.set(fen, apiResult.data);
      return apiResult.data;
    }
    if (apiResult.error?.name === "AbortError")
      throw apiResult.error;
    throw new Error("Both API and local engine failed");
  }
  function parseBestLine(data) {
    const lines = [];
    const pushLine = (uci, pv, score) => {
      if (!uci || uci.length < 4)
        return;
      lines.push({ uci: uci.trim(), pv: (pv || "").trim(), score: score || {} });
    };
    const addFromArray = (arr) => arr.forEach((item) => {
      const pv = item.pv || item.line || item.moves || "";
      const uci = item.uci || (pv ? pv.split(" ")[0] : "");
      const score = scoreFrom(item.score || item.evaluation || item.eval);
      pushLine(uci, pv, score);
    });
    if (Array.isArray(data.analysis))
      addFromArray(data.analysis);
    else if (Array.isArray(data.lines))
      addFromArray(data.lines);
    else if (Array.isArray(data.pvs))
      addFromArray(data.pvs);
    if (!lines.length && typeof data.bestmove === "string") {
      const parts = data.bestmove.split(" ");
      let uci = parts.length > 1 ? parts[1] : parts[0];
      if (uci === "bestmove" && parts[1])
        uci = parts[1];
      const pv = data.pv || data.continuation || uci;
      const score = scoreFrom(data.evaluation);
      pushLine(uci, pv, score);
    }
    lines.sort((a, b) => scoreNumeric(b.score) - scoreNumeric(a.score));
    return lines[0] || null;
  }
  function isEnPassantCapture(fen, from, to, ourColor) {
    const parts = fen.split(" ");
    const ep = parts[3];
    const fromPiece = pieceFromFenChar(fenCharAtSquare(fen, from));
    if (!fromPiece || fromPiece.color !== ourColor || fromPiece.type !== "p")
      return false;
    return ep && ep !== "-" && to === ep && from[0] !== to[0];
  }
  var HANGING_THRESHOLDS = { 6: 100, 5: 90, 4: 60, 3: 40, 2: 40, 1: 15 };
  var premoveEngine = new LocalEngine();
  function evaluatePremove(fen, opponentUci, ourUci, ourColor, evalDisplay) {
    if (!ourUci || ourUci.length < 4) {
      return { execute: false, chance: 0, reasons: [], blocked: "Invalid move" };
    }
    let chance = getEvalBasedPremoveChance(evalDisplay, ourColor);
    const reasons = [];
    const oppSide = ourColor === "w" ? -1 : 1;
    const ourSide = -oppSide;
    if (!opponentUci || opponentUci.length < 4) {
      return { execute: false, chance: 0, reasons: [], blocked: "No predicted opponent move" };
    }
    try {
      premoveEngine.loadFen(fen);
      const oppFrom = nameToSq(opponentUci.substring(0, 2));
      const oppTo = nameToSq(opponentUci.substring(2, 4));
      const oppMoves = premoveEngine.generateLegalMoves();
      const oppMove = oppMoves.find((m) => m.from === oppFrom && m.to === oppTo);
      if (!oppMove)
        return { execute: false, chance: 0, reasons: [], blocked: "Opponent move not legal" };
      premoveEngine.makeMove(oppMove);
      const ourLegalMoves = premoveEngine.generateLegalMoves();
      const ourFrom = nameToSq(ourUci.substring(0, 2));
      const ourTo = nameToSq(ourUci.substring(2, 4));
      const ourMove = ourLegalMoves.find((m) => m.from === ourFrom && m.to === ourTo);
      if (!ourMove) {
        premoveEngine.unmakeMove(oppMove);
        return { execute: false, chance: 0, reasons: [], blocked: "Our move illegal after opponent plays" };
      }
      const movingAbs = Math.abs(ourMove.piece);
      const destPieceAbs = movingAbs;
      const capturedAbs = ourMove.captured !== EMPTY ? Math.abs(ourMove.captured) : 0;
      const capturedVal = capturedAbs > 0 ? PIECE_VAL[capturedAbs] || 0 : 0;
      const movedVal = PIECE_VAL[destPieceAbs] || 0;
      if (destPieceAbs !== 6) {
        premoveEngine.makeMove(ourMove);
        const isDestAttacked = premoveEngine.isAttacked(ourTo, premoveEngine.side);
        premoveEngine.unmakeMove(ourMove);
        if (isDestAttacked && destPieceAbs >= 2) {
          premoveEngine.makeMove(ourMove);
          const oppAttacksPost = [];
          const ourDefendsPost = [];
          const oppReplies = premoveEngine.generateLegalMoves();
          for (const reply of oppReplies) {
            if (reply.to === ourTo)
              oppAttacksPost.push(reply);
          }
          premoveEngine.unmakeMove(ourMove);
          if (oppAttacksPost.length > 0 && capturedVal < movedVal) {
            const riskThreshold = HANGING_THRESHOLDS[destPieceAbs] || 50;
            const pieceNames = { 5: "queen", 4: "rook", 3: "bishop", 2: "knight" };
            const pieceName = pieceNames[destPieceAbs] || "piece";
            const defenderCount = premoveEngine.isAttacked(ourTo, premoveEngine.side) ? 1 : 0;
            const lowestAttackerVal = Math.min(...oppAttacksPost.map((r) => PIECE_VAL[Math.abs(r.piece)] || 100));
            if (defenderCount === 0 || lowestAttackerVal < movedVal) {
              if (destPieceAbs >= 5) {
                premoveEngine.unmakeMove(oppMove);
                return { execute: false, chance: 0, reasons: [], blocked: `Hangs ${pieceName}` };
              }
              chance = Math.max(5, chance - riskThreshold);
              reasons.push(`${pieceName} at risk`);
            }
          }
        }
      }
      premoveEngine.makeMove(ourMove);
      const ourKingSq = premoveEngine.findKingSq(ourSide);
      if (ourKingSq >= 0) {
        const kRank = sqRank(ourKingSq);
        const isBackRank = ourSide === 1 && kRank === 0 || ourSide === -1 && kRank === 7;
        if (isBackRank) {
          const shieldRank = kRank + ourSide;
          if (shieldRank >= 0 && shieldRank <= 7) {
            const kFile = sqFile(ourKingSq);
            let escapable = false;
            for (let df = -1; df <= 1; df++) {
              const sf = kFile + df;
              if (sf < 0 || sf > 7)
                continue;
              const shieldSq = shieldRank * 8 + sf;
              if (premoveEngine.board[shieldSq] === EMPTY && !premoveEngine.isAttacked(shieldSq, premoveEngine.side)) {
                escapable = true;
                break;
              }
            }
            if (!escapable) {
              const backRankAttacked = premoveEngine.isAttacked(ourKingSq, premoveEngine.side);
              if (backRankAttacked) {
                premoveEngine.unmakeMove(ourMove);
                premoveEngine.unmakeMove(oppMove);
                return { execute: false, chance: 0, reasons: [], blocked: "Back-rank mate threat" };
              }
              chance = Math.max(10, chance - 20);
              reasons.push("back-rank weak");
            }
          }
        }
      }
      premoveEngine.unmakeMove(ourMove);
      if (ourLegalMoves.length === 1) {
        chance = Math.min(95, chance + 40);
        reasons.push("forced");
      } else if (ourLegalMoves.length <= 3) {
        chance = Math.min(95, chance + 15);
        reasons.push("few options");
      }
      if (ourTo === oppTo) {
        chance = Math.min(95, chance + 20);
        reasons.push("recapture");
      }
      premoveEngine.makeMove(ourMove);
      if (premoveEngine.inCheck(premoveEngine.side)) {
        chance = Math.min(95, chance + 10);
        reasons.push("check");
      }
      premoveEngine.unmakeMove(ourMove);
      const destAttacked = premoveEngine.isAttacked(ourTo, -premoveEngine.side);
      if (!destAttacked) {
        chance = Math.min(95, chance + 10);
        reasons.push("safe sq");
      }
      const centerSquares = [nameToSq("d4"), nameToSq("d5"), nameToSq("e4"), nameToSq("e5")];
      if (centerSquares.includes(ourTo)) {
        chance = Math.min(95, chance + 5);
        reasons.push("center");
      }
      if (ourLegalMoves.length > 1 && ourLegalMoves.length <= 30) {
        premoveEngine.makeMove(ourMove);
        const ourSearchResult = premoveEngine.searchRoot(3, 200);
        const ourScore = ourSearchResult.score ? -ourSearchResult.score : -premoveEngine.evaluate();
        premoveEngine.unmakeMove(ourMove);
        let secondBest = -Infinity;
        for (const alt of ourLegalMoves) {
          if (alt.from === ourFrom && alt.to === ourTo)
            continue;
          premoveEngine.makeMove(alt);
          const altScore = -premoveEngine.evaluate();
          premoveEngine.unmakeMove(alt);
          if (altScore > secondBest)
            secondBest = altScore;
        }
        if (secondBest > -Infinity && ourScore - secondBest >= 150) {
          chance = Math.min(95, chance + 25);
          reasons.push("dominant");
        }
      }
      premoveEngine.unmakeMove(oppMove);
      const oppScoredMoves = [];
      for (const oMove of oppMoves) {
        premoveEngine.makeMove(oMove);
        const score = -premoveEngine.evaluate();
        premoveEngine.unmakeMove(oMove);
        oppScoredMoves.push({ move: oMove, score });
      }
      oppScoredMoves.sort((a, b) => b.score - a.score);
      const topOppMoves = oppScoredMoves.filter((m) => !(m.move.from === oppFrom && m.move.to === oppTo)).slice(0, 3);
      let illegalCount = 0;
      let badScoreCount = 0;
      for (const { move: altOppMove } of topOppMoves) {
        premoveEngine.makeMove(altOppMove);
        const altLegal = premoveEngine.generateLegalMoves();
        const altOurMove = altLegal.find((m) => m.from === ourFrom && m.to === ourTo);
        if (!altOurMove) {
          illegalCount++;
        } else {
          premoveEngine.makeMove(altOurMove);
          const postScore = -premoveEngine.evaluate();
          premoveEngine.unmakeMove(altOurMove);
          let bestAlt = -Infinity;
          for (const alt of altLegal) {
            if (alt.from === ourFrom && alt.to === ourTo)
              continue;
            premoveEngine.makeMove(alt);
            const altS = -premoveEngine.evaluate();
            premoveEngine.unmakeMove(alt);
            if (altS > bestAlt)
              bestAlt = altS;
          }
          if (bestAlt > -Infinity && bestAlt - postScore >= 200) {
            badScoreCount++;
          }
        }
        premoveEngine.unmakeMove(altOppMove);
      }
      if (topOppMoves.length >= 2 && illegalCount >= 2) {
        chance = Math.max(5, chance - 35);
        reasons.push("unstable (illegal)");
      } else if (illegalCount >= 1) {
        chance = Math.max(10, chance - 15);
        reasons.push("sometimes illegal");
      }
      if (topOppMoves.length >= 2 && badScoreCount >= 2) {
        chance = Math.max(5, chance - 30);
        reasons.push("unstable (bad)");
      } else if (badScoreCount >= 1) {
        chance = Math.max(10, chance - 10);
        reasons.push("risky alt");
      }
    } catch (e) {
      console.warn("GabiBot: evaluatePremove error:", e);
      return { execute: false, chance: 0, reasons: [], blocked: "Evaluation error" };
    }
    chance = Math.min(95, Math.max(0, Math.round(chance)));
    const execute = chance > 0;
    return { execute, chance, reasons, blocked: null };
  }
  function shouldPremove(uci, fen) {
    if (!uci || uci.length < 4)
      return false;
    const game = getGame();
    const ourColor = getPlayerColor(game);
    const from = uci.substring(0, 2);
    const to = uci.substring(2, 4);
    const fromPiece = pieceFromFenChar(fenCharAtSquare(fen, from));
    const toPiece = pieceFromFenChar(fenCharAtSquare(fen, to));
    if (!fromPiece || fromPiece.color !== ourColor)
      return false;
    if (BotState.premoveMode === "every")
      return true;
    if (BotState.premoveMode === "capture") {
      return !!(toPiece && toPiece.color !== ourColor) || isEnPassantCapture(fen, from, to, ourColor);
    }
    if (BotState.premoveMode === "filter")
      return !!BotState.premovePieces[fromPiece.type];
    return false;
  }
  function getEvalBasedPremoveChance(evaluation, ourColor) {
    if (!BotState.premoveEnabled)
      return 0;
    let evalScore = 0;
    if (typeof evaluation === "string") {
      if (evaluation === "-" || evaluation === "Error")
        return 0;
      if (evaluation.includes("M")) {
        const mateNum = parseInt(evaluation.replace("M", "").replace("+", ""), 10);
        if (!isNaN(mateNum))
          return (ourColor === "w" ? mateNum : -mateNum) > 0 ? 100 : 25;
      }
      evalScore = parseFloat(evaluation);
    } else
      evalScore = parseFloat(evaluation);
    if (isNaN(evalScore))
      return 0;
    const ourEval = ourColor === "w" ? evalScore : -evalScore;
    if (ourEval >= 3)
      return 90;
    if (ourEval >= 2)
      return 75;
    if (ourEval >= 1)
      return 55;
    if (ourEval >= 0.5)
      return 40;
    if (ourEval >= 0)
      return 30;
    if (ourEval >= -0.5)
      return 25;
    return 20;
  }
  function getOurMoveFromPV(pv, ourColor, sideToMove) {
    if (!pv)
      return null;
    const moves = pv.trim().split(/\s+/).filter(Boolean);
    if (!moves.length)
      return null;
    return moves[sideToMove === ourColor ? 0 : 1] || null;
  }
  var scheduledMainFen = "";
  var scheduledPremoveFen = "";
  function scheduleAnalysis(kind, fen, tickCallback) {
    if (kind === "main" && scheduledMainFen === fen)
      return;
    if (kind !== "main" && scheduledPremoveFen === fen)
      return;
    if (kind === "main")
      scheduledMainFen = fen;
    else
      scheduledPremoveFen = fen;
    const analysisId = ++currentAnalysisId;
    if (currentAbortController) {
      currentAbortController.abort("superseded");
      currentAbortController = null;
    }
    const ctrl = new AbortController();
    currentAbortController = ctrl;
    const run = async () => {
      analysisRunning = true;
      if (analysisId !== currentAnalysisId || !BotState.hackEnabled) {
        analysisRunning = false;
        return;
      }
      invalidateGameCache();
      const game = getGame();
      if (!game) {
        analysisRunning = false;
        return;
      }
      if (kind === "main" && lastFenProcessedMain === fen) {
        analysisRunning = false;
        return;
      }
      if (kind !== "main" && lastFenProcessedPremove === fen) {
        analysisRunning = false;
        return;
      }
      try {
        BotState.statusInfo = kind === "main" ? "\u{1F504} Analyzing..." : "\u{1F504} Analyzing (premove)...";
        if (BotState.onUpdateDisplay)
          BotState.onUpdateDisplay(pa());
        const randomDepth = getRandomDepth(BotState.botPower);
        if (analysisId !== currentAnalysisId) {
          ctrl.abort("superseded");
          return;
        }
        const data = await fetchAnalysis(fen, randomDepth, ctrl.signal);
        if (analysisId !== currentAnalysisId)
          return;
        const sourceLabel = data.source === "local" ? " [local]" : "";
        const best = parseBestLine(data);
        if (kind === "main") {
          BotState.bestMove = best?.uci || "-";
          BotState.currentEvaluation = scoreToDisplay(best?.score);
          BotState.principalVariation = best?.pv || "Not available";
          BotState.statusInfo = `\u2713 Ready${sourceLabel}`;
          if (BotState.onUpdateDisplay)
            BotState.onUpdateDisplay(pa());
          if (best) {
            const from = best.uci.substring(0, 2);
            const to = best.uci.substring(2, 4);
            const promo = best.uci.length >= 5 ? best.uci[4] : null;
            await executeMove(from, to, fen, promo, tickCallback);
          }
          lastFenProcessedMain = fen;
        } else {
          const ourColor = getPlayerColor(game);
          const stm = getSideToMove(game);
          const pvMoves = (best?.pv || "").trim().split(/\s+/).filter(Boolean);
          const opponentUci = stm !== ourColor && pvMoves.length > 0 ? pvMoves[0] : null;
          const ourUci = getOurMoveFromPV(best?.pv || "", ourColor, stm) || (stm === ourColor ? best?.uci || null : null);
          const premoveEvalDisplay = scoreToDisplay(best?.score);
          if (!ourUci) {
            BotState.statusInfo = `Premove unavailable (no PV)${sourceLabel}`;
            BotState.currentPremoveReasons = "";
            if (BotState.onUpdateDisplay)
              BotState.onUpdateDisplay(pa());
            lastFenProcessedPremove = fen;
            return;
          }
          if (!shouldPremove(ourUci, fen)) {
            BotState.statusInfo = `Premove skipped (${BotState.premoveMode})${sourceLabel}`;
            BotState.currentPremoveReasons = "";
            if (BotState.onUpdateDisplay)
              BotState.onUpdateDisplay(pa());
            lastFenProcessedPremove = fen;
            return;
          }
          const premoveResult = evaluatePremove(fen, opponentUci, ourUci, ourColor, premoveEvalDisplay);
          BotState.currentPremoveReasons = premoveResult.reasons.length > 0 ? premoveResult.reasons.join(", ") : "";
          if (premoveResult.blocked) {
            BotState.statusInfo = `\u{1F6E1}\uFE0F Premove blocked: ${premoveResult.blocked}${sourceLabel}`;
            BotState.currentPremoveReasons = "";
            if (BotState.onUpdateDisplay)
              BotState.onUpdateDisplay(pa());
            lastFenProcessedPremove = fen;
            return;
          }
          if (!premoveResult.execute) {
            BotState.statusInfo = `Premove skipped (no confidence)${sourceLabel}`;
            if (BotState.onUpdateDisplay)
              BotState.onUpdateDisplay(pa());
            lastFenProcessedPremove = fen;
            return;
          }
          const currentChance = premoveResult.chance;
          BotState.currentPremoveChance = currentChance;
          if (premoveResult.reasons.length > 0) {
            console.log(`GabiBot: \u{1F9E0} Premove [${premoveResult.reasons.join(", ")}] \u2192 ${currentChance}%`);
          }
          const roll = Math.random() * 100;
          if (roll > currentChance) {
            const reasonTag = premoveResult.reasons.length > 0 ? ` [${premoveResult.reasons.join(", ")}]` : "";
            BotState.statusInfo = `Premove skipped: eval ${premoveEvalDisplay}${reasonTag}, ${roll.toFixed(0)}% > ${currentChance}%${sourceLabel}`;
            if (BotState.onUpdateDisplay)
              BotState.onUpdateDisplay(pa());
            lastFenProcessedPremove = fen;
            return;
          }
          const from = ourUci.substring(0, 2);
          const to = ourUci.substring(2, 4);
          clearArrows();
          drawArrow(from, to, "rgba(80, 180, 255, 0.7)", 3);
          await simulateClickMove(from, to);
          await sleep(80);
          lastPremoveFen = fen;
          lastPremoveUci = ourUci;
          const reasonSuffix = premoveResult.reasons.length > 0 ? ` [${premoveResult.reasons.join(", ")}]` : "";
          BotState.statusInfo = `\u2705 Premove: ${ourUci} (${currentChance}%)${reasonSuffix}${sourceLabel}`;
          if (BotState.onUpdateDisplay)
            BotState.onUpdateDisplay(pa());
          lastFenProcessedPremove = fen;
        }
      } catch (error) {
        if (String(error?.name || error).toLowerCase().includes("abort") || String(error?.message || error).toLowerCase().includes("superseded")) {
        } else {
          console.error("GabiBot Error:", error);
          BotState.statusInfo = "\u274C Analysis Error";
          BotState.currentEvaluation = "Error";
          if (BotState.onUpdateDisplay)
            BotState.onUpdateDisplay(pa());
        }
      } finally {
        if (kind === "main" && scheduledMainFen === fen)
          scheduledMainFen = "";
        else if (kind !== "main" && scheduledPremoveFen === fen)
          scheduledPremoveFen = "";
        if (currentAbortController === ctrl)
          currentAbortController = null;
        analysisRunning = false;
      }
    };
    run();
  }

  // src/index.js
  (async function() {
    "use strict";
    if (window.__GABIBOT_RUNNING__) {
      console.log("GabiBot: Already running, skipping init.");
      return;
    }
    window.__GABIBOT_RUNNING__ = true;
    console.log("GabiBot: Script loaded, waiting for board...");
    let tickTimer = null;
    let gameStartInterval = null;
    let gameEndInterval = null;
    let lastFenSeen = "";
    let boardMoveObserver = null;
    async function init() {
      try {
        BotState.onUpdateDisplay = (playingAs) => ui.updateDisplay(playingAs);
        const board = await waitForElement(".board, chess-board, .board-layout-vertical, .board-layout-horizontal").catch(() => null);
        await buildUI();
        attachToBoard(board || qs("chess-board") || qs(".board") || qs('[class*="board"]'));
        startDomBoardWatcher();
        startAutoWatchers();
        startStateWatcher();
        console.log("GabiBot: Initialized.");
      } catch (error) {
        console.error("GabiBot Error:", error);
        alert("GabiBot: Could not find chess board. Please refresh or check console.");
      }
    }
    function tick() {
      if (!BotState.hackEnabled)
        return;
      invalidateGameCache();
      const game = getGame();
      if (!game)
        return;
      if (game.isGameOver && game.isGameOver()) {
        BotState.currentEvaluation = "GAME OVER";
        BotState.bestMove = "-";
        BotState.principalVariation = "Game ended";
        BotState.statusInfo = "Game finished";
        clearArrows();
        ui.updateDisplay(pa());
        return;
      }
      const fen = getFen(game);
      if (!fen)
        return;
      if (fen !== lastFenSeen) {
        lastFenSeen = fen;
        cancelPendingMove();
        clearArrows();
        setLastPremoveFen("");
        setLastPremoveUci("");
      }
      if (isPlayersTurn(game)) {
        if (getLastFenProcessedMain() !== fen) {
          scheduleAnalysis("main", fen, () => tick());
        }
      } else {
        if (BotState.premoveEnabled) {
          if (getLastFenProcessedPremove() !== fen) {
            scheduleAnalysis("premove", fen);
          } else {
            const chanceEl = qs('[name="premoveChance"] .itemState');
            if (chanceEl && BotState.currentPremoveChance !== void 0) {
              chanceEl.textContent = `${Math.round(BotState.currentPremoveChance)}%`;
            }
            BotState.statusInfo = getLastPremoveUci() && getLastPremoveFen() === fen ? "Waiting (premove ready)..." : "Waiting for opponent...";
            ui.updateDisplay(pa());
          }
        } else {
          const chanceEl = qs('[name="premoveChance"] .itemState');
          if (chanceEl)
            chanceEl.textContent = "0%";
          BotState.statusInfo = "Waiting for opponent...";
          ui.updateDisplay(pa());
        }
      }
    }
    function startBoardMoveObserver() {
      stopBoardMoveObserver();
      const board = getGame() && (document.querySelector("chess-board") || document.querySelector(".board"));
      if (!board)
        return;
      let debounceTimer = null;
      boardMoveObserver = new MutationObserver(() => {
        if (!BotState.hackEnabled)
          return;
        if (debounceTimer)
          return;
        debounceTimer = setTimeout(() => {
          debounceTimer = null;
          invalidateGameCache();
          tick();
        }, 50);
      });
      boardMoveObserver.observe(board, {
        childList: true,
        subtree: true,
        attributes: true,
        attributeFilter: ["class", "style", "data-piece"]
      });
    }
    function stopBoardMoveObserver() {
      if (boardMoveObserver) {
        boardMoveObserver.disconnect();
        boardMoveObserver = null;
      }
    }
    function startTickLoop() {
      stopTickLoop();
      startBoardMoveObserver();
      const interval = Math.max(100, 1100 - (Number(BotState.updateSpeed) || 8) * 100);
      const scheduleNext = () => {
        tickTimer = setTimeout(() => {
          tick();
          if (BotState.hackEnabled)
            scheduleNext();
        }, interval);
      };
      tick();
      scheduleNext();
    }
    function stopTickLoop() {
      if (tickTimer)
        clearTimeout(tickTimer);
      tickTimer = null;
      stopBoardMoveObserver();
    }
    function startStateWatcher() {
      let lastHackEnabled = BotState.hackEnabled;
      let lastUpdateSpeed = BotState.updateSpeed;
      let lastPremoveEnabled = BotState.premoveEnabled;
      setInterval(() => {
        if (BotState.hackEnabled !== lastHackEnabled) {
          lastHackEnabled = BotState.hackEnabled;
          if (BotState.hackEnabled) {
            BotState.statusInfo = "Ready";
            ui.updateDisplay(pa());
            startTickLoop();
          } else {
            stopTickLoop();
            PositionCache.clear();
            clearArrows();
            cancelPendingMove();
            BotState.statusInfo = "Bot disabled";
            BotState.currentEvaluation = "-";
            BotState.bestMove = "-";
            ui.updateDisplay(pa());
          }
          ui.Settings.save();
        }
        if (BotState.updateSpeed !== lastUpdateSpeed) {
          lastUpdateSpeed = BotState.updateSpeed;
          if (BotState.hackEnabled)
            startTickLoop();
        }
        if (BotState.premoveEnabled !== lastPremoveEnabled) {
          lastPremoveEnabled = BotState.premoveEnabled;
          if (BotState.hackEnabled)
            startTickLoop();
        }
      }, 200);
      if (BotState.hackEnabled)
        startTickLoop();
    }
    function startAutoWatchers() {
      if (gameStartInterval)
        clearInterval(gameStartInterval);
      if (gameEndInterval)
        clearInterval(gameEndInterval);
      let gameEndDetected = false;
      gameEndInterval = setInterval(() => {
        const gameOverModal = qs(".game-over-modal-content");
        if (gameOverModal && !gameEndDetected) {
          console.log("GabiBot: Game over detected");
          clearArrows();
          cancelPendingMove();
          BotState.statusInfo = "Game ended, preparing new game...";
          BotState.currentEvaluation = "-";
          BotState.bestMove = "-";
          ui?.updateDisplay(pa());
          gameEndDetected = true;
          if (BotState.autoRematch) {
            console.log("GabiBot: Auto-rematch enabled");
            setTimeout(() => {
              const modal = qs(".game-over-modal-content");
              if (!modal)
                return console.log("GabiBot: [2s] Modal closed");
              const btn = qsa("button", modal).find(
                (b) => /rematch/i.test((b.textContent || "").trim()) || /rematch/i.test((b.getAttribute?.("aria-label") || "").trim())
              );
              if (btn)
                btn.click();
            }, 2e3);
            setTimeout(() => {
              const modal = qs(".game-over-modal-content");
              if (!modal)
                return;
              const btn = qsa("button", modal).find((b) => /new.*\d+.*min/i.test(b.textContent || ""));
              if (btn)
                btn.click();
            }, 12e3);
            setTimeout(async () => {
              const modal = qs(".game-over-modal-content");
              if (!modal)
                return;
              const closeBtn = qs('[aria-label="Close"]', modal);
              if (closeBtn) {
                closeBtn.click();
                await sleep(500);
              }
              const tab = qs('[data-tab="newGame"]') || qsa(".tabs-tab").find((t) => /new.*game/i.test(t.textContent || ""));
              if (tab) {
                tab.click();
                await sleep(400);
                const startBtn = qsa("button").find((b) => /start.*game/i.test((b.textContent || "").trim()));
                if (startBtn)
                  startBtn.click();
              }
            }, 22e3);
          }
        }
        if (!gameOverModal && gameEndDetected) {
          console.log("GabiBot: New game started, bot analyzing...");
          gameEndDetected = false;
          setLastFenProcessedMain("");
          setLastFenProcessedPremove("");
          setLastPremoveFen("");
          setLastPremoveUci("");
          lastFenSeen = "";
          if (BotState.hackEnabled) {
            BotState.statusInfo = "Ready";
            ui?.updateDisplay(pa());
            setTimeout(() => {
              if (BotState.hackEnabled)
                tick();
            }, 500);
          }
        }
      }, 1e3);
    }
    setTimeout(init, 3e3);
  })();
})();