# Piano Chords & Scales — exercise URL params (for AI agents)

> Machine-readable reference for building practice-exercise URLs on pianokitty.com.
> Generated from the live option schema (OPTIONS_VERSION 41). Also
> available as JSON at /help/params.json and as worked examples at /help/recipes.md.

## How to build an exercise URL

Exercises are configured entirely by URL query params on /app.
Rules:
- Base path: /app. Append params as a query string, e.g. /app?subject=scales&voicing=triads.
- Omit any param to accept its default (defaults listed per param below).
- Arrays (inv, ct, bn) are comma-separated: inv=0,1,2 — ct=0,1 — bn=0,2,4.
- Booleans are true / false: reqinv=false.
- URL-encode special characters in values (e.g. key=F# becomes key=F%23, a space becomes %20 or +).
- Most user preferences (theme, sound, ear/jumper tuning, etc.) are set on
  /preferences and cannot be driven from a URL. EXCEPTION: a small whitelist of
  display prefs — staff/notation, root highlight, fingering (see "URL-overridable
  preferences" below) — CAN be set in the URL. Those overrides apply only to that
  visit and are NOT saved, so a shared link never changes the visitor's prefs.

## Parameters

### `activity` — activity

What the user does in the exercise: piano (live MIDI play), naming (multiple-choice naming), ear (audio recognition), jumper (metronome rhythm game).

- Values: piano, naming, ear, jumper
- Default: `"piano"`

### `subject` — subject

Musical subject the exercise draws from. Tab order (chords → scales → progressions → songs → melodies) drives the /preferences segmented control.

- Values: chords, scales, progressions, songs, melodies
- Default: `"chords"`

### `key` — key

Root key of the exercise. 12 chromatic spellings + 5 flat enharmonics (Db, Eb, Gb, Ab, Bb) for scale tonics that read more naturally with flats.

- Values: C, C#, D, D#, E, F, F#, G, G#, A, A#, B, Db, Eb, Gb, Ab, Bb
- Default: `"C"`

### `scale` — scale

Scale type built off the root key.

- Values: major, minor, harmonicMinor, melodicMinor
- Default: `"major"`

### `inv` — inversions

Inversion indices to drill, comma-separated in the URL (e.g. inv=0,1,2).

- Values: array of 0–3 (comma-separated in URL)
- Default: `[0]`

### `voicing` — voicing

Chord voicing. singleNotes / triads / sevenths apply to both chord-pool and scales subjects; ninths is scales-only and drills the diatonic 9th on each degree (not available for harmonicMinor or the non-diatonic scales). intM2..int8 are 2-note interval voicings (scales subject only): the scale degree is the lower note, the named interval is added above. progression is reserved for the deferred Roman/Arabic custom progression mode.

- Values: singleNotes, triads, sevenths, ninths, progression, intM2, int2, intM3, int3, int4, intTT, int5, intM6, int6, intM7, int7, int8
- Default: `"triads"`

### `hand` — hand

Which hand the exercise practices. "any" accepts either one-hand or two-hand play (single chord or chord doubled across octaves).

- Values: any, left, right, both
- Default: `"right"`

### `lplay` — leftPlays

When hand="both", what the left hand plays alongside the right-hand chord: "same" = the chord doubled, "root" = only the chord root note (inversion-agnostic), "lowest" = only the bass (inversion-aware — in root position root IS the lowest), "octaves" = the root note doubled (two roots one octave apart), "fifths" = root + perfect fifth (two notes), "nothing" = only the right hand grades. Ignored unless hand="both".

- Values: same, root, lowest, octaves, fifths, nothing
- Default: `"same"`

### `tempo` — tempo

Metronome tempo in beats per minute.

- Values: 20 – 300
- Default: `60`

### `meter` — metrum

Time signature driving the metronome strong-beat accent. 4/4 and 3/4 accent beat 1; 6/8 is compound — accent on beat 1 (strong) and beat 4 (medium). Subject-invariant (all tabs), like tempo.

- Values: 4/4, 3/4, 6/8
- Default: `"4/4"`

### `acc` — accidentals

Black-key spelling: always sharps, always flats, random per render, or auto-derived from the current scale.

- Values: sharp, flat, random, auto
- Default: `"auto"`

### `reqinv` — requireInversion

When true, the MIDI evaluator demands the played chord to match the asked inversion (bass note matters); when false, any inversion with the same notes counts as a hit.

- Values: true / false
- Default: `true`

### `ct` — chordTypes

Quality ids the random-draw pool may pick from (e.g. 0 = major triad, 7 = m7, 49 = single note). At least one must be selected.

- Values: array of 0–67 (comma-separated in URL)
- Default: `[0]`

### `bn` — baseNotes

Pitch classes (0..11, C=0) allowed as chord roots when drawing. At least one must remain selected.

- Values: array of 0–11 (comma-separated in URL)
- Default: `[0,2,4,5,7,9,11]`

### `nj` — ninjaMode

Ninja mode (chords subject, piano activity): the user picks the exact voicing per hand via the ninjaLeft/ninjaRight step sets. While on, chordTypes / inversions / hand are ignored by grading and by the exercise identity, the question label shows only the root, and the keyboard hint marks both hands.

- Values: true / false
- Default: `false`

### `njl` — ninjaLeft

Ninja mode: left-hand steps as semitone offsets from the right-hand root (members of NINJA_STEPS in $lib/music/chord/ninja; 0=root … 21=13th). The left hand sounds two octaves below the right root, so its steps can never collide with right-hand pitches.

- Values: array of 0–21 (comma-separated in URL)
- Default: `[]`

### `njr` — ninjaRight

Ninja mode: right-hand steps as semitone offsets from the root (members of NINJA_STEPS). At least one step across both hands must stay selected; the UI enforces it and the engine falls back to a bare root.

- Values: array of 0–21 (comma-separated in URL)
- Default: `[0]`

### `sv` — specificVoicing

Specific Voicing (all subjects, piano activity): the user assigns generic chord parts (root/3rd/5th/7th/9th/11th/13th) to each hand and the drawn chord decides the exact notes. While on, hand / leftPlays / requireInversion / rootless / one-note are bypassed by grading; a part the drawn chord lacks is skipped, a part ticked in neither hand is omitted, and a chord with no assignable part grades as a normal full chord. Ninja mode wins over this in the chords subject.

- Values: true / false
- Default: `false`

### `svl` — specificVoicingLeft

Specific Voicing: parts the LEFT hand plays, as degree numbers (1=root, 3, 5, 7, 9, 11, 13 — see VOICING_PARTS in $lib/music/chord/specific-voicing). The hint places them two octaves below the right-hand root; grading is octave-free.

- Values: array of string (comma-separated in URL)
- Default: `[1]`

### `svr` — specificVoicingRight

Specific Voicing: parts the RIGHT hand plays, as degree numbers. At least one part across both hands must stay selected; the UI enforces it and an empty pair falls back to normal grading.

- Values: array of string (comma-separated in URL)
- Default: `[3,5]`

### `seq` — sequence

Draw order. `random` picks a random pick each draw (scales subject: natural degree order, random inversion per cell); `inOrder` walks the pool in order; `inOrderUpDown` / `inOrderUpDownPeak` walk up then back down (scales subject); `allInversions` drills every inversion of each degree (scales subject). `harmonized` is reserved and falls back to inOrder. `circleFifthsCW`/`circleFifthsCCW`/`chromaticUp`/`chromaticDown` are RETIRED (still parsed for old links/data): they normalize to `inOrder` + the matching `scaleAfterRepeats` direction.

- Values: random, inOrder, harmonized, allInversions, inOrderUpDown, inOrderUpDownPeak, circleFifthsCW, circleFifthsCCW, chromaticUp, chromaticDown
- Default: `"random"`

### `mode` — scaleType

Scale mode for subject=scales — slug from the 20-mode V1 taxonomy (major..chromatic). Non-diatonic slugs (pentatonic*, blues, bebop*, chromatic) force voicing=singleNotes.

- Values: major, naturalMinor, harmonicMinor, melodicMinor, ionian, dorian, phrygian, lydian, mixolydian, aeolian, locrian, tetrachordsMajor, tetrachordsMinor, pentascaleMajor, pentascaleMinor, pentatonicMajor, pentatonicMinor, blues, bebopDominant, bebopMajor, bebopMinor, chromatic
- Default: `"major"`

### `repeat` — scaleRepeat

For subject=scales, sequence=inOrder. `end` appends the tonic after the last degree before looping (resolves the scale). `top` (deferred, used by inOrderUpDown) repeats the peak before descent. `both` does both. `none` plays the bare degree list.

- Values: none, end, top, both
- Default: `"end"`

### `sloop` — scaleLoopCount

Scales subject, sequence=allInversions only: play each degree's inversion run N times before moving to the next degree (per-degree loop). Absent/1 = one run per degree. 1..100. Optional so it stays absent on the vast majority of exercises. (Passes before the "Then" action are `scaleAfterRepeatsCount`.)

- Values: string
- Default: `undefined`

### `sloopok` — scaleLoopFlawlessOnly

Legacy (scales subject): the old all-keys "only flawless" flag. Migrated into `scaleAfterRepeatsFlawlessOnly` when it arrives with a retired all-keys sequence; otherwise ignored.

- Values: string
- Default: `undefined`

### `safter` — scaleAfterRepeats

Scales subject only: what happens after `scaleAfterRepeatsCount` completed passes through the scale (any sequence). done = mark the exercise as done (records a completion, once per session); cofCW = next key around the circle of fifths clockwise (C→G→D→…); cofCCW = counter-clockwise (C→F→Bb→…); chromaticUp = semitone up; chromaticDown = semitone down. Independent of the progressions `advanceAfterRepeats*` fields.

- Values: done, cofCW, cofCCW, chromaticUp, chromaticDown
- Default: `"done"`

### `safn` — scaleAfterRepeatsCount

Scales subject only: how many completed passes trigger `scaleAfterRepeats`. 1..99. Default 1.

- Values: 1 – 99
- Default: `1`

### `safok` — scaleAfterRepeatsFlawlessOnly

Scales subject only: when true (default), only FLAWLESS passes count toward `scaleAfterRepeatsCount` and a flawed pass restarts the count (N clean passes in a row). When false, every completed pass counts.

- Values: true / false
- Default: `true`

### `prog` — progression

Verbatim Roman/Arabic numeral progression text (e.g. "I vi IV V" or "1.2 5 6.2 4") for subject=progressions. Empty value triggers auto-seed to "ii V I" when subject flips to progressions. Per-chord dot-notation inversion overrides the global inversions option.

- Values: string
- Default: `""`

### `spec` — specific

Verbatim chord-name list (e.g. "D, G, C" or "Am.1 Cmaj7 F#m7b5/A C/E") for subject=songs. Tokens split on whitespace, comma, or colon. Each token accepts dot-inversion (e.g. "Cm7.2") and slash-bass (e.g. "C/E"). The `|` token marks a bar boundary and is preserved in the parsed list. Reused by the V1-port chord-text parser (`parseSpecific`).

- Values: string
- Default: `"D, G, C"`

### `spsn` — songsSingleNotes

Songs subject only: when true, each parsed chord in `specific` is graded as a single-note exercise on its root (quality=49, inversion=0). Lets a "C F.2 G" string drive a C → F → G single-note drill instead of full chord voicings. Ignored for other subjects.

- Values: true / false
- Default: `false`

### `sui` — songsUseInversions

Songs subject only: when true, the chords from `specific` keep their root + quality but IGNORE any per-token inversion (C/E, C.2 → C) and are instead drilled through the selected `inversions` set. The play pool = {parsed chords} × {selected inversions} (a pair is skipped when the inversion exceeds the chord quality’s max), walked per `sequence` (random = uniform draw; in-sequence = inversion-major: all chords at the lowest inversion, then the next). Overridden by `songsSingleNotes`. Ignored for other subjects.

- Values: true / false
- Default: `false`

### `title` — songTitle

Songs subject only: optional display title ("Artist - Song") carried from /songs or /editor via the ?title= param. Shown on the ExID card in place of the chord list. Display-only — deliberately excluded from the canonical exid hash (tab-fields) so it never changes an exercise’s identity, pet-name, or difficulty; it rides only in the ?d= slug. Optional so it stays absent (not "") on the vast majority of exercises.

- Values: string
- Default: `undefined`

### `advrep` — advanceAfterRepeats

Progressions subject only: when true, after `advanceAfterRepeatsCount` loops the exercise key auto-advances in the direction given by `advanceAfterRepeatsDirection`. When false ("Mark as done"), the same threshold records the exercise as done once per session instead. Ignored for other subjects.

- Values: true / false
- Default: `false`

### `advrepn` — advanceAfterRepeatsCount

Progressions subject only: how many loops trigger the "Then" action (key advance when `advanceAfterRepeats` is true, mark as done when false). 1..99. Default 3.

- Values: 1 – 99
- Default: `3`

### `advrepd` — advanceAfterRepeatsDirection

Progressions subject only: how the key advances when the repeats threshold is hit. cofCW = circle of fifths clockwise (C→G→D→A→…); cofCCW = counter-clockwise (C→F→Bb→…); chromaticUp = semitone up (C→C#→D→…); chromaticDown = semitone down. Reuses the existing nextCofKey / nextChromaticKey helpers.

- Values: cofCW, cofCCW, chromaticUp, chromaticDown
- Default: `"cofCW"`

### `advrepok` — advanceAfterRepeatsFlawlessOnly

Progressions subject only: when true, only FLAWLESS loops count toward advanceAfterRepeatsCount — any miss resets the flawless progress, so the "Then" action fires after N clean loops in a row. Absent/false (default) = every completed loop counts regardless of mistakes. Optional so it stays absent on the vast majority of exercises.

- Values: string
- Default: `undefined`

### `ptempo` — progressiveTempo

When true, the metronome climbs ("tempo stepping") from progressiveTempoFrom to progressiveTempoTo in progressiveTempoStep increments, playing the current material progressiveTempoAfter clean repeats per rung before bumping up. On reaching the top rung it triggers the mode's natural advance (next key / scale) or a goal board (chords / songs) and resets to the bottom. Subject-invariant (all tabs). While on, it becomes the authority for rep-counting and advancement, suppressing advanceAfterRepeats / scaleLoopCount auto-advance.

- Values: true / false
- Default: `false`

### `ptfrom` — progressiveTempoFrom

Progressive tempo: starting BPM (bottom rung of the ladder). 20..300.

- Values: 20 – 300
- Default: `60`

### `ptto` — progressiveTempoTo

Progressive tempo: target BPM (top rung, always played after the last full step). 20..300.

- Values: 20 – 300
- Default: `200`

### `ptstep` — progressiveTempoStep

Progressive tempo: BPM increment between rungs. 1..200. Default 20.

- Values: 1 – 200
- Default: `20`

### `ptafter` — progressiveTempoAfter

Progressive tempo: how many clean (flawless) repeats to play at each rung before bumping up. 1..99. Default 1.

- Values: 1 – 99
- Default: `1`

### `ptdec` — progressiveTempoDecreaseOnFailure

Progressive tempo: when a run of clean repeats is broken by a wrong chord, drop one rung (tempo -= step, floored at from) and restart the rep count for that rung. When false, a break only restarts the rep count without dropping the tempo. Default true.

- Values: true / false
- Default: `true`

### `qls` — questionLabelStyle

What the /app question label shows when subject is "scales" or "progressions". `chord` (default) keeps the chord long-name. `roman` shows the scale-degree Roman numeral ("ii", "V", "vi"; with a dot-inversion suffix like "V.1" when colorfulInversions is OFF). `romanQuality` adds the chord-quality glyph (UPPER for major, lower for minor, ° dim, + aug) when subject=scales AND the voicing is diatonic triads/sevenths; silently degrades to plain `roman` for progressions, single-note voicing, intervals, and non-diatonic scales. `arabic` shows the bare degree number. `function` shows the locale-aware function name. V1 had this as display-1/2/3 multi-checkboxes; V2 collapses them into one exclusive picker.

- Values: chord, roman, romanQuality, arabic, function
- Default: `"chord"`

### `mmode` — melodyMode

Melodies subject (/repeat): the level ladder, or the custom fields below. Ladder exercises ignore the other melody* fields.

- Values: levels, custom
- Default: `"levels"`

### `mkey` — melodyKey

Melodies custom mode: root key of the note pool. Own field, so a melody link never overwrites the Scales/Progressions key.

- Values: C, C#, D, D#, E, F, F#, G, G#, A, A#, B, Db, Eb, Gb, Ab, Bb
- Default: `"C"`

### `mscale` — melodyScale

Melodies custom mode: scale of the note pool.

- Values: major, naturalMinor, pentatonicMajor, pentatonicMinor, blues, harmonicMinor, melodicMinor, dorian, mixolydian, lydian, phrygianDominant
- Default: `"major"`

### `mrange` — melodyRange

Melodies custom mode: note-pool width.

- Values: five, octave, two-octaves
- Default: `"five"`

### `mleap` — melodyLeap

Melodies custom mode: largest leap in scale steps, 1..7, or 999 for no limit.

- Values: -9007199254740991 – 9007199254740991
- Default: `4`

### `mear` — melodyEar

Melodies custom mode: play by ear, hide the keys the app plays (doubles points).

- Values: true / false
- Default: `false`

## URL-overridable preferences (user options)

These normally live on /preferences. When set in an /app URL they override the
display for that visit only — they are NOT saved and never change the visitor's
stored preferences. Use them to bake a display choice (e.g. staff notation) into
a shareable drill link.

### `nd` — notationDisplay

How the question is rendered on /app. `chordNames` (default) shows the chord label (e.g. "Am7"); `staff` shows the notes on a music staff (VexFlow renderer). Toggled with the N keyboard shortcut. When `staff`, see the staff* sub-options.

- Values: chordNames, staff
- Default: `"chordNames"`

### `smel` — staffMelodic

Staff layout when notationDisplay=staff. `off` = harmonic (notes stacked vertically as a chord); `asc`/`desc`/`random` = melodic (chord split into single notes laid out left-to-right in the chosen order).

- Values: off, asc, desc, random
- Default: `"off"`

### `swhole` — staffWholeNotes

Staff notes rendered as quarter notes (default — filled noteheads with stems hidden) vs whole notes (open noteheads).

- Values: true / false
- Default: `false`

### `sroot` — staffHighlightRoot

When notationDisplay=staff, paint the root note of each chord in orange so the bass note stands out.

- Values: true / false
- Default: `false`

### `soct` — staffOctaveShift

Octave shift for staff rendering: -1 / 0 / +1. Default 0 anchors at middle C.

- Values: string
- Default: `0`

### `sclef` — staffShowClef

Show the clef glyph and the key-signature accidentals at the staff head. When on, per-note accidentals that the signature already covers are stripped, so diatonic chords read clean. Default ON — the per-key spelling pipeline relies on the user being able to see which signature they are in.

- Values: true / false
- Default: `true`

### `clef` — staffClef

Clef used for staff rendering. `auto` derives from the active hand (left → bass, right or both/any → treble). `treble` and `bass` force the choice.

- Values: auto, treble, bass
- Default: `"auto"`

### `hlroot` — highlightRootKey

Mark the chord root on the on-screen piano keyboard with the root color cue. Helps beginners locate the bass note at a glance.

- Values: true / false
- Default: `false`

### `fing` — showFingering

Display piano fingerings (digits 1-5) on keyboard hints. Scales subject + singleNotes voicing only; mutually exclusive with hlType (engaging fingerings overrides scale tint).

- Values: true / false
- Default: `false`

### `hl` — hlType

Scale-note keyboard highlighting (scales subject only). `off` paints nothing; `keys` tints the whole white/black key behind every scale-degree pitch class; `bullets` marks each scale-degree key with a colored dot (green on non-root pitches, red on the tonic).

- Values: off, keys, bullets
- Default: `"off"`

### `instr` — instrument

Visual instrument used by the chord-hint panel under the Q&A. `piano` (default) renders the existing PianoKeyboard SVG. `guitar` renders a 6-string fretboard via the svguitar library, with chord shapes sourced from @tombatossals/chords-db and matched to the exercise inversion. Both libraries are lazy-loaded so piano-only users pay zero bytes. Ukulele defers to Phase 2.

- Values: piano, guitar
- Default: `"piano"`

### `nota` — notation

Chord + note naming convention shown everywhere on screen.

- Values: american, german, solfege, jazz
- Default: `"american"`

### `cnf` — chordNameForm

Chord-label rendering form. Short = "Cm7" / "Cmaj9"; long = "C minor 7" / "C dur9". V1 parity with #chord_names_0/1.

- Values: short, long
- Default: `"short"`

### `slash` — slashInversions

Render inverted chords as slash bass (e.g. C/E) instead of "1st inv" pill. V1 parity with #o-23.

- Values: true / false
- Default: `false`

### `chinv` — chartInversions

Show inversion info in the chord chart labels: slash form (C/E) when slashInversions is on, else dot form (C.2). Off = plain chord names.

- Values: true / false
- Default: `true`

### `colinv` — colorfulInversions

Use per-inversion colors (root #111, 1st #82a905, 2nd #7952b3, 3rd orange) on the /app inversion pill, the chord-balls bass-note stroke, and the /preferences inversion buttons. When off, all inversions render in neutral black.

- Values: true / false
- Default: `true`

### `scn` — showChordName

Show the chord name above the question on /app. Hide via the preferences toggle or by long-pressing the label.

- Values: true / false
- Default: `true`

### `balls` — showAnswerBalls

Show the ball-notes hint inside the answer panel on /app. Half of the hint TYPE pair behind the single Hints container on /preferences (B shortcut); at least one of the pair stays on, so the panel — and the Naming quiz question — is never empty. Hide via that control or by long-pressing the balls. Only consulted once `answerHintsChosen` is true — until then the screen width picks the default (narrow → off).

- Values: true / false
- Default: `true`

### `keys` — showAnswerKeyboard

Show the on-screen piano keyboard inside the answer panel on /app. The other half of the hint TYPE pair (K shortcut; see showAnswerBalls). Hide via the Hints container or by long-pressing the keyboard. Only consulted once `answerHintsChosen` is true — until then the screen width picks the default (narrow → off).

- Values: true / false
- Default: `true`

### `hintsset` — answerHintsChosen

True once the user has explicitly toggled either answer hint (balls / keyboard) on /preferences. While false, both default to OFF on a narrow screen and ON otherwise — a phone has no room to show the answer beside the question, and showing it there gave the answer away before the player had tried. Once true, the stored `showAnswerBalls` / `showAnswerKeyboard` values are honored on every device.

- Values: true / false
- Default: `false`

### `chart` — showChordChart

In Scales / Progressions / Songs subjects, show the chord-chart strip — a horizontal row of every chord in the current cycle. Click a cell to restrict the draw pool to a subset (inOrder walks click-order, random picks within the subset). Hide via the /preferences toggle or by long-pressing the strip.

- Values: true / false
- Default: `true`

### `invpill` — showInversionPill

Show the inversion pill (current chord inversion + hand icons) on /app across Chords, Progressions and Scales subjects, regardless of the notation/chord-label question mode. Colored by colorfulInversions when that option is on. Hide via the /preferences toggle or by long-pressing the pill.

- Values: true / false
- Default: `true`

### `hints` — hintsAlways

Keep the answer panel (balls + keyboard) visible at all times, not only after Space-to-reveal or a wrong attempt. The H shortcut toggles it; B / K choose WHICH surface the panel holds (at least one stays on). V1 `o-0` parity.

- Values: true / false
- Default: `false`

### `hintmiss` — autoHintsOnMiss

Auto-show the answer panel once the player is NEAR DEATH — on the last purple heart, game mode only — and keep it up. Historic key name: the reveal-after-a-wrong-attempt it used to gate is now unconditional (a mistake always reveals the answer, V1 default behavior), so this box is the third of the three behaviors: default / always visible / auto on the last life. Renaming the key would break existing share links and synced states.

- Values: true / false
- Default: `true`

### `qcol` — questionColors

Tint the chord-question block by quality (major, minor, dim, …) for visual recall.

- Values: true / false
- Default: `false`

### `queue` — chordQueueEnabled

When on, /app shows a row of upcoming chords (the queue) instead of just the current chord. The queue advances by one on every correct answer.

- Values: true / false
- Default: `false`

### `queuen` — chordQueueSize

How many slots the chord queue shows when chordQueueEnabled is on. Slot 1 is the currently-graded chord; the rest are the upcoming queue. 2..8.

- Values: 2 – 8
- Default: `4`

### `rootless` — rootlessMode

V1 #o-20 parity. When true, the chord root is dropped from the expected pitch classes during evaluation, so a C major question accepts E + G alone. Single-note questions short-circuit (no-op). Inversion strictness is suspended in this mode — the bass becomes the lowest non-root note and the "wrong-inversion" outcome no longer fires.

- Values: true / false
- Default: `false`

### `onenote` — oneNoteMode

One-note mode. Whatever the exercise draws — a chord, a scale degree, a progression step, a song chord — the RIGHT hand only has to play the chord root; nothing else about the question changes (the same chord is drawn, named and hinted). The left hand is unaffected: `leftPlays` still decides what it owes. Wins over `rootlessMode` when both are on (rootless drops the note one-note keeps); a ninja pattern still overrides both. Single-note questions are unaffected.

- Values: true / false
- Default: `false`

### `extras` — extraNotesPolicy

What extra HELD keys beyond the expected answer do to a match. The segmenter deliberately tolerates keys sustained across questions (common-tone legato); this decides how far that goes. any = a matching candidate wins regardless of what else is held (pre-2026-09 behavior); chordTones (default) = extras are fine while every held key is a correct pitch class (octave doublings, sustained chord tones) and one foreign pc fails as wrong-notes; strict = the held count must equal the expected count, any extra key fails as wrong-count. Applied to the graded side only — a keyboard split still exempts the other hand entirely.

- Values: any, chordTones, strict
- Default: `"chordTones"`

### `forgive` — allowConsecutiveMistakes

When true, missing twice in a row only costs one purple heart; the second consecutive miss is forgiven (V1 strict-mode guard). Game-mode only.

- Values: true / false
- Default: `true`

### `mfm` — moveForwardOnMistake

In `doubleMode`, count a wrong attempt as one of the N reps. Speeds up sessions by not punishing slips; off (default) means only correct answers count toward the rep target.

- Values: true / false
- Default: `false`

### `rlearn` — reinforcementLearning

When on, the random-draw weights drift toward chords you miss more often and away from chords you nail. Only effective when the exercise sequence is "random". See /chord-stats for the live ranking.

- Values: true / false
- Default: `true`

### `dbl` — doubleMode

Repeat each chord N times before advancing (where N is `doubleModeValue`). Reinforces voicing + timing on the same chord before moving on.

- Values: true / false
- Default: `false`

### `dbln` — doubleModeValue

How many times each chord must be played in a row before advancing, when `doubleMode` is on. 2..10.

- Values: 2 – 10
- Default: `3`

### `chunks` — playInChunks

V1 #o-38 parity. When true and the subject is scales with sequence=inOrder, the chart is fed in growing chunks: a flawless full pass through the active chunk advances the chunk (per `chunkMode`). The chord-chart strip visualizes the active chunk via the existing selectedIndices highlight pipeline. User chart-clicks are suppressed while this mode is on.

- Values: true / false
- Default: `false`

### `chunkn` — chunkLength

V1 #o-388 parity. Number of consecutive degrees in each chart chunk when `playInChunks` is on. Clamped to 2..7.

- Values: 2 – 7
- Default: `3`

### `chunkmode` — chunkMode

How the chart chunk advances per flawless loop. `cumulative` grows the chunk by one cell on each pass (V1 default — C → CD → CDE …). `window` slides a fixed-length window forward by `chunkLength` cells each pass (CDE → DEF → GAB …). Both wrap on overflow.

- Values: cumulative, window
- Default: `"cumulative"`

## Legend — chord quality ids (`ct` / chordTypes)

Use these ids in the `ct` param. 37–48 are bare intervals and 49 is a single
note; every other id is a chord. `semitones` lists the interval pattern from
the root. The table below is generated from the catalog — read the ids from
it rather than assuming a range, since chords have been added above 49
(50 and 51 are the two ♭9 chords; 52 and 53 the two upper-structure
dominants, 13♯11 and 7♯9♯5).

| ct id | name | kind | semitones |
| --- | --- | --- | --- |
| 0 | major | chord | [0 4 7] |
| 1 | minor | chord | [0 3 7] |
| 2 | diminished | chord | [0 3 6] |
| 3 | augmented | chord | [0 4 8] |
| 4 | suspended 2nd | chord | [0 2 7] |
| 5 | suspended 4th | chord | [0 5 7] |
| 6 | major 7 | chord | [0 4 7 11] |
| 7 | minor 7 | chord | [0 3 7 10] |
| 8 | dominant 7 | chord | [0 4 7 10] |
| 9 | half-diminished 7 | chord | [0 3 6 10] |
| 10 | diminished 7 | chord | [0 3 6 9] |
| 11 | augmented 7 | chord | [0 4 8 10] |
| 12 | augmented major 7 | chord | [0 4 8 11] |
| 13 | minor-major 7 | chord | [0 3 7 11] |
| 14 | minor 7 sharp 5 | chord | [0 3 8 10] |
| 15 | major 6 | chord | [0 4 7 9] |
| 16 | minor 6 | chord | [0 3 7 9] |
| 17 | add 9 | chord | [0 4 7 14] |
| 18 | minor add 9 | chord | [0 3 7 14] |
| 19 | major 9 | chord | [0 4 7 11 14] |
| 20 | minor 9 | chord | [0 3 7 10 14] |
| 21 | dominant 9 | chord | [0 4 7 10 14] |
| 22 | half-diminished 9 | chord | [0 3 6 10 14] |
| 23 | minor-major 9 | chord | [0 3 7 11 14] |
| 24 | major 9 sharp 5 | chord | [0 4 8 11 14] |
| 25 | minor 11 | chord | [0 3 7 10 14 17] |
| 26 | major 11 | chord | [0 4 7 11 14 17] |
| 27 | dominant 11 | chord | [0 4 7 10 14 17] |
| 28 | half-diminished 11 | chord | [0 3 6 10 14 17] |
| 29 | minor-major 11 | chord | [0 3 7 11 14 17] |
| 30 | augmented major 11 | chord | [0 4 8 11 14 17] |
| 31 | major 13 | chord | [0 4 7 11 14 17 21] |
| 32 | minor 13 | chord | [0 3 7 10 14 17 21] |
| 33 | dominant 13 | chord | [0 4 7 10 14 17 21] |
| 34 | half-diminished 13 | chord | [0 3 6 10 14 17 21] |
| 35 | minor-major 13 | chord | [0 3 7 11 14 17 21] |
| 36 | major 13 sharp 5 | chord | [0 4 8 11 14 17 21] |
| 37 | minor 2nd | interval | [0 1] |
| 38 | major 2nd | interval | [0 2] |
| 39 | minor 3rd | interval | [0 3] |
| 40 | major 3rd | interval | [0 4] |
| 41 | perfect 4th | interval | [0 5] |
| 42 | tritone | interval | [0 6] |
| 43 | perfect 5th | interval | [0 7] |
| 44 | minor 6th | interval | [0 8] |
| 45 | major 6th | interval | [0 9] |
| 46 | minor 7th | interval | [0 10] |
| 47 | major 7th | interval | [0 11] |
| 48 | octave | interval | [0 12] |
| 49 | note | note | [0] |
| 50 | minor 7 flat 9 | chord | [0 3 7 10 13] |
| 51 | half-diminished 7 flat 9 | chord | [0 3 6 10 13] |
| 52 | dominant 13 sharp 11 | chord | [0 4 7 10 14 18 21] |
| 53 | dominant 7 sharp 9 sharp 5 | chord | [0 4 8 10 15] |
| 54 | power chord | chord | [0 7 12] |
| 55 | dominant 7 sus4 | chord | [0 5 7 10] |
| 56 | dominant 7 sus2 | chord | [0 2 7 10] |
| 57 | six-nine | chord | [0 4 7 9 14] |
| 58 | minor six-nine | chord | [0 3 7 9 14] |
| 59 | add 11 | chord | [0 4 7 17] |
| 60 | dominant 7 flat 5 | chord | [0 4 6 10] |
| 61 | dominant 7 flat 9 | chord | [0 4 7 10 13] |
| 62 | dominant 7 sharp 9 | chord | [0 4 7 10 15] |
| 63 | dominant 7 sharp 11 | chord | [0 4 7 10 18] |
| 64 | dominant 9 flat 5 | chord | [0 4 6 10 14] |
| 65 | dominant 9 sharp 11 | chord | [0 4 7 10 14 18] |
| 66 | major 7 sharp 11 | chord | [0 4 7 11 18] |
| 67 | major 9 sharp 11 | chord | [0 4 7 11 14 18] |

## Legend — pitch classes (`bn` / baseNotes)

Use these values in the `bn` param to restrict which roots are drawn.

| bn value | note |
| --- | --- |
| 0 | C |
| 1 | C# |
| 2 | D |
| 3 | D# |
| 4 | E |
| 5 | F |
| 6 | F# |
| 7 | G |
| 8 | G# |
| 9 | A |
| 10 | A# |
| 11 | B |

## Legend — interval voicings (`voicing=intM2..int8`, scales subject)

The scale degree is the lower note; the named interval is added above it.

| voicing | interval above degree |
| --- | --- |
| `intM2` | minor 2nd |
| `int2` | major 2nd |
| `intM3` | minor 3rd |
| `int3` | major 3rd |
| `int4` | perfect 4th |
| `intTT` | tritone |
| `int5` | perfect 5th |
| `intM6` | minor 6th |
| `int6` | major 6th |
| `intM7` | minor 7th |
| `int7` | major 7th |
| `int8` | octave |
