@vessel-dsp/compiler 0.0.0-stage → 0.2.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 (226) hide show
  1. package/README.md +314 -2
  2. package/dist/bake.d.ts +38 -0
  3. package/dist/bake.d.ts.map +1 -0
  4. package/dist/bake.js +101 -0
  5. package/dist/bake.js.map +1 -0
  6. package/dist/bbd-clock.d.ts +171 -0
  7. package/dist/bbd-clock.d.ts.map +1 -0
  8. package/dist/bbd-clock.js +1750 -0
  9. package/dist/bbd-clock.js.map +1 -0
  10. package/dist/block-row.d.ts +51 -0
  11. package/dist/block-row.d.ts.map +1 -0
  12. package/dist/block-row.js +17 -0
  13. package/dist/block-row.js.map +1 -0
  14. package/dist/bypass.d.ts +3 -0
  15. package/dist/bypass.d.ts.map +1 -0
  16. package/dist/bypass.js +228 -0
  17. package/dist/bypass.js.map +1 -0
  18. package/dist/compile.d.ts +24 -0
  19. package/dist/compile.d.ts.map +1 -0
  20. package/dist/compile.js +344 -0
  21. package/dist/compile.js.map +1 -0
  22. package/dist/converter-scale.d.ts +14 -0
  23. package/dist/converter-scale.d.ts.map +1 -0
  24. package/dist/converter-scale.js +133 -0
  25. package/dist/converter-scale.js.map +1 -0
  26. package/dist/couple.d.ts +28 -0
  27. package/dist/couple.d.ts.map +1 -0
  28. package/dist/couple.js +567 -0
  29. package/dist/couple.js.map +1 -0
  30. package/dist/dangling-active-terminal.d.ts +47 -0
  31. package/dist/dangling-active-terminal.d.ts.map +1 -0
  32. package/dist/dangling-active-terminal.js +542 -0
  33. package/dist/dangling-active-terminal.js.map +1 -0
  34. package/dist/declared-program.d.ts +23 -0
  35. package/dist/declared-program.d.ts.map +1 -0
  36. package/dist/declared-program.js +387 -0
  37. package/dist/declared-program.js.map +1 -0
  38. package/dist/denomination.d.ts +48 -0
  39. package/dist/denomination.d.ts.map +1 -0
  40. package/dist/denomination.js +27 -0
  41. package/dist/denomination.js.map +1 -0
  42. package/dist/device-laws.d.ts +237 -0
  43. package/dist/device-laws.d.ts.map +1 -0
  44. package/dist/device-laws.js +3497 -0
  45. package/dist/device-laws.js.map +1 -0
  46. package/dist/diagnostics.d.ts +13 -0
  47. package/dist/diagnostics.d.ts.map +1 -0
  48. package/dist/diagnostics.js +19 -0
  49. package/dist/diagnostics.js.map +1 -0
  50. package/dist/emit.d.ts +8 -0
  51. package/dist/emit.d.ts.map +1 -0
  52. package/dist/emit.js +43 -0
  53. package/dist/emit.js.map +1 -0
  54. package/dist/errors.d.ts +22 -0
  55. package/dist/errors.d.ts.map +1 -0
  56. package/dist/errors.js +39 -0
  57. package/dist/errors.js.map +1 -0
  58. package/dist/firmware-class.d.ts +69 -0
  59. package/dist/firmware-class.d.ts.map +1 -0
  60. package/dist/firmware-class.js +20 -0
  61. package/dist/firmware-class.js.map +1 -0
  62. package/dist/fixtures/circuits.d.ts +1721 -0
  63. package/dist/fixtures/circuits.d.ts.map +1 -0
  64. package/dist/fixtures/circuits.js +13404 -0
  65. package/dist/fixtures/circuits.js.map +1 -0
  66. package/dist/fixtures/expected.d.ts +257 -0
  67. package/dist/fixtures/expected.d.ts.map +1 -0
  68. package/dist/fixtures/expected.js +311 -0
  69. package/dist/fixtures/expected.js.map +1 -0
  70. package/dist/fixtures/index.d.ts +4 -0
  71. package/dist/fixtures/index.d.ts.map +1 -0
  72. package/dist/fixtures/index.js +8 -0
  73. package/dist/fixtures/index.js.map +1 -0
  74. package/dist/fixtures/registry.d.ts +52 -0
  75. package/dist/fixtures/registry.d.ts.map +1 -0
  76. package/dist/fixtures/registry.js +341 -0
  77. package/dist/fixtures/registry.js.map +1 -0
  78. package/dist/generic-tube-fit.d.ts +12 -0
  79. package/dist/generic-tube-fit.d.ts.map +1 -0
  80. package/dist/generic-tube-fit.js +45 -0
  81. package/dist/generic-tube-fit.js.map +1 -0
  82. package/dist/grounded-clock-supply.d.ts +13 -0
  83. package/dist/grounded-clock-supply.d.ts.map +1 -0
  84. package/dist/grounded-clock-supply.js +45 -0
  85. package/dist/grounded-clock-supply.js.map +1 -0
  86. package/dist/identify.d.ts +50 -0
  87. package/dist/identify.d.ts.map +1 -0
  88. package/dist/identify.js +319 -0
  89. package/dist/identify.js.map +1 -0
  90. package/dist/index.d.ts +10 -0
  91. package/dist/index.d.ts.map +1 -0
  92. package/dist/index.js +19 -0
  93. package/dist/index.js.map +1 -0
  94. package/dist/inert-controls.d.ts +15 -0
  95. package/dist/inert-controls.d.ts.map +1 -0
  96. package/dist/inert-controls.js +266 -0
  97. package/dist/inert-controls.js.map +1 -0
  98. package/dist/latch-seed.d.ts +53 -0
  99. package/dist/latch-seed.d.ts.map +1 -0
  100. package/dist/latch-seed.js +257 -0
  101. package/dist/latch-seed.js.map +1 -0
  102. package/dist/link.d.ts +7 -0
  103. package/dist/link.d.ts.map +1 -0
  104. package/dist/link.js +270 -0
  105. package/dist/link.js.map +1 -0
  106. package/dist/lower.d.ts +79 -0
  107. package/dist/lower.d.ts.map +1 -0
  108. package/dist/lower.js +1855 -0
  109. package/dist/lower.js.map +1 -0
  110. package/dist/netlist.d.ts +79 -0
  111. package/dist/netlist.d.ts.map +1 -0
  112. package/dist/netlist.js +4746 -0
  113. package/dist/netlist.js.map +1 -0
  114. package/dist/non-executable-clock-drivers.d.ts +8 -0
  115. package/dist/non-executable-clock-drivers.d.ts.map +1 -0
  116. package/dist/non-executable-clock-drivers.js +33 -0
  117. package/dist/non-executable-clock-drivers.js.map +1 -0
  118. package/dist/open-category.d.ts +98 -0
  119. package/dist/open-category.d.ts.map +1 -0
  120. package/dist/open-category.js +111 -0
  121. package/dist/open-category.js.map +1 -0
  122. package/dist/over-driven-node.d.ts +33 -0
  123. package/dist/over-driven-node.d.ts.map +1 -0
  124. package/dist/over-driven-node.js +188 -0
  125. package/dist/over-driven-node.js.map +1 -0
  126. package/dist/part-catalog.d.ts +7 -0
  127. package/dist/part-catalog.d.ts.map +1 -0
  128. package/dist/part-catalog.js +6639 -0
  129. package/dist/part-catalog.js.map +1 -0
  130. package/dist/part-number.d.ts +32 -0
  131. package/dist/part-number.d.ts.map +1 -0
  132. package/dist/part-number.js +80 -0
  133. package/dist/part-number.js.map +1 -0
  134. package/dist/partition.d.ts +7 -0
  135. package/dist/partition.d.ts.map +1 -0
  136. package/dist/partition.js +242 -0
  137. package/dist/partition.js.map +1 -0
  138. package/dist/port-full-scale.d.ts +70 -0
  139. package/dist/port-full-scale.d.ts.map +1 -0
  140. package/dist/port-full-scale.js +235 -0
  141. package/dist/port-full-scale.js.map +1 -0
  142. package/dist/pot-orientation.d.ts +114 -0
  143. package/dist/pot-orientation.d.ts.map +1 -0
  144. package/dist/pot-orientation.js +551 -0
  145. package/dist/pot-orientation.js.map +1 -0
  146. package/dist/power-domain-control.d.ts +40 -0
  147. package/dist/power-domain-control.d.ts.map +1 -0
  148. package/dist/power-domain-control.js +142 -0
  149. package/dist/power-domain-control.js.map +1 -0
  150. package/dist/proxy-declaration.d.ts +121 -0
  151. package/dist/proxy-declaration.d.ts.map +1 -0
  152. package/dist/proxy-declaration.js +50 -0
  153. package/dist/proxy-declaration.js.map +1 -0
  154. package/dist/registry.d.ts +323 -0
  155. package/dist/registry.d.ts.map +1 -0
  156. package/dist/registry.js +192 -0
  157. package/dist/registry.js.map +1 -0
  158. package/dist/sparse-schedule.d.ts +65 -0
  159. package/dist/sparse-schedule.d.ts.map +1 -0
  160. package/dist/sparse-schedule.js +421 -0
  161. package/dist/sparse-schedule.js.map +1 -0
  162. package/dist/speaker-load.d.ts +38 -0
  163. package/dist/speaker-load.d.ts.map +1 -0
  164. package/dist/speaker-load.js +97 -0
  165. package/dist/speaker-load.js.map +1 -0
  166. package/dist/stage-coverage.d.ts +21 -0
  167. package/dist/stage-coverage.d.ts.map +1 -0
  168. package/dist/stage-coverage.js +124 -0
  169. package/dist/stage-coverage.js.map +1 -0
  170. package/dist/stamp-partition.d.ts +314 -0
  171. package/dist/stamp-partition.d.ts.map +1 -0
  172. package/dist/stamp-partition.js +472 -0
  173. package/dist/stamp-partition.js.map +1 -0
  174. package/dist/supply-reference.d.ts +19 -0
  175. package/dist/supply-reference.d.ts.map +1 -0
  176. package/dist/supply-reference.js +66 -0
  177. package/dist/supply-reference.js.map +1 -0
  178. package/dist/supply-short.d.ts +32 -0
  179. package/dist/supply-short.d.ts.map +1 -0
  180. package/dist/supply-short.js +118 -0
  181. package/dist/supply-short.js.map +1 -0
  182. package/dist/supply-stamps.d.ts +51 -0
  183. package/dist/supply-stamps.d.ts.map +1 -0
  184. package/dist/supply-stamps.js +363 -0
  185. package/dist/supply-stamps.js.map +1 -0
  186. package/dist/terminal-role.d.ts +109 -0
  187. package/dist/terminal-role.d.ts.map +1 -0
  188. package/dist/terminal-role.js +46 -0
  189. package/dist/terminal-role.js.map +1 -0
  190. package/dist/terminal-roles.d.ts +59 -0
  191. package/dist/terminal-roles.d.ts.map +1 -0
  192. package/dist/terminal-roles.js +92 -0
  193. package/dist/terminal-roles.js.map +1 -0
  194. package/dist/transformer.d.ts +181 -0
  195. package/dist/transformer.d.ts.map +1 -0
  196. package/dist/transformer.js +476 -0
  197. package/dist/transformer.js.map +1 -0
  198. package/dist/types.d.ts +3157 -0
  199. package/dist/types.d.ts.map +1 -0
  200. package/dist/types.js +11 -0
  201. package/dist/types.js.map +1 -0
  202. package/dist/unbounded-operating-point.d.ts +35 -0
  203. package/dist/unbounded-operating-point.d.ts.map +1 -0
  204. package/dist/unbounded-operating-point.js +181 -0
  205. package/dist/unbounded-operating-point.js.map +1 -0
  206. package/dist/unexecuted-region.d.ts +3 -0
  207. package/dist/unexecuted-region.d.ts.map +1 -0
  208. package/dist/unexecuted-region.js +122 -0
  209. package/dist/unexecuted-region.js.map +1 -0
  210. package/dist/unimplemented-control-role.d.ts +3 -0
  211. package/dist/unimplemented-control-role.d.ts.map +1 -0
  212. package/dist/unimplemented-control-role.js +61 -0
  213. package/dist/unimplemented-control-role.js.map +1 -0
  214. package/dist/unreachable-output.d.ts +3 -0
  215. package/dist/unreachable-output.d.ts.map +1 -0
  216. package/dist/unreachable-output.js +97 -0
  217. package/dist/unreachable-output.js.map +1 -0
  218. package/dist/unreadable-terminal-role.d.ts +67 -0
  219. package/dist/unreadable-terminal-role.d.ts.map +1 -0
  220. package/dist/unreadable-terminal-role.js +409 -0
  221. package/dist/unreadable-terminal-role.js.map +1 -0
  222. package/dist/unverified-pinout.d.ts +27 -0
  223. package/dist/unverified-pinout.d.ts.map +1 -0
  224. package/dist/unverified-pinout.js +121 -0
  225. package/dist/unverified-pinout.js.map +1 -0
  226. package/package.json +75 -5
