@mica-js/core 0.0.1 → 0.1.1

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.
package/dist/index.js CHANGED
@@ -1,10 +1,2438 @@
1
- //#region src/index.ts
1
+ import { SCALAR_TYPES, createIRInterpreter, evaluateFloatInstruction, evaluateScalarInstruction } from "@mica-js/ir";
2
+ //#region src/scheduler/time.ts
3
+ var MAX_TIME_PS = 72e14;
4
+ var factors = {
5
+ ps: 1,
6
+ ns: 1e3,
7
+ us: 1e6,
8
+ ms: 1e9,
9
+ s: 0xe8d4a51000
10
+ };
11
+ function checked(value, name) {
12
+ if (typeof value !== "number" || !Number.isFinite(value) || !Number.isInteger(value)) throw new TypeError(`${name}=${String(value)} must be a finite integer number of picoseconds`);
13
+ if (value < 0 || value > 72e14) throw new RangeError(`${name}=${value} must be in [0, ${MAX_TIME_PS}] ps`);
14
+ return value === 0 ? 0 : value;
15
+ }
16
+ function timePs(value) {
17
+ return checked(value, "time");
18
+ }
19
+ function toTimePs(value, unit) {
20
+ if (typeof unit !== "string" || !Object.hasOwn(factors, unit)) throw new TypeError(`unit=${String(unit)} must be ps, ns, us, ms, or s`);
21
+ checked(value, `input (${unit})`);
22
+ return checked(value * factors[unit], `conversion ${String(value)} ${unit} to ps`);
23
+ }
24
+ function addTime(left, right) {
25
+ checked(left, "left");
26
+ checked(right, "right");
27
+ return checked(left + right, `addition ${left} + ${right}`);
28
+ }
29
+ function subtractTime(later, earlier) {
30
+ checked(later, "later");
31
+ checked(earlier, "earlier");
32
+ return checked(later - earlier, `subtraction ${later} - ${earlier}`);
33
+ }
34
+ //#endregion
35
+ //#region src/scheduler/errors.ts
36
+ var SchedulerError = class extends Error {
37
+ code;
38
+ name = "SchedulerError";
39
+ instanceId;
40
+ targetName;
41
+ timePs;
42
+ phase;
43
+ eventId;
44
+ steps;
45
+ entriesAtTime;
46
+ cause;
47
+ constructor(code, message, details) {
48
+ super(message, { cause: details.cause });
49
+ this.code = code;
50
+ this.instanceId = details.instanceId;
51
+ this.targetName = details.targetName;
52
+ this.timePs = details.timePs;
53
+ this.phase = details.phase;
54
+ this.eventId = details.eventId;
55
+ this.steps = details.steps ?? 0;
56
+ this.entriesAtTime = details.entriesAtTime ?? 0;
57
+ this.cause = details.cause;
58
+ }
59
+ };
60
+ //#endregion
61
+ //#region src/scheduler/queue.ts
62
+ var MinHeap = class {
63
+ compare;
64
+ #items = [];
65
+ constructor(compare) {
66
+ this.compare = compare;
67
+ }
68
+ peek() {
69
+ return this.#items[0];
70
+ }
71
+ /** Drops non-matching items so callers can release references while preserving heap order. */
72
+ retain(predicate) {
73
+ const kept = this.#items.filter(predicate);
74
+ this.#items.length = 0;
75
+ for (const item of kept) this.push(item);
76
+ }
77
+ push(item) {
78
+ const items = this.#items;
79
+ let index = items.length;
80
+ items.push(item);
81
+ while (index > 0) {
82
+ const parent = Math.floor((index - 1) / 2);
83
+ if (this.compare(items[parent], item) <= 0) break;
84
+ items[index] = items[parent];
85
+ index = parent;
86
+ }
87
+ items[index] = item;
88
+ }
89
+ pop() {
90
+ const items = this.#items;
91
+ const first = items[0];
92
+ const last = items.pop();
93
+ if (items.length === 0 || last === void 0) return first;
94
+ let index = 0;
95
+ while (index * 2 + 1 < items.length) {
96
+ let child = index * 2 + 1;
97
+ if (child + 1 < items.length && this.compare(items[child + 1], items[child]) < 0) child += 1;
98
+ if (this.compare(last, items[child]) <= 0) break;
99
+ items[index] = items[child];
100
+ index = child;
101
+ }
102
+ items[index] = last;
103
+ return first;
104
+ }
105
+ };
106
+ var EventIdPool = class {
107
+ maximum;
108
+ #highest = 0;
109
+ #released = [];
110
+ #head = 0;
111
+ constructor(maximum = 4294967295) {
112
+ this.maximum = maximum;
113
+ }
114
+ allocated(id) {
115
+ return id <= this.#highest;
116
+ }
117
+ take() {
118
+ if (this.#head < this.#released.length) {
119
+ const id = this.#released[this.#head++];
120
+ if (this.#head >= 1024 && this.#head * 2 >= this.#released.length) {
121
+ this.#released = this.#released.slice(this.#head);
122
+ this.#head = 0;
123
+ }
124
+ return id;
125
+ }
126
+ if (this.#highest === this.maximum) return void 0;
127
+ this.#highest += 1;
128
+ return this.#highest;
129
+ }
130
+ release(id) {
131
+ this.#released.push(id);
132
+ }
133
+ };
134
+ //#endregion
135
+ //#region src/scheduler/values.ts
136
+ function scalarType(value) {
137
+ return typeof value === "string" && SCALAR_TYPES.includes(value);
138
+ }
139
+ function captureArgs(types, args) {
140
+ if (!Array.isArray(args) || args.length !== types.length) throw new TypeError(`Expected ${types.length} scalar arguments`);
141
+ return Object.freeze(types.map((type, index) => {
142
+ const input = { type };
143
+ const node = {
144
+ op: "select",
145
+ operands: [
146
+ {
147
+ kind: "ssa",
148
+ value: { type: "bool" }
149
+ },
150
+ {
151
+ kind: "ssa",
152
+ value: input
153
+ },
154
+ {
155
+ kind: "ssa",
156
+ value: input
157
+ }
158
+ ],
159
+ results: [input]
160
+ };
161
+ const value = args[index];
162
+ const result = type === "f32" || type === "f64" ? evaluateFloatInstruction(node, [
163
+ true,
164
+ value,
165
+ value
166
+ ]) : evaluateScalarInstruction(node, [
167
+ true,
168
+ value,
169
+ value
170
+ ]);
171
+ return typeof result === "object" ? Object.freeze({
172
+ type: result.type,
173
+ bits: result.bits
174
+ }) : result;
175
+ }));
176
+ }
177
+ function synchronous(fn) {
178
+ return typeof fn === "function" && Object.prototype.toString.call(fn) === "[object Function]";
179
+ }
180
+ function returnedSynchronously(value) {
181
+ if (value === void 0) return true;
182
+ if (typeof value === "object" && value !== null || typeof value === "function") {
183
+ if (typeof value.then === "function") Promise.resolve(value).catch(() => void 0);
184
+ }
185
+ return false;
186
+ }
187
+ //#endregion
188
+ //#region src/scheduler/runtime.ts
189
+ function compare(left, right) {
190
+ if (left.time !== right.time) return left.time < right.time ? -1 : 1;
191
+ if (left.target.kind !== right.target.kind) return left.target.kind === "control" ? -1 : 1;
192
+ if (left.round !== right.round) return left.round < right.round ? -1 : 1;
193
+ return left.sequence < right.sequence ? -1 : left.sequence === right.sequence ? 0 : 1;
194
+ }
195
+ function limit(value, fallback, name) {
196
+ const result = value === void 0 ? fallback : value;
197
+ if (!Number.isSafeInteger(result) || result < 0) throw new SchedulerError("invalid-options", `${name}=${String(result)} must be a non-negative safe integer`, { timePs: 0 });
198
+ return result;
199
+ }
200
+ function createScheduler(options = {}) {
201
+ if (options === null || typeof options !== "object" || Array.isArray(options)) throw new SchedulerError("invalid-options", "Expected scheduler options", { timePs: 0 });
202
+ return new DeterministicScheduler(limit(options.maxStepsPerEntry, 1e6, "maxStepsPerEntry"), limit(options.maxEntriesPerTime, 1e6, "maxEntriesPerTime"));
203
+ }
204
+ var DeterministicScheduler = class {
205
+ #queue = new MinHeap(compare);
206
+ #instances = /* @__PURE__ */ new Map();
207
+ #identities = /* @__PURE__ */ new Set();
208
+ #destruction = /* @__PURE__ */ new Set();
209
+ #maxSteps;
210
+ #maxEntries;
211
+ #now = timePs(0);
212
+ #state = "paused";
213
+ #error;
214
+ #sequence = 0n;
215
+ #ordinaryRound = 0n;
216
+ #controlRound = 0n;
217
+ #ordinaryStarted = false;
218
+ #controlStarted = false;
219
+ #entries = 0;
220
+ #steps = 0;
221
+ #current;
222
+ #pauseRequested = false;
223
+ constructor(maxSteps, maxEntries) {
224
+ this.#maxSteps = maxSteps;
225
+ this.#maxEntries = maxEntries;
226
+ Object.freeze(this);
227
+ }
228
+ get now() {
229
+ return this.#now;
230
+ }
231
+ get state() {
232
+ return this.#state;
233
+ }
234
+ get error() {
235
+ return this.#error;
236
+ }
237
+ addInstance(definition) {
238
+ return this.#boundary(() => {
239
+ if (definition === null || typeof definition !== "object") throw this.#problem("invalid-instance", "Expected instance definition");
240
+ const { instanceId, targets, executor } = definition;
241
+ if (typeof instanceId !== "string" || instanceId.length === 0 || this.#identities.has(instanceId)) throw this.#problem("invalid-instance", `instanceId=${String(instanceId)} must be nonempty and never reused`);
242
+ if (!Array.isArray(targets)) throw this.#problem("invalid-target", "Expected target declarations");
243
+ const captured = /* @__PURE__ */ new Map();
244
+ for (const target of targets) {
245
+ if (target === null || typeof target !== "object" || captured.has(target)) throw this.#problem("invalid-target", "Expected distinct target declaration objects");
246
+ const { name, kind, params } = target;
247
+ if (typeof name !== "string" || name.length === 0 || kind !== "event" && kind !== "control" || !Array.isArray(params) || !Array.from(params).every(scalarType)) throw this.#problem("invalid-target", "Target requires name, event/control kind, and scalar params");
248
+ const snapshot = Object.freeze({
249
+ name,
250
+ kind,
251
+ params: Object.freeze([...params])
252
+ });
253
+ captured.set(target, snapshot);
254
+ captured.set(snapshot, snapshot);
255
+ }
256
+ if (executor === null || typeof executor !== "object" || !synchronous(executor.execute)) throw this.#problem("executor-contract", "Executor.execute must be a synchronous non-generator method");
257
+ const execute = executor.execute;
258
+ const handle = Object.freeze({
259
+ instanceId,
260
+ at: (target, deadline, args) => this.#boundary(() => this.#at(this.#instance(handle), target, deadline, args)),
261
+ cancel: (id) => this.#boundary(() => this.#cancel(this.#instance(handle), id))
262
+ });
263
+ const instance = {
264
+ handle,
265
+ targets: captured,
266
+ execute,
267
+ receiver: executor,
268
+ ids: new EventIdPool(),
269
+ active: /* @__PURE__ */ new Map(),
270
+ destroyRequested: false,
271
+ destroyed: false
272
+ };
273
+ this.#identities.add(instanceId);
274
+ this.#instances.set(handle, instance);
275
+ return handle;
276
+ });
277
+ }
278
+ enqueueControl(handle, target, args) {
279
+ this.#boundary(() => {
280
+ const instance = this.#instance(handle);
281
+ const captured = this.#target(instance, target, "control");
282
+ const inputs = this.#arguments(captured, args);
283
+ this.#queue.push({
284
+ instance,
285
+ target: captured,
286
+ args: inputs,
287
+ time: this.#now,
288
+ round: this.#controlStarted ? this.#controlRound + 1n : 0n,
289
+ sequence: this.#sequence++,
290
+ id: 0,
291
+ status: "pending"
292
+ });
293
+ });
294
+ }
295
+ destroyInstance(handle) {
296
+ const instance = this.#instances.get(handle);
297
+ if (instance === void 0 || instance.destroyed || instance.destroyRequested) {
298
+ if (this.#error !== void 0) throw this.#problem("scheduler-faulted", "Scheduler faulted; rebuild instead of resuming", this.#error);
299
+ throw this.#problem("invalid-instance", "Instance is foreign, destroyed, or stopping");
300
+ }
301
+ instance.destroyRequested = true;
302
+ this.#destruction.add(instance);
303
+ if (this.#current === void 0) this.#flushDestruction();
304
+ }
305
+ start(options = {}) {
306
+ return this.#boundary(() => {
307
+ if (this.#state === "running") throw this.#problem("reentrant-execution", "Cannot recursively start or step the scheduler");
308
+ if (options === null || typeof options !== "object" || Array.isArray(options)) throw this.#problem("invalid-options", "Expected run options");
309
+ const until = options.until === void 0 ? void 0 : this.#deadline(options.until);
310
+ const maxEntries = limit(options.maxEntries, Number.MAX_SAFE_INTEGER, "maxEntries");
311
+ this.#state = "running";
312
+ this.#pauseRequested = false;
313
+ let executed = 0;
314
+ const result = (reason) => Object.freeze({
315
+ timePs: this.#now,
316
+ executed,
317
+ reason
318
+ });
319
+ try {
320
+ for (;;) {
321
+ if (this.#pauseRequested) return result("paused");
322
+ if (executed >= maxEntries) return result("entry-limit");
323
+ const work = this.#next();
324
+ if (work === void 0 || until !== void 0 && work.time > until) {
325
+ if (until !== void 0) {
326
+ this.#setTime(until);
327
+ return result("until");
328
+ }
329
+ return result("empty");
330
+ }
331
+ this.#setTime(work.time);
332
+ this.#execute(work);
333
+ executed += 1;
334
+ }
335
+ } finally {
336
+ if (this.#error === void 0) this.#state = "paused";
337
+ }
338
+ });
339
+ }
340
+ pause() {
341
+ if (this.#state === "running") this.#pauseRequested = true;
342
+ }
343
+ step() {
344
+ return this.start({ maxEntries: 1 });
345
+ }
346
+ #at(instance, target, deadline, args) {
347
+ const captured = this.#target(instance, target, "event");
348
+ const time = this.#deadline(deadline);
349
+ const inputs = this.#arguments(captured, args);
350
+ const id = instance.ids.take();
351
+ if (id === void 0) throw this.#problem("event-id-exhausted", "All nonzero u32 event IDs are occupied");
352
+ const work = {
353
+ instance,
354
+ target: captured,
355
+ args: inputs,
356
+ time,
357
+ round: time === this.#now && this.#ordinaryStarted ? this.#ordinaryRound + 1n : 0n,
358
+ sequence: this.#sequence++,
359
+ id,
360
+ status: "pending"
361
+ };
362
+ instance.active.set(id, work);
363
+ this.#queue.push(work);
364
+ return id;
365
+ }
366
+ #cancel(instance, id) {
367
+ if (typeof id !== "number" || !Number.isInteger(id) || id < 0 || id > 4294967295 || !instance.ids.allocated(id)) throw this.#problem("invalid-event-id", `eventId=${String(id)} must be 0 or a previously allocated u32 in this instance`);
368
+ if (id === 0) return;
369
+ const work = instance.active.get(id);
370
+ if (work === void 0 || work.status !== "pending") return;
371
+ work.status = "cancelled";
372
+ instance.active.delete(id);
373
+ instance.ids.release(id);
374
+ }
375
+ #target(instance, target, kind) {
376
+ const captured = instance.targets.get(target);
377
+ if (captured === void 0 || captured.kind !== kind) throw this.#problem("invalid-target", `Expected this instance's registered ${kind} target`);
378
+ return captured;
379
+ }
380
+ #arguments(target, args) {
381
+ try {
382
+ return captureArgs(target.params, args);
383
+ } catch (cause) {
384
+ throw this.#problem("invalid-argument", `Arguments for ${target.name} must match its scalar signature`, cause);
385
+ }
386
+ }
387
+ #instance(handle) {
388
+ const instance = this.#instances.get(handle);
389
+ if (instance === void 0 || instance.destroyed || instance.destroyRequested) throw this.#problem("invalid-instance", "Instance is foreign, destroyed, or stopping");
390
+ return instance;
391
+ }
392
+ #deadline(value) {
393
+ const deadline = timePs(value);
394
+ if (deadline < this.#now) throw new RangeError(`deadline=${deadline} ps must be in [${this.#now}, ${MAX_TIME_PS}] ps (not in the past)`);
395
+ return deadline;
396
+ }
397
+ #next() {
398
+ for (;;) {
399
+ const work = this.#queue.peek();
400
+ if (work === void 0) return void 0;
401
+ if (work.status === "pending" && !work.instance.destroyed) return work;
402
+ this.#queue.pop();
403
+ }
404
+ }
405
+ #setTime(time) {
406
+ if (time === this.#now) return;
407
+ this.#now = time;
408
+ this.#entries = 0;
409
+ this.#ordinaryRound = 0n;
410
+ this.#controlRound = 0n;
411
+ this.#ordinaryStarted = false;
412
+ this.#controlStarted = false;
413
+ }
414
+ #execute(work) {
415
+ this.#current = work;
416
+ this.#steps = 0;
417
+ try {
418
+ if (this.#entries >= this.#maxEntries) throw this.#problem("time-budget-exhausted", `Exceeded ${this.#maxEntries} entries at ${this.#now} ps`);
419
+ if (this.#maxSteps === 0) throw this.#problem("entry-budget-exhausted", "Entry reference step budget is 0");
420
+ this.#queue.pop();
421
+ work.status = "running";
422
+ this.#entries += 1;
423
+ if (work.target.kind === "event") {
424
+ this.#ordinaryRound = work.round;
425
+ this.#ordinaryStarted = true;
426
+ } else {
427
+ this.#controlRound = work.round;
428
+ this.#controlStarted = true;
429
+ }
430
+ const remainingSteps = () => this.#maxSteps - this.#steps;
431
+ const context = Object.freeze({
432
+ instanceId: work.instance.handle.instanceId,
433
+ timePs: this.#now,
434
+ phase: work.target.kind,
435
+ eventId: work.id,
436
+ get remainingSteps() {
437
+ return remainingSteps();
438
+ },
439
+ consumeSteps: (count = 1) => this.#boundary(() => {
440
+ if (this.#current !== work || work.status !== "running") throw this.#problem("executor-contract", "Execution context is only valid during its entry");
441
+ if (!Number.isSafeInteger(count) || count < 0) throw this.#problem("executor-contract", "Step count must be a non-negative safe integer");
442
+ if (count > this.#maxSteps - this.#steps) throw this.#problem("entry-budget-exhausted", `Exceeded ${this.#maxSteps} reference steps`);
443
+ this.#steps += count;
444
+ })
445
+ });
446
+ if (!returnedSynchronously(work.instance.execute.call(work.instance.receiver, work.target, work.args, context))) throw this.#problem("executor-contract", "Executor must return synchronously with no result");
447
+ if (this.#error !== void 0) throw this.#error;
448
+ work.status = "completed";
449
+ if (work.id !== 0) {
450
+ work.instance.active.delete(work.id);
451
+ work.instance.ids.release(work.id);
452
+ }
453
+ } catch (cause) {
454
+ if (work.status === "running") work.status = "failed";
455
+ this.#fault(cause);
456
+ throw this.#error;
457
+ } finally {
458
+ this.#current = void 0;
459
+ this.#flushDestruction();
460
+ }
461
+ }
462
+ #flushDestruction() {
463
+ for (const instance of this.#destruction) {
464
+ for (const work of instance.active.values()) if (work.status === "pending") work.status = "cancelled";
465
+ instance.active.clear();
466
+ instance.destroyed = true;
467
+ this.#instances.delete(instance.handle);
468
+ }
469
+ this.#destruction.clear();
470
+ this.#queue.retain((work) => work.status === "pending" && !work.instance.destroyed);
471
+ }
472
+ #problem(code, message, cause) {
473
+ const current = this.#current;
474
+ return new SchedulerError(code, message, {
475
+ timePs: this.#now,
476
+ steps: current === void 0 ? 0 : this.#steps,
477
+ entriesAtTime: this.#entries,
478
+ cause,
479
+ ...current === void 0 ? {} : {
480
+ instanceId: current.instance.handle.instanceId,
481
+ targetName: current.target.name,
482
+ phase: current.target.kind,
483
+ eventId: current.id
484
+ }
485
+ });
486
+ }
487
+ #fault(cause) {
488
+ if (cause === this.#error && this.#error !== void 0) return;
489
+ const previous = this.#error;
490
+ const code = previous?.code ?? (cause instanceof SchedulerError ? cause.code : "execution-failed");
491
+ const message = previous?.message ?? (cause instanceof Error ? cause.message : "Entry execution failed");
492
+ this.#error = this.#problem(code, message, cause);
493
+ this.#state = "faulted";
494
+ }
495
+ #boundary(action) {
496
+ if (this.#error !== void 0) throw this.#problem("scheduler-faulted", "Scheduler faulted; rebuild instead of resuming", this.#error);
497
+ try {
498
+ return action();
499
+ } catch (cause) {
500
+ if (this.#current !== void 0) this.#fault(cause);
501
+ throw cause;
502
+ }
503
+ }
504
+ };
505
+ //#endregion
506
+ //#region src/scheduler/ir-adapter.ts
507
+ var unknownTarget = Object.freeze({
508
+ name: "unregistered",
509
+ kind: "event",
510
+ params: []
511
+ });
512
+ function createIRSchedulerInstance(scheduler, definition) {
513
+ if (definition === null || typeof definition !== "object") throw new SchedulerError("invalid-instance", "Expected IR instance definition", { timePs: scheduler.now });
514
+ const { instanceId, module, validation } = definition;
515
+ const captured = {
516
+ memories: module.memories,
517
+ functions: module.functions,
518
+ domainDeclarations: module.domainDeclarations,
519
+ instructions: module.instructions
520
+ };
521
+ const targets = /* @__PURE__ */ new Map();
522
+ const entries = /* @__PURE__ */ new Map();
523
+ for (const fn of captured.functions) {
524
+ if (fn.category !== "entry" && fn.category !== "lifecycle") continue;
525
+ const target = Object.freeze({
526
+ name: fn.name,
527
+ kind: fn.category === "entry" ? "event" : "control",
528
+ params: Object.freeze([...fn.params])
529
+ });
530
+ targets.set(fn.ref, target);
531
+ entries.set(fn.name, fn.ref);
532
+ }
533
+ let active;
534
+ const context = () => {
535
+ if (active === void 0) throw new SchedulerError("executor-contract", "IR kernel bridge requires an active scheduler entry", { timePs: scheduler.now });
536
+ return active;
537
+ };
538
+ const interpreter = createIRInterpreter(captured, {
539
+ validation,
540
+ maxSteps: Number.MAX_SAFE_INTEGER,
541
+ eventHost: {
542
+ now: () => context().timePs,
543
+ at: (target, deadline, args) => {
544
+ context();
545
+ return instance.at(targets.get(target) ?? unknownTarget, deadline, args);
546
+ },
547
+ cancel: (id) => {
548
+ context();
549
+ instance.cancel(id);
550
+ }
551
+ },
552
+ stepObserver: { beforeStep: () => context().consumeSteps() }
553
+ });
554
+ const instance = scheduler.addInstance({
555
+ instanceId,
556
+ targets: [...targets.values()],
557
+ executor: { execute(target, args, executionContext) {
558
+ active = executionContext;
559
+ try {
560
+ interpreter.execute(entries.get(target.name), args, {
561
+ instanceId: executionContext.instanceId,
562
+ virtualTime: executionContext.timePs
563
+ });
564
+ } finally {
565
+ active = void 0;
566
+ }
567
+ } }
568
+ });
569
+ return Object.freeze({
570
+ instance,
571
+ storage: interpreter.storage,
572
+ at: (target, deadline, args) => instance.at(targets.get(target) ?? unknownTarget, deadline, args),
573
+ cancel: (id) => instance.cancel(id),
574
+ enqueueControl: (target, args) => scheduler.enqueueControl(instance, targets.get(target) ?? unknownTarget, args)
575
+ });
576
+ }
577
+ //#endregion
578
+ //#region src/electrical/validation.ts
579
+ function invalidType(path, value, constraint) {
580
+ let display;
581
+ try {
582
+ display = String(value);
583
+ } catch {
584
+ display = "<unprintable>";
585
+ }
586
+ throw new TypeError(`${path}: ${display} must ${constraint}`);
587
+ }
588
+ function checkIdentity(value, path) {
589
+ if (typeof value !== "string" || value.length === 0) invalidType(path, value, "be a nonempty string");
590
+ }
591
+ function checkFinite(value, path) {
592
+ if (typeof value !== "number" || !Number.isFinite(value)) invalidType(path, value, "be a finite number");
593
+ }
594
+ function checkRange(value, min, max, path) {
595
+ if (value < min || value > max) throw new RangeError(`${path}: ${value} must be within [${min}, ${max}]`);
596
+ }
597
+ function checkInteger(value, min, max, path) {
598
+ checkFinite(value, path);
599
+ if (!Number.isInteger(value)) invalidType(path, value, "be an integer");
600
+ checkRange(value, min, max, path);
601
+ }
602
+ function checkSupply(value) {
603
+ checkFinite(value, "supplyVoltage");
604
+ if (value < 0) throw new RangeError(`supplyVoltage: ${value} must be nonnegative`);
605
+ }
606
+ function checkRecord(value, path, fields) {
607
+ if (value === null || typeof value !== "object" || Array.isArray(value)) invalidType(path, value, "be an object");
608
+ for (const key of Reflect.ownKeys(value)) if (typeof key !== "string" || !fields.includes(key)) invalidType(`${path}.${String(key)}`, Reflect.get(value, key), "be a supported field");
609
+ }
610
+ function checkBoolean(value, path) {
611
+ if (typeof value !== "boolean") invalidType(path, value, "be a boolean");
612
+ }
613
+ function checkChoice(value, choices, path) {
614
+ if (typeof value !== "string" || !choices.includes(value)) invalidType(path, value, `be one of ${choices.join(", ")}`);
615
+ return value;
616
+ }
617
+ //#endregion
618
+ //#region src/electrical/network.ts
619
+ function compareText$3(left, right) {
620
+ return left < right ? -1 : left > right ? 1 : 0;
621
+ }
622
+ function compareSources(left, right) {
623
+ return compareText$3(left.instanceId, right.instanceId) || compareText$3(left.pinName, right.pinName);
624
+ }
625
+ var PIN_FIELDS = [
626
+ "instanceId",
627
+ "pinName",
628
+ "powerReady",
629
+ "mode",
630
+ "outputEnabled",
631
+ "pull",
632
+ "digitalIntent",
633
+ "voltageIntent",
634
+ "model"
635
+ ];
636
+ var MODEL_FIELDS = [
637
+ "kind",
638
+ "minVoltage",
639
+ "maxVoltage",
640
+ "lowVoltage",
641
+ "highVoltage"
642
+ ];
643
+ var PIN_MODES = [
644
+ "high-z",
645
+ "push-pull",
646
+ "open-drain",
647
+ "analog"
648
+ ];
649
+ var PULLS = [
650
+ "none",
651
+ "up",
652
+ "down"
653
+ ];
654
+ var VOLTAGE_MODELS = ["ideal-voltage"];
655
+ function snapshotModel(value, path) {
656
+ checkRecord(value, path, MODEL_FIELDS);
657
+ const kind = checkChoice(value.kind, VOLTAGE_MODELS, `${path}.kind`);
658
+ const minVoltage = value.minVoltage;
659
+ const maxVoltage = value.maxVoltage;
660
+ const lowVoltage = value.lowVoltage;
661
+ const highVoltage = value.highVoltage;
662
+ checkFinite(minVoltage, `${path}.minVoltage`);
663
+ checkFinite(maxVoltage, `${path}.maxVoltage`);
664
+ if (minVoltage > maxVoltage) throw new RangeError(`${path}.minVoltage: ${minVoltage} must be <= maxVoltage ${maxVoltage}`);
665
+ checkFinite(lowVoltage, `${path}.lowVoltage`);
666
+ checkFinite(highVoltage, `${path}.highVoltage`);
667
+ checkRange(lowVoltage, minVoltage, maxVoltage, `${path}.lowVoltage`);
668
+ checkRange(highVoltage, minVoltage, maxVoltage, `${path}.highVoltage`);
669
+ if (lowVoltage > highVoltage) throw new RangeError(`${path}.lowVoltage: ${lowVoltage} must be <= highVoltage ${highVoltage}`);
670
+ return Object.freeze({
671
+ kind,
672
+ minVoltage,
673
+ maxVoltage,
674
+ lowVoltage,
675
+ highVoltage
676
+ });
677
+ }
678
+ /** Internal value capture shared with the topology layer; not exported from the package root. */
679
+ function snapshotElectricalPin(value, path) {
680
+ checkRecord(value, path, PIN_FIELDS);
681
+ const instanceId = value.instanceId;
682
+ const pinName = value.pinName;
683
+ const powerReady = value.powerReady;
684
+ const outputEnabled = value.outputEnabled;
685
+ const mode = checkChoice(value.mode, PIN_MODES, `${path}.mode`);
686
+ const pull = checkChoice(value.pull, PULLS, `${path}.pull`);
687
+ checkIdentity(instanceId, `${path}.instanceId`);
688
+ checkIdentity(pinName, `${path}.pinName`);
689
+ checkBoolean(powerReady, `${path}.powerReady`);
690
+ checkBoolean(outputEnabled, `${path}.outputEnabled`);
691
+ const model = snapshotModel(value.model, `${path}.model`);
692
+ let digitalIntent;
693
+ if ("digitalIntent" in value) {
694
+ const candidate = value.digitalIntent;
695
+ checkBoolean(candidate, `${path}.digitalIntent`);
696
+ digitalIntent = candidate;
697
+ }
698
+ let voltageIntent;
699
+ if ("voltageIntent" in value) {
700
+ const candidate = value.voltageIntent;
701
+ checkFinite(candidate, `${path}.voltageIntent`);
702
+ checkRange(candidate, model.minVoltage, model.maxVoltage, `${path}.voltageIntent`);
703
+ voltageIntent = candidate;
704
+ }
705
+ return Object.freeze({
706
+ instanceId,
707
+ pinName,
708
+ powerReady,
709
+ mode,
710
+ outputEnabled,
711
+ pull,
712
+ ...digitalIntent === void 0 ? {} : { digitalIntent },
713
+ ...voltageIntent === void 0 ? {} : { voltageIntent },
714
+ model
715
+ });
716
+ }
717
+ function snapshotNetwork(input) {
718
+ checkRecord(input, "network", ["networkId", "pins"]);
719
+ checkIdentity(input.networkId, "network.networkId");
720
+ if (!Array.isArray(input.pins)) invalidType("network.pins", input.pins, "be an array");
721
+ const sources = /* @__PURE__ */ new Map();
722
+ const pins = [];
723
+ for (let index = 0; index < input.pins.length; index += 1) {
724
+ const path = `network.pins[${index}]`;
725
+ const pin = snapshotElectricalPin(input.pins[index], path);
726
+ const names = sources.get(pin.instanceId) ?? /* @__PURE__ */ new Set();
727
+ if (names.has(pin.pinName)) invalidType(`${path}.pinName`, pin.pinName, `be unique within instance ${pin.instanceId}`);
728
+ names.add(pin.pinName);
729
+ sources.set(pin.instanceId, names);
730
+ pins.push(pin);
731
+ }
732
+ return Object.freeze({
733
+ networkId: input.networkId,
734
+ pins: Object.freeze(pins.sort(compareSources))
735
+ });
736
+ }
737
+ function snapshotContributions(contributions) {
738
+ return Object.freeze(contributions.map((contribution) => Object.freeze({
739
+ instanceId: contribution.instanceId,
740
+ pinName: contribution.pinName,
741
+ kind: contribution.kind,
742
+ strength: contribution.strength,
743
+ voltage: contribution.voltage
744
+ })).sort((left, right) => compareSources(left, right) || compareText$3(left.kind, right.kind)));
745
+ }
746
+ var ElectricalConflictError = class extends Error {
747
+ name = "ElectricalConflictError";
748
+ network;
749
+ contributions;
750
+ constructor(network, contributions) {
751
+ super(`Network ${network.networkId}: highest-priority voltage spread must be <= 0.001 V`);
752
+ this.network = snapshotNetwork(network);
753
+ this.contributions = snapshotContributions(contributions);
754
+ Object.freeze(this);
755
+ }
756
+ };
757
+ function activeVoltage(pin) {
758
+ if (!pin.outputEnabled) return void 0;
759
+ switch (pin.mode) {
760
+ case "high-z": return;
761
+ case "push-pull": return pin.digitalIntent === void 0 ? void 0 : pin.digitalIntent ? pin.model.highVoltage : pin.model.lowVoltage;
762
+ case "open-drain": return pin.digitalIntent === false ? pin.model.lowVoltage : void 0;
763
+ case "analog": return pin.voltageIntent;
764
+ }
765
+ }
766
+ function resolveElectricalNetwork(input) {
767
+ const network = snapshotNetwork(input);
768
+ const contributions = [];
769
+ for (const pin of network.pins) {
770
+ if (!pin.powerReady) continue;
771
+ const source = {
772
+ instanceId: pin.instanceId,
773
+ pinName: pin.pinName
774
+ };
775
+ const voltage = activeVoltage(pin);
776
+ if (voltage !== void 0) contributions.push({
777
+ ...source,
778
+ kind: "drive",
779
+ strength: "strong",
780
+ voltage
781
+ });
782
+ if (pin.pull !== "none") contributions.push({
783
+ ...source,
784
+ kind: "pull",
785
+ strength: "weak",
786
+ voltage: pin.pull === "up" ? pin.model.highVoltage : pin.model.lowVoltage
787
+ });
788
+ }
789
+ const snapshot = snapshotContributions(contributions);
790
+ if (snapshot.length === 0) return Object.freeze({
791
+ state: "floating",
792
+ digitalFallback: false,
793
+ contributions: snapshot
794
+ });
795
+ const strength = snapshot.some((contribution) => contribution.strength === "strong") ? "strong" : "weak";
796
+ let min = Infinity;
797
+ let max = -Infinity;
798
+ for (const contribution of snapshot) {
799
+ if (contribution.strength !== strength) continue;
800
+ min = Math.min(min, contribution.voltage);
801
+ max = Math.max(max, contribution.voltage);
802
+ }
803
+ if (max - min > .001) throw new ElectricalConflictError(network, snapshot);
804
+ return Object.freeze({
805
+ state: "driven",
806
+ voltage: min,
807
+ strength,
808
+ contributions: snapshot
809
+ });
810
+ }
811
+ //#endregion
812
+ //#region src/electrical/floating-input.ts
813
+ function hash32(text) {
814
+ let hash = 2166136261;
815
+ for (const byte of new TextEncoder().encode(text)) hash = Math.imul(hash ^ byte, 16777619) >>> 0;
816
+ return hash;
817
+ }
818
+ function xorshift32(state) {
819
+ state ^= state << 13;
820
+ state ^= state >>> 17;
821
+ state ^= state << 5;
822
+ return state >>> 0;
823
+ }
824
+ function mix32(value) {
825
+ value = Math.imul(value ^ value >>> 16, 2146121005);
826
+ value = Math.imul(value ^ value >>> 15, 2221713035);
827
+ return (value ^ value >>> 16) >>> 0;
828
+ }
829
+ function createFloatingInput(seed, instanceId, pinName) {
830
+ checkInteger(seed, 0, 4294967295, "seed");
831
+ checkIdentity(instanceId, "instanceId");
832
+ checkIdentity(pinName, "pinName");
833
+ let floatingCount = 0;
834
+ let episodeSeed = 0;
835
+ let initialVoltage;
836
+ let hasEpisode = false;
837
+ function beginFloating(supplyVoltage) {
838
+ if (supplyVoltage !== void 0) checkSupply(supplyVoltage);
839
+ floatingCount = floatingCount + 1 >>> 0;
840
+ episodeSeed = hash32(JSON.stringify([
841
+ "mica-floating-v2",
842
+ seed,
843
+ instanceId,
844
+ pinName,
845
+ floatingCount
846
+ ])) || 1;
847
+ initialVoltage = supplyVoltage === void 0 ? void 0 : xorshift32(episodeSeed) / 4294967296 * supplyVoltage;
848
+ hasEpisode = true;
849
+ }
850
+ function sample(supplyVoltage, timePs, enabled = true) {
851
+ checkBoolean(enabled, "enabled");
852
+ if (!enabled) return Object.freeze({ state: "disabled" });
853
+ checkSupply(supplyVoltage);
854
+ checkInteger(timePs, 0, 72e14, "timePs");
855
+ if (!hasEpisode) throw new Error("Call beginFloating before sampling");
856
+ if (initialVoltage === void 0) initialVoltage = xorshift32(episodeSeed) / 4294967296 * supplyVoltage;
857
+ const low = timePs >>> 0;
858
+ const high = Math.floor(timePs / 4294967296);
859
+ const noise = (mix32(mix32(episodeSeed ^ low) ^ high) / 4294967296 * 2 - 1) * .1;
860
+ return Object.freeze({
861
+ state: "ready",
862
+ voltage: Math.min(supplyVoltage, Math.max(0, initialVoltage + noise))
863
+ });
864
+ }
865
+ return Object.freeze({
866
+ beginFloating,
867
+ sample
868
+ });
869
+ }
870
+ //#endregion
871
+ //#region src/topology/runtime.ts
872
+ var PIN_KINDS = [
873
+ "signal",
874
+ "power",
875
+ "gnd"
876
+ ];
877
+ var CONNECTION_STATES = ["open", "closed"];
878
+ function compareText$2(left, right) {
879
+ return left < right ? -1 : left > right ? 1 : 0;
880
+ }
881
+ function comparePins(left, right) {
882
+ return compareText$2(left.instanceId, right.instanceId) || compareText$2(left.pinName, right.pinName);
883
+ }
884
+ function pinKey$1(instanceId, pinName) {
885
+ return JSON.stringify([instanceId, pinName]);
886
+ }
887
+ function connectionKey(instanceId, connectionId) {
888
+ return JSON.stringify([instanceId, connectionId]);
889
+ }
890
+ /**
891
+ * Escapes the two characters that carry structure in a network ID. Unlike
892
+ * `encodeURIComponent`, this accepts every JavaScript string, including lone surrogates.
893
+ */
894
+ function escapeNetworkIdentity(value) {
895
+ return value.replaceAll("%", "%25").replaceAll("/", "%2F");
896
+ }
897
+ function capturePinReference(value, path, pinsByKey) {
898
+ checkRecord(value, path, ["instanceId", "pinName"]);
899
+ const instanceId = value.instanceId;
900
+ const pinName = value.pinName;
901
+ checkIdentity(instanceId, `${path}.instanceId`);
902
+ checkIdentity(pinName, `${path}.pinName`);
903
+ const declared = pinsByKey.get(pinKey$1(instanceId, pinName));
904
+ if (declared === void 0) throw new RangeError(`${path}: (${JSON.stringify(instanceId)}, ${JSON.stringify(pinName)}) must reference a declared pin`);
905
+ return declared;
906
+ }
907
+ function captureDefinition(value) {
908
+ checkRecord(value, "definition", [
909
+ "pins",
910
+ "wires",
911
+ "connections"
912
+ ]);
913
+ const rawPins = value.pins;
914
+ if (!Array.isArray(rawPins)) invalidType("definition.pins", rawPins, "be an array");
915
+ const pins = [];
916
+ const pinsByKey = /* @__PURE__ */ new Map();
917
+ const instances = /* @__PURE__ */ new Set();
918
+ for (let index = 0; index < rawPins.length; index += 1) {
919
+ const path = `definition.pins[${index}]`;
920
+ const entry = rawPins[index];
921
+ checkRecord(entry, path, [
922
+ "instanceId",
923
+ "pinName",
924
+ "kind"
925
+ ]);
926
+ const instanceId = entry.instanceId;
927
+ const pinName = entry.pinName;
928
+ checkIdentity(instanceId, `${path}.instanceId`);
929
+ checkIdentity(pinName, `${path}.pinName`);
930
+ const kind = checkChoice(entry.kind, PIN_KINDS, `${path}.kind`);
931
+ const key = pinKey$1(instanceId, pinName);
932
+ if (pinsByKey.has(key)) invalidType(`${path}.pinName`, pinName, `be unique for instance ${JSON.stringify(instanceId)}`);
933
+ const ref = Object.freeze({
934
+ instanceId,
935
+ pinName
936
+ });
937
+ const pin = Object.freeze({
938
+ instanceId,
939
+ pinName,
940
+ kind,
941
+ key,
942
+ ref
943
+ });
944
+ pinsByKey.set(key, pin);
945
+ pins.push(pin);
946
+ instances.add(instanceId);
947
+ }
948
+ const rawWires = value.wires;
949
+ if (!Array.isArray(rawWires)) invalidType("definition.wires", rawWires, "be an array");
950
+ const wires = [];
951
+ const wireIds = /* @__PURE__ */ new Set();
952
+ for (let index = 0; index < rawWires.length; index += 1) {
953
+ const path = `definition.wires[${index}]`;
954
+ const entry = rawWires[index];
955
+ checkRecord(entry, path, [
956
+ "wireId",
957
+ "pins",
958
+ "globalGnd"
959
+ ]);
960
+ const wireId = entry.wireId;
961
+ checkIdentity(wireId, `${path}.wireId`);
962
+ if (wireIds.has(wireId)) invalidType(`${path}.wireId`, wireId, "be unique among external wires");
963
+ wireIds.add(wireId);
964
+ const rawWirePins = entry.pins;
965
+ if (!Array.isArray(rawWirePins)) invalidType(`${path}.pins`, rawWirePins, "be an array");
966
+ if (rawWirePins.length === 0) throw new RangeError(`${path}.pins: expected at least one pin`);
967
+ const wirePins = [];
968
+ const wirePinKeys = /* @__PURE__ */ new Set();
969
+ for (let pinIndex = 0; pinIndex < rawWirePins.length; pinIndex += 1) {
970
+ const pin = capturePinReference(rawWirePins[pinIndex], `${path}.pins[${pinIndex}]`, pinsByKey);
971
+ if (wirePinKeys.has(pin.key)) invalidType(`${path}.pins[${pinIndex}].pinName`, pin.pinName, `be unique within wire ${JSON.stringify(wireId)}`);
972
+ wirePinKeys.add(pin.key);
973
+ wirePins.push(pin);
974
+ }
975
+ let globalGnd = false;
976
+ if ("globalGnd" in entry) {
977
+ checkBoolean(entry.globalGnd, `${path}.globalGnd`);
978
+ globalGnd = entry.globalGnd;
979
+ }
980
+ wires.push(Object.freeze({
981
+ wireId,
982
+ pins: Object.freeze(wirePins),
983
+ globalGnd
984
+ }));
985
+ }
986
+ const rawConnections = value.connections;
987
+ if (!Array.isArray(rawConnections)) invalidType("definition.connections", rawConnections, "be an array");
988
+ const connections = [];
989
+ const connectionsByKey = /* @__PURE__ */ new Map();
990
+ for (let index = 0; index < rawConnections.length; index += 1) {
991
+ const path = `definition.connections[${index}]`;
992
+ const entry = rawConnections[index];
993
+ checkRecord(entry, path, [
994
+ "instanceId",
995
+ "connectionId",
996
+ "pins",
997
+ "initial"
998
+ ]);
999
+ const instanceId = entry.instanceId;
1000
+ const connectionId = entry.connectionId;
1001
+ checkIdentity(instanceId, `${path}.instanceId`);
1002
+ checkIdentity(connectionId, `${path}.connectionId`);
1003
+ if (!instances.has(instanceId)) throw new RangeError(`${path}.instanceId: ${JSON.stringify(instanceId)} must reference a declared instance`);
1004
+ const key = connectionKey(instanceId, connectionId);
1005
+ if (connectionsByKey.has(key)) invalidType(`${path}.connectionId`, connectionId, `be unique for instance ${JSON.stringify(instanceId)}`);
1006
+ const rawEndpoints = entry.pins;
1007
+ if (!Array.isArray(rawEndpoints) || rawEndpoints.length !== 2) invalidType(`${path}.pins`, rawEndpoints, "be an array of exactly two pin names");
1008
+ const first = rawEndpoints[0];
1009
+ const second = rawEndpoints[1];
1010
+ checkIdentity(first, `${path}.pins[0]`);
1011
+ checkIdentity(second, `${path}.pins[1]`);
1012
+ if (first === second) invalidType(`${path}.pins[1]`, second, `differ from ${JSON.stringify(first)}`);
1013
+ const pinA = pinsByKey.get(pinKey$1(instanceId, first));
1014
+ if (pinA === void 0) throw new RangeError(`${path}.pins[0]: ${JSON.stringify(first)} must reference a pin declared by instance ${JSON.stringify(instanceId)}`);
1015
+ const pinB = pinsByKey.get(pinKey$1(instanceId, second));
1016
+ if (pinB === void 0) throw new RangeError(`${path}.pins[1]: ${JSON.stringify(second)} must reference a pin declared by instance ${JSON.stringify(instanceId)}`);
1017
+ let initial = "open";
1018
+ if ("initial" in entry) initial = checkChoice(entry.initial, CONNECTION_STATES, `${path}.initial`);
1019
+ const connection = Object.freeze({
1020
+ instanceId,
1021
+ connectionId,
1022
+ pinA,
1023
+ pinB,
1024
+ initial,
1025
+ key
1026
+ });
1027
+ connectionsByKey.set(key, connection);
1028
+ connections.push(connection);
1029
+ }
1030
+ connections.sort((left, right) => compareText$2(left.instanceId, right.instanceId) || compareText$2(left.connectionId, right.connectionId));
1031
+ return Object.freeze({
1032
+ pins: Object.freeze(pins),
1033
+ pinsByKey,
1034
+ instances,
1035
+ wires: Object.freeze(wires),
1036
+ connections: Object.freeze(connections),
1037
+ connectionsByKey
1038
+ });
1039
+ }
2
1040
  /**
3
- * Placeholder export so the build and test pipeline has something to verify.
4
- * Replace with the virtual time and event scheduler API.
1041
+ * Internal single-read snapshot shared with the power layer; not exported from the package root.
1042
+ * Passing this frozen value to `createTopologyRuntime` cannot observe later input mutation.
5
1043
  */
