// Rare-cell shading also treats built pieces as transparent surroundings. function builtForShade(i, x, y) { return (PH[mat[i]] === PH_SOLID && !(flags[i] & F_NAT)) || blockP[(y >> 2) * BW + (x >> 2)] || mach[i] || part[i]; } // CPU colour rules for the rare-cell WebGPU overrides. function cellPixel(x, y) { const { bw, bh, px, vx0, vy0, smoothOn: sm, rain: rk } = R; const bx = x - vx0, by = y - vy0, row = by * bw; if (R.gpuOn) { if (!gpuOverrideHas(row + bx)) GPU.overrideCount++; if (sm) GPU.overrideMask[row + bx] = 255; } const bpx = R.backPx, tpx = R.treePx, fpx = R.fieldPx, mmask = R.moldMask; const OUT = sm ? 0x04000000 : 0xff000000; if (x < 0 || y < 0 || x >= W || y >= H) { px[row + bx] = OUT; if (sm) { bpx[row + bx] = 0; tpx[row + bx] = 0; fpx[row + bx] = 0xff; } return; } const useFog = SET.fog, grain = SET.grain, incand = SET.incandescence; const anim = R.frame, lens = R.lens, tl = THERM[SET.thermalPalette] || THERM.inferno; const glowOn = SET.glow !== 'off' && !lens, gpx = R.glowPx, gw = R.glowW; const fy = y / FOG, fj = Math.floor(fy), ft = fy - fj, fr = fj * FW, fn = Math.min(FH - 1, fj + 1) * FW; const yw = y * W; let skr = SKY_R[y], skg = SKY_G[y], skb = SKY_B[y]; if (rk > 0.01) { const gy = (skr + skg + skb) / 3 * 0.82; skr += (gy - skr) * rk * 0.7; skg += (gy - skg) * rk * 0.7; skb += (gy - skb) * rk * 0.65; } let fv = 255; if (useFog) { const fx = x / FOG, j = Math.floor(fx), t = fx - j, j1 = Math.min(FW - 1, j + 1); const a = fog[fr + j], b = fog[fr + j1], c = fog[fn + j], d = fog[fn + j1]; if ((a & b & c & d) !== 255) fv = (a + (b - a) * t) * (1 - ft) + (c + (d - c) * t) * ft; if (!fv) { px[row + bx] = OUT; if (sm) { bpx[row + bx] = 0; tpx[row + bx] = 0; fpx[row + bx] = 0xff; } return; } // ease the fade into black (smoothstep) and break up the half-resolution steps with grain // noise that is strongest mid-fade and nothing at either end, so the outer edge has no rim if (fv < 255) { let s = fv / 255; s = Math.min(1, Math.max(0, s + (noiseB[yw + x] - 128) * (0.6 / 128) * s * (1 - s))); fv = Math.max(1, 255 * s * s * (3 - 2 * s)); } } const i = yw + x, m = mat[i], nz = noiseB[i] - 128; const T = temp[i]; // Also used by the thermal lens, which always draws in pixels. let looseShade = 1; if (!sm && m !== EMPTY && !builtForShade(i, x, y)) { if (PH[m] === PH_POWDER) looseShade = 1.1 + nz * 0.00065; else if (PH[m] === PH_SOLID && ( (x > 0 && PH[mat[i - 1]] === PH_POWDER && !part[i - 1]) || (x + 1 < W && PH[mat[i + 1]] === PH_POWDER && !part[i + 1]) || (y > 0 && PH[mat[i - W]] === PH_POWDER && !part[i - W]) || (y + 1 < H && PH[mat[i + W]] === PH_POWDER && !part[i + W]) )) looseShade = 0.72; } let r, g, b; if (lens) { const ti = thermIdx(T); if (m === EMPTY) { r = tl[ti] * 0.55; g = tl[ti + 1] * 0.55; b = tl[ti + 2] * 0.55; } else { const v = (COLR[m] + COLG[m] + COLB[m]) / 3 * 0.25; r = tl[ti] * 0.78 + v; g = tl[ti + 1] * 0.78 + v; b = tl[ti + 2] * 0.78 + v; } const p = part[i]; r *= looseShade; g *= looseShade; b *= looseShade; if (p && ((x + y) & 1)) { r = r * 0.7 + 40; g = g * 0.7 + 40; b = b * 0.7 + 40; } if (fv < 255) { r *= fv / 255; g *= fv / 255; b *= fv / 255; } px[row + bx] = 0xff000000 | ((b > 255 ? 255 : b) << 16) | ((g > 255 ? 255 : g) << 8) | (r > 255 ? 255 : r); return; } // background let br, bgc, bb, sky = false; const tb = bgm[i]; if (tb && !sm) { const v = nz * CVAR[tb] / 128; br = TREE_R[tb] + v; bgc = TREE_G[tb] + v; bb = TREE_B[tb] + v; if (digp[i]) { const f = 1 + digp[i] / 700; br *= f; bgc *= f; bb *= f; } } else if (y < surfY[x]) { br = skr; bgc = skg; bb = skb; sky = true; } else { const L = y < dirtY[x] ? BG.dirt : y < graniteY[x] ? BG.stone : y < basaltY[x] ? BG.granite : BG.basalt; const v = sm ? 0 : nz * 0.06; br = L[0] + v; bgc = L[1] + v; bb = L[2] + v; } if (sm) { // the colour behind the grain (alpha marks open sky), and a tree in front of that if there is one bpx[row + bx] = (sky ? 0xff000000 : 0) | ((bb & 255) << 16) | ((bgc & 255) << 8) | (br & 255); if (tb) { const v = nz * CVAR[tb] / 256, f = digp[i] ? 1 + digp[i] / 700 : 1; const tr = Math.max(0, Math.min(255, (TREE_R[tb] + v) * f)), tg = Math.max(0, Math.min(255, (TREE_G[tb] + v) * f)), tbb = Math.max(0, Math.min(255, (TREE_B[tb] + v) * f)); tpx[row + bx] = ((tb === LEAVES ? 0xff : 0x80) << 24) | (tbb << 16) | (tg << 8) | tr; } else tpx[row + bx] = 0; } if (m === EMPTY) { r = br; g = bgc; b = bb; } else { const v = nz * CVAR[m] * (sm ? (PH[m] === PH_SOLID ? 0 : grain * 0.35) : grain) / 128; r = COLR[m] + v; g = COLG[m] + v; b = COLB[m] + v; r *= looseShade; g *= looseShade; b *= looseShade; if (m === FIRE) { const f = (life[i] & 15) * 6; r = 255; g = 120 + f; b = 30 + f * 0.5; } else if (m === WATER && !sm) { const s = ((x * 3 + y * 5 + (anim >> 2)) & 15) === 0 ? 14 : 0; r += s; g += s; b += s; } else if (m === BELTL || m === BELTR) { const dir = m === BELTR ? -1 : 1, on = netSat(i) > 0, ly = y & 3, lx = x & 3; if (ly === 0 || ly === 3) { // top run moves with the load, bottom run returns the other way const st = ((x + (ly === 0 ? dir : -dir) * (on ? anim >> 1 : 0)) >> 1) & 1; const k = ly === 0 ? 1 : 0.72; r = (st ? 92 : 56) * k; g = (st ? 96 : 58) * k; b = (st ? 104 : 66) * k; } else if (lx === 1 || lx === 2) { const spin = on ? ((anim >> 2) + (dir < 0 ? lx : 3 - lx) + ly) & 1 : (lx + ly) & 1; r = spin ? 132 : 104; g = spin ? 120 : 94; b = spin ? 108 : 84; // copper rollers } else { r = 46; g = 48; b = 54; } } else if (m === PIPE) { pipeCellColor(x, y, anim); r = PC[0]; g = PC[1]; b = PC[2]; } else if (m === SHAFT) { const lx = x & 3, ly = y & 3; if (lx === 0 || lx === 3 || ly === 0 || ly === 3) { if (((x + y) & 3) === 0) { r -= 22; g -= 20; b -= 16; } } else { r -= 34; g -= 30; b -= 24; } } else if (m === GATE) { if ((x & 1) === 0 && (y & 3) !== 0) { r -= 40; g -= 24; b -= 20; } } else if (m === CUTST) { const lx = x & 3, ly = y & 3; if (ly === 1 || ly === 3 || lx === (ly < 2 ? 3 : 1)) { r -= 34; g -= 33; b -= 30; } else if (ly === 0 || ly === 2) { r += 10; g += 10; b += 9; } } else if (m === GRINDER || m === SAWB || m === PRESSB || m === RACKB) { const lx = x & 3, ly = y & 3, frame = lx === 0 || lx === 3 || ly === 0 || ly === 3; r = frame ? 96 : 38; g = frame ? 102 : 42; b = frame ? 112 : 50; } else if (m === WOOD) { if ((y & 3) === 2 || ((x + (y >> 2) * 3) & 7) === 0) { r -= 26; g -= 22; b -= 16; } } // planks else if (m === LWOOD) { if (((x * 3 + y) & 3) === 0) { r -= 30; g -= 26; b -= 18; } } // stringy fibres else if (m === HULL) { if (((x * 7 + y * 3) & 15) === 0) { r += 20; g += 20; b += 22; } } else if (m === SPRING) { if ((nz & 7) === 0) { r = 90; g = 150; b = 220; } } const al = ALPHA[m]; if (al < 1) { r = br + (r - br) * al; g = bgc + (g - bgc) * al; b = bb + (b - bb) * al; } const dp = digp[i]; if (dp) { // shaken rock pales with dust and shivers before it shatters (the cracks are drawn over it) const k = dp / 255, lift = k * 30 + Math.sin(anim * 1.9 + x * 1.3 + y * 0.7) * 6 * k; r += lift; g += lift; b += lift * 1.08; } // built wall blocks read as tiles: light top-left edge, dark bottom-right edge if (!sm && PH[m] === PH_SOLID && !(flags[i] & F_NAT) && isWall(blockP[(y >> 2) * BW + (x >> 2)])) { const lx = x & 3, ly = y & 3; const wc = blockP[(y >> 2) * BW + (x >> 2)]; const edge = isSlope(wc) && (wc === BP_SLOPE_BL || wc === BP_SLOPE_TR ? lx === ly : lx + ly === 3); if (edge || lx === 0 || ly === 0) { r += 16; g += 16; b += 16; } else if (lx === 3 || ly === 3) { r -= 18; g -= 18; b -= 18; } } } // field parts drawn over contents const sb = slopeBeltAt(i, x, y, sm); let p = part[i]; if (p === P_FILTER && (sb >= 0 || isSlopeBelt(blockP[slopeSealOwner(i)]))) p = 0; const db = diagonalScrewBlock(x, y, sm); let diagonal = false; if (db >= 0) { const v = blockV[db], joins = diagonalJoins[db]; const sd = diagonalScrewShape(x + 0.5, y + 0.5, db, v, joins); p = sm || sd <= 0 ? SCREW_PARTS[v] : 0; diagonal = !!p; if (sm) tpx[row + bx] = (joins << 24) | ((y - ((db / BW) | 0) * BS + 8) << 16) | ((x - (db % BW) * BS + 8) << 8); } if (p && sm && (IS_SCREW[p] || p === P_FILTER)) { if (m === EMPTY || PH[m] === PH_GAS) { r = 0; g = 0; b = 0; } else { r = Math.max(1, r); } } else if (p) { if (IS_SCREW[p]) { const on = netSat(i) > 0; const vert = p === P_SCREW_U || p === P_SCREW_D; const axis = diagonal ? Math.floor(diagonalAlong) : vert ? y : x; const across = diagonal ? diagonalAcross + 2 : vert ? x & 3 : y & 3; const dir = diagonal && blockV[db] >= 4 ? (blockV[db] >= 6 ? 1 : -1) : (p === P_SCREW_U || p === P_SCREW_L) ? 1 : -1; if (diagonal ? Math.abs(diagonalAcross) > 1.38 || diagonalCap > -0.55 : across === 0 || across === 3) { // copper casing on the two long sides; its load shows through if (m === EMPTY) { r = 150; g = 96; b = 64; } else { r = r * 0.72 + 44; g = g * 0.72 + 26; b = b * 0.72 + 14; } if (!diagonal && (axis & 3) === 0) { r -= 24; g -= 18; b -= 12; } } else { // screw flights: a diagonal stripe that walks along the tube when powered const ch = (((axis + across + dir * (on ? anim >> 1 : 0)) & 3) === 0); if (m === EMPTY) { r = ch ? 120 : 46; g = ch ? 112 : 46; b = ch ? 104 : 52; } else { r = r * 0.75 + (ch ? 34 : 8); g = g * 0.75 + (ch ? 30 : 8); b = b * 0.75 + (ch ? 28 : 10); } } } else if (p === P_FILTER) { const lx = x & 3, ly = y & 3, fb = (y >> 2) * BW + (x >> 2), dir = blockV[fb] & 128 ? -1 : 1; const fc = filterCfg.get(fb), fms = fc ? fc.m : null, fn = fms ? fms.length : 0, inv = fc && fc.inv; if (ly === 0) { // rollers on top, turning the way the load rides; in carry mode, striped with what rides const st = ((x + dir * (anim >> 1)) >> 1) & 1; if (m === EMPTY || st) { if (inv && fn && !st) { const c = fms[((x + dir * (anim >> 1)) >> 2 & 0xffff) % fn]; r = COLR[c] * 0.8; g = COLG[c] * 0.8; b = COLB[c] * 0.8; } else { r = st ? 92 : 56; g = st ? 96 : 58; b = st ? 104 : 66; } } } else if (ly === 3) { // the belt's return run carries on under it, so belts and filters read as one belt const st = ((x - dir * (anim >> 1)) >> 1) & 1; if (m === EMPTY || st) { r = (st ? 92 : 56) * 0.72; g = (st ? 96 : 58) * 0.72; b = (st ? 104 : 66) * 0.72; } } else if ((lx === 0 && endJoin(fb % BW, (fb / BW) | 0, 0) < 0) || (lx === 3 && endJoin(fb % BW, (fb / BW) | 0, 1) < 0)) { if (m === EMPTY || ((x + y) & 1)) { r = 64; g = 66; b = 74; } } // frame only where the line ends else if (m === EMPTY) { // mesh holes, tinted with what falls through them if ((x + y) & 1) { r = 40; g = 42; b = 48; } else if (inv) { r = 58; g = 74; b = 72; } // carry mode: everything else drops else if (fn) { const c = fms[(((x >> 1) + (y >> 1)) & 0xffff) % fn]; r = COLR[c] * 0.75; g = COLG[c] * 0.75; b = COLB[c] * 0.75; } else { r = 24; g = 25; b = 28; } } } else if (p === P_GRATE) { if ((x + y) & 1) { r = 150; g = 78; b = 58; } else if (m === EMPTY) { r *= 0.6; g *= 0.6; b *= 0.6; } } else if (p === P_GOPEN) { if ((x & 3) === 0) { r = r * 0.6 + 50; g = g * 0.6 + 30; b = b * 0.6 + 26; } } else if (p === P_INTAKE) { if (m === EMPTY) { r = 30; g = 32; b = 36; } if ((x & 1) === 0) { r += 50; g += 54; b += 60; } } else if (p === P_PIPE) { if (pipeBodyCell(x, y)) { thinPipeColor(x, y, anim); r = PC[0]; g = PC[1]; b = PC[2]; } } } const slopeArt = sb >= 0 && (m === EMPTY || PH[m] === PH_GAS || m === BELTL || m === BELTR); if (slopeArt && !sm) { const color = slopeBeltColor(sb, x, y, anim, br, bgc, bb); r = color & 255; g = color >> 8 & 255; b = color >> 16 & 255; } const slopeDetail = slopeDetailAt(i, x, y, sm); if (slopeDetail) { const wave = slopeDetail === 2 && ((x + beltGrade(blockP[slopeWallOwner]) * y - slopePhase[slopeWallOwner]) & 3) < 2; // ribs: light rubber with a pale cap; the far wall behind them: muted, corrugated rubber const cap = slopeDetail === 1 && slopeWallOwner >= 0 && !cleatMask[i - W + beltGrade(blockP[slopeWallOwner])]; r = slopeDetail === 1 ? (cap ? 178 : 146) : wave ? 64 : 56; g = slopeDetail === 1 ? (cap ? 182 : 150) : wave ? 68 : 60; b = slopeDetail === 1 ? (cap ? 190 : 158) : wave ? 76 : 68; } // incandescence if (T > 450 && m !== EMPTY && incand) { const gi = glowIndex(PH[m] === PH_SOLID ? T + nz * 0.9 : m === MAGMA ? T + Math.sin(x * 0.19 + y * 0.11 + anim * 0.03 + nz * 0.04) * 55 - 30 : T), w = GLOW_W[gi]; r = r * (1 - w) + GLOW_R[gi] * w; g = g * (1 - w) + GLOW_G[gi] * w; b = b * (1 - w) + GLOW_B[gi] * w; if (glowOn && fv >= 128 && w > 0.3 && ((bx | by) & 1) === 0) { const gk = (by >> 1) * gw + (bx >> 1), s = (w - 0.3) * 1.4; gpx[gk] = 0xff000000 | (Math.min(255, GLOW_B[gi] * s) << 16) | (Math.min(255, GLOW_G[gi] * s) << 8) | Math.min(255, GLOW_R[gi] * s); } } if (r < 0) r = 0; if (g < 0) g = 0; if (b < 0) b = 0; let top = 0xff000000; if (sm) { // what kind of thing this grain is drawn as: 0 open, 1 terrain, 2 loose grain, 3 liquid, // 4 built piece (drawn crisp), 5 smoke and flame, 6 built wall block (drawn as a tile) let cl = 0; const pt = (p === P_PIPE && !pipeBodyCell(x, y)) || (p === P_FILTER && sb >= 0) ? 0 : p; // the open parts of a pipe block show what is in them if (IS_SCREW[pt]) cl = SCREW_CLASS[pt]; else if (p === P_FILTER && sb < 0) { // filter: 13 carries left, 14 right; the tree buffer carries its colour tag (first material) and mode const fb = (y >> 2) * BW + (x >> 2), fc = filterCfg.get(fb), fm = fc && fc.m.length ? fc.m[0] : 0; cl = blockV[fb] & 128 ? 14 : 13; tpx[row + bx] = ((0x20 | (fc && fc.inv ? 0x10 : 0) | beltCaps[fb]) << 24) | (fm ? (COLB[fm] << 16) | (COLG[fm] << 8) | COLR[fm] : 0); } else if (m === BELTL || m === BELTR) { cl = m === BELTL ? 7 : 8; tpx[row + bx] = beltCaps[(y >> 2) * BW + (x >> 2)] << 24; } else if (pt || PARTMAT[m] || mmask[row + bx]) cl = 4; else if (m !== EMPTY) { const ph = PH[m]; if (ph === PH_SOLID) cl = (!(flags[i] & F_NAT) && isWall(blockP[(y >> 2) * BW + (x >> 2)])) ? 6 : 1; else if (ph === PH_POWDER) cl = 2; else if (ph === PH_LIQUID) cl = 3; else cl = 5; } const wallCode = blockP[(y >> 2) * BW + (x >> 2)]; if (isSlope(wallCode) && ((m === EMPTY && !slopeCell(wallCode, x & 3, y & 3)) || (PH[m] === PH_SOLID && !(flags[i] & F_NAT)))) { cl = wallCode; const wm = WALL_OF[blockV[(y >> 2) * BW + (x >> 2)]]; r = COLR[wm]; g = COLG[wm]; b = COLB[wm]; } if (sb >= 0 && (slopeArt || PH[m] === PH_POWDER)) { const code = blockP[sb], dir = isFilter(code) ? (blockV[sb] & 128 ? 1 : 0) : blockV[sb]; const slopeClass = 23 + (code - BP_BELT_UP) * 2 + dir; if (slopeArt) cl = slopeClass; const f = isFilter(code) && filterCfg.get(sb), fm = f && f.m.length ? f.m[(Math.floor((x + beltGrade(code) * y) / 2) & 65535) % f.m.length] : 0; tpx[row + bx] = ((slopeBeltJoins[sb] | 128) << 24) | slopeClass | ((y - ((sb / BW) | 0) * BS + 8) << 16) | ((x - (sb % BW) * BS + 8) << 8); if (slopeArt) { r = fm ? COLR[fm] : 0; g = fm ? COLG[fm] : 0; b = fm ? COLB[fm] : 0; } } if (slopeDetail && slopeWallOwner >= 0) { // ribs and the far wall are both drawn by the wall shader, ribs from the same phase as the simulation const wb = slopeWallOwner; cl = beltGrade(blockP[wb]) < 0 ? 31 : 32; tpx[row + bx] = ((128 | slopePhase[wb] | ((slopeBeltJoins[wb] & 12) << 1) | (blockV[wb] & 1 ? 32 : 0)) << 24) | cl | ((y - ((wb / BW) | 0) * BS + 8) << 16) | ((x - wb % BW * BS + 8) << 8); } top = cl << 24; fpx[row + bx] = (tb ? 0xff000000 : 0) | (cl === 5 ? 0xff0000 : 0) | (cl === 3 ? 0xff00 : 0) | (cl === 1 && !blockP[(y >> 2) * BW + (x >> 2)] && !mach[i] ? 0xff : 0); // terrain: any solid nothing owns, natural or not } if (fv < 255) { const k = fv / 255; r *= k; g *= k; b *= k; if (sm) { const c = bpx[row + bx], t = tpx[row + bx]; bpx[row + bx] = fogColor(c, k); tpx[row + bx] = fogColor(t, k); } } px[row + bx] = top | ((b > 255 ? 255 : b) << 16) | ((g > 255 ? 255 : g) << 8) | (r > 255 ? 255 : r); }