@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,195 @@
1
+ import { robotLimits } from "../kinematics.js";
2
+ /**
3
+ * Where every number the instant sim runs on comes from, in one place, with the order of
4
+ * preference stated: the caller's `options`, then `robot.json`, then a default. Each default is
5
+ * either Pedro Pathing v3's own `ForesightConfig` default (read from the pinned sources jar, see
6
+ * site/docs/simulation.md) or derived from a number `robot.json` already carries, and
7
+ * says which. None is a number made up for the purpose (CLAUDE.md rule 4).
8
+ *
9
+ * The follower gains, the drivetrain geometry and each condition's `ledger` meaning are optional
10
+ * keys in `robot.json` (`followerSchema` and `drivetrainSchema` in `packages/schema/src/robot.ts`,
11
+ * site/docs/file-format.md). This file still reads them structurally, so a key the schema adds
12
+ * later takes effect without a change here.
13
+ */
14
+ /** The default tick for the preview, in seconds. */
15
+ export const DEFAULT_TICK_S = 0.01;
16
+ /** SimLoop's `HeadlessOpModeRunner.TICK_SECONDS`: a Gradle headless sim's 50 Hz loop. */
17
+ export const GRADLE_TICK_S = 0.02;
18
+ /**
19
+ * Seconds the sim runs past the auto period before it gives up on a routine that has not finished:
20
+ * the same margin a Gradle headless sim gives a 30 s period (1700 ticks of 0.02 s is 34 s).
21
+ */
22
+ export const PERIOD_MARGIN_S = 4;
23
+ /** When the field declares no auto period, the sim stops here. FTC's auto period is 30 s. */
24
+ export const DEFAULT_PERIOD_S = 30;
25
+ /** SimLoop's `FakeMotor.EPS`. */
26
+ export const STOP_SNAP_FRACTION = 0.02;
27
+ /** `MecanumConfig.powerThreshold`'s default in Pedro's revhub. */
28
+ export const POWER_THRESHOLD = 0.01;
29
+ /**
30
+ * Pedro's `ForesightConfig` defaults, for the knobs Pedro gives a default to. SPEC: copied from
31
+ * `com/pedropathing/algorithm/ForesightConfig.java` in core-3.0.0-20260828.185437-17.
32
+ */
33
+ export const PEDRO_DEFAULTS = {
34
+ headingPowerPerRad: 1.5,
35
+ headingStaticPower: 0,
36
+ forwardTranslationalPowerPerIn: 0.07,
37
+ strafeTranslationalPowerPerIn: 0.1,
38
+ brakeFeedforwardPowerPerInPerS: 0.009,
39
+ coastPowerPerInPerS: 0.025,
40
+ coastFeedforwardPowerPerInPerS: 0.015,
41
+ maxBrakingPower: 0.2,
42
+ headingDriveRatio: 0.5,
43
+ cosineScale: true,
44
+ turnBeforeDriving: false,
45
+ brakeAggression: 1,
46
+ endParametricT: 0.025,
47
+ endHeadingToleranceRad: 0.007,
48
+ endTranslationalToleranceIn: 0.1,
49
+ endVelocityToleranceInPerS: 0.1,
50
+ holdTimeoutS: 0.1,
51
+ headingDeviationToleranceRad: (11.25 * Math.PI) / 180,
52
+ translationalDeviationToleranceIn: 2.5,
53
+ minCorrectionDistanceIn: 1e-3,
54
+ };
55
+ const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
56
+ /** A `{ value, provenance }` number or a bare number under `source[key]`, when there is one. */
57
+ function numberAt(source, key) {
58
+ if (!isRecord(source))
59
+ return undefined;
60
+ const raw = source[key];
61
+ if (typeof raw === "number" && Number.isFinite(raw))
62
+ return raw;
63
+ if (isRecord(raw) && typeof raw["value"] === "number" && Number.isFinite(raw["value"])) {
64
+ return raw["value"];
65
+ }
66
+ return undefined;
67
+ }
68
+ function booleanAt(source, key) {
69
+ if (!isRecord(source))
70
+ return undefined;
71
+ const raw = source[key];
72
+ if (typeof raw === "boolean")
73
+ return raw;
74
+ if (isRecord(raw) && typeof raw["value"] === "boolean")
75
+ return raw["value"];
76
+ return undefined;
77
+ }
78
+ /** `kinematics.follower` as a plain record, so keys the schema does not type yet can be read. */
79
+ const followerSection = (robot) => robot.kinematics["follower"];
80
+ /** `kinematics.drivetrain`, the plant section the handoff asks for. */
81
+ const drivetrainSection = (robot) => robot.kinematics["drivetrain"];
82
+ /**
83
+ * The follower the sim runs. Pedro's defaults except where a Pedro default would make the preview
84
+ * meaningless for any robot that has not been tuned, and there the value is derived:
85
+ *
86
+ * - `coastFeedforwardPowerPerInPerS` defaults to 1 / maxForwardVel, the power that holds a target
87
+ * velocity on this robot's measured top speed (a robot with a 100 in/s top speed gets 0.01).
88
+ * Pedro's 0.015 would push past full power at any target, so `speedFraction` would do nothing.
89
+ * - `coastPowerPerInPerS` defaults to 0 for the same reason: V3 feeds the coast controller the
90
+ * target velocity as its "error", so a P term there is a second feedforward.
91
+ * - The brake coefficients, which Pedro makes `required()` and so has no default for, come from the
92
+ * measured coasting deceleration: stopping distance v^2 / 2a, linear term 0. APPROX: the real
93
+ * coefficients come from Pedro's braking identification OpModes and are shorter, because they
94
+ * brake under reverse power rather than coast.
95
+ */
96
+ export function followerParams(robot, options = {}) {
97
+ const k = robot.kinematics;
98
+ const section = followerSection(robot);
99
+ const forwardVel = k.maxForwardVelInPerS.value;
100
+ const leverArm = plantLeverArmIn(robot);
101
+ const read = (key, fallback) => options.follower?.[key] ?? numberAt(section, key) ?? fallback;
102
+ const flag = (key, fallback) => options.follower?.[key] ?? booleanAt(section, key) ?? fallback;
103
+ const forwardDecel = k.forwardDecelInPerS2.value;
104
+ const strafeDecel = k.strafeDecelInPerS2.value;
105
+ // The heading's natural deceleration is the forward one acting at the lever arm, the same
106
+ // geometry the plant turns with.
107
+ const headingDecel = forwardDecel / leverArm;
108
+ return {
109
+ forwardTranslationalPowerPerIn: read("forwardTranslationalPowerPerIn", PEDRO_DEFAULTS.forwardTranslationalPowerPerIn),
110
+ strafeTranslationalPowerPerIn: read("strafeTranslationalPowerPerIn", PEDRO_DEFAULTS.strafeTranslationalPowerPerIn),
111
+ headingPowerPerRad: read("headingPowerPerRad", PEDRO_DEFAULTS.headingPowerPerRad),
112
+ headingStaticPower: read("headingStaticPower", PEDRO_DEFAULTS.headingStaticPower),
113
+ coastPowerPerInPerS: read("coastPowerPerInPerS", 0),
114
+ coastFeedforwardPowerPerInPerS: read("coastFeedforwardPowerPerInPerS", 1 / forwardVel),
115
+ brakeFeedforwardPowerPerInPerS: read("brakeFeedforwardPowerPerInPerS", PEDRO_DEFAULTS.brakeFeedforwardPowerPerInPerS),
116
+ maxBrakingPower: read("maxBrakingPower", PEDRO_DEFAULTS.maxBrakingPower),
117
+ headingDriveRatio: read("headingDriveRatio", PEDRO_DEFAULTS.headingDriveRatio),
118
+ cosineScale: flag("cosineScale", PEDRO_DEFAULTS.cosineScale),
119
+ turnBeforeDriving: flag("turnBeforeDriving", PEDRO_DEFAULTS.turnBeforeDriving),
120
+ brakeAggression: read("brakeAggression", PEDRO_DEFAULTS.brakeAggression),
121
+ brakeLinearForwardS: read("brakeLinearForwardS", 0),
122
+ brakeQuadraticForwardS2PerIn: read("brakeQuadraticForwardS2PerIn", 1 / (2 * forwardDecel)),
123
+ brakeLinearStrafeS: read("brakeLinearStrafeS", 0),
124
+ brakeQuadraticStrafeS2PerIn: read("brakeQuadraticStrafeS2PerIn", 1 / (2 * strafeDecel)),
125
+ headingBrakeLinearS: read("headingBrakeLinearS", 0),
126
+ headingBrakeQuadraticS2PerRad: read("headingBrakeQuadraticS2PerRad", 1 / (2 * headingDecel)),
127
+ endParametricT: read("endParametricT", PEDRO_DEFAULTS.endParametricT),
128
+ endHeadingToleranceRad: read("endHeadingToleranceRad", PEDRO_DEFAULTS.endHeadingToleranceRad),
129
+ endTranslationalToleranceIn: read("endTranslationalToleranceIn", PEDRO_DEFAULTS.endTranslationalToleranceIn),
130
+ endVelocityToleranceInPerS: read("endVelocityToleranceInPerS", PEDRO_DEFAULTS.endVelocityToleranceInPerS),
131
+ holdTimeoutS: read("holdTimeoutS", PEDRO_DEFAULTS.holdTimeoutS),
132
+ headingDeviationToleranceRad: read("headingDeviationToleranceRad", PEDRO_DEFAULTS.headingDeviationToleranceRad),
133
+ translationalDeviationToleranceIn: read("translationalDeviationToleranceIn", PEDRO_DEFAULTS.translationalDeviationToleranceIn),
134
+ minCorrectionDistanceIn: read("minCorrectionDistanceIn", PEDRO_DEFAULTS.minCorrectionDistanceIn),
135
+ };
136
+ }
137
+ /**
138
+ * The mecanum lever arm: half the wheel base plus half the track width from
139
+ * `kinematics.drivetrain` when the file carries them, otherwise the arm that makes a full turn
140
+ * command reach `maxAngularVelRadPerS`, so the plant's angular limit is the one `robot.json` states.
141
+ */
142
+ export function plantLeverArmIn(robot) {
143
+ const section = drivetrainSection(robot);
144
+ const trackWidth = numberAt(section, "trackWidthIn");
145
+ const wheelBase = numberAt(section, "wheelBaseIn");
146
+ if (trackWidth !== undefined && wheelBase !== undefined && trackWidth + wheelBase > 0) {
147
+ return wheelBase / 2 + trackWidth / 2;
148
+ }
149
+ return robot.kinematics.maxForwardVelInPerS.value / robot.kinematics.maxAngularVelRadPerS.value;
150
+ }
151
+ /**
152
+ * The plant. The wheel response rate is `kinematics.drivetrain.wheelResponseRatePerS` when present
153
+ * (a robot's own Gradle sim sets the same rate), otherwise 2 * accel / maxForwardVel:
154
+ * the rate at which a first-order wheel loses the same time to reaching top speed, V / (2a), as the
155
+ * constant-acceleration estimate does, so the two agree about what `accelInPerS2` means.
156
+ */
157
+ export function plantParams(robot, field, fidelity, options = {}) {
158
+ const limits = robotLimits(robot);
159
+ const section = drivetrainSection(robot);
160
+ const defaults = {
161
+ maxForwardVelInPerS: limits.maxForwardVelInPerS,
162
+ maxStrafeVelInPerS: Math.min(limits.maxStrafeVelInPerS, limits.maxForwardVelInPerS),
163
+ wheelResponseRatePerS: numberAt(section, "wheelResponseRatePerS") ??
164
+ (2 * limits.accelInPerS2) / limits.maxForwardVelInPerS,
165
+ leverArmIn: plantLeverArmIn(robot),
166
+ forwardDecelInPerS2: limits.forwardDecelInPerS2,
167
+ strafeDecelInPerS2: limits.strafeDecelInPerS2,
168
+ coast: fidelity === "gradle" ? "firstOrder" : "natural",
169
+ stopSnapFraction: STOP_SNAP_FRACTION,
170
+ powerThreshold: POWER_THRESHOLD,
171
+ fieldHalfIn: Math.min(field.sizeIn.xIn, field.sizeIn.yIn) / 2,
172
+ };
173
+ return { ...defaults, ...definedOnly(options.plant) };
174
+ }
175
+ function definedOnly(partial) {
176
+ const out = {};
177
+ if (partial === undefined)
178
+ return out;
179
+ for (const [key, value] of Object.entries(partial)) {
180
+ if (value !== undefined)
181
+ out[key] = value;
182
+ }
183
+ return out;
184
+ }
185
+ /** The tick, the fidelity and the stopping time, resolved. */
186
+ export function runParams(field, options) {
187
+ const fidelity = options.fidelity ?? "robot";
188
+ const tickS = options.tickS ?? (fidelity === "gradle" ? GRADLE_TICK_S : DEFAULT_TICK_S);
189
+ if (!(tickS > 0) || !Number.isFinite(tickS))
190
+ throw new RangeError("tickS must be a positive number of seconds.");
191
+ const periodS = field.periods?.autoS ?? null;
192
+ const maxTimeS = options.maxTimeS ?? (periodS ?? DEFAULT_PERIOD_S) + PERIOD_MARGIN_S;
193
+ return { tickS, fidelity, maxTimeS, periodS };
194
+ }
195
+ //# sourceMappingURL=params.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"params.js","sourceRoot":"","sources":["../../src/sim/params.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,41 @@
1
+ import type { Heading } from "@horizon36596/zenith-schema";
2
+ import { type SimCurve, type XY } from "./curves.js";
3
+ /** `Angle.normalize`: into [0, 2pi). */
4
+ export declare function normalizeAngle(rad: number): number;
5
+ /** `Angle.normalizeSigned`: into [-pi, pi). */
6
+ export declare function normalizeSigned(rad: number): number;
7
+ /** `Angle.turnDirection(normalizedError)`: +1 counter-clockwise, -1 clockwise. */
8
+ export declare function turnDirection(error: number): number;
9
+ /** `Vector2D.theta`: 0 for a zero vector. */
10
+ export declare const theta: (v: XY) => number;
11
+ export interface SimSegment {
12
+ readonly curve: SimCurve;
13
+ /** The target heading at this segment's parametric t, in [0, 2pi) like Pedro's. */
14
+ heading(t: number): number;
15
+ }
16
+ export interface SimPath {
17
+ readonly segments: readonly SimSegment[];
18
+ /** `PathBuilder.Built.segmentLengthsIn`: what the markers measure distance with. */
19
+ readonly runtimeLengthsIn: readonly number[];
20
+ readonly runtimeTotalIn: number;
21
+ /** `PathTracker.endPose`: the last segment's end with its heading at t = 1. */
22
+ readonly endPose: {
23
+ x: number;
24
+ y: number;
25
+ heading: number;
26
+ };
27
+ /** Segments dropped because they had no length, which Pedro's `Line` refuses. */
28
+ readonly droppedSegments: number;
29
+ }
30
+ /**
31
+ * The segment t at which the runtime's chord table reaches `fraction` of the segment's length:
32
+ * `PathBuilder.parameterAt`, 64 chords, linear inside a chord. Exact for a line.
33
+ */
34
+ export declare function parameterAtFraction(curve: SimCurve, fraction: number): number;
35
+ /**
36
+ * Builds the followable path from each segment's control polygon (start, controls, end), in the
37
+ * alliance frame the plan is in. `fallbackHeadingRad` is held when the step has no heading mode,
38
+ * which `validate` refuses but the sim still has to drive.
39
+ */
40
+ export declare function buildSimPath(polygons: readonly (readonly XY[])[], heading: Heading | undefined, fallbackHeadingRad: number): SimPath;
41
+ //# sourceMappingURL=pedroPath.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pedroPath.d.ts","sourceRoot":"","sources":["../../src/sim/pedroPath.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAA8B,MAAM,6BAA6B,CAAC;AACvF,OAAO,EAAwC,KAAK,QAAQ,EAAE,KAAK,EAAE,EAAE,MAAM,aAAa,CAAC;AAW3F,wCAAwC;AACxC,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAGlD;AAED,+CAA+C;AAC/C,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAGnD;AAED,kFAAkF;AAClF,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEnD;AAED,6CAA6C;AAC7C,eAAO,MAAM,KAAK,GAAI,GAAG,EAAE,KAAG,MAAmE,CAAC;AAElG,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,KAAK,EAAE,QAAQ,CAAC;IACzB,mFAAmF;IACnF,OAAO,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;CAC5B;AAED,MAAM,WAAW,OAAO;IACtB,QAAQ,CAAC,QAAQ,EAAE,SAAS,UAAU,EAAE,CAAC;IACzC,oFAAoF;IACpF,QAAQ,CAAC,gBAAgB,EAAE,SAAS,MAAM,EAAE,CAAC;IAC7C,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,+EAA+E;IAC/E,QAAQ,CAAC,OAAO,EAAE;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;IAC5D,iFAAiF;IACjF,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;CAClC;AAsDD;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAsB7E;AAgED;;;;GAIG;AACH,wBAAgB,YAAY,CAC1B,QAAQ,EAAE,SAAS,CAAC,SAAS,EAAE,EAAE,CAAC,EAAE,EACpC,OAAO,EAAE,OAAO,GAAG,SAAS,EAC5B,kBAAkB,EAAE,MAAM,GACzB,OAAO,CAqCT"}
@@ -0,0 +1,191 @@
1
+ import { makeSimCurve, RUNTIME_LENGTH_SAMPLES } from "./curves.js";
2
+ /**
3
+ * A path step as the robot runtime hands it to Pedro: `PathBuilder.build` and `withHeading` in
4
+ * `robot/auto-runtime` (the copy the biobuzz repository runs), then Pedro's own interpolators from
5
+ * `paths/interpolator/Interpolator.java`. The follower advances segment by segment, the way
6
+ * Pedro's `PathTracker` does, and each segment evaluates its heading at its own parametric t.
7
+ */
8
+ const TWO_PI = Math.PI * 2;
9
+ /** `Angle.normalize`: into [0, 2pi). */
10
+ export function normalizeAngle(rad) {
11
+ const angle = rad % TWO_PI;
12
+ return angle < 0 ? angle + TWO_PI : angle;
13
+ }
14
+ /** `Angle.normalizeSigned`: into [-pi, pi). */
15
+ export function normalizeSigned(rad) {
16
+ const angle = normalizeAngle(rad);
17
+ return angle >= Math.PI ? angle - TWO_PI : angle;
18
+ }
19
+ /** `Angle.turnDirection(normalizedError)`: +1 counter-clockwise, -1 clockwise. */
20
+ export function turnDirection(error) {
21
+ return error >= 0 && error <= Math.PI ? 1 : -1;
22
+ }
23
+ /** `Vector2D.theta`: 0 for a zero vector. */
24
+ export const theta = (v) => (v.x * v.x + v.y * v.y < 1e-9 ? 0 : Math.atan2(v.y, v.x));
25
+ /**
26
+ * One segment's heading, exactly as `PathBuilder.withHeading` attaches it and Pedro evaluates it.
27
+ *
28
+ * Two places where that differs from `packages/core/src/heading.ts`, both recorded in the ADR:
29
+ *
30
+ * - `linear`: the runtime splits the authored sweep by arc length and hands each piece to
31
+ * `Path.linear(from, to)`, but Pedro's `Interpolator.linear` takes the short way from `from` to
32
+ * `to` (`Angle.error`), so a piece over half a turn reverses. It is also linear in the segment's
33
+ * parametric t, not in arc length.
34
+ * - `facePoint`: the runtime passes only the point, so `offsetRad` is dropped.
35
+ * - `piecewise`: see `piecewiseSegmentHeading`.
36
+ */
37
+ function segmentHeading(heading, curve, startFraction, endFraction, fallbackRad) {
38
+ if (heading === undefined) {
39
+ const fixed = normalizeAngle(fallbackRad);
40
+ return () => fixed;
41
+ }
42
+ switch (heading.mode) {
43
+ case "tangent":
44
+ return (t) => normalizeAngle(theta(curve.tangent(t)));
45
+ case "tangentReversed":
46
+ return (t) => normalizeAngle(theta(curve.tangent(t)) + Math.PI);
47
+ case "constant": {
48
+ const fixed = normalizeAngle(heading.headingRad);
49
+ return () => fixed;
50
+ }
51
+ case "linear": {
52
+ const sweep = heading.toRad - heading.fromRad;
53
+ const from = normalizeAngle(heading.fromRad + sweep * startFraction);
54
+ const to = normalizeAngle(heading.fromRad + sweep * endFraction);
55
+ const delta = normalizeSigned(to - from);
56
+ return (t) => normalizeAngle(from + delta * t);
57
+ }
58
+ case "facePoint": {
59
+ const px = heading.xIn;
60
+ const py = heading.yIn;
61
+ return (t) => {
62
+ const at = curve.get(t);
63
+ return normalizeAngle(theta({ x: px - at.x, y: py - at.y }));
64
+ };
65
+ }
66
+ case "piecewise":
67
+ return piecewiseSegmentHeading(heading.ranges, curve, startFraction, endFraction, fallbackRad);
68
+ }
69
+ }
70
+ /**
71
+ * The segment t at which the runtime's chord table reaches `fraction` of the segment's length:
72
+ * `PathBuilder.parameterAt`, 64 chords, linear inside a chord. Exact for a line.
73
+ */
74
+ export function parameterAtFraction(curve, fraction) {
75
+ if (fraction <= 0)
76
+ return 0;
77
+ if (fraction >= 1)
78
+ return 1;
79
+ const cumulative = [0];
80
+ let previous = curve.get(0);
81
+ for (let index = 1; index <= RUNTIME_LENGTH_SAMPLES; index += 1) {
82
+ const point = curve.get(index / RUNTIME_LENGTH_SAMPLES);
83
+ cumulative.push(cumulative[index - 1] + Math.hypot(point.x - previous.x, point.y - previous.y));
84
+ previous = point;
85
+ }
86
+ const total = cumulative[RUNTIME_LENGTH_SAMPLES];
87
+ if (total <= 0)
88
+ return fraction;
89
+ const target = fraction * total;
90
+ for (let index = 1; index <= RUNTIME_LENGTH_SAMPLES; index += 1) {
91
+ const upper = cumulative[index];
92
+ if (upper >= target) {
93
+ const lower = cumulative[index - 1];
94
+ const within = upper === lower ? 0 : (target - lower) / (upper - lower);
95
+ return (index - 1 + within) / RUNTIME_LENGTH_SAMPLES;
96
+ }
97
+ }
98
+ return 1;
99
+ }
100
+ /**
101
+ * A `piecewise` heading on one segment, the way `PathBuilder.piecewiseFor` builds it: the ranges
102
+ * that cross this segment are clipped to it, their ends are turned from the step's arc-length
103
+ * fractions into this segment's own t through the chord table, and Pedro's `PiecewiseInterpolator`
104
+ * picks the first range whose end is at or past t. A `linear` range turns the short way between its
105
+ * headings at the two clipped ends, linear in the segment's t; every other mode is Pedro's own.
106
+ */
107
+ function piecewiseSegmentHeading(ranges, curve, startFraction, endFraction, fallbackRad) {
108
+ const span = endFraction - startFraction;
109
+ const pieces = [];
110
+ let previousT = 0;
111
+ for (const range of ranges) {
112
+ const from = Math.max(range.startT, startFraction);
113
+ const to = Math.min(range.endT, endFraction);
114
+ if (to <= from && !(span <= 0 && range === ranges[ranges.length - 1]))
115
+ continue;
116
+ const localFrom = span <= 0 ? 0 : parameterAtFraction(curve, (from - startFraction) / span);
117
+ const localTo = span <= 0 ? 1 : parameterAtFraction(curve, (to - startFraction) / span);
118
+ if (localTo <= previousT)
119
+ continue;
120
+ pieces.push({ endT: localTo, evaluate: rangeSegmentHeading(range, curve, from, to, localFrom, localTo, fallbackRad) });
121
+ previousT = localTo;
122
+ }
123
+ if (pieces.length === 0) {
124
+ const fixed = normalizeAngle(fallbackRad);
125
+ return () => fixed;
126
+ }
127
+ pieces[pieces.length - 1].endT = 1;
128
+ return (t) => {
129
+ const piece = pieces.find((candidate) => t <= candidate.endT) ?? pieces[pieces.length - 1];
130
+ return piece.evaluate(t);
131
+ };
132
+ }
133
+ function rangeSegmentHeading(range, curve, from, to, localFrom, localTo, fallbackRad) {
134
+ const inner = range.heading;
135
+ if (inner.mode !== "linear")
136
+ return segmentHeading(inner, curve, 0, 1, fallbackRad);
137
+ const rangeSpan = range.endT - range.startT;
138
+ const turn = normalizeSigned(inner.toRad - inner.fromRad);
139
+ const at = (fraction) => inner.fromRad + turn * (rangeSpan <= 0 ? 1 : (fraction - range.startT) / rangeSpan);
140
+ const headFrom = normalizeAngle(at(from));
141
+ const delta = at(to) - at(from);
142
+ const localSpan = localTo - localFrom;
143
+ return (t) => {
144
+ const local = localSpan <= 0 ? 1 : Math.min(1, Math.max(0, (t - localFrom) / localSpan));
145
+ return normalizeAngle(headFrom + delta * local);
146
+ };
147
+ }
148
+ /**
149
+ * Builds the followable path from each segment's control polygon (start, controls, end), in the
150
+ * alliance frame the plan is in. `fallbackHeadingRad` is held when the step has no heading mode,
151
+ * which `validate` refuses but the sim still has to drive.
152
+ */
153
+ export function buildSimPath(polygons, heading, fallbackHeadingRad) {
154
+ const kept = polygons.filter((points) => {
155
+ const first = points[0];
156
+ const last = points[points.length - 1];
157
+ if (first === undefined || last === undefined)
158
+ return false;
159
+ if (points.length > 2)
160
+ return true;
161
+ return Math.hypot(last.x - first.x, last.y - first.y) > 0;
162
+ });
163
+ const curves = kept.map((points) => makeSimCurve(points));
164
+ const lengths = curves.map((curve) => curve.runtimeLengthIn);
165
+ const total = lengths.reduce((sum, value) => sum + value, 0);
166
+ let travelled = 0;
167
+ const segments = curves.map((curve, index) => {
168
+ const startFraction = total === 0 ? 0 : travelled / total;
169
+ travelled += lengths[index];
170
+ const endFraction = total === 0 ? 1 : travelled / total;
171
+ return {
172
+ curve,
173
+ heading: segmentHeading(heading, curve, startFraction, endFraction, fallbackHeadingRad),
174
+ };
175
+ });
176
+ const last = segments[segments.length - 1];
177
+ const lastPolygon = polygons[polygons.length - 1];
178
+ const endPoint = last?.curve.end ?? lastPolygon?.[lastPolygon.length - 1] ?? { x: 0, y: 0 };
179
+ return {
180
+ segments,
181
+ runtimeLengthsIn: lengths,
182
+ runtimeTotalIn: total,
183
+ endPose: {
184
+ x: endPoint.x,
185
+ y: endPoint.y,
186
+ heading: last === undefined ? normalizeAngle(fallbackHeadingRad) : last.heading(1),
187
+ },
188
+ droppedSegments: polygons.length - kept.length,
189
+ };
190
+ }
191
+ //# sourceMappingURL=pedroPath.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pedroPath.js","sourceRoot":"","sources":["../../src/sim/pedroPath.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,59 @@
1
+ import type { DrivePowers, MotionState } from "./follower.js";
2
+ import type { PlantParams } from "./types.js";
3
+ /**
4
+ * The simulated chassis, matched to the robot repository's headless sim: four
5
+ * `SimLoop/.../fakehardware/FakeMotor` first-order wheels, `MecanumDrivePlant`'s mounting-sign
6
+ * bookkeeping (which cancels and so is left out), and `MecanumPoseIntegrator`'s forward kinematics
7
+ * with its roller-slip factor on the lateral velocity. The powers reach the wheels the way Pedro's
8
+ * `Mecanum.applyDrive` and `CachedMotor.setPower` write them.
9
+ *
10
+ * One deliberate difference, and only with `coast: "natural"` (the `robot` fidelity): when every
11
+ * wheel is unpowered the chassis loses speed at the measured natural deceleration per axis, the
12
+ * model Pedro's own `getCoastDisplacement` uses, instead of at the first-order rate. The Gradle
13
+ * plant uses one rate for both, which `BiobuzzSimConfig.MAX_ACCEL`'s javadoc records as sheding
14
+ * 387 in/s^2 at zero power where the robot measured 89.
15
+ */
16
+ export interface PlantState {
17
+ x: number;
18
+ /** Field-frame y, inches. */
19
+ y: number;
20
+ /** Unwrapped, like `MecanumPoseIntegrator.heading`. */
21
+ heading: number;
22
+ /** Wheel speeds as fractions of full speed, [FL, FR, BL, BR]. */
23
+ speeds: [number, number, number, number];
24
+ /** The powers the motors were last given, as `CachedMotor` holds them. */
25
+ powers: [number, number, number, number];
26
+ }
27
+ export declare const copyPlantState: (state: PlantState) => PlantState;
28
+ export declare class MecanumPlant {
29
+ readonly params: PlantParams;
30
+ readonly lateralEfficiency: number;
31
+ state: PlantState;
32
+ constructor(params: PlantParams, x: number, y: number, heading: number);
33
+ /**
34
+ * `Mecanum.applyDrive`: wheel powers, normalised so none exceeds 1, each through
35
+ * `CachedMotor.setPower`, which skips a change smaller than the threshold unless the sign flips.
36
+ */
37
+ drive(powers: DrivePowers): void;
38
+ /** `Mecanum.stop`. */
39
+ stop(): void;
40
+ private setPower;
41
+ /** The chassis velocity the wheels give right now: body-frame forward, left, and turn rate. */
42
+ bodyVelocity(): {
43
+ vx: number;
44
+ vy: number;
45
+ omega: number;
46
+ };
47
+ /** The true motion state, as a localizer that measures velocity would report it. */
48
+ motion(): MotionState;
49
+ /** One tick: the wheels chase their powers, then the pose integrates the chassis velocity. */
50
+ update(dt: number): void;
51
+ /**
52
+ * Every wheel unpowered on a floating drivetrain: each body axis sheds speed at its own measured
53
+ * rate, forward and strafe from `robot.json`, rotation at the forward rate over the lever arm.
54
+ * The wheel speeds are then set back from the body velocity, which is exact because the
55
+ * forward kinematics lose only the mode a mecanum cannot drive.
56
+ */
57
+ private coastNaturally;
58
+ }
59
+ //# sourceMappingURL=plant.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plant.d.ts","sourceRoot":"","sources":["../../src/sim/plant.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAE9D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAE9C;;;;;;;;;;;;GAYG;AAEH,MAAM,WAAW,UAAU;IACzB,CAAC,EAAE,MAAM,CAAC;IACV,6BAA6B;IAC7B,CAAC,EAAE,MAAM,CAAC;IACV,uDAAuD;IACvD,OAAO,EAAE,MAAM,CAAC;IAChB,iEAAiE;IACjE,MAAM,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;IACzC,0EAA0E;IAC1E,MAAM,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;CAC1C;AAED,eAAO,MAAM,cAAc,GAAI,OAAO,UAAU,KAAG,UAMjD,CAAC;AAEH,qBAAa,YAAY;IACvB,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAC;IAC7B,QAAQ,CAAC,iBAAiB,EAAE,MAAM,CAAC;IACnC,KAAK,EAAE,UAAU,CAAC;gBAEN,MAAM,EAAE,WAAW,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM;IAMtE;;;OAGG;IACH,KAAK,CAAC,MAAM,EAAE,WAAW,GAAG,IAAI;IAUhC,sBAAsB;IACtB,IAAI,IAAI,IAAI;IAIZ,OAAO,CAAC,QAAQ;IAShB,+FAA+F;IAC/F,YAAY,IAAI;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE;IAUzD,oFAAoF;IACpF,MAAM,IAAI,WAAW;IAiBrB,8FAA8F;IAC9F,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI;IA4BxB;;;;;OAKG;IACH,OAAO,CAAC,cAAc;CAmBvB"}
@@ -0,0 +1,128 @@
1
+ import { normalizeAngle } from "./pedroPath.js";
2
+ export const copyPlantState = (state) => ({
3
+ x: state.x,
4
+ y: state.y,
5
+ heading: state.heading,
6
+ speeds: [...state.speeds],
7
+ powers: [...state.powers],
8
+ });
9
+ export class MecanumPlant {
10
+ params;
11
+ lateralEfficiency;
12
+ state;
13
+ constructor(params, x, y, heading) {
14
+ this.params = params;
15
+ this.lateralEfficiency = params.maxStrafeVelInPerS / params.maxForwardVelInPerS;
16
+ this.state = { x, y, heading, speeds: [0, 0, 0, 0], powers: [0, 0, 0, 0] };
17
+ }
18
+ /**
19
+ * `Mecanum.applyDrive`: wheel powers, normalised so none exceeds 1, each through
20
+ * `CachedMotor.setPower`, which skips a change smaller than the threshold unless the sign flips.
21
+ */
22
+ drive(powers) {
23
+ const f = powers.forward;
24
+ const s = powers.strafe;
25
+ const t = powers.turn;
26
+ const wheels = [f - s - t, f + s + t, f + s - t, f - s + t];
27
+ let max = 1;
28
+ for (const wheel of wheels)
29
+ max = Math.max(max, Math.abs(wheel));
30
+ for (let i = 0; i < 4; i += 1)
31
+ this.setPower(i, wheels[i] / max);
32
+ }
33
+ /** `Mecanum.stop`. */
34
+ stop() {
35
+ for (let i = 0; i < 4; i += 1)
36
+ this.setPower(i, 0);
37
+ }
38
+ setPower(wheel, power) {
39
+ if (!Number.isFinite(power))
40
+ return;
41
+ const desired = power < -1 ? -1 : power > 1 ? 1 : power;
42
+ const last = this.state.powers[wheel];
43
+ if (Math.abs(last - desired) >= this.params.powerThreshold || Math.sign(last) !== Math.sign(desired)) {
44
+ this.state.powers[wheel] = desired;
45
+ }
46
+ }
47
+ /** The chassis velocity the wheels give right now: body-frame forward, left, and turn rate. */
48
+ bodyVelocity() {
49
+ const [fl, fr, bl, br] = this.state.speeds;
50
+ const v = this.params.maxForwardVelInPerS;
51
+ return {
52
+ vx: (v * (fl + fr + bl + br)) / 4,
53
+ vy: ((v * (-fl + fr + bl - br)) / 4) * this.lateralEfficiency,
54
+ omega: (v * (-fl + fr - bl + br)) / (4 * this.params.leverArmIn),
55
+ };
56
+ }
57
+ /** The true motion state, as a localizer that measures velocity would report it. */
58
+ motion() {
59
+ const body = this.bodyVelocity();
60
+ const h = this.state.heading;
61
+ const cos = Math.cos(h);
62
+ const sin = Math.sin(h);
63
+ return {
64
+ x: this.state.x,
65
+ y: this.state.y,
66
+ heading: normalizeAngle(h),
67
+ vx: body.vx * cos - body.vy * sin,
68
+ vy: body.vx * sin + body.vy * cos,
69
+ omega: body.omega,
70
+ twistX: body.vx,
71
+ twistY: body.vy,
72
+ };
73
+ }
74
+ /** One tick: the wheels chase their powers, then the pose integrates the chassis velocity. */
75
+ update(dt) {
76
+ const p = this.params;
77
+ const st = this.state;
78
+ const unpowered = st.powers.every((power) => Math.abs(power) < p.stopSnapFraction);
79
+ if (p.coast === "natural" && unpowered) {
80
+ this.coastNaturally(dt);
81
+ }
82
+ else {
83
+ for (let i = 0; i < 4; i += 1) {
84
+ const power = st.powers[i];
85
+ let speed = st.speeds[i];
86
+ speed += (power - speed) * p.wheelResponseRatePerS * dt;
87
+ if (Math.abs(speed) < p.stopSnapFraction && Math.abs(power) < p.stopSnapFraction)
88
+ speed = 0;
89
+ st.speeds[i] = speed;
90
+ }
91
+ }
92
+ const body = this.bodyVelocity();
93
+ const cos = Math.cos(st.heading);
94
+ const sin = Math.sin(st.heading);
95
+ st.x += (body.vx * cos - body.vy * sin) * dt;
96
+ st.y += (body.vx * sin + body.vy * cos) * dt;
97
+ st.heading += body.omega * dt;
98
+ const half = p.fieldHalfIn;
99
+ st.x = Math.max(-half, Math.min(half, st.x));
100
+ st.y = Math.max(-half, Math.min(half, st.y));
101
+ }
102
+ /**
103
+ * Every wheel unpowered on a floating drivetrain: each body axis sheds speed at its own measured
104
+ * rate, forward and strafe from `robot.json`, rotation at the forward rate over the lever arm.
105
+ * The wheel speeds are then set back from the body velocity, which is exact because the
106
+ * forward kinematics lose only the mode a mecanum cannot drive.
107
+ */
108
+ coastNaturally(dt) {
109
+ const p = this.params;
110
+ const body = this.bodyVelocity();
111
+ const shed = (value, rate) => {
112
+ const step = rate * dt;
113
+ return Math.abs(value) <= step ? 0 : value - Math.sign(value) * step;
114
+ };
115
+ const vx = shed(body.vx, p.forwardDecelInPerS2);
116
+ const vy = shed(body.vy, p.strafeDecelInPerS2);
117
+ const omega = shed(body.omega, p.forwardDecelInPerS2 / p.leverArmIn);
118
+ const v = p.maxForwardVelInPerS;
119
+ const wheelsVy = vy / this.lateralEfficiency;
120
+ const turn = omega * p.leverArmIn;
121
+ const speeds = this.state.speeds;
122
+ speeds[0] = (vx - wheelsVy - turn) / v;
123
+ speeds[1] = (vx + wheelsVy + turn) / v;
124
+ speeds[2] = (vx + wheelsVy - turn) / v;
125
+ speeds[3] = (vx - wheelsVy + turn) / v;
126
+ }
127
+ }
128
+ //# sourceMappingURL=plant.js.map