Domain Specific Shinies

One of the main limitations of S.H.A.O.O.O.H. is that any change to a state machine requires recompiling and restarting the entire program. Compilation time not being something that Rust is renowned for, this makes adding a new shiny hunt more challenging due to the turnaround time to test any changes.

The functions that are used to create states are relatively regular. Each state consists of a name, buttons to be pressed, modifications to the internal state, branching based on the state or image processing and a delay before moving to the next state. This makes it possible to represent each state as a line in a custom language. For example, updating the internal timer once an encounter is ready (the player’s side HP bar is shown) and then moving to the next state:

wait_ready@ State&UpdateTimer +check 0 {HGSS_ENCOUNTER_READY}

As the shiny sparkles for the wild Pokémon are shown before sending out the player’s Pokémon, measuring the time between the start of the battle and the HP bar being shown can be used as an indication of if shiny sparkles were shown or not.

To implement a Domain Specific Language to build the state machines, nom gives a way to implement a parser in Rust. Some amount of logic is needed for the state machines, e.g. different static encounters might need a different number of button presses, or different delays. Rather than implementing a full programming language a templating engine allows some decisions to be made based on the target re-using an existing project. Oddly, no such existing project exists for a shiny hunting DSL…

Actually, that’s not entirely true, a number of shiny hunting bots exist and have sequences defined, such as from Geeze. However, S.H.A.O.O.O.H. takes a slightly different approach where more of the shiny checking is done in the state machine and so needs to be slightly more flexible.

The Rust implementation of Handlebars gave a way to add templating functionality. As an example of the complete state machine for a Sudowoodo shiny hunt, where the target species ID will get filled in when preprocessing the template:

# HGSS Soft Reset (Sudowoodo)
reset@      State 9500..10000 :L:R:Start:Select
title1@     State 4000..4250 :A
title2@     State 4500..4750 :A
title3@     State 4500..4750 :A
start_talk@ State 4000..4500 :A
yes@        State 2000..2000 :A
talk2@      State 3000..300 :A
talk3@      State 2000..2000 :A
talk4@      State 0 :A
wait_start@ State +start_time {HGSS_BATTLE_BLACK_BAR,HGSS_BATTLE_WHITE_TOP}
start_time@ State&StartTimer
wait_bar@   State +wait_ready {DP_IN_ENCOUNTER}
wait_ready@ State&UpdateTimer +check 0 {HGSS_ENCOUNTER_READY}
check@      State&ProcDelay=7300 +done -loop {Sprite:{{target}}:0}
done@       State&Deadend 500
loop@       State 2500..7500

This updated approach initially had some limitations as the available image processing options were still hardcoded. This was improved by adding a mechanism to read in the image processing at runtime. As the different image processing options are implemented as a Rust enum, this used Serde and it could be read in from a json file, no need for a custom language. However, this was implemented after the initial shiny hunt was done using this new method.

'Root' the Domain Specific Shiny

‘Root’ the Domain Specific Shiny

And then… next step a Poké Radar bot (and getting around to adding emulator support to simplify testing).


Last modified on 2026-06-05