# Per-track playback flow — on-device test checklist The parsing/serialization and the `_end_plan`/`_goto_target` decision logic are covered by `tests/run.mjs` and unit tests. What still needs a real device (PM_K-1 / PM_X-1) is the **runtime**: that the gapless seam fires stop / advance / relative-goto at the right bar. ## Setup 1. Flash the new firmware: copy `dist/app.mpy` (PM_K) or `dist/explorer-app.mpy` (PM_X) onto the device as `/app.mpy` (or use the editor's one-click push — it serves this build). 2. Author the test tracks in the web editor: paste each program string into the program-string field, **Save** it as a set-list item, then **Save to device**. (Multi-item cases: create the items in order in one set-list.) 3. On the device, select the set-list and press play (Button A). `b` sets the cycle length in bars; a cycle = `b`, else one master bar. The action fires after `rep × cycle` bars (rep defaults to 1). ## Cases | # | Track(s) | Expect | ✓ | |---|----------|--------|---| | 1 | `t120;kick:4` | **Loops forever.** Joystick-right advances manually. | ☐ | | 2 | `t120;b2;kick:4;end=stop` | Plays **2 bars, then stops** by itself (LED → green, transport stops). | ☐ | | 3 | `t120;b2;kick:4;rep=3;end=stop` | Plays **6 bars (3×2), then stops.** | ☐ | | 4 | A=`t120;b2;kick:4;end=next` B=`t100;b2;snare:4` | A plays 2 bars then **gaplessly advances to B** (no gap, no count-in, tempo jumps to 100). | ☐ | | 5 | A=`t120;b2;kick:4;rep=2;end=next` B=`t100;b2;snare:4` | A plays **4 bars** (2×2) then advances to B. | ☐ | | 6 | 1=`t120;b2;kick:4` 2=`t120;b2;snare:4;end=next` 3=`t120;b2;clap:4;end=-2` | 3 jumps **back 2 → track 1** after its cycle (a looping verse→chorus section). | ☐ | | 7 | 1=`t120;b2;kick:4;end=-2` (first item) | `end=-2` before the start **clamps to track 1** → it just loops itself. | ☐ | | 8 | last item `…;end=next` | Advancing past the last item **wraps to the first** (set-list loops). | ☐ | | 9 | any of the above, mid-flow | **Manual joystick-right / footswitch always advances immediately,** regardless of `end=`. | ☐ | | 10 | global **Continue ON** + an item `t120;b2;kick:4;end=stop` | The item **still stops** — explicit `end=` overrides the global Continue default. | ☐ | | 11 | global **Continue ON** + `t120;b2;kick:4` (no `end=`) | **Advances** after 2 bars — legacy behavior preserved (Continue = default `end=next`). | ☐ | ## What to watch for - **Seam quality** (cases 4–6): the swap should be click-on-the-beat with no audible gap, dropout, or double-trigger at the boundary. This is the highest-risk part of the change. - **Stop cleanliness** (cases 2–3): no extra click after the stop; speaker goes quiet; the play session is logged. - **Ramp/trainer interaction**: a track with `rmp…`/`tr…` *and* an `end=` should ramp/gap normally and still fire the end-action at `rep × cycle`. - **Memory**: relative-goto and advance both go through `_prepare_next` (which `gc.collect()`s before parsing). Watch for any MemoryError fallback (it leaves the track looping instead). Report any case that misbehaves with its number and what happened.