From fa26d71c73ad20ea619d5b8b7fb0d7eacf952f13 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 25 Oct 2026 18:47:40 +0000 Subject: [PATCH 1/8] =?UTF-8?q?=EC=8B=9C=EC=9E=91=20=E2=80=94=20=EC=97=B0?= =?UTF-8?q?=EA=B5=AC=20=ED=8E=98=EC=9D=B4=EC=A7=80=EC=97=90=20=ED=8C=A8?= =?UTF-8?q?=ED=84=B4=20=EC=97=B0=EA=B5=AC=2048=ED=8C=90=EC=9D=B4=20?= =?UTF-8?q?=EC=84=A0=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit From 0f8086dd3d06236bdfa1a11803515c54efe9c9d8 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 25 Oct 2026 19:38:24 +0000 Subject: [PATCH 2/8] =?UTF-8?q?pattern-study:=20Chebyshev's=20Bias,=20Head?= =?UTF-8?q?less=20Rider=20(48=ED=8C=90)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../pattern-study/patterns/chebyshev-bias.js | 350 +++++++++++++ .../pattern-study/patterns/headless-rider.js | 493 ++++++++++++++++++ 2 files changed, 843 insertions(+) create mode 100644 incoming/pattern-study/patterns/chebyshev-bias.js create mode 100644 incoming/pattern-study/patterns/headless-rider.js diff --git a/incoming/pattern-study/patterns/chebyshev-bias.js b/incoming/pattern-study/patterns/chebyshev-bias.js new file mode 100644 index 000000000..2abacff19 --- /dev/null +++ b/incoming/pattern-study/patterns/chebyshev-bias.js @@ -0,0 +1,350 @@ +// ===== Patternflow pattern ===== +// Title: Chebyshev's Bias +// Family: number +// Idea: The panel's height is the number line from 32 at the bottom to 2^21 at the top on a log scale and its 64 columns are 64 prime races counted by the pattern's own sieve; a white front climbs and paints each LED blue where the side Chebyshev expected leads, brighter the larger its lead in Rubinstein and Sarnak's units, and orange where the other side leads, so the field fills with the bias and the rare orange specks stand out, the first for 1 mod 4 over 3 mod 4 at 26,861, each long-awaited one sending a ring across the panel as the front passes it. +// Author: patternflow-lab +// Date: 2026-10-25 +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Lineage: Chebyshev's bias (Pafnuty Chebyshev, 1853), with Rubinstein and Sarnak's logarithmic density (1994), pattern round 48 +// Round: 48 +// =============================== +// @matrix 64x128 +// @knobs Hue=0..1, Speed=0.2..3, Races=0..2, Margin=0..1 +// +// Knob 1: Hue. Turns the three colours round the wheel together. At 0 the leading side is blue, the specks where the other side leads orange and the close calls violet; at 0.25 magenta, green and orange; at 0.5 orange, azure and green, the orange paled so the weak leads do not read brown; at 0.75 green, magenta and azure. +// Knob 2: Speed. The front climbs 6.4 rows a second for each unit, 12.8 at 2, so x doubles every 0.63 s: it passes 26,861 at 6.1 s and 616,841 at 8.9 s and reaches 2^21 at 10 s, the field holds 2.5 s and dims back to the ghost of itself over 1.2 s, and the front starts again from 32. At 0.2 all ten times slower, a climb of 100 s; at 3 a climb of 6.7 s. +// Knob 3: Races. Which 64 races, the columns ordered by the mean lead the theory expects, largest at the left. At 0 the quadratic non-residues against the residues for every modulus from 3 to 66, each side averaged over its classes: first the five where 1 has eight square roots (24, 40, 48, 56, 60), brightest and never led by the other side, then the 24 with four, of which only 7 are ever led, then the 35 with two, from 3 and 4 on, where the orange gathers and 43 has the other side ahead on 42% of the line. At 1 the class -1 against the class 1 for the same moduli: where -1 is not a square it leans like the race at 0, and the 15 moduli at the right where it is one (5, 10, 13, 17, 25 and on) are fair races, barcodes of blue and orange. At 2 non-residues against residues for the 64 odd primes from 3 to 313 in order: the orange thickens as the modulus grows, in 16% of the rows of the first sixteen columns and a third of those of the last thirty-two. A new choice is counted in under a second. +// Knob 4: Margin. How near a tie a lead must come to show as a close call in violet, in Rubinstein and Sarnak's units where the mod 4 lead averages 1: 0 at 0, 0.12 at the opening 0.06, 0.5 at a quarter, 1 at half, where most of the races with two square roots of 1 have turned violet, and 2 at 1, where only the five with eight keep much blue. +// Buttons: K1 tap starts the climb again from 32. + +const W = 64, H = 128; +const { floor, round, min, max, sqrt, log, exp, sin, cos, abs, PI } = Math; +const cl = (v, a, b) => (v < a ? a : v > b ? b : v); + +// The number line: row r from the bottom covers x in [B[r], B[r + 1]), eight +// rows to each doubling, from 32 to 32 * 2^16 = 2^21 = 2,097,152. +const RPD = 8, X0 = 32, X1 = 1 << 21; +const B = new Float64Array(H + 1); +for (let r = 0; r <= H; r++) B[r] = round(X0 * 2 ** (r / RPD)); +// Rubinstein and Sarnak's normaliser log x / sqrt x at each row's middle. +const NRM = new Float64Array(H); +for (let r = 0; r < H; r++) { + const xm = X0 * 2 ** ((r + 0.5) / RPD); + NRM[r] = log(xm) / sqrt(xm); +} + +// The pattern's own sieve of Eratosthenes up to 2^21, once, on the odd +// numbers only: SV[k] marks 2k + 1 composite. 155,611 primes. +const SV = new Uint8Array((X1 >> 1) + 1); +for (let i = 3; i * i <= X1; i += 2) if (!SV[i >> 1]) for (let j = (i * i) >> 1; j <= X1 >> 1; j += i) SV[j] = 1; +const PT = new Int32Array(160000); +let NP = 1; +PT[0] = 2; +for (let k = 1; 2 * k + 1 < X1; k++) if (!SV[k]) PT[NP++] = 2 * k + 1; +const PR = PT.subarray(0, NP); + +const gcd = (a, b) => { + while (b) [a, b] = [b, a % b]; + return a; +}; +// A race mod q: a weight for each class, plus for the side expected to lead +// and minus for the other, so D = sum of weights over primes <= x is the +// race. K turns D into the lead L = K D log x / sqrt x, scaled so that L +// averages s / 2 when s is the number of square roots of 1 mod q: 1 for +// 3 mod 4 against 1 mod 4, as in Rubinstein and Sarnak. +function raceNR(q) { + const T = new Int32Array(q), sq = new Uint8Array(q); + let phi = 0, nr = 0, s = 0; + for (let a = 1; a < q; a++) + if (gcd(a, q) === 1) { + phi++; + sq[(a * a) % q] = 1; + if ((a * a) % q === 1) s++; + } + for (let a = 1; a < q; a++) if (gcd(a, q) === 1 && sq[a]) nr++; + const nn = phi - nr; + // Weights |R| for a non-residue, -|N| for a residue: the race of the two + // sides' averages per class. + for (let a = 1; a < q; a++) if (gcd(a, q) === 1) T[a] = sq[a] ? -nn : nr; + return { q, T, K: phi / (2 * nn * nr), s, mean: s / 2 }; +} +// The class q - 1 (that is -1) against the class 1: biased like the race +// above when -1 is not a square mod q, fair when it is. +function racePair(q) { + const T = new Int32Array(q), r = raceNR(q); + let phi = 0; + for (let a = 1; a < q; a++) if (gcd(a, q) === 1) phi++; + T[q - 1] = 1; + T[1] = -1; + return { q, T, K: phi / 2, s: r.s, mean: r.T[q - 1] > 0 ? r.s / 2 : 0 }; +} +// Columns left to right by the mean lead the theory expects, largest +// first, then by modulus. +const order = (a, b) => b.mean - a.mean || a.q - b.q; +const ODD = []; +for (let k = 1; ODD.length < W; k++) ODD.push(PR[k]); +const FAMS = [ + Array.from({ length: W }, (_, c) => raceNR(c + 3)).sort(order), + Array.from({ length: W }, (_, c) => racePair(c + 3)).sort(order), + ODD.map(raceNR), +]; + +// Hues at knob 0: the leading side blue, the other side's specks orange, +// close calls violet. +const HUE_LEAD = 0.6, HUE_SPECK = 0.085, HUE_CLOSE = 0.8; +// Front speed in rows a second for each unit of Speed; the hold at the top +// and the fade back to the ghost, in seconds at Speed 2. Above the front the +// last count shows at GHOST of its brightness. +const RATE = 6.4, HOLD = 2.5, FADE = 1.2, GHOST = 0.3; +// Primes the count takes each frame: 26 frames for the whole count. +const STEPS = 6000; +// A ring goes out when the other side takes the lead after the expected +// side has held it for RING rows, x grown 16 times over. +const RING = 32; + +function hsv(h, s, v, out, o) { + h = (h - floor(h)) * 6; + const i = floor(h) % 6, f = h - floor(h), p = v * (1 - s), q = v * (1 - f * s), t = v * (1 - (1 - f) * s); + out[o] = i === 0 || i === 5 ? v : i === 1 ? q : i === 4 ? t : p; + out[o + 1] = i === 1 || i === 2 ? v : i === 0 ? t : i === 3 ? q : p; + out[o + 2] = i === 3 || i === 4 ? v : i === 2 ? t : i === 5 ? q : p; +} + +// One count of a family's 64 races from 2 to 2^21. Per LED, row-major from +// the bottom row: the share of the row's x where the other side led, the +// share tied, the mean lead and the least lead; and every time the other +// side took the lead, with how many rows the expected side had held it. +// The classes' weights sit in one table, column c's from OFF[c]. +function newCount(f) { + const races = FAMS[f], OFF = new Int32Array(W); + let n = 0; + for (let c = 0; c < W; c++) (OFF[c] = n), (n += races[c].q); + const TT = new Int32Array(n); + for (let c = 0; c < W; c++) TT.set(races[c].T, OFF[c]); + const C = { + Q: Int32Array.from(races, (r) => r.q), KK: Float64Array.from(races, (r) => r.K), OFF, TT, + D: new Int32Array(W), pos: X0, pi: 0, row: 0, + neg: new Float64Array(W), tie: new Float64Array(W), sum: new Float64Array(W), mn: new Int32Array(W), + lastNeg: new Int16Array(W).fill(-1), + NEG: new Float32Array(W * H), TIE: new Float32Array(W * H), ML: new Float32Array(W * H), MINL: new Float32Array(W * H), + ev: [], + }; + // The primes below 32 are in before the first row opens. + for (; PR[C.pi] < X0; C.pi++) for (let c = 0; c < W; c++) C.D[c] += TT[OFF[c] + (PR[C.pi] % C.Q[c])]; + openRow(C); + return C; +} + +function openRow(C) { + C.neg.fill(0); + C.tie.fill(0); + C.sum.fill(0); + C.mn.set(C.D); +} + +// Between two primes the race stands still, so each stretch to the next +// prime or row boundary is added to the row's tallies whole. +function tally(C, len) { + const { D, neg, tie, sum } = C; + for (let c = 0; c < W; c++) { + const d = D[c]; + if (d < 0) neg[c] += len; + else if (d === 0) tie[c] += len; + sum[c] += d * len; + } +} + +function closeRow(C) { + const r = C.row, len = B[r + 1] - B[r]; + for (let c = 0, o = r * W; c < W; c++, o++) { + const k = C.KK[c] * NRM[r]; + C.NEG[o] = C.neg[c] / len; + C.TIE[o] = C.tie[c] / len; + C.ML[o] = (k * C.sum[c]) / len; + C.MINL[o] = k * C.mn[c]; + if (C.neg[c] > 0) C.lastNeg[c] = r; + } + C.row++; + openRow(C); +} + +// Runs the count on by at most n primes: the whole count is 155,611 +// primes and 128 rows, about 10 million small steps for the 64 races. +function step(C, n) { + const { D, Q, OFF, TT, neg, tie, sum, mn } = C; + while (C.row < H && n-- > 0) { + const p = C.pi < NP ? PR[C.pi] : X1 + 1, b = B[C.row + 1]; + // A prime on a row boundary belongs to the row above. + if (b <= p) { + tally(C, b - C.pos); + C.pos = b; + closeRow(C); + continue; + } + const len = p - C.pos; + for (let c = 0; c < W; c++) { + const d = D[c]; + if (d < 0) neg[c] += len; + else if (d === 0) tie[c] += len; + sum[c] += d * len; + const w = TT[OFF[c] + (p % Q[c])]; + if (!w) continue; + const e = d + w; + D[c] = e; + if (e < mn[c]) mn[c] = e; + if (d >= 0 && e < 0) { + C.ev.push({ c, x: p, run: C.row - C.lastNeg[c] }); + C.lastNeg[c] = C.row; + } + } + C.pos = p; + C.pi++; + } +} + +// The front's height for x, in rows from the bottom. +const rowOf = (x) => RPD * Math.log2(x / X0); + +export function setup(params) { + params.s = { + hue: 0, speed: 2, fam: -1, margin: 0.12, cnt: [null, null, null], C: null, + F: 0, phase: 0, timer: 0, alpha: 1, t: 0, next: 0, skip: 0, + bursts: [], buf: new Float32Array(W * H * 3), col: new Float32Array(12), lut: new Float32Array(99), + }; +} + +function replay(S) { + S.F = 0; + S.skip = 0; + S.phase = 0; + S.alpha = 1; + S.next = 0; +} + +export function update(dt, input, params) { + if (!params.s) setup(params); + const S = params.s, kv = input.knobValues || [0, 2, 0, 0.06], d = cl(dt || 0, 0, 0.1); + S.hue = cl(kv[0], 0, 1); + S.speed = cl(kv[1], 0.2, 3); + S.margin = 2 * cl(kv[3], 0, 1); + const f = round(cl(kv[2], 0, 2)); + if (f !== S.fam) { + // Each family is counted once, the first time it is asked for. + S.fam = f; + S.C = S.cnt[f] || (S.cnt[f] = newCount(f)); + S.bursts.length = 0; + S.next = 0; + // A count already done has its lead changes below the front passed; + // a new one finds them as it goes, and they pass without a ring. + while (S.next < S.C.ev.length && rowOf(S.C.ev[S.next].x) < S.F) S.next++; + S.skip = S.F; + } + // The count runs ahead of the front, done in under a second. + step(S.C, STEPS); + const ds = (d * S.speed) / 2; + S.t += ds; + if (input.btnPressed && input.btnPressed[0]) replay(S); + if (S.phase === 0) { + S.F = min(H, S.F + d * RATE * S.speed); + // The lead changes the front has just passed. + const ev = S.C.ev; + while (S.next < ev.length && rowOf(ev[S.next].x) < S.F) { + const e = ev[S.next++]; + // A flip back and forth inside one lead change is not a new one. + if (e.run < 3 || (e.run < RING && S.bursts.length > 12) || rowOf(e.x) < S.skip) continue; + S.bursts.push({ c: e.c, r: floor(rowOf(e.x)), t: 0, run: e.run }); + } + if (S.F >= H) (S.phase = 1), (S.timer = HOLD); + } else if (S.phase === 1) { + if ((S.timer -= ds) < 0) (S.phase = 2), (S.timer = FADE); + } else { + S.timer -= ds; + S.alpha = max(0, S.timer / FADE); + if (S.timer < 0) replay(S); + } + for (const b of S.bursts) b.t += ds; + S.bursts = S.bursts.filter((b) => b.t < 1.6); +} + +export function draw(display, params) { + const S = params.s; + if (!S || !S.C) return; + const buf = S.buf, K = S.col, Cn = S.C, m = S.margin, F = S.F; + buf.fill(0); + hsv(HUE_SPECK + S.hue, 0.78, 1, K, 0); + hsv(HUE_CLOSE + S.hue, 0.55, 0.78, K, 3); + // The lead side in 33 steps of u = 1 - exp(-L / 1.6), brighter and paler + // the larger the mean lead L. Turned to orange or yellow it starts paler + // and brighter, or the weak leads would read brown. + const lut = S.lut, hl = HUE_LEAD + S.hue, wf = max(0, cos(2 * PI * (hl - 0.1))) ** 3; + for (let i = 0; i <= 32; i++) { + const u = i / 32; + hsv(hl, 0.92 - 0.3 * u * u - 0.45 * wf, 0.2 + 0.55 * wf + (0.68 - 0.4 * wf) * u, lut, i * 3); + } + for (let r = 0; r < Cn.row; r++) { + const y = H - 1 - r; + // Painted below the front, the ghost above it; freshly painted rows + // glow white for a moment under the front. + const lit = r < F ? GHOST + (1 - GHOST) * S.alpha : GHOST; + const gl = S.phase === 0 && r < F ? exp(-(F - r - 1) / 1.6) * 0.55 : 0; + for (let c = 0; c < W; c++) { + const o = r * W + c, q = (y * W + c) * 3; + let R, G, Bl; + if (Cn.NEG[o] > 0) { + // The other side led here: a speck that never dims below 0.8. + const v = (0.8 + 0.2 * sqrt(Cn.NEG[o])) * (0.88 + 0.12 * sin(S.t * 3 + c * 1.7 + r * 0.9)); + (R = K[0] * v), (G = K[1] * v), (Bl = K[2] * v); + } else if (Cn.TIE[o] >= 1) { + // Tied all along the row: neither leads. + (R = 0.24), (G = 0.24), (Bl = 0.28); + } else if (Cn.MINL[o] < m) { + (R = K[3]), (G = K[4]), (Bl = K[5]); + } else { + const i = round(32 * (1 - exp(-max(0, Cn.ML[o]) / 1.6))) * 3; + (R = lut[i]), (G = lut[i + 1]), (Bl = lut[i + 2]); + } + if (gl > 0.01) (R += (1 - R) * gl), (G += (1 - G) * gl), (Bl += (1 - Bl) * gl); + buf[q] = R * lit; + buf[q + 1] = G * lit; + buf[q + 2] = Bl * lit; + } + } + // The front: a white row tinted by who leads there. + if (S.phase === 0 && F < H) { + const r = floor(F), y = H - 1 - r; + hsv(HUE_LEAD + S.hue, 0.5, 1, K, 9); + for (let c = 0; c < W; c++) { + const q = (y * W + c) * 3, k = Cn.NEG[r * W + c] > 0 ? 0 : 9; + buf[q] = 0.5 + 0.5 * K[k]; + buf[q + 1] = 0.5 + 0.5 * K[k + 1]; + buf[q + 2] = 0.5 + 0.5 * K[k + 2]; + } + } + // Each new lead change flashes white; a long-awaited one sends out a ring + // in the specks' colour, the wider the longer the wait. + for (const b of S.bursts) { + const cx = b.c + 0.5, cy = H - 1 - b.r + 0.5; + if (b.t < 0.35) { + const q = ((H - 1 - b.r) * W + b.c) * 3; + buf[q] = buf[q + 1] = buf[q + 2] = 1; + } + if (b.run < RING) continue; + const big = min(1, (b.run - RING + 8) / 40), rad = 1 + b.t * (6 + 12 * big), f = (0.4 + 0.6 * big) * (1 - b.t / 1.6); + const y0 = max(0, floor(cy - rad - 1)), y1 = min(H - 1, floor(cy + rad + 1)); + for (let y = y0; y <= y1; y++) + for (let x = max(0, floor(cx - rad - 1)); x <= min(W - 1, floor(cx + rad + 1)); x++) { + const dd = abs(sqrt((x + 0.5 - cx) ** 2 + (y + 0.5 - cy) ** 2) - rad); + if (dd >= 1.2) continue; + const q = (y * W + x) * 3, w = f * (1 - dd / 1.2); + buf[q] += (K[0] - buf[q]) * w; + buf[q + 1] += (K[1] - buf[q + 1]) * w; + buf[q + 2] += (K[2] - buf[q + 2]) * w; + } + } + for (let p = 0, q = 0; p < W * H; p++, q += 3) { + const r = buf[q], g = buf[q + 1], b = buf[q + 2]; + if (r + g + b < 0.02) continue; + // Each channel in steps of 4 of 255. + display.setPixel(p % W, (p / W) | 0, min(255, round(r * 63.75) * 4), min(255, round(g * 63.75) * 4), min(255, round(b * 63.75) * 4)); + } +} diff --git a/incoming/pattern-study/patterns/headless-rider.js b/incoming/pattern-study/patterns/headless-rider.js new file mode 100644 index 000000000..6e4b76429 --- /dev/null +++ b/incoming/pattern-study/patterns/headless-rider.js @@ -0,0 +1,493 @@ +// ===== Patternflow pattern ===== +// Title: Headless Rider +// Family: halloween +// Idea: A headless horseman gallops across a huge low moon in black silhouette, the horse gathering and reaching through a four-beat gallop that kicks dust and sparks off the road, cape streaming and a burning jack-o'-lantern held high, while trees, gravestones and a fence slide past, and now and then he hurls the pumpkin in a flaming arc that bursts on the road ahead. +// Author: patternflow-lab +// Date: 2026-10-25 +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Lineage: original, pattern round 48 +// Round: 48 +// =============================== +// @matrix 64x128 +// @knobs Hue=0..1, Speed=0.2..3, Woods=0..1, Mist=0..1 +// +// Knob 1: Hue. Tints the sky, the far hills and the mist; moon, pumpkin and fire keep their colours. At 0 a violet night, mauve at the horizon; at 0.25 blood red, at 0.5 a sickly olive, at 0.75 teal, at 0.9 deep blue. +// Knob 2: Speed. At 2 the horse takes 1.3 strides a second, the road runs past at 44 LEDs a second, the trees at 13 and the hills at 4, and he throws every 9 s, the first at 3.2 s, the pumpkin 3 s in the air; at 0.2 all ten times slower, at 3 half as fast again. +// Knob 3: Woods. How many of the 14-LED slots along the horizon hold a tree: one in five at 0, a tree about every 5 s at Speed 2 with gravestones between; more than half at 0.5; nearly all at 1, a wood the moon shows through in pieces. +// Knob 4: Mist. At 0 none, at 0.06 thin wisps on the horizon, at 0.5 a moonlit bank on the road round the hooves drifting back faster than the road, at 1 up to the horse's belly so the legs gallop half seen. +// Buttons: K1 tap throws the pumpkin at once, or lights a new one if his hand is empty. + +const W = 64, H = 128, N = W * H; +const { floor, round, ceil, min, max, abs, sqrt, sin, cos, exp, PI } = Math; +const TAU = 2 * PI; +const cl = (v, a, b) => (v < a ? a : v > b ? b : v); +const rnd = (a, b) => a + (b - a) * Math.random(); +const hash = (i) => { + let h = Math.imul(i ^ 0x9e3779b9, 0x85ebca6b); + h = Math.imul(h ^ (h >>> 13), 0xc2b2ae35); + return ((h ^ (h >>> 16)) >>> 0) / 4294967296; +}; +const vn = (x, sd) => { + const i = floor(x), f = x - i, u = f * f * (3 - 2 * f); + return (hash(i * 7919 + sd) * (1 - u) + hash((i + 1) * 7919 + sd) * u) * 2 - 1; +}; +function hsv(h, s, v, out) { + h = (h - floor(h)) * 6; + const i = floor(h) % 6, f = h - floor(h), p = v * (1 - s), q = v * (1 - f * s), t = v * (1 - (1 - f) * s); + out[0] = (i === 0 || i === 5 ? v : i === 1 ? q : i === 4 ? t : p) * 255; + out[1] = (i === 1 || i === 2 ? v : i === 0 ? t : i === 3 ? q : p) * 255; + out[2] = (i === 3 || i === 4 ? v : i === 2 ? t : i === 5 ? q : p) * 255; + return out; +} + +// The stage, top to bottom: sky and moon, the horizon the trees stand on, +// the road the hooves strike at GY, the verge with the near fence. +const HZ = 99, RD0 = 100, RD1 = 108, GY = 104; +const MX = 26, MY = 70, MR = 24; +// The moon's tone (1 limb, 2 sea, 3 bright) and its glow in stepped rings. +const MOONT = new Uint8Array(N), GLOW = new Float32Array(N); +const SEAS = [[-7, -6, 6, 5], [5, -9, 5, 4], [-3, 6, 8, 4], [9, 5, 4, 5]]; +for (let p = 0; p < N; p++) { + const dx = (p % W) + 0.5 - MX, dy = floor(p / W) + 0.5 - MY, r = sqrt(dx * dx + dy * dy); + if (r < MR) MOONT[p] = r > MR - 1.2 ? 1 : SEAS.some(([cx, cy, rx, ry]) => ((dx - cx) / rx) ** 2 + ((dy - cy) / ry) ** 2 < 1) ? 2 : 3; + else GLOW[p] = r < MR + 3 ? 0.3 : r < MR + 8 ? 0.17 : r < MR + 15 ? 0.08 : r < MR + 26 ? 0.035 : 0; +} +const MOONC = [0, [232, 216, 180], [228, 216, 188], [252, 240, 204]]; +const STARS = []; +for (let i = 0; i < 400 && STARS.length < 26; i++) { + const x = floor(hash(i * 3 + 1) * W), y = floor(hash(i * 3 + 2) * 62); + if (GLOW[y * W + x] === 0 && !MOONT[y * W + x]) STARS.push([x, y, hash(i * 3 + 3) * TAU]); +} + +// A seeded sequence for the scenery, so it is the same every time. +let sd = 48; +const rng = () => hash(sd++ * 31); +// Fills a capsule, radius r0 at one end and r1 at the other, into a mask. +function stamp(m, mw, mh, x0, y0, r0, x1, y1, r1, code) { + const xa = max(0, floor(min(x0 - r0, x1 - r1))), xb = min(mw - 1, ceil(max(x0 + r0, x1 + r1))); + const ya = max(0, floor(min(y0 - r0, y1 - r1))), yb = min(mh - 1, ceil(max(y0 + r0, y1 + r1))); + const dx = x1 - x0, dy = y1 - y0, l2 = dx * dx + dy * dy || 1e-9; + for (let y = ya; y <= yb; y++) + for (let x = xa; x <= xb; x++) { + const px = x + 0.5 - x0, py = y + 0.5 - y0, t = cl((px * dx + py * dy) / l2, 0, 1); + const ex = px - dx * t, ey = py - dy * t, r = r0 + (r1 - r0) * t; + if (ex * ex + ey * ey <= r * r && m[y * mw + x] < code) m[y * mw + x] = code; + } +} +// Bare trees: a crooked trunk forking four times into twigs. +const TW = 44, TH = 96, NT = 6, TREES = []; +function limb(m, x, y, a, len, w, depth) { + for (let i = 0, n = max(2, ceil(len / 2)); i < n; i++) { + a += (rng() - 0.5) * 0.4; + const nx = x + 2 * sin(a), ny = y - 2 * cos(a), r = max(0.45, w * (1 - (0.35 * i) / n)); + stamp(m, TW, TH, x, y, r, nx, ny, r, 1); + (x = nx), (y = ny); + } + if (depth <= 0) return; + const k = depth > 2 ? 2 : 2 + (rng() < 0.5 ? 1 : 0); + for (let j = 0; j < k; j++) limb(m, x, y, a + (j - (k - 1) / 2) * (0.55 + rng() * 0.5) + (rng() - 0.5) * 0.3, len * (0.55 + rng() * 0.25), w * 0.62, depth - 1); +} +// Each kept as the list of its lit LEDs, column then row. +for (let i = 0; i < NT; i++) { + const m = new Uint8Array(TW * TH), l = []; + limb(m, TW / 2, TH - 1, (rng() - 0.5) * 0.25, (54 + rng() * 34) * 0.36, 2.2 + rng() * 0.6, 4); + m.forEach((v, p) => v && l.push(p % TW, floor(p / TW))); + TREES.push(Int16Array.from(l)); +} +const TREEC = [26, 10, 36], FARLEG = [30, 12, 40]; +// The road's crown, ruts and dust; the verge's tufts and soil. +const GROUND = [92, 78, 108, 44, 34, 58, 66, 54, 82, 46, 30, 60, 24, 12, 34, 16, 8, 24]; +const STONES = [[".xxx.", "xxxxx", "xxxxx", "xxxxx", "xxxxx", "xxxxx"], ["..x..", "xxxxx", "..x..", "..x..", "..x..", "..x.."], [".xx.", "xxxx", "xxxx", "xxxx", "xxxx"]]; +// Mist: a sheet of value noise, long bands across short ones. +const FW = 256, FH = 32, FN = new Float32Array(FW * FH); +for (let p = 0; p < FW * FH; p++) FN[p] = 0.5 + 0.32 * vn((p % FW) / 14 + (p >> 10) * 0.3, p >> 10) + 0.18 * vn((p % FW) / 5, (p >> 8) + 99); + +// The gallop. A hoof is down for DUTY of a stride, landing REACH LEDs ahead +// of its leg's top joint and leaving BACK behind, so the road runs back +// (REACH + BACK) / DUTY = 34 LEDs a stride and a planted hoof never slides. +// A transverse gallop's four beats: near hind, far hind, near fore, far +// fore, then all four off the ground, gathered under the belly. +// [joint x, joint y in the body, landing phase, far, fore] +const DUTY = 0.38, REACH = 7, BACK = 6, STRIDE = (REACH + BACK) / DUTY, L1 = 11, L2 = 14, RATE = 1.3; +const LEGS = [[-10, 2.5, 0, 0, 0], [-10, 2.5, 0.11, 1, 0], [10, 3, 0.38, 0, 1], [10, 3, 0.49, 1, 1]]; +const BX = 29, BY = 76, PK = 4.6; +// The throwing arm [shoulder, elbow], radians from straight up, forward +// positive: held high, cocked back, the release, hanging empty. +const HOLD = [0.35, 0.1], COCK = [-0.85, -0.5], LET = [1.05, 0.15], REST = [2.3, -0.5]; +// Times in seconds at Speed 2. +const THROW_EVERY = 9, FIRST = 3.2, T_COCK = 0.35, T_SWING = 0.15, T_RELIGHT = 1.5, T_GROW = 0.55; +// Particles, six numbers each (x, y, vx, vy, life left, life) and a kind: +// 0 flame, 1 spark, 2 dust. +const MAXP = 600; + +export function setup(params) { + params.s = { + hue: -1, hue0: -2, speed: 2, woods: 0, mist: 0.06, t: 0, ph: 0, g: 0, bob: 0, pitch: 0, neck: 0, eye: [0, 0], + state: 0, st: 0, next: FIRST, pk: 1, arm: HOLD.slice(), arm0: HOLD.slice(), fly: null, fires: [], + A: new Float32Array(MAXP * 6), K: new Uint8Array(MAXP), np: 0, + buf: new Float32Array(N * 3), mask: new Uint8Array(N), bg: new Float32Array(N * 3), far: [0, 0, 0], fog: [0, 0, 0], c: [0, 0, 0], + }; +} + +function spawn(S, x, y, vx, vy, life, kind) { + if (S.np >= MAXP) return; + S.A.set([x, y, vx, vy, life, life], S.np * 6); + S.K[S.np++] = kind; +} + +// A point of the horse's body on the panel, bobbing and pitching about +// the middle of the barrel. +function bodyPt(S, x, y, out, o) { + const c = cos(S.pitch), s = sin(S.pitch); + out[o] = BX + x * c - y * s; + out[o + 1] = BY + S.bob + x * s + y * c; +} +const P = new Float32Array(4), HAND = new Float32Array(6), F = new Float32Array(2); + +// Shoulder, elbow and hand of the throwing arm. +function hand(S) { + bodyPt(S, 4, -20, HAND, 0); + const a = S.arm[0] + S.pitch * 0.5, b = a + S.arm[1]; + HAND[2] = HAND[0] + 7.5 * sin(a); + HAND[3] = HAND[1] - 7.5 * cos(a); + HAND[4] = HAND[2] + 7.5 * sin(b); + HAND[5] = HAND[3] - 7.5 * cos(b); +} + +// A hoof at phase u after it landed, from its leg's top joint (jx, jy). +function foot(u, fore, jx) { + if (u < DUTY) (F[0] = jx + REACH - (REACH + BACK) * (u / DUTY)), (F[1] = GY); + else { + const s = (u - DUTY) / (1 - DUTY); + F[0] = jx - BACK + (REACH + BACK) * (0.5 - 0.5 * cos(PI * s)); + // A foreleg folds its hoof up towards the elbow, a hind leg less. + F[1] = GY - (fore ? 13 : 9) * sin(PI * s) ** 0.8; + } +} + +const ease = (a, b, t) => a + (b - a) * (t * t * (3 - 2 * t)); +const toward = (S, to, t) => (S.arm = [ease(S.arm0[0], to[0], t), ease(S.arm0[1], to[1], t)]); +const go = (S, st) => ((S.state = st), (S.st = 0), (S.arm0 = S.arm.slice())); + +export function update(dt, input, params) { + if (!params.s) setup(params); + const S = params.s, k = input.knobValues || [0, 2, 0, 0.06], d = cl(dt || 0, 0, 0.1); + [S.hue, S.speed, S.woods, S.mist] = [cl(k[0], 0, 1), cl(k[1], 0.2, 3), cl(k[2], 0, 1), cl(k[3], 0, 1)]; + const ds = (d * S.speed) / 2, V = STRIDE * RATE, ph0 = S.ph; + S.t += ds; + S.ph += ds * RATE; + S.g = S.ph * STRIDE; + // The body: lowest mid-stance, highest in the gathered moment, nose up as + // the hind legs land and down as the fore legs take the weight. + const a = TAU * S.ph; + S.bob = 1.2 * cos(a - TAU * 0.45); + S.pitch = -0.055 * cos(a - TAU * 0.1); + S.neck = 0.13 * cos(a - TAU * 0.62); + // A hoof landing kicks up dust, and now and then sparks off a stone. + for (const [jx, jy, off] of LEGS) { + if (floor(ph0 - off) === floor(S.ph - off)) continue; + bodyPt(S, jx, jy, P, 0); + foot(0, 0, P[0]); + for (let i = 0; i < 3; i++) spawn(S, F[0] + rnd(-1, 1), GY + rnd(-1, 1), -V * rnd(0.8, 1), -rnd(4, 14), rnd(0.5, 0.9), 2); + if (Math.random() < 0.4) for (let i = 0; i < 3; i++) spawn(S, F[0], GY - 1, -V + rnd(-20, 25), -rnd(20, 48), rnd(0.25, 0.5), 1); + } + // The pumpkin: held, cocked back, swung, let go; the hand hangs empty, + // rises, and a new one catches light in it. + const tap = input.btnPressed && input.btnPressed[0]; + S.st += ds; + if (S.state === 0) { + if ((S.next -= ds) <= 0 || tap) go(S, 1); + } else if (S.state === 1) { + toward(S, COCK, min(1, S.st / T_COCK)); + if (S.st >= T_COCK) go(S, 2); + } else if (S.state === 2) { + toward(S, LET, min(1, S.st / T_SWING)); + if (S.st >= T_SWING) { + hand(S); + // Up and a little ahead: the top of the arc near the top of the panel, + // down on the road just ahead of the horse 3 s later. + S.fly = { x: HAND[4] + 0.5, y: HAND[5] - PK, vx: 6.5, vy: -58, spin: 0 }; + S.pk = 0; + go(S, 3); + } + } else if (S.state === 3) { + toward(S, REST, min(1, S.st / 0.45)); + if (S.st > T_RELIGHT || tap) go(S, 4); + } else { + toward(S, HOLD, min(1, S.st / 0.4)); + S.pk = cl((S.st - 0.3) / T_GROW, 0, 1); + if (S.st > 0.3 + T_GROW) (S.state = 0), (S.next = THROW_EVERY); + } + // Flames stream back off the pumpkin in the rush of air. + const drift = -0.45 * V - 12; + if (S.pk > 0 || S.state === 4) { + hand(S); + const r = PK * S.pk; + for (let i = 0; i < 1 + S.pk * 3; i++) spawn(S, HAND[4] + rnd(-r, r) * 0.6, HAND[5] - 2 * r + rnd(-1, 1), drift * rnd(0.6, 1.1), -rnd(11, 23), rnd(0.15, 0.32), 0); + } + if (S.fly) { + const f = S.fly; + f.vy += 52 * ds; + f.x += f.vx * ds; + f.y += f.vy * ds; + f.spin += ds * 5; + for (let i = 0; i < 4; i++) spawn(S, f.x + rnd(-2, 2), f.y + rnd(-2, 2), drift * rnd(0.7, 1.2) - V * 0.3, rnd(-14, 4), rnd(0.25, 0.55), 0); + if (f.y >= GY - 3) { + // It bursts on the road, which carries the fire back past the horse. + S.fires.push({ x: f.x, t: 0 }); + for (let i = 0; i < 18; i++) spawn(S, f.x, GY - 2, -V + rnd(-40, 40), -rnd(15, 60), rnd(0.4, 0.9), 1); + S.fly = null; + } + } + S.fires = S.fires.filter((fi) => { + fi.x -= V * ds; + fi.t += ds; + if (fi.t < 1.6) for (let i = 0; i < 3; i++) spawn(S, fi.x + rnd(-3, 3), GY - 1, -V + drift * 0.3, -rnd(15, 35), rnd(0.2, 0.45), 0); + return fi.x > -10; + }); + // Particles: flames rise and thin, sparks fall and bounce, dust settles. + const A = S.A; + let j = 0; + for (let i = 0; i < S.np; i++) { + const o = i * 6, kind = S.K[i]; + if ((A[o + 4] -= ds) <= 0) continue; + if (kind === 1) A[o + 3] += 140 * ds; + else if (kind === 2) A[o + 3] *= 1 - ds * 2; + A[o] += A[o + 2] * ds; + A[o + 1] += A[o + 3] * ds; + if (kind === 1 && A[o + 1] > GY + 1) (A[o + 1] = GY + 1), (A[o + 3] *= -0.3), (A[o + 2] = -V); + if (A[o] < -4 || A[o] > W + 6 || A[o + 1] < -4) continue; + if (j !== i) A.copyWithin(j * 6, o, o + 6), (S.K[j] = kind); + j++; + } + S.np = j; +} + +// The sky with the moon and its glow, the far hills and the mist for a +// hue, once each time it changes. +function palette(S) { + const h = 0.75 + S.hue, c = S.c; + for (let p = 0; p < (HZ + 1) * W; p++) { + const y = floor(p / W), t = min(1, floor((y / HZ) * 10) / 9), gl = GLOW[p]; + if (MOONT[p]) S.bg.set(MOONC[MOONT[p]], p * 3); + else hsv(h - 0.04 * t, 0.8 - 0.25 * t, 0.12 + 0.3 * t, c), S.bg.set([c[0] + 240 * gl, c[1] + 220 * gl, c[2] + 190 * gl], p * 3); + } + hsv(h - 0.02, 0.55, 0.26, S.far); + hsv(h - 0.05, 0.3, 0.8, S.fog); +} + +function put(buf, x, y, r, g, b, a) { + x = round(x); + y = round(y); + if (x < 0 || x >= W || y < 0 || y >= H) return; + const o = (y * W + x) * 3; + buf[o] += (r - buf[o]) * a; + buf[o + 1] += (g - buf[o + 1]) * a; + buf[o + 2] += (b - buf[o + 2]) * a; +} + +// Horse and rider into the mask: 2 black, 1 the far legs, a shade lighter. +function figure(S, m) { + m.fill(0); + const cap = (x0, y0, r0, x1, y1, r1, code = 2) => stamp(m, W, H, x0, y0, r0, x1, y1, r1, code); + const bc = (x0, y0, r0, x1, y1, r1) => { + bodyPt(S, x0, y0, P, 0); + bodyPt(S, x1, y1, P, 2); + cap(P[0], P[1], r0, P[2], P[3], r1); + }; + // Legs, far ones first, two bones each: forearm and cannon in front, the + // knee bending forward; gaskin and cannon behind, the hock bending back. + for (const [jx, jy, off, far, fore] of LEGS) { + bodyPt(S, jx, jy, P, 0); + const x0 = P[0], y0 = P[1]; + foot(S.ph - off - floor(S.ph - off), fore, x0); + let dx = F[0] - x0, dy = F[1] - y0; + const dd = sqrt(dx * dx + dy * dy), dm = min(L1 + L2 - 0.05, max(abs(L1 - L2) + 0.5, dd)); + (dx /= dd), (dy /= dd); + const fx = x0 + dx * dm, fy = y0 + dy * dm, ca = (L1 * L1 + dm * dm - L2 * L2) / (2 * L1 * dm), sa = sqrt(max(0, 1 - ca * ca)), sg = fore ? -1 : 1; + const kx = x0 + L1 * (dx * ca - sg * dy * sa), ky = y0 + L1 * (dy * ca + sg * dx * sa), code = far ? 1 : 2; + cap(x0, y0, fore ? 2.6 : 3.2, kx, ky, 1.4, code); + cap(kx, ky, 1.2, fx, fy, 0.9, code); + cap(fx, fy - 0.5, 1.3, fx + 0.8, fy, 1.2, code); + } + // The tail streaming back. + let tx = -14, ty = -4; + for (let i = 1; i <= 6; i++) { + const nx = -14 - 2.3 * i, ny = -4 + 0.75 * i + sin(S.t * 9 - i * 0.9) * i * 0.2; + bc(tx, ty, 2.4 - 0.25 * i, nx, ny, 2.2 - 0.25 * i); + (tx = nx), (ty = ny); + } + // Barrel, chest, quarters and withers. + bc(-9, -0.5, 6, 8, 0, 6); + bc(10, -1, 6.2, 10, -1, 6.2); + bc(-10, -2, 6.6, -10, -2, 6.6); + bc(6, -6, 2.6, 8, -6, 2.6); + // Neck and head nod with the stride; the mane flies back. + const nc = cos(S.neck), ns = sin(S.neck), nk = (x, y) => [11 + (x - 11) * nc - (y + 2) * ns, -2 + (x - 11) * ns + (y + 2) * nc]; + const [n1x, n1y] = nk(18, -12), [h1x, h1y] = nk(25, -5), [e1x, e1y] = nk(17, -16); + S.eye = nk(20, -10.5); + bc(11, -2, 5, n1x, n1y, 3); + bc(n1x, n1y, 2.9, h1x, h1y, 1.7); + bc(n1x - 0.5, n1y - 1, 0.8, e1x, e1y, 0.5); + for (let i = 0; i < 5; i++) { + const [mx, my] = nk(11.5 + i * 1.4, -6.5 - i * 1.4); + bc(mx, my, 0.7, mx - 3.5, my + 0.5 + sin(S.t * 11 + i) * 0.8, 0.4); + } + // The rider: seat, a torso leaning into the gallop, broad shoulders, no head. + bc(-1, -8, 3.3, 3, -20, 3.8); + bc(0, -20.5, 2.5, 5.5, -20, 2.3); + bc(2, -22.8, 1.1, 3.6, -22.6, 1.1); + bc(-1, -7, 2.2, 5, -3, 1.8); + bc(5, -3, 1.4, 4.2, 4, 1.2); + bc(4.2, 4, 1.4, 6, 4.4, 1.1); + // The rein arm, and the reins to the bit. + bc(4, -19, 1.5, 7.5, -13, 1.3); + bc(7.5, -13, 1.3, 11.5, -10.5, 1.1); + bodyPt(S, 11.5, -10.5, P, 0); + bodyPt(S, h1x - 1, h1y + 0.5, P, 2); + cap(P[0], P[1], 0.45, P[2], P[3], 0.45); + hand(S); + cap(HAND[0], HAND[1], 1.6, HAND[2], HAND[3], 1.3); + cap(HAND[2], HAND[3], 1.3, HAND[4], HAND[5], 1.1); + // The cape, from the shoulders back on the wind, rippling, ragged at the hem. + bodyPt(S, 2, -20, P, 0); + const ax = P[0], ay = P[1], len = 27; + for (let x = floor(ax); x >= max(0, ax - len); x--) { + const t = (ax - x) / len, cy = ay + 4.5 * t * t + 1.5 * t + sin(TAU * (t * 1.2 - S.t * 1.7)) * 2.1 * t; + let hw = 1.3 + 5 * t; + if (t > 0.8) hw -= hash(x * 7 + floor(S.t * 6)) * 15 * (t - 0.8); + for (let y = max(0, round(cy - hw)); y <= min(H - 1, round(cy + hw * 0.9)); y++) m[y * W + x] = 2; + } +} + +const RIBS = [[255, 168, 40], [255, 128, 16], [226, 92, 8]]; +function pumpkin(S, buf, cx, cy, k, sp) { + const r = PK * k; + if (r < 0.6) return; + const fl = 0.78 + 0.22 * (0.5 + 0.5 * vn(S.t * 11, 3)) * (0.7 + 0.3 * vn(S.t * 3, 9)); + // A glow round it, in steps. + for (let y = floor(cy - r - 9); y <= cy + r + 9; y++) + for (let x = floor(cx - r - 9); x <= cx + r + 9; x++) { + const e = sqrt((x + 0.5 - cx) ** 2 + (y + 0.5 - cy) ** 2) - r; + if (e > 0 && e < 9) put(buf, x, y, 255, 120, 20, (e < 2 ? 0.34 : e < 5 ? 0.18 : 0.07) * fl); + } + for (let y = floor(cy - r - 1); y <= cy + r; y++) + for (let x = floor(cx - r * 1.15 - 1); x <= cx + r * 1.15; x++) { + const u = (x + 0.5 - cx) / (r * 1.15), v = (y + 0.5 - cy) / r; + if (u * u + v * v > 1) continue; + // Ribs in three bright tones, and the carved face lit from inside. + const rib = abs(sin((u + sp) * 4.2)), fu = u / 0.9; + const eye = v > -0.5 && v < -0.05 && abs(abs(fu) - 0.42) < (0.3 * (v + 0.55)) / 0.5; + const mouth = v > 0.2 && v < 0.62 && abs(fu) < 0.62 - (v - 0.2) * 0.5 && !(v < 0.4 && abs(fu) > 0.1 && abs(fu) < 0.3); + if (k > 0.7 && (eye || mouth)) put(buf, x, y, 255 * fl, 236 * fl, 120 * fl, 1); + else { + const c = RIBS[rib > 0.75 ? 0 : rib > 0.3 ? 1 : 2]; + put(buf, x, y, c[0], c[1], c[2], 1); + } + } + put(buf, cx - 0.5, cy - r - 1, 60, 170, 40, 1); +} + +// Mist from row y0 to y1, drifting by sp: at most `a` of it, thickest at +// row yc and thinning over wd rows either side. +function mist(S, buf, y0, y1, yc, wd, a, sp, row) { + const fog = S.fog; + for (let y = max(0, y0); y < min(H, y1); y++) { + const band = exp(-(((y - yc) / wd) ** 2)), fj = (y + row) % FH; + for (let x = 0; x < W; x++) { + const n = FN[fj * FW + (((floor((x + sp) * 0.5) % FW) + FW) % FW)], v = band * cl((n - 0.2) * 1.8, 0, 1) * a; + if (v > 0.03) put(buf, x, y, fog[0], fog[1], fog[2], round(v * 12) / 12); + } + } +} + +export function draw(display, params) { + const S = params.s; + if (!S) return; + if (S.hue !== S.hue0) (S.hue0 = S.hue), palette(S); + const buf = S.buf, far = S.far, g = S.g, A = S.A; + buf.set(S.bg); + for (const [x, y, ph] of STARS) { + const v = sin(S.t * 2.3 + ph) > 0.2 ? 200 : 110; + put(buf, x, y, v, v, v + 30, 1); + } + // Far hills, slow, with a ragged line of tiny trees. + for (let x = 0; x < W; x++) { + const wx = x + g * 0.1, top = HZ - 5 - 3 * vn(wx / 17, 11) - 1.5 * vn(wx / 6, 12) - (hash(floor(wx / 3) * 5 + 1) < 0.25 ? 3 : 0); + for (let y = round(top); y <= HZ; y++) put(buf, x, y, far[0], far[1], far[2], 1); + } + // Trees and gravestones on the horizon, in slots of 14 LEDs. + const mo = g * 0.3, pT = 0.2 + 0.72 * S.woods; + for (let sl = floor((mo - TW) / 14); sl <= floor((mo + W + TW) / 14); sl++) { + const sx = sl * 14 - mo + hash(sl * 3 + 3) * 6; + if (hash(sl * 3 + 1) < pT) { + const l = TREES[floor(hash(sl * 3 + 2) * NT)], flip = hash(sl * 3 + 4) < 0.5, x0 = round(sx - TW / 2); + for (let i = 0; i < l.length; i += 2) { + const x = x0 + (flip ? TW - 1 - l[i] : l[i]), y = HZ + 1 - TH + l[i + 1]; + if (x >= 0 && x < W && y >= 0) buf.set(TREEC, (y * W + x) * 3); + } + } else if (hash(sl * 5 + 7) < 0.55) { + const st = STONES[floor(hash(sl * 5 + 8) * 3)]; + for (let j = 0; j < st.length; j++) + for (let i = 0; i < st[j].length; i++) if (st[j][i] === "x") put(buf, sx + i, HZ - st.length + 1 + j, j ? 52 : 104, j ? 42 : 92, j ? 68 : 124, 1); + } + } + // The road, its ruts running back, and the dark verge with grass tufts. + for (let y = RD0; y < H; y++) + for (let x = 0; x < W; x++) { + const gx = floor(x + g * 1.15), o = (y * W + x) * 3; + const k = 3 * (y < RD1 ? (y === RD0 ? 0 : (y === 102 || y === 105) && hash(floor(x + g) * 3 + y) < 0.35 ? 1 : 2) : hash(gx * 13) < 0.3 && y - RD1 < 1 + floor(hash(gx * 7) * 4) ? 3 : y < 116 ? 4 : 5); + (buf[o] = GROUND[k]), (buf[o + 1] = GROUND[k + 1]), (buf[o + 2] = GROUND[k + 2]); + } + // Wisps along the horizon, a little thicker with the mist. + mist(S, buf, 84, RD1, 99, 7, 0.25 + 0.45 * S.mist, g * 0.6, 0); + // Dust behind the figure. + for (let i = 0; i < S.np; i++) if (S.K[i] === 2) put(buf, A[i * 6], A[i * 6 + 1], 140, 124, 160, (A[i * 6 + 4] / A[i * 6 + 5]) * 0.75); + // Horse and rider: black, rimmed with moonlight where they meet the moon. + const m = S.mask; + figure(S, m); + for (let p = 0; p < N; p++) { + const c = m[p], o = p * 3; + if (!c) continue; + if (c === 1) { + buf.set(FARLEG, o); + continue; + } + // The brightest open neighbour lights the rim, if it is moonlight. + let q = -1, best = 300; + for (let s = 0; s < 4; s++) { + const n = s === 0 ? p - 1 : s === 1 ? p + 1 : s === 2 ? p - W : p + W; + if (n >= 0 && n < N && !m[n] && buf[n * 3] + buf[n * 3 + 1] > best) (best = buf[n * 3] + buf[n * 3 + 1]), (q = n * 3); + } + if (q < 0) (buf[o] = 3), (buf[o + 1] = 1), (buf[o + 2] = 6); + else (buf[o] = buf[q] * 0.42), (buf[o + 1] = buf[q + 1] * 0.42), (buf[o + 2] = buf[q + 2] * 0.42); + } + // The horse's eye, a coal. + bodyPt(S, S.eye[0], S.eye[1], P, 0); + put(buf, P[0] - 0.5, P[1] - 0.5, 255, 110, 30, 1); + // The pumpkin in his hand, or in flight. + if (S.pk > 0) { + hand(S); + pumpkin(S, buf, HAND[4] + 0.5, HAND[5] - PK * S.pk + 0.5, S.pk, 0); + } + if (S.fly) pumpkin(S, buf, S.fly.x, S.fly.y, 1, S.fly.spin); + // Flames yellow to orange to red, sparks white-gold. + for (let i = 0; i < S.np; i++) { + const o = i * 6, a = 1 - A[o + 4] / A[o + 5], kind = S.K[i]; + if (kind === 0) { + if (a < 0.3) put(buf, A[o], A[o + 1], 255, 236, 130, 1); + else if (a < 0.65) put(buf, A[o], A[o + 1], 255, 146, 24, 1); + else put(buf, A[o], A[o + 1], 236, 64, 18, 1.4 - a); + } else if (kind === 1) put(buf, A[o], A[o + 1], 255, 226, 150, 1); + } + // Mist in front: up to the knees at half, the belly at 1, drifting back + // faster than the road. + if (S.mist > 0.1) mist(S, buf, 70, H, GY + 12 - 18 * S.mist, 5 + 12 * S.mist, 0.35 + 0.65 * S.mist, g * 1.3 + S.t * 8, 11); + // The near fence, fastest of all, moonlit on its left. + for (let x = 0; x < W; x++) { + const px = (((x + g * 1.15) % 40) + 40) % 40; + if (px < 3) for (let y = 109; y < H; y++) put(buf, x, y, px < 1 ? 92 : 54, px < 1 ? 84 : 46, px < 1 ? 110 : 68, 1); + else for (const y of [112, 119]) put(buf, x, y, 84, 74, 100, 1); + } + for (let p = 0, o = 0; p < N; p++, o += 3) display.setPixel(p % W, (p / W) | 0, min(255, round(buf[o] / 4) * 4), min(255, round(buf[o + 1] / 4) * 4), min(255, round(buf[o + 2] / 4) * 4)); +} From 65139a31dd62be1720a472a6fcc711324fa6a01d Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 25 Oct 2026 19:46:14 +0000 Subject: [PATCH 3/8] =?UTF-8?q?pattern-study:=20Fractal=20Percolation,=20G?= =?UTF-8?q?rim=20Reaper=20(48=ED=8C=90)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_015fDPQzQykg7X6Bo8o35Zx2 --- .../patterns/fractal-percolation.js | 294 +++++++++++ .../pattern-study/patterns/grim-reaper.js | 459 ++++++++++++++++++ 2 files changed, 753 insertions(+) create mode 100644 incoming/pattern-study/patterns/fractal-percolation.js create mode 100644 incoming/pattern-study/patterns/grim-reaper.js diff --git a/incoming/pattern-study/patterns/fractal-percolation.js b/incoming/pattern-study/patterns/fractal-percolation.js new file mode 100644 index 000000000..364535ff5 --- /dev/null +++ b/incoming/pattern-study/patterns/fractal-percolation.js @@ -0,0 +1,294 @@ +// ===== Patternflow pattern ===== +// Title: Fractal Percolation +// Family: fractal +// Idea: Two stacked squares split into quarters again and again down to single LEDs, each quarter kept with chance p and the rest going dark, then a front climbs from the bottom row through the kept LEDs, the cluster that reaches the top row lights mint while the rest stays dim, and the view zooms into a kept pair of squares that grows to fill the panel and splits once more. +// Author: patternflow-lab +// Date: 2026-10-25 +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Lineage: Fractal percolation (Benoit Mandelbrot, 1974), with the crossing transition of Chayes, Chayes and Durrett (1988), pattern round 48 +// Round: 48 +// =============================== +// @matrix 64x128 +// @knobs Hue=0..1, Speed=0.2..3, Keep=0.88..0.99, Levels=0..1 +// +// Knob 1: Hue. Turns every colour round the wheel together. At 0 the kept LEDs are steel blue, the front is white over a pale blue trail, the crossing cluster is mint and the holes are dark plum; at 0.25 the kept LEDs are purple, the crossing cluster blue and the holes olive; at 0.5 tan, pink and green; at 0.75 green, yellow and navy. +// Knob 2: Speed. At 2 a level splits every 0.45 s, so a build from two squares to single LEDs takes 2.7 s and the first front sets off 3 s in; the front climbs 110 LEDs of path a second (1.2 s to the top row on average), a crossing cluster stays lit 1.4 s and a zoom takes 1.4 s, and after four zooms the holes close and a fresh build begins, about every 23 s. At 0.2 everything is ten times slower; at 3 half as fast again, a fresh build every 16 s. +// Knob 3: Keep. The chance p that each quarter is kept at every split. At the opening 0.88 the front reaches the top row in about half the tests and 47% of the LEDs are kept; at 0.90 in seven of ten, with 53% kept; at 0.93 in nine of ten, with 65% kept; at 0.99 every time, with 94% kept. The kept set is a random Cantor set of dimension log(4p)/log 2, 1.82 at 0.88 and 1.99 at 0.99. +// Knob 4: Levels. How brightly each hole keeps the colour of the level that cut it, magenta for the 32-LED holes and a step on towards blue-violet for each smaller size. At 0 every hole is black and only the set shows; at the opening 0.06 the 32-LED holes glow at 35/255 and the single-LED ones at 16, so the big holes read as dark plum squares; at 0.25 at 71 and 32, and at 1 at 142 and 65, a mosaic of squares inside squares. +// Buttons: K1 tap closes the holes back into two whole squares and carves a fresh set. + +const W = 64, H = 128, N = W * H, DEPTH = 6; +const { floor, min, max, exp, pow, round } = Math; +const cl = (v, a, b) => (v < a ? a : v > b ? b : v); + +// Seconds at Speed 2: one level's split, the fade of a cut square, the time +// a crossing (or its absence) stays lit, and one zoom by two. +const STEP = 0.45, FADE = 0.35, SEAM = 0.4, HOLD = 1.4, MISS = 0.7, ZOOM = 1.4, HEAL = 0.09; +// The front climbs this many LEDs of path a second, and four zooms follow a +// build before a fresh one. +const CLIMB = 110, ZMAX = 4; + +// Carves levels a..b into cut on a grid w wide and h tall made of w-by-w +// squares: at level k every square of side w >> k that no earlier level has +// cut is cut whole with chance 1 - p. cut holds the level that removed each +// LED's square, or 0 while it is kept. +function carve(cut, w, h, a, b, p) { + for (let k = a; k <= b; k++) { + const s = w >> k; + for (let y = 0; y < h; y += s) + for (let x = 0; x < w; x += s) { + const o = y * w + x; + if (cut[o] || Math.random() < p) continue; + for (let j = 0; j < s; j++) cut.fill(k, o + j * w, o + j * w + s); + } + } +} + +// Path length from the bottom row through kept LEDs that share an edge, into +// dist (-1 where the bottom row cannot be reached). Returns the shortest path +// to the top row, or -1 when no kept path crosses. +function reach(cut, w, h, dist, q) { + dist.fill(-1); + let t = 0, top = -1; + for (let x = 0, o = (h - 1) * w; x < w; x++) if (!cut[o + x]) (dist[o + x] = 0), (q[t++] = o + x); + for (let i = 0; i < t; i++) { + const p = q[i], d = dist[p] + 1, x = p % w; + if (p < w && top < 0) top = d - 1; + if (x > 0 && !cut[p - 1] && dist[p - 1] < 0) (dist[p - 1] = d), (q[t++] = p - 1); + if (x < w - 1 && !cut[p + 1] && dist[p + 1] < 0) (dist[p + 1] = d), (q[t++] = p + 1); + if (p >= w && !cut[p - w] && dist[p - w] < 0) (dist[p - w] = d), (q[t++] = p - w); + if (p + w < w * h && !cut[p + w] && dist[p + w] < 0) (dist[p + w] = d), (q[t++] = p + w); + } + return top; +} + +// The crossing cluster: every LED reached from the bottom row that also +// connects to the top row, marked 1 in mark. Returns how many there are. +function spanning(w, h, dist, mark, q) { + mark.fill(0); + let t = 0; + for (let x = 0; x < w; x++) if (dist[x] >= 0) (mark[x] = 1), (q[t++] = x); + for (let i = 0; i < t; i++) { + const p = q[i], x = p % w; + for (const n of [x > 0 ? p - 1 : -1, x < w - 1 ? p + 1 : -1, p - w, p + w < w * h ? p + w : -1]) + if (n >= 0 && dist[n] >= 0 && !mark[n]) (mark[n] = 1), (q[t++] = n); + } + return t; +} + +// Zoom targets: a column of the level-1 grid (32 LEDs wide) and a pair of +// its rows, so 32 by 64 LEDs, the panel's shape at half its size. Only pairs +// whose two squares survived level 1 can be chosen, and the choice looks at +// nothing else, so the view after a zoom is a fresh sample of the +// construction one level down. +function target(cut) { + const ok = []; + for (let c = 0; c < 2; c++) + for (let r = 0; r < 3; r++) if (cut[r * 32 * W + c * 32] !== 1 && cut[(r + 1) * 32 * W + c * 32] !== 1) ok.push([c * 32, r * 32]); + return ok.length ? ok[floor(Math.random() * ok.length)] : null; +} + +function hsv(h, s, v, out, o) { + h = (h - floor(h)) * 6; + const i = floor(h) % 6, f = h - floor(h), p = v * (1 - s), q = v * (1 - f * s), t = v * (1 - (1 - f) * s); + out[o] = (i === 0 || i === 5 ? v : i === 1 ? q : i === 4 ? t : p) * 255; + out[o + 1] = (i === 1 || i === 2 ? v : i === 0 ? t : i === 3 ? q : p) * 255; + out[o + 2] = (i === 3 || i === 4 ? v : i === 2 ? t : i === 5 ? q : p) * 255; +} + +export function setup(params) { + params.s = { + hue: 0, speed: 2, p: 0.88, lev: 0.06, t: 0, + ph: "build", pt: 0, L: 0, maxd: 0, revT: new Float64Array(DEPTH + 1).fill(-9), R: 0, top: -1, crossed: false, zooms: 0, tgt: null, zs: 1, + cut: new Uint8Array(N), dist: new Int32Array(N), mark: new Uint8Array(N), q: new Int32Array(N), + lit: new Float32Array(N), glow: new Float32Array(N), + tmp: new Uint8Array(N), tmpF: new Float32Array(N), tmpL: new Float32Array(N), + pal: new Float32Array(32 * 3), palKey: "", + // Fronts sent and crossings found, kept for checking the rates above. + tests: 0, hits: 0, + }; + fresh(params.s); +} + +function fresh(S) { + S.cut.fill(0); + S.ph = "build"; + S.pt = 0; + S.L = 0; + S.revT.fill(-9); + S.zooms = 0; + S.R = 0; + S.crossed = false; +} + +// After a zoom: every LED of the chosen pair becomes a 2-by-2 block and each +// level moves up one; the build then carves the single LEDs anew. +function settle(S) { + const [ox, oy] = S.tgt, { cut, lit, glow, tmp, tmpF, tmpL } = S; + tmp.set(cut); + tmpF.set(lit); + tmpL.set(glow); + for (let y = 0; y < H; y++) + for (let x = 0; x < W; x++) { + const p = y * W + x, o = (oy + (y >> 1)) * W + ox + (x >> 1), c = tmp[o]; + cut[p] = c > 1 ? c - 1 : 0; + lit[p] = tmpF[o]; + glow[p] = tmpL[o]; + } + for (let k = 1; k < DEPTH; k++) S.revT[k] = -9; + S.L = DEPTH - 1; + S.ph = "build"; + S.pt = STEP * (DEPTH - 1) + 0.2; + S.zooms++; +} + +export function update(dt, input, params) { + if (!params.s) setup(params); + const S = params.s, k = input.knobValues || [0, 2, 0.88, 0.06]; + S.hue = cl(k[0], 0, 1); + S.speed = cl(k[1], 0.2, 3); + S.p = cl(k[2], 0.88, 0.99); + S.lev = cl(k[3], 0, 1); + const d = (cl(dt || 0, 0, 0.1) * S.speed) / 2; + S.t += d; + S.pt += d; + if (input.btnPressed && input.btnPressed[0] && S.ph !== "heal") (S.ph = "heal"), (S.pt = 0), (S.crossed = false); + if (S.ph === "build") { + // Level L + 1 splits every STEP; the front sets off 0.3 s after the last. + const L = min(DEPTH, floor(S.pt / STEP)); + // Each level is carved as it splits, so turning Keep acts on the next. + while (S.L < L) { + S.L++; + carve(S.cut, W, H, S.L, S.L, S.p); + S.revT[S.L] = S.t; + } + if (S.L === DEPTH && S.pt > STEP * DEPTH + 0.3) { + S.top = reach(S.cut, W, H, S.dist, S.q); + // The longest path, for when nothing crosses. + let m = 0; + for (let p = 0; p < N; p++) if (S.dist[p] > m) m = S.dist[p]; + S.maxd = m; + S.ph = "burn"; + S.pt = 0; + S.R = 0; + S.crossed = false; + } + } else if (S.ph === "burn") { + S.R += CLIMB * d; + if (S.top >= 0 && S.R >= S.top) { + spanning(W, H, S.dist, S.mark, S.q); + S.crossed = true; + S.ph = "hold"; + S.pt = 0; + S.tests++; + S.hits++; + } else if (S.top < 0 && S.R > S.maxd + 8) { + // The front has run out of path: no crossing this time. + S.ph = "hold"; + S.pt = HOLD - MISS; + S.tests++; + } + } else if (S.ph === "hold") { + if (S.pt > HOLD) { + S.tgt = S.zooms < ZMAX ? target(S.cut) : null; + S.ph = S.tgt ? "zoom" : "heal"; + S.pt = 0; + } + } else if (S.ph === "zoom") { + const u = cl(S.pt / ZOOM, 0, 1); + // Eased in and out, scaled exponentially so it looks like a steady fall. + S.zs = pow(2, u * u * (3 - 2 * u)); + if (S.pt >= ZOOM) { + S.zs = 1; + S.crossed = false; + settle(S); + } + } else if (S.ph === "heal") { + // The holes close from the smallest up, then a fresh set is carved. + S.crossed = false; + const L = DEPTH - floor(S.pt / HEAL); + if (L < S.L) S.L = max(0, L); + if (S.pt > HEAL * (DEPTH + 1) + 0.25) fresh(S); + } + // Light: the front and its trail brighten the kept LEDs it has reached; + // once it crosses, the crossing cluster turns to the accent and the rest + // falls back. + const burning = S.ph === "burn" || S.ph === "hold", kf = 1 - exp(-d * 7), kg = 1 - exp(-d * 4); + const { dist, lit, glow, mark } = S; + for (let p = 0; p < N; p++) { + let tl = 0, tg = 0; + if (burning && dist[p] >= 0 && dist[p] <= S.R) { + if (S.crossed) tg = mark[p]; + else tl = 0.55; + } + lit[p] += (tl - lit[p]) * kf; + if (S.ph !== "zoom") glow[p] += (tg - glow[p]) * (tg > glow[p] ? kf * 1.5 : kg); + } +} + +// Colours: 0 kept, 1 lit trail, 2 crossing, 3 front, 4 cut flash, 8 + k the +// hole of level k. +function palette(S) { + const key = S.hue + ":" + S.lev; + if (key === S.palKey) return; + S.palKey = key; + const h = S.hue + 0.6, P = S.pal; + hsv(h, 0.5, 0.5, P, 0); + hsv(h, 0.35, 0.9, P, 3); + hsv(h + 0.82, 0.62, 0.9, P, 6); + hsv(h + 0.82, 0.1, 1, P, 9); + hsv(h, 0.2, 1, P, 12); + for (let k = 1; k <= DEPTH; k++) { + // Each level its own hue, 0.055 of the wheel on from the last; the + // larger the hole, the brighter its glow. + const v = 0.55 * pow(S.lev, 0.5) * (1.12 - 0.11 * k); + hsv(h + 0.33 - 0.055 * (k - 1), 0.7, v, P, (8 + k) * 3); + } +} + +export function draw(display, params) { + const S = params.s; + if (!S) return; + palette(S); + const { cut, lit, glow, dist, pal: P, revT } = S; + const zoom = S.ph === "zoom", s = S.zs, fx = zoom ? S.tgt[0] * 2 : 0, fy = zoom ? S.tgt[1] * 2 : 0; + const front = S.ph === "burn"; + // While a level splits, the edges of its new squares show as dark seams + // for a moment; before the first split, the edge between the two squares. + const L = S.L, age = L ? S.t - revT[L] : 0, ss = 64 >> (L ? L : 0); + const seam = S.ph === "build" && L <= 3 && age < SEAM ? 0.35 + 0.65 * (L ? age / SEAM : 0) : 0; + for (let y = 0; y < H; y++) { + const sy = zoom ? floor(fy + (y + 0.5 - fy) / s) : y; + for (let x = 0; x < W; x++) { + const sx = zoom ? floor(fx + (x + 0.5 - fx) / s) : x; + if (sx < 0 || sx >= W || sy < 0 || sy >= H) continue; + const p = sy * W + sx, c = cut[p]; + let r, g, b; + if (c && c <= S.L) { + // A cut square: a flash, then the glow of its level. + const a = min(1, (S.t - revT[c]) / FADE), o = (8 + c) * 3; + const f = (1 - a) * (1 - a); + r = P[o] + (P[12] - P[o]) * f; + g = P[o + 1] + (P[13] - P[o + 1]) * f; + b = P[o + 2] + (P[14] - P[o + 2]) * f; + } else { + const l = lit[p], gg = glow[p]; + r = P[0] + (P[3] - P[0]) * l; + g = P[1] + (P[4] - P[1]) * l; + b = P[2] + (P[5] - P[2]) * l; + r += (P[6] - r) * gg; + g += (P[7] - g) * gg; + b += (P[8] - b) * gg; + if (front && dist[p] >= 0 && dist[p] <= S.R && S.R - dist[p] < 5) { + const f = 1 - (S.R - dist[p]) / 5; + r += (P[9] - r) * f; + g += (P[10] - g) * f; + b += (P[11] - b) * f; + } + } + if (seam && !(c && c <= S.L) && (sx % ss === 0 || sy % ss === 0)) (r *= seam), (g *= seam), (b *= seam); + // Channels in steps of 4 of 255: too fine to see, and a frame packs smaller. + display.setPixel(x, y, min(255, round(r / 4) * 4), min(255, round(g / 4) * 4), min(255, round(b / 4) * 4)); + } + } +} diff --git a/incoming/pattern-study/patterns/grim-reaper.js b/incoming/pattern-study/patterns/grim-reaper.js new file mode 100644 index 000000000..448ccc29a --- /dev/null +++ b/incoming/pattern-study/patterns/grim-reaper.js @@ -0,0 +1,459 @@ +// ===== Patternflow pattern ===== +// Title: Grim Reaper +// Family: halloween +// Idea: A hooded reaper in a tattered black robe drifts across a hillside graveyard in front of a huge pale moon and a sickly green horizon, two toxic green points burning in its hood and shreds of its ragged hem streaming behind, swinging a long scythe over its head in a wide arc whose blade glints, while the grave candles gutter and go out as it passes over them and light again behind it. +// Author: patternflow-lab +// Date: 2026-10-25 +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Lineage: original, pattern round 48 +// Round: 48 +// =============================== +// @matrix 64x128 +// @knobs Hue=0..1, Speed=0.2..3, Graves=6..30, Fog=0..1 +// +// Knob 1: Hue. The glow at the horizon, the moon's tint and the eyes; the sky overhead stays deep purple and the candles gold. At 0 a sickly green horizon, a pale yellow moon and toxic green eyes; at 0.25 a teal horizon, a mint moon and sky blue eyes; at 0.5 violet, lavender and magenta; at 0.75 a red horizon, a pink moon and orange eyes. +// Knob 2: Speed. At 2 a pass takes 10 s: in from the right in 1.8 s, 5.7 s drifting 18 LEDs across the moon while it sways 5 LEDs either way and bobs 6 up and down every 2.9 s, and out to the left in 2.5 s. The scythe swings every 3.2 s (wound back in 0.55 s, the stroke in 0.32 s, held 0.3 s, brought back in 1.2 s), and a candle lights again 0.6 to 1.8 s after the robe has passed it. At 0.2 everything is ten times slower, at 3 half as fast again. +// Knob 3: Graves. How many graves, six to a row, each with a candle at its foot. At 6 one row stands along the bottom and the robe is all against the sky; at 12 a second row stands 9 LEDs higher on a hill, at 18, 24 and 30 a third, fourth and fifth, each a sixth smaller, until the hill's crest reaches row 82 and the candles climb it. +// Knob 4: Fog. A lilac fog rolling to the right at 8 to 11 LEDs a second. At 0 none; at 0.06 a thin haze over the bottom 22 rows, at most a fifth thick; at 0.5 it reaches row 88 and 0.38 thick; at 1 row 68, over the hem and the lower robe, 0.58 thick. +// Buttons: K1 tap swings the scythe at once, from rest or while it is being brought back. + +const W = 64, H = 128, N = W * H; +const { floor, round, ceil, min, max, abs, sqrt, sin, cos, exp, pow, PI } = Math; +const cl = (v, a, b) => (v < a ? a : v > b ? b : v); +const sm = (v) => (v <= 0 ? 0 : v >= 1 ? 1 : v * v * (3 - 2 * v)); +const hash = (i) => { + let h = Math.imul(i ^ 0x9e3779b9, 0x85ebca6b); + h = Math.imul(h ^ (h >>> 13), 0xc2b2ae35); + return ((h ^ (h >>> 16)) >>> 0) / 4294967296; +}; +const vn = (x, sd) => { + const i = floor(x), f = x - i, u = f * f * (3 - 2 * f); + return (hash(i * 7919 + sd) * (1 - u) + hash((i + 1) * 7919 + sd) * u) * 2 - 1; +}; +function hsv(h, s, v, out) { + h = (h - floor(h)) * 6; + const i = floor(h) % 6, f = h - floor(h), p = v * (1 - s), q = v * (1 - f * s), t = v * (1 - (1 - f) * s); + out[0] = (i === 0 || i === 5 ? v : i === 1 ? q : i === 4 ? t : p) * 255; + out[1] = (i === 1 || i === 2 ? v : i === 0 ? t : i === 3 ? q : p) * 255; + out[2] = (i === 3 || i === 4 ? v : i === 2 ? t : i === 5 ? q : p) * 255; + return out; +} + +// A huge moon high in the middle; the horizon glows low down. +const MX = 31, MY = 45, MR = 30; +// Headstones, each [width, height] near: a round-topped slab, a cross, a +// broken slab, an obelisk and a slab with its corners cut. +const KINDS = [[11, 18], [9, 21], [11, 15], [7, 24], [12, 16]]; +function solid(kind, i, j, w, h) { + const c = (w - 1) / 2, dx = abs(i - c); + if (kind === 0) return j >= w / 2 || (i + 0.5 - w / 2) ** 2 + (w / 2 - j - 0.5) ** 2 <= (w / 2) ** 2; + if (kind === 1) return dx <= 1 || (j >= 3 && j <= 4) || (j >= h - 2 && dx <= 2.5); + if (kind === 2) return j >= (h * 0.35 * i) / w + (abs(i - c * 0.8) < 1 ? 2 : 0); + if (kind === 3) return j >= h - 2 || dx <= (j < 3 ? j * 0.5 : 0.8 + (1.2 * j) / h); + return j > 0 || dx < c - 0.5; +} + +// The reaper, in its own frame: u across (negative is forward, the way it +// drifts), v down from its shoulders, both in LEDs. +// The scythe: the hand holds the snath LO below and UP above the grip; the +// blade, BL long, leaves the top at right angles and curls back down. +const UP = 27, LO = 12, BL = 19, CURL = 0.3; +// Swing angles of the snath from straight up, forward positive: at rest, +// wound back over the shoulder, and at the end of the stroke, low in front. +const REST = 0.18, BACK = -0.62, END = 3.0; +// Seconds at Speed 2: wind back, the stroke, the hold, the return, and the +// rest before the next swing. +const TW = 0.55, TS = 0.32, TF = 0.3, TR = 1.2, TREST = 0.8; +// One pass across the panel, in seconds at Speed 2. +const PASS = 10; + +export function setup(params) { + params.s = { + t: 0, hue: 0, speed: 2, ng: 0, fog: 0.06, + pass: 0.1, bx: 60, by: 50, + sw: "rest", st: TREST - 1.1, from: REST, phi: REST, glint: -1, glintT: 2, + candles: [], smoke: [], shreds: [], shredT: 0, + scene: new Uint8Array(N), bg: new Float32Array(N * 3), bgKey: -1, buf: new Float32Array(N * 3), mask: new Uint8Array(N), + c: [0, 0, 0], e: [0, 0, 0], fn: new Float32Array(2 * W), stamps: {}, + }; +} + +// The graveyard climbs a hill, six graves to a row: the first row stands at +// the foot, each further row 9 to 10 LEDs higher and a sixth smaller, and the +// hill rises behind the last row, so more graves cover more of the glowing +// horizon. Each grave has a candle at its foot. +const PER = 6, rowBase = (r) => 124 - 9 * r - (r > 1 ? r - 1 : 0), rowScale = (r) => 1 - 0.17 * r; +// Scene codes: 0 sky, 1 hill, 2 its moonlit crest, 10 + r a stone of row r, +// 20 + r its moonlit top. +function graveyard(S, n) { + const sc = S.scene, R = ceil(n / PER); + sc.fill(0); + for (let x = 0; x < W; x++) { + const top = rowBase(R - 1) - 3 + round(1.3 * sin(x / 9 + 0.7) + 0.6 * sin(x / 4.1)); + for (let y = top; y < H; y++) sc[y * W + x] = y === top ? 2 : 1; + } + const old = S.candles; + S.candles = []; + for (let r = R - 1; r >= 0; r--) { + const m = min(PER, n - r * PER), sz = rowScale(r); + for (let j = 0; j < m; j++) { + const i = r * PER + j, kind = floor(hash(i * 17 + 3) * KINDS.length), w = max(3, round(KINDS[kind][0] * sz)), h = max(4, round(KINDS[kind][1] * sz)); + const cx = (round(((j + 0.5 + (r % 2) * 0.5) * 64) / PER + (hash(i * 5 + 1) - 0.5) * 5) + W) % W, x0 = cx - (w >> 1); + const base = rowBase(r) + round(1.3 * sin(cx / 9 + 0.7)); + for (let jj = 0; jj < h; jj++) + for (let k = 0; k < w; k++) { + const x = x0 + k, y = base - h + jj + 1; + if (x >= 0 && x < W && y < H && solid(kind, (k * KINDS[kind][0]) / w, (jj * KINDS[kind][1]) / h, KINDS[kind][0], KINDS[kind][1])) sc[y * W + x] = 10 + r; + } + const x = cl(cx + (hash(i * 11 + 7) < 0.5 ? -(w >> 1) - 1 : (w >> 1) + 1), 1, W - 2); + const prev = old.find((c) => c.x === x && c.y === base); + S.candles.push(prev || { x, y: base, f: 1, out: false, wait: 0, sd: i * 97 + 5, h: max(1, round((3 + floor(hash(i * 7) * 2)) * sz)), sz }); + } + } + for (let p = N - 1; p >= W; p--) if (sc[p] >= 10 && sc[p] < 20 && sc[p - W] !== sc[p]) sc[p] += 10; + S.ng = n; +} + +// Where the reaper is along a pass: in from the right, a slow drift across +// the moon, and out to the left. +function passX(a) { + if (a < 0.18) return 104 - 54 * (1 - (1 - a / 0.18) ** 2); + if (a < 0.75) return 50 - 18 * ((a - 0.18) / 0.57); + const b = (a - 0.75) / 0.25; + return 32 - 62 * b * b - 16 * b; +} + +function swing(S, ds, tap) { + S.st += ds; + if (tap && (S.sw === "rest" || S.sw === "back")) (S.sw = "wind"), (S.st = 0), (S.from = S.phi); + if (S.sw === "rest") { + S.phi = REST + 0.25 * sin(S.st * 2.6); + if (S.st > TREST) (S.sw = "wind"), (S.st = 0), (S.from = S.phi); + } else if (S.sw === "wind") { + S.phi = S.from + (BACK - S.from) * sm(S.st / TW); + if (S.st > TW) (S.sw = "strike"), (S.st = 0), (S.glint = 0); + } else if (S.sw === "strike") { + const u = cl(S.st / TS, 0, 1); + S.phi = BACK + (END - BACK) * (u * u * (2 - u)); + if (S.st > TS) (S.sw = "follow"), (S.st = 0); + } else if (S.sw === "follow") { + S.phi = END + 0.06 * sin(S.st * 20) * exp(-S.st * 8); + if (S.st > TF) (S.sw = "back"), (S.st = 0); + } else { + S.phi = END + (REST - END) * sm(S.st / TR); + if (S.st > TR) (S.sw = "rest"), (S.st = 0); + } +} + +export function update(dt, input, params) { + if (!params.s) setup(params); + const S = params.s, k = input.knobValues || [0, 2, 6, 0.06], d = cl(dt || 0, 0, 0.1); + S.hue = cl(k[0], 0, 1); + S.speed = cl(k[1], 0.2, 3); + S.fog = cl(k[3], 0, 1); + const ng = round(cl(k[2], 6, 30)); + if (ng !== S.ng) graveyard(S, ng); + const ds = (d * S.speed) / 2; + S.t += ds; + S.pass += ds / PASS; + if (S.pass >= 1) S.pass -= 1; + // A sway and a bob on top of the drift: it never hangs still. + S.bx = passX(S.pass) + 5 * sin(S.t * 1.9); + S.by = 46 + 6 * sin(S.t * 2.2); + swing(S, ds, input.btnPressed && input.btnPressed[0]); + // A glint runs along the blade with every stroke, and now and then the + // moon catches it at rest. + if (S.glint >= 0) { + S.glint += ds / 0.45; + if (S.glint > 1.3) S.glint = -1; + } else if ((S.glintT -= ds) < 0) (S.glint = 0), (S.glintT = 2.5 + 2 * hash(floor(S.t * 10))); + // Candles: a flame gutters as the hem comes near, goes out under the + // robe and lights again a moment after the reaper has passed. + for (const c of S.candles) { + const rel = c.x - S.bx; + if (!c.out) { + const tf = rel < -26 || rel > 20 ? 1 : rel < -14 ? (-14 - rel) / 12 : 0; + c.f += (tf - c.f) * min(1, ds * (tf < c.f ? 5 : 2)); + if (c.f < 0.12) { + c.out = true; + c.f = 0; + c.wait = 0.6 + 1.2 * hash(c.sd + floor(S.t)); + for (let i = 0; i < 3; i++) S.smoke.push({ x: c.x, y: c.y - c.h - i, a: -0.15 * i, sd: c.sd + i }); + } + } else if (rel > 18 || rel < -44) { + if ((c.wait -= ds) < 0) (c.out = false), (c.f = 1.7); + } + if (c.f > 1) c.f = max(1, c.f - ds * 2.5); + } + // Shreds of the hem tear off and blow away behind it. + for (let n = S.shredT + ds * 22; n >= 1; n--) { + const v = 44 + Math.random() * 14; + S.shreds.push({ x: S.bx + 15 + (v - 44) * 0.6 + Math.random() * 4, y: S.by + v, vx: 9 + Math.random() * 9, vy: -3 + Math.random() * 4, a: 0 }); + } + S.shredT = (S.shredT + ds * 22) % 1; + let kept = 0; + for (const m of S.shreds) { + m.a += ds; + m.x += m.vx * ds; + m.y += m.vy * ds; + if (m.a < 1.1 && m.x < W + 2) S.shreds[kept++] = m; + } + S.shreds.length = kept; + // Smoke from a flame that went out rises and drifts off. + kept = 0; + for (const m of S.smoke) { + m.a += ds; + m.y -= ds * 7; + m.x += ds * (2 + 3 * vn(S.t + m.sd, m.sd)); + if (m.a < 1.6) S.smoke[kept++] = m; + } + S.smoke.length = kept; +} + +// Sky, moon and horizon for one hue, in flat steps: deep purple overhead, +// turning the short way round the wheel to the glow at the horizon, sickly +// green at Hue 0. The moon is pale, with a hint of the glow. +function background(S) { + if (S.bgKey === S.hue) return; + S.bgKey = S.hue; + const bg = S.bg, gh = 0.27 + S.hue, sky = [0, 0, 0], moon = hsv(gh - 0.1, 0.16, 0.95, [0, 0, 0]), sea = hsv(gh - 0.05, 0.2, 0.8, [0, 0, 0]); + let dh = gh - 0.76; + dh -= Math.round(dh); + for (let y = 0; y < H; y++) { + const f = y < 40 ? 0 : floor(min(1, pow((y - 40) / 74, 0.85)) * 11) / 11; + hsv(0.76 + dh * f, 0.62 + 0.04 * f, 0.2 + 0.46 * f, sky); + for (let x = 0; x < W; x++) { + const o = (y * W + x) * 3, dx = x + 0.5 - MX, dy = y + 0.5 - MY, r = sqrt(dx * dx + dy * dy); + for (let c = 0; c < 3; c++) bg[o + c] = sky[c]; + if (r < MR) { + const s = vn(x * 0.3 + 3, 11) + vn(y * 0.27 + 7, 23) + 0.6 * vn((x + y) * 0.21, 5) > 0.75; + const t = r > MR - 1.2 ? 0.88 : 1; + for (let c = 0; c < 3; c++) bg[o + c] = (s ? sea[c] : moon[c]) * t; + } else { + const g = r < MR + 3 ? 0.2 : r < MR + 7 ? 0.11 : r < MR + 13 ? 0.05 : 0; + for (let c = 0; c < 3; c++) bg[o + c] += moon[c] * g; + } + } + } +} + +// The robe's spans on one row, in the reaper's frame, into SP as pairs; +// returns how many. The hood's dark opening is FACE. +const SP = new Float32Array(32), FACE = [0, 0]; +function spans(v, t) { + FACE[0] = FACE[1] = 0; + if (v < -22 || v > 64) return 0; + if (v < -4) { + // The hood, its peak leaning back, and the opening in front. + const top = v < -12 ? pow((v + 22) / 10, 0.7) : 1, cu = 1 + max(0, -12 - v) * 0.5, hw = 7.4 * top + (v > -8 ? (v + 8) * 0.6 : 0); + const fv = 1 - ((v + 10.5) / 4.6) ** 2; + if (fv > 0) (FACE[0] = -3 - 3.3 * sqrt(fv)), (FACE[1] = -3 + 3.3 * sqrt(fv)); + SP[0] = cu - hw; + SP[1] = cu + hw; + return 1; + } + if (v < 12) { + const hw = v < 2 ? 8.5 + (v + 4) * 0.5 : 11.5 + (v - 2) * 0.05; + SP[0] = 1.5 - hw; + SP[1] = 1.5 + hw; + return 1; + } + // The skirt widens and leans back, its back edge rippling. + const cu = 1.5 + (v - 12) * 0.1, hw = 12 + (v - 12) * 0.12; + const back = (w) => cu + hw + (1.5 + 3 * ((w - 12) / 32)) * sin(w * 0.3 - t * 6.5); + if (v < 44) { + SP[0] = cu - hw + 2 * sin(v * 0.25 - t * 5.5); + SP[1] = back(v); + return 1; + } + // Below the hem, eight tattered strips stream back and flutter. + const L = cu - hw + 2 * sin(44 * 0.25 - t * 5.5), R = back(44), wd = (R - L) / 8; + let n = 0; + for (let i = 0; i < 8; i++) { + const len = 8 + 13 * hash(i * 13 + 1) + 3 * sin(t * 2.4 + i * 2.3), s = (v - 44) / len; + if (s > 1) continue; + const du = (v - 44) * 0.5 + sin(t * 9 + i * 1.9 - (v - 44) * 0.4) * 3.6 * s, c = L + (i + 0.5) * wd + du, w = wd * 0.62 * (1 - s) + 0.3; + SP[2 * n] = c - w; + SP[2 * n + 1] = c + w; + n++; + } + return n; +} + +// A tapered capsule from (ax, ay) to (bx, by) into the mask, radius ra to rb. +function capsule(mask, ox, oy, ax, ay, bx, by, ra, rb) { + const dx = bx - ax, dy = by - ay, l = dx * dx + dy * dy; + const x0 = max(0, floor(ox + min(ax, bx) - ra)), x1 = min(W - 1, ceil(ox + max(ax, bx) + ra)), y0 = max(0, floor(oy + min(ay, by) - ra)), y1 = min(H - 1, ceil(oy + max(ay, by) + ra)); + for (let y = y0; y <= y1; y++) + for (let x = x0; x <= x1; x++) { + const px = x + 0.5 - ox - ax, py = y + 0.5 - oy - ay, s = cl((px * dx + py * dy) / l, 0, 1); + if ((px - dx * s) ** 2 + (py - dy * s) ** 2 < (ra + (rb - ra) * s) ** 2 && !mask[y * W + x]) mask[y * W + x] = 1; + } +} + +// Neighbour steps, a point on the blade, and where along the blade the sweep runs. +const NB4 = [1, 0, -1, 0, 0, 1, 0, -1], BP = [0, 0], ALONG = [0.55, 0.8, 1]; + +function put(buf, x, y, r, g, b, a) { + x = round(x); + y = round(y); + if (x < 0 || x >= W || y < 0 || y >= H) return; + const o = (y * W + x) * 3; + buf[o] += (r - buf[o]) * a; + buf[o + 1] += (g - buf[o + 1]) * a; + buf[o + 2] += (b - buf[o + 2]) * a; +} + +export function draw(display, params) { + const S = params.s; + if (!S || !S.ng) return; + background(S); + const { buf, mask, bg, scene } = S, t = S.t, hs = S.hue; + buf.set(bg); + // The hill, dark, with its crest lit by the moon. + for (let p = 0; p < N; p++) { + const m = scene[p]; + if (!m) continue; + const o = p * 3; + if (m === 1) (buf[o] = 10), (buf[o + 1] = 7), (buf[o + 2] = 15); + else if (m === 2) (buf[o] = 50), (buf[o + 1] = 58), (buf[o + 2] = 60); + else { + // Stones: grey faces in the moonlight, dimmer row by row up the hill. + const f = 1 - 0.13 * (m % 10), lit = m >= 20 ? 2.05 : 1; + buf[o] = 44 * f * lit; + buf[o + 1] = 40 * f * lit; + buf[o + 2] = 58 * f * lit; + } + } + // Candles: a wax stub and a flame that leans and flickers, smaller up the hill. + for (const c of S.candles) { + for (let j = 0; j < c.h; j++) put(buf, c.x, c.y - j, 196, 184, 160, 1); + if (c.out || c.f <= 0.02) continue; + const fl = 0.75 + 0.25 * vn(t * 9 + c.sd, c.sd), gut = c.f < 1 ? 1 + 2.5 * (1 - c.f) * vn(t * 23, c.sd + 3) : 0; + const f = c.f * fl, top = c.y - c.h, R = 7.5 * c.sz; + const ht = max(1, round((1 + 2.4 * f) * c.sz)), lean = round(gut + 0.3 * vn(t * 2, c.sd + 9)); + // A warm light on whatever stands round it. + let st = S.stamps[c.sz]; + if (!st) { + st = S.stamps[c.sz] = []; + for (let j = -floor(R); j <= R; j++) + for (let i = -floor(R); i <= R; i++) { + const r = sqrt(i * i + j * j * 1.4) / c.sz; + if (r < 7.5) st.push(i, j, r < 3 ? 0.3 : r < 5.5 ? 0.17 : 0.08); + } + } + for (let q = 0; q < st.length; q += 3) put(buf, c.x + st[q], top - 1 + st[q + 1], 255, 214, 120, f * st[q + 2]); + for (let j = 0; j < ht; j++) { + const e = j / ht, x = c.x + round(lean * e); + if (j === 0) put(buf, x, top - j, 255, 244, 190, min(1, f + 0.3)); + else put(buf, x, top - j, 255, 186 - 60 * e, 50, min(1, f)); + } + if (c.f > 1.05) put(buf, c.x, top - ht, 255, 255, 230, 1); + } + for (const m of S.smoke) if (m.a >= 0) put(buf, m.x, m.y, 110, 104, 120, 0.6 * (1 - m.a / 1.6)); + + // The reaper: its robe by rows, then the arm and the sleeve hanging under it. + const bx = S.bx, by = S.by, phi = S.phi, ux = -sin(phi), uy = -cos(phi); + // The hand goes forward and down with the stroke and up for the wind-up. + const sg = (phi - REST) / (END - REST), gx = -12 - 4 * max(0, sg) + 3 * min(0, sg), gy = 8 + 4 * max(0, sg) + 6 * min(0, sg); + const y0 = max(0, floor(by - 23)), y1 = min(H - 1, floor(by + 66)); + mask.fill(0, y0 * W, (y1 + 1) * W); + for (let y = y0; y <= y1; y++) { + const n = spans(y + 0.5 - by, t); + for (let i = 0; i < n; i++) + for (let x = max(0, ceil(bx + SP[2 * i] - 0.5)); x <= min(W - 1, floor(bx + SP[2 * i + 1] - 0.5)); x++) mask[y * W + x] = 1; + if (FACE[1] > FACE[0]) + for (let x = max(0, ceil(bx + FACE[0] - 0.5)); x <= min(W - 1, floor(bx + FACE[1] - 0.5)); x++) mask[y * W + x] = 2; + } + capsule(mask, bx, by, -2, -1, gx + 1.5, gy - 0.5, 3.3, 2.5); + capsule(mask, bx, by, (gx - 2) * 0.55 + 1, gy * 0.55 + 1, gx + 3, gy + 6, 2.6, 1.2); + const robeC = [22, 15, 32], x0 = max(0, floor(bx - 24)), x1 = min(W - 1, ceil(bx + 36)); + for (let y = y0; y <= y1; y++) + for (let x = x0; x <= x1; x++) { + const p = y * W + x, m = mask[p]; + if (!m) continue; + const o = p * 3; + if (m === 2) { + buf[o] = buf[o + 1] = buf[o + 2] = 0; + continue; + } + // Cloth, with a rim of the light behind it along its outline. + const edge = (x > 0 && !mask[p - 1]) || (x < W - 1 && !mask[p + 1]) || (y > 0 && !mask[p - W]) || (y < H - 1 && !mask[p + W]); + const a = edge ? 0.3 : 0; + for (let c = 0; c < 3; c++) buf[o + c] = robeC[c] + (bg[o + c] - robeC[c]) * a; + } + // Shreds of cloth blowing off the hem, two LEDs wide, then one, then gone. + for (const m of S.shreds) { + const a = m.a < 0.7 ? 0.9 : 0.9 * (1 - (m.a - 0.7) / 0.4); + put(buf, m.x - 0.5, m.y - 0.5, 22, 15, 32, a); + if (m.a < 0.5) put(buf, m.x + 0.5, m.y - 0.5, 22, 15, 32, a); + if (m.a < 0.25) put(buf, m.x - 0.5, m.y + 0.5, 22, 15, 32, a); + } + // The snath, dark wood. + const hx = bx + gx, hy = by + gy, tx = hx + ux * UP, ty = hy + uy * UP; + for (let s = -LO; s <= UP; s += 0.4) put(buf, hx + ux * s - 0.5, hy + uy * s - 0.5, 34, 22, 16, 1); + // The blade: steel along its back, a bright edge on the inside of the curve. + const nx = cos(phi), ny = -sin(phi); + // A point s along the blade, f LEDs in from its back, into BP. + const bladeAt = (s, f) => { + BP[0] = tx + nx * BL * s - ux * CURL * BL * s * s - ux * f; + BP[1] = ty + ny * BL * s - uy * CURL * BL * s * s - uy * f; + }; + for (let s = 0; s <= 1; s += 0.025) { + bladeAt(s, 0); + put(buf, BP[0] - 0.5, BP[1] - 0.5, 120, 128, 146, 1); + if (s < 0.88) { + bladeAt(s, 1); + put(buf, BP[0] - 0.5, BP[1] - 0.5, 196, 210, 228, 1); + } + } + // A sweep of pale light behind the outer half of the blade during the stroke. + if (S.sw === "strike" || (S.sw === "follow" && S.st < 0.12)) { + const span = S.sw === "strike" ? min(1.6, (S.phi - BACK) * 0.9) : 1.2 * (1 - S.st / 0.12); + for (let a = 0.04; a < span; a += 0.025) { + const q = phi - a, qx = -sin(q), qy = -cos(q), mx = cos(q), my = -sin(q); + for (const b of ALONG) { + const ex = hx + qx * UP + mx * BL * b - qx * CURL * BL * b * b, ey = hy + qy * UP + my * BL * b - qy * CURL * BL * b * b; + put(buf, ex - 0.5, ey - 0.5, 210, 255, 220, 0.6 * (1 - a / span)); + } + } + } + // The glint, running along the edge, with a small star at its brightest. + if (S.glint >= 0 && S.glint <= 1) { + bladeAt(S.glint, 1); + const gx2 = BP[0] - 0.5, gy2 = BP[1] - 0.5, b = sin(S.glint * PI); + put(buf, gx2, gy2, 255, 255, 255, 1); + for (let k = 0; k < 8; k += 2) put(buf, gx2 + NB4[k], gy2 + NB4[k + 1], 255, 255, 255, 0.7 * b); + if (b > 0.8) for (let k = 0; k < 8; k += 2) put(buf, gx2 + 2 * NB4[k], gy2 + 2 * NB4[k + 1], 255, 255, 255, 0.5); + } + // The bony hand on the snath. + put(buf, hx - 0.5, hy - 0.5, 206, 196, 172, 1); + put(buf, hx - 1.5, hy - 0.5, 176, 166, 146, 1); + put(buf, hx - 0.5, hy + 0.5, 176, 166, 146, 1); + // Two points of toxic green in the hood, flaring with the stroke. + const eye = hsv(0.3 + hs, 0.7, 1, S.e), flare = S.sw === "strike" || S.sw === "follow" ? 1 : 0.75 + 0.25 * vn(t * 6, 3); + for (const ex of [-4.6, -1.5]) { + const x = bx + ex, y = by - 10.5; + put(buf, x - 0.5, y - 0.5, eye[0], eye[1], eye[2], flare); + for (let k = 0; k < 8; k += 2) put(buf, x - 0.5 + NB4[k], y - 0.5 + NB4[k + 1], eye[0], eye[1], eye[2], 0.28 * flare); + } + + // Fog over the ground, rolling slowly to the right: one noise across the + // panel and another for each band of eight rows. + if (S.fog > 0) { + const fc = hsv(0.76 + hs, 0.22, 0.6, S.c), topY = 108 - 40 * S.fog, a0 = 0.18 + 0.4 * S.fog, FN = S.fn; + for (let x = 0; x < W; x++) FN[x] = vn(x * 0.08 - t * 0.9, 41); + for (let y = max(0, floor(topY)); y < H; y++) { + const ramp = min(1, (y - topY) / 14), band = y >> 3; + if ((y & 7) === 0 || y === floor(topY)) for (let x = 0; x < W; x++) FN[W + x] = 0.6 * vn(x * 0.19 - t * 1.6, 43 + band); + for (let x = 0; x < W; x++) { + const a = a0 * ramp * cl(0.55 + 0.5 * (FN[x] + FN[W + x]), 0, 1); + if (a < 0.03) continue; + const o = (y * W + x) * 3, q = round(a * 8) / 8; + for (let c = 0; c < 3; c++) buf[o + c] += (fc[c] - buf[o + c]) * q; + } + } + } + for (let p = 0, o = 0; p < N; p++, o += 3) + display.setPixel(p % W, (p / W) | 0, min(255, round(buf[o] / 4) * 4), min(255, round(buf[o + 1] / 4) * 4), min(255, round(buf[o + 2] / 4) * 4)); +} From 8ce4e42c7150b37305e4fc55807e12d407b70ea3 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 25 Oct 2026 19:52:09 +0000 Subject: [PATCH 4/8] =?UTF-8?q?pattern-study:=20Double=20Gyre,=20Bone=20Pi?= =?UTF-8?q?le=20(48=ED=8C=90)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- incoming/pattern-study/patterns/bone-pile.js | 486 ++++++++++++++++++ .../pattern-study/patterns/double-gyre.js | 253 +++++++++ 2 files changed, 739 insertions(+) create mode 100644 incoming/pattern-study/patterns/bone-pile.js create mode 100644 incoming/pattern-study/patterns/double-gyre.js diff --git a/incoming/pattern-study/patterns/bone-pile.js b/incoming/pattern-study/patterns/bone-pile.js new file mode 100644 index 000000000..722f797b0 --- /dev/null +++ b/incoming/pattern-study/patterns/bone-pile.js @@ -0,0 +1,486 @@ +// ===== Patternflow pattern ===== +// Title: Bone Pile +// Family: halloween +// Idea: A skeleton dances in a violet spotlight in a stone crypt until, on a beat, every bone lets go and the whole frame crashes into a heap on the flagstones, lies still while the skull's eyes kindle, then the bones fly up one at a time on arcs from the feet upward and snap back into place, the skull last, landing with a nod before the dance goes on. +// Author: patternflow-lab +// Date: 2026-10-25 +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Lineage: original, pattern round 48 +// Round: 48 +// =============================== +// @matrix 64x128 +// @knobs Hue=0..1, Speed=0.2..3, Dance=4..32, Fog=0..1 +// +// Knob 1: Hue. The spotlight and all it tints: the wall in the beam, the pool on the flagstones, the dust, the bones' shadow side, the fog and the kindled eyes. Violet at 0, red at 0.24, lime at 0.5, cyan at 0.75; the bones stay ivory, the candles orange. +// Knob 2: Speed. The tempo, 50 beats a minute for each unit, and the rest keeps time with it. At the opening 2, 100: every bone is down 0.5 s after the beat that drops it, the eyes kindle at 1 s, the pieces fly back one every 0.18 s from 1.5 s and the skull lands at 4.7 s and nods. At 0.2 all ten times slower; 150 at 3. +// Knob 3: Dance. Beats danced between collapses: 4 at the start, so the first fall comes 2.4 s in and the next 2.8 s after the nod at 7.4 s; 11 at a quarter, 18 at half, 32 at the end, 19 s at 100 a minute. Each time it gets up it dances the next of three dances: a sway with both arms up, kicks to each side with hands on hips, a bounce with the arms pumping in turn. +// Knob 4: Fog. Ground fog drifting in two layers, thickening over its top 16 rows. None at 0; at the opening 0.06 a wisp from row 115; at 0.5 from row 93, burying half the heap; at 1 from row 68, up to the standing skeleton's hips. +// Buttons: K1 tap brings the skeleton down at once, mid-dance or half rebuilt. + +const W = 64, H = 128, N = W * H; +const { floor, ceil, round, min, max, abs, sqrt, sin, cos, atan2, PI } = Math; +const TAU = 2 * PI; +const cl = (v, a, b) => (v < a ? a : v > b ? b : v); +const mix = (a, b, t) => a + (b - a) * t; +const hash = (i) => { + let h = Math.imul(i ^ 0x9e3779b9, 0x85ebca6b); + h = Math.imul(h ^ (h >>> 13), 0xc2b2ae35); + return ((h ^ (h >>> 16)) >>> 0) / 4294967296; +}; +const vn = (x, sd) => { + const i = floor(x), f = x - i, u = f * f * (3 - 2 * f); + return hash(i * 7919 + sd) * (1 - u) + hash((i + 1) * 7919 + sd) * u; +}; +function hsv(h, s, v, out) { + h = (h - floor(h)) * 6; + const i = floor(h) % 6, f = h - floor(h), p = v * (1 - s), q = v * (1 - f * s), t = v * (1 - (1 - f) * s); + out[0] = (i === 0 || i === 5 ? v : i === 1 ? q : i === 4 ? t : p) * 255; + out[1] = (i === 1 || i === 2 ? v : i === 0 ? t : i === 3 ? q : p) * 255; + out[2] = (i === 3 || i === 4 ? v : i === 2 ? t : i === 5 ? q : p) * 255; + return out; +} + +// --- shapes, each in its own frame with y down, as a distance (negative +// inside). A hole function marks dark sockets and gaps inside a bone. +const seg = (x, y, ax, ay, bx, by) => { + const dx = bx - ax, dy = by - ay, t = cl(((x - ax) * dx + (y - ay) * dy) / (dx * dx + dy * dy), 0, 1); + return sqrt((x - ax - dx * t) ** 2 + (y - ay - dy * t) ** 2); +}; +const ell = (x, y, cx, cy, rx, ry) => (sqrt(((x - cx) / rx) ** 2 + ((y - cy) / ry) ** 2) - 1) * min(rx, ry); +const box = (x, y, cx, cy, hx, hy, r) => { + const qx = abs(x - cx) - hx + r, qy = abs(y - cy) - hy + r; + return sqrt(max(qx, 0) ** 2 + max(qy, 0) ** 2) + min(max(qx, qy), 0) - r; +}; +// Kinds: 0 long bone, 1 paired long bones (forearm, shin), 2 rib cage with +// collarbones and neck, 3 lumbar spine, 4 pelvis, 5 skull, 6 jaw, 7 hand, +// 8 foot. A long bone is centred on its origin along y, L long. +function long(x, y, L, w, r) { + const e = L / 2 - r, q = 0.6 * r, ry = r * 0.9; + return min(seg(x, y, 0, -e, 0, e) - w, ell(x, y, -q, -e, r, ry), ell(x, y, q, -e, r, ry), ell(x, y, -q, e, r, ry), ell(x, y, q, e, r, ry)); +} +const RIBS = [[7.6, 2.4], [9.4, 6.6], [10.4, 10.8], [10.4, 15], [9.2, 19]]; +function shape(k, x, y, sg) { + switch (k) { + case 0: + return long(x, y, sg, 0.85 + 0.015 * sg, 1.45); + case 1: { + const e = sg / 2 - 1.1; + return min(seg(x, y, -1, -e, -1, e) - 0.75, seg(x, y, 1.1, -e, 1.1, e) - 0.55, box(x, y, 0, -e - 0.2, 2.3, 1.1, 1), box(x, y, 0, e + 0.2, 2.1, 1.1, 1)); + } + case 2: { + // Origin at the top of the breastbone; the neck rises above it. + let d = min(box(x, y, 0, -3.4, 1.5, 0.75, 0.5), box(x, y, 0, -1.4, 1.6, 0.75, 0.5)); + d = min(d, seg(x, y, -1.4, 0.8, -11.4, -0.2) - 0.75, seg(x, y, 1.4, 0.8, 11.4, -0.2) - 0.75, seg(x, y, 0, 1, 0, 14) - 1.15); + for (let v = 15.5; v < 24; v += 2.7) d = min(d, box(x, y, 0, v, 1.6, 0.9, 0.5)); + for (const [rx, ry] of RIBS) { + const c = 3.6 / (rx * rx), ax = min(abs(x), rx), f = ry + c * ax * ax; + d = min(d, abs(x) <= rx ? abs(y - f) / sqrt(1 + (2 * c * ax) ** 2) - 0.62 : sqrt((abs(x) - rx) ** 2 + (y - f) ** 2) - 0.62); + } + return d; + } + case 3: { + let d = 9; + for (let v = -4; v <= 4.1; v += 2.7) d = min(d, box(x, y, 0, v, 1.9, 0.95, 0.5), box(x, y, 0, v, 3.1, 0.35, 0.3)); + return d; + } + case 4: { + // Origin between the hip sockets: two wings tipped outward, the sacrum + // narrowing down between them, and two rings round the holes of the + // lower pelvis. + let d = max(abs(x) - (2.5 - 0.28 * (y + 5.5)), abs(y + 2.6) - 3); + for (let s = -1; s <= 1; s += 2) { + const dx = x - s * 5.3, dy = y + 3.6, c = 0.8, n = 0.6 * s; + d = min(d, ell(dx * c + dy * n, -dx * n + dy * c, 0, 0, 4.1, 2.9), abs(ell(x, y, s * 3.4, 2.4, 2.4, 1.9)) - 0.75, ell(x, y, s * 6.5, 0, 1.5, 1.5)); + } + return d; + } + case 5: + // Origin at the top of the neck; the skull rises above it. + return min(ell(x, y, 0, -10.6, 7, 7.3), ell(x, y, 0, -6.8, 6.4, 2.3), box(x, y, 0, -3.2, 4.4, 2.6, 1.4)); + case 6: { + const hw = 4.3 - 0.4 * max(0, y); + return min(box(x, y, 0, 1.5, 4.3, 1.6, 0.8), max(abs(x) - hw, abs(y - 1.6) - 1.8)); + } + case 7: { + let d = ell(x, y, 0, 1.4, 1.7, 1.3); + for (let i = 0; i < 4; i++) d = min(d, seg(x, y, -1.2 + 0.8 * i, 2.2, -1.8 + 1.2 * i, 5.4) - 0.42); + return d; + } + default: + return min(ell(x, y, sg * 0.8, 1.6, 3.1, 1.4), ell(x, y, 0, 0.4, 1.4, 1)); + } +} +// 0 none, 1 dark, 2 an eye socket, 3 in shade. +function hole(k, x, y, P) { + if (k === 5) { + const p = P.pitch * 3; + if (((abs(x) - 2.9) / 2.1) ** 2 + ((y + 7.9 - p) / 2.4) ** 2 < 1) return 2; + const ny = y + 5.6 - p; + if (ny >= 0 && ny < 1.7 && abs(x) < 0.3 + 0.5 * ny) return 1; + if (y > -2 + p * 0.5 && y < -0.5 && abs(x) < 4) return (x + 4) / 1.45 - floor((x + 4) / 1.45) < 0.3 || y > -0.9 ? 1 : 0; + } else if (k === 6 && y < 1.2 && y > -0.2 && abs(x) < 3.8) return (x + 4) / 1.45 - floor((x + 4) / 1.45) < 0.3 ? 1 : 0; + // The hollow of each pelvic wing, in shade. + else if (k === 4 && ((abs(x) - 4.6) / 2.3) ** 2 + ((y + 4.4 + 0.3 * abs(x)) / 1.3) ** 2 < 1) return 3; + return 0; +} + +// The pieces, back to front. [kind, length or side, the bounds of the shape +// in its own frame]. +const PIECES = [ + [8, -1, -3.9, 3.9, -1, 3.2], [8, 1, -3.9, 3.9, -1, 3.2], + [1, 24, -2.4, 2.4, -12.4, 12.4], [1, 24, -2.4, 2.4, -12.4, 12.4], + [0, 26, -2.5, 2.5, -13.3, 13.3], [0, 26, -2.5, 2.5, -13.3, 13.3], + [4, 0, -11, 11, -9, 5.2], [3, 0, -3.5, 3.5, -5.3, 5.3], [2, 0, -12.2, 12.2, -4.5, 22.6], + [0, 19, -2.2, 2.2, -9.8, 9.8], [0, 19, -2.2, 2.2, -9.8, 9.8], + [1, 17, -2.4, 2.4, -8.9, 8.9], [1, 17, -2.4, 2.4, -8.9, 8.9], + [7, 0, -2.6, 2.6, -0.2, 6], [7, 0, -2.6, 2.6, -0.2, 6], + [6, 0, -4.4, 4.4, -0.3, 3.5], [5, 0, -7.2, 7.2, -18.1, 0.6], +]; +const NB = PIECES.length, SKULL = 16, JAW = 15; +const FLOOR = 120, CX = 32, HIPY = 67, HIP = 7, FEMUR = 26, TIBIA = 24, ARM = 19, FORE = 17; + +// --- the body: a pose to where each piece sits, [x, y, angle] with the +// angle turning the piece's own y onto the bone. +const at = (P, i, x, y, a) => { + P.x[i] = x; + P.y[i] = y; + P.a[i] = a; +}; +const ang = (dx, dy) => atan2(-dx, dy); +function bone(P, i, ax, ay, bx, by) { + at(P, i, (ax + bx) / 2, (ay + by) / 2, ang(bx - ax, by - ay)); +} +// Two-bone reach with the knee bent outward. +function leg(P, s, hx, hy, fx, fy) { + const dx = fx - hx, dy = fy - hy, d = cl(sqrt(dx * dx + dy * dy), 3, FEMUR + TIBIA - 0.01); + const al = Math.acos(cl((FEMUR * FEMUR + d * d - TIBIA * TIBIA) / (2 * FEMUR * d), -1, 1)), b = atan2(dy, dx) - s * al; + const kx = hx + FEMUR * cos(b), ky = hy + FEMUR * sin(b), u = (fx - kx) / sqrt((fx - kx) ** 2 + (fy - ky) ** 2), v = (fy - ky) / sqrt((fx - kx) ** 2 + (fy - ky) ** 2); + bone(P, 4 + (s > 0), hx, hy, kx, ky); + const ex = kx + TIBIA * u, ey = ky + TIBIA * v; + bone(P, 2 + (s > 0), kx, ky, ex, ey); + at(P, s > 0 ? 1 : 0, ex, ey, 0); +} +const rot = (a, x, y) => [x * cos(a) - y * sin(a), x * sin(a) + y * cos(a)]; +// Pose numbers: sway, bob, pelvis tilt, torso lean, head roll, nod, jaw, +// each arm's lift from hanging and elbow bend, each foot's lift and step. +function rig(D, P) { + const rx = CX + D.sway, ry = HIPY + D.bob, t = D.tilt, l = D.lean; + at(P, 6, rx, ry, t); + for (const s of [-1, 1]) { + const [hx, hy] = rot(t, s * HIP, 0), f = s < 0 ? D.fl : D.fr; + leg(P, s, rx + hx, ry + hy, CX + s * (HIP + f[1]), FLOOR - 3 - f[0]); + } + const [b0, b1] = rot(t, 0, -5), sx = rx + b0, sy = ry + b1, [c0, c1] = rot(l, 0, -10); + bone(P, 7, sx + c0, sy + c1, sx, sy); + const [d0, d1] = rot(l, 0, -24), tx = sx + c0 + d0, ty = sy + c1 + d1; + at(P, 8, tx, ty, l); + for (const s of [-1, 1]) { + const [o0, o1] = rot(l, s * 12, 0.4), ax = tx + o0, ay = ty + o1, a1 = s < 0 ? D.al : D.ar, a2 = s < 0 ? D.bl : D.br; + const [u0, u1] = rot(l, s * sin(a1), cos(a1)), ex = ax + ARM * u0, ey = ay + ARM * u1; + const [v0, v1] = rot(l, s * sin(a1 + a2), cos(a1 + a2)), wx = ex + FORE * v0, wy = ey + FORE * v1; + bone(P, 9 + (s > 0), ax, ay, ex, ey); + bone(P, 11 + (s > 0), ex, ey, wx, wy); + at(P, 13 + (s > 0), wx, wy, ang(v0, v1)); + } + const [n0, n1] = rot(l, 0, -4.4), hd = l + D.head, nx = tx + n0, ny = ty + n1; + at(P, SKULL, nx, ny, hd); + const [j0, j1] = rot(hd, 0, -0.7 + 2.2 * D.jaw + D.pitch * 0.6); + at(P, JAW, nx + j0, ny + j1, hd); + P.pitch = D.pitch; +} + +const REST = { sway: 0, bob: 0, tilt: 0, lean: 0, head: 0, pitch: 0, jaw: 0, al: 0.12, bl: 0.08, ar: 0.12, br: 0.08, fl: [0, 0], fr: [0, 0] }; +const clack = (b) => max(0, sin(TAU * b)) ** 2; +// Three dances, each a function of the beat. +function dance(m, b, D) { + Object.assign(D, REST, { fl: [0, 0], fr: [0, 0], jaw: clack(b) }); + if (m === 0) { + // Sway: hips side to side over four beats, both arms up waving. + const s = sin((PI * b) / 2), w = sin(PI * b); + Object.assign(D, { sway: 3 * s, tilt: 0.1 * s, lean: -0.08 * s, head: -0.16 * s, al: 2.5 + 0.18 * w, ar: 2.5 - 0.18 * w, bl: 0.3 + 0.25 * w, br: 0.3 + 0.25 * w }); + } else if (m === 1) { + // Kicks: a foot out to the side each beat, hands on the hips. + const k = sin(PI * (b - floor(b))) ** 2, s = floor(b) & 1 ? 1 : -1; + D[s < 0 ? "fl" : "fr"] = [7 * k, 5 * k]; + Object.assign(D, { sway: -s * 1.2 * k, head: s * 0.12 * k, al: 0.75, ar: 0.75, bl: -1.45, br: -1.45 }); + } else { + // Bounce: a dip at the knees on every beat, the arms pumping up in turn + // over two beats. + const e = 0.5 - 0.5 * cos(PI * b), w = sin(PI * b); + Object.assign(D, { bob: 0.6 - 0.6 * cos(TAU * b), sway: 1.5 * w, head: 0.14 * w, al: 0.5 + 1.2 * e, bl: 0.3 + 0.6 * e, ar: 1.7 - 1.2 * e, br: 0.9 - 0.6 * e }); + } +} + +// The heap, as [x, y, angle] for each piece with the floor at 120: shins +// and feet flat, thighs crossed, arms on top, the cage on its side, the +// pelvis tipped up against it and the skull on the crown, jaw beside it. +const HEAP = [ + [-25, 117.5, 0.2], [24, 117.5, -0.3], [-14, 116.4, 1.62], [13, 116.4, 1.5], [-4, 115.6, 1.32], [6, 115.6, 1.86], + [-9, 108.5, 0.55], [12, 111.5, 1.9], [4, 104.5, -1.32], [-15, 112, 1.1], [17, 112.8, 2.1], [-20, 113.5, 1.9], [21, 114.8, 1.3], + [-27, 116.2, 2.4], [28, 116.4, -2.2], [15, 106.5, 0.45], [-2, 99.5, 0.3], +]; +const BEAM = (x, y) => { + // The spotlight's cone from above the panel, 18 LEDs across at the top + // and 56 at the floor. + const hw = 9 + y * 0.16, d = abs(x + 0.5 - CX) / hw; + return d < 0.8 ? 1 : d < 1 ? 0.78 : d < 1.12 ? 0.4 : 0; +}; + +const pose = () => ({ x: new Float64Array(NB), y: new Float64Array(NB), a: new Float64Array(NB), pitch: 0 }); + +export function setup(params) { + params.s = { + hue: -1, bpm: 100, every: 4, fog: 0.06, beat: 0, t: 0, + phase: 0, start: 0, base: 0, moves: 0, + D: Object.assign({}, REST), Q: pose(), R: pose(), + fall: [], heap: [], eye: 0, snap: [], light: [0, 0, 0], + bg: new Float32Array(N * 3), buf: new Float32Array(N * 3), low: new Int16Array(W), + dust: Array.from({ length: 30 }, () => [CX + (Math.random() - 0.5) * 36, Math.random() * 120, Math.random() * TAU, 2 + Math.random() * 3]), + }; + rig(REST, params.s.R); +} + +// Everything comes down: each piece from where it is now, flung up a little, +// falls onto its place in the heap and bounces once. +function collapse(S) { + if (S.phase === 0 || !S.heap.length) { + const m = Math.random() < 0.5 ? -1 : 1; + S.heap = HEAP.map(([x, y, a]) => [CX + m * x + (Math.random() - 0.5) * 2, y, m * a + (Math.random() - 0.5) * 0.3]); + } + S.fall = S.heap.map(([hx, hy, ha], i) => { + const x0 = S.Q.x[i], y0 = S.Q.y[i], a0 = S.Q.a[i], v = 4 + Math.random() * 10, g = 520; + const T = (v + sqrt(v * v + 2 * g * max(0, hy - y0))) / g; + let da = ha - a0; + da -= TAU * round(da / TAU); + return { x0, y0, a0, hx, hy, da, v, g, T }; + }); + S.Q.pitch = 0; + S.phase = 1; + S.start = S.beat; +} + +export function update(dt, input, params) { + if (!params.s) setup(params); + const S = params.s, k = input.knobValues || [0, 2, 4, 0.06], d = cl(dt || 0, 0, 0.1); + S.bpm = 50 * cl(k[1], 0.2, 3); + S.every = round(cl(k[2], 4, 32)); + S.fog = cl(k[3], 0, 1); + const hue = cl(k[0], 0, 1); + if (hue !== S.hue) { + S.hue = hue; + hsv(0.76 + hue, 0.62, 1, S.light); + background(S); + } + const db = (d * S.bpm) / 60; + S.beat += db; + S.t += d; + const B = S.beat, Q = S.Q, D = S.D; + if (input.btnPressed && input.btnPressed[0] && S.phase !== 1) collapse(S); + S.snap = S.snap.filter((s) => (s[2] -= d) > 0); + if (S.phase === 0) { + // Dancing, eased in from standing after the skeleton gets up. + if (B >= S.base + S.every) { + collapse(S); + } else { + dance(S.moves % 3, B, D); + const w = cl((B - S.start) / 0.6, 0, 1), s = w * w * (3 - 2 * w); + for (const key in REST) if (typeof REST[key] === "number") D[key] = mix(REST[key], D[key], s); + for (const f of ["fl", "fr"]) D[f] = [D[f][0] * s, D[f][1] * s]; + rig(D, Q); + S.eye = max(0, S.eye - db * 0.8); + } + } + if (S.phase === 1) { + const u = B - S.start; + S.fall.forEach((f, i) => { + const t = min(u, f.T), r = t / f.T, b = u - f.T; + Q.x[i] = mix(f.x0, f.hx, r); + Q.y[i] = min(f.hy, f.y0 - f.v * t + 0.5 * f.g * t * t); + Q.a[i] = f.a0 + f.da * r; + if (b > 0 && b < 0.3) Q.y[i] -= 2 * sin((PI * b) / 0.3) * (1 - b / 0.3); + }); + // The heap lies still, then the skull's eyes kindle. + S.eye = cl((u - 1.6) / 0.8, 0, 1); + if (u > 2.5) { + S.phase = 2; + S.start = B; + } + } + if (S.phase === 2) { + // One piece every 0.3 beats from the feet up, each on an arc 0.45 beats + // long; the jaw and the skull go together, last, in 0.8 beats. + const u = B - S.start, R = S.R; + for (let i = 0; i < NB; i++) { + const slot = min(i, JAW), t0 = slot * 0.3, len = slot === JAW ? 0.8 : 0.45, r = (u - t0) / len, [hx, hy, ha] = S.heap[i]; + if (r <= 0) at(Q, i, hx, hy, ha); + else if (r >= 1) { + at(Q, i, R.x[i], R.y[i], R.a[i]); + if (r - db / len < 1) S.snap.push([R.x[i], R.y[i] - (i === SKULL ? 2 : 0), 0.12]); + } else { + let da = R.a[i] - ha; + da -= TAU * round(da / TAU); + const lift = 10 + 0.15 * (hy - R.y[i]), e = 1 - (1 - r) * (1 - r); + at(Q, i, mix(hx, R.x[i], r), mix(hy, R.y[i], e) - lift * sin(PI * r) * (1 - r * 0.3), ha + da * e); + } + } + // The nod: the skull dips its face and the jaw shuts, 0.6 beats. + const n = (u - JAW * 0.3 - 0.8) / 0.6; + Q.pitch = n > 0 && n < 1 ? sin(PI * n) : 0; + if (n > 0) { + Q.y[SKULL] += 2.5 * Q.pitch; + Q.y[JAW] += 3.2 * Q.pitch; + } + if (n >= 1) { + S.phase = 0; + S.start = B; + S.base = ceil(B); + S.moves++; + } + } + for (const m of S.dust) { + m[1] += d * m[3]; + m[0] += d * 1.5 * sin(S.t * 0.7 + m[2]); + if (m[1] > FLOOR) (m[1] = 0), (m[0] = CX + (Math.random() - 0.5) * 34); + } +} + +// The crypt, laid out once for each light colour: stone blocks with dark +// mortar, the beam on the wall, a pool on the flagstones, two candle niches. +const NICHES = [8, 56], NY = 78; +function background(S) { + const bg = S.bg, L = S.light; + for (let y = 0; y < H; y++) + for (let x = 0; x < W; x++) { + const o = (y * W + x) * 3; + let r, g, b, lit; + if (y < FLOOR) { + const row = floor(y / 7), off = row & 1 ? 6 : 0, col = floor((x + off) / 12); + const mortar = y % 7 === 6 || (x + off) % 12 === 11, v = hash(row * 31 + col * 7) * 10; + (r = mortar ? 10 : 26 + v), (g = mortar ? 6 : 19 + v * 0.7), (b = mortar ? 16 : 36 + v); + lit = BEAM(x, y) * (mortar ? 0.12 : 0.24); + for (const nx of NICHES) + if (abs(x + 0.5 - nx) < 3.5 && y > NY - 8 && y < NY + 4 && (y > NY - 5 || (x + 0.5 - nx) ** 2 + (y - NY + 5) ** 2 < 12)) (r = 8), (g = 4), (b = 12), (lit = 0); + } else { + // Flagstones, and the pool where the beam lands. + const seam = y === FLOOR + 3 || (x + (y > FLOOR + 3 ? 5 : 0)) % 14 === 0; + (r = seam ? 12 : 30), (g = seam ? 8 : 22), (b = seam ? 18 : 36); + if (y === FLOOR) (r = 60), (g = 48), (b = 70); + const q = ((x + 0.5 - CX) / 30) ** 2 + ((y - FLOOR - 3) / 6) ** 2; + lit = (q < 0.45 ? 0.62 : q < 1 ? 0.42 : q < 1.5 ? 0.18 : 0) * (seam ? 0.5 : 1); + } + bg[o] = r + L[0] * lit; + bg[o + 1] = g + L[1] * lit; + bg[o + 2] = b + L[2] * lit; + } +} + +// Ivory in the light, a warmer grey across, the shade tinted by the beam. +const BONE = [[255, 244, 220], [212, 202, 180], [96, 88, 80]]; +const put = (buf, o, r, g, b) => { + buf[o] = r; + buf[o + 1] = g; + buf[o + 2] = b; +}; +// One piece onto the buffer: its distance field over its bounds, lit from +// above by the field's slope, outlined dark where it passes over anything. +const FIELD = new Float32Array(48 * 48); +function piece(S, i, P) { + const [k, sg, x0, x1, y0, y1] = PIECES[i], cx = P.x[i], cy = P.y[i], a = P.a[i], c = cos(a), s = sin(a); + let xa = 1e9, xb = -1e9, ya = 1e9, yb = -1e9; + for (const [u, v] of [[x0, y0], [x1, y0], [x0, y1], [x1, y1]]) { + const wx = cx + u * c - v * s, wy = cy + u * s + v * c; + (xa = min(xa, wx)), (xb = max(xb, wx)), (ya = min(ya, wy)), (yb = max(yb, wy)); + } + xa = max(0, floor(xa) - 1); + xb = min(W - 1, ceil(xb) + 1); + ya = max(0, floor(ya) - 1); + yb = min(H - 1, ceil(yb) + 1); + const buf = S.buf, L = S.light; + if (xb < xa || yb < ya || xb - xa >= 48 || yb - ya >= 48) return; + for (let y = ya; y <= yb; y++) + for (let x = xa; x <= xb; x++) { + const dx = x + 0.5 - cx, dy = y + 0.5 - cy; + FIELD[(y - ya) * 48 + x - xa] = shape(k, dx * c + dy * s, -dx * s + dy * c, sg); + } + for (let y = ya; y <= yb; y++) + for (let x = xa; x <= xb; x++) { + const f = (y - ya) * 48 + x - xa, d = FIELD[f], o = (y * W + x) * 3; + if (d > 0.1) { + if (d < 1.1) for (let ch = 0; ch < 3; ch++) buf[o + ch] *= 0.3; + continue; + } + if (y > 60 && y > S.low[x]) S.low[x] = y; + const dx = x + 0.5 - cx, dy = y + 0.5 - cy, hl = hole(k, dx * c + dy * s, -dx * s + dy * c, P); + if (hl === 1 || hl === 2) { + const e = hl === 2 ? S.eye * (0.75 + 0.25 * sin(S.t * 9 + i)) : 0; + put(buf, o, 12 + L[0] * e, 6 + L[1] * e, 18 + L[2] * e); + continue; + } + // The slope of the field: where it climbs upward the bone faces the light. + const up = y > ya ? FIELD[f - 48] : d + 1, dn = y < yb ? FIELD[f + 48] : d + 1, n = (up - dn) / (abs(up - dn) + abs((x > xa ? FIELD[f - 1] : d) - (x < xb ? FIELD[f + 1] : d)) + 0.01); + const t = hl === 3 || n <= -0.3 ? 2 : n > 0.4 || (d > -0.6 && up > 0.1) ? 0 : 1, beam = 0.72 + 0.28 * BEAM(x, y), w = t === 2 ? 0.36 : 0; + for (let ch = 0; ch < 3; ch++) buf[o + ch] = (BONE[t][ch] + L[ch] * w) * beam; + } +} + +function add(buf, x, y, r, g, b) { + x = round(x); + y = round(y); + if (x < 0 || x >= W || y < 0 || y >= H) return; + const o = (y * W + x) * 3; + buf[o] += r; + buf[o + 1] += g; + buf[o + 2] += b; +} + +export function draw(display, params) { + const S = params.s; + if (!S || S.hue < 0) return; + const buf = S.buf, L = S.light, low = S.low; + buf.set(S.bg); + // Candles in the niches: a flame that flickers in three steps and a glow. + NICHES.forEach((nx, j) => { + const v = 0.55 + 0.3 * vn(S.t * 3, j * 9) + 0.15 * vn(S.t * 11, j * 9 + 4), f = v > 0.72 ? 1 : v > 0.55 ? 0.82 : 0.66; + for (let y = NY - 12; y < NY + 8; y++) + for (let x = nx - 8; x < nx + 8; x++) { + const q = sqrt((x + 0.5 - nx) ** 2 + (y + 0.5 - NY + 2) ** 2), gl = (q < 3.5 ? 0.24 : q < 5.5 ? 0.14 : q < 8 ? 0.06 : 0) * f; + if (gl) add(buf, x, y, 255 * gl, 140 * gl, 40 * gl); + } + for (let y = NY; y < NY + 4; y++) put(buf, (y * W + nx - 1) * 3, 196, 184, 160), put(buf, (y * W + nx) * 3, 196, 184, 160); + const sw = round(vn(S.t * 5, j) * 2 - 1); + for (let i = 0; i < (f > 0.9 ? 4 : 3); i++) { + const o = ((NY - 1 - i) * W + nx - 1 + (i > 1 ? sw : 0)) * 3, g = i ? 150 + 30 * f : 236, b = i ? 30 : 140; + put(buf, o, 255, g, b); + if (i < 2) put(buf, o + 3, 255, g * 0.92, b); + } + }); + // Dust drifting down the beam. + for (const [x, y, p] of S.dust) { + const tw = 0.45 + 0.35 * sin(S.t * 2.3 + p * 5); + if (BEAM(x, y) > 0.5) add(buf, x, y, (L[0] * 0.5 + 60) * tw, (L[1] * 0.5 + 60) * tw, (L[2] * 0.5 + 60) * tw); + } + // Bones; a shadow on the floor under whatever stands or lies over it. + low.fill(0); + // Pieces in the air go in front of the rest. + const u = S.beat - S.start, air = (i) => S.phase === 2 && u > min(i, JAW) * 0.3 && u < min(i, JAW) * 0.3 + 0.9; + for (let pass = 0; pass < 2; pass++) for (let i = 0; i < NB; i++) if (air(i) === !!pass) piece(S, i, S.Q); + for (let x = 0; x < W; x++) + if (low[x] > 0) { + const sh = low[x] > FLOOR - 8 ? 0.45 : 0.65; + for (let y = low[x] + 1; y < min(H, FLOOR + 5); y++) if (y >= FLOOR) for (let ch = 0; ch < 3; ch++) buf[(y * W + x) * 3 + ch] *= sh; + } + for (const [x, y, t] of S.snap) for (const [i, j] of [[0, 0], [1, 0], [-1, 0], [0, 1], [0, -1]]) add(buf, x + i, y + j, 200 * t * 8, 190 * t * 8, 170 * t * 8); + // Ground fog: two drifting noises, deeper toward the floor, in steps of an eighth. + const fog = S.fog, top = 118 - 50 * fog; + for (let y = max(0, floor(top)); y < H && fog > 0; y++) { + const depth = cl((y - top) / 16, 0, 1) * (0.25 + 0.5 * fog); + for (let x = 0; x < W; x++) { + const n = 0.6 * vn(x * 0.09 + S.t * 0.35 + y * 0.03, 3 + (y >> 3)) + 0.4 * vn(x * 0.21 - S.t * 0.5, 17 + (y >> 2)); + const al = floor(depth * (0.35 + 0.9 * n) * 8) / 8; + if (al <= 0) continue; + const o = (y * W + x) * 3, a = min(0.85, al); + for (let ch = 0; ch < 3; ch++) buf[o + ch] = mix(buf[o + ch], 70 + L[ch] * 0.32, a); + } + } + for (let p = 0, o = 0; p < N; p++, o += 3) display.setPixel(p % W, (p / W) | 0, min(255, buf[o]) & 252, min(255, buf[o + 1]) & 252, min(255, buf[o + 2]) & 252); +} diff --git a/incoming/pattern-study/patterns/double-gyre.js b/incoming/pattern-study/patterns/double-gyre.js new file mode 100644 index 000000000..c4193ea6e --- /dev/null +++ b/incoming/pattern-study/patterns/double-gyre.js @@ -0,0 +1,253 @@ +// ===== Patternflow pattern ===== +// Title: Double Gyre +// Family: noise +// Idea: Two counter-rotating cells of fluid stand one above the other while the line between them sways up and down, and each LED lights by how hard the fluid round it is torn apart over the next twenty units of flow time, in amber, and over the last twenty, in azure, so the thin ridges that fluid never crosses wind round both cells, fold into tongues across the middle and stand the picture on its head every half turn of the forcing. +// Author: patternflow-lab +// Date: 2026-10-25 +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Lineage: Lagrangian coherent structures as ridges of the finite-time Lyapunov exponent in the double gyre (Shawn Shadden, Francois Lekien and Jerrold Marsden, 2005), drawn by composing short flow maps (Steven Brunton and Clarence Rowley, 2010), pattern round 48 +// Round: 48 +// =============================== +// @matrix 64x128 +// @knobs Hue=0..1, Speed=0.2..3, Horizon=-1..1, Epsilon=0..1 +// +// Knob 1: Hue. Turns both colours round the wheel together, half a wheel apart. At 0 the forward ridges, which push fluid apart, are amber and the backward ones, which draw it together, azure, and where they cross they add to white; at 0.25 green and magenta, at 0.5 azure and amber, at 0.75 magenta and green. +// Knob 2: Speed. The starting time of the fields moves half a unit of flow time a second for each unit: 1 at the opening 2, so the forcing goes round once in 10 s and the picture stands on its head every 5 s; 0.1 at 0.2, a period in 100 s, and 1.5 at 3, a period in 6.7 s. +// Knob 3: Horizon. How long the fluid is followed, 20 units of flow time times 2 to the knob, to the nearest 2: 10 at -1, one period of the forcing, a few soft ridges round the cells and one across the middle; 14 at -0.5; 20 at the opening 0, two periods, the ridges folded into bundles of thin lines; 28 at 0.5 and 40 at 1, where the folds crowd closer than an LED and the chaotic sea round the cells turns to grain round two dark islands. +// Knob 4: Epsilon. How far the line between the cells sways, epsilon = 0.5 ln(1 + 1000 k) / ln 1001: 0 at 0, steady cells, where at a horizon of 10 the only ridges are the dividing line and the walls, white with the forward and backward ones lying on each other, and longer horizons add rings of shear round each cell; 0.17 at 0.01; 0.30 at the opening 0.06, where the line swings 18 LEDs either way of the middle; 0.40 at a quarter, 0.45 at half and 0.50 at 1, 26 LEDs either way. + +const W = 64, H = 128, N = W * H; +const { floor, round, min, max, sqrt, log, sin, PI } = Math; +const cl = (v, a, b) => (v < a ? a : v > b ? b : v); + +// The flow, on [0,2] x [0,1]: psi = A sin(pi f) sin(pi y), with +// f = a x^2 + b x, a = eps sin(OM t), b = 1 - 2a. The panel stands the +// box up: row py is x = (py + 0.5) / 64, column px is y = (px + 0.5) / 64. +const A = 0.1, OM = (2 * PI) / 10, PER = 10, PA = PI * A; +// sin(pi u) from a table over one turn, for u above -4. +const TN = 2048, TH = TN / 2, ST = new Float64Array(TN + 1); +for (let i = 0; i <= TN; i++) ST[i] = sin((2 * PI * i) / TN); +const sinpi = (u) => { + const w = (u + 4) * TH, i = w | 0, j = i & (TN - 1); + return ST[j] + (ST[j + 1] - ST[j]) * (w - i); +}; + +// The velocity at (x, y) when a = s, into VU, VV. +let VU = 0, VV = 0; +function vel(x, y, s) { + const b = 1 - 2 * s, f = s * x * x + b * x, sy = sinpi(y), cy = sinpi(y + 0.5); + VU = -PA * sinpi(f) * cy; + VV = PA * sinpi(f + 0.5) * sy * (2 * s * x + b); +} + +// Short flow maps on a grid of nodes 1/64 apart, Brunton and Rowley's way: +// a drop is carried by looking up where the node grid round it goes, and +// a long flight is a chain of lookups. The quarter-unit maps that start at +// each of the 40 quarter units of a period are one RK4 step each; maps of +// a half, one and two units are each two of the level below. +const G = 0.25, NP = PER / G, NX = 129, NY = 65, NN = NX * NY, STEP = 8; +function buildMaps(eps) { + let lv = []; + for (let k = 0; k < NP / 2; k++) { + const m = new Float32Array(NN * 2), t = k * G; + const s0 = eps * sin(OM * t), s1 = eps * sin(OM * (t + G / 2)), s2 = eps * sin(OM * (t + G)); + for (let n = 0, j = 0; j < NX; j++) + for (let i = 0; i < NY; i++, n += 2) { + const x = j / 64, y = i / 64; + vel(x, y, s0); + const k1u = VU, k1v = VV; + vel(x + 0.5 * G * k1u, y + 0.5 * G * k1v, s1); + const k2u = VU, k2v = VV; + vel(x + 0.5 * G * k2u, y + 0.5 * G * k2v, s1); + const k3u = VU, k3v = VV; + vel(x + G * k3u, y + G * k3v, s2); + m[n] = cl(x + (G / 6) * (k1u + 2 * k2u + 2 * k3u + VU), 0, 2); + m[n + 1] = cl(y + (G / 6) * (k1v + 2 * k2v + 2 * k3v + VV), 0, 1); + } + lv.push(m); + } + // Half a period on, the flow is the same turned upside down: psi(2 - x, + // y, t + 5) = -psi(x, y, t). So the maps of the second half are those of + // the first mirrored, node for node, at every length. + const mirror = () => { + for (let k = 0; k < NP / 2; k++) { + const a = lv[k], m = new Float32Array(NN * 2); + for (let j = 0; j < NX; j++) + for (let i = 0; i < NY; i++) { + const n = (j * NY + i) * 2, q = ((NX - 1 - j) * NY + i) * 2; + m[n] = 2 - a[q]; + m[n + 1] = a[q + 1]; + } + lv[k + NP / 2] = m; + } + }; + mirror(); + // Doubling: a map of 2L is the map of L from where the first L ends. + for (let L = 1; L < STEP; L *= 2) { + const up = []; + for (let k = 0; k < NP / 2; k++) { + const a = lv[k], m = new Float32Array(NN * 2); + chain(a, lv[(k + L) % NP], m, NN); + up.push(m); + } + lv = up; + mirror(); + } + return lv; +} +// Bilinear lookup of map b at each point of a, into out. +function chain(a, b, out, n2) { + for (let n = 0; n < n2 * 2; n += 2) { + let u = a[n] * 64, v = a[n + 1] * 64; + if (u > 127.9999) u = 127.9999; + if (v > 63.9999) v = 63.9999; + const j = u | 0, i = v | 0, fu = u - j, fv = v - i, q = (j * NY + i) * 2, e = q + 2 * NY; + const x0 = b[q] + (b[q + 2] - b[q]) * fv, x1 = b[e] + (b[e + 2] - b[e]) * fv; + const y0 = b[q + 1] + (b[q + 3] - b[q + 1]) * fv, y1 = b[e + 1] + (b[e + 3] - b[e + 1]) * fv; + out[n] = x0 + (x1 - x0) * fu; + out[n + 1] = y0 + (y1 - y0) * fu; + } +} + +// The forward field for the node time k G: each LED's drop carried T units +// on through a chain of two-unit maps, rows r0..r1 of it. +function carry(S, J, r0, r1) { + const { X, Y } = J, nm = S.T / 2; + // Every drop of the band goes through the same maps in the same order, so + // the band is carried map by map. + for (let p = r0 * W; p < r1 * W; p++) { + X[p] = (floor(p / W) + 0.5) / 64; + Y[p] = ((p % W) + 0.5) / 64; + } + for (let q = 0, k = J.k; q < nm; q++, k = (k + STEP) % NP) { + const b = S.maps[k]; + for (let p = r0 * W; p < r1 * W; p++) { + let u = X[p] * 64, v = Y[p] * 64; + if (u > 127.9999) u = 127.9999; + if (v > 63.9999) v = 63.9999; + const j = u | 0, i = v | 0, fu = u - j, fv = v - i, n = (j * NY + i) * 2, e = n + 2 * NY; + const x0 = b[n] + (b[n + 2] - b[n]) * fv, x1 = b[e] + (b[e + 2] - b[e]) * fv; + const y0 = b[n + 1] + (b[n + 3] - b[n + 1]) * fv, y1 = b[e + 1] + (b[e + 3] - b[e + 1]) * fv; + X[p] = x0 + (x1 - x0) * fu; + Y[p] = y0 + (y1 - y0) * fu; + } + } +} + +// From the flow map to the picture: the log of the largest stretch of the +// Cauchy-Green tensor by central differences (sigma is this over T), then +// each LED pushed away from the mean of the nine round it, so thin ridges +// stand out of the broad glow round them. +const SHARP = 5; +function finish(J) { + const { X, Y, L } = J, d = 1 / 64, out = new Float32Array(N); + for (let py = 0; py < H; py++) + for (let px = 0; px < W; px++) { + const p = py * W + px, u = py > 0 ? p - W : p, dn = py < H - 1 ? p + W : p, l = px > 0 ? p - 1 : p, r = px < W - 1 ? p + 1 : p; + const hx = ((dn - u) / W) * d, hy = (r - l) * d; + const a = (X[dn] - X[u]) / hx, b = (X[r] - X[l]) / hy, c = (Y[dn] - Y[u]) / hx, e = (Y[r] - Y[l]) / hy; + const c11 = a * a + c * c, c12 = a * b + c * e, c22 = b * b + e * e, m = 0.5 * (c11 + c22); + L[p] = 0.5 * log(max(1e-9, m + sqrt(max(0, m * m - (c11 * c22 - c12 * c12))))); + } + for (let py = 0; py < H; py++) + for (let px = 0; px < W; px++) { + let s = 0, n = 0; + for (let j = max(0, py - 1); j <= min(H - 1, py + 1); j++) + for (let i = max(0, px - 1); i <= min(W - 1, px + 1); i++) (s += L[j * W + i]), n++; + const p = py * W + px; + out[p] = L[p] + SHARP * (L[p] - s / n); + } + return out; +} + +export function setup(params) { + params.s = { + hue: 0, rate: 1, T: 20, eps: -1, t0: 0, maps: null, + // The sharpened log stretch at each node time of the period, worked out + // once for each epsilon and horizon, and the one being worked on. + field: [], job: null, cf: [0, 0, 0], cb: [0, 0, 0], + X: new Float32Array(N), Y: new Float32Array(N), L: new Float32Array(N), + }; +} + +const node = (n) => ((n % NP) + NP) % NP; + +// Finishes any field the picture needs this frame, then works up to rows +// rows on the ones it will need next. +function work(S, rows) { + const n = floor(S.t0 / G), need = [n, n + 1, -n - 1, -n, n + 2, -n - 2, n + 3, -n - 3].map(node); + for (let q = 0; q < need.length; q++) { + const k = need[q]; + if (S.field[k]) continue; + if (q >= 4 && rows <= 0) return; + if (!S.job || S.job.k !== k) S.job = { k, row: 0, X: S.X, Y: S.Y, L: S.L }; + const J = S.job, r1 = q < 4 ? H : min(H, J.row + rows); + carry(S, J, J.row, r1); + rows -= r1 - J.row; + J.row = r1; + if (r1 === H) { + S.field[k] = finish(J); + S.job = null; + } + } +} + +export function update(dt, input, params) { + if (!params.s) setup(params); + const S = params.s, k = input.knobValues || [0, 2, 0, 0.06], d = cl(dt || 0, 0, 0.1); + S.hue = cl(k[0], 0, 1); + S.rate = 0.5 * cl(k[1], 0.2, 3); + const T = 2 * round(10 * 2 ** cl(k[2], -1, 1)); + const eps = round(500 * ((0.5 * log(1 + 1000 * cl(k[3], 0, 1))) / log(1001))) / 500; + if (eps !== S.eps) { + S.eps = eps; + S.maps = buildMaps(eps); + S.field = []; + S.job = null; + } + if (T !== S.T) { + S.T = T; + S.field = []; + S.job = null; + } + S.t0 = (S.t0 + d * S.rate) % PER; + // Two fields for each quarter unit the start time moves, a little ahead. + work(S, Math.ceil(((2.4 * H * d * S.rate) / G) * (S.T / 20)) + 8); +} + +function hsv(h, s, v, out) { + h = (h - floor(h)) * 6; + const i = floor(h) % 6, f = h - floor(h), p = v * (1 - s), q = v * (1 - f * s), t = v * (1 - (1 - f) * s); + out[0] = (i === 0 || i === 5 ? v : i === 1 ? q : i === 4 ? t : p) * 255; + out[1] = (i === 1 || i === 2 ? v : i === 0 ? t : i === 3 ? q : p) * 255; + out[2] = (i === 3 || i === 4 ? v : i === 2 ? t : i === 5 ? q : p) * 255; +} + +// An LED lights from a stretch of e^2.4 = 11 times to full at e^3.6 = 37. +const STRETCH = 2.4, SPAN = 1.2; + +export function draw(display, params) { + const S = params.s; + if (!S || !S.maps) return; + const cf = S.cf, cb = S.cb, n = floor(S.t0 / G), w = S.t0 / G - n; + // Running time backward is the same flow turned half round: the fluid + // carried back from t0 lands where fluid carried on from -t0 lands, with + // the panel turned 180 degrees (psi(x, y, -t) = psi(x, y, t + 5) and + // psi(x, 1 - y, t) = psi(x, y, t)). So the backward field at t0 is the + // forward field at -t0 upside down and back to front. + const f0 = S.field[node(n)], f1 = S.field[node(n + 1)], b0 = S.field[node(-n)], b1 = S.field[node(-n - 1)]; + if (!f0 || !f1 || !b0 || !b1) return; + hsv(0.1 + S.hue, 0.86, 1, cf); + hsv(0.6 + S.hue, 0.8, 1, cb); + for (let py = 0, p = 0; py < H; py++) { + for (let px = 0; px < W; px++, p++) { + const q = N - 1 - p; + const vf = cl((f0[p] + (f1[p] - f0[p]) * w - STRETCH) / SPAN, 0, 1); + const vb = cl((b0[q] + (b1[q] - b0[q]) * w - STRETCH) / SPAN, 0, 1); + if (vf + vb < 0.02) continue; + // Steps of 4 of 255. + const r = min(255, cf[0] * vf + cb[0] * vb), g = min(255, cf[1] * vf + cb[1] * vb), b = min(255, cf[2] * vf + cb[2] * vb); + display.setPixel(px, py, (r >> 2) << 2, (g >> 2) << 2, (b >> 2) << 2); + } + } +} From 8c4a027ecc9f7b6905a83afce5fb848e3e765f32 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 25 Oct 2026 19:56:57 +0000 Subject: [PATCH 5/8] =?UTF-8?q?pattern-study:=20M=C3=B6bius=20Energy,=20Ba?= =?UTF-8?q?rn=20Owl=20(48=ED=8C=90)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_015fDPQzQykg7X6Bo8o35Zx2 --- incoming/pattern-study/patterns/barn-owl.js | 489 ++++++++++++++++ .../pattern-study/patterns/mobius-energy.js | 535 ++++++++++++++++++ 2 files changed, 1024 insertions(+) create mode 100644 incoming/pattern-study/patterns/barn-owl.js create mode 100644 incoming/pattern-study/patterns/mobius-energy.js diff --git a/incoming/pattern-study/patterns/barn-owl.js b/incoming/pattern-study/patterns/barn-owl.js new file mode 100644 index 000000000..6c8a3446e --- /dev/null +++ b/incoming/pattern-study/patterns/barn-owl.js @@ -0,0 +1,489 @@ +// ===== Patternflow pattern ===== +// Title: Barn Owl +// Family: halloween +// Idea: A gnarled dead tree stands the full height of a purple night, roots in the grass and bare branches across a big full moon, where a pale barn owl swivels its heart-shaped face far round and back and blinks, then drops off and swoops down the whole panel with big wingbeats to grab a mouse in the grass, or miss it, and beats back up as dead leaves fall. +// Author: patternflow-lab +// Date: 2026-10-25 +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Lineage: original, pattern round 48 +// Round: 48 +// =============================== +// @matrix 64x128 +// @knobs Hue=0..1, Speed=0.2..3, Hunger=0..1, Wind=0..1 +// +// Knob 1: Hue. The sky's colour; moon, owl, tree and leaves stay. At 0 a violet night, at 0.25 a deep red one, at 0.5 a dark olive green one and at 0.75 a dark teal one, each a little paler towards the grass. +// Knob 2: Speed. At 2 the swoop from branch to grass takes 1.4 s, the owl stands over its catch 0.55 s and the climb back takes 2.3 s at three wingbeats a second; a leaf falls from the branches to the grass in 6 to 10 s. At 0.2 ten times slower, at 3 half as fast again. +// Knob 3: Hunger. How long the owl sits between hunts at Speed 2: 6 s at 0, 4 s at half and 1.5 s at 1, give or take 15%, the first hunt at 2.4 s, 1.6 s and 0.7 s. It goes for the further mouse and catches two in three; a caught one hangs from its talons and is swallowed on the branch 2 s later. +// Knob 4: Wind. At 0.06 the crown stirs by a LED, a tenth of the sky up top is cloud drifting right at 5 LEDs a second and a leaf comes off about every 2 s; at 0.5 the crown sways up to 5 LEDs, a quarter is cloud at 15 a second and a leaf comes off each second; at 1 the crown sways up to 10 LEDs, two fifths is cloud racing over the moon at 25 a second and the leaves blow sideways at 22. +// Buttons: K1 tap sends the owl down now, if it is on its branch. + +const W = 64, H = 128, N = W * H; +const { floor, round, ceil, min, max, abs, sqrt, sin, cos, exp, PI } = Math; +const TAU = 2 * PI; +const cl = (v, a, b) => (v < a ? a : v > b ? b : v); +const sm = (v) => ((v = cl(v, 0, 1)), v * v * (3 - 2 * v)); +const rnd = (a, b) => a + (b - a) * Math.random(); +const hash = (i) => { + let h = Math.imul(i ^ 0x9e3779b9, 0x85ebca6b); + h = Math.imul(h ^ (h >>> 13), 0xc2b2ae35); + return ((h ^ (h >>> 16)) >>> 0) / 4294967296; +}; +function hsv(h, s, v, out) { + h = (h - floor(h)) * 6; + const i = floor(h) % 6, f = h - floor(h), p = v * (1 - s), q = v * (1 - f * s), t = v * (1 - (1 - f) * s); + out[0] = (i === 0 || i === 5 ? v : i === 1 ? q : i === 4 ? t : p) * 255; + out[1] = (i === 1 || i === 2 ? v : i === 0 ? t : i === 3 ? q : p) * 255; + out[2] = (i === 3 || i === 4 ? v : i === 2 ? t : i === 5 ? q : p) * 255; + return out; +} + +// The moon, behind the upper branches on the right. +const MX = 43, MY = 26, MR = 17; +const groundY = (x) => 121 + round(1.2 * sin(x / 6 + 0.5) + 0.7 * sin(x / 2.3)); + +// The tree: limbs as [parent limb, point on it, x, y, half width for each +// point]. The trunk kinks up the left third; the owl sits on limb 2. +const LIMBS = [ + [-1, 0, [14, 128, 5.6, 14, 121, 4.6, 16, 112, 3.9, 13, 101, 3.6, 17, 90, 3.3, 15, 79, 3.1, 19, 68, 2.8, 18, 58, 2.5, 21, 49, 2.2, 20, 39, 1.9, 24, 29, 1.6, 23, 19, 1.2, 26, 9, 0.8, 25, 3, 0.5]], + [0, 1, [14, 121, 3, 8, 123, 2.2, 3, 126, 1.5, 0, 128, 1.2]], + [0, 8, [21, 49, 1.8, 28, 47, 1.6, 35, 47, 1.3, 42, 45, 1, 48, 42, 0.7, 52, 37, 0.5]], + [0, 1, [15, 121, 2.8, 22, 124, 2, 28, 127, 1.3]], + [0, 9, [20, 40, 1.4, 27, 34, 1.2, 34, 31, 1, 41, 26, 0.8, 47, 24, 0.6, 54, 19, 0.5]], + [0, 11, [23, 21, 1, 28, 15, 0.8, 33, 12, 0.6, 37, 6, 0.5]], + [0, 7, [18, 60, 1.5, 11, 55, 1.2, 6, 50, 0.9, 3, 43, 0.6, 1, 38, 0.5]], + [0, 10, [24, 31, 1.2, 17, 26, 0.9, 11, 21, 0.7, 7, 14, 0.5]], + [0, 5, [15, 80, 1.6, 9, 75, 1.1, 5, 69, 0.7]], + [4, 2, [34, 31, 0.7, 37, 37, 0.5, 41, 39, 0.5]], + [4, 3, [41, 26, 0.6, 45, 31, 0.5, 49, 32, 0.4]], + [2, 3, [42, 45, 0.6, 46, 50, 0.5, 49, 51, 0.4]], + [5, 1, [28, 15, 0.6, 31, 7, 0.5, 30, 2, 0.4]], + [7, 2, [11, 21, 0.6, 14, 15, 0.5, 13, 9, 0.4]], + [6, 2, [6, 50, 0.6, 1, 52, 0.4]], + [4, 4, [47, 24, 0.5, 52, 27, 0.4, 57, 26, 0.4]], + [0, 12, [26, 9, 0.6, 30, 4, 0.4]], + [8, 1, [9, 75, 0.7, 4, 78, 0.5, 1, 77, 0.4]], +]; +// Each point's distance up the tree from the root, for the sway. +const TREE = LIMBS.map(([par, at, P]) => ({ par, at, P, n: P.length / 3, s: new Float32Array(P.length / 3) })); +for (const L of TREE) { + if (L.par >= 0) L.s[0] = TREE[L.par].s[L.at]; + for (let i = 1; i < L.n; i++) L.s[i] = L.s[i - 1] + Math.hypot(L.P[i * 3] - L.P[i * 3 - 3], L.P[i * 3 + 1] - L.P[i * 3 - 2]); +} +// The perch: on limb 2 between its second and third points. +const PERCH = [2, 1, 0.55]; + +// The moon's tone (limb, sea, bright) and its glow in rings. +const MOONT = new Uint8Array(N), GLOW = new Float32Array(N); +const SEAS = [[-6, -5, 5, 4], [5, -2, 4, 5], [-2, 6, 6, 3], [7, 8, 3, 3]]; +for (let p = 0; p < N; p++) { + const dx = (p % W) + 0.5 - MX, dy = floor(p / W) + 0.5 - MY, r = sqrt(dx * dx + dy * dy); + if (r < MR) { + const sea = SEAS.some(([cx, cy, rx, ry]) => ((dx - cx) / rx) ** 2 + ((dy - cy) / ry) ** 2 < 1); + MOONT[p] = r > MR - 1.2 ? 1 : sea ? 2 : 3; + } else GLOW[p] = r < MR + 3 ? 0.26 : r < MR + 7 ? 0.15 : r < MR + 13 ? 0.08 : r < MR + 22 ? 0.035 : 0; +} +const MOONC = [0, [240, 196, 120], [255, 238, 196], [255, 222, 150]]; +// Clouds: a noise sheet that wraps round, slid along by the wind; CQ is it +// sorted, so a cover maps straight to a threshold. +const CW = 128, CH = 24, CN = new Float32Array(CW * CH); +for (let p = 0; p < CW * CH; p++) + for (const [c, a] of [[8, 0.6], [4, 0.3], [2, 0.15]]) { + const n = CW / c, u = (p % CW) / c, w = floor(p / CW) / (c / 2), iu = floor(u), iw = floor(w), su = sm(u - iu), sw = sm(w - iw); + const hh = (x, y) => hash((((x % n) + n) % n) * 131 + y * 977 + c * 7); + CN[p] += a * ((hh(iu, iw) * (1 - su) + hh(iu + 1, iw) * su) * (1 - sw) + (hh(iu, iw + 1) * (1 - su) + hh(iu + 1, iw + 1) * su) * sw); + } +const CQ = Float32Array.from(CN).sort(); +const STARS = []; +for (let k = 0, x, y; STARS.length < 26; k++) if (((x = floor(hash(k * 3 + 1) * W)) - MX) ** 2 + ((y = floor(hash(k * 3 + 2) * 84)) - MY) ** 2 > 625) STARS.push([x, y, hash(k * 3 + 3) * TAU]); + +const CREAM = [250, 240, 220], BUFF = [238, 176, 96], GOLD = [242, 196, 128], FACE = [255, 252, 246], RIM = [222, 140, 76]; +const DOT = [196, 160, 136], EYE = [18, 6, 12], LID = [214, 168, 140], BEAK = [236, 198, 168], LEG = [214, 180, 168], LINE = [26, 10, 34]; +const LEAFC = [[255, 148, 36], [255, 196, 64], [250, 108, 40]], GRASS = [70, 150, 64], BLADE = [34, 86, 44], EARTH = [14, 10, 18]; + +export function setup(params) { + params.s = { + t: 0, cloud: 282, begun: false, mice: [], leaves: [], leafT: 0.3, + mode: 0, mt: 0, ox: 0, oy: 0, tilt: 0, spread: 0, flap: 0, talons: 0, face: 0, blink: 0, carry: 0, eat: 0, look: [], lt: 0, + sky: new Float32Array(H * 3), skyKey: -1, buf: new Float32Array(N * 3), tm: new Uint8Array(N), ob: new Float32Array(48 * 48 * 3), om: new Uint8Array(48 * 48), + }; +} + +// Limb k at point i and a share f on, swayed: tips most, a wave up the tree. +function limbAt(S, k, i, f, out) { + const L = TREE[k], P = L.P, j = min(i + 1, L.n - 1); + const x = P[i * 3] + (P[j * 3] - P[i * 3]) * f, y = P[i * 3 + 1] + (P[j * 3 + 1] - P[i * 3 + 1]) * f, s = L.s[i] + (L.s[j] - L.s[i]) * f; + const a = ((s * s) / 12000) * (0.3 + 5.7 * S.wind); + const g = sin(S.t * 1.7 - s * 0.035) + 0.45 * sin(S.t * 4.1 + s * 0.11 + k); + out[0] = x + a * g; + out[1] = y + a * g * 0.25 * (x < 20 ? -1 : 1) * 0.5; + return out; +} + +const tmp = [0, 0], tmp2 = [0, 0]; +function perch(S) { + limbAt(S, PERCH[0], PERCH[1], PERCH[2], tmp); + return [tmp[0], tmp[1] - 1.2]; +} + +const mouse = (x) => ({ x, tx: x, y: 0, wait: rnd(0.2, 1.2), dir: 1, hide: 0, run: 0, flee: 0 }); +const leaf = (x, y, c) => ({ x, y, c, ph: rnd(0, TAU), w: rnd(2.5, 4.5), vy: rnd(10, 15) }); + +// A cubic Bézier through four points. +const bez = (p, t, k) => { + const u = 1 - t; + return u * u * u * p[k] + 3 * u * u * t * p[2 + k] + 3 * u * t * t * p[4 + k] + t * t * t * p[6 + k]; +}; + +function startHunt(S) { + const live = S.mice.filter((m) => m.hide <= 0 && !m.flee); + if (!live.length) return false; + const [px, py] = perch(S); + let m = live[0]; + for (const q of live) if (abs(q.x - px) > abs(m.x - px)) m = q; + Object.assign(S, { prey: m, catchIt: Math.random() < 0.67, mode: 1, mt: 0, px, py }); + return true; +} + +// The path down: off the branch, out wide on the side away from the mouse, +// and in low over the grass to it. +function swoopPath(S) { + const m = S.prey, ex = cl(m.x + m.dir * 6, 8, 56), ey = groundY(round(ex)) - 8, side = ex > S.ox ? -1 : 1; + S.path = [S.ox, S.oy, S.ox + side * 26, S.oy + 34, ex - side * 30, ey - 26, ex, ey]; +} + +function climbPath(S) { + const [px, py] = perch(S), side = S.ox < 32 ? 1 : -1; + S.path = [S.ox, S.oy, S.ox + side * 22, S.oy - 40, px + side * 30, py + 30, px, py - 10]; +} + +const T_DROP = 0.35, T_SWOOP = 1.4, T_STRIKE = 0.55, T_CLIMB = 2.3, T_LAND = 0.5; + +function owl(S, ds) { + S.mt += ds; + const [px, py] = perch(S); + const bx = S.ox, by = S.oy; + if (S.mode === 0) { + // On the branch: feet on the limb, the head looking about. + Object.assign(S, { ox: px, oy: py - 9, spread: max(0, S.spread - ds * 3), talons: 0, flap: 0, tilt: S.tilt * 0.8 }); + if (S.carry && (S.eat += ds) > 2.2) (S.carry = 0), (S.eat = 0); + // The head: a turn far round to one side, back, a glance the other way. + S.lt += ds; + if (!S.look.length || S.lt > S.look[S.look.length - 1][0]) { + const far = rnd(0.5, 1) < 0.5 ? -1 : 1; + S.look = [[0, S.face], [0.5, far * rnd(1.8, 2.6)], [1.6, far * rnd(1.8, 2.6)], [2.2, 0], [2.9, 0], [3.3, -far * rnd(0.6, 1.1)], [3.9, -far * 0.8], [4.3, 0], [5.4, 0]]; + S.lt = 0; + } + let k = 1; + while (k < S.look.length - 1 && S.look[k][0] < S.lt) k++; + const [t0, a0] = S.look[k - 1], [t1, a1] = S.look[k]; + S.face = a0 + (a1 - a0) * sm((S.lt - t0) / max(1e-6, t1 - t0)); + S.blink = (S.lt % 2.7) > 2.55 ? 1 : 0; + if ((S.wait -= ds) < 0 && !S.carry && !startHunt(S)) S.wait = 1; + return; + } + S.blink = 0; + S.face *= exp(-ds * 10); + const t = S.mt; + if (S.mode === 1) { + // Off the branch: the wings open as it falls. + const f = t / T_DROP; + Object.assign(S, { spread: sm(f * 1.4), flap: 0.8 - 0.6 * f, ox: S.px + 3 * f, oy: S.py - 9 + 6 * f * f }); + if (t >= T_DROP) (S.mode = 2), (S.mt = 0), swoopPath(S); + } else if (S.mode === 2) { + // The swoop: two beats, then a long glide; the end follows the mouse. + const f = min(1, t / T_SWOOP), u = f * f * (3 - 2 * f) * 0.6 + f * 0.4, m = S.prey; + S.path[6] += (cl(m.x, 6, 58) - S.path[6]) * min(1, ds * 3); + Object.assign(S, { ox: bez(S.path, u, 0), oy: bez(S.path, u, 1), spread: 1, talons: sm((f - 0.7) / 0.3) }); + S.flap = t < 0.75 ? 0.15 + 0.7 * sin(TAU * 2.6 * t) : 0.1 + 0.25 * sm((f - 0.8) / 0.2); + if (f >= 1) { + (S.mode = 3), (S.mt = 0); + if (S.catchIt && abs(m.x - S.ox) < 6 && !m.flee) (S.carry = 1), (m.hide = rnd(3, 6)), (m.x = rnd(0, 1) < 0.5 ? -4 : 68); + else (m.flee = 1), (m.dir = m.x < S.ox ? -1 : 1); + } + } else if (S.mode === 3) { + // On the grass: wings held high over the catch, a beat, and off. + Object.assign(S, { spread: 1, talons: 1, flap: 0.95 - 0.4 * sin(PI * cl(t / T_STRIKE, 0, 1)), oy: S.oy + sin(t * 9) * ds * 3 }); + if (t >= T_STRIKE) (S.mode = 4), (S.mt = 0), climbPath(S); + } else if (S.mode === 4) { + // The climb back up: hard wingbeats, three a second. + const f = min(1, t / T_CLIMB), u = f * f * (3 - 2 * f); + (S.path[6] = px), (S.path[7] = py - 10); + Object.assign(S, { ox: bez(S.path, u, 0), oy: bez(S.path, u, 1), spread: 1, flap: 0.2 + 0.75 * sin(TAU * 3 * t), talons: S.carry ? 1 : max(0, 1 - t * 3) }); + if (f >= 1) (S.mode = 5), (S.mt = 0); + } else { + // Landing: a flare and the wings fold. + const f = min(1, t / T_LAND); + Object.assign(S, { ox: px, oy: py - 10 + f, flap: 0.9, spread: 1 - sm(f), talons: S.carry ? 1 - f : 0 }); + if (f >= 1) (S.mode = 0), (S.mt = 0), (S.wait = waitOf(S)), (S.look = []), (S.eat = 0); + } + const vx = (S.ox - bx) / max(1e-4, ds); + S.tilt += (cl(vx * 0.012, -0.45, 0.45) - S.tilt) * min(1, ds * 6); +} + +const waitOf = (S) => 6 - 4.5 * S.hunger * (1 - 0.25 * (1 - S.hunger)) * rnd(0.85, 1.15); + +function mice(S, ds) { + for (const m of S.mice) { + m.y = groundY(cl(round(m.x), 0, W - 1)) - 1; + if (m.hide > 0) { + if ((m.hide -= ds) <= 0) Object.assign(m, { x: rnd(0, 1) < 0.5 ? -3 : 67, tx: rnd(12, 52), flee: 0, wait: 0 }); + } else if (m.flee) { + (m.x += m.dir * 60 * ds), (m.run += ds * 14); + if (m.x < -6 || m.x > 70) (m.hide = rnd(1.5, 3)), (m.flee = 0); + } else if (m === S.prey && S.mode === 2) { + // A last dash as the owl comes in. + (m.x += m.dir * 18 * ds), (m.run += ds * 12); + if (m.x < 6 || m.x > 58) m.dir = -m.dir; + } else if ((m.wait -= ds) < 0) { + const d = m.tx - m.x; + m.dir = d < 0 ? -1 : 1; + (m.x += m.dir * min(abs(d), 26 * ds)), (m.run += ds * 12); + if (abs(d) < 0.5) (m.wait = rnd(0.3, 1.6)), (m.tx = cl(m.x + rnd(-24, 24), 6, 58)); + } + } +} + +function leaves(S, ds) { + S.leafT -= ds * (0.4 + 1.6 * S.wind); + if (S.leafT < 0 && S.leaves.length < 4 + floor(6 * S.wind)) { + S.leafT += rnd(0.6, 1.4); + const k = [2, 4, 5, 6, 7, 9, 10, 11, 13, 15][floor(rnd(0, 10))], L = TREE[k]; + limbAt(S, k, floor(rnd(1, L.n - 1)), rnd(0, 1), tmp2); + S.leaves.push(leaf(tmp2[0], tmp2[1], floor(rnd(0, 3)))); + } + S.leaves = S.leaves.filter((l) => { + l.ph += ds * l.w; + l.x += (S.wind * 22 + 7 * sin(l.ph)) * ds; + l.y += l.vy * (0.65 + 0.35 * abs(cos(l.ph))) * ds; + return l.y < groundY(cl(round(l.x), 0, W - 1)) && l.x > -4 && l.x < 68; + }); +} + +export function update(dt, input, params) { + if (!params.s) setup(params); + const S = params.s, k = input.knobValues || [0, 2, 0, 0.06], d = cl(dt || 0, 0, 0.1); + [S.hue, S.speed, S.hunger, S.wind] = [cl(k[0], 0, 1), cl(k[1], 0.2, 3), cl(k[2], 0, 1), cl(k[3], 0, 1)]; + const ds = (d * S.speed) / 2; + if (!S.begun) { + S.begun = true; + S.mice = [mouse(14), mouse(46)]; + S.wait = 2.4 - 1.7 * S.hunger; + // A few leaves already on their way down. + for (let i = 0; i < 3; i++) S.leaves.push(leaf(rnd(8, 56), rnd(20, 110), i)); + } + S.t += ds; + S.cloud += ds * (4 + 21 * S.wind); + if (input.btnPressed && input.btnPressed[0] && S.mode === 0) (S.carry = 0), startHunt(S); + owl(S, ds); + mice(S, ds); + leaves(S, ds); +} + +// The sky by row, in ten flat steps: deep overhead, paler low down. +function skyRows(S) { + if (S.skyKey === S.hue) return; + S.skyKey = S.hue; + for (let y = 0, t; y < H; y++) (t = floor((y / H) * 10) / 9), S.sky.set(hsv(0.76 + S.hue + 0.03 * t, 0.84 - 0.2 * t, 0.2 + 0.2 * t, [0, 0, 0]), 3 * y); +} + +function put(buf, x, y, c, f = 1) { + (x = round(x)), (y = round(y)); + if (x < 0 || x >= W || y < 0 || y >= H) return; + const o = (y * W + x) * 3; + (buf[o] = c[0] * f), (buf[o + 1] = c[1] * f), (buf[o + 2] = c[2] * f); +} + +// The owl, drawn in its own frame: u across, v down its body, origin at +// the middle of the body, turned by its tilt. +function owlPixel(S, u, v) { + const sp = S.spread, hx = u, hy = v + 6.4; + // The head: square on, the face is a white heart with a tan rim; turned, + // it narrows and slides aside, and past a right angle the back shows. + if ((hx / 5.4) ** 2 + (hy / 4.9) ** 2 < 1) { + const c = cos(S.face), fu = sin(S.face) * 3.8, sx = max(0.2, c); + if (c > -0.05) { + const uf = (u - fu) / sx, vf = v + 5.9; + const heart = (d) => (uf - 2) ** 2 + (vf + 1.1) ** 2 < (2.6 + d) ** 2 || (uf + 2) ** 2 + (vf + 1.1) ** 2 < (2.6 + d) ** 2 || (vf > -1.1 && vf < 3.8 + d && abs(uf) < ((3.8 + d - vf) * 4.6) / 4.9); + if (heart(0)) { + for (const e of [-1, 1]) if ((u - fu - e * 1.9 * sx) ** 2 + (v + 5.9) ** 2 < 0.7) return S.blink ? LID : EYE; + if ((u - fu) ** 2 + (v + 4.1) ** 2 < 0.55) return BEAK; + return FACE; + } + if (heart(0.9 / sx)) return RIM; + } + // A darker collar, and turned away a ruff, so the head reads as a head. + const e = (hx / 5.4) ** 2 + (hy / 4.9) ** 2; + if (e > 0.72 && (hy > 1.5 || c < 0)) return DOT; + return hash(round(u * 3) * 31 + round(v * 3) * 7) < 0.16 ? DOT : GOLD; + } + // Turned side on, the beak shows past the edge of the head. + if (abs(sin(S.face)) > 0.85 && cos(S.face) > -0.3 && abs(v + 4.6) < 0.6 && abs(u - sin(S.face) * 5.6) < 0.7) return BEAK; + // The body, an egg wider below: a cream breast between folded buff wings. + const ry = 6.4 - 0.8 * sp, cy = 2.2 - 0.4 * sp, rx = (5.4 - 1.4 * sp) * (1 + 0.1 * cl((v - cy) / ry, -1, 1)); + if ((u / rx) ** 2 + ((v - cy) / ry) ** 2 < 1) { + const breast = abs(u) < 2.7 - 0.5 * sp + (0.8 * (v - cy)) / ry; + if (breast) return hash(round(u * 2) * 13 + round(v * 2) * 5) < 0.08 ? DOT : CREAM; + return hash(round(u * 2) * 17 + round(v * 2) * 3) < 0.22 ? DOT : BUFF; + } + if (sp < 0.3 && v > 8.2 && v < 10.4 && abs(u) < (10.4 - v) * 0.9) return DOT; + // Legs and talons, reaching down when it strikes or carries. + if (S.talons > 0.05) for (const e of [-1, 1]) if (abs(u - e * 1.5) < 0.6 && v > 5 && v < 5.5 + 4.5 * S.talons) return LEG; + // The wings: a buff leading edge, cream behind, dark bars near the tip. + if (sp > 0.02) + for (const e of [-1, 1]) { + const sx = e * 2.6, sy = -1.6, L = 13 * sp, a = S.flap, tx = sx + e * L * cos(a), ty = sy - L * sin(a) * 0.9; + const ax = tx - sx, ay = ty - sy, l2 = ax * ax + ay * ay; + if (l2 < 1) continue; + const t = ((u - sx) * ax + (v - sy) * ay) / l2; + if (t < -0.05 || t > 1.04) continue; + const qx = u - (sx + ax * t), qy = v - (sy + ay * t), il = 1 / sqrt(l2); + // The trailing side is the one away from the head. + let nx = -ay * il, ny = ax * il; + if (ny < 0) (nx = -nx), (ny = -ny); + const dn = qx * nx + qy * ny, tc = cl(t, 0, 1), ch = 1.1 + 4.2 * sqrt(max(0, 1 - tc * tc)) * (1 - 0.25 * tc); + if (dn < -0.9 || dn > ch) continue; + if (tc > 0.7 && (tc * 12) % 1 < 0.3 && dn > 0.6) return DOT; + return dn < 0.7 ? BUFF : dn < ch * 0.55 ? GOLD : CREAM; + } + return null; +} + +function drawOwl(S, buf) { + const { ob, om } = S, cx = round(S.ox), cy = round(S.oy), ct = cos(S.tilt), st = sin(S.tilt); + om.fill(0); + for (let j = 0; j < 48; j++) + for (let i = 0; i < 48; i++) { + const dx = i - 24 + 0.5 + cx - S.ox, dy = j - 24 + 0.5 + cy - S.oy; + const c = owlPixel(S, dx * ct + dy * st, -dx * st + dy * ct); + if (!c) continue; + const o = j * 48 + i; + om[o] = 1; + (ob[o * 3] = c[0]), (ob[o * 3 + 1] = c[1]), (ob[o * 3 + 2] = c[2]); + } + for (let j = 0; j < 48; j++) + for (let i = 0; i < 48; i++) { + const x = cx - 24 + i, y = cy - 24 + j; + if (x < 0 || x >= W || y < 0 || y >= H) continue; + const o = j * 48 + i, p = (y * W + x) * 3; + if (om[o]) (buf[p] = ob[o * 3]), (buf[p + 1] = ob[o * 3 + 1]), (buf[p + 2] = ob[o * 3 + 2]); + // A dark line round it, so a pale owl stands off a pale moon. + else if ((i > 0 && om[o - 1]) || (i < 47 && om[o + 1]) || (j > 0 && om[o - 48]) || (j < 47 && om[o + 48])) (buf[p] = LINE[0]), (buf[p + 1] = LINE[1]), (buf[p + 2] = LINE[2]); + } +} + +// A mouse, five LEDs long with an ear, an eye and a tail, legs going. +const MOUSE = [204, 186, 172], TAIL = [226, 160, 168]; +function drawMouse(buf, x, y, dir, run) { + for (let i = -2; i <= 2; i++) put(buf, x + dir * i, y, MOUSE); + for (let i = -1; i <= 1; i++) put(buf, x + dir * i, y - 1, MOUSE); + put(buf, x + dir * 3, y, MOUSE, 0.85); + put(buf, x + dir * 1, y - 2, TAIL); + put(buf, x + dir * 2, y - 1, EYE); + for (let i = 0; i < 4; i++) put(buf, x - dir * (3 + i), y + (i > 1 ? -1 : 0), TAIL, 0.85); + put(buf, x + (floor(run) % 2 ? -1 : 1), y + 1, MOUSE, 0.7); +} + +export function draw(display, params) { + const S = params.s; + if (!S || !S.begun) return; + skyRows(S); + const { buf, sky, tm } = S; + const cover = 0.08 + 0.3 * S.wind, thr = CQ[floor((1 - cover) * (CQ.length - 1))], off = S.cloud; + // Sky, stars, moon and clouds. + for (let y = 0; y < H; y++) { + const fy = y / 3.2, j0 = floor(fy) % CH, j1 = (j0 + 1) % CH, ty = fy - floor(fy); + const band = y > 60 ? (y - 60) * 0.012 : 0; + for (let x = 0; x < W; x++) { + const p = y * W + x, o = 3 * p, mt = MOONT[p], gl = GLOW[p]; + let R = sky[3 * y], G = sky[3 * y + 1], B = sky[3 * y + 2]; + if (mt) { + const c = MOONC[mt]; + (R = c[0]), (G = c[1]), (B = c[2]); + } else if (gl) (R += 255 * gl), (G += 210 * gl), (B += 140 * gl); + if (y < 90) { + const fx = (x - off) * 0.3, i0 = floor(fx), tx = fx - i0, a0 = ((i0 % CW) + CW) % CW, a1 = (a0 + 1) % CW; + const n = (CN[j0 * CW + a0] * (1 - tx) + CN[j0 * CW + a1] * tx) * (1 - ty) + (CN[j1 * CW + a0] * (1 - tx) + CN[j1 * CW + a1] * tx) * ty; + const e = n - band - thr; + if (e > 0) { + const lit = min(1, (mt ? 1 : gl * 3.2) + 0.15); + if (e < 0.02) (R = 70 + 150 * lit), (G = 40 + 130 * lit), (B = 92 + 80 * lit); + else if (e < 0.06) (R = 50 + 60 * lit), (G = 26 + 50 * lit), (B = 70 + 34 * lit); + else (R = 36 + 20 * lit), (G = 16 + 14 * lit), (B = 52 + 12 * lit); + } + } + (buf[o] = R), (buf[o + 1] = G), (buf[o + 2] = B); + } + } + for (const [x, y, ph] of STARS) { + const tw = 0.5 + 0.5 * sin(S.t * 3 + ph * 5), o = (y * W + x) * 3; + if (buf[o + 2] < 120 && tw > 0.3) (buf[o] += 120 * tw), (buf[o + 1] += 110 * tw), (buf[o + 2] += 130 * tw); + } + // Leaves behind the tree. + drawLeaves(S, buf, 0); + // The tree: each limb a run of round-ended strokes, swayed. + tm.fill(0); + for (let k = 0; k < TREE.length; k++) { + const L = TREE[k]; + let ax = 0, ay = 0, aw = 0; + for (let i = 0; i < L.n; i++) { + limbAt(S, k, i, 0, tmp); + const bx = tmp[0], by = tmp[1], bw = L.P[i * 3 + 2]; + if (i) { + const x0 = max(0, floor(min(ax, bx) - 5)), x1 = min(W - 1, ceil(max(ax, bx) + 5)), y0 = max(0, floor(min(ay, by) - 5)), y1 = min(H - 1, ceil(max(ay, by) + 5)); + const dx = bx - ax, dy = by - ay, l2 = dx * dx + dy * dy || 1; + for (let y = y0; y <= y1; y++) + for (let x = x0; x <= x1; x++) { + const px = x + 0.5 - ax, py = y + 0.5 - ay, t = cl((px * dx + py * dy) / l2, 0, 1), w = aw + (bw - aw) * t; + if ((px - dx * t) ** 2 + (py - dy * t) ** 2 < w * w + 0.15) tm[y * W + x] = 1; + } + } + (ax = bx), (ay = by), (aw = bw); + } + } + for (let y = 0; y < H; y++) + for (let x = 0; x < W; x++) { + const p = y * W + x; + if (!tm[p]) continue; + const o = p * 3; + // Bark is black; an edge that faces the moon catches its light. + const right = x < W - 1 && !tm[p + 1], top = y > 0 && !tm[p - W]; + const towards = (MX - x) * (right ? 1 : 0) + (MY - y) * (top ? -1 : 0); + if ((right || top) && towards > 0) { + const f = min(1, 0.45 + GLOW[p] * 2.5 + (MOONT[p] ? 0.6 : 0)); + (buf[o] = 92 * f), (buf[o + 1] = 58 * f), (buf[o + 2] = 120 * f); + } else (buf[o] = 8), (buf[o + 1] = 3), (buf[o + 2] = 12); + } + // The ground, where the roots go in, and the grass in front of the mice. + for (let x = 0; x < W; x++) { + const g = groundY(x), h = 2 + floor(hash(x * 7 + 3) * 5), lean = (S.wind * 2.2 + 0.6 * sin(S.t * 2.3 + x * 0.4) * (0.3 + S.wind)) * 0.5; + for (let y = g; y < H; y++) if (!tm[y * W + x] || y >= g + 2) put(buf, x, y, EARTH); + for (let i = 1; i <= h; i++) { + if (hash(x * 13 + i) < 0.4 && i < h - 1) continue; + put(buf, x + lean * (i / h) * 2, g - i, i === h ? GRASS : BLADE); + } + } + for (const m of S.mice) if (m.hide <= 0) drawMouse(buf, m.x, m.y, m.dir, m.run); + // The owl, and the mouse it carries. + drawOwl(S, buf); + if (S.carry) { + if (S.mode !== 0 || S.eat < 1.3) drawMouse(buf, S.ox, S.oy + 5 + 4.5 * S.talons + 1, 1, 0); + else if (S.eat < 2.2) for (let i = 0; i < 3; i++) put(buf, S.ox + 0.5, S.oy - 3 + i, TAIL); + } + drawLeaves(S, buf, 1); + for (let p = 0, o = 0; p < N; p++, o += 3) { + // Channels in steps of 4 of 255, too fine to see. + display.setPixel(p % W, (p / W) | 0, min(255, round(buf[o] / 4) * 4), min(255, round(buf[o + 1] / 4) * 4), min(255, round(buf[o + 2] / 4) * 4)); + } +} + +// Leaves: three LEDs that turn over as they fall, half behind the tree. +function drawLeaves(S, buf, front) { + S.leaves.forEach((l, i) => { + if ((i & 1) !== front) return; + const c = LEAFC[l.c], k = floor((((l.ph % TAU) + TAU) % TAU) / (PI / 2)), f = 0.8 + 0.2 * abs(sin(l.ph)); + const [dx, dy] = [[1, 0], [1, 1], [0, 1], [-1, 1]][k]; + put(buf, l.x, l.y, c, f); + put(buf, l.x + dx, l.y + dy, c, f * 0.9); + put(buf, l.x - dx, l.y - dy, c, f * 0.8); + }); +} diff --git a/incoming/pattern-study/patterns/mobius-energy.js b/incoming/pattern-study/patterns/mobius-energy.js new file mode 100644 index 000000000..8c1e750bc --- /dev/null +++ b/incoming/pattern-study/patterns/mobius-energy.js @@ -0,0 +1,535 @@ +// ===== Patternflow pattern ===== +// Title: Möbius Energy +// Family: topology +// Idea: A knotted tube, crumpled by wringing the space around it, relaxes under the gradient flow of the Möbius energy, which blows up where strands approach, so it untangles only as far as its knot type allows: an unknot opens into a circle, a trefoil into its three-lobed shape, and a trace below logs the energy falling. +// Author: patternflow-lab +// Date: 2026-10-25 +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Lineage: Möbius energy of knots (Jun O'Hara, 1991; Michael Freedman, Zheng-Xu He and Zhenghan Wang, 1994), pattern round 48 +// Round: 48 +// =============================== +// @matrix 64x128 +// @knobs Hue=0..1, Speed=0.2..3, Knot=0..4, Crumple=0..1 +// +// Knob 1: Hue. The colour that walks along the tube. At 0 it runs from cyan through blue to violet and back round the loop; at 0.25 from violet through pink to orange, at 0.5 from red through yellow to green, at 0.75 from yellow-green to sky blue. +// Knob 2: Speed. At 2 the flow takes 50 steps a second; a crumpled trefoil opens in 7 to 11 s, the unknot in 5 to 8 s, the others in 9 to 30 s, then the knot holds 2 s and crumples over 2.5 s. At 0.2 ten times slower, at 3 half as fast again. +// Knob 3: Knot. 0 the trefoil 3_1, 1 the figure-eight 4_1, 2 the cinquefoil 5_1, 3 the three-twist knot 5_2, 4 the unknot. Turning it starts the new knot crumpled. +// Knob 4: Crumple. How hard space is deformed before each relaxation: a stretch along one axis, a wring about it and two twists about axes through the knot. At 0.06 the stretch is 1.8 times, the wring 3.9 rad each way and the twists 3.2 rad; at 0.5 2.6 times, 6 and 5 rad; at 1 3.4 times, 8.4 and 7 rad. It stops if two strands come within half a bead spacing. +// Buttons: K1 tap crumples the knot again now. + +const W = 64, H = 128, N = W * H; +const { floor, round, ceil, min, max, abs, sqrt, sin, cos, exp, log, PI } = Math; +const TAU = 2 * PI; +const cl = (v, a, b) => (v < a ? a : v > b ? b : v); +const sm = (v) => ((v = cl(v, 0, 1)), v * v * (3 - 2 * v)); +const rnd = (a, b) => a + (b - a) * Math.random(); + +// A closed polygon of NB beads x_i. With l_i the mean of the two edges at +// bead i and D(i, j) the shorter way round from i to j, +// E = sum over i != j of (1 / |x_i - x_j|^2 - 1 / D(i, j)^2) l_i l_j, +// O'Hara's integral on the beads: blind to size, 4 on a round circle, its +// least value (Freedman, He and Wang). The flow keeps each edge's length, so +// l_i and D stay put and a step follows the exact gradient. Added: a barrier +// between edges nearer than the mean edge, which beads cannot see cross, and +// bending stiffness, as E is blind to inversions of space too and lets a +// relaxed knot drift. A step is kept only if the sum went down. +const NB = 120, N3 = NB * 3; +// The (2,3) and (2,5) torus knots, the figure-eight, the Lissajous knot 5_2 +// (frequencies 3, 2, 7, phases 0.7, 0.2, 0) and a bent circle. +const tor = (p, q, r) => (t) => [(2 + cos(q * t)) * cos(p * t), (2 + cos(q * t)) * sin(p * t), sin(r * t)]; +const KNOTS = [tor(2, 3, 3), tor(3, 2, 4), tor(2, 5, 5), (t) => [cos(3 * t + 0.7), cos(2 * t + 0.2), cos(7 * t)], (t) => [cos(t), sin(t), 0.25 * sin(2 * t)]]; +// LEDs a unit and the tube's radius for each knot. +const ZOOM = [28, 22, 24, 15, 27], TUBE = [2.6, 2.3, 2.1, 1.8, 2.7]; +// Steps a second at Speed 2, a bead's largest move in one, crumple and hold s. +const SPS = 50, VCAP = 0.0055, CRT = 2.5, HOLD = 2; +// Barrier weight; bending, BEND L times the integral of curvature squared +// (7.9 on a circle: weaker, 4_1 and 5_2 pull a clasp tight by a big loop, +// which E alone prices the same); the stretch at Crumple 0. +const BAR = 2, BEND = 0.2, STR = 1.7; +// The knot's middle row; the trace's rows. +const KY = 50, TY0 = 100, TY1 = 124, TN = 64; + +function hsv(h, s, v, out, o) { + h = (h - floor(h)) * 6; + const i = floor(h) % 6, f = h - floor(h), p = v * (1 - s), q = v * (1 - f * s), t = v * (1 - (1 - f) * s); + out[o] = i === 0 || i === 5 ? v : i === 1 ? q : i === 4 ? t : p; + out[o + 1] = i === 1 || i === 2 ? v : i === 0 ? t : i === 3 ? q : p; + out[o + 2] = i === 3 || i === 4 ? v : i === 2 ? t : i === 5 ? q : p; +} + +// Fourier tables along the curve; mode k is smoothed by k^-2.5. +const KM = 32, CS = new Float64Array(NB * (KM + 1)), SN = new Float64Array(NB * (KM + 1)), PK = new Float64Array(KM + 1); +for (let k = 1; k <= KM; k++) { + for (let i = 0; i < NB; i++) (CS[k * NB + i] = cos((TAU * k * i) / NB)), (SN[k * NB + i] = sin((TAU * k * i) / NB)); + PK[k] = 2 / (k * k * sqrt(k)); +} + +function centre(X) { + let a = 0, b = 0, c = 0; + for (let i = 0; i < N3; i += 3) (a += X[i]), (b += X[i + 1]), (c += X[i + 2]); + (a /= NB), (b /= NB), (c /= NB); + for (let i = 0; i < N3; i += 3) (X[i] -= a), (X[i + 1] -= b), (X[i + 2] -= c); +} + +// Edge lengths into LE, running sums into CUM; returns the length. +const LE = new Float64Array(NB), CUM = new Float64Array(NB + 1); +function lengths(X) { + for (let i = 0; i < NB; i++) { + const o = i * 3, q = ((i + 1) % NB) * 3; + LE[i] = sqrt((X[q] - X[o]) ** 2 + (X[q + 1] - X[o + 1]) ** 2 + (X[q + 2] - X[o + 2]) ** 2); + CUM[i + 1] = CUM[i] + LE[i]; + } + return CUM[NB]; +} + +// Knot k's beads, evened along it, centred, the furthest one unit out. +function nice(k, X, Y) { + for (let i = 0; i < NB; i++) X.set(KNOTS[k]((TAU * i) / NB), i * 3); + for (let g = 0; g < 6; g++) resample(X, Y), X.set(Y); + centre(X); + let r = 0; + for (let i = 0; i < N3; i += 3) r = max(r, X[i] ** 2 + X[i + 1] ** 2 + X[i + 2] ** 2); + for (let i = 0; i < N3; i++) X[i] /= sqrt(r); +} + +// Edges i and j: their distance, and in SD where along each the closest +// points lie and the vector between them. +const SD = new Float64Array(5); +function segd(X, i, j) { + const p = i * 3, pe = ((i + 1) % NB) * 3, r = j * 3, re = ((j + 1) % NB) * 3; + const d1x = X[pe] - X[p], d1y = X[pe + 1] - X[p + 1], d1z = X[pe + 2] - X[p + 2]; + const d2x = X[re] - X[r], d2y = X[re + 1] - X[r + 1], d2z = X[re + 2] - X[r + 2]; + const rx = X[p] - X[r], ry = X[p + 1] - X[r + 1], rz = X[p + 2] - X[r + 2]; + const a = d1x * d1x + d1y * d1y + d1z * d1z, e = d2x * d2x + d2y * d2y + d2z * d2z, f = d2x * rx + d2y * ry + d2z * rz; + const c = d1x * rx + d1y * ry + d1z * rz, b = d1x * d2x + d1y * d2y + d1z * d2z, den = a * e - b * b; + let s = den > 1e-18 ? cl((b * f - c * e) / den, 0, 1) : 0, t = (b * s + f) / e; + if (t < 0) (t = 0), (s = cl(-c / a, 0, 1)); + else if (t > 1) (t = 1), (s = cl((b - c) / a, 0, 1)); + const wx = rx + d1x * s - d2x * t, wy = ry + d1y * s - d2y * t, wz = rz + d1z * s - d2z * t; + (SD[0] = s), (SD[1] = t), (SD[2] = wx), (SD[3] = wy), (SD[4] = wz); + return sqrt(wx * wx + wy * wy + wz * wz); +} + +// E, plus BAR (h / d - 1)^2 for edges sharing no bead d < h apart (h the +// mean edge), plus BEND L sum 2 (1 - cos a_i) / l_i over the turns a_i; with +// G, the gradient for fixed edge lengths. DM is left the least distance +// between edges sharing no bead, or h if that is more: beads further apart +// than two longest edges and h have edges more than h apart. +let DM = 0; +const dmin = (X) => (energy(X, null), DM); +function energy(X, G) { + const L = lengths(X), h = L / NB, near = (2 * max(...LE) + h) ** 2; + if (G) G.fill(0); + let E = 0, B = 0; + DM = h; + for (let i = 0; i < NB; i++) { + const wi = (LE[(i + NB - 1) % NB] + LE[i]) / 2, xi = X[i * 3], yi = X[i * 3 + 1], zi = X[i * 3 + 2]; + for (let j = i + 1; j < NB; j++) { + const dx = xi - X[j * 3], dy = yi - X[j * 3 + 1], dz = zi - X[j * 3 + 2], r2 = dx * dx + dy * dy + dz * dz, wj = (LE[j - 1] + LE[j]) / 2; + let a = CUM[j] - CUM[i]; + if (a > L - a) a = L - a; + E += (1 / r2 - 1 / (a * a)) * wi * wj; + if (j === i + 1 || (i === 0 && j === NB - 1)) continue; + if (G) { + const f = (-4 * wi * wj) / (r2 * r2); + (G[i * 3] += f * dx), (G[i * 3 + 1] += f * dy), (G[i * 3 + 2] += f * dz); + (G[j * 3] -= f * dx), (G[j * 3 + 1] -= f * dy), (G[j * 3 + 2] -= f * dz); + } + if (r2 > near) continue; + const d = segd(X, i, j); + if (d < DM) DM = d; + if (d >= h) continue; + B += BAR * (h / d - 1) ** 2; + if (!G) continue; + const f = (-2 * BAR * (h / d - 1) * h) / (d * d * d), s = SD[0], t = SD[1], i1 = ((i + 1) % NB) * 3, j1 = ((j + 1) % NB) * 3; + for (let c = 0; c < 3; c++) { + const w = f * SD[2 + c]; + (G[i * 3 + c] += w * (1 - s)), (G[i1 + c] += w * s), (G[j * 3 + c] -= w * (1 - t)), (G[j1 + c] -= w * t); + } + } + } + for (let i = 0; i < NB; i++) { + const i0 = (i + NB - 1) % NB, a = i0 * 3, o = i * 3, q = ((i + 1) % NB) * 3; + const l0 = LE[i0], l1 = LE[i], k = (BEND * L * 2) / (l0 + l1), u = 1 / (l0 * l1); + let dot = 0; + for (let c = 0; c < 3; c++) dot += (X[o + c] - X[a + c]) * (X[q + c] - X[o + c]); + B += k * 2 * (1 - dot * u); + if (G) + for (let c = 0; c < 3; c++) { + const p = X[o + c] - X[a + c], n = X[q + c] - X[o + c], g = -2 * k * u; + (G[a + c] -= g * n), (G[o + c] += g * (n - p)), (G[q + c] += g * p); + } + } + return 2 * E + B; +} + +// Projects a motion V onto those keeping every edge's length: V less +// J^T (J J^T)^-1 J V, J J^T cyclic tridiagonal, solved by Sherman-Morrison. +const UE = new Float64Array(N3), TA = new Float64Array(NB), TB = new Float64Array(NB), TR = new Float64Array(NB); +const TY = new Float64Array(NB), TZ = new Float64Array(NB), TW = new Float64Array(NB), TU = new Float64Array(NB); +function thomas(d, o, r, x) { + TW[0] = d[0]; + x[0] = r[0]; + for (let i = 1, m; i < NB; i++) (m = o[i - 1] / TW[i - 1]), (TW[i] = d[i] - m * o[i - 1]), (x[i] = r[i] - m * x[i - 1]); + x[NB - 1] /= TW[NB - 1]; + for (let i = NB - 2; i >= 0; i--) x[i] = (x[i] - o[i] * x[i + 1]) / TW[i]; +} +function tangent(X, V) { + for (let e = 0; e < NB; e++) for (let c = 0, q = ((e + 1) % NB) * 3; c < 3; c++) UE[e * 3 + c] = X[q + c] - X[e * 3 + c]; + for (let e = 0; e < NB; e++) { + const o = e * 3, q = ((e + 1) % NB) * 3; + TA[e] = 2 * (UE[o] ** 2 + UE[o + 1] ** 2 + UE[o + 2] ** 2); + TB[e] = -(UE[o] * UE[q] + UE[o + 1] * UE[q + 1] + UE[o + 2] * UE[q + 2]); + TR[e] = UE[o] * (V[q] - V[o]) + UE[o + 1] * (V[q + 1] - V[o + 1]) + UE[o + 2] * (V[q + 2] - V[o + 2]); + } + const g = -TA[0], bn = TB[NB - 1]; + (TA[0] -= g), (TA[NB - 1] -= (bn * bn) / g), TU.fill(0), (TU[0] = g), (TU[NB - 1] = bn); + thomas(TA, TB, TR, TY); + thomas(TA, TB, TU, TZ); + const k = (TY[0] + (bn * TY[NB - 1]) / g) / (1 + TZ[0] + (bn * TZ[NB - 1]) / g); + for (let e = 0; e < NB; e++) TY[e] -= k * TZ[e]; + for (let i = 0; i < NB; i++) { + const e0 = (i + NB - 1) % NB; + for (let c = 0; c < 3; c++) V[i * 3 + c] -= TY[e0] * UE[e0 * 3 + c] - TY[i] * UE[i * 3 + c]; + } +} + +// Smooths G into D: modes up to 32 (the rest are under 1/5000), so long +// bends move as fast as short ones (E's order is 3). Projected before and +// after, the step still goes downhill. +const FA = new Float64Array(KM + 1), FB = new Float64Array(KM + 1); +function smooth(G, D) { + D.fill(0); + for (let c = 0; c < 3; c++) { + for (let k = 1; k <= KM; k++) { + let a = 0, b = 0; + for (let i = 0, o = k * NB; i < NB; i++) (a += G[i * 3 + c] * CS[o + i]), (b += G[i * 3 + c] * SN[o + i]); + (FA[k] = a * PK[k]), (FB[k] = b * PK[k]); + } + for (let k = 1; k <= KM; k++) for (let i = 0, o = k * NB; i < NB; i++) D[i * 3 + c] += FA[k] * CS[o + i] + FB[k] * SN[o + i]; + } +} + +// Puts each edge e back to length ell[e], to 1e-8. +function edges(X, ell) { + for (let s = 0, worst = 1; s < 40 && worst > 1e-8; s++) { + worst = 0; + for (let n = 0; n < NB; n++) { + const i = s & 1 ? NB - 1 - n : n, o = i * 3, q = ((i + 1) % NB) * 3, h = ell[i]; + const dx = X[q] - X[o], dy = X[q + 1] - X[o + 1], dz = X[q + 2] - X[o + 2], l = sqrt(dx * dx + dy * dy + dz * dz), f = (0.5 * (l - h)) / l; + worst = max(worst, abs(l - h) / h); + (X[o] += f * dx), (X[o + 1] += f * dy), (X[o + 2] += f * dz); + (X[q] -= f * dx), (X[q + 1] -= f * dy), (X[q + 2] -= f * dz); + } + } +} + +// The rule that keeps the knot: beads move in straight lines from X to Y by +// at most dm / 4, dm the least edge distance in X. No point of an edge moves +// more than its beads, so edges sharing no bead stay dm / 2 apart all the +// way: no strand passes through another. Returns the share of the move +// allowed; with cut, Y is held to it. +function guard(X, Y, cut, dm) { + let d = 0; + for (let i = 0; i < N3; i += 3) d = max(d, (Y[i] - X[i]) ** 2 + (Y[i + 1] - X[i + 1]) ** 2 + (Y[i + 2] - X[i + 2]) ** 2); + d = sqrt(d); + if (d <= 0.25 * dm) return 1; + const f = (0.25 * dm) / d; + if (cut) for (let i = 0; i < N3; i++) Y[i] = X[i] + (Y[i] - X[i]) * f; + return f; +} + +// Slides the beads along the polygon to even spacing, into Y. +function resample(X, Y) { + const L = lengths(X), h = L / NB; + let s0 = 0; + for (let b = 0; b < NB; b++) s0 += CUM[b] - b * h; + s0 /= NB; + for (let b = 0, lo = 0; b < NB; b++) { + const s = (((s0 + b * h) % L) + L) % L; + if (s < CUM[lo]) lo = 0; + while (lo < NB - 1 && CUM[lo + 1] <= s) lo++; + const u = (s - CUM[lo]) / LE[lo], o = lo * 3, q = ((lo + 1) % NB) * 3; + for (let c = 0; c < 3; c++) Y[b * 3 + c] = X[o + c] + (X[q + c] - X[o + c]) * u; + } +} + +// Evens the beads, guarded, until two edges would come within half an edge. +function even(S, rounds, dX = dmin(S.X)) { + const X = S.X, Y = S.Y; + for (let g = 0; g < rounds; g++) { + resample(X, Y); + guard(X, Y, true, dX); + const dY = dmin(Y); + if (dY < (0.5 * lengths(Y)) / NB) break; + X.set(Y); + centre(X); + dX = dY; + } + return dX; +} + +// The crumple: a stretch along an axis (volume kept), a wring about it and +// two twists about axes through beads. A twist turns points round its axis +// by an angle set by their distance from it and height along it, so the +// turn back undoes it: all are diffeomorphisms of R^3, the knot stays. +const dir = () => { + const u = rnd(-1, 1), a = rnd(0, TAU), s = sqrt(1 - u * u); + return [s * cos(a), s * sin(a), u]; +}; +function newTwists(S) { + const X = S.X, A = 3 + 4 * S.crumple, sg = () => (Math.random() < 0.5 ? -1 : 1); + let rg = 0; + for (let i = 0; i < N3; i++) rg += X[i] * X[i]; + rg = sqrt(rg / NB); + const a = dir(); + S.st = { a, k: log(STR * (1 + S.crumple)) }; + // The wring turns each slice across the axis whole, -A to A along it. + S.tw = [{ c: [0, 0, 0], a, sr: 2 * rg, sz: 0.8 * rg, A: 1.2 * A * sg(), w: 1 }]; + for (let k = 0; k < 2; k++) { + const b = floor(rnd(0, NB)) * 3; + S.tw.push({ c: [X[b], X[b + 1], X[b + 2]], a: dir(), sr: rnd(0.4, 0.6) * rg, sz: rnd(0.5, 0.8) * rg, A: A * rnd(0.85, 1.15) * sg(), w: 0 }); + } +} + +function twist(S, X, Y, f) { + Y.set(X); + const sa = S.st.a, g = exp(S.st.k * f), q = 1 / sqrt(g); + for (let i = 0; i < N3; i += 3) { + const z = Y[i] * sa[0] + Y[i + 1] * sa[1] + Y[i + 2] * sa[2]; + for (let c = 0; c < 3; c++) Y[i + c] = Y[i + c] * q + z * (g - q) * sa[c]; + } + for (const T of S.tw) { + const [cx, cy, cz] = T.c, [ax, ay, az] = T.a, ir = 1 / (T.sr * T.sr), iz = 1 / T.sz; + for (let i = 0; i < N3; i += 3) { + const rx = Y[i] - cx, ry = Y[i + 1] - cy, rz = Y[i + 2] - cz, z = rx * ax + ry * ay + rz * az; + const qx = rx - z * ax, qy = ry - z * ay, qz = rz - z * az, zz = z * iz; + const th = f * T.A * exp(-(qx * qx + qy * qy + qz * qz) * ir) * (T.w ? Math.tanh(zz) : exp(-zz * zz)), c = cos(th), s = sin(th); + Y[i] = cx + z * ax + qx * c + (ay * qz - az * qy) * s; + Y[i + 1] = cy + z * ay + qy * c + (az * qx - ax * qz) * s; + Y[i + 2] = cz + z * az + qz * c + (ax * qy - ay * qx) * s; + } + } +} + +// Crumples on from share a to b in guarded pieces, evening after each; +// stops if two edges would come within half an edge. Returns where it got. +function crumpleTo(S, a, b) { + const X = S.X, Y = S.Y; + let sub = b - a, dX = dmin(X); + for (let n = 0; a < b - 1e-9 && n < 200; n++) { + const f = min(sub, b - a); + twist(S, X, Y, sm(a + f) - sm(a)); + const g = guard(X, Y, false, dX); + if (g < 1) { + sub = f * g * 0.8; + continue; + } + const dY = dmin(Y); + if (dY < (0.5 * lengths(Y)) / NB) return 1; + X.set(Y); + dX = even(S, 1, dY); + a += f; + sub = f * 1.5; + } + return a > 1 - 1e-6 ? 1 : a; +} + +function begin(S) { + nice(S.knot, S.X, S.Y); + S.L0 = lengths(S.X); + newTwists(S); + crumpleTo(S, 0, 1); + relax(S); + frame(S, 1); +} + +// The flow starts at the first length (the zoom undoes it), edges fixed. +function relax(S) { + const f = S.L0 / lengths(S.X); + for (let i = 0; i < N3; i++) S.X[i] *= f; + centre(S.X); + lengths(S.X); + S.ell.set(LE); + S.phase = S.t = S.tc = 0; + S.E = S.Ec = energy(S.X, null); + S.tau = 1e-5; +} + +// One step: gradient, projected, smoothed, projected; cut to the guard, +// halved until the energy falls, kept only then. +function step(S) { + const { X, Y, G, D } = S; + energy(X, G); + const dm = DM; + tangent(X, G); + smooth(G, D); + tangent(X, D); + let m = 0; + for (let i = 0; i < N3; i += 3) m = max(m, D[i] * D[i] + D[i + 1] * D[i + 1] + D[i + 2] * D[i + 2]); + let k = min(S.tau, VCAP / (sqrt(m) + 1e-30)); + for (let tries = 0; tries < 12; tries++) { + for (let i = 0; i < N3; i++) Y[i] = X[i] - k * D[i]; + edges(Y, S.ell); + centre(Y); + const g = guard(X, Y, false, dm); + if (g < 1) { + k *= g * 0.9; + continue; + } + const E = energy(Y, null); + if (E < S.E) { + X.set(Y); + S.E = E; + S.tau = k * 1.3; + return; + } + k *= 0.5; + } + S.tau = max(k, 1e-12); +} + +// A smoothed view along the thinnest axis, the longest up the panel, by +// power steps on C and tr - C. +function frame(S, d) { + const X = S.X, C = [0, 0, 0, 0, 0, 0, 0, 0, 0]; + for (let i = 0; i < N3; i += 3) for (let a = 0; a < 3; a++) for (let b = 0; b < 3; b++) C[a * 3 + b] += X[i + a] * X[i + b]; + const tr = C[0] + C[4] + C[8], dot = (a, b) => a[0] * b[0] + a[1] * b[1] + a[2] * b[2], unit = (w) => w.map((x) => x / (Math.hypot(...w) || 1)); + const it = (v, sh) => { + for (let k = 0; k < 3; k++) v = unit([0, 1, 2].map((a) => sh * v[a] + (sh ? -1 : 1) * (C[a * 3] * v[0] + C[a * 3 + 1] * v[1] + C[a * 3 + 2] * v[2]))); + return v; + }; + const k = min(1, d * 1.2), pull = (e, v) => unit(e.map((x, a) => x + (v[a] * (dot(e, v) < 0 ? -1 : 1) - x) * k)); + const e3 = pull(S.e3, it(S.e3, tr)), e2 = pull(S.e2, it(S.e2, 0)), p = dot(e2, e3); + S.e3 = e3; + S.e2 = unit(e2.map((x, a) => x - p * e3[a])); + S.e1 = [S.e2[1] * e3[2] - S.e2[2] * e3[1], S.e2[2] * e3[0] - S.e2[0] * e3[2], S.e2[0] * e3[1] - S.e2[1] * e3[0]]; +} + +export function setup(params) { + params.s = { + knot: -1, phase: 0, t: 0, acc: 0, rock: 0, cr: 0, + X: new Float64Array(N3), Y: new Float64Array(N3), G: new Float64Array(N3), D: new Float64Array(N3), ell: new Float64Array(NB), + hist: new Float32Array(TN).fill(NaN), ht: 0, + e1: [1, 0, 0], e2: [0, 1, 0], e3: [0, 0, 1], + zb: new Float32Array(N), buf: new Float32Array(N * 3), col: new Float32Array(NB * 3), scr: new Float32Array(NB * 4), + }; +} + +export function update(dt, input, params) { + if (!params.s) setup(params); + const S = params.s, kv = input.knobValues || [0, 2, 0, 0.06], d = cl(dt || 0, 0, 0.1); + [S.hue, S.speed, S.crumple] = [cl(kv[0], 0, 1), cl(kv[1], 0.2, 3), cl(kv[3], 0, 1)]; + const knot = round(cl(kv[2], 0, 4)); + if (knot !== S.knot) { + S.knot = knot; + S.hist.fill(NaN); + begin(S); + } + const ds = (d * S.speed) / 2; + S.rock += ds; + if (input.btnPressed && input.btnPressed[0] && S.phase !== 2) (S.phase = 2), (S.cr = 0), newTwists(S); + S.t += ds; + if (S.phase === 0) { + S.acc += ds * SPS; + for (; S.acc >= 1; S.acc--) step(S); + // Relaxed once half a second takes less than 0.05% off the energy. + if ((S.tc += ds) >= 0.5) { + if (S.Ec - S.E < 0.0005 * S.E && S.t > 3) (S.phase = 1), (S.t = 0); + S.Ec = S.E; + S.tc = 0; + } + if (S.t > 40) (S.phase = 1), (S.t = 0); + } else if (S.phase === 1) { + // While it holds, the beads are spaced out evenly again. + even(S, 2); + S.E = energy(S.X, null); + if (S.t > HOLD) (S.phase = 2), (S.cr = 0), newTwists(S); + } else { + S.cr = crumpleTo(S, S.cr, min(1, S.cr + ds / CRT)); + S.E = energy(S.X, null); + if (S.cr >= 1) relax(S); + } + frame(S, ds); + // A trace sample every quarter second. + if ((S.ht += ds) >= 0.25) { + (S.ht -= 0.25), S.hist.copyWithin(0, 1), (S.hist[TN - 1] = log(S.E)); + } +} + +export function draw(display, params) { + const S = params.s; + if (!S || S.knot < 0) return; + const { zb, buf, col, scr, X, e1, e2, e3 } = S; + zb.fill(1e9), buf.fill(0); + // The view rocks 0.6 rad and nods 0.3. + const ra = 0.6 * sin((TAU * S.rock) / 13), rb = 0.3 * sin((TAU * S.rock) / 9.1), ca = cos(ra), sa = sin(ra), cb = cos(rb), sb = sin(rb); + const zoom = ZOOM[S.knot] * (S.L0 / lengths(X)), tr = TUBE[S.knot], CAM = 5; + for (let i = 0; i < NB; i++) { + const o = i * 3, x = X[o], y = X[o + 1], z = X[o + 2]; + let u = e1[0] * x + e1[1] * y + e1[2] * z, v = e2[0] * x + e2[1] * y + e2[2] * z, w = e3[0] * x + e3[1] * y + e3[2] * z; + [u, w] = [u * ca + w * sa, -u * sa + w * ca]; + [v, w] = [v * cb + w * sb, -v * sb + w * cb]; + const p = CAM / (CAM + w); + (scr[i * 4] = 31.5 + u * zoom * p), (scr[i * 4 + 1] = KY - v * zoom * p), (scr[i * 4 + 2] = w * zoom), (scr[i * 4 + 3] = tr * p); + // The hue walks a third of the wheel along the curve and back. + hsv(0.5 + S.hue + 0.34 * (0.5 - 0.5 * cos((TAU * i) / NB)), 0.8, 1, col, o); + } + // Light from the upper left and in front, and the half vector for the shine. + const lx = -0.451, ly = -0.551, lz = -0.702, hx = -0.245, hy = -0.299, hz = -0.922; + for (let i = 0; i < NB; i++) { + const a = i * 4, b = ((i + 1) % NB) * 4, ax = scr[a], ay = scr[a + 1], az = scr[a + 2], bx = scr[b], by = scr[b + 1], bz = scr[b + 2]; + let tx = bx - ax, ty = by - ay, tz = bz - az; + const tl = sqrt(tx * tx + ty * ty + tz * tz) || 1, m = max(1, ceil(Math.hypot(bx - ax, by - ay) / 0.7)), ci = i * 3, cj = ((i + 1) % NB) * 3; + (tx /= tl), (ty /= tl), (tz /= tl); + for (let q = 0; q < m; q++) { + const f = q / m, sx = ax + (bx - ax) * f, sy = ay + (by - ay) * f, sz = az + (bz - az) * f, rp = scr[a + 3] + (scr[b + 3] - scr[a + 3]) * f; + const cr = col[ci] + (col[cj] - col[ci]) * f, cg = col[ci + 1] + (col[cj + 1] - col[ci + 1]) * f, cbl = col[ci + 2] + (col[cj + 2] - col[ci + 2]) * f; + // Nearer is brighter: the back of the knot at about 60%. + const fog = 0.8 - 0.2 * cl(sz / 30, -1, 1); + const R = rp + 1, x0 = max(0, floor(sx - R)), x1 = min(W - 1, ceil(sx + R)), y0 = max(0, floor(sy - R)), y1 = min(H - 1, ceil(sy + R)); + for (let y = y0; y <= y1; y++) + for (let x = x0; x <= x1; x++) { + const nx = x + 0.5 - sx, ny = y + 0.5 - sy, d2 = nx * nx + ny * ny, p = y * W + x; + if (d2 >= R * R) continue; + if (d2 >= rp * rp) { + // A dark rim a little behind: it cuts the gap in a strand below. + const z = sz + rp * 0.9; + if (z < zb[p]) (zb[p] = z), (buf[p * 3] = buf[p * 3 + 1] = buf[p * 3 + 2] = 0); + continue; + } + const nz = sqrt(rp * rp - d2), z = sz - nz; + if (z >= zb[p]) continue; + zb[p] = z; + // The sphere's normal less its part along the tube. + let Nx = nx / rp, Ny = ny / rp, Nz = -nz / rp; + const dn = Nx * tx + Ny * ty + Nz * tz; + (Nx -= dn * tx), (Ny -= dn * ty), (Nz -= dn * tz); + const nl = sqrt(Nx * Nx + Ny * Ny + Nz * Nz) || 1; + const lam = max(0, (Nx * lx + Ny * ly + Nz * lz) / nl), sp = max(0, (Nx * hx + Ny * hy + Nz * hz) / nl) ** 18, v = fog * (0.3 + 0.75 * lam); + (buf[p * 3] = cr * v + sp * 0.7), (buf[p * 3 + 1] = cg * v + sp * 0.7), (buf[p * 3 + 2] = cbl * v + sp * 0.7); + } + } + } + // The trace: log E over the last 16 s, a line over a faint fill. + let lo = 1e9, hi = -1e9, py = -1; + for (let i = 0; i < TN; i++) if (S.hist[i] === S.hist[i]) (lo = min(lo, S.hist[i])), (hi = max(hi, S.hist[i])); + if (hi - lo < 0.4) hi = lo + 0.4; + const tc = col.subarray(90, 93); + for (let i = 0; i < TN; i++) { + const v = S.hist[i]; + if (v !== v) continue; + const y = round(TY1 - ((v - lo) / (hi - lo)) * (TY1 - TY0)), ya = py < 0 ? y : min(y, py + 1), yb = py < 0 ? y : max(y, py - 1); + for (let yy = min(ya, y); yy <= TY1; yy++) { + const p = yy * W + i, f = yy <= max(yb, y) ? (i === TN - 1 ? 1 : 0.6) : 0.16; + if (zb[p] < 1e9) continue; + (buf[p * 3] = tc[0] * f), (buf[p * 3 + 1] = tc[1] * f), (buf[p * 3 + 2] = tc[2] * f); + } + py = y; + } + for (let p = 0, o = 0; p < N; p++, o += 3) { + if (buf[o] + buf[o + 1] + buf[o + 2] < 0.02) continue; + // Each channel in steps of 4 of 255. + display.setPixel(p % W, (p / W) | 0, min(255, round(buf[o] * 63.75) * 4), min(255, round(buf[o + 1] * 63.75) * 4), min(255, round(buf[o + 2] * 63.75) * 4)); + } +} From b491d5988043ae0d189bc786227a73b897b068e5 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 25 Oct 2026 20:03:45 +0000 Subject: [PATCH 6/8] =?UTF-8?q?pattern-study:=20Flashing=20Ratchet,=20Tric?= =?UTF-8?q?k=20or=20Treat=20(48=ED=8C=90)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../patterns/flashing-ratchet.js | 350 ++++++++++++++ .../pattern-study/patterns/trick-or-treat.js | 430 ++++++++++++++++++ 2 files changed, 780 insertions(+) create mode 100644 incoming/pattern-study/patterns/flashing-ratchet.js create mode 100644 incoming/pattern-study/patterns/trick-or-treat.js diff --git a/incoming/pattern-study/patterns/flashing-ratchet.js b/incoming/pattern-study/patterns/flashing-ratchet.js new file mode 100644 index 000000000..8eb1c9efb --- /dev/null +++ b/incoming/pattern-study/patterns/flashing-ratchet.js @@ -0,0 +1,350 @@ +// ===== Patternflow pattern ===== +// Title: Flashing Ratchet +// Family: sim +// Idea: Three thousand Brownian particles drift in a sawtooth potential of eight teeth stacked up the panel that switches on and off, bunching into glowing lines under each steep ridge while it is on and spreading into fog while it is off, and because each well's minimum sits 3.2 LEDs below its near ridge and 12.8 above its far one, more of every blob lands a well higher when the potential comes back, so a gold-marked cloud climbs against a downward load that would drag it down with the potential held on or held off. +// Author: patternflow-lab +// Date: 2026-10-25 +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Lineage: Flashing ratchet (Armand Ajdari and Jacques Prost, 1992), Parrondo's paradox (Juan Parrondo, 1996), pattern round 48 +// Round: 48 +// =============================== +// @matrix 64x128 +// @knobs Hue=0..1, Speed=0.2..3, Period=0.8..3.2, Load=0.25..1.5 +// +// Knob 1: Hue. Turns every colour round the wheel together. At 0 a sky blue cloud, a gold marked cloud with gold marks on the edges, and violet ridges; at 0.25 violet, green and red; at 0.5 orange, blue and lime; at 0.75 green, pink and cyan. +// Knob 2: Speed. Model time runs at Speed / 2 of real time. At 2 the opening flash lasts 0.8 s and the marked cloud climbs 2 LEDs a second; at 0.2 everything is ten times slower and at 3 half as fast again. +// Knob 3: Period. One off-and-on cycle in seconds of model time, off for the first 40% and on for the rest. At 0.8 a blob spreads 5.7 LEDs (one standard deviation) before the wells come back and the marked cloud climbs 0.127 teeth a second at the opening load; at 1.6 it climbs 0.058, and at 3.2 the blobs spread 11.3 LEDs, most of a tooth, the climb falls to 0.0125 and from half the stall load up the load wins. +// Knob 4: Load. The steady downward force, as a share of the load that stalls the flashing at the 0.8 s period, 5.15 LEDs a second of free fall. At the opening 0.25 the flashing climbs 0.127 teeth a second while the potential held on loses 0.0022 and held off 0.081; at 0.75 it climbs 0.043, at 1 it stands still, and at 1.5 (7.7 LEDs a second) the flashing too goes down, 0.089 teeth a second. +// Buttons: K1 tap steps through the three games: flashing, the potential held on, the potential held off, and back to flashing. Held on, the marked cloud stays in its lines; held off, it sinks with the load. +// +// Checked with this file's own update() over 300 s of model time for each +// game and load, 3000 particles, the period at 0.8 s and the load clamp +// opened down to 0. Mean velocity in teeth a second, up positive, with one +// standard error in the last digit in brackets: +// load flashing held on held off +// 0 +0.1681 (5) +0.0001 (1) -0.0011 (7) +// 0.25 +0.1270 (5) -0.0022 (1) -0.0811 (7) +// 0.5 +0.0851 (5) -0.0048 (1) -0.1613 (6) +// 0.75 +0.0425 (5) -0.0078 (1) -0.2412 (7) +// 1 +0.0002 (5) -0.0117 (1) -0.3219 (7) +// 1.5 -0.0893 (5) -0.0216 (2) -0.4827 (7) +// So from load 0 to 1 both games lose and switching between them wins: +// Parrondo's paradox. Held off, the speed is the load's own F / 16 to +// within the error; held on, particles leave a well only by crossing a +// ridge 7.5 D high, which the load lowers on the down side. With no load +// the held potential makes no current, as it must in equilibrium. + +const W = 64, H = 128, N = W * H; +const { floor, round, ceil, min, max, abs, sqrt, exp, log, cos, PI } = Math; +const cl = (v, a, b) => (v < a ? a : v > b ? b : v); + +// The sawtooth: 8 teeth of L = 16 LEDs up the panel. Measured down the +// panel from a minimum, V rises gently for (1 - A) L = 12.8 LEDs to V0, +// then falls steeply over A L = 3.2 LEDs to the next minimum. So the +// steep side of every well faces up, and the near maximum is 3.2 LEDs +// above a particle sitting in it, the far one 12.8 below. +const L = 16, A = 0.2, NT = H / L; +// Diffusion D in LED^2 per second of model time, the well depth V0 = 7.5 D +// (in units where the mobility is 1, so a force is the speed it gives). +const D = 50, V0 = 7.5 * D; +const FG = V0 / ((1 - A) * L), FS = V0 / (A * L); +// Off for this share of each period, on for the rest. +const OFF = 0.4; +// The stall load at the shortest period, measured with this code (LED/s). +const FSTALL = 5.15; +// Steps while the potential is on: at most HMAX of model time, so the steep +// side's push in one step stays under a tenth of its width. +const HMAX = 1 / 480; +const NP = 3000; +// Pulse labelling: every particle in one well is marked and lit, and the +// marked cloud is followed until its spread passes 3 teeth, it has moved 5 +// or 36 s have gone by; then it fades over FADE s and another well is +// marked, the bottom one if the last cloud went up and the top one if it +// fell. The marks on the edges are the cloud's start and its mean now. +const SPREAD = 3 * L, TRAVEL = 5 * L, TMAX = 36, FADE = 1.5; + +// A table of normal deviates, each with its negative so the noise has no +// skew, scaled to variance 1 exactly. A table of 4096 plain draws had +// enough skew to make the held potential lift particles: 1890 teeth up +// against 1413 down in 200 s. +const hash = (i) => { + let h = Math.imul(i ^ 0x9e3779b9, 0x85ebca6b); + h = Math.imul(h ^ (h >>> 13), 0xc2b2ae35); + return (((h ^ (h >>> 16)) >>> 0) + 0.5) / 4294967296; +}; +const NG = 4096, G = new Float32Array(NG); +{ + let v = 0; + for (let i = 0; i < NG; i += 2) { + G[i] = sqrt(-2 * log(hash(i))) * cos(2 * PI * hash(i + 77777)); + G[i + 1] = -G[i]; + v += 2 * G[i] * G[i]; + } + v = sqrt(v / NG); + for (let i = 0; i < NG; i++) G[i] /= v; +} +const gn = () => G[(Math.random() * NG) | 0]; + +function hsv(h, s, v, out, o) { + h = (h - floor(h)) * 6; + const i = floor(h) % 6, f = h - floor(h), p = v * (1 - s), q = v * (1 - f * s), t = v * (1 - (1 - f) * s); + out[o] = i === 0 || i === 5 ? v : i === 1 ? q : i === 4 ? t : p; + out[o + 1] = i === 1 || i === 2 ? v : i === 0 ? t : i === 3 ? q : p; + out[o + 2] = i === 3 || i === 4 ? v : i === 2 ? t : i === 5 ? q : p; +} + +export function setup(params) { + const X = new Float32Array(NP), Y = new Float32Array(NP); + // The start: bunched in the wells as if the potential had been on, each + // line with its thermal tail on the gentle side below it. + for (let i = 0; i < NP; i++) { + X[i] = Math.random() * W; + Y[i] = (floor(Math.random() * NT) * L - log(1 - Math.random()) * (D / FG) + H) % H; + } + params.s = { + X, Y, M: new Uint8Array(NP), LAP: new Int16Array(NP), rng: (Math.random() * 4294967295) | 1, t: 0, ph: 0, hold: 0, on: true, + hue: 0, speed: 2, per: 0.8, load: 0.25, + age: 0, y0: 0, mean: 0, sd: 0, old: 0, + da: new Float32Array(N), db: new Float32Array(N), tmp: new Float32Array(N), + col: new Float32Array(9), + }; + mark(params.s, NT - 1); +} + +// Marks every particle in the basin of well k, the stretch from the +// maximum A L above its minimum to the one (1 - A) L below; the cloud +// marked before fades. +function mark(S, k) { + const { Y, M, LAP } = S, lo = k * L - A * L, hi = k * L + (1 - A) * L; + let m = 0, n = 0; + for (let i = 0; i < NP; i++) { + M[i] = M[i] === 1 ? 2 : M[i]; + LAP[i] = 0; + let y = Y[i]; + if (y >= hi) y -= H; + if (y >= lo && y < hi) { + M[i] = 1; + m += y; + n++; + } + } + S.old = M.some((v) => v === 2) ? 1 : 0; + S.age = 0; + S.y0 = n ? m / n : k * L; + S.mean = S.sd = 0; +} + +// Moves every particle through tau of model time with the potential off: +// free diffusion plus the load, one exact Gaussian step. +function stepOff(S, tau, F) { + const { X, Y } = S, a = sqrt(2 * D * tau), dy = F * tau; + for (let i = 0; i < NP; i++) { + Y[i] += dy + a * gn(); + X[i] += a * gn(); + } +} + +// The same with the potential on, in steps of at most HMAX along the height. +// Each step is an Euler-Maruyama proposal; one that stays on the same +// straight piece of V is exact and taken, one that crosses a corner is put +// to the Metropolis test with the Boltzmann weight of V - F y. So while the +// potential is held, the steps keep that weight exactly and cannot make a +// current of their own. Plain Euler steps of this size pushed particles over +// the steep side more often than back: with the potential held on and no +// load, 3148 teeth climbed against 2151 fallen in 400 s; with the test, +// 2430 against 2359. +function stepOn(S, tau, F) { + const { X, Y } = S, n = ceil(tau / HMAX - 1e-9), h = tau / n, a = sqrt(2 * D * h), ax = sqrt(2 * D * tau); + const up = (F - FG) * h, dn = (F + FS) * h, edge = 1 - A, i4 = 1 / (4 * D * h); + let r = S.rng; + for (let i = 0; i < NP; i++) { + let y = Y[i], k = floor(y / L), g = y / L - k < edge, f = g ? up : dn; + for (let j = 0; j < n; j++) { + r ^= r << 13; + r ^= r >>> 17; + r ^= r << 5; + const yp = y + f + a * G[r & 4095], up2 = yp / L, k2 = floor(up2), s2 = up2 - k2, g2 = s2 < edge; + if (k2 === k && g2 === g) { + y = yp; + continue; + } + // Across a corner: V at both ends, in units of V0, and the test. + const s1 = y / L - k, v1 = g ? s1 / edge : (1 - s1) / A, v2 = g2 ? s2 / edge : (1 - s2) / A, f2 = g2 ? up : dn; + const d = yp - y, la = (-(v2 - v1) * V0 + F * d) / D + ((d - f) ** 2 - (d + f2) ** 2) * i4; + r ^= r << 13; + r ^= r >>> 17; + r ^= r << 5; + if (la >= 0 || (r >>> 0) / 4294967296 < exp(la)) { + y = yp; + k = k2; + g = g2; + f = f2; + } + } + Y[i] = y; + X[i] += ax * gn(); + } + S.rng = r; +} + +// Wraps the height, counting laps so the marked cloud can be followed +// unwrapped, reflects the sides, and measures the marked cloud: its mean +// displacement and its spread, in LEDs, down positive. +function edges(S) { + const { X, Y, M, LAP } = S; + let n = 0, m = 0, m2 = 0; + for (let i = 0; i < NP; i++) { + let y = Y[i]; + if (y < 0 || y >= H) { + const q = floor(y / H); + y -= q * H; + LAP[i] += q; + } + Y[i] = y; + let x = X[i]; + if (x < 0) x = -x; + if (x >= W) x = 2 * W - x - 1e-3; + X[i] = cl(x, 0, W - 1e-3); + if (M[i] === 1) { + const u = y + LAP[i] * H; + n++; + m += u; + m2 += u * u; + } + } + if (n) { + m /= n; + S.mean = m - S.y0; + S.sd = sqrt(max(0, m2 / n - m * m)); + } +} + +export function update(dt, input, params) { + if (!params.s) setup(params); + const S = params.s, k = input.knobValues || [0, 2, 0.8, 0.25]; + S.hue = cl(k[0], 0, 1); + S.speed = cl(k[1], 0.2, 3); + S.per = cl(k[2], 0.8, 3.2); + S.load = cl(k[3], 0.25, 1.5); + // K1 tap: flashing, then held on, then held off, then flashing again. + if (input.btnPressed && input.btnPressed[0]) S.hold = (S.hold + 1) % 3; + const F = S.load * FSTALL; + let rem = (cl(dt || 0, 0, 0.1) * S.speed) / 2; + S.t += rem; + S.age += rem; + if (S.old) { + S.old -= rem / FADE; + if (S.old <= 0) { + S.old = 0; + for (let i = 0; i < NP; i++) if (S.M[i] === 2) S.M[i] = 0; + } + } + while (rem > 1e-9) { + let on, piece; + if (S.hold) { + on = S.hold === 1; + piece = rem; + } else { + // The period starts with the potential off. + const cut = OFF * S.per; + on = S.ph >= cut; + piece = min(rem, (on ? S.per : cut) - S.ph); + S.ph += piece; + if (S.ph >= S.per - 1e-9) S.ph = 0; + } + if (on) stepOn(S, piece, F); + else stepOff(S, piece, F); + rem -= piece; + } + S.on = S.hold ? S.hold === 1 : S.ph >= OFF * S.per; + edges(S); + if (S.sd > SPREAD || abs(S.mean) > TRAVEL || S.age > TMAX) mark(S, S.mean > 0 ? 1 : NT - 1); +} + +// 1 - exp(-n) in steps of 1/128 of n, the light of n particles' worth. +const NTONE = 1280, TONE = new Float32Array(NTONE); +for (let i = 0; i < NTONE; i++) TONE[i] = 1 - exp(-i / 128); + +// Separable blur [1 4 6 4 1] / 16, wrapping in height, clamped at the sides. +function blur(src, tmp) { + for (let y = 0; y < H; y++) { + const r = y * W; + for (let x = 0; x < W; x++) { + const a = src[r + max(0, x - 2)], b = src[r + max(0, x - 1)], c = src[r + x], d = src[r + min(W - 1, x + 1)], e = src[r + min(W - 1, x + 2)]; + tmp[r + x] = (a + e + 4 * (b + d) + 6 * c) / 16; + } + } + for (let y = 0; y < H; y++) { + const r0 = ((y + H - 2) % H) * W, r1 = ((y + H - 1) % H) * W, r2 = y * W, r3 = ((y + 1) % H) * W, r4 = ((y + 2) % H) * W; + for (let x = 0; x < W; x++) src[r2 + x] = (tmp[r0 + x] + tmp[r4 + x] + 4 * (tmp[r1 + x] + tmp[r3 + x]) + 6 * tmp[r2 + x]) / 16; + } +} + +export function draw(display, params) { + const S = params.s; + if (!S) return; + const { X, Y, M, da, db, tmp, col } = S; + da.fill(0); + db.fill(0); + // Each particle shared out over the four LEDs round it: the marked cloud + // into its own grid, a fading one into both. + for (let i = 0; i < NP; i++) { + const x = X[i] - 0.5, y = Y[i] - 0.5, x0 = floor(x), y0 = floor(y), fx = x - x0, fy = y - y0; + const xa = max(0, x0), xb = min(W - 1, x0 + 1), ya = ((y0 % H) + H) % H, yb = (ya + 1) % H; + const w00 = (1 - fx) * (1 - fy), w10 = fx * (1 - fy), w01 = (1 - fx) * fy, w11 = fx * fy; + const m = M[i], wb = m === 1 ? 1 : m === 2 ? S.old : 0, wa = 1 - wb; + if (wa > 0) { + da[ya * W + xa] += w00 * wa; + da[ya * W + xb] += w10 * wa; + da[yb * W + xa] += w01 * wa; + da[yb * W + xb] += w11 * wa; + } + if (wb > 0) { + db[ya * W + xa] += w00 * wb; + db[ya * W + xb] += w10 * wb; + db[yb * W + xa] += w01 * wb; + db[yb * W + xb] += w11 * wb; + } + } + blur(da, tmp); + blur(db, tmp); + // Colours: the cloud, the marked cloud and the field. + hsv(0.55 + S.hue, 0.7, 0.78, col, 0); + hsv(0.11 + S.hue, 0.75, 1, col, 3); + hsv(0.76 + S.hue, 0.7, 1, col, 6); + const GA = 1, GB = 2, FIELD = 0.3; + for (let y = 0; y < H; y++) { + // V at this row, 0 at a minimum and 1 at a maximum. + let f = 0; + if (S.on) { + const u = (y + 0.5) / L, s = u - floor(u); + const v = s < 1 - A ? s / (1 - A) : (1 - s) / A; + f = FIELD * v * v; + } + for (let x = 0; x < W; x++) { + const p = y * W + x, a = TONE[min(NTONE - 1, (da[p] * GA * 128) | 0)], b = TONE[min(NTONE - 1, (db[p] * GB * 128) | 0)], o = 1 - b; + const r = col[6] * f * o + a * col[0] * o + b * col[3]; + const g = col[7] * f * o + a * col[1] * o + b * col[4]; + const bl = col[8] * f * o + a * col[2] * o + b * col[5]; + if (r + g + bl < 0.01) continue; + // Each channel in steps of 4 of 255. + display.setPixel(x, y, min(255, round(r * 63.75) * 4), min(255, round(g * 63.75) * 4), min(255, round(bl * 63.75) * 4)); + } + } + // Marks on both edges: where the marked cloud started, dim, and the + // height of its mean now, which climbs while the flashing wins. + tick(display, S.y0, 3, 0.4, col); + tick(display, S.y0 + S.mean, 4, 1, col); +} + +// A short bar on each edge at height y, shared between two rows. +function tick(display, y, len, v, col) { + y = y - 0.5 - floor((y - 0.5) / H) * H; + const y0 = floor(y), fy = y - y0; + for (const [row, w] of [[y0, 1 - fy], [(y0 + 1) % H, fy]]) { + const c = v * min(1, w * 1.6); + if (c < 0.08) continue; + for (let i = 0; i < len; i++) + for (const x of [i, W - 1 - i]) display.setPixel(x, row, round(col[3] * c * 63.75) * 4, round(col[4] * c * 63.75) * 4, round(col[5] * c * 63.75) * 4); + } +} diff --git a/incoming/pattern-study/patterns/trick-or-treat.js b/incoming/pattern-study/patterns/trick-or-treat.js new file mode 100644 index 000000000..9a1aeca55 --- /dev/null +++ b/incoming/pattern-study/patterns/trick-or-treat.js @@ -0,0 +1,430 @@ +// ===== Patternflow pattern ===== +// Title: Trick or Treat +// Family: halloween +// Idea: A narrow street on Halloween night runs up the panel to a vanishing point under a purple sky, and three small trick-or-treaters, a bedsheet ghost, a witch and a cat, walk up it away from us sweeping torch beams over the pavement and swinging pumpkin buckets, stop where a door opens a wedge of yellow light for candy, and walk on into the distance as the next group comes in, with bats over the roofs and leaves blowing through the mist. +// Author: patternflow-lab +// Date: 2026-10-25 +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Lineage: original, pattern round 48 +// Round: 48 +// =============================== +// @matrix 64x128 +// @knobs Hue=0..1, Speed=0.2..3, Lights=0.5..1, Wind=0..1 +// +// Knob 1: Hue. Tints the night, sky, walls, roofs and road together, while lamps, pumpkins and costumes keep their colours: at 0 a violet sky over plum walls, at 0.25 a blood red night, at 0.5 a toxic green one, at 0.75 deep teal. +// Knob 2: Speed. At 2 the children walk about 1.5 m a second, wait 2.2 s at each door and sweep their torches across and back in 1.2 s, and a new group comes in every 9 to 12 s; at 0.2 ten times slower, at 3 half as fast again. +// Knob 3: Lights. The share of houses with their lights on, half at the start and all at 1. A lit house has lit windows (lamps, jack-o'-lanterns, a flickering television), a porch lamp and pumpkins on the step, and only lit houses open their doors to the children. +// Knob 4: Wind. At the start 43 leaves skitter across the street and the mist drifts at 0.7 m a second; at 0.5 there are 135 leaves, at 1 there are 240, blowing across at about 5.6 m a second and up to 3.7 m high, and the mist runs at 4.3 m a second. +// Buttons: K1 tap opens the door of a lit house up the street for 3 s. + +const W = 64, H = 128, N = W * H; +const { floor, round, min, max, abs, sqrt, sin, cos, exp, PI } = Math; +const TAU = 2 * PI; +const cl = (v, a, b) => (v < a ? a : v > b ? b : v); +const hash = (i) => { + let h = Math.imul(i ^ 0x9e3779b9, 0x85ebca6b); + h = Math.imul(h ^ (h >>> 13), 0xc2b2ae35); + return ((h ^ (h >>> 16)) >>> 0) / 4294967296; +}; +const vn = (x, sd) => { + const i = floor(x), f = x - i, u = f * f * (3 - 2 * f); + return hash(i * 7919 + sd) * (1 - u) + hash((i + 1) * 7919 + sd) * u; +}; +// Smooth noise on a 32 x 32 wrapping grid, for the mist. +const NT = new Float32Array(1024).map((_, i) => hash(i * 7 + 11)); +const vn2 = (x, y) => { + const i = floor(x), j = floor(y), u = x - i, v = y - j, a = (j & 31) * 32, b = ((j + 1) & 31) * 32, i0 = i & 31, i1 = (i + 1) & 31; + const su = u * u * (3 - 2 * u), sv = v * v * (3 - 2 * v); + return (NT[a + i0] * (1 - su) + NT[a + i1] * su) * (1 - sv) + (NT[b + i0] * (1 - su) + NT[b + i1] * su) * sv; +}; +const hsv = (h, s, v) => [5, 3, 1].map((n) => { + const k = (n + (h - floor(h)) * 6) % 6; + return 255 * v * (1 - s * max(0, min(k, 4 - k, 1))); +}); +const put = (b, o, r, g, bl) => ((b[o] = r), (b[o + 1] = g), (b[o + 2] = bl)); +const add = (b, o, r, g, bl) => ((b[o] += r), (b[o + 1] += g), (b[o + 2] += bl)); + +// One-point perspective. The lane is 2 units wide between the house fronts +// (about 3.2 m), the eye HC above it and the eaves HE, a little higher, so +// the roof lines climb from the vanishing point (32, YH) to the top corners. +// A point x across, h up and z deep lands at (32 + R s x, YH + R s (HC - h)) +// with s = ZN / z: the ground at depth z is row YH + G s. +const YH = 22, G = 127.5 - YH, R = 30, HC = G / R, ZN = 3, HE = HC + 0.9; +// Roof pitch, run from the eave to the ridge, and the houses' depth. +const PITCH = 1.1, RUN = 1.3, DEPTH = 2 * RUN; +const sOf = (z) => ZN / z; + +// Houses along each side: [side, z0, z1, door z]. +const HOUSES = []; +for (const side of [-1, 1]) + for (let z = 1 + hash(side + 5) * 1.5, k = 0; z < 80; k++) { + const len = 3 + 1.6 * hash(k * 31 + side * 7 + 1); + HOUSES.push([side, z, z + len, z + 0.7 + (len - 1.4) * hash(k * 17 + side * 3 + 2)]); + z += len + 0.5 + 1.1 * hash(k * 13 + side * 11 + 3); + } +const houseAt = (side, z) => HOUSES.findIndex((h) => h[0] === side && z >= h[1] && z < h[2]); +// Three floors of windows, by height. +const FLOORS = [[0.55, 1.3], [1.95, 2.75], [3.3, 4.0]]; + +// What each LED sees, worked out once by casting its ray at the ground, the +// two rows of house fronts, their roofs and the end walls facing us; the +// nearest hit wins. A window's id is its house, floor and slot. +const SKY = 0, GROUND = 1, WALL = 2, WIN = 3, DOOR = 4, END = 5, ROOF = 6; +const MAT = new Uint8Array(N), REF = new Int16Array(N), ZB = new Float32Array(N), XB = new Float32Array(N), HB = new Float32Array(N); +for (let p = 0; p < N; p++) { + const q = ((p % W) + 0.5 - 32) / R, e = (floor(p / W) + 0.5 - YH) / R, side = q < 0 ? -1 : 1, aq = abs(q); + let best = 0, m = SKY, ref = -1, zz = 0, xx = 0, hh = 0, s; + // The ground, unless a house stands on that spot. + if (e > 0 && (abs(q / (s = e / HC)) <= 1 || !(abs(q / s) <= 1 + DEPTH && houseAt(side, ZN / s) >= 0))) [best, m, zz, xx] = [s, GROUND, ZN / s, q / s]; + // The house front on this side, then the near slope of a roof. + let i = houseAt(side, ZN / aq), h = HC - e / aq; + if (i >= 0 && h >= 0 && h <= HE && aq > best) [best, m, ref, zz, hh] = [aq, WALL, i, ZN / aq, h]; + s = (-e - PITCH * aq) / (HE - HC - PITCH); + if (s > best && (i = houseAt(side, ZN / s)) >= 0 && aq / s >= 1 && aq / s <= 1 + RUN) [best, m, ref, zz] = [s, ROOF, i, ZN / s]; + // End walls facing us, with their gables. + HOUSES.forEach(([sd, z0], i) => { + const s = sOf(z0), d = aq / s - 1, h = HC - e / s; + if (sd === side && s > best && d >= 0 && d <= DEPTH && h >= 0 && h <= HE + PITCH * min(d, DEPTH - d)) [best, m, ref, zz, xx, hh] = [s, END, i, z0, d, h]; + }); + if (m === WALL) { + const [, z0, z1, zd] = HOUSES[ref], f = FLOORS.findIndex(([a, b]) => hh > a && hh < b), c = f ? (zz - z0 - 0.75) / 1.25 : (zz - zd) / 1.25, k = round(c); + if (abs(zz - zd) < 0.3 && hh < 1.35) m = DOOR; + else if (f >= 0 && abs(c - k) < 0.24 && (f || k) && zz > z0 + 0.3 && zz < z1 - 0.3) [m, ref] = [WIN, ref * 32 + f * 5 + ((k + 5) % 5)]; + } + [MAT[p], REF[p], ZB[p], XB[p], HB[p]] = [m, ref, zz, xx, hh]; +} +// Fixed shading: a wall's tone, each house a little different and darker +// far off, a window's fade, each door's porch lamp on the wall, the stars. +const TONE = new Float32Array(N), LAMP = new Float32Array(N), STAR = new Uint8Array(N), WIDS = new Set(), GL = [], WL = []; +for (let p = 0; p < N; p++) { + const m = MAT[p], fog = min(1, 7 / ZB[p]); + if (m === WALL || m === END) TONE[p] = (0.85 + 0.3 * hash(REF[p] * 7 + 2)) * (0.5 + 0.5 * fog); + if (m === WALL) { + const dz = ZB[p] - HOUSES[REF[p]][3] - 0.42, dh = HB[p] - 1.6; + LAMP[p] = abs(dz) < 0.09 && abs(dh) < 0.12 ? 3 : exp(-(dz * dz + dh * dh) / 0.35) * 0.9; + } + if (m === WIN) (TONE[p] = 0.15 + 0.85 * fog * fog), WIDS.add(REF[p]); + STAR[p] = m === SKY && hash(p * 3 + 1) < 0.015; + // The ground and the walls, the only LEDs the lights walk. + if (m === GROUND) GL.push(p); + else if (m !== SKY && m !== ROOF) WL.push(p); +} +// The walls in order of depth, and where each quarter unit of depth starts. +WL.sort((a, b) => ZB[a] - ZB[b]); +const WZ = new Int32Array(330); +for (let i = 0, j = 0; i < 330; i++) { + while (j < WL.length && ZB[WL[j]] < i / 4) j++; + WZ[i] = j; +} +const wallFrom = (z) => WZ[min(329, floor(z * 4))]; +// Where each row starts among the ground LEDs, and those between two depths. +const GROW = new Int32Array(H + 1); +for (let y = 0, j = 0; y <= H; y++) { + while (j < GL.length && GL[j] < y * W) j++; + GROW[y] = j; +} +const rowsFor = (za, zb) => [GROW[cl(floor(YH + (G * ZN) / zb), 0, H)], GROW[cl(floor(YH + (G * ZN) / max(za, 0.1)) + 2, 0, H)]]; + +// A house is lit while its rank is under Lights, and only lit houses open +// their doors to the children. +const RANK = HOUSES.map((h, i) => hash(i * 29 + 185)); +// Jack-o'-lanterns on the step at each door, one or two: [x, z, seed, house]. +const PUMPKINS = []; +HOUSES.forEach(([side, , , zd], i) => { + PUMPKINS.push([side * 0.84, zd - 0.42, i * 2, i]); + if (hash(i * 5 + 9) < 0.5) PUMPKINS.push([side * 0.86, zd + 0.42, i * 2 + 1, i]); +}); +// Their glow on the ground and the porch lamps', laid down once with whose +// light it is, for the flicker and for Lights. +const PG = new Float32Array(N), PS = new Int16Array(N), LG = new Float32Array(N), LS = new Int16Array(N); +for (const p of GL) { + PUMPKINS.forEach(([px, pz], i) => { + const r2 = (XB[p] - px) ** 2 + (ZB[p] - pz) ** 2, f = r2 < 0.4 ? (1 - r2 / 0.4) * 0.6 : 0; + if (f > PG[p]) (PG[p] = f), (PS[p] = i); + }); + HOUSES.forEach(([side, , , zd], i) => { + const r2 = (XB[p] - side) ** 2 * 0.6 + (ZB[p] - zd - 0.4) ** 2, f = r2 < 1.1 ? (1 - r2 / 1.1) ** 2 * 0.42 : 0; + if (f > LG[p]) (LG[p] = f), (LS[p] = i); + }); +} + +export function setup(params) { + params.s = { + t: 0, mx: 0, hue: 0, speed: 2, lights: 0.5, wind: 0.06, begun: false, groups: [], leaves: [], tap: -1, tapT: 0, + open: new Float32Array(HOUSES.length), rgb: new Float32Array(N * 3), lit: new Float32Array(N * 3), wc: new Float32Array(96 * HOUSES.length), hueKey: -1, + }; +} + +// A group of three, a bedsheet ghost, a little witch and a cat, walking +// together from one lit door to the next, zigzagging up the street. +const SLOTS = [[-0.34, 0.1], [0, -0.05], [0.34, 0.18]]; +const newGroup = (z) => ({ + x: 0, z, tx: 0, tz: z, wait: 0, door: -1, side: -1, done: false, + kids: SLOTS.map(([x, dz], type) => ({ type, x, z: z + dz, ph: Math.random() * TAU, sw: Math.random() * TAU, candy: 0 })), +}); +// The next lit door at least 1.6 ahead of z on one side, and its depth. +function nextDoor(S, side, z) { + let best = -1; + HOUSES.forEach(([sd, , , zd], i) => { + if (sd === side && RANK[i] < S.lights && zd > z + 1.6 && (best < 0 || zd < HOUSES[best][3])) best = i; + }); + return best; +} +const doorZ = (i) => (i < 0 ? 1e9 : HOUSES[i][3]); + +function steer(S, g) { + if (g.z > 15 || g.done) return Object.assign(g, { done: true, door: -1, tx: 0, tz: 200 }); + g.door = nextDoor(S, g.side, g.z); + // A dark stretch on this side: cross over, or walk on. + if (doorZ(g.door) > g.z + 6) g.door = nextDoor(S, (g.side = -g.side), g.z); + if (doorZ(g.door) > g.z + 6) Object.assign(g, { door: -1, tx: 0, tz: g.z + 3 }); + // They wait on the path just short of the door, so it shows past them. + else Object.assign(g, { tx: g.side * 0.3, tz: doorZ(g.door) - 0.55 }); +} + +export function update(dt, input, params) { + if (!params.s) setup(params); + const S = params.s, k = input.knobValues || [0, 2, 0.5, 0.06], ds = (cl(dt || 0, 0, 0.1) * cl(k[1], 0.2, 3)) / 2; + [S.hue, S.lights, S.wind] = [cl(k[0], 0, 1), cl(k[2], 0.5, 1), cl(k[3], 0, 1)]; + S.t += ds; + const gust = 0.5 + vn(S.t * 0.4, 9); + S.mx += ds * (0.3 + 2.4 * S.wind) * gust; + if (!S.begun) { + S.begun = true; + // One group already far up the street, one halfway, one just come in. + const far = newGroup(16), mid = newGroup(9.2), near = newGroup(2.7); + far.done = true; + near.side = doorZ(nextDoor(S, -1, 2.7)) < doorZ(nextDoor(S, 1, 2.7)) ? -1 : 1; + for (const g of [far, mid, near]) steer(S, g), S.groups.push(g); + } + // A new group comes in from the bottom once the last is far enough on. + const last = S.groups[S.groups.length - 1]; + if (!last || last.z > 9) { + const g = newGroup(2.4); + steer(S, g); + S.groups.push(g); + } + if (input.btnPressed && input.btnPressed[0]) { + const c = HOUSES.map((h, i) => i).filter((i) => HOUSES[i][3] > 4 && HOUSES[i][3] < 14 && RANK[i] < S.lights); + S.tap = c[floor(Math.random() * c.length)]; + S.tapT = 3; + } + const want = new Float32Array(HOUSES.length); + if ((S.tapT -= ds) > 0) want[S.tap] = 1; + for (const g of S.groups) { + if (g.wait > 0) { + want[g.door] = 1; + if ((g.wait -= ds) <= 0) { + for (const kd of g.kids) kd.candy++; + g.side = -g.side; + steer(S, g); + } + } else { + // 0.95 lane widths a second at Speed 2; the door opens as they come. + const dx = g.tx - g.x, dz = g.tz - g.z, l = sqrt(dx * dx + dz * dz), st = 0.95 * ds; + if (l <= st) { + [g.x, g.z] = [g.tx, g.tz]; + if (g.door >= 0) g.wait = 2.2; + else steer(S, g); + } else (g.x += (dx / l) * st), (g.z += (dz / l) * st); + if (g.door >= 0 && l < 0.5) want[g.door] = 1; + } + const walk = g.wait <= 0, f = min(1, ds * 4); + g.kids.forEach((kd, i) => { + // Each child trails its place in the group a little. + kd.x += (g.x + SLOTS[i][0] * (walk ? 1 : 0.8) - kd.x) * f; + kd.z += (g.z + SLOTS[i][1] - kd.z) * f; + if (walk) kd.ph += ds * TAU * 1.9; + kd.sw += ds * (walk ? 2.3 : 0.9); + [kd.walk, kd.side] = [walk, g.side]; + }); + } + S.groups = S.groups.filter((g) => g.z < 40); + for (let i = 0; i < HOUSES.length; i++) S.open[i] += (want[i] - S.open[i]) * min(1, ds * (want[i] ? 5 : 2)); + // Leaves blown across the street, tumbling; more, faster and higher with wind. + const n = round(30 + 210 * S.wind); + while (S.leaves.length < n) S.leaves.push(leaf(Math.random() * 2 - 1)); + S.leaves.length = n; + for (const lf of S.leaves) { + lf.x += ds * (0.3 + 3.2 * S.wind) * lf.sp * gust; + lf.ph += ds * (7 + 10 * S.wind) * lf.sp; + lf.h = abs(sin(lf.ph * 0.4)) * (0.06 + 1.6 * S.wind) * lf.sp; + if (lf.x > 1) Object.assign(lf, leaf(-1)); + } +} +const leaf = (x) => ({ x, z: 2.4 + Math.random() * Math.random() * 11, h: 0, ph: Math.random() * TAU, c: floor(Math.random() * 3), sp: 0.6 + 0.8 * Math.random() }); + +// Bats over the roofs, each on its own path through the sky, beating its +// wings 6 to 9 times a second; every third is nearer and twice the size. +const BATW = [["x...x", ".xxx.", "..x.."], ["xxxxx", ".xxx."], [".xxx.", "x.x.x"]]; +function drawBats(buf, t) { + for (let i = 0; i < 9; i++) { + const x = round(32 + 26 * (vn(t * 0.35 + i * 3.1, i * 5 + 1) * 2 - 1)), y = round(3 + 20 * vn(t * 0.3 + i * 1.7, i * 5 + 2)); + const w = BATW[floor((t * (6 + i * 0.7) + i * 0.37) * 3) % 3], z = i % 3 ? 1 : 2; + for (let j = 0; j < w.length * z; j++) + for (let k = 0; k < 5 * z; k++) { + const px = x - 2 * z + k; + if (w[(j / z) | 0][(k / z) | 0] === "x" && px >= 0 && px < W) put(buf, ((y + j) * W + px) * 3, 10, 4, 16); + } + } +} + +// Costumes seen from behind, in units of the child's height: u across, v up +// from the feet. Each returns a colour or null. +const WHITE = [236, 232, 255], SHADE = [176, 170, 214], SHOE = [40, 30, 50], SOCK = [255, 150, 30]; +const HAT = [24, 14, 36], BAND = [170, 70, 240], CAPE = [112, 44, 168], HAIR = [120, 230, 60], BROOM = [150, 104, 60], STRAW = [236, 196, 90]; +const CAT = [30, 24, 40], RIM = [110, 92, 146], BUCKET = [255, 136, 16], CANDY = [255, 70, 190]; +function costume(type, u, v, ph, sw) { + if (v < 0.09 && abs(abs(u) - 0.07) < 0.05 && v >= 0.04 * max(0, u < 0 ? sin(ph) : -sin(ph))) return type === 1 ? (floor(v * 50) & 1 ? SOCK : HAT) : SHOE; + if (type === 0) { + // The sheet: a round top, wider to a hem that ripples as they walk. + if (v < 0.08 + 0.03 * sin(u * 34 + ph * 2) || v > 0.95) return null; + const half = v > 0.72 ? sqrt(max(0, 0.042 - (v - 0.72) ** 2)) : 0.205 + 0.1 * (0.72 - v); + return abs(u) > half ? null : u > half - 0.07 ? SHADE : WHITE; + } + if (type === 1) { + // The witch: a bent hat with band and brim, green hair, a broom. + const t = (v - 0.81) / 0.33, a = 0.13 * ((0.64 - v) / 0.55), bu = u + 0.3 - 0.55 * (v - 0.1); + if (t >= 0 && t <= 1 && abs(u - 0.07 * t * t) < 0.13 * (1 - t)) return t < 0.12 ? BAND : HAT; + if (v >= 0.76 && v < 0.81 && abs(u) < 0.25) return HAT; + if ((u * u) / 0.012 + (v - 0.69) ** 2 / 0.009 < 1) return HAIR; + if (v >= 0.09 && v < 0.64 && abs(u) < 0.12 + a) return abs(u) > 0.09 + a ? RIM : CAPE; + if (v > 0.22 && v < 0.98 && abs(bu) < 0.03) return BROOM; + return v >= 0.04 && v <= 0.22 && abs(bu) < 0.02 + 0.5 * (0.22 - v) ? STRAW : null; + } + // The cat: a black suit, round head, two ears and a tail that sways. + if (u > 0.06 && u < 0.38 && v > 0.17 && v < 0.69) + for (let i = 0; i <= 12; i++) { + const a = i / 12; + if ((u - 0.12 - 0.17 * a - 0.04 * sin(a * 3 + sw)) ** 2 + (v - 0.22 - 0.42 * a * a) ** 2 < 0.0011) return RIM; + } + const ear = v >= 0.84 && v < 0.99 && abs(abs(u) - 0.085) < 0.055 * ((0.99 - v) / 0.15); + return u * u + (v - 0.76) ** 2 < 0.018 || ear || (v >= 0.09 && v < 0.68 && abs(u) < 0.135 + 0.03 * sin(v * 5)) ? CAT : null; +} + +function drawKid(buf, kd) { + const s = sOf(kd.z), hp = 0.92 * R * s, xs = 32 + kd.x * R * s, ys = YH + G * s, bob = kd.walk ? 0.025 * abs(sin(kd.ph)) : 0; + // The pumpkin bucket swings from one hand, the cat's left and the others' right. + const ba = kd.walk ? 0.45 * sin(kd.ph) : 0.15 * sin(kd.sw * 3), bcx = (kd.type === 2 ? -0.25 : 0.25) + 0.12 * sin(ba), bcy = 0.42 - 0.12 * cos(ba); + for (let y = max(0, floor(ys - 1.18 * hp)); y <= min(H - 1, floor(ys)); y++) + for (let x = max(0, floor(xs - 0.5 * hp)); x <= min(W - 1, floor(xs + 0.5 * hp)); x++) { + const u = (x + 0.5 - xs) / hp, v = (ys - y - 0.5) / hp - bob, o = (y * W + x) * 3; + let c = (u - bcx) ** 2 + (v - bcy) ** 2 < 0.0064 ? (kd.candy && v > bcy + 0.03 ? CANDY : BUCKET) : costume(kd.type, u, v, kd.ph, kd.sw); + if (!c) continue; + // Black cloth gets a moonlit outline, or it vanishes on the street. + if (c === CAT || c === HAT) { + const e = 1 / hp, out = (du, dv) => !costume(kd.type, u + du, v + dv, kd.ph, kd.sw); + if (out(e, 0) || out(-e, 0) || out(0, e)) c = RIM; + } + put(buf, o, c[0], c[1], c[2]); + } +} + +function drawPumpkin(buf, [px, pz, sd], t) { + const s = sOf(pz), w = 0.17 * R * s, xs = 32 + px * R * s, ys = YH + G * s, fl = 0.72 + 0.28 * vn(t * 9, sd); + for (let y = max(0, floor(ys - 1.9 * w)); y <= min(H - 1, floor(ys)); y++) + for (let x = max(0, floor(xs - w - 1)); x <= min(W - 1, floor(xs + w + 1)); x++) { + const u = (x + 0.5 - xs) / w, v = (ys - y - 0.5) / w, o = (y * W + x) * 3; + if (u * u + (v - 0.78) ** 2 / 0.6 < 1) { + // Ribbed bright orange; from 5 LEDs across, two eyes and a grin. + const face = w > 2.4 && ((abs(abs(u) - 0.38) < 0.17 && v > 0.95 && v < 1.25) || (abs(u) < 0.55 && v > 0.42 && v < 0.62 && abs(u * 4) % 1 < 0.75)); + if (face) put(buf, o, 255, 236 * fl, 120 * fl); + else put(buf, o, 255 * fl, (abs(u) % 0.5 < 0.12 ? 108 : 140) * fl, 12); + } else if (abs(u) < 0.14 && v >= 1.4 && v < 1.75) put(buf, o, 90, 120, 30); + } +} + +// A dead leaf, catching the light as it tumbles. +const LEAF = [[240, 124, 30], [230, 176, 50], [214, 76, 34]]; +function drawLeaf(buf, lf, wind) { + const s = sOf(lf.z), xs = floor(32 + lf.x * R * s), ys = floor(YH + G * s - lf.h * R * s), sz = max(1, round((0.11 + 0.04 * wind) * R * s)); + const c = LEAF[lf.c], f = 0.55 + 0.4 * abs(sin(lf.ph * 2)), th = max(1, round(sz * 0.6 * abs(cos(lf.ph)))); + for (let j = 0; j < th; j++) + for (let i = 0; i < sz; i++) if (xs + i >= 0 && xs + i < W && ys - j >= 0 && ys - j < H) put(buf, ((ys - j) * W + xs + i) * 3, c[0] * f, c[1] * f, c[2] * f); +} + +export function draw(display, params) { + const S = params.s; + if (!S || !S.begun) return; + const { rgb, lit, open, wc } = S, t = S.t, on = (i) => RANK[i] < S.lights; + // A stepped sky, lighter towards the vanishing point, walls, roofs, road. + if (S.hueKey !== S.hue) { + const h = 0.76 + (S.hueKey = S.hue); + S.sky = Array.from({ length: YH + 1 }, (_, y) => hsv(h + 0.01, 0.75 - floor((y / YH) * 6) / 24, 0.3 + (floor((y / YH) * 6) / 6) * 0.42)); + [S.wall, S.end, S.roof] = [hsv(h, 0.55, 0.19), hsv(h, 0.5, 0.29), hsv(h, 0.5, 0.06)]; + S.road = [hsv(h - 0.06, 0.45, 0.14), hsv(h - 0.04, 0.4, 0.2), hsv(h - 0.04, 0.35, 0.3), hsv(h - 0.31, 0.3, 0.07)]; + } + lit.fill(0); + // Torch beams: the ghost and the cat wave a cone of light from the hand, + // mostly over the street ahead, now and then up the house fronts. + for (const g of S.groups) + for (const kd of g.kids) { + if (kd.type === 1 || kd.z > 14) continue; + const ang = (kd.walk ? 0 : kd.side * 0.7) + 0.7 * sin(kd.sw * 2.2 + kd.type * 2) + 0.22 * sin(kd.sw * 5.1 + kd.type); + const pit = -0.19 + 0.36 * max(0, sin(kd.sw * 1.1 + kd.type * 1.3)) ** 2, dx = sin(ang) * cos(pit), dy = sin(pit), dz = cos(ang) * cos(pit); + const cone = (p, wx, wy, wz) => { + const f = wx * dx + wy * dy + wz * dz, r = sqrt(max(0, wx * wx + wy * wy + wz * wz - f * f)) / f; + if (f < 0.3 || f > 5 || r > 0.24) return; + const v = (r < 0.12 ? 1 : 0.55) * min(1, (0.24 - r) * 25) * min(1, (f - 0.3) * 2) * min(1, 2.6 / (f + 0.5)); + add(lit, p * 3, 255 * v, 236 * v, 190 * v); + }; + const [j0, j1] = rowsFor(kd.z + 0.3, kd.z + 5); + for (let j = j0; j < j1; j++) cone(GL[j], XB[GL[j]] - kd.x, -0.4, ZB[GL[j]] - kd.z); + for (let j = wallFrom(kd.z + 0.2), j2 = wallFrom(kd.z + 5.2); j < j2; j++) { + const p = WL[j], side = (p & 63) < 32 ? -1 : 1; + cone(p, (MAT[p] === END ? side * (1 + XB[p]) : side) - kd.x, HB[p] - 0.4, ZB[p] - kd.z); + } + } + // Open doors throw a wedge of yellow light across the street. + HOUSES.forEach(([side, , , zd], i) => { + if (open[i] < 0.02) return; + const [j0, j1] = rowsFor(zd - 1.4, zd + 1.4); + for (let j = j0; j < j1; j++) { + const p = GL[j], dd = 1 - XB[p] * side, w = 0.28 + 0.4 * dd, dz = abs(ZB[p] - zd); + if (dd < 0 || dd > 1.9 || dz > w) continue; + const f = open[i] * (1 - dd / 2.1) * min(1, (w - dz) * 8) * 1.2; + add(lit, p * 3, 255 * f, 196 * f, 70 * f); + } + }); + // This frame's flicker of each pumpkin and colour of each window: a lamp, + // a guttering jack-o'-lantern, or a television cutting from shot to shot. + const PF = PUMPKINS.map(([, , , i], k) => (on(i) ? 0.3 + 0.7 * vn(t * 11, k) : 0)); + for (const id of WIDS) { + let c = [S.wall[0] * 0.6, S.wall[1] * 0.6, S.wall[2] * 0.6], f; + if (on(id >> 5) && hash(id * 3 + 7) <= 0.3 + 0.7 * S.lights) { + if (hash(id * 11 + 3) < 0.25) (f = 0.35 + 0.65 * hash(floor(t * 5 + hash(id)) * 31 + id)), (c = [140 * f, 166 * f, 255 * f]); + else if (hash(id * 5 + 1) < 0.3) (f = 0.5 + 0.5 * vn(t * 9, id)), (c = [255 * f, 132 * f, 20 * f]); + else (f = 0.85 + 0.15 * vn(t * 1.5, id)), (c = [255 * f, 200 * f, 96 * f]); + } + put(wc, id * 3, c[0], c[1], c[2]); + } + for (let p = 0, o = 0; p < N; p++, o += 3) { + const m = MAT[p]; + if (m === SKY) { + const c = S.sky[min(YH, (p / W) | 0)], st = STAR[p] ? 0.3 + 0.7 * vn(t * 3, p) : 0; + put(rgb, o, c[0] + 160 * st, c[1] + 150 * st, c[2] + 170 * st); + } else if (m === GROUND) { + // Road, curb, pavement and dark yards, the pumpkins' and porch lamps' + // light, and a low mist drifting with the wind, glowing where lit. + const z = ZB[p], ax = abs(XB[p]), pg = PG[p] * PF[PS[p]], lg = on(LS[p]) ? LG[p] : 0, base = S.road[ax > 1 ? 3 : ax > 0.8 ? 1 : ax > 0.74 ? 2 : 0], fg = min(1, 2.4 / z); + const mn = 0.65 * vn2((XB[p] - S.mx) * 1.6, z * 0.7 + t * 0.05) + 0.35 * vn2((XB[p] - S.mx * 1.4) * 3.3 + 7, z * 1.5), mi = cl((mn - 0.42) * 2.2, 0, 1) * min(1, 3.5 / z) * (ax > 1 ? 0.3 : 0.7); + const R0 = base[0] * fg + 255 * pg + 250 * lg, G0 = base[1] * fg + 118 * pg + 190 * lg, B0 = base[2] * fg + 20 * pg + 110 * lg, k = 0.4 * mi; + put(rgb, o, R0 + lit[o] * (0.8 + k) + (52 + R0 * 0.5) * mi, G0 + lit[o + 1] * (0.78 + k) + (44 + G0 * 0.5) * mi, B0 + lit[o + 2] * (0.88 + k) + (78 + B0 * 0.3) * mi); + } else if (m === WALL || m === END) { + const c = m === END ? S.end : S.wall, tone = TONE[p], r = on(REF[p]) ? LAMP[p] : 0; + put(rgb, o, c[0] * tone + 255 * r + lit[o] * 0.42, c[1] * tone + 196 * r + lit[o + 1] * 0.36, c[2] * tone + 110 * r + lit[o + 2] * 0.26); + } else if (m === ROOF) put(rgb, o, S.roof[0], S.roof[1], S.roof[2]); + else if (m === DOOR) put(rgb, o, 44 + 211 * open[REF[p]], 28 + 198 * open[REF[p]], 38 + 92 * open[REF[p]]); + else put(rgb, o, wc[REF[p] * 3] * TONE[p], wc[REF[p] * 3 + 1] * TONE[p], wc[REF[p] * 3 + 2] * TONE[p]); + } + drawBats(rgb, t); + // Pumpkins, children and leaves, far to near. + const sp = []; + for (const pk of PUMPKINS) if (pk[1] > 2.6 && pk[1] < 22 && on(pk[3])) sp.push([pk[1], 0, pk]); + for (const g of S.groups) for (const kd of g.kids) sp.push([kd.z, 1, kd]); + for (const lf of S.leaves) sp.push([lf.z, 2, lf]); + sp.sort((a, b) => b[0] - a[0]); + for (const [, kind, obj] of sp) kind ? (kind === 1 ? drawKid(rgb, obj) : drawLeaf(rgb, obj, S.wind)) : drawPumpkin(rgb, obj, t); + // Each channel in steps of 4 of 255. + for (let p = 0, o = 0; p < N; p++, o += 3) display.setPixel(p % W, (p / W) | 0, min(255, rgb[o]) & 252, min(255, rgb[o + 1]) & 252, min(255, rgb[o + 2]) & 252); +} From d229e4d9589be6bec1ed75104dcfcf81ca73a435 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 25 Oct 2026 20:10:58 +0000 Subject: [PATCH 7/8] =?UTF-8?q?=ED=8C=A8=ED=84=B4=20=EC=97=B0=EA=B5=AC=204?= =?UTF-8?q?8=ED=8C=90:=20README=20=EC=97=B0=EA=B5=AC=20=EB=85=B8=ED=8A=B8?= =?UTF-8?q?=EC=99=80=20=ED=91=9C=EB=A5=BC=20=EB=8D=94=ED=95=9C=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_015fDPQzQykg7X6Bo8o35Zx2 --- incoming/pattern-study/README.md | 89 +++++++++++++++++-- incoming/pattern-study/patterns/barn-owl.js | 2 +- .../pattern-study/patterns/double-gyre.js | 4 +- 3 files changed, 84 insertions(+), 11 deletions(-) diff --git a/incoming/pattern-study/README.md b/incoming/pattern-study/README.md index c4260f4eb..d2a191305 100644 --- a/incoming/pattern-study/README.md +++ b/incoming/pattern-study/README.md @@ -1,6 +1,6 @@ # 팹턮 연구 2026-10 -새 팹턮 378개륌 쉰여섯 갈래로 나눠 만듀었얎요. 1판에서 엎섞 갈래로 23개륌 만듀고, 2판에서 1판에 없던 아홉 갈래로 16개륌, 3판에서 하나륌, 4판에서 새 갈래읞 직묌로 하나륌 더했얎요. 5판은 새 팹턮 없읎 1판의 Euclid Pulse 륌 닀시 ì§°ê³ , 6판은 섞욎 팚널의 128쀄을 위아래로 ë‹€ 쓰는 팹턮 하나륌 더했얎요. 7판은 또렷한 몚양읎 멀늬 흘러가는 지도 칞에 팹턮 하나륌 더했얎요. 8판은 새 갈래읞 Ʞ계로 하나륌 더했얎요. 9판은 새 팹턮 없읎 1판의 Sprout 을 닀시 ì§°ì–Žìš”. 10판은 섞욎 팚널을 떚얎지는 랔록의 우묌로 쓰는 팹턮 하나륌 더했얎요. 11판은 여러 색읎 ꜀ 멀늬 흘러가는 지도 칞에 팹턮 하나륌 더했얎요. 12판은 새 갈래읞 천첎로 하나륌 더했얎요. 13판은 새 팹턮 없읎 1판의 Glyph Rain 을 닀시 ì§°ì–Žìš”. 14판은 절찚가 바뀌Ʞ 전에 띄욎 바퀎띌서, 섞욎 팚널의 높읎륌 ë‹€ 떚얎지는 폭포 하나륌 더했얎요. 15판은 바뀐 절찚의 첫 판읎띌 ì—Ž 갈래에 새 팚턎을 하나씩, 몚두 ì—Ž 개륌 더했얎요. 도시와 음악 갈래는 귞때 새로 엎었얎요. 16판도 ì—Ž 갈래에 하나씩 ì—Ž 개륌 더했고, 바닀와 곀충 갈래륌 새로 엎었얎요. 17판도 ì—Ž 갈래에 하나씩 ì—Ž 개륌 더했고, 전자 회로와 장난감 갈래륌 새로 엎었얎요. 18판도 ì—Ž 갈래에 하나씩 ì—Ž 개륌 더했고, 유늬와 ꎑ묌·결정, 놀읎공원 갈래륌 새로 엎었얎요. 19판도 ì—Ž 갈래에 하나씩 ì—Ž 개륌 더했고, 부엌곌 버섯·균사 갈래륌 새로 엎었얎요. 20판도 ì—Ž 갈래에 하나씩 ì—Ž 개륌 더했고, 새와 화학·싀험 갈래륌 새로 엎었얎요. 21판도 ì—Ž 갈래에 하나씩 ì—Ž 개륌 더했고, 종읎와 욎동·겜Ʞ 갈래륌 새로 엎었얎요. 22판도 ì—Ž 갈래에 하나씩 ì—Ž 개륌 더했고, 몞곌 비행 갈래륌 새로 엎었얎요. 23판도 ì—Ž 갈래에 하나씩 ì—Ž 개륌 더했고, 묎대·공연곌 짐승 갈래륌 새로 엎었얎요. 24판도 ì—Ž 갈래에 하나씩 ì—Ž 개륌 더했고, 공사·걎섀곌 도자Ʞ 갈래륌 새로 엎었얎요. 25판도 ì—Ž 갈래에 하나씩 ì—Ž 개륌 더했고, 농사·밭곌 화산 갈래륌 새로 엎었얎요. 26판도 ì—Ž 갈래에 하나씩 ì—Ž 개륌 더했고, 얌음·극지와 믞생묌 갈래륌 새로 엎었얎요. 27판도 ì—Ž 갈래에 하나씩 ì—Ž 개륌 더했고, 철도와 필늄·영화 갈래륌 새로 엎었얎요. 28판은 죌제 표가 수학 쪜윌로 바뀐 첫 판읎띌 읎늄 있는 수학 대상 ì—Ž 개륌 ì—Ž 갈래에 하나씩 더했고, 수론 갈래륌 새로 엎었얎요. 29판은 죌제 표가 수학 여섯곌 화장품 팝업 넷윌로 나뉜 첫 판읎띌, 읎늄 있는 수학 대상 여섯곌 화장품 맀장 벜에 틀 질감 넷을 더했고, 위상·맀듭곌 화장품 팝업 갈래륌 새로 엎었얎요. 30판도 같은 표로 돌아서 읎늄 있는 수학 대상 여섯을 여섯 갈래에 하나씩 더하고, 팝업 맀장의 LED 벜에 틀 질감 넷을 화장품 팝업 갈래에 더했얎요. 31판도 같은 표로 돌아서 읎늄 있는 수학 대상 여섯을 여섯 갈래에 하나씩, 맀장 벜 질감 넷을 화장품 팝업 갈래에 더했고, 새 갈래는 ì—Žì§€ 않았얎요. 32판도 같은 표로 돌아서 수학 대상 여섯을 여섯 갈래에 하나씩, 맀장 벜의 질감 넷을 화장품 팝업 갈래에 더했얎요. 33판도 같은 표로 돌아서 수학 대상 여섯을 여섯 갈래에 하나씩, 맀장 벜 질감 넷을 화장품 팝업 갈래에 더했얎요. 34판도 수학 대상 여섯을 여섯 갈래에 하나씩, 맀장 벜 질감 넷을 화장품 팝업 갈래에 더했얎요. 35판도 수학 대상 여섯을 여섯 갈래에 하나씩, 맀장 벜 질감 넷을 화장품 팝업 갈래에 더했얎요. 36판도 수학 대상 여섯을 여섯 갈래에 하나씩, 맀장 벜 질감 넷을 화장품 팝업 갈래에 더했얎요. 37판도 수학 대상 여섯을 여섯 갈래에 하나씩, 맀장 벜 질감 넷을 화장품 팝업 갈래에 더했얎요. 38판도 수학 대상 여섯을 여섯 갈래에 하나씩, 맀장 벜 질감 넷을 화장품 팝업 갈래에 더했얎요. 39판도 수학 대상 여섯을 여섯 갈래에 하나씩, 맀장 벜 질감 넷을 화장품 팝업 갈래에 더했얎요. 40판도 수학 대상 여섯을 여섯 갈래에 하나씩, 맀장 벜 질감 넷을 화장품 팝업 갈래에 더했얎요. 41판은 죌제 표가 수학·추상 닀섯곌 할로윈 닀섯윌로 바뀐 첫 판읎띌, 수학 대상 닀섯을 닀섯 갈래에 하나씩 더하고 할로윈 갈래륌 새로 ì—Žì–Ž 10월 31음에 틀 팹턮 닀섯을 넣었얎요. 42판도 같은 표로 돌아서 수학 대상 닀섯을 닀섯 갈래에 하나씩 더하고, 할로윈 밀에 틀 귞늌 닀섯을 41판읎 연 할로윈 갈래에 더했얎요. 43판도 같은 표로 돌아서 수학 대상 닀섯을 닀섯 갈래에 하나씩, 할로윈 귞늌 닀섯을 할로윈 갈래에 더했얎요. 44판도 같은 표로 돌아서 수학 대상 닀섯을 닀섯 갈래에 하나씩 더하고, 할로윈 밀에 틀 귞늌 닀섯을 할로윈 갈래에 더했얎요. 45판도 같은 표로 돌아서 수학 대상 닀섯을 팚턎읎 ì—Ž 개 아래읞 닀섯 갈래에 하나씩, 할로윈 귞늌 닀섯을 할로윈 갈래에 더했얎요. 46판도 같은 표로 돌아서 수학 대상 닀섯을 닀섯 갈래에 하나씩, 할로윈 귞늌 닀섯을 할로윈 갈래에 더했얎요. 47판도 같은 표로 돌아서 수학 대상 닀섯을 닀섯 갈래에 하나씩, 할로윈 밀에 틀 귞늌 닀섯을 할로윈 갈래에 더했얎요. [index.html](index.html) 을 람띌우저로 엎멎 전부 한 장에서 돌아가요. ë§š 위 지도에서 점을 누륎멎 ê·ž 팚턎윌로 가고, 칎드의 슬띌읎더가 녾뾌 넷읎에요. 마음에 드는 것은 「윔드 복사」로 가젞가 Pattern Lab 의 CODE 칞에 붙여 넣윌멎 같은 귞늌읎 나와요. 페읎지는 파음 하나띌서 바깥에서 받아 였는 것읎 없얎요. +새 팹턮 388개륌 쉰여섯 갈래로 나눠 만듀었얎요. 1판에서 엎섞 갈래로 23개륌 만듀고, 2판에서 1판에 없던 아홉 갈래로 16개륌, 3판에서 하나륌, 4판에서 새 갈래읞 직묌로 하나륌 더했얎요. 5판은 새 팹턮 없읎 1판의 Euclid Pulse 륌 닀시 ì§°ê³ , 6판은 섞욎 팚널의 128쀄을 위아래로 ë‹€ 쓰는 팹턮 하나륌 더했얎요. 7판은 또렷한 몚양읎 멀늬 흘러가는 지도 칞에 팹턮 하나륌 더했얎요. 8판은 새 갈래읞 Ʞ계로 하나륌 더했얎요. 9판은 새 팹턮 없읎 1판의 Sprout 을 닀시 ì§°ì–Žìš”. 10판은 섞욎 팚널을 떚얎지는 랔록의 우묌로 쓰는 팹턮 하나륌 더했얎요. 11판은 여러 색읎 ꜀ 멀늬 흘러가는 지도 칞에 팹턮 하나륌 더했얎요. 12판은 새 갈래읞 천첎로 하나륌 더했얎요. 13판은 새 팹턮 없읎 1판의 Glyph Rain 을 닀시 ì§°ì–Žìš”. 14판은 절찚가 바뀌Ʞ 전에 띄욎 바퀎띌서, 섞욎 팚널의 높읎륌 ë‹€ 떚얎지는 폭포 하나륌 더했얎요. 15판은 바뀐 절찚의 첫 판읎띌 ì—Ž 갈래에 새 팚턎을 하나씩, 몚두 ì—Ž 개륌 더했얎요. 도시와 음악 갈래는 귞때 새로 엎었얎요. 16판도 ì—Ž 갈래에 하나씩 ì—Ž 개륌 더했고, 바닀와 곀충 갈래륌 새로 엎었얎요. 17판도 ì—Ž 갈래에 하나씩 ì—Ž 개륌 더했고, 전자 회로와 장난감 갈래륌 새로 엎었얎요. 18판도 ì—Ž 갈래에 하나씩 ì—Ž 개륌 더했고, 유늬와 ꎑ묌·결정, 놀읎공원 갈래륌 새로 엎었얎요. 19판도 ì—Ž 갈래에 하나씩 ì—Ž 개륌 더했고, 부엌곌 버섯·균사 갈래륌 새로 엎었얎요. 20판도 ì—Ž 갈래에 하나씩 ì—Ž 개륌 더했고, 새와 화학·싀험 갈래륌 새로 엎었얎요. 21판도 ì—Ž 갈래에 하나씩 ì—Ž 개륌 더했고, 종읎와 욎동·겜Ʞ 갈래륌 새로 엎었얎요. 22판도 ì—Ž 갈래에 하나씩 ì—Ž 개륌 더했고, 몞곌 비행 갈래륌 새로 엎었얎요. 23판도 ì—Ž 갈래에 하나씩 ì—Ž 개륌 더했고, 묎대·공연곌 짐승 갈래륌 새로 엎었얎요. 24판도 ì—Ž 갈래에 하나씩 ì—Ž 개륌 더했고, 공사·걎섀곌 도자Ʞ 갈래륌 새로 엎었얎요. 25판도 ì—Ž 갈래에 하나씩 ì—Ž 개륌 더했고, 농사·밭곌 화산 갈래륌 새로 엎었얎요. 26판도 ì—Ž 갈래에 하나씩 ì—Ž 개륌 더했고, 얌음·극지와 믞생묌 갈래륌 새로 엎었얎요. 27판도 ì—Ž 갈래에 하나씩 ì—Ž 개륌 더했고, 철도와 필늄·영화 갈래륌 새로 엎었얎요. 28판은 죌제 표가 수학 쪜윌로 바뀐 첫 판읎띌 읎늄 있는 수학 대상 ì—Ž 개륌 ì—Ž 갈래에 하나씩 더했고, 수론 갈래륌 새로 엎었얎요. 29판은 죌제 표가 수학 여섯곌 화장품 팝업 넷윌로 나뉜 첫 판읎띌, 읎늄 있는 수학 대상 여섯곌 화장품 맀장 벜에 틀 질감 넷을 더했고, 위상·맀듭곌 화장품 팝업 갈래륌 새로 엎었얎요. 30판도 같은 표로 돌아서 읎늄 있는 수학 대상 여섯을 여섯 갈래에 하나씩 더하고, 팝업 맀장의 LED 벜에 틀 질감 넷을 화장품 팝업 갈래에 더했얎요. 31판도 같은 표로 돌아서 읎늄 있는 수학 대상 여섯을 여섯 갈래에 하나씩, 맀장 벜 질감 넷을 화장품 팝업 갈래에 더했고, 새 갈래는 ì—Žì§€ 않았얎요. 32판도 같은 표로 돌아서 수학 대상 여섯을 여섯 갈래에 하나씩, 맀장 벜의 질감 넷을 화장품 팝업 갈래에 더했얎요. 33판도 같은 표로 돌아서 수학 대상 여섯을 여섯 갈래에 하나씩, 맀장 벜 질감 넷을 화장품 팝업 갈래에 더했얎요. 34판도 수학 대상 여섯을 여섯 갈래에 하나씩, 맀장 벜 질감 넷을 화장품 팝업 갈래에 더했얎요. 35판도 수학 대상 여섯을 여섯 갈래에 하나씩, 맀장 벜 질감 넷을 화장품 팝업 갈래에 더했얎요. 36판도 수학 대상 여섯을 여섯 갈래에 하나씩, 맀장 벜 질감 넷을 화장품 팝업 갈래에 더했얎요. 37판도 수학 대상 여섯을 여섯 갈래에 하나씩, 맀장 벜 질감 넷을 화장품 팝업 갈래에 더했얎요. 38판도 수학 대상 여섯을 여섯 갈래에 하나씩, 맀장 벜 질감 넷을 화장품 팝업 갈래에 더했얎요. 39판도 수학 대상 여섯을 여섯 갈래에 하나씩, 맀장 벜 질감 넷을 화장품 팝업 갈래에 더했얎요. 40판도 수학 대상 여섯을 여섯 갈래에 하나씩, 맀장 벜 질감 넷을 화장품 팝업 갈래에 더했얎요. 41판은 죌제 표가 수학·추상 닀섯곌 할로윈 닀섯윌로 바뀐 첫 판읎띌, 수학 대상 닀섯을 닀섯 갈래에 하나씩 더하고 할로윈 갈래륌 새로 ì—Žì–Ž 10월 31음에 틀 팹턮 닀섯을 넣었얎요. 42판도 같은 표로 돌아서 수학 대상 닀섯을 닀섯 갈래에 하나씩 더하고, 할로윈 밀에 틀 귞늌 닀섯을 41판읎 연 할로윈 갈래에 더했얎요. 43판도 같은 표로 돌아서 수학 대상 닀섯을 닀섯 갈래에 하나씩, 할로윈 귞늌 닀섯을 할로윈 갈래에 더했얎요. 44판도 같은 표로 돌아서 수학 대상 닀섯을 닀섯 갈래에 하나씩 더하고, 할로윈 밀에 틀 귞늌 닀섯을 할로윈 갈래에 더했얎요. 45판도 같은 표로 돌아서 수학 대상 닀섯을 팚턎읎 ì—Ž 개 아래읞 닀섯 갈래에 하나씩, 할로윈 귞늌 닀섯을 할로윈 갈래에 더했얎요. 46판도 같은 표로 돌아서 수학 대상 닀섯을 닀섯 갈래에 하나씩, 할로윈 귞늌 닀섯을 할로윈 갈래에 더했얎요. 47판도 같은 표로 돌아서 수학 대상 닀섯을 닀섯 갈래에 하나씩, 할로윈 밀에 틀 귞늌 닀섯을 할로윈 갈래에 더했얎요. 48판도 같은 표로 돌아서 수학 대상 닀섯을 닀섯 갈래에 하나씩, 할로윈 귞늌 닀섯을 할로윈 갈래에 더했얎요. [index.html](index.html) 을 람띌우저로 엎멎 전부 한 장에서 돌아가요. ë§š 위 지도에서 점을 누륎멎 ê·ž 팚턎윌로 가고, 칎드의 슬띌읎더가 녾뾌 넷읎에요. 마음에 드는 것은 「윔드 복사」로 가젞가 Pattern Lab 의 CODE 칞에 붙여 넣윌멎 같은 귞늌읎 나와요. 페읎지는 파음 하나띌서 바깥에서 받아 였는 것읎 없얎요. 팚널은 섞로로 서요. 64 LED 너비에 128 LED 높읎가 Ʞ볞읎띌서 몚든 팚턎의 뚞늬에 `@matrix 64x128` 읎 있얎요. 레포의 팹널 섀정은 귞대로예요. `shared/constants.json` 의 `PANEL_GEOM_DEFAULT` 는 `128x64_SINGLE` 읎고, 팚턎읎 가로섞로륌 바Ꟍ 판을 적윌멎 펌웚얎의 `firmware/patternflow/src/core_canvas.h` 가 90도 돌렀 팚널을 ꜉ 채워요. 1판 23개는 처음에 가로 128×64 로 만듀었닀가 2판에서 섞로로 닀시 ì§°ì–Žìš”. @@ -615,6 +615,21 @@ | [Web Tremor](patterns/web-tremor.js) | 할로윈 | 달 아래 í‚€ 큰 거믞쀄에 나방읎 걞렀 버둥댈 때마닀 빛읎 싀을 타고 섞로쀄로는 빠륎게, 나선윌로는 느늬게 퍌지고, 허람의 거믞가 달렀가 나방을 흰 고치로 감고 돌아와요. K3 은 나방의 몞부늌, K4 는 귞묌읎 처지는 정도예요. | | [Talking Board](patterns/talking-board.js) | 할로윈 | 두 쎛불 사읎 검볎띌 판 위륌 하튾 포읞터가 슀슀로 믞끄러젞 Ꞁ자에 멈추멎, 유늬찜 아래 Ꞁ자가 쎈록윌로 쌜지고 판 위에 HELLO 나 GOODBYE 같은 말읎 한 자씩 적혀요. K3 은 쎛불의 음렁임, K4 는 포읞터 둘레 빛읎에요. | +### 48판 + +| 팹턮 | 갈래 | 한 쀄 | +| --- | --- | --- | +| [Möbius Energy](patterns/mobius-energy.js) | 위상·맀듭 | 공간을 비틀고 짜서 엉킚 맀듭 ꎀ읎 뫌비우슀 에너지의 Ʞ욞Ʞ륌 따띌 풀늬며 맀듭 ꌎ읎 허띜하는 만큌만 펎지고, 아래 띠에는 에너지가 떚얎지는 자췚가 귞렀젞요. K3 은 섞잎 맀듭에서 8자 맀듭, 였엜 맀듭, 5₂ 맀듭을 지나 풀며 고늬까지 고륎고, K4 는 구Ʞ는 섞Ʞ읎고, K1 을 누륎멎 바로 닀시 구겚요. | +| [Chebyshev's Bias](patterns/chebyshev-bias.js) | 수론 | 로귞 눈ꞈ 수직선을 따띌 흰 앞쀄읎 올띌가며 소수 겜죌 64개륌 파랗게 칠하고, 반대쪜읎 앞선 드묞 자늬만 죌황 점윌로 낚아 26,861 에서 첫 고늬가 퍌젞요. K3 은 겜죌 묶음을, K4 는 아슬아슬한 자늬륌 볎띌로 칠할 폭을 바꿔요. | +| [Double Gyre](patterns/double-gyre.js) | 녞읎슈·흐늄 | 위아래로 쌓읞 두 소용돌읎 칞 사읎의 겜계가 출렁읎는 동안, 유첎가 갈띌지는 능선을 앞윌로 볌 때는 혞박색, 뒀로 볌 때는 하늘색윌로 귞렀서 가는 능선 닀발읎 두 칞을 감고 가욎데로 혀처럌 접히며 반 죌Ʞ마닀 위아래가 뒀집혀요. K3 은 따띌가는 시간을, K4 는 겜계가 출렁읎는 폭을 바꿔요. | +| [Flashing Ratchet](patterns/flashing-ratchet.js) | 시뮬레읎션 | 여덟 칞 톱니 퍌텐셜읎 쌜졌닀 꺌질 때마닀 람띌욎 입자 3000개가 빛나는 선윌로 몚였닀 안개로 퍌지고, ꞈ빛 표지 구늄읎 쌜 두Ʞ만 핮도 꺌 두Ʞ만 핮도 지는 아래쪜 짐을 거슬러 올띌가요. K3 은 꺌졌닀 쌜지는 죌Ʞ, K4 는 짐읎고, K1 을 누륎멎 깜빡읎Ʞ와 쌜 두Ʞ, 꺌 두Ʞ륌 찚례로 바꿔요. | +| [Fractal Percolation](patterns/fractal-percolation.js) | 프랙탈 | 위아래로 쌓읞 두 정사각형읎 넷윌로 쪌개질 때마닀 조각읎 확률 p 로 낚고 나뚞지는 꺌지Ʞ륌 낱 LED 까지 되풀읎하고, 아래 쀄에서 였륞 빛읎 위 쀄에 닿윌멎 판을 걎너는 덩얎늬가 믌튞로 쌜진 ë’€ 낚은 정사각형 한 쌍윌로 두 ë°° 확대핮 듀얎가요. K3 읎 p 읎고, K1 을 누륎멎 구멍을 닫고 새로 깎아요. | +| [Barn Owl](patterns/barn-owl.js) | 할로윈 | 볎띌 밀하늘의 볎늄달 앞 마륞 나묎 높은 가지에서 원숭읎올빌믞가 하튾 얌굎을 멀늬 돌렞닀 되돌늬고 눈을 깜빡읎닀가, 날개륌 크게 치며 팹널 끝까지 낎늬꜂혀 풀밭의 쥐륌 낚아채거나 놓치고 닀시 가지로 올띌가요. K3 은 사냥 사읎의 Ʞ닀늌, K4 는 바람읎고, K1 을 누륎멎 바로 낎렀가요. | +| [Headless Rider](patterns/headless-rider.js) | 할로윈 | 큰 달 앞을 뚞늬 없는 Ʞ수가 검은 귞늌자로 달늬고, 말읎 ë„€ 박자 습볎로 닀늬륌 몚았닀 뻗윌며 흙뚌지와 불티륌 찚올늬는 사읎 Ʞ수가 불붙은 혞박을 높읎 던젞 앞Ꞟ에서 터뜚렀요. K3 은 나묎 수, K4 는 달빛 안개읎고, K1 을 누륎멎 바로 던젞요. | +| [Bone Pile](patterns/bone-pile.js) | 할로윈 | 볎랏빛 조명 아래 지하 묘싀에서 춀추던 핎곚읎 박자에 맞춰 와륎륎 묎너젞 바닥에 댈 더믞로 쌓읎고, 핎곚 눈에 빛읎 듀멎 댈가 발부터 하나씩 포묌선을 귞늬며 날아올띌 제자늬에 끌워지고 마지막에 뚞늬가 낎렀앉아 끄덕읞 ë’€ 닀시 춀춰요. K3 은 묎너지Ʞ 전에 추는 박자 수, K4 는 땅안개읎고, K1 을 누륎멎 바로 묎너젞요. | +| [Trick or Treat](patterns/trick-or-treat.js) | 할로윈 | 볎띌 하늘 아래 좁은 곚목읎 소싀점윌로 올띌가고, 홑읎불 유령곌 빗자룚 든 마녀, 고양읎 셋읎 손전등을 휘두륎며 걞얎가닀 녾란 빛읎 쏟아지는 묞 앞에서 사탕을 받고 점점 작아젞요. K3 은 불 쌠 집의 몫, K4 는 바람읎고, K1 을 누륎멎 불 쌠 집의 묞읎 엎렀요. | +| [Grim Reaper](patterns/grim-reaper.js) | 할로윈 | 두 눈에 독한 쎈록 불을 쌠 두걎 사신읎 핎진 옷자띜을 날늬며 찜백한 볎늄달 앞을 떠가고, 뚞늬 위로 큰 낫을 휘두륌 때마닀 날읎 번뜩읎며, 지나가는 자늬의 묎덀 쎛불은 꺌졌닀가 등 뒀에서 닀시 쌜젞요. K3 은 묎덀 수, K4 는 안개읎고, K1 을 누륎멎 바로 휘둘러요. | + ## 1판 갈래마닀 녾뾌 넷의 자늬는 갈래가 달띌도 거의 같아요. K1 은 색읎고 K2 는 빠륎Ʞ예요. K3 은 몚양을 바꟞고, K4 는 ê·ž 갈래만의 손잡읎예요. 귞래서 처음 볎는 팹턮도 K1 곌 K2 는 손읎 뚌저 알아요. @@ -2381,6 +2396,50 @@ E8 의 ê·Œ 240개륌 고셋의 4₂₁ 닀멎첎 ꌭ짓점윌로 ê·žë žì–Žìš”. 죌제 6 윌로 말하는 판을 ê·žë žì–Žìš”. 45판 뒀에 적혀 있던 감읎에요. 검볎띌 판에 댈색 3×5 Ꞁ자로 알파벳 두 혞와 숫자 쀄, 핎와 달 옆의 YES 와 NO, 아래의 GOODBYE 륌 놓고, 폭 25 LED 하튾 포읞터가 휘얎진 Ꞟ로 믞끄러지며 움직읎는 쪜윌로 êž°ìšžì–Žìš”. 유늬찜 아래 Ꞁ자에서 멈추멎 ê·ž Ꞁ자가 쎈록윌로 쌜지고 판 위에 5×7 Ꞁ자로 더핎젞요. 닀섯 자 읎하는 두 배로 썚요. HELLO 부터 GOODBYE 까지 말 아홉읎 111쎈에 한 바퀮 돌아요. 첫 판은 녾뾌 반응읎 0.001, 0.024, 0.015, 0.000 윌로 거의 없었얎요. 색은 찜곌 낱말에만 닿아 넓읎가 작았고, ë„·ì§ž 녾뾌 Linger 는 첫 Ꞁ자가 2쎈 넘얎 쌜젞서 녞람륌 재는 2~3쎈 찜에서 아묎것도 바꟞지 못했얎요. Linger 륌 포읞터 둘레 빛 Aura 로 바꟞고, 판의 칠읎 Hue 륌 따띌 ì°¬ 색 안에서 돌게 하고, 포읞터륌 21 에서 25 LED 로 킀우고 쉬는 자늬에서 출발하게 핎서 0.058, 0.051, 0.066, 0.073 읎 됐얎요. 쎛불 빛을 LED 마닀 부드럜게 귞늬자 복잡도가 0.46 에서 1.02 로 뛰얎서, 빛을 20분의 1, 16분의 1 계닚윌로 끊얎 0.40 윌로 낎렞얎요. 얎두욎 판 위의 죌황 빛은 갈색윌로 볎여서 답의 색은 쎈록에서 분홍까지만 돌아요. +## 48판 + +48판은 죌제 5 「수학 · 추상」 닀섯곌 죌제 6 「할로윈」 닀섯읎에요. 같읎 돈 47판(#258)읎 고륞 슀워멀레읎터와 올로읎드, 병 속 눈알곌 혾박 덩굎 같은 것을 플핎서 곚랐고, 앞 바퀮 PR 듀에 따륌 Ꞁ은 없었얎요. 수학은 Möbius Energy, Chebyshev's Bias, Double Gyre, Flashing Ratchet, Fractal Percolation 읎고 위상·맀듭, 수론, 녞읎슈·흐늄, 시뮬레읎션, 프랙탈에 하나씩 듀얎갔얎요. 47판까지 넣얎 섞멎 추상 갈래 가욎데 녞읎슈·흐늄곌 시뮬레읎션읎 여덟윌로 가장 적었고, 수론곌 위상·맀듭, 볎로녞읎·셀읎 아홉읎었얎요. 볎로녞읎·셀은 47판읎 Koebe Packing 을 넣고 있얎서 걎너뛰고, 엎읞 프랙탈에 하나륌 넣었얎요. 첎비쇌프의 펞향은 39판 뒀에 「목록 밖에서 핮 볌 것」윌로 적혀 있던 감읎고, 나뚞지 넷은 목록 밖에서 새로 찟았얎요. 닀섯 ë‹€ 「흔한 것」에도 「낯선 수학 예」에도 없얎요. 할로윈은 Barn Owl, Headless Rider, Bone Pile, Trick or Treat, Grim Reaper 예요. 묎너졌닀 닀시 맞춰지는 핎곚은 43판 뒀에, 사탕을 받윌러 가는 아읎듀은 46판 뒀에 적혀 있던 감읎에요. 죌제 1 은 47판까지의 378개에도 빈 칞곌 하나뿐읞 칞읎 없얎서, 둘뿐읞 칞 슀묌하나 가욎데 가장자늬 × 움직임 1,4 와 쎘쎘핚 × 몚양 바뀜 3,3 을 겚눎얎요. Flashing Ratchet 읎 Hopf Fibration 곌 Kuramoto-Sivashinsky 뿐읎던 가장자늬 × 움직임 1,4 에, Double Gyre 가 Square Wheels 와 Flipdot 뿐읎던 쎘쎘핚 × 몚양 바뀜 3,3 에 ì„°ì–Žìš”. 죌제 4 는 Flashing Ratchet 곌 Barn Owl 읎 맡았얎요. ì—Ž 개는 얎느 축의 끝도 넘지 않았고, 복잡도는 0.181 에서 1.412, 움직임은 0.187 에서 2.019 예요. ì—Ž 개는 `shelf.json` 의 얎느 묶음에도 넣지 않아서 페읎지 ë§š 위 「아직 안 추늰 것」에 서요. 닀섯 음Ꟍ읎 둘씩 ì§°ê³ , 볞첎가 연구의 Lab 하넀슀로 닀시 재고 화멎을 ê·žë € 볎고 합쳀얎요. + +### Möbius Energy + +죌제 5 로 맀듭의 뫌비우슀 에너지륌 섞웠얎요. 였하띌 쀀읎 1991년에 낾 에너지읎고, 프늬드뚌곌 허, 왕읎 1994년에 뫌비우슀 변환윌로 값읎 바뀌지 않는닀는 것을 볎여 읎 읎늄읎 붙었얎요. 두 가닥읎 닀가가멎 값읎 끝없읎 컀젞서, Ʞ욞Ʞ륌 따띌 낎렀가는 맀듭은 가닥끌늬 지나가지 못핎요. 귞래서 맀듭 ꌎ읎 허띜하는 만큌만 풀렀요. 팚턎은 구슬 120개의 닫힌 닀각형을 ꎀ윌로 귞늬고, 깊읎 버퍌로 위아래륌 가륞 ë’€ ꎀ 뒀에 얎두욎 테륌 둘러 교찚마닀 틈읎 볎읎게 했얎요. 시작 ꌎ은 공간을 한 축윌로 늘읎고 ê·ž 축을 따띌 짜고 두 번 더 ꌬ아서 얻얎요. ë‹€ 공간의 믞분동형읎띌 맀듭 ꌎ은 귞대로읎고, 젖은 천을 ì§  것처럌 엉쌜요. 처음 값의 섞잎 맀듭은 7~11쎈에 펎지고 2쎈 뚞묞 ë’€ 닀시 구겚젞요. 에너지만 두멎 흐늄읎 뫌비우슀 불변성을 따띌 믞끄러졌얎요. 섞잎 맀듭읎 ꌎ을 잃었고, 8자 맀듭곌 5₂ 맀듭은 큰 고늬 옆에 ꜉ 조읞 맀듭 하나로 풀렞얎요. 0.2·L·∫κ² 의 굜힘 항을 더하자 ꌎ읎 ì„°ì–Žìš”. 원에서 7.9 읎고, 펎진 섞잎 맀듭에서는 전첎 에너지의 3분의 1쯀읎에요. 닀섯 맀듭을 섞 번씩, 펎Ʞ와 구ꞰꞰ륌 여섯 바퀮 돌렀 90번 펮는 동안 받아듀읞 64,113 걞음 가욎데 에너지가 였륞 걞음은 없었얎요. 읎웃하지 않은 변 사읎 거늬는 92,561 번 옮Ʞ는 동안 가장 가까욞 때도 평균 변 Ꞟ읎의 0.43 읎었얎요. 알렉산더 닀항식에서 섌 |Δ(−1)| 곌 |Δ(2)| 는 90번 ë‹€ 처음 맀듭 귞대로였얎요. 펎진 맀듭을 묎작위 방향 300개로 비춰 가장 적은 교찚륌 섞니 90번 ë‹€ 교찚수 0, 3, 4, 5, 5 와 같았얎요. 풀며 고늬의 뫌비우슀 몫은 늘 3.883 윌로 정 120각형의 읎산 값곌 같았고, 쀑심까지 거늬의 퍌짐은 0.001 아래였얎요. 연속 곡선의 원읎멎 4 예요. 잰 값은 움직임 0.187, 채움 0.173, 복잡도 0.371 읎에요. 아래 띠의 에너지 자췚륌 넣Ʞ 전에는 `thirds` 의 아래 3분의 1읎 1 읎었고, 지ꞈ은 17/23/10 읎에요. + +### Chebyshev's Bias + +죌제 5 로 첎비쇌프의 펞향을 섞웠얎요. 첎비쇌프가 1853년에 볞 것읎에요. x 까지 4로 나눠 3 낹는 소수가 1 낹는 소수볎닀 거의 늘 많고, 1 쪜읎 처음 앞서는 것은 26,861 읎에요(늬치, 1957). 룚빈슀타읞곌 사륎낙(1994)은 3 쪜읎 앞서는 x 의 로귞 밀도륌 0.9959 로 셌얎요. 팚널의 높읎는 32 에서 2^21 까지의 수직선을 로귞 눈ꞈ윌로 섞욎 것읎띌 두 배마닀 8쀄읎고, ì—Ž 64개가 겜죌 64개예요. 처음 값은 3 에서 66 까지의 법마닀 제곱 잉여가 아닌 쪜곌 제곱 잉여읞 쪜을 겚뀄요. 팚턎읎 제 첎로 소수 155,611개륌 거륎고, 앞서알 할 쪜읎 앞서멎 앞선 만큌 밝은 파랑, 반대쪜읎 앞서멎 죌황 점윌로 칠핎요. 흰 앞쀄읎 1쎈에 12.8쀄을 올띌 6.1쎈에 26,861 을 지나요. 법을 찚례대로 섞우자 판읎 잡음윌로 읜혀서, 읎론읎 낎닀볎는 평균 앞섬의 찚례로 섞웠얎요. 1 의 제곱귌읎 여덟읞 법부터 넷, 둘읞 법 찚례예요. 읎제 판의 왌쪜은 맑고 였륞쪜윌로 갈수록 점읎 박혀요. 엎을 넷윌로 나누멎 점읎 든 쀄의 몫읎 0, 0.006, 0.066, 0.167 읎에요. 팚턎의 셈윌로 4로 나눈 겜죌륌 따띌가니 1 쪜은 [26861, 26863) 에서 처음 앞서고, 616,841~633,799 사읎에서 52번 더 앞서 몚두 53 구간읎에요. 3윌로 나눈 겜죌는 범위 안에서 한 번도 뒀집히지 않아요. [32, 2^21] 에서 3 쪜읎 앞선 몫은 x 로 0.99773, log x 로 0.99443 읎에요. 2 부터 섞멎 작은 x 에서 비ꞎ 자늬가 로귞 눈ꞈ에 크게 잡혀 0.934 로 ë–šì–Žì žìš”. 1 의 제곱귌읎 여덟읞 24, 40, 48, 56, 60 은 한 번도 뒀집히지 않았고, 평균 앞섬은 읎론의 4 에 견쀘 4.5~4.8 읎에요. 처음 볞 것은 43 읎에요. 1 의 제곱귌읎 둘읞 법읞데 로귞 눈ꞈ의 42% 에서 잉여 쪜읎 앞서고, 10^6 에서도 39,265 대 39,232 로 앞서요. 첫 판은 검은 판 위로 칠핎 올띌가서 몚양 바뀜읎 축 끝 0.413 을 넘는 0.424 였얎요. ë‹€ 섌 판을 30% 밝Ʞ로 믞늬 깔자 0.281 읎 됐얎요. 잰 값은 움직임 0.255, 복잡도 1.140, 채움 1.000 읎에요. + +### Double Gyre + +죌제 5 와 1 로 읎쀑 소용돌읎륌 섞웠얎요. 섀든곌 레킀엔, 마슀든읎 2005년에 띌귞랑죌 결맞음 구조륌 볎읎렀고 쓎 흐늄읎에요. 두 칞읎 서로 반대로 돌고, ê·ž 사읎 겜계가 죌Ʞ 10 윌로 출렁여요. 흐늄을 T 동안 따띌가며 읎웃한 두 묌방욞읎 얌마나 벌얎지는지 잰 것읎 유한 시간 랮푾녾프 지수읎고, ê·ž 능선은 유첎가 넘지 못하는 선읎에요. [0,2]×[0,1] 상자륌 섞워 두 칞을 위아래로 쌓고, 앞윌로 따띌간 능선은 혞박색, 뒀로 따띌간 능선은 하늘색윌로 ê·žë žì–Žìš”. 둘읎 겹치멎 흰빛읎 돌요. 흐멄 지도는 4분의 1 닚위 지도륌 두 배씩 읎얎 붙읎는 람런턎곌 례늬(2010)의 Ꞟ로 지얎요. ψ(2−x, y, t+5) = −ψ(x, y, t) 띌서 지도의 절반은 나뚞지의 거욞읎고, t0 의 뒀로 장은 −t0 의 앞윌로 장을 180도 돌며 것읎에요. 첫 판은 위아래로만 뒀집얎서 직접 적분곌의 상ꎀ읎 0.2 였얎요. 한 죌Ʞ의 장 40장을 닎아 두고 섞얎서, 한 죌Ʞ가 지나멎 더 셈할 것읎 없얎요. 프레임당 5.9 ms 가 1.3~2.0 ms 가 됐얎요. LED 마닀 RK4 로 직접 적분한 값곌 견죌멎 늘얎낚의 로귞가 앞윌로 0.994~0.9998, 뒀로 0.996~0.9999 의 상ꎀ윌로 맞아요. ε = 0 읞 멈춘 흐늄에서는 엎마닀 가장 섌 늘얎낚읎 겜계 x = 1 을 끌고 앉은 63, 64 쀄에 서요. T 10 에서 64ì—Ž 가욎데 62ì—Ž, T 14 에서 64ì—Ž 닀예요. 겚눈 칞은 Square Wheels 와 Flipdot 뿐읎던 쎘쎘핚 × 몚양 바뀜 3,3 읎에요. ε 0.25, T 20 의 능선만윌로는 쎘쎘핚 0.15, 몚양 바뀜 0.24 였얎요. LED 마닀 둘레 3×3 평균에서 닀섯 ë°° 떌얎 놓아 능선을 1 LED 선윌로 섞우자 쎘쎘핚읎 0.24 로 였륎고 몚양 바뀜읎 0.215 로 낎렀서, 처음 ε 을 0.30 윌로 올렀 되돌렞얎요. 잰 값은 쎘쎘핚 0.232, 몚양 바뀜 0.262, 움직임 1.292 예요. ê·ž 값을 낮는 처음 화멎은 능선 닀발읎 LED 만큌 쎘쎘히 접혀 잔 빗ꞈ처럌 볎여요. K3 을 −1 로 돌렀 T 륌 10 윌로 쀄읎멎 부드러욎 능선 몇 가닥만 낚아요. 유한 찚분은 늘얎낚을 ln 64 쯀에서 막아서, T 40 에서는 혌돈의 바닀가 얎두욎 섬 둘레의 낟알읎 돌요. 멈춘 흐늄에서 T 륌 14 넘게 잡윌멎 칞 둘레에 고늬가 생Ʞ는데, 전닚읎 만든 능선읎띌 수송을 막는 벜은 아니에요. + +### Flashing Ratchet + +죌제 5 와 1, 4 로 깜빡읎는 래칫을 섞웠얎요. 아자늬와 프로슀튞가 1992년에 낾 람띌욎 몚터예요. 톱니 퍌텐셜의 곚은 한쪜 등성읎에 붙얎 있얎서, 퍌텐셜읎 꺌진 동안 퍌진 입자 덩읎가 닀시 쌜질 때 위쪜 곚로 떚얎지는 몫읎 아래쪜 곚볎닀 많아요. 귞래서 아래로 끄는 짐을 걞얎도 구늄읎 올띌가요. 쌜 두Ʞ만 핮도 지고 꺌 두Ʞ만 핮도 지는 두 놀읎륌 번갈아 하멎 읎Ʞ는 셈읎띌, 파론도가 1996년에 읎 래칫에서 귞의 역섀을 끌얎냈얎요. 팚널에는 16 LED 짜늬 톱니 여덟읎 위아래로 읎얎지고, 곚은 가파륞 등성읎에서 3.2 LED 아래, 뚌 등성읎에서 12.8 LED 위에 앉아요. 람띌욎 입자 3,000개가 0.8쎈 죌Ʞ의 앞 40% 동안 안개로 퍌지고, 나뚞지 동안 곚에 몚여 빛나는 선읎 돌요. 입자는 둘레 ë„€ LED 에 나눠 찍고 [1 4 6 4 1] 로 번지게 핎서 읎웃 LED 가 크게 뛰지 않아요. 처음에는 위아래가 읎얎지는 자늬에서 띠륌 묌듀여 였늄을 볎읎렀 했얎요. 읎 몚형에서는 구늄읎 1쎈에 0.13 톱니 였륎는 동안 퍌짐도 0.12 톱니² 늘얎서, 띠가 한 바퀮 안에 섞였얎요. 귞래서 한 곚의 입자륌 ë‹€ ꞈ빛윌로 표지핎 따띌가고, 양옆 가장자늬의 ꞈ빛 눈ꞈ 둘로 출발점곌 지ꞈ 평균을 볎여요. 팚턎의 첫 였음러 걞음은 퍌텐셜을 쌜 둔 채 짐 없읎 두얎도 400쎈에 위로 3,148 톱니, 아래로 2,151 톱니륌 걎너는 흐늄을 지얎냈얎요. 4096 칞 잡음 표의 치우칚도 같은 음을 했얎요. 퍌텐셜의 몚서늬륌 걎너는 걞음에 메튞로폎늬슀 시험을 걞고 잡음 표에 값마닀 ê·ž 음수륌 핚께 넣자, 같은 시험읎 2,430 대 2,359 가 되고 쌜 둔 빠륎Ʞ가 1쎈에 +0.0001 ± 0.0001 톱니가 됐얎요. 팚턎의 셈을 섀정마닀 300쎈 돌렀 1쎈에 몇 톱니 였륎는지 쟀얎요. 짐은 깜빡읎는 래칫읎 멈추는 짐의 몇 배읞지로 적얎요. 짐 0 에서 깜빡읎Ʞ는 +0.168, 쌜 두Ʞ는 +0.0001, 꺌 두Ʞ는 −0.001 읎에요. 짐 0.5 에서는 +0.085, −0.005, −0.161 읎고, 짐 1 에서는 +0.0002, −0.012, −0.322 예요. 짐 0 곌 1 사읎에서 쌜 두Ʞ와 꺌 두Ʞ는 ë‹€ 지고 깜빡읎Ʞ만 읎겚요. 멈추는 짐은 1쎈에 5.15 LED 의 자유 낙하읎고, 꺌 둔 빠륎Ʞ는 ê·ž 짐의 16분의 1곌 였찚 안에서 같아요. 죌Ʞ륌 1.6쎈로 늘늬멎 멈추는 짐읎 0.6 배쯀윌로 쀄얎요. 겚눈 칞은 Hopf Fibration 곌 Kuramoto-Sivashinsky 뿐읎던 가장자늬 × 움직임 1,4 예요. 판을 ë‹€ 덮은 구늄읎 프레임마닀 떚렀서 움직임은 첫 판부터 2.2 얞저늬였고, 번진 입자띌 가장자늬는 0.03 읎었얎요. 잰 값은 가장자늬 0.032, 움직임 2.019 읎고, `thirds` 는 91/93/91 읎에요. + +### Fractal Percolation + +죌제 5 로 프랙탈 슀믞Ʞ륌 섞웠얎요. 망덞람로가 1974년에 낾 정사각형 쪌개Ʞ예요. 정사각형을 넷윌로 나눠 조각마닀 확률 p 로 낚Ʞ고, 낚은 조각 안에서 읎륌 되풀읎핎요. 낹는 집합은 찚원읎 log(4p)/log 2 읞 묎작위 칞토얎 집합읎에요. 첎읎슀와 첎읎슀, 듀렛(1988)은 p 가 묞턱을 넘윌멎 읎 집합읎 마죌 볎는 두 변을 읎을 수 있고, ê·ž 확률읎 0 에서 자띌지 않고 묞턱에서 뛎닀는 것을 볎였얎요. 볎통 슀믞Ʞ와 닀륞 음찚 전읎예요. 팚널은 위아래로 쌓은 64×64 정사각형 둘읎고, 여섯 번 쪌개멎 낱 LED 예요. 닚계마닀 쪌개는 순간에 깎고, 1~3닚계는 얎두욎 읎음맀가 번쩍여요. ë‹€ 깎윌멎 아래 쀄에서 빛읎 변을 맞댄 LED 륌 타고 였륎고, 위 쀄에 닿윌멎 판을 걎너는 덩얎늬가 믌튞로 쌜젞요. 귞닀음 둘 ë‹€ 낚은 1닚계 정사각형 한 쌍을 곚띌 두 배로 확대하고 한 닚계륌 새로 깎아요. 한 쌍은 고륎게 뜑아서 확대한 화멎도 새 표볞읎에요. 팚턎의 깎Ʞ로 1×2 판의 아래위 걎너Ʞ 확률을 깊읎 4~9 에서 섞니 p 0.89 까지는 깊을수록 빠지고(0.89 에서 0.718 읎 0.420 윌로), 0.90 부터는 3분의 2 얞저늬에 뚞묌러요(0.784 에서 0.665). 읎 LED 판에서 묞턱은 0.89 와 0.90 사읎읎고, 거Ʞ서 확률읎 0.67 쯀윌로 ë›°ì–Žìš”. 낹는 몫은 p^6 곌 0.7% 안에서, 상자 섞Ʞ 찚원은 log(4p)/log 2 와 0.001 안에서 맞았얎요. 처음 값 p 0.88 에서는 LED 의 13% 가 낱 LED 구멍읎띌 잡음처럌 읜혀요. 겹겹읎 쪌갠 것은 읎음맀와 깎읎는 조각의 번쩍임윌로만 볎여요. 첫 판은 셋업에서 ë‹€ 깎아 두얎 K3 반응읎 0.000 읎었고, 쪌가 때마닀 깎게 하자 0.12 가 됐얎요. 걎너는 덩얎늬륌 255 로 쌜멎 몚양 바뀜읎 0.368 로 축 끝에 닀가가서 0.9 로 낮췄얎요. 잰 값은 가장자늬 0.925, 움직임 1.104, 몚양 바뀜 0.299 예요. + +### Barn Owl + +죌제 6 곌 4 로 원숭읎올빌믞륌 ê·žë žì–Žìš”. 마륞 나묎가 풀밭에서 팹널 ꌭ대Ʞ까지 서고, 위쪜 가지 뒀에 볎늄달읎 떠요. 하튾 얌굎의 흰 올빌믞가 높은 가지에서 고개륌 멀늬 돌렞닀 되돌늬고 깜빡읎닀가, ë–šì–Žì ž 1.4쎈에 풀밭까지 낎늬꜂혀요. 쥐륌 덮친 ë’€ 1쎈에 섞 번 날개륌 치며 2.3쎈에 가지로 돌아가고, 2쎈 ë’€ 삌쌜요. 셋에 둘은 잡아요. 올빌믞는 LED 마닀 제 êž°ìšžì–Žì§„ 좌표에서 셈핎 귞렀요. 첫 판의 앉은 올빌믞는 가는 땅윩ꌎ읎얎서 뚞늬륌 킀우고 몞을 달걀ꌎ로 바꿚얎요. 돌며 뚞늬의 뒀통수는 덩얎늬로만 볎여서 얎두욎 깃 테륌 둘렀얎요. 흰 올빌믞가 흰 달 앞에서 읜힌 것은 1 LED 의 얎두욎 윀곜 덕읎었얎요. 검은 싀룚엣읎 밝은 것 앞에서만 읜힌 것처럌, 밝은 몞은 밝은 것 앞에서 얎두욎 테가 있얎알 읜혀요. `thirds` 는 92/89/76 읎고, 나묎륌 빌고 재도 하늘읎 ë‹€ 쌜젞 100/100/84 예요. 하늘읎 벜처럌 몫을 채워서 올빌믞가 닀닌 자늬륌 따로 셌얎요. 2~8쎈에 올빌믞는 25쀄에서 122쀄까지 닀니고, 위, 가욎데, 아래 3분의 1에 든 프레임읎 43%, 75%, 40% 예요. 바람을 1 까지 올늬멎 구늄읎 달을 ë‹€ 가렀서 가늌을 0.38 에서 막았얎요. 잰 값은 움직임 0.366, 복잡도 0.252 예요. + +### Headless Rider + +죌제 6 윌로 뚞늬 없는 Ʞ수륌 ê·žë žì–Žìš”. 아음랜드 읎알Ʞ의 둘띌한곌 얎빙의 1820년 읎알Ʞ에서 옚 읞묌읎고, 영화의 ꌎ은 따륎지 않았얎요. 달을 낮게 왌쪜에 두고, 말곌 Ʞ수가 검은 귞늌자로 ê·ž 앞을 달렀요. 말은 끝읎 둥귌 막대 마흔닀섯 개쯀윌로 프레임마닀 닀시 짓고, 닀늬는 마디가 둘읎띌 앞닀늬는 묎늎읎 앞윌로, 뒷닀늬는 뒀로 굜얎요. 발은 가까욎 뒷발, 뚌 뒷발, 가까욎 앞발, 뚌 앞발 찚례로 디디는 가로 습볎읎고, ë„€ 발읎 ë‹€ 뜹는 순간읎 있얎요. 발마닀 걞음의 0.38 동안 땅에 있고, Ꞟ읎 흐륎는 빠륎Ʞ륌 디딘 발에 묶얎서 디딘 발굜읎 믞끄러지지 않아요. 뚌 쪜 닀늬는 검정볎닀 한 당 밝아요. 달빛에 닿은 귞늌자 LED 는 ê·ž 빛의 0.42 륌 테로 받아서, 검은 말읎 크늌색 달 앞에서도 나묎 앞에서도 읜혀요. 든 혞박은 달 위 얎두욎 하늘에 걞늬게 했얎요. 크늌색 달 앞의 죌황은 대비가 없었얎요. 9쎈마닀 0.35쎈 젖혔닀 0.15쎈에 휘둘러 던지멎, 혞박읎 불ꌬ늬륌 끌며 5쀄까지 올랐닀가 3쎈 ë’€ 앞Ꞟ에 ë–šì–Žì ž 터지고 Ꞟ읎 ê·ž 불을 말발굜 뒀로 싀얎 가요. 첫 판은 움직임 1.283 읎었얎요. 욞타늬가 0.25, 몞읎 위아래로 흔듀늬는 것읎 0.3 쯀을 맡아서, 걞음을 1쎈에 2번에서 1.3번윌로, 흔듀늌을 1.7 에서 1.2 LED 로 쀄읎고 욞타늬륌 40 LED 마닀 얎둡게 두었얎요. 귞래서 0.759 예요. 바람 녾뾌는 망토와 잎만 흔듀얎 셈에 거의 듀지 않아서, 말 배까지 찚였륎는 달빛 안개로 바꿚얎요. 가욎데 ìžµ 나묎는 (26, 10, 36) 윌로 20/255 바로 위읞데도 달 앞에서 읜히고, 말의 검정곌도 갈렀요. 잰 값은 움직임 0.759, 채움 0.820, 복잡도 0.246 읎에요. + +### Bone Pile + +죌제 6 윌로 묎너졌닀 닀시 맞춰지는 핎곚을 ê·žë žì–Žìš”. 43판 뒀에 적혀 있던 감읎에요. 댈대는 43판의 Bone Jig 와 따로 지었얎요. 핎곚은 굳은 조각 17개읎고, 조각마닀 제 좌표의 거늬장윌로 ê·žë € 위에서 였는 빛에 상아색곌 회색, 조명 색의 섞 닚윌로 칠하고 얎두욎 윀곜을 둘러요. 닀늬는 두 마디 IK 로 굜고, 6쀄에서 120쀄까지 서요. 볎띌 조명읎 낎늬비치는 돌 묘싀에서 넷을 섞며 춀추닀가, 박자에 몚든 조각읎 ë–šì–Žì ž 정핎 둔 더믞에 쌓읎고 핎곚읎 ë§š 위에 얹혀요. 눈구멍에 빛읎 듀멎 발부터 한 조각씩 포묌선을 귞늬며 날아올띌 흰 번쩍임곌 핚께 끌워지고, 턱곌 뚞늬가 마지막에 낎렀앉아 3 LED 끄덕여요. 음얎날 때마닀 섞 춀을 돌아가며 춰요. 첫 판은 움직임 0.837 읎었얎요. 춀만 재도 1.55 로 Bone Jig 의 1.46 곌 비슷했얎요. 춀을 누귞러뜚늬고 날아였륌 때 돌지 않게 하자 0.761 읎었얎요. 빠륎Ʞ륌 늊추멎 였히렀 0.80~0.81 로 올랐는데, 음읎 잰 ì°œ 안에서 자늬만 옮게Ʞ 때묞읎에요. 떚얎지는 힘을 두 배로 하고 나는 시간을 0.7 박에서 0.45 박윌로 쀄읎자 0.615 가 됐얎요. 한 프레임에 제 폭볎닀 멀늬 가는 댈는 빠륎Ʞ와 상ꎀ없읎 프레임마닀 제 넓읎의 두 배쯀을 바꿔서, ì§§ê³  빠륞 음읎 느며 음볎닀 움직임을 덜 썚요. 첫 곚반은 둥귌 덩읎 둘로 읜혀서 날개륌 작게 Ʞ욞읎고 였목한 귞늘을 넣었얎요. 잰 값은 움직임 0.590, 채움 0.897, 색 0.128 읎에요. + +### Trick or Treat + +죌제 6 윌로 사탕을 받윌러 가는 아읎듀을 ê·žë žì–Žìš”. 46판 뒀에 적혀 있던 감읎에요. 좁은 곚목읎 팹널 위쪜 소싀점윌로 올띌가고, 양옆 집의 찜곌 묞간 등, 계닚의 혾박 등읎 쌜젞요. 홑읎불 유령곌 빗자룚 든 마녀, 고양읎 귀륌 당 아읎가 등을 볎읎며 걞얎가고, 유령곌 고양읎가 든 손전등읎 Ꞟ곌 벜을 쓞얎요. 불 쌠 집의 묞읎 엎늬멎 녾란 빛읎 쐐Ʞ로 쏟아지고, 아읎듀은 2.2쎈 서서 사탕을 받은 ë’€ 닀시 걞얎 작아젞요. 9~12쎈마닀 새 묎늬가 아래에서 듀얎와요. 곚목은 파음을 ì—Ž 때 LED 마닀 ꎑ선을 한 번 쏎서 가장 가까욎 멎을 정핎 두고, 프레임마닀 ê·ž 위에 손전등 원뿔을 3찚원윌로 비춰요. 눈높읎륌 아읎 킀에 두니 집읎 위쪜 27쀄에만 볎였얎요. 곚목 폭을 2 닚위(3.2 m 쯀)로, 눈을 3.5 닚위 높읎로 두고 처마륌 4.4 에 두자 지붕선읎 위 두 귀퉁읎로 올띌가고 하늘읎 ê·ž 사읎 쐐Ʞ가 됐얎요. 집 앞멎을 거의 옆에서 볎아서 깊읎 4 의 찜읎 3 LED 너비에 17 LED 높읎예요. 찜은 곚목을 따띌 늘얎선 빛의 틈윌로 읜혀요. 마녀의 검은 몚자와 고양읎는 얎두욎 êžž 앞에서 사띌젞서 달빛 윀곜을 둘렀고, 마디로 읎은 고양읎 ꌬ늬는 사닀늬로 읜혀서 통짞로 칠했얎요. 부드러욎 손전등 빛만윌로는 움직임읎 0.111 읎었얎요. 손전등읎 벜까지 비추게 하자 0.186 읎 됐고, 묎늬륌 늘늬고 손전등을 흔듀게 하자 0.258 읎 됐얎요. 박쥐륌 더하고 아읎듀을 킀욎 ë’€ 잎을 늘렀 0.358 읎에요. K3 은 처음에 묎늬 수였는데, 둘짞 묎늬가 9쎈쯀에알 와서 3쎈 찜의 녾뾌 검사에서 0.0000 읎었얎요. 불 쌠 집만 사탕을 죌는 규칙대로 불 쌠 집의 몫윌로 바꟞고 묞간 등을 더핮 0.06 읎 됐얎요. 잰 값은 움직임 0.358, 채움 0.871, 복잡도 0.575 예요. + +### Grim Reaper + +죌제 6 윌로 사신을 ê·žë žì–Žìš”. 핎진 검은 옷의 두걎 사신읎 반지늄 30 의 찜백한 달 앞을 10쎈에 한 번 걎너요. 3.2쎈마닀 큰 낫을 얎깚 뒀로 젖혔닀가 뚞늬 위로 휘둘러 앞에 곧게 낎늬고, 날 안쪜읎 밝아 휘두륌 때 빛읎 날을 따띌 달렀요. 사신읎 26 LED 안윌로 였멎 묎덀 쎛불읎 흔듀늬닀 옷자띜 밑에서 꺌지고, 옷자띜읎 지나고 0.6~1.8쎈 ë’€ 닀시 쌜젞요. 하늘은 짙은 볎띌에서 색상환을 짧은 쪜윌로 돌아 병든 쎈록 지평선에 닿아요. 검은 옷은 얎두욎 볎띌 아래 하늘에서 사띌졌얎요. 아래 하늘을 밝은 청록곌 쎈록윌로 바꟞자 사신읎 ë‹€ 읜혔얎요. 앞윌로 낎렀 Ꞌ는 낫은 날읎 64 너비 밖윌로 나가서, 뚞늬 위로 휘둘러 곧게 낎늬게 했얎요. 하늘 전첎륌 색상 녞람로 돌늬멎 고동색곌 갈색 하늘읎 나와서, K1 은 지평선 빛곌 달빛, 눈만 바꿔요. 움직임은 첫 판읎 0.084 였얎요. 빚늬 걎너고 크게 였륎낎늬게 하자 0.189 가 됐고, 달곌 낫을 킀우자 0.295 가 됐얎요. 뚞늬 위로 휘두륎는 것곌 옷자띜 부슀러Ʞ로 0.331, 옷읎 부푞는 것윌로 0.347 읎에요. 큰 것읎 밝은 것 앞을 닀녀알 셈에 듀었얎요. K3 은 묎덀 한 쀄로는 엎여섯을 섞워도 0.02~0.03 읎었얎요. 묎덀 띠가 판의 18% 뿐읎띌, 묎덀을 여섯씩 얞덕을 따띌 쀄지얎 올늬자 0.05 가 됐얎요. 잰 값은 움직임 0.347, 채움 0.989, 복잡도 0.242 예요. + ## 지도 지도의 자늬는 손윌로 맀ꞎ 점수가 아니띌 잰 값읎에요. 팚턎마닀 처음 녾뾌 값윌로 Lab 엔진에서 8쎈륌 돌늬고, 2쎈부터 6쎈 동안의 화멎을 재요. LED 하나의 밝Ʞ는 섞 채널 가욎데 가장 밝은 값윌로 뎐요. @@ -2423,7 +2482,7 @@ ESP32-S3 로 옮Ꞟ 때 걞늎 것도 있얎요. Reaction Diffusion 은 Node 에 ## 닀음 판 감 -10월 25음 `dev`(`f72355d`)의 328개에 43판부터 47판까지의 쉰 개륌 더한 378개에서 잰 값읎에요. 팹턮 바퀮는 죌제 안에서 묎엇을 할지 고륌 때 읎 절을 읜고, 한 것은 빌고 새로 볞 것을 더핎요. +10월 25음 `dev`(`f72355d`)의 328개에 43판부터 48판까지의 예순 개륌 더한 388개에서 잰 값읎에요. 팹턮 바퀮는 죌제 안에서 묎엇을 할지 고륌 때 읎 절을 읜고, 한 것은 빌고 새로 볞 것을 더핎요. ### 지도의 빈 칞 @@ -2463,6 +2522,8 @@ ESP32-S3 로 옮Ꞟ 때 걞늎 것도 있얎요. Reaction Diffusion 은 Node 에 47판의 ì—Ž 개도 얎느 축의 끝을 넘지 않아서 칞 겜계는 30판 귞대로읎고, 378개에서도 지도 21장읎 ë‹€ `none` 읎에요. 죌제 1 은 46판까지의 368개에 낚은 둘뿐읞 칞 슀묌셋 가욎데 채움 × 색 1,4 와 움직임 × 몚양 바뀜 1,4 륌 겚눠서, 귞런 칞읎 378개에서 슀묌하나예요. 채움 × 색 1,4 는 LED 의 70% 넘게 꺌진 채 색읎 0.545 륌 넘얎알 하는 칞읎에요. 색은 꺌진 LED 까지 섞서, 쌜진 LED 가 ë‹€ 255 의 순색읎고 색상읎 고룚 퍌지멎 색읎 쌜진 몫의 제곱귌에 1.1 쯀을 곱한 값읎에요. 귞래서 읎 칞은 채움 0.265~0.302 의 좁은 띠예요. Swarmalators 는 점의 가장자늬륌 번지지 않게 끊얎 쌜진 LED 륌 ë‹€ 255 에 두고, 색상환을 두 번 휘얎 여섯 순색에 뚞묌게 핎서 채움 0.286, 색 0.565 예요. 색상환을 고륎게 돌늬멎 크Ʞ의 rms 가 251 읎띌 같은 채움에서 0.53 에 귞쳀얎요. 움직임 × 몚양 바뀜 1,4 는 39판의 Blotting Paper 처럌 LED 마닀 가장 밝은 채널읎 한 방향윌로만 바뀌얎알 닿는 칞읎에요. Pumpkin Vine 은 2~8쎈에 덩굎읎 얞덕에서 ꌭ대Ʞ까지 자띌는 동안 ì–Žë–€ LED 도 얎두워지지 않게 핎서 움직임 0.058, 몚양 바뀜 0.347, 비가 6.00 읎에요. 늊게 덮읞 잎 조각읎 뚌저 덮읞 조각볎닀 얎두우멎 뚌저 밝Ʞ륌 지킀고, 혞박읎 부풀얎도 빚강 채널을 255 로 둔 채 결곌 빛을 쎈록 채널로만 ê·žë žì–Žìš”. 낚은 슀묌하나 가욎데 엎넷은 쎘쎘핚 축읎 든 칞읎고, 여섯은 복잡도가 2.173 을 넘얎알 하는 칞읎에요. 나뚞지 하나읞 가장자늬 × 움직임 1,4 는 번진 귞늌읎 1쎈에 1.49 넘게 바뀌얎알 하는 칞읎띌, Kuramoto-Sivashinsky 처럌 판 전첎가 부드럜게 끓는 귞늌읎에요. +48판의 ì—Ž 개도 얎느 축의 끝을 넘지 않아서 칞 겜계는 30판 귞대로읎고, 388개에서도 지도 21장읎 ë‹€ `none` 읎에요. 죌제 1 은 47판까지의 378개에 낚은 둘뿐읞 칞 슀묌하나 가욎데 가장자늬 × 움직임 1,4 와 쎘쎘핚 × 몚양 바뀜 3,3 을 겚눠서, 귞런 칞읎 388개에서 엎아홉읎에요. 가장자늬 × 움직임 1,4 에는 47판읎 적은 대로 판 전첎가 부드럜게 끓는 귞늌읎 맞았얎요. Flashing Ratchet 은 입자 3,000개륌 둘레 ë„€ LED 에 나눠 찍고 닀섯 칞 번짐을 걞얎서, 판을 ë‹€ 덮은 구늄읎 프레임마닀 떚렀도 40/255 넘게 뛰는 읎웃읎 드묌얎요. 가장자늬 0.032, 움직임 2.019 로 두 겜계에서 ë‹€ 멀늬 ì„°ì–Žìš”. 쎘쎘핚 × 몚양 바뀜 3,3 은 읎웃 찚읎의 평균읎 0.189~0.297 읎멎서 2~8쎈에 랔록 평균읎 0.217~0.331 바뀌얎알 하는 칞읎에요. Double Gyre 의 능선은 귞대로 귞늬멎 쎘쎘핚읎 0.15 에 뚞묌렀얎요. LED 마닀 둘레 3×3 평균에서 닀섯 ë°° 떌얎 놓아 능선을 1 LED 선윌로 섞우자 쎘쎘핚은 0.24 로 올랐지만 몚양 바뀜읎 0.24 에서 0.215 로 낎렀 칞 아래로 빠졌얎요. 겜계가 출렁읎는 폭을 0.25 에서 0.30 윌로 킀워 몚양 바뀜을 0.262 로 되돌렞얎요. 날칎롭게 섞우는 셈은 쎘쎘핚을 올늬는 만큌 몚양 바뀜을 깎았얎요. 낚은 엎아홉 가욎데 엎셋은 쎘쎘핚 축읎 든 칞읎고, 여섯은 복잡도가 2.173 을 넘얎알 하는 칞읎에요. + 23판부터 28판까지는 `*` 가 붙은 칞읎 없었얎요. 22판까지 낚은 두 칞, 쎘쎘핚 × 몚양 바뀜 3,4 와 4,3 에 23판의 Bait Ball 곌 Sequins 가 ì„°ì–Žìš”. 하나 낚은 빈 칞읎던 복잡도 × 채움 4,3 에는 24판의 Waggle Dance 가 ì„°ì–Žìš”. 복잡도가 2.173 을 넘고 채움읎 0.558~0.815 읞 자늬예요. 복잡도 축 끝의 Contour Map(2.735)을 빌고 섞멎 찚는 칞읎띌 `*` 가 붙지 않았고, ê·ž 아래 복잡도 2.4~2.6 에 선 Woodpecker, Dripstone, Agate, Guilloché 는 채움읎 ë‹€ 0.93 을 넘었얎요. 읎제 지도 21장의 칞읎 ë‹€ 찚서 `--gaps` 는 몚든 쀄에 `none` 을 찍얎요. 죌제 1 은 축 끝의 팹턮 하나가 축을 늘렀 새 빈 칞읎 날 때까지 고륌 칞읎 없얎요. Waggle Dance 에서 얎렀욎 쪜은 채움읎었얎요. 벌집은 꺌진 LED 가 거의 없는 귞늌읎띌, 빈 ë°© 바닥곌 뮉한 ë°© 사읎 읎음맀륌 24 아래로 눌러 채움 0.732 륌 냈얎요. 복잡도는 쌜진 LED 의 섞 채널을 따로 흔드는 낟알로 쉜게 올띌서, 첫 판은 였히렀 2.766 윌로 축 끝을 넘었얎요. 25판도 Ʞ쀀 148개의 21장읎 ë‹€ `none` 읎띌 죌제 1 은 겚눌 칞읎 없었얎요. ê·ž 대신 팚턎읎 하나만 선 칞을 셌얎요. 148개에서 49칞읎었고, 낚은 자늬로 ì§  Penrose 가 ê·ž 가욎데 여섯 칞에 둘짞로 서서 158개에서는 43칞읎에요. Penrose 가 겚눈 칞은 쎘쎘핚 × 색 3,4 예요. 쎘쎘핚 0.195~0.299 에 색 0.509 위읞 자늬읎고, 4판의 Jacquard Loom 혌자 서 있었얎요. Penrose 는 쎘쎘핚 0.264, 색 0.583 윌로 ê±°êž° ì„°ì–Žìš”. 같은 값윌로 움직임 × 쎘쎘핚 4,3 곌 쎘쎘핚 × 채움 3,3 의 Moire Rings, 쎘쎘핚 × 복잡도 3,2 의 Peg Light, 복잡도 × 색 2,4 의 Gear Train, 채움 × 색 3,4 의 Marbling 곁에도 ì„°ì–Žìš”. 1 LED 얎두욎 선윌로 나눈 작은 타음에 선명한 색을 여럿 두멎 쎘쎘핚곌 색읎 핚께 높은 자늬에 닿아요. 26판도 Ʞ쀀 158개의 21장읎 ë‹€ `none` 읎었얎요. 팚턎읎 하나뿐읞 칞 43 가욎데 넷을 겚눎얎요. Dark Field 는 검은 바탕 위의 흩은 빛윌로 채움 0.263 에 쎘쎘핚 0.222, 복잡도 2.081 읎띌, Foxfire 혌자읎던 복잡도 × 채움 3,1 곌 Peg Light 혌자읎던 쎘쎘핚 × 채움 3,1 에 씚앗 여덟읎 ë‹€ 듀얎요. Origami 는 얎두욎 탁자 위 종읎 한 장읎띌 채움 0.264 읎고, 2쎈의 정사각형읎 8쎈에 좁은 학 Ʞ볞형읎 되얎 몚양 바뀜 0.255 로 Domino Run 혌자읎던 채움 × 몚양 바뀜 1,3 에 서요. Lichtenberg 는 Rabbit Ears 혌자읎던 복잡도 × 몚양 바뀜 4,3 에 씚앗 여덟 가욎데 음곱읎 듀얎요. ë„€ 칞 말고는 바뀐 칞읎 없얎서, 168개에서 팚턎읎 하나뿐읞 칞은 39칞읎에요. 27판도 Ʞ쀀 168개의 21장읎 ë‹€ `none` 읎었고, 39칞 가욎데 둘을 겚눎얎요. Mitosis 는 채움 0.263, 색 0.540 윌로 Neon Alley 곁의 채움 × 색 1,4 에 서요. 판의 4분의 1만 쌜는 귞늌에서 색 0.5 륌 넘Ʞ렀멎 쌜진 LED 가 거의 ë‹€ 순색 최대 밝Ʞ여알 핎서, 형ꎑ의 번짐은 셈에 듀지 않는 21/255 아래로 눌렀얎요. Lava Lake 는 복잡도 2.222, 채움 0.271 로 Moth Lamp 곁의 복잡도 × 채움 4,1 에 서요. 복잡도는 프레임 바읎튞 값의 엔튞로플륌 거의 귞대로 따띌서, 얎두욎 지각의 결을 채널마닀 0~24 전부로 넓히자 1.866 읎 2.010 읎 됐얎요. 붉은 용암은 쌜진 LED 하나에 24 륌 넘는 바읎튞가 빚강 하나뿐읎띌, 읎음맀륌 늘늬Ʞ볎닀 죌황 쪜윌로 데우는 펞읎 채움을 덜 쓰고 복잡도륌 벌었얎요. 색 축의 아래 끝은 Cloud Chamber 의 0.052, 몚양 바뀜 축의 아래 끝은 Climbing Wall 의 0.021 로 옮겚졌지만 혌자 서는 칞은 귞대로띌서, 178개에서 팚턎읎 하나뿐읞 칞은 37칞읎에요. 28판도 Ʞ쀀 178개의 21장읎 ë‹€ `none` 읎었고, 37칞 가욎데 둘을 겚눎얎요. Aztec Diamond 는 쎘쎘핚 0.350, 색 0.640 윌로 Millefiori 곁의 쎘쎘핚 × 색 4,4 에 서요. 쎘쎘핚은 가장 밝은 채널로 재서 ë„€ 색 도믞녞가 맞닿아도 턱읎 없었고, LED 에 묶은 첎컀판읎 ê·ž 턱을 쀬얎요. Stadium Billiard 는 움직임 2.362, 채움 0.186 윌로 Warp 곁의 움직임 × 채움 4,1 에 서요. ê³µ 읎백 개가 1쎈에 125 LED 륌 가멎서 짧은 자췚만 낚겚요. 겚누지 않은 닀섯 칞에도 둘짞가 듀얎서 188개에서 팚턎읎 하나뿐읞 칞은 30칞읎에요. 29판도 Ʞ쀀 188개의 21장읎 ë‹€ `none` 읎었고, 30칞 가욎데 둘을 겚눎얎요. 28판읎 몚듈러 형식의 등고선윌로 겚눠 볎띌던 가장자늬 × 움직임 1,4(Mandelbrot 혌자)는 J-Invariant 가 움직임 0.372 로 몚자랐고, Hopf Fibration 읎 가장자늬 0.207, 움직임 1.607 로 ì„°ì–Žìš”. 원 음흔 개가 저마닀 1쎈에 몇 LED 씩 쓞고 가서 움직임읎 높고, 원마닀 옅은 빛 번짐을 둬서 가장자늬가 낮아요. J-Invariant 는 복잡도 2.693, 색 0.400 윌로 Spectrogram 곁의 복잡도 × 색 4,3 에 서요. 같은 판의 Cream Swirl(0.028)곌 Serum Gel(0.079)읎 가장자늬 축의 아래 끝을 Fire 의 0.120 에서 낎렀서, 가장자늬 칞 겜계가 0.305, 0.560, 0.815 에서 0.232, 0.514, 0.796 윌로 옮겚졌얎요. 귞래서 198개에서는 가장자늬 × 쎘쎘핚 1,2 가 23판 뒀로 처음 `*` 가 붙은 빈 칞읎에요. 가장자늬 0.232 아래에 쎘쎘핚 0.091~0.195 읞 자늬띌, 번지는 바탕 위에 잔 결읎 고륎게 깔늰 귞늌읎 듀얎섀 수 있얎요. 같은 지도의 1,3 곌 1,4, 가장자늬 × 채움 1,1 도 비었지만 끝의 팹턮 하나륌 빌멎 찚서 `*` 가 없얎요. 198개에서 팚턎읎 하나뿐읞 칞은 24칞읎에요. 쎘쎘핚읎 0.299 륌 넘는 팚턎읎 20판에서 셋 늘었고, 셋 ë‹€ ê·ž 값을 닀륞 Ꞟ로 냈얎요. Agate 는 1 LED 띠륌 벜까지의 거늬로 귞얎서, 축에 나란한 띠는 읎웃 쌍의 절반에서만 찚읎가 나고 대각선 띠는 70% 쯀에서 나요. 방향읎 고륎게 섞읎멎 띠 너비 0.85 LED 얞저늬가 가장 높았얎요. Millefiori 는 쎘쎘핚을 가장 밝은 채널로 재서 윔발튞와 칎나늬아 녞랑읎 맞닿아도 턱읎 0 읎었얎요. 고늬마닀 밝은 유늬와 ê·ž 유늬륌 얎둡게 뜑은 유늬륌 번갈아 넣얎알 했얎요. Sunglint 는 반짝임을 묌결 띠마닀 첎컀판의 한쪜 칞에만 둬서 반짝임끌늬 닿지 않게 했얎요. 셋 ë‹€ 채움읎 0.85 륌 넘얎서, 쎘쎘핚 × 채움 4,4 는 꺌진 LED 없읎도 닿는 칞읎었얎요. 21판의 Sandpile 읎 음곱짞예요. 칞 하나가 LED 하나읎고 알 셋 옆의 알 둘읎 가장 흔한 읎웃 쌍읎띌, 밝Ʞ륌 0, 2, 1, 3 찚례로 올렀 더믞 안의 쎘쎘핚읎 0.40 얞저늬예요. 판의 75% 는 덮여알 0.3 을 넘얎요. 22판의 Pin Art 와 Night Flight 가 여덟짞와 아홉짞예요. Pin Art 는 핀곌 틈의 첎컀 결읎띌 읎웃 쌍읎 몚두 핀곌 틈의 쌍읎고, Night Flight 는 Pancakes 의 첎컀 결에 1 LED 가로등을 LED 의 12.7% 만큌 얹얎서 가장자늬 0.258 에 쎘쎘핚 0.328 읎에요. 쎘쎘핚곌 가장자늬는 가장 밝은 채널로 재서, 색을 섞는 자늬마닀 섞은 ë’€ 가장 밝은 채널을 닀시 맞춰알 했얎요. 땅곌 강묌을 섞은 자늬에서 가장 밝은 채널읎 0.68 까지 낎렀가 첎컀 결의 37 읎 41~60 윌로 넘얎갔고, 귞대로 둔 판은 가장자늬가 0.05 높았얎요. 23판의 Sequins 가 엎짞예요. 첎컀 ê²° 없읎, 가로 5 LED 와 섞로 3 LED 간격윌로 엇갈렀 당 슀팜Ꞁ의 빛점곌 귞늌자 쀄로 0.327 을 냈얎요. 쎘쎘핚은 묎늬의 몚양볎닀 턱의 크Ʞ륌 따띌가서, 슀팜Ꞁ 몞통을 0.8 에서 0.6 윌로 얎둡게 하자 은빛 멎읎 0.339 에서 0.311 로 낎렀갔얎요. 26판의 Lichtenberg 는 0.2 륌 넘ꞎ 판을 버렞얎요. 판 얎디서나 돋는 1 LED 잔가지로 0.248 을 냈더니 검은 직교 귞묌읎 나묎판을 덮얎 믞로로 볎였얎요. 잔가지륌 탄 쀄Ʞ에만 달아 빈 나묎륌 42% 낚Ʞ자 0.130 읎었얎요. 쎘쎘핚은 판 전첎 읎웃 쌍의 평균읎띌, 빈 나묎가 넓게 낹는 귞늌은 쀄Ʞ 둘레가 아묎늬 잘아도 평균읎 낮아요. 같은 판의 Dark Field 는 검은 바탕에 섞균 몇백곌 밝은 테두늬륌 판 곳곳에 흩얎 0.222 륌 냈얎요. @@ -2477,7 +2538,7 @@ ESP32-S3 로 옮Ꞟ 때 걞늎 것도 있얎요. Reaction Diffusion 은 Node 에 ### 아직 없는 갈래 -`families.json` 의 쉰여섯 갈래에 없는 것 가욎데 섞욎 팚널에 맞을 것을 적얎 둬요. 29판읎 연 위상·맀듭 갈래는 Hopf Fibration 곌 31판의 Boy's Surface, 33판의 Borromean Rings, 34판의 Lakes of Wada, 36판의 Heawood Torus, 38판의 Sperner's Lemma, 39판의 Borsuk-Ulam, 44판의 Ham Sandwich, 46판의 Reeb Graph 예요. 28판읎 엎렀닀 접은 갈래띌 28판의 Hairy Ball 은 녞읎슈·흐늄에 귞대로 있얎요. 섞잎 맀듭처럌 읎늄 있는 맀듭읎 ê·ž 갈래의 여덟짞로 맞아요. 29판은 화장품 팝업 갈래도 엎었얎요. 죌제 6 의 몫읎띌 죌제 2 로 섞지 않았고, Cream Swirl, Serum Gel, Glitter Fall, Loose Powder 넷읎 ë‹€ 듀얎 있얎요. 30판의 Swatch Row, Pearl Sheen, Bi-Phase, Satin Fold 가 ê·ž 갈래에 넷을 더했고, 31판의 Lip Gloss, Foam Lather, Setting Mist, Sponge Blend 가 넷을 더 더핮 엎둘읎에요. 32판의 Cushion Mesh, Clay Mask, Sugar Scrub, Chrome Powder 가 넷을 더핮 엎여섯읎고, 33판의 Cleansing Balm, Gold Leaf, Essence Drop, Peel-Off 가 넷을 더핮 슀묌읎에요. 34판의 Cat Eye, Velvet Nap, Mood Tint, Hair Gloss 가 넷을 더핮 슀묌넷읎고, 35판의 Bath Fizz, Toner Pad, Sheet Mask, Gilded Cream 읎 넷을 더핮 슀묌여덟읎고, 36판의 Lotion Ribbon, Shimmer Oil, Blooming Gel, Whipped Soap 읎 넷을 더핮 서륞둘읎에요. 38판의 Milk Ring, Couette Shimmer, Photochromic, Serum Fingers 가 넷을 더핮 서륞여섯읎에요. 39판의 Soap Film, Blotting Paper, Serum Drip, Crackle Polish 가 넷을 더핮, 37판의 넷까지 마흔넷읎에요. 41판은 할로윈 갈래륌 엎었얎요. 죌제 6 의 몫읎띌 죌제 2 로 섞지 않았고, Jack-o'-Lantern, Sheet Ghosts, Spider Drop, Cauldron, Eyes in the Dark 닀섯읎 듀얎 있얎요. 28판읎 연 수론 갈래는 Ford Circles 와 30판의 Three Gaps, 31판의 Gaussian Moat, 32판의 Gilbreath Table, 33판의 Goldbach Comet, 35판의 Kolakoski, 36판의 Elliptic Chords, 39판의 Explicit Formula, 44판의 Bulgarian Solitaire 예요. 27판읎 연 철도 갈래는 Funicular 하나읎고, 필늄·영화 갈래는 Horse in Motion 하나예요. 두 갈래는 죌제 표가 수학 쪜윌로 바뀌Ʞ 전에 엎었얎요. ê·ž 뒀로 ROUND.md 는 사묌로 묶는 갈래륌 더 ì—Žì§€ 않아요. 26판읎 연 얌음·극지 갈래는 Iceberg 와 27판의 Sled Dogs 읎고, 믞생묌 갈래는 Dark Field 와 27판의 Mitosis 예요. 25판읎 연 농사·밭 갈래는 Rice Terraces 와 26판의 Rice Planting, 27판의 Center Pivot 읎고, 화산 갈래는 Eruption 곌 26판의 Geyser, 27판의 Lava Lake 예요. 24판읎 연 공사·걎섀 갈래는 Tower Crane 곌 25판의 Excavation, 26판의 Implosion 읎에요. 24판읎 연 도자Ʞ 갈래는 Potter's Wheel 곌 25판의 Kintsugi, 26판의 Noborigama 예요. 23판읎 연 묎대·공연 갈래는 Proscenium 곌 24판의 Flying Trapeze, 25판의 Marionette, 26판의 Shadow Play 예요. 23판읎 연 짐승 갈래는 Giraffe 와 24판의 Emergence, 25판의 Snow Monkeys 예요. 22판읎 연 몞 갈래는 Circulation 곌 23판의 Axon, 24판의 Breath, 27판의 Fluoroscope 예요. 22판읎 연 비행 갈래는 Liftoff 와 23판의 Final Approach, 24판의 Thermal, 25판의 Skydiver 예요. 20판읎 연 새 갈래는 Kingfisher 와 21판의 Woodpecker, 22판의 Weaver, 23판의 Skein 읎에요. 20판읎 연 화학·싀험 갈래는 Chromatography 와 21판의 Chemical Garden, 22판의 Flame Test, 23판의 Elephant Toothpaste 예요. 21판읎 연 종읎 갈래는 Pop-up Book 곌 22판의 Senbazuru, 23판의 Hanging Scroll, 26판의 Origami 예요. 21판읎 연 욎동·겜Ʞ 갈래는 Lap Pool 곌 22판의 Bowling Lane, 23판의 High Dive, 26판의 Pole Vault, 27판의 Climbing Wall 읎에요. 위아래로 움직읎는 겜Ʞ는 High Dive 와 Pole Vault 뒀로 27판에 싀낎 암벜읎 ì„°ì–Žìš”. 19판읎 연 부엌 갈래는 Popcorn 곌 20판의 Kettle, 21판의 Pancakes 읎고, 버섯·균사 갈래는 Foxfire 와 20판의 Veiled Lady, 21판의 Bread Mould 예요. 18판읎 연 유늬 갈래는 Stained Glass 와 19판의 Smalti, 20판의 Millefiori, 27판의 Glassblower 예요. ꎑ묌·결정 갈래는 Bismuth 와 19판의 Dripstone, 20판의 Agate 읎고, 놀읎공원 갈래는 Drop Tower 와 19판의 Helter Skelter, 20판의 High Striker, 25판의 Ferris Wheel 읎에요. 말읎 였륎낎늬는 회전목마는 아직 없얎요. 17판읎 연 전자 회로 갈래는 Circuit Board 와 18판의 Server Rack, 21판의 Nixie 읎고, 장난감 갈래는 Marble Run 곌 18판의 Peg Light, 21판의 Slinky, 22판의 Pin Art 예요. 16판읎 연 바닀 갈래는 Abyss 와 17판의 Shoreline, 20판의 Sunglint, 23판의 Bait Ball 읎고, 곀충 갈래는 Fireflies 와 17판의 Ant Farm, 20판의 Moth Lamp, 24판의 Waggle Dance 예요. 15판읎 연 도시 갈래는 Metro Map 곌 16판의 Neon Alley, 17판의 Crossroads, 22판의 Night Flight 예요. 15판읎 연 음악 갈래는 Player Piano 와 16판의 Harp, 18판의 Turntables, 24판의 Pipe Organ 읎에요. 4판읎 연 직묌 갈래는 Jacquard Loom 곌 15판의 Plait, 19판의 Needlepoint, 23판의 Sequins 읎고, 8판읎 연 Ʞ계 갈래는 Gear Train 곌 15판의 Cam Wave, 19판의 Paternoster 예요. 12판읎 연 천첎 갈래는 Star Trails 와 15판의 Analemma, 16판의 Lunation, 22판의 Galilean Moons 예요. 위성의 궀도는 Galilean Moons 가 ê·žë žê³ , 핎륌 도는 행성은 아직 없얎요. 늬듬·펄슀 갈래는 1판의 Euclid Pulse 와 Pulse Bars 뒀로 20판의 Change Ringing, 32판의 Arnold Tongues, 35판의 Chimera State, 38판의 Infinity Series, 41판의 Siteswap, 44판의 Kac Ring, 46판의 Stochastic Resonance 가 ì„°ì–Žìš”. +`families.json` 의 쉰여섯 갈래에 없는 것 가욎데 섞욎 팚널에 맞을 것을 적얎 둬요. 29판읎 연 위상·맀듭 갈래는 Hopf Fibration 곌 31판의 Boy's Surface, 33판의 Borromean Rings, 34판의 Lakes of Wada, 36판의 Heawood Torus, 38판의 Sperner's Lemma, 39판의 Borsuk-Ulam, 44판의 Ham Sandwich, 46판의 Reeb Graph, 48판의 Möbius Energy 예요. 28판읎 엎렀닀 접은 갈래띌 28판의 Hairy Ball 은 녞읎슈·흐늄에 귞대로 있얎요. 섞잎 맀듭은 48판의 Möbius Energy 가 처음 섞웠얎요. 29판은 화장품 팝업 갈래도 엎었얎요. 죌제 6 의 몫읎띌 죌제 2 로 섞지 않았고, Cream Swirl, Serum Gel, Glitter Fall, Loose Powder 넷읎 ë‹€ 듀얎 있얎요. 30판의 Swatch Row, Pearl Sheen, Bi-Phase, Satin Fold 가 ê·ž 갈래에 넷을 더했고, 31판의 Lip Gloss, Foam Lather, Setting Mist, Sponge Blend 가 넷을 더 더핮 엎둘읎에요. 32판의 Cushion Mesh, Clay Mask, Sugar Scrub, Chrome Powder 가 넷을 더핮 엎여섯읎고, 33판의 Cleansing Balm, Gold Leaf, Essence Drop, Peel-Off 가 넷을 더핮 슀묌읎에요. 34판의 Cat Eye, Velvet Nap, Mood Tint, Hair Gloss 가 넷을 더핮 슀묌넷읎고, 35판의 Bath Fizz, Toner Pad, Sheet Mask, Gilded Cream 읎 넷을 더핮 슀묌여덟읎고, 36판의 Lotion Ribbon, Shimmer Oil, Blooming Gel, Whipped Soap 읎 넷을 더핮 서륞둘읎에요. 38판의 Milk Ring, Couette Shimmer, Photochromic, Serum Fingers 가 넷을 더핮 서륞여섯읎에요. 39판의 Soap Film, Blotting Paper, Serum Drip, Crackle Polish 가 넷을 더핮, 37판의 넷까지 마흔넷읎에요. 41판은 할로윈 갈래륌 엎었얎요. 죌제 6 의 몫읎띌 죌제 2 로 섞지 않았고, Jack-o'-Lantern, Sheet Ghosts, Spider Drop, Cauldron, Eyes in the Dark 닀섯읎 듀얎 있얎요. 28판읎 연 수론 갈래는 Ford Circles 와 30판의 Three Gaps, 31판의 Gaussian Moat, 32판의 Gilbreath Table, 33판의 Goldbach Comet, 35판의 Kolakoski, 36판의 Elliptic Chords, 39판의 Explicit Formula, 44판의 Bulgarian Solitaire, 48판의 Chebyshev's Bias 예요. 27판읎 연 철도 갈래는 Funicular 하나읎고, 필늄·영화 갈래는 Horse in Motion 하나예요. 두 갈래는 죌제 표가 수학 쪜윌로 바뀌Ʞ 전에 엎었얎요. ê·ž 뒀로 ROUND.md 는 사묌로 묶는 갈래륌 더 ì—Žì§€ 않아요. 26판읎 연 얌음·극지 갈래는 Iceberg 와 27판의 Sled Dogs 읎고, 믞생묌 갈래는 Dark Field 와 27판의 Mitosis 예요. 25판읎 연 농사·밭 갈래는 Rice Terraces 와 26판의 Rice Planting, 27판의 Center Pivot 읎고, 화산 갈래는 Eruption 곌 26판의 Geyser, 27판의 Lava Lake 예요. 24판읎 연 공사·걎섀 갈래는 Tower Crane 곌 25판의 Excavation, 26판의 Implosion 읎에요. 24판읎 연 도자Ʞ 갈래는 Potter's Wheel 곌 25판의 Kintsugi, 26판의 Noborigama 예요. 23판읎 연 묎대·공연 갈래는 Proscenium 곌 24판의 Flying Trapeze, 25판의 Marionette, 26판의 Shadow Play 예요. 23판읎 연 짐승 갈래는 Giraffe 와 24판의 Emergence, 25판의 Snow Monkeys 예요. 22판읎 연 몞 갈래는 Circulation 곌 23판의 Axon, 24판의 Breath, 27판의 Fluoroscope 예요. 22판읎 연 비행 갈래는 Liftoff 와 23판의 Final Approach, 24판의 Thermal, 25판의 Skydiver 예요. 20판읎 연 새 갈래는 Kingfisher 와 21판의 Woodpecker, 22판의 Weaver, 23판의 Skein 읎에요. 20판읎 연 화학·싀험 갈래는 Chromatography 와 21판의 Chemical Garden, 22판의 Flame Test, 23판의 Elephant Toothpaste 예요. 21판읎 연 종읎 갈래는 Pop-up Book 곌 22판의 Senbazuru, 23판의 Hanging Scroll, 26판의 Origami 예요. 21판읎 연 욎동·겜Ʞ 갈래는 Lap Pool 곌 22판의 Bowling Lane, 23판의 High Dive, 26판의 Pole Vault, 27판의 Climbing Wall 읎에요. 위아래로 움직읎는 겜Ʞ는 High Dive 와 Pole Vault 뒀로 27판에 싀낎 암벜읎 ì„°ì–Žìš”. 19판읎 연 부엌 갈래는 Popcorn 곌 20판의 Kettle, 21판의 Pancakes 읎고, 버섯·균사 갈래는 Foxfire 와 20판의 Veiled Lady, 21판의 Bread Mould 예요. 18판읎 연 유늬 갈래는 Stained Glass 와 19판의 Smalti, 20판의 Millefiori, 27판의 Glassblower 예요. ꎑ묌·결정 갈래는 Bismuth 와 19판의 Dripstone, 20판의 Agate 읎고, 놀읎공원 갈래는 Drop Tower 와 19판의 Helter Skelter, 20판의 High Striker, 25판의 Ferris Wheel 읎에요. 말읎 였륎낎늬는 회전목마는 아직 없얎요. 17판읎 연 전자 회로 갈래는 Circuit Board 와 18판의 Server Rack, 21판의 Nixie 읎고, 장난감 갈래는 Marble Run 곌 18판의 Peg Light, 21판의 Slinky, 22판의 Pin Art 예요. 16판읎 연 바닀 갈래는 Abyss 와 17판의 Shoreline, 20판의 Sunglint, 23판의 Bait Ball 읎고, 곀충 갈래는 Fireflies 와 17판의 Ant Farm, 20판의 Moth Lamp, 24판의 Waggle Dance 예요. 15판읎 연 도시 갈래는 Metro Map 곌 16판의 Neon Alley, 17판의 Crossroads, 22판의 Night Flight 예요. 15판읎 연 음악 갈래는 Player Piano 와 16판의 Harp, 18판의 Turntables, 24판의 Pipe Organ 읎에요. 4판읎 연 직묌 갈래는 Jacquard Loom 곌 15판의 Plait, 19판의 Needlepoint, 23판의 Sequins 읎고, 8판읎 연 Ʞ계 갈래는 Gear Train 곌 15판의 Cam Wave, 19판의 Paternoster 예요. 12판읎 연 천첎 갈래는 Star Trails 와 15판의 Analemma, 16판의 Lunation, 22판의 Galilean Moons 예요. 위성의 궀도는 Galilean Moons 가 ê·žë žê³ , 핎륌 도는 행성은 아직 없얎요. 늬듬·펄슀 갈래는 1판의 Euclid Pulse 와 Pulse Bars 뒀로 20판의 Change Ringing, 32판의 Arnold Tongues, 35판의 Chimera State, 38판의 Infinity Series, 41판의 Siteswap, 44판의 Kac Ring, 46판의 Stochastic Resonance 가 ì„°ì–Žìš”. ### 멋진 것 더 멋지게 @@ -2515,6 +2576,8 @@ ESP32-S3 로 옮Ꞟ 때 걞늎 것도 있얎요. Reaction Diffusion 은 Node 에 47판의 ì—Ž 개는 가장 낮은 녞람가 Talking Board 의 K2 Speed 로 ë„€ 자늬로 0.0510 읎고, Pumpkin Vine 의 K4 Curl 0.0529 와 Talking Board 의 K1 Hue 0.0575 가 귞닀음읎에요. ì—Ž 개의 녾뾌 마흔 개가 ë‹€ 0.05 륌 넘얎요. Talking Board 의 첫 판은 넷읎 0.001, 0.024, 0.015, 0.000 읎었얎요. 답의 쎈록읎 포읞터 찜곌 낱말에만 싀렀서 Hue 가 바꟞는 넓읎가 작았고, 판의 칠읎 같읎 돌게 하자 올랐얎요. ë„·ì§ž 녾뾌는 Ꞁ자에 뚞묎는 시간읎었는데 첫 Ꞁ자가 2쎈 넘얎 쌜젞서 0.000 읎었얎요. 녾뾌는 2~3쎈 찜에서만 재서, ê·ž ì°œ 뒀에 였는 음을 바꟞는 녾뾌는 0 윌로 셈핎젞요. Pumpkin Vine 의 Lanterns 도 같은 까닭윌로 0.0003 읎띌 Vines 로 바꿚얎요. +48판의 ì—Ž 개는 가장 낮은 녞람가 Barn Owl 의 K3 Hunger 로 ë„€ 자늬로 0.0410 읎고, Grim Reaper 의 K4 Fog 0.0460 곌 K3 Graves 0.0466 읎 귞닀음읎에요. 녾뾌 마흔 개가 ë‹€ 0.04 륌 넘얎요. Barn Owl 의 Hunger 는 사냥 사읎의 Ʞ닀늌을 바꟞는 녞람띌서, 첫 사냥을 2.4쎈, 1.6쎈, 0.7쎈로 당겚 3쎈 ì°œ 안에 듀게 핎알 셈에 듀었얎요. 47판읎 볞 대로 ì°œ 뒀의 음을 바꟞는 녾뾌는 0 윌로 셈핎젞요. Trick or Treat 의 K3 도 묎늬 수였을 때 둘짞 묎늬가 9쎈쯀에알 와서 0.0000 읎었얎요. Fractal Percolation 의 K3 은 셋업에서 집합을 ë‹€ 깎아 둔 첫 판에서 0.000 읎었얎요. 녞람륌 읜Ʞ 전에 귞늌읎 정핎지멎 녞람가 바꿀 것읎 없얎요. Grim Reaper 의 Graves 는 한 쀄의 묎덀 띠가 판의 18% 뿐읎띌 0.02~0.03 읎었고, 묎덀을 여섯씩 얞덕을 따띌 쌓자 올랐얎요. + Flock, Harmonograph, Big Type 은 처음 화멎읎 빚강읎에요. ### 섞로 64×128 @@ -2557,9 +2620,11 @@ Flock, Harmonograph, Big Type 은 처음 화멎읎 빚강읎에요. 47판에서는 Pumpkin Vine 곌 Lonely Runner 가 128쀄을 썚요. Lonely Runner 는 128쀄을 위아래로 읎은 원형 튞랙읎띌 `thirds` 가 43/46/45 예요. Pumpkin Vine 은 덩굎읎 얞덕에서 위쪜 22쀄 귌처까지 감아 올띌 100/100/92 읞데, 볎랏빛 하늘읎 24 위띌서 ê·ž 몫읎 든 값읎에요. 같은 씚앗윌로 하늘을 지우고 재멎 덩굎곌 달만윌로 23/41/64 예요. Koebe Packing(37/67/44)곌 Specimen Jar(57/71/70), Web Tremor(99/72/33), Talking Board(38/94/98), Oloid(29/46/33)도 128쀄을 쓰지만 죌제 4 로 섞지 않았얎요. Pumpkin Carving 은 위 하늘을 24 아래로 낎렀 8/98/93 읎에요. +48판에서는 Flashing Ratchet 곌 Barn Owl 읎 128쀄을 썚요. Flashing Ratchet 은 16 LED 짜늬 톱니 여덟읎 판을 위아래로 ë‹€ 채우고 입자가 위아래로 읎얎젞서 `thirds` 가 91/93/91 읎에요. 바탕까지 칠하는 귞늌읎띌 2~8쎈에 밝Ʞ가 24/255 넘게 바뀐 LED 로 섞멎 100/100/99.8 읎에요. Barn Owl 은 나묎가 풀밭에서 ꌭ대Ʞ까지 서서 92/89/76 읎고, 나묎륌 빌도 하늘읎 쌜젞 100/100/84 예요. 바뀐 LED 로 섞멎 46.3/78.1/66.7 읎고, 올빌믞는 2~8쎈에 25쀄에서 122쀄까지 닀녀요. Chebyshev's Bias(100/100/100)와 Grim Reaper(98/99/98), Headless Rider(99/72/73), Bone Pile(86/91/91), Trick or Treat(87/84/89), Fractal Percolation(73/80/75), Double Gyre(66/66/66)도 128쀄을 ë‹€ 쓰지만 죌제 4 로 섞지 않았얎요. Möbius Energy 는 검은 판에 ꎀ 하나띌 17/23/10 읎에요. + ### 낯선 수학 -죌제 5 로 쓎 수학곌 닀음에 핮 볌 수학읎에요. 28판읎 처음 ì—Ž 개륌 썌얎요. 슈람-뢰람너 진화(Loewner Trace), 포드 원(Ford Circles), 턾 난 ê³µ 정늬(Hairy Ball), 아슈텍 닀읎아몬드와 북극권(Aztec Diamond), 부니몚비치 겜Ʞ장(Stadium Billiard), 띌우지 프랙탈(Rauzy Fractal), 고셋의 4₂₁ 닀멎첎와 E8(Gosset Polytope), 윘웚읎의 볎고 말하Ʞ 수엎(Look and Say), 위토프 게임의 귞런디 값(Wythoff Nim), 쌍곡 평멎의 띠 몚형(Hyperbolic Band)읎에요. 29판은 여섯을 더했얎요. 큎띌읞의 j 불변량(J-Invariant), 혾프 올뭉치(Hopf Fibration), 닀읎슚 람띌욎 욎동(Dyson Gas), 가쌀알 바늘 묞제의 페론 나묎(Perron Tree), 임계선 위의 늬만 제타 핚수(Zeta Loops), Ꞟ버튞-섀넌-늬슈 늬플곌 베읎얎-디아윔니슀의 음곱 번(Seven Shuffles)읎에요. 30판도 여섯을 더했얎요. 섞 간격 정늬(Three Gaps), 혞프슀태터 나비와 TKNN 정수(Hofstadter Butterfly), 녞읎만의 왞부 당구(Outer Billiards), 볌드의 탄도 퇎적곌 KPZ 앞멎(Ballistic Deposition), 아륎놀튞의 믞앀더(Meander), 람로드벀튞와 핎뚞슬늬의 슀믞Ʞ(Percolation)예요. 31판도 여섯을 더했얎요. 마슀킷 조각의 큎띌읞 êµ° 극한 집합(Indra's Pearls), 늬틀우드 닀항식의 ê·Œ(Littlewood Roots), 고든의 가우슀 핎자(Gaussian Moat), 아륎놀튞의 고양읎 사상(Cat Map), 자부슀킀와 크룚슀칌의 KdV 솔늬톀(KdV Solitons), 볎읎 곡멎(Boy's Surface)읎에요. ROUND.md 의 「낯선 수학 예」 여덟 가욎데 쓎 것은 슈람-뢰람너 진화와 아슈텍 닀읎아몬드, 몚듈러 형식, 혾프 올뭉치, 섞 간격 정늬, 왞부 당구 여섯읎었고, 31판읎 늬틀우드 닀항식의 귌곌 큎띌읞 군의 극한 집합을 썚서 여덟을 ë‹€ 썌얎요. 32판도 여섯을 더했얎요. 타벗 횚곌와 양자 칎펫(Talbot Carpet), Ꞟ람레슀 추잡의 표(Gilbreath Table), 원 사상의 아륎놀튞 혀(Arnold Tongues), 윘웚읎와 띌딘의 핀휠 타음링(Pinwheel Tiling), 윌킚슚곌 윌렘섌의 칚입 슀믞Ʞ(Invasion Percolation), 욞람의 묞제와 핎뚞슬늬 곌정(Hammersley Lines)읎에요. 33판도 여섯을 더했얎요. 에읞절, 홀로읎드, 로믹, 비띌귞의 묎작위 정렬 넀튞워크(Sorting Network), 베륎튞랑의 ì—­ì„€(Bertrand Paradox), 펜로슈의 비출 수 없는 ë°©(Penrose Room), 당듀랭 구(Dandelin Spheres), 테읎튞의 볎로메였 고늬(Borromean Rings), 곚드바흐 혜성(Goldbach Comet)읎에요. 34판도 여섯을 더했얎요. 톰슚 묞제와 였음러가 낚Ʞ는 닀섯 읎웃 엎둘(Thomson Problem), 쌈빈의 ë°° 묌결(Kelvin Wake), 요넀알마가 낾 와닀의 혞수(Lakes of Wada), 샀읎데거의 강 귞묌(Scheidegger Rivers), 몚저의 소파 묞제와 핎뚞슬늬의 소파(Moving Sofa), 크누슀의 춀추는 링크로 푌 펜토믞녞 덮Ʞ(Pentomino Cover)예요. 35판도 여섯을 더했얎요. 뢞로의 폭읎 음정한 곡선(Constant Width), 렌니의 죌찚 묞제(Rényi Parking), 칎늬의 비죌Ʞ 왕 타음(Kari-Culik Tiles), 윜띌윔슀킀 수엎(Kolakoski), 쿠띌몚토와 바톡토흐의 킀메띌 상태(Chimera State), 죌윥슀킀 날개(Joukowsky Airfoil)예요. 36판도 여섯을 더했얎요. 귞레읎슚의 정늬가 말하는 곡선 닚축 흐멄(Curve Shortening), 헀우드의 음곱 색 토러슀 지도(Heawood Torus), 슈바륎잠 랜턮(Schwarz Lantern), 윌람러햄곌 깁슀의 현상(Gibbs Phenomenon), 타원 곡선의 현곌 접선(Elliptic Chords), 지뚌의 파국 Ʞ계(Catastrophe Machine)예요. 37판도 여섯을 더했얎요. 포천의 쓞Ʞ 선 알고늬슘(Fortune's Sweep), 앙투안의 목걞읎(Antoine's Necklace), 칎륎만 소용돌읎 Ꞟ의 안정성(Kármán Street), 당당한 원판의 헥사틱 상(Hexatic Phase), 졎슚-멜 쪜맀와 아람띌믞 법칙(Johnson-Mehl), 쿠띌몚토-시바신슀킀 방정식(Kuramoto-Sivashinsky)읎에요. 38판도 여섯을 더했얎요. 앀더슚 국소화(Anderson Localization), 묎얎가 ì°Ÿê³  Chenciner 와 Montgomery 가 슝명한 섞 몞 묞제의 8자 핮(Figure-Eight Orbit), 예섌곌 빈튞너, 에륎되시의 베륎누읎 합성곱(Bernoulli Convolutions), 람띌우얎륎의 고정점 정늬륌 슝명하는 슈페륎너 볎조정늬(Sperner's Lemma), 에겐베륎거와 폎늬아의 항아늬(Pólya Urn), 뇌륎고륎의 묎한 수엎(Infinity Series)읎에요. 39판도 여섯을 더했얎요. 고든, 웹, 월퍌튞의 등슀펙튞럌 북(Isospectral Drums), 폰 망곚튞의 ꌎ로 쓎 늬만의 명시 공식(Explicit Formula), 요한 베륎누읎의 최닚 강하선(Brachistochrone), 슈타읎너의 포늬슘(Steiner Chain), 몚런 집닚윌로 귞늰 킹뚌의 합류(Kingman Coalescent), 볎륎수크-욞람 정늬(Borsuk-Ulam)예요. 40판도 여섯을 더했얎요. 앚더슀의 람띌욎 연속 랜덀 나묎(Continuum Random Tree), 슈뢰딩거의 수소 원자 고유 핚수(Hydrogen Orbital), 저도비치 귌사의 팬쌀읎크(Zeldovich Pancakes), 망덞람로가 낎닀볎고 례러, 슈람, 베륎너가 슝명한 람띌욎 욎동의 바깥 겜계(Brownian Frontier), 복소 ꞎ슈부륎크-란닀우 방정식의 결핚 난류(Defect Turbulence), 룚퍌튞 왕자의 정육멎첎(Prince Rupert's Cube)예요. 41판은 닀섯을 더했얎요. 홀곌 왜걎의 현수선 Ꞟ곌 정닀각형 바퀮(Square Wheels), 퐁슬레의 포늬슘(Poncelet Porism), 슐레플늬의 4찚원 정닀포첎륌 자륞 닚멎(Polytope Slices), 큎늌쌁곌 티뚌, 마귞누손의 사읎튞슀왑(Siteswap), Dudeney 의 잡화상 퍌슐곌 월늬슀-볎여읎-게륎비엔 정늬(Hinged Dissection)예요. 42판은 닀섯을 더했얎요. 바륞슀도륎프 규칙윌로 도는 Ʞ사 여행(Knight's Tour), 페륎믞와 파슀타, 욞람, 칭구의 되돌아옎(FPUT Recurrence), 포슬늬에-늜킚 반전Ʞ(Peaucellier Linkage), 몰늬의 삌등분선 정늬(Morley Triangle), 랄곌 마드띌슀, 소칌의 플벗 알고늬슘윌로 돌며 자Ʞ 회플 걞음(Pivot Walk)읎에요. 43판은 닀섯을 더했얎요. 월늬슀 워컀의 칌레읎도사읎큎(Kaleidocycle), 쌍곡형에서 록소드롬형윌로 감Ʞ는 뫌비우슀 변환의 흐멄(Möbius Loxodrome), 터튞의 용수철 정늬(Tutte Embedding), 디펀튞와 크래비잠의 고늬 정늬륌 품은 히토메자시 사시윔(Hitomezashi), 룜게 현상곌 첎비쇌프 점(Runge Phenomenon)읎에요. 44판은 닀섯을 더했얎요. 마크 칎잠의 고늬 몚형(Kac Ring), 슀톀곌 튜킀의 햄 샌드위치 정늬(Ham Sandwich), 람란튞의 불가늬아 솔늬테얎(Bulgarian Solitaire), 엘슀쌄슀와 프늬시의 탄도 소멞(Ballistic Annihilation), 페레귞늰의 솔늬톀(Peregrine Soliton)읎에요. 45판은 닀섯을 더했얎요. 로걎-셰프와 베륎시크-쌀로프의 극한 곡선윌로 닀가가는 영 도형의 플랑셰렐 성장(Plancherel Growth), 도람룚신 겜계 조걎을 걎 임계 읎징 몚형의 SLE(3) 겜계(Ising Interface), 프늬예제프와 동료듀의 였음러 걷개로 쌓는 로터 띌우터 응집곌 레빈-페레슀의 슀맀시 합(Rotor-Router), 폎늬아의 곌수원 묞제(Pólya's Orchard), 넬슚의 평멎 색칠 묞제와 아읎슈벚의 음곱 색(Hadwiger-Nelson)읎에요. 46판은 닀섯을 더했얎요. 람늎룚앙의 구역(Brillouin Zones), 몚슀 읎론곌 레람의 귞래프(Reeb Graph), 로렌잠 Ʞ첎와 시나읎의 당구(Lorentz Gas), 늬얞 Ꞁ래슀의 점묎늬(Glass Patterns), 벀치와 수테띌, 불플아니의 확률 공명(Stochastic Resonance)읎에요. 47판은 닀섯을 더했얎요. 였킀프와 홍, 슀튞로가잠의 슀워멀레읎터(Swarmalators), 빌슀의 왞로욎 달늬Ʞ 선수 추잡(Lonely Runner), 샀잠의 올로읎드와 ê·ž 전개도(Oloid), 쟚베의 원 채우Ʞ 정늬와 윜늰슀-슀티랐슚의 반복(Koebe Packing), 윘웚읎의 병사와 황ꞈ비 묎게(Conway's Soldiers)예요. +죌제 5 로 쓎 수학곌 닀음에 핮 볌 수학읎에요. 28판읎 처음 ì—Ž 개륌 썌얎요. 슈람-뢰람너 진화(Loewner Trace), 포드 원(Ford Circles), 턾 난 ê³µ 정늬(Hairy Ball), 아슈텍 닀읎아몬드와 북극권(Aztec Diamond), 부니몚비치 겜Ʞ장(Stadium Billiard), 띌우지 프랙탈(Rauzy Fractal), 고셋의 4₂₁ 닀멎첎와 E8(Gosset Polytope), 윘웚읎의 볎고 말하Ʞ 수엎(Look and Say), 위토프 게임의 귞런디 값(Wythoff Nim), 쌍곡 평멎의 띠 몚형(Hyperbolic Band)읎에요. 29판은 여섯을 더했얎요. 큎띌읞의 j 불변량(J-Invariant), 혾프 올뭉치(Hopf Fibration), 닀읎슚 람띌욎 욎동(Dyson Gas), 가쌀알 바늘 묞제의 페론 나묎(Perron Tree), 임계선 위의 늬만 제타 핚수(Zeta Loops), Ꞟ버튞-섀넌-늬슈 늬플곌 베읎얎-디아윔니슀의 음곱 번(Seven Shuffles)읎에요. 30판도 여섯을 더했얎요. 섞 간격 정늬(Three Gaps), 혞프슀태터 나비와 TKNN 정수(Hofstadter Butterfly), 녞읎만의 왞부 당구(Outer Billiards), 볌드의 탄도 퇎적곌 KPZ 앞멎(Ballistic Deposition), 아륎놀튞의 믞앀더(Meander), 람로드벀튞와 핎뚞슬늬의 슀믞Ʞ(Percolation)예요. 31판도 여섯을 더했얎요. 마슀킷 조각의 큎띌읞 êµ° 극한 집합(Indra's Pearls), 늬틀우드 닀항식의 ê·Œ(Littlewood Roots), 고든의 가우슀 핎자(Gaussian Moat), 아륎놀튞의 고양읎 사상(Cat Map), 자부슀킀와 크룚슀칌의 KdV 솔늬톀(KdV Solitons), 볎읎 곡멎(Boy's Surface)읎에요. ROUND.md 의 「낯선 수학 예」 여덟 가욎데 쓎 것은 슈람-뢰람너 진화와 아슈텍 닀읎아몬드, 몚듈러 형식, 혾프 올뭉치, 섞 간격 정늬, 왞부 당구 여섯읎었고, 31판읎 늬틀우드 닀항식의 귌곌 큎띌읞 군의 극한 집합을 썚서 여덟을 ë‹€ 썌얎요. 32판도 여섯을 더했얎요. 타벗 횚곌와 양자 칎펫(Talbot Carpet), Ꞟ람레슀 추잡의 표(Gilbreath Table), 원 사상의 아륎놀튞 혀(Arnold Tongues), 윘웚읎와 띌딘의 핀휠 타음링(Pinwheel Tiling), 윌킚슚곌 윌렘섌의 칚입 슀믞Ʞ(Invasion Percolation), 욞람의 묞제와 핎뚞슬늬 곌정(Hammersley Lines)읎에요. 33판도 여섯을 더했얎요. 에읞절, 홀로읎드, 로믹, 비띌귞의 묎작위 정렬 넀튞워크(Sorting Network), 베륎튞랑의 ì—­ì„€(Bertrand Paradox), 펜로슈의 비출 수 없는 ë°©(Penrose Room), 당듀랭 구(Dandelin Spheres), 테읎튞의 볎로메였 고늬(Borromean Rings), 곚드바흐 혜성(Goldbach Comet)읎에요. 34판도 여섯을 더했얎요. 톰슚 묞제와 였음러가 낚Ʞ는 닀섯 읎웃 엎둘(Thomson Problem), 쌈빈의 ë°° 묌결(Kelvin Wake), 요넀알마가 낾 와닀의 혞수(Lakes of Wada), 샀읎데거의 강 귞묌(Scheidegger Rivers), 몚저의 소파 묞제와 핎뚞슬늬의 소파(Moving Sofa), 크누슀의 춀추는 링크로 푌 펜토믞녞 덮Ʞ(Pentomino Cover)예요. 35판도 여섯을 더했얎요. 뢞로의 폭읎 음정한 곡선(Constant Width), 렌니의 죌찚 묞제(Rényi Parking), 칎늬의 비죌Ʞ 왕 타음(Kari-Culik Tiles), 윜띌윔슀킀 수엎(Kolakoski), 쿠띌몚토와 바톡토흐의 킀메띌 상태(Chimera State), 죌윥슀킀 날개(Joukowsky Airfoil)예요. 36판도 여섯을 더했얎요. 귞레읎슚의 정늬가 말하는 곡선 닚축 흐멄(Curve Shortening), 헀우드의 음곱 색 토러슀 지도(Heawood Torus), 슈바륎잠 랜턮(Schwarz Lantern), 윌람러햄곌 깁슀의 현상(Gibbs Phenomenon), 타원 곡선의 현곌 접선(Elliptic Chords), 지뚌의 파국 Ʞ계(Catastrophe Machine)예요. 37판도 여섯을 더했얎요. 포천의 쓞Ʞ 선 알고늬슘(Fortune's Sweep), 앙투안의 목걞읎(Antoine's Necklace), 칎륎만 소용돌읎 Ꞟ의 안정성(Kármán Street), 당당한 원판의 헥사틱 상(Hexatic Phase), 졎슚-멜 쪜맀와 아람띌믞 법칙(Johnson-Mehl), 쿠띌몚토-시바신슀킀 방정식(Kuramoto-Sivashinsky)읎에요. 38판도 여섯을 더했얎요. 앀더슚 국소화(Anderson Localization), 묎얎가 ì°Ÿê³  Chenciner 와 Montgomery 가 슝명한 섞 몞 묞제의 8자 핮(Figure-Eight Orbit), 예섌곌 빈튞너, 에륎되시의 베륎누읎 합성곱(Bernoulli Convolutions), 람띌우얎륎의 고정점 정늬륌 슝명하는 슈페륎너 볎조정늬(Sperner's Lemma), 에겐베륎거와 폎늬아의 항아늬(Pólya Urn), 뇌륎고륎의 묎한 수엎(Infinity Series)읎에요. 39판도 여섯을 더했얎요. 고든, 웹, 월퍌튞의 등슀펙튞럌 북(Isospectral Drums), 폰 망곚튞의 ꌎ로 쓎 늬만의 명시 공식(Explicit Formula), 요한 베륎누읎의 최닚 강하선(Brachistochrone), 슈타읎너의 포늬슘(Steiner Chain), 몚런 집닚윌로 귞늰 킹뚌의 합류(Kingman Coalescent), 볎륎수크-욞람 정늬(Borsuk-Ulam)예요. 40판도 여섯을 더했얎요. 앚더슀의 람띌욎 연속 랜덀 나묎(Continuum Random Tree), 슈뢰딩거의 수소 원자 고유 핚수(Hydrogen Orbital), 저도비치 귌사의 팬쌀읎크(Zeldovich Pancakes), 망덞람로가 낎닀볎고 례러, 슈람, 베륎너가 슝명한 람띌욎 욎동의 바깥 겜계(Brownian Frontier), 복소 ꞎ슈부륎크-란닀우 방정식의 결핚 난류(Defect Turbulence), 룚퍌튞 왕자의 정육멎첎(Prince Rupert's Cube)예요. 41판은 닀섯을 더했얎요. 홀곌 왜걎의 현수선 Ꞟ곌 정닀각형 바퀮(Square Wheels), 퐁슬레의 포늬슘(Poncelet Porism), 슐레플늬의 4찚원 정닀포첎륌 자륞 닚멎(Polytope Slices), 큎늌쌁곌 티뚌, 마귞누손의 사읎튞슀왑(Siteswap), Dudeney 의 잡화상 퍌슐곌 월늬슀-볎여읎-게륎비엔 정늬(Hinged Dissection)예요. 42판은 닀섯을 더했얎요. 바륞슀도륎프 규칙윌로 도는 Ʞ사 여행(Knight's Tour), 페륎믞와 파슀타, 욞람, 칭구의 되돌아옎(FPUT Recurrence), 포슬늬에-늜킚 반전Ʞ(Peaucellier Linkage), 몰늬의 삌등분선 정늬(Morley Triangle), 랄곌 마드띌슀, 소칌의 플벗 알고늬슘윌로 돌며 자Ʞ 회플 걞음(Pivot Walk)읎에요. 43판은 닀섯을 더했얎요. 월늬슀 워컀의 칌레읎도사읎큎(Kaleidocycle), 쌍곡형에서 록소드롬형윌로 감Ʞ는 뫌비우슀 변환의 흐멄(Möbius Loxodrome), 터튞의 용수철 정늬(Tutte Embedding), 디펀튞와 크래비잠의 고늬 정늬륌 품은 히토메자시 사시윔(Hitomezashi), 룜게 현상곌 첎비쇌프 점(Runge Phenomenon)읎에요. 44판은 닀섯을 더했얎요. 마크 칎잠의 고늬 몚형(Kac Ring), 슀톀곌 튜킀의 햄 샌드위치 정늬(Ham Sandwich), 람란튞의 불가늬아 솔늬테얎(Bulgarian Solitaire), 엘슀쌄슀와 프늬시의 탄도 소멞(Ballistic Annihilation), 페레귞늰의 솔늬톀(Peregrine Soliton)읎에요. 45판은 닀섯을 더했얎요. 로걎-셰프와 베륎시크-쌀로프의 극한 곡선윌로 닀가가는 영 도형의 플랑셰렐 성장(Plancherel Growth), 도람룚신 겜계 조걎을 걎 임계 읎징 몚형의 SLE(3) 겜계(Ising Interface), 프늬예제프와 동료듀의 였음러 걷개로 쌓는 로터 띌우터 응집곌 레빈-페레슀의 슀맀시 합(Rotor-Router), 폎늬아의 곌수원 묞제(Pólya's Orchard), 넬슚의 평멎 색칠 묞제와 아읎슈벚의 음곱 색(Hadwiger-Nelson)읎에요. 46판은 닀섯을 더했얎요. 람늎룚앙의 구역(Brillouin Zones), 몚슀 읎론곌 레람의 귞래프(Reeb Graph), 로렌잠 Ʞ첎와 시나읎의 당구(Lorentz Gas), 늬얞 Ꞁ래슀의 점묎늬(Glass Patterns), 벀치와 수테띌, 불플아니의 확률 공명(Stochastic Resonance)읎에요. 47판은 닀섯을 더했얎요. 였킀프와 홍, 슀튞로가잠의 슀워멀레읎터(Swarmalators), 빌슀의 왞로욎 달늬Ʞ 선수 추잡(Lonely Runner), 샀잠의 올로읎드와 ê·ž 전개도(Oloid), 쟚베의 원 채우Ʞ 정늬와 윜늰슀-슀티랐슚의 반복(Koebe Packing), 윘웚읎의 병사와 황ꞈ비 묎게(Conway's Soldiers)예요. 48판은 닀섯을 더했얎요. 였하띌의 맀듭 뫌비우슀 에너지(Möbius Energy), 첎비쇌프의 펞향곌 룚빈슀타읞-사륎낙의 밀도(Chebyshev's Bias), 섀든곌 레킀엔, 마슀든의 읎쀑 소용돌읎 능선(Double Gyre), 아자늬와 프로슀튞의 깜빡읎는 래칫(Flashing Ratchet), 망덞람로의 프랙탈 슀믞Ʞ(Fractal Percolation)예요. 목록 밖에서 닀음에 핮 볌 것읎에요. 믌윔프슀킀의 묌음표 핚수(1904)는 페늬 나묎륌 읎진 나묎로 펮는 계닚읎에요. 수론 갈래는 32판의 Gilbreath Table 까지 넷읎에요. Ꞟ람레슀 표는 소수 말고 닀륞 수엎로도 섞욞 수 있얎요. 였듀늬윔(1993)에 따륎멎 2 닀음에 간격읎 작고 고륎게 흩얎진 홀수가 였는 수엎에서는 1 의 엎읎 서고, 간격읎 크멎 깚젞요. 간격의 크Ʞ륌 녞람로 걞멎 1 의 엎읎 묎너지는 자늬가 볎여요. 핎뚞슬늬 곌정의 점 대신 격자 칞마닀 지수 분포 묎게륌 두멎 마지막 통곌 슀믞Ʞ(로슀튞, 1981)가 되고, 극한 몚양읎 쌍곡선에서 (√x+√y)² 의 겜계로 바뀌얎요. @@ -2603,6 +2668,8 @@ Flock, Harmonograph, Big Type 은 처음 화멎읎 빚강읎에요. 47판에서 볞 것읎에요. 정늬가 한 점에서만 찞읞 순간은 프레임 사읎로 빠젞나갔얎요. Lonely Runner 는 빠륎Ʞ가 1 부터 k 멎 구역읎 1/k 에 ꌭ 맞게 닿는 순간읎 한 점뿐읎띌, 프레임 사읎륌 24번 나눠 볎고 한 바퀎의 1/256 의 여유륌 죌고서알 셋읎 핚께 왞로워지는 것읎 볎였얎요. 셈읎 찟은 풀읎가 정늬볎닀 많은 것을 말핮 죌Ʞ도 했얎요. Conway's Soldiers 의 ë„€ 군대는 묎게가 ë‹€ ꌭ 1 읎었고, 몚든 뛰얎넘Ʞ가 곌녁 쪜윌로 두 칞 닀가가 묎게륌 하나도 잃지 않았얎요. 귞래서 묎게륌 넓읎로 귞늰 원판읎 귞대로 합쳐젞 고늬륌 채웠얎요. 식을 적는 좌표도 셈윌로 가렞얎요. Oloid 의 ꎘ선 식은 원 A 의 각을 얎디서 재느냐에 따띌 cos α + cos β + cos α cos β = 0 곌 cos α cos β + cos α − cos β = 0 윌로 갈렀서, 접선 둘곌 ꎘ선읎 한 평멎에 놓읎는지륌 행렬식윌로 닀시 셈핎 ë§žì·„ì–Žìš”. 끌늌곌 밀늌읎 맞서는 식은 한쪜읎 0 읎 되는 자늬에서 귞늌읎 깚졌얎요. Swarmalators 는 J = A 띌서 위상읎 정반대읞 점 사읎에 끌늌읎 사띌지고 밀늌만 낚아, 몇 점읎 묎늬 밖윌로 날아갔얎요. Koebe Packing 은 정늬가 하나뿐읎띌고 한 배치륌 60쎈 뒀에도 각의 합 였찚 0.00000 rad 로 지쌰얎요. +48판에서 볞 것읎에요. 정늬륌 셈윌로 지킀렀멎 셈읎 만든 치우칚부터 막아알 했얎요. Flashing Ratchet 의 첫 였음러 걞음은 퍌텐셜을 쌜 둔 채 짐 없읎 두얎도 위로 흐륎는 흐늄을 지얎냈고, 4096 칞 잡음 표의 치우칚도 귞랬얎요. 래칫은 작은 치우칚을 한쪜윌로 몚윌는 장치띌서 셈의 치우칚읎 귞대로 였늄윌로 볎였얎요. 메튞로폎늬슀 시험곌 값마닀 음수륌 짝지은 잡음 표로 막았얎요. Möbius Energy 는 에너지만윌로 흐륎게 하멎 뫌비우슀 불변성을 따띌 믞끄러젞 ꌎ을 잃얎서, 굜힘 항을 더핎알 맀듭읎 ì„°ì–Žìš”. Double Gyre 의 뒀로 장은 앞윌로 장을 180도 돌며 것읞데, 위아래로만 뒀집은 첫 판은 직접 적분곌 상ꎀ읎 0.2 였얎요. 정늬가 말하지 않은 수도 볎였얎요. Chebyshev's Bias 의 43 은 1 의 제곱귌읎 둘읞 법읞데 로귞 눈ꞈ의 42% 에서 잉여 쪜읎 앞섰얎요. Fractal Percolation 의 걎너Ʞ 확률은 읎 LED 판에서 p 0.89 와 0.90 사읎에서 0.67 쯀윌로 뛰었얎요. 겹겹읎 쪌갠 것은 쪌개는 순간에만 볎였얎요. p 0.88 에서 LED 의 13% 가 낱 LED 구멍읎띌, ë‹€ 깎은 판은 잡음처럌 읜혔얎요. 퍌지는 것곌 였륎는 것읎 비슷한 빠륎Ʞ멎 띠륌 묌듀여 볎음 수 없었얎요. Flashing Ratchet 은 였늄 0.13 톱니/쎈와 퍌짐 0.12 톱니²/쎈가 비슷핎서, 한 곚을 ꞈ빛윌로 표지핎 평균만 따띌갔얎요. + 목록 밖에서 31판 뒀에 핮 볌 것읎에요. 몚자 타음(슀믞슀, 마읎얎슀, 캐플런, 굿뚌-슀튞띌우슀, 2023)은 한 가지 타음로만 평멎을 비죌Ʞ로 덮고, 뒀집얎 놓읞 타음읎 판에 드묞드묞 흩얎젞요. 페륎믞-파슀타-욞람-칭구의 되돌아옎(1955)은 KdV Solitons 와 같은 뿌늬띌 같은 판에 두지 않아요. 목록 밖에서 33판 뒀에 핮 볌 것읎에요. 뷔퐁의 바늘(1777)은 Bertrand Paradox 와 같은 ꌎ로, ꞈ을 넘는 바늘의 몫읎 2/π 로 몚읎는 막대와 핚께 섞욞 수 있얎요. 토칎륎슀킀의 ë°©(1995)은 거욞로 된 닀각형 방에서 한 점의 빛읎 닀륞 한 점에만 닿지 못하는 방읎띌 Penrose Room 의 ꎑ선 추적을 귞대로 썚요. 프늬드뚌곌 슀윔띌(1987)는 둥귌 원 셋윌로는 볎로메였 고늬륌 만듀 수 없닀는 것을 볎였는데, 원을 밀얎 넣을 때 얎디서 부딪히는지가 귞늌읎 돌요. 정렬 넀튞워크의 시간 t 의 순엎을 점윌로 찍윌멎 대각선에서 원판을 지나 반대 대각선윌로 도는 점 구늄읎 돌요. ê·ž 원판읎 2007년 녌묞읎 낎닀볞 아륎킀메데슀 법칙읎에요. 간격읎 2k 읞 소수 쌍의 수는 Goldbach Comet 곌 같은 FFT 로 섞고, k 마닀 하디-늬틀우드 읞수로 갈늬는 쀄Ʞ 귞늌읎 돌요. @@ -2617,7 +2684,7 @@ Flock, Harmonograph, Big Type 은 처음 화멎읎 빚강읎에요. 목록 밖에서 38판 뒀에 핮 볌 것읎에요. 였람늬-앙드레 몚형(섞륎죌 였람늬, êžž 앙드레, 1980)은 Anderson Localization 의 묎작위 에너지륌 묎늬수 죌Ʞ의 윔사읞윌로 바Ꟍ 사슬읎띌, 섞Ʞ 2 에서 몚든 상태가 한꺌번에 퍌짐에서 갇힘윌로 넘얎가요. 같은 시공간 귞늌에 ê·ž 묞턱을 녞람로 걞 수 있얎요. 수바윔프와 드믞튞띌시녞비치(2013)가 찟은 섞 몞의 죌Ʞ 궀도 엎섞 갈래는 나비와 나방, 요구륎튞 같은 읎늄읎 붙은 ꌎ읎띌 Figure-Eight Orbit 의 적분을 귞대로 썚요. 게음(1979)읎 볎읞 헥슀 정늬는 칞읎 ë‹€ ì°¬ 헥슀 판에는 ꌭ 한쪜읎 읎Ʞ는 Ꞟ읎 있닀는 것윌로, 슈페륎너 볎조정늬처럌 람띌우얎륎의 고정점 정늬와 같은 힘읎에요. 마늄몚 판을 섞워 두 색읎 찚였륎며 읎ꞎ Ꞟ읎 쌜지는 귞늌읎 돌요. 쉬잠와 튞늌퍌의 윔끌늬 걞음(2004)은 지난 걞음 가욎데 하나륌 곚띌 따륎는 êž°ì–µ 걞음읎띌, 따륎는 확률 3/4 에서 퍌짐읎 바뀌얎요. Pólya Urn 처럌 64엎을 나란히 둘 수 있얎요. -목록 밖에서 39판 뒀에 핮 볌 것읎에요. 첎비쇌프의 펞향(파프누티 첎비쇌프, 1853)은 4로 나눈 나뚞지가 3 읞 소수가 1 읞 소수볎닀 거의 늘 앞서는 겜죌읎고, 룚빈슀타읞곌 사륎낙(1994)읎 앞서는 몫을 0.9959 로 셌얎요. Explicit Formula 의 영점 합에 L 핚수의 영점을 넣윌멎 같은 ꌎ로 ê·ž 겜죌륌 섞욞 수 있얎요. 평멎 람띌욎 겜로의 바깥 겜계는 찚원읎 4/3 읎띌는 만덞람로튞의 추잡(1982)을 례러, 슈람, 베륎너(2001)가 슝명했얎요. 지나간 자늬가 한 번만 쌜지는 겜로 위에 바깥 겜계만 따로 빛나게 할 수 있얎요. +목록 밖에서 39판 뒀에 핮 볌 것읎에요. 평멎 람띌욎 겜로의 바깥 겜계는 찚원읎 4/3 읎띌는 만덞람로튞의 추잡(1982)을 례러, 슈람, 베륎너(2001)가 슝명했얎요. 지나간 자늬가 한 번만 쌜지는 겜로 위에 바깥 겜계만 따로 빛나게 할 수 있얎요. 목록 밖에서 40판 뒀에 핮 볌 것읎에요. 람띌욎 ë§µ(장프랑수아 륎 갈, 귞레고늬 믞에륎몜, 2013)은 같은 엑슀컀션에 람띌욎 뱀을 얹얎 연속 랜덀 나묎륌 구멎윌로 접은 것읎띌 Continuum Random Tree 의 닀음 걞음읎고, 가지점마닀 가지가 끝없읎 나는 안정 레비 나묎(토마 뒀쌀느, 륎 갈, 2002)도 같은 틀로 귞렀요. 람띌욎 겜로의 자륎는 점은 찚원 3/4, 개척점은 7/4 로, 바깥 겜계의 4/3 곌 핚께 례러, 슈람, 베륎너(2001)가 같은 교찚 지수로 셈했얎요. Brownian Frontier 의 걞음에서 둘을 따로 칠하멎 섞 찚원을 한 팚널에서 견쀄 수 있얎요. 포묌 좌표로 푌 수소의 슈타륎크 상태(파욞 엡슀타읞, 1926)는 같은 n 의 궀도륌 섞얎 전Ʞ장 쪜윌로 Ʞ욎 엜을 만듀얎서, 위아래가 닀륞 구늄윌로 섞욎 팚널에 서요. 정사멎첎와 정팔멎첎도 제 몞을 지나간닀는 것은 크늬슀토프 슀크늬바(1968)가 볎였고, 제 몞을 지나가지 못하는 첫 볌록 닀멎첎는 2025년에 슈타읎닝거와 유륎쌀비치가 찟았얎요. 접착 몚형(구륎바토프, 사읎첎프, 샚닀늰, 1989)을 점성읎 낚은 버거슀 흐멄 귞대로 혾프-윜 변환(1950, 1951)윌로 풀멎 Zeldovich Pancakes 의 벜 두께륌 녞람에 걞 수 있얎요. @@ -2633,6 +2700,8 @@ Flock, Harmonograph, Big Type 은 처음 화멎읎 빚강읎에요. 목록 밖에서 47판 뒀에 핮 볌 것읎에요. 윜늰 로버잠가 1969년에 만든 슀페늬윘곌 쀑심 거늬가 반지늄의 √2 배읞 두 원 례러는 Oloid 와 같은 전개도 셈윌로 굎늎 수 있얎요. 두 원 례러는 묎게쀑심 높읎가 귞대로띌서, 0.707 곌 0.75 사읎륌 였가는 올로읎드의 흔듀늌곌 나란히 두멎 견쀄 수 있얎요. Koebe Packing 의 겜계륌 직사각형 대신 원판윌로 바꟞멎 최대 배치가 되고, ê·ž 위에 뫌비우슀 변환을 걞멎 원 묎더Ʞ 전첎가 한 점 쪜윌로 흘러가요. 33칞 펙 솔늬테얎는 가욎데 한 알만 낚Ʞ는 31수 풀읎가 있얎서, Conway's Soldiers 의 묎게 셈윌로 끝낌 수 없는 칞을 칠한 채 ê·ž 풀읎륌 볎여 쀄 수 있얎요. +목록 밖에서 48판 뒀에 핮 볌 것읎에요. 소수의 끝자늬에도 펞향읎 있얎요. 렘킀 올늬버와 사욎닀띌띌잔(2016)은 소수 닀음 소수가 같은 끝자늬로 끝나는 음읎 Ʞ대볎닀 드묌닀는 것을 볎였얎요. 10 윌로 나눈 나뚞지 1, 3, 7, 9 의 4×4 짝 표는 Chebyshev's Bias 의 첎륌 귞대로 썚서 셀 수 있얎요. 비큎늬 제튞(읎늬나 늬플나와 동료듀, 2007)는 사행하는 제튞 둘레에 소용돌읎가 쀄지얎 서는 흐늄읎띌, Double Gyre 의 능선 셈윌로 제튞 한가욎데의 넘지 못하는 벜을 귞늎 수 있얎요. 파읞뚌의 톱니와 멈춀쇠(1962)는 옚도가 같윌멎 돌지 않고 닀륎멎 도는 래칫읎띌, Flashing Ratchet 의 입자 셈에 옚도 둘을 걞얎 견쀄 만핎요. 망덞람로의 곱셈 캐슀쌀읎드(1974)는 낚ꞰꞰ 대신 묎게륌 곱핎 닀쀑 프랙탈 잡도륌 만듀얎서, Fractal Percolation 의 쪌개Ʞ에 밝Ʞ륌 싀얎 볌 수 있얎요. + ### 화장품 팝업 죌제 6 윌로 쓎 질감곌 닀음에 핮 볌 질감읎에요. 29판읎 처음 넷을 썌얎요. 팔레튾 나읎프로 펮 바륎는 크늌(Cream Swirl), 구슬읎 뜬 저곌 빛 귞묌(Serum Gel), 쏟아젞 쌓읎는 Ꞁ늬터(Glitter Fall), 퍌프가 눌렀닀 든 압축 파우더(Loose Powder)예요. 넷 ë‹€ K1 읎 색상환 한 바퀎띌서, 색을 돌늬멎 죌황곌 녞랑읎 갈색읎나 올늬람로 볎읎는 자늬가 있었얎요. Serum Gel 은 ê·ž 둘레에서만 밝Ʞ륌 0.62 와 0.3 에서 0.92 와 0.68 로 올렞얎요. 밝은 바탕은 반짝임을 지웠얎요. 200 위에 55 륌 더한 반짝임은 거의 안 볎여서 Glitter Fall 은 얎두욎 바탕에 섰고, 흰 바탕곌 ꞈ색 한 쀄은 Loose Powder 의 아래 띠로만 썌얎요. 크늌곌 저은 가장자늬 0.028 곌 0.079 로 연구에서 가장 번지는 귞늌읎에요. ê·ž 둘읎 가장자늬 축의 아래 끝을 옮겚서 지도에 새 빈 칞읎 났얎요. 크늌은 빛만 움직여서는 움직임읎 0.019 에 뚞묌렀얎요. 결읎 깊얎알 빛읎 돌 때 바뀌는 LED 가 생겚요. 비누막읎 터질 때 구멍 가장자늬가 테음러와 컬늭의 속도(1959, 1960)로 고륎게 벌얎지며 묌방욞 테륌 낚Ʞ는 것은 Soap Film 의 끝을 늘읞 귞늌읎에요. 큎렌징 였음읎 묌을 만나 가장자늬부터 우윳빛윌로 유화되는 것은 Blotting Paper 처럌 LED 마닀 한 번만 바뀌는 셈윌로 귞늎 만핎요. @@ -2679,8 +2748,6 @@ Flock, Harmonograph, Big Type 은 처음 화멎읎 빚강읎에요. 43판에서 볞 것읎에요. 검은 싀룚엣은 읎번에도 밝은 것을 등젞알 읜혔얎요. Grave Hand 는 지늄 50 LED 의 달 한가욎데에서 손읎 였륎낎늬고, Scarecrow Crows 는 반지늄 26 의 달을 팔 뒀에 두고 하늘을 ë§š 위까지 레벚 168 아래로 낎늬지 않았얎요. 달 앞의 싀룚엣은 테두늬 빛읎 달곌 밝Ʞ가 같윌멎 움직여도 셈에 듀지 않았얎요. Grave Hand 의 쎈록 테두늬륌 0.8 로 낮추자 움직임읎 0.140 에서 올랐얎요. 검은 것끌늬 겹치멎 늘며 것읎 묻혔얎요. Scarecrow Crows 의 늘며 까마귀는 검은 몞통 앞을 돌 때 K3 반응읎 0.01 읎띌, 자늬륌 못 찟은 까마귀륌 지평선 빛 앞 땅에 앉혀 0.05 로 올렞얎요. 맀끈한 빛묎늬는 읎번에도 복잡도륌 올렀서 닚계로 끊었얎요. Grave Hand 의 달묎늬는 0.02 닚계로 1.37 에서 1.04 로, Bone Jig 의 빛웅덩읎와 아지랑읎는 1/24 닚계로 1.07 에서 0.754 로 낎렞얎요. 죌황은 읎번에도 갈색읎 되Ʞ 쉬웠얎요. Leaf Devil 은 깔때Ʞ 뒀쪜 잎도 0.76 밝Ʞ 아래로 얎둡히지 않았고, Bone Jig 의 옅은 잔상은 볎띌륌 띠닀가 0.3 위에서알 빛의 색읎 돌요. 묎늬륌 높읎마닀 고륎게 두는 데도 셈읎 듀었얎요. Leaf Devil 의 첫 판은 잎읎 ë‹€ ꌭ대Ʞ로 던젞젞 깔때Ʞ가 1쎈 낚짓만 찌고, 잎마닀 제 높읎륌 정핎 거Ʞ서 돌게 하자 4.2쎈 낮낮 Ʞ둥읎 됐얎요. 사묌의 ꌎ읎 팚널에서 닀륞 것윌로 읜히Ʞ도 했얎요. Scarecrow Crows 의 옥수숫닚은 첎슀 룩윌로, 닀음엔 팔을 든 사람윌로 볎여서 혾박 셋윌로 바꿚얎요. -43판 뒀에 핮 볌 것읎에요. 박자에 맞춰 댈 더믞로 묎너졌닀가 한 댈씩 날아올띌 닀시 맞춰지는 핎곚은 Bone Jig 의 댈대륌 썚요. - 44판읎 닀섯을 더 ê·žë žì–Žìš”. 깚진 찜의 레읎슀 컀튌 뒀로 눌러 였는 얌굎(Lace Phantom), 돌 턱에서 늘얎젞 떚얎지는 점액(Slime Drip), 핎곚로 몚읎는 수정 구슬 속 ì—°êž°(Crystal Ball), 혌자 흔듀늬는 빈 ê·žë„€(Haunted Swing), 달 앞 닮 위의 검은 고양읎(Black Cat)예요. 42판 뒀에 적은 대로 큰 것 하나가 움직읎는 쪜을 겚눎얎요. 쌜진 몫은 Slime Drip 의 0.351 에서 Black Cat 의 0.631 읎에요. 움직임은 0.353 에서 0.449, 복잡도는 0.101 에서 0.461 읎에요. 44판에서 볞 것읎에요. 읎번에도 검은 싀룚엣은 밝은 것 앞에서만 읜혀서, Black Cat 은 낮은 달 앞에 고양읎륌 앉히고 하늘에 닿는 가장자늬에 달빛 테륌 둘렀얎요. 움직임은 큰 것읎 바꿚얎요. Black Cat 의 ꌬ늬 흔드는 폭을 1.4~1.6배로 킀워도 움직임은 0.014 만 올랐고, 등을 섞욎 몞을 따로 짓자 ê·ž 아치 하나가 0.108 을 맡았얎요. 묎작위로 정하는 음은 씚앗 하나로 재멎 숚었얎요. Slime Drip 은 싀에 맀달늎지륌 동전윌로 정할 때 씚앗 ì—Ž 개의 가욎데 3분의 1 읎 1.4~17.5 로 흩얎젞서, 맀달늰 싀의 수로 정하게 바꿚얎요. 따뜻한 색은 읎번에도 얎두욎 가장자늬에서 갈색읎 돌서, Slime Drip 곌 Crystal Ball 의 K1 은 빚강에서 녾랑 사읎륌 걎너뛰얎요. Lace Phantom 은 잰 값읎 가장 밝은 채널만 읜는 것을 썌얎요. 같은 39 계닚을 옅은 띌벀더와 짙은 볎띌로 칠핎 달빛곌 찜삎 귞늘을 나눎얎요. @@ -2695,7 +2762,7 @@ Flock, Harmonograph, Big Type 은 처음 화멎읎 빚강읎에요. 46판에서 볞 것읎에요. 빛 위에 겹친 빛은 움직임에 듀지 않았얎요. Howling Wolf 의 소늬 고늬는 가장 밝은 채널읎 읎믞 255 읞 달 위에서 더핮도 잰 값읎 바뀌지 않아서 쎈록윌로 섞얎 ê·žë žê³ , 귞래도 0.229 였얎요. 얎두욎 판의 부드러욎 빛도 Lantern Procession 의 움직임을 0.15 얞저늬에 묶었얎요. 안개는 덮얎 칠하멎 거품읎 됐얎요. Vampire Coffin 의 옅은 안개는 벜을 얎둡게 핎서 방욞처럌 볎였고, 빛윌로 더하자 안개로 읜혔얎요. 검은 것은 읎번에도 검은 것 앞에서 사띌젞서, Belfry 의 박쥐는 검은 탑 앞을 지날 때 볎띌로 비치게 칠했얎요. 채널을 4~6 닚위로 끊는 것읎 읎번에도 복잡도륌 낎렞얎요. Lantern Procession 은 0.84 에서 0.50, Vampire Coffin 은 0.85 에서 0.57 읎 됐고 둘 ë‹€ 팚널에서 띠가 볎읎지 않았얎요. 25 KB 가 귞늌을 깎Ʞ도 했얎요. Howling Wolf 는 박쥐륌, Ghost Ship 은 떠였륎는 혌령을 뺐얎요. -46판 뒀에 핮 볌 것읎에요. Lantern Procession 의 êžž 원귌에 손전등을 든 아읎듀을 걷게 하멎, 집집읎 혾박 등읎 쌜진 곚목을 따띌 사탕을 받윌러 가는 귞늌읎 돌요. 달빛을 받은 사람 귞늌자가 늑대로 바뀌는 변신은 Howling Wolf 의 거늬장 둘 사읎륌 섞윌멎 귞늎 만핎요. Belfry 의 종 진자륌 Ꞟ읎가 닀륞 종 셋에 걞멎 섞 종읎 서로 닀륞 죌Ʞ로 쳐서 엇박읎 맞묌렞닀 풀렀요. +46판 뒀에 핮 볌 것읎에요. 달빛을 받은 사람 귞늌자가 늑대로 바뀌는 변신은 Howling Wolf 의 거늬장 둘 사읎륌 섞윌멎 귞늎 만핎요. Belfry 의 종 진자륌 Ꞟ읎가 닀륞 종 셋에 걞멎 섞 종읎 서로 닀륞 죌Ʞ로 쳐서 엇박읎 맞묌렞닀 풀렀요. 47판읎 닀섯을 더 ê·žë žì–Žìš”. 독한 쎈록 액첎 속에 떠서 두늬번거늬닀 나륌 볎는 눈알(Specimen Jar), 128쀄을 감아 였륎며 작은 혾박 등을 쌜는 덩굎(Pumpkin Vine), 칌읎 얌굎을 도렀낎고 쎛불읎 쌜지는 혾박(Pumpkin Carving), 나방읎 버둥댈 때마닀 빛읎 싀을 타고 퍌지는 거믞쀄(Web Tremor), 포읞터가 Ꞁ자륌 ì§šì–Ž 말을 거는 판(Talking Board)읎에요. 넷은 43판곌 45판 뒀에 적혀 있던 감읎고, 병 속 눈알만 새로 찟았얎요. 쌜진 몫은 Specimen Jar 의 0.665 에서 Pumpkin Vine 의 0.974 읎고, 움직임은 Pumpkin Vine 의 0.058 에서 Web Tremor 의 0.527, 복잡도는 Pumpkin Carving 의 0.224 에서 Pumpkin Vine 의 0.747 읎에요. @@ -2703,6 +2770,12 @@ Flock, Harmonograph, Big Type 은 처음 화멎읎 빚강읎에요. 47판 뒀에 핮 볌 것읎에요. 선반 위에 병 셋을 두고 색읎 닀륞 액첎에 눈알을 하나씩 넣얎, 한 눈읎 응시하멎 나뚞지도 찚례로 돌아볎게 하멎 Specimen Jar 의 구 귞늬Ʞ륌 귞대로 썚요. Web Tremor 의 귞래프 파동을 덀불에 읎얎 걞늰 거믞쀄 여러 장에 걞멎, 빛읎 한 귞묌에서 닀륞 귞묌로 걎너가는 것을 볎여 쀄 수 있얎요. +48판읎 닀섯을 더 ê·žë žì–Žìš”. 볎늄달 앞 마륞 나묎에서 낎늬꜂히는 원숭읎올빌믞(Barn Owl), 불붙은 혞박을 던지는 뚞늬 없는 Ʞ수(Headless Rider), 묎너졌닀 닀시 맞춰지는 핎곚(Bone Pile), 손전등을 듀고 사탕을 받윌러 가는 아읎듀(Trick or Treat), 낫을 휘두륎며 달 앞을 떠가는 사신(Grim Reaper)읎에요. 움직임은 0.347 에서 0.759, 복잡도는 0.242 에서 0.575 예요. + +48판에서 볞 것읎에요. 밝은 몞은 밝은 것 앞에서 얎두욎 테가 있얎알 읜혔얎요. Barn Owl 의 흰 올빌믞는 크늌색 달 앞에서 1 LED 얎두욎 윀곜을 두륎자 읜혔얎요. 검은 것은 읎번에도 밝은 것을 등젞알 읜혀서, Grim Reaper 는 아래 하늘을 밝은 청록곌 쎈록윌로 바꿚고 Headless Rider 는 귞늌자 LED 에 달빛의 0.42 륌 테로 얹었얎요. Trick or Treat 의 마녀 몚자와 고양읎도 얎두욎 êžž 앞에서 사띌젞 달빛 윀곜을 둘렀얎요. 죌황은 크늌색 달 앞에서 대비가 없얎서, Headless Rider 의 혞박은 달 위 얎두욎 하늘에 걞었얎요. 큰 것읎 밝은 것 앞을 지나알 움직임읎 올랐얎요. Grim Reaper 는 첫 판읎 0.084 였고, 달곌 낫을 킀우고 뚞늬 위로 휘두륎게 핮 0.347 읎 됐얎요. 빠륞 것읎 움직임을 쀄읎Ʞ도 했얎요. Bone Pile 의 댈는 한 프레임에 제 폭볎닀 멀늬 가멎 빠륎Ʞ와 상ꎀ없읎 제 넓읎의 두 배쯀을 바꿔서, 떚얎지는 것을 빠륎게 하자 움직임읎 0.761 에서 0.615 로 낎렀갔얎요. 닀늬 넷 달늰 짐승의 걞음도 읜혔얎요. Headless Rider 의 말은 가로 습볎의 발 찚례대로 디디고 Ꞟ읎 흐륎는 빠륎Ʞ륌 디딘 발에 묶자 달늬는 말읎 됐얎요. 사묌의 ꌎ은 읎번에도 닀륞 것윌로 읜혔얎요. 마디로 읎은 고양읎 ꌬ늬는 사닀늬였고, 첫 올빌믞는 땅윩, 첫 곚반은 둥귌 덩읎 둘읎었얎요. + +48판 뒀에 핮 볌 것읎에요. Headless Rider 의 말을 개욞 위 닀늬로 달늬게 하멎, 닀늬륌 걎너멎 쫓아였지 못한닀는 읎알Ʞ대로 닀늬 끝에서 불붙은 혞박읎 날아와 터지는 장멎읎 돌요. 핎곚 둘읎 서로의 뚞늬륌 바꿔 끌우고 닀시 바꟞는 장멎은 Bone Pile 의 조각 17개륌 귞대로 썚요. + ## 닀시 돌늬Ʞ `web/` 에서 싀행핎요. `web/node_modules` 가 있얎알 핎요. diff --git a/incoming/pattern-study/patterns/barn-owl.js b/incoming/pattern-study/patterns/barn-owl.js index 6c8a3446e..732205023 100644 --- a/incoming/pattern-study/patterns/barn-owl.js +++ b/incoming/pattern-study/patterns/barn-owl.js @@ -1,7 +1,7 @@ // ===== Patternflow pattern ===== // Title: Barn Owl // Family: halloween -// Idea: A gnarled dead tree stands the full height of a purple night, roots in the grass and bare branches across a big full moon, where a pale barn owl swivels its heart-shaped face far round and back and blinks, then drops off and swoops down the whole panel with big wingbeats to grab a mouse in the grass, or miss it, and beats back up as dead leaves fall. +// Idea: A gnarled dead tree stands the full height of a purple night, roots in the grass and bare branches across a big full moon, where a pale barn owl swivels its heart-shaped face far round and back and blinks, then drops off and swoops down the whole panel with big wingbeats to grab a mouse in the grass or miss it, then beats back up as dead leaves fall. // Author: patternflow-lab // Date: 2026-10-25 // SPDX-License-Identifier: CC-BY-SA-4.0 diff --git a/incoming/pattern-study/patterns/double-gyre.js b/incoming/pattern-study/patterns/double-gyre.js index c4193ea6e..daf8a4505 100644 --- a/incoming/pattern-study/patterns/double-gyre.js +++ b/incoming/pattern-study/patterns/double-gyre.js @@ -1,7 +1,7 @@ // ===== Patternflow pattern ===== // Title: Double Gyre // Family: noise -// Idea: Two counter-rotating cells of fluid stand one above the other while the line between them sways up and down, and each LED lights by how hard the fluid round it is torn apart over the next twenty units of flow time, in amber, and over the last twenty, in azure, so the thin ridges that fluid never crosses wind round both cells, fold into tongues across the middle and stand the picture on its head every half turn of the forcing. +// Idea: Two counter-rotating cells of fluid stand one above the other while the line between them sways up and down, and each LED lights by how hard the fluid round it is torn apart over the next twenty units of flow time in amber and over the last twenty in azure, so the thin ridges that fluid never crosses wind round both cells, fold into tongues across the middle and stand the picture on its head every half turn of the forcing. // Author: patternflow-lab // Date: 2026-10-25 // SPDX-License-Identifier: CC-BY-SA-4.0 @@ -11,7 +11,7 @@ // @matrix 64x128 // @knobs Hue=0..1, Speed=0.2..3, Horizon=-1..1, Epsilon=0..1 // -// Knob 1: Hue. Turns both colours round the wheel together, half a wheel apart. At 0 the forward ridges, which push fluid apart, are amber and the backward ones, which draw it together, azure, and where they cross they add to white; at 0.25 green and magenta, at 0.5 azure and amber, at 0.75 magenta and green. +// Knob 1: Hue. Turns both colours round the wheel together, half a wheel apart. At 0 the forward ridges, which push fluid apart, are amber and the backward ones, which draw it together, azure. Where they cross they add to white. At 0.25 green and magenta, at 0.5 azure and amber, at 0.75 magenta and green. // Knob 2: Speed. The starting time of the fields moves half a unit of flow time a second for each unit: 1 at the opening 2, so the forcing goes round once in 10 s and the picture stands on its head every 5 s; 0.1 at 0.2, a period in 100 s, and 1.5 at 3, a period in 6.7 s. // Knob 3: Horizon. How long the fluid is followed, 20 units of flow time times 2 to the knob, to the nearest 2: 10 at -1, one period of the forcing, a few soft ridges round the cells and one across the middle; 14 at -0.5; 20 at the opening 0, two periods, the ridges folded into bundles of thin lines; 28 at 0.5 and 40 at 1, where the folds crowd closer than an LED and the chaotic sea round the cells turns to grain round two dark islands. // Knob 4: Epsilon. How far the line between the cells sways, epsilon = 0.5 ln(1 + 1000 k) / ln 1001: 0 at 0, steady cells, where at a horizon of 10 the only ridges are the dividing line and the walls, white with the forward and backward ones lying on each other, and longer horizons add rings of shear round each cell; 0.17 at 0.01; 0.30 at the opening 0.06, where the line swings 18 LEDs either way of the middle; 0.40 at a quarter, 0.45 at half and 0.50 at 1, 26 LEDs either way. From a29761b58894ea5e18b56ba0f9dc83607e1a8ca0 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 25 Oct 2026 20:38:06 +0000 Subject: [PATCH 8/8] =?UTF-8?q?=ED=8C=A8=ED=84=B4=20=EC=97=B0=EA=B5=AC=204?= =?UTF-8?q?8=ED=8C=90:=20index.html=20=EC=9D=84=20388=EA=B0=9C=EB=A1=9C=20?= =?UTF-8?q?=EB=8B=A4=EC=8B=9C=20=EC=A7=93=EB=8A=94=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_015fDPQzQykg7X6Bo8o35Zx2 --- incoming/pattern-study/index.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/incoming/pattern-study/index.html b/incoming/pattern-study/index.html index ad1c08449..5bcbc97a4 100644 --- a/incoming/pattern-study/index.html +++ b/incoming/pattern-study/index.html @@ -210,8 +210,8 @@

팹턮 연구

- - + +