The bounded half already asserts that its total is conserved; the centered half
did not. It pins the three states as separate, and separateness is not
completeness: a group with a ring nobody anticipated could fall out of placed,
centre and unplaced alike and simply not exist on the page.
That is the failure that looks like a smaller dataset rather than an error. An
empty chart gets noticed; a chart quietly missing eleven groups does not, and
eleven is exactly how many carry ring -1 today.
Checked with the awkward values rather than the tidy ones: out of range high and
low, missing, fractional, and a numeric string. All seven are accounted for, the
out-of-range pair land in unplaced rather than being clamped to ring 1 or 10,
and a fractional ring is unplaced rather than rounded onto a neighbour it does
not belong to.
Falsified before trusting it: a filter that silently drops non-integer rings
turns the arm red at 5 of 7. The negative control also pins that the count moves
with the input, so it is not counting a constant.
gospel's boundedSet and centeredSet move to library/components/charts, and every
importer follows in the same commit: gospel-internet's two components and the
gate. One implementation, no window in which someone edits the wrong copy.
gospel chose this over copying, for the reason the shared scatter already showed.
Two print-safe components come with them, adapted from gospel's rather than
written fresh: theirs fetch and hold board state, these take joined groups and
every label as props, so the packet and the board draw the same figure from the
same code. gospel's already inked their tallies and used no hover, so the
adaptation is the data path and the labels, not the drawing.
The labels had to change, and not for style: gospel's read T('gospelInternet.…'),
and a gospelInternet-namespaced key cannot resolve for another area's consumer,
so the packet would have rendered the English fallback in every locale. Labels
are props now.
Three invariants gospel gave me that the comments did not carry are pinned here:
No seed prop. Position is hashed from the group's own id, so a group lands in the
same place on every render, forever — two printings of one packet are identical
and a reader comparing sheets sees real movement rather than reshuffling. I had
specced a seed; it would have silently rescrambled every dot while the data was
unchanged, which on paper cannot be corrected. Asserted through the parser, and
adding one back turns it red.
Every ring is listed, empty ones included. Under a filter several of the nine go
to zero, and a decoder that omits its empty rows stops being a decoder: a reader
cannot tell "none here" from "this ring does not exist".
And an arrow is a claim about a relationship, so a group with no recorded
neighbour gets none rather than a default one toward the centre.
The unknown-is-counted rule is now in the module header where the next reader
meets it first, at gospel's request: a printed page has no hover, so a category
the reader cannot see is one they will assume is zero.
The check itself shipped with the plugin modules it covers, but the line that
runs it existed only in a message, which is not a place code lives. So it sat in
internal/ and would never have run — the same failure it was written to prevent,
one layer up.
This is the whole registration in one commit: the file, the script entry and the
word in the gates:pre list. It imports boundedSet.js and centeredSet.js, so it
must land after the plugin commits that add them; landing it first fails
collection and takes gates:pre red for everyone.