Skip to content

Repository files navigation

Better Belt Balancer

A Factorio 2.1 mod. Balancer parts are 1x1 tiles: place several next to each other and they become one balancer, of whatever shape you built. The belts feeding it are its inputs and the belts it feeds are its outputs; orientation alone decides, so there is nothing to configure. Each part connects to one belt, so a four-in four-out balancer is eight parts: four carrying the inputs and four carrying the outputs. Items are balanced across every output exactly, per lane, under every load condition: saturated, starved, partially blocked, asymmetric.

The mod is written in Go, compiled to WebAssembly by TinyGo and then to Lua by FkLua. That covers both halves of a Factorio mod: the control stage that runs while you play, and the settings and data stages that declare the prototypes. There is no hand-written Lua in the shipped mod.

How it works

Existing balancer mods move items from Lua on every tick: they hold the transport lines of every belt in every balancer and shuffle items between them, so their cost grows with how many balancers exist and how busy they are.

This mod compiles instead. When a cluster of parts changes, the guest (the Go program FkLua compiled to Lua) reads the belts around it, plans a network, and builds that network out of real splitters and belts on a hidden surface, stitched to the visible world with linked belts. Then it stops. A running balancer executes no script at all: every item that moves through it is moved by the engine, exactly as if you had built the splitter tree by hand.

   what you build                        what the guest compiles it into
   --------------                        ------------------------------
                                         hidden surface, this cluster's slot
   ==>[#][#]==>                          [>] [L] --.   .-- [S] --.   .-- [>]
   ==>[#][#]==>       <== linked ==>     [>] [L] --'`-'`-- [S] --'`-'`-- [>]
   ==>[#][#]==>           belts          [>] [L] --.   .-- [S] --.   .-- [>]
   ==>[#][#]==>                          [>] [L] --'`-'`-- [S] --'`-'`-- [>]

   eight parts, one cluster.             [>] linked belt   [L] lane splitter
   Four belts point in on the west       [S] splitter.  Two butterfly stages
   face, four point away on the east:    for four lines; the lane splitters
   that is four inputs and four          are why the two lanes of one belt
   outputs, and nothing was configured.  balance and not just the belts.

The network is a butterfly over P = next_pow2(max(inputs, outputs)) lines, log2(P) stages of P/2 splitters, with a lane-splitter stage on entry that makes it lane-accurate rather than only belt-accurate. A 4x4 balancer is 32 hidden entities; an 8x8 is 84.

One balancer supports up to 64 belts per side (inputs and outputs counted separately); the limit is the size of the hidden-surface slot a network compiles into.

One belt per part

Each part connects to one belt. A part already serving a belt refuses a second one: the balancer you have keeps running exactly as it was, the piece comes back to your inventory, and a message says why. Rotating a belt so that it points at a part that is already serving one is refused the same way, and so is placing a part that would join two balancers into one where the joining part would end up with a belt on each side.

The reason is the engine rather than a design choice. Each belt a balancer touches is served by a hidden interface standing on that part's tile, and Factorio 2.1 allows one of those per tile. Building a wider balancer is a matter of building it a part deeper: a column of parts on the input side and a column on the output side, with as many rows as you have belts.

Fast replace

Balancer parts share base's transport-belt fast-replace group, so a part held over a belt, an underground belt end or a lane splitter replaces it the way a splitter does: the belt and whatever it was carrying go to your inventory and the part takes the tile. Dropping a balancer straight into a belt line you already have is one click per tile. Splitters and loaders are not replaced this way (a splitter is two tiles wide, and loaders are a different group).

The group works in both directions, so a belt held over a part replaces the part. Only parts with no belt against any of their free faces can be replaced that way: a part on the edge of a balancer carries the hidden interface that connects it to your belt, and that interface blocks the placement. Dragging a belt line across a balancer therefore takes out the parts in the middle and is refused at the edges, exactly as dragging a belt across a splitter does in the base game. The parts come back as items and the balancer recompiles around what is left.

The collision mask is unchanged: a belt still cannot be laid through a balancer, only fast-replaced onto one part at a time.

Cost and research

Two startup settings decide what a balancer part costs. Both are under Settings > Mod settings > Startup, and both default to what the mod has always shipped, so an existing save is unchanged by the update. Startup settings need a restart of Factorio to take effect.

Balancer part recipe picks the ingredient list:

Option Ingredients
Default 4 iron plates, 2 gears, 2 transport belts
Cheap 2 iron plates, 1 transport belt
Fast belts 4 iron plates, 2 gears, 2 fast transport belts
Express belts 4 steel plates, 2 gears, 2 express transport belts
Splitter 1 splitter, 2 iron plates
Express splitter 1 express splitter, 2 steel plates

Balancer research cost picks which technology unlocks the balancer: Logistics (the default), Logistics 2 or Logistics 3. The cost is read from that technology rather than written down, so it follows whatever your mods charge for that tier, and the balancer's prerequisite moves with it so that it sits beside its own tier in the technology tree.

Both settings are safe in an overhaul pack. No ingredient name reaches the game unless that item is present: each one has a chain of substitutes ending at iron plate, and the first item your mod set actually has is the one used. A recipe naming an item nobody defined would refuse to load the game, so it cannot happen. The same holds for the research: if the technology an option names is missing, or has been turned into a trigger technology with no research cost, the next tier down is used, and if none of them can be read the balancer costs what base charges for Logistics and has no prerequisite.

Belt speed

The belts inside the hidden network run at the speed of the fastest belt in your game, and never slower than 0.25 tiles per tick (120 items per second, about 2.7x express and 2x turbo). In a vanilla or Space Age game nothing is faster than that, so the network runs at 0.25 and a balancer never limits the belts feeding it.

If a mod adds a belt faster than 0.25, the network follows it. Every belt-connectable prototype in the game is read at the last data stage, the fastest speed among them wins, and the four hidden prototypes are given it. That covers transport belts, underground belts, splitters, lane splitters, loaders and linked belts, so a mod that raises only one of those families is still seen. A mod whose own final data stage runs after this one and raises a belt then is the one case that is missed, and the cost is the old behaviour: the network runs at the second-fastest belt's speed.

Performance

Measured on Factorio 2.0.77 headless, base only, Apple M3 Pro, against belt-balancer-2 v2.0.9 and belt-balancer-3 v1.0.1 on identical rigs with a no-balancer control, every arm run back to back in one session. Method, caveats and raw rows: bench/baselines/RESULTS.md; harness: bench/README.md.

per saturated 4x4 balancer, per tick bb2 bb3 this mod ratio
express belts, whole tick 21.9 µs 23.1 µs 0.49 µs 45× / 47×
express belts, mod Lua only 19.1 µs 21.0 µs 0 equal to the control
normal belts, whole tick 7.55 µs 7.67 µs 0.35 µs 22×
  • 200 saturated express balancers cost 0.64 ms/tick against bb2's 4.92 ms: 4% of the 16.67 ms 60-UPS budget instead of 30%. 500 of them cost 2.05 ms/tick.
  • scriptUpdate matches the no-mod control in every cell. There is no on_tick handler; all compiling happens when you build, and a benchmark of a finished save runs none of this mod's Lua.
  • Balance is exact: 1,740,000 items over 200 rigs at a max/min of 1.001, with a per-output spread of 0.15% saturated. Starvation, blocked outputs, asymmetric port counts and recompiles under load all hold, with a headless test for each.
  • A megabase mix (404 balancers of ten shapes plus a 16x16, a 32x32 and a 64x64; 4,376 hidden splitters) costs 0.33 µs per balancer per tick, and the 64x64 splits 64 ways at a max/min of 1.0028.

Item conservation

  • Editing a running balancer never deletes items or drops them on the floor: it drains the hidden network and puts the items straight back into the network it rebuilds (1,173 in, 1,173 out, checked inside one tick; zero items on the ground across a hundred add/remove cycles on a saturated rig). Only a real removal, the last part mined or the surface deleted, returns them to the world.
  • Mining a balancer hands the drained items to the miner's inventory at every step, not only when the last part goes, and mining a belt off its edge counts too. Only what the inventory cannot take spills, as when mining a splitter that was holding items; a robot deconstruction still spills.
  • Space Age stacked belts come back stacked.

Migrating from Belt Balancer 2 or 3

If you are already using Belt Balancer 2 or Belt Balancer 3 this mod adopts what they built. Uninstall the old mod and load your save with BetterBeltBalancer installed: every balancer part left standing from the incumbent mod becomes one of this mod's parts, at the health and the quality it was standing at, and your stacks and blueprints of the old part keep working and place this mod's. The conversion happens once, at load, before the first tick, and the log carries one line saying how many parts on how many surfaces became how many balancers.

On Factorio 2.1 most of those balancers will not run again until you rebuild them, and that is worth knowing before you make the cutover save. This mod allows one belt per balancer part, because 2.1 allows one belt-connectable per tile and every edge of a balancer is one. Belt Balancer's own layout is a single column of parts with a belt on both sides of each, which is two belts per part, so a balancer built that way is converted and then refused: you keep the parts, you keep the items on your belts, and each affected force gets one chat message naming how many balancers need rebuilding with a clickable map ping per balancer. The exception is a balancer you already built one belt per part -- a two-column block, inputs down one side and outputs down the other -- which converts into a working balancer and runs at full rate straight away.

So: migrating on 2.1 converts your parts and hands you a rebuild checklist rather than a working machine, except for the balancers already laid one belt per part.

Nothing happens while the old mod is still installed. Both can sit in a mod list together for as long as you like; this mod does not touch a balancer-part that belongs to a mod that is running, and it does not touch one that belongs to any other mod either.

Make sure you don't have anything of very high value inside your legacy balancer when you make your cutover save: the items the old mod was holding in its own buffer are lost in the migration. It keeps them in its own script state, which Factorio deletes along with the mod before anything of this one's runs. The items on the belts themselves are untouched.

Building

Prerequisites: Go, TinyGo 0.41.1, binaryen (wasm-opt, which TinyGo's wasm build shells out to), Python 3 (the sprite check, the test assertion scripts and the art generator), and a checkout of FkLua at ../FkLua with bin/fklua built (FKLUA=/path/to/fklua overrides). The headless tests and the benchmarks also need a Factorio 2.1 install; set FACTORIO_BIN if it is not at the default Steam location on macOS.

make zip      # dist/better-belt-balancer_<version>.zip, a complete mod
make install  # unpacked, into your Factorio mods directory (MODS_DIR overrides)
make check    # pure-Go unit tests, bindings and lockfile current, gofmt
make test     # headless verification in a real Factorio

make test creates saves with the rigs already built, benchmarks them in a real Factorio, and asserts against the guest's own log lines. Two build switches:

  • QUIET=1 compiles out every [BBB] log line below the error level. The default build is verbose because the suites assert on those lines.

  • GC=leaking builds the guest on FkLua's leaking arena instead of its paced collector. The shipped build is collected: over 3,400 teardown-and-rebuild cycles the leaking arm's heap doubled its way to 32 MiB with a 782 ms tick at the last doubling, where the collected arm ended at 0.5 MiB with a worst tick of 71 ms and no measurable steady-state difference. Both arms build, and the suites that run today are green in both.

  • The icon, logo, and sprites were created by Edjie Arts. A balancer reads as one continuous machine across any shape, with trim only along its real outline.

Repository layout

path contents
guest/go/ the control guest; data/ is the settings and data stages, plan/ the network planner, tune/ what the cost settings and the belt-speed derivation decide, fkapi/ the generated FkLua bindings
mod-data/ the assets the package carries verbatim: graphics, locale, changelog, thumbnail
bench/ the head-to-head benchmark harness, its setup mod and the results
test/ the headless suites and their assertion scripts; fixtures/ holds a small mod, also written in Go, that a data-stage check builds and stages; test/interactive/ is the checklist for the seven things a headless run cannot check, and the mod that stages both its rigs and the demo scenes
fklua.toml mod identity, the API pin, guest language, GC mode and the data module
CLAUDE.md, agents/ maintainer design notes and the full measurement record

Licence

Released under the MIT License, artwork included. FkLua, which compiles the guest, is MIT licensed as well.

The artwork under mod-data/graphics/ and mod-data/thumbnail.png was created for this project by Edjie Arts and is distributed under that licence with attribution retained. If you redistribute or modify this mod, keep the credit.

Credits

Artwork by Edjie Arts.

About

A better belt balancer mod for Factorio

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Contributors

Languages