@vessel-dsp/compiler 0.5.0 → 0.7.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 (88) hide show
  1. package/README.md +4 -2
  2. package/dist/bbd-clock.d.ts +42 -0
  3. package/dist/bbd-clock.d.ts.map +1 -1
  4. package/dist/bbd-clock.js +383 -1
  5. package/dist/bbd-clock.js.map +1 -1
  6. package/dist/compile.d.ts.map +1 -1
  7. package/dist/compile.js +316 -34
  8. package/dist/compile.js.map +1 -1
  9. package/dist/device-laws.d.ts.map +1 -1
  10. package/dist/device-laws.js +581 -98
  11. package/dist/device-laws.js.map +1 -1
  12. package/dist/fixtures/circuits.d.ts +23 -2
  13. package/dist/fixtures/circuits.d.ts.map +1 -1
  14. package/dist/fixtures/circuits.js +205 -2
  15. package/dist/fixtures/circuits.js.map +1 -1
  16. package/dist/index.d.ts +2 -1
  17. package/dist/index.d.ts.map +1 -1
  18. package/dist/index.js +1 -0
  19. package/dist/index.js.map +1 -1
  20. package/dist/lower.d.ts.map +1 -1
  21. package/dist/lower.js +13 -0
  22. package/dist/lower.js.map +1 -1
  23. package/dist/netlist.d.ts +30 -0
  24. package/dist/netlist.d.ts.map +1 -1
  25. package/dist/netlist.js +502 -8
  26. package/dist/netlist.js.map +1 -1
  27. package/dist/part-catalog.d.ts +42 -18
  28. package/dist/part-catalog.d.ts.map +1 -1
  29. package/dist/part-catalog.js +2518 -576
  30. package/dist/part-catalog.js.map +1 -1
  31. package/dist/partition.d.ts.map +1 -1
  32. package/dist/partition.js +6 -0
  33. package/dist/partition.js.map +1 -1
  34. package/dist/registry-data/bjt.d.ts +3 -0
  35. package/dist/registry-data/bjt.d.ts.map +1 -0
  36. package/dist/registry-data/bjt.js +1350 -0
  37. package/dist/registry-data/bjt.js.map +1 -0
  38. package/dist/registry-data/charge-pump.d.ts +3 -0
  39. package/dist/registry-data/charge-pump.d.ts.map +1 -0
  40. package/dist/registry-data/charge-pump.js +204 -0
  41. package/dist/registry-data/charge-pump.js.map +1 -0
  42. package/dist/registry-data/diode.d.ts +3 -0
  43. package/dist/registry-data/diode.d.ts.map +1 -0
  44. package/dist/registry-data/diode.js +786 -0
  45. package/dist/registry-data/diode.js.map +1 -0
  46. package/dist/registry-data/index.d.ts +21 -0
  47. package/dist/registry-data/index.d.ts.map +1 -0
  48. package/dist/registry-data/index.js +103 -0
  49. package/dist/registry-data/index.js.map +1 -0
  50. package/dist/registry-data/jfet.d.ts +3 -0
  51. package/dist/registry-data/jfet.d.ts.map +1 -0
  52. package/dist/registry-data/jfet.js +605 -0
  53. package/dist/registry-data/jfet.js.map +1 -0
  54. package/dist/registry-data/loader.d.ts +137 -0
  55. package/dist/registry-data/loader.d.ts.map +1 -0
  56. package/dist/registry-data/loader.js +350 -0
  57. package/dist/registry-data/loader.js.map +1 -0
  58. package/dist/registry-data/mosfet.d.ts +3 -0
  59. package/dist/registry-data/mosfet.d.ts.map +1 -0
  60. package/dist/registry-data/mosfet.js +63 -0
  61. package/dist/registry-data/mosfet.js.map +1 -0
  62. package/dist/registry-data/opamp.d.ts +37 -0
  63. package/dist/registry-data/opamp.d.ts.map +1 -0
  64. package/dist/registry-data/opamp.js +669 -0
  65. package/dist/registry-data/opamp.js.map +1 -0
  66. package/dist/registry-data/regulator.d.ts +3 -0
  67. package/dist/registry-data/regulator.d.ts.map +1 -0
  68. package/dist/registry-data/regulator.js +189 -0
  69. package/dist/registry-data/regulator.js.map +1 -0
  70. package/dist/registry-data/schema.d.ts +60 -0
  71. package/dist/registry-data/schema.d.ts.map +1 -0
  72. package/dist/registry-data/schema.js +151 -0
  73. package/dist/registry-data/schema.js.map +1 -0
  74. package/dist/registry-data/ujt.d.ts +3 -0
  75. package/dist/registry-data/ujt.d.ts.map +1 -0
  76. package/dist/registry-data/ujt.js +48 -0
  77. package/dist/registry-data/ujt.js.map +1 -0
  78. package/dist/registry-data/zener.d.ts +3 -0
  79. package/dist/registry-data/zener.d.ts.map +1 -0
  80. package/dist/registry-data/zener.js +547 -0
  81. package/dist/registry-data/zener.js.map +1 -0
  82. package/dist/registry.d.ts +93 -6
  83. package/dist/registry.d.ts.map +1 -1
  84. package/dist/registry.js +35 -7
  85. package/dist/registry.js.map +1 -1
  86. package/dist/types.d.ts +242 -19
  87. package/dist/types.d.ts.map +1 -1
  88. package/package.json +1 -1
@@ -9,9 +9,10 @@
9
9
  // them. So the generator and the lowering JSON were deleted and this file kept, rather than the
10
10
  // reverse: the hand edits are the corrections. Recovery point for both: `892cdcf6`.
11
11
  //
12
- // The electrical parameters that come from the device database
13
- // (`web/assets/registry/component-*-chips.json`, still the product's canonical device data) are
14
- // held in agreement with this file by `scripts/report-part-store-drift.ts`, not by generation.
12
+ // The electrical parameters that come from the device database live in
13
+ // `registry-data/` as cited values (`{ value, basis, source }`), and this file builds the
14
+ // entries that carry them. `scripts/report-part-store-drift.ts` is gone (deleted with the
15
+ // lowering-JSON generator); nothing re-runs a generator over this file.
15
16
  //
16
17
  // `registry.ts` deliberately holds no parts -- it is the shape and the matching rules, so the
17
18
  // compiler itself knows about no specific chip and `emptyRegistry` stays a legitimate
@@ -30,16 +31,31 @@
30
31
  // `openLoopGain` plus rails, and the rails are the pedal's rather than the part's, so a dual op-amp
31
32
  // is a complete model from one published number and its pinout.
32
33
  //
33
- // **Gain-bandwidth product is the second figure, and it has its own table** (`opampGainBandwidthDatasheet`
34
- // below) rather than a field on each entry, because one entry here often covers parts whose datasheets
35
- // print different products (the TL072/NE5532/LM358 entry is one law over three datasheets). The TYP column
36
- // is transcribed ("typical" here is that datasheet's own TYP column for that part, not a generic typical
37
- // value), never MIN or MAX, never a graph read by eye, never a registry or curated-record number.
38
- // A part whose datasheet prints no TYP gain-bandwidth row is listed in `opampGainBandwidthAbsent` with the
39
- // reason and gets no figure, so its stamp is `null` and the compile names it (`opamp-gain-bandwidth-default`).
40
- // Matching is by exact folded part id, as everywhere in this file: an id the datasheet does not print is not
41
- // covered because a sibling is.
42
- import { foldPartId } from "./registry.js";
34
+ // **Gain-bandwidth product is the second figure, and it lives in the data registry**
35
+ // (`registry-data/opamp.ts`) rather than as a field on each entry, because one entry here
36
+ // often covers parts whose datasheets print different products (the TL072/NE5532/LM358 entry
37
+ // is one law over three datasheets). The TYP column is transcribed ("typical" here is that
38
+ // datasheet's own TYP column for that part, not a generic typical value), never MIN or MAX,
39
+ // never a graph read by eye, never a registry or curated-record number.
40
+ // A part whose datasheet prints no TYP gain-bandwidth row is listed in
41
+ // `opampGainBandwidthAbsent` with the reason and gets no figure, so its stamp is `null`
42
+ // and the compile names it (`opamp-gain-bandwidth-default`).
43
+ // Matching is by exact folded part id, as everywhere in this file: an id the datasheet
44
+ // does not print is not covered because a sibling is.
45
+ //
46
+ // **Output swing is the third figure, and lives there too** (`registry-data/opamp.ts`): the
47
+ // distance the output stops short of each rail, as a printed **pair** (positive side, negative
48
+ // side) with the condition it was printed at (total supply, load) and how it is carried to a
49
+ // pedal on another supply (`translation`, `"constant"` or `"proportional"`). TYP column only,
50
+ // printed rows only; an id with no such row is listed in `opampOutputSwingAbsent` with the
51
+ // reason and states no swing. Nearly every printed row is at +-15 V and nearly every pedal
52
+ // runs at 9 V, so the carry is a named choice, and `opamp-law-default-parameters` names
53
+ // every device it was carried for.
54
+ import { foldPartId, opampOutputSwingOf } from "./registry.js";
55
+ import { bjtRegistryRecords, chargePumpRegistryRecords, diodeRegistryRecords, findPartRegistryData, jfetRegistryRecords, mosfetRegistryRecords, opampGainBandwidthAbsent, opampOutputSwingAbsent, opampRegistryRecords, regulatorRegistryRecords, ujtRegistryRecords, zenerRegistryRecords, communityProvenanceOf, } from "./registry-data/index.js";
56
+ // The absent lists and the merged op-amp records live in `registry-data/opamp.ts` and are
57
+ // re-exported here so their existing import sites keep working.
58
+ export { opampGainBandwidthAbsent, opampOutputSwingAbsent, opampRegistryRecords, } from "./registry-data/index.js";
43
59
  /**
44
60
  * An op-amp section as the registry states it: the part's own gain, and no rails.
45
61
  */
