Part 1 ended with a paddle that meets 95% of serves. Part 2 asks it to put the ball back over the net into the far court, 70% of the time. Final: 71.7% of 1000 random serves returned.
The number is the least interesting part. It took six training runs, and the agent cheated better than it played. My spec's risk register said reward hacking would show up in curves before it showed up in gifs. It was right three times.
First eval, random policy: "returned 85%." A random paddle does not return 85% of serves. The per-term metrics — logged from day one for exactly this — showed the returns weren't returns: low serves smacked into the far side of the net, rebounded, and landed beyond it, and my detector counted them. Fix: serves must analytically clear the net at spawn, plus a regression test. Honest baseline after fix: 0.0%.
The first real run flatlined at 0%. Instead of shotgunning hyperparameters, I wrote a 40-line probe — a scripted policy that chases the ball perfectly — and asked: can anyone return a serve here? No. 0/40 contacts. Three physical causes, found in order:
After the fixes, the scripted probe returned 20/40. Provably learnable. PPO still refused — until bisection against Phase 1's known-good config found the poison: the paddle-tilt action channel. Random exploration on the tilt hinge lays the face flat often enough to destroy the contact gradient everything else depends on. Dropped it. Flat-face swings clear the net fine. (The wrist came back later as a curriculum — it buys aim, not consistency. A story for Part 3.)
The next run played honest tennis for four million steps — 79% returns — then found something better than tennis. The contact metric read 3170%: thirty-one contact events per episode. The policy had learned to drive to the net, pin the ball against it, and rattle it off the paddle face, farming the contact bonus into a reward of +282 while real returns crashed to 17%. My spec had predicted "micro-touches that maximize contact bonus without returning" almost verbatim.
The fix was mechanical, not reward surgery: a joint limit keeps the receiver in the backcourt — where the honest 79% policy had been playing anyway. Net-pinning became physically impossible. The regression test charges the net for 1.2 seconds and asserts the paddle stays home.
Contact ~97%, no farming, returns grinding through the plateau to 71.7% on the final 1000-serve eval. 10.9M env steps, about 90 minutes of laptop CPU.

Next: Part 3 — self-play, and the bug that beat every probe.
Built inside a personal agentic flywheel — every fix above landed as a gated PR with tests. The repo is jamesponwith/brax-tennis-rl.