From bbfe1a28d94706da96a7dc91d9b330a2a0041d53 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 25 Oct 2026 21:09:11 +0000 Subject: [PATCH 01/25] =?UTF-8?q?=EC=8B=9C=EC=9E=91=20=E2=80=94=20?= =?UTF-8?q?=ED=8C=A8=ED=84=B4=20=EC=97=B0=EA=B5=AC=2050=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_011p1aG8xetifag8g82xnh6r From 4b8bfcfee68ca007dacc3ef983d7f1ab07c1efff Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 25 Oct 2026 21:34:53 +0000 Subject: [PATCH 02/25] =?UTF-8?q?=ED=8C=A8=ED=84=B4:=20Bak-Sneppen=20?= =?UTF-8?q?=EB=A5=BC=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_011p1aG8xetifag8g82xnh6r --- .../pattern-study/patterns/bak-sneppen.js | 157 ++++++++++++++++++ 1 file changed, 157 insertions(+) create mode 100644 incoming/pattern-study/patterns/bak-sneppen.js diff --git a/incoming/pattern-study/patterns/bak-sneppen.js b/incoming/pattern-study/patterns/bak-sneppen.js new file mode 100644 index 000000000..b9302762a --- /dev/null +++ b/incoming/pattern-study/patterns/bak-sneppen.js @@ -0,0 +1,157 @@ +// ===== Patternflow pattern ===== +// Title: Bak-Sneppen +// Family: data +// Idea: Sixty-four species stand on a ring as bars of fitness, and every step the least fit one and its two neighbours are redrawn at random, so the floor of the bars climbs by itself to a threshold near two thirds while fresh colours break out in avalanches around the hopping minimum. +// Author: patternflow-lab +// Date: 2026-10-25 +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Lineage: Bak-Sneppen model of evolution (Per Bak and Kim Sneppen, 1993), with its random-neighbour version (Henrik Flyvbjerg, Kim Sneppen and Per Bak, 1993), pattern round 50 +// Round: 50 +// =============================== +// @matrix 64x128 +// @knobs Hue=0..1, Speed=0.2..3, Random=0..1, Memory=0..1 +// +// Knob 1: Hue. Turns the colours of age round the wheel together. At 0 a bar just redrawn is pale gold, then orange, rose and violet as it waits, and a bar left alone for long is deep blue; the gap line and the marked minimum are aqua. At 0.25 fresh bars are lime and old ones crimson; at 0.5 fresh is aqua and old is gold; at 0.75 fresh is pink and old is green. +// Knob 2: Speed. Steps of the model a second, 25 times the knob, so 50 at 2. Each fresh start first runs a climb: 6,000 steps in 1.8 s, slow at first and faster and faster, so the gap line rises evenly from the bottom to 0.645 on average; then the model walks at the knob's pace for 40 s and the bars are thrown again. At 0.2 everything is ten times slower, at 3 half as fast again. +// Knob 3: Random. The chance that each of the two neighbours redrawn with the minimum is a species picked anywhere on the ring instead of the one beside it. At 0 the classic ring: the floor settles at 0.62 for 64 species, just under the 0.667 tick of the infinite ring, and avalanches stay local. At 0.25 the floor drops to 0.42, at 0.5 to 0.37, and at 1, the random-neighbour model, to 0.35, just over the 1/3 tick, and fresh bars flicker all over the ring. +// Knob 4: Memory. How many steps a bar takes to turn from fresh to old, on a log scale of its age: 150 steps at 0, 184 at the opening 0.06, 820 at 0.5 and 4,500 at 1. Short memory shows only the avalanche of the last second or two as a bright patch on a blue block; long memory paints each bar by how long ago it was touched, so old avalanches stay as bands of rose and violet. +// Buttons: K1 tap throws all 64 bars again from a uniform start and the climb runs anew. + +const W = 64, H = 128, N = 64; +const { floor, round, min, max, abs, log, pow } = Math; +const cl = (v, a, b) => (v < a ? a : v > b ? b : v); + +// The climb: CMAX steps in TC seconds at Speed 2, the step count growing as +// CMAX^u, so the gap, which rises about 0.05 for every doubling of the steps, +// rises evenly. Then RATE * Speed steps a second for CYCLE seconds at Speed 2. +const CMAX = 6000, TC = 1.8, RATE = 25, CYCLE = 40; +// The threshold of the infinite ring (Paczuski, Maslov and Bak, 1996) and of +// the random-neighbour model (Flyvbjerg, Sneppen and Bak, 1993). +const FC = 0.66702, FMF = 1 / 3; + +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, rnd: 0, mem: 0.06, + f: new Float32Array(N), born: new Float64Array(N), step: 0, m: 0, G: 0, + ph: "climb", pt: 0, acc: 0, + // 64 colours of age, plus the gap line, its dim twin, the ticks and the + // marked minimum. + pal: new Float32Array(70 * 3), palKey: "", + }; + throwBars(params.s); +} + +// A uniform start: every species gets a fitness drawn at random and counts +// as old, so the bars the climb redraws stand out from the first step. +function throwBars(S) { + for (let i = 0; i < N; i++) (S.f[i] = Math.random()), (S.born[i] = -1e9); + S.step = 0; + S.G = 0; + S.ph = "climb"; + S.pt = 0; + S.acc = 0; + S.m = argmin(S.f); +} + +function argmin(f) { + let m = 0; + for (let i = 1; i < N; i++) if (f[i] < f[m]) m = i; + return m; +} + +// One step of the model: the least fit species and two others get new +// fitnesses, and the gap, the largest minimum seen since the start, may rise. +function step(S) { + const f = S.f, m = S.m; + if (f[m] > S.G) S.G = f[m]; + S.step++; + const a = Math.random() < S.rnd ? floor(Math.random() * N) : (m + N - 1) % N; + const b = Math.random() < S.rnd ? floor(Math.random() * N) : (m + 1) % N; + for (const i of [m, a, b]) (f[i] = Math.random()), (S.born[i] = S.step); + S.m = argmin(f); +} + +export function update(dt, input, params) { + if (!params.s) setup(params); + const S = params.s, k = input.knobValues || [0, 2, 0, 0.06]; + S.hue = cl(k[0], 0, 1); + S.speed = cl(k[1], 0.2, 3); + S.rnd = cl(k[2], 0, 1); + S.mem = cl(k[3], 0, 1); + const d = cl(dt || 0, 0, 0.1), v = S.speed / 2; + if (input.btnPressed && input.btnPressed[0]) throwBars(S); + S.pt += d * v; + if (S.ph === "climb") { + const u = min(1, S.pt / TC), target = round(pow(CMAX, u)) - 1; + while (S.step < target) step(S); + if (u >= 1) (S.ph = "walk"), (S.pt = 0); + } else { + S.acc += d * RATE * S.speed; + while (S.acc >= 1) step(S), (S.acc -= 1); + if (S.pt > CYCLE) throwBars(S); + } +} + +// Colours of age: index 0 is a bar redrawn this step, 63 a bar older than +// the memory. Then 64 the gap line, 65 the line's dim twin over the dark, +// 66 the threshold ticks, 67 the marked minimum, 68 the pointer above it. +function palette(S) { + const key = S.hue + ":" + S.mem; + if (key === S.palKey) return; + S.palKey = key; + const P = S.pal, h = S.hue + 0.14; + for (let i = 0; i < 64; i++) { + const u = i / 63; + // Warm white, gold, orange, rose and violet to deep blue; the value + // stays above 0.55, and old ages near orange and yellow stay brighter + // still, so no age turns brown or olive whatever the hue. + const hh = h - 0.48 * pow(u, 0.8), dw = abs(hh - floor(hh) - 0.11), warm = max(0, 1 - min(dw, 1 - dw) / 0.14); + hsv(hh, 0.4 + 0.48 * min(1, u * 3), min(1, (1 - 0.45 * u) * (1 + 0.55 * warm * u)), P, i * 3); + } + hsv(h + 0.38, 0.3, 1, P, 64 * 3); + hsv(h + 0.38, 0.45, 0.6, P, 65 * 3); + hsv(h, 0.05, 1, P, 66 * 3); + hsv(h + 0.38, 0.55, 1, P, 67 * 3); + hsv(h + 0.38, 0.6, 0.45, P, 68 * 3); +} + +const row = (f) => cl(round(H - f * H), 0, H - 1); + +export function draw(display, params) { + const S = params.s; + if (!S) return; + palette(S); + const P = S.pal, f = S.f, born = S.born, step = S.step; + // Ages on a log scale up to the memory, 150 to 4,500 steps. + const M = 150 * pow(30, S.mem), lm = log(1 + M); + const yG = S.G > 0 ? row(S.G) : -1, y1 = row(FC), y2 = row(FMF); + for (let x = 0; x < W; x++) { + const top = row(f[x]), mark = x === S.m; + const c = min(63, round((63 * log(1 + step - born[x])) / lm)) * 3; + // Every other bar a shade darker, so 64 bars read as 64 and not a block. + const sh = x & 1 ? 0.86 : 1; + for (let y = 0; y < H; y++) { + let o = -1, s = 1; + if (y >= top) { + o = mark ? 67 * 3 : c; + s = y === top ? 1.12 : sh; + } else if (mark) o = 68 * 3; + if (y === yG) o = y >= top ? 64 * 3 : 65 * 3; + if ((y === y1 || y === y2) && (x < 3 || x > W - 4)) o = 66 * 3; + if (o < 0) { + display.setPixel(x, y, 0, 0, 0); + continue; + } + // Channels in steps of 4 of 255: too fine to see, and a frame packs smaller. + display.setPixel(x, y, min(255, round((P[o] * s) / 4) * 4), min(255, round((P[o + 1] * s) / 4) * 4), min(255, round((P[o + 2] * s) / 4) * 4)); + } + } +} From 5cc8a9499a07d5099b4cac7f9aa5b9e580914b6e Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 25 Oct 2026 21:42:25 +0000 Subject: [PATCH 03/25] =?UTF-8?q?=ED=8C=A8=ED=84=B4:=20Old=20Well=20?= =?UTF-8?q?=EB=A5=BC=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_011p1aG8xetifag8g82xnh6r --- incoming/pattern-study/patterns/old-well.js | 466 ++++++++++++++++++++ 1 file changed, 466 insertions(+) create mode 100644 incoming/pattern-study/patterns/old-well.js diff --git a/incoming/pattern-study/patterns/old-well.js b/incoming/pattern-study/patterns/old-well.js new file mode 100644 index 000000000..578271d04 --- /dev/null +++ b/incoming/pattern-study/patterns/old-well.js @@ -0,0 +1,466 @@ +// ===== Patternflow pattern ===== +// Title: Old Well +// Family: halloween +// Idea: A cut-away old stone well under a moonlit sky lowers its wooden bucket down the whole height of the shaft, and when the bucket touches the black water the water glows sickly green, two pale eyes open beneath and a pale hand reaches up and yanks the bucket under, the rope whipping and the windlass spinning, until the water calms and the bucket is wound back up brimming and dripping, sometimes with the hand still on its rim. +// Author: patternflow-lab +// Date: 2026-10-25 +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Lineage: original, pattern round 50 +// Round: 50 +// =============================== +// @matrix 64x128 +// @knobs Hue=0..1, Speed=0.2..3, Moon=0..1, Fog=0..1 +// +// Knob 1: Hue. The colour of the glow in the water, of the light it throws up the shaft and of what comes up in the bucket: sickly green at 0, cyan at 0.25, violet at 0.5, orange at 0.75. The sky, the stones and the moonlight stay as they are. +// Knob 2: Speed. At 2 the bucket goes down at 18 LEDs a second and comes up at 20, and one round takes about 14 s: down 3.6 s, the water and the hand 4.2 s, a 0.6 s calm, up 4.8 s and 0.9 s hanging at the top. At 0.2 everything is ten times slower, at 3 half as fast again. The first round starts with the bucket already low, so it touches the water 0.7 s in. +// Knob 3: Moon. Where the moon stands. At 0 it is high on the left and its light falls into the shaft slanting right, thinning as it goes down until it reaches the water on the right; at 0.25 it falls almost straight; at 0.5 the moon peeks over the ridge of the roof and the light leans left; at 1 it hangs low on the right and lights only the upper half of the left wall. +// Knob 4: Fog. A mist over the water, added as light, moonlit and tinted by the glow. At 0 none; at 0.06 a thin layer over the water; at 0.5 it fills the bottom third of the shaft; at 1 it rises 70 rows, to just below the ground. +// Buttons: K1 tap lets go of the crank while the bucket is going down or hanging at the top: it falls to the water at once. + +const W = 64, H = 128, N = W * H; +const { floor, round, min, max, abs, sqrt, sin, cos, exp, 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; +} + +// The shaft's inside runs from column SX0 to SX1; its stone walls, seen cut +// through, are the eight columns either side, and earth lies beyond them. +const SX0 = 13, SX1 = 50; +// The parapet's top, the ground and the water. +const PY = 32, GY = 40, WS = 104; +// The little roof: its ridge and its eaves. +const AY = 5, EY = 15; +// The windlass wheel, seen end on, and the column the rope hangs down. +const CX = 26, CY = 22, RX = 31; +// The bucket's rim at the top and floating on the water. +const TOPB = 33, BOTB = 97; +// Seconds into the event at Speed 2: the glow is up, the hand has risen, it +// has a grip, the yank, the glow starts to fade, and it is all over. +const E_GLOW = 0.35, E_HAND0 = 0.5, E_HAND1 = 1.0, E_GRIP = 1.15, E_YANK = 1.3, E_FADE = 2.6, E_END = 4.2; +// Kinds of LED in the still picture. +const SKY = 0, ROOF = 1, GABLE = 2, WOOD = 3, BACK = 4, SECT = 5, SOIL = 6, GRASS = 7, WATER = 8; +const MOONC = [175, 185, 225], TINT = [0.95, 0.9, 1.12]; +// The glow's light, falling off up the shaft from the water. +const UP = new Float32Array(128); +for (let i = 0; i < 128; i++) UP[i] = exp(-i / 16); + +export function setup(params) { + params.s = { + t: 0, hue: 0, speed: 2, moon: -1, fog: 0.06, s: 0.3, + ph: "lower", e: 0, by: 84, v: 0, cyc: 0, cling: false, + th: 0, ex: 0, sv: 0, wa: 0, wp: 0, + hand: { on: false, x: 22, y: 140, c: 0, v: 0, fall: false }, + eo: 0, g: 0, cg: 0, + bits: [], bubbles: [], ripples: [], dripT: 0, bubT: 0, + kind: new Uint8Array(N), alb: new Float32Array(N), bg: new Float32Array(N * 3), buf: new Float32Array(N * 3), + gc: [0, 0, 0], off: new Float32Array(W), + }; + geometry(params.s); +} + +// The still shapes: roof, posts, walls, earth and water, and how much light +// each stone gives back. +function geometry(S) { + const { kind, alb } = S; + for (let y = 0; y < H; y++) + for (let x = 0; x < W; x++) { + const p = y * W + x, ry = AY + ((EY - AY) * abs(x + 0.5 - 32)) / 26; + let k = SKY, a = 0; + if (x >= 6 && x <= 57 && y >= ry && y < ry + 3) (k = ROOF), (a = y < ry + 1 ? 1 : (x + (floor(y - ry) & 1) * 2) % 4 === 0 ? 0.45 : 0.7); + else if (x >= 9 && x <= 54 && y >= ry + 3 && y <= EY) (k = GABLE), (a = x % 4 === 0 ? 0.4 : 1); + else if ((x >= 7 && x <= 56 && (y === EY + 1 || y === EY + 2)) || ((x === 9 || x === 10 || x === 53 || x === 54) && y > EY && y < PY - 1) || (x > 10 && x < 53 && (y === 21 || y === 22))) + (k = WOOD), (a = x === 9 || x === 53 || y === EY + 1 || y === 21 ? 1.3 : 1); + else if ((y === PY - 1 && ((x >= 4 && x <= 13) || (x >= 50 && x <= 59))) || (y >= PY && ((x >= 5 && x < SX0) || (x > SX1 && x <= 58)))) { + // Cut stone: the same courses as the wall behind, one joint per course. + k = SECT; + const c = floor((y - PY + 6) / 6), r = (y - PY + 6) % 6, side = x < 32 ? 0 : 1, j = (x - (side ? 51 : 5) + (c % 2) * 4) % 8; + a = y === PY - 1 ? 1.5 : r === 5 || j === 7 ? 0.3 : 0.78 + 0.22 * floor(hash(c * 31 + side * 7 + (j > 3 ? 1 : 0)) * 3) / 2; + } else if (y >= PY && x >= SX0 && x <= SX1) { + if (y >= WS) k = WATER; + else { + // The back wall, in courses six rows high with staggered joints. + k = BACK; + const c = floor((y - PY) / 6), r = (y - PY) % 6, w = 7 + (c % 3), xx = x - SX0 + (c % 2) * 4 + floor(hash(c * 7 + 1) * 3), si = floor(xx / w); + a = r === 5 || xx % w === w - 1 ? 0.3 : 0.5 + 0.14 * floor(hash(c * 131 + si * 17) * 4) + (r === 0 ? 0.1 : 0) - (hash(p) < 0.12 ? 0.12 : 0); + } + } else if (y > GY) (k = SOIL), (a = hash(p * 3 + 1) < 0.05 ? 1.6 : 1); + else if (y >= GY - 2 && (y === GY || hash(x * 5 + y) < 0.45 + (y - GY) * 0.2)) (k = GRASS), (a = 1); + kind[p] = k; + alb[p] = a; + } +} + +// The beam of moonlight in the shaft at (x, y): 0 in the shadow of the lip, +// 1 inside, with a soft edge. +function beam(s, x, y) { + const d = y - PY, L = SX0 + max(0, s) * d, R = SX1 + 1 + min(0, s) * d; + return cl(min(x + 0.5 - L, R - x - 0.5) / 1.5 + 0.5, 0, 1); +} + +// The still picture for one place of the moon: sky, moon, roof, the lit and +// shaded stones, the dust in the moonbeam and the dark water. +function build(S) { + const u = S.moon, bg = S.bg, kind = S.kind, alb = S.alb; + const mx = 9 + 46 * u, my = 8 + 14 * u * u - 6 * sin(PI * u), s = (S.s = 0.3 - 1.2 * u); + for (let y = 0; y < H; y++) { + const f = floor(min(1, y / 40) * 6) / 6, I = exp(-max(0, y - PY) / 36), amb = 5 + 21 * exp(-max(0, y - PY) / 50); + for (let x = 0; x < W; x++) { + const p = y * W + x, o = p * 3, k = kind[p]; + let r = 0, g = 0, b = 0; + if (k === SKY) { + r = 30 + 14 * f; + g = 20 + 16 * f; + b = 64 + 24 * f; + const dx = x + 0.5 - mx, dy = y + 0.5 - my, dd = sqrt(dx * dx + dy * dy); + if (dd < 5.3) { + const sea = (dx - 1.3) ** 2 + (dy + 1.2) ** 2 < 3.6 || (dx + 1.9) ** 2 + (dy - 1.8) ** 2 < 1.5; + (r = sea ? 196 : 238), (g = sea ? 194 : 234), (b = sea ? 190 : 214); + } else { + const hl = dd < 7.5 ? 0.3 : dd < 10.5 ? 0.16 : dd < 15 ? 0.07 : 0; + (r += MOONC[0] * hl), (g += MOONC[1] * hl), (b += MOONC[2] * hl); + if (hl === 0 && y < 30 && hash(p * 13 + 1) < 0.012) r = g = b = 90 + 80 * hash(p); + } + } else if (k === ROOF) (r = 70 * alb[p]), (g = 50 * alb[p]), (b = 66 * alb[p]); + else if (k === GABLE) (r = 40 * alb[p]), (g = 26 * alb[p]), (b = 30 * alb[p]); + else if (k === WOOD) (r = 104 * alb[p]), (g = 66 * alb[p]), (b = 42 * alb[p]); + else if (k === GRASS) (r = 34), (g = 66), (b = 50); + else if (k === SOIL) (r = alb[p] * 30), (g = alb[p] * 19), (b = alb[p] * 26); + else if (k === SECT) { + const l = 62 - 22 * max(0, y - PY) / 96; + (r = alb[p] * l * TINT[0]), (g = alb[p] * l * TINT[1]), (b = alb[p] * l * TINT[2]); + } else if (k === BACK) { + // A stone gives back the dim light of the night and the moonbeam; + // the dust in the beam glows whatever is behind it. + const m = I * beam(s, x, y); + r = alb[p] * (TINT[0] * amb + MOONC[0] * 0.5 * m) + MOONC[0] * 0.2 * m; + g = alb[p] * (TINT[1] * amb + MOONC[1] * 0.5 * m) + MOONC[1] * 0.2 * m; + b = alb[p] * (TINT[2] * amb + MOONC[2] * 0.5 * m) + MOONC[2] * 0.2 * m; + } else if (k === WATER) { + // Black water; a faint broken copy of the moonbeam just under the top. + const dy = y - WS, m = dy < 6 && (x + dy) % 3 !== 0 ? 0.3 * exp(-1.5) * beam(s, x, WS) * (1 - dy / 6) : 0; + (r = 3 + MOONC[0] * m), (g = 5 + MOONC[1] * m), (b = 10 + MOONC[2] * m); + } + bg[o] = r; + bg[o + 1] = g; + bg[o + 2] = b; + } + } +} + +function touch(S, hard) { + S.ph = "event"; + S.e = 0; + S.by = BOTB; + S.cyc++; + S.ripples.push({ x: RX, t: 0, a: hard ? 2 : 1.2 }); + for (let i = 0; i < (hard ? 14 : 6); i++) S.bits.push({ x: RX - 6 + Math.random() * 12, y: WS - 1, vx: (Math.random() - 0.5) * 18, vy: -14 - Math.random() * (hard ? 34 : 16), gl: 0 }); +} + +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.speed = cl(k[1], 0.2, 3); + S.fog = cl(k[3], 0, 1); + const m = cl(k[2], 0, 1); + if (abs(m - S.moon) > 0.003) (S.moon = m), build(S); + const ds = (d * S.speed) / 2, hd = S.hand; + S.t += ds; + S.e += ds; + if (input.btnPressed && input.btnPressed[0] && (S.ph === "lower" || S.ph === "top")) (S.ph = "drop"), (S.v = 18); + if (S.ph === "lower") { + S.by += 18 * ds; + if (S.by >= BOTB) touch(S, false); + } else if (S.ph === "drop") { + S.v += 160 * ds; + S.by += S.v * ds; + if (S.by >= BOTB) touch(S, true); + } else if (S.ph === "event") { + const e = S.e; + if (e < E_YANK) S.by = BOTB + 1.3 * sin(e * 8) * exp(-e * 2.5); + else { + const y = (e - E_YANK) / 0.3; + S.by = min(190, BOTB + (y < 1 ? 34 * y * y : 34 + 40 * (y - 1))); + if (e - ds < E_YANK) { + // The yank: a splash, a big ring, the rope whips and the wheel runs free. + S.ripples.push({ x: RX, t: 0, a: 2.4 }); + for (let i = 0; i < 18; i++) S.bits.push({ x: RX - 7 + Math.random() * 14, y: WS - 1, vx: (Math.random() - 0.5) * 30, vy: -22 - Math.random() * 40, gl: 0 }); + S.wa = 6; + S.sv = 16; + } + } + S.g = e < E_FADE ? sm(e / E_GLOW) * (0.92 + 0.08 * vn(S.t * 7, 3)) : 1 - sm((e - E_FADE) / (E_END - E_FADE)); + S.eo = e < 0.25 ? 0 : e < 2.8 ? sm((e - 0.25) / 0.2) * (abs(e - 1.9) < 0.07 ? 0.2 : 1) : 1 - sm((e - 2.8) / 0.25); + // The hand rises beside the bucket, takes the rim and goes under with it. + hd.on = e > E_HAND0 && S.by < 150; + hd.fall = false; + hd.x = 22; + hd.y = e < E_HAND1 ? 134 - (134 - BOTB - 6) * (1 - (1 - (e - E_HAND0) / (E_HAND1 - E_HAND0)) ** 2) : S.by + 6; + hd.c = sm((e - E_HAND1) / (E_GRIP - E_HAND1)); + if (e > 1.4 && e < 3.3 && (S.bubT += ds * 10) >= 1) { + S.bubT--; + S.bubbles.push({ x: RX + (Math.random() - 0.5) * 12, y: 128, v: 10 + Math.random() * 9, sd: floor(Math.random() * 999) }); + } + if (e > E_END) (S.ph = "calm"), (S.e = 0), (S.g = 0), (S.eo = 0); + } else if (S.ph === "calm") { + if (S.e > 0.6) { + S.ph = "raise"; + S.by = 132; + // Every other round the hand comes up still holding the rim. + S.cling = S.cyc % 2 === 1; + hd.on = S.cling; + hd.c = 1; + hd.fall = false; + } + } else if (S.ph === "raise") { + S.by -= 20 * ds; + if (hd.on && !hd.fall) { + hd.y = S.by + 6; + if (S.by < 62) (hd.fall = true), (hd.v = 0); + } + if (S.by <= TOPB) (S.by = TOPB), (S.ph = "top"), (S.e = 0); + } else if (S.ph === "top" && S.e > 0.9) (S.ph = "lower"), (S.e = 0); + // A hand that lets go drops back down the shaft, opening as it falls. + if (hd.fall) { + hd.v += 150 * ds; + hd.y += hd.v * ds; + hd.c = max(0, hd.c - ds * 3); + if (hd.y > WS + 12) { + hd.fall = hd.on = false; + S.ripples.push({ x: hd.x + 2, t: 0, a: 1.6 }); + for (let i = 0; i < 8; i++) S.bits.push({ x: hd.x + Math.random() * 5, y: WS - 1, vx: (Math.random() - 0.5) * 16, vy: -12 - Math.random() * 22, gl: 0 }); + } + } + if (S.ph === "raise" || S.ph === "top" || S.ph === "lower") hd.on = hd.on && S.ph === "raise"; + // What the bucket brings up glows and drips, less the higher it gets. + S.cg = S.ph === "raise" || S.ph === "top" ? (S.by < WS - 9 ? exp(-(WS - 9 - S.by) / 40) : 1) : 0; + if (S.cg > 0.15 && S.by < WS - 9 && (S.dripT += ds * 12 * S.cg) >= 1) { + S.dripT--; + S.bits.push({ x: RX - 5 + floor(Math.random() * 10), y: S.by + 10, vx: 0, vy: 0, gl: 1 }); + } + // The rope's length turns the wheel; after the yank it also runs free a while. + S.ex += S.sv * ds; + S.sv *= exp(-1.4 * ds); + S.th = -(S.by - 5 - CY) / 5 + S.ex; + S.wa *= exp(-2.4 * ds); + S.wp += 24 * ds; + // Flying water falls back; drips fall; bubbles rise and break the surface. + let n = 0; + for (const b of S.bits) { + b.vy += 170 * ds; + b.x += b.vx * ds; + b.y += b.vy * ds; + if (b.y > WS && b.vy > 0) { + if (b.gl) S.ripples.push({ x: b.x, t: 0, a: 0.5 }); + } else S.bits[n++] = b; + } + S.bits.length = n; + n = 0; + for (const b of S.bubbles) { + b.y -= b.v * ds; + if (b.y <= WS + 1) S.ripples.push({ x: b.x, t: 0, a: 0.6 }); + else S.bubbles[n++] = b; + } + S.bubbles.length = n; + n = 0; + for (const r of S.ripples) if ((r.t += ds) < 2.2) S.ripples[n++] = r; + S.ripples.length = n; + if (S.ripples.length > 24) S.ripples.splice(0, S.ripples.length - 24); +} + +// Add a colour at a pixel, by alpha. +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; +} + +// Something solid at (x, y): under the water it takes the glow's colour and +// darkens. +function solid(S, x, y, r, g, b) { + if (y > WS + S.off[cl(round(x), 0, W - 1)]) { + const gc = S.gc, k = 0.3 * S.g; + r = r * 0.55 + gc[0] * k; + g = g * 0.55 + gc[1] * k; + b = b * 0.55 + gc[2] * k; + } + put(S.buf, x, y, r, g, b, 1); +} + +// The hand, wrist at (x, y): a long forearm from below, a palm, three long +// fingers and a thumb. c is the grip: 0 spread upward, 1 hooked over to the +// right, onto the bucket's rim. +function hand(S, x, y, c) { + const P = [205, 222, 200]; + for (let j = 0; j < 40; j++) { + const yy = y + 1 + j, xx = x - 1 - j * 0.12; + for (let i = -1; i <= 1; i++) solid(S, xx + i, yy, P[0] * 0.86, P[1] * 0.86, P[2] * 0.86); + } + for (let j = 0; j < 3; j++) for (let i = -2; i <= 2; i++) solid(S, x + i, y - j, P[0], P[1], P[2]); + for (let f = 0; f < 3; f++) { + const bx = x - 2 + 2 * f, sp = (f - 1) * (1 - c); + for (let j = 0; j < 4 + (f === 1 ? 1 : 0); j++) solid(S, bx + round(sp * j * 0.3), y - 3 - j, P[0], P[1], P[2]); + const jx = bx + round(sp * 1.4), jy = y - 7 - (f === 1 ? 1 : 0); + // The last joint: up when open, over and down when it grips. + solid(S, jx + round(c), jy - round(1 - c), P[0], P[1], P[2]); + solid(S, jx + round(2 * c), jy - round(2 * (1 - c)) + round(c), P[0] * 0.9, P[1] * 0.9, P[2] * 0.9); + } + solid(S, x - 3, y - 2, P[0], P[1], P[2]); + solid(S, x - 4, y - 3, P[0], P[1], P[2]); +} + +export function draw(display, params) { + const S = params.s; + if (!S || S.moon < 0) return; + const { buf, alb, kind, off } = S, t = S.t, g = S.g, cg = S.cg, by = S.by, s = S.s; + const gc = hsv(0.3 + S.hue, 0.85, 1, S.gc); + buf.set(S.bg); + // The water's surface, rings running out from where things went in. + for (let x = 0; x < W; x++) { + let v = 0; + for (const r of S.ripples) { + const dx = abs(x + 0.5 - r.x), front = 18 * r.t; + if (dx < front) v += r.a * exp(-1.6 * r.t) * sin(0.9 * (dx - front)) * min(1, (front - dx) / 3); + } + off[x] = cl(round(v), -3, 3); + } + // The glow, and what the bucket brings up, light the stones and the air. + if (g > 0.01 || cg > 0.01) { + for (let y = PY; y < WS; y++) { + const fw = g * UP[WS - y]; + for (let x = SX0 - 1; x <= SX1 + 1; x++) { + let f = fw; + if (cg > 0.01) { + const dx = x - RX, dy = y - by - 4; + f += 0.8 * cg * exp(-sqrt(dx * dx + dy * dy) / 7); + } + if (f < 0.012) continue; + const p = y * W + x, o = p * 3, a = alb[p] * 0.72 + (kind[p] === BACK ? 0.3 : 0); + buf[o] += gc[0] * f * a; + buf[o + 1] += gc[1] * f * a; + buf[o + 2] += gc[2] * f * a; + } + } + } + // The water: black, or lit from below in slow swirls with a brighter + // heart round the eyes. + for (let y = WS; y < H; y++) + for (let x = SX0; x <= SX1; x++) { + const o = (y * W + x) * 3; + if (y < WS + off[x]) { + // A trough: the stones and air above show through. + const q = ((y - 1) * W + x) * 3; + (buf[o] = buf[q]), (buf[o + 1] = buf[q + 1]), (buf[o + 2] = buf[q + 2]); + continue; + } + if (g < 0.01) continue; + const n = vn(x * 0.21 + 1.6 * vn(y * 0.17 - t * 0.6, 3) + t * 0.5, 11 + (y >> 2)), ex = x - RX, ey = y - 117; + const f = g * (0.12 + 0.1 * floor(n * 3 + 1.5) / 2 + 0.6 * exp(-sqrt(ex * ex * 0.4 + ey * ey) / 6) + (y - WS < 2 ? 0.2 : 0)); + buf[o] += gc[0] * f; + buf[o + 1] += gc[1] * f; + buf[o + 2] += gc[2] * f; + } + // Mist over the water, added as light. + if (S.fog > 0) { + const top = WS - 4 - 66 * S.fog, a0 = 0.16 + 0.42 * S.fog, mk = 0.7 * g; + const mr = 150 + (gc[0] - 150) * mk, mg = 150 + (gc[1] - 150) * mk, mb = 186 + (gc[2] - 186) * mk; + for (let y = max(PY, floor(top)); y < WS + 3; y++) { + const ramp = sm((y - top) / 12); + for (let x = SX0; x <= SX1; x++) { + const nn = 0.55 + 0.45 * vn(x * 0.13 - t * 0.7 + y * 0.04, 17 + (y >> 3)) + 0.3 * vn(x * 0.31 + t * 1.1, 29 + (y >> 2)); + const a = floor(a0 * ramp * cl(nn, 0, 1) * 10) / 10; + if (a <= 0) continue; + const o = (y * W + x) * 3; + buf[o] += mr * a; + buf[o + 1] += mg * a; + buf[o + 2] += mb * a; + } + } + } + // Eyes under the water, pale, with dark slits. + if (S.eo > 0.02) + for (const ex of [RX - 6, RX + 4]) { + const ey = 116, wide = S.eo > 0.45; + for (let i = 0; i < 5; i++) { + const pup = i === 2, c = pup ? [20, 30, 20] : [228 + gc[0] * 0.1, 240, 222]; + put(buf, ex + i, ey, c[0], c[1], c[2], min(1, S.eo * 2)); + if (wide && i > 0 && i < 4) { + put(buf, ex + i, ey - 1, c[0], c[1], c[2], 1); + put(buf, ex + i, ey + 1, c[0], c[1], c[2], 1); + } + } + } + // The rope, whipping after the yank. + const end = min(H - 1, by - 5), ry = PY; + for (let y = CY; y <= end; y++) { + const x = RX + S.wa * sin(0.32 * y - S.wp) * ((y - CY) / 90); + const l = y < ry ? 0.95 : 0.5 + 0.5 * exp(-(y - ry) / 48) * (0.5 + 0.5 * beam(s, round(x), y)); + solid(S, x, y, 200 * l, 176 * l, 122 * l); + } + // The windlass wheel: rim, four spokes, the hub and the crank. + const th = S.th; + for (let a = 0; a < 2 * PI; a += 0.2) put(buf, CX + 5 * cos(a), CY + 5 * sin(a), 140, 92, 54, 1); + for (let q = 0; q < 4; q++) { + const a = th + (q * PI) / 2; + for (let r = 1; r < 5; r++) put(buf, CX + r * cos(a), CY + r * sin(a), 118, 76, 44, 1); + } + const ca = th + PI / 4; + for (let r = 1; r <= 7; r++) put(buf, CX + r * cos(ca), CY + r * sin(ca), 90, 90, 104, 1); + put(buf, CX + 7.6 * cos(ca), CY + 7.6 * sin(ca), 170, 120, 70, 1); + put(buf, CX, CY, 60, 60, 72, 1); + // The bucket: staves, two iron hoops, its bail, lit less the deeper it hangs. + if (by - 6 < H) { + const lit = by < PY ? 1 : 0.45 + 0.55 * exp(-(by - PY) / 45) * (0.55 + 0.45 * beam(s, RX, round(by))), gl = g * UP[cl(round(WS - by - 5), 0, 127)] * 0.22; + for (let r = 0; r < 10; r++) { + const xl = RX - 6 + round(r / 9), xr = RX + 5 - round(r / 9); + for (let x = xl; x <= xr; x++) { + const hoop = r === 2 || r === 7, edge = x === xl || x === xr, rim = r === 0; + let c = hoop ? [70, 70, 86] : rim ? [214, 150, 84] : (x & 1) ? [186, 116, 58] : [160, 98, 48]; + const e = edge && !hoop ? 0.7 : 1; + solid(S, x, by + r, (c[0] * lit + gc[0] * gl) * e, (c[1] * lit + gc[1] * gl) * e, (c[2] * lit + gc[2] * gl) * e); + } + } + for (let x = RX - 6; x <= RX + 5; x++) solid(S, x, by - 5 * (1 - ((x - RX + 0.5) / 6) ** 2), 120 * lit, 120 * lit, 136 * lit); + // Brimming with the glowing water it brought up. + if (cg > 0.05) for (let x = RX - 5; x <= RX + 4; x++) put(buf, x, by - 1, gc[0], gc[1], gc[2], min(1, cg * 1.4)); + } + // The hand, rising, gripping, clinging or falling. + const hd = S.hand; + if (hd.on) hand(S, hd.x, hd.y, hd.c); + // Bubbles, flying water and green drips. + for (const b of S.bubbles) { + const x = b.x + 1.2 * sin(b.y * 0.5 + b.sd); + put(buf, x, b.y, 220, 255, 230, 0.8); + if (b.sd % 3 === 0) put(buf, x + 1, b.y, 200, 240, 210, 0.5); + } + for (const b of S.bits) + if (b.gl) put(buf, b.x, b.y, gc[0], gc[1], gc[2], 0.9); + else put(buf, b.x, b.y, 170 + gc[0] * 0.3 * g, 190 + gc[1] * 0.25 * g, 200, 0.85); + // The surface line: moonlit where the beam reaches it, bright with the glow. + for (let x = SX0; x <= SX1; x++) { + const m = beam(s, x, WS) * 0.25, y = WS + off[x], crest = off[x] < 0 ? 1.4 : 1; + put(buf, x, y, (40 + MOONC[0] * m + gc[0] * g * 0.6) * crest, (44 + MOONC[1] * m + gc[1] * g * 0.6) * crest, (60 + MOONC[2] * m + gc[2] * g * 0.6) * crest, 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 b58644187d10c751b057191535776e4102d7b022 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 25 Oct 2026 21:43:11 +0000 Subject: [PATCH 04/25] =?UTF-8?q?=ED=8C=A8=ED=84=B4:=20Murmuration=20?= =?UTF-8?q?=EB=A5=BC=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_011p1aG8xetifag8g82xnh6r --- .../pattern-study/patterns/murmuration.js | 323 ++++++++++++++++++ 1 file changed, 323 insertions(+) create mode 100644 incoming/pattern-study/patterns/murmuration.js diff --git a/incoming/pattern-study/patterns/murmuration.js b/incoming/pattern-study/patterns/murmuration.js new file mode 100644 index 000000000..2174de352 --- /dev/null +++ b/incoming/pattern-study/patterns/murmuration.js @@ -0,0 +1,323 @@ +// ===== Patternflow pattern ===== +// Title: Murmuration +// Family: halloween +// Idea: Over a black treeline at sundown a murmuration of crows swirls and folds against a sky running from violet to hot orange, then every bird sweeps to its place in a giant skull whose eye sockets, nose and the gaps between its teeth let the sky through, the skull holds and looks about with glinting eyes and a chattering jaw, and bursts back into the flock. +// Author: patternflow-lab +// Date: 2026-10-25 +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Lineage: original, pattern round 50 +// Round: 50 +// =============================== +// @matrix 64x128 +// @knobs Hue=0..1, Speed=0.2..3, Flock=0..1, Shape=0..1 +// +// Knob 1: Hue. Turns the sunset round the wheel. At 0 violet overhead through hot pink and orange to a yellow glow round a low sun; at 0.25 brick red overhead through gold to lime; at 0.5 green to cyan; at 0.75 sky blue through violet to pink. The crows stay black, the sun cream and the glints in the eyes toxic green. +// Knob 2: Speed. At 2 the flock swirls 1.4 s before the first call, the birds take 1.9 s to sweep into the shape, the shape holds 1.5 s, the burst takes 1.6 s, and the flock swirls 5 s before the next call, a call every 10 s. At 0.2 everything is ten times slower, at 3 half as fast again. +// Knob 3: Flock. How many crows: 500 at 0, 750 at 0.25, 1,000 at 0.5 and 1,500 at 1. A small flock is a cloud of specks that darkens to a band when it turns edge on, and fills the skull with each bird's wings spread over five LEDs; a large one folds into black sheets and holds one LED and a bit more of the skull per bird, 1.7 at 1,500. +// Knob 4: Shape. What the flock forms. Up to 0.25 a skull 54 LEDs wide and 72 tall whose jaw drops twice and whose eyes glint and look left and right; to 0.5 a bat 62 LEDs across that beats its wings with glinting eyes; to 0.75 a crescent moon 54 LEDs tall that rocks; above 0.75 the three in turn. +// Buttons: K1 tap calls the flock into its shape at once. + +const W = 64, H = 128, N = W * H, MAXB = 1500; +const { floor, round, min, max, abs, sqrt, sin, cos, atan2, exp, 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; +}; +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; +} + +// Seconds at Speed 2: the first swirl, later swirls, the sweep into the +// shape, the hold and the burst. +const T0 = 1.4, TS = 5, TG = 1.9, TH = 1.5, TB = 1.6; + +// The shapes, tested at LED centres; u is across from the middle column. +const SKULL = 0, BAT = 1, MOON = 2; +const CENTRE = [[32, 57], [32, 50], [32, 55]]; +function skull(u, y) { + const au = abs(u); + let inside = (u / 27) ** 2 + ((y - 47) / 26) ** 2 < 1 && y < 66; + // Cheekbones, then the upper jaw, then the chin. + if (y >= 54 && y < 80 && au < 23.5 - max(0, y - 66) * 0.4) inside = true; + if (y >= 80 && y < 93 && au < 16 - max(0, y - 87) * 1.1) inside = true; + if (!inside) return false; + // Eye sockets, flattened under the brow and sloping to the nose. + const du = au - 11.5; + if ((du / 8.5) ** 2 + ((y - 57.5) / 7.5) ** 2 < 1 && y > 50 + 0.18 * (au - 3)) return false; + // The nose, an upturned heart. + if (y >= 66 && y < 75 && au < (y - 66) * 0.45 + 0.6 && !(y > 72 && au < 0.6)) return false; + // The mouth line and the gaps between the teeth. + if (y >= 82 && y < 83 && au < 14) return false; + if (y >= 78 && y < 87 && au < 12 && floor(u + 12) % 4 === 0) return false; + return true; +} +function bat(u, y) { + const s = abs(u); + if ((u / 5) ** 2 + ((y - 54) / 9.5) ** 2 < 1) return true; + if ((u / 5.2) ** 2 + ((y - 44) / 5) ** 2 < 1) return true; + if (y >= 35 && y < 42 && abs(s - 3) < (y - 35) * 0.35) return true; + if (s < 4 || s > 31) return false; + // The arm rises from the shoulder to the wing tip; the trailing edge hangs + // from four finger tips in scallops. + const top = s < 26 ? 48 - 13 * sin((((s - 4) / 22) * PI) / 2) : 35 + (s - 26) * 0.7; + const f = (s - 4) / 9, k = min(2, floor(f)), fr = f - k; + const tips = [66, 59, 51, 40]; + const bottom = tips[k] + (tips[k + 1] - tips[k]) * fr - 5.5 * sin(PI * fr); + return y > top && y < bottom; +} +function moon(u, y) { + return u * u + (y - 55) ** 2 < 27 * 27 && (u - 11) ** 2 + (y - 46) ** 2 > 23 * 23; +} +const SHAPES = [skull, bat, moon]; + +// Ordered-dither rank of an LED, so the first n cells of a shape taken in +// this order are spread evenly over it. +function bayer(x, y) { + let v = 0; + for (let b = 0; b < 3; b++) { + const xb = (x >> b) & 1, yb = (y >> b) & 1; + v |= (((xb ^ yb) << 1) | yb) << (2 * (2 - b)); + } + return v; +} + +export function setup(params) { + const S = (params.s = { + hue: -1, speed: 2, flock: 0, shapeK: 0.06, nb: 500, + t: 0, ph: "swirl", pt: 0, tswirl: T0, shape: SKULL, turn: 0, + // Per bird: place in the flock's sheet, depth, start delay, arc, burst + // direction and speed, flap phase. + a: new Float32Array(MAXB), b: new Float32Array(MAXB), w: new Float32Array(MAXB), + dl: new Float32Array(MAXB), arc: new Float32Array(MAXB), bd: new Float32Array(MAXB), bv: new Float32Array(MAXB), + // Slot: where it sits in the shape, and the LED it was cut from. + tx: new Float32Array(MAXB), ty: new Float32Array(MAXB), tc: new Int32Array(MAXB).fill(-1), + px: new Float32Array(MAXB), py: new Float32Array(MAXB), c: new Float32Array(MAXB), + order: new Int32Array(MAXB), key: new Float32Array(MAXB), + masks: SHAPES.map((fn) => { + const m = new Uint8Array(N); + for (let y = 0; y < H; y++) for (let x = 0; x < W; x++) m[y * W + x] = fn(x + 0.5 - 32, y + 0.5) ? 1 : 0; + return m; + }), + ranked: [], disc: [], dark: new Float32Array(N), bg: new Float32Array(N * 3), glint: 0, look: 0, + }); + for (let i = 0; i < MAXB; i++) { + const r = sqrt(Math.random()), th = Math.random() * 2 * PI; + S.a[i] = r * cos(th); + S.b[i] = r * sin(th); + S.w[i] = (Math.random() - 0.5) * 6; + S.dl[i] = Math.random(); + S.arc[i] = (Math.random() - 0.5) * 2; + S.bd[i] = (Math.random() - 0.5) * 1.6; + S.bv[i] = 30 + Math.random() * 45; + } + // Cells of each shape in dither order. + S.ranked = S.masks.map((m) => { + const cells = []; + for (let p = 0; p < N; p++) if (m[p]) cells.push(p); + const r = cells.map((p) => bayer(p % W & 7, floor(p / W) & 7) + hash(p) * 0.99); + return cells.map((p, i) => [r[i], p]).sort((x, y) => x[0] - y[0]).map((e) => e[1]); + }); + scenery(S); +} + +// Where bird i flies in the flock at time t: a sheet of birds, bent by a +// travelling fold, turned in depth and seen from the side, so it thins out +// when it faces us and darkens to a band when it turns edge on. +function sheet(S, i, t, out) { + const al = 0.21 * t, ca = cos(al), sa = sin(al); + let a = S.a[i] * ca - S.b[i] * sa; + const b = S.a[i] * sa + S.b[i] * ca; + a += 0.16 * sin(3.1 * b - 1.9 * t); + const X = a * (24 + 5 * sin(0.53 * t)), Y = b * (18 + 5 * sin(0.47 * t + 2)); + const Z = 11 * sin(2.3 * a + 1.4 * t + b) + S.w[i]; + const th = 1.15 * sin(0.37 * t) + 0.35 * sin(0.93 * t + 1), ph = 0.8 * sin(0.29 * t + 1); + const x3 = X * cos(th) + Z * sin(th), z3 = Z * cos(th) - X * sin(th); + out[0] = 33 + 9 * sin(0.5 * t + 0.6) + x3; + out[1] = 47 + 17 * sin(0.37 * t + 0.5) + Y * cos(ph) + z3 * sin(ph); +} + +// Hand each bird a slot: birds and slots both taken round in angle, so the +// flock pours into the shape from the side it is on. +function call(S) { + const n = S.nb, sh = S.shape, cells = S.ranked[sh], [cx, cy] = CENTRE[sh], o = [0, 0]; + const slots = []; + for (let k = 0; k < n; k++) { + const p = cells[k % cells.length], x = (p % W) + 0.5, y = floor(p / W) + 0.5; + slots.push([atan2(y - cy, x - cx), x, y, p]); + } + slots.sort((p, q) => p[0] - q[0]); + let mx = 0, my = 0; + for (let i = 0; i < n; i++) sheet(S, i, S.t, o), (S.px[i] = o[0]), (S.py[i] = o[1]), (mx += o[0]), (my += o[1]); + (mx /= n), (my /= n); + const ord = Array.from({ length: n }, (_, i) => i); + for (let i = 0; i < n; i++) S.key[i] = atan2(S.py[i] - my, S.px[i] - mx); + ord.sort((p, q) => S.key[p] - S.key[q]); + for (let k = 0; k < n; k++) { + const i = ord[k], s = slots[k]; + (S.tx[i] = s[1]), (S.ty[i] = s[2]), (S.tc[i] = s[3]); + } + for (let i = n; i < MAXB; i++) S.tc[i] = -1; + // Each bird's wings spread to cover the LEDs between slots. + const rho = 0.9 * sqrt(cells.length / n) + 0.5; + S.disc = []; + const R = Math.ceil(rho); + for (let dy = -R; dy <= R; dy++) for (let dx = -R; dx <= R; dx++) if (dx * dx + dy * dy <= rho * rho) S.disc.push(dx, dy); +} + +// How the shape moves while it holds: the skull's jaw drops twice, the bat +// beats its wings, the moon rocks. +function holdMove(S, x, y, out) { + const h = S.ph === "hold" ? S.pt : 0; + (out[0] = x), (out[1] = y); + if (S.shape === SKULL) { + if (y > 82) out[1] += 3.5 * abs(sin(PI * cl((h - 0.35) / 0.4, 0, 2))); + } else if (S.shape === BAT) { + const s = abs(x - 32); + out[1] += (s > 4 ? ((s - 4) / 27) ** 1.3 : 0) * 9 * sin(h * 2 * PI * 1.4); + } else { + const an = 0.16 * sin(h * 2 * PI * 0.8), dx = x - 32, dy = y - 55; + out[0] = 32 + dx * cos(an) - dy * sin(an); + out[1] = 55 + dx * sin(an) + dy * cos(an); + } +} + +export function update(dt, input, params) { + if (!params.s) setup(params); + const S = params.s, k = input.knobValues || [0, 2, 0, 0.06]; + const hue = cl(k[0], 0, 1); + if (hue !== S.hue) (S.hue = hue), scenery(S); + S.speed = cl(k[1], 0.2, 3); + S.flock = cl(k[2], 0, 1); + S.shapeK = cl(k[3], 0, 1); + S.nb = round(500 + 1000 * S.flock); + const d = (cl(dt || 0, 0, 0.1) * S.speed) / 2; + S.t += d; + S.pt += d; + const tap = input.btnPressed && input.btnPressed[0]; + if (S.ph === "swirl" && (S.pt > S.tswirl || tap)) { + const q = S.shapeK; + S.shape = q < 0.25 ? SKULL : q < 0.5 ? BAT : q < 0.75 ? MOON : S.turn++ % 3; + call(S); + (S.ph = "gather"), (S.pt = 0); + } else if (S.ph === "gather" && S.pt > TG) (S.ph = "hold"), (S.pt = 0); + else if (S.ph === "hold" && S.pt > TH) (S.ph = "burst"), (S.pt = 0); + else if (S.ph === "burst" && S.pt > TB) (S.ph = "swirl"), (S.pt = 0), (S.tswirl = TS); + // Eye glints and where they look, for the skull and the bat. + const h = S.ph === "hold" ? S.pt : -1; + S.glint = h < 0 ? 0 : sm(h / 0.25) * sm((TH - h) / 0.3); + S.look = h < 0 ? 0 : 2.5 * sin(h * 2 * PI * 0.75); + // Bird positions and how close each is to its slot. + const o = [0, 0], m = [0, 0], g = S.pt, ph = S.ph; + for (let i = 0; i < S.nb; i++) { + sheet(S, i, S.t, o); + let x = o[0], y = o[1], c = 0; + if (S.tc[i] >= 0 && ph !== "swirl") { + holdMove(S, S.tx[i], S.ty[i], m); + if (ph === "gather") { + const e = sm(cl((g - 0.6 * S.dl[i]) / (TG - 0.65), 0, 1)), dx = m[0] - x, dy = m[1] - y; + // A swoop off the straight line, widest halfway. + const sw = sin(PI * e) * S.arc[i] * 0.35; + x += dx * e - dy * sw; + y += dy * e + dx * sw; + c = sm((e - 0.88) / 0.12); + } else if (ph === "hold") (x = m[0]), (y = m[1]), (c = 1); + else { + // Out from the middle of the shape, slowing, then back into the sheet. + const [cx, cy] = CENTRE[S.shape], an = atan2(m[1] - cy, m[0] - cx) + S.bd[i]; + const r = (S.bv[i] * (1 - exp(-g * 3.2))) / 3.2, f = sm((g - 0.35) / (TB - 0.35)); + x = (m[0] + cos(an) * r) * (1 - f) + x * f; + y = (m[1] + sin(an) * r - 6 * g * g) * (1 - f) + y * f; + c = 1 - sm(g / 0.12); + } + } + (S.px[i] = x), (S.py[i] = y), (S.c[i] = c); + } +} + +// The sky, the sun on the horizon, the treeline and the field, redrawn when +// the hue turns. +const STOPS = [[0, 0.77, 0.66, 0.8], [34, 0.88, 0.62, 0.92], [62, 0.98, 0.7, 1], [86, 1.06, 0.8, 1], [102, 1.12, 0.55, 1]]; +function treeTop(x) { + return 101 + 2 * sin(x / 6.3 + 1) + 1.5 * sin(x / 2.7) - 5 * max(0, sin(x / 4.1 + 2)) ** 6 - 3 * max(0, sin(x / 2.2)) ** 8; +} +// A dead tree on each side: trunk and branches as segments [x0, y0, x1, y1, width]. +const TREES = [ + [5, 104, 7, 66, 2.2], [7, 66, 4, 56, 1.2], [6.5, 76, 14, 66, 1.2], [14, 66, 19, 64, 0.8], [5.5, 84, 0, 76, 1.2], [6, 70, 9, 58, 0.9], + [58, 104, 57, 80, 1.8], [57, 80, 61, 72, 1], [57, 88, 50, 80, 1], [50, 80, 47, 81, 0.7], [57.5, 84, 64, 82, 0.8], +]; +function scenery(S) { + const bg = S.bg, c = [0, 0, 0], hu = S.hue < 0 ? 0 : S.hue; + for (let y = 0; y < H; y++) { + let k = 0; + while (k < STOPS.length - 2 && y > STOPS[k + 1][0]) k++; + const A = STOPS[k], B = STOPS[k + 1], f = cl((y - A[0]) / (B[0] - A[0]), 0, 1); + for (let x = 0; x < W; x++) { + const p = y * W + x; + hsv(A[1] + (B[1] - A[1]) * f + hu, A[2] + (B[2] - A[2]) * f, A[3] + (B[3] - A[3]) * f, c, 0); + // The sun, low and half sunk, with two steps of glow round it. + const r = sqrt((x + 0.5 - 40) ** 2 + (y + 0.5 - 104) ** 2); + const sun = r < 12 ? 1 : r < 15 ? 0.55 : r < 19 ? 0.25 : 0; + if (sun) for (let j = 0; j < 3; j++) c[j] += (255 * [1, 0.95, 0.75][j] - c[j]) * sun; + let dark = y >= treeTop(x); + for (const [x0, y0, x1, y1, w] of TREES) { + const vx = x1 - x0, vy = y1 - y0, t = cl(((x + 0.5 - x0) * vx + (y + 0.5 - y0) * vy) / (vx * vx + vy * vy), 0, 1); + if ((x + 0.5 - x0 - vx * t) ** 2 + (y + 0.5 - y0 - vy * t) ** 2 < (w * (1 - 0.3 * t)) ** 2) dark = true; + } + if (dark) { + // The field below the trees, a dark plum with furrows. + const fy = y - 108, fu = fy > 0 ? (x - 32) / (fy + 6) : 0; + const v = fy > 0 ? (abs(fu * 3 - round(fu * 3)) < 0.12 ? 0.2 : 0.14) * min(1, fy / 10) : 0; + hsv(0.8 + hu, 0.6, v, c, 0); + } + for (let j = 0; j < 3; j++) bg[p * 3 + j] = c[j]; + } + } +} + +export function draw(display, params) { + const S = params.s; + if (!S) return; + const D = S.dark, bg = S.bg, disc = S.disc; + D.fill(0); + for (let i = 0; i < S.nb; i++) { + const x = S.px[i], y = S.py[i], c = S.c[i]; + const xi = floor(x), yi = floor(y); + if (c < 1 && xi >= 0 && xi < W && yi >= 0 && yi < H) { + // A banking wave runs through the flock: where it passes, the birds + // show their spread wings and the cloud darkens in a moving band. + const bank = sin(2.4 * S.a[i] + 1.3 * S.b[i] - 2.6 * S.t), wing = bank > 0.55 ? 0.75 * (1 - c) : 0; + D[yi * W + xi] += (0.62 + 0.3 * bank * bank) * (1 - c); + if (wing && xi + 1 < W) D[yi * W + xi + 1] += wing; + } + if (c > 0) { + // Wings spread over the slot's share of the shape, cut to the shape so + // the holes stay sharp. + const m = S.masks[S.shape], tc = S.tc[i], tx = tc % W, ty = floor(tc / W); + for (let j = 0; j < disc.length; j += 2) { + const dx = disc[j], dy = disc[j + 1], X = xi + dx, Y = yi + dy, sx = tx + dx, sy = ty + dy; + if (X < 0 || X >= W || Y < 0 || Y >= H || sx < 0 || sx >= W || sy < 0 || sy >= H || !m[sy * W + sx]) continue; + D[Y * W + X] += c; + } + } + } + // Glints: two toxic green points in the skull's sockets or the bat's eyes. + const gl = S.glint, eyes = gl > 0 && S.shape !== MOON ? (S.shape === SKULL ? [[20.5 + S.look, 58], [43.5 + S.look, 58]] : [[30 + S.look * 0.3, 44], [34 + S.look * 0.3, 44]]) : []; + for (let y = 0; y < H; y++) + for (let x = 0; x < W; x++) { + const p = y * W + x, k = 1 - min(1, D[p]); + let r = bg[p * 3] * k, g = bg[p * 3 + 1] * k, b = bg[p * 3 + 2] * k; + for (const [ex, ey] of eyes) { + const q = abs(x + 0.5 - ex) + abs(y + 0.5 - ey), a = gl * (q < 1.1 ? 1 : q < 2.1 ? 0.75 : q < 3.1 ? 0.3 : 0); + if (a) (r += (150 + 105 * (q < 1.1) - r) * a), (g += (255 - g) * a), (b += (60 + 170 * (q < 1.1) - b) * a); + } + // 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)); + } +} From 14f5866cff56eba4f8c4dbbcff44dce8a91a7145 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 25 Oct 2026 21:43:46 +0000 Subject: [PATCH 05/25] =?UTF-8?q?=ED=8C=A8=ED=84=B4:=20Skull=20Swap=20?= =?UTF-8?q?=EB=A5=BC=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_011p1aG8xetifag8g82xnh6r --- incoming/pattern-study/patterns/skull-swap.js | 465 ++++++++++++++++++ 1 file changed, 465 insertions(+) create mode 100644 incoming/pattern-study/patterns/skull-swap.js diff --git a/incoming/pattern-study/patterns/skull-swap.js b/incoming/pattern-study/patterns/skull-swap.js new file mode 100644 index 000000000..f57e858eb --- /dev/null +++ b/incoming/pattern-study/patterns/skull-swap.js @@ -0,0 +1,465 @@ +// ===== Patternflow pattern ===== +// Title: Skull Swap +// Family: halloween +// Idea: Two skeletons dance side by side in a moonlit graveyard, then each lifts off its own skull and they toss them across on crossing arcs past the moon, catch them on the wrong necks, wobble, turn their glowing sockets on each other, shrug, and toss them back. +// Author: patternflow-lab +// Date: 2026-10-25 +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Lineage: original, pattern round 50, built on Bone Pile, pattern round 48 +// Round: 50 +// =============================== +// @matrix 64x128 +// @knobs Hue=0..1, Speed=0.2..3, Toss=0..1, Dance=2..8 +// +// Knob 1: Hue. The night sky and the shade side of the bones, through the cold half of the wheel only: violet at 0, blue at 0.25, cyan at 0.5, sea green at 0.75, toxic green at 1. The moon stays cream, the pumpkin orange, one skull's eyes green and the other's orange. +// Knob 2: Speed. The tempo, 50 beats a minute for each unit. At the opening 100 the skulls leave the hands at 1.6 s, land on the wrong necks at 2.2 s, the two look at each other at 2.9 s, shrug at 3.7 s and throw back at 4.6 s. At 0.2 ten times slower; 150 at 3. +// Knob 3: Toss. How high the skulls fly and how often they spin. At 0 the taller arc tops out over the moon's lower half and each skull turns once; at 0.5 it clears the moon and they turn twice; at 1 they leave the top of the panel, turn three times and the throwers hop. +// Knob 4: Dance. Beats danced between swaps, 2 to 8. Each round they dance the next of three dances: a sway with both arms up, kicks to the side with hands on hips, a bounce at the knees pumping the forearms in turn. +// Buttons: K1 tap ends the dance and starts the throw at once. + +const W = 64, H = 128, N = W * H; +const { floor, ceil, round, min, max, abs, sqrt, sin, cos, atan2, acos, exp, 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 ease = (v) => (v <= 0 ? 0 : v >= 1 ? 1 : v * v * (3 - 2 * v)); +const hsv = (h, s, o) => [5, 3, 1].forEach((n, c) => { + const k = (n + h * 6) % 6; + o[c] = 255 * (1 - s * max(0, min(k, 4 - k, 1))); +}); + +// --- shapes in their own frames, y down, as distances (negative inside). +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; +}; +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)); +} +// Kinds: 0 long bone, 1 paired bones, 2 cage and neck, 3 lumbar, 4 pelvis, +// 5 skull, 6 jaw, 7 hand, 8 foot, 9 hat. The cage is narrower than Bone Pile's. +const RIBS = [[6.4, 2.4], [7.9, 6.6], [8.8, 10.8], [8.8, 15], [7.8, 19]]; +function shape(k, x, y, sg) { + switch (k) { + case 0: + return long(x, y, sg, 0.95 + 0.015 * sg, 1.5); + case 1: { + const e = sg / 2 - 1.1; + return min(seg(x, y, -1, -e, -1, e) - 0.8, seg(x, y, 1.1, -e, 1.1, e) - 0.6, 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: { + 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, -9.6, -0.2) - 0.8, seg(x, y, 1.4, 0.8, 9.6, -0.2) - 0.8, seg(x, y, 0, 1, 0, 14) - 1.2); + 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.7 : sqrt((abs(x) - rx) ** 2 + (y - f) ** 2) - 0.7); + } + 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.4, 0.3)); + return d; + } + case 4: { + 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: + 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.8, 1.4); + 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.5); + return d; + } + case 8: + return min(ell(x, y, sg * 0.8, 1.6, 3.1, 1.4), ell(x, y, 0, 0.4, 1.4, 1)); + default: { + // A witch's hat: brim, tapering cone, bent tip. + const t = cl((-17.4 - y) / 10, 0, 1); + return min(ell(x, y, 0.4, -17, 8.4, 1.4), seg(x, y, 0, -17.4, 2.6, -27.4) - mix(4.6, 0.7, t), seg(x, y, 2.6, -27.4, 6, -28.6) - 0.7); + } + } +} +// 0 none, 1 dark, 2 socket, 3 shade, 4 pupil, 5 hat band; the face slides +// sideways as the head turns, the pupils further. +function hole(k, x, y, Hd) { + if (k === 5) { + const p = Hd.pitch * 3, xs = x - Hd.yaw * 1.7; + if (((abs(xs) - 2.9) / 2.2) ** 2 + ((y + 7.9 - p) / 2.5) ** 2 < 1) return ((abs(xs) - 2.9 - Hd.yaw * Math.sign(xs) * 0.9) / 1.15) ** 2 + ((y + 7.7 - p) / 1.3) ** 2 < 1 ? 4 : 2; + const ny = y + 5.4 - p; + if (ny >= 0 && ny < 1.7 && abs(xs) < 0.3 + 0.5 * ny) return 1; + if (y > -2 + p * 0.5 && y < -0.5 && abs(xs * 0.8) < 3.4) return (xs + 4) / 1.45 - floor((xs + 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; + else if (k === 4 && ((abs(x) - 4.6) / 2.3) ** 2 + ((y + 4.4 + 0.3 * abs(x)) / 1.3) ** 2 < 1) return 3; + else if (k === 9 && y > -19.4 && y < -17.6 && abs(x) < 5.2) return 5; + return 0; +} + +// Body pieces back to front, [kind, length, bounds]; the hands are drawn +// after the heads, to hold one from in front. +const FEMUR = 22, TIBIA = 21, ARM = 15, FORE = 13, HIP = 6.5, FLOOR = 120, HIPY = FLOOR - 3 - FEMUR - TIBIA; +const two = (p) => [p, p]; +const PIECES = [[8, -1, -3.9, 3.9, -1, 3.2], [8, 1, -3.9, 3.9, -1, 3.2], ...two([1, TIBIA, -2.4, 2.4, -11, 11]), ...two([0, FEMUR, -2.6, 2.6, -11.4, 11.4]), [4, 0, -11, 11, -9, 5.2], [3, 0, -3.6, 3.6, -5.3, 5.3], [2, 0, -10.6, 10.6, -4.5, 22.6], ...two([0, ARM, -2.6, 2.6, -7.9, 7.9]), ...two([1, FORE, -2.4, 2.4, -7, 7]), ...two([7, 0, -2.7, 2.7, -0.3, 6.1])]; +const SKULL = [5, 0, -7.2, 7.2, -18.1, 0.6], JAW = [6, 0, -4.4, 4.4, -0.3, 3.5], HAT = [9, 0, -8.6, 7, -29.6, -15.4]; + +// --- the body, posed in Bone Pile's frame (floor 120, x = 0, full size) +// and placed on the panel at its own scale. +const at = (K, i, x, y, a) => { + K.x[i] = K.cx + K.sc * x; + K.y[i] = FLOOR + K.sc * (y - FLOOR); + K.a[i] = a; +}; +const ang = (dx, dy) => atan2(-dx, dy); +const bone = (K, i, ax, ay, bx, by) => at(K, i, (ax + bx) / 2, (ay + by) / 2, ang(bx - ax, by - ay)); +const rot = (a, x, y) => [x * cos(a) - y * sin(a), x * sin(a) + y * cos(a)]; +// Two-bone reach; of the two joints that work, the one nearer (px, py). +function limb(K, ax, ay, tx, ty, L1, L2, i1, i2, px, py) { + const dx = tx - ax, dy = ty - ay, d = cl(sqrt(dx * dx + dy * dy), 2, L1 + L2 - 0.01), b = atan2(dy, dx); + const al = acos(cl((L1 * L1 + d * d - L2 * L2) / (2 * L1 * d), -1, 1)); + let kx = ax + L1 * cos(b + al), ky = ay + L1 * sin(b + al); + const mx = ax + L1 * cos(b - al), my = ay + L1 * sin(b - al); + if ((mx - px) ** 2 + (my - py) ** 2 < (kx - px) ** 2 + (ky - py) ** 2) (kx = mx), (ky = my); + const n = sqrt((tx - kx) ** 2 + (ty - ky) ** 2) || 1, u = (tx - kx) / n, v = (ty - ky) / n; + bone(K, i1, ax, ay, kx, ky); + bone(K, i2, kx, ky, kx + L2 * u, ky + L2 * v); + return [kx + L2 * u, ky + L2 * v, u, v]; +} +// Legs, spine and cage; leaves the neck on the panel in K.nx, K.ny, K.na. +function torso(D, K) { + const rx = D.sway, ry = HIPY + D.bob, t = D.tilt, l = D.lean; + at(K, 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, ax = rx + hx, ay = ry + hy; + const [ex, ey] = limb(K, ax, ay, s * (HIP + f[1]), FLOOR - 3 - f[0], FEMUR, TIBIA, 4 + (s > 0), 2 + (s > 0), ax + s * 60, ay + 20); + at(K, s > 0 ? 1 : 0, ex, ey, 0); + } + const [b0, b1] = rot(t, 0, -5), sx = rx + b0, sy = ry + b1, [c0, c1] = rot(l, 0, -10); + bone(K, 7, sx + c0, sy + c1, sx, sy); + const [d0, d1] = rot(l, 0, -24), tx = sx + c0 + d0, ty = sy + c1 + d1 - D.shr * 0.4; + at(K, 8, tx, ty, l); + const [n0, n1] = rot(l, 0, -4.4); + Object.assign(K, { tx, ty, nx: K.cx + K.sc * (tx + n0), ny: FLOOR + K.sc * (ty + n1 - FLOOR), na: l + D.head }); +} +// Arms from the dance angles, each hand pulled toward its K.hold target. +function arms(D, K) { + const l = D.lean; + for (const s of [-1, 1]) { + const [o0, o1] = rot(l, s * 10, 0.6 - D.shr), ax = K.tx + o0, ay = K.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)); + let wx = ex + FORE * v0, wy = ey + FORE * v1; + const h = K.hold[s > 0 ? 1 : 0]; + if (h[0] > 0) (wx = mix(wx, (h[1] - K.cx) / K.sc, h[0])), (wy = mix(wy, FLOOR + (h[2] - FLOOR) / K.sc, h[0])); + const [hx, hy, u, v] = limb(K, ax, ay, wx, wy, ARM, FORE, 9 + (s > 0), 11 + (s > 0), ex, ey); + at(K, 13 + (s > 0), hx, hy, ang(u, v)); + } +} + +const REST = { sway: 0, bob: 0, tilt: 0, lean: 0, head: 0, pitch: 0, jaw: 0, shr: 0, al: 0.1, bl: 0.06, ar: 0.1, br: 0.06, fl: [0, 0], fr: [0, 0] }; +const clack = (b) => max(0, sin(TAU * b)) ** 2; +function dance(m, b, D) { + Object.assign(D, REST, { fl: [0, 0], fr: [0, 0], jaw: clack(b) }); + const w = sin(PI * b), s2 = sin((PI * b) / 2); + if (m === 0) { + // Sway: hips side to side over four beats, both arms up waving. + Object.assign(D, { sway: 3 * s2, tilt: 0.1 * s2, lean: -0.08 * s2, head: -0.18 * s2, al: 2.75 + 0.18 * w, ar: 2.75 - 0.18 * w, bl: 0.25 + 0.25 * w, br: 0.25 - 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"] = [8 * k, 4 * k]; + Object.assign(D, { sway: -s * 1.2 * k, head: s * 0.16 * k, al: 0.5, ar: 0.5, bl: -1.6, br: -1.6 }); + } else { + // Bounce: a dip at the knees on every beat, elbows at the sides and the + // forearms pumping up in turn. + const e = 0.5 - 0.5 * cos(PI * b); + Object.assign(D, { bob: 1.4 - 1.4 * cos(TAU * b), sway: 1.5 * w, head: 0.16 * w, al: 0.3, bl: 0.2 + 2.3 * e, ar: 0.3, br: 2.5 - 2.3 * e }); + } +} +// The right-hand skeleton dances the mirror image. +function mirror(D) { + [D.al, D.ar, D.bl, D.br, D.fl, D.fr] = [D.ar, D.al, D.br, D.bl, D.fr, D.fl]; + for (const key of ["sway", "tilt", "lean", "head"]) D[key] = -D[key]; +} + +// The story of one round in beats from its start: dance, lift, fly, land on +// the wrong neck, look, shrug, lift, fly back, land home and nod. +const LIFT = 0.6; +function times(S) { + const t0 = S.d, t1 = t0 + LIFT, t2 = t1 + S.T, t3 = t2 + 1.2, t4 = t3 + 1.3, t5 = t4 + 1.6; + return [t0, t1, t2, t3, t4, t5, t5 + LIFT, t5 + LIFT + S.T, t5 + LIFT + S.T + 1.6]; +} + +export function setup(params) { + const body = (cx, sc) => ({ cx, sc, x: new Float64Array(15), y: new Float64Array(15), a: new Float64Array(15), D: { ...REST } }); + params.s = { + hue: -1, beat: 0, t: 0, start: 0, moves: 0, d: 2, T: 1, + light: [0, 0, 0], bg: new Float32Array(N * 3), buf: new Float32Array(N * 3), + // The green-eyed skull belongs to the tall skeleton on the left, the + // orange-eyed one in the hat to the short one on the right. + K: [body(16, 0.8), body(48, 0.68)], + hd: [{ sc: 1, eye: [96, 255, 64], hat: 0 }, { sc: 0.86, eye: [255, 132, 8], hat: 1 }], + }; +} + +// Where a skull's hands go: either side of its cranium. +const sides = (Hd, s) => { + const [p, q] = rot(Hd.a, s * 8.6 * Hd.sc, -6 * Hd.sc); + return [Hd.x + p, Hd.y + q]; +}; +const onNeck = (Hd, K, lift) => { + const [p, q] = rot(K.na, 0, -7 * lift); + Object.assign(Hd, { x: K.nx + p, y: K.ny + q, a: K.na + K.wob, yaw: K.yaw, jaw: K.D.jaw, pitch: K.D.pitch, fly: 0 }); +}; + +export function update(dt, input, params) { + if (!params.s) setup(params); + const S = params.s, k = input.knobValues || [0, 2, 0, 2], d = cl(dt || 0, 0, 0.1); + const hue = cl(k[0], 0, 1); + if (hue !== S.hue) { + S.hue = hue; + hsv(0.78 - 0.47 * hue, 0.62, S.light); + background(S); + } + const toss = cl(k[2], 0, 1), every = round(cl(k[3], 2, 8)); + S.beat += (d * 50 * cl(k[1], 0.2, 3)) / 60; + S.t += d; + let u = S.beat - S.start; + if (u < S.d) S.d = every; + if (input.btnPressed && input.btnPressed[0] && u < S.d) (S.start = S.beat - S.d), (u = S.d); + let T = times(S); + if (u >= T[8]) { + S.start += T[8]; + u -= T[8]; + S.moves++; + S.d = every; + } + T = times(S); + // The arc is settled while no skull is in the air. + if (!((u >= T[1] && u < T[2]) || (u >= T[6] && u < T[7]))) { + S.h = 13 + 32 * toss; + S.T = 0.26 * sqrt(S.h); + S.turns = 1 + floor(toss * 2.99); + S.hop = toss; + T = times(S); + } + const [t0, t1, t2, t3, t4, t5, t6, t7] = T, m = S.moves % 3; + // Bodies: the dance, eased in and out, then the story. + S.K.forEach((K, i) => { + const D = K.D, dir = i ? -1 : 1; + dance(m, S.beat, D); + if (i) mirror(D); + const w = ease(u / 0.5) * (1 - ease((u - t0) / 0.4)); + for (const key in REST) if (typeof REST[key] === "number") D[key] = mix(REST[key], D[key], w); + for (const f of ["fl", "fr"]) D[f] = D[f].map((v) => v * w); + K.wob = K.lift = 0; + for (const [ts, back] of [[t0, 0], [t5, 1]]) { + const q = u - ts; + if (q < 0 || q > LIFT + S.T + 1.6) continue; + if (q < LIFT) { + // Crouch with the skull lifted off the neck. + K.lift = ease((q / LIFT - 0.35) / 0.65); + D.bob += K.lift * (4 + 4 * S.hop); + } else if (q < LIFT + S.T) { + // A hop on the throw, more for a high one. + const f = (q - LIFT) / S.T; + D.bob -= (2 + 7 * S.hop) * sin(PI * cl(f * 2.5, 0, 1)); + } else { + // Landed: the knees give, more under the big skull; the head wobbles. + const v = q - LIFT - S.T, big = (i === 1) !== !!back ? 1.5 : 1; + D.bob += 5 * big * exp(-4 * v) * sin(min(PI, v * 9)); + K.wob = (back ? 0.22 : 0.5) * big * exp(-2.4 * v) * sin(TAU * 2.2 * v) * dir; + } + } + // Look: the sockets turn on the other one; the jaw drops. + const lk = ease((u - t3) / 0.25) * (1 - ease((u - t4 + 0.1) / 0.3)); + K.yaw = dir * lk; + D.lean += 0.07 * lk * dir; + D.jaw = max(D.jaw, 0.9 * lk * ease((u - t3 - 0.45) / 0.15)); + // Shrug: shoulders up, elbows out, hands up and open, head on one side. + const sh = ease((u - t4) / 0.3) * (1 - ease((u - t5 + 0.45) / 0.35)); + if (sh > 0) Object.assign(D, { shr: 3.5 * sh, al: mix(D.al, 0.3, sh), ar: mix(D.ar, 0.3, sh), bl: mix(D.bl, 2.5, sh), br: mix(D.br, 2.5, sh), head: D.head + 0.32 * sh * dir, jaw: max(D.jaw, 0.5 * sh) }); + // Home again: a nod. + const n = (u - t7 - 0.8) / 0.6; + D.pitch = n > 0 && n < 1 ? sin(PI * n) : 0; + torso(D, K); + }); + // Skulls: on a neck, lifted, or flying neck to neck, the left one higher. + const A = S.K[0]; + S.hd.forEach((Hd, i) => { + const own = S.K[i], other = S.K[1 - i]; + let from = null, to = null, f = 0; + if (u < t1) onNeck(Hd, own, own.lift); + else if (u < t2) (from = own), (to = other), (f = (u - t1) / S.T); + else if (u < t6) onNeck(Hd, other, other.lift); + else if (u < t7) (from = other), (to = own), (f = (u - t6) / S.T); + else onNeck(Hd, own, 0); + if (from) { + // The arc is the cranium's middle, and the skull spins about it. + const pv = -9.5 * Hd.sc, h = S.h + (from === A ? 12 : 0), a = mix(from.na, to.na, f) + (from === A ? 1 : -1) * S.turns * TAU * f; + const [p, q] = rot(from.na, 0, pv - 7), [r, s] = rot(to.na, 0, pv), [e, g] = rot(a, 0, pv); + Object.assign(Hd, { x: mix(from.nx + p, to.nx + r, f) - e, y: mix(from.ny + q, to.ny + s, f) - 4 * h * f * (1 - f) - g, a, yaw: 0, jaw: 0.8, pitch: 0, fly: 1 }); + } + }); + // Hands: onto the skull, after it on the throw, up to catch, then down. + S.K.forEach((K, i) => { + K.hold = [[0], [0]]; + const dir = i ? -1 : 1; + for (const [ts, back] of [[t0, 0], [t5, 1]]) { + const q = u - ts, f = (q - LIFT) / S.T, mine = S.hd[back ? 1 - i : i], theirs = S.hd[back ? i : 1 - i]; + if (q < 0 || f > 1 + 0.8 / S.T) continue; + for (const s of [0, 1]) { + const sd = 2 * s - 1; + if (f < 0) K.hold[s] = [ease(q / LIFT / 0.45), ...sides(mine, sd)]; + else if (f < 1) { + const c = ease((f - 0.3) / 0.45), [x, y] = sides({ x: K.nx, y: K.ny, a: K.na, sc: theirs.sc }, sd); + K.hold[s] = [1, mix(K.nx + dir * 9 + sd * 3, x, c), mix(K.ny - 18, y, c)]; + } else K.hold[s] = [1 - ease((q - LIFT - S.T - 0.2) / 0.5), ...sides(theirs, sd)]; + } + } + arms(K.D, K); + }); +} + +// --- the graveyard, laid out per sky colour: a stepped sky, the moon and two +// halo rings, black stones rimmed by the moon, the ground. +const MX = 32, MY = 21, MR = 13, GROUND = 117; +const CRATERS = [[-4, -4, 3.2], [4, 1, 2.6], [-2, 6, 2.2], [6, -6, 1.6], [-7, 2, 1.4]]; +const STONES = [[5, 101, 5.5], [24, 108, 3.6], [41, 107, 3.8], [59, 99, 5.5]]; +function grave(x, y) { + let d = abs(x - 32) < 1.6 && y > 86 && y < GROUND ? 0 : abs(x - 32) < 6 && y > 92 && y < 95.5 ? 0 : 1; + for (const [sx, top, hw] of STONES) if (abs(x - sx) < hw && y < GROUND && (y > top + hw || (x - sx) ** 2 + (y - top - hw) ** 2 < hw * hw)) d = 0; + return d === 0; +} +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, q = sqrt((x + 0.5 - MX) ** 2 + (y + 0.5 - MY) ** 2); + let r, g, b; + if (y < GROUND) { + const v = floor((0.14 + 0.52 * (y / GROUND) ** 1.6) * 20) / 20; + (r = L[0] * v), (g = L[1] * v), (b = L[2] * v); + if (q < MR) { + const sp = CRATERS.some(([a, b, c]) => (x + 0.5 - MX - a) ** 2 + (y + 0.5 - MY - b) ** 2 < c * c) ? 26 : 0; + (r = 255 - sp), (g = 240 - sp), (b = 204 - sp); + } else if (q < MR + 4) (r += 60), (g += 56), (b += 50); + else if (q < MR + 9) (r += 26), (g += 24), (b += 22); + if (grave(x, y)) { + // Black stone, rimmed on top where the moon catches it. + const rim = !grave(x, y - 1); + (r = rim ? 120 : 14), (g = rim ? 110 : 8), (b = rim ? 132 : 22); + } + } else { + const e = y === GROUND ? 1 : 0, sp = (x * 7 + y * y * 3) % 11 ? 0 : 10; + (r = 40 + 30 * e + sp), (g = 26 + 22 * e + sp), (b = 48 + 30 * e + sp); + } + bg[o] = r; + bg[o + 1] = g; + bg[o + 2] = b; + } +} + +// Ivory, grey, and shade tinted by the sky; the hat violet. +const PG = [255, 120, 20], BONE = [[255, 244, 220], [212, 202, 180], [96, 88, 84]]; +const HATC = [[196, 120, 255], [140, 64, 210], [70, 26, 112]]; +const FIELD = new Float32Array(48 * 48); +function piece(S, pc, cx, cy, a, sc, Hd) { + const [k, sg, x0, x1, y0, y1] = pc, 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) * sc, wy = cy + (u * s + v * c) * sc; + (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); + if (xb < xa || yb < ya || xb - xa >= 48 || yb - ya >= 48) return; + const buf = S.buf, L = S.light, z = 1 / sc; + 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) * z, (-dx * s + dy * c) * z, sg) * sc; + } + 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; + } + const dx = x + 0.5 - cx, dy = y + 0.5 - cy, hl = hole(k, (dx * c + dy * s) * z, (-dx * s + dy * c) * z, Hd); + if (hl && hl !== 3) { + // Dark holes, the glowing sockets and pupils, the hat's band. + const e = hl === 1 ? [2, 0, 4] : hl === 5 ? [245, 134, 2] : Hd.eye, g = hl === 2 ? 0.34 : 1; + put(buf, o, e[0] * g + 10, e[1] * g + 6, e[2] * g + 14); + continue; + } + // The slope of the field: where it climbs upward the piece faces the moon. + 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, C = k === 9 ? HATC : BONE, w = t === 2 && k !== 9 ? 0.4 : 0; + for (let ch = 0; ch < 3; ch++) buf[o + ch] = C[t][ch] + L[ch] * w; + } +} +const put = (buf, o, r, g, b) => { + buf[o] = r; + buf[o + 1] = g; + buf[o + 2] = b; +}; +function head(S, Hd) { + piece(S, SKULL, Hd.x, Hd.y, Hd.a, Hd.sc, Hd); + const [j0, j1] = rot(Hd.a, 0, (-0.7 + 2.4 * Hd.jaw + Hd.pitch * 0.6) * Hd.sc); + piece(S, JAW, Hd.x + j0, Hd.y + j1 + Hd.pitch * 2.5 * Hd.sc, Hd.a, Hd.sc, Hd); + if (Hd.hat) piece(S, HAT, Hd.x, Hd.y + Hd.pitch * 2 * Hd.sc, Hd.a, Hd.sc, Hd); +} + +export function draw(display, params) { + const S = params.s; + if (!S || S.hue < 0) return; + const buf = S.buf; + buf.set(S.bg); + // The jack-o'-lantern: glow rings on the ground, a face in three steps. + const v = 0.6 + 0.2 * sin(S.t * 9) + 0.2 * sin(S.t * 23.7), fl = v > 0.82 ? 1 : v > 0.66 ? 0.84 : 0.7; + for (let y = 104; y < H; y++) + for (let x = 14; x < 50; x++) { + const q = sqrt(((x + 0.5 - 32) / 1.6) ** 2 + (y + 0.5 - 121) ** 2), gl = (q < 9 ? 0.3 : q < 13 ? 0.18 : q < 17 ? 0.08 : 0) * fl; + for (let ch = 0, o = (y * W + x) * 3; ch < 3; ch++) buf[o + ch] += PG[ch] * gl; + } + // Shadows of the two skeletons cast forward onto the ground. + for (const K of S.K) + for (let y = GROUND + 1; y < GROUND + 6; y++) + for (let x = 0; x < W; x++) if (abs(x + 0.5 - K.x[6]) < (12 - (y - GROUND) * 1.2) * K.sc) for (let ch = 0; ch < 3; ch++) buf[(y * W + x) * 3 + ch] *= 0.5; + for (let y = 114; y < 127; y++) + for (let x = 24; x < 41; x++) { + const dx = x + 0.5 - 32, dy = y + 0.5 - 121.2, o = (y * W + x) * 3; + const e = (dx / 7.4) ** 2 + (dy / 5) ** 2; + if (e < 1) { + const rib = abs(abs(dx) - 3.4) < 0.6 && e > 0.25, eye = dy > -3 && dy < -0.6 && abs(abs(dx) - 3) < 1.6 + (dy + 3) * 0.5, grin = dy > 0.8 && dy < 2.8 - abs(dx) * 0.15 && abs(dx) < 5 && !(abs(dx) % 2.5 < 0.7 && dy < 1.6); + if (eye || grin) put(buf, o, 255, 196 * fl + 30, 50 * fl); + else put(buf, o, rib ? 220 : 255, rib ? 84 : 118, rib ? 0 : 10); + } else if (e < 1.35 && dy > -6) put(buf, o, buf[o] * 0.3, buf[o + 1] * 0.3, buf[o + 2] * 0.3); + if (abs(dx + 0.3) < 1 && dy > -7.4 && dy < -4.6) put(buf, o, 72, 150, 40); + } + // Bodies, skulls on necks, hands, skulls in the air. + for (const K of S.K) for (let i = 0; i < 13; i++) piece(S, PIECES[i], K.x[i], K.y[i], K.a[i], K.sc, null); + for (const Hd of S.hd) if (!Hd.fly) head(S, Hd); + for (const K of S.K) for (let i = 13; i < 15; i++) piece(S, PIECES[i], K.x[i], K.y[i], K.a[i], K.sc, null); + for (const Hd of S.hd) if (Hd.fly) head(S, Hd); + 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); +} From 53c062b9b31956723042756e7997aac9507d85ce Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 25 Oct 2026 21:44:40 +0000 Subject: [PATCH 06/25] =?UTF-8?q?=ED=8C=A8=ED=84=B4:=20Skull=20Swap=20?= =?UTF-8?q?=EC=9D=98=20=EB=8D=98=EC=A7=80=EB=8A=94=20=EB=86=92=EC=9D=B4?= =?UTF-8?q?=EB=A5=BC=20=ED=8C=90=20=EC=95=88=EC=9C=BC=EB=A1=9C=20=EC=A4=84?= =?UTF-8?q?=EC=9D=B4=EA=B3=A0=20=EB=85=B8=EB=B8=8C=20=EC=84=A4=EB=AA=85?= =?UTF-8?q?=EC=9D=84=20=EC=9E=B0=20=EC=8B=9C=EA=B0=81=EC=97=90=20=EB=A7=9E?= =?UTF-8?q?=EC=B6=98=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_011p1aG8xetifag8g82xnh6r --- incoming/pattern-study/patterns/skull-swap.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/incoming/pattern-study/patterns/skull-swap.js b/incoming/pattern-study/patterns/skull-swap.js index f57e858eb..22074ae96 100644 --- a/incoming/pattern-study/patterns/skull-swap.js +++ b/incoming/pattern-study/patterns/skull-swap.js @@ -12,8 +12,8 @@ // @knobs Hue=0..1, Speed=0.2..3, Toss=0..1, Dance=2..8 // // Knob 1: Hue. The night sky and the shade side of the bones, through the cold half of the wheel only: violet at 0, blue at 0.25, cyan at 0.5, sea green at 0.75, toxic green at 1. The moon stays cream, the pumpkin orange, one skull's eyes green and the other's orange. -// Knob 2: Speed. The tempo, 50 beats a minute for each unit. At the opening 100 the skulls leave the hands at 1.6 s, land on the wrong necks at 2.2 s, the two look at each other at 2.9 s, shrug at 3.7 s and throw back at 4.6 s. At 0.2 ten times slower; 150 at 3. -// Knob 3: Toss. How high the skulls fly and how often they spin. At 0 the taller arc tops out over the moon's lower half and each skull turns once; at 0.5 it clears the moon and they turn twice; at 1 they leave the top of the panel, turn three times and the throwers hop. +// Knob 2: Speed. The tempo, 50 beats a minute for each unit. At the opening 100 the skulls leave the hands at 1.6 s, land on the wrong necks at 2.1 s, the two look at each other at 2.8 s, shrug at 3.6 s and throw back at 4.9 s. At 0.2 ten times slower; 150 at 3. +// Knob 3: Toss. How high the skulls fly and how often they spin. At 0 the higher arc crosses the moon and each skull turns once; at 0.5 it brushes the top edge and they turn twice; at 1 it leaves the panel for a moment, they turn three times and the throwers hop. // Knob 4: Dance. Beats danced between swaps, 2 to 8. Each round they dance the next of three dances: a sway with both arms up, kicks to the side with hands on hips, a bounce at the knees pumping the forearms in turn. // Buttons: K1 tap ends the dance and starts the throw at once. @@ -252,7 +252,7 @@ export function update(dt, input, params) { T = times(S); // The arc is settled while no skull is in the air. if (!((u >= T[1] && u < T[2]) || (u >= T[6] && u < T[7]))) { - S.h = 13 + 32 * toss; + S.h = 13 + 24 * toss; S.T = 0.26 * sqrt(S.h); S.turns = 1 + floor(toss * 2.99); S.hop = toss; From a79800a22f38443b2a23ed56f56c69ad48cab984 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 25 Oct 2026 21:51:36 +0000 Subject: [PATCH 07/25] =?UTF-8?q?=ED=8C=A8=ED=84=B4:=20In=20the=20Flash=20?= =?UTF-8?q?=EB=A5=BC=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_011p1aG8xetifag8g82xnh6r --- .../pattern-study/patterns/in-the-flash.js | 321 ++++++++++++++++++ 1 file changed, 321 insertions(+) create mode 100644 incoming/pattern-study/patterns/in-the-flash.js diff --git a/incoming/pattern-study/patterns/in-the-flash.js b/incoming/pattern-study/patterns/in-the-flash.js new file mode 100644 index 000000000..ef2b2796f --- /dev/null +++ b/incoming/pattern-study/patterns/in-the-flash.js @@ -0,0 +1,321 @@ +// ===== Patternflow pattern ===== +// Title: In the Flash +// Family: halloween +// Idea: A rainy field at night stays almost dark, rain and a faint horizon, until lightning lights it violet-white for a moment and shows a tall thin figure standing in the grass, a little closer at every flash and never once seen moving, until a flash shows its head right in front of you and two glowing eyes stay behind in the dark; the next flash shows an empty field, and then it stands far off again. +// Author: patternflow-lab +// Date: 2026-10-25 +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Lineage: original, pattern round 50 +// Round: 50 +// =============================== +// @matrix 64x128 +// @knobs Hue=0..1, Speed=0.2..3, Rain=0..1, Approach=3..8 +// +// Knob 1: Hue. The tint of the flash and the colour of the eyes, turned together: at 0 a violet-white flash and toxic green eyes; at 0.25 a pink-white flash and blue eyes; at 0.5 a pale yellow-green flash and magenta eyes; at 0.75 a pale cyan flash and orange eyes. The dark sky between flashes stays deep violet. +// Knob 2: Speed. How often the lightning comes. At 2 the figure takes 6.4 s to come from far off to right in front, split between its steps, each gap 0.85 to 1.15 times the even share; the eyes stay at least 2.3 s; at 0.2 everything between flashes is ten times slower, at 3 two thirds as long. A flash itself always lasts about half a second, a bright stroke and a weaker one 0.2 s later. The first flash comes 0.5 s in at Speed 2. +// Knob 3: Rain. At 0 about 200 drops in the air, at 1 about 900, each a streak slanting left, dim in the dark and white in the flash. +// Knob 4: Approach. How many flashes it takes to come from far off to right in front: at 3 it stands 18 LEDs tall on the horizon, then 38, then 82 with its feet at the bottom; at 8 it comes in eight smaller steps, so the lightning comes more often. Then one flash for the face and one for the empty field. +// Buttons: K1 tap brings the next flash at once. + +const W = 64, H = 128, N = W * H, HZ = 70; +const { floor, round, min, max, abs, sqrt, exp, pow } = 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; +}; +// Value noise on a lattice, in two dimensions. +const vn2 = (x, y, sd) => { + const i = floor(x), j = floor(y), u = sm(x - i), v = sm(y - j); + const a = hash(i * 7919 + j * 104729 + sd), b = hash((i + 1) * 7919 + j * 104729 + sd); + const c = hash(i * 7919 + (j + 1) * 104729 + sd), d = hash((i + 1) * 7919 + (j + 1) * 104729 + sd); + return a + (b - a) * u + (c - a) * v + (a - b - c + d) * u * v; +}; +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; +} + +// Seen from eye height 1.6 m with 60 LEDs to the metre at one metre off, the +// horizon on row HZ. The figure is 2.4 m tall and comes from 8 m to 1.75 m. +const FOC = 60, EYE = 1.6, TALL = 2.4, ZFAR = 8, ZNEAR = 1.75; +// Seconds at Speed 2 for the whole approach, and the least the eyes stay. +const TAPP = 6.4, TEYES = 2.3; +const MAXD = 900; + +export function setup(params) { + const S = (params.s = { + t: 0, hue: 0, speed: 2, rain: 0, steps: 3, + next: 0.5, ft: 9, k: 0, n: 0, cyc: 0, x0: 24, fig: null, face: false, eyes: -1, + bolt: new Int16Array(800), nb: 0, bx: 32, + drops: new Float32Array(MAXD * 3), base: new Float32Array(N * 3), lit: new Float32Array(N), fm: new Uint8Array(N), buf: new Float32Array(N * 3), + fc: [0, 0, 0], ec: [0, 0, 0], colF: new Float32Array(W), + }); + scene(S); + for (let i = 0; i < MAXD; i++) { + S.drops[i * 3] = Math.random() * 80; + S.drops[i * 3 + 1] = Math.random() * H; + S.drops[i * 3 + 2] = Math.random(); + } +} + +// The still scene: its dark colours and how much of the flash each LED gives +// back. Clouds in the sky, wet grass below, and a lone tree and a fence line +// that stay black. +function scene(S) { + const { base, lit } = S; + const tree = new Uint8Array(N); + // A bare tree on the horizon: a trunk and branches that fork twice. + const limb = (x, y, a, len, w, d) => { + for (let s = 0; s < len; s += 0.5) { + const px = x + Math.sin(a) * s, py = y - Math.cos(a) * s; + for (let i = -floor(w / 2); i <= floor((w - 1) / 2); i++) { + const xx = round(px) + i, yy = round(py); + if (xx >= 0 && xx < W && yy >= 0 && yy < H) tree[yy * W + xx] = 1; + } + } + if (d < 3) { + const ex = x + Math.sin(a) * len, ey = y - Math.cos(a) * len; + limb(ex, ey, a - 0.45 - 0.3 * hash(d * 7 + len), len * 0.68, max(1, w - 1), d + 1); + limb(ex, ey, a + 0.5 + 0.25 * hash(d * 13 + len), len * 0.62, max(1, w - 1), d + 1); + } + }; + limb(10, HZ + 1, 0.05, 9, 2, 0); + // Fence posts along the right, 1.9 m off, every 1.9 m, with two wires. + const posts = []; + for (let i = 0; i < 16; i++) { + const z = 3.4 + 1.9 * i, x = 32 + (FOC * 1.9) / z, yb = HZ + (FOC * EYE) / z, h = (FOC * 1.15) / z; + posts.push([x, yb, h]); + for (let y = round(yb - h); y <= round(yb); y++) + for (let xx = floor(x - max(0.5, 5 / z)); xx <= floor(x + max(0.5, 5 / z) - 0.01); xx++) if (xx >= 0 && xx < W && y < H) tree[y * W + xx] = 1; + } + for (let i = 0; i + 1 < posts.length; i++) + for (const f of [0.9, 0.5]) { + const [x1, y1, h1] = posts[i], [x2, y2, h2] = posts[i + 1]; + for (let s = 0; s <= 1; s += 0.05) { + const xx = round(x1 + (x2 - x1) * s), yy = round(y1 - h1 * f + (y2 - h2 * f - y1 + h1 * f) * s); + if (xx >= 0 && xx < W && yy >= 0 && yy < H) tree[yy * W + xx] = 1; + } + } + for (let y = 0; y < H; y++) + for (let x = 0; x < W; x++) { + const p = y * W + x, o = p * 3; + if (tree[p]) { + (base[o] = 3), (base[o + 1] = 2), (base[o + 2] = 6), (lit[p] = 0); + continue; + } + if (y < HZ) { + // Clouds: two scales of noise, in five steps, heavier low down. + const c = floor(cl(0.65 * vn2(x / 14, y / 7, 3) + 0.35 * vn2(x / 5, y / 3, 9) - 0.15 + y / 260, 0, 1) * 5) / 5; + const g = y > HZ - 9 ? ((y - HZ + 9) / 9) ** 2 : 0; + base[o] = 12 + 9 * c + 30 * g; + base[o + 1] = 8 + 6 * c + 22 * g; + base[o + 2] = 22 + 14 * c + 40 * g; + lit[p] = 0.22 + 0.45 * c; + } else { + // The field: rows of wet grass closing up toward the horizon. + const d = (y - HZ + 1) / (H - HZ), gr = floor(vn2(x / (2 + 3 * d), (y - HZ) / (1 + 2 * d), 21) * 3) / 3; + base[o] = 5; + base[o + 1] = 6; + base[o + 2] = 10; + lit[p] = (0.16 + 0.14 * gr) * (y < HZ + 3 ? 1.5 : 1); + } + } +} + +// The figure, standing: rows of spans from the top of its head (v = 0) to +// its feet (v = 1), into the mask fm, centred on an LED so that it stays +// symmetric when small. A small head on a long neck, narrow shoulders, a +// thin trunk, long legs, and long arms that hang off the shoulders and +// spread a little to hands below the knee. A lean of the head and how far +// the arms stand off change from flash to flash. +function stand(S, cx, top, h, tilt, spread) { + const fm = S.fm; + fm.fill(0); + cx = floor(cx) + 0.5; + const span = (y, c, hw) => { + for (let x = max(0, floor(cx + c - hw)); x <= min(W - 1, floor(cx + c + hw)); x++) if (abs(x + 0.5 - cx - c) <= hw + 0.02) fm[y * W + x] = 1; + }; + const tw0 = max(0.5, 0.05 * h), aw0 = max(0.5, 0.017 * h), sh = tw0 + aw0 + 1.05, lc = max(1, 0.036 * h), lw = max(0.5, 0.022 * h); + for (let y = max(0, floor(top)); y < min(H, top + h + 1); y++) { + const v = (y + 0.5 - top) / h; + if (v < 0 || v > 1.01) continue; + if (v < 0.095) { + const q = (v - 0.05) / 0.05; + span(y, round(tilt * 0.02 * h * (1 - v * 10)), max(1, 0.042 * h * sqrt(max(0, 1 - q * q)))); + } else if (v < 0.13) span(y, 0, max(0.5, 0.013 * h)); + // Narrow shoulders, joining the arms to the trunk. + else if (v < 0.13 + max(0.035, 1.1 / h)) span(y, 0, sh + 0.6); + else { + if (v < 0.53) span(y, 0, max(0.5, tw0 * (1 - 0.25 * (v - 0.13)))); + // The arms spread a little as they hang; the hands are thinner, with + // a finger turned out. + if (v < 0.84) { + const s = (v - 0.13) / 0.71, hand = v > 0.76, aw = hand ? max(0.5, aw0 * 0.6) : aw0; + for (const sg of [-1, 1]) { + const c = sh + (max(1.6, 0.05 * h) + spread * (sg > 0 ? 1 : 0.5) * 0.04 * h) * s; + span(y, sg * c, aw); + if (hand && h > 40 && v > 0.79) span(y, sg * (c + 1 + (v - 0.79) * h * 0.5), 0.5); + } + } + if (v >= 0.5) { + span(y, -lc, lw); + span(y, lc, lw); + if (v > 0.985 && h > 30) (span(y, -lc - lw, lw), span(y, lc + lw, lw)); + } + } + } +} + +// Right in front: a head from row 13 to row 66, leaning a little, on a thin +// neck, and narrow shoulders falling away to the edges. +const TILT = -0.12, EYEY = 38; +function close(S) { + const fm = S.fm; + fm.fill(0); + for (let y = 0; y < H; y++) { + const dy = (y - 39.5) / 26.5, rx = 19 * (dy > 0 ? 1 - 0.3 * dy * dy : 1), head = abs(dy) < 1 ? rx * sqrt(1 - dy * dy) : 0, hx = 32 + (y - EYEY) * TILT; + const neck = y > 60 ? 6.5 : 0, sh = y > 82 ? 6.5 + (y - 82) * 1.6 : 0; + for (let x = 0; x < W; x++) if (abs(x + 0.5 - hx) <= head || abs(x + 0.5 - 32) <= max(neck, sh)) fm[y * W + x] = 1; + } +} + +// A flash: what it shows, the bolt, and when the next one comes. +function flash(S) { + const n = S.steps, k = S.k; + S.ft = 0; + S.face = false; + S.fig = null; + if (k < n) { + if (k === 0) (S.cyc++), (S.x0 = 15 + 14 * hash(S.cyc * 31)); + const p = n > 1 ? k / (n - 1) : 1, z = ZFAR * pow(ZNEAR / ZFAR, p), h = (FOC * TALL) / z, feet = HZ + (FOC * EYE) / z; + const sd = S.cyc * 17 + k; + S.fig = 1; + stand(S, S.x0 + (32 - S.x0) * p, feet - h, h, hash(sd) * 2 - 1, hash(sd + 5)); + } else if (k === n) (S.face = true), close(S), (S.eyes = 0); + else S.fm.fill(0); + // A bolt: a jagged walk down from the top with two forks, ending low in the + // clouds or at the horizon. + let x = (S.bx = 6 + 52 * Math.random()), nb = 0; + const yEnd = Math.random() < 0.6 ? HZ - 1 : 34 + 24 * Math.random(); + const walk = (x, y, y1, dir, lim) => { + while (y < y1 && nb < lim) { + const ny = y + 2 + floor(Math.random() * 4), nx = x + dir * (Math.random() * 2.5) + (Math.random() - 0.5) * 5; + for (let s = 0; s < 1; s += 0.2) if (nb < lim) (S.bolt[2 * nb] = round(x + (nx - x) * s)), (S.bolt[2 * nb + 1] = round(y + (ny - y) * s)), nb++; + (x = nx), (y = ny); + if (dir === 0 && Math.random() < 0.12 && y < y1 - 12) { + const keep = [x, y]; + walk(x, y, y + 10 + 14 * Math.random(), Math.random() < 0.5 ? -1.6 : 1.6, min(400, nb + 60)); + [x, y] = keep; + } + } + }; + walk(x, 0, yEnd, 0, 400); + S.nb = nb; + // The next flash. + const d = TAPP / n, j = 0.85 + 0.3 * hash(S.n++ * 13 + 7); + // Never more than two flashes a second, whatever the knobs. + S.next = S.t + max(0.26 * S.speed, (k === n ? max(d, TEYES) : d) * j); + S.k = k + 1 > n + 1 ? 0 : k + 1; +} + +export function update(dt, input, params) { + if (!params.s) setup(params); + const S = params.s, kn = input.knobValues || [0, 2, 0, 3], d = cl(dt || 0, 0, 0.1); + S.hue = cl(kn[0], 0, 1); + S.speed = cl(kn[1], 0.2, 3); + S.rain = cl(kn[2], 0, 1); + S.steps = round(cl(kn[3], 3, 8)); + if (S.k > S.steps + 1) S.k = 0; + S.t += (d * S.speed) / 2; + S.ft += d; + if (S.eyes >= 0) S.eyes += (d * S.speed) / 2; + if (S.t >= S.next || (input.btnPressed && input.btnPressed[0] && S.ft > 0.6)) flash(S); + // Rain falls 105 LEDs a second, slanting left. + const dr = S.drops, cnt = 200 + floor(700 * S.rain); + for (let i = 0; i < cnt; i++) { + const o = i * 3, v = 80 + 50 * dr[o + 2]; + dr[o + 1] += v * d; + dr[o] -= 0.22 * v * d; + if (dr[o + 1] > H + 4) (dr[o + 1] -= H + 8), (dr[o] = Math.random() * 80); + } +} + +// How bright the flash is, s seconds after it starts: a stroke, and a second +// weaker one 0.2 s later. +const env = (s) => (s < 0 ? 0 : max(exp(-s / 0.2), 0.62 * sm((s - 0.17) / 0.05) * exp(-max(0, s - 0.22) / 0.24))); + +export function draw(display, params) { + const S = params.s; + if (!S) return; + const { buf, base, lit, fm, colF } = S, F = env(S.ft), fc = hsv(0.76 + S.hue, 0.45, 1, S.fc), ec = hsv(0.3 + S.hue, 0.8, 1, S.ec); + // The sky is brightest round the bolt. + for (let x = 0; x < W; x++) colF[x] = F * (0.85 + 0.75 * exp(-abs(x - S.bx) / 12)); + for (let p = 0, o = 0; p < N; p++, o += 3) { + if (fm[p]) { + buf[o] = buf[o + 1] = buf[o + 2] = 0; + continue; + } + const l = lit[p] * colF[p & 63]; + buf[o] = base[o] + fc[0] * l; + buf[o + 1] = base[o + 1] + fc[1] * l; + buf[o + 2] = base[o + 2] + fc[2] * l; + } + // The bolt, white with a glow of the flash's tint, while the strokes last. + if (F > 0.3) { + const a = min(1, (F - 0.3) * 2.5); + for (let i = 0; i < S.nb; i++) { + const x = S.bolt[2 * i], y = S.bolt[2 * i + 1]; + if (x < 0 || x >= W || y < 0 || y >= HZ || fm[y * W + x]) continue; + for (const dx of [-1, 1]) if (x + dx >= 0 && x + dx < W && !fm[y * W + x + dx]) add(buf, (y * W + x + dx) * 3, fc, 0.5 * a); + const o = (y * W + x) * 3; + buf[o] += (255 - buf[o]) * a; + buf[o + 1] += (255 - buf[o + 1]) * a; + buf[o + 2] += (255 - buf[o + 2]) * a; + } + } + // Rain: dim streaks in the dark, near white in the flash. Two depths. + const dr = S.drops, cnt = 200 + floor(700 * S.rain); + for (let i = 0; i < cnt; i++) { + const o = i * 3, near = dr[o + 2] > 0.6, len = near ? 4 : 2, br = near ? 1 : 0.6; + const r = (30 + 0.9 * fc[0] * F) * br, g = (34 + 0.9 * fc[1] * F) * br, b = (50 + 0.9 * fc[2] * F) * br; + for (let j = 0; j < len; j++) { + const x = round(dr[o] + 0.22 * j), y = round(dr[o + 1] - j); + if (x < 0 || x >= W || y < 0 || y >= H) continue; + const q = (y * W + x) * 3, f = (1 - j / (len + 1)) * (fm[y * W + x] ? 0.35 : 1); + buf[q] = max(buf[q], r * f); + buf[q + 1] = max(buf[q + 1], g * f); + buf[q + 2] = max(buf[q + 2], b * f); + } + } + // The eyes, right in front, glowing on after the flash, blinking once. + if (S.face && S.eyes >= 0) { + const e = S.eyes, open = e < 0.08 ? e / 0.08 : abs(e - 1.1) < 0.08 ? 0.15 : e > TEYES - 0.3 ? max(0, (TEYES - e) / 0.3) : 1; + if (open > 0) + for (const cx of [24.5, 40.5]) + for (let dy = -3; dy <= 3; dy++) + for (let dx = -6; dx <= 6; dx++) { + // Round, with a slit, the outer corners lifted a little. + const side = cx < 32 ? -1 : 1, y = EYEY + dy, x = floor(cx + dx), u = x + 0.5 - cx, lift = u * side > 1.5 ? 0.6 : 0; + const r = (u / 3.3) ** 2 + ((dy + lift) / (2.4 * open + 0.3)) ** 2; + if (r > 1.9) continue; + const o = (y * W + x) * 3; + if (r <= 1) add(buf, o, abs(u) < 0.6 && open > 0.5 ? [10, 10, 10] : r < 0.3 ? [255, 255, 235] : ec, 1); + else add(buf, o, ec, 0.3 * open); + } + } + for (let p = 0, o = 0; p < N; p++, o += 3) + display.setPixel(p & 63, p >> 6, min(255, round(buf[o] / 4) * 4), min(255, round(buf[o + 1] / 4) * 4), min(255, round(buf[o + 2] / 4) * 4)); +} + +function add(buf, o, c, a) { + buf[o] += (c[0] - buf[o]) * a; + buf[o + 1] += (c[1] - buf[o + 1]) * a; + buf[o + 2] += (c[2] - buf[o + 2]) * a; +} From c61083a52b67e418abdeedd4c1bb90b410f312c0 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 25 Oct 2026 21:51:43 +0000 Subject: [PATCH 08/25] =?UTF-8?q?=ED=8C=A8=ED=84=B4:=20Paper=20Lantern=20G?= =?UTF-8?q?host=20=EB=A5=BC=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_011p1aG8xetifag8g82xnh6r --- .../patterns/paper-lantern-ghost.js | 342 ++++++++++++++++++ 1 file changed, 342 insertions(+) create mode 100644 incoming/pattern-study/patterns/paper-lantern-ghost.js diff --git a/incoming/pattern-study/patterns/paper-lantern-ghost.js b/incoming/pattern-study/patterns/paper-lantern-ghost.js new file mode 100644 index 000000000..1e831cb93 --- /dev/null +++ b/incoming/pattern-study/patterns/paper-lantern-ghost.js @@ -0,0 +1,342 @@ +// ===== Patternflow pattern ===== +// Title: Paper Lantern Ghost +// Family: halloween +// Idea: On a rainy purple night a ribbed paper lantern swings from an eave until one big eye opens on it and its seam tears into a wide grin with a long red tongue lolling out, while below a one-eyed paper umbrella hops on its single leg back and forth across the wet ground, tongue out, splashing the puddles that carry the lantern's light. +// Author: patternflow-lab +// Date: 2026-10-25 +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Lineage: original, pattern round 50 +// Round: 50 +// =============================== +// @matrix 64x128 +// @knobs Hue=0..1, Speed=0.2..3, Rain=0..1, Hop=0..1 +// +// Knob 1: Hue. The lantern's light: the paper, its halo, the rain it lights and its reflection in the puddles. Orange at 0, green at 0.25, sky blue at 0.5, magenta at 0.75; the umbrella stays toxic green and the tongues red. +// Knob 2: Speed. Everything's clock, rain included. At the opening 2 the eye opens at 0.8 s, the grin tears open at 1.2 s, the tongue is out by 2.4 s, the eye blinks at 4.6 s and the face closes back into a plain lantern at 7.4 s before it starts again at 9 s; the umbrella hops every 0.6 s. At 0.2 ten times slower, at 3 half again as fast. +// Knob 3: Rain. At 0 a steady rain of 90 short streaks falling nearly straight; at 1 a downpour of 420 longer ones driven slantwise by the wind. Each drop that lands rings a puddle or splashes the stones. +// Knob 4: Hop. How high and far the umbrella hops: 10 LEDs up and 8 along at 0, a little more at the opening 0.06, 46 up and 18 along at 1, high enough to reach the lantern's tongue, each hop longer in the air. + +const W = 64, H = 128, N = W * H; +const { floor, round, min, max, abs, sqrt, sin, cos, 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 ease = (v) => (v <= 0 ? 0 : v >= 1 ? 1 : v * v * (3 - 2 * v)); +const hsv = (h, s, o) => [5, 3, 1].forEach((n, c) => { + const k = (n + (h - floor(h)) * 6) % 6; + o[c] = 255 * (1 - s * max(0, min(k, 4 - k, 1))); +}); + +const PX = 32, PY = 9, LC = 27, GROUND = 110, FOOT = 117; +// Puddles on the stones: [x, y, half width, half height]. +const PUDDLES = [[13, 116, 11, 2.6], [46, 120.5, 13, 3], [24, 125.5, 9, 1.8]]; +const inPuddle = (x, y) => PUDDLES.some(([a, b, c, d]) => ((x - a) / c) ** 2 + ((y - b) / d) ** 2 < 1); + +export function setup(params) { + params.s = { + hue: -1, t: 0, ph: 0, th: 0, wind: 0, L: [0, 0, 0], paper: [0, 0, 0], + bg: new Float32Array(N * 3), buf: new Float32Array(N * 3), + // The tongue: a hanging chain, each point with where it was last frame. + tg: Array.from({ length: 8 }, () => [PX, PY + LC + 8, PX, PY + LC + 8]), + // The umbrella: where it took off and where it lands, the way it faces, + // and how far through the hop it is. + U: { x0: 12, x1: 21, dir: 1, p: 0.1, x: 12, y: 0, tilt: 0, sq: 1, leg: 13 }, + drops: [], rings: [], bits: [], + face: { eye: 0, mouth: 0, tongue: 0 }, + }; + background(params.s); +} + +// A new drop, upwind far enough that the wind carries some to every column. +function drop(top, wind) { + return { x: Math.random() * (66 + wind * 140) - wind * 140, y: top ? Math.random() * -30 : Math.random() * FOOT, v: 95 + Math.random() * 40, fy: GROUND + Math.random() * 17, len: 3 + floor(Math.random() * 3 + wind * 8) }; +} + +export function update(dt, input, params) { + if (!params.s) setup(params); + const S = params.s, k = input.knobValues || [0, 2, 0, 0.06]; + const hue = cl(k[0], 0, 1), rain = cl(k[2], 0, 1), hop = cl(k[3], 0, 1); + const d = cl(dt || 0, 0, 0.1) * (cl(k[1], 0.2, 3) / 2); + if (hue !== S.hue) { + S.hue = hue; + hsv(0.075 + hue, 0.78, S.L); + // The paper glows paler than the light it gives. + for (let c = 0; c < 3; c++) S.paper[c] = mix(S.L[c], [255, 246, 214][c], 0.5); + } + S.t += d; + // The face, on a 9 s loop: the eye opens, the seam tears into a grin, the + // tongue lolls out; a blink; then it all closes up again. + const tau = S.t % 9, F = S.face; + F.eye = ease((tau - 0.8) / 0.3) * (1 - ease((tau - 7.9) / 0.3)) * (1 - sin(PI * cl((tau - 4.6) / 0.25, 0, 1))); + F.mouth = ease((tau - 1.2) / 0.5) * (1 - ease((tau - 7.4) / 0.5)); + F.tongue = 25 * ease((tau - 1.5) / 0.9) * (1 - ease((tau - 6.9) / 0.6)); + // The swing: wider while the face is open. + S.ph += d * 1.8; + S.th = (0.15 + 0.1 * F.mouth) * sin(S.ph) + 0.04 * sin(S.ph * 1.7 + 1); + // The tongue hangs from the lower lip and swings behind the lantern. + const [ax, ay] = toWorld(S.th, 0.5, LC + 6 + 4 * F.mouth), seg = F.tongue / 7, g = 140 * d * d; + S.tg[0] = [ax, ay, ax, ay]; + for (let i = 1; i < 8; i++) { + const p = S.tg[i], vx = (p[0] - p[2]) * 0.94, vy = (p[1] - p[3]) * 0.94; + (p[2] = p[0]), (p[3] = p[1]); + (p[0] += vx), (p[1] += vy + g); + } + for (let n = 0; n < 3; n++) + for (let i = 1; i < 8; i++) { + const a = S.tg[i - 1], b = S.tg[i], dx = b[0] - a[0], dy = b[1] - a[1], l = sqrt(dx * dx + dy * dy) || 1; + (b[0] = a[0] + (dx / l) * seg), (b[1] = a[1] + (dy / l) * seg); + } + // The umbrella: crouch, a parabola from one landing to the next, a squash + // on landing; it turns round at either side. + const U = S.U, P = 0.55 + 0.45 * hop, hh = 10 + 36 * hop, step = 8 + 10 * hop, was = U.p; + U.p += d / P; + if (U.p >= 1) { + U.p -= 1; + U.x0 = U.x1; + if (U.x0 + U.dir * step > 54 || U.x0 + U.dir * step < 10) U.dir = -U.dir; + U.x1 = U.x0 + U.dir * step; + } + const a = cl((U.p - 0.2) / 0.65, 0, 1), cr = U.p < 0.2 ? sin((PI * U.p) / 0.2) : U.p > 0.85 ? sin((PI * (U.p - 0.85)) / 0.15) : 0; + U.x = mix(U.x0, U.x1, a); + U.y = -hh * 4 * a * (1 - a); + U.tilt = 0.16 * U.dir * sin(PI * a); + U.sq = 1 - 0.16 * cr + 0.06 * sin(PI * a); + U.leg = 13 - 5 * cr; + if (was < 0.85 && U.p >= 0.85) { + // Landing: a ring in the puddle and a spray of drops. + S.rings.push([U.x, FOOT, 0, 1.6]); + for (let i = 0; i < 12; i++) S.bits.push([U.x + (Math.random() - 0.5) * 8, FOOT - 1, (Math.random() - 0.5) * 50, -25 - Math.random() * 35, 0]); + } + // Rain, its rings in the puddles and splashes on the stones. + const want = round(90 + 330 * rain), wind = (S.wind = 0.08 + 0.32 * rain); + while (S.drops.length < want) S.drops.push(drop(false, wind)); + if (S.drops.length > want) S.drops.length = want; + for (const r of S.drops) { + r.y += r.v * d; + r.x += r.v * d * wind; + if (r.y > r.fy) { + if (inPuddle(r.x, r.fy)) S.rings.length < 60 && S.rings.push([r.x, r.fy, 0, 1]); + else if (S.bits.length < 120) S.bits.push([r.x, r.fy, (Math.random() - 0.5) * 20, -18, 0.25]); + Object.assign(r, drop(true, wind)); + } + } + S.rings = S.rings.filter((r) => (r[2] += d) < 0.45 * r[3]); + S.bits = S.bits.filter((b) => { + b[0] += b[2] * d; + b[1] += b[3] * d; + b[3] += 160 * d; + return (b[4] += d) < 0.55 && b[1] < FOOT + 2; + }); +} +const toWorld = (th, x, y) => [PX + x * cos(th) - y * sin(th), PY + x * sin(th) + y * cos(th)]; + +// --- the night, laid out once: the eave of a roof along the top with a row +// of round tile ends, a purple sky stepping lighter downward, a bamboo fence, +// wet paving with three puddles. +function background(S) { + const bg = S.bg; + for (let y = 0; y < H; y++) + for (let x = 0; x < W; x++) { + const o = (y * W + x) * 3, v = floor(cl((y - 8) / 84, 0, 1) * 12) / 12; + let r = 34 + 60 * v, g = 20 + 40 * v, b = 62 + 76 * v; + const te = (((x + 3) % 6) - 2.5) ** 2 + (y - 2.5) ** 2; + if (y < 8) { + // Round tile ends along the roof's edge over a dark beam. + const rim = (y < 4 && te > 3 && te < 7.5) || y === 7; + (r = rim ? 76 : 20), (g = rim ? 52 : 12), (b = rim ? 96 : 28); + } else if (y >= 88 && y < GROUND) { + // Slats of split bamboo, two bindings across. + const slat = x % 3 !== 2 || y < 90, bind = abs(y - 93) < 1 || abs(y - 104) < 1; + if (slat || bind) (r = bind ? 46 : 16), (g = bind ? 30 : 10), (b = bind ? 52 : 24); + if (y === 88 && slat) (r = 70), (g = 50), (b = 90); + } else if (y >= GROUND) { + const joint = (y - GROUND) % 6 === 5 || (x + (floor((y - GROUND) / 6) & 1) * 5) % 10 === 0; + (r = joint ? 16 : 34), (g = joint ? 10 : 22), (b = joint ? 24 : 46); + if (inPuddle(x + 0.5, y + 0.5)) (r = 62), (g = 44), (b = 104); + } + bg[o] = r; + bg[o + 1] = g; + bg[o + 2] = b; + } +} + +const put = (buf, o, c) => { + buf[o] = c[0]; + buf[o + 1] = c[1]; + buf[o + 2] = c[2]; +}; +const addc = (buf, o, c, a) => { + buf[o] += c[0] * a; + buf[o + 1] += c[1] * a; + buf[o + 2] += c[2] * a; +}; +const RAIN = [110, 100, 170], BLACK = [18, 8, 22], WHITE = [255, 250, 236], GULLET = [70, 0, 22], TONGUE = [244, 62, 96], GROOVE = [186, 26, 66]; +const GREEN = [120, 236, 44], GREEN2 = [74, 178, 26], RIB = [30, 80, 20], SKIN = [244, 208, 172]; + +// The lantern in its own frame, hung from the hook: a barrel of paper 26 +// wide between two black lacquered caps, ribbed every 3.3 LEDs. +function lantern(S, buf) { + const F = S.face, th = S.th, c = cos(th), s = sin(th), L = S.L, Pp = S.paper; + // Where the eye looks: at the umbrella. + const [cx, cy] = toWorld(th, 0, LC), ux = S.U.x - cx, uy = FOOT - 20 + S.U.y - cy, ul = sqrt(ux * ux + uy * uy) || 1; + const lx0 = ((ux * c + uy * s) / ul) * 2.4, ly0 = ((-ux * s + uy * c) / ul) * 1.8; + for (let y = PY; y < PY + 54; y++) + for (let x = 6; x < 58; x++) { + const dx = x + 0.5 - PX, dy = y + 0.5 - PY, bx = dx * c + dy * s, by = -dx * s + dy * c - LC, o = (y * W + x) * 3; + if (by < -19.5) { + if ((abs(bx) < 0.6 && by > -LC) || (abs(bx) < 2.4 && by > -22)) put(buf, o, BLACK); + continue; + } + if (abs(by) > 16.5) { + if (abs(bx) < (by < 0 ? 7.5 : 8.5) && abs(by) < 20) put(buf, o, abs(by) < 17.6 ? [90, 66, 100] : BLACK); + continue; + } + const hw = 13 * sqrt(1 - (by / 19.5) ** 2); + if (abs(bx) > hw) continue; + // The eye, its lids and the pupil looking at the umbrella. + const ey = (bx - 2) ** 2 / 36 + (by + 5) ** 2 / (25 * F.eye * F.eye + 0.01); + if (F.eye > 0.04 && ey < 1.45) { + put(buf, o, ey > 1 ? BLACK : (bx - 2 - lx0) ** 2 + (by + 5 - ly0 * F.eye) ** 2 < 6.8 ? ((bx - 2.7 - lx0) ** 2 + (by + 5.8 - ly0) ** 2 < 0.7 ? WHITE : BLACK) : WHITE); + continue; + } + // The grin: the seam at row 6 tears open, corners curling up, the + // torn edges ragged. + const q = bx / 11.5, m = F.mouth, rag = 0.5 * sin(bx * 2.7) * m; + const top = 6 - m * (1.6 * (1 - q * q) + 3.2 * q ** 4) + rag, bot = 6 + m * (8.5 * (1 - q * q) - 3.2 * q ** 4) + rag; + if (abs(q) < 1 && by > top - 0.45 && by < bot + 0.45) { + put(buf, o, m < 0.05 || by < top + 0.35 || by > bot - 0.35 ? [150, 40, 30] : GULLET); + continue; + } + // Paper: palest in the middle, the light's own colour at the sides, + // ribs a little darker, bowed with the barrel. + const e = (bx / hw) ** 2, rb = (by + 16.5 + 0.9 * e) % 3.3 < 0.8; + for (let ch = 0; ch < 3; ch++) buf[o + ch] = mix(Pp[ch], L[ch], e) * (rb ? 0.78 : 1); + } +} + +// The tongue: a capsule along the chain, thinning to a round tip, a groove +// down the middle, outlined dark. +function tongue(S, buf) { + const T = S.tg, F = S.face; + if (F.tongue < 1) return; + let xa = 64, xb = 0, ya = 128, yb = 0; + for (const p of T) (xa = min(xa, p[0])), (xb = max(xb, p[0])), (ya = min(ya, p[1])), (yb = max(yb, p[1])); + for (let y = max(0, floor(ya - 4)); y < min(H, yb + 5); y++) + for (let x = max(0, floor(xa - 4)); x < min(W, xb + 5); x++) { + let dm = 9, cm = 9; + for (let i = 1; i < 8; i++) { + const [ax, ay] = T[i - 1], [bx, by] = T[i], vx = bx - ax, vy = by - ay, l2 = vx * vx + vy * vy || 1; + const t = cl(((x + 0.5 - ax) * vx + (y + 0.5 - ay) * vy) / l2, 0, 1), dd = sqrt((x + 0.5 - ax - vx * t) ** 2 + (y + 0.5 - ay - vy * t) ** 2); + const r = i === 7 ? 2.6 : 2.7 - 0.08 * i; + if (dd - r < dm) (dm = dd - r), (cm = dd); + } + const o = (y * W + x) * 3; + if (dm < 0) put(buf, o, cm < 0.55 && y > T[1][1] ? GROOVE : TONGUE); + else if (dm < 1) for (let ch = 0; ch < 3; ch++) buf[o + ch] *= 0.3; + } +} + +// The umbrella in its own frame, origin where the leg meets the canopy, y +// down: a bell of oiled paper 26 wide and 28 tall on five ribs, scalloped +// between the ribs at the rim, one eye, a tongue, one leg on a clog. +function canopy(x, y) { + if (y < -28 || y > 1.4 * abs(sin((PI * x) / 6.5))) return 0; + const s = -y / 28; + return abs(x) < (y < 0 ? 13 * sqrt(1 - s) * (1 - 0.15 * s) : 13) ? 1 : 0; +} +function umbrella(S, buf) { + const U = S.U, c = cos(U.tilt), s = sin(U.tilt), ox = U.x, oy = FOOT - 3.3 - U.leg + U.y, f = U.dir, z = 1 / U.sq; + for (let y = max(0, floor(oy - 34 * U.sq)); y < min(H, oy + U.leg + 5); y++) + for (let x = max(0, floor(ox - 17)); x < min(W, ox + 18); x++) { + const dx = x + 0.5 - ox, dy = y + 0.5 - oy, lx = dx * c + dy * s, ly = -dx * s + dy * c, cy = ly * z, o = (y * W + x) * 3; + // The tongue, out of the mouth under the eye and down over the rim, + // flapping with the hop. + const tl = (cy + 8.5) / 13.5, tx = f * (0.6 + (1.5 + 2.5 * sin(PI * U.p)) * tl); + if (tl > 0 && tl < 1 && abs(lx - tx) < 1.9 - 0.4 * tl) { + put(buf, o, abs(lx - tx) < 0.45 && tl > 0.2 ? GROOVE : TONGUE); + continue; + } + if (canopy(lx, cy)) { + const hw = cy < 0 ? 13 * sqrt(1 + cy / 28) * (1 + (0.15 * cy) / 28) : 13, u = lx / hw; + const ey = (lx - f) ** 2 / 20 + (cy + 15) ** 2 / 16; + if (ey < 1.4) { + // The eye looks the way it is going. + put(buf, o, ey > 1 ? BLACK : (lx - 2 * f) ** 2 + (cy + 15.5) ** 2 < 3.6 ? BLACK : WHITE); + } else if (abs(lx - f * 0.6) < 3 && abs(cy + 8.6) < 1) put(buf, o, GULLET); + else if (cy < -26.5) put(buf, o, BLACK); + else { + const rib = abs(u * 2 - round(u * 2)) * hw < 0.55, side = floor((u + 1) * 2) & 1; + put(buf, o, rib ? RIB : side ? GREEN2 : GREEN); + if (cy > -1.5) for (let ch = 0; ch < 3; ch++) buf[o + ch] *= 0.62; + } + continue; + } + if (canopy(lx + 0.9, cy) || canopy(lx - 0.9, cy) || canopy(lx, cy + 0.9) || canopy(lx, cy - 0.9)) { + for (let ch = 0; ch < 3; ch++) buf[o + ch] *= 0.3; + continue; + } + // The tip. + if (lx * lx + (cy + 29.6) ** 2 < 2.6) { + put(buf, o, BLACK); + continue; + } + // The leg, its knee bending forward in a crouch, and the clog. + const kx = f * (13 - U.leg) * 0.9, kl = U.leg / 2, legd = ly < kl ? abs(lx - kx * (ly / kl)) : abs(lx - kx * (1 - (ly - kl) / kl)); + if (ly > 0 && ly < U.leg && legd < 1.15) put(buf, o, SKIN); + else if (ly >= U.leg && ly < U.leg + 1.4 && abs(lx) < 3.8) put(buf, o, [150, 30, 40]); + else if (ly >= U.leg + 1.4 && ly < U.leg + 3.3 && abs(abs(lx) - 2.3) < 0.8) put(buf, o, [92, 56, 40]); + } +} + +export function draw(display, params) { + const S = params.s; + if (!S || S.hue < 0) return; + const buf = S.buf, L = S.L; + buf.set(S.bg); + const [cx, cy] = toWorld(S.th, 0, LC); + // The halo around the lantern in three steps, and its light under the eave. + for (let y = 0; y < GROUND; y++) + for (let x = 0; x < W; x++) { + const r = sqrt((x + 0.5 - cx) ** 2 + ((y + 0.5 - cy) * 0.8) ** 2), a = r < 22 ? 0.3 : r < 30 ? 0.17 : r < 40 ? 0.07 : 0; + if (a) addc(buf, (y * W + x) * 3, L, a * (y < 9 ? 1.6 : 1)); + } + // The puddles hold the lantern upside down, broken up by ripples, and the + // wet stones catch its light where it falls. + for (let y = GROUND; y < H; y++) + for (let x = 0; x < W; x++) { + const o = (y * W + x) * 3, wob = sin(y * 2.1 + S.t * 5) * 1.2; + if (inPuddle(x + 0.5, y + 0.5)) { + const w = abs(x + 0.5 + wob - cx); + if (w < 11) addc(buf, o, S.paper, w < 6 ? 0.8 : 0.45); + } else if (abs(x + 0.5 - cx) < 11 - (y - GROUND) * 0.3 && (y - GROUND) % 6 === 0) addc(buf, o, L, 0.75); + } + for (const [x, y, t, big] of S.rings) { + const rr = 1 + (t / 0.45 / big) * 4.5 * big, a = 0.6 * (1 - t / 0.45 / big); + for (let i = 0; i < 20; i++) { + const px = round(x + rr * cos((i * TAU) / 20)), py = round(y + rr * 0.32 * sin((i * TAU) / 20)); + if (px >= 0 && px < W && py < H && inPuddle(px + 0.5, py + 0.5)) addc(buf, (py * W + px) * 3, [190, 180, 230], a); + } + } + lantern(S, buf); + // The tongue swings in front of the lantern's lower half and below it. + tongue(S, buf); + umbrella(S, buf); + // Rain: pale lilac streaks, lit the lantern's colour near it. + const wind = S.wind; + for (const r of S.drops) { + const dl = sqrt((r.x - cx) ** 2 + ((r.y - cy) * 0.8) ** 2), lit = dl < 20 ? 0.85 : dl < 32 ? 0.5 : dl < 46 ? 0.22 : 0; + for (let i = 0; i < r.len; i++) { + const x = round(r.x - wind * i), y = round(r.y - i); + if (x < 0 || x >= W || y < 0 || y >= H) continue; + const o = (y * W + x) * 3, a = 1 - i / r.len; + // Lit drops lose the lilac, so they read orange rather than peach. + addc(buf, o, RAIN, 0.55 * a * (1 - lit)); + if (lit) addc(buf, o, L, lit * a); + } + } + for (const [x, y, , , t] of S.bits) { + const px = round(x), py = round(y); + if (px >= 0 && px < W && py >= 0 && py < H) addc(buf, (py * W + px) * 3, [170, 160, 220], 1 - t / 0.55); + } + 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); +} From 0c882a5978f63f4b73045c89dedb12c327785c68 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 25 Oct 2026 21:53:46 +0000 Subject: [PATCH 09/25] =?UTF-8?q?=ED=8C=A8=ED=84=B4:=20In=20the=20Flash=20?= =?UTF-8?q?=EC=9D=98=20=ED=98=95=EC=B2=B4=EA=B0=80=20=EB=B2=88=EC=A9=8D?= =?UTF-8?q?=EC=9E=84=20=EC=82=AC=EC=9D=B4=20=EC=96=B4=EB=91=A0=EC=97=90=20?= =?UTF-8?q?=EB=82=A8=EC=A7=80=20=EC=95=8A=EA=B2=8C=20=ED=95=98=EA=B3=A0=20?= =?UTF-8?q?=EB=91=90=20=ED=8C=A8=ED=84=B4=EC=9D=98=20=EB=85=B8=EB=B8=8C=20?= =?UTF-8?q?=EC=84=A4=EB=AA=85=EC=9D=84=20=EC=9E=B0=20=EA=B0=92=EC=97=90=20?= =?UTF-8?q?=EB=A7=9E=EC=B6=98=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_011p1aG8xetifag8g82xnh6r --- incoming/pattern-study/patterns/in-the-flash.js | 17 ++++++++++++----- incoming/pattern-study/patterns/old-well.js | 4 ++-- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/incoming/pattern-study/patterns/in-the-flash.js b/incoming/pattern-study/patterns/in-the-flash.js index ef2b2796f..34b307a56 100644 --- a/incoming/pattern-study/patterns/in-the-flash.js +++ b/incoming/pattern-study/patterns/in-the-flash.js @@ -12,8 +12,8 @@ // @knobs Hue=0..1, Speed=0.2..3, Rain=0..1, Approach=3..8 // // Knob 1: Hue. The tint of the flash and the colour of the eyes, turned together: at 0 a violet-white flash and toxic green eyes; at 0.25 a pink-white flash and blue eyes; at 0.5 a pale yellow-green flash and magenta eyes; at 0.75 a pale cyan flash and orange eyes. The dark sky between flashes stays deep violet. -// Knob 2: Speed. How often the lightning comes. At 2 the figure takes 6.4 s to come from far off to right in front, split between its steps, each gap 0.85 to 1.15 times the even share; the eyes stay at least 2.3 s; at 0.2 everything between flashes is ten times slower, at 3 two thirds as long. A flash itself always lasts about half a second, a bright stroke and a weaker one 0.2 s later. The first flash comes 0.5 s in at Speed 2. -// Knob 3: Rain. At 0 about 200 drops in the air, at 1 about 900, each a streak slanting left, dim in the dark and white in the flash. +// Knob 2: Speed. How often the lightning comes. At 2 the figure takes 6.4 s to come from far off to right in front, split evenly between its steps, each gap 0.85 to 1.15 times its share, and the eyes stay at least 2.3 s; at 0.2 the gaps are ten times as long, at 3 two thirds as long, but never shorter than half a second. A flash itself always lights the field for about two thirds of a second: a bright stroke, and a weaker one 0.2 s later. The first comes 0.5 s in at Speed 2. +// Knob 3: Rain. At 0 about 200 drops in the air, at 1 about 900, each a streak slanting left, dim in the dark and lit with the flash's tint when it comes. // Knob 4: Approach. How many flashes it takes to come from far off to right in front: at 3 it stands 18 LEDs tall on the horizon, then 38, then 82 with its feet at the bottom; at 8 it comes in eight smaller steps, so the lightning comes more often. Then one flash for the face and one for the empty field. // Buttons: K1 tap brings the next flash at once. @@ -249,7 +249,10 @@ export function update(dt, input, params) { // How bright the flash is, s seconds after it starts: a stroke, and a second // weaker one 0.2 s later. -const env = (s) => (s < 0 ? 0 : max(exp(-s / 0.2), 0.62 * sm((s - 0.17) / 0.05) * exp(-max(0, s - 0.22) / 0.24))); +const env = (s) => { + const f = s < 0 ? 0 : max(exp(-s / 0.2), 0.62 * sm((s - 0.17) / 0.05) * exp(-max(0, s - 0.22) / 0.24)); + return f < 0.02 ? 0 : f; +}; export function draw(display, params) { const S = params.s; @@ -257,9 +260,13 @@ export function draw(display, params) { const { buf, base, lit, fm, colF } = S, F = env(S.ft), fc = hsv(0.76 + S.hue, 0.45, 1, S.fc), ec = hsv(0.3 + S.hue, 0.8, 1, S.ec); // The sky is brightest round the bolt. for (let x = 0; x < W; x++) colF[x] = F * (0.85 + 0.75 * exp(-abs(x - S.bx) / 12)); + // The figure is black against the flash and gone into the dark between. + const hide = 1 - min(1, F * 6); for (let p = 0, o = 0; p < N; p++, o += 3) { if (fm[p]) { - buf[o] = buf[o + 1] = buf[o + 2] = 0; + buf[o] = base[o] * hide; + buf[o + 1] = base[o + 1] * hide; + buf[o + 2] = base[o + 2] * hide; continue; } const l = lit[p] * colF[p & 63]; @@ -288,7 +295,7 @@ export function draw(display, params) { for (let j = 0; j < len; j++) { const x = round(dr[o] + 0.22 * j), y = round(dr[o + 1] - j); if (x < 0 || x >= W || y < 0 || y >= H) continue; - const q = (y * W + x) * 3, f = (1 - j / (len + 1)) * (fm[y * W + x] ? 0.35 : 1); + const q = (y * W + x) * 3, f = (1 - j / (len + 1)) * (fm[y * W + x] ? 0.35 + 0.65 * hide : 1); buf[q] = max(buf[q], r * f); buf[q + 1] = max(buf[q + 1], g * f); buf[q + 2] = max(buf[q + 2], b * f); diff --git a/incoming/pattern-study/patterns/old-well.js b/incoming/pattern-study/patterns/old-well.js index 578271d04..bec7114b8 100644 --- a/incoming/pattern-study/patterns/old-well.js +++ b/incoming/pattern-study/patterns/old-well.js @@ -12,9 +12,9 @@ // @knobs Hue=0..1, Speed=0.2..3, Moon=0..1, Fog=0..1 // // Knob 1: Hue. The colour of the glow in the water, of the light it throws up the shaft and of what comes up in the bucket: sickly green at 0, cyan at 0.25, violet at 0.5, orange at 0.75. The sky, the stones and the moonlight stay as they are. -// Knob 2: Speed. At 2 the bucket goes down at 18 LEDs a second and comes up at 20, and one round takes about 14 s: down 3.6 s, the water and the hand 4.2 s, a 0.6 s calm, up 4.8 s and 0.9 s hanging at the top. At 0.2 everything is ten times slower, at 3 half as fast again. The first round starts with the bucket already low, so it touches the water 0.7 s in. +// Knob 2: Speed. At 2 the bucket goes down at 18 LEDs a second and comes up at 20, and one round takes about 14 s: down 3.6 s, the water and the hand 4.2 s, a 0.6 s calm, up 5 s and 0.9 s hanging at the top. At 0.2 everything is ten times slower, at 3 half as fast again. The first round starts with the bucket already low, so it touches the water 0.7 s in. // Knob 3: Moon. Where the moon stands. At 0 it is high on the left and its light falls into the shaft slanting right, thinning as it goes down until it reaches the water on the right; at 0.25 it falls almost straight; at 0.5 the moon peeks over the ridge of the roof and the light leans left; at 1 it hangs low on the right and lights only the upper half of the left wall. -// Knob 4: Fog. A mist over the water, added as light, moonlit and tinted by the glow. At 0 none; at 0.06 a thin layer over the water; at 0.5 it fills the bottom third of the shaft; at 1 it rises 70 rows, to just below the ground. +// Knob 4: Fog. A mist over the water, added as light, moonlit and tinted by the glow. At 0 none; at 0.06 a thin layer over the water; at 0.5 it fills the bottom half of the shaft; at 1 it rises 70 rows, to the mouth of the well. // Buttons: K1 tap lets go of the crank while the bucket is going down or hanging at the top: it falls to the water at once. const W = 64, H = 128, N = W * H; From 2581dd6dddb7934d330104c68e7e1c4dc1b9e86d Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 25 Oct 2026 21:55:00 +0000 Subject: [PATCH 10/25] =?UTF-8?q?=ED=8C=A8=ED=84=B4:=20Old=20Well=20?= =?UTF-8?q?=EC=9D=98=20=EB=AC=BC=20=EC=9E=A5=EB=A9=B4=EC=9D=84=20=EC=A4=84?= =?UTF-8?q?=EC=9D=B4=EA=B3=A0=20=EB=91=90=EB=A0=88=EB=B0=95=EC=9D=84=20?= =?UTF-8?q?=EB=B9=A8=EB=A6=AC=20=EA=B0=90=EC=95=84=202~8=EC=B4=88=20?= =?UTF-8?q?=EC=95=88=EC=97=90=20=EB=91=90=EB=A0=88=EB=B0=95=EC=9D=B4=2046?= =?UTF-8?q?=EC=A4=84=EA=B9=8C=EC=A7=80=20=EC=98=A4=EB=A5=B4=EA=B2=8C=20?= =?UTF-8?q?=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_011p1aG8xetifag8g82xnh6r --- incoming/pattern-study/patterns/old-well.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/incoming/pattern-study/patterns/old-well.js b/incoming/pattern-study/patterns/old-well.js index bec7114b8..f9eaaa6cd 100644 --- a/incoming/pattern-study/patterns/old-well.js +++ b/incoming/pattern-study/patterns/old-well.js @@ -12,7 +12,7 @@ // @knobs Hue=0..1, Speed=0.2..3, Moon=0..1, Fog=0..1 // // Knob 1: Hue. The colour of the glow in the water, of the light it throws up the shaft and of what comes up in the bucket: sickly green at 0, cyan at 0.25, violet at 0.5, orange at 0.75. The sky, the stones and the moonlight stay as they are. -// Knob 2: Speed. At 2 the bucket goes down at 18 LEDs a second and comes up at 20, and one round takes about 14 s: down 3.6 s, the water and the hand 4.2 s, a 0.6 s calm, up 5 s and 0.9 s hanging at the top. At 0.2 everything is ten times slower, at 3 half as fast again. The first round starts with the bucket already low, so it touches the water 0.7 s in. +// Knob 2: Speed. At 2 the bucket goes down at 18 LEDs a second and is wound up at 26, and one round takes about 12.5 s: down 3.6 s, the water and the hand 3.8 s, a 0.5 s calm, up 3.8 s and 0.9 s hanging at the top. At 0.2 everything is ten times slower, at 3 half as fast again. The first round starts with the bucket already low, so it touches the water 0.5 s in and is pulled under at 1.8 s. // Knob 3: Moon. Where the moon stands. At 0 it is high on the left and its light falls into the shaft slanting right, thinning as it goes down until it reaches the water on the right; at 0.25 it falls almost straight; at 0.5 the moon peeks over the ridge of the roof and the light leans left; at 1 it hangs low on the right and lights only the upper half of the left wall. // Knob 4: Fog. A mist over the water, added as light, moonlit and tinted by the glow. At 0 none; at 0.06 a thin layer over the water; at 0.5 it fills the bottom half of the shaft; at 1 it rises 70 rows, to the mouth of the well. // Buttons: K1 tap lets go of the crank while the bucket is going down or hanging at the top: it falls to the water at once. @@ -52,7 +52,7 @@ const CX = 26, CY = 22, RX = 31; const TOPB = 33, BOTB = 97; // Seconds into the event at Speed 2: the glow is up, the hand has risen, it // has a grip, the yank, the glow starts to fade, and it is all over. -const E_GLOW = 0.35, E_HAND0 = 0.5, E_HAND1 = 1.0, E_GRIP = 1.15, E_YANK = 1.3, E_FADE = 2.6, E_END = 4.2; +const E_GLOW = 0.35, E_HAND0 = 0.5, E_HAND1 = 1.0, E_GRIP = 1.15, E_YANK = 1.3, E_FADE = 2.5, E_END = 3.8; // Kinds of LED in the still picture. const SKY = 0, ROOF = 1, GABLE = 2, WOOD = 3, BACK = 4, SECT = 5, SOIL = 6, GRASS = 7, WATER = 8; const MOONC = [175, 185, 225], TINT = [0.95, 0.9, 1.12]; @@ -63,7 +63,7 @@ for (let i = 0; i < 128; i++) UP[i] = exp(-i / 16); export function setup(params) { params.s = { t: 0, hue: 0, speed: 2, moon: -1, fog: 0.06, s: 0.3, - ph: "lower", e: 0, by: 84, v: 0, cyc: 0, cling: false, + ph: "lower", e: 0, by: 88, v: 0, cyc: 0, cling: false, th: 0, ex: 0, sv: 0, wa: 0, wp: 0, hand: { on: false, x: 22, y: 140, c: 0, v: 0, fall: false }, eo: 0, g: 0, cg: 0, @@ -219,7 +219,7 @@ export function update(dt, input, params) { } if (e > E_END) (S.ph = "calm"), (S.e = 0), (S.g = 0), (S.eo = 0); } else if (S.ph === "calm") { - if (S.e > 0.6) { + if (S.e > 0.5) { S.ph = "raise"; S.by = 132; // Every other round the hand comes up still holding the rim. @@ -229,7 +229,7 @@ export function update(dt, input, params) { hd.fall = false; } } else if (S.ph === "raise") { - S.by -= 20 * ds; + S.by -= 26 * ds; if (hd.on && !hd.fall) { hd.y = S.by + 6; if (S.by < 62) (hd.fall = true), (hd.v = 0); From 49c91c07cc41068da3c02eda77ece5309c6a003f Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 25 Oct 2026 21:56:23 +0000 Subject: [PATCH 11/25] =?UTF-8?q?=ED=8C=A8=ED=84=B4:=20Old=20Well=20?= =?UTF-8?q?=EC=9D=98=20=EB=B9=9B=20=EC=85=88=EC=9D=84=20=ED=91=9C=EB=A1=9C?= =?UTF-8?q?=20=EB=B0=94=EA=BE=B8=EA=B3=A0=20=ED=94=BD=EC=85=80=EB=A7=88?= =?UTF-8?q?=EB=8B=A4=20=EB=A7=8C=EB=93=A4=EB=8D=98=20=EB=B0=B0=EC=97=B4?= =?UTF-8?q?=EC=9D=84=20=EC=97=86=EC=95=A4=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_011p1aG8xetifag8g82xnh6r --- incoming/pattern-study/patterns/old-well.js | 25 +++++++++++++-------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/incoming/pattern-study/patterns/old-well.js b/incoming/pattern-study/patterns/old-well.js index f9eaaa6cd..a79181958 100644 --- a/incoming/pattern-study/patterns/old-well.js +++ b/incoming/pattern-study/patterns/old-well.js @@ -56,9 +56,14 @@ const E_GLOW = 0.35, E_HAND0 = 0.5, E_HAND1 = 1.0, E_GRIP = 1.15, E_YANK = 1.3, // Kinds of LED in the still picture. const SKY = 0, ROOF = 1, GABLE = 2, WOOD = 3, BACK = 4, SECT = 5, SOIL = 6, GRASS = 7, WATER = 8; const MOONC = [175, 185, 225], TINT = [0.95, 0.9, 1.12]; +// The bucket's iron hoops, its rim and its two shades of stave. +const HOOP = [70, 70, 86], RIM = [214, 150, 84], STAVE = [[160, 98, 48], [186, 116, 58]]; // The glow's light, falling off up the shaft from the water. const UP = new Float32Array(128); for (let i = 0; i < 128; i++) UP[i] = exp(-i / 16); +// Light round a point, by squared distance in LEDs. +const RAD = new Float32Array(4096); +for (let i = 0; i < 4096; i++) RAD[i] = exp(-sqrt(i) / 7); export function setup(params) { params.s = { @@ -352,7 +357,7 @@ export function draw(display, params) { let f = fw; if (cg > 0.01) { const dx = x - RX, dy = y - by - 4; - f += 0.8 * cg * exp(-sqrt(dx * dx + dy * dy) / 7); + f += 0.8 * cg * RAD[min(4095, (dx * dx + dy * dy) | 0)]; } if (f < 0.012) continue; const p = y * W + x, o = p * 3, a = alb[p] * 0.72 + (kind[p] === BACK ? 0.3 : 0); @@ -375,7 +380,7 @@ export function draw(display, params) { } if (g < 0.01) continue; const n = vn(x * 0.21 + 1.6 * vn(y * 0.17 - t * 0.6, 3) + t * 0.5, 11 + (y >> 2)), ex = x - RX, ey = y - 117; - const f = g * (0.12 + 0.1 * floor(n * 3 + 1.5) / 2 + 0.6 * exp(-sqrt(ex * ex * 0.4 + ey * ey) / 6) + (y - WS < 2 ? 0.2 : 0)); + const f = g * (0.12 + 0.1 * floor(n * 3 + 1.5) / 2 + 0.6 * RAD[min(4095, round((ex * ex * 0.4 + ey * ey) * 1.36))] + (y - WS < 2 ? 0.2 : 0)); buf[o] += gc[0] * f; buf[o + 1] += gc[1] * f; buf[o + 2] += gc[2] * f; @@ -402,11 +407,11 @@ export function draw(display, params) { for (const ex of [RX - 6, RX + 4]) { const ey = 116, wide = S.eo > 0.45; for (let i = 0; i < 5; i++) { - const pup = i === 2, c = pup ? [20, 30, 20] : [228 + gc[0] * 0.1, 240, 222]; - put(buf, ex + i, ey, c[0], c[1], c[2], min(1, S.eo * 2)); + const pup = i === 2, r = pup ? 20 : 228 + gc[0] * 0.1, gg = pup ? 30 : 240, b = pup ? 20 : 222; + put(buf, ex + i, ey, r, gg, b, min(1, S.eo * 2)); if (wide && i > 0 && i < 4) { - put(buf, ex + i, ey - 1, c[0], c[1], c[2], 1); - put(buf, ex + i, ey + 1, c[0], c[1], c[2], 1); + put(buf, ex + i, ey - 1, r, gg, b, 1); + put(buf, ex + i, ey + 1, r, gg, b, 1); } } } @@ -435,7 +440,7 @@ export function draw(display, params) { const xl = RX - 6 + round(r / 9), xr = RX + 5 - round(r / 9); for (let x = xl; x <= xr; x++) { const hoop = r === 2 || r === 7, edge = x === xl || x === xr, rim = r === 0; - let c = hoop ? [70, 70, 86] : rim ? [214, 150, 84] : (x & 1) ? [186, 116, 58] : [160, 98, 48]; + const c = hoop ? HOOP : rim ? RIM : STAVE[x & 1]; const e = edge && !hoop ? 0.7 : 1; solid(S, x, by + r, (c[0] * lit + gc[0] * gl) * e, (c[1] * lit + gc[1] * gl) * e, (c[2] * lit + gc[2] * gl) * e); } @@ -461,6 +466,8 @@ export function draw(display, params) { const m = beam(s, x, WS) * 0.25, y = WS + off[x], crest = off[x] < 0 ? 1.4 : 1; put(buf, x, y, (40 + MOONC[0] * m + gc[0] * g * 0.6) * crest, (44 + MOONC[1] * m + gc[1] * g * 0.6) * crest, (60 + MOONC[2] * m + gc[2] * g * 0.6) * crest, 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)); + // Out in steps of four. + for (let y = 0, o = 0; y < H; y++) + for (let x = 0; x < W; x++, o += 3) + display.setPixel(x, y, min(255, ((buf[o] + 2) >> 2) << 2), min(255, ((buf[o + 1] + 2) >> 2) << 2), min(255, ((buf[o + 2] + 2) >> 2) << 2)); } From 1b1ed6f36da6f3703f1dc0b3153120de8e14a0f3 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 25 Oct 2026 21:57:47 +0000 Subject: [PATCH 12/25] =?UTF-8?q?=ED=8C=A8=ED=84=B4:=20Old=20Well=20?= =?UTF-8?q?=EC=9D=98=20=EB=A8=B8=EB=A6=AC=20=EA=B8=80=EC=9D=84=20=EC=A4=84?= =?UTF-8?q?=EC=9D=B8=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_011p1aG8xetifag8g82xnh6r --- incoming/pattern-study/patterns/old-well.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/incoming/pattern-study/patterns/old-well.js b/incoming/pattern-study/patterns/old-well.js index a79181958..778827c7f 100644 --- a/incoming/pattern-study/patterns/old-well.js +++ b/incoming/pattern-study/patterns/old-well.js @@ -1,7 +1,7 @@ // ===== Patternflow pattern ===== // Title: Old Well // Family: halloween -// Idea: A cut-away old stone well under a moonlit sky lowers its wooden bucket down the whole height of the shaft, and when the bucket touches the black water the water glows sickly green, two pale eyes open beneath and a pale hand reaches up and yanks the bucket under, the rope whipping and the windlass spinning, until the water calms and the bucket is wound back up brimming and dripping, sometimes with the hand still on its rim. +// Idea: A cut-away stone well under the moon lowers its bucket to the black water, which glows sickly green as two pale eyes open beneath and a pale hand yanks the bucket under, the rope whipping and the windlass spinning, until it is wound back up brimming, sometimes with the hand still on its rim. // Author: patternflow-lab // Date: 2026-10-25 // SPDX-License-Identifier: CC-BY-SA-4.0 @@ -13,7 +13,7 @@ // // Knob 1: Hue. The colour of the glow in the water, of the light it throws up the shaft and of what comes up in the bucket: sickly green at 0, cyan at 0.25, violet at 0.5, orange at 0.75. The sky, the stones and the moonlight stay as they are. // Knob 2: Speed. At 2 the bucket goes down at 18 LEDs a second and is wound up at 26, and one round takes about 12.5 s: down 3.6 s, the water and the hand 3.8 s, a 0.5 s calm, up 3.8 s and 0.9 s hanging at the top. At 0.2 everything is ten times slower, at 3 half as fast again. The first round starts with the bucket already low, so it touches the water 0.5 s in and is pulled under at 1.8 s. -// Knob 3: Moon. Where the moon stands. At 0 it is high on the left and its light falls into the shaft slanting right, thinning as it goes down until it reaches the water on the right; at 0.25 it falls almost straight; at 0.5 the moon peeks over the ridge of the roof and the light leans left; at 1 it hangs low on the right and lights only the upper half of the left wall. +// Knob 3: Moon. Where the moon stands. At 0 high on the left: its light falls into the shaft slanting right and thins all the way down to the water. At 0.25 it falls straight; at 0.5 the moon peeks over the ridge and the light leans left; at 1 it hangs low on the right and lights only the upper left of the shaft. // Knob 4: Fog. A mist over the water, added as light, moonlit and tinted by the glow. At 0 none; at 0.06 a thin layer over the water; at 0.5 it fills the bottom half of the shaft; at 1 it rises 70 rows, to the mouth of the well. // Buttons: K1 tap lets go of the crank while the bucket is going down or hanging at the top: it falls to the water at once. From feb6d0e3ebc522df4e204d6b6a8538b1831a3398 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 25 Oct 2026 22:00:38 +0000 Subject: [PATCH 13/25] =?UTF-8?q?=ED=8C=A8=ED=84=B4:=20Motility=20Clusters?= =?UTF-8?q?=20=EB=A5=BC=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_011p1aG8xetifag8g82xnh6r --- .../patterns/motility-clusters.js | 283 ++++++++++++++++++ 1 file changed, 283 insertions(+) create mode 100644 incoming/pattern-study/patterns/motility-clusters.js diff --git a/incoming/pattern-study/patterns/motility-clusters.js b/incoming/pattern-study/patterns/motility-clusters.js new file mode 100644 index 000000000..a9d090c9a --- /dev/null +++ b/incoming/pattern-study/patterns/motility-clusters.js @@ -0,0 +1,283 @@ +// ===== Patternflow pattern ===== +// Title: Motility Clusters +// Family: particle +// Idea: About 1,400 self-propelled discs swim round the periodic panel, each along its own slowly wandering heading and touching the others only to be pushed apart, and above a persistence threshold they jam into pale packed clusters edged by discs that face inward, coloured round the wheel by heading, while a faint gas of free swimmers runs between them and the clusters merge. +// Author: patternflow-lab +// Date: 2026-10-25 +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Lineage: motility-induced phase separation of active Brownian particles (Yaouen Fily and M. Cristina Marchetti, 2012; Michael Cates and Julien Tailleur, 2015), pattern round 50 +// Round: 50 +// =============================== +// @matrix 64x128 +// @knobs Hue=0..1, Speed=0.2..3, Pe=-1..1, Packing=0.55..0.72 +// +// Knob 1: Hue. Turns the heading wheel and the tint of the packed discs together. At 0 a disc swimming down is red, down-left yellow, up-left green, up cyan, up-right blue and down-right magenta, so a cluster's edge, where the discs face inward, runs red along its top, cyan along its bottom, violet on its left and yellow-green on its right; packed discs are pale lavender. At 0.25 the tint is pale cyan, at 0.5 pale lime and at 0.75 pale pink. +// Knob 2: Speed. At 2 the discs swim 20 LEDs, ten diameters, a second, at 0.2 a tenth of that and at 3 half as fast again; the turning and the clustering keep pace. The head start before the first frame is always 4 seconds of swimming. +// Knob 3: Pe. The Peclet number v0 / (sigma Dr), the distance in diameters a disc swims before its heading forgets itself, 40 times 8 to the knob. At -1 (Pe 5, a heading lasts half a second at Speed 2) the discs stay a gas of brief clumps, the largest holding a tenth of them; at -0.5 (Pe 14) two fifths; at 0 (Pe 40, four seconds) two thirds after ten seconds, with a third already in one cluster 2.5 s in; at 1 (Pe 320) four fifths. +// Knob 4: Packing. The share of the panel the discs would cover laid flat: 0.55 at the start (1,434 discs), where clusters sit in a gas, to 0.72 (1,877 discs), where the packed phase fills most of the panel and the gas lives in dark bubbles. New discs drop in at random places and taken ones leave from random places. +// Buttons: K1 tap scatters every disc into a fresh uniform gas, and the clusters gather again over the next few seconds. + +const W = 64, H = 128, NP = W * H; +const { floor, min, max, sqrt, round, PI, pow, exp } = Math; +const TAU = 2 * PI; +const cl = (v, a, b) => (v < a ? a : v > b ? b : v); + +// Disc diameter in LEDs. The neighbour grid is 32 by 64 cells one diameter +// wide, so two discs that overlap are in the same or neighbouring cells. +const SIG = 2, S2 = SIG * SIG, GX = 32, GY = 64, NC = GX * GY, CW = W / GX, LIST2 = (SIG + 0.25) ** 2; +// Swim speed in LEDs a second at Speed 2 (ten diameters a second), the step, +// and how many passes push overlapping pairs apart in each step. +const V0 = 20, HS = 1 / 80, SWEEPS = 6; +// Most discs the Packing knob can ask for, room for the pair list, and the +// head start: seconds of swimming run before the first frame, so the panel +// opens on clusters already gathering out of the gas. +const NMAX = 1900, PMAX = NMAX * 8, PRE = 4; +// Drawing: the squared radius of a disc's splash of light in LEDs, how pale +// the packed discs' tint is, the light of a packed disc and of a lone one, +// and where the heading wheel starts. +const KR = 2.2, TS = 0.25, LC = 0.92, L0 = 0.3, HUE0 = 0.25; + +export function setup(params) { + params.s = { + hue: 0, speed: 2, pe: 40, phi: 0.55, n: 0, t: 0, acc: 0, pre: false, + x: new Float64Array(NMAX), y: new Float64Array(NMAX), c: new Float64Array(NMAX), s: new Float64Array(NMAX), + cr: new Float32Array(NMAX), ex: new Float32Array(NMAX), ey: new Float32Array(NMAX), + x2: new Float64Array(NMAX), y2: new Float64Array(NMAX), c2: new Float64Array(NMAX), s2: new Float64Array(NMAX), + cr2: new Float32Array(NMAX), ex2: new Float32Array(NMAX), ey2: new Float32Array(NMAX), + kn: new Float32Array(NMAX), kx: new Float32Array(NMAX), ky: new Float32Array(NMAX), + pl: new Int32Array(PMAX), m: 0, cell: new Int32Array(NMAX), cnt: new Int32Array(NC + 1), fill: new Int32Array(NC), + bw: new Float32Array(NP), br: new Float32Array(NP), bg: new Float32Array(NP), bb: new Float32Array(NP), + }; +} + +// A uniform gas: discs dropped anywhere, headings anywhere. +function place(S, n) { + for (let i = S.n; i < n; i++) { + S.x[i] = Math.random() * W; + S.y[i] = Math.random() * H; + const a = Math.random() * TAU; + S.c[i] = Math.cos(a); + S.s[i] = Math.sin(a); + S.cr[i] = S.ex[i] = S.ey[i] = 0; + } + S.n = n; +} + +// Takes discs away at random until n are left. The arrays are in grid order, +// so cutting the tail would empty the bottom of the panel. +function drop(S, n) { + const { x, y, c, s, cr, ex, ey } = S; + while (S.n > n) { + const i = floor(Math.random() * S.n), j = --S.n; + x[i] = x[j]; + y[i] = y[j]; + c[i] = c[j]; + s[i] = s[j]; + cr[i] = cr[j]; + ex[i] = ex[j]; + ey[i] = ey[j]; + } +} + +// Sorts the discs by grid cell, moving every per-disc array along, so the +// pushes below walk memory in order. cnt holds where each cell starts. +function sort(S) { + const { cell, cnt, fill, n } = S; + const { x, y, c, s, cr, ex, ey } = S; + cnt.fill(0); + for (let i = 0; i < n; i++) { + const k = floor(x[i] / CW) + GX * floor(y[i] / CW); + cell[i] = k; + cnt[k + 1]++; + } + for (let k = 0; k < NC; k++) cnt[k + 1] += cnt[k]; + fill.set(cnt.subarray(0, NC)); + const X = S.x2, Y = S.y2, C = S.c2, Sn = S.s2, R = S.cr2, EX = S.ex2, EY = S.ey2; + for (let i = 0; i < n; i++) { + const o = fill[cell[i]]++; + X[o] = x[i]; + Y[o] = y[i]; + C[o] = c[i]; + Sn[o] = s[i]; + R[o] = cr[i]; + EX[o] = ex[i]; + EY[o] = ey[i]; + } + S.x2 = x, S.y2 = y, S.c2 = c, S.s2 = s, S.cr2 = cr, S.ex2 = ex, S.ey2 = ey; + S.x = X, S.y = Y, S.c = C, S.s = Sn, S.cr = R, S.ex = EX, S.ey = EY; +} + +// One step of h seconds: every disc swims V0 h along its heading, the +// heading turns by rotational diffusion dr, then overlapping pairs are pushed +// apart along the line between them, half the overlap each, the overdamped +// limit of a stiff short-range spring. Nothing attracts. +function step(S, h, dr) { + swim(S, S.n, sqrt(6 * dr * h), V0 * h); + sort(S); + const m = (S.m = pushGrid(S.x, S.y, S.cnt, S.pl)); + for (let sw = 1; sw < SWEEPS; sw++) pushList(S.x, S.y, S.pl, m); + wrap(S.x, S.y, S.n); +} + +function swim(S, n, amp, d) { + const { x, y, c, s } = S; + for (let i = 0; i < n; i++) { + const xi = x[i] + d * c[i], yi = y[i] + d * s[i]; + x[i] = xi < 0 ? xi + W : xi >= W ? xi - W : xi; + y[i] = yi < 0 ? yi + H : yi >= H ? yi - H : yi; + const e = amp * (2 * Math.random() - 1), ci = c[i], si = s[i], q = 1 - 0.5 * e * e; + c[i] = ci * q - si * e; + s[i] = si * q + ci * e; + } +} + +// The first pass searches the grid, pushes, and lists every pair closer than +// SIG + 0.25; the later passes push only the listed pairs. Returns the list's +// length. +function pushGrid(x, y, cnt, pl) { + let m = 0; + for (let cy = 0; cy < GY; cy++) { + const dn = (cy + 1 === GY ? 0 : cy + 1) * GX, r0 = cy * GX; + for (let cx = 0; cx < GX; cx++) { + const k0 = r0 + cx, a0 = cnt[k0], a1 = cnt[k0 + 1]; + if (a0 === a1) continue; + const xr = cx + 1 === GX ? 0 : cx + 1, xl = cx === 0 ? GX - 1 : cx - 1; + for (let q = 0; q < 5; q++) { + const k = q === 0 ? k0 : q === 1 ? r0 + xr : q === 2 ? dn + xl : q === 3 ? dn + cx : dn + xr; + const b0 = cnt[k], b1 = cnt[k + 1]; + for (let i = a0; i < a1; i++) + for (let j = q === 0 ? i + 1 : b0; j < b1; j++) { + const dx = near(x[j] - x[i], W), dy = near(y[j] - y[i], H), r2 = dx * dx + dy * dy; + if (r2 >= LIST2) continue; + if (m < PMAX) (pl[m++] = i), (pl[m++] = j); + if (r2 < S2 && r2 > 0) push(x, y, i, j, dx, dy, r2); + } + } + } + } + return m; +} + +function pushList(x, y, pl, m) { + for (let p = 0; p < m; p += 2) { + const i = pl[p], j = pl[p + 1]; + const dx = near(x[j] - x[i], W), dy = near(y[j] - y[i], H), r2 = dx * dx + dy * dy; + if (r2 < S2 && r2 > 0) push(x, y, i, j, dx, dy, r2); + } +} + +// The shortest way round the periodic panel. +const near = (d, L) => (d > L / 2 ? d - L : d < -L / 2 ? d + L : d); + +function push(x, y, i, j, dx, dy, r2) { + const r = sqrt(r2), f = (0.5 * (SIG - r)) / r; + x[i] -= f * dx; + y[i] -= f * dy; + x[j] += f * dx; + y[j] += f * dy; +} + +function wrap(x, y, n) { + for (let i = 0; i < n; i++) { + x[i] = x[i] < 0 ? x[i] + W : x[i] >= W ? x[i] - W : x[i]; + y[i] = y[i] < 0 ? y[i] + H : y[i] >= H ? y[i] - H : y[i]; + } +} + +export function update(dt, input, params) { + if (!params.s) setup(params); + const S = params.s, k = input.knobValues || [0, 2, 0, 0.55]; + S.hue = cl(k[0], 0, 1); + S.speed = cl(k[1], 0.2, 3); + S.pe = 40 * pow(8, cl(k[2], -1, 1)); + S.phi = cl(k[3], 0.55, 0.72); + if (input.btnPressed && input.btnPressed[0]) S.n = 0; + const want = min(NMAX, round((S.phi * NP) / (PI * S2 * 0.25))); + if (want > S.n) place(S, want); + else if (want < S.n) drop(S, want); + // Pe = V0 / (SIG Dr), the persistence length in diameters. + const dr = V0 / (SIG * S.pe); + let run = cl(dt || 0, 0, 0.1) * (S.speed / 2); + if (!S.pre) { + S.pre = true; + run += PRE; + } + S.acc += run; + let ran = 0; + while (S.acc >= HS) { + step(S, HS, dr); + S.acc -= HS; + S.t += HS; + ran += HS; + } + if (ran > 0) touch(S, 1 - exp(-ran * 8)); +} + +// Each disc's neighbours from the last step's pair list: how many touch it +// and the sum of the directions to them, eased so the light does not +// flicker with every knock. A disc packed all round has many neighbours and +// a short sum; a disc at the edge of a cluster has them all on one side. +function touch(S, k) { + const { x, y, pl, m, kn, kx, ky, cr, ex, ey, n } = S; + kn.fill(0, 0, n); + kx.fill(0, 0, n); + ky.fill(0, 0, n); + for (let p = 0; p < m; p += 2) { + const i = pl[p], j = pl[p + 1]; + const dx = near(x[j] - x[i], W), dy = near(y[j] - y[i], H), r = sqrt(dx * dx + dy * dy) + 1e-9; + kn[i]++; + kn[j]++; + kx[i] += dx / r; + ky[i] += dy / r; + kx[j] -= dx / r; + ky[j] -= dy / r; + } + for (let i = 0; i < n; i++) { + cr[i] += (kn[i] - cr[i]) * k; + ex[i] += (kx[i] - ex[i]) * k; + ey[i] += (ky[i] - ey[i]) * k; + } +} + +export function draw(display, params) { + const S = params.s; + if (!S) return; + const { x, y, c, s, cr, ex, ey, bw, br, bg, bb, n } = S; + bw.fill(0); + br.fill(0); + bg.fill(0); + bb.fill(0); + // The colour wheel by heading: each channel is full for discs swimming + // within 60 degrees of its own direction and dark beyond 120, a third of a + // turn apart. Discs packed all round fade to one pale tint. + const h0 = (S.hue + HUE0) * TAU; + const ar = Math.cos(h0), as = Math.sin(h0), ag = Math.cos(h0 + TAU / 3), gs = Math.sin(h0 + TAU / 3), ab = Math.cos(h0 - TAU / 3), bs = Math.sin(h0 - TAU / 3); + const tr = 1 - TS + TS * cl(0.5 + ar, 0, 1), tg = 1 - TS + TS * cl(0.5 + ag, 0, 1), tb = 1 - TS + TS * cl(0.5 + ab, 0, 1); + for (let i = 0; i < n; i++) { + const k = cr[i], e = sqrt(ex[i] * ex[i] + ey[i] * ey[i]) / max(k, 1), ci = c[i], si = s[i]; + const lum = L0 + (LC - L0) * cl((k - 0.5) / 3.5, 0, 1), u = cl((k - 3) / 2, 0, 1) * cl(1 - 1.6 * e, 0, 1), v = 1 - u; + const r = lum * (v * cl(0.5 + ci * ar + si * as, 0, 1) + u * tr); + const g = lum * (v * cl(0.5 + ci * ag + si * gs, 0, 1) + u * tg); + const b = lum * (v * cl(0.5 + ci * ab + si * bs, 0, 1) + u * tb); + const px = x[i], py = y[i], ix = floor(px), iy = floor(py); + for (let yy = iy - 1; yy <= iy + 1; yy++) + for (let xx = ix - 1; xx <= ix + 1; xx++) { + const dx = xx + 0.5 - px, dy = yy + 0.5 - py, w = 1 - (dx * dx + dy * dy) / KR; + if (w <= 0) continue; + const o = ((yy + H) & 127) * W + ((xx + W) & 63); + bw[o] += w; + br[o] += w * r; + bg[o] += w * g; + bb[o] += w * b; + } + } + for (let p = 0, yy = 0; yy < H; yy++) + for (let xx = 0; xx < W; xx++, p++) { + const w = bw[p], f = w > 1 ? 1 / w : 1; + display.setPixel(xx, yy, q4(br[p] * f), q4(bg[p] * f), q4(bb[p] * f)); + } +} + +// Channels in steps of 4 of 255, from 0..1. +const q4 = (v) => ((v * 63 + 0.5) | 0) << 2; + From 9c0326e1dc8a52b406e504ed1d234362927bc72a Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 25 Oct 2026 22:03:04 +0000 Subject: [PATCH 14/25] =?UTF-8?q?=ED=8C=A8=ED=84=B4:=20Inverse=20Cascade?= =?UTF-8?q?=20=EB=A5=BC=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_011p1aG8xetifag8g82xnh6r --- .../pattern-study/patterns/inverse-cascade.js | 322 ++++++++++++++++++ 1 file changed, 322 insertions(+) create mode 100644 incoming/pattern-study/patterns/inverse-cascade.js diff --git a/incoming/pattern-study/patterns/inverse-cascade.js b/incoming/pattern-study/patterns/inverse-cascade.js new file mode 100644 index 000000000..f9ab35eb9 --- /dev/null +++ b/incoming/pattern-study/patterns/inverse-cascade.js @@ -0,0 +1,322 @@ +// ===== Patternflow pattern ===== +// Title: Inverse Cascade +// Family: noise +// Idea: Two-dimensional turbulence solved on the panel's own 64x128 grid: a field of small random eddies gathers within two seconds into round vortices, red-to-amber ones turning one way and indigo-to-sky-blue ones the other, each ringed by contour bands of its vorticity, and same-sign vortices orbit, wrap each other in thin spiral filaments and merge into fewer and larger ones, carrying the energy up to the large scales while the enstrophy drains away down the filaments, until the energy fills the box and a fresh field of small eddies starts the cascade again. +// Author: patternflow-lab +// Date: 2026-10-25 +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Lineage: inverse energy cascade of two-dimensional turbulence (Robert Kraichnan, 1967; George Batchelor, 1969), coherent vortices of decaying two-dimensional turbulence (James McWilliams, 1984), pattern round 50 +// Round: 50 +// =============================== +// @matrix 64x128 +// @knobs Hue=0..1, Speed=0.2..3, Scale=-1..1, Viscosity=0..1 +// +// Knob 1: Hue. Turns both colours round the wheel together, always half a turn apart. At 0 vorticity of one sign runs from deep crimson through red to amber and the white of a vortex core, and of the other from indigo through blue to sky blue and white; on the panel the warm vortices turn clockwise and the cool ones anticlockwise. At 0.25 lime and magenta, at 0.5 olive and cyan, at 0.75 green and violet. The stream function tints each side a little towards one end or the other of its own range. +// Knob 2: Speed. Flow time per second. At 2 the eddies gather into round vortices in about 2 seconds, about 80 vortices become 10 in 9 seconds, and a run ends with 5 to 8 after 13 to 17 seconds; at 1 half as fast, at 0.2 a tenth. Above 2 a frame may take a third step, and at 3 a run lasts about 8 seconds. When the flow is too fast for the steps a frame may take, it runs slower instead. +// Knob 3: Scale. The size of the eddies a run starts from: the noise is filtered round wavenumber 5 at 0, a wavelength of 13 LEDs, round 8.7 at -1 (7 LEDs) and 2.9 at 1 (22 LEDs). Bigger eddies start nearer the end, so a run is shorter. Turning it by more than 0.15 starts a fresh run at the new size. +// Knob 4: Viscosity. The hyperviscosity that takes the enstrophy out at the bottom of its cascade, doubling with every fifth of the knob. At the opening 0.06 a filament two LEDs wide lasts about a tenth of a second and wider ones survive, so the vortices trail long spiral arms; at 1 it is 26 times stronger, filaments up to three LEDs wide dissolve as fast, and the vortices sit in smoother, emptier water. +// Buttons: K1 tap starts a fresh run of small eddies. + +const W = 64, H = 128, N = W * H; +const { floor, min, max, exp, sqrt, abs, cos, sin, PI, log2, pow, round } = Math; +const cl = (v, a, b) => (v < a ? a : v > b ? b : v); + +// The panel is a periodic box 2 pi wide and 4 pi tall, one grid step 2 pi / 64 +// either way, so kx runs over the integers and ky over the half-integers. +// Modes with |kx| or |ky| above 21 are dropped after every product +// (Orszag's two-thirds rule). +const KXC = 21, KYC = 42, DX = (2 * PI) / W; +// Flow time per panel second at Speed 2, and the Courant number each step +// keeps to (with |u| + |v|). A frame takes at most two steps up to Speed 2 +// and three above it; when the flow is faster than that allows, it runs +// slower instead. +const RATE = 7.5, CFL = 0.8; +// Hyperviscosity nu (k / 21)^8: nu at Viscosity 0, doubling with every fifth +// of the knob. +const NU = 12; +// A run ends when the energy-weighted wavenumber has fallen to this share of +// the one it started from (but not below 1, the box), or after this much +// flow time. +const KEND = 0.28, RUN = 240; +// Crossfade into a fresh field, panel seconds. +const FADE = 0.7; +// Colour: the hue of strong warm vorticity at Hue 0; contour bands per unit +// of the display scale and how deep they cut (reaching full depth above a +// sixth of the scale); the brightness curve; the share of the largest |w| that +// shows at full scale; and how far round the wheel the stream function tints. +const WARM = 0.09, BANDS = 4, BDEP = 0.4, GAM = 0.4, NORM = 0.7, TINT = 0.08, LUT = 1024; + +// ---- radix-2 FFT, in place on (re, im) at off, off + st, off + 2 st, ... +function plan(n) { + const lg = log2(n), rev = new Uint16Array(n), c = new Float64Array(n >> 1), s = new Float64Array(n >> 1); + for (let i = 0; i < n; i++) { + let r = 0; + for (let b = 0; b < lg; b++) r |= ((i >> b) & 1) << (lg - 1 - b); + rev[i] = r; + } + for (let i = 0; i < n >> 1; i++) (c[i] = cos((2 * PI * i) / n)), (s[i] = sin((2 * PI * i) / n)); + return { n, rev, c, s, br: new Float64Array(n), bi: new Float64Array(n) }; +} +function fft1(P, re, im, off, st, sign) { + const { n, rev, c, s, br, bi } = P; + for (let i = 0; i < n; i++) { + const j = off + rev[i] * st; + br[i] = re[j]; + bi[i] = im[j]; + } + for (let size = 2; size <= n; size <<= 1) { + const half = size >> 1, step = n / size; + for (let a0 = 0; a0 < n; a0 += size) + for (let k = 0, t = 0; k < half; k++, t += step) { + const wr = c[t], wi = sign * s[t], a = a0 + k, b = a + half; + const xr = br[b] * wr - bi[b] * wi, xi = br[b] * wi + bi[b] * wr; + br[b] = br[a] - xr; + bi[b] = bi[a] - xi; + br[a] += xr; + bi[a] += xi; + } + } + for (let i = 0; i < n; i++) (re[off + i * st] = br[i]), (im[off + i * st] = bi[i]); +} +// Only the 43 columns with |kx| <= 21 can be non-zero, so the y transforms +// run on those alone: after the x transforms going forward (sign -1), before +// them going back (sign +1). Neither direction divides by N. +const COLS = []; +for (let x = 0; x < W; x++) if ((x < W / 2 ? x : W - x) <= KXC) COLS.push(x); +function fft2(S, re, im, sign) { + if (sign < 0) for (let y = 0; y < H; y++) fft1(S.px, re, im, y * W, 1, sign); + for (let t = 0; t < COLS.length; t++) fft1(S.py, re, im, COLS[t], W, sign); + if (sign > 0) for (let y = 0; y < H; y++) fft1(S.px, re, im, y * W, 1, sign); +} + +// The vorticity equation dw/dt = -div(u w) for the spectrum (sr, si), into +// (or, oi), dealiased. One inverse transform brings w and the stream function +// psi (psi^ = -w^ / k^2) to the grid together, as w + i psi; the velocity +// u = -dpsi/dy, v = dpsi/dx comes from fourth-order differences of psi; one +// forward transform takes u w + i v w back, and the two products are pulled +// apart with the conjugate mode. Leaves w and psi on the grid in br, bi +// (and their largest sizes in wm, pm) and returns the largest |u| + |v|. +function tendency(S, sr, si, or, oi) { + const { kx, ky, ik2, mask, neg, ar, ai, br: om, bi: ps } = S; + for (let i = 0; i < N; i++) { + const r = sr[i] / N, m = si[i] / N, q = ik2[i]; + om[i] = r + q * m; + ps[i] = m - q * r; + } + fft2(S, om, ps, 1); + const c = 1 / (12 * DX); + let vmax = 0, wm = 0, pm = 0; + for (let y = 0; y < H; y++) { + const o = y * W, u1 = ((y + 1) & (H - 1)) * W, d1 = ((y - 1) & (H - 1)) * W, u2 = ((y + 2) & (H - 1)) * W, d2 = ((y - 2) & (H - 1)) * W; + for (let x = 0; x < W; x++) { + const r1 = (x + 1) & (W - 1), l1 = (x - 1) & (W - 1), r2 = (x + 2) & (W - 1), l2 = (x - 2) & (W - 1); + const u = -c * (8 * (ps[u1 + x] - ps[d1 + x]) - (ps[u2 + x] - ps[d2 + x])); + const v = c * (8 * (ps[o + r1] - ps[o + l1]) - (ps[o + r2] - ps[o + l2])); + const w = om[o + x], p = ps[o + x]; + ar[o + x] = u * w; + ai[o + x] = v * w; + const sp = abs(u) + abs(v); + if (sp > vmax) vmax = sp; + if (w > wm) wm = w; + else if (-w > wm) wm = -w; + if (p > pm) pm = p; + else if (-p > pm) pm = -p; + } + } + S.wm = wm; + S.pm = pm; + fft2(S, ar, ai, -1); + for (let i = 0; i < N; i++) { + if (!mask[i]) { + or[i] = oi[i] = 0; + continue; + } + const j = neg[i], cr = ar[i], ci = ai[i], dr = ar[j], di = ai[j], a = kx[i], b = ky[i]; + or[i] = 0.5 * (a * (ci - di) - b * (cr - dr)); + oi[i] = -0.5 * (a * (cr + dr) + b * (ci + di)); + } + return vmax; +} + +// One third-order Adams-Bashforth step of length h with the viscous decay +// folded in exactly (integrating factor). The two previous tendencies, each +// already decayed to the present, wait in (p1r, p1i) and (p2r, p2i); the +// coefficients come from integrating the quadratic through the last three +// tendencies, so the step may change length. A fresh run starts with an +// Euler step and a second-order one. +function step(S, h) { + const { wr, wi, nr, ni, p1r, p1i, p2r, p2i, lin } = S; + S.vmax = tendency(S, wr, wi, nr, ni); + // The decay factors for each step length on the ladder are kept, a few + // at a time, so a step that comes back costs no exponentials. + const key = h * S.nu; + let E = S.Ec.get(key); + if (!E) { + if (S.Ec.size > 8) S.Ec.clear(); + E = new Float64Array(N); + for (let i = 0; i < N; i++) E[i] = exp(-key * lin[i]); + S.Ec.set(key, E); + } + const h1 = S.h1, h2 = S.h2; + let c0 = h, c1 = 0, c2 = 0; + if (h1 && h2) { + const a = (h * h * h) / 3, b = (h * h) / 2; + c0 = (a + (2 * h1 + h2) * b + h1 * (h1 + h2) * h) / (h1 * (h1 + h2)); + c1 = -(a + (h1 + h2) * b) / (h1 * h2); + c2 = (a + h1 * b) / ((h1 + h2) * h2); + } else if (h1) (c0 = h * (1 + h / (2 * h1))), (c1 = -(h * h) / (2 * h1)); + for (let i = 0; i < N; i++) { + const e = E[i]; + wr[i] = e * (wr[i] + c0 * nr[i] + c1 * p1r[i] + c2 * p2r[i]); + wi[i] = e * (wi[i] + c0 * ni[i] + c1 * p1i[i] + c2 * p2i[i]); + p2r[i] = e * p1r[i]; + p2i[i] = e * p1i[i]; + p1r[i] = e * nr[i]; + p1i[i] = e * ni[i]; + } + S.h2 = h1; + S.h1 = h; +} + +// A fresh field: white noise filtered to a band round k0, scaled to an rms +// vorticity of 1. +function seed(S) { + const { wr, wi, k2, mask } = S, k0 = S.k0; + for (let i = 0; i < N; i++) (wr[i] = Math.random() * 2 - 1), (wi[i] = 0); + fft2(S, wr, wi, -1); + let z = 0; + for (let i = 0; i < N; i++) { + const q = k2[i] / (k0 * k0), a = mask[i] && k2[i] > 0 ? q * exp(-q) : 0; + wr[i] *= a; + wi[i] *= a; + z += wr[i] * wr[i] + wi[i] * wi[i]; + } + const g = N / sqrt(z); + for (let i = 0; i < N; i++) (wr[i] *= g), (wi[i] *= g); + S.age = 0; + S.h1 = S.h2 = 0; + S.sk = S.scale; + S.runs++; +} + +// The energy-weighted mean wavenumber: sum k |w^|^2 / k^2 over sum |w^|^2 / k^2. +function kmean(S) { + const { wr, wi, ik2, kk } = S; + let e = 0, ek = 0; + for (let i = 0; i < N; i++) { + const a = (wr[i] * wr[i] + wi[i] * wi[i]) * ik2[i]; + e += a; + ek += a * kk[i]; + } + return e > 0 ? ek / e : 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; +} + +// Four tables from |w| / scale to colour, two for each sign of spin, at two +// hues the stream function blends between. Weak vorticity is deep crimson on +// one side and indigo on the other, so it does not sink to brown and navy; +// strong vorticity climbs to amber and to sky blue, and the cores to white. +function palette(S) { + if (S.palHue === S.hue) return; + S.palHue = S.hue; + const c = [0, 0, 0]; + for (let s = 0; s < 4; s++) { + const T = S.pals[s], neg = s & 1, h0 = S.hue + (neg ? WARM + 0.47 : WARM) + ((s & 2 ? 1 : -1) * TINT) / 2; + for (let j = 0; j < LUT; j++) { + const a = j / (LUT - 1), d = BDEP * cl(a / 0.15, 0, 1); + const b = pow(a, GAM) * (1 - d + d * cos(2 * PI * BANDS * a)); + hsv(h0 + (neg ? 0.15 : -0.11) * (1 - a), 1 - 0.55 * a * a, 1, c, 0); + T[j * 3] = 255 * b * c[0]; + T[j * 3 + 1] = 255 * b * c[1]; + T[j * 3 + 2] = 255 * b * c[2]; + } + } +} + +export function setup(params) { + const F = () => new Float64Array(N); + const S = (params.s = { + hue: 0, speed: 2, scale: 0, visc: 0.06, nu: NU, k0: 5, sk: 0, t: 0, age: 0, runs: 0, h1: 0, h2: 0, fade: 0, vmax: 0.4, vs: 0.4, km: 0, + px: plan(W), py: plan(H), + kx: F(), ky: F(), k2: F(), kk: F(), ik2: F(), lin: F(), mask: new Uint8Array(N), neg: new Int32Array(N), + wr: F(), wi: F(), nr: F(), ni: F(), p1r: F(), p1i: F(), p2r: F(), p2i: F(), ar: F(), ai: F(), br: F(), bi: F(), + wm: 0, pm: 0, Ec: new Map(), + pals: [0, 1, 2, 3].map(() => new Float32Array(LUT * 3)), palHue: -1, + old: new Uint8Array(N * 3), cur: new Uint8Array(N * 3), wsc: 0, psc: 0, + }); + for (let y = 0; y < H; y++) + for (let x = 0; x < W; x++) { + const i = y * W + x, jx = x < W / 2 ? x : x - W, jy = y < H / 2 ? y : y - H; + S.kx[i] = jx; + S.ky[i] = jy / 2; + S.k2[i] = jx * jx + (jy * jy) / 4; + S.kk[i] = sqrt(S.k2[i]); + S.ik2[i] = S.k2[i] > 0 ? 1 / S.k2[i] : 0; + S.mask[i] = abs(jx) <= KXC && abs(jy) <= KYC ? 1 : 0; + S.neg[i] = ((H - y) % H) * W + ((W - x) % W); + const q = S.k2[i] / (KXC * KXC); + S.lin[i] = q * q * q * q; + } +} + +export function update(dt, input, params) { + if (!params.s) setup(params); + const S = params.s, k = input.knobValues || [0, 2, 0, 0.06]; + S.hue = cl(k[0], 0, 1); + S.speed = cl(k[1], 0.2, 3); + S.scale = cl(k[2], -1, 1); + S.visc = cl(k[3], 0, 1); + S.nu = NU * pow(2, 5 * S.visc); + S.k0 = 5 * pow(2, -0.8 * S.scale); + const ft = cl(dt || 0, 0, 0.1); + if (!S.runs) seed(S); + else if ((input.btnPressed && input.btnPressed[0]) || abs(S.scale - S.sk) > 0.15 || S.age > RUN || (S.age > 20 && (S.km = (S.km + 1) % 8) === 0 && kmean(S) < max(1, KEND * S.k0))) { + S.old.set(S.cur); + S.fade = 1; + seed(S); + } + S.fade = max(0, S.fade - ft / FADE); + let adv = (ft * S.speed * RATE) / 2; + if (adv <= 0) return; + // The speed limit holds the highest speed seen and lets it go slowly, so + // the step length changes smoothly from frame to frame. + S.vs = max(S.vmax, S.vs * 0.98); + const hmax = (CFL * DX) / max(1e-6, S.vs); + const n = min(S.speed > 2 ? 3 : 2, Math.ceil(adv / hmax)); + // Steps come from a ladder 16 to an octave, so the decay factors are + // worked out again only when the step moves up or down a rung. + const h = pow(2, floor(16 * log2(min(adv / n, hmax))) / 16); + adv = n * h; + for (let j = 0; j < n; j++) step(S, h); + S.t += adv; + S.age += adv; +} + +export function draw(display, params) { + const S = params.s; + if (!S || !S.runs) return; + palette(S); + const { br: om, bi: ps, pals, cur, old } = S, m = S.wm, mp = S.pm; + // The scales follow the field slowly, so a merger does not flash. + S.wsc = S.wsc ? S.wsc + (m - S.wsc) * 0.05 : m; + S.psc = S.psc ? S.psc + (mp - S.psc) * 0.05 : mp; + const sc = (LUT - 1) / max(1e-6, S.wsc * NORM), tp = 0.5 / max(1e-9, S.psc); + const f = S.fade, g = 1 - f; + for (let y = 0; y < H; y++) + for (let x = 0; x < W; x++) { + const i = y * W + x, w = om[i], j = min(LUT - 1, round(abs(w) * sc)) * 3, s = w > 0 ? 0 : 1, A = pals[s], B = pals[s + 2], o = i * 3; + const t = cl(0.5 + ps[i] * tp, 0, 1), u = 1 - t; + cur[o] = A[j] * u + B[j] * t; + cur[o + 1] = A[j + 1] * u + B[j + 1] * t; + cur[o + 2] = A[j + 2] * u + B[j + 2] * t; + if (f > 0) display.setPixel(x, y, cur[o] * g + old[o] * f, cur[o + 1] * g + old[o + 1] * f, cur[o + 2] * g + old[o + 2] * f); + else display.setPixel(x, y, cur[o], cur[o + 1], cur[o + 2]); + } +} From 4193192c1cbafc3fb2de501a6ddfe1b747d2d61e Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 25 Oct 2026 22:08:56 +0000 Subject: [PATCH 15/25] =?UTF-8?q?=ED=8C=A8=ED=84=B4:=20Risset=20Rhythm=20?= =?UTF-8?q?=EC=9D=84=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_011p1aG8xetifag8g82xnh6r --- .../pattern-study/patterns/risset-rhythm.js | 147 ++++++++++++++++++ 1 file changed, 147 insertions(+) create mode 100644 incoming/pattern-study/patterns/risset-rhythm.js diff --git a/incoming/pattern-study/patterns/risset-rhythm.js b/incoming/pattern-study/patterns/risset-rhythm.js new file mode 100644 index 000000000..e0c3fe9cc --- /dev/null +++ b/incoming/pattern-study/patterns/risset-rhythm.js @@ -0,0 +1,147 @@ +// ===== Patternflow pattern ===== +// Title: Risset Rhythm +// Family: rhythm +// Idea: A binary ruler, its ticks longer the coarser their level, lies across five octaves of tempo and zooms out by two every four seconds towards a point just under the panel, so each lane of ticks strikes the playhead ever faster while the lanes slide towards the fast side under a fixed bell of light, and after each doubling the picture is exactly where it began. +// Author: patternflow-lab +// Date: 2026-10-25 +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Lineage: Risset's eternal accelerando (Jean-Claude Risset, 1970s), the rhythmic twin of Shepard tones (Roger Shepard, 1964), pattern round 50 +// Round: 50 +// =============================== +// @matrix 64x128 +// @knobs Hue=0..1, Speed=0.2..3, Spread=-1..1, Ratio=2..4 +// +// Knob 1: Hue. Turns the colours across the panel together; the strikes stay white. At 0 the slow lanes on the left are green, the middle azure and the fast lanes on the right violet; at 0.25 cyan, purple and orange; at 0.5 purple, red-orange and green; at 0.75 orange, green and blue. +// Knob 2: Speed. Every tempo doubles in 8 / Speed seconds: 4 at 2, 40 at 0.2 and 2.7 at 3. At 2 the ticks cross the playhead at 11 rows a second, and a lane strikes 0.23 times a second at the left edge, 1.3 at the middle and 7 at the right edge, each lane twice as often one doubling later, by which time it has slid one lane to the right. +// Knob 3: Spread. The width of the bell of light in octaves of tempo, 1.4 times 2 to the knob. At 0 the edge lanes glow at a fifth of the middle; at -1 (0.7 octaves) one lane carries the light and the edges are dark; at 1 (2.8 octaves) the edges keep two thirds and the hand-over from fast to slow shows plainly. +// Knob 4: Ratio. The tempo ratio between neighbouring layers, rounded to 2, 3 or 4: a binary, ternary or base-four ruler. Its lanes are 12.8, 20.3 and 25.6 LEDs wide, a doubling of tempo always being 12.8, and the picture repeats after one ratio's worth of tempo, every 4, 6.3 and 8 seconds at Speed 2. +// Buttons: K1 tap turns the accelerando into a ritardando and back: the zoom runs the other way, the ticks rise through the playhead, the lanes slide towards the slow side and every tempo halves in the time it took to double. + +const W = 64, H = 128, NP = W * H; +const { floor, ceil, min, max, exp, pow, log2, round, LN2 } = Math; +const cl = (v, a, b) => (v < a ? a : v > b ? b : v); + +// Across the panel runs log tempo: OCT LEDs to an octave, five octaves from +// the slowest layer on the left to the fastest on the right. The loudness +// bell is centred BELL octaves in from the left. +const OCT = W / 5, BELL = 2.4; +// The ruler's zero, the point the zoom closes on, sits 8 rows below the +// bottom edge; the playhead is row PLAY. The layer at the left edge has its +// ticks SP0 rows apart. +const Y0 = H + 8, PLAY = 72, SP0 = 48; +// Seconds for every tempo to double at Speed 2, and how long a hit glows. +const T2 = 4, GLOW = 0.12; +// The light of a played tick against one to come, the rows the white of a +// tick crossing the playhead takes to fade, the halo of a hit and its reach +// in rows, and the faint light of every other lane. +const PAST = 0.6, FLASH = 3, HALO = 0.45, HR = 12, LANE = 0.12; + +export function setup(params) { + params.s = { + t: 0, dir: 1, hue: 0, speed: 2, spread: 1.1, r: 2, + buf: new Float32Array(NP * 3), bell: new Float32Array(W), cr: new Float32Array(W), cg: new Float32Array(W), cb: new Float32Array(W), + }; +} + +export function update(dt, input, params) { + if (!params.s) setup(params); + const S = params.s, k = input.knobValues || [0, 2, 0, 2]; + S.hue = cl(k[0], 0, 1); + S.speed = cl(k[1], 0.2, 3); + S.spread = 1.4 * pow(2, cl(k[2], -1, 1)); + S.r = round(cl(k[3], 2, 4)); + if (input.btnPressed && input.btnPressed[0]) S.dir = -S.dir; + // t counts doublings: every tempo is 2^t times what it was at the start. + S.t += (S.dir * cl(dt || 0, 0, 0.1) * S.speed) / (2 * T2); +} + +function hsv(h, s, v, o, 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][o] = i === 0 || i === 5 ? v : i === 1 ? q : i === 4 ? t : p; + out[1][o] = i === 1 || i === 2 ? v : i === 0 ? t : i === 3 ? q : p; + out[2][o] = i === 3 || i === 4 ? v : i === 2 ? t : i === 5 ? q : p; +} + +export function draw(display, params) { + const S = params.s; + if (!S) return; + const { buf, bell, cr, cg, cb } = S; + buf.fill(0); + // The bell is fixed in log tempo: each column's loudness, and its colour. + for (let x = 0; x < W; x++) { + const u = (x + 0.5) / OCT - BELL; + bell[x] = exp((-u * u) / (2 * S.spread * S.spread)); + hsv(S.hue + 0.3 + (0.55 * x) / W, 0.8, 1, x, [cr, cg, cb]); + } + // One period of the picture is log2(r) doublings: then every layer has + // become the next one. + const r = S.r, per = log2(r), lw = OCT * per; + const z = S.t / per, n = floor(z), p = z - n; + // Real seconds per doubling at this speed, for the glow of a hit. + const Td = (2 * T2) / S.speed; + // Bars just past the playhead burn white, fading over FLASH rows. + const sgn = S.dir > 0 ? 1 : -1; + for (let j = -2; (j + p) * lw < W; j++) { + const x0 = max(0, floor((j + p) * lw + 0.5)), x1 = min(W, floor((j + 1 + p) * lw + 0.5)); + if (x1 <= x0) continue; + const sp = SP0 * pow(r, -(j + p)), th = cl(sp / 4, 0.6, 2.2); + // Ticks: every multiple of sp above the ruler's zero. + const m0 = max(0, ceil((Y0 - H - 2) / sp)), m1 = floor((Y0 + 2) / sp); + if (sp >= 1.2) { + for (let m = m0; m <= m1; m++) { + // Played ticks, past the playhead, are dimmer than the ones to come. + const y = Y0 - m * sp, d = sgn * (y - PLAY); + bar(buf, y, th, x0, x1, d > 0 ? PAST : 1, d > -0.5 ? exp(-max(0, d) / FLASH) : 0, bell, cr, cg, cb); + } + } else { + for (let y = 0; y < H; y++) line(buf, y, x0, x1, min(1, 0.8 / sp) * (sgn * (y - PLAY) > 0 ? PAST : 1), 0, bell, cr, cg, cb); + } + // A hit: the playhead stands phi tick spacings above zero, so the layer + // strikes whenever phi passes a whole number, phi ln2 times a doubling. + const phi = (Y0 - PLAY) / sp, fr = phi - floor(phi); + const age = ((S.dir > 0 ? fr : 1 - fr) * Td) / (phi * LN2); + const g = exp(-age / GLOW); + for (let dy = -HR; dy <= HR; dy++) { + const f = 1 - Math.abs(dy) / (HR + 1); + line(buf, PLAY + dy, x0, x1, HALO * g * f * f, 0.3 * HALO * g * f * f * f, bell, cr, cg, cb); + } + } + // Each lane is faintly lit, with a dark seam at its edges, so the lanes + // show sliding towards the fast side. + for (let x = 0; x < W; x++) { + const u = (x + 0.5) / lw - p, e = (u - floor(u)) * lw, v = LANE * min(1, e / 1.5, (lw - e) / 1.5); + for (let y = 0; y < H; y++) line(buf, y, x, x + 1, v, 0, bell, cr, cg, cb); + } + // The playhead. + for (let x = 0, o = 3 * PLAY * W; x < W; x++, o += 3) (buf[o] += 0.16), (buf[o + 1] += 0.16), (buf[o + 2] += 0.16); + for (let p = 0, y = 0; y < H; y++) + for (let x = 0; x < W; x++, p++) { + const o = 3 * p; + display.setPixel(x, y, q4(buf[o]), q4(buf[o + 1]), q4(buf[o + 2])); + } +} + +// A bar th rows thick centred on row y, spread over the rows it covers. +function bar(buf, y, th, x0, x1, v, w, bell, cr, cg, cb) { + const top = y - th / 2, bot = y + th / 2; + for (let a = floor(top); a < bot; a++) { + const c = min(bot, a + 1) - max(top, a); + if (c > 0) line(buf, a, x0, x1, v * c, w * c, bell, cr, cg, cb); + } +} + +// Adds colour v and white w along row y from x0 to x1, each column weighted +// by the bell. +function line(buf, y, x0, x1, v, w, bell, cr, cg, cb) { + if (y < 0 || y >= H) return; + for (let x = x0, o = 3 * (y * W + x0); x < x1; x++, o += 3) { + const b = bell[x], c = v * b, e = w * b; + buf[o] += c * cr[x] + e; + buf[o + 1] += c * cg[x] + e; + buf[o + 2] += c * cb[x] + e; + } +} + +// Channels in steps of 4 of 255, from 0..1. +const q4 = (v) => (v >= 1 ? 252 : ((v * 63 + 0.5) | 0) << 2); From ecae7c94d198cef8c2b1de3503de58435d059683 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 25 Oct 2026 22:10:11 +0000 Subject: [PATCH 16/25] =?UTF-8?q?=ED=8C=A8=ED=84=B4:=20Diffusion=20Front?= =?UTF-8?q?=20=EB=A5=BC=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_011p1aG8xetifag8g82xnh6r --- .../pattern-study/patterns/diffusion-front.js | 181 ++++++++++++++++++ 1 file changed, 181 insertions(+) create mode 100644 incoming/pattern-study/patterns/diffusion-front.js diff --git a/incoming/pattern-study/patterns/diffusion-front.js b/incoming/pattern-study/patterns/diffusion-front.js new file mode 100644 index 000000000..008ed3407 --- /dev/null +++ b/incoming/pattern-study/patterns/diffusion-front.js @@ -0,0 +1,181 @@ +// ===== Patternflow pattern ===== +// Title: Diffusion Front +// Family: fractal +// Idea: Every LED is a lattice site with a fixed random number and is occupied while that number is below the concentration of a dye diffusing up from the bottom row, erfc(h / 2 sqrt(Dt)); the occupied sites joined to the source glow pale cyan to deep blue with the concentration, a site that has just joined flashes white, islands not joined glow violet, and the cluster's outer boundary, the diffusion front, is drawn gold: a fractal hull that climbs as the square root of time and sits where the concentration is 0.5927, the percolation threshold, until the source shuts, the cluster is cut from it, the dye drains out and a fresh lattice starts. +// Author: patternflow-lab +// Date: 2026-10-25 +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Lineage: diffusion fronts and gradient percolation (Bernard Sapoval, Michel Rosso and Jean-François Gouyet, 1985), pattern round 50 +// Round: 50 +// =============================== +// @matrix 64x128 +// @knobs Hue=0..1, Speed=0.2..3, Hop=0..1, Islands=0..1 +// +// Knob 1: Hue. Turns every colour round the wheel together. At 0 the cluster runs from pale cyan at the front to deep blue at the source, the front is gold and the islands violet; at 0.25 the cluster runs from grey violet to magenta, the front is mint and the islands brick red; at 0.5 the cluster runs from brown to amber, the front is pale blue and the islands olive; at 0.75 the cluster is green, the front pink and the islands teal. +// Knob 2: Speed. At 2 the front is 28 rows up after a quarter of a second, 55 after one second, 78 after two and 121 after 4.8, as the square root of time; it holds half a second, the source shuts and the dye drains for a second, so a run lasts 6.3 seconds. At 0.2 everything is ten times slower, at 3 half as fast again. +// Knob 3: Hop. How often each site draws a new random number. At 0 the lattice is frozen, the gradient percolation of one fixed lattice, and the front only climbs; at 0.5 each site draws again once a second on average and at 1 twice, so the occupied sites flicker like hopping particles, islands join the cluster and fall away, and the front shivers as it climbs. +// Knob 4: Islands. How bright the occupied sites that are not joined to the source glow. At 0 they are dark and only the cluster joined to the source shows; at the opening 0.06 they glow violet at 113 of 255, at 0.25 at 196 and at 1 at 237, nearly as bright as the front. +// Buttons: K1 tap starts a fresh lattice. + +const W = 64, H = 128, N = W * H; +const { floor, min, max, exp, sqrt, abs, pow, round } = Math; +const cl = (v, a, b) => (v < a ? a : v > b ? b : v); + +// erfc(x) to 1.2e-7 (Numerical Recipes' Chebyshev fit). +function erfc(x) { + const z = abs(x), t = 1 / (1 + 0.5 * z); + const r = t * exp(-z * z - 1.26551223 + t * (1.00002368 + t * (0.37409196 + t * (0.09678418 + t * (-0.18628806 + t * (0.27886807 + t * (-1.13520398 + t * (1.48851587 + t * (-0.82215223 + t * 0.17087277))))))))); + return x >= 0 ? r : 2 - r; +} + +// The front sits where p = 0.5927, at h = 0.3778 L with L = 2 sqrt(D t). +// A run grows L from L0 to L1 in GROW seconds (at Speed 2) with L^2 growing +// evenly in time, holds, shuts the source and drains for DRAIN seconds, then +// starts again on a fresh lattice. +const L1 = 320, L0 = 10, GROW = 4.8, HOLD = 0.5, DRAIN = 1.0, PC = 0.5927; + +export function setup(params) { + params.s = { + hue: 0, speed: 2, hop: 0, isl: 0.06, c: 0, ph: 0, L: L0, runs: 0, + u: new Float32Array(N), occ: new Uint8Array(N), A: new Uint8Array(N), B: new Uint8Array(N), hull: new Uint8Array(N), + join: new Float32Array(N), q: new Int32Array(N), p: new Float64Array(H), glow: new Float32Array(N * 3), ease: 1, + nA: 0, nHull: 0, hf: 0, + }; + fresh(params.s); +} + +function fresh(S) { + for (let i = 0; i < N; i++) S.u[i] = Math.random(); + S.join.fill(-1); + S.c = 0; + S.ph = 0; + S.runs++; +} + +// Occupancy from p(h), the cluster joined to the source row (4-neighbour), +// the empty sites joined to the open top (8-neighbour), and the hull: the +// cluster's sites that touch that open emptiness. +function percolate(S) { + const { u, occ, A, B, hull, q, p } = S; + for (let y = 0; y < H; y++) { + const py = p[y], o = y * W; + for (let x = 0; x < W; x++) occ[o + x] = u[o + x] < py ? 1 : 0; + } + A.fill(0); + let t = 0; + for (let x = 0, o = (H - 1) * W; x < W; x++) if (occ[o + x]) (A[o + x] = 1), (q[t++] = o + x); + for (let k = 0; k < t; k++) { + const i = q[k], x = i % W; + if (x > 0 && occ[i - 1] && !A[i - 1]) (A[i - 1] = 1), (q[t++] = i - 1); + if (x < W - 1 && occ[i + 1] && !A[i + 1]) (A[i + 1] = 1), (q[t++] = i + 1); + if (i >= W && occ[i - W] && !A[i - W]) (A[i - W] = 1), (q[t++] = i - W); + if (i + W < N && occ[i + W] && !A[i + W]) (A[i + W] = 1), (q[t++] = i + W); + } + S.nA = t; + B.fill(0); + t = 0; + for (let x = 0; x < W; x++) if (!occ[x]) (B[x] = 1), (q[t++] = x); + for (let k = 0; k < t; k++) { + const i = q[k], x = i % W, y = (i - x) / W; + for (let dy = -1; dy <= 1; dy++) { + const yy = y + dy; + if (yy < 0 || yy >= H) continue; + for (let dx = -1; dx <= 1; dx++) { + const xx = x + dx; + if (xx < 0 || xx >= W) continue; + const j = yy * W + xx; + if (!occ[j] && !B[j]) (B[j] = 1), (q[t++] = j); + } + } + } + let nh = 0, sh = 0; + for (let i = 0; i < N; i++) { + let f = 0; + if (A[i]) { + const x = i % W; + f = i < W || (x > 0 && B[i - 1]) || (x < W - 1 && B[i + 1]) || B[i - W] || (i + W < N && B[i + W]) ? 1 : 0; + if (f) (nh++), (sh += H - 1 - (i - x) / W); + } + hull[i] = f; + } + S.nHull = nh; + S.hf = nh ? sh / nh : 0; +} + +export function update(dt, input, params) { + if (!params.s) setup(params); + const S = params.s, k = input.knobValues || [0, 2, 0, 0.06]; + S.hue = cl(k[0], 0, 1); + S.speed = cl(k[1], 0.2, 3); + S.hop = cl(k[2], 0, 1); + S.isl = cl(k[3], 0, 1); + const d = (cl(dt || 0, 0, 0.1) * S.speed) / 2; + S.ease = 1 - exp(-d / 0.08); + if (input.btnPressed && input.btnPressed[0]) fresh(S); + S.c += d; + if (S.c > GROW + HOLD + DRAIN) fresh(S); + // L^2 = 4 D t grows evenly; once the source shuts at GROW + HOLD, the + // profile is the same erfc less one started at the moment of shutting + // (the source row held at zero from then on), so the source row empties + // at once and the rest drains out through it. + const K = (L1 * L1 - L0 * L0) / GROW, ts = GROW + HOLD; + S.L = sqrt(L0 * L0 + K * S.c); + const Ls = S.c > ts ? sqrt(K * (S.c - ts)) : 0; + for (let y = 0; y < H; y++) { + const h = H - 1 - y; + S.p[y] = erfc(h / S.L) - (Ls > 0 ? erfc(h / Ls) : 0); + } + // Hop: each site draws a new random number at this rate a second. + if (S.hop > 0) { + const r = 1 - exp(-2 * S.hop * d); + for (let i = 0; i < N; i++) if (Math.random() < r) S.u[i] = Math.random(); + } + percolate(S); + const { A, join } = S; + for (let i = 0; i < N; i++) { + if (A[i]) { + if (join[i] < 0) join[i] = S.c; + } else join[i] = -1; + } +} + +function hsv(h, s, v) { + 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); + return i === 0 ? [v, t, p] : i === 1 ? [q, v, p] : i === 2 ? [p, v, t] : i === 3 ? [p, q, v] : i === 4 ? [t, p, v] : [v, p, q]; +} + +export function draw(display, params) { + const S = params.s; + if (!S) return; + const { occ, A, hull, join, p, glow } = S; + const hu = S.hue; + const front = hsv(hu + 0.12, 0.35, 1), flash = hsv(hu + 0.5, 0.05, 1), isl = hsv(hu + 0.78, 0.7, 1); + const il = (255 * S.isl) / (S.isl + 0.075); + for (let y = 0; y < H; y++) { + // The cluster is lit by the concentration where it stands: dim at the + // front, where p is near the threshold, bright near the source. + const sy = pow(cl((p[y] - PC) / (1 - PC), 0, 1), 0.8), lv = 255 * (0.45 + 0.55 * sy); + const c = hsv(hu + 0.5 + 0.12 * sy, 0.4 + 0.5 * sy, 1); + for (let x = 0; x < W; x++) { + const i = y * W + x; + let r = 0, g = 0, b = 0; + if (hull[i]) (r = 255 * front[0]), (g = 255 * front[1]), (b = 255 * front[2]); + else if (A[i]) { + // A site that has just joined flashes pale and settles in half a second. + const a = cl((S.c - join[i]) / 0.5, 0, 1), v = 255 * (1 - a), w = lv * a; + r = flash[0] * v + c[0] * w; + g = flash[1] * v + c[1] * w; + b = flash[2] * v + c[2] * w; + } else if (occ[i]) (r = il * isl[0]), (g = il * isl[1]), (b = il * isl[2]); + // Each LED eases towards its colour in about a tenth of a second, so a + // cluster cut from the source sinks to island colour instead of + // switching. + const o = i * 3, e = S.ease; + glow[o] += (r - glow[o]) * e; + glow[o + 1] += (g - glow[o + 1]) * e; + glow[o + 2] += (b - glow[o + 2]) * e; + display.setPixel(x, y, glow[o], glow[o + 1], glow[o + 2]); + } + } +} From e621dd558eb8c7c078e02eff821aa4e12d43b28f Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 25 Oct 2026 22:11:10 +0000 Subject: [PATCH 17/25] =?UTF-8?q?=ED=8C=A8=ED=84=B4:=20Diffusion=20Front?= =?UTF-8?q?=20=EC=9D=98=20=EC=93=B0=EC=A7=80=20=EC=95=8A=EB=8A=94=20?= =?UTF-8?q?=EA=B0=92=EC=9D=84=20=EB=8D=9C=EC=96=B4=20=EB=82=B8=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_011p1aG8xetifag8g82xnh6r --- incoming/pattern-study/patterns/diffusion-front.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/incoming/pattern-study/patterns/diffusion-front.js b/incoming/pattern-study/patterns/diffusion-front.js index 008ed3407..1935982fc 100644 --- a/incoming/pattern-study/patterns/diffusion-front.js +++ b/incoming/pattern-study/patterns/diffusion-front.js @@ -18,7 +18,7 @@ // Buttons: K1 tap starts a fresh lattice. const W = 64, H = 128, N = W * H; -const { floor, min, max, exp, sqrt, abs, pow, round } = Math; +const { floor, exp, sqrt, abs, pow } = Math; const cl = (v, a, b) => (v < a ? a : v > b ? b : v); // erfc(x) to 1.2e-7 (Numerical Recipes' Chebyshev fit). @@ -36,10 +36,12 @@ const L1 = 320, L0 = 10, GROW = 4.8, HOLD = 0.5, DRAIN = 1.0, PC = 0.5927; export function setup(params) { params.s = { - hue: 0, speed: 2, hop: 0, isl: 0.06, c: 0, ph: 0, L: L0, runs: 0, + hue: 0, speed: 2, hop: 0, isl: 0.06, c: 0, L: L0, runs: 0, u: new Float32Array(N), occ: new Uint8Array(N), A: new Uint8Array(N), B: new Uint8Array(N), hull: new Uint8Array(N), join: new Float32Array(N), q: new Int32Array(N), p: new Float64Array(H), glow: new Float32Array(N * 3), ease: 1, - nA: 0, nHull: 0, hf: 0, + // Front sites and their mean height in rows, kept for checking the + // front against the threshold. + nHull: 0, hf: 0, }; fresh(params.s); } @@ -48,7 +50,6 @@ function fresh(S) { for (let i = 0; i < N; i++) S.u[i] = Math.random(); S.join.fill(-1); S.c = 0; - S.ph = 0; S.runs++; } @@ -71,7 +72,6 @@ function percolate(S) { if (i >= W && occ[i - W] && !A[i - W]) (A[i - W] = 1), (q[t++] = i - W); if (i + W < N && occ[i + W] && !A[i + W]) (A[i + W] = 1), (q[t++] = i + W); } - S.nA = t; B.fill(0); t = 0; for (let x = 0; x < W; x++) if (!occ[x]) (B[x] = 1), (q[t++] = x); From aa820e92ce2ed9168284736f0d718560a86cff27 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 25 Oct 2026 22:12:16 +0000 Subject: [PATCH 18/25] =?UTF-8?q?=ED=8C=A8=ED=84=B4:=20Risset=20Rhythm=20?= =?UTF-8?q?=EC=9D=98=20=EC=B2=98=EC=9D=8C=20=EC=A2=85=20=ED=8F=AD=20?= =?UTF-8?q?=EA=B0=92=EC=9D=84=20=EB=85=B8=EB=B8=8C=EC=99=80=20=EB=A7=9E?= =?UTF-8?q?=EC=B6=98=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_011p1aG8xetifag8g82xnh6r --- incoming/pattern-study/patterns/risset-rhythm.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/incoming/pattern-study/patterns/risset-rhythm.js b/incoming/pattern-study/patterns/risset-rhythm.js index e0c3fe9cc..8dd7c4b64 100644 --- a/incoming/pattern-study/patterns/risset-rhythm.js +++ b/incoming/pattern-study/patterns/risset-rhythm.js @@ -38,7 +38,7 @@ const PAST = 0.6, FLASH = 3, HALO = 0.45, HR = 12, LANE = 0.12; export function setup(params) { params.s = { - t: 0, dir: 1, hue: 0, speed: 2, spread: 1.1, r: 2, + t: 0, dir: 1, hue: 0, speed: 2, spread: 1.4, r: 2, buf: new Float32Array(NP * 3), bell: new Float32Array(W), cr: new Float32Array(W), cg: new Float32Array(W), cb: new Float32Array(W), }; } From 76813df0a196be596195d8e1a6a813f021e8422f Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 25 Oct 2026 22:12:23 +0000 Subject: [PATCH 19/25] =?UTF-8?q?=ED=8C=A8=ED=84=B4:=20Inverse=20Cascade?= =?UTF-8?q?=20=EC=9D=98=20=EC=8A=A4=ED=8E=99=ED=8A=B8=EB=9F=BC=20=EA=B3=A0?= =?UTF-8?q?=EB=A6=AC=EB=A5=BC=202/3=20=EC=95=88=20=EB=AA=A8=EB=93=9C?= =?UTF-8?q?=EB=A1=9C=20=EC=A4=84=EC=9D=B8=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_011p1aG8xetifag8g82xnh6r --- .../pattern-study/patterns/inverse-cascade.js | 31 ++++++++++--------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/incoming/pattern-study/patterns/inverse-cascade.js b/incoming/pattern-study/patterns/inverse-cascade.js index f9ab35eb9..8b917639d 100644 --- a/incoming/pattern-study/patterns/inverse-cascade.js +++ b/incoming/pattern-study/patterns/inverse-cascade.js @@ -97,9 +97,11 @@ function fft2(S, re, im, sign) { // apart with the conjugate mode. Leaves w and psi on the grid in br, bi // (and their largest sizes in wm, pm) and returns the largest |u| + |v|. function tendency(S, sr, si, or, oi) { - const { kx, ky, ik2, mask, neg, ar, ai, br: om, bi: ps } = S; - for (let i = 0; i < N; i++) { - const r = sr[i] / N, m = si[i] / N, q = ik2[i]; + const { kx, ky, ik2, keep, neg, ar, ai, br: om, bi: ps } = S, nk = keep.length; + om.fill(0); + ps.fill(0); + for (let t = 0; t < nk; t++) { + const i = keep[t], r = sr[i] / N, m = si[i] / N, q = ik2[i]; om[i] = r + q * m; ps[i] = m - q * r; } @@ -126,12 +128,8 @@ function tendency(S, sr, si, or, oi) { S.wm = wm; S.pm = pm; fft2(S, ar, ai, -1); - for (let i = 0; i < N; i++) { - if (!mask[i]) { - or[i] = oi[i] = 0; - continue; - } - const j = neg[i], cr = ar[i], ci = ai[i], dr = ar[j], di = ai[j], a = kx[i], b = ky[i]; + for (let t = 0; t < nk; t++) { + const i = keep[t], j = neg[i], cr = ar[i], ci = ai[i], dr = ar[j], di = ai[j], a = kx[i], b = ky[i]; or[i] = 0.5 * (a * (ci - di) - b * (cr - dr)); oi[i] = -0.5 * (a * (cr + dr) + b * (ci + di)); } @@ -165,8 +163,8 @@ function step(S, h) { c1 = -(a + (h1 + h2) * b) / (h1 * h2); c2 = (a + h1 * b) / ((h1 + h2) * h2); } else if (h1) (c0 = h * (1 + h / (2 * h1))), (c1 = -(h * h) / (2 * h1)); - for (let i = 0; i < N; i++) { - const e = E[i]; + for (let t = 0, keep = S.keep, nk = keep.length; t < nk; t++) { + const i = keep[t], e = E[i]; wr[i] = e * (wr[i] + c0 * nr[i] + c1 * p1r[i] + c2 * p2r[i]); wi[i] = e * (wi[i] + c0 * ni[i] + c1 * p1i[i] + c2 * p2i[i]); p2r[i] = e * p1r[i]; @@ -201,10 +199,10 @@ function seed(S) { // The energy-weighted mean wavenumber: sum k |w^|^2 / k^2 over sum |w^|^2 / k^2. function kmean(S) { - const { wr, wi, ik2, kk } = S; + const { wr, wi, ik2, kk, keep } = S; let e = 0, ek = 0; - for (let i = 0; i < N; i++) { - const a = (wr[i] * wr[i] + wi[i] * wi[i]) * ik2[i]; + for (let t = 0; t < keep.length; t++) { + const i = keep[t], a = (wr[i] * wr[i] + wi[i] * wi[i]) * ik2[i]; e += a; ek += a * kk[i]; } @@ -264,6 +262,11 @@ export function setup(params) { const q = S.k2[i] / (KXC * KXC); S.lin[i] = q * q * q * q; } + // The modes inside the two-thirds cut, the only ones that are ever + // non-zero: the spectral loops run over these 3,655 alone. + const keep = []; + for (let i = 0; i < N; i++) if (S.mask[i]) keep.push(i); + S.keep = Int32Array.from(keep); } export function update(dt, input, params) { From 8ab3a53e09018fd63b2b8dce17173a25fb5f9dc7 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 25 Oct 2026 22:12:39 +0000 Subject: [PATCH 20/25] =?UTF-8?q?=ED=8C=A8=ED=84=B4:=20Risset=20Rhythm=20?= =?UTF-8?q?=EC=9D=98=20Hue=20=EC=84=A4=EB=AA=85=EC=9D=84=20=EC=8B=A4?= =?UTF-8?q?=EC=A0=9C=20=EC=83=89=EC=97=90=20=EB=A7=9E=EC=B6=98=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_011p1aG8xetifag8g82xnh6r --- incoming/pattern-study/patterns/risset-rhythm.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/incoming/pattern-study/patterns/risset-rhythm.js b/incoming/pattern-study/patterns/risset-rhythm.js index 8dd7c4b64..dde6b580c 100644 --- a/incoming/pattern-study/patterns/risset-rhythm.js +++ b/incoming/pattern-study/patterns/risset-rhythm.js @@ -11,7 +11,7 @@ // @matrix 64x128 // @knobs Hue=0..1, Speed=0.2..3, Spread=-1..1, Ratio=2..4 // -// Knob 1: Hue. Turns the colours across the panel together; the strikes stay white. At 0 the slow lanes on the left are green, the middle azure and the fast lanes on the right violet; at 0.25 cyan, purple and orange; at 0.5 purple, red-orange and green; at 0.75 orange, green and blue. +// Knob 1: Hue. Turns the colours across the panel together; the strikes stay white. At 0 the slow lanes on the left are green, the middle azure and the fast lanes on the right violet; at 0.25 blue, magenta and red; at 0.5 violet, orange and yellow-green; at 0.75 yellow, green and teal. // Knob 2: Speed. Every tempo doubles in 8 / Speed seconds: 4 at 2, 40 at 0.2 and 2.7 at 3. At 2 the ticks cross the playhead at 11 rows a second, and a lane strikes 0.23 times a second at the left edge, 1.3 at the middle and 7 at the right edge, each lane twice as often one doubling later, by which time it has slid one lane to the right. // Knob 3: Spread. The width of the bell of light in octaves of tempo, 1.4 times 2 to the knob. At 0 the edge lanes glow at a fifth of the middle; at -1 (0.7 octaves) one lane carries the light and the edges are dark; at 1 (2.8 octaves) the edges keep two thirds and the hand-over from fast to slow shows plainly. // Knob 4: Ratio. The tempo ratio between neighbouring layers, rounded to 2, 3 or 4: a binary, ternary or base-four ruler. Its lanes are 12.8, 20.3 and 25.6 LEDs wide, a doubling of tempo always being 12.8, and the picture repeats after one ratio's worth of tempo, every 4, 6.3 and 8 seconds at Speed 2. From f0b13fd60c19739506596793367d1e951a31d6e1 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 25 Oct 2026 22:12:48 +0000 Subject: [PATCH 21/25] =?UTF-8?q?=ED=8C=A8=ED=84=B4:=20Motility=20Clusters?= =?UTF-8?q?=20=EC=9D=98=20=EA=B0=80=EC=9E=A5=EC=9E=90=EB=A6=AC=20=EC=83=89?= =?UTF-8?q?=20=EC=84=A4=EB=AA=85=EC=9D=84=20=EA=B3=A0=EC=B9=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_011p1aG8xetifag8g82xnh6r --- incoming/pattern-study/patterns/motility-clusters.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/incoming/pattern-study/patterns/motility-clusters.js b/incoming/pattern-study/patterns/motility-clusters.js index a9d090c9a..66201322f 100644 --- a/incoming/pattern-study/patterns/motility-clusters.js +++ b/incoming/pattern-study/patterns/motility-clusters.js @@ -11,7 +11,7 @@ // @matrix 64x128 // @knobs Hue=0..1, Speed=0.2..3, Pe=-1..1, Packing=0.55..0.72 // -// Knob 1: Hue. Turns the heading wheel and the tint of the packed discs together. At 0 a disc swimming down is red, down-left yellow, up-left green, up cyan, up-right blue and down-right magenta, so a cluster's edge, where the discs face inward, runs red along its top, cyan along its bottom, violet on its left and yellow-green on its right; packed discs are pale lavender. At 0.25 the tint is pale cyan, at 0.5 pale lime and at 0.75 pale pink. +// Knob 1: Hue. Turns the heading wheel and the tint of the packed discs together. At 0 a disc swimming down is red, down-left yellow, up-left green, up cyan, up-right blue and down-right magenta, so a cluster's edge, where the discs face inward, runs pink-red along its top, cyan along its bottom, violet on its left and yellow-green on its right; packed discs are pale lavender. At 0.25 the tint is pale cyan, at 0.5 pale lime and at 0.75 pale pink. // Knob 2: Speed. At 2 the discs swim 20 LEDs, ten diameters, a second, at 0.2 a tenth of that and at 3 half as fast again; the turning and the clustering keep pace. The head start before the first frame is always 4 seconds of swimming. // Knob 3: Pe. The Peclet number v0 / (sigma Dr), the distance in diameters a disc swims before its heading forgets itself, 40 times 8 to the knob. At -1 (Pe 5, a heading lasts half a second at Speed 2) the discs stay a gas of brief clumps, the largest holding a tenth of them; at -0.5 (Pe 14) two fifths; at 0 (Pe 40, four seconds) two thirds after ten seconds, with a third already in one cluster 2.5 s in; at 1 (Pe 320) four fifths. // Knob 4: Packing. The share of the panel the discs would cover laid flat: 0.55 at the start (1,434 discs), where clusters sit in a gas, to 0.72 (1,877 discs), where the packed phase fills most of the panel and the gas lives in dark bubbles. New discs drop in at random places and taken ones leave from random places. From 4a05dd83aac214e58bede44603e616501ccd1812 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 25 Oct 2026 22:13:27 +0000 Subject: [PATCH 22/25] =?UTF-8?q?=ED=8C=A8=ED=84=B4:=20Inverse=20Cascade?= =?UTF-8?q?=20=EC=9D=98=20=EB=85=B8=EB=B8=8C=20=EC=84=A4=EB=AA=85=EC=9D=84?= =?UTF-8?q?=20=EC=9E=B0=20=EA=B0=92=EC=9C=BC=EB=A1=9C=20=EA=B3=A0=EC=B9=9C?= =?UTF-8?q?=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_011p1aG8xetifag8g82xnh6r --- incoming/pattern-study/patterns/inverse-cascade.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/incoming/pattern-study/patterns/inverse-cascade.js b/incoming/pattern-study/patterns/inverse-cascade.js index 8b917639d..5d6a5d2ee 100644 --- a/incoming/pattern-study/patterns/inverse-cascade.js +++ b/incoming/pattern-study/patterns/inverse-cascade.js @@ -12,8 +12,8 @@ // @knobs Hue=0..1, Speed=0.2..3, Scale=-1..1, Viscosity=0..1 // // Knob 1: Hue. Turns both colours round the wheel together, always half a turn apart. At 0 vorticity of one sign runs from deep crimson through red to amber and the white of a vortex core, and of the other from indigo through blue to sky blue and white; on the panel the warm vortices turn clockwise and the cool ones anticlockwise. At 0.25 lime and magenta, at 0.5 olive and cyan, at 0.75 green and violet. The stream function tints each side a little towards one end or the other of its own range. -// Knob 2: Speed. Flow time per second. At 2 the eddies gather into round vortices in about 2 seconds, about 80 vortices become 10 in 9 seconds, and a run ends with 5 to 8 after 13 to 17 seconds; at 1 half as fast, at 0.2 a tenth. Above 2 a frame may take a third step, and at 3 a run lasts about 8 seconds. When the flow is too fast for the steps a frame may take, it runs slower instead. -// Knob 3: Scale. The size of the eddies a run starts from: the noise is filtered round wavenumber 5 at 0, a wavelength of 13 LEDs, round 8.7 at -1 (7 LEDs) and 2.9 at 1 (22 LEDs). Bigger eddies start nearer the end, so a run is shorter. Turning it by more than 0.15 starts a fresh run at the new size. +// Knob 2: Speed. Flow time per second. At 2 the eddies gather into round vortices in about 2 seconds; some 60 vortices at 2 seconds are 12 at 8, and a run ends with 6 or 7 after 13 to 22 seconds. At 1 half as fast, at 0.2 a tenth. Above 2 a frame may take a third step, and at 3 a run lasts about 8 seconds. When the flow is too fast for the steps a frame may take, it runs slower instead. +// Knob 3: Scale. The size of the eddies a run starts from: the noise is filtered round wavenumber 5 at 0, a wavelength of 13 LEDs, round 8.7 at -1 (7 LEDs) and 2.9 at 1 (22 LEDs). At -1 a run lasts about 10 seconds; the big eddies at 1 carry so much energy that the step limit slows them, and a run lasts about 28. Turning it by more than 0.15 starts a fresh run at the new size. // Knob 4: Viscosity. The hyperviscosity that takes the enstrophy out at the bottom of its cascade, doubling with every fifth of the knob. At the opening 0.06 a filament two LEDs wide lasts about a tenth of a second and wider ones survive, so the vortices trail long spiral arms; at 1 it is 26 times stronger, filaments up to three LEDs wide dissolve as fast, and the vortices sit in smoother, emptier water. // Buttons: K1 tap starts a fresh run of small eddies. From b787fd699e6339f88df5af6fb84818844b2d4428 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 25 Oct 2026 22:14:58 +0000 Subject: [PATCH 23/25] =?UTF-8?q?=ED=8C=A8=ED=84=B4:=20Motility=20Clusters?= =?UTF-8?q?=20=EC=9D=98=20=EC=A3=BC=EC=84=9D=20=ED=95=9C=20=EC=A4=84?= =?UTF-8?q?=EC=9D=84=20=EA=B3=A0=EC=B9=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_011p1aG8xetifag8g82xnh6r --- incoming/pattern-study/patterns/motility-clusters.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/incoming/pattern-study/patterns/motility-clusters.js b/incoming/pattern-study/patterns/motility-clusters.js index 66201322f..d8f9db859 100644 --- a/incoming/pattern-study/patterns/motility-clusters.js +++ b/incoming/pattern-study/patterns/motility-clusters.js @@ -131,9 +131,9 @@ function swim(S, n, amp, d) { } } -// The first pass searches the grid, pushes, and lists every pair closer than -// SIG + 0.25; the later passes push only the listed pairs. Returns the list's -// length. +// The first pass searches the grid and pushes. It also lists every pair +// closer than SIG + 0.25, and the later passes push only those. Returns the +// list's length. function pushGrid(x, y, cnt, pl) { let m = 0; for (let cy = 0; cy < GY; cy++) { From 5f75ed72ae1bd986888ed4392bc926bb36e2d053 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 25 Oct 2026 22:21:17 +0000 Subject: [PATCH 24/25] =?UTF-8?q?=EC=97=B0=EA=B5=AC=20=EB=85=B8=ED=8A=B8?= =?UTF-8?q?=EC=97=90=2050=ED=8C=90=20=EC=97=B4=20=EA=B0=9C=EB=A5=BC=20?= =?UTF-8?q?=EC=A0=81=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_011p1aG8xetifag8g82xnh6r --- incoming/pattern-study/README.md | 81 ++++++++++++++++++++++++++++++-- 1 file changed, 77 insertions(+), 4 deletions(-) diff --git a/incoming/pattern-study/README.md b/incoming/pattern-study/README.md index d2a191305..98849c681 100644 --- a/incoming/pattern-study/README.md +++ b/incoming/pattern-study/README.md @@ -1,6 +1,6 @@ # 팹턮 연구 2026-10 -새 팹턮 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 칞에 붙여 넣윌멎 같은 귞늌읎 나와요. 페읎지는 파음 하나띌서 바깥에서 받아 였는 것읎 없얎요. +새 팹턮 398개륌 쉰여섯 갈래로 나눠 만듀었얎요. 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판도 같은 표로 돌아서 수학 대상 닀섯을 닀섯 갈래에 하나씩, 할로윈 귞늌 닀섯을 할로윈 갈래에 더했얎요. 50판도 같은 표로 돌아서 수학 대상 닀섯을 팚턎읎 적은 닀섯 갈래에 하나씩, 할로윈 밀에 틀 귞늌 닀섯을 할로윈 갈래에 더했얎요. [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판에서 섞로로 닀시 ì§°ì–Žìš”. @@ -630,6 +630,21 @@ | [Trick or Treat](patterns/trick-or-treat.js) | 할로윈 | 볎띌 하늘 아래 좁은 곚목읎 소싀점윌로 올띌가고, 홑읎불 유령곌 빗자룚 든 마녀, 고양읎 셋읎 손전등을 휘두륎며 걞얎가닀 녾란 빛읎 쏟아지는 묞 앞에서 사탕을 받고 점점 작아젞요. K3 은 불 쌠 집의 몫, K4 는 바람읎고, K1 을 누륎멎 불 쌠 집의 묞읎 엎렀요. | | [Grim Reaper](patterns/grim-reaper.js) | 할로윈 | 두 눈에 독한 쎈록 불을 쌠 두걎 사신읎 핎진 옷자띜을 날늬며 찜백한 볎늄달 앞을 떠가고, 뚞늬 위로 큰 낫을 휘두륌 때마닀 날읎 번뜩읎며, 지나가는 자늬의 묎덀 쎛불은 꺌졌닀가 등 뒀에서 닀시 쌜젞요. K3 은 묎덀 수, K4 는 안개읎고, K1 을 누륎멎 바로 휘둘러요. | +### 50판 + +| 팹턮 | 갈래 | 한 쀄 | +| --- | --- | --- | +| [Inverse Cascade](patterns/inverse-cascade.js) | 녞읎슈·흐늄 | 잔 소용돌읎 잡음읎 2쎈 만에 둥귌 소용돌읎로 몚읎고, 같은 쪜윌로 도는 것끌늬 감Ʞ며 합쳐젞 점점 적고 컀젞요. 붉은 쪜곌 낚색 쪜읎 도는 방향읎에요. K3 은 처음 소용돌읎의 크Ʞ, K4 는 가는 싀을 지우는 점성읎고, K1 을 누륎멎 새 잡음에서 닀시 시작핎요. | +| [Diffusion Front](patterns/diffusion-front.js) | 프랙탈 | 바닥에서 퍌젞 였륎는 엌료의 농도만큌 칞읎 ì°šê³ , 바닥에 읎얎진 덩얎늬의 ꞈ빛 프랙탈 앞쀄읎 슀믞Ʞ 묞턱 0.5927 의 자늬륌 따띌 √t 로 올띌갔닀가 바닥읎 닫히멎 가띌앉아요. K3 은 칞읎 난수륌 새로 뜑는 빠륎Ʞ, K4 는 읎얎지지 않은 섬의 밝Ʞ읎고, K1 을 누륎멎 새 격자륌 깔아요. | +| [Motility Clusters](patterns/motility-clusters.js) | 입자 | 슀슀로 헀엄치는 원판 1,434개가 밀얎낎Ʞ만 하는데도 서로 막혀 옅은 덩얎늬로 뭉치고, 안쪜을 볎는 가장자늬 원판읎 헀엄치는 방향의 색윌로 덩얎늬륌 두륎는 동안 흐늰 Ʞ첎가 ê·ž 사읎륌 달늬고 덩얎늬끌늬 합쳐젞요. K3 은 페큮레 수, K4 는 원판읎 덮는 몫읎고, K1 을 누륎멎 고륞 Ʞ첎로 흩얎요. | +| [Bak-Sneppen](patterns/bak-sneppen.js) | 데읎터 귞늌 | 종 64개의 적합도 막대에서 가장 낮은 것곌 양옆을 거듭 새로 뜑윌멎 막대의 바닥읎 저절로 0.62 까지 였륎고, 새 색의 사태가 가장 낮은 자늬륌 따띌 번지닀 멀늬 ë›°ì–Žìš”. K3 은 양옆을 고늬 얎디서나 고륌 확률, K4 는 막대가 였래된 색읎 되는 데 걞늬는 걞음읎고, K1 을 누륎멎 막대륌 닀시 던젞요. | +| [Risset Rhythm](patterns/risset-rhythm.js) | 늬듬·펄슀 | 닀섯 옥타람의 템포륌 가로로 깐 읎진 자가 4쎈마닀 두 배로 쀌아웃하며 팹널 아래 한 점윌로 였귞띌듀얎서, 레읞마닀 눈ꞈ읎 재생선을 치는 빠륎Ʞ가 끝없읎 빚띌지는데 한 번 두 배가 되멎 귞늌읎 처음곌 똑같아요. K3 은 밝Ʞ 종의 폭, K4 는 읎웃 잵의 템포 비읎고, K1 을 누륎멎 끝없읎 느렀젞요. | +| [Murmuration](patterns/murmuration.js) | 할로윈 | 녞을 진 하늘에서 까마귀 떌가 찌륎레Ʞ처럌 접혔닀 펌쳐지며 돌닀가, 몚두 날아듀얎 컀닀란 핎곚읎 되고 턱을 떚구며 눈을 번뜩읞 ë’€ 흩얎젞요. K3 은 까마귀 수, K4 는 떌가 짓는 몚양읎고, K1 을 누륎멎 바로 몚여요. | +| [Old Well](patterns/old-well.js) | 할로윈 | 달빛 아래 잘띌 볞 돌우묌에 두레박읎 낎렀가 검은 묌에 닿윌멎, 묌읎 독한 쎈록윌로 빛나며 두 눈읎 뜚고 찜백한 손읎 올띌와 두레박을 묌속윌로 끌고 가요. 감아올늰 두레박에 ê·ž 손읎 맀달렀 였Ʞ도 핎요. K3 은 달의 자늬, K4 는 묌 위 안개읎고, K1 을 누륎멎 손잡읎륌 놓아 두레박읎 바로 ë–šì–Žì žìš”. | +| [In the Flash](patterns/in-the-flash.js) | 할로윈 | 비 였는 ë°€ 듀판에 번개가 ì¹  때마닀 í‚€ 크고 마륞 형첎가 한 걞음씩 가까읎 서 있고, 움직읎는 몚습은 한 번도 볎읎지 않닀가 끝낮 얌굎읎 눈앞을 채욎 ë’€ 두 눈만 얎둠에 낚아요. K3 은 비, K4 는 닀가였는 걞음 수읎고, K1 을 누륎멎 바로 번개가 쳐요. | +| [Skull Swap](patterns/skull-swap.js) | 할로윈 | 달 뜬 묘지에서 춀추던 핎곚 둘읎 제 뚞늬륌 듀얎 서로 던젞 낚의 목에 끌우고, 눈구멍을 돌렀 마죌 볎며 얎깚륌 윌쓱한 ë’€ 도로 던젞요. K3 은 뚞늬가 나는 높읎와 도는 수, K4 는 바꟞Ʞ 사읎에 추는 박자 수읎고, K1 을 누륎멎 바로 던젞요. | +| [Paper Lantern Ghost](patterns/paper-lantern-ghost.js) | 할로윈 | 비 였는 볎띌 밀에 처마의 종읎 등읎 흔듀늬닀 왞눈을 뜚고, 읎음맀가 찢얎젞 붉은 혀륌 늘얎뜚늬며 웃얎요. ê·ž 아래로 왞눈 종읎우산읎 왞닀늬로 웅덩읎륌 튀Ʞ며 뛰얎닀녀요. K3 은 비, K4 는 우산읎 뛰는 높읎예요. | + ## 1판 갈래마닀 녾뾌 넷의 자늬는 갈래가 달띌도 거의 같아요. K1 은 색읎고 K2 는 빠륎Ʞ예요. K3 은 몚양을 바꟞고, K4 는 ê·ž 갈래만의 손잡읎예요. 귞래서 처음 볎는 팹턮도 K1 곌 K2 는 손읎 뚌저 알아요. @@ -2440,6 +2455,50 @@ E8 의 ê·Œ 240개륌 고셋의 4₂₁ 닀멎첎 ꌭ짓점윌로 ê·žë žì–Žìš”. 죌제 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 예요. +## 50판 + +50판은 죌제 5 「수학 · 추상」 닀섯곌 죌제 6 「할로윈」 닀섯읎에요. 같읎 돈 49판(#260)읎 고륞 뒀러의 펌치Ʞ와 컬늬큐, 가고음곌 늑대읞간 같은 것을 플핎서 곚랐고, 앞 바퀮 PR 듀에 따륌 Ꞁ은 없었얎요. 수학은 Inverse Cascade, Diffusion Front, Motility Clusters, Bak-Sneppen, Risset Rhythm 읎고 녞읎슈·흐늄, 프랙탈, 입자, 데읎터 귞늌, 늬듬·펄슀에 하나씩 듀얎갔얎요. 49판까지 넣얎 섞멎 추상 갈래 가욎데 녞읎슈·흐늄읎 아홉윌로 가장 적었고, 데읎터 귞늌곌 입자, 늬듬·펄슀가 엎읎었얎요. 49판읎 엎읎던 볎로녞읎·셀곌 시뮬레읎션에 하나씩 넣고 있얎서 ê·ž 둘은 걎너뛰고, 엎하나읞 프랙탈에 하나륌 넣었얎요. 닀섯 ë‹€ 「흔한 것」에도 「낯선 수학 예」에도 없고, 읎 절의 「목록 밖에서」에 적힌 감도 아니띌 새로 찟았얎요. 할로윈은 Murmuration, Old Well, In the Flash, Skull Swap, Paper Lantern Ghost 예요. 핎곚 둘읎 뚞늬륌 바꿔 끌우는 장멎은 48판 뒀에 적혀 있던 감읎고, 나뚞지 넷은 새로 찟았얎요. 죌제 1 은 49판까지의 398개에도 빈 칞곌 하나뿐읞 칞읎 없얎서, 둘뿐읞 칞 엎음곱 가욎데 49판읎 겚눈 칞곌 겹치지 않는 움직임 × 복잡도 4,4 와 쎘쎘핚 × 몚양 바뀜 3,4 륌 겚눎얎요. Inverse Cascade 가 Rabbit Ears 와 Spectrogram 뿐읎던 움직임 × 복잡도 4,4 에, Diffusion Front 가 Bait Ball 곌 Photochromic 뿐읎던 쎘쎘핚 × 몚양 바뀜 3,4 에 ì„°ì–Žìš”. 죌제 4 는 Diffusion Front 와 Old Well 읎 맡았얎요. ì—Ž 개는 얎느 축의 끝도 넘지 않았고, 복잡도는 0.088 에서 2.340, 움직임은 0.131 에서 2.123 읎에요. ì—Ž 개는 `shelf.json` 의 얎느 묶음에도 넣지 않아서 페읎지 ë§š 위 「아직 안 추늰 것」에 서요. + +### Inverse Cascade + +죌제 5 와 1 로 2찚원 난류의 거꟞로 가는 캐슀쌀읎드륌 섞웠얎요. 크레읎치넌읎 1967년에, 배철러가 1969년에 낎닀볞 것읎에요. 2찚원 흐늄은 에너지와 엔슀튞로플(소용돌읎도 제곱의 평균)륌 핚께 지쌜서, 엔슀튞로플는 작은 규몚로 낎렀가 사띌지고 에너지는 거꟞로 큰 규몚로 올띌가요. 맥윌늬엄슀(1984)는 풀얎 둔 난류에서 읎 흐늄읎 둥귌 소용돌읎륌 낳고, 같은 쪜윌로 도는 소용돌읎끌늬 합쳐 점점 적고 컀진닀는 것을 볎였얎요. 팚턎은 팚널을 가로 2π, 섞로 4π 의 죌Ʞ 상자로 볎고 소용돌읎도 방정식을 의사 슀펙튞럌윌로 풀얎요. Ʞ수 2 FFT 륌 직접 ì§°ê³ , 곱할 때마닀 2/3 규칙윌로 높은 몚드륌 버늬고, k^8 쎈점성윌로 엔슀튞로플륌 빌요. 소용돌읎도와 흐멄 핚수륌 역변환 한 번에 핚께 얻고, 속도는 흐멄 핚수의 4ì°š 찚분윌로 구핎서 한 걞음에 변환읎 둘읎에요. 한 판은 파수 5 둘레륌 거륞 잡음에서 시작하고, 에너지가 상자륌 채우멎 새 잡음윌로 넘얎가요. 소용돌읎도의 부혞로 붉은 쪜곌 낚색 쪜을 가륎고, 크Ʞ는 밝Ʞ와 등고선 띠 넷윌로 귞늬고, 흐멄 핚수로 색을 조ꞈ 비틀얎요. 겚눈 칞은 Rabbit Ears 와 Spectrogram 뿐읎던 움직임 × 복잡도 4,4 예요. 첫 판은 소용돌읎도륌 밝Ʞ로만 귞렀서 움직임 0.273, 복잡도 1.912 였얎요. 띠륌 넣고 흐늄을 빠륎게 하자 움직임읎 0.582 가 됐고, 밝Ʞ 곡선을 낮추자 1.897 읎 됐지만 복잡도는 2.037 읎었얎요. 흐멄 핚수로 색을 비틀자 복잡도가 2.437 로 올랐얎요. 작은 규몚에 힘을 계속 넣얎 볞 판은 움직임읎 3.0 을 넘었지만 잡음윌로 읜혀서 버렞얎요. 시간 걞음은 AB2 가 CFL 1.6 에서 터졌고 0.8 에서도 판 끝에 엔슀튞로플가 였륎는 걞음읎 나와서, 걞음 Ꞟ읎가 바뀌얎도 되는 AB3 로 바꿚얎요. ê·ž 뒀로 CFL 0.8 에서 엔슀튞로플가 쀄곧 쀄얎요. 프레임당 7.9 ms 가 2.6 ms 가 됐얎요. ë„·ì§ž 녞람로 베타 평멎도 핮 뎀얎요. 띠 몚양 제튞가 섰지만 제튞 앞에 격자 크Ʞ의 빗삎읎 나와서 점성 녞람로 바꿚얎요. 처음 값윌로 90쎈 동안 닀섯 판을 돌늬니, 한 판 동안 에너지는 84~87% 가 낚고 엔슀튞로플는 9~11% 만 낚았얎요. 잡음의 가장 작은 규몚가 빠지는 처음 2쎈륌 빌멎 첫 판에서 에너지가 7.5% 쀄 때 엔슀튞로플가 82% 쀄었얎요. 에너지 슀펙튞럌의 뎉우늬는 파수 5 에서 1쎈에 3, 3쎈에 2, 6.5쎈에 1 로 낎렀가고, 처음 rms 볎닀 섌 덩읎는 2쎈에 61, 4쎈에 32, 8쎈에 12, 13쎈의 판 끝에 7 읎에요. 잰 값은 움직임 1.648, 복잡도 2.340, 쎘쎘핚 0.126 읎에요. + +### Diffusion Front + +죌제 5 와 1, 4 로 확산 앞쀄을 섞웠얎요. 사포발곌 로소, 구예가 1985년에 낾 것읎에요. 바닥에서 엌료가 퍌젞 올띌가멎 높읎 h 의 농도는 erfc(h / 2√(Dt)) 읎고, 칞마닀 정핎 둔 난수가 ê·ž 농도볎닀 작윌멎 칞읎 ì°šìš”. 칞읎 ì°° 확률읎 높읎륌 따띌 êž°ìšžì–Ž 있얎서 êž°ìšžêž° 슀믞Ʞ띌고도 불러요. 바닥에 읎얎진 덩얎늬의 바깥 테두늬가 확산 앞쀄읎에요. 앞쀄의 평균 자늬는 농도가 정사각 격자 자늬 슀믞Ʞ의 묞턱 0.5927 읞 곳읎고, 폭은 Ʞ욞Ʞ의 -4/7 제곱윌로 늘고, 앞쀄은 찚원 7/4 의 프랙탈읎에요. 팚널의 LED 8,192개가 격자 칞읎에요. 프레임마닀 바닥 쀄에서 변을 맞댄 ì°¬ 칞을 따띌 덩얎늬륌 ì°Ÿê³ , ꌭ대Ʞ에서 ꌭ짓점까지 맞댄 빈 칞을 따띌 바깥을 ì°Ÿì•„, 둘읎 맞닿은 칞을 앞쀄로 ꞈ빛에 귞렀요. 덩얎늬는 ê·ž 자늬의 농도대로 앞쀄 쪜 옅은 청록에서 바닥 쪜 짙은 파랑읎 되고, 새로 붙은 칞은 흰빛윌로 번쩍여요. 읎얎지지 않은 섬은 볎띌예요. 앞쀄은 √t 로 올띌 1쎈에 55쀄, 4.8쎈에 121쀄에 닿아요. 0.5쎈 뚞묞 ë’€ 바닥읎 닫히멎 덩얎늬가 바닥에서 끊겚 섬 빛윌로 가띌앉고, 엌료가 바닥윌로 빠진 ë’€ 새 격자가 서요. 겚눈 칞은 Bait Ball 곌 Photochromic 뿐읎던 쎘쎘핚 × 몚양 바뀜 3,4 예요. 첫 판은 앞쀄읎 7.5쎈 동안 였륎고 였래된 칞을 얎둡게 칠핎서 쎘쎘핚 0.175, 몚양 바뀜 0.182 였얎요. 덩얎늬륌 밝게 두자 쎘쎘핚은 0.233 읎 됐지만 몚양 바뀜은 0.200 에 뚞묌렀얎요. √t 로 였륎는 앞쀄은 2쎈에서 8쎈 사읎에 높읎륌 두 배밖에 늘늬지 못핎서, 였늄만윌로는 0.33 에 닿지 않았얎요. 였늄을 4.8쎈로 쀄읎고 바닥을 닫아 엌료륌 빌는 시간을 더핮 잰 ì°œ 안에 한 판읎 ë‹€ 듀게 하자 몚양 바뀜읎 0.373 읎 됐얎요. 섬읎 얎두워 쎘쎘핚읎 0.178 로 떚얎젞서, 섬을 밝히자 0.217 읎 됐얎요. 팚턎의 앞쀄 규칙을 귞대로 옮겚 폭 2,048~4,096 칞, 높읎 64~2,048 쀄의 선형 êž°ìšžêž° 띠로 쟀얎요. 앞쀄 평균 높읎의 농도는 높읎 128 에서 1,024 까지 0.5942, 0.5931, 0.5925, 0.5919 읎고, 128 읎상을 묶윌멎 0.5936 ± 0.0002 예요. 앞쀄의 폭은 높읎의 0.574 제곱윌로 늘얎 4/7(0.571)곌 맞고, 한 엎에 든 앞쀄 칞은 0.423 제곱윌로 늘얎 3/7(0.429)곌 맞아요. 앞쀄 칞 수륌 폭에 견죌멎 0.736 제곱읎띌 앞쀄의 찚원은 1.74 로 7/4 에 가까워요. 팚널의 64ì—Ž 앞쀄은 였늄 48번, 6,068 프레임에서 평균 높읎의 농도가 0.598 ± 0.003 읎었얎요. 잰 값은 쎘쎘핚 0.217, 몚양 바뀜 0.389 읎고, `thirds` 는 47/62/77 읎에요. 섬을 끈 K4 0 에서도 15/47/73 읎에요. + +### Motility Clusters + +죌제 5 로 욎동성읎 만드는 상분늬(MIPS)륌 섞웠얎요. 알우엔 필늬와 크늬슀티나 마륎쌀티가 2012년에 볎읞 현상읎에요. 슀슀로 헀엄치는 원판읎 서로 밀얎낎Ʞ만 하고 끌얎당Ʞ지 않는데도, 헀엄치는 방향을 였래 지킀멎 부딪힌 원판끌늬 막혀 빜빜한 덩얎늬와 묜은 Ʞ첎로 갈띌젞요. 마읎큎 쌀읎잠와 쥘늬앵 타읎왞륎는 2015년에 읎것을 붐비는 곳에서 느렀진 입자가 귞곳을 더 붐비게 하는 되뚹임윌로 정늬했얎요. 팚널은 죌Ʞ 겜계의 판에 지늄 2 LED 원판 1,434개륌 덮는 몫 0.55 로 두고, 원판마닀 1쎈에 20 LED 륌 제 방향윌로 헀엄치게 하고 방향은 회전 확산윌로 흔듀얎요. 겹친 쌍은 지늄 한 칞짜늬 읎웃 격자로 ì°Ÿì•„ìš”. K3 은 페큮레 수 Pe = v0/(σ Dr), 방향을 잊Ʞ 전에 헀엄치는 거늬륌 지늄윌로 잰 값읎고 5 에서 320 까지 가요. 처음에는 슀프링 힘을 귞대로 적분했는데, v0/(κσ) 가 0.17 읞 묎륞 원판은 Pe 0.08 에서 320 까지 덩얎늬륌 만듀지 않았얎요. 넷 읎상곌 겹친 원판의 몫읎 0.057 곌 0.071 읎었고, 원판읎 서로 믞끄러젞 지나갔얎요. 슀프링을 닚닚하게 하멎 지늄 하나륌 가는 데 133걞음읎 듀얎서, 겹친 쌍을 겹친 만큌 반씩 떌얎 놓는 음을 한 걞음에 여섯 번 되풀읎했얎요. 당당한 슀프링의 곌감쇠 극한읎에요. 귞러자 덩얎늬가 섰지만 프레임당 6.4 ms 가 걞렞얎요. 원판을 칞 찚례로 정렬하고, 둘짞 번부터는 첫 번에 몚은 쌍 목록윌로만 떌얎 놓고, 큰 핚수륌 작게 쪌개 2.1~2.4 ms 로 쀄였얎요. 색은 처음에 LED 마닀 방향을 평균핎 방향읎 고륞 곳만 칠하렀 했는데, 고륞 정도의 가욎데 값읎 덩얎늬 안에서 0.53, 가장자늬에서 0.59 로 갈늬지 않았얎요. 귞래서 원판마닀 닿은 읎웃의 수와 귞쪜 방향의 합을 섞서, 사방읎 막힌 원판은 옅은 띌벀더로, 한쪜만 막힌 원판곌 홀로 닀니는 원판은 헀엄치는 방향의 색상환윌로 칠했얎요. 덩얎늬의 위 가장자늬는 아래륌 볎아 분홍빛 빚강, 아래 가장자늬는 위륌 볎아 청록, 왌쪜은 볎띌, 였륞쪜은 연두로 둘러젞요. 첫 화멎 전에 4쎈륌 믞늬 헀엄치게 핎서 첫 프레임읎 Node 에서 130 ms 쯀 걞렀요. 팚턎의 걞음 핚수륌 귞대로 돌렀 셌얎요. 덮는 몫 0.55 에서 10~20쎈 동안 가장 큰 덩얎늬가 가진 원판의 몫은 Pe 5, 8.4, 14, 24, 40, 113, 320 에서 0.11, 0.19, 0.38, 0.52, 0.66, 0.74, 0.80 읎에요. 둘레 5 LED 안의 덮는 몫읎 0.75 륌 넘는 원판도 0.12 에서 0.66 윌로 늘얎서, 묞턱은 Pe 8 곌 14 사읎예요. 처음 값 Pe 40 에서 원판 25개 넘는 덩얎늬는 1쎈에 6.0개, 2쎈에 4.3개, 8쎈에 1.7개, 32쎈에 1.0개로 합쳐지고, 가장 큰 덩얎늬는 496개에서 64쎈에 1,195개로 컀젞요. 덩얎늬 가장자늬에서 읎웃읎 한쪜에 몰늰 원판 2,550개 가욎데 75.4% 가 읎웃 쪜을 볎고, 방향곌 안쪜 사읎 윔사읞의 평균은 가장자늬 0.49, 덩얎늬 안 0.03 읎에요. Pe 녞람륌 재는 2.5쎈에 가장 큰 덩얎늬의 몫은 Pe 5 에서 0.11, Pe 40 에서 0.36 읎에요. 잰 값은 움직임 2.123, 복잡도 1.759, 채움 0.726 읎에요. + +### Bak-Sneppen + +죌제 5 로 박-슀넀펜 진화 몚형을 섞웠얎요. 페륎 박곌 킮 슀넀펜읎 1993년에 낾 몚형읎에요. 고늬에 선 종마닀 0 곌 1 사읎의 적합도가 있고, 걞음마닀 가장 낮은 종곌 ê·ž 양옆 둘읎 새 묎작위 값을 받아요. 아묎것도 맞추지 않았는데 적합도가 거의 ë‹€ 한 묞턱 위로 올띌가고, 가장 낮은 자늬는 한 곳에서 많돌닀 멀늬 ë›°ì–Žìš”. 묎한한 고늬의 묞턱은 0.66702 예요(파추슀킀, 마슬로프, 박, 1996). 팚널의 ì—Ž 64개가 종 64개읎고, 막대는 적합도만큌 128쀄까지 올띌가요. 막대 색은 마지막윌로 바뀐 ë’€ 지난 걞음 수의 로귞로 칠핎요. 방ꞈ 바뀐 막대는 옅은 ꞈ빛읎고, 죌황곌 장믞색, 볎띌륌 지나 였래 귞대로읞 막대는 짙은 파랑읎에요. 가장 낮은 종은 청록윌로 칠하고 ê·ž 위로 흐늰 청록 쀄을 낎늬고, 지ꞈ까지 볞 가장 낮은 값의 최댓값읞 틈 핚수는 가로선윌로 귞렀요. 양쪜 가장자늬의 흰 눈ꞈ은 0.667 곌 1/3 읎에요. 고륎게 던진 막대에서 6,000걞음을 1.8쎈에 돌늬는데, 걞음 수륌 6000^u 로 늘렀서 두 배마닀 0.05 쯀 였륎는 틈 선읎 고륞 빠륎Ʞ로 올띌가요. 귞닀음 1쎈에 50걞음씩 40쎈륌 ê±·ê³  닀시 던젞요. K3 은 양옆 둘을 고늬 얎디서나 고륌 확률읎에요. 1 읎멎 플늬비에륎귞와 슀넀펜, 박읎 같은 핎에 푌 묎작위 읎웃 몚형읎고, 묞턱읎 1/3 읎에요. 첫 판은 던진 막대륌 0걞음에 바뀐 것윌로 셌얎요. 귞래서 였륎는 동안 판 전첎가 빚강곌 분홍윌로 ì—Žë žê³ , 던진 막대륌 였래된 것윌로 섞자 파란 판 위에 새 막대만 ꞈ빛윌로 솟았얎요. 처음 Ʞ억은 53걞음읎띌 2쎈 지난 막대가 ë‹€ 파랑읎었얎요. 184걞음윌로 늘늬자 지난 사태가 볎띌 띠로 낚았얎요. K1 을 0.5 로 돌늬멎 였래된 막대가 올늬람였얎요. 죌황곌 녾랑 가까읎의 였래된 색만 밝게 올렀 ꞈ빛윌로 바꟞자 K1 반응읎 0.33 에서 0.40 읎 됐얎요. 팚턎의 걞음 핚수륌 귞대로 돌렀 셌얎요. 200번 던진 틈은 였늄읎 끝날 때 0.645 ± 0.014 예요. 400만 걞음 동안 적합도의 분포는 0.62 에서 고원의 절반을 넘얎서요. 64종읎띌 묎한한 고늬의 0.667 볎닀 0.05 낮고, 가장 낮은 값의 99번짞 백분위는 0.58 읎에요. K3 을 0.25, 0.5, 1 로 돌늬멎 ê·ž 자늬가 0.42, 0.37, 0.35 로 낎렀가요. 가장 낮은 값읎 0.60 아래에 뚞묎는 사태는 19,619번읎었고, 2~512 걞음에서 크Ʞ 분포의 êž°ìšžêž°ê°€ −0.84, 가장 ꞎ 사태가 4,075 걞음읎에요. 같은 규칙을 1,024종 고늬에서 0.655 로 섞멎 −0.92 읎고, 묎한한 고늬의 읎론 값은 −1.07 읎에요. 묎작위 읎웃 몚형은 64종에서 −1.23(2~64 걞음), 1,024종에서 −1.35 읎고, 평균장 읎론은 −3/2 예요. 고늬가 작을수록 êž°ìšžêž°ê°€ 얕아요. 잰 값은 움직임 0.599, 채움 0.786, 복잡도 0.088 읎에요. + +### Risset Rhythm + +죌제 5 로 늬섞의 영원한 가속을 섞웠얎요. 장큎로드 늬섞가 1970년대에 만든 늬듬의 착각읎고, 로저 셰퍌드가 1964년에 낾 끝없읎 였륎는 음의 늬듬 쪜 짝읎에요. 템포가 f·2^k 읞 박 엎듀읎 ë‹€ 핚께 빚띌지고, 소늬 크Ʞ는 로귞 템포 위에 고정된 종을 따띌요. 몚든 템포가 두 배가 되멎 섞읞 소늬가 처음곌 같아서 박읎 끝없읎 빚띌지는 것처럌 듀렀요. 늬듬 갈래에는 Stochastic Resonance, Chimera State, Kac Ring 처럌 흘러낎늬는 Ʞ록 귞늌읎 읎믞 있얎서 닀륞 귞늌을 찟았얎요. 팚널의 가로는 로귞 템포예요. 12.8 LED 가 한 옥타람띌 닀섯 옥타람가 놓읎고, 잵마닀 제 레읞읎 있얎요. 레읞 안의 눈ꞈ 하나하나가 ê·ž 잵의 박읎에요. 잵마닀 박읎 읎웃 잵의 두 배띌 느며 잵의 눈ꞈ은 였륞쪜의 빠륞 레읞에 ë‹€ 닀시 있얎서, 눈ꞈ읎 였륞쪜 끝에 붙은 가로 막대가 되고 굵은 닚계음수록 êžžì–Žìš”. 읎진 자의 몚양읎 귞대로 서요. 자 전첎가 팹널 아래 8쀄 밑의 영점윌로 4쎈에 두 배씩 였귞띌듀얎서, 눈ꞈ은 72쀄의 재생선을 늘 1쎈에 11쀄로 지나는데 간격읎 좁아젞요. 귞동안 레읞은 한 죌Ʞ에 한 레읞씩 였륞쪜윌로 믞끄러지고, 밝Ʞ는 가로 위에 고정된 종을 따띌 왌쪜에서 쌜지고 였륞쪜에서 꺌젞요. 재생선을 지나는 눈ꞈ은 흰빛윌로 번쩍읎고 레읞에 빛묎늬가 퍌지고, 지난 눈ꞈ은 0.6 윌로 얎둡게 ê·žë € 재생선읎 앞곌 뒀륌 갈띌요. 첫 판은 여섯 옥타람륌 깔고 종을 가욎데에 두었는데, 왌쪜 3분의 1 읎 막대 한두 개뿐읞 얎둠읎었얎요. 닀섯 옥타람로 쀄읎고, 왌쪜 레읞의 눈ꞈ 간격을 64쀄에서 48쀄로, 종의 가욎데륌 2.4 옥타람로 옮Ʞ고 폭을 1.4 옥타람로 넓히고 레읞을 옅게 칠하자 채움읎 0.267 에서 0.384 로 올랐얎요. 레읞을 하나 걞러 칠했더니 한 죌Ʞ가 끝날 때마닀 칠한 레읞읎 한 칞 옆윌로 튀었얎요. 레읞마닀 가장자늬에 얎두욎 솔Ʞ륌 둔 같은 칠로 바꿔 귞늌읎 읎얎지게 했고, 채움은 0.444 가 됐얎요. 확읞한 것읎에요. 닀섯 때에서 4쎈 뒀의 프레임을 견죌니 24,576 채널 값 가욎데 닀륞 것읎 하나도 없었고, 반 죌Ʞ 뒀에는 평균 찚읎가 0.093 읎에요. 비 3 곌 4 에서도 6.34쎈와 8쎈 뒀에 찚읎가 0 읎에요. 한 잵을 레읞읎 믞끄러지는 대로 따띌가며 재생선의 흰 번쩍임을 1/240쎈 간격윌로 ì°Ÿì•„ 박 사읎륌 재니, ìžµ 셋의 템포가 0.43 Hz 에서 1.30 Hz 로 였륎는 동안 로귞 템포의 êž°ìšžêž°ê°€ 4쎈에 0.995, 0.995, 0.996 옥타람읎고 직선에서 벗얎난 것은 많아알 0.004 옥타람예요. 눈ꞈ읎 8쀄볎닀 쎘쎘핎지멎 번쩍임읎 겹쳐 박을 가륌 수 없얎서 ê·ž 앞까지만 셌얎요. 잰 값은 움직임 1.518, 복잡도 0.466, 채움 0.444 예요. + +### Murmuration + +죌제 6 윌로 핮 질 녘의 까마귀 떌륌 ê·žë žì–Žìš”. 검은 나묎쀄곌 마륞 나묎 둘 위로 하늘읎 볎띌에서 진분홍곌 죌황을 지나 반쯀 잠ꞎ 핎의 녞란빛까지 달아였륎고, 까마귀 500마늬가 찌륎레Ʞ 떌처럌 뭉쳀닀 펌쳐지며 돌아요. 1.4쎈에 처음, ê·ž 뒀로는 10쎈마닀 몚든 새가 제자늬로 날아듀얎 너비 54, 높읎 72 LED 의 핎곚읎 되고, 눈구멍곌 윔, 읎 사읎로 하늘읎 비쳐요. 핎곚은 1.5쎈 뚞묌며 턱을 두 번 떚구고, 눈구멍에 독한 쎈록 빛읎 듀얎 왌쪜곌 였륞쪜을 볞 ë’€ í„°ì ž 흩얎젞요. 떌는 새끌늬 셈하는 볎읎드가 아니에요. 새마닀 제 자늬가 있는 판을 접힘 묌결로 구부늬고 깊읎로 돌렀 옆에서 뎐요. 판읎 읎쪜을 마죌 볎멎 점읎 성Ʞ고, 몚서늬로 돌멎 검은 띠로 몰렀요. 날개륌 Ʞ욞읎는 묌결읎 판을 지나가멎 ê·ž 자늬 새가 두 LED 로 펎젞 짙은 띠가 흘러요. 몚양의 칞은 디더 찚례로 고륎게 뜑고, 새와 칞을 각의 찚례로 짝지얎서 떌가 있던 쪜에서 쏟아젞 듀얎가요. 자늬에 닿은 새는 날개륌 펮 제 몫의 LED 륌 덮되 몚양 밖은 칠하지 않아서, 구멍의 가장자늬가 귞대로 서요. 검은 새는 밝은 것 앞에서만 읜혀서 하늘은 ë§š 위도 밝Ʞ 0.8 아래로 낎늬지 않았얎요. 첫 판의 핎곚은 0.72√(넓읎/새 수) + 0.38 반지늄윌로 덮얎서, 2 LED 격자마닀 분홍 점읎 박힌 첎로 볎였얎요. 0.9√(넓읎/새 수) + 0.5 로 넓히자 검게 찌얎요. 처음 떌는 왌쪜 나묎 앞을 였래 돌아서 검은 쀄Ʞ 앞의 새가 사띌졌얎요. 떌의 쀑심을 5 LED 올늬고 도는 폭을 넓혔얎요. 첫 부늄을 1.4쎈에 두얎 녞람륌 재는 2~3쎈 찜에 새가 몚양윌로 듀얎가는 쀑읎띌, Flock 읎 0.05, Shape 가 0.09 예요. K4 륌 올늬멎 날개륌 치는 박쥐, 흔듀늬는 쎈승달, 셋을 돌아가며 짓는 찚례가 나와요. 날개 묌결을 더핮도 움직임은 0.768 에서 0.752 로 거의 귞대로였고, 복잡도가 0.202 에서 0.234 로 올랐얎요. 잰 값은 움직임 0.752, 채움 0.719, 복잡도 0.234 예요. + +### Old Well + +죌제 6 곌 4 로 였래된 돌우묌을 ê·žë žì–Žìš”. 우묌을 옆에서 잘띌 볞 귞늌읎에요. ë§š 위에 달읎 뜬 볎띌 하늘곌 작은 맞배지붕, 끝에서 볞 도륎래 바퀎가 있고, 32쀄의 난간부터 104쀄의 묌까지 38칞 너비의 우묌 속읎 돌 쌜로 낎렀가요. 양옆 여덟 칞은 잘늰 돌벜읎고 ê·ž 밖은 흙읎에요. 두레박읎 1쎈에 18 LED 씩 낎렀가 검은 묌에 닿윌멎 묌읎 독한 쎈록윌로 빛나고 묌 밑에서 찜백한 눈 둘읎 떠요. 0.5쎈 ë’€ 찜백한 손읎 올띌와 테륌 잡고, 1.3쎈에 두레박을 묌속윌로 끌고 듀얎가요. 밧쀄읎 채찍처럌 흔듀늬고 바퀎가 풀렀 돌고, 묌결읎 퍌지고 거품읎 올띌와요. 묌읎 가띌앉윌멎 두레박을 1쎈에 26 LED 로 감아올렀요. 두레박은 빛나는 묌을 넘치게 닎아 흘늬며 올띌였고, 두 번에 한 번은 ê·ž 손읎 테륌 쥔 채 따띌 올띌였닀 62쀄에서 놓고 ë–šì–Žì žìš”. 달빛은 우묌 입구가 드늬욎 귞늌자 밖윌로 비슀듬히 듀얎와 36쀄마닀 e 분의 1 로 쀄얎서, 묌에 닿을 때는 ꌭ대Ʞ의 0.14 배예요. 첫 판의 뒷벜은 달빛을 0.72 만큌 돌렀죌고 48쀄에 걞쳐 쀄얎서 밝은 타음 벜 같았얎요. 0.5 와 36쀄로 바꟞자 우묌에 깊읎가 ì„°ì–Žìš”. 달의 바닀 묎늬 둘은 나란히 앉아 웃는 얌굎로 볎여서 대각선윌로 옮게얎요. 묌빛을 사읞 둘로 귞늬자 갈맀Ʞ 묎늬가 돌서 값 잡음윌로 바꿚얎요. Fog 는 처음 0.03 읎었고, 안개 섞Ʞ륌 0.12 + 0.3 × Fog 에서 0.16 + 0.42 × Fog 로 올렀 0.04 가 됐얎요. 움직임은 첫 판읎 0.102 였얎요. 첫 두레박을 88쀄에서 낎렀 0.5쎈에 닿게 하고, 묌 장멎을 4.2쎈에서 3.8쎈로 쀄읎고, 감는 빠륎Ʞ륌 20 에서 26 LED 로 올늬자 0.131 읎 됐얎요. 2~8쎈에 두레박읎 였륎는 ꌭ대Ʞ도 79쀄에서 46쀄로 올띌갔얎요. ê·ž 찜에서 두레박읎 가욎데 3분의 1 에 든 프레임은 25%, 아래 3분의 1 에 든 프레임은 38% 읎고, 한 바퀮 12.5쎈 동안에는 28쀄에서 127쀄까지 닀녀요. 돌벜곌 뒷벜, 흙을 ë‹€ 지우고 재멎 `thirds` 는 90/14/21 읎에요. 위는 하늘곌 지붕, 바퀎가 채우고, 가욎데는 달빛 êž°ë‘¥ 속 뚌지와 묌빛읎 공Ʞ에 비친 빛, 밧쀄곌 두레박읎 채워요. 잰 값은 움직임 0.131, 복잡도 0.507 읎고, `thirds` 는 96/80/63 읎에요. + +### In the Flash + +죌제 6 윌로 번개가 ì¹  때만 볎읎는 형첎륌 ê·žë žì–Žìš”. 비 였는 ë°€ 듀판은 거의 ì–Žë‘¡ê³ , 빗쀄Ʞ와 옅은 지평선, 왌쪜의 마륞 나묎와 였륞쪜윌로 멀얎지는 욞타늬만 있얎요. 번개가 치멎 하늘곌 듀판읎 볎랏빛 흰색윌로 0.66쎈쯀 밝아지고, 귞때마닀 í‚€ 크고 마륞 검은 형첎가 조ꞈ 더 가까읎 서 있얎요. 움직읎는 몚습은 한 번도 볎읎지 않아요. 눈높읎 1.6 m 에 1 m 앞의 1 m 가 60 LED 읞 원귌윌로 귞렀서, í‚€ 2.4 m 의 형첎가 8 m 에서 1.75 m 까지 였는 동안 킀는 18 LED 에서 82 LED 로 크고 발은 82쀄에서 125쀄로 낎렀와요. 귞닀음 번개에는 뚞늬가 판을 채우고, 번쩍임읎 꺌진 뒀에도 두 눈만 2.3쎈 낚아 한 번 깜빡여요. 닀음 번개는 빈 듀판을 비추고, 귞닀음에 형첎가 닀시 멀늬 서요. 처음 값에서는 0.53쎈, 2.60쎈, 4.77쎈에 닀가였고 7.20쎈에 얌굎, 9.80쎈에 빈 듀판읎에요. 번쩍임은 섌 쀄Ʞ 하나와 0.2쎈 뒀의 앜한 쀄Ʞ 하나읎고, 녞람륌 ë‹€ 올렀도 0.5쎈볎닀 자죌 치지 않게 막았얎요. Speed 3, Approach 8 에서 간격읎 0.53~0.63쎈예요. 18 LED 짜늬 뚌 형첎는 처음에 1 LED 몞통 옆에 얎깚와 ë–šì–Žì§„ 팔읎 서서, 닀늬와 핚께 빗삎처럌 볎였얎요. 얎깚 쀄로 팔을 몞에 잇고, 팔읎 낎렀가며 벌얎지게 하고, 몞 가욎데륌 LED 한가욎데에 맞추자 사람윌로 읜혔얎요. 바로 앞의 뚞늬는 처음에 38×88 달걀ꌎ에 아몬드 눈읎띌 왞계읞윌로 볎였얎요. 38×53 윌로 둥Ꞁ게 핎서 Ʞ욞읎고, 섞로 동공의 둥귌 눈을 넣었얎요. ì–Žë‘  속 형첎도 처음에는 레벚 25 얞저늬의 하늘 앞에 0 윌로 낚아 볎였얎요. 번쩍임 사읎에는 비와 지평선만 낚게 숚게는데, 번쩍임읎 0.009 까지 식은 ꌬ늬에서 4 닚위로 끊는 반올늌읎 형첎 자늬만 한 계닚 닀륎게 낚게얎요. 귞래서 0.02 아래는 0 윌로 잘랐얎요. Hue 는 처음 0.0247 읎었얎요. 번쩍임읎 식는 시간을 0.13쎈에서 0.2쎈로 늘늬고 빗쀄Ʞ도 번쩍임 색윌로 칠하고, 채도륌 0.3 에서 0.45 로 올렀 0.034 가 됐얎요. 하늘은 번쩍임의 0.22~0.67, 듀판은 0.16~0.30 만 돌렀쀘서 판 전첎가 번쩍여도 drift 가 0.339 로 축 끝 0.41 아래예요. 잰 값은 움직임 0.551, drift 0.339, 채움 0.492 예요. + +### Skull Swap + +죌제 6 윌로 서로의 뚞늬륌 바꿔 끌우는 핎곚 둘을 ê·žë žì–Žìš”. 48판 뒀에 적혀 있던 감읎고, Bone Pile 의 조각 귞늬Ʞ륌 가젞와 두 몞읎 같은 윔드륌 썚요. 왌쪜은 0.8 배의 í‚€ 큰 핎곚읎고 눈구멍읎 독한 쎈록윌로 타요. 였륞쪜은 0.68 배의 작은 핎곚읎고 눈읎 죌황읎며, 죌황 띠륌 두륞 볎띌 마녀 몚자륌 썚요. 뚞늬는 몞볎닀 크게 1.0 배와 0.86 배로 귞렀서, 바꿔 끌우멎 작은 몞에 큰 뚞늬가 얹히고 큰 몞에는 몚자 쓎 작은 뚞늬가 얹혀요. 반지늄 13 의 달 아래 검은 묘비와 십자가 앞에서 두 박자륌 추고, 1.56쎈에 두 뚞늬가 엇갈늰 포묌선윌로 달 앞을 걎너 2.12쎈에 낚의 목에 낎렀앉아요. 묎늎읎 꺟읎고 뚞늬가 흔듀늬닀가, 눈구멍곌 동공읎 옆윌로 믞끄러젞 서로륌 볎고 얎깚륌 윌쓱한 ë’€ 도로 던젞요. Bone Pile 의 몞은 두 팔을 듀멎 가욎데에서 27 닚위까지 벌얎젞서, 0.8 배로 왌쪜 절반에 섞우멎 손읎 판 밖 5.6 LED 까지 나갔얎요. 갈비륌 0.85 배로 좁히고 얎깚륌 12 에서 10 윌로 당게얎요. 팔은 19와 17 에서 15와 13 윌로, 닀늬는 26곌 24 에서 22와 21 로 쀄여서 둘읎 32 LED 씩에 듀얎갔얎요. 첫 판은 날아가는 뚞늬륌 목 자늬륌 축윌로 돌렞얎요. 1.75쎈에 두 목 자늬는 x 26.1 곌 37.9 에 있었지만, 1.98 rad 돌아간 뚞늬댈가 9.6 LED 옆윌로 휘돌아 두 뚞늬가 자늬륌 맞바Ꟍ 채 가욎데에서 겹쳀얎요. 뚞늬댈 가욎데륌 축윌로 돌늬고 왌쪜 포묌선을 12 LED 높읎자 쎈록 눈 뚞늬가 위로, 몚자 쓎 뚞늬가 아래로 달 앞에서 엇갈렞얎요. 섞 번짞 춀은 팔을 번갈아 뚞늬 위로 휘두륎게 했더니 수평을 지나는 팔읎 30 LED ë»—ì–Ž 옆 핎곚에 닿아서, 팔꿈치륌 옆구늬에 붙읎고 아래팔만 올늬게 했얎요. 움직임은 0.945 에서 0.976 읎 됐얎요. 하늘은 K1 읎 볎띌에서 독한 쎈록까지 색상환의 ì°¬ 쪜만 돌아서 갈색 하늘읎 나였지 않아요. 파음은 처음에 26.6 KB 띌 죌석을 쀄읎고 색 변환을 한 쀄로 썚서 24.4 KB 로 ë§žì·„ì–Žìš”. 잰 값은 움직임 0.976, 채움 0.828, 색 0.199 예요. + +### Paper Lantern Ghost + +죌제 6 윌로 음볞 요ꎎ 둘을 ê·žë žì–Žìš”. 조친였바쌀는 처마에 걞늰 종읎 등에 눈곌 입읎 생ꞎ 것읎고, 가사였바쌀는 왞눈에 혀륌 낎믌 종읎우산읎 왞닀늬로 뛰는 것읎에요. 둘 ë‹€ 에도 시대 요ꎎ 귞늌에 나였는 믌간 요ꎎ예요. 팹널 위쪜에서는 너비 26 의 등읎 처마 고늬에 맀달렀 흔듀늬고, 9쎈마닀 0.8쎈에 왞눈읎 가늘게 떠지고 1.2쎈에 가욎데볎닀 6 LED 아래 읎음맀가 찢얎젞 입ꌬ늬가 올띌간 웃음읎 돌요. 2.4쎈까지 Ꞟ읎 25 의 붉은 혀가 음곱 마디 사슬로 늘얎젞 흔듀늌을 따띌 출렁읎고, 등의 눈동자는 우산을 따띌가요. 아래에서는 쎈록 종읎우산읎 0.6쎈마닀 웅크렞닀 ë›°ì–Ž x 10 곌 54 사읎륌 였가고, 낎렀앉을 때마닀 웅덩읎에 고늬와 묌방욞을 튀겚요. 등빛은 비와 웅덩읎에 ë–šì–Žì žìš”. 등에서 20 LED 안의 빗쀄Ʞ는 등빛 0.85 륌 더하고, 웅덩읎에는 등읎 거꟞로 음렁여요. 첫 판의 젖은 돌 반짝임은 대각선 묎늬로 찍혀 죌황 빗ꞈ윌로 볎여서, 등 아래 돌 읎음 쀄만 밝혔얎요. 등 가까욎 빗쀄Ʞ는 띌음띜 바탕에 죌황을 더하자 복숭앗빛읎얎서, 등빛을 받는 만큌 띌음띜을 빌자 죌황읎 됐얎요. 귞러자 K3 반응읎 0.06 에서 0.04 로 낎렀가서, 장대비음수록 빗쀄Ʞ륌 3 LED Ꞟ게 하자 0.06 윌로 돌아왔얎요. 바람에 0.4 êž°ìšžì–Ž 떚얎지는 장대비는 낎렀였는 동안 63 LED 옆윌로 가서, 위에서 판 너비만큌만 뿌늬멎 1.2쎈 ë’€ 왌쪜 아래가 비었얎요. 바람읎 셀수록 바람 위쪜윌로 더 넓게 뿌렀 몚든 쀄에 비가 닿게 했얎요. 우산의 혀는 처음에 몞통 뒀에 귞렀젞 테 아래 3 LED 만 볎여서, 몞통 위로 올렀 13.5 LED 로 늘였얎요. 잰 값은 움직임 1.472, 채움 0.869, 색 0.271 읎에요. + ## 지도 지도의 자늬는 손윌로 맀ꞎ 점수가 아니띌 잰 값읎에요. 팚턎마닀 처음 녾뾌 값윌로 Lab 엔진에서 8쎈륌 돌늬고, 2쎈부터 6쎈 동안의 화멎을 재요. LED 하나의 밝Ʞ는 섞 채널 가욎데 가장 밝은 값윌로 뎐요. @@ -2482,7 +2541,7 @@ ESP32-S3 로 옮Ꞟ 때 걞늎 것도 있얎요. Reaction Diffusion 은 Node 에 ## 닀음 판 감 -10월 25음 `dev`(`f72355d`)의 328개에 43판부터 48판까지의 예순 개륌 더한 388개에서 잰 값읎에요. 팹턮 바퀮는 죌제 안에서 묎엇을 할지 고륌 때 읎 절을 읜고, 한 것은 빌고 새로 볞 것을 더핎요. +10월 25음 `dev`(`f72355d`)의 328개에 43판부터 50판까지의 여든 개륌 더한 408개에서 잰 값읎에요. 팹턮 바퀮는 죌제 안에서 묎엇을 할지 고륌 때 읎 절을 읜고, 한 것은 빌고 새로 볞 것을 더핎요. ### 지도의 빈 칞 @@ -2524,6 +2583,8 @@ ESP32-S3 로 옮Ꞟ 때 걞늎 것도 있얎요. Reaction Diffusion 은 Node 에 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 을 넘얎알 하는 칞읎에요. +50판의 ì—Ž 개도 얎느 축의 끝을 넘지 않아서 칞 겜계는 30판 귞대로읎고, 49판까지 넣은 408개에서도 지도 21장읎 ë‹€ `none` 읎에요. 죌제 1 은 49판까지의 398개에 낚은 둘뿐읞 칞 엎음곱 가욎데 움직임 × 복잡도 4,4 와 쎘쎘핚 × 몚양 바뀜 3,4 륌 겚눠서, 귞런 칞읎 408개에서 엎닀섯읎에요. 움직임 × 복잡도 4,4 는 1쎈에 바뀌는 빛읎 1.49 륌 넘윌멎서 한 화멎읎 deflate 로 LED 당 2.173 바읎튞륌 넘얎알 하는 칞읎에요. 판 전첎가 빚늬 바뀌는 귞늌은 잡음읎 되Ʞ 쉬욎데, 2찚원 난류는 판을 ë‹€ 덮은 부드러욎 장읎 쉬지 않고 감겚서 두 값을 핚께 넘었얎요. Inverse Cascade 는 소용돌읎도륌 밝Ʞ로만 귞늎 때 움직임 0.273, 복잡도 1.912 였얎요. 등고선 띠륌 넣고 흐늄을 빠륎게 하고 밝Ʞ 곡선을 낮추자 움직임읎 1.897 읎 됐지만 복잡도는 2.037 읎었고, 흐멄 핚수로 색을 조ꞈ 비틀자 2.437 로 올랐얎요. 복잡도는 읎번에도 색읎 몇 가지냐가 정했얎요. 작은 규몚에 힘을 계속 넣윌멎 움직임읎 3.0 을 넘었지만 판읎 잡음윌로 읜혀서 버렞얎요. 마지막 값은 움직임 1.648, 복잡도 2.340 읎에요. 쎘쎘핚 × 몚양 바뀜 3,4 는 읎웃 찚읎의 평균읎 0.189~0.297 읎멎서 2~8쎈에 랔록 평균읎 0.331 넘게 바뀌얎알 하는 칞읎에요. 48판읎 적은 대로 판읎 크게 옮겚 닀니는 귞늌에 잔 결을 얹는 쪜읎 맞았얎요. Diffusion Front 는 낱 LED 로 ì°šê³  비는 격자가 쎘쎘핚을, 바닥부터 찚올랐닀 빠지는 덩얎늬가 몚양 바뀜을 맡아요. √t 로 였륎는 앞쀄은 2쎈에서 8쎈 사읎에 높읎륌 두 배밖에 늘늬지 못핎서 였늄만윌로는 몚양 바뀜읎 0.200 에 뚞묌렀얎요. 한 판을 6.3쎈로 쀄여 였늄곌 빠짐읎 ë‹€ 잰 찜에 듀게 하자 0.373 읎 됐고, 귞러자 얎두욎 섬읎 쎘쎘핚을 0.178 로 깎아서 섬을 밝혀 0.217 로 되돌렞얎요. 낚은 엎닀섯 가욎데 엎은 쎘쎘핚 축읎 든 칞읎고, 닀섯은 복잡도가 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 륌 냈얎요. @@ -2622,9 +2683,11 @@ Flock, Harmonograph, Big Type 은 처음 화멎읎 빚강읎에요. 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 읎에요. +50판에서는 Diffusion Front 와 Old Well 읎 128쀄을 썚요. Diffusion Front 는 앞쀄읎 바닥에서 √t 로 올띌 4.8쎈에 121쀄에 닿아서 `thirds` 가 47/62/77 읎에요. 읎얎지지 않은 섬을 끈 K4 0 에서도 15/47/73 읎띌, 섞 칞을 덩얎늬와 앞쀄읎 채워요. Old Well 은 지붕부터 묌까지 한 장멎읎띌 96/80/63 읎고, 돌벜곌 뒷벜, 흙을 지우고 재도 90/14/21 읎에요. 2~8쎈에 두레박읎 46쀄까지 올띌였고, 한 바퀮 12.5쎈 동안에는 28쀄에서 127쀄까지 닀녀요. Inverse Cascade(98/98/98)와 Paper Lantern Ghost(96/97/66), Murmuration(86/71/57), Skull Swap(84/80/82), Motility Clusters(67/78/71), Risset Rhythm(44/48/40), Bak-Sneppen(39/96/99)도 128쀄을 ë‹€ 쓰지만 죌제 4 로 섞지 않았얎요. In the Flash 는 번개 사읎가 얎두워서 70/59/15 예요. + ### 낯선 수학 -죌제 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)예요. +죌제 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)예요. 50판은 닀섯을 더했얎요. 크레읎치넌곌 배철러의 2찚원 난류가 거꟞로 가는 캐슀쌀읎드와 맥윌늬엄슀의 소용돌읎(Inverse Cascade), 사포발곌 로소, 구예의 확산 앞쀄(Diffusion Front), 필늬와 마륎쌀티, 쌀읎잠와 타읎왞륎의 욎동성 상분늬(Motility Clusters), 박곌 슀넀펜의 진화 몚형(Bak-Sneppen), 늬섞의 영원한 가속(Risset Rhythm)읎에요. 목록 밖에서 닀음에 핮 볌 것읎에요. 믌윔프슀킀의 묌음표 핚수(1904)는 페늬 나묎륌 읎진 나묎로 펮는 계닚읎에요. 수론 갈래는 32판의 Gilbreath Table 까지 넷읎에요. Ꞟ람레슀 표는 소수 말고 닀륞 수엎로도 섞욞 수 있얎요. 였듀늬윔(1993)에 따륎멎 2 닀음에 간격읎 작고 고륎게 흩얎진 홀수가 였는 수엎에서는 1 의 엎읎 서고, 간격읎 크멎 깚젞요. 간격의 크Ʞ륌 녞람로 걞멎 1 의 엎읎 묎너지는 자늬가 볎여요. 핎뚞슬늬 곌정의 점 대신 격자 칞마닀 지수 분포 묎게륌 두멎 마지막 통곌 슀믞Ʞ(로슀튞, 1981)가 되고, 극한 몚양읎 쌍곡선에서 (√x+√y)² 의 겜계로 바뀌얎요. @@ -2670,6 +2733,8 @@ Flock, Harmonograph, Big Type 은 처음 화멎읎 빚강읎에요. 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 톱니²/쎈가 비슷핎서, 한 곚을 ꞈ빛윌로 표지핎 평균만 따띌갔얎요. +50판에서 볞 것읎에요. 잰 ì°œ 2~8쎈가 수학의 시간표륌 정했얎요. Diffusion Front 의 앞쀄은 √t 로 올띌서 ê·ž 찜에서 높읎륌 두 배밖에 늘늬지 못핎, 한 판을 6.3쎈로 쀄여 였늄곌 빠짐을 ì°œ 안에 넣었얎요. Bak-Sneppen 은 고륎게 던진 막대에서 묞턱까지 였륎는 6,000걞음을 1.8쎈에 넣고, 걞음 수륌 6000^u 로 늘렀 틈 선읎 고륞 빠륎Ʞ로 였륎게 했얎요. Motility Clusters 는 첫 화멎 전에 4쎈륌 믞늬 헀엄치게 핎서 첫 프레임읎 Node 에서 130 ms 쯀 걞렀요. 작은 판은 묞턱을 낮췄얎요. 64종의 Bak-Sneppen 은 틈읎 0.645 ± 0.014 에서 멈춰 묎한한 고늬의 0.667 에 닿지 않았고, 사태 크Ʞ의 Ʞ욞Ʞ도 읎론의 −1.07 볎닀 얕은 −0.84 였얎요. 같은 규칙을 1,024종에서 섞멎 −0.92 예요. 묌늬륌 귞대로 적분하는 Ꞟ은 읎번에도 걞음읎 막았얎요. 묎륞 슀프링의 원판은 Pe 320 에서도 뭉치지 않아서 겹친 만큌 떌얎 놓는 곌감쇠 극한윌로 바꿚고, Inverse Cascade 는 AB2 가 터젞서 걞음 Ꞟ읎가 바뀌얎도 되는 AB3 로 바꿚얎요. 죌Ʞ가 정확한 착각은 픜셀까지 정확했얎요. Risset Rhythm 은 4쎈 뒀의 프레임읎 24,576 채널 값 가욎데 하나도 닀륎지 않아요. + 목록 밖에서 31판 뒀에 핮 볌 것읎에요. 몚자 타음(슀믞슀, 마읎얎슀, 캐플런, 굿뚌-슀튞띌우슀, 2023)은 한 가지 타음로만 평멎을 비죌Ʞ로 덮고, 뒀집얎 놓읞 타음읎 판에 드묞드묞 흩얎젞요. 페륎믞-파슀타-욞람-칭구의 되돌아옎(1955)은 KdV Solitons 와 같은 뿌늬띌 같은 판에 두지 않아요. 목록 밖에서 33판 뒀에 핮 볌 것읎에요. 뷔퐁의 바늘(1777)은 Bertrand Paradox 와 같은 ꌎ로, ꞈ을 넘는 바늘의 몫읎 2/π 로 몚읎는 막대와 핚께 섞욞 수 있얎요. 토칎륎슀킀의 ë°©(1995)은 거욞로 된 닀각형 방에서 한 점의 빛읎 닀륞 한 점에만 닿지 못하는 방읎띌 Penrose Room 의 ꎑ선 추적을 귞대로 썚요. 프늬드뚌곌 슀윔띌(1987)는 둥귌 원 셋윌로는 볎로메였 고늬륌 만듀 수 없닀는 것을 볎였는데, 원을 밀얎 넣을 때 얎디서 부딪히는지가 귞늌읎 돌요. 정렬 넀튞워크의 시간 t 의 순엎을 점윌로 찍윌멎 대각선에서 원판을 지나 반대 대각선윌로 도는 점 구늄읎 돌요. ê·ž 원판읎 2007년 녌묞읎 낎닀볞 아륎킀메데슀 법칙읎에요. 간격읎 2k 읞 소수 쌍의 수는 Goldbach Comet 곌 같은 FFT 로 섞고, k 마닀 하디-늬틀우드 읞수로 갈늬는 쀄Ʞ 귞늌읎 돌요. @@ -2702,6 +2767,8 @@ Flock, Harmonograph, Big Type 은 처음 화멎읎 빚강읎에요. 목록 밖에서 48판 뒀에 핮 볌 것읎에요. 소수의 끝자늬에도 펞향읎 있얎요. 렘킀 올늬버와 사욎닀띌띌잔(2016)은 소수 닀음 소수가 같은 끝자늬로 끝나는 음읎 Ʞ대볎닀 드묌닀는 것을 볎였얎요. 10 윌로 나눈 나뚞지 1, 3, 7, 9 의 4×4 짝 표는 Chebyshev's Bias 의 첎륌 귞대로 썚서 셀 수 있얎요. 비큎늬 제튞(읎늬나 늬플나와 동료듀, 2007)는 사행하는 제튞 둘레에 소용돌읎가 쀄지얎 서는 흐늄읎띌, Double Gyre 의 능선 셈윌로 제튞 한가욎데의 넘지 못하는 벜을 귞늎 수 있얎요. 파읞뚌의 톱니와 멈춀쇠(1962)는 옚도가 같윌멎 돌지 않고 닀륎멎 도는 래칫읎띌, Flashing Ratchet 의 입자 셈에 옚도 둘을 걞얎 견쀄 만핎요. 망덞람로의 곱셈 캐슀쌀읎드(1974)는 낚ꞰꞰ 대신 묎게륌 곱핎 닀쀑 프랙탈 잡도륌 만듀얎서, Fractal Percolation 의 쪌개Ʞ에 밝Ʞ륌 싀얎 볌 수 있얎요. +목록 밖에서 50판 뒀에 핮 볌 것읎에요. 띌읞슀(1975)의 베타 평멎에서는 거꟞로 가는 캐슀쌀읎드가 띠 몚양 제튞로 바뀌얎요. Inverse Cascade 에서는 제튞 앞에 격자 크Ʞ의 빗삎읎 나와 녞람에서 뺐는데, 걞음을 쀄읎멎 섞로 128쀄에 제튞 대여섯 쀄읎 ì„€ 만핎요. 지프와 사포발(1986)의 앞쀄 걷Ʞ는 êž°ìšžêž° 위에서 앞쀄 하나만 따띌 걞윌며 묞턱을 재요. Diffusion Front 의 규칙윌로 ê·ž 걞음읎 128쀄을 였륎낎늬는 것을 귞늎 수 있얎요. 헀엄치는 방향읎 음정하게 도는 칎읎랄 활성 원판(벀녞 늬람헚, 데믞안 레비슀, 2017)은 도는 빠륎Ʞ륌 올늬멎 욎동성 상분늬가 작은 많돌읎 덩얎늬로 쪌개젞서, Motility Clusters 의 셈에 녾뾌 하나륌 더하멎 돌요. 박-슀넀펜의 가장 낮은 종 자늬륌 한 쀄에 한 점씩 쌓아 낎늬멎 사태의 시공간 귞늌읎 프랙탈 계닚윌로 128쀄을 채워요. 늬섞의 가속에 고륞 펄슀 대신 3+3+2 같은 박자 ꌎ을 싀윌멎 같은 쀌 안에서 박자 ꌎ읎 끝없읎 빚띌젞요. + ### 화장품 팝업 죌제 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 마닀 한 번만 바뀌는 셈윌로 귞늎 만핎요. @@ -2774,7 +2841,13 @@ Flock, Harmonograph, Big Type 은 처음 화멎읎 빚강읎에요. 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개륌 귞대로 썚요. +48판 뒀에 핮 볌 것읎에요. Headless Rider 의 말을 개욞 위 닀늬로 달늬게 하멎, 닀늬륌 걎너멎 쫓아였지 못한닀는 읎알Ʞ대로 닀늬 끝에서 불붙은 혞박읎 날아와 터지는 장멎읎 돌요. + +50판읎 닀섯을 더 ê·žë žì–Žìš”. 녞을 하늘에서 핎곚로 몚였닀 흩얎지는 까마귀 ë–Œ(Murmuration), 손읎 두레박을 끌얎가는 돌우묌(Old Well), 번개가 ì¹  때마닀 가까읎 서 있는 형첎(In the Flash), 뚞늬륌 바꿔 끌우는 핎곚 둘(Skull Swap), 혀륌 늘얎뜚늰 종읎 등곌 왞닀늬 우산(Paper Lantern Ghost)읎에요. 핎곚 둘의 뚞늬 바꟞Ʞ는 48판 뒀에 적혀 있던 감읎에요. 움직임은 Old Well 의 0.131 에서 Paper Lantern Ghost 의 1.472 까지, 복잡도는 Murmuration 의 0.234 에서 Old Well 의 0.507 까지예요. + +50판에서 볞 것읎에요. 검은 것은 읎번에도 밝은 것 앞에서만 읜혔얎요. Murmuration 은 하늘을 ë§š 위까지 밝Ʞ 0.8 아래로 낎늬지 않았고, 떌가 왌쪜 나묎 앞을 돌 때 새가 사띌젞서 떌의 Ꞟ을 5 LED 올렞얎요. 떌가 몚양을 지을 때는 새 하나가 덮는 넓읎가 귞늌을 정했얎요. 0.72√(넓읎/새 수) + 0.38 반지늄윌로는 핎곚읎 2 LED 격자마닀 분홍 점읎 박힌 첎였고, 0.9√(넓읎/새 수) + 0.5 로 넓히자 검게 찌얎요. 볎읎지 않는 동안 움직읎는 것은 셈에 듀지 않았얎요. In the Flash 의 형첎는 번쩍임 사읎에만 닀가와서, 움직임 0.551 은 판 전첎륌 바꟞는 번쩍임읎 ë‹€ 맡았얎요. 번쩍임읎 0.009 까지 식은 ꌬ늬에서는 4 닚위 반올늌읎 형첎 자늬만 한 계닚 닀륎게 낚겚 얎둠에 형첎가 비쳀고, 0.02 아래륌 0 윌로 잘띌 지웠얎요. 사묌의 ꌎ은 읎번에도 닀륞 것윌로 읜혔얎요. In the Flash 의 18 LED 짜늬 뚌 형첎는 빗삎로, 38×88 달걀ꌎ 뚞늬는 왞계읞윌로 볎였얎요. Old Well 의 달 바닀 묎늬 둘은 웃는 얌굎로, 사읞 둘로 귞늰 묌빛은 갈맀Ʞ 묎늬로 볎였얎요. 두 몞읎 나란히 서Ʞ에는 64 너비가 좁았얎요. Skull Swap 은 Bone Pile 의 몞을 0.8 배로 쀄여도 두 팔을 듀멎 손읎 판 밖 5.6 LED 까지 나가서, 갈비륌 0.85 배로 좁히고 팔곌 닀늬륌 쀄여 둘을 32 LED 씩에 넣었얎요. 죌황은 읎번에도 섞읎멎 색을 잃었얎요. Paper Lantern Ghost 의 등 가까욎 빗쀄Ʞ는 띌음띜 바탕에 죌황을 더하자 복숭앗빛읎얎서, 등빛을 받는 만큌 띌음띜을 빌서알 죌황윌로 읜혔얎요. + +50판 뒀에 핮 볌 것읎에요. 까마귀 떌가 핎곚 대신 빗자룚 탄 마녀가 되얎 핮 앞을 걎너가게 하멎 몚양읎 뚞묎는 동안에도 판 위륌 움직여요. 두레박 대신 등불을 쀄에 맀달아 우묌에 낎늬멎, 낎렀갈수록 벜에 비친 빛 동귞띌믞가 작아지닀가 묌 위에서 묎얞가가 불을 꺌요. 처마 끝에 작은 등 넷읎 맀달렀 하나씩 눈을 뜚고 웃음읎 옆 등윌로 옮아가는 장멎은 Paper Lantern Ghost 의 등 귞늬Ʞ륌 크Ʞ만 쀄여 썚요. 목 없는 핎곚 하나가 묘비 위에 놓읞 뚞늬듀을 하나씩 썚 볎며 제 뚞늬륌 찟는 장멎은 Skull Swap 의 뚞늬 셈을 귞대로 썚요. ## 닀시 돌늬Ʞ From 0461b80a0a6de1c93fa9657020d0a9be0dd9b9f2 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 25 Oct 2026 22:47:07 +0000 Subject: [PATCH 25/25] =?UTF-8?q?=EC=97=B0=EA=B5=AC=20=ED=8E=98=EC=9D=B4?= =?UTF-8?q?=EC=A7=80=EB=A5=BC=2050=ED=8C=90=EA=B9=8C=EC=A7=80=20=EB=8B=A4?= =?UTF-8?q?=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_011p1aG8xetifag8g82xnh6r --- 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 7fc482271..ca8ec4775 100644 --- a/incoming/pattern-study/index.html +++ b/incoming/pattern-study/index.html @@ -210,8 +210,8 @@

팹턮 연구

- - + +