@horizon36596/zenith-mcp 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 +129 -0
- package/dist/editIo.d.ts +15 -0
- package/dist/editIo.d.ts.map +1 -0
- package/dist/editIo.js +33 -0
- package/dist/editIo.js.map +1 -0
- package/dist/findings.d.ts +35 -0
- package/dist/findings.d.ts.map +1 -0
- package/dist/findings.js +76 -0
- package/dist/findings.js.map +1 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +21 -0
- package/dist/index.js.map +1 -0
- package/dist/project.d.ts +46 -0
- package/dist/project.d.ts.map +1 -0
- package/dist/project.js +106 -0
- package/dist/project.js.map +1 -0
- package/dist/resources.d.ts +3 -0
- package/dist/resources.d.ts.map +1 -0
- package/dist/resources.js +37 -0
- package/dist/resources.js.map +1 -0
- package/dist/result.d.ts +14 -0
- package/dist/result.d.ts.map +1 -0
- package/dist/result.js +27 -0
- package/dist/result.js.map +1 -0
- package/dist/schemas.d.ts +576 -0
- package/dist/schemas.d.ts.map +1 -0
- package/dist/schemas.js +197 -0
- package/dist/schemas.js.map +1 -0
- package/dist/server.d.ts +13 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +27 -0
- package/dist/server.js.map +1 -0
- package/dist/spawnCli.d.ts +28 -0
- package/dist/spawnCli.d.ts.map +1 -0
- package/dist/spawnCli.js +57 -0
- package/dist/spawnCli.js.map +1 -0
- package/dist/tools/analysis.d.ts +4 -0
- package/dist/tools/analysis.d.ts.map +1 -0
- package/dist/tools/analysis.js +128 -0
- package/dist/tools/analysis.js.map +1 -0
- package/dist/tools/auto.d.ts +4 -0
- package/dist/tools/auto.d.ts.map +1 -0
- package/dist/tools/auto.js +43 -0
- package/dist/tools/auto.js.map +1 -0
- package/dist/tools/edit.d.ts +3 -0
- package/dist/tools/edit.d.ts.map +1 -0
- package/dist/tools/edit.js +346 -0
- package/dist/tools/edit.js.map +1 -0
- package/dist/tools/project.d.ts +4 -0
- package/dist/tools/project.d.ts.map +1 -0
- package/dist/tools/project.js +58 -0
- package/dist/tools/project.js.map +1 -0
- package/dist/version.d.ts +3 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +4 -0
- package/dist/version.js.map +1 -0
- package/package.json +67 -0
- package/spec/checks.md +313 -0
- package/spec/file-format.md +672 -0
|
@@ -0,0 +1,672 @@
|
|
|
1
|
+
# File format reference
|
|
2
|
+
|
|
3
|
+
Zenith keeps everything in plain JSON files inside your robot repository. Five kinds of file:
|
|
4
|
+
|
|
5
|
+
| file | what it holds | current `formatVersion` | JSON Schema |
|
|
6
|
+
|---|---|---|---|
|
|
7
|
+
| `zenith.json` | the link file: where everything else lives, how to run the sim | 1 | [`link.json`](https://libraries.horizon36596.org/zenith/schema/v1/link.json) |
|
|
8
|
+
| `robot.json` | one robot: footprint, speeds, intake mouths, commands, conditions | 1 | [`robot.json`](https://libraries.horizon36596.org/zenith/schema/v1/robot.json) |
|
|
9
|
+
| `*.field.json` | one season's field: walls, obstacles, zones, game elements, start rules | 2 | [`field.json`](https://libraries.horizon36596.org/zenith/schema/v1/field.json) |
|
|
10
|
+
| `waypoints.json` | named poses shared by every auto | 1 | [`waypoints.json`](https://libraries.horizon36596.org/zenith/schema/v1/waypoints.json) |
|
|
11
|
+
| `*.auto.json` | one autonomous routine | 3 | [`auto.json`](https://libraries.horizon36596.org/zenith/schema/v1/auto.json) |
|
|
12
|
+
|
|
13
|
+
A typical layout:
|
|
14
|
+
|
|
15
|
+
```text
|
|
16
|
+
<robot repo>/
|
|
17
|
+
zenith.json link file
|
|
18
|
+
autos/
|
|
19
|
+
robot.json this robot
|
|
20
|
+
field/biobuzz.field.json the season's field
|
|
21
|
+
waypoints.json named poses
|
|
22
|
+
first-auto.auto.json one routine
|
|
23
|
+
collect-and-score.auto.json another routine
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
The starter example, `examples/starter` in the Zenith repository, is laid out the same way. The
|
|
27
|
+
examples on this page are its files.
|
|
28
|
+
|
|
29
|
+
`zenith init` writes `zenith.json`, a skeleton `robot.json` and `waypoints.json`, and a copy of the
|
|
30
|
+
BIOBUZZ field. `zenith new <name>` adds an auto.
|
|
31
|
+
|
|
32
|
+
## Units
|
|
33
|
+
|
|
34
|
+
Files use **inches, radians and seconds**. Every numeric key names its unit in a suffix:
|
|
35
|
+
|
|
36
|
+
| suffix | unit | examples |
|
|
37
|
+
|---|---|---|
|
|
38
|
+
| `In` | inches | `xIn`, `widthIn`, `distanceIn` |
|
|
39
|
+
| `Rad` | radians | `headingRad`, `fromRad`, `minRad` |
|
|
40
|
+
| `S` | seconds | `timeoutS`, `settleS`, `autoS` |
|
|
41
|
+
| `InPerS` | inches per second | `maxForwardVelInPerS` |
|
|
42
|
+
| `InPerS2` | inches per second squared | `accelInPerS2` |
|
|
43
|
+
| `RadPerS` | radians per second | `maxAngularVelRadPerS` |
|
|
44
|
+
|
|
45
|
+
The editor shows degrees and converts at the boundary. Files never hold degrees, with one exception:
|
|
46
|
+
the field image's `rotationDeg`, which is a quarter-turn setting, not a measurement.
|
|
47
|
+
|
|
48
|
+
## The field frame
|
|
49
|
+
|
|
50
|
+
All poses are in one frame, fixed by the field file:
|
|
51
|
+
|
|
52
|
+
- The origin is the **centre** of the field.
|
|
53
|
+
- **+X** points to the audience's right.
|
|
54
|
+
- **+Y** points away from the audience.
|
|
55
|
+
- Heading 0 points along +X, and a positive heading turns **counter-clockwise**.
|
|
56
|
+
- Headings are wrapped to (−π, π].
|
|
57
|
+
|
|
58
|
+
An auto's poses are in the frame of its own `alliance`: a RED file's poses are where the robot
|
|
59
|
+
drives as RED, and a BLUE file's poses are where it drives as BLUE. The editor draws a file as its
|
|
60
|
+
own alliance by default, so what you draw is what the robot drives. Autos are usually written for
|
|
61
|
+
the **RED** alliance, the canonical alliance, and Zenith produces the BLUE version by mirroring. For BIOBUZZ the mirror is a **point symmetry** through the field centre:
|
|
62
|
+
a RED pose (x, y, h) becomes the BLUE pose (−x, −y, h + π). Named field elements swap to their
|
|
63
|
+
other-alliance counterparts, so a mirrored routine expects the hive and flowers it will really face.
|
|
64
|
+
Headings mirror with the poses. A `linear` sweep keeps its size and the way it turns, so its `toRad`
|
|
65
|
+
can land outside (−π, π]; under a reflection the sweep turns the other way. The robot runtime mirrors a
|
|
66
|
+
file run as the other alliance the same way.
|
|
67
|
+
|
|
68
|
+
In the robot frame, the nose points along +x and the robot's left along +y.
|
|
69
|
+
|
|
70
|
+
## Canonical form
|
|
71
|
+
|
|
72
|
+
Zenith writes every file the same way, byte for byte. A file loaded and saved without changes comes
|
|
73
|
+
back identical. That keeps git diffs of an auto small and pull requests readable.
|
|
74
|
+
|
|
75
|
+
- UTF-8, LF line endings, a trailing newline.
|
|
76
|
+
- Two-space indent.
|
|
77
|
+
- Object keys in **schema order**, not alphabetical: `name` before `steps`, `kind` before
|
|
78
|
+
`segments`. The order in the examples below is the canonical order.
|
|
79
|
+
- Numbers rounded by the unit of the key that holds them:
|
|
80
|
+
|
|
81
|
+
| key ends in | decimals | examples |
|
|
82
|
+
|---|---|---|
|
|
83
|
+
| `Rad` | 4 | `headingRad`, `fromRad` |
|
|
84
|
+
| `In` | 3 | `xIn`, `lengthIn` |
|
|
85
|
+
| `S` or `S2` | 3 | `timeoutS`, `maxForwardVelInPerS`, `accelInPerS2`, `maxAngularVelRadPerS` |
|
|
86
|
+
|
|
87
|
+
The suffix at the very end decides, so `maxAngularVelRadPerS` ends in `S` and rounds to 3 decimals.
|
|
88
|
+
|
|
89
|
+
Rounding applies **only to the key that names the unit**. An array under such a key rounds its
|
|
90
|
+
elements the same way. A number inside an object under such a key names no unit of its own and keeps
|
|
91
|
+
full precision. So in
|
|
92
|
+
|
|
93
|
+
```json
|
|
94
|
+
"maxForwardVelInPerS": { "value": 60.12345, "provenance": "MEASURED: velocity tuner" }
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
`value` is written as `60.12345`, not rounded to 3 decimals. A measured constant keeps every digit
|
|
98
|
+
it was measured to.
|
|
99
|
+
|
|
100
|
+
## Provenance labels
|
|
101
|
+
|
|
102
|
+
Every constant in `robot.json` and `field.json` should say where it came from. Poses in
|
|
103
|
+
`waypoints.json` should too. The label goes first, then the reason:
|
|
104
|
+
|
|
105
|
+
```json
|
|
106
|
+
"accelInPerS2": { "value": 60, "provenance": "PLACEHOLDER: until calibrated" }
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
| label | use it for | trust |
|
|
110
|
+
|---|---|---|
|
|
111
|
+
| `MEASURED` | measured on the real robot or field | measured |
|
|
112
|
+
| `SPEC` | taken from the game manual or a part's datasheet | measured |
|
|
113
|
+
| `CALIBRATED FROM ROBOT` | fitted by `zenith calibrate` from real-robot recordings | measured |
|
|
114
|
+
| `SET BY HAND` | chosen deliberately, such as a speed limit | derived |
|
|
115
|
+
| `SET FROM EDITOR` | placed by dragging or typing in the editor | derived |
|
|
116
|
+
| `SET FROM SIM` | read off a simulator run | derived |
|
|
117
|
+
| `CALIBRATED FROM SIM` | fitted by `zenith calibrate` from simulator traces | derived |
|
|
118
|
+
| `CARRIED OVER` | copied from an older robot or season | derived |
|
|
119
|
+
| `NEEDS MEASUREMENT` | known to be wrong or missing; measure it | unverified |
|
|
120
|
+
| `PLACEHOLDER` | a stand-in so the file works | unverified |
|
|
121
|
+
| `APPROX` | an estimate, close but not measured | unverified |
|
|
122
|
+
|
|
123
|
+
The editor shows each label as a chip and sorts it into one of the three trust tiers. A label that
|
|
124
|
+
names two tiers, such as `SET FROM SIM; PLACEHOLDER until measured`, counts as the weaker one. A
|
|
125
|
+
number with no label is treated as `NEEDS MEASUREMENT`.
|
|
126
|
+
|
|
127
|
+
## Format versions and migration
|
|
128
|
+
|
|
129
|
+
Every file carries a `formatVersion`. Each kind of file is versioned on its own, and the version
|
|
130
|
+
goes up only for a change that breaks older readers.
|
|
131
|
+
|
|
132
|
+
| file | current | history |
|
|
133
|
+
|---|---|---|
|
|
134
|
+
| `*.auto.json` | 3 | 2 added the [`sequence`](step-kinds.md#sequence) step. 3 added the [`piecewise`](#piecewise-heading) heading mode. |
|
|
135
|
+
| `*.field.json` | 2 | 2 added the optional [`image`](#field-image). |
|
|
136
|
+
| `robot.json` | 1 | unchanged |
|
|
137
|
+
| `waypoints.json` | 1 | unchanged |
|
|
138
|
+
| `zenith.json` | 1 | unchanged |
|
|
139
|
+
|
|
140
|
+
- Zenith **loads older versions** and migrates them in memory. The editor, the CLI and the MCP
|
|
141
|
+
server all do this.
|
|
142
|
+
- Zenith **always writes the current version**. A version 1 or 2 auto comes back from its first save
|
|
143
|
+
as version 3, and otherwise unchanged.
|
|
144
|
+
- The robot runtime **refuses a newer version** than it knows, at init, and the message names both
|
|
145
|
+
versions. Update the runtime when you update the app.
|
|
146
|
+
|
|
147
|
+
The `$schema` URLs keep `v1` in their path. The URL names the kind of file; `formatVersion` says
|
|
148
|
+
which revision of that kind it is.
|
|
149
|
+
|
|
150
|
+
## JSON Schemas
|
|
151
|
+
|
|
152
|
+
The schemas are JSON Schema draft 2020-12, served from the docs site:
|
|
153
|
+
|
|
154
|
+
- `zenith.json`: <https://libraries.horizon36596.org/zenith/schema/v1/link.json>
|
|
155
|
+
- `robot.json`: <https://libraries.horizon36596.org/zenith/schema/v1/robot.json>
|
|
156
|
+
- `*.field.json`: <https://libraries.horizon36596.org/zenith/schema/v1/field.json>
|
|
157
|
+
- `waypoints.json`: <https://libraries.horizon36596.org/zenith/schema/v1/waypoints.json>
|
|
158
|
+
- `*.auto.json`: <https://libraries.horizon36596.org/zenith/schema/v1/auto.json>
|
|
159
|
+
|
|
160
|
+
Put the URL in a file's `$schema` key and editors such as VS Code will complete and check it as you
|
|
161
|
+
type. `$schema` is optional when reading; Zenith writes it on every save. The same schemas ship in
|
|
162
|
+
the `@horizon36596/zenith-schema` npm package, with TypeScript types.
|
|
163
|
+
|
|
164
|
+
## `zenith.json`
|
|
165
|
+
|
|
166
|
+
The link file at the root of the robot repository. It tells Zenith where the other files are. This
|
|
167
|
+
is the starter example's, `examples/starter/zenith.json`:
|
|
168
|
+
|
|
169
|
+
```json
|
|
170
|
+
{
|
|
171
|
+
"$schema": "https://libraries.horizon36596.org/zenith/schema/v1/link.json",
|
|
172
|
+
"formatVersion": 1,
|
|
173
|
+
"autosDir": "autos",
|
|
174
|
+
"robot": "autos/robot.json",
|
|
175
|
+
"field": "autos/field/biobuzz.field.json",
|
|
176
|
+
"waypoints": "autos/waypoints.json",
|
|
177
|
+
"deploy": {
|
|
178
|
+
"kind": "androidAssets",
|
|
179
|
+
"dir": "TeamCode/src/main/assets/autos",
|
|
180
|
+
"commandLibrary": "solverslib",
|
|
181
|
+
"robotClass": "org.firstinspires.ftc.teamcode.zenith.MyRobot"
|
|
182
|
+
},
|
|
183
|
+
"sim": {
|
|
184
|
+
"command": "./gradlew :TeamCode:testDebugUnitTest --tests \"*ZenithAutoHeadlessTest*\" -Dzenith.auto={auto}",
|
|
185
|
+
"trace": "TeamCode/build/sim/{auto}.trace.json"
|
|
186
|
+
},
|
|
187
|
+
"codegen": { "package": "org.firstinspires.ftc.teamcode.zenith", "dir": "TeamCode/src/main/java" }
|
|
188
|
+
}
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
| key | required | meaning |
|
|
192
|
+
|---|---|---|
|
|
193
|
+
| `autosDir` | yes | the folder holding `*.auto.json` files |
|
|
194
|
+
| `robot` | yes | path to `robot.json` |
|
|
195
|
+
| `field` | yes | path to the field file |
|
|
196
|
+
| `waypoints` | no | path to `waypoints.json` |
|
|
197
|
+
| `deploy` | no | where `zenith deploy` copies autos. `kind` is `androidAssets` (the runtime reads them from the APK's assets) or `directory`. `commandLibrary` is `ivy` or `solverslib`: which runtime the generated OpModes and `zenith codegen` classes use ([Choosing a command library](command-libraries.md)). It has no default, and `zenith deploy`, `zenith codegen` and the desktop app's Deploy refuse to write Java until it is set; the schema accepts a file without it, so an older `zenith.json` still opens and validates. `robotClass` is the robot class the generated OpModes build. |
|
|
198
|
+
| `sim` | no | how to run the [high-fidelity sim](simulation.md#high-fidelity-sim). `{auto}` is replaced with the auto's name. |
|
|
199
|
+
| `codegen` | no | the Java package and source folder `zenith codegen` writes to |
|
|
200
|
+
|
|
201
|
+
All paths are relative to the folder holding `zenith.json`.
|
|
202
|
+
|
|
203
|
+
## `robot.json`
|
|
204
|
+
|
|
205
|
+
Everything the planner needs to know about one robot. This example is the starter example's
|
|
206
|
+
`autos/robot.json`, a generic robot: a plain mecanum drive, one front intake, a fixed launcher.
|
|
207
|
+
Every number is a round placeholder. Replace them with your own robot's values as you measure them.
|
|
208
|
+
|
|
209
|
+
```json
|
|
210
|
+
{
|
|
211
|
+
"$schema": "https://libraries.horizon36596.org/zenith/schema/v1/robot.json",
|
|
212
|
+
"formatVersion": 1,
|
|
213
|
+
"name": "Starter robot",
|
|
214
|
+
"frame": { "forward": "+x", "left": "+y", "headingZero": "+x", "headingPositive": "ccw" },
|
|
215
|
+
"footprint": {
|
|
216
|
+
"startIn": { "lengthIn": 18, "widthIn": 18, "provenance": "PLACEHOLDER: starter example, measure your own robot" },
|
|
217
|
+
"expandedIn": { "lengthIn": 18, "widthIn": 18, "provenance": "PLACEHOLDER: starter example, nothing sticks out past the frame" },
|
|
218
|
+
"centreOfRotationIn": { "xIn": 0, "yIn": 0, "provenance": "PLACEHOLDER: starter example, the middle of the frame" }
|
|
219
|
+
},
|
|
220
|
+
"heightIn": { "value": 18, "provenance": "PLACEHOLDER: starter example, measure your own robot" },
|
|
221
|
+
"kinematics": {
|
|
222
|
+
"maxForwardVelInPerS": { "value": 60, "provenance": "PLACEHOLDER: starter example, measure your own robot with Pedro's velocity tuner" },
|
|
223
|
+
"maxStrafeVelInPerS": { "value": 50, "provenance": "PLACEHOLDER: starter example, measure your own robot with Pedro's velocity tuner" },
|
|
224
|
+
"forwardDecelInPerS2": { "value": 60, "provenance": "PLACEHOLDER: starter example, measure your own robot with Pedro's deceleration tuner" },
|
|
225
|
+
"strafeDecelInPerS2": { "value": 40, "provenance": "PLACEHOLDER: starter example, measure your own robot with Pedro's deceleration tuner" },
|
|
226
|
+
"accelInPerS2": { "value": 60, "provenance": "PLACEHOLDER: starter example, the same as the forward deceleration until you calibrate it" },
|
|
227
|
+
"maxAngularVelRadPerS": { "value": 6, "provenance": "PLACEHOLDER: starter example, measure your own robot" },
|
|
228
|
+
"defaultPathSpeedFraction": { "value": 0.8, "provenance": "SET BY HAND: starter example, 80 percent of full speed on every path" },
|
|
229
|
+
"follower": { "library": "pedro", "version": "3.0.0-20260828.185437-17", "holdEnd": true }
|
|
230
|
+
},
|
|
231
|
+
"mouths": [
|
|
232
|
+
{
|
|
233
|
+
"id": "front",
|
|
234
|
+
"side": "FRONT",
|
|
235
|
+
"offsetIn": { "xIn": 7, "yIn": 0 },
|
|
236
|
+
"widthIn": 16,
|
|
237
|
+
"depthIn": 4,
|
|
238
|
+
"provenance": "PLACEHOLDER: starter example, one intake across the front of the frame, flush with the front edge"
|
|
239
|
+
}
|
|
240
|
+
],
|
|
241
|
+
"capacity": { "elementKind": "pollen", "max": 4, "provenance": "SPEC (G407): the most pollen a robot may hold in BIOBUZZ" },
|
|
242
|
+
"shooter": { "kind": "fixed", "settleS": { "value": 0.25, "provenance": "PLACEHOLDER: starter example, time for the robot to settle before it launches" } },
|
|
243
|
+
"commands": [
|
|
244
|
+
{
|
|
245
|
+
"name": "intakeOn",
|
|
246
|
+
"summary": "Runs the front intake inward and keeps it running. The state parameter has one value and a default, so an auto calls intakeOn with no arguments; it tells Zenith's intake checks that this command runs the intake.",
|
|
247
|
+
"params": { "state": { "type": "enum", "values": ["ON"], "default": "ON" } },
|
|
248
|
+
"estimateS": "0",
|
|
249
|
+
"requires": ["intake"],
|
|
250
|
+
"stationary": false
|
|
251
|
+
},
|
|
252
|
+
{
|
|
253
|
+
"name": "intakeOff",
|
|
254
|
+
"summary": "Stops the front intake. Like intakeOn, it takes no arguments in an auto.",
|
|
255
|
+
"params": { "state": { "type": "enum", "values": ["OFF"], "default": "OFF" } },
|
|
256
|
+
"estimateS": "0",
|
|
257
|
+
"requires": ["intake"],
|
|
258
|
+
"stationary": false
|
|
259
|
+
},
|
|
260
|
+
{
|
|
261
|
+
"name": "spinUp",
|
|
262
|
+
"summary": "Brings the launcher up to speed so the next score starts sooner. Ends once the launcher is at speed.",
|
|
263
|
+
"estimateS": "0.5",
|
|
264
|
+
"requires": ["launcher"],
|
|
265
|
+
"stationary": false
|
|
266
|
+
},
|
|
267
|
+
{
|
|
268
|
+
"name": "score",
|
|
269
|
+
"summary": "Launches count pollen into the alliance's up hive cell, then waits for the launcher to settle. The robot must stand still while it runs.",
|
|
270
|
+
"params": { "count": { "type": "integer", "min": 1, "max": 4, "default": 1 } },
|
|
271
|
+
"estimateS": "0.5 + count * 0.5",
|
|
272
|
+
"requires": ["launcher", "intake"],
|
|
273
|
+
"stationary": true,
|
|
274
|
+
"ledger": { "launches": "count" }
|
|
275
|
+
}
|
|
276
|
+
],
|
|
277
|
+
"conditions": [
|
|
278
|
+
{ "name": "holdingPiece", "summary": "At least one pollen is aboard. A sensor in the intake reads it." },
|
|
279
|
+
{ "name": "hopperFull", "summary": "The robot holds as much pollen as it may: four in BIOBUZZ.", "ledger": "full" },
|
|
280
|
+
{ "name": "hopperEmpty", "summary": "The robot holds no pollen.", "ledger": "empty" }
|
|
281
|
+
]
|
|
282
|
+
}
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
### Top-level keys
|
|
286
|
+
|
|
287
|
+
`name`
|
|
288
|
+
: A name for people. Shown in the editor.
|
|
289
|
+
|
|
290
|
+
`frame`
|
|
291
|
+
: How the robot frame is laid out. For almost every robot, leave it as shown.
|
|
292
|
+
|
|
293
|
+
`footprint`
|
|
294
|
+
: `startIn` is the robot's box at the start of the match. `expandedIn` is the box with every
|
|
295
|
+
mechanism out, which the wall and structure checks use. `lengthIn` runs along the nose,
|
|
296
|
+
`widthIn` across it. `centreOfRotationIn` is the offset of the turning centre from the box
|
|
297
|
+
centre. `footprint.heightIn` is also accepted; the top-level `heightIn` wins when both are set.
|
|
298
|
+
|
|
299
|
+
`heightIn`
|
|
300
|
+
: How tall the robot is, so the [`STRUCTURE`](checks-and-findings.md#structure) check knows what
|
|
301
|
+
it can drive under. 18 in when left out.
|
|
302
|
+
|
|
303
|
+
`mouths`
|
|
304
|
+
: Each intake opening: `id`, `side` (`FRONT`, `BACK`, `LEFT` or `RIGHT`), `offsetIn` from the robot
|
|
305
|
+
centre, `widthIn` and `depthIn`. The wall and structure checks sweep the mouths with the body,
|
|
306
|
+
and [`MOUTH_LEADING`](checks-and-findings.md#mouth_leading) uses `side`.
|
|
307
|
+
|
|
308
|
+
`capacity`
|
|
309
|
+
: What the robot collects (`elementKind`) and how many it can hold (`max`).
|
|
310
|
+
|
|
311
|
+
`shooter`
|
|
312
|
+
: Optional. `kind` is free text. A turret adds `turretRangeRad` with `minRad` and `maxRad`, used by
|
|
313
|
+
[`TURRET_RANGE`](checks-and-findings.md#turret_range). `cadenceS` and `settleS` may describe
|
|
314
|
+
firing rhythm.
|
|
315
|
+
|
|
316
|
+
`commands`
|
|
317
|
+
: The named-command registry. See [commands](#commands).
|
|
318
|
+
|
|
319
|
+
`conditions`
|
|
320
|
+
: The named conditions a `wait`, `branch` or `endCondition` may read. `ledger` is `"full"` or
|
|
321
|
+
`"empty"` when the condition means "at capacity" or "holding nothing", so the preview sim can
|
|
322
|
+
answer it from what the robot holds.
|
|
323
|
+
|
|
324
|
+
### `kinematics`
|
|
325
|
+
|
|
326
|
+
| key | meaning |
|
|
327
|
+
|---|---|
|
|
328
|
+
| `maxForwardVelInPerS` | top speed driving nose-first |
|
|
329
|
+
| `maxStrafeVelInPerS` | top speed driving sideways |
|
|
330
|
+
| `forwardDecelInPerS2` | how fast the robot slows when coasting forward |
|
|
331
|
+
| `strafeDecelInPerS2` | how fast it slows when coasting sideways |
|
|
332
|
+
| `accelInPerS2` | how fast it speeds up |
|
|
333
|
+
| `maxAngularVelRadPerS` | top turning rate |
|
|
334
|
+
| `defaultPathSpeedFraction` | the `speedFraction` a path uses when it sets none |
|
|
335
|
+
| `settleS` | optional: time added at the end of a path for the follower to settle. 0.25 s when left out. |
|
|
336
|
+
| `strafeFractionWarn` | optional: the [`STRAFE_FRACTION`](checks-and-findings.md#strafe_fraction) threshold. 0.2 when left out. |
|
|
337
|
+
| `sweepSpeedFraction` | optional: the [`SWEEP_SPEED`](checks-and-findings.md#sweep_speed) limit. 0.4 when left out. |
|
|
338
|
+
| `calibration` | optional: written back after calibration. `band` is the estimate's error band as a fraction, with `samples` and `provenance`. |
|
|
339
|
+
| `follower` | the path follower: `library` (`pedro`), `version` and `holdEnd` |
|
|
340
|
+
| `drivetrain` | optional: `trackWidthIn`, `wheelBaseIn`, `wheelResponseRatePerS` for the preview sim |
|
|
341
|
+
|
|
342
|
+
Each value is an object, `{ "value": ..., "provenance": "..." }`, so it can say where it came from.
|
|
343
|
+
|
|
344
|
+
`follower` may also carry the Pedro follower's gains and tolerances, each an optional
|
|
345
|
+
`{ value, provenance }`, for the [instant preview sim](simulation.md#instant-preview-sim):
|
|
346
|
+
`forwardTranslationalPowerPerIn`, `strafeTranslationalPowerPerIn`, `headingPowerPerRad`,
|
|
347
|
+
`headingStaticPower`, `coastPowerPerInPerS`, `coastFeedforwardPowerPerInPerS`,
|
|
348
|
+
`brakeFeedforwardPowerPerInPerS`, `maxBrakingPower`, `headingDriveRatio`, `brakeAggression`,
|
|
349
|
+
`brakeLinearForwardS`, `brakeQuadraticForwardS2PerIn`, `brakeLinearStrafeS`,
|
|
350
|
+
`brakeQuadraticStrafeS2PerIn`, `headingBrakeLinearS`, `headingBrakeQuadraticS2PerRad`,
|
|
351
|
+
`endParametricT`, `endHeadingToleranceRad`, `endTranslationalToleranceIn`,
|
|
352
|
+
`endVelocityToleranceInPerS`, `holdTimeoutS`, `headingDeviationToleranceRad`,
|
|
353
|
+
`translationalDeviationToleranceIn` and `minCorrectionDistanceIn`. Two switches, `cosineScale` and
|
|
354
|
+
`turnBeforeDriving`, take a boolean or `{ value, provenance }`. A key left out falls back to
|
|
355
|
+
Pedro's default or a value derived from the rest of the file. Copy these from your Pedro constants
|
|
356
|
+
when you want the preview to follow paths the way your robot does.
|
|
357
|
+
|
|
358
|
+
### Commands
|
|
359
|
+
|
|
360
|
+
Each entry describes one command your robot code registers with `NamedCommands`.
|
|
361
|
+
|
|
362
|
+
| key | meaning |
|
|
363
|
+
|---|---|
|
|
364
|
+
| `name` | must match the name registered in the robot code |
|
|
365
|
+
| `summary` | shown in the editor |
|
|
366
|
+
| `params` | the arguments, each with a `type`: `integer`, `number` (with optional `min`, `max`, `default`), `enum` (with `values`), `boolean` or `string` |
|
|
367
|
+
| `estimateS` | how long it takes: an expression over the params using numbers, `+ - * /`, `max` and `min`, or `"unknown"` |
|
|
368
|
+
| `requires` | the subsystems it uses |
|
|
369
|
+
| `stationary` | `true` if the robot must stand still; a marker firing it raises [`STATIONARY_MARKER`](checks-and-findings.md#stationary_marker) |
|
|
370
|
+
| `movesRobot` | `true` if it drives the robot by itself; the next path must start from `"current"` |
|
|
371
|
+
| `ledger` | how it changes what the robot holds, such as `{ "launches": "count" }` |
|
|
372
|
+
|
|
373
|
+
An `estimateS` of `"unknown"` draws a hatched block in the timeline, and the routine's total becomes a
|
|
374
|
+
lower bound.
|
|
375
|
+
|
|
376
|
+
## `*.field.json`
|
|
377
|
+
|
|
378
|
+
One season's field, as vector geometry in the field frame. Zenith ships the BIOBUZZ field. Copy it
|
|
379
|
+
into your repository so you can correct measurements locally.
|
|
380
|
+
|
|
381
|
+
This is the BIOBUZZ field from the starter example, shortened to one obstacle, one zone, two
|
|
382
|
+
elements (a pollen in the RED garden and a flower) and one target. The shipped file has them all.
|
|
383
|
+
|
|
384
|
+
```json
|
|
385
|
+
{
|
|
386
|
+
"$schema": "https://libraries.horizon36596.org/zenith/schema/v1/field.json",
|
|
387
|
+
"formatVersion": 2,
|
|
388
|
+
"season": "biobuzz",
|
|
389
|
+
"name": "BIOBUZZ presented by RTX (FTC 2026-27)",
|
|
390
|
+
"frame": {
|
|
391
|
+
"origin": "centre",
|
|
392
|
+
"xAxis": "audienceRight",
|
|
393
|
+
"yAxis": "awayFromAudience",
|
|
394
|
+
"headingZero": "+x",
|
|
395
|
+
"headingPositive": "ccw",
|
|
396
|
+
"units": "in",
|
|
397
|
+
"canonicalAlliance": "RED",
|
|
398
|
+
"mirror": "pointSymmetry",
|
|
399
|
+
"view": { "audienceAt": "bottom" }
|
|
400
|
+
},
|
|
401
|
+
"sizeIn": { "xIn": 144, "yIn": 144 },
|
|
402
|
+
"image": {
|
|
403
|
+
"src": "app:fields/biobuzz/biobuzz-dark.webp",
|
|
404
|
+
"credit": "Field image by Team Juice 16236",
|
|
405
|
+
"pxBoundsIn": "fullBleed",
|
|
406
|
+
"rotationDeg": 90,
|
|
407
|
+
"variants": [{ "name": "dark", "src": "app:fields/biobuzz/biobuzz-dark.webp" }, { "name": "black", "src": "app:fields/biobuzz/biobuzz-black.webp" }, { "name": "light", "src": "app:fields/biobuzz/biobuzz-light.webp" }],
|
|
408
|
+
"provenance": "SET BY HAND 2026-09-22: full bleed (1080 px spans 144 in, grid every 180 px = one tile) and a 90 degree clockwise turn, checked against the RED loading zone, the four flowers and hiveRed (apps/web/public/fields/biobuzz/SOURCE.md)"
|
|
409
|
+
},
|
|
410
|
+
"periods": { "autoS": 30, "teleopS": 120 },
|
|
411
|
+
"obstacles": [
|
|
412
|
+
{
|
|
413
|
+
"id": "hiveRedPivotBar",
|
|
414
|
+
"kind": "box",
|
|
415
|
+
"minXIn": -22.75,
|
|
416
|
+
"maxXIn": -2.75,
|
|
417
|
+
"minYIn": -18.58,
|
|
418
|
+
"maxYIn": 18.58,
|
|
419
|
+
"minZIn": 25.5,
|
|
420
|
+
"maxZIn": 65.6,
|
|
421
|
+
"solidToRobot": true,
|
|
422
|
+
"provenance": "SPEC plus APPROX (BIOBUZZ Competition Manual V1 §9.6, Figs 9-9 to 9-11). Pivot at x = -12.75: the two hives are 25.5 in centre to centre (Fig 9-10) and the pair is centred on the field (Fig 9-2; FIRST's BIOBUZZ field CAD (STEP v26-27.2) agrees). Cell outer face 18.58 in from the pivot in plan: the cells' inner faces are 18.84 in apart and each cell is 12.04 in deep (Fig 9-9), so the outer face is 21.46 in along the bar, times cos 30 degrees for the 30 degree tilt (Fig 9-10). Opening 20 in wide (§9.6.2, Fig 9-11). z from 25.5 in, the bottom of the down hive, to 65.6 in, the top of the up-cell opening (Fig 9-10). The box is an envelope over both cells' full swing footprint, because either cell may be up. APPROX on the 25.5 in floor: FIRST's BIOBUZZ field CAD (STEP v26-27.2) puts the down-cell floor at 31.981 in and the lowest hive structure at rest at 30.652 in, see TRANSCRIPTION.md."
|
|
423
|
+
}
|
|
424
|
+
],
|
|
425
|
+
"zones": [
|
|
426
|
+
{
|
|
427
|
+
"id": "loadingZoneRed",
|
|
428
|
+
"kind": "rect",
|
|
429
|
+
"minXIn": -72,
|
|
430
|
+
"maxXIn": -61,
|
|
431
|
+
"minYIn": 24,
|
|
432
|
+
"maxYIn": 48,
|
|
433
|
+
"rule": "G304: no start here",
|
|
434
|
+
"provenance": "APPROX (BIOBUZZ Competition Manual V1 §9.3, Figs 9-2 and 9-3; FIRST's Event Field Setup Guide §8.3): 11 in deep off the x = -72 wall, between the y = +24 and y = +48 tile seams, tape included. FIRST's BIOBUZZ field CAD (STEP v26-27.2), on its 23.528 in tile pitch, puts it at x in [-70.674, -59.101], y in [23.907, 46.599], see TRANSCRIPTION.md."
|
|
435
|
+
}
|
|
436
|
+
],
|
|
437
|
+
"elements": [
|
|
438
|
+
{
|
|
439
|
+
"id": "gardenRed0",
|
|
440
|
+
"kind": "pollen",
|
|
441
|
+
"container": null,
|
|
442
|
+
"xIn": -70.6,
|
|
443
|
+
"yIn": -70.6,
|
|
444
|
+
"radiusIn": 1.4,
|
|
445
|
+
"provenance": "APPROX (BIOBUZZ Competition Manual V1 §10.3.1, Fig 10-2): the red garden's four pollen lie in a line along the audience wall starting in the red corner, each touching the next. Pollen are 2.8 in across (§9.8), so ball i sits at x = -(72 - 1.4 - 2.8 i), y = -(72 - 1.4)."
|
|
446
|
+
},
|
|
447
|
+
{
|
|
448
|
+
"id": "flower0",
|
|
449
|
+
"kind": "container",
|
|
450
|
+
"container": "flower",
|
|
451
|
+
"xIn": -69.46,
|
|
452
|
+
"yIn": -24,
|
|
453
|
+
"holds": ["pollen", "pollen", "pollen", "pollen"],
|
|
454
|
+
"provenance": "SPEC plus APPROX (BIOBUZZ Competition Manual V1 §9.7, Figs 9-2, 9-4 and 9-12). On the x = -72 wall at the y = -24 tile seam, one tile off centre. The ring centre 2.54 in off the wall face (x = -69.46) is APPROX, derived from the public field drawings: FIRST's BIOBUZZ field CAD (STEP v26-27.2) puts it 2.629 in off the wall. Retrieval opening 3.55 in tall by 3.57 in deep at the bottom (Fig 9-12), taken as 6 in wide, the width of the foot. Holds 4 pollen at the start of the match (§10.3.1, Fig 10-2).",
|
|
455
|
+
"retrieval": { "side": "field", "openingIn": { "widthIn": 6, "heightIn": 3.55, "depthIn": 3.57 }, "takes": ["pollen"] }
|
|
456
|
+
}
|
|
457
|
+
],
|
|
458
|
+
"targets": [
|
|
459
|
+
{
|
|
460
|
+
"id": "hiveRedUpCell",
|
|
461
|
+
"kind": "cell",
|
|
462
|
+
"provenance": "SPEC plus SET BY HAND. Aim at 59.5 in, the middle of the up-cell opening at 53.5 to 65.6 in (BIOBUZZ Competition Manual V1 Fig 9-10). A launch enters the cell's open outer end (§9.6.2, Fig 9-11), so it approaches along y from outboard of the outer face. The 6 in margin outboard of that face is SET BY HAND: a planning choice, not a game figure.",
|
|
463
|
+
"hive": "hiveRed",
|
|
464
|
+
"aimZIn": 59.5,
|
|
465
|
+
"legalApproach": { "outboardOfOuterFaceByIn": 6, "alongAxis": "y" }
|
|
466
|
+
}
|
|
467
|
+
],
|
|
468
|
+
"startRules": { "touchingWall": true, "ownHalf": true, "notInZones": ["loadingZoneRed"], "notTouchingContainers": ["flower"], "holds": { "pollen": 4 } },
|
|
469
|
+
"rules": { "plugin": "season-biobuzz", "tipTable": [8, 7, 6, 3, 1, 0], "capacity": 4 }
|
|
470
|
+
}
|
|
471
|
+
```
|
|
472
|
+
|
|
473
|
+
!!! note
|
|
474
|
+
The example is formatted for reading. Canonical form writes each object's keys one per line.
|
|
475
|
+
|
|
476
|
+
| key | meaning |
|
|
477
|
+
|---|---|
|
|
478
|
+
| `season`, `name` | the season id and its display name |
|
|
479
|
+
| `frame` | the [field frame](#the-field-frame). `mirror` is `pointSymmetry`, `mirrorX`, `mirrorY` or `none`. `view.audienceAt` is where the editor draws the audience by default. |
|
|
480
|
+
| `sizeIn` | the field's size |
|
|
481
|
+
| `image` | optional picture drawn under the geometry; see [field image](#field-image) |
|
|
482
|
+
| `periods` | `autoS` and `teleopS`; [`TIME_BUDGET`](checks-and-findings.md#time_budget) reads `autoS` |
|
|
483
|
+
| `obstacles` | axis-aligned boxes with a z range. `solidToRobot: true` makes [`STRUCTURE`](checks-and-findings.md#structure) check them. |
|
|
484
|
+
| `zones` | rectangles a rule talks about. `appliesInAuto: true` makes [`KEEPOUT`](checks-and-findings.md#keepout) check them. |
|
|
485
|
+
| `elements` | game pieces and containers. The season plugin reads extra keys it put there. |
|
|
486
|
+
| `targets` | what the robot aims at. The season plugin reads extra keys it put there. |
|
|
487
|
+
| `startRules` | what [`START_ILLEGAL`](checks-and-findings.md#start_illegal) checks, and what the robot holds at the start |
|
|
488
|
+
| `rules.plugin` | the season plugin that tracks game state; see [Seasons](seasons.md) |
|
|
489
|
+
|
|
490
|
+
### Field image
|
|
491
|
+
|
|
492
|
+
An optional picture, added in field format version 2. It is only a picture: no check reads it, and
|
|
493
|
+
the vector geometry stays the truth.
|
|
494
|
+
|
|
495
|
+
| key | meaning |
|
|
496
|
+
|---|---|
|
|
497
|
+
| `src` | the picture, as a path relative to the field file or `app:<path>` for an image the app ships |
|
|
498
|
+
| `credit` | who made the image; shown wherever the image is |
|
|
499
|
+
| `pxBoundsIn` | `"fullBleed"` when the field wall is the image's edge, or `{ "left", "top", "right", "bottom" }` in image pixels |
|
|
500
|
+
| `rotationDeg` | `0`, `90`, `180` or `270`, clockwise as seen on screen. `0` when left out. |
|
|
501
|
+
| `variants` | optional other looks of the same picture, each `{ "name", "src" }` |
|
|
502
|
+
| `provenance` | optional: where the pixel box came from |
|
|
503
|
+
|
|
504
|
+
The BIOBUZZ field images are by Team Juice 16236.
|
|
505
|
+
|
|
506
|
+
## `waypoints.json`
|
|
507
|
+
|
|
508
|
+
Named poses shared by every auto. An auto points at one with `{ "ref": "scoreSouth" }`. Change the
|
|
509
|
+
waypoint and every auto that uses it changes too, so you measure a pose on the field once.
|
|
510
|
+
|
|
511
|
+
```json
|
|
512
|
+
{
|
|
513
|
+
"$schema": "https://libraries.horizon36596.org/zenith/schema/v1/waypoints.json",
|
|
514
|
+
"formatVersion": 1,
|
|
515
|
+
"waypoints": {
|
|
516
|
+
"start": { "xIn": -12, "yIn": -63, "headingRad": 1.5708, "provenance": "PLACEHOLDER: starter example, against the south wall in RED's half, facing the hive; measure your own start tile" },
|
|
517
|
+
"scoreSouth": { "xIn": -12, "yIn": -36, "headingRad": 1.5708, "provenance": "PLACEHOLDER: starter example, south of the RED hive, outside the legal approach margin, facing the up cell" },
|
|
518
|
+
"gardenApproach": { "xIn": -61, "yIn": -44, "headingRad": -1.5708, "provenance": "PLACEHOLDER: starter example, above the RED garden row, intake facing the south wall" },
|
|
519
|
+
"gardenPickup": { "xIn": -61, "yIn": -62, "headingRad": -1.5708, "provenance": "PLACEHOLDER: starter example, the intake over the RED garden row, frame 1 in off the south wall" },
|
|
520
|
+
"park": { "xIn": -40, "yIn": -36, "headingRad": 3.1416, "provenance": "PLACEHOLDER: starter example, an open spot in RED's half to finish in" }
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
```
|
|
524
|
+
|
|
525
|
+
Each waypoint needs `xIn`, `yIn` and `headingRad`. `provenance` is optional but recommended. Poses
|
|
526
|
+
are for the RED alliance.
|
|
527
|
+
|
|
528
|
+
## `*.auto.json`
|
|
529
|
+
|
|
530
|
+
One routine. The file name is `<name>.auto.json`. This is the starter example's
|
|
531
|
+
`first-auto.auto.json`; `collect-and-score.auto.json` beside it adds a curve, a marker, a parallel
|
|
532
|
+
group and a wait until a condition, and [Step kinds](step-kinds.md) shows each of those.
|
|
533
|
+
|
|
534
|
+
```json
|
|
535
|
+
{
|
|
536
|
+
"$schema": "https://libraries.horizon36596.org/zenith/schema/v1/auto.json",
|
|
537
|
+
"formatVersion": 3,
|
|
538
|
+
"name": "first-auto",
|
|
539
|
+
"title": "First auto",
|
|
540
|
+
"description": "The smallest useful auto: drive out from the start tile, score the four preloaded pollen, and park. Three steps: a path, a command and another path. The getting-started guide walks through it.",
|
|
541
|
+
"robot": "autos/robot.json",
|
|
542
|
+
"field": "autos/field/biobuzz.field.json",
|
|
543
|
+
"alliance": "RED",
|
|
544
|
+
"authors": ["Horizon (FTC 36596)"],
|
|
545
|
+
"created": "2026-09-23",
|
|
546
|
+
"start": { "pose": { "ref": "start" }, "holds": { "pollen": 4 } },
|
|
547
|
+
"steps": [
|
|
548
|
+
{
|
|
549
|
+
"id": "driveOut",
|
|
550
|
+
"kind": "path",
|
|
551
|
+
"segments": [
|
|
552
|
+
{ "kind": "line", "from": { "ref": "start" }, "to": { "ref": "scoreSouth" } }
|
|
553
|
+
],
|
|
554
|
+
"heading": { "mode": "tangent" },
|
|
555
|
+
"notes": "A straight line north to the scoring spot. Tangent heading keeps the front of the robot pointing the way it drives."
|
|
556
|
+
},
|
|
557
|
+
{
|
|
558
|
+
"id": "scorePreload",
|
|
559
|
+
"kind": "command",
|
|
560
|
+
"name": "score",
|
|
561
|
+
"args": { "count": 4 },
|
|
562
|
+
"notes": "Launches the four preloaded pollen. score is stationary in robot.json, so it runs as its own step while the robot stands still."
|
|
563
|
+
},
|
|
564
|
+
{
|
|
565
|
+
"id": "park",
|
|
566
|
+
"kind": "path",
|
|
567
|
+
"segments": [
|
|
568
|
+
{ "kind": "line", "from": "current", "to": { "ref": "park" } }
|
|
569
|
+
],
|
|
570
|
+
"heading": { "mode": "tangent" },
|
|
571
|
+
"notes": "from current starts where the last step ended, so the park leg follows on from wherever the robot scored."
|
|
572
|
+
}
|
|
573
|
+
]
|
|
574
|
+
}
|
|
575
|
+
```
|
|
576
|
+
|
|
577
|
+
### Top-level keys
|
|
578
|
+
|
|
579
|
+
| key | required | meaning |
|
|
580
|
+
|---|---|---|
|
|
581
|
+
| `name` | yes | the routine's id; matches the file name |
|
|
582
|
+
| `title`, `description` | no | for people; shown in the editor |
|
|
583
|
+
| `robot`, `field` | no | paths to the robot and field files, when they differ from `zenith.json` |
|
|
584
|
+
| `alliance` | yes | `RED` or `BLUE`: which alliance the file's poses are written for |
|
|
585
|
+
| `authors`, `created` | no | who wrote it and when |
|
|
586
|
+
| `start` | yes | `pose` (a waypoint `ref` or an inline pose) and `holds`, what the robot carries at the start |
|
|
587
|
+
| `steps` | yes | the steps, at least one; see [Step kinds](step-kinds.md) |
|
|
588
|
+
|
|
589
|
+
### Segments
|
|
590
|
+
|
|
591
|
+
| kind | fields | notes |
|
|
592
|
+
|---|---|---|
|
|
593
|
+
| `line` | `from`, `to` | a straight line |
|
|
594
|
+
| `bezier` | `from`, `control[]`, `to` | a curve with one to three control points |
|
|
595
|
+
|
|
596
|
+
`from` and `to` are an inline pose, a waypoint `{ "ref": name }`, or the string `"current"`, the pose
|
|
597
|
+
the previous step ended at. The runtime reads `"current"` from the follower when the step starts;
|
|
598
|
+
the planner takes it from the previous step's end. Segments in one path must meet within 0.5 in.
|
|
599
|
+
|
|
600
|
+
### Heading modes
|
|
601
|
+
|
|
602
|
+
| mode | editor label | Pedro call | fields | the robot faces | use it when |
|
|
603
|
+
|---|---|---|---|---|---|
|
|
604
|
+
| `tangent` | Tangent | `path.tangent()` | | along the direction of travel | the default on mecanum: nose first, front intake leading |
|
|
605
|
+
| `tangentReversed` | Reverse tangent | `path.reverseTangent()` | | opposite the direction of travel | a back mechanism should lead |
|
|
606
|
+
| `constant` | Constant | `path.constant(heading)` | `headingRad` | one fixed heading | a short, deliberate strafe, such as a sweep along a wall |
|
|
607
|
+
| `linear` | Linear | `path.linear(start, end)` | `fromRad`, `toRad` | turning steadily from one heading to the other | the robot must turn during the leg |
|
|
608
|
+
| `facePoint` | Facing point | `path.facingPoint(point)` | `xIn`, `yIn`, `offsetRad?` | toward a point on the field | a camera or launcher should stay on a target while moving |
|
|
609
|
+
| `piecewise` | Piecewise | `Interpolator.piecewise().until(t, ...)` | `ranges[]` | a different mode on each stretch of the path | hold a heading out of a wall, then turn to score, in one leg |
|
|
610
|
+
|
|
611
|
+
The editor labels are Pedro Pathing's own names for these heading interpolators, checked against the
|
|
612
|
+
Pedro build the robot runtime compiles against. The file keeps its own `mode` names.
|
|
613
|
+
|
|
614
|
+
For `linear`, the sweep is shared over the path's segments by length, and each segment's share turns
|
|
615
|
+
the short way round, as Pedro does. A share over half a turn goes the other way and raises the
|
|
616
|
+
[`HEADING`](checks-and-findings.md#heading) warning. The robot runtime ignores `facePoint`'s
|
|
617
|
+
`offsetRad` for now.
|
|
618
|
+
|
|
619
|
+
#### Piecewise heading
|
|
620
|
+
|
|
621
|
+
A `piecewise` heading splits the path into ranges of `t`, the fraction of the path's length from 0
|
|
622
|
+
at the start to 1 at the end, and gives each range its own mode:
|
|
623
|
+
|
|
624
|
+
```json
|
|
625
|
+
"heading": {
|
|
626
|
+
"mode": "piecewise",
|
|
627
|
+
"ranges": [
|
|
628
|
+
{ "startT": 0, "endT": 0.4, "heading": { "mode": "constant", "headingRad": 1.5708 } },
|
|
629
|
+
{ "startT": 0.4, "endT": 1, "heading": { "mode": "linear", "fromRad": 1.5708, "toRad": 3.1416 } }
|
|
630
|
+
]
|
|
631
|
+
}
|
|
632
|
+
```
|
|
633
|
+
|
|
634
|
+
| key | meaning |
|
|
635
|
+
|---|---|
|
|
636
|
+
| `ranges[]` | at least one range, in order along the path |
|
|
637
|
+
| `startT`, `endT` | where the range starts and ends, from 0 to 1; `t` has no unit and is not rounded |
|
|
638
|
+
| `heading` | the range's own mode: `tangent`, `tangentReversed`, `constant`, `linear` or `facePoint`, with that mode's fields; never `piecewise` |
|
|
639
|
+
|
|
640
|
+
- The ranges must cover the whole path: the first starts at 0, each starts where the one before it
|
|
641
|
+
ends, and the last ends at 1. A gap, an overlap or a range that does not end after it starts is a
|
|
642
|
+
[`HEADING_RANGES`](checks-and-findings.md#heading_ranges) error, because Pedro stops the auto on
|
|
643
|
+
the robot when the ranges do not cover the path.
|
|
644
|
+
- A point on a boundary belongs to the range that ends there, as it does in Pedro.
|
|
645
|
+
- A `linear` range turns from `fromRad` at its start to `toRad` at its end, the short way round, over
|
|
646
|
+
that range alone. A range over half a turn raises the `HEADING` warning. `tangent`,
|
|
647
|
+
`tangentReversed` and `facePoint` read the path wherever the range is.
|
|
648
|
+
- The robot runtime builds Pedro's `Interpolator.piecewise()` for each segment of the path, from the
|
|
649
|
+
ranges that cross it.
|
|
650
|
+
|
|
651
|
+
### Markers
|
|
652
|
+
|
|
653
|
+
A marker fires a command part way along a path without stopping it. `at` is one of:
|
|
654
|
+
|
|
655
|
+
- `{ "t": 0.5 }`: a fraction of the whole path's length, from 0 to 1;
|
|
656
|
+
- `{ "distanceIn": 12 }`: inches from the start;
|
|
657
|
+
- `{ "distanceFromEndIn": 6 }`: inches before the end.
|
|
658
|
+
|
|
659
|
+
`command` is `{ "name": ..., "args": ... }`, like a command step. The runtime fires markers on
|
|
660
|
+
distance travelled, so the editor and the robot agree on where they happen.
|
|
661
|
+
|
|
662
|
+
### `expect`
|
|
663
|
+
|
|
664
|
+
Optional notes for the planner's ledger, on a `path` or `command` step. The robot ignores them.
|
|
665
|
+
|
|
666
|
+
| key | meaning |
|
|
667
|
+
|---|---|
|
|
668
|
+
| `collectFrom`, `count` | this step collects `count` pieces from the named container |
|
|
669
|
+
| `launchesInto` | this step launches into the named target |
|
|
670
|
+
| `tip` | `own` or `opponent`: this step tips a hive |
|
|
671
|
+
|
|
672
|
+
The ledger uses them to track what the robot holds and the season state after each step.
|