// World generation helpers: noise and removal of tiny hard rock specks. // ---------- Perlin noise ---------- function makeNoise(rng) { const perm = new Uint8Array(512), gx = new Float32Array(256), gy = new Float32Array(256); const p = []; for (let i = 0; i < 256; i++) p.push(i); for (let i = 255; i > 0; i--) { const j = Math.floor(rng() * (i + 1)); const t = p[i]; p[i] = p[j]; p[j] = t; } for (let i = 0; i < 512; i++) perm[i] = p[i & 255]; for (let i = 0; i < 256; i++) { const a = rng() * Math.PI * 2; gx[i] = Math.cos(a); gy[i] = Math.sin(a); } return function (x, y) { const xi = Math.floor(x), yi = Math.floor(y), xf = x - xi, yf = y - yi; const X = xi & 255, Y = yi & 255; const p0 = perm[Y], p1 = perm[Y + 1]; const h00 = perm[X + p0], h10 = perm[X + 1 + p0], h01 = perm[X + p1], h11 = perm[X + 1 + p1]; const n00 = gx[h00] * xf + gy[h00] * yf, n10 = gx[h10] * (xf - 1) + gy[h10] * yf; const n01 = gx[h01] * xf + gy[h01] * (yf - 1), n11 = gx[h11] * (xf - 1) + gy[h11] * (yf - 1); const u = xf * xf * xf * (xf * (xf * 6 - 15) + 10), v = yf * yf * yf * (yf * (yf * 6 - 15) + 10); return (n00 + u * (n10 - n00) + v * (n01 - n00) + u * v * (n00 - n10 - n01 + n11)) * 1.41; }; } function fbm(n, x, y, oct) { let s = 0, a = 1, f = 1, t = 0; for (let o = 0; o < oct; o++) { s += n(x * f, y * f) * a; t += a; a *= 0.5; f *= 2; } return s / t; } // Small specks of rock harder than the rock around them (a few grains of iron ore in stone, a // chip of granite, a sliver of basalt) are not worth having: a bit too weak to break them leaves // them hanging in the way. Any such piece of SPECK_MIN grains or fewer becomes the softer rock // around it. Bigger veins and pockets stay. live: the world is in play (an older save). const SPECK_MIN = 30; function clearHardSpecks(live) { const seen = new Uint8Array(N), st = new Int32Array(N), cells = new Int32Array(SPECK_MIN + 1); let n = 0; for (const h0 of [6, 5, 4]) { seen.fill(0); const mem = (i) => { const m = mat[i]; return (flags[i] & F_NAT) !== 0 && PH[m] === PH_SOLID && HARD[m] >= h0 && HARD[m] <= 6 && YLD[m] !== 0 && !PARTMAT[m]; }; const soft = (i) => { const m = mat[i]; return (flags[i] & F_NAT) !== 0 && PH[m] === PH_SOLID && HARD[m] >= 1 && HARD[m] < h0 && YLD[m] !== 0 && !PARTMAT[m]; }; for (let i0 = 0; i0 < N; i0++) { if (seen[i0] || !mem(i0)) continue; let sp = 0, sz = 0, x0 = W, y0 = H, x1 = 0, y1 = 0; const hosts = new Map(); st[sp++] = i0; seen[i0] = 1; while (sp) { const i = st[--sp], x = i % W, y = (i / W) | 0; if (sz <= SPECK_MIN) cells[sz] = i; sz++; if (x < x0) x0 = x; if (x > x1) x1 = x; if (y < y0) y0 = y; if (y > y1) y1 = y; for (const j of [x > 0 ? i - 1 : -1, x < W - 1 ? i + 1 : -1, i - W, i + W]) { if (j < 0 || j >= N || seen[j]) continue; if (mem(j)) { seen[j] = 1; st[sp++] = j; } else if (soft(j)) hosts.set(mat[j], (hosts.get(mat[j]) || 0) + 1); } } if (sz > SPECK_MIN) continue; // a speck left floating (in a cave, or dug round) takes the rock nearest round it if (!hosts.size) for (let y = Math.max(0, y0 - 5); y <= Math.min(H - 1, y1 + 5); y++) for (let x = Math.max(0, x0 - 5); x <= Math.min(W - 1, x1 + 5); x++) { const j = y * W + x; if (soft(j)) hosts.set(mat[j], (hosts.get(mat[j]) || 0) + 1); } let host = 0, best = 0; for (const [m, c] of hosts) if (c > best) { best = c; host = m; } if (!host) continue; for (let k = 0; k < sz; k++) { const i = cells[k]; if (live) setCell(i, host, temp[i], F_NAT); else mat[i] = host; } n++; } } return n; }