
Evidence
Every number below came out of a script in this directory. Two harnesses: probe-stats.mjs (node, no browser — MN_derive is pure, so a year can be derived without a page) and shoot.mjs / measure-render.mjs (Playwright / Chromium, --use-gl=swiftshader, over the real artifact inlined by build-harness.mjs). Re-run in that order.
node artifact/strata2/almanac/probe-stats.mjs 2026
node artifact/strata2/almanac/build-harness.mjs
node artifact/strata2/almanac/shoot.mjs proto
node artifact/strata2/almanac/measure-render.mjs
node artifact/strata2/almanac/shoot.mjs harness
node artifact/strata2/almanac/probe-a11y.mjs
node artifact/strata2/almanac/probe-a11y2.mjs
This file was rewritten after an adversarial review. Every number below is from a run of the code as it stands, not carried over. What the review found and what it cost is in After the review at the end.
The shared mechanism, used as found
window.MN_derive(date) derives every day. window.MN_phaseDisc(cv, q, 12) draws every cell. window.MN_EPHEMERIS and the mn:ephemeris event move the cursor. Nothing here redefines any of the three. The one thing this stratum carries of its own is the field's noise, copied verbatim from strata2/mark-weather (which copied it from field.glsl): same constants, its own integer hash — the same class of climate at the same domain offset, not the shader's pixels.
Cost — measured, not estimated
365 days x 288 samples (24 x 12 over the patch) = 105,120 evaluations
node, cold: 169 ms total 0.463 ms/day
Chromium, at mount: 142-161 ms (5 runs: 146 / 142 / 142 / 142 / 161)
Chromium, rebuild: 123.4 ms (same year, second mount)
Each evaluation is three climateF taps (f, f+eps, f−eps) plus one shared warp, i.e. 5 fbm4 = 20 value-noise lookups. It happens once, at mount, and again only when the ephemeris crosses a year boundary.
frame interval on the page: median 16.70 ms · p95 16.80 ms (80 frames)
Vsync-capped, so this is a lower bound on headroom, not a measurement of work — and it is a measurement of the page around this plate, not of this plate, which schedules no frames at all. The grid is painted once into an offscreen canvas and blitted only when the cursor moves.
The three statistics (2026, UTC)
probe-stats.json — the statistic itself, over 365 days:
| min | max | sd | acf lag-1 | acf lag-29 | acf lag-30 | r with illum | |
|---|---|---|---|---|---|---|---|
| 1 · illumination | 0.0000 | 1.0000 | 0.3538 | 0.9752 | 0.9145 | 0.9135 | 1 |
| 2 · field mean density | 0.1692 | 0.5690 | 0.0748 | −0.0087 | −0.0100 | −0.0087 | 0.0098 |
| 3 · warm − cold balance | 0.3277 | 0.6584 | 0.0988 | 0.9619 | 0.9067 | 0.9052 | 0.9905 |
Do lunations appear as bands in (3)? Yes. Autocorrelation at lag 29 is 0.9067 and at lag 30 0.9052, against 0.9145 / 0.9135 for the moon itself. The band is 99.1% of the control's at lag 29.
The reason is not subtle and is worth stating plainly: |df| in this value-noise almost never exceeds the p85 threshold the shader uses, so nearly every cell resolves through the agreement path, and the agreement path is seasoned by illumination. The balance is therefore illumination plus terrain, not an independent signal. Regressing illumination out:
slope on illum 0.2766 (the field's own cap is TIDE = 0.42)
residual sd 0.01361
residual share 13.78% of the balance's sd
So 13.8% of what the calendar draws is the page's climate and 86.2% is the moon. That is the honest split, and it is why (3) is kept over (1): it is the same lunation with the terrain written into it, not a different signal.
Legibility measured on what is drawn
measure-render.json — every day's quantity through the real MN_phaseDisc at N = 12, lit cells counted:
| quantity range | lit cells | sd | distinct states | acf 29 | column contrast | |
|---|---|---|---|---|---|---|
| 1 · illumination | 0.0000–1.0000 | 0–65 | 25.47 | 51 | 0.9126 | 21.34 |
| 2 · mean density | 0.1692–0.5690 | 5–35 | 6.14 | 28 | 0.0126 | 1.66 |
| 3 · balance | 0.0898–0.8772 | 1–61 | 19.24 | 52 | 0.9044 | 15.99 |
Column contrast = mean |column mean − grand mean| of lit cells over the 53 week columns; it is the number that says whether the vertical banding is visible.
Kept: 3. Statistic 2 is a calendar of static — 13× less column contrast than the control, lag-29 autocorrelation 0.013, and only 28 distinguishable disc states. Statistic 1 is the control and prints what the ephemeris plate above it already prints. Statistic 3 holds 75% of the control's column contrast, 99% of its lunation, paints one more distinguishable state (52 vs 51) from a narrower quantity range, and is the only one whose picture is of this page. shot-proto-1440.png is all three drawn together.
No grey, and no second cyan — at BOTH rasters
report-harness.json. The tally is one function, TALLY, run in every shot context, so the claim is proved of each raster rather than proved of one and asserted of the other. That was the review's finding: the old audit ran only at dpr 1.
dpr 1 bitmap 848 x 112 lit 11,462 non-canon 0 essence 0
226,228,230,255 x 11,430 (witness — the discs)
194,51,61,255 x 32 (#c2333d — the cursor ring, exact, opaque)
dpr 2 bitmap 1696 x 224 lit 45,845 non-canon 0 essence 0
226,228,230,255 x 45,717
194,51,61,255 x 128 (32 cells x 4 device pixels)
2040 bitmap 864 x 112 lit 11,336 non-canon 0 essence 0
226,228,230,255 x 11,304
194,51,61,255 x 32
Two colours in the bitmap, at any raster, at any year. The dpr-2 raster previously carried three pixels of 83,236,246,255 — essence at globalAlpha composited over an opaque witness pixel, a mid-tone made by an alpha blend. It is gone twice over: the ring is opaque sigil at globalAlpha = 1, and each ring cell is clearRect before fillRect, so a ring pixel replaces what is under it rather than compositing over it.
Zero essence pixels in every context. The plate spends none of the page's cyan budget. The cursor ring is sigil whether the date is today or one the reader scrubbed to; today is carried by the [today] bracket in the readout and by the canvas's accessible name.
after scrubbing to 2026-03-09:
226,228,230,255 x 11,429
194,51,61,255 x 32 (unchanged — the ring does not change colour)
Found on the way, kept for the record. The first ring build measured two essence tones — 24 cells at alpha 170 and 8 at alpha 227. Bresenham emits each octant endpoint twice, and a cell composited twice at the same alpha comes out brighter than its neighbours: a density lie by a second route. The offsets are deduped into a fixed 32-cell list before anything is drawn. The dpr-2 blend the review found was the same defect surviving in the one raster the audit never read.
The cursor, and a plate that does not move
mn:ephemeris {date: 2026-03-09} -> readout data-iso 2026-09-03 -> 2026-03-09
today (UTC) 2026-09-03
ring colour, both states 194,51,61,255 x 32 (identical)
Nothing on this plate animates. There is no requestAnimationFrame loop: the grid is blitted and the ring drawn when the cursor moves, and not otherwise. So the test is not that the pulse is correct but that there is no pulse — two full-canvas snapshots one second apart:
frame A FNV hash 3892490629 essence pixels 0
frame B FNV hash 3892490629 essence pixels 0 identical: true
rAF calls per 600 ms: page baseline 144 · with 10 almanacs mounted 152
after destroying all 10: 144
Ten instances add eight rAF calls across 600ms — noise from the strata around it, not from this one. Under prefers-reduced-motion the picture is byte-identical to the default picture (probe-a11y.json.reducedMotion: hashes f3feb16332b217c7 both, 11,462 lit) because it is the same code path, not a held t = 3.0. §06's one static correct non-blank frame is satisfied by construction.
The year decides the columns
53 was a constant, and it was wrong. A year needs ceil((dowOffset + days) / 7) columns; a leap year whose 1 January is a Sunday gives dowOffset = 6 and 6 + 366 = 372 cells = 54 columns. At 53, 31 December was derived, counted in the readout and in the aria-label, and never drawn.
report-harness.json.yearCapacity — every year 1980..2060 built and its last day's column checked against the grid it was given:
years needing 54 columns 1984, 2012, 2040
years with a day off the grid none
2040-12-31 (report-harness.json.leapSunday)
cols 54 · days 366 · canvas 864 css / 864 bitmap
sigil ring pixels 32 (the day is drawn AND ringed)
readout 2040-12-31 balance 0.3576 disc 0.161
aria-label almanac: 366 days of 2040 as 54 weekly columns, ...
cost 366 days x 288 samples
shot-harness-2040.png is the fixed regression shot: the cell sits in the last column, top row, with the ring on it. The cost readout is written by build() now, so a 366-day year says 366 rather than repeating the 365 the first year happened to have.
Viewport and raster
1440 x 900 dpr 1 canvas 848 x 112 css / 848 x 112 bitmap h-scroll false
wrap client 1046 · scroll 1046 hint hidden
390 x 844 dpr 1 canvas 848 x 112 / 848 x 112 h-scroll false
wrap client 288 · scroll 878 hint shown
1440 x 900 dpr 2 canvas 848 x 112 css / 1696 x 224 bitmap h-scroll false
lit pixels 45,845 = 4 x 11,462 — an integer upscale, not a blur
1440 x 900 2040 canvas 864 x 112 css / 864 x 112 h-scroll false
Pitch is 16 CSS px, disc 12 cells, scaled by an integer dpr only (1 or 2); drawImage runs with imageSmoothingEnabled = false.
The scroll hint, gated on measured overflow
The hint used to be gated on wrap.clientWidth >= 848, and the scroller's inline width was set to 848 — both forgetting the 34px sticky day column that is part of the scroller's content. Between those two numbers sat a dead zone where the wrap really overflowed, the last days of December were clipped, and nothing said so. Both are now derived from real geometry: the scroller is W + daysEl.offsetWidth (which also survives the 640px breakpoint, where the day column becomes 30px), and the hint is hint.hidden = wrap.scrollWidth <= wrap.clientWidth + 1.
probe-a11y2.json.geometry — the numbers that used to disagree:
scroller inline width 882px wrap.scrollWidth 882
day column 34px canvas css width 848 848 + 34 = 882
report-harness.json.hintSweep — the viewport swept in 3px steps across the old dead zone and well past it, 220 ms settle per step (45 ms raced a resize event at the exact boundary and produced one false mismatch; the settle is stated because it is load-bearing):
54 widths, 960 -> 1119 px mismatches (hint shown != actually overflowing): 0
wrap scrollWidth observed 882 .. 918
first width that fits vw 1041
Previously measured over the same range: 13 of 13 sampled widths from vw 1002 to vw 1038 overflowed with the hint hidden.
console errors 0 · pageerrors 0 (all five contexts, both harnesses,
plus the sweep and both a11y probes)
Reaching a screen reader
The cursor is a state change a sighted reader can see, so it has to arrive in text as well. probe-a11y.json:
p.alm-read role="status" aria-atomic="true"
"2026-09-03 balance 0.4874 disc 0.470 [today]"
canvas aria-label "almanac: 365 days of 2026 as 53 weekly columns, one
dithered phase disc per day, days as rows, Monday
first; cursor on 2026-09-03, today"
The name carries the column count, the cursor's date and whether that date is today — the three things the picture says that the old static label did not. [today] is a text carrier, not a colour one, which is the whole reason the essence ring could be dropped without losing the state.
text nodes checked 45 below 4.5:1 0
lowest ratios 4.86 span.label.label-sigil #cd5860 on #0a0a0b 9.5px
5.01 p.label.label-sigil #cd5860 on #050505
5.22 span.label o-4 on #0a0a0b
focusables added by this stratum 1 .alm-wrap 1046 x 132
:focus-visible solid 1px rgb(194,51,61) offset 2px
ArrowRight x6 at 390 scrollLeft 0 -> 218 (max 590)
listeners after 10 mount/destroy cycles 0 (mn:ephemeris 0, resize 0)
a destroyed instance ignores a later mn:ephemeris and a later resize
double-mounting one host 7 day labels, 12 month labels (not 14/24)
Determinism
Two mounts of the same year in one context, disc quantities FNV-hashed:
hash A 3534019236 · hash B 3534019236 · identical
Same date, same calendar. No Math.random anywhere in this stratum.
The fourth instrument
§09 says the phase disc is "used exactly three times on this page and no more — a primitive, not a motif; the fourth use is the one that turns it into decoration." This stratum is the fourth site and calls the primitive 365 times, and the earlier draft of this file cited the disc as established practice without ever naming the cap it was standing on.
The argument, made rather than assumed: the cap counts instruments, not calls. The rail is one use however many discs it steps through as the reader moves; the machine-state disc is one use though it redraws every frame. The almanac is one instrument — a single grid, read as a grid, whose cell happens to be the disc. What §09 is protecting against is the disc turning up as an ornament beside something that did not need it; a calendar whose only mark is a lit fraction of a computed quantity is the opposite case, and there is nothing else on the plate.
It is the fourth instrument and, on this reading, the last one the page is allowed. The sentence is now in the body flow of the stratum itself (stratum.html) as well as here, so a reader meets the cap where they meet the disc. If the reading is rejected, §09's sentence has to be amended or this plate has to find another mark; the disclosure is not a licence.
Contested
Nothing. Every finding in the review was accepted and fixed. The two statistical claims the review pushed back on without calling them defects are answered where they live: the terrain's share is stated as 13.8% of the balance's variation in both copy.md and this file, next to the autocorrelations, and the reviewer's independent regression on the drawn output (r = 0.9855 on lit-cell counts, residual sd 3.26 cells, 230/365 days differing from the pure-moon prediction by 2 cells or more) agrees with it. The phrase "a calendar of this page rather than of the sky alone" is doing comparative work between three candidates, not claiming the terrain dominates; the split is printed beside it in every place the phrase appears.
After the review
Two reviewers rebuilt the harness independently and reproduced the build byte-for-byte, the date-to-cell-to-pixel chain, the statistic against field.glsl, the legibility table, determinism, the CSS audit, and the contrast ladder. Eight findings. All eight applied:
| finding | now | |
|---|---|---|
| blocking | essence ring on today — a second cyan, and a border | ring is sigil at every date; 0 essence pixels in every context |
| blocking | COLS = 53 drops 31 Dec in 1984/2012/2040 | sized per year; 0 years 1980–2060 with a day off the grid |
| should | dpr-2 bitmap held 3 alpha-blended pixels | clearRect before fillRect; tally runs at both rasters; non-canon 0 / 0 |
| should | proto printed "balance" for all three statistics | prints the drawn statistic; axis legend per statistic |
| should | scroll-hint dead zone, vw ~1002–1041 | gated on measured overflow; 0 mismatches over 54 widths |
| should | §09's three-use cap never named | argued in stratum.html and above |
| should | cursor change never reached a screen reader | role="status", and the cursor is in the canvas's name |
| — | (found while fixing) cost said "365 days" for a 366-day year | written by build(); 2040 reads 366 |
Removing the pulse removed the animation loop with it — only live things beat, and with no essence there is nothing alive on this plate to beat. That is a smaller stratum than the one the review read: no requestAnimationFrame, no t, no reduced-motion branch, and one fewer state to get wrong.
Not established
- Real-GPU rendering. Software GL only; the frame interval is vsync-capped at 16.7 ms, so the headroom is a floor, not a measurement.
- That 288 samples is the right patch resolution. 24 × 12 was the brief's figure and the aspect matches the reference viewport; nothing was measured at 12 × 6 or 48 × 24 to show the statistic has converged.
-
That the balance is stable across years. Every number here is 2026. The disc stretch is derived from
TIDE, so it does not depend on the year, but no second year was derived and compared. -
That the mirrored JS noise agrees with the shader. It uses the same constants and a different hash by design, so it is the same class of climate at the same offset — the agreement is asserted, not measured against a
u_probereadback of the real field. - That column contrast is the right legibility metric. It is a defensible one and it separates the candidates by an order of magnitude; no reader was asked.
-
That a real screen reader reads it as intended.
role="status"and the canvas's accessible name were checked as attributes and in Chromium's accessibility tree. No NVDA, JAWS or VoiceOver was run, so the announcement order and the verbosity of the 366-day label are unmeasured. -
Anything above dpr 2, or on real mobile silicon. The raster is checked at 1 and 2; dpr 3 is clamped to 2 by
sizeFor, which is a decision, not a measurement of what dpr 3 would look like. - That the reading of §09's three-use cap is accepted. The argument is made in The fourth instrument above and in the stratum's own body flow. It is an argument, not a ruling; if it is rejected, §09's sentence has to be amended in the same commit or this plate needs another mark.
- Anything on paper, or in print. As everywhere else in this canon.
Adversarial review · a11y robust
Nothing below is taken from evidence.md. Every number was reproduced in this directory by scripts written for this review:
node artifact/strata2/almanac/build-harness.mjs # rebuilt from source
node artifact/strata2/almanac/probe-a11y.mjs # -> probe-a11y.json
node artifact/strata2/almanac/probe-a11y2.mjs # -> probe-a11y2.json
node artifact/strata2/almanac/probe-a11y3.mjs # -> probe-a11y3.json
plus artifact/review.mjs run twice over the current source — once with the stratum and once against a stratum-free build of the same design-system.src.html — to separate what this stratum adds from what the page already carries.
Screenshots read back as a designer: shot-a11y-1440.png, shot-a11y-390.png, shot-a11y-scrolled-390.png, shot-a11y-plate-{390,1010,1440}.png, shot-a11y-reduced-t0.png, shot-a11y-reduced-t1.png.
Verdict: ships with fixes. One law violation that the builder names but does not fix, one responsive defect the builder's two-viewport test could not see, and one screen-reader gap. Everything the lens actually asks about — reduced motion, contrast, controls, resize, teardown, console — is clean and reproduces.
Blocking
1 · A second essence element in the default state
stratum.js:267-268. Whenever the ephemeris sits on the real today — its default state, with no interaction — the plate paints a 32-cell #00f0ff ring beating on sin⁴(t·1.55). Measured on the calendar canvas at mount:
essence cells 0,241,255,141 x 32 (mid-pulse, one alpha)
witness cells 226,228,230,255 x 11430
non-canon 0
The page's own living dot is alive at the same time. The law is "The cyan budget on the page is one dot, plus the hero's constellation while pressed. Do not add a second." This is a second, and it is not gated behind a press. evidence.md names the tension under "A tension this stratum did not resolve" and keeps the ring anyway; naming a law violation does not discharge it.
Fix. In paint(), drop the cursorIsToday branch:
ctx.fillStyle = "#c2333d";
ctx.globalAlpha = 1;
The state is not lost — readout() already prints [today] as text, which is the non-colour carrier the canon wants anyway. If aliveness must be shown, spend the essence the way the hero does: only while the plate is hovered or focus-within, so the default page still holds exactly one cyan.
Should fix
2 · Scroll-hint dead zone: the year is clipped and says nothing
stratum.js:328 decides the hint with hint.hidden = wrap.clientWidth >= W, where W = COLS * CELL = 848. But the wrap's content is the 34px sticky day column plus the 848px canvas — 882px. stratum.js:236's scroll.style.width = W carries the same omission, so the scroller's box is narrower than what it contains.
Measured (probe-a11y2.json.deadzone), stepping the viewport in 4px:
| viewport | wrap clientWidth | wrap scrollWidth | overflows | hint |
|---|---|---|---|---|
| 1002 | 848 | 882 | yes | hidden |
| 1010 | 855 | 882 | yes | hidden |
| 1026 | 869 | 882 | yes | hidden |
| 1038 | 880 | 882 | yes | hidden |
| 1080 | 918 | 918 | no | hidden (correct) |
| 1000 | 846 | 882 | yes | shown (correct) |
Across roughly 1002–1041px the last ~34px of the grid — the final days of December — are silently clipped with no scroll affordance. shot-a11y-plate-1010.png shows it: the December column runs off the right edge mid-disc and the hint row is absent. §10: "A table that scrolls inside its container says so… The hint is added only when the table actually overflows, and re-evaluated on resize." evidence.md's viewport table records only 1440 and 390, which straddle the window without entering it.
Fix. Test the real overflow rather than a constant, and stop lying about the scroller's width:
if (daysEl) scroll.style.width = (W + daysEl.offsetWidth) + "px";
// ...
var over = wrap.scrollWidth > wrap.clientWidth + 1;
if (hint) hint.hidden = !over;
offsetWidth also survives the 640px breakpoint, where the day column becomes 30px and the constant would need a second value.
3 · The readout changes silently; the canvas never names the cursor
stratum.html:23 and stratum.js:290-298, 219-221. The date, the balance and [today] are rewritten every time the ephemeris fires, with no live region (aria-live and role both null, confirmed in probe-a11y.json.readout). The canvas is role="img" with a good static label —
"almanac: 365 days of 2026, one dithered phase disc per day, weeks as
columns and days as rows, Monday first"
— which never mentions which day is marked. The month row and the weekday column are both aria-hidden="true". So a screen-reader user can learn the grid exists and can learn today's numbers once, but cannot learn that the cursor moved, or where it is, when the ephemeris's own controls move it.
Fix. Two lines. On the readout paragraph in stratum.html:
<p class="alm-read mono" role="status" aria-atomic="true">
and in readout(), fold the cursor into the canvas name:
cv.setAttribute("aria-label", baseLabel + "; cursor on " + d.iso +
(cursorIsToday ? ", today" : ""));
Notes
4 · A tab stop that cannot do anything
.alm-wrap carries tabindex="0" unconditionally. At ≥ ~1042px it does not overflow, so a keyboard reader gets a focus stop with a sigil ring and nothing to scroll. Toggle it where the hint is toggled, with the corrected test: wrap.tabIndex = over ? 0 : -1.
5 · Overflow state is re-evaluated on window resize only
stratum.js:331. A width change that does not raise a window resize — the mobile index sheet locking body scroll and removing the scrollbar, a container-driven reflow — leaves the hint and the tab stop stale. A ResizeObserver on wrap calling the same onResize, disconnect()ed in destroy(), closes it.
6 · The degraded handle is not the same shape as the real one
stratum.js:172-176 returns {destroy} alone when MN_derive / MN_phaseDisc are absent, while the success path also exposes series(), stat and buildMs(). A caller that reads h.stat on the degraded path gets undefined and h.series() throws. The same branch writes "[fail] shared mechanism absent"; §3's status brackets are uppercase ([OK] [WARN] [FAIL]).
7 · role="group" on a scroll region
role="region" is the usual pairing for a focusable, named scroll container and exposes it as a landmark. group is not wrong, only weaker.
Reproduced and clean
Reduced motion. Two element screenshots one second apart under reducedMotion: "reduce" are byte-identical (sha256 166d14fc23746908… both) and non-blank: 11,462 lit pixels, containing the 32-cell ring at alpha 254 — 0.55 + 0.45·sin⁴(4.65) = 0.996 — i.e. the frame is held at t = 3.0 and is correct, not merely still. After dispatching mn:ephemeris {date:"2026-03-09"} under reduce it stays static and the ring redraws as exact sigil 194,51,61,255 × 32. The rAF loop never starts.
Contrast. All 41 text nodes inside the section, each against its true composited background, walked up to the nearest opaque ancestor, with color-mix() float serialisation handled. 0 below 4.5:1. Lowest three:
4.86 9.5px span.label.label-sigil #cd5860 on #0a0a0b "365 days x 288 samples..."
5.01 9.5px p.label.label-sigil #cd5860 on #050505 "09 · e — the almanac"
5.22 9px i.alm-m / .alm-days i o-4 on #0a0a0b month + weekday labels
.tw-hint at 390 measures 4.86 (--sigil-text, correct — not sigil at reduced opacity). The readout and its <i> legend both 5.22.
Controls. Exactly one focusable is added: .alm-wrap, 1046 × 132 at 1440 and 288 × 132 at 390 — both far over the 44px floor. Accessible name "the year, scrollable sideways". :focus-visible computes to solid 1px rgb(194,51,61) at outline-offset: 2px. Focus lands on it and ArrowRight scrolls it (0 → 219px over six presses at 390; End scrolls the page, not the region, which is Chromium's behaviour for a scroll container, not a defect here). No buttons, no inputs, no aria state to get wrong.
Resize. 1440 → 390 → 1440 and a 21-width sweep from 320 to 1920: the document never scrolls horizontally at 360px and above. At 320 there is a 19px overflow — pre-existing: a stratum-free build of the same source overflows identically, by the same element (DIV.ch-phantom, the cloth hero). The almanac contributes zero horizontal overflow at every width tested.
Mount / destroy ×10.
net window listeners after 10 destroys 0 (mn:ephemeris 0, resize 0)
requestAnimationFrame calls / 600ms
baseline (page alone) 180
with 10 instances mounted 555
after destroy 180
600ms later 180
No AudioContext, no observers, no setInterval/setTimeout, no Math.random anywhere in stratum.js. A destroyed instance ignores a later mn:ephemeris (readout unchanged) and a later resize. A host detached without destroy() does not throw on resize. Double-mounting one host does not duplicate the 7 weekday or 12 month labels.
Pixels. 11,462 lit, 0 non-canon: witness 226,228,230,255 × 11,430 plus the ring at a single alpha. No grey, no second tone on the ring.
Determinism. Two fresh browser contexts at ?date=2026-03-09 produce an identical full-canvas pixel hash 831f9f702e16748a66edf327 and identical readouts. Year rollover: 2027-01-01 → 365 days, 2024-02-29 → 366 days, back to 2026-09-03 → 365 days and [today] returns.
Console. 0 console errors and 0 pageerrors across every context run here (1440, 390, dpr 2, reduced motion, and the sweeps).
review.mjs with the stratum present (run over the harness, which inlines the live design-system.src.html):
CONTRAST FAILURES: 2
BUTTON.btn 12px ratio 1.03 "disabled"
BUTTON.btn 12px ratio 1.94 "disabled"
pageerrors: none
radii: U.:50, DIV.ns-controls:50, BUTTON.:50 (unchanged)
faces: Departure 625, Geist 415 · weights 400/931, 500/109
svg 0 · emoji 0 · img w/o alt 0 · lang en · H1 1
focusables: 65
A stratum-free build of the same source reports the identical 2 failures, the identical radii, and 64 focusables — so the stratum adds exactly one focusable and no failure. Caveat for whoever repeats this: the checked-in artifact/design-system.html is a stale 1 September build; running review.mjs against it reports 117 contrast failures and lang: (unset), which is a property of that file, not of this stratum. Rebuild before comparing.
What this review did not establish
- Real assistive-technology behaviour. The aria attributes and the accessibility tree were read; no screen reader was run.
- Real-GPU rendering. Software GL only, same limit the builder names.
- Anything at dpr 3, or on real mobile silicon.
-
Whether the essence-ring law violation was requested by the almanac brief. The brief was not available here;
evidence.mdsays it was, which changes who owes the decision, not whether the shipped page has two cyan marks.
Adversarial review · doctrine
Reviewer: an agent that did not build this. Nothing in evidence.md was taken on trust; every number below was re-derived from a rebuild of the harness in a fresh Chromium. Scripts and raw JSON live in the reviewer's scratch directory (probe1..5.mjs, probe1..4.json); the commands that produced them are named beside each claim.
Verdict: blocked. Two blocking findings — a second essence element in the default state, and a leap-year class in which the calendar silently loses a day. Everything else is repairable in place. The craft underneath is real: the statistic is a faithful algebraic reading of field.glsl, the plumbing is byte-exact, and evidence.md's headline table reproduces to the digit.
What reproduced
-
The build is deterministic.
node build-harness.mjsrewritesharness.htmlandproto.htmlbyte-identically (md5c18643fb…/000ed052…before and after). -
The date → cell → pixel chain is exact. Three days picked blind (index 37, 180, 333 of 2026 →
2026-02-07,2026-06-30,2026-11-30), each date recomputed independently fromDate.UTC(y,0,1+i), each cell position recomputed from an independent Monday-firstdow0 = 3, and each 12×12 cell read back out of the live canvas and compared pixel-for-pixel against a freshwindow.MN_phaseDisc(ref, q, 12): diff = 0 on all three (35, 56 and 33 lit cells respectively).MN_deriveillum for the same instants: 0.6566 / 0.9829 / 0.5851. -
The balance really is the shader's own colour decision. Read out of
field.glslby hand: the polar cell ismix(WITNESS, mix(COLD,WARM, t01*.5+.5), .5), whose warm-position is0.5 + 0.25·t01; the agreement cell ismix(WITNESS, mix(COLD,WARM,u_tide), TIDE), whose warm-position is0.5 + TIDE·(illum − 0.5). Both appear verbatim indayStats. The statistic is not an invention beside the field, it is the field's mix parameter. This is the strongest thing in the stratum. -
The legibility table is independently reproduced, re-rendering all 365 discs through the real primitive and re-counting (
probe4.mjs):
| | lit cells | sd | states | acf 29 | column contrast | |---|---|---|---|---|---| | illum | 0–65 | 25.47 | 51 | 0.9126 | 21.34 | | density | 5–35 | 6.14 | 28 | 0.0126 | 1.66 | | balance | 1–61 | 19.24 | 52 | 0.9044 | 16.00 |
Identical to measure-render.json. The statistic choice is defensible on its own evidence.
-
Determinism — two mounts in one context, FNV hash of all 365 quantities:
3534019236twice. NoMath.randomanywhere in the file. -
No grey at dpr 1. Full readback of the 848×112 bitmap: 11,462 lit,
226,228,230,255 ×11,430+ the 32 ring cells. Nothing else. After scrubbing to2026-03-09:194,51,61,255 ×32— exact#c2333d. -
No tofu. Every glyph the stratum renders, tested in its actual computed face against a forced-fallback rendering:
·U+00B7,—U+2014,×U+00D7 in Departure;—,§U+00A7,−U+2212 in Geist. All render, none matches the fallback raster. No planetary or zodiac glyph anywhere. -
CSS is clean. Every element under
#almanac-harness: zero non-zero border radii, zero box-shadows, zero gradients, zero essence in any CSS colour property. -
Faces and ladder. All chrome, labels, data and the readout compute to Departure; all prose to Geist 400 with 500 emphasis. No text node below
o-4..label-sigiland.tw-hintcompute torgb(205,88,96)=#cd5860— sigil text, not sigil at reduced opacity. -
Focus and keyboard.
.alm-wrapfocus-visible =rgb(194,51,61) solid 1px, offset2px; height 132px; ArrowRight scrolls it (0 → 36). - Reduced motion. Loop never starts; two samples 900 ms apart are byte-identical; 11,462 lit — the same picture as the animated frame; ring held at t = 3.0, alpha 254.
- No horizontal page scroll at 390 or 1440; 0 console errors, 0 pageerrors in every context; frame interval median 16.7 ms / p95 16.7 ms (vsync-capped, 89 frames).
Findings
1 · BLOCKING — a second essence element, in the default state
The cursor ring is 32 cells of #00f0ff whenever the ephemeris sits on the real today, which is the page's default state at load. Measured at mount, with no interaction: 0,240,255,231 ×32 in the calendar bitmap, while the field canvas (#lattice) is simultaneously painting the one living dot from field.glsl. The page's cyan budget is one dot; this makes it two. The mark is also, geometrically, a ring around a cell — law 3 forbids essence as a border in as many words.
evidence.md names this under "A tension this stratum did not resolve". Naming a violation is honest, and it is not a cure: the law in the brief is "do not add a second", not "disclose the second".
Fix. Drop cursorIsToday from the paint decision entirely —
ctx.fillStyle = "#c2333d";
ctx.globalAlpha = 1;
— and delete the pulse from this stratum (only live things beat; an instrumentation ring should be still). Today is already distinguished in the Departure readout by the [today] bracket, which is where §1 says status belongs. If a live mark is judged genuinely necessary, it must be one cell — the centre pixel of today's square, not a 32-cell ring — and DESIGN.md §9's budget sentence must be amended in the same commit rather than exceeded quietly.
2 · BLOCKING — leap years starting on a Sunday lose 31 December
COLS = 53, but the grid needs ceil((dowOffset + days) / 7) columns. For a leap year whose 1 January is a Sunday, dowOffset = 6 and 6 + 365 = 371, so the last day lands in column 53 — one past the end of the 848 px canvas. Enumerated 1980–2060: 1984, 2012, 2040. Verified live by scrubbing the harness to 2040-12-31:
year 2040 · days 366 · last 2040-12-31
dow0 6 · lastCol 53 · lastRow 0 · canvas 53 columns
essence pixels in the bitmap: 0 <- the cursor ring is off-canvas too
readout: "2040-12-31 balance 0.3576 disc 0.161"
The readout and the aria-label both say 366 days; 365 are drawn. A calendar that silently drops a day is a correctness failure, and it takes the cursor with it — on 31 Dec 2040 the plate shows no cursor at all.
Fix. Either widen the grid unconditionally —
var COLS = 54; /* dow0 6 + 366 days = 54 columns; 53 loses 31 Dec */
— or, better, size per year inside build(): set cols = Math.ceil((dowOffset + out.length) / 7), recompute W = cols * CELL, and re-run sizeFor() before paintStill(). Add 2040-12-31 to shoot.mjs as a fixed regression shot.
3 · SHOULD-FIX — three non-canon pixels at dpr 2, and an audit that never looked
At deviceScaleFactor: 2 the bitmap reads:
226,228,230,255 ×45,717
0,241,255,161 ×125
83,236,246,255 ×3 <- not a token
rgb(83,236,246) is essence at globalAlpha composited over an opaque witness disc pixel. It is a mid-tone produced by alpha, which is the precise failure law 2 names, and it is the same class of defect the builder already found and fixed once (the double-composited Bresenham cells). It survives because report-harness.json's audit and auditScrubbed blocks both run only in the dpr-1 context; the dpr-2 shot records litPixels and nothing else. evidence.md's "non-canon pixels 0" is therefore true of one raster and asserted of two.
Fix. Clear before filling, so a ring cell is never a blend:
ctx.clearRect(x, y, px, px);
ctx.fillRect(x, y, px, px);
and run the colour tally in the dpr-2 context as well as dpr 1, then restate the claim in evidence.md with both numbers.
4 · SHOULD-FIX — the readout lies in two of the three prototypes
readout() hard-codes the word balance and .alm-read i hard-codes disc 0 cold-led · 1 warm-led, whatever opts.stat is. In proto.html:
stat illum "2026-09-03 balance 0.4874 disc 0.523 [today]"
stat density "2026-09-03 balance 0.4874 disc 0.187 [today]"
stat balance "2026-09-03 balance 0.4874 disc 0.470 [today]"
The illumination and density plates print a balance figure beside a disc that is not the balance, under an axis legend that does not describe them. This is in the shipped stratum.js, and proto.html / shot-proto-1440.png is the evidence artifact the whole statistic-choice argument rests on — mislabelled instrumentation is the one thing this canon cannot afford.
Fix. Name the statistic and print its own value:
var v = which === "illum" ? d.s.illum : which === "density" ? d.s.density : d.s.balance;
outRead.textContent = d.iso + " " + which + " " + v.toFixed(4) + " disc " + d.q.toFixed(3) + …
and move the axis caption into a per-stat string (illum 0 new · 1 full, density 0 sparse · 1 dense, disc 0 cold-led · 1 warm-led).
5 · SHOULD-FIX — the scroll hint hides while the grid still overflows
sizeFor() sets scroll.style.width = W (848), omitting the 34 px sticky day column, so the real scroll width is 882. The hint is then gated on wrap.clientWidth >= W — 848. Between those two numbers the grid overflows with no hint. Measured by sweeping the viewport in 3 px steps:
vw 1002 → clientW 848, scrollW 882, hint hidden
…
vw 1038 → clientW 880, scrollW 882, hint hidden
Thirteen consecutive sampled widths where up to 34 px of December is off-screen and nothing says so. §10 requires the hint whenever it actually overflows, and re-evaluated on resize — the re-evaluation is there, the threshold is wrong.
Fix. Derive both from the real geometry rather than from W:
var DAYW = daysEl ? daysEl.offsetWidth : 34;
scroll.style.width = (W + DAYW) + "px";
…
hint.hidden = wrap.scrollWidth <= wrap.clientWidth + 1;
6 · SHOULD-FIX — the fourth use of the phase disc is never argued
DESIGN.md §9: the phase disc is "used exactly three times on this page and no more — a primitive, not a motif; the fourth use is the one that turns it into decoration." This stratum is the fourth site and calls it 365 times. stratum.html, copy.md and evidence.md all cite the primitive as established practice and none of them mentions the sentence they are standing on. The defence is available and good — the almanac is one instrument that invokes the primitive per cell, exactly as the rail is one instrument — but it has to be made, not assumed. §16 exists because "every defect this canon has shipped was a rule everybody believed".
Fix. One sentence in the body-flow paragraph that introduces the disc, naming §9's cap and why an instrument is a use and not a motif; and a short "the fourth instrument" heading in evidence.md. If the argument is not accepted, §9's sentence has to be amended in the same commit — either way the collision is on the record.
7 · NOTE — the lede hard-codes 365 in a plate that prints 366
"the same procedure, run three hundred and sixty-five times, printed" sits two lines under a cost label that reads 366 days × 288 samples in any leap year (verified: scrubbing to 2028 gives 366). Fix: "once for every day of the year".
8 · NOTE — the essence triple in evidence.md is not reproducible as stated
evidence.md reports 0,240,255,161 ×32. The readback is premultiplied and rounds with the pulse: across four runs I measured alpha 231, 173, 141 and 254, with the green channel reading 240 or 241 depending on it. Fix: state the hue as 0,240–241,255 with a note that the channel rounds against the composited alpha, or sample at a fixed t the way the reduced-motion frame does.
9 · NOTE — the year has no non-visual reading
The canvas is role="img" with a one-line aria-label; the readout exposes only the ephemeris's own day; arrow keys scroll the container rather than moving a cursor. A keyboard or screen-reader user can reach the instrument and learn nothing from it. Fix: let ArrowLeft/ArrowRight on the focused wrap step cursorIdx and re-announce the readout via aria-live="polite", or mirror the series into a visually-hidden table.
10 · NOTE — two shader terms are dropped without saying so
The mirrored climate reproduces the warp, the shaping and the disagreement thresholds, but omits the shader's sweep (cellc = mix(cellc, WITNESS, sweep*0.14), which damps the balance toward 0.5) and the revelation gate (which re-shapes v, i.e. the density statistic). Both omissions are defensible — they are frame time, not day — but the header comment claims the "same constants as field.glsl" without listing what was left out. Fix: name the two dropped terms and their reason in the constants comment.
Doctrine questions, answered plainly
- Essence outside the one permitted place? Yes — finding 1.
- Grey fill where density should be marks? None at dpr 1. Three composited pixels at dpr 2 — finding 3.
- A glyph Departure cannot render? No. Every glyph verified against a forced fallback.
- Byte-identical from the same inputs? Yes: the build, the mount hash, and three days' cells all reproduce exactly.
-
Does the copy claim about the world where it should state a procedure? No. The lede refuses forecast and judgement explicitly, no figure carries a name, and every rule in
copy.mdships its reason. This part is clean. - Restrained, or a flourish? Restrained. One plate, one instrument, one moving mark, no ornament — and the fix in finding 1 makes it one quieter moving mark. The 4× crop of January–April reads as a proper printed almanac.
- Is the picture of the page or of the sky? The builder's own critique is the honest one and I confirm it on the drawn output, not the statistic: regressing the balance's lit-cell count on illumination's gives r = 0.9855, residual sd 3.26 lit cells; 230 of 365 days differ from the pure-moon prediction by ≥2 cells, 75 by ≥4, only 2 by ≥8. The terrain is present and small. The plate is legible as a lunation with a tremor in it, which is what the copy claims — but a reader looking down any single week column sees seven nearly identical discs, and the within-column spread (7.45 lit cells against illumination's 8.92) is mostly the moon moving through the week, not the field. The claim "a calendar of this page rather than of the sky alone" is true and is carrying more weight than 16.9% of the variance can hold. Consider softening it to what the numbers say.