index.jsx

7.7 kB · jsx · 130 lines

1import { useMemo } from 'react';2import { ready, ink, role } from '../../lib/mrly.js';3import { mount, Page, Row, Pick, Check, Btn, Stats, Stat, Note, Group } from '../../lib/app.jsx';4import { Markup, Sketch } from '../../lib/draw.jsx';5import { useQuery } from '../../lib/query.js';6import { useSeeds, seeded, Picker } from '../../lib/select.jsx';7import { board, bars, line, axis, tag } from '../../lib/chart.js';89const m = await ready();10const TOP = 16;11const CORNERS = [0, 1, 2, 3, 4, 5, 6, 7, 8];12const EIGHTHS = [[-4, '-1/2'], [-3, '-3/8'], [-2, '-1/4'], [-1, '-1/8'], [0, '0'], [1, '1/8'], [2, '1/4'], [3, '3/8'], [4, '1/2']];13const RULER = Array.from({ length: 9 }, (_, step) => step / 8);14const LEVELS = ['level 0', 'level 1', 'level 2', 'level 3'];1516function App() {17  const s = useSeeds();18  const [pick, set] = useQuery({ code: seeded(s, 3, 2, '23'), k: 8, mystery: false, mseed: 1, corners: 4, top: 0, shown: false });1920  const k = Math.min(TOP, Math.max(1, pick.k));21  const sealed = pick.mystery && !pick.shown;22  const code = pick.mystery ? m.random_code(3, 2, pick.mseed) : pick.code.trim();2324  const built = useMemo(() => {25    try {26      return {27        read: JSON.parse(m.walsh_spectrum(code, TOP)),28        series: JSON.parse(m.slice_series(code, TOP)),29        name: m.name_of(code, 3, 2),30        error: null,31      };32    } catch (error) {33      return { read: null, series: null, name: '', error };34    }35  }, [code]);3637  const read = built.read;38  const row = read?.law[k - 1];39  const counted = built.series?.[k - 1].fills;40  const art = useMemo(() => (row ? m.hex_svg(code, row.n, 1, 2, 'cut', Math.max(1, Math.round(320 / row.n))) : ''), [code, row]);4142  const law = (canvas) => {43    if (!read) return;44    const b = board(canvas, 260);45    const rows = read.law, count = rows.length;46    const at = (i) => (i + 0.5) / count;47    for (const step of RULER) line(b, [[0, step], [1, step]], ink.line, { width: 1 });48    if (!sealed) line(b, [[0, read.background], [1, read.background]], ink.dim, { width: 1, dash: [5, 5] });49    line(b, built.series.map((each, i) => [at(i), each.fills / rows[i].triangles]), ink.yellow, { width: 1, dash: [1, 5], dots: 3.5 });50    line(b, rows.map((each, i) => [at(i), each.ink]), ink.blue, { width: 1.6, dots: 2 });51    axis(b, rows.map((each, i) => [at(i), each.n]));52    const next = tag(b, 'closed form read off the spectrum', ink.blue);53    tag(b, 'ink counted on the mesh', ink.yellow, 'left', next + 16);54  };5556  const spectrum = (canvas) => {57    if (!read) return;58    const b = board(canvas, 200, { bottom: 96 });59    const values = read.levels.map((level) => level.sigma);60    bars(b, values, { peak: Math.max(...values.map(Math.abs), 1e-12), color: (i) => role()[i], inset: 14 });61    axis(b, values.map((value, i) => [(i + 0.5) / values.length, LEVELS[i]]), { wall: true });62  };6364  const controls = (65    <>66      <Group name="Design">67        <span className="set" hidden={pick.mystery}>68          <Picker dimension={3} code={pick.code} seeds={s} onChange={set} />69        </span>70        <label>side <input type="range" min={1} max={TOP} value={k} onChange={(e) => set({ k: +e.target.value })} /><span className="num">{`k ${k}, n ${row?.n ?? ''}`}</span></label>71      </Group>72      <Group name="Mystery">73        <Check label="mystery" checked={pick.mystery} onChange={(value) => set({ mystery: value, shown: false })} />74        <span className="set" hidden={!pick.mystery}>75          <Pick label="filled corners" value={pick.corners} options={CORNERS} onChange={(value) => set({ corners: +value })} />76          <Pick label="top level" value={pick.top} options={EIGHTHS} onChange={(value) => set({ top: +value })} />77          <Btn primary={!pick.shown} onClick={() => set({ shown: !pick.shown })}>{pick.shown ? 'Hide again' : 'Reveal'}</Btn>78          <Btn onClick={() => set({ mseed: pick.mseed + 1, shown: false })}>New mystery</Btn>79        </span>80      </Group>81    </>82  );8384  return (85    <Page crumb="spectrometer" title="Point the slice at a sponge and it reads the recipe back" controls={controls}86      sub={<>A cube design is eight yes-or-no answers about corner parities. Cut its cube down the main diagonal and the hexagon comes back part inked, part blank, and that one fraction is an exact closed form in the design's Walsh spectrum, level by level: a steady background, a two-step blink, and two corrections that die as <code>1/n</code> and <code>1/n^2</code>. No fit, no error term. Turn the mystery on and the code is hidden - read the recipe off the curve, then reveal it.</>}87      foot={<>The spectrum is the crate's Walsh-Hadamard transform of the design's eight corners, the four bars are its level sums <code>Sigma_0</code> to <code>Sigma_3</code>, and the blue curve is the ink law evaluated over the integers in Rust and handed here as an exact numerator over <code>96n^2</code>. The yellow dots are the fills the crate counts triangle by triangle on the real mesh, over the <code>6n^2</code> triangles of the hexagon. The two never part: the law predicts the count itself, not an approximation to it. The same hexagon, its mesh census, its pieces and its holes are <a href="../slices">the slices</a> page; the research note this grew from is <a href="/research/slices/">slices</a>, and the theorem, its proof and its checks are the shelf paper <a href="/papers/walsh-spectrometer/">the Walsh spectrometer</a>.</>}>88      <div className="arena">89        <div className="panel">90          <h2>The slice <span>{`side ${row?.n ?? ''}, ${row?.triangles ?? ''} triangles`}</span></h2>91          <Markup svg={art} role="img" aria-label="The slice" />92        </div>93        <div className="panel">94          <h2>The spectrum <span>{sealed ? 'sealed' : 'level sums'}</span></h2>95          <Sketch draw={spectrum} deps={[read, sealed]} className="bars" role="img" aria-label="The spectrum" hidden={sealed} />96          <p className="sub" hidden={!sealed}>The readout is sealed, and the curve below still carries it. The level the blink straddles is <code>Sigma_0</code>, one eighth for every filled corner; the gap between the two combs is <code>Sigma_3</code>, the top level, positive when the higher comb sits at the sides 1, 5, 9.</p>97          {!sealed && <Stats>{read?.coefficients.map((part) => <Stat key={part.mask} label={`F ${part.mask.toString(2).padStart(3, '0')}`}>{part.value.toFixed(3)}</Stat>)}</Stats>}98        </div>99      </div>100      <Sketch draw={law} deps={[read, sealed]} className="bars" role="img" aria-label="The ink law against the counted ink" />101      <Stats>102        <Stat label="name">{sealed ? 'sealed' : built.name}</Stat>103        <Stat label="sign s">{row?.s}</Stat>104        <Stat label="closed form">{row?.fills}</Stat>105        <Stat label="counted">{counted}</Stat>106        <Stat label="verdict">{row ? (row.fills === counted ? 'exact' : 'broken') : ''}</Stat>107        <Stat label="ink">{row?.ink.toFixed(6)}</Stat>108        <Stat label="exact ink">{row ? `${row.numerator}/${row.denominator}` : ''}</Stat>109      </Stats>110      {!sealed && read && (111        <Stats>112          {read.levels.map((level) => <Stat key={level.level} label={`Sigma ${level.level}`}>{`${level.eighths}/8`}</Stat>)}113          <Stat label="background">{read.background.toFixed(4)}</Stat>114          <Stat label="blink">{read.blink.toFixed(4)}</Stat>115        </Stats>116      )}117      {pick.mystery && !sealed && read && (118        <Stats>119          <Stat label="corners">{`${read.corners}, you said ${pick.corners}`}</Stat>120          <Stat label="corners read">{pick.corners === read.corners ? 'right' : 'missed'}</Stat>121          <Stat label="top level">{`${read.levels[3].eighths}/8, you said ${pick.top}/8`}</Stat>122          <Stat label="top level read">{pick.top === read.levels[3].eighths ? 'right' : 'missed'}</Stat>123        </Stats>124      )}125      <Note error={built.error} />126    </Page>127  );128}129130mount(<App />);