From a5c5e786d6430c6d6bc11e3b50788ba7956060f2 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 26 Oct 2026 03:36:41 +0000 Subject: [PATCH 01/10] =?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=2055=ED=8C=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit From bce2965dd8e3ae803107d1508c650d6d82d12ce7 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 26 Oct 2026 04:23:28 +0000 Subject: [PATCH 02/10] =?UTF-8?q?incoming:=2055=ED=8C=90=20Buffon's=20Nood?= =?UTF-8?q?le,=20Vuza=20Canon?= 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_01ANgZzmaYPUurP5cuTFEV6X --- .../pattern-study/patterns/buffon-noodle.js | 324 ++++++++++++++++++ incoming/pattern-study/patterns/vuza-canon.js | 163 +++++++++ 2 files changed, 487 insertions(+) create mode 100644 incoming/pattern-study/patterns/buffon-noodle.js create mode 100644 incoming/pattern-study/patterns/vuza-canon.js diff --git a/incoming/pattern-study/patterns/buffon-noodle.js b/incoming/pattern-study/patterns/buffon-noodle.js new file mode 100644 index 000000000..5173db7db --- /dev/null +++ b/incoming/pattern-study/patterns/buffon-noodle.js @@ -0,0 +1,324 @@ +// ===== Patternflow pattern ===== +// Title: Buffon's Noodle +// Family: data +// Idea: Needles drop one after another onto a floor of lines 16 LEDs apart, warm where they cross a line and cool where they miss, with a white tick on the line at every crossing, while the count of crossings against the length dropped estimates pi below, and bending every needle into an arc of the same length leaves the average alone until, as a closed circle 16 LEDs across, each one crosses exactly twice and the estimate stops wobbling on pi. +// Author: patternflow-lab +// Date: 2026-10-26 +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Lineage: Buffon's needle (Georges-Louis Leclerc, Comte de Buffon, 1777) and Barbier's noodle (Joseph-Emile Barbier, 1860), pattern round 55 +// Round: 55 +// =============================== +// @matrix 64x128 +// @knobs Hue=0..1, Speed=0.2..3, Length=0.2..1, Bend=0..1 +// +// Knob 1: Hue. Turns every colour round the wheel together. At 0 the needles that cross a line are gold and those that miss are azure; at 0.25 green and magenta, at 0.5 blue and orange. The floor lines stay slate. +// Knob 2: Speed. Needles a second, five times the knob: 10 at 2, 1 at 0.2, 15 at 3. A needle lies about 4 seconds at 2 and longer when the drops are slower, so at Length 0.2 the floor holds about 40 needles at 2 and 13 at 0.2. The first 12 fall in the first half second whatever the knob says. +// Knob 3: Length. The needle's length as a share of pi times the line spacing: 0.2 opens on needles 10 LEDs long that cross 0.4 lines on average, 0.5 on 25 LEDs crossing 1, and 1 on 50 LEDs crossing 2. Longer needles leave sooner, about 2 seconds at 1, so the floor carries about the same ink. The estimate is 2 x (length dropped) / (16 x crossings), so any length gives pi on average. +// Knob 4: Bend. Bends each new needle into a circular arc of the same length, turning through Bend x 360 degrees: 0.06 is a needle bowed by 22 degrees, 0.5 a half circle, 1 a closed ring. The average count never changes. At Length 1 and Bend 1 the ring is 16 LEDs across, crosses exactly two lines every time, and the error bars of the trace fall flat onto the pi line. +// Buttons: K1 tap starts the count again. It also starts again by itself half a second after Length or Bend comes to rest at a new value. + +const W = 64, H = 128; +const { floor, min, max, abs, sqrt, sin, cos, asin, PI } = Math; +const TAU = 2 * PI; +const cl = (v, a, b) => (v < a ? a : v > b ? b : v); +// Line spacing, the floor's last row, and line k's height: the centre of +// row 16k, so rows 0, 16, ... 96 carry the lines. +const D = 16, FLOOR = 96, Y0 = 0.5; +// The trace: one column per drop, the newest on the right, beside a pi +// sign; the windowed estimate is over the last WIN drops. +const TX = 7, HIST = W - TX, WIN = 24, TMID = 116, TAMP = 10; +// Hue of the crossing needles at knob 0 (gold), and the missing ones +// opposite it on the wheel. +const HUE0 = 0.13; + +// 3 x 5 digits, row by row from the top, three bits a row. +const FONT = { + 0: 0b111101101101111, 1: 0b010110010010111, 2: 0b111001111100111, 3: 0b111001111001111, + 4: 0b101101111001001, 5: 0b111100111001111, 6: 0b111100111101111, 7: 0b111001001001001, + 8: 0b111101111101111, 9: 0b111101111001111, "-": 0b000000111000000, +}; +// A pi sign, five bits a row. +const PI_SIGN = [0b11111, 0b01010, 0b01010, 0b01010, 0b10011]; + +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; + out[1] = i === 1 || i === 2 ? v : i === 0 ? t : i === 3 ? q : p; + out[2] = i === 3 || i === 4 ? v : i === 2 ? t : i === 5 ? q : p; + return out; +} + +export function setup(params) { + params.s = { + t: 0, acc: 0, acc0: 0, burst: 12, needles: [], epoch: 0, + hue: 0, speed: 2, share: 0.2, bend: 0.06, ref: null, settle: 0, + // Since the last start: drops, length dropped and crossings. + n: 0, sumL: 0, sumC: 0, + winL: new Float64Array(WIN), winC: new Float64Array(WIN), wi: 0, wn: 0, + // The trace, oldest first: the running estimate, the windowed one, and + // a mark where the count started again. + cum: new Float64Array(HIST).fill(NaN), win: new Float64Array(HIST).fill(NaN), mark: new Uint8Array(HIST), + rgb: new Float32Array(W * H * 3), warm: [0, 0, 0], cool: [0, 0, 0], + }; +} + +// The noodle's shape. It is a circular arc of length L turning through th, +// placed by its centroid G and the direction a from the arc's centre to +// its midpoint; the arc's centre sits c behind G. For th near 0 it is the +// straight needle through G, square to a. A point s along the arc (from +// -L/2 to L/2) lies at G + (R cos(s/R) - c) u + R sin(s/R) v, with u the +// direction a and v a quarter turn on. +function shape(L, th) { + if (th < 1e-3) return { R: 0, c: 0 }; + const R = L / th; + return { R, c: (R * sin(th / 2)) / (th / 2) }; +} +function rel(nd, s, o) { + const ca = cos(nd.a), sa = sin(nd.a); + let pu, pv; + if (nd.R === 0) (pu = 0), (pv = s); + else (pu = nd.R * cos(s / nd.R) - nd.c), (pv = nd.R * sin(s / nd.R)); + o[0] = pu * ca - pv * sa; + o[1] = pu * sa + pv * ca; +} + +// Every crossing of the noodle with the lines y = 16k + 0.5, all of them, +// not just the ones on the panel, as positions s along it. Exact: a +// straight needle meets a line at one s, a circle at the two angles whose +// sine puts it on the line, kept when they fall inside the arc. +function crossings(nd) { + const out = [], L = nd.L, k0 = Math.ceil((nd.gy - L - Y0) / D), k1 = floor((nd.gy + L - Y0) / D); + const ca = cos(nd.a), sa = sin(nd.a); + for (let k = k0; k <= k1; k++) { + const y = D * k + Y0; + if (nd.R === 0) { + // y(s) = gy + s cos a + if (abs(ca) < 1e-12) continue; + const s = (y - nd.gy) / ca; + if (abs(s) < L / 2) out.push(s); + } else { + const oy = nd.gy - nd.c * sa, v = (y - oy) / nd.R; + if (abs(v) >= 1) continue; + const b = asin(v); + for (const beta of [b, PI - b]) { + const d = (((beta - (nd.a - nd.th / 2)) % TAU) + TAU) % TAU; + if (d < nd.th) out.push((d - nd.th / 2) * nd.R); + } + } + } + return out; +} + +function drop(S, fall) { + const R = Math.random; + // Four draws a drop whatever the knobs, so drop i lands in the same place + // at every Length and Bend and only its shape differs. + const gx = 2 + 60 * R(), gy = Y0 + FLOOR * R(), a = TAU * R(), spin = (R() < 0.5 ? -1 : 1) * (1.5 + 2 * R()); + const L = S.share * PI * D, th = S.bend * TAU, sh = shape(L, th); + const nd = { gx, gy, a, spin, L, th, R: sh.R, c: sh.c, age: 0, fall, landed: false, epoch: S.epoch }; + nd.cross = crossings(nd); + S.needles.push(nd); +} + +function land(S, nd) { + nd.landed = true; + // Long needles go sooner, so the floor holds about as much ink at any + // Length: about 4 seconds at Speed 2 and Length 0.2, 2 at Length 1. + nd.life = 4 * sqrt(2 / S.speed) * (0.2 / (nd.L / (PI * D))) ** 0.4; + if (nd.epoch !== S.epoch) return; + const C = nd.cross.length; + S.n++; + S.sumL += nd.L; + S.sumC += C; + S.winL[S.wi] = nd.L; + S.winC[S.wi] = C; + S.wi = (S.wi + 1) % WIN; + S.wn = min(WIN, S.wn + 1); + let wl = 0, wc = 0; + for (let i = 0; i < S.wn; i++) (wl += S.winL[i]), (wc += S.winC[i]); + S.cum.copyWithin(0, 1); + S.win.copyWithin(0, 1); + S.mark.copyWithin(0, 1); + S.cum[HIST - 1] = S.sumC ? (2 * S.sumL) / (D * S.sumC) : Infinity; + S.win[HIST - 1] = wc ? (2 * wl) / (D * wc) : Infinity; + S.mark[HIST - 1] = S.n === 1 ? 1 : 0; +} + +function restart(S) { + S.epoch++; + S.n = S.sumL = S.sumC = 0; + S.wi = S.wn = 0; +} + +export function update(dt, input, params) { + if (!params.s) setup(params); + const S = params.s, kv = input.knobValues || [0, 2, 0.2, 0.06], d = cl(dt || 0, 0, 0.1); + S.hue = cl(kv[0], 0, 1); + S.speed = cl(kv[1], 0.2, 3); + S.share = cl(kv[2], 0.2, 1); + S.bend = cl(kv[3], 0, 1); + // A new Length or Bend starts the count again once the knob rests, so the + // readout is about the noodle on the floor. + if (!S.ref) S.ref = [S.share, S.bend]; + if (abs(S.share - S.ref[0]) > 0.004 || abs(S.bend - S.ref[1]) > 0.004) (S.ref = [S.share, S.bend]), (S.settle = 0.5); + if (S.settle > 0 && (S.settle -= d) <= 0) restart(S); + if (input.btnPressed && input.btnPressed[0]) restart(S); + S.t += d; + const fall = 0.32 * cl(2 / S.speed, 0.7, 2); + // The first dozen fall in half a second, so the floor is never bare. + if (S.burst > 0) { + S.acc0 += d * 24; + while (S.acc0 >= 1 && S.burst > 0) S.acc0--, S.burst--, drop(S, 0.3); + } else { + S.acc += d * 5 * S.speed; + while (S.acc >= 1) S.acc--, drop(S, fall); + } + for (const nd of S.needles) { + nd.age += d; + if (!nd.landed && nd.age >= nd.fall) land(S, nd); + } + S.needles = S.needles.filter((nd) => !nd.landed || nd.age < nd.fall + nd.life); +} + +// Lines are laid as points every third of an LED, each lighting its four +// nearest LEDs by how close it falls, and an LED keeps the brightest +// colour laid on it, so a heap of needles never washes out to white. +function plot(S, x, y, c, a) { + const fx = x - 0.5, fy = y - 0.5, ix = floor(fx), iy = floor(fy), tx = fx - ix, ty = fy - iy, rgb = S.rgb; + for (let dy = 0; dy < 2; dy++) { + const yy = iy + dy; + if (yy < 0 || yy > FLOOR) continue; + const wy = dy ? ty : 1 - ty; + for (let dx = 0; dx < 2; dx++) { + const xx = ix + dx; + if (xx < 0 || xx >= W) continue; + const w = min(1, (dx ? tx : 1 - tx) * wy * 1.8) * a, p = (yy * W + xx) * 3; + if (rgb[p] < c[0] * w) rgb[p] = c[0] * w; + if (rgb[p + 1] < c[1] * w) rgb[p + 1] = c[1] * w; + if (rgb[p + 2] < c[2] * w) rgb[p + 2] = c[2] * w; + } + } +} +function put(S, x, y, c, a) { + if (x < 0 || x >= W || y < 0 || y >= H) return; + const p = (y * W + x) * 3; + S.rgb[p] = max(S.rgb[p], c[0] * a); + S.rgb[p + 1] = max(S.rgb[p + 1], c[1] * a); + S.rgb[p + 2] = max(S.rgb[p + 2], c[2] * a); +} +function text(S, str, x, y, colOf) { + for (let i = 0; i < str.length; i++) { + const ch = str[i]; + if (ch === ".") { + put(S, x, y + 4, colOf(i), 1); + x += 2; + continue; + } + const g = FONT[ch]; + if (g === undefined) { + x += 4; + continue; + } + for (let r = 0; r < 5; r++) for (let b = 0; b < 3; b++) if ((g >> (14 - r * 3 - b)) & 1) put(S, x + b, y + r, colOf(i), 1); + x += 4; + } +} +// Error to trace rows: about 1.7 rows for 0.05, 5 for 0.25, never past 10. +const errRows = (e) => (e === Infinity ? TAMP : (TAMP * e) / (abs(e) + 0.25)); + +export function draw(display, params) { + const S = params.s; + if (!S) return; + const { rgb } = S; + rgb.fill(0); + const warm = hsv(HUE0 + S.hue, 0.8, 1, S.warm), cool = hsv(HUE0 + 0.45 + S.hue, 0.75, 1, S.cool); + // The floor lines are slate whatever the hue: a tinted grey would turn + // brown wherever the wheel puts orange. + const slate = [0.24, 0.28, 0.34]; + for (let y = 0; y <= FLOOR; y += D) for (let x = 0; x < W; x++) put(S, x, y, slate, 1); + const o = [0, 0], white = [1, 1, 1]; + // Falling needles first, so landed ones lie over them. + const order = S.needles.slice().sort((p, q) => (p.landed === q.landed ? 0 : p.landed ? 1 : -1)); + for (const nd of order) { + let k = 1, rot = 0, keep = 1, dash = 0; + const c = nd.cross.length ? warm : cool; + let tint = c; + if (!nd.landed) { + // Falling: drawn dashed, larger and turning, as if seen from above, + // settling on its spot as it lands. The colour stays at full + // strength, since a dimmed gold reads brown on the panel. + const u = nd.age / nd.fall, e = (1 - u) * (1 - u); + k = 1 + 0.9 * e; + rot = nd.spin * e; + dash = 1.5; + } else { + const age = nd.age - nd.fall, left = nd.life - age; + // A white flash on landing, and at the end the needle draws in from + // both ends to its middle instead of going dark. + const fl = max(0, 1 - age / 0.25); + tint = [c[0] + (1 - c[0]) * fl, c[1] + (1 - c[1]) * fl, c[2] + (1 - c[2]) * fl]; + keep = cl(left / 0.6, 0, 1); + } + const cr = cos(rot), sr = sin(rot), half = (nd.L / 2) * keep, n = Math.ceil(nd.L * keep * k * 3) + 1; + for (let i = 0; i <= n; i++) { + const s = -half + (2 * half * i) / n; + if (dash && floor((k * (s + half)) / dash) % 2) continue; + rel(nd, s, o); + plot(S, nd.gx + k * (o[0] * cr - o[1] * sr), nd.gy + k * (o[0] * sr + o[1] * cr), tint, 1); + } + } + // The crossings: a white dash on the line where each one meets it, wide + // on landing and narrowing to three LEDs. + for (const nd of S.needles) { + if (!nd.landed) continue; + const age = nd.age - nd.fall, keep = cl((nd.life - age) / 0.6, 0, 1); + for (const s of nd.cross) { + if (abs(s) > (nd.L / 2) * keep) continue; + rel(nd, s, o); + const x = floor(nd.gx + o[0]), y = floor(nd.gy + o[1]); + if (y > FLOOR) continue; + const arm = 1 + max(0, 3 * (1 - age / 0.4)); + for (let dx = -3; dx <= 3; dx++) put(S, x + dx, y, white, cl(arm + 0.5 - abs(dx), 0, 1)); + if (age < 0.3) put(S, x, y - 1, white, 1 - age / 0.3), put(S, x, y + 1, white, 1 - age / 0.3); + } + } + + // The readout. The running estimate to four places: the digits that agree + // with pi in white, the rest cool when it is too high (too few crossings) + // and warm when too low. The drop count on the right. + const est = S.sumC ? (2 * S.sumL) / (D * S.sumC) : Infinity; + const str = est < 10 ? est.toFixed(4) : "-.----", ref = PI.toFixed(4); + let agree = 0; + while (agree < str.length && str[agree] === ref[agree]) agree++; + const over = est > PI; + text(S, str, 1, 99, (i) => (i < agree ? white : over ? cool : warm)); + const ns = String(min(S.n, 99999)); + text(S, ns, W - 4 * ns.length, 99, () => slate.map((v) => v * 2)); + // The pi line, dotted, with its sign. + for (let x = TX; x < W; x += 2) put(S, x, TMID, white, 0.35); + for (let r = 0; r < 5; r++) for (let b = 0; b < 5; b++) if ((PI_SIGN[r] >> (4 - b)) & 1) put(S, 1 + b, TMID - 2 + r, white, 0.8); + // Each drop's column: a bar from the pi line to the estimate over the last + // 24 drops, and a white line through the running estimate. + let prev = NaN; + for (let i = 0; i < HIST; i++) { + const x = TX + i, wv = S.win[i], cv = S.cum[i]; + if (S.mark[i]) for (let y = TMID - TAMP; y <= TMID + TAMP; y++) put(S, x, y, slate, 1.6); + if (!Number.isNaN(wv)) { + // Its body is a checkerboard at full strength rather than a dim fill, + // which would turn the gold brown. + const r = errRows(wv - PI), c = r < 0 ? warm : cool, top = Math.round(r); + for (let j = 1; j <= abs(top); j++) if (j === abs(top) || (j + x) % 2 === 0) put(S, x, TMID - Math.sign(top) * j, c, 1); + } + if (!Number.isNaN(cv)) { + const y = TMID - errRows(cv - PI); + if (!Number.isNaN(prev)) { + const lo = min(prev, y), hi = max(prev, y); + for (let yy = Math.round(lo); yy <= Math.round(hi); yy++) put(S, x, yy, white, 0.95); + } else put(S, x, Math.round(y), white, 0.95); + prev = y; + } else prev = NaN; + } + for (let y = 0, p = 0; y < H; y++) + for (let x = 0; x < W; x++, p += 3) display.setPixel(x, y, (255 * cl(rgb[p], 0, 1)) | 0, (255 * cl(rgb[p + 1], 0, 1)) | 0, (255 * cl(rgb[p + 2], 0, 1)) | 0); +} diff --git a/incoming/pattern-study/patterns/vuza-canon.js b/incoming/pattern-study/patterns/vuza-canon.js new file mode 100644 index 000000000..d2391a292 --- /dev/null +++ b/incoming/pattern-study/patterns/vuza-canon.js @@ -0,0 +1,163 @@ +// ===== Patternflow pattern ===== +// Title: Vuza Canon +// Family: rhythm +// Idea: Twelve voices play the same six-note rhythm from twelve different entries in a cycle of 72 beats, scrolling up the panel one beat a row, and every row holds exactly one note, so the voices tile time with no beat doubled or missed while neither the rhythm nor the entries repeat inside the cycle. +// Author: patternflow-lab +// Date: 2026-10-26 +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Lineage: rhythmic tiling canons (Dan Tudor Vuza, 1991), the non-periodic factorisations of cyclic groups (Gyorgy Hajos, 1950; N. G. de Bruijn, 1953), pattern round 55 +// Round: 55 +// =============================== +// @matrix 64x128 +// @knobs Hue=0..1, Speed=0.2..3, Canon=0..3, Focus=0..12 +// +// Knob 1: Hue. Turns the six note colours round the wheel together. At 0 the six notes of the rhythm run gold, green, cyan, azure, violet and magenta in the order each voice plays them; at 0.5 they run blue, violet, red, orange, lime and cyan. +// Knob 2: Speed. Beats a second, 4.5 times the knob: 9 at 2, so a cycle of 72 passes the now line in 8 seconds; 0.9 at 0.2 (80 seconds) and 13.5 at 3 (5.3 seconds). +// Knob 3: Canon. 0 is Vuza's canon of Z72: the rhythm A = {0, 8, 16, 18, 26, 34}, gaps 8, 8, 2, 8, 8, 38, in 12 voices entering at B = {0, 1, 5, 6, 12, 25, 29, 36, 42, 48, 49, 53}. 1 swaps the roles: 6 voices entering at A play B, the same 72 notes in the same colours regrouped so each column is one colour. 2 is a tiling with the rhythm {0, 1, 8, 9, 16, 17} whose entries {0, 2, 4, 6} + {0, 24, 48} repeat every 24 beats, so the picture comes round three times a cycle. 3 is the plainest tiling, 6 consecutive beats in 12 voices 6 apart: one staircase. +// Knob 4: Focus. 0 lights every voice alike. 1 to 12 pick one voice: it is drawn white-cored with a stem joining its notes, and each of its notes rules a faint line across the panel, so its rhythm lies over the roll as a ruler (for Vuza's A, lines 8, 8, 2, 8 and 8 rows apart, then 38 rows of nothing), while the other voices narrow to two LEDs. With 6 voices, 1 and 2 pick the first, 3 and 4 the second, and so on. + +const W = 64, H = 128, N = 72; +const { floor, min, max, exp, round } = Math; +const cl = (v, a, b) => (v < a ? a : v > b ? b : v); +// The now line, with 104 beats of the past above it and 23 to come below. +const NOW = 104; +// The six note colours at knob 0, a sixth of the wheel apart from gold to +// magenta, skipping red and orange. +const HUES = [0.14, 0.3, 0.46, 0.6, 0.74, 0.88]; + +// Each canon as a rhythm played by voices at a set of entries, and which of +// the two the colours follow. A tiling has |A| x |B| = 72 and every sum +// a + b distinct mod 72; build() checks it and keeps the voice and colour +// of each beat. +const CANONS = [ + { rhythm: [0, 8, 16, 18, 26, 34], entries: [0, 1, 5, 6, 12, 25, 29, 36, 42, 48, 49, 53], colourBy: "rhythm" }, + { rhythm: [0, 1, 5, 6, 12, 25, 29, 36, 42, 48, 49, 53], entries: [0, 8, 16, 18, 26, 34], colourBy: "entries" }, + { rhythm: [0, 1, 8, 9, 16, 17], entries: [0, 2, 4, 6, 24, 26, 28, 30, 48, 50, 52, 54], colourBy: "rhythm" }, + { rhythm: [0, 1, 2, 3, 4, 5], entries: [0, 6, 12, 18, 24, 30, 36, 42, 48, 54, 60, 66], colourBy: "rhythm" }, +]; + +function build(c) { + const voice = new Int8Array(N).fill(-1), colour = new Int8Array(N), note = new Int8Array(N); + c.rhythm.forEach((a, i) => + c.entries.forEach((b, j) => { + const t = (a + b) % N; + if (voice[t] >= 0) throw new Error("not a tiling"); + voice[t] = j; + note[t] = i; + colour[t] = c.colourBy === "rhythm" ? i : j; + }), + ); + return { voices: c.entries.length, voice, colour, note, entries: c.entries }; +} +const TABLES = CANONS.map(build); + +function hsv(h, s, v, out, o) { + h = (h - floor(h)) * 6; + const i = floor(h) % 6, f = h - floor(h), p = v * (1 - s), q = v * (1 - f * s), t = v * (1 - (1 - f) * s); + out[o] = i === 0 || i === 5 ? v : i === 1 ? q : i === 4 ? t : p; + out[o + 1] = i === 1 || i === 2 ? v : i === 0 ? t : i === 3 ? q : p; + out[o + 2] = i === 3 || i === 4 ? v : i === 2 ? t : i === 5 ? q : p; +} + +export function setup(params) { + params.s = { + // p is the beat on the now line, counted from the start. + p: 0, bps: 9, hue: 0, canon: 0, focus: 0, since: 0, last: 0, + buf: new Float32Array(W * H * 3), pal: new Float32Array(18), palHue: -1, + }; +} + +export function update(dt, input, params) { + if (!params.s) setup(params); + const s = params.s, kv = input.knobValues || [0, 2, 0, 0.06], d = cl(dt || 0, 0, 0.1); + s.hue = cl(kv[0], 0, 1); + s.bps = 4.5 * cl(kv[1], 0.2, 3); + s.canon = round(cl(kv[2], 0, 3)); + s.focus = round(cl(kv[3], 0, 12)); + s.p += d * s.bps; + const b = floor(s.p); + if (b !== s.last) (s.last = b), (s.since = 0); + else s.since += d; +} + +export function draw(display, params) { + const s = params.s; + if (!s) return; + const B = s.buf, P = s.pal; + if (s.palHue !== s.hue) { + for (let i = 0; i < 6; i++) hsv(HUES[i] + s.hue, 0.8, 1, P, i * 3); + s.palHue = s.hue; + } + B.fill(0); + const set = (x, y, r, g, b) => { + if (x < 0 || x >= W || y < 0 || y >= H) return; + const o = (y * W + x) * 3; + B[o] = max(B[o], r); + B[o + 1] = max(B[o + 1], g); + B[o + 2] = max(B[o + 2], b); + }; + const T = TABLES[s.canon], nv = T.voices, pitch = floor(60 / nv), lit = nv > 6 ? pitch - 1 : pitch - 2; + const x0 = (W - pitch * nv + (pitch - lit)) >> 1; + // With 6 voices, focus 1 and 2 pick the first, 3 and 4 the second. + const fv = s.focus ? floor(((s.focus - 1) * nv) / 12) : -1; + const now = floor(s.p), flash = exp(-s.since * 9); + const mod = (b) => ((b % N) + N) % N; + + // The scale on the left: a tick every 8 beats, and a dotted line across + // where a cycle of 72 begins. + for (let y = 0; y < H; y++) { + const b = now - (NOW - y), t = mod(b); + if (t % 8 === 0) set(0, y, 0.3, 0.32, 0.38); + if (t === 0) for (let x = 0; x < W; x += 2) set(x, y, 0.32, 0.34, 0.42); + } + // The focused voice rules a faint line across the panel at each of its + // notes, so its rhythm reads as a ruler laid over the others, and a stem + // joins its notes down its own column. + if (fv >= 0) { + const cx = x0 + fv * pitch + (lit >> 1); + let prev = -1; + for (let y = 0; y < H; y++) { + const t = mod(now - (NOW - y)); + if (T.voice[t] !== fv) continue; + for (let x = 2; x < W; x++) set(x, y, 0.2, 0.2, 0.24); + if (prev >= 0) for (let yy = prev + 1; yy < y; yy++) set(cx, yy, 0.45, 0.45, 0.5); + prev = y; + } + } + // The notes: one a row, since the voices tile time. + for (let y = 0; y < H; y++) { + const b = now - (NOW - y), t = mod(b), v = T.voice[t], c = T.colour[t] * 3; + const r = P[c], g = P[c + 1], bl = P[c + 2], xl = x0 + v * pitch; + const future = y > NOW, hit = y === NOW; + let a = xl, z = xl + lit; + // Others narrow to their middle two LEDs while one voice is in focus, + // and the beats still to come are drawn the same way. + if ((fv >= 0 && v !== fv) || future) (a = xl + (lit >> 1) - 1), (z = a + 2); + for (let x = a; x < z; x++) { + if (fv === v && !future) { + // White-cored: the middle LEDs whitened, the ends in the note colour. + const core = x > xl && x < xl + lit - 1 ? 0.55 : 0; + set(x, y, r + (1 - r) * core, g + (1 - g) * core, bl + (1 - bl) * core); + } else set(x, y, r, g, bl); + } + if (hit) { + // The note that sounds flashes white and throws a glow along the now + // line in its colour. + for (let x = xl - 2; x < xl + lit + 2; x++) { + const k = x < xl || x >= xl + lit ? 0.6 * flash : flash; + set(x, y, r + (1 - r) * k, g + (1 - g) * k, bl + (1 - bl) * k); + set(x, y - 1, r * k, g * k, bl * k); + set(x, y + 1, r * k, g * k, bl * k); + } + // The line itself is grey, coloured only near the note: a dim gold + // line across the panel would read brown. + for (let x = 0; x < W; x++) { + const dd = x < xl ? xl - x : x >= xl + lit ? x - xl - lit + 1 : 0, k = 0.7 * flash * exp(-dd / 8); + set(x, y, 0.26 + r * k, 0.27 + g * k, 0.3 + bl * k); + } + } + } + + for (let y = 0, o = 0; y < H; y++) + for (let x = 0; x < W; x++, o += 3) display.setPixel(x, y, min(255, round(B[o] * 255)), min(255, round(B[o + 1] * 255)), min(255, round(B[o + 2] * 255))); +} From 3362da7c9972f33158c98f373b9d34e5cbead22d Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 26 Oct 2026 04:29:17 +0000 Subject: [PATCH 03/10] =?UTF-8?q?incoming:=2055=ED=8C=90=20Question=20Mark?= =?UTF-8?q?=20Function,=20Burrows-Wheeler?= 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_01ANgZzmaYPUurP5cuTFEV6X --- .../pattern-study/patterns/burrows-wheeler.js | 285 ++++++++++++++++++ .../pattern-study/patterns/question-mark.js | 235 +++++++++++++++ 2 files changed, 520 insertions(+) create mode 100644 incoming/pattern-study/patterns/burrows-wheeler.js create mode 100644 incoming/pattern-study/patterns/question-mark.js diff --git a/incoming/pattern-study/patterns/burrows-wheeler.js b/incoming/pattern-study/patterns/burrows-wheeler.js new file mode 100644 index 000000000..e4235f9f7 --- /dev/null +++ b/incoming/pattern-study/patterns/burrows-wheeler.js @@ -0,0 +1,285 @@ +// ===== Patternflow pattern ===== +// Title: Burrows-Wheeler +// Family: algo +// Idea: The 128 rotations of a text stand as 128 rows of coloured letters and glide into sorted order, so the last column at the right edge gathers into runs; then the rows fade to their first and last letters and come back one at a time as the LF walk rebuilds them from the last column alone. +// Author: patternflow-lab +// Date: 2026-10-26 +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Lineage: Burrows-Wheeler transform (Michael Burrows and David Wheeler, 1994), with its perfect clustering on Sturmian words (Sabrina Mantaci, Antonio Restivo and Marinella Sciortino, 2003) and on interval exchange words (Sebastien Ferenczi and Luca Zamboni, 2013), pattern round 55 +// Round: 55 +// =============================== +// @matrix 64x128 +// @knobs Hue=0..1, Speed=0.2..3, Order=0..3, Letters=2..6 +// +// Knob 1: Hue. Turns every colour round the wheel together. At 0 the first letter is bright gold and the last a dim violet at a sixth of its brightness, with green, cyan and blue between them when there are more letters. The run marks, the walk and the mark beside the text's own row are white. +// Knob 2: Speed. At 2 one text takes 6.6 seconds: 0.4 in rotation order, 1 gliding into sorted order, 1.2 sorted, 1.2 with the shared prefixes lit, 0.3 fading to the first and last columns, 2 for the LF walk at 64 rows a second, and 0.5 for a last look. 0.2 is ten times slower and 3 half as fast again. +// Knob 3: Order. How much structure the text has. At 0 every letter is drawn at random; with 2 letters the last column breaks into 64 runs on average and a row needs 8 letters to tell it from its neighbours. At 1 each letter follows a fixed rule on the two before it four times in five, and at 2 the text strings together copies of three short words; for 2 letters both bring the last column down to 25 to 28 runs, and the rows need 17 or 18 letters. At 3 the text is perfectly clustering and the last column is one run per letter in falling order: for 2 letters a Sturmian word, the first one of slope 49/128 next to the Fibonacci word's 0.382, and for more letters the coding of an interval exchange that turns the order of its intervals round. 95% of the rows of the Sturmian word share all 53 letters on the panel with a neighbour. +// Knob 4: Letters. The size of the alphabet, 2 to 6. Each letter is a step dimmer than the one before, from full brightness down to a sixth of it, so two different letters side by side always differ in brightness as well as colour. + +const W = 64, H = 128, N = 128; +const { floor, round, min, max, abs } = Math; +const cl = (v, a, b) => (v < a ? a : v > b ? b : v); +// The first letter of each rotation stands 3 LEDs wide at the left edge +// and the last 5 wide at the right, the two columns the transform keeps. +// Between them the next 52 letters, and a gutter for the run marks. +const FW = 3, BX = 4, MC = 52, GX = 56, LX = 59; +// Hue of the first letter at knob 0, and how far round the wheel the +// alphabet spreads. +const HUE0 = 0.13, SPREAD = 0.7; +// The phases of one text, in seconds at Speed 2: rotation order, the glide, +// the sorted hold, the shared prefixes, the fade to the first and last +// columns, the LF walk, and a last look at the whole sorted matrix. +const PH = [0.4, 1.0, 1.2, 1.2, 0.3, 2.0, 0.5]; +const END = PH.reduce((a, b) => a + b, 0); +const at = (k) => PH.slice(0, k).reduce((a, b) => a + b, 0); +// A letter whose row is not known yet is drawn on every other LED of a +// checkerboard, at this brightness. +const GHOST = 0.75; + +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; +} + +const rnd = (k) => floor(Math.random() * k); + +// The coding of a discrete interval exchange with the order of its +// intervals turned round: point x in interval k moves to x - s_k + t_k, with +// t_k the length of the intervals after k. When the map is one cycle of all +// 128 points, its coding is perfectly clustering: the last column of the +// sorted rotations is the letters in falling order, one run each. +function exchange(lens) { + const m = lens.length, s = [], t = []; + let acc = 0; + for (let k = 0; k < m; k++) s.push(acc), (acc += lens[k]); + for (let k = 0; k < m; k++) t.push(N - s[k] - lens[k]); + const lab = (x) => { + let k = m - 1; + while (x < s[k]) k--; + return k; + }; + const w = new Uint8Array(N); + let x = rnd(N), c = 0; + const x0 = x; + do { + const k = lab(x); + w[c++] = k; + x = x - s[k] + t[k]; + } while (x !== x0 && c < N); + return c === N ? w : null; +} + +// Two letters open on the Sturmian word of slope 49/128, the nearest to the +// Fibonacci word's 1/phi^2 that 128 allows; later texts cut 128 at random. +function clustering(m, first) { + if (m === 2 && first) return exchange([79, 49]); + for (;;) { + // Cut 128 into m pieces of at least 8. + const cuts = []; + while (cuts.length < m - 1) { + const c = 8 + rnd(N - 15); + if (cuts.every((d) => abs(d - c) >= 8)) cuts.push(c); + } + cuts.sort((a, b) => a - b); + const lens = []; + let prev = 0; + for (const c of cuts) lens.push(c - prev), (prev = c); + lens.push(N - prev); + if (lens[0] < 8) continue; + const w = exchange(lens); + if (w) return w; + } +} + +function makeText(order, m, first) { + const T = new Uint8Array(N); + if (order === 0) for (let i = 0; i < N; i++) T[i] = rnd(m); + else if (order === 1) { + // Each letter follows a fixed rule on the two before it, four times in + // five. + const rule = Array.from({ length: m * m }, () => rnd(m)); + T[0] = rnd(m); + T[1] = rnd(m); + for (let i = 2; i < N; i++) T[i] = Math.random() < 0.8 ? rule[T[i - 2] * m + T[i - 1]] : rnd(m); + } else if (order === 2) { + const words = Array.from({ length: 3 }, () => Array.from({ length: 5 + rnd(7) }, () => rnd(m))); + let i = 0; + while (i < N) for (const c of words[rnd(3)]) if (i < N) T[i++] = c; + } else return clustering(m, first); + return T; +} + +export function setup(params) { + params.s = { + hue: 0, sp: 2, ord: -1, m: -1, tau: 0, + T: null, rank: new Int32Array(N), L: new Uint8Array(N), walk: new Int32Array(N), + keep: new Int32Array(N), seen: new Int32Array(N), rowRot: new Int32Array(H), + pos: new Float64Array(N), ord128: Array.from({ length: N }, (_, i) => i), + buf: new Float32Array(W * H * 3), pal: new Float32Array(6 * 3), palKey: "", + }; +} + +// Sort the rotations, read off the last column, and take the LF walk from +// the row that holds the text itself. +function build(s, first) { + const T = makeText(s.ord, s.m, first); + s.T = T; + const idx = Array.from({ length: N }, (_, i) => i); + idx.sort((a, b) => { + for (let k = 0; k < N; k++) { + const d = T[(a + k) % N] - T[(b + k) % N]; + if (d) return d; + } + return a - b; + }); + for (let r = 0; r < N; r++) (s.rank[idx[r]] = r), (s.L[r] = T[(idx[r] + N - 1) % N]); + // LF(r): the row of the rotation one letter earlier, found from the last + // column alone as the count of smaller letters plus the count of the + // same letter above row r. + const C = new Int32Array(8), occ = new Int32Array(8), lf = new Int32Array(N); + for (let r = 0; r < N; r++) C[s.L[r] + 1]++; + for (let c = 1; c < 8; c++) C[c] += C[c - 1]; + for (let r = 0; r < N; r++) lf[r] = C[s.L[r]] + occ[s.L[r]]++; + let r = s.rank[0]; + for (let k = 0; k < N; k++) (s.walk[k] = r), (r = lf[r]); + // The prefix each sorted row shares with a neighbour, plus the letter + // that tells them apart. + const lcp = (a, b) => { + let k = 0; + while (k < N && T[(a + k) % N] === T[(b + k) % N]) k++; + return k; + }; + for (let q = 0; q < N; q++) { + const up = q > 0 ? lcp(idx[q - 1], idx[q]) : 0, dn = q < N - 1 ? lcp(idx[q], idx[q + 1]) : 0; + s.keep[q] = max(up, dn) + 1; + } +} + +export function update(dt, input, params) { + if (!params.s) setup(params); + const s = params.s, kv = input.knobValues || [0, 2, 0, 0.06], d = cl(dt || 0, 0, 0.1); + s.hue = cl(kv[0], 0, 1); + s.sp = cl(kv[1], 0.2, 3); + const ord = round(cl(kv[2], 0, 3)), m = round(cl(kv[3], 2, 6)); + // A turn of Order or Letters starts over at once with a new text, so the + // knob shows in the first sort. + if (ord !== s.ord || m !== s.m) { + s.ord = ord; + s.m = m; + s.tau = 0; + build(s, true); + } + s.tau += d * (s.sp / 2); + if (s.tau >= END) { + s.tau -= END; + build(s, false); + } +} + +function palette(s) { + const P = s.pal, m = s.m; + for (let k = 0; k < m; k++) { + const u = k / (m - 1); + hsv(HUE0 + s.hue + SPREAD * u, 0.7 + 0.2 * u, 1 - 0.84 * u, P, k * 3); + } + s.palKey = s.hue + ":" + m; +} + +const ease = (u) => (u <= 0 ? 0 : u >= 1 ? 1 : u * u * (3 - 2 * u)); + +export function draw(display, params) { + const s = params.s; + if (!s || !s.T) return; + if (s.palKey !== s.hue + ":" + s.m) palette(s); + const { buf: B, pal: P, T, rank, keep, tau } = s; + B.fill(0); + const put = (x, y, o, k) => { + const i = (y * W + x) * 3; + B[i] += P[o] * k; + B[i + 1] += P[o + 1] * k; + B[i + 2] += P[o + 2] * k; + }; + const white = (x, y, k) => { + if (x < 0 || x >= W || y < 0 || y >= H) return; + const i = (y * W + x) * 3; + B[i] += k; + B[i + 1] += k; + B[i + 2] += k; + }; + + // Which rotation sits on each row: rotation i glides from row i to its + // sorted row, all of them at once, and the rows are dealt out in the + // order of where they have got to, so two rows crossing swap places + // and no row is ever empty. + const g = ease((tau - at(1)) / PH[1]), pos = s.pos, ord = s.ord128; + for (let i = 0; i < N; i++) (pos[i] = i + (rank[i] - i) * g), (ord[i] = i); + ord.sort((a, b) => pos[a] - pos[b] || a - b); + for (let y = 0; y < H; y++) s.rowRot[y] = ord[y]; + + // How far each sorted row stays solid. In the prefix phase the ghost + // sweeps in from the right and stops at the shared prefix; then it goes + // on to column 1, leaving the first column, which is the last column + // sorted. The walk makes each row it reaches solid again. + const pref = ease((tau - at(3)) / 0.5), drop = ease((tau - at(4)) / PH[4]); + const inWalk = tau >= at(5) && tau < at(6); + const step = inWalk ? min(N - 1, floor(((tau - at(5)) / PH[5]) * N)) : -1; + s.seen.fill(0); + if (inWalk) for (let k = 0; k <= step; k++) s.seen[s.walk[k]] = 1; + const ghostAll = tau >= at(4) && tau < at(6); + + for (let y = 0; y < H; y++) { + const i = s.rowRot[y], q = rank[i]; + // Letters 0..solid-1 are solid; letter 0 is the first column. + let solid = MC + 1; + if (tau >= at(3) && tau < at(4)) solid = round(MC + 1 + (keep[q] - MC - 1) * pref); + if (ghostAll) solid = round(keep[q] + (1 - keep[q]) * drop); + if (inWalk && s.seen[q]) solid = MC + 1; + const fo = T[i] * 3; + for (let x = 0; x < FW; x++) put(x, y, fo, 1); + for (let c = 1; c <= MC; c++) { + const o = T[(i + c) % N] * 3, x = BX + c - 1; + if (c < solid) put(x, y, o, 1); + else if (((x + y) & 1) === 0) put(x, y, o, GHOST); + } + // The letter that tells the row from its neighbours, lit white while + // the prefixes show, so the sorted contexts draw their own outline. + if (tau >= at(3) && tau < at(4) && keep[q] <= MC + 1) { + const c = keep[q] - 1; + if (c === 0) for (let x = 0; x < FW; x++) white(x, y, 0.6 * pref); + else white(BX + c - 1, y, 0.6 * pref); + } + // The last letter of the rotation. + const lo = T[(i + N - 1) % N] * 3; + for (let x = LX; x < W; x++) put(x, y, lo, 1); + } + + // Run marks in the gutter, once the rows are sorted: one at the top of + // each run of the last column. + const sorted = ease((tau - at(2)) / 0.4); + if (sorted > 0) + for (let q = 1; q < N; q++) + if (s.L[q] !== s.L[q - 1]) for (let x = GX; x < LX; x++) white(x, q, 0.55 * sorted); + + // The walk: the row it stands on flashes, and a line runs from its last + // letter to the same letter's place in the first column, the next row. + if (inWalk) { + for (let back = 0; back < 3; back++) { + const k = step - back; + if (k < 0 || k >= N - 1) continue; + const r0 = s.walk[k], r1 = s.walk[k + 1], a = (0.7 * (3 - back)) / 3; + if (back === 0) for (let x = BX; x < GX; x++) white(x, r0, 0.35); + for (let x = FW; x < LX; x++) white(x, round(r0 + ((r1 - r0) * (LX - 1 - x)) / (LX - 1 - FW)), a * 0.6); + if (back === 0) for (let x = 0; x < FW; x++) white(x, r1, 0.6); + } + } + // The text itself: a white mark beside the row it is on. + const home = s.rowRot.indexOf(0); + if (home >= 0) for (let x = GX; x < LX; x++) white(x, home, x === GX ? 1 : 0.6); + + for (let y = 0, p = 0; y < H; y++) + for (let x = 0; x < W; x++, p += 3) + display.setPixel(x, y, min(255, round(B[p] * 255)), min(255, round(B[p + 1] * 255)), min(255, round(B[p + 2] * 255))); +} diff --git a/incoming/pattern-study/patterns/question-mark.js b/incoming/pattern-study/patterns/question-mark.js new file mode 100644 index 000000000..4f2eaec1f --- /dev/null +++ b/incoming/pattern-study/patterns/question-mark.js @@ -0,0 +1,235 @@ +// ===== Patternflow pattern ===== +// Title: Question Mark Function +// Family: number +// Idea: Minkowski's question mark function rises as a filled staircase that zooms for ever into a quadratic irrational, the Farey fractions marked where the dyadic levels meet the stair, while the point's continued fraction digits and the binary digits of its image scroll past underneath as the same runs. +// Author: patternflow-lab +// Date: 2026-10-26 +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Lineage: Minkowski's question mark function (Hermann Minkowski, 1904), with Denjoy's continued fraction sum (Arnaud Denjoy, 1938), pattern round 55 +// Round: 55 +// =============================== +// @matrix 64x128 +// @knobs Hue=0..1, Speed=0.2..3, Point=0..5, Depth=3..7 +// +// Knob 1: Hue. Turns every colour round the wheel together. At 0 the staircase is azure with a pale rim, the Farey marks and their dyadic lines rose, and the point pale gold, with the lines of its own Stern-Brocot path in a pale grey gold. Underneath, runs of noughts are rose and runs of ones azure. +// Knob 2: Speed. At 2 the bits scroll past at 0.6 a second, so the golden point's loop, where the picture shrinks 6.9 times across and 4 times up, takes 3.3 seconds and the silver point's, 34 times across and 16 up, takes 6.7. 0.2 is ten times slower and 3 half as fast again. +// Knob 3: Point. The quadratic irrational the zoom falls into: 0 the golden point 1/phi = [0; 1, 1, 1, ...], where ? is 2/3; 1 the silver point sqrt(2) - 1 = [0; 2, 2, ...], ? = 2/5; 2 sqrt(3) - 1 = [0; 1, 2, 1, 2, ...], ? = 6/7; 3 (sqrt(3) - 1)/2 = [0; 2, 1, ...], ? = 2/7; 4 [0; 3, 3, ...], ? = 2/9; 5 [0; 1, 3, ...], ? = 14/15. +// Knob 4: Depth. How many levels of the Farey tree are marked, counted from the window's own scale: 3 marks the 7 fractions down to the quarters and fifths on the whole graph, 7 marks 127, and each loop of the zoom fades in the next levels as they grow into view. As on a ruler, the deeper the level the fainter its lines. + +const W = 64, H = 128; +const { floor, round, min, max, abs, sqrt, pow, exp } = Math; +const cl = (v, a, b) => (v < a ? a : v > b ? b : v); +// Hue of the staircase at knob 0, azure so the panel never opens on red, +// and the turn to the marks' colour. +const HUE0 = 0.56, MARK = 0.4; +// The graph's box and the two bars under it, in LED rows. +const GT = 1, GB = 105, BAR1 = 110, BAR2 = 120, CELL = 3; +// The periods of the continued fractions the knob chooses from. +const POINTS = [[1], [2], [1, 2], [2, 1], [3], [1, 3]]; +// Bits a second at Speed 2. +const RATE = 0.6; + +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; +} + +// Denjoy's sum: x = [0; a1, a2, ...] goes to 2 (2^-A1 - 2^-A2 + ...), with +// A_k the sum of the first k partial quotients. +function qm(x) { + if (x <= 0) return 0; + if (x >= 1) return 1; + let y = 0, sg = 1, pw = 2; + for (let k = 0; k < 80 && x > 1e-13; k++) { + const inv = 1 / x, a = floor(inv); + x = inv - a; + pw *= pow(2, -a); + y += sg * pw; + sg = -sg; + if (pw < 1e-16) break; + } + return y; +} + +// The point's map phi(t) = [0; a1, ..., ap + t] as a Moebius matrix, its +// two fixed points, and the multiplier at the attracting one. An odd period +// turns the picture over, so it is taken twice. +function point(k) { + let a = POINTS[k]; + if (a.length % 2) a = a.concat(a); + let m00 = 1, m01 = 0, m10 = 0, m11 = 1; + for (const q of a) { + // Multiply by [[0, 1], [1, q]]: t -> 1 / (q + t). + const n00 = m01, n01 = m00 + q * m01, n10 = m11, n11 = m10 + q * m11; + m00 = n00; m01 = n01; m10 = n10; m11 = n11; + } + // m10 x^2 + (m11 - m00) x - m01 = 0. + const b = m11 - m00, disc = sqrt(b * b + 4 * m10 * m01); + const r1 = (-b + disc) / (2 * m10), r2 = (-b - disc) / (2 * m10); + const x0 = r1 > 0 && r1 < 1 ? r1 : r2, x1 = x0 === r1 ? r2 : r1; + const kap = 1 / (m10 * x0 + m11) ** 2; + let A = 0; + // The binary digits of ?(x0), the units digit first: a1 noughts, a2 + // ones, a3 noughts and so on. + const bits = []; + for (let i = 0; i < a.length; i++) { + for (let j = 0; j < a[i]; j++) bits.push(i % 2); + A += a[i]; + } + return { a, A, x0, x1, kap, y0: qm(x0), bits, sig: pow(2, -A) }; +} + +export function setup(params) { + params.s = { + hue: 0, sp: 2, pk: -1, P: null, z: 0, depth: 3, ys: new Float64Array(W + 1), + mk: [], buf: new Float32Array(W * H * 3), pal: new Float32Array(24), palHue: -1, + }; +} + +export function update(dt, input, params) { + if (!params.s) setup(params); + const s = params.s, kv = input.knobValues || [0, 2, 0, 0.06], d = cl(dt || 0, 0, 0.1); + s.hue = cl(kv[0], 0, 1); + s.sp = cl(kv[1], 0.2, 3); + const pk = round(cl(kv[2], 0, POINTS.length - 1)); + s.depth = cl(kv[3], 3, 7); + if (pk !== s.pk) { + s.pk = pk; + s.P = point(pk); + s.z = 0; + } + // One loop of the zoom is one period of the digits, A bits. + s.z += (d * RATE * (s.sp / 2)) / s.P.A; + s.z -= floor(s.z); +} + +function palette(s) { + const P = s.pal, h = HUE0 + s.hue; + hsv(h, 0.8, 1, P, 0); // stair fill, scaled down the column + hsv(h - 0.03, 0.3, 1, P, 3); // stair edge + hsv(h + MARK, 0.6, 1, P, 6); // mark + hsv(h + MARK, 0.55, 0.36, P, 9); // dyadic arm + hsv(h + 0.62, 0.45, 1, P, 12); // probe ring and path + hsv(h + MARK, 0.8, 0.95, P, 15); // a run of noughts + hsv(h, 0.75, 0.95, P, 18); // a run of ones + hsv(h + 0.62, 0.22, 0.42, P, 21); // dyadic arm on the point's path + s.palHue = s.hue; +} + +// Screen to x and back. u = (x - x0)/(x - x1) turns phi into u -> kap u, +// so the window kap^z u(s) for s in [0, 1] is the whole graph at z = 0 and +// its copy one period down at z = 1, and the loop has no seam. +function uOf(P, x) { return (x - P.x0) / (x - P.x1); } +function xOf(P, u) { return (P.x0 - u * P.x1) / (1 - u); } + +// The Stern-Brocot tree inside the window: each mediant of two Farey +// neighbours goes to the midpoint of their images, which is what ? is. +function walk(s, P, xlo, xhi, a, b, c, dd, ylo, yhi, lev, maxLev, path) { + if (lev > maxLev) return; + if (c / dd < xlo || a / b > xhi) return; + const p = a + c, q = b + dd, m = p / q, ym = (ylo + yhi) / 2; + const onPath = path && P.x0 > a / b && P.x0 < c / dd; + s.mk.push(m, ym, lev, onPath ? 1 : 0); + walk(s, P, xlo, xhi, a, b, p, q, ylo, ym, lev + 1, maxLev, onPath); + walk(s, P, xlo, xhi, p, q, c, dd, ym, yhi, lev + 1, maxLev, onPath); +} + +export function draw(display, params) { + const s = params.s; + if (!s || !s.P) return; + if (s.palHue !== s.hue) palette(s); + const { buf: B, pal: C, P, z } = s; + B.fill(0); + const add = (x, y, o, k) => { + if (x < 0 || x >= W || y < 0 || y >= H) return; + const i = (y * W + x) * 3; + B[i] += C[o] * k; + B[i + 1] += C[o + 1] * k; + B[i + 2] += C[o + 2] * k; + }; + const kx = pow(P.kap, z), ky = pow(P.sig, z); + const sx = (x) => xOf(P, uOf(P, x) / kx); // x -> screen 0..1 + const xs = (t) => xOf(P, kx * uOf(P, t)); // screen 0..1 -> x + const ry = (y) => P.y0 + (y - P.y0) / ky; // y -> screen 0..1, up + const GHt = GB - GT; + const row = (r) => GB - r * GHt; // screen up 0..1 -> LED row (float) + + // The stair: ? at the 65 column edges. It only rises, so between two + // edges the graph fills exactly the span of the two values. + for (let c = 0; c <= W; c++) s.ys[c] = row(ry(qm(xs(c / W)))); + for (let c = 0; c < W; c++) { + const top = s.ys[c + 1], bot = s.ys[c]; + // Fill from the floor of the box up to the low end of the span, + // brightest just under the rim. + const yTop = max(GT, floor(top)), yBot = min(GB, floor(bot)); + for (let y = GB; y > yBot; y--) add(c, y, 0, 0.18 + 0.5 * exp((-5 * (y - bot)) / GHt)); + // The span itself, a riser where ? climbs and a tread where it is flat. + for (let y = yBot; y >= yTop; y--) if (y >= GT && y <= GB) add(c, y, 3, 0.9); + } + + // Farey marks down to Depth levels below the window's own scale. A level + // grows one period deeper each loop, so rel = level - A z is the same at + // z = 0 and z = 1. + const D = s.depth, maxLev = floor(D + P.A * z) + 1; + s.mk.length = 0; + const xlo = xs(0), xhi = xs(1); + walk(s, P, xlo, xhi, 0, 1, 1, 1, 0, 1, 1, maxLev, true); + const mk = s.mk; + for (let i = 0; i < mk.length; i += 4) { + const rel = mk[i + 2] - P.A * z, w = cl(D + 1 - rel, 0, 1); + if (w <= 0) continue; + const px = floor(sx(mk[i]) * W), py = round(row(ry(mk[i + 1]))); + if (px < 0 || px >= W) continue; + const path = mk[i + 3] === 1; + // A dyadic line from the left edge to its fraction, above the stair, + // and a cut down through the fill under it. Like the ticks of a ruler, + // the deeper the level the fainter both are. + const tick = w * (1 - 0.65 * cl((rel - 1) / max(1, D - 1), 0, 1)); + if (py >= GT && py <= GB) { + for (let x = 0; x < px; x++) add(x, py, path ? 21 : 9, tick); + for (let y = py + 2; y <= GB; y++) { + const i3 = (y * W + px) * 3; + B[i3] *= 1 - 0.7 * tick; + B[i3 + 1] *= 1 - 0.7 * tick; + B[i3 + 2] *= 1 - 0.7 * tick; + } + const o = path ? 12 : 6; + add(px, py, o, w); + if (rel < D - 1.5) add(px, py - 1, o, 0.6 * w), add(px + 1, py, o, 0.6 * w), add(px - 1, py, o, 0.6 * w), add(px, py + 1, o, 0.4 * w); + } + } + + // The point itself, fixed at the centre of the zoom. + const qx = floor(P.x0 * W), qy = round(row(P.y0)); + for (let dy = -2; dy <= 2; dy++) + for (let dx = -2; dx <= 2; dx++) { + const x = qx + dx, y = qy + dy; + if (x < 0 || x >= W || y < 0 || y >= H) continue; + const i3 = (y * W + x) * 3, ring = max(abs(dx), abs(dy)) === 2 && abs(dx) + abs(dy) < 4; + B[i3] *= 0.15; + B[i3 + 1] *= 0.15; + B[i3 + 2] *= 0.15; + if (ring) add(x, y, 12, 1); + } + add(qx, qy, 3, 1.5); + + // Underneath: the continued fraction as blocks a_k cells wide, and the + // binary digits of ?(x0) as cells, noughts dim and ones bright. Each block + // sits over the run of bits it becomes, so a gap in the top bar falls + // wherever the bits below change. Both scroll A cells a loop and the + // digits repeat every A cells, so the loop has no seam. + const bits = P.bits, L = bits.length, off = round(P.A * z * CELL); + for (let x = 0; x < W; x++) { + const q = x + off, k = floor(q / CELL) % L, e = q % CELL, bit = bits[k]; + const last = e === CELL - 1, runEnd = last && bits[(k + 1) % L] !== bit, o = bit ? 18 : 15; + if (!runEnd) for (let y = BAR1; y < BAR1 + 7; y++) add(x, y, o, y === BAR1 || y === BAR1 + 6 ? 0.95 : 0.4); + if (!last) for (let y = BAR2; y < BAR2 + 6; y++) add(x, y, o, bit ? 1 : 0.25); + } + + for (let y = 0, p = 0; y < H; y++) + for (let x = 0; x < W; x++, p += 3) + display.setPixel(x, y, min(255, round(B[p] * 255)), min(255, round(B[p + 1] * 255)), min(255, round(B[p + 2] * 255))); +} From 314942a0b018fc19fc6726dc15593697deb5093f Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 26 Oct 2026 04:32:02 +0000 Subject: [PATCH 04/10] =?UTF-8?q?incoming:=2055=ED=8C=90=20Hex=20Theorem,?= =?UTF-8?q?=20Universal=20Cover?= 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_01ANgZzmaYPUurP5cuTFEV6X --- .../pattern-study/patterns/hex-theorem.js | 385 ++++++++++++++++++ .../pattern-study/patterns/universal-cover.js | 256 ++++++++++++ 2 files changed, 641 insertions(+) create mode 100644 incoming/pattern-study/patterns/hex-theorem.js create mode 100644 incoming/pattern-study/patterns/universal-cover.js diff --git a/incoming/pattern-study/patterns/hex-theorem.js b/incoming/pattern-study/patterns/hex-theorem.js new file mode 100644 index 000000000..7dde27ad4 --- /dev/null +++ b/incoming/pattern-study/patterns/hex-theorem.js @@ -0,0 +1,385 @@ +// ===== Patternflow pattern ===== +// Title: Hex Theorem +// Family: cells +// Idea: A Hex board stands on its long diagonal and fills one random cell at a time until one colour's chain joins that colour's two edges and lights up with a pulse running along it; on the full board the loser's cells flood in from both of its edges and stop against the chain, and Gale's walk threads the seam between the colours from the bottom corner out to the corner that names the winner. +// Author: patternflow-lab +// Date: 2026-10-26 +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Lineage: Hex theorem (David Gale, 1979), on the game of Hex (Piet Hein, 1942; John Nash, 1948), pattern round 55 +// Round: 55 +// =============================== +// @matrix 64x128 +// @knobs Hue=0..1, Speed=0.2..3, Size=7..15, Bias=-1..1 +// +// Knob 1: Hue. Turns every colour round the wheel together. At 0 player A, who owns the lower right and upper left edges, is azure and player B, who owns the other two, is magenta, on dark slate empty cells; a winning chain brightens, its shortest path runs near white, and the loser's two floods take two pale tints of its colour. At 0.25 A is violet and B lime, at 0.5 A rose and B teal, at 0.75 A green and B blue. The wheel skips the stretch from orange to yellow, which reads brown and olive on the dim cells. +// Knob 2: Speed. At 2 a 7 x 7 board fills at 18 cells a second and is full in 2.7 s; a 15 x 15 board fills at 57 a second and is full in 4 s. The win holds the filling for 0.45 s, then the proof takes 1.9 s, the hold 1.3 s and the clearing wave 0.9 s, so a 7 x 7 board turns over every 7.3 s. 0.2 is ten times slower and 3 half as fast again. +// Knob 3: Size. The board's side, 7 to 15 cells. Cells are about 8 LEDs across at 7 and 4 at 15, stood 18 to 21% taller than regular so the long diagonal runs 116 rows. At even odds the first chain shows when 86% of a 7 x 7 board is filled (median of 2,000 boards) and 91% of a 15 x 15 board, and Gale's walk is 32 and 110 edges long. +// Knob 4: Bias. Each cell goes to A with chance 0.5 + 0.25 x Bias, from 0.25 to 0.75, and 0.515 at the opening 0.06. A then wins 56% of 7 x 7 boards and 59% of 15 x 15; at Bias 0.5 (0.625) 87% and 97%. With a bias the chain shows when the winner holds about half the board, the threshold of site percolation on the triangular lattice: after 80% of the cells at 0.625 and 67% at 0.75. + +const W = 64, H = 128, N = W * H; +const { floor, min, max, abs, sqrt, exp, round } = Math; +const R3 = sqrt(3); +const cl = (v, a, b) => (v < a ? a : v > b ? b : v); +// The six neighbours in turn round a cell, in axial coordinates (q, r). +const DQ = [1, 1, 0, -1, -1, 0], DR = [0, -1, -1, 0, 1, 1]; +// Board seconds at Speed 2: a 7 x 7 board fills in FILL, longer boards as +// the square root of their side; then the pause on the win, the proof +// (flood and walk), the hold and the clearing wave. +const FILL = 2.7, PAUSE = 0.45, PROOF = 1.9, HOLD = 1.3, CLEAR = 0.9; +// The two players' hues at knob 0, before the wheel below bends them. +const HUE_A = 0.324, HUE_B = 0.797; + +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; +} +// The knob's wheel skips the stretch from orange through yellow to lime, +// which reads brown and olive on the dim cells. +const wheel = (h) => 0.28 + 0.74 * (h - floor(h)); + +export function setup(params) { + params.s = { + n: 0, hue: 0, speed: 2, p: 0.5, clock: 0, + rgb: new Float32Array(N * 3), cc: null, tmp: new Float32Array(3), + }; +} + +// Pixel to cell, once per board size. Cell (q, r) sits at +// x = (q - r) s / 2, y = -(q + r) h, so (0, 0) is the bottom corner, +// (n-1, n-1) the top, and the rows r = 0 and r = n - 1 are the lower right +// and upper left sides. +function layout(S, n) { + const s = 58 / n; + const ky = min(1.25, 116 / (s * (R3 * (n - 1) + 2 / R3))); + const h = (s * R3 / 2) * ky, ox = 32, oy = 64 + (n - 1) * h; + const cell = new Int16Array(N).fill(-1), shade = new Float32Array(N), side = new Uint8Array(N); + const hexm = (dq, dr) => max(abs(dq + dr / 2), abs(dr + dq / 2), abs((dq - dr) / 2)); + for (let y = 0, p = 0; y < H; y++) + for (let x = 0; x < W; x++, p++) { + const u = (x + 0.5 - ox) / (s / 2), v = (oy - y - 0.5) / h; + const fq = (u + v) / 2, fr = (v - u) / 2, fs = -fq - fr; + let q = round(fq), r = round(fr); + const t = round(fs), eq = abs(q - fq), er = abs(r - fr), es = abs(t - fs); + if (eq > er && eq > es) q = -r - t; + else if (er > es) r = -q - t; + if (q >= 0 && q < n && r >= 0 && r < n) { + cell[p] = r * n + q; + // A soft seam a pixel wide where two cells meet. + shade[p] = cl(((0.5 - hexm(fq - q, fr - r)) * s - 0.3) / 0.9, 0, 1); + continue; + } + // Outside: how far to the nearest cell of the board, for the home edges. + const cq = cl(q, 0, n - 1), cr = cl(r, 0, n - 1); + let m = 9; + for (let k = -1; k < 6; k++) { + const aq = cq + (k < 0 ? 0 : DQ[k]), ar = cr + (k < 0 ? 0 : DR[k]); + if (aq < 0 || aq >= n || ar < 0 || ar >= n) continue; + m = min(m, hexm(fq - aq, fr - ar)); + } + const out = (m - 0.5) * s; + const a = cl(out - 0.4, 0, 1) * cl(2.8 - out, 0, 1); + if (a <= 0) continue; + shade[p] = a; + side[p] = max(-fr, fr - (n - 1)) > max(-fq, fq - (n - 1)) ? 1 : 2; + } + Object.assign(S, { s, h, ox, oy, cell, shade, side }); +} + +function newBoard(S, n) { + if (n !== S.n) layout(S, n), (S.cc = new Float32Array(n * n * 3)); + S.n = n; + const M = n * n; + S.cells = new Uint8Array(M); + S.at = new Float32Array(M).fill(-99); + // Each cell's draw is made as the board is laid and held against Bias + // when the cell's turn comes, so a turn of the knob recolours only the + // cells still to come. + S.u = new Float32Array(M); + S.order = new Int16Array(M); + for (let i = 0; i < M; i++) (S.order[i] = i), (S.u[i] = Math.random()); + for (let i = M - 1; i > 0; i--) { + const j = floor(Math.random() * (i + 1)), t = S.order[i]; + S.order[i] = S.order[j]; + S.order[j] = t; + } + // Union-find over the cells and four side nodes: A's rows r = 0 and + // r = n - 1, then B's columns q = 0 and q = n - 1. + S.par = new Int16Array(M + 4); + for (let i = 0; i < M + 4; i++) S.par[i] = i; + S.k = 0; + S.acc = 0; + S.wait = 0; + S.winner = 0; + S.winAt = 0; + S.path = []; + S.onPath = new Int16Array(M).fill(-1); + S.club = new Uint8Array(M); + S.flood = new Int16Array(M); + S.fmax = 1; + S.walk = null; + S.phase = 0; + S.pt = 0; +} + +function find(P, i) { + while (P[i] !== i) (P[i] = P[P[i]]), (i = P[i]); + return i; +} +function join(P, a, b) { + a = find(P, a); + b = find(P, b); + if (a !== b) P[a] = b; +} + +function place(S) { + const n = S.n, M = n * n, P = S.par, i = S.order[S.k++], c = S.u[i] < S.p ? 1 : 2; + S.cells[i] = c; + S.at[i] = S.clock; + const q = i % n, r = (i / n) | 0; + for (let k = 0; k < 6; k++) { + const aq = q + DQ[k], ar = r + DR[k]; + if (aq >= 0 && aq < n && ar >= 0 && ar < n && S.cells[ar * n + aq] === c) join(P, i, ar * n + aq); + } + if (c === 1) { + if (r === 0) join(P, i, M); + if (r === n - 1) join(P, i, M + 1); + } else { + if (q === 0) join(P, i, M + 2); + if (q === n - 1) join(P, i, M + 3); + } + if (!S.winner) { + if (find(P, M) === find(P, M + 1)) win(S, 1); + else if (find(P, M + 2) === find(P, M + 3)) win(S, 2); + } + if (S.winner) { + // The winning chain's whole cluster, which grows as the board fills. + const root = find(P, S.winner === 1 ? M : M + 2); + for (let j = 0; j < M; j++) S.club[j] = S.cells[j] === S.winner && find(P, j) === root ? 1 : 0; + } +} + +const onSide = (n, c, i, which) => (c === 1 ? ((i / n) | 0) === (which ? n - 1 : 0) : i % n === (which ? n - 1 : 0)); + +// Breadth-first distances through colour c from one of its sides. +function bfs(S, c, which, dist) { + const n = S.n, M = n * n, queue = new Int16Array(M); + dist.fill(-1); + let head = 0, tail = 0; + for (let i = 0; i < M; i++) if (S.cells[i] === c && onSide(n, c, i, which)) (dist[i] = 0), (queue[tail++] = i); + while (head < tail) { + const i = queue[head++], q = i % n, r = (i / n) | 0; + for (let k = 0; k < 6; k++) { + const aq = q + DQ[k], ar = r + DR[k], j = ar * n + aq; + if (aq < 0 || aq >= n || ar < 0 || ar >= n || S.cells[j] !== c || dist[j] >= 0) continue; + dist[j] = dist[i] + 1; + queue[tail++] = j; + } + } +} + +// The first chain: the shortest path through the winner's cells from one +// home edge to the other, found the moment the two edges join. +function win(S, c) { + const n = S.n, M = n * n, d0 = new Int16Array(M); + S.winner = c; + S.winAt = S.clock; + S.wait = PAUSE; + bfs(S, c, 0, d0); + let end = -1; + for (let i = 0; i < M; i++) if (d0[i] >= 0 && onSide(n, c, i, 1) && (end < 0 || d0[i] < d0[end])) end = i; + const path = [end]; + while (d0[path[path.length - 1]] > 0) { + const i = path[path.length - 1], q = i % n, r = (i / n) | 0; + for (let k = 0; k < 6; k++) { + const aq = q + DQ[k], ar = r + DR[k], j = ar * n + aq; + if (aq >= 0 && aq < n && ar >= 0 && ar < n && d0[j] === d0[i] - 1) { + path.push(j); + break; + } + } + } + path.reverse(); + S.path = path; + path.forEach((i, k) => (S.onPath[i] = k)); +} + +// On the full board: the loser's cells reached from each of its edges, by +// distance, and Gale's walk. The walk starts at the bottom corner on the +// seam between the two border rows, and at every corner of a cell it meets +// it turns so that A stays on one hand and B on the other. It cannot close +// or stop, so it leaves at another corner, and which one says who won. +function proof(S) { + const n = S.n, M = n * n, lose = 3 - S.winner, d = new Int16Array(M); + S.flood.fill(-1); + for (const which of [0, 1]) { + bfs(S, lose, which, d); + for (let i = 0; i < M; i++) if (d[i] >= 0) S.flood[i] = which * 1000 + d[i]; + } + S.fmax = 1; + for (let i = 0; i < M; i++) if (S.flood[i] >= 0) S.fmax = max(S.fmax, S.flood[i] % 1000); + const col = (q, r) => { + const qi = q >= 0 && q < n, ri = r >= 0 && r < n; + if (qi && ri) return S.cells[r * n + q]; + if (!qi && !ri) return 0; + return ri ? 2 : 1; + }; + const px = (q, r) => S.ox + ((q - r) * S.s) / 2, py = (q, r) => S.oy - (q + r) * S.h; + const vert = (a, b, c) => [(px(a[0], a[1]) + px(b[0], b[1]) + px(c[0], c[1])) / 3, (py(a[0], a[1]) + py(b[0], b[1]) + py(c[0], c[1])) / 3]; + // The cell beside both a and b that is not x. + const other = (a, b, x) => { + const dq = b[0] - a[0], dr = b[1] - a[1]; + let k = 0; + while (DQ[k] !== dq || DR[k] !== dr) k++; + const u = [a[0] + DQ[(k + 5) % 6], a[1] + DR[(k + 5) % 6]]; + return u[0] === x[0] && u[1] === x[1] ? [a[0] + DQ[(k + 1) % 6], a[1] + DR[(k + 1) % 6]] : u; + }; + let a = [0, -1], b = [-1, 0], back = [-1, -1], ahead = [0, 0]; + const pts = [vert(a, b, back)]; + for (let guard = 0; guard < 4 * M + 20; guard++) { + pts.push(vert(a, b, ahead)); + const c = col(ahead[0], ahead[1]); + if (c === 0) break; + let dropped; + if (c === 1) (dropped = a), (a = ahead); + else (dropped = b), (b = ahead); + ahead = other(a, b, dropped); + } + S.walk = pts; + S.walkEnd = ahead[0] === n ? 2 : 1; +} + +export function update(dt, input, params) { + if (!params.s) setup(params); + const S = params.s, kv = input.knobValues || [0, 2, 7, 0.06], d = cl(dt || 0, 0, 0.1); + S.hue = cl(kv[0], 0, 1); + S.speed = cl(kv[1], 0.2, 3); + const n = cl(floor(kv[2] + 0.5), 7, 15); + S.p = 0.5 + 0.25 * cl(kv[3], -1, 1); + if (n !== S.n) newBoard(S, n); + const g = (d * S.speed) / 2; + S.clock += g; + const M = n * n; + if (S.phase === 0) { + if (S.wait > 0) S.wait -= g; + else { + S.acc += (g * M) / (FILL * sqrt(n / 7)); + while (S.acc >= 1 && S.k < M) { + S.acc -= 1; + const had = S.winner; + place(S); + if (!had && S.winner) break; + } + if (S.k >= M) (S.phase = 1), (S.pt = 0), proof(S); + } + } else { + S.pt += g; + if (S.phase === 1 && S.pt >= PROOF + HOLD) (S.phase = 2), (S.pt = 0); + else if (S.phase === 2 && S.pt >= CLEAR) newBoard(S, n); + } +} + +// Lines for the walk: points every third of a pixel, each laid over its +// four nearest LEDs by how close it falls, so the thread stays white +// across the small cells of a big board. +function plot(rgb, x, y, c, a) { + const fx = x - 0.5, fy = y - 0.5, ix = floor(fx), iy = floor(fy), tx = fx - ix, ty = fy - iy; + for (let dy = 0; dy < 2; dy++) { + const yy = iy + dy; + if (yy < 0 || yy >= H) continue; + const wy = dy ? ty : 1 - ty; + for (let dx = 0; dx < 2; dx++) { + const xx = ix + dx; + if (xx < 0 || xx >= W) continue; + const w = min(1, (dx ? tx : 1 - tx) * wy * 2.5) * a, p = (yy * W + xx) * 3; + for (let k = 0; k < 3; k++) rgb[p + k] += (c[k] - rgb[p + k]) * w; + } + } +} + +export function draw(display, params) { + const S = params.s; + if (!S || !S.cells) return; + const n = S.n, M = n * n, cc = S.cc, rgb = S.rgb, T = S.tmp; + const hA = wheel(HUE_A + S.hue), hB = wheel(HUE_B + S.hue); + const end = S.phase >= 1, pt = S.pt; + const pulse = (S.clock - S.winAt) / 1.3; + for (let i = 0, o = 0; i < M; i++, o += 3) { + const c = S.cells[i]; + if (!c) { + (cc[o] = 0.1), (cc[o + 1] = 0.1), (cc[o + 2] = 0.15); + continue; + } + const hc = c === 1 ? hA : hB; + // Once a chain has won, the other cells step back. + let s = 0.85, v = S.winner ? 0.5 : 0.62; + if (S.club[i]) (s = 0.72), (v = 1); + if (end && !S.club[i]) { + // The full board: the loser's cells light up from each of its edges, + // a little apart on the wheel, and stop against the chain. + const f = S.flood[i]; + const at = f >= 0 ? (f % 1000) / S.fmax : 2; + const lit = S.phase === 2 ? (f >= 0 ? 1 : 0) : cl((pt / (PROOF * 0.6) - at) * 3, 0, 1); + hsv(hc + lit * (f >= 1000 ? 0.035 : -0.035), 0.62 - 0.17 * lit, 0.3 + 0.5 * lit, cc, o); + } else hsv(hc, s, v, cc, o); + const k = S.onPath[i]; + if (k >= 0) { + // The chain lights from end to end at 30 cells a second, then a pulse runs along it. + const shown = (S.clock - S.winAt) * 30 - k; + if (shown > 0) { + const ph = pulse - floor(pulse) - k / S.path.length; + const w = 0.4 + 0.55 * exp(-((ph - round(ph)) ** 2) * 60) + 0.4 * exp(-shown / 6); + for (let e = 0; e < 3; e++) cc[o + e] += (1 - cc[o + e]) * min(1, w); + } + } + // A fresh cell flashes white. + const age = S.clock - S.at[i]; + if (age < 0.4) { + const f = 0.85 * exp(-age / 0.1); + for (let e = 0; e < 3; e++) cc[o + e] += (1 - cc[o + e]) * f; + } + } + // The clearing wave runs down the board. + let cut = -1; + if (S.phase === 2) cut = (pt / CLEAR) * (H + 30) - 15; + hsv(hA, 0.9, 1, T, 0); + const eA = [T[0], T[1], T[2]]; + hsv(hB, 0.9, 1, T, 0); + const eB = [T[0], T[1], T[2]]; + const { cell, shade, side } = S; + for (let p = 0, o = 0; p < N; p++, o += 3) { + const i = cell[p]; + if (i >= 0) { + let a = shade[p]; + if (cut > -2) a *= cl((floor(p / W) - cut) / 12, 0, 1); + (rgb[o] = cc[i * 3] * a), (rgb[o + 1] = cc[i * 3 + 1] * a), (rgb[o + 2] = cc[i * 3 + 2] * a); + } else if (side[p]) { + const e = side[p] === 1 ? eA : eB, a = shade[p]; + (rgb[o] = e[0] * a), (rgb[o + 1] = e[1] * a), (rgb[o + 2] = e[2] * a); + } else rgb[o] = rgb[o + 1] = rgb[o + 2] = 0; + } + // Gale's walk, drawn in along the seams after the flood has started, + // with a spark at its head; once it is out, the spark runs it again + // from the bottom corner every 1.2 seconds. + if (S.phase >= 1 && S.walk) { + const pts = S.walk, L = pts.length - 1, drawn = S.phase === 1 && pt < PROOF; + const f = drawn ? cl((pt - PROOF * 0.25) / (PROOF * 0.75), 0, 1) * L : L; + const head = drawn ? f : (((S.phase === 1 ? pt - PROOF : pt + HOLD) / 1.2) % 1) * L; + const col = [0.95, 0.95, 0.86], hot = [1, 1, 1]; + for (let k = 0; k < L && k < f; k++) { + const [xa, ya] = pts[k], [xb, yb] = pts[k + 1], u = min(1, f - k); + const len = Math.hypot(xb - xa, yb - ya), m = Math.ceil(len * 3 * u) || 1; + for (let e = 0; e <= m; e++) { + const y = ya + ((yb - ya) * u * e) / m, a = cut > -2 ? cl((y - cut) / 12, 0, 1) : 1; + if (a <= 0) continue; + const x = xa + ((xb - xa) * u * e) / m, near = cl(1 - abs(k + (u * e) / m - head) / 3, 0, 1); + plot(rgb, x, y, near > 0 ? hot : col, (0.8 + 0.2 * near) * a); + if (near > 0.5) for (const [dx, dy] of [[-0.6, 0], [0.6, 0], [0, -0.6], [0, 0.6]]) plot(rgb, x + dx, y + dy, hot, near * a); + } + } + } + for (let y = 0, o = 0; y < H; y++) + for (let x = 0; x < W; x++, o += 3) display.setPixel(x, y, (255 * cl(rgb[o], 0, 1)) | 0, (255 * cl(rgb[o + 1], 0, 1)) | 0, (255 * cl(rgb[o + 2], 0, 1)) | 0); +} diff --git a/incoming/pattern-study/patterns/universal-cover.js b/incoming/pattern-study/patterns/universal-cover.js new file mode 100644 index 000000000..8ae9cc960 --- /dev/null +++ b/incoming/pattern-study/patterns/universal-cover.js @@ -0,0 +1,256 @@ +// ===== Patternflow pattern ===== +// Title: Universal Cover +// Family: topology +// Idea: A dot loops round a figure-eight at the foot of the panel, one loop a step, while its lift walks the four-way tree above that covers it, the fractal cross of the free group on a and b; the view re-roots on the lift every step so the tree slides and regrows round it, and the reduced word, drawn back to the root and spelled in a strip of blocks, grows by half a letter a step unless Return pulls the lift home. +// Author: patternflow-lab +// Date: 2026-10-26 +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Lineage: universal cover of the figure-eight, the Cayley graph of the free group F2 (Henri Poincaré, 1895; Max Dehn, 1910), with the escape of random walk on the free group (Harry Kesten, 1959), pattern round 55 +// Round: 55 +// =============================== +// @matrix 64x128 +// @knobs Hue=0..1, Speed=0.2..3, Return=0.25..0.85, Depth=-1..1 +// +// Knob 1: Hue. Turns every colour round the wheel together. At 0 the letter a, the left loop and every horizontal edge, is aqua and b, the right loop and every vertical edge, is magenta; the word's path runs in pale tints of the two, the dot and its lift are white and the root wears a gold ring. At 0.25 a is blue and b red, at 0.5 a pink and b aqua, at 0.75 a green and b blue. The wheel skips orange, yellow and lime, which read brown and olive on the dim outer branches. +// Knob 2: Speed. At 2 a step, one loop of the figure-eight below and one edge of the tree above, takes 0.75 s. The lift leaves first and the view follows a quarter of a step behind, so the lift runs up to a quarter of its edge ahead of the centre, 4 LEDs on an a edge and 5.6 on a b edge, before the tree slides under it. Every vertex of the tree lies over the wedge point, and all of them blink as the dot passes it. 0.2 is ten times slower and 3 half as fast again. +// Knob 3: Return. The chance a step cancels the word's last letter and sends the lift back towards the root; the other three letters share the rest, and from the root all four lead away. At 0.25 that is the simple random walk, every letter equally likely: the word grows by half a letter a step, Kesten's escape speed, and was 50.2 letters long after 100 steps (mean of 300 runs). At 0.5 the length is a fair coin's walk, 8.3 letters after 100 steps; at 0.6 it averages 2.5, and at 0.85 the lift sits on the root 41% of the time and the word averages 0.71 letters. +// Knob 4: Depth. How many levels of the tree are drawn round the lift, 4 + 2.5 x Depth, from 1.5 to 6.5, the last one fading in. The lift's own edges lengthen as levels drop so the drawing stays 60 LEDs wide: an a edge is 23 LEDs at -1, 15.9 at the opening 0.06 and 15.2 at 1, and a b edge 1.4 times that. Each level halves the edge and dims it by a fifth. + +const W = 64, H = 128, N = W * H; +const { floor, min, max, exp, cos, sin, PI, LN2 } = Math; +const TAU = 2 * PI; +const cl = (v, a, b) => (v < a ? a : v > b ? b : v); +const ss = (t) => t * t * (3 - 2 * t); +// Letters: 0 a, 1 a^-1, 2 b, 3 b^-1; the inverse of l is l ^ 1. a runs right +// in the tree, b runs up. +const DX = [1, -1, 0, 0], DY = [0, 0, -1, 1]; +// The tree's centre, how much longer a b edge is drawn than an a edge, the +// strip that spells the word, and the figure-eight's wedge point and radius. +const TX = 32, TY = 43, KB = 1.4, WY = 88, BX = 32, BY = 110, BR = 14.5; +// Seconds a step takes at Speed 2. +const STEP = 0.75; +const WHITE = [1, 1, 1]; +const HUE_A = 0.276, HUE_B = 0.73; + +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; +} +// The wheel skips orange, yellow and lime, which read brown and olive on +// the dim outer branches. +const wheel = (h) => 0.28 + 0.74 * (h - floor(h)); + +export function setup(params) { + params.s = { + hue: 0, speed: 2, ret: 0.25, depth: 0, L0: 15, levels: 4, + word: [], x: 0, back: false, t: 0, steps: 0, at: 1, + rgb: new Float32Array(N * 3), col: new Float32Array(12), c: 0, w: 0, + }; + next(params.s); +} + +// The next step: back along the word's last letter with chance Return, or +// on along one of the other three. From the root all four lead away. +function next(S) { + const word = S.word, last = word.length ? word[word.length - 1] : -1; + if (last >= 0 && Math.random() < S.ret) S.x = last ^ 1; + else { + const k = floor(Math.random() * (last >= 0 ? 3 : 4)); + let l = 0; + for (let seen = -1; ; l++) if (l !== (last ^ 1) || last < 0) if (++seen === k) break; + S.x = l; + } + S.back = last >= 0 && S.x === (last ^ 1); + S.t = 0; +} + +export function update(dt, input, params) { + if (!params.s) setup(params); + const S = params.s, kv = input.knobValues || [0, 2, 0.25, 0.06], d = cl(dt || 0, 0, 0.1); + S.hue = cl(kv[0], 0, 1); + S.speed = cl(kv[1], 0.2, 3); + S.ret = cl(kv[2], 0.25, 0.85); + S.depth = cl(kv[3], -1, 1); + // Levels of the tree drawn round the lift, the last one fading in. + S.levels = 4 + 2.5 * S.depth; + // The lift's own edges are as long as keeps the drawn tree 60 LEDs wide: + // the levels add up to 2 - 2^(1 - levels) edges each way. + S.L0 = 30 / (2 - Math.pow(2, 1 - S.levels)); + S.t += (d * S.speed) / 2 / STEP; + if (S.t >= 1) { + if (S.back) S.word.pop(); + else S.word.push(S.x); + S.steps++; + next(S); + } + // The dot and its lift ease out of the wedge point and into the next + // node; the view follows a little behind. + S.w = ss(cl(S.t, 0, 1)); + S.c = ss(cl((S.t - 0.25) / 0.75, 0, 1)); + // Near either end of a step the dot is at the wedge point and the lift + // at a vertex. + S.at = exp(-min(S.t, 1 - S.t) / 0.07); +} + +// The tree stops above the strip, so a zoomed-in tree never runs over +// the figure-eight. +let YMAX = H; +function plot(rgb, x, y, c, o, a) { + const fx = x - 0.5, fy = y - 0.5, ix = floor(fx), iy = floor(fy), tx = fx - ix, ty = fy - iy; + for (let dy = 0; dy < 2; dy++) { + const yy = iy + dy; + if (yy < 0 || yy >= YMAX) continue; + const wy = dy ? ty : 1 - ty; + for (let dx = 0; dx < 2; dx++) { + const xx = ix + dx; + if (xx < 0 || xx >= W) continue; + const w = min(1, (dx ? tx : 1 - tx) * wy * 1.8) * a, p = (yy * W + xx) * 3; + if (rgb[p] < c[o] * w) rgb[p] = c[o] * w; + if (rgb[p + 1] < c[o + 1] * w) rgb[p + 1] = c[o + 1] * w; + if (rgb[p + 2] < c[o + 2] * w) rgb[p + 2] = c[o + 2] * w; + } + } +} +function seg(rgb, xa, ya, xb, yb, c, o, a) { + if (max(xa, xb) < -1 || min(xa, xb) > W + 1 || max(ya, yb) < -1 || min(ya, yb) > H + 1) return; + const n = Math.ceil(Math.hypot(xb - xa, yb - ya) * 3) || 1; + for (let e = 0; e <= n; e++) plot(rgb, xa + ((xb - xa) * e) / n, ya + ((yb - ya) * e) / n, c, o, a); +} + +// The tree drawn from u, the node the lift leaves. An edge is drawn +// L0 / 2^e long, e being its nearer end's distance from the root of the +// view: from u at the start of a step and from v at the end, so e slides +// from d to d - 1 on v's side and to d + 1 elsewhere while the view +// follows. The edge from u to v keeps e = 0. +function grow(S, rgb, x, y, d, inV, last) { + const c = S.c, L0 = S.L0, col = S.col; + for (let l = 0; l < 4; l++) { + if (last >= 0 && l === (last ^ 1)) continue; + const e = d === 0 ? (l === S.x ? 0 : c) : inV ? d - c : d + c; + const len = L0 * exp(-e * LN2) * (l >= 2 ? KB : 1); + if (e >= S.levels || len < 0.5) continue; + const nx = x + DX[l] * len, ny = y + DY[l] * len; + const a = Math.pow(0.8, e) * cl(len / 2, 0.3, 1) * cl(S.levels - e, 0, 1); + // Edges near the lift are drawn up to two LEDs wide. + const half = e < 1 ? 0.45 * (1 - e) : 0; + if (half > 0.05) { + const ox = l >= 2 ? half : 0, oy = l >= 2 ? 0 : half; + seg(rgb, x - ox, y - oy, nx - ox, ny - oy, col, l >= 2 ? 3 : 0, a); + seg(rgb, x + ox, y + oy, nx + ox, ny + oy, col, l >= 2 ? 3 : 0, a); + } else seg(rgb, x, y, nx, ny, col, l >= 2 ? 3 : 0, a); + // Every vertex lies over the wedge point, and they all blink as the + // dot passes it. + if (S.at > 0.03 && len > 1.2) plot(rgb, nx, ny, WHITE, 0, S.at * a * 1.2); + grow(S, rgb, nx, ny, d + 1, d === 0 ? l === S.x : inV, l); + } +} + +export function draw(display, params) { + const S = params.s; + if (!S) return; + const rgb = S.rgb, col = S.col, L0 = S.L0, x = S.x; + rgb.fill(0); + const hA = wheel(HUE_A + S.hue), hB = wheel(HUE_B + S.hue); + hsv(hA, 0.8, 1, col, 0); // a edges + hsv(hB, 0.75, 1, col, 3); // b edges + hsv(hA, 0.35, 1, col, 6); // a on the word's path + hsv(hB, 0.3, 1, col, 9); // b on the word's path + // u sits back from the centre by the share of its edge to v that the + // view has covered. + const luv = L0 * (x >= 2 ? KB : 1); + const ux = TX - DX[x] * luv * S.c, uy = TY - DY[x] * luv * S.c; + YMAX = WY - 2; + grow(S, rgb, ux, uy, 0, false, -1); + + // The reduced word, drawn back from u to the root. On a step that + // cancels the last letter the edge to v is that letter, and it goes out + // behind the lift as the lift runs down it. + const word = S.word, m = word.length; + let px = ux, py = uy, rootSeen = 0; + for (let j = m - 1, dd = 0; j >= 0; j--, dd++) { + const l = word[j] ^ 1, inV = dd === 0 ? l === x : (word[m - 1] ^ 1) === x; + const e = dd === 0 ? (l === x ? 0 : S.c) : inV ? dd - S.c : dd + S.c; + const len = L0 * exp(-e * LN2) * (l >= 2 ? KB : 1); + let sx = px, sy = py; + if (dd === 0 && S.back) (sx = px + DX[l] * len * S.w), (sy = py + DY[l] * len * S.w); + const nx = px + DX[l] * len, ny = py + DY[l] * len; + for (const r of [-0.5, 0.5]) { + const ox = l >= 2 ? r : 0, oy = l >= 2 ? 0 : r; + seg(rgb, sx + ox, sy + oy, nx + ox, ny + oy, col, l >= 2 ? 9 : 6, 1); + } + (px = nx), (py = ny); + if (len < 0.3) break; + if (j === 0) rootSeen = cl(len / 2, 0, 1); + } + if (m === 0) rootSeen = 1; + // The step itself: on a step away from the root the word gains x, drawn + // out behind the lift. + const lx = ux + DX[x] * luv * S.w, ly = uy + DY[x] * luv * S.w; + if (!S.back) + for (const r of [-0.5, 0.5]) { + const ox = x >= 2 ? r : 0, oy = x >= 2 ? 0 : r; + seg(rgb, ux + ox, uy + oy, lx + ox, ly + oy, col, x >= 2 ? 9 : 6, 1); + } + const gold = [1, 0.93, 0.7]; + // The root, the identity, while it is near enough to see. + if (rootSeen > 0) { + const rx = m === 0 ? ux : px, ry = m === 0 ? uy : py; + for (let k = 0; k < 24; k++) plot(rgb, rx + 2.6 * cos((TAU * k) / 24), ry + 2.6 * sin((TAU * k) / 24), gold, 0, rootSeen); + } + // The lift. + for (let yy = -3; yy <= 3; yy++) + for (let xx = -3; xx <= 3; xx++) if (Math.hypot(xx, yy) <= 2.6) plot(rgb, lx + xx * 0.6, ly + yy * 0.6, WHITE, 0, 1); + YMAX = H; + + // The word as a strip, oldest letter at the left: a in its colour, b in + // its own, the inverses at half strength. Past 16 letters the start slides off. + const shown = S.back ? m - S.w : m + (S.t > 0 ? S.w : 0); + const total = Math.ceil(shown), first = max(0, total - 16); + for (let j = first; j < total; j++) { + const l = j < m ? word[j] : x, a = j < shown - 1 ? 1 : shown - j; + const bright = (l & 1 ? 0.45 : 1) * a, o = l >= 2 ? 9 : 6; + const x0 = 1 + (j - first) * 4; + for (let yy = WY; yy < WY + 3; yy++) + for (let xx = x0; xx < x0 + 3; xx++) { + const p = (yy * W + xx) * 3; + for (let k = 0; k < 3; k++) rgb[p + k] = max(rgb[p + k], col[o + k] * bright); + } + } + + // The figure-eight: loop a on the left, loop b on the right, both run + // anticlockwise from the wedge point, and the dot goes round the loop of + // this step's letter, backwards for an inverse. + for (let k = 0; k < 200; k++) { + const th = (TAU * k) / 200; + plot(rgb, BX - BR + BR * cos(th), BY - BR * sin(th), col, 0, 0.7); + plot(rgb, BX + BR - BR * cos(th), BY + BR * sin(th), col, 3, 0.7); + } + // Arrowheads at the tops of the loops show which way the letter runs. + for (const side of [-1, 1]) { + const cx = BX + side * BR, ty = BY - BR, o = side < 0 ? 0 : 3; + for (let k = 1; k <= 3; k++) { + plot(rgb, cx - 1.2 + k, ty - k * 0.9, col, o, 0.9); + plot(rgb, cx - 1.2 + k, ty + k * 0.9, col, o, 0.9); + } + } + const loopA = x < 2, sgn = x & 1 ? -1 : 1, o = loopA ? 6 : 9; + const at = (u) => { + const th = sgn * TAU * u; + return loopA ? [BX - BR + BR * cos(th), BY - BR * sin(th)] : [BX + BR - BR * cos(th), BY + BR * sin(th)]; + }; + const steps = floor(S.w * 120); + for (let k = 0; k <= steps; k++) { + const u = (k / 120), [qx, qy] = at(u); + plot(rgb, qx, qy, col, o, 0.35 + 0.65 * (u / max(S.w, 1e-3))); + } + if (S.at > 0.03) for (let k = 0; k < 28; k++) plot(rgb, BX + 3.4 * cos((TAU * k) / 28), BY + 3.4 * sin((TAU * k) / 28), WHITE, 0, S.at); + const [dx, dy] = at(S.w); + for (let yy = -3; yy <= 3; yy++) + for (let xx = -3; xx <= 3; xx++) if (Math.hypot(xx, yy) <= 2.6) plot(rgb, dx + xx * 0.6, dy + yy * 0.6, WHITE, 0, 1); + + for (let y = 0, p = 0; y < H; y++) + for (let xx = 0; xx < W; xx++, p += 3) display.setPixel(xx, y, (255 * cl(rgb[p], 0, 1)) | 0, (255 * cl(rgb[p + 1], 0, 1)) | 0, (255 * cl(rgb[p + 2], 0, 1)) | 0); +} From 1b8496760f0ab51302ac8b7b242135e33f49da57 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 26 Oct 2026 04:33:03 +0000 Subject: [PATCH 05/10] =?UTF-8?q?incoming:=2055=ED=8C=90=20Toussaint-Wilcz?= =?UTF-8?q?ek,=20Vortex=20Sheet?= 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_01ANgZzmaYPUurP5cuTFEV6X --- .../patterns/toussaint-wilczek.js | 212 +++++++++++ .../pattern-study/patterns/vortex-sheet.js | 334 ++++++++++++++++++ 2 files changed, 546 insertions(+) create mode 100644 incoming/pattern-study/patterns/toussaint-wilczek.js create mode 100644 incoming/pattern-study/patterns/vortex-sheet.js diff --git a/incoming/pattern-study/patterns/toussaint-wilczek.js b/incoming/pattern-study/patterns/toussaint-wilczek.js new file mode 100644 index 000000000..8f6c73815 --- /dev/null +++ b/incoming/pattern-study/patterns/toussaint-wilczek.js @@ -0,0 +1,212 @@ +// ===== Patternflow pattern ===== +// Title: Toussaint-Wilczek +// Family: particle +// Idea: Two species of random walkers sprinkled evenly over the panel annihilate wherever they meet, and the chance surplus of one or the other in each patch survives as green and magenta domains that grow like the square root of time behind dark fizzing seams. +// Author: patternflow-lab +// Date: 2026-10-26 +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Lineage: diffusion-limited annihilation A + B -> 0 (A. A. Ovchinnikov and Ya. B. Zeldovich, 1978; Doug Toussaint and Frank Wilczek, 1983), pattern round 55 +// Round: 55 +// =============================== +// @matrix 64x128 +// @knobs Hue=0..1, Speed=0.2..3, Density=80..320, Excess=0..1 +// +// Knob 1: Hue. Turns both species round the wheel together, B always 0.47 of a turn from A. At 0 A is spring green and B magenta; at 0.25 blue and orange; at 0.6 red and aquamarine. The dim glow of each domain behind its walkers skips the orange and yellow part of the wheel, which reads brown and olive when dark. +// Knob 2: Speed. Hops a second for every walker, 4 for each unit: 8 at 2, 0.8 at 0.2, 12 at 3. The panel is sprinkled again after 100 hops, every 12.5 seconds at 2, so the 2 to 8 second window sees hops 16 to 64, where a row crosses a domain in 16 LEDs on average at the start and 29 at the end, the square root of four times the hops. +// Knob 3: Density. How many A and how many B each LED gets at the sprinkle, as Poisson means, from 80 to 320. Most cancel on the spot and about 10 a LED are left at 80 (20 at 320). The domains grow at the same pace at any Density, and only their fill changes: after 40 hops 0.71 walkers a LED are left at 80 and 1.40 at 320, the square root of four times as many, and 45% or 64% of the LEDs hold one. +// Knob 4: Excess. A surplus of A over B at the sprinkle, 1.6 x knob^2 a LED: 0.006 at the opening 0.06, 47 walkers on the whole panel, 0.4 at 0.5 and 1.6 at 1. The surplus never cancels, so the magenta minority dies out while the green fills in: at 1 the 2,011 B left after 8 hops are 4 after 96, and at 0.5 the 4,881 are 556. +// Buttons: K1 tap sprinkles the panel afresh. + +const W = 64, H = 128, N = W * H; +const { floor, min, max, abs, sqrt, exp, log, cos, PI } = Math; +const cl = (v, a, b) => (v < a ? a : v > b ? b : v); +// Hops per walker per second at Speed 1. At 8 hops a second the 2 to 8 +// second window spans hops 16 to 64, where the LEDs holding a walker fall +// from 59% to 39%: dense enough to read every domain, sparse enough that +// the seams between them are already dark. +const RATE = 4; +// Hops per walker before the panel is sprinkled again. By 100 a row +// crosses a domain in 37 LEDs on average, on a panel 64 wide, and the +// walls start to set the domains' size instead of the walk. +const LIFE = 100; +// Hue of species A at knob 0; B sits opposite. +const HUE_A = 0.38; + +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; +} +// Dim orange and yellow read brown and olive, so the glow's hue wheel skips +// 0.03..0.18 and runs the rest a little faster. +const glowHue = (h) => 0.18 + 0.85 * (h - 0.18 - floor(h - 0.18)); + +export function setup(params) { + let seed = (Math.random() * 4294967296) >>> 0 || 1; + params.s = { + // Signed count per LED: A is positive, B negative. An A and a B on one + // LED cancel at once, so a sign says which species is there. + m: new Int32Array(N), pos: new Int32Array(N), neg: new Int32Array(N), + spark: new Float32Array(N), glow: new Float32Array(N), tmp: new Float32Array(N), + hops: 0, hue: 0, speed: 2, n0: 80, ex: 0.06, born: false, extinct: 0, + rnd: () => { + seed ^= seed << 13; + seed ^= seed >>> 17; + seed ^= seed << 5; + return seed >>> 0; + }, + pal: new Float32Array(24), palHue: -1, rgb: new Float32Array(N * 3), + }; +} + +// A Poisson count, by its normal: the means here are 80 and up. +function poisson(S, l) { + const u = (S.rnd() + 1) / 4294967297, v = S.rnd() / 4294967296; + return max(0, floor(l + sqrt(l) * sqrt(-2 * log(u)) * cos(2 * PI * v) + 0.5)); +} + +// Every LED gets a Poisson number of A and of B; what is left after they +// cancel on the spot is the starting field. Its sign is white noise, and +// the walk smooths it: a patch L LEDs wide keeps a surplus of about L times +// the square root of the Density, so the density left falls as one over +// the square root of the hops (Toussaint and Wilczek's t^(-d/4) in two +// dimensions), not as one over the hops the well-stirred rate law gives. +// Over 5 to 80 hops it fell with exponent -0.49 at Density 80. +function sprinkle(S) { + const d = 1.6 * S.ex * S.ex; + let sum = 0; + for (let i = 0; i < N; i++) sum += S.m[i] = poisson(S, S.n0) - poisson(S, S.n0); + // The two species start exactly level, or level but for the Excess: a + // chance surplus over the whole panel would hand it to one colour within + // seconds. Each correction adds or cancels one particle on a random LED. + for (let k = Math.round(d * N) - sum, s = k > 0 ? 1 : -1; k !== 0; k -= s) S.m[S.rnd() % N] += s; + S.hops = 0; + S.extinct = 0; + S.spark.fill(0); +} + +// One sub-step: each walker hops to a random neighbour with chance p, the +// walls reflect, and opposite signs arriving on one LED annihilate in pairs. +// The signed sum is the whole reaction; the spark marks where it happened. +function step(S, p) { + const { m, pos, neg, spark } = S; + pos.fill(0); + neg.fill(0); + const P = floor(p * 1073741824); + let nA = 0, nB = 0; + for (let y = 0, i = 0; y < H; y++) + for (let x = 0; x < W; x++, i++) { + let k = m[i]; + if (!k) continue; + const to = k > 0 ? pos : neg; + k = abs(k); + if (m[i] > 0) nA += k; + else nB += k; + for (let j = 0; j < k; j++) { + const r = S.rnd(); + if (r >>> 2 >= P) { + to[i]++; + continue; + } + const d = r & 3; + let xx = x, yy = y; + if (d === 0) xx = x + 1 < W ? x + 1 : x - 1; + else if (d === 1) xx = x > 0 ? x - 1 : x + 1; + else if (d === 2) yy = y + 1 < H ? y + 1 : y - 1; + else yy = y > 0 ? y - 1 : y + 1; + to[yy * W + xx]++; + } + } + for (let i = 0; i < N; i++) { + const a = pos[i], b = neg[i]; + m[i] = a - b; + if (a && b) spark[i] = 1; + } + S.nA = nA; + S.nB = nB; +} + +export function update(dt, input, params) { + if (!params.s) setup(params); + const S = params.s, kv = input.knobValues || [0, 2, 80, 0.06], d = cl(dt || 0, 0, 0.1); + S.hue = cl(kv[0], 0, 1); + S.speed = cl(kv[1], 0.2, 3); + S.n0 = cl(kv[2], 80, 320); + S.ex = cl(kv[3], 0, 1); + const tap = input.btnPressed && input.btnPressed[0]; + if (!S.born || tap || S.hops > LIFE || S.extinct > 40) { + sprinkle(S); + S.born = true; + } + // Hops this frame, split into sub-steps of at most one hop each, so a walker + // moves on some frames and not others instead of jumping two LEDs. + const want = d * S.speed * RATE, n = Math.ceil(want - 1e-9); + for (let k = 0; k < n; k++) step(S, want / n); + S.hops += want; + if (!S.nA || !S.nB) S.extinct += want; + for (let i = 0; i < N; i++) S.spark[i] *= exp(-d * 3.5); + // The glow: the signed field blurred by two box passes of radius 2, scaled + // by its own spread so it holds its brightness while the walkers thin out. + const { m, glow, tmp } = S; + for (let i = 0; i < N; i++) glow[i] = m[i]; + for (let pass = 0; pass < 2; pass++) { + for (let y = 0; y < H; y++) { + let acc = 0; + const o = y * W; + for (let x = -2; x <= 2; x++) acc += glow[o + cl(x, 0, W - 1)]; + for (let x = 0; x < W; x++) { + tmp[o + x] = acc / 5; + acc += glow[o + min(W - 1, x + 3)] - glow[o + max(0, x - 2)]; + } + } + for (let x = 0; x < W; x++) { + let acc = 0; + for (let y = -2; y <= 2; y++) acc += tmp[cl(y, 0, H - 1) * W + x]; + for (let y = 0; y < H; y++) { + glow[y * W + x] = acc / 5; + acc += tmp[min(H - 1, y + 3) * W + x] - tmp[max(0, y - 2) * W + x]; + } + } + } + let ss = 0; + for (let i = 0; i < N; i++) ss += glow[i] * glow[i]; + S.rms = sqrt(ss / N) || 1; +} + +function palette(S) { + const P = S.pal, h = HUE_A + S.hue; + hsv(h, 0.8, 1, P, 0); // A particle + hsv(h + 0.47, 0.75, 1, P, 3); // B particle + hsv(glowHue(h), 0.85, 1, P, 6); // A glow + hsv(glowHue(h + 0.47), 0.85, 1, P, 9); // B glow + S.palHue = S.hue; +} + +export function draw(display, params) { + const S = params.s; + if (!S) return; + if (S.palHue !== S.hue) palette(S); + const { m, glow, spark, pal: P, rgb } = S; + const inv = 1 / (1.6 * S.rms); + for (let i = 0, o = 0; i < N; i++, o += 3) { + const k = m[i]; + if (k) { + const c = k > 0 ? 0 : 3, a = abs(k), L = a === 1 ? 0.93 : a === 2 ? 0.97 : 1; + rgb[o] = P[c] * L; + rgb[o + 1] = P[c + 1] * L; + rgb[o + 2] = P[c + 2] * L; + continue; + } + // Gaps stay dark against the walkers, which keeps the speckle sharp; a + // gap next to walkers lifts a little, so steps between gaps are soft. + const x = i % W, nb = (x + 1 < W && m[i + 1] ? 1 : 0) + (x > 0 && m[i - 1] ? 1 : 0) + (i + W < N && m[i + W] ? 1 : 0) + (i >= W && m[i - W] ? 1 : 0); + const g = glow[i] * inv, c = g > 0 ? 6 : 9, a = min(1, abs(g)) * 0.2 + 0.01 + 0.01 * nb, sp = spark[i] * 0.45; + rgb[o] = P[c] * a + sp; + rgb[o + 1] = P[c + 1] * a + sp; + rgb[o + 2] = P[c + 2] * a + sp; + } + for (let y = 0, o = 0; y < H; y++) + for (let x = 0; x < W; x++, o += 3) display.setPixel(x, y, (255 * min(1, rgb[o])) | 0, (255 * min(1, rgb[o + 1])) | 0, (255 * min(1, rgb[o + 2])) | 0); +} diff --git a/incoming/pattern-study/patterns/vortex-sheet.js b/incoming/pattern-study/patterns/vortex-sheet.js new file mode 100644 index 000000000..b3635e207 --- /dev/null +++ b/incoming/pattern-study/patterns/vortex-sheet.js @@ -0,0 +1,334 @@ +// ===== Patternflow pattern ===== +// Title: Vortex Sheet +// Family: sim +// Idea: A vortex sheet runs up the panel between a stream flowing down on its left and one flowing up on its right, and a small ripple on it grows and rolls up into a double-branched spiral that winds the two fluids into each other in thinner and thinner layers. +// Author: patternflow-lab +// Date: 2026-10-26 +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Lineage: Kelvin-Helmholtz instability of a vortex sheet (Hermann von Helmholtz, 1868; Lord Kelvin, 1871), computed past Moore's curvature singularity (Derek Moore, 1979) with Robert Krasny's vortex-blob method (1986), pattern round 55 +// Round: 55 +// =============================== +// @matrix 64x128 +// @knobs Hue=0..1, Speed=0.2..3, Blob=0.15..0.5, Waves=1..4 +// +// Knob 1: Hue. Turns every colour round the wheel together. At 0 the left stream is dark teal with azure tracers, the right one dark violet with magenta tracers, and the sheet pale lavender; at 0.25 indigo and maroon with a pink sheet; at 0.5 maroon and green with a pale yellow one. The dark streams skip the orange and yellow part of the wheel, which reads brown and olive when dim. +// Knob 2: Speed. At 2 each stream runs 22 LEDs a second at one wave on the panel, the ripple grows by e every 1.1 seconds at the opening Blob, and a fresh sheet starts every 8.6 seconds. At 0.2 everything is ten times slower, at 3 half as fast again. +// Knob 3: Blob. Krasny's delta, how far each point vortex of the sheet is smoothed, from 0.15 to 0.5. A smaller blob grows the ripple faster and winds the spiral tighter: the ripple grows at 2.64 a unit of time at 0.15, 2.34 at 0.25 and 1.72 at 0.5, against pi for the bare sheet, and 8 seconds into a sheet the row through the core crosses it 25 times at 0.15, 19 at 0.25, 13 at 0.35 and 9 at 0.5. Below 0.15 the 340 points could no longer hold the core and the sheet frayed. +// Knob 4: Waves. How many wavelengths of the sheet stand on the panel, 1 to 4: one period is 128, 64, 43 or 32 LEDs tall, and each rolls up into its own spiral, so 1 opens on one spiral 62 LEDs wide by 8 seconds and 4 makes a chain of four small ones. The sheet is computed over one period and repeated, so four cost what one does. +// Buttons: K1 tap starts a fresh sheet with a new ripple. + +const W = 64, H = 128, NP = W * H; +const { floor, min, max, sin, cos, exp, round, PI } = Math; +const TAU = 2 * PI; +const cl = (v, a, b) => (v < a ? a : v > b ? b : v); +// Sheet time units per second at Speed 1. In one unit the far streams +// slide one wavelength past each other, half a wavelength each way. +const RATE = 0.175; +// Points on a fresh sheet, the most it may grow to, and the widest gap +// (in wavelengths) before a point is put in between. 340 points keep a +// frame near 2 ms; at the opening Blob a sheet has them all after about 3.5 +// seconds, and the core goes on winding with the points it has. +const N0 = 96, NMAX = 340, GAP = 0.015; +// A sheet is started again at this age, when the core has wound into a +// disc the LEDs no longer part. +const AGE = 3.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; +} +// Dim orange and yellow read brown and olive, so the streams' hue wheel +// skips 0.03..0.18 and runs the rest a little faster. +const dimHue = (h) => 0.18 + 0.85 * (h - 0.18 - floor(h - 0.18)); + +export function setup(params) { + const M = NMAX + 8; + params.s = { + n: 0, x: new Float64Array(M), y: new Float64Array(M), g: new Float64Array(M), w: new Float64Array(M), + // RK4 stages, and the trig of each point packed for the kernel. + k: Array.from({ length: 8 }, () => new Float64Array(M)), x0: new Float64Array(M), y0: new Float64Array(M), + pk: new Float64Array(M * 5), + tx: new Float64Array(600), ty: new Float64Array(600), nt: 0, tWaves: 0, + tu: new Float64Array(600), tv: new Float64Array(600), + t: 0, born: false, hue: 0, speed: 2, delta: 0.15, waves: 1, + side: new Uint8Array(NP), flip: new Int16Array((W + 2) * H), rgb: new Float32Array(NP * 3), old: new Float32Array(NP * 3), fade: 0, + pal: new Float32Array(24), palHue: -1, + }; +} + +// A fresh sheet: Krasny's ripple x = G + a sin 2 pi G, y = -a sin 2 pi G, +// with a random phase and size, so each roll-up stands at its own height +// and comes a little sooner or later. A second harmonic was tried for +// variety; below Blob 0.25 it rolled up into a second small spiral that +// tangled with the first. +function start(S) { + const R = Math.random, a = 0.035 + 0.02 * R(), ph = R(); + S.n = N0; + for (let i = 0; i < N0; i++) { + const G = i / N0, e = a * sin(TAU * (G + ph)); + S.g[i] = G; + S.x[i] = G + e; + S.y[i] = -e; + } + S.t = 0; + S.nt = 0; +} + +// Circulation carried by each point: half the gap in G to each neighbour. +function weights(S) { + const { n, g, w } = S; + for (let i = 0; i < n; i++) { + const a = i ? g[i - 1] : g[n - 1] - 1, b = i + 1 < n ? g[i + 1] : g[0] + 1; + w[i] = (b - a) / 2; + } +} + +// The blob kernel for one period of the sheet, with total circulation 1: +// u_i = -1/2 sum w_j sinh 2pi(y_i - y_j) / D, v_i = 1/2 sum w_j sin 2pi(x_i - x_j) / D, +// D = cosh 2pi(y_i - y_j) - cos 2pi(x_i - x_j) + delta^2. +// The differences come from products of each point's own trig, packed five +// numbers a point with its weight, and each pair is visited once since the +// kernel is odd. +function pack(S, X, Y) { + const { n, w, pk } = S; + for (let i = 0, o = 0; i < n; i++, o += 5) { + const a = TAU * X[i], e = exp(TAU * Y[i]), ie = 1 / e; + pk[o] = cos(a); + pk[o + 1] = sin(a); + pk[o + 2] = (e + ie) / 2; + pk[o + 3] = (e - ie) / 2; + pk[o + 4] = w[i]; + } +} +function velocity(S, X, Y, U, V) { + const { n, pk } = S, d2 = S.delta * S.delta; + pack(S, X, Y); + U.fill(0, 0, n); + V.fill(0, 0, n); + for (let i = 0, oi = 0; i < n; i++, oi += 5) { + const ci = pk[oi], si = pk[oi + 1], hi = pk[oi + 2], gi = pk[oi + 3], wi = pk[oi + 4]; + let ui = 0, vi = 0; + for (let j = i + 1, o = oi + 5; j < n; j++, o += 5) { + const cj = pk[o], sj = pk[o + 1], hj = pk[o + 2], gj = pk[o + 3], wj = pk[o + 4]; + const q = 0.5 / (hi * hj - gi * gj - ci * cj - si * sj + d2), fu = (gi * hj - hi * gj) * q, fv = (si * cj - ci * sj) * q; + ui -= wj * fu; + vi += wj * fv; + U[j] += wi * fu; + V[j] -= wi * fv; + } + U[i] += ui; + V[i] += vi; + } +} + +function rk4(S, h) { + const { n, x, y, x0, y0, k } = S; + for (let i = 0; i < n; i++) (x0[i] = x[i]), (y0[i] = y[i]); + const cs = [0, 0.5, 0.5, 1]; + for (let st = 0; st < 4; st++) { + if (st) for (let i = 0; i < n; i++) (x[i] = x0[i] + cs[st] * h * k[2 * st - 2][i]), (y[i] = y0[i] + cs[st] * h * k[2 * st - 1][i]); + velocity(S, x, y, k[2 * st], k[2 * st + 1]); + } + for (let i = 0; i < n; i++) { + x[i] = x0[i] + (h / 6) * (k[0][i] + 2 * k[2][i] + 2 * k[4][i] + k[6][i]); + y[i] = y0[i] + (h / 6) * (k[1][i] + 2 * k[3][i] + 2 * k[5][i] + k[7][i]); + } +} + +// Where a gap has opened wider than GAP, a point goes in at the middle G, +// placed on the cubic through the four points around it. The points run +// once round the period in order of G; the one after the last is the +// first moved on by a whole period, so a point that goes in there is +// simply added at the end. +function refine(S) { + const { x, y, g } = S; + const at = (q, o) => { + const n = S.n, f = floor(q / n), m = q - f * n; + o[0] = x[m] + f; + o[1] = y[m]; + o[2] = g[m] + f; + }; + const a = [0, 0, 0], b = [0, 0, 0], c = [0, 0, 0], e = [0, 0, 0]; + for (let i = 0; i < S.n && S.n < NMAX; i++) { + at(i, b); + at(i + 1, c); + const dx = c[0] - b[0], dy = c[1] - b[1]; + if (dx * dx + dy * dy < GAP * GAP) continue; + at(i - 1, a); + at(i + 2, e); + const n = S.n; + for (let m = n; m > i + 1; m--) (x[m] = x[m - 1]), (y[m] = y[m - 1]), (g[m] = g[m - 1]); + x[i + 1] = (-a[0] + 9 * b[0] + 9 * c[0] - e[0]) / 16; + y[i + 1] = (-a[1] + 9 * b[1] + 9 * c[1] - e[1]) / 16; + g[i + 1] = (b[2] + c[2]) / 2; + S.n++; + i++; + } +} + +// Tracers: dots laid on a grid of the period, moved by the sheet's own +// velocity, so the two streams and the winding read away from the sheet. +function seedTracers(S) { + const sc = H / S.waves, rows = max(2, round(sc / 6)), cols = 11; + S.nt = 0; + for (let r = 0; r < rows; r++) + for (let c = 0; c < cols; c++) { + if (S.nt >= 600) break; + S.tx[S.nt] = (r + 0.5) / rows; + S.ty[S.nt] = ((c + 0.5) / cols - 0.5) * (W / sc); + S.nt++; + } + S.tWaves = S.waves; +} + +// Tracers feel every other point of the sheet, each carrying the +// circulation of two: they only have to read the streams, and it halves +// their cost. +function tracerVel(S, TX, TY, n, U, V) { + const { pk } = S, d2 = S.delta * S.delta, np = S.n; + for (let i = 0; i < n; i++) { + const a = TAU * TX[i], e = exp(TAU * TY[i]), ie = 1 / e; + const ci = cos(a), si = sin(a), hi = (e + ie) / 2, gi = (e - ie) / 2; + let u = 0, v = 0; + for (let j = 0, o = 0; j + 1 < np; j += 2, o += 10) { + const cj = pk[o], sj = pk[o + 1], hj = pk[o + 2], gj = pk[o + 3]; + const q = (0.5 * (pk[o + 4] + pk[o + 9])) / (hi * hj - gi * gj - ci * cj - si * sj + d2); + u -= (gi * hj - hi * gj) * q; + v += (si * cj - ci * sj) * q; + } + U[i] = u; + V[i] = v; + } +} + +export function update(dt, input, params) { + if (!params.s) setup(params); + const S = params.s, kv = input.knobValues || [0, 2, 0.15, 1], d = cl(dt || 0, 0, 0.1); + S.hue = cl(kv[0], 0, 1); + S.speed = cl(kv[1], 0.2, 3); + S.delta = cl(kv[2], 0.15, 0.5); + S.waves = cl(round(kv[3]), 1, 4); + const tap = input.btnPressed && input.btnPressed[0]; + if (!S.born || tap || S.t > AGE) { + if (S.born) S.fade = 1; + start(S); + S.born = true; + } + if (S.tWaves !== S.waves || !S.nt) seedTracers(S); + S.fade = max(0, S.fade - d * 2); + // A point at the core's centre turns at up to pi / delta^2 radians a unit + // of time if all the circulation sat in its blob, and about half does. + // Steps are cut so RK4 turns it under 2.5 radians, inside its stable 2.8; + // at Speed 3 that is one step a frame, and a run with four times the + // steps put every point within 0.05 LED of it after 2.6 units. + const h = d * S.speed * RATE, sub = Math.ceil(h / (2.5 / (PI / (2 * S.delta * S.delta)))); + for (let k = 0; k < sub; k++) { + weights(S); + rk4(S, h / sub); + S.t += h / sub; + refine(S); + } + // Tracers take one step with the sheet where it now is. + weights(S); + pack(S, S.x, S.y); + const nt = S.nt, TX = S.tx, TY = S.ty, TU = S.tu, TV = S.tv; + tracerVel(S, TX, TY, nt, TU, TV); + for (let i = 0; i < nt; i++) { + TX[i] += h * TU[i]; + TY[i] += h * TV[i]; + TX[i] -= floor(TX[i]); + } +} + +function palette(S) { + const P = S.pal, h = 0.55 + S.hue; + hsv(dimHue(h), 0.8, 0.26, P, 0); // left fluid + hsv(dimHue(h + 0.3), 0.8, 0.26, P, 3); // right fluid + hsv(h, 0.6, 0.75, P, 6); // left tracers + hsv(h + 0.3, 0.6, 0.75, P, 9); // right tracers + hsv(h + 0.15, 0.3, 0.95, P, 12); // sheet + S.palHue = S.hue; +} + +// Points laid along a line every third of an LED, each lighting its four +// nearest LEDs by how close it falls; an LED keeps the brightest. +function plot(S, x, y, P, o, a) { + const fx = x - 0.5, fy = y - 0.5, ix = floor(fx), iy = floor(fy), tx = fx - ix, ty = fy - iy, rgb = S.rgb; + for (let dy = 0; dy < 2; dy++) { + const yy = iy + dy; + if (yy < 0 || yy >= H) continue; + const wy = dy ? ty : 1 - ty; + for (let dx = 0; dx < 2; dx++) { + const xx = ix + dx; + if (xx < 0 || xx >= W) continue; + const k = min(1, (dx ? tx : 1 - tx) * wy * 1.7) * a, p = (yy * W + xx) * 3; + if (rgb[p] < P[o] * k) rgb[p] = P[o] * k; + if (rgb[p + 1] < P[o + 1] * k) rgb[p + 1] = P[o + 1] * k; + if (rgb[p + 2] < P[o + 2] * k) rgb[p + 2] = P[o + 2] * k; + } + } +} + +export function draw(display, params) { + const S = params.s; + if (!S || !S.n) return; + if (S.palHue !== S.hue) palette(S); + const { x, y, n, rgb, flip } = S, P = S.pal; + const sc = H / S.waves, ox = W / 2; + const PX = (Y) => ox + Y * sc, PY = (X, k) => (X + k) * sc; + // Which fluid each LED holds: the crossings of the sheet to its left, + // counted row by row. The sheet spans every row, so the far left is + // always the left stream. + flip.fill(0); + for (let k = -1; k <= S.waves; k++) + for (let i = 0; i < n; i++) { + const j = i + 1 < n ? i + 1 : 0, wrap = j === 0 ? 1 : 0; + const ya = PY(x[i], k), yb = PY(x[j] + wrap, k), xa = PX(y[i]), xb = PX(y[j]); + const lo = Math.ceil(min(ya, yb) - 0.5), hi = Math.ceil(max(ya, yb) - 0.5); + for (let r = max(0, lo); r < min(H, hi); r++) { + const f = (r + 0.5 - ya) / (yb - ya), xc = xa + f * (xb - xa); + flip[r * (W + 2) + cl(Math.ceil(xc - 0.5), 0, W)] ^= 1; + } + } + for (let r = 0; r < H; r++) { + let par = 0; + for (let c = 0; c < W; c++) { + par ^= flip[r * (W + 2) + c]; + const p = (r * W + c) * 3, o = par ? 3 : 0; + rgb[p] = P[o]; + rgb[p + 1] = P[o + 1]; + rgb[p + 2] = P[o + 2]; + S.side[r * W + c] = par; + } + } + // Tracers, in the colour of the fluid they are in now, each with a streak + // of where it was a tenth of a second ago. + const back = 0.1 * S.speed * RATE; + for (let k = -1; k <= S.waves; k++) + for (let i = 0; i < S.nt; i++) { + const px = PX(S.ty[i]), py = PY(S.tx[i], k); + if (px < 0 || px >= W || py < 0 || py >= H) continue; + const o = S.side[floor(py) * W + floor(px)] ? 9 : 6, ex = S.tv[i] * back * sc, ey = S.tu[i] * back * sc; + for (let e = 3; e >= 0; e--) plot(S, px - (ex * e) / 3, py - (ey * e) / 3, P, o, 1 - e * 0.22); + } + // The sheet on top. + for (let k = -1; k <= S.waves; k++) + for (let i = 0; i < n; i++) { + const j = i + 1 < n ? i + 1 : 0, wrap = j === 0 ? 1 : 0; + const xa = PX(y[i]), ya = PY(x[i], k), xb = PX(y[j]), yb = PY(x[j] + wrap, k); + if ((ya < -2 && yb < -2) || (ya > H + 2 && yb > H + 2)) continue; + const m = Math.ceil(Math.hypot(xb - xa, yb - ya) * 3) || 1; + for (let e = 0; e <= m; e++) plot(S, xa + ((xb - xa) * e) / m, ya + ((yb - ya) * e) / m, P, 12, 1); + } + const f = S.fade, old = S.old; + for (let r = 0, p = 0; r < H; r++) + for (let c = 0; c < W; c++, p += 3) { + let R = rgb[p], G = rgb[p + 1], B = rgb[p + 2]; + if (f > 0) (R = R * (1 - f) + old[p] * f), (G = G * (1 - f) + old[p + 1] * f), (B = B * (1 - f) + old[p + 2] * f); + else (old[p] = R), (old[p + 1] = G), (old[p + 2] = B); + display.setPixel(c, r, (255 * min(1, R)) | 0, (255 * min(1, G)) | 0, (255 * min(1, B)) | 0); + } +} From a6134a13e9f3d8e3bb54e30f421a2efaf7897b14 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 26 Oct 2026 04:35:09 +0000 Subject: [PATCH 06/10] =?UTF-8?q?incoming:=2055=ED=8C=90=20Imaginary=20Geo?= =?UTF-8?q?metry,=20Tissot's=20Indicatrix?= 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_01ANgZzmaYPUurP5cuTFEV6X --- .../patterns/imaginary-geometry.js | 319 ++++++++++++++++++ .../patterns/tissot-indicatrix.js | 168 +++++++++ 2 files changed, 487 insertions(+) create mode 100644 incoming/pattern-study/patterns/imaginary-geometry.js create mode 100644 incoming/pattern-study/patterns/tissot-indicatrix.js diff --git a/incoming/pattern-study/patterns/imaginary-geometry.js b/incoming/pattern-study/patterns/imaginary-geometry.js new file mode 100644 index 000000000..3dce1cab9 --- /dev/null +++ b/incoming/pattern-study/patterns/imaginary-geometry.js @@ -0,0 +1,319 @@ +// ===== Patternflow pattern ===== +// Title: Imaginary Geometry +// Family: noise +// Idea: Thirteen flow lines of a drifting Gaussian free field leave one point on the bottom edge as a fan of angles and climb to one point on the top edge, each turning wherever the field turns it, so a line of larger angle always stays to the left, and where two come within an LED they run on together as one and part again without ever crossing. +// Author: patternflow-lab +// Date: 2026-10-26 +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Lineage: imaginary geometry, the flow lines of the Gaussian free field (Jason Miller and Scott Sheffield, 2016); Gaussian Free Field (round 52) draws the same field's level lines, this one its flow lines, pattern round 55 +// Round: 55 +// =============================== +// @matrix 64x128 +// @knobs Hue=0..1, Speed=0.2..3, Kappa=0.5..3, Spread=4..6.2 +// +// Knob 1: Hue. Turns every colour round the wheel together. At 0 the lines run from magenta on the left (the largest angle) through violet and blue to cyan on the right, with white marks where a line joins another for four LEDs or more. Behind them the direction a line of angle 0 would take at each LED shows as a faint hue, 22 of 255 in its brightest channel; that faint wheel skips orange and yellow, which read brown and olive so dark. +// Knob 2: Speed. How fast the field drifts. Each of its 2,048 sine modes is a smooth random process whose law stays the free field's: at 2 the largest mode forgets itself in 1.5 s and the finest, 4 LEDs from crest to crest, in 0.24 s. At 0.2 ten times slower, at 3 half as fast again. +// Knob 3: Kappa. The SLE parameter. It sets chi = 2/sqrt(kappa) - sqrt(kappa)/2, from 2.47 at 0.5 to 0.29 at 3, and the lines turn by the field divided by chi. At 0.5 they wobble round a lens 37 LEDs wide at mid-height and ride on another line for 7% of their length, at 1 for 19%. At 2 they braid into one winding bundle 26 LEDs wide, riding together 42% of the way, and at 3 into a ribbon 11 LEDs wide, 55%. The field is smoothed over 1.2 LEDs up to kappa 1.55, then over a width that grows to 4.1 LEDs at 3, so that a line's turning stays near 0.6 radians per LED, root mean square, and the line stays a line. +// Knob 4: Spread. The fan of angles, in radians: thirteen lines evenly from -2 to +2 at 4, up to -3.1 to +3.1 at 6.2. At kappa 0.5 the lens is 37 LEDs wide at mid-height at 4 and 57 at 6.2, where the outermost lines hug the side edges: a line whose angle reached pi would run along the bottom edge. +// Buttons: K1 tap draws a new field, and the fan grows from the bottom point again over about a second. + +const W = 64, H = 128, NX = W + 1, NY = H + 1, NN = NX * NY; +const { floor, min, max, abs, sqrt, sin, cos, exp, log, atan2, hypot, PI } = Math; +const TAU = 2 * PI, HALF = PI / 2; +const cl = (v, a, b) => (v < a ? a : v > b ? b : v); +// Lines, sine modes across and up, and points a line may hold. +const NL = 13, MM = 32, NM = 64, MAXP = 3000; +// The root on the bottom edge and the target on the top, both at x = 32. +// Positions are in LEDs with v measured up from the bottom edge, and the +// field lives on the 65 x 129 nodes at the LED corners. +const RX = 32; + +function hsv(h, s, v, 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); + o[0] = i === 0 || i === 5 ? v : i === 1 ? q : i === 4 ? t : p; + o[1] = i === 1 || i === 2 ? v : i === 0 ? t : i === 3 ? q : p; + o[2] = i === 3 || i === 4 ? v : i === 2 ? t : i === 5 ? q : p; +} + +// The field is h0 + lambda U + chi V. h0 is the free field, zero on the +// edges. lambda U carries the boundary values -lambda left of the root and +// +lambda right of it that make the angle-0 line from root to target an +// SLE(kappa), with lambda = pi / sqrt(kappa). chi V is the coordinate change +// h o phi - chi arg phi' from the half-plane: V is the boundary's own +// turning, 0 along the bottom, pi/2 up the sides and pi along the top, +// minus on the left. U and V jump at the root and the target, so each is +// split into those jumps, written out (argR, argT), and a smooth rest +// solved once on the nodes. +const argR = (u, v) => atan2(v, u - RX); +const argT = (u, v) => (v >= H ? (u >= RX ? 0 : -PI) : atan2(v - H, u - RX)); + +// Laplace's equation inside, the edge nodes held: over-relaxed sweeps. +function harmonic(F) { + const om = 2 / (1 + sin(PI / NY)); + for (let it = 0; it < 4000; it++) { + let worst = 0; + for (let j = 1; j < H; j++) + for (let i = 1; i < W; i++) { + const p = j * NX + i, d = om * (0.25 * (F[p - 1] + F[p + 1] + F[p - NX] + F[p + NX]) - F[p]); + F[p] += d; + if (abs(d) > worst) worst = abs(d); + } + if (worst < 1e-7) break; + } +} + +// Sine modes of the 64 x 128 rectangle. With the free field's covariance +// -log|x - y|, mode (m, n) has standard deviation sqrt(2 pi / mu) on the +// orthonormal basis, mu being its eigenvalue. TC is its time constant in +// seconds at Speed 1: 3 s for the largest, down as mu^-1/4. +const SXT = new Float64Array(MM * NX), SYT = new Float64Array(NM * NY); +for (let m = 0; m < MM; m++) for (let i = 0; i < NX; i++) SXT[m * NX + i] = sin((PI * (m + 1) * i) / W); +for (let n = 0; n < NM; n++) for (let j = 0; j < NY; j++) SYT[n * NY + j] = sin((PI * (n + 1) * j) / H); +const MU = new Float64Array(MM * NM), AMP = new Float64Array(MM * NM), TC = new Float64Array(MM * NM); +for (let m = 0; m < MM; m++) + for (let n = 0; n < NM; n++) { + const q = m * NM + n, mu = PI * PI * (((m + 1) / W) ** 2 + ((n + 1) / H) ** 2); + MU[q] = mu; + AMP[q] = sqrt((2 * PI) / mu) * (2 / sqrt(W * H)); + TC[q] = 3 * (MU[0] / mu) ** 0.25; + } + +function gauss() { + const a = Math.random() || 1e-12, b = Math.random(); + return sqrt(-2 * log(a)) * cos(TAU * b); +} + +export function setup(params) { + const SU = new Float64Array(NN), SV = new Float64Array(NN); + for (let j = 0; j < NY; j++) + for (let i = 0; i < NX; i++) { + if (i > 0 && i < W && j > 0 && j < H) continue; + const p = j * NX + i; + let ud, vd; + if (j === 0) (ud = i < RX ? -1 : i > RX ? 1 : 0), (vd = i === 0 ? -PI / 4 : i === W ? PI / 4 : 0); + else if (j === H) (ud = i < RX ? -1 : i > RX ? 1 : 0), (vd = i === 0 ? (-3 * PI) / 4 : i === W ? (3 * PI) / 4 : i < RX ? -PI : i > RX ? PI : 0); + else if (i === 0) (ud = -1), (vd = -HALF); + else (ud = 1), (vd = HALF); + const at = argT(i, j), aR = j === 0 ? ud : 1 - (2 / PI) * argR(i, j); + // The two ends themselves take the rest's limit there, which is 0. + const end = i === RX && (j === 0 || j === H); + SU[p] = end ? 0 : ud - aR - (1 + (2 / PI) * at); + SV[p] = end ? 0 : vd - (PI + 2 * at); + } + harmonic(SU); + harmonic(SV); + // The jumps at the nodes too, for the look-up away from the two ends. + const AR = new Float64Array(NN), AV = new Float64Array(NN); + for (let j = 0; j < NY; j++) + for (let i = 0; i < NX; i++) { + const p = j * NX + i, at = argT(i, j); + AR[p] = 2 + (2 / PI) * (at - argR(i, j)); + AV[p] = PI + 2 * at; + } + const K = MM * NM, X = new Float64Array(K), Y = new Float64Array(K); + for (let q = 0; q < K; q++) (X[q] = gauss()), (Y[q] = gauss() / TC[q]); + params.s = { + SU, SV, AR, AV, X, Y, C: new Float64Array(K), B: new Float64Array(MM * NY), h: new Float64Array(NN), P: new Float64Array(NN), F: new Float64Array(NN), + own: new Int16Array(W * H), pts: Array.from({ length: NL }, () => new Float32Array(MAXP * 2)), vis: Array.from({ length: NL }, () => new Uint8Array(MAXP)), np: new Int32Array(NL), + jx: new Float32Array(40), jy: new Float32Array(40), nj: 0, + rgb: new Float32Array(W * H * 3), col: new Float64Array(3), pal: new Float32Array(192), palHue: -1, + t: 0, hue: 0, speed: 2, kappa: 0.5, spread: 4, chi: 1, lc: 1, eps: 1, + }; +} + +export function update(dt, input, params) { + if (!params.s) setup(params); + const S = params.s, kv = input.knobValues || [0, 2, 0.5, 4], d = cl(dt || 0, 0, 0.1); + S.hue = cl(kv[0], 0, 1); + S.speed = cl(kv[1], 0.2, 3); + S.kappa = cl(kv[2], 0.5, 3); + S.spread = cl(kv[3], 4, 6.2); + if (input.btnPressed && input.btnPressed[0]) { + for (let q = 0; q < S.X.length; q++) (S.X[q] = gauss()), (S.Y[q] = gauss() / TC[q]); + S.t = 0; + } + S.t += d; + const rk = sqrt(S.kappa); + S.chi = 2 / rk - rk / 2; + // lambda / chi = 2 pi / (4 - kappa): how far the boundary values turn a line. + S.lc = PI / rk / S.chi; + // A line turns by the gradient of h / chi, whose spread is 1 / (sqrt 2 eps + // chi) for a field smoothed over eps: eps keeps that near 0.6 rad per LED, + // and never under 1.2 LEDs, where the finest modes run out. + S.eps = max(1.2, 1 / (sqrt(2) * S.chi * 0.6)); + // Each mode is a critically damped oscillator driven by noise: its path is + // smooth, so the lines glide, and its law stays the unit normal. + const de = d * S.speed, X = S.X, Y = S.Y; + for (let q = 0; q < X.length; q++) { + const tc = TC[q]; + Y[q] += (-2 * Y[q] / tc - X[q] / (tc * tc)) * de + (2 / (tc * sqrt(tc))) * sqrt(de) * gauss(); + X[q] += Y[q] * de; + } + const C = S.C, e2 = (S.eps * S.eps) / 2, kmax2 = 4.6 / e2; + let mTop = 0; + for (let m = 0; m < MM; m++) + for (let n = 0; n < NM; n++) { + const q = m * NM + n; + if (MU[q] > kmax2) { + C[q] = 0; + continue; + } + C[q] = AMP[q] * exp(-MU[q] * e2) * X[q]; + if (m + 1 > mTop) mTop = m + 1; + } + // Mode k is even or odd about the middle as k is odd or even, so each sum + // serves a node and its mirror. + const B = S.B, h = S.h; + for (let m = 0; m < mTop; m++) + for (let j = 0; j <= H / 2; j++) { + let e = 0, o = 0; + for (let n = 0; n < NM; n += 2) { + e += C[m * NM + n] * SYT[n * NY + j]; + o += C[m * NM + n + 1] * SYT[(n + 1) * NY + j]; + } + B[m * NY + j] = e + o; + B[m * NY + H - j] = e - o; + } + for (let j = 0; j < NY; j++) + for (let i = 0; i <= W / 2; i++) { + let e = 0, o = 0; + for (let m = 0; m < mTop; m += 2) { + e += SXT[m * NX + i] * B[m * NY + j]; + if (m + 1 < mTop) o += SXT[(m + 1) * NX + i] * B[(m + 1) * NY + j]; + } + h[j * NX + i] = e + o; + h[j * NX + W - i] = e - o; + } + // P is the angle field without the jumps at the ends, F with them. + const P = S.P, F = S.F, ic = 1 / S.chi, lc = S.lc; + for (let p = 0; p < NN; p++) { + P[p] = h[p] * ic + lc * S.SU[p] + S.SV[p]; + F[p] = P[p] + lc * S.AR[p] + S.AV[p]; + } + trace(S); +} + +// h / chi at a point: the angle a line of angle 0 turns from north. Within +// 6 LEDs of either end the jumps are written out instead of interpolated. +function phiAt(S, u, v) { + const fu = cl(u, 0, W - 1e-6), fv = cl(v, 0, H - 1e-6), i = floor(fu), j = floor(fv), a = fu - i, b = fv - j, p = j * NX + i; + if ((u - RX) ** 2 + v * v > 36 && (u - RX) ** 2 + (v - H) ** 2 > 36) { + const F = S.F; + return (F[p] * (1 - a) + F[p + 1] * a) * (1 - b) + (F[p + NX] * (1 - a) + F[p + NX + 1] * a) * b; + } + const P = S.P, rest = (P[p] * (1 - a) + P[p + 1] * a) * (1 - b) + (P[p + NX] * (1 - a) + P[p + NX + 1] * a) * b; + const at = argT(u, min(v, H)); + return rest + S.lc * (2 + (2 / PI) * (at - argR(u, max(v, 0)))) + PI + 2 * at; +} + +// Each line follows exp(i (h / chi + theta)) from the root, by midpoint steps +// of half an LED that shorten near the ends. The middle line goes first and +// the rest pair off outward. A line that steps into an LED an earlier line +// holds rides with it, undrawn, until it steps into a free LED again. In the +// smoothed field two lines of different angle cannot even touch, since they +// would cross with the larger angle going right; in the free field itself +// lines whose angles differ by less than pi kappa / (4 - kappa) do touch +// and bounce off. Drawn at one LED, both look like this. +function trace(S) { + const own = S.own, grow = S.t < 2.5 ? 180 * S.t : 1e9, lc = S.lc, sp = S.spread, mid = (NL - 1) / 2; + own.fill(0); + S.nj = 0; + for (let k = 0; k <= mid; k++) + for (const L of k ? [mid + k, mid - k] : [mid]) { + const th = -sp / 2 + (sp * L) / (NL - 1), pts = S.pts[L], vis = S.vis[L], id = L + 1; + // Near the root the field is the half-plane's alone, and the line of + // angle theta leaves along the one ray it maps to itself. + const a0 = (HALF + th + lc) / (1 + (2 * lc) / PI); + let u = RX + cos(a0), v = sin(a0), n = 0, len = 0, ride = false, ju = 0, jv = 0, rc = 0; + (vis[n] = 1), (pts[2 * n] = RX), (pts[2 * n++ + 1] = 0); + (vis[n] = 1), (pts[2 * n] = u), (pts[2 * n++ + 1] = v); + let ci = floor(u), cj = floor(H - v); + while (n < MAXP) { + const rr = hypot(u - RX, v), rt = hypot(u - RX, v - H), s = cl(0.15 * min(rr, rt), 0.05, 0.5); + let f = HALF + th + phiAt(S, u, v); + f = HALF + th + phiAt(S, u + 0.5 * s * cos(f), v + 0.5 * s * sin(f)); + u += s * cos(f); + v += s * sin(f); + len += s; + if (rt < 0.8 || u < 0.05 || u > W - 0.05 || v < 0.05 || v > H - 0.05 || len > grow) break; + const ni = floor(u), nj = floor(H - v); + if (ni !== ci || nj !== cj) { + let o = own[nj * W + ni]; + if (ni !== ci && nj !== cj) { + // A diagonal step: the side LED nearer the path counts too, so + // two lines cannot slip past each other through a corner. + const sx = abs(u - max(ni, ci)) < abs(H - v - max(nj, cj)) ? ci : ni, sy = sx === ci ? nj : cj, os = own[sy * W + sx]; + if (os && os !== id) o = o || os; + else if (!o && rr > 3.5) own[sy * W + sx] = id; + } + // The lines all leave the root together, so the first 3.5 LEDs are shared. + const held = rr > 3.5 && o && o !== id; + if (held && !ride) (ju = u), (jv = v), (rc = 0); + if (held && ++rc === 4 && S.nj < 40) (S.jx[S.nj] = ju), (S.jy[S.nj++] = jv); + ride = held; + if (!held) own[nj * W + ni] = id; + (ci = ni), (cj = nj); + } + vis[n] = ride ? 0 : 1; + (pts[2 * n] = u), (pts[2 * n++ + 1] = v); + } + S.np[L] = n; + } +} + +// Lines are laid as points every third of an LED, each lighting its four +// nearest LEDs by how close it falls; an LED keeps the brightest. +function plot(S, x, y, c, a) { + const fx = x - 0.5, fy = y - 0.5, ix = floor(fx), iy = floor(fy), tx = fx - ix, ty = fy - iy, rgb = S.rgb; + for (let dy = 0; dy < 2; dy++) { + const yy = iy + dy; + if (yy < 0 || yy >= H) continue; + const wy = dy ? ty : 1 - ty; + for (let dx = 0; dx < 2; dx++) { + const xx = ix + dx; + if (xx < 0 || xx >= W) continue; + const w = min(1, (dx ? tx : 1 - tx) * wy * 1.7) * a, p = (yy * W + xx) * 3; + if (rgb[p] < c[0] * w) rgb[p] = c[0] * w; + if (rgb[p + 1] < c[1] * w) rgb[p + 1] = c[1] * w; + if (rgb[p + 2] < c[2] * w) rgb[p + 2] = c[2] * w; + } + } +} + +export function draw(display, params) { + const S = params.s; + if (!S) return; + const rgb = S.rgb, col = S.col, pal = S.pal, F = S.F; + // The faint wheel runs from lime round through blue and magenta to red, + // leaving out orange and yellow. + if (S.palHue !== S.hue) { + for (let k = 0; k < 64; k++) { + const fh = k / 64 + S.hue; + hsv(0.22 + 0.8 * (fh - floor(fh)), 0.6, 0.088, col); + (pal[3 * k] = col[0]), (pal[3 * k + 1] = col[1]), (pal[3 * k + 2] = col[2]); + } + S.palHue = S.hue; + } + for (let r = 0, o = 0; r < H; r++) + for (let c = 0; c < W; c++, o += 3) { + const p = (H - 1 - r) * NX + c, f = (F[p] + F[p + 1] + F[p + NX] + F[p + NX + 1]) / (4 * TAU), q = 3 * ((64 * (f - floor(f))) | 0); + (rgb[o] = pal[q]), (rgb[o + 1] = pal[q + 1]), (rgb[o + 2] = pal[q + 2]); + } + // Line colours go round half the wheel by angle, cyan to magenta at 0. + const c = [0, 0, 0]; + for (let L = 0; L < NL; L++) { + const pts = S.pts[L], vis = S.vis[L], n = S.np[L]; + hsv(0.45 + S.hue + (0.45 * L) / (NL - 1), 0.8, 1, c); + for (let k = 0; k + 1 < n; k++) { + if (!vis[k + 1]) continue; + const xa = pts[2 * k], ya = H - pts[2 * k + 1], xb = pts[2 * k + 2], yb = H - pts[2 * k + 3]; + const m = Math.ceil(hypot(xb - xa, yb - ya) * 3) || 1; + for (let e = 0; e < m; e++) plot(S, xa + ((xb - xa) * e) / m, ya + ((yb - ya) * e) / m, c, 1); + } + } + const white = [1, 1, 1]; + for (let k = 0; k < S.nj; k++) plot(S, S.jx[k], H - S.jy[k], white, 0.85); + for (let y = 0, p = 0; y < H; y++) + for (let x = 0; x < W; x++, p += 3) display.setPixel(x, y, (255 * cl(rgb[p], 0, 1)) | 0, (255 * cl(rgb[p + 1], 0, 1)) | 0, (255 * cl(rgb[p + 2], 0, 1)) | 0); +} diff --git a/incoming/pattern-study/patterns/tissot-indicatrix.js b/incoming/pattern-study/patterns/tissot-indicatrix.js new file mode 100644 index 000000000..63594a8bc --- /dev/null +++ b/incoming/pattern-study/patterns/tissot-indicatrix.js @@ -0,0 +1,168 @@ +// ===== Patternflow pattern ===== +// Title: Tissot's Indicatrix +// Family: space +// Idea: A lattice of equal small circles on a turning sphere, each drawn as the ellipse a map projection makes of it: on Mercator they stay circles but swell towards the top and bottom edges, the row at 80 degrees nearly six times as wide as the equator's, and as the projection blends into an equal-area one the same circles flatten into ellipses that all cover the same area and turn one colour. +// Author: patternflow-lab +// Date: 2026-10-26 +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Lineage: Tissot's indicatrix (Nicolas Auguste Tissot, 1859) on the Mercator projection (Gerardus Mercator, 1569) and Lambert's cylindrical equal-area projection (Johann Heinrich Lambert, 1772), pattern round 55 +// Round: 55 +// =============================== +// @matrix 64x128 +// @knobs Hue=0..1, Speed=0.2..3, Tilt=0..90, Blend=0..1 +// +// Knob 1: Hue. Turns every colour round the wheel together. At 0 a circle is cyan at its smallest (the Mercator equator), azure where it covers three times that, violet at 14 times (latitude 75 on Mercator) and pink-magenta from 42 times (latitude 81); its outline is a paler tint of the same colour and the graticule is a dim mauve. The wheel leaves out orange and yellow, which read brown and olive in the dim fills. +// Knob 2: Speed. How fast the sphere turns about its own axis: at 2 ten degrees a second, a whole turn in 36 s and a circle across the 180 degrees of the panel in 18 s. At 0.2 one degree a second, at 3 fifteen. +// Knob 3: Tilt. How far the projection's axis leans from the sphere's, in degrees. At 0 the normal aspect: the graticule is square, the circles glide sideways in nine rows, and the row at 80 degrees is 14 LEDs in radius against 2.5 at the equator. Turned up, the sphere's pole comes down into the panel with the meridians meeting at it, and each circle swells as its path carries it towards the top or bottom edge and shrinks back. At 90 the transverse aspect: the pole sits at the centre and the circles wheel round it. +// Knob 4: Blend. The projection's height, blended from Mercator at 0 to the equal-area cylindrical projection that fits the same box at 1, whose standard parallels are at 55.7 degrees. At 1 a circle on the equator is three times as tall as wide and one at 80 degrees ten times as wide as tall, and every one covers 61 square LEDs; on Mercator they run from 19 to 650. It opens at 0.06, nearly all Mercator. + +const W = 64, H = 128; +const { floor, min, max, abs, sqrt, sin, cos, asin, atan2, log, tan, PI } = Math; +const TAU = 2 * PI, DEG = PI / 180; +const cl = (v, a, b) => (v < a ? a : v > b ? b : v); +// 180 degrees of longitude across, so 64 / pi LEDs to the radian, and +// Mercator's y = ln tan(pi/4 + phi/2) from -pi to pi down the 128 rows: +// y reaches pi at 85.0511 degrees, the cut Web Mercator makes for its +// square world, and the box is exactly 1:2. +const SC = W / PI, PHIMAX = 2 * Math.atan(Math.exp(PI)) - PI / 2, SMAX = sin(PHIMAX); +// The circles' geodesic radius, and h * k of the equal-area map, in LEDs +// squared per steradian. +const RAD = 7 * DEG, HKEA = (SC * W) / SMAX; + +function hsv(h, s, v, 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); + o[0] = i === 0 || i === 5 ? v : i === 1 ? q : i === 4 ? t : p; + o[1] = i === 1 || i === 2 ? v : i === 0 ? t : i === 3 ? q : p; + o[2] = i === 3 || i === 4 ? v : i === 2 ? t : i === 5 ? q : p; +} +// The wheel from lime round through blue and magenta to red, without the +// orange and yellow that turn brown and olive when dim. +const wheel = (f) => 0.22 + 0.8 * (f - floor(f)); + +// Circle centres: rows every 20 degrees of latitude, each with as many +// circles as fit 20 degrees apart round it, odd rows shifted half a step. +// No two centres are closer than 19.7 degrees, so the 7-degree circles +// never touch. The poles are left out: they are 10 degrees from the 80 row. +const CL = []; +for (let r = -4; r <= 4; r++) { + const lat = r * 20 * DEG, n = Math.round(18 * cos(lat)); + for (let k = 0; k < n; k++) CL.push([lat, ((k + (r & 1 ? 0.5 : 0)) / n) * TAU]); +} +const NC = CL.length; + +export function setup(params) { + params.s = { spin: 0, hue: 0, speed: 2, tilt: 0, blend: 0.06, rgb: new Float32Array(W * H * 3), col: new Float64Array(3), o: [0, 0] }; +} + +export function update(dt, input, params) { + if (!params.s) setup(params); + const S = params.s, kv = input.knobValues || [0, 2, 0, 0.06], d = cl(dt || 0, 0, 0.1); + S.hue = cl(kv[0], 0, 1); + S.speed = cl(kv[1], 0.2, 3); + S.tilt = cl(kv[2], 0, 90) * DEG; + S.blend = cl(kv[3], 0, 1); + S.spin = (S.spin + d * S.speed * 5 * DEG) % TAU; +} + +// A point of the sphere, turned about the sphere's axis, then seen in the +// projection's frame, whose axis leans from the sphere's by the tilt +// towards the middle of the panel: o gets latitude and longitude there. +function rot(S, lat, lon, o) { + const cp = cos(lat), x0 = cp * cos(lon + S.spin), y0 = cp * sin(lon + S.spin), z0 = sin(lat); + const ct = cos(S.tilt), st = sin(S.tilt); + o[0] = asin(cl(-x0 * st + z0 * ct, -1, 1)); + o[1] = atan2(y0, x0 * ct + z0 * st); +} +// Height above the middle row, in LEDs: Mercator blended into the +// equal-area map scaled to the same box. +const yOf = (b, phi) => (1 - b) * SC * log(tan(PI / 4 + phi / 2)) + (b * W * sin(phi)) / SMAX; + +// Lines are laid as points, each lighting its four nearest LEDs by how +// close it falls; an LED keeps the brightest. +function plot(S, x, y, c, a) { + const fx = x - 0.5, fy = y - 0.5, ix = floor(fx), iy = floor(fy), tx = fx - ix, ty = fy - iy, rgb = S.rgb; + for (let dy = 0; dy < 2; dy++) { + const yy = iy + dy; + if (yy < 0 || yy >= H) continue; + const wy = dy ? ty : 1 - ty; + for (let dx = 0; dx < 2; dx++) { + const xx = ix + dx; + if (xx < 0 || xx >= W) continue; + const w = min(1, (dx ? tx : 1 - tx) * wy * 1.7) * a, p = (yy * W + xx) * 3; + if (rgb[p] < c[0] * w) rgb[p] = c[0] * w; + if (rgb[p + 1] < c[1] * w) rgb[p + 1] = c[1] * w; + if (rgb[p + 2] < c[2] * w) rgb[p + 2] = c[2] * w; + } + } +} + +// A graticule line, sampled along the sphere and joined on the panel. A +// join is skipped where the line leaves the visible half or the cut. +function gratLine(S, c, lat0, lon0, lat1, lon1, steps) { + const o = S.o, b = S.blend; + let px = 0, py = 0, ok = false; + for (let k = 0; k <= steps; k++) { + const u = k / steps; + rot(S, lat0 + (lat1 - lat0) * u, lon0 + (lon1 - lon0) * u, o); + const inside = abs(o[0]) < PHIMAX + 0.02 && abs(o[1]) < PI / 2 + 0.05; + const x = W / 2 + SC * o[1], y = H / 2 - yOf(b, cl(o[0], -1.55, 1.55)); + if (inside && ok && abs(x - px) + abs(y - py) < 20) { + const m = Math.ceil(Math.hypot(x - px, y - py) * 2.5) || 1; + for (let e = 1; e <= m; e++) plot(S, px + ((x - px) * e) / m, py + ((y - py) * e) / m, c, 1); + } + (px = x), (py = y), (ok = inside); + } +} + +export function draw(display, params) { + const S = params.s; + if (!S) return; + const rgb = S.rgb, col = S.col, b = S.blend, o = S.o; + rgb.fill(0); + // The sphere's graticule every 30 degrees, and its poles. + const gc = [0, 0, 0]; + hsv(wheel(0.85 + S.hue), 0.3, 0.32, gc); + for (let k = 0; k < 12; k++) gratLine(S, gc, -PI / 2, k * 30 * DEG, PI / 2, k * 30 * DEG, 360); + for (let k = -2; k <= 2; k++) gratLine(S, gc, k * 30 * DEG, 0, k * 30 * DEG, TAU, 720); + for (const lat of [PI / 2, -PI / 2]) { + rot(S, lat, 0, o); + if (abs(o[0]) < PHIMAX && abs(o[1]) < PI / 2) plot(S, W / 2 + SC * o[1], H / 2 - yOf(b, o[0]), [0.85, 0.85, 0.85], 1); + } + // Each circle as its indicatrix: a cylindrical map keeps meridians and + // parallels square, so the ellipse lies along the panel's axes with + // semi-axes r k across and r h up, k = SC / cos phi along the parallel + // and h = dy/dphi along the meridian. On Mercator h = k. + for (let i = 0; i < NC; i++) { + rot(S, CL[i][0], CL[i][1], o); + const ph = o[0], la = o[1]; + if (abs(ph) > PHIMAX || abs(la) > PI / 2 + 0.3) continue; + const cp = cos(ph), k = SC / cp, h = ((1 - b) * SC) / cp + (b * W * cp) / SMAX; + const A = RAD * k, B = RAD * h, cx = W / 2 + SC * la, cy = H / 2 - yOf(b, ph); + // The colour is h k against the equal-area map's, on a log scale from + // the Mercator equator's 0.32 up to 13.5. + const tn = cl((log((h * k) / HKEA) + 1.15) / 3.75, 0, 1); + hsv(wheel(0.35 + 0.44 * tn + S.hue), 0.7, 1, col); + // A circle fades over the last 4 degrees before the cut, where it is + // about 20 LEDs across on Mercator, so it does not drop out whole. + const fade = cl((PHIMAX - abs(ph)) / (4 * DEG), 0, 1); + const lr = 0.55 + 0.45 * col[0], lg = 0.55 + 0.45 * col[1], lb = 0.55 + 0.45 * col[2]; + const x0 = max(0, floor(cx - A - 1)), x1 = min(W - 1, floor(cx + A + 1)), y0 = max(0, floor(cy - B - 1)), y1 = min(H - 1, floor(cy + B + 1)); + for (let yy = y0; yy <= y1; yy++) + for (let xx = x0; xx <= x1; xx++) { + // Distance to the outline in LEDs, to first order: (rho - 1) / |grad rho|. + const qx = (xx + 0.5 - cx) / A, qy = (yy + 0.5 - cy) / B, r2 = qx * qx + qy * qy, r = sqrt(r2) || 1e-9; + const dist = (r - 1) / (sqrt((qx / A) ** 2 + (qy / B) ** 2) / r || 1); + const line = cl(1 - abs(dist) / 0.8, 0, 1), fill = dist < 0 ? cl(-dist, 0, 1) * (0.16 + 0.14 * (1 - r2)) : 0; + const v = max(line, fill) * fade; + if (v <= 0) continue; + const p = (yy * W + xx) * 3, pale = line > fill; + const cr = (pale ? lr : col[0]) * v, cg = (pale ? lg : col[1]) * v, cb = (pale ? lb : col[2]) * v; + if (rgb[p] < cr) rgb[p] = cr; + if (rgb[p + 1] < cg) rgb[p + 1] = cg; + if (rgb[p + 2] < cb) rgb[p + 2] = cb; + } + } + for (let y = 0, p = 0; y < H; y++) + for (let x = 0; x < W; x++, p += 3) display.setPixel(x, y, (255 * cl(rgb[p], 0, 1)) | 0, (255 * cl(rgb[p + 1], 0, 1)) | 0, (255 * cl(rgb[p + 2], 0, 1)) | 0); +} From dbe377ba7055771a2bc3c6d49650db12260266a9 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 26 Oct 2026 04:38:57 +0000 Subject: [PATCH 07/10] =?UTF-8?q?incoming:=20Tissot's=20Indicatrix=20?= =?UTF-8?q?=EA=B0=80=2025=EB=8F=84=20=EA=B8=B0=EC=9A=B4=20=EC=B6=95?= =?UTF-8?q?=EC=97=90=EC=84=9C=20=EC=97=B4=EB=A6=B0=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- incoming/pattern-study/patterns/tissot-indicatrix.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/incoming/pattern-study/patterns/tissot-indicatrix.js b/incoming/pattern-study/patterns/tissot-indicatrix.js index 63594a8bc..38ba32e2a 100644 --- a/incoming/pattern-study/patterns/tissot-indicatrix.js +++ b/incoming/pattern-study/patterns/tissot-indicatrix.js @@ -9,11 +9,11 @@ // Round: 55 // =============================== // @matrix 64x128 -// @knobs Hue=0..1, Speed=0.2..3, Tilt=0..90, Blend=0..1 +// @knobs Hue=0..1, Speed=0.2..3, Tilt=25..90, Blend=0..1 // // Knob 1: Hue. Turns every colour round the wheel together. At 0 a circle is cyan at its smallest (the Mercator equator), azure where it covers three times that, violet at 14 times (latitude 75 on Mercator) and pink-magenta from 42 times (latitude 81); its outline is a paler tint of the same colour and the graticule is a dim mauve. The wheel leaves out orange and yellow, which read brown and olive in the dim fills. // Knob 2: Speed. How fast the sphere turns about its own axis: at 2 ten degrees a second, a whole turn in 36 s and a circle across the 180 degrees of the panel in 18 s. At 0.2 one degree a second, at 3 fifteen. -// Knob 3: Tilt. How far the projection's axis leans from the sphere's, in degrees. At 0 the normal aspect: the graticule is square, the circles glide sideways in nine rows, and the row at 80 degrees is 14 LEDs in radius against 2.5 at the equator. Turned up, the sphere's pole comes down into the panel with the meridians meeting at it, and each circle swells as its path carries it towards the top or bottom edge and shrinks back. At 90 the transverse aspect: the pole sits at the centre and the circles wheel round it. +// Knob 3: Tilt. How far the projection's axis leans from the sphere's, in degrees. It opens at 25: the sphere's pole sits just inside the top edge with the meridians fanning out from it, the circles travel on curved paths, and each one swells as its path carries it towards the top or bottom edge and shrinks back. The normal aspect, where the circles only glide sideways in nine rows, would be 0; the knob stops at 25 so the panel opens on circles that change size as they move. At 90 the transverse aspect: the pole sits at the centre and the circles wheel round it. On Mercator a circle's size depends only on its height on the map, so at every tilt the rows near the top and bottom edge are the ones at 14 LEDs in radius against 2.5 in the middle. // Knob 4: Blend. The projection's height, blended from Mercator at 0 to the equal-area cylindrical projection that fits the same box at 1, whose standard parallels are at 55.7 degrees. At 1 a circle on the equator is three times as tall as wide and one at 80 degrees ten times as wide as tall, and every one covers 61 square LEDs; on Mercator they run from 19 to 650. It opens at 0.06, nearly all Mercator. const W = 64, H = 128; @@ -52,15 +52,15 @@ for (let r = -4; r <= 4; r++) { const NC = CL.length; export function setup(params) { - params.s = { spin: 0, hue: 0, speed: 2, tilt: 0, blend: 0.06, rgb: new Float32Array(W * H * 3), col: new Float64Array(3), o: [0, 0] }; + params.s = { spin: 0, hue: 0, speed: 2, tilt: 25 * DEG, blend: 0.06, rgb: new Float32Array(W * H * 3), col: new Float64Array(3), o: [0, 0] }; } export function update(dt, input, params) { if (!params.s) setup(params); - const S = params.s, kv = input.knobValues || [0, 2, 0, 0.06], d = cl(dt || 0, 0, 0.1); + const S = params.s, kv = input.knobValues || [0, 2, 25, 0.06], d = cl(dt || 0, 0, 0.1); S.hue = cl(kv[0], 0, 1); S.speed = cl(kv[1], 0.2, 3); - S.tilt = cl(kv[2], 0, 90) * DEG; + S.tilt = cl(kv[2], 25, 90) * DEG; S.blend = cl(kv[3], 0, 1); S.spin = (S.spin + d * S.speed * 5 * DEG) % TAU; } From 53c5b59dc6aab1bedfb469e94439f1db0e0b5251 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 26 Oct 2026 04:40:02 +0000 Subject: [PATCH 08/10] =?UTF-8?q?incoming:=20Vuza=20Canon=20=EC=9D=98=20?= =?UTF-8?q?=EC=84=B1=EB=B6=80=EB=A7=88=EB=8B=A4=20=EC=98=85=EC=9D=80=20?= =?UTF-8?q?=EA=B8=B8=EC=9E=A1=EC=9D=B4=20=EC=A4=84=EC=9D=84=20=EA=B9=90?= =?UTF-8?q?=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- incoming/pattern-study/patterns/vuza-canon.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/incoming/pattern-study/patterns/vuza-canon.js b/incoming/pattern-study/patterns/vuza-canon.js index d2391a292..01f47f2dd 100644 --- a/incoming/pattern-study/patterns/vuza-canon.js +++ b/incoming/pattern-study/patterns/vuza-canon.js @@ -110,6 +110,14 @@ export function draw(display, params) { if (t % 8 === 0) set(0, y, 0.3, 0.32, 0.38); if (t === 0) for (let x = 0; x < W; x += 2) set(x, y, 0.32, 0.34, 0.42); } + // A dim lane down the middle of each voice's column, so the voices read as + // columns between their notes. It also keeps a fifth of the panel lit: + // with the notes alone the picture lit 7% and stretched the study map's + // fill axis past its end. + for (let v = 0; v < nv; v++) { + const cx = x0 + v * pitch + (lit >> 1); + for (let y = 0; y < H; y++) set(cx, y, 0.11, 0.115, 0.13); + } // The focused voice rules a faint line across the panel at each of its // notes, so its rhythm reads as a ruler laid over the others, and a stem // joins its notes down its own column. From c8d7c94d50f9270044b07ec9913b46f36d5d6a99 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 26 Oct 2026 04:46:09 +0000 Subject: [PATCH 09/10] =?UTF-8?q?incoming:=2055=ED=8C=90=20=EC=97=B0?= =?UTF-8?q?=EA=B5=AC=20=EB=85=B8=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- incoming/pattern-study/README.md | 85 +++++++++++++++++++++++++++++--- 1 file changed, 77 insertions(+), 8 deletions(-) diff --git a/incoming/pattern-study/README.md b/incoming/pattern-study/README.md index 95e2da37a..340a341e1 100644 --- a/incoming/pattern-study/README.md +++ b/incoming/pattern-study/README.md @@ -1,6 +1,6 @@ # 팹턮 연구 2026-10 -새 팹턮 438개륌 쉰여섯 갈래로 나눠 만듀었얎요. 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판도 같은 표로 돌아서 수학 대상 닀섯을 닀섯 갈래에 하나씩, 할로윈 귞늌 닀섯을 할로윈 갈래에 더했얎요. 49판도 같은 표로 돌아서 수학 대상 닀섯을 닀섯 갈래에 하나씩, 할로윈 귞늌 닀섯을 할로윈 갈래에 더했얎요. 50판도 같은 표로 돌아서 수학 대상 닀섯을 팚턎읎 적은 닀섯 갈래에 하나씩, 할로윈 밀에 틀 귞늌 닀섯을 할로윈 갈래에 더했얎요. 51판도 같은 표로 돌아서 수학 대상 닀섯을 닀섯 갈래에 하나씩, 할로윈 밀에 틀 귞늌 닀섯을 할로윈 갈래에 더했얎요. 52판도 같은 표로 돌아서 수학 대상 닀섯을 팚턎읎 적은 닀섯 갈래에 하나씩, 할로윈 귞늌 닀섯을 할로윈 갈래에 더했얎요. 53판은 할로윈 몫읎 쉬게 된 첫 판읎띌, 수학 대상 ì—Ž 개륌 ì—Ž 갈래에 하나씩 더했얎요. [index.html](index.html) 을 람띌우저로 엎멎 전부 한 장에서 돌아가요. ë§š 위 지도에서 점을 누륎멎 ê·ž 팚턎윌로 가고, 칎드의 슬띌읎더가 녾뾌 넷읎에요. 마음에 드는 것은 「윔드 복사」로 가젞가 Pattern Lab 의 CODE 칞에 붙여 넣윌멎 같은 귞늌읎 나와요. 페읎지는 파음 하나띌서 바깥에서 받아 였는 것읎 없얎요. +새 팹턮 448개륌 쉰여섯 갈래로 나눠 만듀었얎요. 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판도 같은 표로 돌아서 수학 대상 닀섯을 닀섯 갈래에 하나씩, 할로윈 귞늌 닀섯을 할로윈 갈래에 더했얎요. 49판도 같은 표로 돌아서 수학 대상 닀섯을 닀섯 갈래에 하나씩, 할로윈 귞늌 닀섯을 할로윈 갈래에 더했얎요. 50판도 같은 표로 돌아서 수학 대상 닀섯을 팚턎읎 적은 닀섯 갈래에 하나씩, 할로윈 밀에 틀 귞늌 닀섯을 할로윈 갈래에 더했얎요. 51판도 같은 표로 돌아서 수학 대상 닀섯을 닀섯 갈래에 하나씩, 할로윈 밀에 틀 귞늌 닀섯을 할로윈 갈래에 더했얎요. 52판도 같은 표로 돌아서 수학 대상 닀섯을 팚턎읎 적은 닀섯 갈래에 하나씩, 할로윈 귞늌 닀섯을 할로윈 갈래에 더했얎요. 53판은 할로윈 몫읎 쉬게 된 첫 판읎띌, 수학 대상 ì—Ž 개륌 ì—Ž 갈래에 하나씩 더했얎요. 55판도 수학 대상 ì—Ž 개륌 ì—Ž 갈래에 하나씩 더했얎요. [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판에서 섞로로 닀시 ì§°ì–Žìš”. @@ -705,6 +705,21 @@ | [Four-Vertex Theorem](patterns/four-vertex.js) | 선·궀적 | 섞로로 선 맀끈한 닫힌 곡선읎 천천히 몚양을 바꟞멎, 곡률 쀑심읎 귞늬는 축폐선읎 ê·ž 안에서 ꞈ빛 별로 돌고, 별의 첚점은 곡률읎 가장 크거나 작은 흰 ꌭ짓점곌 점선윌로 읎얎젞 둘씩 태얎나고 사띌젞도 넷 아래로는 쀄지 않아요. K3 은 반지늄을 휘는 묌결의 수, K4 는 곡선읎 였목하게 팚읎는 정도읎고, 닚추륌 누륎멎 닀음 묌결읎 잠깐 부풀얎 제비ꌬ늬가 한꺌번에 ì—Žë žë‹€ 닫혀요. | | [Euler's Elastica](patterns/elastica.js) | 묌늬·슀프링 | 양 끝읎 핀에 걞늰 120 LED 짜늬 가는 막대가 천천히 눌렞닀 풀늬며 였음러의 탄성 곡선 가족을 ë‹€ 지나가요. 삎짝 휜 활에서 아치와 직각 엘띌슀티칎륌 지나, 두 끝읎 만나는 묌방욞곌 끝읎 서로 지나치는 고늬까지 가요. 위의 진자는 막대륌 달늬는 구슬 자늬의 막대와 늘 나란히 흔듀늬고, 아래에서는 ê·ž 진자의 궀도가 상평멎을 돌아요. K3 은 반파 수 1~4 읎고, K4 는 풀 때 얎디까지 돌아였는지예요. 닚추륌 누륎멎 하쀑읎 멈춰 한 몚양읎 서 있얎요. | +### 55판 + +| 팹턮 | 갈래 | 한 쀄 | +| --- | --- | --- | +| [Imaginary Geometry](patterns/imaginary-geometry.js) | 녞읎슈·흐늄 | 팹널 아래 가욎데 한 점에서 가우슀 자유장의 흐늄선 엎섞 개가 각도 부채ꌎ로 떠나 위 가욎데 한 점까지 였륎는데, 각도가 큰 선읎 늘 왌쪜에 뚞묌고 두 선읎 한 LED 안윌로 붙윌멎 하나로 같읎 가닀가 닀시 갈띌질 뿐 서로 걎너지 않아요. 장읎 천천히 흘러서 선듀읎 렌슈처럌 벌얎졌닀가 땋은 닀발로 몚여요. K3 은 선읎 얌마나 굜읎치는지 정하는 κ(0.5~3), K4 는 부채ꌎ의 각도 폭읎고, 닚추륌 누륎멎 새 장에서 부채ꌎ읎 닀시 자띌요. | +| [Tissot's Indicatrix](patterns/tissot-indicatrix.js) | 공간·원귌 | 도는 구 위에 같은 크Ʞ로 깐 작은 원듀을 메륎칎토륎 지도에 옮Ʞ멎 원은 원 귞대로지만 위아래 끝윌로 갈수록 부풀얎서, 위도 80도의 원은 적도의 원볎닀 거의 여섯 ë°° ë„“ì–Žìš”. 투영의 축읎 25도 Ʞ욎 채 엎렀서 구의 극읎 위 가장자늬 안에 앉고, 원듀은 휜 Ꞟ을 따띌 돌며 위아래 끝에 닀가갈 때 부풀었닀 가욎데로 였며 쀄얎요. K3 은 축의 êž°ìšžêž°(25~90도), K4 는 메륎칎토륎에서 등적 투영윌로 섞는 정도읎고, ë‹€ 섞윌멎 원읎 넓읎가 같은 납작한 타원읎 되며 한 색읎 돌요. | +| [Question Mark Function](patterns/question-mark.js) | 수론 | 믌윔프슀킀의 묌음표 핚수가 속을 채욎 계닚윌로 서서 황ꞈ비 점 1/φ 륌 향핎 끝없읎 확대되고 한 죌Ʞ마닀 귞래프 전첎의 사볞 안윌로 듀얎가 읎음맀 없읎 돌아였는 동안, 계닚에는 팚늬 분수에서 꺟읎는 읎분 높읎선읎 붙고 아래 두 쀄에는 ê·ž 점의 연분수 자늿수와 ?(x) 의 읎진 자늿수가 같은 Ꞟ읎의 덩얎늬로 맞묌렀 흘러가요. K3 은 확대핮 듀얎가는 읎찚 묎늬수 여섯, K4 는 표시하는 팚늬 나묎의 당 수(3~7)예요. | +| [Burrows-Wheeler](patterns/burrows-wheeler.js) | 알고늬슘 | 128자 Ꞁ의 회전 128개가 한 쀄씩 쌓여 사선 쀄묎늬로 섰닀가 1쎈 동안 정렬된 자늬로 믞끄러지멎 였륞쪜 끝의 마지막 Ꞁ자 엎읎 같은 Ꞁ자끌늬 덩얎늬로 몚읎고, 행듀읎 읎웃곌 갈늬는 Ꞁ자까지만 낚았닀가 첫 Ꞁ자와 마지막 Ꞁ자 ì—Žë§Œ 빌고 흐렀진 ë’€ LF 걞음을 따띌 한 쀄씩 닀시 삎아나요. K3 은 Ꞁ의 짜임윌로 묎작위 Ꞁ에서 슀투늄 닚얎까지 가고, K4 는 Ꞁ자 수(2~6)예요. | +| [Buffon's Noodle](patterns/buffon-noodle.js) | 데읎터 귞늌 | 16 LED 간격의 바닥 선 위로 바늘읎 1쎈에 10개씩 떚얎젞서, 선을 걎넌 바늘은 ꞈ색, 못 걎넌 바늘은 하늘색읎 되고 걎넌 자늬마닀 선 위에 흰 눈ꞈ읎 서요. 아래에서는 떚얎뜚늰 Ꞟ읎와 걎넌 횟수로 셈한 π 가 숫자와 자췚로 흔듀늬며 닀가가요. K3 은 바늘 Ꞟ읎(선 간격 × π 의 0.2~1ë°°), K4 는 같은 Ꞟ읎 귞대로 바늘을 휘는 정도예요. Ꞟ읎 1 에서 ë‹€ 휘멎 지늄 16 의 고늬가 맀번 ꌭ 두 번 걎너서 숫자가 3.1416 에 멈추고, 닚추륌 누륎멎 셈을 처음부터 닀시 핎요. | +| [Vuza Canon](patterns/vuza-canon.js) | 늬듬·펄슀 | 72박 죌Ʞ에서 엎두 성부가 같은 여섯 음 늬듬 {0, 8, 16, 18, 26, 34} 륌 저마닀 닀륞 박에서 시작핎 한 쀄에 한 박씩 위로 흘러가고, 얎느 쀄에도 음읎 ꌭ 하나만 쌜젞서 박을 하나도 겹치거나 빌지 않고 시간을 덮얎요. 지ꞈ 쀄에 닿는 음은 하얗게 번쩍읎고 죌Ʞ가 바뀌는 쀄에는 점선읎 ê·žì–Žì žìš”. K3 은 캐넌을 바꿔서 역할을 바Ꟍ 여섯 성부, 24박마닀 되풀읎하는 타음링, 계닚 하나로 가는 가장 닚순한 타음링을 볎여 죌고, K4 는 한 성부륌 곚띌 ê·ž 음마닀 팚널을 가로지륎는 선을 ê·žì–Ž 늬듬을 자처럌 읜게 핎요. | +| [Hex Theorem](patterns/hex-theorem.js) | 볎로녞읎·셀 | ꞎ 대각선윌로 섞욎 헥슀 판에 칞읎 하나씩 묎작위로 찚닀가 한 색의 사슬읎 제 두 변을 잇는 순간 ê·ž 사슬읎 밝아지고, 판읎 ë‹€ 찚멎 진 색읎 제 두 변에서 밀렀 듀얎였닀 사슬 앞에서 멈추는 사읎 게음의 Ꞟ읎 두 색 사읎 읎음맀륌 따띌 아래 ꌭ짓점에서 읎ꞎ 쪜 ꌭ짓점까지 하얗게 ê·žì–Žì žìš”. K3 은 판의 한 변(7~15칞), K4 는 한 색읎 칞을 가젞갈 확률(0.25~0.75)읎에요. | +| [Universal Cover](patterns/universal-cover.js) | 위상·맀듭 | 아래 8자 고늬륌 점읎 한 걞음에 한 바퀎씩 도는 동안 위의 ë„€ 갈래 나묎에서는 ê·ž 올늌읎 같은 Ꞁ자의 변을 ê±·ê³ , 나묎는 걞음마닀 올늌을 새 뿌늬로 삌아 믞끄러지며 닀시 자띌요. 뿌늬까지 쀄읞 낱말은 빛나는 Ꞟ곌 랔록 띠로 낚아 걞음마닀 반 Ꞁ자씩 êžžì–Žì žìš”. K3 은 마지막 Ꞁ자륌 되돌늬는 확률(0.25~0.85), K4 는 귞늬는 나묎의 당 수(1.5~6.5)예요. | +| [Toussaint-Wilczek](patterns/toussaint-wilczek.js) | 입자 | 쎈록곌 자홍 두 종의 입자가 판 가득 뿌렀젞 걷닀가 서로 만나멎 짝지얎 사띌지고, 처음 우연히 더 많았던 쪜읎 낚은 자늬마닀 같은 색 영역읎 걞음 수의 제곱귌윌로 넓얎지며, ê·ž 사읎 읎음맀는 얎둡게 낚아 입자가 사띌지는 자늬에서 잿빛윌로 반짝여요. K3 은 처음 뿌늬는 밀도(LED 마닀 80~320), K4 는 A 가 B 볎닀 많은 몫읎띌 올늬멎 자홍읎 사띌지고 쎈록읎 판을 채워요. 닚추륌 누륎멎 새로 뿌렀요. | +| [Vortex Sheet](patterns/vortex-sheet.js) | 시뮬레읎션 | 팚널을 섞로로 가륎는 와류 시튞륌 사읎에 두고 왌쪜 묌은 아래로, 였륞쪜 묌은 위로 흐륎멎, 시튞의 작은 묌결읎 자띌 두 갈래 나선윌로 말렀 듀며 청록곌 볎띌 두 묌을 점점 얇은 겹윌로 감아 넣얎요. K3 은 크띌슀니의 덩읎 ÎŽ(0.15~0.5)띌 작을수록 나선읎 쎘쎘히 감Ʞ고, K4 는 팚널에 쌓는 파장 수(1~4)예요. 닚추륌 누륎멎 새 묌결로 닀시 시작핎요. | + ## 1판 갈래마닀 녾뾌 넷의 자늬는 갈래가 달띌도 거의 같아요. K1 은 색읎고 K2 는 빠륎Ʞ예요. K3 은 몚양을 바꟞고, K4 는 ê·ž 갈래만의 손잡읎예요. 귞래서 처음 볎는 팹턮도 K1 곌 K2 는 손읎 뚌저 알아요. @@ -2735,6 +2750,50 @@ E8 의 ê·Œ 240개륌 고셋의 4₂₁ 닀멎첎 ꌭ짓점윌로 ê·žë žì–Žìš”. 죌제 5 로 였음러의 엘띌슀티칎(1744)륌 섞웠얎요. 알윔프 베륎누읎(1694)가 묻고 였음러가 ë‹€ 귞늰, 끝에서 눌며 가는 막대의 몚양읎에요. 막대의 접선각 Ξ 는 sin(Ξ/2) = k sn(√λ s + K) 륌 따륎고, 읎것은 진자 방정식 Ξ'' = −λ sin Ξ 읎에요. 킀륎히혞프(1859)의 욎동 유비띌서, 팚턎은 막대 위에 ê·ž 진자륌, 아래에 진자의 상평멎을 같읎 귞렀요. 막대륌 달늬는 구슬 자늬의 접선곌 위의 진자가 늘 나란핎요. 막대 Ꞟ읎는 120 LED 읎고, 닫힌 ꌎ을 8,000 쀄곌 16,000 쀄로 쪌개 늬처드슚 왞삜윌로 재멎 120 곌 2.4e-11 안읎에요. 핀의 곡률은 막대 위 가장 큰 곡률의 3.0e-15 ë°° 아래읎고, RK4 로 따로 푌 Ξ'' = −λ sin Ξ 는 닫힌 ꌎ곌 1.5e-10 LED 안에서 겹쳀얎요. 두 끝의 거늬는 D/L = 2E(k)/K(k) − 1 읎띌, 끝읎 만나는 묌방욞은 α = 130.7099° 에서 서요. 몚드 1~4 가 ë‹€ 같은 각읎에요. 몚드 1 의 휚은 한쪜윌로만 나서 114도에서 48.4 LED 까지 ë„“ì–Žì žìš”. 귞래서 핀은 막대가 팚널에 듀얎가는 동안 가욎데 섞로쀄에 있닀가, 넓은 몚양에서만 추력선읎 핀을 싣고 20.2 LED 까지 옆윌로 비쌜요. 첫 판은 막대와 작은 진자, 분늬선만 귞늰 상평멎읎띌 `thirds` 가 7/16/12, Hue 반응읎 0.03 읎었얎요. 가족의 가욎데 몚양읎 납작핎서, 2~8쎈 동안 막대만 섞멎 2.8/18.2/2.4 예요. 묌방욞은 높읎 31 에 너비 47 읎고, 직각 엘띌슀티칎는 55 에 46 읎에요. 진자가 꺟읎는 끝에서 였래 뚞묎는 만큌 밝힌 ꞎ ë…žì¶œ 부채ꌎ을 더하자 위 3분의 1 읎 8 에서 16 읎 됐얎요. 상평멎에 30~150도 궀도 닀섯을 깔고 Ξ 의 색윌로 칠하자 아래가 21, Hue 가 0.05 가 됐얎요. 지ꞈ 판은 16/18/17 읎고 녾뾌는 0.04, 0.09, 0.08, 0.08 읎에요. 색상환을 돌늬멎 얎두욎 죌황곌 녞랑읎 진자 부채ꌎ에서 갈색곌 올늬람로 볎여서, 색상환읎 장믞색에서 띌임까지 서둘러 지나가게 휘었얎요. 고늬가 제 몞을 가로지륎는 자늬는 뒀에 였는 가닥을 위로 귞늬고 검은 테로 아래 가닥을 끊얎서, 위아래로 지나가는 것윌로 읜혀요. +## 55판 + +55판도 죌제 5 「수학 · 추상」 ì—Ž 개예요. 같읎 도는 54판(#265)의 PR 볞묞에서 ì—Ž 개와 지도 칞을 읜고 플핎서 곚랐고, 앞 바퀮 PR 듀에는 댓Ꞁ도 늬뷰도 없얎서 따륌 Ꞁ읎 없었얎요. 53판까지 든 438개에서 추상 갈래 가욎데 녞읎슈·흐늄읎 엎하나로 가장 적고, 볎로녞읎·셀곌 데읎터 귞늌, 입자, 시뮬레읎션읎 엎둘읎띌 읎 닀섯에 하나씩 넣었얎요. 나뚞지 닀섯은 엎셋읞 갈래 가욎데 54판읎 하나도 넣지 않는 위상·맀듭곌 늬듬·펄슀에 하나씩, 수론곌 알고늬슘, 공간·원귌에 하나씩 듀얎갔얎요. ì—Ž 개 ë‹€ 「흔한 것」에도 「낯선 수학 예」에도 없고, 넷은 앞 판듀읎 「목록 밖에서 핮 볌 것」에 적얎 둔 감읎에요. 묌음표 핚수와 뷔퐁의 바늘, 헥슀 정늬, 허수 Ʞ하예요. Imaginary Geometry 는 흐늄장을 귞늬지만 펄며 녞읎슈가 아니띌 가우슀 자유장 하나가 몚든 선을 정하고, 각도 순서대로 놓읞 선읎 서로 걎너지 않는닀는 정늬가 귞늌읎띌 「흔한 것」의 흐늄장곌 닀륎닀고 뎀얎요. 죌제 1 은 가장자늬 × 쎘쎘핚 3,4 에 Toussaint-Wilczek, 쎘쎘핚 × 몚양 바뀜 4,3 에 Burrows-Wheeler 륌 섞웠고, 둘읎 서로의 칞에도 듀얎갔얎요. 죌제 4 는 Imaginary Geometry 와 Tissot's Indicatrix, Universal Cover 예요. + +### Imaginary Geometry + +죌제 5 와 4 로 제읎슚 밀러와 슀윧 셰필드(2016)의 허수 Ʞ하륌 섞웠얎요. 가우슀 자유장 h 에서 각도 Ξ 의 흐늄선은 exp(i(h/χ + Ξ)) 쪜윌로 가고 χ = 2/√κ − √κ/2 읞데, 52판 Gaussian Free Field 가 귞늰 등고선 대신 읎 흐늄선을 귞렀요. 아래 가욎데에서 위 가욎데로 가는 각도 0 의 선읎 SLE(κ) 가 되도록 장에 겜계값 ∓λ 와 사각형 테두늬의 꺟임을 더했얎요. 닀듬은 장에서는 각도가 닀륞 두 선읎 걎널 수 없얎서, 녾뾌 여섯 조합에서 60쎈씩 선 13,338개륌 셈했더니 걎넌 곳읎 하나도 없었고 몚두 위 가욎데 점에 닿았얎요. 첫 판은 닀륞 선읎 찚지한 LED 에 듀얎간 선을 거Ʞ서 멈췄는데, κ 3 에서는 엎섞 선 가욎데 엎둘읎 뿌늬 귌처에서 멈췄고 녾뾌 반응읎 0.09, 0.07, 0.07, 0.13 읎었얎요. 붙은 동안만 귞늬지 않고 떚얎지멎 닀시 귞늬게 바꟞자 같읎 가는 몫읎 κ 0.5 에서 7%, 3 에서 55% 읞 땋은 닀발읎 되고 녞람가 0.21, 0.16, 0.17, 0.18 읎 됐얎요. 뒀에 깐 방향 색읎 25/255 띌 `thirds` 가 100/100/100 윌로 나와서 22 로 낮췄고, 선만윌로 33/37/34, 한 프레임 2.7 ms 예요. 읎 팚널에서는 κ 가 4 쪜윌로 갈수록 h/χ 가 컀젞 선읎 LED 하나에 몇 띌디안씩 꺟읎는 낙서가 돌서, κ 1.55 부터 장을 흐늬는 폭을 넓혀 3 에서 4.1 LED 로 두었더니 선읎 LED 하나에 0.6 띌디안쯀씩만 꺟여요. + +### Tissot's Indicatrix + +죌제 5 와 4 로 니윜띌 였귀슀튞 티소(1859)의 지시 타원을 메륎칎토륎(1569) 투영에 섞웠얎요. 겜도 180도륌 가로 64 LED 에, 위도 ±85.0511도륌 섞로 128 LED 에 놓윌멎 y = ln tan(π/4 + φ/2) 가 ꌭ ±π 에 닿아 판읎 정확히 1:2 읎고, 웹 메륎칎토륎가 정사각형 섞계륌 자륎는 위도와 같아요. 구 위에 반지늄 7도의 원을 20도 간격 쀄로 깔고, 원마닀 투영의 국소 믞분윌로 겜선 쪜 h 와 위선 쪜 k 륌 셈핎 반축 r·k, r·h 의 타원윌로 귞렀요. K4 륌 1 로 올늬멎 같은 상자에 맞춘 람베륎튞 원통 등적 투영읎 되는데, 표쀀 위선읎 55.7도읎고 원마닀 61 제곱 LED 로 넓읎가 같아젞 한 색읎 돌요. 메륎칎토륎에서는 적도의 원읎 19, 80도의 원읎 650 제곱 LED 예요. 첫 판은 반지늄 5도 원 184개륌 15도 쀄마닀 놓았는데 적도 원읎 지늄 3.6 LED 띌 점묎늬처럌 읜혔고, 75도 쀄 위가 비얎 `thirds` 가 43/71/42 였얎요. 반지늄 7도 원 104개륌 20도 쀄에 놓자 80도 쀄 원읎 반지늄 14 LED 로 위아래 끝을 채웠얎요. 귞래도 축을 Ʞ욞읎지 않은 볎통 메륎칎토륎에서는 원 쀄읎 옆윌로만 믞끄러젞서, 팚널에서는 멈춘 점묎늬로 읜혔얎요. 메륎칎토륎의 원 크Ʞ는 지도 위 높읎로만 정핎젞서 축을 Ʞ욞여도 위아래 끝의 원읎 가장 크고, Ʞ욞읎멎 원마닀 휜 Ꞟ을 따띌 돌며 ê·ž 크Ʞ륌 였가요. 귞래서 K3 을 25도부터 두얎 구의 극읎 위 가장자늬 안에 앉은 채 엎늬게 했고, `thirds` 41/72/48, 녾뾌 0.06, 0.17, 0.19, 0.22 가 됐얎요. 겜위선을 빌고 원만 섞얎도 33/60/40 읎에요. 90 의 가로 메륎칎토륎에서는 구의 극읎 판 한가욎데에 앉아 원마닀 ê·ž 둘레륌 한 바퀮 도는 동안 위 끝곌 아래 끝에서 두 번 부풀었닀 쀄얎요. + +### Question Mark Function + +죌제 5 로 믌윔프슀킀의 묌음표 핚수(1904)륌 수론 갈래에 섞웠얎요. x = [0; a1, a2, ...] 읎멎 ?(x) 의 읎진 자늿수는 0 읎 a1 − 1 개 뒀에 1 읎 a2 개, 0 읎 a3 개씩 번갈아 읎얎젞서(당죌아, 1938), 계닚 귞래프 아래에 연분수 덩얎늬 쀄곌 읎진 자늿수 쀄을 맞대얎 ê·žë žì–Žìš”. Ford Circles 가 읎믞 황ꞈ비로 파고듀얎서 원은 두지 않았얎요. 확대는 u = (x − x0)/(x − x1) 에서 죌Ʞ의 뫌비우슀 사상읎 곱셈읎 되는 것을 썚서, 황ꞈ비 점에서는 3.33쎈마닀 가로 6.85ë°°, 섞로 4ë°° 듀얎가고 0.10쎈와 3.43쎈의 프레임읎 픜셀까지 같았얎요. 첫 판은 움직임 0.321, 녾뾌 0.13, 0.08, 0.09, 0.08 읎었는데, 점의 고늬가 8 LED 띌 묻혔고 두 쀄읎 2 LED 칞을 똑같읎 번갈아 칠핎 같은 쀄묎늬가 두 번 있는 것처럌 읜혔얎요. 칞을 3 LED 로 넓혀 위 쀄은 덩얎늬가 끝나는 곳에만 틈을 두고 고늬륌 5×5 로 킀우자 움직임 0.407, 녾뾌 0.13, 0.09, 0.10, 0.09 가 됐얎요. 얎두욎 죌황 높읎선은 갈색윌로 읜혀 장밋빛윌로 바꿚고, Depth 7 의 127 쀄읎 왌쪜 위륌 한 판윌로 덮얎서 자의 눈ꞈ처럌 깊은 닚계음수록 흐늬게 했얎요. 귞래프 전첎륌 64 엎에 귞늬멎 ?(0.1) 읎 2^-9 음 만큌 0 귌처가 납작핎서, 계닚은 8번 ì—Ž 얞저늬에서알 바닥을 떠나요. + +### Burrows-Wheeler + +죌제 5 와 1 로 버로슀-휠러 변환(1994)을 섞우고, Hitomezashi 와 Sequins 둘뿐읎던 쎘쎘핚 × 몚양 바뀜 4,3 칞을 겚눎얎요. 묎작위 Ꞁ은 마지막 엎읎 두 Ꞁ자에서 평균 64 덩얎늬로 흩얎지고, 만타치와 레슀티볎, 쇌륎티녞(2003)가 볎읞 대로 슀투늄 닚얎는 ꌭ 두 덩얎늬 b^k a^m 읎 돌요. 128 은 플볎나치 수가 아니띌서 Order 의 끝은 êž°ìšžêž° 49/128 의 슀투늄 닚얎읎고, Ꞁ자가 셋 읎상읎멎 페렌치와 잠볎니(2013)의 대칭 구간 교환 부혞륌 썌는데 Ꞁ자 수 닀섯 가지에서 50개씩 250개가 몚두 Ꞁ자마닀 한 덩얎늬였얎요. 첫 판은 쎘쎘핚 0.389, 몚양 바뀜 0.258 로 처음부터 칞 안읎었지만, 첫 Ꞁ자 엎을 3 LED 로 따로 섞우자 몚양 바뀜읎 0.240 윌로 칞 아래 끝에 붙었얎요. 아직 몚륎는 행을 첎컀판의 한 칞 걎너 하나만 쌜는 유령윌로 귞렀서, 랔록 평균은 반윌로 쀄얎도 읎웃 LED 사읎 계닚은 귞대로띌 쎘쎘핚을 잃지 않아요. 유령 밝Ʞ륌 0.9 에서 0.75 로 낎늬자 쎘쎘핚 0.367, 몚양 바뀜 0.273 읎 됐고, 가장자늬가 0.760 읎띌 가장자늬 × 쎘쎘핚 3,4 칞에도 듀얎가요. 첫 판의 믞끄러짐은 행마닀 반올늌한 자늬에 놓아 한가욎데서 128쀄 가욎데 평균 54.5쀄읎 비었는데, ë§€ 프레임 지나옚 자늬 순서대로 닀시 나눠 죌자 빈 쀄읎 없얎졌얎요. 읎 팚널에서는 슀투늄 닚얎의 행 95% 가 읎웃곌 팚널의 53 Ꞁ자륌 ë‹€ 같읎 썚서, 정렬된 판읎 섞로 쀄묎늬가 되고 공통 접두사 닚계에서도 거의 흐렀지지 않았얎요. 묎작위 Ꞁ로 여는 처음 화멎은 잡음처럌 볎읎고, 마지막 엎읎 덩얎늬로 몚읎는 것은 K3 을 올렀알 또렷핎요. + +### Buffon's Noodle + +죌제 5 로 뷔퐁의 바늘(1777)곌 바륎비에의 국수(1860)륌 데읎터 귞늌 갈래에 섞웠얎요. Ꞟ읎 L 읞 바늘을 간격 d 의 평행선에 떚얎뜚늬멎 평균 2L/(πd) 번 걎너는데, 바륎비에는 같은 Ꞟ읎의 곡선읎멎 얎떻게 휘얎도 평균읎 같닀는 것을 볎였얎요. 팚턎의 교찚 셈은 팹널 밖 선까지 직선은 한 점윌로, 혾는 사읞 값 두 각윌로 정확히 풀얎요. Ꞟ읎 셋곌 휹 닀섯에 200,000번씩 떚얎뜚렞더니 평균읎 2L/(πd) 와 0.005 안에서 맞았얎요. Ꞟ읎 1 에서 한 번 떚얎뜚늎 때 걎넌 수의 분산은 곧은 바늘 1.08, 반원 0.40, 닫힌 고늬 0 읎었고, 5분 3,004개륌 떚얎뜚렀도 Ꞟ읎 0.2 의 바늘은 3.077 에 뚞묌렀얎요. 첫 판은 녞람가 0.05, 0.06, 0.13, 0.07 로 한 번에 지났지만, 떚얎지는 바늘곌 였래된 바늘을 옅게 귞늰 ꞈ색읎 갈색윌로 볎였고 Hue 0.5 에서는 바닥 선도 갈색읎었얎요. 떚얎지는 바늘을 점선윌로, 사띌지는 바늘을 양 끝부터 쀄얎듀게 바꟞고 바닥 선을 색상곌 상ꎀ없는 회청색윌로 두었얎요. 였찚 막대는 옅게 채우는 대신 바둑판 묎늬로 찍었고, 마지막 값은 움직임 0.302, 채움 0.195, 녾뾌 0.05, 0.06, 0.12, 0.07 읎에요. 64 폭에서 지늄 16 의 고늬가 선 하나륌 두 번 걎너는 몚습은 한눈에 읜혀서, 숫자가 3.1416 에 서는 것볎닀 였찚 막대가 π 선에 납작하게 눕는 것읎 뚌저 볎여요. + +### Vuza Canon + +죌제 5 로 부자(1991)의 늬듬 타음링 캐넌을 늬듬·펄슀 갈래에 섞웠얎요. Z72 륌 A + B 로 나누되 A 도 B 도 ì–Žë–€ 읎동에도 귞대로 있지 않은 ꌎ읎고, 하요시(1950)와 더 람띌윈(1953)읎 볞 비죌Ʞ 분핎에서 왔얎요. A 와 B 륌 곱한 72개의 합읎 72의 나뚞지륌 ꌭ 한 번씩 덮는지, 71가지 읎동 얎디에서도 두 집합읎 제자늬로 였지 않는지 닀시 셈했고, 팹턮도 ì—Ž 때 겹치는 박읎 있윌멎 멈춰요. 한 박을 한 쀄로 두얎 지난 104박곌 올 23박읎 볎읎고, 음은 A 의 몇 번짞 음읞지로 칠핎서 한 색의 음듀읎 B 륌 귞대로 옮ꞎ ꌎ로 서요. 귞래서 K3 을 1 로 돌렀 역할을 바꟞멎 같은 72개 음읎 같은 색 귞대로 여섯 Ʞ둥윌로 닀시 몚여요. 첫 판은 녞람가 0.04, 0.08, 0.10, 0.02 였얎요. K4 가 고륞 성부만 굵게 낚Ʞ고 나뚞지륌 두 LED 로 좁혀서 바뀌는 LED 가 적었얎요. 고륞 성부의 음마닀 팚널을 가로지륎는 옅은 선을 Ꞌ자 0.05 가 됐고, A 는 8, 8, 2, 8, 8 쀄 간격의 선 닀섯곌 38쀄의 빈 곳윌로 자처럌 읜혀요. 지ꞈ 쀄을 처음에는 음의 색을 옅게 깔았는데 ꞈ색 박에서 올늬람로 볎여서 회색 선윌로 바꟞고 음 귌처만 묌듀였얎요. 음만 귞늰 판은 쌜진 몫읎 0.074 띌 채움 축의 아래 끝을 Dragon Fold 의 0.117 에서 끌얎낎렀, 채움 칞의 겜계가 0.302, 0.558, 0.815 에서 0.268, 0.537, 0.806 윌로 옮겚질 판읎었얎요. 귞러멎 복잡도 × 채움 4,1 곌 채움 × 몚양 바뀜 1,4 가 팹턮 하나뿐읞 칞읎 돌서, 성부마닀 가욎데에 33/255 의 옅은 Ꞟ잡읎 쀄을 깔았얎요. 채움읎 0.243 읎 되고, 음 사읎에서도 성부가 Ʞ둥윌로 읜혀요. 마지막 값은 움직임 1.043, 녾뾌 0.04, 0.07, 0.12, 0.04 읎고 한 프레임 0.4 ms 예요. + +### Hex Theorem + +죌제 5 로 헥슀 정늬륌 볎로녞읎·셀 갈래에 섞웠얎요. 플튞 하읞(1942)곌 졎 낎시(1948)의 헥슀 판은 ë‹€ 채우멎 ꌭ 한 색만 제 두 변을 잇는데, 데읎비드 게음(1979)은 읎것읎 람띌우얎륎 고정점 정늬와 같은 말임을 볎였얎요. 판읎 ë‹€ 찚멎 진 색읎 제 두 변에서 흘러 듀얎였닀 읎ꞎ 사슬 앞에서 멈추고, 두 색 사읎 읎음맀에는 게음의 Ꞟ읎 아래 ꌭ짓점에서 읎ꞎ 쪜 ꌭ짓점까지 ê·žì–Žì žìš”. 판 크Ʞ 셋곌 Bias 넷윌로 24,000판을 채웠더니 늘 한 색만 읎었고, 게음의 Ꞟ읎 나간 ꌭ짓점도 맀번 읎ꞎ 색곌 맞았얎요. Ʞ욎 판에서는 읎ꞎ 색읎 판의 절반쯀을 가질 때 사슬읎 ì„°ì–Žìš”. 확률 0.625 에서 80%, 0.75 에서 67% 가 찌을 때띌, 삌각 격자 자늬 슀믞Ʞ의 묞턱 1/2 읎 팚널에서 귞대로 볎여요. 첫 판은 7 × 7 판읎 3.2쎈에 찚서 여는 판의 첫 승늬가 3.03쎈, 49칞 가욎데 46칞짞에 왔는데, 2.7쎈로 쀄읎자 2.57쎈가 되고 녞람가 0.13, 0.12, 0.13, 0.07 에서 0.13, 0.13, 0.15, 0.10 읎 됐얎요. 진 색의 묌결을 읎ꞎ 사슬만큌 밝게 칠하멎 누가 읎게는지 읜히지 않아서, 묌결은 옅게 하고 낚은 칞은 0.3 까지 낮췄얎요. + +### Universal Cover + +죌제 5 와 4 로 8자 고늬의 범플복 공간을 위상·맀듭 갈래에 섞웠얎요. 8자 고늬의 Ʞ볞군은 a 와 b 의 자유군 F2 읎고(앙늬 푞앵칎레, 1895), ê·ž 범플복은 F2 의 쌀음늬 귞래프읞 ë„€ 갈래 나묎예요(막슀 덎, 1910). 아래 고늬에서 점읎 한 바퀮 돌 때마닀 위의 올늌읎 같은 Ꞁ자의 변 하나륌 지나서, 쀄읞 낱말읎 뿌늬에서 올늌까지의 Ꞟ로 낚고 ê·ž Ꞟ은 낱말읎 항등원윌로 쀄 때만 닫혀요. 화멎은 걞음마닀 올늌을 새 뿌늬로 삌고, 변 Ꞟ읎 L0/2^e 의 e 륌 떠나는 마디에서 잰 거늬에서 닿는 마디에서 잰 거늬로 믞끄러뜚렀 한쪜 가지는 자띌고 닀륞 쪜은 쀄며 나묎가 끊Ʞ지 않고 옮겚 가요. 되돌늌 0.25 의 닚순 묎작위 걞음을 300번 돌늬자 100걞음 ë’€ 낱말읎 평균 50.2 Ꞁ자로 쌀슀텐(1959)의 탈출 속도 1/2 에 맞았고, 0.85 에서는 올늌읎 뿌늬에 41.4% 뚞묌러 셈한 값 41.2% 와 맞았얎요. 첫 판은 녞람가 0.03, 0.05, 0.05, 0.08, `thirds` 가 22/22/13 읎었고, 깊은 가지륌 밝히고 올늌 둘레 변을 2 LED 로 ê·žë € Hue 륌 0.05 로 올렞얎요. K4 륌 쀌에서 읎늄대로 귞늬는 당 수로 바꟞자 0.03 윌로 떚얎젞서, 닚읎 쀄멎 변을 늘여 나묎가 늘 60 LED 너비륌 채우게 하니 0.06 읎 됐고, 마지막 값은 `thirds` 20/21/15 에 녾뾌 0.05, 0.07, 0.06, 0.06 읎에요. 1.5닚에서는 깊은 가지의 흐늰 뚌지가 사띌지고 굵은 십자 하나가 낹는데, 팚널에서는 였히렀 귞쪜읎 덎의 êµ° 귞늌처럌 읜혔얎요. + +### Toussaint-Wilczek + +죌제 5 와 1 로 였람친니윔프와 저도비치(1978), 투생곌 윌첵(1983)의 확산 제한 소멞 A + B → 0 을 섞우고, Percolation 곌 Sequins 둘뿐읎던 가장자늬 × 쎘쎘핚 3,4 칞을 겚눎얎요. LED 마닀 A 와 B 륌 평균 80개씩 푞아송윌로 뿌렀 ê·ž 자늬에서 상쇄하멎 낚은 부혞가 흰 잡음읎 되고, 씚앗 여덟윌로 재 볎니 낚은 밀도가 5걞음에서 80걞음 사읎에 지수 −0.49 로 쀄얎 잘 섞읞 반응식의 −1 볎닀 훚씬 느렞얎요. 첫 판은 걞음읎 쎈당 20번에 Density 60 읎띌 2.5쎈 만에 영역읎 팹널 너비만 핎졌고, 가장자늬 0.846, 쎘쎘핚 0.277 읎었얎요. 팹널 전첎의 우연한 덀도 묞제여서, 둘짞 판에서는 덀 2,478 개 때묞에 8쎈에 낚은 3,600 개 가욎데 3,039 개가 자홍읎었얎요. 두 종을 ꌭ 같은 수로 맞춰 뿌늬고 걞음을 쎈당 8번, Density 륌 80 윌로 하자 2~8쎈에 LED 의 59% 에서 39% 가 입자륌 품었얎요. 빈 LED 는 얎둡게 두고 읎웃 입자 하나마닀 2.5/255 만 올렀 부드러욎 계닚을 낚Ʞ자 가장자늬 0.663, 쎘쎘핚 0.344 가 됐고, 녾뾌는 0.38, 0.38, 0.24, 0.34 예요. 영역읎 넓얎지며 랔록 평균읎 바뀌얎 몚양 바뀜읎 0.220 읎띌, 쎘쎘핚 × 몚양 바뀜 4,3 칞에도 듀얎가요. LED 의 절반쯀만 입자가 쌜도 영역은 색윌로 읜혀서 뒀의 흐늰 빛은 거의 볎읎지 않았고, 영역을 가륎는 것은 얎두욎 읎음맀였얎요. + +### Vortex Sheet + +죌제 5 로 헬늄홀잠(1868)와 쌈빈(1871)의 와류 시튞 불안정을 섞웠얎요. 크띌슀니(1986)가 묎얎(1979)의 곡률 특읎점 너뚞까지 셈한 와동 덩읎 방법 귞대로, 한 파장의 시튞륌 점 와동 96개로 시작핎 틈읎 0.015 파장을 넘윌멎 점을 넣얎 340개까지 늘늬며 RK4 로 풀고, 팚널에는 ê·ž 죌Ʞ륌 Waves 만큌 쌓아요. 덩읎륌 0.05 로 쀄읎멎 작은 묌결의 성장률읎 2.97 로 ë§š 시튞의 π 에 닀가갔고, ë‹€ 말늬는 동안 순환윌로 가쀑한 시튞의 가로 묎게쀑심은 9e-6 파장 안에서 귞대로였얎요. 첫 판은 틈을 0.0075 로 잡아 2쎈 안에 420 점을 ë‹€ 썌고, 한 프레임읎 5.0 ms 에 시튞가 2.5쎈마닀 새로 ì„°ì–Žìš”. 틈을 두 배로 넓히고 컀널을 점마닀 닀섯 수로 묶얎 읜자 2.2 ms 에 시튞 하나가 8.6쎈륌 삎아요. 새 시튞마닀 섞던 둘짞 고조파는 덩읎 0.25 아래에서 작은 둘짞 나선윌로 말렀 첫 나선곌 엉쌜서 뺐고, 덩읎 0.1 에서는 400 점윌로도 윔얎륌 붙잡지 못핎 4.5쎈부터 시튞가 핎젞서 K3 은 0.15 에서 ì—Žì–Žìš”. 4쎈가 지나멎 윔얎의 겹읎 LED 볎닀 쎘쎘핎젞 연볎띌 원판윌로 찚지만, ê·ž 둘레의 겹은 청록곌 볎띌 두 묌의 띠로 갈렀 또렷읎 읜혀요. 움직임 0.671, 녾뾌는 0.18, 0.10, 0.07, 0.09 읎고 `thirds` 는 100/100/100 읎에요. + ## 지도 지도의 자늬는 손윌로 맀ꞎ 점수가 아니띌 잰 값읎에요. 팚턎마닀 처음 녾뾌 값윌로 Lab 엔진에서 8쎈륌 돌늬고, 2쎈부터 6쎈 동안의 화멎을 재요. LED 하나의 밝Ʞ는 섞 채널 가욎데 가장 밝은 값윌로 뎐요. @@ -2777,7 +2836,7 @@ ESP32-S3 로 옮Ꞟ 때 걞늎 것도 있얎요. Reaction Diffusion 은 Node 에 ## 닀음 판 감 -10월 25음 `dev`(`f72355d`)의 328개에 43판부터 53판까지의 백엎 개륌 더한 438개에서 잰 값읎에요. 팹턮 바퀮는 죌제 안에서 묎엇을 할지 고륌 때 읎 절을 읜고, 한 것은 빌고 새로 볞 것을 더핎요. +10월 25음 `dev`(`f72355d`)의 328개에 43판부터 53판까지의 백엎 개와 55판의 ì—Ž 개륌 더한 448개에서 잰 값읎에요. 팹턮 바퀮는 죌제 안에서 묎엇을 할지 고륌 때 읎 절을 읜고, 한 것은 빌고 새로 볞 것을 더핎요. ### 지도의 빈 칞 @@ -2829,6 +2888,8 @@ ESP32-S3 로 옮Ꞟ 때 걞늎 것도 있얎요. Reaction Diffusion 은 Node 에 53판에서는 축 끝읎 둘 옮겚졌얎요. Gaussian Periods 가 움직임 축의 위 끝을 Ripple Tank 의 4.751 에서 4.910 윌로, Squared Rectangle 읎 복잡도 축의 아래 끝을 Hinged Dissection 의 0.058 에서 0.051 로 넓혔얎요. 귞래서 움직임 칞의 겜계는 0.061, 0.301, 1.490 에서 0.061, 0.305, 1.529 로, 복잡도 칞의 겜계는 0.620, 1.397, 2.173 에서 0.615, 1.393, 2.172 로 조ꞈ 옮겚졌얎요. 빈 칞은 생Ʞ지 않아서 438개에서도 지도 21장읎 ë‹€ `none` 읎에요. 죌제 1 은 51판까지의 418개에 낚은 둘뿐읞 칞 ì—Žì…‹ 가욎데, 같읎 돈 52판읎 겚눈 복잡도 × 몚양 바뀜 4,4 와 가장자늬 × 복잡도 3,4 륌 빌고 움직임 × 쎘쎘핚 4,4 와 복잡도 × 채움 4,1 을 겚눎얎요. 52판까지 든 428개에서 엎하나읎던 귞런 칞읎 438개에서 아홉읎에요. 움직임 × 쎘쎘핚 4,4 는 1쎈에 바뀌는 빛읎 1.49 륌 넘윌멎서 읎웃 찚읎의 평균읎 0.297 을 넘얎알 하는 칞읎에요. 판읎 빠륎게 바뀌는 잔 결은 잡음읎 되Ʞ 쉬욎데, Gaussian Periods 는 ë–šì–Žì ž 선 점 3,641개가 저마닀 자늬륌 옮겚서 두 값을 핚께 넘었얎요. 점읎 서로 붙윌멎 쎘쎘핚읎 낎렀가서, 점을 직선 대신 혞륌 따띌 옮Ʞ고 몚서늬까지 펌쳐알 했얎요. 복잡도 × 채움 4,1 은 쌜진 LED 가 30.2% 아래읎멎서 한 화멎읎 deflate 로 LED 당 2.173 바읎튞륌 넘얎알 하는 칞읎에요. Leapfrogging Vortices 의 첫 판은 한 프레임의 바읎튞 엔튞로플가 5.70 비튞, LED 당 2.14 바읎튞띌 deflate 가 ê·ž 위로 갈 수 없었얎요. 꺌진 LED 도 바읎튞륌 쓰게, 묌읎 옚 자늬륌 거꟞로 따띌가는 반짝읎륌 24 아래로 싀었얎요. 낚은 아홉 가욎데 음곱은 쎘쎘핚 축읎 든 칞읎고, 둘은 복잡도가 2.172 륌 넘얎알 하는 칞읎에요. +55판의 ì—Ž 개도 얎느 축의 끝을 넘지 않아서 칞 겜계는 53판 귞대로읎고, 448개에서도 지도 21장읎 ë‹€ `none` 읎에요. 처음 지은 Vuza Canon 은 쌜진 몫읎 0.074 띌 채움 축의 아래 끝을 Dragon Fold 의 0.117 에서 끌얎낎늎 판읎었얎요. 채움 칞의 겜계가 0.302, 0.558, 0.815 에서 0.268, 0.537, 0.806 윌로 옮겚지멎 Lava Lake 와 Moth Lamp 가 복잡도 × 채움 4,1 에서 빠젞서, 성부마닀 옅은 Ꞟ잡읎 쀄을 깔아 0.243 윌로 올렞얎요. 죌제 1 은 53판까지의 438개에 낚은 둘뿐읞 칞 여덟 가욎데, 같읎 돈 54판읎 겚눈 복잡도 × 채움 4,2 와 쎘쎘핚 × 몚양 바뀜 4,4 륌 빌고 가장자늬 × 쎘쎘핚 3,4 와 쎘쎘핚 × 몚양 바뀜 4,3 을 겚눎얎요. 가장자늬 × 쎘쎘핚 3,4 는 40/255 륌 넘는 읎웃 찚읎의 몫읎 0.500~0.790 읎멎서 읎웃 찚읎의 평균읎 0.297 을 넘얎알 하는 칞읎에요. Toussaint-Wilczek 은 2~8쎈에 LED 의 59% 에서 39% 륌 한 LED 짜늬 입자로 쌜고 빈 LED 륌 얎둡게 두얎 0.663, 0.344 에 ì„°ì–Žìš”. 쎘쎘핚 × 몚양 바뀜 4,3 은 읎웃 찚읎의 평균읎 0.297 을 넘윌멎서 2~8쎈에 4×16 칞 평균읎 0.217~0.331 바뀌얎알 하는 칞읎에요. Burrows-Wheeler 는 밝Ʞ가 0.84 만큌 닀륞 두 Ꞁ자의 행렬로 쎘쎘핚 0.367 을 낎고, 아직 몚륎는 행을 첎컀판 반쪜만 쌜는 LF 걞음윌로 몚양 바뀜 0.273 을 냈얎요. 두 팚턎은 서로의 칞에도 듀얎가서 둘뿐읎던 두 칞읎 넷씩읎 됐얎요. Toussaint-Wilczek 의 몚양 바뀜읎 0.220, Burrows-Wheeler 의 가장자늬가 0.760 읎에요. 한 LED 짜늬 칞읎 판을 ë‹€ 덮고 읎웃끌늬 밝Ʞ가 크게 닀륎멎 쎘쎘핚 축의 ë§š 위 쀄에 서는데, ê·ž 칞읎 시간에 따띌 넓은 덩얎늬로 몚읎거나 흐렀지멎 몚양 바뀜도 핚께 올띌요. + 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 륌 냈얎요. @@ -2843,7 +2904,7 @@ ESP32-S3 로 옮Ꞟ 때 걞늎 것도 있얎요. Reaction Diffusion 은 Node 에 ### 아직 없는 갈래 -`families.json` 의 쉰여섯 갈래에 없는 것 가욎데 섞욎 팚널에 맞을 것을 적얎 둬요. 29판읎 연 위상·맀듭 갈래는 Hopf Fibration 곌 31판의 Boy's Surface, 33판의 Borromean Rings, 34판의 Lakes of Wada, 36판의 Heawood Torus, 38판의 Sperner's Lemma, 39판의 Borsuk-Ulam, 44판의 Ham Sandwich, 46판의 Reeb Graph, 48판의 Möbius Energy, 49판의 Square Peg, 51판의 Persistence Barcode, 53판의 Belt Trick 읎에요. 28판읎 엎렀닀 접은 갈래띌 28판의 Hairy Ball 은 녞읎슈·흐늄에 귞대로 있얎요. 섞잎 맀듭은 48판의 Möbius Energy 가 처음 섞웠얎요. 29판은 화장품 팝업 갈래도 엎었얎요. 죌제 6 의 몫읎띌 죌제 2 로 섞지 않았고, Cream Swirl, Serum Gel, Glitter Fall, Loose Powder 넷읎 ë‹€ 듀얎 있얎요. 30판의 Swatch Row, Pearl Sheen, Bi-Phase, Satin Fold 가 ê·ž 갈래에 넷을 더했고, 31판의 Lip Gloss, Foam Lather, Setting Mist, Sponge Blend 가 넷을 더 더핮 엎둘읎에요. 32판의 Cushion Mesh, Clay Mask, Sugar Scrub, Chrome Powder 가 넷을 더핮 엎여섯읎고, 33판의 Cleansing Balm, Gold Leaf, Essence Drop, Peel-Off 가 넷을 더핮 슀묌읎에요. 34판의 Cat Eye, Velvet Nap, Mood Tint, Hair Gloss 가 넷을 더핮 슀묌넷읎고, 35판의 Bath Fizz, Toner Pad, Sheet Mask, Gilded Cream 읎 넷을 더핮 슀묌여덟읎고, 36판의 Lotion Ribbon, Shimmer Oil, Blooming Gel, Whipped Soap 읎 넷을 더핮 서륞둘읎에요. 38판의 Milk Ring, Couette Shimmer, Photochromic, Serum Fingers 가 넷을 더핮 서륞여섯읎에요. 39판의 Soap Film, Blotting Paper, Serum Drip, Crackle Polish 가 넷을 더핮, 37판의 넷까지 마흔넷읎에요. 41판은 할로윈 갈래륌 엎었얎요. 죌제 6 의 몫읎띌 죌제 2 로 섞지 않았고, Jack-o'-Lantern, Sheet Ghosts, Spider Drop, Cauldron, Eyes in the Dark 닀섯읎 듀얎 있얎요. 28판읎 연 수론 갈래는 Ford Circles 와 30판의 Three Gaps, 31판의 Gaussian Moat, 32판의 Gilbreath Table, 33판의 Goldbach Comet, 35판의 Kolakoski, 36판의 Elliptic Chords, 39판의 Explicit Formula, 44판의 Bulgarian Solitaire, 48판의 Chebyshev's Bias, 49판의 Curlicue, 51판의 Sato-Tate, 53판의 Gaussian Periods 예요. 27판읎 연 철도 갈래는 Funicular 하나읎고, 필늄·영화 갈래는 Horse in Motion 하나예요. 두 갈래는 죌제 표가 수학 쪜윌로 바뀌Ʞ 전에 엎었얎요. ê·ž 뒀로 ROUND.md 는 사묌로 묶는 갈래륌 더 ì—Žì§€ 않아요. 26판읎 연 얌음·극지 갈래는 Iceberg 와 27판의 Sled Dogs 읎고, 믞생묌 갈래는 Dark Field 와 27판의 Mitosis 예요. 25판읎 연 농사·밭 갈래는 Rice Terraces 와 26판의 Rice Planting, 27판의 Center Pivot 읎고, 화산 갈래는 Eruption 곌 26판의 Geyser, 27판의 Lava Lake 예요. 24판읎 연 공사·걎섀 갈래는 Tower Crane 곌 25판의 Excavation, 26판의 Implosion 읎에요. 24판읎 연 도자Ʞ 갈래는 Potter's Wheel 곌 25판의 Kintsugi, 26판의 Noborigama 예요. 23판읎 연 묎대·공연 갈래는 Proscenium 곌 24판의 Flying Trapeze, 25판의 Marionette, 26판의 Shadow Play 예요. 23판읎 연 짐승 갈래는 Giraffe 와 24판의 Emergence, 25판의 Snow Monkeys 예요. 22판읎 연 몞 갈래는 Circulation 곌 23판의 Axon, 24판의 Breath, 27판의 Fluoroscope 예요. 22판읎 연 비행 갈래는 Liftoff 와 23판의 Final Approach, 24판의 Thermal, 25판의 Skydiver 예요. 20판읎 연 새 갈래는 Kingfisher 와 21판의 Woodpecker, 22판의 Weaver, 23판의 Skein 읎에요. 20판읎 연 화학·싀험 갈래는 Chromatography 와 21판의 Chemical Garden, 22판의 Flame Test, 23판의 Elephant Toothpaste 예요. 21판읎 연 종읎 갈래는 Pop-up Book 곌 22판의 Senbazuru, 23판의 Hanging Scroll, 26판의 Origami 예요. 21판읎 연 욎동·겜Ʞ 갈래는 Lap Pool 곌 22판의 Bowling Lane, 23판의 High Dive, 26판의 Pole Vault, 27판의 Climbing Wall 읎에요. 위아래로 움직읎는 겜Ʞ는 High Dive 와 Pole Vault 뒀로 27판에 싀낎 암벜읎 ì„°ì–Žìš”. 19판읎 연 부엌 갈래는 Popcorn 곌 20판의 Kettle, 21판의 Pancakes 읎고, 버섯·균사 갈래는 Foxfire 와 20판의 Veiled Lady, 21판의 Bread Mould 예요. 18판읎 연 유늬 갈래는 Stained Glass 와 19판의 Smalti, 20판의 Millefiori, 27판의 Glassblower 예요. ꎑ묌·결정 갈래는 Bismuth 와 19판의 Dripstone, 20판의 Agate 읎고, 놀읎공원 갈래는 Drop Tower 와 19판의 Helter Skelter, 20판의 High Striker, 25판의 Ferris Wheel 읎에요. 말읎 였륎낎늬는 회전목마는 아직 없얎요. 17판읎 연 전자 회로 갈래는 Circuit Board 와 18판의 Server Rack, 21판의 Nixie 읎고, 장난감 갈래는 Marble Run 곌 18판의 Peg Light, 21판의 Slinky, 22판의 Pin Art 예요. 16판읎 연 바닀 갈래는 Abyss 와 17판의 Shoreline, 20판의 Sunglint, 23판의 Bait Ball 읎고, 곀충 갈래는 Fireflies 와 17판의 Ant Farm, 20판의 Moth Lamp, 24판의 Waggle Dance 예요. 15판읎 연 도시 갈래는 Metro Map 곌 16판의 Neon Alley, 17판의 Crossroads, 22판의 Night Flight 예요. 15판읎 연 음악 갈래는 Player Piano 와 16판의 Harp, 18판의 Turntables, 24판의 Pipe Organ 읎에요. 4판읎 연 직묌 갈래는 Jacquard Loom 곌 15판의 Plait, 19판의 Needlepoint, 23판의 Sequins 읎고, 8판읎 연 Ʞ계 갈래는 Gear Train 곌 15판의 Cam Wave, 19판의 Paternoster 예요. 12판읎 연 천첎 갈래는 Star Trails 와 15판의 Analemma, 16판의 Lunation, 22판의 Galilean Moons 예요. 위성의 궀도는 Galilean Moons 가 ê·žë žê³ , 핎륌 도는 행성은 아직 없얎요. 늬듬·펄슀 갈래는 1판의 Euclid Pulse 와 Pulse Bars 뒀로 20판의 Change Ringing, 32판의 Arnold Tongues, 35판의 Chimera State, 38판의 Infinity Series, 41판의 Siteswap, 44판의 Kac Ring, 46판의 Stochastic Resonance 가 ì„°ì–Žìš”. +`families.json` 의 쉰여섯 갈래에 없는 것 가욎데 섞욎 팚널에 맞을 것을 적얎 둬요. 29판읎 연 위상·맀듭 갈래는 Hopf Fibration 곌 31판의 Boy's Surface, 33판의 Borromean Rings, 34판의 Lakes of Wada, 36판의 Heawood Torus, 38판의 Sperner's Lemma, 39판의 Borsuk-Ulam, 44판의 Ham Sandwich, 46판의 Reeb Graph, 48판의 Möbius Energy, 49판의 Square Peg, 51판의 Persistence Barcode, 53판의 Belt Trick, 55판의 Universal Cover 예요. 28판읎 엎렀닀 접은 갈래띌 28판의 Hairy Ball 은 녞읎슈·흐늄에 귞대로 있얎요. 섞잎 맀듭은 48판의 Möbius Energy 가 처음 섞웠얎요. 29판은 화장품 팝업 갈래도 엎었얎요. 죌제 6 의 몫읎띌 죌제 2 로 섞지 않았고, Cream Swirl, Serum Gel, Glitter Fall, Loose Powder 넷읎 ë‹€ 듀얎 있얎요. 30판의 Swatch Row, Pearl Sheen, Bi-Phase, Satin Fold 가 ê·ž 갈래에 넷을 더했고, 31판의 Lip Gloss, Foam Lather, Setting Mist, Sponge Blend 가 넷을 더 더핮 엎둘읎에요. 32판의 Cushion Mesh, Clay Mask, Sugar Scrub, Chrome Powder 가 넷을 더핮 엎여섯읎고, 33판의 Cleansing Balm, Gold Leaf, Essence Drop, Peel-Off 가 넷을 더핮 슀묌읎에요. 34판의 Cat Eye, Velvet Nap, Mood Tint, Hair Gloss 가 넷을 더핮 슀묌넷읎고, 35판의 Bath Fizz, Toner Pad, Sheet Mask, Gilded Cream 읎 넷을 더핮 슀묌여덟읎고, 36판의 Lotion Ribbon, Shimmer Oil, Blooming Gel, Whipped Soap 읎 넷을 더핮 서륞둘읎에요. 38판의 Milk Ring, Couette Shimmer, Photochromic, Serum Fingers 가 넷을 더핮 서륞여섯읎에요. 39판의 Soap Film, Blotting Paper, Serum Drip, Crackle Polish 가 넷을 더핮, 37판의 넷까지 마흔넷읎에요. 41판은 할로윈 갈래륌 엎었얎요. 죌제 6 의 몫읎띌 죌제 2 로 섞지 않았고, Jack-o'-Lantern, Sheet Ghosts, Spider Drop, Cauldron, Eyes in the Dark 닀섯읎 듀얎 있얎요. 28판읎 연 수론 갈래는 Ford Circles 와 30판의 Three Gaps, 31판의 Gaussian Moat, 32판의 Gilbreath Table, 33판의 Goldbach Comet, 35판의 Kolakoski, 36판의 Elliptic Chords, 39판의 Explicit Formula, 44판의 Bulgarian Solitaire, 48판의 Chebyshev's Bias, 49판의 Curlicue, 51판의 Sato-Tate, 53판의 Gaussian Periods, 55판의 Question Mark Function 읎에요. 27판읎 연 철도 갈래는 Funicular 하나읎고, 필늄·영화 갈래는 Horse in Motion 하나예요. 두 갈래는 죌제 표가 수학 쪜윌로 바뀌Ʞ 전에 엎었얎요. ê·ž 뒀로 ROUND.md 는 사묌로 묶는 갈래륌 더 ì—Žì§€ 않아요. 26판읎 연 얌음·극지 갈래는 Iceberg 와 27판의 Sled Dogs 읎고, 믞생묌 갈래는 Dark Field 와 27판의 Mitosis 예요. 25판읎 연 농사·밭 갈래는 Rice Terraces 와 26판의 Rice Planting, 27판의 Center Pivot 읎고, 화산 갈래는 Eruption 곌 26판의 Geyser, 27판의 Lava Lake 예요. 24판읎 연 공사·걎섀 갈래는 Tower Crane 곌 25판의 Excavation, 26판의 Implosion 읎에요. 24판읎 연 도자Ʞ 갈래는 Potter's Wheel 곌 25판의 Kintsugi, 26판의 Noborigama 예요. 23판읎 연 묎대·공연 갈래는 Proscenium 곌 24판의 Flying Trapeze, 25판의 Marionette, 26판의 Shadow Play 예요. 23판읎 연 짐승 갈래는 Giraffe 와 24판의 Emergence, 25판의 Snow Monkeys 예요. 22판읎 연 몞 갈래는 Circulation 곌 23판의 Axon, 24판의 Breath, 27판의 Fluoroscope 예요. 22판읎 연 비행 갈래는 Liftoff 와 23판의 Final Approach, 24판의 Thermal, 25판의 Skydiver 예요. 20판읎 연 새 갈래는 Kingfisher 와 21판의 Woodpecker, 22판의 Weaver, 23판의 Skein 읎에요. 20판읎 연 화학·싀험 갈래는 Chromatography 와 21판의 Chemical Garden, 22판의 Flame Test, 23판의 Elephant Toothpaste 예요. 21판읎 연 종읎 갈래는 Pop-up Book 곌 22판의 Senbazuru, 23판의 Hanging Scroll, 26판의 Origami 예요. 21판읎 연 욎동·겜Ʞ 갈래는 Lap Pool 곌 22판의 Bowling Lane, 23판의 High Dive, 26판의 Pole Vault, 27판의 Climbing Wall 읎에요. 위아래로 움직읎는 겜Ʞ는 High Dive 와 Pole Vault 뒀로 27판에 싀낎 암벜읎 ì„°ì–Žìš”. 19판읎 연 부엌 갈래는 Popcorn 곌 20판의 Kettle, 21판의 Pancakes 읎고, 버섯·균사 갈래는 Foxfire 와 20판의 Veiled Lady, 21판의 Bread Mould 예요. 18판읎 연 유늬 갈래는 Stained Glass 와 19판의 Smalti, 20판의 Millefiori, 27판의 Glassblower 예요. ꎑ묌·결정 갈래는 Bismuth 와 19판의 Dripstone, 20판의 Agate 읎고, 놀읎공원 갈래는 Drop Tower 와 19판의 Helter Skelter, 20판의 High Striker, 25판의 Ferris Wheel 읎에요. 말읎 였륎낎늬는 회전목마는 아직 없얎요. 17판읎 연 전자 회로 갈래는 Circuit Board 와 18판의 Server Rack, 21판의 Nixie 읎고, 장난감 갈래는 Marble Run 곌 18판의 Peg Light, 21판의 Slinky, 22판의 Pin Art 예요. 16판읎 연 바닀 갈래는 Abyss 와 17판의 Shoreline, 20판의 Sunglint, 23판의 Bait Ball 읎고, 곀충 갈래는 Fireflies 와 17판의 Ant Farm, 20판의 Moth Lamp, 24판의 Waggle Dance 예요. 15판읎 연 도시 갈래는 Metro Map 곌 16판의 Neon Alley, 17판의 Crossroads, 22판의 Night Flight 예요. 15판읎 연 음악 갈래는 Player Piano 와 16판의 Harp, 18판의 Turntables, 24판의 Pipe Organ 읎에요. 4판읎 연 직묌 갈래는 Jacquard Loom 곌 15판의 Plait, 19판의 Needlepoint, 23판의 Sequins 읎고, 8판읎 연 Ʞ계 갈래는 Gear Train 곌 15판의 Cam Wave, 19판의 Paternoster 예요. 12판읎 연 천첎 갈래는 Star Trails 와 15판의 Analemma, 16판의 Lunation, 22판의 Galilean Moons 예요. 위성의 궀도는 Galilean Moons 가 ê·žë žê³ , 핎륌 도는 행성은 아직 없얎요. 늬듬·펄슀 갈래는 1판의 Euclid Pulse 와 Pulse Bars 뒀로 20판의 Change Ringing, 32판의 Arnold Tongues, 35판의 Chimera State, 38판의 Infinity Series, 41판의 Siteswap, 44판의 Kac Ring, 46판의 Stochastic Resonance 가 ì„°ì–Žìš”. ### 멋진 것 더 멋지게 @@ -2893,6 +2954,8 @@ ESP32-S3 로 옮Ꞟ 때 걞늎 것도 있얎요. Reaction Diffusion 은 Node 에 53판의 ì—Ž 개는 가장 낮은 녞람가 Max-Flow Min-Cut 의 K4 Grain 윌로 ë„€ 자늬로 0.0365 읎고, 같은 팚턎의 K2 Speed 0.0368 곌 Catenoid-Helicoid 의 K3 Squares 0.0390 읎 귞닀음읎에요. 녾뾌 마흔 개가 ë‹€ 0.036 을 넘얎요. 검은 바탕에 적은 점을 찍는 귞늌은 읎번에도 색 녞람가 앜했얎요. Costas Array 의 Hue 는 첫 판읎 0.02 였고, 지ꞈ 욞늬는 음의 행을 띠로 칠하고 사볞읎 얹힌 판을 옅게 칠핎 0.0419 가 됐얎요. Euler's Elastica 의 Hue 는 막대와 작은 진자만 묌듀음 때 0.03 읎었고, 상평멎의 궀도 닀섯을 Ξ 의 색윌로 칠핎 0.0447 읎 됐얎요. 녞람륌 재는 2~3쎈 찜읎 읎번에도 첫 음을 정했얎요. Barycentric Subdivision 의 Steer 는 첫 닚읎 2.7쎈띌 찜을 거의 ë‹€ 덮얎서 0.02 였고, 앞윌로 갈 섞 걞음을 흰 선윌로 믞늬 Ꞌ고 한 닚을 2.2쎈로 쀄여 0.0697 읎 됐얎요. Max-Flow Min-Cut 의 Grain 은 용량읎 고륎게 흩얎지는 쪜윌로 두멎 낮은 값에서 절닚읎 원천 막대에 붙얎서, 용량을 맀끄러욎 지형곌 ꎀ마닀 제 몫윌로 나누는 비윚로 바꿚얎요. Gaussian Periods 와 Squared Rectangle 은 넷 ë‹€ 0.13 을 넘얎요. 점마닀 자늬가, 타음마닀 크Ʞ가 녞람로 바뀌얎서예요. +55판의 ì—Ž 개는 가장 낮은 녞람가 Vuza Canon 의 K1 Hue 로 ë„€ 자늬로 0.0402 읎고, 같은 팚턎의 K4 Focus 0.0412 와 Buffon's Noodle 의 K1 Hue 0.0469 가 귞닀음읎에요. 녾뾌 마흔 개가 ë‹€ 0.040 을 넘얎요. 검은 바탕에 가는 것을 귞늬는 귞늌은 읎번에도 고륎는 녞람가 앜했얎요. Vuza Canon 의 Focus 는 고륞 성부만 굵게 낚Ꞟ 때 0.02 였고, ê·ž 성부의 음마닀 팚널을 가로지륎는 옅은 선을 Ꞌ자 0.04 륌 넘었얎요. Universal Cover 의 Depth 는 나묎륌 확대하는 녞람로 둘 때 0.03 읎었고, 닚읎 쀄멎 변을 늘여 나묎가 늘 60 LED 너비륌 채우게 하자 0.06 읎 됐얎요. 녞람륌 재는 2~3쎈 찜읎 읎번에도 첫 음을 정했얎요. Hex Theorem 은 7 × 7 판읎 3.2쎈에 찚서 첫 사슬읎 3.03쎈에 섰고, 판을 2.7쎈에 채워 2.57쎈로 당Ʞ자 녞람가 0.13, 0.12, 0.13, 0.07 에서 0.13, 0.13, 0.15, 0.10 읎 됐얎요. Burrows-Wheeler 와 Toussaint-Wilczek 은 넷 ë‹€ 0.24 륌 넘얎요. 판을 ë‹€ 덮은 한 LED 짜늬 칞읎 녞람마닀 통짞로 바뀌얎서예요. + Flock, Harmonograph, Big Type 은 처음 화멎읎 빚강읎에요. ### 섞로 64×128 @@ -2947,11 +3010,13 @@ Flock, Harmonograph, Big Type 은 처음 화멎읎 빚강읎에요. 53판에서는 Catenoid-Helicoid 와 Max-Flow Min-Cut 읎 128쀄을 썚요. Catenoid-Helicoid 는 나선멎읎 축을 따띌 판을 섞로로 채우고, 킀가 너비의 0.66 배륌 넘지 못하는 현수멎에서는 시선을 낎렀 입을 듀여닀볎게 핎서 `thirds` 가 25/61/30 읎에요. Max-Flow Min-Cut 은 원천 막대에서 싱크 막대까지 귞묌읎 판을 덮고 흐늄읎 아래에서 위로 올띌가서 42/48/58 읎에요. 위아래 두 쀄씩의 막대륌 빌도 한 칞에서 빠지는 몫읎 4.7 읎 안 돌서 섞 칞읎 ë‹€ 5 륌 넘얎요. Barycentric Subdivision(99/95/85)곌 Squared Rectangle(85/82/84), Gaussian Periods(21/48/21), Costas Array(26/17/16), Belt Trick(18/33/18), Leapfrogging Vortices(16/48/9), Four-Vertex Theorem(9/49/9)도 섞 칞읎 ë‹€ 5 륌 넘지만 죌제 4 로 섞지 않았얎요. Euler's Elastica(16/18/17)는 120 LED 막대가 곧게 서멎 판 높읎륌 ë‹€ 쓰지만, 2~8쎈 동안 막대만 섞멎 2.8/18.2/2.4 띌서 섞지 않았얎요. 위아래 칞은 진자와 상평멎읎 채워요. +55판에서는 Imaginary Geometry 와 Tissot's Indicatrix, Universal Cover 가 128쀄을 썚요. Imaginary Geometry 는 아래 가욎데 한 점에서 떠난 흐늄선읎 위 가욎데 한 점까지 판을 섞로로 걎너서, 뒀에 깐 방향 색을 22/255 로 낮춰 선만 섞얎도 `thirds` 가 33/37/34 예요. Tissot's Indicatrix 는 위도 ±85.05도륌 128쀄에 놓은 메륎칎토륎띌 원읎 위아래 끝에서 가장 컀서 41/72/48 읎고, 겜위선을 빌고 원만 섞얎도 33/60/40 읎에요. Universal Cover 는 플복 공간을 바닥 공간 위에 귞늬는 귞대로 나묎륌 위에, 8자 고늬륌 아래에 두얎 20/21/15 예요. Burrows-Wheeler(82/81/81)와 Vortex Sheet(100/100/100), Toussaint-Wilczek(67/68/63), Question Mark Function(32/61/61), Hex Theorem(27/64/30), Vuza Canon(24/24/24), Buffon's Noodle(21/18/18)도 섞 칞읎 ë‹€ 5 륌 넘지만 죌제 4 로 섞지 않았얎요. Vuza Canon 은 Ꞟ잡읎 쀄을 빌멎 6/7/8 읎에요. + ### 낯선 수학 -죌제 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)예요. 49판은 닀섯을 더했얎요. 뒀러의 전개 묞제와 셰퍌드의 추잡(DÃŒrer's Unfolding), 베늬와 곚드버귞가 재규격화한 섞타 합의 ꌬ부랑 곡선(Curlicue), 였음러 방정식곌 푞앵소의 폎혞드로 볞 테니슀 띌쌓 정늬(Tennis Racket), 쌐프의 사슬곌 ë„€ 색 정늬(Kempe Chains), 퇎플늬잠의 낎접 정사각형 묞제와 귞늰-로람의 직사각형(Square Peg)읎에요. 50판은 닀섯을 더했얎요. 크레읎치넌곌 배철러의 2찚원 난류가 거꟞로 가는 캐슀쌀읎드와 맥윌늬엄슀의 소용돌읎(Inverse Cascade), 사포발곌 로소, 구예의 확산 앞쀄(Diffusion Front), 필늬와 마륎쌀티, 쌀읎잠와 타읎왞륎의 욎동성 상분늬(Motility Clusters), 박곌 슀넀펜의 진화 몚형(Bak-Sneppen), 늬섞의 영원한 가속(Risset Rhythm)읎에요. 51판은 닀섯을 더했얎요. 슀믞슀와 마읎얎슀, 캐플런, 굿뚌-슀튞띌우슀의 몚자 타음곌 H/T/P/F 메타타음 치환(Hat Monotile), 반도와 동료듀의 최적 속도 몚형곌 슀Ʞ알마의 원형 튾랙 싀험(Phantom Jam), 사토와 테읎튞의 법칙곌 타원곡선의 a_p(Sato-Tate), 칌띌비와 알우의 공간에서 핞슚읎 잘띌 귞늰 5ì°š 페륎마 곡선(Calabi-Yau Quintic), 에덞슀람룚너와 레처, 조몚로디얞의 지속 혞몰로지와 바윔드(Persistence Barcode)예요. 52판은 닀섯을 더했얎요. 슈람곌 셰필드가 등고선을 SLE(4) 로 볎읞 2찚원 가우슀 자유장(Gaussian Free Field), 당튀와 마슈묎닀륎, 베링거의 ꎑ탄성 힘 사슬(Force Chains), 앀슀컎의 사쀑죌와 마테읎칎, 플잠몚늬슀의 같은 통계 닎ꞈ질(Anscombe's Quartet), 드 람띌윈 수엎곌 BEST 정늬(de Bruijn Sequence), 띌읞슀의 베타 평멎 띠 제튞(Zonal Jets)예요. 53판은 엎을 더했얎요. 듀크, 가륎시아, 룚잠가 귞늰 가우슀 죌Ʞ(Gaussian Periods), 부륎가 볎읞 현수멎곌 나선멎의 휹(Catenoid-Helicoid), 디랙의 벚튞 묘Ʞ(Belt Trick), 헬늄홀잠와 러람의 ì—°êž° 고늬 개구늬뜀(Leapfrogging Vortices), 람룩슀, 슀믞슀, 슀톀, 텃의 정사각형 분할(Squared Rectangle), 포드와 풀컀슚의 최대 흐멄 최소 절닚(Max-Flow Min-Cut), 윔슀타슀 ë°°ì—Ž(Costas Array), 바띌니, 비얎든, 칞의 묎게쀑심 분할(Barycentric Subdivision), 묎윔파디아알와 크넀저의 ë„€ ꌭ짓점 정늬(Four-Vertex Theorem), 였음러의 엘띌슀티칎(Euler's Elastica)예요. +죌제 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)예요. 49판은 닀섯을 더했얎요. 뒀러의 전개 묞제와 셰퍌드의 추잡(DÃŒrer's Unfolding), 베늬와 곚드버귞가 재규격화한 섞타 합의 ꌬ부랑 곡선(Curlicue), 였음러 방정식곌 푞앵소의 폎혞드로 볞 테니슀 띌쌓 정늬(Tennis Racket), 쌐프의 사슬곌 ë„€ 색 정늬(Kempe Chains), 퇎플늬잠의 낎접 정사각형 묞제와 귞늰-로람의 직사각형(Square Peg)읎에요. 50판은 닀섯을 더했얎요. 크레읎치넌곌 배철러의 2찚원 난류가 거꟞로 가는 캐슀쌀읎드와 맥윌늬엄슀의 소용돌읎(Inverse Cascade), 사포발곌 로소, 구예의 확산 앞쀄(Diffusion Front), 필늬와 마륎쌀티, 쌀읎잠와 타읎왞륎의 욎동성 상분늬(Motility Clusters), 박곌 슀넀펜의 진화 몚형(Bak-Sneppen), 늬섞의 영원한 가속(Risset Rhythm)읎에요. 51판은 닀섯을 더했얎요. 슀믞슀와 마읎얎슀, 캐플런, 굿뚌-슀튞띌우슀의 몚자 타음곌 H/T/P/F 메타타음 치환(Hat Monotile), 반도와 동료듀의 최적 속도 몚형곌 슀Ʞ알마의 원형 튾랙 싀험(Phantom Jam), 사토와 테읎튞의 법칙곌 타원곡선의 a_p(Sato-Tate), 칌띌비와 알우의 공간에서 핞슚읎 잘띌 귞늰 5ì°š 페륎마 곡선(Calabi-Yau Quintic), 에덞슀람룚너와 레처, 조몚로디얞의 지속 혞몰로지와 바윔드(Persistence Barcode)예요. 52판은 닀섯을 더했얎요. 슈람곌 셰필드가 등고선을 SLE(4) 로 볎읞 2찚원 가우슀 자유장(Gaussian Free Field), 당튀와 마슈묎닀륎, 베링거의 ꎑ탄성 힘 사슬(Force Chains), 앀슀컎의 사쀑죌와 마테읎칎, 플잠몚늬슀의 같은 통계 닎ꞈ질(Anscombe's Quartet), 드 람띌윈 수엎곌 BEST 정늬(de Bruijn Sequence), 띌읞슀의 베타 평멎 띠 제튞(Zonal Jets)예요. 53판은 엎을 더했얎요. 듀크, 가륎시아, 룚잠가 귞늰 가우슀 죌Ʞ(Gaussian Periods), 부륎가 볎읞 현수멎곌 나선멎의 휹(Catenoid-Helicoid), 디랙의 벚튞 묘Ʞ(Belt Trick), 헬늄홀잠와 러람의 ì—°êž° 고늬 개구늬뜀(Leapfrogging Vortices), 람룩슀, 슀믞슀, 슀톀, 텃의 정사각형 분할(Squared Rectangle), 포드와 풀컀슚의 최대 흐멄 최소 절닚(Max-Flow Min-Cut), 윔슀타슀 ë°°ì—Ž(Costas Array), 바띌니, 비얎든, 칞의 묎게쀑심 분할(Barycentric Subdivision), 묎윔파디아알와 크넀저의 ë„€ ꌭ짓점 정늬(Four-Vertex Theorem), 였음러의 엘띌슀티칎(Euler's Elastica)예요. 55판은 엎을 더했얎요. 밀러와 셰필드의 허수 Ʞ하(Imaginary Geometry), 티소의 지시 타원곌 메륎칎토륎 투영(Tissot's Indicatrix), 믌윔프슀킀의 묌음표 핚수(Question Mark Function), 버로슀와 휠러의 변환(Burrows-Wheeler), 뷔퐁의 바늘곌 바륎비에의 국수(Buffon's Noodle), 부자의 늬듬 타음링 캐넌(Vuza Canon), 하읞곌 낎시의 헥슀와 게음의 헥슀 정늬(Hex Theorem), 8자 고늬의 범플복곌 자유군의 쌀음늬 귞래프(Universal Cover), 였람친니윔프와 저도비치, 투생곌 윌첵의 확산 제한 소멞(Toussaint-Wilczek), 헬늄홀잠와 쌈빈의 와류 시튞와 크띌슀니의 와동 덩읎(Vortex Sheet)예요. -목록 밖에서 닀음에 핮 볌 것읎에요. 믌윔프슀킀의 묌음표 핚수(1904)는 페늬 나묎륌 읎진 나묎로 펮는 계닚읎에요. 수론 갈래는 32판의 Gilbreath Table 까지 넷읎에요. Ꞟ람레슀 표는 소수 말고 닀륞 수엎로도 섞욞 수 있얎요. 였듀늬윔(1993)에 따륎멎 2 닀음에 간격읎 작고 고륎게 흩얎진 홀수가 였는 수엎에서는 1 의 엎읎 서고, 간격읎 크멎 깚젞요. 간격의 크Ʞ륌 녞람로 걞멎 1 의 엎읎 묎너지는 자늬가 볎여요. 핎뚞슬늬 곌정의 점 대신 격자 칞마닀 지수 분포 묎게륌 두멎 마지막 통곌 슀믞Ʞ(로슀튞, 1981)가 되고, 극한 몚양읎 쌍곡선에서 (√x+√y)² 의 겜계로 바뀌얎요. +목록 밖에서 닀음에 핮 볌 것읎에요. 수론 갈래는 32판의 Gilbreath Table 까지 넷읎에요. Ꞟ람레슀 표는 소수 말고 닀륞 수엎로도 섞욞 수 있얎요. 였듀늬윔(1993)에 따륎멎 2 닀음에 간격읎 작고 고륎게 흩얎진 홀수가 였는 수엎에서는 1 의 엎읎 서고, 간격읎 크멎 깚젞요. 간격의 크Ʞ륌 녞람로 걞멎 1 의 엎읎 묎너지는 자늬가 볎여요. 핎뚞슬늬 곌정의 점 대신 격자 칞마닀 지수 분포 묎게륌 두멎 마지막 통곌 슀믞Ʞ(로슀튞, 1981)가 되고, 극한 몚양읎 쌍곡선에서 (√x+√y)² 의 겜계로 바뀌얎요. 28판에서 볞 것읎에요. 수학 대상은 셈읎 정확핎서, 끝없는 확대나 흐늄읎 거의 ë‹€ 작은 틀 하나로 돌아요. Ford Circles 는 찜을 읎웃한 두 수렎 분수의 틀로 잡아 1800쎈 뒀에도 수가 작고, Hyperbolic Band 는 59 LED 마닀, Rauzy Fractal 은 섞 닚계마닀, Gosset Polytope 는 36쎈마닀 화멎읎 정확히 돌아와요. 반대로 묎작위가 죌읞공읞 셋은 2~8쎈 찜읎 얎느 때에 걞늬느냐가 값을 정했얎요. Stadium Billiard 는 2~5쎈만 재멎 움직임 0.63, Ʞ첎만 재멎 3.1 읎었얎요. 찚례대로 자띌는 Look and Say 는 빈 쪜에서 엎늬멎 2~8쎈의 `thirds` 가 24/62/64 였고, 처음 슀묎 쀄을 빠륎게 쳐 낎자 48/68/69 가 됐얎요. 수학을 귞대로 귞늰 화멎은 귞대로 두멎 너묎 고요하거나 너묎 얎두웠얎요. Ford Circles 의 고륞 원판은 채움 0.971 의 회색 판읎었고, Gosset Polytope 의 몚서늬 귞묌은 LED 하나에 0.012 씩만 쌓였얎요. 빛을 테두늬나 고늬처럌 수학읎 정한 자늬에 몚윌멎 읜혔얎요. @@ -3005,9 +3070,11 @@ Flock, Harmonograph, Big Type 은 처음 화멎읎 빚강읎에요. 53판에서 볞 것읎에요. 정늬가 수륌 못 박는 대상은 읎번에도 셈윌로 지쌜뎀얎요. Squared Rectangle 은 86,400 프레임 동안 타음 넓읎의 합읎 큰 직사각형곌 3.7e-15 안에서 같았고, Max-Flow Min-Cut 은 2,700판에서 흐멄 값곌 절닚 용량읎 한 번도 얎Ꞌ나지 않았얎요. Costas Array 는 ë°°ì—Ž 273개의 변위 241,856개가 겹치지 않았고, Four-Vertex Theorem 은 54,000 프레임 동안 ꌭ짓점읎 늘 넷 읎상의 짝수였얎요. 정늬가 귞늌에서도 서렀멎 귞늬는 셈읎 정늬의 ꌎ을 따띌알 했얎요. Belt Trick 은 늬볞 점의 맀개변수륌 늬볞을 따띌 잰 Ꞟ읎 대신 정육멎첎 쀑심에서 잰 거늬로 읜얎서, 구멎마닀 한 덩읎로 돌아 늬볞읎 서로 뚫고 지나갈 수 없얎요. Gaussian Periods 는 점을 직선윌로 옮Ʞ멎 닀시 서는 동안 가욎데로 몰렀서, 닚위귌마닀 짧은 혞륌 따띌 돌게 했얎요. 수학읎 정한 비윚은 팚널에 귞대로 듀얎가지 않았얎요. 2:1 볌록 곡선의 옆 곡률 쀑심은 팹널 밖 90 LED 에 있얎서 Four-Vertex Theorem 은 1.4:1 로 섞웠고, 현수멎은 높읎가 너비의 0.66 배륌 넘지 못핎 Catenoid-Helicoid 는 시선을 낎렞얎요. 엘띌슀티칎의 묌방욞은 높읎 31 에 너비 47 읎에요. 대칭읎 정늬의 조걎읎Ʞ도 했얎요. 평멎 소용돌읎 넷은 너비 비 0.38 아래에서 슀슀로 대칭을 깚서, 둥귌 고늬륌 귞늬는 Leapfrogging Vortices 는 거욞을 걞었얎요. 극한을 말하는 정늬는 읎번에도 LED 한 칞에서 멈췄얎요. Barycentric Subdivision 은 3 LED 아래로 가는 조각을 더 자륎지 않아서, 깊읎 4 와 5 가 3 곌 같아 녞람륌 2 에서 3 사읎로 쀄였얎요. +55판에서 볞 것읎에요. 정늬가 못 박는 것은 읎번에도 셈윌로 지쌜뎀얎요. Imaginary Geometry 는 녾뾌 여섯 조합에서 흐늄선 13,338개가 한 번도 걎너지 않았고, Hex Theorem 은 24,000판에서 늘 한 색만 읎게얎요. Buffon's Noodle 은 Ꞟ읎 셋곌 휹 닀섯에 200,000번씩 떚얎뜚렀 평균읎 2L/(πd) 와 0.005 안에서 맞았고, Universal Cover 는 100걞음 ë’€ 낱말읎 평균 50.2 Ꞁ자로 쌀슀텐의 탈출 속도 1/2 에 맞았얎요. Burrows-Wheeler 는 구간 교환 부혞 250개의 마지막 엎읎 몚두 Ꞁ자마닀 한 덩얎늬였얎요. 수학읎 정한 비윚읎 팚널에 ꌭ 맞Ʞ도 했얎요. 메륎칎토륎륌 위도 ±85.0511도에서 자륎멎 겜도 180도와 정확히 1:2 띌서, Tissot's Indicatrix 는 웹 메륎칎토륎가 자륎는 자늬에서 잘랐얎요. 연속읎나 극한을 말하는 수학은 읎번에도 LED 한 칞에서 멈췄얎요. Vortex Sheet 는 4쎈가 지나멎 윔얎의 겹읎 LED 볎닀 쎘쎘핎젞 연볎띌 원판윌로 ì°šê³ , Question Mark Function 은 ?(0.1) 읎 2^-9 띌 계닚읎 8번 ì—Ž 얞저늬에서알 바닥을 떠나요. κ 가 4 쪜윌로 가멎 흐늄선읎 LED 하나에 몇 띌디안씩 꺟여서, Imaginary Geometry 는 장을 흐늬는 폭을 4.1 LED 까지 넓혀알 했얎요. 정늬가 한 쀄로 서는 귞늌은 팚널에서도 한 쀄로 읜혔얎요. Vuza Canon 은 얎느 쀄에도 음읎 ꌭ 하나띌서 타음링읎 1 LED 쀄 하나로 볎읎고, Buffon's Noodle 은 ë‹€ 휜 고늬가 맀번 선 두 개륌 걎너 였찚 막대가 π 선에 납작하게 누워요. + 목록 밖에서 31판 뒀에 핮 볌 것읎에요. 페륎믞-파슀타-욞람-칭구의 되돌아옎(1955)은 KdV Solitons 와 같은 뿌늬띌 같은 판에 두지 않아요. -목록 밖에서 33판 뒀에 핮 볌 것읎에요. 뷔퐁의 바늘(1777)은 Bertrand Paradox 와 같은 ꌎ로, ꞈ을 넘는 바늘의 몫읎 2/π 로 몚읎는 막대와 핚께 섞욞 수 있얎요. 토칎륎슀킀의 ë°©(1995)은 거욞로 된 닀각형 방에서 한 점의 빛읎 닀륞 한 점에만 닿지 못하는 방읎띌 Penrose Room 의 ꎑ선 추적을 귞대로 썚요. 프늬드뚌곌 슀윔띌(1987)는 둥귌 원 셋윌로는 볎로메였 고늬륌 만듀 수 없닀는 것을 볎였는데, 원을 밀얎 넣을 때 얎디서 부딪히는지가 귞늌읎 돌요. 정렬 넀튞워크의 시간 t 의 순엎을 점윌로 찍윌멎 대각선에서 원판을 지나 반대 대각선윌로 도는 점 구늄읎 돌요. ê·ž 원판읎 2007년 녌묞읎 낎닀볞 아륎킀메데슀 법칙읎에요. 간격읎 2k 읞 소수 쌍의 수는 Goldbach Comet 곌 같은 FFT 로 섞고, k 마닀 하디-늬틀우드 읞수로 갈늬는 쀄Ʞ 귞늌읎 돌요. +목록 밖에서 33판 뒀에 핮 볌 것읎에요. 토칎륎슀킀의 ë°©(1995)은 거욞로 된 닀각형 방에서 한 점의 빛읎 닀륞 한 점에만 닿지 못하는 방읎띌 Penrose Room 의 ꎑ선 추적을 귞대로 썚요. 프늬드뚌곌 슀윔띌(1987)는 둥귌 원 셋윌로는 볎로메였 고늬륌 만듀 수 없닀는 것을 볎였는데, 원을 밀얎 넣을 때 얎디서 부딪히는지가 귞늌읎 돌요. 정렬 넀튞워크의 시간 t 의 순엎을 점윌로 찍윌멎 대각선에서 원판을 지나 반대 대각선윌로 도는 점 구늄읎 돌요. ê·ž 원판읎 2007년 녌묞읎 낎닀볞 아륎킀메데슀 법칙읎에요. 간격읎 2k 읞 소수 쌍의 수는 Goldbach Comet 곌 같은 FFT 로 섞고, k 마닀 하디-늬틀우드 읞수로 갈늬는 쀄Ʞ 귞늌읎 돌요. 목록 밖에서 34판 뒀에 핮 볌 것읎에요. 타메슀 묞제(플터 타메슀, 1930)는 구 위에 같은 원 N 개륌 겹치지 않게 가장 크게 놓는 묞제띌, 톰슚 묞제와 달늬 가장 가까욎 거늬만 볎고 얎디에도 닿지 않고 덜귞럭거늬는 원읎 생겚요. 로드늬게슀-읎투륎베와 늬날도의 최적 하천망(1992)은 같은 격자에서 묌Ꞟ마닀 Q^0.5 륌 더한 에너지가 가장 작은 귞묌을 닎ꞈ질로 찟아서, 샀읎데거의 묎작위 귞묌볎닀 덜 구불거늬는 나묎가 돌요. 로믹의 양손잡읎 소파(댄 로믹, 2018)는 왌쪜 몚퉁읎와 였륞쪜 몚퉁읎륌 ë‹€ 돌 수 있얎서, 지귞재귞 복도륌 128쀄 쌓고 소파 하나가 끝까지 였륎게 할 수 있얎요. 데읎나 슀윧(1958)읎 처음 컎퓚터로 푌 8×8 판의 가욎데 2×2 구멍 묞제는 펜토믞녞 덮Ʞ가 65가지읎고, 칞 8 LED 멎 판읎 64×64 로 팹널 너비에 ꌭ 맞아요. 표멎장력읎 있는 묌결은 원천읎 최소 위상속도 23 cm/s 아래로 느렀지멎 항적읎 사띌젞요(띌파엘곌 드 ì  , 1996). Kelvin Wake 의 FFT 표에 분산식 하나만 바꟞멎 ê·ž 묞턱을 녞람에 걞 수 있얎요. @@ -3017,7 +3084,7 @@ Flock, Harmonograph, Big Type 은 처음 화멎읎 빚강읎에요. 목록 밖에서 37판 뒀에 핮 볌 것읎에요. 당당한 정사각형은 원판곌 달늬 테튞띌틱 상을 지나 녹아서(볎읎치에혞프슀킀와 프렌쌈, 2004), Hexatic Phase 의 색을 격자 회전 90도 한 바퀎로 바꟞멎 같은 귞늌윌로 견쀄 수 있얎요. 결정마닀 자띌는 빠륎Ʞ륌 닀륎게 하멎 Johnson-Mehl 의 겜계가 아폎로니우슀 원읎 되는 곱셈 가쀑 볎로녞읎가 돌요. -목록 밖에서 38판 뒀에 핮 볌 것읎에요. 였람늬-앙드레 몚형(섞륎죌 였람늬, êžž 앙드레, 1980)은 Anderson Localization 의 묎작위 에너지륌 묎늬수 죌Ʞ의 윔사읞윌로 바Ꟍ 사슬읎띌, 섞Ʞ 2 에서 몚든 상태가 한꺌번에 퍌짐에서 갇힘윌로 넘얎가요. 같은 시공간 귞늌에 ê·ž 묞턱을 녞람로 걞 수 있얎요. 수바윔프와 드믞튞띌시녞비치(2013)가 찟은 섞 몞의 죌Ʞ 궀도 엎섞 갈래는 나비와 나방, 요구륎튞 같은 읎늄읎 붙은 ꌎ읎띌 Figure-Eight Orbit 의 적분을 귞대로 썚요. 게음(1979)읎 볎읞 헥슀 정늬는 칞읎 ë‹€ ì°¬ 헥슀 판에는 ꌭ 한쪜읎 읎Ʞ는 Ꞟ읎 있닀는 것윌로, 슈페륎너 볎조정늬처럌 람띌우얎륎의 고정점 정늬와 같은 힘읎에요. 마늄몚 판을 섞워 두 색읎 찚였륎며 읎ꞎ Ꞟ읎 쌜지는 귞늌읎 돌요. 쉬잠와 튞늌퍌의 윔끌늬 걞음(2004)은 지난 걞음 가욎데 하나륌 곚띌 따륎는 êž°ì–µ 걞음읎띌, 따륎는 확률 3/4 에서 퍌짐읎 바뀌얎요. Pólya Urn 처럌 64엎을 나란히 둘 수 있얎요. +목록 밖에서 38판 뒀에 핮 볌 것읎에요. 였람늬-앙드레 몚형(섞륎죌 였람늬, êžž 앙드레, 1980)은 Anderson Localization 의 묎작위 에너지륌 묎늬수 죌Ʞ의 윔사읞윌로 바Ꟍ 사슬읎띌, 섞Ʞ 2 에서 몚든 상태가 한꺌번에 퍌짐에서 갇힘윌로 넘얎가요. 같은 시공간 귞늌에 ê·ž 묞턱을 녞람로 걞 수 있얎요. 수바윔프와 드믞튞띌시녞비치(2013)가 찟은 섞 몞의 죌Ʞ 궀도 엎섞 갈래는 나비와 나방, 요구륎튞 같은 읎늄읎 붙은 ꌎ읎띌 Figure-Eight Orbit 의 적분을 귞대로 썚요. 쉬잠와 튞늌퍌의 윔끌늬 걞음(2004)은 지난 걞음 가욎데 하나륌 곚띌 따륎는 êž°ì–µ 걞음읎띌, 따륎는 확률 3/4 에서 퍌짐읎 바뀌얎요. Pólya Urn 처럌 64엎을 나란히 둘 수 있얎요. 목록 밖에서 39판 뒀에 핮 볌 것읎에요. 평멎 람띌욎 겜로의 바깥 겜계는 찚원읎 4/3 읎띌는 만덞람로튞의 추잡(1982)을 례러, 슈람, 베륎너(2001)가 슝명했얎요. 지나간 자늬가 한 번만 쌜지는 겜로 위에 바깥 겜계만 따로 빛나게 할 수 있얎요. @@ -3043,10 +3110,12 @@ Flock, Harmonograph, Big Type 은 처음 화멎읎 빚강읎에요. 목록 밖에서 51판 뒀에 핮 볌 것읎에요. 슀펙터(슀믞슀, 마읎얎슀, 캐플런, 굿뚌-슀튞띌우슀, 2023)는 ê±°ìšž 없읎도 되풀읎하지 않는 타음읎띌, ê·ž 치환윌로 Hat Monotile 의 덩읎 묌결을 돌늬멎 죌황 ê±°ìšž 몚자가 없는 짝읎 돌요. 종수 2 곡선의 a_p 륌 Sato-Tate 의 빗방욞로 떚얎뜚늬멎 반원 대신 USp(4) 대각합의 돔읎 쌓여서, 같은 법칙읎 닀륞 몚양을 채우는 것읎 볎여요. Calabi-Yau Quintic 의 조각 귞늬Ʞ륌 찚수 n 을 녞람에 걎 페륎마 곡선 z1^n + z2^n = 1 로 넓히멎 조각 n² 개와 종수 (n−1)(n−2)/2 가 핚께 바뀌얎요. Persistence Barcode 의 알파 복합첎륌 구 위의 점읎나 토러슀 닚멎윌로 옮Ʞ멎 H2 막대까지 서요. 두 ì°šì„  고늬에서 찚선을 바꟞는 찚는 Phantom Jam 의 정첎륌 옆 찚선윌로 옮겚서, 시공간 귞늌 둘을 나란히 섞욞 수 있얎요. -목록 밖에서 52판 뒀에 핮 볌 것읎에요. 같은 가우슀 자유장 위에 각도마닀 흐늄선을 귞윌멎 한 점에서 부채처럌 갈띌지는 SLE(κ) 곡선듀읎 서요. 제읎슚 밀러와 슀윧 셰필드의 허수 Ʞ하(2016)예요. ê· ì—Ž 끝의 응력장(조지 얎윈, 1957)을 Force Chains 의 백색ꎑ 등색선 표로 칠하멎, 균엎읎 자띌는 동안 ê·ž 끝에 나비 몚양 묎늬가 따띌가요. 마테읎칎와 플잠몚늬슀는 같은 녌묞에서 닀섯 수 요앜읎 같은 상자 귞늌 옆에서 점듀읎 한 덩얎늬에서 두 덩얎늬로 갈띌졌닀 몚읎는 것도 볎였얎요. 드 람띌윈 토러슀는 4×4 판 위에 몚든 2×2 묎늬가 ꌭ 한 번씩 나였는 2찚원 짝읎띌, 팚널을 ê·ž 판윌로 덮고 찜을 상하좌우로 굎렀 볌 만핎요. 베타 평멎에서 마찰을 더 낮추고 잠재 소용돌읎도 q + βy 의 등고선윌로 칠하멎, 동쪜 제튞가 섞임을 막는 벜읎 되는 PV 계닚(데읎비드 드늬첌, 마읎큎 맀킚타읎얎, 2008)읎 볎여요. +목록 밖에서 52판 뒀에 핮 볌 것읎에요. ê· ì—Ž 끝의 응력장(조지 얎윈, 1957)을 Force Chains 의 백색ꎑ 등색선 표로 칠하멎, 균엎읎 자띌는 동안 ê·ž 끝에 나비 몚양 묎늬가 따띌가요. 마테읎칎와 플잠몚늬슀는 같은 녌묞에서 닀섯 수 요앜읎 같은 상자 귞늌 옆에서 점듀읎 한 덩얎늬에서 두 덩얎늬로 갈띌졌닀 몚읎는 것도 볎였얎요. 드 람띌윈 토러슀는 4×4 판 위에 몚든 2×2 묎늬가 ꌭ 한 번씩 나였는 2찚원 짝읎띌, 팚널을 ê·ž 판윌로 덮고 찜을 상하좌우로 굎렀 볌 만핎요. 베타 평멎에서 마찰을 더 낮추고 잠재 소용돌읎도 q + βy 의 등고선윌로 칠하멎, 동쪜 제튞가 섞임을 막는 벜읎 되는 PV 계닚(데읎비드 드늬첌, 마읎큎 맀킚타읎얎, 2008)읎 볎여요. 목록 밖에서 53판 뒀에 핮 볌 것읎에요. 극소곡멎의 연ꎀ족은 엔넀퍌 멎곌 셰륎크 멎에도 있얎서, Catenoid-Helicoid 의 첎컀와 법선 셈윌로 닀륞 극소곡멎의 휚을 귞늎 수 있얎요. 벚튞 묘Ʞ의 H(1, t) 륌 3찚원 구멎의 입첎 사영윌로 옆에 찍윌멎 360° 에 −1 에 닿았닀 720° 에 1 로 돌아였는 점읎 두 ê²¹ 덮개륌 바로 볎여요. Leapfrogging Vortices 의 거욞을 풀멎 너비 비 0.382 아래에서 개구늬뜀읎 슀슀로 대칭을 깚고 흩얎지는 애치슚(2000)의 불안정을 녾뾌 하나로 볎음 수 있얎요. 정사각형 분할은 쌍대 귞묌을 돌늬멎 90도 돌아간 분할읎 나와서, 두 귞묌을 반씩 섞얎 직사각형읎 옆윌로 눕는 장멎읎 돌요. 최소 절닚은 평멎 귞묌에서 쌍대 귞래프의 최닚 겜로띌, 닀익슀튞띌 묌결읎 왌쪜 벜에서 였륞쪜 벜윌로 번지며 웅덩읎 테두늬와 같은 선을 Ꞌ는 것을 볎음 수 있얎요. 곚롬 자는 윔슀타슀 배엎의 성질을 1찚원에 둔 것읎고, 찚수 32 와 33 은 윔슀타슀 배엎읎 하나도 알렀지지 않은 가장 작은 찚수예요. 묎게쀑심 분할에서 둥귌 자식을 고륎는 확률의 묞턱은 2/9 와 1/3 사읎에 있얎서, ê·ž 사읎륌 잘게 재멎 납작핎지는 걷Ʞ와 둥Ꞁ게 뚞묎는 ê±·êž°ê°€ 갈늬는 자늬륌 찟을 수 있얎요. 분늬선 바깥에서 넘얎 도는 진자는 곡률 부혞가 바뀌지 않는 비굎곡 엘띌슀티칎띌서, 녞람로 분늬선을 걎너멎 였음러의 1744년 귞늌의 아홉 갈래가 한 판에 읎얎젞요. 공간 곡선의 ë„€ ꌭ짓점 정늬(섞디흐, 1992)도 있얎요. +목록 밖에서 55판 뒀에 핮 볌 것읎에요. 같은 각도의 흐늄선을 판 여러 점에서 띄우멎 만나는 대로 합쳐젞 나묎가 되고, ê·ž 나묎륌 따띌 도는 곡선읎 판을 ë‹€ 메우는 SLE(16/κ) 예요. 밀러와 셰필드의 『Imaginary geometry IV』(2017)에 있얎요. 같은 원듀을 원통 대신 원뿔곌 방위 투영에 얹윌멎 왜곡읎 한 원읎나 한 점 둘레로 몚여서, 람베륎튞가 1772년에 낾 정각 원뿔 투영곌 방위 등적 투영을 Tissot's Indicatrix 의 K4 로 였가게 핮 볌 만핎요. 윘웚읎의 상자 핚수(1976)는 묌음표 핚수륌 뒀집은 ꌎ읎띌 같은 계닚을 눕혀 싀수 전첎로 늘렀 볌 수 있고, 슈테륞의 읎원자 수엎(몚늬잠 슈테륞, 1858)도 128쀄을 채우는 산맥읎 될 만핎요. 천곌 폭슀, 늰던의 며던 분핎(1958)는 Ꞁ을 사전순윌로 컀지지 않는 며던 닚얎듀로 자륎는 것읎띌, 조각마닀 색읎 닀륞 띠로 ê·žë € 볌 만핎요. 크로프턎 공식(몚걎 크로프턎, 1868)은 Buffon's Noodle 곌 같은 셈윌로, 묎작위 직선읎 곡선을 걎너는 수로 곡선의 Ꞟ읎륌 재요. 늬듬 타음링의 닀륞 끝은 푞Ꞁ레드의 슀펙튞럌 추잡(벀튞 푞Ꞁ레드, 1974)읎고, 타였(2004)가 5찚원에서 반례륌 찟았얎요. 삌각 격자 슀믞Ʞ에서 사각형을 가로지륌 확률은 칎디의 공식(졎 칎디, 1992)을 따륎고 슀믞륎녞프(2001)가 슝명핎서, Hex Theorem 의 판 몚양을 바꟞며 걎넌 횟수륌 섞멎 공식의 곡선에 닀가가요. 자유군의 부분군은 8자 고늬의 유한 플복읎고, 슀톚링슀(1983)의 ì ‘êž°ê°€ 생성 낱말의 ꜃잎 고늬에서 같은 Ꞁ자의 변을 하나씩 ì ‘ì–Ž ê·ž 플복을 만듀얎요. A + B → 0 에 입자륌 끊임없읎 고륎게 넣얎도 정상 상태에서 영역읎 갈띌젞서(늰덎버귞, 웚슀튞, 윔펠만, 1988), 영역읎 팹널 크Ʞ까지 퍌진 채 흔듀늬는 판을 섞욞 수 있얎요. 한 죌Ʞ에 두 파장을 넣고 반 파장 섭동을 더하멎 읎웃 나선읎 서로 돌닀 하나로 합치는 짝짓Ʞ(위넌튞와 람띌우얞드, 1974)가 나와요. + ### 화장품 팝업 죌제 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 마닀 한 번만 바뀌는 셈윌로 귞늎 만핎요. From 95300cfc99f49dbd7f7978d2e19acb0969bb0894 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 26 Oct 2026 05:12:19 +0000 Subject: [PATCH 10/10] =?UTF-8?q?incoming:=2055=ED=8C=90=20=EC=97=B4=20?= =?UTF-8?q?=EA=B0=9C=EB=A5=BC=20=EB=84=A3=EC=96=B4=20index.html=20?= =?UTF-8?q?=EC=9D=84=20=EB=8B=A4=EC=8B=9C=20=EC=A7=93=EB=8A=94=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 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 bfb3c504b..ff0401f43 100644 --- a/incoming/pattern-study/index.html +++ b/incoming/pattern-study/index.html @@ -210,8 +210,8 @@

팹턮 연구

- - + +