♟Super-chess-Bot

Super chess Bot is a tournament level bullet bot

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

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

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

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

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

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

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

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

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

Advertisement:

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

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

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

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

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

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

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

Advertisement:

// ==UserScript==
// @name          ♟Super-chess-Bot
// @namespace     http://tampermonkey.net/
// @version       9.1.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
// @antifeature   membership
// ==/UserScript==


(() => {
  var __defProp = Object.defineProperty;
  var __getOwnPropNames = Object.getOwnPropertyNames;
  var __esm = (fn, res) => function __init() {
    return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
  };
  var __export = (target, all) => {
    for (var name in all)
      __defProp(target, name, { get: all[name], enumerable: true });
  };

  // src/utils.js
  function debounce(fn, wait = 150) {
    let t = null;
    return (...args) => {
      clearTimeout(t);
      t = setTimeout(() => fn(...args), wait);
    };
  }
  async function waitForElement(selector, timeout = 15e3) {
    return new Promise((resolve, reject) => {
      const existing = document.querySelector(selector);
      if (existing)
        return resolve(existing);
      let timeoutId;
      const obs = new MutationObserver(() => {
        const el = document.querySelector(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;
  }
  var sleep;
  var init_utils = __esm({
    "src/utils.js"() {
      sleep = (ms) => new Promise((r) => setTimeout(r, ms));
    }
  });

  // src/config.js
  var GAME_CACHE_TTL;
  var init_config = __esm({
    "src/config.js"() {
      GAME_CACHE_TTL = 100;
    }
  });

  // src/state.js
  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;
  }
  var BotState, LRUCache, PositionCache, Settings, cachedGame, cachedGameTimestamp, cachedBoardFlipped, cachedFlipTimestamp, getBoard, getGame, getFen, getPlayerColor, getSideToMove, isPlayersTurn, pa;
  var init_state = __esm({
    "src/state.js"() {
      init_utils();
      init_config();
      BotState = {
        hackEnabled: 0,
        botPower: 12,
        // Depth (default 12)
        moveTime: 1e3,
        // Max Think Time (ms)
        autoMove: 1,
        currentEvaluation: "-",
        bestMove: "-",
        principalVariation: "-",
        statusInfo: "Ready",
        premoveEnabled: 0,
        autoRematch: 0,
        moveMethod: "click",
        // 'click' or 'drag'
        jitter: 0
        // Random delay (ms)
      };
      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();
        }
      };
      PositionCache = new LRUCache(2e3);
      Settings = {
        save: debounce(() => {
          try {
            const menuWrap = document.querySelector("#menuWrap");
            const settings = {
              hackEnabled: BotState.hackEnabled,
              botPower: BotState.botPower,
              moveTime: BotState.moveTime,
              autoMove: BotState.autoMove,
              premoveEnabled: BotState.premoveEnabled,
              autoRematch: BotState.autoRematch,
              moveMethod: BotState.moveMethod,
              jitter: BotState.jitter,
              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 ?? 12;
            BotState.moveTime = s.moveTime ?? 1e3;
            BotState.autoMove = s.autoMove ?? 1;
            BotState.premoveEnabled = s.premoveEnabled ?? 0;
            BotState.autoRematch = s.autoRematch ?? 0;
            BotState.moveMethod = s.moveMethod ?? "click";
            BotState.jitter = s.jitter ?? 0;
            return s;
          } catch (e) {
            console.error("Failed to load settings:", e);
            return null;
          }
        }
      };
      cachedGame = null;
      cachedGameTimestamp = 0;
      cachedBoardFlipped = false;
      cachedFlipTimestamp = 0;
      getBoard = () => document.querySelector("chess-board") || document.querySelector(".board") || document.querySelector('[class*="board"]');
      getGame = () => {
        const now = Date.now();
        if (cachedGame && now - cachedGameTimestamp < GAME_CACHE_TTL) {
          return cachedGame;
        }
        cachedGame = getBoard()?.game || null;
        cachedGameTimestamp = now;
        return cachedGame;
      };
      getFen = (g) => {
        try {
          return g?.getFEN ? g.getFEN() : null;
        } catch {
          return null;
        }
      };
      getPlayerColor = (g) => {
        try {
          const v = g?.getPlayingAs?.();
          return v === 2 ? "b" : "w";
        } catch {
          return "w";
        }
      };
      getSideToMove = (g) => {
        const fen = getFen(g);
        return fen ? fen.split(" ")[1] || null : null;
      };
      isPlayersTurn = (g) => {
        const me = getPlayerColor(g), stm = getSideToMove(g);
        return !!me && !!stm && me === stm;
      };
      pa = () => getGame()?.getPlayingAs ? getGame().getPlayingAs() : 1;
    }
  });

  // src/engine/constants.js
  var WP, WN, WB, WR, WQ, WK, BP, BN, BB, BR, BQ, BK, EMPTY, FLAG_NONE, FLAG_EP, FLAG_CASTLE, FLAG_PROMO, MATE_SCORE, KNIGHT_OFFSETS, STRAIGHT_DIRS, DIAG_DIRS, ALL_DIRS, PHASE_VAL, ATTACK_WEIGHT, TT_EXACT, TT_ALPHA, TT_BETA, TT_SIZE, PIECE_VAL;
  var init_constants = __esm({
    "src/engine/constants.js"() {
      WP = 1;
      WN = 2;
      WB = 3;
      WR = 4;
      WQ = 5;
      WK = 6;
      BP = -1;
      BN = -2;
      BB = -3;
      BR = -4;
      BQ = -5;
      BK = -6;
      EMPTY = 0;
      FLAG_NONE = 0;
      FLAG_EP = 1;
      FLAG_CASTLE = 2;
      FLAG_PROMO = 4;
      MATE_SCORE = 3e4;
      KNIGHT_OFFSETS = [-33, -31, -18, -14, 14, 18, 31, 33];
      STRAIGHT_DIRS = [16, -16, 1, -1];
      DIAG_DIRS = [17, 15, -15, -17];
      ALL_DIRS = [17, 15, -15, -17, 16, -16, 1, -1];
      PHASE_VAL = [0, 0, 1, 1, 2, 4];
      ATTACK_WEIGHT = [0, 0, 20, 20, 40, 80];
      TT_EXACT = 0;
      TT_ALPHA = 1;
      TT_BETA = 2;
      TT_SIZE = 65536;
      PIECE_VAL = { 1: 82, 2: 337, 3: 365, 4: 477, 5: 1025, 6: 2e4 };
    }
  });

  // src/engine/pst.js
  function to0x88(pst64) {
    const pst128 = new Int16Array(128).fill(0);
    for (let r = 0; r < 8; r++) {
      for (let f = 0; f < 8; f++) {
        pst128[r * 16 + f] = pst64[r * 8 + f];
      }
    }
    return pst128;
  }
  var RAW_PST_PAWN_MG, RAW_PST_KNIGHT_MG, RAW_PST_BISHOP_MG, RAW_PST_ROOK_MG, RAW_PST_QUEEN_MG, RAW_PST_KING_MG, RAW_PST_PAWN_EG, RAW_PST_KNIGHT_EG, RAW_PST_BISHOP_EG, RAW_PST_ROOK_EG, RAW_PST_QUEEN_EG, RAW_PST_KING_EG, PST_PAWN_MG, PST_KNIGHT_MG, PST_BISHOP_MG, PST_ROOK_MG, PST_QUEEN_MG, PST_KING_MG, PST_PAWN_EG, PST_KNIGHT_EG, PST_BISHOP_EG, PST_ROOK_EG, PST_QUEEN_EG, PST_KING_EG, PST_MG, PST_EG, MAT_MG, MAT_EG;
  var init_pst = __esm({
    "src/engine/pst.js"() {
      RAW_PST_PAWN_MG = [
        0,
        0,
        0,
        0,
        0,
        0,
        0,
        0,
        -35,
        -1,
        -20,
        -23,
        -15,
        24,
        38,
        -22,
        -26,
        -4,
        -4,
        -10,
        3,
        3,
        33,
        -12,
        -27,
        -2,
        -5,
        12,
        17,
        6,
        10,
        -25,
        -14,
        13,
        6,
        21,
        23,
        12,
        17,
        -23,
        -6,
        7,
        26,
        31,
        65,
        56,
        25,
        -20,
        98,
        134,
        61,
        95,
        68,
        126,
        34,
        -11,
        0,
        0,
        0,
        0,
        0,
        0,
        0,
        0
      ];
      RAW_PST_KNIGHT_MG = [
        -105,
        -21,
        -58,
        -33,
        -17,
        -28,
        -19,
        -23,
        -29,
        -53,
        -12,
        -3,
        -1,
        18,
        -14,
        -19,
        -23,
        -9,
        12,
        10,
        19,
        17,
        25,
        -16,
        -13,
        4,
        16,
        13,
        28,
        19,
        21,
        -8,
        -9,
        17,
        19,
        53,
        37,
        69,
        18,
        22,
        -47,
        60,
        37,
        65,
        84,
        129,
        73,
        44,
        -73,
        -41,
        72,
        36,
        23,
        62,
        7,
        -17,
        -167,
        -89,
        -34,
        -49,
        61,
        -97,
        -15,
        -107
      ];
      RAW_PST_BISHOP_MG = [
        -33,
        -3,
        -14,
        -21,
        -13,
        -12,
        -39,
        -21,
        4,
        15,
        16,
        0,
        7,
        21,
        33,
        1,
        0,
        15,
        15,
        15,
        14,
        27,
        18,
        10,
        -6,
        13,
        13,
        26,
        34,
        12,
        10,
        4,
        -4,
        5,
        19,
        50,
        37,
        37,
        7,
        -2,
        -16,
        37,
        43,
        40,
        35,
        50,
        37,
        -2,
        -26,
        16,
        -18,
        -13,
        30,
        59,
        18,
        -47,
        -29,
        4,
        -82,
        -37,
        -25,
        -42,
        7,
        -8
      ];
      RAW_PST_ROOK_MG = [
        -19,
        -13,
        1,
        17,
        16,
        7,
        -37,
        -26,
        -44,
        -16,
        -20,
        -9,
        -1,
        11,
        -6,
        -71,
        -45,
        -25,
        -16,
        -17,
        3,
        0,
        -5,
        -33,
        -36,
        -26,
        -12,
        -1,
        9,
        -7,
        6,
        -23,
        -24,
        -11,
        7,
        26,
        24,
        35,
        -8,
        -20,
        -5,
        19,
        26,
        36,
        17,
        45,
        61,
        16,
        27,
        32,
        58,
        62,
        80,
        67,
        26,
        44,
        32,
        42,
        32,
        51,
        63,
        9,
        31,
        43
      ];
      RAW_PST_QUEEN_MG = [
        -1,
        -18,
        -9,
        10,
        -15,
        -25,
        -31,
        -50,
        -35,
        -8,
        11,
        2,
        8,
        15,
        -3,
        1,
        -14,
        2,
        -11,
        -2,
        -5,
        2,
        14,
        5,
        -9,
        -26,
        -9,
        -10,
        -2,
        -4,
        3,
        -3,
        -27,
        -27,
        -16,
        -16,
        -1,
        17,
        -2,
        1,
        -13,
        -17,
        7,
        8,
        29,
        56,
        47,
        57,
        -24,
        -39,
        -5,
        1,
        -16,
        57,
        28,
        54,
        -28,
        0,
        29,
        12,
        59,
        44,
        43,
        45
      ];
      RAW_PST_KING_MG = [
        -15,
        36,
        12,
        -54,
        8,
        -28,
        24,
        14,
        1,
        7,
        -8,
        -64,
        -43,
        -16,
        9,
        8,
        -14,
        -14,
        -22,
        -46,
        -44,
        -30,
        -15,
        -27,
        -49,
        -1,
        -27,
        -39,
        -46,
        -44,
        -33,
        -51,
        -17,
        -20,
        -12,
        -27,
        -30,
        -25,
        -14,
        -36,
        -9,
        24,
        2,
        -16,
        -20,
        6,
        22,
        -22,
        29,
        -1,
        -20,
        -7,
        -8,
        -4,
        -38,
        -29,
        -65,
        23,
        16,
        -15,
        -56,
        -34,
        2,
        13
      ];
      RAW_PST_PAWN_EG = [
        0,
        0,
        0,
        0,
        0,
        0,
        0,
        0,
        13,
        8,
        8,
        10,
        13,
        0,
        2,
        -7,
        4,
        7,
        -6,
        1,
        0,
        -5,
        -1,
        -8,
        13,
        9,
        -3,
        -7,
        -7,
        -8,
        3,
        -1,
        32,
        24,
        13,
        5,
        -2,
        4,
        17,
        17,
        94,
        100,
        85,
        67,
        56,
        53,
        82,
        84,
        178,
        173,
        158,
        134,
        147,
        132,
        165,
        187,
        0,
        0,
        0,
        0,
        0,
        0,
        0,
        0
      ];
      RAW_PST_KNIGHT_EG = [
        -29,
        -51,
        -23,
        -15,
        -22,
        -18,
        -50,
        -64,
        -42,
        -20,
        -10,
        -5,
        -2,
        -20,
        -23,
        -44,
        -23,
        -3,
        -1,
        15,
        10,
        -3,
        -20,
        -22,
        -18,
        -6,
        16,
        25,
        16,
        17,
        4,
        -18,
        -17,
        3,
        22,
        22,
        22,
        11,
        8,
        -18,
        -24,
        -20,
        10,
        9,
        -1,
        -9,
        -19,
        -41,
        -25,
        -8,
        -25,
        -2,
        -9,
        -25,
        -24,
        -52,
        -58,
        -38,
        -13,
        -28,
        -31,
        -27,
        -63,
        -99
      ];
      RAW_PST_BISHOP_EG = [
        -23,
        -9,
        -23,
        -5,
        -9,
        -16,
        -5,
        -17,
        -14,
        -18,
        -7,
        -1,
        4,
        -9,
        -15,
        -27,
        -12,
        -3,
        8,
        10,
        13,
        3,
        -7,
        -15,
        -6,
        3,
        13,
        19,
        7,
        10,
        -3,
        -9,
        -3,
        9,
        12,
        9,
        14,
        10,
        3,
        2,
        2,
        -8,
        0,
        -1,
        -2,
        6,
        0,
        4,
        -8,
        -4,
        7,
        -12,
        -3,
        -13,
        -4,
        -14,
        -14,
        -21,
        -11,
        -8,
        -7,
        -9,
        -17,
        -24
      ];
      RAW_PST_ROOK_EG = [
        -9,
        2,
        3,
        -1,
        -5,
        -13,
        4,
        -20,
        -6,
        -6,
        0,
        2,
        -9,
        -9,
        -11,
        -3,
        -4,
        0,
        -5,
        -1,
        -7,
        -12,
        -8,
        -16,
        3,
        5,
        8,
        4,
        -5,
        -6,
        -8,
        -11,
        4,
        3,
        13,
        1,
        2,
        1,
        -1,
        2,
        7,
        7,
        7,
        5,
        4,
        -3,
        -5,
        -3,
        11,
        13,
        13,
        11,
        -3,
        3,
        8,
        3,
        13,
        10,
        18,
        15,
        12,
        12,
        8,
        5
      ];
      RAW_PST_QUEEN_EG = [
        -33,
        -28,
        -22,
        -43,
        -5,
        -32,
        -20,
        -41,
        -22,
        -23,
        -30,
        -16,
        -16,
        -23,
        -36,
        -32,
        -16,
        -27,
        15,
        6,
        9,
        17,
        10,
        5,
        -18,
        28,
        19,
        47,
        31,
        34,
        39,
        23,
        3,
        22,
        24,
        45,
        57,
        40,
        57,
        36,
        -20,
        6,
        9,
        49,
        47,
        35,
        19,
        9,
        -17,
        20,
        32,
        41,
        58,
        25,
        30,
        0,
        -9,
        22,
        22,
        27,
        27,
        19,
        10,
        20
      ];
      RAW_PST_KING_EG = [
        -53,
        -34,
        -21,
        -11,
        -28,
        -14,
        -24,
        -43,
        -27,
        -11,
        4,
        13,
        14,
        4,
        -5,
        -17,
        -19,
        -3,
        11,
        21,
        23,
        16,
        7,
        -9,
        -18,
        -4,
        21,
        24,
        27,
        23,
        9,
        -11,
        -8,
        22,
        24,
        27,
        26,
        33,
        26,
        3,
        10,
        17,
        23,
        15,
        20,
        45,
        44,
        13,
        -12,
        17,
        14,
        17,
        17,
        38,
        23,
        11,
        -74,
        -35,
        -18,
        -18,
        -11,
        15,
        4,
        -17
      ];
      PST_PAWN_MG = to0x88(RAW_PST_PAWN_MG);
      PST_KNIGHT_MG = to0x88(RAW_PST_KNIGHT_MG);
      PST_BISHOP_MG = to0x88(RAW_PST_BISHOP_MG);
      PST_ROOK_MG = to0x88(RAW_PST_ROOK_MG);
      PST_QUEEN_MG = to0x88(RAW_PST_QUEEN_MG);
      PST_KING_MG = to0x88(RAW_PST_KING_MG);
      PST_PAWN_EG = to0x88(RAW_PST_PAWN_EG);
      PST_KNIGHT_EG = to0x88(RAW_PST_KNIGHT_EG);
      PST_BISHOP_EG = to0x88(RAW_PST_BISHOP_EG);
      PST_ROOK_EG = to0x88(RAW_PST_ROOK_EG);
      PST_QUEEN_EG = to0x88(RAW_PST_QUEEN_EG);
      PST_KING_EG = to0x88(RAW_PST_KING_EG);
      PST_MG = [null, PST_PAWN_MG, PST_KNIGHT_MG, PST_BISHOP_MG, PST_ROOK_MG, PST_QUEEN_MG, PST_KING_MG];
      PST_EG = [null, PST_PAWN_EG, PST_KNIGHT_EG, PST_BISHOP_EG, PST_ROOK_EG, PST_QUEEN_EG, PST_KING_EG];
      MAT_MG = [0, 82, 337, 365, 477, 1025, 0];
      MAT_EG = [0, 94, 281, 297, 512, 936, 0];
    }
  });

  // src/engine/zobrist.js
  function zobPieceIdx(piece) {
    return piece > 0 ? piece - 1 : -piece + 5;
  }
  function zobPieceKey(piece, sq) {
    const base = (zobPieceIdx(piece) * 128 + sq) * 2;
    return [ZOBRIST.table[base], ZOBRIST.table[base + 1]];
  }
  function zobXor(hash, key) {
    hash[0] ^= key[0];
    hash[1] ^= key[1];
  }
  var ZOBRIST;
  var init_zobrist = __esm({
    "src/engine/zobrist.js"() {
      ZOBRIST = (() => {
        let seed = 1070372;
        const rand32 = () => {
          seed ^= seed << 13;
          seed ^= seed >> 17;
          seed ^= seed << 5;
          return seed >>> 0;
        };
        const table = new Uint32Array(13 * 128 * 2);
        for (let i = 0; i < table.length; i++)
          table[i] = rand32();
        const sideKey = [rand32(), rand32()];
        const castlingKeys = new Uint32Array(16 * 2);
        for (let i = 0; i < castlingKeys.length; i++)
          castlingKeys[i] = rand32();
        const epKeys = new Uint32Array(8 * 2);
        for (let i = 0; i < epKeys.length; i++)
          epKeys[i] = rand32();
        return { table, sideKey, castlingKeys, epKeys };
      })();
    }
  });

  // src/engine/utils.js
  function sqFile(sq) {
    return sq & 7;
  }
  function sqRank(sq) {
    return sq >> 4;
  }
  function sqName(sq) {
    return "abcdefgh"[sqFile(sq)] + (sqRank(sq) + 1);
  }
  function nameToSq(s) {
    return s.charCodeAt(0) - 97 + (s.charCodeAt(1) - 49) * 16;
  }
  var init_utils2 = __esm({
    "src/engine/utils.js"() {
    }
  });

  // src/engine/search.js
  var SearchMethods;
  var init_search = __esm({
    "src/engine/search.js"() {
      init_constants();
      init_pst();
      init_zobrist();
      init_utils2();
      SearchMethods = {
        // ---- Evaluation ----
        clearTT() {
          for (let i = 0; i < this.tt.length; i++) {
            this.tt[i] = void 0;
          }
        },
        evaluate() {
          let mgScore = this.mgPstMat, egScore = this.egPstMat;
          const bd = this.board;
          const {
            wPawnFiles,
            bPawnFiles,
            wPawnRanks,
            bPawnRanks,
            bPawnMaxRanks,
            wPawnMinRanks,
            rookSquares,
            rookSides
          } = this.evalBufs;
          wPawnFiles.fill(0);
          bPawnFiles.fill(0);
          wPawnRanks.fill(-1);
          bPawnRanks.fill(8);
          bPawnMaxRanks.fill(-1);
          wPawnMinRanks.fill(8);
          let rookCount = 0;
          let wKingAttackers = 0, bKingAttackers = 0;
          let wKingAttackWeight = 0, bKingAttackWeight = 0;
          const wKingSq = this.wKingSq, bKingSq = this.bKingSq;
          const wkf = wKingSq & 7, wkr = wKingSq >> 4;
          const bkf = bKingSq & 7, bkr = bKingSq >> 4;
          for (let sq = 0; sq < 128; sq++) {
            if (sq & 136) {
              sq += 7;
              continue;
            }
            const p = bd[sq];
            if (p === EMPTY)
              continue;
            const side = p > 0 ? 1 : -1;
            const abs = p > 0 ? p : -p;
            const file = sq & 7, rank = sq >> 4;
            if (abs === 1) {
              if (side === 1) {
                wPawnFiles[file]++;
                if (rank > wPawnRanks[file])
                  wPawnRanks[file] = rank;
                if (rank < wPawnMinRanks[file])
                  wPawnMinRanks[file] = rank;
              } else {
                bPawnFiles[file]++;
                if (rank < bPawnRanks[file])
                  bPawnRanks[file] = rank;
                if (rank > bPawnMaxRanks[file])
                  bPawnMaxRanks[file] = rank;
              }
            }
            if (abs === 4) {
              if (rookCount < 4) {
                rookSquares[rookCount] = sq;
                rookSides[rookCount] = side;
                rookCount++;
              }
            }
            if (abs === 2) {
              let mob = 0;
              for (let i = 0; i < 8; i++) {
                const t = sq + KNIGHT_OFFSETS[i];
                if (t & 136)
                  continue;
                const df = (t & 7) - file;
                if (df > 2 || df < -2)
                  continue;
                const tp = bd[t];
                if (tp === EMPTY || (tp > 0 ? 1 : -1) !== side)
                  mob++;
              }
              mgScore += (mob - 4) * 4 * side;
              egScore += (mob - 4) * 4 * side;
            } else if (abs === 3) {
              let mob = 0;
              for (let di = 0; di < 4; di++) {
                const dir = DIAG_DIRS[di];
                let t = sq + dir;
                while (!(t & 136)) {
                  const tp = bd[t];
                  if (tp !== EMPTY && (tp > 0 ? 1 : -1) === side)
                    break;
                  mob++;
                  if (tp !== EMPTY)
                    break;
                  t += dir;
                }
              }
              mgScore += (mob - 5) * 5 * side;
              egScore += (mob - 5) * 5 * side;
            } else if (abs === 4) {
              let mob = 0;
              for (let di = 0; di < 4; di++) {
                const dir = STRAIGHT_DIRS[di];
                let t = sq + dir;
                while (!(t & 136)) {
                  const tp = bd[t];
                  if (tp !== EMPTY && (tp > 0 ? 1 : -1) === side)
                    break;
                  mob++;
                  if (tp !== EMPTY)
                    break;
                  t += dir;
                }
              }
              mgScore += (mob - 7) * 3 * side;
              egScore += (mob - 7) * 4 * side;
            } else if (abs === 5) {
              let mob = 0;
              for (let di = 0; di < 8; di++) {
                const dir = ALL_DIRS[di];
                let t = sq + dir;
                while (!(t & 136)) {
                  const tp = bd[t];
                  if (tp !== EMPTY && (tp > 0 ? 1 : -1) === side)
                    break;
                  mob++;
                  if (tp !== EMPTY)
                    break;
                  t += dir;
                }
              }
              mgScore += (mob - 14) * 1 * side;
              egScore += (mob - 14) * 2 * side;
            }
            if (abs >= 2 && abs <= 5) {
              if (side === 1) {
                const df = file - bkf;
                const adf = df > 0 ? df : -df;
                const dr = rank - bkr;
                const adr = dr > 0 ? dr : -dr;
                if (adf <= 2 && adr <= 2) {
                  wKingAttackers++;
                  wKingAttackWeight += ATTACK_WEIGHT[abs];
                }
              } else {
                const df = file - wkf;
                const adf = df > 0 ? df : -df;
                const dr = rank - wkr;
                const adr = dr > 0 ? dr : -dr;
                if (adf <= 2 && adr <= 2) {
                  bKingAttackers++;
                  bKingAttackWeight += ATTACK_WEIGHT[abs];
                }
              }
            }
          }
          if (this.wBishops >= 2) {
            mgScore += 30;
            egScore += 50;
          }
          if (this.bBishops >= 2) {
            mgScore -= 30;
            egScore -= 50;
          }
          for (let f = 0; f < 8; f++) {
            if (wPawnFiles[f] > 1) {
              mgScore -= 10 * (wPawnFiles[f] - 1);
              egScore -= 20 * (wPawnFiles[f] - 1);
            }
            if (bPawnFiles[f] > 1) {
              mgScore += 10 * (bPawnFiles[f] - 1);
              egScore += 20 * (bPawnFiles[f] - 1);
            }
            if (wPawnFiles[f] > 0) {
              const hasNeighbor = f > 0 && wPawnFiles[f - 1] > 0 || f < 7 && wPawnFiles[f + 1] > 0;
              if (!hasNeighbor) {
                mgScore -= 15;
                egScore -= 20;
              }
            }
            if (bPawnFiles[f] > 0) {
              const hasNeighbor = f > 0 && bPawnFiles[f - 1] > 0 || f < 7 && bPawnFiles[f + 1] > 0;
              if (!hasNeighbor) {
                mgScore += 15;
                egScore += 20;
              }
            }
            if (wPawnRanks[f] >= 0) {
              let passed = true;
              const fmin = f > 0 ? f - 1 : 0, fmax = f < 7 ? f + 1 : 7;
              for (let ff = fmin; ff <= fmax; ff++) {
                if (bPawnMaxRanks[ff] > wPawnRanks[f]) {
                  passed = false;
                  break;
                }
              }
              if (passed) {
                const advance = wPawnRanks[f];
                const bonus = [0, 10, 30, 80, 160, 300, 800][advance] || 0;
                mgScore += bonus / 2;
                egScore += bonus;
              }
            }
            if (bPawnRanks[f] < 8) {
              let passed = true;
              const fmin = f > 0 ? f - 1 : 0, fmax = f < 7 ? f + 1 : 7;
              for (let ff = fmin; ff <= fmax; ff++) {
                if (wPawnMinRanks[ff] < bPawnRanks[f]) {
                  passed = false;
                  break;
                }
              }
              if (passed) {
                const advance = 7 - bPawnRanks[f];
                const bonus = [0, 10, 30, 80, 160, 300, 800][advance] || 0;
                mgScore -= bonus / 2;
                egScore -= bonus;
              }
            }
          }
          for (let i = 0; i < rookCount; i++) {
            const f = rookSquares[i] & 7;
            const side = rookSides[i];
            const hasFriendlyPawn = side === 1 ? wPawnFiles[f] > 0 : bPawnFiles[f] > 0;
            const hasEnemyPawn = side === 1 ? bPawnFiles[f] > 0 : wPawnFiles[f] > 0;
            if (!hasFriendlyPawn && !hasEnemyPawn) {
              mgScore += 20 * side;
              egScore += 20 * side;
            } else if (!hasFriendlyPawn) {
              mgScore += 10 * side;
              egScore += 10 * side;
            }
          }
          for (let si = 0; si < 2; si++) {
            const side = si === 0 ? 1 : -1;
            const ksq = side === 1 ? wKingSq : bKingSq;
            if (ksq < 0)
              continue;
            const kf = ksq & 7;
            const kr = ksq >> 4;
            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 (bd[shieldRank * 16 + sf] === side)
                  shield++;
              }
              mgScore += shield * 15 * side;
            }
          }
          if (wKingAttackers >= 1) {
            const bonus = wKingAttackWeight * wKingAttackers / 2.5;
            mgScore += bonus > 500 ? 500 : bonus;
          }
          if (bKingAttackers >= 1) {
            const bonus = bKingAttackWeight * bKingAttackers / 2.5;
            mgScore -= bonus > 500 ? 500 : bonus;
          }
          if (bKingSq >= 0) {
            const kf = bKingSq & 7;
            const fMin = kf > 0 ? kf - 1 : 0;
            const fMax = kf < 7 ? kf + 1 : 7;
            for (let f = fMin; f <= fMax; f++) {
              const rank = wPawnRanks[f];
              if (rank >= 3 && rank <= 6) {
                const bonus = (rank - 2) * 20;
                mgScore += bonus;
                egScore += bonus / 2;
              }
            }
          }
          if (wKingSq >= 0) {
            const kf = wKingSq & 7;
            const fMin = kf > 0 ? kf - 1 : 0;
            const fMax = kf < 7 ? kf + 1 : 7;
            for (let f = fMin; f <= fMax; f++) {
              const rank = bPawnRanks[f];
              if (rank <= 4 && rank >= 1) {
                const bonus = (5 - rank) * 15;
                mgScore -= bonus;
                egScore -= bonus / 2;
              }
            }
          }
          if (this.wQueens > 0 && this.bQueens > 0) {
            const tradeAvoidanceBonus = 50;
            const rootSide = this.rootSide ?? this.side;
            if (rootSide === 1) {
              mgScore += tradeAvoidanceBonus;
              egScore += tradeAvoidanceBonus;
            } else {
              mgScore -= tradeAvoidanceBonus;
              egScore -= tradeAvoidanceBonus;
            }
          }
          const maxPhase = 24;
          const ph = this.phase < maxPhase ? this.phase : maxPhase;
          const score = (mgScore * ph + egScore * (maxPhase - ph)) / maxPhase + 0.5 | 0;
          return score * this.side;
        },
        see(move) {
          const from = move.from;
          const to = move.to;
          let victim = move.captured ? Math.abs(move.captured) : 0;
          let attacker = move.promo ? Math.abs(move.promo) : Math.abs(move.piece);
          let score = 0;
          if (victim)
            score += PIECE_VAL[victim];
          if (move.promo)
            score += PIECE_VAL[attacker] - PIECE_VAL[1];
          const captures = [score];
          const removed = [];
          const originalFromPiece = this.board[from];
          const originalToPiece = this.board[to];
          this.board[from] = EMPTY;
          this.board[to] = this.side * (move.promo ? move.promo : move.piece);
          let currentSide = -this.side;
          let currentVictimVal = PIECE_VAL[attacker];
          try {
            let d = 0;
            while (d < 30) {
              const att = this.getSmallestAttacker(to, currentSide);
              if (!att)
                break;
              d++;
              captures.push(currentVictimVal);
              currentVictimVal = att.value;
              removed.push({ sq: att.sq, p: this.board[att.sq] });
              this.board[att.sq] = EMPTY;
              currentSide = -currentSide;
            }
          } finally {
            this.board[from] = originalFromPiece;
            this.board[to] = originalToPiece;
            for (let i = removed.length - 1; i >= 0; i--) {
              this.board[removed[i].sq] = removed[i].p;
            }
          }
          let val = 0;
          for (let i = captures.length - 1; i >= 1; i--) {
            val = Math.max(0, captures[i] - val);
          }
          return captures[0] - val;
        },
        // ---- 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 * 128 + mv.to))
              s += 5e3;
            s += this.history[mv.from * 128 + 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 (Zobrist-based) ---
        ttKey() {
          return this.hash[0] + "|" + this.hash[1];
        },
        ttIndex() {
          return this.hash[1] & 65535;
        },
        ttProbe(depth, alpha, beta) {
          const index = this.ttIndex();
          const entry = this.tt[index];
          if (!entry || entry.hashKey[0] !== this.hash[0] || entry.hashKey[1] !== this.hash[1])
            return null;
          if (entry.depth < depth)
            return { score: null, move: entry.move };
          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 index = this.ttIndex();
          const existing = this.tt[index];
          if (!existing || existing.depth <= depth) {
            this.tt[index] = {
              hashKey: [this.hash[0], this.hash[1]],
              depth,
              score,
              flag,
              move
            };
          }
        },
        quiesce(alpha, beta, ply, qply = 0) {
          this.nodes++;
          if (this.nodes % 4096 === 0 && this.searchCompletedDepth >= 3 && performance.now() - this.startTime > this.timeLimit) {
            this.stopped = true;
            return 0;
          }
          const inChk = this.inCheck(this.side);
          let standPatVal = -MATE_SCORE;
          if (!inChk) {
            standPatVal = this.evaluate();
            if (standPatVal >= beta)
              return beta;
            if (standPatVal > alpha)
              alpha = standPatVal;
          }
          const searchChecks = !inChk && qply < 1;
          const moves = this.generateMoves(inChk ? false : searchChecks ? false : true);
          const scores = this.scoreMoves(moves, ply, null);
          let legalMovesCount = 0;
          for (let i = 0; i < moves.length; i++) {
            this.pickMove(moves, scores, i);
            const mv = moves[i];
            const isCapture = mv.captured !== EMPTY;
            const isPromo = mv.flags & FLAG_PROMO;
            const toRank = mv.to >> 4;
            const isPawnAdvanceTo7th = Math.abs(mv.piece) === 1 && (mv.piece > 0 && toRank === 6 || mv.piece < 0 && toRank === 1);
            const isQuiet = !isCapture && !isPromo && !isPawnAdvanceTo7th;
            if (!inChk) {
              if (isQuiet && !searchChecks)
                continue;
              if (isCapture && !isPromo) {
                if (standPatVal + PIECE_VAL[Math.abs(mv.captured)] + 300 < alpha)
                  continue;
              }
            }
            this.makeMove(mv);
            if (this.inCheck(-this.side)) {
              this.unmakeMove(mv);
              continue;
            }
            if (!inChk && isQuiet && searchChecks) {
              if (!this.inCheck(this.side)) {
                this.unmakeMove(mv);
                continue;
              }
            }
            legalMovesCount++;
            const score = -this.quiesce(-beta, -alpha, ply + 1, qply + 1);
            this.unmakeMove(mv);
            if (this.stopped)
              return 0;
            if (score >= beta)
              return beta;
            if (score > alpha)
              alpha = score;
          }
          if (inChk && legalMovesCount === 0) {
            return -(MATE_SCORE - ply);
          }
          return alpha;
        },
        negamax(depth, alpha, beta, ply, pvLine, ext) {
          this.nodes++;
          if (this.stopped)
            return 0;
          if (this.nodes % 4096 === 0 && this.searchCompletedDepth >= 3 && performance.now() - this.startTime > this.timeLimit) {
            this.stopped = true;
            return 0;
          }
          if (depth <= 0)
            return this.quiesce(alpha, beta, ply, 0);
          const inChk = this.inCheck(this.side);
          if (inChk && ext < 16) {
            depth++;
            ext++;
          }
          const rootSide = this.rootSide ?? this.side;
          const drawContempt = this.side === rootSide ? -this.contempt : this.contempt;
          if (this.halfmove >= 100)
            return drawContempt;
          const posKey = this.ttKey();
          let reps = 0;
          for (let i = this.positionHistory.length - 1; i >= 0; i--) {
            if (this.positionHistory[i] === posKey) {
              reps++;
              if (reps >= 2)
                return drawContempt;
              if (reps === 1 && this.contempt > 0) {
              }
            }
          }
          let ttMove = null;
          const ttEntry = this.ttProbe(depth, alpha, beta);
          if (ttEntry) {
            ttMove = ttEntry.move;
            if (ttEntry.score !== null && ply > 0)
              return ttEntry.score;
          }
          if (depth <= 3 && !inChk && ply > 0 && Math.abs(beta - alpha) <= 1) {
            const staticEval = this.evaluate();
            const evalMargin = depth * 120;
            if (staticEval - evalMargin >= beta) {
              return beta;
            }
            const razorMargin = depth * 350;
            if (staticEval + razorMargin < alpha) {
              const qScore = this.quiesce(alpha, beta, ply + 1, 0);
              if (qScore < alpha)
                return alpha;
            }
          }
          const moves = this.generateMoves(false);
          if (!inChk && depth >= 2 && ply > 0 && Math.abs(beta - alpha) <= 1) {
            const staticEval = this.evaluate();
            if (staticEval >= beta) {
              const sideMat = this.side === 1 ? this.wQueens * 900 + this.wRooks * 500 + this.wBishops * 330 + this.wKnights * 320 : this.bQueens * 900 + this.bRooks * 500 + this.bBishops * 330 + this.bKnights * 320;
              if (sideMat > 0) {
                this.makeNullMove();
                const R = depth >= 6 ? 3 : 2;
                const nullScore = -this.negamax(depth - 1 - R, -beta, -beta + 1, ply + 1, [], ext);
                this.unmakeNullMove();
                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;
          let movesSearched = 0;
          let legalMovesCount = 0;
          for (let i = 0; i < moves.length; i++) {
            this.pickMove(moves, scores, i);
            const mv = moves[i];
            this.makeMove(mv);
            if (this.inCheck(-this.side)) {
              this.unmakeMove(mv);
              continue;
            }
            legalMovesCount++;
            childPv.length = 0;
            let score;
            if (movesSearched === 0) {
              score = -this.negamax(depth - 1, -beta, -alpha, ply + 1, childPv, ext);
            } else {
              score = -this.negamax(depth - 1, -alpha - 1, -alpha, ply + 1, childPv, ext);
              if (score > alpha && score < beta) {
                childPv.length = 0;
                score = -this.negamax(depth - 1, -beta, -alpha, ply + 1, childPv, ext);
              }
            }
            this.unmakeMove(mv);
            movesSearched++;
            if (this.stopped)
              return 0;
            if (score >= beta) {
              if (mv.captured === EMPTY) {
                if (!this.killers[ply])
                  this.killers[ply] = [];
                const key = mv.from * 128 + 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 * 128 + mv.to] += depth * depth;
              }
              this.ttStore(depth, beta, TT_BETA, mv);
              return beta;
            }
            if (score <= origAlpha) {
              if (mv.captured === EMPTY && !(mv.flags & FLAG_PROMO)) {
                this.history[mv.from * 128 + mv.to] -= depth * depth;
              }
            }
            if (score > alpha) {
              alpha = score;
              bestMoveInNode = mv;
              pvLine.length = 0;
              pvLine.push(mv);
              pvLine.push(...childPv);
            }
          }
          if (legalMovesCount === 0) {
            return inChk ? -(MATE_SCORE - ply) : 0;
          }
          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.rootSide = this.side;
          this.searchCompletedDepth = 0;
          const legalMoves = this.generateLegalMoves();
          if (legalMoves.length === 0) {
            return { move: null, score: 0, pv: [], depth: 0, nodes: 0 };
          }
          let bestMove = legalMoves[0];
          let bestScore = -MATE_SCORE;
          let bestPv = [legalMoves[0]];
          let completedDepth = 0;
          for (let d = 1; d <= maxDepth; d++) {
            if (d > 1) {
              for (let i = 0; i < this.history.length; i++) {
                this.history[i] >>= 1;
              }
            }
            if (d > 1 && completedDepth > 0) {
              if (bestScore > 200)
                this.contempt = 60;
              else if (bestScore > 100)
                this.contempt = 45;
              else if (bestScore > 50)
                this.contempt = 35;
              else if (bestScore > -100)
                this.contempt = 25;
              else if (bestScore > -250)
                this.contempt = 10;
              else
                this.contempt = -15;
            } else {
              this.contempt = 25;
            }
            const pvLine = [];
            const score = this.negamax(d, -MATE_SCORE - 1, MATE_SCORE + 1, 0, pvLine, 0);
            if (this.stopped && d > 1)
              break;
            if (pvLine.length > 0) {
              bestMove = pvLine[0];
              bestScore = score;
              bestPv = pvLine.slice();
              completedDepth = d;
              this.searchCompletedDepth = d;
            }
            const absScore = bestScore > 0 ? bestScore : -bestScore;
            if (absScore > MATE_SCORE - 100)
              break;
          }
          const whiteScore = bestScore * this.side;
          return { move: bestMove, score: whiteScore, pv: bestPv, depth: completedDepth, nodes: this.nodes };
        },
        analyze(fen, depth, timeLimit) {
          this.loadFen(fen);
          this.clearTT();
          const timeMs = timeLimit || 1e3;
          const searchDepth = depth || 12;
          this.contempt = 25;
          const result = this.searchRoot(searchDepth, timeMs);
          let dirtyPlayTimeScale = 1;
          if (result.score < -500)
            dirtyPlayTimeScale = 0.4;
          else if (result.score < -200)
            dirtyPlayTimeScale = 0.6;
          else if (result.score < -100)
            dirtyPlayTimeScale = 0.8;
          if (!result.move) {
            return { success: false, bestmove: "(none)", evaluation: 0, depth: result.depth || 0 };
          }
          const uci = this.moveToUci(result.move);
          const pvStr = result.pv.map((m) => this.moveToUci(m)).join(" ");
          let scoreObj;
          const absScore = result.score > 0 ? result.score : -result.score;
          if (absScore > MATE_SCORE - 200) {
            const mateIn = (MATE_SCORE - absScore + 1) / 2 | 0;
            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",
            dirtyPlayTimeScale
          };
        }
      };
    }
  });

  // src/engine/local-engine.js
  var LocalEngine;
  var init_local_engine = __esm({
    "src/engine/local-engine.js"() {
      init_constants();
      init_pst();
      init_zobrist();
      init_utils2();
      init_search();
      LocalEngine = class {
        constructor() {
          this.board = new Int8Array(128).fill(EMPTY);
          this.side = 1;
          this.castling = 0;
          this.epSquare = -1;
          this.halfmove = 0;
          this.fullmove = 1;
          this.wKingSq = 4;
          this.bKingSq = 116;
          this.stateStack = [];
          this.nodes = 0;
          this.timeLimit = 0;
          this.startTime = 0;
          this.stopped = false;
          this.pvTable = [];
          this.killers = [];
          this.history = new Int32Array(16384);
          this.tt = new Array(TT_SIZE);
          this.evalBufs = {
            wPawnFiles: new Uint8Array(8),
            bPawnFiles: new Uint8Array(8),
            wPawnRanks: new Int8Array(8),
            bPawnRanks: new Int8Array(8),
            bPawnMaxRanks: new Int8Array(8),
            wPawnMinRanks: new Int8Array(8),
            rookSquares: new Int8Array(4),
            rookSides: new Int8Array(4)
          };
          this.hash = [0, 0];
          this.phase = 0;
          this.positionHistory = [];
          this.contempt = 0;
          this.rootSide = 1;
          this.mgPstMat = 0;
          this.egPstMat = 0;
          this.wBishops = 0;
          this.bBishops = 0;
          this.wKnights = 0;
          this.bKnights = 0;
          this.wRooks = 0;
          this.bRooks = 0;
          this.wQueens = 0;
          this.bQueens = 0;
        }
        _packPieceCounts() {
          return this.wQueens | this.bQueens << 4 | this.wRooks << 8 | this.bRooks << 12 | this.wBishops << 16 | this.bBishops << 20 | this.wKnights << 24 | this.bKnights << 28;
        }
        _unpackPieceCounts(packed) {
          this.wQueens = packed & 15;
          this.bQueens = packed >> 4 & 15;
          this.wRooks = packed >> 8 & 15;
          this.bRooks = packed >> 12 & 15;
          this.wBishops = packed >> 16 & 15;
          this.bBishops = packed >> 20 & 15;
          this.wKnights = packed >> 24 & 15;
          this.bKnights = packed >> 28 & 15;
        }
        makeNullMove() {
          this.stateStack.push({
            castling: this.castling,
            epSquare: this.epSquare,
            halfmove: this.halfmove,
            fullmove: this.fullmove,
            wKingSq: this.wKingSq,
            bKingSq: this.bKingSq,
            hash: [this.hash[0], this.hash[1]],
            mgPstMat: this.mgPstMat,
            egPstMat: this.egPstMat,
            phase: this.phase,
            pieceCounts: this._packPieceCounts()
          });
          if (this.epSquare >= 0) {
            const ef = sqFile(this.epSquare) * 2;
            zobXor(this.hash, [ZOBRIST.epKeys[ef], ZOBRIST.epKeys[ef + 1]]);
          }
          this.epSquare = -1;
          this.side = -this.side;
          zobXor(this.hash, ZOBRIST.sideKey);
          this.halfmove++;
          if (this.side === -1)
            this.fullmove++;
        }
        unmakeNullMove() {
          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;
          this.hash = st.hash;
          this.mgPstMat = st.mgPstMat;
          this.egPstMat = st.egPstMat;
          this.phase = st.phase;
          this._unpackPieceCounts(st.pieceCounts);
        }
        reset() {
          this.history.fill(0);
          this.killers = [];
          this.tt = new Array(TT_SIZE);
          this.positionHistory = [];
        }
        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;
                const sq = r * 16 + f;
                this.board[sq] = piece;
                if (piece === WK)
                  this.wKingSq = sq;
                else if (piece === BK)
                  this.bKingSq = sq;
                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 = [];
          this.positionHistory = [];
          this._initIncrementalScores();
          this._computeHash();
        }
        _initIncrementalScores() {
          this.mgPstMat = 0;
          this.egPstMat = 0;
          this.phase = 0;
          this.wBishops = 0;
          this.bBishops = 0;
          this.wKnights = 0;
          this.bKnights = 0;
          this.wRooks = 0;
          this.bRooks = 0;
          this.wQueens = 0;
          this.bQueens = 0;
          for (let sq = 0; sq < 128; sq++) {
            if (sq & 136) {
              sq += 7;
              continue;
            }
            const p = this.board[sq];
            if (p === EMPTY)
              continue;
            this._addPieceScore(p, sq);
          }
        }
        _addPieceScore(p, sq) {
          const side = p > 0 ? 1 : -1;
          const abs = p > 0 ? p : -p;
          const pstSq = side === 1 ? sq : sq ^ 112;
          this.mgPstMat += (MAT_MG[abs] + PST_MG[abs][pstSq]) * side;
          this.egPstMat += (MAT_EG[abs] + PST_EG[abs][pstSq]) * side;
          if (abs >= 2 && abs <= 5)
            this.phase += PHASE_VAL[abs];
          if (abs === 2) {
            if (side === 1)
              this.wKnights++;
            else
              this.bKnights++;
          }
          if (abs === 3) {
            if (side === 1)
              this.wBishops++;
            else
              this.bBishops++;
          }
          if (abs === 4) {
            if (side === 1)
              this.wRooks++;
            else
              this.bRooks++;
          }
          if (abs === 5) {
            if (side === 1)
              this.wQueens++;
            else
              this.bQueens++;
          }
        }
        _removePieceScore(p, sq) {
          const side = p > 0 ? 1 : -1;
          const abs = p > 0 ? p : -p;
          const pstSq = side === 1 ? sq : sq ^ 112;
          this.mgPstMat -= (MAT_MG[abs] + PST_MG[abs][pstSq]) * side;
          this.egPstMat -= (MAT_EG[abs] + PST_EG[abs][pstSq]) * side;
          if (abs >= 2 && abs <= 5)
            this.phase -= PHASE_VAL[abs];
          if (abs === 2) {
            if (side === 1)
              this.wKnights--;
            else
              this.bKnights--;
          }
          if (abs === 3) {
            if (side === 1)
              this.wBishops--;
            else
              this.bBishops--;
          }
          if (abs === 4) {
            if (side === 1)
              this.wRooks--;
            else
              this.bRooks--;
          }
          if (abs === 5) {
            if (side === 1)
              this.wQueens--;
            else
              this.bQueens--;
          }
        }
        _computeHash() {
          this.hash = [0, 0];
          for (let sq = 0; sq < 128; sq++) {
            if (sq & 136) {
              sq += 7;
              continue;
            }
            const p = this.board[sq];
            if (p !== EMPTY)
              zobXor(this.hash, zobPieceKey(p, sq));
          }
          if (this.side === -1)
            zobXor(this.hash, ZOBRIST.sideKey);
          const ck = this.castling * 2;
          zobXor(this.hash, [ZOBRIST.castlingKeys[ck], ZOBRIST.castlingKeys[ck + 1]]);
          if (this.epSquare >= 0) {
            const ef = sqFile(this.epSquare) * 2;
            zobXor(this.hash, [ZOBRIST.epKeys[ef], ZOBRIST.epKeys[ef + 1]]);
          }
        }
        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 * 16 + 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.positionHistory.push(this.hash[0] + "|" + this.hash[1]);
          this.stateStack.push({
            castling: this.castling,
            epSquare: this.epSquare,
            halfmove: this.halfmove,
            fullmove: this.fullmove,
            wKingSq: this.wKingSq,
            bKingSq: this.bKingSq,
            hash: [this.hash[0], this.hash[1]],
            mgPstMat: this.mgPstMat,
            egPstMat: this.egPstMat,
            phase: this.phase,
            pieceCounts: this._packPieceCounts()
          });
          const { from, to, flags, piece, promo } = mv;
          const abs = Math.abs(piece);
          zobXor(this.hash, zobPieceKey(piece, from));
          if (mv.captured !== EMPTY && !(flags & FLAG_EP)) {
            zobXor(this.hash, zobPieceKey(mv.captured, to));
            this._removePieceScore(mv.captured, to);
          }
          this.board[from] = EMPTY;
          this._removePieceScore(piece, from);
          const landed = flags & FLAG_PROMO ? promo : piece;
          this.board[to] = landed;
          this._addPieceScore(landed, to);
          zobXor(this.hash, zobPieceKey(landed, to));
          if (abs === 6) {
            if (this.side === 1)
              this.wKingSq = to;
            else
              this.bKingSq = to;
          }
          if (flags & FLAG_EP) {
            const epCapSq = to - this.side * 16;
            const capPawn = this.board[epCapSq] || -this.side;
            zobXor(this.hash, zobPieceKey(capPawn, epCapSq));
            this._removePieceScore(capPawn, epCapSq);
            this.board[epCapSq] = EMPTY;
          }
          if (flags & FLAG_CASTLE) {
            if (to > from) {
              const rook = this.side * WR;
              zobXor(this.hash, zobPieceKey(rook, from + 3));
              this._removePieceScore(rook, from + 3);
              this.board[from + 3] = EMPTY;
              this.board[from + 1] = rook;
              zobXor(this.hash, zobPieceKey(rook, from + 1));
              this._addPieceScore(rook, from + 1);
            } else {
              const rook = this.side * WR;
              zobXor(this.hash, zobPieceKey(rook, from - 4));
              this._removePieceScore(rook, from - 4);
              this.board[from - 4] = EMPTY;
              this.board[from - 1] = rook;
              zobXor(this.hash, zobPieceKey(rook, from - 1));
              this._addPieceScore(rook, from - 1);
            }
          }
          const oldCastling = this.castling;
          if (this.epSquare >= 0) {
            const ef = sqFile(this.epSquare) * 2;
            zobXor(this.hash, [ZOBRIST.epKeys[ef], ZOBRIST.epKeys[ef + 1]]);
          }
          if (abs === 1 && Math.abs(to - from) === 32) {
            this.epSquare = from + to >> 1;
          } else {
            this.epSquare = -1;
          }
          if (this.epSquare >= 0) {
            const ef = sqFile(this.epSquare) * 2;
            zobXor(this.hash, [ZOBRIST.epKeys[ef], ZOBRIST.epKeys[ef + 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 === 112 || to === 112)
            this.castling &= ~8;
          if (from === 119 || to === 119)
            this.castling &= ~4;
          if (oldCastling !== this.castling) {
            const oc = oldCastling * 2, nc = this.castling * 2;
            zobXor(this.hash, [ZOBRIST.castlingKeys[oc], ZOBRIST.castlingKeys[oc + 1]]);
            zobXor(this.hash, [ZOBRIST.castlingKeys[nc], ZOBRIST.castlingKeys[nc + 1]]);
          }
          this.halfmove = abs === 1 || mv.captured !== EMPTY ? 0 : this.halfmove + 1;
          if (this.side === -1)
            this.fullmove++;
          this.side = -this.side;
          zobXor(this.hash, ZOBRIST.sideKey);
        }
        unmakeMove(mv) {
          this.side = -this.side;
          this.positionHistory.pop();
          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;
          this.hash = st.hash;
          this.mgPstMat = st.mgPstMat;
          this.egPstMat = st.egPstMat;
          this.phase = st.phase;
          this._unpackPieceCounts(st.pieceCounts);
          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 * 16] = -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 attackSourceRank = (sq >> 4) - pawnDir;
          if (attackSourceRank >= 0 && attackSourceRank <= 7) {
            const sourceSqBase = sq - pawnDir * 16;
            const s1 = sourceSqBase - 1;
            if (!(s1 & 136) && this.board[s1] === bySide)
              return true;
            const s2 = sourceSqBase + 1;
            if (!(s2 & 136) && this.board[s2] === bySide)
              return true;
          }
          const kn = bySide * WN;
          for (let i = 0; i < 8; i++) {
            const t = sq - KNIGHT_OFFSETS[i];
            if (t & 136)
              continue;
            if (this.board[t] === kn)
              return true;
          }
          const kg = bySide * WK;
          for (let i = 0; i < 8; i++) {
            const t = sq + ALL_DIRS[i];
            if (t & 136)
              continue;
            if (this.board[t] === kg)
              return true;
          }
          const sideR = bySide * WR, sideQ = bySide * WQ, sideB = bySide * WB;
          for (let i = 0; i < 4; i++) {
            const dir = STRAIGHT_DIRS[i];
            let t = sq + dir;
            while (!(t & 136)) {
              const p = this.board[t];
              if (p !== EMPTY) {
                if (p === sideR || p === sideQ)
                  return true;
                break;
              }
              t += dir;
            }
          }
          for (let i = 0; i < 4; i++) {
            const dir = DIAG_DIRS[i];
            let t = sq + dir;
            while (!(t & 136)) {
              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;
          const bd = this.board;
          for (let sq = 0; sq < 128; sq++) {
            if (sq & 136) {
              sq += 7;
              continue;
            }
            const p = bd[sq];
            if (p === EMPTY || (p > 0 ? 1 : -1) !== s)
              continue;
            const abs = p > 0 ? p : -p;
            if (abs === 1) {
              const dir = s;
              const promoRank = s === 1 ? 7 : 0;
              const startRank = s === 1 ? 1 : 6;
              const fwd = sq + dir * 16;
              if (!(fwd & 136) && bd[fwd] === EMPTY) {
                if (fwd >> 4 === 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 * 16;
                  if (sq >> 4 === startRank && !(fwd2 & 136) && bd[fwd2] === EMPTY) {
                    moves.push(this.createMove(sq, fwd2));
                  }
                }
              }
              const captureOffsets = [dir * 16 - 1, dir * 16 + 1];
              for (const offset of captureOffsets) {
                const csq = sq + offset;
                if (csq & 136)
                  continue;
                const cp = bd[csq];
                if (cp !== EMPTY && (cp > 0 ? 1 : -1) === opp) {
                  if (csq >> 4 === 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 (let i = 0; i < 8; i++) {
                const t = sq + KNIGHT_OFFSETS[i];
                if (t & 136)
                  continue;
                const tp = bd[t];
                if (tp !== EMPTY && (tp > 0 ? 1 : -1) === s)
                  continue;
                if (capturesOnly && tp === EMPTY)
                  continue;
                moves.push(this.createMove(sq, t));
              }
            } else if (abs === 6) {
              for (let i = 0; i < 8; i++) {
                const t = sq + ALL_DIRS[i];
                if (t & 136)
                  continue;
                const tp = bd[t];
                if (tp !== EMPTY && (tp > 0 ? 1 : -1) === 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 && bd[5] === EMPTY && bd[6] === EMPTY && !this.isAttacked(5, -1) && !this.isAttacked(6, -1)) {
                    moves.push(this.createMove(4, 6, FLAG_CASTLE));
                  }
                  if (this.castling & 2 && sq === 4 && bd[3] === EMPTY && bd[2] === EMPTY && bd[1] === EMPTY && !this.isAttacked(3, -1) && !this.isAttacked(2, -1)) {
                    moves.push(this.createMove(4, 2, FLAG_CASTLE));
                  }
                } else {
                  if (this.castling & 4 && sq === 116 && bd[117] === EMPTY && bd[118] === EMPTY && !this.isAttacked(117, 1) && !this.isAttacked(118, 1)) {
                    moves.push(this.createMove(116, 118, FLAG_CASTLE));
                  }
                  if (this.castling & 8 && sq === 116 && bd[115] === EMPTY && bd[114] === EMPTY && bd[113] === EMPTY && !this.isAttacked(115, 1) && !this.isAttacked(114, 1)) {
                    moves.push(this.createMove(116, 114, FLAG_CASTLE));
                  }
                }
              }
            } else {
              const dirs = abs === 3 ? DIAG_DIRS : abs === 4 ? STRAIGHT_DIRS : ALL_DIRS;
              for (let di = 0; di < dirs.length; di++) {
                const dir = dirs[di];
                let t = sq + dir;
                while (!(t & 136)) {
                  const tp = bd[t];
                  if (tp !== EMPTY && (tp > 0 ? 1 : -1) === 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;
        }
        getSmallestAttacker(sq, bySide) {
          const pawnDir = bySide === 1 ? 1 : -1;
          const attackSourceRank = (sq >> 4) - pawnDir;
          if (attackSourceRank >= 0 && attackSourceRank <= 7) {
            const sourceSqBase = sq - pawnDir * 16;
            for (const offset of [-1, 1]) {
              const s = sourceSqBase + offset;
              if (!(s & 136) && this.board[s] === bySide) {
                return { piece: bySide, sq: s, value: PIECE_VAL[1] };
              }
            }
          }
          const kn = bySide * WN;
          for (let i = 0; i < 8; i++) {
            const t = sq + KNIGHT_OFFSETS[i];
            if (t & 136)
              continue;
            if (this.board[t] === kn)
              return { piece: kn, sq: t, value: PIECE_VAL[2] };
          }
          const sideB = bySide * WB;
          const sideQ = bySide * WQ;
          for (let i = 0; i < 4; i++) {
            const dir = DIAG_DIRS[i];
            let t = sq + dir;
            while (!(t & 136)) {
              const p = this.board[t];
              if (p !== EMPTY) {
                if (p === sideB || p === sideQ) {
                  return { piece: p, sq: t, value: PIECE_VAL[Math.abs(p)] };
                }
                break;
              }
              t += dir;
            }
          }
          const sideR = bySide * WR;
          for (let i = 0; i < 4; i++) {
            const dir = STRAIGHT_DIRS[i];
            let t = sq + dir;
            while (!(t & 136)) {
              const p = this.board[t];
              if (p !== EMPTY) {
                if (p === sideR || p === sideQ) {
                  return { piece: p, sq: t, value: PIECE_VAL[Math.abs(p)] };
                }
                break;
              }
              t += dir;
            }
          }
          const kg = bySide * WK;
          for (let i = 0; i < 8; i++) {
            const t = sq + ALL_DIRS[i];
            if (t & 136)
              continue;
            if (this.board[t] === kg)
              return { piece: kg, sq: t, value: PIECE_VAL[6] };
          }
          return null;
        }
      };
      Object.assign(LocalEngine.prototype, SearchMethods);
    }
  });

  // src/engine/analysis.js
  function analyzeLocally(fen, depth, timeLimit) {
    const start = performance.now();
    const result = localEngine.analyze(fen, depth, timeLimit);
    const elapsed = performance.now() - start;
    return result;
  }
  function resetEngine() {
    if (localEngine && typeof localEngine.reset === "function") {
      localEngine.reset();
    } else if (localEngine && typeof localEngine.clearTT === "function") {
      localEngine.clearTT();
    }
  }
  async function getAnalysis(fen, depth, timeLimit, signal) {
    const cached = PositionCache.get(fen);
    if (cached) {
      return cached;
    }
    if (signal?.aborted)
      throw new DOMException("Aborted", "AbortError");
    const res = analyzeLocally(fen, depth, timeLimit);
    await new Promise((r) => setTimeout(r, 0));
    if (res.success && res.depth >= 1) {
      PositionCache.set(fen, res);
    }
    return res;
  }
  function parseBestLine(data) {
    const lines = [];
    const pushLine = (uci, pv, score) => {
      if (!uci || uci.length < 4 || uci === "(none)" || !/^[a-h]/.test(uci))
        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;
  }
  var localEngine;
  var init_analysis = __esm({
    "src/engine/analysis.js"() {
      init_state();
      init_utils();
      init_local_engine();
      localEngine = new LocalEngine();
    }
  });

  // src/engine/premove.js
  function parsePromotion(uci) {
    if (!uci || uci.length < 5)
      return null;
    const map = { q: 5, r: 4, b: 3, n: 2 };
    return map[uci[4].toLowerCase()] || null;
  }
  function findMove(moves, from, to, promo) {
    const candidates = moves.filter((m) => m.from === from && m.to === to);
    if (candidates.length === 0)
      return void 0;
    if (!promo || candidates.length === 1)
      return candidates[0];
    const promoMatch = candidates.find((m) => {
      const mp = m.promotedPiece ?? m.promoted ?? m.promo ?? 0;
      return Math.abs(mp) === promo;
    });
    return promoMatch || candidates[0];
  }
  function resetTimer(engine, ms) {
    engine.startTime = performance.now();
    engine.timeLimit = ms;
    engine.stopped = false;
    engine.nodes = 0;
  }
  function checkHanging(engine, ourMove) {
    const seeScore = engine.see(ourMove);
    if (seeScore < 0) {
      return { hanging: true, reason: `Unsafe trade (SEE ${seeScore})` };
    }
    return { hanging: false, reason: null };
  }
  function evaluatePremove(fen, opponentUci, ourUci, ourColor) {
    if (!ourUci || ourUci.length < 4) {
      return { execute: false, confidence: 0, reasons: [], blocked: "Invalid move" };
    }
    if (!opponentUci || opponentUci.length < 4) {
      return { execute: false, confidence: 0, reasons: [], blocked: "No predicted opponent move" };
    }
    const oppSide = ourColor === "w" ? -1 : 1;
    const ourSide = -oppSide;
    const reasons = [];
    let confidence = 0.7;
    try {
      premoveEngine.loadFen(fen);
      resetTimer(premoveEngine, PREMOVE_CONFIG.quiesceMsScoring);
      const oppFrom = nameToSq(opponentUci.substring(0, 2));
      const oppTo = nameToSq(opponentUci.substring(2, 4));
      const oppPromo = parsePromotion(opponentUci);
      const oppMoves = premoveEngine.generateLegalMoves();
      const oppMove = findMove(oppMoves, oppFrom, oppTo, oppPromo);
      if (!oppMove) {
        return { execute: false, confidence: 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 ourPromo = parsePromotion(ourUci);
      const ourMove = findMove(ourLegalMoves, ourFrom, ourTo, ourPromo);
      if (!ourMove) {
        premoveEngine.unmakeMove(oppMove);
        return { execute: false, confidence: 0, reasons: [], blocked: "Our move illegal after opponent plays" };
      }
      const hangResult = checkHanging(premoveEngine, ourMove);
      if (hangResult.hanging) {
        premoveEngine.unmakeMove(oppMove);
        return { execute: false, confidence: 0, reasons: [], blocked: hangResult.reason };
      }
      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 * 16 + sf;
              if (premoveEngine.board[shieldSq] === EMPTY && !premoveEngine.isAttacked(shieldSq, oppSide)) {
                escapable = true;
                break;
              }
            }
            if (!escapable) {
              if (premoveEngine.isAttacked(ourKingSq, oppSide)) {
                premoveEngine.unmakeMove(ourMove);
                premoveEngine.unmakeMove(oppMove);
                return { execute: false, confidence: 0, reasons: [], blocked: "Back-rank mate threat" };
              }
              reasons.push("back-rank weak");
              confidence -= 0.1;
            }
          }
        }
      }
      if (premoveEngine.inCheck(oppSide)) {
        reasons.push("check");
        confidence += 0.1;
      }
      if (ourMove.flags & FLAG_PROMO) {
        reasons.push("promotion");
      }
      if (ourMove.flags & FLAG_CASTLE) {
        reasons.push("castling");
        confidence += 0.05;
      }
      if (ourMove.flags & FLAG_EP) {
        reasons.push("en passant");
        confidence -= 0.05;
      }
      premoveEngine.unmakeMove(ourMove);
      if (ourLegalMoves.length === 1) {
        reasons.push("forced");
        confidence += 0.2;
      } else if (ourLegalMoves.length <= 3) {
        reasons.push("few options");
        confidence += 0.05;
      }
      if (ourTo === oppTo) {
        reasons.push("recapture");
        confidence += 0.1;
      }
      if (!premoveEngine.isAttacked(ourTo, oppSide)) {
        reasons.push("safe sq");
        confidence += 0.05;
      }
      const centerSquares = [nameToSq("d4"), nameToSq("d5"), nameToSq("e4"), nameToSq("e5")];
      if (centerSquares.includes(ourTo)) {
        reasons.push("center");
      }
      resetTimer(premoveEngine, PREMOVE_CONFIG.quiesceMsScoring);
      premoveEngine.makeMove(ourMove);
      const mainEval = -premoveEngine.quiesce(-MATE_SCORE, MATE_SCORE, 0);
      premoveEngine.unmakeMove(ourMove);
      let mainBestAlt = -Infinity;
      for (const alt of ourLegalMoves) {
        if (alt.from === ourFrom && alt.to === ourTo)
          continue;
        resetTimer(premoveEngine, PREMOVE_CONFIG.quiesceMsPerMove);
        premoveEngine.makeMove(alt);
        const altEval = -premoveEngine.quiesce(-MATE_SCORE, MATE_SCORE, 0);
        premoveEngine.unmakeMove(alt);
        if (altEval > mainBestAlt)
          mainBestAlt = altEval;
      }
      const mainIsMate = mainEval > MATE_SCORE - 100;
      if (!mainIsMate && mainBestAlt > -Infinity && mainBestAlt - mainEval > PREMOVE_CONFIG.stabilityThreshold) {
        premoveEngine.unmakeMove(oppMove);
        return {
          execute: false,
          confidence: 0,
          reasons: ["suboptimal"],
          blocked: "Move suboptimal against predicted opponent move"
        };
      }
      premoveEngine.unmakeMove(oppMove);
      const preSorted = oppMoves.filter((m) => !(m.from === oppFrom && m.to === oppTo)).map((m) => ({
        move: m,
        priority: m.captured !== EMPTY ? PIECE_VAL[Math.abs(m.captured)] || 0 : 0
      })).sort((a, b) => b.priority - a.priority).slice(0, PREMOVE_CONFIG.altMovesToCheck + 4);
      const scored = [];
      for (const { move: oMove } of preSorted) {
        resetTimer(premoveEngine, PREMOVE_CONFIG.quiesceMsPerMove);
        premoveEngine.makeMove(oMove);
        const score = -premoveEngine.quiesce(-MATE_SCORE, MATE_SCORE, 0);
        premoveEngine.unmakeMove(oMove);
        scored.push({ move: oMove, score });
      }
      scored.sort((a, b) => b.score - a.score);
      const topAlts = scored.slice(0, PREMOVE_CONFIG.altMovesToCheck);
      for (const { move: altOppMove } of topAlts) {
        premoveEngine.makeMove(altOppMove);
        const altLegal = premoveEngine.generateLegalMoves();
        const altOurMove = findMove(altLegal, ourFrom, ourTo, ourPromo);
        if (!altOurMove) {
          premoveEngine.unmakeMove(altOppMove);
          continue;
        }
        resetTimer(premoveEngine, PREMOVE_CONFIG.quiesceMsPerMove);
        premoveEngine.makeMove(altOurMove);
        const postScore = -premoveEngine.quiesce(-MATE_SCORE, MATE_SCORE, 0);
        premoveEngine.unmakeMove(altOurMove);
        let bestAlt = -Infinity;
        for (const alt of altLegal) {
          if (alt.from === ourFrom && alt.to === ourTo)
            continue;
          resetTimer(premoveEngine, PREMOVE_CONFIG.quiesceMsPerMove);
          premoveEngine.makeMove(alt);
          const altS = -premoveEngine.quiesce(-MATE_SCORE, MATE_SCORE, 0);
          premoveEngine.unmakeMove(alt);
          if (altS > bestAlt)
            bestAlt = altS;
        }
        premoveEngine.unmakeMove(altOppMove);
        const postIsMate = postScore > MATE_SCORE - 100;
        if (!postIsMate && bestAlt > -Infinity && bestAlt - postScore > PREMOVE_CONFIG.stabilityThreshold) {
          return {
            execute: false,
            confidence: 0,
            reasons: ["unstable"],
            blocked: "Move suboptimal in alternate opponent response"
          };
        }
      }
    } catch (e) {
      console.warn("GabiBot: evaluatePremove error:", e);
      return { execute: false, confidence: 0, reasons: [], blocked: "Evaluation error" };
    }
    confidence = Math.max(0, Math.min(1, confidence));
    const execute = confidence >= PREMOVE_CONFIG.minConfidence;
    return { execute, confidence, reasons, blocked: execute ? null : "Low confidence" };
  }
  function evaluatePremoveChain(fen, pv, ourColor, sideToMove) {
    if (!pv)
      return [];
    const moves = pv.trim().split(/\s+/).filter(Boolean);
    if (moves.length < 2)
      return [];
    if (sideToMove === ourColor)
      return [];
    const oppSide = ourColor === "w" ? -1 : 1;
    const ourSide = -oppSide;
    const pairs = [];
    for (let i = 0; i + 1 < moves.length; i += 2) {
      pairs.push({ oppUci: moves[i], ourUci: moves[i + 1] });
    }
    if (pairs.length === 0)
      return [];
    const first = evaluatePremove(fen, pairs[0].oppUci, pairs[0].ourUci, ourColor);
    if (!first.execute)
      return [];
    const chain = [{
      uci: pairs[0].ourUci,
      oppUci: pairs[0].oppUci,
      confidence: first.confidence,
      reasons: first.reasons
    }];
    if (pairs.length < 2)
      return chain;
    try {
      premoveEngine.loadFen(fen);
      const m0OppFrom = nameToSq(pairs[0].oppUci.substring(0, 2));
      const m0OppTo = nameToSq(pairs[0].oppUci.substring(2, 4));
      const m0OppPromo = parsePromotion(pairs[0].oppUci);
      let legalMoves = premoveEngine.generateLegalMoves();
      const m0Opp = findMove(legalMoves, m0OppFrom, m0OppTo, m0OppPromo);
      if (!m0Opp)
        return chain;
      premoveEngine.makeMove(m0Opp);
      const m0OurFrom = nameToSq(pairs[0].ourUci.substring(0, 2));
      const m0OurTo = nameToSq(pairs[0].ourUci.substring(2, 4));
      const m0OurPromo = parsePromotion(pairs[0].ourUci);
      legalMoves = premoveEngine.generateLegalMoves();
      const m0Our = findMove(legalMoves, m0OurFrom, m0OurTo, m0OurPromo);
      if (!m0Our) {
        premoveEngine.unmakeMove(m0Opp);
        return chain;
      }
      premoveEngine.makeMove(m0Our);
      const appliedMoves = [m0Opp, m0Our];
      for (let i = 1; i < pairs.length && chain.length < PREMOVE_CONFIG.maxChainDepth; i++) {
        const { oppUci, ourUci } = pairs[i];
        if (!oppUci || !ourUci || oppUci.length < 4 || ourUci.length < 4)
          break;
        const oFrom = nameToSq(oppUci.substring(0, 2));
        const oTo = nameToSq(oppUci.substring(2, 4));
        const oPromo = parsePromotion(oppUci);
        legalMoves = premoveEngine.generateLegalMoves();
        const oMove = findMove(legalMoves, oFrom, oTo, oPromo);
        if (!oMove)
          break;
        premoveEngine.makeMove(oMove);
        appliedMoves.push(oMove);
        const uFrom = nameToSq(ourUci.substring(0, 2));
        const uTo = nameToSq(ourUci.substring(2, 4));
        const uPromo = parsePromotion(ourUci);
        legalMoves = premoveEngine.generateLegalMoves();
        const uMove = findMove(legalMoves, uFrom, uTo, uPromo);
        if (!uMove) {
          premoveEngine.unmakeMove(oMove);
          appliedMoves.pop();
          break;
        }
        const chainHang = checkHanging(premoveEngine, uMove);
        if (chainHang.hanging) {
          premoveEngine.unmakeMove(oMove);
          appliedMoves.pop();
          break;
        }
        resetTimer(premoveEngine, PREMOVE_CONFIG.quiesceMsPerMove);
        premoveEngine.makeMove(uMove);
        const chainOurEval = -premoveEngine.quiesce(-MATE_SCORE, MATE_SCORE, 0);
        premoveEngine.unmakeMove(uMove);
        let chainBestAlt = -Infinity;
        for (const cAlt of legalMoves) {
          if (cAlt.from === uFrom && cAlt.to === uTo)
            continue;
          resetTimer(premoveEngine, PREMOVE_CONFIG.quiesceMsPerMove);
          premoveEngine.makeMove(cAlt);
          const cAltEval = -premoveEngine.quiesce(-MATE_SCORE, MATE_SCORE, 0);
          premoveEngine.unmakeMove(cAlt);
          if (cAltEval > chainBestAlt)
            chainBestAlt = cAltEval;
        }
        const chainIsMate = chainOurEval > MATE_SCORE - 100;
        if (!chainIsMate && chainBestAlt > -Infinity && chainBestAlt - chainOurEval > PREMOVE_CONFIG.stabilityThreshold) {
          premoveEngine.unmakeMove(oMove);
          appliedMoves.pop();
          break;
        }
        premoveEngine.makeMove(uMove);
        appliedMoves.push(uMove);
        const moveReasons = [];
        if (premoveEngine.inCheck(oppSide)) {
          moveReasons.push("check");
        }
        if (legalMoves.length === 1) {
          moveReasons.push("forced");
        }
        if (uTo === oTo) {
          moveReasons.push("recapture");
        }
        let moveConfidence = first.confidence - i * PREMOVE_CONFIG.chainConfidenceDecay;
        if (moveReasons.includes("check"))
          moveConfidence += 0.1;
        if (moveReasons.includes("forced"))
          moveConfidence += 0.15;
        if (moveReasons.includes("recapture"))
          moveConfidence += 0.1;
        moveConfidence = Math.max(0, Math.min(1, moveConfidence));
        if (moveConfidence < PREMOVE_CONFIG.minConfidence) {
          for (let j = appliedMoves.length - 1; j >= 0; j--) {
            premoveEngine.unmakeMove(appliedMoves[j]);
          }
          break;
        }
        chain.push({
          uci: ourUci,
          oppUci,
          confidence: moveConfidence,
          reasons: moveReasons
        });
      }
      premoveEngine.loadFen(fen);
    } catch (e) {
      console.warn("GabiBot: evaluatePremoveChain error:", e);
    }
    return chain;
  }
  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 PREMOVE_CONFIG, premoveEngine;
  var init_premove = __esm({
    "src/engine/premove.js"() {
      init_local_engine();
      init_constants();
      init_utils2();
      PREMOVE_CONFIG = {
        stabilityThreshold: 150,
        // cp — block if alt is this much better
        minConfidence: 0.45,
        // minimum confidence to execute
        altMovesToCheck: 5,
        // top opponent alternatives to test
        quiesceMsPerMove: 40,
        // ms budget per quiesce call
        quiesceMsScoring: 80,
        // ms budget for scoring all opp moves
        maxChainDepth: 3,
        // max premoves to queue
        chainConfidenceDecay: 0.12
        // confidence penalty per chain depth
      };
      premoveEngine = new LocalEngine();
    }
  });

  // src/engine/san.js
  function getEngine() {
    if (!sanEngine)
      sanEngine = new LocalEngine();
    return sanEngine;
  }
  function uciToSan(fen, uci) {
    if (!uci)
      return "";
    if (typeof uci === "object") {
      if (typeof uci.uci === "string") {
        uci = uci.uci;
      } else if (typeof uci.from === "string" && typeof uci.to === "string") {
        uci = uci.from + uci.to + (uci.promo || uci.promotion || "");
      }
    }
    if (typeof uci !== "string" || uci.length < 4)
      return uci;
    const engine = getEngine();
    engine.loadFen(fen);
    const from = nameToSq(uci.substring(0, 2));
    const to = nameToSq(uci.substring(2, 4));
    const promo = uci.length >= 5 ? uci[4].toLowerCase() : null;
    const moves = engine.generateLegalMoves();
    const move = moves.find((m) => m.from === from && m.to === to && (!promo || m.flags & FLAG_PROMO));
    if (!move)
      return uci;
    if (move.flags & FLAG_CASTLE) {
      const toFile = sqFile(to);
      if (toFile === 6)
        return "O-O";
      if (toFile === 2)
        return "O-O-O";
    }
    const piece = engine.board[from];
    const pieceType = Math.abs(piece);
    const isPawn = pieceType === 1;
    const isCapture = engine.board[to] !== 0 || move.flags & FLAG_EP;
    let san = "";
    if (!isPawn) {
      san += PIECE_SYMBOLS[piece] || "";
    }
    if (!isPawn && moves.length > 1) {
      const samePieceMoves = moves.filter(
        (m) => m.to === to && m.from !== from && engine.board[m.from] === piece
      );
      if (samePieceMoves.length > 0) {
        const fromFile = sqFile(from);
        const fromRank = sqRank(from);
        const sameFile = samePieceMoves.some((m) => sqFile(m.from) === fromFile);
        const sameRank = samePieceMoves.some((m) => sqRank(m.from) === fromRank);
        if (!sameFile) {
          san += uci[0];
        } else if (!sameRank) {
          san += uci[1];
        } else {
          san += uci.substring(0, 2);
        }
      }
    }
    if (isPawn && isCapture) {
      san += uci[0];
    }
    if (isCapture) {
      san += "x";
    }
    san += uci.substring(2, 4);
    if (move.flags & FLAG_PROMO && promo) {
      san += "=" + (PROMO_SYMBOLS[promo] || promo.toUpperCase());
    }
    engine.makeMove(move);
    const opponentMoves = engine.generateLegalMoves();
    const inCheck = engine.inCheck(-engine.side);
    if (opponentMoves.length === 0 && inCheck) {
      san += "#";
    } else if (inCheck) {
      san += "+";
    }
    return san;
  }
  function getMoveNumber(fen) {
    const parts = fen.split(" ");
    return parseInt(parts[5]) || 1;
  }
  function isWhiteToMove(fen) {
    const parts = fen.split(" ");
    return parts[1] === "w";
  }
  function formatMove(fen, uci) {
    const san = uciToSan(fen, uci);
    const moveNum = getMoveNumber(fen);
    const isWhite = isWhiteToMove(fen);
    if (isWhite) {
      return `${moveNum}. ${san}`;
    } else {
      return `${moveNum}... ${san}`;
    }
  }
  function pvToSan(fen, pv) {
    if (!fen || !pv)
      return pv || "";
    const moves = pv.trim().split(/\s+/).filter(Boolean);
    if (moves.length === 0)
      return "";
    const engine = getEngine();
    engine.loadFen(fen);
    const sanMoves = [];
    const appliedMoves = [];
    for (const uci of moves) {
      if (!uci || uci.length < 4)
        break;
      const from = nameToSq(uci.substring(0, 2));
      const to = nameToSq(uci.substring(2, 4));
      const promo = uci.length >= 5 ? uci[4].toLowerCase() : null;
      const legalMoves = engine.generateLegalMoves();
      const move = legalMoves.find((m) => m.from === from && m.to === to && (!promo || m.flags & FLAG_PROMO));
      if (!move)
        break;
      const san = uciToSanFromEngine(engine, move, legalMoves, uci, promo);
      sanMoves.push(san);
      engine.makeMove(move);
      appliedMoves.push(move);
    }
    for (let i = appliedMoves.length - 1; i >= 0; i--) {
      engine.unmakeMove(appliedMoves[i]);
    }
    return sanMoves.join(" ");
  }
  function uciToSanFromEngine(engine, move, moves, uci, promo) {
    const from = move.from;
    const to = move.to;
    if (move.flags & FLAG_CASTLE) {
      const toFile = sqFile(to);
      if (toFile === 6)
        return "O-O";
      if (toFile === 2)
        return "O-O-O";
    }
    const piece = engine.board[from];
    const pieceType = Math.abs(piece);
    const isPawn = pieceType === 1;
    const isCapture = engine.board[to] !== 0 || move.flags & FLAG_EP;
    let san = "";
    if (!isPawn) {
      san += PIECE_SYMBOLS[piece] || "";
    }
    if (!isPawn && moves.length > 1) {
      const samePieceMoves = moves.filter(
        (m) => m.to === to && m.from !== from && engine.board[m.from] === piece
      );
      if (samePieceMoves.length > 0) {
        const fromFile = sqFile(from);
        const fromRank = sqRank(from);
        const sameFile = samePieceMoves.some((m) => sqFile(m.from) === fromFile);
        const sameRank = samePieceMoves.some((m) => sqRank(m.from) === fromRank);
        if (!sameFile)
          san += uci[0];
        else if (!sameRank)
          san += uci[1];
        else
          san += uci.substring(0, 2);
      }
    }
    if (isPawn && isCapture)
      san += uci[0];
    if (isCapture)
      san += "x";
    san += uci.substring(2, 4);
    if (move.flags & FLAG_PROMO && promo) {
      san += "=" + (PROMO_SYMBOLS[promo] || promo.toUpperCase());
    }
    engine.makeMove(move);
    const opponentMoves = engine.generateLegalMoves();
    const inCheck = engine.inCheck(-engine.side);
    if (opponentMoves.length === 0 && inCheck)
      san += "#";
    else if (inCheck)
      san += "+";
    engine.unmakeMove(move);
    return san;
  }
  var PIECE_SYMBOLS, PROMO_SYMBOLS, sanEngine;
  var init_san = __esm({
    "src/engine/san.js"() {
      init_local_engine();
      init_constants();
      init_utils2();
      PIECE_SYMBOLS = {
        [WN]: "N",
        [WB]: "B",
        [WR]: "R",
        [WQ]: "Q",
        [WK]: "K",
        [BN]: "N",
        [BB]: "B",
        [BR]: "R",
        [BQ]: "Q",
        [BK]: "K"
      };
      PROMO_SYMBOLS = { q: "Q", r: "R", b: "B", n: "N" };
      sanEngine = null;
    }
  });

  // src/ui.js
  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 name="moveMethod" class="listItem">
      <input class="checkboxMod" type="checkbox">
      <a class="itemDescription">Drag 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 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 class="divider"></div>

    <div name="botPower" class="listItem">
      <input min="1" max="30" value="12" class="rangeSlider" type="range">
      <a class="itemDescription">Depth</a>
      <a class="itemState">12</a>
    </div>
    <div name="moveTime" class="listItem">
      <!-- Mapped slider: 0-100 map to 100ms-10000ms log scale -->
      <input min="0" max="100" value="50" class="rangeSlider" type="range">
      <a class="itemDescription">Think</a>
      <a class="itemState">1.0s</a>
    </div>
    <div name="jitter" class="listItem">
      <input min="0" max="100" value="0" class="rangeSlider" type="range">
      <a class="itemDescription">Jitter</a>
      <a class="itemState">0.0s</a>
    </div>

    <div class="divider"></div>
  </div>
  
  <!-- Terminal Console -->
  <div id="consoleHeader" class="console-header">
    <span class="console-title">Terminal</span>
    <div class="console-actions">
      <button id="consoleCopyBtn" title="Copy log">\u29C9</button>
      <button id="consoleClearBtn" title="Clear log">\u2715</button>
    </div>
  </div>
  <div id="consoleWindow" class="console-window">
     <div class="log-line info">GabiBot Ready</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 minmax(0, 1fr) auto; /* Header, Settings, Console */
    width: 360px; max-height: 85vh;
    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 0fr; max-height: 50px; }
  #menuWrap.minimized #itemsList, #menuWrap.minimized #consoleWindow { overflow: hidden; opacity: 0; display: none; }
  #menuWrap.grabbing { cursor: grabbing !important; opacity: 0.9; }
  .divider { height: 1px; background: rgba(255, 255, 255, 0.1); margin: 10px 0; }
  
  #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.05); 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; display: flex; flex-direction: column; height: 100%; }
  ::-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; flex-shrink: 0; }
  .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; }

  /* Console Header */
  .console-header {
    display: flex; align-items: center; justify-content: space-between;
    padding: 4px 10px;
    background: rgba(0, 0, 0, 0.25);
    border-top: 1px solid rgba(255, 255, 255, 0.08);
    flex-shrink: 0;
  }
  .console-title { color: rgba(255,255,255,0.4); font-size: 10px; font-family: monospace; letter-spacing: 0.5px; text-transform: uppercase; }
  .console-actions { display: flex; gap: 4px; }
  .console-actions button {
    background: rgba(255,255,255,0.07); border: 1px solid rgba(255,255,255,0.1);
    color: rgba(255,255,255,0.5); width: 20px; height: 20px; border-radius: 3px;
    cursor: pointer; font-size: 11px; padding: 0; line-height: 1;
    transition: background 0.15s, color 0.15s;
  }
  .console-actions button:hover { background: rgba(255,255,255,0.15); color: #fff; }
  #consoleCopyBtn.copied { color: #4CAF50; border-color: #4CAF50; }
  /* Console Window Styles - Edge to Edge */
  .console-window {
    background: rgba(0, 0, 0, 0.3);
    padding: 12px;
    min-height: 80px;
    max-height: 150px;
    overflow-y: auto;
    font-family: "Consolas", "Monaco", "Courier New", monospace;
    font-size: 12px;
    display: flex;
    flex-direction: column;
    gap: 3px;
    border-radius: 0 0 8px 8px;
  }
  .log-line { color: #bbb; line-height: 1.5; word-break: break-all; }
  .log-line.status { color: #4CAF50; font-weight: bold; }
  .log-line.error { color: #ff5252; }
  .log-line.warn { color: #ffab40; }
  .log-line.info { color: #81d4fa; }
  .log-line.move { color: #64B5F6; }
  .log-line.opponent { color: #FFB74D; }
`;
    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";
        if (name === "moveMethod") {
          modInput.checked = BotState[key] === "drag";
          modState.textContent = BotState[key] === "drag" ? "On" : "Off";
        }
        modInput.addEventListener("input", () => {
          if (name === "moveMethod") {
            BotState[key] = modInput.checked ? "drag" : "click";
            modState.textContent = BotState[key] === "drag" ? "On" : "Off";
          } else {
            BotState[key] = modInput.checked ? 1 : 0;
            modState.textContent = BotState[key] ? "On" : "Off";
          }
          Settings.save();
        });
      } else if (type === "range") {
        const toLog = (v) => Math.round(100 * Math.pow(100, v / 100));
        const toLin = (v) => Math.log(v / 100) / Math.log(100) * 100;
        if (name === "moveTime") {
          modInput.value = toLin(BotState.moveTime);
          modState.textContent = (BotState.moveTime / 1e3).toFixed(1) + "s";
          modInput.addEventListener("input", () => {
            const val = parseInt(modInput.value, 10);
            const timeMs = toLog(val);
            BotState.moveTime = timeMs;
            modState.textContent = (timeMs / 1e3).toFixed(1) + "s";
            Settings.save();
          });
        } else if (name === "jitter") {
          const toLogJitter = (v) => {
            if (v === 0)
              return 0;
            return Math.round(100 * Math.pow(100, v / 100));
          };
          const toLinJitter = (v) => {
            if (v <= 0)
              return 0;
            return Math.log(v / 100) / Math.log(100) * 100;
          };
          modInput.value = toLinJitter(BotState.jitter);
          modState.textContent = (BotState.jitter / 1e3).toFixed(1) + "s";
          modInput.addEventListener("input", () => {
            const val = parseInt(modInput.value, 10);
            const timeMs = toLogJitter(val);
            BotState.jitter = timeMs;
            modState.textContent = (timeMs / 1e3).toFixed(1) + "s";
            Settings.save();
          });
        } else {
          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();
          });
        }
      }
    }
    bindControl("enableHack", "checkbox", "BotState.hackEnabled");
    bindControl("autoMove", "checkbox", "BotState.autoMove");
    bindControl("moveMethod", "checkbox", "BotState.moveMethod");
    bindControl("botPower", "range", "BotState.botPower");
    bindControl("moveTime", "range", "BotState.moveTime");
    bindControl("jitter", "range", "BotState.jitter");
    bindControl("premoveEnabled", "checkbox", "BotState.premoveEnabled");
    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");
      }
    });
    document.getElementById("consoleClearBtn").addEventListener("click", () => ui.clearConsole());
    document.getElementById("consoleCopyBtn").addEventListener("click", () => {
      const consoleEl = menuWrap.querySelector("#consoleWindow");
      if (!consoleEl)
        return;
      const text = Array.from(consoleEl.querySelectorAll(".log-line")).map((l) => l.textContent.trim()).join("\n");
      navigator.clipboard.writeText(text).then(() => {
        const btn = document.getElementById("consoleCopyBtn");
        btn.classList.add("copied");
        btn.title = "Copied!";
        setTimeout(() => {
          btn.classList.remove("copied");
          btn.title = "Copy log";
        }, 1500);
      }).catch(() => {
      });
    });
  }
  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);
  }
  var ui;
  var init_ui = __esm({
    "src/ui.js"() {
      init_state();
      init_utils();
      init_board();
      init_san();
      ui = {
        menuWrap: null,
        consoleEl: null,
        lastStatus: "",
        lastBestMove: "",
        moveNumber: 0,
        lastMoveColor: null,
        log(msg, type = "info") {
          if (!this.consoleEl && this.menuWrap) {
            this.consoleEl = this.menuWrap.querySelector("#consoleWindow");
          }
          if (!this.consoleEl)
            return;
          const line = document.createElement("div");
          line.className = `log-line ${type}`;
          line.textContent = msg;
          this.consoleEl.appendChild(line);
          while (this.consoleEl.childElementCount > 100) {
            this.consoleEl.removeChild(this.consoleEl.firstChild);
          }
          this.consoleEl.scrollTop = this.consoleEl.scrollHeight;
        },
        clearConsole() {
          if (!this.consoleEl && this.menuWrap) {
            this.consoleEl = this.menuWrap.querySelector("#consoleWindow");
          }
          if (!this.consoleEl)
            return;
          this.consoleEl.innerHTML = "";
          this.lastStatus = "";
          this.lastBestMove = "";
          this.moveNumber = 0;
          this.lastMoveColor = null;
        },
        updateDisplay(playingAs) {
          if (BotState.statusInfo !== this.lastStatus) {
            const blocked = [
              "Making move...",
              "Move made",
              "Waiting for opponent",
              "Ready",
              "Analyzing...",
              "Analyzing (premove)",
              "Pondering",
              "Move canceled",
              "Manual move",
              "Premove unavailable",
              "Premove skipped"
            ];
            const isBlocked = blocked.some((s) => BotState.statusInfo.includes(s));
            if (!isBlocked) {
              this.log(BotState.statusInfo, "status");
            }
            this.lastStatus = BotState.statusInfo;
          }
          if (BotState.bestMove !== "-" && BotState.bestMove !== this.lastBestMove) {
            let msg = `Eval: ${BotState.currentEvaluation}`;
            if (BotState.principalVariation && BotState.principalVariation !== "-" && BotState.principalVariation !== "Not available") {
              try {
                const game = getGame();
                const fen = game ? getFen(game) : null;
                const sanPv = fen ? pvToSan(fen, BotState.principalVariation) : BotState.principalVariation;
                msg += ` | PV: ${sanPv}`;
              } catch {
                msg += ` | PV: ${BotState.principalVariation}`;
              }
            } else {
              msg += ` | Best: ${BotState.bestMove}`;
            }
            this.log(msg, "info");
            this.lastBestMove = BotState.bestMove;
          }
          updateEvaluationBar(BotState.currentEvaluation, playingAs);
        },
        Settings
      };
    }
  });

  // src/engine/scheduler.js
  var scheduler_exports = {};
  __export(scheduler_exports, {
    clearPremoveChain: () => clearPremoveChain,
    getLastFenProcessedMain: () => getLastFenProcessedMain,
    getLastFenProcessedPremove: () => getLastFenProcessedPremove,
    getPremoveChain: () => getPremoveChain,
    scheduleAnalysis: () => scheduleAnalysis,
    setLastFenProcessedMain: () => setLastFenProcessedMain,
    setLastFenProcessedPremove: () => setLastFenProcessedPremove,
    tryChainPremove: () => tryChainPremove
  });
  function getLastFenProcessedMain() {
    return lastFenProcessedMain;
  }
  function setLastFenProcessedMain(fen) {
    lastFenProcessedMain = fen;
  }
  function getLastFenProcessedPremove() {
    return lastFenProcessedPremove;
  }
  function setLastFenProcessedPremove(fen) {
    lastFenProcessedPremove = fen;
  }
  function getPremoveChain() {
    return premoveChain;
  }
  function clearPremoveChain() {
    premoveChain = [];
    chainBaseFen = "";
  }
  async function tryChainPremove(currentFen, tickCallback) {
    if (premoveChain.length === 0)
      return false;
    const next = premoveChain[0];
    const result = evaluatePremove(currentFen, next.oppUci, next.uci, getPlayerColor(getGame()));
    if (!result.execute) {
      clearPremoveChain();
      return false;
    }
    premoveChain.shift();
    const from = next.uci.substring(0, 2);
    const to = next.uci.substring(2, 4);
    clearArrows();
    drawArrow(from, to, "rgba(80, 180, 255, 0.7)", 3);
    if (BotState.moveMethod === "drag") {
      await simulateDragMove(from, to);
    } else {
      await simulateClickMove(from, to);
    }
    await sleep(80);
    BotState.statusInfo = `\u2705 Chain premove: ${next.uci} (${premoveChain.length} queued)`;
    if (BotState.onUpdateDisplay)
      BotState.onUpdateDisplay(pa());
    return true;
  }
  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 () => {
      if (analysisId !== currentAnalysisId || !BotState.hackEnabled)
        return;
      invalidateGameCache();
      const game = getGame();
      if (!game)
        return;
      if (kind === "main" && lastFenProcessedMain === fen)
        return;
      if (kind !== "main" && lastFenProcessedPremove === fen)
        return;
      try {
        BotState.statusInfo = kind === "main" ? "\u{1F504} Analyzing..." : BotState.premoveEnabled ? "\u{1F504} Analyzing (premove)..." : "\u{1F504} Pondering...";
        if (BotState.onUpdateDisplay)
          BotState.onUpdateDisplay(pa());
        const targetDepth = BotState.botPower || 12;
        if (analysisId !== currentAnalysisId) {
          ctrl.abort("superseded");
          return;
        }
        const data = await getAnalysis(fen, targetDepth, BotState.moveTime, ctrl.signal);
        if (analysisId !== currentAnalysisId)
          return;
        const best = parseBestLine(data);
        if (kind === "main") {
          if (!best) {
            throw new Error("No valid move found by engine.");
          }
          consecutiveFailures = 0;
          BotState.bestMove = best?.uci || "-";
          BotState.currentEvaluation = scoreToDisplay(best?.score);
          BotState.principalVariation = best?.pv || "Not available";
          BotState.statusInfo = `\u2713 Ready (D${data.depth || "?"})`;
          if (BotState.onUpdateDisplay)
            BotState.onUpdateDisplay(pa());
          const game2 = getGame();
          const currentFen = getFen(game2);
          if (currentFen) {
            ui.log(formatMove(currentFen, best.uci), "move");
          }
          clearPremoveChain();
          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;
            clearArrows();
            drawArrow(from, to, "rgba(100, 255, 100, 0.7)", 3);
            await executeMove(from, to, fen, promo, data.depth, tickCallback);
          }
          lastFenProcessedMain = fen;
        } else {
          consecutiveFailures = 0;
          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);
          if (!ourUci) {
            BotState.statusInfo = BotState.premoveEnabled ? `Premove unavailable (no PV)` : `Pondering... (D${data.depth})`;
            if (BotState.onUpdateDisplay)
              BotState.onUpdateDisplay(pa());
            lastFenProcessedPremove = fen;
            return;
          }
          if (!BotState.premoveEnabled) {
            BotState.statusInfo = `Pondering... (D${data.depth})`;
            if (BotState.onUpdateDisplay)
              BotState.onUpdateDisplay(pa());
            lastFenProcessedPremove = fen;
            return;
          }
          const chain = evaluatePremoveChain(fen, best?.pv || "", ourColor, stm);
          if (chain.length === 0) {
            const premoveResult = evaluatePremove(fen, opponentUci, ourUci, ourColor);
            if (premoveResult.blocked) {
              const san = uciToSan(fen, ourUci);
              ui.log(`\u26A0 Premove ${san} blocked: ${premoveResult.blocked}`, "warn");
              lastFenProcessedPremove = fen;
              return;
            }
            if (!premoveResult.execute) {
              lastFenProcessedPremove = fen;
              return;
            }
            const from2 = ourUci.substring(0, 2);
            const to2 = ourUci.substring(2, 4);
            clearArrows();
            drawArrow(from2, to2, "rgba(80, 180, 255, 0.7)", 3);
            if (BotState.moveMethod === "drag") {
              await simulateDragMove(from2, to2);
            } else {
              await simulateClickMove(from2, to2);
            }
            await sleep(80);
            ui.log(`\u26A1 ${formatMove(fen, ourUci)}`, "move");
            lastFenProcessedPremove = fen;
            return;
          }
          const firstMove = chain[0];
          premoveChain = chain.slice(1);
          chainBaseFen = fen;
          const from = firstMove.uci.substring(0, 2);
          const to = firstMove.uci.substring(2, 4);
          clearArrows();
          drawArrow(from, to, "rgba(80, 180, 255, 0.7)", 3);
          for (let i = 0; i < premoveChain.length; i++) {
            const qm = premoveChain[i];
            const qFrom = qm.uci.substring(0, 2);
            const qTo = qm.uci.substring(2, 4);
            const opacity = Math.max(0.2, 0.5 - i * 0.15);
            drawArrow(qFrom, qTo, `rgba(80, 180, 255, ${opacity})`, 2);
          }
          if (BotState.moveMethod === "drag") {
            await simulateDragMove(from, to);
          } else {
            await simulateClickMove(from, to);
          }
          await sleep(80);
          ui.log(`\u26A1 ${formatMove(fen, firstMove.uci)}`, "move");
          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);
          consecutiveFailures++;
          if (consecutiveFailures <= MAX_FAILURES) {
            BotState.statusInfo = `\u274C Error - Retrying (${consecutiveFailures}/${MAX_FAILURES})...`;
            BotState.currentEvaluation = "Error";
            if (BotState.onUpdateDisplay)
              BotState.onUpdateDisplay(pa());
            if (kind === "main" && scheduledMainFen === fen)
              scheduledMainFen = "";
            else if (kind !== "main" && scheduledPremoveFen === fen)
              scheduledPremoveFen = "";
            setTimeout(() => {
              resetEngine();
              if (kind === "main" && lastFenProcessedMain === fen)
                lastFenProcessedMain = "";
              else if (kind !== "main" && lastFenProcessedPremove === fen)
                lastFenProcessedPremove = "";
              scheduleAnalysis(kind, fen, tickCallback);
            }, 1e3);
          } else {
            BotState.statusInfo = "\u274C Engine Failed (Max Retries)";
            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;
      }
    };
    run();
  }
  var consecutiveFailures, MAX_FAILURES, currentAnalysisId, currentAbortController, lastFenProcessedMain, lastFenProcessedPremove, premoveChain, chainBaseFen, scheduledMainFen, scheduledPremoveFen;
  var init_scheduler = __esm({
    "src/engine/scheduler.js"() {
      init_state();
      init_utils();
      init_board();
      init_analysis();
      init_premove();
      init_ui();
      init_san();
      consecutiveFailures = 0;
      MAX_FAILURES = 3;
      currentAnalysisId = 0;
      currentAbortController = null;
      lastFenProcessedMain = "";
      lastFenProcessedPremove = "";
      premoveChain = [];
      chainBaseFen = "";
      scheduledMainFen = "";
      scheduledPremoveFen = "";
    }
  });

  // src/board.js
  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());
    if (boardMoveObserver) {
      stopMoveWatcher();
      startMoveWatcher();
    }
  }
  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 = document.querySelector("chess-board") || document.querySelector(".board") || document.querySelector('[class*="board"]');
      if (!newBoard)
        return;
      if (!boardCtx || boardCtx.boardEl !== newBoard) {
        attachToBoard(newBoard);
      }
    }, 200));
    domObserver.observe(document.body, { childList: true, subtree: true });
  }
  function onBoardMutation(cb) {
    mutationListeners.add(cb);
    return () => mutationListeners.delete(cb);
  }
  function startMoveWatcher() {
    stopMoveWatcher();
    const game = getGame();
    const board = game && (document.querySelector("chess-board") || document.querySelector(".board"));
    if (!board)
      return;
    boardMoveObserver = new MutationObserver(() => {
      if (debounceTimer)
        return;
      debounceTimer = setTimeout(() => {
        debounceTimer = null;
        invalidateGameCache();
        mutationListeners.forEach((cb) => cb());
      }, 80);
    });
    boardMoveObserver.observe(board, {
      childList: true,
      subtree: true,
      attributes: true,
      attributeFilter: ["class", "style", "data-piece"]
    });
  }
  function stopMoveWatcher() {
    if (boardMoveObserver) {
      boardMoveObserver.disconnect();
      boardMoveObserver = null;
    }
    if (debounceTimer) {
      clearTimeout(debounceTimer);
      debounceTimer = null;
    }
  }
  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;
    }
    const result = { x: offsetX + (x + 0.5) * tile, y: offsetY + (y + 0.5) * tile };
    if (isNaN(result.x) || isNaN(result.y)) {
      console.warn(`GabiBot: Invalid coordinates for square ${square}. Board size: ${rect.width}x${rect.height}`);
      return null;
    }
    return result;
  }
  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;
  }
  function getMoveCoordinates(from, to) {
    const a = getSquareCenterClientXY(from);
    const b = getSquareCenterClientXY(to);
    if (!a || !b)
      return null;
    return { a, b };
  }
  function createPointerOpts(x, y, buttons) {
    return {
      clientX: x,
      clientY: y,
      buttons,
      pointerId: 1,
      pointerType: "mouse",
      isPrimary: true,
      bubbles: true,
      cancelable: true,
      composed: true
    };
  }
  async function simulateClickMove(from, to) {
    const coords = getMoveCoordinates(from, to);
    if (!coords)
      return false;
    const { a, b } = coords;
    const usePointer = !!window.PointerEvent;
    const startEl = getTargetAt(a.x, a.y);
    const endEl = getTargetAt(b.x, b.y);
    const downStart = createPointerOpts(a.x, a.y, 1);
    const upStart = createPointerOpts(a.x, a.y, 0);
    const downEnd = createPointerOpts(b.x, b.y, 1);
    const upEnd = createPointerOpts(b.x, b.y, 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 simulateDragMove(from, to) {
    const coords = getMoveCoordinates(from, to);
    if (!coords)
      return false;
    const { a, b } = coords;
    const usePointer = !!window.PointerEvent;
    const startEl = getTargetAt(a.x, a.y);
    const down = createPointerOpts(a.x, a.y, 1);
    dispatchPointerOrMouse(startEl, usePointer ? "pointerdown" : "mousedown", down, usePointer);
    await sleep(20);
    const steps = 10;
    for (let i = 1; i <= steps; i++) {
      const t = i / steps;
      const mx = a.x + (b.x - a.x) * t;
      const my = a.y + (b.y - a.y) * t;
      const stepTarget = getTargetAt(mx, my);
      const moveOpts = createPointerOpts(mx, my, 1);
      dispatchPointerOrMouse(stepTarget, usePointer ? "pointermove" : "mousemove", moveOpts, usePointer);
      await sleep(12);
    }
    const endEl = getTargetAt(b.x, b.y);
    const up = createPointerOpts(b.x, b.y, 0);
    dispatchPointerOrMouse(endEl, usePointer ? "pointerup" : "mouseup", up, usePointer);
    return true;
  }
  async function waitForFenChange(prevFen, timeout = 1e3) {
    return new Promise((resolve) => {
      let g = getGame();
      let fen = g?.getFEN ? g.getFEN() : null;
      if (fen && fen !== prevFen)
        return resolve(true);
      const start = performance.now();
      let resolved = false;
      const cleanup = onBoardMutation(() => {
        if (resolved)
          return;
        g = getGame();
        fen = g?.getFEN ? g.getFEN() : null;
        if (fen && fen !== prevFen) {
          resolved = true;
          resolve(true);
        }
      });
      const check = () => {
        if (resolved)
          return;
        g = getGame();
        fen = g?.getFEN ? g.getFEN() : null;
        if (fen && fen !== prevFen) {
          resolved = true;
          cleanup();
          return resolve(true);
        }
        if (performance.now() - start > timeout) {
          resolved = true;
          cleanup();
          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 = () => Array.from(document.querySelectorAll('[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;
    if (BotState.moveMethod === "drag") {
      const dragged = await simulateDragMove(from, to);
      if (!dragged)
        return false;
    } else {
      await simulateClickMove(from, to);
    }
    if (promotionChar)
      await maybeSelectPromotion(String(promotionChar).toLowerCase());
    const changed = await waitForFenChange(beforeFen, 2500);
    return !!changed;
  }
  function executeMove(from, to, analysisFen, promotionChar, depth, tickCallback) {
    if (BotState.hackEnabled && BotState.autoMove) {
      if (!getGame() || !isPlayersTurn(getGame()))
        return;
      cancelPendingMove();
      const thinkTime = BotState.moveTime;
      const jitter = BotState.jitter ? Math.random() * BotState.jitter : 0;
      const totalDelay = thinkTime + jitter;
      const depthLabel = depth ? ` (D${depth})` : "";
      BotState.statusInfo = `Thinking${depthLabel} (${(totalDelay / 1e3).toFixed(1)}s)...`;
      if (BotState.onUpdateDisplay)
        BotState.onUpdateDisplay(pa());
      pendingMoveTimeoutId = setTimeout(async () => {
        const g = getGame();
        if (!g || !isPlayersTurn(g) || getFen(g) !== analysisFen) {
          BotState.statusInfo = "Move canceled (state 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);
        if (success) {
          BotState.statusInfo = "\u2713 Move made!";
          if (BotState.onUpdateDisplay)
            BotState.onUpdateDisplay(pa());
        } else {
          BotState.statusInfo = "\u274C Move failed \u2014 retrying...";
          if (BotState.onUpdateDisplay)
            BotState.onUpdateDisplay(pa());
          Promise.resolve().then(() => (init_scheduler(), scheduler_exports)).then((module) => {
            module.setLastFenProcessedMain("");
            if (tickCallback) {
              setTimeout(() => tickCallback(), 500);
            }
          });
        }
      }, 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)";
    }
  }
  var boardCtx, domObserver, pendingMoveTimeoutId, boardMoveObserver, mutationListeners, debounceTimer;
  var init_board = __esm({
    "src/board.js"() {
      init_utils();
      init_state();
      boardCtx = null;
      domObserver = null;
      pendingMoveTimeoutId = null;
      boardMoveObserver = null;
      mutationListeners = /* @__PURE__ */ new Set();
      debounceTimer = null;
    }
  });

  // src/index.js
  init_state();
  init_utils();
  init_board();
  init_ui();

  // src/controller.js
  init_state();
  init_utils();
  init_board();
  init_ui();
  init_scheduler();
  init_analysis();
  init_san();
  var BotController = class {
    constructor() {
      this.isActive = false;
      this.tickTimer = null;
      this.checkInterval = null;
      this.lastFenSeen = "";
      this.lastOpponentMove = null;
      this.gameStarted = false;
      this.gameEndDetected = false;
      this.mutationCleanup = null;
    }
    init() {
      this.startSettingsWatcher();
      startDomBoardWatcher();
      if (BotState.hackEnabled) {
        this.start();
      }
    }
    start() {
      if (this.isActive)
        return;
      this.isActive = true;
      console.log("GabiBot: Controller started.");
      BotState.statusInfo = "Ready";
      ui.updateDisplay(pa());
      this.startTickLoop();
      this.startGameCheckLoop();
    }
    stop() {
      if (!this.isActive)
        return;
      this.isActive = false;
      console.log("GabiBot: Controller stopped.");
      this.stopTickLoop();
      this.stopGameCheckLoop();
      PositionCache.clear();
      clearArrows();
      cancelPendingMove();
      BotState.statusInfo = "Bot disabled";
      BotState.currentEvaluation = "-";
      BotState.bestMove = "-";
      ui.updateDisplay(pa());
    }
    toggle() {
      if (this.isActive)
        this.stop();
      else
        this.start();
    }
    // --- Main Logic Loop ---
    startTickLoop() {
      this.stopTickLoop();
      startMoveWatcher();
      if (!this.mutationCleanup) {
        this.mutationCleanup = onBoardMutation(() => {
          if (!this.isActive || !BotState.hackEnabled)
            return;
          this.tick();
        });
      }
      const loop = () => {
        if (!this.isActive || !BotState.hackEnabled)
          return;
        this.tick();
        this.tickTimer = setTimeout(loop, 1e3);
      };
      loop();
    }
    stopTickLoop() {
      if (this.tickTimer)
        clearTimeout(this.tickTimer);
      this.tickTimer = null;
      stopMoveWatcher();
      if (this.mutationCleanup) {
        this.mutationCleanup();
        this.mutationCleanup = null;
      }
    }
    tick() {
      invalidateGameCache();
      const game = getGame();
      if (!game)
        return;
      if (game.isGameOver && game.isGameOver()) {
        this.handleInternalGameOver();
        return;
      }
      const fen = getFen(game);
      if (!fen)
        return;
      if (fen !== this.lastFenSeen) {
        if (!this.gameStarted) {
          this.gameStarted = true;
          const playerColor = getPlayerColor(game);
          const color = playerColor === "w" ? "White" : "Black";
          ui.log(`Bot is playing ${color}`, "status");
        }
        if (this.lastFenSeen && isPlayersTurn(game)) {
          const oppMove = this.extractLastMove(this.lastFenSeen, fen);
          if (oppMove) {
            ui.log(formatMove(this.lastFenSeen, oppMove), "opponent");
          } else {
            ui.log("...", "opponent");
          }
        }
        this.lastFenSeen = fen;
        cancelPendingMove();
        clearArrows();
      }
      if (isPlayersTurn(game)) {
        if (getLastFenProcessedMain() !== fen) {
          scheduleAnalysis("main", fen, () => this.tick());
        }
      } else {
        if (getLastFenProcessedPremove() !== fen) {
          scheduleAnalysis("premove", fen);
        } else {
          this.setStatus(BotState.premoveEnabled ? "Waiting for opponent..." : "Pondering...");
        }
      }
    }
    handleInternalGameOver() {
      if (BotState.statusInfo !== "Game finished") {
        BotState.currentEvaluation = "GAME OVER";
        BotState.bestMove = "-";
        BotState.principalVariation = "Game ended";
        BotState.statusInfo = "Game finished";
        clearArrows();
        ui.updateDisplay(pa());
      }
    }
    // checkFailsafe was removed — it was the root cause of the infinite
    // fail→reset→fail loop. When a move fails, the bot now simply waits
    // for the board position to change (opponent move / new game).
    // See tests/failsafe-loop.test.js for the proof.
    setStatus(msg) {
      if (BotState.statusInfo !== msg) {
        BotState.statusInfo = msg;
        ui.updateDisplay(pa());
      }
    }
    /**
     * Extract the last move from FEN change (opponent's move)
     * Returns UCI notation
     */
    extractLastMove(prevFen, newFen) {
      try {
        const game = getGame();
        if (game) {
          const parseMove = (m) => {
            if (!m)
              return null;
            if (typeof m === "string")
              return m;
            if (typeof m === "object") {
              if (typeof m.uci === "string")
                return m.uci;
              if (typeof m.from === "string" && typeof m.to === "string") {
                return m.from + m.to + (m.promo || m.promotion || "");
              }
            }
            return null;
          };
          if (game.getLastMove) {
            const lastMove = game.getLastMove();
            const uci = parseMove(lastMove);
            if (uci)
              return uci;
          }
          if (game.getMoveHistory) {
            const history = game.getMoveHistory();
            if (history && history.length > 0) {
              const last = history[history.length - 1];
              const uci = parseMove(last?.move || last);
              if (uci)
                return uci;
            }
          }
        }
        return null;
      } catch {
        return null;
      }
    }
    // --- Game Check Loop (Start/End detection) ---
    // Replaces gameStartInterval / gameEndInterval
    startGameCheckLoop() {
      this.stopGameCheckLoop();
      this.checkInterval = setInterval(() => this.checkGameState(), 1e3);
    }
    stopGameCheckLoop() {
      if (this.checkInterval)
        clearInterval(this.checkInterval);
      this.checkInterval = null;
    }
    checkGameState() {
      const gameOverModal = document.querySelector(".game-over-modal-content");
      if (gameOverModal && !this.gameEndDetected) {
        this.gameEndDetected = true;
        clearArrows();
        cancelPendingMove();
        BotState.statusInfo = "Game ended, preparing new game...";
        BotState.currentEvaluation = "-";
        BotState.bestMove = "-";
        ui.updateDisplay(pa());
        if (BotState.autoRematch) {
          this.handleAutoRematch();
        }
      }
      if (!gameOverModal && this.gameEndDetected) {
        this.gameEndDetected = false;
        this.gameStarted = false;
        setLastFenProcessedMain("");
        setLastFenProcessedPremove("");
        this.lastFenSeen = "";
        this.lastOpponentMove = null;
        resetEngine();
        ui.clearConsole();
        const playerColor = getPlayerColor(getGame());
        const color = playerColor === "w" ? "White" : "Black";
        ui.log(`Bot is playing ${color}`, "status");
        BotState.statusInfo = "Ready";
        ui.updateDisplay(pa());
        setTimeout(() => this.tick(), 500);
      }
    }
    handleAutoRematch() {
      console.log("GabiBot: Auto-queue sequence initiated");
      setTimeout(() => {
        const actions = [
          { name: "Accept", sel: 'button[data-cy="game-over-modal-rematch-button"], .game-over-buttons-button', text: "Accept" },
          { name: "Rematch", sel: '[data-cy="sidebar-game-over-rematch-button"]' },
          { name: "New 1 Min Modal", sel: '[data-cy="game-over-modal-new-game-button"]' },
          { name: "New 1 Min", sel: '[data-cy="sidebar-game-over-new-game-button"]' },
          { name: "Arena Next", sel: '[data-cy="next-arena-game-button"]' },
          { name: "Arena Request", sel: '[data-cy="request-arena-game"]' }
        ];
        for (const action of actions) {
          const btns = document.querySelectorAll(action.sel);
          for (const btn of btns) {
            if (btn && btn.offsetParent !== null) {
              if (action.text && !btn.textContent.toLowerCase().includes(action.text.toLowerCase())) {
                continue;
              }
              console.log(`GabiBot: Clicking ${action.name}`);
              btn.click();
              return;
            }
          }
        }
      }, 1500);
    }
    // --- Settings / State Watcher ---
    startSettingsWatcher() {
      let lastHackEnabled = BotState.hackEnabled;
      let lastUpdateSpeed = BotState.updateSpeed;
      setInterval(() => {
        if (BotState.hackEnabled !== lastHackEnabled) {
          lastHackEnabled = BotState.hackEnabled;
          if (BotState.hackEnabled)
            this.start();
          else
            this.stop();
          Settings.save();
        }
      }, 200);
    }
  };
  var controller = new BotController();

  // 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...");
    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();
        const boardEl = board || document.querySelector("chess-board") || document.querySelector(".board") || document.querySelector('[class*="board"]');
        attachToBoard(boardEl);
        controller.init();
        console.log("GabiBot: Initialized.");
      } catch (error) {
        console.error("GabiBot Error:", error);
        alert("GabiBot: Could not find chess board. Please refresh or check console.");
      }
    }
    setTimeout(init, 3e3);
  })();
})();