Files

31 KiB
Raw Permalink Blame History

Q-W0 Track 4 — structural sizing + placement audit

Date: 2026-07-28 · Branch: pq-w0-audit · READ-ONLY static analysis (no build, no code edits)

Method. Line counts measured with wc -l on the worktree; seams derived from function-definition skeletons (grep for top-level definitions + section markers) plus targeted reads. Acceptance bar = Daniel's three heuristics: (a) more directories a must, files ≤ ~600 lines, SRP applies to files and namespaces; (b) templates are good where they dedup at compile time; (c) saved CPU beats abstraction — no dispatch-stack blowouts, prefer static polymorphism where types are compile-time-known.

Measured sizes differ from the wave brief in several places (the tree moved after the brief was drafted — GA/pS/pS-usage landed): reasampler_editor.cpp 3065 (brief said 3035), reasampler_processor.cpp 1164 (1004), sampler_core.cpp 968 (1049), sample_map.cpp 970 (807), sample_map.h 708 (600), sampler_core.h 762 (820), actions.cpp 1016 (996), persist.cpp 852 (812). All numbers below are the measured ones.


1. Oversize census + seams

Every .cpp/.h in src/ (both sides) over ~600 lines, with the real responsibility clusters. Where a file is genuinely one responsibility, I say so and recommend leaving it.

1.1 The four planned splits — do the plan's seams still land sub-600?

T4-01 — src/bank_panel.cpp (3459; plan assumed 2424). The plan's six seams (panel_render / panel_thumbnails / panel_audition / panel_input / panel_bank_ops / panel_window) no longer all land sub-600 at current size. Tally against the skeleton:

Planned TU Functions (line spans) Est. LOC Verdict
panel_thumbnails computeThumbnail/thumbnailFor (420487) ~130 fine
panel_render drawCardMeta/drawThumbnail (487547), kit adapters (547570), drawFooter (685780), drawToolbar/drawMoreButton/drawTooltip (9921155), drawRegionGrid/drawCardDropTarget/drawRegionHeader/drawTabStrip/paintPanel (13341646) ~700 over — needs the layout cut below
(unplanned) panel_layout toolbar/footer/menu rect + row/cluster builders (571684, 785991), split geometry + region rects + L7 slot-order display bridge (11561333) ~500 new TU required — this is pure-ish geometry glue, distinct from LICE drawing; extracting it puts panel_render at ~550
panel_audition initPreview/startAudition/stopAudition/deinitPreview (18741965) ~90 fine (keep the direct call-through guardrail)
panel_input input helpers + regionAt (19652010), click routing handleBanksChromeClickhandleKey/accelerator (23842710), plus new-content detection (16471874, ~230) ~800 over — needs the drag cut below
(unplanned) panel_drag updateDropTarget/dropTargetBankId/classifyCardDrag/applyDragCursor/resolveHover/updateHover/maybeShowTooltip/onMouseMove/doReorderDrop/doReplaceDrop/resetDragState/onLBtnUp/handleRightClick (27113185) ~475 new TU required — the card-drag/hover state machine is a cohesive cluster of its own (it already has a pure mirror, card_drag); extracting it puts panel_input at ~550
panel_bank_ops promptText/mintBankId/doCreateBankremoveSamples/focusedSelectionIds/resolveDragPathsForOs (20062231) + popup menus (22312384) ~375 fine (menus ride with bank_ops or input — either works; they invoke the ops)
panel_window handleDropFiles/dlgProc/openPanel/closePanel (31853336) + public API (33363459) ~275 fine

Proposal: eight TUs, not six — add panel_layout and panel_drag. Severity: high (it is the biggest file in the repo). Disposition: reshapes wave Q-W2 — the wave brief must name eight seams, or two of its six TUs ship >600 on day one.

