Cracking Pokémon GO's RNG
how to turn 64 bits into an entire pokémon
Every Pokémon in Pokémon GO comes with a 64 bit encounter ID. A long sequence of digits that may look arbitrary, but can be used to accurately predict a Pokémon’s stats without ever asking the server for it. Add your player ID, and you can even predict whether it will be shiny for you!
Traditionally, most MMO games spread their players across different servers to manage load. Pokémon GO, however, is not a traditional MMO. All players share one map: the real world, which also happens to be pretty big. This introduces a big challenge: how do you make sure millions of players see the same Pokémon, with the same attributes, at the same time?
The answer is to make randomness repeatable. A pesudorandom number generator takes a seed number and produces more random-looking numbers from it. Take the same seed and follow the same steps and you’ll always get the same results.
I’ve spent the last months investigating the mechanics behind wild Pokémon generation. I now feel confident enough to share what I was able to uncover here.
Pokémon come in three parts
Nearby
PokéStopsIn-game locations representing real-world point of interests in view distance may show which Pokémon are around them, these are called Nearby Pokémon. Data is very limited, just containing their encounter ID and basic information about the species.
Wild
Wild Pokémon are what appear on the in-game map when the player gets within 40 meters around them. In addition to the encounter ID and species data, these also reveal their exact spawnpoint, giving their exact location and disappear time.
Encounter
Click on a wild Pokémon and you enter the encounter. This gives all data about that Pokémon: IVsIndividual values; the strength of this Pokémon, level, shininess, and so on.
Spawnpoints are just level 20 S2 cellsS2 cells partition the globe into a grid-like hierarchy of cells. Level 20 cells are about 9x9 meters each where Pokémon are spawning at the same time each hour. Which cells become spawnpoints is determined by signals like player activity or their proximity to PokéStops.*
Everything is deterministic
In 2021, James Prompanya gave some insight into how the Pokémon GO backend works.*