@@ -0,0 +1,142 @@
1
+ /**
2
+ * The kinds that separate the mains and HT domains from the audio graph.
3
+ *
4
+ * A supply, a rail and a battery are the domain's origin. A **transformer** is here because its
5
+ * windings couple magnetically rather than electrically: a mains primary and a heater secondary
6
+ * share no node, and treating the device as a conductor would make every rail reachable from the
7
+ * wall socket.
8
+ */
9
+ const DOMAIN_BOUNDARY_KINDS = new Set([
10
+ "voltage-source",
11
+ "current-source",
12
+ "battery",
13
+ "rail",
14
+ "transformer",
15
+ "ground",
16
+ ]);
17
+ /**
18
+ * The devices reachable from the audio ports without crossing a domain boundary.
19
+ *
20
+ * Ground is excluded as a hop for the reason a shared-node test always must: nearly every device
21
+ * touches it, so counting it makes the whole circuit one neighbourhood.
22
+ */
23
+ function devicesReachableFromPorts(netlist, ports) {
24
+ const byNode = new Map();
25
+ for (const device of netlist.devices) {
26
+ if (DOMAIN_BOUNDARY_KINDS.has(device.kind)) {
27
+ continue;
28
+ }
29
+ for (const node of device.nodes) {
30
+ if (node === 0) {
31
+ continue;
32
+ }
33
+ const bucket = byNode.get(node) ?? [];
34
+ bucket.push(device);
35
+ byNode.set(node, bucket);
36
+ }
37
+ }
38
+ const seenNodes = new Set(ports);
39
+ const seenDevices = new Set();
40
+ const queue = [...ports];
41
+ while (queue.length > 0) {
42
+ const node = queue.pop();
43
+ for (const device of byNode.get(node) ?? []) {
44
+ if (seenDevices.has(device.id)) {
45
+ continue;
46
+ }
47
+ seenDevices.add(device.id);
48
+ for (const next of device.nodes) {
49
+ if (next !== 0 && !seenNodes.has(next)) {
50
+ seenNodes.add(next);
51
+ queue.push(next);
52
+ }
53
+ }
54
+ }
55
+ }
56
+ return seenDevices;
57
+ }
58
+ /**
59
+ * A device exposed as a sweepable user control that is **power-domain hardware**: it sits outside
60
+ * the audio graph and no panel entry claims it.
61
+ *
62
+ * **Why this is reported rather than decided.** Structure cannot tell a user control from fixed
63
+ * hardware on this side of the boundary. A standby switch and a mains fuse are both switches in
64
+ * the supply, and one belongs on the front panel while the other belongs inside the chassis --
65
+ * only the document can say which. So the compiler names the ambiguity and the fix is a
66
+ * `deviceInterface.controls` entry, or a kind that is not a switch.
67
+ *
68
+ * **A rule was attempted and it does not exist.** The promising one was "a switch declaring a
69
+ * `FuseRating` is a fuse, therefore not a control" -- legal evidence, since it reads the presence
70
+ * of a typed property rather than a name, and 26 corpus switches declare one. It fails on its own
71
+ * data: `mesa-boogie-dual-rectifier`'s `SRC_POWER_SWITCH_FUSE_4A`, `orange-rockerverb`'s
72
+ * `SRC_PRIMARY_POWER_SWITCH_AND_FUSE` and `sunn-beta-lead`'s `SRC_POWER_SWITCH_FUSE` are each a
73
+ * power switch *and* a fuse in one two-terminal component, and a power switch is exactly the user
74
+ * control the rule would have suppressed. Narrowing it to "two terminals and no switch-shaped
75
+ * property" keeps all three, because a fuse and a combined switch-and-fuse are structurally
76
+ * identical: two terminals, one closed contact. The difference is whether a human operates it,
77
+ * which is not a fact about the circuit.
78
+ *
79
+ * **Measured 2026-09-04: 67 of the corpus's 958 program controls**, across the packets that
80
+ * model their own power supply. It read 80 before `boss-bf-2`'s three service trims were
81
+ * declared, and 76 before every signal jack became a seed -- see the note in the function. They are mains switches,
82
+ * voltage selectors, and mains, HT and heater fuses -- `vox-ac30-top-boost` alone contributes
83
+ * `SW_MAINS`, a 230/120 selector and four fuses. A fuse offered to a player as a knob to sweep is
84
+ * the shape of the problem.
85
+ *
86
+ * **The traversal's one known imprecision** is at the B+ boundary: it stops at the transformer but
87
+ * not at the rectifier and filter, so B+ nodes stay reachable through a tube's plate. That keeps
88
+ * `SW1_STANDBY` and `S_STANDBY_SWITCH` out of this warning, which is right -- a standby switch is
89
+ * a front-panel control -- and also keeps `F2_HT_FUSE` out, which is not.
90
+ */
91
+ export function findPowerDomainControls(netlist,
92
+ // A control carries the `role` and `label` its `deviceInterface.controls` entry gave it, so
93
+ // "the panel does not claim this" is exactly both being absent -- no second pass over the
94
+ // document, and no way for the two readings to drift apart.
95
+ controls, ports) {
96
+ // **Every signal jack is a seed, not just the two selected ports.**
97
+ //
98
+ // A `.vdsp` names one input and one output port, and on a two-channel amp that selects one
99
+ // channel: `fender-super-reverb-aa1069`'s port is its *Vibrato* input, so the Normal channel
100
+ // hangs off a jack the traversal never started from. Combined with the transformer cut --
101
+ // which stops the walk re-entering from the speaker-side output port -- the search was
102
+ // effectively one-directional from a single channel's input, and it called three
103
+ // `Normal-Volume` pots and `soldano-slo-100`'s `SRC_PRESENCE_25K` power-domain hardware.
104
+ //
105
+ // Seeding every jack node drops the count from 76 to 67 and rescues all nine. It also rescues
106
+ // `soldano-slo-100`'s `SRC_F1_BPLUS_FUSE`, which is a false negative -- a B+ fuse is not a
107
+ // panel control -- and that trade is deliberate: this warning offers candidates for a human
108
+ // decision, and naming a Presence knob as power hardware costs more than missing one fuse.
109
+ const jackNodes = netlist.devices
110
+ .filter((device) => device.kind === "jack")
111
+ .flatMap((device) => device.nodes)
112
+ .filter((node) => node !== 0);
113
+ const unclaimed = controls.filter((control) => (control.role ?? null) === null && (control.label ?? null) === null);
114
+ if (unclaimed.length === 0) {
115
+ return [];
116
+ }
117
+ const reachable = devicesReachableFromPorts(netlist, [
118
+ ...ports,
119
+ ...new Set(jackNodes),
120
+ ]);
121
+ const warnings = [];
122
+ for (const { id } of unclaimed) {
123
+ if (reachable.has(id)) {
124
+ continue;
125
+ }
126
+ const device = netlist.devices.find((candidate) => candidate.id === id);
127
+ if (device === undefined) {
128
+ continue;
129
+ }
130
+ warnings.push({
131
+ code: "power-domain-control",
132
+ device: id,
133
+ detail: `${device.kind} ${id} is offered as a sweepable control, but it sits outside the ` +
134
+ "audio graph -- unreachable from either port without crossing a supply, rail or " +
135
+ "transformer -- and no `deviceInterface.controls` entry claims it as a panel " +
136
+ "control. Power-domain hardware, not a control: declare it on the panel if a " +
137
+ "player operates it, and give it a kind that is not a switch if one does not.",
138
+ });
139
+ }
140
+ return warnings;
141
+ }
142
+ //# sourceMappingURL=power-domain-control.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"power-domain-control.js","sourceRoot":"","sources":["../src/power-domain-control.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,121 @@
1
+ /**
2
+ * **A declared proxy: a macro that stands in for a part it is not.**
3
+ *
4
+ * `RDD63H101` is a 144-pin microcode-driven digital delay processor bound to
5
+ * `bucket-brigade-delay-line` — an analog charge-transfer model. That binding may well be the right
6
+ * engineering call: removing it makes `boss-dd-2` and `boss-dsd-3` silent, which **trades a stated
7
+ * approximation for an unstated absence** and destroys information. What is wrong today is not the
8
+ * binding; it is that **nothing in the catalog says it is a stand-in**, so it reads as an identity
9
+ * claim, and the packets' own records say that chip's microcode is unavailable.
10
+ *
11
+ * **This is format item 8's shape, one level up.** The author knew the macro was an approximation
12
+ * and had nowhere to say so, so they wrote the only thing the format allowed: a binding that
13
+ * asserts identity. **Every required assertion with no "approximate, because" option manufactures
14
+ * a false claim**, and the coping strategies are the same three — overstate it, omit it and lose
15
+ * the reason, or write prose beside it.
16
+ *
17
+ * **Why this is a separate construct from item 8's reason field, since the unification was asked
18
+ * for.** They share the principle and not the payload. An absent part number needs a reason a
19
+ * *consumer* can branch on — unknown, excluded, not-applicable — and nothing more. A proxy needs
20
+ * three things a *fidelity* query must be able to read separately, and merging them into one
21
+ * free-text annotation would lose exactly the field that does the work: `doesNotReproduce`. **One
22
+ * generic "declared inexactness" bag would be uncheckable, which is the property both constructs
23
+ * exist to provide.** They belong in one format package and not in one field.
24
+ */
25
+ /**
26
+ * **A thing the model fails to reproduce, written so it can be RUN.**
27
+ *
28
+ * The test: *could someone who has never read this declaration run it and get a number that either
29
+ * agrees or does not?* — *"does not reproduce the microcode's modulation behaviour"* fails that;
30
+ * *"delay time does not track the clock, measured as time-of-first-energy"* passes it.
31
+ *
32
+ * **This is the falsifier, and it is the reason a declaration is not paperwork.** Each entry
33
+ * becomes the **acceptance row for the real block** when someone finally writes it: the measurement
34
+ * is already chosen, the expected value of the stand-in is already recorded, and a real model has
35
+ * to move that number. A prose sentence here gives the next person nothing to run.
36
+ */
37
+ export type Measurable = {
38
+ /** What is not reproduced. */
39
+ readonly claim: string;
40
+ /** How to measure it, in terms someone who has not read this could execute. */
41
+ readonly measurement: string;
42
+ /** What THIS model yields for that measurement -- the number a real block must move. */
43
+ readonly standIn: string;
44
+ /**
45
+ * **`registered-unrun` is a PRE-REGISTRATION WITHOUT A FIXTURE, and it is not the same thing as
46
+ * a refuted claim.** Nine `subsumes` claims were withdrawn on 2026-09-11 because they were shown
47
+ * **false**; a well-formed measurable that no packet can currently run is **unrun**, which is a
48
+ * different state. Deleting it destroys the evidence that the claim was stated *before* any
49
+ * measurement — and pre-registration is the thing this method exists to make valuable.
50
+ *
51
+ * **What it must never do is look like it ran, or count toward anything measured.**
52
+ */
53
+ readonly status: "measured" | "registered-unrun";
54
+ /** `registered-unrun` only: what fixture would run it. Required, so the claim names its own cure. */
55
+ readonly fixture?: string;
56
+ /** `registered-unrun` only: withdraw if no fixture exists by this date. A registration with no expiry is a permanent excuse. */
57
+ readonly expires?: string;
58
+ };
59
+ export type ProxyDeclaration = {
60
+ /** The real part and what it actually is — not what the macro models. */
61
+ readonly standsFor: string;
62
+ /** Why the stand-in is defensible at all, in terms a reviewer can disagree with. */
63
+ readonly justification: string;
64
+ /**
65
+ * **What the proxy does NOT reproduce. Required, and required to be non-empty.**
66
+ *
67
+ * A proxy that reproduces everything is not a proxy, it is a model — so an empty list is
68
+ * always an error, never a well-behaved case. This is the field that makes the declaration
69
+ * cost something: an author who cannot name a single thing their stand-in fails to reproduce
70
+ * has not examined it.
71
+ */
72
+ readonly doesNotReproduce: readonly Measurable[];
73
+ /**
74
+ * **True by construction, so passing proves nothing.** Kept apart from `doesNotReproduce`
75
+ * because listing a structural invariant among measurables inflates the count of things
76
+ * measured — the same inflation this programme spent 2026-09-11 removing. A compiler check is
77
+ * the right home for these; they are recorded here so the boundary is visible.
78
+ */
79
+ readonly structuralInvariants?: readonly string[];
80
+ };
81
+ export type ProxyViolation = {
82
+ readonly field: string;
83
+ readonly problem: string;
84
+ };
85
+ /** Structural validation. A declaration that cannot fail is not a declaration. */
86
+ export declare function validateProxy(p: ProxyDeclaration): readonly ProxyViolation[];
87
+ /**
88
+ * **A CITED DESIGN-TIME DERIVATION — a different claim from `subsumes`, with a different check.**
89
+ *
90
+ * `subsumes` claims **parametric dependence**: mutate the device's spec and the macro's parameters
91
+ * move. That is the only form a census can trust without reading anything, and it is what
92
+ * `scripts/report-subsumption-mutant.ts` tests.
93
+ *
94
+ * A design-time derivation is legitimate and **the mutant would reject it**, because the value is a
95
+ * literal in the catalog rather than a computed expression. So it gets its own field and its own
96
+ * check: **RECOMPUTATION**. Apply the cited formula to the cited values and require it to reproduce
97
+ * the constant.
98
+ *
99
+ * > **A cited derivation that does not reproduce the constant is a guess with a footnote**, and
100
+ * > recomputation catches that where a citation alone does not.
101
+ *
102
+ * **The nine withdrawn `subsumes` claims could be re-made this way, and cannot be**: the constant
103
+ * is `stages: 1024`, which is the **MN3007's datasheet stage count** — the macro's default. The
104
+ * same 1024 appears in `boss-rv-6`, whose Renesas reverb DSP has a 64Mbit SDRAM and no bucket
105
+ * brigade anywhere. **No formula from three 64Kx1 DRAMs lands on 1024 stages of a device class the
106
+ * pedal does not contain.**
107
+ */
108
+ export type DerivedFrom = {
109
+ /** Which macro parameter this derives. */
110
+ readonly parameter: string;
111
+ /** The device whose spec determines it, by canonical part id. */
112
+ readonly fromPart: string;
113
+ /** The source, cited to edition and page, as the triode registry cites per parameter. */
114
+ readonly citation: string;
115
+ /** The arithmetic, stated so it can be re-run -- not a pointer at the parts. */
116
+ readonly formula: string;
117
+ /** What the formula yields. Must equal the constant the entry declares. */
118
+ readonly recomputes: number;
119
+ };
120
+ export declare function validateDerivation(d: DerivedFrom, declared: number): readonly ProxyViolation[];
121
+ //# sourceMappingURL=proxy-declaration.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"proxy-declaration.d.ts","sourceRoot":"","sources":["../src/proxy-declaration.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH;;;;;;;;;;;GAWG;AACH,MAAM,MAAM,UAAU,GAAG;IACxB,8BAA8B;IAC9B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,+EAA+E;IAC/E,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,wFAAwF;IACxF,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB;;;;;;;;OAQG;IACH,QAAQ,CAAC,MAAM,EAAE,UAAU,GAAG,kBAAkB,CAAC;IACjD,qGAAqG;IACrG,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B,gIAAgI;IAChI,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;CAC1B,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG;IAC9B,yEAAyE;IACzE,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,oFAAoF;IACpF,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B;;;;;;;OAOG;IACH,QAAQ,CAAC,gBAAgB,EAAE,SAAS,UAAU,EAAE,CAAC;IACjD;;;;;OAKG;IACH,QAAQ,CAAC,oBAAoB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CAClD,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG;IAAE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC;AAElF,kFAAkF;AAClF,wBAAgB,aAAa,CAAC,CAAC,EAAE,gBAAgB,GAAG,SAAS,cAAc,EAAE,CAoC5E;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,MAAM,WAAW,GAAG;IACzB,0CAA0C;IAC1C,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,iEAAiE;IACjE,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,yFAAyF;IACzF,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,gFAAgF;IAChF,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,2EAA2E;IAC3E,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;CAC5B,CAAC;AAEF,wBAAgB,kBAAkB,CAAC,CAAC,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,GAAG,SAAS,cAAc,EAAE,CAU9F"}
@@ -0,0 +1,50 @@
1
+ /** Structural validation. A declaration that cannot fail is not a declaration. */
2
+ export function validateProxy(p) {
3
+ const out = [];
4
+ if (p.standsFor.trim() === "")
5
+ out.push({ field: "standsFor", problem: "must name the real part and what it actually is" });
6
+ if (p.justification.trim() === "")
7
+ out.push({ field: "justification", problem: "must say why the stand-in is defensible" });
8
+ if (p.doesNotReproduce.length === 0)
9
+ out.push({
10
+ field: "doesNotReproduce",
11
+ problem: "must name at least one thing the proxy fails to reproduce -- a proxy that reproduces everything is a model, not a proxy",
12
+ });
13
+ for (const [i, m] of p.doesNotReproduce.entries()) {
14
+ if (m.status === "registered-unrun") {
15
+ if ((m.fixture ?? "").trim() === "")
16
+ out.push({ field: `doesNotReproduce[${i}].fixture`, problem: "a registered-unrun claim must name the fixture that would run it" });
17
+ if ((m.expires ?? "").trim() === "")
18
+ out.push({ field: `doesNotReproduce[${i}].expires`, problem: "a registration with no expiry is a permanent excuse" });
19
+ }
20
+ // **All three parts are required, because two of them are the falsifier.** A `claim` alone
21
+ // is prose; a `measurement` without a `standIn` cannot be compared; a `standIn` without a
22
+ // `measurement` cannot be reproduced.
23
+ if (m.claim.trim() === "")
24
+ out.push({ field: `doesNotReproduce[${i}].claim`, problem: "is empty" });
25
+ if (m.measurement.trim() === "")
26
+ out.push({
27
+ field: `doesNotReproduce[${i}].measurement`,
28
+ problem: "must say how to measure it -- a claim nobody can run is not a falsifier",
29
+ });
30
+ if (m.standIn.trim() === "")
31
+ out.push({
32
+ field: `doesNotReproduce[${i}].standIn`,
33
+ problem: "must give the number THIS model yields, which is what a real block has to move",
34
+ });
35
+ }
36
+ return out;
37
+ }
38
+ export function validateDerivation(d, declared) {
39
+ const out = [];
40
+ for (const [k, v] of [["parameter", d.parameter], ["fromPart", d.fromPart], ["citation", d.citation], ["formula", d.formula]])
41
+ if (v.trim() === "")
42
+ out.push({ field: k, problem: "is empty" });
43
+ if (d.recomputes !== declared)
44
+ out.push({
45
+ field: "recomputes",
46
+ problem: `the cited formula yields ${d.recomputes} and the entry declares ${declared} -- a cited derivation that does not reproduce the constant is a guess with a footnote`,
47
+ });
48
+ return out;
49
+ }
50
+ //# sourceMappingURL=proxy-declaration.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"proxy-declaration.js","sourceRoot":"","sources":["../src/proxy-declaration.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,323 @@
1
+ import type { DeviceLaw, MacroModel, PartIdentity } from "./types";
2
+ /**
3
+ * One section of a multi-section part: a law plus which of the device's terminals it uses.
4
+ *
5
+ * `terminals` are indices into the device's own terminal list, given in the canonical order the
6
+ * law's lowering expects -- two ends for a two-terminal law, `plus, minus, output` for an op-amp.
7
+ * That order is the contract, because a section bypasses the terminal-role lookup lowering does
8
+ * for a whole device: an 8-pin dual op-amp has two outputs and two inverting inputs, so no single
9
+ * role token can name either of them unambiguously.
10
+ */
11
+ export type PartSection = {
12
+ readonly law: DeviceLaw;
13
+ readonly terminals: readonly number[];
14
+ };
15
+ /**
16
+ * The terminal order a `sections` model's indices assume, as role tokens, index-aligned with the
17
+ * device's own terminal list. `null` means that position carries no role token to check against.
18
+ *
19
+ * **Positional indices are only meaningful against a known declaration order, and the corpus proves
20
+ * two documents can order the same chip differently.** `boss-ce-2b` declares a `TL022CP` as
21
+ * `pin1_OUT_B, pin2_IN_B_MINUS, pin3_IN_B_PLUS, pin4_VEE, pin5_IN_A_PLUS, ...` -- physical pin
22
+ * order -- while `boss-bf-2` declares the same part number grouped by section, as
23
+ * `outA, invertingInputA, nonInvertingInputA, outB, ...`. One entry's `terminals` indices are
24
+ * correct for exactly one of those, and applied to the other they wire an amplifier's output to
25
+ * its input.
26
+ *
27
+ * Without this field that mistake is silent: every index resolves, every section stamps, and the
28
+ * pedal renders a plausible wrong circuit. With it, a document the entry was not written against
29
+ * refuses by name instead. It is the same reasoning as `identify`'s whole-token comparison -- a
30
+ * coverage bound is acceptable, a wrong answer is not.
31
+ */
32
+ export type PartPinout = readonly (string | null)[];
33
+ /**
34
+ * What the registry knows about a part. Either it reduces to lumped elements the
35
+ * solver can stamp, or it is a DSP algorithm with ports that the runtime implements.
36
+ *
37
+ * **`sections` exists because most real chips are multi-section and neither other arm can express
38
+ * one.** A dual op-amp is two amplifiers on eight pins: not one `law`, which produces a single
39
+ * element, and not a `macro` either, because a macro becomes its own block solved *outside* the MNA
40
+ * system while an op-amp's output depends on the feedback network that depends on its output.
41
+ * Lowering it to a separate DSP block breaks the loop that makes it an amplifier.
42
+ *
43
+ * A `sections` part is expanded by stage 3 into one ordinary device per section, so partitioning,
44
+ * lowering and the runtime see nothing new.
45
+ */
46
+ /**
47
+ * Where a macro's ports sit, named by terminal role rather than by terminal index.
48
+ *
49
+ * **This is the same problem `PartPinout` solves for `sections`, with a better answer available.**
50
+ * A positional index is only meaningful against a known declaration order, and the corpus orders
51
+ * one bucket brigade three ways: `boss-ce-2` declares `gnd, cp1, in, vgg, vdd, cp2, out1, out2`,
52
+ * `boss-dm-2` declares the same family part as `gnd, cp2, out1, out2, vdd, cp1, in, vgg`, and
53
+ * `electro-harmonix-electric-mistress` declares a two-terminal shell as `input, output`. One entry
54
+ * carrying indices is correct for exactly one of those and silently mis-wires the rest.
55
+ *
56
+ * `sections` cannot escape indices — a dual op-amp has two outputs and two inverting inputs, so no
57
+ * single role token names either unambiguously, which is why that arm refuses on a pinout guard
58
+ * instead. A macro's audio in and audio out are exactly one terminal each, so a role token *does*
59
+ * name them, and resolving by role is order-independent rather than order-checked. One entry then
60
+ * serves every declaration order that names its terminals at all.
61
+ *
62
+ * The bound is the same one `identify` accepts: whole-token comparison cannot invent a match, but
63
+ * the transcriber must have used a spelling listed here. A packet numbering its pins `pin1..pin8`
64
+ * produces no role tokens and is refused by name rather than guessed at.
65
+ */
66
+ export type MacroPortRoles = {
67
+ /** Aliases for the audio input terminal. Compared as whole folded tokens. */
68
+ readonly audioIn: readonly string[];
69
+ /** Aliases for the audio output terminal. Compared as whole folded tokens. */
70
+ readonly audioOut: readonly string[];
71
+ /**
72
+ * Aliases for a **second** audio output terminal, for a part that has two.
73
+ *
74
+ * A BBD is the case this exists for: an MN3208/BL3208 brings out OUT1 and OUT2, and a design
75
+ * puts a balance trimmer across them because the signal appears on both while the clock
76
+ * feedthrough appears in antiphase, so the pot nulls the clock and keeps the audio. Declaring
77
+ * only OUT1 left OUT2 driven by nothing, and the trimmer then acted as a plain attenuator
78
+ * against whatever bled through its own track -- measured on `mxr-carbon-copy` as **6.0 dB
79
+ * lost across four brigades at the trimmers' centre default against 0.67 dB with the wipers
80
+ * on the driven pin**, which a compandor downstream then squared.
81
+ *
82
+ * Empty (the default for every part that does not set it) means the part has one output, and
83
+ * nothing about the single-output path changes.
84
+ */
85
+ readonly audioOut2?: readonly string[];
86
+ /**
87
+ * Aliases for the terminal whose network derives the `parameter` port, if the part has one.
88
+ *
89
+ * Optional in effect rather than in type: an empty list means the part has none. Unlike the
90
+ * audio ports, failing to resolve this is not a refusal — `couple.ts` documents an absent
91
+ * `parameter` port as the safe default, where an absent audio port is an amputation.
92
+ */
93
+ readonly parameter: readonly string[];
94
+ };
95
+ /**
96
+ * A macro as the registry states it: everything `MacroModel` carries except the terminal indices
97
+ * and the pedal-specific parameters, which stage 3 resolves per device.
98
+ *
99
+ * Same split as `sections`' op-amp rails, and for the same reason — a registry describes a part,
100
+ * and where its terminals landed and how fast its clock runs are properties of the pedal it was
101
+ * fitted to.
102
+ */
103
+ /**
104
+ * 0-indexed physical terminal positions for a document that numbers its pins (`pin1`, `pin2`, ...
105
+ * -- `terminalRoleToken` returns null for those) and so carries no role tokens. Only consulted when
106
+ * every terminal is bare; a document that names even one terminal resolves by role, never by these.
107
+ */
108
+ export type MacroBarePinPositions = {
109
+ readonly audioIn: number;
110
+ readonly audioOut: number;
111
+ readonly parameter?: number;
112
+ };
113
+ export type MacroPartModel = {
114
+ readonly modelId: string;
115
+ /** Part-intrinsic parameters only. Pedal-specific ones are merged in at resolution. */
116
+ readonly parameters: Readonly<Record<string, number>>;
117
+ readonly ports: MacroPortRoles;
118
+ readonly barePinPositions?: MacroBarePinPositions;
119
+ readonly audioPortImpedanceOhms: MacroModel["audioPortImpedanceOhms"];
120
+ readonly parameterReferenceVolts: number | null;
121
+ };
122
+ export type PartModel = {
123
+ readonly kind: "law";
124
+ readonly law: DeviceLaw;
125
+ } | {
126
+ readonly kind: "sections";
127
+ readonly sections: readonly PartSection[];
128
+ /** The declaration order `sections[*].terminals` index into. See `PartPinout`. */
129
+ readonly pinout: PartPinout;
130
+ } | {
131
+ readonly kind: "macro";
132
+ readonly macro: MacroPartModel;
133
+ } | {
134
+ /**
135
+ * A chip whose silicon holds both lumped analog stages and a sampled
136
+ * core (first case: the PT2399's internal filter op-amps around its
137
+ * ADC/RAM/DAC): `sections` expand to ordinary MNA devices exactly as
138
+ * the `sections` arm does, and `core` resolves to the macro block
139
+ * exactly as the `macro` arm does, against the same device. Neither
140
+ * arm can express this alone -- a section is a lumped law and a
141
+ * macro solves outside MNA -- so the entry carries both, and the
142
+ * arms stay exclusive otherwise.
143
+ */
144
+ readonly kind: "sections-core";
145
+ readonly sections: readonly PartSection[];
146
+ /** The declaration order `sections[*].terminals` index into. See `PartPinout`. */
147
+ readonly pinout: PartPinout;
148
+ /** The sampled core, resolved as a macro against the whole device. */
149
+ readonly core: MacroPartModel;
150
+ };
151
+ /**
152
+ * The index of the one terminal whose role matches any of `aliases`, or `null`.
153
+ *
154
+ * `null` for no match and for an ambiguous one. Two terminals answering to the same port role is a
155
+ * declaration this entry cannot address — a `SAD1024`'s two cascaded sections both carry an
156
+ * `input` if a document names them that way — and picking the first would be a guess about which
157
+ * half the audio enters.
158
+ */
159
+ export declare function terminalWithRole(terminalRoles: readonly (string | null)[], aliases: readonly string[]): number | null;
160
+ /**
161
+ * Does this device declare the terminal order a `sections` entry's indices were written against?
162
+ *
163
+ * Length must match, and every position the entry names must carry that role token. A position the
164
+ * entry leaves `null` is unchecked, which is what lets a part whose document numbers its pins
165
+ * (`pin1`, `pin2`, ... -- `terminalRoleToken` returns null for those) still be expanded.
166
+ */
167
+ export declare function pinoutMatches(pinout: PartPinout, terminalRoles: readonly (string | null)[]): boolean;
168
+ import type { FirmwareEvidence } from "./firmware-class";
169
+ import type { TerminalProfile } from "./terminal-role";
170
+ import type { ProxyDeclaration } from "./proxy-declaration";
171
+ export type PartEntry = {
172
+ /** Matched whole and case-folded against a device's part number. */
173
+ readonly partIds: readonly string[];
174
+ /** Matched whole against the source's closed type vocabulary. */
175
+ readonly declaredTypes: readonly string[];
176
+ /**
177
+ * Required terminal role tokens. Every group must be satisfied by at least one
178
+ * alias, compared as whole tokens. Empty means this part has no pinout signature.
179
+ */
180
+ readonly terminalRoleGroups: readonly (readonly string[])[];
181
+ readonly model: PartModel;
182
+ /**
183
+ * **Present when this entry's model is a STAND-IN for a part it is not.** Required for any
184
+ * macro binding a part whose packets declare its behaviour unavailable, and read by
185
+ * `report-record-behaviour.ts`: a firmware-blocked part that executes a macro is a fidelity
186
+ * overclaim **unless** the binding declares itself a proxy and says what it fails to
187
+ * reproduce. Absent means the entry asserts it models the part.
188
+ */
189
+ readonly proxy?: ProxyDeclaration;
190
+ /**
191
+ * **Whether this chip's behaviour comes from a program — typed, with positive evidence.**
192
+ * Absent means `undetermined`, which is **not** a scope boundary. See `firmware-class.ts`: the
193
+ * compiler's current operative test reads the packet's *record* rather than the chip, so a
194
+ * failed dump search becomes "this contains firmware" with nothing demonstrating it.
195
+ */
196
+ readonly firmware?: FirmwareEvidence;
197
+ /**
198
+ * **The whole package's own supply current at no load, in amps, from its datasheet.**
199
+ *
200
+ * An `ideal-opamp` takes its rails as numbers and a clock driver's phases are ideal sources, so
201
+ * without this no IC draws anything from the supply: `boss-ce-2`'s battery read 2.7 mA against
202
+ * the 9 mA its service notes print, the missing ~6 mA being its 4558, TL022 and MN3101.
203
+ * `device-laws.ts` lowers it as a conductance across the package's supply pins, sized at the
204
+ * pedal's own rail span, so the draw is right at the rail the pedal runs on. A device that
205
+ * declares one section of a multi-section package draws that section's share.
206
+ *
207
+ * Absent means no datasheet figure is recorded, and the part draws nothing, as before.
208
+ */
209
+ readonly quiescentSupplyAmps?: number;
210
+ /**
211
+ * **What each terminal IS, with per-terminal evidence.** Absent means `unpopulated`, which is a
212
+ * correct state rather than a gap: a consumer that needs audio terminals must fall back to
213
+ * bridging everything and **report its result as an upper bound**.
214
+ */
215
+ readonly terminalProfile?: TerminalProfile;
216
+ /**
217
+ * **Which other parts this entry's model ACCOUNTS FOR.** Canonical part ids of devices whose
218
+ * behaviour is inside this model's abstraction, so a consumer can move them out of an engine-gap
219
+ * count **with evidence** rather than by a packet-level guess.
220
+ *
221
+ * **This is the third instance of one defect.** A macro block carries `kind id modelId
222
+ * parameters audioIn audioOut parameter modulation clockControl` and **no way to say what it
223
+ * stands for** — so whoever bound `RDD63H101` to a bucket-brigade had nowhere to write *"and
224
+ * this also accounts for the three DRAMs and the ladder"*, exactly as whoever wrote
225
+ * `undisclosed-roland-boss-dsp` had nowhere to write *"this part has no name"*, and exactly as
226
+ * `open` stood in for five different claims. **Prose in an identity field, a catch-all value,
227
+ * and a macro that cannot name its scope: three coping strategies, one missing field each.**
228
+ *
229
+ * **Deliberately on the ENTRY rather than inside `proxy`**, because subsumption and
230
+ * approximation are independent: `M50195` is a legitimate model of a fixed-function chip — not
231
+ * a proxy at all — and it still subsumes the `4164` DRAM it addresses.
232
+ *
233
+ * **WITHDRAWN ON ITS FIRST DAY, and that is the field working.** Two entries declared
234
+ * `subsumes` on 2026-09-11 -- `RDD63H101` claiming three DRAMs and an R2R ladder, `M50195P`
235
+ * claiming a `4164` -- and all nine claims passed the structural check (12-14 shared nodes) and
236
+ * then **all nine collapsed under the parameter mutant**. `stages: 1024` is a catalog constant
237
+ * and `delaySeconds` comes from the owner's own `DelayMs`; neither moves when the subsumed
238
+ * device's part is changed. The macros are not parameterised by what they claimed to account
239
+ * for, so the claims were decorative. **A shared bus looks identical either way, which is
240
+ * exactly what the structural check cannot see.**
241
+ *
242
+ * **The field stays; the claims go.** Any future `subsumes` entry must pass
243
+ * `scripts/report-subsumption-mutant.ts`: mutate the subsumed device's spec and the claiming
244
+ * macro's parameters must move. **THE DERIVATION IS THE EVIDENCE** -- a subsumption claim that
245
+ * cannot name which macro parameter the subsumed device determines is as empty as a device
246
+ * parameter with no source citation, which the triode registry already knows how to carry.
247
+ */
248
+ readonly subsumes?: readonly string[];
249
+ /**
250
+ * **Claims that were MADE and REFUTED, kept rather than erased.** "Declared and refuted" is a
251
+ * different failure from "the record said nothing", and a census that collapses them loses the
252
+ * distinction between a packet that never spoke and one that spoke and was wrong.
253
+ */
254
+ readonly subsumesRefuted?: readonly {
255
+ readonly parts: readonly string[];
256
+ readonly refutedBy: string;
257
+ readonly on: string;
258
+ }[];
259
+ /**
260
+ * **This part is a data converter, and what its full scale is.** Read by `converter-scale.ts`:
261
+ * a program runs in volts, so a DSP between an ADC and a DAC is unity only if the two full
262
+ * scales are equal. The real gain is `dac / adc`, and it is a fact about the two parts, not
263
+ * something a document should have to declare.
264
+ */
265
+ readonly converter?: ConverterDeclaration;
266
+ };
267
+ /**
268
+ * A converter's full scale, cited. `fullScaleVoltsPeakToPeak` is null when no datasheet was
269
+ * obtained, and `basis` then says what was searched: an unknown full scale is a named gap, never
270
+ * a guessed number.
271
+ */
272
+ export type ConverterDeclaration = {
273
+ readonly direction: "adc" | "dac";
274
+ readonly fullScaleVoltsPeakToPeak: number | null;
275
+ readonly basis: string;
276
+ };
277
+ export type PartRegistry = {
278
+ readonly entries: readonly PartEntry[];
279
+ };
280
+ export declare const emptyRegistry: PartRegistry;
281
+ /** Run `fn` with registry reads recorded against `owner`. Nesting is honoured; the outer set wins. */
282
+ export declare function withRecordedReads<T>(owner: string, fn: () => T): T;
283
+ /** What each owner consulted. Keyed by the owner string passed to `withRecordedReads`. */
284
+ export declare function recordedReads(): ReadonlyMap<string, ReadonlySet<string>>;
285
+ /** Forget everything recorded so far. Callers that measure one packet at a time need this. */
286
+ export declare function clearRecordedReads(): void;
287
+ export declare function registryEntryFor(registry: PartRegistry, identity: PartIdentity, terminalCount?: number): PartEntry | null;
288
+ export declare function registryModelFor(registry: PartRegistry, identity: PartIdentity, terminalCount?: number): PartModel | null;
289
+ /** Part numbers compare ignoring case and separators: `MN-3007` is `mn3007`. */
290
+ export declare function foldPartId(value: string | undefined | null): string;
291
+ /** Typed vocabulary values compare ignoring case and whitespace, keeping structure. */
292
+ export declare function foldToken(value: string | undefined | null): string;
293
+ /**
294
+ * The law a registered part supplies for a device whose class already names its law kind.
295
+ *
296
+ * This is rung 1 of `identify.ts`'s evidence ladder and **only** rung 1: an exact manufacturer
297
+ * part number, compared whole and case-folded. The other two rungs are deliberately unreachable
298
+ * here.
299
+ *
300
+ * - **Declared type is not consulted.** `Circuit.Pentode` is a *class*, and resolving a class to
301
+ * one part's model would claim every pentode in the corpus is an EL84 — the same reason
302
+ * `part-catalog.ts` leaves `declaredTypes` empty for op-amps.
303
+ * - **Pinout signature is not consulted.** Every pentode has the same electrodes, so a pinout
304
+ * match cannot tell an EL84 from a KT66. That is exactly the case where a wrong identity is
305
+ * worse than none.
306
+ *
307
+ * `lawKind` must match the law the device's class already implies, so an entry can only refine
308
+ * the law that class was going to get. A triode cannot be handed a pentode's fit by a catalog
309
+ * typo; it falls back instead.
310
+ *
311
+ * Returns `null` when nothing matches, which is a correct and expected answer — the caller keeps
312
+ * its device-class default. See `findGenericTubeFits`, which reports every such fallback, because
313
+ * a silent one is what let every amp in the corpus run a 6V6 fit.
314
+ */
315
+ export declare function registryLawFor(registry: PartRegistry, partNumber: string | null, lawKind: DeviceLaw["kind"]): DeviceLaw | null;
316
+ /**
317
+ * How far a part's output stops short of each supply rail, from the registry entry that names
318
+ * it, or `null` when the part is not in the registry or states none (the rails are then used as
319
+ * they are). Read from either an `ideal-opamp` law entry or the first section of a `sections`
320
+ * entry, because a declared `opamp` device never reaches the section lowering.
321
+ */
322
+ export declare function registryOpampSwingDrop(registry: PartRegistry, partNumber: string | null): number | null;
323
+ //# sourceMappingURL=registry.d.ts.map