@vessel-dsp/compiler 0.5.0 → 0.6.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 (77) hide show
  1. package/README.md +4 -2
  2. package/dist/bbd-clock.d.ts.map +1 -1
  3. package/dist/bbd-clock.js +5 -0
  4. package/dist/bbd-clock.js.map +1 -1
  5. package/dist/compile.d.ts.map +1 -1
  6. package/dist/compile.js +261 -12
  7. package/dist/compile.js.map +1 -1
  8. package/dist/device-laws.d.ts.map +1 -1
  9. package/dist/device-laws.js +541 -97
  10. package/dist/device-laws.js.map +1 -1
  11. package/dist/fixtures/circuits.d.ts +23 -2
  12. package/dist/fixtures/circuits.d.ts.map +1 -1
  13. package/dist/fixtures/circuits.js +205 -2
  14. package/dist/fixtures/circuits.js.map +1 -1
  15. package/dist/index.d.ts +2 -1
  16. package/dist/index.d.ts.map +1 -1
  17. package/dist/index.js +1 -0
  18. package/dist/index.js.map +1 -1
  19. package/dist/netlist.d.ts +30 -0
  20. package/dist/netlist.d.ts.map +1 -1
  21. package/dist/netlist.js +488 -8
  22. package/dist/netlist.js.map +1 -1
  23. package/dist/part-catalog.d.ts +35 -18
  24. package/dist/part-catalog.d.ts.map +1 -1
  25. package/dist/part-catalog.js +2474 -592
  26. package/dist/part-catalog.js.map +1 -1
  27. package/dist/registry-data/bjt.d.ts +3 -0
  28. package/dist/registry-data/bjt.d.ts.map +1 -0
  29. package/dist/registry-data/bjt.js +1350 -0
  30. package/dist/registry-data/bjt.js.map +1 -0
  31. package/dist/registry-data/charge-pump.d.ts +3 -0
  32. package/dist/registry-data/charge-pump.d.ts.map +1 -0
  33. package/dist/registry-data/charge-pump.js +204 -0
  34. package/dist/registry-data/charge-pump.js.map +1 -0
  35. package/dist/registry-data/diode.d.ts +3 -0
  36. package/dist/registry-data/diode.d.ts.map +1 -0
  37. package/dist/registry-data/diode.js +683 -0
  38. package/dist/registry-data/diode.js.map +1 -0
  39. package/dist/registry-data/index.d.ts +20 -0
  40. package/dist/registry-data/index.d.ts.map +1 -0
  41. package/dist/registry-data/index.js +100 -0
  42. package/dist/registry-data/index.js.map +1 -0
  43. package/dist/registry-data/jfet.d.ts +3 -0
  44. package/dist/registry-data/jfet.d.ts.map +1 -0
  45. package/dist/registry-data/jfet.js +605 -0
  46. package/dist/registry-data/jfet.js.map +1 -0
  47. package/dist/registry-data/loader.d.ts +137 -0
  48. package/dist/registry-data/loader.d.ts.map +1 -0
  49. package/dist/registry-data/loader.js +350 -0
  50. package/dist/registry-data/loader.js.map +1 -0
  51. package/dist/registry-data/mosfet.d.ts +3 -0
  52. package/dist/registry-data/mosfet.d.ts.map +1 -0
  53. package/dist/registry-data/mosfet.js +63 -0
  54. package/dist/registry-data/mosfet.js.map +1 -0
  55. package/dist/registry-data/opamp.d.ts +37 -0
  56. package/dist/registry-data/opamp.d.ts.map +1 -0
  57. package/dist/registry-data/opamp.js +669 -0
  58. package/dist/registry-data/opamp.js.map +1 -0
  59. package/dist/registry-data/regulator.d.ts +3 -0
  60. package/dist/registry-data/regulator.d.ts.map +1 -0
  61. package/dist/registry-data/regulator.js +189 -0
  62. package/dist/registry-data/regulator.js.map +1 -0
  63. package/dist/registry-data/schema.d.ts +56 -0
  64. package/dist/registry-data/schema.d.ts.map +1 -0
  65. package/dist/registry-data/schema.js +142 -0
  66. package/dist/registry-data/schema.js.map +1 -0
  67. package/dist/registry-data/zener.d.ts +3 -0
  68. package/dist/registry-data/zener.d.ts.map +1 -0
  69. package/dist/registry-data/zener.js +547 -0
  70. package/dist/registry-data/zener.js.map +1 -0
  71. package/dist/registry.d.ts +61 -6
  72. package/dist/registry.d.ts.map +1 -1
  73. package/dist/registry.js +35 -7
  74. package/dist/registry.js.map +1 -1
  75. package/dist/types.d.ts +209 -19
  76. package/dist/types.d.ts.map +1 -1
  77. 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, 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,430 @@ 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 entry's id list: canonical ids plus typed alias spellings, in listing order. */
169
+ function entryIdsOf(record) {
170
+ return [...record.partIds, ...(record.aliases ?? [])];
171
+ }
172
+ /** A bjt record's law entry. Throws when the record is not class `bjt` or states nothing. */
173
+ export function bjtLawEntry(record) {
174
+ if (record.class !== "bjt") {
175
+ throw new Error(`${record.partIds.join(",")}: a bjt entry needs class "bjt", not "${record.class}"`);
176
+ }
177
+ if (Object.keys(record.params).length === 0) {
178
+ throw new Error(`${record.partIds.join(",")}: a bjt record states no parameters`);
179
+ }
157
180
  return {
158
- partIds,
181
+ partIds: entryIdsOf(record),
159
182
  declaredTypes: [],
160
183
  terminalRoleGroups: [],
161
- model: { kind: "law", law },
184
+ ...provisionalArmOf(record),
185
+ model: {
186
+ kind: "law",
187
+ law: {
188
+ kind: "bjt",
189
+ polarity: "npn",
190
+ saturationCurrent: citedValueOf(record, "saturationCurrent") ?? 1e-14,
191
+ forwardBeta: citedValueOf(record, "forwardBeta") ?? 100,
192
+ reverseBeta: citedValueOf(record, "reverseBeta") ?? 1,
193
+ thermalVoltage: citedValueOf(record, "thermalVoltage") ?? 0.025_852,
194
+ leakageAmps: citedValueOf(record, "leakageAmps") ?? 0,
195
+ },
196
+ },
197
+ };
198
+ }
199
+ /**
200
+ * A signal-diode record's law entry. Requires the forward pair together: the only
201
+ * derivation the registry admits (two printed (If, Vf) points) yields IS and n as one
202
+ * working, so a lone IS without n is a broken derivation and throws rather than shipping
203
+ * half a knee. Gap-filled breakdownVolts 0 keeps the no-knee reading the unregistered path
204
+ * stamps (a zener knee belongs to class `zener` and the existing `zenerLaw` path, not here).
205
+ */
206
+ export function diodeLawEntry(record) {
207
+ if (record.class !== "diode") {
208
+ throw new Error(`${record.partIds.join(",")}: a diode entry needs class "diode", not "${record.class}"`);
209
+ }
210
+ const saturationCurrent = citedValueOf(record, "saturationCurrent");
211
+ const emissionCoefficient = citedValueOf(record, "emissionCoefficient");
212
+ if (saturationCurrent === undefined || emissionCoefficient === undefined) {
213
+ throw new Error(`${record.partIds.join(",")}: a diode record states no forward pair ` +
214
+ `(saturationCurrent ${saturationCurrent === undefined ? "absent" : "present"}, ` +
215
+ `emissionCoefficient ${emissionCoefficient === undefined ? "absent" : "present"})`);
216
+ }
217
+ return {
218
+ partIds: entryIdsOf(record),
219
+ declaredTypes: [],
220
+ terminalRoleGroups: [],
221
+ ...provisionalArmOf(record),
222
+ model: {
223
+ kind: "law",
224
+ law: {
225
+ kind: "diode",
226
+ saturationCurrent,
227
+ emissionCoefficient,
228
+ thermalVoltage: citedValueOf(record, "thermalVoltage") ?? 0.025_852,
229
+ breakdownVolts: citedValueOf(record, "breakdownVolts") ?? 0,
230
+ seriesResistance: citedValueOf(record, "seriesResistance") ?? 1.0,
231
+ isLed: false,
232
+ },
233
+ },
162
234
  };
163
235
  }
164
- // --- Zener diodes: reverse breakdown from the device database ------------------------------------
236
+ /**
237
+ * A JFET- or MOSFET-channel record's law entry. The class selects the gap-fill constants
238
+ * (JFET depletion defaults with the silicon gate; MOSFET enhancement defaults with the
239
+ * insulated gate), so a JFET record can never stamp MOSFET gate zeros and the reverse.
240
+ * Throws on any other class and on an empty record.
241
+ */
242
+ export function fetLawEntry(record, cls) {
243
+ if (record.class !== cls) {
244
+ throw new Error(`${record.partIds.join(",")}: a ${cls} entry needs class "${cls}", not "${record.class}"`);
245
+ }
246
+ if (Object.keys(record.params).length === 0) {
247
+ throw new Error(`${record.partIds.join(",")}: a ${cls} record states no parameters`);
248
+ }
249
+ const channelDefaults = cls === "jfet"
250
+ ? {
251
+ thresholdVolts: -2,
252
+ transconductance: 1e-3,
253
+ channelLengthModulation: 0,
254
+ subthresholdVolts: 0.07,
255
+ gateSaturationCurrent: 1e-5,
256
+ gateOnsetVolts: 0.5,
257
+ gateScaleVolts: 0.06,
258
+ }
259
+ : {
260
+ thresholdVolts: 2,
261
+ transconductance: 1e-3,
262
+ channelLengthModulation: 0,
263
+ subthresholdVolts: 0,
264
+ gateSaturationCurrent: 0,
265
+ gateOnsetVolts: 0,
266
+ gateScaleVolts: 1,
267
+ };
268
+ return {
269
+ partIds: entryIdsOf(record),
270
+ declaredTypes: [],
271
+ terminalRoleGroups: [],
272
+ ...provisionalArmOf(record),
273
+ model: {
274
+ kind: "law",
275
+ law: {
276
+ kind: "fet",
277
+ channel: "n",
278
+ thresholdVolts: citedValueOf(record, "thresholdVolts") ??
279
+ channelDefaults.thresholdVolts,
280
+ transconductance: citedValueOf(record, "transconductance") ??
281
+ channelDefaults.transconductance,
282
+ channelLengthModulation: citedValueOf(record, "channelLengthModulation") ??
283
+ channelDefaults.channelLengthModulation,
284
+ subthresholdVolts: citedValueOf(record, "subthresholdVolts") ??
285
+ channelDefaults.subthresholdVolts,
286
+ gateSaturationCurrent: citedValueOf(record, "gateSaturationCurrent") ??
287
+ channelDefaults.gateSaturationCurrent,
288
+ gateOnsetVolts: citedValueOf(record, "gateOnsetVolts") ??
289
+ channelDefaults.gateOnsetVolts,
290
+ gateScaleVolts: citedValueOf(record, "gateScaleVolts") ??
291
+ channelDefaults.gateScaleVolts,
292
+ },
293
+ },
294
+ };
295
+ }
296
+ // --- JFET channel records: built by `fetLawEntry` (wave-1 builders above) -------------------
297
+ //
298
+ // The JFET class default is avowedly no part's values; see `registry-data/jfet.ts` for the
299
+ // measured gate-defect note, the rank argument for keeping the 2SK30A class channel, and the
300
+ // provenance. The generic builder maps each record's present parameters and gap-fills the
301
+ // class channel, so 2SK30A (gate term only) stamps byte-identically to before while full
302
+ // channels (2N5457) stamp their cited values.
303
+ // --- Zener diodes: reverse breakdown from the data registry -------------------------------------
165
304
  //
166
305
  // `breakdownVolts` only; forward behaviour repeats the class default by construction (see
167
306
  // `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
307
+ // overlays the declared value on a catalog match, so the corpus diodes that already
169
308
  // declare one (e.g. `boss-ph-1r` D1 11 V against the registry's 10.99) render unchanged.
