@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.
- package/LICENSE +21 -0
- package/NOTICE +18 -0
- package/README.md +16 -0
- package/dist/canonicalize.d.ts +25 -0
- package/dist/canonicalize.d.ts.map +1 -0
- package/dist/canonicalize.js +199 -0
- package/dist/canonicalize.js.map +1 -0
- package/dist/check.d.ts +26 -0
- package/dist/check.d.ts.map +1 -0
- package/dist/check.js +231 -0
- package/dist/check.js.map +1 -0
- package/dist/checks/heading.d.ts +17 -0
- package/dist/checks/heading.d.ts.map +1 -0
- package/dist/checks/heading.js +78 -0
- package/dist/checks/heading.js.map +1 -0
- package/dist/checks/intake.d.ts +28 -0
- package/dist/checks/intake.d.ts.map +1 -0
- package/dist/checks/intake.js +131 -0
- package/dist/checks/intake.js.map +1 -0
- package/dist/checks/motion.d.ts +28 -0
- package/dist/checks/motion.d.ts.map +1 -0
- package/dist/checks/motion.js +252 -0
- package/dist/checks/motion.js.map +1 -0
- package/dist/checks/schedule.d.ts +29 -0
- package/dist/checks/schedule.d.ts.map +1 -0
- package/dist/checks/schedule.js +166 -0
- package/dist/checks/schedule.js.map +1 -0
- package/dist/checks/season.d.ts +6 -0
- package/dist/checks/season.d.ts.map +1 -0
- package/dist/checks/season.js +126 -0
- package/dist/checks/season.js.map +1 -0
- package/dist/continuity.d.ts +38 -0
- package/dist/continuity.d.ts.map +1 -0
- package/dist/continuity.js +69 -0
- package/dist/continuity.js.map +1 -0
- package/dist/diff.d.ts +37 -0
- package/dist/diff.d.ts.map +1 -0
- package/dist/diff.js +258 -0
- package/dist/diff.js.map +1 -0
- package/dist/edit/command.d.ts +4 -0
- package/dist/edit/command.d.ts.map +1 -0
- package/dist/edit/command.js +16 -0
- package/dist/edit/command.js.map +1 -0
- package/dist/edit/errors.d.ts +9 -0
- package/dist/edit/errors.d.ts.map +1 -0
- package/dist/edit/errors.js +12 -0
- package/dist/edit/errors.js.map +1 -0
- package/dist/edit/fields.d.ts +40 -0
- package/dist/edit/fields.d.ts.map +1 -0
- package/dist/edit/fields.js +150 -0
- package/dist/edit/fields.js.map +1 -0
- package/dist/edit/finish.d.ts +15 -0
- package/dist/edit/finish.d.ts.map +1 -0
- package/dist/edit/finish.js +36 -0
- package/dist/edit/finish.js.map +1 -0
- package/dist/edit/groups.d.ts +66 -0
- package/dist/edit/groups.d.ts.map +1 -0
- package/dist/edit/groups.js +277 -0
- package/dist/edit/groups.js.map +1 -0
- package/dist/edit/ids.d.ts +39 -0
- package/dist/edit/ids.d.ts.map +1 -0
- package/dist/edit/ids.js +76 -0
- package/dist/edit/ids.js.map +1 -0
- package/dist/edit/index.d.ts +22 -0
- package/dist/edit/index.d.ts.map +1 -0
- package/dist/edit/index.js +22 -0
- package/dist/edit/index.js.map +1 -0
- package/dist/edit/meta.d.ts +15 -0
- package/dist/edit/meta.d.ts.map +1 -0
- package/dist/edit/meta.js +13 -0
- package/dist/edit/meta.js.map +1 -0
- package/dist/edit/path.d.ts +36 -0
- package/dist/edit/path.d.ts.map +1 -0
- package/dist/edit/path.js +135 -0
- package/dist/edit/path.js.map +1 -0
- package/dist/edit/steps.d.ts +53 -0
- package/dist/edit/steps.d.ts.map +1 -0
- package/dist/edit/steps.js +0 -0
- package/dist/edit/steps.js.map +1 -0
- package/dist/edit/template.d.ts +18 -0
- package/dist/edit/template.d.ts.map +1 -0
- package/dist/edit/template.js +30 -0
- package/dist/edit/template.js.map +1 -0
- package/dist/edit/timing.d.ts +11 -0
- package/dist/edit/timing.d.ts.map +1 -0
- package/dist/edit/timing.js +23 -0
- package/dist/edit/timing.js.map +1 -0
- package/dist/edit/tree.d.ts +32 -0
- package/dist/edit/tree.d.ts.map +1 -0
- package/dist/edit/tree.js +90 -0
- package/dist/edit/tree.js.map +1 -0
- package/dist/estimate.d.ts +48 -0
- package/dist/estimate.d.ts.map +1 -0
- package/dist/estimate.js +443 -0
- package/dist/estimate.js.map +1 -0
- package/dist/findingHelp.d.ts +18 -0
- package/dist/findingHelp.d.ts.map +1 -0
- package/dist/findingHelp.js +114 -0
- package/dist/findingHelp.js.map +1 -0
- package/dist/fix.d.ts +21 -0
- package/dist/fix.d.ts.map +1 -0
- package/dist/fix.js +180 -0
- package/dist/fix.js.map +1 -0
- package/dist/footprint.d.ts +33 -0
- package/dist/footprint.d.ts.map +1 -0
- package/dist/footprint.js +71 -0
- package/dist/footprint.js.map +1 -0
- package/dist/geometry/angle.d.ts +13 -0
- package/dist/geometry/angle.d.ts.map +1 -0
- package/dist/geometry/angle.js +21 -0
- package/dist/geometry/angle.js.map +1 -0
- package/dist/geometry/curve.d.ts +24 -0
- package/dist/geometry/curve.d.ts.map +1 -0
- package/dist/geometry/curve.js +78 -0
- package/dist/geometry/curve.js.map +1 -0
- package/dist/geometry/path.d.ts +29 -0
- package/dist/geometry/path.d.ts.map +1 -0
- package/dist/geometry/path.js +61 -0
- package/dist/geometry/path.js.map +1 -0
- package/dist/geometry/quadrature.d.ts +13 -0
- package/dist/geometry/quadrature.d.ts.map +1 -0
- package/dist/geometry/quadrature.js +57 -0
- package/dist/geometry/quadrature.js.map +1 -0
- package/dist/geometry/sat.d.ts +40 -0
- package/dist/geometry/sat.d.ts.map +1 -0
- package/dist/geometry/sat.js +86 -0
- package/dist/geometry/sat.js.map +1 -0
- package/dist/geometry/vec.d.ts +27 -0
- package/dist/geometry/vec.d.ts.map +1 -0
- package/dist/geometry/vec.js +30 -0
- package/dist/geometry/vec.js.map +1 -0
- package/dist/heading.d.ts +77 -0
- package/dist/heading.d.ts.map +1 -0
- package/dist/heading.js +164 -0
- package/dist/heading.js.map +1 -0
- package/dist/headingRanges.d.ts +48 -0
- package/dist/headingRanges.d.ts.map +1 -0
- package/dist/headingRanges.js +132 -0
- package/dist/headingRanges.js.map +1 -0
- package/dist/index.d.ts +41 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +41 -0
- package/dist/index.js.map +1 -0
- package/dist/kinematics.d.ts +63 -0
- package/dist/kinematics.d.ts.map +1 -0
- package/dist/kinematics.js +71 -0
- package/dist/kinematics.js.map +1 -0
- package/dist/ledger.d.ts +49 -0
- package/dist/ledger.d.ts.map +1 -0
- package/dist/ledger.js +136 -0
- package/dist/ledger.js.map +1 -0
- package/dist/load.d.ts +12 -0
- package/dist/load.d.ts.map +1 -0
- package/dist/load.js +12 -0
- package/dist/load.js.map +1 -0
- package/dist/mirror.d.ts +81 -0
- package/dist/mirror.d.ts.map +1 -0
- package/dist/mirror.js +282 -0
- package/dist/mirror.js.map +1 -0
- package/dist/plan.d.ts +29 -0
- package/dist/plan.d.ts.map +1 -0
- package/dist/plan.js +159 -0
- package/dist/plan.js.map +1 -0
- package/dist/prBody.d.ts +24 -0
- package/dist/prBody.d.ts.map +1 -0
- package/dist/prBody.js +112 -0
- package/dist/prBody.js.map +1 -0
- package/dist/provenance.d.ts +21 -0
- package/dist/provenance.d.ts.map +1 -0
- package/dist/provenance.js +46 -0
- package/dist/provenance.js.map +1 -0
- package/dist/render.d.ts +18 -0
- package/dist/render.d.ts.map +1 -0
- package/dist/render.js +464 -0
- package/dist/render.js.map +1 -0
- package/dist/resolve.d.ts +39 -0
- package/dist/resolve.d.ts.map +1 -0
- package/dist/resolve.js +172 -0
- package/dist/resolve.js.map +1 -0
- package/dist/season.d.ts +65 -0
- package/dist/season.d.ts.map +1 -0
- package/dist/season.js +16 -0
- package/dist/season.js.map +1 -0
- package/dist/sim/commands.d.ts +97 -0
- package/dist/sim/commands.d.ts.map +1 -0
- package/dist/sim/commands.js +185 -0
- package/dist/sim/commands.js.map +1 -0
- package/dist/sim/curves.d.ts +37 -0
- package/dist/sim/curves.d.ts.map +1 -0
- package/dist/sim/curves.js +187 -0
- package/dist/sim/curves.js.map +1 -0
- package/dist/sim/follower.d.ts +124 -0
- package/dist/sim/follower.d.ts.map +1 -0
- package/dist/sim/follower.js +472 -0
- package/dist/sim/follower.js.map +1 -0
- package/dist/sim/ideal.d.ts +103 -0
- package/dist/sim/ideal.d.ts.map +1 -0
- package/dist/sim/ideal.js +393 -0
- package/dist/sim/ideal.js.map +1 -0
- package/dist/sim/index.d.ts +11 -0
- package/dist/sim/index.d.ts.map +1 -0
- package/dist/sim/index.js +10 -0
- package/dist/sim/index.js.map +1 -0
- package/dist/sim/params.d.ts +91 -0
- package/dist/sim/params.d.ts.map +1 -0
- package/dist/sim/params.js +195 -0
- package/dist/sim/params.js.map +1 -0
- package/dist/sim/pedroPath.d.ts +41 -0
- package/dist/sim/pedroPath.d.ts.map +1 -0
- package/dist/sim/pedroPath.js +191 -0
- package/dist/sim/pedroPath.js.map +1 -0
- package/dist/sim/plant.d.ts +59 -0
- package/dist/sim/plant.d.ts.map +1 -0
- package/dist/sim/plant.js +128 -0
- package/dist/sim/plant.js.map +1 -0
- package/dist/sim/residuals.d.ts +36 -0
- package/dist/sim/residuals.d.ts.map +1 -0
- package/dist/sim/residuals.js +86 -0
- package/dist/sim/residuals.js.map +1 -0
- package/dist/sim/simulate.d.ts +27 -0
- package/dist/sim/simulate.d.ts.map +1 -0
- package/dist/sim/simulate.js +924 -0
- package/dist/sim/simulate.js.map +1 -0
- package/dist/sim/types.d.ts +209 -0
- package/dist/sim/types.d.ts.map +1 -0
- package/dist/sim/types.js +2 -0
- package/dist/sim/types.js.map +1 -0
- package/dist/tokens.d.ts +93 -0
- package/dist/tokens.d.ts.map +1 -0
- package/dist/tokens.js +93 -0
- package/dist/tokens.js.map +1 -0
- package/dist/trace.d.ts +89 -0
- package/dist/trace.d.ts.map +1 -0
- package/dist/trace.js +187 -0
- package/dist/trace.js.map +1 -0
- package/dist/types.d.ts +219 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/unimplemented.d.ts +8 -0
- package/dist/unimplemented.d.ts.map +1 -0
- package/dist/unimplemented.js +11 -0
- package/dist/unimplemented.js.map +1 -0
- 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
|