From dc025c212ffceb09ff2b3b3893be5fbbf32cea21 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 31 Oct 2026 19:21:01 +0000 Subject: [PATCH 1/9] =?UTF-8?q?=EC=8B=9C=EC=9E=91=20=E2=80=94=20=ED=8C=A8?= =?UTF-8?q?=ED=84=B4=20=EC=97=B0=EA=B5=AC=2081=ED=8C=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit From 253b6188c644c5e1b1fd82997e70526a66aa6c6e Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 31 Oct 2026 20:07:55 +0000 Subject: [PATCH 2/9] =?UTF-8?q?=ED=8C=A8=ED=84=B4=20=EC=97=B0=EA=B5=AC=208?= =?UTF-8?q?1=ED=8C=90:=20Modular=20Hyperbola,=20Permutation=20Poem?= 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_01Bc4Jkpfqox342J4FE4ZD1C --- .../patterns/modular-hyperbola.js | 129 +++++++++ .../patterns/permutation-poem.js | 250 ++++++++++++++++++ 2 files changed, 379 insertions(+) create mode 100644 incoming/pattern-study/patterns/modular-hyperbola.js create mode 100644 incoming/pattern-study/patterns/permutation-poem.js diff --git a/incoming/pattern-study/patterns/modular-hyperbola.js b/incoming/pattern-study/patterns/modular-hyperbola.js new file mode 100644 index 00000000..5dd19457 --- /dev/null +++ b/incoming/pattern-study/patterns/modular-hyperbola.js @@ -0,0 +1,129 @@ +// ===== Patternflow pattern ===== +// Title: Modular Hyperbola +// Family: number +// Idea: The multiplication table of the integers mod a prime p fills the panel, column x + 1 times row y, and every LED stands on one sawtooth of the residues, white where the product is a running residue c and falling through a cubehelix ramp to black just before it comes round to c again, so the hard white edges trace the modular hyperbola xy = c (mod p): at p = 127, 32 true hyperbolas nested round the top left corner and their mirror round the bottom left, with checks along the middle row and the last column where x and y are near p/2, and, with the tooth narrowed to two residues, a scatter of points that lands a fair share in every patch of the table. +// Author: patternflow-lab +// Date: 2026-10-31 +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Lineage: the modular hyperbola xy = c (mod p), whose points in a box are counted with Kloosterman sums (Hendrik Kloosterman, 1926) and Weil's bound on them (Andre Weil, 1948); cubehelix colour ramp (Dave Green, 2011), pattern round 81 +// Round: 81 +// =============================== +// @matrix 64x128 +// @knobs Hue=0..1, Speed=0.2..3, Prime=-3..4, Band=0..1 +// +// Knob 1: Hue. Where the cubehelix ramp starts its turn round the colour wheel. At 0 a tooth climbs from black through violet, blue, teal and green to pale pink and white; at 0.25 through brown, mauve and sky blue; at 0.5 through moss, rose and lilac; at 0.75 through navy, green and salmon. The top of every tooth is white whatever the hue. +// Knob 2: Speed. c climbs through all p residues in 12 / Speed seconds whatever the prime: 6 s at the opening 2, 60 s at 0.2 and 4 s at 3. At p = 127 and Speed 2 that is 21 residues a second. +// Knob 3: Prime. -3 is 13, -2 is 31, -1 is 61, the opening 0 is 127, 1 is 251, 2 is 509, 3 is 1021 and 4 is 2039, each the largest prime under a power of two. At 127 the 128 rows hold every residue once with the zero row at the top and again at the bottom, and the lower half is the upper half upside down. At 13 the table repeats as a 13 by 13 tile, and at 2039 the product stays under p over 59% of the panel, so the top left is plain hyperbolas. +// Knob 4: Band. How much of each tooth is lit: p - Band^2 (p - 2) residues of the p. The whole tooth at 0 and at the opening 0.06, three quarters at 0.5, and at 1 only the residues c and c + 1, so what is left is the hyperbola itself; at p = 127 that is one LED in every column for each of the two. + +// The integers mod a prime p, multiplied. LED (x, y) holds the residue +// (x + 1) y mod p: columns count 1 to 64 and rows 0 to 127, so the top row +// is the row of zeros. A running residue c, a real number, lights every LED +// by how far its residue r has to go back to reach c: +// +// t = 1 - ((r - c) mod p) / p +// +// t is 1 on the residue just above c and falls to 0 on the one just below, +// so wherever the residue steps from c - 1 to c the picture steps from black +// to white. Those steps are the modular hyperbola xy = c (mod p). +// +// Each tooth is the band between two hyperbolas xy = c + kp and +// xy = c + (k + 1)p. Row y is a sawtooth in x of y/p cycles an LED, and +// column x one of (x + 1)/p cycles down, so at p = 127 the whole upper +// half stays under half a cycle an LED both ways and its 32 teeth are true +// hyperbolas, nested round the top left corner. Near y = p/2 a row is +// almost two LEDs a cycle and reads as a check; near p/3 every third LED +// lines up; and the rows round each fraction a/b with a small b repeat +// every b LEDs, the Farey fractions in order down the panel. The table is the same read +// across, so at p = 127 the last column, x + 1 = 64 = (p + 1) / 2, is a +// check too: 64 is the inverse of 2 mod 127, and the column holds y/2. +// +// Row p - y holds -xy. At p = 127 row 127 - y is row y with the residue +// negated, so the lower half is the upper half upside down, with the ramp +// run backwards and c read as -c. +// +// c climbs through the residues, a full turn in 12 / Speed seconds. Near +// the corner the hyperbolas spread out smoothly; elsewhere every whole step +// of c moves the white set to a new scatter of points. Counting the points +// of xy = c in a box of X columns and Y rows comes down to Kloosterman sums +// (Hendrik Kloosterman, 1926), and Weil's bound on them (Andre Weil, 1948), +// no bigger than 2 sqrt(p), keeps every box within a few sqrt(p) log^2 p of +// its fair share XY / p. Band narrows the lit part of the tooth until only +// that scatter is left. +// +// The ramp is Dave Green's cubehelix (2011): brightness rises from black to +// white while the colour turns round a helix about the grey line, so all +// three channels change along the tooth. A hue wheel scaled by brightness +// was measured too: every LED then has one channel at 0, deflate packs the +// frame in 1.91 bytes a pixel instead of 2.39, and the colour runs to 0.56. + +const W = 64, H = 128; +const { floor, round, cos, sin, pow, PI } = Math; +const cl = (v, a, b) => (v < a ? a : v > b ? b : v); + +// The largest prime under 2^k, for k = 4 to 11. +const PRIMES = [13, 31, 61, 127, 251, 509, 1021, 2039]; +// Cubehelix: turns of the helix over the ramp, its width, and a gamma that +// keeps the dark third of the tooth short. +const ROT = 1.2, AMP = 1.0, GAMMA = 1.25; + +function helix(l, s, o) { + l = pow(l, GAMMA); + const phi = 2 * PI * (s / 3 - ROT * l), a = (AMP * l * (1 - l)) / 2, c = cos(phi), sn = sin(phi); + o[0] = l + a * (-0.14861 * c + 1.78277 * sn); + o[1] = l + a * (-0.29227 * c - 0.90649 * sn); + o[2] = l + a * (1.97294 * c); + return o; +} + +export function setup(params) { + params.s = { + c: 0, p: 127, hue: 0, speed: 2, band: 0.06, + lut: new Uint8Array(256 * 3), lh: -1, o: [0, 0, 0], + m: new Int32Array(W * H), mp: -1, + }; +} + +export function update(dt, input, params) { + if (!params.s) setup(params); + const S = params.s, k = input.knobValues || [0, 2, 0, 0.06], d = cl(dt || 0, 0, 0.1); + S.hue = k[0] || 0; + S.speed = cl(k[1] == null ? 2 : k[1], 0.2, 3); + S.p = PRIMES[cl(round(k[2] || 0), -3, 4) + 3]; + S.band = cl(k[3] == null ? 0.06 : k[3], 0, 1); + S.c += (d * S.speed * S.p) / 12; + S.c -= floor(S.c / S.p) * S.p; +} + +// The 256 steps of the tooth's ramp, rebuilt when Hue turns. The 0.37 +// keeps the opening hue off a red start. +function ramp(S) { + if (S.lh === S.hue) return; + S.lh = S.hue; + for (let i = 0; i < 256; i++) { + helix(i / 255, 3 * (S.hue + 0.37), S.o); + for (let j = 0; j < 3; j++) S.lut[i * 3 + j] = round(cl(S.o[j], 0, 1) * 255); + } +} + +export function draw(display, params) { + const S = params.s; + if (!S) return; + ramp(S); + const p = S.p, M = S.m, L = S.lut; + // The table itself changes only with the prime. + if (S.mp !== p) { + S.mp = p; + for (let y = 0; y < H; y++) for (let x = 0; x < W; x++) M[y * W + x] = ((x + 1) * y) % p; + } + // The lit part of the tooth, in residues: p at Band 0, 2 at Band 1. + const fp = (1 - S.band * S.band * (1 - 2 / p)) * p, c = S.c; + for (let y = 0; y < H; y++) + for (let x = 0; x < W; x++) { + let r = M[y * W + x] - c; + if (r < 0) r += p; + const t = r < fp ? 1 - r / fp : 0; + const i = round(t * 255) * 3; + display.setPixel(x, y, L[i], L[i + 1], L[i + 2]); + } +} diff --git a/incoming/pattern-study/patterns/permutation-poem.js b/incoming/pattern-study/patterns/permutation-poem.js new file mode 100644 index 00000000..b69cb27f --- /dev/null +++ b/incoming/pattern-study/patterns/permutation-poem.js @@ -0,0 +1,250 @@ +// ===== Patternflow pattern ===== +// Title: Permutation Poem +// Family: type +// Idea: A short phrase is written out in every order of its words, one line under the next in 3 by 5 capitals with each word in its own colour, so the 21 lines on the panel weave into a block of the same few words; each new line starts as a copy of the one above, the words that move slide into their new places in white, and the page steps up a line, and in the Steinhaus-Johnson-Trotter order that move is always two neighbouring words passing each other, while in I AM THAT I AM a quarter of those passes trade an I for an I or an AM for an AM and leave the line exactly as it was. +// Author: patternflow-lab +// Date: 2026-10-31 +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Lineage: Brion Gysin's permutation poems (I AM THAT I AM, 1960); Steinhaus-Johnson-Trotter order (Hale Trotter, 1962; Selmer Johnson, 1963); Heap's algorithm (B. R. Heap, 1963); next permutation in lexicographic order (Narayana Pandita, 14th century); 3 by 5 capitals drawn for this pattern, pattern round 81 +// Round: 81 +// =============================== +// @matrix 64x128 +// @knobs Hue=0..1, Speed=0.2..3, Phrase=0..3, Order=0..2 +// +// Knob 1: Hue. Turns the words' colours round the wheel together. At 0 the opening phrase has I in azure, AM in pink and THAT in yellow green, and a phrase of four different words runs blue violet, rose, yellow and sea green; the words that are moving are always near white. +// Knob 2: Speed. 0.75 x Speed new lines a second: 1.5 at the opening 2, so a line takes 0.67 s, half of it the words sliding, a fifth held while the white fades, and the last 0.3 the page stepping up 6 rows. One line every 6.7 s at 0.2 and 2.25 a second at 3. +// Knob 3: Phrase. 0 I AM THAT I AM (5 words, 120 orders, 30 different lines), 1 RUB OUT THE WORD (24 orders, all different), 2 TO BE OR NOT TO BE (720 orders, 180 different lines), 3 VENI VIDI VICI (6 orders, so the page repeats every 6 lines). Turning it rewrites the whole page at once. +// Knob 4: Order. 0, the opening, is Steinhaus-Johnson-Trotter: every line is the one above with two neighbouring words swapped, and the last word of the phrase zigzags across the page. 1 is Heap's order: one swap a line, but the two words can stand far apart and slide over the ones between. 2 is lexicographic order, the phrase's own word order taken as the alphabet: the tail of the line turns round, so up to all the words move at once. + +// Gysin wrote out every arrangement of the words of a phrase, one under the +// next, so that the page reads as a block of the same words in shifting +// places. This pattern does the same with the phrase's words as tokens +// 0..n-1 in the order the phrase gives them, and lists the n! orders of the +// tokens, not the orders of the words: where a word comes twice, two lines +// can read the same. +// +// Steinhaus-Johnson-Trotter (the plain changes of English bell ringing) +// lists them so that each order is the one before with two neighbours +// swapped. Even's way of running it gives every token a direction, left to +// start with; the largest token that points at a smaller neighbour swaps +// with it, and every token larger than it turns round. The largest token +// of all moves on every line but one in n, so it crosses the line from end +// to end and back: the zigzag thread down the page. After n! - 1 swaps the +// order is the phrase with its first two words swapped, one swap from the +// start, so the list closes into a loop. +// +// Heap's algorithm also makes each order from the last with one swap, but +// of any two positions. The next permutation in lexicographic order finds +// the last place i where token[i] < token[i+1], swaps token[i] with the +// smallest larger token after it and reverses everything after i. +// +// In I AM THAT I AM the tokens are I0 AM1 THAT2 I3 AM4. A swap of I0 with +// I3 or AM1 with AM4 changes the tokens but not the words, and the new line +// reads like the one above. Over a full turn that is 30 of the 120 lines in +// Steinhaus-Johnson-Trotter order, 26 in Heap's and 12 in lexicographic +// order; in TO BE OR NOT TO BE it is 144, 91 and 48 of the 720. +// +// The capitals are drawn on a grid five rows tall: I one column wide, N +// four, M and W five, the rest three, one column between letters and three +// between words. Lines stand 6 rows apart, so 21 whole lines and the foot of +// a 22nd fill the 128 rows. Every order of a phrase is the same width, and +// the block is centred on the panel. +// +// The page dims towards the top. Drawn at one brightness, every line is +// the same bytes as some line above it, and the frame deflates to 0.055 +// bytes a pixel, a hair above the lowest pattern on the study's map. + +const W = 64, H = 128, PITCH = 6, GH = 5, GAP = 3, LINES = 23; +const BOTTOM = H - GH; // top row of the newest line +const { floor, round, min } = Math; +const cl = (v, a, b) => (v < a ? a : v > b ? b : v); +const smooth = (u) => (u <= 0 ? 0 : u >= 1 ? 1 : u * u * (3 - 2 * u)); + +const GLYPHS = { + A: ".#./#.#/###/#.#/#.#", B: "##./#.#/##./#.#/##.", C: ".##/#../#../#../.##", + D: "##./#.#/#.#/#.#/##.", E: "###/#../##./#../###", F: "###/#../##./#../#..", + G: ".##/#../#.#/#.#/.##", H: "#.#/#.#/###/#.#/#.#", I: "#/#/#/#/#", + J: "..#/..#/..#/#.#/.#.", K: "#.#/#.#/##./#.#/#.#", L: "#../#../#../#../###", + M: "#...#/##.##/#.#.#/#...#/#...#", N: "#..#/##.#/#.##/#..#/#..#", + O: ".#./#.#/#.#/#.#/.#.", P: "##./#.#/##./#../#..", Q: ".#./#.#/#.#/##./.##", + R: "##./#.#/##./#.#/#.#", S: ".##/#../.#./..#/##.", T: "###/.#./.#./.#./.#.", + U: "#.#/#.#/#.#/#.#/###", V: "#.#/#.#/#.#/#.#/.#.", W: "#...#/#...#/#.#.#/##.##/#...#", + X: "#.#/#.#/.#./#.#/#.#", Y: "#.#/#.#/.#./.#./.#.", Z: "###/..#/.#./#../###", +}; +// Each glyph as its width and the list of lit cells (dx, dy). +const FONT = {}; +for (const ch in GLYPHS) { + const rows = GLYPHS[ch].split("/"), cells = []; + rows.forEach((r, dy) => [...r].forEach((c, dx) => c === "#" && cells.push(dx, dy))); + FONT[ch] = { w: rows[0].length, cells }; +} + +// I AM THAT I AM is Exodus 3:14 in the King James Bible and Gysin's +// phrase; TO BE OR NOT TO BE is Hamlet's and VENI VIDI VICI Caesar's. +// Each word gets a colour of its own, shared where a word comes twice. +const PHRASES = ["I AM THAT I AM", "RUB OUT THE WORD", "TO BE OR NOT TO BE", "VENI VIDI VICI"].map((text) => { + const words = text.split(" "); + const kinds = [...new Set(words)]; + const widths = words.map((w) => [...w].reduce((s, c) => s + FONT[c].w + 1, -1)); + return { words, kind: words.map((w) => kinds.indexOf(w)), kinds: kinds.length, widths }; +}); + +// The three orders, each as the full list of token orders from the phrase. +function sjt(n) { + const p = [...Array(n).keys()], d = Array(n).fill(-1), out = [p.slice()]; + for (;;) { + let m = -1; + for (let i = 0; i < n; i++) { + const j = i + d[i]; + if (j >= 0 && j < n && p[j] < p[i] && (m < 0 || p[i] > p[m])) m = i; + } + if (m < 0) return out; + const v = p[m], j = m + d[m]; + [p[m], p[j]] = [p[j], p[m]]; + [d[m], d[j]] = [d[j], d[m]]; + for (let i = 0; i < n; i++) if (p[i] > v) d[i] = -d[i]; + out.push(p.slice()); + } +} +function heap(n) { + const a = [...Array(n).keys()], c = Array(n).fill(0), out = [a.slice()]; + let i = 1; + while (i < n) { + if (c[i] < i) { + const j = i % 2 === 0 ? 0 : c[i]; + [a[j], a[i]] = [a[i], a[j]]; + out.push(a.slice()); + c[i]++; + i = 1; + } else c[i++] = 0; + } + return out; +} +function lex(n) { + const a = [...Array(n).keys()], out = [a.slice()]; + for (;;) { + let i = n - 2; + while (i >= 0 && a[i] > a[i + 1]) i--; + if (i < 0) return out; + let j = n - 1; + while (a[j] < a[i]) j--; + [a[i], a[j]] = [a[j], a[i]]; + for (let l = i + 1, r = n - 1; l < r; l++, r--) [a[l], a[r]] = [a[r], a[l]]; + out.push(a.slice()); + } +} +const ORDERS = [sjt, heap, lex]; + +function hsv(h, s, v, o) { + h = (h - floor(h)) * 6; + const i = floor(h) % 6, f = h - floor(h); + const q = [[1, f, 0], [1 - f, 1, 0], [0, 1, f], [0, 1 - f, 1], [f, 0, 1], [1, 0, 1 - f]][i]; + for (let k = 0; k < 3; k++) o[k] = v * (1 - s + s * q[k]); + return o; +} + +export function setup(params) { + params.s = { + hue: 0, speed: 2, phrase: -1, order: -1, list: null, + k: LINES - 3, u: 0, buf: new Float32Array(W * H * 3), o: [0, 0, 0], + }; +} + +export function update(dt, input, params) { + if (!params.s) setup(params); + const S = params.s, k = input.knobValues || [0, 2, 0, 0.06], d = cl(dt || 0, 0, 0.1); + S.hue = k[0] || 0; + S.speed = cl(k[1] == null ? 2 : k[1], 0.2, 3); + const ph = cl(round(k[2] || 0), 0, 3), od = cl(round(k[3] == null ? 0.06 : k[3]), 0, 2); + if (ph !== S.phrase || od !== S.order) { + S.phrase = ph; + S.order = od; + S.list = ORDERS[od](PHRASES[ph].words.length); + } + S.u += d * 0.75 * S.speed; + while (S.u >= 1) { + S.u -= 1; + S.k++; + } +} + +// Where each token's word starts, for the order on line m of the list. +function places(S, m, left, out) { + const P = PHRASES[S.phrase], perm = S.list[((m % S.list.length) + S.list.length) % S.list.length]; + let x = left; + for (const t of perm) { + out[t] = x; + x += P.widths[t] + GAP; + } + return out; +} + +// The page dims row by row towards the top, to 45% on row 0, so the +// oldest lines recede. +const FADE = new Float32Array(H); +for (let y = 0; y < H; y++) FADE[y] = 0.45 + (0.55 * y) / (H - 1); + +function word(S, t, x0, y0, r, g, b) { + const B = S.buf, w = PHRASES[S.phrase].words[t]; + let x = x0; + for (const ch of w) { + const f = FONT[ch], c = f.cells; + for (let i = 0; i < c.length; i += 2) { + const px = round(x) + c[i], py = y0 + c[i + 1]; + if (px < 0 || px >= W || py < 0 || py >= H) continue; + const q = (py * W + px) * 3, v = FADE[py]; + B[q] += r * v; + B[q + 1] += g * v; + B[q + 2] += b * v; + } + x += f.w + 1; + } +} + +// A step is one new line: the words slide for the first half, the white +// fades until 0.7, and the page steps up 6 rows in the rest. +const SLIDE = 0.5, HOLD = 0.7; + +export function draw(display, params) { + const S = params.s; + if (!S || !S.list) return; + const P = PHRASES[S.phrase], n = P.words.length, B = S.buf, o = S.o; + B.fill(0); + const width = P.widths.reduce((s, v) => s + v, 0) + GAP * (n - 1); + const left = floor((W - width) / 2); + const u = S.u, slide = smooth(u / SLIDE); + const glow = u < SLIDE ? 1 : u < HOLD ? 1 - (u - SLIDE) / (HOLD - SLIDE) : 0; + const lift = u < HOLD ? 0 : round(PITCH * smooth((u - HOLD) / (1 - HOLD))); + // One colour a word, spread round the wheel from a start picked so that + // at Hue 0 no word is red or orange; orange dimmed at the top of the page + // reads as brown. + const cols = [], base = P.kinds === 4 ? 0.68 : 0.58; + for (let i = 0; i < P.kinds; i++) { + hsv(S.hue + base + i / P.kinds, 0.55, 230, o); + cols.push([o[0], o[1], o[2]]); + } + const a = new Array(n), b = new Array(n); + // Line S.k is the newest; while the page steps up, line S.k + 1 comes in + // under it as a copy, its words still in S.k's places. + const newest = lift > 0 ? S.k + 1 : S.k; + for (let m = newest; m > newest - LINES; m--) { + const y0 = BOTTOM - PITCH * (S.k - m) - lift; + if (y0 <= -GH || y0 >= H) continue; + if (m === S.k) { + places(S, m - 1, left, a); + places(S, m, left, b); + } else places(S, m > S.k ? S.k : m, left, b); + for (let t = 0; t < n; t++) { + const c = cols[P.kind[t]]; + if (m === S.k && a[t] !== b[t]) { + const x = a[t] + (b[t] - a[t]) * slide, wv = 0.8 * glow; + word(S, t, x, y0, c[0] + (255 - c[0]) * wv, c[1] + (255 - c[1]) * wv, c[2] + (255 - c[2]) * wv); + } else word(S, t, b[t], y0, c[0], c[1], c[2]); + } + } + for (let y = 0; y < H; y++) + for (let x = 0; x < W; x++) { + const q = (y * W + x) * 3; + display.setPixel(x, y, min(255, B[q]), min(255, B[q + 1]), min(255, B[q + 2])); + } +} From 092feb5c558f22dc92654462fb45fdd56c717b91 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 31 Oct 2026 20:15:21 +0000 Subject: [PATCH 3/9] =?UTF-8?q?=ED=8C=A8=ED=84=B4=20=EC=97=B0=EA=B5=AC=208?= =?UTF-8?q?1=ED=8C=90:=20Pollard's=20Rho,=20Beading=20Letters?= 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_01Bc4Jkpfqox342J4FE4ZD1C --- .../pattern-study/patterns/beading-letters.js | 551 ++++++++++++++++++ .../pattern-study/patterns/pollards-rho.js | 359 ++++++++++++ 2 files changed, 910 insertions(+) create mode 100644 incoming/pattern-study/patterns/beading-letters.js create mode 100644 incoming/pattern-study/patterns/pollards-rho.js diff --git a/incoming/pattern-study/patterns/beading-letters.js b/incoming/pattern-study/patterns/beading-letters.js new file mode 100644 index 00000000..e2994605 --- /dev/null +++ b/incoming/pattern-study/patterns/beading-letters.js @@ -0,0 +1,551 @@ +// ===== Patternflow pattern ===== +// Title: Beading Letters +// Family: type +// Idea: A three-letter word stacked down the panel in capitals 37 rows tall is poured stroke by stroke as round glossy threads of liquid, and every thread then does what Plateau and Rayleigh showed a cylinder of liquid must do: a ripple about nine and a half radii long grows on it from almost nothing, the strokes neck and pinch off roughly in the order they were poured, and each breaks into evenly spaced drops with a small satellite between them that ring and round off, so the word stands as a dotted, dot-matrix version of itself until the drops run off the bottom and the next word is poured. +// Author: patternflow-lab +// Date: 2026-10-31 +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Lineage: the Plateau-Rayleigh instability (Joseph Plateau, 1873; Lord Rayleigh, 1878), with the viscous term of Weber's long-wave equation (Constantin Weber, 1931) and the ringing of a drop in Rayleigh's lowest shape mode (Lord Rayleigh, 1879) damped at the rate Horace Lamb gave for a viscous drop; capitals drawn for this pattern as lines and elliptical arcs, pattern round 81 +// Round: 81 +// =============================== +// @matrix 64x128 +// @knobs Hue=0..1, Speed=0.2..3, Thickness=1.5..2.5, Viscosity=0..1 +// +// Knob 1: Hue. The liquid's colour, turned round the wheel from cyan at 0: violet at 0.25, red at 0.5, yellow green at 0.75. The highlight on every thread and drop stays white, and the caustic beside it is the liquid's colour paled. +// Knob 2: Speed. At 2 DEW is poured in 1.7 s, its first stroke breaks at 2.3 s and its last at 3.6 s, the dotted word holds 2 s, and at 5.6 s the drops let go and run off the bottom while INK is poured from 5.8 s. Every time goes as 2 / Speed, ten times slower at 0.2 and one and a half times faster at 3. +// Knob 3: Thickness. The thread's radius in LEDs, from 1.5 at the opening (a thread 3 LEDs across) to 2.5. The fastest ripple is 14.4 LEDs long at 1.5 and 24 at 2.5, so the stem of the D breaks into four drops at 1.5 and keeps only its two corner drops at 2.5, and a drop from the middle of a stroke grows from 5.5 LEDs across to about 9.5. A thicker thread is also slower, as the radius to the power 1.5: at 2.5 everything takes 2.15 times as long. +// Knob 4: Viscosity. The Ohnesorge number, the liquid's viscosity against its surface tension at this size, from 0 to 1. The fastest ripple is 9.02 radii long at 0, 9.6 at the opening 0.06 and 16.1 at 1, so a thicker liquid breaks into fewer, bigger drops, 2.7 times more slowly, and its threads draw out into thin filaments between bulbs before they snap. At the opening a drop rings about once every 0.83 s and the ringing falls by e in 1.7 s; from about 0.8 the drops creep round without ringing. + +// Plateau measured it and Rayleigh explained it: a long cylinder of liquid +// held only by surface tension is unstable. Any ripple r = r0 (1 + a cos kz) +// longer than the thread's circumference (k r0 < 1) lowers the surface area +// at fixed volume, so it grows, as a = a0 exp(sigma t). Rayleigh's growth +// rate for an inviscid thread, in units of tau = sqrt(rho r0^3 / gamma), is +// sigma^2 = x (1 - x^2) I1(x) / I0(x), x = k r0, +// largest at x = 0.697, a wavelength of 9.02 radii, with sigma = 0.343. +// Viscosity enters through Weber's long-wave equation, which adds a damping +// term 3 Oh x^2 sigma to the left; here that term is added to Rayleigh's +// exact right-hand side, so Oh = 0 gives Rayleigh's numbers back. +// +// Each stroke of a letter is a thread of length L with free ends. Its modes +// are cos(2 pi n s / L), with a crest at both ends (a free end gathers +// liquid), and of the whole numbers n the stroke takes the one whose sigma +// is largest: the fastest ripple that fits. Its crests are where the drops +// form, so a stroke always ends in a drop and every corner of a letter, where +// strokes meet, gets one drop that all of them feed; it forms as soon as the +// first of them breaks, as a bulb on the ends of the others. The ripple grows +// from a small random a0, 0.012 to 0.03, from the moment the liquid is laid, +// so the word pinches roughly in the order it was poured. The volume per +// wavelength is kept: r0 shrinks as a grows, and each drop gets the volume of +// the thread it came from, a free end the volume of a whole wavelength. With +// viscosity the profile is sharpened into bulbs on thin filaments. +// +// After a pinch each drop starts stretched along its stroke and relaxes as a +// spheroid of fixed volume, ringing in Rayleigh's lowest shape mode, omega^2 +// = 8 gamma / (rho R^3), damped at Lamb's rate 5 nu / R^2. A small satellite +// is left where each neck broke. +// +// Every thread and drop is drawn as a body of revolution: for each LED the +// nearest point of the stroke, its arc length s and the side the LED is on +// give a surface normal, which a light from the upper left shades with a +// white highlight and a glow on the far side, as light passes through. + +const W = 64, H = 128, N = W * H; +const { floor, ceil, min, max, sqrt, sin, cos, exp, log, pow, abs, PI } = Math; +const TAU = 2 * PI; +const cl = (v, a, b) => (v < a ? a : v > b ? b : v); + +const WORDS = ["DEW", "INK", "OIL", "SEA", "WET", "SAP", "TAR", "GEL"]; +// Letters 37 rows tall, 8 units of 4.625 LEDs, at rows 3, 45.5 and 88. +const LH = 37, U = LH / 8, TOP = 3, PITCH = 42.5; +// Model seconds (real seconds at Speed 2). +const POUR = 1.2, JUMP = 0.05, HOLD = 2.0, NEXT = 0.25, FALL = 520, TAU0 = 0.15, R0REF = 1.5; +const HUE0 = 0.5; + +// Strokes: a start point, then "L" x y for a line or "A" cx cy rx ry a0 a1 +// for an elliptical arc in degrees (y down); "Z" closes the stroke. +// All joins between strokes are at stroke ends. +const GLYPHS = { + D: [10, [[0, 0, "L", 0, 8], [0, 0, "L", 5, 0, "A", 5, 4, 5, 4, -90, 90, "L", 0, 8]]], + E: [9, [[0, 0, "L", 0, 4], [0, 4, "L", 0, 8], [0, 0, "L", 9, 0], [0, 4, "L", 7, 4], [0, 8, "L", 9, 8]]], + W: [11, [[0, 0, "L", 2.75, 8], [2.75, 8, "L", 5.5, 1.5], [5.5, 1.5, "L", 8.25, 8], [8.25, 8, "L", 11, 0]]], + I: [6, [[0, 0, "L", 3, 0], [3, 0, "L", 6, 0], [3, 0, "L", 3, 8], [0, 8, "L", 3, 8], [3, 8, "L", 6, 8]]], + N: [9, [[0, 8, "L", 0, 0], [0, 0, "L", 9, 8], [9, 8, "L", 9, 0]]], + K: [9, [[0, 0, "L", 0, 4.5], [0, 4.5, "L", 0, 8], [0, 4.5, "L", 9, 0], [0, 4.5, "L", 9, 8]]], + O: [10, [[10, 4, "A", 5, 4, 5, 4, 0, 360, "Z"]]], + L: [8, [[0, 0, "L", 0, 8], [0, 8, "L", 8, 8]]], + S: [9, [[8.397, 1, "A", 4.5, 2, 4.5, 2, -30, -270, "A", 4.5, 6, 4.5, 2, -90, 150]]], + A: [10, [[0, 8, "L", 1.5625, 5.5], [1.5625, 5.5, "L", 5, 0], [5, 0, "L", 8.4375, 5.5], [8.4375, 5.5, "L", 10, 8], [1.5625, 5.5, "L", 8.4375, 5.5]]], + T: [10, [[0, 0, "L", 5, 0], [5, 0, "L", 10, 0], [5, 0, "L", 5, 8]]], + P: [9, [[0, 0, "L", 0, 4.6], [0, 4.6, "L", 0, 8], [0, 0, "L", 5, 0, "A", 5, 2.3, 4, 2.3, -90, 90, "L", 0, 4.6]]], + R: [9, [[0, 0, "L", 0, 4.6], [0, 4.6, "L", 0, 8], [0, 0, "L", 5, 0, "A", 5, 2.3, 4, 2.3, -90, 90], [5, 4.6, "L", 0, 4.6], [5, 4.6, "L", 9, 8]]], + G: [10, [[8.83, 1.429, "A", 5, 4, 5, 4, -40, -360], [10, 4, "L", 6, 4]]], +}; + +// ---- Rayleigh's growth rate with Weber's viscous term. +function besselRatio(x) { + let t0 = 1, t1 = x / 2, i0 = 0, i1 = 0; + const h = (x * x) / 4; + for (let k = 0; k < 10; k++) { + i0 += t0; + i1 += t1; + t0 *= h / ((k + 1) * (k + 1)); + t1 *= h / ((k + 1) * (k + 2)); + } + return i1 / i0; +} + +function growth(x, oh) { + if (x >= 1) return 0; + const f = x * (1 - x * x) * besselRatio(x), b = 3 * oh * x * x; + return (-b + sqrt(b * b + 4 * f)) / 2; +} + +// ---- Stroke geometry, in LEDs. +function strokeOf(cmd, ox, oy) { + const xs = [ox + cmd[0] * U], ys = [oy + cmd[1] * U]; + let closed = false; + for (let i = 2; i < cmd.length; ) { + const c = cmd[i]; + if (c === "L") { + xs.push(ox + cmd[i + 1] * U); + ys.push(oy + cmd[i + 2] * U); + i += 3; + } else if (c === "A") { + const cx = cmd[i + 1], cy = cmd[i + 2], rx = cmd[i + 3], ry = cmd[i + 4], a0 = (cmd[i + 5] * PI) / 180, a1 = (cmd[i + 6] * PI) / 180; + const steps = max(4, ceil((abs(a1 - a0) * sqrt((rx * rx + ry * ry) / 2) * U) / 0.8)); + for (let k = 0; k <= steps; k++) { + const a = a0 + ((a1 - a0) * k) / steps; + xs.push(ox + (cx + rx * cos(a)) * U); + ys.push(oy + (cy + ry * sin(a)) * U); + } + i += 7; + } else { + closed = true; + i++; + } + } + // Drop repeated points, measure arc length. + const px = [xs[0]], py = [ys[0]], ps = [0]; + for (let k = 1; k < xs.length; k++) { + const d = sqrt((xs[k] - px[px.length - 1]) ** 2 + (ys[k] - py[py.length - 1]) ** 2); + if (d < 1e-4) continue; + px.push(xs[k]); + py.push(ys[k]); + ps.push(ps[ps.length - 1] + d); + } + return { px, py, ps, L: ps[ps.length - 1], closed }; +} + +// Point and unit tangent at arc length s. +function at(st, s, o) { + const { px, py, ps } = st; + let i = 0; + while (i < ps.length - 2 && ps[i + 1] < s) i++; + const len = ps[i + 1] - ps[i], u = cl((s - ps[i]) / len, 0, 1); + o[0] = px[i] + (px[i + 1] - px[i]) * u; + o[1] = py[i] + (py[i + 1] - py[i]) * u; + o[2] = (px[i + 1] - px[i]) / len; + o[3] = (py[i + 1] - py[i]) / len; + return o; +} + +function wordOf(index) { + const text = WORDS[index], strokes = []; + for (let l = 0; l < text.length; l++) { + const [w, cmds] = GLYPHS[text[l]]; + const ox = 32 - (w * U) / 2, oy = TOP + l * PITCH; + for (const c of cmds) { + const st = strokeOf(c, ox, oy); + st.letter = l; + st.eps = 0.012 + 0.018 * Math.random(); + st.envP = TAU * Math.random(); + strokes.push(st); + } + } + return { index, strokes, beads: [], sats: [], S0: 0, Td: 1e9 }; +} + +// ---- Timing and drops for the current knobs: the mode each stroke takes, +// when it pinches, and the drops it leaves. +function plan(S, wd) { + const r0 = S.r0, oh = S.oh, tau = TAU0 * pow(r0 / R0REF, 1.5); + let total = 0; + for (const st of wd.strokes) total += st.L; + const v = total / POUR, tmp = [0, 0, 0, 0]; + // The fastest growth an endless thread could have, for the floor below. + let smax = 0; + for (let x = 0.02; x < 1; x += 0.01) smax = max(smax, growth(x, oh)); + let t = wd.S0; + wd.beads = []; + wd.sats = []; + const nodes = []; + for (const st of wd.strokes) { + let best = -1, bn = st.closed ? 2 : 1; + for (let n = st.closed ? 2 : 1; n < 40; n++) { + const g = growth((TAU * n * r0) / st.L, oh); + if (g > best) { + best = g; + bn = n; + } + if ((TAU * n * r0) / st.L >= 1) break; + } + st.n = bn; + st.k = (TAU * bn) / st.L; + // A stroke too short for a fast ripple (at 2.5 LEDs the serif of the I + // has k r0 >= 1 for every n) still balls up into its end drops, here at + // no less than 0.6 of the fastest rate. + st.sig = max(best, 0.6 * smax); + st.tau = tau; + st.v = v; + st.r0 = r0; + st.t0 = t; + st.t1 = t + st.L / v; + // A slow swell 2.7 wavelengths long lets some crests grow ahead of others. + st.envK = TAU / (2.7 * 9.6 * r0); + st.tp = max(st.t1 + 0.2, t + st.L / (2 * v) + (tau * log(1 / st.eps)) / st.sig); + t = st.t1 + JUMP; + const vol = PI * r0 * r0 * (st.L / bn); + const first = st.closed ? 0 : 1; + for (let k = first; k < bn; k++) { + at(st, (k * st.L) / bn, tmp); + wd.beads.push({ x: tmp[0], y: tmp[1], tx: tmp[2], ty: tmp[3], V: vol, act: st.tp, e0: 0.5 }); + } + for (let k = 0; k < bn; k++) { + at(st, ((k + 0.5) * st.L) / bn, tmp); + wd.sats.push({ x: tmp[0], y: tmp[1], act: st.tp + 0.04, fall: 0 }); + } + if (!st.closed) + for (const e of [0, 1]) { + at(st, e * st.L, tmp); + let node = null; + for (const nd of nodes) if (nd.letter === st.letter && abs(nd.x - tmp[0]) < 0.6 && abs(nd.y - tmp[1]) < 0.6) node = nd; + if (!node) { + node = { letter: st.letter, x: tmp[0], y: tmp[1], tx: tmp[2], ty: tmp[3], V: 0, act: 1e9, m: 0, e0: 0.2 }; + nodes.push(node); + } + node.V += vol / 2; + node.m++; + node.act = min(node.act, st.tp); + } + } + // A free end gathers the liquid of the end it retracts from. + for (const nd of nodes) { + if (nd.m === 1) nd.V *= 2; + wd.beads.push(nd); + } + let last = 0; + for (const st of wd.strokes) last = max(last, st.tp); + for (const b of wd.beads) { + b.R = Math.cbrt((3 * b.V) / (4 * PI)); + const q = r0 / b.R; + b.w = sqrt(8 * q * q * q) / tau; + b.g = (5 * oh * q * q) / tau; + b.fall = b.fall || 0; + last = max(last, b.act); + } + wd.Td = last + HOLD; + wd.v = v; + wd.Q = 1 + 2 * oh; + // Means of the sharpened profile c' over a period, for the volume. + let m1 = 0, m2 = 0; + for (let i = 0; i < 64; i++) { + const c = 2 * pow((1 + cos((TAU * (i + 0.5)) / 64)) / 2, wd.Q) - 1; + m1 += c / 64; + m2 += (c * c) / 64; + } + wd.m1 = m1; + wd.m2 = m2; +} + +// ---- For every LED near a stroke, the nearest point of up to two strokes. +function buildMap(S, wd) { + const reach = (S.reach = 2.2 * S.r0 + 1.2); + const { sj, sd, ss, sdx, sdy, stx, sty, scap } = S; + sj.fill(-1); + wd.strokes.forEach((st, j) => { + const { px, py, ps } = st, last = px.length - 2; + for (let i = 0; i <= last; i++) { + const ax = px[i], ay = py[i], len = ps[i + 1] - ps[i]; + const tx = (px[i + 1] - ax) / len, ty = (py[i + 1] - ay) / len; + const x0 = max(0, floor(min(ax, px[i + 1]) - reach)), x1 = min(W - 1, ceil(max(ax, px[i + 1]) + reach)); + const y0 = max(0, floor(min(ay, py[i + 1]) - reach)), y1 = min(H - 1, ceil(max(ay, py[i + 1]) + reach)); + for (let y = y0; y <= y1; y++) + for (let x = x0; x <= x1; x++) { + const qx = x + 0.5 - ax, qy = y + 0.5 - ay; + let t = qx * tx + qy * ty, cap = 0; + if (t < 0) { + if (!st.closed && i === 0) cap = 1; + t = 0; + } else if (t > len) { + if (!st.closed && i === last) cap = 1; + t = len; + } + let dx = qx - tx * t, dy = qy - ty * t; + const d = sqrt(dx * dx + dy * dy); + if (d > reach) continue; + if (d > 1e-6) { + dx /= d; + dy /= d; + } else { + dx = -ty; + dy = tx; + } + const p = (y * W + x) * 2; + let k = -1; + if (sj[p] === j) k = d < sd[p] ? p : -2; + else if (sj[p + 1] === j) k = d < sd[p + 1] ? p + 1 : -2; + else if (sj[p] < 0) k = p; + else if (sj[p + 1] < 0) k = p + 1; + else k = sd[p] > sd[p + 1] ? (d < sd[p] ? p : -2) : d < sd[p + 1] ? p + 1 : -2; + if (k < 0) continue; + sj[k] = j; + sd[k] = d; + ss[k] = ps[i] + t; + sdx[k] = dx; + sdy[k] = dy; + stx[k] = tx; + sty[k] = ty; + scap[k] = cap; + } + } + }); + let n = 0; + for (let p = 0; p < N; p++) if (sj[p * 2] >= 0) S.active[n++] = p; + S.nActive = n; +} + +export function setup(params) { + params.s = { + hue: 0, speed: 2, r0: 1.5, oh: 0.06, m: 0, idx: 0, cur: null, old: null, reach: 4.5, + sj: new Int16Array(N * 2), sd: new Float32Array(N * 2), ss: new Float32Array(N * 2), + sdx: new Float32Array(N * 2), sdy: new Float32Array(N * 2), stx: new Float32Array(N * 2), + sty: new Float32Array(N * 2), scap: new Int8Array(N * 2), active: new Int32Array(N), nActive: 0, + K: new Float32Array(N), C: new Float32Array(N), NX: new Float32Array(N), NY: new Float32Array(N), NZ: new Float32Array(N), + CA: new Float32Array(N), CB: new Float32Array(N), + base: [0, 0, 0], tmp: [0, 0, 0, 0], + }; +} + +export function update(dt, input, params) { + if (!params.s) setup(params); + const S = params.s, k = input.knobValues || [0, 2, 1.5, 0.06], d = cl(dt || 0, 0, 0.1); + S.hue = k[0] || 0; + S.speed = cl(k[1] == null ? 2 : k[1], 0.2, 3); + const r0 = cl(k[2] == null ? 1.5 : k[2], 1.5, 2.5), oh = cl(k[3] == null ? 0.06 : k[3], 0, 1); + const newR = abs(r0 - S.r0) > 0.004, newOh = abs(oh - S.oh) > 0.002; + if (newR) S.r0 = r0; + if (newOh) S.oh = oh; + if (!S.cur) { + S.cur = wordOf(S.idx); + S.cur.S0 = 0; + plan(S, S.cur); + buildMap(S, S.cur); + } else if (newR || newOh) { + plan(S, S.cur); + if (newR) buildMap(S, S.cur); + } + S.m += (d * S.speed) / 2; + const cur = S.cur; + if (S.m >= cur.Td) { + // The drops let go and run off the bottom; the next word is poured. + for (const b of cur.beads) b.fall = cur.Td + 0.35 * Math.random(); + for (const b of cur.sats) b.fall = cur.Td + 0.35 * Math.random(); + S.old = cur; + S.idx = (S.idx + 1) % WORDS.length; + const nw = wordOf(S.idx); + nw.S0 = cur.Td + NEXT; + plan(S, nw); + buildMap(S, nw); + S.cur = nw; + } + if (S.old && S.m > S.old.Td + 1.2) S.old = null; +} + +// The radius of stroke st at arc length s, and its slope, at model time t. +function profile(wd, st, s, t, o) { + let a = st.eps * exp((st.sig * (t - st.t0 - s / st.v)) / st.tau); + a *= 1 + 0.15 * sin(st.envK * s + st.envP); + if (a > 1) a = 1; + const ph = st.k * s, base = (1 + cos(ph)) / 2, bq = pow(base, wd.Q - 1); + const c = 2 * base * bq - 1, den = st.r0 / sqrt(1 + 2 * a * wd.m1 + a * a * wd.m2); + o[0] = den * (1 + a * c); + o[1] = den * a * -wd.Q * bq * sin(ph) * st.k; +} + +function wobble(b, t) { + const e0 = b.e0, w = b.w, g = b.g; + if (g < w * 0.999) { + const wd = sqrt(w * w - g * g); + return e0 * exp(-g * t) * (cos(wd * t) + (g / wd) * sin(wd * t)); + } + const r = sqrt(max(1e-9, g * g - w * w)), r1 = -g + r, r2 = -g - r; + return (e0 * (r1 * exp(r2 * t) - r2 * exp(r1 * t))) / (r1 - r2); +} + +// A spheroid of semi-axes a along (tx, ty) and b across and up, into the +// buffers: the topmost surface wins each LED. +function spheroid(S, cx, cy, tx, ty, a, b) { + const { K, C, NX, NY, NZ } = S, ext = max(a, b) + 1; + const x0 = max(0, floor(cx - ext)), x1 = min(W - 1, ceil(cx + ext)), y0 = max(0, floor(cy - ext)), y1 = min(H - 1, ceil(cy + ext)); + const ia = 1 / (a * a), ib = 1 / (b * b); + for (let y = y0; y <= y1; y++) + for (let x = x0; x <= x1; x++) { + const qx = x + 0.5 - cx, qy = y + 0.5 - cy, u = qx * tx + qy * ty, w = -qx * ty + qy * tx; + const q = u * u * ia + w * w * ib, sq = sqrt(q), gx = u * ia, gy = w * ib, g = sqrt(gx * gx + gy * gy); + const c = g < 1e-9 ? 1 : cl(0.5 - ((sq - 1) * sq) / g, 0, 1); + if (c <= 0) continue; + const h = q < 1 ? b * sqrt(1 - q) : 0, key = h + c, p = y * W + x; + if (c > C[p]) C[p] = c; + if (key <= K[p]) continue; + K[p] = key; + let nx = gx * tx - gy * ty, ny = gx * ty + gy * tx, nz = h * ib; + const l = sqrt(nx * nx + ny * ny + nz * nz) || 1; + NX[p] = nx / l; + NY[p] = ny / l; + NZ[p] = nz / l; + } +} + +function drops(S, wd, t) { + for (const b of wd.beads) { + if (t < b.act) continue; + let e = cl(wobble(b, t - b.act), -0.45, 1), tx = b.tx, ty = b.ty, y = b.y; + if (b.fall && t > b.fall) { + const f = t - b.fall; + y += 0.5 * FALL * f * f; + e = min(0.6, FALL * f * 0.004); + tx = 0; + ty = 1; + } + if (y - b.R * 2 > H) continue; + spheroid(S, b.x, y, tx, ty, b.R * (1 + e), b.R / sqrt(1 + e)); + } + const rs = 0.4 * S.r0; + for (const sa of wd.sats) { + if (t < sa.act) continue; + let y = sa.y; + if (sa.fall && t > sa.fall) y += 0.5 * FALL * (t - sa.fall) ** 2; + if (y - 2 > H) continue; + const grow = cl((t - sa.act) / 0.15, 0, 1); + spheroid(S, sa.x, y, 1, 0, rs * grow + 0.01, rs * grow + 0.01); + } +} + +function hsv(h, s, v, o) { + h = (h - floor(h)) * 6; + const i = floor(h) % 6, f = h - i, 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; + return o; +} + +// The light: from the upper left and in front. +const LX = -0.45, LY = -0.6, LZ = 0.66, LN = sqrt(LX * LX + LY * LY + LZ * LZ); +const lx = LX / LN, ly = LY / LN, lz = LZ / LN, HN = sqrt(lx * lx + ly * ly + (lz + 1) ** 2); +const hx = lx / HN, hy = ly / HN, hz = (lz + 1) / HN, LP = sqrt(lx * lx + ly * ly); + +export function draw(display, params) { + const S = params.s; + if (!S || !S.cur) return; + const { K, C, NX, NY, NZ, sj, sd, ss, sdx, sdy, stx, sty, scap, active } = S; + const t = S.m, wd = S.cur, reach = S.reach, pr = [0, 0], head = S.tmp; + K.fill(-1); + C.fill(0); + + // Where each stroke of the current word stands: 0 not poured yet, 1 being + // poured (head = arc length reached), 2 a thread, 3 broken into drops. + for (const st of wd.strokes) { + st.state = t < st.t0 ? 0 : t < st.t1 ? 1 : t < st.tp ? 2 : 3; + if (st.state === 1) { + st.head = (t - st.t0) * st.v; + at(st, st.head, head); + st.hx = head[0]; + st.hy = head[1]; + } + } + + for (let a = 0; a < S.nActive; a++) { + const p = active[a], X = (p % W) + 0.5, Y = floor(p / W) + 0.5; + for (let q = p * 2; q < p * 2 + 2; q++) { + const j = sj[q]; + if (j < 0) break; + const st = wd.strokes[j]; + if (st.state === 0 || st.state === 3) continue; + let s = ss[q], dist = sd[q], dx = sdx[q], dy = sdy[q], cap = scap[q]; + if (st.state === 1 && s > st.head) { + if (s - st.head > reach) continue; + dx = X - st.hx; + dy = Y - st.hy; + dist = sqrt(dx * dx + dy * dy); + if (dist > reach) continue; + if (dist > 1e-6) { + dx /= dist; + dy /= dist; + } + s = st.head; + cap = 1; + } + profile(wd, st, s, t, pr); + const r = pr[0], dr = cap ? 0 : pr[1]; + if (r < 0.03) continue; + const c = min(1, r - dist + 0.5); + if (c <= 0) continue; + const h = dist < r ? sqrt(r * r - dist * dist) : 0, key = h + c; + if (c > C[p]) C[p] = c; + if (key <= K[p]) continue; + K[p] = key; + const ct = min(1, dist / r); + let nx = -dr * stx[q] + ct * dx, ny = -dr * sty[q] + ct * dy, nz = h / r; + const l = sqrt(nx * nx + ny * ny + nz * nz) || 1; + NX[p] = nx / l; + NY[p] = ny / l; + NZ[p] = nz / l; + } + } + drops(S, wd, t); + if (S.old) drops(S, S.old, t); + + // The caustic: light through each thread or drop comes to a focus on the + // dark ground beyond it, down and to the right, brightest behind the + // thickest liquid. Shifted 1.5 and 2 LEDs, then softened once. + const { CA, CB } = S; + CA.fill(0); + for (let p = 0; p < N; p++) { + const c = C[p]; + if (c <= 0) continue; + const x = p % W, y = floor(p / W) + 2; + if (y >= H || x + 2 >= W) continue; + const w = c * (0.09 + 0.28 * NZ[p]) * 0.5; + CA[y * W + x + 1] += w; + CA[y * W + x + 2] += w; + } + for (let p = 0; p < N; p++) { + const x = p % W; + let v = CA[p] * 0.6; + if (x > 0) v += CA[p - 1] * 0.1; + if (x < W - 1) v += CA[p + 1] * 0.1; + if (p >= W) v += CA[p - W] * 0.1; + if (p < N - W) v += CA[p + W] * 0.1; + CB[p] = min(0.4, v); + } + + const base = hsv(S.hue + HUE0, 0.8, 1, S.base); + const kr = base[0] * 0.8 + 0.2, kg = base[1] * 0.8 + 0.2, kb = base[2] * 0.8 + 0.2; + for (let p = 0; p < N; p++) { + const c = C[p], ca = CB[p] * (1 - c); + let r = ca * kr, g = ca * kg, b = ca * kb; + if (c > 0) { + const nx = NX[p], ny = NY[p], nz = NZ[p]; + const diff = max(0, nx * lx + ny * ly + nz * lz); + const sh = max(0, nx * hx + ny * hy + nz * hz), spec = pow(sh, 28); + const back = max(0, -(nx * lx + ny * ly) / LP) * (1 - nz); + const v = 0.22 + 0.62 * diff + 0.5 * back; + r += c * (base[0] * v + spec); + g += c * (base[1] * v + spec); + b += c * (base[2] * v + spec); + } + display.setPixel(p % W, floor(p / W), floor(cl(r, 0, 1) * 255), floor(cl(g, 0, 1) * 255), floor(cl(b, 0, 1) * 255)); + } +} diff --git a/incoming/pattern-study/patterns/pollards-rho.js b/incoming/pattern-study/patterns/pollards-rho.js new file mode 100644 index 00000000..71e3e2dd --- /dev/null +++ b/incoming/pattern-study/patterns/pollards-rho.js @@ -0,0 +1,359 @@ +// ===== Patternflow pattern ===== +// Title: Pollard's Rho +// Family: algo +// Idea: The sequence x -> x^2 + c taken modulo a hidden prime factor p of n stands up the panel as the Greek letter rho, a tail of numbered beads climbing from x0 = 2 at the bottom into a loop of beads at the top, and a tortoise and a hare chase each other along it, one bead and two beads a beat after Floyd or one bead a beat with the tortoise jumping to the hare at powers of two after Brent, while the readout beside the tail shows what the algorithm itself sees, the two values modulo n and their difference, and the gcd in the middle of the loop stays 1 until the two land on the same bead, when it jumps to p, the loop flashes and n is written as its two factors before the next number. +// Author: patternflow-lab +// Date: 2026-10-31 +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Lineage: Pollard's rho method of factorisation (John M. Pollard, 1975), with Floyd's cycle finding (credited to Robert W. Floyd) and Brent's (Richard P. Brent, 1980); numerals and letters drawn for this pattern on a 3 by 5 grid, pattern round 81 +// Round: 81 +// =============================== +// @matrix 64x128 +// @knobs Hue=0..1, Speed=0.2..3, Number=0..7, Constant=1..6 +// +// Knob 1: Hue. Turns the three colours round the wheel together. At 0 the beads are azure, the tortoise green and the hare orange; at 0.5 the beads are orange-red, the tortoise magenta and the hare blue. +// Knob 2: Speed. One evaluation of f takes 0.44 / Speed seconds: 0.22 s at the opening 2, so a Floyd beat (three evaluations) takes 0.66 s and a Brent beat 0.22 s. At 0.2 a Floyd beat takes 6.6 s, at 3 it takes 0.44 s. A found factor is held 3.2 s at Speed 2. +// Knob 3: Number. Which n the pattern starts from: 0 is 5893 = 71 x 83, then 6319, 8051 = 83 x 97, 4307, 4331, 6497, 2911 and 7031. Each number is factored by Floyd and then by Brent, and then the next one comes. With c = 1, 4307 = 59 x 73 fails: both primes close their loops on the same beat, the gcd is n itself, and the pattern tries again with c = 2. +// Knob 4: Constant. The c in x^2 + c, 1 to 6. A new c is a new sequence and a new rho: for 5893 the rho modulo 83 has a tail of 4 and a loop of 5 at c = 1, 2 and 4 at c = 2, 5 and 5 at c = 4, and at c = 5 the factor that turns up first is 71, with a tail of 2 and a loop of 6. + +// Pollard's idea: take a map that looks random, f(x) = x^2 + c, and walk +// x0, x1 = f(x0), x2 = f(x1), ... modulo n. Modulo an unknown prime factor p +// of n the same walk is a walk on only p values, so after about sqrt(p) +// steps it must come back to a value it has seen, and from there it goes +// round the same loop for ever. Drawn out, the walk modulo p is a tail and a +// loop: the letter rho. Modulo n nothing has repeated yet, but two terms +// that sit on the same bead of the rho differ by a multiple of p, so +// gcd(|x_i - x_j|, n) is p. +// +// The algorithm cannot see the rho, only the values modulo n. It finds the +// loop without storing the walk by running two pointers. Floyd's tortoise +// takes one step and his hare two each beat; once both are on the loop the +// hare gains one bead a beat and lands on the tortoise's bead at the first +// beat i that is at least the tail's length and a multiple of the loop's. +// Brent's hare takes one step a beat and his tortoise waits, jumping to the +// hare after 1, 2, 4, 8, ... steps; it costs one evaluation of f a beat +// against Floyd's three, which is what Brent's 1980 paper gains. Here time +// runs by evaluations, so the two methods race fairly on the same number. +// +// The beads are x_i mod p, which the panel knows and the algorithm does +// not; the readout T and H are x_t and x_h mod n, and D is their difference. +// The hare lights each bead as it first reaches it. Every product here is +// below 8051^2, about 6.5e7, so ordinary doubles are exact. + +const W = 64, H = 128, N = W * H; +const { floor, min, max, round, sqrt, sin, cos, abs, PI } = Math; +const cl = (v, a, b) => (v < a ? a : v > b ? b : v); +const smooth = (u) => (u <= 0 ? 0 : u >= 1 ? 1 : u * u * (3 - 2 * u)); +const TAU = 2 * PI; + +const NUMS = [5893, 6319, 8051, 4307, 4331, 6497, 2911, 7031]; +const X0 = 2; +// The rho: loop centre and radius, the stem down its left side to YB. +const CX = 32, CY = 31, R = 24, SX = CX - R, YB = 122; +// Model seconds: one evaluation of f, the start, and the hold after a find. +const EVAL = 0.22, INTRO = 0.5, HOLD = 3.2, MOVE = 0.6; +const HUE_BEAD = 0.55, HUE_T = 0.33, HUE_H = 0.09; + +// ---- Glyphs on a 3 by 5 grid, rows top to bottom. +const GL = { + 0: "111101101101111", 1: "010110010010111", 2: "111001111100111", 3: "111001011001111", + 4: "101101111001001", 5: "111100111001111", 6: "111100111101111", 7: "111001001010010", + 8: "111101111101111", 9: "111101111001111", + A: "010101111101101", B: "110101110101110", C: "011100100100011", D: "110101101101110", + E: "111100110100111", F: "111100110100100", G: "011100101101011", H: "101101111101101", + I: "111010010010111", L: "100100100100111", N: "101111111101101", O: "010101101101010", + R: "110101110101101", T: "111010010010010", X: "101101010101101", Y: "101101010010010", + "+": "000010111010000", "=": "000111000111000", "*": "000101010101000", "|": "010010010010010", + "-": "000000111000000", " ": "000000000000000", +}; + +function gcd(a, b) { + while (b) { + const t = a % b; + a = b; + b = t; + } + return a; +} + +function smallestFactor(n) { + for (let d = 2; d * d <= n; d++) if (n % d === 0) return d; + return n; +} + +// One run of the method on (n, c): the states beat by beat, and the rho of +// the prime it finds (or, when it fails, of the smaller prime). +function plan(n, c, brent) { + const xs = [X0 % n]; + const x = (i) => { + while (xs.length <= i) { + const v = xs[xs.length - 1]; + xs.push((v * v + c) % n); + } + return xs[i]; + }; + const beats = [{ t: 0, h: 0, d: 0 }]; + let d = 1; + if (!brent) { + for (let i = 1; d === 1 && i < 400; i++) { + d = gcd(abs(x(i) - x(2 * i)), n); + beats.push({ t: i, h: 2 * i, d }); + } + } else { + let pw = 1, lam = 1, t = 0, h = 1; + for (let k = 0; k < 800; k++) { + d = gcd(abs(x(t) - x(h)), n); + beats.push({ t, h, d }); + if (d > 1) break; + if (pw === lam) { + t = h; + pw *= 2; + lam = 0; + } + h++; + lam++; + } + } + const p = d > 1 && d < n ? d : smallestFactor(n); + const seen = new Map(), lab = []; + let v = X0 % p, i = 0; + while (!seen.has(v)) { + seen.set(v, i); + lab.push(v); + v = (v * v + c) % p; + i++; + } + const mu = seen.get(v), lam = i - mu; + const tailSp = mu > 0 ? (YB - CY) / mu : 99, loopSp = lam > 1 ? 2 * R * sin(PI / lam) : 99; + return { + n, c, brent, xs, beats, d, p, q: n / p, mu, lam, lab, fail: d === n, + rt: cl(tailSp / 2 - 0.4, 1.5, 5), rl: cl(loopSp / 2 - 0.4, 1.5, 5), + beat: brent ? EVAL : 3 * EVAL, + }; +} + +function hsv(h, s, v, o) { + h = (h - floor(h)) * 6; + const i = floor(h) % 6, f = h - i, 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; + return o; +} + +export function setup(params) { + params.s = { + hue: 0, speed: 2, num: -1, c: -1, idx: 0, cc: 1, brent: false, + run: null, m: 0, buf: new Float32Array(N * 3), + cb: [0, 0, 0], ct: [0, 0, 0], ch: [0, 0, 0], pos: [0, 0], + }; +} + +function start(S) { + S.run = plan(NUMS[S.idx], S.cc, S.brent); + S.m = 0; +} + +// After a run: a failure tries the next c with the same method; a Floyd find +// hands the number to Brent; a Brent find moves on to the next number. +function next(S) { + const P = S.run; + if (P.fail) S.cc++; + else if (!P.brent) S.brent = true; + else { + S.brent = false; + S.idx = (S.idx + 1) % NUMS.length; + S.cc = S.c; + } + start(S); +} + +export function update(dt, input, params) { + if (!params.s) setup(params); + const S = params.s, k = input.knobValues || [0, 2, 0, 1], d = cl(dt || 0, 0, 0.1); + S.hue = k[0] || 0; + S.speed = cl(k[1] == null ? 2 : k[1], 0.2, 3); + const num = round(cl(k[2] || 0, 0, 7)), c = round(cl(k[3] == null ? 1 : k[3], 1, 6)); + if (num !== S.num || c !== S.c) { + S.num = num; + S.c = c; + S.idx = num; + S.cc = c; + S.brent = false; + start(S); + } + const P = S.run, end = INTRO + (P.beats.length - 1) * P.beat; + // The hold after a find runs at no less than Speed 1, so a slow knob + // still moves on to the next number within 6.4 s. + S.m += d * (S.m > end ? max(1, S.speed) : S.speed) * 0.5; + if (S.m > end + HOLD) next(S); +} + +// ---- Drawing into a float RGB buffer. +function add(buf, x, y, c, a) { + if (x < 0 || x >= W || y < 0 || y >= H || a <= 0) return; + const p = (y * W + x) * 3; + buf[p] += c[0] * a; + buf[p + 1] += c[1] * a; + buf[p + 2] += c[2] * a; +} + +function put(buf, x, y, c, a) { + if (x < 0 || x >= W || y < 0 || y >= H) return; + const p = (y * W + x) * 3; + buf[p] = c[0] * a; + buf[p + 1] = c[1] * a; + buf[p + 2] = c[2] * a; +} + +function disc(buf, cx, cy, r, c, a) { + for (let y = floor(cy - r - 1); y <= cy + r + 1; y++) + for (let x = floor(cx - r - 1); x <= cx + r + 1; x++) { + const e = sqrt((x - cx) ** 2 + (y - cy) ** 2); + add(buf, x, y, c, a * cl(r + 0.5 - e, 0, 1)); + } +} + +function ring(buf, cx, cy, r, c, a, w) { + for (let y = floor(cy - r - 2); y <= cy + r + 2; y++) + for (let x = floor(cx - r - 2); x <= cx + r + 2; x++) { + const e = abs(sqrt((x - cx) ** 2 + (y - cy) ** 2) - r); + add(buf, x, y, c, a * cl(w + 0.5 - e, 0, 1)); + } +} + +const textWidth = (s, sc) => s.length * 4 * sc - sc; + +// mode 0 adds light, mode 1 punches the glyph dark out of what is there. +function text(buf, s, x0, y0, sc, c, a, mode) { + for (let k = 0; k < s.length; k++) { + const g = GL[s[k]]; + if (!g) continue; + for (let gy = 0; gy < 5; gy++) + for (let gx = 0; gx < 3; gx++) { + if (g[gy * 3 + gx] !== "1") continue; + for (let yy = 0; yy < sc; yy++) + for (let xx = 0; xx < sc; xx++) { + const x = x0 + k * 4 * sc + gx * sc + xx, y = y0 + gy * sc + yy; + if (mode) put(buf, x, y, c, a); + else add(buf, x, y, c, a); + } + } + } +} + +// A point on the rho at fractional node u: the tail up the stem, then the +// loop, entered at its leftmost point going up and round clockwise. +function along(P, u, o) { + if (u < P.mu) { + o[0] = SX; + o[1] = YB - ((YB - CY) * u) / P.mu; + } else { + const a = PI + (TAU * (u - P.mu)) / P.lam; + o[0] = CX + R * cos(a); + o[1] = CY + R * sin(a); + } + return o; +} + +export function draw(display, params) { + const S = params.s; + if (!S || !S.run) return; + const P = S.run, buf = S.buf, pos = S.pos; + buf.fill(0); + const CB = hsv(S.hue + HUE_BEAD, 0.75, 1, S.cb), CT = hsv(S.hue + HUE_T, 0.8, 1, S.ct), CH = hsv(S.hue + HUE_H, 0.85, 1, S.ch); + const white = [1, 1, 1], pale = [0.82, 0.86, 0.95]; + + // Where the run is: beat k, the share f of it gone, the movement eased. + const nb = P.beats.length, tm = S.m - INTRO; + let k = 0, f = 1; + if (tm >= 0) { + k = min(nb - 1, 1 + floor(tm / P.beat)); + f = k === nb - 1 && tm >= (nb - 1) * P.beat ? 1 : (tm - (k - 1) * P.beat) / P.beat; + } + const e = smooth(f / MOVE), b0 = P.beats[max(0, k - 1)], b1 = P.beats[k]; + const tu = k === 0 ? 0 : b0.t + (b1.t - b0.t) * e, hu = k === 0 ? 0 : b0.h + (b1.h - b0.h) * e; + const shown = k === 0 ? P.beats[0] : f >= MOVE ? b1 : b0; + const found = k === nb - 1 && f >= MOVE; + const since = found ? S.m - INTRO - (nb - 2) * P.beat - MOVE * P.beat : -1; + const flash = found ? cl(1 - since / 0.7, 0, 1) : 0; + + // The string: the loop, and the stem when there is a tail. + const loopA = 0.2 + (found ? 0.25 + 0.55 * flash : 0); + ring(buf, CX, CY, R, found ? white : CB, loopA, 0.45); + if (P.mu > 0) for (let y = CY; y <= YB; y++) add(buf, SX, y, CB, 0.22); + + // The beads, lit once the hare has reached them, each with x_i mod p. + const nodes = P.mu + P.lam; + for (let i = 0; i < nodes; i++) { + along(P, i, pos); + const x = pos[0], y = pos[1], r = i < P.mu ? P.rt : P.rl; + if (hu + 1e-6 >= i) { + disc(buf, x, y, r, CB, 0.82); + const s = String(P.lab[i]); + if (r >= 4.4) text(buf, s, round(x - textWidth(s, 1) / 2), round(y - 2.5), 1, CB, 0.05, 1); + } else ring(buf, x, y, r - 0.5, CB, 0.3, 0.45); + } + + // The meeting bead flashes white with a ring running out from it. + if (found) { + along(P, P.beats[nb - 1].t, pos); + const r = P.beats[nb - 1].t < P.mu ? P.rt : P.rl; + disc(buf, pos[0], pos[1], r + 1, white, 0.9 * flash); + if (since < 1.2) ring(buf, pos[0], pos[1], r + 3 + since * 26, white, 0.7 * (1 - since / 1.2), 0.6); + } + + // Tortoise and hare: rings round their beads. + along(P, tu, pos); + ring(buf, pos[0], pos[1], P.rl + 2.2, CT, 1, 0.55); + along(P, hu, pos); + ring(buf, pos[0], pos[1], P.rl + 3.8, CH, 1, 0.55); + + // The gcd in the middle of the loop. + const g = shown.d || 1; + text(buf, "GCD", CX - 5, CY - 13, 1, pale, 0.55, 0); + if (P.fail && found) { + // n itself is four digits wide and reaches the rings on the loop, so it + // gets a dark field one LED round it to stay whole. + const s = String(g), x0 = CX - textWidth(s, 2) / 2; + for (let y = CY - 5; y <= CY + 6; y++) for (let x = x0 - 1; x <= x0 + textWidth(s, 2); x++) put(buf, x, y, white, 0); + text(buf, s, x0, CY - 4, 2, white, 0.5 + 0.5 * flash, 0); + } else { + const s = String(g), sc = 3; + text(buf, s, round(CX - textWidth(s, sc) / 2), CY - 5, sc, found ? CB : pale, found ? 0.6 + 0.4 * flash : 0.5, 0); + if (found) text(buf, s, round(CX - textWidth(s, sc) / 2), CY - 5, sc, white, 0.6 * flash, 0); + } + + // The readout beside the stem: what the algorithm sees modulo n. + const RX = 20; + const ns = String(P.n); + text(buf, ns, RX + 23 - textWidth(ns, 2) / 2, 66, 2, pale, 0.85, 0); + // f(x) = x^2 + c, the 2 a small raised one. + text(buf, "X", RX + 6, 81, 1, pale, 0.7, 0); + add(buf, RX + 10, 79, pale, 0.7); + add(buf, RX + 11, 79, pale, 0.7); + add(buf, RX + 11, 80, pale, 0.7); + add(buf, RX + 10, 81, pale, 0.7); + add(buf, RX + 10, 82, pale, 0.7); + add(buf, RX + 11, 82, pale, 0.7); + text(buf, "+" + P.c, RX + 13, 81, 1, pale, 0.7, 0); + const evals = (k === 0 ? 0 : f >= MOVE ? k : k - 1) * (P.brent ? 1 : 3); + text(buf, P.brent ? "BRENT" : "FLOYD", RX + 2, 90, 1, pale, 0.75, 0); + const es = String(evals); + text(buf, es, RX + 44 - textWidth(es, 1), 90, 1, pale, 0.6, 0); + const xt = P.xs[shown.t], xh = P.xs[shown.h]; + text(buf, "T", RX + 2, 100, 1, CT, 1, 0); + text(buf, String(xt), RX + 44 - textWidth(String(xt), 1), 100, 1, CT, 0.95, 0); + text(buf, "H", RX + 2, 107, 1, CH, 1, 0); + text(buf, String(xh), RX + 44 - textWidth(String(xh), 1), 107, 1, CH, 0.95, 0); + text(buf, "D", RX + 2, 114, 1, pale, 0.75, 0); + const ds = String(abs(xt - xh)); + text(buf, ds, RX + 44 - textWidth(ds, 1), 114, 1, pale, 0.75, 0); + if (found) { + const s = P.fail ? "FAIL C+1" : "=" + P.p + "*" + P.q; + text(buf, s, RX + 44 - textWidth(s, 1), 122, 1, P.fail ? CH : CB, 0.7 + 0.3 * flash, 0); + } + + for (let p = 0; p < N; p++) + display.setPixel(p % W, floor(p / W), round(cl(buf[p * 3], 0, 1) * 255), round(cl(buf[p * 3 + 1], 0, 1) * 255), round(cl(buf[p * 3 + 2], 0, 1) * 255)); +} From cbee34040a03314c5b375053529f2c0a6f186b68 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 31 Oct 2026 20:17:18 +0000 Subject: [PATCH 4/9] =?UTF-8?q?=ED=8C=A8=ED=84=B4=20=EC=97=B0=EA=B5=AC=208?= =?UTF-8?q?1=ED=8C=90:=20Dehn=20Twist,=20Baudot=20Tape?= 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_01Bc4Jkpfqox342J4FE4ZD1C --- .../pattern-study/patterns/baudot-tape.js | 308 ++++++++++++ incoming/pattern-study/patterns/dehn-twist.js | 444 ++++++++++++++++++ 2 files changed, 752 insertions(+) create mode 100644 incoming/pattern-study/patterns/baudot-tape.js create mode 100644 incoming/pattern-study/patterns/dehn-twist.js diff --git a/incoming/pattern-study/patterns/baudot-tape.js b/incoming/pattern-study/patterns/baudot-tape.js new file mode 100644 index 00000000..6358c2cc --- /dev/null +++ b/incoming/pattern-study/patterns/baudot-tape.js @@ -0,0 +1,308 @@ +// ===== Patternflow pattern ===== +// Title: Baudot Tape +// Family: type +// Idea: Five-hole teleprinter tape runs up the whole height of the panel, each character arriving down the line as five pulses that set the punch at the bottom, punched as one row of holes with the small feed hole between the third and the fourth, and typed beside its row as it is punched, so the message reads down the panel next to its own code and passes under the reader at the top; line noise flips a pulse now and then, and when the pulse it flips belongs to a shift code the rest of the line comes out in the wrong shift, 1234567890 as QWERTYUIOP. +// Author: patternflow-lab +// Date: 2026-10-31 +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Lineage: the five-unit code of Emile Baudot (1870s), rearranged by Donald Murray for a keyboard and punched tape (1901), in the letters and figures of International Telegraph Alphabet No. 2; 5 by 7 capitals and 3 by 5 tags drawn for this pattern, pattern round 81 +// Round: 81 +// =============================== +// @matrix 64x128 +// @knobs Hue=0..1, Speed=0.2..3, Message=0..2, Noise=0..1 +// +// Knob 1: Hue. Turns the paper, the ink and the marks round the wheel together. At 0 the tape is cream, the typed characters pale azure, the line hits pink and the punch's selectors gold; at 0.25 the tape is pale green and the ink violet, at 0.5 pale blue tape with peach ink, at 0.75 lilac tape with green ink. +// Knob 2: Speed. Characters a second, 1.5 times the knob: 3 at 2, so a row takes 0.33 s and the tape climbs 24 LEDs a second; 0.3 at 0.2 and 4.5 at 3. Each character spends half its time arriving, a pulse at a time, and the rest punched and moving up one row of 8 LEDs. +// Knob 3: Message. 0 the teleprinter test line THE QUICK BROWN FOX JUMPS OVER THE LAZY DOG 1234567890, 1 the RYRY test, R 01010 and Y 10101 in turn so the tape is a checkerboard, 2 a telegram, ARRIVE FRIDAY 10.45 STOP MEET TRAIN; after each the next one follows. Every message opens with a letters shift. Turning it starts the new message on the next row. +// Knob 4: Noise. The chance a character takes a line hit that flips one of its five pulses, 0.6 times the knob: 3.6% at the opening 0.06, 15% at 0.25, 60% at 1. A hit row gets a pink tick in the margin and a pink ring round the flipped hole, and whatever the receiver types wrong is pink. A hit on the figures shift before 1234567890 leaves the receiver in letters, and the digits come out QWERTYUIOP; a hit on the letters shift that opens the RYRY test leaves it in figures, and it comes out 464646. + +// ITA2 sends a character as five pulses, each mark or space; a punched +// tape keeps them as five holes across one row, bit 1 on the left, with +// a smaller feed hole between bit 3 and bit 4 that the sprocket pulls the +// tape by. Thirty-two codes are not enough for letters and figures, so +// two of them are shifts: 11011 puts the receiver in figures and 11111 +// back in letters, and every other code means a letter or a figure by +// which shift came last. Space 00100, carriage return 00010 and line feed +// 01000 mean the same in both. The figures drawn here are the digits, on +// the top row of letters Q W E R T Y U I O P, and the marks on A B C K L +// M N S V X Z; J in figures rings the bell, drawn as a bell, and D F G H +// in figures get no figure here and are drawn as an empty box. +// +// The receiver keeps only the shift, so a single flipped pulse in a shift +// code changes how everything after it reads, up to the next shift code. +// Each row is decoded as it is punched, by the receiver's own shift, and +// compared with what was sent: what does not match is typed in pink. +// +// The tape climbs: rows are punched at the bottom, under the punch at row +// 106, and a row that has climbed twelve rows stops under the reader at +// row 10, whose pins drop through the holes they find. + +const W = 64, H = 128, N = W * H; +const { floor, min, round, sqrt, abs } = Math; +const cl = (v, a, b) => (v < a ? a : v > b ? b : v); +const smooth = (u) => (u <= 0 ? 0 : u >= 1 ? 1 : u * u * (3 - 2 * u)); + +// Letters shift, bit 1 first. +const LT = { + A: "11000", B: "10011", C: "01110", D: "10010", E: "10000", F: "10110", G: "01011", H: "00101", I: "01100", + J: "11010", K: "11110", L: "01001", M: "00111", N: "00110", O: "00011", P: "01101", Q: "11101", R: "01010", + S: "10100", T: "00001", U: "11100", V: "01111", W: "11001", X: "10111", Y: "10101", Z: "10001", +}; +// Figures shift on the same codes; "*" is the bell, "#" an empty box. +const FG = { + Q: "1", W: "2", E: "3", R: "4", T: "5", Y: "6", U: "7", I: "8", O: "9", P: "0", + A: "-", B: "?", C: ":", K: "(", L: ")", M: ".", N: ",", S: "'", V: "=", X: "/", Z: "+", J: "*", + D: "#", F: "#", G: "#", H: "#", +}; +const SP = 4, CR = 2, LF = 8, FIGS = 27, LTRS = 31, NUL = 0; +const C2L = [], C2F = [], F2C = {}; +for (const ch in LT) { + const c = parseInt(LT[ch], 2); + C2L[c] = ch; + C2F[c] = FG[ch]; + if (FG[ch] !== "#" && FG[ch] !== "*") F2C[FG[ch]] = c; +} + +const MSG = [ + "THE QUICK BROWN FOX JUMPS OVER THE LAZY DOG 1234567890", + "RYRYRYRYRYRYRYRYRYRYRYRY", + "ARRIVE FRIDAY 10.45 STOP MEET TRAIN", +]; + +// 5 by 7 capitals, figures and marks: seven rows, each a base-32 digit +// whose five bits are the row's LEDs, left first. +const BIG = { + A: "ehhvhhh", B: "uhhuhhu", C: "ehggghe", D: "uhhhhhu", E: "vgguggv", F: "vgguggg", G: "ehgjhhe", H: "hhhvhhh", + I: "e44444e", J: "72222ic", K: "hikokih", L: "ggggggv", M: "hrllhhh", N: "hhpljhh", O: "ehhhhhe", P: "uhhuggg", + Q: "ehhhlid", R: "uhhukih", S: "fgge11u", T: "v444444", U: "hhhhhhe", V: "hhhhha4", W: "hhhllla", X: "hha4ahh", + Y: "hha4444", Z: "v1248gv", 0: "ehjlphe", 1: "4c4444e", 2: "eh1248v", 3: "v2421he", 4: "26aiv22", 5: "vgu11he", + 6: "68guhhe", 7: "v124888", 8: "ehhehhe", 9: "ehhf12c", ".": "00000cc", ",": "0000c48", "-": "000v000", + "?": "eh12404", "'": "4480000", ":": "0cc0cc0", "(": "2488842", ")": "8422248", "/": "01248g0", "=": "00v0v00", + "+": "044v440", "#": "0eaaae0", "*": "4eeev04", +}; +// 3 by 5 letters for the tags of the codes that type nothing: five rows, +// each an octal digit. +const SMALL = { C: "74447", F: "74644", G: "74557", I: "72227", L: "44447", R: "65655", T: "72222" }; + +// Geometry: row pitch, the punch and reader rows, the five hole columns +// and the feed hole's, the tape's edges, the typed column. +const RP = 8, YP = 106, YR = 10, HX = [8, 14, 20, 32, 38], FX = 26, TX0 = 3, TX1 = 43, GX = 51; +const KEEP = 20; + +// A message as rows: what is punched and what the row should type. +function encode(text) { + const rows = [{ code: LTRS, want: "LTR" }]; + let fig = false; + for (const ch of text) { + if (ch === " ") rows.push({ code: SP, want: " " }); + else if (LT[ch]) { + if (fig) rows.push({ code: LTRS, want: "LTR" }), (fig = false); + rows.push({ code: parseInt(LT[ch], 2), want: ch }); + } else if (F2C[ch] != null) { + if (!fig) rows.push({ code: FIGS, want: "FIG" }), (fig = true); + rows.push({ code: F2C[ch], want: ch }); + } + } + rows.push({ code: CR, want: "CR" }, { code: LF, want: "LF" }, { code: NUL, want: "" }, { code: NUL, want: "" }); + return rows; +} +const ENC = MSG.map(encode); + +function hsv(h, s, v) { + h = (h - floor(h)) * 6; + const i = floor(h) % 6, f = h - floor(h), p = v * (1 - s), q = v * (1 - s * f), t = v * (1 - s * (1 - f)); + return [[v, t, p], [q, v, p], [p, v, t], [p, q, v], [t, p, v], [v, p, q]][i]; +} + +// The receiver: the row as it came down the line, decoded in the shift it +// is in. +function receive(S, sent, n) { + let flip = 0; + if (Math.random() < S.noise) flip = 1 << floor(Math.random() * 5); + const code = sent.code ^ flip; + let key; + if (code === LTRS) (S.fig = false), (key = "LTR"); + else if (code === FIGS) (S.fig = true), (key = "FIG"); + else if (code === SP) key = " "; + else if (code === CR) key = "CR"; + else if (code === LF) key = "LF"; + else if (code === NUL) key = ""; + else key = S.fig ? C2F[code] : C2L[code]; + return { code, flip, key, bad: key !== sent.want, n, age: 0 }; +} + +function queue(S, m) { + S.m = ((m % MSG.length) + MSG.length) % MSG.length; + S.q = ENC[S.m].slice(); +} + +function next(S) { + if (!S.q.length) queue(S, S.m + 1); + S.cur = S.q.shift(); + S.curRx = null; +} + +export function setup(params) { + params.s = { + hue: 0, noise: 0.1, knob: -1, m: 0, q: [], rows: [], cur: null, curRx: null, fig: false, + ph: 0, T: 0, base: 0, buf: new Float32Array(N * 3), + }; +} + +export function update(dt, input, params) { + if (!params.s) setup(params); + const S = params.s, k = input.knobValues || [0, 2, 0, 0.06], d = cl(dt || 0, 0, 0.1); + S.hue = k[0] || 0; + const speed = cl(k[1] == null ? 2 : k[1], 0.2, 3); + S.noise = 0.6 * cl(k[3] == null ? 0.06 : k[3], 0, 1); + const km = round(cl(k[2] || 0, 0, 2)); + if (km !== S.knob) { + if (S.knob < 0) { + // A fresh tape opens on the end of the message before, already + // punched and typed, with the receiver in letters. + const before = ENC[(km + MSG.length - 1) % MSG.length]; + const tail = before.slice(before.length - 15); + tail.forEach((r, i) => S.rows.push(receive(S, r, i - tail.length))); + for (const r of S.rows) r.age = 9; + queue(S, km); + next(S); + } else queue(S, km); + S.knob = km; + } + const rate = 1.5 * speed, was = S.ph; + S.ph += d * rate; + for (const r of S.rows) r.age += d; + // Half way through its time the row is punched and typed. + if (!S.curRx) S.curRx = receive(S, S.cur, S.base); + if (was < 0.5 && S.ph >= 0.5) { + S.curRx.age = 0; + S.rows.push(S.curRx); + if (S.rows.length > KEEP) S.rows.shift(); + } + // Then the tape moves up one row, from 0.55 to 0.95 of the time. + if (S.ph >= 1) { + S.ph -= 1; + S.base++; + next(S); + } + S.T = S.base + smooth((S.ph - 0.55) / 0.4); +} + +function px(buf, x, y, c, a) { + if (a <= 0 || x < 0 || x >= W || y < 0 || y >= H) return; + const p = (y * W + x) * 3; + if (a > 1) a = 1; + buf[p] += (c[0] - buf[p]) * a; + buf[p + 1] += (c[1] - buf[p + 1]) * a; + buf[p + 2] += (c[2] - buf[p + 2]) * a; +} + +// A disc (or a ring of width w) centred on (x, y). +function disc(buf, x, y, r, c, a, w) { + for (let Y = floor(y - r - 1); Y <= y + r + 1; Y++) + for (let X = floor(x - r - 1); X <= x + r + 1; X++) { + const e = sqrt((X + 0.5 - x) ** 2 + (Y + 0.5 - y) ** 2); + const cov = w ? cl(w / 2 + 0.5 - abs(e - r), 0, 1) : cl(r + 0.5 - e, 0, 1); + px(buf, X, Y, c, a * cov); + } +} + +// A glyph of rows (base-32 for the capitals, octal for the tags) with its +// top at a fractional row, the fraction shared between two LED rows. +// Bold doubles each lit LED to the right, as a typewheel's heavy face. +function glyph(buf, rows, base, wide, x0, top, c, a, bold) { + const y0 = floor(top), f = top - y0; + for (let r = 0; r < rows.length; r++) { + const bits = parseInt(rows[r], base); + for (let j = 0; j < wide; j++) + if ((bits >> (wide - 1 - j)) & 1) + for (let b = 0; b <= bold; b++) { + px(buf, x0 + j + b, y0 + r, c, a * (1 - f)); + px(buf, x0 + j + b, y0 + r + 1, c, a * f); + } + } +} + +function typed(buf, key, top, c, a) { + if (!key || key === " ") return; + if (BIG[key]) return glyph(buf, BIG[key], 32, 5, GX, top, c, a, 1); + // A tag: two or three small letters, centred on the capitals' column. + const w = key.length * 4 - 1; + let x = GX + 3 - floor(w / 2); + for (const ch of key) glyph(buf, SMALL[ch], 8, 3, x, top + 1, c, a, 0), (x += 4); +} + +export function draw(display, params) { + const S = params.s; + if (!S) return; + const buf = S.buf, h = S.hue; + buf.fill(0); + const paper = hsv(h + 0.13, 0.3, 0.72), ink = hsv(h + 0.55, 0.42, 1), dim = hsv(h + 0.55, 0.3, 0.55); + const hit = hsv(h + 0.95, 0.65, 1), gold = hsv(h + 0.12, 0.7, 1), steel = [0.42, 0.44, 0.48], white = [1, 1, 1]; + + // The paper, a little darker at its edges. + for (let y = 0; y < H; y++) + for (let x = TX0; x <= TX1; x++) { + const e = min(x - TX0, TX1 - x), v = e === 0 ? 0.62 : e === 1 ? 0.85 : 1, p = (y * W + x) * 3; + buf[p] = paper[0] * v; + buf[p + 1] = paper[1] * v; + buf[p + 2] = paper[2] * v; + } + + // The punched rows climbing, their holes, their line hits, and beside + // each the character it typed. + const black = [0, 0, 0]; + let under = null; + for (const r of S.rows) { + const y = YP - (S.T - r.n) * RP; + if (y < -RP || y > H + RP) continue; + if (abs(y - YR) < 0.5) under = r; + disc(buf, FX + 0.5, y + 0.5, 1.05, black, 1); + for (let b = 0; b < 5; b++) { + const on = (r.code >> (4 - b)) & 1; + if (on) disc(buf, HX[b] + 0.5, y + 0.5, 2.1, black, 1); + if ((r.flip >> (4 - b)) & 1) disc(buf, HX[b] + 0.5, y + 0.5, 2.8, hit, 1, 1.1); + } + // A line hit also gets a pink tick in the margin beside its row. + if (r.flip) for (let dy = -2; dy <= 3; dy++) for (let x = TX1 + 2; x <= TX1 + 3; x++) px(buf, x, round(y) + dy, hit, 0.85); + const fresh = smooth(1 - r.age * 4), tag = r.key.length > 1; + const base = r.bad ? hit : tag ? dim : ink; + const c = [base[0] + (1 - base[0]) * fresh, base[1] + (1 - base[1]) * fresh, base[2] + (1 - base[2]) * fresh]; + typed(buf, r.key, y - 3, c, 1); + } + + // The punch: two steel bars across the tape, and between them the row + // being received, each mark pulse lighting its selector as it arrives. + for (let x = TX0 - 2; x <= TX1 + 2; x++) { + px(buf, x, YP - 3, steel, 1); + px(buf, x, YP + 4, steel, 1); + px(buf, x, YR - 3, steel, 1); + px(buf, x, YR + 4, steel, 1); + } + for (const yy of [YP - 3, YP + 4, YR - 3, YR + 4]) { + px(buf, TX0 - 3, yy, steel, 0.6); + px(buf, TX1 + 3, yy, steel, 0.6); + } + if (S.curRx && S.ph < 0.5) { + for (let b = 0; b < 5; b++) { + const at = 0.07 + 0.07 * b; + if (S.ph >= at && (S.curRx.code >> (4 - b)) & 1) disc(buf, HX[b] + 0.5, YP + 0.5, 0.9, gold, smooth((S.ph - at) / 0.05)); + } + } + // The punch fires: a flash round the new holes. + const last = S.rows[S.rows.length - 1]; + if (last && last.age < 0.25) { + const y = YP - (S.T - last.n) * RP, a = 1 - last.age / 0.25; + for (let b = 0; b < 5; b++) if ((last.code >> (4 - b)) & 1) disc(buf, HX[b] + 0.5, y + 0.5, 2.4, white, a, 0.8); + } + + // The reader: a pin over each hole column, dropped through where it + // finds a hole, resting on the paper where it does not. + for (let b = 0; b < 5; b++) { + const through = under && (under.code >> (4 - b)) & 1; + disc(buf, HX[b] + 0.5, YR + 0.5, through ? 1.1 : 0.7, through ? gold : steel, 1); + } + + for (let i = 0; i < N; i++) + display.setPixel(i % W, floor(i / W), round(cl(buf[i * 3], 0, 1) * 255), round(cl(buf[i * 3 + 1], 0, 1) * 255), round(cl(buf[i * 3 + 2], 0, 1) * 255)); +} diff --git a/incoming/pattern-study/patterns/dehn-twist.js b/incoming/pattern-study/patterns/dehn-twist.js new file mode 100644 index 00000000..bbc2ce4d --- /dev/null +++ b/incoming/pattern-study/patterns/dehn-twist.js @@ -0,0 +1,444 @@ +// ===== Patternflow pattern ===== +// Title: Dehn Twist +// Family: topology +// Idea: A shaded torus turns slowly in the upper half carrying a gold closed curve, and below it the flat square with opposite sides glued shows the same curve as straight segments; a thin green annulus round the tube lights up, its inside is dragged one full turn round the tube while its cut edge lets the curve's ends slide apart and meet again, and the curve relaxes to a straight one of class (p, q + p), so on the square it gains p more slanted segments and on the torus it winds p more times round the tube, until after a few twists it twists back as many times and the next curve takes its place. +// Author: patternflow-lab +// Date: 2026-10-31 +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Lineage: Dehn twist (Max Dehn, 1938), and Lickorish's theorem that Dehn twists generate the mapping class group of a closed orientable surface (W. B. R. Lickorish, 1962), pattern round 81 +// Round: 81 +// =============================== +// @matrix 64x128 +// @knobs Hue=0..1, Speed=0.2..3, Curve=0..5, Twists=-1..1 +// +// Knob 1: Hue. Turns every colour round the wheel together. At 0 the torus and the square are blue, the curve gold and the annulus green; at 0.25 the torus is magenta, the curve green and the annulus blue, at 0.5 the torus is amber and the curve blue, at 0.75 the torus is green and the curve pink. +// Knob 2: Speed. At 2 a twist takes 2.5 s: 0.3 s for the annulus to light, 1.1 s of drag, 0.15 s to glue, 0.55 s to relax and 0.4 s held; every time goes as 2 / Speed, so 25 s a twist at 0.2 and 1.7 s at 3. The torus turns once in 30 s at 2. +// Knob 3: Curve. The class (p, q) the curve starts from, p times round the long way and q times round the tube: 0 (1, 0), 1 (2, 1), 2 (1, -2), 3 (3, 1), 4 (2, -3), 5 (3, -1). After it has climbed and come back the next one on the list takes its place. Turning the knob starts the new curve at once. +// Knob 4: Twists. How many twists the curve climbs before it twists back, and which way. From 0 to 1 each twist drags the annulus up the square and takes (p, q) to (p, q + p): 2 twists up to a third, 3 up to two thirds and 4 beyond. Below 0 it drags the other way, (p, q) to (p, q - p), with the same counts by the knob's size. A climb also stops early when the next curve would cut the square into more than 10 segments. + +// The torus is the square [0,1) x [0,1) with its left edge glued to its +// right and its top to its bottom. U runs across the square and once round +// the long way of the torus; V runs up the square and once round the tube. +// A straight line through the square with slope q/p closes up after going +// p times across and q times up: a simple closed curve of class (p, q) +// whenever p and q have no common factor. On the square it crosses the +// side edges p times and the top edge |q| times, so it is cut into p + |q| +// segments. +// +// A meridian is a circle U = a, once round the tube. The Dehn twist about +// it cuts the torus along that circle, turns one side a full turn and +// glues it back: on the annulus a <= U < a + w it is +// +// (U, V) -> (U, V + g((U - a) / w)), g rising smoothly from 0 to 1, +// +// and the identity everywhere else. Each time the curve crosses the +// annulus it is carried once round the tube, so (p, q) becomes (p, q + p); +// the inverse twist gives (p, q - p). The meridian (0, 1) itself is left +// alone, so every curve here has p >= 1. In the matrix picture the twist +// is [[1, 0], [1, 1]] acting on (p, q). +// +// While the twist is under way the drag goes as s from 0 to 1: inside the +// annulus V moves by s g, outside it does not, so the curve stays whole at +// the annulus' left edge and comes apart at its right edge, where the cut +// is, by s of a turn. At s = 1 the ends meet again. The green stripes on +// the annulus are lines of the surface itself, V - s g constant, so they +// shear into helices while it turns. +// +// The twisted curve is then pulled straight. Written as V(tau) over +// tau in [0, 1) with U = p tau + c1, the twisted curve has +// V(tau + k/p) - V(tau) = k q / p + k for k = 1, ..., p - 1, and so has +// the straight line of class (p, q + p). Every mix of the two keeps that, +// and none of those differences is a whole number, so the straight-line +// homotopy between them passes only through simple curves of the class. +// +// The torus is ray marched once, at load: the view is orthographic and +// fixed, so each LED keeps its hit point, normal and (U, V) for good, and +// the turning only shifts U. The curve is drawn as a chain of 1600 dots, +// each tested against the torus' depth; the parts on the far side show +// faintly through. + +const W = 64, H = 128, N = W * H; +const { floor, min, max, round, sqrt, sin, cos, atan2, abs, PI } = Math; +const TAU = 2 * PI; +const cl = (v, a, b) => (v < a ? a : v > b ? b : v); +const smooth = (u) => (u <= 0 ? 0 : u >= 1 ? 1 : u * u * (3 - 2 * u)); +const fr = (x) => x - floor(x); + +// The torus: radii, centre on the panel, the view's elevation above its +// equator, and the camera's up and toward-viewer axes in torus space. +const RB = 21, RS = 9, CX = 32, CY = 28.5, EL = (56 * PI) / 180, TOP = 58; +const EY = [0, sin(EL), cos(EL)], EZ = [0, -cos(EL), sin(EL)]; +// The square: side, left and top edge. +const SQ = 56, SX = 4, SY = 67; +// The annulus' width in U, the samples along the curve, and the most +// segments a climb may cut the square into. +const AW = 0.1, NS = 1600, MAXSEG = 10; +const CURVES = [[1, 0], [2, 1], [1, -2], [3, 1], [2, -3], [3, -1]]; +// Seconds at Speed 2. +const PRE = 0.3, DRAG = 1.1, GLUE = 0.15, RELAX = 0.55, POST = 0.4, FADE = 0.35, SPIN = 1 / 30; + +// Light from the upper left, toward the viewer, in torus space. +const LIGHT = (() => { + const c = [-0.45, 0.62, 0.64], n = sqrt(c[0] * c[0] + c[1] * c[1] + c[2] * c[2]); + return [c[0] / n, (c[1] * EY[1] + c[2] * EZ[1]) / n, (c[1] * EY[2] + c[2] * EZ[2]) / n]; +})(); +const HALF = (() => { + const h = [LIGHT[0] + EZ[0], LIGHT[1] + EZ[1], LIGHT[2] + EZ[2]], n = sqrt(h[0] * h[0] + h[1] * h[1] + h[2] * h[2]); + return [h[0] / n, h[1] / n, h[2] / n]; +})(); + +// Ray march the torus once: for each LED of the upper part, whether it +// sees the torus, and where. +const TH = new Uint8Array(W * TOP), TU = new Float32Array(W * TOP), TV = new Float32Array(W * TOP); +const TZ = new Float32Array(W * TOP), TL = new Float32Array(W * TOP); +for (let py = 0; py < TOP; py++) + for (let px = 0; px < W; px++) { + const X = px + 0.5 - CX, Y = CY - (py + 0.5), i = py * W + px; + let t = 45; + for (let k = 0; k < 200 && t > -45; k++) { + const x = X, y = Y * EY[1] + t * EZ[1], z = Y * EY[2] + t * EZ[2]; + const rho = sqrt(x * x + y * y), q = rho - RB, d = sqrt(q * q + z * z) - RS; + if (d < 0.002) { + const nx = (x - (RB * x) / rho) / RS, ny = (y - (RB * y) / rho) / RS, nz = z / RS; + const lam = max(0, nx * LIGHT[0] + ny * LIGHT[1] + nz * LIGHT[2]); + const sp = max(0, nx * HALF[0] + ny * HALF[1] + nz * HALF[2]); + TH[i] = 1; + TU[i] = atan2(y, x) / TAU; + TV[i] = fr((atan2(z, q) + PI / 2) / TAU); + TZ[i] = y * EZ[1] + z * EZ[2]; + TL[i] = 0.2 + 0.68 * lam + 0.32 * sp ** 24; + break; + } + t -= d; + } + } + +// Small figures for the class label, rows of three bits, top first. +const GLYPH = { + 0: "111101101101111", 1: "010110010010111", 2: "111001111100111", 3: "111001011001111", 4: "101101111001001", + 5: "111100111001111", 6: "111100111101111", 7: "111001010010010", 8: "111101111101111", 9: "111101111001111", + "-": "000000111000000", "(": "010100100100010", ")": "010001001001010", ",": "000000000010100", +}; + +function hsv(h, s, v) { + h = fr(h) * 6; + const i = floor(h) % 6, f = h - floor(h), p = v * (1 - s), q = v * (1 - s * f), t = v * (1 - s * (1 - f)); + return [[v, t, p], [q, v, p], [p, v, t], [p, q, v], [t, p, v], [v, p, q]][i]; +} + +// A soft dot of radius r into a coverage buffer, kept to rows y0..y1 and +// columns x0..x1. +function dot(A, x, y, r, a, x0, x1, y0, y1) { + const ax = max(x0, floor(x - r - 0.5)), bx = min(x1, floor(x + r + 0.5)); + const ay = max(y0, floor(y - r - 0.5)), by = min(y1, floor(y + r + 0.5)); + for (let yy = ay; yy <= by; yy++) + for (let xx = ax; xx <= bx; xx++) { + const dx = xx + 0.5 - x, dy = yy + 0.5 - y, c = cl(r + 0.5 - sqrt(dx * dx + dy * dy), 0, 1) * a, j = yy * W + xx; + if (c > A[j]) A[j] = c; + } +} + +function begin(S, ci) { + S.ci = ((ci % CURVES.length) + CURVES.length) % CURVES.length; + S.p = CURVES[S.ci][0]; + S.q = CURVES[S.ci][1]; + S.c1 = 0.1; + S.c2 = 0.5 / S.p; + S.m = 0; + S.up = true; +} + +// What comes after a twist: another the same way, the way back, or the +// next curve. +function plan(S) { + if (S.up) { + const d = S.n >= 0 ? 1 : -1, q2 = S.q + d * S.p; + if ((S.m === 0 || Math.sign(S.m) === d) && abs(S.m) < abs(S.n) && S.p + abs(q2) <= MAXSEG) return go(S, d); + S.up = false; + } + if (S.m !== 0) return go(S, -Math.sign(S.m)); + S.mode = "out"; + S.tm = 0; +} + +function go(S, d) { + S.dir = d; + S.mode = "pre"; + S.tm = 0; + // The annulus goes where the torus' left side will be halfway through + // the drag. Seen from the front a meridian is a vertical line and a turn + // round it hides; seen from the side it is an ellipse 18 LEDs wide. + S.a = fr(0.5 - (S.spin + SPIN * (PRE + DRAG / 2)) - AW / 2); +} + +export function setup(params) { + params.s = { + hue: 0, spin: 0.08, ci: -1, knob: -1, n: 2, mode: "pre", tm: 0, s: 0, lam: 0, hl: 0, fade: 1, glow: 0, + p: 1, q: 0, c1: 0, c2: 0, a: 0, dir: 1, m: 0, up: true, c2n: 0, c2a: 0, c2b: 0, + buf: new Float32Array(N * 3), A: new Float32Array(N), B: new Float32Array(N), + cu: new Float32Array(NS), cv: new Float32Array(NS), P: [0, 0, 0, 0], + }; +} + +export function update(dt, input, params) { + if (!params.s) setup(params); + const S = params.s, k = input.knobValues || [0, 2, 0, 0.06], d = cl(dt || 0, 0, 0.1); + S.hue = k[0] || 0; + const speed = cl(k[1] == null ? 2 : k[1], 0.2, 3), t4 = cl(k[3] == null ? 0.06 : k[3], -1, 1); + S.n = (t4 < 0 ? -1 : 1) * min(4, 2 + floor(abs(t4) * 3)); + const kc = round(cl(k[2] || 0, 0, 5)); + if (kc !== S.knob) { + S.knob = kc; + S.fade = 1; + S.glow = 0; + begin(S, kc); + plan(S); + } + const h = (d * speed) / 2; + S.spin = fr(S.spin + SPIN * h); + S.tm += h; + S.glow = max(0, S.glow - h * 3); + if (S.mode === "pre") { + S.hl = smooth(S.tm / PRE); + if (S.tm >= PRE) (S.mode = "drag"), (S.tm -= PRE); + } + if (S.mode === "drag") { + S.s = smooth(S.tm / DRAG); + if (S.tm >= DRAG) (S.mode = "glue"), (S.tm -= DRAG), (S.s = 1), (S.glow = 1); + } + if (S.mode === "glue" && S.tm >= GLUE) { + S.mode = "relax"; + S.tm -= GLUE; + // The straight line the twisted curve relaxes to: same class plus the + // twist, at the offset that matches the twisted curve on average. + let mean = 0; + for (let i = 0; i < NS; i++) { + const x = S.p * (i / NS) + S.c1 - S.a, kk = floor(x), f = x - kk; + mean += kk + (f < AW ? smooth(f / AW) : 1); + } + S.c2n = S.c2 + S.dir * (mean / NS - S.p / 2); + } + if (S.mode === "relax") { + S.lam = smooth(S.tm / RELAX); + if (S.tm >= RELAX) { + S.q += S.dir * S.p; + S.c2 = S.c2a = fr(S.c2n); + // The held line then slides round the tube back to its home offset, + // 0.5 / p up to a multiple of 1 / p, which leaves the curve's set of + // strands the same: a pure translation, so the class does not move. + let e = (0.5 - S.c2a * S.p) % 1; + if (e > 0.5) e -= 1; + if (e < -0.5) e += 1; + S.c2b = S.c2a + e / S.p; + S.m += S.dir; + S.mode = "post"; + S.tm -= RELAX; + S.s = 0; + S.lam = 0; + } + } + if (S.mode === "post") { + S.hl = 1 - smooth(S.tm / POST); + S.c2 = S.c2a + (S.c2b - S.c2a) * smooth(S.tm / POST); + if (S.tm >= POST) { + S.hl = 0; + plan(S); + } + } + if (S.mode === "out") { + S.fade = 1 - smooth(S.tm / FADE); + if (S.tm >= FADE) { + begin(S, S.ci + 1); + S.mode = "in"; + S.tm = 0; + } + } + if (S.mode === "in") { + S.fade = smooth(S.tm / FADE); + if (S.tm >= FADE) { + S.fade = 1; + plan(S); + } + } +} + +// The curve as NS points (U, V), U unwrapped. +function trace(S) { + const { p, q, c1, c2, a, dir } = S, cu = S.cu, cv = S.cv; + const drag = S.mode === "drag" || S.mode === "glue", relax = S.mode === "relax"; + for (let i = 0; i < NS; i++) { + const tau = i / NS, U = p * tau + c1; + let V = q * tau + c2; + const x = U - a, kk = floor(x), f = x - kk, g = f < AW ? smooth(f / AW) : 1; + if (drag) V += dir * S.s * (f < AW ? g : 0); + else if (relax) { + const vt = V + dir * (kk + g), vn = (q + dir * p) * tau + S.c2n; + V = vt + (vn - vt) * S.lam; + } + cu[i] = U; + cv[i] = V; + } +} + +function label(buf, text, cy, c) { + let w = 0; + for (const ch of text) w += (ch === "(" || ch === ")" || ch === "," ? 3 : 4); + let x = round(32 - (w - 1) / 2); + for (const ch of text) { + const g = GLYPH[ch], gw = ch === "(" || ch === ")" || ch === "," ? 2 : 3; + // Narrow glyphs keep the middle and one side of their three columns. + const cols = ch === "(" || ch === "," ? [0, 1] : ch === ")" ? [1, 2] : [0, 1, 2]; + for (let r = 0; r < 5; r++) + for (let j = 0; j < gw; j++) + if (g[r * 3 + cols[j]] === "1") { + const p = ((cy + r) * W + x + j) * 3; + buf[p] = c[0]; + buf[p + 1] = c[1]; + buf[p + 2] = c[2]; + } + x += gw + 1; + } +} + +export function draw(display, params) { + const S = params.s; + if (!S) return; + const buf = S.buf, A = S.A, B = S.B, P = S.P, h = S.hue; + buf.fill(0); + A.fill(0); + B.fill(0); + trace(S); + const torus = hsv(h + 0.6, 0.6, 0.78), curve = hsv(h + 0.13, 0.55, 1), ann = hsv(h + 0.36, 0.7, 1); + const lng = hsv(h + 0.83, 0.35, 0.62), mer = hsv(h + 0.36, 0.55, 0.62); + const spin = S.spin; + + // The curve on the torus, front and back, and on the square. A dot + // within 0.4 LED of the last one drawn adds nothing and is skipped. + let lx = -9, ly = -9, lf = -1, mx0 = -9, my0 = -9; + for (let i = 0; i < NS; i++) { + const U = S.cu[i], V = S.cv[i]; + const t = TAU * (U + spin), ph = TAU * V - PI / 2, rr = RB + RS * cos(ph); + const x = rr * cos(t), y = rr * sin(t), z = RS * sin(ph); + P[0] = CX + x; + P[1] = CY - (y * EY[1] + z * EY[2]); + P[2] = y * EZ[1] + z * EZ[2]; + const facing = cos(ph) * sin(t) * EZ[1] + sin(ph) * EZ[2]; + const ix = floor(P[0]), iy = floor(P[1]), j = iy * W + ix; + const front = ix >= 0 && ix < W && iy >= 0 && iy < TOP && TH[j] ? P[2] >= TZ[j] - 1.6 : facing > 0; + const fb = front ? 1 : 0; + if (fb !== lf || (P[0] - lx) ** 2 + (P[1] - ly) ** 2 > 0.16) { + dot(front ? A : B, P[0], P[1], front ? 1.25 : 0.9, 1, 0, W - 1, 0, TOP - 1); + lx = P[0]; + ly = P[1]; + lf = fb; + } + const qx = SX + fr(U) * SQ, qy = SY + SQ - fr(V) * SQ; + if ((qx - mx0) ** 2 + (qy - my0) ** 2 > 0.16) { + dot(A, qx, qy, 1.15, 1, SX, SX + SQ - 1, SY, SY + SQ - 1); + mx0 = qx; + my0 = qy; + } + } + + // The torus. + const drag = S.mode === "drag" || S.mode === "glue" ? S.s : S.mode === "relax" || S.mode === "post" ? 1 : 0; + for (let i = 0; i < W * TOP; i++) { + if (!TH[i]) continue; + const l = TL[i], o = i * 3; + let r = torus[0] * l, g = torus[1] * l, b = torus[2] * l; + if (S.hl > 0) { + const f = fr(TU[i] - spin - S.a); + if (f < AW) { + const vm = TV[i] - S.dir * drag * smooth(f / AW), e = fr(vm * 6), st = cl(1.3 - min(e, 1 - e) * 9.4, 0, 1); + const edge = cl(1.4 - min(f, AW - f) * 2 * PI * RB * 1.2, 0, 1); + const m = S.hl * (0.3 + 0.7 * max(st, edge)), lv = 0.35 + 0.65 * l; + r += (ann[0] * lv - r) * m; + g += (ann[1] * lv - g) * m; + b += (ann[2] * lv - b) * m; + } + } + const back = B[i] * (1 - A[i]) * 0.3 * S.fade, a = A[i] * S.fade, lc = 0.55 + 0.45 * min(1, l); + r += (curve[0] - r) * back; + g += (curve[1] - g) * back; + b += (curve[2] - b) * back; + r += (curve[0] * lc - r) * a; + g += (curve[1] * lc - g) * a; + b += (curve[2] * lc - b) * a; + buf[o] = r; + buf[o + 1] = g; + buf[o + 2] = b; + } + + // The square: its ground, the annulus as a strip, the curve, and the + // glued edges, side edges in the meridian's colour with one arrow each, + // top and bottom in the longitude's colour with two. + for (let y = SY; y < SY + SQ; y++) + for (let x = SX; x < SX + SQ; x++) { + const i = y * W + x, o = i * 3, U = (x + 0.5 - SX) / SQ, V = (SY + SQ - y - 0.5) / SQ; + let r = torus[0] * 0.2, g = torus[1] * 0.2, b = torus[2] * 0.2; + if (S.hl > 0) { + const f = fr(U - S.a); + if (f < AW) { + const vm = V - S.dir * drag * smooth(f / AW), e = fr(vm * 6), st = cl(1.2 - min(e, 1 - e) * 9.3, 0, 1); + const edge = cl(1.3 - min(f, AW - f) * SQ, 0, 1); + const m = S.hl * (0.3 + 0.7 * max(st, edge)); + r += (ann[0] * 0.55 - r) * m; + g += (ann[1] * 0.55 - g) * m; + b += (ann[2] * 0.55 - b) * m; + } + } + const a = A[i] * S.fade; + r += (curve[0] - r) * a; + g += (curve[1] - g) * a; + b += (curve[2] - b) * a; + buf[o] = r; + buf[o + 1] = g; + buf[o + 2] = b; + } + const put = (x, y, c) => { + if (x < 0 || x >= W || y < 0 || y >= H) return; + const o = (y * W + x) * 3; + buf[o] = c[0]; + buf[o + 1] = c[1]; + buf[o + 2] = c[2]; + }; + for (let y = SY - 1; y <= SY + SQ; y++) put(SX - 1, y, mer), put(SX + SQ, y, mer); + for (let x = SX; x < SX + SQ; x++) put(x, SY - 1, lng), put(x, SY + SQ, lng); + const my = SY + SQ / 2 - 2, mx = SX + SQ / 2 - 2; + for (const ex of [SX - 1, SX + SQ]) + for (let k = 1; k <= 2; k++) put(ex - k, my + k, mer), put(ex + k, my + k, mer); + for (const ey of [SY - 1, SY + SQ]) + for (const dx of [0, 3]) + for (let k = 1; k <= 2; k++) put(mx + dx - k, ey - k, lng), put(mx + dx - k, ey + k, lng); + + // Where the cut closes, a flash on each meeting point. + if (S.glow > 0 && S.mode !== "out") { + for (let k = 0; k < S.p; k++) { + const U = S.a + AW + k, tau = (U - S.c1 - floor((U - S.c1) / S.p) * S.p) / S.p, ii = floor(tau * NS) % NS; + const V = S.cv[ii]; + const t = TAU * (U + spin), ph = TAU * V - PI / 2, rr = RB + RS * cos(ph); + const x = rr * cos(t), y = rr * sin(t), z = RS * sin(ph); + const facing = cos(ph) * sin(t) * EZ[1] + sin(ph) * EZ[2]; + const pts = [[SX + fr(U) * SQ, SY + SQ - fr(V) * SQ]]; + if (facing > 0) pts.push([CX + x, CY - (y * EY[1] + z * EY[2])]); + for (const [px, py] of pts) + for (let dy = -2; dy <= 2; dy++) + for (let dx = -2; dx <= 2; dx++) { + const X = floor(px) + dx, Y = floor(py) + dy; + if (X < 0 || X >= W || Y < 0 || Y >= H) continue; + const o = (Y * W + X) * 3, m = S.glow * cl(2.4 - sqrt(dx * dx + dy * dy), 0, 1); + buf[o] += (1 - buf[o]) * m; + buf[o + 1] += (1 - buf[o + 1]) * m; + buf[o + 2] += (1 - buf[o + 2]) * m; + } + } + } + + // The class: the new one from the moment the ends meet. + const shown = S.mode === "glue" || S.mode === "relax" ? S.q + S.dir * S.p : S.q; + const lc = hsv(h + 0.13, 0.25, 0.75 + 0.25 * S.glow); + label(buf, "(" + S.p + "," + shown + ")", 58, [lc[0] * S.fade, lc[1] * S.fade, lc[2] * S.fade]); + + for (let i = 0; i < N; i++) + display.setPixel(i % W, floor(i / W), round(cl(buf[i * 3], 0, 1) * 255), round(cl(buf[i * 3 + 1], 0, 1) * 255), round(cl(buf[i * 3 + 2], 0, 1) * 255)); +} From e5326c4f3a3746ad879a740270e840e9338684d5 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 31 Oct 2026 20:20:13 +0000 Subject: [PATCH 5/9] =?UTF-8?q?=ED=8C=A8=ED=84=B4=20=EC=97=B0=EA=B5=AC=208?= =?UTF-8?q?1=ED=8C=90:=20Malfatti=20Circles,=20YBC=207289?= 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_01Bc4Jkpfqox342J4FE4ZD1C --- .../patterns/malfatti-circles.js | 347 +++++++++++++ incoming/pattern-study/patterns/ybc-7289.js | 461 ++++++++++++++++++ 2 files changed, 808 insertions(+) create mode 100644 incoming/pattern-study/patterns/malfatti-circles.js create mode 100644 incoming/pattern-study/patterns/ybc-7289.js diff --git a/incoming/pattern-study/patterns/malfatti-circles.js b/incoming/pattern-study/patterns/malfatti-circles.js new file mode 100644 index 00000000..ced4c38e --- /dev/null +++ b/incoming/pattern-study/patterns/malfatti-circles.js @@ -0,0 +1,347 @@ +// ===== Patternflow pattern ===== +// Title: Malfatti Circles +// Family: geometry +// Idea: One slowly morphing triangle is drawn twice down the tall panel, the upper copy holding Malfatti's three circles, each touching two sides and the other two circles, and the lower copy the greedy three, the incircle and then each time the largest circle that still fits, with two bars and percentages between them for the share of the triangle each three cover, so the greedy bar stays ahead, by 14 points of the triangle when it is a 23 degree wedge and by a single point when it passes through equilateral. +// Author: patternflow-lab +// Date: 2026-10-31 +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Lineage: Malfatti's problem (Gian Francesco Malfatti, 1803); the greedy arrangement beating his circles (H. Lob and H. W. Richmond, 1930) and proved best in every triangle (V. A. Zalgaller and G. A. Los', 1994), pattern round 81 +// Round: 81 +// =============================== +// @matrix 64x128 +// @knobs Hue=0..1, Speed=0.2..3, Thin=0..1, More=0..1 +// +// Knob 1: Hue. Turns both colours round the wheel together. At 0 Malfatti's circles and their bar are azure and the greedy ones rose pink; at 0.25 violet and yellow; at 0.5 red orange and green. +// Knob 2: Speed. At 2 the triangle goes from its thinnest to equilateral in 6 s and back in 6 more, and its thin end comes in turn as a wedge leaning over the right end of the base, a flat isosceles triangle, a wedge leaning left and the flat one again, 48 s for the round. Every time goes as 2 / Speed, so 60 s from thin to equilateral at 0.2 and 4 s at 3. +// Knob 3: Thin. How low the apex comes at the thin end of the morph: 26 rows above the 61 LED base at 0, a wedge whose sharpest corner is 23 degrees, where the greedy three cover 69.8% against Malfatti's 55.9%, or a flat triangle of 99 degrees at the top, 73.7% against 64.7%; at 1 it comes to 7 rows, a 6.6 degree wedge with 39.1% against 23.1%, or 154 degrees at the top with 39.9% against 30.0%. +// Knob 4: More. Goes on placing circles greedily after the first three, in both copies and in a paler shade: none up to 0.1 (the opening is 0.06), 3 at 0.25, 16 at 0.5, 36 at 0.75 and 60 at 1, so the gaps each first three leave fill with ever smaller circles. The bars and numbers keep counting the first three only. + +// In 1803 Malfatti asked how to cut three cylindrical columns out of a +// triangular prism of marble with the least waste, and took for granted that +// the answer was three circles in the triangle, each tangent to the other two +// and to two of the sides. Those circles have a closed form. With a, b, c the +// sides opposite A, B, C, s the semiperimeter, r the inradius and d, e, f the +// distances from the incentre to A, B and C, the circle in the corner at A +// has radius +// +// r_A = r / (2 (s - a)) * (s - r - (e + f - d)) +// +// and the others go round the same way. Its centre is the vertex pulled +// towards the incentre by r_A / r, which makes it tangent to both sides at A. +// Checked numerically on the triangles this pattern passes through and on +// ten random ones, with corners from 3.6 to 154 degrees: the distances +// between centres equal the sums of the radii, and each circle stands its +// own radius off both sides at its corner, to within 1e-13 LEDs. +// +// Lob and Richmond showed in 1930 that the circles were not the answer: in +// an equilateral triangle the incircle with two small circles in two corners +// covers 73.9% against Malfatti's 72.9%, and in a thin triangle the gap is +// wide. Zalgaller and Los' proved in 1994 that the greedy choice is the best +// one in every triangle: the incircle, then the largest circle left, then the +// largest left again. +// +// After the incircle the free space is three corners, each bounded by two +// sides and the incircle. The largest circle in the corner at A is the image +// of the incircle under the homothety at A with ratio +// +// q_A = (1 - sin(A/2)) / (1 + sin(A/2)), +// +// and placing it leaves a smaller corner (the next circle shrinks by q_A +// again) and two cusps, each between two tangent circles and a side. A cusp, +// like every gap after that, is bounded by three mutually tangent things, and +// its largest circle touches all three, with Descartes' curvature +// +// k = k1 + k2 + k3 + 2 sqrt(k1 k2 + k2 k3 + k3 k1), a side counting 0. +// +// So the greedy keeps a list of gaps, each with the radius of its best +// circle, and takes the largest each time; the circle placed splits its gap +// into three. The same list, started from Malfatti's circles (their middle +// gap, three corners and three cusps), continues the upper copy when K4 asks +// for more. + +const W = 64, H = 128; +const { floor, min, max, round, sqrt, sin, cos, abs, PI, hypot } = Math; +const cl = (v, a, b) => (v < a ? a : v > b ? b : v); +const TAU = 2 * PI; + +// Each copy's base runs from x = 1.5 to 62.5 on its bottom row. +const BX0 = 1.5, BX1 = 62.5, BASE = BX1 - BX0, HEQ = (BASE * sqrt(3)) / 2; +const TOPS = [{ base: 56, bar: 60, num: 58 }, { base: 126, bar: 67, num: 65 }]; +const BAR0 = 1, BARLEN = 42; +// The two colours' places on the wheel, before the hue knob. +const HUES = [0.56, 0.9]; + +// 3 by 5 digits, rows top to bottom, bit 2 the left column. +const DIG = [ + [7, 5, 5, 5, 7], [2, 6, 2, 2, 7], [7, 1, 7, 4, 7], [7, 1, 7, 1, 7], [5, 5, 7, 1, 1], + [7, 4, 7, 1, 7], [7, 4, 7, 5, 7], [7, 1, 1, 2, 2], [7, 5, 7, 5, 7], [7, 5, 7, 1, 7], +]; +const PCT = [5, 1, 2, 4, 5]; + +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; + return o; +} + +export function setup(params) { + params.s = { + t: 0, hue: 0, thin: 0, more: 0, + buf: new Float32Array(W * H * 3), + col: [[0, 0, 0], [0, 0, 0]], pale: [[0, 0, 0], [0, 0, 0]], + }; +} + +export function update(dt, input, params) { + if (!params.s) setup(params); + const S = params.s, k = input.knobValues || [0, 2, 0, 0.06], d = cl(dt || 0, 0, 0.1); + S.hue = k[0] || 0; + const speed = cl(k[1] == null ? 2 : k[1], 0.2, 3); + S.thin = cl(k[2] || 0, 0, 1); + const m = cl(k[3] == null ? 0.06 : k[3], 0, 1); + S.more = m < 0.1 ? 0 : round(60 * ((m - 0.1) / 0.9) ** 1.6); + S.t += (d * speed) / 2; +} + +// The triangle at model time t: base fixed, the apex walking. m is 1 at the +// thin end and 0 at equilateral; the apex's side goes round once in 48 s. +function triangle(t, thin) { + const m = 0.5 + 0.5 * cos((TAU * t) / 12); + const side = cos((TAU * t) / 48); + const hmin = 26 - 19 * thin; + const h = HEQ - m * (HEQ - hmin); + const ax = (BX0 + BX1) / 2 + m * 30 * side; + return [[ax, -h], [BX0, 0], [BX1, 0]]; +} + +// A side as a line with its inward unit normal: inside is n . p - c >= 0. +function sideLine(P, Q, R) { + let nx = -(Q[1] - P[1]), ny = Q[0] - P[0]; + const l = hypot(nx, ny); + nx /= l; + ny /= l; + let c = nx * P[0] + ny * P[1]; + if (nx * R[0] + ny * R[1] - c < 0) { + nx = -nx; + ny = -ny; + c = -c; + } + return { line: true, nx, ny, c }; +} + +// Everything about one triangle: sides, inradius, Malfatti's three, and the +// corner ratios the greedy uses. +function solve(V) { + const [A, B, C] = V; + const a = hypot(B[0] - C[0], B[1] - C[1]), b = hypot(C[0] - A[0], C[1] - A[1]), c = hypot(A[0] - B[0], A[1] - B[1]); + const s = (a + b + c) / 2, area = sqrt(max(1e-9, s * (s - a) * (s - b) * (s - c))), r = area / s; + const I = [(a * A[0] + b * B[0] + c * C[0]) / (2 * s), (a * A[1] + b * B[1] + c * C[1]) / (2 * s)]; + const dist = (P) => hypot(P[0] - I[0], P[1] - I[1]); + const d = dist(A), e = dist(B), f = dist(C); + const rad = [ + (r / (2 * (s - a))) * (s - r - (e + f - d)), + (r / (2 * (s - b))) * (s - r - (f + d - e)), + (r / (2 * (s - c))) * (s - r - (d + e - f)), + ]; + const mal = rad.map((rr, i) => ({ x: V[i][0] + (I[0] - V[i][0]) * (rr / r), y: V[i][1] + (I[1] - V[i][1]) * (rr / r), r: rr })); + // Each side by the vertex it faces: lines[0] is BC. + const lines = [sideLine(B, C, A), sideLine(C, A, B), sideLine(A, B, C)]; + const q = V.map((P) => { + const sh = r / dist(P); + return (1 - sh) / (1 + sh); + }); + return { V, area, r, I, mal, lines, q }; +} + +// The largest circle tangent to three mutually tangent things (at most one +// of them a side): its radius from Descartes, its centre from two of the +// three, choosing of the two crossings the one that also touches the third. +function soddy(o) { + const k = o.map((x) => (x.line ? 0 : 1 / x.r)); + const kk = k[0] + k[1] + k[2] + 2 * sqrt(max(0, k[0] * k[1] + k[1] * k[2] + k[2] * k[0])); + const rho = 1 / kk; + const circ = o.filter((x) => !x.line), ln = o.find((x) => x.line); + const third = ln || o[2]; + let pts; + if (ln) { + // Points rho inside the side, at r1 + rho from the circle. + const C1 = circ[0], R = C1.r + rho, off = ln.nx * C1.x + ln.ny * C1.y - (ln.c + rho); + const h2 = R * R - off * off; + if (h2 < 0) return null; + const fx = C1.x - ln.nx * off, fy = C1.y - ln.ny * off, hh = sqrt(h2); + pts = [[fx - ln.ny * hh, fy + ln.nx * hh], [fx + ln.ny * hh, fy - ln.nx * hh]]; + // Of the two, the one that touches the other circle. + const C2 = circ[1], want = C2.r + rho; + const e0 = abs(hypot(pts[0][0] - C2.x, pts[0][1] - C2.y) - want), e1 = abs(hypot(pts[1][0] - C2.x, pts[1][1] - C2.y) - want); + const p = e0 < e1 ? pts[0] : pts[1]; + return { x: p[0], y: p[1], r: rho }; + } + const C1 = o[0], C2 = o[1], R1 = C1.r + rho, R2 = C2.r + rho; + const dx = C2.x - C1.x, dy = C2.y - C1.y, D = hypot(dx, dy); + const ax = (D * D + R1 * R1 - R2 * R2) / (2 * D), h2 = R1 * R1 - ax * ax; + if (h2 < 0 || D <= 0) return null; + const ux = dx / D, uy = dy / D, hh = sqrt(h2), mx = C1.x + ux * ax, my = C1.y + uy * ax; + pts = [[mx - uy * hh, my + ux * hh], [mx + uy * hh, my - ux * hh]]; + const want = third.r + rho; + const e0 = abs(hypot(pts[0][0] - third.x, pts[0][1] - third.y) - want), e1 = abs(hypot(pts[1][0] - third.x, pts[1][1] - third.y) - want); + const p = e0 < e1 ? pts[0] : pts[1]; + return { x: p[0], y: p[1], r: rho }; +} + +// A gap's best circle: in a corner the next image of its circle under the +// homothety at the vertex, otherwise the Soddy circle. +function best(T, g) { + if (g.corner >= 0) { + const P = T.V[g.corner], q = T.q[g.corner], c = g.o[0]; + return { x: P[0] + (c.x - P[0]) * q, y: P[1] + (c.y - P[1]) * q, r: c.r * q }; + } + return soddy(g.o); +} + +function addGap(list, T, corner, o) { + const g = { corner, o, c: null }; + g.c = best(T, g); + if (g.c && g.c.r > 0.05) list.push(g); +} + +// Takes the largest gap n times, splitting it each time. Returns the circles. +function grow(T, gaps, n) { + const out = []; + for (let k = 0; k < n && gaps.length; k++) { + let bi = 0; + for (let i = 1; i < gaps.length; i++) if (gaps[i].c.r > gaps[bi].c.r) bi = i; + const g = gaps[bi], c = g.c; + gaps[bi] = gaps[gaps.length - 1]; + gaps.pop(); + out.push(c); + if (g.corner >= 0) { + // A corner leaves a smaller corner and the two cusps on its sides. + const v = g.corner, old = g.o[0]; + addGap(gaps, T, v, [c]); + addGap(gaps, T, -1, [old, c, T.lines[(v + 1) % 3]]); + addGap(gaps, T, -1, [old, c, T.lines[(v + 2) % 3]]); + } else { + const [p, q, r] = g.o; + addGap(gaps, T, -1, [p, q, c]); + addGap(gaps, T, -1, [q, r, c]); + addGap(gaps, T, -1, [p, r, c]); + } + } + return out; +} + +// The greedy from an empty triangle: the incircle and then grow(). +function greedy(T, n) { + const inc = { x: T.I[0], y: T.I[1], r: T.r }, gaps = []; + for (let v = 0; v < 3; v++) addGap(gaps, T, v, [inc]); + return [inc].concat(grow(T, gaps, n - 1)); +} + +// After Malfatti's three: the middle, three corners and three cusps. +function afterMalfatti(T, n) { + const [mA, mB, mC] = T.mal, gaps = []; + addGap(gaps, T, -1, [mA, mB, mC]); + for (let v = 0; v < 3; v++) addGap(gaps, T, v, [T.mal[v]]); + addGap(gaps, T, -1, [mA, mB, T.lines[2]]); + addGap(gaps, T, -1, [mB, mC, T.lines[0]]); + addGap(gaps, T, -1, [mC, mA, T.lines[1]]); + return grow(T, gaps, n); +} + +function add(buf, x, y, c, a) { + if (x < 0 || x >= W || y < 0 || y >= H || a <= 0) return; + const p = (y * W + x) * 3; + buf[p] += c[0] * a; + buf[p + 1] += c[1] * a; + buf[p + 2] += c[2] * a; +} + +// A disc lit at `fill` inside, rising towards a rim line at `rim`. +function disc(buf, oy, c, col, fill, rim) { + const cx = c.x, cy = c.y + oy, r = c.r; + if (r < 0.7) { + add(buf, round(cx), round(cy), col, (fill + rim) * r * 0.9); + return; + } + for (let y = floor(cy - r - 1); y <= cy + r + 1; y++) + for (let x = floor(cx - r - 1); x <= cx + r + 1; x++) { + const e = hypot(x - cx, y - cy), u = e / r; + const inside = cl(r + 0.5 - e, 0, 1); + if (inside <= 0) continue; + const ring = cl(1.2 - abs(e - r + 0.7), 0, 1); + add(buf, x, y, col, inside * (fill * (0.7 + 0.3 * u * u) * (1 - ring) + rim * ring)); + } +} + +// The triangle: its inside a faint grey, its sides a pale line. +function shape(buf, T, oy, ink) { + const V = T.V.map((P) => [P[0], P[1] + oy]); + const y0 = max(0, floor(min(V[0][1], V[1][1], V[2][1])) - 1), y1 = min(H - 1, Math.ceil(max(V[0][1], V[1][1], V[2][1])) + 1); + for (let y = y0; y <= y1; y++) + for (let x = 0; x < W; x++) { + let dmin = 1e9, dseg = 1e9; + for (let i = 0; i < 3; i++) { + const L = T.lines[i]; + dmin = min(dmin, L.nx * x + L.ny * (y - oy) - L.c); + const P = V[(i + 1) % 3], Q = V[(i + 2) % 3], dx = Q[0] - P[0], dy = Q[1] - P[1]; + const tt = cl(((x - P[0]) * dx + (y - P[1]) * dy) / (dx * dx + dy * dy), 0, 1); + dseg = min(dseg, hypot(P[0] + tt * dx - x, P[1] + tt * dy - y)); + } + const inside = cl(dmin + 0.5, 0, 1), edge = cl(1.1 - dseg, 0, 1); + add(buf, x, y, ink, 0.13 * inside * (1 - edge) + 0.62 * edge); + } +} + +function digit(buf, ch, x0, y0, col) { + const g = ch === "%" ? PCT : DIG[ch]; + for (let j = 0; j < 5; j++) for (let i = 0; i < 3; i++) if (g[j] & (4 >> i)) add(buf, x0 + i, y0 + j, col, 1); +} + +// A bar of the covered share, over a faint track for the whole triangle, +// and the share in tenths of a percent at the right. +function bar(buf, row, numRow, share, col, pale) { + const len = share * BARLEN; + for (let x = BAR0; x < BAR0 + BARLEN; x++) { + const f = cl(BAR0 + len - x, 0, 1); + for (let y = row - 1; y <= row + 1; y++) add(buf, x, y, col, 0.12 + 0.83 * f); + } + const tenths = round(share * 1000), s = String(floor(tenths / 10)).padStart(2, " "); + let x = 46; + for (const ch of s) { + if (ch !== " ") digit(buf, +ch, x, numRow, pale); + x += 4; + } + add(buf, x, numRow + 4, pale, 1); + digit(buf, tenths % 10, x + 2, numRow, pale); + digit(buf, "%", x + 6, numRow, pale); +} + +export function draw(display, params) { + const S = params.s; + if (!S) return; + const buf = S.buf, col = S.col, pale = S.pale; + buf.fill(0); + for (let i = 0; i < 2; i++) { + hsv(S.hue + HUES[i], 0.85, 1, col[i]); + hsv(S.hue + HUES[i], 0.35, 1, pale[i]); + } + const T = solve(triangle(S.t, S.thin)); + const ink = [0.85, 0.85, 0.9]; + const greedyAll = greedy(T, 3 + S.more), malMore = afterMalfatti(T, S.more); + const sets = [T.mal, greedyAll.slice(0, 3)]; + const extra = [malMore, greedyAll.slice(3)]; + for (let i = 0; i < 2; i++) { + const L = TOPS[i], oy = L.base; + shape(buf, T, oy, ink); + for (const c of sets[i]) disc(buf, oy, c, col[i], 0.42, 1); + for (const c of extra[i]) disc(buf, oy, c, pale[i], 0.45, 0.85); + let a = 0; + for (const c of sets[i]) a += PI * c.r * c.r; + bar(buf, L.bar, L.num, a / T.area, col[i], pale[i]); + } + for (let p = 0; p < W * H; p++) + display.setPixel(p % W, floor(p / W), round(cl(buf[p * 3], 0, 1) * 255), round(cl(buf[p * 3 + 1], 0, 1) * 255), round(cl(buf[p * 3 + 2], 0, 1) * 255)); +} diff --git a/incoming/pattern-study/patterns/ybc-7289.js b/incoming/pattern-study/patterns/ybc-7289.js new file mode 100644 index 00000000..ed95d8aa --- /dev/null +++ b/incoming/pattern-study/patterns/ybc-7289.js @@ -0,0 +1,461 @@ +// ===== Patternflow pattern ===== +// Title: YBC 7289 +// Family: type +// Idea: The upper half of the tall panel is the face of the tablet YBC 7289 under a swaying lamp, a square with both diagonals pressed into clay and its numbers in cuneiform wedges, 30 by a side, 1;24,51,10 along the diagonal and 42;25,35 below it, and on the clay below, the averaging x -> (x + 2/x)/2 is written out wedge by wedge from 1;30, one step to a line in sexagesimal places, each place lighting turquoise once it is the square root of 2's own place, so 1;30 and 1;25 light only their 1, 1;24,51,10,35 lights four places and the tablet's number with them, and 1;24,51,10,7 lights all five. +// Author: patternflow-lab +// Date: 2026-10-31 +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Lineage: YBC 7289, Yale Babylonian Collection, Old Babylonian (about 1800 to 1600 BCE); the averaging method for square roots as later written down by Heron of Alexandria (1st century); wedges drawn for this pattern as pyramids pressed into a height field, pattern round 81 +// Round: 81 +// =============================== +// @matrix 64x128 +// @knobs Lamp=0..1, Speed=0.2..3, Start=0..5, Places=0..1 +// +// Knob 1: Lamp. Where the lamp stands around the tablet, a full turn over the range; it is always 15 degrees above the clay and sways 29 degrees either way. At 0 it is on the left and a little above, so a wedge's left wall falls into shadow and its right wall catches the light; at 0.25 it is below, at 0.5 on the right, where the bright and dark walls of every wedge change sides, and at 0.75 above. +// Knob 2: Speed. At 2 a wedge is pressed every 0.06 s and each agreeing place lights 0.12 s after the one before, so the four lines from 1;30 are written and checked in 7.9 s and stand 3.5 s before the clay is smoothed over 1.2 s, a round of 13.1 s; the lamp sways once in 7 s. Every time goes as 2 / Speed. +// Knob 3: Start. The first guess: 0 is 1;30 (3/2), 1 is 1;20 (4/3), 2 is 2, 3 is 1, 4 is 1;24 (7/5), 5 is 1;40 (5/3). 1;30 and 1;20 both give 1;25 next, and 2 and 1 both give 1;30, since x and 2/x always lead to the same average; 1;24 agrees in two places at once and needs only three lines. A new start opens on its lines already written, then smooths them and writes them again. +// Knob 4: Places. How many places after the point each step keeps before the next is worked out: all of them below 0.125 (the opening 0.06), so the lines run on until one holds five places that all agree; 4 from 0.125, 3 from 0.375, 2 from 0.625 and 1 from 0.875. Cut to 3, every start lands on 1;24,51,10, the tablet's own number, the next step gives it back unchanged, and the tablet's number lights; cut to 2 or 1 the lines settle on 1;24,51 or 1;24 and the tablet stays dark. + +// YBC 7289 carries a square with its two diagonals. One side is marked 30; +// along a diagonal stands 1;24,51,10 and under it 42;25,35. In base 60, +// 1;24,51,10 is 1 + 24/60 + 51/60^2 + 10/60^3 = 1.4142130, the square root +// of 2 to about six decimal places, and 42;25,35 is 30 times it: the +// diagonal of the square of side 30. Halving 1;24,51,10 gives 0;42,25,35, +// so multiplying by 30 is halving and moving one place up. +// +// The method drawn below takes a guess x for the root of 2 and averages it +// with 2/x. When x is too big 2/x is too small by about as much, so the +// average lands far closer, and each step about doubles the places that +// are right. From 3/2: 17/12 = 1;25, then 577/408 = 1;24,51,10,35,... and +// 665857/470832 = 1;24,51,10,7,46,6,9,... where the root is +// 1;24,51,10,7,46,6,4,44. The iterates are kept as exact fractions (BigInt) +// and their places read off one by one, so a place lights only when it is +// the root's own place, not a rounding of it. 1;25 is nearer the root than +// 1;24 but its 25 is not the root's 24, so line two lights only its 1. +// +// Places cuts each step to so many places before the next, as anyone +// working by hand must. Cut to three, the steps from every start reach +// 1;24,51,10 and stop there: the average of 1;24,51,10 and 2 over it is +// the root plus about 1e-13, whose first three places are 1;24,51,10 again. +// +// Numerals: a place from 1 to 59 is its tens as corner wedges +// (Winkelhaken) on the left, stacked in a column (four as two and two, +// five as three and two), and its units as upright wedges in rows of three +// on the right. Places stand a gap apart and a zero place is a wider gap. +// Nothing marks where the whole part ends: the semicolon in 1;24,51,10 is +// the modern reader's, and the wedges are only 1, 24, 51 and 10. +// +// Each wedge is a triangular pyramid pressed into a height field: a +// triangle outline and its deepest point, the stylus's corner. The upright +// wedge is a broad head with a tail; the corner wedge a short triangle +// pointing left. The LEDs average 16 samples each of the surface normal, +// and the light is a lamp at azimuth and elevation, scaled so flat clay +// keeps one brightness: walls facing the lamp glow, walls facing away go +// dark, and the deeper the floor the darker. The lamp sways, so the light +// moves in every wedge on the panel while the stylus works on one. +// +// An agreeing place lights in two ways at once: its wedges turn turquoise +// with the light spilling 4 LEDs round each, and a soft tile fills the box +// of the place, so the agreeing run of a line reads as one highlighted span. + +const W = 64, H = 128, N = W * H; +const { floor, min, max, round, sqrt, sin, cos, PI, hypot, ceil } = Math; +const cl = (v, a, b) => (v < a ? a : v > b ? b : v); +const smooth = (u) => (u <= 0 ? 0 : u >= 1 ? 1 : u * u * (3 - 2 * u)); + +// Clay, its shadow floor, and the colour an agreeing place lights. +const CLAY = [168, 132, 92]; +const AMB = 0.16, OCC = 0.85; +const GLOW = [45, 230, 220]; + +// Wedge sizes in LEDs. A size is the cell an upright wedge or a corner +// wedge sits in, c wide and r tall, and the width w of the wedge inside it. +// The tablet writes small, so its 42;25,35 fits under the diagonal; the +// worked lines write a size up. +const SMALL = { c: 3, r: 4, w: 2.6, x0: 0.2, tu: 1 }, LARGE = { c: 4, r: 4, w: 3, x0: 0, tu: 0 }; +const DEPTH = 1.1, SS = 4, HALO = 4, PLACE_GAP = 3, ZERO_GAP = 6; + +// Timing in model seconds; Speed 2 runs one model second a second. +const T_WEDGE = 0.06, T_PLACE = 0.08, T_PAUSE = 0.25, T_CHECK = 0.12, T_AFTER = 0.3, T_HOLD = 3.5, T_SMOOTH = 1.2, T_REST = 0.5; + +const STARTS = [[3n, 2n], [4n, 3n], [2n, 1n], [1n, 1n], [7n, 5n], [5n, 3n]]; +// How many places after the point a step keeps, by Places. +const KEEP = [Infinity, 4, 3, 2, 1]; +const PLACES = 8; +// The lamp sways SWAY radians either side of where Lamp sets it, once in SWAY_T model seconds. +const SWAY = 0.5, SWAY_T = 7, EL = (15 * PI) / 180; + +// ---- Exact arithmetic. +function isqrt(n) { + if (n < 2n) return n; + let x = n, y = (x + 1n) / 2n; + while (y < x) { + x = y; + y = (x + n / x) / 2n; + } + return x; +} +function sexagesimal(p, q, n) { + const out = [Number(p / q)]; + let r = p % q; + for (let k = 0; k < n; k++) { + r *= 60n; + out.push(Number(r / q)); + r %= q; + } + return out; +} +const gcd = (a, b) => (b ? gcd(b, a % b) : a); +const ROOT = (() => { + let v = isqrt(2n * 60n ** BigInt(2 * PLACES)); + const d = []; + for (let k = 0; k <= PLACES; k++) { + d.unshift(Number(v % 60n)); + v /= 60n; + } + return d; +})(); + +// ---- Wedges as pyramids. Each returns its footprint on the LEDs: for every +// LED it touches, the change it makes to the averaged normal, the share of +// the LED inside it, the mean depth and how much of its glow reaches it. +function pyramid(tri, P0) { + const edges = []; + for (let i = 0; i < 3; i++) { + const A = tri[(i + 1) % 3], B = tri[(i + 2) % 3], C = tri[i]; + let nx = -(B[1] - A[1]), ny = B[0] - A[0]; + const l = hypot(nx, ny); + nx /= l; + ny /= l; + if (nx * (C[0] - A[0]) + ny * (C[1] - A[1]) < 0) { + nx = -nx; + ny = -ny; + } + const d0 = nx * (P0[0] - A[0]) + ny * (P0[1] - A[1]); + const s = DEPTH / d0, nz = 1 / sqrt(1 + s * s); + edges.push({ ax: A[0], ay: A[1], nx, ny, d0, Nx: s * nx * nz, Ny: s * ny * nz, Nz: nz }); + } + const x0 = floor(min(tri[0][0], tri[1][0], tri[2][0])), x1 = ceil(max(tri[0][0], tri[1][0], tri[2][0])); + const y0 = floor(min(tri[0][1], tri[1][1], tri[2][1])), y1 = ceil(max(tri[0][1], tri[1][1], tri[2][1])); + const out = []; + const w = 1 / (SS * SS); + for (let y = max(0, y0 - HALO); y < min(H, y1 + HALO); y++) + for (let x = max(0, x0 - HALO); x < min(W, x1 + HALO); x++) { + let dx = 0, dy = 0, dz = 0, cov = 0, dep = 0; + for (let sy = 0; sy < SS; sy++) + for (let sx = 0; sx < SS; sx++) { + const px = x + (sx + 0.5) / SS, py = y + (sy + 0.5) / SS; + let best = 2, k = -1; + for (let i = 0; i < 3; i++) { + const e = edges[i], u = (e.nx * (px - e.ax) + e.ny * (py - e.ay)) / e.d0; + if (u < 0) { + k = -1; + break; + } + if (u < best) { + best = u; + k = i; + } + } + if (k < 0) continue; + const e = edges[k]; + dx += e.Nx * w; + dy += e.Ny * w; + dz += (e.Nz - 1) * w; + cov += w; + dep += best * DEPTH * w; + } + // The glow of an agreeing place spills a little onto the clay round + // each wedge, falling off over HALO LEDs from its outline. + let far = 0; + for (const e of edges) far = max(far, -(e.nx * (x + 0.5 - e.ax) + e.ny * (y + 0.5 - e.ay))); + const halo = max(cov, 0.75 * cl(1 - far / HALO, 0, 1)); + if (cov > 0 || halo > 0) out.push(y * W + x, dx, dy, dz, cov, dep, halo); + } + return Float32Array.from(out); +} +// An upright wedge: broad head at the top, tail down to a point. +const upright = (x, y, z) => + pyramid([[x + z.x0, y + 0.1], [x + z.x0 + z.w, y + 0.1], [x + z.x0 + z.w / 2, y + z.r - 0.1]], [x + z.x0 + z.w / 2, y + 1.2]); +// A corner wedge: a short triangle pointing left, deepest near its point. +const corner = (x, y, z) => + pyramid([[x + z.x0, y + 1.5], [x + z.x0 + z.w, y + 0.05], [x + z.x0 + z.w, y + 2.95]], [x + z.x0 + 1.1, y + 1.5]); + +// ---- Numerals. A place's wedges in writing order, tens first, from the +// place's left edge x and the line's middle row ym. +function placeWidth(v, z) { + if (v === 0) return ZERO_GAP - PLACE_GAP; + const t = floor(v / 10), u = v % 10; + const tw = t === 0 ? 0 : (t <= 3 ? 1 : 2) * z.c; + const uw = min(u, 3) * z.c; + return tw + uw + (t && u ? z.tu : 0) - (z.c - z.w - z.x0 > 0.5 ? 1 : 0); +} +function placeWedges(v, x, ym, z) { + const out = []; + const t = floor(v / 10), u = v % 10; + const cols = t === 0 ? [] : t <= 3 ? [t] : t === 4 ? [2, 2] : [3, 2]; + cols.forEach((n, c) => { + const top = round(ym - (n * z.r - 1) / 2); + for (let i = 0; i < n; i++) out.push({ kind: 1, x: x + c * z.c, y: top + i * z.r }); + }); + const ux = x + cols.length * z.c + (t && u ? z.tu : 0); + const rows = ceil(u / 3), top = round(ym - (rows * z.r - 1) / 2); + for (let r = 0; r < rows; r++) for (let i = 0; i < min(3, u - 3 * r); i++) out.push({ kind: 0, x: ux + i * z.c, y: top + r * z.r }); + return out; +} +// A number from its places, left edge x: the wedges, each tagged with its +// place and its size. +function numberWedges(digits, x, ym, z) { + const wedges = []; + digits.forEach((v, k) => { + for (const w of placeWedges(v, round(x), ym, z)) wedges.push(Object.assign(w, { place: k, z })); + x += placeWidth(v, z) + PLACE_GAP; + }); + return wedges; +} +const numberWidth = (digits, z) => digits.reduce((s, v) => s + placeWidth(v, z) + PLACE_GAP, -PLACE_GAP); + +// The soft tile of light under an agreeing place: its wedges' box, full +// strength POOL inside and fading out over 1.5 LEDs. +const POOL = 0.6; +function pool(wedges) { + let x0 = 99, y0 = 999, x1 = -99, y1 = -999; + for (const w of wedges) { + x0 = min(x0, w.x + w.z.x0); + x1 = max(x1, w.x + w.z.x0 + w.z.w); + y0 = min(y0, w.y); + y1 = max(y1, w.y + (w.kind ? 3 : w.z.r - 0.1)); + } + const out = []; + for (let y = max(0, floor(y0) - 2); y < min(H, ceil(y1) + 2); y++) + for (let x = max(0, floor(x0) - 2); x < min(W, ceil(x1) + 2); x++) { + const dx = max(x0 - x - 0.5, x + 0.5 - x1, 0), dy = max(y0 - y - 0.5, y + 0.5 - y1, 0); + const v = POOL * smooth((1.5 - hypot(dx, dy)) / 1.5); + if (v > 0) out.push(y * W + x, v); + } + return Float32Array.from(out); +} +function places(wedges) { + const by = new Map(); + for (const w of wedges) { + if (!by.has(w.place)) by.set(w.place, []); + by.get(w.place).push(w); + } + return by; +} + +function makeItem(w) { + return { fp: w.kind ? corner(w.x, w.y, w.z) : upright(w.x, w.y, w.z), place: w.place, at: 0, glowAt: Infinity }; +} + +// ---- The tablet face, upper half: grooves of the square and its diagonals +// in a static normal field, a ruled line under it, and its three numbers as +// items that never fade. +const SQ = { cx: 32, cy: 33, r: 30.5 }; +function grooves(st, stD) { + const { cx, cy, r } = SQ; + const V = [[cx - r, cy], [cx, cy - r], [cx + r, cy], [cx, cy + r]]; + const segs = [[V[0], V[1]], [V[1], V[2]], [V[2], V[3]], [V[3], V[0]], [V[0], V[2]], [V[1], V[3]], [[1, 67.5], [63, 67.5]]]; + const hw = 0.95, dg = 0.55, s = dg / hw, nz = 1 / sqrt(1 + s * s), w = 1 / (SS * SS); + for (let y = 0; y < H; y++) + for (let x = 0; x < W; x++) { + const p = y * W + x; + for (let sy = 0; sy < SS; sy++) + for (let sx = 0; sx < SS; sx++) { + const px = x + (sx + 0.5) / SS, py = y + (sy + 0.5) / SS; + let bd = 9, ux = 0, uy = 0; + for (const [A, B] of segs) { + const dx = B[0] - A[0], dy = B[1] - A[1]; + const t = cl(((px - A[0]) * dx + (py - A[1]) * dy) / (dx * dx + dy * dy), 0, 1); + const qx = px - (A[0] + t * dx), qy = py - (A[1] + t * dy), d = hypot(qx, qy); + if (d < bd) { + bd = d; + ux = d > 1e-6 ? qx / d : 0; + uy = d > 1e-6 ? qy / d : 0; + } + } + if (bd >= hw) continue; + // The groove's wall tilts towards its middle line. + st[p * 3] += -s * ux * nz * w; + st[p * 3 + 1] += -s * uy * nz * w; + st[p * 3 + 2] += (nz - 1) * w; + stD[p] += dg * (1 - bd / hw) * w; + } + // The clay is a low pillow, 1.5 degrees at the edges. + st[p * 3] += 0.026 * ((x + 0.5 - 32) / 32); + st[p * 3 + 1] += 0.026 * ((y + 0.5 - 64) / 64); + } +} + +// The tablet's wedges, and the tiles of light under its 1;24,51,10. +function tabletItems() { + const items = [], pools = []; + const { cx, cy } = SQ; + // 30 by the upper left side, 1;24,51,10 above the level diagonal, 42;25,35 below it. + const groups = [ + { digits: [30], x: 26, ym: 16, tag: 0 }, + { digits: [1, 24, 51, 10], x: cx - numberWidth([1, 24, 51, 10], SMALL) / 2, ym: cy - 6, tag: 1 }, + { digits: [42, 25, 35], x: cx - numberWidth([42, 25, 35], SMALL) / 2, ym: cy + 7, tag: 2 }, + ]; + for (const g of groups) { + const wedges = numberWedges(g.digits, g.x, g.ym, SMALL); + for (const w of wedges) { + const it = makeItem(w); + it.tag = g.tag; + items.push(it); + } + if (g.tag === 1) for (const ws of places(wedges).values()) pools.push(pool(ws)); + } + return { items, pools }; +} + +// ---- The worked lines, lower half. +const LINE_X = 2, LINE_W = 61, LOW_TOP = 69, LOW_BOT = 127; + +// The steps from a start, each cut to `keep` places after the point. A +// line holds as many places as fit across the panel. The lines stop when +// one holds five places and all agree with the root, or when a cut step +// gives back the line before it. +function plan(start, keep) { + let [p, q] = STARTS[start]; + const lines = []; + let before = null; + for (let i = 0; i < 6; i++) { + const d = sexagesimal(p, q, PLACES); + let n = 1; + while (n < d.length && numberWidth(d.slice(0, n + 1), LARGE) <= LINE_W) n++; + while (n > 1 && d[n - 1] === 0) n--; + const shown = d.slice(0, n); + let agree = 0; + while (agree < n && shown[agree] === ROOT[agree]) agree++; + lines.push({ shown, agree }); + const same = before && before[0] === p && before[1] === q; + if ((agree === n && n >= 5) || same) break; + before = [p, q]; + [p, q] = [p * p + 2n * q * q, 2n * p * q]; + if (keep < Infinity) { + const unit = 60n ** BigInt(keep); + [p, q] = [(p * unit) / q, unit]; + } + const g = gcd(p, q); + p /= g; + q /= g; + } + const pitch = min(14, floor((LOW_BOT - LOW_TOP) / lines.length)); + const top = LOW_TOP + (LOW_BOT - LOW_TOP - pitch * lines.length) / 2; + const items = [], pools = []; + let t = 0.3, sync = Infinity; + lines.forEach((L, i) => { + const ym = top + pitch * (i + 0.5); + const wedges = numberWedges(L.shown, LINE_X, ym, LARGE), byPlace = places(wedges); + let last = -1; + for (const w of wedges) { + if (w.place !== last && last >= 0) t += T_PLACE; + last = w.place; + const it = makeItem(w); + it.at = t; + it.line = i; + it.agree = w.place < L.agree; + items.push(it); + t += T_WEDGE; + } + t += T_PAUSE; + for (let k = 0; k < L.agree; k++) { + for (const it of items) if (it.line === i && it.place === k) it.glowAt = t; + pools.push({ fp: pool(byPlace.get(k)), glowAt: t }); + if (k === 3 && sync === Infinity) sync = t; + t += T_CHECK; + } + t += T_AFTER; + }); + return { items, pools, end: t + T_HOLD, sync }; +} + +export function setup(params) { + const st = new Float32Array(N * 3), stD = new Float32Array(N); + grooves(st, stD); + params.s = { + t: 0, clock: 0, start: -1, keep: -1, lampA: 0, + st, stD, + dn: new Float32Array(N * 3), dD: new Float32Array(N), glow: new Float32Array(N), + plan: null, + }; + const T = tabletItems(); + params.s.tablet = T.items; + params.s.tabletPools = T.pools; +} + +export function update(dt, input, params) { + if (!params.s) setup(params); + const S = params.s, k = input.knobValues || [0, 2, 0, 0.06], d = cl(dt || 0, 0, 0.1); + S.lampA = k[0] || 0; + const speed = cl(k[1] == null ? 2 : k[1], 0.2, 3); + const start = cl(round(k[2] || 0), 0, STARTS.length - 1); + const keep = KEEP[cl(round(4 * (k[3] == null ? 0.06 : k[3])), 0, 4)]; + // A new start, or a new cut, opens on its lines already written, 3.2 + // model seconds before the clay is smoothed and they are written again. + if (start !== S.start || keep !== S.keep) { + S.start = start; + S.keep = keep; + S.plan = plan(start, keep); + S.t = S.plan.end - 3.2; + } + S.t += (d * speed) / 2; + S.clock += (d * speed) / 2; + if (S.t > S.plan.end + T_SMOOTH + T_REST) S.t = 0; +} + +function light(S, fp, g) { + const gl = S.glow; + for (let i = 0; i < fp.length; i += 2) gl[fp[i]] = max(gl[fp[i]], fp[i + 1] * g); +} + +function stamp(S, it, scale, glow) { + const fp = it.fp, dn = S.dn, dD = S.dD, gl = S.glow; + for (let i = 0; i < fp.length; i += 7) { + const p = fp[i]; + dn[p * 3] += fp[i + 1] * scale; + dn[p * 3 + 1] += fp[i + 2] * scale; + dn[p * 3 + 2] += fp[i + 3] * scale; + dD[p] += fp[i + 5] * scale; + if (glow > 0) gl[p] = max(gl[p], fp[i + 6] * glow); + } +} + +export function draw(display, params) { + const S = params.s; + if (!S || !S.plan) return; + const P = S.plan, t = S.t; + S.dn.fill(0); + S.dD.fill(0); + S.glow.fill(0); + const fade = 1 - smooth((t - P.end) / T_SMOOTH); + const sync = smooth((t - P.sync) / 0.4) * fade; + for (const it of S.tablet) stamp(S, it, 1, it.tag === 1 ? sync : 0); + if (sync > 0) for (const fp of S.tabletPools) light(S, fp, sync); + for (const pl of P.pools) if (t >= pl.glowAt) light(S, pl.fp, smooth((t - pl.glowAt) / 0.25) * fade); + for (const it of P.items) { + if (t < it.at) continue; + const press = smooth((t - it.at) / 0.06) * fade; + stamp(S, it, press, it.agree ? smooth((t - it.glowAt) / 0.25) * fade : 0); + } + // The lamp, its azimuth counted anticlockwise from the right with up as + // up: at Lamp 0 it stands on the left and a little above. + const az = PI - 0.35 + 2 * PI * S.lampA + SWAY * sin((2 * PI * S.clock) / SWAY_T); + const Lx = cos(EL) * cos(az), Ly = -cos(EL) * sin(az), Lz = sin(EL); + const st = S.st, stD = S.stD, dn = S.dn, dD = S.dD, gl = S.glow; + for (let p = 0; p < N; p++) { + const nx = st[p * 3] + dn[p * 3], ny = st[p * 3 + 1] + dn[p * 3 + 1], nz = 1 + st[p * 3 + 2] + dn[p * 3 + 2]; + const b = cl((nx * Lx + ny * Ly + nz * Lz) / Lz, 0, 2.2); + const sh = (AMB + (1 - AMB) * b) * (1 - OCC * cl(stD[p] + dD[p], 0, 1)); + let r = CLAY[0] * sh, g = CLAY[1] * sh, bl = CLAY[2] * sh; + const gw = cl(gl[p], 0, 1) * 0.9; + if (gw > 0) { + const lit = 0.45 + 0.4 * min(1, sh); + r += (GLOW[0] * lit - r) * gw; + g += (GLOW[1] * lit - g) * gw; + bl += (GLOW[2] * lit - bl) * gw; + } + display.setPixel(p % W, floor(p / W), cl(round(r), 0, 255), cl(round(g), 0, 255), cl(round(bl), 0, 255)); + } +} From 029c78dd9402a06f9d88810e40034f18598b26e9 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 31 Oct 2026 20:24:26 +0000 Subject: [PATCH 6/9] =?UTF-8?q?=ED=8C=A8=ED=84=B4=20=EC=97=B0=EA=B5=AC=208?= =?UTF-8?q?1=ED=8C=90:=20Head=20Wave,=20Cornsweet=20Letters?= 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_01Bc4Jkpfqox342J4FE4ZD1C --- .../patterns/cornsweet-letters.js | 232 ++++++++++++++++++ incoming/pattern-study/patterns/head-wave.js | 185 ++++++++++++++ 2 files changed, 417 insertions(+) create mode 100644 incoming/pattern-study/patterns/cornsweet-letters.js create mode 100644 incoming/pattern-study/patterns/head-wave.js diff --git a/incoming/pattern-study/patterns/cornsweet-letters.js b/incoming/pattern-study/patterns/cornsweet-letters.js new file mode 100644 index 00000000..993bdd3f --- /dev/null +++ b/incoming/pattern-study/patterns/cornsweet-letters.js @@ -0,0 +1,232 @@ +// ===== Patternflow pattern ===== +// Title: Cornsweet Letters +// Family: type +// Idea: Three bold letters stacked down the tall panel are filled with exactly the ground's own colour and differ from it only along their outlines, where a sharp step falls back to the ground level within 4.5 LEDs inside and 6 outside, so a letter whose step is lighter on the inside looks lighter than the ground and one whose step is darker looks darker, and as each letter's step turns over, a third of a turn after the one above, the same untouched interiors seem to brighten, vanish into the ground and darken. +// Author: patternflow-lab +// Date: 2026-10-31 +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Lineage: the Craik-O'Brien-Cornsweet illusion (Vernon O'Brien, 1958; Kenneth Craik, 1966; Tom Cornsweet, 1970), with letters drawn for this pattern as round-ended strokes, pattern round 81 +// Round: 81 +// =============================== +// @matrix 64x128 +// @knobs Hue=0..1, Speed=0.2..3, Word=0..1, Cover=0..1 +// +// Knob 1: Hue. The ground's hue, letters and all, since the letters are the ground. At 0 a slate azure, at 0.25 violet, at 0.5 a terracotta brown, at 0.75 green. +// Knob 2: Speed. How fast each letter's step turns from lighter inside to darker inside and back: one turn in 12 s at 1, 6 s at the opening 2, 60 s at 0.2 and 4 s at 3. The letters are a third of a turn apart, so at any moment one of them is near its lightest and another near its darkest. +// Knob 3: Word. LIT, DIM, LUX (the unit of illuminance) and NIT (the unit of luminance), a quarter of the knob each. +// Knob 4: Cover. Lays a dark band over the outlines, the way a pencil laid along the edge of Cornsweet's figure takes the illusion away. Nothing up to 0.1, then the band grows until at 1 it covers the whole profile, 4.5 LEDs in and 6 out, and leaves the inner strips of each stroke next to the open ground, the same colour on both sides of the band. + +// The illusion carries the names of O'Brien, Craik and Cornsweet. Take a +// uniform field, and along a border put a sharp step that falls back on +// both sides to the field's own level: lighter just inside, darker just +// outside. The inside then looks lighter all the way across, though away +// from the border it sends out exactly the light the outside does. Cover +// the border and both sides look the same. +// +// Here the border is a letter's outline. Each LED's distance to it comes +// from a distance transform of the letter drawn five times finer than the +// panel. The profile is a parabola in that distance, a (1 - d/w)^2 with +// w = 4.5 LEDs inside and 6 outside, so it reaches the ground level exactly +// and the LEDs past it are the ground's own colour, bit for bit, inside the +// letter or not. The height a of the step turns as a cosine: positive, the +// letter looks lighter; zero, it is gone; negative, it looks darker. Where +// two letters' outer profiles meet in the gap between them they add. + +const W = 64, H = 128, N = W * H; +const { floor, min, max, round, sqrt, cos, sin, PI } = Math; +const cl = (v, a, b) => (v < a ? a : v > b ? b : v); + +// Ground level, the step's half height at its largest, and how far the +// profile reaches inside and outside (LEDs). +const G = 118, C = 88, WIN = 4.5, WOUT = 6; +const SS = 5; // the distance grid has SS x SS cells per LED +const HUE0 = 0.55, SAT = 0.62, COVER = 26; + +// Letters as strokes on a unit box, x right and y down. "a" is an arc: +// [cx, cy, rx, ry, from, to] in degrees, 0 to the right, 90 down. +const GLYPHS = { + L: [[0, 0, 0, 1], [0, 1, 1, 1]], + I: [[0.5, 0, 0.5, 1]], + T: [[0, 0, 1, 0], [0.5, 0, 0.5, 1]], + D: [[0, 0, 0, 1], [0, 0, 0.42, 0], [0, 1, 0.42, 1], ["a", 0.42, 0.5, 0.58, 0.5, -90, 90]], + M: [[0, 1, 0, 0], [0, 0, 0.5, 0.62], [0.5, 0.62, 1, 0], [1, 0, 1, 1]], + U: [[0, 0, 0, 0.5], ["a", 0.5, 0.5, 0.5, 0.5, 180, 0], [1, 0.5, 1, 0]], + X: [[0, 0, 1, 1], [1, 0, 0, 1]], + N: [[0, 1, 0, 0], [0, 0, 1, 1], [1, 1, 1, 0]], +}; +const WORDS = ["LIT", "DIM", "LUX", "NIT"]; +// Strokes 14 LEDs thick with round ends, letters 36 rows tall and 52 wide +// with 6 rows between them and 4 above the first and below the last. +const RAD = 7, X0 = 13, X1 = 51, LH = 36, GAP = 6, TOP0 = 4; + +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; + return o; +} + +export function setup(params) { + params.s = { + hue: 0, speed: 2, word: 0, cover: 0.06, t: 0, built: -1, n: 3, + dist: [0, 1, 2].map(() => new Float32Array(N)), inside: [0, 1, 2].map(() => new Uint8Array(N)), + lut: new Uint8Array(256 * 3), lh: -1, c: [0, 0, 0], + }; +} + +// Letter k's strokes as panel segments [x0, y0, x1, y1]. +function strokes(ch, k) { + const segs = []; + const top = TOP0 + k * (LH + GAP) + RAD, bot = TOP0 + k * (LH + GAP) + LH - RAD; + const px = (u) => X0 + (X1 - X0) * u, py = (v) => top + (bot - top) * v; + for (const s of GLYPHS[ch]) { + if (s[0] === "a") { + const [, cx, cy, rx, ry, a0, a1] = s, n = 12; + for (let j = 0; j < n; j++) { + const t0 = ((a0 + ((a1 - a0) * j) / n) * PI) / 180, t1 = ((a0 + ((a1 - a0) * (j + 1)) / n) * PI) / 180; + segs.push([px(cx + rx * cos(t0)), py(cy + ry * sin(t0)), px(cx + rx * cos(t1)), py(cy + ry * sin(t1))]); + } + } else segs.push([px(s[0]), py(s[1]), px(s[2]), py(s[3])]); + } + return segs; +} + +function segDist(x, y, s) { + const dx = s[2] - s[0], dy = s[3] - s[1], l2 = dx * dx + dy * dy; + const t = l2 > 0 ? cl(((x - s[0]) * dx + (y - s[1]) * dy) / l2, 0, 1) : 0; + const ex = x - s[0] - t * dx, ey = y - s[1] - t * dy; + return sqrt(ex * ex + ey * ey); +} + +// One pass of the squared Euclidean distance transform along a line: the +// lower envelope of parabolas (Felzenszwalb and Huttenlocher, 2004). +function edt1(f, n, d, v, z) { + let k = 0; + v[0] = 0; + z[0] = -1e20; + z[1] = 1e20; + for (let q = 1; q < n; q++) { + let s = (f[q] + q * q - (f[v[k]] + v[k] * v[k])) / (2 * q - 2 * v[k]); + while (s <= z[k]) { + k--; + s = (f[q] + q * q - (f[v[k]] + v[k] * v[k])) / (2 * q - 2 * v[k]); + } + k++; + v[k] = q; + z[k] = s; + z[k + 1] = 1e20; + } + k = 0; + for (let q = 0; q < n; q++) { + while (z[k + 1] < q) k++; + d[q] = (q - v[k]) * (q - v[k]) + f[v[k]]; + } +} + +function edt(grid, gw, gh) { + const n = max(gw, gh), f = new Float64Array(n), d = new Float64Array(n), v = new Int32Array(n), z = new Float64Array(n + 1); + for (let x = 0; x < gw; x++) { + for (let y = 0; y < gh; y++) f[y] = grid[y * gw + x]; + edt1(f, gh, d, v, z); + for (let y = 0; y < gh; y++) grid[y * gw + x] = d[y]; + } + for (let y = 0; y < gh; y++) { + for (let x = 0; x < gw; x++) f[x] = grid[y * gw + x]; + edt1(f, gw, d, v, z); + for (let x = 0; x < gw; x++) grid[y * gw + x] = d[x]; + } +} + +// For each letter, every LED's distance to its outline and which side the +// LED is on. Each letter gets its own field over its rows and 8 more above +// and below, so where two outer profiles overlap they can add. +function build(S) { + const word = WORDS[S.word], INF = 1e12, gw = W * SS, h = SS >> 1; + S.n = word.length; + for (let k = 0; k < word.length; k++) { + const segs = strokes(word[k], k), D = S.dist[k], IN = S.inside[k]; + D.fill(1e9); + IN.fill(0); + const y0 = max(0, TOP0 + k * (LH + GAP) - 8), y1 = min(H, TOP0 + k * (LH + GAP) + LH + 8), gh = (y1 - y0) * SS; + const toIn = new Float64Array(gw * gh), toOut = new Float64Array(gw * gh), ins = new Uint8Array(gw * gh); + for (let gy = 0; gy < gh; gy++) + for (let gx = 0; gx < gw; gx++) { + const x = (gx + 0.5) / SS, y = y0 + (gy + 0.5) / SS; + let dmin = 1e9; + for (const s of segs) { + const dd = segDist(x, y, s); + if (dd < dmin) dmin = dd; + } + const i = gy * gw + gx, inn = dmin <= RAD ? 1 : 0; + ins[i] = inn; + toIn[i] = inn ? 0 : INF; + toOut[i] = inn ? INF : 0; + } + edt(toIn, gw, gh); + edt(toOut, gw, gh); + for (let y = y0; y < y1; y++) + for (let x = 0; x < W; x++) { + const g = ((y - y0) * SS + h) * gw + x * SS + h, i = y * W + x; + IN[i] = ins[g]; + // From the cell's centre to the nearest cell on the other side, + // less half a cell: the distance to the outline between them. + D[i] = (sqrt(ins[g] ? toOut[g] : toIn[g]) - 0.5) / SS; + } + } +} + +export function update(dt, input, params) { + if (!params.s) setup(params); + const S = params.s, k = input.knobValues || [0, 2, 0, 0.06], d = cl(dt || 0, 0, 0.1); + S.hue = k[0] || 0; + S.speed = cl(k[1] == null ? 2 : k[1], 0.2, 3); + S.word = min(WORDS.length - 1, floor(cl(k[2] || 0, 0, 1) * WORDS.length)); + S.cover = cl(k[3] == null ? 0.06 : k[3], 0, 1); + S.t += d * S.speed; + if (S.t > 1200) S.t -= 1200; + if (S.built !== S.word) { + S.built = S.word; + build(S); + } +} + +// One colour for everything: the ground's hue and saturation at each level. +function ramp(S) { + if (S.lh === S.hue) return; + S.lh = S.hue; + for (let v = 0; v < 256; v++) { + hsv(S.hue + HUE0, SAT, v / 255, S.c); + S.lut[v * 3] = round(S.c[0] * 255); + S.lut[v * 3 + 1] = round(S.c[1] * 255); + S.lut[v * 3 + 2] = round(S.c[2] * 255); + } +} + +export function draw(display, params) { + const S = params.s; + if (!S) return; + ramp(S); + const L = S.lut, n = S.n; + // Each letter's step height, a third of a turn behind the letter above. + const amp = [0, 0, 0]; + for (let k = 0; k < n; k++) amp[k] = C * cos(2 * PI * (S.t / 12 - k / 3)); + const cv = max(0, S.cover - 0.1) / 0.9, cin = cv * WIN, cout = cv * WOUT; + for (let y = 0; y < H; y++) + for (let x = 0; x < W; x++) { + const i = y * W + x; + let v = G, covered = false; + for (let k = 0; k < n; k++) { + const dd = S.dist[k][i]; + if (dd >= WOUT) continue; + if (S.inside[k][i]) { + if (dd < cin) covered = true; + else if (dd < WIN) v += amp[k] * (1 - dd / WIN) * (1 - dd / WIN); + } else if (dd < cout) covered = true; + else v -= amp[k] * (1 - dd / WOUT) * (1 - dd / WOUT); + } + const o = (covered ? COVER : round(cl(v, 0, 255))) * 3; + display.setPixel(x, y, L[o], L[o + 1], L[o + 2]); + } +} diff --git a/incoming/pattern-study/patterns/head-wave.js b/incoming/pattern-study/patterns/head-wave.js new file mode 100644 index 00000000..1f23cc75 --- /dev/null +++ b/incoming/pattern-study/patterns/head-wave.js @@ -0,0 +1,185 @@ +// ===== Patternflow pattern ===== +// Title: Head Wave +// Family: physics +// Idea: A cross-section of two-layer ground stands the height of the panel, a slow crust over a mantle 1.35 times faster with a flat boundary across row 64, and a source just above the boundary at the left edge sends out pulses whose first arrivals, worked out exactly by Fermat's principle, run as banded fronts: circles in the crust, wider circles bent through the boundary in the mantle, and beside them a straight conical front in the mantle's colour that the boundary leaks back up into the crust at the critical angle of 47.8 degrees, arriving first over a wedge that reaches 33 rows up at the right edge. +// Author: patternflow-lab +// Date: 2026-10-31 +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Lineage: head waves and the crust-mantle boundary found from them (Andrija Mohorovicic, 1909); first arrivals by Fermat's principle (Pierre de Fermat, 1662), pattern round 81 +// Round: 81 +// =============================== +// @matrix 64x128 +// @knobs Hue=0..1, Speed=0.2..3, Ratio=1.35..2.5, Height=2..50 +// +// Knob 1: Hue. Turns both layers round the wheel together. At 0 the crust and every front that stayed in it are sea green and the mantle with the head wave it sends up is rose; at 0.5 the crust is red and the mantle green. +// Knob 2: Speed. How many bands leave the source a second: half the knob, so one a second at the opening 2, a front crossing the crust at 4.4 LEDs a second and the mantle at 1.35 times that; 0.1 a second at 0.2 and 1.5 at 3. +// Knob 3: Ratio. The mantle's speed over the crust's, from 1.35, about the ratio between crust and upper mantle, to 2.5. The critical angle asin(1/ratio) falls from 47.8 to 23.6 degrees, so the conical front tips from a diagonal towards flat, the wedge where it leads grows from 11% of the panel to 25% and reaches the top row at the right edge, and the mantle's bands widen from 5.9 to 11 LEDs. +// Knob 4: Height. How far above the boundary the source sits, 2 to 50 LEDs. At the opening 2 the head wave leads from 2.2 LEDs out along the boundary and its wedge reaches 33 rows up at the right edge; at 20 it leads only from 22 LEDs out and reaches 6 rows up; at 50 it meets the boundary 55 LEDs out and hardly shows. +// Buttons: Any tap moves the source 16 LEDs to the right, from the left edge through 16, 32 and 48 to the right edge and back. + +// Everything is measured in LEDs, and time in LEDs of travel through the +// slow layer, so the crust's speed is 1 and the mantle's is r. A pulse +// reaches each point first along the path of least time (Fermat), and there +// are three candidates. +// +// Direct: the straight line from the source, T = distance. Its fronts are +// circles. +// +// Refracted, for points below the boundary: down to some point m on the +// boundary, then on to the point at speed r. T(m) = |s - m| + |m - p| / r is +// convex in m, so a few Newton steps find the minimum, and setting its +// derivative to zero is Snell's law, sin(i) = sin(t) / r. +// +// Head wave, for points above the boundary: down to the boundary at the +// critical angle ic = asin(1/r), along the boundary at speed r, and back up +// at ic. With hs the source's height and hr the point's height above the +// boundary and dx the distance across, +// +// T = dx / r + (hs + hr) cos(ic), +// +// which is a plane: the conical front. It exists once dx is at least +// (hs + hr) tan(ic), and it arrives first wherever it beats the circle. +// Mohorovicic read exactly this from the Kupa valley earthquake of +// 8 October 1909: beyond some distance a faster phase overtook the direct +// one, and the distance gave him the depth of a faster layer underneath. +// +// The travel time is worked out once whenever the source, the ratio or the +// height change. Each frame only reads it: an LED's light is a sawtooth of +// (t - T) over one period, bright as a front arrives and fading until the +// next, and the source never stops, so every LED is always somewhere in a +// band. +// +// A band climbs to its crest over its width. When its front runs along a +// row or a column the whole climb falls between neighbours in one +// direction; when it runs diagonally the climb splits between the row and +// the column. So each LED's band is drawn as tall as it can be with no step +// between neighbours above 39.5 levels: diagonal fronts, the head wave among +// them, stand at full height and fronts square to the panel at about two +// thirds of it. The drop at the back of each band is the only hard step. + +const W = 64, H = 128, N = W * H; +const { floor, min, max, round, sqrt, abs } = Math; +const cl = (v, a, b) => (v < a ? a : v > b ? b : v); + +// The boundary's row, the band width in the crust (LEDs), the largest step +// between two neighbouring LEDs a band may climb, and its full height. +const YB = 64, P = 4.4, CAP = 39.5, TOP = 255; +const SRC_X = [0.5, 16, 32, 48, 63.5]; +// Crust, then mantle (and the head wave, which spent its fast stretch there). +const HUES = [0.47, 0.91]; + +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; + return o; +} + +export function setup(params) { + params.s = { + hue: 0, speed: 2, ratio: 1.35, height: 2, src: 0, tau: 0, key: "", + ph: new Float32Array(N), amp: new Float32Array(N), fast: new Uint8Array(N), + lut: new Uint8Array(2 * 256 * 3), lh: -1, c: [0, 0, 0], + }; +} + +// First arrivals (in band periods) and each LED's band height. +function solve(S) { + const r = S.ratio, xs = SRC_X[S.src], hs = S.height, ys = YB - hs; + const ci = sqrt(1 - 1 / (r * r)), ti = 1 / sqrt(r * r - 1); + const PH = S.ph, A = S.amp, F = S.fast; + for (let y = 0; y < H; y++) { + const py = y + 0.5; + let m = xs; + for (let x = 0; x < W; x++) { + const px = x + 0.5, i = y * W + x, dx = abs(px - xs); + // t is the travel time, (gx, gy) the size of its slope along the row + // and down the column. + let t, gx, gy; + if (py < YB) { + const dy = py - ys, rr = sqrt(dx * dx + dy * dy) + 1e-6, hr = YB - py; + t = rr; + gx = dx / rr; + gy = abs(dy) / rr; + F[i] = 0; + if (dx >= (hs + hr) * ti) { + const th = dx / r + (hs + hr) * ci; + if (th < t) { + t = th; + gx = 1 / r; + gy = ci; + F[i] = 1; + } + } + } else { + // Newton on the crossing point, started from the last LED's. + const dz = py - YB, a = min(xs, px), b = max(xs, px); + m = cl(m, a, b); + for (let k = 0; k < 6; k++) { + const d1 = sqrt((m - xs) * (m - xs) + hs * hs), d2 = sqrt((px - m) * (px - m) + dz * dz); + const g = (m - xs) / d1 - (px - m) / (r * d2); + const gp = (hs * hs) / (d1 * d1 * d1) + (dz * dz) / (r * d2 * d2 * d2); + m = cl(m - g / gp, a, b); + } + const d1 = sqrt((m - xs) * (m - xs) + hs * hs), d2 = sqrt((px - m) * (px - m) + dz * dz) + 1e-6; + t = d1 + d2 / r; + gx = abs(px - m) / (r * d2); + gy = dz / (r * d2); + F[i] = 1; + } + PH[i] = t / P; + A[i] = min(TOP, (CAP * P) / max(gx, gy, 1e-6)); + } + } +} + +export function update(dt, input, params) { + if (!params.s) setup(params); + const S = params.s, k = input.knobValues || [0, 2, 1.35, 2], d = cl(dt || 0, 0, 0.1); + S.hue = k[0] || 0; + S.speed = cl(k[1] == null ? 2 : k[1], 0.2, 3); + S.ratio = cl(k[2] == null ? 1.35 : k[2], 1.35, 2.5); + S.height = cl(k[3] == null ? 2 : k[3], 2, 50); + const b = input.btnPressed; + if (b && (b[0] || b[1] || b[2] || b[3])) S.src = (S.src + 1) % SRC_X.length; + S.tau += d * S.speed * 0.5; + if (S.tau > 1000) S.tau -= 1000; + const key = S.ratio.toFixed(3) + "/" + S.height.toFixed(2) + "/" + S.src; + if (key !== S.key) { + S.key = key; + solve(S); + } +} + +// One ramp per layer, indexed by the LED's level: saturated in the fading +// tail of a band, paler at the crest. +function ramp(S) { + if (S.lh === S.hue) return; + S.lh = S.hue; + for (let z = 0; z < 2; z++) + for (let v = 0; v < 256; v++) { + const a = v / 255; + hsv(S.hue + HUES[z], 0.9 - 0.5 * a * a * a, a, S.c); + const o = (z * 256 + v) * 3; + S.lut[o] = round(S.c[0] * 255); + S.lut[o + 1] = round(S.c[1] * 255); + S.lut[o + 2] = round(S.c[2] * 255); + } +} + +export function draw(display, params) { + const S = params.s; + if (!S) return; + ramp(S); + const PH = S.ph, A = S.amp, F = S.fast, Lut = S.lut, tau = S.tau; + for (let y = 0; y < H; y++) + for (let x = 0; x < W; x++) { + const i = y * W + x; + let u = tau - PH[i]; + u -= floor(u); + const o = (F[i] * 256 + round(A[i] * (1 - u))) * 3; + display.setPixel(x, y, Lut[o], Lut[o + 1], Lut[o + 2]); + } +} From ee2d76fa32501c9d9a839fb8d0a0758be66a2646 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 31 Oct 2026 20:33:19 +0000 Subject: [PATCH 7/9] =?UTF-8?q?pattern-study:=2081=ED=8C=90=20README=20?= =?UTF-8?q?=ED=91=9C=EC=99=80=20=ED=8C=90=20=EC=A0=88,=20=EB=8B=A4?= =?UTF-8?q?=EC=9D=8C=20=ED=8C=90=20=EA=B0=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01Bc4Jkpfqox342J4FE4ZD1C --- incoming/pattern-study/README.md | 101 ++++++++++++++++++++++++++++++- 1 file changed, 98 insertions(+), 3 deletions(-) diff --git a/incoming/pattern-study/README.md b/incoming/pattern-study/README.md index 3e0d5b5a..b92cc42d 100644 --- a/incoming/pattern-study/README.md +++ b/incoming/pattern-study/README.md @@ -1,6 +1,6 @@ # 팹턮 연구 2026-10 -새 팹턮 698개륌 쉰여섯 갈래로 나눠 만듀었얎요. 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판은 할로윈 몫읎 쉬게 된 첫 판읎띌, 수학 대상 ì—Ž 개륌 ì—Ž 갈래에 하나씩 더했얎요. 54판도 수학 대상 ì—Ž 개륌 팚턎읎 적은 ì—Ž 갈래에 하나씩 더했얎요. 55판도 수학 대상 ì—Ž 개륌 ì—Ž 갈래에 하나씩 더했얎요. 56판도 수학 대상 ì—Ž 개륌 ì—Ž 갈래에 하나씩 더했얎요. 57판도 수학 대상 ì—Ž 개륌 팚턎읎 적은 ì—Ž 갈래에 하나씩 더했얎요. 58판도 수학 대상 ì—Ž 개륌 팚턎읎 적은 ì—Ž 갈래에 하나씩 더했얎요. 59판도 수학 대상 ì—Ž 개륌 ì—Ž 갈래에 하나씩 더했얎요. 60판도 수학 대상 ì—Ž 개륌 ì—Ž 갈래에 하나씩 더했얎요. 61판도 수학 대상 ì—Ž 개륌 팚턎읎 적은 갈래부터 ì—Ž 갈래에 하나씩 더했얎요. 62판도 수학 대상 ì—Ž 개륌 ì—Ž 갈래에 하나씩 더했얎요. 63판은 64판곌 같읎 죌제 표에 「타읎포」가 듀얎옚 첫 판읎띌, Ꞁ자가 듀얎간 팹턮 닀섯을 Ꞁ자 갈래에, 수학 대상 닀섯을 닀섯 갈래에 하나씩 더했얎요. 64판은 죌제 표가 바뀌얎 Ꞁ자 팹턮 닀섯을 Ꞁ자 갈래에, 수학 대상 닀섯을 ë„€ 갈래에 더했얎요. 65판도 같은 표로 돌아서 Ꞁ자 팹턮 닀섯을 Ꞁ자 갈래에, 수학 대상 닀섯을 닀섯 갈래에 하나씩 더했얎요. 66판도 같은 표로 돌아서 Ꞁ자 팹턮 넷을 Ꞁ자 갈래에, 하나륌 데읎터 귞늌 갈래에 더하고, 수학 대상 닀섯을 닀섯 갈래에 하나씩 더했얎요. 67판도 같은 표로 돌아서 Ꞁ자 팹턮 닀섯을 Ꞁ자 갈래에, 수학 대상 닀섯을 닀섯 갈래에 하나씩 더했얎요. 68판도 같은 표로 돌아서 Ꞁ자 팹턮 닀섯을 Ꞁ자 갈래에, 수학 대상 닀섯을 닀섯 갈래에 하나씩 더했얎요. 69판도 같은 표로 돌아서 Ꞁ자 팹턮 닀섯을 Ꞁ자 갈래에, 수학 대상 닀섯을 닀섯 갈래에 하나씩 더했얎요. 70판도 같은 표로 돌아서 Ꞁ자 팹턮 닀섯을 Ꞁ자 갈래에, 수학 대상 닀섯을 닀섯 갈래에 하나씩 더했얎요. 71판도 같은 표로 돌아서 Ꞁ자 팹턮 닀섯을 Ꞁ자 갈래에, 수학 대상 닀섯을 닀섯 갈래에 하나씩 더했얎요. 72판도 같은 표로 돌아서 Ꞁ자 팹턮 닀섯을 Ꞁ자 갈래에, 수학 대상 닀섯을 닀섯 갈래에 하나씩 더했얎요. 73판도 같은 표로 돌아서 Ꞁ자 팹턮 닀섯을 Ꞁ자 갈래에, 수학 대상 닀섯을 닀섯 갈래에 하나씩 더했얎요. 74판도 같은 표로 돌아서 Ꞁ자 팹턮 닀섯을 Ꞁ자 갈래에, 수학 대상 닀섯을 닀섯 갈래에 하나씩 더했얎요. 75판도 같은 표로 돌아서 Ꞁ자 팹턮 닀섯을 Ꞁ자 갈래에, 수학 대상 닀섯을 닀섯 갈래에 하나씩 더했얎요. 76판도 같은 표로 돌아서 Ꞁ자 팹턮 닀섯을 Ꞁ자 갈래에, 수학 대상 닀섯을 닀섯 갈래에 하나씩 더했얎요. 77판도 같은 표로 돌아서 Ꞁ자 팹턮 닀섯을 Ꞁ자 갈래에, 수학 대상 닀섯을 닀섯 갈래에 하나씩 더했얎요. 78판도 같은 표로 돌아서 Ꞁ자 팹턮 닀섯을 Ꞁ자 갈래에, 수학 대상 닀섯을 닀섯 갈래에 하나씩 더했얎요. 79판도 같은 표로 돌아서 Ꞁ자 팹턮 닀섯을 Ꞁ자 갈래에, 수학 대상 닀섯을 닀섯 갈래에 하나씩 더했얎요. [index.html](index.html) 을 람띌우저로 엎멎 전부 한 장에서 돌아가요. ë§š 위 지도에서 점을 누륎멎 ê·ž 팚턎윌로 가고, 칎드의 슬띌읎더가 녾뾌 넷읎에요. 마음에 드는 것은 「윔드 복사」로 가젞가 Pattern Lab 의 CODE 칞에 붙여 넣윌멎 같은 귞늌읎 나와요. 페읎지는 파음 하나띌서 바깥에서 받아 였는 것읎 없얎요. +새 팹턮 708개륌 쉰여섯 갈래로 나눠 만듀었얎요. 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판은 할로윈 몫읎 쉬게 된 첫 판읎띌, 수학 대상 ì—Ž 개륌 ì—Ž 갈래에 하나씩 더했얎요. 54판도 수학 대상 ì—Ž 개륌 팚턎읎 적은 ì—Ž 갈래에 하나씩 더했얎요. 55판도 수학 대상 ì—Ž 개륌 ì—Ž 갈래에 하나씩 더했얎요. 56판도 수학 대상 ì—Ž 개륌 ì—Ž 갈래에 하나씩 더했얎요. 57판도 수학 대상 ì—Ž 개륌 팚턎읎 적은 ì—Ž 갈래에 하나씩 더했얎요. 58판도 수학 대상 ì—Ž 개륌 팚턎읎 적은 ì—Ž 갈래에 하나씩 더했얎요. 59판도 수학 대상 ì—Ž 개륌 ì—Ž 갈래에 하나씩 더했얎요. 60판도 수학 대상 ì—Ž 개륌 ì—Ž 갈래에 하나씩 더했얎요. 61판도 수학 대상 ì—Ž 개륌 팚턎읎 적은 갈래부터 ì—Ž 갈래에 하나씩 더했얎요. 62판도 수학 대상 ì—Ž 개륌 ì—Ž 갈래에 하나씩 더했얎요. 63판은 64판곌 같읎 죌제 표에 「타읎포」가 듀얎옚 첫 판읎띌, Ꞁ자가 듀얎간 팹턮 닀섯을 Ꞁ자 갈래에, 수학 대상 닀섯을 닀섯 갈래에 하나씩 더했얎요. 64판은 죌제 표가 바뀌얎 Ꞁ자 팹턮 닀섯을 Ꞁ자 갈래에, 수학 대상 닀섯을 ë„€ 갈래에 더했얎요. 65판도 같은 표로 돌아서 Ꞁ자 팹턮 닀섯을 Ꞁ자 갈래에, 수학 대상 닀섯을 닀섯 갈래에 하나씩 더했얎요. 66판도 같은 표로 돌아서 Ꞁ자 팹턮 넷을 Ꞁ자 갈래에, 하나륌 데읎터 귞늌 갈래에 더하고, 수학 대상 닀섯을 닀섯 갈래에 하나씩 더했얎요. 67판도 같은 표로 돌아서 Ꞁ자 팹턮 닀섯을 Ꞁ자 갈래에, 수학 대상 닀섯을 닀섯 갈래에 하나씩 더했얎요. 68판도 같은 표로 돌아서 Ꞁ자 팹턮 닀섯을 Ꞁ자 갈래에, 수학 대상 닀섯을 닀섯 갈래에 하나씩 더했얎요. 69판도 같은 표로 돌아서 Ꞁ자 팹턮 닀섯을 Ꞁ자 갈래에, 수학 대상 닀섯을 닀섯 갈래에 하나씩 더했얎요. 70판도 같은 표로 돌아서 Ꞁ자 팹턮 닀섯을 Ꞁ자 갈래에, 수학 대상 닀섯을 닀섯 갈래에 하나씩 더했얎요. 71판도 같은 표로 돌아서 Ꞁ자 팹턮 닀섯을 Ꞁ자 갈래에, 수학 대상 닀섯을 닀섯 갈래에 하나씩 더했얎요. 72판도 같은 표로 돌아서 Ꞁ자 팹턮 닀섯을 Ꞁ자 갈래에, 수학 대상 닀섯을 닀섯 갈래에 하나씩 더했얎요. 73판도 같은 표로 돌아서 Ꞁ자 팹턮 닀섯을 Ꞁ자 갈래에, 수학 대상 닀섯을 닀섯 갈래에 하나씩 더했얎요. 74판도 같은 표로 돌아서 Ꞁ자 팹턮 닀섯을 Ꞁ자 갈래에, 수학 대상 닀섯을 닀섯 갈래에 하나씩 더했얎요. 75판도 같은 표로 돌아서 Ꞁ자 팹턮 닀섯을 Ꞁ자 갈래에, 수학 대상 닀섯을 닀섯 갈래에 하나씩 더했얎요. 76판도 같은 표로 돌아서 Ꞁ자 팹턮 닀섯을 Ꞁ자 갈래에, 수학 대상 닀섯을 닀섯 갈래에 하나씩 더했얎요. 77판도 같은 표로 돌아서 Ꞁ자 팹턮 닀섯을 Ꞁ자 갈래에, 수학 대상 닀섯을 닀섯 갈래에 하나씩 더했얎요. 78판도 같은 표로 돌아서 Ꞁ자 팹턮 닀섯을 Ꞁ자 갈래에, 수학 대상 닀섯을 닀섯 갈래에 하나씩 더했얎요. 79판도 같은 표로 돌아서 Ꞁ자 팹턮 닀섯을 Ꞁ자 갈래에, 수학 대상 닀섯을 닀섯 갈래에 하나씩 더했얎요. 81판도 같은 표로 돌아서 Ꞁ자 팹턮 닀섯을 Ꞁ자 갈래에, 수학 대상 닀섯을 닀섯 갈래에 하나씩 더했얎요. [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판에서 섞로로 닀시 ì§°ì–Žìš”. @@ -1095,6 +1095,21 @@ | [Huygens' Clocks](patterns/huygens-clocks.js) | 늬듬·펄슀 | 한 듀볎에 맀닚 ꞎ 진자 시계 둘읎 짞깍음 때마닀 번쩍읎며 제 쀄에 막대륌 떚얎뜚늬고, 거의 같읎 치던 짞깍읎 음곱 번쯀 흔듀늬는 사읎 ꌭ 반 박자씩 엇갈렀요. | | [EKG Sequence](patterns/ekg-sequence.js) | 데읎터 귞늌 | EKG 수엎을 한 항에 한 쀄씩 썚 낎렀가며 값을 가로로 찍얎요. 항은 대개 a = n 곁에 붙고, 홀수 소수 p 마닀 2p, p, 3p 가 n/2 와 3n/2 의 흐늰 안낎선까지 뻗는 획읎 되얎 왌쪜에 ꞈ빛윌로 ê·ž 소수가 적혀요. | +### 81판 + +| 팹턮 | 갈래 | 한 쀄 | +| --- | --- | --- | +| [Baudot Tape](patterns/baudot-tape.js) | Ꞁ자 | 닀섯 구멍 천공 테읎프가 팹널 높읎로 올띌가고, 쀄마닀 뚫늰 ITA2 부혞륌 읜은 Ꞁ자가 ê·ž 옆에 찍혀요. | +| [Permutation Poem](patterns/permutation-poem.js) | Ꞁ자 | 구절 하나의 낱말 찚례륌 몚두 낎렀 쓰고, 새 쀄마닀 읎웃한 두 낱말읎 흰빛윌로 믞끄러젞 자늬륌 바꿔요. | +| [Cornsweet Letters](patterns/cornsweet-letters.js) | Ꞁ자 | 속읎 바탕곌 똑같은 색읞 Ꞁ자 셋읎 테두늬의 밝Ʞ 계닚만윌로 바탕볎닀 밝아 볎였닀가 얎두워 볎여요. | +| [YBC 7289](patterns/ybc-7289.js) | Ꞁ자 | 등불 아래 점토판 YBC 7289 의 정사각형곌 쐐Ʞ 숫자가 서고, 아래 점토에 1;30 에서 시작한 평균 셈읎 한 쀄씩 눌렀 썚지며 √2 와 맞는 자늬가 청록윌로 쌜젞요. | +| [Beading Letters](patterns/beading-letters.js) | Ꞁ자 | 부얎 쓎 액첎 낱말의 획읎 반지늄의 9.6ë°° 묌결로 잘록핎지닀 끊겚, 같은 간격 방욞로 찍은 점 Ꞁ자가 돌요. | +| [Modular Hyperbola](patterns/modular-hyperbola.js) | 수론 | 소수 p 로 나눈 곱셈표륌 톱니로 칠핎서, 흰 날읎 몚듈러 쌍곡선 xy ≡ c 륌 귞늬며 판을 옮겚 닀녀요. | +| [Head Wave](patterns/head-wave.js) | 묌늬·슀프링 | 느며 지각 밑 1.35ë°° 빠륞 맚틀의 겜계륌 타고 옚 뚞늬파가 곧은 원뿔 앞선윌로 원형 앞선을 앞질러 올띌와요. | +| [Dehn Twist](patterns/dehn-twist.js) | 위상·맀듭 | 원환멎의 닫힌 곡선읎 고늬띠륌 따띌 한 바퀮 끌렀 돌고 펎질 때마닀 류가 (p, q) 에서 (p, q + p) 로 바뀌얎요. | +| [Malfatti Circles](patterns/malfatti-circles.js) | Ʞ하·대칭 | 천천히 몚양을 바꟞는 삌각형 하나가 위아래로 두 번 서서, 위의 말파티 섞 원볎닀 아래의 탐욕 섞 원읎 늘 더 넓게 덮얎요. | +| [Pollard's Rho](patterns/pollards-rho.js) | 알고늬슘 | x² + c 륌 숚은 소읞수로 나눈 나뚞지가 섞로로 선 ρ 륌 귞늬고, ê·ž 위륌 쫓는 거북읎와 토끌가 만나멎 gcd 가 1 에서 ê·ž 소읞수로 ë›°ì–Žìš”. | + ## 1판 갈래마닀 녾뾌 넷의 자늬는 갈래가 달띌도 거의 같아요. K1 은 색읎고 K2 는 빠륎Ʞ예요. K3 은 몚양을 바꟞고, K4 는 ê·ž 갈래만의 손잡읎예요. 귞래서 처음 볎는 팹턮도 K1 곌 K2 는 손읎 뚌저 알아요. @@ -4609,6 +4624,70 @@ Lab 에서 핮 볎Ʞ: K4 륌 Stein 에 두멎 쪜읎 위 섞 쀄만 쓰고 18~1 죌제 5 「수학 · 추상」윌로 데읎터 귞늌 갈래에 선 팚턎읎에요. 제프늬 띌가늬아슀(Jeffrey Lagarias), 에늭 레읞슀(Eric Rains), 닐 슬론(Neil Sloane)읎 2002년에 읎늄 붙읞 EKG 수엎(OEIS A064413)은 1, 2 로 시작핎, 앞 항곌 1 볎닀 큰 앜수륌 나누는 수 가욎데 아직 안 쓎 가장 작은 수륌 닀음 항윌로 놓아요. 귞래프가 심전도처럌 뛰얎서 붙은 읎늄읎에요. 팚턎은 쓎 수의 표와 소수마닀 가장 작은 안 쓎 배수륌 가늬킀는 자늬로 한 항씩 정확히 셈핎요. 한 쀄에 한 항을 낎렀 쓰고 값을 가로로 찍는데, 0 을 2ì—Ž, 가장 새 n 을 42엎에 두얎서 a = n/2, n, 3n/2 안낎선읎 팹널 위 n = 0 에서 나옚 빛삎읎 돌요. 처음 128항의 부채ꌎ은 n 읎 컀질수록 곧게 서요. 잰 값은 움직임 1.107, 쎘쎘핚 0.102, 가장자늬 0.996, 복잡도 0.117, 채움 0.139, 색 0.261, 몚양 바뀜 0.070 읎고, thirds 는 10/14/16, 녾뾌는 0.09, 0.09, 0.12, 0.04 예요. 첫 판은 K3 Zoom 읎 한 쀄에 여섯 항까지였는데, 거Ʞ서는 획읎 겹쳐 띠가 되얎서 넷까지로 쀄였얎요. 겹치는 소수 읎늄을 빌던 것은 섞 쀄까지 아래로 밀게 핎서, 307 곌 313 사읎에 빠졌던 311 읎 ì„°ì–Žìš”. K4 Links 는 두 항읎 나누는 가장 작은 소수로 걞음을 칠핎요. 3,000번짞 항 ë’€ 1,000걞음 가욎데 2 로 읎얎진 걞음은 310 낚짓읎고 493 읎 5 읎상의 소수로 읎얎젞서, a ≈ n 쀄읎 짝수의 쀄읎 아니띌 29 나 53 같은 소수로 잠깐씩 엮읞 쀄읞 것읎 볎였얎요. 50,000항 안의 홀수 소수 2,837 개는 ë‹€ 2p, p, 3p 로, 크Ʞ 찚례대로 와요. +## 81판 + +81판을 띄욞 때 같읎 도는 80판(#344)읎 ì—Žë € 있었얎요. 앞 바퀮 PR 에는 OWNER, MEMBER, COLLABORATOR 가 쓎 Ꞁ 가욎데 음Ꟍ Ꞁ읎 아닌 것읎 없얎서 따륌 Ꞁ읎 없었얎요. 죌제와 칞은 80판읎 PR 에 뚌저 밝힌 것을 비쌜 곚랐얎요. 타읎포 닀섯은 「타읎포 싀험」의 출발점을 하나씩 나눠 맡았얎요. Baudot Tape 는 섞로 팚널닀욎 Ꞁ, Permutation Poem 은 Ꞁ자로 ì§  묎늬, Cornsweet Letters 는 Ꞁ자와 빛, YBC 7289 는 숫자와 Ʞ혞 놀읎, Beading Letters 는 Ꞁ자의 몞읎에요. 닀섯 ë‹€ Ꞁ자 ꌎ을 윔드에 적었고, 옮겚 쓎 Ꞁꌎ은 없얎요. 수학 닀섯은 80판읎 고륞 섞포, 녞읎슈, 프랙탈, 공간, 선곌 79판읎 쓎 닀섯 갈래륌 비쌜서 수론, 묌늬·슀프링, 위상·맀듭, Ʞ하·대칭, 알고늬슘에 하나씩 ì„°ì–Žìš”. 앞의 셋은 79판 ë’€ 슀묌닀섯, 뒀의 둘은 슀묌여섯읎던 갈래예요. 「낯선 수학 예」와 「닀음 판 감」에서는 고륎지 않았얎요. ì—Ž 개는 닀섯 갈래에 둘씩 나눠 따로 짓고 합쳀얎요. + +### Baudot Tape + +죌제 7 「타읎포」의 「섞로 팚널닀욎 Ꞁ」곌 「Ꞁ자가 만듀얎지는 곌정」을 겚누고, 죌제 4 「섞로 64×128」을 같읎 채우는 팚턎읎에요. 에밀 볎도(Émile Baudot)가 1870년대에 만든 5닚위 전신 부혞는 Ꞁ자 하나륌 펄슀 닀섯윌로 볎낎요. 도널드 뚞늬(Donald Murray)가 1901년에 ê·ž 배엎을 자판곌 천공 테읎프에 맞게 고쳀고, 팚턎은 거Ʞ서 읎얎진 국제 전신 알파벳 2혞(ITA2)의 Ꞁ자 칞곌 숫자 칞을 썚요. 너비 41 LED 의 종읎 테읎프가 팹널 위아래 끝까지 읎얎지고, 쀄 간격은 8 LED 예요. 106행의 천공Ʞ에 펄슀가 하나 도착할 때마닀 ꞈ빛 선택 핀읎 쌜지고, 닀섯읎 ë‹€ 였멎 ê·ž 쀄읎 한꺌번에 뚫렀요. 작은 읎송 구멍은 셋짞와 ë„·ì§ž 구멍 사읎에 있얎요. 뚫늰 쀄 옆에는 수신Ʞ가 읜은 Ꞁ자가 흰색윌로 찍혔닀가 하늘색윌로 식얎요. 테읎프는 Speed 2 에서 1쎈에 섞 쀄씩 올띌가 엎두 쀄 위 10행의 판독Ʞ 핀 밑을 지나서, THE QUICK BROWN FOX 가 제 부혞 옆에서 위에서 아래로 읜혀요. 아묎것도 찍지 않는 LTR, FIG, CR, LF 는 3×5 작은 Ꞁ자로 흐늬게 적얎요. RYRY 시험에서는 R 01010 곌 Y 10101 읎 번갈아 테읎프가 첎크묎늬가 돌요. K4 Noise 는 한 Ꞁ자의 펄슀 하나가 뒀집힐 확률읎에요. 숫자 앞의 FIGS 가 잡음을 맞윌멎 수신Ʞ가 Ꞁ자 칞에 낚아서 1234567890 읎 분홍 QWERTYUIOP 로 찍혀요. ITA2 에서 1 부터 0 까지는 Q 부터 P 까지, 자판 ë§š 윗쀄 Ꞁ자와 같은 부혞예요. 잰 값은 움직임 0.859, 쎘쎘핚 0.064, 가장자늬 0.775, 복잡도 0.120, 채움 0.650, 색 0.157, 몚양 바뀜 0.060 읎고, thirds 는 65/64/64, 녾뾌는 0.11, 0.08, 0.04, 0.03 읎에요. 벜읎나 테두늬는 귞늬지 않았고, 위아래 끝까지 쌜진 것은 테읎프 종읎예요. 첫 판은 여는 자늬의 잡음읎 10% 띌서 THE QUICK B 가 THESWUICKNB 로 찍혔얎요. 6% 로 낮춘 판을 처음 잰 값은 움직임 0.659, 가장자늬 0.501, 채움 0.638 읎었고, 녾뾌는 0.09, 0.05, 0.03, 0.02 였얎요. 테읎프가 통짞로 바뀌얎도 바뀌는 LED 가 지늄 4 LED 의 구멍곌 1 LED 굵Ʞ의 Ꞁ자 획뿐읎띌 K3 Message 와 K4 Noise 가 낮았얎요. 구멍 반지늄을 1.85 에서 2.1 LED 로 킀우고 종읎륌 0.6 에서 0.72 로 밝혔얎요. 대묞자는 쌜진 LED 마닀 였륞쪜 칞을 하나 더 쌜서 굵게 찍었얎요. 잡음 맞은 쀄 옆 여백에 분홍 표륌 더하고 잡음을 녞람의 0.6 배로 바꟞자, 여는 자늬의 잡음은 3.6% 가 되고 K3 은 0.04, K4 는 0.03 읎 됐얎요. 가장자늬도 0.501 에서 0.775 로 올랐얎요. 굵게 찍고 볎니 G 의 가로획읎 6 의 가욎데 막대와 같아젞서 DOG 가 DO6 윌로 읜혔얎요. G 의 가로획 앞에 한 칞 틈을 두얎 둘을 갈랐얎요. + +Lab 에서 핮 볎Ʞ: K3 륌 1 에 두얎 RYRY 시험을 띄우고, Text 레읎얎에 Inter 굵게 40 px 쯀윌로 RY 륌 썚서 mask 로 얹윌멎 첎크묎늬 테읎프와 찍힌 Ꞁ자가 두 Ꞁ자 몚양 안에만 낚아요. + +Ʞ능 감: Text 레읎얎 Ꞁꌎ 목록에 윔드 팚턎읎 귞늰 비튞맵 Ꞁ자 표륌 올늬는 Ꞟ읎요. 읎 팚턎의 5×7 Ꞁ자 마흔아홉 자륌 손윌로 귞렞는데, Lab 에서 같은 Ꞁꌎ로 낱말을 얹을 수가 없얎요. + +### Permutation Poem + +죌제 7 「타읎포」의 「Ꞁ자로 ì§  묎늬」와 「녞람와 Ꞁ」을 같읎 겚눈 팚턎읎에요. 람띌읎얞 êž°ì‹ (Brion Gysin)은 1960년에 I AM THAT I AM 의 낱말을 몚든 찚례로 늘얎놓은 순엎 시륌 썌얎요. 팚턎은 구절의 낱말 n 개륌 토큰윌로 볎고 n! 가지 찚례륌 한 쀄씩 낎렀 썚요. Ꞁ자는 읎 팚턎을 위핎 귞늰 높읎 5 의 대묞자읎고, I 는 한 칞 너비, M 곌 W 는 닀섯 칞읎에요. 6 행 간격윌로 21 쀄읎 128 행을 채워요. 낱말마닀 제 색읎띌 같은 낱말읎 쀄을 따띌 옮겚 가는 Ꞟ읎 색싀처럌 볎여요. 찚례는 헀음 튞로터(Hale Trotter)가 1962년에, 셀뚞 졎슚(Selmer Johnson)읎 1963년에 낾 Steinhaus-Johnson-Trotter 찚례예요. 새 쀄은 윗쀄을 베낀 채로 듀얎였고, 읎웃한 두 낱말만 흰빛윌로 서로 지나 믞끄러진 ë’€ 쪜읎 한 쀄 올띌가요. 가장 큰 토큰읞 마지막 낱말읎 n 번에 n − 1 번 움직여서, ê·ž 낱말의 색읎 쪜을 지귞재귞로 가로질러요. K3 Phrase 는 구절 넷 가욎데 하나륌 고륎고, VENI VIDI VICI 는 찚례가 여섯뿐읎띌 쪜읎 여섯 쀄마닀 되풀읎돌요. K4 Order 는 B. R. 힙(B. R. Heap)읎 1963년에 낾 찚례와 사전 순윌로 바꿔요. 힙의 찚례에서는 ë–šì–Žì§„ 두 낱말읎 사읎 낱말을 넘얎 바뀌고, 사전 순에서는 쀄 ꌬ늬가 뒀집혀 여러 낱말읎 한꺌번에 움직여요. I AM THAT I AM 은 토큰 ì°šë¡€ 120 가지가 낱말로는 30 가지예요. Steinhaus-Johnson-Trotter 찚례로 한 바퀮 120 번 바꟞는 동안 30 번은 I 와 I, 또는 AM 곌 AM 을 바꿔서 새 쀄읎 윗쀄곌 똑같읎 읜혀요. 힙의 찚례에서는 26 번, 사전 순에서는 12 번읎에요. 잰 값은 움직임 0.756, 쎘쎘핚 0.184, 가장자늬 1.000, 복잡도 0.305, 채움 0.237, 색 0.175, 몚양 바뀜 0.037 읎고, thirds 는 23/23/23, 녾뾌는 0.08, 0.10, 0.21, 0.12 예요. 첫 판은 몚든 쀄을 한 밝Ʞ로 ê·žë žì–Žìš”. 귞러멎 얎느 쀄읎나 위에 같은 바읎튞의 쀄읎 있얎서 복잡도가 0.055 로, 지도에서 가장 낮은 0.051 바로 위였얎요. 쪜을 ë§š 아래 행 100% 에서 ë§š 위 행 45% 까지 행마닀 얎둡게 하자 복잡도가 0.305 가 되고, 쎘쎘핚은 0.253 에서 0.184 로 낎렀갔얎요. 위가 얎두워지자 ë„€ 낱말 구절의 죌황 낱말읎 갈색윌로 볎여서, ê·ž 구절듀의 색 시작을 0.58 에서 0.68 로 옮게얎요. 같은 낱말 둘읎 자늬륌 바꿀 때는 믞끄러지는 Ꞟ읎 ꌭ 같아서, 0.33쎈 믞끄럌의 한가욎데에서 두 AM 읎 한 자늬에 포개젞 쀄에 낱말읎 넷만 볎여요. 닀륞 낱말 둘읎 지날 때는 겹친 자늬가 흰 덩얎늬읞데, 같은 낱말은 흰 AM 하나로 또렷읎 읜혔얎요. + +Lab 에서 핮 볎Ʞ: Text 레읎얎에 AM 을 Silkscreen 윌로 팹널 너비만 하게 흰색윌로 쓰고 multiply 로 얹윌멎, 큰 AM 안에 든 작은 낱말만 낚아 작은 낱말로 ì§  큰 낱말읎 돌요. 쪜읎 올띌가는 동안 작은 낱말듀읎 큰 Ꞁ자 속을 지나가요. + +Ʞ능 감: 녞람가 정수 칞을 고륌 때 Lab 녾뾌 옆에 ê·ž 칞의 읎늄을 볎여 죌는 Ꞟ읎요. K3 의 구절 넷곌 K4 의 찚례가 녞람에서는 1.37 같은 수로만 볎여서, 지ꞈ 얎느 구절곌 얎느 찚례읞지는 화멎의 Ꞁ을 읜고서알 알았얎요. + +### Cornsweet Letters + +죌제 7 「타읎포」의 「Ꞁ자와 빛」을 겚눈 팚턎읎에요. 버넌 였람띌읎얞(Vernon O'Brien)읎 1958년에 적은 착시로, 쌀넀슀 크레읎크(Kenneth Craik)의 1966년 Ꞁ곌 톰 윘슀윗(Tom Cornsweet)의 1970년 책에서 읎늄읎 더핎졌얎요. 고륞 바탕에 겜계륌 두고, 겜계에서 날칎롭게 뛰었닀가 양쪜윌로 바탕 높읎까지 돌아였는 밝Ʞ 닚멎만 귞윌멎 안쪜 멎 전첎가 바탕볎닀 밝아 볎여요. 팚턎은 LIT 륌 획 14 LED 의 둥귌 대묞자로 36행씩 섞로로 쌓아요. Ꞁ자 속은 바탕곌 RGB 가 똑같고, 테두늬에만 ±88 의 계닚을 두얎 안윌로 4.5 LED, 밖윌로 6 LED 에 걞쳐 포묌선윌로 바탕 118 까지 낎렀였게 했얎요. 거늬는 Ꞁ자륌 닀섯 ë°° 곱게 귞늰 격자의 거늬 변환윌로 셈핎요. 계닚 높읎는 Ꞁ자마닀 윔사읞윌로 돌아요. 한 바퀎가 6쎈읎고 Ꞁ자끌늬 3분의 1 바퀎씩 얎Ꞌ나서, 한 Ꞁ자가 가장 밝아 볎음 때 나뚞지 둘은 절반 높읎로 얎두워 볎여요. 계닚읎 10 아래읞 0.2쎈쯀은 Ꞁ자가 바탕에 녹아 없얎젞요. K3 Word 는 LIT, DIM, LUX, NIT 넷을 고륎고, LUX 와 NIT 는 조도와 휘도의 닚위예요. K4 Cover 는 연필로 겜계륌 가늬듯 테두늬에 얎두욎 띠륌 덮얎요. 1 에서는 닚멎을 ë‹€ 덮얎서, 띠 안에 낚은 획 가욎데 5 LED 와 띠 바깥 바탕읎 같은 색윌로 드러나요. 잰 값은 움직임 0.040, 쎘쎘핚 0.020, 가장자늬 0.128, 복잡도 0.262, 채움 1.000, 색 0.129, 몚양 바뀜 0.024 읎고, thirds 는 100/100/100, 녾뾌는 0.25, 0.06, 0.04, 0.13 읎에요. 첫 판은 획 10 LED, 계닚 ±70 읎었고 닚멎은 안 3.5, 밖 4.5 LED 였얎요. Ꞁ자는 4분의 1 바퀎씩 얎Ꞌ나게 돌렞얎요. 움직임읎 0.023 윌로 축 아래 끝 0.019 에 붙었고, 2쎈에 L 곌 T 가 핚께 0 을 지나 I 하나만 낚았얎요. 획은 14 LED 로 굵히고 계닚은 ±88 로 킀웠얎요. 닚멎은 4.5 와 6 LED 로 넓히고 얎Ꞌ낚은 3분의 1 바퀎로 바꿚얎요. 획 14 에서는 I 의 위아래 가로획읎 쀄Ʞ와 붙얎 덩얎늬가 되얎서, I 는 쀄Ʞ 하나로 두었얎요. 귞러자 바깥 LED 륌 행 띠로 나눠 Ꞁ자에 맡ꞎ 탓에 Ꞁ자 사읎 6행 틈 가욎데에 두 닚멎 높읎가 맞닿는 가는 가로쀄읎 ì„°ì–Žìš”. Ꞁ자마닀 거늬장을 따로 두얎 틈에서는 두 닚멎을 더하자 쀄읎 사띌졌얎요. 움직임은 0.040, 몚양 바뀜은 0.012 에서 0.024 가 됐얎요. 읎 팚널에서 생각 못 한 것은 DIM 의 D 였얎요. 속공간읎 가욎데에서 23 LED 너비읞데 높읎가 8행뿐읎띌, 위아래 테두늬에서 듀얎였는 바깥 닚멎읎 가장 깊은 두 쀄에도 17% 낚아요. 읎 속공간은 한 번도 바탕 높읎에 닿지 않아요. + +Lab 에서 핮 볎Ʞ: K1 을 0 에 두고 Text 레읎얎에 아묎 낱말읎나 #2d6076 닚색윌로 썚서 sharp edges 륌 쌜고 normal 로 얹윌멎, 낱말은 바탕에서도 Ꞁ자 속에서도 볎읎지 않고 테두늬 닚멎 위에서만 드러나요. 팚턎의 Ꞁ자 속읎 바탕곌 같은 색읎띌는 것을 손윌로 확읞하는 Ꞟ읎에요. + +Ʞ능 감: Text 레읎얎 색 칞에 밑 레읎얎 LED 의 색을 찍얎 였는 슀포읎튞요. 바탕색 #2d6076 을 팹턮 윔드의 HSV 식윌로 따로 셈핎서알 Text 레읎얎 색윌로 넣을 수 있었얎요. + +### YBC 7289 + +죌제 7 「타읎포」의 「숫자와 Ʞ혞 놀읎」륌 겚눈 팚턎읎에요. 예음 바빌로니아 컬렉션(Yale Babylonian Collection)의 YBC 7289 는 Ʞ원전 1800~1600년쯀의 고바빌로니아 점토판읎에요. 대각선 둘을 귞은 정사각형의 한 변에 30, 대각선을 따띌 1;24,51,10, ê·ž 아래에 42;25,35 가 적혀 있얎요. 1;24,51,10 은 √2 륌 소수 여섯짞 자늬쯀까지 맞힌 값읎고, 42;25,35 는 ê·ž 30ë°°, 변읎 30 읞 정사각형의 대각선읎에요. 팹널 위쪜은 흔듀늬는 등불 아래의 판 앞멎읎에요. 아래쪜 점토에는 x 륌 x 와 2/x 의 평균윌로 바꟞는 셈읎 1;30 에서 시작핎 한 쀄에 한 걞음씩 쐐Ʞ로 눌렀 썚지고, √2 의 자늬와 같은 자늬는 청록윌로 쌜젞요. 읎 평균법은 뒀에 알렉산드늬아의 헀론(Heron of Alexandria)읎 1섞Ʞ에 적얎 낚게얎요. 1;30 곌 1;25 는 1 만 쌜지고, 1;24,51,10,35 는 ë„·ì§ž 자늬까지 쌜지멎서 위 판의 1;24,51,10 도 같읎 쌜젞요. 1;24,51,10,7 은 닀섯 자늬가 ë‹€ 쌜젞요. 쐐Ʞ는 Ꞁꌎ 파음읎 아니띌 높읎장에 눌러 넣은 삌각뿔읎에요. LED 마닀 법선 16개륌 평균핎 등불로 비추고, 닚위 쐐Ʞ는 셋씩 쀄로, 몚서늬 쐐Ʞ(Winkelhaken)는 Ʞ둥윌로 쌓았얎요. 셈은 BigInt 분수로 정확히 핎서 반올늌윌로 쌜지는 자늬는 없얎요. K4 Places 로 걞음마닀 낚Ꞟ 자늬륌 셋윌로 자륎멎 여섯 시작읎 몚두 판의 1;24,51,10 에 닿고, 닀음 걞음읎 같은 수륌 돌렀쀘요. 잰 값은 움직임 0.027, 쎘쎘핚 0.055, 가장자늬 0.583, 복잡도 0.655, 채움 0.978, 색 0.171, 몚양 바뀜 0.023 읎고, thirds 는 95/98/99, 녾뾌는 0.10, 0.05, 0.04, 0.03 읎에요. thirds 는 점토 바탕읎 팚널을 ë‹€ 채워서 나옚 값읎띌 죌제 4 의 슝거가 되지 않아요. 첫 판은 쐐Ʞ륌 ë‹€ 3 LED 칞에 쓰고 빈 점토에서 시작했얎요. 쐐Ʞ 하나륌 누륎는 것윌로는 화멎읎 거의 바뀌지 않아서 움직임읎 0.003, 몚양 바뀜읎 0.006 읎었고, K2 Speed 가 0.0042, K3 Start 가 0.0055 로 걞렞얎요. 등불을 7쎈에 한 번 29도씩 흔듀고, 아래 쀄을 4 LED 칞윌로 킀우고, 새로 쌜멎 ë‹€ 쓎 쀄읎 3.2쎈 서 있닀가 지워진 ë’€ 닀시 썚지게 했얎요. 귞러자 움직임읎 0.027, K3 가 0.04 가 됐얎요. K4 는 처음에 등불 높읎였얎요. 67판 Braille Relief 가 읎믞 등불 높읎륌 녞람로 두얎서 자늬 수로 바꿚얎요. 맞는 자늬의 빛읎 쐐Ʞ 둘레 4 LED 와 자늬 상자까지 번지게 하자 K4 가 0.023 에서 0.03 읎 됐얎요. 1;25 는 1;24 볎닀 √2 에 가까욎데도 25 가 귌의 24 가 아니띌서 둘짞 쀄은 1 만 쌜젞요. 0.086 얎Ꞌ난 첫 쀄곌 0.0025 얎Ꞌ난 둘짞 쀄읎 읎 팚널에서는 똑같읎 한 자늬만 쌜졌얎요. + +Lab 에서 핮 볎Ʞ: 첫 두 쀄 였륞펞, 70~98행의 28~62엎은 늘 빈 점토예요. 거Ʞ에 Text 레읎얎로 1.414213 을 Silkscreen 윌로 쓰고 multiply 로 얹윌멎 점토색읎 곱핎젞 눌러 쓎 현대 숫자처럌 얎둡게 앉아서, 60진 쀄곌 10진 값을 나란히 볌 수 있얎요. + +Ʞ능 감: Text 레읎얎가 Ꞁ자륌 덮임 값곌 핚께 깊읎로도 낎죌는 Ꞟ읎요. Ꞁ자륌 점토에 눌러 쓎 것처럌 비추렀멎 높읎장읎 있얎알 핎서, 쐐Ʞ마닀 삌각뿔을 손윌로 셈핎 법선을 평균했얎요. + +### Beading Letters + +죌제 7 「타읎포」의 「Ꞁ자의 몞」을 겚눈 팚턎읎에요. 조제프 플띌토(Joseph Plateau)는 1873년에 액첎 Ʞ둥읎 제 둘레볎닀 ꞎ 묌결 앞에서 버티지 못한닀고 적었고, 레음늬 겜(Lord Rayleigh)은 1878년에 ê·ž 묌결읎 반지늄의 9.02ë°° Ꞟ읎에서 가장 빚늬 자란닀고 셈했얎요. 팚턎은 섞 Ꞁ자 낱말을 37행 대묞자로 섞로로 쌓고, 획을 쓰는 찚례대로 반지늄 1.5 LED 의 둥귌 액첎 싀로 부얎요. 싀에는 흰 반사ꎑ읎 서고, 싀을 지나 몚읞 빛읎 였륞쪜 아래 바닥에 옅게 떠요. 획마닀 양 끝에 마룚가 였는 묌결 가욎데 가장 빚늬 자띌는 것을 곚띌, 0.012~0.03 의 작은 높읎에서 부은 때부터 킀워요. 부은 지 2쎈쯀 지나 목읎 끊Ʞ멎 마룚마닀 방욞읎 맺히고, 끊ꞎ 목에는 작은 위성 방욞읎 낚아요. 방욞은 레음늬가 1879년에 셈한 가장 낮은 ꌎ 진동윌로 0.83쎈마닀 Ꞟ얎졌닀 납작핎지고, 1.7쎈에 e 분의 1로 잊아듀얎요. DEW 가 점 Ꞁ자 DEW 로 2쎈 서 있닀가 방욞읎 바닥윌로 떚얎지고 INK 가 부얎젞요. K4 Viscosity 는 였넀조륎게 수(Ohnesorge number)예요. 윘슀탄틎 베버(Constantin Weber)가 1931년에 적은 ꞎ 묌결 식의 점성 항을 레음늬의 식에 더핎서, 0 에서 1 로 올늬멎 가장 빠륞 묌결읎 반지늄의 9.02배에서 16.1배로 Ꞟ얎지고 끊ꞰꞰ까지 2.7ë°° 느렀젞요. 귞쪜 끝에서는 싀읎 가는 싀였띌Ʞ에 꿰읞 구슬읎 된 뒀에 끊겚요. 잰 값은 움직임 0.300, 쎘쎘핚 0.041, 가장자늬 0.410, 복잡도 0.662, 채움 0.188, 색 0.171, 몚양 바뀜 0.132 읎고, thirds 는 17/19/18, 녾뾌는 0.08, 0.09, 0.08, 0.05 예요. 첫 판은 38행 Ꞁ자륌 1.5행에서 시작핎서 D 위 끝 방욞읎 팹널 밖윌로 잘렞고, 채움읎 0.131 로 축의 아래 끝 0.117 에 붙얎 있었얎요. Ꞁ자륌 37행윌로 쀄여 3행에서 시작하고 바닥에 몚읎는 빛을 더하자 채움읎 0.188, 복잡도가 0.447 에서 0.662 가 됐얎요. 첫 판에서는 몚서늬 방욞읎 ê·ž 몚서늬의 획읎 ë‹€ 끊ꞎ 뒀에알 맺혀서, E 의 위쪜 반 섞로획읎 끊Ʞ는 순간 통짞로 사띌졌얎요. 몚서늬 방욞을 첫 획읎 끊Ꞟ 때 맺게 바꟞자, 아직 안 끊ꞎ 획 끝에 방욞읎 혹처럌 붙었얎요. 생각 못 한 것은 E 의 가욎데 몚서늬예요. 획 셋읎 묌을 대서 ê·ž 방욞은 반지늄 3.55 LED 로 획 가욎데 ë°©ìšž 2.75 볎닀 29% 크고, 점 Ꞁ자 E 는 가욎데 왌쪜 점 하나만 유난히 굵얎요. + +Lab 에서 핮 볎Ʞ: 없음. 섞 Ꞁ자가 128행을 ë‹€ 쓰고 가로로도 64칞 가욎데 51칞까지 찚지핎서 Text 레읎얎 낱말을 얹을 빈 자늬가 없고, 낱말도 6쎈쯀마닀 바뀌얎요. + +Ʞ능 감: Lab 믞늬볎Ʞ륌 멈추고 한 프레임씩 넘Ʞ는 닚추요. 싀읎 방욞로 끊Ʞ는 순간읎 0.1쎈 안에 지나가서, ê·ž 사읎 프레임을 따로 뜑아 뎐알 읎음맀가 맀끈한지 볌 수 있었얎요. + +### Modular Hyperbola + +죌제 5 「수학 · 추상」윌로 수론 갈래에 서고, 죌제 1 「지도의 빈 칞」을 같읎 채우는 팚턎읎에요. Agate, Multiplicative Chaos, Percolation, Sunglint 넷뿐읞 쎘쎘핚 × 복잡도 4,4 칞을 겚눎얎요. 읎웃 찚읎 평균읎 0.297 을 넘윌멎서 한 프레임읎 픜셀마닀 2.17 바읎튞 넘게 낚아알 하는 칞읎에요. 팚널은 소수 p 로 나눈 곱셈표예요. 엎은 1 부터 64, 행은 0 부터 127 읎띌 LED (x, y) 에 (x + 1)y mod p 가 듀얎요. LED 마닀 제 나뚞지가 돌아가는 나뚞지 c 에서 얌마나 앞에 있는지로 톱니의 높읎륌 받아서, 나뚞지가 c 읞 곳읎 희고 ê·ž 뒀로 큐람헬늭슀 랚프륌 따띌 얎두워지닀 c 바로 앞에서 검게 끝나요. 검정에서 흰색윌로 꺟읎는 날읎 몚듈러 쌍곡선 xy ≡ c (mod p) 예요. 읎 점듀읎 상자 하나에 몇 개 드는지는 헹드멭 큎룚슀테륎만(Hendrik Kloosterman)읎 1926년에 낾 합윌로 셈핎요. 앙드레 베유(André Weil)가 1948년에 ê·ž 합읎 2√p 륌 넘지 않는닀고 볎여서, 얎느 상자든 제 몫 XY/p 에서 √p log²p 의 몇 ë°° 안에 듀얎요. K4 Band 륌 1 로 올늬멎 톱니가 나뚞지 둘로 좁아젞 ê·ž 흩뿌늌만 낹는데, p = 127 읎멎 엎마닀 ꌭ 한 LED 씩읎에요. K3 Prime 은 13 부터 2039 까지 2의 거듭제곱 아래 가장 큰 소수 여덟 가욎데 하나륌 곚띌요. 127 에서는 128 행읎 나뚞지륌 한 번씩 ë‹€ 닎고, 행 127 − y 가 행 y 의 음수띌서 아래 절반읎 위 절반을 뒀집은 귞늌읎 돌요. 잰 값은 움직임 0.342, 쎘쎘핚 0.336, 가장자늬 0.769, 복잡도 2.391, 채움 0.901, 색 0.224, 몚양 바뀜 0.040 읎고, thirds 는 90/90/90, 녾뾌는 0.16, 0.45, 0.37, 0.44 예요. 첫 판부터 데읎람 귞늰(Dave Green)의 큐람헬늭슀(2011)로 칠핎서 칞 안에 ì„°ì–Žìš”. 견죌렀고 색상환에 밝Ʞ륌 곱한 랚프로도 재 볎니 복잡도 1.910, 색 0.562 로 복잡도 칞 3 에 뚞묌렀얎요. ê·ž 랹프는 LED 마닀 한 채널읎 늘 0 읎띌 deflate 가 잘 눌렀고, 큐람헬늭슀는 섞 채널읎 ë‹€ 따로 올띌서 낹는 바읎튞가 많아요. 구석의 xy < 127 읞 637 LED 만 맀끈한 쌍곡선읎고 ê·ž 밖은 묎아레가 될 쀄 알았얎요. 귞런데 톱니가 가로로는 한 LED 에 y/127 바퀮, 섞로로는 (x + 1)/127 바퀮 돌아서, 위 절반에서는 두 방향 ë‹€ 반 바퀎륌 넘지 않아요. 귞래서 xy = c + 127k 쌍곡선 32 개가 접히지 않고 ë‹€ 볎였얎요. 첎크 묎늬는 y 나 x + 1 읎 63.5 에 닿는 가욎데 쀄곌 였륞쪜 끝 엎에만 서요. + +### Head Wave + +죌제 5 「수학 · 추상」윌로 묌늬·슀프링 갈래에 서고, 죌제 1 「지도의 빈 칞」곌 죌제 4 「섞로 64×128」을 같읎 채우는 팚턎읎에요. 겚눈 칞은 가장자늬가 0.210 아래읎멎서 쎘쎘핚읎 0.1894 에서 0.2969 사읎읞 가장자늬 × 쎘쎘핚 1,3 읎에요. 안드늬알 몚혞로비치치(Andrija Mohorovičić)는 1909년 쿠파 곚짜Ʞ 지진의 Ʞ록에서, 진앙에서 뚌 ꎀ잡소에는 곧장 옚 파볎닀 빠륞 파가 뚌저 닿는 것을 볎고 지각 밑에 더 빠륞 잵읎 있닀고 읜었얎요. ê·ž 빠륞 파가 겜계륌 타고 옚 뚞늬파예요. 팚널은 땅의 섞로 닚멎읎띌 128행읎 깊읎읎고, 64행에 평평한 겜계가 지나요. 위는 느며 지각, 아래는 1.35ë°° 빠륞 맚틀읎에요. 왌쪜 끝 겜계 2 LED 위의 진원읎 1쎈에 띠 하나씩 펄슀륌 낎볎낎고, LED 마닀 첫 도착 시각을 플에륎 드 페륎마(Pierre de Fermat)가 1662년에 적은 최소 시간 원늬로 정확히 셈핎요. 겜계 아래 LED 는 겜계륌 걎너는 점을 뉎턎법윌로 곚띌 굎절 Ꞟ의 시간을 얻얎요. 뚞늬파는 임계각 47.8도로 겜계에 낎렀가 맹틀 속도로 겜계륌 달늬닀 같은 각윌로 올띌였는 Ꞟ읎띌, 앞선읎 곧은 원뿔읎 되얎 지각의 원형 앞선에 맞닿아요. 겜계에서 2.2 LED 밖부터 뚌저 닿고, ê·ž 쐐Ʞ는 였륞쪜 끝에서 33행 위까지 올띌가요. 뚞늬파는 맹틀 색윌로 칠핎서 청록 지각 안에 장밋빛 곧은 띠가 비슀듬히 서요. 버튌을 누륎멎 진원읎 16 LED 씩 였륞쪜윌로 옮겚 가요. 잰 값은 움직임 1.744, 쎘쎘핚 0.197, 가장자늬 0.144, 복잡도 1.984, 채움 0.892, 색 0.396, 몚양 바뀜 0.038 읎고, thirds 는 87/89/90, 녾뾌는 0.30, 0.22, 0.21, 0.25 예요. 첫 판은 진원을 (6, 44)에, 겜계륌 56행에 두고 5.5 LED 너비 띠륌 43~255 의 톱니로 칠했고, 페륎마 ꎑ선 48 가닥을 얎두욎 선윌로 귞었얎요. 쎘쎘핚 0.150, 가장자늬 0.185 로 1,2 칞읎었얎요. ꎑ선을 빌멎 0.142 와 0.115 띌서, ꎑ선은 쎘쎘핚을 0.008 올늬는 사읎 가장자늬륌 0.07 올늰 셈읎에요. 앞선읎 행읎나 엎곌 나란하멎 띠의 였륎막읎 한쪜 읎웃에만 싀늬고, 대각선읎멎 행곌 엎에 나뉘얎요. 귞래서 LED 마닀 읎웃 찚읎가 39.5 륌 넘지 않는 가장 높은 띠륌 ê·žë žì–Žìš”. 대각선 앞선은 255 까지 서고, 팚널곌 나란한 앞선은 174 에서 멈춰요. 바닥은 0 윌로 낎늬고 띠는 4.4 LED 로 좁혔얎요. 진원을 왌쪜 끝 겜계 2 LED 위로 옮겚 뚞늬파 쐐Ʞ륌 킀우자 0.197 곌 0.144 가 되얎 1,3 칞에 ì„°ì–Žìš”. 뚞늬파 쐐Ʞ만 따로 재멎 쎘쎘핚읎 0.240 읎에요. 앞선의 법선읎 팹널 대각선에서 2.8도만 벗얎나서 였륎막읎 행곌 엎에 거의 반씩 싀렀요. 맚틀은 띠가 1.35ë°° 넓얎서 0.184 에 귞쳐요. K3 Ratio 륌 2.5 로 올늬멎 임계각읎 23.6도로 쀄얎 원뿔 앞선읎 눕고, 쐐Ʞ는 팚널의 11% 에서 25% 로 컀젞 였륞쪜 끝 ë§š 윗쀄까지 닿아요. 팚널에서 처음 볞 것은 64행의 꺟임읎에요. 뚞늬파 앞선은 겜계와 47.8도로 만나는데, 바로 아래 맚틀의 앞선은 겜계륌 슀치며 지나는 굎절파띌 거의 곧게 서서, 띠마닀 겜계에서 42도쯀 꺟여요. + +### Dehn Twist + +죌제 5 「수학 · 추상」윌로 위상·맀듭 갈래에 선 팚턎읎에요. 막슀 덎(Max Dehn)읎 1938년에 적은 덎 ꌬ임(Dehn twist)은 곡멎을 닫힌 곡선 하나륌 따띌 자륎고, 한쪜을 한 바퀮 돌렀 닀시 붙읎는 사상읎에요. W. B. R. 늬윔늬시(W. B. R. Lickorish)는 1962년에 닫힌 가향 곡멎의 사상류군읎 덎 ꌬ임윌로 생성된닀는 것을 볎였얎요. 원환멎에서는 자였선을 도는 ꌬ임곌 겜도선을 도는 ꌬ임 둘읎멎 돌요. 팹널 위쪜에서는 ꎀ 반지늄 9 LED, 쀑심 반지늄 21 LED 의 원환멎을 적도에서 56도 위에서 낎렀닀볎고, 원환멎은 Speed 2 에서 30쎈에 한 바퀮 돌아요. 아래쪜에는 맞은펞 변끌늬 붙읞 56×56 정사각형읎 같은 곡선을 곧은 선분윌로 볎여 쀘요. ꌬ임 한 번은 2.5쎈예요. ꞎ 둘레의 10분의 1 너비읞 쎈록 고늬띠가 쌜지고, ê·ž 안읎 ꎀ을 따띌 한 바퀮 끌렀 돌아요. 고늬띠의 잘늰 변에서 곡선의 두 끝읎 벌얎졌닀가 한 바퀮 뒀에 흰 빛을 낎며 닀시 만나고, 곡선읎 곧게 펎지는 동안 원환멎곌 정사각형 사읎의 Ꞁ자가 (1,0) 에서 (1,1) 로 바뀌얎요. 정사각형에서는 선분읎 p 개 늘고, 원환멎에서는 곡선읎 ꎀ을 p 번 더 감아요. K4 Twists 만큌 올띌간 ë’€ 같은 수만큌 거꟞로 ꌬ여 낎렀였고, 목록의 닀음 류 (2,1) 로 넘얎가요. 잰 값은 움직임 0.257, 쎘쎘핚 0.058, 가장자늬 0.399, 복잡도 0.802, 채움 0.682, 색 0.200, 몚양 바뀜 0.059 읎고, thirds 는 63/58/82, 녾뾌는 0.12, 0.07, 0.11, 0.05 예요. 첫 판은 너비 0.15 의 고늬띠륌 원환멎 앞쪜에 두었얎요. 앞에서 볎멎 자였선읎 섞로 한 쀄로 서서, ꎀ을 도는 끌Ʞ가 쎈록 덩얎늬 뒀로 숚었얎요. 한 프레임에 3.2 ms 도 듀었얎요. 고늬띠륌 왌쪜 옆윌로 옮Ʞ자 자였선읎 18 LED 너비의 타원윌로 볎였얎요. 너비륌 0.1 로 쀄읎고, 앞 점에서 0.4 LED 안에 찍히는 점을 걎너뛰얎 2.1 ms 가 됐얎요. ê·ž 판을 처음 잰 값은 움직임 0.132 였고, 나뚞지는 지ꞈ곌 거의 같았얎요. 읎 팚널에서 뜻밖읎었던 것은, (1,0) 읎 두 번 ꌬ였닀 두 번 풀렀 (1,0) 윌로 돌아왔는데 곡선은 ꎀ 밑에 숚얎 있었닀는 거예요. 펎진 곡선은 ꌬ읞 곡선의 평균 높읎에 서고 고늬띠는 ꌬ임마닀 닀륞 자였선에 서서, 류가 같아도 곡선읎 ꎀ을 따띌 조ꞈ씩 밀렀났얎요. 귞래서 펎진 ë’€ 0.4쎈 동안 곡선을 ꎀ을 따띌 제자늬로 밀얎 두었고, 움직임읎 0.257 읎 됐얎요. + +### Malfatti Circles + +죌제 5 「수학 · 추상」윌로 Ʞ하·대칭 갈래에 서고, 죌제 4 「섞로 64×128」을 같읎 채우는 팚턎읎에요. 잔 프란첎슀윔 말파티(Gian Francesco Malfatti)는 1803년에 삌각Ʞ둥 대늬석에서 원Ʞ둥 셋을 가장 적게 버늬며 깎아 낮는 묞제륌 냈고, 답을 서로 닿윌며 저마닀 두 변에 닿는 섞 원윌로 여게얎요. 1930년에 H. 로람(H. Lob)와 H. W. 늬치뚌드(H. W. Richmond)가 정삌각형에서는 낎접원곌 몚서늬 원 둘읎 더 넓닀는 것을 볎였고, 1994년에 V. A. 잘갈레륎(V. A. Zalgaller)와 G. A. 로슀(G. A. Los')가 낎접원 닀음 낚은 자늬에서 가장 큰 원을 고륎는 탐욕읎 얎느 삌각형에서나 가장 넓닀고 슝명했얎요. 팚널은 천천히 몚양을 바꟞는 삌각형 하나륌 위아래로 두 번 귞렀요. 위에는 말파티의 섞 원, 아래에는 탐욕의 섞 원읎 서고, 둘 사읎 막대와 숫자가 섞 원읎 덮는 몫을 적얎요. 말파티 반지늄은 닫힌 ꌎ r_A = r/(2(s − a)) · (s − r − (e + f − d)) 로 셈했얎요. 팚턎읎 지나는 삌각형곌 묎작위 삌각형 ì—Ž 개에서 쀑심 사읎 거늬와 두 반지늄의 합, 변까지 거늬와 반지늄읎 1e-13 LED 안에서 맞는 것을 셈윌로 확읞했얎요. 정삌각형에서는 72.9% 와 73.9% 로 1 포읞튞 찚읎읎고, 23도 쐐Ʞ에서는 55.9% 와 69.8% 로 14 포읞튞 벌얎젞요. K4 More 륌 올늬멎 두 판 ë‹€ 낚은 틈을 탐욕윌로 60개까지 더 채워요. 잰 값은 움직임 0.155, 쎘쎘핚 0.072, 가장자늬 0.719, 복잡도 0.636, 채움 0.421, 색 0.251, 몚양 바뀜 0.121 읎고, thirds 는 29/41/55, 녾뾌는 0.09, 0.11, 0.07, 0.04 예요. 첫 판도 잰 값은 지ꞈ곌 같았는데 뚞늬 죌석의 숫자가 틀렞얎요. 23도 쐐Ʞ에서 64% 대 50% 띌고 적은 몫읎 셈핎 볎니 69.8% 대 55.9% 였얎요. ꌭ짓점읎 한 바퀮 도는 데는 36쎈가 아니띌 48쎈가 걞렞고, 색은 하늘색곌 혞박색읎 아니띌 하늘색곌 분홍읎었얎요. ê·ž 쀄듀을 셈한 값윌로 고쳀얎요. 23도 쐐Ʞ에서 탐욕의 둘짞와 ì…‹ì§ž 원은 닀륞 두 몚서늬로 가지 않았얎요. 낎접원 닀음의 반지늄 6.9 와 4.6 읎 둘 ë‹€ 가장 런족한 몚서늬에 같은 비 0.664 로 쀄지얎 듀얎가요. + +### Pollard's Rho + +죌제 5 「수학 · 추상」윌로 알고늬슘 갈래에 선 팚턎읎에요. 졎 폎띌드(John M. Pollard)가 1975년에 낾 로 방법(rho method)은 x 륌 x² + c 로 거듭 볎낎며 n 윌로 나눈 나뚞지륌 셈핎요. n 의 숚은 소읞수 p 로 나눈 나뚞지는 값읎 p 개뿐읎띌 ì–žì  ê°€ 볞 값윌로 돌아였고, ê·ž 뒀로는 같은 고늬만 돌아요. ꌬ늬 하나에 고늬 하나띌서 귞늬슀 Ꞁ자 ρ ꌎ읎에요. 팚널은 ê·ž ρ 륌 섞워 x₀ = 2 에서 시작한 ꌬ늬륌 아래에서 올늬고 고늬륌 위에 두며, 구슬마닀 x_i mod p 륌 적얎요. 알고늬슘은 ρ 륌 볎지 못하고 n 윌로 나눈 값만 뎐요. 로버튞 플로읎드(Robert W. Floyd)의 것윌로 알렀진 고늬 찟Ʞ대로 거북읎는 한 박자에 한 칞, 토끌는 두 칞 가고, ꌬ늬 옆 Ꞁ쀄에는 두 값곌 ê·ž 찚읎가 n 윌로 나눈 값윌로 떠요. 고늬 가욎데의 gcd(|x_t − x_h|, n) 는 1 로 있닀가 둘읎 같은 구슬에 서는 박자에 p 로 뛰고, 고늬가 번쩍읞 ë’€ 아래에 =83×71 처럌 n 의 두 읞수가 적혀요. 귞닀음 같은 수륌 늬처드 뾌렌튾(Richard P. Brent)가 1980년에 낾 방식윌로 한 번 더 풀얎요. 토끌가 한 박자에 한 칞 가고 거북읎는 1, 2, 4, 8 걞음마닀 토끌 자늬로 걎너뛰얎서 f 륌 한 박자에 한 번만 셈핎요. 시간을 f 륌 셈한 수로 흘렀서, 두 방식읎 같은 수륌 두고 같은 잣대로 겚뀄요. 잰 값은 움직임 0.180, 쎘쎘핚 0.087, 가장자늬 0.777, 복잡도 0.333, 채움 0.193, 색 0.193, 몚양 바뀜 0.086 읎고, thirds 는 22/19/15, 녾뾌는 0.05, 0.07, 0.08, 0.06 읎에요. 첫 판도 읎 값 귞대로 검사륌 지났고, 고친 것은 싀팚하는 순간 하나예요. c = 1 의 4307 = 59 × 73 은 두 소수의 고늬가 같은 박자에 닫혀서 gcd 가 n 자첎읞 4307 읎 되는데, ë„€ 자늬 수가 만난 구슬을 두륞 토끌 고늬에 닿아 4 가 반쯀 묻혔얎요. ê·ž 수에만 한 LED 둘레의 얎두욎 바탕을 깔았얎요. 팚널에 섞워 볎고 안 것은 c 하나가 얎느 읞수륌 뚌저 잡을지 바Ꟍ닀는 거예요. 5893 은 c = 1 에서 83 을 법윌로 한 ꌬ늬 4, 고늬 5 의 ρ 가 뚌저 닫혀 83 읎 나였는데, c = 5 에서는 71 을 법윌로 한 ꌬ늬 2, 고늬 6 의 ρ 가 뚌저 닫혀서 71 읎 나와요. + ## 지도 지도의 자늬는 손윌로 맀ꞎ 점수가 아니띌 잰 값읎에요. 팚턎마닀 처음 녾뾌 값윌로 Lab 엔진에서 8쎈륌 돌늬고, 2쎈부터 6쎈 동안의 화멎을 재요. LED 하나의 밝Ʞ는 섞 채널 가욎데 가장 밝은 값윌로 뎐요. @@ -4651,7 +4730,7 @@ ESP32-S3 로 옮Ꞟ 때 걞늎 것도 있얎요. Reaction Diffusion 은 Node 에 ## 닀음 판 감 -10월 25음 `dev`(`f72355d`)의 328개에 43판부터 79판까지의 삌백예순 개륌 더한 698개에서 잰 값읎에요. 팹턮 바퀮는 죌제 안에서 묎엇을 할지 고륌 때 읎 절을 읜고, 한 것은 빌고 새로 볞 것을 더핎요. +10월 25음 `dev`(`f72355d`)의 328개에 43판부터 79판까지와 81판의 삌백음흔 개륌 더한 708개에서 잰 값읎에요. 팹턮 바퀮는 죌제 안에서 묎엇을 할지 고륌 때 읎 절을 읜고, 한 것은 빌고 새로 볞 것을 더핎요. ### 지도의 빈 칞 @@ -4765,6 +4844,8 @@ ESP32-S3 로 옮Ꞟ 때 걞늎 것도 있얎요. Reaction Diffusion 은 Node 에 79판은 78판까지 든 688개에 빈 칞도, 셋 읎하읞 칞도 없얎서 넷뿐읞 칞 엎하나 가욎데 가장자늬 × 쎘쎘핚 1,2 와 쎘쎘핚 × 몚양 바뀜 3,4 륌 겚눎얎요. 79판을 띄욞 때 같읎 도는 바퀮는 없었얎요. 가장자늬 × 쎘쎘핚 1,2 는 6/255 넘는 읎웃 찚읎의 79% 넘게가 40/255 아래로 묎륎멎서 읎웃 찚읎 평균읎 21~48/255 읞 칞읎에요. 팹널 얎디서나 ꜉ ì°¬ 빛읎 13~20 LED 마닀 였륎낎늬는 맀끈한 묎늬여알 핎요. Ramsey Fringes 는 밝Ʞ륌 묎늬 위상의 삌각파로 칠핎 Ʞ욞Ʞ륌 1 LED 에 35~39/255 로 고륎게 두고 가장자늬 0.148, 쎘쎘핚 0.091 읎에요. 사읞 ꌎ 귞대로 칠하멎 êž°ìšžêž°ê°€ 마룚와 곚에서 0 윌로 가띌앉아 평균읎 귞만큌 낮아요. 읎조륌 가로에 둔 판은 찚읎가 가로로만 생겚 쎘쎘핚읎 0.078 을 넘지 못했얎요. 쎘쎘핚 × 몚양 바뀜 3,4 는 잘게 끊ꞎ 귞늌읎 2~8쎈 사읎에 판의 절반 넘게 꺌지거나 쌜젞알 하는 칞읎에요. Quantum Walk 는 동전 한쪜의 확률읎 귞늬는 혞로 쎘쎘핚 0.213 을, ë‹€ 쓎 도표가 얎두워지는 7.7~8쎈로 몚양 바뀜 0.378 을 얻얎요. ì—Ž 개 ë‹€ 음곱 축의 끝 안에 듀얎 칞 겜계는 60판 귞대로예요. 78판까지 든 688개에 79판 ì—Ž 개륌 넣은 698개에서 두 칞은 닀섯읎 됐고, 엎하나읎던 넷뿐읞 칞은 아홉읎에요. 낚은 아홉 가욎데 넷읎 가장자늬 × 쎘쎘핚의 1,3 1,4 2,3 2,4 읎고, 넷읎 복잡도 2.172 륌 넘는 칞읎에요. +81판은 79판까지 든 698개에 빈 칞도, 셋 읎하읞 칞도 없얎서 넷뿐읞 칞 아홉 가욎데, 같읎 도는 80판(#344)읎 고륞 가장자늬 × 복잡도 4,4 와 가장자늬 × 쎘쎘핚 2,3 을 빌고 가장자늬 × 쎘쎘핚 1,3 곌 쎘쎘핚 × 복잡도 4,4 륌 겚눎얎요. 가장자늬 × 쎘쎘핚 1,3 은 40/255 륌 넘는 계닚의 몫읎 0.210 아래읎멎서 읎웃 찚읎 평균읎 0.189~0.297 읞 칞읎에요. 였륎막읎 거의 ë‹€ 묎륎고 띠마닀 한 번만 크게 떚얎지는 톱니가 Ꞟ읎에요. Head Wave 는 첫 도착 시각을 톱니로 칠하되 LED 마닀 읎웃 찚읎가 39.5 륌 넘지 않는 가장 높은 띠륌 귞렀서, 가장자늬 0.144, 쎘쎘핚 0.197 읎에요. 진원을 (6, 44)에 두고 ꎑ선을 귞은 첫 판은 가장자늬 0.185, 쎘쎘핚 0.150 윌로 1,2 칞읎었얎요. 앞선읎 행읎나 엎곌 나란하멎 였륎막읎 한쪜 읎웃에만 싀렀서, 둥귌 앞선만윌로는 쎘쎘핚읎 0.20 얞저늬에서 멈춰요. 진원을 왌쪜 끝윌로 옮겚 앞선읎 팹널 대각선윌로 Ʞ욎 뚞늬파 쐐Ʞ륌 킀우자 칞에 ì„°ì–Žìš”. 쎘쎘핚 × 복잡도 4,4 는 읎웃 찚읎 평균읎 0.297 을 넘윌멎서 복잡도가 2.172 륌 넘는 칞읎에요. Modular Hyperbola 는 소수 127 로 나눈 곱셈표륌 큐람헬늭슀 톱니로 칠핎 쎘쎘핚 0.336, 복잡도 2.391 읎에요. 색상환에 밝Ʞ륌 곱한 랚프로 견죌얎 볎니 LED 마닀 한 채널읎 0 읎띌 deflate 가 잘 눌러서 복잡도가 1.910 에 뚞묌렀얎요. ì—Ž 개 ë‹€ 음곱 축의 끝 안에 듀얎 칞 겜계는 60판 귞대로예요. 79판까지 든 698개에 81판 ì—Ž 개륌 넣은 708개에서 두 칞은 닀섯읎 됐고, 아홉읎던 넷뿐읞 칞은 음곱읎에요. 80판읎 PR 에 올늰 ì—Ž 개까지 넣은 718개에서는 닀섯읎에요. 낚은 닀섯은 가장자늬 × 쎘쎘핚 1,4 와 2,4, 복잡도 × 채움 4,1 곌 4,2, 복잡도 × 몚양 바뀜 4,4 예요. + ### 아직 없는 갈래 `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 가 ì„°ì–Žìš”. @@ -4932,9 +5013,11 @@ Flock, Harmonograph, Big Type 은 처음 화멎읎 빚강읎에요. 79판에서는 Semaphore 와 Mushroom Billiard 가 128쀄을 썚요. Semaphore 는 낱말 Ꞟ읎가 몇읎든 신혞 자섞와 대묞자가 128쀄을 나눠 서서 16/12/13 읎고, 벜읎나 테두늬는 없얎요. 첫 판은 10/8/8 읎었고, 팔을 늘늬고 닀섯 Ꞁ자 낱말의 대묞자 획을 굵혀 셋 ë‹€ 12 륌 넘었얎요. 새로 쌜멎 앞 낱말읎 흐늬게 깔렀 있얎서 2~8쎈에도 아래 3분의 1읎 비지 않아요. Mushroom Billiard 는 반지늄 30 의 갓읎 위 3분의 1을, 95행의 쀄Ʞ가 가욎데와 아래륌 채워 40/15/15 읎고, 테두늬륌 끄멎 33/12/12 예요. 나뚞지 여덟 가욎데 Ramsey Fringes 는 95/98/95 로 판을 ë‹€ 덮고, Neon Spreading 은 59/59/59, Möbius Type 은 41/46/39 예요. Quantum Walk 는 원뿔읎 아래로 ë„“ì–Žì ž 20/53/67 읎고, Lempel-Ziv 는 쪜읎 위에서부터 찚서 39/35/16, Huygens' Clocks 는 Ʞ록읎 아래에 쌓여 12/29/39 예요. Beam Letters 는 16/20/18, EKG Sequence 는 10/14/16 읎에요. +81판에서는 Baudot Tape 와 Head Wave 가 128쀄을 썚요. Baudot Tape 는 너비 41 LED 의 종읎 테읎프가 위아래 끝까지 읎얎젞 65/64/64 읎고, 벜읎나 테두늬는 귞늬지 않았얎요. Head Wave 는 땅의 섞로 닚멎읎띌 128행읎 깊읎읎고, 띠가 판을 ë‹€ 덮얎 87/89/90 읎에요. 나뚞지 여덟 가욎데 Cornsweet Letters 는 바탕읎 판을 ë‹€ 채워 100/100/100, YBC 7289 는 점토가 덮얎 95/98/99, Modular Hyperbola 는 90/90/90 읎에요. Dehn Twist 는 원환멎곌 붙읞 정사각형읎 위아래에 서서 63/58/82, Malfatti Circles 는 삌각형 둘읎 쌓여 29/41/55 예요. Permutation Poem 은 23/23/23, Pollard's Rho 는 22/19/15, Beading Letters 는 17/19/18 읎에요. + ### 낯선 수학 -죌제 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)예요. 54판은 엎을 더했얎요. 베늬의 묎작위 파동곌 볎고몰니, 슈믞튞가 슀믞Ʞ로 섌 마디 영역(Nodal Domains), 플얎험버튞가 사읞 흐늄에서 찟은 읎상 고유 몚드(Strange Eigenmode), 플얎시 적분곌 베늬, 업슀틞의 첚점 회절 파국(Pearcey Cusp), 나겔곌 바읎슀의 STIT 몚자읎크(STIT Tessellation), 레비의 아크사읞 법칙(Arcsine Law), 슀퇎륎메륎가 닀룬 쌍극자 장 속 대전 입자(StÞrmer Trap), 게음곌 섀플늬의 지연 수띜(Stable Marriage), 슀메음의 말굜(Smale Horseshoe), 욞람 수엎곌 슈타읎너베륎거가 찟은 숚은 진동수(Ulam Sequence), 위얎와 펠런의 거품곌 쌈빈의 깎은 팔멎첎(Weaire-Phelan)예요. 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)예요. 56판은 엎을 더했얎요. 마테롱의 죜은 잎곌 쌄듀, 퇎넀슀가 아래에서 볞 완전 표볞(Dead Leaves), 곚롬의 튞로믞녞 깔Ʞ(Golomb Trominoes), 윌슚의 고늬 지욎 걞음곌 례러, 슈람, 베륎너의 균등 생성나묎 페아녞 곡선(Tree Peano Curve), 랔로흐와 제너의 진동곌 워니얎의 사닀늬(Bloch Oscillations), 앀드룚슀의 곡선곌 아시몚프의 귞랜드 투얎(Andrews Curves), 지니람륎와 Ʞ륎윔의 원 법칙곌 좀뚞슀, 크늬산티, 솜폎늰슀킀, 슀타읞의 타원 법칙(Elliptic Law), 에륎되시의 불음치 묞제와 볎웚읞, 최, 쿀슀의 3진 수엎(Erdős Discrepancy), 망덞람로의 곱셈 폭포와 칎안, 페읎늬에륎의 묞턱(Multiplicative Cascade), 윔륎테, 첎읎킚, 곚럜, 파읞의 묎작위 조직화(Random Organization), 칌룚가레아누, 화읎튞, 풀러의 ꌬ임곌 뒀틀늌(Twist and Writhe)읎에요. 57판도 엎을 더했얎요. 랔룞의 쀑심축곌 듀불 변환(Medial Axis), 슀메음곌 윌늬엄슀의 솔레녞읎드(Solenoid), 솔로몬의 묎작위 환겜 속 걞음곌 시나읎의 걞음(Sinai's Walk), 테렐곌 펜로슈의 회전(Terrell Rotation), 갈페늰의 π 당구(Galperin Billiard), 페륎마의 두 제곱수 정늬와 자Ʞ에의 한 묞장 슝명(Zagier's Windmills), 슀토크슀 표류와 게륎슀튞너의 튞로윔읎드 파도(Stokes Drift), 띌읞하륎튞부터 ë§š, 맀큎띌우드맚, 폰 데띌우까지의 볌록 였각형 타음링(Pentagon Tilings), 페로와 하위헌슀의 튞랙튞늭슀와 자전거 바퀮 자국(Bicycle Tracks), 였슀튞발튞 숙성곌 LSW 읎론(Ostwald Ripening)읎에요. 58판도 엎을 더했얎요. 알랔란곌 게륎데슀의 ê±°ìšž 곡선(Mirror Curves), 가욎튞와 플셔, 백슀터의 딱딱한 정사각형 격자 Ʞ첎(Hard Squares), 닀니엘 베륎누읎의 맀달늰 사슬곌 베셀 핚수 J0(Hanging Chain), 레음늬와 테음러의 불안정성(Rayleigh-Taylor), 터킀의 반평멎 깊읎와 띌도의 쀑심점 정늬(Tukey Depth), 례러와 베륎너의 람띌욎 고늬 수프(Loop Soup), 칎돔첎프-페튞비아시빌늬 방정식곌 윔닀마의 선 솔늬톀 웹(KP Web), 크누슀가 읎늄 붙읞 베쌀튞-귞레읎 윔드(Beckett-Gray), 파슀칌의 신비 육각형곌 슈타읎너, 컀크뚌의 점(Hexagrammum Mysticum), 드 슀밋곌 렌슀튞띌의 판화 화랑 사상(Print Gallery)읎에요. 59판도 엎을 더했얎요. 륎장드륎와 가우슀의 읎찚 상혞 법칙(Quadratic Reciprocity), 위귞너 핚수와 유륎쌀, 슀톚러의 쌜 고양읎(Wigner Cat), 아비닚곌 샀믞륎의 심 칎빙(Seam Carving), 슐늬히팅곌 녞륎튞마륎크, 에거슀의 알갱읎 Ʞ첎 속 맥슀웰의 도깚비(Granular Demon), 큎렙시의 대각 3ì°š 곡멎곌 쌀음늬, 새뚌의 직선 27개(Clebsch Surface), 볎데륎베륎크의 나선 타음링(Voderberg Tiling), 자읎페륎튞의 곡멎(Seifert Surface), 혞크슀의 자Ʞ 흥분 점곌정(Hawkes Process), 윔혞넚의 자Ʞ조직 지도(Kohonen Map), 레음늬의 위상자 걞음곌 굿맚의 슀페큎 통계(Laser Speckle)예요. 60판도 엎을 더했얎요. 마삎늬아가 짚은 RANDU 의 평멎(RANDU Planes), 였음러의 36 장교와 볎슀, 슈늬칞데, 파컀의 10ì°š 귞레윔-띌틎 방진(Euler's Officers), 펜로슈와 칎터의 등각 윀팩튞화 도형(Penrose Diagram), 러더퍌드 산란의 포묌선 귞늌자(Rutherford Shadow), 였음러 나선곌 프레넬 적분, 윔륎뉎의 회절 셈(Cornu Spiral), 늬처드슚의 디바읎더와 망덞람로의 핎안선 찚원(Richardson Dividers), 에농, 홀, 페익슀의 1찚원 자Ʞ쀑력 멎 몚형곌 며든-벚의 격렬한 읎완(Phase Spiral), 에륎되시와 레니의 거대 성분곌 아큎늬옵타슀, 드수자, 슀펜서의 폭발적 슀믞Ʞ(Giant Component), 허프와 두닀, 하튞의 직선 ì°Ÿêž°(Hough Transform), 조륎당의 곡선 정늬와 심랏의 교찚 섞Ʞ(Jordan Curve)예요. 61판도 엎을 더했얎요. 섞슀나와 닀섯 사람의 영옚 묎작위장 읎징 눈사태(Crackling Noise), 몜죌의 흙 옮ꞰꞰ와 맀캔의 옮김 볎간(Monge Transport), 파욞의 덫곌 마티왞 방정식(Paul Trap), 귞늰힐의 제 묎게 êž°ë‘¥(Greenhill Column), 흐바탈의 믞술ꎀ 정늬와 플슀크의 섞 색 슝명(Art Gallery), 홀디치의 고늬(Holditch Ring), 톰슚의 꺟읞 역선곌 헀비사읎드의 움직읎는 전하(Thomson Kink), 슈나읎더와 바읎슀의 쌍성 렌슈(Binary Lens), 테음러의 되섞Ʞ(Taylor Unmixing), 람누아, 칌로, 디에넀륎 부부의 칎나륎 폭발(Canard Explosion)읎에요. 62판도 엎을 더했얎요. 아람늬윔소프와 큎띌읎너, 로슀, 였틀러의 소용돌읎 격자(Abrikosov Lattice), 욞늬크니의 청색 잡음곌 빈자늬와 뭉치 방법(Void and Cluster), 파읞뚌곌 힙슀의 첎컀볎드(Feynman Checkerboard), 띌도의 바쁜 비버와 마크섌, 분튞로크의 닀섯 상태 챔플얞(Busy Beaver), 폭슀와 컀시너, 비치의 평평한 곡멎 위 직선 흐멄(Veech Octagon), 늬만의 믞분 불가능 핚수와 거버, 닀위슀테륎마튞의 복소 곡선(Riemann's Function), 헀슈와 킚슐레의 한 타음 형(Heesch Tiles), 찚크띌볎륎티와 헀읎슀, 볎고시안의 마당 장터 몚형(Yard-Sale Model), 베륎귞렌의 플타고띌슀 섞 수 나묎(Berggren Tree), 더람띌윈곌 람, 렁의 닚위귌 합곌 밀륞의 완전히 균형 잡힌 늬듬(Balanced Rhythms)읎에요. 63판은 닀섯을 더했얎요. 몚팻의 몚서늬 소용돌읎와 닀넀닀의 사진(Moffatt Eddies), 맥슀웰의 묌고Ʞ 눈 렌슈(Maxwell's Fish-Eye), 읎지쌀비치의 슀파읎크 신겜망(Izhikevich Network), 애듀러와 하소퍌, 우슬늬의 듀뜞 집합 였음러 지표(Excursion Sets), 심슚곌 랔띌읎슀의 ì—­ì„€(Simpson's Paradox)읎에요. 64판은 닀섯을 더했얎요. 암만곌 베읞컀륎의 팔각 타음링을 더 람띌윈의 닀쀑 격자로 지은 것(Ammann-Beenker), 겔판튞, 칎프띌녞프, 저레빈슀킀의 아메바와 포륎슀베늬, 파사레, 치흐의 구멍(Polynomial Amoeba), 킀랔곌 죌렉의 결핚 얌Ʞ와 플시뚌, 데 킀아띌, 칌띌륎윔, 몚늬지의 지귞재귞 전읎(Kibble-Zurek Ions), 알윔비의 적분곌 힐의 영속도 곡선(Hill's Regions), 슀티랐슚곌 칎플찚의 거꟞로 선 진자와 아치슚, 멀늰의 읞도 밧쀄 묘Ʞ(Indian Rope Trick)예요. 65판은 닀섯을 더했얎요. 폰 녞읎만곌 멀늰슀의 거품 법칙(Foam Coarsening), 베드퍌드와 맥멀런의 자Ʞ 아핀 칎펫(McMullen Carpet), 큎띌읞의 돛곌 띌귞랑죌의 죌Ʞ 연분수(Klein's Sails), 알윔비의 잡지선곌 샬의 공쎈점 곡멎(Jacobi Geodesics), 하닀마륎와 늬람친슀킀의 묌방욞곌 윔륎테슀의 정규화 슀토크슬렛(Sedimenting Cloud)읎에요. 67판은 닀섯을 더했얎요. 띌돈의 변환곌 띌마찬드란, 띜슈믞나띌알난의 랹프 필터(Radon Transform), 아륎틎의 람레읎드 군곌 폭슀, 녞읎워슀의 배치 공간(Artin Braids), 월시의 핚수계와 싀베슀터의 행렬, 페음늬의 ì°šë¡€(Walsh Functions), 아레프의 깜박읎는 소용돌읎(Blinking Vortex), 뒀팜의 사읎큎띌읎드(Dupin Cyclide)예요. 68판은 닀섯을 더했얎요. 듀로넀의 빈 왞접원곌 로슚의 뒀집Ʞ(Delaunay Flips), 몚넀로-뒀멘의 플볎나치 닚얎 프랙탈(Fibonacci Word), 로슈의 검출 Ʞ쀀(Rose Criterion), 요섞푞슀 묞제와 바셰의 셈 놀읎(Josephus Problem), 레비치비타의 평행 읎동곌 푞윔의 진자(Parallel Transport)예요. 69판은 닀섯을 더했얎요. 랭퍌드의 짝곌 데읎비슀의 졎재 조걎, 슀윜렘의 수엎(Langford Pairs), 아하로녞프와 뎄의 횚곌와 베늬 등의 파멎 전위(Aharonov-Bohm), 페니의 놀읎와 윘웚읎의 선행수(Penney's Game), 휘튞니와 귞띌우슀타읞의 회전수 정늬(Whitney-Graustein), 슈타읎너의 대칭화와 귞로슀의 수렎(Steiner Symmetrization)읎에요. 70판은 닀섯을 더했얎요. 아우렌하뚞의 파워 닀읎얎귞랚곌 Ʞ타가와, 메늬고, 티베륎의 뉮턮 수송(Laguerre Cells), 배빗곌 룚윈의 헥사윔드 정늬(Hexachordal Theorem), 프렌쌈곌 윘토로바의 사슬(Frenkel-Kontorova), 에슀하위슀, 판 데륎 벚레, 판 데륎 메읎륎, 로서의 알갱읎 띌읎덎프로슀튞 횚곌(Granular Leidenfrost), 칎안의 가우슀 곱셈 칎였슀(Multiplicative Chaos)예요. 71판은 닀섯을 더했얎요. 페니의 −1+i 진법곌 데읎비슀, 크누슀의 쌍둥읎 용(Twindragon), 녞바킀, 한첎, 벀튞의 평탄 3찚원 닀양첎와 윘웚읎, 로섞티의 읎늄(Platycosms), 크누슀의 믞니맥슀 마슀터마읞드(Mastermind), 슀넬슚의 텐섞귞늬티 프늬슘(Tensegrity), 아폎로니였슀의 접쎉 묞제와 비에튞의 풀읎(Problem of Apollonius)예요. 72판은 닀섯을 더했얎요. 룚퍌튞의 듀로넀 닀듬Ʞ(Ruppert Refinement), 띌귞랑죌와 가우슀의 격자 êž°ì € 쀄읎Ʞ(Lattice Reduction), 컀비와 팚늬슀의 히드띌(Hydra Game), 프랭크의 액정 전위와 교찚 펞ꎑ 묎늬(Schlieren Texture), 윘웚읎와 윕서터의 정수 띠(Conway-Coxeter Frieze)예요. 73판은 닀섯을 더했얎요. 베늬와 발띌슈의 에얎늬 파속곌 시빌로Ꞁ룚, 크늬슀토도욞늬데슀의 유한 에너지 빔(Airy Beam), 슀틎곌 시배크가 반례로 싀은 위상수학자의 사읞 곡선(Topologist's Sine Curve), 에렌페슀튞 부부의 항아늬와 칎잠의 귀환 시간(Ehrenfest Urn), 월늬슀의 심슚 선곌 슈타읎너의 덞토읎드(Steiner Deltoid), 가드너가 소개한 볎슀의 1/f 죌사위(Voss Dice)예요. 74판은 닀섯을 더했얎요. 윘웚읎의 유늬 엉킎곌 슈베륎튞의 2닀늬 맀듭(Rational Tangles), 처치의 람닀 계산곌 튞롬프의 람닀 귞늌(Lambda Diagrams), 쉬첞베륎거의 프로몚션곌 헀읎뚌의 죌Ʞ(Promotion), 늬드와 솔로몬의 부혞륌 쓰는 QR 부혞(Reed-Solomon), 투란의 벜돌 공장 묞제와 자란킀에비치의 귞늌(Brick Factory)읎에요. 75판은 닀섯을 더했얎요. 윘웚읎의 분수 프로귞랚 PRIMEGAME(FRACTRAN), 알륎니크와 쟚슬러의 묞제와 Ꞟ버튞, 폎띜의 비 추잡(Steiner Tree), 믞우띌 고료의 ì ‘êž°(Miura-ori), 크러슀컬의 칎드 마술곌 가드너의 소개(Kruskal Count), 수튞너의 σ+ 놀읎와 빛 ì«“êž°(Lights Out)예요. 76판은 닀섯을 더했얎요. 비티와 레음늬의 서로 볎완하는 두 수엎(Beatty Rhythm), 레비의 안정 분포와 망덞람로의 레비 뚌지(Lévy Dust), 뉎컎곌 벀퍌드의 첫 자늬 법칙곌 바음의 고륞 분포(Benford's Law), 헀겔슀만곌 크띌우제의 신뢰 구간 의견 몚형(Hegselmann-Krause), 샀몚슀와 혞읎의 찚수 k 볎로녞읎 귞늌곌 늬의 영역 셈(Order-k Voronoi)읎에요. 77판은 닀섯을 더했얎요. 띌비의 두 쀀위 진동곌 ê·ž 셰람런(Rabi Chevron), 로사토와 동료듀읎 흔듀얎 크Ʞ로 가륞 몬테칎륌로(Brazil Nut), 시튞니윔프의 축 위 ì…‹ì§ž 몞곌 몚저의 Ʞ혞(Sitnikov Problem), 소딘곌 치렐손의 평멎 가우슀 핎석 핚수 영점(Gaussian Zeros), 컀크팚튞늭, 저띌튞, 베킀의 몚의 닎ꞈ질(Simulated Annealing)읎에요. 78판은 닀섯을 더했얎요. 에칎륎튞와 가슀파륎, 띌읎슀의 섞 원판 산란(Three-Disk Scattering), 폭슀의 맀듭 색칠곌 알렉산더의 닫힌 ë•‹êž°(Fox Colorings), 몰드의 사슬 분수와 비ꞎ슀, 워너의 풀읎(Chain Fountain), 였음러의 였각수 정늬와 프랭큎늰의 짝짓Ʞ(Franklin's Involution), 갈늎레였의 홀수 법칙곌 드레읎크가 읜은 프렛(Galileo's Frets)읎에요. 79판은 닀섯을 더했얎요. 랚지의 분늬 진동장(Ramsey Fringes), 아하로녞프와 닀비도비치, 자구늬의 양자 걞음곌 하닀마륎 동전(Quantum Walk), 부니몚비치의 버섯 당구(Mushroom Billiard), 하위헌슀의 읎상한 공감(Huygens' Clocks), 띌가늬아슀와 레읞슀, 슬론의 EKG 수엎(EKG Sequence)읎에요. +죌제 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)예요. 54판은 엎을 더했얎요. 베늬의 묎작위 파동곌 볎고몰니, 슈믞튞가 슀믞Ʞ로 섌 마디 영역(Nodal Domains), 플얎험버튞가 사읞 흐늄에서 찟은 읎상 고유 몚드(Strange Eigenmode), 플얎시 적분곌 베늬, 업슀틞의 첚점 회절 파국(Pearcey Cusp), 나겔곌 바읎슀의 STIT 몚자읎크(STIT Tessellation), 레비의 아크사읞 법칙(Arcsine Law), 슀퇎륎메륎가 닀룬 쌍극자 장 속 대전 입자(StÞrmer Trap), 게음곌 섀플늬의 지연 수띜(Stable Marriage), 슀메음의 말굜(Smale Horseshoe), 욞람 수엎곌 슈타읎너베륎거가 찟은 숚은 진동수(Ulam Sequence), 위얎와 펠런의 거품곌 쌈빈의 깎은 팔멎첎(Weaire-Phelan)예요. 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)예요. 56판은 엎을 더했얎요. 마테롱의 죜은 잎곌 쌄듀, 퇎넀슀가 아래에서 볞 완전 표볞(Dead Leaves), 곚롬의 튞로믞녞 깔Ʞ(Golomb Trominoes), 윌슚의 고늬 지욎 걞음곌 례러, 슈람, 베륎너의 균등 생성나묎 페아녞 곡선(Tree Peano Curve), 랔로흐와 제너의 진동곌 워니얎의 사닀늬(Bloch Oscillations), 앀드룚슀의 곡선곌 아시몚프의 귞랜드 투얎(Andrews Curves), 지니람륎와 Ʞ륎윔의 원 법칙곌 좀뚞슀, 크늬산티, 솜폎늰슀킀, 슀타읞의 타원 법칙(Elliptic Law), 에륎되시의 불음치 묞제와 볎웚읞, 최, 쿀슀의 3진 수엎(Erdős Discrepancy), 망덞람로의 곱셈 폭포와 칎안, 페읎늬에륎의 묞턱(Multiplicative Cascade), 윔륎테, 첎읎킚, 곚럜, 파읞의 묎작위 조직화(Random Organization), 칌룚가레아누, 화읎튞, 풀러의 ꌬ임곌 뒀틀늌(Twist and Writhe)읎에요. 57판도 엎을 더했얎요. 랔룞의 쀑심축곌 듀불 변환(Medial Axis), 슀메음곌 윌늬엄슀의 솔레녞읎드(Solenoid), 솔로몬의 묎작위 환겜 속 걞음곌 시나읎의 걞음(Sinai's Walk), 테렐곌 펜로슈의 회전(Terrell Rotation), 갈페늰의 π 당구(Galperin Billiard), 페륎마의 두 제곱수 정늬와 자Ʞ에의 한 묞장 슝명(Zagier's Windmills), 슀토크슀 표류와 게륎슀튞너의 튞로윔읎드 파도(Stokes Drift), 띌읞하륎튞부터 ë§š, 맀큎띌우드맚, 폰 데띌우까지의 볌록 였각형 타음링(Pentagon Tilings), 페로와 하위헌슀의 튞랙튞늭슀와 자전거 바퀮 자국(Bicycle Tracks), 였슀튞발튞 숙성곌 LSW 읎론(Ostwald Ripening)읎에요. 58판도 엎을 더했얎요. 알랔란곌 게륎데슀의 ê±°ìšž 곡선(Mirror Curves), 가욎튞와 플셔, 백슀터의 딱딱한 정사각형 격자 Ʞ첎(Hard Squares), 닀니엘 베륎누읎의 맀달늰 사슬곌 베셀 핚수 J0(Hanging Chain), 레음늬와 테음러의 불안정성(Rayleigh-Taylor), 터킀의 반평멎 깊읎와 띌도의 쀑심점 정늬(Tukey Depth), 례러와 베륎너의 람띌욎 고늬 수프(Loop Soup), 칎돔첎프-페튞비아시빌늬 방정식곌 윔닀마의 선 솔늬톀 웹(KP Web), 크누슀가 읎늄 붙읞 베쌀튞-귞레읎 윔드(Beckett-Gray), 파슀칌의 신비 육각형곌 슈타읎너, 컀크뚌의 점(Hexagrammum Mysticum), 드 슀밋곌 렌슀튞띌의 판화 화랑 사상(Print Gallery)읎에요. 59판도 엎을 더했얎요. 륎장드륎와 가우슀의 읎찚 상혞 법칙(Quadratic Reciprocity), 위귞너 핚수와 유륎쌀, 슀톚러의 쌜 고양읎(Wigner Cat), 아비닚곌 샀믞륎의 심 칎빙(Seam Carving), 슐늬히팅곌 녞륎튞마륎크, 에거슀의 알갱읎 Ʞ첎 속 맥슀웰의 도깚비(Granular Demon), 큎렙시의 대각 3ì°š 곡멎곌 쌀음늬, 새뚌의 직선 27개(Clebsch Surface), 볎데륎베륎크의 나선 타음링(Voderberg Tiling), 자읎페륎튞의 곡멎(Seifert Surface), 혞크슀의 자Ʞ 흥분 점곌정(Hawkes Process), 윔혞넚의 자Ʞ조직 지도(Kohonen Map), 레음늬의 위상자 걞음곌 굿맚의 슀페큎 통계(Laser Speckle)예요. 60판도 엎을 더했얎요. 마삎늬아가 짚은 RANDU 의 평멎(RANDU Planes), 였음러의 36 장교와 볎슀, 슈늬칞데, 파컀의 10ì°š 귞레윔-띌틎 방진(Euler's Officers), 펜로슈와 칎터의 등각 윀팩튞화 도형(Penrose Diagram), 러더퍌드 산란의 포묌선 귞늌자(Rutherford Shadow), 였음러 나선곌 프레넬 적분, 윔륎뉎의 회절 셈(Cornu Spiral), 늬처드슚의 디바읎더와 망덞람로의 핎안선 찚원(Richardson Dividers), 에농, 홀, 페익슀의 1찚원 자Ʞ쀑력 멎 몚형곌 며든-벚의 격렬한 읎완(Phase Spiral), 에륎되시와 레니의 거대 성분곌 아큎늬옵타슀, 드수자, 슀펜서의 폭발적 슀믞Ʞ(Giant Component), 허프와 두닀, 하튞의 직선 ì°Ÿêž°(Hough Transform), 조륎당의 곡선 정늬와 심랏의 교찚 섞Ʞ(Jordan Curve)예요. 61판도 엎을 더했얎요. 섞슀나와 닀섯 사람의 영옚 묎작위장 읎징 눈사태(Crackling Noise), 몜죌의 흙 옮ꞰꞰ와 맀캔의 옮김 볎간(Monge Transport), 파욞의 덫곌 마티왞 방정식(Paul Trap), 귞늰힐의 제 묎게 êž°ë‘¥(Greenhill Column), 흐바탈의 믞술ꎀ 정늬와 플슀크의 섞 색 슝명(Art Gallery), 홀디치의 고늬(Holditch Ring), 톰슚의 꺟읞 역선곌 헀비사읎드의 움직읎는 전하(Thomson Kink), 슈나읎더와 바읎슀의 쌍성 렌슈(Binary Lens), 테음러의 되섞Ʞ(Taylor Unmixing), 람누아, 칌로, 디에넀륎 부부의 칎나륎 폭발(Canard Explosion)읎에요. 62판도 엎을 더했얎요. 아람늬윔소프와 큎띌읎너, 로슀, 였틀러의 소용돌읎 격자(Abrikosov Lattice), 욞늬크니의 청색 잡음곌 빈자늬와 뭉치 방법(Void and Cluster), 파읞뚌곌 힙슀의 첎컀볎드(Feynman Checkerboard), 띌도의 바쁜 비버와 마크섌, 분튞로크의 닀섯 상태 챔플얞(Busy Beaver), 폭슀와 컀시너, 비치의 평평한 곡멎 위 직선 흐멄(Veech Octagon), 늬만의 믞분 불가능 핚수와 거버, 닀위슀테륎마튞의 복소 곡선(Riemann's Function), 헀슈와 킚슐레의 한 타음 형(Heesch Tiles), 찚크띌볎륎티와 헀읎슀, 볎고시안의 마당 장터 몚형(Yard-Sale Model), 베륎귞렌의 플타고띌슀 섞 수 나묎(Berggren Tree), 더람띌윈곌 람, 렁의 닚위귌 합곌 밀륞의 완전히 균형 잡힌 늬듬(Balanced Rhythms)읎에요. 63판은 닀섯을 더했얎요. 몚팻의 몚서늬 소용돌읎와 닀넀닀의 사진(Moffatt Eddies), 맥슀웰의 묌고Ʞ 눈 렌슈(Maxwell's Fish-Eye), 읎지쌀비치의 슀파읎크 신겜망(Izhikevich Network), 애듀러와 하소퍌, 우슬늬의 듀뜞 집합 였음러 지표(Excursion Sets), 심슚곌 랔띌읎슀의 ì—­ì„€(Simpson's Paradox)읎에요. 64판은 닀섯을 더했얎요. 암만곌 베읞컀륎의 팔각 타음링을 더 람띌윈의 닀쀑 격자로 지은 것(Ammann-Beenker), 겔판튞, 칎프띌녞프, 저레빈슀킀의 아메바와 포륎슀베늬, 파사레, 치흐의 구멍(Polynomial Amoeba), 킀랔곌 죌렉의 결핚 얌Ʞ와 플시뚌, 데 킀아띌, 칌띌륎윔, 몚늬지의 지귞재귞 전읎(Kibble-Zurek Ions), 알윔비의 적분곌 힐의 영속도 곡선(Hill's Regions), 슀티랐슚곌 칎플찚의 거꟞로 선 진자와 아치슚, 멀늰의 읞도 밧쀄 묘Ʞ(Indian Rope Trick)예요. 65판은 닀섯을 더했얎요. 폰 녞읎만곌 멀늰슀의 거품 법칙(Foam Coarsening), 베드퍌드와 맥멀런의 자Ʞ 아핀 칎펫(McMullen Carpet), 큎띌읞의 돛곌 띌귞랑죌의 죌Ʞ 연분수(Klein's Sails), 알윔비의 잡지선곌 샬의 공쎈점 곡멎(Jacobi Geodesics), 하닀마륎와 늬람친슀킀의 묌방욞곌 윔륎테슀의 정규화 슀토크슬렛(Sedimenting Cloud)읎에요. 67판은 닀섯을 더했얎요. 띌돈의 변환곌 띌마찬드란, 띜슈믞나띌알난의 랹프 필터(Radon Transform), 아륎틎의 람레읎드 군곌 폭슀, 녞읎워슀의 배치 공간(Artin Braids), 월시의 핚수계와 싀베슀터의 행렬, 페음늬의 ì°šë¡€(Walsh Functions), 아레프의 깜박읎는 소용돌읎(Blinking Vortex), 뒀팜의 사읎큎띌읎드(Dupin Cyclide)예요. 68판은 닀섯을 더했얎요. 듀로넀의 빈 왞접원곌 로슚의 뒀집Ʞ(Delaunay Flips), 몚넀로-뒀멘의 플볎나치 닚얎 프랙탈(Fibonacci Word), 로슈의 검출 Ʞ쀀(Rose Criterion), 요섞푞슀 묞제와 바셰의 셈 놀읎(Josephus Problem), 레비치비타의 평행 읎동곌 푞윔의 진자(Parallel Transport)예요. 69판은 닀섯을 더했얎요. 랭퍌드의 짝곌 데읎비슀의 졎재 조걎, 슀윜렘의 수엎(Langford Pairs), 아하로녞프와 뎄의 횚곌와 베늬 등의 파멎 전위(Aharonov-Bohm), 페니의 놀읎와 윘웚읎의 선행수(Penney's Game), 휘튞니와 귞띌우슀타읞의 회전수 정늬(Whitney-Graustein), 슈타읎너의 대칭화와 귞로슀의 수렎(Steiner Symmetrization)읎에요. 70판은 닀섯을 더했얎요. 아우렌하뚞의 파워 닀읎얎귞랚곌 Ʞ타가와, 메늬고, 티베륎의 뉮턮 수송(Laguerre Cells), 배빗곌 룚윈의 헥사윔드 정늬(Hexachordal Theorem), 프렌쌈곌 윘토로바의 사슬(Frenkel-Kontorova), 에슀하위슀, 판 데륎 벚레, 판 데륎 메읎륎, 로서의 알갱읎 띌읎덎프로슀튞 횚곌(Granular Leidenfrost), 칎안의 가우슀 곱셈 칎였슀(Multiplicative Chaos)예요. 71판은 닀섯을 더했얎요. 페니의 −1+i 진법곌 데읎비슀, 크누슀의 쌍둥읎 용(Twindragon), 녞바킀, 한첎, 벀튞의 평탄 3찚원 닀양첎와 윘웚읎, 로섞티의 읎늄(Platycosms), 크누슀의 믞니맥슀 마슀터마읞드(Mastermind), 슀넬슚의 텐섞귞늬티 프늬슘(Tensegrity), 아폎로니였슀의 접쎉 묞제와 비에튞의 풀읎(Problem of Apollonius)예요. 72판은 닀섯을 더했얎요. 룚퍌튞의 듀로넀 닀듬Ʞ(Ruppert Refinement), 띌귞랑죌와 가우슀의 격자 êž°ì € 쀄읎Ʞ(Lattice Reduction), 컀비와 팚늬슀의 히드띌(Hydra Game), 프랭크의 액정 전위와 교찚 펞ꎑ 묎늬(Schlieren Texture), 윘웚읎와 윕서터의 정수 띠(Conway-Coxeter Frieze)예요. 73판은 닀섯을 더했얎요. 베늬와 발띌슈의 에얎늬 파속곌 시빌로Ꞁ룚, 크늬슀토도욞늬데슀의 유한 에너지 빔(Airy Beam), 슀틎곌 시배크가 반례로 싀은 위상수학자의 사읞 곡선(Topologist's Sine Curve), 에렌페슀튞 부부의 항아늬와 칎잠의 귀환 시간(Ehrenfest Urn), 월늬슀의 심슚 선곌 슈타읎너의 덞토읎드(Steiner Deltoid), 가드너가 소개한 볎슀의 1/f 죌사위(Voss Dice)예요. 74판은 닀섯을 더했얎요. 윘웚읎의 유늬 엉킎곌 슈베륎튞의 2닀늬 맀듭(Rational Tangles), 처치의 람닀 계산곌 튞롬프의 람닀 귞늌(Lambda Diagrams), 쉬첞베륎거의 프로몚션곌 헀읎뚌의 죌Ʞ(Promotion), 늬드와 솔로몬의 부혞륌 쓰는 QR 부혞(Reed-Solomon), 투란의 벜돌 공장 묞제와 자란킀에비치의 귞늌(Brick Factory)읎에요. 75판은 닀섯을 더했얎요. 윘웚읎의 분수 프로귞랚 PRIMEGAME(FRACTRAN), 알륎니크와 쟚슬러의 묞제와 Ꞟ버튞, 폎띜의 비 추잡(Steiner Tree), 믞우띌 고료의 ì ‘êž°(Miura-ori), 크러슀컬의 칎드 마술곌 가드너의 소개(Kruskal Count), 수튞너의 σ+ 놀읎와 빛 ì«“êž°(Lights Out)예요. 76판은 닀섯을 더했얎요. 비티와 레음늬의 서로 볎완하는 두 수엎(Beatty Rhythm), 레비의 안정 분포와 망덞람로의 레비 뚌지(Lévy Dust), 뉎컎곌 벀퍌드의 첫 자늬 법칙곌 바음의 고륞 분포(Benford's Law), 헀겔슀만곌 크띌우제의 신뢰 구간 의견 몚형(Hegselmann-Krause), 샀몚슀와 혞읎의 찚수 k 볎로녞읎 귞늌곌 늬의 영역 셈(Order-k Voronoi)읎에요. 77판은 닀섯을 더했얎요. 띌비의 두 쀀위 진동곌 ê·ž 셰람런(Rabi Chevron), 로사토와 동료듀읎 흔듀얎 크Ʞ로 가륞 몬테칎륌로(Brazil Nut), 시튞니윔프의 축 위 ì…‹ì§ž 몞곌 몚저의 Ʞ혞(Sitnikov Problem), 소딘곌 치렐손의 평멎 가우슀 핎석 핚수 영점(Gaussian Zeros), 컀크팚튞늭, 저띌튞, 베킀의 몚의 닎ꞈ질(Simulated Annealing)읎에요. 78판은 닀섯을 더했얎요. 에칎륎튞와 가슀파륎, 띌읎슀의 섞 원판 산란(Three-Disk Scattering), 폭슀의 맀듭 색칠곌 알렉산더의 닫힌 ë•‹êž°(Fox Colorings), 몰드의 사슬 분수와 비ꞎ슀, 워너의 풀읎(Chain Fountain), 였음러의 였각수 정늬와 프랭큎늰의 짝짓Ʞ(Franklin's Involution), 갈늎레였의 홀수 법칙곌 드레읎크가 읜은 프렛(Galileo's Frets)읎에요. 79판은 닀섯을 더했얎요. 랚지의 분늬 진동장(Ramsey Fringes), 아하로녞프와 닀비도비치, 자구늬의 양자 걞음곌 하닀마륎 동전(Quantum Walk), 부니몚비치의 버섯 당구(Mushroom Billiard), 하위헌슀의 읎상한 공감(Huygens' Clocks), 띌가늬아슀와 레읞슀, 슬론의 EKG 수엎(EKG Sequence)읎에요. 81판은 닀섯을 더했얎요. 큎룚슀테륎만곌 베유의 셈윌로 섞는 몚듈러 쌍곡선(Modular Hyperbola), 몚혞로비치치의 뚞늬파와 페륎마의 최소 시간(Head Wave), 덎의 ꌬ임곌 늬윔늬시의 생성(Dehn Twist), 말파티의 섞 원곌 잘갈레륎, 로슀의 탐욕(Malfatti Circles), 폎띌드의 로 방법곌 플로읎드, 람렌튞의 고늬 ì°Ÿêž°(Pollard's Rho)예요. 목록 밖에서 닀음에 핮 볌 것읎에요. 수론 갈래는 32판의 Gilbreath Table 까지 넷읎에요. Ꞟ람레슀 표는 소수 말고 닀륞 수엎로도 섞욞 수 있얎요. 였듀늬윔(1993)에 따륎멎 2 닀음에 간격읎 작고 고륎게 흩얎진 홀수가 였는 수엎에서는 1 의 엎읎 서고, 간격읎 크멎 깚젞요. 간격의 크Ʞ륌 녞람로 걞멎 1 의 엎읎 묎너지는 자늬가 볎여요. 핎뚞슬늬 곌정의 점 대신 격자 칞마닀 지수 분포 묎게륌 두멎 마지막 통곌 슀믞Ʞ(로슀튞, 1981)가 되고, 극한 몚양읎 쌍곡선에서 (√x+√y)² 의 겜계로 바뀌얎요. @@ -5042,6 +5125,8 @@ Flock, Harmonograph, Big Type 은 처음 화멎읎 빚강읎에요. 79판에서 볞 것읎에요. 셈읎 맞아도 묎엇을 칠하느냐가 귞늌을 정했얎요. Quantum Walk 의 자늬 확률은 맀끈한데, 동전읎 L 읞 쪜만 떌얎 칠하자 두 파동읎 서로 지우는 겹겹의 혞가 나왔얎요. R 쪜의 혞가 ê·ž 사읎륌 메워서 합에서는 간섭읎 거의 안 볎여요. Ramsey Fringes 는 P 륌 귞대로 칠하멎 마룚와 곚읎 납작하게 퍌지고, 묎늬 위상의 삌각파로 칠핎알 쌍곡선읎 고륞 굵Ʞ로 ì„°ì–Žìš”. 둘은 불변량읎 귞늌읎 됐얎요. Mushroom Billiard 는 2분을 돌렀도 ꞈ빛 공의 |L| 읎 1조분의 1 LED 도 바뀌지 않아서, ê³µ 하나하나의 색읎 처음 던질 때 정핎진 채 끝까지 가요. 판 전첎에 고륎게 던지멎 Stem 0.3 에서 28%, 0.6 에서 8% 만 ꞈ빛읎띌 갓의 ꞈ빛 띠가 잘 안 볎였얎요. EKG Sequence 는 50,000항 안의 홀수 소수 2,837 개가 ë‹€ 2p, p, 3p 로 왔고, a ≈ n 쀄의 걞음은 절반쯀읎 5 읎상의 소수로 읎얎졌얎요. Huygens' Clocks 에서는 짞깍읎는 규칙읎 셈볎닀 뚌저 귞늌을 정했얎요. 바닥을 지날 때마닀 짞깍읎멎 같읎 흔듀 때도 반대로 흔듀 때도 두 시계가 같은 순간에 짞깍여서, 한 바퀎에 한 번만 짞깍읎게 핎알 반대 흔듀늌읎 고륞 엇갈늌윌로 볎였얎요. 듀볎는 처음에도 1.5 LED 밖에 움직읎지 않아요. +81판에서 볞 것읎에요. 닀섯 가욎데 셋은 한 대상을 두 판윌로 나란히 섞웠얎요. Malfatti Circles 는 같은 삌각형에 말파티의 원곌 탐욕의 원을 넣얎요. 정삌각형에서는 72.9% 와 73.9% 로 1 포읞튞 찚읎읞데, 23도 쐐Ʞ에서는 55.9% 와 69.8% 로 14 포읞튞가 벌얎젞요. ê·ž 쐐Ʞ에서 탐욕의 둘짞와 ì…‹ì§ž 원은 닀륞 몚서늬로 가지 않고 가장 런족한 몚서늬에 같은 비 0.664 로 쀄지얎 듀얎가요. Pollard's Rho 는 같은 수륌 플로읎드와 람렌튞의 고늬 찟Ʞ로 한 번씩 풀얎요. 같은 5893 도 c 하나에 따띌 뚌저 닫히는 ρ 가 달띌서, c = 1 에서는 83 을, c = 5 에서는 71 을 잡아요. Dehn Twist 는 같은 곡선을 원환멎곌 붙읞 정사각형에 귞렀요. 두 번 ꌬ였닀 두 번 풀렀 같은 류 (1,0) 윌로 돌아와도 곡선은 ꎀ을 따띌 조ꞈ씩 밀렀나 있었얎요. 류가 같닀고 곡선읎 제자늬읞 것은 아니띌서, 펎진 ë’€ 0.4쎈 동안 곡선을 제자늬로 밀얎 두었얎요. Head Wave 의 띠는 64행에서 42도쯀 꺟여요. 뚞늬파 앞선은 겜계와 임계각윌로 만나고, 바로 아래 맚틀의 앞선은 겜계륌 슀치는 굎절파띌 거의 곧게 서요. Modular Hyperbola 는 묎아레가 될 쀄 알았던 판읎 맀끈했얎요. p = 127 읎멎 위 절반 얎디서나 톱니가 한 LED 에 반 바퀎륌 넘지 않아서, 쌍곡선 xy = c + 127k 서륞두 개가 접히지 않고 ë‹€ 볎였얎요. + 목록 밖에서 31판 뒀에 핮 볌 것읎에요. 페륎믞-파슀타-욞람-칭구의 되돌아옎(1955)은 KdV Solitons 와 같은 뿌늬띌 같은 판에 두지 않아요. 목록 밖에서 33판 뒀에 핮 볌 것읎에요. 토칎륎슀킀의 ë°©(1995)은 거욞로 된 닀각형 방에서 한 점의 빛읎 닀륞 한 점에만 닿지 못하는 방읎띌 Penrose Room 의 ꎑ선 추적을 귞대로 썚요. 프늬드뚌곌 슀윔띌(1987)는 둥귌 원 셋윌로는 볎로메였 고늬륌 만듀 수 없닀는 것을 볎였는데, 원을 밀얎 넣을 때 얎디서 부딪히는지가 귞늌읎 돌요. 정렬 넀튞워크의 시간 t 의 순엎을 점윌로 찍윌멎 대각선에서 원판을 지나 반대 대각선윌로 도는 점 구늄읎 돌요. ê·ž 원판읎 2007년 녌묞읎 낎닀볞 아륎킀메데슀 법칙읎에요. 간격읎 2k 읞 소수 쌍의 수는 Goldbach Comet 곌 같은 FFT 로 섞고, k 마닀 하디-늬틀우드 읞수로 갈늬는 쀄Ʞ 귞늌읎 돌요. @@ -5136,6 +5221,8 @@ Flock, Harmonograph, Big Type 은 처음 화멎읎 빚강읎에요. 목록 밖에서 79판 뒀에 핮 볌 것읎에요. 랚지의 원자 빔처럌 속도가 퍌젞 빈 시간 T 가 흩얎지멎 옆 묎늬는 지워지고 가욎데 묎늬만 낚아서, Ramsey Fringes 에 속도 폭 녾뾌 하나륌 더핮 섞워 볌 만핎요. 2찚원 귞로버 걞음은 녞늬였 읎누읎(Norio Inui)와 동료듀읎 2004년에 볎읞 대로 한 점에서 나선 확률의 음부가 출발점 곁에 끝까지 갇혀서, 퍌젞 나가는 고늬와 가욎데에 낹는 밝은 점을 64×128 판에 같읎 섞욞 수 있얎요. 버섯 당구의 버윔프 좌표(혞 위의 자늬와 듀얎였는 각의 사읞)륌 팹널 반쪜에 두멎, 공마닀 ꞈ빛 섬에 찍히는지 하늘색 바닀에 찍히는지 볎여요. 제임슀 팬턞늬옚(James Pantaleone)읎 2002년에 볎읞 대로 굎늌대 위 판에 얹은 메튞로놈듀은 같은 박윌로 잠겚서, Huygens' Clocks 의 엇박곌 짝읎 돌요. 옐로슀톀 순엎(OEIS A098550)은 a(n) 읎 a(n−2) 와는 앜수륌 나누고 a(n−1) 곌는 나누지 않는 가장 작은 수띌, 귞래프가 EKG 수엎의 섞 쀄곌 달늬 간헐천처럌 솟아요. +목록 밖에서 81판 뒀에 핮 볌 것읎에요. 니윜띌슀 칎잠(Nicholas Katz)가 1988년에 볎읞 대로, 소수 p 륌 정하고 a 륌 1 부터 p − 1 까지 돌며 큎룚슀테륎만 합 K(1, a; p)/2√p 는 반원 분포로 쌓여요. 한 행에 소수 하나씩 ê·ž 분포륌 막대로 낎렀 쓰멎 p 가 컀질수록 반원읎 서는 것읎 볎음 거예요. 잉에 레만(Inge Lehmann)은 1936년에 P파 귞늌자 띠 안윌로 희믞하게 듀얎였는 파륌 볎고 낎핵읎 있닀고 읜었얎요. 지구 반쪜 닚멎을 섞욎 팚널에 놓고 ìžµ 셋의 첫 도착을 같은 페륎마 원늬로 셈하멎, 귞늌자 띠와 ê·ž 안윌로 꺟여 드는 낎핵 파가 같읎 서요. 자였선 ꌬ임곌 겜도선 ꌬ임을 번갈아 걞멎 곡선의 류가 (1,0) 에서 (1,1), (2,1), (2,3), (5,3) 윌로 플볎나치 수륌 따띌 자띌요. 늬윔늬시(1962)의 말대로 원환멎에서는 읎 두 ꌬ임윌로 몚든 사상류륌 낌 수 있얎서, 닀음에 걞 ꌬ임을 녞람로 고륎는 판을 핮 볌 만핎요. 프레더멭 소디(Frederick Soddy)가 1937년 Nature 에 낾 육각 사슬(hexlet)은 서로 닿는 섞 구에 몚두 닿는 구 여섯읎 얎디서 시작핎도 고늬로 닫혀요. 입첎 사영윌로 섞워 돌늬멎 말파티 원 닀음 판읎 될 만핎요. 졎 폎띌드가 1978년에 낾 읎산 로귞의 캥거룚 방법(lambda method)은 두 마늬가 뛮 자국읎 한 번 겹치멎 ê·ž 뒀로 같은 Ꞟ을 가서 λ ꌎ읎 돌요. 섞로 팚널에 λ 륌 섞우멎 읎번 ρ 와 짝읎 돌요. + ### 타읎포 죌제 7 로 핮 볞 Ꞁ자 싀험곌 닀음에 핮 볌 것읎에요. 63판곌 64판읎 같읎 돌며 닀섯씩 처음 만듀었얎요. 63판의 닀섯은 한Ꞁ 자몚가 날아와 몚아쓰Ʞ로 잠Ʞ는 조늜(Jamo Assembly), 놀턎곌 하뚌의 Ʞ혞 몚자읎크(Type Mosaic), 혞프슀태터의 튞늜렛 귞늌자(Trip-let), 칎프늬칎의 계산 종읎(Kaprekar's Routine), 캐럎의 낱말 사닀늬(Word Ladder)예요. @@ -5268,6 +5355,14 @@ Flock, Harmonograph, Big Type 은 처음 화멎읎 빚강읎에요. 79판 뒀에 핮 볌 것읎에요. 발터 에렌슈타읞(Walter Ehrenstein)의 방사선 착시(1941)는 선읎 끊ꞎ 자늬에 밝은 원읎 떠서, Neon Spreading 의 격자륌 방사선윌로 바꟞멎 선 끝만윌로 서는 밝은 Ꞁ자와 색 번짐을 한 판에서 견쀄 수 있얎요. LZ78(1978)은 같은 Ꞁ을 읜윌며 사전 나묎륌 킀워서, 「잭읎 지은 집」의 나묎륌 섞욎 팚널에 귞늬멎 Lempel-Ziv 의 믞닫읎 찜곌 짝읎 돌요. 졎 넀읎플얎(John Napier)의 막대(1617)는 숫자마닀 ê·ž 배수륌 대각선윌로 나눈 칞에 적은 막대띌, 막대륌 늘얎놓고 대각선을 더핮 곱을 읜는 장멎읎 섞로 팚널에 맞아요. 큎로드 샀프(Claude Chappe)의 ꎑ학 전신(1794)은 탑 위 팔 셋의 각윌로 신혞륌 적얎서, Semaphore 의 자섞륌 탑에서 탑윌로 넘Ʞ멎 낱말읎 팚널을 따띌 올띌가요. +81판의 닀섯은 닀섯 구멍 천공 테읎프와 ê·ž 옆에 찍히는 Ꞁ자(Baudot Tape), 읎웃 두 낱말만 바꟞며 몚든 찚례륌 낎렀 쓰는 순엎 시(Permutation Poem), 속읎 바탕곌 같은 색읞데 테두늬 닚멎만윌로 밝아 볎읎는 Ꞁ자(Cornsweet Letters), 점토판에 쐐Ʞ로 눌러 쓎 60진 √2 와 ê·ž 평균 셈(YBC 7289), 액첎 획읎 방욞로 끊겚 점 Ꞁ자가 되는 낱말(Beading Letters)읎에요. + +81판에서 볞 것읎에요. 읎번에도 닀섯읎 ë‹€ Ꞁ자 ꌎ을 윔드에 적었고, 옮겚 쓎 Ꞁꌎ은 없얎요. 둘은 같은 Ʞ혞가 두 번 나올 때 생Ʞ는 음을 볎였얎요. Permutation Poem 의 I AM THAT I AM 은 Steinhaus-Johnson-Trotter 찚례로 120 번 바꟞는 가욎데 30 번읎 I 와 I, 또는 AM 곌 AM 을 바꿔서 새 쀄읎 윗쀄곌 똑같읎 읜혀요. ê·ž 믞끄럌 한가욎데에서 두 AM 은 한 자늬에 포개젞 흰 AM 하나로 읜혀요. Baudot Tape 에서는 숫자 1 부터 0 읎 Ꞁ자 Q 부터 P 까지와 같은 부혞띌서, 숫자 앞의 FIGS 하나륌 잃은 1234567890 읎 QWERTYUIOP 로 찍혀요. 둘은 Ꞁ자의 속공간읎 귞늌을 정했얎요. Cornsweet Letters 의 DIM 은 D 의 속공간읎 8행뿐읎띌 테두늬 닚멎읎 가장 깊은 쀄에도 17% 낚아서, 속읎 한 번도 바탕 높읎에 닿지 않아요. Beading Letters 의 E 는 가욎데 몚서늬에 획 셋읎 묌을 대서, ê·ž 방욞읎 획 가욎데 방욞볎닀 29% 컀요. YBC 7289 에서는 자늬가 맞는 것곌 값읎 가까욎 것읎 갈렞얎요. √2 에서 0.0025 얎Ꞌ난 1;25 와 0.086 얎Ꞌ난 1;30 읎 똑같읎 첫 자늬만 쌜젞요. + +Ʞ능 감은 둘읎 Ꞁ자륌 윔드와 Text 레읎얎 사읎에 죌고받는 Ꞟ을, 셋읎 Lab 의 녞람와 믞늬볎Ʞ륌 손볎는 Ꞟ을 바랐얎요. Baudot Tape 는 윔드 팚턎읎 귞늰 비튞맵 Ꞁ자 표륌 Text 레읎얎 Ꞁꌎ로 올늬Ʞ륌, YBC 7289 는 Text 레읎얎가 Ꞁ자륌 덮임 값곌 핚께 깊읎로도 낎죌Ʞ륌 바랐얎요. Permutation Poem 은 정수 칞을 고륎는 녾뾌 옆에 ê·ž 칞의 읎늄을 볎여 죌Ʞ륌 바랐얎요. Cornsweet Letters 는 밑 레읎얎 LED 의 색을 찍얎 였는 슀포읎튞륌, Beading Letters 는 믞늬볎Ʞ륌 멈추고 한 프레임씩 넘Ʞ는 닚추륌 바랐얎요. + +81판 뒀에 핮 볌 것읎에요. 레몜 크녞(Raymond Queneau)가 1961년에 낾 『시 백조 펾(Cent mille milliards de poÚmes)』처럌, 소넀튞 ì—Žë„€ 쀄마닀 ì—Ž 갈래 가욎데 하나륌 고륎는 판을 녞람로 넘겚 볌 만핎요. 갈래 Ꞁ은 직접 지얎 넣얎알 핎요. 에륞슀튞 마흐(Ernst Mach)가 1865년에 적은 마흐 띠는 밝Ʞ êž°ìšžêž°ê°€ 꺟읎는 자늬에 귞늬지 않은 밝은 쀄곌 얎두욎 쀄읎 볎읎는 착시예요. Ꞁ자 테두늬에 계닚 대신 Ʞ욞Ʞ의 꺟임만 두멎 귞늬지 않은 선윌로 Ꞁ자가 서요. Ꞟ버튞 버넘(Gilbert Vernam)읎 1917년에 만든 암혞 테읎프륌 핮 볌 만핎요. 평묞 테읎프와 엎쇠 테읎프의 구멍을 쀄마닀 XOR 핮 ì…‹ì§ž 테읎프에 찍윌멎 팚널에 테읎프 셋읎 나란히 서고, 같은 엎쇠륌 한 번 더 더하멎 Ꞁ읎 돌아와요. 였토 녞읎게바우얎(Otto Neugebauer)와 에읎람러햄 잭슀(Abraham Sachs)가 1945년에 풀얎 읜은 Plimpton 322 는 플타고띌슀 섞 수에서 나옚 값을 60진법윌로 적은 표예요. 쀄마닀 쐐Ʞ로 눌러 쓰고, 셈읎 맞는 칞을 쌜는 판읎 될 만핎요. 로버튞 디걎(Robert D. Deegan)곌 동료듀읎 1997년에 밝힌 컀플 얌룩 횚곌로 Ꞁ자륌 썚 볎멎, 묌읎 마륎며 획 가장자늬로 색읎 몰렀 테두늬만 진한 Ꞁ자가 낚아요. + ### 화장품 팝업 죌제 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 53c9424ed52a0a7fbebb9d11af8d1f7baf4e3a07 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 31 Oct 2026 21:29:55 +0000 Subject: [PATCH 8/9] =?UTF-8?q?pattern-study:=2081=ED=8C=90=20=EC=97=B4=20?= =?UTF-8?q?=EA=B0=9C=EB=A5=BC=20=EB=93=A0=20index.html=20=EC=9D=84=20?= =?UTF-8?q?=EB=8B=A4=EC=8B=9C=20=EC=A7=93=EB=8A=94=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01Bc4Jkpfqox342J4FE4ZD1C --- 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 185f3ca6..39ecccfd 100644 --- a/incoming/pattern-study/index.html +++ b/incoming/pattern-study/index.html @@ -210,8 +210,8 @@

팹턮 연구

- - + + - + +