@horizon36596/zenith-core 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.
Files changed (244) hide show
  1. package/LICENSE +21 -0
  2. package/NOTICE +18 -0
  3. package/README.md +16 -0
  4. package/dist/canonicalize.d.ts +25 -0
  5. package/dist/canonicalize.d.ts.map +1 -0
  6. package/dist/canonicalize.js +199 -0
  7. package/dist/canonicalize.js.map +1 -0
  8. package/dist/check.d.ts +26 -0
  9. package/dist/check.d.ts.map +1 -0
  10. package/dist/check.js +231 -0
  11. package/dist/check.js.map +1 -0
  12. package/dist/checks/heading.d.ts +17 -0
  13. package/dist/checks/heading.d.ts.map +1 -0
  14. package/dist/checks/heading.js +78 -0
  15. package/dist/checks/heading.js.map +1 -0
  16. package/dist/checks/intake.d.ts +28 -0
  17. package/dist/checks/intake.d.ts.map +1 -0
  18. package/dist/checks/intake.js +131 -0
  19. package/dist/checks/intake.js.map +1 -0
  20. package/dist/checks/motion.d.ts +28 -0
  21. package/dist/checks/motion.d.ts.map +1 -0
  22. package/dist/checks/motion.js +252 -0
  23. package/dist/checks/motion.js.map +1 -0
  24. package/dist/checks/schedule.d.ts +29 -0
  25. package/dist/checks/schedule.d.ts.map +1 -0
  26. package/dist/checks/schedule.js +166 -0
  27. package/dist/checks/schedule.js.map +1 -0
  28. package/dist/checks/season.d.ts +6 -0
  29. package/dist/checks/season.d.ts.map +1 -0
  30. package/dist/checks/season.js +126 -0
  31. package/dist/checks/season.js.map +1 -0
  32. package/dist/continuity.d.ts +38 -0
  33. package/dist/continuity.d.ts.map +1 -0
  34. package/dist/continuity.js +69 -0
  35. package/dist/continuity.js.map +1 -0
  36. package/dist/diff.d.ts +37 -0
  37. package/dist/diff.d.ts.map +1 -0
  38. package/dist/diff.js +258 -0
  39. package/dist/diff.js.map +1 -0
  40. package/dist/edit/command.d.ts +4 -0
  41. package/dist/edit/command.d.ts.map +1 -0
  42. package/dist/edit/command.js +16 -0
  43. package/dist/edit/command.js.map +1 -0
  44. package/dist/edit/errors.d.ts +9 -0
  45. package/dist/edit/errors.d.ts.map +1 -0
  46. package/dist/edit/errors.js +12 -0
  47. package/dist/edit/errors.js.map +1 -0
  48. package/dist/edit/fields.d.ts +40 -0
  49. package/dist/edit/fields.d.ts.map +1 -0
  50. package/dist/edit/fields.js +150 -0
  51. package/dist/edit/fields.js.map +1 -0
  52. package/dist/edit/finish.d.ts +15 -0
  53. package/dist/edit/finish.d.ts.map +1 -0
  54. package/dist/edit/finish.js +36 -0
  55. package/dist/edit/finish.js.map +1 -0
  56. package/dist/edit/groups.d.ts +66 -0
  57. package/dist/edit/groups.d.ts.map +1 -0
  58. package/dist/edit/groups.js +277 -0
  59. package/dist/edit/groups.js.map +1 -0
  60. package/dist/edit/ids.d.ts +39 -0
  61. package/dist/edit/ids.d.ts.map +1 -0
  62. package/dist/edit/ids.js +76 -0
  63. package/dist/edit/ids.js.map +1 -0
  64. package/dist/edit/index.d.ts +22 -0
  65. package/dist/edit/index.d.ts.map +1 -0
  66. package/dist/edit/index.js +22 -0
  67. package/dist/edit/index.js.map +1 -0
  68. package/dist/edit/meta.d.ts +15 -0
  69. package/dist/edit/meta.d.ts.map +1 -0
  70. package/dist/edit/meta.js +13 -0
  71. package/dist/edit/meta.js.map +1 -0
  72. package/dist/edit/path.d.ts +36 -0
  73. package/dist/edit/path.d.ts.map +1 -0
  74. package/dist/edit/path.js +135 -0
  75. package/dist/edit/path.js.map +1 -0
  76. package/dist/edit/steps.d.ts +53 -0
  77. package/dist/edit/steps.d.ts.map +1 -0
  78. package/dist/edit/steps.js +0 -0
  79. package/dist/edit/steps.js.map +1 -0
  80. package/dist/edit/template.d.ts +18 -0
  81. package/dist/edit/template.d.ts.map +1 -0
  82. package/dist/edit/template.js +30 -0
  83. package/dist/edit/template.js.map +1 -0
  84. package/dist/edit/timing.d.ts +11 -0
  85. package/dist/edit/timing.d.ts.map +1 -0
  86. package/dist/edit/timing.js +23 -0
  87. package/dist/edit/timing.js.map +1 -0
  88. package/dist/edit/tree.d.ts +32 -0
  89. package/dist/edit/tree.d.ts.map +1 -0
  90. package/dist/edit/tree.js +90 -0
  91. package/dist/edit/tree.js.map +1 -0
  92. package/dist/estimate.d.ts +48 -0
  93. package/dist/estimate.d.ts.map +1 -0
  94. package/dist/estimate.js +443 -0
  95. package/dist/estimate.js.map +1 -0
  96. package/dist/findingHelp.d.ts +18 -0
  97. package/dist/findingHelp.d.ts.map +1 -0
  98. package/dist/findingHelp.js +114 -0
  99. package/dist/findingHelp.js.map +1 -0
  100. package/dist/fix.d.ts +21 -0
  101. package/dist/fix.d.ts.map +1 -0
  102. package/dist/fix.js +180 -0
  103. package/dist/fix.js.map +1 -0
  104. package/dist/footprint.d.ts +33 -0
  105. package/dist/footprint.d.ts.map +1 -0
  106. package/dist/footprint.js +71 -0
  107. package/dist/footprint.js.map +1 -0
  108. package/dist/geometry/angle.d.ts +13 -0
  109. package/dist/geometry/angle.d.ts.map +1 -0
  110. package/dist/geometry/angle.js +21 -0
  111. package/dist/geometry/angle.js.map +1 -0
  112. package/dist/geometry/curve.d.ts +24 -0
  113. package/dist/geometry/curve.d.ts.map +1 -0
  114. package/dist/geometry/curve.js +78 -0
  115. package/dist/geometry/curve.js.map +1 -0
  116. package/dist/geometry/path.d.ts +29 -0
  117. package/dist/geometry/path.d.ts.map +1 -0
  118. package/dist/geometry/path.js +61 -0
  119. package/dist/geometry/path.js.map +1 -0
  120. package/dist/geometry/quadrature.d.ts +13 -0
  121. package/dist/geometry/quadrature.d.ts.map +1 -0
  122. package/dist/geometry/quadrature.js +57 -0
  123. package/dist/geometry/quadrature.js.map +1 -0
  124. package/dist/geometry/sat.d.ts +40 -0
  125. package/dist/geometry/sat.d.ts.map +1 -0
  126. package/dist/geometry/sat.js +86 -0
  127. package/dist/geometry/sat.js.map +1 -0
  128. package/dist/geometry/vec.d.ts +27 -0
  129. package/dist/geometry/vec.d.ts.map +1 -0
  130. package/dist/geometry/vec.js +30 -0
  131. package/dist/geometry/vec.js.map +1 -0
  132. package/dist/heading.d.ts +77 -0
  133. package/dist/heading.d.ts.map +1 -0
  134. package/dist/heading.js +164 -0
  135. package/dist/heading.js.map +1 -0
  136. package/dist/headingRanges.d.ts +48 -0
  137. package/dist/headingRanges.d.ts.map +1 -0
  138. package/dist/headingRanges.js +132 -0
  139. package/dist/headingRanges.js.map +1 -0
  140. package/dist/index.d.ts +41 -0
  141. package/dist/index.d.ts.map +1 -0
  142. package/dist/index.js +41 -0
  143. package/dist/index.js.map +1 -0
  144. package/dist/kinematics.d.ts +63 -0
  145. package/dist/kinematics.d.ts.map +1 -0
  146. package/dist/kinematics.js +71 -0
  147. package/dist/kinematics.js.map +1 -0
  148. package/dist/ledger.d.ts +49 -0
  149. package/dist/ledger.d.ts.map +1 -0
  150. package/dist/ledger.js +136 -0
  151. package/dist/ledger.js.map +1 -0
  152. package/dist/load.d.ts +12 -0
  153. package/dist/load.d.ts.map +1 -0
  154. package/dist/load.js +12 -0
  155. package/dist/load.js.map +1 -0
  156. package/dist/mirror.d.ts +81 -0
  157. package/dist/mirror.d.ts.map +1 -0
  158. package/dist/mirror.js +282 -0
  159. package/dist/mirror.js.map +1 -0
  160. package/dist/plan.d.ts +29 -0
  161. package/dist/plan.d.ts.map +1 -0
  162. package/dist/plan.js +159 -0
  163. package/dist/plan.js.map +1 -0
  164. package/dist/prBody.d.ts +24 -0
  165. package/dist/prBody.d.ts.map +1 -0
  166. package/dist/prBody.js +112 -0
  167. package/dist/prBody.js.map +1 -0
  168. package/dist/provenance.d.ts +21 -0
  169. package/dist/provenance.d.ts.map +1 -0
  170. package/dist/provenance.js +46 -0
  171. package/dist/provenance.js.map +1 -0
  172. package/dist/render.d.ts +18 -0
  173. package/dist/render.d.ts.map +1 -0
  174. package/dist/render.js +464 -0
  175. package/dist/render.js.map +1 -0
  176. package/dist/resolve.d.ts +39 -0
  177. package/dist/resolve.d.ts.map +1 -0
  178. package/dist/resolve.js +172 -0
  179. package/dist/resolve.js.map +1 -0
  180. package/dist/season.d.ts +65 -0
  181. package/dist/season.d.ts.map +1 -0
  182. package/dist/season.js +16 -0
  183. package/dist/season.js.map +1 -0
  184. package/dist/sim/commands.d.ts +97 -0
  185. package/dist/sim/commands.d.ts.map +1 -0
  186. package/dist/sim/commands.js +185 -0
  187. package/dist/sim/commands.js.map +1 -0
  188. package/dist/sim/curves.d.ts +37 -0
  189. package/dist/sim/curves.d.ts.map +1 -0
  190. package/dist/sim/curves.js +187 -0
  191. package/dist/sim/curves.js.map +1 -0
  192. package/dist/sim/follower.d.ts +124 -0
  193. package/dist/sim/follower.d.ts.map +1 -0
  194. package/dist/sim/follower.js +472 -0
  195. package/dist/sim/follower.js.map +1 -0
  196. package/dist/sim/ideal.d.ts +103 -0
  197. package/dist/sim/ideal.d.ts.map +1 -0
  198. package/dist/sim/ideal.js +393 -0
  199. package/dist/sim/ideal.js.map +1 -0
  200. package/dist/sim/index.d.ts +11 -0
  201. package/dist/sim/index.d.ts.map +1 -0
  202. package/dist/sim/index.js +10 -0
  203. package/dist/sim/index.js.map +1 -0
  204. package/dist/sim/params.d.ts +91 -0
  205. package/dist/sim/params.d.ts.map +1 -0
  206. package/dist/sim/params.js +195 -0
  207. package/dist/sim/params.js.map +1 -0
  208. package/dist/sim/pedroPath.d.ts +41 -0
  209. package/dist/sim/pedroPath.d.ts.map +1 -0
  210. package/dist/sim/pedroPath.js +191 -0
  211. package/dist/sim/pedroPath.js.map +1 -0
  212. package/dist/sim/plant.d.ts +59 -0
  213. package/dist/sim/plant.d.ts.map +1 -0
  214. package/dist/sim/plant.js +128 -0
  215. package/dist/sim/plant.js.map +1 -0
  216. package/dist/sim/residuals.d.ts +36 -0
  217. package/dist/sim/residuals.d.ts.map +1 -0
  218. package/dist/sim/residuals.js +86 -0
  219. package/dist/sim/residuals.js.map +1 -0
  220. package/dist/sim/simulate.d.ts +27 -0
  221. package/dist/sim/simulate.d.ts.map +1 -0
  222. package/dist/sim/simulate.js +924 -0
  223. package/dist/sim/simulate.js.map +1 -0
  224. package/dist/sim/types.d.ts +209 -0
  225. package/dist/sim/types.d.ts.map +1 -0
  226. package/dist/sim/types.js +2 -0
  227. package/dist/sim/types.js.map +1 -0
  228. package/dist/tokens.d.ts +93 -0
  229. package/dist/tokens.d.ts.map +1 -0
  230. package/dist/tokens.js +93 -0
  231. package/dist/tokens.js.map +1 -0
  232. package/dist/trace.d.ts +89 -0
  233. package/dist/trace.d.ts.map +1 -0
  234. package/dist/trace.js +187 -0
  235. package/dist/trace.js.map +1 -0
  236. package/dist/types.d.ts +219 -0
  237. package/dist/types.d.ts.map +1 -0
  238. package/dist/types.js +2 -0
  239. package/dist/types.js.map +1 -0
  240. package/dist/unimplemented.d.ts +8 -0
  241. package/dist/unimplemented.d.ts.map +1 -0
  242. package/dist/unimplemented.js +11 -0
  243. package/dist/unimplemented.js.map +1 -0
  244. package/package.json +59 -0
