Back to AI War 2Community Announcements
5.808 Memory Inferno
May 23, 2026by x-4000 (Chris McElligott-Park)
GamePatchNote Digest
AI War 2 Patch Notes — May 23, 2026
Aggregated from Steam, cross-tracked with Battle.net coverage on GamePatchNote.
Full release notes here.
It's been a hot minute since I got really involved in the code of this game, but here I am again. The code updates are pretty immense, and are paired with some similar updates to Heart of the Machine. Figured I would approach both games at once -- AI War 2 benefited even more than HotM!
As far as Badger's work on mods, there are a bunch of tweaks to the Armada Empire faction; balance and economic overhaul. And for the Dyson Empire, mostly balance changes; some nerfs but mostly giving the Reapers (the Dyson's enemies) some real teeth at higher difficulties.
5.808 Memory Inferno Changelog
General
- Imperial Spire: the AI King can't be targeted until the dire guard posts are dead. This will make the Imperial Spire slightly more efficient at ending the game.
Code Improvements
- The game is now set to use C# 13 as a language, despite still being on .net 4.7.1. This is important, because there are features from C# 8 and 10 that I need, and that unity profives. However, it also includes more stuff than the game could run on its own, so just don't use those (those things would compile, but then the game would choke on them, so no big deal.
- FunctorComparer is now a struct, and thus does not allocate anymore when lists, etc, are being sorted.
- Updated all of the Sorts to now be static sorts. Both in the main game and all of the mods that are not broken. This dramatically cuts down on the memory pressure from these things.
- Backported the Heart of the Machine threading control improvements so that long-running threads spin up more quickly and are reused more efficiently. This also makes ThreadStatic hits much more consistent.
- Fixed up how the enumerators for most collections are gotten so that they no longer cause boxing and thus don't cause GC churn.
- Fixed up small structs to properly use IEquatable so that if they are the key or value of a dictionary, they no longer get boxed and again don't add to GC churn.
- Fixed 18 of the 19 initially-identified spots where ForEach/DoForEach were causing memory to throw because of how they were passing data in. However, then identified another 2800 spots where the same pattern was being used, and did not push into them because the carnage to the codebase would be immense.
- Fixed a really boneheaded mistake with ArcenInput calling multiple times per frame to look at textboxes when they were there. This just added slowness and some memory pressure any time a textbox was on screen.
- Ported in some of the improvements from HotM's ArcenDoubleCharacterBuffer, but only a few of them in order to not break mods. What happens now is basically there is less memory pressure when there are no-op situations.
- Improved the handling of the EnumIndexedArray class family to no longer box and thus no longer increase memory pressure. We couldn't do that with the prior 7.3 language version, but now it is possible. It is peppered all throughout the codebase, and each call that is made to it saves 17 bytes per call. There were several hundred calls per frame, so that could add up to several KB. This is on a frame that was measured using 900KB per frame, even after prior improvements were verified to work.
- We now get our IP addresses, when desired, from a different service that is run by cloudflare. It is snappy and doesn't have delays and outages like the service we used to use sometimes had.
- A new kind of closure-less, struct-based enumerable surface is being provided to a lot of the kinds of code that the game uses, and those can then be iterated at the cost of no garbage in the form foreach ( var v in theThing) {}, rather than theThing.DoForEach( delegate (var v ) {} ); like it used to be. It's very similar code, but yields no garbage whereas the old code yielded hundreds of kb per frame in aggregate. There are 2600+ caller sites, though, and mods also exist, so the old style and the new will coexist, and be converted as there is time. The net result of this is that things will be faster as well as more correct, since they don't have to go to the heap to get the data they need just to iterate. This would mainly benefit simulation speed more than framerate, and it would benefit larger and more complex games the most.
- Updated the ConcurrentDictionary to use a new enumerator style that is zero-gc, doesn't involve a yield, gives the same result as before, is 1.9x faster than before when there are entries in place, and is 4x faster than before when the dictionary is empty. This happens all over the place, so it's a big improvement.
- Thread Abort Exceptions are now purposefully quieted, as they are part of the normal operation of the game and not something to worry a player about. Also, a variety of spots have been adjusted to use those now.
- A bunch of code has been converted to use the new foreach style instead of the closure style. It's that thing that is functionally-identical but doesn't throw off garbage in the heap memory. I've done a big pass through both mods as well as the external code.
- The custom StringBuilder class that we were using for many years was lacking various hardware acceleration capabilities that the official one had. It has been retired.
- A new ChainList has been added, which is backed by a BucketedArrayPool. This is to be used for GameCommands so that they can "soft reuse" memory, checking it in and out. This is a bit more complex than the cyclical array pool concept that I've been using for years in AI War 2 and refined further in HotM, but it's the same broad concept. Basically, there is a library, and the ChainLists make requests from the library to rent arrays of specific sizes (at the moment, of 16 entries and 32 entries long for int, float, string, and FInt, but it can be for any size), and then they hang on to some number of rentals that are given back, but not over a certain size. The sizes of 16 and 32 are much larger than the 4 and 8 that GameCommands used to use, so that's actually something that will make them have more headroom before having to expand, and should help balance out the performance of them having to talk to the librarian with a lock vs just allocating. It does lead to a lot of "wasted" memory on smaller lists, but that's actually not a problem since it's all recycled within seconds at the longest (and usually faster than that) anyway. The total pool of required memory is actually less with this approach, despite all this extra headroom, because we aren't involving the GC. At the moment it is only allowed to use about 32 MB of memory for the sites that are being targeted. This should cover about twice the capacity needed for a busy multiplayer game with a lot of AIs. If the game still needs more than that in aggregate, then it will just be hitting alloc to the heap more, like it is now. As part of the contract of this whole thing, enumeration is very fast, but direct indexing list is disallowed because it's VERY slow and pointless. The internal structure is a linked-list of arrays, which is similar to how the Microsoft ConcurrentQueue structures itself, and it also does not have an indexer for the same kinds of reasons. On the other end, the ChainList assume that only one thread is writing at a time to it, but any number of threads are reading from it, matching my own customized List<> methodology. That dodges some of the worst downsides of the ConcurrentQueue, with the shift of course being that ConcurrentQueue can have many writers at once, which the ChainList cannot.
- The game is now fully converted over to using ChainList for all of the GameCommands, and also all of the mods have been updated to use that. Per-frame peak GC calls has dropped from 900kb to 416kb on the spikes, which is honestly less of a savings than expected, but that just means something is still boxing in some way. The other smaller frames have all dropped by 50-100kb per frame, except for the smallest-possible alloc frames that were already at about 50kb. This is a huge savings in terms of gc churn, and should make the game run a lot smoother in general. Still going to investigate further, though.
- Added a new little python program that recompiles all the mods, which is even more useful here compared to the version I have in HotM. This skips any mods that are explicitly disabled as being nonfunctional, too, which is nice.
- Fixed some issues with a debugging path that used a closure in the network/serialization code that was hitting a quirk of C#'s compilation to IL. Basically, when multiple closures reference the same thing, it winds up boxing them. This was causing a truly huge amount of gc churn -- at least 200kb per network frame -- even when logging is turned off, and even in single-player. This was a huge waste, and has been refactored to not use closures so that it doesn't run into this bug.
- Fixed another 55 or so boxing sites relating to value types in various collections. This was on my list of things to port from HotM, but it hadn't been done yet.
- The system menu's about section is set to have expandable sections that don't cause such huge gc alloc just being in the menu (it was 2.8 MB of GC from just that one window when it was open, 3x a second, before).
- The Escape Menu now shows the memory and framerate and sim speed much higher than before, all together in two lines. The rest of the extended details that would be in this menu are now in expandable sub-sections, as has been the case in HotM for a while. This prevents the escape menu itself from hammering the gc 3x a second. With this in place, I can easily verify that the per-second gc pressure added is between 1-5mb, rather than being 50mb+ like in prior versions.
- Fixed an issue where at some point in the past, the tooltips were set to have unrestricted update speeds that did not properly cache their last values. This led to no visible difference in the tooltips, but was causing them to use way more cpu and also to hammer memory for about 200-400kb per frame. Now that no longer happens.
- Fixed a similar issue with the ongoing message display in the upper right-hand corner recalculating the size of its text every frame rather than every time the text changed, which was making it bash the cpu and the ram.
Forge of Empires
- The tyderian abomination gives the armada more resources
- Nerf the Jrathek so it can no longer paralyze elderlings
- Increase the mass of some Tyderian ships; an additional jrathek nerf
- Improve the description of the tyderian sommoning hacks to indicate the resource in question
- Korlarh can no longer be zombified, science mines cost more metal (a flat 200K)
- Armada mines no longer use the 'less resources the more times you drill' concept; instead there's a Tiering of drills; every X times you drill you need to use a higher tier; this generates more resources but costs a lot more metal. Intended to make metal more of a constraint, so you can't just endlessly drill science at minimal effort and to make things more interesting. Ideally this will make players feel like they need more metal, so they capture the producers
- Make the Reapers actually honour their difficulty setting; previously a lot of their difficulty tunables were only at "difficulty 5"; higher difficulty levels should be more meaningful now. In particular, there's more of an AIP cost for drilling planets at higher difficulties
- Spheres can no longer be built adjacent to eachother
- Neinzul Moon drones now attrition more readily
- Big thanks to The Grand Mugwump for detailed notes and suggestions, and also to Ktosiek for additional feedback
- Some nerfs to the DZ Empire zenith and templar turrets
Heart of the Machine 1.0 Is Out Now!
https://steamcommunity.com/games/2001070/announcements/detail/538879349212841215 https://steamcommunity.com/ogg/2001070/announcements/detail/664986477286917109
https://store.steampowered.com/app/2001070/Heart_of_the_Machine/