Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
176 changes: 176 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
# AGENTS.md - Team Spirit FTC Robotics

## Project Overview

This is an FTC (FIRST Tech Challenge) robotics project for Team Spirit, built on Road Runner v1.0. The project is an Android application that deploys to an FTC Robot Controller REV Control Hub. Team code lives in the `TeamCode` module.

## Build Commands

All commands use Gradle with the Android Gradle Plugin. Run from the project root.

```bash
# Build entire project
./gradlew build

# Build debug APK only
./gradlew assembleDebug

# Clean and rebuild
./gradlew clean assembleDebug
```

There are no unit tests in this project. Testing is done by deploying to the robot and running OpModes via the FTC Driver Station.

## Code Style Guidelines

### File Organization

- Package declaration: `org.firstinspires.ftc.teamcode`
- Subpackages: `robot`, `opmodes.teleop`, `opmodes.auto`, `opmodes.tuning`, `utils`, `messages`, `tuning`
- One public class per file
- Filename matches class name

### Imports

Order imports by:

1. Android/SDK imports
2. Road Runner / Acmerobotics imports
3. Qualcomm (robotcore) imports
4. Static imports
5. Team internal imports

```java
import com.acmerobotics.dashboard.config.Config;
import com.qualcomm.robotcore.hardware.DcMotorSimple;
import com.qualcomm.robotcore.hardware.Servo;
import org.firstinspires.ftc.robotcore.external.Telemetry;
import org.firstinspires.ftc.teamcode.robot.Shooter;
```

### Naming Conventions

- **Classes:** PascalCase (`MecanumDrive`, `SpiritTeleop2`)
- **Methods:** camelCase (`updatePoseEstimate`, `setDrivePowers`)
- **Constants:** SCREAMING_SNAKE_CASE with descriptive names (`nearTiltPosition`, `farShooterVelocity`)
- **Instance fields:** camelCase with `m` prefix optional; prefer descriptive names (`left`, `right`, `telemetry`)
- **Parameters:** camelCase (`hardwareMap`, `telemetry`)
- **Config fields (tunable):** `static public` with descriptive names

### Formatting

- 4-space indentation (no tabs)
- Line length: 100-120 characters max
- One statement per line
- Opening brace on same line
- Use blank lines to separate logical sections within methods

### Annotations

- `@Config` - Exposes static fields to FTC Dashboard for live tuning
- `@TeleOp` / `@Autonomous` - OpMode registration
- `@Override` - Always use when overriding methods

### FTC SDK Patterns

#### DcMotor RunMode

- `RUN_USING_ENCODER` - Enables SDK's built-in PIDF velocity controller
- `RUN_WITHOUT_ENCODER` - Correct mode for custom feedback/feedforward; still reads encoders

#### Feedforward (kV) Calculation

The SDK's PIDF `F` coefficient is kV scaled by 32767:

```java
F = 32767 * kV // e.g., max 2496 ticks/sec → F = 32767 / 2496 ≈ 13.13
```

#### Gamepad Edge Detection

Use SDK 11.0+ built-in methods:

```java
gamepad1.leftBumperWasPressed()
gamepad1.rightBumperWasReleased()
gamepad1.xWasPressed()
```

#### Hub Detection

Use `HubHelper` to determine which hub a motor is on - don't hardcode hub assumptions.

### Class Structure

#### Subsystems (robot package)

- Constructor takes `HardwareMap` and optionally `Telemetry`
- `static public` fields for tunable parameters (with `@Config`)
- Update methods called each loop iteration
- Clear, descriptive Javadoc on public methods

#### OpModes

- Extend `LinearOpMode` for teleop/auto
- Initialize hardware in `runOpMode()` before `waitForStart()`
- Main loop uses `while (opModeIsActive())`
- Usually nothing goes after the main loop, but especially not hardware commands
- Use gamepad edge detection for button state changes

#### Hardware Access

```java
// Motors
DcMotorEx motor = hardwareMap.get(DcMotorEx.class, "motorName");
motor.setMode(DcMotor.RunMode.RUN_WITHOUT_ENCODER);

// Servos
Servo servo = hardwareMap.get(Servo.class, "servoName");
servo.setPosition(0.5);

// Sensors
ColorSensor color = hardwareMap.get(ColorSensor.class, "color");
```

