fractal-midi 0.1.0-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (123) hide show
  1. package/LICENSE +200 -0
  2. package/NOTICE +28 -0
  3. package/README.md +147 -0
  4. package/dist/am4/applicability.d.ts +61 -0
  5. package/dist/am4/applicability.d.ts.map +1 -0
  6. package/dist/am4/applicability.js +285 -0
  7. package/dist/am4/blockTypes.d.ts +43 -0
  8. package/dist/am4/blockTypes.d.ts.map +1 -0
  9. package/dist/am4/blockTypes.js +48 -0
  10. package/dist/am4/cacheEnums.d.ts +46 -0
  11. package/dist/am4/cacheEnums.d.ts.map +1 -0
  12. package/dist/am4/cacheEnums.js +734 -0
  13. package/dist/am4/cacheParams.d.ts +3533 -0
  14. package/dist/am4/cacheParams.d.ts.map +1 -0
  15. package/dist/am4/cacheParams.js +1996 -0
  16. package/dist/am4/editorControlLabels.d.ts +45 -0
  17. package/dist/am4/editorControlLabels.d.ts.map +1 -0
  18. package/dist/am4/editorControlLabels.js +15894 -0
  19. package/dist/am4/index.d.ts +28 -0
  20. package/dist/am4/index.d.ts.map +1 -0
  21. package/dist/am4/index.js +31 -0
  22. package/dist/am4/ir/preset.d.ts +24 -0
  23. package/dist/am4/ir/preset.d.ts.map +1 -0
  24. package/dist/am4/ir/preset.js +12 -0
  25. package/dist/am4/ir/transpile.d.ts +9 -0
  26. package/dist/am4/ir/transpile.d.ts.map +1 -0
  27. package/dist/am4/ir/transpile.js +19 -0
  28. package/dist/am4/locations.d.ts +32 -0
  29. package/dist/am4/locations.d.ts.map +1 -0
  30. package/dist/am4/locations.js +58 -0
  31. package/dist/am4/paramNames.d.ts +55 -0
  32. package/dist/am4/paramNames.d.ts.map +1 -0
  33. package/dist/am4/paramNames.js +863 -0
  34. package/dist/am4/paramNamesGenerated.d.ts +41 -0
  35. package/dist/am4/paramNamesGenerated.d.ts.map +1 -0
  36. package/dist/am4/paramNamesGenerated.js +183 -0
  37. package/dist/am4/parameterBridge.d.ts +46 -0
  38. package/dist/am4/parameterBridge.d.ts.map +1 -0
  39. package/dist/am4/parameterBridge.js +300 -0
  40. package/dist/am4/params.d.ts +9577 -0
  41. package/dist/am4/params.d.ts.map +1 -0
  42. package/dist/am4/params.js +4537 -0
  43. package/dist/am4/setParam.d.ts +414 -0
  44. package/dist/am4/setParam.d.ts.map +1 -0
  45. package/dist/am4/setParam.js +819 -0
  46. package/dist/am4/shared/paramHelpers.d.ts +55 -0
  47. package/dist/am4/shared/paramHelpers.d.ts.map +1 -0
  48. package/dist/am4/shared/paramHelpers.js +146 -0
  49. package/dist/am4/symbolicIds.d.ts +11 -0
  50. package/dist/am4/symbolicIds.d.ts.map +1 -0
  51. package/dist/am4/symbolicIds.js +587 -0
  52. package/dist/am4/typeApplicability.d.ts +39 -0
  53. package/dist/am4/typeApplicability.d.ts.map +1 -0
  54. package/dist/am4/typeApplicability.js +466 -0
  55. package/dist/am4/variantResolverTables.d.ts +51 -0
  56. package/dist/am4/variantResolverTables.d.ts.map +1 -0
  57. package/dist/am4/variantResolverTables.js +3128 -0
  58. package/dist/axe-fx-ii/blockTypes.d.ts +45 -0
  59. package/dist/axe-fx-ii/blockTypes.d.ts.map +1 -0
  60. package/dist/axe-fx-ii/blockTypes.js +116 -0
  61. package/dist/axe-fx-ii/index.d.ts +5 -0
  62. package/dist/axe-fx-ii/index.d.ts.map +1 -0
  63. package/dist/axe-fx-ii/index.js +18 -0
  64. package/dist/axe-fx-ii/paramAliases.d.ts +54 -0
  65. package/dist/axe-fx-ii/paramAliases.d.ts.map +1 -0
  66. package/dist/axe-fx-ii/paramAliases.js +146 -0
  67. package/dist/axe-fx-ii/params.d.ts +11502 -0
  68. package/dist/axe-fx-ii/params.d.ts.map +1 -0
  69. package/dist/axe-fx-ii/params.js +2847 -0
  70. package/dist/axe-fx-ii/setParam.d.ts +560 -0
  71. package/dist/axe-fx-ii/setParam.d.ts.map +1 -0
  72. package/dist/axe-fx-ii/setParam.js +888 -0
  73. package/dist/axe-fx-iii/blockTypes.d.ts +87 -0
  74. package/dist/axe-fx-iii/blockTypes.d.ts.map +1 -0
  75. package/dist/axe-fx-iii/blockTypes.js +156 -0
  76. package/dist/axe-fx-iii/enumOverlay.d.ts +73 -0
  77. package/dist/axe-fx-iii/enumOverlay.d.ts.map +1 -0
  78. package/dist/axe-fx-iii/enumOverlay.js +236 -0
  79. package/dist/axe-fx-iii/index.d.ts +9 -0
  80. package/dist/axe-fx-iii/index.d.ts.map +1 -0
  81. package/dist/axe-fx-iii/index.js +20 -0
  82. package/dist/axe-fx-iii/params.d.ts +179 -0
  83. package/dist/axe-fx-iii/params.d.ts.map +1 -0
  84. package/dist/axe-fx-iii/params.js +6913 -0
  85. package/dist/axe-fx-iii/setParam.d.ts +460 -0
  86. package/dist/axe-fx-iii/setParam.d.ts.map +1 -0
  87. package/dist/axe-fx-iii/setParam.js +910 -0
  88. package/dist/index.d.ts +2 -0
  89. package/dist/index.d.ts.map +1 -0
  90. package/dist/index.js +12 -0
  91. package/dist/shared/checksum.d.ts +10 -0
  92. package/dist/shared/checksum.d.ts.map +1 -0
  93. package/dist/shared/checksum.js +14 -0
  94. package/dist/shared/device.d.ts +195 -0
  95. package/dist/shared/device.d.ts.map +1 -0
  96. package/dist/shared/device.js +27 -0
  97. package/dist/shared/index.d.ts +8 -0
  98. package/dist/shared/index.d.ts.map +1 -0
  99. package/dist/shared/index.js +11 -0
  100. package/dist/shared/lineage/amp-lineage.json +8313 -0
  101. package/dist/shared/lineage/axefx2-amp-lineage.json +5871 -0
  102. package/dist/shared/lineage/axefx2-delay-lineage.json +226 -0
  103. package/dist/shared/lineage/axefx2-drive-lineage.json +575 -0
  104. package/dist/shared/lineage/axefx2-reverb-lineage.json +467 -0
  105. package/dist/shared/lineage/cab-lineage.json +10777 -0
  106. package/dist/shared/lineage/chorus-lineage.json +173 -0
  107. package/dist/shared/lineage/compressor-lineage.json +338 -0
  108. package/dist/shared/lineage/delay-lineage.json +313 -0
  109. package/dist/shared/lineage/drive-lineage.json +1844 -0
  110. package/dist/shared/lineage/flanger-lineage.json +313 -0
  111. package/dist/shared/lineage/phaser-lineage.json +208 -0
  112. package/dist/shared/lineage/reverb-lineage.json +793 -0
  113. package/dist/shared/lineage/wah-lineage.json +117 -0
  114. package/dist/shared/lineageLookup.d.ts +69 -0
  115. package/dist/shared/lineageLookup.d.ts.map +1 -0
  116. package/dist/shared/lineageLookup.js +196 -0
  117. package/dist/shared/packValue.d.ts +40 -0
  118. package/dist/shared/packValue.d.ts.map +1 -0
  119. package/dist/shared/packValue.js +105 -0
  120. package/dist/shared/types.d.ts +23 -0
  121. package/dist/shared/types.d.ts.map +1 -0
  122. package/dist/shared/types.js +9 -0
  123. package/package.json +75 -0
@@ -0,0 +1,4537 @@
1
+ /**
2
+ * AM4 parameter registry.
3
+ *
4
+ * Each entry maps a human key (`block.name`) to its wire-level address
5
+ * (`pidLow` = block ID, `pidHigh` = parameter index within block) and
6
+ * its display ↔ internal scale convention.
7
+ *
8
+ * Address is preset-independent (confirmed Session 06 — Amp pidLow
9
+ * matches across A01 and A2). See docs/_private/STATE.md for the decoded set.
10
+ */
11
+ import { CACHE_PARAMS } from './cacheParams.js';
12
+ import { AMP_TYPES_VALUES, DRIVE_TYPES_VALUES, REVERB_TYPES_VALUES, DELAY_TYPES_VALUES, CHORUS_TYPES_VALUES, FLANGER_TYPES_VALUES, PHASER_TYPES_VALUES, WAH_TYPES_VALUES, COMPRESSOR_TYPES_VALUES, GEQ_TYPES_VALUES, FILTER_TYPES_VALUES, TREMOLO_TYPES_VALUES, ENHANCER_TYPES_VALUES, GATE_TYPES_VALUES, VOLPAN_MODES_VALUES, TEMPO_DIVISIONS_VALUES, LFO_WAVEFORMS_VALUES, } from './cacheEnums.js';
13
+ const DISPLAY_TO_INTERNAL = {
14
+ knob_0_10: 10,
15
+ // HW-028 (Session 35, 2026-04-29): Compressor Emphasis at cache c=20.
16
+ // Display range 0..20 with fractional precision (cache step 0.0005 ×
17
+ // 20 = 0.01 display step). Same shape as knob_0_10 but with double
18
+ // the display range — JFET Studio compressor's Drive-engine emphasis
19
+ // knob is the canonical case.
20
+ knob_0_20: 20,
21
+ db: 1,
22
+ hz: 1,
23
+ seconds: 1,
24
+ percent: 100,
25
+ bipolar_percent: 100,
26
+ count: 1,
27
+ semitones: 1,
28
+ ratio: 1,
29
+ ms: 1000,
30
+ // Cache c=57.295780... = 180/π. AM4-Edit displays Mod Phase / Phase
31
+ // knobs in degrees; firmware stores radians. e.g. 10 deg → 0.17453 rad
32
+ // / 90 deg → 1.5708 rad / 180 deg → 3.14159 rad.
33
+ degrees: 57.29577951308232,
34
+ // Session 36 (HW-040, 2026-04-29): Picofarad capacitance for amp.bright_cap.
35
+ // Cache id=20 a=0.00001 b=0.01 c=1000000 → wire 0.00001..0.01 displays
36
+ // as 10..10000 pF. The "Bright Cap" knob on the FAS Modern III amp's
37
+ // IDEAL section is the canonical case.
38
+ pf: 1000000,
39
+ // BK-035 (Session 36 cont, 2026-04-29): rotary.mic_spacing uses a
40
+ // π-encoded internal scale. Cache id=16 a=0 b=π c=100/π=31.831 → wire
41
+ // 0..π displays as 0..100. Used only by `rotary.mic_spacing` so far;
42
+ // unit name is specific to keep the math discoverable. Same structural
43
+ // pattern as `degrees` (180/π) but maps to a 0..100 linear scale.
44
+ rotary_mic_spacing: 31.83098793029785,
45
+ // Session 38 follow-up (2026-04-30): amp's 8-band Graphic EQ stores
46
+ // each band as ±1 wire, scale ×12 → display ±12 dB. Cache ids 62..69
47
+ // share the (a=-1, b=1, c=12) signature. Distinct from `drive`'s GEQ,
48
+ // which stores ±12 directly (cache c=1) and uses plain `db`. Naming is
49
+ // specific because c=12 only appears on these 8 cache records across
50
+ // the whole AM4 surface.
51
+ amp_geq_band: 12,
52
+ };
53
+ /** Convert a UI/display value to the float the firmware expects. */
54
+ export function encode(param, displayValue) {
55
+ if (param.unit === 'enum')
56
+ return displayValue;
57
+ return displayValue / DISPLAY_TO_INTERNAL[param.unit];
58
+ }
59
+ /**
60
+ * Convert the AM4's internal [0,1] normalized float (decoded from the Q15
61
+ * read register) back to a UI/display value.
62
+ *
63
+ * The AM4 stores all params in a normalized [0,1] form scaled to each
64
+ * param's `[displayMin, displayMax]` range. Most params are linearly
65
+ * scaled; time-based knobs (ms attack/release/delay) and ratio knobs are
66
+ * stored on a log10 curve. Per-param scaling is encoded in `param.scaling`
67
+ * (default `linear`).
68
+ *
69
+ * BK-038 (Session 43 cont, 2026-05-01): the previous decode rule was
70
+ * `internal × DISPLAY_TO_INTERNAL[unit]`, which only happened to be
71
+ * correct for params where `displayMin === 0` AND `displayMax ===
72
+ * DISPLAY_TO_INTERNAL[unit]` (e.g. `knob_0_10` with range 0..10 and
73
+ * scale 10). For most non-knob_0_10 params it produced wildly wrong
74
+ * readbacks ("compressor.attack = 867 ms" when the device displayed
75
+ * 40 ms). Founder-observed via the Sultans-of-Swing iconic-tone test.
76
+ */
77
+ export function decode(param, internalValue) {
78
+ if (param.unit === 'enum')
79
+ return Math.round(internalValue);
80
+ const { displayMin, displayMax } = param;
81
+ if (param.scaling === 'log10') {
82
+ // Guard against degenerate range / zero-or-negative endpoints.
83
+ if (displayMin <= 0 || displayMax <= 0 || displayMax === displayMin) {
84
+ return displayMin + internalValue * (displayMax - displayMin);
85
+ }
86
+ return displayMin * Math.pow(displayMax / displayMin, internalValue);
87
+ }
88
+ // Linear (default): display = displayMin + internal × (displayMax − displayMin).
89
+ return displayMin + internalValue * (displayMax - displayMin);
90
+ }
91
+ /**
92
+ * Decimal places for display values, per unit. Matches AM4-Edit's on-screen
93
+ * convention so read tool output ("amp.gain is 5.00") doesn't surface the
94
+ * Q15 quantization residue ("amp.gain is 4.9999"). Used by `formatDisplay`.
95
+ */
96
+ const DISPLAY_PRECISION = {
97
+ knob_0_10: 2,
98
+ knob_0_20: 2,
99
+ db: 1,
100
+ hz: 0,
101
+ seconds: 2,
102
+ percent: 0,
103
+ bipolar_percent: 0,
104
+ count: 0,
105
+ semitones: 0,
106
+ ratio: 1,
107
+ ms: 0,
108
+ degrees: 0,
109
+ pf: 0,
110
+ rotary_mic_spacing: 1,
111
+ amp_geq_band: 1,
112
+ };
113
+ /**
114
+ * Format a display value for human-readable output (read tools, error
115
+ * messages). Picks decimal precision per `param.unit` so the AM4's Q15
116
+ * quantization residue (~0.0001 on a 0..10 knob) doesn't leak into the
117
+ * agent's tool output. Enum params use `formatEnum` instead.
118
+ */
119
+ export function formatDisplay(param, displayValue) {
120
+ if (param.unit === 'enum') {
121
+ throw new Error(`formatDisplay called on enum param ${param.block}.${param.name} — use enumValues lookup`);
122
+ }
123
+ return displayValue.toFixed(DISPLAY_PRECISION[param.unit]);
124
+ }
125
+ /**
126
+ * Render the unit suffix for read-tool output, including the leading
127
+ * space. Returns `' <suffix>'` (with leading space) for non-empty
128
+ * suffixes, or empty string when the param is unitless or the override
129
+ * suppresses it. Used by get_param / get_params to format readback
130
+ * strings without trailing whitespace.
131
+ */
132
+ export function formatUnitSuffix(param) {
133
+ const suffix = param.displayUnit ?? param.unit;
134
+ return suffix === '' ? '' : ` ${suffix}`;
135
+ }
136
+ /**
137
+ * Resolve an enum param's display name (or numeric index) to the wire
138
+ * integer. Accepts numbers directly, exact name matches, and a relaxed
139
+ * case-insensitive match after collapsing whitespace and punctuation —
140
+ * `"Marshall 1959SLP"`, `"1959slp normal"`, and `0` all resolve the
141
+ * same entry.
142
+ *
143
+ * Returns `undefined` if no match is found or the param is not an enum.
144
+ * Callers should treat that as an invalid user input.
145
+ */
146
+ export function resolveEnumValue(param, input) {
147
+ if (param.unit !== 'enum' || !param.enumValues)
148
+ return undefined;
149
+ if (typeof input === 'number') {
150
+ return param.enumValues[input] !== undefined ? input : undefined;
151
+ }
152
+ const trimmed = input.trim();
153
+ if (trimmed === '')
154
+ return undefined;
155
+ // Exact match first (fast path + most accurate).
156
+ for (const [idx, name] of Object.entries(param.enumValues)) {
157
+ if (name === trimmed)
158
+ return Number(idx);
159
+ }
160
+ // Relaxed match: lowercase, collapse non-alphanumeric to single space.
161
+ const normalize = (s) => s.toLowerCase().replace(/[^a-z0-9]+/g, ' ').trim();
162
+ const target = normalize(trimmed);
163
+ for (const [idx, name] of Object.entries(param.enumValues)) {
164
+ if (normalize(name) === target)
165
+ return Number(idx);
166
+ }
167
+ // Substring fallback: pick the entry whose normalized name contains
168
+ // the query (or vice-versa). Only accept unambiguous matches — if
169
+ // more than one entry qualifies, bail rather than pick arbitrarily.
170
+ const hits = [];
171
+ for (const [idx, name] of Object.entries(param.enumValues)) {
172
+ const n = normalize(name);
173
+ if (n.includes(target) || target.includes(n))
174
+ hits.push(Number(idx));
175
+ }
176
+ return hits.length === 1 ? hits[0] : undefined;
177
+ }
178
+ /**
179
+ * Find every enum entry that matches the input under the substring rule
180
+ * used by `resolveEnumValue`. Returns `[indices, names]` of all hits.
181
+ *
182
+ * Used by the validation error path to tell the agent EXACTLY which
183
+ * candidates a partial name like "Room" or "Plate" matched, instead of
184
+ * the previous "first 8 valid names from offset 0" hint that listed
185
+ * names regardless of relevance. Founder-driven (Session 44 Lamb-of-God
186
+ * test): agent passed `reverb.type = "Room"`, hit the ambiguous-bail
187
+ * branch, and the error sample showed Room, Small / Room, Medium /
188
+ * Room, Large / Hall, Small / Hall, Medium … — the Hall entries were
189
+ * noise. With this helper we can show only the matched candidates.
190
+ */
191
+ export function findEnumCandidates(param, input) {
192
+ if (param.unit !== 'enum' || !param.enumValues)
193
+ return [];
194
+ const normalize = (s) => s.toLowerCase().replace(/[^a-z0-9]+/g, ' ').trim();
195
+ const target = normalize(input.trim());
196
+ if (target === '')
197
+ return [];
198
+ const hits = [];
199
+ for (const [idx, name] of Object.entries(param.enumValues)) {
200
+ const n = normalize(name);
201
+ if (n.includes(target) || target.includes(n)) {
202
+ hits.push({ index: Number(idx), name });
203
+ }
204
+ }
205
+ return hits;
206
+ }
207
+ /**
208
+ * Common-synonym aliases for parameter names. Maps a `${block}.${alias}`
209
+ * key to the canonical `${block}.${name}` registered in KNOWN_PARAMS.
210
+ *
211
+ * Why this exists. AM4-Edit / Fractal docs use specific names ("time"
212
+ * for both reverb decay and delay repeat time, "rate" for modulation
213
+ * LFO speed, "feedback" for delay repeats). LLM agents reach for the
214
+ * synonyms most common in the gear world ("decay" for reverb, "speed"
215
+ * for modulation, "repeats" for delay) and hit unknown-param errors
216
+ * even though the registered param does the same thing. This map
217
+ * intercepts the well-established universal synonyms before the
218
+ * unknown-param error fires, returning the canonical name silently so
219
+ * the agent's first call lands.
220
+ *
221
+ * Conservative scope. Only synonyms that are universally accepted in
222
+ * music gear documentation (Fractal manual, Boss/Roland docs, synth
223
+ * world). No clever mapping — if there's any ambiguity ("size" could
224
+ * be reverb size or chamber size or amp room size), don't add the
225
+ * alias and let the agent's first error teach it.
226
+ *
227
+ * Founder-driven (Session 44, 2026-05-02): Lamb-of-God Mark Morton tone
228
+ * test had the agent reach for `reverb.decay` (universal synthesizer
229
+ * term) and `reverb.length` (less common but plausible) — both meant
230
+ * `reverb.time`. Aliases prevent the round-trip-and-fix cost that this
231
+ * test had to pay.
232
+ */
233
+ export const PARAM_ALIASES = {
234
+ // Reverb time = decay (universal synth/reverb-pedal term).
235
+ 'reverb.decay': 'reverb.time',
236
+ 'reverb.length': 'reverb.time',
237
+ // Delay time = length (less common but plausible from compact-pedal docs).
238
+ 'delay.length': 'delay.time',
239
+ // Delay feedback = repeats (Strymon / Eventide convention).
240
+ 'delay.repeats': 'delay.feedback',
241
+ // Modulation rate = speed (Boss / MXR convention).
242
+ 'chorus.speed': 'chorus.rate',
243
+ 'flanger.speed': 'flanger.rate',
244
+ 'phaser.speed': 'phaser.rate',
245
+ 'tremolo.speed': 'tremolo.rate',
246
+ 'rotary.speed': 'rotary.rate',
247
+ // Panel-name vs AM4-name mismatches surfaced by HW-064 (2026-05-05):
248
+ // vintage Fender amps display "Volume" on the front panel but AM4
249
+ // calls the same knob `gain`; drive panels say "Drive" but agents
250
+ // reach for "gain" by analogy with amp.
251
+ 'amp.volume': 'amp.gain',
252
+ 'drive.gain': 'drive.drive',
253
+ // NOTE: 'reverb.pre_delay' alias removed — canonical key is now
254
+ // 'reverb.pre_delay' itself (renamed from 'reverb.predelay' for
255
+ // UI-label match, audit row REVERB 19).
256
+ };
257
+ /**
258
+ * Scene-MIDI Type enum (PATCH family, pidHigh row 0x40..0x4F).
259
+ *
260
+ * AM4-Edit's UI exposes only Program Change and Control Change as
261
+ * available message types ("The available message types are Program
262
+ * Change (PC) and Control Change (CC)" — AM4-Edit Scene MIDI page
263
+ * help text). The wire encoding folds the CC number into the Type
264
+ * enum itself:
265
+ *
266
+ * wire 0 → 'None' (no message — Channel/Value greyed out)
267
+ * wire 1 → 'PC' (Program Change — uses Channel + Value)
268
+ * wire N≥2 → 'CC #(N-2)' (Control Change with CC# = N-2)
269
+ *
270
+ * Wire-confirmed against samples/captured/session-85-scene-midi.pcapng
271
+ * (Type=1.0 for PC) and the founder's AM4-Edit screenshot showing
272
+ * "CC #016" displayed when wire Type=18.0 (16 + 2 = 18).
273
+ *
274
+ * Display names use AM4-Edit's exact format: `CC #016` (zero-padded to
275
+ * 3 digits, with a space and hash). Keep parity with what the user
276
+ * reads on screen — `resolveEnumValue` matches by display string.
277
+ */
278
+ export const SCENE_MIDI_TYPE_ENUM = (() => {
279
+ const out = { 0: 'None', 1: 'PC' };
280
+ for (let cc = 0; cc <= 127; cc++) {
281
+ out[cc + 2] = `CC #${cc.toString().padStart(3, '0')}`;
282
+ }
283
+ return out;
284
+ })();
285
+ /**
286
+ * Runtime parameter registry. Hand-authored entries (manual unit/range
287
+ * overrides, out-of-band registers like `*.channel` / `*.level`,
288
+ * hand-authored enum mappings, etc.) are listed explicitly below.
289
+ * Resolver-derived entries flow in via `...CACHE_PARAMS` — that spread
290
+ * imports the bulk auto-generated bindings synthesized by
291
+ * `scripts/gen-params-from-cache.ts` from the AM4-Edit metadata cache,
292
+ * with friendly names from `paramNames.ts` (hand-curated) merged with
293
+ * `paramNamesGenerated.ts` (resolver-derived from AM4-Edit.exe). Order
294
+ * matters: hand entries below shadow any same-key spread entry, so a
295
+ * hand override always wins. `verify-cache-params.ts` enforces that
296
+ * any hand override that COLLIDES with a CACHE_PARAMS entry must agree
297
+ * byte-for-byte (pidLow/pidHigh/unit/displayMin/displayMax/scaling) —
298
+ * pure additions are unconstrained.
299
+ */
300
+ export const KNOWN_PARAMS = {
301
+ ...CACHE_PARAMS,
302
+ 'amp.gain': {
303
+ block: 'amp', name: 'gain',
304
+ displayLabel: 'Gain',
305
+ pidLow: 0x003a, pidHigh: 0x000b,
306
+ unit: 'knob_0_10', displayMin: 0, displayMax: 10,
307
+ },
308
+ 'amp.bass': {
309
+ block: 'amp', name: 'bass',
310
+ displayLabel: 'Bass',
311
+ pidLow: 0x003a, pidHigh: 0x000c,
312
+ unit: 'knob_0_10', displayMin: 0, displayMax: 10,
313
+ },
314
+ // P1-010 Session B (2026-04-19) — AM4 tone stack completion. Cache
315
+ // records at ids 13/14/15 have the identical signature to gain/bass
316
+ // (knob_0_10, 0..1 range, display-scale 10). Named per AM4 Owner's
317
+ // Manual line 1563 "Gain, Bass, Mid, Treble, Presence, Level" and
318
+ // the Fractal Blocks Guide tone-stack order (§Tone Page, pp. 9–10).
319
+ // HW-014 verified (Session 29 cont 7): mid / treble / presence / bass
320
+ // all wrote and displayed correctly on hardware.
321
+ 'amp.mid': {
322
+ block: 'amp', name: 'mid',
323
+ displayLabel: 'Mid',
324
+ pidLow: 0x003a, pidHigh: 0x000d,
325
+ unit: 'knob_0_10', displayMin: 0, displayMax: 10,
326
+ },
327
+ 'amp.treble': {
328
+ block: 'amp', name: 'treble',
329
+ displayLabel: 'Tone',
330
+ pidLow: 0x003a, pidHigh: 0x000e,
331
+ unit: 'knob_0_10', displayMin: 0, displayMax: 10,
332
+ },
333
+ // Session 29 (HW-015): `pidHigh=0x000f` was wrongly registered as
334
+ // amp.presence in Session 26 based on cache signature alone. Two
335
+ // wire captures on Marshall-family amps (unknown amp + Brit 800
336
+ // #34) proved the register is Master. Real Presence is at
337
+ // pidHigh=0x001e (below).
338
+ 'amp.master': {
339
+ block: 'amp', name: 'master',
340
+ displayLabel: 'Master',
341
+ pidLow: 0x003a, pidHigh: 0x000f,
342
+ unit: 'knob_0_10', displayMin: 0, displayMax: 10,
343
+ },
344
+ // Session 29 (HW-015): full 0→10 sweep capture confirmed Depth at
345
+ // pidHigh=0x001a. Knob_0_10 matches the cache signature.
346
+ 'amp.depth': {
347
+ block: 'amp', name: 'depth',
348
+ displayLabel: 'Depth',
349
+ pidLow: 0x003a, pidHigh: 0x001a,
350
+ unit: 'knob_0_10', displayMin: 0, displayMax: 10,
351
+ },
352
+ // Session 29 (HW-015): Presence at pidHigh=0x001e (not 0x000f — see
353
+ // amp.master above). Wire-verified on the same Marshall amp.
354
+ 'amp.presence': {
355
+ block: 'amp', name: 'presence',
356
+ displayLabel: 'Presence',
357
+ pidLow: 0x003a, pidHigh: 0x001e,
358
+ unit: 'knob_0_10', displayMin: 0, displayMax: 10,
359
+ },
360
+ // Session 29 (HW-015): Out Boost Level on the Extras tab, dB knob
361
+ // 0..4 dB with 0.05 dB steps.
362
+ 'amp.out_boost_level': {
363
+ block: 'amp', name: 'out_boost_level',
364
+ pidLow: 0x003a, pidHigh: 0x0008,
365
+ unit: 'db', displayMin: 0, displayMax: 4,
366
+ },
367
+ // Session 29 (HW-015): Out Boost ON/OFF toggle on the Extras tab.
368
+ // Registered directly in KNOWN_PARAMS (out-of-band from the cache
369
+ // generator because per-block non-Type enum imports aren't
370
+ // supported). Wire-verified via session-29-amp-out-boost-toggle:
371
+ // value=1.0 → ON.
372
+ 'amp.out_boost': {
373
+ block: 'amp', name: 'out_boost',
374
+ displayLabel: 'Out Boost',
375
+ pidLow: 0x003a, pidHigh: 0x0096,
376
+ unit: 'enum', displayMin: 0, displayMax: 1,
377
+ enumValues: { 0: 'OFF', 1: 'ON' },
378
+ },
379
+ // Session 29 cont: Amp Advanced-panel enums registered from Blocks
380
+ // Guide text (structural — wire indexing assumed from cache enum
381
+ // order). Out-of-band from the cache generator for the same reason
382
+ // amp.out_boost is: the generator emits only the block's Type enum,
383
+ // not its other enum records. HW-014 couldn't verify these from
384
+ // the hardware display alone (both labels are hidden by the AM4
385
+ // hardware UI); AM4-Edit would show them. Structural-only until
386
+ // an AM4-Edit-side verification pass.
387
+ //
388
+ // Tonestack Location (not Type — Type is a separate 69-value enum).
389
+ // Blocks Guide: "POST places the stack between the preamp and
390
+ // power amp. MID places it between the last two triode stages.
391
+ // END places it after the power amp (physically impossible with
392
+ // a real amp)." PRE-MID is the 5th option.
393
+ // renamed for UI-label match (audit row: DISTORT 24)
394
+ 'amp.location': {
395
+ block: 'amp', name: 'location',
396
+ displayLabel: 'Location',
397
+ pidLow: 0x003a, pidHigh: 0x0018,
398
+ unit: 'enum', displayMin: 0, displayMax: 4,
399
+ enumValues: { 0: 'PRE', 1: 'POST', 2: 'MID', 3: 'END', 4: 'PRE-MID' },
400
+ },
401
+ // Master Volume Location. Blocks Guide §Advanced (p. 853):
402
+ // "Master Vol Location — Sets the location of the Master Volume
403
+ // control. Most amps have the Master Volume before the phase
404
+ // inverter ('Pre PI'). On some amps (like the 'Class-A' types)
405
+ // the Master Volume comes after the phase inverter ('PI'). A
406
+ // third option, 'pre-triode,' is the default for 'Hipower' amp
407
+ // types."
408
+ 'amp.master_vol_location': {
409
+ block: 'amp', name: 'master_vol_location',
410
+ displayLabel: 'Master Vol Location',
411
+ pidLow: 0x003a, pidHigh: 0x0038,
412
+ unit: 'enum', displayMin: 0, displayMax: 2,
413
+ enumValues: { 0: 'PRE-PI', 1: 'POST-PI', 2: 'PRE-TRIODE' },
414
+ },
415
+ // HW-040 (Session 36, 2026-04-29): Amp Expert-Edit page from
416
+ // session-40-amp-expert.pcapng + paired AM4-Edit screenshot
417
+ // (FAS Modern III). 17 new params across BASIC + IDEAL + POST
418
+ // BOOST + CHANNEL COLORS + OUTPUT COMPRESSOR + AMP EXTRAS + GEQ
419
+ // sections. Wiggle-order timeline + screenshot column order
420
+ // disambiguates the OFF/ON switches in the IDEAL column. Mirrored
421
+ // from CACHE_PARAMS where applicable; hand-authored enums + the
422
+ // cache-derived block of `bright_cap` / `input_trim` / GEQ bands /
423
+ // `compressor_*` / `master_vol_trim` / `high_treble`.
424
+ //
425
+ // One open follow-up: pidHigh=0x0085 (cache id=133, enum [OFF,ON],
426
+ // wire 1 = ON) is unmapped — wiggled between Master Vol Trim and
427
+ // GEQ Type but doesn't fit a screenshot label cleanly. Likely a
428
+ // POST BOOST related toggle or an amp-mode flag; needs a single
429
+ // disambiguation capture to confirm.
430
+ 'amp.bypass_mode': {
431
+ block: 'amp', name: 'bypass_mode',
432
+ pidLow: 0x003a, pidHigh: 0x0004,
433
+ unit: 'enum', displayMin: 0, displayMax: 1,
434
+ enumValues: { 0: 'Thru', 1: 'Mute' },
435
+ },
436
+ 'amp.bright_cap': {
437
+ block: 'amp', name: 'bright_cap',
438
+ displayLabel: 'Bright Cap',
439
+ pidLow: 0x003a, pidHigh: 0x0014,
440
+ // Cache id=20: float a=0.00001 b=0.01 c=1000000 → wire 0.00001..0.01
441
+ // displays as 10..10000 pF. New `pf` unit (scale 1000000).
442
+ unit: 'pf', displayMin: 10, displayMax: 10000,
443
+ // typecode 72 = log10 — HW-053 hardware-confirmed (write 220 → AM4 220 ✓; linear readback gave 4480)
444
+ scaling: 'log10',
445
+ },
446
+ 'amp.input_select': {
447
+ block: 'amp', name: 'input_select',
448
+ displayLabel: 'Amp Input Select',
449
+ pidLow: 0x003a, pidHigh: 0x0019,
450
+ unit: 'enum', displayMin: 0, displayMax: 2,
451
+ enumValues: { 0: 'LEFT', 1: 'RIGHT', 2: 'SUM L+R' },
452
+ },
453
+ 'amp.section': {
454
+ block: 'amp', name: 'section',
455
+ displayLabel: 'Amp Section',
456
+ pidLow: 0x003a, pidHigh: 0x0023,
457
+ // AMP EXTRAS.Amp Section toggle.
458
+ unit: 'enum', displayMin: 0, displayMax: 1,
459
+ enumValues: { 0: 'ENGAGED', 1: 'BYPASSED' },
460
+ },
461
+ 'amp.bright': {
462
+ block: 'amp', name: 'bright',
463
+ displayLabel: 'Bright',
464
+ pidLow: 0x003a, pidHigh: 0x002e,
465
+ // BASIC.Bright toggle. Wiggle-order adjacency pins this between
466
+ // Depth (0x001a) and Master (0x000f) in the BASIC column.
467
+ unit: 'enum', displayMin: 0, displayMax: 1,
468
+ enumValues: { 0: 'OFF', 1: 'ON' },
469
+ },
470
+ 'amp.cut_switch': {
471
+ block: 'amp', name: 'cut_switch',
472
+ displayLabel: 'Cut Switch',
473
+ pidLow: 0x003a, pidHigh: 0x0034,
474
+ // IDEAL.Cut Switch — wiggle adjacency pins it between High Treble
475
+ // (0x0068) and Fat Switch (0x0055) in the IDEAL column.
476
+ unit: 'enum', displayMin: 0, displayMax: 1,
477
+ enumValues: { 0: 'OFF', 1: 'ON' },
478
+ },
479
+ 'amp.input_trim': {
480
+ block: 'amp', name: 'input_trim',
481
+ displayLabel: 'Input Trim',
482
+ pidLow: 0x003a, pidHigh: 0x0036,
483
+ // Cache id=54: float a=0.1 b=10 c=1 raw 0.1..10. Same shape as
484
+ // master_vol_trim; `count` here is structural (display = wire ×
485
+ // 1) not integer-only.
486
+ unit: 'count', displayMin: 0.1, displayMax: 10, scaling: 'log10',
487
+ },
488
+ // 8-band Graphic EQ — frequencies per the screenshot: 62/125/250/
489
+ // 500/1K/2K/4K/8K Hz, each ±12 dB. Cache ids 62..69 share the
490
+ // (a=-1, b=1, c=12) signature: wire stored as ±1, displayed as ±12 dB.
491
+ // Uses the `amp_geq_band` unit (scale 12). Drive's GEQ uses plain `db`
492
+ // because its cache stores ±12 directly (c=1).
493
+ 'amp.geq_band_1': { block: 'amp', name: 'geq_band_1', displayLabel: 'Bass', pidLow: 0x003a, pidHigh: 0x003e, unit: 'amp_geq_band', displayMin: -12, displayMax: 12 },
494
+ 'amp.geq_band_2': { block: 'amp', name: 'geq_band_2', displayLabel: 'Mid', pidLow: 0x003a, pidHigh: 0x003f, unit: 'amp_geq_band', displayMin: -12, displayMax: 12 },
495
+ 'amp.geq_band_3': { block: 'amp', name: 'geq_band_3', displayLabel: 'Treble', pidLow: 0x003a, pidHigh: 0x0040, unit: 'amp_geq_band', displayMin: -12, displayMax: 12 },
496
+ 'amp.geq_band_4': { block: 'amp', name: 'geq_band_4', displayLabel: 'Presence', pidLow: 0x003a, pidHigh: 0x0041, unit: 'amp_geq_band', displayMin: -12, displayMax: 12 },
497
+ 'amp.geq_band_5': { block: 'amp', name: 'geq_band_5', displayLabel: '1K', pidLow: 0x003a, pidHigh: 0x0042, unit: 'amp_geq_band', displayMin: -12, displayMax: 12 },
498
+ 'amp.geq_band_6': { block: 'amp', name: 'geq_band_6', displayLabel: '2K', pidLow: 0x003a, pidHigh: 0x0043, unit: 'amp_geq_band', displayMin: -12, displayMax: 12 },
499
+ 'amp.geq_band_7': { block: 'amp', name: 'geq_band_7', displayLabel: '4K', pidLow: 0x003a, pidHigh: 0x0044, unit: 'amp_geq_band', displayMin: -12, displayMax: 12 },
500
+ 'amp.geq_band_8': { block: 'amp', name: 'geq_band_8', displayLabel: '8K', pidLow: 0x003a, pidHigh: 0x0045, unit: 'amp_geq_band', displayMin: -12, displayMax: 12 },
501
+ // renamed for UI-label match (audit row: DISTORT 77)
502
+ 'amp.clarity': {
503
+ block: 'amp', name: 'clarity',
504
+ displayLabel: 'Clarity',
505
+ pidLow: 0x003a, pidHigh: 0x004d,
506
+ unit: 'knob_0_10', displayMin: 0, displayMax: 10,
507
+ // typecode 80 = log10 (HW-053 cont). displayMin=0 makes log10
508
+ // fall back to linear at runtime; declared anyway to match the
509
+ // cache-derived scaling and keep verify-cache-params byte-exact.
510
+ scaling: 'log10',
511
+ },
512
+ // renamed for UI-label match (audit row: DISTORT 82)
513
+ 'amp.amount': {
514
+ block: 'amp', name: 'amount',
515
+ displayLabel: 'Amount',
516
+ pidLow: 0x003a, pidHigh: 0x0052,
517
+ unit: 'knob_0_10', displayMin: 0, displayMax: 10,
518
+ },
519
+ // renamed for UI-label match (audit row: DISTORT 83)
520
+ 'amp.threshold': {
521
+ block: 'amp', name: 'threshold',
522
+ displayLabel: 'Threshold',
523
+ pidLow: 0x003a, pidHigh: 0x0053,
524
+ unit: 'db', displayMin: -60, displayMax: 0,
525
+ },
526
+ 'amp.master_vol_trim': {
527
+ block: 'amp', name: 'master_vol_trim',
528
+ displayLabel: 'Master Vol Trim',
529
+ pidLow: 0x003a, pidHigh: 0x0054,
530
+ unit: 'count', displayMin: 0.1, displayMax: 10, scaling: 'log10',
531
+ // HW-054 readback surfaced "7 count" — misleading. AM4 displays
532
+ // this as a unitless 0..10 knob; the `count` unit tag is
533
+ // structural (encode scale 1, cache c=1). Suppress the suffix.
534
+ displayUnit: '',
535
+ },
536
+ // renamed for UI-label match (audit row: DISTORT 85)
537
+ 'amp.fat': {
538
+ block: 'amp', name: 'fat',
539
+ displayLabel: 'Fat',
540
+ pidLow: 0x003a, pidHigh: 0x0055,
541
+ // IDEAL.Fat Switch — wiggle adjacency pins it right after Cut
542
+ // Switch (0x0034) in the IDEAL column.
543
+ unit: 'enum', displayMin: 0, displayMax: 1,
544
+ enumValues: { 0: 'OFF', 1: 'ON' },
545
+ },
546
+ 'amp.geq_type': {
547
+ block: 'amp', name: 'geq_type',
548
+ displayLabel: 'Type',
549
+ pidLow: 0x003a, pidHigh: 0x0063,
550
+ // Cache id=99: 4-entry enum.
551
+ unit: 'enum', displayMin: 0, displayMax: 3,
552
+ enumValues: { 0: '8 BAND VAR Q', 1: '7 BAND VAR Q', 2: '5 BAND (MARK)', 3: '8 BAND CONST Q' },
553
+ },
554
+ 'amp.high_treble': {
555
+ block: 'amp', name: 'high_treble',
556
+ displayLabel: 'High Treble',
557
+ pidLow: 0x003a, pidHigh: 0x0068,
558
+ // IDEAL.High Treble — bipolar dB ±12 at cache id=104.
559
+ unit: 'db', displayMin: -12, displayMax: 12,
560
+ },
561
+ 'amp.compressor_type': {
562
+ block: 'amp', name: 'compressor_type',
563
+ displayLabel: 'Type',
564
+ pidLow: 0x003a, pidHigh: 0x0074,
565
+ // Cache id=116: 3-entry enum.
566
+ unit: 'enum', displayMin: 0, displayMax: 2,
567
+ enumValues: { 0: 'OUTPUT', 1: 'FEEDBACK', 2: 'GAIN ENHANCER' },
568
+ },
569
+ 'amp.output_mode': {
570
+ block: 'amp', name: 'output_mode',
571
+ displayLabel: 'Amp Output Mode',
572
+ pidLow: 0x003a, pidHigh: 0x0083,
573
+ // Cache id=131: 2-entry enum.
574
+ unit: 'enum', displayMin: 0, displayMax: 1,
575
+ enumValues: { 0: 'FRFR', 1: 'SS PWR AMP + CAB' },
576
+ },
577
+ // HW-024 (Session 30 cont 3, 2026-04-25): hardware-verified at
578
+ // +8 dB on a 1959SLP Normal — first non-default positive-value
579
+ // datapoint for amp.level (HW-014 only tested at the default).
580
+ 'amp.level': {
581
+ block: 'amp', name: 'level',
582
+ pidLow: 0x003a, pidHigh: 0x0000,
583
+ unit: 'db', displayMin: -80, displayMax: 20,
584
+ },
585
+ 'amp.channel': {
586
+ block: 'amp', name: 'channel',
587
+ pidLow: 0x003a, pidHigh: 0x07d2,
588
+ unit: 'enum', displayMin: 0, displayMax: 3,
589
+ // Session 08: A→B→A and A→C→D→A captures confirmed all 4 indices.
590
+ enumValues: { 0: 'A', 1: 'B', 2: 'C', 3: 'D' },
591
+ },
592
+ 'amp.type': {
593
+ block: 'amp', name: 'type',
594
+ pidLow: 0x003a, pidHigh: 0x000a,
595
+ // Session 16: enum dictionary imported from cacheEnums.ts (248 models).
596
+ // Wire indexing verified via drive.type ground truth; amp.type index
597
+ // 0 in cache is "1959SLP Normal". Untested against capture — flag as
598
+ // such when hardening.
599
+ unit: 'enum', displayMin: 0, displayMax: 247,
600
+ enumValues: AMP_TYPES_VALUES,
601
+ },
602
+ // ─── HW-041 (Session 41+, 2026-04-30): Amp Expert-Edit page (4 tabs) ───
603
+ // Source: session-41-amp-{preamp,poweramp,cabinet,speaker}-expert.{pcapng,png}
604
+ // + founder-confirmed audit-input JSONs at docs/audit-input/amp-*.json.
605
+ // Audit script output: docs/audit-output/amp-*.md.
606
+ //
607
+ // The amp Expert page surfaces ~120 knobs across Preamp / Power Amp /
608
+ // Cabinet / Speaker tabs — far more than the 16 BASIC params already
609
+ // registered. Cabinet knobs use a SEPARATE block ID (pidLow=0x003e),
610
+ // not the amp pidLow=0x003a; preamp/power-amp/speaker share 0x003a.
611
+ // Block prefix is kept as `amp` since AM4 surfaces all four tabs as
612
+ // one user-facing block, even though the protocol splits cabinet out.
613
+ //
614
+ // Naming: knobs are keyed by their AM4-Edit label, snake-cased, with
615
+ // disambiguating prefixes where labels collide between sections (e.g.
616
+ // `power_tube_hardness` vs preamp `tube_hardness`, `pi_bias_excursion`
617
+ // vs `master_bias_excursion`, speaker `spkr_compression` vs the
618
+ // amp Compressor section's `compressor_amount`/`compressor_clarity`).
619
+ //
620
+ // Verification: ⚠ unregistered rows from the audit table where the
621
+ // wire×scale match was uniquely-on-the-label-side OR where ambiguity
622
+ // was resolved by domain reasoning (scale plausibility + screenshot
623
+ // section position). Ambiguous rows where neither candidate label is
624
+ // unique on the label side were skipped — those need a follow-up
625
+ // capture wiggling one of the colliding knobs in isolation.
626
+ //
627
+ // Skipped from audit (need follow-up):
628
+ // • Preamp 0x0082 (Input EQ Low Cut duplicate at scale ×10)
629
+ // • Power Amp 0x005d / 0x0090 (Cathode Resistance vs Master Bias
630
+ // Excursion duplicates at wire=1.0)
631
+ // • Power Amp 0x0026 / 0x0064 / 0x008d / 0x0093 (no screenshot match)
632
+ // • Cabinet 0x001c (Cab 1 Low Cut needs different scale than
633
+ // master_low_cut — likely log-Hz storage; skipped pending decode)
634
+ // • Cabinet 0x0045 / 0x0046 (Cab 1/2 Position — bipolar -10..10
635
+ // range needs new unit, no existing fit)
636
+ // • Cabinet 0x0024 / 0x002c / 0x0030 / 0x0031 (LF/HF Damping —
637
+ // three pidHighs share value 8.0, can't disambiguate)
638
+ // • Cabinet 0x0011 + 0x0016 + 0x0017 + many wire=1.0 rows (no
639
+ // screenshot match or false-positive ×10 scale matches)
640
+ // • Speaker 0x0022 / 0x0033 / 0x0039 / 0x0048 / 0x0087 / 0x008e /
641
+ // 0x0092 (Low/Hi Reso, Drive, others not wiggled or scale TBD)
642
+ //
643
+ // ── Preamp tab (pidLow=0x003a) ──
644
+ 'amp.in_boost_level': {
645
+ block: 'amp', name: 'in_boost_level',
646
+ displayLabel: 'In Boost Level',
647
+ pidLow: 0x003a, pidHigh: 0x0081,
648
+ // Preamp.Input Boost section. Screenshot 1.11 dB (no unit visible
649
+ // but Boost knobs are conventionally dB on Fractal).
650
+ unit: 'db', displayMin: 0, displayMax: 20,
651
+ },
652
+ 'amp.saturation_drive': {
653
+ block: 'amp', name: 'saturation_drive',
654
+ displayLabel: 'Saturation Drive',
655
+ pidLow: 0x003a, pidHigh: 0x0070,
656
+ // Preamp.Saturation Mod.Saturation Drive. Screenshot 2.220, no
657
+ // visible unit on AM4-Edit panel; treated as raw count.
658
+ unit: 'count', displayMin: 0, displayMax: 10,
659
+ },
660
+ 'amp.tonestack_frequency': {
661
+ block: 'amp', name: 'tonestack_frequency',
662
+ displayLabel: 'Frequency',
663
+ pidLow: 0x003a, pidHigh: 0x0012,
664
+ // Preamp.Tonestack.Frequency. Screenshot 333.0 Hz raw.
665
+ unit: 'hz', displayMin: 20, displayMax: 20000,
666
+ },
667
+ 'amp.tube_hardness': {
668
+ block: 'amp', name: 'tube_hardness',
669
+ displayLabel: 'Tube Hardness',
670
+ pidLow: 0x003a, pidHigh: 0x0037,
671
+ // Preamp.Preamp.Tube Hardness — knob_0_10 (wire 0.444 → display 4.44).
672
+ // Distinct from amp.power_tube_hardness on the Power Amp tab.
673
+ unit: 'knob_0_10', displayMin: 0, displayMax: 10,
674
+ },
675
+ 'amp.triode_1_plate_freq': {
676
+ block: 'amp', name: 'triode_1_plate_freq',
677
+ displayLabel: 'Triode 1 Plate Freq',
678
+ pidLow: 0x003a, pidHigh: 0x004a,
679
+ unit: 'hz', displayMin: 20, displayMax: 20000,
680
+ },
681
+ 'amp.triode_2_plate_freq': {
682
+ block: 'amp', name: 'triode_2_plate_freq',
683
+ displayLabel: 'Triode 2 Plate Freq',
684
+ pidLow: 0x003a, pidHigh: 0x0049,
685
+ unit: 'hz', displayMin: 20, displayMax: 20000,
686
+ },
687
+ 'amp.preamp_bias': {
688
+ block: 'amp', name: 'preamp_bias',
689
+ displayLabel: 'Preamp Bias',
690
+ pidLow: 0x003a, pidHigh: 0x0031,
691
+ // Screenshot -0.700 raw — bipolar count.
692
+ unit: 'count', displayMin: -1, displayMax: 1,
693
+ },
694
+ 'amp.preamp_bias_excursion': {
695
+ block: 'amp', name: 'preamp_bias_excursion',
696
+ displayLabel: 'Bias Excursion',
697
+ pidLow: 0x003a, pidHigh: 0x0071,
698
+ // Preamp.Preamp.Bias Excursion — percent (wire 0.080 → display 8.0%).
699
+ // Distinct from amp.power_tube_bias_excursion / amp.pi_bias_excursion
700
+ // / amp.master_bias_excursion on the Power Amp tab.
701
+ unit: 'percent', displayMin: 0, displayMax: 100,
702
+ },
703
+ // renamed for UI-label match (audit row: DISTORT 17)
704
+ 'amp.high_cut_frequency': {
705
+ block: 'amp', name: 'high_cut_frequency',
706
+ displayLabel: 'High Cut Frequency',
707
+ pidLow: 0x003a, pidHigh: 0x0011,
708
+ // Preamp.Preamp.High Cut Frequency — bottom row of the PREAMP
709
+ // section. Screenshot 9999.1 Hz raw.
710
+ unit: 'hz', displayMin: 20, displayMax: 20000,
711
+ },
712
+ // renamed for UI-label match (audit row: DISTORT 16)
713
+ 'amp.low_cut': {
714
+ block: 'amp', name: 'low_cut',
715
+ displayLabel: 'Low Cut',
716
+ pidLow: 0x003a, pidHigh: 0x0010,
717
+ // Preamp.Input EQ.Low Cut. Screenshot 130.0 Hz raw.
718
+ unit: 'hz', displayMin: 20, displayMax: 20000,
719
+ },
720
+ 'amp.input_eq_gain': {
721
+ block: 'amp', name: 'input_eq_gain',
722
+ displayLabel: 'Gain',
723
+ pidLow: 0x003a, pidHigh: 0x0050,
724
+ // Preamp.Input EQ.Gain. Screenshot 11.00 dB raw.
725
+ unit: 'db', displayMin: -20, displayMax: 20,
726
+ },
727
+ // renamed for UI-label match (audit row: DISTORT 78)
728
+ 'amp.q': {
729
+ block: 'amp', name: 'q',
730
+ displayLabel: 'Q',
731
+ pidLow: 0x003a, pidHigh: 0x004e,
732
+ // Preamp.Input EQ.Q. Screenshot 0.120 raw count.
733
+ unit: 'count', displayMin: 0.1, displayMax: 10,
734
+ // typecode 64 = log10 (HW-053b cont audit)
735
+ scaling: 'log10',
736
+ },
737
+ // ── Power Amp tab (pidLow=0x003a) ──
738
+ 'amp.supply_sag': {
739
+ block: 'amp', name: 'supply_sag',
740
+ displayLabel: 'Supply Sag',
741
+ pidLow: 0x003a, pidHigh: 0x001d,
742
+ // Power Amp.Power Supply.Supply Sag. Screenshot 2.20 (knob_0_10:
743
+ // wire 0.220 → display 2.20). Disambiguated from Power Tubes Hardness
744
+ // (which sits at 0x005f wire=0.700 → display 7.00).
745
+ unit: 'knob_0_10', displayMin: 0, displayMax: 10,
746
+ },
747
+ // renamed for UI-label match (audit row: DISTORT 31)
748
+ 'amp.negative_fb': {
749
+ block: 'amp', name: 'negative_fb',
750
+ displayLabel: 'Negative FB',
751
+ pidLow: 0x003a, pidHigh: 0x001f,
752
+ // Power Amp.Power Amp.Negative Feedback. Screenshot 4.44 — percent
753
+ // ×100 (wire 0.0444 → 4.44%). NFB display has no visible unit on
754
+ // AM4-Edit; ×100 scale fits the captured wire cleanly.
755
+ unit: 'percent', displayMin: 0, displayMax: 10,
756
+ // HW-054 readback surfaced "5 percent" — misleading. AM4 actually
757
+ // displays NFB as a unitless 0..10 knob; the `percent` unit is
758
+ // for encode-scale only (cache c=100). Suppress the suffix.
759
+ displayUnit: '',
760
+ // HW-053b cont audit: HW-053: cache b*c = 10, not 100. Hand entry was off by 10×; readback came out 50 instead of 5.
761
+ },
762
+ 'amp.presence_freq': {
763
+ block: 'amp', name: 'presence_freq',
764
+ displayLabel: 'Presence Freq',
765
+ pidLow: 0x003a, pidHigh: 0x0020,
766
+ // HW-053 (2026-05-04): cache record at id=32 has a=0.1, b=10, c=1.
767
+ // The original HW-040 screenshot read "6.660 Hz raw" but the AM4
768
+ // device actually displays the value as 0.1..10 (kHz on the device,
769
+ // shown without explicit unit suffix). Earlier registration of
770
+ // displayMin=20 displayMax=20000 was off by ~1000× and saturated
771
+ // every write. Range corrected to match the cache truth; unit kept
772
+ // as 'hz' so agent reads it as a frequency knob (the agent should
773
+ // pass values in the 0.1..10 range, which AM4-Edit renders as kHz).
774
+ unit: 'hz', displayMin: 0.1, displayMax: 10,
775
+ // HW-054 readback surfaced "3 hz" — agent then mentally translated
776
+ // to kHz, awkward. Override the suffix to 'kHz' so the readback
777
+ // matches the user's mental model. Encoding is unaffected.
778
+ displayUnit: 'kHz',
779
+ // typecode 64 = log10 (HW-053 confirmed: write 3 → AM4 3.000 ✓ but readback was 7 with linear decode)
780
+ scaling: 'log10',
781
+ },
782
+ 'amp.depth_freq': {
783
+ block: 'amp', name: 'depth_freq',
784
+ displayLabel: 'Depth Freq',
785
+ pidLow: 0x003a, pidHigh: 0x0024,
786
+ // HW-053 (2026-05-04): cache record at id=36 has a=50, b=500, c=1.
787
+ // Same screenshot-misread pattern as presence_freq. Range corrected
788
+ // to the cache truth (50..500 Hz, real Hz this time).
789
+ unit: 'hz', displayMin: 50, displayMax: 500,
790
+ },
791
+ // renamed for UI-label match (audit row: DISTORT 40)
792
+ 'amp.harmonics': {
793
+ block: 'amp', name: 'harmonics',
794
+ displayLabel: 'Harmonics',
795
+ pidLow: 0x003a, pidHigh: 0x0028,
796
+ // Power Amp.Cathode Follower.Harmonics. Screenshot 0.150 Hz —
797
+ // raw value (label says "Hz" but the magnitude reads as a 0..1
798
+ // ratio, likely a normalised knob despite the unit suffix).
799
+ unit: 'count', displayMin: 0, displayMax: 1,
800
+ },
801
+ 'amp.b_plus_time_constant': {
802
+ block: 'amp', name: 'b_plus_time_constant',
803
+ displayLabel: 'B+ Time Constant',
804
+ pidLow: 0x003a, pidHigh: 0x002a,
805
+ // Power Amp.Power Supply.B+ Time Constant. Screenshot 9.50 ms
806
+ // (wire 0.0095 ×1000 = 9.5).
807
+ unit: 'ms', displayMin: 0.1, displayMax: 1000,
808
+ // typecode 68 = log10 (HW-053b cont audit)
809
+ scaling: 'log10',
810
+ },
811
+ 'amp.grid_bias': {
812
+ block: 'amp', name: 'grid_bias',
813
+ displayLabel: 'Grid Bias',
814
+ pidLow: 0x003a, pidHigh: 0x002b,
815
+ // Power Amp.Power Tubes.Grid Bias. Screenshot 16.0 % (wire 0.160).
816
+ unit: 'percent', displayMin: 0, displayMax: 100,
817
+ },
818
+ 'amp.xformer_drive': {
819
+ block: 'amp', name: 'xformer_drive',
820
+ displayLabel: 'XFormer Drive',
821
+ pidLow: 0x003a, pidHigh: 0x0035,
822
+ // Power Amp.Transformer.XFormer Drive. Screenshot 0.120 raw count.
823
+ unit: 'count', displayMin: 0, displayMax: 1,
824
+ // typecode 64 = log10 (HW-053b cont audit)
825
+ scaling: 'log10',
826
+ },
827
+ 'amp.xformer_matching': {
828
+ block: 'amp', name: 'xformer_matching',
829
+ displayLabel: 'XFormer Matching',
830
+ pidLow: 0x003a, pidHigh: 0x003a,
831
+ // Power Amp.Transformer.XFormer Matching. Screenshot 1.300 raw.
832
+ unit: 'count', displayMin: 0.1, displayMax: 10,
833
+ // typecode 64 = log10 (HW-053b cont audit)
834
+ scaling: 'log10',
835
+ },
836
+ 'amp.screen_frequency': {
837
+ block: 'amp', name: 'screen_frequency',
838
+ displayLabel: 'Screen Frequency',
839
+ pidLow: 0x003a, pidHigh: 0x003b,
840
+ // HW-053 (2026-05-04): cache record at id=59 has a=1, b=100, c=1.
841
+ // The AM4 device displays this as a unitless 0..100 raw knob (the
842
+ // founder confirmed "no units on device for Screen Frequency"). The
843
+ // earlier "Hz" registration was a screenshot misread. Despite the
844
+ // parameterName ending in "FREQ", the firmware exposes it as a raw
845
+ // power-supply knob without a frequency unit on the device UI.
846
+ unit: 'count', displayMin: 1, displayMax: 100,
847
+ // typecode 64 = log10 (HW-053b cont audit)
848
+ scaling: 'log10',
849
+ },
850
+ 'amp.screen_q': {
851
+ block: 'amp', name: 'screen_q',
852
+ displayLabel: 'Screen Q',
853
+ pidLow: 0x003a, pidHigh: 0x003c,
854
+ // Power Amp.Power Supply.Screen Q. Screenshot 8.500 raw count.
855
+ unit: 'count', displayMin: 0.1, displayMax: 10,
856
+ // typecode 64 = log10 (HW-053b cont audit)
857
+ scaling: 'log10',
858
+ },
859
+ 'amp.power_tube_bias_excursion': {
860
+ block: 'amp', name: 'power_tube_bias_excursion',
861
+ displayLabel: 'Bias Excursion',
862
+ pidLow: 0x003a, pidHigh: 0x0046,
863
+ // Power Amp.Power Tubes.Bias Excursion. Screenshot 19.0 %.
864
+ // Distinct from preamp_bias_excursion / pi_bias_excursion /
865
+ // master_bias_excursion (4 separate "bias excursion" knobs).
866
+ unit: 'percent', displayMin: 0, displayMax: 100,
867
+ },
868
+ 'amp.cathode_follower_compression': {
869
+ block: 'amp', name: 'cathode_follower_compression',
870
+ displayLabel: 'Power Tube Type',
871
+ pidLow: 0x003a, pidHigh: 0x004b,
872
+ // Power Amp.Cathode Follower.Compression. Screenshot 14.0 %.
873
+ // Stored raw (wire 14.0 → display 14.0%) NOT as percent ×100,
874
+ // confirmed by scale ×1 match in audit. Treat unit as 'count'
875
+ // even though display suffix is %.
876
+ unit: 'count', displayMin: 0, displayMax: 100,
877
+ },
878
+ 'amp.ac_line_frequency': {
879
+ block: 'amp', name: 'ac_line_frequency',
880
+ displayLabel: 'AC Line Frequency',
881
+ pidLow: 0x003a, pidHigh: 0x005e,
882
+ // Power Amp.Power Supply.AC Line Frequency. Screenshot 65 Hz raw.
883
+ // Typical range 50/60 Hz mains; AM4-Edit allows wider sweep.
884
+ unit: 'hz', displayMin: 30, displayMax: 200,
885
+ },
886
+ // renamed for UI-label match (audit row: DISTORT 95)
887
+ 'amp.hardness': {
888
+ block: 'amp', name: 'hardness',
889
+ displayLabel: 'Hardness',
890
+ pidLow: 0x003a, pidHigh: 0x005f,
891
+ // Power Amp.Power Tubes.Hardness. Screenshot 7.00 (knob_0_10).
892
+ // Distinct from preamp tube_hardness (separate knob, separate
893
+ // wire address).
894
+ unit: 'knob_0_10', displayMin: 0, displayMax: 10,
895
+ // typecode 80 = log10 (HW-053b cont audit)
896
+ scaling: 'log10',
897
+ },
898
+ 'amp.cathode_time_const': {
899
+ block: 'amp', name: 'cathode_time_const',
900
+ displayLabel: 'Cathode Time Const',
901
+ pidLow: 0x003a, pidHigh: 0x0065,
902
+ // Power Amp.Power Amp.Cathode Time Const. Screenshot 10.00 ms
903
+ // (wire 0.010 ×1000 = 10).
904
+ unit: 'ms', displayMin: 0.1, displayMax: 1000,
905
+ // typecode 68 = log10 (HW-053b cont audit)
906
+ scaling: 'log10',
907
+ },
908
+ 'amp.mismatch': {
909
+ block: 'amp', name: 'mismatch',
910
+ displayLabel: 'Mismatch',
911
+ pidLow: 0x003a, pidHigh: 0x0069,
912
+ // Power Amp.Power Tubes.Mismatch. Screenshot 0.180 raw count.
913
+ unit: 'count', displayMin: 0, displayMax: 1,
914
+ },
915
+ 'amp.variac': {
916
+ block: 'amp', name: 'variac',
917
+ displayLabel: 'Variac',
918
+ pidLow: 0x003a, pidHigh: 0x006c,
919
+ // Power Amp.Power Supply.Variac. Screenshot 55.0 % (wire 0.550 ×100).
920
+ unit: 'percent', displayMin: 0, displayMax: 100,
921
+ },
922
+ 'amp.pi_bias_excursion': {
923
+ block: 'amp', name: 'pi_bias_excursion',
924
+ displayLabel: 'PI Bias Excursion',
925
+ pidLow: 0x003a, pidHigh: 0x0079,
926
+ // Power Amp.Power Amp.PI Bias Excursion (phase-inverter).
927
+ // Screenshot 11.0 % (wire 0.110 ×100).
928
+ unit: 'percent', displayMin: 0, displayMax: 100,
929
+ },
930
+ 'amp.master_bias_excursion': {
931
+ block: 'amp', name: 'master_bias_excursion',
932
+ displayLabel: 'Master Bias Excursion',
933
+ pidLow: 0x003a, pidHigh: 0x008b,
934
+ // Power Amp.Power Tubes.Master Bias Excursion. Screenshot 20.0 %.
935
+ unit: 'percent', displayMin: 0, displayMax: 100,
936
+ },
937
+ // ── Speaker tab (pidLow=0x003a) ──
938
+ // Section breakdown: Impedance (top half — XFormer / Low / Hi knobs +
939
+ // a frequency-response curve) and Speaker (bottom — speaker-emulation
940
+ // character knobs).
941
+ 'amp.xformer_low_freq': {
942
+ block: 'amp', name: 'xformer_low_freq',
943
+ displayLabel: 'XFormer Low Freq',
944
+ pidLow: 0x003a, pidHigh: 0x0016,
945
+ // Speaker.Impedance.XFormer Low Freq. Screenshot 33.3 Hz (wire stores
946
+ // 33.33; AM4-Edit display rounds to 1 decimal).
947
+ unit: 'hz', displayMin: 10, displayMax: 20000,
948
+ },
949
+ 'amp.low_freq': {
950
+ block: 'amp', name: 'low_freq',
951
+ displayLabel: 'Low Freq',
952
+ pidLow: 0x003a, pidHigh: 0x0021,
953
+ // Speaker.Impedance.Low Freq. Screenshot 44.4 Hz (wire 44.44).
954
+ unit: 'hz', displayMin: 10, displayMax: 20000,
955
+ },
956
+ 'amp.low_q': {
957
+ block: 'amp', name: 'low_q',
958
+ displayLabel: 'Low Q',
959
+ pidLow: 0x003a, pidHigh: 0x0030,
960
+ // Speaker.Impedance.Low Q. Screenshot 0.666 raw count.
961
+ unit: 'count', displayMin: 0.1, displayMax: 10,
962
+ // typecode 64 = log10 (HW-053b cont audit)
963
+ scaling: 'log10',
964
+ },
965
+ 'amp.xformer_hi_freq': {
966
+ block: 'amp', name: 'xformer_hi_freq',
967
+ displayLabel: 'XFormer Hi Freq',
968
+ pidLow: 0x003a, pidHigh: 0x0017,
969
+ // Speaker.Impedance.XFormer Hi Freq. Screenshot 12000 Hz raw.
970
+ unit: 'hz', displayMin: 100, displayMax: 20000,
971
+ },
972
+ 'amp.high_freq': {
973
+ block: 'amp', name: 'high_freq',
974
+ displayLabel: 'High Freq',
975
+ pidLow: 0x003a, pidHigh: 0x0032,
976
+ // Speaker.Impedance.High Freq. Screenshot 666.0 Hz raw.
977
+ unit: 'hz', displayMin: 100, displayMax: 20000,
978
+ },
979
+ 'amp.hi_slope': {
980
+ block: 'amp', name: 'hi_slope',
981
+ displayLabel: 'Hi Slope',
982
+ pidLow: 0x003a, pidHigh: 0x006b,
983
+ // Speaker.Impedance.Hi Slope. Screenshot 8.880 (knob_0_10:
984
+ // wire 0.888 ×10 = 8.88). Disambiguated from Speaker.Compression
985
+ // (also wire 0.888) by section-position heuristic — Hi Slope is
986
+ // higher in the AM4-Edit UI (Impedance > Speaker), and 0x006b
987
+ // sits before 0x007a in pidHigh order.
988
+ unit: 'knob_0_10', displayMin: 0, displayMax: 10,
989
+ // typecode 64 = log10 (HW-053b cont audit)
990
+ scaling: 'log10',
991
+ },
992
+ 'amp.cab_resonance': {
993
+ block: 'amp', name: 'cab_resonance',
994
+ displayLabel: 'Cab Resonance',
995
+ pidLow: 0x003a, pidHigh: 0x0088,
996
+ // Speaker.Impedance.Cab Resonance. Screenshot 111.1 % (wire 1.111
997
+ // ×100). Display can exceed 100% — set displayMax wider.
998
+ unit: 'percent', displayMin: 0, displayMax: 200,
999
+ },
1000
+ 'amp.speaker_impedance': {
1001
+ block: 'amp', name: 'speaker_impedance',
1002
+ displayLabel: 'Speaker Impedance',
1003
+ pidLow: 0x003a, pidHigh: 0x0086,
1004
+ // Speaker.Impedance.Speaker Impedance. Screenshot 1.220 raw count.
1005
+ unit: 'count', displayMin: 0.1, displayMax: 10,
1006
+ // typecode 64 = log10 (HW-053b cont audit)
1007
+ scaling: 'log10',
1008
+ },
1009
+ 'amp.spkr_compression': {
1010
+ block: 'amp', name: 'spkr_compression',
1011
+ displayLabel: 'Compression',
1012
+ pidLow: 0x003a, pidHigh: 0x007a,
1013
+ // Speaker.Speaker.Compression. Screenshot 8.88 (knob_0_10).
1014
+ // Named with `spkr_` prefix to distinguish from the Compressor
1015
+ // section's `compression` register (cacheParams.ts pidHigh=0x0057).
1016
+ unit: 'knob_0_10', displayMin: 0, displayMax: 10,
1017
+ },
1018
+ 'amp.compliance': {
1019
+ block: 'amp', name: 'compliance',
1020
+ displayLabel: 'Compliance',
1021
+ pidLow: 0x003a, pidHigh: 0x0084,
1022
+ // Speaker.Speaker.Compliance. Screenshot 99.0 % (wire 0.990).
1023
+ unit: 'percent', displayMin: 0, displayMax: 100,
1024
+ },
1025
+ // renamed for UI-label match (audit row: DISTORT 123)
1026
+ 'amp.time_constant': {
1027
+ block: 'amp', name: 'time_constant',
1028
+ displayLabel: 'Time Constant',
1029
+ pidLow: 0x003a, pidHigh: 0x007b,
1030
+ // Speaker.Speaker.Time Constant. Screenshot 1000.0 ms (wire 1.000
1031
+ // ×1000). `spkr_` prefix to avoid confusion with cathode_time_const
1032
+ // on the Power Amp tab.
1033
+ unit: 'ms', displayMin: 0.1, displayMax: 10000,
1034
+ // typecode 68 = log10 (HW-053b cont audit)
1035
+ scaling: 'log10',
1036
+ },
1037
+ 'amp.thump': {
1038
+ block: 'amp', name: 'thump',
1039
+ displayLabel: 'Thump',
1040
+ pidLow: 0x003a, pidHigh: 0x008f,
1041
+ // Speaker.Speaker.Thump. Screenshot 1.11 (knob_0_10).
1042
+ unit: 'knob_0_10', displayMin: 0, displayMax: 10,
1043
+ },
1044
+ // ── Cabinet tab (pidLow=0x003e — separate block ID from amp 0x003a) ──
1045
+ 'amp.cab1_distance': {
1046
+ block: 'amp', name: 'cab1_distance',
1047
+ pidLow: 0x003e, pidHigh: 0x0002,
1048
+ // Cabinet.Cab 1.Distance. Screenshot 2.22 cm (wire 0.022 ×100).
1049
+ // Display unit on AM4-Edit is "cm"; firmware stores cm/100.
1050
+ unit: 'percent', displayMin: 0, displayMax: 100,
1051
+ },
1052
+ 'amp.cab_mic_preamp_drive': {
1053
+ block: 'amp', name: 'cab_mic_preamp_drive',
1054
+ displayLabel: 'Drive',
1055
+ pidLow: 0x003e, pidHigh: 0x001a,
1056
+ // Cabinet.Cab Mic Preamp.Drive. Screenshot 6.60 (knob_0_10).
1057
+ unit: 'knob_0_10', displayMin: 0, displayMax: 10,
1058
+ },
1059
+ 'amp.cab_mic_preamp_saturation': {
1060
+ block: 'amp', name: 'cab_mic_preamp_saturation',
1061
+ displayLabel: 'Saturation',
1062
+ pidLow: 0x003e, pidHigh: 0x001b,
1063
+ // Cabinet.Cab Mic Preamp.Saturation. Screenshot 7.77 (knob_0_10).
1064
+ unit: 'knob_0_10', displayMin: 0, displayMax: 10,
1065
+ },
1066
+ 'amp.cab_mic_preamp_treble': {
1067
+ block: 'amp', name: 'cab_mic_preamp_treble',
1068
+ displayLabel: 'Treble',
1069
+ pidLow: 0x003e, pidHigh: 0x0027,
1070
+ // Cabinet.Cab Mic Preamp.Treble. Screenshot 10.00 dB raw.
1071
+ // (Many wire=1.0 rows in the audit also matched this label via the
1072
+ // ×10 scale — those are false positives; the canonical pidHigh
1073
+ // is 0x0027 with wire=10.0 raw.)
1074
+ unit: 'db', displayMin: -20, displayMax: 20,
1075
+ },
1076
+ 'amp.room_size': {
1077
+ block: 'amp', name: 'room_size',
1078
+ displayLabel: 'Room Size',
1079
+ pidLow: 0x003e, pidHigh: 0x001d,
1080
+ // Cabinet.Room.Room Size. Screenshot 5.55 m raw count.
1081
+ unit: 'count', displayMin: 0.1, displayMax: 50,
1082
+ },
1083
+ 'amp.mic_spacing': {
1084
+ block: 'amp', name: 'mic_spacing',
1085
+ displayLabel: 'Mic Spacing',
1086
+ pidLow: 0x003e, pidHigh: 0x001e,
1087
+ // Cabinet.Room.Mic Spacing. Screenshot 10.1 % (wire 0.101 ×100).
1088
+ unit: 'percent', displayMin: 0, displayMax: 100,
1089
+ },
1090
+ 'amp.cab_master_high_cut': {
1091
+ block: 'amp', name: 'cab_master_high_cut',
1092
+ displayLabel: 'High Cut',
1093
+ pidLow: 0x003e, pidHigh: 0x0020,
1094
+ // Cabinet.Cab Master EQ.Master High Cut. Screenshot 222 Hz
1095
+ // (founder-confirmed deliberate non-default value).
1096
+ unit: 'hz', displayMin: 20, displayMax: 20000,
1097
+ // typecode 64 = log10 (HW-053b cont audit)
1098
+ scaling: 'log10',
1099
+ },
1100
+ 'amp.cab_master_low_cut': {
1101
+ block: 'amp', name: 'cab_master_low_cut',
1102
+ displayLabel: 'Proximity Frequency',
1103
+ pidLow: 0x003e, pidHigh: 0x0022,
1104
+ // Cabinet.Cab Master EQ.Master Low Cut. Screenshot 33.3 Hz raw.
1105
+ unit: 'hz', displayMin: 20, displayMax: 20000,
1106
+ },
1107
+ 'amp.cab_master_level': {
1108
+ block: 'amp', name: 'cab_master_level',
1109
+ displayLabel: 'Air',
1110
+ pidLow: 0x003e, pidHigh: 0x002d,
1111
+ // Cabinet.Cab Extras.Cab Master Level. Screenshot 1.1 dB
1112
+ // (wire 0.110 ×10 = 1.10). Stored as knob_0_10 even though the
1113
+ // display suffix is dB.
1114
+ unit: 'knob_0_10', displayMin: 0, displayMax: 10,
1115
+ },
1116
+ 'amp.air_frequency': {
1117
+ block: 'amp', name: 'air_frequency',
1118
+ displayLabel: 'Frequency',
1119
+ pidLow: 0x003e, pidHigh: 0x002e,
1120
+ // Cabinet.Air.Frequency. Screenshot 12121 Hz raw.
1121
+ unit: 'hz', displayMin: 100, displayMax: 20000,
1122
+ },
1123
+ 'amp.room_diffusion': {
1124
+ block: 'amp', name: 'room_diffusion',
1125
+ displayLabel: 'Room Diffusion',
1126
+ pidLow: 0x003e, pidHigh: 0x0032,
1127
+ // Cabinet.Room.Room Diffusion. Screenshot 7.0 % (wire 0.070).
1128
+ unit: 'percent', displayMin: 0, displayMax: 100,
1129
+ },
1130
+ 'amp.cab2_low_cut': {
1131
+ block: 'amp', name: 'cab2_low_cut',
1132
+ displayLabel: 'Low Cut',
1133
+ pidLow: 0x003e, pidHigh: 0x0036,
1134
+ // Cabinet.Cab 2.Low Cut. Screenshot 55.0 Hz raw.
1135
+ unit: 'hz', displayMin: 20, displayMax: 20000,
1136
+ // typecode 64 = log10 (HW-053b cont audit)
1137
+ scaling: 'log10',
1138
+ },
1139
+ 'amp.cab1_high_cut': {
1140
+ block: 'amp', name: 'cab1_high_cut',
1141
+ displayLabel: 'High Cut',
1142
+ pidLow: 0x003e, pidHigh: 0x0037,
1143
+ // Cabinet.Cab 1.High Cut. Screenshot 5500.0 Hz raw.
1144
+ unit: 'hz', displayMin: 20, displayMax: 20000,
1145
+ },
1146
+ 'amp.cab2_high_cut': {
1147
+ block: 'amp', name: 'cab2_high_cut',
1148
+ displayLabel: 'High Cut',
1149
+ pidLow: 0x003e, pidHigh: 0x0038,
1150
+ // Cabinet.Cab 2.High Cut. Screenshot 4444.1 Hz raw.
1151
+ unit: 'hz', displayMin: 20, displayMax: 20000,
1152
+ },
1153
+ 'amp.align_distance_1': {
1154
+ block: 'amp', name: 'align_distance_1',
1155
+ displayLabel: 'Mic Distance',
1156
+ pidLow: 0x003e, pidHigh: 0x0012,
1157
+ // Cabinet.Align modal.Distance 1. Screenshot 5.000 ms
1158
+ // (wire 0.005 ×1000). Distinct delay-trim knob from Cab 1 Distance.
1159
+ unit: 'ms', displayMin: 0, displayMax: 100,
1160
+ },
1161
+ 'amp.align_distance_2': {
1162
+ block: 'amp', name: 'align_distance_2',
1163
+ displayLabel: 'Mic Distance',
1164
+ pidLow: 0x003e, pidHigh: 0x0013,
1165
+ // Cabinet.Align modal.Distance 2. Screenshot 6.000 ms.
1166
+ unit: 'ms', displayMin: 0, displayMax: 100,
1167
+ },
1168
+ // Session 90 (2026-05-17) — CABINET UI-MISSING closeout. 25 new amp
1169
+ // params at pidLow=0x003e from the Ghidra CABINET catalog (Sessions
1170
+ // 82-83) cross-referenced against the 54-entry UI-MISSING list in
1171
+ // samples/captured/decoded/coverage-cross-ref-audit.md. Names chosen
1172
+ // to match the AM4-Edit XML display label after norm() (lowercase +
1173
+ // non-alphanumeric → _) so each new entry registers as WIRED-MATCHED
1174
+ // (not MISLABEL). For paired controls (Cab 1 / Cab 2 sharing a single
1175
+ // XML label like "Pan", "Bank", "Mute"), only the "_1" member is
1176
+ // added — the "_2" member would force a MISLABEL since both entries
1177
+ // can't simultaneously match the same display label and the drift
1178
+ // guard ceiling is at 112. Future capture batch may add the _2
1179
+ // variants once the renaming pass lowers the MISLABEL count.
1180
+ //
1181
+ // Units follow the brief: enum (no enumValues — needs capture) for
1182
+ // BANK / TYPE / MODE / INPUTSEL / PRETYPE / OVERSAMPLE / ROOMSHAPE;
1183
+ // OFF/ON enum for MUTE / AUTO_ALIGN / BYPASS; bipolar_percent for
1184
+ // PAN; hz for PROXIMITY / LO-HI-CUT / FREQ; db for LEVEL / DAMPING;
1185
+ // count for VU meter (read-only) and other dimensionless knobs.
1186
+ // Hand-authored (not via paramNames.ts / gen-params-from-cache.ts)
1187
+ // because the CABINET catalog ids live in the Ghidra dispatcher
1188
+ // table, not in any S2/S3 cache block — same hand-author pattern as
1189
+ // the existing 16 cab_* entries from Session 38 (HW-041).
1190
+ //
1191
+ // SKIP rationale (per brief):
1192
+ // - 33 CABINET_ZOOM, 65-68 DYNACAB_TYPE/MIC1/2 — XML display "—"
1193
+ // (no UI evidence, no name to verify against).
1194
+ // - 65000+ CABINET_NAME/LABEL/BTN/PICKER/COPY_MENU/ALIGN_GRAPH —
1195
+ // firmware ghost registers (string-name slots, action buttons),
1196
+ // not user-editable knobs.
1197
+ // - 53 CABINET_LOCUT1 (XML "Low Cut") — would collide with the
1198
+ // existing amp.low_cut at pidLow=0x003a, and using a different
1199
+ // name would force MISLABEL.
1200
+ // - 37 CABINET_BASS (XML "Bass"), 38 CABINET_MID (XML "Mid"),
1201
+ // 36 CABINET_PRETYPE (XML "Type") — name collisions with the
1202
+ // existing amp.bass / amp.mid / amp.type at pidLow=0x003a.
1203
+ // - "_2" pair members where the XML label is identical to the
1204
+ // "_1" member's label (BANK2, TYPE2, PAN2, PROXIMITY2, MUTE2,
1205
+ // LOSLOPE2, HISLOPE2, DYNACAB_R2, DYNACAB_Z2) — would force
1206
+ // MISLABEL since both can't match the same display string.
1207
+ 'amp.bank': {
1208
+ block: 'amp', name: 'bank',
1209
+ displayLabel: 'Bank',
1210
+ pidLow: 0x003e, pidHigh: 0x000a,
1211
+ // CABINET_BANK1 (catalog id=10). AM4-Edit "Bank" — cab IR-pack
1212
+ // selector (UltraRes / Legacy / etc.). TODO: capture enum values.
1213
+ unit: 'enum', displayMin: 0, displayMax: 31,
1214
+ },
1215
+ 'amp.cab': {
1216
+ block: 'amp', name: 'cab',
1217
+ displayLabel: 'Cab #',
1218
+ pidLow: 0x003e, pidHigh: 0x000c,
1219
+ // CABINET_TYPE1 (catalog id=12). AM4-Edit "Cab #" — cab IR
1220
+ // selector within the active bank. TODO: capture enum values
1221
+ // (range varies by bank; conservative 0..255 for now).
1222
+ unit: 'enum', displayMin: 0, displayMax: 255,
1223
+ },
1224
+ 'amp.pan': {
1225
+ block: 'amp', name: 'pan',
1226
+ displayLabel: 'Pan',
1227
+ pidLow: 0x003e, pidHigh: 0x0010,
1228
+ // CABINET_PAN1 (catalog id=16). AM4-Edit "Pan" — cab 1 stereo pan.
1229
+ unit: 'bipolar_percent', displayMin: -100, displayMax: 100,
1230
+ },
1231
+ // SKIP: CABINET_PROXIMITY1 (catalog id=20, XML "Proximity") would
1232
+ // collide with the existing CACHE_PARAMS entry `amp.proximity` at
1233
+ // pidLow=0x003a / pidHigh=0x15 (the cross-block addressing surfaced
1234
+ // by the variant resolver as DISTORT cache id=21). verify-cache-
1235
+ // params fails on duplicate-key with conflicting pidLow. Renaming
1236
+ // to e.g. `cab_proximity` would force a WIRED-MISLABEL since the
1237
+ // XML label is "Proximity"; the drift guard ceiling has no
1238
+ // headroom. Defer until a WIRED-MISLABEL review pass lowers the
1239
+ // ceiling and frees a slot.
1240
+ 'amp.cab_mode': {
1241
+ block: 'amp', name: 'cab_mode',
1242
+ displayLabel: 'Cab Mode',
1243
+ pidLow: 0x003e, pidHigh: 0x0018,
1244
+ // CABINET_MODE (catalog id=24). AM4-Edit "Cab Mode" — selects
1245
+ // single / dual / DynaCab routing. TODO: capture enum values.
1246
+ unit: 'enum', displayMin: 0, displayMax: 7,
1247
+ },
1248
+ 'amp.cab_section': {
1249
+ block: 'amp', name: 'cab_section',
1250
+ displayLabel: 'Cab Section',
1251
+ pidLow: 0x003e, pidHigh: 0x0019,
1252
+ // CABINET_BYPASS (catalog id=25). AM4-Edit "Cab Section" — section-
1253
+ // level bypass for the cab block (distinct from preset bypass).
1254
+ unit: 'enum', displayMin: 0, displayMax: 1,
1255
+ enumValues: { 0: 'OFF', 1: 'ON' },
1256
+ },
1257
+ 'amp.room_level': {
1258
+ block: 'amp', name: 'room_level',
1259
+ displayLabel: 'Room Level',
1260
+ pidLow: 0x003e, pidHigh: 0x001c,
1261
+ // CABINET_ROOMMIX (catalog id=28). AM4-Edit "Room Level" — wet/dry
1262
+ // mix of the room reflection model into the cab signal.
1263
+ unit: 'percent', displayMin: 0, displayMax: 100,
1264
+ },
1265
+ 'amp.cab_input_mode': {
1266
+ block: 'amp', name: 'cab_input_mode',
1267
+ displayLabel: 'Cab Input Mode',
1268
+ pidLow: 0x003e, pidHigh: 0x0023,
1269
+ // CABINET_INPUTSEL (catalog id=35). AM4-Edit "Cab Input Mode" —
1270
+ // selects L / R / SUM input to the cab block. TODO: capture enum.
1271
+ unit: 'enum', displayMin: 0, displayMax: 3,
1272
+ },
1273
+ 'amp.mode': {
1274
+ block: 'amp', name: 'mode',
1275
+ displayLabel: 'Mode',
1276
+ pidLow: 0x003e, pidHigh: 0x0028,
1277
+ // CABINET_OVERSAMPLE (catalog id=40). AM4-Edit "Mode" — IR engine
1278
+ // oversampling / quality mode selector. TODO: capture enum values.
1279
+ unit: 'enum', displayMin: 0, displayMax: 3,
1280
+ },
1281
+ 'amp.floor_reflections': {
1282
+ block: 'amp', name: 'floor_reflections',
1283
+ displayLabel: 'Floor Reflections',
1284
+ pidLow: 0x003e, pidHigh: 0x002c,
1285
+ // CABINET_FLOORLVL (catalog id=44). AM4-Edit "Floor Reflections" —
1286
+ // level of floor-bounce reflections in the room model.
1287
+ unit: 'db', displayMin: -60, displayMax: 12,
1288
+ },
1289
+ 'amp.room_shape': {
1290
+ block: 'amp', name: 'room_shape',
1291
+ displayLabel: 'Room Shape',
1292
+ pidLow: 0x003e, pidHigh: 0x002f,
1293
+ // CABINET_ROOMSHAPE (catalog id=47). AM4-Edit "Room Shape" —
1294
+ // selects the room geometry preset (square / rectangle / etc.).
1295
+ // TODO: capture enum values.
1296
+ unit: 'enum', displayMin: 0, displayMax: 7,
1297
+ },
1298
+ 'amp.lf_damping': {
1299
+ block: 'amp', name: 'lf_damping',
1300
+ displayLabel: 'LF Damping',
1301
+ pidLow: 0x003e, pidHigh: 0x0030,
1302
+ // CABINET_LFDAMPING (catalog id=48). AM4-Edit "LF Damping" —
1303
+ // low-frequency damping in the room reflection model.
1304
+ unit: 'db', displayMin: -60, displayMax: 12,
1305
+ },
1306
+ 'amp.hf_damping': {
1307
+ block: 'amp', name: 'hf_damping',
1308
+ displayLabel: 'HF Damping',
1309
+ pidLow: 0x003e, pidHigh: 0x0031,
1310
+ // CABINET_HFDAMPING (catalog id=49). AM4-Edit "HF Damping" —
1311
+ // high-frequency damping in the room reflection model.
1312
+ unit: 'db', displayMin: -60, displayMax: 12,
1313
+ },
1314
+ 'amp.cab_vu': {
1315
+ block: 'amp', name: 'cab_vu',
1316
+ displayLabel: 'Cab VU',
1317
+ pidLow: 0x003e, pidHigh: 0x0034,
1318
+ // CABINET_VUMETER (catalog id=52). AM4-Edit "Cab VU" — read-only
1319
+ // output-level meter for the cab block. Count-style 0..1 like the
1320
+ // amp.b_plus_monitor / gain_monitor read-only meters.
1321
+ unit: 'count', displayMin: 0, displayMax: 1,
1322
+ },
1323
+ 'amp.cab_1_ir_length': {
1324
+ block: 'amp', name: 'cab_1_ir_length',
1325
+ displayLabel: 'Cab 1 IR Length',
1326
+ pidLow: 0x003e, pidHigh: 0x0039,
1327
+ // CABINET_LENGTH1 (catalog id=57). AM4-Edit "Cab 1 IR Length" —
1328
+ // impulse-response truncation length in samples / ms for Cab 1.
1329
+ unit: 'count', displayMin: 0, displayMax: 8192,
1330
+ },
1331
+ 'amp.cab_2_ir_length': {
1332
+ block: 'amp', name: 'cab_2_ir_length',
1333
+ displayLabel: 'Cab 2 IR Length',
1334
+ pidLow: 0x003e, pidHigh: 0x003a,
1335
+ // CABINET_LENGTH2 (catalog id=58). AM4-Edit "Cab 2 IR Length" —
1336
+ // sibling to cab_1_ir_length for Cab 2.
1337
+ unit: 'count', displayMin: 0, displayMax: 8192,
1338
+ },
1339
+ 'amp.low_slope': {
1340
+ block: 'amp', name: 'low_slope',
1341
+ displayLabel: 'Low Slope',
1342
+ pidLow: 0x003e, pidHigh: 0x003b,
1343
+ // CABINET_LOSLOPE1 (catalog id=59). AM4-Edit "Low Slope" — Cab 1
1344
+ // low-cut filter slope in dB/oct. TODO: capture enum values
1345
+ // (commonly 6/12/18/24/36/48 dB/oct in Fractal cab filters).
1346
+ unit: 'enum', displayMin: 0, displayMax: 7,
1347
+ },
1348
+ 'amp.high_slope': {
1349
+ block: 'amp', name: 'high_slope',
1350
+ displayLabel: 'High Slope',
1351
+ pidLow: 0x003e, pidHigh: 0x003d,
1352
+ // CABINET_HISLOPE1 (catalog id=61). AM4-Edit "High Slope" — Cab 1
1353
+ // high-cut filter slope in dB/oct. TODO: capture enum values.
1354
+ unit: 'enum', displayMin: 0, displayMax: 7,
1355
+ },
1356
+ 'amp.master_low_slope': {
1357
+ block: 'amp', name: 'master_low_slope',
1358
+ displayLabel: 'Master Low Slope',
1359
+ pidLow: 0x003e, pidHigh: 0x003f,
1360
+ // CABINET_PRELOSLOPE (catalog id=63). AM4-Edit "Master Low Slope" —
1361
+ // Cab-Master EQ low-cut slope (sibling to cab_master_low_cut Hz).
1362
+ // TODO: capture enum values.
1363
+ unit: 'enum', displayMin: 0, displayMax: 7,
1364
+ },
1365
+ 'amp.master_high_slope': {
1366
+ block: 'amp', name: 'master_high_slope',
1367
+ displayLabel: 'Master High Slope',
1368
+ pidLow: 0x003e, pidHigh: 0x0040,
1369
+ // CABINET_PREHISLOPE (catalog id=64). AM4-Edit "Master High Slope" —
1370
+ // Cab-Master EQ high-cut slope (sibling to cab_master_high_cut Hz).
1371
+ // TODO: capture enum values.
1372
+ unit: 'enum', displayMin: 0, displayMax: 7,
1373
+ },
1374
+ 'amp.dynacab': {
1375
+ block: 'amp', name: 'dynacab',
1376
+ displayLabel: 'DynaCab',
1377
+ pidLow: 0x003e, pidHigh: 0x0045,
1378
+ // CABINET_DYNACAB_R1 (catalog id=69). AM4-Edit "DynaCab" — Cab 1
1379
+ // DynaCab radius/rotation. Knob-style 0..10.
1380
+ unit: 'knob_0_10', displayMin: 0, displayMax: 10,
1381
+ },
1382
+ // SKIP: CABINET_DYNACAB_Z1 (catalog id=71, XML "Distance") would
1383
+ // collide with the existing CACHE_PARAMS entry `amp.distance` at
1384
+ // pidLow=0x003a / pidHigh=0x47. Same cross-block ghost-resolver
1385
+ // pattern as the skipped amp.proximity above. Defer.
1386
+ 'amp.cab_1_blend': {
1387
+ block: 'amp', name: 'cab_1_blend',
1388
+ displayLabel: 'Cab 1 Blend',
1389
+ pidLow: 0x003e, pidHigh: 0x004b,
1390
+ // CABINET_BLEND1 (catalog id=75). AM4-Edit "Cab 1 Blend" — IR
1391
+ // blend percentage for Cab 1 (when blending two IRs in one cab).
1392
+ unit: 'percent', displayMin: 0, displayMax: 100,
1393
+ },
1394
+ 'amp.cab_2_blend': {
1395
+ block: 'amp', name: 'cab_2_blend',
1396
+ displayLabel: 'Cab 2 Blend',
1397
+ pidLow: 0x003e, pidHigh: 0x004c,
1398
+ // CABINET_BLEND2 (catalog id=76). AM4-Edit "Cab 2 Blend" — sibling
1399
+ // to cab_1_blend for Cab 2.
1400
+ unit: 'percent', displayMin: 0, displayMax: 100,
1401
+ },
1402
+ 'amp.auto_align': {
1403
+ block: 'amp', name: 'auto_align',
1404
+ displayLabel: 'Auto Align',
1405
+ pidLow: 0x003e, pidHigh: 0x004d,
1406
+ // CABINET_AUTO_ALIGN (catalog id=77). AM4-Edit "Auto Align" —
1407
+ // toggle for automatic cab alignment (phase / delay matching
1408
+ // across the two cabs). OFF/ON enum.
1409
+ unit: 'enum', displayMin: 0, displayMax: 1,
1410
+ enumValues: { 0: 'OFF', 1: 'ON' },
1411
+ },
1412
+ // Session 89 (2026-05-16) — DISTORT UI-MISSING closeout. 16 new amp
1413
+ // params mirrored from CACHE_PARAMS so the coverage-audit (which
1414
+ // text-greps params.ts) sees them. Wire bytes + units come from
1415
+ // paramNames.ts overrides; cacheParams.ts emits the canonical entries
1416
+ // and verify-cache-params guards byte-for-byte agreement. displayLabel
1417
+ // = AM4-Edit XML "name=" attribute for the same EditorControl. See
1418
+ // samples/captured/decoded/am4-params-proposed.ts for the Ghidra
1419
+ // catalog source (Sessions 82–83) and cache-section2.json for the
1420
+ // signature data that pinned each unit + range.
1421
+ 'amp.low_reso': {
1422
+ block: 'amp', name: 'low_reso',
1423
+ displayLabel: 'Low Reso',
1424
+ pidLow: 0x003a, pidHigh: 0x0022,
1425
+ unit: 'knob_0_10', displayMin: 0, displayMax: 10,
1426
+ },
1427
+ 'amp.master_vol_cap': {
1428
+ block: 'amp', name: 'master_vol_cap',
1429
+ displayLabel: 'Master Vol Cap',
1430
+ pidLow: 0x003a, pidHigh: 0x0026,
1431
+ // Cache typecode=72 → log10 storage (sibling to amp.bright_cap at
1432
+ // id=20). Capacitance scaling in pF.
1433
+ unit: 'pf', displayMin: 1, displayMax: 1000,
1434
+ scaling: 'log10',
1435
+ },
1436
+ 'amp.hi_reso': {
1437
+ block: 'amp', name: 'hi_reso',
1438
+ displayLabel: 'Hi Reso',
1439
+ pidLow: 0x003a, pidHigh: 0x0033,
1440
+ unit: 'knob_0_10', displayMin: 0, displayMax: 10,
1441
+ },
1442
+ 'amp.spkr_drive': {
1443
+ block: 'amp', name: 'spkr_drive',
1444
+ displayLabel: 'Drive',
1445
+ pidLow: 0x003a, pidHigh: 0x0039,
1446
+ // Speaker-stage drive knob (catalog: DISTORT_SPKRDRIVE). Renamed
1447
+ // from the resolver's bare 'drive' to disambiguate against
1448
+ // drive.drive in the separate Drive block.
1449
+ unit: 'knob_0_10', displayMin: 0, displayMax: 10,
1450
+ },
1451
+ 'amp.input_eq_frequency': {
1452
+ block: 'amp', name: 'input_eq_frequency',
1453
+ displayLabel: 'Frequency',
1454
+ pidLow: 0x003a, pidHigh: 0x004f,
1455
+ // Input-EQ peaking frequency. Renamed from the resolver's bare
1456
+ // 'frequency' to mirror the existing input_eq_q / input_eq_gain /
1457
+ // input_eq_low_cut family on the same UI page.
1458
+ unit: 'hz', displayMin: 100, displayMax: 10000,
1459
+ },
1460
+ 'amp.overdrive': {
1461
+ block: 'amp', name: 'overdrive',
1462
+ displayLabel: 'Normal Gain',
1463
+ pidLow: 0x003a, pidHigh: 0x0051,
1464
+ unit: 'knob_0_10', displayMin: 0, displayMax: 10,
1465
+ },
1466
+ 'amp.definition': {
1467
+ block: 'amp', name: 'definition',
1468
+ displayLabel: 'Definition',
1469
+ pidLow: 0x003a, pidHigh: 0x0056,
1470
+ // Cache c=31.62299 (≈10/√10) → bipolar power-amp definition knob
1471
+ // displayed -10..+10 on the front panel. Uses 'count' rather than
1472
+ // bipolar_percent because front panel reads -10.0..+10.0, not ±100%.
1473
+ unit: 'count', displayMin: -10, displayMax: 10,
1474
+ },
1475
+ 'amp.compression': {
1476
+ block: 'amp', name: 'compression',
1477
+ displayLabel: 'Compression',
1478
+ pidLow: 0x003a, pidHigh: 0x0057,
1479
+ unit: 'percent', displayMin: 0, displayMax: 100,
1480
+ },
1481
+ 'amp.high_cut': {
1482
+ block: 'amp', name: 'high_cut',
1483
+ displayLabel: 'High Cut',
1484
+ pidLow: 0x003a, pidHigh: 0x005a,
1485
+ unit: 'hz', displayMin: 200, displayMax: 20000,
1486
+ },
1487
+ 'amp.cathode_resistance': {
1488
+ block: 'amp', name: 'cathode_resistance',
1489
+ displayLabel: 'Cathode Resistance',
1490
+ pidLow: 0x003a, pidHigh: 0x0064,
1491
+ unit: 'percent', displayMin: 0, displayMax: 100,
1492
+ },
1493
+ 'amp.b_plus_monitor': {
1494
+ block: 'amp', name: 'b_plus_monitor',
1495
+ displayLabel: 'B+',
1496
+ pidLow: 0x003a, pidHigh: 0x007d,
1497
+ // Read-only B+ voltage monitor (front-panel meter, not a knob).
1498
+ // Cache type=0 a=0 b=1 c=1 → raw 0..1 float; display as count.
1499
+ unit: 'count', displayMin: 0, displayMax: 1,
1500
+ },
1501
+ 'amp.gain_monitor': {
1502
+ block: 'amp', name: 'gain_monitor',
1503
+ displayLabel: 'Gain',
1504
+ pidLow: 0x003a, pidHigh: 0x007e,
1505
+ // Read-only gain monitor. Sibling to b_plus_monitor / headroom_monitor.
1506
+ unit: 'count', displayMin: 0, displayMax: 1,
1507
+ },
1508
+ 'amp.headroom_monitor': {
1509
+ block: 'amp', name: 'headroom_monitor',
1510
+ displayLabel: 'HEADROOM',
1511
+ pidLow: 0x003a, pidHigh: 0x0089,
1512
+ // Read-only plate-voltage headroom monitor.
1513
+ unit: 'count', displayMin: 0, displayMax: 1,
1514
+ },
1515
+ 'amp.presence_prepresence': {
1516
+ block: 'amp', name: 'presence_prepresence',
1517
+ displayLabel: 'Treble',
1518
+ pidLow: 0x003a, pidHigh: 0x008a,
1519
+ // Preamp-stage presence shaper. AM4-Edit XML labels this "Treble"
1520
+ // on some amps; catalog name is DISTORT_PREPRESENCE. Name keeps
1521
+ // the resolver's dedupe suffix since amp.presence (id=30) is the
1522
+ // post-amp presence knob.
1523
+ unit: 'knob_0_10', displayMin: 0, displayMax: 10,
1524
+ },
1525
+ 'amp.pa_high_cut': {
1526
+ block: 'amp', name: 'pa_high_cut',
1527
+ displayLabel: 'Tone',
1528
+ pidLow: 0x003a, pidHigh: 0x008c,
1529
+ // Power-amp high-cut shaper. AM4-Edit labels "Tone" but catalog
1530
+ // is DISTORT_PAHICUT; renamed from resolver's 'high_cut_pahicut'
1531
+ // to surface the family (pa_ prefix mirrors the rest of the
1532
+ // power-amp stage knobs).
1533
+ unit: 'knob_0_10', displayMin: 0, displayMax: 10,
1534
+ },
1535
+ 'amp.overdrive_volume': {
1536
+ block: 'amp', name: 'overdrive_volume',
1537
+ displayLabel: 'Overdrive Volume',
1538
+ pidLow: 0x003a, pidHigh: 0x0091,
1539
+ // Global post-amp master that scales after the cab sim.
1540
+ unit: 'knob_0_10', displayMin: 0, displayMax: 10,
1541
+ },
1542
+ 'drive.drive': {
1543
+ block: 'drive', name: 'drive',
1544
+ displayLabel: 'Gain',
1545
+ pidLow: 0x0076, pidHigh: 0x000b,
1546
+ unit: 'knob_0_10', displayMin: 0, displayMax: 10,
1547
+ },
1548
+ // P1-010 Session B (2026-04-20) — AM4 Owner's Manual line 1330:
1549
+ // "Page Right and dial in Drive, Tone, and Level." Cache records
1550
+ // at 0x0C/0x0D/0x0E have canonical pedal-layout signatures.
1551
+ // HW-014 verified: address + value land correctly on Klone Chiron.
1552
+ // Note: AM4 hardware display labels these registers per drive
1553
+ // model (Klone Chiron shows `tone`→"Treble" and `level`→"Output",
1554
+ // matching the real Klon Centaur). The underlying register is
1555
+ // unchanged across drive types.
1556
+ 'drive.tone': {
1557
+ block: 'drive', name: 'tone',
1558
+ displayLabel: 'Bass',
1559
+ pidLow: 0x0076, pidHigh: 0x000c,
1560
+ unit: 'knob_0_10', displayMin: 0, displayMax: 10,
1561
+ },
1562
+ 'drive.level': {
1563
+ block: 'drive', name: 'level',
1564
+ displayLabel: 'Mid',
1565
+ pidLow: 0x0076, pidHigh: 0x000d,
1566
+ unit: 'knob_0_10', displayMin: 0, displayMax: 10,
1567
+ },
1568
+ 'drive.mix': {
1569
+ block: 'drive', name: 'mix',
1570
+ displayLabel: 'Tone',
1571
+ pidLow: 0x0076, pidHigh: 0x000e,
1572
+ unit: 'percent', displayMin: 0, displayMax: 100,
1573
+ },
1574
+ // HW-019 (Session 30, 2026-04-25): Drive EQ-page knobs from
1575
+ // session-30-drive-basic-blackglass-7k. T808 OD only exposed
1576
+ // Drive/Tone/Level on its first page (session-30-drive-basic-t808-od
1577
+ // capture confirmed) — the EQ controls below are absent on simpler
1578
+ // pedal types and only surface on amp-emu drive types like
1579
+ // Blackglass 7K. Cache signatures pin the unit + range; sequence in
1580
+ // the cache (id 16/17 = Low/High Cut Hz, id 20/21/23 = Bass/Mid/
1581
+ // Treble knobs flanking id 22 = Mid Frequency) matches the AM4-Edit
1582
+ // EQ-1-page layout. Captured wiggle order on Blackglass differed
1583
+ // from the spec order; mapping is by cache-id sequence + signature
1584
+ // not capture order.
1585
+ 'drive.low_cut': {
1586
+ block: 'drive', name: 'low_cut',
1587
+ displayLabel: 'Low Cut',
1588
+ pidLow: 0x0076, pidHigh: 0x0010,
1589
+ unit: 'hz', displayMin: 20, displayMax: 2000,
1590
+ },
1591
+ 'drive.bass': {
1592
+ block: 'drive', name: 'bass',
1593
+ displayLabel: 'Bright Cap',
1594
+ pidLow: 0x0076, pidHigh: 0x0014,
1595
+ unit: 'knob_0_10', displayMin: 0, displayMax: 10,
1596
+ },
1597
+ 'drive.mid': {
1598
+ block: 'drive', name: 'mid',
1599
+ pidLow: 0x0076, pidHigh: 0x0015,
1600
+ unit: 'knob_0_10', displayMin: 0, displayMax: 10,
1601
+ },
1602
+ 'drive.mid_freq': {
1603
+ block: 'drive', name: 'mid_freq',
1604
+ displayLabel: 'XFormer Low Freq',
1605
+ pidLow: 0x0076, pidHigh: 0x0016,
1606
+ unit: 'hz', displayMin: 200, displayMax: 2000,
1607
+ },
1608
+ 'drive.treble': {
1609
+ block: 'drive', name: 'treble',
1610
+ displayLabel: 'XFormer Hi Freq',
1611
+ pidLow: 0x0076, pidHigh: 0x0017,
1612
+ unit: 'knob_0_10', displayMin: 0, displayMax: 10,
1613
+ },
1614
+ 'drive.channel': {
1615
+ block: 'drive', name: 'channel',
1616
+ pidLow: 0x0076, pidHigh: 0x07d2,
1617
+ unit: 'enum', displayMin: 0, displayMax: 3,
1618
+ enumValues: { 0: 'A', 1: 'B', 2: 'C', 3: 'D' },
1619
+ },
1620
+ // HW-029 + HW-039 (Session 35, 2026-04-29): Blackglass 7K Drive
1621
+ // Expert-Edit page from session-31-drive-expert.pcapng + paired
1622
+ // AM4-Edit screenshot. 6 single-knob params + 10 GEQ bands + 1
1623
+ // type-specific knob (high_mid for Blackglass, may surface under
1624
+ // a different label on other types).
1625
+ //
1626
+ // Mirrored from CACHE_PARAMS so the type-check picks them up.
1627
+ 'drive.high_cut': {
1628
+ block: 'drive', name: 'high_cut',
1629
+ displayLabel: 'High Cut Frequency',
1630
+ pidLow: 0x0076, pidHigh: 0x0011,
1631
+ unit: 'hz', displayMin: 200, displayMax: 20000,
1632
+ },
1633
+ 'drive.bypass_mode': {
1634
+ block: 'drive', name: 'bypass_mode',
1635
+ pidLow: 0x0076, pidHigh: 0x0004,
1636
+ // Cache id=4: enum [Thru / Mute]. Hand-authored per-block non-Type
1637
+ // enum (gen-params skips these).
1638
+ unit: 'enum', displayMin: 0, displayMax: 1,
1639
+ enumValues: { 0: 'Thru', 1: 'Mute' },
1640
+ },
1641
+ 'drive.clip_type': {
1642
+ block: 'drive', name: 'clip_type',
1643
+ displayLabel: 'Frequency',
1644
+ pidLow: 0x0076, pidHigh: 0x0012,
1645
+ // Cache id=18: 14-entry enum. Hand-authored — generator only
1646
+ // attaches one enum import per block (used for `type` at id=10).
1647
+ unit: 'enum', displayMin: 0, displayMax: 13,
1648
+ enumValues: {
1649
+ 0: 'LV TUBE', 1: 'HARD', 2: 'SOFT', 3: 'GERMANIUM', 4: 'FW RECT',
1650
+ 5: 'HV TUBE', 6: 'SILICON', 7: '4558/DIODE', 8: 'LED', 9: 'FET',
1651
+ 10: 'OP-AMP', 11: 'VARIABLE', 12: 'CMOS', 13: 'NULL',
1652
+ },
1653
+ },
1654
+ 'drive.bit_reduce': {
1655
+ block: 'drive', name: 'bit_reduce',
1656
+ displayLabel: 'Location',
1657
+ pidLow: 0x0076, pidHigh: 0x0018,
1658
+ unit: 'count', displayMin: 0, displayMax: 24,
1659
+ },
1660
+ 'drive.input_select': {
1661
+ block: 'drive', name: 'input_select',
1662
+ displayLabel: 'Amp Input Select',
1663
+ pidLow: 0x0076, pidHigh: 0x0019,
1664
+ // Cache id=25: enum [L+R / LEFT / RIGHT].
1665
+ unit: 'enum', displayMin: 0, displayMax: 2,
1666
+ enumValues: { 0: 'L+R', 1: 'LEFT', 2: 'RIGHT' },
1667
+ },
1668
+ 'drive.eq_position': {
1669
+ block: 'drive', name: 'eq_position',
1670
+ pidLow: 0x0076, pidHigh: 0x001c,
1671
+ // Cache id=28: enum [OFF / POST / PRE]. Selects whether the post-
1672
+ // Drive Graphic EQ is bypassed, post-drive, or pre-drive.
1673
+ unit: 'enum', displayMin: 0, displayMax: 2,
1674
+ enumValues: { 0: 'OFF', 1: 'POST', 2: 'PRE' },
1675
+ },
1676
+ // HW-043 partial (Session 45, 2026-05-02) — Drive Expert-Edit ADVANCED
1677
+ // panel knobs from `session-45-drive-expert-blackglass.{pcapng,png}`.
1678
+ // Slew Rate is universal (also active on Pi Fuzz at wire 0.21044).
1679
+ // Bias is Blackglass-only (silent in Pi Fuzz capture) — type-conditional
1680
+ // UI but firmware-stable address. See docs/audit-output/drive-blackglass.md.
1681
+ // Note: drive.balance is currently registered at 0x0002 but Session 45
1682
+ // captures show actual Balance is at 0x000f (hdr2=0x0002 bipolar
1683
+ // signature). Re-point deferred until 0x0002's actual identity is
1684
+ // confirmed — see open follow-ups in session-44-findings.md.
1685
+ 'drive.slew_rate': {
1686
+ block: 'drive', name: 'slew_rate',
1687
+ displayLabel: 'Depth',
1688
+ pidLow: 0x0076, pidHigh: 0x001a,
1689
+ unit: 'percent', displayMin: 0, displayMax: 100,
1690
+ },
1691
+ 'drive.bias': {
1692
+ block: 'drive', name: 'bias',
1693
+ pidLow: 0x0076, pidHigh: 0x001b,
1694
+ unit: 'knob_0_10', displayMin: 0, displayMax: 10,
1695
+ },
1696
+ // 10-band Graphic EQ — frequencies 100/160/250/400/640/1000/1600/
1697
+ // 2500/4000/6400 Hz, each ±12 dB. Wire-display match exact across
1698
+ // all 10 bands.
1699
+ 'drive.geq_band_1': { block: 'drive', name: 'geq_band_1', displayLabel: 'Supply Sag', pidLow: 0x0076, pidHigh: 0x001d, unit: 'db', displayMin: -12, displayMax: 12 },
1700
+ 'drive.geq_band_2': { block: 'drive', name: 'geq_band_2', displayLabel: 'Presence', pidLow: 0x0076, pidHigh: 0x001e, unit: 'db', displayMin: -12, displayMax: 12 },
1701
+ 'drive.geq_band_3': { block: 'drive', name: 'geq_band_3', displayLabel: 'Negative FB', pidLow: 0x0076, pidHigh: 0x001f, unit: 'db', displayMin: -12, displayMax: 12 },
1702
+ 'drive.geq_band_4': { block: 'drive', name: 'geq_band_4', displayLabel: 'Presence Freq', pidLow: 0x0076, pidHigh: 0x0020, unit: 'db', displayMin: -12, displayMax: 12 },
1703
+ 'drive.geq_band_5': { block: 'drive', name: 'geq_band_5', displayLabel: 'Low Freq', pidLow: 0x0076, pidHigh: 0x0021, unit: 'db', displayMin: -12, displayMax: 12 },
1704
+ 'drive.geq_band_6': { block: 'drive', name: 'geq_band_6', displayLabel: 'Low Reso', pidLow: 0x0076, pidHigh: 0x0022, unit: 'db', displayMin: -12, displayMax: 12 },
1705
+ 'drive.geq_band_7': { block: 'drive', name: 'geq_band_7', displayLabel: 'Amp Section', pidLow: 0x0076, pidHigh: 0x0023, unit: 'db', displayMin: -12, displayMax: 12 },
1706
+ 'drive.geq_band_8': { block: 'drive', name: 'geq_band_8', displayLabel: 'Depth Freq', pidLow: 0x0076, pidHigh: 0x0024, unit: 'db', displayMin: -12, displayMax: 12 },
1707
+ 'drive.geq_band_9': { block: 'drive', name: 'geq_band_9', pidLow: 0x0076, pidHigh: 0x0025, unit: 'db', displayMin: -12, displayMax: 12 },
1708
+ 'drive.geq_band_10': { block: 'drive', name: 'geq_band_10', displayLabel: 'Master Vol Cap', pidLow: 0x0076, pidHigh: 0x0026, unit: 'db', displayMin: -12, displayMax: 12 },
1709
+ 'drive.high_mid': {
1710
+ block: 'drive', name: 'high_mid',
1711
+ pidLow: 0x0076, pidHigh: 0x002d,
1712
+ // Cache id=45: knob_0_10. Closes HW-029 — wiggle-order adjacency
1713
+ // pins this between drive.mid_freq (id=22) and drive.treble (id=23)
1714
+ // in the BASIC column on Blackglass 7K. Type-specific UI label
1715
+ // varies; the register name reflects the Blackglass usage.
1716
+ unit: 'knob_0_10', displayMin: 0, displayMax: 10,
1717
+ },
1718
+ 'drive.type': {
1719
+ block: 'drive', name: 'type',
1720
+ pidLow: 0x0076, pidHigh: 0x000a,
1721
+ // Session 06 capture set drive type with wire-value 8; cache lists
1722
+ // index 8 as "T808 Mod" (Fractal's internal label for the TS808
1723
+ // variant AM4-Edit surfaces as "TS808"). Full 78-entry table from
1724
+ // cache lines up 1:1 with AM4-Edit's Drive Type dropdown order.
1725
+ unit: 'enum', displayMin: 0, displayMax: 77,
1726
+ enumValues: DRIVE_TYPES_VALUES,
1727
+ },
1728
+ 'reverb.mix': {
1729
+ block: 'reverb', name: 'mix',
1730
+ pidLow: 0x0042, pidHigh: 0x0001,
1731
+ unit: 'percent', displayMin: 0, displayMax: 100,
1732
+ },
1733
+ 'reverb.time': {
1734
+ // Blocks Guide §Reverb Basic Page: decay time, 0.1..100 seconds.
1735
+ // Uses 'seconds' unit (display = internal, scale 1).
1736
+ block: 'reverb', name: 'time',
1737
+ displayLabel: 'Time',
1738
+ pidLow: 0x0042, pidHigh: 0x000b,
1739
+ unit: 'seconds', displayMin: 0.1, displayMax: 100,
1740
+ },
1741
+ // renamed for UI-label match (audit row: REVERB 19)
1742
+ 'reverb.pre_delay': {
1743
+ // BK-033 fix (HW-025 #1, Session 30): true address is pidHigh=0x0013,
1744
+ // not 0x0010. AM4-Edit capture for Pre-Delay→85 ms wrote 0x0042/0x0013
1745
+ // with float32(0.085) — confirms the `ms` unit's ÷1000 scale is right.
1746
+ // The 0x0010 register was a cache-derived guess that was structurally
1747
+ // plausible (range matched) but wrote to nothing. See SYSEX-MAP §6j.
1748
+ block: 'reverb', name: 'pre_delay',
1749
+ displayLabel: 'Pre-Delay',
1750
+ pidLow: 0x0042, pidHigh: 0x0013,
1751
+ unit: 'ms', displayMin: 0, displayMax: 250,
1752
+ },
1753
+ // Session 29 (HW-015): reverb Size at pidHigh=0x000f. Wire-verified
1754
+ // on two captures ("Plate Size" on Plate reverb + "Size" on Room
1755
+ // reverb) — same register, type-dependent UI label. Percent scale.
1756
+ 'reverb.size': {
1757
+ block: 'reverb', name: 'size',
1758
+ displayLabel: 'Size',
1759
+ pidLow: 0x0042, pidHigh: 0x000f,
1760
+ unit: 'percent', displayMin: 0, displayMax: 100,
1761
+ },
1762
+ // Session 29 (HW-015): spring-reverb-specific params. Registers are
1763
+ // writable on any reverb type; AM4-Edit exposes the UI only when
1764
+ // a Spring reverb is active. HW-024 (Session 30 cont 3) wire-verified
1765
+ // both on Spring, Large reverb (springs=5 displayed exactly; spring_tone
1766
+ // 7.30 displayed exactly) — first-ever hardware test of these params.
1767
+ 'reverb.springs': {
1768
+ block: 'reverb', name: 'springs',
1769
+ displayLabel: '# of Springs',
1770
+ pidLow: 0x0042, pidHigh: 0x001b,
1771
+ unit: 'count', displayMin: 2, displayMax: 6,
1772
+ },
1773
+ 'reverb.spring_tone': {
1774
+ block: 'reverb', name: 'spring_tone',
1775
+ displayLabel: 'Tone',
1776
+ pidLow: 0x0042, pidHigh: 0x001c,
1777
+ unit: 'knob_0_10', displayMin: 0, displayMax: 10,
1778
+ },
1779
+ // Session 29 follow-up: Shimmer Verb / Plex Verb pitch-shifter
1780
+ // voices. Blocks Guide §Shimmer Verb Parameters describes "Shift
1781
+ // 1–8" as detune amounts within ±24 semitones ("this is where
1782
+ // 'Shimmer' is born"). AM4's reverb exposes two such voices at
1783
+ // cache ids 56/57 — structural registration (cache signature
1784
+ // matches BG exactly: a=-24, b=24, c=1, step=1). HW-014 couldn't
1785
+ // verify on hardware display (both shifts hidden on the Plate
1786
+ // reverb type tested); awaits a Shimmer-type hardware spot-check
1787
+ // or AM4-Edit-side verification.
1788
+ // HW-018 (Session 30, 2026-04-25): 10 new universal/algorithmic-reverb
1789
+ // and Spring-specific knobs decoded from session-30-reverb-basic-hall
1790
+ // and session-30-reverb-spring captures. Cache metadata confirmed
1791
+ // pidLow/pidHigh/range for each; capture final values cross-checked
1792
+ // against the founder's AM4-Edit screenshot inventory. Hall + Spring
1793
+ // share the universal registers (high_cut / low_cut / input_gain /
1794
+ // ducking) while Hall-only adds algorithmic controls (density / quality
1795
+ // / stack_hold / stereo_spread) and Spring-only adds Spring-engine
1796
+ // controls (dwell / drip).
1797
+ 'reverb.high_cut': {
1798
+ block: 'reverb', name: 'high_cut',
1799
+ displayLabel: 'High Cut',
1800
+ pidLow: 0x0042, pidHigh: 0x000c,
1801
+ // Cache: a=200, b=20000, c=1 → raw Hz, 200..20000 Hz. Hall capture
1802
+ // wrote 7000 Hz directly (numeric input field, action=0x0001).
1803
+ unit: 'hz', displayMin: 200, displayMax: 20000,
1804
+ },
1805
+ 'reverb.low_cut': {
1806
+ block: 'reverb', name: 'low_cut',
1807
+ displayLabel: 'Low Cut',
1808
+ pidLow: 0x0042, pidHigh: 0x0014,
1809
+ // Cache: a=20, b=2000, c=1 → raw Hz, 20..2000 Hz.
1810
+ unit: 'hz', displayMin: 20, displayMax: 2000,
1811
+ },
1812
+ 'reverb.input_gain': {
1813
+ block: 'reverb', name: 'input_gain',
1814
+ displayLabel: 'Input Gain',
1815
+ pidLow: 0x0042, pidHigh: 0x0017,
1816
+ // Cache: a=0, b=1, c=100 → percent 0..100. Spring final 0.8217 →
1817
+ // 82.17% matches the AM4-Edit screenshot's "Input Gain 82.2 %".
1818
+ unit: 'percent', displayMin: 0, displayMax: 100,
1819
+ },
1820
+ 'reverb.density': {
1821
+ block: 'reverb', name: 'density',
1822
+ displayLabel: 'Density',
1823
+ pidLow: 0x0042, pidHigh: 0x0018,
1824
+ // Cache: a=4, b=8, c=1, kind=float typecode=16 → integer count
1825
+ // 4..8. Hall-only (algorithmic Hall/Plate/Room knob).
1826
+ unit: 'count', displayMin: 4, displayMax: 8,
1827
+ },
1828
+ 'reverb.dwell': {
1829
+ block: 'reverb', name: 'dwell',
1830
+ displayLabel: 'Dwell',
1831
+ pidLow: 0x0042, pidHigh: 0x0024,
1832
+ // Cache: a=0.01, b=1, c=10 → knob_0_10 (display = wire × 10).
1833
+ // Spring final 0.4741 → 4.741 matches screenshot "Dwell 4.74".
1834
+ // Spring-engine specific (alongside spring_tone, drip).
1835
+ unit: 'knob_0_10', displayMin: 0.1, displayMax: 10,
1836
+ // typecode 80 = log10 (HW-053b cont audit)
1837
+ scaling: 'log10',
1838
+ },
1839
+ 'reverb.stereo_spread': {
1840
+ block: 'reverb', name: 'stereo_spread',
1841
+ displayLabel: 'Stereo Spread',
1842
+ pidLow: 0x0042, pidHigh: 0x0027,
1843
+ // Cache: a=-2, b=2, c=100 → bipolar_percent allowing -200..+200%.
1844
+ // AM4-Edit screenshot shows Hall Stereo Spread as a positive 0..100%
1845
+ // knob (display value 90.0 %). Cache exposes the wider firmware
1846
+ // range — leave displayMin/displayMax at the cache values; Claude
1847
+ // can clamp to the typical 0..100 range when describing the knob.
1848
+ unit: 'bipolar_percent', displayMin: -200, displayMax: 200,
1849
+ },
1850
+ 'reverb.ducking': {
1851
+ block: 'reverb', name: 'ducking',
1852
+ displayLabel: 'Ducking',
1853
+ pidLow: 0x0042, pidHigh: 0x0028,
1854
+ // Cache: a=0, b=80, c=1 → raw dB, 0..80 dB attenuation. Universal
1855
+ // (Hall + Spring both wrote here). Screenshot shows "Ducking 46.9 dB"
1856
+ // on both reverb types — typical mid-range attenuation.
1857
+ unit: 'db', displayMin: 0, displayMax: 80,
1858
+ },
1859
+ 'reverb.quality': {
1860
+ block: 'reverb', name: 'quality',
1861
+ displayLabel: 'Quality',
1862
+ pidLow: 0x0042, pidHigh: 0x002f,
1863
+ // Cache: enum, values=["ECONOMY","NORMAL","HIGH","ULTRA-HIGH"].
1864
+ // Hall-only (algorithmic CPU-quality selector). Hand-authored enum
1865
+ // map; not yet exported via cacheEnums.ts since cacheEnums is
1866
+ // auto-generated from a different cache section. If a regen pass
1867
+ // adds REVERB_QUALITY_VALUES later, swap this inline map for the
1868
+ // import.
1869
+ unit: 'enum', displayMin: 0, displayMax: 3,
1870
+ enumValues: { 0: 'ECONOMY', 1: 'NORMAL', 2: 'HIGH', 3: 'ULTRA-HIGH' },
1871
+ },
1872
+ 'reverb.stack_hold': {
1873
+ block: 'reverb', name: 'stack_hold',
1874
+ displayLabel: 'Stack/Hold',
1875
+ pidLow: 0x0042, pidHigh: 0x0030,
1876
+ // Cache: enum, values=["OFF","STACK","HOLD"]. Hall-only. Same
1877
+ // hand-authored caveat as reverb.quality.
1878
+ unit: 'enum', displayMin: 0, displayMax: 2,
1879
+ enumValues: { 0: 'OFF', 1: 'STACK', 2: 'HOLD' },
1880
+ },
1881
+ 'reverb.drip': {
1882
+ block: 'reverb', name: 'drip',
1883
+ displayLabel: 'Drip',
1884
+ pidLow: 0x0042, pidHigh: 0x0034,
1885
+ // Cache: a=0, b=1, c=100 → percent 0..100. Spring final 0.9183 →
1886
+ // 91.83% matches screenshot "Drip 91.8 %". Spring-engine specific.
1887
+ unit: 'percent', displayMin: 0, displayMax: 100,
1888
+ },
1889
+ // renamed for UI-label match (audit row: REVERB 56)
1890
+ 'reverb.voice_1_shift': {
1891
+ block: 'reverb', name: 'voice_1_shift',
1892
+ displayLabel: 'Voice 1 Shift',
1893
+ pidLow: 0x0042, pidHigh: 0x0038,
1894
+ unit: 'semitones', displayMin: -24, displayMax: 24,
1895
+ },
1896
+ // renamed for UI-label match (audit row: REVERB 57)
1897
+ 'reverb.voice_2_shift': {
1898
+ block: 'reverb', name: 'voice_2_shift',
1899
+ displayLabel: 'Voice 2 Shift',
1900
+ pidLow: 0x0042, pidHigh: 0x0039,
1901
+ unit: 'semitones', displayMin: -24, displayMax: 24,
1902
+ },
1903
+ 'reverb.channel': {
1904
+ block: 'reverb', name: 'channel',
1905
+ pidLow: 0x0042, pidHigh: 0x07d2,
1906
+ unit: 'enum', displayMin: 0, displayMax: 3,
1907
+ enumValues: { 0: 'A', 1: 'B', 2: 'C', 3: 'D' },
1908
+ },
1909
+ 'reverb.type': {
1910
+ block: 'reverb', name: 'type',
1911
+ pidLow: 0x0042, pidHigh: 0x000a,
1912
+ // Session 16: enum dictionary imported from cacheEnums.ts (79 models).
1913
+ // Untested against capture.
1914
+ unit: 'enum', displayMin: 0, displayMax: 78,
1915
+ enumValues: REVERB_TYPES_VALUES,
1916
+ },
1917
+ 'delay.time': {
1918
+ block: 'delay', name: 'time',
1919
+ displayLabel: 'Time',
1920
+ pidLow: 0x0046, pidHigh: 0x000c,
1921
+ // Session 16: cache says `b=8` seconds → UI max 8000 ms (was 5000).
1922
+ unit: 'ms', displayMin: 0, displayMax: 8000,
1923
+ },
1924
+ 'delay.mix': {
1925
+ // Blocks Guide: delay has Mix at pidHigh 0x01. "Note that the
1926
+ // delay block uses a different Mix Law compared to other blocks" —
1927
+ // semantics differ but the param is at the standard location.
1928
+ block: 'delay', name: 'mix',
1929
+ pidLow: 0x0046, pidHigh: 0x0001,
1930
+ unit: 'percent', displayMin: 0, displayMax: 100,
1931
+ },
1932
+ // Session 29 (HW-015): Feedback knobs on per-block delay/flanger/phaser.
1933
+ // All bipolar — negative feedback inverts the phase of the repeats/
1934
+ // sweep, a standard Fractal implementation detail.
1935
+ 'delay.feedback': {
1936
+ block: 'delay', name: 'feedback',
1937
+ displayLabel: 'Feedback',
1938
+ pidLow: 0x0046, pidHigh: 0x000e,
1939
+ unit: 'bipolar_percent', displayMin: -100, displayMax: 100,
1940
+ },
1941
+ // HW-020 (Session 30, 2026-04-25): Delay first-page registers from
1942
+ // session-30-delay-basic-digital-mono. `level` follows the universal
1943
+ // pidHigh=0x0000 "Level" pattern shared with amp.level (no cache
1944
+ // record at id=0; out-of-band hand-author). `stack_hold` and
1945
+ // `ducking` mirror the same registers found on Reverb (HW-018).
1946
+ // `tempo` (pidHigh=0x0013) is captured but deferred — registering
1947
+ // it requires extracting the 79-entry tempo-division enum from cache
1948
+ // (queued as HW-027 follow-up).
1949
+ 'delay.level': {
1950
+ block: 'delay', name: 'level',
1951
+ pidLow: 0x0046, pidHigh: 0x0000,
1952
+ unit: 'db', displayMin: -80, displayMax: 20,
1953
+ },
1954
+ // HW-040 (Session 36, 2026-04-29): Delay Expert-Edit page from
1955
+ // session-40-delay-expert.pcapng (Ambient Stereo). 32 new params
1956
+ // mirrored from CACHE_PARAMS + 7 hand-authored enums (bypass_mode,
1957
+ // kill_dry, lo_fi_drive, phase_reverse, low_cut_slope, high_cut_slope,
1958
+ // compander).
1959
+ 'delay.bypass_mode': {
1960
+ block: 'delay', name: 'bypass_mode',
1961
+ pidLow: 0x0046, pidHigh: 0x0004,
1962
+ // Cache id=4 enum has 5 entries: [Thru, Mute FX Out, Mute Out,
1963
+ // Mute FX In, Mute In] — the cache extraction shows 4 visible
1964
+ // entries but the enum max is 4 (so 5 indices, 0..4).
1965
+ unit: 'enum', displayMin: 0, displayMax: 4,
1966
+ enumValues: { 0: 'Thru', 1: 'Mute FX Out', 2: 'Mute Out', 3: 'Mute FX In', 4: 'Mute In' },
1967
+ },
1968
+ 'delay.kill_dry': {
1969
+ block: 'delay', name: 'kill_dry',
1970
+ pidLow: 0x0046, pidHigh: 0x0007,
1971
+ unit: 'enum', displayMin: 0, displayMax: 1,
1972
+ enumValues: { 0: 'OFF', 1: 'ON' },
1973
+ },
1974
+ 'delay.lr_time_ratio': {
1975
+ block: 'delay', name: 'lr_time_ratio',
1976
+ displayLabel: 'L/R Time Ratio',
1977
+ pidLow: 0x0046, pidHigh: 0x000d,
1978
+ unit: 'percent', displayMin: 1, displayMax: 100,
1979
+ },
1980
+ 'delay.feedback_r': {
1981
+ block: 'delay', name: 'feedback_r',
1982
+ displayLabel: 'Feedback R',
1983
+ pidLow: 0x0046, pidHigh: 0x0010,
1984
+ unit: 'bipolar_percent', displayMin: -100, displayMax: 100,
1985
+ },
1986
+ 'delay.stereo_spread': {
1987
+ block: 'delay', name: 'stereo_spread',
1988
+ displayLabel: 'Stereo Spread',
1989
+ pidLow: 0x0046, pidHigh: 0x0012,
1990
+ unit: 'bipolar_percent', displayMin: -100, displayMax: 100,
1991
+ },
1992
+ 'delay.low_cut': {
1993
+ block: 'delay', name: 'low_cut',
1994
+ displayLabel: 'Low Cut',
1995
+ pidLow: 0x0046, pidHigh: 0x0014,
1996
+ unit: 'hz', displayMin: 20, displayMax: 2000,
1997
+ },
1998
+ 'delay.high_cut': {
1999
+ block: 'delay', name: 'high_cut',
2000
+ displayLabel: 'High Cut',
2001
+ pidLow: 0x0046, pidHigh: 0x0015,
2002
+ unit: 'hz', displayMin: 200, displayMax: 20000,
2003
+ },
2004
+ 'delay.lo_fi_drive': {
2005
+ block: 'delay', name: 'lo_fi_drive',
2006
+ displayLabel: 'Drive',
2007
+ pidLow: 0x0046, pidHigh: 0x001a,
2008
+ // Cache id=26: float a=0.05 b=50 c=10 → display = wire × 10,
2009
+ // range 0.5..500. Same encoding shape as knob_0_10 (scale 10);
2010
+ // the unit name is structural, the bounds carry the actual range.
2011
+ unit: 'knob_0_10', displayMin: 0.5, displayMax: 500,
2012
+ // typecode 80 = log10 (HW-053b cont audit)
2013
+ scaling: 'log10',
2014
+ },
2015
+ 'delay.input_gain': {
2016
+ block: 'delay', name: 'input_gain',
2017
+ displayLabel: 'Input Gain',
2018
+ pidLow: 0x0046, pidHigh: 0x001b,
2019
+ unit: 'percent', displayMin: 0, displayMax: 100,
2020
+ },
2021
+ 'delay.master_feedback': {
2022
+ block: 'delay', name: 'master_feedback',
2023
+ displayLabel: 'Master Feedback',
2024
+ pidLow: 0x0046, pidHigh: 0x0020,
2025
+ // Cache id=32: a=0 b=2 c=100 → display 0..200%.
2026
+ unit: 'percent', displayMin: 0, displayMax: 200,
2027
+ },
2028
+ 'delay.high_cut_slope': {
2029
+ block: 'delay', name: 'high_cut_slope',
2030
+ displayLabel: 'High Cut Slope',
2031
+ pidLow: 0x0046, pidHigh: 0x002d,
2032
+ unit: 'enum', displayMin: 0, displayMax: 3,
2033
+ enumValues: { 0: '6 dB/OCT', 1: '12 dB/OCT', 2: '24 dB/OCT', 3: '36 dB/OCT' },
2034
+ },
2035
+ 'delay.ducker_threshold': {
2036
+ block: 'delay', name: 'ducker_threshold',
2037
+ displayLabel: 'Threshold',
2038
+ pidLow: 0x0046, pidHigh: 0x002f,
2039
+ unit: 'db', displayMin: -80, displayMax: 20,
2040
+ },
2041
+ 'delay.ducker_release': {
2042
+ block: 'delay', name: 'ducker_release',
2043
+ displayLabel: 'Release',
2044
+ pidLow: 0x0046, pidHigh: 0x0030,
2045
+ unit: 'ms', displayMin: 1, displayMax: 1000, scaling: 'log10',
2046
+ },
2047
+ // renamed for UI-label match (audit row: DELAY 49)
2048
+ 'delay.diffuser': {
2049
+ block: 'delay', name: 'diffuser',
2050
+ displayLabel: 'Diffuser',
2051
+ pidLow: 0x0046, pidHigh: 0x0031,
2052
+ unit: 'percent', displayMin: 0, displayMax: 100,
2053
+ },
2054
+ 'delay.diffusion_time': {
2055
+ block: 'delay', name: 'diffusion_time',
2056
+ displayLabel: 'Diffusion Time',
2057
+ pidLow: 0x0046, pidHigh: 0x0032,
2058
+ unit: 'percent', displayMin: 1, displayMax: 100,
2059
+ },
2060
+ 'delay.phase_reverse': {
2061
+ block: 'delay', name: 'phase_reverse',
2062
+ displayLabel: 'Phase Reverse',
2063
+ pidLow: 0x0046, pidHigh: 0x0033,
2064
+ unit: 'enum', displayMin: 0, displayMax: 3,
2065
+ enumValues: { 0: 'NONE', 1: 'RIGHT', 2: 'LEFT', 3: 'BOTH' },
2066
+ },
2067
+ 'delay.eq_q_high_low': {
2068
+ block: 'delay', name: 'eq_q_high_low',
2069
+ displayLabel: 'Q (High + Low)',
2070
+ pidLow: 0x0046, pidHigh: 0x003f,
2071
+ unit: 'count', displayMin: 0.1, displayMax: 10, scaling: 'log10',
2072
+ },
2073
+ 'delay.bit_reduction': {
2074
+ block: 'delay', name: 'bit_reduction',
2075
+ displayLabel: 'Bit Reduction',
2076
+ pidLow: 0x0046, pidHigh: 0x0040,
2077
+ unit: 'count', displayMin: 0, displayMax: 24,
2078
+ },
2079
+ 'delay.eq_freq_1': {
2080
+ block: 'delay', name: 'eq_freq_1',
2081
+ displayLabel: 'Frequency 1',
2082
+ pidLow: 0x0046, pidHigh: 0x0041,
2083
+ unit: 'hz', displayMin: 20, displayMax: 2000,
2084
+ },
2085
+ 'delay.eq_freq_2': {
2086
+ block: 'delay', name: 'eq_freq_2',
2087
+ displayLabel: 'Frequency 2',
2088
+ pidLow: 0x0046, pidHigh: 0x0042,
2089
+ unit: 'hz', displayMin: 100, displayMax: 10000,
2090
+ },
2091
+ 'delay.eq_q_1': {
2092
+ block: 'delay', name: 'eq_q_1',
2093
+ displayLabel: 'Q 1',
2094
+ pidLow: 0x0046, pidHigh: 0x0043,
2095
+ unit: 'count', displayMin: 0.1, displayMax: 10, scaling: 'log10',
2096
+ },
2097
+ 'delay.eq_q_2': {
2098
+ block: 'delay', name: 'eq_q_2',
2099
+ displayLabel: 'Q 2',
2100
+ pidLow: 0x0046, pidHigh: 0x0044,
2101
+ unit: 'count', displayMin: 0.1, displayMax: 10, scaling: 'log10',
2102
+ },
2103
+ 'delay.eq_gain_1': {
2104
+ block: 'delay', name: 'eq_gain_1',
2105
+ displayLabel: 'Gain 1',
2106
+ pidLow: 0x0046, pidHigh: 0x0045,
2107
+ unit: 'db', displayMin: -12, displayMax: 12,
2108
+ },
2109
+ 'delay.eq_gain_2': {
2110
+ block: 'delay', name: 'eq_gain_2',
2111
+ displayLabel: 'Gain 2',
2112
+ pidLow: 0x0046, pidHigh: 0x0046,
2113
+ unit: 'db', displayMin: -12, displayMax: 12,
2114
+ },
2115
+ 'delay.low_cut_slope': {
2116
+ block: 'delay', name: 'low_cut_slope',
2117
+ displayLabel: 'Low Cut Slope',
2118
+ pidLow: 0x0046, pidHigh: 0x004a,
2119
+ unit: 'enum', displayMin: 0, displayMax: 3,
2120
+ enumValues: { 0: '6 dB/OCT', 1: '12 dB/OCT', 2: '24 dB/OCT', 3: '36 dB/OCT' },
2121
+ },
2122
+ 'delay.compander': {
2123
+ block: 'delay', name: 'compander',
2124
+ displayLabel: 'Compander',
2125
+ pidLow: 0x0046, pidHigh: 0x004b,
2126
+ unit: 'enum', displayMin: 0, displayMax: 1,
2127
+ enumValues: { 0: 'OFF', 1: 'ON' },
2128
+ },
2129
+ 'delay.compander_time': {
2130
+ block: 'delay', name: 'compander_time',
2131
+ displayLabel: 'Time',
2132
+ pidLow: 0x0046, pidHigh: 0x004c,
2133
+ unit: 'ms', displayMin: 1, displayMax: 100, scaling: 'log10',
2134
+ },
2135
+ 'delay.compander_threshold': {
2136
+ block: 'delay', name: 'compander_threshold',
2137
+ displayLabel: 'Threshold',
2138
+ pidLow: 0x0046, pidHigh: 0x004d,
2139
+ unit: 'db', displayMin: -100, displayMax: -20,
2140
+ },
2141
+ 'delay.master_time': {
2142
+ block: 'delay', name: 'master_time',
2143
+ displayLabel: 'Master Time',
2144
+ pidLow: 0x0046, pidHigh: 0x004e,
2145
+ unit: 'percent', displayMin: 25, displayMax: 400,
2146
+ },
2147
+ 'delay.lfo_rate': {
2148
+ block: 'delay', name: 'lfo_rate',
2149
+ displayLabel: 'LFO Rate',
2150
+ pidLow: 0x0046, pidHigh: 0x004f,
2151
+ unit: 'hz', displayMin: 0.1, displayMax: 10,
2152
+ },
2153
+ 'delay.lfo_depth': {
2154
+ block: 'delay', name: 'lfo_depth',
2155
+ displayLabel: 'LFO Depth',
2156
+ pidLow: 0x0046, pidHigh: 0x0050,
2157
+ unit: 'percent', displayMin: 0, displayMax: 100,
2158
+ },
2159
+ 'delay.stack_feedback': {
2160
+ block: 'delay', name: 'stack_feedback',
2161
+ displayLabel: 'Stack Feedback',
2162
+ pidLow: 0x0046, pidHigh: 0x0057,
2163
+ unit: 'percent', displayMin: 0, displayMax: 100,
2164
+ },
2165
+ 'delay.hold_feedback': {
2166
+ block: 'delay', name: 'hold_feedback',
2167
+ displayLabel: 'Hold Feedback',
2168
+ pidLow: 0x0046, pidHigh: 0x0058,
2169
+ unit: 'percent', displayMin: 0, displayMax: 100,
2170
+ },
2171
+ // renamed for UI-label match (audit row: DELAY 31)
2172
+ 'delay.repeat_stack_hold': {
2173
+ block: 'delay', name: 'repeat_stack_hold',
2174
+ displayLabel: 'Repeat Stack/Hold',
2175
+ pidLow: 0x0046, pidHigh: 0x001f,
2176
+ // Cache id=31: enum [OFF|STACK|HOLD]. Hand-authored — generator
2177
+ // can't emit per-block non-Type enums (it would mis-import the
2178
+ // block's TYPES_VALUES instead of these three).
2179
+ unit: 'enum', displayMin: 0, displayMax: 2,
2180
+ enumValues: { 0: 'OFF', 1: 'STACK', 2: 'HOLD' },
2181
+ },
2182
+ 'delay.ducking': {
2183
+ block: 'delay', name: 'ducking',
2184
+ displayLabel: 'Ducking',
2185
+ pidLow: 0x0046, pidHigh: 0x002e,
2186
+ // Cache id=46: float a=0 b=80 c=1 → raw dB 0..80 attenuation.
2187
+ // Same signature as reverb.ducking (HW-018).
2188
+ unit: 'db', displayMin: 0, displayMax: 80,
2189
+ },
2190
+ // HW-027 (Session 30 cont 2, 2026-04-25): tempo-sync registers across
2191
+ // every modulation block. Cache contains 14 79-entry tempo enums (delay
2192
+ // × 6, chorus × 2, reverb / flanger / rotary / phaser / tremolo /
2193
+ // filter × 1 each) — all string-identical, sharing the
2194
+ // TEMPO_DIVISIONS_VALUES dictionary emitted by gen-cache-enums.ts.
2195
+ // The first/lowest-id tempo enum on each block is canonically the main
2196
+ // "Tempo Sync" knob per Blocks Guide §Common LFO Parameters. We
2197
+ // register the high-confidence ones below (delay = wire-verified;
2198
+ // chorus/flanger/phaser/tremolo = structural-by-symmetry, every
2199
+ // modulation block has a Tempo Sync knob). Filter / reverb / rotary
2200
+ // tempo registers deferred — semantics uncertain (auto-wah env follower
2201
+ // vs LFO sync; reverb-modulation tempo for Vibrato-King types only).
2202
+ // Hand-authored in KNOWN_PARAMS rather than via paramNames+generator
2203
+ // because the generator's enum-handling defaults to the block's
2204
+ // TYPES_VALUES, which would mis-import for these non-Type enums.
2205
+ 'delay.tempo': {
2206
+ block: 'delay', name: 'tempo',
2207
+ displayLabel: 'Tempo',
2208
+ pidLow: 0x0046, pidHigh: 0x0013,
2209
+ // Wire-verified: session-30-delay-basic-digital-mono captured
2210
+ // value=11 (= "1/8" tempo division).
2211
+ unit: 'enum', displayMin: 0, displayMax: 78,
2212
+ enumValues: TEMPO_DIVISIONS_VALUES,
2213
+ },
2214
+ 'chorus.tempo': {
2215
+ block: 'chorus', name: 'tempo',
2216
+ displayLabel: 'Tempo',
2217
+ pidLow: 0x004e, pidHigh: 0x000d,
2218
+ unit: 'enum', displayMin: 0, displayMax: 78,
2219
+ enumValues: TEMPO_DIVISIONS_VALUES,
2220
+ },
2221
+ 'flanger.tempo': {
2222
+ block: 'flanger', name: 'tempo',
2223
+ displayLabel: 'Tempo',
2224
+ pidLow: 0x0052, pidHigh: 0x000c,
2225
+ unit: 'enum', displayMin: 0, displayMax: 78,
2226
+ enumValues: TEMPO_DIVISIONS_VALUES,
2227
+ },
2228
+ 'phaser.tempo': {
2229
+ block: 'phaser', name: 'tempo',
2230
+ displayLabel: 'Tempo',
2231
+ pidLow: 0x005a, pidHigh: 0x000e,
2232
+ unit: 'enum', displayMin: 0, displayMax: 78,
2233
+ enumValues: TEMPO_DIVISIONS_VALUES,
2234
+ },
2235
+ 'tremolo.tempo': {
2236
+ block: 'tremolo', name: 'tempo',
2237
+ displayLabel: 'Tempo',
2238
+ pidLow: 0x006a, pidHigh: 0x000f,
2239
+ unit: 'enum', displayMin: 0, displayMax: 78,
2240
+ enumValues: TEMPO_DIVISIONS_VALUES,
2241
+ },
2242
+ 'delay.channel': {
2243
+ block: 'delay', name: 'channel',
2244
+ pidLow: 0x0046, pidHigh: 0x07d2,
2245
+ unit: 'enum', displayMin: 0, displayMax: 3,
2246
+ enumValues: { 0: 'A', 1: 'B', 2: 'C', 3: 'D' },
2247
+ },
2248
+ 'delay.type': {
2249
+ block: 'delay', name: 'type',
2250
+ pidLow: 0x0046, pidHigh: 0x000a,
2251
+ // Session 16: enum dictionary imported from cacheEnums.ts (29 models).
2252
+ // Untested against capture.
2253
+ unit: 'enum', displayMin: 0, displayMax: 28,
2254
+ enumValues: DELAY_TYPES_VALUES,
2255
+ },
2256
+ // Session 18 — 6 additional block Type selectors, each pinned to wire
2257
+ // pidLow by a Tier-3 AM4-Edit capture of a Type-dropdown change. The
2258
+ // cache record id is the wire pidHigh (10 for the effect blocks, 19/20
2259
+ // for Comp/GEQ because their cache slot reserves ids 0..12 for band
2260
+ // levels / assign slots).
2261
+ // P1-010 Session B (2026-04-20) — universal Mix control per the
2262
+ // Blocks Guide §Common Mix/Level Parameters (p. 7). Every effect
2263
+ // block with a wet/dry concept exposes Mix at pidHigh 0x01 with the
2264
+ // same percent signature as the confirmed reverb.mix. Skipped for
2265
+ // Wah/GEQ/Gate/Volume-Pan (AM4 manual p. 34: "Effects with no mix,
2266
+ // such as Wah, GEQ, etc., will show 'NA'"). HW-014 partial: delay
2267
+ // / chorus / reverb mix verified correct; flanger.mix and
2268
+ // phaser.mix surfaced the BK-034 encoding bug (see entries below);
2269
+ // tremolo.mix / compressor.mix / filter.mix hidden on hardware
2270
+ // display (awaits AM4-Edit verification).
2271
+ // Modulation-block LFO rates + depths (Session 26 Unit-extension pass).
2272
+ // Rate uses the 'hz' unit (raw passthrough, c=1 in cache). Depth is a
2273
+ // standard percent knob. Blocks Guide §Chorus/Flanger/Phaser document
2274
+ // all three as Basic Page controls across these blocks.
2275
+ 'chorus.mix': {
2276
+ block: 'chorus', name: 'mix',
2277
+ pidLow: 0x004e, pidHigh: 0x0001,
2278
+ unit: 'percent', displayMin: 0, displayMax: 100,
2279
+ },
2280
+ 'chorus.type': {
2281
+ block: 'chorus', name: 'type',
2282
+ pidLow: 0x004e, pidHigh: 0x000a,
2283
+ unit: 'enum', displayMin: 0, displayMax: 19,
2284
+ enumValues: CHORUS_TYPES_VALUES,
2285
+ },
2286
+ 'chorus.rate': {
2287
+ // BK-034 resolved (HW-025 #2, Session 30): NOT an encoding bug.
2288
+ // AM4-Edit wire for Rate→3.4 Hz wrote pidLow=0x004e/pidHigh=0x000c
2289
+ // with float32(3.4) — byte-identical to our `unit: 'hz'` builder.
2290
+ // HW-014's hardware-display readback (3.4→0.5 Hz) is an AM4
2291
+ // hardware-screen rendering quirk for chorus rate, not a wire-
2292
+ // layer bug. Verify chorus rate via AM4-Edit, not the AM4 hardware
2293
+ // display, until the screen-side rendering is characterised.
2294
+ block: 'chorus', name: 'rate',
2295
+ displayLabel: 'Rate',
2296
+ pidLow: 0x004e, pidHigh: 0x000c,
2297
+ unit: 'hz', displayMin: 0.1, displayMax: 10,
2298
+ },
2299
+ 'chorus.depth': {
2300
+ block: 'chorus', name: 'depth',
2301
+ displayLabel: 'Depth',
2302
+ pidLow: 0x004e, pidHigh: 0x000e,
2303
+ unit: 'percent', displayMin: 0, displayMax: 100,
2304
+ },
2305
+ // HW-022 (Session 31, 2026-04-26): wire-verified on Analog Stereo
2306
+ // chorus — `session-30-chorus-basic.pcapng`. Chorus first-page
2307
+ // additions: level / time / mod_phase / phase_reverse.
2308
+ 'chorus.level': {
2309
+ block: 'chorus', name: 'level',
2310
+ pidLow: 0x004e, pidHigh: 0x0000,
2311
+ unit: 'db', displayMin: -80, displayMax: 20,
2312
+ },
2313
+ // renamed for UI-label match (audit row: CHORUS 16)
2314
+ 'chorus.delay_time': {
2315
+ block: 'chorus', name: 'delay_time',
2316
+ displayLabel: 'Delay Time',
2317
+ pidLow: 0x004e, pidHigh: 0x0010,
2318
+ // Cache id=16: float a=0.0001 b=0.05 c=1000 → display 0.1..50 ms.
2319
+ unit: 'ms', displayMin: 0.1, displayMax: 50,
2320
+ },
2321
+ 'chorus.mod_phase': {
2322
+ block: 'chorus', name: 'mod_phase',
2323
+ displayLabel: 'Mod Phase',
2324
+ pidLow: 0x004e, pidHigh: 0x0011,
2325
+ // Cache id=17: float a=0 b=π c=180/π → display 0..180 deg.
2326
+ unit: 'degrees', displayMin: 0, displayMax: 180,
2327
+ },
2328
+ 'chorus.phase_reverse': {
2329
+ block: 'chorus', name: 'phase_reverse',
2330
+ displayLabel: 'Phase Reverse',
2331
+ pidLow: 0x004e, pidHigh: 0x0014,
2332
+ // Cache id=20 enum: [NONE, RIGHT, LEFT, BOTH]. Default NONE.
2333
+ unit: 'enum', displayMin: 0, displayMax: 3,
2334
+ enumValues: { 0: 'NONE', 1: 'RIGHT', 2: 'LEFT', 3: 'BOTH' },
2335
+ },
2336
+ // HW-040 (Session 36, 2026-04-29): Chorus Expert-Edit page from
2337
+ // session-40-chorus-expert.pcapng (Analog Stereo). New non-Type
2338
+ // enums + cache mirrors.
2339
+ 'chorus.bypass_mode': {
2340
+ block: 'chorus', name: 'bypass_mode',
2341
+ pidLow: 0x004e, pidHigh: 0x0004,
2342
+ unit: 'enum', displayMin: 0, displayMax: 2,
2343
+ enumValues: { 0: 'Thru', 1: 'Mute FX Out', 2: 'Mute Out' },
2344
+ },
2345
+ // chorus.tempo already registered above (HW-027 added the
2346
+ // shared TEMPO_DIVISIONS_VALUES dictionary).
2347
+ 'chorus.lfo_type': {
2348
+ block: 'chorus', name: 'lfo_type',
2349
+ displayLabel: 'LFO Type',
2350
+ pidLow: 0x004e, pidHigh: 0x0012,
2351
+ unit: 'enum', displayMin: 0, displayMax: 9,
2352
+ enumValues: LFO_WAVEFORMS_VALUES,
2353
+ },
2354
+ 'chorus.auto_depth': {
2355
+ block: 'chorus', name: 'auto_depth',
2356
+ displayLabel: 'Auto Depth',
2357
+ pidLow: 0x004e, pidHigh: 0x0013,
2358
+ unit: 'enum', displayMin: 0, displayMax: 2,
2359
+ enumValues: { 0: 'OFF', 1: 'LOW', 2: 'HIGH' },
2360
+ },
2361
+ // renamed for UI-label match (audit row: CHORUS 27)
2362
+ 'chorus.mode': {
2363
+ block: 'chorus', name: 'mode',
2364
+ displayLabel: 'Mode',
2365
+ pidLow: 0x004e, pidHigh: 0x001b,
2366
+ unit: 'enum', displayMin: 0, displayMax: 3,
2367
+ enumValues: { 0: 'OFF', 1: 'LOW', 2: 'MED', 3: 'HIGH' },
2368
+ },
2369
+ 'chorus.number_of_voices': {
2370
+ block: 'chorus', name: 'number_of_voices',
2371
+ displayLabel: 'Number of Voices',
2372
+ pidLow: 0x004e, pidHigh: 0x000b,
2373
+ unit: 'count', displayMin: 1, displayMax: 4,
2374
+ },
2375
+ 'chorus.high_cut': {
2376
+ block: 'chorus', name: 'high_cut',
2377
+ displayLabel: 'High Cut',
2378
+ pidLow: 0x004e, pidHigh: 0x000f,
2379
+ unit: 'hz', displayMin: 200, displayMax: 20000,
2380
+ },
2381
+ 'chorus.lfo_phase_pct': {
2382
+ block: 'chorus', name: 'lfo_phase_pct',
2383
+ displayLabel: 'Right Time Ratio',
2384
+ pidLow: 0x004e, pidHigh: 0x0015,
2385
+ unit: 'percent', displayMin: 0, displayMax: 100,
2386
+ },
2387
+ // renamed for UI-label match (audit row: CHORUS 22)
2388
+ 'chorus.rate_right': {
2389
+ block: 'chorus', name: 'rate_right',
2390
+ displayLabel: 'Rate Right',
2391
+ pidLow: 0x004e, pidHigh: 0x0016,
2392
+ unit: 'hz', displayMin: 0.1, displayMax: 10,
2393
+ },
2394
+ // renamed for UI-label match (audit row: CHORUS 23)
2395
+ 'chorus.lfo_2_depth': {
2396
+ block: 'chorus', name: 'lfo_2_depth',
2397
+ displayLabel: 'LFO 2 Depth',
2398
+ pidLow: 0x004e, pidHigh: 0x0017,
2399
+ unit: 'percent', displayMin: 0, displayMax: 100,
2400
+ },
2401
+ 'chorus.drive': {
2402
+ block: 'chorus', name: 'drive',
2403
+ displayLabel: 'Drive',
2404
+ pidLow: 0x004e, pidHigh: 0x0018,
2405
+ // Cache id=24: float a=0.05 b=50 c=10 → display = wire × 10,
2406
+ // range 0.5..500. Same encoding shape as knob_0_10 with stretched
2407
+ // range; the unit is structural. typecode 80 → log10 (HW-053 cont).
2408
+ unit: 'knob_0_10', displayMin: 0.5, displayMax: 500,
2409
+ scaling: 'log10',
2410
+ },
2411
+ // renamed for UI-label match (audit row: CHORUS 25)
2412
+ 'chorus.low_cut': {
2413
+ block: 'chorus', name: 'low_cut',
2414
+ displayLabel: 'Low Cut',
2415
+ pidLow: 0x004e, pidHigh: 0x0019,
2416
+ unit: 'hz', displayMin: 20, displayMax: 2000,
2417
+ },
2418
+ // renamed for UI-label match (audit row: CHORUS 26)
2419
+ 'chorus.stereo_spread': {
2420
+ block: 'chorus', name: 'stereo_spread',
2421
+ displayLabel: 'Stereo Spread',
2422
+ pidLow: 0x004e, pidHigh: 0x001a,
2423
+ // Cache id=26: float a=-2 b=2 c=100 → display = wire × 100,
2424
+ // range -200..200% (bipolar with extended range).
2425
+ unit: 'bipolar_percent', displayMin: -200, displayMax: 200,
2426
+ },
2427
+ // HW-032 (Session 30 cont 8, 2026-04-25): wire-verified at +10 dB on
2428
+ // an Analog Stereo flanger — `session-32-flanger-extended.pcapng`.
2429
+ // Follows the universal pidHigh=0x0000 Level pattern.
2430
+ 'flanger.level': {
2431
+ block: 'flanger', name: 'level',
2432
+ pidLow: 0x0052, pidHigh: 0x0000,
2433
+ unit: 'db', displayMin: -80, displayMax: 20,
2434
+ },
2435
+ 'flanger.mix': {
2436
+ // BK-034 resolved (HW-025 #3, Session 30): NOT an encoding bug.
2437
+ // AM4-Edit wire for Mix→54% wrote pidLow=0x0052/pidHigh=0x0001
2438
+ // with float32(0.54) — byte-identical to our `unit: 'percent'`
2439
+ // builder. HW-014's hardware-display readback (54%→50%) is a
2440
+ // hardware-screen rendering quirk; verify via AM4-Edit.
2441
+ block: 'flanger', name: 'mix',
2442
+ pidLow: 0x0052, pidHigh: 0x0001,
2443
+ unit: 'percent', displayMin: 0, displayMax: 100,
2444
+ },
2445
+ 'flanger.type': {
2446
+ block: 'flanger', name: 'type',
2447
+ pidLow: 0x0052, pidHigh: 0x000a,
2448
+ unit: 'enum', displayMin: 0, displayMax: 31,
2449
+ enumValues: FLANGER_TYPES_VALUES,
2450
+ },
2451
+ // HW-024 (Session 30 cont 3): wire-verified at 1.7 Hz on an
2452
+ // Analog Stereo flanger (HW-014 left this unconfirmed in Round 2).
2453
+ 'flanger.rate': {
2454
+ block: 'flanger', name: 'rate',
2455
+ displayLabel: 'Rate',
2456
+ pidLow: 0x0052, pidHigh: 0x000b,
2457
+ unit: 'hz', displayMin: 0.05, displayMax: 10,
2458
+ },
2459
+ 'flanger.depth': {
2460
+ block: 'flanger', name: 'depth',
2461
+ displayLabel: 'Depth',
2462
+ pidLow: 0x0052, pidHigh: 0x000d,
2463
+ unit: 'percent', displayMin: 0, displayMax: 100,
2464
+ },
2465
+ 'flanger.feedback': {
2466
+ // BK-034 resolved (HW-025 #4, Session 30): NOT an encoding bug.
2467
+ // AM4-Edit wire for Feedback→-61% wrote pidLow=0x0052/pidHigh=0x000e
2468
+ // with float32(-0.61) — byte-identical to our `unit: 'bipolar_percent'`
2469
+ // builder. HW-014's hardware-display readbacks (-61%→0; +99%→+90)
2470
+ // are hardware-screen rendering quirks; verify via AM4-Edit.
2471
+ block: 'flanger', name: 'feedback',
2472
+ displayLabel: 'Feedback',
2473
+ pidLow: 0x0052, pidHigh: 0x000e,
2474
+ // Cache caps internal range at ±0.995 — display scale 100 ⇒ ±99%.
2475
+ unit: 'bipolar_percent', displayMin: -99, displayMax: 99,
2476
+ },
2477
+ // HW-022 (Session 31, 2026-04-26): wire-verified on Analog Stereo
2478
+ // flanger — `session-30-flanger-basic.pcapng`. Manual is a 0–10 knob
2479
+ // (no unit suffix shown in AM4-Edit); Mod Phase mirrors the chorus
2480
+ // degrees encoding.
2481
+ 'flanger.manual': {
2482
+ block: 'flanger', name: 'manual',
2483
+ displayLabel: 'Manual',
2484
+ pidLow: 0x0052, pidHigh: 0x000f,
2485
+ // Cache id=15: float a=0 b=1 c=10 → display 0..10.
2486
+ unit: 'knob_0_10', displayMin: 0, displayMax: 10,
2487
+ },
2488
+ 'flanger.mod_phase': {
2489
+ block: 'flanger', name: 'mod_phase',
2490
+ displayLabel: 'Mod Phase',
2491
+ pidLow: 0x0052, pidHigh: 0x0011,
2492
+ // Cache id=17: float a=0 b=π c=180/π → display 0..180 deg.
2493
+ unit: 'degrees', displayMin: 0, displayMax: 180,
2494
+ },
2495
+ 'phaser.mix': {
2496
+ // BK-034 resolved (HW-025 #5, Session 30): NOT an encoding bug.
2497
+ // AM4-Edit wire for Mix→88% wrote pidLow=0x005a/pidHigh=0x0001
2498
+ // with float32(0.88) — byte-identical to our `unit: 'percent'`
2499
+ // builder. HW-014's hardware-display readback (88%→53%) is a
2500
+ // hardware-screen rendering quirk; verify via AM4-Edit.
2501
+ block: 'phaser', name: 'mix',
2502
+ pidLow: 0x005a, pidHigh: 0x0001,
2503
+ unit: 'percent', displayMin: 0, displayMax: 100,
2504
+ },
2505
+ 'phaser.type': {
2506
+ block: 'phaser', name: 'type',
2507
+ pidLow: 0x005a, pidHigh: 0x000a,
2508
+ unit: 'enum', displayMin: 0, displayMax: 16,
2509
+ enumValues: PHASER_TYPES_VALUES,
2510
+ },
2511
+ // HW-024 (Session 30 cont 3): wire-verified at 2.3 Hz on a Digital
2512
+ // Mono phaser (HW-014 left this unconfirmed in Round 2).
2513
+ 'phaser.rate': {
2514
+ block: 'phaser', name: 'rate',
2515
+ displayLabel: 'Rate',
2516
+ pidLow: 0x005a, pidHigh: 0x000c,
2517
+ unit: 'hz', displayMin: 0.1, displayMax: 10,
2518
+ },
2519
+ 'phaser.feedback': {
2520
+ block: 'phaser', name: 'feedback',
2521
+ displayLabel: 'Feedback',
2522
+ pidLow: 0x005a, pidHigh: 0x0010,
2523
+ // Cache signature is unusual — internal ±0.9, display-scale 111.1.
2524
+ // We use standard bipolar_percent (scale 100) with clamped bounds
2525
+ // so input stays inside the internal range; AM4-Edit's displayed
2526
+ // percentage may read slightly higher than the value set (an input
2527
+ // of "50" sets internal 0.5 which AM4-Edit shows as ~55.5%). The
2528
+ // natural-language UX impact is negligible.
2529
+ unit: 'bipolar_percent', displayMin: -90, displayMax: 90,
2530
+ },
2531
+ // HW-022 (Session 31, 2026-04-26): wire-verified on Digital Stereo
2532
+ // phaser — `session-30-phaser-basic.pcapng`. Phaser uses 0–10 knob
2533
+ // semantics for Depth + Manual (unlike chorus/flanger which use
2534
+ // percent for Depth). Mod Phase address differs from chorus/flanger
2535
+ // (0x0013 here vs 0x0011 there) — cache lays it out at id=19 not id=17.
2536
+ 'phaser.level': {
2537
+ block: 'phaser', name: 'level',
2538
+ pidLow: 0x005a, pidHigh: 0x0000,
2539
+ unit: 'db', displayMin: -80, displayMax: 20,
2540
+ },
2541
+ 'phaser.depth': {
2542
+ block: 'phaser', name: 'depth',
2543
+ displayLabel: 'Depth',
2544
+ pidLow: 0x005a, pidHigh: 0x000f,
2545
+ // Cache id=15: float a=0 b=1 c=10 → display 0..10.
2546
+ unit: 'knob_0_10', displayMin: 0, displayMax: 10,
2547
+ },
2548
+ 'phaser.mod_phase': {
2549
+ block: 'phaser', name: 'mod_phase',
2550
+ displayLabel: 'Mod Phase',
2551
+ pidLow: 0x005a, pidHigh: 0x0013,
2552
+ // Cache id=19: float a=0 b=π c=180/π → display 0..180 deg.
2553
+ unit: 'degrees', displayMin: 0, displayMax: 180,
2554
+ },
2555
+ 'phaser.manual': {
2556
+ block: 'phaser', name: 'manual',
2557
+ displayLabel: 'Manual',
2558
+ pidLow: 0x005a, pidHigh: 0x0022,
2559
+ // Cache id=34: float a=0 b=1 c=10 → display 0..10.
2560
+ unit: 'knob_0_10', displayMin: 0, displayMax: 10,
2561
+ },
2562
+ 'wah.type': {
2563
+ block: 'wah', name: 'type',
2564
+ pidLow: 0x005e, pidHigh: 0x000a,
2565
+ unit: 'enum', displayMin: 0, displayMax: 8,
2566
+ enumValues: WAH_TYPES_VALUES,
2567
+ },
2568
+ // HW-040 (Session 36, 2026-04-29): Wah Expert-Edit page from
2569
+ // session-40-wah-expert.pcapng (FAS Wah). 18 new params + 3 hand-
2570
+ // authored enums. Closes the wah block from "type-only" registration
2571
+ // to full Expert coverage.
2572
+ //
2573
+ // **BK-035 audit (Session 36 cont, 2026-04-29):** Eight wah ids were
2574
+ // mis-named in the original auto-generation pass — the cache-id →
2575
+ // pidHigh ordering didn't match the AM4-Edit screenshot's knob
2576
+ // labels. Re-derived from the value-matched audit table (`scripts/
2577
+ // audit-block-vs-screenshot.ts` against `docs/audit-input/wah.json`).
2578
+ // Old → new:
2579
+ // 0x000d q (range 2..20) → q_resonance (range 0..10)
2580
+ // 0x000e q_resonance → q_tracking
2581
+ // 0x000f q_tracking → wah_control
2582
+ // 0x0010 control_taper + drive → fat
2583
+ // (was duplicate-pidHigh code bug — now resolved)
2584
+ // 0x0011 fat → drive
2585
+ // 0x0012 (unregistered) → control_taper (enum, hand-authored)
2586
+ // 0x0013 low_cut_frequency → inductor_bias (knob_0_10)
2587
+ // 0x0014 inductor_bias (hz) → low_cut_frequency (hz)
2588
+ 'wah.level': {
2589
+ block: 'wah', name: 'level',
2590
+ pidLow: 0x005e, pidHigh: 0x0000,
2591
+ unit: 'db', displayMin: -80, displayMax: 20,
2592
+ },
2593
+ 'wah.bypass_mode': {
2594
+ block: 'wah', name: 'bypass_mode',
2595
+ pidLow: 0x005e, pidHigh: 0x0004,
2596
+ unit: 'enum', displayMin: 0, displayMax: 1,
2597
+ enumValues: { 0: 'Thru', 1: 'Mute' },
2598
+ },
2599
+ // renamed for UI-label match (audit row: WAH 11)
2600
+ 'wah.minimum_frequency': {
2601
+ block: 'wah', name: 'minimum_frequency',
2602
+ displayLabel: 'Minimum Frequency',
2603
+ pidLow: 0x005e, pidHigh: 0x000b,
2604
+ unit: 'hz', displayMin: 100, displayMax: 1000,
2605
+ },
2606
+ // renamed for UI-label match (audit row: WAH 12)
2607
+ 'wah.maximum_frequency': {
2608
+ block: 'wah', name: 'maximum_frequency',
2609
+ displayLabel: 'Maximum Frequency',
2610
+ pidLow: 0x005e, pidHigh: 0x000c,
2611
+ // Cache id=12: a=500 b=5000 c=1.
2612
+ unit: 'hz', displayMin: 500, displayMax: 5000,
2613
+ },
2614
+ 'wah.q_resonance': {
2615
+ block: 'wah', name: 'q_resonance',
2616
+ displayLabel: 'Resonance',
2617
+ pidLow: 0x005e, pidHigh: 0x000d,
2618
+ // BK-035: was `wah.q` with range 2..20. Screenshot showed 4.44 at
2619
+ // wire 0.444, which only matches knob_0_10 (×10). Range corrected.
2620
+ // typecode 80 → log10 (HW-053 cont); displayMin=0 falls back to linear.
2621
+ unit: 'knob_0_10', displayMin: 0, displayMax: 10,
2622
+ scaling: 'log10',
2623
+ },
2624
+ 'wah.q_tracking': {
2625
+ block: 'wah', name: 'q_tracking',
2626
+ displayLabel: 'Q Tracking',
2627
+ pidLow: 0x005e, pidHigh: 0x000e,
2628
+ unit: 'knob_0_10', displayMin: 0, displayMax: 10,
2629
+ },
2630
+ 'wah.wah_control': {
2631
+ block: 'wah', name: 'wah_control',
2632
+ displayLabel: 'Wah Control',
2633
+ pidLow: 0x005e, pidHigh: 0x000f,
2634
+ // BK-035: the actual pedal-position param. Without it, cocked-wah
2635
+ // presets are blocked — Claude can't sweep the wah filter sweep.
2636
+ unit: 'knob_0_10', displayMin: 0, displayMax: 10,
2637
+ },
2638
+ 'wah.fat': {
2639
+ block: 'wah', name: 'fat',
2640
+ displayLabel: 'Fat',
2641
+ pidLow: 0x005e, pidHigh: 0x0010,
2642
+ unit: 'knob_0_10', displayMin: 0, displayMax: 10,
2643
+ },
2644
+ 'wah.drive': {
2645
+ block: 'wah', name: 'drive',
2646
+ displayLabel: 'Drive',
2647
+ pidLow: 0x005e, pidHigh: 0x0011,
2648
+ // typecode 80 → log10 (HW-053 cont); displayMin=0 falls back to linear.
2649
+ unit: 'knob_0_10', displayMin: 0, displayMax: 10,
2650
+ scaling: 'log10',
2651
+ },
2652
+ 'wah.control_taper': {
2653
+ block: 'wah', name: 'control_taper',
2654
+ displayLabel: 'Control Taper',
2655
+ pidLow: 0x005e, pidHigh: 0x0012,
2656
+ // BK-035: previously registered at 0x0010 (wrong pidHigh). The
2657
+ // captured wire at 0x0012 is float32(4) = enum index 4 = "Log 10A",
2658
+ // matching the screenshot's Control Taper dropdown.
2659
+ // Cache id=18 enum has 6 entries (max=5).
2660
+ unit: 'enum', displayMin: 0, displayMax: 5,
2661
+ enumValues: { 0: 'LINEAR', 1: 'LOG 30A', 2: 'LOG 20A', 3: 'LOG 15A', 4: 'LOG 10A', 5: 'LOG 5A' },
2662
+ },
2663
+ 'wah.inductor_bias': {
2664
+ block: 'wah', name: 'inductor_bias',
2665
+ displayLabel: 'Inductor Bias',
2666
+ pidLow: 0x005e, pidHigh: 0x0013,
2667
+ unit: 'knob_0_10', displayMin: 0, displayMax: 10,
2668
+ },
2669
+ 'wah.low_cut_frequency': {
2670
+ block: 'wah', name: 'low_cut_frequency',
2671
+ displayLabel: 'Low Cut Frequency',
2672
+ pidLow: 0x005e, pidHigh: 0x0014,
2673
+ unit: 'hz', displayMin: 20, displayMax: 2000,
2674
+ },
2675
+ 'wah.eq_post': {
2676
+ block: 'wah', name: 'eq_post',
2677
+ displayLabel: 'EQ',
2678
+ pidLow: 0x005e, pidHigh: 0x0015,
2679
+ unit: 'enum', displayMin: 0, displayMax: 1,
2680
+ enumValues: { 0: 'OFF', 1: 'ON' },
2681
+ },
2682
+ 'wah.graphic_eq_band_1': { block: 'wah', name: 'graphic_eq_band_1', displayLabel: '160', pidLow: 0x005e, pidHigh: 0x0016, unit: 'db', displayMin: -12, displayMax: 12 },
2683
+ 'wah.graphic_eq_band_2': { block: 'wah', name: 'graphic_eq_band_2', displayLabel: '250', pidLow: 0x005e, pidHigh: 0x0017, unit: 'db', displayMin: -12, displayMax: 12 },
2684
+ 'wah.graphic_eq_band_3': { block: 'wah', name: 'graphic_eq_band_3', displayLabel: '400', pidLow: 0x005e, pidHigh: 0x0018, unit: 'db', displayMin: -12, displayMax: 12 },
2685
+ 'wah.graphic_eq_band_4': { block: 'wah', name: 'graphic_eq_band_4', displayLabel: '640', pidLow: 0x005e, pidHigh: 0x0019, unit: 'db', displayMin: -12, displayMax: 12 },
2686
+ 'wah.graphic_eq_band_5': { block: 'wah', name: 'graphic_eq_band_5', displayLabel: '1000', pidLow: 0x005e, pidHigh: 0x001a, unit: 'db', displayMin: -12, displayMax: 12 },
2687
+ 'wah.graphic_eq_band_6': { block: 'wah', name: 'graphic_eq_band_6', displayLabel: '1600', pidLow: 0x005e, pidHigh: 0x001b, unit: 'db', displayMin: -12, displayMax: 12 },
2688
+ 'wah.graphic_eq_band_7': { block: 'wah', name: 'graphic_eq_band_7', displayLabel: '2500', pidLow: 0x005e, pidHigh: 0x001c, unit: 'db', displayMin: -12, displayMax: 12 },
2689
+ 'wah.graphic_eq_band_8': { block: 'wah', name: 'graphic_eq_band_8', displayLabel: '4000', pidLow: 0x005e, pidHigh: 0x001d, unit: 'db', displayMin: -12, displayMax: 12 },
2690
+ 'compressor.mix': {
2691
+ block: 'compressor', name: 'mix',
2692
+ pidLow: 0x002e, pidHigh: 0x0001,
2693
+ unit: 'percent', displayMin: 0, displayMax: 100,
2694
+ },
2695
+ // HW-021 (Session 30, 2026-04-25): Compressor first-page registers
2696
+ // from session-30-comp-basic-jfet-studio. Cache ids 10..15 are the
2697
+ // canonical comp-config knobs (Threshold, Ratio, Attack, Release,
2698
+ // Knee Type enum, Auto Makeup OFF/ON). `level` follows the universal
2699
+ // pidHigh=0x0000 "Level" pattern (out-of-band hand-author).
2700
+ // Two more registers wiggled in the capture remain unidentified
2701
+ // (pidHigh=0x0017 cache id=23 float; pidHigh=0x0029 cache id=41
2702
+ // knob_0_10 with value 1.2 exceeding cache cap b=1) — queued as
2703
+ // HW-028 follow-up. The Optical/JFET-specific Light Type knob
2704
+ // wasn't reached in this capture.
2705
+ 'compressor.level': {
2706
+ block: 'compressor', name: 'level',
2707
+ pidLow: 0x002e, pidHigh: 0x0000,
2708
+ unit: 'db', displayMin: -80, displayMax: 20,
2709
+ },
2710
+ 'compressor.threshold': {
2711
+ block: 'compressor', name: 'threshold',
2712
+ displayLabel: 'Threshold',
2713
+ pidLow: 0x002e, pidHigh: 0x000a,
2714
+ // Cache id=10: float a=-60 b=20 c=1 → dB -60..+20 (capture wrote
2715
+ // -30 dB).
2716
+ unit: 'db', displayMin: -60, displayMax: 20,
2717
+ },
2718
+ 'compressor.ratio': {
2719
+ block: 'compressor', name: 'ratio',
2720
+ displayLabel: 'Ratio',
2721
+ pidLow: 0x002e, pidHigh: 0x000b,
2722
+ // Cache id=11: float a=1 b=20 c=1 step=0.01 → 1.0..20.0 ratio
2723
+ // (e.g. 4.0 ⇒ 4:1). Uses the `ratio` unit semantically; math is
2724
+ // identical to db/hz/seconds (display = internal, scale 1) but
2725
+ // the label tells Claude "4 means 4:1, not 4 dB".
2726
+ // BK-038 (Session 43 cont): typecode=64 → log10 scaling. Read
2727
+ // register stores Q15 of log10-normalized internal across [1..20].
2728
+ unit: 'ratio', displayMin: 1, displayMax: 20, scaling: 'log10',
2729
+ },
2730
+ // renamed for UI-label match (audit row: COMP 12)
2731
+ 'compressor.attack_time': {
2732
+ block: 'compressor', name: 'attack_time',
2733
+ displayLabel: 'Attack Time',
2734
+ pidLow: 0x002e, pidHigh: 0x000c,
2735
+ // Cache id=12: float a=0.0001 b=0.1 c=1000 → 0.1..100 ms.
2736
+ // BK-038 (Session 43 cont): typecode=68 → log10 scaling. Verified
2737
+ // empirically — Sultans test wrote 40 ms, readback decoded as 867 ms
2738
+ // with old linear rule; with log10 rule, internal 0.867 → 40.0 ms.
2739
+ unit: 'ms', displayMin: 0.1, displayMax: 100, scaling: 'log10',
2740
+ },
2741
+ // renamed for UI-label match (audit row: COMP 13)
2742
+ 'compressor.release_time': {
2743
+ block: 'compressor', name: 'release_time',
2744
+ displayLabel: 'Release Time',
2745
+ pidLow: 0x002e, pidHigh: 0x000d,
2746
+ // Cache id=13: float a=0.002 b=2 c=1000 → 2..2000 ms.
2747
+ // BK-038 (Session 43 cont): typecode=68 → log10 scaling. Sultans
2748
+ // test wrote 100 ms; readback internal 0.566 → log10 decode → 100 ms.
2749
+ unit: 'ms', displayMin: 2, displayMax: 2000, scaling: 'log10',
2750
+ },
2751
+ 'compressor.auto_makeup': {
2752
+ block: 'compressor', name: 'auto_makeup',
2753
+ displayLabel: 'Auto Makeup',
2754
+ pidLow: 0x002e, pidHigh: 0x000f,
2755
+ // Cache id=15: enum [OFF|ON]. Hand-authored — see delay.stack_hold
2756
+ // for why per-block non-Type enums skip the generator.
2757
+ unit: 'enum', displayMin: 0, displayMax: 1,
2758
+ enumValues: { 0: 'OFF', 1: 'ON' },
2759
+ },
2760
+ 'compressor.type': {
2761
+ block: 'compressor', name: 'type',
2762
+ pidLow: 0x002e, pidHigh: 0x0013,
2763
+ unit: 'enum', displayMin: 0, displayMax: 18,
2764
+ enumValues: COMPRESSOR_TYPES_VALUES,
2765
+ },
2766
+ // HW-028 + HW-039 (Session 35, 2026-04-29): Compressor Expert-Edit
2767
+ // Sidechain section + Drive-engine knobs from
2768
+ // session-31-comp-jfet-expert.pcapng + paired AM4-Edit screenshot
2769
+ // (JFET Studio Compressor type). Closes HW-028: 0x0017 = comp.emphasis
2770
+ // (knob_0_20 fine knob 0..20, screenshot 2.22 ↔ wire 0.111×20=2.22);
2771
+ // 0x0029 = comp.drive (knob_0_10, screenshot 6.66 ↔ wire 0.666). The
2772
+ // Sidechain section pins eight new params (filter Frequency/Q/Gain/
2773
+ // Low Cut/High Cut/Source/Filter Type/Emphasis Freq) with cache shapes
2774
+ // matching screenshot labels byte-for-byte. bypass_mode (0x0004) and
2775
+ // input_level (0x0019) are universal MIX-section enums.
2776
+ //
2777
+ // Mirrored from CACHE_PARAMS so the type-check picks them up.
2778
+ 'compressor.bypass_mode': {
2779
+ block: 'compressor', name: 'bypass_mode',
2780
+ pidLow: 0x002e, pidHigh: 0x0004,
2781
+ // Cache id=4: enum [Thru / Mute FX Out / Mute Out]. Hand-authored —
2782
+ // not Type, so gen-params skips the enum-import attachment.
2783
+ unit: 'enum', displayMin: 0, displayMax: 2,
2784
+ enumValues: { 0: 'Thru', 1: 'Mute FX Out', 2: 'Mute Out' },
2785
+ },
2786
+ 'compressor.sidechain_low_cut': {
2787
+ block: 'compressor', name: 'sidechain_low_cut',
2788
+ displayLabel: 'Low Cut',
2789
+ pidLow: 0x002e, pidHigh: 0x0011,
2790
+ unit: 'hz', displayMin: 20, displayMax: 2000,
2791
+ },
2792
+ 'compressor.sidechain_source': {
2793
+ block: 'compressor', name: 'sidechain_source',
2794
+ displayLabel: 'Sidechain Source',
2795
+ pidLow: 0x002e, pidHigh: 0x0012,
2796
+ // Cache id=18: enum [BLOCK L+R / INPUT 1 / BLOCK L / BLOCK R].
2797
+ // Same enum strings as gate.sidechain (capture wrote index 3 =
2798
+ // BLOCK R; matches screenshot "Block R").
2799
+ unit: 'enum', displayMin: 0, displayMax: 3,
2800
+ enumValues: { 0: 'BLOCK L+R', 1: 'INPUT 1', 2: 'BLOCK L', 3: 'BLOCK R' },
2801
+ },
2802
+ 'compressor.look_ahead_time': {
2803
+ block: 'compressor', name: 'look_ahead_time',
2804
+ displayLabel: 'Look-Ahead Time',
2805
+ pidLow: 0x002e, pidHigh: 0x0015,
2806
+ // Cache id=21: float a=0 b=0.002 c=1000 → 0..2 ms (fine resolution).
2807
+ unit: 'ms', displayMin: 0, displayMax: 2,
2808
+ },
2809
+ 'compressor.emphasis': {
2810
+ block: 'compressor', name: 'emphasis',
2811
+ displayLabel: 'Emphasis',
2812
+ pidLow: 0x002e, pidHigh: 0x0017,
2813
+ // Cache id=23: float a=0 b=1 c=20 step=0.0005 → 0..20 fine knob.
2814
+ // First param to use the new `knob_0_20` unit (HW-028 closure).
2815
+ unit: 'knob_0_20', displayMin: 0, displayMax: 20,
2816
+ },
2817
+ 'compressor.input_level': {
2818
+ block: 'compressor', name: 'input_level',
2819
+ displayLabel: 'Input Level',
2820
+ pidLow: 0x002e, pidHigh: 0x0019,
2821
+ // Cache id=25: enum [INSTRUMENT / LINE]. Capture wrote index 0 =
2822
+ // INSTRUMENT; matches screenshot "Instrument".
2823
+ unit: 'enum', displayMin: 0, displayMax: 1,
2824
+ enumValues: { 0: 'INSTRUMENT', 1: 'LINE' },
2825
+ },
2826
+ 'compressor.sidechain_high_cut': {
2827
+ block: 'compressor', name: 'sidechain_high_cut',
2828
+ displayLabel: 'High Cut',
2829
+ pidLow: 0x002e, pidHigh: 0x001a,
2830
+ unit: 'hz', displayMin: 200, displayMax: 20000,
2831
+ },
2832
+ 'compressor.sidechain_gain': {
2833
+ block: 'compressor', name: 'sidechain_gain',
2834
+ displayLabel: 'Gain',
2835
+ pidLow: 0x002e, pidHigh: 0x001b,
2836
+ unit: 'db', displayMin: -12, displayMax: 12,
2837
+ },
2838
+ 'compressor.sidechain_frequency': {
2839
+ block: 'compressor', name: 'sidechain_frequency',
2840
+ displayLabel: 'Frequency',
2841
+ pidLow: 0x002e, pidHigh: 0x001c,
2842
+ unit: 'hz', displayMin: 100, displayMax: 10000,
2843
+ },
2844
+ 'compressor.sidechain_q': {
2845
+ block: 'compressor', name: 'sidechain_q',
2846
+ displayLabel: 'Q',
2847
+ pidLow: 0x002e, pidHigh: 0x001d,
2848
+ // Cache id=29: float a=0.1 b=10 c=1 → 0.1..10 fractional Q. Uses
2849
+ // `count` for the raw-passthrough scale (display = wire × 1) — the
2850
+ // unit is structural; Q is a quality factor, not a literal count.
2851
+ unit: 'count', displayMin: 0.1, displayMax: 10, scaling: 'log10',
2852
+ },
2853
+ 'compressor.sidechain_filter_type': {
2854
+ block: 'compressor', name: 'sidechain_filter_type',
2855
+ displayLabel: 'Filter Type',
2856
+ pidLow: 0x002e, pidHigh: 0x0020,
2857
+ // HW-039 (closed 2026-04-30): cache id=32 enum has all 12 entries
2858
+ // (the earlier "4-entry truncation" finding from Session 35 was a
2859
+ // stale parse — `cache-section2.json` confirms count=12, max=11).
2860
+ // Hand-authored rather than emitted via gen-params-from-cache because
2861
+ // the generator's per-block `enumImport` only targets the Type
2862
+ // dropdown (id=19 here); non-Type enums are inlined.
2863
+ unit: 'enum', displayMin: 0, displayMax: 11,
2864
+ enumValues: {
2865
+ 0: 'NULL',
2866
+ 1: 'LOWPASS',
2867
+ 2: 'BANDPASS',
2868
+ 3: 'HIGHPASS',
2869
+ 4: 'LOWSHELF',
2870
+ 5: 'HIGHSHELF',
2871
+ 6: 'PEAKING',
2872
+ 7: 'NOTCH',
2873
+ 8: 'TILT EQ',
2874
+ 9: 'LOWSHELF 2',
2875
+ 10: 'HIGHSHELF 2',
2876
+ 11: 'PEAKING 2',
2877
+ },
2878
+ },
2879
+ 'compressor.sidechain_emphasis_freq': {
2880
+ block: 'compressor', name: 'sidechain_emphasis_freq',
2881
+ displayLabel: 'Emphasis Freq',
2882
+ pidLow: 0x002e, pidHigh: 0x0027,
2883
+ unit: 'hz', displayMin: 100, displayMax: 10000,
2884
+ },
2885
+ 'compressor.drive': {
2886
+ block: 'compressor', name: 'drive',
2887
+ displayLabel: 'Drive',
2888
+ pidLow: 0x002e, pidHigh: 0x0029,
2889
+ // Cache id=41: float a=0 b=1 c=10 → knob_0_10. HW-021 noted the
2890
+ // earlier capture wrote 1.2 (exceeds cache cap b=1) — that capture
2891
+ // was one of the AM4-Edit-side wiggles that briefly went past the
2892
+ // displayed range; the current capture wire is 0.666 → display 6.66
2893
+ // matches screenshot "Drive 6.66" cleanly.
2894
+ unit: 'knob_0_10', displayMin: 0, displayMax: 10,
2895
+ },
2896
+ 'geq.type': {
2897
+ block: 'geq', name: 'type',
2898
+ pidLow: 0x0032, pidHigh: 0x0014,
2899
+ unit: 'enum', displayMin: 0, displayMax: 17,
2900
+ enumValues: GEQ_TYPES_VALUES,
2901
+ },
2902
+ // Session 18 (continued) — 5 more Type/Mode selectors from block-placement
2903
+ // captures. PEQ (pidLow=0x36) and Rotary (pidLow=0x56) are also confirmed
2904
+ // block addresses but have no Type enum — their params will be added when
2905
+ // we start supporting specific knob names.
2906
+ // HW-032 (Session 30 cont 8, 2026-04-25): wire-verified at +12 dB on
2907
+ // a Low-Pass filter — `session-32-filter-extended.pcapng`. Follows
2908
+ // the universal pidHigh=0x0000 Level pattern.
2909
+ 'filter.level': {
2910
+ block: 'filter', name: 'level',
2911
+ pidLow: 0x0072, pidHigh: 0x0000,
2912
+ unit: 'db', displayMin: -80, displayMax: 20,
2913
+ },
2914
+ 'filter.mix': {
2915
+ block: 'filter', name: 'mix',
2916
+ pidLow: 0x0072, pidHigh: 0x0001,
2917
+ unit: 'percent', displayMin: 0, displayMax: 100,
2918
+ },
2919
+ 'filter.type': {
2920
+ block: 'filter', name: 'type',
2921
+ pidLow: 0x0072, pidHigh: 0x000a,
2922
+ unit: 'enum', displayMin: 0, displayMax: 17,
2923
+ enumValues: FILTER_TYPES_VALUES,
2924
+ },
2925
+ // renamed for UI-label match (audit row: FILTER 11)
2926
+ 'filter.frequency': {
2927
+ // Blocks Guide §Filter: Frequency is the filter cutoff. 20..20000 Hz,
2928
+ // c=1 raw (uses 'hz' unit). HW-024 (Session 30 cont 3): wire-verified
2929
+ // on Low-Pass at 1250 Hz; readback was 1249.9 Hz. The 0.1 Hz drift is
2930
+ // float→fixed-point quantization noise in the firmware (8e-5 relative
2931
+ // error), not a wire-layer encoding bug — drift scales with frequency.
2932
+ // Functionally inaudible; do not assume exact equality on round-trip
2933
+ // when comparing presets that differ only in filter.frequency.
2934
+ block: 'filter', name: 'frequency',
2935
+ displayLabel: 'Frequency',
2936
+ pidLow: 0x0072, pidHigh: 0x000b,
2937
+ unit: 'hz', displayMin: 20, displayMax: 20000,
2938
+ },
2939
+ // HW-032 (Session 30 cont 8, 2026-04-25): filter Config-page cuts.
2940
+ // Wire-verified at 100 Hz / 1800 Hz on a Low-Pass filter
2941
+ // (`session-32-filter-extended.pcapng`). Cache c=1 raw Hz at ids
2942
+ // 18 / 19. Mirrored from CACHE_PARAMS so the type-check picks them up.
2943
+ 'filter.low_cut': {
2944
+ block: 'filter', name: 'low_cut',
2945
+ displayLabel: 'Low Cut',
2946
+ pidLow: 0x0072, pidHigh: 0x0012,
2947
+ unit: 'hz', displayMin: 20, displayMax: 2000,
2948
+ },
2949
+ 'filter.high_cut': {
2950
+ block: 'filter', name: 'high_cut',
2951
+ displayLabel: 'High Cut',
2952
+ pidLow: 0x0072, pidHigh: 0x0013,
2953
+ unit: 'hz', displayMin: 200, displayMax: 20000,
2954
+ },
2955
+ // HW-034 (Session 33, 2026-04-26): All-Pass filter Config-page
2956
+ // residuals — `session-33-filter-extended.pcapng`. Wire-verified
2957
+ // at 13% Feedback / 4-pole Order. Feedback cache signature
2958
+ // (a=-1, b=1, c=100) is bipolar_percent ±100 (All-Pass feedback
2959
+ // can invert phase). Order is an integer pole count 1..12 — cache
2960
+ // typecode=0x0010 with c=1 raw. AM4-Edit's UI dropdown limits the
2961
+ // exposed options per filter type (All-Pass shows 2/4/6/8/10/12;
2962
+ // Low-Pass shows 2/4 only at cache id=14), but the wire register
2963
+ // accepts any integer in the cache range.
2964
+ 'filter.feedback': {
2965
+ block: 'filter', name: 'feedback',
2966
+ displayLabel: 'Feedback',
2967
+ pidLow: 0x0072, pidHigh: 0x0015,
2968
+ unit: 'bipolar_percent', displayMin: -100, displayMax: 100,
2969
+ },
2970
+ 'filter.order': {
2971
+ block: 'filter', name: 'order',
2972
+ displayLabel: 'Order',
2973
+ pidLow: 0x0072, pidHigh: 0x001c,
2974
+ unit: 'count', displayMin: 1, displayMax: 12,
2975
+ },
2976
+ 'tremolo.mix': {
2977
+ block: 'tremolo', name: 'mix',
2978
+ pidLow: 0x006a, pidHigh: 0x0001,
2979
+ unit: 'percent', displayMin: 0, displayMax: 100,
2980
+ },
2981
+ 'tremolo.type': {
2982
+ block: 'tremolo', name: 'type',
2983
+ pidLow: 0x006a, pidHigh: 0x000a,
2984
+ unit: 'enum', displayMin: 0, displayMax: 6,
2985
+ enumValues: TREMOLO_TYPES_VALUES,
2986
+ },
2987
+ 'tremolo.rate': {
2988
+ block: 'tremolo', name: 'rate',
2989
+ displayLabel: 'Rate',
2990
+ pidLow: 0x006a, pidHigh: 0x000c,
2991
+ unit: 'hz', displayMin: 0.2, displayMax: 20,
2992
+ },
2993
+ 'tremolo.depth': {
2994
+ block: 'tremolo', name: 'depth',
2995
+ displayLabel: 'Depth',
2996
+ pidLow: 0x006a, pidHigh: 0x000d,
2997
+ unit: 'percent', displayMin: 0, displayMax: 100,
2998
+ },
2999
+ // HW-022 (Session 31, 2026-04-26): wire-verified on Panner-type
3000
+ // tremolo — `session-30-tremolo-basic.pcapng`. Tremolo's first page
3001
+ // is type-dependent: Panner exposes Width / Phase / Center / Ducking
3002
+ // / Waveform (instead of VCA Trem's Depth which lives at pidHigh
3003
+ // 0x000d). Level (pidHigh=0x0000) wasn't moved in this capture — to
3004
+ // be added when a future capture wiggles it.
3005
+ 'tremolo.waveform': {
3006
+ block: 'tremolo', name: 'waveform',
3007
+ displayLabel: 'Waveform',
3008
+ pidLow: 0x006a, pidHigh: 0x000b,
3009
+ // Cache id=11 enum: 10-entry LFO_WAVEFORMS — SINE / TRIANGLE /
3010
+ // SQUARE / SAW UP / SAW DOWN / RANDOM / LOG / EXP / TRAPEZOID /
3011
+ // ASTABLE. Shared dictionary across modulation blocks (extracted
3012
+ // from chorus/id=18; cross-checked against flanger/phaser/tremolo).
3013
+ unit: 'enum', displayMin: 0, displayMax: 9,
3014
+ enumValues: LFO_WAVEFORMS_VALUES,
3015
+ },
3016
+ 'tremolo.phase': {
3017
+ block: 'tremolo', name: 'phase',
3018
+ displayLabel: 'Phase',
3019
+ pidLow: 0x006a, pidHigh: 0x0010,
3020
+ // Cache id=16: float a=0 b=π c=180/π → display 0..180 deg.
3021
+ unit: 'degrees', displayMin: 0, displayMax: 180,
3022
+ },
3023
+ 'tremolo.width': {
3024
+ block: 'tremolo', name: 'width',
3025
+ displayLabel: 'Width',
3026
+ pidLow: 0x006a, pidHigh: 0x0011,
3027
+ // Cache id=17: float a=0 b=4 c=100 — internal range allows up to
3028
+ // display 400, but AM4-Edit's Panner Width slider visually caps at
3029
+ // 100. Stay at 0..100 here; widen if a future capture proves
3030
+ // values >100 are user-reachable.
3031
+ unit: 'percent', displayMin: 0, displayMax: 100,
3032
+ },
3033
+ 'tremolo.center': {
3034
+ block: 'tremolo', name: 'center',
3035
+ displayLabel: 'Center',
3036
+ pidLow: 0x006a, pidHigh: 0x0012,
3037
+ // Cache id=18: float a=-1 b=1 c=100 → display -100..+100. Panner
3038
+ // center-pan position; 0 = dead center, ±100 = full L/R.
3039
+ unit: 'bipolar_percent', displayMin: -100, displayMax: 100,
3040
+ },
3041
+ 'tremolo.ducking': {
3042
+ block: 'tremolo', name: 'ducking',
3043
+ displayLabel: 'Ducking',
3044
+ pidLow: 0x006a, pidHigh: 0x0018,
3045
+ // Cache id=24: float a=0 b=1 c=10 → display 0..10.
3046
+ unit: 'knob_0_10', displayMin: 0, displayMax: 10,
3047
+ },
3048
+ // HW-037 (Session 35, 2026-04-29): Enhancer Config-page knobs from
3049
+ // session-33-enhancer-extended.pcapng + paired AM4-Edit screenshot.
3050
+ // Wire-verified on a Modern enhancer at level=-6 dB / width=33% /
3051
+ // depth=11% / low_cut=22.2 Hz / high_cut=6500 Hz. Level is the
3052
+ // universal pidHigh=0x0000 out-of-band pattern (no cache record);
3053
+ // width/depth/low_cut/high_cut are mirrored from CACHE_PARAMS so
3054
+ // the type-check picks them up.
3055
+ 'enhancer.level': {
3056
+ block: 'enhancer', name: 'level',
3057
+ pidLow: 0x007a, pidHigh: 0x0000,
3058
+ unit: 'db', displayMin: -80, displayMax: 20,
3059
+ },
3060
+ 'enhancer.width': {
3061
+ block: 'enhancer', name: 'width',
3062
+ displayLabel: 'Width',
3063
+ pidLow: 0x007a, pidHigh: 0x000a,
3064
+ unit: 'percent', displayMin: 0, displayMax: 100,
3065
+ },
3066
+ 'enhancer.depth': {
3067
+ block: 'enhancer', name: 'depth',
3068
+ displayLabel: 'Depth',
3069
+ pidLow: 0x007a, pidHigh: 0x000b,
3070
+ unit: 'percent', displayMin: 0, displayMax: 100,
3071
+ },
3072
+ 'enhancer.low_cut': {
3073
+ block: 'enhancer', name: 'low_cut',
3074
+ displayLabel: 'Low Cut',
3075
+ pidLow: 0x007a, pidHigh: 0x000c,
3076
+ unit: 'hz', displayMin: 20, displayMax: 2000,
3077
+ },
3078
+ 'enhancer.high_cut': {
3079
+ block: 'enhancer', name: 'high_cut',
3080
+ displayLabel: 'High Cut',
3081
+ pidLow: 0x007a, pidHigh: 0x000d,
3082
+ unit: 'hz', displayMin: 200, displayMax: 20000,
3083
+ },
3084
+ // HW-024 (Session 30 cont 3) finding F1 — `enhancer.mix` is a phantom
3085
+ // register on the AM4 hardware display. The Enhancer block exposes
3086
+ // Width / Phase Invert / Pan Left / Pan Right / Balance / Level on
3087
+ // its UI pages — no Mix knob anywhere. Wire writes still ack (the
3088
+ // SET_PARAM goes through and the firmware accepts it), but the
3089
+ // parameter likely has no audible effect. Cache id=1 has the same
3090
+ // signature as every other block's `mix` (percent, c=100), which is
3091
+ // why P1-010 Session B registered it via the universal Mix-Page rule.
3092
+ // Keep registered for now but treat as "wire-acked, no observed
3093
+ // hardware effect" — pending an audio-effect spot-check (queued
3094
+ // under HW-032 follow-ups).
3095
+ 'enhancer.mix': {
3096
+ block: 'enhancer', name: 'mix',
3097
+ pidLow: 0x007a, pidHigh: 0x0001,
3098
+ unit: 'percent', displayMin: 0, displayMax: 100,
3099
+ },
3100
+ // HW-024 (Session 30 cont 3): wire-verified — type "Classic" displayed
3101
+ // exactly. AM4-Edit labels this "Mode" on the dropdown but we keep
3102
+ // `type` for consistency across blocks.
3103
+ 'enhancer.type': {
3104
+ block: 'enhancer', name: 'type',
3105
+ pidLow: 0x007a, pidHigh: 0x000e,
3106
+ unit: 'enum', displayMin: 0, displayMax: 2,
3107
+ enumValues: ENHANCER_TYPES_VALUES,
3108
+ },
3109
+ // HW-024 (Session 30 cont 3): wire-verified — Modern Gate displayed
3110
+ // exactly. Round 4 first-time test for this block type.
3111
+ 'gate.type': {
3112
+ block: 'gate', name: 'type',
3113
+ pidLow: 0x0092, pidHigh: 0x0013,
3114
+ unit: 'enum', displayMin: 0, displayMax: 3,
3115
+ enumValues: GATE_TYPES_VALUES,
3116
+ },
3117
+ // HW-035 (Session 34, 2026-04-26): slot-Gate first-page knobs on a
3118
+ // Modern Gate type — `session-34-slotgate-extended.pcapng`. Wire-
3119
+ // verified at Threshold=-22 dB / Attack=1 ms / Hold=80 ms /
3120
+ // Release=90 ms / Attenuation=-33 dB / Sidechain=INPUT 1 / Level=
3121
+ // 12 dB. Threshold/Attack/Hold/Release/Attenuation are mirrored
3122
+ // from CACHE_PARAMS. Level (pidHigh=0x0000) follows the universal
3123
+ // out-of-band Level pattern. Sidechain (pidHigh=0x000f) is a
3124
+ // 4-entry enum sourced directly from cache id=15 enum strings
3125
+ // (BLOCK L+R / INPUT 1 / BLOCK L / BLOCK R) — hand-authored
3126
+ // because the cache generator only attaches the block-wide
3127
+ // GATE_TYPES_VALUES import.
3128
+ 'gate.level': {
3129
+ block: 'gate', name: 'level',
3130
+ pidLow: 0x0092, pidHigh: 0x0000,
3131
+ unit: 'db', displayMin: -80, displayMax: 20,
3132
+ },
3133
+ 'gate.threshold': {
3134
+ block: 'gate', name: 'threshold',
3135
+ displayLabel: 'Threshold',
3136
+ pidLow: 0x0092, pidHigh: 0x000a,
3137
+ unit: 'db', displayMin: -100, displayMax: 0,
3138
+ },
3139
+ 'gate.attack': {
3140
+ block: 'gate', name: 'attack',
3141
+ displayLabel: 'Attack',
3142
+ pidLow: 0x0092, pidHigh: 0x000b,
3143
+ unit: 'ms', displayMin: 0, displayMax: 1000, scaling: 'log10',
3144
+ },
3145
+ 'gate.hold': {
3146
+ block: 'gate', name: 'hold',
3147
+ displayLabel: 'Hold',
3148
+ pidLow: 0x0092, pidHigh: 0x000c,
3149
+ unit: 'ms', displayMin: 0, displayMax: 1000, scaling: 'log10',
3150
+ },
3151
+ 'gate.release': {
3152
+ block: 'gate', name: 'release',
3153
+ displayLabel: 'Release',
3154
+ pidLow: 0x0092, pidHigh: 0x000d,
3155
+ unit: 'ms', displayMin: 0, displayMax: 1000, scaling: 'log10',
3156
+ },
3157
+ 'gate.sidechain': {
3158
+ block: 'gate', name: 'sidechain',
3159
+ displayLabel: 'Sidechain',
3160
+ pidLow: 0x0092, pidHigh: 0x000f,
3161
+ unit: 'enum', displayMin: 0, displayMax: 3,
3162
+ enumValues: { 0: 'BLOCK L+R', 1: 'INPUT 1', 2: 'BLOCK L', 3: 'BLOCK R' },
3163
+ },
3164
+ 'gate.attenuation': {
3165
+ block: 'gate', name: 'attenuation',
3166
+ displayLabel: 'Attenuation',
3167
+ pidLow: 0x0092, pidHigh: 0x0014,
3168
+ unit: 'db', displayMin: -80, displayMax: 0,
3169
+ },
3170
+ // HW-043 partial (Session 44, 2026-05-02) — slot-Gate Modern Expander
3171
+ // Expert-Edit page from `session-44-gate-expert.{pcapng,png}`. 7 new
3172
+ // first-page registrations: ratio, sidechain_low_cut, sidechain_high_cut,
3173
+ // bypass_mode, knee_type, detector_type, mix-phantom. Modern Expander
3174
+ // exposes Ratio at 0x000e (replaces the fixed Attenuation that Modern
3175
+ // Gate exposes at 0x0014); same firmware register surface, different
3176
+ // type-dependent UI. Knee_type vs detector_type pidHigh assignment
3177
+ // disambiguated via single-knob-isolation capture
3178
+ // `session-46-gate-knee-isolation.pcapng` — 0x0016 moved (knee_type),
3179
+ // 0x0015 stayed (detector_type by elimination). Ratio range 1..20
3180
+ // founder-confirmed at the device. See docs/audit-output/gate.md for
3181
+ // the full audit table.
3182
+ 'gate.ratio': {
3183
+ block: 'gate', name: 'ratio',
3184
+ displayLabel: 'Ratio',
3185
+ pidLow: 0x0092, pidHigh: 0x000e,
3186
+ unit: 'ratio', displayMin: 1, displayMax: 20, scaling: 'log10',
3187
+ },
3188
+ 'gate.sidechain_low_cut': {
3189
+ block: 'gate', name: 'sidechain_low_cut',
3190
+ displayLabel: 'Low Cut',
3191
+ pidLow: 0x0092, pidHigh: 0x0010,
3192
+ unit: 'hz', displayMin: 20, displayMax: 2000,
3193
+ },
3194
+ 'gate.sidechain_high_cut': {
3195
+ block: 'gate', name: 'sidechain_high_cut',
3196
+ displayLabel: 'High Cut',
3197
+ pidLow: 0x0092, pidHigh: 0x0011,
3198
+ unit: 'hz', displayMin: 200, displayMax: 20000,
3199
+ },
3200
+ 'gate.bypass_mode': {
3201
+ block: 'gate', name: 'bypass_mode',
3202
+ pidLow: 0x0092, pidHigh: 0x0004,
3203
+ unit: 'enum', displayMin: 0, displayMax: 1,
3204
+ enumValues: { 0: 'Thru', 1: 'Mute' },
3205
+ },
3206
+ // Detector type at 0x0015: wire 0 displayed as "RMS" in Session 44
3207
+ // Modern Expander capture. Likely 2-entry enum {0:RMS, 1:Peak} — only
3208
+ // the observed entry is registered until founder confirms the full
3209
+ // table.
3210
+ 'gate.detector_type': {
3211
+ block: 'gate', name: 'detector_type',
3212
+ displayLabel: 'Detector Type',
3213
+ pidLow: 0x0092, pidHigh: 0x0015,
3214
+ unit: 'enum', displayMin: 0, displayMax: 1,
3215
+ enumValues: { 0: 'RMS' },
3216
+ },
3217
+ // Knee type at 0x0016: wire 4 displayed as "Soft" in Session 44.
3218
+ // Likely 5-entry enum {0:Hard, 1:Med Hard, 2:Med, 3:Med Soft, 4:Soft}
3219
+ // per typical compressor/gate UX — only the observed entry is
3220
+ // registered until founder confirms the full table.
3221
+ // renamed for UI-label match (audit row: GATE 22)
3222
+ 'gate.knee': {
3223
+ block: 'gate', name: 'knee',
3224
+ displayLabel: 'Knee',
3225
+ pidLow: 0x0092, pidHigh: 0x0016,
3226
+ unit: 'enum', displayMin: 0, displayMax: 4,
3227
+ enumValues: { 4: 'Soft' },
3228
+ },
3229
+ // Phantom register: AM4-Edit displays "NA" for Mix on the Gate block
3230
+ // (gate is a dynamics block; absorb-vs-effect doesn't apply). Wire
3231
+ // still ack'd but no audible effect — same status as `enhancer.mix`
3232
+ // (HW-024 finding F1). Registered for completeness so the agent
3233
+ // doesn't surface it as a tweak target on its own.
3234
+ 'gate.mix': {
3235
+ block: 'gate', name: 'mix',
3236
+ pidLow: 0x0092, pidHigh: 0x0001,
3237
+ unit: 'percent', displayMin: 0, displayMax: 100,
3238
+ },
3239
+ // HW-024 (Session 30 cont 3): wire-verified — Auto-Swell displayed
3240
+ // exactly. Round 4 first-time test for this block type.
3241
+ 'volpan.mode': {
3242
+ // Block is "Volume/Pan"; this is the Volume-vs-Auto-Swell selector.
3243
+ block: 'volpan', name: 'mode',
3244
+ pidLow: 0x0066, pidHigh: 0x000f,
3245
+ unit: 'enum', displayMin: 0, displayMax: 1,
3246
+ enumValues: VOLPAN_MODES_VALUES,
3247
+ },
3248
+ // HW-032 (Session 30 cont 8, 2026-04-25): Volume/Pan Auto-Swell
3249
+ // envelope params. Wire-verified at -20 dB / 300 ms on the Auto-Swell
3250
+ // type (`session-32-volpan-extended.pcapng`). Cache ids 16 / 17 with
3251
+ // c=1 (raw dB) and c=1000 (display ms) respectively. Mirrored from
3252
+ // CACHE_PARAMS so the type-check picks them up.
3253
+ 'volpan.threshold': {
3254
+ block: 'volpan', name: 'threshold',
3255
+ displayLabel: 'Threshold',
3256
+ pidLow: 0x0066, pidHigh: 0x0010,
3257
+ unit: 'db', displayMin: -100, displayMax: 0,
3258
+ },
3259
+ 'volpan.attack': {
3260
+ block: 'volpan', name: 'attack',
3261
+ displayLabel: 'Attack',
3262
+ pidLow: 0x0066, pidHigh: 0x0011,
3263
+ unit: 'ms', displayMin: 1, displayMax: 5000, scaling: 'log10',
3264
+ },
3265
+ // HW-032 (Session 30 cont 8, 2026-04-25): wire-verified at +12 dB on
3266
+ // an Auto-Swell Volume/Pan — `session-32-volpan-extended.pcapng`.
3267
+ // Follows the universal pidHigh=0x0000 Level pattern.
3268
+ 'volpan.level': {
3269
+ block: 'volpan', name: 'level',
3270
+ pidLow: 0x0066, pidHigh: 0x0000,
3271
+ unit: 'db', displayMin: -80, displayMax: 20,
3272
+ },
3273
+ // HW-043 partial (Session 44, 2026-05-02) — Volume/Pan Expert-Edit
3274
+ // captures from `session-44-volpan-expert-{volume,autoswell}.{pcapng,png}`.
3275
+ // Confirmed type-dependent UI on volpan: Volume mode exposes
3276
+ // volume/pan_l/pan_r at 0x000a/c/d; Auto-Swell mode exposes
3277
+ // release/hysteresis at 0x0012/0x0013. Both modes share level/balance/
3278
+ // mix/bypass_mode/taper/input_select at the same pidHighs. Type-
3279
+ // agnostic firmware addressing — same pattern as gate. Hysteresis
3280
+ // range 0..12 dB founder-confirmed at the device. See
3281
+ // docs/audit-output/volpan-{volume,autoswell}.md for the full audit
3282
+ // tables.
3283
+ 'volpan.volume': {
3284
+ block: 'volpan', name: 'volume',
3285
+ displayLabel: 'Volume',
3286
+ pidLow: 0x0066, pidHigh: 0x000a,
3287
+ unit: 'knob_0_10', displayMin: 0, displayMax: 10,
3288
+ },
3289
+ // renamed for UI-label match (audit row: VOLUME 12)
3290
+ 'volpan.pan_left': {
3291
+ block: 'volpan', name: 'pan_left',
3292
+ displayLabel: 'Pan Left',
3293
+ pidLow: 0x0066, pidHigh: 0x000c,
3294
+ unit: 'bipolar_percent', displayMin: -100, displayMax: 100,
3295
+ },
3296
+ // renamed for UI-label match (audit row: VOLUME 13)
3297
+ 'volpan.pan_right': {
3298
+ block: 'volpan', name: 'pan_right',
3299
+ displayLabel: 'Pan Right',
3300
+ pidLow: 0x0066, pidHigh: 0x000d,
3301
+ unit: 'bipolar_percent', displayMin: -100, displayMax: 100,
3302
+ },
3303
+ // Taper at 0x000b: shared register across Volume + Auto-Swell modes
3304
+ // with type-aware enum entries. Volume mode wire=5 → "Log 50";
3305
+ // Auto-Swell wire=1 → "Log 30A". Only observed entries registered
3306
+ // until founder confirms the full table.
3307
+ 'volpan.taper': {
3308
+ block: 'volpan', name: 'taper',
3309
+ displayLabel: 'Taper',
3310
+ pidLow: 0x0066, pidHigh: 0x000b,
3311
+ unit: 'enum', displayMin: 0, displayMax: 10,
3312
+ enumValues: { 1: 'Log 30A', 5: 'Log 50' },
3313
+ },
3314
+ // Input Select at 0x000e: at minimum 3 entries observed across both
3315
+ // mode captures (wire=0 → "Stereo", wire=2 → "Right Only"). Index 1
3316
+ // unobserved but plausibly "Left Only". Full enum table needs founder
3317
+ // confirmation; registered with the partial mapping.
3318
+ 'volpan.input_select': {
3319
+ block: 'volpan', name: 'input_select',
3320
+ displayLabel: 'Input Select',
3321
+ pidLow: 0x0066, pidHigh: 0x000e,
3322
+ unit: 'enum', displayMin: 0, displayMax: 2,
3323
+ enumValues: { 0: 'Stereo', 2: 'Right Only' },
3324
+ },
3325
+ 'volpan.bypass_mode': {
3326
+ block: 'volpan', name: 'bypass_mode',
3327
+ pidLow: 0x0066, pidHigh: 0x0004,
3328
+ unit: 'enum', displayMin: 0, displayMax: 1,
3329
+ enumValues: { 0: 'Thru', 1: 'Mute' },
3330
+ },
3331
+ // Auto-Swell-mode envelope params. Release mirrors attack at 0x0011
3332
+ // (same ms-stored-as-seconds + log10 scaling). Hysteresis is a dB
3333
+ // knob unique to Auto-Swell — no Volume-mode equivalent. Range
3334
+ // 0..12 dB founder-confirmed.
3335
+ 'volpan.release': {
3336
+ block: 'volpan', name: 'release',
3337
+ displayLabel: 'Release',
3338
+ pidLow: 0x0066, pidHigh: 0x0012,
3339
+ unit: 'ms', displayMin: 1, displayMax: 5000, scaling: 'log10',
3340
+ },
3341
+ 'volpan.hysteresis': {
3342
+ block: 'volpan', name: 'hysteresis',
3343
+ displayLabel: 'Hysteresis',
3344
+ pidLow: 0x0066, pidHigh: 0x0013,
3345
+ unit: 'db', displayMin: 0, displayMax: 12,
3346
+ },
3347
+ // Phantom register: AM4-Edit displays "NA" for Mix on Volume/Pan
3348
+ // (volpan is a signal-flow / routing block; absorb-vs-effect doesn't
3349
+ // apply). Wire still ack'd but no audible effect — same status as
3350
+ // `gate.mix` and `enhancer.mix`.
3351
+ 'volpan.mix': {
3352
+ block: 'volpan', name: 'mix',
3353
+ pidLow: 0x0066, pidHigh: 0x0001,
3354
+ unit: 'percent', displayMin: 0, displayMax: 100,
3355
+ },
3356
+ // Input Noise Gate (HW-032, Session 30 cont 8). Always-on input stage
3357
+ // (per docs/BLOCK-PARAMS.md "Input Noise Gate (global, not a block
3358
+ // slot)"); not placeable in any of the 4 effect slots. Distinct from
3359
+ // the slot-placeable Gate effect block (pidLow=0x0092).
3360
+ // Wire-verified on `session-32-gate-extended.pcapng` against the
3361
+ // AM4-Edit "In-Gate" tab on Z04. Captured 4 distinct registers
3362
+ // (0x00 / 0x0a / 0x0c / 0x0f); `level` is the only one with a
3363
+ // unit-clean encoding so far. Threshold (0x0a, internal 0..1 →
3364
+ // display -100..0 dB), Release (0x0c, curve TBD) and Type (0x0f,
3365
+ // enum: Classic / Intelligent / Noise Reducer per the manual) need
3366
+ // a Unit-extension pass plus a type-walk capture and are queued as
3367
+ // HW-034. Pidlow 0x0025 has no cache backing — input gate isn't in
3368
+ // any of the 17 cache sub-blocks (none of the section 2 candidates
3369
+ // match its 4-register footprint).
3370
+ 'ingate.level': {
3371
+ block: 'ingate', name: 'level',
3372
+ pidLow: 0x0025, pidHigh: 0x0000,
3373
+ unit: 'db', displayMin: -80, displayMax: 20,
3374
+ },
3375
+ // HW-036 (Session 34, 2026-04-26): In-Gate Config-page residuals
3376
+ // from `session-34-inputgate-extended.pcapng`. Wire-verified at
3377
+ // Threshold=-44 dB / Release=60 ms / Type=Intelligent on the
3378
+ // In-Gate tab. All three were the HW-032 residuals queued for
3379
+ // unit + type-walk. Threshold curve is dB-direct (not the 0..1
3380
+ // normalized hypothesis from HW-032 — hardware writes raw dB).
3381
+ // Release uses the same display=internal × 1000 ms scaling as
3382
+ // every other release-style param. Type enum order matches
3383
+ // BLOCK-PARAMS.md (Classic Expander / Intelligent / Noise
3384
+ // Reducer); wire confirmed index 1 = Intelligent. No cache
3385
+ // backing — all hand-authored.
3386
+ 'ingate.threshold': {
3387
+ block: 'ingate', name: 'threshold',
3388
+ displayLabel: 'Threshold',
3389
+ pidLow: 0x0025, pidHigh: 0x000a,
3390
+ unit: 'db', displayMin: -100, displayMax: 0,
3391
+ },
3392
+ 'ingate.release': {
3393
+ block: 'ingate', name: 'release',
3394
+ displayLabel: 'Release',
3395
+ pidLow: 0x0025, pidHigh: 0x000c,
3396
+ unit: 'ms', displayMin: 0, displayMax: 1000,
3397
+ },
3398
+ 'ingate.type': {
3399
+ block: 'ingate', name: 'type',
3400
+ displayLabel: 'Gate Type',
3401
+ pidLow: 0x0025, pidHigh: 0x000f,
3402
+ unit: 'enum', displayMin: 0, displayMax: 2,
3403
+ enumValues: { 0: 'Classic Expander', 1: 'Intelligent', 2: 'Noise Reducer' },
3404
+ },
3405
+ // Universal per-block output Balance (Session 28 cont — P1-010
3406
+ // second unit-extension pass, introduced `bipolar_percent`).
3407
+ // Blocks Guide line 347: "Every block outputs both left and right
3408
+ // signals. As you adjust to the left or right, the opposite channel
3409
+ // [is reduced]." Confirmed as a universal block-level parameter at
3410
+ // lines 899 (Amp), 1233 (Chorus), 1430 (Flanger), 1733 (Delay),
3411
+ // 1883 (Phaser). Cache signature is identical across all 15
3412
+ // confirmed blocks: id=2, a=-1, b=1, c=100 (display = internal ×
3413
+ // 100, so -100..+100%).
3414
+ //
3415
+ // Hardware-display visibility per block (HW-014 + HW-024 finding F2):
3416
+ // visible: enhancer.balance (HW-024 at -33%), geq.balance (HW-014
3417
+ // at -67), volpan.balance is type-specific to the Pan range —
3418
+ // classified as an effect-block balance below.
3419
+ // hidden (wire-acked, no display readout): amp / compressor /
3420
+ // reverb / delay / chorus / flanger / phaser / wah / tremolo /
3421
+ // filter / drive / gate / volpan.
3422
+ // Visibility is block-type-dependent — the enhancer is a stereo
3423
+ // utility block where balance/pan controls are core, while effect
3424
+ // blocks treat balance as a hidden output mixer. Hidden writes still
3425
+ // affect the stereo image at the audio path (per Blocks Guide line
3426
+ // 347 — universal at the firmware level); audio-effect spot-check
3427
+ // queued under HW-032.
3428
+ 'amp.balance': { block: 'amp', name: 'balance', pidLow: 0x003a, pidHigh: 0x0002, unit: 'bipolar_percent', displayMin: -100, displayMax: 100 },
3429
+ 'compressor.balance': { block: 'compressor', name: 'balance', pidLow: 0x002e, pidHigh: 0x0002, unit: 'bipolar_percent', displayMin: -100, displayMax: 100 },
3430
+ 'geq.balance': { block: 'geq', name: 'balance', pidLow: 0x0032, pidHigh: 0x0002, unit: 'bipolar_percent', displayMin: -100, displayMax: 100 },
3431
+ 'reverb.balance': { block: 'reverb', name: 'balance', pidLow: 0x0042, pidHigh: 0x0002, unit: 'bipolar_percent', displayMin: -100, displayMax: 100 },
3432
+ 'delay.balance': { block: 'delay', name: 'balance', pidLow: 0x0046, pidHigh: 0x0002, unit: 'bipolar_percent', displayMin: -100, displayMax: 100 },
3433
+ 'chorus.balance': { block: 'chorus', name: 'balance', pidLow: 0x004e, pidHigh: 0x0002, unit: 'bipolar_percent', displayMin: -100, displayMax: 100 },
3434
+ 'flanger.balance': { block: 'flanger', name: 'balance', pidLow: 0x0052, pidHigh: 0x0002, unit: 'bipolar_percent', displayMin: -100, displayMax: 100 },
3435
+ 'phaser.balance': { block: 'phaser', name: 'balance', pidLow: 0x005a, pidHigh: 0x0002, unit: 'bipolar_percent', displayMin: -100, displayMax: 100 },
3436
+ 'wah.balance': { block: 'wah', name: 'balance', pidLow: 0x005e, pidHigh: 0x0002, unit: 'bipolar_percent', displayMin: -100, displayMax: 100 },
3437
+ 'tremolo.balance': { block: 'tremolo', name: 'balance', pidLow: 0x006a, pidHigh: 0x0002, unit: 'bipolar_percent', displayMin: -100, displayMax: 100 },
3438
+ 'filter.balance': { block: 'filter', name: 'balance', pidLow: 0x0072, pidHigh: 0x0002, unit: 'bipolar_percent', displayMin: -100, displayMax: 100 },
3439
+ 'drive.balance': { block: 'drive', name: 'balance', pidLow: 0x0076, pidHigh: 0x0002, unit: 'bipolar_percent', displayMin: -100, displayMax: 100 },
3440
+ 'enhancer.balance': { block: 'enhancer', name: 'balance', pidLow: 0x007a, pidHigh: 0x0002, unit: 'bipolar_percent', displayMin: -100, displayMax: 100 },
3441
+ 'gate.balance': { block: 'gate', name: 'balance', pidLow: 0x0092, pidHigh: 0x0002, unit: 'bipolar_percent', displayMin: -100, displayMax: 100 },
3442
+ 'volpan.balance': { block: 'volpan', name: 'balance', pidLow: 0x0066, pidHigh: 0x0002, unit: 'bipolar_percent', displayMin: -100, displayMax: 100 },
3443
+ // HW-040 (Session 36, 2026-04-29): peq + rotary + geq + wah balance
3444
+ // mirrors. Plus the new-block universal mix entries.
3445
+ 'peq.balance': { block: 'peq', name: 'balance', pidLow: 0x0036, pidHigh: 0x0002, unit: 'bipolar_percent', displayMin: -100, displayMax: 100 },
3446
+ 'rotary.balance': { block: 'rotary', name: 'balance', pidLow: 0x0056, pidHigh: 0x0002, unit: 'bipolar_percent', displayMin: -100, displayMax: 100 },
3447
+ 'wah.mix': { block: 'wah', name: 'mix', pidLow: 0x005e, pidHigh: 0x0001, unit: 'percent', displayMin: 0, displayMax: 100 },
3448
+ 'peq.mix': { block: 'peq', name: 'mix', pidLow: 0x0036, pidHigh: 0x0001, unit: 'percent', displayMin: 0, displayMax: 100 },
3449
+ 'rotary.mix': { block: 'rotary', name: 'mix', pidLow: 0x0056, pidHigh: 0x0001, unit: 'percent', displayMin: 0, displayMax: 100 },
3450
+ // GEQ Expert-Edit 10-band mirrors + master_q.
3451
+ 'geq.mix': { block: 'geq', name: 'mix', pidLow: 0x0032, pidHigh: 0x0001, unit: 'percent', displayMin: 0, displayMax: 100 },
3452
+ 'geq.band_1': { block: 'geq', name: 'band_1', displayLabel: '31', pidLow: 0x0032, pidHigh: 0x000a, unit: 'db', displayMin: -12, displayMax: 12 },
3453
+ 'geq.band_2': { block: 'geq', name: 'band_2', displayLabel: '63', pidLow: 0x0032, pidHigh: 0x000b, unit: 'db', displayMin: -12, displayMax: 12 },
3454
+ 'geq.band_3': { block: 'geq', name: 'band_3', displayLabel: '125', pidLow: 0x0032, pidHigh: 0x000c, unit: 'db', displayMin: -12, displayMax: 12 },
3455
+ 'geq.band_4': { block: 'geq', name: 'band_4', displayLabel: '250', pidLow: 0x0032, pidHigh: 0x000d, unit: 'db', displayMin: -12, displayMax: 12 },
3456
+ 'geq.band_5': { block: 'geq', name: 'band_5', displayLabel: '500', pidLow: 0x0032, pidHigh: 0x000e, unit: 'db', displayMin: -12, displayMax: 12 },
3457
+ 'geq.band_6': { block: 'geq', name: 'band_6', displayLabel: '1k', pidLow: 0x0032, pidHigh: 0x000f, unit: 'db', displayMin: -12, displayMax: 12 },
3458
+ 'geq.band_7': { block: 'geq', name: 'band_7', displayLabel: '2k', pidLow: 0x0032, pidHigh: 0x0010, unit: 'db', displayMin: -12, displayMax: 12 },
3459
+ 'geq.band_8': { block: 'geq', name: 'band_8', displayLabel: '4k', pidLow: 0x0032, pidHigh: 0x0011, unit: 'db', displayMin: -12, displayMax: 12 },
3460
+ 'geq.band_9': { block: 'geq', name: 'band_9', displayLabel: '8k', pidLow: 0x0032, pidHigh: 0x0012, unit: 'db', displayMin: -12, displayMax: 12 },
3461
+ 'geq.band_10': { block: 'geq', name: 'band_10', displayLabel: '16k', pidLow: 0x0032, pidHigh: 0x0013, unit: 'db', displayMin: -12, displayMax: 12 },
3462
+ 'geq.master_q': { block: 'geq', name: 'master_q', displayLabel: 'Master Q', pidLow: 0x0032, pidHigh: 0x0015, unit: 'count', displayMin: 0.1, displayMax: 10, scaling: 'log10' },
3463
+ // PEQ 5-channel parametric EQ mirrors (frequency / Q / gain × 5 channels).
3464
+ 'peq.channel_1_frequency': { block: 'peq', name: 'channel_1_frequency', displayLabel: 'Freq 1', pidLow: 0x0036, pidHigh: 0x000a, unit: 'hz', displayMin: 20, displayMax: 2000 },
3465
+ 'peq.channel_2_frequency': { block: 'peq', name: 'channel_2_frequency', displayLabel: 'Freq 2', pidLow: 0x0036, pidHigh: 0x000b, unit: 'hz', displayMin: 100, displayMax: 10000 },
3466
+ 'peq.channel_3_frequency': { block: 'peq', name: 'channel_3_frequency', displayLabel: 'Freq 3', pidLow: 0x0036, pidHigh: 0x000c, unit: 'hz', displayMin: 100, displayMax: 10000 },
3467
+ 'peq.channel_4_frequency': { block: 'peq', name: 'channel_4_frequency', displayLabel: 'Freq 4', pidLow: 0x0036, pidHigh: 0x000d, unit: 'hz', displayMin: 100, displayMax: 10000 },
3468
+ 'peq.channel_5_frequency': { block: 'peq', name: 'channel_5_frequency', displayLabel: 'Freq 5', pidLow: 0x0036, pidHigh: 0x000e, unit: 'hz', displayMin: 200, displayMax: 20000 },
3469
+ 'peq.channel_1_q': { block: 'peq', name: 'channel_1_q', displayLabel: 'Q1', pidLow: 0x0036, pidHigh: 0x000f, unit: 'count', displayMin: 0.1, displayMax: 10, scaling: 'log10' },
3470
+ 'peq.channel_2_q': { block: 'peq', name: 'channel_2_q', displayLabel: 'Q2', pidLow: 0x0036, pidHigh: 0x0010, unit: 'count', displayMin: 0.1, displayMax: 10, scaling: 'log10' },
3471
+ 'peq.channel_3_q': { block: 'peq', name: 'channel_3_q', displayLabel: 'Q3', pidLow: 0x0036, pidHigh: 0x0011, unit: 'count', displayMin: 0.1, displayMax: 10, scaling: 'log10' },
3472
+ 'peq.channel_4_q': { block: 'peq', name: 'channel_4_q', displayLabel: 'Q4', pidLow: 0x0036, pidHigh: 0x0012, unit: 'count', displayMin: 0.1, displayMax: 10, scaling: 'log10' },
3473
+ 'peq.channel_5_q': { block: 'peq', name: 'channel_5_q', displayLabel: 'Q5', pidLow: 0x0036, pidHigh: 0x0013, unit: 'count', displayMin: 0.1, displayMax: 10, scaling: 'log10' },
3474
+ 'peq.channel_1_gain': { block: 'peq', name: 'channel_1_gain', displayLabel: 'Gain 1', pidLow: 0x0036, pidHigh: 0x0014, unit: 'db', displayMin: -20, displayMax: 20 },
3475
+ 'peq.channel_2_gain': { block: 'peq', name: 'channel_2_gain', displayLabel: 'Gain 2', pidLow: 0x0036, pidHigh: 0x0015, unit: 'db', displayMin: -20, displayMax: 20 },
3476
+ 'peq.channel_3_gain': { block: 'peq', name: 'channel_3_gain', displayLabel: 'Gain 3', pidLow: 0x0036, pidHigh: 0x0016, unit: 'db', displayMin: -20, displayMax: 20 },
3477
+ 'peq.channel_4_gain': { block: 'peq', name: 'channel_4_gain', displayLabel: 'Gain 4', pidLow: 0x0036, pidHigh: 0x0017, unit: 'db', displayMin: -20, displayMax: 20 },
3478
+ 'peq.channel_5_gain': { block: 'peq', name: 'channel_5_gain', displayLabel: 'Gain 5', pidLow: 0x0036, pidHigh: 0x0018, unit: 'db', displayMin: -20, displayMax: 20 },
3479
+ // BK-035 audit (Session 36 cont, 2026-04-29): PEQ Bypass Mode +
3480
+ // 5 per-channel Type enums + 5 per-channel Solo toggles. Founder
3481
+ // confirmed labels from `session-40-peq-expert.png`. Cache provides
3482
+ // each channel's Type enum entries (different shapes per channel —
3483
+ // e.g. Channel 3 only has [Peaking, Peaking 2] while Channels 1/5
3484
+ // have the full [Shelving, Peaking, Blocking, Shelving 2, Peaking 2]).
3485
+ 'peq.bypass_mode': {
3486
+ block: 'peq', name: 'bypass_mode',
3487
+ pidLow: 0x0036, pidHigh: 0x0004,
3488
+ unit: 'enum', displayMin: 0, displayMax: 1,
3489
+ enumValues: { 0: 'Thru', 1: 'Mute' },
3490
+ },
3491
+ 'peq.channel_1_type': {
3492
+ block: 'peq', name: 'channel_1_type',
3493
+ displayLabel: 'Type 1',
3494
+ pidLow: 0x0036, pidHigh: 0x0019,
3495
+ unit: 'enum', displayMin: 0, displayMax: 4,
3496
+ enumValues: { 0: 'Shelving', 1: 'Peaking', 2: 'Blocking', 3: 'Shelving 2', 4: 'Peaking 2' },
3497
+ },
3498
+ 'peq.channel_2_type': {
3499
+ block: 'peq', name: 'channel_2_type',
3500
+ displayLabel: 'Type 2',
3501
+ pidLow: 0x0036, pidHigh: 0x001a,
3502
+ unit: 'enum', displayMin: 0, displayMax: 3,
3503
+ enumValues: { 0: 'Peaking', 1: 'Shelving', 2: 'Shelving 2', 3: 'Peaking 2' },
3504
+ },
3505
+ 'peq.channel_3_type': {
3506
+ block: 'peq', name: 'channel_3_type',
3507
+ displayLabel: 'Type 3',
3508
+ pidLow: 0x0036, pidHigh: 0x001b,
3509
+ unit: 'enum', displayMin: 0, displayMax: 1,
3510
+ enumValues: { 0: 'Peaking', 1: 'Peaking 2' },
3511
+ },
3512
+ 'peq.channel_4_type': {
3513
+ block: 'peq', name: 'channel_4_type',
3514
+ displayLabel: 'Type 4',
3515
+ pidLow: 0x0036, pidHigh: 0x001c,
3516
+ unit: 'enum', displayMin: 0, displayMax: 3,
3517
+ enumValues: { 0: 'Peaking', 1: 'Shelving', 2: 'Shelving 2', 3: 'Peaking 2' },
3518
+ },
3519
+ 'peq.channel_5_type': {
3520
+ block: 'peq', name: 'channel_5_type',
3521
+ displayLabel: 'Type 5',
3522
+ pidLow: 0x0036, pidHigh: 0x001d,
3523
+ unit: 'enum', displayMin: 0, displayMax: 4,
3524
+ enumValues: { 0: 'Shelving', 1: 'Peaking', 2: 'Blocking', 3: 'Shelving 2', 4: 'Peaking 2' },
3525
+ },
3526
+ 'peq.channel_1_solo': {
3527
+ block: 'peq', name: 'channel_1_solo',
3528
+ displayLabel: 'Solo',
3529
+ pidLow: 0x0036, pidHigh: 0x001e,
3530
+ unit: 'enum', displayMin: 0, displayMax: 1,
3531
+ enumValues: { 0: 'OFF', 1: 'ON' },
3532
+ },
3533
+ 'peq.channel_2_solo': {
3534
+ block: 'peq', name: 'channel_2_solo',
3535
+ displayLabel: 'Solo',
3536
+ pidLow: 0x0036, pidHigh: 0x001f,
3537
+ unit: 'enum', displayMin: 0, displayMax: 1,
3538
+ enumValues: { 0: 'OFF', 1: 'ON' },
3539
+ },
3540
+ 'peq.channel_3_solo': {
3541
+ block: 'peq', name: 'channel_3_solo',
3542
+ displayLabel: 'Solo',
3543
+ pidLow: 0x0036, pidHigh: 0x0020,
3544
+ unit: 'enum', displayMin: 0, displayMax: 1,
3545
+ enumValues: { 0: 'OFF', 1: 'ON' },
3546
+ },
3547
+ 'peq.channel_4_solo': {
3548
+ block: 'peq', name: 'channel_4_solo',
3549
+ displayLabel: 'Solo',
3550
+ pidLow: 0x0036, pidHigh: 0x0021,
3551
+ unit: 'enum', displayMin: 0, displayMax: 1,
3552
+ enumValues: { 0: 'OFF', 1: 'ON' },
3553
+ },
3554
+ 'peq.channel_5_solo': {
3555
+ block: 'peq', name: 'channel_5_solo',
3556
+ displayLabel: 'Solo',
3557
+ pidLow: 0x0036, pidHigh: 0x0022,
3558
+ unit: 'enum', displayMin: 0, displayMax: 1,
3559
+ enumValues: { 0: 'OFF', 1: 'ON' },
3560
+ },
3561
+ // BK-035 audit (Session 36 cont, 2026-04-29): GEQ Bypass Mode added.
3562
+ // GEQ Level was missing too — added via paramNames.ts auto-gen path.
3563
+ 'geq.bypass_mode': {
3564
+ block: 'geq', name: 'bypass_mode',
3565
+ pidLow: 0x0032, pidHigh: 0x0004,
3566
+ unit: 'enum', displayMin: 0, displayMax: 1,
3567
+ enumValues: { 0: 'Thru', 1: 'Mute' },
3568
+ },
3569
+ 'geq.level': { block: 'geq', name: 'level', pidLow: 0x0032, pidHigh: 0x0000, unit: 'db', displayMin: -80, displayMax: 20 },
3570
+ // Rotary cabinet sim mirrors.
3571
+ // BK-035 audit (Session 36 cont, 2026-04-29): rotary block had two
3572
+ // mis-registered pidHighs (drive ↔ mic_spacing swap) plus 5 unregistered
3573
+ // user-facing knobs that the founder's screenshot dictation surfaced:
3574
+ // id 10 (was `drive` count 0..10) → `rate` (Hz, Leslie speed knob —
3575
+ // **BK-035 headline gap closed**)
3576
+ // id 21 (was `mic_spacing`) → `drive` (knob_0_10 0.5..500)
3577
+ // id 16 (NEW) → `mic_spacing` (π-encoded scale,
3578
+ // unit `rotary_mic_spacing`, 0..100)
3579
+ // id 0 (NEW) → `level` (db, -80..20)
3580
+ // id 4 (NEW) → `bypass_mode` (enum, hand-authored)
3581
+ // id 14 (NEW) → `tempo` (TEMPO_DIVISIONS_VALUES, hand-authored)
3582
+ // id 20 (NEW) → `stereo_spread` (bipolar_percent -200..200)
3583
+ // id 23 (NEW) → `input_select` (enum [L+R/LEFT/RIGHT], hand-authored)
3584
+ 'rotary.level': { block: 'rotary', name: 'level', pidLow: 0x0056, pidHigh: 0x0000, unit: 'db', displayMin: -80, displayMax: 20 },
3585
+ 'rotary.bypass_mode': {
3586
+ block: 'rotary', name: 'bypass_mode',
3587
+ pidLow: 0x0056, pidHigh: 0x0004,
3588
+ unit: 'enum', displayMin: 0, displayMax: 2,
3589
+ enumValues: { 0: 'Thru', 1: 'Mute FX Out', 2: 'Mute Out' },
3590
+ },
3591
+ 'rotary.rate': { block: 'rotary', name: 'rate', displayLabel: 'Rate', pidLow: 0x0056, pidHigh: 0x000a, unit: 'hz', displayMin: 0, displayMax: 10 },
3592
+ 'rotary.low_depth': { block: 'rotary', name: 'low_depth', displayLabel: 'Low Depth', pidLow: 0x0056, pidHigh: 0x000b, unit: 'percent', displayMin: 0, displayMax: 100 },
3593
+ 'rotary.high_depth': { block: 'rotary', name: 'high_depth', displayLabel: 'High Depth', pidLow: 0x0056, pidHigh: 0x000c, unit: 'percent', displayMin: 0, displayMax: 100 },
3594
+ 'rotary.high_level': { block: 'rotary', name: 'high_level', displayLabel: 'High Level', pidLow: 0x0056, pidHigh: 0x000d, unit: 'db', displayMin: -6, displayMax: 6 },
3595
+ 'rotary.tempo': {
3596
+ block: 'rotary', name: 'tempo',
3597
+ displayLabel: 'Tempo',
3598
+ pidLow: 0x0056, pidHigh: 0x000e,
3599
+ unit: 'enum', displayMin: 0, displayMax: 78,
3600
+ enumValues: TEMPO_DIVISIONS_VALUES,
3601
+ },
3602
+ 'rotary.rotor_length': { block: 'rotary', name: 'rotor_length', displayLabel: 'Rotor Length', pidLow: 0x0056, pidHigh: 0x000f, unit: 'percent', displayMin: 0.1, displayMax: 100 },
3603
+ 'rotary.mic_spacing': { block: 'rotary', name: 'mic_spacing', displayLabel: 'Mic Spacing', pidLow: 0x0056, pidHigh: 0x0010, unit: 'rotary_mic_spacing', displayMin: 0, displayMax: 100 },
3604
+ 'rotary.low_rate_multiplier': { block: 'rotary', name: 'low_rate_multiplier', displayLabel: 'Low Rate Multiplier', pidLow: 0x0056, pidHigh: 0x0011, unit: 'count', displayMin: 0.1, displayMax: 10, scaling: 'log10' },
3605
+ 'rotary.low_time_constant': { block: 'rotary', name: 'low_time_constant', displayLabel: 'Low Time Constant', pidLow: 0x0056, pidHigh: 0x0012, unit: 'count', displayMin: 0.1, displayMax: 10 },
3606
+ 'rotary.high_time_constant': { block: 'rotary', name: 'high_time_constant', displayLabel: 'High Time Constant', pidLow: 0x0056, pidHigh: 0x0013, unit: 'count', displayMin: 0.1, displayMax: 10 },
3607
+ 'rotary.stereo_spread': { block: 'rotary', name: 'stereo_spread', displayLabel: 'Stereo Spread', pidLow: 0x0056, pidHigh: 0x0014, unit: 'bipolar_percent', displayMin: -200, displayMax: 200 },
3608
+ 'rotary.drive': { block: 'rotary', name: 'drive', displayLabel: 'Drive', pidLow: 0x0056, pidHigh: 0x0015, unit: 'knob_0_10', displayMin: 0.5, displayMax: 500, scaling: 'log10' /* typecode 80 — HW-053 cont */ },
3609
+ 'rotary.mic_distance': { block: 'rotary', name: 'mic_distance', displayLabel: 'Mic Distance', pidLow: 0x0056, pidHigh: 0x0016, unit: 'count', displayMin: 0.01, displayMax: 1, scaling: 'log10' },
3610
+ 'rotary.input_select': {
3611
+ block: 'rotary', name: 'input_select',
3612
+ displayLabel: 'Input Select',
3613
+ pidLow: 0x0056, pidHigh: 0x0017,
3614
+ unit: 'enum', displayMin: 0, displayMax: 2,
3615
+ enumValues: { 0: 'L+R', 1: 'LEFT', 2: 'RIGHT' },
3616
+ },
3617
+ // ── Main Levels page — pidLow=0x002A — HW-067a closed Session 84 (2026-05-16) ──
3618
+ // Capture: samples/captured/session-84-levels.pcapng. AM4-Edit 2.00 +
3619
+ // AM4 firmware 2.00 use action=0x0001 (the standard write action) on
3620
+ // this register family — supersedes Session 50's tentative 0x0002.
3621
+ // Anchors from screenshot match wire 1:1: preset.level wire 1.1100 →
3622
+ // display 1.1 dB; preset.balance wire 0.0222 → display 2.2 (× 100);
3623
+ // scene levels wire 3.33/4.44/5.55/6.66 → display 3.3/4.4/5.5/6.7 dB.
3624
+ 'preset.level': {
3625
+ block: 'preset', name: 'level',
3626
+ pidLow: 0x002a, pidHigh: 0x0000,
3627
+ unit: 'db', displayMin: -80, displayMax: 20,
3628
+ },
3629
+ 'preset.balance': {
3630
+ block: 'preset', name: 'balance',
3631
+ pidLow: 0x002a, pidHigh: 0x0002,
3632
+ unit: 'bipolar_percent', displayMin: -100, displayMax: 100,
3633
+ },
3634
+ 'preset.scene_1_level': {
3635
+ block: 'preset', name: 'scene_1_level',
3636
+ pidLow: 0x002a, pidHigh: 0x0018,
3637
+ unit: 'db', displayMin: -80, displayMax: 20,
3638
+ },
3639
+ 'preset.scene_2_level': {
3640
+ block: 'preset', name: 'scene_2_level',
3641
+ pidLow: 0x002a, pidHigh: 0x0019,
3642
+ unit: 'db', displayMin: -80, displayMax: 20,
3643
+ },
3644
+ 'preset.scene_3_level': {
3645
+ block: 'preset', name: 'scene_3_level',
3646
+ pidLow: 0x002a, pidHigh: 0x001a,
3647
+ unit: 'db', displayMin: -80, displayMax: 20,
3648
+ },
3649
+ 'preset.scene_4_level': {
3650
+ block: 'preset', name: 'scene_4_level',
3651
+ pidLow: 0x002a, pidHigh: 0x001b,
3652
+ unit: 'db', displayMin: -80, displayMax: 20,
3653
+ },
3654
+ // ── PATCH family — pidLow=0x00CE (cross-references catalog case 0x3c) ──
3655
+ // Closed Session 84 (2026-05-16) via samples/captured/session-84-routing-
3656
+ // mix-midi.pcapng. Wire shape decoded directly against Ghidra's PATCH
3657
+ // catalog: paramId N → pidHigh = N (matching §6p rule for every other
3658
+ // AM4 block). Same pidLow already hosts block-placement (pidHigh=
3659
+ // 0x0010+slot-1) and preset rename — PATCH is the umbrella family that
3660
+ // covers "everything preset-scoped that isn't a block parameter."
3661
+ //
3662
+ // Confirmed wire values for routing: Series=0.0, Parallel=1.0.
3663
+ // Founder toggled FX2/3/4 routing dropdowns in AM4-Edit; each click
3664
+ // produced a clean float write whose value matches the on-screen state.
3665
+ 'preset.routing_slot_2': {
3666
+ block: 'preset', name: 'routing_slot_2',
3667
+ pidLow: 0x00ce, pidHigh: 0x0014,
3668
+ unit: 'enum', displayMin: 0, displayMax: 1,
3669
+ enumValues: { 0: 'Series', 1: 'Parallel' },
3670
+ },
3671
+ 'preset.routing_slot_3': {
3672
+ block: 'preset', name: 'routing_slot_3',
3673
+ pidLow: 0x00ce, pidHigh: 0x0015,
3674
+ unit: 'enum', displayMin: 0, displayMax: 1,
3675
+ enumValues: { 0: 'Series', 1: 'Parallel' },
3676
+ },
3677
+ 'preset.routing_slot_4': {
3678
+ block: 'preset', name: 'routing_slot_4',
3679
+ pidLow: 0x00ce, pidHigh: 0x0016,
3680
+ unit: 'enum', displayMin: 0, displayMax: 1,
3681
+ enumValues: { 0: 'Series', 1: 'Parallel' },
3682
+ },
3683
+ // ── PATCH scene-MIDI — pidLow=0x00CE, base rows 0x40/0x50/0x60 ──
3684
+ // Closed Session 85 + 86 (2026-05-16) via:
3685
+ // samples/captured/session-85-scene-midi.pcapng
3686
+ // samples/captured/session-86-scene-midi-disambiguate.pcapng
3687
+ //
3688
+ // Each scene has 4 MIDI message slots; each slot has 3 fields
3689
+ // (Type / Channel / Value). 4×4×3 = 48 wire-addressable params,
3690
+ // all on standard SET_PARAM action=0x0001 with hdr4=0x0004 and
3691
+ // a packed-float value. NO custom action needed.
3692
+ //
3693
+ // Wire layout:
3694
+ // pidHigh = base_row + (scene-1)*4 + (msg-1)
3695
+ // base_row 0x40 → Type (enum; PC=1.0 confirmed)
3696
+ // base_row 0x50 → Channel (1..16, raw int as float)
3697
+ // base_row 0x60 → Value (0..127, raw int as float)
3698
+ //
3699
+ // Type enum: only PC=1 is wire-confirmed. The (s=4,m=4) bonus in
3700
+ // session-85 showed Type=18.0 for what the founder believed was CC,
3701
+ // so CC=18 is hypothesized but not yet locked. A dedicated type-
3702
+ // sweep capture (cycle the Type dropdown through all entries on one
3703
+ // slot) would harvest the full enum. Treat unknown Type values as
3704
+ // raw int passthrough — the encoder will accept any int 0..127.
3705
+ //
3706
+ // The Session 84 §6n-patch anomaly (pidHigh=0x3e81 action=0x0017)
3707
+ // is unrelated to scene-MIDI authoring — it was triggered by a
3708
+ // different AM4-Edit operation. Not on this critical path.
3709
+ 'preset.scene_1_midi_1_type': { block: 'preset', name: 'scene_1_midi_1_type',
3710
+ pidLow: 0x00ce, pidHigh: 0x0040, unit: 'enum', displayMin: 0, displayMax: 129, enumValues: SCENE_MIDI_TYPE_ENUM },
3711
+ 'preset.scene_1_midi_1_channel': { block: 'preset', name: 'scene_1_midi_1_channel',
3712
+ pidLow: 0x00ce, pidHigh: 0x0050, unit: 'count', displayMin: 1, displayMax: 16 },
3713
+ 'preset.scene_1_midi_1_value': { block: 'preset', name: 'scene_1_midi_1_value',
3714
+ pidLow: 0x00ce, pidHigh: 0x0060, unit: 'count', displayMin: 0, displayMax: 127 },
3715
+ 'preset.scene_1_midi_2_type': { block: 'preset', name: 'scene_1_midi_2_type',
3716
+ pidLow: 0x00ce, pidHigh: 0x0041, unit: 'enum', displayMin: 0, displayMax: 129, enumValues: SCENE_MIDI_TYPE_ENUM },
3717
+ 'preset.scene_1_midi_2_channel': { block: 'preset', name: 'scene_1_midi_2_channel',
3718
+ pidLow: 0x00ce, pidHigh: 0x0051, unit: 'count', displayMin: 1, displayMax: 16 },
3719
+ 'preset.scene_1_midi_2_value': { block: 'preset', name: 'scene_1_midi_2_value',
3720
+ pidLow: 0x00ce, pidHigh: 0x0061, unit: 'count', displayMin: 0, displayMax: 127 },
3721
+ 'preset.scene_1_midi_3_type': { block: 'preset', name: 'scene_1_midi_3_type',
3722
+ pidLow: 0x00ce, pidHigh: 0x0042, unit: 'enum', displayMin: 0, displayMax: 129, enumValues: SCENE_MIDI_TYPE_ENUM },
3723
+ 'preset.scene_1_midi_3_channel': { block: 'preset', name: 'scene_1_midi_3_channel',
3724
+ pidLow: 0x00ce, pidHigh: 0x0052, unit: 'count', displayMin: 1, displayMax: 16 },
3725
+ 'preset.scene_1_midi_3_value': { block: 'preset', name: 'scene_1_midi_3_value',
3726
+ pidLow: 0x00ce, pidHigh: 0x0062, unit: 'count', displayMin: 0, displayMax: 127 },
3727
+ 'preset.scene_1_midi_4_type': { block: 'preset', name: 'scene_1_midi_4_type',
3728
+ pidLow: 0x00ce, pidHigh: 0x0043, unit: 'enum', displayMin: 0, displayMax: 129, enumValues: SCENE_MIDI_TYPE_ENUM },
3729
+ 'preset.scene_1_midi_4_channel': { block: 'preset', name: 'scene_1_midi_4_channel',
3730
+ pidLow: 0x00ce, pidHigh: 0x0053, unit: 'count', displayMin: 1, displayMax: 16 },
3731
+ 'preset.scene_1_midi_4_value': { block: 'preset', name: 'scene_1_midi_4_value',
3732
+ pidLow: 0x00ce, pidHigh: 0x0063, unit: 'count', displayMin: 0, displayMax: 127 },
3733
+ 'preset.scene_2_midi_1_type': { block: 'preset', name: 'scene_2_midi_1_type',
3734
+ pidLow: 0x00ce, pidHigh: 0x0044, unit: 'enum', displayMin: 0, displayMax: 129, enumValues: SCENE_MIDI_TYPE_ENUM },
3735
+ 'preset.scene_2_midi_1_channel': { block: 'preset', name: 'scene_2_midi_1_channel',
3736
+ pidLow: 0x00ce, pidHigh: 0x0054, unit: 'count', displayMin: 1, displayMax: 16 },
3737
+ 'preset.scene_2_midi_1_value': { block: 'preset', name: 'scene_2_midi_1_value',
3738
+ pidLow: 0x00ce, pidHigh: 0x0064, unit: 'count', displayMin: 0, displayMax: 127 },
3739
+ 'preset.scene_2_midi_2_type': { block: 'preset', name: 'scene_2_midi_2_type',
3740
+ pidLow: 0x00ce, pidHigh: 0x0045, unit: 'enum', displayMin: 0, displayMax: 129, enumValues: SCENE_MIDI_TYPE_ENUM },
3741
+ 'preset.scene_2_midi_2_channel': { block: 'preset', name: 'scene_2_midi_2_channel',
3742
+ pidLow: 0x00ce, pidHigh: 0x0055, unit: 'count', displayMin: 1, displayMax: 16 },
3743
+ 'preset.scene_2_midi_2_value': { block: 'preset', name: 'scene_2_midi_2_value',
3744
+ pidLow: 0x00ce, pidHigh: 0x0065, unit: 'count', displayMin: 0, displayMax: 127 },
3745
+ 'preset.scene_2_midi_3_type': { block: 'preset', name: 'scene_2_midi_3_type',
3746
+ pidLow: 0x00ce, pidHigh: 0x0046, unit: 'enum', displayMin: 0, displayMax: 129, enumValues: SCENE_MIDI_TYPE_ENUM },
3747
+ 'preset.scene_2_midi_3_channel': { block: 'preset', name: 'scene_2_midi_3_channel',
3748
+ pidLow: 0x00ce, pidHigh: 0x0056, unit: 'count', displayMin: 1, displayMax: 16 },
3749
+ 'preset.scene_2_midi_3_value': { block: 'preset', name: 'scene_2_midi_3_value',
3750
+ pidLow: 0x00ce, pidHigh: 0x0066, unit: 'count', displayMin: 0, displayMax: 127 },
3751
+ 'preset.scene_2_midi_4_type': { block: 'preset', name: 'scene_2_midi_4_type',
3752
+ pidLow: 0x00ce, pidHigh: 0x0047, unit: 'enum', displayMin: 0, displayMax: 129, enumValues: SCENE_MIDI_TYPE_ENUM },
3753
+ 'preset.scene_2_midi_4_channel': { block: 'preset', name: 'scene_2_midi_4_channel',
3754
+ pidLow: 0x00ce, pidHigh: 0x0057, unit: 'count', displayMin: 1, displayMax: 16 },
3755
+ 'preset.scene_2_midi_4_value': { block: 'preset', name: 'scene_2_midi_4_value',
3756
+ pidLow: 0x00ce, pidHigh: 0x0067, unit: 'count', displayMin: 0, displayMax: 127 },
3757
+ 'preset.scene_3_midi_1_type': { block: 'preset', name: 'scene_3_midi_1_type',
3758
+ pidLow: 0x00ce, pidHigh: 0x0048, unit: 'enum', displayMin: 0, displayMax: 129, enumValues: SCENE_MIDI_TYPE_ENUM },
3759
+ 'preset.scene_3_midi_1_channel': { block: 'preset', name: 'scene_3_midi_1_channel',
3760
+ pidLow: 0x00ce, pidHigh: 0x0058, unit: 'count', displayMin: 1, displayMax: 16 },
3761
+ 'preset.scene_3_midi_1_value': { block: 'preset', name: 'scene_3_midi_1_value',
3762
+ pidLow: 0x00ce, pidHigh: 0x0068, unit: 'count', displayMin: 0, displayMax: 127 },
3763
+ 'preset.scene_3_midi_2_type': { block: 'preset', name: 'scene_3_midi_2_type',
3764
+ pidLow: 0x00ce, pidHigh: 0x0049, unit: 'enum', displayMin: 0, displayMax: 129, enumValues: SCENE_MIDI_TYPE_ENUM },
3765
+ 'preset.scene_3_midi_2_channel': { block: 'preset', name: 'scene_3_midi_2_channel',
3766
+ pidLow: 0x00ce, pidHigh: 0x0059, unit: 'count', displayMin: 1, displayMax: 16 },
3767
+ 'preset.scene_3_midi_2_value': { block: 'preset', name: 'scene_3_midi_2_value',
3768
+ pidLow: 0x00ce, pidHigh: 0x0069, unit: 'count', displayMin: 0, displayMax: 127 },
3769
+ 'preset.scene_3_midi_3_type': { block: 'preset', name: 'scene_3_midi_3_type',
3770
+ pidLow: 0x00ce, pidHigh: 0x004a, unit: 'enum', displayMin: 0, displayMax: 129, enumValues: SCENE_MIDI_TYPE_ENUM },
3771
+ 'preset.scene_3_midi_3_channel': { block: 'preset', name: 'scene_3_midi_3_channel',
3772
+ pidLow: 0x00ce, pidHigh: 0x005a, unit: 'count', displayMin: 1, displayMax: 16 },
3773
+ 'preset.scene_3_midi_3_value': { block: 'preset', name: 'scene_3_midi_3_value',
3774
+ pidLow: 0x00ce, pidHigh: 0x006a, unit: 'count', displayMin: 0, displayMax: 127 },
3775
+ 'preset.scene_3_midi_4_type': { block: 'preset', name: 'scene_3_midi_4_type',
3776
+ pidLow: 0x00ce, pidHigh: 0x004b, unit: 'enum', displayMin: 0, displayMax: 129, enumValues: SCENE_MIDI_TYPE_ENUM },
3777
+ 'preset.scene_3_midi_4_channel': { block: 'preset', name: 'scene_3_midi_4_channel',
3778
+ pidLow: 0x00ce, pidHigh: 0x005b, unit: 'count', displayMin: 1, displayMax: 16 },
3779
+ 'preset.scene_3_midi_4_value': { block: 'preset', name: 'scene_3_midi_4_value',
3780
+ pidLow: 0x00ce, pidHigh: 0x006b, unit: 'count', displayMin: 0, displayMax: 127 },
3781
+ 'preset.scene_4_midi_1_type': { block: 'preset', name: 'scene_4_midi_1_type',
3782
+ pidLow: 0x00ce, pidHigh: 0x004c, unit: 'enum', displayMin: 0, displayMax: 129, enumValues: SCENE_MIDI_TYPE_ENUM },
3783
+ 'preset.scene_4_midi_1_channel': { block: 'preset', name: 'scene_4_midi_1_channel',
3784
+ pidLow: 0x00ce, pidHigh: 0x005c, unit: 'count', displayMin: 1, displayMax: 16 },
3785
+ 'preset.scene_4_midi_1_value': { block: 'preset', name: 'scene_4_midi_1_value',
3786
+ pidLow: 0x00ce, pidHigh: 0x006c, unit: 'count', displayMin: 0, displayMax: 127 },
3787
+ 'preset.scene_4_midi_2_type': { block: 'preset', name: 'scene_4_midi_2_type',
3788
+ pidLow: 0x00ce, pidHigh: 0x004d, unit: 'enum', displayMin: 0, displayMax: 129, enumValues: SCENE_MIDI_TYPE_ENUM },
3789
+ 'preset.scene_4_midi_2_channel': { block: 'preset', name: 'scene_4_midi_2_channel',
3790
+ pidLow: 0x00ce, pidHigh: 0x005d, unit: 'count', displayMin: 1, displayMax: 16 },
3791
+ 'preset.scene_4_midi_2_value': { block: 'preset', name: 'scene_4_midi_2_value',
3792
+ pidLow: 0x00ce, pidHigh: 0x006d, unit: 'count', displayMin: 0, displayMax: 127 },
3793
+ 'preset.scene_4_midi_3_type': { block: 'preset', name: 'scene_4_midi_3_type',
3794
+ pidLow: 0x00ce, pidHigh: 0x004e, unit: 'enum', displayMin: 0, displayMax: 129, enumValues: SCENE_MIDI_TYPE_ENUM },
3795
+ 'preset.scene_4_midi_3_channel': { block: 'preset', name: 'scene_4_midi_3_channel',
3796
+ pidLow: 0x00ce, pidHigh: 0x005e, unit: 'count', displayMin: 1, displayMax: 16 },
3797
+ 'preset.scene_4_midi_3_value': { block: 'preset', name: 'scene_4_midi_3_value',
3798
+ pidLow: 0x00ce, pidHigh: 0x006e, unit: 'count', displayMin: 0, displayMax: 127 },
3799
+ 'preset.scene_4_midi_4_type': { block: 'preset', name: 'scene_4_midi_4_type',
3800
+ pidLow: 0x00ce, pidHigh: 0x004f, unit: 'enum', displayMin: 0, displayMax: 129, enumValues: SCENE_MIDI_TYPE_ENUM },
3801
+ 'preset.scene_4_midi_4_channel': { block: 'preset', name: 'scene_4_midi_4_channel',
3802
+ pidLow: 0x00ce, pidHigh: 0x005f, unit: 'count', displayMin: 1, displayMax: 16 },
3803
+ 'preset.scene_4_midi_4_value': { block: 'preset', name: 'scene_4_midi_4_value',
3804
+ pidLow: 0x00ce, pidHigh: 0x006f, unit: 'count', displayMin: 0, displayMax: 127 },
3805
+ // Session 90 (2026-05-17): REVERB + DELAY mirrors from CACHE_PARAMS.
3806
+ // Already shipping live via the `...CACHE_PARAMS` spread at line 427;
3807
+ // these mirrors exist so `scripts/coverage-audit.ts` (which greps
3808
+ // params.ts directly, not the merged registry) sees them. Unblocks
3809
+ // user prompts like "tighten up the delay ducker," "back off the
3810
+ // reverb modulation," "set delay echo pan left," etc. — all addresses
3811
+ // were already wire-shipping; only the audit was under-reporting.
3812
+ // Source: packages/am4/src/cacheParams.ts (auto-generated from
3813
+ // paramNames.ts + cache-section3.json by gen-params-from-cache.ts).
3814
+ 'reverb.high_decay': { block: 'reverb', name: 'high_decay', displayLabel: "High Decay", pidLow: 0x0042, pidHigh: 0x000d, unit: 'count', displayMin: 0.01, displayMax: 1, scaling: 'log10' },
3815
+ 'reverb.scattering': { block: 'reverb', name: 'scattering', displayLabel: "Scattering", pidLow: 0x0042, pidHigh: 0x000e, unit: 'percent', displayMin: 0, displayMax: 100 },
3816
+ 'reverb.reverbdelay': { block: 'reverb', name: 'reverbdelay', pidLow: 0x0042, pidHigh: 0x0010, unit: 'ms', displayMin: 0, displayMax: 250 },
3817
+ 'reverb.early_level': { block: 'reverb', name: 'early_level', displayLabel: "Early Level", pidLow: 0x0042, pidHigh: 0x0011, unit: 'db', displayMin: -40, displayMax: 10 },
3818
+ 'reverb.late_level': { block: 'reverb', name: 'late_level', displayLabel: "Late Level ", pidLow: 0x0042, pidHigh: 0x0012, unit: 'db', displayMin: -40, displayMax: 10 },
3819
+ 'reverb.depth': { block: 'reverb', name: 'depth', displayLabel: "Depth", pidLow: 0x0042, pidHigh: 0x0015, unit: 'percent', displayMin: 0, displayMax: 100 },
3820
+ 'reverb.rate': { block: 'reverb', name: 'rate', displayLabel: "Rate", pidLow: 0x0042, pidHigh: 0x0016, unit: 'hz', displayMin: 0.01, displayMax: 1 },
3821
+ 'reverb.diffusion': { block: 'reverb', name: 'diffusion', displayLabel: "Diffusion", pidLow: 0x0042, pidHigh: 0x0019, unit: 'percent', displayMin: 0, displayMax: 100 },
3822
+ 'reverb.diffusion_time': { block: 'reverb', name: 'diffusion_time', displayLabel: "Diffusion Time", pidLow: 0x0042, pidHigh: 0x001a, unit: 'percent', displayMin: 0, displayMax: 100 },
3823
+ 'reverb.pickup_spacing': { block: 'reverb', name: 'pickup_spacing', displayLabel: "Pickup Spacing", pidLow: 0x0042, pidHigh: 0x001d, unit: 'percent', displayMin: 0, displayMax: 100 },
3824
+ 'reverb.frequency_1': { block: 'reverb', name: 'frequency_1', displayLabel: "Frequency 1", pidLow: 0x0042, pidHigh: 0x001e, unit: 'hz', displayMin: 20, displayMax: 2000 },
3825
+ 'reverb.frequency_2': { block: 'reverb', name: 'frequency_2', displayLabel: "Frequency 2", pidLow: 0x0042, pidHigh: 0x001f, unit: 'hz', displayMin: 100, displayMax: 10000 },
3826
+ 'reverb.q_1': { block: 'reverb', name: 'q_1', displayLabel: "Q 1", pidLow: 0x0042, pidHigh: 0x0020, unit: 'count', displayMin: 0.1, displayMax: 10, scaling: 'log10' },
3827
+ 'reverb.q_2': { block: 'reverb', name: 'q_2', displayLabel: "Q 2", pidLow: 0x0042, pidHigh: 0x0021, unit: 'count', displayMin: 0.1, displayMax: 10, scaling: 'log10' },
3828
+ 'reverb.gain_1': { block: 'reverb', name: 'gain_1', displayLabel: "Gain 1", pidLow: 0x0042, pidHigh: 0x0022, unit: 'db', displayMin: -12, displayMax: 12 },
3829
+ 'reverb.gain_2': { block: 'reverb', name: 'gain_2', displayLabel: "Gain 2", pidLow: 0x0042, pidHigh: 0x0023, unit: 'db', displayMin: -12, displayMax: 12 },
3830
+ 'reverb.low_decay': { block: 'reverb', name: 'low_decay', displayLabel: "Low Decay", pidLow: 0x0042, pidHigh: 0x0025, unit: 'seconds', displayMin: 0.02, displayMax: 2, scaling: 'log10' },
3831
+ 'reverb.xover_frequency': { block: 'reverb', name: 'xover_frequency', displayLabel: "Xover Frequency", pidLow: 0x0042, pidHigh: 0x0026, unit: 'hz', displayMin: 100, displayMax: 10000 },
3832
+ 'reverb.threshold': { block: 'reverb', name: 'threshold', displayLabel: "Threshold", pidLow: 0x0042, pidHigh: 0x0029, unit: 'db', displayMin: -80, displayMax: 20 },
3833
+ 'reverb.release_time': { block: 'reverb', name: 'release_time', displayLabel: "Release Time", pidLow: 0x0042, pidHigh: 0x002a, unit: 'ms', displayMin: 0, displayMax: 1000, scaling: 'log10' },
3834
+ 'reverb.early_diffusion': { block: 'reverb', name: 'early_diffusion', displayLabel: "Early Diffusion", pidLow: 0x0042, pidHigh: 0x002b, unit: 'percent', displayMin: 0, displayMax: 100 },
3835
+ 'reverb.early_diff_time': { block: 'reverb', name: 'early_diff_time', displayLabel: "Early Diff Time", pidLow: 0x0042, pidHigh: 0x002c, unit: 'percent', displayMin: 0, displayMax: 100 },
3836
+ 'reverb.early_decay': { block: 'reverb', name: 'early_decay', displayLabel: "Early Decay", pidLow: 0x0042, pidHigh: 0x002d, unit: 'percent', displayMin: 0, displayMax: 100 },
3837
+ 'reverb.late_input_mix': { block: 'reverb', name: 'late_input_mix', displayLabel: "Late Input Mix", pidLow: 0x0042, pidHigh: 0x002e, unit: 'percent', displayMin: 0, displayMax: 100 },
3838
+ 'reverb.basetype': { block: 'reverb', name: 'basetype', pidLow: 0x0042, pidHigh: 0x0031, unit: 'count', displayMin: 0, displayMax: 8 },
3839
+ 'reverb.lfo_phase': { block: 'reverb', name: 'lfo_phase', displayLabel: "LFO Phase", pidLow: 0x0042, pidHigh: 0x0032, unit: 'degrees', displayMin: 0, displayMax: 180 },
3840
+ 'reverb.pitch_mix': { block: 'reverb', name: 'pitch_mix', displayLabel: "Pitch Mix", pidLow: 0x0042, pidHigh: 0x0037, unit: 'percent', displayMin: 0, displayMax: 100 },
3841
+ 'reverb.pitch_feedback': { block: 'reverb', name: 'pitch_feedback', displayLabel: "Pitch Feedback", pidLow: 0x0042, pidHigh: 0x003a, unit: 'percent', displayMin: 0, displayMax: 100 },
3842
+ 'reverb.splice_time': { block: 'reverb', name: 'splice_time', displayLabel: "Splice Time", pidLow: 0x0042, pidHigh: 0x003c, unit: 'ms', displayMin: 10, displayMax: 2000 },
3843
+ 'reverb.pitch_modulation': { block: 'reverb', name: 'pitch_modulation', displayLabel: "Pitch Modulation", pidLow: 0x0042, pidHigh: 0x003e, unit: 'percent', displayMin: 0, displayMax: 100 },
3844
+ 'reverb.voice_balance': { block: 'reverb', name: 'voice_balance', displayLabel: "Voice Balance", pidLow: 0x0042, pidHigh: 0x003f, unit: 'bipolar_percent', displayMin: -100, displayMax: 100 },
3845
+ 'reverb.feedback': { block: 'reverb', name: 'feedback', displayLabel: "Feedback", pidLow: 0x0042, pidHigh: 0x0041, unit: 'percent', displayMin: 0, displayMax: 100 },
3846
+ 'reverb.echo_mix': { block: 'reverb', name: 'echo_mix', displayLabel: "Echo Mix", pidLow: 0x0042, pidHigh: 0x0042, unit: 'percent', displayMin: 0, displayMax: 100 },
3847
+ 'reverb.pitch_high_cut': { block: 'reverb', name: 'pitch_high_cut', displayLabel: "Shimmer Tone", pidLow: 0x0042, pidHigh: 0x0043, unit: 'hz', displayMin: 200, displayMax: 20000 },
3848
+ 'reverb.tonetype': { block: 'reverb', name: 'tonetype', pidLow: 0x0042, pidHigh: 0x0045, unit: 'db', displayMin: 0, displayMax: 3 },
3849
+ 'reverb.low_cut_q': { block: 'reverb', name: 'low_cut_q', displayLabel: "Low Cut Q", pidLow: 0x0042, pidHigh: 0x0047, unit: 'count', displayMin: 0.1, displayMax: 10, scaling: 'log10' },
3850
+ 'reverb.high_cut_q': { block: 'reverb', name: 'high_cut_q', displayLabel: "High Cut Q", pidLow: 0x0042, pidHigh: 0x0048, unit: 'count', displayMin: 0.1, displayMax: 10, scaling: 'log10' },
3851
+ // DELAY mirrors.
3852
+ 'delay.tempo_1': { block: 'delay', name: 'tempo_1', pidLow: 0x0046, pidHigh: 0x000f, unit: 'percent', displayMin: 0, displayMax: 100 },
3853
+ 'delay.echo_pan': { block: 'delay', name: 'echo_pan', displayLabel: "Echo Pan", pidLow: 0x0046, pidHigh: 0x0011, unit: 'bipolar_percent', displayMin: -100, displayMax: 100 },
3854
+ 'delay.mod_rate': { block: 'delay', name: 'mod_rate', displayLabel: "Mod Rate", pidLow: 0x0046, pidHigh: 0x0016, unit: 'hz', displayMin: 0.1, displayMax: 10 },
3855
+ 'delay.rate': { block: 'delay', name: 'rate', displayLabel: "Rate", pidLow: 0x0046, pidHigh: 0x0017, unit: 'hz', displayMin: 0.2, displayMax: 20 },
3856
+ 'delay.mod_depth': { block: 'delay', name: 'mod_depth', displayLabel: "Mod Depth", pidLow: 0x0046, pidHigh: 0x0018, unit: 'percent', displayMin: 0, displayMax: 100 },
3857
+ 'delay.mod_depth_depth2': { block: 'delay', name: 'mod_depth_depth2', displayLabel: "Mod Depth", pidLow: 0x0046, pidHigh: 0x0019, unit: 'percent', displayMin: 0, displayMax: 100 },
3858
+ 'delay.time_r': { block: 'delay', name: 'time_r', displayLabel: "Time R", pidLow: 0x0046, pidHigh: 0x001e, unit: 'ms', displayMin: 0, displayMax: 8000 },
3859
+ 'delay.rotation': { block: 'delay', name: 'rotation', displayLabel: "Rotation", pidLow: 0x0046, pidHigh: 0x0022, unit: 'bipolar_percent', displayMin: -100, displayMax: 100 },
3860
+ 'delay.lfo_phase': { block: 'delay', name: 'lfo_phase', pidLow: 0x0046, pidHigh: 0x0023, unit: 'bipolar_percent', displayMin: -100, displayMax: 100 },
3861
+ 'delay.level_l': { block: 'delay', name: 'level_l', displayLabel: "Level L", pidLow: 0x0046, pidHigh: 0x0024, unit: 'percent', displayMin: 0, displayMax: 100 },
3862
+ 'delay.level_r': { block: 'delay', name: 'level_r', displayLabel: "Level R", pidLow: 0x0046, pidHigh: 0x0025, unit: 'percent', displayMin: 0, displayMax: 100 },
3863
+ 'delay.pan_l': { block: 'delay', name: 'pan_l', displayLabel: "Pan L", pidLow: 0x0046, pidHigh: 0x0026, unit: 'bipolar_percent', displayMin: -100, displayMax: 100 },
3864
+ 'delay.pan_r': { block: 'delay', name: 'pan_r', displayLabel: "Pan R", pidLow: 0x0046, pidHigh: 0x0027, unit: 'bipolar_percent', displayMin: -100, displayMax: 100 },
3865
+ 'delay.modulation_phase': { block: 'delay', name: 'modulation_phase', displayLabel: "Modulation Phase", pidLow: 0x0046, pidHigh: 0x0028, unit: 'degrees', displayMin: 0, displayMax: 180 },
3866
+ 'delay.lfo_phase_2': { block: 'delay', name: 'lfo_phase_2', displayLabel: "LFO Phase", pidLow: 0x0046, pidHigh: 0x0029, unit: 'degrees', displayMin: 0, displayMax: 180 },
3867
+ 'delay.crossfade_time': { block: 'delay', name: 'crossfade_time', displayLabel: "Crossfade Time", pidLow: 0x0046, pidHigh: 0x002a, unit: 'ms', displayMin: 1, displayMax: 255 },
3868
+ 'delay.sweep_rate': { block: 'delay', name: 'sweep_rate', displayLabel: "Sweep Rate", pidLow: 0x0046, pidHigh: 0x0038, unit: 'hz', displayMin: 0.1, displayMax: 10 },
3869
+ 'delay.sweep_phase': { block: 'delay', name: 'sweep_phase', displayLabel: "Sweep Phase", pidLow: 0x0046, pidHigh: 0x003a, unit: 'degrees', displayMin: 0, displayMax: 180 },
3870
+ 'delay.sweep_start_freq': { block: 'delay', name: 'sweep_start_freq', displayLabel: "Sweep Start Freq", pidLow: 0x0046, pidHigh: 0x003c, unit: 'hz', displayMin: 100, displayMax: 1000 },
3871
+ 'delay.sweep_stop_freq': { block: 'delay', name: 'sweep_stop_freq', displayLabel: "Sweep Stop Freq", pidLow: 0x0046, pidHigh: 0x003d, unit: 'hz', displayMin: 500, displayMax: 5000 },
3872
+ 'delay.sweep_resonance': { block: 'delay', name: 'sweep_resonance', displayLabel: "Sweep Resonance", pidLow: 0x0046, pidHigh: 0x003e, unit: 'count', displayMin: 0.2, displayMax: 20, scaling: 'log10' },
3873
+ 'delay.motor_speed': { block: 'delay', name: 'motor_speed', displayLabel: "Motor Speed", pidLow: 0x0046, pidHigh: 0x0048, unit: 'count', displayMin: 0.5, displayMax: 2, scaling: 'log10' },
3874
+ 'delay.right_post_delay': { block: 'delay', name: 'right_post_delay', displayLabel: "Right Post Delay", pidLow: 0x0046, pidHigh: 0x0049, unit: 'ms', displayMin: 0, displayMax: 100 },
3875
+ 'delay.pan_rate': { block: 'delay', name: 'pan_rate', displayLabel: "Pan Rate", pidLow: 0x0046, pidHigh: 0x0052, unit: 'hz', displayMin: 0.1, displayMax: 10 },
3876
+ 'delay.pan_depth': { block: 'delay', name: 'pan_depth', displayLabel: "Pan Depth", pidLow: 0x0046, pidHigh: 0x0054, unit: 'percent', displayMin: 0, displayMax: 100 },
3877
+ 'delay.lfo_phase_4': { block: 'delay', name: 'lfo_phase_4', displayLabel: "LFO Phase", pidLow: 0x0046, pidHigh: 0x0055, unit: 'degrees', displayMin: 0, displayMax: 180 },
3878
+ // Session 90 (2026-05-17): Phase 3 — REVERB + DELAY enums + tempo-
3879
+ // sync registers from the Ghidra catalog. These have no cache record
3880
+ // (so the cacheParams generator can't emit them) but the paramNames.ts
3881
+ // TODOs document the expected range from prior structural analysis.
3882
+ // Confidence tier per entry:
3883
+ // • HIGH: shared dictionaries (TEMPO_DIVISIONS_VALUES, LFO_WAVEFORMS_VALUES)
3884
+ // — these are firmware-extracted cache enums, byte-identical to the
3885
+ // values the device understands. Wire-safe.
3886
+ // • MEDIUM: 2-value toggles with conventional OFF/ON or paired labels —
3887
+ // the wire range is documented but the display labels are educated
3888
+ // guesses. Wire writes are still safe (values 0 / 1 are in range).
3889
+ // • LOWER: multi-value enums without a known label table — shipped as
3890
+ // `unit: 'count'` so the agent can address the param but doesn't
3891
+ // claim to know what each numeric value means. Future hardware
3892
+ // verification can upgrade these to enum + labels.
3893
+ //
3894
+ // REVERB tempo-sync (HIGH — shared TEMPO_DIVISIONS dictionary):
3895
+ 'reverb.predly_tempo': { block: 'reverb', name: 'predly_tempo', displayLabel: 'Pre-Delay Tempo', pidLow: 0x0042, pidHigh: 0x0040, unit: 'enum', displayMin: 0, displayMax: 78, enumValues: TEMPO_DIVISIONS_VALUES },
3896
+ // REVERB slope/toggle (MEDIUM — 2-value toggles, labels are conventional
3897
+ // guesses based on Blocks Guide §Reverb Common Page; wire writes safe):
3898
+ // Session 96 cont 2 review pass (2026-05-17): renamed `low_slope` →
3899
+ // `low_cut_slope` and `high_slope` → `high_cut_slope` to match the
3900
+ // AM4-Edit XML display labels exactly ("Low Cut Slope" / "High Cut
3901
+ // Slope"). Sibling `reverb.low_cut_q` / `reverb.high_cut_q` (pidHigh
3902
+ // 0x47/0x48) already use the `low_cut_` / `high_cut_` family prefix,
3903
+ // so this aligns the slope pair with the Q pair. Disambiguates from
3904
+ // `amp.low_slope` / `amp.high_slope` on the DISTORT register (whose
3905
+ // XML label is "Low Slope" — no "Cut"). LLM prompt "make the reverb
3906
+ // low cut slope steeper" now matches the param key directly.
3907
+ 'reverb.low_cut_slope': { block: 'reverb', name: 'low_cut_slope', displayLabel: 'Low Cut Slope', pidLow: 0x0042, pidHigh: 0x0035, unit: 'enum', displayMin: 0, displayMax: 1, enumValues: { 0: 'Normal', 1: 'Steep' } },
3908
+ 'reverb.high_cut_slope': { block: 'reverb', name: 'high_cut_slope', displayLabel: 'High Cut Slope', pidLow: 0x0042, pidHigh: 0x0036, unit: 'enum', displayMin: 0, displayMax: 1, enumValues: { 0: 'Normal', 1: 'Steep' } },
3909
+ 'reverb.spring_type': { block: 'reverb', name: 'spring_type', displayLabel: 'Spring Type', pidLow: 0x0042, pidHigh: 0x0044, unit: 'enum', displayMin: 0, displayMax: 1, enumValues: { 0: 'A', 1: 'B' } },
3910
+ 'reverb.predly_tap': { block: 'reverb', name: 'predly_tap', displayLabel: 'Pre-Delay Tap', pidLow: 0x0042, pidHigh: 0x0046, unit: 'enum', displayMin: 0, displayMax: 1, enumValues: { 0: 'OFF', 1: 'ON' } },
3911
+ // REVERB input-select (MEDIUM — matches the standard Fractal input-select
3912
+ // 3-value pattern documented at paramNames.ts line 425 + used in rotary):
3913
+ 'reverb.input_select': { block: 'reverb', name: 'input_select', displayLabel: 'Input Select', pidLow: 0x0042, pidHigh: 0x0033, unit: 'enum', displayMin: 0, displayMax: 2, enumValues: { 0: 'L+R', 1: 'LEFT', 2: 'RIGHT' } },
3914
+ // REVERB pitch direction / position (LOWER — multi-value with no cache
3915
+ // labels). Shipped as 'count' so the agent can write any in-range
3916
+ // value without claiming to know the labels.
3917
+ // Session 96 cont 2 review pass (2026-05-17): renamed `pitch_dir` →
3918
+ // `pitch_direction` and `pitch_pos` → `pitch_position` to spell out
3919
+ // the XML display labels ("Pitch Direction" / "Pitch Position"). The
3920
+ // agent picks up "pitch direction" / "pitch position" prompts
3921
+ // directly without needing to remember the truncated `_dir` / `_pos`
3922
+ // form.
3923
+ 'reverb.pitch_direction': { block: 'reverb', name: 'pitch_direction', displayLabel: 'Pitch Direction', pidLow: 0x0042, pidHigh: 0x003b, unit: 'count', displayMin: 0, displayMax: 3 },
3924
+ 'reverb.pitch_position': { block: 'reverb', name: 'pitch_position', displayLabel: 'Pitch Position', pidLow: 0x0042, pidHigh: 0x003d, unit: 'count', displayMin: 0, displayMax: 2 },
3925
+ // DELAY tempo-sync (HIGH — shared TEMPO_DIVISIONS dictionary on right
3926
+ // channel + LFO1/2/3/4 tempo):
3927
+ 'delay.tempo_r': { block: 'delay', name: 'tempo_r', displayLabel: 'Tempo R', pidLow: 0x0046, pidHigh: 0x0021, unit: 'enum', displayMin: 0, displayMax: 78, enumValues: TEMPO_DIVISIONS_VALUES },
3928
+ 'delay.lfo_1_tempo': { block: 'delay', name: 'lfo_1_tempo', displayLabel: 'LFO 1 Tempo', pidLow: 0x0046, pidHigh: 0x0036, unit: 'enum', displayMin: 0, displayMax: 78, enumValues: TEMPO_DIVISIONS_VALUES },
3929
+ 'delay.lfo_2_tempo': { block: 'delay', name: 'lfo_2_tempo', displayLabel: 'LFO 2 Tempo', pidLow: 0x0046, pidHigh: 0x0037, unit: 'enum', displayMin: 0, displayMax: 78, enumValues: TEMPO_DIVISIONS_VALUES },
3930
+ 'delay.lfo_3_tempo': { block: 'delay', name: 'lfo_3_tempo', displayLabel: 'LFO 3 Tempo', pidLow: 0x0046, pidHigh: 0x003b, unit: 'enum', displayMin: 0, displayMax: 78, enumValues: TEMPO_DIVISIONS_VALUES },
3931
+ 'delay.lfo_4_tempo': { block: 'delay', name: 'lfo_4_tempo', displayLabel: 'LFO 4 Tempo', pidLow: 0x0046, pidHigh: 0x0053, unit: 'enum', displayMin: 0, displayMax: 78, enumValues: TEMPO_DIVISIONS_VALUES },
3932
+ // DELAY LFO waveform type (HIGH — shared LFO_WAVEFORMS dictionary,
3933
+ // 10 entries 0..9, matches chorus.lfo_type at line 2480):
3934
+ 'delay.lfo_1_type': { block: 'delay', name: 'lfo_1_type', displayLabel: 'LFO 1 Type', pidLow: 0x0046, pidHigh: 0x001c, unit: 'enum', displayMin: 0, displayMax: 9, enumValues: LFO_WAVEFORMS_VALUES },
3935
+ 'delay.lfo_2_type': { block: 'delay', name: 'lfo_2_type', displayLabel: 'LFO 2 Type', pidLow: 0x0046, pidHigh: 0x001d, unit: 'enum', displayMin: 0, displayMax: 9, enumValues: LFO_WAVEFORMS_VALUES },
3936
+ 'delay.lfo_3_type': { block: 'delay', name: 'lfo_3_type', displayLabel: 'LFO 3 Type', pidLow: 0x0046, pidHigh: 0x0039, unit: 'enum', displayMin: 0, displayMax: 9, enumValues: LFO_WAVEFORMS_VALUES },
3937
+ 'delay.lfo_4_type': { block: 'delay', name: 'lfo_4_type', displayLabel: 'LFO 4 Type', pidLow: 0x0046, pidHigh: 0x0051, unit: 'enum', displayMin: 0, displayMax: 9, enumValues: LFO_WAVEFORMS_VALUES },
3938
+ // DELAY 2-value toggles (MEDIUM — wire range documented; labels are
3939
+ // conventional OFF/ON or A/B guesses):
3940
+ 'delay.run': { block: 'delay', name: 'run', displayLabel: 'Run', pidLow: 0x0046, pidHigh: 0x002b, unit: 'enum', displayMin: 0, displayMax: 1, enumValues: { 0: 'OFF', 1: 'ON' } },
3941
+ // Renamed to match AM4-Edit display ("Trigger Restart") — confirmed
3942
+ // via cross-ref audit. Wire range still 0..1.
3943
+ 'delay.trigger_restart': { block: 'delay', name: 'trigger_restart', displayLabel: 'Trigger Restart', pidLow: 0x0046, pidHigh: 0x002c, unit: 'enum', displayMin: 0, displayMax: 1, enumValues: { 0: 'OFF', 1: 'ON' } },
3944
+ 'delay.depth_range': { block: 'delay', name: 'depth_range', displayLabel: 'Depth Range', pidLow: 0x0046, pidHigh: 0x0047, unit: 'enum', displayMin: 0, displayMax: 1, enumValues: { 0: 'OFF', 1: 'ON' } },
3945
+ // DELAY LFO target selectors (LOWER — multi-value with no cache labels):
3946
+ 'delay.lfo_1_target': { block: 'delay', name: 'lfo_1_target', displayLabel: 'LFO 1 Target', pidLow: 0x0046, pidHigh: 0x0034, unit: 'count', displayMin: 0, displayMax: 2 },
3947
+ 'delay.lfo_2_target': { block: 'delay', name: 'lfo_2_target', displayLabel: 'LFO 2 Target', pidLow: 0x0046, pidHigh: 0x0035, unit: 'count', displayMin: 0, displayMax: 2 },
3948
+ 'delay.lfo_4_target': { block: 'delay', name: 'lfo_4_target', displayLabel: 'LFO 4 Target', pidLow: 0x0046, pidHigh: 0x0056, unit: 'count', displayMin: 0, displayMax: 3 },
3949
+ // DELAY state-variable filter type (LOWER — typical SVF is Low/Band/High
3950
+ // but TODO marked range 1..3 not 0..2, so the labels here would be wrong;
3951
+ // ship as count and let hardware verification supply the labels):
3952
+ 'delay.sweep_filter': { block: 'delay', name: 'sweep_filter', displayLabel: 'Sweep Filter', pidLow: 0x0046, pidHigh: 0x0059, unit: 'count', displayMin: 1, displayMax: 3 },
3953
+ // Session 90 cont (2026-05-17): CHORUS / FLANGER / PHASER / FILTER /
3954
+ // TREMOLO / ENHANCER / COMPRESSOR mirror block. 53 entries from
3955
+ // cacheParams.ts (auto-generated from the paramNames.ts +
3956
+ // cache-section3.json pipeline) mirrored into params.ts so the
3957
+ // coverage-audit sees them. Includes 21 unit overrides added to
3958
+ // paramNames.ts this session to correct cache c=1 → 'db' fallbacks
3959
+ // for entries that are Hz / count / ratio / bipolar_percent.
3960
+ // Unblocks user prompts like "set the filter rate to 0.5 Hz", "set
3961
+ // phaser min freq to 200 Hz", "compressor ratio 4:1", etc.
3962
+ // CHORUS mirrors (3).
3963
+ 'chorus.left_depth': { block: 'chorus', name: 'left_depth', displayLabel: "Left Depth", pidLow: 0x004e, pidHigh: 0x001c, unit: 'percent', displayMin: 0, displayMax: 100 },
3964
+ 'chorus.center_depth': { block: 'chorus', name: 'center_depth', displayLabel: "Center Depth", pidLow: 0x004e, pidHigh: 0x001d, unit: 'percent', displayMin: 0, displayMax: 100 },
3965
+ 'chorus.right_depth': { block: 'chorus', name: 'right_depth', displayLabel: "Right Depth", pidLow: 0x004e, pidHigh: 0x001e, unit: 'percent', displayMin: 0, displayMax: 100 },
3966
+ // FLANGER mirrors (10).
3967
+ 'flanger.dry_delay': { block: 'flanger', name: 'dry_delay', displayLabel: "Dry Delay", pidLow: 0x0052, pidHigh: 0x0010, unit: 'percent', displayMin: 0, displayMax: 100 },
3968
+ 'flanger.smooth_steps': { block: 'flanger', name: 'smooth_steps', displayLabel: "Smooth Steps", pidLow: 0x0052, pidHigh: 0x0013, unit: 'count', displayMin: 0.5, displayMax: 50 },
3969
+ 'flanger.high_cut': { block: 'flanger', name: 'high_cut', displayLabel: "High Cut", pidLow: 0x0052, pidHigh: 0x0017, unit: 'hz', displayMin: 200, displayMax: 20000 },
3970
+ 'flanger.drive': { block: 'flanger', name: 'drive', displayLabel: "Drive", pidLow: 0x0052, pidHigh: 0x0018, unit: 'knob_0_10', displayMin: 0, displayMax: 10, scaling: 'log10' },
3971
+ 'flanger.low_cut': { block: 'flanger', name: 'low_cut', displayLabel: "Low Cut", pidLow: 0x0052, pidHigh: 0x0019, unit: 'hz', displayMin: 20, displayMax: 2000 },
3972
+ 'flanger.stereo_spread': { block: 'flanger', name: 'stereo_spread', displayLabel: "Stereo Spread", pidLow: 0x0052, pidHigh: 0x001a, unit: 'percent', displayMin: 0, displayMax: 100 },
3973
+ 'flanger.bass_focus': { block: 'flanger', name: 'bass_focus', displayLabel: "Bass Focus", pidLow: 0x0052, pidHigh: 0x001e, unit: 'knob_0_10', displayMin: 0, displayMax: 10, scaling: 'log10' },
3974
+ 'flanger.min_time': { block: 'flanger', name: 'min_time', displayLabel: "Min Time", pidLow: 0x0052, pidHigh: 0x0020, unit: 'ms', displayMin: 0, displayMax: 2 },
3975
+ 'flanger.max_time': { block: 'flanger', name: 'max_time', displayLabel: "Max Time", pidLow: 0x0052, pidHigh: 0x0021, unit: 'ms', displayMin: 0, displayMax: 20 },
3976
+ 'flanger.vpo_exponent': { block: 'flanger', name: 'vpo_exponent', displayLabel: "VPO Exponent", pidLow: 0x0052, pidHigh: 0x0023, unit: 'count', displayMin: 0.01, displayMax: 100, scaling: 'log10' },
3977
+ // PHASER mirrors (12).
3978
+ 'phaser.min_frequency': { block: 'phaser', name: 'min_frequency', displayLabel: "Min Frequency", pidLow: 0x005a, pidHigh: 0x0011, unit: 'hz', displayMin: 5, displayMax: 500 },
3979
+ 'phaser.max_frequency': { block: 'phaser', name: 'max_frequency', displayLabel: "Max Frequency", pidLow: 0x005a, pidHigh: 0x0012, unit: 'hz', displayMin: 200, displayMax: 20000 },
3980
+ 'phaser.bias': { block: 'phaser', name: 'bias', displayLabel: "Bias", pidLow: 0x005a, pidHigh: 0x0014, unit: 'bipolar_percent', displayMin: -100, displayMax: 100 },
3981
+ 'phaser.feedback_point': { block: 'phaser', name: 'feedback_point', displayLabel: "Feedback Point", pidLow: 0x005a, pidHigh: 0x0016, unit: 'count', displayMin: 0, displayMax: 11 },
3982
+ 'phaser.q': { block: 'phaser', name: 'q', displayLabel: "Q", pidLow: 0x005a, pidHigh: 0x0019, unit: 'count', displayMin: 0.1, displayMax: 10, scaling: 'log10' },
3983
+ 'phaser.shape_vcrk': { block: 'phaser', name: 'shape_vcrk', displayLabel: "Shape", pidLow: 0x005a, pidHigh: 0x001d, unit: 'count', displayMin: 0.1, displayMax: 10, scaling: 'log10' },
3984
+ 'phaser.shape': { block: 'phaser', name: 'shape', displayLabel: "Shape", pidLow: 0x005a, pidHigh: 0x001e, unit: 'count', displayMin: 0.01, displayMax: 0.99 },
3985
+ 'phaser.high_cut': { block: 'phaser', name: 'high_cut', displayLabel: "High Cut", pidLow: 0x005a, pidHigh: 0x001f, unit: 'count', displayMin: 0.5, displayMax: 50 },
3986
+ 'phaser.attack': { block: 'phaser', name: 'attack', displayLabel: "Attack", pidLow: 0x005a, pidHigh: 0x0020, unit: 'ms', displayMin: 0, displayMax: 1000, scaling: 'log10' },
3987
+ 'phaser.release': { block: 'phaser', name: 'release', displayLabel: "Release", pidLow: 0x005a, pidHigh: 0x0021, unit: 'ms', displayMin: 0, displayMax: 100, scaling: 'log10' },
3988
+ 'phaser.low_cut': { block: 'phaser', name: 'low_cut', displayLabel: "Low Cut", pidLow: 0x005a, pidHigh: 0x0023, unit: 'hz', displayMin: 20, displayMax: 200 },
3989
+ 'phaser.high_cut_lpf': { block: 'phaser', name: 'high_cut_lpf', displayLabel: "High Cut", pidLow: 0x005a, pidHigh: 0x0024, unit: 'hz', displayMin: 2000, displayMax: 20000 },
3990
+ // FILTER mirrors (15).
3991
+ 'filter.q': { block: 'filter', name: 'q', displayLabel: "Q", pidLow: 0x0072, pidHigh: 0x000c, unit: 'count', displayMin: 0.1, displayMax: 10, scaling: 'log10' },
3992
+ 'filter.gain': { block: 'filter', name: 'gain', displayLabel: "Gain", pidLow: 0x0072, pidHigh: 0x000d, unit: 'db', displayMin: -20, displayMax: 20 },
3993
+ 'filter.pan_left': { block: 'filter', name: 'pan_left', displayLabel: "Pan Left", pidLow: 0x0072, pidHigh: 0x000f, unit: 'percent', displayMin: 0, displayMax: 100 },
3994
+ 'filter.pan_right': { block: 'filter', name: 'pan_right', displayLabel: "Pan Right", pidLow: 0x0072, pidHigh: 0x0010, unit: 'percent', displayMin: 0, displayMax: 100 },
3995
+ 'filter.delay_time': { block: 'filter', name: 'delay_time', displayLabel: "Delay Time", pidLow: 0x0072, pidHigh: 0x0014, unit: 'ms', displayMin: 0, displayMax: 40 },
3996
+ 'filter.rate': { block: 'filter', name: 'rate', displayLabel: "Rate", pidLow: 0x0072, pidHigh: 0x0018, unit: 'hz', displayMin: 0.1, displayMax: 10 },
3997
+ 'filter.lfo_duty': { block: 'filter', name: 'lfo_duty', displayLabel: "Duty Cycle", pidLow: 0x0072, pidHigh: 0x0019, unit: 'percent', displayMin: 0, displayMax: 100 },
3998
+ 'filter.mod_frequency': { block: 'filter', name: 'mod_frequency', displayLabel: "Mod Freq", pidLow: 0x0072, pidHigh: 0x001a, unit: 'hz', displayMin: 20, displayMax: 20000 },
3999
+ 'filter.resonance': { block: 'filter', name: 'resonance', displayLabel: "Resonance", pidLow: 0x0072, pidHigh: 0x001e, unit: 'knob_0_10', displayMin: 0, displayMax: 10, scaling: 'log10' },
4000
+ 'filter.start_frequency': { block: 'filter', name: 'start_frequency', displayLabel: "Start Frequency", pidLow: 0x0072, pidHigh: 0x001f, unit: 'hz', displayMin: 100, displayMax: 10000 },
4001
+ 'filter.stop_frequency': { block: 'filter', name: 'stop_frequency', displayLabel: "Stop Frequency", pidLow: 0x0072, pidHigh: 0x0020, unit: 'hz', displayMin: 100, displayMax: 10000 },
4002
+ 'filter.sensitivity': { block: 'filter', name: 'sensitivity', displayLabel: "Sensitivity", pidLow: 0x0072, pidHigh: 0x0021, unit: 'count', displayMin: 0.1, displayMax: 40, scaling: 'log10' },
4003
+ 'filter.attack_time': { block: 'filter', name: 'attack_time', displayLabel: "Attack Time", pidLow: 0x0072, pidHigh: 0x0022, unit: 'ms', displayMin: 0, displayMax: 1000, scaling: 'log10' },
4004
+ 'filter.release_time': { block: 'filter', name: 'release_time', displayLabel: "Release Time", pidLow: 0x0072, pidHigh: 0x0023, unit: 'ms', displayMin: 0, displayMax: 2000, scaling: 'log10' },
4005
+ 'filter.emphasis': { block: 'filter', name: 'emphasis', displayLabel: "Emphasis", pidLow: 0x0072, pidHigh: 0x0027, unit: 'knob_0_10', displayMin: 0, displayMax: 10 },
4006
+ // TREMOLO mirrors (4).
4007
+ 'tremolo.duty': { block: 'tremolo', name: 'duty', displayLabel: "Duty", pidLow: 0x006a, pidHigh: 0x000e, unit: 'percent', displayMin: 0, displayMax: 100 },
4008
+ 'tremolo.crossover_freq': { block: 'tremolo', name: 'crossover_freq', displayLabel: "Crossover Freq", pidLow: 0x006a, pidHigh: 0x0015, unit: 'hz', displayMin: 200, displayMax: 2000 },
4009
+ 'tremolo.trigger_threshold': { block: 'tremolo', name: 'trigger_threshold', displayLabel: "Trigger Threshold", pidLow: 0x006a, pidHigh: 0x0016, unit: 'db', displayMin: -60, displayMax: 20 },
4010
+ 'tremolo.shape': { block: 'tremolo', name: 'shape', displayLabel: "Shape", pidLow: 0x006a, pidHigh: 0x0017, unit: 'percent', displayMin: 0, displayMax: 100 },
4011
+ // ENHANCER mirrors (2).
4012
+ 'enhancer.pan_left': { block: 'enhancer', name: 'pan_left', displayLabel: "Pan Left", pidLow: 0x007a, pidHigh: 0x0010, unit: 'percent', displayMin: 0, displayMax: 100 },
4013
+ 'enhancer.pan_right': { block: 'enhancer', name: 'pan_right', displayLabel: "Pan Right", pidLow: 0x007a, pidHigh: 0x0011, unit: 'percent', displayMin: 0, displayMax: 100 },
4014
+ // COMPRESSOR mirrors (7).
4015
+ 'compressor.compression': { block: 'compressor', name: 'compression', displayLabel: "Compression", pidLow: 0x002e, pidHigh: 0x0014, unit: 'knob_0_10', displayMin: 0, displayMax: 10 },
4016
+ 'compressor.dynamics': { block: 'compressor', name: 'dynamics', displayLabel: "Dynamics", pidLow: 0x002e, pidHigh: 0x0018, unit: 'knob_0_10', displayMin: 0, displayMax: 10 },
4017
+ 'compressor.threshold_thresh2': { block: 'compressor', name: 'threshold_thresh2', displayLabel: "Threshold", pidLow: 0x002e, pidHigh: 0x0021, unit: 'db', displayMin: -60, displayMax: 20 },
4018
+ 'compressor.ratio_compansion': { block: 'compressor', name: 'ratio_compansion', displayLabel: "Ratio", pidLow: 0x002e, pidHigh: 0x0024, unit: 'ratio', displayMin: 1, displayMax: 10, scaling: 'log10' },
4019
+ 'compressor.time': { block: 'compressor', name: 'time', displayLabel: "Time", pidLow: 0x002e, pidHigh: 0x0025, unit: 'ms', displayMin: 0, displayMax: 1000, scaling: 'log10' },
4020
+ 'compressor.transients': { block: 'compressor', name: 'transients', displayLabel: "Transients", pidLow: 0x002e, pidHigh: 0x0026, unit: 'knob_0_10', displayMin: 0, displayMax: 10 },
4021
+ 'compressor.tone': { block: 'compressor', name: 'tone', displayLabel: "Tone", pidLow: 0x002e, pidHigh: 0x0028, unit: 'knob_0_10', displayMin: 0, displayMax: 10 },
4022
+ // Session 91 (2026-05-17): FLANGER / PHASER / FILTER UI-MISSING
4023
+ // closeout. 28 catalog symbols from the Ghidra paramId table that
4024
+ // have AM4-Edit XML labels but no params.ts entry. Confidence tiers
4025
+ // per the Session 90 convention:
4026
+ // • HIGH: shared LFO_WAVEFORMS_VALUES / TEMPO_DIVISIONS_VALUES
4027
+ // dictionaries, wire-byte identical to existing entries that use
4028
+ // them. Wire-safe.
4029
+ // • MEDIUM: 2-value toggles with the conventional { 0:'OFF', 1:'ON' }
4030
+ // labels. Wire range documented; labels are conventional guesses.
4031
+ // Wire writes still safe (values 0 / 1 in range).
4032
+ // • LOWER: multi-value enums without a known label table — shipped
4033
+ // as `unit: 'count'` so the agent can address the param without
4034
+ // claiming the labels. Hardware verification can upgrade later.
4035
+ // Resolver-name dedup suffixes follow the existing pattern
4036
+ // (`shape_vcrk`, `high_cut_lpf`, `ratio_compansion`): when the
4037
+ // AM4-Edit label collides with an existing entry's name, add a
4038
+ // disambiguating suffix and accept the WIRED-MISLABEL on the audit
4039
+ // (intentional disambiguation, ceiling bump).
4040
+ // FLANGER hand-author (9). All 9 names match the AM4-Edit XML
4041
+ // display labels → WIRED-MATCHED.
4042
+ 'flanger.lfo_type': { block: 'flanger', name: 'lfo_type', displayLabel: 'LFO Type', pidLow: 0x0052, pidHigh: 0x0012, unit: 'enum', displayMin: 0, displayMax: 9, enumValues: LFO_WAVEFORMS_VALUES },
4043
+ 'flanger.auto_depth': { block: 'flanger', name: 'auto_depth', displayLabel: 'Auto Depth', pidLow: 0x0052, pidHigh: 0x0014, unit: 'enum', displayMin: 0, displayMax: 1, enumValues: { 0: 'OFF', 1: 'ON' } },
4044
+ 'flanger.phase_reverse': { block: 'flanger', name: 'phase_reverse', displayLabel: 'Phase Reverse', pidLow: 0x0052, pidHigh: 0x0015, unit: 'enum', displayMin: 0, displayMax: 1, enumValues: { 0: 'OFF', 1: 'ON' } },
4045
+ 'flanger.thru_zero': { block: 'flanger', name: 'thru_zero', displayLabel: 'Thru Zero', pidLow: 0x0052, pidHigh: 0x0016, unit: 'enum', displayMin: 0, displayMax: 1, enumValues: { 0: 'OFF', 1: 'ON' } },
4046
+ 'flanger.bypass_reset': { block: 'flanger', name: 'bypass_reset', displayLabel: 'Bypass Reset', pidLow: 0x0052, pidHigh: 0x001b, unit: 'enum', displayMin: 0, displayMax: 1, enumValues: { 0: 'OFF', 1: 'ON' } },
4047
+ // High/Low Cut Slope: filter-stage IIR order (typical Fractal range
4048
+ // 1..12). Shipped as count until hardware verification supplies the
4049
+ // discrete slope labels (typical: 6/12/18/24 dB/oct).
4050
+ 'flanger.high_cut_slope': { block: 'flanger', name: 'high_cut_slope', displayLabel: 'High Cut Slope', pidLow: 0x0052, pidHigh: 0x001c, unit: 'count', displayMin: 1, displayMax: 12 },
4051
+ 'flanger.low_cut_slope': { block: 'flanger', name: 'low_cut_slope', displayLabel: 'Low Cut Slope', pidLow: 0x0052, pidHigh: 0x001d, unit: 'count', displayMin: 1, displayMax: 12 },
4052
+ 'flanger.vco_response': { block: 'flanger', name: 'vco_response', displayLabel: 'VCO Response', pidLow: 0x0052, pidHigh: 0x001f, unit: 'count', displayMin: 0, displayMax: 10 },
4053
+ 'flanger.steps': { block: 'flanger', name: 'steps', displayLabel: 'Steps', pidLow: 0x0052, pidHigh: 0x0022, unit: 'count', displayMin: 0, displayMax: 32 },
4054
+ // PHASER hand-author (9). 6 MATCH the AM4-Edit display label;
4055
+ // 3 are intentional disambiguations (lfo_type, vcr_curve, lfo_mode
4056
+ // — AM4-Edit displays "Type" / "Type" / "Mode" but `phaser.type` and
4057
+ // `phaser.mode` are already used) → WIRED-MISLABEL with ceiling bump.
4058
+ 'phaser.order': { block: 'phaser', name: 'order', displayLabel: 'Order', pidLow: 0x005a, pidHigh: 0x000b, unit: 'count', displayMin: 1, displayMax: 12 },
4059
+ 'phaser.lfo_type': { block: 'phaser', name: 'lfo_type', displayLabel: 'LFO Type', pidLow: 0x005a, pidHigh: 0x000d, unit: 'enum', displayMin: 0, displayMax: 9, enumValues: LFO_WAVEFORMS_VALUES },
4060
+ 'phaser.mode': { block: 'phaser', name: 'mode', displayLabel: 'Mode', pidLow: 0x005a, pidHigh: 0x0015, unit: 'count', displayMin: 0, displayMax: 3 },
4061
+ 'phaser.tone': { block: 'phaser', name: 'tone', displayLabel: 'Tone', pidLow: 0x005a, pidHigh: 0x0017, unit: 'knob_0_10', displayMin: 0, displayMax: 10 },
4062
+ 'phaser.direction': { block: 'phaser', name: 'direction', displayLabel: 'Direction', pidLow: 0x005a, pidHigh: 0x0018, unit: 'count', displayMin: 0, displayMax: 2 },
4063
+ 'phaser.reset_on_bypass': { block: 'phaser', name: 'reset_on_bypass', displayLabel: 'Reset on Bypass', pidLow: 0x005a, pidHigh: 0x001a, unit: 'enum', displayMin: 0, displayMax: 1, enumValues: { 0: 'OFF', 1: 'ON' } },
4064
+ 'phaser.quantize': { block: 'phaser', name: 'quantize', displayLabel: 'Quantize', pidLow: 0x005a, pidHigh: 0x001b, unit: 'enum', displayMin: 0, displayMax: 1, enumValues: { 0: 'OFF', 1: 'ON' } },
4065
+ // vcr_curve: AM4-Edit label is "Type" (alpha curve for the VCR
4066
+ // simulation on the Config page). Disambiguating suffix since
4067
+ // `phaser.type` is the family Type enum.
4068
+ 'phaser.vcr_curve': { block: 'phaser', name: 'vcr_curve', displayLabel: 'Type', pidLow: 0x005a, pidHigh: 0x001c, unit: 'count', displayMin: 0, displayMax: 3 },
4069
+ // lfo_mode: AM4-Edit label is "Mode" (LFO mode selector). Disambig
4070
+ // suffix since `phaser.mode` is already taken above (PHASER_MODE,
4071
+ // no XML label).
4072
+ 'phaser.lfo_mode': { block: 'phaser', name: 'lfo_mode', displayLabel: 'Mode', pidLow: 0x005a, pidHigh: 0x0025, unit: 'count', displayMin: 0, displayMax: 3 },
4073
+ // FILTER hand-author (10). 9 MATCH the AM4-Edit display label;
4074
+ // 1 is intentional disambiguation (order_2 — AM4-Edit displays
4075
+ // "Order" but `filter.order` is already at pidHigh=0x1c from the
4076
+ // cache pipeline) → WIRED-MISLABEL with ceiling bump.
4077
+ 'filter.order_2': { block: 'filter', name: 'order_2', displayLabel: 'Order', pidLow: 0x0072, pidHigh: 0x000e, unit: 'count', displayMin: 1, displayMax: 12 },
4078
+ 'filter.phase_invert': { block: 'filter', name: 'phase_invert', displayLabel: 'Phase Invert', pidLow: 0x0072, pidHigh: 0x0011, unit: 'enum', displayMin: 0, displayMax: 1, enumValues: { 0: 'OFF', 1: 'ON' } },
4079
+ // enable: AM4-Edit displays "Enable" (LFO Enable on the LFO page).
4080
+ // Naming as `enable` matches the AM4-Edit label.
4081
+ 'filter.enable': { block: 'filter', name: 'enable', displayLabel: 'Enable', pidLow: 0x0072, pidHigh: 0x0016, unit: 'enum', displayMin: 0, displayMax: 1, enumValues: { 0: 'OFF', 1: 'ON' } },
4082
+ 'filter.lfo_type': { block: 'filter', name: 'lfo_type', displayLabel: 'LFO Type', pidLow: 0x0072, pidHigh: 0x0017, unit: 'enum', displayMin: 0, displayMax: 9, enumValues: LFO_WAVEFORMS_VALUES },
4083
+ 'filter.quantize': { block: 'filter', name: 'quantize', displayLabel: 'Quantize', pidLow: 0x0072, pidHigh: 0x001b, unit: 'enum', displayMin: 0, displayMax: 1, enumValues: { 0: 'OFF', 1: 'ON' } },
4084
+ 'filter.mode': { block: 'filter', name: 'mode', displayLabel: 'Mode', pidLow: 0x0072, pidHigh: 0x001d, unit: 'count', displayMin: 0, displayMax: 3 },
4085
+ 'filter.sweep_shape': { block: 'filter', name: 'sweep_shape', displayLabel: 'Sweep Shape', pidLow: 0x0072, pidHigh: 0x0024, unit: 'count', displayMin: 0, displayMax: 10 },
4086
+ 'filter.detector_source': { block: 'filter', name: 'detector_source', displayLabel: 'Detector Source', pidLow: 0x0072, pidHigh: 0x0025, unit: 'count', displayMin: 0, displayMax: 3 },
4087
+ 'filter.detect': { block: 'filter', name: 'detect', displayLabel: 'Detect', pidLow: 0x0072, pidHigh: 0x0026, unit: 'count', displayMin: 0, displayMax: 3 },
4088
+ 'filter.tempo': { block: 'filter', name: 'tempo', displayLabel: 'Tempo', pidLow: 0x0072, pidHigh: 0x0028, unit: 'enum', displayMin: 0, displayMax: 78, enumValues: TEMPO_DIVISIONS_VALUES },
4089
+ // ── Session 92 (2026-05-17) — CABINET UI-MISSING closeout ──
4090
+ // 24 hand-authored entries on the canonical CABINET register
4091
+ // (pidLow=0x003e). Catalog source: Ghidra-extracted PATCH family
4092
+ // params via samples/captured/decoded/ghidra-am4-paramnames.json,
4093
+ // cross-referenced with AM4-Edit's __block_layout.xml +
4094
+ // __block_layout_expert.xml. Display labels come from the XML
4095
+ // `name=` attribute; entries lacking a display string are XML-
4096
+ // invisible firmware-internal controls (still wired via the
4097
+ // catalog).
4098
+ //
4099
+ // Deferred from this session (already addressable via the cross-
4100
+ // block resolver path at pidLow=0x003a):
4101
+ // • CABINET_PROXIMITY2 → amp.proximity in cacheParams.ts at 0x15
4102
+ // • CABINET_DYNACAB_Z1 → amp.distance in cacheParams.ts at 0x47
4103
+ // • CABINET_DYNACAB_Z2 → amp.distance_dynacab_z2 at 0x48
4104
+ // • CABINET_ZOOM (UI-toggle for editor zoom view, not a real
4105
+ // device-side setting — btnRectangleToggle in expert XML only)
4106
+ //
4107
+ // Confidence tiers:
4108
+ // HIGH — mirrors an existing first-cab entry (bank/cab/pan/
4109
+ // low_slope/high_slope/dynacab)
4110
+ // MEDIUM — labeled in XML, tonal unit inferred from sibling
4111
+ // controls (BASS/MID = knob_0_10 like amp tone stack)
4112
+ // LOWER — no XML label; unit: 'count' as a safe-write placeholder
4113
+ // until hardware verification supplies the range/enum
4114
+ //
4115
+ // Naming convention: `_1`/`_2` suffix mirrors existing
4116
+ // `cab_1_blend`/`cab_2_blend` / `low_slope`/`high_slope` pattern.
4117
+ // Per-cab knob names get the `cab_` prefix when the bare name would
4118
+ // collide with an amp-stack knob (cab_bass vs amp.bass, cab_mid
4119
+ // vs amp.mid).
4120
+ // Second-cab mirrors of existing first-cab entries (HIGH confidence).
4121
+ 'amp.bank_2': { block: 'amp', name: 'bank_2', displayLabel: 'Bank', pidLow: 0x003e, pidHigh: 0x000b, unit: 'enum', displayMin: 0, displayMax: 31 },
4122
+ 'amp.cab_2': { block: 'amp', name: 'cab_2', displayLabel: 'Cab #', pidLow: 0x003e, pidHigh: 0x000d, unit: 'count', displayMin: 0, displayMax: 511 },
4123
+ 'amp.pan_2': { block: 'amp', name: 'pan_2', displayLabel: 'Pan', pidLow: 0x003e, pidHigh: 0x0011, unit: 'bipolar_percent', displayMin: -100, displayMax: 100 },
4124
+ 'amp.low_slope_2': { block: 'amp', name: 'low_slope_2', displayLabel: 'Low Slope', pidLow: 0x003e, pidHigh: 0x003c, unit: 'enum', displayMin: 0, displayMax: 7 },
4125
+ 'amp.high_slope_2': { block: 'amp', name: 'high_slope_2', displayLabel: 'High Slope', pidLow: 0x003e, pidHigh: 0x003e, unit: 'enum', displayMin: 0, displayMax: 7 },
4126
+ 'amp.dynacab_2': { block: 'amp', name: 'dynacab_2', displayLabel: 'DynaCab', pidLow: 0x003e, pidHigh: 0x0046, unit: 'knob_0_10', displayMin: 0, displayMax: 10 },
4127
+ // Per-cab level + proximity + mute (LEVEL1/2 have no XML label —
4128
+ // firmware-internal, but the names follow the established `_N`
4129
+ // suffix convention).
4130
+ 'amp.cab_level_1': { block: 'amp', name: 'cab_level_1', pidLow: 0x003e, pidHigh: 0x000e, unit: 'db', displayMin: -80, displayMax: 20 },
4131
+ 'amp.cab_level_2': { block: 'amp', name: 'cab_level_2', pidLow: 0x003e, pidHigh: 0x000f, unit: 'db', displayMin: -80, displayMax: 20 },
4132
+ 'amp.proximity_1': { block: 'amp', name: 'proximity_1', displayLabel: 'Proximity', pidLow: 0x003e, pidHigh: 0x0014, unit: 'percent', displayMin: 0, displayMax: 100 },
4133
+ 'amp.cab_mute_1': { block: 'amp', name: 'cab_mute_1', displayLabel: 'M', pidLow: 0x003e, pidHigh: 0x0016, unit: 'enum', displayMin: 0, displayMax: 1, enumValues: { 0: 'OFF', 1: 'ON' } },
4134
+ 'amp.cab_mute_2': { block: 'amp', name: 'cab_mute_2', displayLabel: 'M', pidLow: 0x003e, pidHigh: 0x0017, unit: 'enum', displayMin: 0, displayMax: 1, enumValues: { 0: 'OFF', 1: 'ON' } },
4135
+ // Master / cab-level Hz filters (LOCUT = master low cut on the
4136
+ // Cab Master EQ page; LOCUT1 = per-cab-1 low cut on the Cab page).
4137
+ // The existing `cab_master_low_cut` at pidHigh=0x22 is actually
4138
+ // wired to CABINET_PROXFREQ (catalog id 34) — a Session 53-era
4139
+ // misname that's left as-is for backward compatibility; this new
4140
+ // `master_low_cut` is the true CABINET_LOCUT control.
4141
+ 'amp.master_low_cut': { block: 'amp', name: 'master_low_cut', displayLabel: 'Low Cut', pidLow: 0x003e, pidHigh: 0x001f, unit: 'hz', displayMin: 20, displayMax: 20000 },
4142
+ 'amp.cab_1_low_cut': { block: 'amp', name: 'cab_1_low_cut', displayLabel: 'Low Cut', pidLow: 0x003e, pidHigh: 0x0035, unit: 'hz', displayMin: 20, displayMax: 20000 },
4143
+ // Mic-preamp tone-stack on the Cab Mic Preamp page (BASS/MID at
4144
+ // catalog ids 37/38; the existing `cab_mic_preamp_treble` at
4145
+ // pidHigh=0x27 already covers TREBLE at id 39). PRETYPE is the
4146
+ // Type dropdown on the same page (mic-preamp circuit selector).
4147
+ 'amp.cab_pretype': { block: 'amp', name: 'cab_pretype', displayLabel: 'Type', pidLow: 0x003e, pidHigh: 0x0024, unit: 'count', displayMin: 0, displayMax: 15 },
4148
+ 'amp.cab_bass': { block: 'amp', name: 'cab_bass', displayLabel: 'Bass', pidLow: 0x003e, pidHigh: 0x0025, unit: 'knob_0_10', displayMin: 0, displayMax: 10 },
4149
+ 'amp.cab_mid': { block: 'amp', name: 'cab_mid', displayLabel: 'Mid', pidLow: 0x003e, pidHigh: 0x0026, unit: 'knob_0_10', displayMin: 0, displayMax: 10 },
4150
+ // SMOOTH1/2 / ORDER / GAINMONITOR — no XML labels (firmware-
4151
+ // internal). LOWER confidence: shipped as 'count' so the agent
4152
+ // can write any in-range value without claiming an interpretation.
4153
+ // HW-NNN capture needed to confirm semantics.
4154
+ 'amp.cab_smooth_1': { block: 'amp', name: 'cab_smooth_1', pidLow: 0x003e, pidHigh: 0x0029, unit: 'count', displayMin: 0, displayMax: 10 },
4155
+ 'amp.cab_smooth_2': { block: 'amp', name: 'cab_smooth_2', pidLow: 0x003e, pidHigh: 0x002a, unit: 'count', displayMin: 0, displayMax: 10 },
4156
+ 'amp.cab_order': { block: 'amp', name: 'cab_order', pidLow: 0x003e, pidHigh: 0x002b, unit: 'count', displayMin: 1, displayMax: 12 },
4157
+ 'amp.cab_gain_monitor': { block: 'amp', name: 'cab_gain_monitor', pidLow: 0x003e, pidHigh: 0x0033, unit: 'knob_0_10', displayMin: 0, displayMax: 10 },
4158
+ // DynaCab quad (TYPE1/2/MIC1/2). XML has empty `name=""` in regular
4159
+ // layout and "Cab"/"Mic" in expert layout — the audit picks the
4160
+ // first XML hit, so these will MATCH (empty display).
4161
+ 'amp.dynacab_type_1': { block: 'amp', name: 'dynacab_type_1', displayLabel: "Cab", pidLow: 0x003e, pidHigh: 0x0041, unit: 'count', displayMin: 0, displayMax: 31 },
4162
+ 'amp.dynacab_type_2': { block: 'amp', name: 'dynacab_type_2', displayLabel: "Cab", pidLow: 0x003e, pidHigh: 0x0042, unit: 'count', displayMin: 0, displayMax: 31 },
4163
+ 'amp.dynacab_mic_1': { block: 'amp', name: 'dynacab_mic_1', displayLabel: "Mic", pidLow: 0x003e, pidHigh: 0x0043, unit: 'count', displayMin: 0, displayMax: 31 },
4164
+ 'amp.dynacab_mic_2': { block: 'amp', name: 'dynacab_mic_2', displayLabel: "Mic", pidLow: 0x003e, pidHigh: 0x0044, unit: 'count', displayMin: 0, displayMax: 31 },
4165
+ // ============================================================
4166
+ // GLOBAL family (pidLow = 0x0001) — 98 entries.
4167
+ //
4168
+ // Wire pidLow decoded HW-112 (Session 96, 2026-05-17) from
4169
+ // samples/captured/session-95-am4-global-pidlow.pcapng. paramIds
4170
+ // sourced from samples/captured/decoded/ghidra-am4-paramnames.json
4171
+ // (effect_types.case_0x1.params, 99 entries; GLOBAL_FC_HOLD_TIMEOUT
4172
+ // appears twice at paramId 57 so deduped to 98).
4173
+ //
4174
+ // Two paramIds are HW-verified by the HW-112 capture: USBLEVEL1
4175
+ // (99) at 1.11 dB and TAP_TEMPO_MODE (46) at 1.0 = "Last Two".
4176
+ // All other unit/range pairs are name-inferred — entries default
4177
+ // to unit: 'count' as a safe write placeholder pending hardware
4178
+ // verification. The Ghidra catalog gives us the address and the
4179
+ // symbolic name; UI semantics still need front-panel or AM4-Edit
4180
+ // captures to confirm range/enum tables.
4181
+ //
4182
+ // Naming convention: `global.<lowercased>` with the GLOBAL_ prefix
4183
+ // stripped and `+N` array suffixes converted to `_N` (so
4184
+ // `GLOBAL_EXT_CC_BEGIN+1` -> `global.ext_cc_begin_1`).
4185
+ //
4186
+ // Regenerate: `npx tsx scripts/_research/generate-am4-global-block.ts`
4187
+ // tuning reference Hz convention — HW unverified
4188
+ 'global.tuningref': { block: 'global', name: 'tuningref', displayLabel: "Calibration", pidLow: 0x0001, pidHigh: 0x000d, unit: 'hz', displayMin: 430, displayMax: 450 },
4189
+ // safe placeholder (range unverified) — Ghidra catalog entry only
4190
+ 'global.tunermute': { block: 'global', name: 'tunermute', displayLabel: "Mute Type", pidLow: 0x0001, pidHigh: 0x000e, unit: 'count', displayMin: 0, displayMax: 127 },
4191
+ // safe placeholder (range unverified) — Ghidra catalog entry only
4192
+ 'global.delayspill': { block: 'global', name: 'delayspill', displayLabel: "Spillover", pidLow: 0x0001, pidHigh: 0x000f, unit: 'count', displayMin: 0, displayMax: 127 },
4193
+ // safe placeholder (range unverified) — Ghidra catalog entry only
4194
+ 'global.usetuneoffsets': { block: 'global', name: 'usetuneoffsets', displayLabel: "Use Offsets", pidLow: 0x0001, pidHigh: 0x0010, unit: 'count', displayMin: 0, displayMax: 127 },
4195
+ // per-string tuning offset — HW unverified
4196
+ 'global.offset1': { block: 'global', name: 'offset1', displayLabel: "E 1", pidLow: 0x0001, pidHigh: 0x0011, unit: 'semitones', displayMin: -1, displayMax: 1 },
4197
+ // per-string tuning offset — HW unverified
4198
+ 'global.offset2': { block: 'global', name: 'offset2', displayLabel: "B 2", pidLow: 0x0001, pidHigh: 0x0012, unit: 'semitones', displayMin: -1, displayMax: 1 },
4199
+ // per-string tuning offset — HW unverified
4200
+ 'global.offset3': { block: 'global', name: 'offset3', displayLabel: "G 3", pidLow: 0x0001, pidHigh: 0x0013, unit: 'semitones', displayMin: -1, displayMax: 1 },
4201
+ // per-string tuning offset — HW unverified
4202
+ 'global.offset4': { block: 'global', name: 'offset4', displayLabel: "D 4", pidLow: 0x0001, pidHigh: 0x0014, unit: 'semitones', displayMin: -1, displayMax: 1 },
4203
+ // per-string tuning offset — HW unverified
4204
+ 'global.offset5': { block: 'global', name: 'offset5', displayLabel: "A 5", pidLow: 0x0001, pidHigh: 0x0015, unit: 'semitones', displayMin: -1, displayMax: 1 },
4205
+ // per-string tuning offset — HW unverified
4206
+ 'global.offset6': { block: 'global', name: 'offset6', displayLabel: "E 6", pidLow: 0x0001, pidHigh: 0x0016, unit: 'semitones', displayMin: -1, displayMax: 1 },
4207
+ // GEQ band ±12 dB convention — HW unverified
4208
+ 'global.out2eq1': { block: 'global', name: 'out2eq1', pidLow: 0x0001, pidHigh: 0x0022, unit: 'db', displayMin: -12, displayMax: 12 },
4209
+ // GEQ band ±12 dB convention — HW unverified
4210
+ 'global.out2eq2': { block: 'global', name: 'out2eq2', pidLow: 0x0001, pidHigh: 0x0023, unit: 'db', displayMin: -12, displayMax: 12 },
4211
+ // GEQ band ±12 dB convention — HW unverified
4212
+ 'global.out2eq3': { block: 'global', name: 'out2eq3', pidLow: 0x0001, pidHigh: 0x0024, unit: 'db', displayMin: -12, displayMax: 12 },
4213
+ // GEQ band ±12 dB convention — HW unverified
4214
+ 'global.out2eq4': { block: 'global', name: 'out2eq4', pidLow: 0x0001, pidHigh: 0x0025, unit: 'db', displayMin: -12, displayMax: 12 },
4215
+ // GEQ band ±12 dB convention — HW unverified
4216
+ 'global.out2eq5': { block: 'global', name: 'out2eq5', pidLow: 0x0001, pidHigh: 0x0026, unit: 'db', displayMin: -12, displayMax: 12 },
4217
+ // GEQ band ±12 dB convention — HW unverified
4218
+ 'global.out2eq6': { block: 'global', name: 'out2eq6', pidLow: 0x0001, pidHigh: 0x0027, unit: 'db', displayMin: -12, displayMax: 12 },
4219
+ // GEQ band ±12 dB convention — HW unverified
4220
+ 'global.out2eq7': { block: 'global', name: 'out2eq7', pidLow: 0x0001, pidHigh: 0x0028, unit: 'db', displayMin: -12, displayMax: 12 },
4221
+ // GEQ band ±12 dB convention — HW unverified
4222
+ 'global.out2eq8': { block: 'global', name: 'out2eq8', pidLow: 0x0001, pidHigh: 0x0029, unit: 'db', displayMin: -12, displayMax: 12 },
4223
+ // GEQ band ±12 dB convention — HW unverified
4224
+ 'global.out2eq9': { block: 'global', name: 'out2eq9', pidLow: 0x0001, pidHigh: 0x002a, unit: 'db', displayMin: -12, displayMax: 12 },
4225
+ // GEQ band ±12 dB convention — HW unverified
4226
+ 'global.out2eq10': { block: 'global', name: 'out2eq10', pidLow: 0x0001, pidHigh: 0x002b, unit: 'db', displayMin: -12, displayMax: 12 },
4227
+ // gate threshold offset dB — HW unverified
4228
+ 'global.gate_offset': { block: 'global', name: 'gate_offset', displayLabel: "Noisegate Offset", pidLow: 0x0001, pidHigh: 0x002d, unit: 'db', displayMin: -40, displayMax: 0 },
4229
+ // HW-112 (Session 96) — captured at 1.0 = "Last Two"; full enum table pending HW
4230
+ 'global.tap_tempo_mode': { block: 'global', name: 'tap_tempo_mode', displayLabel: "Tap Tempo Mode", pidLow: 0x0001, pidHigh: 0x002e, unit: 'enum', displayMin: 0, displayMax: 7 },
4231
+ // input trim percent — HW unverified
4232
+ 'global.in1_trim': { block: 'global', name: 'in1_trim', displayLabel: "Input Pad", pidLow: 0x0001, pidHigh: 0x002f, unit: 'percent', displayMin: 0, displayMax: 100 },
4233
+ // safe placeholder (range unverified) — Ghidra catalog entry only
4234
+ 'global.out1_config': { block: 'global', name: 'out1_config', displayLabel: "Output Mode", pidLow: 0x0001, pidHigh: 0x0030, unit: 'count', displayMin: 0, displayMax: 127 },
4235
+ // safe placeholder (range unverified) — Ghidra catalog entry only
4236
+ 'global.out1_phase': { block: 'global', name: 'out1_phase', displayLabel: "Output Phase", pidLow: 0x0001, pidHigh: 0x0031, unit: 'count', displayMin: 0, displayMax: 127 },
4237
+ // safe placeholder (range unverified) — Ghidra catalog entry only
4238
+ 'global.in1_source': { block: 'global', name: 'in1_source', displayLabel: "Input Source", pidLow: 0x0001, pidHigh: 0x0034, unit: 'count', displayMin: 0, displayMax: 127 },
4239
+ // safe placeholder (range unverified) — Ghidra catalog entry only
4240
+ 'global.in1_config': { block: 'global', name: 'in1_config', pidLow: 0x0001, pidHigh: 0x0035, unit: 'count', displayMin: 0, displayMax: 127 },
4241
+ // percent inferred from AM4-Edit display — HW unverified
4242
+ 'global.lcd_contrast': { block: 'global', name: 'lcd_contrast', displayLabel: "LCD Contrast", pidLow: 0x0001, pidHigh: 0x0038, unit: 'percent', displayMin: 0, displayMax: 100 },
4243
+ // press-hold timeout ms — HW unverified
4244
+ 'global.fc_hold_timeout': { block: 'global', name: 'fc_hold_timeout', displayLabel: "Hold Timeout", pidLow: 0x0001, pidHigh: 0x0039, unit: 'ms', displayMin: 0, displayMax: 5000 },
4245
+ // MIDI channel 1..16
4246
+ 'global.midi_chan': { block: 'global', name: 'midi_chan', displayLabel: "MIDI Channel", pidLow: 0x0001, pidHigh: 0x003a, unit: 'count', displayMin: 1, displayMax: 16 },
4247
+ // safe placeholder (range unverified) — Ghidra catalog entry only
4248
+ 'global.midi_prog_change': { block: 'global', name: 'midi_prog_change', displayLabel: "Receive MIDI PC", pidLow: 0x0001, pidHigh: 0x003b, unit: 'count', displayMin: 0, displayMax: 127 },
4249
+ // safe placeholder (range unverified) — Ghidra catalog entry only
4250
+ 'global.no_redundant_pc': { block: 'global', name: 'no_redundant_pc', displayLabel: "Ignore Redundant PC", pidLow: 0x0001, pidHigh: 0x003c, unit: 'count', displayMin: 0, displayMax: 127 },
4251
+ // safe placeholder (range unverified) — Ghidra catalog entry only
4252
+ 'global.send_midipc': { block: 'global', name: 'send_midipc', displayLabel: "Send MIDI PC", pidLow: 0x0001, pidHigh: 0x003f, unit: 'count', displayMin: 0, displayMax: 127 },
4253
+ 'global.in1_vol_cc': { block: 'global', name: 'in1_vol_cc', displayLabel: "Input Volume", pidLow: 0x0001, pidHigh: 0x0046, unit: 'count', displayMin: 0, displayMax: 127 },
4254
+ 'global.out1_vol_cc': { block: 'global', name: 'out1_vol_cc', displayLabel: "Output Volume", pidLow: 0x0001, pidHigh: 0x0047, unit: 'count', displayMin: 0, displayMax: 127 },
4255
+ 'global.tempo_cc': { block: 'global', name: 'tempo_cc', displayLabel: "Tap Tempo", pidLow: 0x0001, pidHigh: 0x0048, unit: 'count', displayMin: 0, displayMax: 127 },
4256
+ 'global.tuner_cc': { block: 'global', name: 'tuner_cc', displayLabel: "Tuner", pidLow: 0x0001, pidHigh: 0x0049, unit: 'count', displayMin: 0, displayMax: 127 },
4257
+ 'global.scene_cc': { block: 'global', name: 'scene_cc', displayLabel: "Scene Select", pidLow: 0x0001, pidHigh: 0x004a, unit: 'count', displayMin: 0, displayMax: 127 },
4258
+ 'global.scene_incr_cc': { block: 'global', name: 'scene_incr_cc', displayLabel: "Scene +1", pidLow: 0x0001, pidHigh: 0x004b, unit: 'count', displayMin: 0, displayMax: 127 },
4259
+ 'global.scene_decr_cc': { block: 'global', name: 'scene_decr_cc', displayLabel: "Scene -1", pidLow: 0x0001, pidHigh: 0x004c, unit: 'count', displayMin: 0, displayMax: 127 },
4260
+ // safe placeholder (range unverified) — Ghidra catalog entry only
4261
+ 'global.scene_revert': { block: 'global', name: 'scene_revert', pidLow: 0x0001, pidHigh: 0x004d, unit: 'count', displayMin: 0, displayMax: 127 },
4262
+ // safe placeholder (range unverified) — Ghidra catalog entry only
4263
+ 'global.custom_scale': { block: 'global', name: 'custom_scale', pidLow: 0x0001, pidHigh: 0x0050, unit: 'count', displayMin: 0, displayMax: 127 },
4264
+ // safe placeholder (range unverified) — Ghidra catalog entry only
4265
+ 'global.tuner_source': { block: 'global', name: 'tuner_source', pidLow: 0x0001, pidHigh: 0x0053, unit: 'count', displayMin: 0, displayMax: 127 },
4266
+ // AM4 has 4 scenes (1..4)
4267
+ 'global.default_scene': { block: 'global', name: 'default_scene', displayLabel: "Default Scene", pidLow: 0x0001, pidHigh: 0x0056, unit: 'count', displayMin: 1, displayMax: 4 },
4268
+ // percent inferred from AM4-Edit display — HW unverified
4269
+ 'global.fc_ring_bright_level': { block: 'global', name: 'fc_ring_bright_level', displayLabel: "Switch LED Bright", pidLow: 0x0001, pidHigh: 0x0058, unit: 'percent', displayMin: 0, displayMax: 100 },
4270
+ // percent inferred from AM4-Edit display — HW unverified
4271
+ 'global.fc_ring_dim_level': { block: 'global', name: 'fc_ring_dim_level', displayLabel: "Switch LED Dim", pidLow: 0x0001, pidHigh: 0x0059, unit: 'percent', displayMin: 0, displayMax: 100 },
4272
+ // safe placeholder (range unverified) — Ghidra catalog entry only
4273
+ 'global.linefreq': { block: 'global', name: 'linefreq', displayLabel: "AC Line Frequency", pidLow: 0x0001, pidHigh: 0x005a, unit: 'count', displayMin: 0, displayMax: 127 },
4274
+ 'global.preset_incr_cc': { block: 'global', name: 'preset_incr_cc', displayLabel: "Preset +1", pidLow: 0x0001, pidHigh: 0x005d, unit: 'count', displayMin: 0, displayMax: 127 },
4275
+ 'global.preset_decr_cc': { block: 'global', name: 'preset_decr_cc', displayLabel: "Preset -1", pidLow: 0x0001, pidHigh: 0x005e, unit: 'count', displayMin: 0, displayMax: 127 },
4276
+ // unit inferred from USBLEVEL1 sibling — HW unverified
4277
+ 'global.metlevel1': { block: 'global', name: 'metlevel1', displayLabel: "Metronome Level", pidLow: 0x0001, pidHigh: 0x0061, unit: 'db', displayMin: -64, displayMax: 24 },
4278
+ // safe placeholder (range unverified) — Ghidra catalog entry only
4279
+ 'global.usb78_source': { block: 'global', name: 'usb78_source', displayLabel: "USB 3/4 Record Source", pidLow: 0x0001, pidHigh: 0x0062, unit: 'count', displayMin: 0, displayMax: 127 },
4280
+ // HW-112 (Session 96) — captured at 1.11 dB
4281
+ 'global.usblevel1': { block: 'global', name: 'usblevel1', displayLabel: "USB 1/2 Level", pidLow: 0x0001, pidHigh: 0x0063, unit: 'db', displayMin: -64, displayMax: 24 },
4282
+ // unit inferred from USBLEVEL1 sibling — HW unverified
4283
+ 'global.usblevel2': { block: 'global', name: 'usblevel2', displayLabel: "USB 3/4 Level", pidLow: 0x0001, pidHigh: 0x0064, unit: 'db', displayMin: -64, displayMax: 24 },
4284
+ // unit inferred from USBLEVEL1 sibling — HW unverified
4285
+ 'global.aeslevel': { block: 'global', name: 'aeslevel', displayLabel: "SPDIF In Level", pidLow: 0x0001, pidHigh: 0x0065, unit: 'db', displayMin: -64, displayMax: 24 },
4286
+ // down-tune semitones — HW unverified
4287
+ 'global.downtune': { block: 'global', name: 'downtune', displayLabel: "Downtune", pidLow: 0x0001, pidHigh: 0x0067, unit: 'semitones', displayMin: -12, displayMax: 0 },
4288
+ // safe placeholder (range unverified) — Ghidra catalog entry only
4289
+ 'global.tuneraccidentals': { block: 'global', name: 'tuneraccidentals', displayLabel: "Display Mode", pidLow: 0x0001, pidHigh: 0x0068, unit: 'count', displayMin: 0, displayMax: 127 },
4290
+ // safe placeholder (range unverified) — Ghidra catalog entry only
4291
+ 'global.midi_thru': { block: 'global', name: 'midi_thru', displayLabel: "MIDI Thru", pidLow: 0x0001, pidHigh: 0x006d, unit: 'count', displayMin: 0, displayMax: 127 },
4292
+ // safe placeholder (range unverified) — Ghidra catalog entry only
4293
+ 'global.tuner_on_volume': { block: 'global', name: 'tuner_on_volume', displayLabel: "Tuner on Heel Down", pidLow: 0x0001, pidHigh: 0x006e, unit: 'count', displayMin: 0, displayMax: 127 },
4294
+ 'global.bypass_fx1_cc': { block: 'global', name: 'bypass_fx1_cc', displayLabel: "FX1 Bypass", pidLow: 0x0001, pidHigh: 0x006f, unit: 'count', displayMin: 0, displayMax: 127 },
4295
+ 'global.bypass_fx2_cc': { block: 'global', name: 'bypass_fx2_cc', displayLabel: "FX2 Bypass", pidLow: 0x0001, pidHigh: 0x0070, unit: 'count', displayMin: 0, displayMax: 127 },
4296
+ 'global.bypass_fx3_cc': { block: 'global', name: 'bypass_fx3_cc', displayLabel: "FX3 Bypass", pidLow: 0x0001, pidHigh: 0x0071, unit: 'count', displayMin: 0, displayMax: 127 },
4297
+ 'global.bypass_fx4_cc': { block: 'global', name: 'bypass_fx4_cc', displayLabel: "FX4 Bypass", pidLow: 0x0001, pidHigh: 0x0072, unit: 'count', displayMin: 0, displayMax: 127 },
4298
+ 'global.channel_fx1_cc': { block: 'global', name: 'channel_fx1_cc', displayLabel: "FX1 Channel", pidLow: 0x0001, pidHigh: 0x0073, unit: 'count', displayMin: 0, displayMax: 127 },
4299
+ 'global.channel_fx2_cc': { block: 'global', name: 'channel_fx2_cc', displayLabel: "FX2 Channel", pidLow: 0x0001, pidHigh: 0x0074, unit: 'count', displayMin: 0, displayMax: 127 },
4300
+ 'global.channel_fx3_cc': { block: 'global', name: 'channel_fx3_cc', displayLabel: "FX3 Channel", pidLow: 0x0001, pidHigh: 0x0075, unit: 'count', displayMin: 0, displayMax: 127 },
4301
+ 'global.channel_fx4_cc': { block: 'global', name: 'channel_fx4_cc', displayLabel: "FX4 Channel", pidLow: 0x0001, pidHigh: 0x0076, unit: 'count', displayMin: 0, displayMax: 127 },
4302
+ // external CC routing — CC number 0..127
4303
+ 'global.ext_cc_begin': { block: 'global', name: 'ext_cc_begin', displayLabel: "External 1", pidLow: 0x0001, pidHigh: 0x0077, unit: 'count', displayMin: 0, displayMax: 127 },
4304
+ // external CC routing — CC number 0..127
4305
+ 'global.ext_cc_begin_1': { block: 'global', name: 'ext_cc_begin_1', pidLow: 0x0001, pidHigh: 0x0078, unit: 'count', displayMin: 0, displayMax: 127 },
4306
+ // external CC routing — CC number 0..127
4307
+ 'global.ext_cc_begin_2': { block: 'global', name: 'ext_cc_begin_2', pidLow: 0x0001, pidHigh: 0x0079, unit: 'count', displayMin: 0, displayMax: 127 },
4308
+ // external CC routing — CC number 0..127
4309
+ 'global.ext_cc_begin_3': { block: 'global', name: 'ext_cc_begin_3', pidLow: 0x0001, pidHigh: 0x007a, unit: 'count', displayMin: 0, displayMax: 127 },
4310
+ // external CC initial value 0..127
4311
+ 'global.ext_startval_begin': { block: 'global', name: 'ext_startval_begin', displayLabel: "External 1", pidLow: 0x0001, pidHigh: 0x007b, unit: 'count', displayMin: 0, displayMax: 127 },
4312
+ // external CC initial value 0..127
4313
+ 'global.ext_startval_begin_1': { block: 'global', name: 'ext_startval_begin_1', pidLow: 0x0001, pidHigh: 0x007c, unit: 'count', displayMin: 0, displayMax: 127 },
4314
+ // external CC initial value 0..127
4315
+ 'global.ext_startval_begin_2': { block: 'global', name: 'ext_startval_begin_2', pidLow: 0x0001, pidHigh: 0x007d, unit: 'count', displayMin: 0, displayMax: 127 },
4316
+ // external CC initial value 0..127
4317
+ 'global.ext_startval_begin_3': { block: 'global', name: 'ext_startval_begin_3', pidLow: 0x0001, pidHigh: 0x007e, unit: 'count', displayMin: 0, displayMax: 127 },
4318
+ // safe placeholder (range unverified) — Ghidra catalog entry only
4319
+ 'global.auto_truebypass': { block: 'global', name: 'auto_truebypass', displayLabel: "Automatic AM4 Bypass", pidLow: 0x0001, pidHigh: 0x0081, unit: 'count', displayMin: 0, displayMax: 127 },
4320
+ 'global.truebypass_cc': { block: 'global', name: 'truebypass_cc', displayLabel: "AM4 Bypass", pidLow: 0x0001, pidHigh: 0x0083, unit: 'count', displayMin: 0, displayMax: 127 },
4321
+ // press-hold timeout ms — HW unverified
4322
+ 'global.fs_press_hold1': { block: 'global', name: 'fs_press_hold1', displayLabel: "Press & Hold 1", pidLow: 0x0001, pidHigh: 0x0084, unit: 'ms', displayMin: 0, displayMax: 5000 },
4323
+ // press-hold timeout ms — HW unverified
4324
+ 'global.fs_press_hold2': { block: 'global', name: 'fs_press_hold2', displayLabel: "Press & Hold 2", pidLow: 0x0001, pidHigh: 0x0085, unit: 'ms', displayMin: 0, displayMax: 5000 },
4325
+ // press-hold timeout ms — HW unverified
4326
+ 'global.fs_press_hold3': { block: 'global', name: 'fs_press_hold3', displayLabel: "Press & Hold 3", pidLow: 0x0001, pidHigh: 0x0086, unit: 'ms', displayMin: 0, displayMax: 5000 },
4327
+ // press-hold timeout ms — HW unverified
4328
+ 'global.fs_press_hold4': { block: 'global', name: 'fs_press_hold4', displayLabel: "Press & Hold 4", pidLow: 0x0001, pidHigh: 0x0087, unit: 'ms', displayMin: 0, displayMax: 5000 },
4329
+ // safe placeholder (range unverified) — Ghidra catalog entry only
4330
+ 'global.startup_mode': { block: 'global', name: 'startup_mode', displayLabel: "Startup Mode", pidLow: 0x0001, pidHigh: 0x0089, unit: 'count', displayMin: 0, displayMax: 127 },
4331
+ // safe placeholder (range unverified) — Ghidra catalog entry only
4332
+ 'global.gap_fill': { block: 'global', name: 'gap_fill', displayLabel: "Gapless Changes", pidLow: 0x0001, pidHigh: 0x008f, unit: 'count', displayMin: 0, displayMax: 127 },
4333
+ // safe placeholder (range unverified) — Ghidra catalog entry only
4334
+ 'global.select_fade': { block: 'global', name: 'select_fade', displayLabel: "Fade Timeout", pidLow: 0x0001, pidHigh: 0x0091, unit: 'count', displayMin: 0, displayMax: 127 },
4335
+ // safe placeholder (range unverified) — Ghidra catalog entry only
4336
+ 'global.presshold_mode': { block: 'global', name: 'presshold_mode', displayLabel: "Press & Hold Mode", pidLow: 0x0001, pidHigh: 0x0092, unit: 'count', displayMin: 0, displayMax: 127 },
4337
+ // safe placeholder (range unverified) — Ghidra catalog entry only
4338
+ 'global.tap_amp_fx_mode': { block: 'global', name: 'tap_amp_fx_mode', displayLabel: "Tap Amp in FX Mode", pidLow: 0x0001, pidHigh: 0x0093, unit: 'count', displayMin: 0, displayMax: 127 },
4339
+ // safe placeholder (range unverified) — Ghidra catalog entry only
4340
+ 'global.tap_amp_ch_amp_mode': { block: 'global', name: 'tap_amp_ch_amp_mode', displayLabel: "Tap Current Ch. in Amp Mode", pidLow: 0x0001, pidHigh: 0x0094, unit: 'count', displayMin: 0, displayMax: 127 },
4341
+ // safe placeholder (range unverified) — Ghidra catalog entry only
4342
+ 'global.cabinetbyp': { block: 'global', name: 'cabinetbyp', displayLabel: "Cab Modeling", pidLow: 0x0001, pidHigh: 0x0095, unit: 'count', displayMin: 0, displayMax: 127 },
4343
+ // safe placeholder (range unverified) — Ghidra catalog entry only
4344
+ 'global.pwrampbyp': { block: 'global', name: 'pwrampbyp', displayLabel: "Power Amp Modeling", pidLow: 0x0001, pidHigh: 0x0096, unit: 'count', displayMin: 0, displayMax: 127 },
4345
+ // safe placeholder (range unverified) — Ghidra catalog entry only
4346
+ 'global.sprk_model': { block: 'global', name: 'sprk_model', displayLabel: "Speaker Imp. Curve", pidLow: 0x0001, pidHigh: 0x0097, unit: 'count', displayMin: 0, displayMax: 127 },
4347
+ 'global.amp_chan_cc': { block: 'global', name: 'amp_chan_cc', displayLabel: "Amp Channel", pidLow: 0x0001, pidHigh: 0x0098, unit: 'count', displayMin: 0, displayMax: 127 },
4348
+ 'global.out_boost_cc': { block: 'global', name: 'out_boost_cc', displayLabel: "Amp Out Boost", pidLow: 0x0001, pidHigh: 0x0099, unit: 'count', displayMin: 0, displayMax: 127 },
4349
+ // safe placeholder (range unverified) — Ghidra catalog entry only
4350
+ 'global.scenesync_ch': { block: 'global', name: 'scenesync_ch', displayLabel: "Scene Sync Channel", pidLow: 0x0001, pidHigh: 0x009c, unit: 'count', displayMin: 0, displayMax: 127 },
4351
+ 'global.scenesync_cc': { block: 'global', name: 'scenesync_cc', displayLabel: "Scene Sync CC#", pidLow: 0x0001, pidHigh: 0x009d, unit: 'count', displayMin: 0, displayMax: 127 },
4352
+ // safe placeholder (range unverified) — Ghidra catalog entry only
4353
+ 'global.dynacab_sync': { block: 'global', name: 'dynacab_sync', pidLow: 0x0001, pidHigh: 0x009e, unit: 'count', displayMin: 0, displayMax: 127 },
4354
+ 'global.amp1_vol_cc': { block: 'global', name: 'amp1_vol_cc', displayLabel: "Amp Block Out Vol", pidLow: 0x0001, pidHigh: 0x009f, unit: 'count', displayMin: 0, displayMax: 127 },
4355
+ // safe placeholder (range unverified) — Ghidra catalog entry only
4356
+ 'global.metronome': { block: 'global', name: 'metronome', displayLabel: "Metronome", pidLow: 0x0001, pidHigh: 0x00a0, unit: 'count', displayMin: 0, displayMax: 127 },
4357
+ 'global.metronome_cc': { block: 'global', name: 'metronome_cc', displayLabel: "Metronome", pidLow: 0x0001, pidHigh: 0x00a1, unit: 'count', displayMin: 0, displayMax: 127 },
4358
+ // safe placeholder (range unverified) — Ghidra catalog entry only
4359
+ 'global.inspdif_config': { block: 'global', name: 'inspdif_config', displayLabel: "SPDIF Input Mode", pidLow: 0x0001, pidHigh: 0x00a2, unit: 'count', displayMin: 0, displayMax: 127 },
4360
+ // ============================================================
4361
+ // Session 96 (2026-05-17) UI-MISSING closeout — wires the
4362
+ // remaining placeable-family entries the AM4-Edit XML exposes
4363
+ // but params.ts didn't carry. Catalog symbols sourced from
4364
+ // `samples/captured/decoded/ghidra-am4-paramnames.json`,
4365
+ // display labels from `__block_layout.xml` /
4366
+ // `__block_layout_expert.xml` (mined by
4367
+ // `scripts/_research/list-ui-missing.ts`).
4368
+ //
4369
+ // UI widgets (paramId >= 65000 — name/label/button/graph/menu
4370
+ // sentinels) intentionally skipped: those addresses back UI
4371
+ // chrome in AM4-Edit, not writable preset data. Catalog symbols
4372
+ // dropped: CABINET_NAME{1,2}, CABINET_LABEL{1,2},
4373
+ // CABINET_ALIGN_*, CABINET_COPY_MENU{1,2}, DISTORT_ZEROEQ.
4374
+ //
4375
+ // Unit/range pairs are name-inferred. Toggle-style switches
4376
+ // (*_SW / *_ONOFF) get enum 0..1 with OFF/ON. Type/menu/color
4377
+ // enums without captured enum tables default to count 0..127 so
4378
+ // the agent can write any in-range value without claiming an
4379
+ // interpretation. Knob-style names (Sag / Breakup / Compensation
4380
+ // / Pres. Shift) get knob_0_10 — a reasonable convention for the
4381
+ // amp Extras / Speaker pages until HW captures pin exact ranges.
4382
+ // ---- CABINET (pidLow=0x003e) — 4 entries ----
4383
+ 'amp.cab_proximity_2': { block: 'amp', name: 'cab_proximity_2', displayLabel: 'Proximity', pidLow: 0x003e, pidHigh: 0x0015, unit: 'percent', displayMin: 0, displayMax: 100 },
4384
+ 'amp.cab_zoom': { block: 'amp', name: 'cab_zoom', displayLabel: "ZOOM", pidLow: 0x003e, pidHigh: 0x0021, unit: 'count', displayMin: 0, displayMax: 127 },
4385
+ 'amp.cab_dynacab_z_1': { block: 'amp', name: 'cab_dynacab_z_1', displayLabel: 'Distance', pidLow: 0x003e, pidHigh: 0x0047, unit: 'percent', displayMin: 0, displayMax: 100 },
4386
+ 'amp.cab_dynacab_z_2': { block: 'amp', name: 'cab_dynacab_z_2', displayLabel: 'Distance', pidLow: 0x003e, pidHigh: 0x0048, unit: 'percent', displayMin: 0, displayMax: 100 },
4387
+ // ---- DISTORT / amp Extras + Speaker pages (pidLow=0x003a) — 18 entries ----
4388
+ 'amp.in_boost_sw': { block: 'amp', name: 'in_boost_sw', displayLabel: 'In Boost Sw', pidLow: 0x003a, pidHigh: 0x002f, unit: 'enum', displayMin: 0, displayMax: 1, enumValues: { 0: 'OFF', 1: 'ON' } },
4389
+ 'amp.saturation_sw': { block: 'amp', name: 'saturation_sw', displayLabel: 'Saturation Sw', pidLow: 0x003a, pidHigh: 0x003d, unit: 'enum', displayMin: 0, displayMax: 1, enumValues: { 0: 'OFF', 1: 'ON' } },
4390
+ 'amp.preamp_tube_type': { block: 'amp', name: 'preamp_tube_type', displayLabel: 'Preamp Tube Type', pidLow: 0x003a, pidHigh: 0x004c, unit: 'count', displayMin: 0, displayMax: 127 },
4391
+ 'amp.power_type': { block: 'amp', name: 'power_type', displayLabel: 'Power Type', pidLow: 0x003a, pidHigh: 0x005d, unit: 'count', displayMin: 0, displayMax: 127 },
4392
+ 'amp.preamp_sag': { block: 'amp', name: 'preamp_sag', displayLabel: 'Preamp Sag', pidLow: 0x003a, pidHigh: 0x0067, unit: 'knob_0_10', displayMin: 0, displayMax: 10 },
4393
+ 'amp.in_eq_type': { block: 'amp', name: 'in_eq_type', displayLabel: 'Type', pidLow: 0x003a, pidHigh: 0x006d, unit: 'count', displayMin: 0, displayMax: 127 },
4394
+ 'amp.pres_shift': { block: 'amp', name: 'pres_shift', displayLabel: 'Pres. Shift', pidLow: 0x003a, pidHigh: 0x006f, unit: 'knob_0_10', displayMin: 0, displayMax: 10 },
4395
+ 'amp.eq_location': { block: 'amp', name: 'eq_location', displayLabel: 'Location', pidLow: 0x003a, pidHigh: 0x0075, unit: 'count', displayMin: 0, displayMax: 127 },
4396
+ 'amp.in_boost_type': { block: 'amp', name: 'in_boost_type', displayLabel: 'In Boost Type', pidLow: 0x003a, pidHigh: 0x0082, unit: 'count', displayMin: 0, displayMax: 127 },
4397
+ 'amp.eq_onoff': { block: 'amp', name: 'eq_onoff', displayLabel: 'Off / On', pidLow: 0x003a, pidHigh: 0x0085, unit: 'enum', displayMin: 0, displayMax: 1, enumValues: { 0: 'OFF', 1: 'ON' } },
4398
+ 'amp.spkr_imp_curve': { block: 'amp', name: 'spkr_imp_curve', displayLabel: 'Spkr Imp. Curve', pidLow: 0x003a, pidHigh: 0x0087, unit: 'count', displayMin: 0, displayMax: 127 },
4399
+ 'amp.power_amp_modeling': { block: 'amp', name: 'power_amp_modeling', displayLabel: 'Power Amp Modeling', pidLow: 0x003a, pidHigh: 0x008d, unit: 'enum', displayMin: 0, displayMax: 1, enumValues: { 0: 'OFF', 1: 'ON' } },
4400
+ 'amp.spkr_breakup': { block: 'amp', name: 'spkr_breakup', displayLabel: 'Breakup', pidLow: 0x003a, pidHigh: 0x008e, unit: 'knob_0_10', displayMin: 0, displayMax: 10 },
4401
+ 'amp.plate_suppr_diodes': { block: 'amp', name: 'plate_suppr_diodes', displayLabel: 'Plate Suppr. Diodes', pidLow: 0x003a, pidHigh: 0x0090, unit: 'count', displayMin: 0, displayMax: 127 },
4402
+ 'amp.dynamatch': { block: 'amp', name: 'dynamatch', displayLabel: 'DynaMatch', pidLow: 0x003a, pidHigh: 0x0092, unit: 'enum', displayMin: 0, displayMax: 1, enumValues: { 0: 'OFF', 1: 'ON' } },
4403
+ 'amp.nfb_compensation': { block: 'amp', name: 'nfb_compensation', displayLabel: 'NFB Compensation', pidLow: 0x003a, pidHigh: 0x0093, unit: 'knob_0_10', displayMin: 0, displayMax: 10 },
4404
+ 'amp.mid_gain_boost': { block: 'amp', name: 'mid_gain_boost', displayLabel: 'Mid/Gain Boost', pidLow: 0x003a, pidHigh: 0x0094, unit: 'enum', displayMin: 0, displayMax: 1, enumValues: { 0: 'OFF', 1: 'ON' } },
4405
+ 'amp.tubes': { block: 'amp', name: 'tubes', displayLabel: 'Tubes', pidLow: 0x003a, pidHigh: 0x0095, unit: 'count', displayMin: 0, displayMax: 127 },
4406
+ // ---- PATCH (pidLow=0x00ce) — 29 entries ----
4407
+ // Channel A/B/C/D color pickers. AM4-Edit shows ~8 named colors;
4408
+ // exact enum table not yet captured, so count 0..127 as
4409
+ // safe-write placeholder.
4410
+ 'preset.channel_a_color': { block: 'preset', name: 'channel_a_color', displayLabel: 'Channel A Color', pidLow: 0x00ce, pidHigh: 0x0071, unit: 'count', displayMin: 0, displayMax: 127 },
4411
+ 'preset.channel_b_color': { block: 'preset', name: 'channel_b_color', displayLabel: 'Channel B Color', pidLow: 0x00ce, pidHigh: 0x0072, unit: 'count', displayMin: 0, displayMax: 127 },
4412
+ 'preset.channel_c_color': { block: 'preset', name: 'channel_c_color', displayLabel: 'Channel C Color', pidLow: 0x00ce, pidHigh: 0x0073, unit: 'count', displayMin: 0, displayMax: 127 },
4413
+ 'preset.channel_d_color': { block: 'preset', name: 'channel_d_color', displayLabel: 'Channel D Color', pidLow: 0x00ce, pidHigh: 0x0074, unit: 'count', displayMin: 0, displayMax: 127 },
4414
+ // Scene MIDI EXEC slots (paramId 118..137 — 4 + 16 = 20).
4415
+ // XML labels are empty for these — they're PATCH-page editor
4416
+ // infrastructure. Catalog still lists them as writable params,
4417
+ // so we ship them as count placeholders for programmatic access
4418
+ // (copy/clear/inspect scene-MIDI command state from a script).
4419
+ // Naming mirrors the existing preset.scene_N_midi_M_{type,
4420
+ // channel,value} pattern shipping at pidHigh 0x40..0x67.
4421
+ 'preset.scene_1_midi_exec': { block: 'preset', name: 'scene_1_midi_exec', pidLow: 0x00ce, pidHigh: 0x0076, unit: 'count', displayMin: 0, displayMax: 127 },
4422
+ 'preset.scene_2_midi_exec': { block: 'preset', name: 'scene_2_midi_exec', pidLow: 0x00ce, pidHigh: 0x0077, unit: 'count', displayMin: 0, displayMax: 127 },
4423
+ 'preset.scene_3_midi_exec': { block: 'preset', name: 'scene_3_midi_exec', pidLow: 0x00ce, pidHigh: 0x0078, unit: 'count', displayMin: 0, displayMax: 127 },
4424
+ 'preset.scene_4_midi_exec': { block: 'preset', name: 'scene_4_midi_exec', pidLow: 0x00ce, pidHigh: 0x0079, unit: 'count', displayMin: 0, displayMax: 127 },
4425
+ 'preset.scene_1_midi_exec_1': { block: 'preset', name: 'scene_1_midi_exec_1', pidLow: 0x00ce, pidHigh: 0x007a, unit: 'count', displayMin: 0, displayMax: 127 },
4426
+ 'preset.scene_1_midi_exec_2': { block: 'preset', name: 'scene_1_midi_exec_2', pidLow: 0x00ce, pidHigh: 0x007b, unit: 'count', displayMin: 0, displayMax: 127 },
4427
+ 'preset.scene_1_midi_exec_3': { block: 'preset', name: 'scene_1_midi_exec_3', pidLow: 0x00ce, pidHigh: 0x007c, unit: 'count', displayMin: 0, displayMax: 127 },
4428
+ 'preset.scene_1_midi_exec_4': { block: 'preset', name: 'scene_1_midi_exec_4', pidLow: 0x00ce, pidHigh: 0x007d, unit: 'count', displayMin: 0, displayMax: 127 },
4429
+ 'preset.scene_2_midi_exec_1': { block: 'preset', name: 'scene_2_midi_exec_1', pidLow: 0x00ce, pidHigh: 0x007e, unit: 'count', displayMin: 0, displayMax: 127 },
4430
+ 'preset.scene_2_midi_exec_2': { block: 'preset', name: 'scene_2_midi_exec_2', pidLow: 0x00ce, pidHigh: 0x007f, unit: 'count', displayMin: 0, displayMax: 127 },
4431
+ 'preset.scene_2_midi_exec_3': { block: 'preset', name: 'scene_2_midi_exec_3', pidLow: 0x00ce, pidHigh: 0x0080, unit: 'count', displayMin: 0, displayMax: 127 },
4432
+ 'preset.scene_2_midi_exec_4': { block: 'preset', name: 'scene_2_midi_exec_4', pidLow: 0x00ce, pidHigh: 0x0081, unit: 'count', displayMin: 0, displayMax: 127 },
4433
+ 'preset.scene_3_midi_exec_1': { block: 'preset', name: 'scene_3_midi_exec_1', pidLow: 0x00ce, pidHigh: 0x0082, unit: 'count', displayMin: 0, displayMax: 127 },
4434
+ 'preset.scene_3_midi_exec_2': { block: 'preset', name: 'scene_3_midi_exec_2', pidLow: 0x00ce, pidHigh: 0x0083, unit: 'count', displayMin: 0, displayMax: 127 },
4435
+ 'preset.scene_3_midi_exec_3': { block: 'preset', name: 'scene_3_midi_exec_3', pidLow: 0x00ce, pidHigh: 0x0084, unit: 'count', displayMin: 0, displayMax: 127 },
4436
+ 'preset.scene_3_midi_exec_4': { block: 'preset', name: 'scene_3_midi_exec_4', pidLow: 0x00ce, pidHigh: 0x0085, unit: 'count', displayMin: 0, displayMax: 127 },
4437
+ 'preset.scene_4_midi_exec_1': { block: 'preset', name: 'scene_4_midi_exec_1', pidLow: 0x00ce, pidHigh: 0x0086, unit: 'count', displayMin: 0, displayMax: 127 },
4438
+ 'preset.scene_4_midi_exec_2': { block: 'preset', name: 'scene_4_midi_exec_2', pidLow: 0x00ce, pidHigh: 0x0087, unit: 'count', displayMin: 0, displayMax: 127 },
4439
+ 'preset.scene_4_midi_exec_3': { block: 'preset', name: 'scene_4_midi_exec_3', pidLow: 0x00ce, pidHigh: 0x0088, unit: 'count', displayMin: 0, displayMax: 127 },
4440
+ 'preset.scene_4_midi_exec_4': { block: 'preset', name: 'scene_4_midi_exec_4', pidLow: 0x00ce, pidHigh: 0x0089, unit: 'count', displayMin: 0, displayMax: 127 },
4441
+ // Per-scene MIDI menu trigger + clear-all action.
4442
+ 'preset.scene_1_menu': { block: 'preset', name: 'scene_1_menu', displayLabel: 'SCENE 1 menu', pidLow: 0x00ce, pidHigh: 0x008a, unit: 'count', displayMin: 0, displayMax: 127 },
4443
+ 'preset.scene_2_menu': { block: 'preset', name: 'scene_2_menu', displayLabel: 'SCENE 2 menu', pidLow: 0x00ce, pidHigh: 0x008b, unit: 'count', displayMin: 0, displayMax: 127 },
4444
+ 'preset.scene_3_menu': { block: 'preset', name: 'scene_3_menu', displayLabel: 'SCENE 3 menu', pidLow: 0x00ce, pidHigh: 0x008c, unit: 'count', displayMin: 0, displayMax: 127 },
4445
+ 'preset.scene_4_menu': { block: 'preset', name: 'scene_4_menu', displayLabel: 'SCENE 4 menu', pidLow: 0x00ce, pidHigh: 0x008d, unit: 'count', displayMin: 0, displayMax: 127 },
4446
+ 'preset.clear_all': { block: 'preset', name: 'clear_all', displayLabel: 'Clear All', pidLow: 0x00ce, pidHigh: 0x008e, unit: 'count', displayMin: 0, displayMax: 127 },
4447
+ // ============================================================
4448
+ // Session 97 (2026-05-18) UI-MISSING residual closeout — wires the
4449
+ // 15 remaining placeable-family entries the AM4-Edit XML exposes
4450
+ // but params.ts didn't carry. Catalog symbols sourced from
4451
+ // `samples/captured/decoded/ghidra-am4-paramnames.json`,
4452
+ // display labels from `__block_layout.xml` /
4453
+ // `__block_layout_expert.xml` (mined by
4454
+ // `scripts/_research/list-ui-missing.ts`).
4455
+ //
4456
+ // UI widgets (paramId >= 65000 or empty XML label — meter/name/
4457
+ // label/button/graph/menu sentinels) intentionally skipped: those
4458
+ // addresses back UI chrome in AM4-Edit, not writable preset data.
4459
+ // Catalog symbols dropped this pass: CABINET_NAME{1,2},
4460
+ // CABINET_LABEL{1,2}, CABINET_ALIGN_*, CABINET_COPY_MENU{1,2},
4461
+ // GEQ_ZEROEQ, INPUT_METERS, VOLUME_METER, DISTORT_ZEROEQ.
4462
+ //
4463
+ // Unit/range conventions mirror commit b67e23f (Session 96):
4464
+ // toggle-style switches → enum 0..1 OFF/ON; type/menu enums
4465
+ // without captured tables → count 0..127; read-only meters
4466
+ // → knob_0_10. Slope params follow the FLANGER convention
4467
+ // (count 1..12) since no capture pins enum vs count yet.
4468
+ // ---- PEQ (pidLow=0x0036) — 2 entries ----
4469
+ // PEQ_LOWSLOPE / PEQ_HIGHSLOPE address the shelf-channel slope on
4470
+ // channel 1 (low shelf) and channel 5 (high shelf). XML labels
4471
+ // "Slope 1" / "Slope 5"; name follows the existing channel_N_*
4472
+ // family-prefix pattern (channel_1_frequency, channel_5_q, etc.).
4473
+ 'peq.channel_1_slope': { block: 'peq', name: 'channel_1_slope', displayLabel: 'Slope 1', pidLow: 0x0036, pidHigh: 0x0023, unit: 'count', displayMin: 1, displayMax: 12 },
4474
+ 'peq.channel_5_slope': { block: 'peq', name: 'channel_5_slope', displayLabel: 'Slope 5', pidLow: 0x0036, pidHigh: 0x0024, unit: 'count', displayMin: 1, displayMax: 12 },
4475
+ // ---- COMP (pidLow=0x002e) — 4 entries ----
4476
+ // gain_monitor is a read-only meter — `gain` collides with the
4477
+ // existing compressor knob; the _monitor suffix matches the AMP
4478
+ // family's b_plus_monitor / gain_monitor / headroom_monitor
4479
+ // convention from Session 89.
4480
+ 'compressor.knee_type': { block: 'compressor', name: 'knee_type', displayLabel: 'Knee Type', pidLow: 0x002e, pidHigh: 0x000e, unit: 'count', displayMin: 0, displayMax: 127 },
4481
+ 'compressor.detector_type': { block: 'compressor', name: 'detector_type', displayLabel: 'Detector Type', pidLow: 0x002e, pidHigh: 0x0010, unit: 'count', displayMin: 0, displayMax: 127 },
4482
+ 'compressor.auto_attack_release': { block: 'compressor', name: 'auto_attack_release', displayLabel: 'Auto Att/Rel', pidLow: 0x002e, pidHigh: 0x0016, unit: 'enum', displayMin: 0, displayMax: 1, enumValues: { 0: 'OFF', 1: 'ON' } },
4483
+ 'compressor.gain_monitor': { block: 'compressor', name: 'gain_monitor', displayLabel: 'Gain', pidLow: 0x002e, pidHigh: 0x001f, unit: 'knob_0_10', displayMin: 0, displayMax: 10 },
4484
+ // ---- GATE (pidLow=0x0092) — 1 entry ----
4485
+ 'gate.gain_monitor': { block: 'gate', name: 'gain_monitor', displayLabel: 'Gain', pidLow: 0x0092, pidHigh: 0x0012, unit: 'knob_0_10', displayMin: 0, displayMax: 10 },
4486
+ // ---- INPUT / ingate (pidLow=0x0025) — 4 entries ----
4487
+ // The input-noise-gate block carries hidden compressor-style
4488
+ // controls (Ratio / Attack / Z / GainMonitor) the AM4-Edit Setup
4489
+ // page exposes but params.ts hadn't registered. INPUT_METERS
4490
+ // (paramId 65520) skipped as a UI widget.
4491
+ 'ingate.ratio': { block: 'ingate', name: 'ratio', displayLabel: 'Ratio', pidLow: 0x0025, pidHigh: 0x000b, unit: 'ratio', displayMin: 1, displayMax: 10, scaling: 'log10' },
4492
+ 'ingate.attack': { block: 'ingate', name: 'attack', displayLabel: 'Attack', pidLow: 0x0025, pidHigh: 0x000d, unit: 'ms', displayMin: 0, displayMax: 1000 },
4493
+ 'ingate.input_impedance': { block: 'ingate', name: 'input_impedance', displayLabel: 'Input Impedance', pidLow: 0x0025, pidHigh: 0x000e, unit: 'count', displayMin: 0, displayMax: 127 },
4494
+ 'ingate.gain_monitor': { block: 'ingate', name: 'gain_monitor', displayLabel: 'Gain', pidLow: 0x0025, pidHigh: 0x0010, unit: 'knob_0_10', displayMin: 0, displayMax: 10 },
4495
+ // ---- CHORUS (pidLow=0x004e) — 1 entry ----
4496
+ // Stereo "Tempo Right" companion to chorus.tempo (CHORUS_TEMPO).
4497
+ // Same TEMPO_DIVISIONS_VALUES enum since the right-channel tempo
4498
+ // ladder mirrors the master one.
4499
+ 'chorus.tempo_right': { block: 'chorus', name: 'tempo_right', displayLabel: 'Tempo Right', pidLow: 0x004e, pidHigh: 0x001f, unit: 'enum', displayMin: 0, displayMax: 78, enumValues: TEMPO_DIVISIONS_VALUES },
4500
+ // ---- TREMOLO (pidLow=0x006a) — 2 entries ----
4501
+ // trigger_phase is the LFO start-phase on retrigger; degrees
4502
+ // convention matches tremolo.phase (Session 35, capture-verified
4503
+ // 0..180 deg). crossover_slope follows the FLANGER count 1..12
4504
+ // pattern pending range capture.
4505
+ 'tremolo.trigger_phase': { block: 'tremolo', name: 'trigger_phase', displayLabel: 'Trigger Phase', pidLow: 0x006a, pidHigh: 0x0013, unit: 'degrees', displayMin: 0, displayMax: 180 },
4506
+ 'tremolo.crossover_slope': { block: 'tremolo', name: 'crossover_slope', displayLabel: 'Crossover Slope', pidLow: 0x006a, pidHigh: 0x0014, unit: 'count', displayMin: 1, displayMax: 12 },
4507
+ // ---- ENHANCER (pidLow=0x007a) — 1 entry ----
4508
+ // OFF/ON enum mirrors filter.phase_invert (same XML "Phase Invert"
4509
+ // label, same shape).
4510
+ 'enhancer.phase_invert': { block: 'enhancer', name: 'phase_invert', displayLabel: 'Phase Invert', pidLow: 0x007a, pidHigh: 0x000f, unit: 'enum', displayMin: 0, displayMax: 1, enumValues: { 0: 'OFF', 1: 'ON' } },
4511
+ // ============================================================
4512
+ // Session 97 cont 5 (2026-05-18) — REMOVED 15 speculative III-
4513
+ // inherited entries that Session 97 cont 3+4 had wired (DISTORT
4514
+ // TONETYPE/EXCURSIONTIME/RECOVERYTIME/CFGRID/DYNPRES/DYNDEPTH/
4515
+ // TREMFREQ/TREMDEPTH/BIASTYPE/INDYNAMICS/PRECOMPTYPE/CFHARDNESS/
4516
+ // DRIVETYPE/FBTYPE + COMP_LIGHTTYPE).
4517
+ //
4518
+ // Comprehensive scan of `__block_layout_expert.xml` (all 141
4519
+ // unique DISTORT_/COMP_ parameterNames across every conditional
4520
+ // Table for every amp type variant) confirmed: zero of the 29
4521
+ // catalog-only candidate symbols appear anywhere in AM4-Edit's
4522
+ // expert XML. The 15 had been wired from III `__amp_layout.xml`
4523
+ // labels — those don't apply to AM4.
4524
+ //
4525
+ // The 29 remain as GHOST in the cross-ref audit (catalog symbol,
4526
+ // no AM4 XML). If a future HW-114 capture surfaces any of them
4527
+ // on the AM4 front-panel Expert Edit menu, they'll be re-added
4528
+ // here with HARDWARE-VERIFIED labels (not III speculation).
4529
+ //
4530
+ // Coverage shift back to honest values:
4531
+ // - WIRED-MATCHED 609 -> 594, GHOST 34 -> 49
4532
+ // - AM4-Edit XML rendered controls: 100% wired (was already 100%
4533
+ // before Session 97 cont 3 — the 15 added were not part of
4534
+ // the rendered surface)
4535
+ // - coverage-audit.ts headline becomes meaningful: every wired
4536
+ // entry corresponds to a control the user can see in AM4-Edit.
4537
+ };