# 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_` (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_` 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 — one of the two sanctioned instrument→ext-state writes (`bake_wire`'s `rsbake_` request key is the other), 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). The fold reports `counted` — the live records still attributed to their `rsusage_*` keys — because the flattened path list cannot answer "who holds this"; `counted` is empty whenever `abortPrune` is set, since attribution is exactly what an unreadable record destroys. - **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:** `ownerNonce` is not persisted, so after save→reopen an instance cannot recognize its own prior-session usage record and unions forever. Reasoning, constraints, and the rejected candidates live in `docs/TODO.md` ("Persist ReaSampler 9000 instance identity…"). ## 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`, and its peer `extStateWriteLanded` — the ONLY verdict on whether a `SetProjExtState` write took, because that API's return is the size of the WHOLE extname's state and cannot speak for one key. Three shells bind it: the bake landing's answer/clear write-back, `saveToActiveProject`'s `banks` write, and the instrument bridge's two prefix-guarded writes. Its `nullopt` means ABSENT specifically, so every caller must route an Overflow read to its own "could not check" answer rather than folding it in. `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. - `bake_wire` — the resample bake's request/outcome pair on ONE per-instance key (`rsbake_`): the instrument writes a `BakeRequest`, invokes the extension's action synchronously, and reads the extension's `BakeOutcome` back over the same key inside that one call. Not a handshake — a call and a return, and it must not grow a claim protocol. Also the ONE home of the bake action's command-id suffix and of the leading underscore `NamedCommandLookup` needs but `rec->Register("command_id", …)` does not, so both artifacts name one action. `BakeStatus` values are WIRE INTEGERS: never renumber, only append, and an unrecognized value decodes as `Failed` rather than as the numeric default `Ok`. It owns BOTH ends' reading of that key, since the key's contents are the only evidence either side gets: `classifyBakeAnswer` (instrument side — six kinds, of which `Unanswered`, the request still sitting there untouched, separates "nothing wrote an outcome over our key" from a refusal — it does NOT identify a landing that never ran, since a skipped key and a rejected answer-write look identical from here) and `classifyBakeScan` + `kMaxRequestAgeSeconds` (extension side — the per-key Land / RefuseWrongProject / ClearStale / IgnoreUnreadable / IgnoreNotARequest verdict over every open tab, stated without a REAPER type so the multi-tab matrix is unit-provable). `BakeScanTally` + `describeBakeScan` + `describeBakeKey` are that same reading counted and spoken — the rationale lives at the types. **The report's absence is NOT evidence the landing never ran**, and no sentence either artifact prints may say it is: `answered` is pass-wide and counts a QUEUED write, so a pass can answer some other key while skipping ours, or have our own answer's `SetProjExtState` rejected. The summary is therefore silent only when the pass answered somebody, left no key unanswered, and every answer was READ BACK from its own key; `describeBakeKey` prints one line per enumerated key regardless, which is the only thing that names WHICH key — the counts cannot. The read-back verdict behind `BakeWriteProof` is `ext_state_read.h`'s `extStateWriteLanded`, above. `bakeLandingAfterPersist` is the ONE route to a `Banked` landing: every `Land` verdict is assigned `Unpersisted` and passed through it, so no shell path — a dedup hit least of all, since its target may be an entry the same pass just added — can claim the word without the pass's persist having CONFIRMED its bank write. What `Banked` claims is exactly that `persisted` input; the enum comment is that claim's one home. - `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 `classifyReaperSurface`, the prefix classifier mapping a `GetThingFromPoint` (info token, track-present) pair onto `core/ui/drag_out`'s `ReaperSurface`. Classifier ordering is load-bearing: the embed strip is matched before the `tcp`/`mcp` panel family, which now claims the WHOLE track panel rather than just its FX sub-elements. 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; `counted` carries key-attributed live records), `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. It lives here because it is a *wire format* with an instrument-side writer; the fold's output is consumed by `core/tracking`'s authority, which owns every consumer-facing decision built on it. ## 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.