T4-02 — src/main.cpp (1897; plan assumed 1762). The plan's three hoists are the right seams, but capture_orchestrator as specced lands ~885 lines — over by half again. Tally: FxBypassGuard (627731, ~105), renderOffline + captureAndIndexOne + RunCapture + RunCaptureItemAssign (731909, ~180), batch family (ItemSelectionGuard/selectOnlyItem/RunBatchCaptureItems/collectRazorAreas/ TrackSelectionGuard/RunBatchCaptureRazor, 9091181, ~270), RunRecaptureFromSource (12001398, ~200), realtime + insert actions (13981512, ~115). scope_resolve (361590) ≈ 230 ✓; realtime_lifecycle (186360) ≈ 175 ✓; registration/entry residue (15121897) ≈ 385 ✓ (shrinks further under Q-W6's table). Proposal: split the orchestrator seam once more: capture_orchestrator (FxBypassGuard + single-capture path + realtime/insert action bodies, ~450) and capture_batch (batch family + RunRecaptureFromSource + the two selection guards, ~470). Recapture is planner-driven like batch and shares the selection-guard machinery — it belongs with batch, not the single-shot path. Severity: high. Disposition: reshapes wave Q-W3 — add the fourth TU to the brief.

T4-03 — src/actions.cpp (1016; plan assumed 981). Plan's seams still land: design_view_actions (59421, ~360), bank_actions (4221016 minus prune, ~490), prune_action (doBankPruneFolder 821916 + registration share, ~130). All sub-600. Disposition: no change to Q-W4.

T4-04 — src/persist.cpp (852; plan assumed 766). Plan's seams still land: ext_state_io (helpers + saveToActiveProject + writeAssignmentRequest, 112288, ~180), prune_fs (scanPruneOrphans/pruneDryRun/pruneOrphanSet/deleteOrphanFile/ pruneReclaim, 288539, ~250 — pS-usage growth landed here, exactly where the plan isolates it), session (load/guid/poll/reload, 539852, ~315). All sub-600. Disposition: no change to Q-W5.

1.2 Known offenders beyond the planned four — extension side

T4-05 — src/bank_book.cpp (1109) + bank_book.h (457). Three genuine seams: SlotMap (27143, ~115 — a self-contained ordered-slot container with its own serialize at 614), BankBook registry/CRUD/transfer/slot-reconcile (145545, ~400), and JSON serialize + Parser (5471109, ~560). Q-W1 deletes the Parser + ObjWriter/writeEscaped copies; what remains of serialization rewired onto core/json is ~150. Proposal: after Q-W1, split slot_map into its own TU/header pair (it is a distinct type with its own tests-worthy invariants); bank_book.cpp lands ~550. Severity: medium. Disposition: fold into Q-W1 (the JSON rewire already opens this file; the slot_map file split is one git mv-shaped extraction on top).

T4-06 — src/view_mode_model.cpp (1049) + view_mode_model.h (748). Four seams: indexes (ModeRegistry/MembershipIndex/LaneOwnershipIndex, 29140), pure planners (autoTagNewContent/planItemRetag/planLaneMinting/makeParkPlan/makeRestorePlan/ nextModeId, 143326, ~185), ViewModeModel state + visibility/toggle planning (332490), and JSON serialize + Parser (4941049, ~555). Q-W1 deletes the Parser (~390); remainder ~660. Proposal: split planners (view_plan.cpp) from model+indexes (view_mode_model.cpp, ~450 after JSON extraction). The header's 26 structs split the same way: mode/membership/lane types + model class vs. the plan-record structs (TrackPlan/TogglePlan/AutoTag/ItemRetagOp/LaneMint*). Severity: medium. Disposition: fold into Q-W1 (JSON rewire opens the file; planner split rides it). If the wave wants to stay minimal, the planner split can defer — post-extraction ~660 is marginal, not pathological.

T4-07 — src/bank_model.cpp (767). Two seams only: the model (Sample equality + BankIndex verbs, 25145, ~120) and JSON (ObjWriter/writeSample/Parser::parseSample/parseIndex, 148767, ~620). This file is the poster child for Q-W1: after the extraction it is ~250 total (model + thin serialize using core/json). Disposition: already owned by Q-W1; no new seam needed.

T4-08 — src/capture_realtime.cpp (867). Two seams: the async record lifecycle (RealtimeCaptureState snapshot/restore, begin/tick/ abort, 177324 + 569867, ~450) and the file-side finalize (readAllBytes/writeU32LE/ trimAutoTailInPlace/finalizeRecording, 324566, ~240). The lifecycle is genuinely one responsibility (the header itself documents why the restore lives on the state object). The finalize half — WAV byte-patching, decay-scan trim, move-into-bank — is a distinct concern that talks to wav_trim, not to the transport. Proposal: split capture_realtime_finalize.cpp (~240); lifecycle TU lands ~600 with the file banner. Both stay in shell/capture/. Severity: low-medium. Disposition: ride Q-W3 (the wave already renames this family per the Q-9 naming rider — same-wave file surgery is free).

