Adds the per-track end-action model designed in docs/track-format.md §3, end to end across both engines, both firmwares, and the editors. Grammar (parsed + serialized by engine.js and both app.py): rep=<n> cycles before the end-action fires (default 1) end=stop stop after rep cycles end=next advance one track (sugar for end=+1) end=<±N> relative goto after rep cycles (e.g. end=-2 = D.S.) (absent) loop forever — the metronome default Firmware runtime (pico-cp + pico-explorer): _on_new_bar now consults a per-track _end_plan() and fires stop / gapless-advance / relative-goto at the right bar. A cycle = b<bars>, else one master bar; fire bar = rep * cycle. Explicit end= governs; with no end, the global Continue toggle stays a default (=end=next, still needs b<bars>) so existing set-lists and the CONT UI are unchanged. _prepare_next takes a target index; the seam machinery, _do_advance and live-sync all carry rep/end. Editors (editor.html + editor-beta.html): state.rep/state.end thread through applySetup / currentSetup / currentPatch so load -> edit -> save preserves the flow; authoring is via the program-string field (no graphical control yet). Tests: the 3 playback-flow vectors now pass on both engines (39 pass / 3 known). Runtime decision logic (_end_plan / _goto_target) unit-tested for stop, rep, relative goto clamp/wrap, and legacy-Continue precedence. Codec round-trip verified idempotent. Both firmwares compile + mpy-cross clean. Also: untrack stale __pycache__/*.pyc build artifacts and gitignore them. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|---|---|---|
| .. | ||
| adapters | ||
| fixtures | ||
| .gitignore | ||
| README.md | ||
| run.mjs | ||
Track-format conformance tests
Golden-vector suite that pins the track ("program"/"patch") format to a single meaning and checks that both implementations agree:
- web —
src/engine.js - firmware —
pico-cp/app.py
The spec is docs/track-format.md. Any new implementation (e.g. a Rust engine) must pass the
same vectors — that is what keeps "the same groove on the device and in the browser" true.
Run
node tests/run.mjs # table of pass / known-divergence / FAIL per case
node tests/run.mjs -v # also print expected-vs-actual diffs for unexpected failures
Exit code is non-zero on any unexpected failure or round-trip (idempotency) break, so it works as a CI gate.
Layout
fixtures/track-format.json— the vectors. Each hasin(a patch),norm(expected normalized meaning, see spec §5), astatus, and optionalexpectFaillisting impls known to differ today.adapters/js_adapter.mjs— loads the realsrc/engine.jsgrammar (no copy) and normalizes.adapters/py_adapter.py— extracts the realpico-cp/app.pygrammar functions viaast(no copy) and normalizes.run.mjs— runs every vector through both adapters and reports.
Reading the result
✓ pass— implementation matches the spec for that vector.· known— a divergence/feature listed inexpectFail; expected, not a failure.✗ FAIL— an unexpected mismatch (a regression). Investigate.★ fixed— an impl listed inexpectFailnow passes; remove it fromexpectFail.
When you fix a divergence in code, delete that impl from the case's expectFail. When you
implement the new playback-flow tokens (rep / end), those cases flip to pass.