Parcourir la source

Merge remote-tracking branch 'origin/main' into m8-load-guard-rails

Claude il y a 1 jour
Parent
commit
6b2e90e65f

+ 14 - 12
docs/build-plan.md

@@ -1,8 +1,10 @@
-# mpckit 1.0 build plan
+# mpckit 0.5.0 build plan
 
 
-Written 2026-11-10. Companion to `mpckit/xpj-format-and-scope.md` (format reference, verified edit recipes, agreed 1.0 scope) and the "mpckit main screen mockup" design artifact. Read the format doc first; this plan does not repeat the field paths.
+0.5.0 is a beta release for testing. The milestones for 1.0 (including beta testers using pre-1.0 versions) will be added later.
 
 
-## 1. What 1.0 is
+Written 2026-11-10. Companion to `mpckit/xpj-format-and-scope.md` (format reference, verified edit recipes, agreed 0.5.0 scope) and the "mpckit main screen mockup" design artifact. Read the format doc first; this plan does not repeat the field paths.
+
+## 1. What 0.5.0 is
 
 
 A desktop app (Linux, Windows, Mac) for building MPC Sample kits on a computer and exporting them to the device.
 A desktop app (Linux, Windows, Mac) for building MPC Sample kits on a computer and exporting them to the device.
 
 
@@ -13,14 +15,14 @@ A desktop app (Linux, Windows, Mac) for building MPC Sample kits on a computer a
 - Sequences, 128 slots: add, remove, name, BPM, bar length. No note editing.
 - Sequences, 128 slots: add, remove, name, BPM, bar length. No note editing.
 - WAV only.
 - WAV only.
 
 
-Not in 1.0: start/end trim and waveform, level and pan, loop and trigger modes, envelopes, filters, effects, pad colours, other audio formats, songs, note editing.
+Not in 0.5.0: start/end trim and waveform, level and pan, loop and trigger modes, envelopes, filters, effects, pad colours, other audio formats, songs, note editing.
 
 
 ## 2. Stack
 ## 2. Stack
 
 
 - Go for all file work.
 - Go for all file work.
 - Wails v3 (v3.1.0, the latest stable release when M4 started; it needs Go 1.26) for the window, dialogs, file drop and the Go-to-frontend bridge.
 - Wails v3 (v3.1.0, the latest stable release when M4 started; it needs Go 1.26) for the window, dialogs, file drop and the Go-to-frontend bridge.
 - Svelte + TypeScript + Vite for the UI (plain Svelte, not SvelteKit).
 - Svelte + TypeScript + Vite for the UI (plain Svelte, not SvelteKit).
-- No database, no network access, no settings file in 1.0.
+- No database, no network access, no settings file in 0.5.0.
 
 
 Wails pieces this plan relies on (confirm against the installed version at setup):
 Wails pieces this plan relies on (confirm against the installed version at setup):
 - Services: Go structs registered with the app; TypeScript bindings generated with `wails3 generate bindings`.
 - Services: Go structs registered with the app; TypeScript bindings generated with `wails3 generate bindings`.
@@ -61,7 +63,7 @@ The device accepts files only if we stay close to what it writes, so the tree mu
 - Parse with `json.Decoder` token by token and `UseNumber()`, building our own tree: objects keep key order (slice of key/value pairs), numbers keep their original text.
 - Parse with `json.Decoder` token by token and `UseNumber()`, building our own tree: objects keep key order (slice of key/value pairs), numbers keep their original text.
 - Write in the device's layout: one key or value per line, no indentation, `": "` after keys, no trailing newline. Do not escape `<`, `>`, `&` or `/`.
 - Write in the device's layout: one key or value per line, no indentation, `": "` after keys, no trailing newline. Do not escape `<`, `>`, `&` or `/`.
 - New numeric values: integer fields as integers (`4`); float fields always with a decimal point (`96.0`, `20.0`, `5.0`, never `96`). Tuning `pitch` is a 32-bit float widened to 64-bit and printed in shortest form (12.4 is written `12.399999618530273`; the device wrote the same float64 with 17 digits, `12.399999618530274`. Both read back identically, so the shortest form is kept.)
 - New numeric values: integer fields as integers (`4`); float fields always with a decimal point (`96.0`, `20.0`, `5.0`, never `96`). Tuning `pitch` is a 32-bit float widened to 64-bit and printed in shortest form (12.4 is written `12.399999618530273`; the device wrote the same float64 with 17 digits, `12.399999618530274`. Both read back identically, so the shortest form is kept.)
