Satisfactory Accounting
The tool is live at satisfactory-accounting.github.io.
Contents
Background
Satisfactory is a game where you get dropped into an alien world and have to construct factories out of a variety of machines and converyor belts to harvest resources and produce products to ship to space to the people who sent you to the planet. It’s a lighthearted management game that’s open to a lot of creativity, and people build all kinds of impressive factory systems across their worlds.
One of the core mechanics of the game is balancing the rates of production and consumption of various resources. Lots of early-game products are used in the production of late-game products in complicated chains that ultimately end up involving dozens of intermediate products. For example, producing 5 “Reinforced Iron Plate”, a relatively early product, requires 30 “Iron Plate” and 60 “Screws”. 20 “Iron Plate” requires 30 “Iron Ingots”, 40 “Screws” requires 10 “Iron Rods”, and 15 “Iron Rods” requires 15 “Iron Ingots”. 30 “Iron Ingots” requires 30 “Iron Ore”, which is the base resource that everything else here is made from.
Players have to find places in the world where they can mine the requisite base resources, and then have to set up their factories to balance the rates of production of different products in order to optimize production, and when setting up new factories, you often need products you’re already producing, so its helpful to know how much stuff different factories output already.
However, the game currently doesn’t tell you anything about your global production rates. You can go to any individual machine in any of your factories and see what its production rate is, but there’s no in-game tools for working out just how much stuff you have to spare. This is a game about throughputs, so checking how much of something you have stored up also doesn’t mean much.
I found that there were a variety of tools available online for planning new factories, for example there were tools that let you say what you wanted to produce and they would tell you how many of what machines to build to get the requested output, but I couldn’t find a tool to do what I wanted, which was keep track of what stuff my factories already made. What I wanted was something like an accounting tool to track my factories’ net production balances.
So since I couldn’t find what I wanted, I made it myself.
Gathering the Data
One of the key features of a tool like this would be that it would know how the game works. After all, you could reasonably keep track of your factory production in something like a spreadsheet, but that would require you to input all the production rates manually. If your accounting tool knows a bit more about the game, such as what things are available to be manufactured and what their manufacturing costs are, it can help you out significantly by letting you select things from a list.
To provide that, I would need a list of all the possible machines and products in the game, which would be a ton of work to do manually. Additionally, it would also benefit from having icons matching the icons used in the game so players can recognize things quickly, which is even more work. Fortunately the work of figuring out how to load all the game data was already solved by the folks who run Satisfactory Tools, so I was able to just pull their data and re-process it into a format that suited what I wanted to do.
Re-processing the data was a mostly straightforward task of extracting lists of products, machines, recipies and the like that were relevant to what my tool needed to do, but it did have a few instances where I needed to add special-cases to my script for certain oddities, for example their data for the output of oil rigs was multiplied by 1000 for some reason, so I had to filter that.
Structures and Balances
I started out by building a separate accounting library with no UI. What I wanted was a system where I could enter a particular building and a recipe associated with that building and automatically get a net balance for that structure. I also wanted to be able to group up buildings so that you could keep track of which buildings are in which factories.
Managing different building types required me to create a model of what kinds of buildings existed in the game, as there are a few different kinds. Miners have no inputs but produce raw materials, various kinds of smelters, constructors, and assemblers convert one or more inputs into one or more products, power plants convert different inputs into electricity to power everything else. Many of these different building types vary slightly in their mechanics, so I had to build a set of different models that could calculate the net inputs and outputs for each.
To track the total input and output of different buildings, I created a simple Balance
type which tracks the balance of power and each different item being produced or consumed
and which supports a few different operations such as adding together multiple balances to
get a net balance.
/// The balance of a node, including items produced or consumed and power used.
#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
pub struct Balance {
/// Net power in MW (negative is consumption, positive is production).
pub power: f32,
/// Net balance of each item type, in units-per-minute by ID.
pub balances: BTreeMap<ItemId, f32>,
}
impl AddAssign<&Balance> for Balance {
fn add_assign(&mut self, rhs: &Self) {
self.power += rhs.power;
for (&item, &balance) in &rhs.balances {
*self.balances.entry(item).or_default() += balance;
}
}
}
// and Sub, Mul<f32>, Div<f32>, and Sum
To allow buildings to be grouped together, I put them into a tree, with buildings as the leaf nodes of the tree and all the intermediate nodes as the groups. The balance of a group is simply the sum of the balances of all of its children, right up to the root group which is the total balance for an entire world. Players can assign names to groups and each group has a unique ID (which will become useful later for the user interface).
One thing I paid particular attention to was making sure that my system could be updated from game version to game version. Satisfactory is (as of 2023) still in early access, so the game’s balance is regularly changed. I needed to make sure that players would be able to switch the version of the game their accounting was set to without breaking everything. Fortunately, the way my balance tree works does not break if items or recipes are missing, so at first I just added a button to update to the latest version whenever the game was updated, but I later added an interface to explicitly select what version of the game you’re playing at.
The UI
You might notice that the balance type I showed before is in Rust, which is not the most common language for user interfaces, especially not web interfaces. But I really like Rust, and I certainly like working in Rust more than JavaScript, even if Rust isn’t always the best language for complexly interconnected things like user interfaces. So I found a library called Yew, which is a React-like framework that’s written in Rust and runs in the browser with WebAssembly.
Using Yew I was able to pretty easily create displays for most of the different types of machines and the tree structure of the groups. However I did have to make some custom components for a few things.
For many of the inputs, I wanted to allow a ‘click to change’ feature, where text would display as plain text, but turn into a distinct text-input box when you click on it, then switch back once you’ve entered a value, which required some custom work.
Slightly more complicated was I wanted users to be able to select buildings and recipies by typing in their names or selecting them from a list (with the list filtered based on what is typed). That’s not how HTML dropdowns work though, so I had to make a custom component for just that which combines a text box and custom-rendered list to allow practical selection of structure types. For filtering, I found a Rust library that provided a fuzzy-search feature, so you wouldn’t have to get the name exactly right in order to filter to it.
The most complicated feature to add was drag-and-drop rearranging of elements. Web APIs provide basic tools for drag-and-drop, but with inconsistent capabilities across browsers. For example, the drag-and-drop API lets you put some data in the associated object and query it from the drop location, but not all browsers actually give you the data back, so for cross browser compatibility, I ended up having to maintain part of the drag-and-drop state information myself. But I did eventually get it working.
One interesting internal feature of my tool is that once a section of the tree is built,
it is treated as immutable. So to change the state of a node, you have to rebuild all of
its parents with the changed state. However, this ends up playing really nicely with
Rust’s Rc
(reference counted pointers), which don’t allow mutation without a lock.
Essentially, what it allows me to do is re-use unchanged parts of the tree without making
new copies of them, while rebuilding the node that changed and all its parents. Because
nodes are immutable and reference counted, I can store an undo/redo history of the entire
tree any time a change is made without too much memory footprint, since most of it will
always be re-used across changes. Even moving nodes from one location in the tree to
another preserves the original node and all of its siblings at both the origin and
destination; only the parents in both the source and destination of the move have to be
rebuilt.
Releasing
To release this project, I decided to publish it on GitHub’s static hosting service, since I didn’t need any serverside functionality. It lives at satisfactory-accounting.github.io.
Once I released it, I made an announcement on the Satisfactory official reddit page to tell other player about it, and it seems some people really liked it. For privacy reasons I didn’t put any metrics on the site so I have no idea how many people actually use it, but there’s enough of them that I get issue reports and pull requests from time to time, especially when the game gets updated and I need to update my tool to match what recipies and such are in the latest version of the game. And someone found it good enough to list on the game’s wiki (though they probably list practically any tool that’s halfway decent).