### Error Handling

- Avoid exceptions in control loops; use null checks instead
- Log errors via telemetry: `telemetry.addData("Error", message)`
- Don't catch exceptions silently - at minimum log and display
- Hardware initialization failures should stop the OpMode

### Telemetry & Debugging

- Use FTC Dashboard (`FtcDashboard.getInstance()`) for real-time tuning
- When using field overlay, use `DashboardTelemetryPacketAccess` to draw directly
- Don't create separate TelemetryPacket when using Dashboard telemetry
- Keep debug telemetry minimal during competition matches
- Use the format string parameter for `telemetry.addData` instead of `String.format`:
```java
telemetry.addData("Velocity", "%.1f", value); // correct
telemetry.addData("Velocity", String.format("%.1f", value)); // avoid
```

### Hardware Shutdown

**Critical:** Never send hardware commands after `opModeIsActive()` returns false. This crashes the Control Hub.

```java
@Override
public void runOpMode() {
DcMotorEx motor = hardwareMap.get(DcMotorEx.class, "motorName");
motor.setPower(0)
waitForStart();
while (opModeIsActive()) {
// only here
}
// never here
}
```

### Progress Reports

When requested, create progress reports in:
`TeamCode/src/main/java/org/firstinspires/ftc/teamcode/ProgressReports/`

Include: problems discussed, decisions made, gotchas encountered, solutions reached.
74 changes: 39 additions & 35 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,24 @@
# CLAUDE.md
## Workflow Preferences

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
- Do not run builds (`./gradlew`) or git commands (`git commit`, `git push`, etc.) by default — the user handles build testing and git operations themselves. Running builds is fine if the user reports a build problem and needs help diagnosing it.
- The user may ask for a progress report at the end of a session. These go in `TeamCode/src/main/java/org/firstinspires/ftc/teamcode/ProgressReports/` and should summarize the full session: the problems the user brought up, the discussion and decisions made, any gotchas encountered, and the solutions reached — not just a list of code changes.

## Project Overview

This is an FTC (FIRST Tech Challenge) robotics project for Team Spirit, built on the Road Runner v1.0 quickstart. It is an Android application that deploys to an FTC Robot Controller phone/Control Hub. The robot features a mecanum drivetrain with a ball shooter mechanism (carousel, kicker, tilt servo), intake motor, and lift.
This is an FTC (FIRST Tech Challenge) robotics project for Team Spirit, built on the Road Runner v1.0 quickstart. It is an Android application that deploys to an FTC Robot Controller REV Control Hub. The robot features a mecanum drivetrain with a ball shooter mechanism (carousel, kicker, tilt servo), intake motor, and lift. The team's code starts after `08f0898`

## Java Version

The project targets Java 8 for Android compatibility. Avoid using features from newer Java versions:

- Records (Java 16+)
- `var` keyword (Java 10+)
- Switch expressions (Java 14+)
- Text blocks (Java 15+)

## Build Commands

This project uses Gradle with the Android Gradle Plugin. All commands should be run from the project root.
This project uses Gradle with the Android Gradle Plugin. All commands should be run from the project root. Use `./gradlew` directly from bash — do NOT use `gradlew.bat` or `cmd /c`. For git commands, avoid using `git -C <path>` — just run `git` from the project root instead.

```bash
# Build the project
Expand All @@ -32,45 +42,39 @@ There are no unit tests in this project. Testing is done by deploying to the rob

The build config is split across files: `build.common.gradle` (shared Android config, rarely modify), `build.dependencies.gradle` (FTC SDK dependencies v11.0.0), and `TeamCode/build.gradle` (team dependencies including Road Runner).

### Key Dependencies
### Important Patterns

- **FTC SDK 11.0.0** - Core robotics framework
- **Road Runner 1.0.1** (core + actions) + **Road Runner FTC 0.1.25** - Motion planning and trajectory following
- **FTC Dashboard 0.5.0** - Live telemetry and parameter tuning via `@Config` annotation
- Road Runner artifacts come from `https://maven.brott.dev/`
- Classes annotated with `@Config` expose their `static public` fields to FTC Dashboard for live tuning
- Drive parameters (kS, kV, kA, PID gains, wheel velocity limits) live in `MecanumDrive.Params`
- The `MecanumDrive` constructor uses `LynxModule.BulkCachingMode.AUTO` for efficient hub communication

