@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,81 @@
1
+ import { type Auto, type Field, type Pose, type Waypoints } from "@horizon36596/zenith-schema";
2
+ import type { Vec2 } from "./geometry/vec.js";
3
+ /**
4
+ * Mirroring a routine to the other alliance, the way `field.json` declares the field is symmetric
5
+ * (`alliances.mirror`). BIOBUZZ, like most FTC games, is point symmetric about the field centre.
6
+ *
7
+ * Every mode is its own inverse: mirroring twice gives the document back, which is the property the
8
+ * editor's "flip alliance" button rests on and what `05` section 6 asks to be tested.
9
+ *
10
+ * A pose that lives in `waypoints.json` is a special case. There is one set of waypoints, in the
11
+ * canonical alliance's frame, and the robot runtime resolves a reference to those numbers and then
12
+ * mirrors the pose only when the file's alliance is not the one it is running as. So a mirrored
13
+ * file that kept `{ "ref": "a" }` would drive to `a` itself, where the routine run as the other
14
+ * alliance drives to the mirror of `a`. `mirrorAuto` therefore writes a referenced pose out as the
15
+ * mirrored numbers when it is given the waypoints, which is what a caller saving the result must do.
16
+ */
17
+ export type MirrorMode = "pointSymmetry" | "mirrorX" | "mirrorY" | "none";
18
+ /**
19
+ * The symmetry this field declares between the two alliances. `field.json` is the only place that
20
+ * says it, and every renderer asks here rather than assuming point symmetry.
21
+ */
22
+ export declare const mirrorForField: (field: Field) => MirrorMode;
23
+ /**
24
+ * Whether a routine has to be mirrored to be drawn for `viewAlliance`.
25
+ *
26
+ * A file's poses are in the frame of the file's own `alliance` (site/docs/file-format.md), so the
27
+ * rule is the robot runtime's (`AutoFile.mirrorsInto`): mirror if and only if the alliance being
28
+ * viewed is not the file's. A BLUE file viewed as BLUE is drawn as it is, where the robot running it
29
+ * as BLUE drives; viewed as RED it is mirrored. `field.frame.canonicalAlliance` plays no part: it
30
+ * says which alliance the field file's own rules (`startRules`) are written for, not which frame a
31
+ * file is in. The field itself, its picture and its obstacles, is never mirrored.
32
+ *
33
+ * `render` and the editor's canvas both ask this one function, which is what keeps the SVG and the
34
+ * canvas drawing the same file the same way. A caller that draws a mirrored view has to put every
35
+ * point it writes back through the same mirror (every kind is its own inverse), so what is drawn
36
+ * is what is stored.
37
+ *
38
+ * `viewAlliance` is what a caller is asking to see; omit it to mean the routine's own alliance.
39
+ */
40
+ export declare function shouldMirror(auto: Auto, field: Field, viewAlliance?: Auto["alliance"]): boolean;
41
+ /** `mirrorX` reflects across the x axis, so y changes sign; `mirrorY` reflects across the y axis. */
42
+ export declare function mirrorVec(point: Vec2, mode: MirrorMode): Vec2;
43
+ /**
44
+ * The mirrored heading, and its own inverse: mirroring twice gives back the exact number the file
45
+ * carried, for every heading canonical form can write, half turns included.
46
+ *
47
+ * The one value that cannot round-trip is a heading at exactly +/-pi in full double precision, which
48
+ * is the same pose either way and which four-decimal canonical form never writes.
49
+ */
50
+ export declare function mirrorHeadingRad(headingRad: number, mode: MirrorMode): number;
51
+ /** The mirrored pose, keeping the provenance: the number came from the same place either way. */
52
+ export declare function mirrorPose(pose: Pose, mode: MirrorMode): Pose;
53
+ /**
54
+ * +1 when a mirror keeps the way a turn goes, -1 when it reverses it. A point symmetry is a half
55
+ * turn of the whole field, so a counter-clockwise turn stays counter-clockwise; a reflection makes
56
+ * it clockwise.
57
+ */
58
+ export declare function mirrorTurnSign(mode: MirrorMode): 1 | -1;
59
+ /** The other alliance's name for a field id; `null` when there is none. */
60
+ export type MirrorIds = (id: string) => string | null;
61
+ /**
62
+ * The same routine for the other alliance: every pose mirrored and the alliance flipped. Mirroring
63
+ * twice gives the document back, byte for byte, when the document names no waypoint or no
64
+ * `waypoints` is passed.
65
+ *
66
+ * `waypoints` is the project's `waypoints.json`, and a caller that is going to write the result out
67
+ * has to pass it too. With it, every `{ "ref": name }` pose is written as the mirrored numbers of
68
+ * that waypoint, so the saved file drives where the robot runtime drives the original run as the
69
+ * other alliance (the module comment says why a kept reference would not). That step is one way: the
70
+ * reference does not come back when the result is mirrored again. Left out, references are carried
71
+ * across unchanged, which is only safe for a result that is never saved or run. A name missing from
72
+ * `waypoints` is refused.
73
+ *
74
+ * `mirrorId` is the season's `SeasonRules.mirrorId` hook, and a caller that is going to write the
75
+ * result out has to pass it: a step's `expect.collectFrom` and `expect.launchesInto` name field
76
+ * elements by id, and `hiveRedUpCell` means the RED hive whichever alliance the file claims. Left
77
+ * out, those ids are carried across unchanged, which is only safe for a preview that never reads
78
+ * them back — the editor's "mirror to the other alliance" is a view toggle and does exactly that.
79
+ */
80
+ export declare function mirrorAuto(auto: Auto, mode?: MirrorMode, mirrorId?: MirrorIds, waypoints?: Waypoints): Auto;
81
+ //# sourceMappingURL=mirror.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mirror.d.ts","sourceRoot":"","sources":["../src/mirror.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,IAAI,EAET,KAAK,KAAK,EACV,KAAK,IAAI,EAIT,KAAK,SAAS,EACf,MAAM,6BAA6B,CAAC;AAErC,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,mBAAmB,CAAC;AAE9C;;;;;;;;;;;;;GAaG;AACH,MAAM,MAAM,UAAU,GAAG,eAAe,GAAG,SAAS,GAAG,SAAS,GAAG,MAAM,CAAC;AAE1E;;;GAGG;AACH,eAAO,MAAM,cAAc,GAAI,OAAO,KAAK,KAAG,UAAgC,CAAC;AAE/E;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,YAAY,CAC1B,IAAI,EAAE,IAAI,EACV,KAAK,EAAE,KAAK,EACZ,YAAY,CAAC,EAAE,IAAI,CAAC,UAAU,CAAC,GAC9B,OAAO,CAGT;AAED,qGAAqG;AACrG,wBAAgB,SAAS,CAAC,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,UAAU,GAAG,IAAI,CAW7D;AAuCD;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,GAAG,MAAM,CAe7E;AAED,iGAAiG;AACjG,wBAAgB,UAAU,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,UAAU,GAAG,IAAI,CAQ7D;AAgGD;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,UAAU,GAAG,CAAC,GAAG,CAAC,CAAC,CAEvD;AAwDD,2EAA2E;AAC3E,MAAM,MAAM,SAAS,GAAG,CAAC,EAAE,EAAE,MAAM,KAAK,MAAM,GAAG,IAAI,CAAC;AAEtD;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,UAAU,CACxB,IAAI,EAAE,IAAI,EACV,IAAI,GAAE,UAA4B,EAClC,QAAQ,CAAC,EAAE,SAAS,EACpB,SAAS,CAAC,EAAE,SAAS,GACpB,IAAI,CAYN"}
package/dist/mirror.js ADDED
@@ -0,0 +1,282 @@
1
+ import { childLists, withChildLists, } from "@horizon36596/zenith-schema";
2
+ import { wrapAngle } from "./geometry/angle.js";
3
+ /**
4
+ * The symmetry this field declares between the two alliances. `field.json` is the only place that
5
+ * says it, and every renderer asks here rather than assuming point symmetry.
6
+ */
7
+ export const mirrorForField = (field) => field.frame.mirror;
8
+ /**
9
+ * Whether a routine has to be mirrored to be drawn for `viewAlliance`.
10
+ *
11
+ * A file's poses are in the frame of the file's own `alliance` (site/docs/file-format.md), so the
12
+ * rule is the robot runtime's (`AutoFile.mirrorsInto`): mirror if and only if the alliance being
13
+ * viewed is not the file's. A BLUE file viewed as BLUE is drawn as it is, where the robot running it
14
+ * as BLUE drives; viewed as RED it is mirrored. `field.frame.canonicalAlliance` plays no part: it
15
+ * says which alliance the field file's own rules (`startRules`) are written for, not which frame a
16
+ * file is in. The field itself, its picture and its obstacles, is never mirrored.
17
+ *
18
+ * `render` and the editor's canvas both ask this one function, which is what keeps the SVG and the
19
+ * canvas drawing the same file the same way. A caller that draws a mirrored view has to put every
20
+ * point it writes back through the same mirror (every kind is its own inverse), so what is drawn
21
+ * is what is stored.
22
+ *
23
+ * `viewAlliance` is what a caller is asking to see; omit it to mean the routine's own alliance.
24
+ */
25
+ export function shouldMirror(auto, field, viewAlliance) {
26
+ if (mirrorForField(field) === "none")
27
+ return false;
28
+ return (viewAlliance ?? auto.alliance) !== auto.alliance;
29
+ }
30
+ /** `mirrorX` reflects across the x axis, so y changes sign; `mirrorY` reflects across the y axis. */
31
+ export function mirrorVec(point, mode) {
32
+ switch (mode) {
33
+ case "pointSymmetry":
34
+ return { xIn: -point.xIn, yIn: -point.yIn };
35
+ case "mirrorX":
36
+ return { xIn: point.xIn, yIn: -point.yIn };
37
+ case "mirrorY":
38
+ return { xIn: -point.xIn, yIn: point.yIn };
39
+ default:
40
+ return { xIn: point.xIn, yIn: point.yIn };
41
+ }
42
+ }
43
+ /**
44
+ * How far past the (-pi, pi] boundary a heading may sit and still be read as the half turn it is.
45
+ *
46
+ * Canonical form rounds radians to four decimals, so a half turn is written 3.1416 or -3.1416, both
47
+ * a hair outside the interval. Wrapping one of those to the other side is what stopped mirroring
48
+ * twice from giving the document back: the pose was right and the bytes were not. Half of the last
49
+ * canonical digit is far more than the 7.4e-6 involved and far less than anything a heading means.
50
+ */
51
+ const HALF_TURN_TOLERANCE_RAD = 5e-5;
52
+ const inCanonicalRange = (rad) => rad > -Math.PI - HALF_TURN_TOLERANCE_RAD && rad <= Math.PI + HALF_TURN_TOLERANCE_RAD;
53
+ /**
54
+ * A half turn from `rad`: the same angle either way round, so the one that stays inside the
55
+ * canonical range is chosen, and `preferNegative` decides when both do.
56
+ *
57
+ * Both do exactly when `rad` is a hair either side of zero, whose two half-turn partners are the
58
+ * two spellings of a half turn, +3.1416 and -3.1416. Which one to write back is the whole question
59
+ * this function exists to answer: picking by the side of zero the caller came from is what makes
60
+ * mirroring its own inverse there, rather than turning every half turn positive.
61
+ */
62
+ function halfTurn(rad, preferNegative) {
63
+ const up = rad + Math.PI;
64
+ const down = rad - Math.PI;
65
+ const upFits = inCanonicalRange(up);
66
+ const downFits = inCanonicalRange(down);
67
+ if (upFits && downFits)
68
+ return preferNegative ? down : up;
69
+ if (upFits)
70
+ return up;
71
+ if (downFits)
72
+ return down;
73
+ // A heading written far outside the canonical range; there is no spelling to preserve.
74
+ return wrapAngle(up);
75
+ }
76
+ /** Negation without producing -0, which canonical form would write as 0 anyway. */
77
+ const negate = (rad) => (rad === 0 ? 0 : -rad);
78
+ /**
79
+ * The mirrored heading, and its own inverse: mirroring twice gives back the exact number the file
80
+ * carried, for every heading canonical form can write, half turns included.
81
+ *
82
+ * The one value that cannot round-trip is a heading at exactly +/-pi in full double precision, which
83
+ * is the same pose either way and which four-decimal canonical form never writes.
84
+ */
85
+ export function mirrorHeadingRad(headingRad, mode) {
86
+ switch (mode) {
87
+ case "pointSymmetry":
88
+ return halfTurn(headingRad, headingRad < 0);
89
+ case "mirrorX":
90
+ // Reflecting across the x axis is exact negation, which is its own inverse for every value,
91
+ // boundary included. Wrapping the result is what broke that at -pi.
92
+ return negate(headingRad);
93
+ case "mirrorY":
94
+ // Reflecting across the y axis is pi - h, which is the negation taken a half turn round.
95
+ return halfTurn(negate(headingRad), headingRad > 0);
96
+ default:
97
+ // "none": the field declares no symmetry, so the heading is the heading.
98
+ return headingRad;
99
+ }
100
+ }
101
+ /** The mirrored pose, keeping the provenance: the number came from the same place either way. */
102
+ export function mirrorPose(pose, mode) {
103
+ const point = mirrorVec(pose, mode);
104
+ return {
105
+ ...pose,
106
+ xIn: point.xIn,
107
+ yIn: point.yIn,
108
+ ...(pose.headingRad === undefined ? {} : { headingRad: mirrorHeadingRad(pose.headingRad, mode) }),
109
+ };
110
+ }
111
+ /**
112
+ * A pose source for the other alliance. A waypoint reference becomes the mirrored waypoint's numbers
113
+ * when `waypoints` is given, and is left as it is when not; see the module comment.
114
+ */
115
+ function mirrorSource(source, mode, waypoints) {
116
+ if (source === "current")
117
+ return source;
118
+ if (!("ref" in source))
119
+ return mirrorPose(source, mode);
120
+ if (waypoints === undefined)
121
+ return source;
122
+ const waypoint = waypoints.waypoints[source.ref];
123
+ if (waypoint === undefined) {
124
+ throw new Error(`This routine cannot be mirrored: it names the waypoint ${JSON.stringify(source.ref)}, which is not in waypoints.json.`);
125
+ }
126
+ return mirrorPose({ xIn: waypoint.xIn, yIn: waypoint.yIn, headingRad: waypoint.headingRad }, mode);
127
+ }
128
+ const mirrorSegment = (segment, mode, waypoints) => segment.kind === "bezier"
129
+ ? {
130
+ ...segment,
131
+ from: mirrorSource(segment.from, mode, waypoints),
132
+ control: segment.control.map((point) => mirrorPose(point, mode)),
133
+ to: mirrorSource(segment.to, mode, waypoints),
134
+ }
135
+ : {
136
+ ...segment,
137
+ from: mirrorSource(segment.from, mode, waypoints),
138
+ to: mirrorSource(segment.to, mode, waypoints),
139
+ };
140
+ /**
141
+ * The other alliance's name for a field id, from the season's `mirrorId` hook.
142
+ *
143
+ * Mirroring is refused rather than guessed: an id with no counterpart would leave a BLUE routine
144
+ * expecting to launch into the RED hive, and LEGAL_APPROACH, TURRET_RANGE and the ledger would all
145
+ * then be about the wrong side of the field.
146
+ */
147
+ function mirrorExpect(step, mirrorId) {
148
+ const expected = step.expect;
149
+ if (expected === undefined || mirrorId === undefined)
150
+ return expected;
151
+ const other = (id) => {
152
+ if (id === undefined)
153
+ return undefined;
154
+ const mapped = mirrorId(id);
155
+ if (mapped === null) {
156
+ throw new Error(`This routine cannot be mirrored: ${JSON.stringify(id)} has no counterpart on the other alliance.`);
157
+ }
158
+ return mapped;
159
+ };
160
+ return {
161
+ ...expected,
162
+ ...(expected.collectFrom === undefined ? {} : { collectFrom: other(expected.collectFrom) }),
163
+ ...(expected.launchesInto === undefined ? {} : { launchesInto: other(expected.launchesInto) }),
164
+ };
165
+ }
166
+ function mirrorStep(step, mode, mirrorId, waypoints) {
167
+ switch (step.kind) {
168
+ case "path": {
169
+ // Markers are placed by t or by distance along the path, so they mirror with it untouched.
170
+ const heading = step.heading;
171
+ const expected = mirrorExpect(step, mirrorId);
172
+ return {
173
+ ...step,
174
+ segments: step.segments.map((segment) => mirrorSegment(segment, mode, waypoints)),
175
+ ...(heading === undefined ? {} : { heading: mirrorHeading(heading, mode) }),
176
+ ...(expected === undefined ? {} : { expect: expected }),
177
+ };
178
+ }
179
+ case "command": {
180
+ const expected = mirrorExpect(step, mirrorId);
181
+ return expected === undefined ? step : { ...step, expect: expected };
182
+ }
183
+ case "sequence":
184
+ case "parallel":
185
+ case "branch":
186
+ return withChildLists(step, childLists(step).map((list) => list.map((child) => mirrorStep(child, mode, mirrorId, waypoints))));
187
+ default:
188
+ return step;
189
+ }
190
+ }
191
+ /**
192
+ * +1 when a mirror keeps the way a turn goes, -1 when it reverses it. A point symmetry is a half
193
+ * turn of the whole field, so a counter-clockwise turn stays counter-clockwise; a reflection makes
194
+ * it clockwise.
195
+ */
196
+ export function mirrorTurnSign(mode) {
197
+ return mode === "mirrorX" || mode === "mirrorY" ? -1 : 1;
198
+ }
199
+ const TWO_PI = Math.PI * 2;
200
+ /**
201
+ * The mirrored end of a `linear` sweep. The sweep `toRad - fromRad` is what the robot shares out
202
+ * between segments (`linearHeadingPieces`), so it has to survive the mirror with its size and with
203
+ * its direction turned by `mirrorTurnSign`. Mirroring each end on its own keeps both in the
204
+ * canonical range, which moves the sweep by a whole turn whenever the two ends wrap differently:
205
+ * a 270 degree sweep split over two segments then turned 90 degrees the other way on the other
206
+ * alliance. So the end is the mirrored heading moved by whole turns until the sweep is right, which
207
+ * leaves it untouched, and the file unchanged, whenever the two already agree.
208
+ */
209
+ function mirrorSweepEnd(heading, fromRad, mode) {
210
+ const toRad = mirrorHeadingRad(heading.toRad, mode);
211
+ const sweep = mirrorTurnSign(mode) * (heading.toRad - heading.fromRad);
212
+ const turns = Math.round((sweep - (toRad - fromRad)) / TWO_PI);
213
+ return turns === 0 ? toRad : toRad + turns * TWO_PI;
214
+ }
215
+ function mirrorHeading(heading, mode) {
216
+ switch (heading.mode) {
217
+ case "constant":
218
+ return { ...heading, headingRad: mirrorHeadingRad(heading.headingRad, mode) };
219
+ case "linear": {
220
+ const fromRad = mirrorHeadingRad(heading.fromRad, mode);
221
+ return { ...heading, fromRad, toRad: mirrorSweepEnd(heading, fromRad, mode) };
222
+ }
223
+ case "facePoint": {
224
+ const point = mirrorVec({ xIn: heading.xIn, yIn: heading.yIn }, mode);
225
+ // The offset is measured from the line to the point, so it survives the mirror unchanged
226
+ // under point symmetry and changes sign under a reflection.
227
+ return {
228
+ ...heading,
229
+ xIn: point.xIn,
230
+ yIn: point.yIn,
231
+ ...(heading.offsetRad === undefined || mode === "pointSymmetry" || mode === "none"
232
+ ? {}
233
+ : { offsetRad: -heading.offsetRad }),
234
+ };
235
+ }
236
+ case "piecewise":
237
+ // The ranges are fractions of the path, which the mirror keeps; only their headings move.
238
+ return {
239
+ ...heading,
240
+ ranges: heading.ranges.map((range) => ({
241
+ ...range,
242
+ heading: mirrorHeading(range.heading, mode),
243
+ })),
244
+ };
245
+ default:
246
+ // `tangent` and `tangentReversed` follow the mirrored path on their own.
247
+ return heading;
248
+ }
249
+ }
250
+ /**
251
+ * The same routine for the other alliance: every pose mirrored and the alliance flipped. Mirroring
252
+ * twice gives the document back, byte for byte, when the document names no waypoint or no
253
+ * `waypoints` is passed.
254
+ *
255
+ * `waypoints` is the project's `waypoints.json`, and a caller that is going to write the result out
256
+ * has to pass it too. With it, every `{ "ref": name }` pose is written as the mirrored numbers of
257
+ * that waypoint, so the saved file drives where the robot runtime drives the original run as the
258
+ * other alliance (the module comment says why a kept reference would not). That step is one way: the
259
+ * reference does not come back when the result is mirrored again. Left out, references are carried
260
+ * across unchanged, which is only safe for a result that is never saved or run. A name missing from
261
+ * `waypoints` is refused.
262
+ *
263
+ * `mirrorId` is the season's `SeasonRules.mirrorId` hook, and a caller that is going to write the
264
+ * result out has to pass it: a step's `expect.collectFrom` and `expect.launchesInto` name field
265
+ * elements by id, and `hiveRedUpCell` means the RED hive whichever alliance the file claims. Left
266
+ * out, those ids are carried across unchanged, which is only safe for a preview that never reads
267
+ * them back — the editor's "mirror to the other alliance" is a view toggle and does exactly that.
268
+ */
269
+ export function mirrorAuto(auto, mode = "pointSymmetry", mirrorId, waypoints) {
270
+ return {
271
+ ...auto,
272
+ alliance: auto.alliance === "RED" ? "BLUE" : "RED",
273
+ start: {
274
+ ...auto.start,
275
+ // A start pose is a waypoint reference or a pose, never "current", and mirroring keeps it one
276
+ // of those two, so the result is still a start pose.
277
+ pose: mirrorSource(auto.start.pose, mode, waypoints),
278
+ },
279
+ steps: auto.steps.map((step) => mirrorStep(step, mode, mirrorId, waypoints)),
280
+ };
281
+ }
282
+ //# sourceMappingURL=mirror.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mirror.js","sourceRoot":"","sources":["../src/mirror.ts"],"names":[],"mappings":"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"}
package/dist/plan.d.ts ADDED
@@ -0,0 +1,29 @@
1
+ import type { Field, Robot } from "@horizon36596/zenith-schema";
2
+ import type { ResolvedAuto } from "./resolve.js";
3
+ import type { Plan, PlanStep } from "./types.js";
4
+ export interface PlanOptions {
5
+ /** Distance between footprint samples along a path, in inches. The spec's default is 2. */
6
+ sampleSpacingIn?: number;
7
+ /** Which footprint box to sweep. The checks use the expanded one. */
8
+ footprint?: "startIn" | "expandedIn";
9
+ /**
10
+ * Reuse the geometry of a step that has not structurally changed. On by default: dragging one
11
+ * waypoint in the editor replans one step and hands back the rest, which is what keeps the
12
+ * checks under a frame. Turn it off to measure the cold cost of planning.
13
+ */
14
+ memoize?: boolean;
15
+ }
16
+ /** Forgets every memoised step, for a caller that has edited a robot or field file in place. */
17
+ export declare function clearPlanCache(): void;
18
+ /**
19
+ * Turns a resolved auto into per-step geometry: the samples every 2 in of arc (plus the endpoints
20
+ * and every segment boundary) with the pose, the heading and the footprint at each
21
+ * (site/docs/paths-explained.md).
22
+ *
23
+ * Pure and deterministic: the same files always give the same samples, so the CLI, the editor and
24
+ * a reviewer see the same answer.
25
+ */
26
+ export declare function plan(resolved: ResolvedAuto, robot: Robot, field: Field, options?: PlanOptions): Plan;
27
+ /** Every step of a plan, parallel and branch children included, in order. */
28
+ export declare function flattenSteps(steps: readonly PlanStep[]): PlanStep[];
29
+ //# sourceMappingURL=plan.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plan.d.ts","sourceRoot":"","sources":["../src/plan.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,6BAA6B,CAAC;AAKhE,OAAO,KAAK,EAAE,YAAY,EAAgB,MAAM,cAAc,CAAC;AAC/D,OAAO,KAAK,EAAW,IAAI,EAAc,QAAQ,EAAE,MAAM,YAAY,CAAC;AAEtE,MAAM,WAAW,WAAW;IAC1B,2FAA2F;IAC3F,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,qEAAqE;IACrE,SAAS,CAAC,EAAE,SAAS,GAAG,YAAY,CAAC;IACrC;;;;OAIG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAgCD,gGAAgG;AAChG,wBAAgB,cAAc,IAAI,IAAI,CAErC;AAED;;;;;;;GAOG;AACH,wBAAgB,IAAI,CAClB,QAAQ,EAAE,YAAY,EACtB,KAAK,EAAE,KAAK,EACZ,KAAK,EAAE,KAAK,EACZ,OAAO,GAAE,WAAgB,GACxB,IAAI,CAiHN;AAED,6EAA6E;AAC7E,wBAAgB,YAAY,CAAC,KAAK,EAAE,SAAS,QAAQ,EAAE,GAAG,QAAQ,EAAE,CAOnE"}
package/dist/plan.js ADDED
@@ -0,0 +1,159 @@
1
+ import { footprintAt } from "./footprint.js";
2
+ import { makePath, sampleDistances, SAMPLE_SPACING_IN } from "./geometry/path.js";
3
+ import { headingFunction } from "./heading.js";
4
+ /**
5
+ * The memo of planned steps, keyed on the robot (footprints depend on it) and then on the step's
6
+ * structure. Planning is pure, so a step whose geometry, heading and start pose are unchanged has
7
+ * the same samples as last time; sweeping the footprint is the expensive part and this is what
8
+ * makes an edit cost one step rather than the whole routine.
9
+ *
10
+ * A hit hands back the same `PlanStep` object, so a plan's steps are to be read and not written to.
11
+ * The per-robot map is bounded, because an editor session drags a waypoint hundreds of times and
12
+ * every drag is a new key.
13
+ */
14
+ const MEMO_LIMIT = 1024;
15
+ const memo = new WeakMap();
16
+ /** The structure a planned step depends on, as a string; a miss costs a replan and nothing worse. */
17
+ const identityOf = (step) => ({
18
+ id: step.id,
19
+ kind: step.kind,
20
+ step: step.step,
21
+ startPose: step.startPose,
22
+ endPose: step.endPose,
23
+ segments: step.segments?.map((segment) => segment.pointsIn),
24
+ children: step.children?.map(identityOf),
25
+ });
26
+ /**
27
+ * A WeakMap cannot be emptied, so every key carries this number and raising it makes the next
28
+ * lookup miss for every robot.
29
+ */
30
+ let cacheEpoch = 0;
31
+ /** Forgets every memoised step, for a caller that has edited a robot or field file in place. */
32
+ export function clearPlanCache() {
33
+ cacheEpoch += 1;
34
+ }
35
+ /**
36
+ * Turns a resolved auto into per-step geometry: the samples every 2 in of arc (plus the endpoints
37
+ * and every segment boundary) with the pose, the heading and the footprint at each
38
+ * (site/docs/paths-explained.md).
39
+ *
40
+ * Pure and deterministic: the same files always give the same samples, so the CLI, the editor and
41
+ * a reviewer see the same answer.
42
+ */
43
+ export function plan(resolved, robot, field, options = {}) {
44
+ const spacingIn = options.sampleSpacingIn ?? SAMPLE_SPACING_IN;
45
+ const which = options.footprint ?? "expandedIn";
46
+ const findings = [...resolved.findings];
47
+ const perRobot = memo.get(robot) ?? new Map();
48
+ memo.set(robot, perRobot);
49
+ const staticSample = (pose) => {
50
+ const footprint = footprintAt(pose, robot, which);
51
+ return { sIn: 0, t: 0, pose, footprintIn: footprint.bodyIn, mouthsIn: footprint.mouthsIn };
52
+ };
53
+ const planned = (step) => {
54
+ if (step.kind === "path" && step.segments !== undefined && step.segments.length > 0) {
55
+ const geometry = makePath(step.segments.map((segment) => segment.pointsIn));
56
+ const heading = step.step.kind === "path" ? step.step.heading : undefined;
57
+ if (heading === undefined) {
58
+ findings.push({
59
+ severity: "error",
60
+ stepId: step.id,
61
+ code: "HEADING_MISSING",
62
+ message: "A path step needs a heading mode: Pedro throws at run time without an interpolator.",
63
+ });
64
+ }
65
+ // With no mode declared, hold the heading the step started at so the footprint sweep still
66
+ // means something; the HEADING_MISSING error above is what stops the file being valid.
67
+ const headingAt = heading === undefined
68
+ ? () => step.startPose.headingRad
69
+ : headingFunction(heading, geometry);
70
+ const samples = [];
71
+ for (const sIn of sampleDistances(geometry, spacingIn)) {
72
+ const t = geometry.tAtDistance(sIn);
73
+ const point = geometry.pointAtDistance(sIn);
74
+ const pose = { xIn: point.xIn, yIn: point.yIn, headingRad: headingAt(t) };
75
+ const footprint = footprintAt(pose, robot, which);
76
+ samples.push({
77
+ sIn,
78
+ t,
79
+ pose,
80
+ footprintIn: footprint.bodyIn,
81
+ mouthsIn: footprint.mouthsIn,
82
+ });
83
+ }
84
+ const last = samples[samples.length - 1];
85
+ const endPose = last === undefined ? step.endPose : last.pose;
86
+ return {
87
+ id: step.id,
88
+ kind: step.kind,
89
+ step: step.step,
90
+ startPose: samples[0]?.pose ?? step.startPose,
91
+ endPose,
92
+ lengthIn: geometry.lengthIn,
93
+ samples,
94
+ geometry,
95
+ segments: step.segments,
96
+ };
97
+ }
98
+ if (step.children !== undefined) {
99
+ const children = step.children.map(planStep);
100
+ return {
101
+ id: step.id,
102
+ kind: step.kind,
103
+ step: step.step,
104
+ startPose: step.startPose,
105
+ endPose: step.endPose,
106
+ lengthIn: children.reduce((sum, child) => sum + child.lengthIn, 0),
107
+ samples: [],
108
+ children,
109
+ };
110
+ }
111
+ return {
112
+ id: step.id,
113
+ kind: step.kind,
114
+ step: step.step,
115
+ startPose: step.startPose,
116
+ endPose: step.endPose,
117
+ lengthIn: 0,
118
+ samples: [staticSample(step.startPose)],
119
+ };
120
+ };
121
+ const planStep = (step) => {
122
+ if (options.memoize === false)
123
+ return planned(step);
124
+ const key = `${String(cacheEpoch)}|${which}|${String(spacingIn)}|${JSON.stringify(identityOf(step))}`;
125
+ const hit = perRobot.get(key);
126
+ if (hit !== undefined) {
127
+ findings.push(...hit.findings);
128
+ return hit.step;
129
+ }
130
+ const before = findings.length;
131
+ const fresh = planned(step);
132
+ perRobot.set(key, { step: fresh, findings: findings.slice(before) });
133
+ if (perRobot.size > MEMO_LIMIT) {
134
+ const oldest = perRobot.keys().next();
135
+ if (oldest.done !== true)
136
+ perRobot.delete(oldest.value);
137
+ }
138
+ return fresh;
139
+ };
140
+ return {
141
+ auto: resolved.auto,
142
+ robot,
143
+ field,
144
+ startPose: resolved.startPose,
145
+ steps: resolved.steps.map(planStep),
146
+ findings,
147
+ };
148
+ }
149
+ /** Every step of a plan, parallel and branch children included, in order. */
150
+ export function flattenSteps(steps) {
151
+ const out = [];
152
+ for (const step of steps) {
153
+ out.push(step);
154
+ if (step.children !== undefined)
155
+ out.push(...flattenSteps(step.children));
156
+ }
157
+ return out;
158
+ }
159
+ //# sourceMappingURL=plan.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plan.js","sourceRoot":"","sources":["../src/plan.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,24 @@
1
+ import type { Auto } from "@horizon36596/zenith-schema";
2
+ import type { Estimate, Finding, LedgerRow } from "./types.js";
3
+ /**
4
+ * The pull request body of site/docs/github.md: the name, what the
5
+ * routine does, the committed render, a table of steps with their estimates and strafe, the totals
6
+ * against the autonomous period, the ledger at the end and whether the simulator was run.
7
+ *
8
+ * It is built in core rather than in the CLI or the GitHub client so that `zenith propose`, the
9
+ * editor's Propose button and the review bot all write the same body, and so that the body can be
10
+ * tested without a network.
11
+ */
12
+ /** The FTC autonomous period, used when the caller does not pass the field's own number. */
13
+ export declare const DEFAULT_PERIOD_S = 30;
14
+ export interface PrBodyOptions {
15
+ /** Where the committed SVG lives, usually `autos/.renders/<name>.svg` on the work branch. */
16
+ renderUrl?: string;
17
+ /** One line about the simulator run, or nothing, which reads as "not run". */
18
+ simSummary?: string;
19
+ /** The autonomous period in seconds, from `field.periods.autoS`. */
20
+ periodS?: number;
21
+ }
22
+ /** The body of the pull request a proposal opens (`06` section 3). */
23
+ export declare function prBody(auto: Auto, estimate: Estimate | null, findings: readonly Finding[], ledgerRows: readonly LedgerRow[], options?: PrBodyOptions): string;
24
+ //# sourceMappingURL=prBody.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prBody.d.ts","sourceRoot":"","sources":["../src/prBody.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAQ,MAAM,6BAA6B,CAAC;AAE9D,OAAO,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,SAAS,EAAgB,MAAM,YAAY,CAAC;AAE7E;;;;;;;;GAQG;AAEH,4FAA4F;AAC5F,eAAO,MAAM,gBAAgB,KAAK,CAAC;AAEnC,MAAM,WAAW,aAAa;IAC5B,6FAA6F;IAC7F,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,8EAA8E;IAC9E,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,oEAAoE;IACpE,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAqED,sEAAsE;AACtE,wBAAgB,MAAM,CACpB,IAAI,EAAE,IAAI,EACV,QAAQ,EAAE,QAAQ,GAAG,IAAI,EACzB,QAAQ,EAAE,SAAS,OAAO,EAAE,EAC5B,UAAU,EAAE,SAAS,SAAS,EAAE,EAChC,OAAO,GAAE,aAAkB,GAC1B,MAAM,CAkDR"}