Skip to content

226 explore using java for autos - #227

Merged
godmar merged 20 commits into
mainfrom
226-explore-using-java-for-autos
Jun 19, 2026
Merged

226 explore using java for autos#227
godmar merged 20 commits into
mainfrom
226-explore-using-java-for-autos

Conversation

@godmar

@godmar godmar commented Jun 17, 2026

Copy link
Copy Markdown
Contributor

First attempt by Claude

godmar and others added 3 commits June 16, 2026 21:40
Replace the Python auto-serialization layer with a Java generator that
builds the Autos object graph and serializes it via the robot's own
coppercore Gson config, so the JSON round-trips through the robot by
construction.

- auto_generator_java/: Dsl (builders + sequence/parallel/race + geometry
  and autopilot helpers), AutosGen (all autos/routines ported from
  autos.py/routines.py/constants.py), TypeTagger (sets the @jsontype
  discriminator the polymorph adapter doesn't write), Field (loads the
  AprilTag layout headlessly, honoring the fieldType deploy constant, and
  reuses FieldConstants.Tower for climb poses).
- generate.sh + dump-classpath.gradle: compile/run via javac+java against
  the cached classpath, bypassing gradle on the hot path (~1.8s vs gradle's
  ~4.3s warm floor).
- build_autos.py now invokes the Java generator; publish + structural
  round-trip verification are unchanged.
- check_auto_roundtrip.py bootstraps the generator before launching the sim.
- Add authoring constructors to Auto, AutoPilotAction, XBasedAutoPilotAction,
  NetworkConfigurableWait; make FollowPathPlannerPath's RobotConfig lazy so
  the class loads without NetworkTables.
