feat: Audit and add all divergent/regional evolution lines - #1656
feat: Audit and add all divergent/regional evolution lines#1656FallenDeity wants to merge 8 commits into
Conversation
jemarq04
left a comment
There was a problem hiding this comment.
This is great! Whenever I've updated the evolutions I've left these "form" fields pointing to Pokemon as they were initially, but this is so much cleaner! Plus the solution for Toxtricity is very clean. I added a few comments and questions here.
As a separate thought, I'm not sure that I am loving the condition expression? It is difficult to read and we don't translate this in any way before it's served. Adding a single field that acts as an arbitrary expression seems too complicated and reads confusing. I know that these are odd conditions to the evolutions, but I feel like we can find a better way to model it. Maybe just additional unique evolution triggers?
|
a few thoughts of mine of the expression side
this is what an expression looks like in the evolution chain it is a postfix/rpn expression https://en.wikipedia.org/wiki/Reverse_Polish_notation parsing it to a human readable form is trivial and it gives quite a few benefits over an infix expression the expression is actively evaluable by any client consumer programming language agnostic this is not a fixed trigger it depends on values and we can let consumers decide how they are going to display the expression hence variable information and data types are tagged under evolution-variable, they can either make it into human readable via stack operation or evaluate it based on conditions without unsafe elements like evals etc also kind of another distinction is a trigger is a specific event to be met to lead to evolution while condition expression is like a baseline condition that has to be met its not guaranteed pokemon will not release a scenario where there is an expression as such and an event too like even rn a new trigger only would combine level-up + another condition |
|
Vivillon can be easily handled too, but with many lines. The main issue would be Alcremie, awful case, which needs more columns in the trigger file (?) |
|
dont think we need to handle vivillon since any spewpa can evolve into any form based on real world region unless we want the real world mapping we can skip it evolved_form null should just be a catch all to any vivillon form, same for milcery again unless we want all permutation of which sweet + swirl/spin found an exhaustive list for future reference if we ever need to come back to this: https://bulbapedia.bulbagarden.net/wiki/List_of_Pok%C3%A9mon_with_form_differences |
…ndant wurmple chain form references
|
Handling Scatterbug → Spewpa → Vivillon should be 40 rows. The key question was whether the pattern is decided at evolution or earlier. Bulbapedia, on the Scatterbug page:
So Scatterbug and Spewpa already carry the pattern from the moment they're generated, even though they look identical. These are 20 real internal forms, and the database already has all of them (20/20/20). A river Scatterbug always yields a river Vivillon. Structurally this is the same as Flabébé → Floette → Florges, where the branch didn't hesitate to add 5+5 rows. Cost: 20 rows off row 342 (spewpa, lvl 9) + 20 off row 341 (vivillon, lvl 12). Two warnings if you take it on:
|
Ah ok, I had not heard of the postfix notation before now and coming from a math/physics background this was a very confusing expression haha! I suppose I now understand the reasoning behind this, but I'm not sure that I'm 100% onboard either. The data we serve is (at least I believe it is) human readable. For example, one could provide the allowed natures being proposed here as a bitmask assuming the user would parse it themselves, but its better to provide it in a human readable way as a list of nature resources as you've done. On the suggestion to use triggers, they have also been used to describe very specific evolutions. For example, we could have stored Gimmighoul's evolution as I'm not totally against using the expression and variables, so if this is the best approach then we should go for it and explain things appropriately in the documentation. I just wanted to see if there were any other options. Maybe we can ping some others to see what they think on this? @Naramsim @notblisy |
|
Thinking on it a bit further, I think something else that concerns me is that, in general, a user won’t have access to the EC or PID, so knowing the formula on postfix or infix notation won’t help much. These resources help describe the condition a general pokemon would need to evolve, without knowing its secret values, so a tool using this API would only be able to provide the formula. Since we want this to be as clear as possible, I think that would just be confusing. I think replacing this with a simple integer field of |
I agree just giving a percentage value is tempting but its misleading adding a percentage chance means here that there is a say 1% chance for x to evolve into y at any moment in time. But that's not the case this is not a chance at time of evolution but fixed since the pokemon is generated by the game also I do see use cases of this in terms of rng calculators, similar tools to pkhex, any simulators etc its a trade off between how much we want to abstract away and how technically accurate we want to be imo there is precedence for this with growth rate where we serve a latex formula i dont see why we cant do the same here https://pokeapi.co/api/v2/growth-rate/4/ perhaps we can serve both the formula and the percentage chance interpretation of it |
Oh, nice. Then why not apply a latex formula in |
|
because latex is for visual representation and its hard to parse, in python for example u would need a sympy which is a few mb of dependencies just for an expression, with rpn u can evaluate it in 5-6 lines of code in any language In [1]: def eval_rpn(expr, **variables):
...: stack = []
...: for t in expr.split():
...: stack.append(eval(f"{stack.pop(-2)} {t} {stack.pop()}") if not t.isalnum() else variables.get(t, t))
...: return stack[0]
...:
In [2]: print(eval_rpn("EC 100 % 0 ==", EC=105400))
...: # True (Family of Three / 3-Segment)
...:
...: print(eval_rpn("PID 16 >> 10 % 4 <=", PID=0x12345678))
...: # True (Silcoon)
True
True
In [3]: def rpn_to_infix(expr):
...: stack = []
...: for t in expr.split():
...: stack.append(f"({stack.pop(-2)} {t} {stack.pop()})" if not t.isalnum() else t)
...: return stack[0]
...:
...: print(rpn_to_infix("EC 100 % 0 =="))
...: # '((EC % 100) == 0)'
((EC % 100) == 0)also latex dosent have bitwise shift |
|
Why does this need to be parsed at all? These are secret values that the user generally wouldn’t have. If anything it should just be a static expression for the user to be able to read. |
|
so just an infix expression? i agree that for most use cases its a read only thing but some tools like check pkhex for example or similar line of tools might benefit from having the ability to be able to parse it that being said parsing from postfix to infix is trivial the other way around is slightly complicated but doable given its a niche use case I can make it an infix expression I suppose if that sounds better |
|
I think the use case of within PKHeX is the only one I can think of where having this as an easily parsed expression with postfix would be useful. Unless the user has the save data accessible to determine the PID/EC, an infix expression would be clearer, as this would be commonly accessed as the condition needed for any given pokemon (e.g. wurmple) to evolve. I see your point on the percent chance field – maybe we move forward with an infix expression then? We can wait and see what the others think about it though. |
|
I tend to lean toward abstraction in cases with formulas. If I'm a casual player, not RNG Manipping or using PKHeX or ACE, I am going to this API or a website that serves it to know how do I get a silcoon or cascoon from a wurmple. The answer is effectively: Catch a couple and evolve them, it's random. And we can figure out roughly what the chances of 0 - 65535 mod 10 are to give each result. So, each wurmple has X random chance. I don't even know if we'd have to put what percent it is, I'd probably just have an evolution method that says "random". It's random. Some are cleaner than others-- Stuff like maushold or dundunsparce are more easily abstracted into its 1 out of 100 rarity or whatever, but idk. I'd probably have a random tag and then maybe an explanation section in prose where each time a thing is used you can put the formula there, similar to how we do encounter conditions, right? So like, if someone wants quick info from encounter method, it will just say "trade X Pokemon", but prose could be lengthier. |
|
You could always create another table that contains both infix and postfix expressions, and make |
|
Since the evolution trigger is different between them (level up or the special case of tandemaus), I think a condition is still the best place. And since it's X mod 10, the result will always be between 0-9. It'll be 50/50 Silcoon/Cascoon, and then 99/1% Maushold and Dudunsparce forms. |
|
I would still prefer the percentage, as it's the end result of the formulas that would need to be evaluated with secret values. If someone wanted to figure out the method behind the randomness, they can look online and discover the PID/EC calculations. I personally only knew that it was 50% Silcoon and 50% Cascoon, I hadn't ever taken the time to figure out how the game had determined it until a year or so ago. |
|
lets go with infix then given its a niche enough case that someone needs the evaluation they would just have to write a bit of extra code for it for vast majority infix will be readable out of the box |
|
Seeing as we have 2 for percentages and 2 for the expression (one maintainer on each team too) maybe we bring in @Naramsim as a tie-breaker lol |

