docs: correct two false claims in the Phase Ε spec

REAPER's GetUserFileName serves both pickers — the no-save-picker finding was a
regex miss. A new enum value is structural, not additive: BankModel rejects an
out-of-range SourceMode/Tier rather than degrading.
This commit is contained in:
2026-08-02 12:38:41 -04:00
parent 2f8e88880e
commit 2cb0b52884
2 changed files with 83 additions and 35 deletions
+33 -13
View File
@@ -188,12 +188,28 @@ So the package header carries two integers:
- **`minReaderVersion`** — the oldest reader that can read this package *safely*.
Bumped only when a change is **structural** (a field's meaning changes, a section
is removed, framing changes); left alone when a change is **additive** (a new
optional manifest key, a new enum value with a defined degrade).
optional manifest key, a new `Sample` field with a defined absent-value — exactly
the four listed above).
The reader's rule is one line: **read it iff
`minReaderVersion <= kPackageFormatVersion`.** `formatVersion` is then only for the
message text and the log.
**One change class that looks additive and is not: a new enum value.**
`BankModel::deserialize` *rejects* an out-of-range `SourceMode` or `Tier` rather than
degrading it (`bank_model.cpp:232-239`, `:339-346`), and every enum a package carries
rides inside the nested `BankModel` blob. So growing either vocabulary is
**structural** and bumps `minReaderVersion` too. This is wider than packages and
predates them: `BankModel::deserialize` is also the live project ext-state parser
(`bank_book_json.cpp:99`), so appending a `SourceMode` value already strands an older
build opening a newer project's `.rpp`. Phase Ε inherits that property; it did not
cause it, and changing it — degrade-to-`Unknown` at those two sites, the way
`BakeStatus` already does — is a change to the model layer, not a package concern. It
leaves the argument above untouched: the four fields that motivated the two-integer
design are *fields*, and `parseSample`'s `skipValue()` fallback
(`bank_model.cpp:370-372`), plus the manifest parsers' equivalent at each level, still
carries them forward.
This is a borrowed pattern, not an invention: Matroska's `EBMLVersion` /
`EBMLReadVersion` pair, PDF's catalog `/Version` over the header version, and OOXML's
`mc:Ignorable` markup-compatibility mechanism all separate "what I am" from "what you
@@ -211,8 +227,9 @@ keys take their defined defaults, exactly as `Sample`'s additive fields already
and exactly as `origin_ledger` lifts a v1 path-only blob into v2 records with kind
`Unknown` and empty ids (`origin_ledger.cpp:14-16`). Unrecognized manifest keys are
skipped, which is already how every parser in this repo behaves
(`bank_book_json.cpp:182`). Unrecognized enum integers degrade to their defined
`Unknown`-equivalent, never to the numeric default and never to a parse failure —
(`bank_book_json.cpp:182`). Unrecognized enum integers (the manifest's own —
`BankModel`'s nested ones reject) degrade to their defined `Unknown`-equivalent,
never to the numeric default and never to a parse failure —
`bake_wire`'s rule verbatim (`src/core/wire/CLAUDE.md:83`: "an unrecognized value
decodes as `Failed` rather than as the numeric default `Ok`").
**The user sees:** a normal import summary. Optionally a single console line naming
@@ -617,16 +634,19 @@ responsibility seam, which is what the structural heuristic asks for.
- `package_io` — read a package file to bytes, write bytes through temp + atomic
rename, read a bank file's bytes, write a landed file, enumerate existing bank-folder
names, and execute the rollback delete (citing the `prune_fs` carve-out).
- The platform file pickers, under the `#ifdef _WIN32` / `#else swell/swell.h` split
this codebase already uses (`src/shell/panel/draw_kit.cpp:11-15`,
`src/shell/persist/prune_fs.cpp:35-38`). **Verified:** the REAPER API offers a *read*
picker — `GetUserFileNameForRead(char* filenameNeed4096, const char* title, const
char* defext)`, `reaper_plugin_functions.h:3798` — and **no save picker at all**
(a sweep of the header for `FileNameFor|SaveFile|Browse` returns only that one
entry). Export's destination picker therefore comes from Win32 `GetSaveFileNameW`
on Windows and SWELL's `BrowseForSaveFile` elsewhere
(`vendor/WDL/WDL/swell/swell-functions.h:167`). This is a real asymmetry between
the two verbs and the spec should not paper over it.
- The file picker, which is REAPER's own on every platform — no `#ifdef _WIN32` /
`#else swell/swell.h` split, no Win32 `GetSaveFileNameW`, no wide-char round trip.
**Verified:** `GetUserFileName(int mode, const char* caption, const char*
initial_file_or_path, const char* extension_list, char* fnOutNeedBig, int
fnOutNeedBig_sz)` `reaper_plugin_functions.h:3790`, documented at `:3788` — serves
both verbs symmetrically: `mode=0` chooses a new file (export's destination),
`mode=1` an existing one (import's source). `extension_list` takes the
`'ReaSampler banks|*.rsbank|All files|*.*'` form. `GetUserFileNameForRead` is
explicitly "Superseded, see GetUserFileName" (`:3796`) and is not used. No fallback
is needed: `src/app/main.cpp:15` defines `REAPERAPI_IMPLEMENT` without
`REAPERAPI_MINIMAL`, so the resolver walks the full table (`GetUserFileName` at
`:9084`), and `main.cpp:292-293` refuses to load the extension if any one function
fails to resolve — so no REAPER build that loads us can lack it.
- `export_bank` / `import_bank` — the promptless verbs, mirroring
`src/shell/bank_ops/`'s pattern exactly: take a `ReaSamplerSession&`, do the work,
return an outcome, **no prompts and no message boxes**. The bindable action and the