@vessel-dsp/compiler 0.5.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (88) hide show
  1. package/README.md +4 -2
  2. package/dist/bbd-clock.d.ts +42 -0
  3. package/dist/bbd-clock.d.ts.map +1 -1
  4. package/dist/bbd-clock.js +383 -1
  5. package/dist/bbd-clock.js.map +1 -1
  6. package/dist/compile.d.ts.map +1 -1
  7. package/dist/compile.js +316 -34
  8. package/dist/compile.js.map +1 -1
  9. package/dist/device-laws.d.ts.map +1 -1
  10. package/dist/device-laws.js +581 -98
  11. package/dist/device-laws.js.map +1 -1
  12. package/dist/fixtures/circuits.d.ts +23 -2
  13. package/dist/fixtures/circuits.d.ts.map +1 -1
  14. package/dist/fixtures/circuits.js +205 -2
  15. package/dist/fixtures/circuits.js.map +1 -1
  16. package/dist/index.d.ts +2 -1
  17. package/dist/index.d.ts.map +1 -1
  18. package/dist/index.js +1 -0
  19. package/dist/index.js.map +1 -1
  20. package/dist/lower.d.ts.map +1 -1
  21. package/dist/lower.js +13 -0
  22. package/dist/lower.js.map +1 -1
  23. package/dist/netlist.d.ts +30 -0
  24. package/dist/netlist.d.ts.map +1 -1
  25. package/dist/netlist.js +502 -8
  26. package/dist/netlist.js.map +1 -1
  27. package/dist/part-catalog.d.ts +42 -18
  28. package/dist/part-catalog.d.ts.map +1 -1
  29. package/dist/part-catalog.js +2518 -576
  30. package/dist/part-catalog.js.map +1 -1
  31. package/dist/partition.d.ts.map +1 -1
  32. package/dist/partition.js +6 -0
  33. package/dist/partition.js.map +1 -1
  34. package/dist/registry-data/bjt.d.ts +3 -0
  35. package/dist/registry-data/bjt.d.ts.map +1 -0
  36. package/dist/registry-data/bjt.js +1350 -0
  37. package/dist/registry-data/bjt.js.map +1 -0
  38. package/dist/registry-data/charge-pump.d.ts +3 -0
  39. package/dist/registry-data/charge-pump.d.ts.map +1 -0
  40. package/dist/registry-data/charge-pump.js +204 -0
  41. package/dist/registry-data/charge-pump.js.map +1 -0
  42. package/dist/registry-data/diode.d.ts +3 -0
  43. package/dist/registry-data/diode.d.ts.map +1 -0
  44. package/dist/registry-data/diode.js +786 -0
  45. package/dist/registry-data/diode.js.map +1 -0
  46. package/dist/registry-data/index.d.ts +21 -0
  47. package/dist/registry-data/index.d.ts.map +1 -0
  48. package/dist/registry-data/index.js +103 -0
  49. package/dist/registry-data/index.js.map +1 -0
  50. package/dist/registry-data/jfet.d.ts +3 -0
  51. package/dist/registry-data/jfet.d.ts.map +1 -0
  52. package/dist/registry-data/jfet.js +605 -0
  53. package/dist/registry-data/jfet.js.map +1 -0
  54. package/dist/registry-data/loader.d.ts +137 -0
  55. package/dist/registry-data/loader.d.ts.map +1 -0
  56. package/dist/registry-data/loader.js +350 -0
  57. package/dist/registry-data/loader.js.map +1 -0
  58. package/dist/registry-data/mosfet.d.ts +3 -0
  59. package/dist/registry-data/mosfet.d.ts.map +1 -0
  60. package/dist/registry-data/mosfet.js +63 -0
  61. package/dist/registry-data/mosfet.js.map +1 -0
  62. package/dist/registry-data/opamp.d.ts +37 -0
  63. package/dist/registry-data/opamp.d.ts.map +1 -0
  64. package/dist/registry-data/opamp.js +669 -0
  65. package/dist/registry-data/opamp.js.map +1 -0
  66. package/dist/registry-data/regulator.d.ts +3 -0
  67. package/dist/registry-data/regulator.d.ts.map +1 -0
  68. package/dist/registry-data/regulator.js +189 -0
  69. package/dist/registry-data/regulator.js.map +1 -0
  70. package/dist/registry-data/schema.d.ts +60 -0
  71. package/dist/registry-data/schema.d.ts.map +1 -0
  72. package/dist/registry-data/schema.js +151 -0
  73. package/dist/registry-data/schema.js.map +1 -0
  74. package/dist/registry-data/ujt.d.ts +3 -0
  75. package/dist/registry-data/ujt.d.ts.map +1 -0
  76. package/dist/registry-data/ujt.js +48 -0
  77. package/dist/registry-data/ujt.js.map +1 -0
  78. package/dist/registry-data/zener.d.ts +3 -0
  79. package/dist/registry-data/zener.d.ts.map +1 -0
  80. package/dist/registry-data/zener.js +547 -0
  81. package/dist/registry-data/zener.js.map +1 -0
  82. package/dist/registry.d.ts +93 -6
  83. package/dist/registry.d.ts.map +1 -1
  84. package/dist/registry.js +35 -7
  85. package/dist/registry.js.map +1 -1
  86. package/dist/types.d.ts +242 -19
  87. package/dist/types.d.ts.map +1 -1
  88. package/package.json +1 -1
