Accounting Logic Must Not Follow Physics Too Closely

Once I had finished what I considered to be a release-ready version of my accounting/logistics mod (Factory Ledger version 0.8.0), I began working with it to verify its behaviour.

The first thing I examined were the transaction accounts: Receiving, Transition and Shipping. I performed a full reset. After resetting, all inventory should obviously be zero. Then I checked the receiving side. That looked fine. I could clearly see the three basic materials — coal, iron~ and copper ore — flowing into the system. The accounts became positive accordingly. Good. That was exactly what I had expected.

Next, I looked at the shipping side, i.e. the outbound side. This naturally takes a while until the system ramps up because all the intermediate buffers have to be filled first: copper has to move from raw resources through multiple processing steps before finished products appear. The shipping was OK, too. The number of utility science packs shipped is the same as the number produced.

As a side note, one could probably optimise this ramp-up with some kind of priority control, but that is a separate optimisation topic and not relevant here.

Head of the movement table in Excel.

What mattered was this: something did not look right. The transaction account was not balanced. As it should have been. At that point, it was clear that a detailed analysis was needed.

After letting the system run for a while, I ended up with around 200,000 transaction records. I exported these and loaded them into Excel. As the factory ledger's output format is semicolon-separated, I simply converted it to tabs and replaced the equals signs. Then, it could be pasted directly into Excel. After that, the columns need to be labelled properly, and once they are clean, analysis becomes straightforward.




8 furnesses M10 to M17 for copper-plates


The first step was simple filtering: what goes in and what goes out? Again, something was clearly wrong. 

I took a small part of the whole system, a section with not too many different materials. Here we only have copper ore, and this copper ore is smelted into copper plates. I have eight furnaces, and my mod has registered all of them. This allows me to identify in my pivot table what is a machine, what is an inserter, and what is a chest. They are clearly marked with numbers. You can see this on the right-hand side.

With this part of my data in the Excel sheet, I created a pivot table, which is also shown on the right-hand side. In the rows, you can see the different inserters, each identified by a number, for example 115, 117, and so on. In the columns, I have copper ore and copper plates, always showing the give and the take.

As in accounting, this should be balanced. Everything that goes in should also go out. This is logistics. There is no creation out of nothing, and there are no black holes where things disappear. So the system should be balanced.

But if you look at the table, you can see that this is not the case. There are inserters that give more material than they take: they put material away, but they take less. And there is also the opposite effect. Some inserters give only a small amount but take much more. This is very difficult to understand.

Examining  the code explained why.

A transaction was logged when the last item was placed onto a belt or when the last item was taken. From a physical perspective, however, these processes often consist of multiple actions. A stack inserter may pick up several items at once, but then place them onto a belt one by one over many ticks.

The code, however, created only a single booking for this entire process, and this booking used the remaining item count at the final moment when the event was emitted. As a result, if an inserter took 12 items from a chest but placed them onto the belt one by one, the final GIVE event often recorded a count of 1, because only the last item was still in the inserter’s hand at that moment. The corresponding TAKE event, however, still showed 12. From an accounting perspective, this is clearly wrong.

This issue did not become apparent by just thinking about the problem or by reading the code. It emerged through a discussion with ChatGPT. I described the observed imbalance in the data and the behavior visible in the pivot table. ChatGPT then analyzed the relevant Factorio API events and their semantics. This led to the insight that different internal events are involved in taking and placing items, and that my logging logic was tied to the wrong moment in time. Based on this understanding, I decided to change the implementation to fix the issue.

Factorio simulates physics. Accounting does not.

Physically, items move one by one on a belt. From an accounting perspective, however, a transfer is a transfer: moving 12 items onto a belt is one booking with a quantity of 12. Software sits in between these two views and has to reconcile them. That is harder than it looks.

In the original implementation, physical micro-steps and accounting transactions were mixed. Inserters in Factorio operate as a continuous physical process: items are picked up, held, and released again, sometimes instantly, sometimes over many ticks, sometimes stalled because a belt or machine is blocked. During a single logical transport operation, the internal held stack count can increase and decrease multiple times. From an accounting point of view, these intermediate steps are irrelevant. What matters are the net effects: how many items were taken from a source and how many were delivered to a destination.


link to factorio mod portal
(under development take newest)
The mistake was treating low-level state changes, such as the inserter hand becoming empty or non-empty, as if they directly represented business transactions. That works only as long as quantities change atomically. As soon as stack sizes, belts, and partial unloading come into play, this approach breaks down.

The fix was therefore not to log more events, but to change the abstraction level. The physical simulation remains fully tick-based and unchanged. Internally, the software now aggregates quantity changes while the inserter is holding items: increases are accumulated as pending TAKE quantities, decreases as pending GIVE quantities. Only when a logical transfer is complete is a single transaction emitted with the correct total quantity.

This cleanly separates physics, software, and accounting. Physics defines what really happens, software makes it work, and accounting produces data that is consistent and analyzable. This issue only became visible once I stopped guessing and started analyzing the transaction data itself.

This approach deliberately separates three layers that should never be conflated:

  1. Physical simulation: Tick-by-tick, item-by-item behavior (belts, delays, blocking, partial unloading).

  2. Logical transport operation: “Inserter moved X items from A to B,” regardless of how long that took or how many micro-steps were involved.

  3. Accounting / analysis data: Stable, aggregate transaction records that can be summed, balanced, and meaningfully analyzed in tools like Excel.

Once this separation is enforced in the code, the transaction data becomes consistent and trustworthy. Quantities balance. Pivot tables make sense. And higher-level analysis — throughput, bottlenecks, abnormal delays — can be built on top of the accounting layer, instead of being confused with it.

The bug was not a Factorio quirk, but my modeling error: accounting logic must not follow physics too closely. It has to abstract it.

Transparency note:

I am responsible for the ideas, concepts and overall direction of this work. I define the questions, goals and constraints. Throughout the process, I work closely with AI systems in an assistant capacity.

They help me to think through problems, read and interpret APIs, validate assumptions, write and refine code, and improve technical explanations. This support is especially effective in areas where speed, breadth and systematic analysis matter.

This does not replace authorship or responsibility. The division of labour is clear: I act as the spiritus rector, setting intent, structure and judgement, while the AI system acts as a highly capable assistant, helping to translate these ideas into concrete implementations and texts.



Blog: , Seite:
Version: 1.4 April 2025, Kontakt: E-Mail Martin Wölker
Pirmasens, Germany, 2018-, ausgelesen am: , Licence CC BY



Kommentare