{
  "license": "CC0-1.0",
  "shader": {
    "id": "77qfigl5",
    "title": "chessv2",
    "license": "CC0-1.0",
    "provenance_type": "bred",
    "made_with": "glslop",
    "status": "active",
    "compile_ok": true,
    "render_hash": "0000003c3c3c3c00",
    "tags": [],
    "parents": [
      "jcg4d38m"
    ],
    "author": "c9jwkv23klru",
    "author_name": "croze",
    "author_url": "/u/croze",
    "created_at": "2026-10-10T05:50:00.874Z",
    "poster_url": "/posters/77qfigl5.avif",
    "stats": {
      "slops": 0,
      "views": 15,
      "forks": 1
    },
    "supersedes": "jcg4d38m",
    "superseded_by": "qseyeqht",
    "self": "/api/v1/shaders/77qfigl5",
    "isf": "/*{\n  \"DESCRIPTION\": \"Playable chess entirely in GLSL, framed by a live visualization of its pixel memory. Board data, legal moves, history and rule registers drive the border; moves, captures and check light it up. Choose From and To, then press Move, or enable Auto-play for a random legal move every second. Changing resolution or seeking resets the game; keep live playback running.\",\n  \"ISFVSN\": \"2\",\n  \"CATEGORIES\": [\n    \"Game\",\n    \"Chess\",\n    \"Interactive\"\n  ],\n  \"INPUTS\": [\n    {\n      \"NAME\": \"uFrom\",\n      \"TYPE\": \"long\",\n      \"LABEL\": \"From\",\n      \"DEFAULT\": 12,\n      \"VALUES\": [\n        0,\n        1,\n        2,\n        3,\n        4,\n        5,\n        6,\n        7,\n        8,\n        9,\n        10,\n        11,\n        12,\n        13,\n        14,\n        15,\n        16,\n        17,\n        18,\n        19,\n        20,\n        21,\n        22,\n        23,\n        24,\n        25,\n        26,\n        27,\n        28,\n        29,\n        30,\n        31,\n        32,\n        33,\n        34,\n        35,\n        36,\n        37,\n        38,\n        39,\n        40,\n        41,\n        42,\n        43,\n        44,\n        45,\n        46,\n        47,\n        48,\n        49,\n        50,\n        51,\n        52,\n        53,\n        54,\n        55,\n        56,\n        57,\n        58,\n        59,\n        60,\n        61,\n        62,\n        63\n      ],\n      \"LABELS\": [\n        \"a1\",\n        \"b1\",\n        \"c1\",\n        \"d1\",\n        \"e1\",\n        \"f1\",\n        \"g1\",\n        \"h1\",\n        \"a2\",\n        \"b2\",\n        \"c2\",\n        \"d2\",\n        \"e2\",\n        \"f2\",\n        \"g2\",\n        \"h2\",\n        \"a3\",\n        \"b3\",\n        \"c3\",\n        \"d3\",\n        \"e3\",\n        \"f3\",\n        \"g3\",\n        \"h3\",\n        \"a4\",\n        \"b4\",\n        \"c4\",\n        \"d4\",\n        \"e4\",\n        \"f4\",\n        \"g4\",\n        \"h4\",\n        \"a5\",\n        \"b5\",\n        \"c5\",\n        \"d5\",\n        \"e5\",\n        \"f5\",\n        \"g5\",\n        \"h5\",\n        \"a6\",\n        \"b6\",\n        \"c6\",\n        \"d6\",\n        \"e6\",\n        \"f6\",\n        \"g6\",\n        \"h6\",\n        \"a7\",\n        \"b7\",\n        \"c7\",\n        \"d7\",\n        \"e7\",\n        \"f7\",\n        \"g7\",\n        \"h7\",\n        \"a8\",\n        \"b8\",\n        \"c8\",\n        \"d8\",\n        \"e8\",\n        \"f8\",\n        \"g8\",\n        \"h8\"\n      ]\n    },\n    {\n      \"NAME\": \"uTo\",\n      \"TYPE\": \"long\",\n      \"LABEL\": \"To\",\n      \"DEFAULT\": 28,\n      \"VALUES\": [\n        0,\n        1,\n        2,\n        3,\n        4,\n        5,\n        6,\n        7,\n        8,\n        9,\n        10,\n        11,\n        12,\n        13,\n        14,\n        15,\n        16,\n        17,\n        18,\n        19,\n        20,\n        21,\n        22,\n        23,\n        24,\n        25,\n        26,\n        27,\n        28,\n        29,\n        30,\n        31,\n        32,\n        33,\n        34,\n        35,\n        36,\n        37,\n        38,\n        39,\n        40,\n        41,\n        42,\n        43,\n        44,\n        45,\n        46,\n        47,\n        48,\n        49,\n        50,\n        51,\n        52,\n        53,\n        54,\n        55,\n        56,\n        57,\n        58,\n        59,\n        60,\n        61,\n        62,\n        63\n      ],\n      \"LABELS\": [\n        \"a1\",\n        \"b1\",\n        \"c1\",\n        \"d1\",\n        \"e1\",\n        \"f1\",\n        \"g1\",\n        \"h1\",\n        \"a2\",\n        \"b2\",\n        \"c2\",\n        \"d2\",\n        \"e2\",\n        \"f2\",\n        \"g2\",\n        \"h2\",\n        \"a3\",\n        \"b3\",\n        \"c3\",\n        \"d3\",\n        \"e3\",\n        \"f3\",\n        \"g3\",\n        \"h3\",\n        \"a4\",\n        \"b4\",\n        \"c4\",\n        \"d4\",\n        \"e4\",\n        \"f4\",\n        \"g4\",\n        \"h4\",\n        \"a5\",\n        \"b5\",\n        \"c5\",\n        \"d5\",\n        \"e5\",\n        \"f5\",\n        \"g5\",\n        \"h5\",\n        \"a6\",\n        \"b6\",\n        \"c6\",\n        \"d6\",\n        \"e6\",\n        \"f6\",\n        \"g6\",\n        \"h6\",\n        \"a7\",\n        \"b7\",\n        \"c7\",\n        \"d7\",\n        \"e7\",\n        \"f7\",\n        \"g7\",\n        \"h7\",\n        \"a8\",\n        \"b8\",\n        \"c8\",\n        \"d8\",\n        \"e8\",\n        \"f8\",\n        \"g8\",\n        \"h8\"\n      ]\n    },\n    {\n      \"NAME\": \"uPromotion\",\n      \"TYPE\": \"long\",\n      \"LABEL\": \"Promote to\",\n      \"DEFAULT\": 5,\n      \"VALUES\": [\n        5,\n        4,\n        3,\n        2\n      ],\n      \"LABELS\": [\n        \"Queen\",\n        \"Rook\",\n        \"Bishop\",\n        \"Knight\"\n      ]\n    },\n    {\n      \"NAME\": \"uMove\",\n      \"TYPE\": \"event\",\n      \"LABEL\": \"Move\"\n    },\n    {\n      \"NAME\": \"uAutoPlay\",\n      \"TYPE\": \"bool\",\n      \"LABEL\": \"Auto-play (1 move / second)\",\n      \"DEFAULT\": false\n    },\n    {\n      \"NAME\": \"uNewGame\",\n      \"TYPE\": \"event\",\n      \"LABEL\": \"New game\"\n    },\n    {\n      \"NAME\": \"uFlip\",\n      \"TYPE\": \"bool\",\n      \"LABEL\": \"Flip board\",\n      \"DEFAULT\": false\n    },\n    {\n      \"NAME\": \"uClaim\",\n      \"TYPE\": \"event\",\n      \"LABEL\": \"Claim draw / arm next move\"\n    },\n    {\n      \"NAME\": \"uResign\",\n      \"TYPE\": \"event\",\n      \"LABEL\": \"Resign\"\n    },\n    {\n      \"NAME\": \"uDrawAgreement\",\n      \"TYPE\": \"event\",\n      \"LABEL\": \"Both players agree to draw\"\n    }\n  ],\n  \"PASSES\": [\n    {\n      \"TARGET\": \"moveState\",\n      \"FLOAT\": true,\n      \"FILTER\": \"NEAREST\"\n    },\n    {\n      \"TARGET\": \"legalMoves\",\n      \"PERSISTENT\": true,\n      \"FLOAT\": true,\n      \"FILTER\": \"NEAREST\"\n    },\n    {\n      \"TARGET\": \"facts\",\n      \"PERSISTENT\": true,\n      \"FLOAT\": true,\n      \"FILTER\": \"NEAREST\"\n    },\n    {\n      \"TARGET\": \"gameState\",\n      \"PERSISTENT\": true,\n      \"FLOAT\": true,\n      \"FILTER\": \"NEAREST\"\n    },\n    {\n      \"TARGET\": \"textAtlas\",\n      \"PERSISTENT\": true,\n      \"FLOAT\": false,\n      \"FILTER\": \"LINEAR\"\n    },\n    {}\n  ]\n}*/\n\n// CHESS — a complete local game in one glslop ISF fragment shader.\n// No custom JavaScript, HTML, vertex shader, images, or external textures.\n// Use glslop's From/To dropdowns, then press Move. Promotion is a preset.\n// Requires glslop FLOAT targets (RGBA16F) and a render size of at least 64x64.\n// Rule values are small exact integers; the ply counter is split base1024.\n// A separate bounded, fractional texel drives cosmetic border animation.\n#ifdef GL_ES\nprecision highp float;\nprecision highp int;\n#endif\n\n// Data occupies a 64-column table in each full-resolution target's corner.\n// 0..15: packed pieces;16:side,rights,ep,halfmove;17:selected,pendingF,pendingT,result;\n// 18:lastF,lastT,plyLow,check;19:historyCursor,historyCount,repeats,claimFlags;\n// 20:event,mover,captured,promoted;21:claimArmed,claimAfterMove,0,0;\n// 22:positionChanged,0,plyHigh,123;23/24:previous event inputs (edge detection).\n// 25:move pulse,capture pulse,rejected-move pulse,phase seconds modulo4.\n// 26:previous autoplay toggle,start whole seconds modulo1024,start fraction,0.\n// 27:PRNG low byte,PRNG high byte,total legal moves including promotions,0.\n// 28:white king square,black king square,0,0; updated atomically with pieces.\n// 64+17*i: 16 exact board texels plus (side,rights,legalEp,0), i=0..150.\nvec2 dataPixel(int index) {\n    float n = float(index);\n    return vec2(mod(n, 64.0), floor(n / 64.0)) + 0.5;\n}\nvec4 gameData(int index) { return IMG_PIXEL(gameState, dataPixel(index)); }\nvec4 moveData(int index) { return IMG_PIXEL(moveState, dataPixel(index)); }\nvec4 factData(int index) { return IMG_PIXEL(facts, dataPixel(index)); }\nint packedPiece(vec4 sampleValue, int component) {\n    // No dynamic vector indexing or branch ladder in the many rule lookups.\n    vec4 mask = vec4(equal(vec4(float(component)), vec4(0.0, 1.0, 2.0, 3.0)));\n    return int(dot(sampleValue, mask));\n}\nint rulesBasePiece(int square) {\n    if (square < 0 || square >= 64) return 0;\n    int column = square / 4;\n    // All rule evaluation reads the candidate committed in pass 0. Pass 0\n    // itself uses uiPiece/gameData explicitly to read the previous position.\n    return packedPiece(moveData(column), square - column * 4);\n}\nint rulesBaseKing(int side) {\n    vec4 kings = moveData(28);\n    return int(side == 1 ? kings.x : kings.y);\n}\nfloat storedLegal(int from, int to) {\n    if (from < 0 || from >= 64 || to < 0 || to >= 64) return 0.0;\n    return IMG_PIXEL(legalMoves, vec2(float(to), float(from)) + 0.5).r;\n}\n\n// Rendering adapters. The final pass reads the completed gameState.\nint uiPiece(int square) {\n    if (square < 0 || square >= 64) return 0;\n    int column = square / 4;\n    return packedPiece(gameData(column), square - column * 4);\n}\nint uiData(int key) {\n    ivec4 meta = ivec4(gameData(17));\n    ivec4 last = ivec4(gameData(18));\n    int side = int(gameData(16).x);\n    if (key == 0) return side;\n    if (key == 1) return meta.w == 0 && uiPiece(uFrom) * side > 0 ? uFrom : -1;\n    if (key == 2 || key == 3) return -1;\n    if (key == 4) return meta.w;\n    if (key == 5) return last.x;\n    if (key == 6) return last.y;\n    if (key == 7) return last.w;\n    if (key == 8) return int(gameData(19).w);\n    if (key == 9) return uFlip ? 1 : 0;\n    if (key == 10) return uTo;\n    if (key == 11) return last.z + 1024 * int(gameData(22).z);\n    if (key == 12) return uPromotion;\n    if (key == 13) return gameData(22).w > 122.0 ? 1 : 0;\n    if (key == 14) return int(gameData(20).x);\n    if (key == 15) return int(gameData(21).x);\n    return 0;\n}\nfloat uiLegal(int from, int to) { return storedLegal(from, to); }\n\n\n// Chess rules for GLSL ES 1.00 / WebGL 1.\n// No arrays, integer textures, bitwise operators, or dynamic array indices.\n// Piece values: pawn 1, knight 2, bishop 3, rook 4, queen 5, king 6.\n// White is positive, Black is negative; a1 = 0 and h8 = 63.\n// The containing shader supplies this float-texture decoder:\nint rulesBasePiece(int square);\nint rulesBaseKing(int side);\n\nint ruleAbs(int value) { return value < 0 ? -value : value; }\nint ruleSide(int value) { return value > 0 ? 1 : (value < 0 ? -1 : 0); }\nint ruleFile(int square) { return square - (square / 8) * 8; }\nint ruleRank(int square) { return square / 8; }\nbool ruleInside(int file, int rank) {\n    return file >= 0 && file < 8 && rank >= 0 && rank < 8;\n}\nbool ruleHasRight(int rights, int bitValue) {\n    return mod(floor(float(rights) / float(bitValue)), 2.0) > 0.5;\n}\nint ruleRemoveRight(int rights, int bitValue) {\n    return ruleHasRight(rights, bitValue) ? rights - bitValue : rights;\n}\n\n// A single hypothetical move overlays the already committed moveState.\n// The pass pipeline supplies that base board; nested overlays are unnecessary.\n// Arithmetic masks avoid branch/return expansion at every attack lookup.\nstruct RulePosition {\n    int fromSquare;\n    int toSquare;\n    int movingPiece;\n    int epVictim;\n    int rookFrom;\n    int rookTo;\n    int rookPiece;\n};\nRulePosition ruleCurrent() {\n    return RulePosition(-1, -1, 0, -1, -1, -1, 0);\n}\n\nint ruleOverlayPiece(RulePosition position, int square, int basePiece) {\n    vec4 mask = vec4(equal(vec4(float(square)),\n        vec4(float(position.toSquare), float(position.rookTo),\n             float(position.fromSquare), float(position.epVictim))));\n    float clearRook = float(square == position.rookFrom);\n    float replaced = max(max(mask.x, mask.y), max(max(mask.z, mask.w), clearRook));\n    return int(float(basePiece) * (1.0 - replaced) +\n               float(position.movingPiece) * mask.x + float(position.rookPiece) * mask.y);\n}\nint rulePiece(RulePosition position, int square) {\n    if (square < 0 || square >= 64) return 0;\n    return ruleOverlayPiece(position, square, rulesBasePiece(square));\n}\n\n// Build an overlay using pieces the caller has already read from the base.\n// promotion is an absolute piece type 2..5; all other values mean queen.\n// The caller owns turn, clocks, castling rights, and en-passant metadata.\nRulePosition ruleMoveOverlay(int moving, int target, int from, int to, int promotion, int ep) {\n    int side = ruleSide(moving);\n    int kind = ruleAbs(moving);\n    RulePosition move = RulePosition(from, to, moving, -1, -1, -1, 0);\n\n    if (kind == 1 && to == ep && target == 0 &&\n        ruleAbs(ruleFile(to) - ruleFile(from)) == 1 &&\n        ruleRank(to) - ruleRank(from) == side) {\n        move.epVictim = to - side * 8;\n    }\n    if (kind == 6 && ruleAbs(to - from) == 2) {\n        move.rookFrom = to > from ? from + 3 : from - 4;\n        move.rookTo = to > from ? from + 1 : from - 1;\n        // legalMove verifies the original rook before a castle is accepted.\n        move.rookPiece = side * 4;\n    }\n    if (kind == 1 && (ruleRank(to) == 0 || ruleRank(to) == 7)) {\n        int promotedKind = promotion >= 2 && promotion <= 5 ? promotion : 5;\n        move.movingPiece = side * promotedKind;\n    }\n\n    return move;\n}\n\nivec2 ruleKnightStep(int direction) {\n    int odd = direction - 2 * (direction / 2);\n    int verticalSign = direction / 2 - 2 * (direction / 4);\n    return ivec2((1 + odd) * (direction < 4 ? 1 : -1),\n                 (2 - odd) * (1 - 2 * verticalSign));\n}\nivec2 ruleRayStep(int direction) {\n    int odd = direction - 2 * (direction / 2);\n    int signValue = 1 - 2 * odd;\n    if (direction < 4)\n        return direction < 2 ? ivec2(signValue, 0) : ivec2(0, signValue);\n    return ivec2(direction < 6 ? 1 : -1, signValue);\n}\n\n// Geometric attacks include those of pinned pieces, as required for\n// king movement and castling. This function does not call legalMove.\nbool attacked(RulePosition position, int square, int bySide) {\n    if (square < 0 || square >= 64) return false;\n    int file = ruleFile(square);\n    int rank = ruleRank(square);\n\n    // Two pawn sources and eight knight sources share one board lookup site.\n    for (int source = 0; source < 10; ++source) {\n        ivec2 offset = source < 2 ? ivec2(source * 2 - 1, -bySide) :\n                                   ruleKnightStep(source - 2);\n        ivec2 p = ivec2(file, rank) + offset;\n        int expected = bySide * (source < 2 ? 1 : 2);\n        if (ruleInside(p.x, p.y) && rulePiece(position, p.y * 8 + p.x) == expected)\n            return true;\n    }\n    for (int direction = 0; direction < 8; ++direction) {\n        ivec2 step = ruleRayStep(direction);\n        for (int distance = 1; distance <= 7; ++distance) {\n            ivec2 p = ivec2(file, rank) + step * distance;\n            if (!ruleInside(p.x, p.y)) break;\n            int piece = rulePiece(position, p.y * 8 + p.x);\n            if (piece == 0) continue;\n            if (ruleSide(piece) == bySide) {\n                int kind = ruleAbs(piece);\n                if (distance == 1 && kind == 6) return true;\n                if (kind == 5) return true;\n                if (direction < 4 && kind == 4) return true;\n                if (direction >= 4 && kind == 3) return true;\n            }\n            break;\n        }\n    }\n    return false;\n}\n\nint kingSquare(RulePosition position, int side) {\n    return position.movingPiece == side * 6 ? position.toSquare : rulesBaseKing(side);\n}\nbool inCheck(RulePosition position, int side) {\n    int king = kingSquare(position, side);\n    return king < 0 || attacked(position, king, -side);\n}\n\n// Call only after validating rook, bishop, or queen movement geometry.\nbool rulePathClear(RulePosition position, int from, int to) {\n    int df = ruleFile(to) - ruleFile(from);\n    int dr = ruleRank(to) - ruleRank(from);\n    int step = ruleSide(df) + 8 * ruleSide(dr);\n    for (int distance = 1; distance <= 7; ++distance) {\n        int square = from + step * distance;\n        if (square == to) return true;\n        if (square < 0 || square >= 64 || rulePiece(position, square) != 0) return false;\n    }\n    return false;\n}\n\nbool legalMove(RulePosition position, int side, int rights, int ep, int from, int to) {\n    if (from < 0 || from >= 64 || to < 0 || to >= 64 || from == to) return false;\n    if (side != 1 && side != -1) return false;\n    int moving = rulePiece(position, from);\n    int target = rulePiece(position, to);\n    if (ruleSide(moving) != side || ruleSide(target) == side || ruleAbs(target) == 6)\n        return false;\n\n    int kind = ruleAbs(moving);\n    int ff = ruleFile(from);\n    int fr = ruleRank(from);\n    int tf = ruleFile(to);\n    int tr = ruleRank(to);\n    int df = tf - ff;\n    int dr = tr - fr;\n    bool shape = false;\n    bool castle = false;\n\n    if (kind == 1) {\n        int homeRank = side == 1 ? 1 : 6;\n        if (df == 0 && target == 0) {\n            shape = dr == side || (fr == homeRank && dr == 2 * side &&\n                                   rulePiece(position, from + side * 8) == 0);\n        } else if (ruleAbs(df) == 1 && dr == side) {\n            bool ordinaryCapture = ruleSide(target) == -side;\n            bool enPassant = to == ep && target == 0 && tr == (side == 1 ? 5 : 2) &&\n                rulePiece(position, to - side * 8) == -side;\n            shape = ordinaryCapture || enPassant;\n        }\n    } else if (kind == 2) {\n        shape = (ruleAbs(df) == 1 && ruleAbs(dr) == 2) ||\n                (ruleAbs(df) == 2 && ruleAbs(dr) == 1);\n    } else if (kind >= 3 && kind <= 5) {\n        bool diagonal = ruleAbs(df) == ruleAbs(dr);\n        bool straight = df == 0 || dr == 0;\n        shape = ((kind != 4 && diagonal) || (kind != 3 && straight)) &&\n                 rulePathClear(position, from, to);\n    } else if (kind == 6) {\n        shape = ruleAbs(df) <= 1 && ruleAbs(dr) <= 1;\n        int base = side == 1 ? 0 : 56;\n        if (from == base + 4 && tr == fr && ruleAbs(df) == 2) {\n            bool kingside = df > 0;\n            int neededRight = side == 1 ? (kingside ? 1 : 2) : (kingside ? 4 : 8);\n            int rookSquare = base + (kingside ? 7 : 0);\n            bool empty = kingside ?\n                (rulePiece(position, base + 5) == 0 && rulePiece(position, base + 6) == 0) :\n                (rulePiece(position, base + 1) == 0 && rulePiece(position, base + 2) == 0 &&\n                 rulePiece(position, base + 3) == 0);\n            castle = ruleHasRight(rights, neededRight) && empty &&\n                     rulePiece(position, rookSquare) == side * 4;\n            shape = castle;\n        }\n    }\n    if (!shape) return false;\n\n    // Underpromotions have the same occupancy as a queen, hence the same\n    // answer for the moving side's king safety. Choice happens on commit.\n    // A single attack call site checks the final position, then the original\n    // and transit positions for castling. This prevents three inlined copies\n    // of the entire attack evaluator in drivers without shader subroutines.\n    int originalKing = kingSquare(position, side);\n    if (originalKing < 0) return false;\n    for (int stage = 0; stage < 3; ++stage) {\n        if (stage > 0 && !castle) break;\n        int testTo = stage == 0 ? to :\n                     (stage == 1 ? from : from + (to > from ? 1 : -1));\n        RulePosition trial = ruleMoveOverlay(moving, target, from, testTo, 5, ep);\n        int king = kind == 6 ? testTo : originalKing;\n        if (attacked(trial, king, -side)) return false;\n    }\n    return true;\n}\n\n// Supply the pieces read BEFORE the move. A removed right never returns.\nint nextCastlingRights(int rights, int moving, int captured, int from, int to) {\n    if (moving == 6) {\n        rights = ruleRemoveRight(rights, 1);\n        rights = ruleRemoveRight(rights, 2);\n    }\n    if (moving == -6) {\n        rights = ruleRemoveRight(rights, 4);\n        rights = ruleRemoveRight(rights, 8);\n    }\n    if ((moving == 4 && from == 7) || (captured == 4 && to == 7))\n        rights = ruleRemoveRight(rights, 1);\n    if ((moving == 4 && from == 0) || (captured == 4 && to == 0))\n        rights = ruleRemoveRight(rights, 2);\n    if ((moving == -4 && from == 63) || (captured == -4 && to == 63))\n        rights = ruleRemoveRight(rights, 4);\n    if ((moving == -4 && from == 56) || (captured == -4 && to == 56))\n        rights = ruleRemoveRight(rights, 8);\n    return rights;\n}\n\n// Provably dead material classes only: K/K, K+B/K, K+N/K, or bishop-only\n// positions in which every bishop occupies the same square color.\n// K+NN/K and K+N/K+N are not automatic draws: mate remains possible.\nbool insufficientMaterial(RulePosition position) {\n    int whiteKings = 0;\n    int blackKings = 0;\n    int minors = 0;\n    int knights = 0;\n    int bishopColor = -1;\n    bool bishopsSameColor = true;\n    for (int square = 0; square < 64; ++square) {\n        int piece = rulePiece(position, square);\n        int kind = ruleAbs(piece);\n        if (kind == 0) continue;\n        if (kind == 6) {\n            if (piece > 0) ++whiteKings;\n            else ++blackKings;\n        } else if (kind == 2) {\n            ++minors;\n            ++knights;\n        } else if (kind == 3) {\n            ++minors;\n            int sum = ruleFile(square) + ruleRank(square);\n            int color = sum - 2 * (sum / 2);\n            if (bishopColor < 0) bishopColor = color;\n            else if (bishopColor != color) bishopsSameColor = false;\n        } else {\n            return false;\n        }\n    }\n    if (whiteKings != 1 || blackKings != 1) return false;\n    return minors <= 1 || (knights == 0 && bishopsSameColor);\n}\n\n\nint initialPiece(int square) {\n    int rank = ruleRank(square), file = ruleFile(square);\n    if (rank == 1) return 1;\n    if (rank == 6) return -1;\n    if (rank != 0 && rank != 7) return 0;\n    int p = 6;\n    if (file == 0 || file == 7) p = 4;\n    if (file == 1 || file == 6) p = 2;\n    if (file == 2 || file == 5) p = 3;\n    if (file == 3) p = 5;\n    return rank == 0 ? p : -p;\n}\nvec4 initialPacked(int column) {\n    return vec4(float(initialPiece(column * 4)), float(initialPiece(column * 4 + 1)),\n                float(initialPiece(column * 4 + 2)), float(initialPiece(column * 4 + 3)));\n}\nvec4 positionPacked(RulePosition position, int column) {\n    // Pass 0 commits directly over the previous board. Rule evaluation in\n    // later passes uses moveState, so it never needs a sampler-selection branch.\n    vec4 previous = gameData(column);\n    return vec4(float(ruleOverlayPiece(position, column * 4, int(previous.r))),\n                float(ruleOverlayPiece(position, column * 4 + 1, int(previous.g))),\n                float(ruleOverlayPiece(position, column * 4 + 2, int(previous.b))),\n                float(ruleOverlayPiece(position, column * 4 + 3, int(previous.a))));\n}\nvec4 currentEventInputs() {\n    return vec4(uMove ? 1.0 : 0.0, uNewGame ? 1.0 : 0.0,\n                uClaim ? 1.0 : 0.0, uResign ? 1.0 : 0.0);\n}\n\n// Split timestamps retain sub-millisecond accuracy in RGBA16F instead of\n// accumulating a rounded half-float delta on every frame. The clock wraps\n// after 1024 seconds; each successful move starts a fresh one-second wait.\nvec2 autoTimestamp() {\n    float seconds = max(TIME, 0.0);\n    return vec2(mod(floor(seconds), 1024.0), fract(seconds));\n}\nfloat autoElapsed(vec2 currentTime, vec2 startTime) {\n    return mod(currentTime.x - startTime.x + 1024.0, 1024.0) +\n           currentTime.y - startTime.y;\n}\n\n// A full-period 16-bit LCG. Both stored bytes are exact in RGBA16F, and the\n// largest intermediate (65535 * 253 + 17) stays below 2^24, so highp float\n// represents every integer in this calculation exactly. This is a game\n// randomizer, not a cryptographic generator.\nvec2 autoNextSeed(vec2 bytes) {\n    float seed = bytes.x + bytes.y * 256.0;\n    float next = mod(seed * 253.0 + 17.0, 65536.0);\n    return vec2(mod(next, 256.0), floor(next / 256.0));\n}\nvec2 autoInitialSeed() {\n    float seed = mod(floor(mod(max(TIME, 0.0), 64.0) * 1024.0) + 31821.0, 65536.0);\n    return vec2(mod(seed, 256.0), floor(seed / 256.0));\n}\n\n// Select a weighted move ordinal, then resolve it with at most 64 source\n// lookups and 64 destination lookups. A promotion has four tickets, one\n// for each legal piece choice. Ordinary moves have one ticket each.\n// Keeping this work outside historical fragments avoids a 4096-cell scan.\nivec3 autoChooseMove(vec2 randomBytes, int total) {\n    if (total <= 0) return ivec3(-1, -1, 5);\n    float randomValue = randomBytes.x + randomBytes.y * 256.0;\n    int ticket = int(floor(randomValue * float(total) / 65536.0));\n    int chosenFrom = -1;\n    for (int from = 0; from < 64; ++from) {\n        int weight = int(factData(from).g);\n        if (ticket < weight) { chosenFrom = from; break; }\n        ticket -= weight;\n    }\n    if (chosenFrom < 0) return ivec3(-1, -1, 5);\n    for (int to = 0; to < 64; ++to) {\n        vec4 entry = IMG_PIXEL(legalMoves, vec2(float(to), float(chosenFrom)) + 0.5);\n        int weight = entry.r > 0.5 ? (entry.g > 0.5 ? 4 : 1) : 0;\n        if (ticket < weight) return ivec3(chosenFrom, to, weight == 4 ? ticket + 2 : 5);\n        ticket -= weight;\n    }\n    return ivec3(-1, -1, 5);\n}\n\n// PASS 0: process event edges or one due automatic move. Manual actions\n// take priority; a frame can commit at most one move, never a catch-up burst.\n// The old completed position and old legal-move table are from last frame.\nvec4 advanceGame(int index) {\n    if (index >= 2631) return vec4(0.0);\n    vec4 oldValue = gameData(index);\n    ivec4 system = ivec4(gameData(22));\n    vec4 previousEvents = gameData(23);\n    bool resetNow = system.w != 123 || (uNewGame && previousEvents.y < 0.5);\n    if (resetNow) {\n        if (index < 16) return initialPacked(index);\n        if (index == 16) return vec4(1.0, 15.0, -1.0, 0.0);\n        if (index == 17) return vec4(-1.0, -1.0, -1.0, 0.0);\n        if (index == 18) return vec4(-1.0, -1.0, 0.0, 0.0);\n        if (index == 19) return vec4(0.0, 1.0, 1.0, 0.0);\n        if (index == 22) return vec4(1.0, 0.0, 0.0, 123.0);\n        if (index == 23) return currentEventInputs();\n        if (index == 24) return vec4(uDrawAgreement ? 1.0 : 0.0, 0.0, 0.0, 0.0);\n        if (index == 26) return vec4(uAutoPlay ? 1.0 : 0.0, autoTimestamp(), 0.0);\n        if (index == 27) return vec4(autoInitialSeed(), 0.0, 0.0);\n        if (index == 28) return vec4(4.0, 60.0, 0.0, 0.0);\n        return vec4(0.0);\n    }\n    // History is updated in pass 3; no rule work for historical cells here.\n    // Only 27 board/metadata fragments can enter automatic move selection.\n    if (index >= 29) return oldValue;\n    if (index == 23) return currentEventInputs();\n    if (index == 24) return vec4(uDrawAgreement ? 1.0 : 0.0, 0.0, 0.0, 0.0);\n\n    ivec4 rules = ivec4(gameData(16));\n    ivec4 ui = ivec4(gameData(17));\n    ivec4 last = ivec4(gameData(18));\n    ivec4 hist = ivec4(gameData(19));\n    ivec4 event = ivec4(gameData(20));\n    ivec4 claim = ivec4(gameData(21));\n    vec4 glow = gameData(25);\n    vec4 autoClock = gameData(26);\n    vec4 randomState = gameData(27);\n    vec4 kings = gameData(28);\n    float dt = clamp(TIMEDELTA, 0.0, 0.10);\n    glow = vec4(glow.xyz * exp(-dt * vec3(2.8, 1.8, 4.0)), mod(glow.w + dt, 4.0));\n    glow.xyz *= step(vec3(0.001), glow.xyz);\n    system.x = 0;\n    bool moveNow = uMove && previousEvents.x < 0.5;\n    bool claimNow = uClaim && previousEvents.z < 0.5;\n    bool resignNow = uResign && previousEvents.w < 0.5;\n    bool agreeNow = uDrawAgreement && gameData(24).x < 0.5;\n    bool committed = false;\n    RulePosition position = ruleCurrent();\n    int chosenFrom = uFrom;\n    int chosenTo = uTo;\n    int chosenPromotion = uPromotion;\n\n    vec2 now = autoTimestamp();\n    bool manualAction = moveNow || claimNow || resignNow || agreeNow;\n    bool restartClock = !uAutoPlay || autoClock.x < 0.5 || ui.w != 0 ||\n                        manualAction || TIMEDELTA < 0.0;\n    autoClock.x = uAutoPlay ? 1.0 : 0.0;\n    if (restartClock) autoClock.yz = now;\n    else if (autoElapsed(now, autoClock.yz) >= 1.0) {\n        // Restart even if no move is available; terminal adjudication normally\n        // prevents this branch entirely when the legal table is empty.\n        autoClock.yz = now;\n        if (randomState.z > 0.5) {\n            randomState.xy = autoNextSeed(randomState.xy);\n            ivec3 choice = autoChooseMove(randomState.xy, int(randomState.z));\n            if (choice.x >= 0 && choice.y >= 0) {\n                chosenFrom = choice.x;\n                chosenTo = choice.y;\n                chosenPromotion = choice.z;\n                moveNow = true;\n            }\n        }\n    }\n\n    if (ui.w == 0) {\n        if (claimNow) {\n            if (hist.w > 0) ui.w = ruleHasRight(hist.w, 1) ? 7 : 8;\n            else { claim.x = 1 - claim.x; event.x = claim.x == 1 ? 5 : 0; }\n        } else if (resignNow) {\n            bool opponentHasMaterial = false;\n            for (int square = 0; square < 64; ++square) {\n                int p = uiPiece(square);\n                if (p * rules.x < 0 && ruleAbs(p) != 6) opponentHasMaterial = true;\n            }\n            ui.w = opponentHasMaterial ? (rules.x == 1 ? 10 : 9) : 4;\n        } else if (agreeNow) ui.w = 11;\n        else if (moveNow) {\n            if (storedLegal(chosenFrom, chosenTo) < 0.5) { event.x = 2; glow.z = 1.0; }\n            else {\n                int moving = uiPiece(chosenFrom);\n                int target = uiPiece(chosenTo);\n                int captured = target;\n                if (ruleAbs(moving) == 1 && chosenTo == rules.z && captured == 0 &&\n                    ruleFile(chosenFrom) != ruleFile(chosenTo)) captured = -rules.x;\n                bool irreversible = ruleAbs(moving) == 1 || captured != 0;\n                int rights = nextCastlingRights(rules.y, moving, target, chosenFrom, chosenTo);\n                position = ruleMoveOverlay(moving, target, chosenFrom, chosenTo,\n                                           chosenPromotion, rules.z);\n                if (moving == 6) kings.x = float(chosenTo);\n                if (moving == -6) kings.y = float(chosenTo);\n                int ep = ruleAbs(moving) == 1 && ruleAbs(chosenTo - chosenFrom) == 16 ?\n                         (chosenTo + chosenFrom) / 2 : -1;\n                rules = ivec4(-rules.x, rights, ep, irreversible ? 0 : rules.w + 1);\n                int low = last.z + 1;\n                if (low >= 1024) { low = 0; system.z += 1; }\n                last = ivec4(chosenFrom, chosenTo, low, 0);\n                int cursor = irreversible ? 0 : hist.x + 1;\n                if (cursor >= 151) cursor = 0;\n                int count = irreversible ? 1 : hist.y + 1;\n                if (count > 151) count = 151;\n                hist = ivec4(cursor, count, 0, 0);\n                event = ivec4(3, moving, captured, ruleAbs(moving) == 1 &&\n                    (ruleRank(chosenTo) == 0 || ruleRank(chosenTo) == 7) ? chosenPromotion : 0);\n                claim = ivec4(0, claim.x, 0, 0);\n                system.x = 1;\n                committed = true;\n                // The same confirmed move drives both human and autoplay\n                // flashes. Captures include en passant; no stale pulse is reused.\n                glow.xyz = vec3(1.0, captured != 0 ? 1.0 : 0.0, 0.0);\n                autoClock.yz = now;\n            }\n        }\n    }\n    if (ui.w != 0) { claim = ivec4(0); hist.w = 0; }\n    if (index < 16) return committed ? positionPacked(position, index) : oldValue;\n    if (index == 16) return vec4(rules);\n    if (index == 17) return vec4(ui);\n    if (index == 18) return vec4(last);\n    if (index == 19) return vec4(hist);\n    if (index == 20) return vec4(event);\n    if (index == 21) return vec4(claim);\n    if (index == 22) return vec4(system);\n    if (index == 25) return glow;\n    if (index == 26) return autoClock;\n    if (index == 27) return randomState;\n    if (index == 28) return kings;\n    return oldValue;\n}\n\n// PASS 1: one fragment per ordered (from,to) pair, 64x64 in the corner.\n// Cache it across idle frames; only a changed board requires rule evaluation.\nvec4 calculateLegal(ivec2 pixel) {\n    if (pixel.x >= 64 || pixel.y >= 64) return vec4(0.0);\n    if (moveData(22).x < 0.5) return IMG_THIS_PIXEL(legalMoves);\n    int from = pixel.y, to = pixel.x;\n    ivec4 rules = ivec4(moveData(16));\n    RulePosition position = ruleCurrent();\n    if (!legalMove(position, rules.x, rules.y, rules.z, from, to)) return vec4(0.0);\n    bool promotion = ruleAbs(rulePiece(position, from)) == 1 &&\n                     (ruleRank(to) == 0 || ruleRank(to) == 7);\n    return vec4(1.0, promotion ? 1.0 : 0.0, 0.0, 0.0);\n}\n\n// PASS 2: parallel reductions keep large nested loops out of adjudication.\n// row 0: R = legal destinations per source; G = full move count, counting\n// each promotion as four choices. Rows1..3: historical matches.\n// row4/column0: check and provably insufficient material.\nvec4 calculateFacts(int index) {\n    if (index > 256) return vec4(0.0);\n    if (moveData(22).x < 0.5) return IMG_THIS_PIXEL(facts);\n    if (index < 64) {\n        float count = 0.0;\n        float weightedCount = 0.0;\n        for (int to = 0; to < 64; ++to) {\n            vec4 entry = IMG_PIXEL(legalMoves, vec2(float(to), float(index)) + 0.5);\n            count += entry.r;\n            weightedCount += entry.r + 3.0 * entry.g;\n        }\n        return vec4(count, weightedCount, 0.0, 0.0);\n    }\n    if (index < 215) {\n        int slot = index - 64;\n        ivec4 hist = ivec4(moveData(19));\n        if (slot >= hist.y || slot == hist.x) return vec4(0.0);\n        ivec4 rules = ivec4(moveData(16));\n        // A newly available EP target always follows a pawn move, which resets\n        // history to one position. Other history comparisons have ep == -1.\n        if (rules.z >= 0) return vec4(0.0);\n        vec4 key = vec4(float(rules.x), float(rules.y), -1.0, 0.0);\n        if (any(notEqual(moveData(64 + slot * 17 + 16), key))) return vec4(0.0);\n        bool same = true;\n        for (int column = 0; column < 16; ++column) {\n            if (any(notEqual(moveData(column), moveData(64 + slot * 17 + column)))) {\n                same = false; break;\n            }\n        }\n        return vec4(same ? 1.0 : 0.0, 0.0, 0.0, 0.0);\n    }\n    if (index == 256) {\n        RulePosition position = ruleCurrent();\n        return vec4(inCheck(position, int(moveData(16).x)) ? 1.0 : 0.0,\n                    insufficientMaterial(position) ? 1.0 : 0.0, 0.0, 0.0);\n    }\n    return vec4(0.0);\n}\n\n// The legal table already includes pin/discovered-check tests. Reuse it for\n// exact repetition identity instead of inlining legalMove a second time.\n// Only an actually legal en-passant capture makes the raw EP target relevant.\nint cachedEffectiveEp(int side, int ep) {\n    if (ep < 0 || ep >= 64 || (side != 1 && side != -1)) return -1;\n    if (ruleRank(ep) != (side == 1 ? 5 : 2) || rulesBasePiece(ep) != 0 ||\n        rulesBasePiece(ep - side * 8) != -side) return -1;\n    for (int direction = -1; direction <= 1; direction += 2) {\n        int file = ruleFile(ep) + direction;\n        if (file < 0 || file >= 8) continue;\n        int from = (ruleRank(ep) - side) * 8 + file;\n        if (rulesBasePiece(from) == side && storedLegal(from, ep) > 0.5) return ep;\n    }\n    return -1;\n}\n\n// PASS 3: complete the new state and write one exact historical position.\n// A game-ending result locks all subsequent move events until New Game.\nvec4 finishGame(int index) {\n    if (index >= 2631) return vec4(0.0);\n    vec4 value = moveData(index);\n    if (moveData(22).x < 0.5) return value;\n    if (index == 27) {\n        // Cache the total for next frame's weighted random ticket. Seed bytes\n        // remain whatever pass 0 wrote; this texel never enters repetition keys.\n        float total = 0.0;\n        for (int from = 0; from < 64; ++from) total += factData(from).g;\n        value.z = total;\n        return value;\n    }\n    ivec4 hist = ivec4(moveData(19));\n    int historyStart = 64 + hist.x * 17;\n    if (index >= historyStart && index < historyStart + 17) {\n        int column = index - historyStart;\n        if (column < 16) return moveData(column);\n        ivec4 rules = ivec4(moveData(16));\n        int ep = cachedEffectiveEp(rules.x, rules.z);\n        return vec4(float(rules.x), float(rules.y), float(ep), 0.0);\n    }\n    if (index < 17 || index > 21) return value;\n    ivec4 ui = ivec4(moveData(17));\n    ivec4 last = ivec4(moveData(18));\n    ivec4 event = ivec4(moveData(20));\n    ivec4 claim = ivec4(moveData(21));\n    ivec4 rules = ivec4(moveData(16));\n    float available = 0.0;\n    for (int from = 0; from < 64; ++from) available += factData(from).r;\n    int repeats = 1;\n    for (int slot = 0; slot < 151; ++slot) repeats += int(factData(64 + slot).r);\n    hist.z = repeats;\n    hist.w = (repeats >= 3 ? 1 : 0) + (rules.w >= 100 ? 2 : 0);\n    vec4 factsNow = factData(256);\n    last.w = factsNow.r > 0.5 ? 1 : 0;\n    if (ui.w == 0) {\n        if (claim.y != 0 && hist.w > 0) ui.w = ruleHasRight(hist.w, 1) ? 7 : 8;\n        else if (available < 0.5) ui.w = last.w != 0 ? (rules.x == 1 ? 2 : 1) : 3;\n        else if (factsNow.g > 0.5) ui.w = 4;\n        else if (repeats >= 5) ui.w = 5;\n        else if (rules.w >= 150) ui.w = 6;\n        if (claim.y != 0 && ui.w == 0) event.x = 6;\n    }\n    claim.y = 0;\n    if (ui.w != 0) { claim = ivec4(0); hist.w = 0; }\n    if (index == 17) return vec4(ui);\n    if (index == 18) return vec4(last);\n    if (index == 19) return vec4(hist);\n    if (index == 20) return vec4(event);\n    if (index == 21) return vec4(claim);\n    return value;\n}\n\n\n// GLSLOP / ISF compatible rendering: GLSL ES 1.00, no derivative extension,\n// integer textures, dynamically indexed arrays, or external image/font assets.\n// RENDERSIZE, TIME, FRAMEINDEX and PASSINDEX are supplied by ISF.\nconst float INSET = 0.055;\nconst float BOARD_SIZE = 0.89;\n\nbool chessWide() { return RENDERSIZE.x >= RENDERSIZE.y * 1.36; }\nfloat chessSize() {\n    if (chessWide()) return min(RENDERSIZE.x * 0.54, RENDERSIZE.y * 0.79);\n    return min(RENDERSIZE.x * 0.91, RENDERSIZE.y * 0.61);\n}\nvec2 chessOrigin() {\n    float size = chessSize();\n    if (!chessWide())\n        return vec2((RENDERSIZE.x - size) * 0.5,\n                    (RENDERSIZE.y - 1.54 * size) * 0.5 + 0.455 * size);\n    return vec2((RENDERSIZE.x - size) * 0.5,\n                (RENDERSIZE.y - size) * 0.5 + 0.015 * size);\n}\nvec2 chessPoint(vec2 pixel) { return (pixel - chessOrigin()) / chessSize(); }\n\nfloat sdBox(vec2 p, vec2 b) {\n    vec2 q = abs(p) - b;\n    return length(max(q, 0.0)) + min(max(q.x, q.y), 0.0);\n}\n\nfloat sdRoundBox(vec2 p, vec2 b, float r) {\n    return sdBox(p, b - r) - r;\n}\n\nfloat sdCircle(vec2 p, float r) { return length(p) - r; }\n\nfloat sdCapsule(vec2 p, vec2 a, vec2 b, float r) {\n    vec2 pa = p - a, ba = b - a;\n    return length(pa - ba * clamp(dot(pa, ba) / dot(ba, ba), 0.0, 1.0)) - r;\n}\n\n// Bottom half-width, top half-width, half-height.\nfloat sdTrapezoid(vec2 p, float bottom, float top, float height) {\n    vec2 k1 = vec2(top, height);\n    vec2 k2 = vec2(top - bottom, 2.0 * height);\n    p.x = abs(p.x);\n    vec2 ca = vec2(max(0.0, p.x - ((p.y < 0.0) ? bottom : top)), abs(p.y) - height);\n    vec2 cb = p - k1 + k2 * clamp(dot(k1 - p, k2) / dot(k2, k2), 0.0, 1.0);\n    float signValue = (cb.x < 0.0 && ca.y < 0.0) ? -1.0 : 1.0;\n    return signValue * sqrt(min(dot(ca, ca), dot(cb, cb)));\n}\n\nfloat sdEllipse(vec2 p, vec2 radii) {\n    // A distance approximation is sufficient for these small, softly beveled\n    // silhouettes and avoids the cost of an exact ellipse solver per pixel.\n    float k0 = length(p / radii);\n    float k1 = length(p / (radii * radii));\n    if (k0 < 0.00001) return -min(radii.x, radii.y);\n    return k0 * (k0 - 1.0) / max(k1, 0.00001);\n}\n\nvoid polygonEdge(vec2 p, vec2 a, vec2 b, inout float d, inout float side) {\n    vec2 e = b - a;\n    vec2 w = p - a;\n    vec2 q = w - e * clamp(dot(w, e) / dot(e, e), 0.0, 1.0);\n    d = min(d, dot(q, q));\n    bool up = p.y >= a.y;\n    bool down = p.y < b.y;\n    bool crossPositive = e.x * w.y > e.y * w.x;\n    if ((up && down && crossPositive) || (!up && !down && !crossPositive)) side = -side;\n}\n\nfloat knightOutline(vec2 p) {\n    float d = 1.0e5;\n    float side = 1.0;\n    polygonEdge(p, vec2(-0.174, -0.218), vec2(0.184, -0.218), d, side);\n    polygonEdge(p, vec2(-0.135, -0.106), vec2(-0.174, -0.218), d, side);\n    polygonEdge(p, vec2(-0.101, -0.021), vec2(-0.135, -0.106), d, side);\n    polygonEdge(p, vec2(-0.156, 0.049), vec2(-0.101, -0.021), d, side);\n    polygonEdge(p, vec2(-0.218, 0.035), vec2(-0.156, 0.049), d, side);\n    polygonEdge(p, vec2(-0.277, 0.082), vec2(-0.218, 0.035), d, side);\n    polygonEdge(p, vec2(-0.253, 0.146), vec2(-0.277, 0.082), d, side);\n    polygonEdge(p, vec2(-0.158, 0.221), vec2(-0.253, 0.146), d, side);\n    polygonEdge(p, vec2(-0.097, 0.255), vec2(-0.158, 0.221), d, side);\n    polygonEdge(p, vec2(-0.078, 0.347), vec2(-0.097, 0.255), d, side);\n    polygonEdge(p, vec2(-0.017, 0.292), vec2(-0.078, 0.347), d, side);\n    polygonEdge(p, vec2(0.059, 0.299), vec2(-0.017, 0.292), d, side);\n    polygonEdge(p, vec2(0.148, 0.232), vec2(0.059, 0.299), d, side);\n    polygonEdge(p, vec2(0.206, 0.124), vec2(0.148, 0.232), d, side);\n    polygonEdge(p, vec2(0.217, -0.036), vec2(0.206, 0.124), d, side);\n    polygonEdge(p, vec2(0.146, -0.189), vec2(0.217, -0.036), d, side);\n    polygonEdge(p, vec2(0.184, -0.218), vec2(0.146, -0.189), d, side);\n    return side * sqrt(d) - 0.006;\n}\n\nfloat queenCrown(vec2 p) {\n    float d = 1.0e5;\n    float side = 1.0;\n    polygonEdge(p, vec2(-0.147, 0.082), vec2(0.147, 0.082), d, side);\n    polygonEdge(p, vec2(-0.217, 0.273), vec2(-0.147, 0.082), d, side);\n    polygonEdge(p, vec2(-0.163, 0.218), vec2(-0.217, 0.273), d, side);\n    polygonEdge(p, vec2(-0.118, 0.331), vec2(-0.163, 0.218), d, side);\n    polygonEdge(p, vec2(-0.060, 0.230), vec2(-0.118, 0.331), d, side);\n    polygonEdge(p, vec2(0.000, 0.305), vec2(-0.060, 0.230), d, side);\n    polygonEdge(p, vec2(0.060, 0.230), vec2(0.000, 0.305), d, side);\n    polygonEdge(p, vec2(0.118, 0.331), vec2(0.060, 0.230), d, side);\n    polygonEdge(p, vec2(0.163, 0.218), vec2(0.118, 0.331), d, side);\n    polygonEdge(p, vec2(0.217, 0.273), vec2(0.163, 0.218), d, side);\n    polygonEdge(p, vec2(0.147, 0.082), vec2(0.217, 0.273), d, side);\n    d = side * sqrt(d);\n    d = min(d, sdCircle(p - vec2(-0.217, 0.273), 0.028));\n    d = min(d, sdCircle(p - vec2(-0.118, 0.331), 0.028));\n    d = min(d, sdCircle(p - vec2( 0.000, 0.305), 0.028));\n    d = min(d, sdCircle(p - vec2( 0.118, 0.331), 0.028));\n    d = min(d, sdCircle(p - vec2( 0.217, 0.273), 0.028));\n    return d;\n}\n\nfloat pieceDistance(vec2 p, int type) {\n    // Shared turned pedestal; all pieces keep the same optical baseline.\n    float d = sdRoundBox(p - vec2(0.0, -0.313), vec2(0.245, 0.041), 0.021);\n    d = min(d, sdRoundBox(p - vec2(0.0, -0.250), vec2(0.205, 0.028), 0.015));\n    d = min(d, sdTrapezoid(p - vec2(0.0, -0.213), 0.181, 0.155, 0.022));\n\n    if (type == 1) {\n        d = min(d, sdTrapezoid(p - vec2(0.0, -0.094), 0.158, 0.065, 0.111));\n        d = min(d, sdRoundBox(p - vec2(0.0, 0.033), vec2(0.124, 0.027), 0.012));\n        d = min(d, sdCircle(p - vec2(0.0, 0.166), 0.110));\n    } else if (type == 2) {\n        d = min(d, knightOutline(p));\n    } else if (type == 3) {\n        d = min(d, sdTrapezoid(p - vec2(0.0, -0.071), 0.158, 0.062, 0.137));\n        d = min(d, sdRoundBox(p - vec2(0.0, 0.061), vec2(0.133, 0.025), 0.012));\n        float mitre = sdEllipse(p - vec2(0.0, 0.197), vec2(0.108, 0.143));\n        mitre = min(mitre, sdCircle(p - vec2(0.0, 0.333), 0.030));\n        mitre = max(mitre, -sdCapsule(p, vec2(-0.027, 0.176), vec2(0.106, 0.300), 0.017));\n        d = min(d, mitre);\n    } else if (type == 4) {\n        d = min(d, sdTrapezoid(p - vec2(0.0, -0.080), 0.157, 0.131, 0.132));\n        d = min(d, sdRoundBox(p - vec2(0.0, 0.046), vec2(0.163, 0.023), 0.008));\n        float tower = sdTrapezoid(p - vec2(0.0, 0.135), 0.140, 0.182, 0.087);\n        tower = min(tower, sdRoundBox(p - vec2(0.0, 0.211), vec2(0.205, 0.081), 0.006));\n        tower = max(tower, -sdBox(p - vec2(-0.071, 0.285), vec2(0.033, 0.065)));\n        tower = max(tower, -sdBox(p - vec2( 0.071, 0.285), vec2(0.033, 0.065)));\n        d = min(d, tower);\n    } else if (type == 5) {\n        d = min(d, sdTrapezoid(p - vec2(0.0, -0.072), 0.170, 0.101, 0.140));\n        d = min(d, sdRoundBox(p - vec2(0.0, 0.073), vec2(0.155, 0.024), 0.010));\n        d = min(d, queenCrown(p));\n    } else if (type == 6) {\n        d = min(d, sdTrapezoid(p - vec2(0.0, -0.077), 0.169, 0.091, 0.136));\n        d = min(d, sdRoundBox(p - vec2(0.0, 0.061), vec2(0.145, 0.024), 0.010));\n        d = min(d, sdEllipse(p - vec2(0.0, 0.135), vec2(0.133, 0.088)));\n        d = min(d, sdRoundBox(p - vec2(0.0, 0.217), vec2(0.069, 0.020), 0.009));\n        d = min(d, sdRoundBox(p - vec2(0.0, 0.294), vec2(0.027, 0.085), 0.004));\n        d = min(d, sdRoundBox(p - vec2(0.0, 0.322), vec2(0.082, 0.024), 0.004));\n    }\n    return d;\n}\n\nfloat pieceDetail(vec2 p, int type) {\n    float d = sdCapsule(p, vec2(-0.200, -0.286), vec2(0.200, -0.286), 0.0050);\n    d = min(d, sdCapsule(p, vec2(-0.160, -0.228), vec2(0.160, -0.228), 0.0045));\n    if (type == 1) {\n        d = min(d, sdCapsule(p, vec2(-0.097, 0.026), vec2(0.097, 0.026), 0.004));\n    } else if (type == 2) {\n        d = min(d, sdCircle(p - vec2(-0.105, 0.182), 0.016));\n        d = min(d, sdCapsule(p, vec2(-0.238, 0.100), vec2(-0.191, 0.112), 0.006));\n        d = min(d, sdCapsule(p, vec2(0.055, 0.247), vec2(0.120, 0.171), 0.005));\n        d = min(d, sdCapsule(p, vec2(0.120, 0.171), vec2(0.151, 0.065), 0.005));\n        d = min(d, sdCapsule(p, vec2(0.151, 0.065), vec2(0.139, -0.050), 0.005));\n    } else if (type == 3) {\n        d = min(d, sdCapsule(p, vec2(-0.102, 0.054), vec2(0.102, 0.054), 0.004));\n    } else if (type == 4) {\n        d = min(d, sdCapsule(p, vec2(-0.135, 0.058), vec2(0.135, 0.058), 0.0045));\n        d = min(d, sdCapsule(p, vec2(-0.157, 0.153), vec2(0.157, 0.153), 0.004));\n    } else if (type == 5) {\n        d = min(d, sdCapsule(p, vec2(-0.122, 0.087), vec2(0.122, 0.087), 0.005));\n    } else if (type == 6) {\n        d = min(d, sdCapsule(p, vec2(-0.115, 0.057), vec2(0.115, 0.057), 0.0045));\n    }\n    return d;\n}\n\nfloat coverage(float d, float aa) { return 1.0 - smoothstep(-aa, aa, d); }\n\nvec3 drawPiece(vec3 under, vec2 p, int piece, float aa) {\n    int type = piece < 0 ? -piece : piece;\n    float shadow = pieceDistance(p - vec2(0.022, -0.034), type);\n    under *= 1.0 - 0.27 * (1.0 - smoothstep(-0.015, 0.052, shadow));\n    float d = pieceDistance(p, type);\n    float white = piece > 0 ? 1.0 : 0.0;\n    vec3 outline = mix(vec3(0.59, 0.65, 0.56), vec3(0.19, 0.23, 0.19), white);\n    under = mix(under, outline, coverage(d - 0.009, aa));\n\n    vec3 material = mix(vec3(0.145, 0.195, 0.172), vec3(0.929, 0.905, 0.821), white);\n    material += (p.y + 0.15) * mix(0.09, 0.055, white);\n    material -= p.x * 0.055;\n    material += 0.027 * exp(-90.0 * pow(p.x + 0.065 + 0.11 * p.y, 2.0));\n    float bevel = exp(-max(-d, 0.0) * 80.0);\n    material += bevel * (0.023 + 0.065 * p.y - 0.068 * p.x);\n\n    float detail = coverage(pieceDetail(p, type), aa * 0.8);\n    vec3 engraving = mix(vec3(0.48, 0.56, 0.47), vec3(0.43, 0.46, 0.36), white);\n    material = mix(material, engraving, detail * mix(0.55, 0.45, white));\n    return mix(under, material, coverage(d, aa));\n}\n\n\n// A 5 x 7 font: every glyphData integer is exactly representable by highp float.\nvec2 atlasFontBits(int glyph) {\n    if (glyph == 1) return vec2(1033777.0, 14897.0); // A\n    if (glyph == 2) return vec2(1001022.0, 31281.0); // B\n    if (glyph == 3) return vec2(541199.0, 15888.0); // C\n    if (glyph == 4) return vec2(575038.0, 31281.0); // D\n    if (glyph == 5) return vec2(999967.0, 32272.0); // E\n    if (glyph == 6) return vec2(999952.0, 32272.0); // F\n    if (glyph == 7) return vec2(771631.0, 15888.0); // G\n    if (glyph == 8) return vec2(1033777.0, 17969.0); // H\n    if (glyph == 9) return vec2(135327.0, 31876.0); // I\n    if (glyph == 10) return vec2(84556.0, 7234.0); // J\n    if (glyph == 11) return vec2(807505.0, 18004.0); // K\n    if (glyph == 12) return vec2(541215.0, 16912.0); // L\n    if (glyph == 13) return vec2(706097.0, 18293.0); // M\n    if (glyph == 14) return vec2(708209.0, 18233.0); // N\n    if (glyph == 15) return vec2(575022.0, 14897.0); // O\n    if (glyph == 16) return vec2(999952.0, 31281.0); // P\n    if (glyph == 17) return vec2(579149.0, 14897.0); // Q\n    if (glyph == 18) return vec2(1004113.0, 31281.0); // R\n    if (glyph == 19) return vec2(459838.0, 15888.0); // S\n    if (glyph == 20) return vec2(135300.0, 31876.0); // T\n    if (glyph == 21) return vec2(575022.0, 17969.0); // U\n    if (glyph == 22) return vec2(574788.0, 17969.0); // V\n    if (glyph == 23) return vec2(710314.0, 17969.0); // W\n    if (glyph == 24) return vec2(141873.0, 17962.0); // X\n    if (glyph == 25) return vec2(135300.0, 17962.0); // Y\n    if (glyph == 26) return vec2(139807.0, 31778.0); // Z\n    if (glyph == 27) return vec2(714286.0, 14899.0); // 0\n    if (glyph == 28) return vec2(135310.0, 4484.0); // 1\n    if (glyph == 29) return vec2(69919.0, 14881.0); // 2\n    if (glyph == 30) return vec2(459838.0, 30753.0); // 3\n    if (glyph == 31) return vec2(621634.0, 2250.0); // 4\n    if (glyph == 32) return vec2(984126.0, 32272.0); // 5\n    if (glyph == 33) return vec2(1001006.0, 14864.0); // 6\n    if (glyph == 34) return vec2(139528.0, 31778.0); // 7\n    if (glyph == 35) return vec2(476718.0, 14897.0); // 8\n    if (glyph == 36) return vec2(492590.0, 14897.0); // 9\n    if (glyph == 37) return vec2(139792.0, 1058.0); // /\n    if (glyph == 38) return vec2(1015808.0, 0.0); // -\n    if (glyph == 39) return vec2(198.0, 0.0); // .\n    if (glyph == 40) return vec2(6336.0, 198.0); // :\n    if (glyph == 41) return vec2(1020032.0, 132.0); // +\n    if (glyph == 42) return vec2(69904.0, 16644.0); // >\n    if (glyph == 43) return vec2(270466.0, 2184.0); // (\n    if (glyph == 44) return vec2(67720.0, 8322.0); // )\n    return vec2(0.0);\n}\n\nvec4 atlasLabelPack(int label, int part) {\n    // Keep one return so large unified shaders preserve every table entry.\n    vec4 value = vec4(0.0);\n    if (label == 0) { // GLSL CHESS\n        if (part == 0) value = vec4(3224327.0, 1343680.0, 1235.0, 0.0);\n        if (part == 1) value = vec4(0.0, 0.0, 0.0, 0.0);\n        if (part == 2) value = vec4(0.0, 0.0, 0.0, 0.0);\n    }\n    if (label == 1) { // WHITE TO MOVE\n        if (part == 0) value = vec4(5280279.0, 4014085.0, 5829440.0, 5.0);\n        if (part == 1) value = vec4(0.0, 0.0, 0.0, 0.0);\n        if (part == 2) value = vec4(0.0, 0.0, 0.0, 0.0);\n    }\n    if (label == 2) { // BLACK TO MOVE\n        if (part == 0) value = vec4(791298.0, 4014091.0, 5829440.0, 5.0);\n        if (part == 1) value = vec4(0.0, 0.0, 0.0, 0.0);\n        if (part == 2) value = vec4(0.0, 0.0, 0.0, 0.0);\n    }\n    if (label == 3) { // WHITE IN CHECK\n        if (part == 0) value = vec4(5280279.0, 3706885.0, 1343680.0, 707.0);\n        if (part == 1) value = vec4(0.0, 0.0, 0.0, 0.0);\n        if (part == 2) value = vec4(0.0, 0.0, 0.0, 0.0);\n    }\n    if (label == 4) { // BLACK IN CHECK\n        if (part == 0) value = vec4(791298.0, 3706891.0, 1343680.0, 707.0);\n        if (part == 1) value = vec4(0.0, 0.0, 0.0, 0.0);\n        if (part == 2) value = vec4(0.0, 0.0, 0.0, 0.0);\n    }\n    if (label == 5) { // WHITE WINS\n        if (part == 0) value = vec4(5280279.0, 2453509.0, 1230.0, 0.0);\n        if (part == 1) value = vec4(0.0, 0.0, 0.0, 0.0);\n        if (part == 2) value = vec4(0.0, 0.0, 0.0, 0.0);\n    }\n    if (label == 6) { // BLACK WINS\n        if (part == 0) value = vec4(791298.0, 2453515.0, 1230.0, 0.0);\n        if (part == 1) value = vec4(0.0, 0.0, 0.0, 0.0);\n        if (part == 2) value = vec4(0.0, 0.0, 0.0, 0.0);\n    }\n    if (label == 7) { // DRAW\n        if (part == 0) value = vec4(6034564.0, 0.0, 0.0, 0.0);\n        if (part == 1) value = vec4(0.0, 0.0, 0.0, 0.0);\n        if (part == 2) value = vec4(0.0, 0.0, 0.0, 0.0);\n    }\n    if (label == 8) { // CHECKMATE\n        if (part == 0) value = vec4(807427.0, 5247819.0, 5.0, 0.0);\n        if (part == 1) value = vec4(0.0, 0.0, 0.0, 0.0);\n        if (part == 2) value = vec4(0.0, 0.0, 0.0, 0.0);\n    }\n    if (label == 9) { // STALEMATE\n        if (part == 0) value = vec4(3151123.0, 5247813.0, 5.0, 0.0);\n        if (part == 1) value = vec4(0.0, 0.0, 0.0, 0.0);\n        if (part == 2) value = vec4(0.0, 0.0, 0.0, 0.0);\n    }\n    if (label == 10) { // DEAD POSITION\n        if (part == 0) value = vec4(1052996.0, 5043200.0, 3970313.0, 14.0);\n        if (part == 1) value = vec4(0.0, 0.0, 0.0, 0.0);\n        if (part == 2) value = vec4(0.0, 0.0, 0.0, 0.0);\n    }\n    if (label == 11) { // FIVEFOLD REPETITION\n        if (part == 0) value = vec4(1401414.0, 1098694.0, 4215936.0, 5281029.0);\n        if (part == 1) value = vec4(58313.0, 0.0, 0.0, 0.0);\n        if (part == 2) value = vec4(0.0, 0.0, 0.0, 0.0);\n    }\n    if (label == 12) { // 75 MOVE RULE\n        if (part == 0) value = vec4(3409954.0, 21903.0, 1361234.0, 0.0);\n        if (part == 1) value = vec4(0.0, 0.0, 0.0, 0.0);\n        if (part == 2) value = vec4(0.0, 0.0, 0.0, 0.0);\n    }\n    if (label == 13) { // DRAW CLAIMED\n        if (part == 0) value = vec4(6034564.0, 311488.0, 1069897.0, 0.0);\n        if (part == 1) value = vec4(0.0, 0.0, 0.0, 0.0);\n        if (part == 2) value = vec4(0.0, 0.0, 0.0, 0.0);\n    }\n    if (label == 14) { // NEW GAME\n        if (part == 0) value = vec4(94542.0, 1364039.0, 0.0, 0.0);\n        if (part == 1) value = vec4(0.0, 0.0, 0.0, 0.0);\n        if (part == 2) value = vec4(0.0, 0.0, 0.0, 0.0);\n    }\n    if (label == 15) { // FLIP\n        if (part == 0) value = vec4(4231942.0, 0.0, 0.0, 0.0);\n        if (part == 1) value = vec4(0.0, 0.0, 0.0, 0.0);\n        if (part == 2) value = vec4(0.0, 0.0, 0.0, 0.0);\n    }\n    if (label == 16) { // CLAIM DRAW\n        if (part == 0) value = vec4(2364163.0, 4734989.0, 1473.0, 0.0);\n        if (part == 1) value = vec4(0.0, 0.0, 0.0, 0.0);\n        if (part == 2) value = vec4(0.0, 0.0, 0.0, 0.0);\n    }\n    if (label == 17) { // PROMOTE PAWN\n        if (part == 0) value = vec4(3470480.0, 21775.0, 3764304.0, 0.0);\n        if (part == 1) value = vec4(0.0, 0.0, 0.0, 0.0);\n        if (part == 2) value = vec4(0.0, 0.0, 0.0, 0.0);\n    }\n    if (label == 18) { // CANCEL\n        if (part == 0) value = vec4(843843.0, 773.0, 0.0, 0.0);\n        if (part == 1) value = vec4(0.0, 0.0, 0.0, 0.0);\n        if (part == 2) value = vec4(0.0, 0.0, 0.0, 0.0);\n    }\n    if (label == 19) { // QUEEN\n        if (part == 0) value = vec4(1332561.0, 14.0, 0.0, 0.0);\n        if (part == 1) value = vec4(0.0, 0.0, 0.0, 0.0);\n        if (part == 2) value = vec4(0.0, 0.0, 0.0, 0.0);\n    }\n    if (label == 20) { // ROOK\n        if (part == 0) value = vec4(2946002.0, 0.0, 0.0, 0.0);\n        if (part == 1) value = vec4(0.0, 0.0, 0.0, 0.0);\n        if (part == 2) value = vec4(0.0, 0.0, 0.0, 0.0);\n    }\n    if (label == 21) { // BISHOP\n        if (part == 0) value = vec4(2175554.0, 1039.0, 0.0, 0.0);\n        if (part == 1) value = vec4(0.0, 0.0, 0.0, 0.0);\n        if (part == 2) value = vec4(0.0, 0.0, 0.0, 0.0);\n    }\n    if (label == 22) { // KNIGHT\n        if (part == 0) value = vec4(1872779.0, 1288.0, 0.0, 0.0);\n        if (part == 1) value = vec4(0.0, 0.0, 0.0, 0.0);\n        if (part == 2) value = vec4(0.0, 0.0, 0.0, 0.0);\n    }\n    if (label == 23) { // FROM\n        if (part == 0) value = vec4(3470470.0, 0.0, 0.0, 0.0);\n        if (part == 1) value = vec4(0.0, 0.0, 0.0, 0.0);\n        if (part == 2) value = vec4(0.0, 0.0, 0.0, 0.0);\n    }\n    if (label == 24) { // TO\n        if (part == 0) value = vec4(980.0, 0.0, 0.0, 0.0);\n        if (part == 1) value = vec4(0.0, 0.0, 0.0, 0.0);\n        if (part == 2) value = vec4(0.0, 0.0, 0.0, 0.0);\n    }\n    if (label == 25) { // PROMOTION\n        if (part == 0) value = vec4(3470480.0, 3970319.0, 14.0, 0.0);\n        if (part == 1) value = vec4(0.0, 0.0, 0.0, 0.0);\n        if (part == 2) value = vec4(0.0, 0.0, 0.0, 0.0);\n    }\n    if (label == 26) { // CHOOSE FROM / TO IN CONTROLS - PRESS MOVE\n        if (part == 0) value = vec4(3994115.0, 1573203.0, 54226.0, 4014117.0);\n        if (part == 1) value = vec4(57920.0, 5301187.0, 5030866.0, 4196736.0);\n        if (part == 2) value = vec4(5058898.0, 5829440.0, 5.0, 0.0);\n    }\n    if (label == 27) { // PRESS NEW GAME IN CONTROLS TO RESTART\n        if (part == 0) value = vec4(5002384.0, 1368083.0, 290839.0, 2359629.0);\n        if (part == 1) value = vec4(3944462.0, 4007182.0, 5244108.0, 1384463.0);\n        if (part == 2) value = vec4(4723987.0, 20.0, 0.0, 0.0);\n    }\n    if (label == 28) { // TWO PLAYER CHESS\n        if (part == 0) value = vec4(62932.0, 6558480.0, 787589.0, 5058888.0);\n        if (part == 1) value = vec4(0.0, 0.0, 0.0, 0.0);\n        if (part == 2) value = vec4(0.0, 0.0, 0.0, 0.0);\n    }\n    if (label == 29) { // DRAW BY AGREEMENT\n        if (part == 0) value = vec4(6034564.0, 102528.0, 1384897.0, 3691333.0);\n        if (part == 1) value = vec4(20.0, 0.0, 0.0, 0.0);\n        if (part == 2) value = vec4(0.0, 0.0, 0.0, 0.0);\n    }\n    if (label == 30) { // RESIGNATION\n        if (part == 0) value = vec4(2437458.0, 5247879.0, 58313.0, 0.0);\n        if (part == 1) value = vec4(0.0, 0.0, 0.0, 0.0);\n        if (part == 2) value = vec4(0.0, 0.0, 0.0, 0.0);\n    }\n    if (label == 31) { // 50 MOVE RULE\n        if (part == 0) value = vec4(3409632.0, 21903.0, 1361234.0, 0.0);\n        if (part == 1) value = vec4(0.0, 0.0, 0.0, 0.0);\n        if (part == 2) value = vec4(0.0, 0.0, 0.0, 0.0);\n    }\n    if (label == 32) { // THREEFOLD REPETITION\n        if (part == 0) value = vec4(1384980.0, 3207557.0, 1384452.0, 2441552.0);\n        if (part == 1) value = vec4(3732052.0, 0.0, 0.0, 0.0);\n        if (part == 2) value = vec4(0.0, 0.0, 0.0, 0.0);\n    }\n    if (label == 33) { // DRAW AVAILABLE IN CONTROLS\n        if (part == 0) value = vec4(6034564.0, 352320.0, 529161.0, 2359628.0);\n        if (part == 1) value = vec4(3944462.0, 4007182.0, 1228.0, 0.0);\n        if (part == 2) value = vec4(0.0, 0.0, 0.0, 0.0);\n    }\n    if (label == 34) { // ILLEGAL MOVE - TRY A HIGHLIGHTED SQUARE\n        if (part == 0) value = vec4(1360649.0, 49223.0, 1401805.0, 5245312.0);\n        if (part == 1) value = vec4(263762.0, 1872384.0, 1872648.0, 1070344.0);\n        if (part == 2) value = vec4(5575872.0, 21633.0, 0.0, 0.0);\n    }\n    if (label == 35) { // DRAW CLAIM ARMED - SELECT MOVE AND PRESS MOVE\n        if (part == 0) value = vec4(6034564.0, 311488.0, 262985.0, 1069906.0);\n        if (part == 1) value = vec4(4983168.0, 807685.0, 3985428.0, 262486.0);\n        if (part == 2) value = vec4(4194574.0, 5058898.0, 5829440.0, 5.0);\n    }\n    if (label == 36) { // NO DRAW CLAIM AVAILABLE FOR THIS POSITION\n        if (part == 0) value = vec4(1049550.0, 94290.0, 2364163.0, 5771277.0);\n        if (part == 1) value = vec4(311873.0, 21250.0, 74694.0, 5018132.0);\n        if (part == 2) value = vec4(5043200.0, 3970313.0, 14.0, 0.0);\n    }\n    if (label == 37) { // FLOAT BUFFERS REQUIRED\n        if (part == 0) value = vec4(324358.0, 5513236.0, 4739462.0, 1384467.0);\n        if (part == 1) value = vec4(4756817.0, 261.0, 0.0, 0.0);\n        if (part == 2) value = vec4(0.0, 0.0, 0.0, 0.0);\n    }\n    if (label == 38) { // THIS HOST CANNOT STORE THE CHESS POSITION\n        if (part == 0) value = vec4(5018132.0, 5042688.0, 274452.0, 5305230.0);\n        if (part == 1) value = vec4(4015296.0, 5243218.0, 786760.0, 5058888.0);\n        if (part == 2) value = vec4(5043200.0, 3970313.0, 14.0, 0.0);\n    }\n    if (label == 39) { // ENABLE FLOAT TARGETS TO PLAY\n        if (part == 0) value = vec4(529285.0, 1573196.0, 5247948.0, 4723968.0);\n        if (part == 1) value = vec4(5062983.0, 62720.0, 6558480.0, 0.0);\n        if (part == 2) value = vec4(0.0, 0.0, 0.0, 0.0);\n    }\n    if (label == 40) { // PIECE MEMORY\n        if (part == 0) value = vec4(807504.0, 1363973.0, 6628301.0, 0.0);\n        if (part == 1) value = vec4(0.0, 0.0, 0.0, 0.0);\n        if (part == 2) value = vec4(0.0, 0.0, 0.0, 0.0);\n    }\n    if (label == 41) { // 16 RGBA / 64 VALUES\n        if (part == 0) value = vec4(4720732.0, 4231.0, 8261669.0, 3151232.0);\n        if (part == 1) value = vec4(78165.0, 0.0, 0.0, 0.0);\n        if (part == 2) value = vec4(0.0, 0.0, 0.0, 0.0);\n    }\n    if (label == 42) { // LEGAL 64 X 64\n        if (part == 0) value = vec4(291148.0, 8261644.0, 8652288.0, 31.0);\n        if (part == 1) value = vec4(0.0, 0.0, 0.0, 0.0);\n        if (part == 2) value = vec4(0.0, 0.0, 0.0, 0.0);\n    }\n    if (label == 43) { // FROM > TO\n        if (part == 0) value = vec4(3470470.0, 5245568.0, 15.0, 0.0);\n        if (part == 1) value = vec4(0.0, 0.0, 0.0, 0.0);\n        if (part == 2) value = vec4(0.0, 0.0, 0.0, 0.0);\n    }\n    if (label == 44) { // LEGAL CACHE / LOCKED\n        if (part == 0) value = vec4(291148.0, 274444.0, 20995.0, 3981349.0);\n        if (part == 1) value = vec4(1069763.0, 0.0, 0.0, 0.0);\n        if (part == 2) value = vec4(0.0, 0.0, 0.0, 0.0);\n    }\n    if (label == 45) { // REVERSIBLE HISTORY\n        if (part == 0) value = vec4(1401170.0, 562386.0, 2097484.0, 4015305.0);\n        if (part == 1) value = vec4(1618.0, 0.0, 0.0, 0.0);\n        if (part == 2) value = vec4(0.0, 0.0, 0.0, 0.0);\n    }\n    if (label == 46) { // 151 SLOTS\n        if (part == 0) value = vec4(116764.0, 5305107.0, 19.0, 0.0);\n        if (part == 1) value = vec4(0.0, 0.0, 0.0, 0.0);\n        if (part == 2) value = vec4(0.0, 0.0, 0.0, 0.0);\n    }\n    if (label == 47) { // CURSOR\n        if (part == 0) value = vec4(5055811.0, 1167.0, 0.0, 0.0);\n        if (part == 1) value = vec4(0.0, 0.0, 0.0, 0.0);\n        if (part == 2) value = vec4(0.0, 0.0, 0.0, 0.0);\n    }\n    if (label == 48) { // COUNT\n        if (part == 0) value = vec4(3756995.0, 20.0, 0.0, 0.0);\n        if (part == 1) value = vec4(0.0, 0.0, 0.0, 0.0);\n        if (part == 2) value = vec4(0.0, 0.0, 0.0, 0.0);\n    }\n    if (label == 49) { // REGISTERS\n        if (part == 0) value = vec4(2388306.0, 4740371.0, 19.0, 0.0);\n        if (part == 1) value = vec4(0.0, 0.0, 0.0, 0.0);\n        if (part == 2) value = vec4(0.0, 0.0, 0.0, 0.0);\n    }\n    if (label == 50) { // SIDE\n        if (part == 0) value = vec4(1327699.0, 0.0, 0.0, 0.0);\n        if (part == 1) value = vec4(0.0, 0.0, 0.0, 0.0);\n        if (part == 2) value = vec4(0.0, 0.0, 0.0, 0.0);\n    }\n    if (label == 51) { // PLY\n        if (part == 0) value = vec4(103184.0, 0.0, 0.0, 0.0);\n        if (part == 1) value = vec4(0.0, 0.0, 0.0, 0.0);\n        if (part == 2) value = vec4(0.0, 0.0, 0.0, 0.0);\n    }\n    if (label == 52) { // HALF\n        if (part == 0) value = vec4(1622088.0, 0.0, 0.0, 0.0);\n        if (part == 1) value = vec4(0.0, 0.0, 0.0, 0.0);\n        if (part == 2) value = vec4(0.0, 0.0, 0.0, 0.0);\n    }\n    if (label == 53) { // REPEAT\n        if (part == 0) value = vec4(1376594.0, 1281.0, 0.0, 0.0);\n        if (part == 1) value = vec4(0.0, 0.0, 0.0, 0.0);\n        if (part == 2) value = vec4(0.0, 0.0, 0.0, 0.0);\n    }\n    if (label == 54) { // EP\n        if (part == 0) value = vec4(1029.0, 0.0, 0.0, 0.0);\n        if (part == 1) value = vec4(0.0, 0.0, 0.0, 0.0);\n        if (part == 2) value = vec4(0.0, 0.0, 0.0, 0.0);\n    }\n    if (label == 55) { // CASTLING\n        if (part == 0) value = vec4(5320771.0, 1892940.0, 0.0, 0.0);\n        if (part == 1) value = vec4(0.0, 0.0, 0.0, 0.0);\n        if (part == 2) value = vec4(0.0, 0.0, 0.0, 0.0);\n    }\n    if (label == 56) { // CLAIM\n        if (part == 0) value = vec4(2364163.0, 13.0, 0.0, 0.0);\n        if (part == 1) value = vec4(0.0, 0.0, 0.0, 0.0);\n        if (part == 2) value = vec4(0.0, 0.0, 0.0, 0.0);\n    }\n    if (label == 57) { // WHITE\n        if (part == 0) value = vec4(5280279.0, 5.0, 0.0, 0.0);\n        if (part == 1) value = vec4(0.0, 0.0, 0.0, 0.0);\n        if (part == 2) value = vec4(0.0, 0.0, 0.0, 0.0);\n    }\n    if (label == 58) { // BLACK\n        if (part == 0) value = vec4(791298.0, 11.0, 0.0, 0.0);\n        if (part == 1) value = vec4(0.0, 0.0, 0.0, 0.0);\n        if (part == 2) value = vec4(0.0, 0.0, 0.0, 0.0);\n    }\n    if (label == 59) { // AUTO / 1S\n        if (part == 0) value = vec4(4015425.0, 7342400.0, 19.0, 0.0);\n        if (part == 1) value = vec4(0.0, 0.0, 0.0, 0.0);\n        if (part == 2) value = vec4(0.0, 0.0, 0.0, 0.0);\n    }\n    if (label == 60) { // AUTO / COMPLETE\n        if (part == 0) value = vec4(4015425.0, 788800.0, 3212111.0, 21765.0);\n        if (part == 1) value = vec4(0.0, 0.0, 0.0, 0.0);\n        if (part == 2) value = vec4(0.0, 0.0, 0.0, 0.0);\n    }\n    if (label == 61) { // AUTO PLAY / RANDOM LEGAL / 1 SECOND\n        if (part == 0) value = vec4(4015425.0, 312320.0, 151577.0, 1106002.0);\n        if (part == 1) value = vec4(3146575.0, 3150277.0, 7342400.0, 808128.0);\n        if (part == 2) value = vec4(17295.0, 0.0, 0.0, 0.0);\n    }\n    if (label == 62) { // DESTINATIONS\n        if (part == 0) value = vec4(5321028.0, 5247881.0, 5039049.0, 0.0);\n        if (part == 1) value = vec4(0.0, 0.0, 0.0, 0.0);\n        if (part == 2) value = vec4(0.0, 0.0, 0.0, 0.0);\n    }\n    if (label == 63) { // RGBA STATE\n        if (part == 0) value = vec4(270802.0, 345280.0, 340.0, 0.0);\n        if (part == 1) value = vec4(0.0, 0.0, 0.0, 0.0);\n        if (part == 2) value = vec4(0.0, 0.0, 0.0, 0.0);\n    }\n    if (label == 64) { // K Q K Q\n        if (part == 0) value = vec4(69643.0, 69643.0, 0.0, 0.0);\n        if (part == 1) value = vec4(0.0, 0.0, 0.0, 0.0);\n        if (part == 2) value = vec4(0.0, 0.0, 0.0, 0.0);\n    }\n    if (label == 65) { // WK\n        if (part == 0) value = vec4(727.0, 0.0, 0.0, 0.0);\n        if (part == 1) value = vec4(0.0, 0.0, 0.0, 0.0);\n        if (part == 2) value = vec4(0.0, 0.0, 0.0, 0.0);\n    }\n    if (label == 66) { // WQ\n        if (part == 0) value = vec4(1111.0, 0.0, 0.0, 0.0);\n        if (part == 1) value = vec4(0.0, 0.0, 0.0, 0.0);\n        if (part == 2) value = vec4(0.0, 0.0, 0.0, 0.0);\n    }\n    if (label == 67) { // BK\n        if (part == 0) value = vec4(706.0, 0.0, 0.0, 0.0);\n        if (part == 1) value = vec4(0.0, 0.0, 0.0, 0.0);\n        if (part == 2) value = vec4(0.0, 0.0, 0.0, 0.0);\n    }\n    if (label == 68) { // BQ\n        if (part == 0) value = vec4(1090.0, 0.0, 0.0, 0.0);\n        if (part == 1) value = vec4(0.0, 0.0, 0.0, 0.0);\n        if (part == 2) value = vec4(0.0, 0.0, 0.0, 0.0);\n    }\n    if (label == 69) { // REPEAT\n        if (part == 0) value = vec4(1376594.0, 1281.0, 0.0, 0.0);\n        if (part == 1) value = vec4(0.0, 0.0, 0.0, 0.0);\n        if (part == 2) value = vec4(0.0, 0.0, 0.0, 0.0);\n    }\n    if (label == 70) { // 50\n        if (part == 0) value = vec4(1760.0, 0.0, 0.0, 0.0);\n        if (part == 1) value = vec4(0.0, 0.0, 0.0, 0.0);\n        if (part == 2) value = vec4(0.0, 0.0, 0.0, 0.0);\n    }\n    if (label == 71) { // RESULT\n        if (part == 0) value = vec4(5583186.0, 1292.0, 0.0, 0.0);\n        if (part == 1) value = vec4(0.0, 0.0, 0.0, 0.0);\n        if (part == 2) value = vec4(0.0, 0.0, 0.0, 0.0);\n    }\n    if (label == 72) { // STATE 16 - 24\n        if (part == 0) value = vec4(5248275.0, 8765445.0, 7604608.0, 31.0);\n        if (part == 1) value = vec4(0.0, 0.0, 0.0, 0.0);\n        if (part == 2) value = vec4(0.0, 0.0, 0.0, 0.0);\n    }\n    if (label == 73) { // ACTIVE\n        if (part == 0) value = vec4(2441409.0, 342.0, 0.0, 0.0);\n        if (part == 1) value = vec4(0.0, 0.0, 0.0, 0.0);\n        if (part == 2) value = vec4(0.0, 0.0, 0.0, 0.0);\n    }\n    if (label == 74) { // SIGNED PIECES\n        if (part == 0) value = vec4(3699283.0, 4194565.0, 1323337.0, 19.0);\n        if (part == 1) value = vec4(0.0, 0.0, 0.0, 0.0);\n        if (part == 2) value = vec4(0.0, 0.0, 0.0, 0.0);\n    }\n    if (label == 75) { // CURRENT / VALID\n        if (part == 0) value = vec4(4793667.0, 82821.0, 352293.0, 16972.0);\n        if (part == 1) value = vec4(0.0, 0.0, 0.0, 0.0);\n        if (part == 2) value = vec4(0.0, 0.0, 0.0, 0.0);\n    }\n    if (label == 76) { // R\n        if (part == 0) value = vec4(18.0, 0.0, 0.0, 0.0);\n        if (part == 1) value = vec4(0.0, 0.0, 0.0, 0.0);\n        if (part == 2) value = vec4(0.0, 0.0, 0.0, 0.0);\n    }\n    if (label == 77) { // G\n        if (part == 0) value = vec4(7.0, 0.0, 0.0, 0.0);\n        if (part == 1) value = vec4(0.0, 0.0, 0.0, 0.0);\n        if (part == 2) value = vec4(0.0, 0.0, 0.0, 0.0);\n    }\n    if (label == 78) { // B\n        if (part == 0) value = vec4(2.0, 0.0, 0.0, 0.0);\n        if (part == 1) value = vec4(0.0, 0.0, 0.0, 0.0);\n        if (part == 2) value = vec4(0.0, 0.0, 0.0, 0.0);\n    }\n    if (label == 79) { // A\n        if (part == 0) value = vec4(1.0, 0.0, 0.0, 0.0);\n        if (part == 1) value = vec4(0.0, 0.0, 0.0, 0.0);\n        if (part == 2) value = vec4(0.0, 0.0, 0.0, 0.0);\n    }\n    if (label == 80) { // LIVE PIXEL STATE\n        if (part == 0) value = vec4(1401420.0, 6329344.0, 4981509.0, 1392724.0);\n        if (part == 1) value = vec4(0.0, 0.0, 0.0, 0.0);\n        if (part == 2) value = vec4(0.0, 0.0, 0.0, 0.0);\n    }\n    if (label == 81) { // TURN\n        if (part == 0) value = vec4(3745108.0, 0.0, 0.0, 0.0);\n        if (part == 1) value = vec4(0.0, 0.0, 0.0, 0.0);\n        if (part == 2) value = vec4(0.0, 0.0, 0.0, 0.0);\n    }\n    if (label == 82) { // HISTORY RESET\n        if (part == 0) value = vec4(5321288.0, 103567.0, 1388882.0, 20.0);\n        if (part == 1) value = vec4(0.0, 0.0, 0.0, 0.0);\n        if (part == 2) value = vec4(0.0, 0.0, 0.0, 0.0);\n    }\n    if (label == 83) { // TO X / FROM Y\n        if (part == 0) value = vec4(6292436.0, 1575232.0, 54226.0, 25.0);\n        if (part == 1) value = vec4(0.0, 0.0, 0.0, 0.0);\n        if (part == 2) value = vec4(0.0, 0.0, 0.0, 0.0);\n    }\n    if (label == 84) { // LOW 8 BITS / RGBA\n        if (part == 0) value = vec4(95180.0, 2367523.0, 9700564.0, 554112.0);\n        if (part == 1) value = vec4(1.0, 0.0, 0.0, 0.0);\n        if (part == 2) value = vec4(0.0, 0.0, 0.0, 0.0);\n    }\n    if (label == 85) { // READ / WRITE\n        if (part == 0) value = vec4(1053010.0, 6031680.0, 1393234.0, 0.0);\n        if (part == 1) value = vec4(0.0, 0.0, 0.0, 0.0);\n        if (part == 2) value = vec4(0.0, 0.0, 0.0, 0.0);\n    }\n    if (label == 86) { // LEGAL MOVES\n        if (part == 0) value = vec4(291148.0, 3985420.0, 78166.0, 0.0);\n        if (part == 1) value = vec4(0.0, 0.0, 0.0, 0.0);\n        if (part == 2) value = vec4(0.0, 0.0, 0.0, 0.0);\n    }\n    if (label == 87) { // STORED / MATCH\n        if (part == 0) value = vec4(4781331.0, 9699589.0, 5247808.0, 515.0);\n        if (part == 1) value = vec4(0.0, 0.0, 0.0, 0.0);\n        if (part == 2) value = vec4(0.0, 0.0, 0.0, 0.0);\n    }\n    if (label == 88) { // ARMED\n        if (part == 0) value = vec4(1365121.0, 4.0, 0.0, 0.0);\n        if (part == 1) value = vec4(0.0, 0.0, 0.0, 0.0);\n        if (part == 2) value = vec4(0.0, 0.0, 0.0, 0.0);\n    }\n    if (label == 89) { // RANDOM\n        if (part == 0) value = vec4(1106002.0, 847.0, 0.0, 0.0);\n        if (part == 1) value = vec4(0.0, 0.0, 0.0, 0.0);\n        if (part == 2) value = vec4(0.0, 0.0, 0.0, 0.0);\n    }\n    return value;\n}\nfloat atlasLabelLength(int label) {\n    // One return avoids driver miscompilation of a long early-return chain.\n    float value = 0.0;\n    if (label == 0) value = 10.0;\n    if (label == 1) value = 13.0;\n    if (label == 2) value = 13.0;\n    if (label == 3) value = 14.0;\n    if (label == 4) value = 14.0;\n    if (label == 5) value = 10.0;\n    if (label == 6) value = 10.0;\n    if (label == 7) value = 4.0;\n    if (label == 8) value = 9.0;\n    if (label == 9) value = 9.0;\n    if (label == 10) value = 13.0;\n    if (label == 11) value = 19.0;\n    if (label == 12) value = 12.0;\n    if (label == 13) value = 12.0;\n    if (label == 14) value = 8.0;\n    if (label == 15) value = 4.0;\n    if (label == 16) value = 10.0;\n    if (label == 17) value = 12.0;\n    if (label == 18) value = 6.0;\n    if (label == 19) value = 5.0;\n    if (label == 20) value = 4.0;\n    if (label == 21) value = 6.0;\n    if (label == 22) value = 6.0;\n    if (label == 23) value = 4.0;\n    if (label == 24) value = 2.0;\n    if (label == 25) value = 9.0;\n    if (label == 26) value = 41.0;\n    if (label == 27) value = 37.0;\n    if (label == 28) value = 16.0;\n    if (label == 29) value = 17.0;\n    if (label == 30) value = 11.0;\n    if (label == 31) value = 12.0;\n    if (label == 32) value = 20.0;\n    if (label == 33) value = 26.0;\n    if (label == 34) value = 39.0;\n    if (label == 35) value = 45.0;\n    if (label == 36) value = 41.0;\n    if (label == 37) value = 22.0;\n    if (label == 38) value = 41.0;\n    if (label == 39) value = 28.0;\n    if (label == 40) value = 12.0;\n    if (label == 41) value = 19.0;\n    if (label == 42) value = 13.0;\n    if (label == 43) value = 9.0;\n    if (label == 44) value = 20.0;\n    if (label == 45) value = 18.0;\n    if (label == 46) value = 9.0;\n    if (label == 47) value = 6.0;\n    if (label == 48) value = 5.0;\n    if (label == 49) value = 9.0;\n    if (label == 50) value = 4.0;\n    if (label == 51) value = 3.0;\n    if (label == 52) value = 4.0;\n    if (label == 53) value = 6.0;\n    if (label == 54) value = 2.0;\n    if (label == 55) value = 8.0;\n    if (label == 56) value = 5.0;\n    if (label == 57) value = 5.0;\n    if (label == 58) value = 5.0;\n    if (label == 59) value = 9.0;\n    if (label == 60) value = 15.0;\n    if (label == 61) value = 35.0;\n    if (label == 62) value = 12.0;\n    if (label == 63) value = 10.0;\n    if (label == 64) value = 7.0;\n    if (label == 65) value = 2.0;\n    if (label == 66) value = 2.0;\n    if (label == 67) value = 2.0;\n    if (label == 68) value = 2.0;\n    if (label == 69) value = 6.0;\n    if (label == 70) value = 2.0;\n    if (label == 71) value = 6.0;\n    if (label == 72) value = 13.0;\n    if (label == 73) value = 6.0;\n    if (label == 74) value = 13.0;\n    if (label == 75) value = 15.0;\n    if (label == 76) value = 1.0;\n    if (label == 77) value = 1.0;\n    if (label == 78) value = 1.0;\n    if (label == 79) value = 1.0;\n    if (label == 80) value = 16.0;\n    if (label == 81) value = 4.0;\n    if (label == 82) value = 13.0;\n    if (label == 83) value = 13.0;\n    if (label == 84) value = 17.0;\n    if (label == 85) value = 12.0;\n    if (label == 86) value = 11.0;\n    if (label == 87) value = 14.0;\n    if (label == 88) value = 5.0;\n    if (label == 89) value = 6.0;\n    return value;\n}\n\n// PASS 4: build the font and text data once in a persistent RGBA8 texture.\n// Its 64x64 corner contains 45 padded glyphs, 90 strings, and string lengths.\n// All payloads are bytes; no large integer is ever stored in a FLOAT target.\nvec2 atlasTablePixel(int index) {\n    float n = float(index);\n    return vec2(mod(n, 64.0), floor(n / 64.0)) + 0.5;\n}\n\nvec4 buildTextAtlas(ivec2 pixel) {\n    if (pixel.x >= 64 || pixel.y >= 64) return vec4(0.0);\n    if (pixel.x == 63 && pixel.y == 63) return vec4(1.0, 0.0, 1.0, 1.0);\n    vec4 sentinel = IMG_PIXEL(textAtlas, vec2(63.5));\n    if (sentinel.r > 0.99 && sentinel.g < 0.01 && sentinel.b > 0.99 && sentinel.a > 0.99)\n        return IMG_PIXEL(textAtlas, vec2(float(pixel.x), float(pixel.y)) + 0.5);\n\n    if (pixel.y < 40) {\n        int column = pixel.x / 7, row = pixel.y / 8;\n        int glyph = column + row * 9;\n        int x = pixel.x - column * 7 - 1;\n        int y = pixel.y - row * 8;\n        if (column >= 9 || glyph >= 45 || x < 0 || x >= 5 || y >= 7) return vec4(0.0);\n        vec2 data = atlasFontBits(glyph);\n        float word = y < 4 ? data.x : data.y;\n        float shift = float((y < 4 ? y : y - 4) * 5 + 4 - x);\n        float bit = mod(floor(word / exp2(shift)), 2.0);\n        return vec4(bit);\n    }\n\n    int index = (pixel.y - 40) * 64 + pixel.x;\n    if (index < 1080) {\n        int label = index / 12;\n        int chunk = index - label * 12;\n        int part = chunk / 4;\n        int component = chunk - part * 4;\n        vec4 words = atlasLabelPack(label, part);\n        float word = words.x;\n        if (component == 1) word = words.y;\n        if (component == 2) word = words.z;\n        if (component == 3) word = words.w;\n        return vec4(mod(word, 64.0), mod(floor(word / 64.0), 64.0),\n                    mod(floor(word / 4096.0), 64.0), mod(floor(word / 262144.0), 64.0)) / 255.0;\n    }\n    if (index < 1170) return vec4(atlasLabelLength(index - 1080) / 255.0, 0.0, 0.0, 1.0);\n    return vec4(0.0);\n}\n\nfloat labelLength(int label) {\n    return floor(IMG_PIXEL(textAtlas, atlasTablePixel(3640 + label)).r * 255.0 + 0.5);\n}\n\nfloat glyphCoverage(vec2 p, int glyph, float pixel) {\n    vec2 q = p / pixel;\n    if (glyph <= 0 || glyph >= 45 || q.x < -0.5 || q.x > 5.5 || q.y < -0.5 || q.y > 7.5)\n        return 0.0;\n    int row = glyph / 9;\n    int column = glyph - row * 9;\n    vec2 origin = vec2(float(column * 7 + 1), float(row * 8));\n    // Coordinates stay inside this glyph's two horizontal/one vertical pads.\n    // Clamp the final vertical pad at its center, before the text-data rows.\n    vec2 samplePoint = origin + clamp(q, vec2(-0.5), vec2(5.5, 7.5));\n    samplePoint.y = min(samplePoint.y, float(row * 8) + 7.5);\n    float ink = IMG_PIXEL(textAtlas, samplePoint).r;\n    // Row zero has a virtual bottom pad outside the texture's clamped edge.\n    if (row == 0) ink *= clamp(q.y + 0.5, 0.0, 1.0);\n    float aa = clamp(0.65 / (chessSize() * pixel), 0.075, 0.49);\n    return smoothstep(0.5 - aa, 0.5 + aa, ink);\n}\n\nfloat labelCoverageCount(vec2 p, int label, float pixel, float count) {\n    if (p.x < 0.0 || p.x >= count * 6.0 * pixel || p.y < -pixel || p.y > 8.0 * pixel)\n        return 0.0;\n    int character = int(floor(p.x / (6.0 * pixel)));\n    int chunk = character / 4;\n    int component = character - chunk * 4;\n    vec4 data = floor(IMG_PIXEL(textAtlas, atlasTablePixel(2560 + label * 12 + chunk)) * 255.0 + 0.5);\n    int glyph = packedPiece(data, component);\n    vec2 local = vec2(p.x - float(character) * 6.0 * pixel, p.y);\n    return glyphCoverage(local, glyph, pixel);\n}\n\nfloat labelCoverage(vec2 p, int label, float pixel) {\n    if (p.y < -pixel || p.y > 8.0 * pixel) return 0.0;\n    return labelCoverageCount(p, label, pixel, labelLength(label));\n}\n\nvec3 labelLeft(vec3 color, vec2 uv, vec2 anchor, int label, float pixel, vec3 ink) {\n    return mix(color, ink, labelCoverage(uv - anchor, label, pixel));\n}\n\nvec3 labelRight(vec3 color, vec2 uv, vec2 anchor, int label, float pixel, vec3 ink) {\n    if (uv.y < anchor.y - pixel || uv.y > anchor.y + 8.0 * pixel) return color;\n    float count = labelLength(label);\n    anchor.x -= (count * 6.0 - 1.0) * pixel;\n    return mix(color, ink, labelCoverageCount(uv - anchor, label, pixel, count));\n}\n\nvec3 labelCenter(vec3 color, vec2 uv, vec2 anchor, int label, float pixel, vec3 ink) {\n    if (uv.y < anchor.y - pixel || uv.y > anchor.y + 8.0 * pixel) return color;\n    float count = labelLength(label);\n    anchor.x -= (count * 6.0 - 1.0) * pixel * 0.5;\n    return mix(color, ink, labelCoverageCount(uv - anchor, label, pixel, count));\n}\n\nfloat boardCoordinate(vec2 p, int glyph) {\n    float pixel = 0.00215;\n    return glyphCoverage(p + vec2(2.5, 3.5) * pixel, glyph, pixel);\n}\n\nvec3 squareReadout(vec3 color, vec2 uv, vec2 anchor, int square, vec3 ink) {\n    float pixel = 0.0035;\n    float alpha;\n    if (square < 0 || square > 63) {\n        alpha = glyphCoverage(uv - anchor, 38, pixel);\n    } else {\n        int rank = square / 8;\n        int file = square - rank * 8;\n        alpha = glyphCoverage(uv - anchor, file + 1, pixel);\n        alpha += glyphCoverage(uv - anchor - vec2(6.0 * pixel, 0.0), rank + 28, pixel);\n    }\n    return mix(color, ink, min(alpha, 1.0));\n}\n\n// Live data instruments. Cells are decoded directly from persistent pixels.\n// Animation changes their illumination only; it never invents data values.\nvec3 dataCyan() { return vec3(0.19, 0.82, 0.89); }\nvec3 dataAmber() { return vec3(1.00, 0.65, 0.24); }\nvec3 dataInk() { return vec3(0.53, 0.70, 0.69); }\nvec3 channelInk(int channel) {\n    if (channel == 0) return vec3(0.94, 0.38, 0.31);\n    if (channel == 1) return vec3(0.35, 0.85, 0.53);\n    if (channel == 2) return vec3(0.30, 0.66, 1.00);\n    return vec3(1.00, 0.73, 0.35);\n}\n\nvec4 dataPanelRect(int panel) {\n    if (chessWide()) {\n        if (panel == 0) return vec4(-0.390, 0.515, 0.335, 0.485);\n        if (panel == 1) return vec4( 1.055, 0.530, 0.335, 0.470);\n        if (panel == 2) return vec4( 1.055, 0.015, 0.335, 0.465);\n        return vec4(-0.390, 0.015, 0.335, 0.450);\n    }\n    if (panel == 0) return vec4(0.015, -0.445, 0.290, 0.282);\n    if (panel == 1) return vec4(0.355, -0.445, 0.290, 0.282);\n    return vec4(0.695, -0.445, 0.290, 0.282);\n}\n\nbool inDataRect(vec2 uv, vec4 rect) {\n    vec2 p = uv - rect.xy;\n    return p.x >= 0.0 && p.y >= 0.0 && p.x < rect.z && p.y < rect.w;\n}\n\nvec3 instrumentPanel(vec3 color, vec2 uv, vec4 rect, vec3 accent, float energy) {\n    float aa = 0.7 / chessSize();\n    vec2 p = uv - rect.xy - rect.zw * 0.5;\n    float d = sdRoundBox(p, rect.zw * 0.5, 0.008);\n    color += accent * (0.018 + energy * 0.022) * exp(-max(d, 0.0) * 130.0);\n    vec3 surface = vec3(0.022, 0.054, 0.058) + 0.011 * (p.y / rect.w + 0.5);\n    color = mix(color, surface, coverage(d, aa));\n    color = mix(color, accent * (0.30 + energy * 0.35), coverage(abs(d) - 0.0007, aa));\n    float top = sdBox(uv - vec2(rect.x + rect.z * 0.5, rect.y + rect.w - 0.002),\n                      vec2(rect.z * 0.34, 0.0008));\n    color += accent * coverage(top, aa) * (0.35 + 0.40 * energy);\n    return color;\n}\n\nvec3 numberRight(vec3 color, vec2 uv, vec2 anchor, int value, int digits, float pixel, vec3 ink) {\n    if (uv.y < anchor.y - pixel || uv.y > anchor.y + 8.0 * pixel) return color;\n    float offset = (anchor.x - uv.x) / pixel;\n    int place = int(floor(max((offset + 0.5) / 6.0, 0.0)));\n    if (offset < -0.5 || place >= digits) return color;\n    float magnitude = abs(float(value));\n    float power = pow(10.0, float(place));\n    if (place > 0 && magnitude < power) return color;\n    int digit = int(mod(floor(magnitude / power), 10.0));\n    vec2 at = anchor - vec2((float(place) * 6.0 + 5.0) * pixel, 0.0);\n    return mix(color, ink, glyphCoverage(uv - at, digit + 27, pixel));\n}\n\nvec3 bitLamp(vec3 color, vec2 uv, vec2 center, float radius, bool on, vec3 ink, float pulse) {\n    float d = length(uv - center) - radius;\n    float aa = 0.65 / chessSize();\n    color += ink * (on ? 0.12 + pulse * 0.16 : 0.006) * exp(-max(d, 0.0) * 330.0);\n    return mix(color, on ? ink * (0.8 + 0.2 * pulse) : vec3(0.08, 0.14, 0.145), coverage(d, aa));\n}\n\nfloat numericBit(float value, float bit) {\n    return mod(floor(mod(value, 256.0) / exp2(bit)), 2.0);\n}\n\nvec3 registerBits(vec3 color, vec2 uv, vec4 rect, float energy) {\n    if (!inDataRect(uv, rect)) return color;\n    vec2 q = (uv - rect.xy) / rect.zw * vec2(32.0, 9.0);\n    int column = int(floor(q.x));\n    int channel = column / 8;\n    int bit = 7 - (column - channel * 8);\n    int row = int(floor(q.y));\n    float value = float(packedPiece(gameData(16 + row), channel));\n    float active = numericBit(value, float(bit));\n    float d = sdBox(fract(q) - 0.5, vec2(0.35, 0.30));\n    float aa = 0.6 / (chessSize() * min(rect.z / 32.0, rect.w / 9.0));\n    vec3 ink = channelInk(channel);\n    color = mix(color, ink * (0.075 + active * (0.75 + 0.17 * energy)), coverage(d, aa));\n    return color;\n}\n\nvec3 pieceMemory(vec3 color, vec2 uv, vec4 rect, vec4 pulse) {\n    bool wide = chessWide();\n    float size = wide ? 0.285 : 0.168;\n    vec2 origin = rect.xy + (wide ? vec2(0.025, 0.097) : vec2(0.012, 0.065));\n    if (inDataRect(uv, vec4(origin, size, size))) {\n        vec2 q = (uv - origin) * 8.0 / size;\n        int square = int(floor(q.x)) + int(floor(q.y)) * 8;\n        int piece = uiPiece(square);\n        int type = piece < 0 ? -piece : piece;\n        vec2 p = fract(q) - 0.5;\n        float aa = 0.7 * 8.0 / (chessSize() * size);\n        vec3 ink = piece > 0 ? dataCyan() : dataAmber();\n        float d = sdRoundBox(p, vec2(0.44), 0.045);\n        vec3 cell = vec3(0.030, 0.075, 0.080);\n        if (piece != 0) cell = ink * (0.16 + float(type) * 0.033);\n        color = mix(color, cell, coverage(d, aa));\n        int component = square - (square / 4) * 4;\n        float channel = coverage(sdBox(p - vec2(0.0, -0.35), vec2(0.31, 0.027)), aa);\n        color = mix(color, channelInk(component), channel * (piece == 0 ? 0.20 : 0.65));\n        vec2 center = origin + (floor(q) + 0.5) * size / 8.0;\n        float fontPixel = size / 8.0 * 0.071;\n        float glyph = glyphCoverage(uv - center + vec2(2.5, 2.6) * fontPixel, type + 27, fontPixel);\n        color = mix(color, piece == 0 ? vec3(0.10, 0.21, 0.22) : ink, glyph);\n        if (square == uiData(5) || square == uiData(6)) {\n            float ring = coverage(abs(d) - 0.026, aa);\n            color = mix(color, mix(dataCyan(), dataAmber(), pulse.y), ring * (0.4 + 0.6 * pulse.x));\n        }\n    }\n    float titlePixel = wide ? 0.0028 : 0.00205;\n    color = labelLeft(color, uv, rect.xy + vec2(0.014, rect.w - 0.030), 40, titlePixel, dataCyan());\n    if (wide) {\n        color = labelLeft(color, uv, rect.xy + vec2(0.025, 0.411), 41, 0.00195, dataInk());\n        color = labelLeft(color, uv, rect.xy + vec2(0.025, 0.046), 74, 0.0020, dataInk());\n        color = bitLamp(color, uv, rect.xy + vec2(0.255, 0.052), 0.005, true, dataCyan(), pulse.x);\n        color = bitLamp(color, uv, rect.xy + vec2(0.282, 0.052), 0.005, true, dataAmber(), pulse.y);\n    } else {\n        vec4 rules = gameData(16), hist = gameData(19);\n        float x = rect.x + 0.198;\n        color = labelLeft(color, uv, vec2(x, rect.y + 0.216), 51, 0.00150, dataInk());\n        color = numberRight(color, uv, vec2(rect.x + 0.278, rect.y + 0.194), uiData(11), 6, 0.00215, dataCyan());\n        color = labelLeft(color, uv, vec2(x, rect.y + 0.165), 52, 0.00150, dataInk());\n        color = numberRight(color, uv, vec2(rect.x + 0.278, rect.y + 0.143), int(rules.w), 3, 0.00215, dataAmber());\n        color = labelLeft(color, uv, vec2(x, rect.y + 0.114), 53, 0.00150, dataInk());\n        color = numberRight(color, uv, vec2(rect.x + 0.278, rect.y + 0.092), int(hist.z), 2, 0.00215, dataCyan());\n        color = registerBits(color, uv, vec4(rect.xy + vec2(0.012, 0.014), 0.266, 0.029), pulse.x);\n        for (int bit = 0; bit < 4; ++bit)\n            color = bitLamp(color, uv, vec2(x + 0.011 + float(bit) * 0.020, rect.y + 0.062), 0.0032,\n                numericBit(rules.y, float(bit)) > 0.5, bit < 2 ? dataCyan() : dataAmber(), pulse.x);\n    }\n    return color;\n}\n\nvec3 legalMemory(vec3 color, vec2 uv, vec4 rect, vec4 pulse) {\n    bool wide = chessWide();\n    float size = wide ? 0.283 : 0.220;\n    vec2 origin = rect.xy + (wide ? vec2(0.026, 0.102) : vec2(0.035, 0.027));\n    bool locked = uiData(4) != 0;\n    vec3 ink = uiData(0) > 0 ? dataCyan() : dataAmber();\n    if (inDataRect(uv, vec4(origin, size, size))) {\n        vec2 q = (uv - origin) / size * 64.0;\n        int to = int(floor(q.x)), from = int(floor(q.y));\n        float active = storedLegal(from, to);\n        float glow = 0.0;\n        for (int dy = -1; dy <= 1; ++dy) {\n            for (int dx = -1; dx <= 1; ++dx) {\n                float bit = storedLegal(from + dy, to + dx);\n                vec2 delta = q - floor(q) - vec2(float(dx), float(dy)) - 0.5;\n                glow += bit * exp(-2.9 * dot(delta, delta));\n            }\n        }\n        color = vec3(0.025, 0.073, 0.085);\n        vec2 group = abs(fract(q / 8.0) - 0.5);\n        float gridLine = max(smoothstep(0.475, 0.5, group.x), smoothstep(0.475, 0.5, group.y));\n        color += vec3(0.030, 0.085, 0.095) * gridLine;\n        float scan = exp(-350.0 * pow(fract(q.y / 64.0 - pulse.w * 0.25 + 1.0) - 0.5, 2.0));\n        float d = sdBox(fract(q) - 0.5, vec2(0.38));\n        float aa = 0.65 * 64.0 / (chessSize() * size);\n        color += ink * glow * (0.25 + pulse.x * 0.35);\n        color = mix(color, ink * (0.84 + 0.20 * scan), active * coverage(d, aa));\n        color += ink * scan * 0.025;\n        if (!uAutoPlay && uiData(1) >= 0) {\n            if (from == uiData(1) || to == uiData(10)) color += dataAmber() * 0.10;\n        }\n        if (locked) color *= 0.45;\n    }\n    int title = locked ? 44 : 42;\n    color = labelLeft(color, uv, rect.xy + vec2(0.014, rect.w - 0.026), title,\n        wide ? 0.00235 : 0.00195, locked ? dataAmber() : dataCyan());\n    if (wide) {\n        color = labelLeft(color, uv, rect.xy + vec2(0.026, 0.407), 86, 0.00190, dataInk());\n        color = numberRight(color, uv, rect.xy + vec2(0.309, 0.404), int(gameData(27).z), 4, 0.00260, ink);\n        vec4 histogram = vec4(rect.xy + vec2(0.026, 0.043), 0.283, 0.038);\n        if (inDataRect(uv, histogram)) {\n            vec2 q = (uv - histogram.xy) / histogram.zw;\n            int from = int(floor(q.x * 64.0));\n            float count = factData(from).r;\n            if (q.y < count / 32.0)\n                color = mix(color, ink * 0.7, 0.75);\n        }\n        color = labelLeft(color, uv, rect.xy + vec2(0.026, 0.017), 62, 0.00165, dataInk());\n        color = labelRight(color, uv, rect.xy + vec2(0.309, 0.017), 43, 0.00165, dataInk());\n    } else {\n        color = labelLeft(color, uv, rect.xy + vec2(0.035, 0.006), 83, 0.00155, dataInk());\n        color = numberRight(color, uv, rect.xy + vec2(0.255, 0.006), int(gameData(27).z), 4, 0.00175, ink);\n    }\n    return color;\n}\n\nvec3 historyMemory(vec3 color, vec2 uv, vec4 rect, vec4 pulse) {\n    bool wide = chessWide();\n    vec4 hist = gameData(19);\n    vec4 area = wide ? vec4(rect.xy + vec2(0.019, 0.111), 0.297, 0.272)\n                     : vec4(rect.xy + vec2(0.013, 0.070), 0.264, 0.172);\n    vec2 shape = wide ? vec2(13.0, 12.0) : vec2(16.0, 10.0);\n    if (inDataRect(uv, area)) {\n        vec2 q = (uv - area.xy) / area.zw * shape;\n        int slot = int(floor(q.x) + floor(q.y) * shape.x);\n        vec2 p = fract(q);\n        bool valid = slot < 151 && slot < int(hist.y);\n        color = vec3(0.029, 0.066, 0.074);\n        float frame = coverage(abs(sdBox(p - 0.5, vec2(0.465))) - 0.014,\n            0.55 / (chessSize() * min(area.z / shape.x, area.w / shape.y)));\n        color = mix(color, vec3(0.08, 0.19, 0.21), frame * 0.50);\n        if (valid && p.x > 0.09 && p.x < 0.91 && p.y > 0.09 && p.y < 0.91) {\n            vec2 memoryUV = (p - 0.09) / 0.82;\n            int square = int(floor(memoryUV.x * 8.0)) + int(floor(memoryUV.y * 8.0)) * 8;\n            int column = square / 4;\n            int piece = packedPiece(gameData(64 + slot * 17 + column), square - column * 4);\n            if (piece != 0) {\n                float strength = 0.35 + abs(float(piece)) * 0.055;\n                color = (piece > 0 ? dataCyan() : dataAmber()) * strength;\n            }\n        }\n        if (valid && factData(64 + slot).r > 0.5)\n            color = mix(color, dataAmber(), frame * 0.90);\n        if (slot == int(hist.x))\n            color = mix(color, dataAmber(), frame * (0.65 + 0.35 * pulse.x));\n        if (slot >= 151) color *= 0.35;\n    }\n    color = labelLeft(color, uv, rect.xy + vec2(0.014, rect.w - 0.027), 45,\n        wide ? 0.00220 : 0.00190, dataCyan());\n    if (wide) {\n        color = labelLeft(color, uv, rect.xy + vec2(0.019, 0.406), 46, 0.0019, dataInk());\n        color = labelLeft(color, uv, rect.xy + vec2(0.019, 0.067), 47, 0.00185, dataInk());\n        color = numberRight(color, uv, rect.xy + vec2(0.143, 0.065), int(hist.x), 3, 0.0026, dataAmber());\n        color = labelLeft(color, uv, rect.xy + vec2(0.180, 0.067), 48, 0.00185, dataInk());\n        color = numberRight(color, uv, rect.xy + vec2(0.315, 0.065), int(hist.y), 3, 0.0026, dataCyan());\n        color = labelLeft(color, uv, rect.xy + vec2(0.019, 0.024), 87, 0.00185, dataInk());\n    } else {\n        color = labelLeft(color, uv, rect.xy + vec2(0.014, 0.027), 47, 0.00165, dataInk());\n        color = numberRight(color, uv, rect.xy + vec2(0.135, 0.025), int(hist.x), 3, 0.0022, dataAmber());\n        color = labelLeft(color, uv, rect.xy + vec2(0.160, 0.027), 48, 0.00165, dataInk());\n        color = numberRight(color, uv, rect.xy + vec2(0.276, 0.025), int(hist.y), 3, 0.0022, dataCyan());\n    }\n    return color;\n}\n\nvec3 registerMemory(vec3 color, vec2 uv, vec4 rect, vec4 pulse) {\n    vec4 rules = gameData(16), hist = gameData(19);\n    color = labelLeft(color, uv, rect.xy + vec2(0.018, 0.408), 49, 0.0028, dataCyan());\n    color = labelLeft(color, uv, rect.xy + vec2(0.018, 0.352), 81, 0.0020, dataInk());\n    color = labelRight(color, uv, rect.xy + vec2(0.313, 0.350), rules.x > 0.0 ? 57 : 58,\n        0.0028, rules.x > 0.0 ? dataCyan() : dataAmber());\n    color = labelLeft(color, uv, rect.xy + vec2(0.018, 0.298), 51, 0.0019, dataInk());\n    color = numberRight(color, uv, rect.xy + vec2(0.153, 0.296), uiData(11), 6, 0.0027, dataCyan());\n    color = labelLeft(color, uv, rect.xy + vec2(0.189, 0.298), 52, 0.0019, dataInk());\n    color = numberRight(color, uv, rect.xy + vec2(0.313, 0.296), int(rules.w), 3, 0.0027, dataAmber());\n    color = labelLeft(color, uv, rect.xy + vec2(0.018, 0.251), 53, 0.0018, dataInk());\n    color = numberRight(color, uv, rect.xy + vec2(0.153, 0.249), int(hist.z), 2, 0.0027, dataCyan());\n    color = labelLeft(color, uv, rect.xy + vec2(0.189, 0.251), 54, 0.0019, dataInk());\n    color = squareReadout(color, uv, rect.xy + vec2(0.272, 0.247), int(rules.z), dataAmber());\n    color = labelLeft(color, uv, rect.xy + vec2(0.018, 0.200), 55, 0.0017, dataInk());\n    for (int bit = 0; bit < 4; ++bit) {\n        float x = rect.x + 0.167 + float(bit) * 0.043;\n        vec3 ink = bit < 2 ? dataCyan() : dataAmber();\n        color = bitLamp(color, uv, vec2(x, rect.y + 0.209), 0.0046,\n            numericBit(rules.y, float(bit)) > 0.5, ink, pulse.x);\n        color = labelCenter(color, uv, vec2(x, rect.y + 0.186), 65 + bit, 0.0013, dataInk());\n    }\n    color = labelLeft(color, uv, rect.xy + vec2(0.018, 0.151), 56, 0.0018, dataInk());\n    color = bitLamp(color, uv, rect.xy + vec2(0.184, 0.159), 0.0045,\n        numericBit(hist.w, 0.0) > 0.5, dataAmber(), pulse.x);\n    color = bitLamp(color, uv, rect.xy + vec2(0.242, 0.159), 0.0045,\n        numericBit(hist.w, 1.0) > 0.5, dataAmber(), pulse.x);\n    color = bitLamp(color, uv, rect.xy + vec2(0.300, 0.159), 0.0045,\n        gameData(21).x > 0.5, dataCyan(), pulse.x);\n    color = labelCenter(color, uv, rect.xy + vec2(0.184, 0.135), 76, 0.00125, dataInk());\n    color = labelCenter(color, uv, rect.xy + vec2(0.242, 0.135), 70, 0.00125, dataInk());\n    color = labelCenter(color, uv, rect.xy + vec2(0.300, 0.135), 88, 0.00115, dataInk());\n    color = labelLeft(color, uv, rect.xy + vec2(0.018, 0.121), 84, 0.0016, dataInk());\n    color = registerBits(color, uv, vec4(rect.xy + vec2(0.019, 0.020), 0.297, 0.087), pulse.x);\n    return color;\n}\n\nvec3 memoryRail(vec3 color, vec2 uv, vec4 pulse) {\n    vec2 p = uv - 0.5;\n    float radius = 0.518;\n    float edge = max(abs(p.x), abs(p.y));\n    float distanceToRail = abs(edge - radius);\n    if (distanceToRail > 0.045) return color;\n    float path;\n    if (abs(p.x) > abs(p.y)) {\n        path = p.x > 0.0 ? 1.0 + (radius - p.y) / (2.0 * radius)\n                         : 3.0 + (p.y + radius) / (2.0 * radius);\n    } else {\n        path = p.y > 0.0 ? (p.x + radius) / (2.0 * radius)\n                         : 2.0 + (radius - p.x) / (2.0 * radius);\n    }\n    int square = int(floor(path * 16.0));\n    if (square < 0) square = 0;\n    if (square > 63) square = 63;\n    int piece = uiPiece(square);\n    vec3 ink = piece > 0 ? dataCyan() : dataAmber();\n    if (piece == 0) ink = vec3(0.09, 0.24, 0.26);\n    float energy = piece == 0 ? 0.15 : 0.40 + abs(float(piece)) * 0.055;\n    if (square == uiData(5) || square == uiData(6)) energy += pulse.x * 1.3;\n    if (uiData(7) != 0) ink = mix(ink, vec3(1.0, 0.32, 0.20), 0.43);\n    ink = mix(ink, vec3(1.0, 0.27, 0.18), pulse.z * 0.8);\n    float scan = pow(max(0.0, cos((path - pulse.w) * 1.5707963)), 32.0);\n    float segment = fract(path * 16.0);\n    float segmentMask = smoothstep(0.03, 0.13, segment) * (1.0 - smoothstep(0.87, 0.97, segment));\n    float glow = exp(-distanceToRail * 170.0) * segmentMask;\n    color += ink * glow * (0.16 + pulse.x * 0.10 + pulse.y * 0.25) * energy;\n    float core = coverage(distanceToRail - 0.0038, 0.6 / chessSize()) * segmentMask;\n    color = mix(color, ink * (energy + scan * 0.30), core);\n    float hairline = coverage(abs(edge - 0.532) - 0.00055, 0.65 / chessSize());\n    color += mix(dataCyan(), dataAmber(), pulse.y) * hairline * (0.19 + pulse.x * 0.35);\n    return color;\n}\n\nvec3 drawStateBorder(vec3 color, vec2 uv, vec4 pulse) {\n    color = memoryRail(color, uv, pulse);\n    vec4 a = dataPanelRect(0), b = dataPanelRect(1), c = dataPanelRect(2);\n    color = instrumentPanel(color, uv, a, dataCyan(), pulse.x);\n    color = instrumentPanel(color, uv, b, dataCyan(), pulse.x);\n    color = instrumentPanel(color, uv, c, dataAmber(), pulse.y + pulse.x * 0.4);\n    color = pieceMemory(color, uv, a, pulse);\n    color = legalMemory(color, uv, b, pulse);\n    color = historyMemory(color, uv, c, pulse);\n    if (chessWide()) {\n        vec4 d = dataPanelRect(3);\n        color = instrumentPanel(color, uv, d, dataCyan(), pulse.x);\n        color = registerMemory(color, uv, d, pulse);\n    }\n    return color;\n}\n\n// Parent supplies uiPiece(square), uiLegal(from,to), and uiData(key).\n// Keys: turn, selection, pending from/to, result, last from/to, check,\n// claim flags, flip, destination, ply, promotion, float support, event, armed.\nvec4 drawChess(vec2 fragCoord) {\n    vec2 uv = chessPoint(fragCoord);\n    float pixelAA = 0.70 / chessSize();\n    vec4 pulse = gameData(25);\n    vec2 screenUV = fragCoord / RENDERSIZE;\n    vec3 color = vec3(0.013, 0.029, 0.035);\n    color += 0.009 * (1.0 - screenUV.y);\n    color *= 1.0 - 0.08 * length(screenUV - 0.5);\n    vec3 cream = vec3(0.86, 0.88, 0.77);\n    vec3 muted = vec3(0.47, 0.57, 0.48);\n    vec3 gold = vec3(0.83, 0.77, 0.48);\n    vec3 red = vec3(0.94, 0.49, 0.40);\n\n    int turn = uiData(0);\n    int selectedSquare = uAutoPlay ? -1 : uiData(1);\n    int status = uiData(4);\n    int flip = uiData(9);\n    int destination = uAutoPlay ? uiData(6) : uiData(10);\n    bool inCheck = uiData(7) != 0;\n\n    vec2 boardUV = (uv - INSET) / BOARD_SIZE;\n    vec2 grid = boardUV * 8.0;\n    bool inside = boardUV.x >= 0.0 && boardUV.y >= 0.0 && boardUV.x < 1.0 && boardUV.y < 1.0;\n    bool frame = uv.x >= 0.0 && uv.x < 1.0 && uv.y >= 0.0 && uv.y < 1.0;\n\n    if (frame) {\n        color = vec3(0.033, 0.067, 0.069) + 0.010 * (1.0 - uv.y);\n    }\n    if (inside) {\n        int displayFile = int(floor(grid.x));\n        int displayRank = int(floor(grid.y));\n        int file = flip == 0 ? displayFile : 7 - displayFile;\n        int rank = flip == 0 ? displayRank : 7 - displayRank;\n        int square = file + rank * 8;\n        int piece = uiPiece(square);\n        int type = piece < 0 ? -piece : piece;\n        vec2 p = fract(grid) - 0.5;\n        float aa = pixelAA * 8.0 / BOARD_SIZE;\n        bool lightSquare = mod(float(file + rank), 2.0) > 0.5;\n        color = lightSquare ? vec3(0.871, 0.863, 0.791) : vec3(0.431, 0.552, 0.480);\n        color += 0.015 * (boardUV.y - boardUV.x);\n\n        if (square == uiData(5) || square == uiData(6))\n            color = mix(color, vec3(0.81, 0.75, 0.40), 0.31);\n        if (square == destination && square != selectedSquare && status == 0 && !uAutoPlay)\n            color = mix(color, vec3(0.96, 0.96, 0.88), 0.13);\n        if (square == selectedSquare && status == 0)\n            color = mix(color, vec3(0.88, 0.80, 0.44), 0.49);\n        if (inCheck && type == 6 && (piece > 0 ? 1 : -1) == turn) {\n            float glow = exp(-4.1 * dot(p, p));\n            color = mix(color, vec3(0.79, 0.23, 0.20), 0.40 + 0.29 * glow);\n        }\n        if (square == selectedSquare && status == 0) {\n            float border = abs(sdBox(p, vec2(0.468))) - 0.009;\n            color = mix(color, vec3(0.95, 0.90, 0.60), 0.85 * coverage(border, aa));\n        }\n\n        bool legal = false;\n        if (selectedSquare >= 0 && selectedSquare < 64 && status == 0)\n            legal = uiLegal(selectedSquare, square) > 0.5;\n        if (legal) {\n            float marker;\n            if (piece == 0) marker = coverage(sdCircle(p, 0.069), aa);\n            else {\n                float ring = abs(length(p) - 0.442) - 0.027;\n                marker = coverage(ring, aa) * smoothstep(0.230, 0.260, min(abs(p.x), abs(p.y)));\n            }\n            color = mix(color, vec3(0.10, 0.25, 0.18), marker * 0.49);\n        }\n        if (uiData(5) >= 0 && uiData(6) >= 0 && pulse.x > 0.005) {\n            int fromRank = uiData(5) / 8, toRank = uiData(6) / 8;\n            vec2 from = vec2(float(uiData(5) - fromRank * 8), float(fromRank));\n            vec2 to = vec2(float(uiData(6) - toRank * 8), float(toRank));\n            if (flip != 0) { from = 7.0 - from; to = 7.0 - to; }\n            from = INSET + (from + 0.5) * BOARD_SIZE / 8.0;\n            to = INSET + (to + 0.5) * BOARD_SIZE / 8.0;\n            float trace = sdCapsule(uv, from, to, 0.0015);\n            float glow = exp(-max(trace, 0.0) * 330.0);\n            color = mix(color, mix(dataCyan(), dataAmber(), pulse.y),\n                glow * pulse.x * (0.18 + 0.20 * pulse.y));\n            if (square == uiData(6) && pulse.y > 0.005) {\n                float radius = 0.10 + (1.0 - pulse.y) * 0.45;\n                float ring = abs(length(p) - radius) - 0.020;\n                color = mix(color, dataAmber(), coverage(ring, aa) * pulse.y * 0.65);\n            }\n        }\n        if (piece != 0) color = drawPiece(color, p, piece, aa);\n        float edge = min(min(boardUV.x, boardUV.y), min(1.0 - boardUV.x, 1.0 - boardUV.y));\n        color *= 1.0 - 0.12 * (1.0 - smoothstep(0.0, 0.006, edge));\n    } else if (frame) {\n        float rim = abs(sdBox(uv - 0.5, vec2(BOARD_SIZE * 0.5 + 0.003))) - 0.0006;\n        color = mix(color, vec3(0.54, 0.60, 0.49), 0.28 * coverage(rim, pixelAA));\n        if (uv.x >= INSET && uv.x < 1.0 - INSET) {\n            int file = int(floor(grid.x));\n            int actual = flip == 0 ? file : 7 - file;\n            float x = INSET + (float(file) + 0.5) * BOARD_SIZE / 8.0;\n            float lower = boardCoordinate(uv - vec2(x, 0.0275), actual + 1);\n            float upper = boardCoordinate(uv - vec2(x, 0.9725), actual + 1);\n            color = mix(color, vec3(0.76, 0.80, 0.69), lower * 0.85 + upper * 0.53);\n        }\n        if (uv.y >= INSET && uv.y < 1.0 - INSET) {\n            int rank = int(floor(grid.y));\n            int actual = flip == 0 ? rank : 7 - rank;\n            float y = INSET + (float(rank) + 0.5) * BOARD_SIZE / 8.0;\n            float left = boardCoordinate(uv - vec2(0.0275, y), actual + 28);\n            float right = boardCoordinate(uv - vec2(0.9725, y), actual + 28);\n            color = mix(color, vec3(0.76, 0.80, 0.69), left * 0.85 + right * 0.53);\n        }\n    }\n\n    color = drawStateBorder(color, uv, pulse);\n    color = labelLeft(color, uv, vec2(0.055, 1.068), 0, 0.0040, cream);\n    color = labelLeft(color, uv, vec2(0.055, 1.044), 80, 0.00215, muted);\n    int statusLabel = turn > 0 ? 1 : 2;\n    int reason = -1;\n    vec3 statusInk = cream;\n    if (inCheck) { statusLabel = turn > 0 ? 3 : 4; statusInk = red; }\n    if (status == 1 || status == 9) statusLabel = 5;\n    else if (status == 2 || status == 10) statusLabel = 6;\n    else if (status != 0) statusLabel = 7;\n    if (status != 0) statusInk = gold;\n    if (status == 1 || status == 2) reason = 8;\n    else if (status == 3) reason = 9;\n    else if (status == 4) reason = 10;\n    else if (status == 5) reason = 11;\n    else if (status == 6) reason = 12;\n    else if (status == 7) reason = 32;\n    else if (status == 8) reason = 31;\n    else if (status == 9 || status == 10) reason = 30;\n    else if (status == 11) reason = 29;\n    color = labelRight(color, uv, vec2(0.945, 1.070), statusLabel, 0.0035, statusInk);\n    if (reason >= 0)\n        color = labelRight(color, uv, vec2(0.945, 1.044), reason, 0.00215, muted);\n    if (uAutoPlay) {\n        float elapsed = mod(mod(floor(TIME), 1024.0) - gameData(26).y + 1024.0, 1024.0)\n            + fract(TIME) - gameData(26).z;\n        float progress = clamp(elapsed, 0.0, 1.0);\n        vec3 autoInk = status == 0 ? dataCyan() : dataAmber();\n        color = bitLamp(color, uv, vec2(0.351, 1.081), 0.0040, true, autoInk,\n            status == 0 ? 1.0 - progress : 0.0);\n        color = labelLeft(color, uv, vec2(0.366, 1.075), status == 0 ? 59 : 60, 0.00185, autoInk);\n        if (status == 0) {\n            float track = coverage(sdBox(uv - vec2(0.416, 1.057), vec2(0.050, 0.0010)), pixelAA);\n            color = mix(color, autoInk * 0.2, track);\n            float fill = coverage(sdBox(uv - vec2(0.366 + progress * 0.050, 1.057),\n                vec2(max(0.00001, progress * 0.050), 0.0010)), pixelAA);\n            color = mix(color, autoInk, fill);\n        }\n    }\n\n    // These are readouts for the host's native ISF controls, not canvas buttons.\n    float ruleLine = coverage(sdBox(uv - vec2(0.5, -0.020), vec2(0.445, 0.00055)), pixelAA);\n    color = mix(color, muted, ruleLine * 0.25);\n    color = labelLeft(color, uv, vec2(0.055, -0.064), 23, 0.0026, muted);\n    color = squareReadout(color, uv, vec2(0.149, -0.066), uAutoPlay ? uiData(5) : selectedSquare, cream);\n    color = labelLeft(color, uv, vec2(0.266, -0.064), 24, 0.0026, muted);\n    color = squareReadout(color, uv, vec2(0.320, -0.066), destination, cream);\n    color = labelLeft(color, uv, vec2(0.582, -0.064), 25, 0.0023, muted);\n    int promotion = uiData(12);\n    int promotionLabel = 19;\n    if (promotion == 4) promotionLabel = 20;\n    else if (promotion == 3) promotionLabel = 21;\n    else if (promotion == 2) promotionLabel = 22;\n    if (uAutoPlay)\n        color = labelRight(color, uv, vec2(0.945, -0.066), 89, 0.0031, cream);\n    else\n        color = labelRight(color, uv, vec2(0.945, -0.066), promotionLabel, 0.0031, cream);\n\n    int footer = 26;\n    vec3 footerInk = muted;\n    if (uAutoPlay) { footer = 61; footerInk = dataCyan() * 0.80; }\n    if (uiData(8) != 0) { footer = 33; footerInk = gold; }\n    if (uiData(14) == 2) { footer = 34; footerInk = red; }\n    if (uiData(14) == 6) { footer = 36; footerInk = red; }\n    if (uiData(15) != 0) { footer = 35; footerInk = gold; }\n    if (status != 0) { footer = 27; footerInk = muted; }\n    color = labelCenter(color, uv, vec2(0.5, -0.117), footer, 0.00255, footerInk);\n\n    if (uiData(13) == 0) {\n        color *= 0.20;\n        float panel = coverage(sdRoundBox(uv - vec2(0.5, 0.5), vec2(0.46, 0.115), 0.012), pixelAA);\n        color = mix(color, vec3(0.10, 0.14, 0.115), panel);\n        color = labelCenter(color, uv, vec2(0.5, 0.545), 37, 0.0040, red);\n        color = labelCenter(color, uv, vec2(0.5, 0.477), 38, 0.0027, cream);\n        color = labelCenter(color, uv, vec2(0.5, 0.426), 39, 0.00265, muted);\n    }\n    return vec4(color, 1.0);\n}\n\n\nvoid main() {\n    ivec2 pixel = ivec2(floor(gl_FragCoord.xy));\n    int index = pixel.y * 64 + pixel.x;\n    if (PASSINDEX == 0) gl_FragColor = pixel.x < 64 ? advanceGame(index) : vec4(0.0);\n    else if (PASSINDEX == 1) gl_FragColor = calculateLegal(pixel);\n    else if (PASSINDEX == 2) gl_FragColor = pixel.x < 64 ? calculateFacts(index) : vec4(0.0);\n    else if (PASSINDEX == 3) gl_FragColor = pixel.x < 64 ? finishGame(index) : vec4(0.0);\n    else if (PASSINDEX == 4) gl_FragColor = buildTextAtlas(pixel);\n    else gl_FragColor = drawChess(gl_FragCoord.xy);\n}\n"
  }
}