stratum · seed identity · squad · measured · half right

The King Wen dither

The received hexagram sequence as a threshold matrix, checked structurally (28 inversion pairs, 4 complement pairs, the eight self-inverse figures where the sequence puts them). The clumping prediction was half right, which is the reason Bayer stays.

evidence produced by scripts in artifact/strata2/kingwen-dither/ · plates in the review · live in the canon
The King Wen dither plate

Evidence

Built directly on the real artifact. Harness: build-harness.mjs inlines artifact/design-system.src.html with the bundled fonts the way artifact/build.mjs does, then grafts this stratum's CSS, HTML and JS onto the end of §09 and mounts it. Probes: probe.mjs (Playwright/Chromium, --use-gl=swiftshader, results in probe.json), crop-check.mjs (crop-check.json), kingwen-check.mjs (kingwen-check.txt). Screenshots shot-1440.png, shot-panels.png, shot-mats.png, shot-390.png, shot-reduced.png.

Revision after adversarial review. Two reviewers reproduced the harness independently and raised nine items. All nine are applied; every affected number was re-measured by fix-probe.mjs (→ fix-probe.json) against a harness rebuilt from source, and solo-build.mjs (→ solo-gate-absent.html) builds the gate-absent mount the blocking finding used. New screenshots: fix-host-1440.png, fix-host-390.png, fix-mats.png, fix-slider.png, fix-gate-absent.png, fix-gate-absent-note.png. What changed is recorded under After review below, with the before-and-after number on each line.

What it is

The ephemeris claims a six-bit figure's trigram coordinates are its address in the 8×8 Bayer matrix — three bits of row, three of column, both built by the same recursive halving. If that holds, the sixty-four thresholds can be re-ordered by any other complete ordering of the sixty-four figures without moving a figure out of its cell. The received (King Wen) sequence is one. The plate builds that matrix — threshold at cell (upper, lower) is that figure's sequence position minus one — and runs the photograph through both at the same gain, from the same luma, with the numbers printed under each panel.

No name, no judgement and no reading is attached to any figure. The sequence is used as a permutation of 0–63 and nothing else.

The prediction, stated before the run

Recorded in copy.md and on the plate above the panels, written before probe.mjs existed:

the received order will clump, because Bayer's recurrence exists to spread thresholds evenly across every 2×2 and 4×4 sub-block and a sequence assembled by pairing figures under inversion has no such property.

Partly right, and the half that was wrong is the half the brief named.

The table, verified — kingwen-check.mjs

Trigram index is three bits, bit 0 the bottom line, yang = 1. Hexagram value is lower trigram in bits 0–2, upper in bits 3–5. The table is written as [lower, upper] pairs so it can be audited against a printed edition without binary arithmetic.

check 1  entries                                     64
check 1  distinct values                             64
check 1  covers 0..63 exactly once                   PASS

check 2  pairs joined by inversion                   28 of 32
check 2  pairs joined by complement                   4 of 32
check 2  pairs failing both                           0

check 3  self-inverse figures in 0..63                8   (expected 2^3)
check 3  their King Wen positions        1 2 27 28 29 30 61 62
check 3  positions as expected                       PASS
check 3  every self-inverse figure is in one of them PASS

check 4  #1  = 111111 = 63                           PASS
check 4  #2  = 000000 =  0                           PASS
check 4  #11 = 000111 =  7                           PASS
check 4  #12 = 111000 = 56 = rev(7)                  PASS
check 4  #63 = 010101 = 21                           PASS
check 4  #64 = 101010 = 42 = rev(21)                 PASS

check 5  doubled-trigram figures                      8
check 5  positions                        1 2 29 30 51 52 57 58

Two structural facts of the received sequence, both required by the brief and both confirmed: consecutive pairs are joined by inversion (28 of 32 pairs), and the eight figures that are unchanged by inversion — exactly 2³ of them, one for each choice of the upper three lines — occupy the four remaining pairs and are joined by complement instead. Every pair is one or the other; none is neither. The eight doubled-trigram figures land at 1, 2, 29, 30, 51, 52, 57, 58.

The Bayer matrix the plate uses was compared against a freshly generated recurrence in-page: identical (crop-check.json → bayerIsRecurrence: true).

Measured — the gain sweep, both panels, one photograph

Statistics over the aligned 168×120 sub-region (divisible by 4, the window, and by 8, the matrix tile). var figures are ×10⁻³. power below tile is the share of non-DC spectral power of the dither residual at radial frequencies ≤ 1/8 cycles per cell — that is, at scales coarser than the matrix tile, which is what "clumpy" means when it is made into a number.

gain    var lit density      var residual        spectral flatness    power below tile     lit cells
        bayer    kingwen     bayer    kingwen    bayer    kingwen     bayer    kingwen     bayer    kingwen
0.90    65.34     67.77      1.04      5.17      0.1774   0.2366      0.73%    12.13%      6949     6944
1.00    79.58     82.10      0.97      4.53      0.1837   0.2497      0.69%    13.55%      7724     7748
1.12   100.24    102.52      1.02      3.65      0.1803   0.2687      0.80%    13.27%      8616     8654
1.30   134.14    136.56      1.19      4.15      0.1818   0.2782      0.74%    13.83%     10002    10041
1.50   147.68    149.75      0.95      3.37      0.1822   0.2846      0.73%    13.34%     10908    10939

windows per panel                          1260  (42 × 30, non-overlapping 4×4)
cells measured per panel                  20160
lit-cell gap at the canon's gain (1.12)      38 of 20160 = 0.19%