-- Float fields written in 1.0: `seq.bpm`, `pad.tempo`, `layer.pitch`, and `metadata.tempo` / `metadata.tune` on new pool entries. Everything else we write is an integer or a string.
+- Float fields written in 0.5.0: `seq.bpm`, `pad.tempo`, `layer.pitch`, and `metadata.tempo` / `metadata.tune` on new pool entries. Everything else we write is an integer or a string.
 - Helpers: get/set by path, deep clone of a subtree, insert and remove array elements.
 - Helpers: get/set by path, deep clone of a subtree, insert and remove array elements.
 
 
 Why not `map[string]any` or a path-patching library: maps lose key order, and we need to insert whole objects (pool entries, sequences) in the device's layout.
 Why not `map[string]any` or a path-patching library: maps lose key order, and we need to insert whole objects (pool entries, sequences) in the device's layout.
@@ -89,7 +91,7 @@ Every edit is a method that changes the tree using the recipes in the format doc
 | `ClearPad(slot)` | Replaces the pad with the blank pad; drops the pool entry if no pad still uses it. |
 | `ClearPad(slot)` | Replaces the pad with the blank pad; drops the pool entry if no pad still uses it. |
 | `CopyPad(from, to)` / `MovePad` / `SwapPads` | Whole-pad copies. Copies share the pool entry (device-tested). |
 | `CopyPad(from, to)` / `MovePad` / `SwapPads` | Whole-pad copies. Copies share the pool entry (device-tested). |
 | `PadHasNotes(slot)` | True if any sequence has a note or automation event on note 36 + slot. Drives the move warning. |
 | `PadHasNotes(slot)` | True if any sequence has a note or automation event on note 36 + slot. Drives the move warning. |
-| `SetTune(slot, coarse, fine)` | Writes `coarseTune`, `fineTune`, `pitch` on the layer. Clamps to the 1.0 ranges. |
+| `SetTune(slot, coarse, fine)` | Writes `coarseTune`, `fineTune`, `pitch` on the layer. Clamps to the 0.5.0 ranges. |
 | `SetMuteGroup(slot, n)` | `whichMuteGroup`; 0 is off. |
 | `SetMuteGroup(slot, n)` | `whichMuteGroup`; 0 is off. |
 | `AddSequence(slot)` | Clones the template sequence; name `Sequence NN` (slot + 1); BPM copied from the most recently edited sequence, else 120; 2 bars. |
 | `AddSequence(slot)` | Clones the template sequence; name `Sequence NN` (slot + 1); BPM copied from the most recently edited sequence, else 120; 2 bars. |
 | `RemoveSequence(slot)` | Refused when it is the last sequence. If it was `currentSequence`, point that at the lowest remaining slot. |
 | `RemoveSequence(slot)` | Refused when it is the last sequence. If it was `currentSequence`, point that at the lowest remaining slot. |
@@ -109,13 +111,13 @@ Validation at the edge of the model: WAV must be uncompressed (integer PCM or 32
 - Folder: the user picks a destination; write `<Name>.xpj` and `<Name>_[ProjectData]/` with every pool file. Create the data folder even when empty. If either already exists, ask before replacing. Write to a temporary name in the same directory and rename at the end.
 - Folder: the user picks a destination; write `<Name>.xpj` and `<Name>_[ProjectData]/` with every pool file. Create the data folder even when empty. If either already exists, ask before replacing. Write to a temporary name in the same directory and rename at the end.
 - Zip: the same two entries at the zip root.
 - Zip: the same two entries at the zip root.
 - Before writing, check every source file still exists; report all missing files in one message.
 - Before writing, check every source file still exists; report all missing files in one message.
-- There is no separate Save in 1.0: Load reads a project, Export writes one.
+- There is no separate Save in 0.5.0: Load reads a project, Export writes one.
 
 
 ### 4.5 Playback preview
 ### 4.5 Playback preview
 
 
 - Go serves the selected pad's source WAV to the frontend at a local route (the app's asset handler, or the built-in file-server service).
 - Go serves the selected pad's source WAV to the frontend at a local route (the app's asset handler, or the built-in file-server service).
 - The frontend decodes it with Web Audio and plays it through a buffer source with `detune` = coarse x 100 + fine (cents). Pitch and speed change together, as on the device.
 - The frontend decodes it with Web Audio and plays it through a buffer source with `detune` = coarse x 100 + fine (cents). Pitch and speed change together, as on the device.
