A one-button fishing minigame in Godot 4, with ten species
Cast, strike when it bites and hold to keep the green zone on the fish. Ten species depending on water depth and time of day, and an old fisherman who hands you a better rod. Here's how I built it.

On the pier at Caleta Salobre, Abel, an old fisherman, is waiting to give you a rod. Facing the water, G (PESCAR, "fish", on mobile) casts the float wherever the camera is looking. After a while the float dips, makes a sound and the phone vibrates, and you have 1.3 seconds to press again and strike. Then the fight starts: hold to push the green zone up, let go to let it drop, and while the fish stays inside, the catch bar fills.

There are ten species plus the odd old boot. What bites depends on where the float lands (the shore, the deep water off the end of the pier, or the open sea, which you can only reach from the boat) and on the time of day: octopus and squid come out at night. Talking to Abel opens a logbook with every species and its weight record. With 3 species he gives you a better rod, with 6 a hat, and with all 10 a trophy on the pier.
How it's built
As with the farm, the rules sit in a class with no nodes (peces.gd): each species is a dictionary with its zones, time of day, difficulty from 1 to 5, how often it bites relative to the others, and its weight. The zone comes only from the depth where the float lands: under 1.5 m is shore, over 12 m is open sea.
The minigame is Stardew Valley's, boiled down to numbers between 0 and 1. Every so often the fish picks a new spot on the bar, further away and more often the stronger it is. These are the lines that decide the fight:
var gain := GAIN / (1.0 + 0.15 * maxf(level - 1, 0))
fight["progress"] = float(fight["progress"]) + (gain if inside(fight) else -LOSS) * delta
if float(fight["progress"]) >= 1.0:
return "won"
if float(fight["progress"]) <= 0.0:
return "lost"
One important detail: the fish never goes below 0.38. If you don't press, the green zone sits at the bottom and can't reach it, so you can't win without playing.
Since it's all arithmetic, I could simulate it: 200 fights per species. Without pressing, no fish is ever landed. Pressing at random, between 0.5 and 16 %. A simulated player that follows the fish with a 0.25 s delay lands 197–200 out of 200, and with 0.4 s tuna drops to 184 (200 with the good rod). A fight lasts 3 to 12 s. These aren't players, they're autopilots.
The fishing mod doesn't know about the boat. It asks whatever is carrying you whether it allows_fishing(), and the boat says yes when it's stopped. That way each mod tests its own side of the contract.
What went wrong
- Fishing from the boat made you stand up on the bench. The fishing poses were complete (hips, knees, ankles) and overwrote the seated one. The other Claude session building the boat in parallel spotted it in a screenshot. Now fishing keeps whatever pose was there and only changes torso, head and arms.
- Loading a save removed the boat pose, even when you weren't fishing. A test with the game saved on board exposed it.
- The trophy was seen edge-on when arriving from the village, and Abel faced north. A close-up screenshot showed it; now both face the village, with a test.
- A "you can't" test was testing the wrong thing. "With the rod in your backpack you can't fish" was checked far from the water, where you couldn't fish anyway, and the mutation survived. It's now checked next to the pier.
Measured on PC: 47 checks of its own (7 s). Draw cost while fishing on the pier, 82–100 calls.
Still unverified
Not tested on a phone: no FPS, and no holding PESCAR with a real finger. The sounds (cast, splash, bite) were generated in code and measured, but nobody has listened to them. The fishing balance comes from simulations, not players. And fishing from the boat was only tested together once, outside the suite.