SONG MAP LAB

D3 · DISPLAY EVOLUTION · APPROVED VARIANT · TIGHTENED 2026-08-26
Today’s pill track asks opacity to answer “where am I” — and inside one section colour opacity has 1.33:1 of headroom to say it with. The evolved display answers that question with position and language instead, and lets brightness do the one job it is good at: spent vs. not-spent. Three bands, three viewing distances, one data model — NOW (read at 2 m), NEXT (caught in the corner of the eye), FORM (read on a deliberate look).

This is the tighten pass, not a new proposal. The evolved variant is approved; fit-to-width is ruled; what changed is that the approach gutter now reads at the distances R1 specifies, the ASH spent token became a per-palette token and reaches every drained surface, and EDIT shows its coexistence with a running clock instead of asserting it. Every arc-minute and every contrast ratio on this page is computed live from the rendered CSS, both palettes — so no claim here can drift from the swatch it describes.
TEMPO 120
SQUINT halves the render to approximate doubling the viewing distance — an approximation only (a CSS pixel is not a device point on your monitor). GRAYSCALE proves nothing here is hue-only.
LEADEVOLVED · PERFORMANCEthree bands: section plate / approach gutter / form ribbon
METRONOME
PERFORMANCE
120
BPM
INTRO
1/2
FRAME
BARS
NEXT ▸ VERSE IN
BAR 1 / 24  
EVOLVED · EDITsame tree, edit density — you edit 7 sections, not 24 bars, and the clock never stops
METRONOME
EDIT
120
BPM
CHORUS▾
6
PAYOFF
BARS IN SECTION
+  INSERT SECTION AFTER
NOW · INTRO 1/2 EDITING · CHORUS
SELECTED SECTION · CHORUS ALL-BARS GRID
The clock does not stop to let you edit. The playhead is live on the same tick as the panel beside it — edit section 4 while the music is in section 2 and the foot reports both. SELECTION is a ring, NOW is a playhead: two marks, never merged. Tap a block to select; the grid below shows only that section’s bars, at 58 pt — above the 44 pt touch floor the shipped 26 pt pill misses.
REFERENCE SHIPPED TODAY — SongMapPillTrack.swift recreated at true point geometry (20 pt pill :170 · 8 pt label :184 · 4 pt gap :85 · 0.38 follow-anchor :123-133), on the same clock as the panels above.
WHAT THE STRIP CANNOT TELL YOU
· which section — only a 3-letter tag at 8 pt (≈3.0 arcmin at 1 m, against a 14–16′ floor)
· how deep into it — there is no bar-of-section count anywhere in the app
· what is coming — bar +1 renders identically to bar +40
· the shape of the tune — 24 bars, ~9 visible, the rest scrolled away
· which bar is spent — a played verse can render 2.04× brighter than the outro you are playing right now

This strip keeps the shipped .past 0.35 opacity. The ASH token deliberately does not reach it — it is evidence of today, not a proposal.

THREE BANDS · THREE DISTANCES

EACH SCALE OWNED EXACTLY ONCE
BAND 1SECTION PLATE
What am I playing, how deep in. Section name 46 px Bebas (cap 32.2 pt ≈ 17.2 arcmin @1 m — above R1’s 16-arcmin floor, below the 20–22 band the BPM numeral owns). Bar-of-section counter 44 px Bebas — lifted from 40: at 40 it sat at 15.0′, under the 16′ minimum R1 §0 sets for a numeric position readout, on a page that cited that minimum. The filament fills across the section, in ASH over a role-coloured trough — the section burns down.
BAND 2APPROACH GUTTER
What is coming, and when. Two-stage telegraph, and in this pass it finally reads: name 38 px Bebas (14.3′), numeral 44 px Bebas (16.5′). v1 shipped 16 px Overpass ≈ 6.0′ — a warning nobody could read. Geometry is constant between idle and armed; arming is a pure luminance onset (measured below), which is R1 §5.2’s top-ranked peripheral channel and removes the layout shift v1 pushed into the ribbon.
BAND 3FORM RIBBON
Where am I in the whole tune. Fits to width — never scrolls (RULED, SYNTHESIS §3.3; D3 §6.1’s fork is closed), so the shape of the song is always visible. Names engraved once per section block, not once per bar. Deliberate-look tier: 8.5 px labels are not claimed to be glanceable, and this card says so rather than letting the reader assume otherwise.
SCALE LAWone scale, one element
beat → the dot lane (already LIT).  bar → the ribbon playhead steps.  section → the plate filament fills.
Nothing renders the same scale twice, so nothing competes for the glance.
ONE-GLOW LAW HELD. LIT #F0A040 appears in exactly two places on this screen: the beat dot, and the BPM numeral at the instant a Song Map tempo actually changes (the readout is alive). The playhead is ink #EAD8B0 and earns its salience by moving — R1 §5.2 ranks motion as an independent peripheral channel, so it does not need to be the brightest thing to be the most noticeable. Anticipation is never LIT — same call the July warm-up mockup made.

