import { useMemo, useState } from 'react'; import { ready, ink } from '../../lib/mrly.js'; import { mount, Page, Row, Pick, Slider, Stats, Stat, Note } from '../../lib/app.jsx'; import { Pixels, Signs, Sketch } from '../../lib/draw.jsx'; import { board, line, axis, tag } from '../../lib/chart.js'; import { useQuery } from '../../lib/query.js'; import { Ramp } from '../../lib/select.jsx'; const m = await ready(); const CARPET = '23'; const SIZES = [128, 192, 256, 320, 384]; const hues = () => ({ plus: ink.yellow, minus: ink.blue, empty: ink.deep }); const fixed = (value, places) => (Number.isFinite(value) ? value.toFixed(places) : 'none'); function Arm({ read }) { const rows = read.rows; const draw = (canvas) => { const b = board(canvas, 240); const n = rows.length; const at = (i) => (i + 0.5) / n; const step = Math.max(1, Math.ceil(n / 12)); axis(b, rows.map((row, i) => [at(i), String(row.n)]).filter((_, i) => i % step === 0), { wall: true }); b.ctx.strokeStyle = ink.line; b.ctx.lineWidth = 1; b.ctx.beginPath(); b.ctx.moveTo(b.x(0), b.y(0.5)); b.ctx.lineTo(b.x(1), b.y(0.5)); b.ctx.stroke(); line(b, rows.map((row, i) => [at(i), row.law]), ink.blue, { width: 1.5 }); line(b, rows.map((row, i) => [at(i), row.ink]), ink.yellow, { width: 0, dots: 2.6 }); tag(b, 'the counted ink of the band, one dot a layer', ink.yellow); tag(b, '1/2 + chi_8(n)/(2n)', ink.blue, 'right'); }; return ; } function Fall({ read }) { const draw = (canvas) => { const b = board(canvas, 240, { left: 46 }); const walk = read.walk; if (walk.length < 2) { tag(b, 'two rungs are needed before the decay reads', ink.dim); return; } const model = walk.map(([count]) => [count, -Math.log(count) / 4 + read.constant]); const lows = walk.map(([, value]) => value).concat(model.map(([, value]) => value)); const low = Math.min(...lows), high = Math.max(...lows); const span = Math.max(high - low, 1e-9); const first = Math.log(walk[0][0]), last = Math.log(walk[walk.length - 1][0]); const reach = Math.max(last - first, 1e-9); const place = ([count, value]) => [(Math.log(count) - first) / reach, (value - low) / span]; line(b, model.map(place), ink.blue, { width: 1.5, dash: [4, 4] }); line(b, walk.map(place), ink.yellow, { width: 1.8 }); axis(b, [[0, String(walk[0][0])], [0.5, 'L'], [1, String(walk[walk.length - 1][0])]], { wall: true }); b.ctx.fillStyle = ink.dim; b.ctx.textAlign = 'right'; b.ctx.fillText(fixed(high, 3), b.left - 6, b.y(1) + 4); b.ctx.fillText(fixed(low, 3), b.left - 6, b.y(0) + 4); b.ctx.textAlign = 'left'; tag(b, 'L times the excess, read against ln L', ink.yellow); tag(b, read.slope === null ? 'the L/2 window mixes the parities here' : `slope ${read.slope.toFixed(6)} against ${read.target.toFixed(6)}`, ink.blue, 'right'); }; return ; } function App() { const [pick, set] = useQuery({ layers: 28, half: 0, size: 256 }); const [look, setLook] = useState({ ramp: 'fire', levels: 24, invert: false }); let error = null; let view = null; let arm = null; let fall = null; try { const field = m.star_field(CARPET, pick.layers, pick.size); let low = Infinity, high = -Infinity; for (const value of field) if (!Number.isNaN(value)) { low = Math.min(low, value); high = Math.max(high, value); } view = { pixels: m.paint_span(field, pick.size, low, high, look.ramp, look.levels, look.invert), band: m.star_band(CARPET, pick.layers, pick.size, pick.half), low, high, }; arm = JSON.parse(m.star_layers(CARPET, pick.layers, pick.half)); fall = JSON.parse(m.star_decay(CARPET, pick.layers, pick.half)); } catch (fault) { error = fault; } const ribbon = useMemo(() => (arm ? arm.rows.slice(0, 12) : []), [arm]); const deepest = arm ? arm.rows[arm.rows.length - 1] : null; const controls = ( <>

The stack

set({ layers: v })} /> [v, v])} onChange={(v) => set({ size: +v })} />

The band

set({ half: v })} />

Colour

setLook({ ...look, ...patch })} />
); return ( The picture resamples every layer onto one square, which is the ideal frame; the numbers below never resample, reading each layer on the cube's own cells, which is the cell frame. That difference is the whole point: the same star decays at -0.1807 in the ideal frame, -0.1252 in the lattice frame and exactly -1/4 on the arm in the cell frame, and widening the band walks the coefficient again. Only the order (ln L)/L, the sign, and the cell frame's -1/4 survive the change of frame, and only -1/4 has a closed form beside it. At odd L the constant shifts by 1/8, so the slider steps by two and stays even. That same character term traps the sliding window: it cancels between L/2 and L only when L is divisible by four, so at L = 2 mod 4 the slope is refused rather than reported wrong, and what you read instead is the other branch of the 1/L^2 term. The stack the layers come from is moire and volume, the mesh they are counted on is slices, and the ink law the background uses is read back from the corners in spectrometer. The width readout beside the slope is the closed form -(K + b)/(4(2K + 1)) at K = floor(W/2), so an odd width is never a new band: x - y is even on the cut, and W and W - 1 read one point set and one coefficient. The mathematics, its proofs and the frames it was measured in are on the hexagon page. Every number here is computed in Rust; the page only draws.}>

The stack {pick.layers} cut layers on one square, the mean ink at every sample

{view && } {pick.layers} {fall?.deepest} {view && `${fixed(view.low, 4)} to ${fixed(view.high, 4)}`}

The band yellow where the star is read, blue the background it is read against

{view && } {`${pick.half} cells`} {fall?.arm ? 'the exact diameter x = y' : 'a band about it'}

The arm the band's ink at every odd layer against the closed form 1/2 + chi_8(n)/(2n)

{arm && }
{ribbon.map((row) => ( n {row.n} {`${row.numer}/${row.denom}`} {`law ${row.lawNumer}/${row.lawDenom}`} ))}
{arm && `${arm.exact} of ${arm.layers} layers`} {deepest && `${deepest.chi > 0 ? '+' : ''}${deepest.chi}`} {deepest && fixed(deepest.ink, 6)} {deepest && fixed(deepest.hex, 6)} {deepest && fixed(deepest.excess, 6)}

The decay L times the mean excess, which is -(ln L)/4 + C on the arm in the cell frame

{fall && } {fall && fixed(fall.scaled, 6)} {fall && fixed(fall.logged, 10)} {fall && fixed(fall.constant, 10)} {fall && (fall.slope === null ? 'needs L divisible by four' : fixed(fall.slope, 6))} {fall && fixed(fall.target, 6)} {fall?.branch} {fall && fixed(fall.residual, 8)} {fall && (fall.predicted === null ? 'odd L has none' : fixed(fall.predicted, 8))}
{fall && fall.rows.map((row) => ( L {row.layers} {fixed(row.scaled, 6)} {fixed(row.logged, 8)} ))}
); } mount();