package/README.md CHANGED
@@ -4,7 +4,7 @@ Headless circuit compiler turning `.vdsp` / `CircuitDocument` schematic data int
4
4
 
5
5
  ## Install
6
6
 
7
- `@vessel-dsp/compiler` 0.5.0 is on npm:
7
+ `@vessel-dsp/compiler` 0.7.0 is on npm:
8
8
 
9
9
  ```bash
10
10
  bun add @vessel-dsp/compiler
@@ -103,6 +103,8 @@ Values on `.`:
103
103
  | `parseQuantity` | Parse a quantity to SI base units (`10k` to 10000). |
104
104
  | `emptyRegistry` | No part knowledge; measures what device class laws alone cover. |
105
105
  | `pedalPartCatalog` | The catalog corpus facing scripts inject. |
106
+ | `partRegistryData` | Stage-1 data registry: every migrated parameter as a cited `{ value, basis, source }`, validated at load. |
107
+ | `findPartRegistryData` | Find a stage-1 data entry by exact folded part id or alias. |
106
108
  | `foldPartId` | Fold case and separators (`MN-3007` is `mn3007`). |
107
109
  | `foldToken` | Fold case and whitespace for typed vocabulary values. |
108
110
  | `registryEntryFor` | Entry lookup for a device. |
@@ -119,4 +121,4 @@ Types on `.` (import with `import type`): `CompileOptions`, `CompileResult`, `Co
119
121
 
120
122
  `./diagnostics` (report-only reach into stages for instruments, not consumer API): `blockRow`, `describeRow`, `nodeAt`, `stampNodes`, `stampInSourceNodes`, `perSample`, `SILICON_FORWARD_BETA`, `SILICON_SATURATION_CURRENT`, `THERMAL_VOLTAGE`, `findBistableLatches`, `OPEN_RULES`, `connectedToBothPorts`, `isPacketDisconnected`, `outputDependence`, `validateProxy`, `recordedReads`, `withRecordedReads`, `clearRecordedReads`, `speakerImpedance`, `speakerImpedanceMagnitude`, `speakerOnePort`, `clockTimingNodes`, `deriveBbdDelayFromNetlist`, `findClockDriverDevice`, plus the accompanying types (`PerSample`, `BistableLatch`, `NetGraph`, `FirmwareClass`, and the violation types).
121
123
 
