High Stakes robot, VRC 2024-25
Won 13 awards in the 2024-25 season, including 3 Excellence Awards, 2 Tournament Champion titles, and the Design Award that qualified the team for the World Championship.

- Team
- VEX Team 2147Z
- Role
- Design and programming lead
- Period
- Sep 2024 to May 2025
- Drive
- 6 × 11 W motors, 450 rpm, 6.3 ft/s
- Free speed
- 6.3 ft/s at 450 rpm
- Autonomous
- 21 tuned routines
What the game rewards
In High Stakes, rings are scored onto mobile goals and wall stakes, and a goal moved into a corner has its value doubled or cancelled. Our scoring analysis found that doubled goals and negative corner goals were each worth just over a quarter of the 118 available points, more than any other way of scoring. So the robot is built to possess goals and hold corners instead of climbing at the end of the match. Every subsystem came out of that one decision. The first robot paired a 450 rpm chassis with a hook intake and a two-bar pivot lift, and a pneumatic clamp held the goal steady while we filled it.
Every mechanism started from the same three limits. The robot had to start inside an 18 × 18 × 18 in volume and draw at most 88 W of combined motor power, and every custom plastic part had to fit on one 12 × 24 in sheet no thicker than 0.07 in.
Choosing designs with a decision matrix
We chose every mechanism with a decision matrix, which is a scored table of options and criteria. Each design was scored against criteria such as speed, weight, and ease of build. The winner was often not the fastest design on paper. It was the one we could build and repair between matches.
- Drivetrain4 configurations29.3 / 30
- Hook intakevs flex wheel, conveyor, claw25 / 40
- Two-bar liftvs four bar, chain bar, double reverse four bar36 / 40
- Deposit liftDecember redesign, 3 candidates26 / 30
- Passive hangvs winch, piston36 / 40
Score in points, out of the maximum for that matrix
Chassis
The chassis is a tradeoff between speed and pushing power. A 450 rpm drive on 3.25 in wheels won the matrix at 29.3 out of 30. Its free speed is 6.3 ft/s, which is quick enough to reach a corner first without giving up the torque to hold it. It runs six 11 W motors through a 36:48 reduction, with 4 omni wheels for turning and 2 traction wheels for pushing resistance.
After the first tournament the chassis was the one subsystem we rebuilt. Its center of gravity sat behind the center of the frame, which made the robot tip and sink into the foam tiles, and the bottom of the intake hit the ground. We lengthened the frame by 7 holes, swapping the 23 in C-channel rails for 30 in rails, and added 2 more powered omni wheels. Spreading the same weight over 8 points of contact stopped the sinking and let us hold our ground against defense, without touching the gearing we already trusted.
Intake
The intake is a hook system: a chain run of plastic hooks that catches rings at floor level and carries each one up and over a mobile goal. It scored 25 out of 40 on its matrix and won on speed and accuracy against a flex wheel, a conveyor, and a claw.
Our first full robot test failed 2 of 3 trials, because rings hit the goal stake and dropped to the floor instead of seating. I made a parameter chart of how hook height, shaft termination distance, and hook speed each moved where a ring landed. It showed that raising the hooks alone did not fix it. Extending and raising the termination shaft with a 5x L-channel threw the ring past the stake.
- First full-robot test
- Failed 2 of 3 trials
Lift
The lift is a two-bar, a single pivoting linkage that carries the whole intake up to wall stake height. It won its matrix at 36 out of 40 against a four bar, a chain bar, and a double reverse four bar, mostly on weight and ease of build. A 1x33 beam on it snapped from the constant tension of the intake hanging out past the pivot, and we replaced it with a half-cut 3x33 C-channel that puts more material along the loaded axis.
By December our reflection showed that our wall stake cycle was slower than most competitive designs, and that faster teams were reaching the center goal before us. We scored three replacements: an actuated hook stage, a six-bar ring redirect, and a two-bar deposit mechanism. The redirect could hold 2 rings at once, which looked good on paper, but every team we watched running one had to clear jams in the middle of a match. We chose the two-bar deposit lift, which scored 26 out of 30. It ends on the opposite side of the robot from the top intake shaft and drops a ring straight onto the stake.
Sweeper and hang
Four rings sit stacked in each corner, and clearing them was costing us corners we had already won. The sweeper is an arm that extends off the side of the chassis and wedges between the ring stack and the field perimeter, and then the robot pivots and drags the whole stack out. A fixed wedge needs no air and no moving parts, but it clears only one ring per pass, which is far too slow against a stack of 4. So the sweeper is an actuated 2x22 C-channel arm on pillow block bearings, driven by a single-acting piston, with a polycarbonate face that keeps the stack in front of the arm.
The hang lifts the robot onto the center ladder and is worth 3 to 12 points in the last 30 seconds. We had no motor ports and no air left to spend, so we limited the design to passive parts. It scored 36 out of 40 against a winch and a piston, winning almost entirely on the resources it did not use. Two half-cut 3x16 C-channels mount to the two-bar frame under tension from latex tubing, and 1.5 in standoffs grip the rung.
Built by hand
Every custom part was cut, drilled, and bent by hand. I modeled each part in Fusion 360 with the Sheet Metal tools, which unfold a bent part into a flat pattern. I printed the pattern at scale, taped it to the polycarbonate, and cut and drilled through the paper. Bends were formed with a heat gun, including a 70 degree bend on the plate that sets the clamp tilt angle. Working this way kept the parts on one sheet and let us replace a broken part the same night.
Control software
The code is C++ on PROS, built on LemLib, with a fork of it that I maintain as LemLib-2147 (v1.1.0 through v1.2.1) so we could add features the original did not have. Each subsystem is its own class with its own background task on a 20 ms loop, so the intake and the lift update independently of what the driver code is doing. Driver control and autonomous share one interface into every mechanism.
The lateral PID is tuned to 10, 0.05, and 60, and the angular PID to 7.3, 0.0075, and 78.6. The lift runs the same controller off a rotation sensor instead of a motor encoder. Dividing the raw centidegree reading by 36000 converts it to rotations, and the threshold check in the code is a workaround for the sensor wrapping from 1.0 back to 0 at the bottom of the travel: a reading above 0.875 is treated as 0, so a wrapped value does not command a full rotation backwards.
void Lift::moveToPID(float position) {
double rotationData = static_cast<double>(liftRotation.get_angle()) / 36000.0;
if (rotationData > .875) { rotationData = 0; }
float error = position - rotationData;
liftMotor.move(liftPID.update(error));
}Odometry and the distance reset
Odometry reports where the robot is in field coordinates. It uses a 2.75 in horizontal tracking wheel mounted 3.5 in off center, a 2 in vertical tracking wheel offset 0.75 in, and an inertial sensor for heading. Because the position is in field coordinates, retuning a routine means changing one coordinate instead of rewriting every move after it.
The weakness is drift. The rounding in each update is tiny, but it adds up: the reported position drifted about 1.5 inches over a 30 second autonomous routine, and it keeps growing over a longer skills run. A GPS sensor would fix that directly, but its strips are not guaranteed at every competition, and we could not build a season around a sensor that might not be there. Instead I added a distanceReset class to our LemLib fork. It uses 2 distance sensors aimed at perpendicular walls, averages 10 filtered readings off each wall, converts them to an absolute field position, and writes that straight back into odometry. A skills routine can now correct itself halfway through.
lemlib::distanceReset reset(xRobotDistance, yRobotDistance, 6, 5);
// mid-routine, facing into quadrant II
reset.reset(lemlib::x, lemlib::y, lemlib::II, 10);AutoBuilder
Writing an autonomous routine usually means guessing a coordinate, running it, and editing the number after watching the robot miss. AutoBuilder removes the guessing. I push the robot around the field by hand with odometry running, and at each position I want, a controller button prints the matching LemLib call to the brain terminal with the live position already filled in. A drives to a point, X turns to a heading, B turns to a point, and the other buttons print the lift, intake, and clamp calls. Writing a routine became a matter of driving it once and pasting the output. We ended the season with 21 tuned routines, and a bad field or a broken part stopped costing us a night of retuning.
if (controller.get_digital_new_press(pros::E_CONTROLLER_DIGITAL_A)) {
printf("chassis.moveToPoint(%f, %f, 1500, {}, false); \n",
chassis.getPose().x, chassis.getPose().y);
} else if (controller.get_digital_new_press(pros::E_CONTROLLER_DIGITAL_X)) {
printf("chassis.turnToHeading(%f, 500); \n", chassis.getPose().theta);
}Subsystem states
Each mechanism runs as a state machine. The intake has states such as regular, which drives the motor at a set voltage, ejecting, which reverses a wrong-colored ring back out, and relative, which moves the hooks a fixed distance to seat a ring. Its task picks one state every 20 ms before it touches a motor, and two checks run ahead of that on every pass. Color sort compares the hue from the optical sensor with our alliance range and flags an opposing ring for ejection. Anti-jam watches for a motor drawing more than 1800 mA at zero efficiency and reverses it briefly to clear.
The lift resolves its state into a target height and feeds that to its own PID loop. The clamp has a task that watches a distance sensor and closes by itself once a goal is inside, so the driver only holds a heading toward the goal. Because each mechanism is built this way, a change to one state reached all 21 autonomous routines without editing any of them.
void Intake::intakeTask() {
autoEject();
antiJam();
switch (currentState) {
case regular:
intakeMotor.move(intakeVoltage);
break;
case ejecting:
while (intakeOptical.get_proximity() > distanceThreshold) {
intakeMotor.move(127);
}
delay(5);
intakeMotor.move(-127);
delay(80);
setState(regular);
break;
}
delay(20);
}Autonomous routines
Three of the autonomous routines, recorded on the practice field.
Result
The team won 13 awards in the season. Twelve came at 10 VRC events: 3 Excellence Awards, 2 Tournament Champion titles, 2 Innovate Awards, 2 Judges Awards, an Amaze Award, a Robot Skills Champion award, and the Design Award at the California Region 4 championship, which qualified us for the World Championship. The thirteenth was a special recognition award at the VEX AI World Championship in April 2025.
