Everything each face draws is a function of one monotone number. If switching faces restarted anything, BEATS SINCE ARM would jump and CLOCK REBASES would climb. Switch faces mid-bar and watch neither move.
And check that the needle can move at all. A counter wired to code that cannot
increment it proves nothing, so the console carries its own negative control: set
BAD FACE · RE-ARMS and switch. That branch does exactly what a face with an
.onAppear { engine.restart() } would do — the row turns red, the ledger says
CLOCK RESTARTED, and the bar visibly snaps back to 1. Set it back to CLEAN SWITCH
and the counter stops climbing. That is the proof; the zero on its own was a
thermometer with no mercury.
852 pt screen − 59 pt safe area − 83 pt tab bar = 710 pt. SPENT is the sum of the
active face's non-spacer children and their margins, read from offsetHeight —
a layout value, which a CSS transform does not affect, so this is true points at any stage
scale. The spacer's own measured height is printed beside the slack as an independent
cross-check; the two agree within integer rounding. Under 710 means the SE-class latent
scroll (MetronomeView.swift:106-122) never actually fires.
Measured, all four states fit: CLASSIC 558 at 4/4 · RADIAL 681 · RADIAL + MAP 683 · STAGE 517. Note what that ranking says — the radial face is the tight one either way, and D1's published estimate of "~42 pt slack" does not survive adding a session pill. Dynamic Type on the gauge captions has to be capped or the dial shrinks first.
Move the METER and watch CLASSIC's number move. It did not, until 2026-08-30: this
face drew every meter as one row, so it read 558 pt at 3, 4, 5, 6 and 7 alike — insensitive
to the one variable that has historically broken this layout. The shipping row splits above
six beats (LEDBeatRow.swift:66), so 7/4 now costs a second row — 625 pt,
85 pt of slack — and 6/8 draws the compound group divider instead of hairlines (still
558: an absolutely-positioned divider is free). A budget that cannot go up is not a
budget.
DERIVED solves each token's lightness by bisection until its WCAG ratio against
#0C0C0E equals the ratio the shipping Ember hex actually measures — the
targets are read off Theme.swift:123-139 at load, never typed.
Same SOLVER as accent-lab.html:1205-1227; not the same constants, and
saying "same law" was too loose. Shared: bisect lightness at a fixed saturation until
the ratio against the ground hits a target. Different: this page reads its targets off
Ember rather than typing them, and it picks its own per-role saturations — six of nine
differ from accent-lab's bright-field set (ink 8→42, dimText 30→60, ghost 12→60, borderDot
32→8, lit 72→85, deep 68→76). The one that could have mattered is burnt:
accent-lab's Studio family anchors underlay to the armed colour
(accent-lab.html:1214), which this page does not — but its Console
family, the dark one, solves underlay against the ground exactly as here
(accent-lab.html:1220, S 55 to this page's 60, target 3.00 against Ember's
measured 3.03). On this field the two agree in shape and very nearly in number.
ARM-STEP is the luminance jump from resting accent to armed — the thing that makes a beat read as an onset rather than a hue. TIER SPLIT is ember-vs-cream: if it collapses, the once-per-bar law loses its second tier.
The two ΔE rows are the only ones that can fail, and that is the point. Every WCAG
row above is algebraically pinned — each token is solved to its own ratio against the
same ground, so ratio(lit,amber) ≡ ratio(lit,bg)/ratio(amber,bg) and the
ACTIVE column reproduces the EMBER column at every hue on the wheel. What the solver does
not preserve is chromatic separation: two tokens can both hit their target luminance
and still slide onto each other in colour. The radial face reads by exactly that — played
versus unplayed is a burnt/cold area code, and the once-per-bar law's second tier is
lit/ink. Both are measured in ΔE*ab, and their floor is 80 % of what Ember
itself achieves for the same pair, so the floor is derived too. Drag the hue slider: the
ΔE rows move and can go under; the seven above them will not budge.
AND THE DEFAULT HUE IS UNDER BOTH FLOORS — DELIBERATELY, BECAUSE THAT IS
THE FINDING. Swept all 360 hues on this page: Ember measures 42.2 area and
45.9 tier, and 217 of 360 hues clear 80 % of that. The ones that do not are a
single contiguous arc — H 161°–257°, cyan through blue into indigo — bottoming at
26.9 at H 185° (area, −36 %) and 29.9 at H 181° (tier, −35 %). That arc is
where accent-lab.html's recommendation lives. The luminance law says Cyan is
legal and it is right; this says a cyan family will read the played-vs-unplayed ring and the
ember-vs-cream tier noticeably flatter than Ember does, and that cost is invisible to
every contrast row on this table. It is not a veto — it is a number to price, and it is
exactly the sort of thing an instrument with a pinned needle would never have shown.
θ′ = cappt ÷ (1.8667 × dm), from R1 §2.2's h ≈ d·θ·0.0002909 and §1's
163 pt/in. Cap ratio 0.700 for Bebas and Overpass Mono alike, measured from the bundled
TTFs (glance-lab.html:1553-1557). R1 §7.1 puts the BPM numeral in the
20–22′ optimal band at its working distance; secondary text may sit at the 14–16′ floor.
Switch to CLASSIC and read the top row. Bebas 72 is a 50.4 pt cap — 27.0′ at 1 m and 13.5′ at 2 m. At a music stand the default face is comfortably readable; at 2 m it is under R1's 14′ automotive floor, never mind the 20–22′ band its own category calls for. That single number is the entire argument for alternate faces — not that CLASSIC is wrong, but that it is a 1 m instrument, and STAGE (24.7′ at 2 m) is the same metronome answering a different room.
WHAT THE RADIAL FACE ADDS TO D1. glance-lab's D1 is a display; a face has to be operable. Three additions, each with its cost stated:
· Transport that respects the ring. D1 has no tempo control anywhere — the numeral inside the ring is a readout with nothing to drive it. This adds one 74 pt bar with three zones (− / TAP / +) beside a 74 pt ignition. All zones clear the 44 pt touch floor. The ignition is 74, not CLASSIC's 92, because the ring already carries "alive"; two 92 pt bloom sources would double the sacred hex on one screen.
· Song Map coexistence. The approved three-band display
(songmap-lab.html:476-527) compressed to sit under a dial. Costs,
stated: the dial drops from 344 to 252 pt (ring Ø 318 → 232, pips Ø 240 → 182), the BPM numeral
from 118 to 106 (cap 74.2 pt — 19.9′ at 2 m against the full face's 22.1′, i.e. the bottom
edge of R1 §7.1's 20-22′ optimal band rather than inside it), the BEAT gauge is dropped (the ring
shows beat at 100 % duty and the plate shows section position), the "BARS IN SECTION" caption is
displaced by the global bar count, and the tempo NAME loses its seat entirely — it is the
only readout on the face that is a description rather than a measurement, and a map whose sections
carry their own tempos arguably should not show one global name anyway. The NEXT gutter falls to
Bebas 34 — 23.8 pt cap, 12.7′ at 1 m, below R1's 14′ label floor; it clears 16′ only at
0.7 m (18.2′). So with the map on, the NEXT band is a 0.7 m object. That is the honest price and
the owner should price it.
· Session pill — four states where the shipping one has two. The flagship is
already on the performance face: MetronomeView.swift:180 renders a pill
whenever session.role != .none, and CLASSIC on this page now draws that exact
object (MetronomeView.swift:792-811) rather than pretending the slot is empty —
an earlier cut of this page omitted it, which made switching away from a live session look
like the session had been destroyed. What the radial pill adds is vocabulary: today's
has a host state and a guest state and no way at all to say RECONNECTING, because
role stays .guest right through a reconnect. Set the console to
RECONNECT and switch between faces to see it: the radial pill dims and pulses on the bar
phase; the shipping pill reads a steady, confident "Guest" while the link is flapping. That
phase is legible today only as a row in a Settings list
(FollowingHostBanner.swift:34-43) — nowhere you can see it while playing.
Geometry is constant across SOLO / HOSTING / FOLLOWING / RECONNECTING, so joining a session is
a luminance onset (R1 §5.2's top-ranked peripheral channel), not a reflow. The peer-count
string and the SF-Symbol stand-in glyphs are invented for the mock [unverified].
WHAT STAGE GIVES UP, ON PURPOSE. No subdivision channel, no gauges, no map. At 2 m in the corner of the eye a 240 pt pip ring running at 8 Hz is texture, not information (R1 §5.1: acuity collapses fast with eccentricity). What is left is a 334 pt ring, the largest numeral on the page, a bar figure and an ignition — 193 pt of measured slack, spent on air rather than on readouts, which is the whole reason it reads from across a room. Its numeral clears 24.7′ at 2 m, above R1's optimal band; CLASSIC's clears 13.5′.
WHAT THIS PAGE DOES NOT SETTLE. Whether faces are per-user or per-context (a Song Map probably wants RADIAL; a live gig probably wants STAGE); whether the switcher lives in Settings beside the theme cards or as a long-press on the numeral; and the lit-law tension named in the header — the beat channel fires the ember once per bar, but the sweep, filament and ignition are continuously lit. All three are owner calls, not research questions. One new one, opened by this round: CLASSIC now shows the shipping two-state pill and the other two faces show a four-state one, which is honest but is also two capsules for one idea. Either the shipping pill grows the two missing phases and every face wears it, or the new pill lands everywhere and CLASSIC stops being "today's screen, unchanged". That is a call, and it is cheap either way.
Sources touched: Theme.swift:123-139 ·
LEDBeatRow.swift:66, 98, 99-105, 117-121, 140-159, 292-294, 324-336, 338-347 ·
TransportControls.swift:6-10, 85-88, 291-293 ·
MetronomeView.swift:106-125, 175-183, 376-384, 792-811 · SettingsView.swift:452-538 ·
ModelsMetronomeState.swift:29-43 · FollowingHostBanner.swift:27-56 ·
glance-lab.html (D1) · songmap-lab.html (three-band display, SONG at :894-900) ·
accent-lab.html:1205-1227 (deriveFamily) · R1-glanceability.md §1, §2.2, §2.3, §5.1, §5.2, §7.