- Remove the Python-serialization layer: frc/robot/util/ts/* (PythonGenerator,
  Python*/Generated* annotations), the SIM codegen block in JsonConstants, and
  auto_generator/src/*.py.

Output is structurally identical to the previous Autos.json except the two
climb-routine poses, which now use the WELDED layout from the deploy constant
instead of the andymark layout the Python generator had hardcoded. The
committed Autos.json is left unchanged in this baseline.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Structurally identical to the previous file except the LeftClimb/RightClimb
target poses, which now use the WELDED AprilTag layout (the deploy fieldType
constant) instead of the andymark layout the old Python generator hardcoded.

The remaining line churn is formatting only: the Java/gson output uses 2-space
indentation and always emits doubles (e.g. -90.0, 1.0), whereas the Python
output used 4-space indentation and bare int literals. Autos.json is excluded
from spotless and the round-trip check is structural, so the format is
inconsequential.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add auto_generator_java to the spotless Java target so the generator sources
are held to the same googleJavaFormat standard as the robot code, and apply
the formatter. Pure formatting; generator output is unchanged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@godmar godmar linked an issue Jun 17, 2026 that may be closed by this pull request
godmar and others added 3 commits June 16, 2026 22:08
Port the intent comments, TODOs, and commented-out alternatives from the old
Python autos.py/routines.py/constants.py into AutosGen (cycle markers, the
high-speed trench constraint rationale, the "beeline to trench exit" note, the
autopilot-panic waits in the depot routine, the Follower "don't deploy into the
trench" note, and the climb lineup notes). No effect on generated output.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
coppercore's @JsonType/@JsonSubtype only choose the subclass on deserialize;
PolymorphTypeAdapterFactory.write() does not emit the discriminator, so the
"type" field must be populated on the object (see ExampleJsonSyncClass, where
each subtype passes its name to the base constructor). Previously this was done
by a separate generator-side TypeTagger pass.

Instead, AutoAction now resolves its own discriminator from the @JsonSubtype
table at construction (single source of truth, no per-subclass boilerplate).
Gson still overwrites it from JSON on load (Unsafe skips the initializer), so
this only takes effect when actions are built directly in Java. TypeTagger is
removed; generator output is unchanged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The generator now runs with the WPILib native libraries on its library path, so
auto code has unrestricted access to the rest of the robot code (HAL, Filesystem,
NetworkTables) exactly as on the robot — rather than being constrained to a
headless subset. This removes the previous workarounds:

- Field.java no longer reflects into AprilTagConstants.cachedLayout. It initializes
  HAL, loads AprilTagConstants for the environment via the normal coppercore path,
  and uses FieldConstants.Tower directly (no geometry duplication, no reflection).
- FollowPathPlannerPath's eager static initializer is restored (the lazy-init
  workaround was only needed when NetworkTables couldn't load headlessly).

generate.sh: bootstrap now also runs extractReleaseNative; the run sets
LD_LIBRARY_PATH + -Djava.library.path to build/jni/release. Since initializing HAL
prints native chatter to stdout, the generator writes JSON to a file and generate.sh
emits that file to stdout, keeping stdout clean JSON.

Output is structurally identical; spotlessCheck and the auto round-trip CI pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@godmar

godmar commented Jun 17, 2026

Copy link
Copy Markdown
Contributor Author

The agent confirmed that the old and new .json were identical, but testing is needed.

As is, this shaves 3,222 of lines of code off in total:

 auto_generator/src/__init__.py                                      |    3 
 auto_generator/src/auto_action.py                                   |  751 ------------
 auto_generator/src/auto_lib.py                                      |  255 ----
 auto_generator/src/autos.py                                         |  434 -------
 auto_generator/src/constants.py                                     |   60 -
 auto_generator/src/field_locations.py                               |  675 -----------
 auto_generator/src/routines.py                                      |  148 --
 auto_generator/src/shorthands.py                                    |  206 ---
 auto_generator/src/units.py                                         |  122 --
 b/auto_generator/build_autos.py                                     |  120 --
 b/auto_generator_java/dump-classpath.gradle                         |   15 
 b/auto_generator_java/frc/robot/autogen/AutosGen.java               |  383 ++++++
 b/auto_generator_java/frc/robot/autogen/Dsl.java                    |  299 +++++
 b/auto_generator_java/frc/robot/autogen/Field.java                  |   68 +
 b/auto_generator_java/generate.sh                                   |   80 +
 b/build.gradle                                                      |   12 
 b/ci-scripts/check_auto_roundtrip.py                                |   25 
 b/src/main/deploy/constants/comp/Autos.json                         | 6498 ++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------------------------------------------------
 b/src/main/java/frc/robot/auto/Auto.java                            |   15 
 b/src/main/java/frc/robot/auto/AutoAction.java                      |   49 
 b/src/main/java/frc/robot/auto/drive/AutoPilotAction.java           |   24 
 b/src/main/java/frc/robot/auto/drive/DriveAutoAction.java           |    2 
 b/src/main/java/frc/robot/auto/drive/XBasedAutoPilotAction.java     |   17 
 b/src/main/java/frc/robot/auto/general/NetworkConfigurableWait.java |    9 
 b/src/main/java/frc/robot/constants/JsonConstants.java              |   22 
 b/src/main/java/frc/robot/util/json/JSONAPTarget.java               |    5 
 src/main/java/frc/robot/util/ts/GeneratedDefault.java               |   13 
 src/main/java/frc/robot/util/ts/GeneratedOptional.java              |   14 
 src/main/java/frc/robot/util/ts/PythonAppend.java                   |   25 
 src/main/java/frc/robot/util/ts/PythonAppends.java                  |   13 
 src/main/java/frc/robot/util/ts/PythonGenerator.java                |  918 ---------------
 src/main/java/frc/robot/util/ts/PythonGeometryMethods.java          |  399 ------
 src/main/java/frc/robot/util/ts/PythonMethod.java                   |   42 
 src/main/java/frc/robot/util/ts/PythonMethods.java                  |   13 
 34 files changed, 4256 insertions(+), 7478 deletions(-)

The logic of the autos is mostly retained (in all its glory). Some ergonomics is lost (maybe?) because of the lack of a context for sequential lists, etc. In the Java version, all auto objects are explicitly built.

That said, if this tests ok, I would prefer using this approach.

godmar and others added 8 commits June 17, 2026 13:19
On success the tuning server's POST response is just the full autos JSON echoed
back, which is noise on stdout. Drain it silently and only print the status line;
failures still surface the response body via the raised PublishError.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
generate.sh was bash-only and could not run on native Windows (no bash; ':'
classpath separator; ./gradlew; LD_LIBRARY_PATH). Replace it with generate.py,
which branches per-OS:
- gradlew.bat vs ./gradlew
- os.pathsep for the classpath
- native loader var: PATH (Windows), DYLD_LIBRARY_PATH (macOS), LD_LIBRARY_PATH (Linux)
- java/javac resolved from JAVA_HOME when set (WPILib's JDK), else PATH

build_autos.py imports generate.py as a module and calls generate() directly
(no second Python process), returning the JSON in-memory; generator progress and
HAL native chatter stream to stderr so stdout stays clean.

Verified on Linux: bootstrap + hot path produce byte-for-structure-identical
output, build_autos.py and the auto round-trip CI pass, hot path ~2s. The Windows
code paths are explicit but not executed here; the one unverified assumption is
that GradleRIO's extractReleaseNative populates build/jni/release with the Windows
DLLs (standard GradleRIO layout).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replace the varargs seq(...)/parallel(...)/race(...) helpers with fluent
combinators on AutoAction:

    action.andThen(b, c)        // Sequence of action, b, c
    action.inParallelWith(x)    // Parallel of action, x
    a.andThen(b).andThen(c)     // chained, flattened
    race.racingWith(x, y)       // Race of x, y

andThen/inParallelWith/racingWith live on AutoAction; Sequence/Parallel/Race
override them to flatten and gain static of(...)/of(List) factories. Dsl exposes
empty seq/par/race starters; the combinators return new (flattened) containers,
so the shared starters are never mutated.

AutosGen is rewritten in this style: parallels start from the action
(stowIntake().inParallelWith(followPath(...))), and the conditional builders
(aggressive/conservative/cycleIntake) accumulate with `result = result.andThen(...)`
instead of a mutable List + add(), so there are no more List/a.add() calls. Code
logic is otherwise unchanged.

Generated Autos.json is structurally identical; spotlessCheck and the auto
round-trip CI pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A trailing `.inParallelWith(x)` on a chain read ambiguously — it was unclear
whether x ran alongside the whole preceding sequence or just the last action.

Remove the .inParallelWith / .racingWith decorators and instead make grouping
explicit:
- inParallel(a, b, c) / inRace(a, b, c) factories (Dsl), used inside andThen:
    seq.andThen(x, inParallel(a, b), y)
- action.andThenInParallel(a, b) / andThenRacing(a, b) sugar on AutoAction
  (= andThen(Parallel.of(...)) / andThen(Race.of(...))).

The empty `par`/`race` starters are removed; `seq` remains for andThen chains.
AutosGen's parallels are now written as inParallel(...) groups. Generated
Autos.json is structurally identical; spotlessCheck and the round-trip CI pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The generator Java (AutosGen, Dsl, Field) now lives under src/main/java like the
rest of the robot code, so it's compiled by gradle as part of main and covered by
spotless automatically (it's inert at robot runtime — never invoked). The driver
tooling (generate.py, dump-classpath.gradle) moves next to build_autos.py in
auto_generator/, and auto_generator_java/ is removed.

generate.py now fast-compiles only the autogen package (src/main/java/frc/robot/
autogen) on the hot path and puts that output first on the run classpath so it
takes precedence over the copy gradle compiles into build/classes/java/main.
build.gradle's spotless java target drops the now-redundant auto_generator_java
entry.

Generated Autos.json is structurally identical; spotlessCheck and the round-trip
CI pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Class AutosGen renamed to GenerateAutos (file, class, constructor, javadoc,
  and MAIN_CLASS in generate.py).
- The AutoPilotAction builder terminal Dsl.Ap.ap() is renamed to toAutoAction()
  so it no longer collides in name with the Dsl.ap() entry point.

Generated Autos.json is structurally identical; spotlessCheck passes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Consistent with Ap.toAutoAction(); the XBased autopilot builder terminal now
reads clearly. Generated Autos.json is structurally identical; spotlessCheck passes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Replace `ap().pose(x, y, a)` with an `autoPilotTo(x, y, a)` entry point
  (overloads for (x, y), (x, y, angle), and Pose2d); the target pose is now
  required at construction (Ap.reference is final).
- Rename all builder setters to the with* convention: withVelocity,
  withEntryAngle, withConstraints, withPidGains, and also withProfile,
  withRotationRadius, withCanMirror.

Reads e.g. autoPilotTo(3.5, 7.55, -90).withVelocity(4.2).withConstraints(...)
.toAutoAction(). Generated Autos.json is structurally identical; spotlessCheck passes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@godmar

godmar commented Jun 17, 2026

Copy link
Copy Markdown
Contributor Author

After a few iterations and discussions about ergonomics, this version looks fine. Claude checked that the generated Autos.json is structurally identical to what we started with.

The ergonomics (with builders, etc.) now resembles that of some of the coppercode code. Some decisions are a bit arbitrary, such as having immutable sequences, but should be fine. Overall, the autos themselves have the same flavor as before, but all the Python language crossing is gone.

@godmar
godmar requested a review from thetaback June 17, 2026 21:21
godmar and others added 5 commits June 17, 2026 17:47
Nothing outside these classes referenced `actions` — construction goes through
of(...)/andThen(...) and it's read only by toCommand(). They were public only by
historical data-holder convention. Gson (de)serializes private fields reflectively
(the JSON key stays "actions"), so this changes nothing on the wire.

Verified: generated Autos.json structurally identical; round-trip CI passes
(robot deserializes into the private fields); spotlessCheck passes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Field.initialize() previously hand-rolled AprilTagConstants loading (deploy path
resolution + JSONSync), duplicating JsonConstants. Extract the robot's full
constant-loading sequence into JsonConstants.loadConstants() (no tuning server,
no RobotContainer) and have both robot startup and the generator call it:

- JsonConstants.loadConstants() loads every constant for the config.json-selected
  environment; loadConstants(RobotContainer) delegates to it then starts the
  tuning server when enabled (unchanged robot behavior).
- Field.initialize() now just does HAL.initialize() + JsonConstants.loadConstants(),
  so FieldConstants and all other frc.robot.constants are available to autos with
  zero duplicated loading logic.

Because loadConstants() is config.json-driven (like the robot), the generator now
selects its environment from config.json; the generator no longer takes an env
argument (build_autos still uses env only to choose the output directory).

Generated Autos.json is structurally identical; spotlessCheck and the round-trip
CI pass. Cost: the hot path grows to ~2.7s since loading all constants also runs
Phoenix sim init and the @AfterJsonLoad hooks.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Comment thread auto_generator/build_autos.py Outdated
@godmar
godmar merged commit d60b2f7 into main Jun 19, 2026
3 checks passed
@godmar
godmar deleted the 226-explore-using-java-for-autos branch June 19, 2026 20:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

(experimental) explore replacing Python with Java

2 participants