There is a small tribe of Pokémon players who accept exactly one way of hunting shinies: real cartridge, real console. No emulators, no cheat codes, no save editing, no RNG manipulation — the odds table says 1/8192, so you draw the lottery 8,192 honest times. The most hardcore scene in this community is three or four GBAs laid out on a desk, both hands cycling A+B+Start+Select soft resets, eyeballing each intro animation for an off-color sprite, for a week straight.
I respect that stubbornness. I just don't want to own those hands. So I'm starting a new project called LastBall: build a machine that presses through those 8,191 failures for me.
The name is the entire ruleset. The machine soft-resets, walks the route, glances at the Pokémon's color as it comes out, and goes again if it's normal. The instant a round looks wrong, it stops all input, pushes that frozen frame to my phone, and waits — doing nothing. Running, catching, resetting past it: none of these are within its authority. In the words of a comment in the codebase: come throw the last ball. The lottery grind belongs to the machine; the last ball belongs to the human.
To serve this tribe, the machine itself has to survive the same scrutiny. So the boundary is drawn hard: it only does what human hands do — press physical buttons, look at a screen. The end state is a mechanical finger resting on a real console's buttons and a camera pointed at a real screen. Not one byte of the game is touched; the shiny is rolled entirely by the cartridge itself. An emulator will appear below, but only as a development rig: get the loop and the detector reliable in software first, then move onto hardware.
Roadmap and where it stands
Four steps, with the interfaces cut on day one: the loop engine only knows two abstractions, a "hand" (ButtonDriver) and an "eye" (FrameSource). It never learns whether a press lands in an emulator or on a real console, whether a frame comes from a screenshot or a camera. Swapping hardware means swapping implementations of those two interfaces; the loop doesn't change by a single line.
P0: get the loop running on a software rig (done). mGBA plus a Lua bridge plays both hand and eye, and the full loop runs against an Emerald starter (Torchic): soft reset, replay a button route to the Pokémon's send-out, grab a frame, compare the judge region's hue histogram against a baseline — Gen 3 shinies literally are an alternate palette, so color comparison is the right tool, not a proxy. The loop body contains zero LLM calls. The model does exactly one job, once: interactively feel out the "walk to send-out" button route in the emulator, then trim it over a few verified attempts down to the shortest reproducible version. Locked in, it replays tens of thousands of times at zero cost.
P1: the reliability layer (done). The goal is a machine you dare leave unattended for days. ntfy pushes reach my phone with the frozen frame attached as a PNG. Every round is audited, with a human review page. There's an RNG canary: Emerald boots with a constant seed of 0, so a robot with perfect timing would meet the same Pokémon forever — the scheduler deliberately sweeps the delay frame by frame with jitter, and the canary watches judge-frame hashes and alarms if repeats pile up. A shiny drill injects a fake shiny palette into the emulator's video memory to prove the FREEZE path actually fires. And an LLM supervisor backstops execution accidents — a stuck menu or a blank screen may be reviewed and retried, but a suspected shiny never passes through its hands; it goes straight to the human. The detector's whole stance fits in one sentence: false alarms are fine, misses are not. This phase also brought two rounds of performance work, cutting a round from ~49 seconds to ~26.
P2: swap the hand (not started). ButtonDriver moves from the mGBA bridge to a mechanical finger driven by a Raspberry Pi Pico, pressing a real console's physical buttons. The problems here are all about execution determinism: consistent press force and duration, a reliable serial link, and the loop noticing when a press silently didn't register.
P3: swap the eye (not started). FrameSource moves from emulator screenshots to a camera filming a real screen. The detector has to hold up under lighting changes, moiré, and perspective distortion — the baseline and thresholds will almost certainly need recalibrating against camera frames. Only when this step lands does the machine deserve the word "authentic."
The next step touches no hardware: run one complete hunt on the software rig — thousands of rounds — and let the long-running problems surface on their own. When it has drawn all eight thousand lottery tickets for me and that one wrong-looking screenshot pops up on my phone, I'll go throw the last ball.
Top comments (1)
The framing is interesting because it makes failure part of the interaction instead of hiding it. A system that fails thousands of times before the human move can still feel meaningful if the final action is clear and the machine's attempts are legible.