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