import { useEffect, useState } from 'react';
import { ready, ink, fit } from '../../lib/mrly.js';
import { mount, Page, Row, Pick, Slider, Check, Btn, Stat, Note } from '../../lib/app.jsx';
import { Grid, Signs, Sketch } from '../../lib/draw.jsx';
import { Picker, useSeeds } from '../../lib/select.jsx';
import { useQuery, share } from '../../lib/query.js';
const m = await ready();
const ROUNDS = 8;
const SIDE = 512;
const LEVELS = 9;
const RATES = 64;
const FIRST = { round: 6, length: 256, level: 6, fold: 'sign', code: '9', side: 2, base: 2, depth: 3 };
const GALLERY =
'Three of the four lifts fold, and the crate checks each one against the Kronecker power of its own corner tile rather than trusting the picture. The corner tile is the only candidate worth testing: if a grid is a tile folded L times then its corner block is that tile with every bit flipped by (L - 1) t00, and folding the block L times flips the grid again by L (L - 1) t00, which is even. So a grid folds if and only if it folds from its own corner, and no search is needed.';
const FILTER =
'The filter has a closed form in both folds, and the closed form is not the Thue-Morse grid. Under the and fold the next level is this level blown up and masked by the base tile, so the difference is this level blown up and masked by the tile complement. Under the plus-minus fold the next level is this level blown up and exclusive-ored with the repeated tile, so the difference is the repeated tile alone. Either way the filter keeps the last digit and throws every other one, so its output repeats with period equal to the tile side while the Thue-Morse grid does not, and the two differ at every side past the tile. Sharper on the plus-minus fold at tile side two: the disagreement is exactly half the sites at every side four and beyond, for every one of the sixteen designs, because the low digits fix a residue class and inside each class the high digits carry opposite letters exactly half the time. Half is the score a coin gets, so the filter output is no evidence of Thue-Morse at all. The resemblance is real and it is generic rather than special: both pictures are digit rules, so both are Kronecker powers of one small tile, and the eye reads any plus-minus speckle at the finest scale as Thue-Morse. Proved here, and pinned in the crate at every code and level the page draws.';
const attempt = (fn) => {
try {
return { ...fn(), error: null };
} catch (error) {
return { error };
}
};
const hue = (bit) => (bit ? ink.blue : ink.orange);
const ribbon = (rows, length) => (canvas) => {
const tall = rows.length * 26 + 4;
const [ctx, w] = fit(canvas, tall);
ctx.clearRect(0, 0, w, tall);
const step = (w - 16) / length;
rows.forEach((row, r) => {
for (let i = 0; i < row.length; i++) {
ctx.fillStyle = row[i];
ctx.fillRect(8 + i * step, 2 + r * 26, Math.max(1, step - 1), 22);
}
});
};
function spread(runs) {
const out = [];
for (const run of runs) for (let k = 0; k < run; k++) out.push(run);
return out;
}
function tile(bits) {
return { width: 2, height: 2, types: Uint8Array.from(bits) };
}
function verdict(row) {
if (row.twin) return the same grid as {row.twin};
if (row.folds) return a Kronecker power;
return no Kronecker power;
}
function App() {
const shared = useSeeds();
const [pick, set] = useQuery(FIRST);
const [playing, setPlaying] = useState(false);
const [parity, setParity] = useState(false);
const clamp = (value, low, high) => Math.max(low, Math.min(value, high));
const round = clamp(pick.round, 1, ROUNDS);
const level = clamp(pick.level, 1, LEVELS);
const length = clamp(pick.length, 16, 4096);
const cap = m.level_cap(pick.side, 1, SIDE);
const depth = clamp(pick.depth, 1, Math.max(1, cap - 1));
useEffect(() => {
if (!playing) return;
const tick = setInterval(() => set({ round: round >= ROUNDS ? 1 : round + 1 }), 700);
return () => clearInterval(tick);
}, [playing, round]);
const word = attempt(() => ({ read: JSON.parse(m.morse_word(length)) }));
const seed = attempt(() => {
const stage = Array.from(m.morse_stage(round));
const digits = JSON.parse(m.morse_word(stage.length)).digits;
return { stage, digits, agree: stage.every((bit, i) => bit === digits[i]) };
});
const gallery = attempt(() => ({
rows: JSON.parse(m.morse_gallery(level)),
grids: ['parity', 'and', 'xor', 'sum'].map((kind) => m.morse_lift(kind, level)),
}));
const filter = attempt(() => ({
read: JSON.parse(m.morse_filter(pick.code, pick.side, pick.base, depth, pick.fold)),
coarse: pick.fold === 'sign' ? m.morse_signs(pick.code, pick.side, pick.base, depth) : m.two_grid(pick.code, pick.side, depth, 0, pick.base),
fine: pick.fold === 'sign' ? m.morse_signs(pick.code, pick.side, pick.base, depth + 1) : m.two_grid(pick.code, pick.side, depth + 1, 0, pick.base),
difference: m.morse_difference(pick.code, pick.side, pick.base, depth, pick.fold),
}));
const rates = attempt(() => ({ read: JSON.parse(m.magic_rates(['3', '7'], [2, 2], [2, 2], 'thue-morse', RATES)) }));
const wordLink = share({
l0code: '3', l0base: 2, l0n: 2, l1code: '7', l1base: 2, l1n: 2,
view: 'nest', compare: 'swap', chart: 'exponent', schedule: 'thue-morse', length: RATES,
});
const letters = () => {
const { stage, digits, agree } = seed;
const top = parity ? digits : stage;
const low = parity ? stage : digits;
return (
<>
The digit rule is `t(n)`, the parity of the count of one-bits of `n`. The substitution grows the same word from a single 0 by doubling: every 0 becomes 01 and every 1 becomes 10. Digits are the recursion, so the two constructions are one construction seen twice, and the chip above is a live comparison rather than a claim.
{GALLERY}
The first lift is the sign grid `(-1)^(popcount(i) + popcount(j))`, the Kronecker power of the two-by-two tile with plus one on its diagonal, and that tile is a mrly design read plus-minus. The second is the Walsh-Hadamard pattern, the gasket read the same way, which is the spectrometer's world. The third is not a third grid at all: `popcount(i xor j)` and `popcount(i) + popcount(j)` agree modulo two, so `t` carries exclusive or to exclusive or and the third lift is the first. The fourth carries, and carrying is not a digit rule, so it does not fold; its grid is constant along every antidiagonal instead, which is a Hankel pattern and never a Kronecker power past side two.
Because `t(2n) = t(n)` and `t(2n+1) = 1 - t(n)`, the word changes at every even place, so no run reaches three: `000` and `111` never appear. The word that marks where the runs break is the period-doubling word, and the page checks that identity term by term at every length it draws. Verified in the crates, and stated in research/connectivity.md at every length to `2^20`.
Read the letters as designs rather than as bits and the word becomes a schedule: one design per level, the domino where the letter is 0 and the gasket where it is 1. That is exactly what the words draws, with the component exponent charted along the schedule and its periodic control beside it.
Along Thue-Morse over a gasket-and-domino pair the component exponent is exactly `(1/2) log 6`, with a two-sided certificate rather than a fit: the word has no three equal letters in a row, which caps the sandwich suffix, and it is balanced, which pins the letter counts to within one half of `L/2`. The aperiodicity earns nothing extra here, because the exponent depends on the letter frequencies alone, so a periodic word of the same frequencies returns the same number. Proved, research/connectivity.md.
{FILTER}