import { useMemo } from 'react'; import { ready, ink } from '../../lib/mrly.js'; import { mount, Page, Group, Pick, Slider, Stats, Stat, Note } from '../../lib/app.jsx'; import { Grid, Pixels } from '../../lib/draw.jsx'; import { useQuery } from '../../lib/query.js'; import { useSeeds, seeded, Picker, Ramp } from '../../lib/select.jsx'; const m = await ready(); const SIDES = [2, 3, 5]; const DEEPEST = 5; const BUDGET = 65536; const cap = (side) => Math.min(DEEPEST, m.level_cap(side, 2, BUDGET)); const wrap = (value, span) => ((value % span) + span) % span; function App() { const s = useSeeds(); const [pick, set] = useQuery({ code: seeded(s, 2, 3, '495'), side: 3, base: 3, level: 4, t1: 0, t2: 0, bar: 10, ramp: 'heat', levels: 32, invert: false, }); const code = pick.code.trim(); const level = Math.max(1, Math.min(pick.level, cap(pick.side))); const span = pick.side ** level; const t1 = wrap(pick.t1, span); const t2 = wrap(pick.t2, span); const built = useMemo(() => { try { return { grid: m.two_grid(code, pick.side, level, 0, pick.base), field: m.modes_field(code, pick.side, level, pick.base), digits: m.modes_digits(code, pick.side, pick.base), name: m.name_of(code, 2, pick.base), error: null, }; } catch (error) { return { grid: null, field: null, digits: 0, name: '', error }; } }, [code, pick.side, level, pick.base]); const read = useMemo(() => built.field && Float32Array.from(built.field, (v) => v ** (1 / level)), [built.field, level]); const heat = useMemo(() => read && m.paint_span(read, span, 0, 1, pick.ramp, pick.levels, pick.invert), [read, pick.ramp, pick.levels, pick.invert]); const wave = useMemo(() => { if (built.error) return null; return m.paint_span(m.modes_pattern(code, pick.side, level, pick.base, t1, t2), span, -1, 1, 'diverge', pick.levels, false); }, [built.field, t1, t2, pick.levels]); const value = useMemo(() => (built.error ? null : m.modes_value(code, pick.side, level, pick.base, t1, t2)), [built.field, t1, t2]); const large = useMemo(() => (built.error ? 0 : m.modes_large(code, pick.side, level, pick.base, pick.bar / 100)), [built.field, pick.bar]); const mass = built.digits ** level; const seek = (event) => { const box = event.currentTarget.getBoundingClientRect(); const col = Math.floor(((event.clientX - box.left) / box.width) * span); const row = Math.floor(((event.clientY - box.top) / box.height) * span); set({ t1: Math.max(0, Math.min(span - 1, row)), t2: Math.max(0, Math.min(span - 1, col)) }); }; const controls = ( <> [v, v])} onChange={(v) => set({ side: +v, level: Math.min(level, cap(+v)) })} /> set({ level: v, t1: wrap(t1, pick.side ** v), t2: wrap(t2, pick.side ** v) })} /> set({ t1: v })} /> set({ t2: v })} /> set({ bar: v })} /> ); return ( Lay a design's stencil at the level over every point of a side^level by side^level torus and add up what it covers. That operator has one family of modes, the waves e(<t, x> / side^level), and each one is stretched by a single number. Pick a frequency and watch its wave; the middle panel is the whole field of those numbers at once.} controls={controls} foot={<>The design is the picker's plane code at its side and its residue base; the filled cells of its level-one tile are the digit set F, and the stencil at the level is every sum of level of them scaled by the powers of the side. The eigenvalue is a product over the digits, so the field |lambda| is level rescaled copies of one small transform multiplied together, which is why it repeats at every scale like the design itself. The middle panel reads the field at its per-level root, |lambda|^(1/level) / fill, the average size of one factor; the printed numbers are the raw ones. The same design stacked over its own scales is moire, turned on itself radial, and joined into a network whose Laplacian has its own spectrum on spectra; the same stencil run as a neighbourhood is mrlylife. Every eigenvalue, every count and every wave comes out of the crates through wasm; the page only draws.}>

Proved On the torus (Z/side^level)^2 the mask operator (A x)(u) = sum over s in S_level of x(u + s) holds every character e(<t, x> / side^level) fixed in direction, with eigenvalue lambda(t) = prod over j < level of hat F(side^j t / side^level) where hat F(y) = sum over v in F of e(<v, y>), so lambda(0) = fill^level.

The mask {`level ${level}, ${mass} cells of ${span * span}`}

{built.grid && }

The eigenvalue field {`|lambda|^(1/${level}) / fill, click to pick t`}

{heat && }

The mode {`t = (${t1}, ${t2})`}

{wave && }
{built.name} {pick.side} {pick.base} {built.digits} {mass} {`${span} by ${span}`} {value ? value[0].toFixed(6) : ''} {value ? value[1].toFixed(6) : ''} {value ? value[2].toFixed(6) : ''} {`${large} at ${pick.bar}% of fill^level`} {`${((large / (span * span)) * 100).toFixed(3)}%`}
); } mount();