residual ratio, kingwen ÷ bayer      4.98  4.69  3.57  3.49  3.56
  (gains 0.90 1.00 1.12 1.30 1.50, re-measured by fix-probe.mjs)

Where the prediction held. The dither residual — local lit density minus local target density — is 3.5× to 5.0× more variable under King Wen at every gain (3.65 against 1.02 at 1.12; the sweep's own extremes are 3.49 at gain 1.30 and 4.98 at 0.90). The share of residual power at scales coarser than the tile is 13.27% against 0.80%, a factor of 16.6. The panels show it without any statistic: the King Wen plate bands and blocks where the Bayer plate resolves the subject.

Where the prediction failed. The statistic the brief named — raw variance of local lit density over 4×4 windows — barely moves: 102.52 against 100.24 at the canon's gain, +2.3%, and the gap never exceeds +3.7% across the sweep. That is not a measurement of the dither. It is a measurement of the photograph: a dark region is sparse under any matrix, and the image's own contrast dominates the window-to-window spread by two orders of magnitude. Reported as the brief asked, and reported as insufficient. The residual variance is the same statistic with the photograph subtracted, and it is the one that answers the question. That diagnosis was a claim when it was first written; it is now a measurement — on a flat field the same raw statistic separates the two matrices completely (0.000e-3 against 5.859e-3), see After review · 6 below.

Where the second statistic points the other way. Spectral flatness of the residual is higher for King Wen (0.2687 against 0.1803). Read naively — flat spectrum means noise-like means good — that says the received order is the better dither, which the panels flatly contradict. The explanation is that flatness measures how evenly power is spread over bins, not which bins: Bayer's residual is a sharply structured periodic pattern with energy concentrated in a few high-frequency harmonics of the 8-cell tile, so its spectrum is spiky and its flatness is low while its low-frequency content is near zero. Flatness is the wrong instrument for this question, and it is left on the plate because finding that out is worth more than deleting it.

The crop is not lying — crop-check.mjs

The plate computes its spectral pair on a tile-aligned 64×64 crop so it can run live on every gain change. Recomputed over the full aligned 168×120 field with a general-N DFT at gain 1.12:

flatness power below tile bayer 64×64 crop 0.1803 0.80% bayer full 168×120 0.1037 0.43% kingwen 64×64 crop 0.2687 13.27% kingwen full 168×120 0.1458 14.02%

Both statistics keep their ordering and the below-tile gap widens from 16.6× to 32.6× on the full field. Absolute flatness shifts with the bin count, as it must. The crop understates the result; it does not invent it.

Canon conformance — probe.mjs

console errors (all four contexts)                    0
horizontal scroll at 1440                             none
horizontal scroll at 390            scrollWidth 390 = clientWidth 390
distinct pixel colours, bayer panel      190,242,248,235  (one)
distinct pixel colours, king wen panel   190,242,248,235  (one)
grey pixels in either panel                           0
unlit cells                             fully transparent (alpha 0)
essence used anywhere in the plate                    0 occurrences
SVG elements                                          0
emoji / ⌗ / ≡ / ☰ / ⋮ in the plate                     none
non-zero border radius              1 rule: the circular mark (u, 50%)
faces rendering                          Geist 400/500, Departure 400
text colours          o-3 prose, o-4 labels, o-2 data, o-1 emphasis,
                      sigil-text for the two sigil labels — floor is o-4
interactive elements                     1 (input#kwGain, 908 × 44 px)
focus-visible          1px sigil outline, 3px offset — matching the canon's
                       own range inputs (.gate-ctl, .eph-scrub), not §15's
                       prose, which says 2px and disagrees with its own
                       shipped controls
mid-grey pixels over the whole control                0  (909 × 45 px)
reduced-motion panels vs default    sha256 identical, both canvases
  bayer  45741977fd61eefd   king wen  45d70bbb23ef9c1b   (both states)
lit cells under reduced motion                     8779  (not blank)
destroy()                            64 grid cells → 0, listeners removed
mobile matrix cell at 390                        40 × 51 px
aria-hidden on either operator plate                  none
role / name on each operator plate       img, both named (see §7 below)

There is no animation on this plate, so the reduced-motion state is not a degraded frame at t = 3.0 — it is the same frame, proven byte-identical. The argument here is a measurement and a moving measurement is harder to read.

The phase disc was not used on the gain control. §09 fixes that primitive at three instruments and says the fourth use turns it into decoration; this would have been the fourth.

Today's figure is read from window.MN_EPHEMERIS (and the mn:ephemeris event) and marked with a sigil corner tick in both plates at the same coordinates:

today · figure 011001 · dui over zhen · cell [3,1] ·
bayer threshold 29 · king wen position 17          (2 cells marked)

Same address, same figure, two different numbers — the whole claim, in one row.

Not established

After review — what changed, and the number it changed to

Nine findings from two independent reviewers. One blocking, eight should-fix. All applied in place; every affected measurement re-run by fix-probe.mjs against a harness rebuilt from design-system.src.html.

1 · blocking — prefers-reduced-motion frame at t = 3.0 was blank without the gate. boot() polled readSource() every 120 ms and only reached fallbackSource() after forty misses, so a mount with no §08 gate above it — a path the plate declares it supports, and the path any device takes before gateRender() has painted — showed two empty canvases for 4.9 s while their role="img" labels described a photograph that was not there.

The stand-in is now drawn on the first miss, polling continues underneath it, and the real plate replaces it the moment it lands; #kwSrcNote is cleared in the same breath. Re-measured on solo-gate-absent.html under reducedMotion: 'reduce', sampling the live canvases:

t (ms)      120    500   1500   3000   4000   5200
lit, bayer      8559   8559   8559   8559   8559   8559
lit, king wen   8516   8516   8516   8516   8516   8516
stat characters  115    115    115    115    115    115

Before: 0 / 0 / 0 lit and 0 characters at every one of t = 500, 1500, 3000, 4000; first paint at t ≈ 4900. After: correct and non-blank from t = 120 ms, identical at every sample. The t = 3.0 frame is the same frame as t = 0.12.

2 · the control painted a real grey. accent-color delegated the track to the UA, which paints a solid mid-tone bar — on a plate whose whole argument is that a mid-tone must be a density of marks. accent-color is gone; the track is ::-webkit-slider-runnable-track / ::-moz-range-track at height:1px, background:var(--rule), and the thumb a 9 × 15 square rgb(var(--witness)), no radius. Real pixels over the whole 909 × 45 control:

mid-grey px distinct greys colours present before (accent-color) 7316 44 incl. 59,59,59 ×3192 after 0 0 5,5,5 · 27,27,27 · 226,228,230

27,27,27 is --rule (rgba(230,230,230,.10)) composited on --void: a 1px line, which is what the canon draws rules with. 226,228,230 is --witness, 135 px of it — the thumb.

3 · the two operator plates were out of register. min-height:52px was a floor, and the King Wen cell carries an extra figure, so its rows grew. Pinned instead: grid-auto-rows:56px (51px under 520px), min-height:0. 56px is the tallest cell's content — 13px mark + 3 + 16px figure + 3 + 9px numeral + 12px padding — so nothing is clipped and nothing stretches.

@1440                          before                    after
bayer row heights        52.0 × 8                  56.0 × 8
king wen row heights     53.5 55.7 55.8 55.2 54.3 55.9 55.5 53.2   56.0 × 8
drift of the same address, row 1→8   0.0 … 21.9px      0.0 × 8
grid heights             425 vs 448                457 vs 457
@390 grid heights        377 vs 408                417 vs 417
@390 drift, row 8                    29px               0px
cells whose content overflows           —                  0

4 · "not a second encoding of it" was false. The gate thresholds its float lumaBuf; #g-src stores that luma rounded to 8 bits for display, and readSource() reads the rounded value back. Copy on the plate, in copy.md and the comment at stratum.js now read "read back from the gate's own source plate, at the 8-bit precision that plate stores", and the divergence is printed rather than claimed away. Re-measured, this plate's Bayer panel against the shipped #g-b8, both at gain 1.12:

cells compared                              21168
cells differing                                19
divergence                                  0.090%

The plate's internal result is untouched: both of its panels read the same quantized luma, so the Bayer-against-King-Wen comparison is exact.

5 · a number rounded outward. "3.6× to 5.3× more variable at every gain" was wrong in both directions: 5.3 appears nowhere in the data, and at gain 1.30 the ratio is 3.49, below the stated floor. Re-measured ratios are 4.98 / 4.69 / 3.57 / 3.49 / 3.56 across the sweep. The prose and the sweep table now say 3.5× – 5.0× and print the five ratios. The parallel claim stands unchanged: maximum density gap +3.73% at gain 0.90.

6 · the missing generality test, run. The self-critique named the flat and high-key case as "the cheapest way the headline result could still be wrong" and left it unrun. Run now, same 168 × 120 window, gain 1.00, var × 10⁻³:

field              dv bayer  dv king wen   gap    ev bayer  ev king wen  ratio
uniform 0.50          0.000       5.859     —        0.000       5.859     —
uniform 0.14          0.732       4.639  +533%       0.732       4.639   6.33×
uniform 0.86          0.732       0.732     0%       0.732       0.732   1.00×
linear ramp          87.423      88.356   +1.1%      0.659       5.120   7.77×

At uniform 0.50 the separation is complete: Bayer's variance is exactly zero — spreading density evenly across every 4×4 sub-block is precisely what the recurrence is for — against 5.859e-3 for King Wen. At 0.14 the brief's own raw statistic finally moves, +533%. At 0.86 the two genuinely tie, near saturation where almost every cell is lit under either ordering and there is nothing left to arrange.

So the suspicion was correct and it resolves in the plate's favour: the +2.3% near-tie on the photograph is this subject's contrast swamping the raw statistic, not the statistic failing. "Generality across images" moves out of Not established and becomes this row.

7 · the plates were hidden from assistive technology. aria-hidden="true" on both grids put the 128 threshold numbers that are the section's claim out of reach, and gave neither plate a name. Replaced with role="img" and a name on each; role="img" prunes descendants, so the <u> / <s> elements — whose real semantics are annotation and deletion — stay out of the tree while the plate itself becomes findable. Measured after: aria-hidden absent on both, role="img" on both, both named, 0 focusables added, one control still.

8 · a machine notice set in the prose face. #kwSrcNote was a bare <span> inside a .lede, so [WARN] gate source plate absent — deterministic stand-in rendered in Geist and ran on from the end of a sentence. Now #kwSrcNote:not(:empty) is display:block, Departure at 11px, --sigil-text. Measured in the gate-absent mount: computed family Departure, display block, its own line (left edge flush with its parent), contrast 5.01:1 on --void — the floor, and above 4.5.

9 · Departure coverage, settled. The open item in Not established was inconclusive because the probe queried the family name "Departure Mono"; the real family is "Departure". Re-probed with the correct name: the face is confirmed loaded (its control glyph differs from generic monospace), a private-use codepoint renders a 563-ink notdef box, and all 47 distinct characters this plate sets in Departure render real glyphs — zero matching the tofu signature, zero blank. The ASCII labels stay, because they are legible and there is no reason to trade that for a sigma, but the coverage question is now closed rather than open.

Unaffected and re-confirmed after all nine: 0 console errors, scrollWidth 390 = clientWidth 390, one control, both canvases still one colour (190,242,248) with 0 grey and 0 essence pixels, and the sweep numbers identical to the originals to every printed digit.

Contested

One number, and it is a disagreement between the two reviews rather than with either of them. Reviewer B prescribed grid-auto-rows:52px with a mobile 46px, computing the King Wen cell's content as "6×1px + 5×2px = 16px plus the 9px numeral and two 3px gaps, so 52px holds it". That omits the threshold mark, which is the tallest thing in the cell: dotFor(63, 11) = 2 + 11 × 1.0 = 13.0px. Full content height is 13 + 3 + 16 + 3 + 9 = 44px, plus 12px of padding = 56px; at 52px the darkest cells clip by 4px. Reviewer A's grid-auto-rows:56px is the number that was applied, and it agrees with A's own measurement of the tallest floored row (55.9px). Mobile is 51px by the same arithmetic (13 + 2 + 16 + 2 + 8 + 10). Measured after the change: 0 cells whose content overflows its box, at 1440 and at 390.

Nothing else is contested. The other eight findings are accepted in full.

Adversarial review · a11y robust

Reviewer probes (written fresh, not the builder's): a11y.mjs (contrast / controls / keyboard / live regions / mount-destroy ×10 / reduced motion / resize), a11y2.mjs (console baseline, gate-absent mount, forced-colors), a11y3.mjs (fallback timing), a11y4.mjs (plate geometry), a11y5.mjs + a11y6.mjs (320px reflow, duplicate ids), and review.mjs re-pointed at the harness. harness.html was rebuilt from build-harness.mjs before every run. Raw output: the reviewer's scratchpad a11y.json / a11y2.json.

Verdict: ships with fixes. Inside the real artifact the plate is clean — 0 page errors in five browser contexts, every text node ≥ 5.01:1, one 44px named control, reduced-motion frames byte-identical and non-blank, no listener/timer/observer leak across ten mount-destroy cycles, and review.mjs over the harness reports exactly 2 contrast failures (the exempt disabled buttons) and 0 page errors. One blocking defect and three lesser ones are below; none of them is in the real-artifact path except #2 and #4.


Findings

1. BLOCKING — the plate is blank through t=3.0 when the gate is not above it

stratum.js promises, in a comment and in #kwSrcNote, that "a deterministic field stands in so the panels are never blank." It does not stand in for 4.9 seconds. boot() polls readSource() every 120 ms and only calls fallbackSource() after ++srcTries > 40.

Measured (a11y3.mjs, standalone mount, prefers-reduced-motion: reduce):

t (ms)kwPanelB litkwPanelK litstat chars#kwSrcNote
500000(empty)
1500000(empty)
3000000(empty)
4000000(empty)
500085598516115[WARN] gate source plate absent …

The canon law is "prefers-reduced-motion: ONE static, correct, NON-BLANK frame at t=3.0". In this supported mount path the t=3.0 frame is two empty canvases carrying role="img" labels that describe a photograph that is not there, and five missing stat rows. The same window opens in the real artifact on any device where §08's gateRender() has not yet painted #g-src.

Fix — draw the stand-in on the first miss and keep polling; swap in the real plate when it lands:

function boot() {
  if (!alive) return;
  if (readSource()) {
    var n = host.querySelector("#kwSrcNote"); if (n) n.textContent = "";
    render(); return;
  }
  if (!luma) { fallbackSource(); render(); }   // first miss: never a blank frame
  if (++srcTries > 40) return;                 // stop polling, keep the stand-in
  timer = global.setTimeout(boot, 120);
}

2. SHOULD-FIX — the two operator plates do not line up, and the copy says they do

stratum.css line 86 says "both plates take the same cell height so the same address sits at the same place in both", and stratum.html line 40 tells the reader "the left-hand cell at the same coordinates holds the same figure". min-height:52px is a minimum, and the King Wen cells carry an extra .kw-fig, so they grow — unevenly.

Measured (a11y4.mjs):

Bayer plateKing Wen plate
grid height @1440425 px448 px
cell height @144052.0 (uniform)53.2 – 55.9
row tops @14401, 54, 107, 160, 213, 266, 319, 3721, 55.5, 112.2, 169, 225.2, 280.5, 337.4, 393.9
grid height @390377 px408 px

By row 8 the same address sits 21.9 px lower in the received plate than in the constructed one — 42 % of a cell. The reader is asked to compare cell to cell across a gutter; the rows drift apart as the eye travels down. On a comparison plate whose only argument is address correspondence, this is the one alignment that has to hold.

Fix — pin the track height instead of flooring it:

.kw-grid{ grid-auto-rows:52px; }
.kw-grid i{ min-height:0; }
@media (max-width:520px){ .kw-grid{ grid-auto-rows:46px; } }

(.kw-fig is 6 × 1px + 5 × 2px = 16 px plus the 9 px numeral and two 3 px gaps — comfortably inside 52.)


3. SHOULD-FIX — the whole argument is aria-hidden, and neither plate has a name

#kwGridB and #kwGridK are aria-hidden="true" (confirmed: 119 characters of text each, 0 focusables). The 128 threshold numbers that are the section's claim reach no assistive technology, and neither plate carries a name — a screen-reader user gets the two canvases' labels and the prose, and nothing that says a plate exists here at all. Related: the marks use <u> and the figures <s>, whose real semantics (annotation, deletion) would announce wrongly if the hiding were simply removed.

Fix — swap the hiding for a name. role="img" prunes descendants, so the <u>/<s> misuse stays out of the tree and the plate becomes findable:

<div class="kw-grid" id="kwGridB" role="img"
     aria-label="eight by eight plate — the constructed Bayer thresholds, one to sixty-four; row is the upper trigram, column the lower"></div>
<div class="kw-grid kw-grid-fig" id="kwGridK" role="img"
     aria-label="eight by eight plate — the received sequence positions, one to sixty-four, at the same sixty-four addresses"></div>

4. SHOULD-FIX — a machine warning set in the prose face, mid-sentence

#kwSrcNote is a bare <span> inside a .lede, so [WARN] gate source plate absent — deterministic stand-in renders in Geist and runs on from the end of the paragraph (visible in the standalone screenshot). Departure carries labels, data and chrome; a bracketed machine notice is chrome, and the [ ] are Departure's own glyphs.

Fix — stratum.css:

#kwSrcNote:not(:empty){
  display:block;margin-top:6px;
  font-family:var(--font-system);font-size:11px;letter-spacing:.06em;
  color:var(--sigil-text);            /* 5.01:1 on --void, measured */
}

5. NOTE — destroy() leaves the plate on screen

Listeners, timers and observers are clean (see evidence). What is not released is the picture: after destroy() the canvases still hold 8770 lit pixels, both stat blocks still hold their five rows, and data-kw-measured="1" is still on the host — while the gain slider remains focusable and is now inert. A host that tears the stratum down is left with a live-looking, dead plate.

Fix — at the end of destroy():

[cvB, cvK].forEach(function (c) { c.getContext("2d").clearRect(0, 0, c.width, c.height); });
while (statB.firstChild) statB.removeChild(statB.firstChild);
while (statK.firstChild) statK.removeChild(statK.firstChild);
host.removeAttribute("data-kw-measured");

6. NOTE — the gain readout announces the value a second time

<output id="kwGainOut"> has no aria-live override, so it is an implicit role="status" polite live region. Every arrow-key step announces twice: once as the slider's own value, once as the output's changed text.

Fix — aria-live="off" on the <output>. The slider already speaks its value.


7. NOTE — .kw-mat overhangs at 320 CSS px

WCAG 1.4.10 reflow is measured at 320 px. .kw-mats uses repeat(auto-fit,minmax(300px,1fr)), and at 320 px the 300 px track exceeds the available column: .kw-mat overhangs the viewport by 12 px. Not a regression — the canon page's own scrollWidth at 320 is 347 with and without the stratum (a11y6.mjs), so the stratum adds nothing to the page's existing overflow — but the plate itself clips.

Fix — repeat(auto-fit,minmax(min(300px,100%),1fr)), and the same for .kw-gate's 258px.


8. NOTE — a second concurrent mount produces duplicate ids

kwGain, kwPanelB, kwStatB, kwPanelK, kwStatK, kwGainOut, kwGridB, kwGridK are hardcoded in the fragment. Mounting twice gives 2 × #kwGain (confirmed), and the <label for="kwGain"> then names only the first. No live harm on a single-instance page. Fix if it matters — suffix the ids at mount time and set label.htmlFor to match.


9. NOT A FINDING — checked before reporting


Evidence — what I measured, not what evidence.md claims

Zero page errors in five contexts: default @1440, reducedMotion:'reduce', resize 1440→390→1440, forcedColors:'active', and the gate-absent standalone mount. The three ERR_FILE_NOT_FOUND console errors seen against raw design-system.src.html are its unsubstituted font placeholders and are absent from the built harness.

Contrast — every text node in #kw-host, composited against its true painted background (walk the ancestor chain to the first background with α > 0.92, then alpha-composite the colour onto it). 32 nodes at 1440 and again at 390: 0 below 4.5:1. Floor is 5.01:1 (.label-sigil, var(--sigil-text) #cd5860 on #050505). The o-4 labels that sit on --void-2 rather than --void — .kw-cap span, .kw-stat span — measure 5.22:1, so the token stack survives the lighter panel ground. .kw-grid i b at o-2 measures 11.47:1; the stat values 11.47:1; the bayer 8×8 caption 15.86:1.

The one control. INPUT[type=range]#kwGain — 907.9 × 44 @1440, 273.1 × 44 @390, 252 × 44 @320. Accessible name threshold gain from <label for>; aria-describedby="kwGainHelp"; tabIndex 0; not disabled. Reached by Tab in 11 presses from the section's first control (trail ends … INPUT#ephScrub → INPUT#kwGain). :focus-visible matches on focus; computed outline 1px solid rgb(194,51,61), offset 3px. ArrowRight moves 1.12 → 1.13, the <output> follows, both panels repaint and all ten stat rows recompute (readings() at 1.13: below-tile 0.00796 bayer / 0.13258 king wen).

Reduced motion. #kw-host screenshots one second apart: sha256 cb7415a9758d71bd both — byte-identical. Whole-page frames also identical (e21cdf979d5145bd). Non-blank: kwPanelB 8770/21168 lit, kwPanelK 8779/21168, 116 chars of stats, 64 grid cells. (With the gate present — see finding 1 for the path where this fails.)

Resize 1440 → 390 → 1440. scrollWidth === clientWidth at all three steps (1440/1440, 390/390, 1440/1440); zero elements inside the host overhang the viewport at any step; the layout returns exactly (host 1084 px and gain 907.9 px before and after).

Mount / destroy ×10. Instrumented window.add/removeEventListener, setTimeout/clearTimeout, requestAnimationFrame, AudioContext, and all three observer constructors around ten full cycles: window-listener ledger nets 0 (mn:ephemeris reads −1, which is the pre-instrumentation mount's removal), 0 rAF from the stratum, 0 AudioContext, 0 MutationObserver / ResizeObserver / IntersectionObserver, both grids emptied to 0 children on every destroy(). Separately, destroying mid boot-retry stops the setTimeout chain dead — the fired count froze at 1 and did not move across the following 2.5 s.

review.mjs over the harness (the stratum grafted into the real artifact):

— CONTRAST FAILURES: 2
    {"sel":"BUTTON.btn","ratio":1.03,"txt":"disabled"}
    {"sel":"BUTTON.btn","ratio":1.94,"txt":"disabled"}
— essence used as fill/border: [{"sel":"U.","as":"background","size":"9x9"},
                                {"sel":"I.ns-dot","as":"background","size":"10x10"}]
— sigil uses: 0 as background: 0
— non-zero radii: ["U.:50","DIV.ns-controls:50","BUTTON.:50"]
— svg icons: 0  emoji: 0  img w/o alt: 0
— landmarks: {"nav":2,"main":1,"header":1,"footer":1}  lang: en   H1 count: 1
— pageerrors: none

Exactly the 2 exempt disabled buttons and 0 page errors, as required. The essence budget is untouched — both entries are the canon's own dots, neither is the stratum's. Caveat worth recording: the checked-in artifact/design-system.html is a stale 1 September build and reports 117 contrast failures and 57 sigil-as-text uses on its own, before any stratum. The harness built from today's .src.html is the correct comparison and is what the numbers above come from.

Numbers reproduced. At gain 1.12 the shipped plate prints, and I read back from the DOM: bayer 8616 / 20160, 100.24e-3, 1.02e-3, 0.1803, 0.80%; king wen 8654 / 20160, 102.52e-3, 3.65e-3, 0.2687, 13.27%. Identical to evidence.md. The plate reads the real #g-src (present, 168×126) in the harness — no stand-in, the warn slot stayed empty — so both panels genuinely argue about one encoding of one photograph.

Read as a designer. At 1440 the plate is restrained and the argument is visible before the numbers are: the received panel visibly curdles into 8-cell blocks while the constructed panel holds an even grain. The sigil corner ticks on the canon cell and on today's address are instrumentation, not decoration. The only thing the eye catches on is finding 2 — the right-hand operator plate slowly sliding out of register with the left as you read down it.

Adversarial review · doctrine

Reviewer's own run, not a reading of evidence.md. Everything below was re-measured from the rendered page or recomputed from scratch; where a number in evidence.md is quoted it is because I reproduced it, and where it is wrong I say so.

Harness rebuilt before use (node build-harness.mjs) — the committed harness.html is byte-identical to a fresh build, so the shipped screenshots are of the shipped code.

Verdict: ships with fixes. No law is broken by anything this stratum invented. Four items want fixing before it lands; one is inherited from the shipped canon and is named as such.

Scripts and artefacts written by this review, all under rv/: kw-table-check.mjs (independent sequence re-entry), rv-probe.mjs + rv-probe.json, rv2.mjs + rv2.json, rv3.mjs + rv3.json, rv4.mjs, screenshots rv-gate.png rv-mats.png rv-host-1440.png rv-host-390.png crop-390-mats.png rv-slider.png ctl-shipped-gate.png ctl-kingwen.png.


What survived the attack

The table is genuinely King Wen, not merely structurally plausible. The builder's kingwen-check.mjs verifies internal properties, which a wrong table could also satisfy. I re-entered the sequence by a different route — hexagram name → (lower, upper) trigram for all sixty-four, then converted to the same six-bit encoding — and compared:

independent re-entry vs shipped table  : IDENTICAL (64/64)
covers 0..63 exactly once              : PASS
pairs joined by inversion               : 28 of 32
pairs joined by complement              :  4 of 32
pairs joined by neither                 :  0
self-inverse figures                    :  8 -> positions 1 2 27 28 29 30 61 62
complement-pair positions               :    1 2 27 28 29 30 61 62
every self-inverse sits in a cpl pair   : PASS

The two sets coincide exactly, which is the fact the plate claims. The Bayer 8×8 produced by the recurrence is the canonical published matrix (0 32 8 40 2 34 10 42 / 48 16 56 24 …).

Every headline statistic reproduces. I re-derived the luma from #g-src, re-ran both matrices, wrote my own 4×4 window pass and a deliberately different direct O(N⁴) 2-D DFT, and got the plate's numbers to fourteen significant figures:

plate independent bayer dv 100.24482709e-3 100.24482709e-3 bayer ev 1.02343887e-3 1.02343887e-3 bayer low 0.7964652% 0.7964652% kingwen ev 3.65387930e-3 3.65387930e-3 kingwen low 13.2697662% 13.2697662% lit 8616 / 8654 8616 / 8654

The result is real. The King Wen panel bands and blocks; the Bayer panel resolves the subject. The plate is the strongest thing in strata2/ on the evidence.

Real pixels, sampled. Both canvases: exactly one RGB triple 190,242,248, alphas {0, 235}, 0 grey pixels, 0 essence pixels. Computed-style sweep over every node and both pseudo-elements in the host: 0 essence in any of color / background / border / outline / fill / stroke / accent-color, 0 gradients, 0 grey backgrounds, non-zero border-radius in exactly one rule (the circular witness mark). No SVG, no emoji, no ⌗ ≡ ☰ ⋮, no planetary or zodiac glyph, no hexagram glyph — names only.

Departure coverage: settled, not "inconclusive". evidence.md leaves this open. It can be closed. Rendering in the real family name (Departure, not "Departure Mono" — that is why the builder's probe fell back and read inconclusive): the face is confirmed loaded (a control glyph differs from generic monospace), a private-use codepoint renders a 563-ink notdef box, and all 47 distinct characters the plate sets in Departure render real glyphs — zero match the tofu signature, zero render blank. The ASCII rewrite was still the right call, but the open question is now answered.

Reproducible. Canvas SHA-256 identical after a gain round-trip (1.12 → 1.40 → 1.12) and identical again under reducedMotion: reduce; 8,779 lit cells, not blank. No Math.random, no Date.now, no navigator, no geolocation, no getTimezoneOffset, no localStorage. Nothing is personalised to the viewer; the only date input is window.MN_EPHEMERIS / MN_derive.

Ephemeris cross-check. MN_EPHEMERIS.figure = 25 = MN_derive(new Date(date)).hexValue; exactly 2 cells carry data-today, both data-fig="25". The printed line checks out by hand: B8[3][1] + 1 = 29, and figure 25 is lower zhen / upper dui, which is position 17.

No AudioContext is instantiated (constructor present in the browser, never called — the stratum contains no reference to it).

Chrome. 0 console errors across four contexts. No horizontal scroll at 1440 or 390 (scrollWidth 390 = clientWidth 390, zero elements overflowing the host). One control, 908×44, tabIndex 0, :focus-visible = rgb(194,51,61) 1px solid — visible and confirmed in the 390 screenshot. Lowest text contrast on the plate is 5.01:1 (the sigil-text label and the today line), above the 4.5 floor; everything else is 5.21–5.22 or better. destroy() clears 128 cells to 0.

Copy. No name, no judgement, no reading, no correspondence is attached to any figure. The sequence is used as a permutation and said so plainly. It is a procedure with a reason, not a claim about the world, and "Not established" in evidence.md is unusually honest — it names the perceptual claim, the single photograph, the un-built rival orderings, and the fact that the plate assumes §09's address claim rather than testing it. Restraint per CANON-SEED 4 holds: one constraint (same addresses, different numbers), no flourish, no animation, no fourth phase disc.


Findings

1 · Grey fill in real pixels — the gain slider · should-fix · inherited

Law: grey is a lie told by dots; no grey fills, ever. The two canvases are clean, but the control is not. Screenshotting the 908×44 input#kwGain region and counting pixels:

mid-grey pixels        7,316   across 44 distinct grey values
dominant               (59,59,59)  ×3,192   — the unfilled track
                       (133,133,133) ×1,768 — track edge / AA
                       (230,230,230) ×2,275 — accent fill + thumb

That is a solid mid-grey bar, drawn by the UA because the range track is left unstyled and only accent-color is set.

This is inherited, not invented. I measured the shipped gate's own #gGain the same way: same construction, same greys — (59,59,59), (133,133,133), 6,466 mid-grey pixels in 804×44. .eph-scrub input[type=range] is built identically. So the canon ships this today.

Fix, inside this stratum only: style the track and thumb rather than delegating to accent-color — ::-webkit-slider-runnable-track / ::-moz-range-track as background:var(--void) with a 1px solid var(--rule) centre line, and the thumb as a square rgb(var(--witness)) mark. It is the one place on the plate where real pixels contain grey, and the plate's own argument is that a mid-tone must be a density of marks.

2 · The two operator grids do not align row-for-row · should-fix

stratum.css:86 states the intent: "both plates take the same cell height so the same address sits at the same place in both — the point of the comparison is that only the number moved." Measured at 1440, that is false:

bayer   row heights   52.0 ×8                     grid height 425.0
kingwen row heights   53.5 55.7 55.8 55.2
                      54.3 55.9 55.5 53.2         grid height 448.0
vertical offset of the same address, rows 1..8:
                      0.0  1.5  5.2  9.0
                     12.2 14.5 18.4 21.9

min-height:52px is a floor, so the figure-bearing cells grow — and grow unevenly, because each row's height is set by its tallest witness mark. The right-hand plate is a ragged grid where the left is a perfect one, and by the bottom row the same figure sits 22px lower on one side than the other. Visible in rv/rv-mats.png: the sigil today-tick is at a different height in the two plates.

Fix: .kw-grid{grid-auto-rows:56px} and drop min-height from .kw-grid i (or set an explicit height on both grids). One line.

3 · evidence.md misstates its own sweep · should-fix

"The dither residual … is 3.6× to 5.3× more variable under King Wen at every gain" — and the summary line "residual ratio 3.6–5.3×".

Recomputed from evidence's own printed table, and independently from a live sweep of the plate:

gain   0.90  1.00  1.12  1.30  1.50
ratio  4.98  4.69  3.57  3.49  3.56      (mine)
ratio  4.97  4.67  3.58  3.49  3.55      (from evidence's own table)

The true range is 3.49×–4.98×. At gain 1.30 the ratio is 3.49, below the stated floor, so "at every gain" is false as written; and 5.3 appears nowhere. Both ends are rounded outward in the direction that flatters the result, which is the one direction an evidence file must not round. The finding itself is unaffected — 3.5× is still a large separation.

Fix: state 3.5×–5.0×. (The parallel claim "the gap never exceeds +3.7%" is sound: measured max +3.73% at gain 0.90.)

4 · "not a second encoding of it" is false by 19 cells · should-fix

copy.md and the plate: "Source luma is read from the plate above — the same photograph, not a second encoding of it"; stratum.js:228 claims reading #g-src "guarantees both plates are arguing about one image and not two encodings of it."

It is a second encoding. The gate thresholds its float lumaBuf; #g-src holds that luma rounded to 8 bits for display, and readSource() reads the rounded value back. Rendering #kwPanelB and the shipped #g-b8 at the same gain 1.12 and comparing lit/unlit cell by cell:

differing cells   19 of 21,168   (0.090%)

The plate's internal comparison is unaffected — both its panels use the same quantized luma, so the Bayer-vs-King-Wen result stands untouched. What fails is the identity claim with the section above.

Fix: say "read back from the gate's own source plate, at the 8-bit precision that plate stores" and print the 19-cell divergence as a line of evidence — it is a better sentence than the absolute one, and it is true.

5 · The cheapest missing generality test, run — and it favours the plate · should-fix

evidence.md names this as unrun: "A flat or high-key image was not tried, which is the cheapest way the headline result could still be wrong." It is ten lines. I ran it, at gain 1.00, same 168×120 region, same window code:

field                dv bayer   dv kingwen   delta     ev ratio
uniform 0.50          0.000       5.859      —          —
uniform 0.14 (low)    0.732       4.639     +533%      6.33x
uniform 0.86 (high)   0.732       0.732       0%       1.00x
linear ramp          87.423      88.356      +1.1%     7.77x
the photograph       79.578      82.099      +3.2%     4.69x

On a uniform mid-grey field the brief's named statistic — raw 4×4 lit-density variance — separates the two matrices completely: Bayer is exactly 0.000e-3 (every 4×4 window carries identical density, which is what the recurrence is for), King Wen is 5.859e-3. On a low-key flat field it is +533%. So the builder's suspicion in the self-critique is correct and resolvable: the +2.3% near-tie on the photograph is this subject's contrast swamping the statistic, not the statistic failing. The high-key tie is real and worth stating (near saturation both matrices light nearly everything).

Fix: add one flat-field row to the plate or to evidence.md and move this out of "Not established". It converts a self-critique into a result.

6 · A third face in the computed style tree · note

input#kwGain computes font-family: Arial — the UA default for form controls, on the element itself and both pseudo-elements. Law 6 is two faces, no third. The control renders no text, so nothing is visibly wrong, but a style audit of the host returns three families instead of two. The shipped controls have the same. Fix: .kw-ctl input[type=range]{font:inherit}.

7 · Focus offset is 3px where the law says 2px · note

.kw-ctl input[type=range]:focus-visible{outline-offset:3px}. DESIGN.md §15 and §3 both specify "1px sigil outline at 2px offset". The stratum matches the two shipped range inputs (.gate-ctl, .eph-scrub), which are also 3px — so this is inherited precedent standing against the written rule. Fix: 2px here, or amend the document to say ranges take 3px. Either is fine; the silent divergence is not.

8 · "power below tile" includes the tile · note

spectralStats counts r <= 0.125, i.e. radial frequency at or below 1/8 cycles per cell, while the label and evidence.md both say "at scales coarser than the matrix tile." The tile fundamental itself is inside the bucket. The inclusion is conservative — it can only inflate Bayer's share, and Bayer's is 0.80% — so the 16.6× result stands. The label overstates the precision of the measurement. Fix: r < 0.125, or rename to "power at or below the tile".

9 · A raw hex in a stratum that says it uses none · note

stratum.css:1 — "no new hexes, existing token names only". stratum.css:59 sets accent-color:#e6e6e6. It is not a new hex (it is the signal token's value, copied from the shipped gate), but it is a literal where rgb(var(--signal)) exists. Moot if finding 1 is fixed, since the track gets styled explicitly.

10 · Small factual slips in evidence.md · note

11 · Fractional upscale drops below 2× on mobile · note

evidence.md honestly discloses the fractional display factor at 1440. It does not mention that it is also anisotropic (3.051 × 3.055), nor that at 390 the panels render at 1.762× — where a 1-bit pattern aliases hardest and the King Wen banding partly turns into moiré that is the resampler's, not the matrix's. Inherited from the shipped gate; worth one sentence, since the mobile panel is where a reader is most likely to mistake an artefact for the result.

12 · The argument is invisible to assistive technology · note

Both operator grids carry aria-hidden="true", so the 128 numbers that make the plate's point reach no screen reader; the canvases have aria-labels that describe the picture but not the comparison, and #kwToday is the only textual route in. Defensible — the grids restate a claim the prose already makes — but it is a deliberate choice and belongs in evidence.md rather than being silent.


Not established by this review