1044
+ function captureTopologyDefinition(value) {
1045
+ const captured = captureDefinition(value);
1046
+ return Object.freeze({
1047
+ pins: Object.freeze(captured.pins.map((pin) => Object.freeze({
1048
+ instanceId: pin.instanceId,
1049
+ pinName: pin.pinName,
1050
+ kind: pin.kind
1051
+ }))),
1052
+ wires: Object.freeze(captured.wires.map((wire) => Object.freeze({
1053
+ wireId: wire.wireId,
1054
+ pins: Object.freeze(wire.pins.map((pin) => pin.ref)),
1055
+ globalGnd: wire.globalGnd
1056
+ }))),
1057
+ connections: Object.freeze(captured.connections.map((connection) => Object.freeze({
1058
+ instanceId: connection.instanceId,
1059
+ connectionId: connection.connectionId,
1060
+ pins: Object.freeze([connection.pinA.pinName, connection.pinB.pinName]),
1061
+ initial: connection.initial
1062
+ })))
1063
+ });
1064
+ }
1065
+ function capturePatch(value, definition) {
1066
+ checkRecord(value, "patch", ["instanceId", "connections"]);
1067
+ const instanceId = value.instanceId;
1068
+ checkIdentity(instanceId, "patch.instanceId");
1069
+ if (!definition.instances.has(instanceId)) throw new RangeError(`patch.instanceId: ${JSON.stringify(instanceId)} must reference a declared instance`);
1070
+ const rawConnections = value.connections;
1071
+ if (!Array.isArray(rawConnections)) invalidType("patch.connections", rawConnections, "be an array");
1072
+ if (rawConnections.length === 0) throw new RangeError("patch.connections: expected at least one connection update");
1073
+ const seen = /* @__PURE__ */ new Set();
1074
+ const entries = [];
1075
+ for (let index = 0; index < rawConnections.length; index += 1) {
1076
+ const path = `patch.connections[${index}]`;
1077
+ const entry = rawConnections[index];
1078
+ checkRecord(entry, path, ["connectionId", "state"]);
1079
+ const connectionId = entry.connectionId;
1080
+ checkIdentity(connectionId, `${path}.connectionId`);
1081
+ const state = checkChoice(entry.state, CONNECTION_STATES, `${path}.state`);
1082
+ const key = connectionKey(instanceId, connectionId);
1083
+ if (!definition.connectionsByKey.has(key)) throw new RangeError(`${path}.connectionId: ${JSON.stringify(connectionId)} must reference a connection declared by instance ${JSON.stringify(instanceId)}`);
1084
+ if (seen.has(key)) invalidType(`${path}.connectionId`, connectionId, `be unique within patch for instance ${JSON.stringify(instanceId)}`);
1085
+ seen.add(key);
1086
+ entries.push(Object.freeze({
1087
+ key,
1088
+ state
1089
+ }));
1090
+ }
1091
+ return Object.freeze({
1092
+ instanceId,
1093
+ entries: Object.freeze(entries)
1094
+ });
1095
+ }
1096
+ function capturePinState(value, path, definition) {
1097
+ const state = snapshotElectricalPin(value, path);
1098
+ const key = pinKey$1(state.instanceId, state.pinName);
1099
+ if (!definition.pinsByKey.has(key)) throw new RangeError(`${path}: (${JSON.stringify(state.instanceId)}, ${JSON.stringify(state.pinName)}) must reference a declared pin`);
1100
+ return Object.freeze({
1101
+ key,
1102
+ state
1103
+ });
1104
+ }
1105
+ function captureFloating(options) {
1106
+ if (!("floating" in options)) return void 0;
1107
+ const value = options.floating;
1108
+ checkRecord(value, "options.floating", [
1109
+ "seed",
1110
+ "supplyVoltage",
1111
+ "samplingEnabled"
1112
+ ]);
1113
+ const seed = value.seed;
1114
+ checkInteger(seed, 0, 4294967295, "options.floating.seed");
1115
+ const supplyVoltage = value.supplyVoltage;
1116
+ if (typeof supplyVoltage !== "function") invalidType("options.floating.supplyVoltage", supplyVoltage, "be a function");
1117
+ let samplingEnabled = () => true;
1118
+ if ("samplingEnabled" in value) {
1119
+ const candidate = value.samplingEnabled;
1120
+ if (typeof candidate !== "function") invalidType("options.floating.samplingEnabled", candidate, "be a function");
1121
+ samplingEnabled = candidate;
1122
+ }
1123
+ return Object.freeze({
1124
+ seed,
1125
+ supplyVoltage,
1126
+ samplingEnabled
1127
+ });
1128
+ }
1129
+ function initialConnectionStates(definition) {
1130
+ const states = /* @__PURE__ */ new Map();
1131
+ for (const connection of definition.connections) states.set(connection.key, connection.initial);
1132
+ return states;
1133
+ }
1134
+ function buildComponents(definition, connectionStates) {
1135
+ const orderedPins = [...definition.pins].sort(comparePins);
1136
+ const indices = /* @__PURE__ */ new Map();
1137
+ orderedPins.forEach((pin, index) => indices.set(pin.key, index));
1138
+ const parent = orderedPins.map((_, index) => index);
1139
+ const find = (node) => {
1140
+ let root = node;
1141
+ while (parent[root] !== root) {
1142
+ const next = parent[root];
1143
+ parent[root] = parent[next];
1144
+ root = next;
1145
+ }
1146
+ return root;
1147
+ };
1148
+ const union = (left, right) => {
1149
+ const leftRoot = find(left);
1150
+ const rightRoot = find(right);
1151
+ if (leftRoot !== rightRoot) parent[rightRoot] = leftRoot;
1152
+ };
1153
+ for (const wire of definition.wires) {
1154
+ const first = wire.pins[0];
1155
+ if (first === void 0) continue;
1156
+ const firstIndex = indices.get(first.key);
1157
+ if (firstIndex === void 0) continue;
1158
+ for (let index = 1; index < wire.pins.length; index += 1) {
1159
+ const pin = wire.pins[index];
1160
+ const pinIndex = pin === void 0 ? void 0 : indices.get(pin.key);
1161
+ if (pinIndex !== void 0) union(firstIndex, pinIndex);
1162
+ }
1163
+ }
1164
+ for (const connection of definition.connections) {
1165
+ if (connectionStates.get(connection.key) !== "closed") continue;
1166
+ const left = indices.get(connection.pinA.key);
1167
+ const right = indices.get(connection.pinB.key);
1168
+ if (left !== void 0 && right !== void 0) union(left, right);
1169
+ }
1170
+ const groups = /* @__PURE__ */ new Map();
1171
+ for (const pin of orderedPins) {
1172
+ const index = indices.get(pin.key);
1173
+ if (index === void 0) continue;
1174
+ const root = find(index);
1175
+ const group = groups.get(root);
1176
+ if (group === void 0) groups.set(root, [pin]);
1177
+ else group.push(pin);
1178
+ }
1179
+ const groundedRoots = /* @__PURE__ */ new Set();
1180
+ for (const wire of definition.wires) {
1181
+ if (!wire.globalGnd) continue;
1182
+ const first = wire.pins[0];
1183
+ const firstIndex = first === void 0 ? void 0 : indices.get(first.key);
1184
+ if (firstIndex !== void 0) groundedRoots.add(find(firstIndex));
1185
+ }
1186
+ const components = [];
1187
+ for (const [root, pins] of groups) {
1188
+ const canonical = pins[0];
1189
+ if (canonical === void 0) continue;
1190
+ components.push(Object.freeze({
1191
+ networkId: `net:${escapeNetworkIdentity(canonical.instanceId)}/${escapeNetworkIdentity(canonical.pinName)}`,
1192
+ pins: Object.freeze(pins),
1193
+ groundReady: pins.some((pin) => pin.kind === "gnd") && groundedRoots.has(root)
1194
+ }));
1195
+ }
1196
+ components.sort((left, right) => compareText$2(left.networkId, right.networkId));
1197
+ return Object.freeze(components);
1198
+ }
1199
+ function indexComponents(components) {
1200
+ const byPin = /* @__PURE__ */ new Map();
1201
+ for (const component of components) for (const pin of component.pins) byPin.set(pin.key, component);
1202
+ return byPin;
1203
+ }
1204
+ function resolveComponent(component, pinStates) {
1205
+ const pins = component.pins.map((pin) => {
1206
+ const state = pinStates.get(pin.key);
1207
+ if (state === void 0) throw new Error(`Invariant: missing committed pin state for (${pin.instanceId}, ${pin.pinName})`);
1208
+ return state;
1209
+ });
1210
+ return resolveElectricalNetwork({
1211
+ networkId: component.networkId,
1212
+ pins
1213
+ });
1214
+ }
1215
+ function readySupply(options, pin) {
1216
+ if (!options.samplingEnabled(pin)) return void 0;
1217
+ return options.supplyVoltage(pin);
1218
+ }
1219
+ var TopologyRuntimeImpl = class {
1220
+ #scheduler;
1221
+ #definition;
1222
+ #floatingOptions;
1223
+ #target;
1224
+ #instance;
1225
+ #connectionStates;
1226
+ #pinStates;
1227
+ #components;
1228
+ #componentByPin;
1229
+ #results;
1230
+ #floating = /* @__PURE__ */ new Map();
1231
+ #commands = [];
1232
+ #destroyed = false;
1233
+ constructor(scheduler, definitionInput, optionsInput) {
1234
+ const definition = captureDefinition(definitionInput);
1235
+ checkRecord(optionsInput, "options", [
1236
+ "instanceId",
1237
+ "pinStates",
1238
+ "floating"
1239
+ ]);
1240
+ const instanceId = optionsInput.instanceId;
1241
+ checkIdentity(instanceId, "options.instanceId");
1242
+ const rawPinStates = optionsInput.pinStates;
1243
+ if (!Array.isArray(rawPinStates)) invalidType("options.pinStates", rawPinStates, "be an array");
1244
+ const pinStates = /* @__PURE__ */ new Map();
1245
+ for (let index = 0; index < rawPinStates.length; index += 1) {
1246
+ const path = `options.pinStates[${index}]`;
1247
+ const captured = capturePinState(rawPinStates[index], path, definition);
1248
+ if (pinStates.has(captured.key)) invalidType(`${path}.pinName`, captured.state.pinName, "be unique among initial pin states");
1249
+ pinStates.set(captured.key, captured.state);
1250
+ }
1251
+ for (const pin of definition.pins) if (!pinStates.has(pin.key)) throw new RangeError(`options.pinStates: missing state for (${JSON.stringify(pin.instanceId)}, ${JSON.stringify(pin.pinName)})`);
1252
+ const floatingOptions = captureFloating(optionsInput);
1253
+ const connectionStates = initialConnectionStates(definition);
1254
+ const components = buildComponents(definition, connectionStates);
1255
+ const results = /* @__PURE__ */ new Map();
1256
+ for (const component of components) results.set(component.networkId, resolveComponent(component, pinStates));
1257
+ this.#scheduler = scheduler;
1258
+ this.#definition = definition;
1259
+ this.#floatingOptions = floatingOptions;
1260
+ this.#connectionStates = connectionStates;
1261
+ this.#pinStates = pinStates;
1262
+ this.#components = components;
1263
+ this.#componentByPin = indexComponents(components);
1264
+ this.#results = results;
1265
+ this.#updateFloating(components, results);
1266
+ this.#target = Object.freeze({
1267
+ name: "topology.commit",
1268
+ kind: "control",
1269
+ params: Object.freeze([])
1270
+ });
1271
+ this.#instance = scheduler.addInstance({
1272
+ instanceId,
1273
+ targets: [this.#target],
1274
+ executor: { execute: () => this.#runNextCommand() }
1275
+ });
1276
+ Object.freeze(this);
1277
+ }
1278
+ get instance() {
1279
+ return this.#instance;
1280
+ }
1281
+ submit(patch) {
1282
+ this.#enqueue({
1283
+ kind: "patch",
1284
+ patch: capturePatch(patch, this.#definition)
1285
+ });
1286
+ }
1287
+ updatePinStates(states) {
1288
+ if (!Array.isArray(states)) invalidType("states", states, "be an array");
1289
+ if (states.length === 0) return;
1290
+ const updates = [];
1291
+ const seen = /* @__PURE__ */ new Set();
1292
+ for (let index = 0; index < states.length; index += 1) {
1293
+ const captured = capturePinState(states[index], `states[${index}]`, this.#definition);
1294
+ if (seen.has(captured.key)) invalidType(`states[${index}].pinName`, captured.state.pinName, "be unique within one pin-state update");
1295
+ seen.add(captured.key);
1296
+ updates.push(captured);
1297
+ }
1298
+ this.#enqueue({
1299
+ kind: "pins",
1300
+ updates: Object.freeze(updates)
1301
+ });
1302
+ }
1303
+ refresh(pins) {
1304
+ if (pins === void 0) {
1305
+ this.#enqueue({
1306
+ kind: "refresh",
1307
+ pins: void 0
1308
+ });
1309
+ return;
1310
+ }
1311
+ if (!Array.isArray(pins)) invalidType("pins", pins, "be an array");
1312
+ if (pins.length === 0) return;
1313
+ const captured = [];
1314
+ const seen = /* @__PURE__ */ new Set();
1315
+ for (let index = 0; index < pins.length; index += 1) {
1316
+ const pin = capturePinReference(pins[index], `pins[${index}]`, this.#definition.pinsByKey);
1317
+ if (seen.has(pin.key)) continue;
1318
+ seen.add(pin.key);
1319
+ captured.push(pin);
1320
+ }
1321
+ this.#enqueue({
1322
+ kind: "refresh",
1323
+ pins: Object.freeze(captured)
1324
+ });
1325
+ }
1326
+ connectionState(instanceId, connectionId) {
1327
+ checkIdentity(instanceId, "instanceId");
1328
+ checkIdentity(connectionId, "connectionId");
1329
+ const state = this.#connectionStates.get(connectionKey(instanceId, connectionId));
1330
+ if (state === void 0) throw new RangeError(`(${JSON.stringify(instanceId)}, ${JSON.stringify(connectionId)}) must reference a declared connection`);
1331
+ return state;
1332
+ }
1333
+ networks() {
1334
+ return Object.freeze(this.#components.map((component) => this.#snapshot(component)));
1335
+ }
1336
+ networkOf(pin) {
1337
+ const captured = capturePinReference(pin, "pin", this.#definition.pinsByKey);
1338
+ const component = this.#componentByPin.get(captured.key);
1339
+ return component === void 0 ? void 0 : this.#snapshot(component);
1340
+ }
1341
+ floatingInput(pin) {
1342
+ const captured = capturePinReference(pin, "pin", this.#definition.pinsByKey);
1343
+ if (this.#floatingOptions === void 0) return void 0;
1344
+ return this.#track(captured).input;
1345
+ }
1346
+ destroy() {
1347
+ if (this.#destroyed) return;
1348
+ this.#destroyed = true;
1349
+ this.#commands.length = 0;
1350
+ this.#scheduler.destroyInstance(this.#instance);
1351
+ }
1352
+ #enqueue(command) {
1353
+ this.#commands.push(command);
1354
+ try {
1355
+ this.#scheduler.enqueueControl(this.#instance, this.#target, []);
1356
+ } catch (cause) {
1357
+ this.#commands.pop();
1358
+ throw cause;
1359
+ }
1360
+ }
1361
+ #runNextCommand() {
1362
+ const command = this.#commands.shift();
1363
+ if (command === void 0) throw new Error("Topology control entry has no pending command");
1364
+ switch (command.kind) {
1365
+ case "patch":
1366
+ this.#applyPatch(command.patch);
1367
+ return;
1368
+ case "pins":
1369
+ this.#applyPinStates(command.updates);
1370
+ return;
1371
+ case "refresh":
1372
+ this.#applyRefresh(command.pins);
1373
+ return;
1374
+ }
1375
+ }
1376
+ #applyPatch(patch) {
1377
+ let nextStates;
1378
+ const changed = /* @__PURE__ */ new Set();
1379
+ for (const entry of patch.entries) {
1380
+ const current = this.#connectionStates.get(entry.key);
1381
+ if (current === void 0) throw new Error("Invariant: patch references an unknown connection state");
1382
+ if (current === entry.state) continue;
1383
+ if (nextStates === void 0) nextStates = new Map(this.#connectionStates);
1384
+ nextStates.set(entry.key, entry.state);
1385
+ const connection = this.#definition.connectionsByKey.get(entry.key);
1386
+ if (connection === void 0) throw new Error("Invariant: patch references an unknown connection declaration");
1387
+ changed.add(connection.pinA.key);
1388
+ changed.add(connection.pinB.key);
1389
+ }
1390
+ if (nextStates === void 0) return;
1391
+ const nextComponents = buildComponents(this.#definition, nextStates);
1392
+ const nextByPin = indexComponents(nextComponents);
1393
+ const affected = /* @__PURE__ */ new Set();
1394
+ for (const key of changed) {
1395
+ const before = this.#componentByPin.get(key);
1396
+ const after = nextByPin.get(key);
1397
+ if (before !== void 0) affected.add(before.networkId);
1398
+ if (after !== void 0) affected.add(after.networkId);
1399
+ }
1400
+ const targets = nextComponents.filter((component) => affected.has(component.networkId));
1401
+ let nextResults;
1402
+ try {
1403
+ nextResults = this.#resolve(targets, this.#pinStates);
1404
+ } catch (cause) {
1405
+ this.#connectionStates = nextStates;
1406
+ this.#components = nextComponents;
1407
+ this.#componentByPin = nextByPin;
1408
+ this.#pruneResults(nextComponents);
1409
+ this.#clearResults(targets);
1410
+ throw cause;
1411
+ }
1412
+ this.#connectionStates = nextStates;
1413
+ this.#components = nextComponents;
1414
+ this.#componentByPin = nextByPin;
1415
+ this.#pruneResults(nextComponents);
1416
+ for (const [networkId, result] of nextResults) this.#results.set(networkId, result);
1417
+ this.#updateFloating(targets, nextResults);
1418
+ }
1419
+ #applyPinStates(updates) {
1420
+ const nextStates = new Map(this.#pinStates);
1421
+ const updated = /* @__PURE__ */ new Set();
1422
+ for (const update of updates) {
1423
+ nextStates.set(update.key, update.state);
1424
+ updated.add(update.key);
1425
+ }
1426
+ const targets = this.#components.filter((component) => component.pins.some((pin) => updated.has(pin.key)));
1427
+ let nextResults;
1428
+ try {
1429
+ nextResults = this.#resolve(targets, nextStates);
1430
+ } catch (cause) {
1431
+ this.#pinStates = nextStates;
1432
+ this.#clearResults(targets);
1433
+ throw cause;
1434
+ }
1435
+ this.#pinStates = nextStates;
1436
+ for (const [networkId, result] of nextResults) this.#results.set(networkId, result);
1437
+ this.#updateFloating(targets, nextResults);
1438
+ }
1439
+ #applyRefresh(pins) {
1440
+ let targets;
1441
+ if (pins === void 0) targets = this.#components;
1442
+ else {
1443
+ const keys = new Set(pins.map((pin) => pin.key));
1444
+ targets = this.#components.filter((component) => component.pins.some((pin) => keys.has(pin.key)));
1445
+ }
1446
+ let nextResults;
1447
+ try {
1448
+ nextResults = this.#resolve(targets, this.#pinStates);
1449
+ } catch (cause) {
1450
+ this.#clearResults(targets);
1451
+ throw cause;
1452
+ }
1453
+ for (const [networkId, result] of nextResults) this.#results.set(networkId, result);
1454
+ this.#updateFloating(targets, nextResults);
1455
+ }
1456
+ #resolve(targets, pinStates) {
1457
+ const results = /* @__PURE__ */ new Map();
1458
+ for (const component of targets) results.set(component.networkId, resolveComponent(component, pinStates));
1459
+ return results;
1460
+ }
1461
+ #clearResults(targets) {
1462
+ for (const component of targets) this.#results.delete(component.networkId);
1463
+ }
1464
+ #pruneResults(components) {
1465
+ const ids = new Set(components.map((component) => component.networkId));
1466
+ for (const networkId of [...this.#results.keys()]) if (!ids.has(networkId)) this.#results.delete(networkId);
1467
+ }
1468
+ #updateFloating(targets, results) {
1469
+ const options = this.#floatingOptions;
1470
+ if (options === void 0) return;
1471
+ const now = this.#scheduler.now;
1472
+ for (const component of targets) {
1473
+ const result = results.get(component.networkId);
1474
+ if (result === void 0) continue;
1475
+ const floating = result.state === "floating";
1476
+ for (const pin of component.pins) {
1477
+ const track = this.#track(pin);
1478
+ if (!floating) {
1479
+ track.physical = "driven";
1480
+ track.pendingInitialization = false;
1481
+ continue;
1482
+ }
1483
+ if (track.physical !== "floating") {
1484
+ const supply = readySupply(options, pin.ref);
1485
+ if (supply === void 0) {
1486
+ track.input.beginFloating();
1487
+ track.pendingInitialization = true;
1488
+ } else {
1489
+ track.input.beginFloating(supply);
1490
+ track.pendingInitialization = false;
1491
+ }
1492
+ track.physical = "floating";
1493
+ } else if (track.pendingInitialization) {
1494
+ const supply = readySupply(options, pin.ref);
1495
+ if (supply !== void 0) {
1496
+ track.input.sample(supply, now, true);
1497
+ track.pendingInitialization = false;
1498
+ }
1499
+ }
1500
+ }
1501
+ }
1502
+ }
1503
+ #track(pin) {
1504
+ let track = this.#floating.get(pin.key);
1505
+ if (track === void 0) {
1506
+ const options = this.#floatingOptions;
1507
+ if (options === void 0) throw new Error("Invariant: floating track requested without floating options");
1508
+ track = {
1509
+ input: createFloatingInput(options.seed, pin.instanceId, pin.pinName),
1510
+ physical: "unknown",
1511
+ pendingInitialization: false
1512
+ };
1513
+ this.#floating.set(pin.key, track);
1514
+ }
1515
+ return track;
1516
+ }
1517
+ #snapshot(component) {
1518
+ return Object.freeze({
1519
+ networkId: component.networkId,
1520
+ pins: Object.freeze(component.pins.map((pin) => pin.ref)),
1521
+ groundReady: component.groundReady,
1522
+ result: this.#results.get(component.networkId)
1523
+ });
1524
+ }
1525
+ };
1526
+ function createTopologyRuntime(scheduler, definition, options) {
1527
+ return new TopologyRuntimeImpl(scheduler, definition, options);
1528
+ }
1529
+ //#endregion
1530
+ //#region src/power/validation.ts
1531
+ var INPUT_MODES = ["two-state", "three-state"];
1532
+ var DOMAIN_FIELDS = [
1533
+ "supply",
1534
+ "ground",
1535
+ "voltage",
1536
+ "dependsOn"
1537
+ ];
1538
+ var VOLTAGE_FIELDS = ["turnOn", "turnOff"];
1539
+ var BINDING_FIELDS = ["powerDomain", "levels"];
1540
+ var LEVEL_FIELDS = ["low", "high"];
1541
+ var DEVICE_FIELDS = [
1542
+ "instanceId",
1543
+ "powerDomains",
1544
+ "pins"
1545
+ ];
1546
+ var DEFINITION_FIELDS = ["topology", "devices"];
1547
+ var OPTION_FIELDS = [
1548
+ "instanceId",
1549
+ "topologyInstanceId",
1550
+ "pinStates",
1551
+ "floating",
1552
+ "inputMode",
1553
+ "maxPropagationRounds",
1554
+ "observer"
1555
+ ];
1556
+ var FLOATING_FIELDS = ["seed"];
1557
+ var DEFAULT_MAX_PROPAGATION_ROUNDS = 1e4;
1558
+ function compareText$1(left, right) {
1559
+ return left < right ? -1 : left > right ? 1 : 0;
1560
+ }
1561
+ function compareRefs(left, right) {
1562
+ return compareText$1(left.instanceId, right.instanceId) || compareText$1(left.pinName, right.pinName);
1563
+ }
1564
+ function pinKey(instanceId, pinName) {
1565
+ return JSON.stringify([instanceId, pinName]);
1566
+ }
1567
+ function domainKey(instanceId, domainId) {
1568
+ return JSON.stringify([instanceId, domainId]);
1569
+ }
1570
+ function captureEntries(value, path) {
1571
+ if (value === null || typeof value !== "object" || Array.isArray(value)) invalidType(path, value, "be an object");
1572
+ const entries = [];
1573
+ for (const key of Reflect.ownKeys(value)) {
1574
+ if (typeof key !== "string") invalidType(`${path}.${String(key)}`, Reflect.get(value, key), "use a string key");
1575
+ if (key.length === 0) throw new RangeError(`${path}: map keys must be nonempty strings`);
1576
+ entries.push([key, Reflect.get(value, key)]);
1577
+ }
1578
+ entries.sort(([left], [right]) => compareText$1(left, right));
1579
+ return Object.freeze(entries);
1580
+ }
1581
+ function captureLevels(value, path) {
1582
+ checkRecord(value, path, LEVEL_FIELDS);
1583
+ const low = value.low;
1584
+ const high = value.high;
1585
+ checkFinite(low, `${path}.low`);
1586
+ checkFinite(high, `${path}.high`);
1587
+ if (low < 0 || low > 1) throw new RangeError(`${path}.low: ${low} must be within [0, 1]`);
1588
+ if (high < 0 || high > 1) throw new RangeError(`${path}.high: ${high} must be within [0, 1]`);
1589
+ if (low >= high) throw new RangeError(`${path}.low: ${low} must be less than high ${high}`);
1590
+ return Object.freeze({
1591
+ low,
1592
+ high
1593
+ });
1594
+ }
1595
+ function captureVoltage(value, path) {
1596
+ checkRecord(value, path, VOLTAGE_FIELDS);
1597
+ const turnOn = value.turnOn;
1598
+ const turnOff = value.turnOff;
1599
+ checkFinite(turnOn, `${path}.turnOn`);
1600
+ checkFinite(turnOff, `${path}.turnOff`);
1601
+ if (turnOff < 0) throw new RangeError(`${path}.turnOff: ${turnOff} must be nonnegative`);
1602
+ if (turnOn <= turnOff) throw new RangeError(`${path}.turnOn: ${turnOn} must be greater than turnOff ${turnOff}`);
1603
+ return Object.freeze({
1604
+ turnOn,
1605
+ turnOff
1606
+ });
1607
+ }
1608
+ function resolveDeclaredPin(value, path, instanceId, declared, kind) {
1609
+ checkIdentity(value, path);
1610
+ const key = pinKey(instanceId, value);
1611
+ const pin = declared.get(key);
1612
+ if (pin === void 0) throw new RangeError(`${path}: ${JSON.stringify(value)} must reference a pin declared by instance ${JSON.stringify(instanceId)}`);
1613
+ if (pin.kind !== kind) throw new RangeError(`${path}: pin ${JSON.stringify(value)} must have kind ${JSON.stringify(kind)}`);
1614
+ return pin.ref;
1615
+ }
1616
+ function captureDomains(deviceValue, path, instanceId, declared) {
1617
+ const rawDomains = deviceValue.powerDomains;
1618
+ const entries = captureEntries(rawDomains, `${path}.powerDomains`);
1619
+ const interim = /* @__PURE__ */ new Map();
1620
+ for (const [domainId, declaration] of entries) {
1621
+ const domainPath = `${path}.powerDomains.${domainId}`;
1622
+ checkRecord(declaration, domainPath, DOMAIN_FIELDS);
1623
+ const supply = resolveDeclaredPin(declaration.supply, `${domainPath}.supply`, instanceId, declared, "power");
1624
+ const ground = resolveDeclaredPin(declaration.ground, `${domainPath}.ground`, instanceId, declared, "gnd");
1625
+ const { turnOn, turnOff } = captureVoltage(declaration.voltage, `${domainPath}.voltage`);
1626
+ let dependsOnIds = Object.freeze([]);
1627
+ if ("dependsOn" in declaration) {
1628
+ const rawDependsOn = declaration.dependsOn;
1629
+ if (!Array.isArray(rawDependsOn)) invalidType(`${domainPath}.dependsOn`, rawDependsOn, "be an array");
1630
+ const seen = /* @__PURE__ */ new Set();
1631
+ for (let index = 0; index < rawDependsOn.length; index += 1) {
1632
+ const dependency = rawDependsOn[index];
1633
+ checkIdentity(dependency, `${domainPath}.dependsOn[${index}]`);
1634
+ if (dependency === domainId) throw new RangeError(`${domainPath}.dependsOn: domain must not depend on itself`);
1635
+ if (seen.has(dependency)) invalidType(`${domainPath}.dependsOn[${index}]`, dependency, "be unique within the dependency set");
1636
+ seen.add(dependency);
1637
+ }
1638
+ dependsOnIds = Object.freeze([...seen].sort(compareText$1));
1639
+ }
1640
+ interim.set(domainId, Object.freeze({
1641
+ instanceId,
1642
+ domainId,
1643
+ supply,
1644
+ ground,
1645
+ turnOn,
1646
+ turnOff,
1647
+ dependsOnIds
1648
+ }));
1649
+ }
1650
+ for (const [domainId, domain] of interim) for (const dependency of domain.dependsOnIds) if (!interim.has(dependency)) throw new RangeError(`${path}.powerDomains.${domainId}.dependsOn: ${JSON.stringify(dependency)} must reference a domain declared by the same device`);
1651
+ const indegree = /* @__PURE__ */ new Map();
1652
+ const dependents = /* @__PURE__ */ new Map();
1653
+ for (const [domainId, domain] of interim) {
1654
+ indegree.set(domainId, domain.dependsOnIds.length);
1655
+ for (const dependency of domain.dependsOnIds) {
1656
+ const list = dependents.get(dependency);
1657
+ if (list === void 0) dependents.set(dependency, [domainId]);
1658
+ else list.push(domainId);
1659
+ }
1660
+ }
1661
+ const ready = [...indegree.keys()].filter((domainId) => indegree.get(domainId) === 0);
1662
+ const order = [];
1663
+ while (ready.length > 0) {
1664
+ ready.sort(compareText$1);
1665
+ const domainId = ready.shift();
1666
+ order.push(domainId);
1667
+ for (const dependent of dependents.get(domainId) ?? []) {
1668
+ const next = indegree.get(dependent) - 1;
1669
+ indegree.set(dependent, next);
1670
+ if (next === 0) ready.push(dependent);
1671
+ }
1672
+ }
1673
+ if (order.length !== interim.size) {
1674
+ const remaining = [...interim.keys()].filter((domainId) => !order.includes(domainId));
1675
+ throw new RangeError(`${path}.powerDomains.dependsOn: dependency graph must be acyclic; cycle involves ${remaining.sort(compareText$1).map((domainId) => JSON.stringify(domainId)).join(", ")}`);
1676
+ }
1677
+ const domainsById = /* @__PURE__ */ new Map();
1678
+ const domains = [];
1679
+ for (const domainId of order) {
1680
+ const domain = interim.get(domainId);
1681
+ const dependsOn = Object.freeze(domain.dependsOnIds.map((dependency) => domainsById.get(dependency)));
1682
+ let depth = 0;
1683
+ for (const dependency of dependsOn) depth = Math.max(depth, dependency.depth + 1);
1684
+ const captured = Object.freeze({
1685
+ instanceId: domain.instanceId,
1686
+ domainId: domain.domainId,
1687
+ key: domainKey(domain.instanceId, domain.domainId),
1688
+ supply: domain.supply,
1689
+ ground: domain.ground,
1690
+ turnOn: domain.turnOn,
1691
+ turnOff: domain.turnOff,
1692
+ dependsOn,
1693
+ depth
1694
+ });
1695
+ domainsById.set(domainId, captured);
1696
+ domains.push(captured);
1697
+ }
1698
+ return Object.freeze(domains);
1699
+ }
1700
+ function capturePinBindings(deviceValue, path, instanceId, domainsById, declared) {
1701
+ const result = /* @__PURE__ */ new Map();
1702
+ if (!("pins" in deviceValue)) return result;
1703
+ const entries = captureEntries(deviceValue.pins, `${path}.pins`);
1704
+ for (const [pinName, binding] of entries) {
1705
+ const bindingPath = `${path}.pins.${pinName}`;
1706
+ checkRecord(binding, bindingPath, BINDING_FIELDS);
1707
+ const key = pinKey(instanceId, pinName);
1708
+ const pin = declared.get(key);
1709
+ if (pin === void 0) throw new RangeError(`${bindingPath}: ${JSON.stringify(pinName)} must reference a pin declared by instance ${JSON.stringify(instanceId)}`);
1710
+ if (pin.kind !== "signal") throw new RangeError(`${bindingPath}: powerDomain is only supported on kind 'signal' pins`);
1711
+ let domain;
1712
+ if ("powerDomain" in binding) {
1713
+ const domainId = binding.powerDomain;
1714
+ checkIdentity(domainId, `${bindingPath}.powerDomain`);
1715
+ domain = domainsById.get(domainId);
1716
+ if (domain === void 0) throw new RangeError(`${bindingPath}.powerDomain: ${JSON.stringify(domainId)} must reference a domain declared by the same device`);
1717
+ }
1718
+ let levels;
1719
+ if ("levels" in binding) {
1720
+ if (domain === void 0) throw new RangeError(`${bindingPath}.levels requires a powerDomain binding`);
1721
+ levels = captureLevels(binding.levels, `${bindingPath}.levels`);
1722
+ }
1723
+ if (domain !== void 0) result.set(key, Object.freeze({
1724
+ domain,
1725
+ levels
1726
+ }));
1727
+ }
1728
+ return result;
1729
+ }
1730
+ function capturePowerDefinition(value) {
1731
+ checkRecord(value, "definition", DEFINITION_FIELDS);
1732
+ const topology = captureTopologyDefinition(value.topology);
1733
+ const declared = /* @__PURE__ */ new Map();
1734
+ const instances = /* @__PURE__ */ new Set();
1735
+ for (const pin of topology.pins) {
1736
+ const ref = Object.freeze({
1737
+ instanceId: pin.instanceId,
1738
+ pinName: pin.pinName
1739
+ });
1740
+ declared.set(pinKey(pin.instanceId, pin.pinName), Object.freeze({
1741
+ ref,
1742
+ kind: pin.kind
1743
+ }));
1744
+ instances.add(pin.instanceId);
1745
+ }
1746
+ const rawDevices = value.devices;
1747
+ if (!Array.isArray(rawDevices)) invalidType("definition.devices", rawDevices, "be an array");
1748
+ const devices = [];
1749
+ const devicesById = /* @__PURE__ */ new Map();
1750
+ const bindingsByKey = /* @__PURE__ */ new Map();
1751
+ for (let index = 0; index < rawDevices.length; index += 1) {
1752
+ const path = `definition.devices[${index}]`;
1753
+ const deviceValue = rawDevices[index];
1754
+ checkRecord(deviceValue, path, DEVICE_FIELDS);
1755
+ const instanceId = deviceValue.instanceId;
1756
+ checkIdentity(instanceId, `${path}.instanceId`);
1757
+ if (devicesById.has(instanceId)) invalidType(`${path}.instanceId`, instanceId, "be unique among power device definitions");
1758
+ if (!instances.has(instanceId)) throw new RangeError(`${path}.instanceId: ${JSON.stringify(instanceId)} must reference a declared topology instance`);
1759
+ const domains = captureDomains(deviceValue, path, instanceId, declared);
1760
+ const domainsById = new Map(domains.map((domain) => [domain.domainId, domain]));
1761
+ const device = Object.freeze({
1762
+ instanceId,
1763
+ domains,
1764
+ domainsById
1765
+ });
1766
+ devices.push(device);
1767
+ devicesById.set(instanceId, device);
1768
+ const bindings = capturePinBindings(deviceValue, path, instanceId, domainsById, declared);
1769
+ for (const [key, binding] of bindings) bindingsByKey.set(key, binding);
1770
+ }
1771
+ devices.sort((left, right) => compareText$1(left.instanceId, right.instanceId));
1772
+ const domainsInOrder = [];
1773
+ for (const device of devices) domainsInOrder.push(...device.domains);
1774
+ const domainsByKey = new Map(domainsInOrder.map((domain) => [domain.key, domain]));
1775
+ const pins = [];
1776
+ for (const [key, pin] of declared) {
1777
+ const binding = bindingsByKey.get(key);
1778
+ pins.push(Object.freeze({
1779
+ ref: pin.ref,
1780
+ key,
1781
+ kind: pin.kind,
1782
+ domain: binding?.domain,
1783
+ levels: binding?.levels
1784
+ }));
1785
+ }
1786
+ pins.sort((left, right) => compareRefs(left.ref, right.ref));
1787
+ const pinsByKey = new Map(pins.map((pin) => [pin.key, pin]));
1788
+ return Object.freeze({
1789
+ topology,
1790
+ devices: Object.freeze(devices),
1791
+ devicesById,
1792
+ domainsInOrder: Object.freeze(domainsInOrder),
1793
+ domainsByKey,
1794
+ pins: Object.freeze(pins),
1795
+ pinsByKey,
1796
+ instances
1797
+ });
1798
+ }
1799
+ function captureObserver(value, path) {
1800
+ if (value === null || typeof value !== "object" && typeof value !== "function") invalidType(path, value, "be an object");
1801
+ const notify = Reflect.get(value, "onPowerDomainChange");
1802
+ if (typeof notify !== "function") invalidType(`${path}.onPowerDomainChange`, notify, "be a function");
1803
+ return Object.freeze({
1804
+ receiver: value,
1805
+ notify
1806
+ });
1807
+ }
1808
+ function captureLimit(value, fallback, path) {
1809
+ if (value === void 0) return fallback;
1810
+ if (typeof value !== "number" || !Number.isFinite(value)) invalidType(path, value, "be a finite number");
1811
+ if (!Number.isSafeInteger(value) || value < 0) throw new RangeError(`${path}: ${value} must be a non-negative safe integer`);
1812
+ return value;
1813
+ }
1814
+ function capturePowerOptions(value, definition) {
1815
+ checkRecord(value, "options", OPTION_FIELDS);
1816
+ const instanceId = value.instanceId;
1817
+ checkIdentity(instanceId, "options.instanceId");
1818
+ let topologyInstanceId = `${instanceId}:topology`;
1819
+ if ("topologyInstanceId" in value) {
1820
+ const candidate = value.topologyInstanceId;
1821
+ checkIdentity(candidate, "options.topologyInstanceId");
1822
+ topologyInstanceId = candidate;
1823
+ }
1824
+ if (topologyInstanceId === instanceId) throw new RangeError("options.topologyInstanceId must differ from options.instanceId");
1825
+ const rawStates = value.pinStates;
1826
+ if (!Array.isArray(rawStates)) invalidType("options.pinStates", rawStates, "be an array");
1827
+ const intents = /* @__PURE__ */ new Map();
1828
+ for (let index = 0; index < rawStates.length; index += 1) {
1829
+ const path = `options.pinStates[${index}]`;
1830
+ const state = snapshotElectricalPin(rawStates[index], path);
1831
+ const key = pinKey(state.instanceId, state.pinName);
1832
+ if (!definition.pinsByKey.has(key)) throw new RangeError(`${path}: (${JSON.stringify(state.instanceId)}, ${JSON.stringify(state.pinName)}) must reference a declared pin`);
1833
+ if (intents.has(key)) invalidType(`${path}.pinName`, state.pinName, `be unique within instance ${JSON.stringify(state.instanceId)}`);
1834
+ intents.set(key, state);
1835
+ }
1836
+ const initialStates = [];
1837
+ for (const pin of definition.pins) {
1838
+ const intent = intents.get(pin.key);
1839
+ if (intent === void 0) throw new RangeError(`options.pinStates: missing state for (${JSON.stringify(pin.ref.instanceId)}, ${JSON.stringify(pin.ref.pinName)})`);
1840
+ initialStates.push(pin.domain === void 0 ? intent : Object.freeze({
1841
+ ...intent,
1842
+ powerReady: false
1843
+ }));
1844
+ }
1845
+ let floating;
1846
+ if ("floating" in value) {
1847
+ const rawFloating = value.floating;
1848
+ checkRecord(rawFloating, "options.floating", FLOATING_FIELDS);
1849
+ const seed = rawFloating.seed;
1850
+ checkInteger(seed, 0, 4294967295, "options.floating.seed");
1851
+ floating = Object.freeze({ seed });
1852
+ }
1853
+ let inputMode = "three-state";
1854
+ if ("inputMode" in value) inputMode = checkChoice(value.inputMode, INPUT_MODES, "options.inputMode");
1855
+ const maxPropagationRounds = captureLimit(value.maxPropagationRounds, DEFAULT_MAX_PROPAGATION_ROUNDS, "options.maxPropagationRounds");
1856
+ let observer;
1857
+ if ("observer" in value) observer = captureObserver(value.observer, "options.observer");
1858
+ return Object.freeze({
1859
+ instanceId,
1860
+ topologyInstanceId,
1861
+ intents,
1862
+ initialStates: Object.freeze(initialStates),
1863
+ floating,
1864
+ inputMode,
1865
+ maxPropagationRounds,
1866
+ observer
1867
+ });
1868
+ }
1869
+ //#endregion
1870
+ //#region src/power/runtime.ts
1871
+ var NO_OPENED = Object.freeze([]);
1872
+ var NO_REASONS = Object.freeze([]);
1873
+ var GATED_DIGITAL = Object.freeze({
1874
+ value: 0,
1875
+ ready: false,
1876
+ source: "gated"
1877
+ });
1878
+ var GATED_VOLTAGE = Object.freeze({
1879
+ voltage: 0,
1880
+ ready: false,
1881
+ source: "gated"
1882
+ });
1883
+ function compareText(left, right) {
1884
+ return left < right ? -1 : left > right ? 1 : 0;
1885
+ }
1886
+ function compareSnapshots(left, right) {
1887
+ return compareText(left.instanceId, right.instanceId) || compareText(left.domainId, right.domainId);
1888
+ }
1889
+ function nextState(voltage, groundReady, dependenciesReady, previous, turnOn, turnOff) {
1890
+ if (!groundReady || voltage === null || !dependenciesReady) return "off";
1891
+ if (voltage <= turnOff) return "off";
1892
+ if (voltage >= turnOn) return "on";
1893
+ return previous;
1894
+ }
1895
+ function downReasons(domain, snapshot) {
1896
+ const reasons = [];
1897
+ if (!snapshot.groundReady) reasons.push("ground-not-ready");
1898
+ else if (snapshot.voltage === null) reasons.push("supply-indeterminate");
1899
+ if (!snapshot.dependenciesReady) reasons.push("dependency-off");
1900
+ if (snapshot.groundReady && snapshot.voltage !== null && snapshot.voltage <= domain.turnOff) reasons.push("below-turn-off");
1901
+ return Object.freeze(reasons);
1902
+ }
1903
+ function sortChanges(changes, domainsByKey) {
1904
+ const depth = (change) => domainsByKey.get(domainKey(change.instanceId, change.domainId))?.depth ?? 0;
1905
+ const down = changes.filter((change) => change.state === "off").sort((left, right) => compareText(left.instanceId, right.instanceId) || depth(right) - depth(left) || compareText(left.domainId, right.domainId));
1906
+ const up = changes.filter((change) => change.state === "on").sort((left, right) => compareText(left.instanceId, right.instanceId) || depth(left) - depth(right) || compareText(left.domainId, right.domainId));
1907
+ return Object.freeze([...down, ...up]);
1908
+ }
1909
+ function sortStates(states) {
1910
+ return Object.freeze([...states.entries()].sort(([left], [right]) => compareText(left, right)).map(([, state]) => state));
1911
+ }
1912
+ var PowerFeedbackError = class extends Error {
1913
+ name = "PowerFeedbackError";
1914
+ timePs;
1915
+ rounds;
1916
+ maxRounds;
1917
+ domains;
1918
+ constructor(timePs, rounds, maxRounds, domains) {
1919
+ super(`Power propagation exceeded ${maxRounds} rounds at ${timePs} ps`);
1920
+ this.timePs = timePs;
1921
+ this.rounds = rounds;
1922
+ this.maxRounds = maxRounds;
1923
+ this.domains = Object.freeze(domains.map((domain) => Object.freeze({ ...domain })));
1924
+ Object.freeze(this);
1925
+ }
1926
+ };
1927
+ var PowerRuntimeImpl = class {
1928
+ #scheduler;
1929
+ #definition;
1930
+ #topology;
1931
+ #target;
1932
+ #instance;
1933
+ #inputMode;
1934
+ #maxPropagationRounds;
1935
+ #signalPins;
1936
+ #intents;
1937
+ #effective;
1938
+ #submittedGate;
1939
+ #notified = /* @__PURE__ */ new Map();
1940
+ #pendingOpen = /* @__PURE__ */ new Set();
1941
+ #digital = /* @__PURE__ */ new Map();
1942
+ #memory = /* @__PURE__ */ new Map();
1943
+ #dirty = /* @__PURE__ */ new Set();
1944
+ #commands = [];
1945
+ #domains = /* @__PURE__ */ new Map();
1946
+ #observer;
1947
+ #topologyReady = false;
1948
+ #destroyed = false;
1949
+ #budgetTime;
1950
+ #rounds = 0;
1951
+ #topologyEpoch = 0;
1952
+ #appliedEpoch = 0;
1953
+ constructor(scheduler, definitionInput, optionsInput) {
1954
+ const definition = capturePowerDefinition(definitionInput);
1955
+ const options = capturePowerOptions(optionsInput, definition);
1956
+ this.#scheduler = scheduler;
1957
+ this.#definition = definition;
1958
+ this.#inputMode = options.inputMode;
1959
+ this.#maxPropagationRounds = options.maxPropagationRounds;
1960
+ this.#observer = options.observer;
1961
+ this.#intents = new Map(options.intents);
1962
+ this.#effective = new Map(options.initialStates.map((state) => [pinKey(state.instanceId, state.pinName), state]));
1963
+ this.#submittedGate = new Map(options.initialStates.map((state) => [pinKey(state.instanceId, state.pinName), state.powerReady]));
1964
+ this.#signalPins = Object.freeze(definition.pins.filter((pin) => pin.kind === "signal"));
1965
+ for (const pin of this.#signalPins) this.#memory.set(pin.key, 0);
1966
+ for (const domain of definition.domainsInOrder) this.#notified.set(domain.key, "off");
1967
+ this.#target = Object.freeze({
1968
+ name: "power.propagate",
1969
+ kind: "control",
1970
+ params: Object.freeze([])
1971
+ });
1972
+ let instance;
1973
+ let topology;
1974
+ try {
1975
+ instance = scheduler.addInstance({
1976
+ instanceId: options.instanceId,
1977
+ targets: [this.#target],
1978
+ executor: { execute: () => this.#runNextCommand() }
1979
+ });
1980
+ this.#instance = instance;
1981
+ topology = createTopologyRuntime(scheduler, definition.topology, {
1982
+ instanceId: options.topologyInstanceId,
1983
+ pinStates: options.initialStates,
1984
+ ...options.floating === void 0 ? {} : { floating: {
1985
+ seed: options.floating.seed,
1986
+ supplyVoltage: (pin) => this.#floatingSupply(pin),
1987
+ samplingEnabled: (pin) => this.#floatingSamplingEnabled(pin)
1988
+ } }
1989
+ });
1990
+ this.#topology = topology;
1991
+ this.#domains = this.#computeDomains();
1992
+ for (const domain of definition.domainsInOrder) if (this.#domains.get(domain.key)?.state === "on") this.#pendingOpen.add(domain.key);
1993
+ this.#topologyReady = true;
1994
+ this.#interpretPins();
1995
+ this.#enqueuePropagate(0, NO_OPENED);
1996
+ } catch (cause) {
1997
+ if (topology !== void 0) try {
1998
+ topology.destroy();
1999
+ } catch {}
2000
+ if (instance !== void 0) try {
2001
+ scheduler.destroyInstance(instance);
2002
+ } catch {}
2003
+ throw cause;
2004
+ }
2005
+ Object.freeze(this);
2006
+ }
2007
+ get instance() {
2008
+ return this.#instance;
2009
+ }
2010
+ get inputMode() {
2011
+ return this.#inputMode;
2012
+ }
2013
+ submit(patch) {
2014
+ this.#assertAlive();
2015
+ this.#topology.submit(patch);
2016
+ this.#topologyEpoch += 1;
2017
+ this.#enqueuePropagate(this.#topologyEpoch, NO_OPENED);
2018
+ }
2019
+ updatePinStates(states) {
2020
+ this.#assertAlive();
2021
+ if (!Array.isArray(states)) invalidType("states", states, "be an array");
2022
+ if (states.length === 0) return;
2023
+ const updates = [];
2024
+ const seen = /* @__PURE__ */ new Set();
2025
+ for (let index = 0; index < states.length; index += 1) {
2026
+ const state = snapshotElectricalPin(states[index], `states[${index}]`);
2027
+ const key = pinKey(state.instanceId, state.pinName);
2028
+ if (!this.#definition.pinsByKey.has(key)) throw new RangeError(`states[${index}]: (${JSON.stringify(state.instanceId)}, ${JSON.stringify(state.pinName)}) must reference a declared pin`);
2029
+ if (seen.has(key)) invalidType(`states[${index}].pinName`, state.pinName, `be unique within instance ${JSON.stringify(state.instanceId)}`);
2030
+ seen.add(key);
2031
+ updates.push(Object.freeze({
2032
+ key,
2033
+ state
2034
+ }));
2035
+ }
2036
+ for (const update of updates) {
2037
+ this.#intents.set(update.key, update.state);
2038
+ this.#dirty.add(update.key);
2039
+ }
2040
+ this.#enqueuePropagate(this.#topologyEpoch, NO_OPENED);
2041
+ }
2042
+ refresh(pins) {
2043
+ this.#assertAlive();
2044
+ if (pins !== void 0) {
2045
+ if (!Array.isArray(pins)) invalidType("pins", pins, "be an array");
2046
+ if (pins.length === 0) return;
2047
+ }
2048
+ this.#topology.refresh(pins);
2049
+ this.#topologyEpoch += 1;
2050
+ this.#enqueuePropagate(this.#topologyEpoch, NO_OPENED);
2051
+ }
2052
+ connectionState(instanceId, connectionId) {
2053
+ return this.#topology.connectionState(instanceId, connectionId);
2054
+ }
2055
+ networks() {
2056
+ return this.#topology.networks();
2057
+ }
2058
+ networkOf(pin) {
2059
+ return this.#topology.networkOf(pin);
2060
+ }
2061
+ floatingInput(pin) {
2062
+ return this.#topology.floatingInput(pin);
2063
+ }
2064
+ domain(instanceId, domainId) {
2065
+ checkIdentity(instanceId, "instanceId");
2066
+ checkIdentity(domainId, "domainId");
2067
+ const key = domainKey(instanceId, domainId);
2068
+ if (!this.#definition.domainsByKey.has(key)) throw new RangeError(`(${JSON.stringify(instanceId)}, ${JSON.stringify(domainId)}) must reference a declared power domain`);
2069
+ return this.#domains.get(key);
2070
+ }
2071
+ domains(instanceId) {
2072
+ const snapshot = this.#snapshots();
2073
+ if (instanceId === void 0) return snapshot;
2074
+ checkIdentity(instanceId, "instanceId");
2075
+ if (!this.#definition.instances.has(instanceId)) throw new RangeError(`${JSON.stringify(instanceId)} must reference a declared topology instance`);
2076
+ return Object.freeze(snapshot.filter((domain) => domain.instanceId === instanceId));
2077
+ }
2078
+ pinGate(pin) {
2079
+ const captured = this.#signalPin(pin, "pin");
2080
+ const gate = this.#gateFor(captured);
2081
+ return Object.freeze({
2082
+ instanceId: captured.ref.instanceId,
2083
+ pinName: captured.ref.pinName,
2084
+ domainId: gate.domainId,
2085
+ ready: gate.ready,
2086
+ reason: gate.reason
2087
+ });
2088
+ }
2089
+ pinIntent(pin) {
2090
+ const captured = this.#resolvePin(pin, "pin");
2091
+ return this.#intents.get(captured.key);
2092
+ }
2093
+ sampleDigital(pin) {
2094
+ const captured = this.#signalPin(pin, "pin");
2095
+ const sample = this.#digital.get(captured.key);
2096
+ if (sample === void 0) throw new Error(`Invariant: missing digital interpretation for (${JSON.stringify(captured.ref.instanceId)}, ${JSON.stringify(captured.ref.pinName)})`);
2097
+ return sample;
2098
+ }
2099
+ sampleVoltage(pin) {
2100
+ const captured = this.#signalPin(pin, "pin");
2101
+ if (!this.#gateFor(captured).ready) return GATED_VOLTAGE;
2102
+ const result = this.#topology.networkOf(captured.ref)?.result;
2103
+ if (result === void 0) throw new Error(`Invariant: network result missing for (${JSON.stringify(captured.ref.instanceId)}, ${JSON.stringify(captured.ref.pinName)})`);
2104
+ if (result.state === "driven") return Object.freeze({
2105
+ voltage: result.voltage,
2106
+ ready: true,
2107
+ source: "sampled"
2108
+ });
2109
+ const floating = this.#topology.floatingInput(captured.ref);
2110
+ if (floating === void 0) throw new Error(`Floating voltage sampling for (${JSON.stringify(captured.ref.instanceId)}, ${JSON.stringify(captured.ref.pinName)}) requires PowerOptions.floating`);
2111
+ const supply = this.#floatingSupply(captured.ref);
2112
+ if (supply === void 0) throw new RangeError(`Floating voltage sampling for (${JSON.stringify(captured.ref.instanceId)}, ${JSON.stringify(captured.ref.pinName)}) requires a nonnegative supply`);
2113
+ const sample = floating.sample(supply, this.#scheduler.now, true);
2114
+ if (sample.state !== "ready") throw new Error(`Invariant: enabled floating sampling returned disabled for (${JSON.stringify(captured.ref.instanceId)}, ${JSON.stringify(captured.ref.pinName)})`);
2115
+ return Object.freeze({
2116
+ voltage: sample.voltage,
2117
+ ready: true,
2118
+ source: "floating"
2119
+ });
2120
+ }
2121
+ observe(observer) {
2122
+ this.#assertAlive();
2123
+ this.#observer = observer === void 0 ? void 0 : captureObserver(observer, "observer");
2124
+ }
2125
+ destroy() {
2126
+ if (this.#destroyed) return;
2127
+ this.#destroyed = true;
2128
+ this.#observer = void 0;
2129
+ this.#commands.length = 0;
2130
+ this.#topology.destroy();
2131
+ this.#scheduler.destroyInstance(this.#instance);
2132
+ }
2133
+ #assertAlive() {
2134
+ if (this.#destroyed) throw new Error("Power runtime is destroyed");
2135
+ }
2136
+ #enqueue(command) {
2137
+ this.#commands.push(command);
2138
+ try {
2139
+ this.#scheduler.enqueueControl(this.#instance, this.#target, []);
2140
+ } catch (cause) {
2141
+ this.#commands.pop();
2142
+ throw cause;
2143
+ }
2144
+ }
2145
+ #enqueuePropagate(epoch, opened) {
2146
+ this.#enqueue(Object.freeze({
2147
+ kind: "propagate",
2148
+ epoch,
2149
+ opened
2150
+ }));
2151
+ }
2152
+ #runNextCommand() {
2153
+ const command = this.#commands.shift();
2154
+ if (command === void 0) throw new Error("Power control entry has no pending command");
2155
+ if (command.kind === "notify") {
2156
+ this.#notify();
2157
+ return;
2158
+ }
2159
+ this.#propagate(command.epoch, command.opened);
2160
+ }
2161
+ #propagate(epoch, opened) {
2162
+ const now = this.#scheduler.now;
2163
+ if (this.#budgetTime !== now) {
2164
+ this.#budgetTime = now;
2165
+ this.#rounds = 0;
2166
+ }
2167
+ this.#rounds += 1;
2168
+ if (this.#rounds > this.#maxPropagationRounds) throw new PowerFeedbackError(now, this.#rounds, this.#maxPropagationRounds, this.#snapshots());
2169
+ if (epoch > this.#appliedEpoch) this.#appliedEpoch = epoch;
2170
+ if (this.#appliedEpoch < this.#topologyEpoch) {
2171
+ this.#enqueuePropagate(this.#topologyEpoch, opened);
2172
+ return;
2173
+ }
2174
+ for (const key of opened) this.#pendingOpen.delete(key);
2175
+ this.#domains = this.#computeDomains();
2176
+ const updates = this.#desiredUpdates();
2177
+ if (updates.off.size > 0) {
2178
+ this.#submitUpdates(updates.off, false);
2179
+ this.#enqueuePropagate(this.#topologyEpoch, NO_OPENED);
2180
+ return;
2181
+ }
2182
+ if (this.#hasNotificationChange()) {
2183
+ this.#enqueue(Object.freeze({ kind: "notify" }));
2184
+ return;
2185
+ }
2186
+ if (updates.on.size > 0) {
2187
+ const openedDomains = this.#submitUpdates(updates.on, true);
2188
+ this.#enqueuePropagate(this.#topologyEpoch, openedDomains);
2189
+ return;
2190
+ }
2191
+ this.#interpretPins();
2192
+ }
2193
+ #notify() {
2194
+ if (this.#appliedEpoch < this.#topologyEpoch) {
2195
+ this.#enqueuePropagate(this.#topologyEpoch, NO_OPENED);
2196
+ return;
2197
+ }
2198
+ let updates = this.#desiredUpdates();
2199
+ if (updates.off.size > 0) {
2200
+ this.#submitUpdates(updates.off, false);
2201
+ this.#enqueuePropagate(this.#topologyEpoch, NO_OPENED);
2202
+ return;
2203
+ }
2204
+ const changes = this.#notificationChanges();
2205
+ const opened = /* @__PURE__ */ new Set();
2206
+ for (const change of changes) {
2207
+ const key = domainKey(change.instanceId, change.domainId);
2208
+ if (change.state === "on") {
2209
+ this.#pendingOpen.add(key);
2210
+ opened.add(key);
2211
+ } else this.#pendingOpen.delete(key);
2212
+ }
2213
+ if (changes.length > 0) {
2214
+ this.#interpretPins();
2215
+ const ordered = sortChanges(changes, this.#definition.domainsByKey);
2216
+ if (this.#observer !== void 0) this.#observer.notify.call(this.#observer.receiver, ordered, this.#snapshots());
2217
+ for (const change of changes) this.#notified.set(domainKey(change.instanceId, change.domainId), change.state);
2218
+ }
2219
+ updates = this.#desiredUpdates();
2220
+ if (updates.off.size > 0) this.#submitUpdates(updates.off, false);
2221
+ if (updates.on.size > 0) for (const key of this.#submitUpdates(updates.on, true)) opened.add(key);
2222
+ if (opened.size > 0 || updates.on.size > 0 || updates.off.size > 0) {
2223
+ this.#enqueuePropagate(this.#topologyEpoch, Object.freeze([...opened].sort(compareText)));
2224
+ return;
2225
+ }
2226
+ this.#interpretPins();
2227
+ }
2228
+ #notificationChanges() {
2229
+ const changes = [];
2230
+ const now = this.#scheduler.now;
2231
+ for (const domain of this.#definition.domainsInOrder) {
2232
+ const previous = this.#notified.get(domain.key) ?? "off";
2233
+ const snapshot = this.#domains.get(domain.key);
2234
+ if (snapshot.state === previous) continue;
2235
+ changes.push(Object.freeze({
2236
+ ...snapshot,
2237
+ timePs: now,
2238
+ previousState: previous,
2239
+ reasons: snapshot.state === "off" ? downReasons(domain, snapshot) : NO_REASONS
2240
+ }));
2241
+ }
2242
+ return changes;
2243
+ }
2244
+ #hasNotificationChange() {
2245
+ for (const domain of this.#definition.domainsInOrder) {
2246
+ const previous = this.#notified.get(domain.key) ?? "off";
2247
+ if (this.#domains.get(domain.key).state !== previous) return true;
2248
+ }
2249
+ return false;
2250
+ }
2251
+ #desiredUpdates() {
2252
+ const off = /* @__PURE__ */ new Map();
2253
+ const on = /* @__PURE__ */ new Map();
2254
+ for (const pin of this.#definition.pins) {
2255
+ const intent = this.#intents.get(pin.key);
2256
+ const desired = pin.domain === void 0 ? intent.powerReady : this.#domains.get(pin.domain.key)?.state === "on";
2257
+ if (!this.#dirty.has(pin.key) && this.#submittedGate.get(pin.key) === desired) continue;
2258
+ const state = pin.domain === void 0 ? intent : Object.freeze({
2259
+ ...intent,
2260
+ powerReady: desired
2261
+ });
2262
+ if (desired) on.set(pin.key, state);
2263
+ else off.set(pin.key, state);
2264
+ }
2265
+ return {
2266
+ off,
2267
+ on
2268
+ };
2269
+ }
2270
+ #submitUpdates(updates, desired) {
2271
+ if (updates.size === 0) return NO_OPENED;
2272
+ const opened = /* @__PURE__ */ new Set();
2273
+ for (const [key, state] of updates) {
2274
+ const pin = this.#definition.pinsByKey.get(key);
2275
+ const wasSubmitted = this.#submittedGate.get(key) === true;
2276
+ this.#effective.set(key, state);
2277
+ this.#submittedGate.set(key, desired);
2278
+ this.#dirty.delete(key);
2279
+ if (pin.domain === void 0) continue;
2280
+ if (desired) {
2281
+ if (!wasSubmitted) {
2282
+ this.#pendingOpen.add(pin.domain.key);
2283
+ opened.add(pin.domain.key);
2284
+ }
2285
+ } else this.#pendingOpen.delete(pin.domain.key);
2286
+ }
2287
+ this.#topology.updatePinStates(sortStates(updates));
2288
+ this.#topologyEpoch += 1;
2289
+ return Object.freeze([...opened].sort(compareText));
2290
+ }
2291
+ #computeDomains() {
2292
+ const next = /* @__PURE__ */ new Map();
2293
+ for (const domain of this.#definition.domainsInOrder) {
2294
+ const groundNetwork = this.#topology.networkOf(domain.ground);
2295
+ const supplyNetwork = this.#topology.networkOf(domain.supply);
2296
+ const groundReady = groundNetwork?.groundReady === true;
2297
+ const supply = supplyNetwork?.result;
2298
+ const voltage = groundReady && supply?.state === "driven" ? supply.voltage : null;
2299
+ const dependenciesReady = domain.dependsOn.every((dependency) => next.get(dependency.key)?.state === "on");
2300
+ const state = nextState(voltage, groundReady, dependenciesReady, this.#domains.get(domain.key)?.state ?? "off", domain.turnOn, domain.turnOff);
2301
+ next.set(domain.key, Object.freeze({
2302
+ instanceId: domain.instanceId,
2303
+ domainId: domain.domainId,
2304
+ state,
2305
+ voltage,
2306
+ groundReady,
2307
+ dependenciesReady
2308
+ }));
2309
+ }
2310
+ return next;
2311
+ }
2312
+ #snapshots() {
2313
+ return Object.freeze([...this.#domains.values()].sort(compareSnapshots));
2314
+ }
2315
+ #interpretPins() {
2316
+ for (const pin of this.#signalPins) this.#interpretPin(pin);
2317
+ }
2318
+ #interpretPin(pin) {
2319
+ if (!this.#gateFor(pin).ready) {
2320
+ this.#digital.set(pin.key, GATED_DIGITAL);
2321
+ return;
2322
+ }
2323
+ const result = this.#topology.networkOf(pin.ref)?.result;
2324
+ if (result === void 0) throw new Error(`Invariant: network result missing for (${JSON.stringify(pin.ref.instanceId)}, ${JSON.stringify(pin.ref.pinName)})`);
2325
+ if (result.state === "floating") {
2326
+ this.#digital.set(pin.key, Object.freeze({
2327
+ value: 0,
2328
+ ready: true,
2329
+ source: "floating"
2330
+ }));
2331
+ return;
2332
+ }
2333
+ let low;
2334
+ let high;
2335
+ if (pin.levels !== void 0) {
2336
+ const domain = pin.domain === void 0 ? void 0 : this.#domains.get(pin.domain.key);
2337
+ if (domain === void 0 || domain.voltage === null) throw new Error(`Invariant: missing domain voltage for (${JSON.stringify(pin.key)})`);
2338
+ low = pin.levels.low * domain.voltage;
2339
+ high = pin.levels.high * domain.voltage;
2340
+ } else {
2341
+ const effective = this.#effective.get(pin.key);
2342
+ if (effective === void 0) throw new Error(`Invariant: missing effective pin state for (${JSON.stringify(pin.key)})`);
2343
+ low = effective.model.lowVoltage;
2344
+ high = effective.model.highVoltage;
2345
+ }
2346
+ const voltage = result.voltage;
2347
+ let value;
2348
+ let source;
2349
+ if (voltage <= low) {
2350
+ value = 0;
2351
+ source = "sampled";
2352
+ } else if (voltage >= high) {
2353
+ value = 1;
2354
+ source = "sampled";
2355
+ } else if (this.#inputMode === "three-state") {
2356
+ value = this.#memory.get(pin.key) ?? 0;
2357
+ source = "threshold-range";
2358
+ } else {
2359
+ value = 0;
2360
+ source = "sampled";
2361
+ }
2362
+ if (source === "sampled") this.#memory.set(pin.key, value);
2363
+ this.#digital.set(pin.key, Object.freeze({
2364
+ value,
2365
+ ready: true,
2366
+ source
2367
+ }));
2368
+ }
2369
+ #gateFor(pin) {
2370
+ if (pin.domain === void 0) return {
2371
+ domainId: null,
2372
+ ready: true,
2373
+ reason: "no-domain"
2374
+ };
2375
+ const snapshot = this.#domains.get(pin.domain.key);
2376
+ if (snapshot === void 0 || snapshot.state === "off") return {
2377
+ domainId: pin.domain.domainId,
2378
+ ready: false,
2379
+ reason: "domain-off"
2380
+ };
2381
+ if (this.#pendingOpen.has(pin.domain.key)) return {
2382
+ domainId: pin.domain.domainId,
2383
+ ready: false,
2384
+ reason: "opening"
2385
+ };
2386
+ return {
2387
+ domainId: pin.domain.domainId,
2388
+ ready: true,
2389
+ reason: "powered"
2390
+ };
2391
+ }
2392
+ #resolvePin(value, path) {
2393
+ checkRecord(value, path, ["instanceId", "pinName"]);
2394
+ const instanceId = value.instanceId;
2395
+ const pinName = value.pinName;
2396
+ checkIdentity(instanceId, `${path}.instanceId`);
2397
+ checkIdentity(pinName, `${path}.pinName`);
2398
+ const captured = this.#definition.pinsByKey.get(pinKey(instanceId, pinName));
2399
+ if (captured === void 0) throw new RangeError(`${path}: (${JSON.stringify(instanceId)}, ${JSON.stringify(pinName)}) must reference a declared pin`);
2400
+ return captured;
2401
+ }
2402
+ #signalPin(value, path) {
2403
+ const pin = this.#resolvePin(value, path);
2404
+ if (pin.kind !== "signal") throw new RangeError(`${path}: (${JSON.stringify(pin.ref.instanceId)}, ${JSON.stringify(pin.ref.pinName)}) must be a kind 'signal' pin`);
2405
+ return pin;
2406
+ }
2407
+ #floatingSupply(pin) {
2408
+ const captured = this.#definition.pinsByKey.get(pinKey(pin.instanceId, pin.pinName));
2409
+ if (captured === void 0) return void 0;
2410
+ if (captured.domain !== void 0) {
2411
+ if (!this.#topologyReady) return void 0;
2412
+ const snapshot = this.#domains.get(captured.domain.key);
2413
+ if (snapshot === void 0 || snapshot.state !== "on") return void 0;
2414
+ return snapshot.voltage ?? void 0;
2415
+ }
2416
+ const effective = this.#effective.get(captured.key);
2417
+ if (effective === void 0) return void 0;
2418
+ const maxVoltage = effective.model.maxVoltage;
2419
+ return maxVoltage >= 0 ? maxVoltage : void 0;
2420
+ }
2421
+ #floatingSamplingEnabled(pin) {
2422
+ const captured = this.#definition.pinsByKey.get(pinKey(pin.instanceId, pin.pinName));
2423
+ if (captured === void 0) return false;
2424
+ if (captured.domain === void 0) return true;
2425
+ if (!this.#topologyReady) return false;
2426
+ return this.#domains.get(captured.domain.key)?.state === "on";
2427
+ }
2428
+ };
2429
+ function createPowerRuntime(scheduler, definition, options) {
2430
+ return new PowerRuntimeImpl(scheduler, definition, options);
2431
+ }
2432
+ //#endregion
2433
+ //#region src/index.ts
6
2434
  var MICA_CORE_VERSION = "0.0.0";
7
2435
  //#endregion
8
- export { MICA_CORE_VERSION };
2436
+ export { ElectricalConflictError, MAX_TIME_PS, MICA_CORE_VERSION, PowerFeedbackError, SchedulerError, addTime, createFloatingInput, createIRSchedulerInstance, createPowerRuntime, createScheduler, createTopologyRuntime, resolveElectricalNetwork, subtractTime, timePs, toTimePs };
9
2437
 
10
2438
  //# sourceMappingURL=index.js.map