Round 0 is the ladder's cheapest hard gate: for each candidate, a desk survey
and a demonstrated accessibility route -- a window exposing one node with a
role and a name, read back through the platform adapter. It is first precisely
so that a candidate with no accessibility story costs one round instead of
four.
C1 (egui/eframe 0.35.0) passes on a first-party route: egui-winit's accesskit
feature is literally dep:accesskit_winit, so the chain runs eframe ->
egui-winit -> accesskit_winit -> accesskit_unix and inherits that crate's
window-lifecycle handling. C2 (vello 0.9.0 + winit 0.30.13) passes on the
manual accesskit_winit route the contract names, wired into the same
ApplicationHandler that owns the vello renderer.
C3 (iced 0.14.0) FAILS, and the interesting part is that it first reported as
a pass. The probe registered a hand-built two-node tree through
accesskit_unix::Adapter, which takes no window handle at all -- only handlers
-- and registers with AT-SPI from process identity. It read back cleanly. It
also happened to label its button exactly as iced's own view() labelled a
button, so the transcript looked as though iced had produced it. Deleting iced
from that probe would produce the identical readback. Round 0 asks whether the
CANDIDATE exposes a route; a process-level side channel answers a different
question.
The verdict is recorded with dual attribution, because two distinct things
went wrong. The probe-design defect is the false positive above. The candidate
limitation -- which alone fails the round -- is that iced 0.14 ships no
accessibility integration anywhere (accesskit appears in no iced crate
manifest) and its stock runner hands application code neither the winit
ActiveEventLoop nor a pre-visibility Window, both of which every
accesskit_winit::Adapter constructor requires. That scoping matters and is
deliberate: iced_winit documents a conversion module for users implementing a
custom event loop, so a hand-built shell remains conceivable but unproven, and
would mean owning the shell. Upstream iced #552 is still open.
The evidence file keeps the verifier's factual READBACK: PASS beneath a
ROUND-0 RESULT: FAIL annotation rather than being rewritten. A corrected record
that erases the false positive teaches nobody why it was false.
Two findings carried forward. C1's frame node is unnamed -- its readback path
is application:'probe-egui' / frame:'' / button:'...' where C2 names its frame
-- which is non-disqualifying here but means a screen-reader user hears an
unnamed window, and round 3 must check it. And AT-SPI application registration
is gated behind two settings that are off by default; without both, probes
connect to the bus and enumerate zero applications, which would read as a
candidate failure rather than the environment absence it is.
The spike workspace lives outside the root workspace with its own lockfile;
the root gains one line, exclude = ["spikes"], and nothing else. Round 0's
probes depend on no epiphany crate, which is what makes this round independent
of the pinned source baseline the later rounds need.
Root gate unchanged: fmt clean, clippy -D warnings clean, 1371 tests passed 0
failed. Spike workspace gated separately: fmt clean, all probes build --locked.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RSX4zSLgKvtiXaPjnMqLGz
Pin 7 of the spike contract holds that staff-by-measure dimensions do not pin a
workload, and the workload is what gets tessellated. This is that recipe: one
region, one staff instance per staff, a fixed seven-event bar of six eighths
plus a quarter, and integral per-measure quotas for rests, accidentals, ledger
notes and slurs. Nothing is sampled. The generator still takes a seed, but its
only role is replica minting as in the testkit fixtures -- content is fixed by
construction, which is strictly stronger than a fixed seed over a free
selection algorithm.
Three review rounds, and nearly every finding was the same mistake in a new
place: a claim about the pipeline inferred from an adjacent fact instead of
read from the code that emits.
Ties are not curves. The Curve doc says slurs engrave to one "and ties and
other span curves will follow" -- future tense; constrained.rs has a Slur arm
and no Tie arm, so ties trace as zero-extent anchors. The casting lines first
cited only transform curves produced upstream. Ties are now zero for the
primary ladder, and the decisive reason is not the miscount: every structural
anchor is emitted at the default x, which is the clef column, so casting files
all of them into the first system regardless of musical position. At the top
rung that would pile 2,400 anchors into the most likely damage target and
distort the per-system rebuild timing the whole spike turns on.
The census counts the parser's output, not the source string. H and V each
become a PathCommand::LineTo, so counting M/L/C/Z tokens undercounts exactly
the commands a tessellator consumes -- a sharp is 71, not 57. The census is now
defined as render_data(name).outline.len(), which measures the API candidates
actually see and removes the whole error class.
Ledger lines are emitted per staff step, not per off-staff note. A note one
space outside the staff emits none at all; the first ledger sits two steps out.
So off-staff pitches are pinned by absolute step rather than by "ledger
position", which would have produced about half the ledger ink it implied.
Also corrected: an all-eighths bar cannot yield a quarter rest (rest_glyph maps
value to glyph exactly), 4/4 uses timeSig4 which is single-subpath rather than
the multi-subpath coverage first claimed, barlines are glyphs and not strokes,
and the accidental cadence is one fixed cycle with bounded edge imbalance --
not equal thirds, since no rung's measure count divides by three.
Two-voice engraving is NOT unproven -- rs4_two_voice_counterpoint passes
against the real engraver. The narrower gap is collision handling, rest
displacement and deliberate stem-direction separation, and the ladder stays at
one voice as an isolation choice rather than out of doubt.
The remaining gate is separate and still open: no candidate code exists until
the generator emits its per-rung census, glyph histogram, SHA-256 of each
rung's canonical layout bytes, and the F4 engrave preflight -- and that output
is approved. Recipe approval is not generator approval.
Docs only. No Cargo manifest yet, so the root workspace is untouched; the
exclude line lands with the generator.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RSX4zSLgKvtiXaPjnMqLGz