-- For loaded pads that already carry a start/end trim, play only that region so the preview matches the device, even though trim is not editable in 1.0.
+- For loaded pads that already carry a start/end trim, play only that region so the preview matches the device, even though trim is not editable in 0.5.0.
 - Mute-group choking: the frontend tracks which pads are sounding and their mute group. Starting a pad first stops every other sounding pad in the same non-zero group, with a 5-10 ms fade so the cut does not click. Group 0 (off) never chokes. The fade length is our choice, not measured from the device.
 - Mute-group choking: the frontend tracks which pads are sounding and their mute group. Starting a pad first stops every other sounding pad in the same non-zero group, with a 5-10 ms fade so the cut does not click. Group 0 (off) never chokes. The fade length is our choice, not measured from the device.
 - A new click on the same pad restarts it (pads are monophonic on the device).
 - A new click on the same pad restarts it (pads are monophonic on the device).
 - No effects.
 - No effects.
@@ -193,7 +195,7 @@ Done when: Device checkpoint B passes.
 
 
 **M9. Packaging and release.**
 **M9. Packaging and release.**
 Icons, version stamping, builds for the three systems, README with install notes, licence. The About modal and README state that mpckit is not affiliated with Akai.
 Icons, version stamping, builds for the three systems, README with install notes, licence. The About modal and README state that mpckit is not affiliated with Akai.
-Done when: a tagged 1.0 build runs on each system.
+Done when: a tagged v0.5.0 build runs on each system.
 
 
 ## 7. Device checkpoints
 ## 7. Device checkpoints
 
 
@@ -240,9 +242,9 @@ Conclusions:
 - `sliceInfo.End` is counted in the file's own frames at its own sample rate. (The 22.05 kHz pad settles this: read as 44.1 kHz frames it would have stopped halfway, before the beep.)
 - `sliceInfo.End` is counted in the file's own frames at its own sample rate. (The 22.05 kHz pad settles this: read as 44.1 kHz frames it would have stopped halfway, before the beep.)
 - Accepted so far: 8, 16, 24 and 32-bit integer and 32-bit float; mono and stereo; 22.05, 44.1, 48 and 96 kHz.
 - Accepted so far: 8, 16, 24 and 32-bit integer and 32-bit float; mono and stereo; 22.05, 44.1, 48 and 96 kHz.
 
 
-1.0 rule for assigning a file: accept any uncompressed WAV (integer PCM or 32-bit float), mono or stereo. Refuse compressed WAVs (ADPCM, MP3-in-WAV), more than two channels, and anything over 20 minutes.
+0.5.0 rule for assigning a file: accept any uncompressed WAV (integer PCM or 32-bit float), mono or stereo. Refuse compressed WAVs (ADPCM, MP3-in-WAV), more than two channels, and anything over 20 minutes.
 
 
-Not yet tested: sample rates above 96 kHz or below 22.05 kHz; 64-bit float (refused in 1.0). The extensible header that many audio tools write was confirmed in checkpoint A (PCM inside, 16 and 24-bit, mono and stereo); mpckit accepts it when it declares PCM or float inside.
+Not yet tested: sample rates above 96 kHz or below 22.05 kHz; 64-bit float (refused in 0.5.0). The extensible header that many audio tools write was confirmed in checkpoint A (PCM inside, 16 and 24-bit, mono and stereo); mpckit accepts it when it declares PCM or float inside.
 
 
 **B (after M8), round trips through the app:** step-by-step projects and a results table in `docs/checkpoint-b.md`.
 **B (after M8), round trips through the app:** step-by-step projects and a results table in `docs/checkpoint-b.md`.
 1. Load `DemoFile` (recorded notes), change nothing, export: sequences still play the same on the device.
 1. Load `DemoFile` (recorded notes), change nothing, export: sequences still play the same on the device.