309
+ // The rows live in `registry-data/zener.ts` as cited values; the entries array below maps
310
+ // them through `zenerLaw`, so the membership set below and the shipped entries cannot
311
+ // drift apart: there is one list, not two.
312
+ /** Read a zener record's cited breakdown, loudly. The loader guarantees the citation. */
313
+ function zenerBreakdownVoltsOf(record) {
314
+ const breakdown = record.params["breakdownVolts"];
315
+ if (breakdown === undefined || !Number.isFinite(breakdown.value)) {
316
+ throw new Error(`${record.partIds.join(",")}: a zener record states no breakdownVolts`);
317
+ }
318
+ return breakdown.value;
319
+ }
320
+ export const zenerCatalogPartIds = new Set(zenerRegistryRecords.flatMap((record) => record.partIds.map((id) => foldPartId(id))));
321
+ // --- Fixed-rail regulators: wiring skeletons over registry-data voltages ---------------------------
322
+ // The rail numbers live in `registry-data/regulator.ts` as cited values; this table holds only
323
+ // the terminal wiring (role groups, section terminals) each rail's entry shares. Keyed by the
324
+ // rail volts the record states, so a record always lands on its own wiring and a skeleton row
325
+ // no record consumes throws at load (see the check below). `pinout` is `[null, null, null]`
326
+ // for all of them: no regulator entry asserts a pin order.
327
+ const regulatorTerminalStructures = {
328
+ // 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.
329
+ // 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.
330
+ 5: {
331
+ terminalRoleGroups: [["vin", "in", "input", "pin1"], ["gnd", "pin2"], ["vout", "out", "output", "pin3"]],
332
+ terminals: [2, 1],
333
+ },
334
+ 9: {
335
+ terminalRoleGroups: [["vin", "in", "input", "pin1"], ["gnd", "pin2"], ["vout", "out", "output", "pin3"]],
336
+ terminals: [2, 1],
337
+ },
338
+ // Wave 5: 12 V and 3.3 V 78Lxx rails (LM78L12/LM78L33, TI SNVS754O). Same wiring
339
+ // shape as the 5 V/9 V entries, and the same latent TO-92-vs-TO-220 pin-order
340
+ // caveat as the 5 V entry above: no corpus packet names these rails today, so the
341
+ // order is asserted for parity, not verified against a packet.
342
+ 12: {
343
+ terminalRoleGroups: [["vin", "in", "input", "pin1"], ["gnd", "pin2"], ["vout", "out", "output", "pin3"]],
344
+ terminals: [2, 1],
345
+ },
346
+ 3.3: {
347
+ terminalRoleGroups: [["vin", "in", "input", "pin1"], ["gnd", "pin2"], ["vout", "out", "output", "pin3"]],
348
+ terminals: [2, 1],
349
+ },
350
+ // 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.
351
+ 18: {
352
+ terminalRoleGroups: [["vin", "in", "input", "pin1"], ["gnd", "ground", "pin2"], ["vout", "out", "output", "pin3"]],
353
+ terminals: [2, 1],
354
+ },
355
+ // `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.
356
+ [-15]: {
357
+ terminalRoleGroups: [["gnd", "pin1"], ["vin", "in", "input", "pin2"], ["vout", "out", "output", "pin3"]],
358
+ terminals: [2, 0],
359
+ },
360
+ // `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.
361
+ 15: {
362
+ terminalRoleGroups: [],
363
+ terminals: [2, 1],
364
+ },
365
+ };
366
+ /** Build one regulator entry from its registry record and the wiring skeleton for its rail. */
367
+ function regulatorEntry(record) {
368
+ const volts = record.params["volts"];
369
+ const sourceOhms = record.params["sourceOhms"];
370
+ if (volts === undefined || sourceOhms === undefined) {
371
+ throw new Error(`${record.partIds.join(",")}: a regulator record states no volts or sourceOhms`);
372
+ }
373
+ const structure = regulatorTerminalStructures[volts.value];
374
+ if (structure === undefined) {
375
+ throw new Error(`${record.partIds.join(",")}: no regulator wiring skeleton for ${String(volts.value)} V`);
376
+ }
377
+ return {
378
+ partIds: [...record.partIds],
379
+ declaredTypes: [],
380
+ terminalRoleGroups: structure.terminalRoleGroups,
381
+ ...provisionalArmOf(record),
382
+ model: {
383
+ kind: "sections",
384
+ sections: [
385
+ {
386
+ law: {
387
+ kind: "voltage-source",
388
+ volts: volts.value,
389
+ sourceOhms: sourceOhms.value,
390
+ },
391
+ terminals: [...structure.terminals],
392
+ },
393
+ ],
394
+ pinout: [null, null, null],
395
+ },
396
+ };
397
+ }
398
+ /** The regulator entry for one rail, by the volts its registry record states. Position-preserving. */
399
+ function regulatorEntryForVolts(volts) {
400
+ const record = regulatorRegistryRecords.find((candidate) => candidate.params["volts"]?.value === volts);
401
+ if (record === undefined) {
402
+ throw new Error(`no regulator registry record states ${String(volts)} V`);
403
+ }
404
+ return regulatorEntry(record);
405
+ }
406
+ // Every skeleton row is consumed by exactly one record, and every record lands on a row:
407
+ // a rail added to one side without the other throws here rather than silently defaulting.
408
+ for (const key of Object.keys(regulatorTerminalStructures)) {
409
+ if (!regulatorRegistryRecords.some((record) => record.params["volts"]?.value === Number(key))) {
410
+ throw new Error(`regulator wiring skeleton for ${key} V is consumed by no registry record`);
411
+ }
412
+ }
413
+ // --- Charge pumps: wiring skeletons over registry-data loss/slope ---------------------------------
414
+ // The two numbers the derivation reads (`sourceOhms`, `chargePumpLossVolts`) live in
415
+ // `registry-data/charge-pump.ts` as cited values; this table holds only the terminal wiring
416
+ // (role groups, package-order pinout) each pump's entry shares. The 8-pin parts are NOT
417
+ // pin-identical: pin 1 is BOOST (TC1044S, ICL7660S, TC7662B, MAX1044), NC (ICL7660, ICL7660A),
418
+ // FB/SD (LT1054) or SHDN (LTC1144). Keyed by the record's first part id, so a record always
419
+ // lands on its own wiring; a record with no skeleton row, or a skeleton row no record
420
+ // consumes, throws at load (see the check below).
170
421
  //
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)));
422
+ // `volts: 0` in the built law is an unused placeholder: the pump rail is derived per circuit
423
+ // from the input node (see `pumpDerivation` in device-laws.ts), and pump parts bypass generic
424
+ // section expansion, `registrySuppliedVoltages`, and `declaredRegulatorTerminals`, so this
425
+ // number is never stamped.
426
+ const chargePumpTerminalStructures = {
427
+ // Pinout verified against Microchip DS21348A (2001), p1 pin-configuration diagram and general
428
+ // description: 1 BOOST, 2 CAP+, 3 GND, 4 CAP-, 5 VOUT, 6 LV, 7 OSC, 8 V+.
429
+ BOOST_7660: {
430
+ terminalRoleGroups: [
431
+ ["boost"],
432
+ ["capp", "capplus"],
433
+ ["gnd", "ground"],
434
+ ["capm", "capminus"],
435
+ ["vout", "negativeoutput", "voutnegative", "out", "output"],
436
+ ["lv", "lowvoltage"],
437
+ ["osc", "oscillator"],
438
+ ["vplus", "vin", "vdd", "vcc", "input"],
439
+ ],
440
+ pinout: ["boost", "capp", "gnd", "capm", "vout", "lv", "osc", "vplus"],
441
+ },
442
+ // Pinout verified against Intersil FN3072 Rev 7.00, p1 pinout diagram (8 Ld PDIP/SOIC top view):
443
+ // 1 NC, 2 CAP+, 3 GND, 4 CAP-, 5 VOUT, 6 LV, 7 OSC, 8 V+.
444
+ NC_7660: {
445
+ terminalRoleGroups: [
446
+ ["nc", "noconnect"],
447
+ ["capp", "capplus"],
448
+ ["gnd", "ground"],
449
+ ["capm", "capminus"],
450
+ ["vout", "negativeoutput", "voutnegative", "out", "output"],
451
+ ["lv", "lowvoltage"],
452
+ ["osc", "oscillator"],
453
+ ["vplus", "vin", "vdd", "vcc", "input"],
454
+ ],
455
+ pinout: ["nc", "capp", "gnd", "capm", "vout", "lv", "osc", "vplus"],
456
+ },
457
+ // Pinout verified against TI SLVS033G, p3 pin config + pin-functions table (PDIP-8): 1 FB/SD
458
+ // (shutdown/error-amp input), 2 CAP+, 3 GND, 4 CAP-, 5 VOUT (output), 6 VREF (2.5 V reference
459
+ // output), 7 OSC (input), 8 VCC (supply). This is NOT the 7660 pin order: pin 1 is an input and
460
+ // pin 6 an output, so the part is footprint-compatible but not a drop-in substitute.
461
+ FBSD_LT1054: {
462
+ terminalRoleGroups: [
463
+ ["fbsd", "fb", "sd", "shutdown"],
464
+ ["capp", "capplus"],
465
+ ["gnd", "ground"],
466
+ ["capm", "capminus"],
467
+ ["vout", "negativeoutput", "voutnegative", "out", "output"],
468
+ ["vref", "reference"],
469
+ ["osc", "oscillator"],
470
+ ["vcc", "vin", "vplus", "vdd", "input"],
471
+ ],
472
+ pinout: ["fbsd", "capp", "gnd", "capm", "vout", "vref", "osc", "vcc"],
473
+ },
474
+ // Pinout verified against Linear Technology 1144fa, p2 config + p5 Pin Functions: 1 BOOST
475
+ // (x10 when tied high), 2 CAP+, 3 GND, 4 CAP-, 5 VOUT, 6 SHDN (float/V+ = run, GND = shutdown),
476
+ // 7 OSC, 8 V+. No LV pin and no low-voltage mode split.
477
+ SHDN_LTC1144: {
478
+ terminalRoleGroups: [
479
+ ["boost"],
480
+ ["capp", "capplus"],
481
+ ["gnd", "ground"],
482
+ ["capm", "capminus"],
483
+ ["vout", "negativeoutput", "voutnegative", "out", "output"],
484
+ ["shdn", "shutdown"],
485
+ ["osc", "oscillator"],
486
+ ["vplus", "vin", "vdd", "vcc", "input"],
487
+ ],
488
+ pinout: ["boost", "capp", "gnd", "capm", "vout", "shdn", "osc", "vplus"],
489
+ },
490
+ };
491
+ /** Which wiring skeleton each charge-pump record builds on, by the record's first part id. */
492
+ const chargePumpStructureFor = {
493
+ TC1044S: "BOOST_7660",
494
+ ICL7660: "NC_7660",
495
+ ICL7660A: "NC_7660",
496
+ ICL7660S: "BOOST_7660",
497
+ TC7662B: "BOOST_7660",
498
+ LT1054: "FBSD_LT1054",
499
+ LTC1144: "SHDN_LTC1144",
500
+ MAX1044: "BOOST_7660",
501
+ };
502
+ /** Build one charge-pump entry from its registry record and its wiring skeleton. */
503
+ function chargePumpEntry(record) {
504
+ const sourceOhms = record.params["sourceOhms"];
505
+ const loss = record.params["chargePumpLossVolts"];
506
+ if (sourceOhms === undefined || loss === undefined) {
507
+ throw new Error(`${record.partIds.join(",")}: a charge-pump record states no sourceOhms or chargePumpLossVolts`);
508
+ }
509
+ const key = record.partIds[0];
510
+ const skeletonName = key === undefined ? undefined : chargePumpStructureFor[key];
511
+ const skeleton = skeletonName === undefined ? undefined : chargePumpTerminalStructures[skeletonName];
512
+ if (skeleton === undefined) {
513
+ throw new Error(`${record.partIds.join(",")}: no charge-pump wiring skeleton for ${String(key)}`);
514
+ }
515
+ return {
516
+ partIds: [...record.partIds],
517
+ declaredTypes: [],
518
+ terminalRoleGroups: skeleton.terminalRoleGroups,
519
+ ...provisionalArmOf(record),
520
+ model: {
521
+ kind: "sections",
522
+ sections: [
523
+ {
524
+ law: {
525
+ kind: "voltage-source",
526
+ volts: 0,
527
+ sourceOhms: sourceOhms.value,
528
+ },
529
+ terminals: [4, 2],
530
+ },
531
+ ],
532
+ pinout: skeleton.pinout,
533
+ },
534
+ chargePumpLossVolts: loss.value,
535
+ };
536
+ }
537
+ /** The charge-pump entry for one family, by the record's first part id. Position-preserving. */
538
+ function chargePumpEntryFor(firstPartId) {
539
+ const record = chargePumpRegistryRecords.find((candidate) => candidate.partIds[0] === firstPartId);
540
+ if (record === undefined) {
541
+ throw new Error(`no charge-pump registry record starts with ${firstPartId}`);
542
+ }
543
+ return chargePumpEntry(record);
544
+ }
545
+ // Every skeleton row is consumed by exactly one record, and every record lands on a row.
546
+ for (const key of Object.keys(chargePumpStructureFor)) {
547
+ if (!chargePumpRegistryRecords.some((record) => record.partIds[0] === key)) {
548
+ throw new Error(`charge-pump wiring skeleton for ${key} is consumed by no registry record`);
549
+ }
550
+ }
221
551
  const entries = [
222
552
  // uPC4558, from the artifacts library's own vendored copy:
223
553
  // `parts/op-amps/UPC4558/datasheets/renesas-upc258-upc4558.pdf` (Renesas `UPC258, UPC4558`,
@@ -309,6 +639,8 @@ const entries = [
309
639
  "NJM4558DD",
310
640
  "NJM4558D",
311
641
  "JRC4558D",
642
+ "JRC4558",
643
+ "JRC4558DD",
312
644
  "NJM4558M",
313
645
  "NJM4558E",
314
646
  "NJM4558V",
@@ -397,18 +729,12 @@ const entries = [
397
729
  //
398
730
  // VOM Maximum peak output voltage swing RL = 10 kOhm, TA = 25 C, VCC+- = +-15 V
399
731
  // 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
- //
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
732
  //
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.
733
+ // Open-loop gain stays the house 100000 the family entry it was split from carried; it is not a
734
+ // sourced TL06x figure. The output swing is not in this entry: it has its own table,
735
+ // `registry-data/opamp.ts`, which stamps the printed pair (1.5 V a side, `constant`) onto these
736
+ // ids, for a declared `opamp` device and a package alike. The graph-read 1.2 V this entry carried
737
+ // until row 16 (Figure 6-1 at +-4.5 V) was not a printed figure and is gone.
412
738
  {
413
739
  "partIds": [
414
740
  "TL062",
@@ -436,8 +762,7 @@ const entries = [
436
762
  "kind": "ideal-opamp",
437
763
  "railHigh": null,
438
764
  "railLow": null,
439
- "openLoopGain": 100000,
440
- "outputSwingDropVolts": 1.2
765
+ "openLoopGain": 100000
441
766
  },
442
767
  "terminals": [
443
768
  2,
@@ -450,8 +775,7 @@ const entries = [
450
775
  "kind": "ideal-opamp",
451
776
  "railHigh": null,
452
777
  "railLow": null,
453
- "openLoopGain": 100000,
454
- "outputSwingDropVolts": 1.2
778
+ "openLoopGain": 100000
455
779
  },
456
780
  "terminals": [
457
781
  4,
@@ -494,8 +818,7 @@ const entries = [
494
818
  "kind": "ideal-opamp",
495
819
  "railHigh": null,
496
820
  "railLow": null,
497
- "openLoopGain": 100000,
498
- "outputSwingDropVolts": 1.2
821
+ "openLoopGain": 100000
499
822
  },
500
823
  "terminals": [
501
824
  0,
@@ -3084,18 +3407,10 @@ const entries = [
3084
3407
  },
3085
3408
  // Texas Instruments OP07x, from "OP07x Precision Operational Amplifiers" SLOS099H (rev. March
3086
3409
  // 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).
3410
+ // Open-loop gain stays the house 100000. The output swing is not in this entry: it has its own
3411
+ // registry-data record (`registry-data/opamp.ts`), which types the printed +-12.8 V (RL = 2 kOhm, VS = +-15 V)
3412
+ // as a `proportional` pair for OP07CP, OP07DP and OP07DN, and lists the bare `OP07` id absent (the
3413
+ // sheet's characteristics apply to the C and D grades only).
3099
3414
  {
3100
3415
  "partIds": [
3101
3416
  "OP07",
@@ -3129,8 +3444,7 @@ const entries = [
3129
3444
  "kind": "ideal-opamp",
3130
3445
  "railHigh": null,
3131
3446
  "railLow": null,
3132
- "openLoopGain": 100000,
3133
- "outputSwingDropVolts": 2.2
3447
+ "openLoopGain": 100000
3134
3448
  },
3135
3449
  "terminals": [
3136
3450
  0,
@@ -3147,6 +3461,11 @@ const entries = [
3147
3461
  }
3148
3462
  },
3149
3463
  // --- Single Op-Amps (LM741, TL071, NJM5534D) --------------------------------------------------------------------------
3464
+ // Wave 5: LM741CN rides this entry. It is the exact PDIP orderable spelling printed
3465
+ // in the TI LM741 SNOSC25D (May 1998, rev Oct 2015) Package Option Addendum:
3466
+ // 'LM741CN/NOPB | Active | Production | PDIP (P) | 8', part marking 'LM 741CN'.
3467
+ // The specs sit under the LM741C column family; open-loop gain stays the house
3468
+ // 100000 and no GBW row is quoted (see opampGainBandwidthAbsent).
3150
3469
  {
3151
3470
  "partIds": [
3152
3471
  "LM741",
@@ -3158,7 +3477,9 @@ const entries = [
3158
3477
  "NJM5534",
3159
3478
  "NJM5534D",
3160
3479
  "NE5534",
3161
- "NJM5534M"
3480
+ "NJM5534M",
3481
+ "JRC5534",
3482
+ "LM741CN"
3162
3483
  ],
3163
3484
  "declaredTypes": [],
3164
3485
  "terminalRoleGroups": [
@@ -3236,170 +3557,16 @@ const entries = [
3236
3557
  ]
3237
3558
  }
3238
3559
  },
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
- },
3560
+ // --- 5V Voltage Regulators: ids and rail from registry-data/regulator.ts; wiring above --------
3561
+ regulatorEntryForVolts(5),
3562
+ // --- 9V Voltage Regulators: ids and rail from registry-data/regulator.ts; wiring above --------
3563
+ regulatorEntryForVolts(9),
3564
+ // --- 12V Voltage Regulators (wave 5: LM78L12): ids and rail from registry-data/regulator.ts; wiring above --------
3565
+ regulatorEntryForVolts(12),
3566
+ // --- 3.3V Voltage Regulators (wave 5: LM78L33): ids and rail from registry-data/regulator.ts; wiring above --------
3567
+ regulatorEntryForVolts(3.3),
3568
+ // --- 18V Voltage Regulators: ids and rail from registry-data/regulator.ts; wiring above --------
3569
+ regulatorEntryForVolts(18),
3403
3570
  // --- Active Physical Analog Comparators (LM311, NJM311M, LM319, NJM319M) ----------------------------------------------------
3404
3571
  // --- Active Physical Analog Comparators (LM311, NJM311M, LM319, NJM319M, LM393, LM339) ------------------------------------
3405
3572
  {
@@ -5209,63 +5376,33 @@ const entries = [
5209
5376
  ]
5210
5377
  }
5211
5378
  },
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.
5379
+ // --- Negative Voltage Regulators: ids and rail from registry-data/regulator.ts; wiring above --------
5380
+ // The 7912/LM7912 sharing and the signed-volts convention are recorded on the record; the
5381
+ // wiring skeleton for -15 V is shared. Until the convention is settled, a negative regulator
5382
+ // using this entry must declare its output `positive`, which is why deluxe-memory-man does.
5383
+ regulatorEntryForVolts(-15),
5384
+ // --- TC1044S charge-pump inverter (Microchip): ids and loss/slope from registry-data/charge-pump.ts; wiring above ---
5385
+ chargePumpEntryFor("TC1044S"),
5386
+ // --- ICL7660 charge-pump inverter (Intersil/Renesas): ids and loss/slope from registry-data/charge-pump.ts; wiring above ---
5387
+ chargePumpEntryFor("ICL7660"),
5388
+ // --- ICL7660A charge-pump inverter (Intersil/Renesas): ids and loss/slope from registry-data/charge-pump.ts; wiring above ---
5389
+ chargePumpEntryFor("ICL7660A"),
5390
+ // --- ICL7660S charge-pump inverter (Renesas): ids and loss/slope from registry-data/charge-pump.ts; wiring above ---
5391
+ chargePumpEntryFor("ICL7660S"),
5392
+ // --- TC7662B charge-pump inverter (Microchip): ids and loss/slope from registry-data/charge-pump.ts; wiring above ---
5393
+ chargePumpEntryFor("TC7662B"),
5394
+ // --- LT1054 switched-capacitor converter (TI second source): ids and loss/slope from registry-data/charge-pump.ts; wiring above ---
5395
+ chargePumpEntryFor("LT1054"),
5396
+ // --- LTC1144 wide-input converter (Linear Tech): ids and loss/slope from registry-data/charge-pump.ts; wiring above ---
5397
+ chargePumpEntryFor("LTC1144"),
5398
+ // --- MAX1044 switched-capacitor converter (Maxim): ids and loss/slope from registry-data/charge-pump.ts; wiring above ---
5399
+ chargePumpEntryFor("MAX1044"),
5400
+ // --- AK4552VT8 8-pin codec body alternative ----------------------------------------------------------------------------------
5215
5401
  {
5216
5402
  "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"
5403
+ "AK4552VT8",
5404
+ "AK4552VT",
5405
+ "AK4552"
5269
5406
  ],
5270
5407
  "declaredTypes": [],
5271
5408
  "terminalRoleGroups": [],
@@ -5370,10 +5507,11 @@ const entries = [
5370
5507
  // output=pinout[0] -- the `ideal-opamp` law reads only those three; vplus/vminus are not
5371
5508
  // wired to the law (rails come from the circuit, exactly as the CD4049 template above).
5372
5509
  // 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.
5510
+ // class-general 100 dB (1e4) rather than a sourced MC33178 figure. It states **no output swing**:
5511
+ // the 1.1 V this entry carried until row 16 was the registry's (`component-opamp-chips.json`)
5512
+ // citation of the onsemi MC33178-D table at +-15 V, never re-read here because the datasheet is not
5513
+ // vendored, which does not meet the printed-TYP-only rule; the id is listed in
5514
+ // `opampOutputSwingAbsent` with that reason.
5377
5515
  {
5378
5516
  "partIds": [
5379
5517
  "MC33178",
@@ -5390,8 +5528,7 @@ const entries = [
5390
5528
  "kind": "ideal-opamp",
5391
5529
  "railHigh": null,
5392
5530
  "railLow": null,
5393
- "openLoopGain": 100000,
5394
- "outputSwingDropVolts": 1.1
5531
+ "openLoopGain": 100000
5395
5532
  },
5396
5533
  "terminals": [
5397
5534
  2,
@@ -5723,52 +5860,14 @@ const entries = [
5723
5860
  ]
5724
5861
  }
5725
5862
  },
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
- },
5863
+ // --- LM340T-15 +15V linear regulator (mxr-m117r-flanger): ids and rail from registry-data/regulator.ts; wiring above ----
5864
+ // The sole remaining blocker on `mxr-m117r-flanger` after the `CD4013AE` fix above: its `VR1`
5865
+ // names PartNumber `LM340T-15`, an unregistered fixed +15V linear regulator. Same shape and same
5866
+ // `voltage-source` law as the 78L05/78L09 entries (input/ground/output, output held at the
5867
+ // regulator's fixed rail relative to ground) -- `VR1` declares exactly three terminals,
5868
+ // `input, ground, output`, in that order, matching the regulator entries' `terminals: [2, 1]`
5869
+ // (output relative to ground) convention directly.
5870
+ regulatorEntryForVolts(15),
5772
5871
  // --- Tube rectifiers ------------------------------------------------------------------
5773
5872
  //
5774
5873
  // A vacuum rectifier drops tens of volts where a silicon diode drops less than one, and that
@@ -6696,181 +6795,1814 @@ const entries = [
6696
6795
  ]
6697
6796
  }
6698
6797
  },
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
- },
6798
+ // Wave-1 op-amp entries: sections whose gain-bandwidth product the data registry
6799
+ // stamps (see `registry-data/opamp.ts` and `withDatasheetGainBandwidth` below). The
6800
+ // open-loop gain is the sheet's own TYP AOL transcribed; the swing pair is deliberately
6801
+ // absent (owner D4 freeze, upheld for this wave).
6802
+ // TI LM833 dual audio op-amp, from `parts/op-amps/LM833/datasheets/lm833-ti.pdf` (TI
6803
+ // SLOS481B, rev Oct 2014). Pin diagram and Pin Functions table, 6 p3 (D/SOIC, DGK/MSOP
6804
+ // and P/PDIP packages, TOP VIEW): 1 OUT1, 2 IN1-, 3 IN1+, 4 VCC-, 5 IN2+, 6 IN2-,
6805
+ // 7 OUT2, 8 VCC+ -- the standard dual order, read off the sheet's own table, with the
6806
+ // same pin-numbered role convention as the 4558 entry above (pin-numbered tokens only;
6807
+ // no generic alias may satisfy two sections at once).
6808
+ //
6809
+ // Electrical characteristics 7.5/7.6 p5 at VCC+- = +-15 V, TA = 25 C:
6810
+ // Large-signal differential voltage amplification AVD, RL >= 2 kOhm, VO = +-10 V:
6811
+ // MIN 90 TYP 110 dB. Used as `openLoopGain: decibelGain(110)`, the typ figure.
6812
+ // GBW is not in this entry: it lives in the data-registry row (TYP 16 MHz at f = 100 kHz),
6813
+ // stamped by the pass below. Wave 4: LM833N rides this entry for open-loop gain
6814
+ // (TI LM833-N sheet SNOSBD8E prints LM833N/NOPB as the PDIP orderable of the same
6815
+ // die with the same AV 110 dB TYP) but carries its own GBW row: the LM833-N sheet
6816
+ // prints GBW TYP 15 MHz, not the LM833 sheet's 16, so the pass splits the entry.
6823
6817
  {
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",
6818
+ "partIds": [
6819
+ "LM833",
6820
+ "LM833N"
6828
6821
  ],
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.",
6822
+ "declaredTypes": [],
6823
+ "terminalRoleGroups": [
6824
+ [
6825
+ "OUT_A_pin1"
6826
+ ],
6827
+ [
6828
+ "INVERTING_A_pin2"
6829
+ ],
6830
+ [
6831
+ "NONINVERTING_A_pin3"
6832
+ ],
6833
+ [
6834
+ "V_MINUS_pin4"
6835
+ ],
6836
+ [
6837
+ "NONINVERTING_B_pin5"
6838
+ ],
6839
+ [
6840
+ "INVERTING_B_pin6"
6841
+ ],
6842
+ [
6843
+ "OUT_B_pin7"
6844
+ ],
6845
+ [
6846
+ "V_PLUS_pin8"
6847
+ ]
6848
+ ],
6849
+ "model": {
6850
+ "kind": "sections",
6851
+ "pinout": [
6852
+ "OUT_A",
6853
+ "IN_A_MINUS",
6854
+ "IN_A_PLUS",
6855
+ "VEE",
6856
+ "IN_B_PLUS",
6857
+ "IN_B_MINUS",
6858
+ "OUT_B",
6859
+ "VCC"
6860
+ ],
6861
+ "sections": [
6862
+ {
6863
+ "law": {
6864
+ "kind": "ideal-opamp",
6865
+ "railHigh": null,
6866
+ "railLow": null,
6867
+ "openLoopGain": 316227.7660168379
6868
+ },
6869
+ "terminals": [
6870
+ 2,
6871
+ 1,
6872
+ 0
6873
+ ]
6874
+ },
6875
+ {
6876
+ "law": {
6877
+ "kind": "ideal-opamp",
6878
+ "railHigh": null,
6879
+ "railLow": null,
6880
+ "openLoopGain": 316227.7660168379
6881
+ },
6882
+ "terminals": [
6883
+ 4,
6884
+ 5,
6885
+ 6
6886
+ ]
6887
+ }
6888
+ ]
6889
+ }
6836
6890
  },
6891
+ // Wave-3 op-amp entries: sections whose open-loop gain is the sheet's own TYP AOL
6892
+ // transcribed (the gain-bandwidth product lives in `registry-data/opamp.ts`, stamped by
6893
+ // the passes below; the swing pair stays absent per the D4 freeze). GBW rows and catalog
6894
+ // entries are 1:1 by part id, so no entry splits. LM1458/N carry AOL only (no GBW row
6895
+ // exists; see `opampGainBandwidthAbsent`).
6896
+ // NJR NJM4580 dual audio op-amp, from `parts/op-amps/JRC4580/datasheets/njm4580d-njr.pdf`
6897
+ // (NJR Ver. 2012-09-14). PIN FUNCTION table, p1 (DIP-8 TOP VIEW): 1 A OUTPUT, 2 A -INPUT,
6898
+ // 3 A +INPUT, 4 V-, 5 B +INPUT, 6 B -INPUT, 7 B OUTPUT, 8 V+ -- the standard dual order,
6899
+ // with the same pin-numbered role convention as the LM833 entry above (pin-numbered
6900
+ // tokens only; no generic alias may satisfy two sections at once).
6901
+ //
6902
+ // Electrical characteristics p2 at V+/V- = +-15 V, Ta = 25 C:
6903
+ // Voltage Gain AV, RL >= 2 kOhm, VO = +-10 V: MIN 90 TYP 110 dB.
6904
+ // Used as `openLoopGain: decibelGain(110)`, the typ figure (the D rank changes the noise
6905
+ // row only). The JRC ids rest on `parts/op-amps/JRC4580.md` ('JRC4580D / NJM4580D',
6906
+ // primary local datasheet this sheet) -- the JRC4558D precedent (owner decision
6907
+ // 2026-10-15: the NJR/JRC marking rides the NJM sheet). GBW is not in this entry: it
6908
+ // lives in the data-registry row (TYP 15 MHz at f = 10 kHz), stamped by the pass below.
6837
6909
  {
6838
- partIds: ["OP07"],
6839
- reason: "The OP07 sheet's electrical characteristics apply to OP07C and OP07D only; the bare id has no column.",
6910
+ "partIds": [
6911
+ "JRC4580D",
6912
+ "JRC4580",
6913
+ "NJM4580D",
6914
+ "NJM4580"
6915
+ ],
6916
+ "declaredTypes": [],
6917
+ "terminalRoleGroups": [
6918
+ [
6919
+ "OUT_A_pin1"
6920
+ ],
6921
+ [
6922
+ "INVERTING_A_pin2"
6923
+ ],
6924
+ [
6925
+ "NONINVERTING_A_pin3"
6926
+ ],
6927
+ [
6928
+ "V_MINUS_pin4"
6929
+ ],
6930
+ [
6931
+ "NONINVERTING_B_pin5"
6932
+ ],
6933
+ [
6934
+ "INVERTING_B_pin6"
6935
+ ],
6936
+ [
6937
+ "OUT_B_pin7"
6938
+ ],
6939
+ [
6940
+ "V_PLUS_pin8"
6941
+ ]
6942
+ ],
6943
+ "model": {
6944
+ "kind": "sections",
6945
+ "pinout": [
6946
+ "OUT_A",
6947
+ "IN_A_MINUS",
6948
+ "IN_A_PLUS",
6949
+ "VEE",
6950
+ "IN_B_PLUS",
6951
+ "IN_B_MINUS",
6952
+ "OUT_B",
6953
+ "VCC"
6954
+ ],
6955
+ "sections": [
6956
+ {
6957
+ "law": {
6958
+ "kind": "ideal-opamp",
6959
+ "railHigh": null,
6960
+ "railLow": null,
6961
+ "openLoopGain": 316227.7660168379
6962
+ },
6963
+ "terminals": [
6964
+ 2,
6965
+ 1,
6966
+ 0
6967
+ ]
6968
+ },
6969
+ {
6970
+ "law": {
6971
+ "kind": "ideal-opamp",
6972
+ "railHigh": null,
6973
+ "railLow": null,
6974
+ "openLoopGain": 316227.7660168379
6975
+ },
6976
+ "terminals": [
6977
+ 4,
6978
+ 5,
6979
+ 6
6980
+ ]
6981
+ }
6982
+ ]
6983
+ }
6840
6984
  },
6985
+ // TI OPA2134 dual audio op-amp, from `parts/op-amps/OPA2134/datasheets/opa2134-ti.pdf`
6986
+ // (TI SBOS058B, Dec 1997, Rev. B Nov 2024). Table 4-1 Pin Functions: OPA2134, p3 (D/SOIC
6987
+ // 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,
6988
+ // OUT B-7, V+-8 -- the standard dual order, same pin-numbered role convention.
6989
+ // OPA2134PA is a printed PDIP-8 orderable of the same die (orderable table, marking
6990
+ // OPA2134PA); the specs below are family-wide with no per-orderable split.
6991
+ //
6992
+ // Electrical characteristics 5.7 p7 at TA = 25 C, VS = +-15 V:
6993
+ // AOL Open-loop voltage gain, RL = 2 kOhm, -13.8 V <= VO <= 13.5 V:
6994
+ // MIN 104 TYP 120 dB (a second row at RL = 10 kOhm reads the same 104/120).
6995
+ // Used as `openLoopGain: decibelGain(120)`, the typ figure. GBW is not in this entry:
6996
+ // it lives in the data-registry row (TYP 8 MHz), stamped by the pass below.
6841
6997
  {
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) {
6855
- const rowOf = new Map();
6856
- for (const row of opampGainBandwidthDatasheet) {
6857
- for (const id of row.partIds) {
6858
- const folded = foldPartId(id);
6859
- if (rowOf.has(folded))
6860
- throw new Error(`gain-bandwidth table names ${id} twice`);
6861
- rowOf.set(folded, row);
6862
- }
6863
- }
6864
- const absent = new Set();
6865
- for (const { partIds } of opampGainBandwidthAbsent) {
6866
- for (const id of partIds) {
6867
- const folded = foldPartId(id);
6868
- if (rowOf.has(folded))
6869
- throw new Error(`${id} is both given a gain-bandwidth figure and listed absent`);
6870
- absent.add(folded);
6871
- }
6872
- }
6873
- const seen = new Set();
6998
+ "partIds": [
6999
+ "OPA2134",
7000
+ "OPA2134PA"
7001
+ ],
7002
+ "declaredTypes": [],
7003
+ "terminalRoleGroups": [
7004
+ [
7005
+ "OUT_A_pin1"
7006
+ ],
7007
+ [
7008
+ "INVERTING_A_pin2"
7009
+ ],
7010
+ [
7011
+ "NONINVERTING_A_pin3"
7012
+ ],
7013
+ [
7014
+ "V_MINUS_pin4"
7015
+ ],
7016
+ [
7017
+ "NONINVERTING_B_pin5"
7018
+ ],
7019
+ [
7020
+ "INVERTING_B_pin6"
7021
+ ],
7022
+ [
7023
+ "OUT_B_pin7"
7024
+ ],
7025
+ [
7026
+ "V_PLUS_pin8"
7027
+ ]
7028
+ ],
7029
+ "model": {
7030
+ "kind": "sections",
7031
+ "pinout": [
7032
+ "OUT_A",
7033
+ "IN_A_MINUS",
7034
+ "IN_A_PLUS",
7035
+ "VEE",
7036
+ "IN_B_PLUS",
7037
+ "IN_B_MINUS",
7038
+ "OUT_B",
7039
+ "VCC"
7040
+ ],
7041
+ "sections": [
7042
+ {
7043
+ "law": {
7044
+ "kind": "ideal-opamp",
7045
+ "railHigh": null,
7046
+ "railLow": null,
7047
+ "openLoopGain": 1000000
7048
+ },
7049
+ "terminals": [
7050
+ 2,
7051
+ 1,
7052
+ 0
7053
+ ]
7054
+ },
7055
+ {
7056
+ "law": {
7057
+ "kind": "ideal-opamp",
7058
+ "railHigh": null,
7059
+ "railLow": null,
7060
+ "openLoopGain": 1000000
7061
+ },
7062
+ "terminals": [
7063
+ 4,
7064
+ 5,
7065
+ 6
7066
+ ]
7067
+ }
7068
+ ]
7069
+ }
7070
+ },
7071
+ // TI LM1458 dual op-amp, from `parts/op-amps/LM1458/datasheets/lm1458.pdf` (TI SNOSBU4D,
7072
+ // Apr 1998, Rev. D Mar 2013, ex-National; NOT the ST sheet `MC1458.md` points at).
7073
+ // Connection Diagram Fig. 2 Dual-In-Line Package (Top View), p1: 1 OUTPUT A,
7074
+ // 2 INVERTING INPUT A, 3 NON-INVERTING INPUT A, 4 V-, 5 NON-INVERTING INPUT B,
7075
+ // 6 INVERTING INPUT B, 7 OUTPUT B, 8 V+ -- the standard dual order, same pin-numbered
7076
+ // role convention. LM1458N is printed (Abs Max 'LM1458N 400 mW' p2; orderables
7077
+ // LM1458N/NOPB p5, PDIP-8); the AC specs sit under the generic LM1458 column, which
7078
+ // covers it (the CA1458 entry above is the neighbouring point not jumped to: Harris
7079
+ // 200,000 vs TI 160,000 for the same row, so this comes from the document that names
7080
+ // LM1458).
7081
+ //
7082
+ // Electrical characteristics p2 at TA = 25 C, VS = +-15 V, VOUT = +-10 V, RL >= 2 kOhm:
7083
+ // Large Signal Voltage Gain: LM1458 MIN 20 TYP 160 V/mV (LM1558 50/160 is the sibling
7084
+ // column). Used as `openLoopGain: 160000`, the typ figure. The sheet prints no GBW row
7085
+ // at all (see `opampGainBandwidthAbsent`); the swing pair stays absent per the D4 freeze
7086
+ // (see `opampOutputSwingAbsent`).
7087
+ {
7088
+ "partIds": [
7089
+ "LM1458",
7090
+ "LM1458N"
7091
+ ],
7092
+ "declaredTypes": [],
7093
+ "terminalRoleGroups": [
7094
+ [
7095
+ "OUT_A_pin1"
7096
+ ],
7097
+ [
7098
+ "INVERTING_A_pin2"
7099
+ ],
7100
+ [
7101
+ "NONINVERTING_A_pin3"
7102
+ ],
7103
+ [
7104
+ "V_MINUS_pin4"
7105
+ ],
7106
+ [
7107
+ "NONINVERTING_B_pin5"
7108
+ ],
7109
+ [
7110
+ "INVERTING_B_pin6"
7111
+ ],
7112
+ [
7113
+ "OUT_B_pin7"
7114
+ ],
7115
+ [
7116
+ "V_PLUS_pin8"
7117
+ ]
7118
+ ],
7119
+ "model": {
7120
+ "kind": "sections",
7121
+ "pinout": [
7122
+ "OUT_A",
7123
+ "IN_A_MINUS",
7124
+ "IN_A_PLUS",
7125
+ "VEE",
7126
+ "IN_B_PLUS",
7127
+ "IN_B_MINUS",
7128
+ "OUT_B",
7129
+ "VCC"
7130
+ ],
7131
+ "sections": [
7132
+ {
7133
+ "law": {
7134
+ "kind": "ideal-opamp",
7135
+ "railHigh": null,
7136
+ "railLow": null,
7137
+ "openLoopGain": 160000
7138
+ },
7139
+ "terminals": [
7140
+ 2,
7141
+ 1,
7142
+ 0
7143
+ ]
7144
+ },
7145
+ {
7146
+ "law": {
7147
+ "kind": "ideal-opamp",
7148
+ "railHigh": null,
7149
+ "railLow": null,
7150
+ "openLoopGain": 160000
7151
+ },
7152
+ "terminals": [
7153
+ 4,
7154
+ 5,
7155
+ 6
7156
+ ]
7157
+ }
7158
+ ]
7159
+ }
7160
+ },
7161
+ // Wave-4 op-amp entries: sections whose open-loop gain is the sheet's own TYP AOL
7162
+ // transcribed (the gain-bandwidth product lives in `registry-data/opamp.ts`, stamped by
7163
+ // the passes below; the swing pair stays absent per the D4 freeze). GBW rows and catalog
7164
+ // entries are 1:1 by part id, so no entry splits. LM308 and UA741CP carry AOL only (no
7165
+ // GBW row exists; see `opampGainBandwidthAbsent`).
7166
+ // TI LM108/LM208/LM308 single precision op-amp, from SNOSBS5A (May 2004, rev Sept
7167
+ // 2011, OBSOLETE): 5.x Electrical Characteristics (TA = 25C, VS = +-15 V, VOUT = +-10 V,
7168
+ // RL >= 10 kOhm): 'Large Signal Voltage Gain | MIN 50 (LM108/208) / 25 (LM308) |
7169
+ // TYP 300 | V/mV'. Used as `openLoopGain: 300000`. No GBW row is transcribed (the
7170
+ // sheet prints compensation values and open-loop response graphs only; the Fig. 6
7171
+ // 250 kHz/3.5 MHz numbers belong to the LM101A-boosted application circuit).
7172
+ // Pinout: Figure 8 Dual-In-Line Package Top View (8-Pin) p11 (1 COMP, 2 INPUT-,
7173
+ // 3 INPUT+, 4 V-, 5 NC, 6 OUTPUT, 7 V+, 8 COMP 2); the entry keeps the LF351
7174
+ // three-terminal capture (IN+ pin3, IN- pin2, OUT pin6), which is the same
7175
+ // LM741-compatible single layout.
7176
+ {
7177
+ "partIds": [
7178
+ "LM308"
7179
+ ],
7180
+ "declaredTypes": [],
7181
+ "terminalRoleGroups": [
7182
+ [
7183
+ "vin+",
7184
+ "in+",
7185
+ "pin3"
7186
+ ],
7187
+ [
7188
+ "vin-",
7189
+ "in-",
7190
+ "pin2"
7191
+ ],
7192
+ [
7193
+ "vout",
7194
+ "out",
7195
+ "pin6"
7196
+ ]
7197
+ ],
7198
+ "model": {
7199
+ "kind": "sections",
7200
+ "sections": [
7201
+ {
7202
+ "law": {
7203
+ "kind": "ideal-opamp",
7204
+ "railHigh": null,
7205
+ "railLow": null,
7206
+ "openLoopGain": 300000
7207
+ },
7208
+ "terminals": [
7209
+ 0,
7210
+ 1,
7211
+ 2
7212
+ ]
7213
+ }
7214
+ ],
7215
+ "pinout": [
7216
+ "vin+",
7217
+ "vin-",
7218
+ "vout"
7219
+ ]
7220
+ }
7221
+ },
7222
+ // TI LM324 quad op-amp, from SLOS066AE (Aug 1975, rev Sept 2025): 6.7 Electrical
7223
+ // Characteristics for LM324/LM324K (VCC = 15 V, VO = 1 V to 11 V, RL >= 2 kOhm, 25C):
7224
+ // 'Large-signal differential voltage amplification AVD | MIN 25 TYP 100 | V/mV'.
7225
+ // Used as `openLoopGain: 100000`. GBW is not in this entry: it lives in the
7226
+ // data-registry row (B1 unity-gain bandwidth TYP 1.2 MHz, 6.10 Operating Conditions),
7227
+ // stamped by the pass below. Pinout: Table 5-1 Pin Functions N (PDIP) p3, the
7228
+ // standard quad order (1OUT, 1IN-, 1IN+, VCC+, 2IN+, 2IN-, 2OUT, 3OUT, 3IN-, 3IN+,
7229
+ // VCC-, 4IN+, 4IN-, 4OUT), same A/B/C/D convention as the TL074 entry above.
7230
+ {
7231
+ "partIds": [
7232
+ "LM324"
7233
+ ],
7234
+ "declaredTypes": [],
7235
+ "terminalRoleGroups": [
7236
+ [
7237
+ "OUT_A_pin1"
7238
+ ],
7239
+ [
7240
+ "INVERTING_A_pin2"
7241
+ ],
7242
+ [
7243
+ "NONINVERTING_A_pin3"
7244
+ ],
7245
+ [
7246
+ "V_PLUS_pin4"
7247
+ ],
7248
+ [
7249
+ "NONINVERTING_B_pin5"
7250
+ ],
7251
+ [
7252
+ "INVERTING_B_pin6"
7253
+ ],
7254
+ [
7255
+ "OUT_B_pin7"
7256
+ ],
7257
+ [
7258
+ "OUT_C_pin8"
7259
+ ],
7260
+ [
7261
+ "INVERTING_C_pin9"
7262
+ ],
7263
+ [
7264
+ "NONINVERTING_C_pin10"
7265
+ ],
7266
+ [
7267
+ "V_MINUS_pin11"
7268
+ ],
7269
+ [
7270
+ "NONINVERTING_D_pin12"
7271
+ ],
7272
+ [
7273
+ "INVERTING_D_pin13"
7274
+ ],
7275
+ [
7276
+ "OUT_D_pin14"
7277
+ ]
7278
+ ],
7279
+ "model": {
7280
+ "kind": "sections",
7281
+ "pinout": [
7282
+ "OUT_A",
7283
+ "IN_A_MINUS",
7284
+ "IN_A_PLUS",
7285
+ "VCC",
7286
+ "IN_B_PLUS",
7287
+ "IN_B_MINUS",
7288
+ "OUT_B",
7289
+ "OUT_C",
7290
+ "IN_C_MINUS",
7291
+ "IN_C_PLUS",
7292
+ "VEE",
7293
+ "IN_D_PLUS",
7294
+ "IN_D_MINUS",
7295
+ "OUT_D"
7296
+ ],
7297
+ "sections": [
7298
+ {
7299
+ "law": {
7300
+ "kind": "ideal-opamp",
7301
+ "railHigh": null,
7302
+ "railLow": null,
7303
+ "openLoopGain": 100000
7304
+ },
7305
+ "terminals": [
7306
+ 2,
7307
+ 1,
7308
+ 0
7309
+ ]
7310
+ },
7311
+ {
7312
+ "law": {
7313
+ "kind": "ideal-opamp",
7314
+ "railHigh": null,
7315
+ "railLow": null,
7316
+ "openLoopGain": 100000
7317
+ },
7318
+ "terminals": [
7319
+ 4,
7320
+ 5,
7321
+ 6
7322
+ ]
7323
+ },
7324
+ {
7325
+ "law": {
7326
+ "kind": "ideal-opamp",
7327
+ "railHigh": null,
7328
+ "railLow": null,
7329
+ "openLoopGain": 100000
7330
+ },
7331
+ "terminals": [
7332
+ 9,
7333
+ 8,
7334
+ 7
7335
+ ]
7336
+ },
7337
+ {
7338
+ "law": {
7339
+ "kind": "ideal-opamp",
7340
+ "railHigh": null,
7341
+ "railLow": null,
7342
+ "openLoopGain": 100000
7343
+ },
7344
+ "terminals": [
7345
+ 11,
7346
+ 12,
7347
+ 13
7348
+ ]
7349
+ }
7350
+ ]
7351
+ }
7352
+ },
7353
+ // TI uA741C single op-amp in the PDIP-8 UA741CP orderable, from SLOS094H (Nov 1970,
7354
+ // rev Feb 2026): 5.4 Electrical Characteristics uA741C (VCC+- = +-15 V, VO = +-10 V,
7355
+ // RL >= 2 kOhm, 25C): 'Large-signal differential voltage amplification AVD |
7356
+ // MIN 20 TYP 200 | V/mV'. Used as `openLoopGain: 200000`. The sheet prints the
7357
+ // orderable as UA741CP (PDIP P|8) and the device table as uA741CP; the ASCII id is
7358
+ // the orderable spelling. No GBW row is transcribed (switching tables print only
7359
+ // rise time, overshoot and SR). Pinout: Figure 4-2 D-or-P 8-pin Top View + Table 4-2
7360
+ // Pin Functions p3 (1 NC, 2 IN-, 3 IN+, 4 VCC-, 5 NC, 6 OUT, 7 VCC+, 8 NC); the entry
7361
+ // keeps the LF351 three-terminal capture (IN+ pin3, IN- pin2, OUT pin6).
7362
+ {
7363
+ "partIds": [
7364
+ "UA741CP"
7365
+ ],
7366
+ "declaredTypes": [],
7367
+ "terminalRoleGroups": [
7368
+ [
7369
+ "vin+",
7370
+ "in+",
7371
+ "pin3"
7372
+ ],
7373
+ [
7374
+ "vin-",
7375
+ "in-",
7376
+ "pin2"
7377
+ ],
7378
+ [
7379
+ "vout",
7380
+ "out",
7381
+ "pin6"
7382
+ ]
7383
+ ],
7384
+ "model": {
7385
+ "kind": "sections",
7386
+ "sections": [
7387
+ {
7388
+ "law": {
7389
+ "kind": "ideal-opamp",
7390
+ "railHigh": null,
7391
+ "railLow": null,
7392
+ "openLoopGain": 200000
7393
+ },
7394
+ "terminals": [
7395
+ 0,
7396
+ 1,
7397
+ 2
7398
+ ]
7399
+ }
7400
+ ],
7401
+ "pinout": [
7402
+ "vin+",
7403
+ "vin-",
7404
+ "vout"
7405
+ ]
7406
+ }
7407
+ },
7408
+ // TI TLC2262 dual LinCMOS op-amp, from SLOS177D (Feb 1997, rev Mar 2001): TLC2262C
7409
+ // operating characteristics (VDD+- = +-5 V, the 10 V table closest to a 9 V pedal):
7410
+ // 'Large-signal differential voltage amplification, RL = 50 kOhm, VO = +-4 V:
7411
+ // MIN 80 TYP 200 V/mV'. Used as `openLoopGain: 200000`. Coordinator standing rule
7412
+ // 2026-10-19: the bare id the sheet names in its description takes the commercial (C)
7413
+ // grade column, disclosed here; no suffixed grade is aliased. GBW is not in this
7414
+ // entry: it lives in the data-registry row (TYP 0.73 MHz, same table), stamped by the
7415
+ // pass below. Pinout: D/P/PW 8-pin Top View p3, the standard dual order
7416
+ // (1OUT, 1IN-, 1IN+, VDD-/GND, 2IN+, 2IN-, 2OUT, VDD+), same A/B convention as the
7417
+ // LM833 entry above.
7418
+ {
7419
+ "partIds": [
7420
+ "TLC2262"
7421
+ ],
7422
+ "declaredTypes": [],
7423
+ "terminalRoleGroups": [
7424
+ [
7425
+ "OUT_A_pin1"
7426
+ ],
7427
+ [
7428
+ "INVERTING_A_pin2"
7429
+ ],
7430
+ [
7431
+ "NONINVERTING_A_pin3"
7432
+ ],
7433
+ [
7434
+ "V_MINUS_pin4"
7435
+ ],
7436
+ [
7437
+ "NONINVERTING_B_pin5"
7438
+ ],
7439
+ [
7440
+ "INVERTING_B_pin6"
7441
+ ],
7442
+ [
7443
+ "OUT_B_pin7"
7444
+ ],
7445
+ [
7446
+ "V_PLUS_pin8"
7447
+ ]
7448
+ ],
7449
+ "model": {
7450
+ "kind": "sections",
7451
+ "pinout": [
7452
+ "OUT_A",
7453
+ "IN_A_MINUS",
7454
+ "IN_A_PLUS",
7455
+ "VEE",
7456
+ "IN_B_PLUS",
7457
+ "IN_B_MINUS",
7458
+ "OUT_B",
7459
+ "VCC"
7460
+ ],
7461
+ "sections": [
7462
+ {
7463
+ "law": {
7464
+ "kind": "ideal-opamp",
7465
+ "railHigh": null,
7466
+ "railLow": null,
7467
+ "openLoopGain": 200000
7468
+ },
7469
+ "terminals": [
7470
+ 2,
7471
+ 1,
7472
+ 0
7473
+ ]
7474
+ },
7475
+ {
7476
+ "law": {
7477
+ "kind": "ideal-opamp",
7478
+ "railHigh": null,
7479
+ "railLow": null,
7480
+ "openLoopGain": 200000
7481
+ },
7482
+ "terminals": [
7483
+ 4,
7484
+ 5,
7485
+ 6
7486
+ ]
7487
+ }
7488
+ ]
7489
+ }
7490
+ },
7491
+ // onsemi MC33174 quad op-amp, from MC33171/D Rev. 14 Jul 2025: DC table (VCC = +15 V,
7492
+ // VEE = -15 V, RL to ground, TA = 25C) p3: 'Large Signal Voltage Gain (VO = +-10 V,
7493
+ // RL = 10 k), AVOL MIN 50 TYP 500 V/mV'. Used as `openLoopGain: 500000`. GBW is not
7494
+ // in this entry: it lives in the data-registry row (TYP 1.8 MHz at f = 100 kHz, AC
7495
+ // table p4), stamped by the pass below. Pinout: PIN CONNECTIONS quad diagram p2
7496
+ // incl. PDIP-14 (Output 1 = 1, Inputs 1, VCC, Inputs 2, Output 2, Output 3,
7497
+ // Inputs 3, VEE, Inputs 4, Output 4), the standard quad order, same A/B/C/D
7498
+ // convention as the TL074 entry.
7499
+ {
7500
+ "partIds": [
7501
+ "MC33174"
7502
+ ],
7503
+ "declaredTypes": [],
7504
+ "terminalRoleGroups": [
7505
+ [
7506
+ "OUT_A_pin1"
7507
+ ],
7508
+ [
7509
+ "INVERTING_A_pin2"
7510
+ ],
7511
+ [
7512
+ "NONINVERTING_A_pin3"
7513
+ ],
7514
+ [
7515
+ "V_PLUS_pin4"
7516
+ ],
7517
+ [
7518
+ "NONINVERTING_B_pin5"
7519
+ ],
7520
+ [
7521
+ "INVERTING_B_pin6"
7522
+ ],
7523
+ [
7524
+ "OUT_B_pin7"
7525
+ ],
7526
+ [
7527
+ "OUT_C_pin8"
7528
+ ],
7529
+ [
7530
+ "INVERTING_C_pin9"
7531
+ ],
7532
+ [
7533
+ "NONINVERTING_C_pin10"
7534
+ ],
7535
+ [
7536
+ "V_MINUS_pin11"
7537
+ ],
7538
+ [
7539
+ "NONINVERTING_D_pin12"
7540
+ ],
7541
+ [
7542
+ "INVERTING_D_pin13"
7543
+ ],
7544
+ [
7545
+ "OUT_D_pin14"
7546
+ ]
7547
+ ],
7548
+ "model": {
7549
+ "kind": "sections",
7550
+ "pinout": [
7551
+ "OUT_A",
7552
+ "IN_A_MINUS",
7553
+ "IN_A_PLUS",
7554
+ "VCC",
7555
+ "IN_B_PLUS",
7556
+ "IN_B_MINUS",
7557
+ "OUT_B",
7558
+ "OUT_C",
7559
+ "IN_C_MINUS",
7560
+ "IN_C_PLUS",
7561
+ "VEE",
7562
+ "IN_D_PLUS",
7563
+ "IN_D_MINUS",
7564
+ "OUT_D"
7565
+ ],
7566
+ "sections": [
7567
+ {
7568
+ "law": {
7569
+ "kind": "ideal-opamp",
7570
+ "railHigh": null,
7571
+ "railLow": null,
7572
+ "openLoopGain": 500000
7573
+ },
7574
+ "terminals": [
7575
+ 2,
7576
+ 1,
7577
+ 0
7578
+ ]
7579
+ },
7580
+ {
7581
+ "law": {
7582
+ "kind": "ideal-opamp",
7583
+ "railHigh": null,
7584
+ "railLow": null,
7585
+ "openLoopGain": 500000
7586
+ },
7587
+ "terminals": [
7588
+ 4,
7589
+ 5,
7590
+ 6
7591
+ ]
7592
+ },
7593
+ {
7594
+ "law": {
7595
+ "kind": "ideal-opamp",
7596
+ "railHigh": null,
7597
+ "railLow": null,
7598
+ "openLoopGain": 500000
7599
+ },
7600
+ "terminals": [
7601
+ 9,
7602
+ 8,
7603
+ 7
7604
+ ]
7605
+ },
7606
+ {
7607
+ "law": {
7608
+ "kind": "ideal-opamp",
7609
+ "railHigh": null,
7610
+ "railLow": null,
7611
+ "openLoopGain": 500000
7612
+ },
7613
+ "terminals": [
7614
+ 11,
7615
+ 12,
7616
+ 13
7617
+ ]
7618
+ }
7619
+ ]
7620
+ }
7621
+ },
7622
+ // Wave-5 op-amp entries: sections whose open-loop gain is the sheet's own TYP AOL
7623
+ // transcribed (the gain-bandwidth product lives in `registry-data/opamp.ts`, stamped by
7624
+ // the passes below; the swing pair stays absent per the D4 freeze). GBW rows and catalog
7625
+ // entries are 1:1 by part id, so no entry splits. TL072C carries AOL only (no single
7626
+ // GBW figure exists; see `opampGainBandwidthAbsent`).
7627
+ // TI TL072C dual JFET-input op-amp, from SLOS080W (Sep 1978, rev Jul 2025): 5.8 DC
7628
+ // (VS = +-15 V, TA = 25C, VO = 0 V): 'AOL | Open-loop voltage gain | TL07xC |
7629
+ // MIN 25 TYP 200 V/mV'. Used as `openLoopGain: 200000` (bare id takes the commercial
7630
+ // C-grade column, disclosed; no suffixed alias). Pinout: Table 4-3 Pin Functions
7631
+ // TL072x (1 1OUT, 2 1IN-, 3 1IN+, 4 VCC-, 5 2IN+, 6 2IN-, 7 2OUT, 8 VCC+), the
7632
+ // standard dual order, same pin-numbered role convention as the NJM4580 entry.
7633
+ {
7634
+ "partIds": [
7635
+ "TL072C"
7636
+ ],
7637
+ "declaredTypes": [],
7638
+ "terminalRoleGroups": [
7639
+ [
7640
+ "OUT_A_pin1"
7641
+ ],
7642
+ [
7643
+ "INVERTING_A_pin2"
7644
+ ],
7645
+ [
7646
+ "NONINVERTING_A_pin3"
7647
+ ],
7648
+ [
7649
+ "V_MINUS_pin4"
7650
+ ],
7651
+ [
7652
+ "NONINVERTING_B_pin5"
7653
+ ],
7654
+ [
7655
+ "INVERTING_B_pin6"
7656
+ ],
7657
+ [
7658
+ "OUT_B_pin7"
7659
+ ],
7660
+ [
7661
+ "V_PLUS_pin8"
7662
+ ]
7663
+ ],
7664
+ "model": {
7665
+ "kind": "sections",
7666
+ "pinout": [
7667
+ "OUT_A",
7668
+ "IN_A_MINUS",
7669
+ "IN_A_PLUS",
7670
+ "VEE",
7671
+ "IN_B_PLUS",
7672
+ "IN_B_MINUS",
7673
+ "OUT_B",
7674
+ "VCC"
7675
+ ],
7676
+ "sections": [
7677
+ {
7678
+ "law": {
7679
+ "kind": "ideal-opamp",
7680
+ "railHigh": null,
7681
+ "railLow": null,
7682
+ "openLoopGain": 200000
7683
+ },
7684
+ "terminals": [
7685
+ 2,
7686
+ 1,
7687
+ 0
7688
+ ]
7689
+ },
7690
+ {
7691
+ "law": {
7692
+ "kind": "ideal-opamp",
7693
+ "railHigh": null,
7694
+ "railLow": null,
7695
+ "openLoopGain": 200000
7696
+ },
7697
+ "terminals": [
7698
+ 4,
7699
+ 5,
7700
+ 6
7701
+ ]
7702
+ }
7703
+ ]
7704
+ }
7705
+ },
7706
+ // TI OPA1678 dual audio op-amp, from SBOS855E (Jan 2017 - Dec 2022): 6.7 Electrical
7707
+ // Characteristics (VS = +-15 V, TA = 25C, RL = 2 kOhm, VCM = VOUT = midsupply):
7708
+ // 'AOL | Open-loop voltage gain | (V-) + 0.8 V <= VO <= (V+) - 0.8 V | MIN 106
7709
+ // TYP 114 | dB'. Used as `openLoopGain: decibelGain(114)` = 501187.2336272725.
7710
+ // GBW lives in the data-registry row (TYP 16 MHz at G = 1). Pinout: 5 Pin
7711
+ // Configuration and Functions, Pin Functions OPA1678 (OUT A-1, -IN A-2, +IN A-3,
7712
+ // V--4, +IN B-5, -IN B-6, OUT B-7, V+-8), the standard dual order.
7713
+ {
7714
+ "partIds": [
7715
+ "OPA1678"
7716
+ ],
7717
+ "declaredTypes": [],
7718
+ "terminalRoleGroups": [
7719
+ [
7720
+ "OUT_A_pin1"
7721
+ ],
7722
+ [
7723
+ "INVERTING_A_pin2"
7724
+ ],
7725
+ [
7726
+ "NONINVERTING_A_pin3"
7727
+ ],
7728
+ [
7729
+ "V_MINUS_pin4"
7730
+ ],
7731
+ [
7732
+ "NONINVERTING_B_pin5"
7733
+ ],
7734
+ [
7735
+ "INVERTING_B_pin6"
7736
+ ],
7737
+ [
7738
+ "OUT_B_pin7"
7739
+ ],
7740
+ [
7741
+ "V_PLUS_pin8"
7742
+ ]
7743
+ ],
7744
+ "model": {
7745
+ "kind": "sections",
7746
+ "pinout": [
7747
+ "OUT_A",
7748
+ "IN_A_MINUS",
7749
+ "IN_A_PLUS",
7750
+ "VEE",
7751
+ "IN_B_PLUS",
7752
+ "IN_B_MINUS",
7753
+ "OUT_B",
7754
+ "VCC"
7755
+ ],
7756
+ "sections": [
7757
+ {
7758
+ "law": {
7759
+ "kind": "ideal-opamp",
7760
+ "railHigh": null,
7761
+ "railLow": null,
7762
+ "openLoopGain": 501187.2336272725
7763
+ },
7764
+ "terminals": [
7765
+ 2,
7766
+ 1,
7767
+ 0
7768
+ ]
7769
+ },
7770
+ {
7771
+ "law": {
7772
+ "kind": "ideal-opamp",
7773
+ "railHigh": null,
7774
+ "railLow": null,
7775
+ "openLoopGain": 501187.2336272725
7776
+ },
7777
+ "terminals": [
7778
+ 4,
7779
+ 5,
7780
+ 6
7781
+ ]
7782
+ }
7783
+ ]
7784
+ }
7785
+ },
7786
+ // TI (Burr-Brown) OPA134 single audio op-amp, from SBOS058B (Dec 1997, Rev. B
7787
+ // Nov 2024): 5.7 Electrical Characteristics (TA = 25C, VS = +-15 V): 'AOL |
7788
+ // Open-loop voltage gain | RL = 2 kOhm, -13.8 V <= VO <= 13.5 V | MIN 104
7789
+ // TYP 120 | dB' (a second row at RL = 10 kOhm reads the same 104/120). Used as
7790
+ // `openLoopGain: decibelGain(120)` = 1000000. GBW lives in the data-registry row
7791
+ // (TYP 8 MHz). OPA134PA is the printed PDIP-8 orderable of the same die
7792
+ // (orderable table: 'OPA134PA | Active | Production | PDIP (P) | 8'; family specs
7793
+ // with no per-orderable split). Pinout: Figure 4-1 + Pin Functions OPA134
7794
+ // (+IN-3, -IN-2, OUT-6); the entry keeps the LF351 three-terminal capture.
7795
+ {
7796
+ "partIds": [
7797
+ "OPA134PA"
7798
+ ],
7799
+ "declaredTypes": [],
7800
+ "terminalRoleGroups": [
7801
+ [
7802
+ "vin+",
7803
+ "in+",
7804
+ "pin3"
7805
+ ],
7806
+ [
7807
+ "vin-",
7808
+ "in-",
7809
+ "pin2"
7810
+ ],
7811
+ [
7812
+ "vout",
7813
+ "out",
7814
+ "pin6"
7815
+ ]
7816
+ ],
7817
+ "model": {
7818
+ "kind": "sections",
7819
+ "sections": [
7820
+ {
7821
+ "law": {
7822
+ "kind": "ideal-opamp",
7823
+ "railHigh": null,
7824
+ "railLow": null,
7825
+ "openLoopGain": 1000000
7826
+ },
7827
+ "terminals": [
7828
+ 0,
7829
+ 1,
7830
+ 2
7831
+ ]
7832
+ }
7833
+ ],
7834
+ "pinout": [
7835
+ "vin+",
7836
+ "vin-",
7837
+ "vout"
7838
+ ]
7839
+ }
7840
+ },
7841
+ // TI (Burr-Brown) OPA604 single op-amp, from SBOS019A (Jan 1992 - Sep 2003):
7842
+ // ELECTRICAL CHARACTERISTICS (TA = +25C, VS = +-15 V, MIN/TYP/MAX columns):
7843
+ // 'OPEN-LOOP GAIN | Open-Loop Voltage Gain, VO = +-10 V, RL = 1 kOhm: MIN 80
7844
+ // TYP 100 dB'. Used as `openLoopGain: decibelGain(100)` = 100000. GBW lives in
7845
+ // the data-registry row (TYP 20 MHz at G = 100, quoted with its condition).
7846
+ // OPA604AP is named by the specs header ('OPA604AP, AU'); the PDIP suffix follows
7847
+ // the Burr-Brown AP convention (the Package Option Addendum lists AU active).
7848
+ // Pinout: the DIP/SOIC diagram (-In-2, +In-3, OUT-6); the LF351 three-terminal
7849
+ // capture, same as the UA741CP entry.
7850
+ {
7851
+ "partIds": [
7852
+ "OPA604AP"
7853
+ ],
7854
+ "declaredTypes": [],
7855
+ "terminalRoleGroups": [
7856
+ [
7857
+ "vin+",
7858
+ "in+",
7859
+ "pin3"
7860
+ ],
7861
+ [
7862
+ "vin-",
7863
+ "in-",
7864
+ "pin2"
7865
+ ],
7866
+ [
7867
+ "vout",
7868
+ "out",
7869
+ "pin6"
7870
+ ]
7871
+ ],
7872
+ "model": {
7873
+ "kind": "sections",
7874
+ "sections": [
7875
+ {
7876
+ "law": {
7877
+ "kind": "ideal-opamp",
7878
+ "railHigh": null,
7879
+ "railLow": null,
7880
+ "openLoopGain": 100000
7881
+ },
7882
+ "terminals": [
7883
+ 0,
7884
+ 1,
7885
+ 2
7886
+ ]
7887
+ }
7888
+ ],
7889
+ "pinout": [
7890
+ "vin+",
7891
+ "vin-",
7892
+ "vout"
7893
+ ]
7894
+ }
7895
+ },
7896
+ // Wave-2 op-amp entries: sections whose open-loop gain is the sheet's own TYP AOL
7897
+ // transcribed (the gain-bandwidth product lives in `registry-data/opamp.ts`, stamped by
7898
+ // the passes below; the swing pair stays absent per the D4 freeze). GBW rows and catalog
7899
+ // entries are 1:1 by part id, so no entry splits.
7900
+ // ADI AD712 dual precision BiFET op-amp, from the ADI AD712 datasheet Rev. B
7901
+ // (SPECIFICATIONS at VS = +-15 V @ TA = +25C): OPEN-LOOP GAIN MIN 150 TYP 400 V/mV
7902
+ // (J/A/S grades; K/B/T and C print MIN 200 TYP 400 -- TYP 400 in every grade, so the
7903
+ // bare id carries it). Used as `openLoopGain: 400000`. The sheet's frequency row reads
7904
+ // 'Small Signal Bandwidth 3.0 MIN 4.0 TYP MHz' with no closed-loop gain stated and never
7905
+ // uses the term gain-bandwidth, so no GBW figure is transcribed (see
7906
+ // `opampGainBandwidthAbsent`). Pinout: 'pinned out in a standard op amp configuration'
7907
+ // (dual DIP-8 order, same pin-numbered role convention as the LM833 entry above).
7908
+ {
7909
+ "partIds": [
7910
+ "AD712"
7911
+ ],
7912
+ "declaredTypes": [],
7913
+ "terminalRoleGroups": [
7914
+ [
7915
+ "OUT_A_pin1"
7916
+ ],
7917
+ [
7918
+ "INVERTING_A_pin2"
7919
+ ],
7920
+ [
7921
+ "NONINVERTING_A_pin3"
7922
+ ],
7923
+ [
7924
+ "V_MINUS_pin4"
7925
+ ],
7926
+ [
7927
+ "NONINVERTING_B_pin5"
7928
+ ],
7929
+ [
7930
+ "INVERTING_B_pin6"
7931
+ ],
7932
+ [
7933
+ "OUT_B_pin7"
7934
+ ],
7935
+ [
7936
+ "V_PLUS_pin8"
7937
+ ]
7938
+ ],
7939
+ "model": {
7940
+ "kind": "sections",
7941
+ "pinout": [
7942
+ "OUT_A",
7943
+ "IN_A_MINUS",
7944
+ "IN_A_PLUS",
7945
+ "VEE",
7946
+ "IN_B_PLUS",
7947
+ "IN_B_MINUS",
7948
+ "OUT_B",
7949
+ "VCC"
7950
+ ],
7951
+ "sections": [
7952
+ {
7953
+ "law": {
7954
+ "kind": "ideal-opamp",
7955
+ "railHigh": null,
7956
+ "railLow": null,
7957
+ "openLoopGain": 400000
7958
+ },
7959
+ "terminals": [
7960
+ 2,
7961
+ 1,
7962
+ 0
7963
+ ]
7964
+ },
7965
+ {
7966
+ "law": {
7967
+ "kind": "ideal-opamp",
7968
+ "railHigh": null,
7969
+ "railLow": null,
7970
+ "openLoopGain": 400000
7971
+ },
7972
+ "terminals": [
7973
+ 4,
7974
+ 5,
7975
+ 6
7976
+ ]
7977
+ }
7978
+ ]
7979
+ }
7980
+ },
7981
+ // TI LF347 quad JFET-input op-amp, from SLOS013C (Mar 1987, rev Mar 2016):
7982
+ // 6.5 AVD 'Large signal differential voltage, VO = +-10 V, RL = 2 kOhm, 25C: MIN 25
7983
+ // TYP 100 V/mV' (6.6 LF347B: MIN 50 TYP 100). Used as `openLoopGain: 100000`.
7984
+ // LF347N is the PDIP-14 of the LF347 grade. Pinout: the sheet's own Pin Functions
7985
+ // table (1 1OUT, 2 1IN-, 3 1IN+, 4 VCC+, 5 2IN+, 6 2IN-, 7 2OUT, 8 3OUT, 9 3IN-,
7986
+ // 10 3IN+, 11 VCC-, 12 4IN+, 13 4IN-, 14 4OUT) -- the standard quad order.
7987
+ {
7988
+ "partIds": [
7989
+ "LF347",
7990
+ "LF347N"
7991
+ ],
7992
+ "declaredTypes": [],
7993
+ "terminalRoleGroups": [
7994
+ [
7995
+ "OUT_A_pin1"
7996
+ ],
7997
+ [
7998
+ "INVERTING_A_pin2"
7999
+ ],
8000
+ [
8001
+ "NONINVERTING_A_pin3"
8002
+ ],
8003
+ [
8004
+ "V_PLUS_pin4"
8005
+ ],
8006
+ [
8007
+ "NONINVERTING_B_pin5"
8008
+ ],
8009
+ [
8010
+ "INVERTING_B_pin6"
8011
+ ],
8012
+ [
8013
+ "OUT_B_pin7"
8014
+ ],
8015
+ [
8016
+ "OUT_C_pin8"
8017
+ ],
8018
+ [
8019
+ "INVERTING_C_pin9"
8020
+ ],
8021
+ [
8022
+ "NONINVERTING_C_pin10"
8023
+ ],
8024
+ [
8025
+ "V_MINUS_pin11"
8026
+ ],
8027
+ [
8028
+ "NONINVERTING_D_pin12"
8029
+ ],
8030
+ [
8031
+ "INVERTING_D_pin13"
8032
+ ],
8033
+ [
8034
+ "OUT_D_pin14"
8035
+ ]
8036
+ ],
8037
+ "model": {
8038
+ "kind": "sections",
8039
+ "pinout": [
8040
+ "OUT_A",
8041
+ "IN_A_MINUS",
8042
+ "IN_A_PLUS",
8043
+ "VCC",
8044
+ "IN_B_PLUS",
8045
+ "IN_B_MINUS",
8046
+ "OUT_B",
8047
+ "OUT_C",
8048
+ "IN_C_MINUS",
8049
+ "IN_C_PLUS",
8050
+ "VEE",
8051
+ "IN_D_PLUS",
8052
+ "IN_D_MINUS",
8053
+ "OUT_D"
8054
+ ],
8055
+ "sections": [
8056
+ {
8057
+ "law": {
8058
+ "kind": "ideal-opamp",
8059
+ "railHigh": null,
8060
+ "railLow": null,
8061
+ "openLoopGain": 100000
8062
+ },
8063
+ "terminals": [
8064
+ 2,
8065
+ 1,
8066
+ 0
8067
+ ]
8068
+ },
8069
+ {
8070
+ "law": {
8071
+ "kind": "ideal-opamp",
8072
+ "railHigh": null,
8073
+ "railLow": null,
8074
+ "openLoopGain": 100000
8075
+ },
8076
+ "terminals": [
8077
+ 4,
8078
+ 5,
8079
+ 6
8080
+ ]
8081
+ },
8082
+ {
8083
+ "law": {
8084
+ "kind": "ideal-opamp",
8085
+ "railHigh": null,
8086
+ "railLow": null,
8087
+ "openLoopGain": 100000
8088
+ },
8089
+ "terminals": [
8090
+ 9,
8091
+ 8,
8092
+ 7
8093
+ ]
8094
+ },
8095
+ {
8096
+ "law": {
8097
+ "kind": "ideal-opamp",
8098
+ "railHigh": null,
8099
+ "railLow": null,
8100
+ "openLoopGain": 100000
8101
+ },
8102
+ "terminals": [
8103
+ 11,
8104
+ 12,
8105
+ 13
8106
+ ]
8107
+ }
8108
+ ]
8109
+ }
8110
+ },
8111
+ // National Semiconductor LF351 single JFET-input op-amp, from DS005648 Rev. 3 (1998):
8112
+ // AVOL 'Large Signal Voltage Gain, VS = +-15 V, TA = 25C, VO = +-10 V, RL = 2 kOhm:
8113
+ // MIN 25 TYP 100 V/mV'. Used as `openLoopGain: 100000`. LF351N is the DIP orderable
8114
+ // (NS Package N08E, sheet p11 'Order Number LF351M or LF351N'). Pinout: 'pin compatible
8115
+ // with the standard LM741' (single DIP-8: IN+ pin3, IN- pin2, OUT pin6), the same
8116
+ // three-terminal capture as the TL061 entry above.
8117
+ {
8118
+ "partIds": [
8119
+ "LF351",
8120
+ "LF351N"
8121
+ ],
8122
+ "declaredTypes": [],
8123
+ "terminalRoleGroups": [
8124
+ [
8125
+ "vin+",
8126
+ "in+",
8127
+ "pin3"
8128
+ ],
8129
+ [
8130
+ "vin-",
8131
+ "in-",
8132
+ "pin2"
8133
+ ],
8134
+ [
8135
+ "vout",
8136
+ "out",
8137
+ "pin6"
8138
+ ]
8139
+ ],
8140
+ "model": {
8141
+ "kind": "sections",
8142
+ "sections": [
8143
+ {
8144
+ "law": {
8145
+ "kind": "ideal-opamp",
8146
+ "railHigh": null,
8147
+ "railLow": null,
8148
+ "openLoopGain": 100000
8149
+ },
8150
+ "terminals": [
8151
+ 0,
8152
+ 1,
8153
+ 2
8154
+ ]
8155
+ }
8156
+ ],
8157
+ "pinout": [
8158
+ "vin+",
8159
+ "vin-",
8160
+ "vout"
8161
+ ]
8162
+ }
8163
+ },
8164
+ // SGS-THOMSON L272 dual power op-amp, from the L272 datasheet (Jan 1995):
8165
+ // ELECTRICAL CHARACTERISTICS (Vs = 24 V, Tamb = 25C): 'Gv | O. L. Voltage Gain |
8166
+ // f = 100 Hz | MIN 60 TYP 70 dB' (f = 1 kHz TYP 50 dB). Used as
8167
+ // `openLoopGain: decibelGain(70)` = 3162.2776601683795. Pinout: the sheet's PIN
8168
+ // CONNECTIONS are diagrams only (no text pin table could be extracted), so this entry
8169
+ // asserts NO pin order (neutral null pinout, M5218 precedent) and serves
8170
+ // section-grouped declarations; a packet study must verify the order before pin
8171
+ // positions are typed. One family table for L272/L272M/L272D.
8172
+ {
8173
+ "partIds": [
8174
+ "L272",
8175
+ "L272M",
8176
+ "L272D"
8177
+ ],
8178
+ "declaredTypes": [],
8179
+ "terminalRoleGroups": [],
8180
+ "model": {
8181
+ "kind": "sections",
8182
+ "sections": [
8183
+ {
8184
+ "law": {
8185
+ "kind": "ideal-opamp",
8186
+ "railHigh": null,
8187
+ "railLow": null,
8188
+ "openLoopGain": 3162.2776601683795
8189
+ },
8190
+ "terminals": [
8191
+ 2,
8192
+ 1,
8193
+ 0
8194
+ ]
8195
+ },
8196
+ {
8197
+ "law": {
8198
+ "kind": "ideal-opamp",
8199
+ "railHigh": null,
8200
+ "railLow": null,
8201
+ "openLoopGain": 3162.2776601683795
8202
+ },
8203
+ "terminals": [
8204
+ 4,
8205
+ 5,
8206
+ 6
8207
+ ]
8208
+ }
8209
+ ],
8210
+ "pinout": [
8211
+ null,
8212
+ null,
8213
+ null,
8214
+ null,
8215
+ null,
8216
+ null,
8217
+ null,
8218
+ null
8219
+ ]
8220
+ }
8221
+ },
8222
+ // TI TL074 quad JFET-input op-amp, from SLOS080W (Sep 1978, rev Jul 2025):
8223
+ // 5.8 DC (VS = +-15 V, TA = 25C, VO = 0 V): 'AOL | Open-loop voltage gain | TL07xC |
8224
+ // MIN 25 TYP 200 V/mV' (full-range row MIN-only 15; AC/BC 50/200, M 35/200, I MIN-only
8225
+ // 25 -- the I grade is not covered). Used as `openLoopGain: 200000`. TL074CN (PDIP-N)
8226
+ // and TL074CD (SOIC-D) are C-grade 'all other devices' for the 5.25 MHz GBW row.
8227
+ // Pinout: the sheet's own Table 4-6 Pin Functions TL074x (1 1OUT, 2 1IN-, 3 1IN+,
8228
+ // 4 VCC+, 5 2IN+, 6 2IN-, 7 2OUT, 8 3OUT, 9 3IN-, 10 3IN+, 11 VCC-, 12 4IN+,
8229
+ // 13 4IN-, 14 4OUT).
8230
+ {
8231
+ "partIds": [
8232
+ "TL074",
8233
+ "TL074CN",
8234
+ "TL074CD"
8235
+ ],
8236
+ "declaredTypes": [],
8237
+ "terminalRoleGroups": [
8238
+ [
8239
+ "OUT_A_pin1"
8240
+ ],
8241
+ [
8242
+ "INVERTING_A_pin2"
8243
+ ],
8244
+ [
8245
+ "NONINVERTING_A_pin3"
8246
+ ],
8247
+ [
8248
+ "V_PLUS_pin4"
8249
+ ],
8250
+ [
8251
+ "NONINVERTING_B_pin5"
8252
+ ],
8253
+ [
8254
+ "INVERTING_B_pin6"
8255
+ ],
8256
+ [
8257
+ "OUT_B_pin7"
8258
+ ],
8259
+ [
8260
+ "OUT_C_pin8"
8261
+ ],
8262
+ [
8263
+ "INVERTING_C_pin9"
8264
+ ],
8265
+ [
8266
+ "NONINVERTING_C_pin10"
8267
+ ],
8268
+ [
8269
+ "V_MINUS_pin11"
8270
+ ],
8271
+ [
8272
+ "NONINVERTING_D_pin12"
8273
+ ],
8274
+ [
8275
+ "INVERTING_D_pin13"
8276
+ ],
8277
+ [
8278
+ "OUT_D_pin14"
8279
+ ]
8280
+ ],
8281
+ "model": {
8282
+ "kind": "sections",
8283
+ "pinout": [
8284
+ "OUT_A",
8285
+ "IN_A_MINUS",
8286
+ "IN_A_PLUS",
8287
+ "VCC",
8288
+ "IN_B_PLUS",
8289
+ "IN_B_MINUS",
8290
+ "OUT_B",
8291
+ "OUT_C",
8292
+ "IN_C_MINUS",
8293
+ "IN_C_PLUS",
8294
+ "VEE",
8295
+ "IN_D_PLUS",
8296
+ "IN_D_MINUS",
8297
+ "OUT_D"
8298
+ ],
8299
+ "sections": [
8300
+ {
8301
+ "law": {
8302
+ "kind": "ideal-opamp",
8303
+ "railHigh": null,
8304
+ "railLow": null,
8305
+ "openLoopGain": 200000
8306
+ },
8307
+ "terminals": [
8308
+ 2,
8309
+ 1,
8310
+ 0
8311
+ ]
8312
+ },
8313
+ {
8314
+ "law": {
8315
+ "kind": "ideal-opamp",
8316
+ "railHigh": null,
8317
+ "railLow": null,
8318
+ "openLoopGain": 200000
8319
+ },
8320
+ "terminals": [
8321
+ 4,
8322
+ 5,
8323
+ 6
8324
+ ]
8325
+ },
8326
+ {
8327
+ "law": {
8328
+ "kind": "ideal-opamp",
8329
+ "railHigh": null,
8330
+ "railLow": null,
8331
+ "openLoopGain": 200000
8332
+ },
8333
+ "terminals": [
8334
+ 9,
8335
+ 8,
8336
+ 7
8337
+ ]
8338
+ },
8339
+ {
8340
+ "law": {
8341
+ "kind": "ideal-opamp",
8342
+ "railHigh": null,
8343
+ "railLow": null,
8344
+ "openLoopGain": 200000
8345
+ },
8346
+ "terminals": [
8347
+ 11,
8348
+ 12,
8349
+ 13
8350
+ ]
8351
+ }
8352
+ ]
8353
+ }
8354
+ },
8355
+ // Wave 2 (JED): TI CD4016B quad bilateral switch. Its own entry (NOT an alias of the
8356
+ // CD4066 entry above): the 4016 is the high-Ron predecessor and the 4066 its
8357
+ // pin-compatible low-Ron replacement (onsemi MC14066B: 'designed to be pin-for-pin
8358
+ // compatible with the MC14016B, but has much lower ON resistance'; ST HCF4066B: 'pin
8359
+ // for pin compatible with HCF4016B, but exhibits a much lower ON resistance').
8360
+ // On-resistance from TI CD4016B SCHS026D Rev. E (Aug 2024), Features p1: '280 Ohm
8361
+ // typical on-state resistance for 15 V operation' (switch matching 'within 10 Ohm typ
8362
+ // over 15 V signal-input range'). The 4.5 table's at-rail TYP at 15 V is 200 Ohm
8363
+ // (MAX 400; mid-supply worst-case MAX-only 850) -- the 280 headline is the over-range
8364
+ // figure and is the transcribed value. offOhms/thresholdVolts repeat the 4066 entry
8365
+ // (not newly cited). Only the ids the TI sheet owns: the unbuffered CD4016, HEF/HCF/MC
8366
+ // variants are not covered (Nexperia's HEF4016B specs a different test: 115 Ohm
8367
+ // peak-typ at 15 V, 100 uA, sharing no figure with this row).
8368
+ {
8369
+ "partIds": [
8370
+ "4016B",
8371
+ "CD4016B"
8372
+ ],
8373
+ "declaredTypes": [],
8374
+ "terminalRoleGroups": [
8375
+ [
8376
+ "switch_a",
8377
+ "in",
8378
+ "a",
8379
+ "sig_in",
8380
+ "inout1",
8381
+ "1in/out",
8382
+ "in/out"
8383
+ ],
8384
+ [
8385
+ "switch_b",
8386
+ "out",
8387
+ "b",
8388
+ "sig_out",
8389
+ "inout2",
8390
+ "1out/in",
8391
+ "out/in"
8392
+ ],
8393
+ [
8394
+ "control",
8395
+ "ctrl",
8396
+ "c",
8397
+ "gate",
8398
+ "ctl",
8399
+ "1control"
8400
+ ],
8401
+ [
8402
+ "vcc",
8403
+ "vdd",
8404
+ "vplus",
8405
+ "v+"
8406
+ ],
8407
+ [
8408
+ "vss",
8409
+ "gnd",
8410
+ "vminus",
8411
+ "v-",
8412
+ "ground"
8413
+ ]
8414
+ ],
8415
+ "model": {
8416
+ "kind": "sections",
8417
+ "sections": [
8418
+ {
8419
+ "law": {
8420
+ "kind": "analog-switch",
8421
+ "onOhms": 280,
8422
+ "offOhms": 1000000000,
8423
+ "thresholdVolts": 2
8424
+ },
8425
+ "terminals": [
8426
+ 0,
8427
+ 1,
8428
+ 2
8429
+ ]
8430
+ }
8431
+ ],
8432
+ "pinout": [
8433
+ "switch_a",
8434
+ "switch_b",
8435
+ "control",
8436
+ "vcc",
8437
+ "vss"
8438
+ ]
8439
+ }
8440
+ },
8441
+ // Wave 2 (JED): Cabintech CT3101 BBD clock generator. Its own entry mirroring the
8442
+ // MN3101 above (same 8-pin DIL footprint, same terminal order), NOT an unsourced alias:
8443
+ // the CT3101's own datasheet ('CT3101 BBD Clock Generator - Datasheet', Rev 1,
8444
+ // Aug 2023, Cabintech Global LLC, https://cabintechglobal.com/pdf/CABINTECH_CT3101.pdf)
8445
+ // states it 'is pin-for-pin compatible with the discontinued Panasonic MN3101' and 'can
8446
+ // be used as a drop-in replacement for the MN3101', with the pin table 1 GND, 2 CP1,
8447
+ // 3 Vdd (-3 V to -18 V), 4 CP2, 5 OX3, 6 OX2, 7 OX1, 8 Vgg (14/15 Vdd output).
8448
+ // No quiescentSupplyAmps: the sheet says only 'about 1/2 of MN3101' (no figure).
8449
+ // It is a hybrid module (CD4047-based), not a monolithic second source.
8450
+ {
8451
+ "partIds": [
8452
+ "CT3101"
8453
+ ],
8454
+ "declaredTypes": [],
8455
+ "terminalRoleGroups": [
8456
+ [
8457
+ "gnd",
8458
+ "v-",
8459
+ "vss",
8460
+ "pin1",
8461
+ "gndpin1"
8462
+ ],
8463
+ [
8464
+ "cp1",
8465
+ "clk1",
8466
+ "clock1",
8467
+ "phi1",
8468
+ "pin2",
8469
+ "cp1pin2"
8470
+ ],
8471
+ [
8472
+ "vdd",
8473
+ "v+",
8474
+ "vcc",
8475
+ "pin3",
8476
+ "vddpin3"
8477
+ ],
8478
+ [
8479
+ "cp2",
8480
+ "clk2",
8481
+ "clock2",
8482
+ "phi2",
8483
+ "pin4",
8484
+ "cp2pin4"
8485
+ ],
8486
+ [
8487
+ "ox3",
8488
+ "osc3",
8489
+ "oscctrl",
8490
+ "pin5",
8491
+ "ox3pin5"
8492
+ ],
8493
+ [
8494
+ "ox2",
8495
+ "osc2",
8496
+ "oscout",
8497
+ "pin6",
8498
+ "ox2pin6"
8499
+ ],
8500
+ [
8501
+ "ox1",
8502
+ "osc1",
8503
+ "oscin",
8504
+ "osc",
8505
+ "pin7",
8506
+ "ox1pin7"
8507
+ ],
8508
+ [
8509
+ "vgg",
8510
+ "vggout",
8511
+ "pin8",
8512
+ "vggoutpin8"
8513
+ ]
8514
+ ],
8515
+ "model": {
8516
+ "kind": "sections",
8517
+ "sections": [
8518
+ {
8519
+ "law": {
8520
+ "kind": "clock-driver",
8521
+ "defaultFrequency": 10000
8522
+ },
8523
+ "terminals": [
8524
+ 1,
8525
+ 3,
8526
+ 7,
8527
+ 2,
8528
+ 6,
8529
+ 0
8530
+ ]
8531
+ }
8532
+ ],
8533
+ "pinout": [
8534
+ null,
8535
+ null,
8536
+ null,
8537
+ null,
8538
+ null,
8539
+ null,
8540
+ null,
8541
+ null
8542
+ ]
8543
+ }
8544
+ },
8545
+ ...zenerRegistryRecords.map((record) => zenerLaw(record)),
8546
+ // Wave-1 discrete fill: bjt, diode-forward, mosfet and jfet-channel records become
8547
+ // law entries through the builders above -- one list per class, so the entries cannot
8548
+ // drift from the records. 2SK30A rides the same generic path as every other JFET: its
8549
+ // record carries only gateSaturationCurrent, so the builder gap-fills the class channel
8550
+ // and the stamp is byte-identical to the bespoke mapping this replaces.
8551
+ ...bjtRegistryRecords.map((record) => bjtLawEntry(record)),
8552
+ ...diodeRegistryRecords.map((record) => diodeLawEntry(record)),
8553
+ ...mosfetRegistryRecords.map((record) => fetLawEntry(record, "mosfet")),
8554
+ ...jfetRegistryRecords.map((record) => fetLawEntry(record, "jfet")),
8555
+ ];
8556
+ // --- Op-amp gain-bandwidth product: stamped from the data registry ------------------------------
8557
+ /**
8558
+ * Merge a stamped row's `community`-basis parameters into the entry's provisional arm.
8559
+ * A row whose parameters are all better-sourced adds nothing, and an entry with no
8560
+ * provisional value keeps the field absent rather than gaining an empty one.
8561
+ */
8562
+ function withRowProvisional(entry, row) {
8563
+ const merged = [...(entry.provisionalParams ?? [])];
8564
+ row.params.forEach((param, index) => {
8565
+ if (param?.basis !== "community" || typeof param.url !== "string")
8566
+ return;
8567
+ const name = row.names[index];
8568
+ if (!merged.some((held) => held.name === name)) {
8569
+ merged.push({ name, url: param.url });
8570
+ }
8571
+ });
8572
+ return merged.length === 0 ? {} : { provisionalParams: merged };
8573
+ }
8574
+ //
8575
+ // The figures live in `registry-data/opamp.ts` as cited values (datasheet TYP rows, quoted).
8576
+ // This pass stamps each record's product onto the op-amp sections of exactly the ids it
8577
+ // covers. An entry whose ids have different products (or none) is split into one entry per
8578
+ // product, keeping every other field, so no id changes behaviour except by gaining its own
8579
+ // figure.
8580
+ //
8581
+ // Loud on any mismatch, as before: a record or absent id the catalog holds no op-amp entry
8582
+ // for, an id in both lists, or an id in two records is a table error and throws at load
8583
+ // rather than being silently skipped.
8584
+ function withDatasheetGainBandwidth(base) {
8585
+ const rowOf = new Map();
8586
+ for (const row of opampRegistryRecords) {
8587
+ if (row.gainBandwidthHz === undefined)
8588
+ continue;
8589
+ for (const id of row.partIds) {
8590
+ const folded = foldPartId(id);
8591
+ if (rowOf.has(folded))
8592
+ throw new Error(`gain-bandwidth table names ${id} twice`);
8593
+ rowOf.set(folded, row);
8594
+ }
8595
+ }
8596
+ const absent = new Set();
8597
+ for (const { partIds } of opampGainBandwidthAbsent) {
8598
+ for (const id of partIds) {
8599
+ const folded = foldPartId(id);
8600
+ if (rowOf.has(folded))
8601
+ throw new Error(`${id} is both given a gain-bandwidth figure and listed absent`);
8602
+ absent.add(folded);
8603
+ }
8604
+ }
8605
+ const seen = new Set();
6874
8606
  const out = [];
6875
8607
  for (const entry of base) {
6876
8608
  const sections = entry.model.kind === "sections" ? entry.model.sections : null;
@@ -6896,13 +8628,21 @@ function withDatasheetGainBandwidth(base) {
6896
8628
  out.push(groups.size === 1 ? entry : { ...entry, partIds });
6897
8629
  continue;
6898
8630
  }
8631
+ const hertz = row.gainBandwidthHz?.value;
8632
+ if (hertz === undefined || !(hertz > 0)) {
8633
+ throw new Error(`${partIds.join(",")}: gain-bandwidth record states no positive product`);
8634
+ }
6899
8635
  out.push({
6900
8636
  ...entry,
6901
8637
  partIds,
8638
+ ...withRowProvisional(entry, {
8639
+ params: [row.gainBandwidthHz],
8640
+ names: ["gainBandwidthHz"],
8641
+ }),
6902
8642
  model: {
6903
8643
  ...model,
6904
8644
  sections: model.sections.map((section) => section.law.kind === "ideal-opamp"
6905
- ? { ...section, law: { ...section.law, gainBandwidthHz: row.hertz } }
8645
+ ? { ...section, law: { ...section.law, gainBandwidthHz: hertz } }
6906
8646
  : section),
6907
8647
  },
6908
8648
  });
@@ -6914,18 +8654,160 @@ function withDatasheetGainBandwidth(base) {
6914
8654
  }
6915
8655
  return out;
6916
8656
  }
8657
+ // --- Op-amp output swing: stamped from the data registry -------------------------------------------------
8658
+ //
8659
+ // The same rules as the gain-bandwidth pass above. A record states the drop from each rail at
8660
+ // the printed condition; `translation` is how that pair is carried to a pedal on another supply:
8661
+ // "constant" the printed drops are applied as printed (a headroom independent of the supply);
8662
+ // "proportional" both are multiplied by the op-amp's supply span over `supplyVolts`.
8663
+ // The datasheet states neither: it is the owner's reading, recorded per record, and the table does not
8664
+ // say it is shown right. Only the OP07 reading has a held-out hardware test behind it (T-a1, row 16),
8665
+ // and a test of a rule written first is not a measurement of the rule.
8666
+ /**
8667
+ * The catalog's op-amp entries with each registry record's swing pair stamped onto the sections
8668
+ * of exactly the ids it covers. An entry whose ids have different rows (or none) is split into
8669
+ * one entry per row, keeping every other field, so no id changes behaviour except by gaining
8670
+ * its own pair.
8671
+ *
8672
+ * Loud on any mismatch, as `withDatasheetGainBandwidth` is: a record or absent id the catalog
8673
+ * holds no op-amp entry for, an id in both lists, an id in two records, a base entry that
8674
+ * already carries a swing field (every figure comes through the registry), or a record whose
8675
+ * numbers do not validate (`opampOutputSwingOf`) throws at load.
8676
+ */
8677
+ export function withDatasheetOutputSwing(base, rows = opampRegistryRecords, absentRows = opampOutputSwingAbsent) {
8678
+ const rowOf = new Map();
8679
+ for (const row of rows) {
8680
+ const high = row.outputSwingDropHighVolts;
8681
+ const low = row.outputSwingDropLowVolts;
8682
+ const printed = row.outputSwingPrinted;
8683
+ if (high === undefined && low === undefined && printed === undefined)
8684
+ continue;
8685
+ if (high === undefined || low === undefined || printed === undefined) {
8686
+ 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"})`);
8687
+ }
8688
+ for (const id of row.partIds) {
8689
+ const folded = foldPartId(id);
8690
+ if (rowOf.has(folded))
8691
+ throw new Error(`output-swing table names ${id} twice`);
8692
+ rowOf.set(folded, row);
8693
+ }
8694
+ }
8695
+ const absent = new Set();
8696
+ for (const { partIds, reason } of absentRows) {
8697
+ if (reason.length === 0)
8698
+ throw new Error(`output-swing absent row for ${partIds.join(",")} states no reason`);
8699
+ for (const id of partIds) {
8700
+ const folded = foldPartId(id);
8701
+ if (rowOf.has(folded))
8702
+ throw new Error(`${id} is both given an output-swing pair and listed absent`);
8703
+ absent.add(folded);
8704
+ }
8705
+ }
8706
+ const seen = new Set();
8707
+ const out = [];
8708
+ for (const entry of base) {
8709
+ const sections = entry.model.kind === "sections" ? entry.model.sections : null;
8710
+ const isOpamp = sections !== null && sections.length > 0 && sections.every((section) => section.law.kind === "ideal-opamp");
8711
+ if (!isOpamp || sections === null || entry.model.kind !== "sections") {
8712
+ out.push(entry);
8713
+ continue;
8714
+ }
8715
+ for (const section of sections) {
8716
+ const law = section.law;
8717
+ if (law.kind === "ideal-opamp" &&
8718
+ (law.outputSwingDropHighVolts !== undefined ||
8719
+ law.outputSwingDropLowVolts !== undefined ||
8720
+ law.outputSwingPrinted !== undefined)) {
8721
+ throw new Error(`${entry.partIds.join(",")}: an op-amp entry states an output swing in place; it must come through the registry`);
8722
+ }
8723
+ }
8724
+ const groups = new Map();
8725
+ for (const id of entry.partIds) {
8726
+ const folded = foldPartId(id);
8727
+ seen.add(folded);
8728
+ const row = rowOf.get(folded) ?? null;
8729
+ const group = groups.get(row);
8730
+ if (group === undefined)
8731
+ groups.set(row, [id]);
8732
+ else
8733
+ group.push(id);
8734
+ }
8735
+ const model = entry.model;
8736
+ for (const [row, partIds] of groups) {
8737
+ if (row === null) {
8738
+ out.push(groups.size === 1 ? entry : { ...entry, partIds });
8739
+ continue;
8740
+ }
8741
+ const high = row.outputSwingDropHighVolts?.value;
8742
+ const low = row.outputSwingDropLowVolts?.value;
8743
+ const printed = row.outputSwingPrinted;
8744
+ if (high === undefined || low === undefined || printed === undefined) {
8745
+ throw new Error(`${partIds.join(",")}: an op-amp swing record states a half pair`);
8746
+ }
8747
+ const stamped = {
8748
+ ...entry,
8749
+ partIds,
8750
+ ...withRowProvisional(entry, {
8751
+ params: [row.outputSwingDropHighVolts, row.outputSwingDropLowVolts],
8752
+ names: ["outputSwingDropHighVolts", "outputSwingDropLowVolts"],
8753
+ }),
8754
+ model: {
8755
+ ...model,
8756
+ sections: model.sections.map((section) => section.law.kind === "ideal-opamp"
8757
+ ? {
8758
+ ...section,
8759
+ law: {
8760
+ ...section.law,
8761
+ outputSwingDropHighVolts: high,
8762
+ outputSwingDropLowVolts: low,
8763
+ outputSwingPrinted: {
8764
+ supplyVolts: printed.supplyVolts,
8765
+ loadOhms: printed.loadOhms,
8766
+ translation: printed.translation,
8767
+ },
8768
+ },
8769
+ }
8770
+ : section),
8771
+ },
8772
+ };
8773
+ // A malformed record (a non-positive figure, an unknown translation) throws here, naming the ids.
8774
+ for (const section of stamped.model.kind === "sections" ? stamped.model.sections : []) {
8775
+ if (section.law.kind === "ideal-opamp")
8776
+ opampOutputSwingOf(section.law, partIds.join(","));
8777
+ }
8778
+ out.push(stamped);
8779
+ }
8780
+ }
8781
+ for (const folded of [...rowOf.keys(), ...absent]) {
8782
+ if (!seen.has(folded))
8783
+ throw new Error(`output-swing table names ${folded}, which no op-amp entry holds`);
8784
+ }
8785
+ return out;
8786
+ }
6917
8787
  /**
6918
8788
  * 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.
8789
+ * Derived from the jfet registry records that state NO channel parameter (a record that
8790
+ * states thresholdVolts or transconductance carries a full channel and is not gate-only):
8791
+ * such an entry keeps its avowed class channel, which is never graded against the database's
8792
+ * beta/lambda/Vp the way a transcribed channel would be. Mirrors `zenerCatalogPartIds`.
6923
8793
  */
6924
- export const gateOnlyFetPartIds = new Set(["2sk30a"]);
8794
+ const GATE_ONLY_EXCLUDED_PARAMS = new Set([
8795
+ "thresholdVolts",
8796
+ "transconductance",
8797
+ "channelLengthModulation",
8798
+ "subthresholdVolts",
8799
+ ]);
8800
+ export const gateOnlyFetPartIds = new Set(jfetRegistryRecords
8801
+ .filter((record) => Object.keys(record.params).every((name) => !GATE_ONLY_EXCLUDED_PARAMS.has(name)))
8802
+ .flatMap((record) => [
8803
+ ...record.partIds,
8804
+ ...(record.aliases ?? []),
8805
+ ])
8806
+ .map((id) => foldPartId(id)));
6925
8807
  /**
6926
8808
  * The catalog the corpus-facing scripts inject.
6927
8809
  */
6928
8810
  export const pedalPartCatalog = {
6929
- entries: withDatasheetGainBandwidth(entries),
8811
+ entries: withDatasheetOutputSwing(withDatasheetGainBandwidth(entries)),
6930
8812
  };
6931
8813
  //# sourceMappingURL=part-catalog.js.map