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 40 px Bebas.
Hairline filament fills across the section.
BAND 2APPROACH GUTTER
What is coming, and when. Two-stage telegraph. Dim “NEXT ▸ X”
always; the numeral and the ramp only inside the last two bars. Luminance + motion,
never hue (R1 §5.2 — abrupt colour change alone does not capture attention).
BAND 3FORM RIBBON
Where am I in the whole tune. Fits to width — never scrolls,
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.
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
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.
STATE CHANNELS · MEASURED
WCAG RELATIVE LUMINANCE · EMBER GROUND #0C0C0E (Y=0.0037)
| claim | today | proposed |
| 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
PROPOSAL — REQUIRES OWNER RULING; TOUCHES SectionType.color SEMANTICS
| role | sections | Y / CR |
| FRAME |
intro, outro, out head, rest | 0.115 / 3.07:1 |
| STATEMENT |
verse, head, theme, exposition, recap, one drop, riddim | 0.315 / 6.79:1 |
| LIFT |
pre-chorus, development, variation | 0.560 / 11.35:1 |
| PAYOFF |
chorus, hook, shout chorus | 0.330 / 7.07:1 |
| DEPARTURE |
bridge, solo, trading 4s, break, dub | 0.205 / 4.75:1 |
| ASH |
every spent bar, whatever its role | 0.033 / 1.54:1 |
Separation, CIE76 ΔE across the five: 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).
ASH clears the F6 visibility floor at 1.54:1 (Theme.swift:120-121) and sits ΔE 20.7 from the
nearest role. 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 DEGRADES; A SCROLLER JUST CLIPS
Rules, in order: 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;
below 14 px the name is dropped entirely and the block is colour-only. Nothing is
ever clipped off-screen — the whole form is always present, which is the property a horizontally
scrolling strip structurally cannot have.
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.