Change description
Went through a list of complicated/branched evolutions and regional ones to make sure everything being served by pokeapi is accurate and information regarding it is complete
This adds some new evolution information
condition_expression: for some formulaic ones like tandmaus form, dundunsparce, wurmple etc which rely on encryption constant/individual valuesneeds_one_of_natures: for toxel -> amped/low-key (this is 25-bit bitmask field for the natures)base_formandevolved_formfrom pokemon to pokemon-form rationale behind this is pokemon form is the complete store of all variants and some specialized ones which evolutions depend on like burmy, sinistea etc which are more cosmetic variants with no stat changes or differences other than look but they are canon for required evolution base and next forms so moving everything to pokemon form helps with cosmetic evolution dependencies and also acts the same for ones with technical differences since they point to the respective pokemon entry for stats/moves etc without introducing new form specific fields for this use case and future proofs use for more such evo linescases are some pre forms might be cosmetic like burmy but permanent when wormadam, or both pre and post like sinstea line where they have the fake one and the legit one
References:
Context
Fixes: #1199 #1315 #966 #1037
#1086 partially my understanding goes like this pokemon-species is the biological species in the pokedex its concept and lore, then pokemon further adds the mechanical context like stats, movepools, game timeline etc, followed by form which finally adds the cosmetic layer and points back to pokemon if there is a mechanical change so fallback is in order from that abstraction layer
I dont think species should be removed since some regional variants like paldean wooper etc still use the national dex 174 id and not some 1000+ they share similar egg cycles, hatch rate and pokedex based stuff I am of the opinion we can specialize based on the abstraction layer as mentioned above with some minor details we can move around like mentioned below. going with a unified model destroys all abstraction and generates tons of dups of stuff with only cosmetic changes or visual like vivillon, alcremie etc
#1026 partially again since some points do make sense but species should still exist since thats the lore or universal stuff it holds like pokedex has only one entry for rotom and then it differs bw diff mechanical forms removing species would be duplicating all of that, same for gmax, megas etc or any variant pokemon like deoxys, the evo chain changes rn currently solves both of above issues pertaining to this
some arguments such as gender diffs is not technically valid since there is no stat diff and there is visual diffs which is already present as sprites in pokemon form and pokemon
partial because color_id is valid that changes for regional it should go under pokemon-form since its cosmetic imo
prolly a few others since evo chain linking has been a pain point bought up multiple times this should solve that but I agree current pokemon evolution chains are now no longer linear so if anyone is working with similar to what i linked as a tree below they WOULD have to use something like a tree or a graph with trigger conditions as edges if they want a robust solution.
AI coding assistance disclosure
Used AI to review/summarize some of the bulbapedia and pokeapi.fr site pages for proses and info, locate some csv records and flows etc
Contributor check list
A cli tree constructed from our evolution chain data for some of the complicated lines for review
Details