1f24c4b095
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.
335 lines
24 KiB
Markdown
335 lines
24 KiB
Markdown
# Q-W0 Track 2 — architecture-smell audit (functional lens)
|
||
|
||
Static analysis of the whole `src/` tree (extension + `src/vst/`), 2026-07-28, branch
|
||
`pq-w0-audit`. Complement to the grep-verified SOLID audit (§2) and naming audit (§2b) in
|
||
`docs/product/code-organization.md` — this track reports the **functional** smells those did not
|
||
target: duplicated *algorithms* (not merely duplicated responsibilities), reinvented wheels,
|
||
poor abstractions, and leaky pure/shell boundaries. Findings already catalogued there (the four
|
||
god-modules, the 4× JSON `Parser`, fat headers, `promptText`/`mintBankId` duplication, namespace
|
||
flatness, naming families) are **not restated**; where a finding below touches the same file it
|
||
is because the functional mechanism is new.
|
||
|
||
Every claim below was verified by grep/read of the actual tree. Line numbers are as of this
|
||
audit's snapshot. Wave assignments reference the Q-W1..Q-W6 waves (landed history in
|
||
`docs/ARCHIVE.md`; sequencing in `docs/product/code-organization.md` §5).
|
||
|
||
---
|
||
|
||
## Findings
|
||
|
||
### T2-01 — 3× copy-pasted length-prefixed wire `Cursor`, with security-hardening drift
|
||
**Location:** `src/provenance.cpp:56–137`, `src/assignment_request.cpp:25–121`,
|
||
`src/sample_usage.cpp:25–89` (plus a fourth sibling: `src/vst/bank_sync.cpp:11–28`
|
||
`parseBankGeneration` re-rolls the same guarded decimal accumulate).
|
||
|
||
**Mechanism.** The `<len>':'<bytes>` ext-state wire idiom ("one grammar across every ext-state
|
||
seam", per sample_usage's own comment) is implemented as three near-identical `putField` +
|
||
`Cursor` copies — and they have **drifted on the hardening**. The two newer copies
|
||
(`assignment_request`, `sample_usage`) carry a 20-digit length cap and an overflow guard
|
||
(`len > (SIZE_MAX - digit) / 10 → fail`) plus the subtraction-first bounds check
|
||
(`len > s_.size() - start`). The oldest copy (`provenance.cpp:65–82`) has **neither**: a crafted
|
||
long digit run wraps `len` silently, and the additive bounds check `start + len > s_.size()`
|
||
can itself wrap, letting a wrapped length pass. Downstream, `parseFingerprint`
|
||
(`provenance.cpp:197–199`) calls `r.trackGuids.reserve(guidCount)` on an **unbounded** count
|
||
parsed by the equally unguarded `fieldSizeT` — a corrupt/crafted `Sample.provenance` string in
|
||
the bank JSON can drive `reserve(huge)` into `std::length_error`/`bad_alloc` thrown through the
|
||
shell. (`sample_usage` fixed exactly this with its `count > wire.size()/4 + 1` sanity bound,
|
||
`sample_usage.cpp:121`; the fix was never backported.) `std::string::assign` clamping keeps the
|
||
wrap short of UB, but the parse-integrity promise ("never UB, never a partial value") is upheld
|
||
in two copies and eroded in the third — the textbook cost of a duplicated algorithm.
|
||
|
||
**Severity:** High (the drift already produced a concrete robustness gap on a persisted,
|
||
user-editable input; the class of bug will recur with every new wire seam).
|
||
**Disposition:** **fix-now, split:** (a) backport the hardened `field()` + a count sanity bound
|
||
to `provenance.cpp` **in Q-W0** — small, pure, existing `provenance_tests` covers round-trip and
|
||
malformed-input paths; (b) the structural collapse (one shared `wire` codec module beside
|
||
`core/json`, all three seams + `parseBankGeneration` consuming it) belongs to **Q-W1**, which is
|
||
already the serialization-extraction wave. Rationale: the hazard is cheap to close now; the
|
||
dedup is a relocation-adjacent move that should ride the wave already creating `core/`.
|
||
|
||
### T2-02 — a FIFTH hand-rolled JSON decoder the §2 audit did not count
|
||
**Location:** `src/tail_control.cpp:88–130` (`valueAfterKey` + `deserializeTailSetting`).
|
||
|
||
**Mechanism.** The catalogued DRY violation is "JSON `Parser` duplicated 4×" (`bank_model`,
|
||
`bank_book`, `view_mode_model`, `owned_manifest`). `tail_control` carries a fifth, structurally
|
||
different JSON decode: a substring-scan reader (`json.find("\"key\"")` → skip ws → parse token).
|
||
It is correct for the flat single-object payload it reads (the file argues this honestly), but
|
||
it is a fifth place JSON-reading behavior is defined, with different tolerance semantics (a key
|
||
found inside a *string value* would match — impossible today only because the writer is its own
|
||
sole producer). If Q-W1 extracts `core/json` from the four `Parser`s and misses this site, the
|
||
"one JSON path" goal is silently not achieved.
|
||
|
||
**Severity:** Med (no live bug; a completeness gap in the already-planned fix).
|
||
**Disposition:** **fix-now, folded into Q-W1** — add `tail_control` to the Q-W1 consumer list
|
||
explicitly. Rationale: zero extra cost when `core/json` lands; a stray fifth decoder afterward
|
||
would be a defect of the wave.
|
||
|
||
### T2-03 — `readFileBytes` hand-rolled five times, both sides of the artifact split
|
||
**Location:** `src/capture.cpp:232`, `src/capture_realtime.cpp:329` (as `readAllBytes`),
|
||
`src/ingest.cpp:86` (comment admits: "of capture.cpp's readFileBytes"),
|
||
`src/vst/reasampler_processor.cpp:72`, and inline in `src/vst/reasampler_editor.cpp:749–756`.
|
||
|
||
**Mechanism.** The identical ifstream-binary-ate/tellg/read whole-file loader exists five times
|
||
(two spellings, one anonymous inline). Well past extract-on-third-occurrence, and the copies
|
||
already disagree cosmetically (name, empty-on-failure comment placement) — the next divergence
|
||
will be behavioral (e.g. one copy gaining a size ceiling the others lack).
|
||
|
||
**Severity:** Med.
|
||
**Disposition:** **fix-now, folded into Q-W1** — a trivial pure `readFileBytes` helper in the
|
||
`core/` utility home Q-W1 creates; both CMake targets link it. Rationale: five occurrences of a
|
||
ten-line function is pure debt with a zero-risk fix, but creating its home is exactly Q-W1's
|
||
job — doing it days earlier in the flat tree would just move the file twice.
|
||
|
||
### T2-04 — the growing `GetProjExtState` read loop, three copies, pure half only half-used
|
||
**Location:** `src/persist.cpp:140–157` (`getProjExtStateString`),
|
||
`src/vst/reaper_bridge.cpp:93–107` (self-described "mirrors persist.cpp's growing strategy"),
|
||
`src/usage_scan.cpp:168–181` (self-described "the persist.cpp idiom").
|
||
|
||
**Mechanism.** The grow-buffer-until-it-fits retry loop over `GetProjExtState` is implemented
|
||
three times, in three TUs, on both sides of the split. The fiddly part — interpreting the int
|
||
return against the filled buffer — is *already extracted pure* as
|
||
`bridge_marshal::decodeGetProjExtState`, but only `reaper_bridge` consumes it; `persist` and
|
||
`usage_scan` interpret `rv` inline with their own conventions (persist: `rv <= 0` → absent;
|
||
bridge: return AND non-empty buffer). The absent-vs-truncated-vs-empty semantics are precisely
|
||
the kind of edge that drifts when defined thrice. `usage_scan`'s copy is prune-safety-adjacent
|
||
(an unreadable usage record must abort the prune) — its read loop deserves the tested pure
|
||
decode, not an inline reimplementation.
|
||
|
||
**Severity:** Med.
|
||
**Disposition:** **fix-now, assigned to the downstream wave that opens `persist`**
|
||
(Q-W4 per the current wave map; whichever wave splits `persist.cpp` is the moment). Generalize
|
||
the retry policy (next-capacity/done decision) into `bridge_marshal` (or its `core/` successor)
|
||
and make all three loops consume it. Rationale: touching persist's session machinery outside
|
||
its own wave risks the highest-traffic shell for a dedup that has no live bug today.
|
||
|
||
### T2-05 — 19 rect structs + ~15 inline point-in-rect predicates across the pure UI family
|
||
**Location (structs):** `action_bar.h:61`, `bank_grid.h:23`, `card_drag.h:109`,
|
||
`component_geometry.h:28,53,106`, `drag_out.h:40`, `footer_bar.h:41`, `mode_switch.h:21,35`,
|
||
`overflow_menu.h:23,39`, `prune_button.h:32,46`, `tab_strip.h:24,51`, `tooltip.h:20`,
|
||
`vst/editor_geometry.h:19`, `vst/velocity_curve.h:124`.
|
||
**Location (predicates):** inline half-open `px >= r.x && px < r.x + r.width && …` re-typed in
|
||
`action_bar.cpp`, `bank_grid.cpp`, `card_drag.cpp` (×2), `component_geometry.cpp` (×2),
|
||
`drag_out.cpp`, `footer_bar.cpp`, `mode_switch.cpp`, `overflow_menu.cpp`, `prune_button.cpp`,
|
||
`tab_strip.cpp` (×2), `bank_panel.cpp:1049`, plus `vst/editor_geometry.cpp:20`.
|
||
|
||
**Mechanism.** §2b.2 catalogued the *naming/collision* half of this (the `footer_bar.h` "NAME
|
||
NOTE" hand-checking smell). The functional half is uncatalogued: nineteen structurally identical
|
||
axis-aligned `{x, y, w, h}` record types, each with its own hand-typed containment predicate.
|
||
Every new pure-UI module re-mints both. This is Daniel's heuristic (b) verbatim: N near-identical
|
||
concrete implementations that one shared type collapses at compile time — one `ui::Rect` + one
|
||
`contains(Rect, x, y)` free function (both already exist in embryo as `vst/editor_geometry`'s
|
||
`Rect`/`contains`), with per-module aliases or thin wrappers only where a struct carries extra
|
||
fields (e.g. `TabRect::index`). Zero runtime cost; deletes ~15 chances for the next half-open/
|
||
closed-interval inconsistency to slip in.
|
||
|
||
**Severity:** Med.
|
||
**Disposition:** **fix-now, folded into Q-W2** (the ui/ relocation wave) — collapsing the type
|
||
zoo is nearly free precisely when every one of these files is being moved and re-namespaced;
|
||
doing it pre-reorg would churn 19 headers twice. Rationale: same-moment-as-relocation is the
|
||
stated principle for renames (§2b intro); it holds identically for type unification.
|
||
|
||
### T2-06 — pure-computable layout math stranded in the VST editor shell (the §2 scope gap)
|
||
**Location:** `src/vst/reasampler_editor.cpp` — `SampleFaceLayout` (~line 922) and its builder,
|
||
`ClusterLayout` (~line 964), `zonesStripArea`/`noteEntryFieldsArea`/`noteEntryFieldRect`/
|
||
`zonesControlPanel`/`zonesDeckArea`/`zonesCurveButton` (lines 992–1042), the channel-toggle
|
||
segment rects (~line 1065), banner rect math (~line 1195), among ~49 inline geometry
|
||
computations across the 3,065-LOC TU.
|
||
|
||
**Mechanism.** The codebase's own grammar homes exactly this class of math in pure modules
|
||
(`editor_geometry`, `knob_deck`, `curve_popup`, `capture_browser`, …), yet the editor shell has
|
||
accreted a second, untested layout layer: whole named layout structs and pure `Rect → Rect`
|
||
functions that take only ints and rects, compiled into the one TU that cannot be unit-tested
|
||
without a host window. This is the mirrored form of the pure/shell leak (algorithm math living
|
||
untestable in a shell). Note also the audit-scope gap this exposes: §2's god-module catalogue
|
||
covered the extension tree only — `reasampler_editor.cpp` (3,065 LOC) and
|
||
`reasampler_processor.cpp` (1,164 LOC) repeat the bank_panel pattern on the VST side and appear
|
||
in no existing finding.
|
||
|
||
**Severity:** Med (no correctness bug found in the stranded math; the cost is untestability and
|
||
the growth trajectory — the editor gained ~500 LOC/phase through r11).
|
||
**Disposition:** **document-and-defer, with a named reshape:** Q-W0 should surface a downstream
|
||
point (the wave that opens `src/vst/`, or a new one) hoisting the Sample-face/Zone-panel layout
|
||
into the existing pure homes (`editor_geometry` is the natural owner). Rationale: a hoist is a
|
||
behavior-preserving mechanical move best done under the reorg's test discipline, not pre-reorg;
|
||
but it must be a recorded point or the layer keeps growing.
|
||
|
||
### T2-07 — the extension links the entire voice engine to serialize one preset blob
|
||
**Location:** `CMakeLists.txt:383–385` (`instrument_drop` → PUBLIC `sample_map`);
|
||
`src/instrument_drop.cpp` includes `vst/sample_map.h`, which pulls `sampler_core.h` →
|
||
`pitch_shift.h` + `velocity_curve.h`.
|
||
|
||
**Mechanism.** `instrument_drop` (extension side) deliberately reuses
|
||
`sample_map::serializeComponentState` so the `.vstpreset` payload and the instrument's own
|
||
reader cannot drift — the right DRY call, explicitly documented in CMake. But the shared writer
|
||
lives *inside* the module that also owns zone resolution, WAV decode plumbing, and (via header
|
||
fan-in) the whole voice engine — so `reaper_reasampler` compiles and links `sampler_core`,
|
||
`pitch_shift`, and `velocity_curve` object code it never executes. The abstraction is right; its
|
||
*granularity* is wrong: the ComponentState codec is not separable from the engine stack today.
|
||
|
||
**Severity:** Low (dead weight in the binary and a misleading dependency edge; no runtime cost —
|
||
heuristic (c) is about call chains, which this does not add).
|
||
**Disposition:** **document-and-defer to Q-W1/Q-W2 module-homing:** when serialization gets its
|
||
`core/` home, split a `component_state` codec module (types + serialize/deserialize only) out of
|
||
`sample_map`; both artifacts link the codec, only the VST links the engine. Rationale: purely
|
||
structural, zero behavior change, and exactly the kind of module-boundary decision the reorg
|
||
waves exist to make once, deliberately.
|
||
|
||
### T2-08 — WAV/RIFF byte-format knowledge spread across four modules, two chunk walkers
|
||
**Location:** `src/wav_trim.cpp` (canonical parse: `parseWavLayout`/`extractFloatFrames`),
|
||
`src/capture_paths.cpp:31–115` (a second, independent RIFF chunk walker for content hashing),
|
||
`src/ingest.cpp:108–160` (hand-built 32f WAV writer), `src/capture_realtime.cpp:423` (in-place
|
||
RIFF/data size patch).
|
||
|
||
**Mechanism.** The tree is disciplined about *decoding* ("no third WAV reader" — sample_map,
|
||
editor, processor all route through `wav_trim`), but RIFF *container* knowledge is still minted
|
||
per site: `capture_paths` walks chunks with its own tag/size/pad-byte logic to hash `fmt `+`data`
|
||
while skipping metadata; `wav_trim` walks the same container shape for layout; `ingest` writes
|
||
headers by hand; `capture_realtime` patches sizes by offset. Four places know the RIFF framing
|
||
rules (even-byte padding, chunk-header arithmetic); a drift in any one (e.g. pad-byte handling)
|
||
would desynchronize hashing from decoding — the dedup-by-hash and null-test invariants both sit
|
||
on this.
|
||
|
||
**Severity:** Low (all four are currently correct against each other by inspection; the smell is
|
||
the maintenance surface, not a live divergence).
|
||
**Disposition:** **document-and-defer** — consolidate into a `core/wav` home (walker + layout +
|
||
writer + patch) when the reorg assigns module homes. Rationale: pre-reorg consolidation churns
|
||
the capture hot path (§3 guardrail) for no functional gain; the reorg wave that relocates
|
||
`wav_trim` is the natural moment.
|
||
|
||
### T2-09 — the two capture backends' Sample-stamping epilogue is copy-paste with silent divergences
|
||
**Location:** `src/capture.cpp:490–537` vs `src/capture_realtime.cpp:505–540`.
|
||
|
||
**Mechanism.** The finished-capture metadata stamp — `trackGuids`, `channelCount`, `sampleRate`,
|
||
`Master_GetTempo`, the `TimeMap_GetTimeSigAtTime` block, the WAV-aware `hashWavContent` content
|
||
hash (comment block duplicated verbatim, ~10 lines), `createdTimestamp` — is written twice, once
|
||
per backend. The copies have already diverged in quiet ways: offline passes `proj = nullptr`
|
||
(active project) to `TimeMap_GetTimeSigAtTime` while realtime pins `st.proj_`; the sampleRate
|
||
fallback logic differs in shape; realtime overrides `lengthSeconds` post-hoc. Some divergence is
|
||
semantic (realtime's tail-trim length), but the shared stamp is one concept — a future field
|
||
(e.g. a new provenance stamp) must currently be added in two places, and the time-sig
|
||
active-project vs pinned-project asymmetry is exactly the kind of drift that produces a
|
||
wrong-project stamp during a background-project capture.
|
||
|
||
**Severity:** Med.
|
||
**Disposition:** **fix-now, folded into Q-W3** (the wave already hoisting capture orchestration
|
||
out of `main.cpp` / right-sizing `capture.h`). Extract a `stampCaptureSample(Sample&, const
|
||
CaptureRequest&, ReaProject*)` shared helper; the divergent bits (length override) stay in the
|
||
realtime caller. Rationale: the fix touches both backend TUs, which Q-W3 opens anyway; doing it
|
||
there keeps one review of the precision-invariant-adjacent code.
|
||
|
||
### T2-10 — the two thumbnail pipelines' cache-invalidation strategies have drifted
|
||
**Location:** `src/bank_panel.cpp:422–483` (extension: `computeThumbnail` + pure
|
||
`ThumbnailKey{id, width, generation}` via `bank_grid::thumbnailKeyString`) vs
|
||
`src/vst/reasampler_editor.cpp:715–789` (VST: `monoPcmFor` keyed by bare `sampleId`,
|
||
`thumbnailFor` keyed by ad-hoc `sampleId + "|" + binCount`, invalidated by wholesale
|
||
`clear()` at lines 159–160/894).
|
||
|
||
**Mechanism.** The dock panel and the VST browser render the same thumbnails through the shared
|
||
`peaks::computeEnvelope`, but the caching layer around it was re-designed independently on each
|
||
side: the extension bakes the bank generation into a *pure, tested* key type; the editor
|
||
hand-concats a string key with no generation and relies on call-site `clear()`s (bank-refresh,
|
||
resize). Both are correct **today** — but correctness on the editor side is distributed across
|
||
remembering every clear site, and the bin-clamp guard comment ("computeEnvelope pads binCount >
|
||
frameCount…") is duplicated verbatim in both TUs (`bank_panel.cpp:462`,
|
||
`reasampler_editor.cpp:780`), marking the copied design. A future refresh path that forgets the
|
||
clear shows stale waveforms with no test to catch it.
|
||
|
||
**Severity:** Low.
|
||
**Disposition:** **document-and-defer** — when T2-06's layout hoist opens the editor, adopt the
|
||
pure `ThumbnailKey` (or a shared `thumb_cache` helper) on the VST side. Rationale: no live bug;
|
||
unifying cache policy is a natural rider on the editor wave, pointless as standalone churn.
|
||
|
||
### T2-11 — ComponentState v1→v11 deserialize chain: sound, but the legacy branches triplicate the shared read
|
||
**Location:** `src/vst/sample_map.cpp:775–945` (`deserializeComponentState`).
|
||
|
||
**Mechanism.** Audited the full lift chain for functional soundness: the bounded `ByteReader`
|
||
latches on truncation, every version's tail fields carry per-field corrupt fallbacks
|
||
(previewVelocity → mid default, voiceCount → default-not-clamp, gain → unity, refs → keep-parsed
|
||
prefix), and the strict-prefix envelope discipline is honest. **No correctness finding.** The
|
||
smell is shape: the v3, v4, and v5 branches each re-implement the mode-byte → marker → idLen/id
|
||
→ zones read sequence that the v6+ shared path also implements (three near-copies of the same
|
||
cursor walk, lines 806–841 vs 851+), and each new envelope version adds another
|
||
`version >= kꞏꞏꞏV*Version` stanza to a function already ~170 lines long.
|
||
|
||
**Severity:** Low.
|
||
**Disposition:** **document-and-defer, explicitly.** The legacy branches are frozen back-compat
|
||
contract code with saved-project blobs as their only callers; rewriting them into a table-driven
|
||
lift risks the one thing they must never break, for zero user-visible gain. Record the pattern
|
||
so the *next* envelope bump (v12) prefers extending the shared path over minting another branch.
|
||
(The unbounded-suffix version-constant naming is §2b territory; not restated.)
|
||
|
||
---
|
||
|
||
## Surfaces checked and found clean
|
||
|
||
Recorded per the wave's no-silent-omission rule; each was read/grepped this audit.
|
||
|
||
- **Pure-module include hygiene, both trees.** Every module CLAUDE.md claims pure was scanned
|
||
for REAPER/SWELL/WDL/LICE/VST3-SDK includes: all clean, `src/` and `src/vst/` both. The one
|
||
grep hit in `pitch_shift.h` is a comment (the S16 WDL-exclusion note), not an include. The one
|
||
cross-tree include (`instrument_drop` → `vst/sample_map.h`) is pure-to-pure — see T2-07 for
|
||
the granularity concern; it is not a boundary violation.
|
||
- **`bank_sync`** — the generation/consume decision rules are pure, explicit, and exhaustively
|
||
commented (rules 1–4); `parseBankGeneration` is overflow-guarded (its duplication is rolled
|
||
into T2-01's family, not a separate defect).
|
||
- **`bridge_marshal`** — one honest job, done pure, with the S1 string-scan JSON reader
|
||
documented as retired (verified: no second JSON parser on the VST side; `sample_map` routes
|
||
through `BankBook::deserialize`).
|
||
- **The realtime record lifecycle** — *not* an implicit state machine: `RecordPhase` is an
|
||
explicit enum with pure per-tick transitions in `realtime_record.h`; `main.cpp` holds only the
|
||
handle + project pointer. (Its *residence* in main.cpp is catalogued §2.1; nothing functional
|
||
to add.)
|
||
- **Project-identity transitions** — `classifyProjectTransition` is pure (capture_paths), the
|
||
shell passes `sameProjectObject` as a bool to keep it so; the GUID-primary layering is
|
||
decision-tabled in one place.
|
||
- **`usage_scan`** — every decision delegated to pure `sample_usage`; container recursion is
|
||
depth-bounded with a protect-on-truncation fail-safe; the `std::function` parm-getter
|
||
indirection is prune-scan-cold (heuristic (c) satisfied — no hot-path chain).
|
||
- **Exception boundaries** — the three `catch (...)` sites (`bank_book.cpp:794` stol guard,
|
||
`instrument_drop_win.cpp:74` REAPER-callback boundary, `render_settings.cpp:180` stod guard)
|
||
are all documented, narrow, and non-swallowing in intent (each converts to an explicit
|
||
failure value). No silent error swallowing found.
|
||
- **`FxBypassGuard` (main.cpp) vs `view.cpp` park/restore** — both are snapshot-mutate-restore
|
||
over track flags and *look* like a dedup candidate; they are deliberately not one. Different
|
||
flag sets, different invariants (precision-neutralization vs Design-View parking), different
|
||
failure postures. Duplication of shape, not of concept — correctly left separate.
|
||
- **Path resolution** — `capture_paths::resolveBankFile` is the single resolver on both sides
|
||
of the split (panel, insert, drag_out, editor, processor). No parallel path logic.
|
||
- **WAV decode on the play path** — `wav_trim` is genuinely the only decoder (T2-08 concerns
|
||
the *container* knowledge spread, not a second decoder).
|
||
- **Draw layer** — the VST editor/embed compile the same `draw_kit`/`theme`/`component_geometry`
|
||
the extension uses (verified in CMake + includes); no parallel draw vocabulary grew on the
|
||
VST side.
|
||
- **Interface cost audit (heuristic (c))** — `ICaptureBackend` is the tree's only virtual
|
||
interface; two real implementations, dispatched once per capture (cold). No hot-path virtual
|
||
or std::function chain found in `sampler_core`/`pitch_shift`/`sample_map` (all static calls).
|
||
No interface-with-one-implementation found anywhere.
|
||
- **Boolean-parameter proliferation** — swept `src/` headers for multi-bool signatures; the only
|
||
hit is `bank_grid::applyClick(…, bool ctrl, bool shift, …)`, which mirrors physical modifier
|
||
keys and reads fine at call sites. Not a finding.
|
||
|
||
## Cross-checks against the §2/§2b audits (gaps noted, not restated)
|
||
|
||
- §2's evidence base is scoped to the extension tree ("45 files / ~19,800 LOC"); the full tree
|
||
is now ~39,000 LOC. The VST shells repeat the god-module pattern uncatalogued
|
||
(`reasampler_editor.cpp` 3,065 LOC, `reasampler_processor.cpp` 1,164 LOC) — carried here as
|
||
T2-06's scope note so the reorg waves size the `src/vst/` work realistically.
|
||
- §2.1's "4× JSON Parser" undercounts by one — T2-02 (`tail_control`).
|
||
- §2b.2's shared-rect naming hazard has an uncatalogued functional twin — T2-05.
|
||
|
||
## Summary table
|
||
|
||
| ID | Finding | Severity | Disposition | Where |
|
||
|-------|---------------------------------------------------------------|----------|-------------------|--------------|
|
||
| T2-01 | Wire `Cursor` ×3 with hardening drift; provenance unguarded | High | fix-now (split) | Q-W0 backport + Q-W1 dedup |
|
||
| T2-02 | Fifth JSON decoder in `tail_control` | Med | fix-now | Q-W1 |
|
||
| T2-03 | `readFileBytes` ×5 across both artifacts | Med | fix-now | Q-W1 |
|
||
| T2-04 | Growing ext-state read loop ×3; pure decode half-adopted | Med | fix-now | persist's wave (Q-W4) |
|
||
| T2-05 | 19 rect structs + ~15 inline point-in-rect predicates | Med | fix-now | Q-W2 |
|
||
| T2-06 | Layout math stranded in VST editor shell (+§2 scope gap) | Med | document-and-defer (named reshape) | src/vst wave |
|
||
| T2-07 | Extension links voice engine to share the preset serializer | Low | document-and-defer | Q-W1/Q-W2 homing |
|
||
| T2-08 | RIFF container knowledge in 4 modules / 2 chunk walkers | Low | document-and-defer | core/wav homing |
|
||
| T2-09 | Capture backends' Sample-stamp epilogue copy-paste w/ drift | Med | fix-now | Q-W3 |
|
||
| T2-10 | Thumbnail cache-invalidation strategies drifted across split | Low | document-and-defer | editor wave |
|
||
| T2-11 | ComponentState legacy lift branches triplicate the shared read | Low | document-and-defer | (pattern note for v12) |
|