{
  "license": "CC0-1.0",
  "shader": {
    "id": "jcg4d38m",
    "title": "chess",
    "license": "CC0-1.0",
    "provenance_type": "witnessed_generation",
    "made_with": "glslop",
    "status": "active",
    "compile_ok": true,
    "render_hash": "007e7e7e7e7e3c00",
    "tags": [],
    "parents": [],
    "author": "c9jwkv23klru",
    "author_name": "croze",
    "author_url": "/u/croze",
    "created_at": "2026-10-10T03:54:09.611Z",
    "poster_url": "/posters/jcg4d38m.avif",
    "stats": {
      "slops": 0,
      "views": 15,
      "forks": 1
    },
    "superseded_by": "77qfigl5",
    "self": "/api/v1/shaders/jcg4d38m",
    "isf": "/*{\n  \"DESCRIPTION\": \"Playable chess entirely in GLSL. Choose From and To, then press Move. Promotion defaults to Queen. Rules, history, checkmate and draws live in feedback textures. No mouse binding is required. 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\": \"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  ]\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// All state values are small exact integers; the ply counter is split base1024.\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// 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    if (component == 0) return int(sampleValue.r);\n    if (component == 1) return int(sampleValue.g);\n    if (component == 2) return int(sampleValue.b);\n    return int(sampleValue.a);\n}\nint rulesBasePiece(int square) {\n    if (square < 0 || square >= 64) return 0;\n    int column = square / 4;\n    vec4 sampleValue = PASSINDEX == 0 ? gameData(column) : moveData(column);\n    return packedPiece(sampleValue, square - column * 4);\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);\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// An overlay changes at most four squares without copying the board.\n// Two layers allow a committed move plus a hypothetical reply, including\n// the legal en-passant test needed for exact repetition identity.\nstruct RuleMove {\n    int fromSquare;\n    int toSquare;\n    int movingPiece;\n    int epVictim;\n    int rookFrom;\n    int rookTo;\n    int rookPiece;\n};\nstruct RulePosition {\n    RuleMove first;\n    RuleMove second;\n};\n\nRuleMove ruleNoMove() {\n    return RuleMove(-1, -1, 0, -1, -1, -1, 0);\n}\nRulePosition ruleCurrent() {\n    return RulePosition(ruleNoMove(), ruleNoMove());\n}\n\nint rulePiece(RulePosition position, int square) {\n    if (square < 0 || square >= 64) return 0;\n    if (square == position.second.toSquare) return position.second.movingPiece;\n    if (square == position.second.rookTo) return position.second.rookPiece;\n    if (square == position.second.fromSquare || square == position.second.epVictim ||\n        square == position.second.rookFrom) return 0;\n    if (square == position.first.toSquare) return position.first.movingPiece;\n    if (square == position.first.rookTo) return position.first.rookPiece;\n    if (square == position.first.fromSquare || square == position.first.epVictim ||\n        square == position.first.rookFrom) return 0;\n    return rulesBasePiece(square);\n}\n\n// Apply a legal move to a position with zero or one existing overlay.\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 ruleAfterMove(RulePosition position, int from, int to, int promotion, int ep) {\n    int moving = rulePiece(position, from);\n    int side = ruleSide(moving);\n    int kind = ruleAbs(moving);\n    RuleMove move = RuleMove(from, to, moving, -1, -1, -1, 0);\n\n    if (kind == 1 && to == ep && rulePiece(position, to) == 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        move.rookPiece = rulePiece(position, move.rookFrom);\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    if (position.first.fromSquare < 0) position.first = move;\n    else position.second = move;\n    return position;\n}\n\nivec2 ruleKnightStep(int direction) {\n    if (direction == 0) return ivec2(1, 2);\n    if (direction == 1) return ivec2(2, 1);\n    if (direction == 2) return ivec2(2, -1);\n    if (direction == 3) return ivec2(1, -2);\n    if (direction == 4) return ivec2(-1, -2);\n    if (direction == 5) return ivec2(-2, -1);\n    if (direction == 6) return ivec2(-2, 1);\n    return ivec2(-1, 2);\n}\nivec2 ruleRayStep(int direction) {\n    if (direction == 0) return ivec2(1, 0);\n    if (direction == 1) return ivec2(-1, 0);\n    if (direction == 2) return ivec2(0, 1);\n    if (direction == 3) return ivec2(0, -1);\n    if (direction == 4) return ivec2(1, 1);\n    if (direction == 5) return ivec2(1, -1);\n    if (direction == 6) return ivec2(-1, 1);\n    return ivec2(-1, -1);\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    int pawnRank = rank - bySide;\n\n    if (ruleInside(file - 1, pawnRank) &&\n        rulePiece(position, pawnRank * 8 + file - 1) == bySide) return true;\n    if (ruleInside(file + 1, pawnRank) &&\n        rulePiece(position, pawnRank * 8 + file + 1) == bySide) return true;\n\n    for (int direction = 0; direction < 8; ++direction) {\n        ivec2 p = ivec2(file, rank) + ruleKnightStep(direction);\n        if (ruleInside(p.x, p.y) && rulePiece(position, p.y * 8 + p.x) == bySide * 2)\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    for (int square = 0; square < 64; ++square) {\n        if (rulePiece(position, square) == side * 6) return square;\n    }\n    return -1;\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) {\n        shape = ruleAbs(df) == ruleAbs(dr) && rulePathClear(position, from, to);\n    } else if (kind == 4) {\n        shape = (df == 0 || dr == 0) && rulePathClear(position, from, to);\n    } else if (kind == 5) {\n        shape = (df == 0 || dr == 0 || ruleAbs(df) == ruleAbs(dr)) &&\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    if (castle) {\n        if (attacked(position, from, -side)) return false;\n        int transit = from + (to > from ? 1 : -1);\n        RulePosition crossing = ruleAfterMove(position, from, transit, 5, ep);\n        if (attacked(crossing, transit, -side)) 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    RulePosition trial = ruleAfterMove(position, from, to, 5, ep);\n    return !inCheck(trial, side);\n}\n\n// Supply the PREMOVE position. A removed castling right never returns.\nint nextCastlingRights(RulePosition position, int rights, int from, int to) {\n    int moving = rulePiece(position, from);\n    int captured = rulePiece(position, 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// Only an actually legal EP capture changes the repetition identity.\n// In particular, a pinned pawn next to the double-pushed pawn is not enough.\nint effectiveEp(RulePosition position, int side, int rights, int ep) {\n    if (ep < 0 || ep >= 64 || (side != 1 && side != -1)) return -1;\n    if (ruleRank(ep) != (side == 1 ? 5 : 2) || rulePiece(position, ep) != 0 ||\n        rulePiece(position, ep - side * 8) != -side) return -1;\n    int epFile = ruleFile(ep);\n    for (int direction = -1; direction <= 1; direction += 2) {\n        int file = epFile + direction;\n        if (file < 0 || file >= 8) continue;\n        int from = (ruleRank(ep) - side) * 8 + file;\n        if (rulePiece(position, from) == side &&\n            legalMove(position, side, rights, ep, from, ep)) return ep;\n    }\n    return -1;\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    return vec4(float(rulePiece(position, column * 4)), float(rulePiece(position, column * 4 + 1)),\n                float(rulePiece(position, column * 4 + 2)), float(rulePiece(position, column * 4 + 3)));\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// PASS 0: process only rising edges of the host's ISF event uniforms.\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        return vec4(0.0);\n    }\n    // History is updated in pass 3; no rule work for historical cells here.\n    if (index >= 25) 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    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\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 = rulePiece(position, 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(uFrom, uTo) < 0.5) event.x = 2;\n            else {\n                int moving = rulePiece(position, uFrom);\n                int captured = rulePiece(position, uTo);\n                if (ruleAbs(moving) == 1 && uTo == rules.z && captured == 0 &&\n                    ruleFile(uFrom) != ruleFile(uTo)) captured = -rules.x;\n                bool irreversible = ruleAbs(moving) == 1 || captured != 0;\n                int rights = nextCastlingRights(position, rules.y, uFrom, uTo);\n                position = ruleAfterMove(position, uFrom, uTo, uPromotion, rules.z);\n                int ep = ruleAbs(moving) == 1 && ruleAbs(uTo - uFrom) == 16 ? (uTo + uFrom) / 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(uFrom, uTo, 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(uTo) == 0 || ruleRank(uTo) == 7) ? uPromotion : 0);\n                claim = ivec4(0, claim.x, 0, 0);\n                system.x = 1;\n                committed = true;\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    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: legal-move counts by source square. 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        for (int to = 0; to < 64; ++to) count += storedLegal(index, to);\n        return vec4(count, 0.0, 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// 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    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 = effectiveEp(ruleCurrent(), rules.x, rules.y, 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\nfloat chessSize() { return min(RENDERSIZE.x * 0.94, RENDERSIZE.y * 0.79); }\nvec2 chessOrigin() {\n    float size = chessSize();\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 fontBits(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 labelPack(int label, int part) {\n    if (label == 0) { // GLSL CHESS\n        if (part == 0) return vec4(3224327.0, 1343680.0, 1235.0, 0.0);\n        if (part == 1) return vec4(0.0, 0.0, 0.0, 0.0);\n        if (part == 2) return vec4(0.0, 0.0, 0.0, 0.0);\n    }\n    if (label == 1) { // WHITE TO MOVE\n        if (part == 0) return vec4(5280279.0, 4014085.0, 5829440.0, 5.0);\n        if (part == 1) return vec4(0.0, 0.0, 0.0, 0.0);\n        if (part == 2) return vec4(0.0, 0.0, 0.0, 0.0);\n    }\n    if (label == 2) { // BLACK TO MOVE\n        if (part == 0) return vec4(791298.0, 4014091.0, 5829440.0, 5.0);\n        if (part == 1) return vec4(0.0, 0.0, 0.0, 0.0);\n        if (part == 2) return vec4(0.0, 0.0, 0.0, 0.0);\n    }\n    if (label == 3) { // WHITE IN CHECK\n        if (part == 0) return vec4(5280279.0, 3706885.0, 1343680.0, 707.0);\n        if (part == 1) return vec4(0.0, 0.0, 0.0, 0.0);\n        if (part == 2) return vec4(0.0, 0.0, 0.0, 0.0);\n    }\n    if (label == 4) { // BLACK IN CHECK\n        if (part == 0) return vec4(791298.0, 3706891.0, 1343680.0, 707.0);\n        if (part == 1) return vec4(0.0, 0.0, 0.0, 0.0);\n        if (part == 2) return vec4(0.0, 0.0, 0.0, 0.0);\n    }\n    if (label == 5) { // WHITE WINS\n        if (part == 0) return vec4(5280279.0, 2453509.0, 1230.0, 0.0);\n        if (part == 1) return vec4(0.0, 0.0, 0.0, 0.0);\n        if (part == 2) return vec4(0.0, 0.0, 0.0, 0.0);\n    }\n    if (label == 6) { // BLACK WINS\n        if (part == 0) return vec4(791298.0, 2453515.0, 1230.0, 0.0);\n        if (part == 1) return vec4(0.0, 0.0, 0.0, 0.0);\n        if (part == 2) return vec4(0.0, 0.0, 0.0, 0.0);\n    }\n    if (label == 7) { // DRAW\n        if (part == 0) return vec4(6034564.0, 0.0, 0.0, 0.0);\n        if (part == 1) return vec4(0.0, 0.0, 0.0, 0.0);\n        if (part == 2) return vec4(0.0, 0.0, 0.0, 0.0);\n    }\n    if (label == 8) { // CHECKMATE\n        if (part == 0) return vec4(807427.0, 5247819.0, 5.0, 0.0);\n        if (part == 1) return vec4(0.0, 0.0, 0.0, 0.0);\n        if (part == 2) return vec4(0.0, 0.0, 0.0, 0.0);\n    }\n    if (label == 9) { // STALEMATE\n        if (part == 0) return vec4(3151123.0, 5247813.0, 5.0, 0.0);\n        if (part == 1) return vec4(0.0, 0.0, 0.0, 0.0);\n        if (part == 2) return vec4(0.0, 0.0, 0.0, 0.0);\n    }\n    if (label == 10) { // DEAD POSITION\n        if (part == 0) return vec4(1052996.0, 5043200.0, 3970313.0, 14.0);\n        if (part == 1) return vec4(0.0, 0.0, 0.0, 0.0);\n        if (part == 2) return vec4(0.0, 0.0, 0.0, 0.0);\n    }\n    if (label == 11) { // FIVEFOLD REPETITION\n        if (part == 0) return vec4(1401414.0, 1098694.0, 4215936.0, 5281029.0);\n        if (part == 1) return vec4(58313.0, 0.0, 0.0, 0.0);\n        if (part == 2) return vec4(0.0, 0.0, 0.0, 0.0);\n    }\n    if (label == 12) { // 75 MOVE RULE\n        if (part == 0) return vec4(3409954.0, 21903.0, 1361234.0, 0.0);\n        if (part == 1) return vec4(0.0, 0.0, 0.0, 0.0);\n        if (part == 2) return vec4(0.0, 0.0, 0.0, 0.0);\n    }\n    if (label == 13) { // DRAW CLAIMED\n        if (part == 0) return vec4(6034564.0, 311488.0, 1069897.0, 0.0);\n        if (part == 1) return vec4(0.0, 0.0, 0.0, 0.0);\n        if (part == 2) return vec4(0.0, 0.0, 0.0, 0.0);\n    }\n    if (label == 14) { // NEW GAME\n        if (part == 0) return vec4(94542.0, 1364039.0, 0.0, 0.0);\n        if (part == 1) return vec4(0.0, 0.0, 0.0, 0.0);\n        if (part == 2) return vec4(0.0, 0.0, 0.0, 0.0);\n    }\n    if (label == 15) { // FLIP\n        if (part == 0) return vec4(4231942.0, 0.0, 0.0, 0.0);\n        if (part == 1) return vec4(0.0, 0.0, 0.0, 0.0);\n        if (part == 2) return vec4(0.0, 0.0, 0.0, 0.0);\n    }\n    if (label == 16) { // CLAIM DRAW\n        if (part == 0) return vec4(2364163.0, 4734989.0, 1473.0, 0.0);\n        if (part == 1) return vec4(0.0, 0.0, 0.0, 0.0);\n        if (part == 2) return vec4(0.0, 0.0, 0.0, 0.0);\n    }\n    if (label == 17) { // PROMOTE PAWN\n        if (part == 0) return vec4(3470480.0, 21775.0, 3764304.0, 0.0);\n        if (part == 1) return vec4(0.0, 0.0, 0.0, 0.0);\n        if (part == 2) return vec4(0.0, 0.0, 0.0, 0.0);\n    }\n    if (label == 18) { // CANCEL\n        if (part == 0) return vec4(843843.0, 773.0, 0.0, 0.0);\n        if (part == 1) return vec4(0.0, 0.0, 0.0, 0.0);\n        if (part == 2) return vec4(0.0, 0.0, 0.0, 0.0);\n    }\n    if (label == 19) { // QUEEN\n        if (part == 0) return vec4(1332561.0, 14.0, 0.0, 0.0);\n        if (part == 1) return vec4(0.0, 0.0, 0.0, 0.0);\n        if (part == 2) return vec4(0.0, 0.0, 0.0, 0.0);\n    }\n    if (label == 20) { // ROOK\n        if (part == 0) return vec4(2946002.0, 0.0, 0.0, 0.0);\n        if (part == 1) return vec4(0.0, 0.0, 0.0, 0.0);\n        if (part == 2) return vec4(0.0, 0.0, 0.0, 0.0);\n    }\n    if (label == 21) { // BISHOP\n        if (part == 0) return vec4(2175554.0, 1039.0, 0.0, 0.0);\n        if (part == 1) return vec4(0.0, 0.0, 0.0, 0.0);\n        if (part == 2) return vec4(0.0, 0.0, 0.0, 0.0);\n    }\n    if (label == 22) { // KNIGHT\n        if (part == 0) return vec4(1872779.0, 1288.0, 0.0, 0.0);\n        if (part == 1) return vec4(0.0, 0.0, 0.0, 0.0);\n        if (part == 2) return vec4(0.0, 0.0, 0.0, 0.0);\n    }\n    if (label == 23) { // FROM\n        if (part == 0) return vec4(3470470.0, 0.0, 0.0, 0.0);\n        if (part == 1) return vec4(0.0, 0.0, 0.0, 0.0);\n        if (part == 2) return vec4(0.0, 0.0, 0.0, 0.0);\n    }\n    if (label == 24) { // TO\n        if (part == 0) return vec4(980.0, 0.0, 0.0, 0.0);\n        if (part == 1) return vec4(0.0, 0.0, 0.0, 0.0);\n        if (part == 2) return vec4(0.0, 0.0, 0.0, 0.0);\n    }\n    if (label == 25) { // PROMOTION\n        if (part == 0) return vec4(3470480.0, 3970319.0, 14.0, 0.0);\n        if (part == 1) return vec4(0.0, 0.0, 0.0, 0.0);\n        if (part == 2) return 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) return vec4(3994115.0, 1573203.0, 54226.0, 4014117.0);\n        if (part == 1) return vec4(57920.0, 5301187.0, 5030866.0, 4196736.0);\n        if (part == 2) return 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) return vec4(5002384.0, 1368083.0, 290839.0, 2359629.0);\n        if (part == 1) return vec4(3944462.0, 4007182.0, 5244108.0, 1384463.0);\n        if (part == 2) return vec4(4723987.0, 20.0, 0.0, 0.0);\n    }\n    if (label == 28) { // TWO PLAYER CHESS\n        if (part == 0) return vec4(62932.0, 6558480.0, 787589.0, 5058888.0);\n        if (part == 1) return vec4(0.0, 0.0, 0.0, 0.0);\n        if (part == 2) return vec4(0.0, 0.0, 0.0, 0.0);\n    }\n    if (label == 29) { // DRAW BY AGREEMENT\n        if (part == 0) return vec4(6034564.0, 102528.0, 1384897.0, 3691333.0);\n        if (part == 1) return vec4(20.0, 0.0, 0.0, 0.0);\n        if (part == 2) return vec4(0.0, 0.0, 0.0, 0.0);\n    }\n    if (label == 30) { // RESIGNATION\n        if (part == 0) return vec4(2437458.0, 5247879.0, 58313.0, 0.0);\n        if (part == 1) return vec4(0.0, 0.0, 0.0, 0.0);\n        if (part == 2) return vec4(0.0, 0.0, 0.0, 0.0);\n    }\n    if (label == 31) { // 50 MOVE RULE\n        if (part == 0) return vec4(3409632.0, 21903.0, 1361234.0, 0.0);\n        if (part == 1) return vec4(0.0, 0.0, 0.0, 0.0);\n        if (part == 2) return vec4(0.0, 0.0, 0.0, 0.0);\n    }\n    if (label == 32) { // THREEFOLD REPETITION\n        if (part == 0) return vec4(1384980.0, 3207557.0, 1384452.0, 2441552.0);\n        if (part == 1) return vec4(3732052.0, 0.0, 0.0, 0.0);\n        if (part == 2) return vec4(0.0, 0.0, 0.0, 0.0);\n    }\n    if (label == 33) { // DRAW AVAILABLE IN CONTROLS\n        if (part == 0) return vec4(6034564.0, 352320.0, 529161.0, 2359628.0);\n        if (part == 1) return vec4(3944462.0, 4007182.0, 1228.0, 0.0);\n        if (part == 2) return vec4(0.0, 0.0, 0.0, 0.0);\n    }\n    if (label == 34) { // ILLEGAL MOVE - TRY A HIGHLIGHTED SQUARE\n        if (part == 0) return vec4(1360649.0, 49223.0, 1401805.0, 5245312.0);\n        if (part == 1) return vec4(263762.0, 1872384.0, 1872648.0, 1070344.0);\n        if (part == 2) return 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) return vec4(6034564.0, 311488.0, 262985.0, 1069906.0);\n        if (part == 1) return vec4(4983168.0, 807685.0, 3985428.0, 262486.0);\n        if (part == 2) return 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) return vec4(1049550.0, 94290.0, 2364163.0, 5771277.0);\n        if (part == 1) return vec4(311873.0, 21250.0, 74694.0, 5018132.0);\n        if (part == 2) return vec4(5043200.0, 3970313.0, 14.0, 0.0);\n    }\n    if (label == 37) { // FLOAT BUFFERS REQUIRED\n        if (part == 0) return vec4(324358.0, 5513236.0, 4739462.0, 1384467.0);\n        if (part == 1) return vec4(4756817.0, 261.0, 0.0, 0.0);\n        if (part == 2) return 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) return vec4(5018132.0, 5042688.0, 274452.0, 5305230.0);\n        if (part == 1) return vec4(4015296.0, 5243218.0, 786760.0, 5058888.0);\n        if (part == 2) return vec4(5043200.0, 3970313.0, 14.0, 0.0);\n    }\n    if (label == 39) { // ENABLE FLOAT TARGETS TO PLAY\n        if (part == 0) return vec4(529285.0, 1573196.0, 5247948.0, 4723968.0);\n        if (part == 1) return vec4(5062983.0, 62720.0, 6558480.0, 0.0);\n        if (part == 2) return vec4(0.0, 0.0, 0.0, 0.0);\n    }\n    return vec4(0.0);\n}\nfloat labelLength(int label) {\n    if (label == 0) return 10.0;\n    if (label == 1) return 13.0;\n    if (label == 2) return 13.0;\n    if (label == 3) return 14.0;\n    if (label == 4) return 14.0;\n    if (label == 5) return 10.0;\n    if (label == 6) return 10.0;\n    if (label == 7) return 4.0;\n    if (label == 8) return 9.0;\n    if (label == 9) return 9.0;\n    if (label == 10) return 13.0;\n    if (label == 11) return 19.0;\n    if (label == 12) return 12.0;\n    if (label == 13) return 12.0;\n    if (label == 14) return 8.0;\n    if (label == 15) return 4.0;\n    if (label == 16) return 10.0;\n    if (label == 17) return 12.0;\n    if (label == 18) return 6.0;\n    if (label == 19) return 5.0;\n    if (label == 20) return 4.0;\n    if (label == 21) return 6.0;\n    if (label == 22) return 6.0;\n    if (label == 23) return 4.0;\n    if (label == 24) return 2.0;\n    if (label == 25) return 9.0;\n    if (label == 26) return 41.0;\n    if (label == 27) return 37.0;\n    if (label == 28) return 16.0;\n    if (label == 29) return 17.0;\n    if (label == 30) return 11.0;\n    if (label == 31) return 12.0;\n    if (label == 32) return 20.0;\n    if (label == 33) return 26.0;\n    if (label == 34) return 39.0;\n    if (label == 35) return 45.0;\n    if (label == 36) return 41.0;\n    if (label == 37) return 22.0;\n    if (label == 38) return 41.0;\n    if (label == 39) return 28.0;\n    return 0.0;\n}\n\nfloat glyphCoverage(vec2 p, int glyph, float pixel) {\n    vec2 q = p / pixel;\n    if (q.x < -0.8 || q.x > 5.8 || q.y < -0.8 || q.y > 7.8) return 0.0;\n    vec2 glyphData = fontBits(glyph);\n    float baseX = floor(q.x), baseY = floor(q.y);\n    float aa = 0.65 / (chessSize() * pixel);\n    float alpha = 0.0;\n    // Adjacent lit pixels add their coverages, keeping connected strokes solid.\n    for (int dy = -1; dy <= 1; ++dy) {\n        for (int dx = -1; dx <= 1; ++dx) {\n            float x = baseX + float(dx), y = baseY + float(dy);\n            if (x >= 0.0 && x < 5.0 && y >= 0.0 && y < 7.0) {\n                float value = y < 4.0 ? glyphData.x : glyphData.y;\n                float shift = (y < 4.0 ? y : y - 4.0) * 5.0 + 4.0 - x;\n                float bit = mod(floor(value / exp2(shift)), 2.0);\n                if (bit > 0.5) {\n                    float d = sdBox(q - vec2(x + 0.5, y + 0.5), vec2(0.5));\n                    alpha += coverage(d, aa);\n                }\n            }\n        }\n    }\n    return min(alpha, 1.0);\n}\n\nfloat packedCharacter(vec4 glyphData, float index) {\n    float value = glyphData.x;\n    if (index >= 4.0) value = glyphData.y;\n    if (index >= 8.0) value = glyphData.z;\n    if (index >= 12.0) value = glyphData.w;\n    return mod(floor(value / exp2(mod(index, 4.0) * 6.0)), 64.0);\n}\n\nfloat labelCoverage(vec2 p, int label, float pixel) {\n    float count = labelLength(label);\n    if (p.x < 0.0 || p.x >= count * 6.0 * pixel ||\n        p.y < -pixel || p.y > 8.0 * pixel) return 0.0;\n    float character = floor(p.x / (6.0 * pixel));\n    int part = int(floor(character / 16.0));\n    vec4 glyphData = labelPack(label, part);\n    int glyph = int(packedCharacter(glyphData, mod(character, 16.0)));\n    vec2 local = vec2(p.x - character * 6.0 * pixel, p.y);\n    return glyphCoverage(local, glyph, pixel);\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    anchor.x -= (labelLength(label) * 6.0 - 1.0) * pixel;\n    return labelLeft(color, uv, anchor, label, pixel, ink);\n}\n\nvec3 labelCenter(vec3 color, vec2 uv, vec2 anchor, int label, float pixel, vec3 ink) {\n    anchor.x -= (labelLength(label) * 6.0 - 1.0) * pixel * 0.5;\n    return labelLeft(color, uv, anchor, label, pixel, ink);\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// 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    vec2 screenUV = fragCoord / RENDERSIZE;\n    vec3 color = vec3(0.066, 0.095, 0.080);\n    color += 0.014 * (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 = uiData(1);\n    int status = uiData(4);\n    int flip = uiData(9);\n    int destination = 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.092, 0.124, 0.111) + 0.014 * (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)\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 (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 = labelLeft(color, uv, vec2(0.055, 1.042), 0, 0.0040, cream);\n    color = labelLeft(color, uv, vec2(0.055, 1.016), 28, 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.044), statusLabel, 0.0035, statusInk);\n    if (reason >= 0)\n        color = labelRight(color, uv, vec2(0.945, 1.016), reason, 0.00215, muted);\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), 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    color = labelRight(color, uv, vec2(0.945, -0.066), promotionLabel, 0.0031, cream);\n\n    int footer = 26;\n    vec3 footerInk = muted;\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 gl_FragColor = drawChess(gl_FragCoord.xy);\n}\n",
    "generator": {
      "model": "chatgpt pro",
      "provider": null,
      "prompt": null,
      "params": null
    }
  }
}