+ 22 - 22
docs/xpj-format-and-scope.md

@@ -1,4 +1,4 @@
-# mpckit: MPC Sample project format and 1.0 / 2.0 scope
+# mpckit: MPC Sample project format and 0.5.0 / 2.0 scope
 
 
 Sources: three projects saved on an MPC Sample, in `testdata/device/` of `bajdev/mpckit` (first committed as `demo-projects/`), analysed 2026-11-10.
 Sources: three projects saved on an MPC Sample, in `testdata/device/` of `bajdev/mpckit` (first committed as `demo-projects/`), analysed 2026-11-10.
 
 
@@ -15,7 +15,7 @@ Everything below is observed in those files unless marked as an open question. T
 - The `.xpj` reader/writer is its own Go package with no UI code.
 - The `.xpj` reader/writer is its own Go package with no UI code.
 - New projects are built from the blank project as a template; edits change only known fields.
 - New projects are built from the blank project as a template; edits change only known fields.
 
 
-## 1.0 scope (agreed 2026-11-10)
+## 0.5.0 scope (agreed 2026-11-10 as the 1.0 scope; renamed 2026-11-11: 0.5.0 is a beta release for testing)
 
 
 Intended use: set up new kits on the desktop, then move to the device to create sequences and tracks.
 Intended use: set up new kits on the desktop, then move to the device to create sequences and tracks.
 
 
@@ -35,7 +35,7 @@ UI (mockup agreed 2026-11-10, "mpckit main screen mockup" artifact):
 - Pads: click to select and play; drag to move, drag onto a filled pad to swap, Alt-drag to copy; drop a WAV to assign.
 - Pads: click to select and play; drag to move, drag onto a filled pad to swap, Alt-drag to copy; drop a WAV to assign.
 
 
 2.0 or later:
 2.0 or later:
-- Start and end trim, together with a waveform view (moved out of 1.0: not useful without the waveform). The file fields are already verified (section 3).
+- Start and end trim, together with a waveform view (moved out of 0.5.0: not useful without the waveform). The file fields are already verified (section 3).
 - Level and pan (fields not yet confirmed from a device file).
 - Level and pan (fields not yet confirmed from a device file).
 - Trigger and loop modes, envelopes, filters, effects, pad colours.
 - Trigger and loop modes, envelopes, filters, effects, pad colours.
 - Sequence editing (notes), including making notes follow a moved pad.
 - Sequence editing (notes), including making notes follow a moved pad.
@@ -91,7 +91,7 @@ A pad's settings hold nothing that depends on its slot: all 128 blank pads are i
 - Move: copy, then reset the source slot to the blank pad.
 - Move: copy, then reset the source slot to the blank pad.
 - Swap: exchange the two `pad[n]` objects.
 - Swap: exchange the two `pad[n]` objects.
 
 
-Recorded notes follow the slot, not the sample (a note stores 36 + n). Moving a pad that has notes in a sequence changes what those notes play. Decided for 1.0: warn before moving a pad that has recorded notes, and leave the notes untouched. Rewriting notes to follow the pad belongs with sequence editing, a later feature.
+Recorded notes follow the slot, not the sample (a note stores 36 + n). Moving a pad that has notes in a sequence changes what those notes play. Decided for 0.5.0: warn before moving a pad that has recorded notes, and leave the notes untouched. Rewriting notes to follow the pad belongs with sequence editing, a later feature.
 
 
 ### Tune a pad
 ### Tune a pad
 Three fields on the layer, written together:
 Three fields on the layer, written together:
@@ -131,7 +131,7 @@ Copy the blank sequence, set `sequences[].key` to the slot (0-based), then name,
 
 
 | Property | Path | Seen | Proposed |
 | Property | Path | Seen | Proposed |
 |---|---|---|---|
 |---|---|---|---|