THE TELEGRAPH · TWO STAGES

EXTENDS THE BANKED .APPROACHING CURVE — DOES NOT REPLACE IT
BAR n−3
n−2
n−1
n
CHORUS
        quiet —————————— STAGE 1 text · STAGE 2 ramp · catch
STAGE 12 bars out
The gutter numeral lights and counts bars: IN 2 → IN 1. A text channel — no new glow, no second light source. At 120 BPM 4/4 that is 4 seconds of warning; the July mockup’s single-bar ramp gave 2, which is a rhythm-game budget, not a musician’s — you cannot set up a fill in two seconds.
STAGE 2final bar
The numeral drops to beat resolution — 4 · 3 · 2 · 1 — while the banked curve runs verbatim underneath it: opacity = 0.70 + 0.24·t², t = (currentBeat+1)/beatsPerBar, beat-stepped, capped at 0.94, riding the gutter filament and the ribbon’s next block. Two channels agreeing is motion-principle 1; it is not a second light source.
THE FIXre-based, not re-curved
With .future = 0.70 the curve’s beat-1 step is 1.02:1 — invisible. Re-basing .future → 0.45 makes the same curve open at 1.43:1 and close at 1.80:1. The curve was right; its floor was too high.
RESTRAINTunchanged
Ordinary bar→bar transitions inside a section get nothing. Triggers stay exactly as banked: section start, or isTempoChange(at:) (SongMapPillTrack.swift:76-80). Loop wrap adds one: ↺ LOOP IN 1.
DOES THE WARNING READ? — MEASURED LIVE OFF THE RENDERED CSS
elementsizecap@1 m@2 mtier
THE ARM IS A LUMINANCE ONSET, NOT A MOVE
stepfromtoonset
WHY THE SIZES ARE THE FIX. R1 §0 files a numeric position readout under 16′ minimum / 20′ preferred and a secondary label under the 14–16′ automotive floor. v1’s gutter ran the name at 6.0′ and the countdown at 11.3′ — both under their own category’s floor, while the page cited those floors three cards away. Sizes here are the R1 conversion (163 pt/in, Bebas cap ratio 0.70), measured from getComputedStyle at load, so the table cannot drift from the stylesheet. Overpass Mono’s cap ratio is still the 0.70 working estimate — R1 §3’s open item, flagged in every row that uses it.

STATE CHANNELS · MEASURED

WCAG RELATIVE LUMINANCE · EMBER GROUND #0C0C0E (Y=0.0037)
claimtodayproposed
spread across the 23 section colours 32.9×
Y 0.0145 → 0.4777
5 roles
hue-separated
a spent verse vs. the bar you are playing (outro) 2.04× brighter
0.1287 vs 0.0630
impossible
past = one ash token
.future → .playing step inside one hue 1.33:1 n/a — NOW is
position, not brightness
what carries “this bar is now” opacity + ring + ×1.06 scale + shadow
(four decorations doing one job)
the playhead sits on it
+ the plate names it
tap target, narrowest pill 26 pt wide
(minWidth :169) vs 44 pt HIG floor
ribbon is display-only;
editing is 58 pt tiles
WHY 23 COLOURS CANNOT WORK. Hold every section at one luminance so that brightness is free to mean state, and 20 distinct hues must share 360° — the computed worst neighbours land 0.3° apart (verse/one-drop) and 1.0° apart (chorus/trading-4s). Let luminance vary to separate them and it swamps the state channel again, which is exactly what ships today. The constraint is geometric, not aesthetic. So colour stops being the section’s identity and becomes its ROLE; the NAME is the identity, at 46 px.

THE ROLE RAMP · 5, NOT 23