### Code Organization (`TeamCode/src/main/java/.../teamcode/`)
## FTC SDK Gotchas

**Drive classes (root package):**
- `Localizer` - Interface for all localization strategies (`setPose`, `getPose`, `update`)
- `TankDrive` / `robot/MecanumDrive` - Drive classes with embedded `DriveLocalizer` inner class; contain Road Runner `PARAMS` (feedforward, PID gains, constraints). **The team uses `MecanumDrive`** (set in `TuningOpModes.DRIVE_CLASS`)
- `ThreeDeadWheelLocalizer`, `TwoDeadWheelLocalizer`, `PinpointLocalizer`, `OTOSLocalizer` - Alternative localizer implementations that can be swapped into the drive class
### DcMotor RunMode

**Robot subsystems (`robot/` package):**
- `Intake` - Single motor (`intakeMotor`)
- `Shooter` - Dual flywheel motors (`shooterMotorLeft`/`shooterMotorRight`) + tilt servo; uses feedforward+feedback velocity control with smoothing
- `Carousel` - Two servos (`carouselServo` + `kickerServo`) that rotate a ball carousel and kick balls into the shooter; positions are computed from degree offsets
- `Lift` - Encoder-controlled lift motor for raising the robot
- `RUN_USING_ENCODER` enables the SDK's built-in PIDF velocity controller. Only use this if you want the SDK to handle velocity control.
- `RUN_WITHOUT_ENCODER` is the correct mode when doing custom feedback/feedforward in team code. Despite the name, it still reads encoder values — it just doesn't use them for internal control.

**OpModes (`robot/` package):**
- `SpiritTeleop2` - Main teleop; gamepad1 drives + controls tilt/carousel positions, gamepad2 controls intake/shooter/lift. Launch sequence is a multi-step state machine (IDLE -> RAMPING -> LAUNCHING with 16 timed steps)
- `SpiritAutoFar`, `SpiritAutoBlueFar`, `SpiritAutoBlueNear`, `SpiritAutoRedFar`, `SpiritAutoRedNear` - Autonomous routines, mostly timed drive movements
### RUN_USING_ENCODER feedforward (kV)