-| Project name | file name and `_[ProjectData]` folder name | DemoFile | 1.0 |
+| Project name | file name and `_[ProjectData]` folder name | DemoFile | 0.5.0 |
 | Master tempo | `masterTempo`, `masterTempoEnabled` | 128.0, false in all three | 2.0 (sequences carry their own BPM) |
 | Master tempo | `masterTempo`, `masterTempoEnabled` | 128.0, false in all three | 2.0 (sequences carry their own BPM) |
 | Project key | `key` | "C Major" | 2.0 |
 | Project key | `key` | "C Major" | 2.0 |
 | Info tags | `info.title`, `artist`, `originalArtist`, `album`, `year`, `genre`, `copyright`, `notes` | all empty | 2.0 |
 | Info tags | `info.title`, `artist`, `originalArtist`, `album`, `year`, `genre`, `copyright`, `notes` | all empty | 2.0 |
@@ -144,28 +144,28 @@ Copy the blank sequence, set `sequences[].key` to the slot (0-based), then name,
 
 
 | Property | Path | Seen | Proposed |
 | Property | Path | Seen | Proposed |
 |---|---|---|---|
 |---|---|---|---|
-| Name | `samples[].name` | "Linn 9000 kick" | 1.0 |
-| File | `samples[].path` | "Linn 9000 kick.wav" | 1.0 |
-| Load mode | `samples[].loadImpl` | 0 for one-shots and the user sample; 1 for three factory loops | 1.0, write 0 |
-| Tempo | `samples[].metadata.tempo` | 0.0 for the user sample; 20-114 for factory | 1.0, write 0.0 |
-| Root note, tune | `samples[].metadata.rootNote`, `tune` | 60, 0.0 | 1.0, defaults |
-| Key | `samples[].metadata.key` | "C Natural Minor" on the user sample | 1.0, default |
+| Name | `samples[].name` | "Linn 9000 kick" | 0.5.0 |
+| File | `samples[].path` | "Linn 9000 kick.wav" | 0.5.0 |
+| Load mode | `samples[].loadImpl` | 0 for one-shots and the user sample; 1 for three factory loops | 0.5.0, write 0 |
+| Tempo | `samples[].metadata.tempo` | 0.0 for the user sample; 20-114 for factory | 0.5.0, write 0.0 |
+| Root note, tune | `samples[].metadata.rootNote`, `tune` | 60, 0.0 | 0.5.0, defaults |
+| Key | `samples[].metadata.key` | "C Natural Minor" on the user sample | 0.5.0, default |
 
 
 ### Pad: assignment, tuning, trim (verified)
 ### Pad: assignment, tuning, trim (verified)
 
 
 | Property | Path | Proposed |
 | Property | Path | Proposed |
 |---|---|---|
 |---|---|---|
-| Sample | `layer.sampleName`, `layer.sampleFile` | 1.0 |
-| Slice end | `layer.sliceInfo.End` | 1.0, computed |
-| Pad tempo | `pad[n].tempo` | 1.0, computed |
-| Tune | `layer.coarseTune`, `fineTune`, `pitch` | 1.0 |
+| Sample | `layer.sampleName`, `layer.sampleFile` | 0.5.0 |
+| Slice end | `layer.sliceInfo.End` | 0.5.0, computed |
+| Pad tempo | `pad[n].tempo` | 0.5.0, computed |
+| Tune | `layer.coarseTune`, `fineTune`, `pitch` | 0.5.0 |
 | Start, end | `layer.sliceInfo.Start`, `LoopStart`, `End` | 2.0, with waveform view |
 | Start, end | `layer.sliceInfo.Start`, `LoopStart`, `End` | 2.0, with waveform view |
 
 
 ### Pad: playback
 ### Pad: playback
 
 
 | Property | Path | Seen | Proposed |
 | Property | Path | Seen | Proposed |
 |---|---|---|---|
 |---|---|---|---|