When walking around, the client polls the server every 10 seconds for all objects that are within its view. This request goes to the Spatial Query Backend, where persistent in-game objects live in, like PokéStops. Results are cached based on level 15 S2 cells.
When asked about how Pokémon catching works, however, he mentions this:
For catching Pokémon, that actually doesn’t go to the spatial query backend.*
So that’s interesting: Pokémon seem to not be stored persistently. We know that encounters give all data about a wild Pokémon. And encounter payloads only consist of the encounter ID and spawnpoint ID, meaning that a complete Pokémon must be identifiable from these two values. It has long been theorized that they actually act as a seed, rather than a key for a lookup table, which the interview kind of confirms. But reversing a random number generator can be very challenging, so no one was ever able to find out how exactly.
Getting there
Analyzing this behavior would take significant statistical expertise, which I don’t posess. But there’s this new magic black box: You give it a set of inputs and outputs, and it will churn on it like no human ever would. So I originally handed this problem to GPT 5.6 Sol, and lo-and-behold: After a day of hard churning, it did end up finding how to derive IVs from an encounter ID.
This is a big mystery solvled after it being only a theory for over 10 years. But there’s more: After many months of further analyzing game mechanics, here’s everthing I was able to uncover.
Predicting encounters
Let’s start with how a Pokémon’s stats are generated. Those that you would otherwise only get from an encounter.
Construct a java random number generator with this seed:
encounter ID XOR 0x18ADE5CD737BConsume 2 rolls
The next roll determines if that Pokémon becomes a Ditto, at 2% odds and given Ditto can disguise as the given species
The next 4 rolls determine the IVs and level, and apply a higher floor for weather-boosted spawns
Consume one Gaussian roll, then determine size from the next roll, based on specific cutoffs
Determine the quick and charge move by indexing the available move pools with two more rolls
A sample implementation may look like this:
salt = 0x18ADE5CD737B
rng = java.Random(encounter_id xor salt)
rng.next_int()
rng.next_int()
ditto_roll + rng.next_float()
is_ditto = is_ditto_species and ditto_roll < 0.02
if weather_boosted:
attack = rng.next_int(12) + 4
defense = rng.next_int(12) + 4
stamina = rng.next_int(12) + 4
level = rng.next_int(30) + 6
else:
attack = rng.next_int(16)
defense = rng.next_int(16)
stamina = rng.next_int(16)
level = rng.next_int(30) + 1
rng.next_gaussian()
size_roll = rng.next_double()
if size_roll < 0.004:
size = "XXS"
elif size_roll < 0.029:
size = "XS"
elif size_roll < 0.971:
size = "M"
elif size_roll < 0.996:
size = "XL"
else:
size = "XXL"
# overwrite size for pumpkaboo and gourgeist here
fast_move = fast_move_pool[rng.next_int(len(fast_move_pool))]
charge_move = charged_move_pool[rng.next_int(len(charged_move_pool))] Shininess and backgrounds ✨
Encounter a Pokémon, and it might turn out shinyIt has a different color. These are very rare and unique per player, making them highly saught after.
Given IVs are directly derived from the encounter ID, you might think that shininess is determined in a similar fashion. And you’d be right. Given the encounter ID, the player’s internal account ID, and the species’ shiny odds, shininess is perfectly predictable:
Construct a java random number generator with the seed being constructed like this:
- Encode the encounter ID as 8 bytes, little-endian
- Append the player ID, encoded as ASCII
- SHA-1 hash that byte sequence
- Take the first 8 bytes of the hash, interpreted as little-endian 64-bit integer
First roll determines shininess, given the shiny odds
Second roll determines whether that Pokémon has a backgroundSpecial collector cosmetic, similar to shinies, given the background odds
Pokémon disguised as Ditto will always be overwritten as non-shiny, and an actual shiny Ditto is rolled separately
payload = encounter_id.to_bytes(8, "little") + player_id.encode("ascii")
digest = hashlib.sha1(payload).digest()
seed = int.from_bytes(digest[:8], "little")
rng = JavaRandom(seed)
# odds differ per species, most have 1/512
shiny = rng.next_float() < 1/512
# odds vary, 1/25 are confirmed for community day events
has_background = rng.next_float() < 1/25
# dittos get special treatment
if is_ditto:
shiny = rng.next_float() < 1/64 Spawn pools, nests, and predicting the species
Predicting a Pokémon species is where it gets more complicated. Here’s what I was able to find:
Takeaways
For nestsParks, spawning a select species more frequently, lower 25% of species rolls will always be overwritten with the nesting species
Before drawing a species, it’s first determined which pool it’s drawn from
There’s a regionalExclusive to specific parts of the world pool, making up 5% of all draws
If there’s an active event, the event pool takes up 60% of spawns
Pool boundaries may vary, or there might be special pools active. For example, I was able to detect a 2% special pool only consisting of Minior
General pools
General pools are where a normal, non-event species is drawn from. Exact details remain unclear, but here’s the main points I was able to confirm:
They’re based on level 16 S2 cells
There’s 4 different geographical classifications that each influence species weights:
- Landform: Plains, hills, mountains, or tablelands
- Landcover: Forests, shrublands, croplands, grasslands, non-vegetated, snow, or settlements
- Climate: Polar, cool, warm, boreal, tropical, or subtropical
- Features: River, lake, ocean, city, beach, or desert
Weather conditions at spawn time affects species weights
As of October 2026, there’s no evidence for hemisphere or time of day to be of influence
And again, here’s the general implementation for how to predict a species:
# generate species roll
species_rng = java.Random(encounter_id)
species_roll = species_rng.next_double()
# generate spawn pool roll
pool_salt = 0x44444A87
pool_rng = java.Random(java.Long.hashCode(encounter_id) * 31 + pool_salt)
pool_roll = pool_rng.next_double()
# overwrite bottom 25% of rolls with nesting species
if nest and species_roll < 0.25:
species = NESTING_SPECIES
return
# decide what pool to use
if pool_roll >= 0.95:
pool = REGIONAL_POOL
elif ongoing_event and pool_roll >= 0.35:
pool = EVENT_POOL
else:
pool = GENERAL_POOL
# read from a given spawn pool, for example:
if roll < 0.80:
species = BULBASAUR
elif roll < 0.99:
species = IVYSAUR
else:
species = VENUSAUR Implications, what this gets us, and what we’ve learned
- Shinies are now worthless
Shinies are now perfectly predictable, allowing maps and feeds to show exactly which Pokémon will be shiny for you. This takes away a lot of value from them.

- Nearby, wild, and encountered Pokémon are now (almost) the same
The limited data provided through nearby and wild Pokémon can now be used to perfectly derive the full data-breadth an encounter would give you. This makes finding specific Pokémon much easier and effective, given you use modified game clients.
- Predicting weather boost changes
For every full hour, the in-game weather might change, re-rolling all stats of Pokémon that are affected by that weather. Now, you can perfectly predict the post-roll stats of these Pokémon.
- Perfectly analyzing nests
To know the nesting species, you would previously have to look at the most common spawn within nest boundaries, producing many false-positives. Just looking at an encounter ID can now determine wheter a spawn comes from a nest. You wouldn’t even have to know the exact park boundaries anymore.
- Knowing about Ditto before catching it
Ditto are only revealed upon catching. Previously, it was possible to detect some Ditto under specific conditions, but now it’s possible to reliably detect all Ditto, without having to catch them.
- Parks don’t spawn low-Pokédex Pokémon
Spawn pools are ordered by the species’ Pokédex number. Since nests always overwrite low rolls with the nesting species, the lower 25% of pool entries always get overwritten by the nesting species.
This can be easily observed in the screenshot below. At the time of taking it, Ponyta spawns occupy rolls below 0.15. It makes them common everywhere, but completely within nest boundaries.

Investigating this was a lot of fun, and I’ll continue researching more hidden game mechanics. In the mean-time, feel free to check out Demap to check out how I implemented these findings into my mapping project.