@horizon36596/zenith-cli 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 +207 -0
- package/dist/autoLoad.d.ts +54 -0
- package/dist/autoLoad.d.ts.map +1 -0
- package/dist/autoLoad.js +139 -0
- package/dist/autoLoad.js.map +1 -0
- package/dist/calibrateFit.d.ts +38 -0
- package/dist/calibrateFit.d.ts.map +1 -0
- package/dist/calibrateFit.js +69 -0
- package/dist/calibrateFit.js.map +1 -0
- package/dist/commandLibrary.d.ts +19 -0
- package/dist/commandLibrary.d.ts.map +1 -0
- package/dist/commandLibrary.js +27 -0
- package/dist/commandLibrary.js.map +1 -0
- package/dist/commands/calibrate.d.ts +18 -0
- package/dist/commands/calibrate.d.ts.map +1 -0
- package/dist/commands/calibrate.js +116 -0
- package/dist/commands/calibrate.js.map +1 -0
- package/dist/commands/codegen.d.ts +12 -0
- package/dist/commands/codegen.d.ts.map +1 -0
- package/dist/commands/codegen.js +63 -0
- package/dist/commands/codegen.js.map +1 -0
- package/dist/commands/deploy.d.ts +63 -0
- package/dist/commands/deploy.d.ts.map +1 -0
- package/dist/commands/deploy.js +181 -0
- package/dist/commands/deploy.js.map +1 -0
- package/dist/commands/diff.d.ts +11 -0
- package/dist/commands/diff.d.ts.map +1 -0
- package/dist/commands/diff.js +68 -0
- package/dist/commands/diff.js.map +1 -0
- package/dist/commands/estimate.d.ts +9 -0
- package/dist/commands/estimate.d.ts.map +1 -0
- package/dist/commands/estimate.js +81 -0
- package/dist/commands/estimate.js.map +1 -0
- package/dist/commands/init.d.ts +10 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +67 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/new.d.ts +10 -0
- package/dist/commands/new.d.ts.map +1 -0
- package/dist/commands/new.js +28 -0
- package/dist/commands/new.js.map +1 -0
- package/dist/commands/propose.d.ts +15 -0
- package/dist/commands/propose.d.ts.map +1 -0
- package/dist/commands/propose.js +67 -0
- package/dist/commands/propose.js.map +1 -0
- package/dist/commands/render.d.ts +16 -0
- package/dist/commands/render.d.ts.map +1 -0
- package/dist/commands/render.js +90 -0
- package/dist/commands/render.js.map +1 -0
- package/dist/commands/sim.d.ts +31 -0
- package/dist/commands/sim.d.ts.map +1 -0
- package/dist/commands/sim.js +213 -0
- package/dist/commands/sim.js.map +1 -0
- package/dist/commands/validate.d.ts +19 -0
- package/dist/commands/validate.d.ts.map +1 -0
- package/dist/commands/validate.js +97 -0
- package/dist/commands/validate.js.map +1 -0
- package/dist/coreGate.d.ts +19 -0
- package/dist/coreGate.d.ts.map +1 -0
- package/dist/coreGate.js +23 -0
- package/dist/coreGate.js.map +1 -0
- package/dist/fieldImage.d.ts +19 -0
- package/dist/fieldImage.d.ts.map +1 -0
- package/dist/fieldImage.js +63 -0
- package/dist/fieldImage.js.map +1 -0
- package/dist/findings.d.ts +20 -0
- package/dist/findings.d.ts.map +1 -0
- package/dist/findings.js +57 -0
- package/dist/findings.js.map +1 -0
- package/dist/index.d.ts +32 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +194 -0
- package/dist/index.js.map +1 -0
- package/dist/javaCodegen.d.ts +44 -0
- package/dist/javaCodegen.d.ts.map +1 -0
- package/dist/javaCodegen.js +537 -0
- package/dist/javaCodegen.js.map +1 -0
- package/dist/launcher.d.ts +39 -0
- package/dist/launcher.d.ts.map +1 -0
- package/dist/launcher.js +153 -0
- package/dist/launcher.js.map +1 -0
- package/dist/lib.d.ts +13 -0
- package/dist/lib.d.ts.map +1 -0
- package/dist/lib.js +13 -0
- package/dist/lib.js.map +1 -0
- package/dist/project.d.ts +33 -0
- package/dist/project.d.ts.map +1 -0
- package/dist/project.js +104 -0
- package/dist/project.js.map +1 -0
- package/dist/skeletons.d.ts +16 -0
- package/dist/skeletons.d.ts.map +1 -0
- package/dist/skeletons.js +120 -0
- package/dist/skeletons.js.map +1 -0
- package/dist/traceReport.d.ts +75 -0
- package/dist/traceReport.d.ts.map +1 -0
- package/dist/traceReport.js +91 -0
- package/dist/traceReport.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 +71 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Horizon (FTC 36596)
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/NOTICE
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
Zenith
|
|
2
|
+
Copyright (c) 2026 Horizon (FTC 36596)
|
|
3
|
+
|
|
4
|
+
Zenith is licensed under the MIT License; see LICENSE.
|
|
5
|
+
|
|
6
|
+
Third-party assets
|
|
7
|
+
|
|
8
|
+
- BIOBUZZ field images (apps/web/public/fields/biobuzz/*.webp) by Team Juice 16236, from the r/FTC
|
|
9
|
+
post "BIOBUZZ custom field images (MeepMeep compatible)". Used with the credit the author asks for.
|
|
10
|
+
|
|
11
|
+
Bundled fonts
|
|
12
|
+
|
|
13
|
+
The following fonts are bundled in apps/web/public/fonts/. Each is licensed under the SIL Open Font
|
|
14
|
+
License, Version 1.1. The full licence text ships next to each font file.
|
|
15
|
+
|
|
16
|
+
- Jost, licensed under the SIL Open Font License 1.1 (jost-OFL.txt).
|
|
17
|
+
- IBM Plex Sans, licensed under the SIL Open Font License 1.1 (ibm-plex-sans-OFL.txt).
|
|
18
|
+
- JetBrains Mono, licensed under the SIL Open Font License 1.1 (jetbrains-mono-OFL.txt).
|
package/README.md
ADDED
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
# @horizon36596/zenith-cli
|
|
2
|
+
|
|
3
|
+
The `zenith` command for [Zenith](https://libraries.horizon36596.org/zenith/), the FTC autonomous
|
|
4
|
+
planner by Horizon (FTC 36596). It validates, estimates, renders, generates code for, deploys and
|
|
5
|
+
simulates the autos in a robot repository, with `--json` on every verb and no prompts, so a CI job
|
|
6
|
+
or an agent can drive it.
|
|
7
|
+
|
|
8
|
+
```
|
|
9
|
+
npm install --save-dev @horizon36596/zenith-cli
|
|
10
|
+
npx zenith --version
|
|
11
|
+
npx zenith init
|
|
12
|
+
npx zenith validate autos/*.auto.json
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Requires Node 22 or newer. Documentation: https://libraries.horizon36596.org/zenith/. Licence: MIT.
|
|
16
|
+
|
|
17
|
+
## Package reference
|
|
18
|
+
|
|
19
|
+
The `zenith` command line tool: every verb an editor, a CI job, or an agent needs to work with a
|
|
20
|
+
Zenith project without opening the web app. The docs site's CLI reference
|
|
21
|
+
(https://libraries.horizon36596.org/zenith/cli-reference/) is the user-facing version of this file.
|
|
22
|
+
|
|
23
|
+
### Install and run
|
|
24
|
+
|
|
25
|
+
Inside this monorepo:
|
|
26
|
+
|
|
27
|
+
```
|
|
28
|
+
pnpm install
|
|
29
|
+
pnpm --filter @horizon36596/zenith-cli build
|
|
30
|
+
pnpm zenith -- <verb> [args]
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
`pnpm zenith` forwards the `--` separator pnpm inserts before `<verb>`; `main()` drops that one
|
|
34
|
+
leading `--` before parsing, so flags after the verb (`--project`, `--json`, ...) work normally.
|
|
35
|
+
|
|
36
|
+
Or, once built, `zenith` is the package's bin and can be run directly (`node packages/cli/dist/index.js`,
|
|
37
|
+
or just `zenith` if the workspace's `node_modules/.bin` is on `PATH`).
|
|
38
|
+
|
|
39
|
+
### Project resolution
|
|
40
|
+
|
|
41
|
+
Every verb that needs a project looks for `zenith.json` in `--project <dir>` if given, otherwise by
|
|
42
|
+
walking up from the current directory. An auto argument may be a path to a `.auto.json` file or its
|
|
43
|
+
bare name inside `autosDir`.
|
|
44
|
+
|
|
45
|
+
### Conventions every verb follows
|
|
46
|
+
|
|
47
|
+
- `--json` on every verb prints machine-readable output instead of the human-readable text, and never
|
|
48
|
+
mixes the two.
|
|
49
|
+
- No verb ever prompts. If stdin would be needed for a decision, the verb fails instead.
|
|
50
|
+
- Exit codes are the same convention everywhere: **0** ok, **1** the auto has findings errors (a
|
|
51
|
+
validation or feasibility problem in the auto itself), **2** usage, IO, or a verb that depends on a
|
|
52
|
+
`@horizon36596/zenith-core` export not landed yet.
|
|
53
|
+
- Every verb that calls a `@horizon36596/zenith-core` M1 export (`estimate`, `ledger`, `render`, `diff`) is real
|
|
54
|
+
as of this build; `src/coreGate.ts`'s `NotImplementedError` handling (`core M1 not landed yet:
|
|
55
|
+
...` to stderr, or `{"error": "..."}` under `--json`, exit code 2) is a defensive fallback rather
|
|
56
|
+
than the common case, kept so a future gap still fails with a clear message instead of a stack
|
|
57
|
+
trace.
|
|
58
|
+
- Every verb that needs season-specific rules (`validate`, `estimate`, `render`, `sim`, `propose`)
|
|
59
|
+
resolves them through `@horizon36596/zenith-seasons` from the project's `field.json`. When the field names no
|
|
60
|
+
season plugin, or one this build does not carry, the season-gated checks (`CAPACITY`,
|
|
61
|
+
`EMPTY_SHOT`, `MOUTH_LEADING`, `SWEEP_SPEED`, `START_ILLEGAL`, `LEGAL_APPROACH`, `TURRET_RANGE`)
|
|
62
|
+
and the ledger are simply absent rather than wrong; the verb still runs and prints a `warning:`
|
|
63
|
+
line to stderr (and a `seasonWarnings` array under `--json`) saying so.
|
|
64
|
+
|
|
65
|
+
### Verbs
|
|
66
|
+
|
|
67
|
+
#### `zenith validate <autos...> [--json] [--project <dir>]`
|
|
68
|
+
|
|
69
|
+
Checks one or more `.auto.json` files against the schema and the feasibility rules. Prints a findings
|
|
70
|
+
table (or JSON). Exit 1 if any file has an error-level finding.
|
|
71
|
+
|
|
72
|
+
#### `zenith init [--command-library ivy|solverslib] [--json] [--force]`
|
|
73
|
+
|
|
74
|
+
Writes a starter `zenith.json` and an `autos/` directory that validates, for a new project.
|
|
75
|
+
`--command-library` writes `deploy.commandLibrary`; the value is read in any case and written
|
|
76
|
+
lowercase. There is no default: without it the `deploy` section names no library, `init` prints a
|
|
77
|
+
note, and `zenith codegen` refuses, as does `zenith deploy` once there is a `codegen` section, until
|
|
78
|
+
`"ivy"` or `"solverslib"` is added.
|
|
79
|
+
|
|
80
|
+
#### `zenith new <name> [--alliance RED|BLUE] [--json] [--force] [--project <dir>]`
|
|
81
|
+
|
|
82
|
+
Writes a new `.auto.json` from a canonical skeleton.
|
|
83
|
+
|
|
84
|
+
#### `zenith estimate <auto> [--json] [--explain] [--project <dir>]`
|
|
85
|
+
|
|
86
|
+
Prints a per-step table (id, kind, nominal time, low-high band, strafe percentage, an unknown-time
|
|
87
|
+
flag) and the total against the field's auto period. `--explain` also prints the assumptions behind
|
|
88
|
+
the numbers (`estimate.explain`). Calls `@horizon36596/zenith-core`'s `estimate`.
|
|
89
|
+
|
|
90
|
+
#### `zenith render <auto> (--svg <out> | --png <out>) [--alliance RED|BLUE] [--base <otherAuto>] [--json] [--project <dir>]`
|
|
91
|
+
|
|
92
|
+
Draws the field, the path, findings and the ledger to SVG or PNG. PNG rendering goes through
|
|
93
|
+
`@resvg/resvg-js` (a CLI-only dependency; `@horizon36596/zenith-core` stays pure and only produces SVG text).
|
|
94
|
+
`--base` loads and validates a second auto for comparison; `@horizon36596/zenith-core`'s `render` does not yet take
|
|
95
|
+
an overlay, so today `--base` is validated but not drawn, and the CLI says so on stderr rather than
|
|
96
|
+
silently ignoring it.
|
|
97
|
+
|
|
98
|
+
#### `zenith codegen <auto> [--json] [--project <dir>]`
|
|
99
|
+
|
|
100
|
+
Writes the generated, read-only Java class described on the docs site's Robot runtime page
|
|
101
|
+
("Generated Java") to `zenith.json`'s `codegen.dir/<package path>/<PascalName>Generated.java`. The header
|
|
102
|
+
records the source file's sha256 (first 8 hex characters) so a stale generated file is easy to spot.
|
|
103
|
+
Output is deterministic: the same auto always produces byte-identical Java. The class is written for
|
|
104
|
+
the runtime `deploy.commandLibrary` names, `"ivy"` or `"solverslib"`. The key is required and has no
|
|
105
|
+
default: without it, or without a `deploy` block, the verb exits 2 with the same message as
|
|
106
|
+
`zenith deploy`.
|
|
107
|
+
|
|
108
|
+
A path step from `"current"` is written as `DeferredPath.of(...)`, the call both runtimes' `AutoBuilder`
|
|
109
|
+
makes, so it is built from the live pose when the step starts. Numbers that come from the path's
|
|
110
|
+
length (a marker placed anywhere but the start, a heading shared out by arc length) stay the planned
|
|
111
|
+
path's, and the generated method says so in a comment. `examples/starter/generated/` in the
|
|
112
|
+
repository has the output for both libraries, and
|
|
113
|
+
[Choosing a command library](https://libraries.horizon36596.org/zenith/command-libraries/) explains the
|
|
114
|
+
differences.
|
|
115
|
+
|
|
116
|
+
#### `zenith deploy [--dry-run] [--json] [--project <dir>]`
|
|
117
|
+
|
|
118
|
+
Copies `autosDir/*.auto.json`, `waypoints.json`, `robot.json` and the field file into
|
|
119
|
+
`zenith.json.deploy.dir`. When `zenith.json` has a `codegen` section, it also writes or refreshes a
|
|
120
|
+
three-line `@Autonomous` stub per auto under `codegen.dir/<package>/generated/<PascalName>Auto.java`.
|
|
121
|
+
Each stub extends `AutoFromFile` from the runtime `deploy.commandLibrary` names:
|
|
122
|
+
`org.horizon36596.zenith.ivy` for `"ivy"` or `org.horizon36596.zenith.solverslib` for `"solverslib"`.
|
|
123
|
+
The stubs are the only Java the verb writes, so the key is required, with no default, exactly when
|
|
124
|
+
there is a `codegen` section; then, without it, the verb copies nothing, exits 2, and prints:
|
|
125
|
+
|
|
126
|
+
```
|
|
127
|
+
zenith: zenith.json does not say which command library your robot code uses, and Zenith does not pick one. Add "commandLibrary": "solverslib" or "commandLibrary": "ivy" to its "deploy" section. See https://libraries.horizon36596.org/zenith/command-libraries/.
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
With no `codegen` section it only copies, and needs no library. The first line of the output names the library
|
|
131
|
+
(`command library: Ivy (org.horizon36596.zenith.ivy)`), and `--json` output carries it as
|
|
132
|
+
`commandLibrary` (`null` when there is no `codegen` section and none is named). Stubs for autos that no longer exist are removed. An auto with a findings error is skipped (reported, not
|
|
133
|
+
copied) but does not stop the other autos from deploying; overall exit code is 1 if anything was
|
|
134
|
+
skipped. `--dry-run` lists every action (`copy:`, `write:`, `keep:`, `remove:`) without touching disk.
|
|
135
|
+
|
|
136
|
+
#### `zenith diff <a> <b> [--json]`
|
|
137
|
+
|
|
138
|
+
Prints the structural diff between two autos: added, removed, changed and moved steps, as a table or
|
|
139
|
+
markdown-flavoured text (or as JSON under `--json`). Calls `@horizon36596/zenith-core`'s `diff`.
|
|
140
|
+
|
|
141
|
+
#### `zenith sim <auto> [--open] [--timeout <seconds>] [--json] [--project <dir>]`
|
|
142
|
+
|
|
143
|
+
Runs `zenith.json`'s `sim.command` (with `{auto}` substituted) through the shell, streaming its
|
|
144
|
+
output; a non-zero exit or a missing trace file fails with exit 2 and the command's output tail on
|
|
145
|
+
stderr. Reads the trace at `sim.trace` and prints estimate vs actual per step (nominal, actual, delta,
|
|
146
|
+
and the largest cross-track distance between recorded poses and the planned path) plus a whole-run
|
|
147
|
+
summary (structure contacts, launches, tips, pieces held at the end). `--open` also renders the plan
|
|
148
|
+
to `autosDir/.renders/<name>.sim.svg` and prints its path, without spawning an image viewer.
|
|
149
|
+
|
|
150
|
+
#### `zenith calibrate [--traces <dir>] [--write] [--date <date>] [--json] [--project <dir>]`
|
|
151
|
+
|
|
152
|
+
Pairs every path step's recorded duration (from every `*.trace.json` in `--traces`, or
|
|
153
|
+
`traces/`, or `sim.trace`'s directory) against the plan's geometry, and fits `accelInPerS2`, `settleS`,
|
|
154
|
+
and a per-heading-mode scale by a grid search over the trapezoidal time model
|
|
155
|
+
(the docs site's Simulation page). Reports the residual band left after both
|
|
156
|
+
corrections. `--write` updates `robot.json`'s `kinematics.accelInPerS2` and `kinematics.settleS` with
|
|
157
|
+
provenance `CALIBRATED FROM SIM <date> (<n> steps)`; the per-mode scale has no field in
|
|
158
|
+
`@horizon36596/zenith-schema` yet, so it is reported but not persisted. The CLI reads the clock for the provenance
|
|
159
|
+
date (or takes `--date`); `@horizon36596/zenith-core`'s fit itself never does.
|
|
160
|
+
|
|
161
|
+
#### `zenith propose <auto> [--dry-run] [--base <branch>] [--json] [--project <dir>]`
|
|
162
|
+
|
|
163
|
+
The CLI half of proposing an auto change as a pull request (the docs site's GitHub page). Only `--dry-run` is implemented
|
|
164
|
+
here: it validates the auto (a findings error blocks with exit 1), renders an SVG to
|
|
165
|
+
`autosDir/.renders/<name>.svg`, and prints the PR body — `@horizon36596/zenith-core`'s `prBody`, given the local
|
|
166
|
+
render path directly as `renderUrl` since a `--dry-run` run has made no commit for a real GitHub URL
|
|
167
|
+
to point at — to stdout. Without `--dry-run` it prints "GitHub mode: use the web app or pass
|
|
168
|
+
--dry-run" and exits 2, because opening an actual pull request is `@horizon36596/zenith-github`'s job, which this
|
|
169
|
+
package does not depend on.
|
|
170
|
+
|
|
171
|
+
`zenith diff` and `zenith propose --dry-run` both format through `@horizon36596/zenith-core`'s own
|
|
172
|
+
`diffToMarkdown`/`prBody` (this package carries no separate copy of either), so a diff printed by the
|
|
173
|
+
CLI and a PR body opened by `@horizon36596/zenith-github` read the same way for the same inputs.
|
|
174
|
+
|
|
175
|
+
### Driving it with `--json`
|
|
176
|
+
|
|
177
|
+
Every verb above is safe for an agent or a script to call directly: pass `--json`, read stdout as one
|
|
178
|
+
JSON document, and use the exit code to decide whether to keep going.
|
|
179
|
+
|
|
180
|
+
- Check the exit code first. `0` means the JSON is the successful result. `1` means the auto has a
|
|
181
|
+
findings error; the JSON is a findings list, not the thing you asked for. `2` means something
|
|
182
|
+
prevented the verb from running at all (bad usage, a missing project, an IO error, or a `@horizon36596/zenith-core`
|
|
183
|
+
export not landed yet); the JSON (where the verb prints one on `2`) is `{"error": "..."}`.
|
|
184
|
+
- Never parse the human-readable (non-`--json`) output. It is formatted for a terminal and can change
|
|
185
|
+
wording between versions without notice; the JSON shape is the contract.
|
|
186
|
+
- `zenith estimate <auto> --json` and `zenith sim <auto> --json` are the two verbs most useful for an
|
|
187
|
+
agent checking its own work after editing an auto: estimate answers "does this still fit the auto
|
|
188
|
+
period", and sim (once a trace exists) answers "did the robot actually do what the plan said".
|
|
189
|
+
- `zenith propose <auto> --dry-run --json` is the one to call before asking a human to open a real pull
|
|
190
|
+
request: it fails loudly (exit 1) if the auto does not validate, and otherwise hands back the exact
|
|
191
|
+
PR body text under `body` and the render path under `render`.
|
|
192
|
+
- A verb that depends on a `@horizon36596/zenith-core` M1 export not yet landed in this checkout (`estimate`,
|
|
193
|
+
`ledger`, `render`, `diff`) exits 2 with `{"error": "core M1 not landed yet: ..."}` rather than a
|
|
194
|
+
stack trace, so a script can detect "not ready yet" and retry later instead of treating it as its own
|
|
195
|
+
bug.
|
|
196
|
+
|
|
197
|
+
### Tests
|
|
198
|
+
|
|
199
|
+
```
|
|
200
|
+
pnpm --filter @horizon36596/zenith-cli test
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
Each verb has a test file next to its command module under `src/commands/`, plus tests for the pure
|
|
204
|
+
helpers (`src/javaCodegen.ts`, `src/calibrateFit.ts`, `src/traceReport.ts`, `src/format/`). Tests for
|
|
205
|
+
verbs that call into a `@horizon36596/zenith-core` M1 export check both branches - the real result once that export
|
|
206
|
+
has landed, and the `core M1 not landed yet` gate message before it has - so the suite passes and stays
|
|
207
|
+
meaningful throughout M1, not just at the end of it.
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { type Finding, type Plan } from "@horizon36596/zenith-core";
|
|
2
|
+
import { type Auto, type Field, type Robot } from "@horizon36596/zenith-schema";
|
|
3
|
+
import { type Project } from "./project.js";
|
|
4
|
+
/** One auto file, parsed, resolved, planned and checked, ready for a verb to use. */
|
|
5
|
+
export interface LoadedAuto {
|
|
6
|
+
path: string;
|
|
7
|
+
auto: Auto;
|
|
8
|
+
robot: Robot;
|
|
9
|
+
field: Field;
|
|
10
|
+
plan: Plan;
|
|
11
|
+
/** Every finding `check()` raised, schema problems included. */
|
|
12
|
+
findings: Finding[];
|
|
13
|
+
/**
|
|
14
|
+
* Full sentences from `@horizon36596/zenith-seasons` when `field.json` names a season plugin this build does
|
|
15
|
+
* not carry (or none at all): the routine still plans and estimates, but every season-dependent
|
|
16
|
+
* check and ledger row is absent, and a caller should say so rather than stay silent about it.
|
|
17
|
+
*/
|
|
18
|
+
seasonWarnings: string[];
|
|
19
|
+
}
|
|
20
|
+
export type LoadAutoResult = {
|
|
21
|
+
ok: true;
|
|
22
|
+
loaded: LoadedAuto;
|
|
23
|
+
} | {
|
|
24
|
+
ok: false;
|
|
25
|
+
path: string;
|
|
26
|
+
findings: Finding[];
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* `auto.name` is substituted into a shell command (`zenith sim`) and into filesystem paths
|
|
30
|
+
* (`zenith sim --open`, `zenith propose`) elsewhere in this package. Constraining it here, at the
|
|
31
|
+
* one place every verb loads an auto, means every one of those call sites can use the value without
|
|
32
|
+
* re-checking it: no shell metacharacter, path separator or ".." segment can ever reach them
|
|
33
|
+
* through `auto.name`.
|
|
34
|
+
*/
|
|
35
|
+
export declare const AUTO_NAME_PATTERN: RegExp;
|
|
36
|
+
export declare function assertValidAutoName(name: string): void;
|
|
37
|
+
/**
|
|
38
|
+
* Turns a verb's `<auto>` argument into a file path: a path that exists as given, or a bare name
|
|
39
|
+
* resolved against the project's `autosDir`. Falls back to the given path so a bad argument still
|
|
40
|
+
* fails with a plain "cannot read" message rather than a confusing one about the autos directory.
|
|
41
|
+
*/
|
|
42
|
+
export declare function resolveAutoPath(cwd: string, project: Project, autoArg: string): string;
|
|
43
|
+
/**
|
|
44
|
+
* A schema issue's `path` (e.g. `["steps", 4, "segments", 0, "control"]`) names a JSON location,
|
|
45
|
+
* not a step id, so a naive `path.join(".")` used as `Finding.stepId` sends `selectStep` nowhere
|
|
46
|
+
* an agent or the editor can act on (finding 26). This walks the raw (unvalidated) JSON back along
|
|
47
|
+
* the issue's path, remembering the deepest `steps`/`then`/`else` array index it passes through,
|
|
48
|
+
* and returns that step's own `id` when it has one, or its positional id (`step<n>`) otherwise —
|
|
49
|
+
* the same convention `@horizon36596/zenith-core`'s `effectiveId` uses for a top-level, unnamed step.
|
|
50
|
+
*/
|
|
51
|
+
export declare function nearestStepId(rawJson: unknown, path: readonly (string | number)[]): string;
|
|
52
|
+
/** Parses, resolves, plans and checks one auto file. Never throws; schema failures come back as findings. */
|
|
53
|
+
export declare function loadAutoAndPlan(path: string, project: Project): LoadAutoResult;
|
|
54
|
+
//# sourceMappingURL=autoLoad.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"autoLoad.d.ts","sourceRoot":"","sources":["../src/autoLoad.ts"],"names":[],"mappings":"AAEA,OAAO,EAAkE,KAAK,OAAO,EAAE,KAAK,IAAI,EAAE,MAAM,2BAA2B,CAAC;AAEpI,OAAO,EAAe,KAAK,IAAI,EAAE,KAAK,KAAK,EAAE,KAAK,KAAK,EAAE,MAAM,6BAA6B,CAAC;AAC7F,OAAO,EAAgB,KAAK,OAAO,EAAE,MAAM,cAAc,CAAC;AAE1D,qFAAqF;AACrF,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,IAAI,CAAC;IACX,KAAK,EAAE,KAAK,CAAC;IACb,KAAK,EAAE,KAAK,CAAC;IACb,IAAI,EAAE,IAAI,CAAC;IACX,gEAAgE;IAChE,QAAQ,EAAE,OAAO,EAAE,CAAC;IACpB;;;;OAIG;IACH,cAAc,EAAE,MAAM,EAAE,CAAC;CAC1B;AAED,MAAM,MAAM,cAAc,GAAG;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,MAAM,EAAE,UAAU,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,OAAO,EAAE,CAAA;CAAE,CAAC;AASjH;;;;;;GAMG;AACH,eAAO,MAAM,iBAAiB,QAAsB,CAAC;AAErD,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAMtD;AA0BD;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAatF;AAED;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,SAAS,CAAC,MAAM,GAAG,MAAM,CAAC,EAAE,GAAG,MAAM,CAyB1F;AAED,6GAA6G;AAC7G,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,cAAc,CAwC9E"}
|
package/dist/autoLoad.js
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { isAbsolute, join, resolve as resolvePath } from "node:path";
|
|
3
|
+
import { check, estimate, loadAuto, loadField, loadRobot, plan, resolve } from "@horizon36596/zenith-core";
|
|
4
|
+
import { resolveSeason } from "@horizon36596/zenith-seasons";
|
|
5
|
+
import { SchemaError } from "@horizon36596/zenith-schema";
|
|
6
|
+
import { readJsonFile } from "./project.js";
|
|
7
|
+
const schemaFinding = (stepId, message) => ({
|
|
8
|
+
severity: "error",
|
|
9
|
+
stepId,
|
|
10
|
+
code: "SCHEMA",
|
|
11
|
+
message,
|
|
12
|
+
});
|
|
13
|
+
/**
|
|
14
|
+
* `auto.name` is substituted into a shell command (`zenith sim`) and into filesystem paths
|
|
15
|
+
* (`zenith sim --open`, `zenith propose`) elsewhere in this package. Constraining it here, at the
|
|
16
|
+
* one place every verb loads an auto, means every one of those call sites can use the value without
|
|
17
|
+
* re-checking it: no shell metacharacter, path separator or ".." segment can ever reach them
|
|
18
|
+
* through `auto.name`.
|
|
19
|
+
*/
|
|
20
|
+
export const AUTO_NAME_PATTERN = /^[A-Za-z0-9._-]+$/;
|
|
21
|
+
export function assertValidAutoName(name) {
|
|
22
|
+
if (!AUTO_NAME_PATTERN.test(name)) {
|
|
23
|
+
throw new Error(`Invalid auto file:\n name: must match ${AUTO_NAME_PATTERN.source} (letters, digits, ".", "_", "-" only); got ${JSON.stringify(name)}.`);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* An auto may name its own robot or field file; when it does, that file wins over the link file's,
|
|
28
|
+
* and a broken override is a finding rather than a crash. Mirrors `commands/validate.ts`'s
|
|
29
|
+
* `override`, kept separate so this module has no dependency on that command.
|
|
30
|
+
*/
|
|
31
|
+
function override(project, path, load, fallback, findings) {
|
|
32
|
+
if (path === undefined)
|
|
33
|
+
return fallback;
|
|
34
|
+
const full = isAbsolute(path) ? path : join(project.root, path);
|
|
35
|
+
try {
|
|
36
|
+
return load(readJsonFile(full));
|
|
37
|
+
}
|
|
38
|
+
catch (error) {
|
|
39
|
+
findings.push(schemaFinding("(file)", `Cannot use ${path}: ${error.message.split("\n")[0] ?? ""}`));
|
|
40
|
+
return fallback;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Turns a verb's `<auto>` argument into a file path: a path that exists as given, or a bare name
|
|
45
|
+
* resolved against the project's `autosDir`. Falls back to the given path so a bad argument still
|
|
46
|
+
* fails with a plain "cannot read" message rather than a confusing one about the autos directory.
|
|
47
|
+
*/
|
|
48
|
+
export function resolveAutoPath(cwd, project, autoArg) {
|
|
49
|
+
const direct = isAbsolute(autoArg) ? autoArg : resolvePath(cwd, autoArg);
|
|
50
|
+
if (existsSync(direct))
|
|
51
|
+
return direct;
|
|
52
|
+
// A path (as opposed to a bare name) may be meant relative to the project root rather than the
|
|
53
|
+
// working directory, e.g. `--project examples/starter` paired with `autos/x.auto.json` when the
|
|
54
|
+
// shell's cwd is the repository root, not the project.
|
|
55
|
+
if (!isAbsolute(autoArg)) {
|
|
56
|
+
const inProject = join(project.root, autoArg);
|
|
57
|
+
if (inProject !== direct && existsSync(inProject))
|
|
58
|
+
return inProject;
|
|
59
|
+
}
|
|
60
|
+
const byName = join(project.root, project.link.autosDir, `${autoArg}.auto.json`);
|
|
61
|
+
if (existsSync(byName))
|
|
62
|
+
return byName;
|
|
63
|
+
return direct;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* A schema issue's `path` (e.g. `["steps", 4, "segments", 0, "control"]`) names a JSON location,
|
|
67
|
+
* not a step id, so a naive `path.join(".")` used as `Finding.stepId` sends `selectStep` nowhere
|
|
68
|
+
* an agent or the editor can act on (finding 26). This walks the raw (unvalidated) JSON back along
|
|
69
|
+
* the issue's path, remembering the deepest `steps`/`then`/`else` array index it passes through,
|
|
70
|
+
* and returns that step's own `id` when it has one, or its positional id (`step<n>`) otherwise —
|
|
71
|
+
* the same convention `@horizon36596/zenith-core`'s `effectiveId` uses for a top-level, unnamed step.
|
|
72
|
+
*/
|
|
73
|
+
export function nearestStepId(rawJson, path) {
|
|
74
|
+
let node = rawJson;
|
|
75
|
+
let lastStep;
|
|
76
|
+
let lastIndex = -1;
|
|
77
|
+
const stepListKeys = new Set(["steps", "then", "else"]);
|
|
78
|
+
for (let i = 0; i < path.length; i += 1) {
|
|
79
|
+
const key = path[i];
|
|
80
|
+
if (node === null || typeof node !== "object")
|
|
81
|
+
break;
|
|
82
|
+
const next = Array.isArray(node)
|
|
83
|
+
? node[key]
|
|
84
|
+
: node[key];
|
|
85
|
+
if (typeof key === "string" && stepListKeys.has(key) && typeof path[i + 1] === "number") {
|
|
86
|
+
const idx = path[i + 1];
|
|
87
|
+
if (Array.isArray(next) && idx >= 0 && idx < next.length) {
|
|
88
|
+
lastStep = next[idx];
|
|
89
|
+
lastIndex = idx;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
node = next;
|
|
93
|
+
}
|
|
94
|
+
if (lastStep !== undefined && lastStep !== null && typeof lastStep === "object") {
|
|
95
|
+
const id = lastStep.id;
|
|
96
|
+
if (typeof id === "string" && id.length > 0)
|
|
97
|
+
return id;
|
|
98
|
+
}
|
|
99
|
+
return lastIndex >= 0 ? `step${String(lastIndex + 1)}` : "(file)";
|
|
100
|
+
}
|
|
101
|
+
/** Parses, resolves, plans and checks one auto file. Never throws; schema failures come back as findings. */
|
|
102
|
+
export function loadAutoAndPlan(path, project) {
|
|
103
|
+
let json;
|
|
104
|
+
try {
|
|
105
|
+
json = readJsonFile(path);
|
|
106
|
+
}
|
|
107
|
+
catch (error) {
|
|
108
|
+
return { ok: false, path, findings: [schemaFinding("(file)", error.message)] };
|
|
109
|
+
}
|
|
110
|
+
try {
|
|
111
|
+
const auto = loadAuto(json);
|
|
112
|
+
assertValidAutoName(auto.name);
|
|
113
|
+
const findings = [];
|
|
114
|
+
const robot = override(project, auto.robot, loadRobot, project.robot, findings);
|
|
115
|
+
const field = override(project, auto.field, loadField, project.field, findings);
|
|
116
|
+
const planned = plan(resolve(auto, project.waypoints), robot, field);
|
|
117
|
+
const season = resolveSeason(field);
|
|
118
|
+
findings.push(...check(planned, estimate(planned, robot), robot, field, season.rules));
|
|
119
|
+
return {
|
|
120
|
+
ok: true,
|
|
121
|
+
loaded: { path, auto, robot, field, plan: planned, findings, seasonWarnings: [...season.warnings] },
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
catch (error) {
|
|
125
|
+
if (error instanceof SchemaError) {
|
|
126
|
+
return {
|
|
127
|
+
ok: false,
|
|
128
|
+
path,
|
|
129
|
+
findings: error.issues.map((issue) => schemaFinding(issue.path.length === 0 ? "(file)" : nearestStepId(json, issue.path), `${issue.path.join(".")}: ${issue.message}`)),
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
return {
|
|
133
|
+
ok: false,
|
|
134
|
+
path,
|
|
135
|
+
findings: [schemaFinding("(file)", error instanceof Error ? error.message : String(error))],
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
//# sourceMappingURL=autoLoad.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"autoLoad.js","sourceRoot":"","sources":["../src/autoLoad.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `zenith calibrate`'s fit (site/docs/simulation.md): pairs a trace's
|
|
3
|
+
* recorded step durations against the plan's geometry and fits the free parameters of the time
|
|
4
|
+
* model - `accel`, `settleS`, and a per
|
|
5
|
+
* heading-mode scale. Pure: no clock, no randomness, so the same samples always fit the same
|
|
6
|
+
* numbers and a test can assert on them exactly.
|
|
7
|
+
*
|
|
8
|
+
* The fit is deliberately simple rather than the full two-pass velocity profile `core.estimate`
|
|
9
|
+
* will run once M1 lands: a symmetric trapezoidal accelerate/cruise/decelerate model against a
|
|
10
|
+
* fixed cruise speed (the robot's own measured `maxForwardVelInPerS`), searched on a coarse grid
|
|
11
|
+
* over `accel` and `settleS` (the spec allows "simple grid or closed form"). The per-mode scale
|
|
12
|
+
* then absorbs whatever the trapezoid model does not capture for a `constant` or `linear` heading
|
|
13
|
+
* leg, which behaves more like a strafe. The residual band is what is left after both corrections,
|
|
14
|
+
* reported as the fraction the estimate should still expect to be wrong by.
|
|
15
|
+
*/
|
|
16
|
+
export interface CalibrationSample {
|
|
17
|
+
stepId: string;
|
|
18
|
+
/** The path step's heading mode, or "none" for a step this fit does not otherwise use. */
|
|
19
|
+
headingMode: string;
|
|
20
|
+
lengthIn: number;
|
|
21
|
+
actualS: number;
|
|
22
|
+
}
|
|
23
|
+
export type ModeScale = Record<string, number>;
|
|
24
|
+
export interface CalibrationResult {
|
|
25
|
+
accelInPerS2: number;
|
|
26
|
+
settleS: number;
|
|
27
|
+
scaleByMode: ModeScale;
|
|
28
|
+
/** The residual band as fractions of the actual duration, e.g. -0.12 to 0.15. Zero when there are
|
|
29
|
+
* no samples to measure a residual from. */
|
|
30
|
+
residualLowFraction: number;
|
|
31
|
+
residualHighFraction: number;
|
|
32
|
+
sampleCount: number;
|
|
33
|
+
}
|
|
34
|
+
/** A symmetric trapezoid: accelerate to `cruiseInPerS` (or to a lower peak on a short leg), cruise,
|
|
35
|
+
* decelerate at the same rate, then add the settle time. */
|
|
36
|
+
export declare function predictTravelS(lengthIn: number, cruiseInPerS: number, accelInPerS2: number, settleS: number): number;
|
|
37
|
+
export declare function fitCalibration(samples: readonly CalibrationSample[], maxForwardVelInPerS: number): CalibrationResult;
|
|
38
|
+
//# sourceMappingURL=calibrateFit.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"calibrateFit.d.ts","sourceRoot":"","sources":["../src/calibrateFit.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,MAAM,CAAC;IACf,0FAA0F;IAC1F,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,MAAM,SAAS,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;AAE/C,MAAM,WAAW,iBAAiB;IAChC,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,SAAS,CAAC;IACvB;gDAC4C;IAC5C,mBAAmB,EAAE,MAAM,CAAC;IAC5B,oBAAoB,EAAE,MAAM,CAAC;IAC7B,WAAW,EAAE,MAAM,CAAC;CACrB;AAKD;4DAC4D;AAC5D,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CASpH;AAYD,wBAAgB,cAAc,CAAC,OAAO,EAAE,SAAS,iBAAiB,EAAE,EAAE,mBAAmB,EAAE,MAAM,GAAG,iBAAiB,CA+CpH"}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
const ACCEL_GRID = { minInPerS2: 10, maxInPerS2: 220, stepInPerS2: 5 };
|
|
2
|
+
const SETTLE_GRID = { minS: 0, maxS: 1, stepS: 0.05 };
|
|
3
|
+
/** A symmetric trapezoid: accelerate to `cruiseInPerS` (or to a lower peak on a short leg), cruise,
|
|
4
|
+
* decelerate at the same rate, then add the settle time. */
|
|
5
|
+
export function predictTravelS(lengthIn, cruiseInPerS, accelInPerS2, settleS) {
|
|
6
|
+
if (lengthIn <= 0 || cruiseInPerS <= 0 || accelInPerS2 <= 0)
|
|
7
|
+
return settleS;
|
|
8
|
+
const accelDistanceIn = (cruiseInPerS * cruiseInPerS) / (2 * accelInPerS2);
|
|
9
|
+
if (2 * accelDistanceIn <= lengthIn) {
|
|
10
|
+
const cruiseS = (lengthIn - 2 * accelDistanceIn) / cruiseInPerS;
|
|
11
|
+
return 2 * (cruiseInPerS / accelInPerS2) + cruiseS + settleS;
|
|
12
|
+
}
|
|
13
|
+
const peakInPerS = Math.sqrt(accelInPerS2 * lengthIn);
|
|
14
|
+
return (2 * peakInPerS) / accelInPerS2 + settleS;
|
|
15
|
+
}
|
|
16
|
+
function sumSquaredError(samples, cruiseInPerS, accelInPerS2, settleS) {
|
|
17
|
+
let error = 0;
|
|
18
|
+
for (const sample of samples) {
|
|
19
|
+
const predicted = predictTravelS(sample.lengthIn, cruiseInPerS, accelInPerS2, settleS);
|
|
20
|
+
const residual = predicted - sample.actualS;
|
|
21
|
+
error += residual * residual;
|
|
22
|
+
}
|
|
23
|
+
return error;
|
|
24
|
+
}
|
|
25
|
+
export function fitCalibration(samples, maxForwardVelInPerS) {
|
|
26
|
+
const usable = samples.filter((sample) => sample.lengthIn > 0 && sample.actualS > 0);
|
|
27
|
+
if (usable.length === 0) {
|
|
28
|
+
return { accelInPerS2: 0, settleS: 0, scaleByMode: {}, residualLowFraction: 0, residualHighFraction: 0, sampleCount: 0 };
|
|
29
|
+
}
|
|
30
|
+
let bestAccel = ACCEL_GRID.minInPerS2;
|
|
31
|
+
let bestSettle = SETTLE_GRID.minS;
|
|
32
|
+
let bestError = Number.POSITIVE_INFINITY;
|
|
33
|
+
for (let accel = ACCEL_GRID.minInPerS2; accel <= ACCEL_GRID.maxInPerS2; accel += ACCEL_GRID.stepInPerS2) {
|
|
34
|
+
for (let settle = SETTLE_GRID.minS; settle <= SETTLE_GRID.maxS; settle += SETTLE_GRID.stepS) {
|
|
35
|
+
const error = sumSquaredError(usable, maxForwardVelInPerS, accel, settle);
|
|
36
|
+
if (error < bestError) {
|
|
37
|
+
bestError = error;
|
|
38
|
+
bestAccel = accel;
|
|
39
|
+
bestSettle = settle;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
const ratiosByMode = new Map();
|
|
44
|
+
for (const sample of usable) {
|
|
45
|
+
const predicted = predictTravelS(sample.lengthIn, maxForwardVelInPerS, bestAccel, bestSettle);
|
|
46
|
+
const ratio = predicted === 0 ? 1 : sample.actualS / predicted;
|
|
47
|
+
const list = ratiosByMode.get(sample.headingMode) ?? [];
|
|
48
|
+
list.push(ratio);
|
|
49
|
+
ratiosByMode.set(sample.headingMode, list);
|
|
50
|
+
}
|
|
51
|
+
const scaleByMode = {};
|
|
52
|
+
for (const [mode, ratios] of ratiosByMode) {
|
|
53
|
+
scaleByMode[mode] = ratios.reduce((sum, ratio) => sum + ratio, 0) / ratios.length;
|
|
54
|
+
}
|
|
55
|
+
const residualFractions = usable.map((sample) => {
|
|
56
|
+
const scale = scaleByMode[sample.headingMode] ?? 1;
|
|
57
|
+
const predicted = predictTravelS(sample.lengthIn, maxForwardVelInPerS, bestAccel, bestSettle) * scale;
|
|
58
|
+
return (predicted - sample.actualS) / sample.actualS;
|
|
59
|
+
});
|
|
60
|
+
return {
|
|
61
|
+
accelInPerS2: bestAccel,
|
|
62
|
+
settleS: bestSettle,
|
|
63
|
+
scaleByMode,
|
|
64
|
+
residualLowFraction: Math.min(0, ...residualFractions),
|
|
65
|
+
residualHighFraction: Math.max(0, ...residualFractions),
|
|
66
|
+
sampleCount: usable.length,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
//# sourceMappingURL=calibrateFit.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"calibrateFit.js","sourceRoot":"","sources":["../src/calibrateFit.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { Link } from "@horizon36596/zenith-schema";
|
|
2
|
+
/**
|
|
3
|
+
* The command library the robot runtime builds on, from `zenith.json` `deploy.commandLibrary`
|
|
4
|
+
* (site/docs/command-libraries.md). Neither library is a default: every command that writes Java
|
|
5
|
+
* refuses until the project names one.
|
|
6
|
+
*/
|
|
7
|
+
export type CommandLibrary = "solverslib" | "ivy";
|
|
8
|
+
/** Both libraries, in the order Zenith lists them. Neither is a default. */
|
|
9
|
+
export declare const LIBRARIES: readonly CommandLibrary[];
|
|
10
|
+
/** The Java package each library's runtime classes live in. */
|
|
11
|
+
export declare const RUNTIME_PACKAGE: Readonly<Record<CommandLibrary, string>>;
|
|
12
|
+
/** The name a person reads, for CLI output and the desktop deploy preview. */
|
|
13
|
+
export declare const LIBRARY_LABEL: Readonly<Record<CommandLibrary, string>>;
|
|
14
|
+
/**
|
|
15
|
+
* @returns the project's command library
|
|
16
|
+
* @throws ProjectError when `zenith.json` does not name one; Zenith never picks one for a team
|
|
17
|
+
*/
|
|
18
|
+
export declare function commandLibraryOf(link: Link): CommandLibrary;
|
|
19
|
+
//# sourceMappingURL=commandLibrary.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"commandLibrary.d.ts","sourceRoot":"","sources":["../src/commandLibrary.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,6BAA6B,CAAC;AAGxD;;;;GAIG;AACH,MAAM,MAAM,cAAc,GAAG,YAAY,GAAG,KAAK,CAAC;AAElD,4EAA4E;AAC5E,eAAO,MAAM,SAAS,EAAE,SAAS,cAAc,EAA0B,CAAC;AAE1E,+DAA+D;AAC/D,eAAO,MAAM,eAAe,EAAE,QAAQ,CAAC,MAAM,CAAC,cAAc,EAAE,MAAM,CAAC,CAGpE,CAAC;AAEF,8EAA8E;AAC9E,eAAO,MAAM,aAAa,EAAE,QAAQ,CAAC,MAAM,CAAC,cAAc,EAAE,MAAM,CAAC,CAGlE,CAAC;AAEF;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,IAAI,GAAG,cAAc,CAU3D"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { ProjectError } from "./project.js";
|
|
2
|
+
/** Both libraries, in the order Zenith lists them. Neither is a default. */
|
|
3
|
+
export const LIBRARIES = ["solverslib", "ivy"];
|
|
4
|
+
/** The Java package each library's runtime classes live in. */
|
|
5
|
+
export const RUNTIME_PACKAGE = {
|
|
6
|
+
solverslib: "org.horizon36596.zenith.solverslib",
|
|
7
|
+
ivy: "org.horizon36596.zenith.ivy",
|
|
8
|
+
};
|
|
9
|
+
/** The name a person reads, for CLI output and the desktop deploy preview. */
|
|
10
|
+
export const LIBRARY_LABEL = {
|
|
11
|
+
solverslib: "SolversLib",
|
|
12
|
+
ivy: "Ivy",
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* @returns the project's command library
|
|
16
|
+
* @throws ProjectError when `zenith.json` does not name one; Zenith never picks one for a team
|
|
17
|
+
*/
|
|
18
|
+
export function commandLibraryOf(link) {
|
|
19
|
+
const library = link.deploy?.commandLibrary;
|
|
20
|
+
if (library === undefined) {
|
|
21
|
+
throw new ProjectError('zenith.json does not say which command library your robot code uses, and Zenith does not pick one. ' +
|
|
22
|
+
'Add "commandLibrary": "solverslib" or "commandLibrary": "ivy" to its "deploy" section. ' +
|
|
23
|
+
"See https://libraries.horizon36596.org/zenith/command-libraries/.");
|
|
24
|
+
}
|
|
25
|
+
return library;
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=commandLibrary.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"commandLibrary.js","sourceRoot":"","sources":["../src/commandLibrary.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAS5C,4EAA4E;AAC5E,MAAM,CAAC,MAAM,SAAS,GAA8B,CAAC,YAAY,EAAE,KAAK,CAAC,CAAC;AAE1E,+DAA+D;AAC/D,MAAM,CAAC,MAAM,eAAe,GAA6C;IACvE,UAAU,EAAE,oCAAoC;IAChD,GAAG,EAAE,6BAA6B;CACnC,CAAC;AAEF,8EAA8E;AAC9E,MAAM,CAAC,MAAM,aAAa,GAA6C;IACrE,UAAU,EAAE,YAAY;IACxB,GAAG,EAAE,KAAK;CACX,CAAC;AAEF;;;GAGG;AACH,MAAM,UAAU,gBAAgB,CAAC,IAAU;IACzC,MAAM,OAAO,GAAG,IAAI,CAAC,MAAM,EAAE,cAAc,CAAC;IAC5C,IAAI,OAAO,KAAK,SAAS,EAAE,CAAC;QAC1B,MAAM,IAAI,YAAY,CACpB,qGAAqG;YACnG,yFAAyF;YACzF,mEAAmE,CACtE,CAAC;IACJ,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC"}
|