@@ -0,0 +1,57 @@
1
+ const cache = new Map();
2
+ /** P_n(x) and its derivative, by the standard three-term recurrence. */
3
+ function legendre(n, x) {
4
+ let previous = 1;
5
+ let current = x;
6
+ for (let k = 2; k <= n; k += 1) {
7
+ const next = ((2 * k - 1) * x * current - (k - 1) * previous) / k;
8
+ previous = current;
9
+ current = next;
10
+ }
11
+ if (n === 0)
12
+ return { value: 1, derivative: 0 };
13
+ const derivative = (n * (x * current - previous)) / (x * x - 1);
14
+ return { value: current, derivative };
15
+ }
16
+ export function gaussLegendre(order) {
17
+ const cached = cache.get(order);
18
+ if (cached !== undefined)
19
+ return cached;
20
+ const nodes = [];
21
+ const weights = [];
22
+ const half = Math.floor((order + 1) / 2);
23
+ for (let i = 1; i <= half; i += 1) {
24
+ // Chebyshev-like first guess, then Newton to machine precision.
25
+ let x = Math.cos((Math.PI * (i - 0.25)) / (order + 0.5));
26
+ for (let iteration = 0; iteration < 100; iteration += 1) {
27
+ const { value, derivative } = legendre(order, x);
28
+ const step = value / derivative;
29
+ x -= step;
30
+ if (Math.abs(step) < 1e-15)
31
+ break;
32
+ }
33
+ const { derivative } = legendre(order, x);
34
+ const weight = 2 / ((1 - x * x) * derivative * derivative);
35
+ nodes.push(-x);
36
+ weights.push(weight);
37
+ if (i !== order - i + 1) {
38
+ nodes.push(x);
39
+ weights.push(weight);
40
+ }
41
+ }
42
+ const quadrature = { nodes, weights };
43
+ cache.set(order, quadrature);
44
+ return quadrature;
45
+ }
46
+ /** Integrates f over [a, b] with an `order`-point Gauss-Legendre rule. */
47
+ export function integrate(f, a, b, order) {
48
+ const { nodes, weights } = gaussLegendre(order);
49
+ const half = (b - a) / 2;
50
+ const middle = (a + b) / 2;
51
+ let sum = 0;
52
+ for (let i = 0; i < nodes.length; i += 1) {
53
+ sum += weights[i] * f(middle + half * nodes[i]);
54
+ }
55
+ return sum * half;
56
+ }
57
+ //# sourceMappingURL=quadrature.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"quadrature.js","sourceRoot":"","sources":["../../src/geometry/quadrature.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,40 @@
1
+ import { type Vec2 } from "./vec.js";
2
+ /** An axis-aligned box in the field frame: obstacles, zones and the field perimeter are these. */
3
+ export interface Box2 {
4
+ minXIn: number;
5
+ maxXIn: number;
6
+ minYIn: number;
7
+ maxYIn: number;
8
+ }
9
+ /** A rectangle carried by the robot: centre, size and the heading it is rotated to. */
10
+ export interface Rect {
11
+ centreIn: Vec2;
12
+ lengthIn: number;
13
+ widthIn: number;
14
+ headingRad: number;
15
+ }
16
+ /** The four corners, counter-clockwise, of a rotated rectangle. */
17
+ export declare function rectCorners(rect: Rect): Vec2[];
18
+ export declare const boxCorners: (box: Box2) => Vec2[];
19
+ export interface OverlapResult {
20
+ overlaps: boolean;
21
+ /** How deep the overlap is on its shallowest separating axis, in inches; 0 when apart. */
22
+ penetrationIn: number;
23
+ }
24
+ /**
25
+ * Separating-axis test between a convex polygon (the robot's rotated footprint or a mouth box) and
26
+ * an axis-aligned box (an obstacle). site/docs/checks-and-findings.md asks for
27
+ * the deepest penetration, which is the smallest overlap over the candidate axes.
28
+ */
29
+ export declare function polygonOverlapsBox(polygon: readonly Vec2[], box: Box2): OverlapResult;
30
+ /** True when the point is inside the box, edges included. */
31
+ export declare const pointInBox: (point: Vec2, box: Box2) => boolean;
32
+ export interface OutsideResult {
33
+ outside: boolean;
34
+ /** The furthest any corner is outside the box, in inches. */
35
+ worstIn: number;
36
+ worstPointIn: Vec2 | null;
37
+ }
38
+ /** How far a polygon pokes out of a box. The PERIMETER check reads `worstIn`. */
39
+ export declare function polygonOutsideBox(polygon: readonly Vec2[], box: Box2): OutsideResult;
40
+ //# sourceMappingURL=sat.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sat.d.ts","sourceRoot":"","sources":["../../src/geometry/sat.ts"],"names":[],"mappings":"AAAA,OAAO,EAAe,KAAK,IAAI,EAAE,MAAM,UAAU,CAAC;AAElD,kGAAkG;AAClG,MAAM,WAAW,IAAI;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,uFAAuF;AACvF,MAAM,WAAW,IAAI;IACnB,QAAQ,EAAE,IAAI,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,mEAAmE;AACnE,wBAAgB,WAAW,CAAC,IAAI,EAAE,IAAI,GAAG,IAAI,EAAE,CAU9C;AAED,eAAO,MAAM,UAAU,GAAI,KAAK,IAAI,KAAG,IAAI,EAK1C,CAAC;AA+BF,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,OAAO,CAAC;IAClB,0FAA0F;IAC1F,aAAa,EAAE,MAAM,CAAC;CACvB;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,SAAS,IAAI,EAAE,EAAE,GAAG,EAAE,IAAI,GAAG,aAAa,CAerF;AAED,6DAA6D;AAC7D,eAAO,MAAM,UAAU,GAAI,OAAO,IAAI,EAAE,KAAK,IAAI,KAAG,OAI3B,CAAC;AAE1B,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,OAAO,CAAC;IACjB,6DAA6D;IAC7D,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,IAAI,GAAG,IAAI,CAAC;CAC3B;AAED,iFAAiF;AACjF,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,SAAS,IAAI,EAAE,EAAE,GAAG,EAAE,IAAI,GAAG,aAAa,CAgBpF"}
@@ -0,0 +1,86 @@
1
+ import { add, rotate } from "./vec.js";
2
+ /** The four corners, counter-clockwise, of a rotated rectangle. */
3
+ export function rectCorners(rect) {
4
+ const halfLength = rect.lengthIn / 2;
5
+ const halfWidth = rect.widthIn / 2;
6
+ const local = [
7
+ { xIn: halfLength, yIn: halfWidth },
8
+ { xIn: -halfLength, yIn: halfWidth },
9
+ { xIn: -halfLength, yIn: -halfWidth },
10
+ { xIn: halfLength, yIn: -halfWidth },
11
+ ];
12
+ return local.map((corner) => add(rect.centreIn, rotate(corner, rect.headingRad)));
13
+ }
14
+ export const boxCorners = (box) => [
15
+ { xIn: box.minXIn, yIn: box.minYIn },
16
+ { xIn: box.maxXIn, yIn: box.minYIn },
17
+ { xIn: box.maxXIn, yIn: box.maxYIn },
18
+ { xIn: box.minXIn, yIn: box.maxYIn },
19
+ ];
20
+ function project(points, axis) {
21
+ let min = Infinity;
22
+ let max = -Infinity;
23
+ for (const point of points) {
24
+ const value = point.xIn * axis.xIn + point.yIn * axis.yIn;
25
+ if (value < min)
26
+ min = value;
27
+ if (value > max)
28
+ max = value;
29
+ }
30
+ return { min, max };
31
+ }
32
+ function axesOf(polygon) {
33
+ const axes = [];
34
+ for (let i = 0; i < polygon.length; i += 1) {
35
+ const a = polygon[i];
36
+ const b = polygon[(i + 1) % polygon.length];
37
+ const edge = { xIn: b.xIn - a.xIn, yIn: b.yIn - a.yIn };
38
+ const normalLength = Math.hypot(edge.xIn, edge.yIn);
39
+ if (normalLength < 1e-12)
40
+ continue;
41
+ axes.push({ xIn: -edge.yIn / normalLength, yIn: edge.xIn / normalLength });
42
+ }
43
+ return axes;
44
+ }
45
+ /**
46
+ * Separating-axis test between a convex polygon (the robot's rotated footprint or a mouth box) and
47
+ * an axis-aligned box (an obstacle). site/docs/checks-and-findings.md asks for
48
+ * the deepest penetration, which is the smallest overlap over the candidate axes.
49
+ */
50
+ export function polygonOverlapsBox(polygon, box) {
51
+ const worldAxes = [
52
+ { xIn: 1, yIn: 0 },
53
+ { xIn: 0, yIn: 1 },
54
+ ];
55
+ const corners = boxCorners(box);
56
+ let smallest = Infinity;
57
+ for (const axis of [...axesOf(polygon), ...worldAxes]) {
58
+ const a = project(polygon, axis);
59
+ const b = project(corners, axis);
60
+ const overlap = Math.min(a.max, b.max) - Math.max(a.min, b.min);
61
+ if (overlap <= 0)
62
+ return { overlaps: false, penetrationIn: 0 };
63
+ if (overlap < smallest)
64
+ smallest = overlap;
65
+ }
66
+ return { overlaps: true, penetrationIn: smallest };
67
+ }
68
+ /** True when the point is inside the box, edges included. */
69
+ export const pointInBox = (point, box) => point.xIn >= box.minXIn &&
70
+ point.xIn <= box.maxXIn &&
71
+ point.yIn >= box.minYIn &&
72
+ point.yIn <= box.maxYIn;
73
+ /** How far a polygon pokes out of a box. The PERIMETER check reads `worstIn`. */
74
+ export function polygonOutsideBox(polygon, box) {
75
+ let worstIn = 0;
76
+ let worstPointIn = null;
77
+ for (const point of polygon) {
78
+ const out = Math.max(box.minXIn - point.xIn, point.xIn - box.maxXIn, box.minYIn - point.yIn, point.yIn - box.maxYIn);
79
+ if (out > worstIn) {
80
+ worstIn = out;
81
+ worstPointIn = point;
82
+ }
83
+ }
84
+ return { outside: worstPointIn !== null, worstIn, worstPointIn };
85
+ }
86
+ //# sourceMappingURL=sat.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sat.js","sourceRoot":"","sources":["../../src/geometry/sat.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Points and poses in the field frame. Units are inches and radians throughout core, and every key
3
+ * names its unit, the same as the files do (CLAUDE.md rule 3).
4
+ */
5
+ export interface Vec2 {
6
+ xIn: number;
7
+ yIn: number;
8
+ }
9
+ export interface Pose extends Vec2 {
10
+ headingRad: number;
11
+ }
12
+ export declare const vec: (xIn: number, yIn: number) => Vec2;
13
+ export declare const pose: (xIn: number, yIn: number, headingRad: number) => Pose;
14
+ export declare const add: (a: Vec2, b: Vec2) => Vec2;
15
+ export declare const sub: (a: Vec2, b: Vec2) => Vec2;
16
+ export declare const scale: (a: Vec2, k: number) => Vec2;
17
+ export declare const dot: (a: Vec2, b: Vec2) => number;
18
+ /** The 2-D cross product's z component; positive when b is counter-clockwise from a. */
19
+ export declare const cross: (a: Vec2, b: Vec2) => number;
20
+ export declare const length: (a: Vec2) => number;
21
+ export declare const distance: (a: Vec2, b: Vec2) => number;
22
+ export declare function normalize(a: Vec2): Vec2;
23
+ /** Rotates about the origin by `rad`, counter-clockwise, which is the field's positive direction. */
24
+ export declare function rotate(a: Vec2, rad: number): Vec2;
25
+ /** Straight-line interpolation. Headings are interpolated by lerpAngle, not by this. */
26
+ export declare const lerp: (a: Vec2, b: Vec2, t: number) => Vec2;
27
+ //# sourceMappingURL=vec.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vec.d.ts","sourceRoot":"","sources":["../../src/geometry/vec.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,MAAM,WAAW,IAAI;IACnB,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,IAAK,SAAQ,IAAI;IAChC,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,eAAO,MAAM,GAAG,GAAI,KAAK,MAAM,EAAE,KAAK,MAAM,KAAG,IAAsB,CAAC;AAEtE,eAAO,MAAM,IAAI,GAAI,KAAK,MAAM,EAAE,KAAK,MAAM,EAAE,YAAY,MAAM,KAAG,IAIlE,CAAC;AAEH,eAAO,MAAM,GAAG,GAAI,GAAG,IAAI,EAAE,GAAG,IAAI,KAAG,IAAoD,CAAC;AAE5F,eAAO,MAAM,GAAG,GAAI,GAAG,IAAI,EAAE,GAAG,IAAI,KAAG,IAAoD,CAAC;AAE5F,eAAO,MAAM,KAAK,GAAI,GAAG,IAAI,EAAE,GAAG,MAAM,KAAG,IAA4C,CAAC;AAExF,eAAO,MAAM,GAAG,GAAI,GAAG,IAAI,EAAE,GAAG,IAAI,KAAG,MAAuC,CAAC;AAE/E,wFAAwF;AACxF,eAAO,MAAM,KAAK,GAAI,GAAG,IAAI,EAAE,GAAG,IAAI,KAAG,MAAuC,CAAC;AAEjF,eAAO,MAAM,MAAM,GAAI,GAAG,IAAI,KAAG,MAAkC,CAAC;AAEpE,eAAO,MAAM,QAAQ,GAAI,GAAG,IAAI,EAAE,GAAG,IAAI,KAAG,MAAkD,CAAC;AAE/F,wBAAgB,SAAS,CAAC,CAAC,EAAE,IAAI,GAAG,IAAI,CAGvC;AAED,qGAAqG;AACrG,wBAAgB,MAAM,CAAC,CAAC,EAAE,IAAI,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI,CAIjD;AAED,wFAAwF;AACxF,eAAO,MAAM,IAAI,GAAI,GAAG,IAAI,EAAE,GAAG,IAAI,EAAE,GAAG,MAAM,KAAG,IAGjD,CAAC"}
@@ -0,0 +1,30 @@
1
+ export const vec = (xIn, yIn) => ({ xIn, yIn });
2
+ export const pose = (xIn, yIn, headingRad) => ({
3
+ xIn,
4
+ yIn,
5
+ headingRad,
6
+ });
7
+ export const add = (a, b) => ({ xIn: a.xIn + b.xIn, yIn: a.yIn + b.yIn });
8
+ export const sub = (a, b) => ({ xIn: a.xIn - b.xIn, yIn: a.yIn - b.yIn });
9
+ export const scale = (a, k) => ({ xIn: a.xIn * k, yIn: a.yIn * k });
10
+ export const dot = (a, b) => a.xIn * b.xIn + a.yIn * b.yIn;
11
+ /** The 2-D cross product's z component; positive when b is counter-clockwise from a. */
12
+ export const cross = (a, b) => a.xIn * b.yIn - a.yIn * b.xIn;
13
+ export const length = (a) => Math.hypot(a.xIn, a.yIn);
14
+ export const distance = (a, b) => Math.hypot(a.xIn - b.xIn, a.yIn - b.yIn);
15
+ export function normalize(a) {
16
+ const len = length(a);
17
+ return len === 0 ? { xIn: 0, yIn: 0 } : { xIn: a.xIn / len, yIn: a.yIn / len };
18
+ }
19
+ /** Rotates about the origin by `rad`, counter-clockwise, which is the field's positive direction. */
20
+ export function rotate(a, rad) {
21
+ const cos = Math.cos(rad);
22
+ const sin = Math.sin(rad);
23
+ return { xIn: a.xIn * cos - a.yIn * sin, yIn: a.xIn * sin + a.yIn * cos };
24
+ }
25
+ /** Straight-line interpolation. Headings are interpolated by lerpAngle, not by this. */
26
+ export const lerp = (a, b, t) => ({
27
+ xIn: a.xIn + (b.xIn - a.xIn) * t,
28
+ yIn: a.yIn + (b.yIn - a.yIn) * t,
29
+ });
30
+ //# sourceMappingURL=vec.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vec.js","sourceRoot":"","sources":["../../src/geometry/vec.ts"],"names":[],"mappings":"AAaA,MAAM,CAAC,MAAM,GAAG,GAAG,CAAC,GAAW,EAAE,GAAW,EAAQ,EAAE,CAAC,CAAC,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC,CAAC;AAEtE,MAAM,CAAC,MAAM,IAAI,GAAG,CAAC,GAAW,EAAE,GAAW,EAAE,UAAkB,EAAQ,EAAE,CAAC,CAAC;IAC3E,GAAG;IACH,GAAG;IACH,UAAU;CACX,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,GAAG,GAAG,CAAC,CAAO,EAAE,CAAO,EAAQ,EAAE,CAAC,CAAC,EAAE,GAAG,EAAE,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,GAAG,EAAE,GAAG,EAAE,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC;AAE5F,MAAM,CAAC,MAAM,GAAG,GAAG,CAAC,CAAO,EAAE,CAAO,EAAQ,EAAE,CAAC,CAAC,EAAE,GAAG,EAAE,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,GAAG,EAAE,GAAG,EAAE,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC;AAE5F,MAAM,CAAC,MAAM,KAAK,GAAG,CAAC,CAAO,EAAE,CAAS,EAAQ,EAAE,CAAC,CAAC,EAAE,GAAG,EAAE,CAAC,CAAC,GAAG,GAAG,CAAC,EAAE,GAAG,EAAE,CAAC,CAAC,GAAG,GAAG,CAAC,EAAE,CAAC,CAAC;AAExF,MAAM,CAAC,MAAM,GAAG,GAAG,CAAC,CAAO,EAAE,CAAO,EAAU,EAAE,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,GAAG,CAAC;AAE/E,wFAAwF;AACxF,MAAM,CAAC,MAAM,KAAK,GAAG,CAAC,CAAO,EAAE,CAAO,EAAU,EAAE,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,GAAG,CAAC;AAEjF,MAAM,CAAC,MAAM,MAAM,GAAG,CAAC,CAAO,EAAU,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC;AAEpE,MAAM,CAAC,MAAM,QAAQ,GAAG,CAAC,CAAO,EAAE,CAAO,EAAU,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC;AAE/F,MAAM,UAAU,SAAS,CAAC,CAAO;IAC/B,MAAM,GAAG,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC;IACtB,OAAO,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,CAAC,CAAC,GAAG,GAAG,GAAG,EAAE,GAAG,EAAE,CAAC,CAAC,GAAG,GAAG,GAAG,EAAE,CAAC;AACjF,CAAC;AAED,qGAAqG;AACrG,MAAM,UAAU,MAAM,CAAC,CAAO,EAAE,GAAW;IACzC,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IAC1B,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IAC1B,OAAO,EAAE,GAAG,EAAE,CAAC,CAAC,GAAG,GAAG,GAAG,GAAG,CAAC,CAAC,GAAG,GAAG,GAAG,EAAE,GAAG,EAAE,CAAC,CAAC,GAAG,GAAG,GAAG,GAAG,CAAC,CAAC,GAAG,GAAG,GAAG,EAAE,CAAC;AAC5E,CAAC;AAED,wFAAwF;AACxF,MAAM,CAAC,MAAM,IAAI,GAAG,CAAC,CAAO,EAAE,CAAO,EAAE,CAAS,EAAQ,EAAE,CAAC,CAAC;IAC1D,GAAG,EAAE,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC;IAChC,GAAG,EAAE,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC;CACjC,CAAC,CAAC"}
@@ -0,0 +1,77 @@
1
+ import type { Heading, HeadingRange } from "@horizon36596/zenith-schema";
2
+ import type { PathGeometry } from "./geometry/path.js";
3
+ /** The direction of travel at t, wrapped to (-pi, pi]. */
4
+ export declare function tangentHeading(path: PathGeometry, t: number): number;
5
+ type LinearHeading = Extract<Heading, {
6
+ mode: "linear";
7
+ }>;
8
+ /**
9
+ * How far past half a turn a piece may ask before HEADING calls it reversed. Canonical form rounds
10
+ * radians to four decimals, so the half turn the editor writes, 3.1416, is a hair over pi; either
11
+ * way round is the same half turn to the person who wrote it, and the robot's choice is drawn.
12
+ */
13
+ export declare const HALF_TURN_TOLERANCE_RAD = 0.001;
14
+ /** One segment's share of a `linear` heading, the way the robot runtime hands it to Pedro. */
15
+ export interface LinearHeadingPiece {
16
+ /** Where the piece starts and ends along the step, as arc-length fractions. */
17
+ startT: number;
18
+ endT: number;
19
+ /** The headings the runtime passes to Pedro's `Path.linear(from, to)` for this segment. */
20
+ fromRad: number;
21
+ toRad: number;
22
+ /** The turn the robot actually makes over the piece: the short way from `fromRad` to `toRad`. */
23
+ turnRad: number;
24
+ /**
25
+ * True when the piece asks for more than half a turn (past `HALF_TURN_TOLERANCE_RAD`), so the
26
+ * robot turns the other way round from the one the file wrote.
27
+ */
28
+ reversed: boolean;
29
+ }
30
+ /**
31
+ * The pieces of a `linear` heading, one per segment, cut exactly as
32
+ * `robot/auto-runtime/PathBuilder.withHeading` cuts them: the sweep `toRad - fromRad` the file wrote
33
+ * is shared out by arc length, and each segment gets `Path.linear(from, to)` over its share. Pedro's
34
+ * `Interpolator.linear` then turns the short way, so a share of
35
+ * more than half a turn goes the other way round on the robot. A deliberate 270 degree sweep split
36
+ * over two segments of equal length therefore does turn 270 degrees, and the same sweep on one
37
+ * segment turns 90 degrees the other way.
38
+ */
39
+ export declare function linearHeadingPieces(heading: LinearHeading, path: PathGeometry): LinearHeadingPiece[];
40
+ /**
41
+ * h(t) for a heading mode (site/docs/file-format.md, Heading modes). Always wrapped to (-pi, pi].
42
+ *
43
+ * `linear` turns the way the robot turns: each segment's share of the sweep the short way round
44
+ * (`linearHeadingPieces`), because that is what Pedro does with the numbers the runtime gives it,
45
+ * and the preview must show what the robot does. A share of more than half a turn is what the
46
+ * HEADING warning points out. `tangent` and `facePoint` interpolate nothing: they read the geometry
47
+ * at t.
48
+ */
49
+ export declare function headingFunction(heading: Heading, path: PathGeometry): (t: number) => number;
50
+ /**
51
+ * The heading at arc-length fraction t of a step, for any mode: `headingFunction` for one sample.
52
+ * Pure and cheap enough to call per frame; the ideal playback and the canvas both read it.
53
+ */
54
+ export declare function headingAtT(heading: Heading, path: PathGeometry, t: number): number;
55
+ /**
56
+ * Which range of a `piecewise` heading owns t, the way Pedro's `PiecewiseInterpolator` picks one:
57
+ * the first range whose end is at or past t (`ceilingEntry`), so a boundary belongs to the range it
58
+ * ends. Past the last end, the last range. The ranges are taken in file order.
59
+ */
60
+ export declare function rangeIndexAt(ranges: readonly HeadingRange[], t: number): number;
61
+ /**
62
+ * h(t) inside one range, with t still the step's own. `linear` turns the short way from `fromRad`
63
+ * at the range's start to `toRad` at its end, which is what the runtime's range interpolator does;
64
+ * every other mode reads the whole path at t, exactly as it would outside a range.
65
+ */
66
+ export declare function rangeHeadingFunction(range: HeadingRange, path: PathGeometry): (t: number) => number;
67
+ /** How close two range ends must be to count as the same t. Canonical form keeps t exact. */
68
+ export declare const RANGE_T_TOLERANCE = 0.000001;
69
+ /**
70
+ * What is wrong with a `piecewise` heading's ranges, in one sentence, or null when Pedro will take
71
+ * them: they must start at 0, each start where the one before ended, each end after it starts, and
72
+ * the last end at exactly 1 (`PiecewiseInterpolator.until` refuses a t that does not increase, and
73
+ * `interpolate` refuses to run until the ranges reach 1).
74
+ */
75
+ export declare function rangeProblem(ranges: readonly HeadingRange[]): string | null;
76
+ export {};
77
+ //# sourceMappingURL=heading.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"heading.d.ts","sourceRoot":"","sources":["../src/heading.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,YAAY,EAAgB,MAAM,6BAA6B,CAAC;AAEvF,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAEvD,0DAA0D;AAC1D,wBAAgB,cAAc,CAAC,IAAI,EAAE,YAAY,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAUpE;AAED,KAAK,aAAa,GAAG,OAAO,CAAC,OAAO,EAAE;IAAE,IAAI,EAAE,QAAQ,CAAA;CAAE,CAAC,CAAC;AAE1D;;;;GAIG;AACH,eAAO,MAAM,uBAAuB,QAAO,CAAC;AAQ5C,8FAA8F;AAC9F,MAAM,WAAW,kBAAkB;IACjC,+EAA+E;IAC/E,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,2FAA2F;IAC3F,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,iGAAiG;IACjG,OAAO,EAAE,MAAM,CAAC;IAChB;;;OAGG;IACH,QAAQ,EAAE,OAAO,CAAC;CACnB;AAED;;;;;;;;GAQG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,aAAa,EAAE,IAAI,EAAE,YAAY,GAAG,kBAAkB,EAAE,CAcpG;AAED;;;;;;;;GAQG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,YAAY,GAAG,CAAC,CAAC,EAAE,MAAM,KAAK,MAAM,CAkC3F;AAED;;;GAGG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,YAAY,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAElF;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,SAAS,YAAY,EAAE,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAK/E;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,YAAY,EAAE,IAAI,EAAE,YAAY,GAAG,CAAC,CAAC,EAAE,MAAM,KAAK,MAAM,CASnG;AAED,6FAA6F;AAC7F,eAAO,MAAM,iBAAiB,WAAO,CAAC;AAEtC;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,SAAS,YAAY,EAAE,GAAG,MAAM,GAAG,IAAI,CA0B3E"}
@@ -0,0 +1,164 @@
1
+ import { wrapAngle } from "./geometry/angle.js";
2
+ /** The direction of travel at t, wrapped to (-pi, pi]. */
3
+ export function tangentHeading(path, t) {
4
+ let tangent = path.tangentAt(t);
5
+ if (Math.hypot(tangent.xIn, tangent.yIn) < 1e-9) {
6
+ // A cusp or a repeated control point: look a little way along the path instead. The sample is
7
+ // not negated at the far end, because `tangentAt` is the derivative with respect to u and so
8
+ // already points the way the robot is travelling wherever it is taken.
9
+ const nudged = t < 0.5 ? Math.min(t + 1e-4, 1) : Math.max(t - 1e-4, 0);
10
+ tangent = path.tangentAt(nudged);
11
+ }
12
+ return wrapAngle(Math.atan2(tangent.yIn, tangent.xIn));
13
+ }
14
+ /**
15
+ * How far past half a turn a piece may ask before HEADING calls it reversed. Canonical form rounds
16
+ * radians to four decimals, so the half turn the editor writes, 3.1416, is a hair over pi; either
17
+ * way round is the same half turn to the person who wrote it, and the robot's choice is drawn.
18
+ */
19
+ export const HALF_TURN_TOLERANCE_RAD = 1e-3;
20
+ /** Pedro's `Angle.normalizeSigned`: [-pi, pi), so an exact half turn goes clockwise, as it does on the robot. */
21
+ const pedroSigned = (rad) => {
22
+ const wrapped = wrapAngle(rad);
23
+ return wrapped === Math.PI ? -Math.PI : wrapped;
24
+ };
25
+ /**
26
+ * The pieces of a `linear` heading, one per segment, cut exactly as
27
+ * `robot/auto-runtime/PathBuilder.withHeading` cuts them: the sweep `toRad - fromRad` the file wrote
28
+ * is shared out by arc length, and each segment gets `Path.linear(from, to)` over its share. Pedro's
29
+ * `Interpolator.linear` then turns the short way, so a share of
30
+ * more than half a turn goes the other way round on the robot. A deliberate 270 degree sweep split
31
+ * over two segments of equal length therefore does turn 270 degrees, and the same sweep on one
32
+ * segment turns 90 degrees the other way.
33
+ */
34
+ export function linearHeadingPieces(heading, path) {
35
+ const sweep = heading.toRad - heading.fromRad;
36
+ const total = path.lengthIn;
37
+ const pieces = [];
38
+ for (let index = 0; index + 1 < path.breaks.length; index += 1) {
39
+ const startT = total === 0 ? 0 : path.breaks[index] / total;
40
+ const endT = total === 0 ? 1 : path.breaks[index + 1] / total;
41
+ const fromRad = heading.fromRad + sweep * startT;
42
+ const toRad = heading.fromRad + sweep * endT;
43
+ const turnRad = pedroSigned(toRad - fromRad);
44
+ const reversed = Math.abs(toRad - fromRad) > Math.PI + HALF_TURN_TOLERANCE_RAD;
45
+ pieces.push({ startT, endT, fromRad, toRad, turnRad, reversed });
46
+ }
47
+ return pieces;
48
+ }
49
+ /**
50
+ * h(t) for a heading mode (site/docs/file-format.md, Heading modes). Always wrapped to (-pi, pi].
51
+ *
52
+ * `linear` turns the way the robot turns: each segment's share of the sweep the short way round
53
+ * (`linearHeadingPieces`), because that is what Pedro does with the numbers the runtime gives it,
54
+ * and the preview must show what the robot does. A share of more than half a turn is what the
55
+ * HEADING warning points out. `tangent` and `facePoint` interpolate nothing: they read the geometry
56
+ * at t.
57
+ */
58
+ export function headingFunction(heading, path) {
59
+ switch (heading.mode) {
60
+ case "piecewise": {
61
+ const ranges = heading.ranges;
62
+ const inner = ranges.map((range) => rangeHeadingFunction(range, path));
63
+ return (t) => {
64
+ const index = rangeIndexAt(ranges, t);
65
+ return inner[index](t);
66
+ };
67
+ }
68
+ case "tangent":
69
+ return (t) => tangentHeading(path, t);
70
+ case "tangentReversed":
71
+ return (t) => wrapAngle(tangentHeading(path, t) + Math.PI);
72
+ case "constant":
73
+ return () => wrapAngle(heading.headingRad);
74
+ case "linear": {
75
+ const pieces = linearHeadingPieces(heading, path);
76
+ return (t) => {
77
+ const clamped = t < 0 ? 0 : t > 1 ? 1 : t;
78
+ const piece = pieces.find((candidate) => clamped <= candidate.endT) ?? pieces[pieces.length - 1];
79
+ const span = piece.endT - piece.startT;
80
+ const local = span <= 0 ? 1 : (clamped - piece.startT) / span;
81
+ return wrapAngle(piece.fromRad + piece.turnRad * local);
82
+ };
83
+ }
84
+ case "facePoint":
85
+ return (t) => {
86
+ const point = path.pointAt(t);
87
+ const bearing = Math.atan2(heading.yIn - point.yIn, heading.xIn - point.xIn);
88
+ return wrapAngle(bearing + (heading.offsetRad ?? 0));
89
+ };
90
+ }
91
+ }
92
+ /**
93
+ * The heading at arc-length fraction t of a step, for any mode: `headingFunction` for one sample.
94
+ * Pure and cheap enough to call per frame; the ideal playback and the canvas both read it.
95
+ */
96
+ export function headingAtT(heading, path, t) {
97
+ return headingFunction(heading, path)(t);
98
+ }
99
+ /**
100
+ * Which range of a `piecewise` heading owns t, the way Pedro's `PiecewiseInterpolator` picks one:
101
+ * the first range whose end is at or past t (`ceilingEntry`), so a boundary belongs to the range it
102
+ * ends. Past the last end, the last range. The ranges are taken in file order.
103
+ */
104
+ export function rangeIndexAt(ranges, t) {
105
+ for (let index = 0; index < ranges.length; index += 1) {
106
+ if (t <= ranges[index].endT + 1e-12)
107
+ return index;
108
+ }
109
+ return Math.max(0, ranges.length - 1);
110
+ }
111
+ /**
112
+ * h(t) inside one range, with t still the step's own. `linear` turns the short way from `fromRad`
113
+ * at the range's start to `toRad` at its end, which is what the runtime's range interpolator does;
114
+ * every other mode reads the whole path at t, exactly as it would outside a range.
115
+ */
116
+ export function rangeHeadingFunction(range, path) {
117
+ const heading = range.heading;
118
+ if (heading.mode !== "linear")
119
+ return headingFunction(heading, path);
120
+ const span = range.endT - range.startT;
121
+ const turnRad = pedroSigned(heading.toRad - heading.fromRad);
122
+ return (t) => {
123
+ const local = span <= 0 ? 1 : Math.min(1, Math.max(0, (t - range.startT) / span));
124
+ return wrapAngle(heading.fromRad + turnRad * local);
125
+ };
126
+ }
127
+ /** How close two range ends must be to count as the same t. Canonical form keeps t exact. */
128
+ export const RANGE_T_TOLERANCE = 1e-6;
129
+ /**
130
+ * What is wrong with a `piecewise` heading's ranges, in one sentence, or null when Pedro will take
131
+ * them: they must start at 0, each start where the one before ended, each end after it starts, and
132
+ * the last end at exactly 1 (`PiecewiseInterpolator.until` refuses a t that does not increase, and
133
+ * `interpolate` refuses to run until the ranges reach 1).
134
+ */
135
+ export function rangeProblem(ranges) {
136
+ if (ranges.length === 0)
137
+ return "A piecewise heading needs at least one range.";
138
+ const first = ranges[0];
139
+ if (Math.abs(first.startT) > RANGE_T_TOLERANCE) {
140
+ return `The first range starts at t ${String(first.startT)}; it must start at 0, or the robot has no heading before it.`;
141
+ }
142
+ for (const [index, range] of ranges.entries()) {
143
+ const label = `Range ${String(index + 1)}`;
144
+ if (range.endT <= range.startT + RANGE_T_TOLERANCE) {
145
+ return `${label} ends at t ${String(range.endT)}, which is not after its start at ${String(range.startT)}.`;
146
+ }
147
+ const next = ranges[index + 1];
148
+ if (next === undefined)
149
+ continue;
150
+ const gap = next.startT - range.endT;
151
+ if (gap > RANGE_T_TOLERANCE) {
152
+ return `${label} ends at t ${String(range.endT)} but range ${String(index + 2)} starts at ${String(next.startT)}, leaving a gap with no heading.`;
153
+ }
154
+ if (gap < -RANGE_T_TOLERANCE) {
155
+ return `${label} ends at t ${String(range.endT)} but range ${String(index + 2)} starts earlier, at ${String(next.startT)}; ranges must not overlap.`;
156
+ }
157
+ }
158
+ const last = ranges[ranges.length - 1];
159
+ if (Math.abs(last.endT - 1) > RANGE_T_TOLERANCE) {
160
+ return `The last range ends at t ${String(last.endT)}; it must end at 1, or the robot has no heading for the rest of the path.`;
161
+ }
162
+ return null;
163
+ }
164
+ //# sourceMappingURL=heading.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"heading.js","sourceRoot":"","sources":["../src/heading.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAGhD,0DAA0D;AAC1D,MAAM,UAAU,cAAc,CAAC,IAAkB,EAAE,CAAS;IAC1D,IAAI,OAAO,GAAG,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC;IAChC,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,GAAG,EAAE,OAAO,CAAC,GAAG,CAAC,GAAG,IAAI,EAAE,CAAC;QAChD,8FAA8F;QAC9F,6FAA6F;QAC7F,uEAAuE;QACvE,MAAM,MAAM,GAAG,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,IAAI,EAAE,CAAC,CAAC,CAAC;QACvE,OAAO,GAAG,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;IACnC,CAAC;IACD,OAAO,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,GAAG,EAAE,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC;AACzD,CAAC;AAID;;;;GAIG;AACH,MAAM,CAAC,MAAM,uBAAuB,GAAG,IAAI,CAAC;AAE5C,iHAAiH;AACjH,MAAM,WAAW,GAAG,CAAC,GAAW,EAAU,EAAE;IAC1C,MAAM,OAAO,GAAG,SAAS,CAAC,GAAG,CAAC,CAAC;IAC/B,OAAO,OAAO,KAAK,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC;AAClD,CAAC,CAAC;AAmBF;;;;;;;;GAQG;AACH,MAAM,UAAU,mBAAmB,CAAC,OAAsB,EAAE,IAAkB;IAC5E,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,GAAG,OAAO,CAAC,OAAO,CAAC;IAC9C,MAAM,KAAK,GAAG,IAAI,CAAC,QAAQ,CAAC;IAC5B,MAAM,MAAM,GAAyB,EAAE,CAAC;IACxC,KAAK,IAAI,KAAK,GAAG,CAAC,EAAE,KAAK,GAAG,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,KAAK,IAAI,CAAC,EAAE,CAAC;QAC/D,MAAM,MAAM,GAAG,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAE,IAAI,CAAC,MAAM,CAAC,KAAK,CAAY,GAAG,KAAK,CAAC;QACxE,MAAM,IAAI,GAAG,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAE,IAAI,CAAC,MAAM,CAAC,KAAK,GAAG,CAAC,CAAY,GAAG,KAAK,CAAC;QAC1E,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,GAAG,KAAK,GAAG,MAAM,CAAC;QACjD,MAAM,KAAK,GAAG,OAAO,CAAC,OAAO,GAAG,KAAK,GAAG,IAAI,CAAC;QAC7C,MAAM,OAAO,GAAG,WAAW,CAAC,KAAK,GAAG,OAAO,CAAC,CAAC;QAC7C,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,GAAG,OAAO,CAAC,GAAG,IAAI,CAAC,EAAE,GAAG,uBAAuB,CAAC;QAC/E,MAAM,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,CAAC,CAAC;IACnE,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,eAAe,CAAC,OAAgB,EAAE,IAAkB;IAClE,QAAQ,OAAO,CAAC,IAAI,EAAE,CAAC;QACrB,KAAK,WAAW,CAAC,CAAC,CAAC;YACjB,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;YAC9B,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,oBAAoB,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC,CAAC;YACvE,OAAO,CAAC,CAAC,EAAE,EAAE;gBACX,MAAM,KAAK,GAAG,YAAY,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;gBACtC,OAAQ,KAAK,CAAC,KAAK,CAA2B,CAAC,CAAC,CAAC,CAAC;YACpD,CAAC,CAAC;QACJ,CAAC;QACD,KAAK,SAAS;YACZ,OAAO,CAAC,CAAC,EAAE,EAAE,CAAC,cAAc,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;QACxC,KAAK,iBAAiB;YACpB,OAAO,CAAC,CAAC,EAAE,EAAE,CAAC,SAAS,CAAC,cAAc,CAAC,IAAI,EAAE,CAAC,CAAC,GAAG,IAAI,CAAC,EAAE,CAAC,CAAC;QAC7D,KAAK,UAAU;YACb,OAAO,GAAG,EAAE,CAAC,SAAS,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;QAC7C,KAAK,QAAQ,CAAC,CAAC,CAAC;YACd,MAAM,MAAM,GAAG,mBAAmB,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;YAClD,OAAO,CAAC,CAAC,EAAE,EAAE;gBACX,MAAM,OAAO,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;gBAC1C,MAAM,KAAK,GACT,MAAM,CAAC,IAAI,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,OAAO,IAAI,SAAS,CAAC,IAAI,CAAC,IAAK,MAAM,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,CAAwB,CAAC;gBAC7G,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,GAAG,KAAK,CAAC,MAAM,CAAC;gBACvC,MAAM,KAAK,GAAG,IAAI,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,GAAG,KAAK,CAAC,MAAM,CAAC,GAAG,IAAI,CAAC;gBAC9D,OAAO,SAAS,CAAC,KAAK,CAAC,OAAO,GAAG,KAAK,CAAC,OAAO,GAAG,KAAK,CAAC,CAAC;YAC1D,CAAC,CAAC;QACJ,CAAC;QACD,KAAK,WAAW;YACd,OAAO,CAAC,CAAC,EAAE,EAAE;gBACX,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;gBAC9B,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,GAAG,GAAG,KAAK,CAAC,GAAG,EAAE,OAAO,CAAC,GAAG,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC;gBAC7E,OAAO,SAAS,CAAC,OAAO,GAAG,CAAC,OAAO,CAAC,SAAS,IAAI,CAAC,CAAC,CAAC,CAAC;YACvD,CAAC,CAAC;IACN,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,UAAU,CAAC,OAAgB,EAAE,IAAkB,EAAE,CAAS;IACxE,OAAO,eAAe,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;AAC3C,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,YAAY,CAAC,MAA+B,EAAE,CAAS;IACrE,KAAK,IAAI,KAAK,GAAG,CAAC,EAAE,KAAK,GAAG,MAAM,CAAC,MAAM,EAAE,KAAK,IAAI,CAAC,EAAE,CAAC;QACtD,IAAI,CAAC,IAAK,MAAM,CAAC,KAAK,CAAkB,CAAC,IAAI,GAAG,KAAK;YAAE,OAAO,KAAK,CAAC;IACtE,CAAC;IACD,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;AACxC,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,oBAAoB,CAAC,KAAmB,EAAE,IAAkB;IAC1E,MAAM,OAAO,GAAiB,KAAK,CAAC,OAAO,CAAC;IAC5C,IAAI,OAAO,CAAC,IAAI,KAAK,QAAQ;QAAE,OAAO,eAAe,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;IACrE,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,GAAG,KAAK,CAAC,MAAM,CAAC;IACvC,MAAM,OAAO,GAAG,WAAW,CAAC,OAAO,CAAC,KAAK,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC7D,OAAO,CAAC,CAAC,EAAE,EAAE;QACX,MAAM,KAAK,GAAG,IAAI,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,KAAK,CAAC,MAAM,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC;QAClF,OAAO,SAAS,CAAC,OAAO,CAAC,OAAO,GAAG,OAAO,GAAG,KAAK,CAAC,CAAC;IACtD,CAAC,CAAC;AACJ,CAAC;AAED,6FAA6F;AAC7F,MAAM,CAAC,MAAM,iBAAiB,GAAG,IAAI,CAAC;AAEtC;;;;;GAKG;AACH,MAAM,UAAU,YAAY,CAAC,MAA+B;IAC1D,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,+CAA+C,CAAC;IAChF,MAAM,KAAK,GAAG,MAAM,CAAC,CAAC,CAAiB,CAAC;IACxC,IAAI,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,iBAAiB,EAAE,CAAC;QAC/C,OAAO,+BAA+B,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,8DAA8D,CAAC;IAC3H,CAAC;IACD,KAAK,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,EAAE,EAAE,CAAC;QAC9C,MAAM,KAAK,GAAG,SAAS,MAAM,CAAC,KAAK,GAAG,CAAC,CAAC,EAAE,CAAC;QAC3C,IAAI,KAAK,CAAC,IAAI,IAAI,KAAK,CAAC,MAAM,GAAG,iBAAiB,EAAE,CAAC;YACnD,OAAO,GAAG,KAAK,cAAc,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,qCAAqC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC;QAC9G,CAAC;QACD,MAAM,IAAI,GAAG,MAAM,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC;QAC/B,IAAI,IAAI,KAAK,SAAS;YAAE,SAAS;QACjC,MAAM,GAAG,GAAG,IAAI,CAAC,MAAM,GAAG,KAAK,CAAC,IAAI,CAAC;QACrC,IAAI,GAAG,GAAG,iBAAiB,EAAE,CAAC;YAC5B,OAAO,GAAG,KAAK,cAAc,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,cAAc,MAAM,CAAC,KAAK,GAAG,CAAC,CAAC,cAAc,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,kCAAkC,CAAC;QACpJ,CAAC;QACD,IAAI,GAAG,GAAG,CAAC,iBAAiB,EAAE,CAAC;YAC7B,OAAO,GAAG,KAAK,cAAc,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,cAAc,MAAM,CAAC,KAAK,GAAG,CAAC,CAAC,uBAAuB,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,4BAA4B,CAAC;QACvJ,CAAC;IACH,CAAC;IACD,MAAM,IAAI,GAAG,MAAM,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,CAAiB,CAAC;IACvD,IAAI,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,GAAG,CAAC,CAAC,GAAG,iBAAiB,EAAE,CAAC;QAChD,OAAO,4BAA4B,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,2EAA2E,CAAC;IAClI,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC"}
@@ -0,0 +1,48 @@
1
+ import type { Heading, HeadingRange, RangeHeading } from "@horizon36596/zenith-schema";
2
+ /**
3
+ * Pure edits on a `piecewise` heading's ranges (site/docs/file-format.md, "Piecewise heading"): turn a
4
+ * heading into ranges, split a range at a t, move the boundary between two ranges, delete a range and
5
+ * change one range's mode. The inspector and the canvas both call these, and every one keeps the
6
+ * ranges covering 0 to 1 in order with no gap and no overlap, so an edit can never produce the
7
+ * HEADING_RANGES error; only a hand-edited file can.
8
+ *
9
+ * t is the step's arc-length fraction, as everywhere else in a heading.
10
+ */
11
+ type Piecewise = Extract<Heading, {
12
+ mode: "piecewise";
13
+ }>;
14
+ /** The narrowest range an edit may leave, as a fraction of the path. */
15
+ export declare const MIN_RANGE_T = 0.02;
16
+ /**
17
+ * The same heading as one range from 0 to 1, or the heading itself when it is piecewise already.
18
+ * A step with no heading starts as one tangent range, the editor's default mode.
19
+ */
20
+ export declare function toPiecewise(heading: Heading | undefined): Piecewise;
21
+ /** The range that holds `t` strictly inside it, or -1 when t is on a boundary or outside 0..1. */
22
+ export declare function rangeContaining(heading: Piecewise, t: number): number;
23
+ /**
24
+ * Splits the range that holds `t` into two at `t`. Both halves keep the range's mode; a `linear`
25
+ * range is cut at the heading it passes through at `t`, so the robot turns exactly as before until
26
+ * either half is changed. Returns the heading unchanged when either half would be narrower than
27
+ * `MIN_RANGE_T`.
28
+ */
29
+ export declare function splitRange(heading: Piecewise, t: number): Piecewise;
30
+ /**
31
+ * Moves the boundary between range `index` and range `index + 1` to `t`, clamped so neither range
32
+ * gets narrower than `MIN_RANGE_T`. The two ranges keep their modes and values.
33
+ */
34
+ export declare function moveBoundary(heading: Piecewise, index: number, t: number): Piecewise;
35
+ /**
36
+ * Deletes range `index`; the range before it grows to cover the gap, or the one after it when it is
37
+ * the first. The last range cannot be deleted: a piecewise heading needs one.
38
+ */
39
+ export declare function deleteRange(heading: Piecewise, index: number): Piecewise;
40
+ /** Replaces range `index`'s own heading, keeping where it starts and ends. */
41
+ export declare function setRangeHeading(heading: Piecewise, index: number, inner: RangeHeading): Piecewise;
42
+ /**
43
+ * A heading of `mode` for a range, carrying over the angle the range already holds where the new
44
+ * mode has one: a linear range starts where the old range started and ends where it ended.
45
+ */
46
+ export declare function rangeHeadingForMode(mode: RangeHeading["mode"], range: HeadingRange): RangeHeading;
47
+ export {};
48
+ //# sourceMappingURL=headingRanges.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"headingRanges.d.ts","sourceRoot":"","sources":["../src/headingRanges.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAEvF;;;;;;;;GAQG;AAEH,KAAK,SAAS,GAAG,OAAO,CAAC,OAAO,EAAE;IAAE,IAAI,EAAE,WAAW,CAAA;CAAE,CAAC,CAAC;AAEzD,wEAAwE;AACxE,eAAO,MAAM,WAAW,OAAO,CAAC;AAiBhC;;;GAGG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE,OAAO,GAAG,SAAS,GAAG,SAAS,CAInE;AAED,kGAAkG;AAClG,wBAAgB,eAAe,CAAC,OAAO,EAAE,SAAS,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAErE;AAED;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,SAAS,EAAE,CAAC,EAAE,MAAM,GAAG,SAAS,CAoBnE;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,SAAS,CAcpF;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,GAAG,SAAS,CAcxE;AAED,8EAA8E;AAC9E,wBAAgB,eAAe,CAAC,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,YAAY,GAAG,SAAS,CAMjG;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,YAAY,CAAC,MAAM,CAAC,EAAE,KAAK,EAAE,YAAY,GAAG,YAAY,CAajG"}