FTC robot code for the 2026-27 BIOBUZZ season, built on the PedroPathing Quickstart v3.0.2 (PedroPathing v3, FTC SDK 12.0.0). Seeded from HORS-Pedro; the MainHORS control scheme carries over, the old code does not.
Robot: mecanum drive with Pinpoint odometry (2 pods), 2 intake motors, 2 independent shooters (pollen and nectar, one goBILDA 5000-series 6000 RPM motor each, same spin direction, different speeds), 2 independent gate servos (one per shooter). No turret: aiming turns the whole robot.
The MainHORS teleop with odometry aim, and a Zenith-planned 2-tip pollen auto, both tested in SimLoop.
| OpMode | File | What it does |
|---|---|---|
| MainHORS TeleOp | teamcode/teleop/MainHorsTeleOp.java |
The teleop: MainHORS controls plus odometry aim (see below) |
| Two tip | teamcode/zenith/generated/TwoTipAuto.java (generated) |
The 2-tip pollen auto, played from autos/two-tip.auto.json by Zenith (see below) |
| Choose Alliance | teamcode/opmodes/ChooseAlliance.java |
Run before the auto: dpad left = RED, dpad right = BLUE |
| Shooter Tuner | teamcode/tuning/ShooterTuner.java |
Measures each shooter's feedforward (power sweep) and P (search), prints PIDF to paste into MechanismConstants. Robot on blocks, no pollen |
| Tuning (Quickstart) | teamcode/pedro/Tuning.java + procedures/ |
PedroPathing tuners, including the Pinpoint Tuner |
| Name | Device | Notes |
|---|---|---|
| frontLeft, backLeft | DcMotor | Drive, FORWARD |
| frontRight, backRight | DcMotor | Drive, REVERSE |
| intakeLeft, intakeRight | DcMotor | Intakes, run together |
| shooterRight | DcMotorEx | Pollen shooter (confirmed: right side, top-down with the intake up) |
| shooterLeft | DcMotorEx | Nectar shooter (confirmed: left side) |
| gateRight | Servo | Pollen gate (confirmed) |
| gateLeft | Servo | Nectar gate (confirmed) |
| pinpoint | GoBildaPinpointDriver | Pinpoint odometry computer (2 pods), used by the Pedro localizer |
Shooter/gate names, directions, and tuning values live in
teamcode/subsystems/MechanismConstants.java.
| Control | Action |
|---|---|
| Left stick | Drive (forward / strafe) |
| Right stick X | Turn (overridden while aiming) |
| Left trigger (hold) | Aim: turn the robot to face our hive's cell on this half of the field, and spin both shooters up (no dpad down needed; a volley under way keeps them up after you let go) |
| Right bumper (hold) | Slow mode, 0.45x. While aiming: each press nudges the aim 1° right instead |
| Right trigger | Intake in (a stalled intake reverses for 0.3 s on its own; the Fire line says INTAKE JAM. After 3 reverses that do not clear it the intake stops: INTAKE JAMMED: clear by hand, until you let go) |
| Y | Volley: spins both shooters up; once they are at speed (or after 2 s if they never get there) both gates open and the intake runs for 1 s, then the gates close and the intake stops. No pose or aim check; it does not aim. B and the intake trigger are locked out while it runs. Y again cancels. Fire line: Y SEQ |
| Dpad down | Toggle both shooters on/off, each at its own target (start OFF), to keep them spinning without aiming |
| Dpad left / right | Both shooters' target RPM − / +, the same 50 rpm each press. Gamepad1 only: gamepad2's dpad left / right do nothing |
| Left bumper | While aiming: each press nudges the aim 1° left. Nothing otherwise. The nudge (up to ±10°) stays until an X reset; the Pose line shows it |
| A (hold) | Outtake: both intake motors run out. Wins over right trigger; locked out while the Y volley runs |
| B | Toggle both gates open / closed. No pose, aim or speed check: you decide. The Fire line still says READY, or NOT READY: ... with the reason, so you know whether the shot would score |
| X (hold 0.5 s) | Reset the pose (wipes Pinpoint drift): push the robot into your alliance's corner past the loading zone (RED: north-west, BLUE: south-east), back flat on the side wall and its side flat on the end wall, intake facing the field, then hold X until the pad rumbles. A tap does nothing |
| Dpad up (gamepad1 only) | Flower bar down / stowed. It starts stowed (0.80); the first press puts it down (0.0), the next stows it. The right servo is reversed, so it gets 1 minus the left's number. While it is down the drive is capped at 0.6x (BAR DOWN: slow) |
| Gamepad2 left trigger (hold) | Gamepad2's only flower bar control: held, the bar goes down; let go, it goes back up. Gamepad2's dpad up does nothing |
Before PLAY, pick the alliance: dpad left = RED, dpad right = BLUE. If an auto ran first, its alliance and final pose are picked up automatically (the SDK blackboard), once, and only if the auto saved the pose under a minute ago; if not, the teleop assumes it starts at the auto's park spot (back to the west wall by the loading zone, intake facing the field; mirrored for BLUE), and INIT telemetry says why.
Driving display, extras on the Fire line: INTAKE JAM: reversing, LOW BATTERY 11.2 V (under
11.5 V for a second), and ENDGAME n s counting down the last 30 s. The gamepads rumble three
short blips as the endgame starts and once, long, 10 s before the end. A shooter whose encoder
reads nothing while it is pushed shows ENCODER? (no speed reading) and runs on its feedforward
alone instead of winding up to full power; its gate stays shut and the other shooter fires alone (POLLEN FAULT: nectar only, with a long left-side rumble). A backwards encoder
(negative rpm while pushed forward) shows ENCODER REVERSED? and is handled the same way. Also on
the Fire line: GATES OPEN while they are, and SLOW LOOP n ms when the loop averages over 40 ms.
The Loop line ends with where the last loop went: odo (the Pinpoint read), mech (the hubs'
encoder read and both shooter loops) and drive (the four drive writes), in ms. A pose off the field
or one that jumped further than the robot can drive in a loop is INVALID (aim off) until X resets it.
Live tuning (Panels): every class with tuning numbers is @Configurable. Connect the laptop to
the robot's Wi-Fi and open Panels in a browser (http://192.168.43.1:8001); the values change on the
robot as you edit them, and go back to the code's values when the robot restarts. Copy a value you
want to keep into the code.
Idle and trims: idle is OFF for now (SHOOTER_IDLE_ENABLED = false): unasked, the flywheels stand
still. Switched on, with neither dpad down nor aim asking for them, both flywheels idle at 800 rpm
(IDLE), so a spin-up starts part way; idle never counts as ready to fire. The rpm trims carry over
to the next teleop; press back during INIT to clear them.
INIT check (teleop and both autos): a pass/fail list before PLAY: Pinpoint READY, battery at 12.5 V or more, both shooter encoders at rest; the teleop adds whether an auto pose is waiting.
Failsafes in the autos: if the Pinpoint stops reporting READY mid-route (5 loops), or is not
READY 1 s after PLAY, the auto halts: drive stopped, mechanisms off, gates shut (AUTO HALTED).
At the spin-up timeout a volley fires only if the flywheel is at 80 % of its target or more;
otherwise it is skipped with the pollen kept (SHOOTER never reached speed). The hand-written
state machine auto also watches the clock: still at the garden at 20.5 s it leaves with what it has,
and reaching the last volley at 25.2 s or later it skips the volley and parks (LATE). The Zenith
auto runs its steps in a fixed order, so it has no such cutoff.
Aim. No camera and no turret: holding the left trigger turns the whole chassis to face the
hive cell on the robot's half of the field (north cell when y > 0, south when y < 0, with 2 in of
hysteresis at the line), from the Pinpoint pose and the hive's fixed field coordinates (BIOBUZZ
field file: RED hive pivot (-12.75, 0), BLUE (+12.75, 0), cell centres 9.264 in either side along
y). A launch only scores from the up cell's own quarter, so the driver picks the cell by where
they drive; there is no goal toggle.
The driver keeps forward/strafe; yaw gets priority over translation, and a feedforward on the
bearing rate keeps the aim on target while strafing. If the Pinpoint is not READY or the pose is not a number, aim falls
back to manual turn and telemetry shows POSE INVALID. Telemetry always shows the alliance,
target cell, aiming on/off, heading error and pose.
autos/two-tip.auto.json, planned with Zenith and played on the
robot by Zenith's runtime through the Pedro v3 follower and the named commands in
zenith/BioHorsCommands.java. Pollen only, 12 pollen in 3 volleys of 4 (RED described):
- Score the 4 preload from
scoreSouthinto the up cell, on the 3 staged nectar: tip 1. - Go round the west of the hive frame to flower1 (north wall), take its 4 with the flower bar down,
score them from
scoreNorth. - Sweep the RED garden row along the south wall (bar stowed), drive back and score from
scoreNorth: tip 2. Park.
One OpMode per alliance: pick the one for your side. "Two tip" runs autos/two-tip.auto.json
(RED); "Two tip (BLUE)" runs autos/two-tip-blue.auto.json, Zenith's mirror of the RED file
(BIOBUZZ is point-symmetric), written by node tools/mirror_auto.mjs. The Zenith autos ignore
Choose Alliance (it is still used by the state-machine auto and by the teleop when no auto ran).
When the auto stops, its alliance and pose go to the teleop's aim. After any change to the RED auto
or autos/waypoints.json, re-run node tools/mirror_auto.mjs, then npx zenith deploy and
npx zenith codegen two-tip-blue; BlueAutoMirrorTest fails until you do.
Editing it: npm install (pins the Zenith CLI and MCP in package.json), then use the Zenith MCP
server (.mcp.json) or npx zenith validate|estimate|render|deploy|sim ... --json. The
build-auto skill (.claude/skills/build-auto/, with references/robot.md for this robot) is the
workflow. Never edit TeamCode/src/main/assets/autos/ or zenith/generated/: npx zenith deploy
writes them. No mid-path markers: Zenith v0.1.2's marker runner needs a Pedro method Pedro 3.0.2 lacks.
./gradlew :TeamCode:testDebugUnitTest (Windows: .\gradlew.bat ...) runs the real OpModes on a
laptop against fake hardware, in TeamCode/src/test/java/org/firstinspires/ftc/teamcode/sim/:
BioHorsSim: this robot's fake devices and physics (drive, Pinpoint, shooters, gates, intake).MainHorsTeleOpSimTest: the teleop (drive, slow mode, aim, goal switch, aim while strafing, Pinpoint-fault fallback, BLUE, shooters, gates, intake, determinism).ZenithAutoMatchTest+BiobuzzMatch+TracedZenithAuto: the deployed auto as a BIOBUZZ match, stopped at 30 s like the field: RED twice (identical logs) and BLUE once. It checks 16 pollen and 2 tips in AUTO, every step in order, no structure contact, strafing under 20 % of the driving, and the robot's belief against the sim's truth.npx zenith sim two-tipruns the same test.LiveDriveTest: the live-controller session's plumbing (LiveDrive,LiveDriveSession, below) with scripted input: input reaches the teleop, a recorded session replays to the same log.
Every sim number is a placeholder until measured (docs/TUNING.md), so the sim proves the code's
logic and wiring, not the real robot's timing.
RLOGs and Zenith traces land in TeamCode/build/sim/ (gitignored; re-run the tests to regenerate).
Watch a run on the 3D field. Open TeamCode/build/sim/two-tip-red.rlog (or two-tip-blue.rlog,
teleop-aim.rlog) in AdvantageScope, open a 3D Field tab
(or 2D Field) and pick 2026-2027 Field. From RealOutputs/Field, drag:
Robotonto the field: the robot's true pose (choose a robot model, e.g. FTC drive base);RobotBeliefand set it to Ghost: where the robot thinks it is (match runs);Pollenand set it to Game Piece → Pollen: the garden line, the flower columns, the 4 aboard, each launch flying into the hive, the pollen in the cells, and each tip's spill.
Press play. Poses are logged in AdvantageScope's FTC "Center/Rotated" frame (metres), converted
from the field frame the code uses by sim/AdvantageScopeFrame. Match/scored, Match/tips,
Shooter/pollenRpm plot on a Line Graph.
Drive it yourself with a controller. Plug a USB game controller (Xbox, Logitech F310 in X mode, PS4/PS5) into the laptop and run
.\gradlew.bat driveSim
to drive the teleop on the simulated robot and field in real time, with a top-down window and the
Driver Station telemetry. Hold BACK + START to end early. Each session is recorded under
TeamCode/build/sim/live/ as an RLOG for AdvantageScope plus the controller input, which
.\gradlew.bat driveSim -Preplay=latest replays exactly. Options and details:
docs/LIVE_DRIVE.md.
The full brief, item by item, is docs/TUNING.md.
Every tuning number in the code carries a provenance label: SPEC (from a
spec sheet), PLACEHOLDER (made up so the code runs), or
NEEDS MEASUREMENT. None are measured. Do not treat any as real.
- Shooters (
subsystems/MechanismConstants.java): each shooter has its own target RPM, PIDF, tolerance, and feedforward. Gains are placeholders.ticksPerRev= 28 and feedforward max = 2800 ticks/sec are the goBILDA 6000 RPM motor spec.ticksPerRevcounts per flywheel revolution, so 28 only holds if the flywheel is direct-driven (1:1); with a belt/gear ratio, multiply it by the ratio. The 2800 ticks/sec is the motor encoder's free speed and does not change with the ratio. Shooter motor directions and which motor is pollen still need verifying on the robot. - Gates (
subsystems/MechanismConstants.java): open/closed position per gate. Both start at the old shared placeholders (0.67 / 0.485). - Pinpoint (
pedro/Constants.java): pod offsets and directions. Run the Quickstart's Pinpoint Tuner on the robot. - Aim (
aim/AimController.java): kP, yaw cap, minimum yaw, on-target tolerance, turn rate at full yaw, and which way the shooter fires (SHOOTER_FACING_OFFSET_RAD). Teleop default start pose (teleop/MainHorsTeleOp.java) when no auto ran first. - Drive shaping, intake power, Foresight gains, auto timing: placeholders, labelled in their files.
Open in Android Studio (Narwhal 3 Feature Drop or later) and build like any
FTC SDK project. The PedroPathing dependencies (revhub 3.0.2, tuning 1.0.3)
are in build.dependencies.gradle.
Zenith (beta) and SimLoop (alpha) are by Horizon (FTC 36596) and are early; they are looking for testers and contributors. Zenith: https://zenith.horizon36596.org · SimLoop: https://github.com/Horizon-36596/simloop · Example robot: https://github.com/Horizon-36596/Horizon-Library-Examples · Docs: https://libraries.horizon36596.org · Discord: https://discord.gg/p2D3JpM4f6 · Release video: https://youtu.be/NFww9Mt-ZR4
Work on branches and open PRs; Greptile reviews every PR. Do not push to
main directly.

