@litfamily/lithermes 1.0.28 → 1.0.30

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 (167) hide show
  1. package/README.md +12 -9
  2. package/README_Ko-KR.md +12 -9
  3. package/assets/lithermes-plugin/README.md +15 -2
  4. package/assets/lithermes-plugin/__init__.py +49 -10
  5. package/assets/lithermes-plugin/adhd_stack.py +228 -0
  6. package/assets/lithermes-plugin/core.py +30 -4
  7. package/assets/lithermes-plugin/core_commands.py +99 -0
  8. package/assets/lithermes-plugin/core_contexts.py +54 -4
  9. package/assets/lithermes-plugin/core_contract.py +2 -2
  10. package/assets/lithermes-plugin/core_routing.py +126 -0
  11. package/assets/lithermes-plugin/diagnostics.py +3 -2
  12. package/assets/lithermes-plugin/lit_mark.py +75 -0
  13. package/assets/lithermes-plugin/payload-version.json +472 -60
  14. package/assets/lithermes-plugin/plugin.yaml +1 -1
  15. package/assets/lithermes-plugin/scientific_visualization.py +23 -0
  16. package/assets/lithermes-plugin/skills/frontend-ui-ux/SKILL.md +7 -7
  17. package/assets/lithermes-plugin/skills/frontend-ui-ux/kit/components.css +414 -0
  18. package/assets/lithermes-plugin/skills/frontend-ui-ux/kit/fonts.json +42 -0
  19. package/assets/lithermes-plugin/skills/frontend-ui-ux/kit/layouts/landing.html +164 -0
  20. package/assets/lithermes-plugin/skills/frontend-ui-ux/kit/layouts/settings.html +114 -0
  21. package/assets/lithermes-plugin/skills/frontend-ui-ux/kit/layouts/workspace.html +115 -0
  22. package/assets/lithermes-plugin/skills/frontend-ui-ux/kit/styles/computational-brutalism.json +227 -0
  23. package/assets/lithermes-plugin/skills/frontend-ui-ux/kit/styles/field-notes.json +205 -0
  24. package/assets/lithermes-plugin/skills/frontend-ui-ux/kit/styles/graphite.json +200 -0
  25. package/assets/lithermes-plugin/skills/frontend-ui-ux/kit/styles/meadow.json +199 -0
  26. package/assets/lithermes-plugin/skills/frontend-ui-ux/kit/styles/neo-brutalism.json +222 -0
  27. package/assets/lithermes-plugin/skills/frontend-ui-ux/kit/ui.js +226 -0
  28. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/README.md +2 -4
  29. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/complete-contract.md +4 -4
  30. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/craft-floor.md +10 -0
  31. package/assets/lithermes-plugin/skills/frontend-ui-ux/scripts/design-lock.mjs +400 -0
  32. package/assets/lithermes-plugin/skills/frontend-ui-ux/scripts/probe-rules.json +6 -1
  33. package/assets/lithermes-plugin/skills/frontend-ui-ux/scripts/probe.mjs +76 -4
  34. package/assets/lithermes-plugin/skills/lit-adhd/SKILL.md +194 -0
  35. package/assets/lithermes-plugin/skills/lit-adhd/references/brief-contract.md +55 -0
  36. package/assets/lithermes-plugin/skills/lit-adhd/references/card-examples.md +86 -0
  37. package/assets/lithermes-plugin/skills/lit-adhd/references/status-taxonomy.md +40 -0
  38. package/assets/lithermes-plugin/skills/lit-adhd/scripts/adhd.mjs +11 -0
  39. package/assets/lithermes-plugin/skills/lit-adhd/scripts/lib/collect.mjs +554 -0
  40. package/assets/lithermes-plugin/skills/lit-adhd/scripts/lib/gauge.mjs +89 -0
  41. package/assets/lithermes-plugin/skills/lit-adhd/scripts/lib/hermes-sessions.mjs +219 -0
  42. package/assets/lithermes-plugin/skills/lit-adhd/scripts/lib/next-step.mjs +101 -0
  43. package/assets/lithermes-plugin/skills/lit-adhd/scripts/lib/plan-task-rows.mjs +32 -0
  44. package/assets/lithermes-plugin/skills/lit-adhd/scripts/lib/redact.mjs +42 -0
  45. package/assets/lithermes-plugin/skills/lit-adhd/scripts/lib/secret-shapes.mjs +59 -0
  46. package/assets/lithermes-plugin/skills/lit-diagram-drawer/SKILL.md +7 -4
  47. package/assets/lithermes-plugin/skills/lit-diagram-drawer/references/style-guide.md +20 -11
  48. package/assets/lithermes-plugin/skills/lit-diagram-drawer/references/type-architecture.md +3 -3
  49. package/assets/lithermes-plugin/skills/lit-diagram-drawer/references/type-dependency.md +1 -1
  50. package/assets/lithermes-plugin/skills/lit-diagram-drawer/references/type-tree.md +3 -3
  51. package/assets/lithermes-plugin/skills/lit-diagram-drawer/references/type-wardley.md +1 -1
  52. package/assets/lithermes-plugin/skills/lit-diagram-drawer/references/verifier-guide.md +5 -0
  53. package/assets/lithermes-plugin/skills/lit-diagram-drawer/scripts/diagram_quality.py +4 -4
  54. package/assets/lithermes-plugin/skills/lit-docx/SKILL.md +1 -1
  55. package/assets/lithermes-plugin/skills/lit-hijack/SKILL.md +239 -0
  56. package/assets/lithermes-plugin/skills/lit-hijack/references/capture-and-provenance.md +123 -0
  57. package/assets/lithermes-plugin/skills/lit-hijack/references/clean-room-build.md +128 -0
  58. package/assets/lithermes-plugin/skills/lit-hijack/references/distil-and-licence.md +128 -0
  59. package/assets/lithermes-plugin/skills/lit-hijack/references/gap-and-port-design.md +152 -0
  60. package/assets/lithermes-plugin/skills/lit-hijack/references/parity-and-report.md +131 -0
  61. package/assets/lithermes-plugin/skills/lit-hijack/scripts/hijack.mjs +995 -0
  62. package/assets/lithermes-plugin/skills/lit-plan/SKILL.md +1 -0
  63. package/assets/lithermes-plugin/skills/lit-pptx/SKILL.md +11 -6
  64. package/assets/lithermes-plugin/skills/lit-pptx/fonts/jetbrains-mono/JetBrainsMono-Bold.ttf +0 -0
  65. package/assets/lithermes-plugin/skills/lit-pptx/fonts/jetbrains-mono/JetBrainsMono-Regular.ttf +0 -0
  66. package/assets/lithermes-plugin/skills/lit-pptx/fonts/jetbrains-mono/OFL.txt +93 -0
  67. package/assets/lithermes-plugin/skills/lit-pptx/fonts/provenance.json +1 -0
  68. package/assets/lithermes-plugin/skills/lit-pptx/references/anti-slop-checklist.md +3 -0
  69. package/assets/lithermes-plugin/skills/lit-pptx/references/authoring-guide.md +1 -1
  70. package/assets/lithermes-plugin/skills/lit-pptx/references/consensus-protocol.md +1 -1
  71. package/assets/lithermes-plugin/skills/lit-pptx/references/direction-step.md +8 -2
  72. package/assets/lithermes-plugin/skills/lit-pptx/references/examples/13-engineering-review-computational-brutalism-ko.md +175 -0
  73. package/assets/lithermes-plugin/skills/lit-pptx/references/examples/14-launch-pitch-neo-brutalism-ko.md +156 -0
  74. package/assets/lithermes-plugin/skills/lit-pptx/references/examples/15-design-lecture-bauhaus-ko.md +141 -0
  75. package/assets/lithermes-plugin/skills/lit-pptx/references/examples/16-community-programme-riso-ko.md +160 -0
  76. package/assets/lithermes-plugin/skills/lit-pptx/references/examples/17-public-data-briefing-newsroom-ko.md +183 -0
  77. package/assets/lithermes-plugin/skills/lit-pptx/references/layout-families.md +1 -1
  78. package/assets/lithermes-plugin/skills/lit-pptx/references/tonalities/bauhaus.md +105 -0
  79. package/assets/lithermes-plugin/skills/lit-pptx/references/tonalities/computational-brutalism.md +111 -0
  80. package/assets/lithermes-plugin/skills/lit-pptx/references/tonalities/neo-brutalism.md +114 -0
  81. package/assets/lithermes-plugin/skills/lit-pptx/references/tonalities/newsroom.md +106 -0
  82. package/assets/lithermes-plugin/skills/lit-pptx/references/tonalities/riso.md +101 -0
  83. package/assets/lithermes-plugin/skills/lit-pptx/scripts/craft_extras.py +49 -0
  84. package/assets/lithermes-plugin/skills/lit-pptx/scripts/deck_output.py +4 -2
  85. package/assets/lithermes-plugin/skills/lit-pptx/scripts/lib/render-pack.js +96 -10
  86. package/assets/lithermes-plugin/skills/lit-pptx/scripts/lib/template-registry.js +50 -6
  87. package/assets/lithermes-plugin/skills/lit-pptx/templates/tonalities/bauhaus/pack.yaml +103 -0
  88. package/assets/lithermes-plugin/skills/lit-pptx/templates/tonalities/computational-brutalism/pack.yaml +99 -0
  89. package/assets/lithermes-plugin/skills/lit-pptx/templates/tonalities/neo-brutalism/pack.yaml +107 -0
  90. package/assets/lithermes-plugin/skills/lit-pptx/templates/tonalities/newsroom/pack.yaml +99 -0
  91. package/assets/lithermes-plugin/skills/lit-pptx/templates/tonalities/riso/pack.yaml +99 -0
  92. package/assets/lithermes-plugin/skills/lit-recap/SKILL.md +1 -1
  93. package/assets/lithermes-plugin/skills/lit-schematic/SKILL.md +144 -0
  94. package/assets/lithermes-plugin/skills/lit-schematic/examples/iso-heat-exchanger/build.mjs +38 -0
  95. package/assets/lithermes-plugin/skills/lit-schematic/examples/iso-heat-exchanger/scene.mjs +133 -0
  96. package/assets/lithermes-plugin/skills/lit-schematic/examples/journal-adsorption-rig/build.mjs +87 -0
  97. package/assets/lithermes-plugin/skills/lit-schematic/examples/pid-dosing-skid/build.mjs +85 -0
  98. package/assets/lithermes-plugin/skills/lit-schematic/references/cutaway-outputs.md +70 -0
  99. package/assets/lithermes-plugin/skills/lit-schematic/references/isometric-craft.md +101 -0
  100. package/assets/lithermes-plugin/skills/lit-schematic/references/journal-figures.md +72 -0
  101. package/assets/lithermes-plugin/skills/lit-schematic/references/kit-api.md +134 -0
  102. package/assets/lithermes-plugin/skills/lit-schematic/references/pfd-and-layout.md +53 -0
  103. package/assets/lithermes-plugin/skills/lit-schematic/references/pid-drafting.md +103 -0
  104. package/assets/lithermes-plugin/skills/lit-schematic/references/pid-symbols.md +163 -0
  105. package/assets/lithermes-plugin/skills/lit-schematic/references/verification.md +100 -0
  106. package/assets/lithermes-plugin/skills/lit-schematic/scripts/doctor.mjs +57 -0
  107. package/assets/lithermes-plugin/skills/lit-schematic/scripts/lib/figure.mjs +81 -0
  108. package/assets/lithermes-plugin/skills/lit-schematic/scripts/lib/fonts.mjs +27 -0
  109. package/assets/lithermes-plugin/skills/lit-schematic/scripts/lib/geom.mjs +113 -0
  110. package/assets/lithermes-plugin/skills/lit-schematic/scripts/lib/interactive.mjs +292 -0
  111. package/assets/lithermes-plugin/skills/lit-schematic/scripts/lib/isa.mjs +115 -0
  112. package/assets/lithermes-plugin/skills/lit-schematic/scripts/lib/iso.mjs +719 -0
  113. package/assets/lithermes-plugin/skills/lit-schematic/scripts/lib/legend-sheet.mjs +162 -0
  114. package/assets/lithermes-plugin/skills/lit-schematic/scripts/lib/pid.mjs +937 -0
  115. package/assets/lithermes-plugin/skills/lit-schematic/scripts/lib/png.mjs +132 -0
  116. package/assets/lithermes-plugin/skills/lit-schematic/scripts/lib/qa-core.mjs +658 -0
  117. package/assets/lithermes-plugin/skills/lit-schematic/scripts/lib/route.mjs +218 -0
  118. package/assets/lithermes-plugin/skills/lit-schematic/scripts/lib/sheet.mjs +890 -0
  119. package/assets/lithermes-plugin/skills/lit-schematic/scripts/lib/svg.mjs +82 -0
  120. package/assets/lithermes-plugin/skills/lit-schematic/scripts/lib/theme.mjs +115 -0
  121. package/assets/lithermes-plugin/skills/lit-schematic/scripts/motion.mjs +129 -0
  122. package/assets/lithermes-plugin/skills/lit-schematic/scripts/qa.mjs +92 -0
  123. package/assets/lithermes-plugin/skills/lit-schematic/scripts/render.mjs +233 -0
  124. package/assets/lithermes-plugin/skills/lit-schematic/scripts/scaffold.mjs +60 -0
  125. package/assets/lithermes-plugin/skills/lit-scientific-visualization/SKILL.md +19 -1
  126. package/assets/lithermes-plugin/skills/lit-typographic-motion/SKILL.md +12 -11
  127. package/assets/lithermes-plugin/skills/lit-typographic-motion/bin/motion.mjs +56 -2
  128. package/assets/lithermes-plugin/skills/lit-typographic-motion/bin/runtime.mjs +129 -4
  129. package/assets/lithermes-plugin/skills/lit-typographic-motion/engine/THIRD_PARTY_NOTICES +9 -0
  130. package/assets/lithermes-plugin/skills/lit-typographic-motion/engine/blender/runner.py +218 -0
  131. package/assets/lithermes-plugin/skills/lit-typographic-motion/engine/blender.mjs +154 -0
  132. package/assets/lithermes-plugin/skills/lit-typographic-motion/engine/board.mjs +132 -0
  133. package/assets/lithermes-plugin/skills/lit-typographic-motion/engine/completion.mjs +48 -5
  134. package/assets/lithermes-plugin/skills/lit-typographic-motion/engine/gate.mjs +13 -0
  135. package/assets/lithermes-plugin/skills/lit-typographic-motion/engine/house.mjs +44 -0
  136. package/assets/lithermes-plugin/skills/lit-typographic-motion/engine/scaffold.mjs +238 -0
  137. package/assets/lithermes-plugin/skills/lit-typographic-motion/engine/score.mjs +643 -0
  138. package/assets/lithermes-plugin/skills/lit-typographic-motion/engine/sound-dsp.mjs +21 -0
  139. package/assets/lithermes-plugin/skills/lit-typographic-motion/engine/sound.mjs +171 -0
  140. package/assets/lithermes-plugin/skills/lit-typographic-motion/engine/stage-qa.mjs +23 -3
  141. package/assets/lithermes-plugin/skills/lit-typographic-motion/engine/stage.mjs +124 -17
  142. package/assets/lithermes-plugin/skills/lit-typographic-motion/engine/styles.mjs +240 -0
  143. package/assets/lithermes-plugin/skills/lit-typographic-motion/engine/treatment.mjs +152 -10
  144. package/assets/lithermes-plugin/skills/lit-typographic-motion/engine/vendor/three/LICENSE +21 -0
  145. package/assets/lithermes-plugin/skills/lit-typographic-motion/engine/vendor/three/addons/environments/RoomEnvironment.js +144 -0
  146. package/assets/lithermes-plugin/skills/lit-typographic-motion/engine/vendor/three/addons/geometries/RoundedBoxGeometry.js +155 -0
  147. package/assets/lithermes-plugin/skills/lit-typographic-motion/engine/vendor/three/three.module.js +54571 -0
  148. package/assets/lithermes-plugin/skills/lit-typographic-motion/recipes/hero-3d/expected.json +67 -0
  149. package/assets/lithermes-plugin/skills/lit-typographic-motion/recipes/hero-3d/index.html +139 -0
  150. package/assets/lithermes-plugin/skills/lit-typographic-motion/references/depth.md +74 -0
  151. package/assets/lithermes-plugin/skills/lit-typographic-motion/references/studio.md +87 -0
  152. package/assets/lithermes-plugin/skills/lit-typographic-motion/references/style-bibles.md +9 -2
  153. package/assets/lithermes-plugin/skills/lit-typographic-motion/references/treatment.md +24 -8
  154. package/assets/lithermes-plugin/skills/lit-typographic-motion/styles/paper.json +41 -0
  155. package/assets/lithermes-plugin/skills/lit-typographic-motion/styles/swiss-signal.json +41 -0
  156. package/assets/lithermes-plugin/skills/lit-typographic-motion/styles/terminalcore.json +42 -0
  157. package/assets/lithermes-plugin/skills/lit-typographic-motion/styles/tidal.json +41 -0
  158. package/assets/lithermes-plugin/skills/litresearch/SKILL.md +4 -0
  159. package/assets/lithermes-plugin/skills/readme-studio/SKILL.md +1 -1
  160. package/assets/lithermes-plugin/skills/readme-studio/templates/hyperframes/package-lock.json +3 -3
  161. package/assets/lithermes-plugin/skills/readme-studio/templates/remotion/package-lock.json +3 -3
  162. package/assets/lithermes-plugin/skills/review-work/SKILL.md +2 -0
  163. package/assets/lithermes-plugin/skills/start-work/SKILL.md +2 -1
  164. package/assets/lithermes-plugin/skills/visual-qa/references/complete-contract.md +3 -3
  165. package/package.json +1 -1
  166. package/readme-assets/badge-version.svg +1 -1
  167. package/src/lib/skillPayload.js +1 -1