T4-09 — src/view.cpp (677). Two halves: park/restore + flag application (snapshotTrack/applyFlags/parkFxOffline/ restoreFxOffline/applyMode, ~350) and lane management (laneName/managedLaneOrdinals/ applyLanePlays/applyLaneOps/readLaneTracks/assignItemToLane/applyMintPlan/ mintManagedLanes/reconcileManagedLanes, ~330). The D2 lane machinery arrived after the file's original charter and is a separable concern. Proposal: split view_lanes.cpp. Severity: low (677 is barely over). Disposition: document-and-defer unless Q-W1's relocation is already touching it — then take the free split.

T4-10 — src/ingest.cpp (636). Seams: pure-ish WAV/PCM helpers (buildFloat32Wav/decodePcmSource + byte I/O, 86217, ~130 — see T4-22: buildFloat32Wav is a pure function trapped in a shell TU), importFileIntoActiveBank (249420, ~170), and the three ingest surfaces + registration (420636). Extracting the pure WAV build into a testable core module (wav_write beside wav_trim, or one wav_codec) drops the shell to ~500 and gains a test target. Severity: low-medium. Disposition: fix in whichever wave lands the WAV consolidation (T4-22); the file split itself is a rider.

1.3 Known offenders — VST side (entirely absent from the current plan)

T4-11 — src/vst/reasampler_editor.cpp (3065) + reasampler_editor.h (539). The single biggest unplanned file — it grew past main.cpp in the r11 recomposition, after the plan was written. It is now the bank_panel.cpp of the VST artifact, with the same god-module profile. Real seams, from the skeleton:

Proposed TU Functions (line spans) Est. LOC
editor_session ctor/dtor, refreshFromBank/rebuildVisible/onSyncTimer/commitAndReload/loadSelection/upsertPickedOverride/effective-zone helpers (146400), PCM + thumbnail caches monoPcmFor/thumbnailFor (719800) ~420
editor_controls control-value map controlValue/applyControl (399475), knob-deck plumbing zoneDeckGroupDescs/deckGroupDescs/deckControlNorm/applyDeckKnob/deckValueLabel (475649), envelope pack/unpack + commitPickedMarkers (649719), applyZoneControl (25762587) ~470
editor_layout (pure candidate — see T4-23) anon-ns geometry: computeSampleBands/clusterRects/zone-panel areas/channelToggleRects (9041076), computeBrowseModal (17561785), zoneContentArea (19171924) ~250
editor_paint_sample paint dispatch (1169), drawTitleBand, paintSample/paintEnvelopeOverlay/paintVelocityCurve/paintKnobDeck/paintCurveButton/paintCurvePopup/paintEmptyState + drawKnobFace/drawSpectralStrip/drawRootMarker (10761756) ~590
editor_paint_browse_zone paintBrowse (17851917), paintZone (19242041) ~260
editor_input resolveHover (20412145), onMouseDown (21472576 — a 430-line per-face dispatch), popup/curve mouse (16371735), onMouseMove/onMouseUp/onMouseRDown/onMouseWheel/onSearchChar/onFilesDropped (25872929) ~880 → split by face: editor_input_sample (~500: sample-face hit branches + drag state + curve popup) and editor_input_browse_zone (~380: browser cards/scroll/search + zone strip/note entry)
editor_platform IPlugView overrides isPlatformTypeSupported/canResize/checkSizeConstraint/attachedToParent/removedFromParent/onSize/invalidate (800904), wndProc + non-Windows stubs (29293065) ~280

Seven-to-eight TUs, all sub-600, each a real cohesive cluster (session/bridge state · param plumbing · layout · paint × 2 · input × 2 · platform). The face structure (Sample / Browse / Zone) is the natural input/paint split axis — it mirrors how the code already dispatches. Severity: highest of the audit — this is the largest unowned file in the repo. Disposition: reshapes the wave plan — needs a new owning wave (see §1.5 / summary table).

