@vessel-dsp/compiler 0.0.0-stage → 0.3.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 +118 -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 +348 -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 +3513 -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 +1742 -0
  63. package/dist/fixtures/circuits.d.ts.map +1 -0
  64. package/dist/fixtures/circuits.js +13896 -0
  65. package/dist/fixtures/circuits.js.map +1 -0
  66. package/dist/fixtures/expected.d.ts +261 -0
  67. package/dist/fixtures/expected.d.ts.map +1 -0
  68. package/dist/fixtures/expected.js +315 -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 +15 -0
  127. package/dist/part-catalog.d.ts.map +1 -0
  128. package/dist/part-catalog.js +6712 -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,472 @@
1
+ // Compile-time stamp partitioning for MNA blocks.
2
+ //
3
+ // Classifies stamps into linear vs nonlinear (port rows), control-dependent vs
4
+ // constant, and dynamic/reactive state. Precomputed at compile time and emitted on
5
+ // the program as data, avoiding runtime re-classification or dynamic row allocations.
6
+ import { atLeast, perBlock } from "./denomination.js";
7
+ /**
8
+ * Which fields of each stamp kind hold a node id.
9
+ *
10
+ * One table rather than a switch per consumer. `remapStampNodes` used to restate this as 150
11
+ * lines of `{ ...stamp, anode: row(stamp.anode), cathode: row(stamp.cathode) }`, and
12
+ * `stampTerminals` in `sparse-schedule.ts` restates a near-copy of it again for a different
13
+ * purpose. That is the same fact written twice, and the documented failure mode of getting it
14
+ * wrong is severe: a stamp kind missing a case kept its authored node ids while every other
15
+ * stamp in the block moved to row indices, which is an element quietly wired to the wrong
16
+ * nodes that still renders.
17
+ *
18
+ * `satisfies` checks this harder than the switch did. The mapped type requires **every** stamp
19
+ * kind to appear -- a new kind is a compile error here -- and requires each listed name to be
20
+ * an actual key of that kind's own variant, so a typo or a field moved during a refactor fails
21
+ * to build rather than silently renumbering nothing.
22
+ */
23
+ export const STAMP_SHAPES = {
24
+ conductance: { nodes: ["a", "b"], aux: 0, ports: "none", newton: false },
25
+ "controlled-conductance": {
26
+ nodes: ["a", "b"],
27
+ aux: 0,
28
+ ports: "none",
29
+ newton: false,
30
+ control: true,
31
+ },
32
+ "controlled-resistance": {
33
+ nodes: ["a", "b"],
34
+ aux: 0,
35
+ ports: "none",
36
+ newton: false,
37
+ control: true,
38
+ },
39
+ capacitor: {
40
+ nodes: ["a", "b"],
41
+ aux: 0,
42
+ ports: "none",
43
+ newton: false,
44
+ dynamic: true,
45
+ },
46
+ inductor: {
47
+ nodes: ["a", "b"],
48
+ aux: 0,
49
+ ports: "none",
50
+ newton: false,
51
+ dynamic: true,
52
+ },
53
+ switch: {
54
+ nodes: ["a", "b"],
55
+ aux: 0,
56
+ ports: "none",
57
+ newton: false,
58
+ control: true,
59
+ },
60
+ selector: {
61
+ nodes: ["common", "throwNode"],
62
+ aux: 0,
63
+ ports: "none",
64
+ newton: false,
65
+ control: true,
66
+ },
67
+ // A sine EMF is time-varying and linear: it differs between samples and is a constant
68
+ // within the system being solved at each one, exactly as the input source is.
69
+ "dc-source": {
70
+ nodes: ["positive", "negative"],
71
+ aux: 1,
72
+ ports: "none",
73
+ newton: false,
74
+ },
75
+ "ac-source": {
76
+ nodes: ["positive", "negative"],
77
+ aux: 1,
78
+ ports: "none",
79
+ newton: false,
80
+ },
81
+ "input-source": { nodes: ["node"], aux: 1, ports: "none", newton: false },
82
+ // A `coupled` port's write-back, same shape as `dc-source`: a same-sample source term,
83
+ // not a curve. The macro core it reads from is a delay line, which needs no Newton loop
84
+ // of its own either -- see couple.ts's header for why that stays true for this build.
85
+ "macro-audio-source": {
86
+ nodes: ["node"],
87
+ aux: 1,
88
+ ports: "none",
89
+ newton: false,
90
+ },
91
+ "logic-divider": {
92
+ nodes: ["clockNode", "qNode", "gndNode"],
93
+ aux: 1,
94
+ ports: "none",
95
+ newton: false,
96
+ dynamic: true,
97
+ },
98
+ // A VCCS is linear within a sample: transconductance couples the controlling input pair
99
+ // (inP, inN) to the output pair (outP, outN) across the Jacobian matrix.
100
+ vccs: {
101
+ nodes: ["outP", "outN", "inP", "inN"],
102
+ aux: 0,
103
+ ports: "none",
104
+ newton: false,
105
+ },
106
+ transformer: {
107
+ nodes: ["primaryPlus", "primaryMinus", "secondaryPlus", "secondaryMinus"],
108
+ aux: 1,
109
+ ports: "nodes-and-aux",
110
+ newton: false,
111
+ },
112
+ // A tank is stateful but not non-linear: within one sample it is a fixed conductance and a
113
+ // fixed Thevenin source. Its memory advances once per sample, after the solve. It still
114
+ // declares port rows, which is why `ports` and `newton` are separate facts here.
115
+ "spring-reverb": {
116
+ nodes: ["inputPlus", "inputMinus", "outputPlus", "outputMinus"],
117
+ aux: 1,
118
+ ports: "nodes",
119
+ newton: false,
120
+ dynamic: true,
121
+ },
122
+ // Rails make an op-amp saturate, which is the mode switch Newton is for. Without them it is
123
+ // a plain high-gain VCVS and the row stays linear.
124
+ "ideal-opamp": {
125
+ nodes: ["plus", "minus", "output"],
126
+ aux: 1,
127
+ ports: "railed-opamp",
128
+ newton: "railed-opamp",
129
+ },
130
+ diode: { nodes: ["anode", "cathode"], aux: 0, ports: "nodes", newton: true },
131
+ "tube-diode": {
132
+ nodes: ["plate", "cathode"],
133
+ aux: 0,
134
+ ports: "nodes",
135
+ newton: true,
136
+ },
137
+ bjt: {
138
+ nodes: ["base", "collector", "emitter"],
139
+ aux: 0,
140
+ ports: "nodes",
141
+ newton: true,
142
+ },
143
+ fet: {
144
+ nodes: ["gate", "drain", "source"],
145
+ aux: 0,
146
+ ports: "nodes",
147
+ newton: true,
148
+ },
149
+ triode: {
150
+ nodes: ["grid", "cathode", "plate"],
151
+ aux: 0,
152
+ ports: "nodes",
153
+ newton: true,
154
+ },
155
+ pentode: {
156
+ nodes: ["grid", "cathode", "plate", "screen"],
157
+ aux: 0,
158
+ ports: "nodes",
159
+ newton: true,
160
+ },
161
+ ota: {
162
+ nodes: ["plus", "minus", "bias", "output", "vee"],
163
+ aux: 0,
164
+ ports: "nodes",
165
+ newton: true,
166
+ },
167
+ optocoupler: {
168
+ nodes: ["ldrA", "ldrB", "ledAnode", "ledCathode"],
169
+ aux: 0,
170
+ ports: "nodes",
171
+ newton: true,
172
+ },
173
+ "analog-switch": {
174
+ nodes: ["a", "b", "control"],
175
+ aux: 0,
176
+ ports: "nodes",
177
+ newton: true,
178
+ },
179
+ compandor: {
180
+ nodes: ["rectIn", "rectCap", "cellIn", "sumNode", "vref"],
181
+ aux: 0,
182
+ ports: "nodes",
183
+ newton: true,
184
+ dynamic: true,
185
+ },
186
+ // Owns three consecutive auxiliary rows, which its matrix square covers but its port rows
187
+ // deliberately do not.
188
+ "clock-driver": {
189
+ nodes: ["cp1", "cp2", "vgg", "vdd", "ox1", "gnd"],
190
+ aux: 3,
191
+ ports: "nodes",
192
+ newton: true,
193
+ },
194
+ comparator: {
195
+ nodes: ["plus", "minus", "output", "vee"],
196
+ aux: 0,
197
+ ports: "nodes",
198
+ newton: true,
199
+ },
200
+ "linear-vca": {
201
+ nodes: ["plus", "minus", "output", "control"],
202
+ aux: 0,
203
+ ports: "nodes",
204
+ newton: true,
205
+ },
206
+ };
207
+ /** The shape entry for a stamp, with the table's per-kind literal types widened away. */
208
+ export function stampShape(stamp) {
209
+ return STAMP_SHAPES[stamp.kind];
210
+ }
211
+ /** Read a named node field off a stamp. */
212
+ export function stampNodeValue(stamp, field) {
213
+ return stamp[field];
214
+ }
215
+ /** The auxiliary rows a stamp owns, as absolute row indices. */
216
+ export function stampAuxRows(stamp, nodeCount) {
217
+ const shape = stampShape(stamp);
218
+ if (shape.aux === 0)
219
+ return [];
220
+ const base = nodeCount + (stamp.sourceIndex ?? 0);
221
+ return Array.from({ length: shape.aux }, (_, offset) => base + offset);
222
+ }
223
+ /**
224
+ * Rewrite a stamp's node fields through `row`.
225
+ *
226
+ * The spread reassigns keys the stamp already carries, so their position in the object is
227
+ * unchanged -- which matters because a lowered stamp is serialised into the emitted program
228
+ * and key order is part of those bytes.
229
+ */
230
+ export function remapStampNodes(stamp, row) {
231
+ const remapped = { ...stamp };
232
+ for (const field of stampShape(stamp).nodes) {
233
+ remapped[field] = row(stampNodeValue(stamp, field));
234
+ }
235
+ return remapped;
236
+ }
237
+ /**
238
+ * Does this stamp have to be re-evaluated inside a Newton loop?
239
+ *
240
+ * **The stamp-level twin of `partition.ts`'s `lawNeedsNewton`, and deliberately a second fact
241
+ * rather than a reuse of the first.** A law and its stamps are not the same list -- a jack's
242
+ * `port-engage` law lowers to a plain `conductance`, an `ac-source` law lowers to two stamps,
243
+ * an `open` law lowers to none -- and what a solver has to do is decided by what is in the
244
+ * block, not by what was in the document. `Block.linear` is read by the runtime to choose
245
+ * between one pass and an iteration cap, so it has to describe the stamps it will evaluate.
246
+ *
247
+ * The two mistakes are not symmetric, which is why the table has to be exhaustive. Calling a
248
+ * linear stamp nonlinear costs a few iterations; calling a nonlinear stamp linear makes
249
+ * `iterate` run one pass, evaluate the curve once from a zero start, and emit that as a solved
250
+ * answer -- no held sample, no telemetry, no diagnostic.
251
+ */
252
+ export function stampNeedsNewton(stamp) {
253
+ const { newton } = stampShape(stamp);
254
+ if (newton === "railed-opamp") {
255
+ return stamp.railHigh !== null;
256
+ }
257
+ return newton;
258
+ }
259
+ /**
260
+ * Which runtime control moves a stamp's value, or `null` when it is constant.
261
+ *
262
+ * Read off the shape table's `control` flag. A stamp missing that flag would silently make a
263
+ * block look `controlFree`, and step 4's whole promise is that such a block's matrix is
264
+ * constant -- so the omission would be a factorisation frozen against a knob that still moves.
265
+ */
266
+ export function stampControl(stamp) {
267
+ return stampShape(stamp).control === true
268
+ ? (stamp.control ?? null)
269
+ : null;
270
+ }
271
+ /**
272
+ * Which rows a stamp's device law actually writes into the Jacobian / RHS, or `null` when the
273
+ * stamp writes none.
274
+ *
275
+ * Read off the shape table's `ports`, which is a **separate fact from `newton`** and not a
276
+ * synonym for it. `transformer` and `spring-reverb` are both linear within a sample yet
277
+ * declare port rows, so deriving this from `stampNeedsNewton` would drop them.
278
+ *
279
+ * The returned order is not significant: `computeStampPartition` feeds these into a `Set` and
280
+ * sorts it.
281
+ */
282
+ export function stampPortRows(stamp, nodeCount) {
283
+ const shape = stampShape(stamp);
284
+ if (shape.ports === "none")
285
+ return null;
286
+ if (shape.ports === "railed-opamp" &&
287
+ stamp.railHigh === null) {
288
+ return null;
289
+ }
290
+ const rows = shape.nodes.map((field) => stampNodeValue(stamp, field));
291
+ return shape.ports === "nodes"
292
+ ? rows
293
+ : [...rows, ...stampAuxRows(stamp, nodeCount)];
294
+ }
295
+ /**
296
+ * Whether a stamp carries dynamic/reactive state (e.g. companion state across time steps).
297
+ */
298
+ export function stampIsDynamic(stamp) {
299
+ return stampShape(stamp).dynamic === true;
300
+ }
301
+ /**
302
+ * Compute the complete stamp partition for an MNA block.
303
+ */
304
+ export function computeStampPartition(stamps, nodeCount) {
305
+ const ports = new Set();
306
+ const linearStampIndices = [];
307
+ const nonlinearStampIndices = [];
308
+ const constantStampIndices = [];
309
+ const controlStampIndices = [];
310
+ const dynamicStampIndices = [];
311
+ for (let index = 0; index < stamps.length; index += 1) {
312
+ const stamp = stamps[index];
313
+ const rows = stampPortRows(stamp, nodeCount);
314
+ const isLinear = rows === null;
315
+ if (isLinear) {
316
+ linearStampIndices.push(index);
317
+ }
318
+ else {
319
+ nonlinearStampIndices.push(index);
320
+ for (const row of rows) {
321
+ if (row !== 0) {
322
+ ports.add(row);
323
+ }
324
+ }
325
+ }
326
+ const isControl = stampControl(stamp) !== null;
327
+ if (isControl) {
328
+ controlStampIndices.push(index);
329
+ }
330
+ const isDynamic = stampIsDynamic(stamp);
331
+ if (isDynamic) {
332
+ dynamicStampIndices.push(index);
333
+ }
334
+ const isSignalSource = stamp.kind === "input-source" ||
335
+ stamp.kind === "macro-audio-source" ||
336
+ stamp.kind === "ac-source";
337
+ if (isLinear && !isControl && !isDynamic && !isSignalSource) {
338
+ constantStampIndices.push(index);
339
+ }
340
+ }
341
+ // Auxiliary rows owned by sources whose non-ground terminals are all in `ports` have no
342
+ // off-diagonal coupling into the linear interior `L`. Leaving such an aux row in `L` creates an
343
+ // all-zero row in `M_lin[L][L]` when source resistance is zero, making `M_lin[L][L]` singular
344
+ // and dropping the source excitation. Promoting the aux row to `ports` couples it directly to
345
+ // the port equations.
346
+ for (const stamp of stamps) {
347
+ const auxRows = stampAuxRows(stamp, nodeCount);
348
+ if (auxRows.length === 0)
349
+ continue;
350
+ const nodes = stampShape(stamp).nodes.map((f) => stampNodeValue(stamp, f));
351
+ const nonGroundNodes = nodes.filter((n) => n !== 0);
352
+ if (nonGroundNodes.length > 0 &&
353
+ nonGroundNodes.every((n) => ports.has(n))) {
354
+ for (const aux of auxRows) {
355
+ ports.add(aux);
356
+ }
357
+ }
358
+ }
359
+ return {
360
+ portRows: [...ports].sort((a, b) => a - b),
361
+ linearStampIndices,
362
+ nonlinearStampIndices,
363
+ constantStampIndices,
364
+ controlStampIndices,
365
+ dynamicStampIndices,
366
+ };
367
+ }
368
+ /**
369
+ * Thresholds for solving a block by eliminating its linear interior and iterating only the
370
+ * nonlinear port rows (the runtime's Schur-complement path).
371
+ *
372
+ * All three must hold, and they are a cost model rather than a correctness rule -- eliminating
373
+ * a block that fails them is still a correct solve, just a slower one. The ratio says the
374
+ * interior is worth removing at all, the floor keeps the extra bookkeeping off small blocks,
375
+ * and the cubic saving estimates the dense-factorisation work actually avoided.
376
+ */
377
+ const ELIMINATE_MIN_UNKNOWN_RATIO = 2.4;
378
+ const ELIMINATE_MIN_UNKNOWNS = 12;
379
+ /**
380
+ * **Per BLOCK, and typed so it cannot be compared against a per-packet figure.** A prediction
381
+ * stated in these units was measured in packet units on 2026-09-11 and the resulting null was
382
+ * meaningless -- 84% of a block became 0.3% of a packet, and four independent checks passed it
383
+ * because each was correct at the level it was stated. The type now makes that comparison fail
384
+ * to compile rather than fail to be noticed.
385
+ */
386
+ const ELIMINATE_MIN_ALGEBRAIC_SAVING = perBlock(5000);
387
+ /**
388
+ * Whether a block should carry the eliminated (Schur-complement) solve.
389
+ *
390
+ * Shared by `lower.ts`, which decides it when a region is first lowered, and `couple.ts`,
391
+ * which must re-decide it after adding a stamp. The two carried byte-identical copies of this
392
+ * predicate, so a threshold changed in one and not the other would have made a coupled block
393
+ * disagree with the same block uncoupled.
394
+ */
395
+ export function shouldEliminate(unknowns, portRowCount, linear,
396
+ /**
397
+ * The document's `convergence-opt-in` flag. Where set, a nonlinear block with port rows is
398
+ * eliminated regardless of the cost rule -- it overrides the cost veto, but not the
399
+ * linear / zero-port guard above, which a portless or linear block cannot use elimination
400
+ * for at all.
401
+ */
402
+ convergenceOptIn = false) {
403
+ if (linear || portRowCount === 0) {
404
+ return false;
405
+ }
406
+ if (convergenceOptIn) {
407
+ return true;
408
+ }
409
+ if (derivedAdmissionEnabled()) {
410
+ return derivedShouldEliminate(unknowns, portRowCount);
411
+ }
412
+ return (unknowns / portRowCount >= ELIMINATE_MIN_UNKNOWN_RATIO &&
413
+ unknowns >= ELIMINATE_MIN_UNKNOWNS &&
414
+ atLeast(perBlock(unknowns ** 3 - portRowCount ** 3), ELIMINATE_MIN_ALGEBRAIC_SAVING));
415
+ }
416
+ /**
417
+ * **The measured dense-versus-sparse penalty**, from the WASM console. First measured 2026-08-25
418
+ * on two matched-size pairs (`boss-ch-1`/`orange-gro100` at 85 unknowns, `boss-ce-5`/
419
+ * `mxr-carbon-copy` at ~100) and pinned at 1.9 with the caveat that it "should grow with size".
420
+ * The 2026-09-17 re-fit measured the on/off cost of the five large amps (72-119 unknowns) directly
421
+ * -- jcm800 1.41x, vox 1.85x, peavey 2.57x, sunn 2.57x, rockerverb 2.98x -- and fitting
422
+ * `R = C (p/n)^b` to those gives **C = 7.2, b = 1.98 (R^2 = 0.79)**. The constant was ~4x too low
423
+ * (the "grows with size" direction was right) but the exponent is **~2, not the 1.5** the
424
+ * sparse-cost hypothesis predicted: the phase-2 cost regression is a too-low constant, not a
425
+ * size-dependent exponent. **Measured, not chosen** — which is the whole point of using it here.
426
+ */
427
+ const MEASURED_DENSIFICATION = 7.2;
428
+ /**
429
+ * The derived admission rule, behind `VESSEL_DERIVED_ELIMINATION=1`.
430
+ *
431
+ * **Cost goes as unknowns² × nonlinear count** — fitted across ten packets with WASM-measured cost,
432
+ * `R² = 0.674`. Elimination replaces an `n`-unknown iterated system with a `p`-unknown one, and its
433
+ * own factorisation is prepare-time for a linear interior. What it costs per sample is
434
+ * **densification**: the port system is dense where the original may have been sparse. So:
435
+ *
436
+ * ```
437
+ * admit when MEASURED_DENSIFICATION * p² < n² i.e. n/p > sqrt(7.2) = 2.68
438
+ * ```
439
+ *
440
+ * **No free constant.** `7.2` is the measured on/off of the two solve paths at 72-119 unknowns and
441
+ * the exponent is fitted (~2); neither was chosen to admit or reject any particular packet — which
442
+ * is the failure this replaces. Lowering `2.4` until it admitted the three packets whose saving had
443
+ * been predicted would have made the prediction and the admission one choice, and no later
444
+ * measurement could have separated them.
445
+ *
446
+ * **`ELIMINATE_MIN_UNKNOWNS` is still consulted and is still hand-set.** The model has **no term
447
+ * for fixed per-block overhead**, and without one the inequality admits every block with any
448
+ * interior — 241 of 241, which is not a predicate. The floor stands in for that missing term, and
449
+ * the sample straddling it (15 admitted blocks below, 31 above) is designed to retire it: if a
450
+ * 7-unknown block predicted at 84% delivers nothing, the overhead is the whole saving at that size
451
+ * and the crossover **is** the floor, derived rather than chosen.
452
+ */
453
+ export function derivedShouldEliminate(unknowns, portRowCount) {
454
+ if (portRowCount === 0) {
455
+ return false;
456
+ }
457
+ return (MEASURED_DENSIFICATION * portRowCount ** 2 < unknowns ** 2 &&
458
+ unknowns >= ELIMINATE_MIN_UNKNOWNS);
459
+ }
460
+ /**
461
+ * Off by default, and deliberately so: enabling it changes the compiled program of a large share of
462
+ * the corpus, which moves all seven gates at once and would not localise a failure. It is measured
463
+ * on a registered sample spanning the prediction range first — see
464
+ * `artifacts/docs/validation/2026-09-11-what-actually-drives-cpu-cost.md`.
465
+ */
466
+ function derivedAdmissionEnabled() {
467
+ // Guarded like the runtime's limiter trace: the compiler also runs in the browser, where
468
+ // `process` does not exist. A measurement-only switch does not earn a CompileOptions field
469
+ // threaded through lower.ts and couple.ts, so it stays an environment read.
470
+ return typeof process !== "undefined" && process.env?.VESSEL_DERIVED_ELIMINATION === "1";
471
+ }
472
+ //# sourceMappingURL=stamp-partition.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stamp-partition.js","sourceRoot":"","sources":["../src/stamp-partition.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,19 @@
1
+ import type { Block, Program } from "./types";
2
+ /**
3
+ * Read from the lowered `dc-source` stamps rather than the declared `rail`/`battery` devices,
4
+ * for the same reason `over-driven-node.ts` reads stamps: lowering is what collapses a
5
+ * redundant pair of supply declarations into one source, and the stamps are what the runtime
6
+ * executes and the deck is emitted from.
7
+ *
8
+ * Zero-volt sources are ignored. A `0 V` source is a ground tie, not a supply, and counting it
9
+ * would make an unpowered circuit look powered.
10
+ *
11
+ * **`dual-rail` is not the same as `positive-ground`, and conflating them would be the whole
12
+ * bug.** `klon-centaur` (`+9`, `+18`, `-9`) and `earthquaker-devices-plumes` (`+9`, `-9`) each
13
+ * derive a negative rail from a charge pump inside an ordinary center-negative pedal, so their
14
+ * *external* supply is the same as every other pedal's. What matters for a shared supply is
15
+ * whether a positive rail exists at all, which is why the chain rule below is stated that way
16
+ * and not as "positive-ground versus everything".
17
+ */
18
+ export declare function supplyReference(blocks: readonly Block[]): Program["supplyReference"];
19
+ //# sourceMappingURL=supply-reference.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"supply-reference.d.ts","sourceRoot":"","sources":["../src/supply-reference.ts"],"names":[],"mappings":"AAoBA,OAAO,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAE9C;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,eAAe,CAC9B,MAAM,EAAE,SAAS,KAAK,EAAE,GACtB,OAAO,CAAC,iBAAiB,CAAC,CA6B5B"}
@@ -0,0 +1,66 @@
1
+ // Which terminal of its supply a circuit references to ground.
2
+ //
3
+ // A pedal's DC jack is center-negative or center-positive, and **that convention is not an
4
+ // electrical fact this pipeline can read or needs to**: MNA solves node voltages against a
5
+ // reference node, so once the source says which node is the supply and what sign it carries,
6
+ // how the barrel is wired is invisible to the solve. Nothing in the corpus declares barrel
7
+ // polarity, and nothing here reads it.
8
+ //
9
+ // What *is* electrical is the **sign of the supply relative to ground**. A germanium PNP fuzz
10
+ // references the battery's positive terminal to ground and runs a negative rail — measured over
11
+ // the corpus, five packets do: `dallas-arbiter-fuzz-face-ac128`,
12
+ // `dallas-rangemaster-original-treble-booster`, `sola-sound-mki-tone-bender`,
13
+ // `sola-sound-tone-bender-professional-mkii`, and `maestro-fz-1a` at **-1.5 V**. Get that sign
14
+ // wrong and the PNP stages never reach conduction, which is silence rather than a wrong tone.
15
+ //
16
+ // The reason a program carries this at all is that it is the one supply fact a *chain* needs:
17
+ // two pedals on one physical supply must agree about which terminal is ground, and a chain of
18
+ // programs is where that can be checked (`src/runtime/supply-ground.ts`). It is not a
19
+ // simulation input — each program is solved against its own declared rails either way.
20
+ /**
21
+ * Read from the lowered `dc-source` stamps rather than the declared `rail`/`battery` devices,
22
+ * for the same reason `over-driven-node.ts` reads stamps: lowering is what collapses a
23
+ * redundant pair of supply declarations into one source, and the stamps are what the runtime
24
+ * executes and the deck is emitted from.
25
+ *
26
+ * Zero-volt sources are ignored. A `0 V` source is a ground tie, not a supply, and counting it
27
+ * would make an unpowered circuit look powered.
28
+ *
29
+ * **`dual-rail` is not the same as `positive-ground`, and conflating them would be the whole
30
+ * bug.** `klon-centaur` (`+9`, `+18`, `-9`) and `earthquaker-devices-plumes` (`+9`, `-9`) each
31
+ * derive a negative rail from a charge pump inside an ordinary center-negative pedal, so their
32
+ * *external* supply is the same as every other pedal's. What matters for a shared supply is
33
+ * whether a positive rail exists at all, which is why the chain rule below is stated that way
34
+ * and not as "positive-ground versus everything".
35
+ */
36
+ export function supplyReference(blocks) {
37
+ let positive = false;
38
+ let negative = false;
39
+ for (const block of blocks) {
40
+ if (block.kind !== "mna") {
41
+ continue;
42
+ }
43
+ for (const stamp of block.stamps) {
44
+ if (stamp.kind !== "dc-source") {
45
+ continue;
46
+ }
47
+ if (stamp.volts > 0) {
48
+ positive = true;
49
+ }
50
+ if (stamp.volts < 0) {
51
+ negative = true;
52
+ }
53
+ }
54
+ }
55
+ if (positive && negative) {
56
+ return "dual-rail";
57
+ }
58
+ if (positive) {
59
+ return "negative-ground";
60
+ }
61
+ if (negative) {
62
+ return "positive-ground";
63
+ }
64
+ return "unpowered";
65
+ }
66
+ //# sourceMappingURL=supply-reference.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"supply-reference.js","sourceRoot":"","sources":["../src/supply-reference.ts"],"names":[],"mappings":"AAAA,+DAA+D;AAC/D,EAAE;AACF,2FAA2F;AAC3F,2FAA2F;AAC3F,6FAA6F;AAC7F,2FAA2F;AAC3F,uCAAuC;AACvC,EAAE;AACF,8FAA8F;AAC9F,gGAAgG;AAChG,iEAAiE;AACjE,8EAA8E;AAC9E,+FAA+F;AAC/F,8FAA8F;AAC9F,EAAE;AACF,8FAA8F;AAC9F,8FAA8F;AAC9F,sFAAsF;AACtF,uFAAuF;AAIvF;;;;;;;;;;;;;;;GAeG;AACH,MAAM,UAAU,eAAe,CAC9B,MAAwB;IAExB,IAAI,QAAQ,GAAG,KAAK,CAAC;IACrB,IAAI,QAAQ,GAAG,KAAK,CAAC;IACrB,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC5B,IAAI,KAAK,CAAC,IAAI,KAAK,KAAK,EAAE,CAAC;YAC1B,SAAS;QACV,CAAC;QACD,KAAK,MAAM,KAAK,IAAI,KAAK,CAAC,MAAM,EAAE,CAAC;YAClC,IAAI,KAAK,CAAC,IAAI,KAAK,WAAW,EAAE,CAAC;gBAChC,SAAS;YACV,CAAC;YACD,IAAI,KAAK,CAAC,KAAK,GAAG,CAAC,EAAE,CAAC;gBACrB,QAAQ,GAAG,IAAI,CAAC;YACjB,CAAC;YACD,IAAI,KAAK,CAAC,KAAK,GAAG,CAAC,EAAE,CAAC;gBACrB,QAAQ,GAAG,IAAI,CAAC;YACjB,CAAC;QACF,CAAC;IACF,CAAC;IACD,IAAI,QAAQ,IAAI,QAAQ,EAAE,CAAC;QAC1B,OAAO,WAAW,CAAC;IACpB,CAAC;IACD,IAAI,QAAQ,EAAE,CAAC;QACd,OAAO,iBAAiB,CAAC;IAC1B,CAAC;IACD,IAAI,QAAQ,EAAE,CAAC;QACd,OAAO,iBAAiB,CAAC;IAC1B,CAAC;IACD,OAAO,WAAW,CAAC;AACpB,CAAC"}
@@ -0,0 +1,32 @@
1
+ import type { Netlist, SupplyShortWarning } from "./types";
2
+ /**
3
+ * A diode whose **anode sits on a supply node and whose cathode sits on ground** is forward
4
+ * biased by the whole supply. In a real circuit that is a dead short: the junction conducts
5
+ * until something fails.
6
+ *
7
+ * The runtime does not diverge on it, which is why this went unseen. `DIODE_VOLTAGE_LIMIT` caps
8
+ * the junction voltage at `0.9 V` before the current is computed, so instead of running away the
9
+ * diode settles at about **1 A** — and the supply is an ideal source, so it simply sinks that
10
+ * current and the pedal renders a plausible level. `ibanez-ts808` renders a gain of `0.506` with
11
+ * a 1 A short on its rail. The clamp is a convergence device that turned out to be hiding a
12
+ * circuit defect, which is the same shape as several other masks found the same day.
13
+ *
14
+ * **Directness is required, and it is what makes this precise.** The check asks that the anode
15
+ * be *on* a node a `voltage-source` or `rail` device terminates, and the cathode be ground
16
+ * itself. An indicator LED fed through a series resistor does not match, because its anode sits
17
+ * on the resistor's node rather than the supply's — so the common legitimate arrangement is
18
+ * excluded by construction rather than by a special case.
19
+ *
20
+ * Measured over the pedal corpus: **6 diodes across 6 packets** match structurally, of which the
21
+ * two that compile carry `1.074 A` each. The other four are in the unsupported population, so
22
+ * they are flagged the moment their packets compile.
23
+ *
24
+ * The warning states the structure and the polarity, and does **not** claim the source is wrong.
25
+ * A protection diode reversed in the source and a protection diode this pipeline has mis-read
26
+ * look identical from here; what makes it reportable is that either way the compiled circuit
27
+ * contains a short. Checked on `ibanez-ts808`: the source itself declares `anode` on the 9 V
28
+ * node and `cathode` on ground, so the role reading is not at fault there — but that is a
29
+ * per-packet finding rather than something this stage should assert.
30
+ */
31
+ export declare function findSupplyShorts(netlist: Netlist): readonly SupplyShortWarning[];
32
+ //# sourceMappingURL=supply-short.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"supply-short.d.ts","sourceRoot":"","sources":["../src/supply-short.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAU,OAAO,EAAU,kBAAkB,EAAE,MAAM,SAAS,CAAC;AAM3E;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,wBAAgB,gBAAgB,CAC/B,OAAO,EAAE,OAAO,GACd,SAAS,kBAAkB,EAAE,CAuD/B"}