@@ -0,0 +1,218 @@
1
+ // Orthogonal line routing for P&IDs, PFDs and 2D apparatus panels.
2
+ //
3
+ // Drafting rules it encodes: a line leaves its port straight out along the port's normal
4
+ // before it turns; it never enters a symbol other than at its own ports; it turns as little
5
+ // as possible; it may cross another line only at a right angle on a straight run, never turn
6
+ // on top of another line and never run along one. A failed search throws instead of drawing
7
+ // through a symbol, so the caller moves a symbol or gives waypoints.
8
+
9
+ import { EPS, dist, pointIn, simplify } from "./geom.mjs";
10
+
11
+ const DIRS = [[1, 0], [0, 1], [-1, 0], [0, -1]];
12
+ const dirIndex = ([dx, dy]) => DIRS.findIndex(([x, y]) => Math.abs(x - dx) < EPS && Math.abs(y - dy) < EPS);
13
+
14
+ class Heap {
15
+ constructor() { this.items = []; }
16
+ get size() { return this.items.length; }
17
+ push(item) {
18
+ const items = this.items;
19
+ items.push(item);
20
+ let i = items.length - 1;
21
+ while (i > 0) {
22
+ const parent = (i - 1) >> 1;
23
+ if (less(items[parent], items[i])) break;
24
+ [items[parent], items[i]] = [items[i], items[parent]];
25
+ i = parent;
26
+ }
27
+ }
28
+ pop() {
29
+ const items = this.items;
30
+ const top = items[0];
31
+ const last = items.pop();
32
+ if (items.length) {
33
+ items[0] = last;
34
+ let i = 0;
35
+ for (;;) {
36
+ const l = 2 * i + 1;
37
+ const r = l + 1;
38
+ let m = i;
39
+ if (l < items.length && less(items[l], items[m])) m = l;
40
+ if (r < items.length && less(items[r], items[m])) m = r;
41
+ if (m === i) break;
42
+ [items[m], items[i]] = [items[i], items[m]];
43
+ i = m;
44
+ }
45
+ }
46
+ return top;
47
+ }
48
+ }
49
+ const less = (a, b) => (a.f !== b.f ? a.f < b.f : a.seq < b.seq);
50
+
51
+ function axisValues(lo, hi, step, extra) {
52
+ const values = new Set();
53
+ const first = Math.ceil(lo / step) * step;
54
+ for (let v = first; v <= hi + EPS; v += step) values.add(Math.round(v * 1000) / 1000);
55
+ for (const v of extra) if (v >= lo - EPS && v <= hi + EPS) values.add(Math.round(v * 1000) / 1000);
56
+ return [...values].sort((a, b) => a - b);
57
+ }
58
+
59
+ // Existing lines as axis-aligned runs, so a candidate move can be classified as a clean
60
+ // crossing, an overlap or a turn on a line.
61
+ function occupancy(lines) {
62
+ const horizontal = [];
63
+ const vertical = [];
64
+ for (const points of lines) {
65
+ for (let i = 1; i < points.length; i++) {
66
+ const [a, b] = [points[i - 1], points[i]];
67
+ if (Math.abs(a[1] - b[1]) < EPS) horizontal.push({ y: a[1], x0: Math.min(a[0], b[0]), x1: Math.max(a[0], b[0]) });
68
+ else if (Math.abs(a[0] - b[0]) < EPS) vertical.push({ x: a[0], y0: Math.min(a[1], b[1]), y1: Math.max(a[1], b[1]) });
69
+ }
70
+ }
71
+ return {
72
+ onHorizontal: ([x, y], margin = 0) => horizontal.some((s) => Math.abs(s.y - y) < margin + EPS && x >= s.x0 - EPS && x <= s.x1 + EPS),
73
+ onVertical: ([x, y], margin = 0) => vertical.some((s) => Math.abs(s.x - x) < margin + EPS && y >= s.y0 - EPS && y <= s.y1 + EPS),
74
+ };
75
+ }
76
+
77
+ export function routeOrthogonal({
78
+ from,
79
+ to,
80
+ obstacles = [],
81
+ lines = [],
82
+ bounds,
83
+ grid = 2.5,
84
+ clearance = 2,
85
+ stub = 5,
86
+ bendCost = 9,
87
+ crossCost = 4,
88
+ nearCost = 3,
89
+ exempt = [],
90
+ }) {
91
+ if (!bounds) throw new Error("routeOrthogonal: bounds are required");
92
+ const startDir = dirIndex(from.dir ?? [1, 0]);
93
+ const endDir = dirIndex(to.dir ?? [-1, 0]);
94
+ if (startDir < 0 || endDir < 0) throw new Error("routeOrthogonal: port directions must be axis-aligned unit vectors");
95
+ const inflated = obstacles.map((box) => [box[0] - clearance, box[1] - clearance, box[2] + clearance, box[3] + clearance]);
96
+ const blockedPoint = (p) => inflated.some((box) => pointIn(p, box, EPS));
97
+ // The stub runs out along the port normal until it clears every symbol's clearance zone,
98
+ // its own included: a nozzle on a curved head sits inside its vessel's bounding box.
99
+ const stubEnd = (port, d, label) => {
100
+ for (let k = stub; k <= stub + 80; k += 0.5) {
101
+ const p = [port.x + DIRS[d][0] * k, port.y + DIRS[d][1] * k];
102
+ if (!blockedPoint(p)) return p;
103
+ }
104
+ throw new Error(`routeOrthogonal: the ${label} port at (${port.x}, ${port.y}) faces into another symbol; turn the port or move the symbol`);
105
+ };
106
+ // Two ports that face each other on one axis with nothing between them join straight.
107
+ if ((startDir + 2) % 4 === endDir) {
108
+ const along = [to.x - from.x, to.y - from.y];
109
+ const straight = DIRS[startDir][0] === 0 ? Math.abs(along[0]) < EPS : Math.abs(along[1]) < EPS;
110
+ const ahead = along[0] * DIRS[startDir][0] + along[1] * DIRS[startDir][1] > 0;
111
+ if (straight && ahead) {
112
+ let clear = true;
113
+ const length = Math.hypot(along[0], along[1]);
114
+ for (let k = 0.5; k < length - 0.5 && clear; k += 0.5) {
115
+ const p = [from.x + DIRS[startDir][0] * k, from.y + DIRS[startDir][1] * k];
116
+ if (obstacles.some((box) => !exempt.includes(box) && pointIn(p, box, EPS))) clear = false;
117
+ }
118
+ if (clear) return [[from.x, from.y], [to.x, to.y]];
119
+ }
120
+ }
121
+ const s = stubEnd(from, startDir, "start");
122
+ const t = stubEnd(to, endDir, "end");
123
+ const xs = axisValues(bounds[0], bounds[2], grid, [s[0], t[0], from.x, to.x]);
124
+ const ys = axisValues(bounds[1], bounds[3], grid, [s[1], t[1], from.y, to.y]);
125
+ const xi = new Map(xs.map((v, i) => [v, i]));
126
+ const yi = new Map(ys.map((v, i) => [v, i]));
127
+ const key = (ix, iy) => iy * xs.length + ix;
128
+ const occ = occupancy(lines);
129
+ const sx = xi.get(Math.round(s[0] * 1000) / 1000);
130
+ const sy = yi.get(Math.round(s[1] * 1000) / 1000);
131
+ const tx = xi.get(Math.round(t[0] * 1000) / 1000);
132
+ const ty = yi.get(Math.round(t[1] * 1000) / 1000);
133
+ const arrive = (endDir + 2) % 4;
134
+ const heap = new Heap();
135
+ const best = new Map();
136
+ const came = new Map();
137
+ let seq = 0;
138
+ const h = (ix, iy) => Math.abs(xs[ix] - t[0]) + Math.abs(ys[iy] - t[1]);
139
+ heap.push({ ix: sx, iy: sy, d: startDir, g: 0, f: h(sx, sy), seq: seq++ });
140
+ best.set(`${key(sx, sy)}:${startDir}`, 0);
141
+ let goal = null;
142
+ while (heap.size) {
143
+ const cur = heap.pop();
144
+ const stateKey = `${key(cur.ix, cur.iy)}:${cur.d}`;
145
+ if (cur.g > (best.get(stateKey) ?? Infinity) + EPS) continue;
146
+ if (cur.ix === tx && cur.iy === ty) {
147
+ const finalCost = cur.g + (cur.d === arrive ? 0 : bendCost);
148
+ if (!goal || finalCost < goal.cost) goal = { state: stateKey, cost: finalCost };
149
+ if (cur.d === arrive) break;
150
+ continue;
151
+ }
152
+ const here = [xs[cur.ix], ys[cur.iy]];
153
+ for (let d = 0; d < 4; d++) {
154
+ if (d === (cur.d + 2) % 4) continue;
155
+ const nx = cur.ix + DIRS[d][0];
156
+ const ny = cur.iy + DIRS[d][1];
157
+ if (nx < 0 || ny < 0 || nx >= xs.length || ny >= ys.length) continue;
158
+ const next = [xs[nx], ys[ny]];
159
+ const mid = [(here[0] + next[0]) / 2, (here[1] + next[1]) / 2];
160
+ if (blockedPoint(next) || blockedPoint(mid)) continue;
161
+ const horizontalMove = d % 2 === 0;
162
+ // Running along an existing line, or turning on top of one, is never allowed.
163
+ if (horizontalMove ? occ.onHorizontal(mid) : occ.onVertical(mid)) continue;
164
+ const turning = d !== cur.d;
165
+ const isEndpoint = (cur.ix === sx && cur.iy === sy);
166
+ if (turning && !isEndpoint && (occ.onHorizontal(here) || occ.onVertical(here))) continue;
167
+ let step = dist(here, next);
168
+ if (horizontalMove ? occ.onVertical(next) : occ.onHorizontal(next)) {
169
+ const atGoal = nx === tx && ny === ty;
170
+ if (atGoal) continue;
171
+ step += crossCost;
172
+ }
173
+ if (horizontalMove ? occ.onHorizontal(mid, grid * 0.99) : occ.onVertical(mid, grid * 0.99)) step += nearCost;
174
+ const g = cur.g + step + (turning ? bendCost : 0);
175
+ const nextKey = `${key(nx, ny)}:${d}`;
176
+ if (g + EPS < (best.get(nextKey) ?? Infinity)) {
177
+ best.set(nextKey, g);
178
+ came.set(nextKey, stateKey);
179
+ heap.push({ ix: nx, iy: ny, d, g, f: g + h(nx, ny), seq: seq++ });
180
+ }
181
+ }
182
+ }
183
+ if (!goal) {
184
+ throw new Error(`routeOrthogonal: no clear route from (${from.x}, ${from.y}) to (${to.x}, ${to.y}); move a symbol, widen the gap, or pass waypoints`);
185
+ }
186
+ const cells = [];
187
+ for (let k = goal.state; k; k = came.get(k)) {
188
+ const index = Number(k.split(":")[0]);
189
+ cells.push([xs[index % xs.length], ys[Math.floor(index / xs.length)]]);
190
+ if (!came.has(k)) break;
191
+ }
192
+ cells.reverse();
193
+ return simplify([[from.x, from.y], ...cells, [to.x, to.y]]);
194
+ }
195
+
196
+ // Checks a hand-given polyline: every leg axis-aligned and the first and last legs on the
197
+ // port normals. Returns the simplified corners.
198
+ export function orthogonal(points, { from, to } = {}) {
199
+ const out = simplify(points);
200
+ for (let i = 1; i < out.length; i++) {
201
+ const [a, b] = [out[i - 1], out[i]];
202
+ if (Math.abs(a[0] - b[0]) > EPS && Math.abs(a[1] - b[1]) > EPS) {
203
+ throw new Error(`orthogonal: leg ${i} from (${a}) to (${b}) is diagonal; P&ID lines run horizontally or vertically`);
204
+ }
205
+ }
206
+ const check = (port, a, b, label) => {
207
+ if (!port?.dir) return;
208
+ const d = [Math.sign(b[0] - a[0]), Math.sign(b[1] - a[1])];
209
+ if (d[0] !== Math.sign(port.dir[0]) || d[1] !== Math.sign(port.dir[1])) {
210
+ throw new Error(`orthogonal: the ${label} leg must leave its port along the port's normal`);
211
+ }
212
+ };
213
+ if (out.length >= 2) {
214
+ check(from, out[0], out[1], "first");
215
+ check(to, out.at(-1), out.at(-2), "last");
216
+ }
217
+ return out;
218
+ }