T4-12 — src/vst/reasampler_processor.cpp (1164) + reasampler_processor.h (502). Four seams: VST3 lifecycle + bus boilerplate (queryInterfacesetupProcessing, setBusArrangements, 118209 + 492506, ~120), component-state I/O (setState/getState + legacyLiftShouldRun, 209349 + 774904, ~270), accessors/param setters (349492, ~140), and reload + drain + usage-publish + render (reloadInstrument/publishUsage/ publishBuiltLocked/rebuildVoiceEngine/retireIdleDrain 506774 ~270, process 9041164 ~260). Proposal: three TUs — processor_state (state I/O + accessors, ~410), processor_reload (reload/drain/usage, ~290), reasampler_processor.cpp (lifecycle + process(), ~400). Keep process() and its block-render helpers in one TU (heuristic c — see T4-27). All are member functions of one class; partial-class-across-TUs is the same pattern the Q-W2 panel split uses. Severity: medium-high. Disposition: new VST wave (see §1.5).

T4-13 — src/vst/sample_map.cpp (970) + sample_map.h (708). Two clean halves plus a small third: resolution core (bank-JSON distill/select/list, SampleRefs management, PCM downmix/extract/decode, resolvePlay, keymap builders, performance-map resolution + reconcileSingleCaptureZones, 14406, ~390) and binary component-state codec (putU32le/ putU64le/ByteReader, zones payload put/read, serializePerformance/deserializePerformance, serializeComponentState/deserializeComponentState v3→v11 lift ladder, selection codec, 406970, ~565). Proposal: split component_state_io.cpp (the codec, ~565) from sample_map.cpp (resolution, ~400). The header splits the same way: wire/state structs (ComponentState/SampleRefEntry/codec decls) vs. resolution API (SelectedSample/PerformanceMap/ZonePlaySeconds/resolvers). This is the same shape as the extension's model-vs-JSON split and directly reduces rebuild fan-out — the editor and processor both include sample_map.h today and recompile on every codec tweak. Severity: medium-high (the codec grows every ComponentState version bump — v6→v11 in one quarter; it will cross 600 on its own soon). Disposition: new VST wave.

T4-14 — src/vst/sampler_core.cpp (968) + sampler_core.h (762). Contents: pitch math (1530), Keymap (3458), AdsrEnvelope/TriggerEnvelope/PitchEnvelope (62251, ~190), Voice (255680 — advanceFrame alone is ~200), VoiceEngine (680968, ~290). This TU is a genuine single responsibility — the realtime voice engine — and it is the hottest code in the repo. Every function in it sits on the per-sample render path; the envelope tick()s and Voice::advanceFrame benefit from same-TU inlining (no LTO assumption in the build). Splitting the .cpp along class lines would put per-sample calls across TU boundaries — precisely the heuristic-(c) violation the phase forbids. Proposal: leave the TU whole at 968 (documented exception to the 600 bar, justified by the hot path), but split the header, which is where the pain actually is: zone_params.h (the enums

  • AdsrParams/TriggerParams/PitchEnvParams/ZonePlayParams/SampleLoop/SampleData — what sample_map, the editor, and the codec actually need, ~250) vs. sampler_core.h (Keymap + the engine classes, ~500). Today every UI TU that reads a param struct recompiles when a Voice member changes. Severity: medium. Disposition: header split in the new VST wave; TU stays — recommend recording the exception in the wave brief so nobody "fixes" it later.

T4-15 — src/view_mode_model.h (748) — covered under T4-06 (splits with its TU). T4-16 — src/vst/sample_map.h (708) — covered under T4-13. T4-17 — src/vst/sampler_core.h (762) — covered under T4-14.

1.4 Borderline (no action, for the record)

capture.cpp (549), reasampler_editor.h (539 — shrinks when the editor splits move private helpers into their TUs), reasampler_processor.h (502 — same), bank_book.h (457), pitch_shift.cpp (371 — single responsibility, hot, leave), persist.h/capture.h (341/234 — Q-W6 fat-header pass already owns them). None need action beyond what their TU splits imply.

1.5 The structural conclusion for the wave plan

The existing plan splits 4 files, all extension-side. The census says 9 files need splitting and 2 need header-only splits — 5 of them VST-side, which currently have no owning wave. The VST work is the same kind and size as Q-W2 (the editor alone ≈ the old bank_panel). Recommendation: add one VST god-module wave (call it Q-W2v, runnable in parallel with Q-W2 — different artifact, zero file overlap; or sequence after W5 as Q-W7 if Daniel wants serial waves). Q-W1's relocation scope also grows: the ~20 clean VST pure libs relocate + namespace in W1 alongside the extension's 30.


