2024-08

It’s somewhat nice to have been procrastinating writing this month’s post not because of a dearth of updates as has previously been the case but instead because there’s been too much progress to properly cover and too many more interesting things I’d rather be working on.

Last June I got burnt out working on the -Dchance and -Dcalc features of the engine, specifically in the process of attempting to complete what I have been calling the transitions function. basket helped originally motivate this work, but after I understood the use case I immediately recognized it could potentially unlock the “holy grail” I’d been chasing for a while – being able to use one codebase to solve multiple different problems. My work on damage calculators helped shape my thesis that correct and complete Pokémon tooling necessarily requires a full blown engine with a complete mechanics implementation, but different use cases (simiulators, AI, damage calculators) need to make different tradeoffs which makes it difficult (or perhaps undesirable) to attempt to optimally support them with a one-size-fits-all solution.

My original attempts at adding the features to the engine to support this functionality were fraught with problems – I didn’t properly understand the full complexity and nuances and kept trying to bolt-on edge cases or make small adaptations to my original designs which didn’t pan out but which involved a lot of churn and frustration. Furthermore, I was trying to solve the whole problem at once without making sure the abstractions I was attempting to build on were solid and correct, which meant that it was often unclear if my entire approach was wrong or simply half-baked and poorly realized. In the end, I abandoned the work, thoroughly discouraged from pretty much all coding (I spend the latter half of the year making some progress on Generation II support in the engine and then building this site and pkmn.ai instead).

Having already been burned, I was very afraid of returning to the problem – continually throwing away thousands of lines of code after every attempt in the process of developing an understanding for the problem space is fairly demoralizing and is also why @pkmn/logs and @pkmn/img have been similarly placed on the back burner (though in those cases I am fairly confident that I do know how to solve the problem at this point, it’s just a matter of writing the code). However, in the case of transitions and friends (“durations”, “pending”, etc), I was also confident that to figure out a solution I would need to be willing to let the problem fully consume me as there is too much state and complexity to be able to work on it in a casual manner over the course of several months. This was rather unappealing to me as “no-lifing” Pokémon development is a rather at odds with many other life goals.

Fortunately(?), developments in my personal life made it so that distracting myself for a month by focusing all my thoughts and energy on a hard problem actually proved to be desirable, and as a result I returned to this work and happily made a ton of progress:

With the transitions function finished for any scenario not involving durations, I started to get excited about the potential applications. I spent some time thinking about how it will be used to implement a robust damage calculator which led me to flesh out my draft of a forthcoming article on “Damage Calculators from First Principles”. The current assumptions made by damage calculators are rather specious, though clearly damage calculators are thought to provide value in spite of this. I feel that in a lot of cases people are trying to use a damage calculator as an incredibly abstracted solver… which is why I then spent a lot of time exploring using transitions to build a solver for Generation I perfect-info 1v1 endgames.

basket has done a ton of work here, though unfortunately I need to redo a lot of it myself to be able to understand and appreciate it. I spent about a week taking a deep dive into linear programming and Nash equilibrium solvers, especially getting my hands dirty in the guts of lrslib. I think I ultimately will need to write something custom here, partly due to licensing issues but also because I feel there are potential gains to be had by optimizing for the specific domain – the Pokémon use case involves solving zero sum games on exclusively small dimension matrices and the AI use case can often afford to trade accuracy for speed (an ε-Nash equilibirum is sufficient for small enough ε).

I was eventually motivated enough to return to finish transitions completely, and resumed working on the final hurdle – “durations”:

Ultimately, I wasn’t able to fully finish implementing durations in August (the transitions function still needs to be able to iterate over all possible duration outcomes), as I pivoted to working on usage statistics instead after seeing activity in the smogon/usage-stats repository. pkmn/stats has been sitting at effective feature parity with Smogon’s Python scripts for years but I never made a push to get it used upstream as I was hoping to wait to be able to deliver the 100x gains @pkmn/logs can theoretically unlock. However, the features, maintainability gains, and modest speed improvements that even naive usage of the existing pkmn stats code can provide are valuable to realize independent of any further improvements, so I will be working to onboard Smogon as soon as possible. Tbolt has made some great contributions here, and hopefully the move to a modern codebase will encourage more future contributors to help push the community’s stats processing forward after a long period of relative stagnation.

Finally, I attended a local Zig meetup which both motivated me to work towards eventually being able to give a talk about the pkmn engine and its usage of Zig (which requires at least shipping something first!), and to add back support for Zig v0.11.0. I was concerned about the new features I’ve been adding to the engine impacting performance, but thankfully that’s not the case – the Zig compiler has just gotten a lot worse at optimizing it. It’s going to be a challenge to maintain support for newer versions of Zig given its volatility and the potential for breaking language changes, but ultimately a key reason the engine is written in Zig is the speed of the compiled code and taking a 20% hit is a non-starter.

pre