Kinetic
The motion planner. It turns timed knots into jerk-limited motion, with bit-identical results on every chip.
A header-only C++ library with no hardware dependencies. It is the planner in Nucleus, and you can use it on its own. No release yet. Apache-2.0.
How it plans
- Knots in, a sampled trajectory out
Every source becomes knots: the curve passes position
pat timet_us, optionally with an end velocity. The planner never sees a wire format, so a script and a live stream feed it the same way. - Event-driven
Pending knots are solved when they change, at the next sample. The curve is continuous in position and velocity, so the motor never gets a step in speed.
- Timing comes first
A knot the limits cannot reach in time is moved toward the previous knot. It is never dropped or placed late, so a script keeps its timing and gives up some stroke length instead.
- Limits are ceilings
The planner stays under the velocity, acceleration and jerk limits and does not plan to reach them. Every trim, clamp and refusal is recorded as an anomaly you can read, so you can see where a pattern asked for too much.
- Knots Each knot is a time and a position, classed by its chords as an end, a crest or a through point, so the planner knows where the stroke turns before it draws anything.
- Angles Crests and ends lie flat. A through point takes pchip's angle, then four sweeps match its acceleration on both sides, inside the monotone band. Free knots use one of two styles, or a blend: crisp, which never overshoots between monotone knots, or smooth, which may overshoot but keeps acceleration continuous. This trace is crisp.
- Handle lengths Every handle starts at a third of its span. A piece over a ceiling scales both by one factor, nearest 1 first, and skips the side that cannot help: speed over wants shorter handles, acceleration or jerk over wants longer.
- Legality check Speed, acceleration and jerk are read at 11 points on the piece, then exactly at their turning points, bracketed and bisected. A piece passes within 0.1% of every ceiling.
- Trim When no length works, the knot moves toward the one before it by the least legal amount, found in 16 halvings, and keeps its time. Here a 72 mm stroke keeps 53.4 mm, 74%.
- 1 ms samples The pieces join as one composite Bézier, with a corner ramp at jmax where two accelerations differ, and the firmware samples it every 1 ms, 128 samples ahead, so a planner stall shorter than 128 ms doesn't reach the motor.
A trace of the real planner building one script, the same bits as its WebAssembly build, on a 100 mm window with the handles page's ceilings: 1,000 mm/s, 50,000 mm/s², 5,000,000 mm/s³.
Determinism
The plan you preview in the browser is the plan the firmware runs, as long as both are built with the same compiler flag.
- Difference between native, WebAssembly and ESP32-P4 builds of the same plan, with -ffp-contract=off.
- 0ULP
How that holds
Quantities are float; time is uint64_t microseconds that you supply. The same calls give bit-identical results on every IEEE-754 target, as long as everything that compiles the planner uses -ffp-contract=off and not -ffast-math. Without the flag, GCC fuses multiply-adds on targets such as the ESP32-P4 and the results drift by a few ULPs from native and WebAssembly builds. The test suite pins a fingerprint of the rendered motion.
Using the library
You call submit() with knots and positionAt() on your own clock. It needs C++20 and builds with CMake or PlatformIO. A WebAssembly build with a small C interface is in the repo.
Units, and what it leaves to you
Position is normalized 0 to 1 across the stroke window you give it, so mapping to millimeters, steps or encoder counts is your code's job.
Kinetic plans motion inside a window. Homing, rail limits, e-stop, arbitration and step generation belong to the machine around it, and in OpenValence that is Nucleus.
In Nucleus, Valence motion segments become knots in the firmware's motion arbiter. The diagrams show where the planner sits, and Components lists the other parts.
Repo · Playground · Nucleus · Diagrams · Components