2. src/vst/ placement in the Q-3 directory map

The settled Q-3 map (core/{model,view,capture,audio,ui,reclaim,version,json}, shell/{capture,panel,view,persist,actions}, app/) covers only the extension. Two viable shapes for the VST artifact; this is Daniel's fork to call at triage.

T4-18 — Leading recommendation: integrate into the same core//shell/ top split, with instrument/ subsystem dirs beneath.

core/instrument/engine/   sampler_core, pitch_shift, velocity_curve, master_gain
core/instrument/map/      sample_map (+component_state_io), bank_sync, bridge_marshal, note_entry, trigger_seam
core/instrument/ui/       editor_geometry, keyboard_strip, waveform_view, capture_browser,
                          browser_scroll, param_slider, knob_deck, curve_popup,
                          envelope_overlay, envelope_edit, embed_strip
shell/instrument/         reaper_bridge, processor TUs, editor TUs, reasampler_embed, vst_entry,
                          reasampler_vst.h / reasampler_uid.h

Rationale:

  1. One rule, no special case. Q-3's settled reasoning is "top-level by the load-bearing discipline, because the pure/shell split is the invariant worth making structural." That reasoning is artifact-agnostic — a file's directory should tell you whether it may touch a host type (REAPER or VST3 SDK), and shell/instrument/ says exactly that.
  2. The artifact boundary is a link-graph fact, not a source-layout fact — and the sources already straddle it. Verified cross-artifact consumers: sample_map links bank_book + wav_trim + sampler_core; the editor includes draw_kit/theme/component_geometry/capture_paths/ peaks/wav_trim/app_version/ext_keys (extension-side modules); the extension's pure instrument_drop includes vst/reasampler_uid.h. An artifact-first subtree would either duplicate these or still reach across — the boundary it draws is already false.
  3. Namespace map falls out: reasampler::instrument::{engine,map,ui} beside reasampler::model etc. — the Q-4 rule applied uniformly.
  4. CMake impact: path edits only. Targets, links, and test executables are unchanged; the VST3-gate (EXISTS pluginfactory.cpp) already guards targets, not directories.

Cost to name: the VST3-gated targets stay interleaved through the top-level CMakeLists.txt rather than being isolatable behind one add_subdirectory. Mitigable by grouping the instrument targets into one guarded block (or one include()d .cmake file) without moving sources.

T4-19 — Alternative: parallel artifact-first subtreesrc/vst/core/{engine,map,ui} + src/vst/shell/, extension keeps src/core|shell|app. Pros: the artifact boundary is visible at top level; the whole VST tree (sources and a dedicated src/vst/CMakeLists.txt) can sit behind one SDK-gated add_subdirectory, which is the cleanest possible expression of "this half only exists on Windows with the submodule slice". Cons: two parallel core/ trees dilute the "directory = may it touch a host type" invariant into "check which subtree first"; the shared-module reality (point 2 above) means the subtree is not actually self-contained — its purity is cosmetic; and the gated-add_subdirectory win is achievable under T4-18 with an include() anyway. Recommend T4-18; T4-19 is defensible if Daniel weighs artifact legibility above discipline uniformity. Disposition: reshapes Q-W1 (the relocation wave executes whichever shape is chosen).


3. Template-collapse opportunities

Judged per heuristic (b) — proposed only where duplication is real and the template earns it; two anti-recommendations included, because a forced template is the worse smell.

T4-20 — Little-endian byte codec: real template win. Five hand-rolled copies, verified: putU32le/putU64le + ByteReader (vst/sample_map.cpp 406500), writeU32LE (capture_realtime.cpp 342), readU32LE lambda (capture_paths.cpp 49), putU32 lambda (ingest.cpp 134), appendU32LE (instrument_drop.cpp 18). One header — core/wire/bytes.h (or beside core/json): template <class T> void putLE(std::vector<uint8_t>&, T) / template <class T> bool readLE(ByteReader&, T&) with the double↔bits helpers — replaces all five, compile-time dispatched, zero runtime cost, and gives the ComponentState codec (T4-13) a tested primitive. Entirely off hot paths (serialization/file I/O only). Severity: medium (each new ComponentState version re-duplicates today). Disposition: fix in the wave that lands component_state_io (the biggest consumer); consumers rewire opportunistically.