-| Mute group | `pad[n].whichMuteGroup` | 0 none; 1 on both hats in DemoFile; device offers 0-16 | 1.0 |
+| Mute group | `pad[n].whichMuteGroup` | 0 none; 1 on both hats in DemoFile; device offers 0-16 | 0.5.0 |
 | Level, pan | `pad[n].mixable.volume`, `pan` | 0.7079 (unity), 0.5 centre | 2.0 (not yet seen changed) |
 | Level, pan | `pad[n].mixable.volume`, `pan` | 0.7079 (unity), 0.5 centre | 2.0 (not yet seen changed) |
 | Trigger mode | `pad[n].triggerMode` | 0; 3 on the looped pad | 2.0 until mapped |
 | Trigger mode | `pad[n].triggerMode` | 0; 3 on the looped pad | 2.0 until mapped |
 | Layer loop | `layer.loopMode`, `sliceInfo.LoopMode` | 0/1 | 2.0 until mapped |
 | Layer loop | `layer.loopMode`, `sliceInfo.LoopMode` | 0/1 | 2.0 until mapped |
@@ -193,10 +193,10 @@ Copy the blank sequence, set `sequences[].key` to the slot (0-based), then name,
 
 
 | Property | Path | Seen | Proposed |
 | Property | Path | Seen | Proposed |
 |---|---|---|---|
 |---|---|---|---|
-| Slot | `sequences[].key` | 0, 1 | 1.0 |
-| Name | `seq.name` | "Intro", "Sequence 02" | 1.0 |
-| BPM | `seq.bpm`, `seq.tempoEnable` | 105, 110, 128; true | 1.0 |
-| Length | `seq.lengthBars` plus the 25 dependent fields | 2, 4 | 1.0 |
+| Slot | `sequences[].key` | 0, 1 | 0.5.0 |
+| Name | `seq.name` | "Intro", "Sequence 02" | 0.5.0 |
+| BPM | `seq.bpm`, `seq.tempoEnable` | 105, 110, 128; true | 0.5.0 |
+| Length | `seq.lengthBars` plus the 25 dependent fields | 2, 4 | 0.5.0 |
 | Time signature | `seq.timeSignatureTrack.timeSignatures[]` | 4/4 | 2.0 |
 | Time signature | `seq.timeSignatureTrack.timeSignatures[]` | 4/4 | 2.0 |
 | Transpose | `seq.transposition` | 0 | 2.0 |
 | Transpose | `seq.transposition` | 0 | 2.0 |
 | Notes | `...eventList.events[]` type 3: `time`, `note`, `velocity`, `length`, `probability`, `ratchet` | up to 32 notes | preserve |
 | Notes | `...eventList.events[]` type 3: `time`, `note`, `velocity`, `length`, `probability`, `ratchet` | up to 32 notes | preserve |
