# M1: xpj container and ordered JSON tree Branch: `m1-xpj-tree` → `main` ## What changed - New Go module `git.monoexp.com/bajdev/mpckit` (`go 1.24`). - New package `internal/xpj` (no Wails, no domain knowledge), per build plan 4.1: - **Container** (`file.go`): gunzip, split off the five header lines, parse the body; write header + body, gzip level 6 with mtime 0, XFL 0, OS 3 (the same 10 header bytes the device writes). `ReadFile` / `WriteFile`, `Data()`, `Clone()`. - **Ordered tree** (`node.go`, `json.go`): parsed token by token with `json.Decoder` + `UseNumber()`. Objects keep key order (and duplicate keys); numbers keep their original text. Written in the device layout: one key or value per line, no indentation, `": "` after keys, `[]` / `{}` for empty containers, no trailing newline, `< > & /` not escaped. - **New values**: `NewInt` (`4`), `NewFloat` (always `96.0`, never `96`; Python-repr shortest form, matching the reference writer), `NewFloat32` (rounds to float32 first: 12.4 → `12.399999618530273`), plus string/bool/null/array/object. - **Helpers**: `Get` / `Set` by path (`tracks[0].program.drum.instruments[4].layersv[0]`), `Lookup` / `SetKey` / `DeleteKey`, array `Insert` / `Append` / `Remove`, deep `Clone`, `Int()` / `Float()` accessors. - Moved `demo-projects/` to `testdata/device/` (build plan section 3 and 10.1); updated the two doc references. ## Notes - Only the decompressed bytes are guaranteed identical. The compressed bytes differ from the device's (different deflate implementation); the device accepted the Python writer's gzip output, which uses the same header bytes as ours. - String escaping follows the reference writer (Python `json`, `ensure_ascii=False`). None of the device files contain an escape, so the device's own escaping style is unknown. - Writing a NaN/Inf float panics, and a Number node with invalid text fails at write time rather than producing a bad file. ## Testing `go vet ./...` and `go test ./...` pass. - **M1 done-when**: `TestRoundTripDeviceProjects` reads DemoBlank, Demo02 and DemoFile and re-encodes each byte-identical to the decompressed original (about 3 MB each), both directly and through gzip and back. - The same passes for the device-accepted files in `testdata/accepted` (T1-T3) and `testdata/wav-formats` (W1, W2). - Shape check on DemoBlank: tested header, 66 data keys, `data.version` 28, 128 pads, "Sequence 01" at 128 BPM. - Unit tests for layout, key order, escapes, parse errors, invalid number text, float formatting against Python repr values, paths, array edits and clone independence. ## Next M2: edit operations in `internal/project`, matching `MpckitT1.xpj` / `MpckitT3.xpj` and rebuilding `Demo02`.