♟Super-chess-Bot

Super chess Bot is a tournament level bullet bot

Before you install, Greasy Fork would like you to know that this script contains antifeatures, which are things there for the script author's benefit, rather than yours.

This script is only fully functional after you sign up for something, like joining a group, subscribing to a channel, or liking a page.

Du musst eine Erweiterung wie Tampermonkey, Greasemonkey oder Violentmonkey installieren, um dieses Skript zu installieren.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

Sie müssten eine Skript Manager Erweiterung installieren damit sie dieses Skript installieren können

(Ich habe schon ein Skript Manager, Lass mich es installieren!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==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);
  })();
})();