122
- `./fixtures` (248 exports: synthetic circuits, hand-computed expectations, and their registries): `resistorDivider`, `rcLowPass`, `diodeClipper`, `invertingAmplifier`, `potDivider`, `commonEmitterAmplifier`, `emitterFollower`, `triodeGainStage`, `unknownChip`, `dividerGain`, `potGainAt`, `fixtureRegistry`, and the rest. These are the only circuits whose answers are known independently of the instruments, which is what makes them shippable.
124
+ `./fixtures` (251 exports: synthetic circuits, hand-computed expectations, and their registries): `resistorDivider`, `rcLowPass`, `diodeClipper`, `invertingAmplifier`, `potDivider`, `commonEmitterAmplifier`, `emitterFollower`, `triodeGainStage`, `unknownChip`, `dividerGain`, `potGainAt`, `fixtureRegistry`, and the rest. These are the only circuits whose answers are known independently of the instruments, which is what makes them shippable.
@@ -138,6 +138,48 @@ export type OpenOx2ClockLawOutcome = {
138
138
  * form (a different oscillator; no corpus case) all read absent here.
139
139
  */
140
140
  export declare function deriveOpenOx2ClockLaw(netlist: Netlist, registry: PartRegistry): OpenOx2ClockLawOutcome;
141
+ /**
142
+ * Derive a BBD delay from a UJT relaxation-oscillator clock network through a
143
+ * divide-by-two flip-flop.
144
+ *
145
+ * The recognised form, every condition a whole-value comparison on typed fields
146
+ * plus connectivity (no names, no prose, no patterns):
147
+ *
148
+ * - one `kind: bjt` device whose declared type is `Circuit.UnijunctionTransistor`
149
+ * and whose part resolves to a `ujt` registry law (the eta the formula reads);
150
+ * its `emitter`/`base1`/`base2` terminal roles must each resolve to exactly one
151
+ * node, with base1 on ground;
152
+ * - the emitter charges from a supply rail through one or more two-terminal
153
+ * resistors, optionally plus one switch-selected branch: a three-terminal
154
+ * `kind: switch` with declared `common`/`throw`/`throw` roles whose common joins
155
+ * the switched resistor, one throw sits on the rail and the other does not;
156
+ * - exactly one timing capacitor (under 1 uF, the MN310x bound) from the emitter
157
+ * to a node that is AC ground: tied to ground through resistors totalling at
158
+ * most 1% of the smallest charging resistance (the corpus form returns through
159
+ * 68 ohms against 25 kohm), with nothing on it but the timing capacitor,
160
+ * ground-return resistors and capacitors;
161
+ * - a 4013-family divider (`UJT_DIVIDER_PART_IDS`) with one half toggle-wired
162
+ * (`data` and `not-q` on one node) and that half's `q`/`not-q` on the BBD's own
163
+ * clock nodes;
164
+ * - exactly one buffer transistor: a `bjt` (not itself a UJT) with its collector
165
+ * on the divider's clock node, its emitter on the rail, its base coupled to the
166
+ * timing capacitor's far end through exactly one capacitor, and no other active
167
+ * device on the clock or base nodes (resistor biasing is allowed).
168
+ *
169
+ * The frequency is the ideal relaxation law f = 1/(R*C*ln(1/(1-eta))) with the
170
+ * netlist's R and C and the registry's eta; the divider halves it, so the BBD sees
171
+ * f/2 and delay = stages/f_UJT. The ideal law charges 0 to Vp rather than valley
172
+ * to Vp (VF is printed, the valley voltage is graph-only), which overstates the
173
+ * period by about 6% -- second order against the printed eta spread, and stated
174
+ * rather than corrected. A switch with a control sweeps both positions through
175
+ * `clockControl` (min/max resistances, the control's own taper, nominal at the
176
+ * default position); a switch without one, or no switch at all, derives the
177
+ * single selected resistance statically. Anything else about the form -- a second
178
+ * UJT, an unregistered UJT part, an eta outside (0, 1), an unknown charging
179
+ * branch, a missing or miswired divider, buffer or timing capacitor -- is
180
+ * `no-clock-driver`, and the caller falls through to the gate diagnosis.
181
+ */
182
+ export declare function deriveUjtRelaxationClock(bbdDevice: Device, netlist: Netlist, stages: number, registry: PartRegistry): BbdClockDerivationOutcome;
141
183
  /**
142
184
  * Princeton Technology PT2399 delay time from external VCO resistance R (in Ohms).
143
185
  *
@@ -1 +1 @@
1
- {"version":3,"file":"bbd-clock.d.ts","sourceRoot":"","sources":["../src/bbd-clock.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"bbd-clock.d.ts","sourceRoot":"","sources":["../src/bbd-clock.ts"],"names":[],"mappings":"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"}
package/dist/bbd-clock.js CHANGED
@@ -16,7 +16,7 @@
16
16
  // - ambiguous multi-path timing networks across different oscillator node pairs
17
17
  // - unconnected oscillator pins
18
18
  import { GROUND } from "./types.js";
19
- import { foldToken, registryEntryFor } from "./registry.js";
19
+ import { foldToken, registryEntryFor, registryLawFor } from "./registry.js";
20
20
  const CD4047_PART_IDS = new Set([
21
21
  "cd4047",
22
22
  "cd4047b",
@@ -34,6 +34,11 @@ const MN3101_PART_IDS = new Set([
34
34
  "upd3101",
35
35
  "upd3101c",
36
36
  "upd3101d",
37
+ // Wave 2 (JED): Cabintech CT3101. Its own datasheet ('CT3101 BBD Clock Generator',
38
+ // Rev 1, Aug 2023) states it 'is pin-for-pin compatible with the discontinued
39
+ // Panasonic MN3101' and 'can be used as a drop-in replacement for the MN3101', so it
40
+ // joins the MN3101 clock family -- a sourced equivalence, not an assumed alias.
41
+ "ct3101",
37
42
  ]);
38
43
  const MN3102_PART_IDS = new Set([
39
44
  "mn3102",
@@ -988,6 +993,15 @@ function implausibleClock(fClockHz, stages) {
988
993
  export function deriveBbdDelayFromNetlist(bbdDevice, netlist, stages, registry) {
989
994
  const clockDev = findClockDriverDevice(netlist);
990
995
  if (!clockDev) {
996
+ // A UJT relaxation oscillator through a divide-by-two into the brigade is a
997
+ // dedicated timing network with a printed formula of its own (section below),
998
+ // so it is tried before the generic gate-oscillator diagnosis. A mere divider
999
+ // spelling on the clock pins is not enough: without the UJT form this falls
1000
+ // through to the gate branch, where the buffer transistor reads as steering.
1001
+ const ujt = deriveUjtRelaxationClock(bbdDevice, netlist, stages, registry);
1002
+ if (ujt.outcome === "derived" || ujt.outcome === "refused") {
1003
+ return ujt;
1004
+ }
991
1005
  // No dedicated CD4047/MN310x. Before reporting that nothing clocks this brigade -- which
992
1006
  // on two corpus packets is simply false -- look for a logic part on its clock pins.
993
1007
  const gate = findGateClockSource(bbdDevice, netlist);
@@ -1574,6 +1588,374 @@ export function deriveOpenOx2ClockLaw(netlist, registry) {
1574
1588
  cFarads,
1575
1589
  };
1576
1590
  }
1591
+ /**
1592
+ * Dual D-type flip-flop parts that divide a clock by two, by exact part id.
1593
+ *
1594
+ * A closed vocabulary matched as whole folded values, like `GATE_CLOCK_PART_IDS`
1595
+ * above, but narrower: only the 4013 family divides, while the 14069/4011/4043
1596
+ * members of that set oscillate or gate and must never reach the toggle check below.
1597
+ * `scl4013a` is the Solid State Scientific spelling the corpus's UJT-clocked
1598
+ * brigade declares; it rides the CD4013AE registry entry on core main, which this
1599
+ * module never consults (it matches folded part ids, not aliases), so it is listed
1600
+ * here literally.
1601
+ */
1602
+ const UJT_DIVIDER_PART_IDS = new Set([
1603
+ "cd4013",
1604
+ "cd4013a",
1605
+ "cd4013ae",
1606
+ "cd4013b",
1607
+ "cd4013be",
1608
+ "hef4013",
1609
+ "hcf4013",
1610
+ "mc14013",
1611
+ "mc14013b",
1612
+ "scl4013a",
1613
+ ]);
1614
+ /** The source's own closed type for a unijunction transistor, folded for comparison. */
1615
+ const UJT_DECLARED_TYPE = "circuit.unijunctiontransistor";
1616
+ /**
1617
+ * Derive a BBD delay from a UJT relaxation-oscillator clock network through a
1618
+ * divide-by-two flip-flop.
1619
+ *
1620
+ * The recognised form, every condition a whole-value comparison on typed fields
1621
+ * plus connectivity (no names, no prose, no patterns):
1622
+ *
1623
+ * - one `kind: bjt` device whose declared type is `Circuit.UnijunctionTransistor`
1624
+ * and whose part resolves to a `ujt` registry law (the eta the formula reads);
1625
+ * its `emitter`/`base1`/`base2` terminal roles must each resolve to exactly one
1626
+ * node, with base1 on ground;
1627
+ * - the emitter charges from a supply rail through one or more two-terminal
1628
+ * resistors, optionally plus one switch-selected branch: a three-terminal
1629
+ * `kind: switch` with declared `common`/`throw`/`throw` roles whose common joins
1630
+ * the switched resistor, one throw sits on the rail and the other does not;
1631
+ * - exactly one timing capacitor (under 1 uF, the MN310x bound) from the emitter
1632
+ * to a node that is AC ground: tied to ground through resistors totalling at
1633
+ * most 1% of the smallest charging resistance (the corpus form returns through
1634
+ * 68 ohms against 25 kohm), with nothing on it but the timing capacitor,
1635
+ * ground-return resistors and capacitors;
1636
+ * - a 4013-family divider (`UJT_DIVIDER_PART_IDS`) with one half toggle-wired
1637
+ * (`data` and `not-q` on one node) and that half's `q`/`not-q` on the BBD's own
1638
+ * clock nodes;
1639
+ * - exactly one buffer transistor: a `bjt` (not itself a UJT) with its collector
1640
+ * on the divider's clock node, its emitter on the rail, its base coupled to the
1641
+ * timing capacitor's far end through exactly one capacitor, and no other active
1642
+ * device on the clock or base nodes (resistor biasing is allowed).
1643
+ *
1644
+ * The frequency is the ideal relaxation law f = 1/(R*C*ln(1/(1-eta))) with the
1645
+ * netlist's R and C and the registry's eta; the divider halves it, so the BBD sees
1646
+ * f/2 and delay = stages/f_UJT. The ideal law charges 0 to Vp rather than valley
1647
+ * to Vp (VF is printed, the valley voltage is graph-only), which overstates the
1648
+ * period by about 6% -- second order against the printed eta spread, and stated
1649
+ * rather than corrected. A switch with a control sweeps both positions through
1650
+ * `clockControl` (min/max resistances, the control's own taper, nominal at the
1651
+ * default position); a switch without one, or no switch at all, derives the
1652
+ * single selected resistance statically. Anything else about the form -- a second
1653
+ * UJT, an unregistered UJT part, an eta outside (0, 1), an unknown charging
1654
+ * branch, a missing or miswired divider, buffer or timing capacitor -- is
1655
+ * `no-clock-driver`, and the caller falls through to the gate diagnosis.
1656
+ */
1657
+ export function deriveUjtRelaxationClock(bbdDevice, netlist, stages, registry) {
1658
+ const clockNodes = bbdClockNodes(bbdDevice);
1659
+ if (clockNodes.size === 0) {
1660
+ return { outcome: "no-clock-driver" };
1661
+ }
1662
+ const ujts = netlist.devices.filter((device) => device.kind === "bjt" &&
1663
+ foldToken(device.identity.declaredType ?? "") === UJT_DECLARED_TYPE);
1664
+ if (ujts.length !== 1) {
1665
+ return { outcome: "no-clock-driver" };
1666
+ }
1667
+ const ujt = ujts[0];
1668
+ const emitter = terminalNodeByRole(ujt, "emitter");
1669
+ const base1 = terminalNodeByRole(ujt, "base1");
1670
+ const base2 = terminalNodeByRole(ujt, "base2");
1671
+ if (emitter === null ||
1672
+ base1 === null ||
1673
+ base2 === null ||
1674
+ emitter === base1 ||
1675
+ emitter === base2 ||
1676
+ base1 === base2) {
1677
+ return { outcome: "no-clock-driver" };
1678
+ }
1679
+ if (base1 !== GROUND) {
1680
+ return { outcome: "no-clock-driver" };
1681
+ }
1682
+ const ujtLaw = registryLawFor(registry, ujt.identity.partNumber, "ujt");
1683
+ if (ujtLaw === null || ujtLaw.kind !== "ujt") {
1684
+ return { outcome: "no-clock-driver" };
1685
+ }
1686
+ const eta = ujtLaw.intrinsicStandoffRatio;
1687
+ if (!(eta > 0) || !(eta < 1) || !Number.isFinite(eta)) {
1688
+ return {
1689
+ outcome: "refused",
1690
+ reason: `${ujt.id} (${ujt.identity.partNumber ?? "unmarked UJT"}) states ` +
1691
+ `intrinsicStandoffRatio ${eta}, outside (0, 1), so ln(1/(1-eta)) is not a frequency`,
1692
+ };
1693
+ }
1694
+ // The rail is whatever a supply-kind device asserts: kinds, never names.
1695
+ const railNodes = new Set();
1696
+ for (const device of netlist.devices) {
1697
+ if (device.kind !== "voltage-source" && device.kind !== "rail") {
1698
+ continue;
1699
+ }
1700
+ for (const node of device.nodes) {
1701
+ if (node !== GROUND) {
1702
+ railNodes.add(node);
1703
+ }
1704
+ }
1705
+ }
1706
+ // Charging branches: two-terminal resistors with exactly one end on the emitter.
1707
+ const charging = netlist.devices.filter((device) => device.kind === "resistor" &&
1708
+ device.nodes.length === 2 &&
1709
+ (device.nodes[0] === emitter) !== (device.nodes[1] === emitter) &&
1710
+ (device.parameters.ohms ?? 0) > 0);
1711
+ if (charging.length === 0) {
1712
+ return { outcome: "no-clock-driver" };
1713
+ }
1714
+ const farEnd = (device) => (device.nodes[0] === emitter ? device.nodes[1] : device.nodes[0]);
1715
+ // Selector candidates: three-terminal switches with exactly one `common` and
1716
+ // two `throw`s by declared role. The footswitch and every other unrelated
1717
+ // switch in the document matches that shape, so the one that counts is the
1718
+ // switch whose common joins a charging branch -- throws in node order (the
1719
+ // same order `switchPoles` and both selector evaluations use: position p
1720
+ // shorts the common to throws[p]).
1721
+ const candidates = netlist.devices.filter((device) => {
1722
+ if (device.kind !== "switch" || device.nodes.length !== 3) {
1723
+ return false;
1724
+ }
1725
+ const declared = device.identity.declaredTerminalRoles;
1726
+ return (declared.length === 3 &&
1727
+ declared.filter((role) => role === "common").length === 1 &&
1728
+ declared.filter((role) => role === "throw").length === 2);
1729
+ });
1730
+ const chargingFarEnds = new Set(charging.map((device) => (device.nodes[0] === emitter ? device.nodes[1] : device.nodes[0])));
1731
+ const linked = candidates.filter((device) => {
1732
+ const commonIndex = device.identity.declaredTerminalRoles.findIndex((role) => role === "common");
1733
+ return chargingFarEnds.has(device.nodes[commonIndex]);
1734
+ });
1735
+ if (linked.length > 1) {
1736
+ return { outcome: "no-clock-driver" };
1737
+ }
1738
+ const selector = linked[0] ?? null;
1739
+ let commonNode = null;
1740
+ let throwNodes = [];
1741
+ if (selector !== null) {
1742
+ const declared = selector.identity.declaredTerminalRoles;
1743
+ const commonIndex = declared.findIndex((role) => role === "common");
1744
+ commonNode = selector.nodes[commonIndex];
1745
+ throwNodes = selector.nodes.filter((_, index) => index !== commonIndex);
1746
+ if (nodeDegree(netlist, commonNode) !== 2) {
1747
+ return { outcome: "no-clock-driver" };
1748
+ }
1749
+ }
1750
+ const fixed = charging.filter((device) => railNodes.has(farEnd(device)));
1751
+ const switched = charging.filter((device) => commonNode !== null && farEnd(device) === commonNode);
1752
+ const unknown = charging.filter((device) => !fixed.includes(device) && !switched.includes(device));
1753
+ if (fixed.length === 0 || unknown.length > 0) {
1754
+ return { outcome: "no-clock-driver" };
1755
+ }
1756
+ if (switched.length > 1) {
1757
+ return { outcome: "no-clock-driver" };
1758
+ }
1759
+ let railThrow = null;
1760
+ if (selector !== null) {
1761
+ if (switched.length !== 1) {
1762
+ return { outcome: "no-clock-driver" };
1763
+ }
1764
+ const onRail = throwNodes.filter((node) => railNodes.has(node));
1765
+ const offRail = throwNodes.filter((node) => !railNodes.has(node));
1766
+ // Exactly one throw on the rail and the other elsewhere: a selector that
1767
+ // chooses between two feeds is a different circuit.
1768
+ if (onRail.length !== 1 || offRail.length !== 1) {
1769
+ return { outcome: "no-clock-driver" };
1770
+ }
1771
+ if (offRail[0] === emitter || offRail[0] === commonNode) {
1772
+ return { outcome: "no-clock-driver" };
1773
+ }
1774
+ railThrow = onRail[0];
1775
+ }
1776
+ const conductance = (devices) => devices.reduce((sum, device) => sum + 1 / (device.parameters.ohms ?? 0), 0);
1777
+ const fixedConductance = conductance(fixed);
1778
+ const switchedConductance = conductance(switched);
1779
+ const resistanceAt = (position) => {
1780
+ const selected = throwNodes[position];
1781
+ const extra = selected !== undefined && selected === railThrow ? switchedConductance : 0;
1782
+ return 1 / (fixedConductance + extra);
1783
+ };
1784
+ // The timing capacitor: exactly one sub-microfarad capacitor off the emitter.
1785
+ const timingCaps = netlist.devices.filter((device) => device.kind === "capacitor" &&
1786
+ device.nodes.length === 2 &&
1787
+ (device.nodes[0] === emitter) !== (device.nodes[1] === emitter) &&
1788
+ (device.parameters.farads ?? 0) > 0 &&
1789
+ (device.parameters.farads ?? 0) < 1e-6);
1790
+ if (timingCaps.length !== 1) {
1791
+ return { outcome: "no-clock-driver" };
1792
+ }
1793
+ const cFarads = timingCaps[0].parameters.farads ?? 0;
1794
+ const capFar = farEnd(timingCaps[0]);
1795
+ if (capFar === GROUND || capFar === emitter) {
1796
+ // A grounded timing capacitor takes its output off B1 instead: a second
1797
+ // form, not implemented. (The corpus form returns through 68 ohms.)
1798
+ return { outcome: "no-clock-driver" };
1799
+ }
1800
+ // AC ground: the far end returns to ground through resistors totalling at most
1801
+ // 1% of the smallest charging resistance, and carries nothing but the timing
1802
+ // capacitor, those returns and capacitors. The 1% is the negligibility bound:
1803
+ // the charge current divides between R_charge and the return, so the rate
1804
+ // error from ignoring the return is under a percent by construction.
1805
+ const returnConductance = netlist.devices
1806
+ .filter((device) => device.kind === "resistor" && isTwoTerminalBetween(device, capFar, GROUND))
1807
+ .reduce((sum, device) => sum + 1 / (device.parameters.ohms ?? 0), 0);
1808
+ const resistances = throwNodes.length > 0
1809
+ ? throwNodes.map((_, index) => resistanceAt(index))
1810
+ : [1 / fixedConductance];
1811
+ const minResistance = Math.min(...resistances);
1812
+ const returnOhms = returnConductance > 0 ? 1 / returnConductance : Infinity;
1813
+ if (!(returnOhms <= 0.01 * minResistance)) {
1814
+ return { outcome: "no-clock-driver" };
1815
+ }
1816
+ const strangers = netlist.devices.filter((device) => device !== timingCaps[0] &&
1817
+ device.nodes.includes(capFar) &&
1818
+ !(device.kind === "resistor" ||
1819
+ device.kind === "capacitor"));
1820
+ if (strangers.length > 0) {
1821
+ return { outcome: "no-clock-driver" };
1822
+ }
1823
+ // The divider: a 4013-family part with one half toggle-wired onto the BBD clocks.
1824
+ let dividerClock = null;
1825
+ for (const divider of netlist.devices) {
1826
+ if (divider.kind !== "ic" ||
1827
+ !UJT_DIVIDER_PART_IDS.has(foldToken(divider.identity.partNumber ?? ""))) {
1828
+ continue;
1829
+ }
1830
+ for (const half of [1, 2]) {
1831
+ const clock = terminalNodeByRole(divider, `clock${half}`);
1832
+ const data = terminalNodeByRole(divider, `data${half}`);
1833
+ const notQ = terminalNodeByRole(divider, `notq${half}`);
1834
+ const q = terminalNodeByRole(divider, `q${half}`);
1835
+ if (clock === null || data === null || notQ === null || q === null) {
1836
+ continue;
1837
+ }
1838
+ if (clock === GROUND || data === GROUND) {
1839
+ continue;
1840
+ }
1841
+ // Toggle wiring: D driven by its own half's not-Q.
1842
+ if (data !== notQ) {
1843
+ continue;
1844
+ }
1845
+ if (q === notQ || clock === q || clock === notQ) {
1846
+ continue;
1847
+ }
1848
+ if (!clockNodes.has(q) || !clockNodes.has(notQ)) {
1849
+ continue;
1850
+ }
1851
+ dividerClock = clock;
1852
+ break;
1853
+ }
1854
+ if (dividerClock !== null) {
1855
+ break;
1856
+ }
1857
+ }
1858
+ if (dividerClock === null) {
1859
+ return { outcome: "no-clock-driver" };
1860
+ }
1861
+ const clockNode = dividerClock;
1862
+ // The buffer: exactly one transistor with its collector on the divider clock,
1863
+ // its emitter on the rail and its base coupled from the timing node. Resistor
1864
+ // biasing on either node is allowed; anything active is a different circuit.
1865
+ const buffers = netlist.devices.filter((device) => device.kind === "bjt" &&
1866
+ device !== ujt &&
1867
+ foldToken(device.identity.declaredType ?? "") !== UJT_DECLARED_TYPE &&
1868
+ terminalNodeByRole(device, "collector") === clockNode);
1869
+ if (buffers.length !== 1) {
1870
+ return { outcome: "no-clock-driver" };
1871
+ }
1872
+ const buffer = buffers[0];
1873
+ const bufferEmitter = terminalNodeByRole(buffer, "emitter");
1874
+ const bufferBase = terminalNodeByRole(buffer, "base");
1875
+ if (bufferEmitter === null || !railNodes.has(bufferEmitter)) {
1876
+ return { outcome: "no-clock-driver" };
1877
+ }
1878
+ if (bufferBase === null || bufferBase === GROUND) {
1879
+ return { outcome: "no-clock-driver" };
1880
+ }
1881
+ const coupling = netlist.devices.filter((device) => device.kind === "capacitor" && isTwoTerminalBetween(device, capFar, bufferBase));
1882
+ if (coupling.length !== 1) {
1883
+ return { outcome: "no-clock-driver" };
1884
+ }
1885
+ const activeOnClock = netlist.devices.filter((device) => device !== buffer &&
1886
+ ACTIVE_TIMING_KINDS.has(device.kind) &&
1887
+ device.nodes.includes(clockNode));
1888
+ const activeOnBase = netlist.devices.filter((device) => device !== buffer &&
1889
+ ACTIVE_TIMING_KINDS.has(device.kind) &&
1890
+ device.nodes.includes(bufferBase));
1891
+ if (activeOnClock.length > 0 || activeOnBase.length > 0) {
1892
+ return { outcome: "no-clock-driver" };
1893
+ }
1894
+ const formulaConstant = Math.log(1 / (1 - eta));
1895
+ if (!Number.isFinite(formulaConstant) || formulaConstant <= 0) {
1896
+ return { outcome: "no-clock-driver" };
1897
+ }
1898
+ let controlId = null;
1899
+ let taper = "linear";
1900
+ let ohmsAtControlMin = resistances[0];
1901
+ let ohmsAtControlMax = resistances[0];
1902
+ let rOhms = resistances[0];
1903
+ // `ohmsAtControlMin/Max` are position-indexed, not sorted: the resistance with
1904
+ // the control at its minimum (position 0, first throw) and at its maximum.
1905
+ // The runtime lerps between them by control position, so a selector whose open
1906
+ // position is the larger resistance keeps Min > Max and still evaluates both
1907
+ // positions exactly. Buffer sizing takes the larger of the two.
1908
+ if (selector !== null) {
1909
+ const control = selector.control !== null
1910
+ ? netlist.controls.find((entry) => entry.id === selector.control)
1911
+ : undefined;
1912
+ if (control !== undefined) {
1913
+ controlId = control.id;
1914
+ taper = control.taper;
1915
+ ohmsAtControlMin = resistances[0];
1916
+ ohmsAtControlMax = resistances[throwNodes.length - 1];
1917
+ const position = Math.min(1, Math.max(0, control.defaultPosition));
1918
+ const selected = Math.min(throwNodes.length - 1, Math.max(0, Math.floor(position * throwNodes.length)));
1919
+ rOhms = resistanceAt(selected);
1920
+ }
1921
+ else {
1922
+ // A fixed selector states one position: derive it statically. Same
1923
+ // position rule as `lower.ts` (`parameters.position ?? 1`).
1924
+ const selected = Math.min(throwNodes.length - 1, Math.max(0, Math.floor((selector.parameters.position ?? 1) * throwNodes.length)));
1925
+ rOhms = resistanceAt(selected);
1926
+ ohmsAtControlMin = rOhms;
1927
+ ohmsAtControlMax = rOhms;
1928
+ }
1929
+ }
1930
+ if (!(rOhms > 0) || !Number.isFinite(rOhms) || !(cFarads > 0)) {
1931
+ return { outcome: "no-clock-driver" };
1932
+ }
1933
+ const fUjtHz = 1 / (rOhms * cFarads * formulaConstant);
1934
+ const fClockHz = fUjtHz / 2;
1935
+ const implausible = implausibleClock(fClockHz, stages);
1936
+ if (implausible !== null) {
1937
+ return { outcome: "refused", reason: implausible };
1938
+ }
1939
+ const delaySeconds = stages / fUjtHz;
1940
+ if (!Number.isFinite(delaySeconds) || !(delaySeconds > 0)) {
1941
+ return { outcome: "no-clock-driver" };
1942
+ }
1943
+ return {
1944
+ outcome: "derived",
1945
+ delaySeconds,
1946
+ rOhms,
1947
+ cFarads,
1948
+ fClockHz,
1949
+ family: "unknown",
1950
+ stages,
1951
+ formulaConstant,
1952
+ offsetSeconds: 0,
1953
+ controlId,
1954
+ taper,
1955
+ ohmsAtControlMin,
1956
+ ohmsAtControlMax,
1957
+ };
1958
+ }
1577
1959
  /**
1578
1960
  * Princeton Technology PT2399 delay time from external VCO resistance R (in Ohms).
1579
1961
  *