@@ -247,7 +247,7 @@ Four projects written by the Go writer (`tools/checkpoint-a`, built from commit
 - **Tempo** (`MpckitTempo`): sequences at 20, 30, 300 and 999 BPM all work. The device's tempo control runs from 20 to 999, and mpckit uses the same range.
 - **Tempo** (`MpckitTempo`): sequences at 20, 30, 300 and 999 BPM all work. The device's tempo control runs from 20 to 999, and mpckit uses the same range.
 - **WAV headers** (`MpckitWav`): 48 kHz 24-bit stereo, 44.1 kHz 16-bit stereo and mono, all with the extensible header, play the same as a plain-header control.
 - **WAV headers** (`MpckitWav`): 48 kHz 24-bit stereo, 44.1 kHz 16-bit stereo and mono, all with the extensible header, play the same as a plain-header control.
 
 
-Every item on the original 1.0 list (project name, pad sample assignment, sequence name, BPM, bar length, adding a sequence) plus pad tuning has now been written by these recipes and accepted by the device.
+Every item on the original 0.5.0 list (project name, pad sample assignment, sequence name, BPM, bar length, adding a sequence) plus pad tuning has now been written by these recipes and accepted by the device.
 
 
 ## 8. Open questions
 ## 8. Open questions
 
 
@@ -256,6 +256,6 @@ Every item on the original 1.0 list (project name, pad sample assignment, sequen
 2. Level and pan have not been seen changed; confirm they are written to `pad[n].mixable`.
 2. Level and pan have not been seen changed; confirm they are written to `pad[n].mixable`.
 3. `triggerMode` values (0 and 3 seen) and how they pair with `loopMode`.
 3. `triggerMode` values (0 and 3 seen) and how they pair with `loopMode`.
 4. When a sequence with notes is shortened, are notes past the new end kept or removed?
 4. When a sequence with notes is shortened, are notes past the new end kept or removed?
-5. Sample rates outside 22.05-96 kHz and 64-bit float are untested. (The extensible header was confirmed in checkpoint A; other WAV bit depths and sample rates are confirmed; non-WAV formats are out of 1.0.)
+5. Sample rates outside 22.05-96 kHz and 64-bit float are untested. (The extensible header was confirmed in checkpoint A; other WAV bit depths and sample rates are confirmed; non-WAV formats are out of 0.5.0.)
 6. What key and tempo to write for a sample mpckit imports (the device detects them; mpckit cannot yet).
 6. What key and tempo to write for a sample mpckit imports (the device detects them; mpckit cannot yet).
 7. Is `currentSequence` a slot number or an index into `sequences[]`? Every file seen has 0, and slot 0 is stored second, so the files cannot tell. mpckit treats it as a slot (build plan 4.3); checkpoint B4 should remove the current sequence and check which one the device opens.
 7. Is `currentSequence` a slot number or an index into `sequences[]`? Every file seen has 0, and slot 0 is stored second, so the files cannot tell. mpckit treats it as a slot (build plan 4.3); checkpoint B4 should remove the current sequence and check which one the device opens.

+ 2 - 2
internal/project/project.go

@@ -1,5 +1,5 @@
 // Package project is the mpckit domain model: one MPC Sample project and
 // Package project is the mpckit domain model: one MPC Sample project and
-// every edit 1.0 makes to it. Edits follow the verified recipes in
+// every edit 0.5.0 makes to it. Edits follow the verified recipes in
 // docs/xpj-format-and-scope.md section 3 and change only the fields those
 // docs/xpj-format-and-scope.md section 3 and change only the fields those
 // recipes name; everything else is written back as read.
 // recipes name; everything else is written back as read.
 package project
 package project
@@ -29,7 +29,7 @@ const (
 	DataSuffix    = "_[ProjectData]"
 	DataSuffix    = "_[ProjectData]"
 )
 )
 
 
-// Tuning ranges for 1.0 (format doc, open question 1a).
+// Tuning ranges for 0.5.0 (format doc, open question 1a).
 const (
 const (
 	MinCoarse, MaxCoarse = -24, 24
 	MinCoarse, MaxCoarse = -24, 24
 	MinFine, MaxFine     = -90, 90
 	MinFine, MaxFine     = -90, 90

+ 21 - 0
pull-requests/beta-0-5-0/plan-for-0-5-0-beta.md

@@ -0,0 +1,21 @@
+# Plan for 0.5.0 (beta) instead of 1.0
+
+Branch: `beta-0-5-0` → `main`
+
+## What changed
+
+The current milestones (M1–M9) produce a beta release for testing, 0.5.0, not 1.0. The milestones needed for 1.0, including beta testers using pre-1.0 versions, will be added later.
+
+- `docs/build-plan.md`:
+  - Every "1.0" is now "0.5.0": title, "What 0.5.0 is", "Not in 0.5.0", the design notes, and M9's done-when ("a tagged v0.5.0 build").
+  - A line under the title says 0.5.0 is a beta release for testing and the 1.0 milestones come later.
+- `docs/xpj-format-and-scope.md`: the same scope was labelled "1.0" there and the plan refers to it, so those labels are now "0.5.0" too. The scope heading notes it was agreed on 2026-11-10 as the 1.0 scope and renamed on 2026-11-11.
+- `internal/project/project.go`: two comments ("every edit 1.0 makes", "tuning ranges for 1.0") now say 0.5.0.
+
+Not changed: the format doc's "2.0" labels (features after this release), and the test comment calling start/end trim "a 2.0 feature". They can be revisited when the 1.0 milestones are planned.
+
+Merges cleanly with `m8-load-guard-rails`, which also edits the build plan (checked with a trial merge).
+
+## Testing
+
+Docs and comments only. `go vet ./internal/project` passes.