**Tuning (`tuning/` package):**
- `TuningOpModes` - Registers all Road Runner tuning OpModes (ramp loggers, push tests, direction debuggers, feedforward/feedback tuners). See [Road Runner tuning docs](https://rr.brott.dev/docs/v1-0/tuning/)
- `LocalizationTest`, `ManualFeedbackTuner`, `SplineTest` - Custom tuning OpModes
The SDK's velocity PIDF `F` coefficient is a kV, but scaled by 32767. To calculate: `F = 32767 * kV`. For example, a motor measured at max 2496 ticks/sec → `F = 32767 / 2496 ≈ 13.13`.

**Messages (`messages/` package):** Data classes for Road Runner's FlightRecorder logging.
### Gamepad edge detection

### Hardware Configuration Names
SDK 11.0 added rising/falling edge detection methods directly on the Gamepad object, e.g. `gamepad1.leftBumperWasPressed()` and `gamepad1.leftBumperWasReleased()`. SDK 11.1 extended this to triggers. See `ConceptGamepadEdgeDetection` sample for usage. Code targeting older SDK versions must track previous button state manually.

Motors: `leftFront`, `leftBack`, `rightBack`, `rightFront`, `intakeMotor`, `shooterMotorLeft`, `shooterMotorRight`, `liftMotor`
Servos: `tiltServo`, `kickerServo`, `carouselServo`
IMU: `imu`
### Identifying which hub a motor is on

### Important Patterns
Use `HubHelper` to determine whether a motor is plugged into the Control Hub or the Expansion Hub. It returns the `LynxModule` for the hub the motor is connected to. Do not manually iterate `LynxModule` instances or hardcode hub assumptions.

- Classes annotated with `@Config` expose their `static public` fields to FTC Dashboard for live tuning
- Drive parameters (kS, kV, kA, PID gains, wheel velocity limits) live in `MecanumDrive.Params`
- The `MecanumDrive` constructor uses `LynxModule.BulkCachingMode.AUTO` for efficient hub communication
- `rightFront` motor direction is reversed in `MecanumDrive`; other motor directions are default
### Loop sleep

Avoid calling `sleep()` to intentionally slow down control or sampling loops — prefer higher sample rates. Sleep is fine in situations where the OpMode is just waiting for stop (e.g., `while (opModeIsActive()) sleep(100);`).

### FTC Dashboard field overlay

When using FTC Dashboard telemetry alongside field overlay drawing (e.g., in `DrivingBase`), use `DashboardTelemetryPacketAccess` to get the underlying `TelemetryPacket` and draw on its `fieldOverlay()` directly. Do not create a separate `TelemetryPacket` and send it manually — that writes extra telemetry lines and duplicates data on the dashboard.

### No hardware commands after opModeIsActive() returns false

In a `LinearOpMode`, never send any hardware commands (motor power, servo position, sensor reads, etc.) after `opModeIsActive()` returns `false`. The SDK takes sole responsibility for shutting down hardware at that point. Sending commands after this will crash the Control Hub and force a restart.
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# Progress Report — 2026-02-13

## Session Goal

Review recent changes to the Shooter subsystem (Shooter.java and ShooterMotor.java) covering the voltage-based feedforward rewrite, ShooterMotor extraction, and acceleration profiling.

## Bugs Found and Fixed

### Copy-paste error: `leftKA` passed to right motor

In `Shooter.update()`, the right motor's `update()` call was receiving `leftKA` instead of `rightKA` for the acceleration feedforward gain. Since the two motors' kA values differ by ~30% (`12.5/2380.5` vs `12.5/1835.8`), the right motor's acceleration compensation was consistently wrong.

**Fix:** Changed to `rightKA`.

### Uninitialized `lastTimeNanos` causing massive first `dt`

`lastTimeNanos` defaulted to 0. On the first call to `update()` with a nonzero target, `dt` would equal `System.nanoTime() / 1e9` (seconds since boot — potentially thousands of seconds), causing `profiledVelocity` to massively overshoot the target on the first iteration. The same problem recurred any time `shooterVelocity` was set to 0 and back, because the early return skipped updating `lastTimeNanos`.

**Fix:** Added a first-call guard: when `lastTimeNanos == 0`, seed it with the current time and return without running the control loop.

## Design Changes

### Velocity filter runs continuously (even when shooter is off)

The user wanted the smoothed velocity filters to stay current while the motors coast, so that on resume the profile can seed from the actual coasting speed rather than ramping from zero.

**Approach:** Split `ShooterMotor.update()` into two methods:
- `updateFilter(double alpha)` — reads the encoder and updates the IIR low-pass filter. Called every loop unconditionally.
- `update(...)` — runs feedforward + feedback control using the already-smoothed value. Only called when the shooter is active.

In `Shooter.update()`, timing and filter updates now run before the `shooterVelocity == 0` check. When velocity goes to zero, the motors coast but the filters keep tracking. On resume, `profiledVelocity` is seeded from `Math.max(left.smoothActualVelocity, right.smoothActualVelocity)` so the acceleration profile picks up from the faster motor's actual speed.

### Eliminated redundant `acceleration` / `prevProfiledVelocity`

The user noticed two acceleration variables: `accel` (clamped profile acceleration) and `acceleration` (derived from the delta of `profiledVelocity` and `prevProfiledVelocity`). Outside of the snap-to-target case, these are algebraically identical. During snap, the desired value is 0. Simplified to just use `accel` directly (set to 0 on snap), which also eliminated the `prevProfiledVelocity` field entirely.

### Added `isReady()` method

Added a method to indicate the shooter is up to speed and safe to fire. Returns true when:
1. `shooterVelocity != 0` (target is set)
2. `profiledVelocity == shooterVelocity` (profile has snapped to target, accel = 0)
3. Both motors' smoothed velocities are within `readyThreshold` (default 25 tps) of the target

`readyThreshold` is a `@Config` static field, tunable via FTC Dashboard.

## Minor Cleanup

- Removed unused `voltageSensor` field from Shooter (voltage is now read once in the constructor as a local)
- Removed unused `VoltageSensor` import from ShooterMotor
Loading