libpetri 3.0.0 → 3.0.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.
Files changed (37) hide show
  1. package/README.md +49 -1
  2. package/dist/chunk-6NH64RCU.js +1016 -0
  3. package/dist/chunk-6NH64RCU.js.map +1 -0
  4. package/dist/{chunk-5W6SVYPD.js → chunk-JZIEWVAV.js} +839 -44
  5. package/dist/chunk-JZIEWVAV.js.map +1 -0
  6. package/dist/debug/index.d.ts +2 -2
  7. package/dist/doclet/index.d.ts +12 -3
  8. package/dist/doclet/index.js +5 -1
  9. package/dist/doclet/index.js.map +1 -1
  10. package/dist/doclet/resources/petrinet-diagrams.css +21 -0
  11. package/dist/doclet/resources/petrinet-diagrams.js +3575 -3573
  12. package/dist/{render-ZGZEZ5RK.js → elk-place-YVNQFGXI.js} +3 -258
  13. package/dist/elk-place-YVNQFGXI.js.map +1 -0
  14. package/dist/{event-store-Df_sAVQ_.d.ts → event-store-BFX_yJ8I.d.ts} +1 -1
  15. package/dist/export/index.d.ts +1 -1
  16. package/dist/index.d.ts +4 -4
  17. package/dist/index.js +1 -1
  18. package/dist/pan-zoom-Cp51IkDl.d.ts +33 -0
  19. package/dist/{petri-net-UQBBkvLl.d.ts → petri-net-WSScMyDL.d.ts} +20 -1
  20. package/dist/preprocess-FN3F75JR.js +193 -0
  21. package/dist/preprocess-FN3F75JR.js.map +1 -0
  22. package/dist/render-QOHGDWNE.js +78 -0
  23. package/dist/render-QOHGDWNE.js.map +1 -0
  24. package/dist/render-dom/index.d.ts +28 -29
  25. package/dist/render-dom/index.js +14 -21
  26. package/dist/render-dom/index.js.map +1 -1
  27. package/dist/verification/index.d.ts +269 -4
  28. package/dist/verification/index.js +7 -1
  29. package/dist/verification/index.js.map +1 -1
  30. package/dist/viewer/index.d.ts +24 -32
  31. package/dist/viewer/index.js +9 -1003
  32. package/dist/viewer/index.js.map +1 -1
  33. package/dist/viewer/viewer.css +21 -0
  34. package/dist/viewer/viewer.iife.js +3575 -3573
  35. package/package.json +2 -2
  36. package/dist/chunk-5W6SVYPD.js.map +0 -1
  37. package/dist/render-ZGZEZ5RK.js.map +0 -1