@@ -87,11 +103,13 @@ function tubeLaw(partIds, law) {
87
103
  * So a newly covered zener renders its old forward behaviour exactly and only gains a
88
104
  * reverse knee; deriving the forward law is plan step 2's work, not this entry's.
89
105
  */
90
- function zenerLaw(partIds, breakdownVolts) {
106
+ function zenerLaw(record) {
107
+ const breakdownVolts = zenerBreakdownVoltsOf(record);
91
108
  return {
92
- partIds,
109
+ partIds: [...record.partIds],
93
110
  declaredTypes: [],
94
111
  terminalRoleGroups: [],
112
+ ...provisionalArmOf(record),
95
113
  model: {
96
114
  kind: "law",
97
115
  law: {
@@ -106,118 +124,469 @@ function zenerLaw(partIds, breakdownVolts) {
106
124
  };
107
125
  }
108
126
  /**
109
- * Every part id a `zenerLaw` entry covers, folded for comparison. `report-part-store-drift.ts`
110
- * keys its forward-law exclusion on this set -- a catalog product -- rather than on the
111
- * registry row's `type`, so a hand-fitted diode law that happens to sit on a zener id is
112
- * still graded, and a zener entry is never mistaken for a forward claim.
127
+ * The `provisionalParams` arm of a law entry built from a data record: the record's
128
+ * `community`-basis parameters with their source URLs, or absent when the record holds
129
+ * none. Spread into the built entry, so an entry with no provisional value is shaped
130
+ * exactly as before (the field stays absent, not empty).
113
131
  */
114
- // --- Switch JFETs: per-part silicon gate data, channel deliberately unrefined -----------------
115
- //
116
- // The JFET class default (`device-laws.ts`'s `case "jfet"`) is avowedly no part's values, and
117
- // its gate prefactor (`gateSaturationCurrent: 1e-5`, reused from the triode grid) parks a
118
- // series switch whose gate sits ~0.4 V above its channel in the junction's exponential foot,
119
- // rectifying microamp signal currents into the audio path. Measured 2026-10-06 on
120
- // `ibanez-ts9` Q102 (Vgs +0.392, gate peaks 5 uA): the engine's H2 sits at -29.6 dB there
121
- // against -57 to -64 dB on the physical reissue specimen and -77.9 dB with the FET replaced
122
- // by 100 ohm; killing only the gate terms reproduces the resistor swap to the printed digit,
123
- // and ngspice with the stock-card-equivalent NJF mapping agrees with the engine to 0.1/0.8 dB,
124
- // so the defect is the parameter value, not the solver. The TS9 H2 is channel-inert (a
125
- // 52-to-836-ohm Rds sweep moves it 0.2 dB), so the entry below refines the gate term only.
132
+ function provisionalArmOf(record) {
133
+ const provisional = communityProvenanceOf(record);
134
+ return provisional.length === 0 ? {} : { provisionalParams: provisional };
135
+ }
136
+ /**
137
+ * Every part id a `zenerLaw` entry covers, folded for comparison: a hand-fitted diode law
138
+ * that happens to sit on a zener id is still graded as a forward claim, and a zener entry
139
+ * is never mistaken for one.
140
+ */
141
+ // --- Wave-1 discrete builders: cited record -> law entry --------------------------------------
126
142
  //
127
- // The entry carries the class channel by construction, not by measurement:
128
- // `thresholdVolts`, `transconductance`, `channelLengthModulation` and `subthresholdVolts`
129
- // repeat the class default, and `device-laws.ts` reads only the gate term from a part
130
- // entry. The channel is rank-dependent (an unbinned 2SK30A spans IDSS 0.3-6.5 mA across
131
- // R/O/Y/GR), and the database row's beta.typ disagrees with `boss-sg-1-slow-gear`'s
132
- // recorded gain-cell island (report 4: the island's own Beta=1.2m reproduces its 59.9x,
133
- // beta.typ 0.0004664 would halve it), so refining the channel here would trade one
134
- // packet's fix for another's regression on evidence neither side owns. A rank-binned
135
- // channel fit belongs in the packet or a rank-suffixed entry, not here.
143
+ // Stage-1 wired the bjt, mosfet, diode-forward and jfet-channel cases to read the registry
144
+ // (document-typed > registry > class default, exact folded part id). These builders turn the
145
+ // wave-1 `registry-data` records into the law entries those cases read. Every builder maps the
146
+ // record's PRESENT parameters and gap-fills the rest with the class default the unregistered
147
+ // path stamps -- the literals below repeat `device-laws.ts` (`SILICON_SATURATION_CURRENT`,
148
+ // `SILICON_FORWARD_BETA`, reverseBeta 1, `THERMAL_VOLTAGE`, leakageAmps 0; the JFET/MOSFET
149
+ // channel and gate constants; the diode forward defaults) BY VALUE, deliberately: importing
150
+ // them would drag the stamping path into the catalog, and a drift between the two copies is
151
+ // caught by the sparse-record test (`registry-discrete-wiring`), which stamps a one-parameter
152
+ // entry and requires every omitted field to equal the empty-registry stamp.
136
153
  //
137
- // Provenance and uncertainty, stated rather than fitted: the gate transcribes the device
138
- // database's silicon-junction convention (`saturationCurrent.typ` 1e-14, the same constant
139
- // as `SILICON_SATURATION_CURRENT`); onset/scale stay class. The Toshiba 2SK30A sheet
140
- // (`parts/jfets/2SK30A/datasheets/toshiba-2sk30a.pdf`, curated in `parts/jfets/2SK30A.md`)
141
- // prints IDSS ranks, VGS(off) and Yfs, and no forward gate current at all (only reverse
142
- // IGSS). The gate IS is therefore a silicon-junction order choice, not a measured 2SK30A
143
- // forward current: it puts the foot current at switch biases three orders below signal
144
- // currents (verified H2-free for gateSaturationCurrent in {1e-7, 1e-8, 1e-9, 0}; 1e-14 sits
145
- // inside that verified-flat region at the repo's silicon constant). Never fitted to a specimen.
154
+ // Bin and package spellings merge into the entry's `partIds`: `registryLawFor` matches whole
155
+ // folded ids against `partIds` only, so an alias that stayed on the record would never match.
156
+ // The data loader's uniqueness scope already covers ids plus aliases, so the merge cannot
157
+ // introduce a collision the loader missed.
146
158
  //
147
- // Coverage is deliberately a single exact id: the part `ibanez-ts9` declares.
148
- // The match is `registryLawFor`'s whole folded id (`2sk30a`), with no prefix or
149
- // substring rule anywhere on the path, so `2SK30ATM`, `2SK30ATM-Y` and `2SK44SPC
150
- // do not match. `boss-cs-2` (2SK30ATM-Y), `boss-ds-1` (2SK30ATM), `boss-dm-2`
151
- // (2SK30ATM-Y) and `ibanez-ts808` (2SK44SPC) each need their own packet study
152
- // before their behaviour may move, so they stay on the class default until then.
153
- // `boss-sg-1-slow-gear` declares the same id; its gate sits 0.5 V below onset in
154
- // every state, so the gate refinement is inert there (a ~0.02 dB Vgs shift from the
155
- // removed 4.5 nA foot current, measured in the report-5 rerun).
156
- function fetLaw(partIds, law) {
159
+ // Structural fields stay at stamp time, never in the entry: a bjt entry's `polarity` and a
160
+ // fet entry's `channel` are inert placeholders (`npn` / `n`) that `device-laws.ts` always
161
+ // overwrites from the device's own `pnp` / `pChannel`. A bjt entry therefore serves NPN and
162
+ // PNP parts alike, and no entry asserts a polarity or channel about anyone's part.
163
+ /** One record's cited value for a schema parameter, or absent when the record omits it. */
164
+ function citedValueOf(record, name) {
165
+ const param = record.params[name];
166
+ return param === undefined ? undefined : param.value;
167
+ }
168
+ /** The `citedParams` arm of a law entry built from a record: the parameters it states. */
169
+ function citedArmOf(record) {
170
+ return { citedParams: Object.keys(record.params) };
171
+ }
172
+ /** The entry's id list: canonical ids plus typed alias spellings, in listing order. */
173
+ function entryIdsOf(record) {
174
+ return [...record.partIds, ...(record.aliases ?? [])];
175
+ }
176
+ /** A bjt record's law entry. Throws when the record is not class `bjt` or states nothing. */
177
+ export function bjtLawEntry(record) {
178
+ if (record.class !== "bjt") {
179
+ throw new Error(`${record.partIds.join(",")}: a bjt entry needs class "bjt", not "${record.class}"`);
180
+ }
181
+ if (Object.keys(record.params).length === 0) {
182
+ throw new Error(`${record.partIds.join(",")}: a bjt record states no parameters`);
183
+ }
157
184
  return {
158
- partIds,
185
+ partIds: entryIdsOf(record),
159
186
  declaredTypes: [],
160
187
  terminalRoleGroups: [],
161
- model: { kind: "law", law },
188
+ ...provisionalArmOf(record),
189
+ ...citedArmOf(record),
190
+ model: {
191
+ kind: "law",
192
+ law: {
193
+ kind: "bjt",
194
+ polarity: "npn",
195
+ saturationCurrent: citedValueOf(record, "saturationCurrent") ?? 1e-14,
196
+ forwardBeta: citedValueOf(record, "forwardBeta") ?? 100,
197
+ reverseBeta: citedValueOf(record, "reverseBeta") ?? 1,
198
+ thermalVoltage: citedValueOf(record, "thermalVoltage") ?? 0.025_852,
199
+ leakageAmps: citedValueOf(record, "leakageAmps") ?? 0,
200
+ },
201
+ },
202
+ };
203
+ }
204
+ /**
205
+ * A signal-diode record's law entry. Requires the forward pair together: the only
206
+ * derivation the registry admits (two printed (If, Vf) points) yields IS and n as one
207
+ * working, so a lone IS without n is a broken derivation and throws rather than shipping
208
+ * half a knee. Gap-filled breakdownVolts 0 keeps the no-knee reading the unregistered path
209
+ * stamps (a zener knee belongs to class `zener` and the existing `zenerLaw` path, not here).
210
+ */
211
+ export function diodeLawEntry(record) {
212
+ if (record.class !== "diode") {
213
+ throw new Error(`${record.partIds.join(",")}: a diode entry needs class "diode", not "${record.class}"`);
214
+ }
215
+ const saturationCurrent = citedValueOf(record, "saturationCurrent");
216
+ const emissionCoefficient = citedValueOf(record, "emissionCoefficient");
217
+ if (saturationCurrent === undefined || emissionCoefficient === undefined) {
218
+ throw new Error(`${record.partIds.join(",")}: a diode record states no forward pair ` +
219
+ `(saturationCurrent ${saturationCurrent === undefined ? "absent" : "present"}, ` +
220
+ `emissionCoefficient ${emissionCoefficient === undefined ? "absent" : "present"})`);
221
+ }
222
+ return {
223
+ partIds: entryIdsOf(record),
224
+ declaredTypes: [],
225
+ terminalRoleGroups: [],
226
+ ...provisionalArmOf(record),
227
+ ...citedArmOf(record),
228
+ model: {
229
+ kind: "law",
230
+ law: {
231
+ kind: "diode",
232
+ saturationCurrent,
233
+ emissionCoefficient,
234
+ thermalVoltage: citedValueOf(record, "thermalVoltage") ?? 0.025_852,
235
+ breakdownVolts: citedValueOf(record, "breakdownVolts") ?? 0,
236
+ seriesResistance: citedValueOf(record, "seriesResistance") ?? 1.0,
237
+ isLed: false,
238
+ },
239
+ },
240
+ };
241
+ }
242
+ /**
243
+ * A JFET- or MOSFET-channel record's law entry. The class selects the gap-fill constants
244
+ * (JFET depletion defaults with the silicon gate; MOSFET enhancement defaults with the
245
+ * insulated gate), so a JFET record can never stamp MOSFET gate zeros and the reverse.
246
+ * Throws on any other class and on an empty record.
247
+ */
248
+ export function fetLawEntry(record, cls) {
249
+ if (record.class !== cls) {
250
+ throw new Error(`${record.partIds.join(",")}: a ${cls} entry needs class "${cls}", not "${record.class}"`);
251
+ }
252
+ if (Object.keys(record.params).length === 0) {
253
+ throw new Error(`${record.partIds.join(",")}: a ${cls} record states no parameters`);
254
+ }
255
+ const channelDefaults = cls === "jfet"
256
+ ? {
257
+ thresholdVolts: -2,
258
+ transconductance: 1e-3,
259
+ channelLengthModulation: 0,
260
+ subthresholdVolts: 0.07,
261
+ gateSaturationCurrent: 1e-5,
262
+ gateOnsetVolts: 0.5,
263
+ gateScaleVolts: 0.06,
264
+ }
265
+ : {
266
+ thresholdVolts: 2,
267
+ transconductance: 1e-3,
268
+ channelLengthModulation: 0,
269
+ subthresholdVolts: 0,
270
+ gateSaturationCurrent: 0,
271
+ gateOnsetVolts: 0,
272
+ gateScaleVolts: 1,
273
+ };
274
+ return {
275
+ partIds: entryIdsOf(record),
276
+ declaredTypes: [],
277
+ terminalRoleGroups: [],
278
+ ...provisionalArmOf(record),
279
+ ...citedArmOf(record),
280
+ model: {
281
+ kind: "law",
282
+ law: {
283
+ kind: "fet",
284
+ channel: "n",
285
+ thresholdVolts: citedValueOf(record, "thresholdVolts") ??
286
+ channelDefaults.thresholdVolts,
287
+ transconductance: citedValueOf(record, "transconductance") ??
288
+ channelDefaults.transconductance,
289
+ channelLengthModulation: citedValueOf(record, "channelLengthModulation") ??
290
+ channelDefaults.channelLengthModulation,
291
+ subthresholdVolts: citedValueOf(record, "subthresholdVolts") ??
292
+ channelDefaults.subthresholdVolts,
293
+ gateSaturationCurrent: citedValueOf(record, "gateSaturationCurrent") ??
294
+ channelDefaults.gateSaturationCurrent,
295
+ gateOnsetVolts: citedValueOf(record, "gateOnsetVolts") ??
296
+ channelDefaults.gateOnsetVolts,
297
+ gateScaleVolts: citedValueOf(record, "gateScaleVolts") ??
298
+ channelDefaults.gateScaleVolts,
299
+ },
300
+ },
162
301
  };
163
302
  }
164
- // --- Zener diodes: reverse breakdown from the device database ------------------------------------
303
+ /**
304
+ * A unijunction-transistor record's law entry. Requires the tap fraction and the bar
305
+ * together: eta without Rbb cannot place the peak point and Rbb without eta cannot
306
+ * split the bar, so a half record throws rather than shipping half a device. The
307
+ * emitter diode and the snap are law structure, never entry fields.
308
+ */
309
+ export function ujtLawEntry(record) {
310
+ if (record.class !== "ujt") {
311
+ throw new Error(`${record.partIds.join(",")}: a ujt entry needs class "ujt", not "${record.class}"`);
312
+ }
313
+ const intrinsicStandoffRatio = citedValueOf(record, "intrinsicStandoffRatio");
314
+ const interbaseResistanceOhms = citedValueOf(record, "interbaseResistanceOhms");
315
+ if (intrinsicStandoffRatio === undefined || interbaseResistanceOhms === undefined) {
316
+ throw new Error(`${record.partIds.join(",")}: a ujt record states no tap and bar ` +
317
+ `(intrinsicStandoffRatio ${intrinsicStandoffRatio === undefined ? "absent" : "present"}, ` +
318
+ `interbaseResistanceOhms ${interbaseResistanceOhms === undefined ? "absent" : "present"})`);
319
+ }
320
+ return {
321
+ partIds: entryIdsOf(record),
322
+ declaredTypes: [],
323
+ terminalRoleGroups: [],
324
+ ...provisionalArmOf(record),
325
+ model: {
326
+ kind: "law",
327
+ law: {
328
+ kind: "ujt",
329
+ intrinsicStandoffRatio,
330
+ interbaseResistanceOhms,
331
+ },
332
+ },
333
+ };
334
+ }
335
+ // --- JFET channel records: built by `fetLawEntry` (wave-1 builders above) -------------------
336
+ //
337
+ // The JFET class default is avowedly no part's values; see `registry-data/jfet.ts` for the
338
+ // measured gate-defect note, the rank argument for keeping the 2SK30A class channel, and the
339
+ // provenance. The generic builder maps each record's present parameters and gap-fills the
340
+ // class channel, so 2SK30A (gate term only) stamps byte-identically to before while full
341
+ // channels (2N5457) stamp their cited values.
342
+ // --- Zener diodes: reverse breakdown from the data registry -------------------------------------
165
343
  //
166
344
  // `breakdownVolts` only; forward behaviour repeats the class default by construction (see
167
345
  // `zenerLaw` above). A device that declares its own breakdown keeps it: `device-laws.ts`
168
- // overlays the declared value on a catalog match, so the 26 corpus diodes that already
346
+ // overlays the declared value on a catalog match, so the corpus diodes that already
169
347
  // declare one (e.g. `boss-ph-1r` D1 11 V against the registry's 10.99) render unchanged.
348
+ // The rows live in `registry-data/zener.ts` as cited values; the entries array below maps
349
+ // them through `zenerLaw`, so the membership set below and the shipped entries cannot
350
+ // drift apart: there is one list, not two.
351
+ /** Read a zener record's cited breakdown, loudly. The loader guarantees the citation. */
352
+ function zenerBreakdownVoltsOf(record) {
353
+ const breakdown = record.params["breakdownVolts"];
354
+ if (breakdown === undefined || !Number.isFinite(breakdown.value)) {
355
+ throw new Error(`${record.partIds.join(",")}: a zener record states no breakdownVolts`);
356
+ }
357
+ return breakdown.value;
358
+ }
359
+ export const zenerCatalogPartIds = new Set(zenerRegistryRecords.flatMap((record) => record.partIds.map((id) => foldPartId(id))));
360
+ // --- Fixed-rail regulators: wiring skeletons over registry-data voltages ---------------------------
361
+ // The rail numbers live in `registry-data/regulator.ts` as cited values; this table holds only
362
+ // the terminal wiring (role groups, section terminals) each rail's entry shares. Keyed by the
363
+ // rail volts the record states, so a record always lands on its own wiring and a skeleton row
364
+ // no record consumes throws at load (see the check below). `pinout` is `[null, null, null]`
365
+ // for all of them: no regulator entry asserts a pin order.
366
+ const regulatorTerminalStructures = {
367
+ // UNRESOLVED: this entry conflates two packages with OPPOSITE pin orders. The TO-92/SOT-89 78L05 is pin1=VOUT, pin2=GND, pin3=VIN (verified: TI LM78L, SNVS754O, Figure 4-2 SOT-89 top view and Figure 4-3 LP 3-pin TO-92 bottom view). The TO-220 7805 is pin1=VIN, pin2=GND, pin3=VOUT. The role groups below assert the 7805 order, because `pin1` sits in the vin group and `pin3` in the vout group.
368
+ // Consequence: a document that declares a 78L05's terminals as pin1/pin2/pin3 binds its input and output the wrong way round. No corpus packet does today -- all seven three-pin regulators declare typed roles as of 2026-09-05 -- so this is latent, not live. Fixing it means SPLITTING the entry by package before a `pinout` can be written, because one pin map cannot serve both.
369
+ 5: {
370
+ terminalRoleGroups: [["vin", "in", "input", "pin1"], ["gnd", "pin2"], ["vout", "out", "output", "pin3"]],
371
+ terminals: [2, 1],
372
+ },
373
+ 9: {
374
+ terminalRoleGroups: [["vin", "in", "input", "pin1"], ["gnd", "pin2"], ["vout", "out", "output", "pin3"]],
375
+ terminals: [2, 1],
376
+ },
377
+ // Wave 5: 12 V and 3.3 V 78Lxx rails (LM78L12/LM78L33, TI SNVS754O). Same wiring
378
+ // shape as the 5 V/9 V entries, and the same latent TO-92-vs-TO-220 pin-order
379
+ // caveat as the 5 V entry above: no corpus packet names these rails today, so the
380
+ // order is asserted for parity, not verified against a packet.
381
+ 12: {
382
+ terminalRoleGroups: [["vin", "in", "input", "pin1"], ["gnd", "pin2"], ["vout", "out", "output", "pin3"]],
383
+ terminals: [2, 1],
384
+ },
385
+ 3.3: {
386
+ terminalRoleGroups: [["vin", "in", "input", "pin1"], ["gnd", "pin2"], ["vout", "out", "output", "pin3"]],
387
+ terminals: [2, 1],
388
+ },
389
+ // electro-harmonix-q-tron's U6 (PartNumber 78L18AC) is the 24V-to-+18V rail; it matches by part id on the first rung, so the role groups below are the standard in/gnd/out family spelling kept for parity with the 5V/9V entries, not a new contract.
390
+ 18: {
391
+ terminalRoleGroups: [["vin", "in", "input", "pin1"], ["gnd", "ground", "pin2"], ["vout", "out", "output", "pin3"]],
392
+ terminals: [2, 1],
393
+ },
394
+ // `volts` here is SIGNED, which contradicts the convention its consumer documents: `declaredRegulatorTerminals` says "the law carries a magnitude" and reverses the source's sense for an output declared `negative`. Declaring `negative` against a signed -15 therefore double-negates and puts +15V on the rail -- measured 2026-09-05. Until this is settled, a negative regulator using this entry must declare its output `positive`, which is why deluxe-memory-man does.
395
+ [-15]: {
396
+ terminalRoleGroups: [["gnd", "pin1"], ["vin", "in", "input", "pin2"], ["vout", "out", "output", "pin3"]],
397
+ terminals: [2, 0],
398
+ },
399
+ // `terminalRoleGroups` is left empty rather than copying the existing regulators' generic `in`/`gnd`/`out` aliases: `VR1` already resolves through the exact part id, and this repo's own corpus shows that generic pinout-signature fallback misidentifying an unrelated part with similarly generic terminal names (`boss-oc-2`'s `BA634-5`, five terminals including `in`/`gnd`/`out`, incidentally satisfies the existing 78L05 groups and is only saved from a wrong law by the later terminal-count guard). No reason to add a second entry to that same weak ladder when the exact part id already does the job.
400
+ 15: {
401
+ terminalRoleGroups: [],
402
+ terminals: [2, 1],
403
+ },
404
+ };
405
+ /** Build one regulator entry from its registry record and the wiring skeleton for its rail. */
406
+ function regulatorEntry(record) {
407
+ const volts = record.params["volts"];
408
+ const sourceOhms = record.params["sourceOhms"];
409
+ if (volts === undefined || sourceOhms === undefined) {
410
+ throw new Error(`${record.partIds.join(",")}: a regulator record states no volts or sourceOhms`);
411
+ }
412
+ const structure = regulatorTerminalStructures[volts.value];
413
+ if (structure === undefined) {
414
+ throw new Error(`${record.partIds.join(",")}: no regulator wiring skeleton for ${String(volts.value)} V`);
415
+ }
416
+ return {
417
+ partIds: [...record.partIds],
418
+ declaredTypes: [],
419
+ terminalRoleGroups: structure.terminalRoleGroups,
420
+ ...provisionalArmOf(record),
421
+ model: {
422
+ kind: "sections",
423
+ sections: [
424
+ {
425
+ law: {
426
+ kind: "voltage-source",
427
+ volts: volts.value,
428
+ sourceOhms: sourceOhms.value,
429
+ },
430
+ terminals: [...structure.terminals],
431
+ },
432
+ ],
433
+ pinout: [null, null, null],
434
+ },
435
+ };
436
+ }
437
+ /** The regulator entry for one rail, by the volts its registry record states. Position-preserving. */
438
+ function regulatorEntryForVolts(volts) {
439
+ const record = regulatorRegistryRecords.find((candidate) => candidate.params["volts"]?.value === volts);
440
+ if (record === undefined) {
441
+ throw new Error(`no regulator registry record states ${String(volts)} V`);
442
+ }
443
+ return regulatorEntry(record);
444
+ }
445
+ // Every skeleton row is consumed by exactly one record, and every record lands on a row:
446
+ // a rail added to one side without the other throws here rather than silently defaulting.
447
+ for (const key of Object.keys(regulatorTerminalStructures)) {
448
+ if (!regulatorRegistryRecords.some((record) => record.params["volts"]?.value === Number(key))) {
449
+ throw new Error(`regulator wiring skeleton for ${key} V is consumed by no registry record`);
450
+ }
451
+ }
452
+ // --- Charge pumps: wiring skeletons over registry-data loss/slope ---------------------------------
453
+ // The two numbers the derivation reads (`sourceOhms`, `chargePumpLossVolts`) live in
454
+ // `registry-data/charge-pump.ts` as cited values; this table holds only the terminal wiring
455
+ // (role groups, package-order pinout) each pump's entry shares. The 8-pin parts are NOT
456
+ // pin-identical: pin 1 is BOOST (TC1044S, ICL7660S, TC7662B, MAX1044), NC (ICL7660, ICL7660A),
457
+ // FB/SD (LT1054) or SHDN (LTC1144). Keyed by the record's first part id, so a record always
458
+ // lands on its own wiring; a record with no skeleton row, or a skeleton row no record
459
+ // consumes, throws at load (see the check below).
170
460
  //
171
- // Only real manufacturer part numbers appear here: no prose aliases (`3V`, `5V6 zener`,
172
- // `zener/protection diode`) and no `SOURCE-*-ZENER` placeholder ids. A packet that prints a
173
- // voltage instead of a part number states no part, so the catalog must not answer it; those
174
- // values belong in the packet as declared `breakdownVolts`.
175
- //
176
- // Each row is `[partIds, breakdownVolts]`; the entries array below maps the table through
177
- // `zenerLaw`, so the drift script's membership set above and the shipped entries cannot drift
178
- // apart: there is one list, not two.
179
- const zenerBreakdownVolts = [
180
- [["1N5230"], 4.7],
181
- [["1N961B"], 10],
182
- [["1N965B"], 15],
183
- [["1N969B"], 22],
184
- [["1N4744A"], 15],
185
- [["1N4751A", "1N4751A 30V", "1N4751A-30V", "1N4751A 30 V"], 30],
186
- [["1N4753A"], 36],
187
- [["RD5.1EB3", "RD5.1EB 3", "RD5.1EB-3"], 5.235],
188
- // Bare RD5.1EB spans every bin but only B3 is in the registry (NEC RD5.1E B3:
189
- // Vz 5.12–5.35 V, typ 5.235; vendored at parts/diodes/RD5_1EB3). Stamping B3's
190
- // typical for the unbinned part is a derived stand-in, disclosed, not a printed
191
- // unbinned value.
192
- [["RD5.1EB"], 5.235],
193
- // RD3.0EB1/RD3.0EB3 are real part numbers whose registry value lives on the
194
- // SOURCE-3V-ZENER row (BV 3); the placeholder id and the prose aliases stay out.
195
- [["RD3.0EB3", "RD3.0EB1", "RD3.0EB-1", "RD3.0EB-3"], 3],
196
- [["RD4.7EB", "02BZ4.7A", "02BZ4_7A", "02BZ4-7A"], 4.7],
197
- [["RD5.6EB3", "RD-5.6EB3", "RD5.6EB 3", "RD5.6EB-3"], 5.76],
198
- [["RD5.6EB2", "RD-5.6EB2", "RD5.6EB 2", "RD5.6EB-2", "RD5.6JB2", "RD5.6J B2", "RD5.6J-B2"], 5.58],
199
- [["RD6.6EB3", "RD6.6EB 3", "RD6.6EB-3"], 6.62],
200
- [["RD8.2EB3", "RD8.2EB 3", "RD8.2EB-3"], 8.18],
201
- // MTZ5.6B is the real part behind the SOURCE-5V6-ZENER row (BV 5.6).
202
- [["MTZ5.6B"], 5.6],
203
- [["RD11EB3", "RD11EB 3", "RD11EB-3", "RD11E B3", "RD11E-B3"], 10.99],
204
- // RD11EB is the unbinned part with its own registry row (NEC suffix-B range
205
- // 10.18–11.26 V, typ 11): not the B3 typical under a shorter name.
206
- [["RD11EB"], 11],
207
- [["0Z11A", "05Z11A"], 11],
208
- [["1N4733A"], 5.1],
209
- [["1N5331"], 5.1],
210
- [["1N5239B"], 9.1],
211
- [["1N4742A", "1N4742"], 12],
212
- [["RD5.6S", "RD5.6S B3", "RD5.6SB3", "RD5.6S-B3"], 5.6],
213
- [["RD11FB3", "RD11FB-3"], 11.15],
214
- [["PESD12VL1BA"], 12],
215
- // The flat `component-zener-diode-chips.json` rows with no diode-file entry.
216
- [["1N5231"], 5.1],
217
- [["1N5231B"], 5.1],
218
- [["1N4733"], 5.1],
219
- ];
220
- export const zenerCatalogPartIds = new Set(zenerBreakdownVolts.flatMap(([partIds]) => partIds.map(foldPartId)));
461
+ // `volts: 0` in the built law is an unused placeholder: the pump rail is derived per circuit
462
+ // from the input node (see `pumpDerivation` in device-laws.ts), and pump parts bypass generic
463
+ // section expansion, `registrySuppliedVoltages`, and `declaredRegulatorTerminals`, so this
464
+ // number is never stamped.
465
+ const chargePumpTerminalStructures = {
466
+ // Pinout verified against Microchip DS21348A (2001), p1 pin-configuration diagram and general
467
+ // description: 1 BOOST, 2 CAP+, 3 GND, 4 CAP-, 5 VOUT, 6 LV, 7 OSC, 8 V+.
468
+ BOOST_7660: {
469
+ terminalRoleGroups: [
470
+ ["boost"],
471
+ ["capp", "capplus"],
472
+ ["gnd", "ground"],
473
+ ["capm", "capminus"],
474
+ ["vout", "negativeoutput", "voutnegative", "out", "output"],
475
+ ["lv", "lowvoltage"],
476
+ ["osc", "oscillator"],
477
+ ["vplus", "vin", "vdd", "vcc", "input"],
478
+ ],
479
+ pinout: ["boost", "capp", "gnd", "capm", "vout", "lv", "osc", "vplus"],
480
+ },
481
+ // Pinout verified against Intersil FN3072 Rev 7.00, p1 pinout diagram (8 Ld PDIP/SOIC top view):
482
+ // 1 NC, 2 CAP+, 3 GND, 4 CAP-, 5 VOUT, 6 LV, 7 OSC, 8 V+.
483
+ NC_7660: {
484
+ terminalRoleGroups: [
485
+ ["nc", "noconnect"],
486
+ ["capp", "capplus"],
487
+ ["gnd", "ground"],
488
+ ["capm", "capminus"],
489
+ ["vout", "negativeoutput", "voutnegative", "out", "output"],
490
+ ["lv", "lowvoltage"],
491
+ ["osc", "oscillator"],
492
+ ["vplus", "vin", "vdd", "vcc", "input"],
493
+ ],
494
+ pinout: ["nc", "capp", "gnd", "capm", "vout", "lv", "osc", "vplus"],
495
+ },
496
+ // Pinout verified against TI SLVS033G, p3 pin config + pin-functions table (PDIP-8): 1 FB/SD
497
+ // (shutdown/error-amp input), 2 CAP+, 3 GND, 4 CAP-, 5 VOUT (output), 6 VREF (2.5 V reference
498
+ // output), 7 OSC (input), 8 VCC (supply). This is NOT the 7660 pin order: pin 1 is an input and
499
+ // pin 6 an output, so the part is footprint-compatible but not a drop-in substitute.
500
+ FBSD_LT1054: {
501
+ terminalRoleGroups: [
502
+ ["fbsd", "fb", "sd", "shutdown"],
503
+ ["capp", "capplus"],
504
+ ["gnd", "ground"],
505
+ ["capm", "capminus"],
506
+ ["vout", "negativeoutput", "voutnegative", "out", "output"],
507
+ ["vref", "reference"],
508
+ ["osc", "oscillator"],
509
+ ["vcc", "vin", "vplus", "vdd", "input"],
510
+ ],
511
+ pinout: ["fbsd", "capp", "gnd", "capm", "vout", "vref", "osc", "vcc"],
512
+ },
513
+ // Pinout verified against Linear Technology 1144fa, p2 config + p5 Pin Functions: 1 BOOST
514
+ // (x10 when tied high), 2 CAP+, 3 GND, 4 CAP-, 5 VOUT, 6 SHDN (float/V+ = run, GND = shutdown),
515
+ // 7 OSC, 8 V+. No LV pin and no low-voltage mode split.
516
+ SHDN_LTC1144: {
517
+ terminalRoleGroups: [
518
+ ["boost"],
519
+ ["capp", "capplus"],
520
+ ["gnd", "ground"],
521
+ ["capm", "capminus"],
522
+ ["vout", "negativeoutput", "voutnegative", "out", "output"],
523
+ ["shdn", "shutdown"],
524
+ ["osc", "oscillator"],
525
+ ["vplus", "vin", "vdd", "vcc", "input"],
526
+ ],
527
+ pinout: ["boost", "capp", "gnd", "capm", "vout", "shdn", "osc", "vplus"],
528
+ },
529
+ };
530
+ /** Which wiring skeleton each charge-pump record builds on, by the record's first part id. */
531
+ const chargePumpStructureFor = {
532
+ TC1044S: "BOOST_7660",
533
+ ICL7660: "NC_7660",
534
+ ICL7660A: "NC_7660",
535
+ ICL7660S: "BOOST_7660",
536
+ TC7662B: "BOOST_7660",
537
+ LT1054: "FBSD_LT1054",
538
+ LTC1144: "SHDN_LTC1144",
539
+ MAX1044: "BOOST_7660",
540
+ };
541
+ /** Build one charge-pump entry from its registry record and its wiring skeleton. */
542
+ function chargePumpEntry(record) {
543
+ const sourceOhms = record.params["sourceOhms"];
544
+ const loss = record.params["chargePumpLossVolts"];
545
+ if (sourceOhms === undefined || loss === undefined) {
546
+ throw new Error(`${record.partIds.join(",")}: a charge-pump record states no sourceOhms or chargePumpLossVolts`);
547
+ }
548
+ const key = record.partIds[0];
549
+ const skeletonName = key === undefined ? undefined : chargePumpStructureFor[key];
550
+ const skeleton = skeletonName === undefined ? undefined : chargePumpTerminalStructures[skeletonName];
551
+ if (skeleton === undefined) {
552
+ throw new Error(`${record.partIds.join(",")}: no charge-pump wiring skeleton for ${String(key)}`);
553
+ }
554
+ return {
555
+ partIds: [...record.partIds],
556
+ declaredTypes: [],
557
+ terminalRoleGroups: skeleton.terminalRoleGroups,
558
+ ...provisionalArmOf(record),
559
+ model: {
560
+ kind: "sections",
561
+ sections: [
562
+ {
563
+ law: {
564
+ kind: "voltage-source",
565
+ volts: 0,
566
+ sourceOhms: sourceOhms.value,
567
+ },
568
+ terminals: [4, 2],
569
+ },
570
+ ],
571
+ pinout: skeleton.pinout,
572
+ },
573
+ chargePumpLossVolts: loss.value,
574
+ };
575
+ }
576
+ /** The charge-pump entry for one family, by the record's first part id. Position-preserving. */
577
+ function chargePumpEntryFor(firstPartId) {
578
+ const record = chargePumpRegistryRecords.find((candidate) => candidate.partIds[0] === firstPartId);
579
+ if (record === undefined) {
580
+ throw new Error(`no charge-pump registry record starts with ${firstPartId}`);
581
+ }
582
+ return chargePumpEntry(record);
583
+ }
584
+ // Every skeleton row is consumed by exactly one record, and every record lands on a row.
585
+ for (const key of Object.keys(chargePumpStructureFor)) {
586
+ if (!chargePumpRegistryRecords.some((record) => record.partIds[0] === key)) {
587
+ throw new Error(`charge-pump wiring skeleton for ${key} is consumed by no registry record`);
588
+ }
589
+ }
221
590
  const entries = [
222
591
  // uPC4558, from the artifacts library's own vendored copy:
223
592
  // `parts/op-amps/UPC4558/datasheets/renesas-upc258-upc4558.pdf` (Renesas `UPC258, UPC4558`,
@@ -309,6 +678,8 @@ const entries = [
309
678
  "NJM4558DD",
310
679
  "NJM4558D",
311
680
  "JRC4558D",
681
+ "JRC4558",
682
+ "JRC4558DD",
312
683
  "NJM4558M",
313
684
  "NJM4558E",
314
685
  "NJM4558V",
@@ -397,18 +768,12 @@ const entries = [
397
768
  //
398
769
  // VOM Maximum peak output voltage swing RL = 10 kOhm, TA = 25 C, VCC+- = +-15 V
399
770
  // Min +-10 V Typ +-13.5 V (table 6.7)
400
- // Figure 6-1, VOM vs |VCC+-| (RL = 10 kOhm), is roughly linear: VOM is about 3.3 V at
401
- // |VCC+-| = 4.5 V, the +-4.5 V a 9 V single supply gives.
402
771
  //
403
- // `outputSwingDropVolts` is 1.2 V: the distance the output stops short of each rail at that
404
- // 9 V operating point (4.5 - 3.3). **That figure is read by eye from the plotted line, not
405
- // printed.** The printed figure is the 1.5 V (15 - 13.5) of table 6.7, at +-15 V. 1.2 was
406
- // chosen because the pedals fitted with this part run on 9 V, and it is disclosed here rather
407
- // than presented as a datasheet number. Open-loop gain stays the house 100000 the family
408
- // entry it was split from carried; it is not a sourced TL06x figure.
409
- //
410
- // The drop also reaches a *declared* `opamp` device (the ElectroSmash script-logo Phase 90's
411
- // TL061s) through `registryOpampSwingDrop`, which reads the first section's law.
772
+ // Open-loop gain stays the house 100000 the family entry it was split from carried; it is not a
773
+ // sourced TL06x figure. The output swing is not in this entry: it has its own table,
774
+ // `registry-data/opamp.ts`, which stamps the printed pair (1.5 V a side, `constant`) onto these
775
+ // ids, for a declared `opamp` device and a package alike. The graph-read 1.2 V this entry carried
776
+ // until row 16 (Figure 6-1 at +-4.5 V) was not a printed figure and is gone.
412
777
  {
413
778
  "partIds": [
414
779
  "TL062",
@@ -436,8 +801,7 @@ const entries = [
436
801
  "kind": "ideal-opamp",
437
802
  "railHigh": null,
438
803
  "railLow": null,
439
- "openLoopGain": 100000,
440
- "outputSwingDropVolts": 1.2
804
+ "openLoopGain": 100000
441
805
  },
442
806
  "terminals": [
443
807
  2,
@@ -450,8 +814,7 @@ const entries = [
450
814
  "kind": "ideal-opamp",
451
815
  "railHigh": null,
452
816
  "railLow": null,
453
- "openLoopGain": 100000,
454
- "outputSwingDropVolts": 1.2
817
+ "openLoopGain": 100000
455
818
  },
456
819
  "terminals": [
457
820
  4,
@@ -494,8 +857,7 @@ const entries = [
494
857
  "kind": "ideal-opamp",
495
858
  "railHigh": null,
496
859
  "railLow": null,
497
- "openLoopGain": 100000,
498
- "outputSwingDropVolts": 1.2
860
+ "openLoopGain": 100000
499
861
  },
500
862
  "terminals": [
501
863
  0,
@@ -1555,7 +1917,21 @@ const entries = [
1555
1917
  "clock",
1556
1918
  "phi1",
1557
1919
  "phi1a"
1558
- ]
1920
+ ],
1921
+ // The two 512-stage sections chain when the document ties the first's output to the
1922
+ // second's input (the EH-4700 does: pins 5 and 15 share one node). The 1024 stages
1923
+ // are then read at the second section's output, pin 12.
1924
+ "cascade": {
1925
+ "sectionOut": [
1926
+ "outputa"
1927
+ ],
1928
+ "nextIn": [
1929
+ "inputb"
1930
+ ],
1931
+ "finalOut": [
1932
+ "outputb"
1933
+ ]
1934
+ }
1559
1935
  },
1560
1936
  "barePinPositions": {
1561
1937
  "audioIn": 1,
@@ -3084,18 +3460,10 @@ const entries = [
3084
3460
  },
3085
3461
  // Texas Instruments OP07x, from "OP07x Precision Operational Amplifiers" SLOS099H (rev. March
3086
3462
  // 2023), `parts/op-amps/OP07/datasheets/op07-ti.pdf` in vessel-dsp/artifacts, section 6.5.
3087
- //
3088
- // VOM Output voltage swing, VS = +-15 V RL = 10 kOhm: min +-12 typ +-13
3089
- // RL = 2 kOhm: min +-11.5 typ +-12.8
3090
- //
3091
- // `outputSwingDropVolts` is 2.2 V: 15 - 12.8, the table's own default condition (RL = 2 kOhm,
3092
- // typ). **That is a +-15 V figure carried to a 9 V single supply on the assumption that a
3093
- // bipolar output stage's headroom is constant, not proportional to the supply.** The datasheet
3094
- // states nothing at 9 V, and the proportional reading would give 0.66 V. The measured pin 6
3095
- // swing on a specimen has not been taken: the RAT specimen study
3096
- // (`packet-study/pro-co-rat-2-v4b-op07cp`) scored this law UNDECIDED, its band exceeding 20 dB,
3097
- // and it is shipped on the datasheet alone, as a typical and never a measured part. The datasheet
3098
- // bracket across load current is 2.0 (typ, 10 kOhm) to 3.5 (min spec, 2 kOhm).
3463
+ // Open-loop gain stays the house 100000. The output swing is not in this entry: it has its own
3464
+ // registry-data record (`registry-data/opamp.ts`), which types the printed +-12.8 V (RL = 2 kOhm, VS = +-15 V)
3465
+ // as a `proportional` pair for OP07CP, OP07DP and OP07DN, and lists the bare `OP07` id absent (the
3466
+ // sheet's characteristics apply to the C and D grades only).
3099
3467
  {
3100
3468
  "partIds": [
3101
3469
  "OP07",
@@ -3129,8 +3497,7 @@ const entries = [
3129
3497
  "kind": "ideal-opamp",
3130
3498
  "railHigh": null,
3131
3499
  "railLow": null,
3132
- "openLoopGain": 100000,
3133
- "outputSwingDropVolts": 2.2
3500
+ "openLoopGain": 100000
3134
3501
  },
3135
3502
  "terminals": [
3136
3503
  0,
@@ -3147,6 +3514,11 @@ const entries = [
3147
3514
  }
3148
3515
  },
3149
3516
  // --- Single Op-Amps (LM741, TL071, NJM5534D) --------------------------------------------------------------------------
3517
+ // Wave 5: LM741CN rides this entry. It is the exact PDIP orderable spelling printed
3518
+ // in the TI LM741 SNOSC25D (May 1998, rev Oct 2015) Package Option Addendum:
3519
+ // 'LM741CN/NOPB | Active | Production | PDIP (P) | 8', part marking 'LM 741CN'.
3520
+ // The specs sit under the LM741C column family; open-loop gain stays the house
3521
+ // 100000 and no GBW row is quoted (see opampGainBandwidthAbsent).
3150
3522
  {
3151
3523
  "partIds": [
3152
3524
  "LM741",
@@ -3158,7 +3530,9 @@ const entries = [
3158
3530
  "NJM5534",
3159
3531
  "NJM5534D",
3160
3532
  "NE5534",
3161
- "NJM5534M"
3533
+ "NJM5534M",
3534
+ "JRC5534",
3535
+ "LM741CN"
3162
3536
  ],
3163
3537
  "declaredTypes": [],
3164
3538
  "terminalRoleGroups": [
@@ -3236,170 +3610,16 @@ const entries = [
3236
3610
  ]
3237
3611
  }
3238
3612
  },
3239
- // --- 5V Voltage Regulators (78L05, uPC78L05, 78M05, TA78L05F) - Causal input-dependent physical modeling! ------------------
3240
- // UNRESOLVED: this entry conflates two packages with OPPOSITE pin orders. The TO-92/SOT-89 78L05 is pin1=VOUT, pin2=GND, pin3=VIN (verified: TI LM78L, SNVS754O, Figure 4-2 SOT-89 top view and Figure 4-3 LP 3-pin TO-92 bottom view). The TO-220 7805 is pin1=VIN, pin2=GND, pin3=VOUT. The role groups below assert the 7805 order, because `pin1` sits in the vin group and `pin3` in the vout group.
3241
- // Consequence: a document that declares a 78L05's terminals as pin1/pin2/pin3 binds its input and output the wrong way round. No corpus packet does today -- all seven three-pin regulators declare typed roles as of 2026-09-05 -- so this is latent, not live. Fixing it means SPLITTING the entry by package before a `pinout` can be written, because one pin map cannot serve both.
3242
- {
3243
- "partIds": [
3244
- "uPC78L05",
3245
- "78L05",
3246
- "7805",
3247
- "78M05",
3248
- "TA78L05F",
3249
- // Toshiba's TA78L05S (product page detail.TA78L05S): the same 5 V TA78L die in TO-92.
3250
- // Without it, `boss-dd-5`'s IC5 fell to the pin-name rung and matched the 78L18 entry,
3251
- // solving the pedal's 5 V rail at 18 V.
3252
- "TA78L05S",
3253
- "2930L05",
3254
- "BA78L05",
3255
- "uPC78L05T"
3256
- ],
3257
- "declaredTypes": [],
3258
- "terminalRoleGroups": [
3259
- [
3260
- "vin",
3261
- "in",
3262
- "input",
3263
- "pin1"
3264
- ],
3265
- [
3266
- "gnd",
3267
- "pin2"
3268
- ],
3269
- [
3270
- "vout",
3271
- "out",
3272
- "output",
3273
- "pin3"
3274
- ]
3275
- ],
3276
- "model": {
3277
- "kind": "sections",
3278
- "sections": [
3279
- {
3280
- "law": {
3281
- "kind": "voltage-source",
3282
- "volts": 5,
3283
- "sourceOhms": 1
3284
- },
3285
- "terminals": [
3286
- 2,
3287
- 1
3288
- ]
3289
- }
3290
- ],
3291
- "pinout": [
3292
- null,
3293
- null,
3294
- null
3295
- ]
3296
- }
3297
- },
3298
- // --- 9V Voltage Regulators (7809, 78L09, 78M09, uPC78L09) - Causal input-dependent physical modeling! ------------------
3299
- {
3300
- "partIds": [
3301
- "uPC78L09",
3302
- "78L09",
3303
- "7809",
3304
- "78M09",
3305
- "TA78L09F",
3306
- "2930L09",
3307
- "BA78L09",
3308
- "uPC78L09T"
3309
- ],
3310
- "declaredTypes": [],
3311
- "terminalRoleGroups": [
3312
- [
3313
- "vin",
3314
- "in",
3315
- "input",
3316
- "pin1"
3317
- ],
3318
- [
3319
- "gnd",
3320
- "pin2"
3321
- ],
3322
- [
3323
- "vout",
3324
- "out",
3325
- "output",
3326
- "pin3"
3327
- ]
3328
- ],
3329
- "model": {
3330
- "kind": "sections",
3331
- "sections": [
3332
- {
3333
- "law": {
3334
- "kind": "voltage-source",
3335
- "volts": 9,
3336
- "sourceOhms": 1
3337
- },
3338
- "terminals": [
3339
- 2,
3340
- 1
3341
- ]
3342
- }
3343
- ],
3344
- "pinout": [
3345
- null,
3346
- null,
3347
- null
3348
- ]
3349
- }
3350
- },
3351
- // --- 18V Voltage Regulators (78L18, 78L18AC, 78L18T) - same 3-pin voltage-source shape as 5V/9V above ----------------------
3352
- // electro-harmonix-q-tron's U6 (PartNumber 78L18AC) is the 24V-to-+18V rail; it matches by part id on the first rung, so the
3353
- // role groups below are the standard in/gnd/out family spelling kept for parity with the 5V/9V entries, not a new contract.
3354
- {
3355
- "partIds": [
3356
- "78L18",
3357
- "78L18AC",
3358
- "78L18T",
3359
- "7818"
3360
- ],
3361
- "declaredTypes": [],
3362
- "terminalRoleGroups": [
3363
- [
3364
- "vin",
3365
- "in",
3366
- "input",
3367
- "pin1"
3368
- ],
3369
- [
3370
- "gnd",
3371
- "ground",
3372
- "pin2"
3373
- ],
3374
- [
3375
- "vout",
3376
- "out",
3377
- "output",
3378
- "pin3"
3379
- ]
3380
- ],
3381
- "model": {
3382
- "kind": "sections",
3383
- "sections": [
3384
- {
3385
- "law": {
3386
- "kind": "voltage-source",
3387
- "volts": 18,
3388
- "sourceOhms": 1
3389
- },
3390
- "terminals": [
3391
- 2,
3392
- 1
3393
- ]
3394
- }
3395
- ],
3396
- "pinout": [
3397
- null,
3398
- null,
3399
- null
3400
- ]
3401
- }
3402
- },
3613
+ // --- 5V Voltage Regulators: ids and rail from registry-data/regulator.ts; wiring above --------
3614
+ regulatorEntryForVolts(5),
3615
+ // --- 9V Voltage Regulators: ids and rail from registry-data/regulator.ts; wiring above --------
3616
+ regulatorEntryForVolts(9),
3617
+ // --- 12V Voltage Regulators (wave 5: LM78L12): ids and rail from registry-data/regulator.ts; wiring above --------
3618
+ regulatorEntryForVolts(12),
3619
+ // --- 3.3V Voltage Regulators (wave 5: LM78L33): ids and rail from registry-data/regulator.ts; wiring above --------
3620
+ regulatorEntryForVolts(3.3),
3621
+ // --- 18V Voltage Regulators: ids and rail from registry-data/regulator.ts; wiring above --------
3622
+ regulatorEntryForVolts(18),
3403
3623
  // --- Active Physical Analog Comparators (LM311, NJM311M, LM319, NJM319M) ----------------------------------------------------
3404
3624
  // --- Active Physical Analog Comparators (LM311, NJM311M, LM319, NJM319M, LM393, LM339) ------------------------------------
3405
3625
  {
@@ -5209,63 +5429,33 @@ const entries = [
5209
5429
  ]
5210
5430
  }
5211
5431
  },
5212
- // --- Negative Voltage Regulators (LM7915, LM7912) - Causal input-dependent physical modeling! --------------------------------
5213
- // UNRESOLVED, two separate problems. (1) `7912`/`LM7912` are -12V parts sharing an entry hardcoded to volts: -15, so a 7912 would be modelled 3V low. Only one corpus device uses this entry (deluxe-memory-man's U12_LM7915), so the defect is latent.
5214
- // (2) `volts` here is SIGNED, which contradicts the convention its consumer documents: `declaredRegulatorTerminals` says "the law carries a magnitude" and reverses the source's sense for an output declared `negative`. Declaring `negative` against a signed -15 therefore double-negates and puts +15V on the rail -- measured 2026-09-05. Until this is settled, a negative regulator using this entry must declare its output `positive`, which is why deluxe-memory-man does.
5432
+ // --- Negative Voltage Regulators: ids and rail from registry-data/regulator.ts; wiring above --------
5433
+ // The 7912/LM7912 sharing and the signed-volts convention are recorded on the record; the
5434
+ // wiring skeleton for -15 V is shared. Until the convention is settled, a negative regulator
5435
+ // using this entry must declare its output `positive`, which is why deluxe-memory-man does.
5436
+ regulatorEntryForVolts(-15),
5437
+ // --- TC1044S charge-pump inverter (Microchip): ids and loss/slope from registry-data/charge-pump.ts; wiring above ---
5438
+ chargePumpEntryFor("TC1044S"),
5439
+ // --- ICL7660 charge-pump inverter (Intersil/Renesas): ids and loss/slope from registry-data/charge-pump.ts; wiring above ---
5440
+ chargePumpEntryFor("ICL7660"),
5441
+ // --- ICL7660A charge-pump inverter (Intersil/Renesas): ids and loss/slope from registry-data/charge-pump.ts; wiring above ---
5442
+ chargePumpEntryFor("ICL7660A"),
5443
+ // --- ICL7660S charge-pump inverter (Renesas): ids and loss/slope from registry-data/charge-pump.ts; wiring above ---
5444
+ chargePumpEntryFor("ICL7660S"),
5445
+ // --- TC7662B charge-pump inverter (Microchip): ids and loss/slope from registry-data/charge-pump.ts; wiring above ---
5446
+ chargePumpEntryFor("TC7662B"),
5447
+ // --- LT1054 switched-capacitor converter (TI second source): ids and loss/slope from registry-data/charge-pump.ts; wiring above ---
5448
+ chargePumpEntryFor("LT1054"),
5449
+ // --- LTC1144 wide-input converter (Linear Tech): ids and loss/slope from registry-data/charge-pump.ts; wiring above ---
5450
+ chargePumpEntryFor("LTC1144"),
5451
+ // --- MAX1044 switched-capacitor converter (Maxim): ids and loss/slope from registry-data/charge-pump.ts; wiring above ---
5452
+ chargePumpEntryFor("MAX1044"),
5453
+ // --- AK4552VT8 8-pin codec body alternative ----------------------------------------------------------------------------------
5215
5454
  {
5216
5455
  "partIds": [
5217
- "LM7915",
5218
- "7915",
5219
- "7912",
5220
- "LM7912"
5221
- ],
5222
- "declaredTypes": [],
5223
- "terminalRoleGroups": [
5224
- [
5225
- "gnd",
5226
- "pin1"
5227
- ],
5228
- [
5229
- "vin",
5230
- "in",
5231
- "input",
5232
- "pin2"
5233
- ],
5234
- [
5235
- "vout",
5236
- "out",
5237
- "output",
5238
- "pin3"
5239
- ]
5240
- ],
5241
- "model": {
5242
- "kind": "sections",
5243
- "sections": [
5244
- {
5245
- "law": {
5246
- "kind": "voltage-source",
5247
- "volts": -15,
5248
- "sourceOhms": 1
5249
- },
5250
- "terminals": [
5251
- 2,
5252
- 0
5253
- ]
5254
- }
5255
- ],
5256
- "pinout": [
5257
- null,
5258
- null,
5259
- null
5260
- ]
5261
- }
5262
- },
5263
- // --- AK4552VT8 8-pin codec body alternative ----------------------------------------------------------------------------------
5264
- {
5265
- "partIds": [
5266
- "AK4552VT8",
5267
- "AK4552VT",
5268
- "AK4552"
5456
+ "AK4552VT8",
5457
+ "AK4552VT",
5458
+ "AK4552"
5269
5459
  ],
5270
5460
  "declaredTypes": [],
5271
5461
  "terminalRoleGroups": [],
@@ -5370,10 +5560,11 @@ const entries = [
5370
5560
  // output=pinout[0] -- the `ideal-opamp` law reads only those three; vplus/vminus are not
5371
5561
  // wired to the law (rails come from the circuit, exactly as the CD4049 template above).
5372
5562
  // No MC33178 datasheet is vendored in the artifacts library, so the open-loop gain is the
5373
- // class-general 100 dB (1e4) rather than a sourced MC33178 figure. `outputSwingDropVolts` 1.1 is
5374
- // the registry's (`component-opamp-chips.json`) figure, 15 V - 13.9 V from the onsemi MC33178-D
5375
- // output swing table at +-15 V, RL = 2 kOhm; that datasheet is likewise not vendored here, so the
5376
- // figure is carried from the registry's citation and not re-read.
5563
+ // class-general 100 dB (1e4) rather than a sourced MC33178 figure. It states **no output swing**:
5564
+ // the 1.1 V this entry carried until row 16 was the registry's (`component-opamp-chips.json`)
5565
+ // citation of the onsemi MC33178-D table at +-15 V, never re-read here because the datasheet is not
5566
+ // vendored, which does not meet the printed-TYP-only rule; the id is listed in
5567
+ // `opampOutputSwingAbsent` with that reason.
5377
5568
  {
5378
5569
  "partIds": [
5379
5570
  "MC33178",
@@ -5390,8 +5581,7 @@ const entries = [
5390
5581
  "kind": "ideal-opamp",
5391
5582
  "railHigh": null,
5392
5583
  "railLow": null,
5393
- "openLoopGain": 100000,
5394
- "outputSwingDropVolts": 1.1
5584
+ "openLoopGain": 100000
5395
5585
  },
5396
5586
  "terminals": [
5397
5587
  2,
@@ -5463,6 +5653,11 @@ const entries = [
5463
5653
  // positions fail `pinoutMatches`, so this needs its own entry rather than an alias added to the
5464
5654
  // existing one. "CD4013AE" is not registered elsewhere, so no collision.
5465
5655
  //
5656
+ // `SCL4013A` (Solid State Scientific, the part `electro-harmonix-slapback-echo`'s `IC3` is printed as on the
5657
+ // factory sheet: a 14-pin 7+7 package with the 4013 pin legend, second-half outputs 12/13 NC) rides this
5658
+ // entry rather than the first CD4013 one because that packet declares the same `pin<N>_<role>` names
5659
+ // as `mxr-m117r-flanger`'s `U3`, in strict pin order 1..14.
5660
+ //
5466
5661
  // Verified directly against the document: `U3`'s 14 terminals are declared in strict ascending
5467
5662
  // pin order (`pin1_q1` through `pin14_vdd`), matching the standard CD4013 pinout, and the
5468
5663
  // component's own `Description` corroborates the same reading pin-by-pin ("pins 4 RESET1, 6 SET1,
@@ -5471,7 +5666,8 @@ const entries = [
5471
5666
  // existing CD4013 entry already assumes for the same law.
5472
5667
  {
5473
5668
  "partIds": [
5474
- "CD4013AE"
5669
+ "CD4013AE",
5670
+ "SCL4013A"
5475
5671
  ],
5476
5672
  "declaredTypes": [],
5477
5673
  "terminalRoleGroups": [],
@@ -5723,52 +5919,14 @@ const entries = [
5723
5919
  ]
5724
5920
  }
5725
5921
  },
5726
- // --- LM340T-15 +15V linear regulator (mxr-m117r-flanger) --------------------------------------------------------------------
5727
- //
5728
- // Not a CMOS/analog-switch part either, but the sole remaining blocker on `mxr-m117r-flanger`
5729
- // after the `CD4013AE` fix above: its `VR1` names PartNumber `LM340T-15`, an unregistered fixed
5730
- // +15V linear regulator. Same shape and same `voltage-source` law as the existing 78L05/78L09
5731
- // entries above (input/ground/output, output held at the regulator's fixed rail relative to
5732
- // ground) -- only the voltage differs, read directly off the part number and the component's own
5733
- // `Description: "15 V regulator from the selected factory schematic."` `VR1` declares exactly
5734
- // three terminals, `input, ground, output`, in that order, matching the existing regulator
5735
- // entries' `terminals: [2, 1]` (output relative to ground) convention directly.
5736
- //
5737
- // `terminalRoleGroups` is left empty rather than copying the existing regulator entries'
5738
- // generic `in`/`gnd`/`out` aliases: `VR1` already resolves through the exact-partId rung, and
5739
- // this repo's own corpus shows that generic pinout-signature fallback misidentifying an unrelated
5740
- // part with similarly-generic terminal names (`boss-oc-2`'s `BA634-5`, five terminals including
5741
- // `in`/`gnd`/`out`, incidentally satisfies the existing 78L05 entry's groups and is only saved
5742
- // from a wrong law by the later terminal-count guard). No reason to add a second entry to that
5743
- // same weak ladder when the exact part id already does the job.
5744
- {
5745
- "partIds": [
5746
- "LM340T-15"
5747
- ],
5748
- "declaredTypes": [],
5749
- "terminalRoleGroups": [],
5750
- "model": {
5751
- "kind": "sections",
5752
- "sections": [
5753
- {
5754
- "law": {
5755
- "kind": "voltage-source",
5756
- "volts": 15,
5757
- "sourceOhms": 1
5758
- },
5759
- "terminals": [
5760
- 2,
5761
- 1
5762
- ]
5763
- }
5764
- ],
5765
- "pinout": [
5766
- null,
5767
- null,
5768
- null
5769
- ]
5770
- }
5771
- },
5922
+ // --- LM340T-15 +15V linear regulator (mxr-m117r-flanger): ids and rail from registry-data/regulator.ts; wiring above ----
5923
+ // The sole remaining blocker on `mxr-m117r-flanger` after the `CD4013AE` fix above: its `VR1`
5924
+ // names PartNumber `LM340T-15`, an unregistered fixed +15V linear regulator. Same shape and same
5925
+ // `voltage-source` law as the 78L05/78L09 entries (input/ground/output, output held at the
5926
+ // regulator's fixed rail relative to ground) -- `VR1` declares exactly three terminals,
5927
+ // `input, ground, output`, in that order, matching the regulator entries' `terminals: [2, 1]`
5928
+ // (output relative to ground) convention directly.
5929
+ regulatorEntryForVolts(15),
5772
5930
  // --- Tube rectifiers ------------------------------------------------------------------
5773
5931
  //
5774
5932
  // A vacuum rectifier drops tens of volts where a silicon diode drops less than one, and that
@@ -6696,164 +6854,1798 @@ const entries = [
6696
6854
  ]
6697
6855
  }
6698
6856
  },
6699
- ...zenerBreakdownVolts.map(([partIds, breakdownVolts]) => zenerLaw(partIds, breakdownVolts)),
6700
- // 2SK30A series/shunt switches (`ibanez-ts9` Q102/Q104, the only packet with
6701
- // specimen evidence; `boss-sg-1-slow-gear` declares the same id and its gate is
6702
- // inert there). Channel repeats the class default by construction (see above);
6703
- // only `gateSaturationCurrent` carries part data.
6704
- fetLaw(["2SK30A"], {
6705
- kind: "fet",
6706
- channel: "n",
6707
- thresholdVolts: -2,
6708
- transconductance: 1e-3,
6709
- channelLengthModulation: 0,
6710
- subthresholdVolts: 0.07,
6711
- gateSaturationCurrent: 1e-14,
6712
- gateOnsetVolts: 0.5,
6713
- gateScaleVolts: 0.06,
6714
- }),
6715
- ];
6716
- export const opampGainBandwidthDatasheet = [
6717
- {
6718
- partIds: ["NJM4558DD", "NJM4558D", "JRC4558D", "NJM4558M", "NJM4558E", "NJM4558V", "NJM4558L", "NJM4558"],
6719
- hertz: 3e6,
6720
- source: "NJM4558/datasheets/nisshinbo-njm4558.pdf (Nisshinbo NJM4558, date code 20250303) p2, ELECTRICAL " +
6721
- "CHARACTERISTICS (V+/V-=+-15V, Ta=25C): 'Gain Bandwidth Product | GB | MIN - | TYP 3 | MAX - | MHz'. " +
6722
- "One column for every suffix; the D rank changes the noise row only. " +
6723
- "JRC4558D is not a printed id in this sheet: it is the NJR/JRC marking of the NJM4558D per " +
6724
- "parts/op-amps/JRC4558D.md ('NJR/JRC version. Same datasheet specs as NJM4558'; GBP 3 M), included per " +
6725
- "owner decision 2026-10-15 because a Boss SD-1 specimen carries a chip marked 'JRC 4558D' and three " +
6726
- "corpus packets name JRC4558D. Secondary only: the 1980s JRC databook scan prints the part as NJM4558D " +
6727
- "under the JRC logo (p2-98).",
6728
- },
6729
- {
6730
- partIds: ["RC4558", "RC4558P", "RC4558D"],
6731
- hertz: 4e6,
6732
- source: "RC4558/datasheets/rc4558-ti.pdf and RC4558P/datasheets/rc4558p-ti.pdf (TI SLOS073H, rev Oct 2024; the " +
6733
- "two are identical through p35) 5.5 p6, VCC+-=+-15V, TA=25C, RL=2k: 'GBW | Gain-bandwidth product | " +
6734
- "f = 10kHz | TYP 4 | MHz'. Rev G printed 3 MHz; this copy (Rev H) changed it to 4.",
6735
- },
6736
- {
6737
- partIds: ["TL061", "TL061CP", "TL061CN", "TL062", "TL062C", "TL062CP", "TL062CN"],
6738
- hertz: 1e6,
6739
- source: "TL062/datasheets/tl062.pdf (TI SLOS078N, rev Aug 2023) Table 6.7 p8 (the same row and value in Tables " +
6740
- "6.8 p9 and 6.9 p10): 'B1 | Unity-gain bandwidth | RL = 10k, TA = 25C | TYP 1 | MHz' in every grade column.",
6741
- },
6742
- {
6743
- partIds: ["TL071CP", "TL072CP", "TL082CP"],
6744
- hertz: 5.25e6,
6745
- source: "TL072/datasheets/tl072.pdf and TL071/datasheets/tl071.pdf (byte-identical; TI SLOS080W, rev Jul 2025) " +
6746
- "5.8 p15, VS=+-15V, TA=25C: 'GBW | Gain-bandwidth product | All other devices | TYP 5.25 | MHz' (the row " +
6747
- "'All NS and PS packages; All TL07xM devices' is TYP 3). TL082CP: TL082/datasheets/tl082.pdf (SLOS081O, " +
6748
- "rev Sep 2025) 5.8 p14, the same two rows; its Rev N entry p29: 'Increased gain bandwidth of all " +
6749
- "non-NS/non-PS packages and non-TL08xM devices from 3 MHz to 5.25 MHz'. A -CP part is a PDIP: neither an " +
6750
- "NS/PS package nor an M device, so the 'all other devices' row applies.",
6751
- },
6752
- {
6753
- partIds: ["NE5532", "NE5532P", "NE5532D"],
6754
- hertz: 12e6,
6755
- source: "NE5532/datasheets/ne5532.pdf (TI SLOS075K, rev Dec 2025) 5.5 p4, VCC+-=+-15V, TA=25C: 'B1 | Unity-gain " +
6756
- "bandwidth | TYP 12 | MHz' (p1 Features: '12MHz Typ'; Rev K p11: 'Changed ... from 10MHz to 12MHz typ'). " +
6757
- "The same document's prose, 6.3.1 p6 and 7.1.2.1 p8, still says 10 MHz; the characteristics table is taken.",
6758
- },
6759
- {
6760
- partIds: ["LM358", "LM358N", "LM358P"],
6761
- hertz: 0.7e6,
6762
- source: "LM358/datasheets/lm358-ti.pdf (TI SLOS068AB, rev Oct 2024) 5.7 p10 (LM358, LM358A), VS=5V, TA=25C: " +
6763
- "'GBW | Gain bandwidth product | TYP 0.7 | MHz'. The B/BA versions (5.5 p6) are TYP 1.2 and are different " +
6764
- "part ids; the only PDIP orderables printed are LM358P and LM358AP.",
6765
- },
6766
- {
6767
- partIds: ["M5218AL", "M5218AP", "M5218AFP"],
6768
- hertz: 7e6,
6769
- source: "5218/datasheets/mitsubishi-m5218.pdf (Mitsubishi M5218AL/P/FP, no document number or date; image scan) " +
6770
- "p2 (folio 2-468), Ta=25C, Vcc=+-15V: 'fT | Gain bandwidth product | TYP 7 | MHz' (p1 Features: " +
6771
- "'fT=7MHz(typ.)'). The sheet is the A grade; the plain M5218L, P and FP ids are not on it.",
6772
- },
6773
- {
6774
- partIds: ["TL022", "TL022C", "TL022CP", "TL022CD", "TL022CN"],
6775
- hertz: 0.5e6,
6776
- source: "TL022/datasheets/ti-tl022.pdf (TI SLOS076, rev Sep 1990) p3, VCC+-=+-15V, TA=25C: 'B1 | Unity-gain " +
6777
- "bandwidth | TL022C TYP 0.5 | TL022M TYP 0.5 | MHz'.",
6778
- },
6779
- {
6780
- partIds: ["OP07CP", "OP07DP", "OP07DN"],
6781
- hertz: 0.6e6,
6782
- source: "OP07/datasheets/op07-ti.pdf (TI SLOS099H, rev Mar 2023) 6.5 p6, TA=25C, VS=+-15V, RL=2k, FREQUENCY " +
6783
- "RESPONSE: 'Unity gain bandwidth | MIN 0.4 | TYP 0.6 | MHz'; footnote (1): the listed specifications apply " +
6784
- "to OP07C and OP07D. The row has no package split.",
6785
- },
6786
- {
6787
- partIds: ["NE5534", "NJM5534", "NJM5534D", "NJM5534M"],
6788
- hertz: 10e6,
6789
- source: "NJM5534D/datasheets/NE5534_TI.pdf (TI SLOS070D, rev Nov 2014) 7.5 p5, VCC+-=+-15V, TA=25C: 'B1 | " +
6790
- "Unity-gain bandwidth | CC = 22 pF, CL = 100 pF | TYP 10 | MHz'; and njrc-njm5534d-page-2.png (New Japan " +
6791
- "Radio NJM5534, folio 4-236): 'Gain Bandwidth Product | GB | CC=22pF, CL=100pF | TYP 10 | MHz'. Both state " +
6792
- "the figure with a 22 pF compensation capacitor.",
6793
- },
6794
- ];
6795
- /** Op-amp ids this catalog holds whose datasheet states no TYP gain-bandwidth figure, and why. They stay `null`. */
6796
- export const opampGainBandwidthAbsent = [
6797
- {
6798
- partIds: ["uPC4558C", "uPC4558G2", "uPC4558"],
6799
- reason: "UPC4558/datasheets/renesas-upc258-upc4558.pdf (Renesas R03DS0145EJ0200 Rev 2.00) prints no gain-bandwidth " +
6800
- "row: the p3 electrical characteristics list slew rate (1.0 V/us) and no GB/GBW/fT/B1. The p4 'AV vs. f' " +
6801
- "graph reaches 0 dB near 1.5 MHz by eye, which is not a printed figure. The registry's 3 MHz has no " +
6802
- "support in this sheet.",
6803
- },
6804
- {
6805
- partIds: ["RC4558C", "4558"],
6806
- reason: "RC4558C is not an orderable printed in SLOS073H, and '4558' is a generic name no datasheet owns.",
6807
- },
6808
- {
6809
- partIds: ["TL071", "TL071CN", "TL072", "TL072CN", "TL072IP", "TL072D", "TL082"],
6810
- reason: "The TL07x/TL08x GBW row splits on package and grade (TYP 3 MHz for NS/PS packages and M grades, TYP 5.25 " +
6811
- "for all other devices). These ids do not state, or the datasheet does not print for them, which row " +
6812
- "applies.",
6813
- },
6814
- {
6815
- partIds: ["LF353", "LF353N", "LF353M", "LM741", "UA741", "uA741", "TA7504S", "TA7504", "MC33178", "MC33178P", "MC33178D", "BA718"],
6816
- reason: "No vendored datasheet.",
6817
- },
6818
- {
6819
- partIds: ["CA1458", "CA1458E", "CA1458T"],
6820
- reason: "The datasheet this file cites for open-loop gain (Harris File Number 531.2) is not vendored in the " +
6821
- "artifacts library, so no GBW row can be quoted.",
6822
- },
6857
+ // Wave-1 op-amp entries: sections whose gain-bandwidth product the data registry
6858
+ // stamps (see `registry-data/opamp.ts` and `withDatasheetGainBandwidth` below). The
6859
+ // open-loop gain is the sheet's own TYP AOL transcribed; the swing pair is deliberately
6860
+ // absent (owner D4 freeze, upheld for this wave).
6861
+ // TI LM833 dual audio op-amp, from `parts/op-amps/LM833/datasheets/lm833-ti.pdf` (TI
6862
+ // SLOS481B, rev Oct 2014). Pin diagram and Pin Functions table, 6 p3 (D/SOIC, DGK/MSOP
6863
+ // and P/PDIP packages, TOP VIEW): 1 OUT1, 2 IN1-, 3 IN1+, 4 VCC-, 5 IN2+, 6 IN2-,
6864
+ // 7 OUT2, 8 VCC+ -- the standard dual order, read off the sheet's own table, with the
6865
+ // same pin-numbered role convention as the 4558 entry above (pin-numbered tokens only;
6866
+ // no generic alias may satisfy two sections at once).
6867
+ //
6868
+ // Electrical characteristics 7.5/7.6 p5 at VCC+- = +-15 V, TA = 25 C:
6869
+ // Large-signal differential voltage amplification AVD, RL >= 2 kOhm, VO = +-10 V:
6870
+ // MIN 90 TYP 110 dB. Used as `openLoopGain: decibelGain(110)`, the typ figure.
6871
+ // GBW is not in this entry: it lives in the data-registry row (TYP 16 MHz at f = 100 kHz),
6872
+ // stamped by the pass below. Wave 4: LM833N rides this entry for open-loop gain
6873
+ // (TI LM833-N sheet SNOSBD8E prints LM833N/NOPB as the PDIP orderable of the same
6874
+ // die with the same AV 110 dB TYP) but carries its own GBW row: the LM833-N sheet
6875
+ // prints GBW TYP 15 MHz, not the LM833 sheet's 16, so the pass splits the entry.
6823
6876
  {
6824
- partIds: [
6825
- "M5218A", "M5218L", "M5218P", "M5218FP", "M5218",
6826
- "M5216AL", "M5216AP", "M5216AFP", "M5216A", "M5216L", "M5216P", "M5216FP", "M5216",
6827
- "M5281AL", "M5281AP", "M5281AFP", "M5281A", "M5281L", "M5281P", "M5281FP", "M5281",
6877
+ "partIds": [
6878
+ "LM833",
6879
+ "LM833N"
6828
6880
  ],
6829
- reason: "The vendored Mitsubishi sheet is the M5218AL/P/FP document; the plain M5218 L/P/FP, the bare 'A' and " +
6830
- "'M5218' ids are not printed on it, and M5216/M5281 appear in no vendored datasheet.",
6831
- },
6832
- {
6833
- partIds: ["M5223L", "M5223P", "M5223FP", "M5223"],
6834
- reason: "M5223AL/datasheets/m5223al-page-2.png has no gain-bandwidth row; its 'Voltage Gain vs Frequency Response' " +
6835
- "graph reaches 0 dB near 1 MHz at VCC=15 V and near 2 MHz at VCC=30 V by eye.",
6881
+ "declaredTypes": [],
6882
+ "terminalRoleGroups": [
6883
+ [
6884
+ "OUT_A_pin1"
6885
+ ],
6886
+ [
6887
+ "INVERTING_A_pin2"
6888
+ ],
6889
+ [
6890
+ "NONINVERTING_A_pin3"
6891
+ ],
6892
+ [
6893
+ "V_MINUS_pin4"
6894
+ ],
6895
+ [
6896
+ "NONINVERTING_B_pin5"
6897
+ ],
6898
+ [
6899
+ "INVERTING_B_pin6"
6900
+ ],
6901
+ [
6902
+ "OUT_B_pin7"
6903
+ ],
6904
+ [
6905
+ "V_PLUS_pin8"
6906
+ ]
6907
+ ],
6908
+ "model": {
6909
+ "kind": "sections",
6910
+ "pinout": [
6911
+ "OUT_A",
6912
+ "IN_A_MINUS",
6913
+ "IN_A_PLUS",
6914
+ "VEE",
6915
+ "IN_B_PLUS",
6916
+ "IN_B_MINUS",
6917
+ "OUT_B",
6918
+ "VCC"
6919
+ ],
6920
+ "sections": [
6921
+ {
6922
+ "law": {
6923
+ "kind": "ideal-opamp",
6924
+ "railHigh": null,
6925
+ "railLow": null,
6926
+ "openLoopGain": 316227.7660168379
6927
+ },
6928
+ "terminals": [
6929
+ 2,
6930
+ 1,
6931
+ 0
6932
+ ]
6933
+ },
6934
+ {
6935
+ "law": {
6936
+ "kind": "ideal-opamp",
6937
+ "railHigh": null,
6938
+ "railLow": null,
6939
+ "openLoopGain": 316227.7660168379
6940
+ },
6941
+ "terminals": [
6942
+ 4,
6943
+ 5,
6944
+ 6
6945
+ ]
6946
+ }
6947
+ ]
6948
+ }
6836
6949
  },
6950
+ // Wave-3 op-amp entries: sections whose open-loop gain is the sheet's own TYP AOL
6951
+ // transcribed (the gain-bandwidth product lives in `registry-data/opamp.ts`, stamped by
6952
+ // the passes below; the swing pair stays absent per the D4 freeze). GBW rows and catalog
6953
+ // entries are 1:1 by part id, so no entry splits. LM1458/N carry AOL only (no GBW row
6954
+ // exists; see `opampGainBandwidthAbsent`).
6955
+ // NJR NJM4580 dual audio op-amp, from `parts/op-amps/JRC4580/datasheets/njm4580d-njr.pdf`
6956
+ // (NJR Ver. 2012-09-14). PIN FUNCTION table, p1 (DIP-8 TOP VIEW): 1 A OUTPUT, 2 A -INPUT,
6957
+ // 3 A +INPUT, 4 V-, 5 B +INPUT, 6 B -INPUT, 7 B OUTPUT, 8 V+ -- the standard dual order,
6958
+ // with the same pin-numbered role convention as the LM833 entry above (pin-numbered
6959
+ // tokens only; no generic alias may satisfy two sections at once).
6960
+ //
6961
+ // Electrical characteristics p2 at V+/V- = +-15 V, Ta = 25 C:
6962
+ // Voltage Gain AV, RL >= 2 kOhm, VO = +-10 V: MIN 90 TYP 110 dB.
6963
+ // Used as `openLoopGain: decibelGain(110)`, the typ figure (the D rank changes the noise
6964
+ // row only). The JRC ids rest on `parts/op-amps/JRC4580.md` ('JRC4580D / NJM4580D',
6965
+ // primary local datasheet this sheet) -- the JRC4558D precedent (owner decision
6966
+ // 2026-10-15: the NJR/JRC marking rides the NJM sheet). GBW is not in this entry: it
6967
+ // lives in the data-registry row (TYP 15 MHz at f = 10 kHz), stamped by the pass below.
6837
6968
  {
6838
- partIds: ["OP07"],
6839
- reason: "The OP07 sheet's electrical characteristics apply to OP07C and OP07D only; the bare id has no column.",
6969
+ "partIds": [
6970
+ "JRC4580D",
6971
+ "JRC4580",
6972
+ "NJM4580D",
6973
+ "NJM4580"
6974
+ ],
6975
+ "declaredTypes": [],
6976
+ "terminalRoleGroups": [
6977
+ [
6978
+ "OUT_A_pin1"
6979
+ ],
6980
+ [
6981
+ "INVERTING_A_pin2"
6982
+ ],
6983
+ [
6984
+ "NONINVERTING_A_pin3"
6985
+ ],
6986
+ [
6987
+ "V_MINUS_pin4"
6988
+ ],
6989
+ [
6990
+ "NONINVERTING_B_pin5"
6991
+ ],
6992
+ [
6993
+ "INVERTING_B_pin6"
6994
+ ],
6995
+ [
6996
+ "OUT_B_pin7"
6997
+ ],
6998
+ [
6999
+ "V_PLUS_pin8"
7000
+ ]
7001
+ ],
7002
+ "model": {
7003
+ "kind": "sections",
7004
+ "pinout": [
7005
+ "OUT_A",
7006
+ "IN_A_MINUS",
7007
+ "IN_A_PLUS",
7008
+ "VEE",
7009
+ "IN_B_PLUS",
7010
+ "IN_B_MINUS",
7011
+ "OUT_B",
7012
+ "VCC"
7013
+ ],
7014
+ "sections": [
7015
+ {
7016
+ "law": {
7017
+ "kind": "ideal-opamp",
7018
+ "railHigh": null,
7019
+ "railLow": null,
7020
+ "openLoopGain": 316227.7660168379
7021
+ },
7022
+ "terminals": [
7023
+ 2,
7024
+ 1,
7025
+ 0
7026
+ ]
7027
+ },
7028
+ {
7029
+ "law": {
7030
+ "kind": "ideal-opamp",
7031
+ "railHigh": null,
7032
+ "railLow": null,
7033
+ "openLoopGain": 316227.7660168379
7034
+ },
7035
+ "terminals": [
7036
+ 4,
7037
+ 5,
7038
+ 6
7039
+ ]
7040
+ }
7041
+ ]
7042
+ }
6840
7043
  },
7044
+ // TI OPA2134 dual audio op-amp, from `parts/op-amps/OPA2134/datasheets/opa2134-ti.pdf`
7045
+ // (TI SBOS058B, Dec 1997, Rev. B Nov 2024). Table 4-1 Pin Functions: OPA2134, p3 (D/SOIC
7046
+ // and P/PDIP 8-pin, top view Fig. 4-2): OUT A-1, -IN A-2, +IN A-3, V--4, +IN B-5, -IN B-6,
7047
+ // OUT B-7, V+-8 -- the standard dual order, same pin-numbered role convention.
7048
+ // OPA2134PA is a printed PDIP-8 orderable of the same die (orderable table, marking
7049
+ // OPA2134PA); the specs below are family-wide with no per-orderable split.
7050
+ //
7051
+ // Electrical characteristics 5.7 p7 at TA = 25 C, VS = +-15 V:
7052
+ // AOL Open-loop voltage gain, RL = 2 kOhm, -13.8 V <= VO <= 13.5 V:
7053
+ // MIN 104 TYP 120 dB (a second row at RL = 10 kOhm reads the same 104/120).
7054
+ // Used as `openLoopGain: decibelGain(120)`, the typ figure. GBW is not in this entry:
7055
+ // it lives in the data-registry row (TYP 8 MHz), stamped by the pass below.
6841
7056
  {
6842
- partIds: ["TL022CP2", "AK4552VT", "AK4552", "AK4552VT13", "AK4552VT8"],
6843
- reason: "Not an op-amp datasheet part: a converter or macro shell modelled with an op-amp section.",
6844
- },
6845
- ];
6846
- /**
6847
- * The catalog's op-amp entries with each datasheet product stamped onto the sections of exactly the ids
6848
- * it covers. An entry whose ids have different products (or none) is split into one entry per product,
6849
- * keeping every other field, so no id changes behaviour except by gaining its own figure.
6850
- *
6851
- * Loud on any mismatch: a row or absent id the catalog holds no op-amp entry for, an id in both lists, or
6852
- * an id in two rows is a table error and throws at load rather than being silently skipped.
6853
- */
6854
- function withDatasheetGainBandwidth(base) {
7057
+ "partIds": [
7058
+ "OPA2134",
7059
+ "OPA2134PA"
7060
+ ],
7061
+ "declaredTypes": [],
7062
+ "terminalRoleGroups": [
7063
+ [
7064
+ "OUT_A_pin1"
7065
+ ],
7066
+ [
7067
+ "INVERTING_A_pin2"
7068
+ ],
7069
+ [
7070
+ "NONINVERTING_A_pin3"
7071
+ ],
7072
+ [
7073
+ "V_MINUS_pin4"
7074
+ ],
7075
+ [
7076
+ "NONINVERTING_B_pin5"
7077
+ ],
7078
+ [
7079
+ "INVERTING_B_pin6"
7080
+ ],
7081
+ [
7082
+ "OUT_B_pin7"
7083
+ ],
7084
+ [
7085
+ "V_PLUS_pin8"
7086
+ ]
7087
+ ],
7088
+ "model": {
7089
+ "kind": "sections",
7090
+ "pinout": [
7091
+ "OUT_A",
7092
+ "IN_A_MINUS",
7093
+ "IN_A_PLUS",
7094
+ "VEE",
7095
+ "IN_B_PLUS",
7096
+ "IN_B_MINUS",
7097
+ "OUT_B",
7098
+ "VCC"
7099
+ ],
7100
+ "sections": [
7101
+ {
7102
+ "law": {
7103
+ "kind": "ideal-opamp",
7104
+ "railHigh": null,
7105
+ "railLow": null,
7106
+ "openLoopGain": 1000000
7107
+ },
7108
+ "terminals": [
7109
+ 2,
7110
+ 1,
7111
+ 0
7112
+ ]
7113
+ },
7114
+ {
7115
+ "law": {
7116
+ "kind": "ideal-opamp",
7117
+ "railHigh": null,
7118
+ "railLow": null,
7119
+ "openLoopGain": 1000000
7120
+ },
7121
+ "terminals": [
7122
+ 4,
7123
+ 5,
7124
+ 6
7125
+ ]
7126
+ }
7127
+ ]
7128
+ }
7129
+ },
7130
+ // TI LM1458 dual op-amp, from `parts/op-amps/LM1458/datasheets/lm1458.pdf` (TI SNOSBU4D,
7131
+ // Apr 1998, Rev. D Mar 2013, ex-National; NOT the ST sheet `MC1458.md` points at).
7132
+ // Connection Diagram Fig. 2 Dual-In-Line Package (Top View), p1: 1 OUTPUT A,
7133
+ // 2 INVERTING INPUT A, 3 NON-INVERTING INPUT A, 4 V-, 5 NON-INVERTING INPUT B,
7134
+ // 6 INVERTING INPUT B, 7 OUTPUT B, 8 V+ -- the standard dual order, same pin-numbered
7135
+ // role convention. LM1458N is printed (Abs Max 'LM1458N 400 mW' p2; orderables
7136
+ // LM1458N/NOPB p5, PDIP-8); the AC specs sit under the generic LM1458 column, which
7137
+ // covers it (the CA1458 entry above is the neighbouring point not jumped to: Harris
7138
+ // 200,000 vs TI 160,000 for the same row, so this comes from the document that names
7139
+ // LM1458).
7140
+ //
7141
+ // Electrical characteristics p2 at TA = 25 C, VS = +-15 V, VOUT = +-10 V, RL >= 2 kOhm:
7142
+ // Large Signal Voltage Gain: LM1458 MIN 20 TYP 160 V/mV (LM1558 50/160 is the sibling
7143
+ // column). Used as `openLoopGain: 160000`, the typ figure. The sheet prints no GBW row
7144
+ // at all (see `opampGainBandwidthAbsent`); the swing pair stays absent per the D4 freeze
7145
+ // (see `opampOutputSwingAbsent`).
7146
+ {
7147
+ "partIds": [
7148
+ "LM1458",
7149
+ "LM1458N"
7150
+ ],
7151
+ "declaredTypes": [],
7152
+ "terminalRoleGroups": [
7153
+ [
7154
+ "OUT_A_pin1"
7155
+ ],
7156
+ [
7157
+ "INVERTING_A_pin2"
7158
+ ],
7159
+ [
7160
+ "NONINVERTING_A_pin3"
7161
+ ],
7162
+ [
7163
+ "V_MINUS_pin4"
7164
+ ],
7165
+ [
7166
+ "NONINVERTING_B_pin5"
7167
+ ],
7168
+ [
7169
+ "INVERTING_B_pin6"
7170
+ ],
7171
+ [
7172
+ "OUT_B_pin7"
7173
+ ],
7174
+ [
7175
+ "V_PLUS_pin8"
7176
+ ]
7177
+ ],
7178
+ "model": {
7179
+ "kind": "sections",
7180
+ "pinout": [
7181
+ "OUT_A",
7182
+ "IN_A_MINUS",
7183
+ "IN_A_PLUS",
7184
+ "VEE",
7185
+ "IN_B_PLUS",
7186
+ "IN_B_MINUS",
7187
+ "OUT_B",
7188
+ "VCC"
7189
+ ],
7190
+ "sections": [
7191
+ {
7192
+ "law": {
7193
+ "kind": "ideal-opamp",
7194
+ "railHigh": null,
7195
+ "railLow": null,
7196
+ "openLoopGain": 160000
7197
+ },
7198
+ "terminals": [
7199
+ 2,
7200
+ 1,
7201
+ 0
7202
+ ]
7203
+ },
7204
+ {
7205
+ "law": {
7206
+ "kind": "ideal-opamp",
7207
+ "railHigh": null,
7208
+ "railLow": null,
7209
+ "openLoopGain": 160000
7210
+ },
7211
+ "terminals": [
7212
+ 4,
7213
+ 5,
7214
+ 6
7215
+ ]
7216
+ }
7217
+ ]
7218
+ }
7219
+ },
7220
+ // Wave-4 op-amp entries: sections whose open-loop gain is the sheet's own TYP AOL
7221
+ // transcribed (the gain-bandwidth product lives in `registry-data/opamp.ts`, stamped by
7222
+ // the passes below; the swing pair stays absent per the D4 freeze). GBW rows and catalog
7223
+ // entries are 1:1 by part id, so no entry splits. LM308 and UA741CP carry AOL only (no
7224
+ // GBW row exists; see `opampGainBandwidthAbsent`).
7225
+ // TI LM108/LM208/LM308 single precision op-amp, from SNOSBS5A (May 2004, rev Sept
7226
+ // 2011, OBSOLETE): 5.x Electrical Characteristics (TA = 25C, VS = +-15 V, VOUT = +-10 V,
7227
+ // RL >= 10 kOhm): 'Large Signal Voltage Gain | MIN 50 (LM108/208) / 25 (LM308) |
7228
+ // TYP 300 | V/mV'. Used as `openLoopGain: 300000`. No GBW row is transcribed (the
7229
+ // sheet prints compensation values and open-loop response graphs only; the Fig. 6
7230
+ // 250 kHz/3.5 MHz numbers belong to the LM101A-boosted application circuit).
7231
+ // Pinout: Figure 8 Dual-In-Line Package Top View (8-Pin) p11 (1 COMP, 2 INPUT-,
7232
+ // 3 INPUT+, 4 V-, 5 NC, 6 OUTPUT, 7 V+, 8 COMP 2); the entry keeps the LF351
7233
+ // three-terminal capture (IN+ pin3, IN- pin2, OUT pin6), which is the same
7234
+ // LM741-compatible single layout.
7235
+ {
7236
+ "partIds": [
7237
+ "LM308"
7238
+ ],
7239
+ "declaredTypes": [],
7240
+ "terminalRoleGroups": [
7241
+ [
7242
+ "vin+",
7243
+ "in+",
7244
+ "pin3"
7245
+ ],
7246
+ [
7247
+ "vin-",
7248
+ "in-",
7249
+ "pin2"
7250
+ ],
7251
+ [
7252
+ "vout",
7253
+ "out",
7254
+ "pin6"
7255
+ ]
7256
+ ],
7257
+ "model": {
7258
+ "kind": "sections",
7259
+ "sections": [
7260
+ {
7261
+ "law": {
7262
+ "kind": "ideal-opamp",
7263
+ "railHigh": null,
7264
+ "railLow": null,
7265
+ "openLoopGain": 300000
7266
+ },
7267
+ "terminals": [
7268
+ 0,
7269
+ 1,
7270
+ 2
7271
+ ]
7272
+ }
7273
+ ],
7274
+ "pinout": [
7275
+ "vin+",
7276
+ "vin-",
7277
+ "vout"
7278
+ ]
7279
+ }
7280
+ },
7281
+ // TI LM324 quad op-amp, from SLOS066AE (Aug 1975, rev Sept 2025): 6.7 Electrical
7282
+ // Characteristics for LM324/LM324K (VCC = 15 V, VO = 1 V to 11 V, RL >= 2 kOhm, 25C):
7283
+ // 'Large-signal differential voltage amplification AVD | MIN 25 TYP 100 | V/mV'.
7284
+ // Used as `openLoopGain: 100000`. GBW is not in this entry: it lives in the
7285
+ // data-registry row (B1 unity-gain bandwidth TYP 1.2 MHz, 6.10 Operating Conditions),
7286
+ // stamped by the pass below. Pinout: Table 5-1 Pin Functions N (PDIP) p3, the
7287
+ // standard quad order (1OUT, 1IN-, 1IN+, VCC+, 2IN+, 2IN-, 2OUT, 3OUT, 3IN-, 3IN+,
7288
+ // VCC-, 4IN+, 4IN-, 4OUT), same A/B/C/D convention as the TL074 entry above.
7289
+ {
7290
+ "partIds": [
7291
+ "LM324"
7292
+ ],
7293
+ "declaredTypes": [],
7294
+ "terminalRoleGroups": [
7295
+ [
7296
+ "OUT_A_pin1"
7297
+ ],
7298
+ [
7299
+ "INVERTING_A_pin2"
7300
+ ],
7301
+ [
7302
+ "NONINVERTING_A_pin3"
7303
+ ],
7304
+ [
7305
+ "V_PLUS_pin4"
7306
+ ],
7307
+ [
7308
+ "NONINVERTING_B_pin5"
7309
+ ],
7310
+ [
7311
+ "INVERTING_B_pin6"
7312
+ ],
7313
+ [
7314
+ "OUT_B_pin7"
7315
+ ],
7316
+ [
7317
+ "OUT_C_pin8"
7318
+ ],
7319
+ [
7320
+ "INVERTING_C_pin9"
7321
+ ],
7322
+ [
7323
+ "NONINVERTING_C_pin10"
7324
+ ],
7325
+ [
7326
+ "V_MINUS_pin11"
7327
+ ],
7328
+ [
7329
+ "NONINVERTING_D_pin12"
7330
+ ],
7331
+ [
7332
+ "INVERTING_D_pin13"
7333
+ ],
7334
+ [
7335
+ "OUT_D_pin14"
7336
+ ]
7337
+ ],
7338
+ "model": {
7339
+ "kind": "sections",
7340
+ "pinout": [
7341
+ "OUT_A",
7342
+ "IN_A_MINUS",
7343
+ "IN_A_PLUS",
7344
+ "VCC",
7345
+ "IN_B_PLUS",
7346
+ "IN_B_MINUS",
7347
+ "OUT_B",
7348
+ "OUT_C",
7349
+ "IN_C_MINUS",
7350
+ "IN_C_PLUS",
7351
+ "VEE",
7352
+ "IN_D_PLUS",
7353
+ "IN_D_MINUS",
7354
+ "OUT_D"
7355
+ ],
7356
+ "sections": [
7357
+ {
7358
+ "law": {
7359
+ "kind": "ideal-opamp",
7360
+ "railHigh": null,
7361
+ "railLow": null,
7362
+ "openLoopGain": 100000
7363
+ },
7364
+ "terminals": [
7365
+ 2,
7366
+ 1,
7367
+ 0
7368
+ ]
7369
+ },
7370
+ {
7371
+ "law": {
7372
+ "kind": "ideal-opamp",
7373
+ "railHigh": null,
7374
+ "railLow": null,
7375
+ "openLoopGain": 100000
7376
+ },
7377
+ "terminals": [
7378
+ 4,
7379
+ 5,
7380
+ 6
7381
+ ]
7382
+ },
7383
+ {
7384
+ "law": {
7385
+ "kind": "ideal-opamp",
7386
+ "railHigh": null,
7387
+ "railLow": null,
7388
+ "openLoopGain": 100000
7389
+ },
7390
+ "terminals": [
7391
+ 9,
7392
+ 8,
7393
+ 7
7394
+ ]
7395
+ },
7396
+ {
7397
+ "law": {
7398
+ "kind": "ideal-opamp",
7399
+ "railHigh": null,
7400
+ "railLow": null,
7401
+ "openLoopGain": 100000
7402
+ },
7403
+ "terminals": [
7404
+ 11,
7405
+ 12,
7406
+ 13
7407
+ ]
7408
+ }
7409
+ ]
7410
+ }
7411
+ },
7412
+ // TI uA741C single op-amp in the PDIP-8 UA741CP orderable, from SLOS094H (Nov 1970,
7413
+ // rev Feb 2026): 5.4 Electrical Characteristics uA741C (VCC+- = +-15 V, VO = +-10 V,
7414
+ // RL >= 2 kOhm, 25C): 'Large-signal differential voltage amplification AVD |
7415
+ // MIN 20 TYP 200 | V/mV'. Used as `openLoopGain: 200000`. The sheet prints the
7416
+ // orderable as UA741CP (PDIP P|8) and the device table as uA741CP; the ASCII id is
7417
+ // the orderable spelling. No GBW row is transcribed (switching tables print only
7418
+ // rise time, overshoot and SR). Pinout: Figure 4-2 D-or-P 8-pin Top View + Table 4-2
7419
+ // Pin Functions p3 (1 NC, 2 IN-, 3 IN+, 4 VCC-, 5 NC, 6 OUT, 7 VCC+, 8 NC); the entry
7420
+ // keeps the LF351 three-terminal capture (IN+ pin3, IN- pin2, OUT pin6).
7421
+ {
7422
+ "partIds": [
7423
+ "UA741CP"
7424
+ ],
7425
+ "declaredTypes": [],
7426
+ "terminalRoleGroups": [
7427
+ [
7428
+ "vin+",
7429
+ "in+",
7430
+ "pin3"
7431
+ ],
7432
+ [
7433
+ "vin-",
7434
+ "in-",
7435
+ "pin2"
7436
+ ],
7437
+ [
7438
+ "vout",
7439
+ "out",
7440
+ "pin6"
7441
+ ]
7442
+ ],
7443
+ "model": {
7444
+ "kind": "sections",
7445
+ "sections": [
7446
+ {
7447
+ "law": {
7448
+ "kind": "ideal-opamp",
7449
+ "railHigh": null,
7450
+ "railLow": null,
7451
+ "openLoopGain": 200000
7452
+ },
7453
+ "terminals": [
7454
+ 0,
7455
+ 1,
7456
+ 2
7457
+ ]
7458
+ }
7459
+ ],
7460
+ "pinout": [
7461
+ "vin+",
7462
+ "vin-",
7463
+ "vout"
7464
+ ]
7465
+ }
7466
+ },
7467
+ // TI TLC2262 dual LinCMOS op-amp, from SLOS177D (Feb 1997, rev Mar 2001): TLC2262C
7468
+ // operating characteristics (VDD+- = +-5 V, the 10 V table closest to a 9 V pedal):
7469
+ // 'Large-signal differential voltage amplification, RL = 50 kOhm, VO = +-4 V:
7470
+ // MIN 80 TYP 200 V/mV'. Used as `openLoopGain: 200000`. Coordinator standing rule
7471
+ // 2026-10-19: the bare id the sheet names in its description takes the commercial (C)
7472
+ // grade column, disclosed here; no suffixed grade is aliased. GBW is not in this
7473
+ // entry: it lives in the data-registry row (TYP 0.73 MHz, same table), stamped by the
7474
+ // pass below. Pinout: D/P/PW 8-pin Top View p3, the standard dual order
7475
+ // (1OUT, 1IN-, 1IN+, VDD-/GND, 2IN+, 2IN-, 2OUT, VDD+), same A/B convention as the
7476
+ // LM833 entry above.
7477
+ {
7478
+ "partIds": [
7479
+ "TLC2262"
7480
+ ],
7481
+ "declaredTypes": [],
7482
+ "terminalRoleGroups": [
7483
+ [
7484
+ "OUT_A_pin1"
7485
+ ],
7486
+ [
7487
+ "INVERTING_A_pin2"
7488
+ ],
7489
+ [
7490
+ "NONINVERTING_A_pin3"
7491
+ ],
7492
+ [
7493
+ "V_MINUS_pin4"
7494
+ ],
7495
+ [
7496
+ "NONINVERTING_B_pin5"
7497
+ ],
7498
+ [
7499
+ "INVERTING_B_pin6"
7500
+ ],
7501
+ [
7502
+ "OUT_B_pin7"
7503
+ ],
7504
+ [
7505
+ "V_PLUS_pin8"
7506
+ ]
7507
+ ],
7508
+ "model": {
7509
+ "kind": "sections",
7510
+ "pinout": [
7511
+ "OUT_A",
7512
+ "IN_A_MINUS",
7513
+ "IN_A_PLUS",
7514
+ "VEE",
7515
+ "IN_B_PLUS",
7516
+ "IN_B_MINUS",
7517
+ "OUT_B",
7518
+ "VCC"
7519
+ ],
7520
+ "sections": [
7521
+ {
7522
+ "law": {
7523
+ "kind": "ideal-opamp",
7524
+ "railHigh": null,
7525
+ "railLow": null,
7526
+ "openLoopGain": 200000
7527
+ },
7528
+ "terminals": [
7529
+ 2,
7530
+ 1,
7531
+ 0
7532
+ ]
7533
+ },
7534
+ {
7535
+ "law": {
7536
+ "kind": "ideal-opamp",
7537
+ "railHigh": null,
7538
+ "railLow": null,
7539
+ "openLoopGain": 200000
7540
+ },
7541
+ "terminals": [
7542
+ 4,
7543
+ 5,
7544
+ 6
7545
+ ]
7546
+ }
7547
+ ]
7548
+ }
7549
+ },
7550
+ // onsemi MC33174 quad op-amp, from MC33171/D Rev. 14 Jul 2025: DC table (VCC = +15 V,
7551
+ // VEE = -15 V, RL to ground, TA = 25C) p3: 'Large Signal Voltage Gain (VO = +-10 V,
7552
+ // RL = 10 k), AVOL MIN 50 TYP 500 V/mV'. Used as `openLoopGain: 500000`. GBW is not
7553
+ // in this entry: it lives in the data-registry row (TYP 1.8 MHz at f = 100 kHz, AC
7554
+ // table p4), stamped by the pass below. Pinout: PIN CONNECTIONS quad diagram p2
7555
+ // incl. PDIP-14 (Output 1 = 1, Inputs 1, VCC, Inputs 2, Output 2, Output 3,
7556
+ // Inputs 3, VEE, Inputs 4, Output 4), the standard quad order, same A/B/C/D
7557
+ // convention as the TL074 entry.
7558
+ {
7559
+ "partIds": [
7560
+ "MC33174"
7561
+ ],
7562
+ "declaredTypes": [],
7563
+ "terminalRoleGroups": [
7564
+ [
7565
+ "OUT_A_pin1"
7566
+ ],
7567
+ [
7568
+ "INVERTING_A_pin2"
7569
+ ],
7570
+ [
7571
+ "NONINVERTING_A_pin3"
7572
+ ],
7573
+ [
7574
+ "V_PLUS_pin4"
7575
+ ],
7576
+ [
7577
+ "NONINVERTING_B_pin5"
7578
+ ],
7579
+ [
7580
+ "INVERTING_B_pin6"
7581
+ ],
7582
+ [
7583
+ "OUT_B_pin7"
7584
+ ],
7585
+ [
7586
+ "OUT_C_pin8"
7587
+ ],
7588
+ [
7589
+ "INVERTING_C_pin9"
7590
+ ],
7591
+ [
7592
+ "NONINVERTING_C_pin10"
7593
+ ],
7594
+ [
7595
+ "V_MINUS_pin11"
7596
+ ],
7597
+ [
7598
+ "NONINVERTING_D_pin12"
7599
+ ],
7600
+ [
7601
+ "INVERTING_D_pin13"
7602
+ ],
7603
+ [
7604
+ "OUT_D_pin14"
7605
+ ]
7606
+ ],
7607
+ "model": {
7608
+ "kind": "sections",
7609
+ "pinout": [
7610
+ "OUT_A",
7611
+ "IN_A_MINUS",
7612
+ "IN_A_PLUS",
7613
+ "VCC",
7614
+ "IN_B_PLUS",
7615
+ "IN_B_MINUS",
7616
+ "OUT_B",
7617
+ "OUT_C",
7618
+ "IN_C_MINUS",
7619
+ "IN_C_PLUS",
7620
+ "VEE",
7621
+ "IN_D_PLUS",
7622
+ "IN_D_MINUS",
7623
+ "OUT_D"
7624
+ ],
7625
+ "sections": [
7626
+ {
7627
+ "law": {
7628
+ "kind": "ideal-opamp",
7629
+ "railHigh": null,
7630
+ "railLow": null,
7631
+ "openLoopGain": 500000
7632
+ },
7633
+ "terminals": [
7634
+ 2,
7635
+ 1,
7636
+ 0
7637
+ ]
7638
+ },
7639
+ {
7640
+ "law": {
7641
+ "kind": "ideal-opamp",
7642
+ "railHigh": null,
7643
+ "railLow": null,
7644
+ "openLoopGain": 500000
7645
+ },
7646
+ "terminals": [
7647
+ 4,
7648
+ 5,
7649
+ 6
7650
+ ]
7651
+ },
7652
+ {
7653
+ "law": {
7654
+ "kind": "ideal-opamp",
7655
+ "railHigh": null,
7656
+ "railLow": null,
7657
+ "openLoopGain": 500000
7658
+ },
7659
+ "terminals": [
7660
+ 9,
7661
+ 8,
7662
+ 7
7663
+ ]
7664
+ },
7665
+ {
7666
+ "law": {
7667
+ "kind": "ideal-opamp",
7668
+ "railHigh": null,
7669
+ "railLow": null,
7670
+ "openLoopGain": 500000
7671
+ },
7672
+ "terminals": [
7673
+ 11,
7674
+ 12,
7675
+ 13
7676
+ ]
7677
+ }
7678
+ ]
7679
+ }
7680
+ },
7681
+ // Wave-5 op-amp entries: sections whose open-loop gain is the sheet's own TYP AOL
7682
+ // transcribed (the gain-bandwidth product lives in `registry-data/opamp.ts`, stamped by
7683
+ // the passes below; the swing pair stays absent per the D4 freeze). GBW rows and catalog
7684
+ // entries are 1:1 by part id, so no entry splits. TL072C carries AOL only (no single
7685
+ // GBW figure exists; see `opampGainBandwidthAbsent`).
7686
+ // TI TL072C dual JFET-input op-amp, from SLOS080W (Sep 1978, rev Jul 2025): 5.8 DC
7687
+ // (VS = +-15 V, TA = 25C, VO = 0 V): 'AOL | Open-loop voltage gain | TL07xC |
7688
+ // MIN 25 TYP 200 V/mV'. Used as `openLoopGain: 200000` (bare id takes the commercial
7689
+ // C-grade column, disclosed; no suffixed alias). Pinout: Table 4-3 Pin Functions
7690
+ // TL072x (1 1OUT, 2 1IN-, 3 1IN+, 4 VCC-, 5 2IN+, 6 2IN-, 7 2OUT, 8 VCC+), the
7691
+ // standard dual order, same pin-numbered role convention as the NJM4580 entry.
7692
+ {
7693
+ "partIds": [
7694
+ "TL072C"
7695
+ ],
7696
+ "declaredTypes": [],
7697
+ "terminalRoleGroups": [
7698
+ [
7699
+ "OUT_A_pin1"
7700
+ ],
7701
+ [
7702
+ "INVERTING_A_pin2"
7703
+ ],
7704
+ [
7705
+ "NONINVERTING_A_pin3"
7706
+ ],
7707
+ [
7708
+ "V_MINUS_pin4"
7709
+ ],
7710
+ [
7711
+ "NONINVERTING_B_pin5"
7712
+ ],
7713
+ [
7714
+ "INVERTING_B_pin6"
7715
+ ],
7716
+ [
7717
+ "OUT_B_pin7"
7718
+ ],
7719
+ [
7720
+ "V_PLUS_pin8"
7721
+ ]
7722
+ ],
7723
+ "model": {
7724
+ "kind": "sections",
7725
+ "pinout": [
7726
+ "OUT_A",
7727
+ "IN_A_MINUS",
7728
+ "IN_A_PLUS",
7729
+ "VEE",
7730
+ "IN_B_PLUS",
7731
+ "IN_B_MINUS",
7732
+ "OUT_B",
7733
+ "VCC"
7734
+ ],
7735
+ "sections": [
7736
+ {
7737
+ "law": {
7738
+ "kind": "ideal-opamp",
7739
+ "railHigh": null,
7740
+ "railLow": null,
7741
+ "openLoopGain": 200000
7742
+ },
7743
+ "terminals": [
7744
+ 2,
7745
+ 1,
7746
+ 0
7747
+ ]
7748
+ },
7749
+ {
7750
+ "law": {
7751
+ "kind": "ideal-opamp",
7752
+ "railHigh": null,
7753
+ "railLow": null,
7754
+ "openLoopGain": 200000
7755
+ },
7756
+ "terminals": [
7757
+ 4,
7758
+ 5,
7759
+ 6
7760
+ ]
7761
+ }
7762
+ ]
7763
+ }
7764
+ },
7765
+ // TI OPA1678 dual audio op-amp, from SBOS855E (Jan 2017 - Dec 2022): 6.7 Electrical
7766
+ // Characteristics (VS = +-15 V, TA = 25C, RL = 2 kOhm, VCM = VOUT = midsupply):
7767
+ // 'AOL | Open-loop voltage gain | (V-) + 0.8 V <= VO <= (V+) - 0.8 V | MIN 106
7768
+ // TYP 114 | dB'. Used as `openLoopGain: decibelGain(114)` = 501187.2336272725.
7769
+ // GBW lives in the data-registry row (TYP 16 MHz at G = 1). Pinout: 5 Pin
7770
+ // Configuration and Functions, Pin Functions OPA1678 (OUT A-1, -IN A-2, +IN A-3,
7771
+ // V--4, +IN B-5, -IN B-6, OUT B-7, V+-8), the standard dual order.
7772
+ {
7773
+ "partIds": [
7774
+ "OPA1678"
7775
+ ],
7776
+ "declaredTypes": [],
7777
+ "terminalRoleGroups": [
7778
+ [
7779
+ "OUT_A_pin1"
7780
+ ],
7781
+ [
7782
+ "INVERTING_A_pin2"
7783
+ ],
7784
+ [
7785
+ "NONINVERTING_A_pin3"
7786
+ ],
7787
+ [
7788
+ "V_MINUS_pin4"
7789
+ ],
7790
+ [
7791
+ "NONINVERTING_B_pin5"
7792
+ ],
7793
+ [
7794
+ "INVERTING_B_pin6"
7795
+ ],
7796
+ [
7797
+ "OUT_B_pin7"
7798
+ ],
7799
+ [
7800
+ "V_PLUS_pin8"
7801
+ ]
7802
+ ],
7803
+ "model": {
7804
+ "kind": "sections",
7805
+ "pinout": [
7806
+ "OUT_A",
7807
+ "IN_A_MINUS",
7808
+ "IN_A_PLUS",
7809
+ "VEE",
7810
+ "IN_B_PLUS",
7811
+ "IN_B_MINUS",
7812
+ "OUT_B",
7813
+ "VCC"
7814
+ ],
7815
+ "sections": [
7816
+ {
7817
+ "law": {
7818
+ "kind": "ideal-opamp",
7819
+ "railHigh": null,
7820
+ "railLow": null,
7821
+ "openLoopGain": 501187.2336272725
7822
+ },
7823
+ "terminals": [
7824
+ 2,
7825
+ 1,
7826
+ 0
7827
+ ]
7828
+ },
7829
+ {
7830
+ "law": {
7831
+ "kind": "ideal-opamp",
7832
+ "railHigh": null,
7833
+ "railLow": null,
7834
+ "openLoopGain": 501187.2336272725
7835
+ },
7836
+ "terminals": [
7837
+ 4,
7838
+ 5,
7839
+ 6
7840
+ ]
7841
+ }
7842
+ ]
7843
+ }
7844
+ },
7845
+ // TI (Burr-Brown) OPA134 single audio op-amp, from SBOS058B (Dec 1997, Rev. B
7846
+ // Nov 2024): 5.7 Electrical Characteristics (TA = 25C, VS = +-15 V): 'AOL |
7847
+ // Open-loop voltage gain | RL = 2 kOhm, -13.8 V <= VO <= 13.5 V | MIN 104
7848
+ // TYP 120 | dB' (a second row at RL = 10 kOhm reads the same 104/120). Used as
7849
+ // `openLoopGain: decibelGain(120)` = 1000000. GBW lives in the data-registry row
7850
+ // (TYP 8 MHz). OPA134PA is the printed PDIP-8 orderable of the same die
7851
+ // (orderable table: 'OPA134PA | Active | Production | PDIP (P) | 8'; family specs
7852
+ // with no per-orderable split). Pinout: Figure 4-1 + Pin Functions OPA134
7853
+ // (+IN-3, -IN-2, OUT-6); the entry keeps the LF351 three-terminal capture.
7854
+ {
7855
+ "partIds": [
7856
+ "OPA134PA"
7857
+ ],
7858
+ "declaredTypes": [],
7859
+ "terminalRoleGroups": [
7860
+ [
7861
+ "vin+",
7862
+ "in+",
7863
+ "pin3"
7864
+ ],
7865
+ [
7866
+ "vin-",
7867
+ "in-",
7868
+ "pin2"
7869
+ ],
7870
+ [
7871
+ "vout",
7872
+ "out",
7873
+ "pin6"
7874
+ ]
7875
+ ],
7876
+ "model": {
7877
+ "kind": "sections",
7878
+ "sections": [
7879
+ {
7880
+ "law": {
7881
+ "kind": "ideal-opamp",
7882
+ "railHigh": null,
7883
+ "railLow": null,
7884
+ "openLoopGain": 1000000
7885
+ },
7886
+ "terminals": [
7887
+ 0,
7888
+ 1,
7889
+ 2
7890
+ ]
7891
+ }
7892
+ ],
7893
+ "pinout": [
7894
+ "vin+",
7895
+ "vin-",
7896
+ "vout"
7897
+ ]
7898
+ }
7899
+ },
7900
+ // TI (Burr-Brown) OPA604 single op-amp, from SBOS019A (Jan 1992 - Sep 2003):
7901
+ // ELECTRICAL CHARACTERISTICS (TA = +25C, VS = +-15 V, MIN/TYP/MAX columns):
7902
+ // 'OPEN-LOOP GAIN | Open-Loop Voltage Gain, VO = +-10 V, RL = 1 kOhm: MIN 80
7903
+ // TYP 100 dB'. Used as `openLoopGain: decibelGain(100)` = 100000. GBW lives in
7904
+ // the data-registry row (TYP 20 MHz at G = 100, quoted with its condition).
7905
+ // OPA604AP is named by the specs header ('OPA604AP, AU'); the PDIP suffix follows
7906
+ // the Burr-Brown AP convention (the Package Option Addendum lists AU active).
7907
+ // Pinout: the DIP/SOIC diagram (-In-2, +In-3, OUT-6); the LF351 three-terminal
7908
+ // capture, same as the UA741CP entry.
7909
+ {
7910
+ "partIds": [
7911
+ "OPA604AP"
7912
+ ],
7913
+ "declaredTypes": [],
7914
+ "terminalRoleGroups": [
7915
+ [
7916
+ "vin+",
7917
+ "in+",
7918
+ "pin3"
7919
+ ],
7920
+ [
7921
+ "vin-",
7922
+ "in-",
7923
+ "pin2"
7924
+ ],
7925
+ [
7926
+ "vout",
7927
+ "out",
7928
+ "pin6"
7929
+ ]
7930
+ ],
7931
+ "model": {
7932
+ "kind": "sections",
7933
+ "sections": [
7934
+ {
7935
+ "law": {
7936
+ "kind": "ideal-opamp",
7937
+ "railHigh": null,
7938
+ "railLow": null,
7939
+ "openLoopGain": 100000
7940
+ },
7941
+ "terminals": [
7942
+ 0,
7943
+ 1,
7944
+ 2
7945
+ ]
7946
+ }
7947
+ ],
7948
+ "pinout": [
7949
+ "vin+",
7950
+ "vin-",
7951
+ "vout"
7952
+ ]
7953
+ }
7954
+ },
7955
+ // Wave-2 op-amp entries: sections whose open-loop gain is the sheet's own TYP AOL
7956
+ // transcribed (the gain-bandwidth product lives in `registry-data/opamp.ts`, stamped by
7957
+ // the passes below; the swing pair stays absent per the D4 freeze). GBW rows and catalog
7958
+ // entries are 1:1 by part id, so no entry splits.
7959
+ // ADI AD712 dual precision BiFET op-amp, from the ADI AD712 datasheet Rev. B
7960
+ // (SPECIFICATIONS at VS = +-15 V @ TA = +25C): OPEN-LOOP GAIN MIN 150 TYP 400 V/mV
7961
+ // (J/A/S grades; K/B/T and C print MIN 200 TYP 400 -- TYP 400 in every grade, so the
7962
+ // bare id carries it). Used as `openLoopGain: 400000`. The sheet's frequency row reads
7963
+ // 'Small Signal Bandwidth 3.0 MIN 4.0 TYP MHz' with no closed-loop gain stated and never
7964
+ // uses the term gain-bandwidth, so no GBW figure is transcribed (see
7965
+ // `opampGainBandwidthAbsent`). Pinout: 'pinned out in a standard op amp configuration'
7966
+ // (dual DIP-8 order, same pin-numbered role convention as the LM833 entry above).
7967
+ {
7968
+ "partIds": [
7969
+ "AD712"
7970
+ ],
7971
+ "declaredTypes": [],
7972
+ "terminalRoleGroups": [
7973
+ [
7974
+ "OUT_A_pin1"
7975
+ ],
7976
+ [
7977
+ "INVERTING_A_pin2"
7978
+ ],
7979
+ [
7980
+ "NONINVERTING_A_pin3"
7981
+ ],
7982
+ [
7983
+ "V_MINUS_pin4"
7984
+ ],
7985
+ [
7986
+ "NONINVERTING_B_pin5"
7987
+ ],
7988
+ [
7989
+ "INVERTING_B_pin6"
7990
+ ],
7991
+ [
7992
+ "OUT_B_pin7"
7993
+ ],
7994
+ [
7995
+ "V_PLUS_pin8"
7996
+ ]
7997
+ ],
7998
+ "model": {
7999
+ "kind": "sections",
8000
+ "pinout": [
8001
+ "OUT_A",
8002
+ "IN_A_MINUS",
8003
+ "IN_A_PLUS",
8004
+ "VEE",
8005
+ "IN_B_PLUS",
8006
+ "IN_B_MINUS",
8007
+ "OUT_B",
8008
+ "VCC"
8009
+ ],
8010
+ "sections": [
8011
+ {
8012
+ "law": {
8013
+ "kind": "ideal-opamp",
8014
+ "railHigh": null,
8015
+ "railLow": null,
8016
+ "openLoopGain": 400000
8017
+ },
8018
+ "terminals": [
8019
+ 2,
8020
+ 1,
8021
+ 0
8022
+ ]
8023
+ },
8024
+ {
8025
+ "law": {
8026
+ "kind": "ideal-opamp",
8027
+ "railHigh": null,
8028
+ "railLow": null,
8029
+ "openLoopGain": 400000
8030
+ },
8031
+ "terminals": [
8032
+ 4,
8033
+ 5,
8034
+ 6
8035
+ ]
8036
+ }
8037
+ ]
8038
+ }
8039
+ },
8040
+ // TI LF347 quad JFET-input op-amp, from SLOS013C (Mar 1987, rev Mar 2016):
8041
+ // 6.5 AVD 'Large signal differential voltage, VO = +-10 V, RL = 2 kOhm, 25C: MIN 25
8042
+ // TYP 100 V/mV' (6.6 LF347B: MIN 50 TYP 100). Used as `openLoopGain: 100000`.
8043
+ // LF347N is the PDIP-14 of the LF347 grade. Pinout: the sheet's own Pin Functions
8044
+ // table (1 1OUT, 2 1IN-, 3 1IN+, 4 VCC+, 5 2IN+, 6 2IN-, 7 2OUT, 8 3OUT, 9 3IN-,
8045
+ // 10 3IN+, 11 VCC-, 12 4IN+, 13 4IN-, 14 4OUT) -- the standard quad order.
8046
+ {
8047
+ "partIds": [
8048
+ "LF347",
8049
+ "LF347N"
8050
+ ],
8051
+ "declaredTypes": [],
8052
+ "terminalRoleGroups": [
8053
+ [
8054
+ "OUT_A_pin1"
8055
+ ],
8056
+ [
8057
+ "INVERTING_A_pin2"
8058
+ ],
8059
+ [
8060
+ "NONINVERTING_A_pin3"
8061
+ ],
8062
+ [
8063
+ "V_PLUS_pin4"
8064
+ ],
8065
+ [
8066
+ "NONINVERTING_B_pin5"
8067
+ ],
8068
+ [
8069
+ "INVERTING_B_pin6"
8070
+ ],
8071
+ [
8072
+ "OUT_B_pin7"
8073
+ ],
8074
+ [
8075
+ "OUT_C_pin8"
8076
+ ],
8077
+ [
8078
+ "INVERTING_C_pin9"
8079
+ ],
8080
+ [
8081
+ "NONINVERTING_C_pin10"
8082
+ ],
8083
+ [
8084
+ "V_MINUS_pin11"
8085
+ ],
8086
+ [
8087
+ "NONINVERTING_D_pin12"
8088
+ ],
8089
+ [
8090
+ "INVERTING_D_pin13"
8091
+ ],
8092
+ [
8093
+ "OUT_D_pin14"
8094
+ ]
8095
+ ],
8096
+ "model": {
8097
+ "kind": "sections",
8098
+ "pinout": [
8099
+ "OUT_A",
8100
+ "IN_A_MINUS",
8101
+ "IN_A_PLUS",
8102
+ "VCC",
8103
+ "IN_B_PLUS",
8104
+ "IN_B_MINUS",
8105
+ "OUT_B",
8106
+ "OUT_C",
8107
+ "IN_C_MINUS",
8108
+ "IN_C_PLUS",
8109
+ "VEE",
8110
+ "IN_D_PLUS",
8111
+ "IN_D_MINUS",
8112
+ "OUT_D"
8113
+ ],
8114
+ "sections": [
8115
+ {
8116
+ "law": {
8117
+ "kind": "ideal-opamp",
8118
+ "railHigh": null,
8119
+ "railLow": null,
8120
+ "openLoopGain": 100000
8121
+ },
8122
+ "terminals": [
8123
+ 2,
8124
+ 1,
8125
+ 0
8126
+ ]
8127
+ },
8128
+ {
8129
+ "law": {
8130
+ "kind": "ideal-opamp",
8131
+ "railHigh": null,
8132
+ "railLow": null,
8133
+ "openLoopGain": 100000
8134
+ },
8135
+ "terminals": [
8136
+ 4,
8137
+ 5,
8138
+ 6
8139
+ ]
8140
+ },
8141
+ {
8142
+ "law": {
8143
+ "kind": "ideal-opamp",
8144
+ "railHigh": null,
8145
+ "railLow": null,
8146
+ "openLoopGain": 100000
8147
+ },
8148
+ "terminals": [
8149
+ 9,
8150
+ 8,
8151
+ 7
8152
+ ]
8153
+ },
8154
+ {
8155
+ "law": {
8156
+ "kind": "ideal-opamp",
8157
+ "railHigh": null,
8158
+ "railLow": null,
8159
+ "openLoopGain": 100000
8160
+ },
8161
+ "terminals": [
8162
+ 11,
8163
+ 12,
8164
+ 13
8165
+ ]
8166
+ }
8167
+ ]
8168
+ }
8169
+ },
8170
+ // National Semiconductor LF351 single JFET-input op-amp, from DS005648 Rev. 3 (1998):
8171
+ // AVOL 'Large Signal Voltage Gain, VS = +-15 V, TA = 25C, VO = +-10 V, RL = 2 kOhm:
8172
+ // MIN 25 TYP 100 V/mV'. Used as `openLoopGain: 100000`. LF351N is the DIP orderable
8173
+ // (NS Package N08E, sheet p11 'Order Number LF351M or LF351N'). Pinout: 'pin compatible
8174
+ // with the standard LM741' (single DIP-8: IN+ pin3, IN- pin2, OUT pin6), the same
8175
+ // three-terminal capture as the TL061 entry above.
8176
+ {
8177
+ "partIds": [
8178
+ "LF351",
8179
+ "LF351N"
8180
+ ],
8181
+ "declaredTypes": [],
8182
+ "terminalRoleGroups": [
8183
+ [
8184
+ "vin+",
8185
+ "in+",
8186
+ "pin3"
8187
+ ],
8188
+ [
8189
+ "vin-",
8190
+ "in-",
8191
+ "pin2"
8192
+ ],
8193
+ [
8194
+ "vout",
8195
+ "out",
8196
+ "pin6"
8197
+ ]
8198
+ ],
8199
+ "model": {
8200
+ "kind": "sections",
8201
+ "sections": [
8202
+ {
8203
+ "law": {
8204
+ "kind": "ideal-opamp",
8205
+ "railHigh": null,
8206
+ "railLow": null,
8207
+ "openLoopGain": 100000
8208
+ },
8209
+ "terminals": [
8210
+ 0,
8211
+ 1,
8212
+ 2
8213
+ ]
8214
+ }
8215
+ ],
8216
+ "pinout": [
8217
+ "vin+",
8218
+ "vin-",
8219
+ "vout"
8220
+ ]
8221
+ }
8222
+ },
8223
+ // SGS-THOMSON L272 dual power op-amp, from the L272 datasheet (Jan 1995):
8224
+ // ELECTRICAL CHARACTERISTICS (Vs = 24 V, Tamb = 25C): 'Gv | O. L. Voltage Gain |
8225
+ // f = 100 Hz | MIN 60 TYP 70 dB' (f = 1 kHz TYP 50 dB). Used as
8226
+ // `openLoopGain: decibelGain(70)` = 3162.2776601683795. Pinout: the sheet's PIN
8227
+ // CONNECTIONS are diagrams only (no text pin table could be extracted), so this entry
8228
+ // asserts NO pin order (neutral null pinout, M5218 precedent) and serves
8229
+ // section-grouped declarations; a packet study must verify the order before pin
8230
+ // positions are typed. One family table for L272/L272M/L272D.
8231
+ {
8232
+ "partIds": [
8233
+ "L272",
8234
+ "L272M",
8235
+ "L272D"
8236
+ ],
8237
+ "declaredTypes": [],
8238
+ "terminalRoleGroups": [],
8239
+ "model": {
8240
+ "kind": "sections",
8241
+ "sections": [
8242
+ {
8243
+ "law": {
8244
+ "kind": "ideal-opamp",
8245
+ "railHigh": null,
8246
+ "railLow": null,
8247
+ "openLoopGain": 3162.2776601683795
8248
+ },
8249
+ "terminals": [
8250
+ 2,
8251
+ 1,
8252
+ 0
8253
+ ]
8254
+ },
8255
+ {
8256
+ "law": {
8257
+ "kind": "ideal-opamp",
8258
+ "railHigh": null,
8259
+ "railLow": null,
8260
+ "openLoopGain": 3162.2776601683795
8261
+ },
8262
+ "terminals": [
8263
+ 4,
8264
+ 5,
8265
+ 6
8266
+ ]
8267
+ }
8268
+ ],
8269
+ "pinout": [
8270
+ null,
8271
+ null,
8272
+ null,
8273
+ null,
8274
+ null,
8275
+ null,
8276
+ null,
8277
+ null
8278
+ ]
8279
+ }
8280
+ },
8281
+ // TI TL074 quad JFET-input op-amp, from SLOS080W (Sep 1978, rev Jul 2025):
8282
+ // 5.8 DC (VS = +-15 V, TA = 25C, VO = 0 V): 'AOL | Open-loop voltage gain | TL07xC |
8283
+ // MIN 25 TYP 200 V/mV' (full-range row MIN-only 15; AC/BC 50/200, M 35/200, I MIN-only
8284
+ // 25 -- the I grade is not covered). Used as `openLoopGain: 200000`. TL074CN (PDIP-N)
8285
+ // and TL074CD (SOIC-D) are C-grade 'all other devices' for the 5.25 MHz GBW row.
8286
+ // Pinout: the sheet's own Table 4-6 Pin Functions TL074x (1 1OUT, 2 1IN-, 3 1IN+,
8287
+ // 4 VCC+, 5 2IN+, 6 2IN-, 7 2OUT, 8 3OUT, 9 3IN-, 10 3IN+, 11 VCC-, 12 4IN+,
8288
+ // 13 4IN-, 14 4OUT).
8289
+ {
8290
+ "partIds": [
8291
+ "TL074",
8292
+ "TL074CN",
8293
+ "TL074CD"
8294
+ ],
8295
+ "declaredTypes": [],
8296
+ "terminalRoleGroups": [
8297
+ [
8298
+ "OUT_A_pin1"
8299
+ ],
8300
+ [
8301
+ "INVERTING_A_pin2"
8302
+ ],
8303
+ [
8304
+ "NONINVERTING_A_pin3"
8305
+ ],
8306
+ [
8307
+ "V_PLUS_pin4"
8308
+ ],
8309
+ [
8310
+ "NONINVERTING_B_pin5"
8311
+ ],
8312
+ [
8313
+ "INVERTING_B_pin6"
8314
+ ],
8315
+ [
8316
+ "OUT_B_pin7"
8317
+ ],
8318
+ [
8319
+ "OUT_C_pin8"
8320
+ ],
8321
+ [
8322
+ "INVERTING_C_pin9"
8323
+ ],
8324
+ [
8325
+ "NONINVERTING_C_pin10"
8326
+ ],
8327
+ [
8328
+ "V_MINUS_pin11"
8329
+ ],
8330
+ [
8331
+ "NONINVERTING_D_pin12"
8332
+ ],
8333
+ [
8334
+ "INVERTING_D_pin13"
8335
+ ],
8336
+ [
8337
+ "OUT_D_pin14"
8338
+ ]
8339
+ ],
8340
+ "model": {
8341
+ "kind": "sections",
8342
+ "pinout": [
8343
+ "OUT_A",
8344
+ "IN_A_MINUS",
8345
+ "IN_A_PLUS",
8346
+ "VCC",
8347
+ "IN_B_PLUS",
8348
+ "IN_B_MINUS",
8349
+ "OUT_B",
8350
+ "OUT_C",
8351
+ "IN_C_MINUS",
8352
+ "IN_C_PLUS",
8353
+ "VEE",
8354
+ "IN_D_PLUS",
8355
+ "IN_D_MINUS",
8356
+ "OUT_D"
8357
+ ],
8358
+ "sections": [
8359
+ {
8360
+ "law": {
8361
+ "kind": "ideal-opamp",
8362
+ "railHigh": null,
8363
+ "railLow": null,
8364
+ "openLoopGain": 200000
8365
+ },
8366
+ "terminals": [
8367
+ 2,
8368
+ 1,
8369
+ 0
8370
+ ]
8371
+ },
8372
+ {
8373
+ "law": {
8374
+ "kind": "ideal-opamp",
8375
+ "railHigh": null,
8376
+ "railLow": null,
8377
+ "openLoopGain": 200000
8378
+ },
8379
+ "terminals": [
8380
+ 4,
8381
+ 5,
8382
+ 6
8383
+ ]
8384
+ },
8385
+ {
8386
+ "law": {
8387
+ "kind": "ideal-opamp",
8388
+ "railHigh": null,
8389
+ "railLow": null,
8390
+ "openLoopGain": 200000
8391
+ },
8392
+ "terminals": [
8393
+ 9,
8394
+ 8,
8395
+ 7
8396
+ ]
8397
+ },
8398
+ {
8399
+ "law": {
8400
+ "kind": "ideal-opamp",
8401
+ "railHigh": null,
8402
+ "railLow": null,
8403
+ "openLoopGain": 200000
8404
+ },
8405
+ "terminals": [
8406
+ 11,
8407
+ 12,
8408
+ 13
8409
+ ]
8410
+ }
8411
+ ]
8412
+ }
8413
+ },
8414
+ // Wave 2 (JED): TI CD4016B quad bilateral switch. Its own entry (NOT an alias of the
8415
+ // CD4066 entry above): the 4016 is the high-Ron predecessor and the 4066 its
8416
+ // pin-compatible low-Ron replacement (onsemi MC14066B: 'designed to be pin-for-pin
8417
+ // compatible with the MC14016B, but has much lower ON resistance'; ST HCF4066B: 'pin
8418
+ // for pin compatible with HCF4016B, but exhibits a much lower ON resistance').
8419
+ // On-resistance from TI CD4016B SCHS026D Rev. E (Aug 2024), Features p1: '280 Ohm
8420
+ // typical on-state resistance for 15 V operation' (switch matching 'within 10 Ohm typ
8421
+ // over 15 V signal-input range'). The 4.5 table's at-rail TYP at 15 V is 200 Ohm
8422
+ // (MAX 400; mid-supply worst-case MAX-only 850) -- the 280 headline is the over-range
8423
+ // figure and is the transcribed value. offOhms/thresholdVolts repeat the 4066 entry
8424
+ // (not newly cited). Only the ids the TI sheet owns: the unbuffered CD4016, HEF/HCF/MC
8425
+ // variants are not covered (Nexperia's HEF4016B specs a different test: 115 Ohm
8426
+ // peak-typ at 15 V, 100 uA, sharing no figure with this row).
8427
+ {
8428
+ "partIds": [
8429
+ "4016B",
8430
+ "CD4016B"
8431
+ ],
8432
+ "declaredTypes": [],
8433
+ "terminalRoleGroups": [
8434
+ [
8435
+ "switch_a",
8436
+ "in",
8437
+ "a",
8438
+ "sig_in",
8439
+ "inout1",
8440
+ "1in/out",
8441
+ "in/out"
8442
+ ],
8443
+ [
8444
+ "switch_b",
8445
+ "out",
8446
+ "b",
8447
+ "sig_out",
8448
+ "inout2",
8449
+ "1out/in",
8450
+ "out/in"
8451
+ ],
8452
+ [
8453
+ "control",
8454
+ "ctrl",
8455
+ "c",
8456
+ "gate",
8457
+ "ctl",
8458
+ "1control"
8459
+ ],
8460
+ [
8461
+ "vcc",
8462
+ "vdd",
8463
+ "vplus",
8464
+ "v+"
8465
+ ],
8466
+ [
8467
+ "vss",
8468
+ "gnd",
8469
+ "vminus",
8470
+ "v-",
8471
+ "ground"
8472
+ ]
8473
+ ],
8474
+ "model": {
8475
+ "kind": "sections",
8476
+ "sections": [
8477
+ {
8478
+ "law": {
8479
+ "kind": "analog-switch",
8480
+ "onOhms": 280,
8481
+ "offOhms": 1000000000,
8482
+ "thresholdVolts": 2
8483
+ },
8484
+ "terminals": [
8485
+ 0,
8486
+ 1,
8487
+ 2
8488
+ ]
8489
+ }
8490
+ ],
8491
+ "pinout": [
8492
+ "switch_a",
8493
+ "switch_b",
8494
+ "control",
8495
+ "vcc",
8496
+ "vss"
8497
+ ]
8498
+ }
8499
+ },
8500
+ // Wave 2 (JED): Cabintech CT3101 BBD clock generator. Its own entry mirroring the
8501
+ // MN3101 above (same 8-pin DIL footprint, same terminal order), NOT an unsourced alias:
8502
+ // the CT3101's own datasheet ('CT3101 BBD Clock Generator - Datasheet', Rev 1,
8503
+ // Aug 2023, Cabintech Global LLC, https://cabintechglobal.com/pdf/CABINTECH_CT3101.pdf)
8504
+ // states it 'is pin-for-pin compatible with the discontinued Panasonic MN3101' and 'can
8505
+ // be used as a drop-in replacement for the MN3101', with the pin table 1 GND, 2 CP1,
8506
+ // 3 Vdd (-3 V to -18 V), 4 CP2, 5 OX3, 6 OX2, 7 OX1, 8 Vgg (14/15 Vdd output).
8507
+ // No quiescentSupplyAmps: the sheet says only 'about 1/2 of MN3101' (no figure).
8508
+ // It is a hybrid module (CD4047-based), not a monolithic second source.
8509
+ {
8510
+ "partIds": [
8511
+ "CT3101"
8512
+ ],
8513
+ "declaredTypes": [],
8514
+ "terminalRoleGroups": [
8515
+ [
8516
+ "gnd",
8517
+ "v-",
8518
+ "vss",
8519
+ "pin1",
8520
+ "gndpin1"
8521
+ ],
8522
+ [
8523
+ "cp1",
8524
+ "clk1",
8525
+ "clock1",
8526
+ "phi1",
8527
+ "pin2",
8528
+ "cp1pin2"
8529
+ ],
8530
+ [
8531
+ "vdd",
8532
+ "v+",
8533
+ "vcc",
8534
+ "pin3",
8535
+ "vddpin3"
8536
+ ],
8537
+ [
8538
+ "cp2",
8539
+ "clk2",
8540
+ "clock2",
8541
+ "phi2",
8542
+ "pin4",
8543
+ "cp2pin4"
8544
+ ],
8545
+ [
8546
+ "ox3",
8547
+ "osc3",
8548
+ "oscctrl",
8549
+ "pin5",
8550
+ "ox3pin5"
8551
+ ],
8552
+ [
8553
+ "ox2",
8554
+ "osc2",
8555
+ "oscout",
8556
+ "pin6",
8557
+ "ox2pin6"
8558
+ ],
8559
+ [
8560
+ "ox1",
8561
+ "osc1",
8562
+ "oscin",
8563
+ "osc",
8564
+ "pin7",
8565
+ "ox1pin7"
8566
+ ],
8567
+ [
8568
+ "vgg",
8569
+ "vggout",
8570
+ "pin8",
8571
+ "vggoutpin8"
8572
+ ]
8573
+ ],
8574
+ "model": {
8575
+ "kind": "sections",
8576
+ "sections": [
8577
+ {
8578
+ "law": {
8579
+ "kind": "clock-driver",
8580
+ "defaultFrequency": 10000
8581
+ },
8582
+ "terminals": [
8583
+ 1,
8584
+ 3,
8585
+ 7,
8586
+ 2,
8587
+ 6,
8588
+ 0
8589
+ ]
8590
+ }
8591
+ ],
8592
+ "pinout": [
8593
+ null,
8594
+ null,
8595
+ null,
8596
+ null,
8597
+ null,
8598
+ null,
8599
+ null,
8600
+ null
8601
+ ]
8602
+ }
8603
+ },
8604
+ ...zenerRegistryRecords.map((record) => zenerLaw(record)),
8605
+ // Wave-1 discrete fill: bjt, diode-forward, mosfet and jfet-channel records become
8606
+ // law entries through the builders above -- one list per class, so the entries cannot
8607
+ // drift from the records. 2SK30A rides the same generic path as every other JFET: its
8608
+ // record carries only gateSaturationCurrent, so the builder gap-fills the class channel
8609
+ // and the stamp is byte-identical to the bespoke mapping this replaces.
8610
+ ...bjtRegistryRecords.map((record) => bjtLawEntry(record)),
8611
+ ...diodeRegistryRecords.map((record) => diodeLawEntry(record)),
8612
+ ...mosfetRegistryRecords.map((record) => fetLawEntry(record, "mosfet")),
8613
+ ...jfetRegistryRecords.map((record) => fetLawEntry(record, "jfet")),
8614
+ ...ujtRegistryRecords.map((record) => ujtLawEntry(record)),
8615
+ ];
8616
+ // --- Op-amp gain-bandwidth product: stamped from the data registry ------------------------------
8617
+ /**
8618
+ * Merge a stamped row's `community`-basis parameters into the entry's provisional arm.
8619
+ * A row whose parameters are all better-sourced adds nothing, and an entry with no
8620
+ * provisional value keeps the field absent rather than gaining an empty one.
8621
+ */
8622
+ function withRowProvisional(entry, row) {
8623
+ const merged = [...(entry.provisionalParams ?? [])];
8624
+ row.params.forEach((param, index) => {
8625
+ if (param?.basis !== "community" || typeof param.url !== "string")
8626
+ return;
8627
+ const name = row.names[index];
8628
+ if (!merged.some((held) => held.name === name)) {
8629
+ merged.push({ name, url: param.url });
8630
+ }
8631
+ });
8632
+ return merged.length === 0 ? {} : { provisionalParams: merged };
8633
+ }
8634
+ //
8635
+ // The figures live in `registry-data/opamp.ts` as cited values (datasheet TYP rows, quoted).
8636
+ // This pass stamps each record's product onto the op-amp sections of exactly the ids it
8637
+ // covers. An entry whose ids have different products (or none) is split into one entry per
8638
+ // product, keeping every other field, so no id changes behaviour except by gaining its own
8639
+ // figure.
8640
+ //
8641
+ // Loud on any mismatch, as before: a record or absent id the catalog holds no op-amp entry
8642
+ // for, an id in both lists, or an id in two records is a table error and throws at load
8643
+ // rather than being silently skipped.
8644
+ function withDatasheetGainBandwidth(base) {
6855
8645
  const rowOf = new Map();
6856
- for (const row of opampGainBandwidthDatasheet) {
8646
+ for (const row of opampRegistryRecords) {
8647
+ if (row.gainBandwidthHz === undefined)
8648
+ continue;
6857
8649
  for (const id of row.partIds) {
6858
8650
  const folded = foldPartId(id);
6859
8651
  if (rowOf.has(folded))
@@ -6896,13 +8688,21 @@ function withDatasheetGainBandwidth(base) {
6896
8688
  out.push(groups.size === 1 ? entry : { ...entry, partIds });
6897
8689
  continue;
6898
8690
  }
8691
+ const hertz = row.gainBandwidthHz?.value;
8692
+ if (hertz === undefined || !(hertz > 0)) {
8693
+ throw new Error(`${partIds.join(",")}: gain-bandwidth record states no positive product`);
8694
+ }
6899
8695
  out.push({
6900
8696
  ...entry,
6901
8697
  partIds,
8698
+ ...withRowProvisional(entry, {
8699
+ params: [row.gainBandwidthHz],
8700
+ names: ["gainBandwidthHz"],
8701
+ }),
6902
8702
  model: {
6903
8703
  ...model,
6904
8704
  sections: model.sections.map((section) => section.law.kind === "ideal-opamp"
6905
- ? { ...section, law: { ...section.law, gainBandwidthHz: row.hertz } }
8705
+ ? { ...section, law: { ...section.law, gainBandwidthHz: hertz } }
6906
8706
  : section),
6907
8707
  },
6908
8708
  });
@@ -6914,18 +8714,160 @@ function withDatasheetGainBandwidth(base) {
6914
8714
  }
6915
8715
  return out;
6916
8716
  }
8717
+ // --- Op-amp output swing: stamped from the data registry -------------------------------------------------
8718
+ //
8719
+ // The same rules as the gain-bandwidth pass above. A record states the drop from each rail at
8720
+ // the printed condition; `translation` is how that pair is carried to a pedal on another supply:
8721
+ // "constant" the printed drops are applied as printed (a headroom independent of the supply);
8722
+ // "proportional" both are multiplied by the op-amp's supply span over `supplyVolts`.
8723
+ // The datasheet states neither: it is the owner's reading, recorded per record, and the table does not
8724
+ // say it is shown right. Only the OP07 reading has a held-out hardware test behind it (T-a1, row 16),
8725
+ // and a test of a rule written first is not a measurement of the rule.
8726
+ /**
8727
+ * The catalog's op-amp entries with each registry record's swing pair stamped onto the sections
8728
+ * of exactly the ids it covers. An entry whose ids have different rows (or none) is split into
8729
+ * one entry per row, keeping every other field, so no id changes behaviour except by gaining
8730
+ * its own pair.
8731
+ *
8732
+ * Loud on any mismatch, as `withDatasheetGainBandwidth` is: a record or absent id the catalog
8733
+ * holds no op-amp entry for, an id in both lists, an id in two records, a base entry that
8734
+ * already carries a swing field (every figure comes through the registry), or a record whose
8735
+ * numbers do not validate (`opampOutputSwingOf`) throws at load.
8736
+ */
8737
+ export function withDatasheetOutputSwing(base, rows = opampRegistryRecords, absentRows = opampOutputSwingAbsent) {
8738
+ const rowOf = new Map();
8739
+ for (const row of rows) {
8740
+ const high = row.outputSwingDropHighVolts;
8741
+ const low = row.outputSwingDropLowVolts;
8742
+ const printed = row.outputSwingPrinted;
8743
+ if (high === undefined && low === undefined && printed === undefined)
8744
+ continue;
8745
+ if (high === undefined || low === undefined || printed === undefined) {
8746
+ throw new Error(`${row.partIds.join(",")}: an op-amp swing record states a half pair (high ${high === undefined ? "absent" : "present"}, low ${low === undefined ? "absent" : "present"}, printed ${printed === undefined ? "absent" : "present"})`);
8747
+ }
8748
+ for (const id of row.partIds) {
8749
+ const folded = foldPartId(id);
8750
+ if (rowOf.has(folded))
8751
+ throw new Error(`output-swing table names ${id} twice`);
8752
+ rowOf.set(folded, row);
8753
+ }
8754
+ }
8755
+ const absent = new Set();
8756
+ for (const { partIds, reason } of absentRows) {
8757
+ if (reason.length === 0)
8758
+ throw new Error(`output-swing absent row for ${partIds.join(",")} states no reason`);
8759
+ for (const id of partIds) {
8760
+ const folded = foldPartId(id);
8761
+ if (rowOf.has(folded))
8762
+ throw new Error(`${id} is both given an output-swing pair and listed absent`);
8763
+ absent.add(folded);
8764
+ }
8765
+ }
8766
+ const seen = new Set();
8767
+ const out = [];
8768
+ for (const entry of base) {
8769
+ const sections = entry.model.kind === "sections" ? entry.model.sections : null;
8770
+ const isOpamp = sections !== null && sections.length > 0 && sections.every((section) => section.law.kind === "ideal-opamp");
8771
+ if (!isOpamp || sections === null || entry.model.kind !== "sections") {
8772
+ out.push(entry);
8773
+ continue;
8774
+ }
8775
+ for (const section of sections) {
8776
+ const law = section.law;
8777
+ if (law.kind === "ideal-opamp" &&
8778
+ (law.outputSwingDropHighVolts !== undefined ||
8779
+ law.outputSwingDropLowVolts !== undefined ||
8780
+ law.outputSwingPrinted !== undefined)) {
8781
+ throw new Error(`${entry.partIds.join(",")}: an op-amp entry states an output swing in place; it must come through the registry`);
8782
+ }
8783
+ }
8784
+ const groups = new Map();
8785
+ for (const id of entry.partIds) {
8786
+ const folded = foldPartId(id);
8787
+ seen.add(folded);
8788
+ const row = rowOf.get(folded) ?? null;
8789
+ const group = groups.get(row);
8790
+ if (group === undefined)
8791
+ groups.set(row, [id]);
8792
+ else
8793
+ group.push(id);
8794
+ }
8795
+ const model = entry.model;
8796
+ for (const [row, partIds] of groups) {
8797
+ if (row === null) {
8798
+ out.push(groups.size === 1 ? entry : { ...entry, partIds });
8799
+ continue;
8800
+ }
8801
+ const high = row.outputSwingDropHighVolts?.value;
8802
+ const low = row.outputSwingDropLowVolts?.value;
8803
+ const printed = row.outputSwingPrinted;
8804
+ if (high === undefined || low === undefined || printed === undefined) {
8805
+ throw new Error(`${partIds.join(",")}: an op-amp swing record states a half pair`);
8806
+ }
8807
+ const stamped = {
8808
+ ...entry,
8809
+ partIds,
8810
+ ...withRowProvisional(entry, {
8811
+ params: [row.outputSwingDropHighVolts, row.outputSwingDropLowVolts],
8812
+ names: ["outputSwingDropHighVolts", "outputSwingDropLowVolts"],
8813
+ }),
8814
+ model: {
8815
+ ...model,
8816
+ sections: model.sections.map((section) => section.law.kind === "ideal-opamp"
8817
+ ? {
8818
+ ...section,
8819
+ law: {
8820
+ ...section.law,
8821
+ outputSwingDropHighVolts: high,
8822
+ outputSwingDropLowVolts: low,
8823
+ outputSwingPrinted: {
8824
+ supplyVolts: printed.supplyVolts,
8825
+ loadOhms: printed.loadOhms,
8826
+ translation: printed.translation,
8827
+ },
8828
+ },
8829
+ }
8830
+ : section),
8831
+ },
8832
+ };
8833
+ // A malformed record (a non-positive figure, an unknown translation) throws here, naming the ids.
8834
+ for (const section of stamped.model.kind === "sections" ? stamped.model.sections : []) {
8835
+ if (section.law.kind === "ideal-opamp")
8836
+ opampOutputSwingOf(section.law, partIds.join(","));
8837
+ }
8838
+ out.push(stamped);
8839
+ }
8840
+ }
8841
+ for (const folded of [...rowOf.keys(), ...absent]) {
8842
+ if (!seen.has(folded))
8843
+ throw new Error(`output-swing table names ${folded}, which no op-amp entry holds`);
8844
+ }
8845
+ return out;
8846
+ }
6917
8847
  /**
6918
8848
  * Every part id whose `fet` entry refines the gate term only, folded for comparison.
6919
- * `report-part-store-drift.ts` keys its channel-field exclusion on this set -- a catalog
6920
- * product -- rather than on the registry row, so a gate-only entry's avowed class channel
6921
- * is never graded against the database's beta/lambda/Vp the way a transcribed channel
6922
- * would be. Mirrors `zenerCatalogPartIds` above.
8849
+ * Derived from the jfet registry records that state NO channel parameter (a record that
8850
+ * states thresholdVolts or transconductance carries a full channel and is not gate-only):
8851
+ * such an entry keeps its avowed class channel, which is never graded against the database's
8852
+ * beta/lambda/Vp the way a transcribed channel would be. Mirrors `zenerCatalogPartIds`.
6923
8853
  */
6924
- export const gateOnlyFetPartIds = new Set(["2sk30a"]);
8854
+ const GATE_ONLY_EXCLUDED_PARAMS = new Set([
8855
+ "thresholdVolts",
8856
+ "transconductance",
8857
+ "channelLengthModulation",
8858
+ "subthresholdVolts",
8859
+ ]);
8860
+ export const gateOnlyFetPartIds = new Set(jfetRegistryRecords
8861
+ .filter((record) => Object.keys(record.params).every((name) => !GATE_ONLY_EXCLUDED_PARAMS.has(name)))
8862
+ .flatMap((record) => [
8863
+ ...record.partIds,
8864
+ ...(record.aliases ?? []),
8865
+ ])
8866
+ .map((id) => foldPartId(id)));
6925
8867
  /**
6926
8868
  * The catalog the corpus-facing scripts inject.
6927
8869
  */
6928
8870
  export const pedalPartCatalog = {
6929
- entries: withDatasheetGainBandwidth(entries),
8871
+ entries: withDatasheetOutputSwing(withDatasheetGainBandwidth(entries)),
6930
8872
  };
6931
8873
  //# sourceMappingURL=part-catalog.js.map