DEFERRED TO 1.2 (SYNTHESIS §3.4) — NEEDS A DEVICE PASS ON ≥2 PALETTES. ASH SHIPS EARLIER, ON ITS OWN, WITH THE PLATE.
rolesectionsY / CR
FRAME intro, outro, out head, rest0.115 / 3.07:1
STATEMENT verse, head, theme, exposition, recap, one drop, riddim0.315 / 6.79:1
LIFT pre-chorus, development, variation0.560 / 11.35:1
PAYOFF chorus, hook, shout chorus0.330 / 7.07:1
DEPARTURE bridge, solo, trading 4s, break, dub0.205 / 4.75:1
ASH every spent bar, whatever its role—
ASH IS A PER-PALETTE TOKEN — COMPUTED LIVE, BOTH GROUNDS
groundashCR vs groundvs 1.53 floor
A CORRECTION, STATED LOUD. D3 §5 reports ASH #32323A at 1.58:1 on the Midnight ground and calls it “above the 1.53 floor.” Recomputed here it is 1.41:1 — below the floor (#32323A Y 0.0325, Midnight ground Y 0.0088; the Ember figure in the same paragraph reproduces exactly, so the formula agrees and the Midnight number does not). The fix is not a new colour family: Midnight gets #2C3A58, lifted within the slate hue until it clears — precisely the move Theme.swift:255-256 already makes for Midnight’s dotNorm/borderDot. D3 §5 predicted “ASH is likely a third [Midnight deviation]”; this is it, measured. Worth noting and not over-reading: a correctly lifted Midnight ash measures 1.58:1 — the exact figure D3 §5 prints beside the wrong hex, which reads like a colour that got separated from its measurement. That is a guess at the provenance, not a finding.

ASH’s rule, so the next palette derives it instead of guessing: sit at the F6 visibility floor (1.53:1, Theme.swift:120-121) — a spent bar is exactly as loud as an unlit beat dot, never louder. On Ember that lands within a rounding step of the shipped dotNorm #34322E.

Separation, CIE76 ΔE across the five roles: normal 33.5 · protanopia 22.9 · deuteranopia 16.8 · tritanopia 5.6 (LIFT/PAYOFF — the one weak pair; they differ by ΔL* 15.4 so grayscale still splits them, and the block name is always engraved beside them). The gold→hot-orange LIFT→PAYOFF pair is the ember DNA doing the semantic work: the tune literally heats up into the chorus.

DENSITY LADDER · THE RIBBON UNDER STRESS

FIT-TO-WIDTH IS RULED (SYNTHESIS §3.3) — IT DEGRADES; A SCROLLER JUST CLIPS. EVERY CASE IS SHOWN PART-PLAYED, SO THE ASH TOKEN IS TESTED AT EVERY DENSITY.
Rules, in order — and there are now four, not three: below 6 px per bar the per-bar rules are dropped and only section blocks survive; below 34 px per block the engraved name elides to its initial; if the full name would be wider than its block it falls back to the 3-letter short form first; below 14 px the name is dropped entirely and the block is colour-only. The width tier is new in this pass — v1 could hand a 10-character name (“PRE-CHORUS”) to a block that passed the 34 px test and clip it. Nothing is ever clipped off-screen: the whole form is always present, which is the property a horizontally scrolling strip structurally cannot have.

ASH SURVIVES THE SQUEEZE. Each ladder row is played to roughly a third, so the spent run renders in the live --ash token even at ~4.5 px per bar — the case where per-bar rules are gone and only the ash/role boundary and the playhead say where you are. Toggle GRAYSCALE: the boundary holds without hue, which is the whole point of the token.

UNMAPPED IS NOT GREY MUSH. A fresh map is 8 bars of .empty (SongMapModel.swift:23) and renders today as eight identical blank chiclets — a poor first impression of the flagship practice surface. Unmapped renders instead as an honest amber bar ruler: numbered, playable, obviously un-authored.
Every colour literal is shipped code — Ember Theme.swift:122-146, Midnight :246-270, legacy section hues SongMapSection.swift:110-136. The reference strip's geometry is SongMapPillTrack.swift:85, 153, 163, 169-170, 184, 204-238.
Type sizes are annotated with cap-height and arc-minute subtense per R1-glanceability.md (163 pt/in, Bebas cap ratio 0.70; the Overpass Mono ratio is still unverified — R1 §3). The luminance arithmetic is WCAG relative luminance computed on the shipped hexes; the alpha compositing is done in linear light and is approximate for the on-device blend.
Every arc-minute and every ratio on this page is computed in-page from the rendered CSS, on whichever palette is active — the tables cannot drift from the stylesheet they describe. That is also how D3 §5’s Midnight ASH figure was caught. Still unmeasured: none of this has been seen at 0.7–2 m on a real device in practice-room light, and SQUINT is an approximation, not a measurement.