docs: 1.0 documentation restructure
Split root CLAUDE.md into 19 per-directory files scoped to their source area. Roll v0 history into docs/ARCHIVE.md; retire CONTEXT.md, CONTEXT-ARCHIVE.md, PLAN.md, COMPLETED.md. Move plan docs under docs/. Rescue 9 live deferrals into docs/TODO.md.
This commit is contained in:
@@ -0,0 +1,94 @@
|
||||
# src/core/wire — pure ext-state and wire-format codecs, cross-artifact contracts
|
||||
|
||||
## Scope
|
||||
|
||||
The lexical/wire layer underneath the domain grammars: length-prefixed ext-state field
|
||||
codecs, the little-endian byte codec, the `GetProjExtState` grow-loop retry policy, the
|
||||
FOREVER-FROZEN VST3 class-UID macros, and the two mirror-image cross-artifact wires
|
||||
(`assignment_request` extension→instrument, `sample_usage` instrument→extension) plus
|
||||
the FX-drop payload builder (`instrument_drop`). Domain grammars themselves (what the
|
||||
fields *mean*) stay in their own modules (`bank_model`, `sample_map`, `provenance`,
|
||||
`bank_sync`, …) — this directory owns lexing/emitting/marshalling only, not domain
|
||||
semantics.
|
||||
|
||||
This directory owns two cross-artifact contracts specifically:
|
||||
- `reasampler_uid.h` is the FOREVER-FROZEN VST3 class-UID header shared by the runtime
|
||||
`FUID` (`reasampler_vst.h`) and the `.vstpreset` hex string (`instrument_drop`) — so
|
||||
binary identity and preset identity cannot diverge.
|
||||
- `assignment_request` and `sample_usage` are mirror-image wires: the former carries the
|
||||
drop payload extension→instrument, the latter carries usage records instrument→extension.
|
||||
|
||||
## Invariants
|
||||
|
||||
- **The VST-host bridge (the integration mechanism, stated once here).** A VST3
|
||||
hosted inside REAPER can call back into REAPER's own API by
|
||||
resolving function pointers by name over the host callback (`hostcb(&effect,
|
||||
0xdeadbeef, 0xdeadf00d, 0, "FunctionName", 0.0)` — the same string-keyed API table the
|
||||
extension uses; verified in `video_processor.h` and the `reaper_plugin_functions.h`
|
||||
`GetProjExtState`/`SetProjExtState`/`EnumProjExtState` entries). The plugin can also
|
||||
fetch its host context — the track/take/project it was instantiated in (opcode
|
||||
`0xdeadf00e`). Consequence: the instrument reads the *same* live `"reasampler"`
|
||||
ext-state that `persist` writes, follows the active project, and needs no
|
||||
"point me at the bank folder" wiring — it asks REAPER which project it is in. Confirm
|
||||
the bridge opcodes and by-name resolution against the vendored `vendor/reaper-sdk/sdk/`
|
||||
headers (`reaper_plugin.h`, `video_processor.h`, `reaper_plugin_functions.h`) before
|
||||
relying on new opcodes.
|
||||
|
||||
- **Instance-usage wire — `rsusage_<instanceGuid>` (pS-usage; pure portions only —
|
||||
the extension-side scan shell `usage_scan` and the prune-abort behavior are owned
|
||||
by the `shell/persist` layer, a parallel dispatch).**
|
||||
Each VST3 instance holds a per-instance GUID persisted in `ComponentState` v11
|
||||
(`instanceGuid` field; pre-v11 blobs mint the guid on first publish). At the tail of
|
||||
every `reloadInstrument` call (off audio thread) the processor publishes its held
|
||||
`SampleRefs` paths to the ext-state key `rsusage_<instanceGuid>` in the `"reasampler"`
|
||||
namespace via `reaper_bridge::writeUsageExtState` — an entry point that is
|
||||
**prefix-guarded** (accepts only `rsusage_`-prefixed keys, refuses all others), so the
|
||||
read-only-bank invariant is structurally enforced. Direction: the instrument writes
|
||||
usage keys; the extension reads them — the one sanctioned instrument→ext-state write,
|
||||
a deliberate exception analogous to `assignment_request` on the other wire.
|
||||
Usage records are **never cleared by the instrument at teardown** (REAPER destroys the
|
||||
plugin instance when an FX chain is set offline, including Design View's CPU-park, so a
|
||||
terminate-time clear would strip a still-live instance's record); liveness is decided
|
||||
extension-side at prune-scan time.
|
||||
- **The pure fold (`sample_usage::foldUsageRecords` / `usageHeldPaths`).** A record
|
||||
counts iff its publishing track still hosts at least one instance (offline FX
|
||||
included); a record with no track context counts while any instance exists; and when
|
||||
records exist but zero instances were identified, **every** record's paths are
|
||||
protected (identity-failure net — a matcher failure must never degrade toward
|
||||
delete). The guarantee: a capture held by any live instance can never be deleted; if
|
||||
the prune cannot determine with certainty which captures are held, it aborts
|
||||
entirely (deletes nothing). Over-protection is the accepted residual; under-protection
|
||||
is a data-loss bug.
|
||||
- **Collision safety (`planUsagePublish`).** A persisted GUID is copyable (FX copy /
|
||||
track duplication). `ownerNonce` — a per-lifetime nonce minted fresh in memory at
|
||||
instance creation, never persisted — proves "exactly this incarnation wrote the key
|
||||
last." `unioned` — a sticky multi-writer poison: once a same-track sibling is
|
||||
detected, the key enters union-forever mode (holds only accumulate, never drop).
|
||||
Resolution always leans over-protect: same-nonce + not-unioned → clean replace;
|
||||
same-track foreign nonce or unioned → union; cross-track foreign nonce → remint under
|
||||
a fresh key. None of the three directions can under-protect.
|
||||
- **Deferred follow-up (TODO.md, not this dispatch's scope):** `ownerNonce` is not
|
||||
persisted, so after save→reopen an instance cannot recognize its own prior-session
|
||||
usage record — it unions and marks the record `unioned` forever, so prune stops
|
||||
reclaiming captures the instance once held but no longer uses (safe, but the bank
|
||||
folder grows unbounded). Persisting the nonce is deferred because a persisted nonce
|
||||
would be inherited by a Ctrl+D in-place FX duplicate, and a divergent clone must
|
||||
still be detected and protected fail-safe without reintroducing the sibling-drop bug.
|
||||
|
||||
## Modules
|
||||
|
||||
- `wire` (`core/wire`) — the ONE length-prefixed ext-state wire codec (Q-W1): `putField`/`parseUnsignedDecimal` + the bounds-checked `Cursor` (`field`/`fieldInt`/`fieldInt64`/`fieldSizeT`/`fieldDouble`), replacing four near-identical copies (`provenance` / `assignment_request` / `sample_usage` / `bank_sync`). `core/wire/bytes.h` is the sibling little-endian byte codec (`putLE`, `ByteReader`, `doubleToBits`/`bitsToDouble`) that `component_state_io` is the biggest consumer of. `core/wire/ext_state_read.h` owns the `GetProjExtState` grow-loop retry policy (Absent/Complete/Overflow) shared by `persist`, `usage_scan`, and `reaper_bridge`. `core/wire/reasampler_uid.h` (the FOREVER-FROZEN VST3 class-UID macros) also lives in this directory.
|
||||
- `reasampler_uid.h` — SDK-free header owning the FOREVER-FROZEN VST3 class-UID integer macros (stable + beta pairs, `REASAMPLER_PROC_UID_*` / `REASAMPLER_PROC_UID_BETA_*`) and the `REASAMPLER_ACTIVE_UID_*` channel-selector macros. Split out of `reasampler_vst.h` so the pure extension side (`instrument_drop`) can derive the `.vstpreset` class-ID hex string without pulling in the VST3 SDK. Both `reasampler_vst.h` (runtime `FUID`) and `instrument_drop` (preset hex string) source from this single header — the binary identity and the preset-file identity cannot diverge.
|
||||
- `assignment_request` — pure ingest-assign wire: typed request record carrying the drop payload from the `ingest` shell through to the VST3 bridge.
|
||||
- `instrument_drop` — pure FX-drop payload builder: constructs a Steinberg-format `.vstpreset` image (channel-active class ID + the instrument's own component state, capture pre-selected) the shell applies via `TrackFX_SetPreset`; owns the `infoNamesFxHotspot` prefix classifier for `GetThingFromPoint` tokens. All-or-nothing contract — caller rolls back via `TrackFX_Delete` on any failure.
|
||||
- `sample_usage` — instance-usage wire: `UsageRecord`, `planUsagePublish` (fresh/heal/clean-replace/union/remint publish plan), `foldUsageRecords`/`usageHeldPaths` (liveness fold — protect-all when records exist but no instance is live; abort→protect-all on unreadable record), `identityMatches` (ReaSampler 9000 FX identity). REAPER-free, unit-tested. The mirror of `assignment_request` on the instrument→extension direction: the wire format and the two safety-critical decisions (what to write on publish, which records count at prune time) are pure so they are provable without a DAW.
|
||||
|
||||
## Gotchas
|
||||
|
||||
- `bytes.h`, `ext_state_read.h`, and `reasampler_uid.h` are header-only (no sibling
|
||||
`.cpp` / no dedicated `_tests` target of their own) — they are consumed directly by
|
||||
the modules named in their bullets above; don't go looking for a standalone build
|
||||
target for them.
|
||||
- `sample_usage` is deliberately silent on liveness *enumeration* (which FX instances
|
||||
are currently live) — that scan lives in `shell/persist`'s `usage_scan`, not here. This
|
||||
directory owns only the wire format and the two pure fold/collision decisions.
|
||||
Reference in New Issue
Block a user