T4-21 — Rect family: unify, but with a concrete type, NOT a template. Verified 12+ byte-identical {int x,y,width,height} structs (ActionBarRect/CellRect/ PanelClientRect/FooterBarRect/HeaderRect/SegmentRect/MenuBarRect/MenuButtonRect/ FooterRect/ButtonRect/TabStripRect/KitBox…) plus a second grammar on the VST side (editor_geometry's Rect is LTRB with left/top/right/bottom + height()). The right tool is one concrete ui::Rect + contains() with per-role type aliases (using ButtonRect = ui::Rect;) so call sites keep their semantic names — Q-W1's "one ui:: owner" note already points here; this finding extends it: (a) retire the XYWH-vs-LTRB fork by picking one grammar (LTRB has the live contains/height users; either works — pick once), and (b) the per-type contains/hitTest* one-liners collapse for free. A template rect would model nothing — the types differ in name only. Watch: cross-lib name collisions (extension Rect vs vst Rect) surface only when both headers meet in one TU — sample_map and the editor are exactly such TUs; the Q-4 sub-namespaces are the fix. Severity: medium. Disposition: ride Q-W1 (it is the settled ui:: unification, widened to include editor_geometry::Rect).

T4-22 — Linear rect-scan hit-tests: small template, real but modest. hitTestCell (bank_grid), hitTestSlot (card_drag), and the tab/segment scans are the same first-rect-containing-point loop over records that carry a rect plus extra fields (SlotCellRect adds slot). After T4-21, a single template <class R> int hitIndex(int px, int py, span<const R>) (requiring r.rect.contains(px,py) or a rect accessor) collapses them. Earns its keep only if T4-21 lands first; alone it would be a forced template. Severity: low. Disposition: document-and-defer; opportunistic rider on Q-W1.

T4-23 — WAV build/parse consolidation (dedup, not template). buildFloat32Wav (ingest, 116179) hand-writes the float32 header that wav_trim hand-parses and capture_paths/capture_realtime chunk-scan/byte-patch. One pure wav_codec (or fold build into wav_trim, renamed) gives one tested owner of the RIFF layout. Concrete functions; nothing to template. Severity: low-medium. Disposition: fix-now-sized, but assign to the wave that opens ingest.cpp (T4-10) to avoid a standalone churn commit.

T4-24 — clamp01: dedup with one inline, anti-template. Six verified copies (envelope_overlay, master_gain, param_slider, reasampler_editor, velocity_curve + envelope_edit's clamp). One constexpr inline double clamp01(double) in a shared core header (or just std::clamp at call sites). Not a template candidate — std::clamp already is one. Severity: trivial. Disposition: rider on Q-W1 relocation.

T4-25 — JSON Parser/ObjWriter/writeEscaped/intToStr ×4 — already owned by Q-W1; confirmed still accurate (verified in bank_model/bank_book/view_mode_model/ owned_manifest; sample_usage uses its own rsusage k/v wire, not a fifth JSON parser — no scope growth). Concrete class, not a template. Disposition: no change.


4. Indirection audit (heuristic c)

T4-26 — ICaptureBackend is now a dead abstraction: one implementation, zero polymorphic call sites. The brief's "two implementations — earning its keep" assumption is FALSE at current state. Verified: capture.h itself documents (lines 139148, the "SEAM CHOICE" comment) that RealtimeRecordBackend deliberately does not implement ICaptureBackend — it has a bespoke async begin/tick/abort seam. OfflineRenderBackend is the sole deriver, and the only construction site (main.cpp:738) instantiates the concrete type; nobody anywhere holds an ICaptureBackend*/&. The interface costs a vtable and models nothing. Proposal: delete ICaptureBackend; OfflineRenderBackend becomes a plain concrete class. Note CLAUDE.md/CONTEXT still describe the module as "ICaptureBackend interface; two backends" — the doc should be corrected in the same commit. Severity: low runtime, medium hygiene (it misleads — this audit's own brief was misled). Disposition: fix in Q-W3 (the wave that rehomes the capture orchestration and touches every call site).

T4-27 — Warning to the Q-W2v brief (T4-14): do not split sampler_core.cpp along class lines. AdsrEnvelope::tick/TriggerEnvelope::amplitudeAt/PitchEnvelope::tick are called per-voice per-sample from Voice::advanceFrame, which is called per-sample from VoiceEngine::render. Same-TU definition is what lets the compiler inline this stack today (no LTO configured). A by-class TU split converts the hottest inner loop into cross-TU calls — the exact dispatch-stack blowout heuristic (c) forbids. If a split is ever wanted, the envelopes must move as header-defined (inline) classes, not to a TU. The engine TU staying whole at 968 is the correct trade.

T4-28 — Warning to the Q-W2 brief (reaffirming the plan's own guardrail). panel_audition and the preview idle path must stay direct call-throughs after the 8-TU split — the plan already says this; the two added TUs (T4-01: panel_layout, panel_drag) introduce no new risk (layout is paint-time, drag is input-time), but the split of onMouseMove (which calls hover + drag + tooltip) should keep per-mouse-move work as plain free-function calls, no interface.

T4-29 — Warning to the processor split (T4-12). Keep process() and any per-block helpers it calls in one TU. The state/reload/accessor TUs are UI-thread or setup-time — safe to move freely. The atomic-pointer-swap pattern (publishBuiltLocked) must not gain a virtual seam.

T4-30 — No other gratuitous indirection found (verified, not assumed). The only extension-side virtual is T4-26. VST-side virtuals are all VST3-SDK-mandated overrides (SingleComponentEffect, CPluginView, IReaperUIEmbedInterface) — not ours to remove. The layered pure→shell pairs (card_dragbank_panel, realtime_recordcapture_realtime, drag_outdrag_out_win, prune_reconcilepersist) are the load-bearing discipline, not forwarding waste — each layer adds the decision/side-effect split, and all are direct calls. bank_panel's ~20-function free-API is a module boundary, not a dispatch chain; Q-W2's header segmentation thins it.


Summary table — every oversize file → proposed seams → owning wave

File (LOC) Proposed TUs/headers Owning wave
bank_panel.cpp (3459) 8 TUs: render / layout (new) / thumbnails / audition / input / drag (new) / bank_ops / window Q-W2 (reshaped: 6→8 seams)
vst/reasampler_editor.cpp (3065) 8 TUs: session / controls / layout (pure candidate) / paint_sample / paint_browse_zone / input_sample / input_browse_zone / platform NEW wave Q-W2v
main.cpp (1897) orchestrator / batch+recapture (new) / scope_resolve / realtime_lifecycle / app-entry residue Q-W3 (reshaped: 3→4 hoists) + Q-W6
vst/reasampler_processor.cpp (1164) processor_state / processor_reload / lifecycle+process (whole) NEW wave Q-W2v
bank_book.cpp (1109) slot_map / bank_book / JSON→core/json Q-W1 (+slot_map rider)
view_mode_model.cpp (1049) + .h (748) indexes+model / planners / JSON→core/json; header splits likewise Q-W1 (+planner rider)
actions.cpp (1016) design_view_actions / bank_actions / prune_action (unchanged) Q-W4 (no change)
vst/sample_map.cpp (970) + .h (708) sample_map (resolution) / component_state_io (codec); header splits likewise NEW wave Q-W2v
vst/sampler_core.cpp (968) + .h (762) TU stays whole (hot-path exception, T4-27); header → zone_params.h + sampler_core.h NEW wave Q-W2v (header only)
capture_realtime.cpp (867) lifecycle / finalize Q-W3 (rides the Q-9 rename)
persist.cpp (852) session / ext_state_io / prune_fs (unchanged) Q-W5 (no change)
bank_model.cpp (767) model / JSON→core/json (unchanged) Q-W1 (no change)
view.cpp (677) apply / lanes defer, or Q-W1 rider
ingest.cpp (636) wav helpers→core / import / surfaces wave owning T4-23

Wave-plan deltas requested of triage: (1) Q-W2 grows to 8 seams; (2) Q-W3 grows to 4 hoists + the ICaptureBackend deletion; (3) a new VST god-module wave (Q-W2v) owns the editor / processor / sample_map splits + the sampler_core header split — parallel-safe with Q-W2 (zero file overlap); (4) Q-W1's relocation scope includes the VST pure libs under the placement shape chosen at the T4-18/T4-19 fork; (5) the core/wire/bytes.h LE-codec template (T4-20) lands with component_state_io.