stratum · seed identity · squad · measured

The almanac

365 days derived at noon, drawn as phase discs of the warm/cold balance. Lunations appear as bands: lag-29 autocorrelation 0.907 against 0.915 for the moon itself. Two colours in the bitmap at every raster.

evidence produced by scripts in artifact/strata2/almanac/ · plates in the review · live in the canon
The almanac plate

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:

minmaxsdacf lag-1acf lag-29acf lag-30r with illum
1 · illumination0.00001.00000.35380.97520.91450.91351
2 · field mean density0.16920.56900.0748−0.0087−0.0100−0.00870.0098
3 · warm − cold balance0.32770.65840.09880.96190.90670.90520.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 rangelit cellssddistinct statesacf 29column contrast
1 · illumination0.0000–1.00000–6525.47510.912621.34
2 · mean density0.1692–0.56905–356.14280.01261.66
3 · balance0.0898–0.87721–6119.24520.904415.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:

findingnow
blockingessence ring on today — a second cyan, and a borderring is sigil at every date; 0 essence pixels in every context
blockingCOLS = 53 drops 31 Dec in 1984/2012/2040sized per year; 0 years 1980–2060 with a day off the grid
shoulddpr-2 bitmap held 3 alpha-blended pixelsclearRect before fillRect; tally runs at both rasters; non-canon 0 / 0
shouldproto printed "balance" for all three statisticsprints the drawn statistic; axis legend per statistic
shouldscroll-hint dead zone, vw ~1002–1041gated on measured overflow; 0 mismatches over 54 widths
should§09's three-use cap never namedargued in stratum.html and above
shouldcursor change never reached a screen readerrole="status", and the cursor is in the canvas's name
—(found while fixing) cost said "365 days" for a 366-day yearwritten 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

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:

viewportwrap clientWidthwrap scrollWidthoverflowshint
1002848882yeshidden
1010855882yeshidden
1026869882yeshidden
1038880882yeshidden
1080918918nohidden (correct)
1000846882yesshown (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

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

| | 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.


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