@@ -1,194 +1,3 @@
1
- // src/viewer/render.ts
2
- import { instance as vizInstance } from "@viz-js/viz";
3
-
4
- // src/viewer/layout/preprocess.ts
5
- function classifyKind(id) {
6
- if (id.startsWith("p_")) return "place";
7
- if (id.startsWith("t_")) return "transition";
8
- return "junction";
9
- }
10
- function parseAttrs(body) {
11
- const attrs = {};
12
- for (const m of body.matchAll(/(\w+)=("(?:[^"]|\\.)*"|\S+?)(?=[\s,]|$)/g)) {
13
- const [, key, raw] = m;
14
- const v = raw.startsWith('"') && raw.endsWith('"') ? raw.slice(1, -1) : raw;
15
- attrs[key] = v;
16
- }
17
- return attrs;
18
- }
19
- function parseLibpetriDot(dot) {
20
- const nodes = /* @__PURE__ */ new Map();
21
- for (const m of dot.matchAll(/^\s*([pjt]_[A-Za-z0-9_]+)\s*\[(.*)\];\s*$/gm)) {
22
- const [, id, body] = m;
23
- nodes.set(id, { id, kind: classifyKind(id), attrs: parseAttrs(body) });
24
- }
25
- const clusters = /* @__PURE__ */ new Map();
26
- const nodeToCluster = /* @__PURE__ */ new Map();
27
- for (const cm of dot.matchAll(/subgraph (cluster_\w+)\s*\{([^]*?)^\s*\}/gm)) {
28
- const [, clusterId, body] = cm;
29
- const memberIds = [...body.matchAll(/^\s*([pjt]_[A-Za-z0-9_]+)\s*\[/gm)].map((m) => m[1]);
30
- clusters.set(clusterId, {
31
- id: clusterId,
32
- shortName: clusterId.replace(/^cluster_/, ""),
33
- nodes: memberIds
34
- });
35
- for (const n of memberIds) nodeToCluster.set(n, clusterId);
36
- }
37
- const stripped = dot.replace(/subgraph cluster_\w+\s*\{[^]*?^\s*\}/gm, "");
38
- for (const m of stripped.matchAll(/^\s*([pjt]_[A-Za-z0-9_]+)\s*\[/gm)) {
39
- const id = m[1];
40
- if (!nodes.has(id)) {
41
- nodes.set(id, { id, kind: classifyKind(id), attrs: {} });
42
- }
43
- }
44
- const orphans = [...nodes.keys()].filter((n) => !nodeToCluster.has(n));
45
- const edges = [];
46
- for (const m of dot.matchAll(/([pjt]_[A-Za-z0-9_]+)\s*->\s*([pjt]_[A-Za-z0-9_]+)\s*(\[[^\]]*\])?/g)) {
47
- const [, src, dst, rawAttrs] = m;
48
- const attrs = rawAttrs ?? "";
49
- if (attrs.includes("lhead=") || attrs.includes("ltail=")) continue;
50
- let arc = "normal";
51
- if (attrs.includes('label="reset"') || attrs.includes("#fd7e14")) arc = "reset";
52
- else if (attrs.includes('arrowhead="odot"') || attrs.includes("#dc3545")) arc = "inhibitor";
53
- else if (attrs.includes('label="read"')) arc = "read";
54
- const stripped2 = attrs.startsWith("[") && attrs.endsWith("]") ? attrs.slice(1, -1) : "";
55
- edges.push({ src, dst, arc, rawAttrs: stripped2 });
56
- }
57
- return { nodes, clusters, nodeToCluster, orphans, edges };
58
- }
59
- function foldOrphans(graph, threshold = 0.7) {
60
- if (threshold <= 0) return graph;
61
- const nodeToCluster = new Map(graph.nodeToCluster);
62
- const clusterMembers = /* @__PURE__ */ new Map();
63
- for (const [id, c] of graph.clusters) clusterMembers.set(id, [...c.nodes]);
64
- const tally = /* @__PURE__ */ new Map();
65
- const bump = (orphan, other) => {
66
- const oc = nodeToCluster.get(other);
67
- if (!oc) return;
68
- let t = tally.get(orphan);
69
- if (!t) {
70
- t = /* @__PURE__ */ new Map();
71
- tally.set(orphan, t);
72
- }
73
- t.set(oc, (t.get(oc) ?? 0) + 1);
74
- };
75
- for (const e of graph.edges) {
76
- const sIsOrphan = !nodeToCluster.has(e.src);
77
- const dIsOrphan = !nodeToCluster.has(e.dst);
78
- if (sIsOrphan && !dIsOrphan) bump(e.src, e.dst);
79
- if (dIsOrphan && !sIsOrphan) bump(e.dst, e.src);
80
- }
81
- for (const id of graph.orphans) {
82
- const t = tally.get(id);
83
- if (!t) continue;
84
- let total = 0;
85
- let bestCluster = null;
86
- let bestCount = 0;
87
- for (const [c, n] of t) {
88
- total += n;
89
- if (n > bestCount) {
90
- bestCount = n;
91
- bestCluster = c;
92
- }
93
- }
94
- if (bestCluster && total > 0 && bestCount / total >= threshold) {
95
- clusterMembers.get(bestCluster).push(id);
96
- nodeToCluster.set(id, bestCluster);
97
- }
98
- }
99
- const clusters = /* @__PURE__ */ new Map();
100
- for (const [id, c] of graph.clusters) {
101
- clusters.set(id, { ...c, nodes: clusterMembers.get(id) });
102
- }
103
- const orphans = [...graph.nodes.keys()].filter((n) => !nodeToCluster.has(n));
104
- return { ...graph, clusters, nodeToCluster, orphans };
105
- }
106
- function replicateShared(graph, opts = {}) {
107
- const max = opts.max ?? Infinity;
108
- const replicateTransitions = opts.replicateTransitions ?? false;
109
- const nodes = new Map(graph.nodes);
110
- const nodeToCluster = new Map(graph.nodeToCluster);
111
- const clusterMembers = /* @__PURE__ */ new Map();
112
- for (const [id, c] of graph.clusters) clusterMembers.set(id, [...c.nodes]);
113
- const edges = graph.edges.map((e) => ({ ...e }));
114
- const foreignByNode = /* @__PURE__ */ new Map();
115
- const noteForeign = (nodeId, otherId) => {
116
- if (!nodeId.startsWith("p_") && !(replicateTransitions && nodeId.startsWith("t_"))) return;
117
- const home = nodeToCluster.get(nodeId);
118
- const otherCluster = nodeToCluster.get(otherId);
119
- if (!otherCluster) return;
120
- if (otherCluster === home) return;
121
- let s = foreignByNode.get(nodeId);
122
- if (!s) {
123
- s = /* @__PURE__ */ new Set();
124
- foreignByNode.set(nodeId, s);
125
- }
126
- s.add(otherCluster);
127
- };
128
- for (const e of edges) {
129
- noteForeign(e.src, e.dst);
130
- noteForeign(e.dst, e.src);
131
- }
132
- const replicaMap = /* @__PURE__ */ new Map();
133
- let replicatedPlaces = 0;
134
- let totalCopies = 0;
135
- for (const [placeId, foreigns] of foreignByNode) {
136
- if (foreigns.size === 0) continue;
137
- if (foreigns.size > max) continue;
138
- const perCluster = /* @__PURE__ */ new Map();
139
- const orig = nodes.get(placeId);
140
- if (!orig) continue;
141
- for (const clusterId of foreigns) {
142
- const shortName = clusterId.replace(/^cluster_/, "");
143
- const repId = `${placeId}__rep__${shortName}`;
144
- perCluster.set(clusterId, repId);
145
- clusterMembers.get(clusterId).push(repId);
146
- nodeToCluster.set(repId, clusterId);
147
- nodes.set(repId, {
148
- id: repId,
149
- kind: orig.kind,
150
- attrs: { ...orig.attrs },
151
- replica: true,
152
- replicaOf: placeId
153
- });
154
- totalCopies++;
155
- }
156
- replicaMap.set(placeId, perCluster);
157
- replicatedPlaces++;
158
- nodes.set(placeId, { ...orig, replica: true, replicaOf: placeId });
159
- }
160
- for (const e of edges) {
161
- const sMap = replicaMap.get(e.src);
162
- const dMap = replicaMap.get(e.dst);
163
- if (sMap) {
164
- const dCl = nodeToCluster.get(e.dst);
165
- if (dCl && sMap.has(dCl)) e.src = sMap.get(dCl);
166
- }
167
- if (dMap) {
168
- const sCl = nodeToCluster.get(e.src);
169
- if (sCl && dMap.has(sCl)) e.dst = dMap.get(sCl);
170
- }
171
- }
172
- for (const [origId] of replicaMap) {
173
- if (nodeToCluster.has(origId)) continue;
174
- const stillHasEdge = edges.some((e) => e.src === origId || e.dst === origId);
175
- if (!stillHasEdge) nodes.delete(origId);
176
- }
177
- const clusters = /* @__PURE__ */ new Map();
178
- for (const [id, c] of graph.clusters) {
179
- clusters.set(id, { ...c, nodes: clusterMembers.get(id) });
180
- }
181
- const orphans = [...nodes.keys()].filter((n) => !nodeToCluster.has(n));
182
- return {
183
- nodes,
184
- clusters,
185
- nodeToCluster,
186
- orphans,
187
- edges,
188
- replicateStats: { replicatedPlaces, totalCopies }
189
- };
190
- }
191
-
192
1
  // src/viewer/layout/elk-place.ts
193
2
  import ELK from "elkjs/lib/elk.bundled.js";
194
3
  var ORCHESTRATOR_CLUSTER_ID = "cluster_orchestrator";
@@ -681,72 +490,8 @@ function writeBack(graph, layout) {
681
490
  lines.push("}");
682
491
  return lines.join("\n");
683
492
  }
684
-
685
- // src/viewer/render.ts
686
- var vizPromise = null;
687
- function getViz() {
688
- if (!vizPromise) vizPromise = vizInstance();
689
- return vizPromise;
690
- }
691
- async function renderDotToSvg(dotSource) {
692
- const viz = await getViz();
693
- return viz.renderSVGElement(dotSource, { engine: "dot" });
694
- }
695
- function fnv1a(input) {
696
- let h = 2166136261;
697
- for (let i = 0; i < input.length; i++) {
698
- h ^= input.charCodeAt(i);
699
- h = Math.imul(h, 16777619);
700
- }
701
- return (h >>> 0).toString(16);
702
- }
703
- var PINNED_DOT_CACHE_CAP = 16;
704
- var pinnedDotCache = /* @__PURE__ */ new Map();
705
- function getCachedPinnedDot(key) {
706
- const hit = pinnedDotCache.get(key);
707
- if (hit !== void 0) {
708
- pinnedDotCache.delete(key);
709
- pinnedDotCache.set(key, hit);
710
- }
711
- return hit;
712
- }
713
- function setCachedPinnedDot(key, value) {
714
- pinnedDotCache.set(key, value);
715
- while (pinnedDotCache.size > PINNED_DOT_CACHE_CAP) {
716
- const oldest = pinnedDotCache.keys().next().value;
717
- if (oldest === void 0) break;
718
- pinnedDotCache.delete(oldest);
719
- }
720
- }
721
- function _clearElkLayoutCache() {
722
- pinnedDotCache.clear();
723
- }
724
- async function renderDotToSvgWithElkLayout(dotSource, cfg) {
725
- const key = fnv1a(dotSource + "\0" + JSON.stringify(cfg ?? {}));
726
- let pinnedDot = getCachedPinnedDot(key);
727
- if (pinnedDot === void 0) {
728
- const graph = replicateShared(
729
- foldOrphans(parseLibpetriDot(dotSource), 0.7),
730
- { max: Infinity }
731
- );
732
- const layout = await elkLayout(graph, cfg);
733
- pinnedDot = writeBack(graph, layout);
734
- setCachedPinnedDot(key, pinnedDot);
735
- }
736
- const viz = await getViz();
737
- return viz.renderSVGElement(pinnedDot, {
738
- // nop2 draws the pinned node positions AND our ELK-computed orthogonal
739
- // edge routes verbatim. See writeBack/edgePosSpline: we route edges
740
- // ourselves rather than let Graphviz's ortho router run, which crashes
741
- // the wasm on large nets.
742
- engine: "nop2",
743
- yInvert: true
744
- });
745
- }
746
493
  export {
747
- _clearElkLayoutCache,
748
- getViz,
749
- renderDotToSvg,
750
- renderDotToSvgWithElkLayout
494
+ elkLayout,
495
+ writeBack
751
496
  };
752
- //# sourceMappingURL=render-ZGZEZ5RK.js.map
497
+ //# sourceMappingURL=elk-place-YVNQFGXI.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/viewer/layout/elk-place.ts"],"sourcesContent":["/**\n * ELK placement + DOT pin-mode rewrite.\n *\n * Stage 2 of the C0 pipeline. Takes the {@link GraphModel} produced by\n * {@link parseLibpetriDot} → {@link foldOrphans} → {@link replicateShared},\n * runs ELKjs to compute absolute `(x, y)` per node, bounding boxes per\n * cluster, and orthogonal `(x, y)` routes per edge, then emits a fresh DOT\n * string with node positions pinned via `pos=\"x,y!\"`, cluster boxes via\n * `bb=\"…\"`, and each edge's route as a `pos=\"e,…\"` spline. Downstream callers\n * render the result through `@viz-js/viz` with `engine: 'nop2'` — Graphviz\n * draws the pinned node positions AND edge routes verbatim, doing no layout or\n * routing of its own (see {@link writeBack} / {@link edgePosSpline} for why we\n * route edges ourselves rather than let Graphviz's ortho router run).\n *\n * `elkLayout` takes (graph, cfg?); `writeBack` takes (graph, layout).\n * Neither touches the original DOT text — the model after replication has\n * nodes the original DOT doesn't, so patching the original would be\n * incorrect.\n *\n * Layout is configurable via {@link ElkLayoutConfig}: the per-subnet\n * algorithm (`clusterLayout`) and whether clusters dominated by side-effect\n * leaf places pack those leaves into a grid sub-block (`leafPacking`).\n *\n * @module viewer/layout/elk-place\n */\n\nimport ELK from 'elkjs/lib/elk.bundled.js';\nimport type {\n ElkExtendedEdge,\n ElkNode,\n} from 'elkjs/lib/elk-api.js';\nimport type { ArcType, GraphModel, ParsedCluster, ParsedNode } from './preprocess.js';\n\nconst ORCHESTRATOR_CLUSTER_ID = 'cluster_orchestrator';\n\n// Visual sizing — kept proportional to the v3 reference renderer so the\n// resulting bounding box is comparable to C0-GOOD_v3.svg.\nconst PLACE_RADIUS = 22;\nconst PLACE_CHARW = 7.4;\nconst PLACE_PAD_BELOW = 16;\nconst FONT_PLACE = 13;\nconst TRANSITION_MIN_W = 180;\nconst TRANSITION_H = 40;\nconst TRANSITION_CHARW = 8.5;\nconst JUNCTION_SIZE = 28;\nconst FONT_CLUSTER = 17;\nconst ROOT_SPACING = 90;\nconst CLUSTER_SPACING = 16;\n\n/**\n * Width/height in points that ELK should reserve for a node. Mirrors\n * `nodeDims` in v3 lines 66–78 — places get extra height to leave room for\n * the xlabel underneath the circle.\n */\nfunction nodeDims(node: ParsedNode): { width: number; height: number } {\n if (node.kind === 'place') {\n const label = (node.attrs.xlabel ?? node.attrs.label ?? node.id).replace(/\\\\n/g, ' ');\n const labelWidth = label.length * PLACE_CHARW;\n const circleD = PLACE_RADIUS * 2 + 4;\n return {\n width: Math.max(circleD, labelWidth),\n height: circleD + PLACE_PAD_BELOW + FONT_PLACE,\n };\n }\n if (node.kind === 'junction') {\n return { width: JUNCTION_SIZE, height: JUNCTION_SIZE };\n }\n const label = (node.attrs.label ?? node.id).replace(/\\\\n/g, ' ');\n return {\n width: Math.max(TRANSITION_MIN_W, label.length * TRANSITION_CHARW),\n height: TRANSITION_H,\n };\n}\n\nexport interface NodePosition {\n readonly x: number;\n readonly y: number;\n readonly width: number;\n readonly height: number;\n}\n\nexport interface ClusterBox {\n readonly x: number;\n readonly y: number;\n readonly width: number;\n readonly height: number;\n}\n\nexport interface EdgePath {\n readonly start: { x: number; y: number };\n readonly end: { x: number; y: number };\n readonly bends: ReadonlyArray<{ x: number; y: number }>;\n}\n\nexport interface LayoutResult {\n readonly nodePositions: ReadonlyMap<string, NodePosition>;\n readonly clusterBoxes: ReadonlyMap<string, ClusterBox>;\n /** Keyed on `${src}->${dst}` — ELK-computed edge routes for Graphviz `nop2`. */\n readonly edgePaths: ReadonlyMap<string, EdgePath>;\n readonly totalWidth: number;\n readonly totalHeight: number;\n}\n\n// Shared edge-routing options for the layered layouts. Spacing gives parallel\n// edges their own channels (ELK's ~10pt default lets dense fan-in/out at\n// junctions and shared places collapse into one thick line) and keeps edges\n// off the nodes/labels they pass. `mergeEdges` bundles arcs that share an\n// endpoint into a common orthogonal trunk that branches at each end — so e.g.\n// a transition's many reset arcs read as one labelled bus with a tidy branch\n// up to each place, instead of a stack of nested right-angle brackets.\n// Trade-off (accepted, EXP-004): arcs sharing an endpoint run collinearly along\n// the shared trunk, so over that stretch their strokes over-paint — only the\n// endpoints (arrowheads/labels) stay per-type distinct. Kept intentionally for\n// legibility on dense fan-in/out; any added ELK layout cost is paid once per\n// unique net and amortized by the pinned-DOT cache in render.ts.\nconst LAYERED_EDGE_OPTS: Record<string, string> = {\n 'elk.spacing.edgeEdge': '18',\n 'elk.spacing.edgeNode': '18',\n 'elk.layered.spacing.edgeEdgeBetweenLayers': '16',\n 'elk.layered.spacing.edgeNodeBetweenLayers': '16',\n 'elk.layered.mergeEdges': 'true',\n};\n\nconst CLUSTER_OPTIONS: Record<string, string> = {\n 'elk.padding': `[top=${FONT_CLUSTER + 16},left=16,bottom=16,right=16]`,\n 'elk.algorithm': 'layered',\n 'elk.direction': 'DOWN',\n 'elk.spacing.nodeNode': String(CLUSTER_SPACING),\n 'elk.layered.spacing.nodeNodeBetweenLayers': String(CLUSTER_SPACING + 6),\n 'elk.edgeRouting': 'ORTHOGONAL',\n ...LAYERED_EDGE_OPTS,\n};\n\nconst ROOT_OPTIONS: Record<string, string> = {\n 'elk.algorithm': 'org.eclipse.elk.rectpacking',\n 'elk.aspectRatio': '1.4',\n 'elk.padding': '[top=24,left=24,bottom=24,right=24]',\n 'elk.spacing.nodeNode': String(ROOT_SPACING),\n};\n\nconst ORCHESTRATOR_OPTIONS: Record<string, string> = {\n ...CLUSTER_OPTIONS,\n 'elk.aspectRatio': '2.4',\n 'elk.spacing.nodeNode': '10',\n};\n\n/**\n * Layout options for the synthetic orchestrator when it IS the whole\n * diagram — the flat \"one-net\" view (no real subnet clusters).\n *\n * `ORCHESTRATOR_OPTIONS` deliberately packs the orphan block tight\n * (`nodeNode: 10`, `aspectRatio: 2.4`) so it stays compact next to the\n * real subnet blocks the `rectpacking` root arranges. When the graph is\n * flat there are no other blocks: that compaction just bunches the whole\n * net up. Flat mode instead gets a generous layered layout — a clean\n * place→transition rank flow — with no forced aspect ratio.\n */\nconst FLAT_OPTIONS: Record<string, string> = {\n 'elk.padding': `[top=24,left=24,bottom=24,right=24]`,\n 'elk.algorithm': 'layered',\n 'elk.direction': 'DOWN',\n 'elk.spacing.nodeNode': '45',\n 'elk.layered.spacing.nodeNodeBetweenLayers': '60',\n 'elk.edgeRouting': 'ORTHOGONAL',\n ...LAYERED_EDGE_OPTS,\n};\n\n/**\n * Per-subnet ELK algorithm used when {@link ElkLayoutConfig.clusterLayout}\n * is `'rectpacking'` — packs a cluster's nodes as a 2-D grid, ignoring\n * intra-cluster edges. Maximally compact, but the place→transition flow\n * reading order is lost.\n */\nconst RECTPACK_CLUSTER_OPTIONS: Record<string, string> = {\n 'elk.padding': `[top=${FONT_CLUSTER + 16},left=16,bottom=16,right=16]`,\n 'elk.algorithm': 'org.eclipse.elk.rectpacking',\n 'elk.aspectRatio': '1.3',\n 'elk.spacing.nodeNode': String(CLUSTER_SPACING),\n};\n\n// Sub-layout options for a leaf-packed cluster: the flow part keeps the\n// layered place→transition rank flow; the leaf part is a compact grid.\nconst FLOW_SUB_OPTIONS: Record<string, string> = {\n 'elk.algorithm': 'layered',\n 'elk.direction': 'DOWN',\n 'elk.spacing.nodeNode': String(CLUSTER_SPACING),\n 'elk.layered.spacing.nodeNodeBetweenLayers': String(CLUSTER_SPACING + 6),\n 'elk.edgeRouting': 'ORTHOGONAL',\n ...LAYERED_EDGE_OPTS,\n};\nconst LEAF_SUB_OPTIONS: Record<string, string> = {\n 'elk.algorithm': 'org.eclipse.elk.rectpacking',\n 'elk.aspectRatio': '1.4',\n 'elk.spacing.nodeNode': String(CLUSTER_SPACING),\n};\n/** Gap between the layered flow and the packed leaf block, in points. */\nconst LEAF_BLOCK_GAP = 40;\n/** Padding inside a leaf-packed cluster box (left/right/bottom). */\nconst SUB_PAD = 16;\n/** Top padding inside a cluster box — room for the cluster label. */\nconst SUB_PAD_TOP = FONT_CLUSTER + 16;\n\n// ======================== Layout configuration ========================\n\n/** Per-subnet ELK algorithm. */\nexport type ClusterLayout = 'layered' | 'rectpacking';\n\n/** Tuning for side-effect-leaf packing — see {@link ElkLayoutConfig}. */\nexport interface LeafPackingOptions {\n /**\n * A cluster is \"dominated\" — and its side-effect leaf places get packed\n * into a grid sub-block — when it has at least this many of them.\n * Default 10.\n */\n readonly minLeaves?: number;\n /**\n * Arc types that mark a place as a side-effect leaf (a place whose every\n * intra-cluster arc is one of these carries no data-flow ordering).\n * Default `['reset', 'read']`.\n */\n readonly arcs?: readonly ArcType[];\n}\n\n/**\n * Layout configuration for {@link elkLayout}, surfaced to viewer callers\n * via `MountOptions`.\n */\nexport interface ElkLayoutConfig {\n /** Per-subnet ELK algorithm. Default `'layered'`. */\n readonly clusterLayout?: ClusterLayout;\n /**\n * Pack side-effect leaf places into a rectpacking sub-block, in clusters\n * where they dominate. `true` (default) enables it with defaults; `false`\n * disables it; an object tunes the threshold / arc types. Only takes\n * effect when `clusterLayout` is `'layered'`.\n */\n readonly leafPacking?: boolean | LeafPackingOptions;\n}\n\ninterface ResolvedConfig {\n readonly clusterLayout: ClusterLayout;\n readonly leafPacking: {\n readonly enabled: boolean;\n readonly minLeaves: number;\n readonly arcs: ReadonlySet<ArcType>;\n };\n}\n\nconst DEFAULT_MIN_LEAVES = 10;\n\nfunction resolveConfig(cfg?: ElkLayoutConfig): ResolvedConfig {\n const lp = cfg?.leafPacking ?? true;\n const lpObj: LeafPackingOptions = typeof lp === 'object' ? lp : {};\n return {\n clusterLayout: cfg?.clusterLayout ?? 'layered',\n leafPacking: {\n enabled: lp !== false,\n minLeaves: lpObj.minLeaves ?? DEFAULT_MIN_LEAVES,\n arcs: new Set(lpObj.arcs ?? ['reset', 'read']),\n },\n };\n}\n\n/** A cluster pre-laid-out as [layered flow] stacked over [packed leaves]. */\ninterface PrebuiltCluster {\n readonly width: number;\n readonly height: number;\n /** member node id → position relative to the cluster's top-left. */\n readonly rel: ReadonlyMap<string, NodePosition>;\n}\n\n/**\n * Split a cluster's members into flow nodes and \"side-effect leaf places\".\n *\n * A side-effect leaf is a place whose every intra-cluster arc is a\n * side-effect arc (per `arcs` — reset/read). Such a place is not on any\n * data-flow path, so packing it as a grid cell loses no reading order.\n */\nfunction classifyLeaves(\n graph: GraphModel,\n cluster: ParsedCluster,\n arcs: ReadonlySet<ArcType>,\n): { flow: string[]; leaves: string[] } {\n const members = new Set(cluster.nodes);\n const intraDegree = new Map<string, number>();\n const intraSideEffect = new Map<string, number>();\n for (const e of graph.edges) {\n if (!members.has(e.src) || !members.has(e.dst)) continue;\n const sideEffect = arcs.has(e.arc) ? 1 : 0;\n for (const id of [e.src, e.dst]) {\n intraDegree.set(id, (intraDegree.get(id) ?? 0) + 1);\n intraSideEffect.set(id, (intraSideEffect.get(id) ?? 0) + sideEffect);\n }\n }\n const flow: string[] = [];\n const leaves: string[] = [];\n for (const id of cluster.nodes) {\n const node = graph.nodes.get(id);\n if (!node) continue;\n const deg = intraDegree.get(id) ?? 0;\n const isLeaf =\n node.kind === 'place' &&\n deg >= 1 &&\n (intraSideEffect.get(id) ?? 0) === deg;\n (isLeaf ? leaves : flow).push(id);\n }\n return { flow, leaves };\n}\n\n/**\n * Lay a dominated cluster out as two stacked blocks: the flow nodes via\n * `layered` (place→transition rank flow), the side-effect leaves via\n * `rectpacking` (a compact grid). Returns the cluster's overall size and\n * every member node's position relative to the cluster's top-left.\n */\nasync function layoutDominatedCluster(\n elk: InstanceType<typeof ELK>,\n graph: GraphModel,\n flow: string[],\n leaves: string[],\n): Promise<PrebuiltCluster> {\n const dimsOf = (id: string): { width: number; height: number } =>\n nodeDims(graph.nodes.get(id)!);\n\n const flowSet = new Set(flow);\n const flowEdges: ElkExtendedEdge[] = [];\n graph.edges.forEach((e, i) => {\n if (flowSet.has(e.src) && flowSet.has(e.dst)) {\n flowEdges.push({ id: `fe${i}`, sources: [e.src], targets: [e.dst] });\n }\n });\n\n const flowLayout: ElkNode = await elk.layout({\n id: '__flow',\n layoutOptions: FLOW_SUB_OPTIONS,\n children: flow.map(id => ({ id, ...dimsOf(id) })),\n edges: flowEdges,\n });\n const leafLayout: ElkNode = await elk.layout({\n id: '__leaves',\n layoutOptions: LEAF_SUB_OPTIONS,\n children: leaves.map(id => ({ id, ...dimsOf(id) })),\n edges: [],\n });\n\n const flowW = flowLayout.width ?? 0;\n const flowH = flowLayout.height ?? 0;\n const leafW = leafLayout.width ?? 0;\n const leafH = leafLayout.height ?? 0;\n const contentW = Math.max(flowW, leafW);\n const contentH = flowH + (leaves.length > 0 ? LEAF_BLOCK_GAP + leafH : 0);\n\n const rel = new Map<string, NodePosition>();\n const flowOffX = SUB_PAD + (contentW - flowW) / 2;\n for (const ch of flowLayout.children ?? []) {\n rel.set(ch.id, {\n x: flowOffX + (ch.x ?? 0),\n y: SUB_PAD_TOP + (ch.y ?? 0),\n width: ch.width ?? 0,\n height: ch.height ?? 0,\n });\n }\n const leafOffX = SUB_PAD + (contentW - leafW) / 2;\n const leafOffY = SUB_PAD_TOP + flowH + LEAF_BLOCK_GAP;\n for (const ch of leafLayout.children ?? []) {\n rel.set(ch.id, {\n x: leafOffX + (ch.x ?? 0),\n y: leafOffY + (ch.y ?? 0),\n width: ch.width ?? 0,\n height: ch.height ?? 0,\n });\n }\n\n return {\n width: contentW + 2 * SUB_PAD,\n height: contentH + SUB_PAD_TOP + SUB_PAD,\n rel,\n };\n}\n\n/**\n * Group edges by the cluster that contains both endpoints (or 'root' if\n * the edge crosses cluster boundaries).\n *\n * Orphans count as living in `cluster_orchestrator` for routing purposes,\n * matching v3's `ownerOf` (line 223).\n */\nfunction partitionEdges(graph: GraphModel): {\n byCluster: Map<string, ElkExtendedEdge[]>;\n cross: ElkExtendedEdge[];\n /** edgeId → \"src->dst\" so the layout walk can recover the original key. */\n edgeIdToKey: Map<string, string>;\n} {\n const byCluster = new Map<string, ElkExtendedEdge[]>();\n const cross: ElkExtendedEdge[] = [];\n const edgeIdToKey = new Map<string, string>();\n const ownerOf = (id: string): string =>\n graph.nodeToCluster.get(id) ?? ORCHESTRATOR_CLUSTER_ID;\n\n graph.edges.forEach((e, i) => {\n const so = ownerOf(e.src);\n const dt = ownerOf(e.dst);\n const id = `e${i}`;\n edgeIdToKey.set(id, `${e.src}->${e.dst}`);\n const elkEdge: ElkExtendedEdge = {\n id,\n sources: [e.src],\n targets: [e.dst],\n };\n if (so === dt) {\n let list = byCluster.get(so);\n if (!list) { list = []; byCluster.set(so, list); }\n list.push(elkEdge);\n } else {\n cross.push(elkEdge);\n }\n });\n return { byCluster, cross, edgeIdToKey };\n}\n\n/**\n * Run ELK layout against the C0 graph and return absolute positions.\n *\n * Orphan nodes are wrapped in a synthetic `cluster_orchestrator` so ELK\n * lays them out as a single block alongside the real subnets, which the\n * `rectpacking` root algorithm then packs.\n *\n * With the default config a cluster dominated by side-effect leaf places\n * (≥ `leafPacking.minLeaves` of them) is pre-laid-out as a layered flow\n * stacked over a packed grid of those leaves — so a transition with many\n * reset arcs no longer strings its leaves into one wide row. See\n * {@link ElkLayoutConfig}.\n */\nexport async function elkLayout(\n graph: GraphModel,\n cfg?: ElkLayoutConfig,\n): Promise<LayoutResult> {\n const config = resolveConfig(cfg);\n const { byCluster, cross, edgeIdToKey } = partitionEdges(graph);\n const elk = new ELK();\n const clusterOptions =\n config.clusterLayout === 'rectpacking' ? RECTPACK_CLUSTER_OPTIONS : CLUSTER_OPTIONS;\n\n // Pre-lay-out clusters dominated by side-effect leaf places. Only in\n // 'layered' mode — 'rectpacking' already packs every cluster's nodes.\n const prebuilt = new Map<string, PrebuiltCluster>();\n if (config.clusterLayout === 'layered' && config.leafPacking.enabled) {\n for (const [clusterId, cluster] of graph.clusters) {\n const { flow, leaves } = classifyLeaves(graph, cluster, config.leafPacking.arcs);\n if (leaves.length >= config.leafPacking.minLeaves && flow.length > 0) {\n prebuilt.set(clusterId, await layoutDominatedCluster(elk, graph, flow, leaves));\n }\n }\n }\n\n const elkChildren: ElkNode[] = [];\n for (const [clusterId, cluster] of graph.clusters) {\n const pre = prebuilt.get(clusterId);\n if (pre) {\n // Childless fixed-size box — ELK's root packer places it; the member\n // node positions are spliced back in during the walk.\n elkChildren.push({ id: clusterId, width: pre.width, height: pre.height });\n continue;\n }\n elkChildren.push({\n id: clusterId,\n layoutOptions: clusterOptions,\n children: cluster.nodes\n .map(id => graph.nodes.get(id))\n .filter((n): n is ParsedNode => n !== undefined)\n .map(n => ({ id: n.id, ...nodeDims(n) })),\n edges: byCluster.get(clusterId) ?? [],\n });\n }\n // Synthetic orchestrator cluster holds the remaining orphans. When there\n // are no real clusters the orchestrator IS the whole diagram (flat view),\n // so it gets a generous full layered layout instead of the compact block\n // packing tuned for sitting beside real subnets.\n const orchestratorOptions =\n graph.clusters.size === 0\n ? FLAT_OPTIONS\n : config.clusterLayout === 'rectpacking'\n ? RECTPACK_CLUSTER_OPTIONS\n : ORCHESTRATOR_OPTIONS;\n elkChildren.push({\n id: ORCHESTRATOR_CLUSTER_ID,\n layoutOptions: orchestratorOptions,\n children: graph.orphans\n .map(id => graph.nodes.get(id))\n .filter((n): n is ParsedNode => n !== undefined)\n .map(n => ({ id: n.id, ...nodeDims(n) })),\n edges: byCluster.get(ORCHESTRATOR_CLUSTER_ID) ?? [],\n });\n\n const rootGraph: ElkNode = {\n id: 'root',\n layoutOptions: ROOT_OPTIONS,\n children: elkChildren,\n // Cross-cluster edges feed `edgePaths` (the routes nop2 draws), but\n // `rectpacking` never routes them, so they fall back to the L-corner in\n // `edgePosSpline`. Drop them when any cluster is a childless prebuilt box —\n // the edges reference member nodes ELK can no longer resolve in the hierarchy.\n edges: prebuilt.size > 0 ? [] : cross,\n };\n const layout: ElkNode = await elk.layout(rootGraph);\n\n const nodePositions = new Map<string, NodePosition>();\n const clusterBoxes = new Map<string, ClusterBox>();\n const edgePaths = new Map<string, EdgePath>();\n\n const walk = (node: ElkNode, parentX: number, parentY: number): void => {\n const ax = parentX + (node.x ?? 0);\n const ay = parentY + (node.y ?? 0);\n const pre = node.id !== 'root' ? prebuilt.get(node.id) : undefined;\n const isCluster =\n node.id !== 'root' && ((node.children?.length ?? 0) > 0 || pre !== undefined);\n if (isCluster) {\n clusterBoxes.set(node.id, {\n x: ax, y: ay,\n width: node.width ?? 0,\n height: node.height ?? 0,\n });\n // Prebuilt cluster: splice in the pre-computed member positions,\n // offset by the cluster's ELK-assigned absolute origin.\n if (pre) {\n for (const [memberId, r] of pre.rel) {\n nodePositions.set(memberId, {\n x: ax + r.x, y: ay + r.y, width: r.width, height: r.height,\n });\n }\n }\n }\n if (node.id !== 'root' && !isCluster) {\n nodePositions.set(node.id, {\n x: ax, y: ay,\n width: node.width ?? 0,\n height: node.height ?? 0,\n });\n }\n // Edges inside this node — their section coords are in the parent's\n // coordinate frame, so the offset to absolutize is (parentX, parentY)\n // for root edges, or (ax, ay) for cluster-internal edges.\n const edgeOffsetX = node.id === 'root' ? parentX : ax;\n const edgeOffsetY = node.id === 'root' ? parentY : ay;\n for (const edge of node.edges ?? []) {\n const key = edge.id ? edgeIdToKey.get(edge.id) : undefined;\n const section = edge.sections?.[0];\n if (!key || !section) continue;\n edgePaths.set(key, {\n start: {\n x: edgeOffsetX + section.startPoint.x,\n y: edgeOffsetY + section.startPoint.y,\n },\n end: {\n x: edgeOffsetX + section.endPoint.x,\n y: edgeOffsetY + section.endPoint.y,\n },\n bends: (section.bendPoints ?? []).map(p => ({\n x: edgeOffsetX + p.x,\n y: edgeOffsetY + p.y,\n })),\n });\n }\n for (const child of node.children ?? []) walk(child, ax, ay);\n };\n walk(layout, 0, 0);\n\n return {\n nodePositions,\n clusterBoxes,\n edgePaths,\n totalWidth: layout.width ?? 0,\n totalHeight: layout.height ?? 0,\n };\n}\n\n/**\n * Escape an attribute value for inclusion in a DOT string literal.\n *\n * Quotes are doubled (per the DOT escape convention `\\\"`).\n */\nfunction quote(value: string): string {\n return `\"${value.replace(/\\\\/g, '\\\\\\\\').replace(/\"/g, '\\\\\"')}\"`;\n}\n\n// ======================== Orthogonal edge routing ========================\n//\n// We render the pinned graph with Graphviz engine `nop2`, which draws edges\n// from the `pos=` spline we supply verbatim (no routing). We supply ELK's own\n// orthogonal route for each edge. This deliberately avoids Graphviz's `ortho`\n// spline router, which is unusable here: its maze/trapezoid allocator (see\n// `mkMaze`, `trapezoid.c`) requests a large block that the @viz-js/viz wasm\n// heap denies past ~220 node obstacles, and Graphviz does not check the failed\n// allocation, so the next write traps the wasm (\"memory access out of\n// bounds\"). Native Graphviz has the heap headroom and never trips it, but the\n// wasm build does — so on the big Marvin nets `splines=ortho` hard-crashes.\n// Drawing ELK's routes ourselves sidesteps that router entirely and cannot\n// crash, on a net of any size.\n\n/** Length of the drawn arrowhead, in points (Graphviz default ≈ 10). */\nconst ARROW_LEN = 10;\n\n/**\n * Inches → drawn *half*-extent in points (72 dpi ÷ 2). Libpetri's `width`/\n * `height` attrs are inches; a node's drawn radius / box half-side is\n * `inches * HALF_PT_PER_IN`.\n */\nconst HALF_PT_PER_IN = 36;\n\ninterface Pt {\n readonly x: number;\n readonly y: number;\n}\n\n/** Visual node center — Graphviz draws the shape centered on the pinned pos. */\nfunction drawnCenter(pos: NodePosition): Pt {\n return { x: pos.x + pos.width / 2, y: pos.y + pos.height / 2 };\n}\n\ninterface Extent {\n readonly shape: 'circle' | 'diamond' | 'box';\n readonly rx: number;\n readonly ry: number;\n}\n\n/**\n * Half-extents of a node's *drawn* shape (not ELK's layout-reserved box).\n * The drawn shape comes from libpetri's own `width`/`height` attrs\n * (inches → points): circles/diamonds are fixed-size, while a transition box\n * grows to its label, so its half-width is estimated from the label length.\n */\nfunction visualExtent(node: ParsedNode): Extent {\n const wIn = parseFloat(node.attrs.width ?? '');\n const hIn = parseFloat(node.attrs.height ?? '');\n const shape = node.attrs.shape ?? '';\n // Fallbacks are the libpetri StyleConstants defaults in drawn half-points:\n // place/end 0.35in, junction 0.3in, transition box 0.8×0.4in.\n if (shape === 'circle' || shape === 'doublecircle') {\n const r =\n (Number.isFinite(wIn) ? wIn * HALF_PT_PER_IN : 0.35 * HALF_PT_PER_IN) +\n (shape === 'doublecircle' ? 4 : 0); // +4pt for the outer end-place ring\n return { shape: 'circle', rx: r, ry: r };\n }\n if (shape === 'diamond') {\n // Junctions are squares-on-point; approachEndpoint clips to the diamond's\n // bounding box, so an arrowhead entering near a tip can float a few points\n // off the slanted face — cosmetic on 0.3in junctions.\n return {\n shape: 'diamond',\n rx: Number.isFinite(wIn) ? wIn * HALF_PT_PER_IN : 0.3 * HALF_PT_PER_IN,\n ry: Number.isFinite(hIn) ? hIn * HALF_PT_PER_IN : 0.3 * HALF_PT_PER_IN,\n };\n }\n // Box (transition): height is reliable; width grows to the label, so estimate\n // from the label length (~6pt/char + 16pt pad) and floor by the attr width.\n const label = (node.attrs.label ?? '').replace(/\\\\n/g, ' ');\n const estHalfW = (label.length * 6 + 16) / 2;\n return {\n shape: 'box',\n rx: Math.max(Number.isFinite(wIn) ? wIn * HALF_PT_PER_IN : 0.8 * HALF_PT_PER_IN, estHalfW),\n ry: Number.isFinite(hIn) ? hIn * HALF_PT_PER_IN : 0.4 * HALF_PT_PER_IN,\n };\n}\n\ninterface Approach {\n /** Point ON the visual node boundary where the edge meets the shape. */\n readonly entry: Pt;\n /** Optional pre-entry corner that keeps the connector a right angle. */\n readonly jog?: Pt;\n}\n\n/**\n * Where an edge should meet a node's *visual* boundary, given ELK's route\n * point on the node's layout box (`boxEnd`, larger than the drawn shape) and\n * the adjacent route point (`neighbor`). Keeps the approach axis-aligned.\n *\n * When the approach would land within the shape's span it clips straight onto\n * the boundary (preserving ELK's fan-out across a wide box). When it would\n * MISS the shape — e.g. fan-in to a place whose label-padded box is far wider\n * than its little circle, so ELK enters at an x beyond the circle radius — it\n * funnels to the near-side centre of the shape and returns a `jog` so the\n * connector turns at a right angle instead of leaving the arrowhead floating\n * beside the node.\n */\nfunction approachEndpoint(center: Pt, ext: Extent, boxEnd: Pt, neighbor: Pt): Approach {\n const vertical = Math.abs(neighbor.x - boxEnd.x) <= Math.abs(neighbor.y - boxEnd.y);\n if (vertical) {\n const sgn = neighbor.y < center.y ? -1 : 1; // meet the top (−) or bottom (+)\n const off = Math.abs(boxEnd.x - center.x);\n const reach = ext.shape === 'circle' ? ext.rx - 1 : ext.rx;\n if (off < reach) {\n const dy = ext.shape === 'circle' ? Math.sqrt(ext.rx * ext.rx - off * off) : ext.ry;\n return { entry: { x: boxEnd.x, y: center.y + sgn * dy } };\n }\n return { entry: { x: center.x, y: center.y + sgn * ext.ry }, jog: { x: center.x, y: neighbor.y } };\n }\n const sgn = neighbor.x < center.x ? -1 : 1; // meet the left (−) or right (+)\n const off = Math.abs(boxEnd.y - center.y);\n const reach = ext.shape === 'circle' ? ext.rx - 1 : ext.ry;\n if (off < reach) {\n const dx = ext.shape === 'circle' ? Math.sqrt(ext.rx * ext.rx - off * off) : ext.rx;\n return { entry: { x: center.x + sgn * dx, y: boxEnd.y } };\n }\n return { entry: { x: center.x + sgn * ext.rx, y: center.y }, jog: { x: neighbor.x, y: center.y } };\n}\n\n/** Format a point as Graphviz `x,y` with 2-decimal precision. */\nconst fmtPt = (p: Pt): string => `${p.x.toFixed(2)},${p.y.toFixed(2)}`;\nconst lerp = (a: Pt, b: Pt, t: number): Pt => ({ x: a.x + (b.x - a.x) * t, y: a.y + (b.y - a.y) * t });\n\n/**\n * Build a Graphviz edge `pos` spline that draws ELK's orthogonal route as\n * straight segments, clipped to the visual node boundaries so arrowheads land\n * on the shape edge. Format: `e,<tip> <start> <c1> <c2> <anchor> …` where the\n * control-point triples are collinear (so each cubic renders as a line).\n *\n * Cross-cluster and leaf-packed edges have no ELK route (rectpacking and\n * prebuilt boxes don't route them), so they fall back to a straight L-corner\n * between node centers — which may cross intervening nodes. That is the\n * accepted price of right-angle edges at any net size.\n *\n * Returns null when either endpoint has no laid-out position.\n */\nfunction edgePosSpline(\n graph: GraphModel,\n layout: LayoutResult,\n src: string,\n dst: string,\n): string | null {\n const srcPos = layout.nodePositions.get(src);\n const dstPos = layout.nodePositions.get(dst);\n const srcNode = graph.nodes.get(src);\n const dstNode = graph.nodes.get(dst);\n if (!srcPos || !dstPos || !srcNode || !dstNode) return null;\n\n const sc = drawnCenter(srcPos);\n const tc = drawnCenter(dstPos);\n const route = layout.edgePaths.get(`${src}->${dst}`);\n // Full orthogonal polyline. ELK's own route when available (its right-angle\n // bends AND its box-border anchor points); otherwise an L-corner between the\n // drawn centers (vertical-then-horizontal) so the edge still turns square.\n let raw: Pt[];\n if (route) {\n raw = [route.start, ...route.bends, route.end];\n } else if (sc.x === tc.x || sc.y === tc.y) {\n raw = [sc, tc];\n } else {\n raw = [sc, { x: sc.x, y: tc.y }, tc];\n }\n if (raw.length < 2) return null;\n\n const extS = visualExtent(srcNode);\n const extT = visualExtent(dstNode);\n // Land both ends on the visual shape boundary (with a right-angle jog when\n // ELK's approach would otherwise miss a small shape — see approachEndpoint).\n const startA = approachEndpoint(sc, extS, raw[0]!, raw[1]!);\n const endA = approachEndpoint(tc, extT, raw[raw.length - 1]!, raw[raw.length - 2]!);\n\n // Drawn polyline: source boundary → (jog) → ELK bends → (jog) → target\n // boundary, dropping any zero-length steps so segments stay clean.\n const poly: Pt[] = [];\n const push = (p: Pt): void => {\n const last = poly[poly.length - 1];\n if (!last || last.x !== p.x || last.y !== p.y) poly.push(p);\n };\n push(startA.entry);\n if (startA.jog) push(startA.jog);\n for (const p of raw.slice(1, -1)) push(p);\n if (endA.jog) push(endA.jog);\n push(endA.entry);\n if (poly.length < 2) return null;\n\n // Safety net: force every segment axis-aligned. A few ELK routes come back\n // as a single diagonal 2-point segment; insert a corner where needed,\n // oriented so the segment entering the target keeps its approach axis.\n const ortho: Pt[] = [poly[0]!];\n for (let i = 1; i < poly.length; i++) {\n const a = ortho[ortho.length - 1]!;\n const b = poly[i]!;\n if (Math.abs(b.x - a.x) > 1 && Math.abs(b.y - a.y) > 1) {\n ortho.push(i === poly.length - 1 ? { x: b.x, y: a.y } : { x: a.x, y: b.y });\n }\n ortho.push(b);\n }\n\n const tip = ortho[ortho.length - 1]!;\n const prev = ortho[ortho.length - 2]!;\n // Pull the drawn line back from the tip by one arrowhead length, along the\n // (axis-aligned) approach, so Graphviz draws the arrow in that gap.\n const ax = prev.x - tip.x;\n const ay = prev.y - tip.y;\n const al = Math.hypot(ax, ay) || 1;\n // Clamp the pull-back to the final segment so the arrow base never overshoots\n // past `prev` and doubles the drawn line back on itself. Short final stubs\n // (< ARROW_LEN) are realistic here — a mergeEdges branch stub or the corner an\n // approachEndpoint jog lands next to the last ELK bend can both be tiny.\n const pull = Math.min(ARROW_LEN, al);\n const arrowBase: Pt = { x: tip.x + (ax / al) * pull, y: tip.y + (ay / al) * pull };\n\n const anchors: Pt[] = [...ortho.slice(0, -1), arrowBase];\n let spline = fmtPt(anchors[0]!);\n for (let i = 1; i < anchors.length; i++) {\n const a = anchors[i - 1]!;\n const b = anchors[i]!;\n spline += ` ${fmtPt(lerp(a, b, 1 / 3))} ${fmtPt(lerp(a, b, 2 / 3))} ${fmtPt(b)}`;\n }\n return `e,${fmtPt(tip)} ${spline}`;\n}\n\n/**\n * Render a node's attribute list — preserving the parsed attrs (including\n * libpetri's `width`/`height`, which are the *visual* shape dimensions)\n * and adding the ELK-computed `pos` so Graphviz `nop2` can pin it.\n *\n * The `pos=\"x,y!\"` form (with trailing `!`) is the neato pin-mode marker.\n * Do NOT emit ELK's layout-reserved width/height to Graphviz — those are\n * the bounding boxes needed for ELK packing (which account for xlabel\n * spillover), not the visible shape sizes. libpetri's standard styles use\n * `width=0.35` for places (25pt circles); overriding with ELK's\n * ~3 inch label-padded box made Graphviz render 114pt-radius circles.\n */\nfunction nodeAttrLine(node: ParsedNode, pos: NodePosition): string {\n const attrs: string[] = [];\n // Center the pinned position on the node's bbox (ELK gives the top-left).\n const c = drawnCenter(pos);\n attrs.push(`pos=${quote(`${c.x.toFixed(2)},${c.y.toFixed(2)}!`)}`);\n for (const [k, v] of Object.entries(node.attrs)) {\n if (k === 'pos') continue;\n attrs.push(`${k}=${quote(v)}`);\n }\n return `${node.id} [${attrs.join(', ')}];`;\n}\n\nfunction edgeAttrLine(\n graph: GraphModel,\n layout: LayoutResult,\n src: string,\n dst: string,\n rawAttrs: string,\n): string {\n // Splice libpetri's original attrs verbatim so style/penwidth/label/color/\n // arrowhead match `DotExporter` 1:1, then add ELK's orthogonal route as a\n // `pos=` spline for Graphviz `nop2` to draw. See `edgePosSpline` for why we\n // route ourselves rather than use Graphviz's (wasm-crashing) ortho router.\n const pos = edgePosSpline(graph, layout, src, dst);\n const parts = [rawAttrs.trim(), pos ? `pos=\"${pos}\"` : ''].filter(Boolean);\n const body = parts.join(', ');\n return `${src} -> ${dst}` + (body ? ` [${body}];` : ';');\n}\n\n/**\n * Emit a Graphviz DOT string with node positions AND edge routes pinned.\n * Render the result with `@viz-js/viz` using:\n *\n * ```ts\n * viz.renderSVGElement(dot, {\n * engine: 'nop2',\n * yInvert: true,\n * });\n * ```\n *\n * `engine: 'nop2'` honours the pinned `pos=` on every node AND the `pos=`\n * spline on every edge, drawing both verbatim with no layout or routing. We\n * supply ELK's own orthogonal edge routes (clipped to the visual node\n * boundary for correct arrowheads — see {@link edgePosSpline}). This replaces\n * the former `nop`/`nop1` mode, where Graphviz routed edges itself: that gave\n * curved (not right-angle) edges, and switching it to `splines=ortho` crashed\n * the wasm on large nets (Graphviz's ortho maze allocator overruns the wasm\n * heap). Routing ourselves gives right angles on a net of any size, no crash.\n *\n * `yInvert: true` matches ELK's Y-down convention to Graphviz's Y-up.\n */\nexport function writeBack(graph: GraphModel, layout: LayoutResult): string {\n const lines: string[] = [];\n lines.push('digraph LibpetriPinned {');\n lines.push(' rankdir=TB;');\n lines.push(' overlap=\"false\";');\n lines.push(' compound=\"true\";');\n lines.push(' fontname=\"Helvetica,Arial,sans-serif\";');\n lines.push(' outputorder=\"edgesfirst\";');\n lines.push(' node [fontname=\"Helvetica,Arial,sans-serif\", fontsize=10];');\n lines.push(' edge [fontname=\"Helvetica,Arial,sans-serif\", fontsize=9];');\n\n // Clusters with their pinned bounding boxes\n for (const [clusterId, cluster] of graph.clusters) {\n const box = layout.clusterBoxes.get(clusterId);\n lines.push(` subgraph ${clusterId} {`);\n lines.push(` label=${quote(cluster.shortName)};`);\n lines.push(` style=\"rounded,dashed\";`);\n lines.push(` bgcolor=\"#FAFAFA\";`);\n lines.push(` penwidth=1.5;`);\n if (box) {\n const x1 = box.x.toFixed(2);\n const y1 = box.y.toFixed(2);\n const x2 = (box.x + box.width).toFixed(2);\n const y2 = (box.y + box.height).toFixed(2);\n lines.push(` bb=${quote(`${x1},${y1},${x2},${y2}`)};`);\n }\n for (const nodeId of cluster.nodes) {\n const node = graph.nodes.get(nodeId);\n const pos = layout.nodePositions.get(nodeId);\n if (!node || !pos) continue;\n lines.push(' ' + nodeAttrLine(node, pos));\n }\n lines.push(' }');\n }\n\n // Orphans (root-level nodes) — laid out inside the synthetic\n // orchestrator cluster but emitted at root so they sit alongside the\n // real subnets without their own labelled box.\n for (const orphanId of graph.orphans) {\n const node = graph.nodes.get(orphanId);\n const pos = layout.nodePositions.get(orphanId);\n if (!node || !pos) continue;\n lines.push(' ' + nodeAttrLine(node, pos));\n }\n\n // Edges — libpetri's original styling attrs plus ELK's orthogonal route as\n // a `pos=` spline, drawn verbatim by Graphviz `nop2`.\n for (const edge of graph.edges) {\n lines.push(' ' + edgeAttrLine(graph, layout, edge.src, edge.dst, edge.rawAttrs));\n }\n\n lines.push('}');\n return lines.join('\\n');\n}\n"],"mappings":"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","names":["label","sgn","off","reach"]}
@@ -1,4 +1,4 @@
1
- import { T as Token } from './petri-net-UQBBkvLl.js';
1
+ import { T as Token } from './petri-net-WSScMyDL.js';
2
2
 
3
3
  /**
4
4
  * Events emitted during Petri Net execution.
@@ -1,4 +1,4 @@
1
- import { P as PetriNet } from '../petri-net-UQBBkvLl.js';
1
+ import { P as PetriNet } from '../petri-net-WSScMyDL.js';
2
2
 
3
3
  /**
4
4
  * Format-agnostic typed graph model.
package/dist/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
- import { P as PetriNet, S as SubnetDef, a as Place, T as Token, b as Transition, E as EnvironmentPlace, c as TransitionContext, O as Out } from './petri-net-UQBBkvLl.js';
2
- export { A as Arc, d as ArcInhibitor, e as ArcInput, f as ArcOutput, g as ArcRead, h as ArcReset, C as Channel, i as ComposeBindings, F as FusionSet, j as FusionSetBuilder, I as In, k as InAll, l as InAtLeast, m as InExactly, n as InOne, o as Instance, p as Interface, q as InterfaceBuilder, K as KeyFn, L as LogFn, M as MAX_DURATION_MS, r as MatchKey, s as MatchSpec, N as NameId, t as OutAnd, u as OutForwardInput, v as OutPlace, w as OutTimeout, x as OutXor, y as OutputEntry, z as PetriNetBuilder, B as Port, D as PortDirection, G as SubnetDefBuilder, H as SubnetInstance, J as Timing, Q as TimingDeadline, R as TimingDelayed, U as TimingExact, V as TimingImmediate, W as TimingWindow, X as TokenInput, Y as TokenOutput, Z as TransitionAction, _ as TransitionBuilder, $ as VerificationHarness, a0 as VerificationResult, a1 as all, a2 as allPlaces, a3 as and, a4 as andPlaces, a5 as arcPlace, a6 as atLeast, a7 as consumptionCount, a8 as deadline, a9 as delayed, aa as earliest, ab as enumerateBranches, ac as environmentPlace, ad as exact, ae as exactly, af as fork, ag as forwardInput, ah as hasDeadline, ai as immediate, aj as inhibitorArc, ak as inputArc, al as isPassthrough, am as isUnit, an as keyForPlace, ao as latest, ap as matchCorrelates, aq as matchKey, ar as matchSpec, as as nameId, at as one, au as outPlace, av as outputArc, aw as passthrough, ax as place, ay as produce, az as readArc, aA as requiredCount, aB as resetArc, aC as timeout, aD as timeoutPlace, aE as tokenAt, aF as tokenOf, aG as transform, aH as transformAsync, aI as transformFrom, aJ as unitToken, aK as window, aL as withTimeout, aM as xor, aN as xorPlaces } from './petri-net-UQBBkvLl.js';
3
- import { E as EventStore } from './event-store-Df_sAVQ_.js';
4
- export { A as ActionTimedOut, a as ExecutionCompleted, b as ExecutionStarted, I as InMemoryEventStore, L as LogMessage, M as MarkingSnapshot, N as NetEvent, T as TokenAdded, c as TokenRemoved, d as TransitionClockRestarted, e as TransitionCompleted, f as TransitionEnabled, g as TransitionFailed, h as TransitionStarted, i as TransitionTimedOut, j as eventTransitionName, k as eventsOfType, l as failures, m as filterEvents, n as inMemoryEventStore, o as isFailureEvent, p as noopEventStore, t as transitionEvents } from './event-store-Df_sAVQ_.js';
1
+ import { P as PetriNet, S as SubnetDef, a as Place, T as Token, b as Transition, E as EnvironmentPlace, c as TransitionContext, O as Out } from './petri-net-WSScMyDL.js';
2
+ export { A as Arc, d as ArcInhibitor, e as ArcInput, f as ArcOutput, g as ArcRead, h as ArcReset, C as Channel, i as ComposeBindings, F as FusionSet, j as FusionSetBuilder, I as In, k as InAll, l as InAtLeast, m as InExactly, n as InOne, o as Instance, p as Interface, q as InterfaceBuilder, K as KeyFn, L as LogFn, M as MAX_DURATION_MS, r as MatchKey, s as MatchSpec, N as NameId, t as OutAnd, u as OutForwardInput, v as OutPlace, w as OutTimeout, x as OutXor, y as OutputEntry, z as PetriNetBuilder, B as Port, D as PortDirection, G as SubnetDefBuilder, H as SubnetInstance, J as Timing, Q as TimingDeadline, R as TimingDelayed, U as TimingExact, V as TimingImmediate, W as TimingWindow, X as TokenInput, Y as TokenOutput, Z as TransitionAction, _ as TransitionBuilder, $ as VerificationHarness, a0 as VerificationResult, a1 as all, a2 as allPlaces, a3 as and, a4 as andPlaces, a5 as arcPlace, a6 as atLeast, a7 as consumptionCount, a8 as deadline, a9 as delayed, aa as earliest, ab as enumerateBranches, ac as environmentPlace, ad as exact, ae as exactly, af as fork, ag as forwardInput, ah as hasDeadline, ai as immediate, aj as inhibitorArc, ak as inputArc, al as isPassthrough, am as isUnit, an as keyForPlace, ao as latest, ap as matchCorrelates, aq as matchKey, ar as matchSpec, as as nameId, at as one, au as outPlace, av as outputArc, aw as passthrough, ax as place, ay as produce, az as readArc, aA as requiredCount, aB as resetArc, aC as timeout, aD as timeoutPlace, aE as tokenAt, aF as tokenOf, aG as transform, aH as transformAsync, aI as transformFrom, aJ as unitToken, aK as window, aL as withTimeout, aM as xor, aN as xorPlaces } from './petri-net-WSScMyDL.js';
3
+ import { E as EventStore } from './event-store-BFX_yJ8I.js';
4
+ export { A as ActionTimedOut, a as ExecutionCompleted, b as ExecutionStarted, I as InMemoryEventStore, L as LogMessage, M as MarkingSnapshot, N as NetEvent, T as TokenAdded, c as TokenRemoved, d as TransitionClockRestarted, e as TransitionCompleted, f as TransitionEnabled, g as TransitionFailed, h as TransitionStarted, i as TransitionTimedOut, j as eventTransitionName, k as eventsOfType, l as failures, m as filterEvents, n as inMemoryEventStore, o as isFailureEvent, p as noopEventStore, t as transitionEvents } from './event-store-BFX_yJ8I.js';
5
5
 
6
6
  /**
7
7
  * Discriminated sum-type abstraction over Petri nets, distinguishing **closed**
package/dist/index.js CHANGED
@@ -27,7 +27,7 @@ import {
27
27
  withTimeout,
28
28
  xor,
29
29
  xorPlaces
30
- } from "./chunk-5W6SVYPD.js";
30
+ } from "./chunk-JZIEWVAV.js";
31
31
  import {
32
32
  eventTransitionName,
33
33
  isFailureEvent
@@ -0,0 +1,33 @@
1
+ import panzoom from 'panzoom';
2
+
3
+ /**
4
+ * Pan/zoom wrapper for the canonical libpetri viewer.
5
+ *
6
+ * Delegates to the `panzoom` library (no hand-rolled wheel handlers — we
7
+ * intentionally drop the IIFE/javadoc version's bespoke math in favour of
8
+ * a single battle-tested implementation). Defaults match the old debug-ui
9
+ * and dev-preview values so the canonical viewer feels identical to what
10
+ * users had before.
11
+ *
12
+ * @module viewer/pan-zoom
13
+ */
14
+
15
+ type PanzoomInstance = ReturnType<typeof panzoom>;
16
+ type PanzoomOptions = Parameters<typeof panzoom>[1];
17
+ /**
18
+ * Default panzoom configuration shared across all viewer surfaces.
19
+ *
20
+ * - `maxZoom: 1000` — effectively unlimited; lets users dive into ~150-node
21
+ * diagrams without hitting an artificial ceiling.
22
+ * - `minZoom: 0.02` — allows fitting very large nets in a small viewport.
23
+ * - `smoothScroll: false` — sharper interactive feel.
24
+ * - `zoomDoubleClickSpeed: 1` — single-click double-zoom toggle.
25
+ */
26
+ declare const DEFAULT_PANZOOM_OPTS: {
27
+ readonly smoothScroll: false;
28
+ readonly zoomDoubleClickSpeed: 1;
29
+ readonly maxZoom: 1000;
30
+ readonly minZoom: 0.02;
31
+ };
32
+
33
+ export { DEFAULT_PANZOOM_OPTS as D, type PanzoomInstance as P, type PanzoomOptions as a };
@@ -982,6 +982,25 @@ interface SmtVerificationResult {
982
982
  readonly discoveredInvariants: readonly string[];
983
983
  readonly counterexampleTrace: readonly MarkingState[];
984
984
  readonly counterexampleTransitions: readonly string[];
985
+ /**
986
+ * Outcome of the abstract counterexample replay, as a TRI-STATE. `null` means
987
+ * "the replay did not apply"; the two booleans both mean it ran.
988
+ *
989
+ * - `true` — an abstract firing chain from M₀ to a property-violating state
990
+ * was re-executed TS-side; `counterexampleTrace` is that chain in FIRING
991
+ * (replay) order and the verdict is `violated`.
992
+ * - `false` — the replay ran without confirming the trace. Either it could not
993
+ * settle the question (nothing decoded from the Z3 derivation, M₀ absent
994
+ * from the decoded set, or a node/segment budget hit), in which case the
995
+ * `violated` verdict rests on Spacer's SAT answer alone; or the search
996
+ * completed and found NO chain, in which case the verdict was downgraded to
997
+ * `unknown`. The report distinguishes the two ("UNCONFIRMED" vs "FAILED").
998
+ * - `null` — replay did not apply: non-violated verdict, replay disabled via
999
+ * `counterexampleReplay(false)`, the coloured ν-encoding / Route B (whose
1000
+ * state shapes are outside the flat replayer's scope), or a structural
1001
+ * proof.
1002
+ */
1003
+ readonly counterexampleConfirmed: boolean | null;
985
1004
  readonly elapsedMs: number;
986
1005
  readonly statistics: SmtStatistics;
987
1006
  }
@@ -1777,4 +1796,4 @@ declare class PetriNetBuilder {
1777
1796
  private buildWithFusion;
1778
1797
  }
1779
1798
 
1780
- export { type VerificationHarness as $, type Arc as A, type Port as B, type Channel as C, type PortDirection as D, type EnvironmentPlace as E, FusionSet as F, SubnetDefBuilder as G, type SubnetInstance as H, type In as I, type Timing as J, type KeyFn as K, type LogFn as L, MAX_DURATION_MS as M, type NameId as N, type Out as O, PetriNet as P, type TimingDeadline as Q, type TimingDelayed as R, SubnetDef as S, type Token as T, type TimingExact as U, type TimingImmediate as V, type TimingWindow as W, TokenInput as X, TokenOutput as Y, type TransitionAction as Z, TransitionBuilder as _, type Place as a, type Unknown as a$, type VerificationResult as a0, all as a1, allPlaces as a2, and as a3, andPlaces as a4, arcPlace as a5, atLeast as a6, consumptionCount as a7, deadline as a8, delayed as a9, requiredCount as aA, resetArc as aB, timeout as aC, timeoutPlace as aD, tokenAt as aE, tokenOf as aF, transform as aG, transformAsync as aH, transformFrom as aI, unitToken as aJ, window as aK, withTimeout as aL, xor as aM, xorPlaces as aN, MarkingState as aO, type PInvariant as aP, MarkingStateBuilder as aQ, type SmtProperty as aR, type SmtVerificationResult as aS, type BranchPlaceBound as aT, type DeadlockFree as aU, type JoinedOrDeadLettered as aV, type MutualExclusion as aW, type PlaceBound as aX, type Proven as aY, type SmtStatistics as aZ, type TokenSupplier as a_, earliest as aa, enumerateBranches as ab, environmentPlace as ac, exact as ad, exactly as ae, fork as af, forwardInput as ag, hasDeadline as ah, immediate as ai, inhibitorArc as aj, inputArc as ak, isPassthrough as al, isUnit as am, keyForPlace as an, latest as ao, matchCorrelates as ap, matchKey as aq, matchSpec as ar, nameId as as, one as at, outPlace as au, outputArc as av, passthrough as aw, place as ax, produce as ay, readArc as az, Transition as b, type Unreachable as b0, type Verdict as b1, type Violated as b2, branchPlaceBound as b3, deadlockFree as b4, isProven as b5, isViolated as b6, joinedOrDeadLettered as b7, mutualExclusion as b8, pInvariant as b9, pInvariantToString as ba, placeBound as bb, propertyDescription as bc, unreachable as bd, TransitionContext as c, type ArcInhibitor as d, type ArcInput as e, type ArcOutput as f, type ArcRead as g, type ArcReset as h, ComposeBindings as i, FusionSetBuilder as j, type InAll as k, type InAtLeast as l, type InExactly as m, type InOne as n, Instance as o, Interface as p, InterfaceBuilder as q, type MatchKey as r, type MatchSpec as s, type OutAnd as t, type OutForwardInput as u, type OutPlace as v, type OutTimeout as w, type OutXor as x, type OutputEntry as y, PetriNetBuilder as z };
1799
+ export { type VerificationHarness as $, type Arc as A, type Port as B, type Channel as C, type PortDirection as D, type EnvironmentPlace as E, FusionSet as F, SubnetDefBuilder as G, type SubnetInstance as H, type In as I, type Timing as J, type KeyFn as K, type LogFn as L, MAX_DURATION_MS as M, type NameId as N, type Out as O, PetriNet as P, type TimingDeadline as Q, type TimingDelayed as R, SubnetDef as S, type Token as T, type TimingExact as U, type TimingImmediate as V, type TimingWindow as W, TokenInput as X, TokenOutput as Y, type TransitionAction as Z, TransitionBuilder as _, type Place as a, type Unknown as a$, type VerificationResult as a0, all as a1, allPlaces as a2, and as a3, andPlaces as a4, arcPlace as a5, atLeast as a6, consumptionCount as a7, deadline as a8, delayed as a9, requiredCount as aA, resetArc as aB, timeout as aC, timeoutPlace as aD, tokenAt as aE, tokenOf as aF, transform as aG, transformAsync as aH, transformFrom as aI, unitToken as aJ, window as aK, withTimeout as aL, xor as aM, xorPlaces as aN, MarkingState as aO, type PInvariant as aP, type SmtProperty as aQ, MarkingStateBuilder as aR, type SmtVerificationResult as aS, type BranchPlaceBound as aT, type DeadlockFree as aU, type JoinedOrDeadLettered as aV, type MutualExclusion as aW, type PlaceBound as aX, type Proven as aY, type SmtStatistics as aZ, type TokenSupplier as a_, earliest as aa, enumerateBranches as ab, environmentPlace as ac, exact as ad, exactly as ae, fork as af, forwardInput as ag, hasDeadline as ah, immediate as ai, inhibitorArc as aj, inputArc as ak, isPassthrough as al, isUnit as am, keyForPlace as an, latest as ao, matchCorrelates as ap, matchKey as aq, matchSpec as ar, nameId as as, one as at, outPlace as au, outputArc as av, passthrough as aw, place as ax, produce as ay, readArc as az, Transition as b, type Unreachable as b0, type Verdict as b1, type Violated as b2, branchPlaceBound as b3, deadlockFree as b4, isProven as b5, isViolated as b6, joinedOrDeadLettered as b7, mutualExclusion as b8, pInvariant as b9, pInvariantToString as ba, placeBound as bb, propertyDescription as bc, unreachable as bd, TransitionContext as c, type ArcInhibitor as d, type ArcInput as e, type ArcOutput as f, type ArcRead as g, type ArcReset as h, ComposeBindings as i, FusionSetBuilder as j, type InAll as k, type InAtLeast as l, type InExactly as m, type InOne as n, Instance as o, Interface as p, InterfaceBuilder as q, type MatchKey as r, type MatchSpec as s, type OutAnd as t, type OutForwardInput as u, type OutPlace as v, type OutTimeout as w, type OutXor as x, type OutputEntry as y, PetriNetBuilder as z };