Three planners in one window, all reading the running game's own prototype tables, so they work with any mod set. Factory: ask for X per second and get machine counts, module and beacon effects, raw inputs and a blueprint whose rows are as long as one saturated belt can feed. Power: size an accumulator bank, a solar field or a steam plant against the night on the surface you are standing on. Shortages: rank what your machines are actually waiting for.
Providing the player with new tools or adjusting the game interface, without fundamentally changing gameplay.
Version: 0.20.0
Date: 2026-08-28
Features:
- "Do the modules pay for themselves?" under the factory plan. It solves the same plan three
times - no modules, speed modules, productivity modules - and compares, so it needs no new
physics: the effect chain, the productivity clamps and the power figures are the ones the
plan already uses. A comparison computed a second way would eventually disagree with the
plan it is about.
- The two kinds of module trade in different currencies, so the answer is not one score.
Speed buys machines and pays in power; productivity buys raw material and pays in machines
and power, because it shrinks the whole tree behind the stage as well as the stage itself.
Measured on an advanced circuit line: speed goes 45 to 21 machines for 24 MW more, while
productivity goes to 61 machines and 400 MW more but drops raw input per item from 23.6 to
8.7.
- "Raw per item" rather than per second, so plans built at different rates compare.
- Behind a button, like the shortage scan: it re-runs the whole solve once per variant. Your
own module setting is restored afterwards, and the headless run asserts that.
Fixes:
- The "these are alternatives, not a shopping list" note was only in the steam view, though
0.19.1 claimed it was in all three. It is in all three now.
Testing:
- The stray-global scan caught the new section being called above its own definition, on the
first run after writing it.
- The headless run checks the power figure per machine against the prototype: usage times
(1 + consumption bonus) plus drain, and that the bonus fits in the machine's module slots.
That check was itself wrong first time round - stage.power is watts while
get_max_energy_usage is joules per tick - and reported a factor of exactly 60 as a bug. The
440 MW a productivity line draws is real: a Space Age electromagnetic plant pulls 2 MW, and
productivity modules take it to 10.
Version: 0.19.1
Date: 2026-08-27
Fixes:
- The steam view subtracted the standing plant twice. Sized for the gap, the generators already
running have already been taken off - they are what produced the baseload the gap was
measured after - so comparing the result against them again said "the 706 standing cover the
56 you need" when those 706 ARE the 660 MW and the 56 are on top. The gap figure is stated as
additional now; only the whole-load figure is a total a standing count may be subtracted
from.
- The three views read as a shopping list rather than as alternatives, so the pane could ask
for a bank and a plant for the same gap - and if the plant covers it, the bank is for a load
that no longer exists. Each view now says it is one of three ways to cover one gap.
- A load measured across a night the bank did not survive is not demand. After the bank empties,
what the statistics record is what the network could still deliver while machines browned
out, so the figure is a floor and anything sized from it is too small by exactly what was
missing. That night is now detected - discharge that stops before the night does - and the
pane says so rather than answering confidently with an under-estimate.
- A "generators per boiler" row, so a pair that does not belong together (a vanilla boiler
feeds two steam engines) is visible instead of only showing up as a strange count.
Version: 0.19.0
Date: 2026-08-27
Features:
- A third tab: Shortages. It scans the crafting machines on the surface and ranks what they are
waiting for - "48 machines are waiting for copper cable, mostly electronic circuit". That
number is in no statistic: a stopped machine consumed nothing, so the production graph shows
zero for exactly the item that has run out. It comes from entity.status, the same figure the
game puts in a machine's tooltip, plus the machine's own recipe and input inventory to name
the ingredient the status does not.
- Machines are grouped into working, short, backed up, unpowered, switched off and other, and
the groups are checked to add up to what was scanned. A machine blocked on its output is not
short of anything - whatever it feeds is - so those are counted apart.
- No power hides everything behind it: a machine without power never gets as far as noticing
what else it is missing, so an unpowered factory ranks as one problem rather than a hundred.
The pane says so, or an empty shortage list would read as "nothing is short here".
- Each ranked item has a button that opens the factory planner with it as the target, so
"this is short" leads straight to "here is what it would take to make it".
- The scan is behind a button and capped, and reports what it skipped. It is the only thing in
this window that has to touch entities.
Fixes:
- The steam view shows both plants again: the gap the sun and existing burners leave, and what
steam alone would need for the whole night. Removing the second in 0.18.0 was wrong - it is a
different question, not the wrong one, and the comparison is itself the information.
Testing:
- The test helper compared with subtraction unconditionally, so a string comparison did not
fail a check, it crashed the run at that line and took every test after it. It handles both
now.
- Which ingredient a stopped machine is missing is tested against a mock machine, because the
headless run cannot: a machine built this tick reports no_power, and power needs ticks to
flow. That masking is itself a finding, and is now what the headless run asserts.
Version: 0.18.1
Date: 2026-08-27
Changes:
- The steam view shows both plants rather than only the gap: what the sun and the existing
burners leave uncovered, and what it would take for steam to carry the whole load alone for
the full length of the night. The second was removed in 0.18.0 as "the wrong question" - it
is not, it is a different one, and the comparison between them is itself the information:
it says how much of the night is already being carried. A switch decides which the headline
states; both are always in the table.
Version: 0.18.0
Date: 2026-08-27
Fixes:
- The steam view priced the whole load for the whole night instead of the gap that is actually
left open. On a save drawing 710 MW with a 660 MW plant already running, that is 14,910 fuel
packets against 2,100 - the figure was arithmetically right and answering the wrong question.
It now uses the same deficit the accumulator view does: what the sun and whatever already
burns leave uncovered, integrated across the night. Generators are sized on the worst
instant, fuel on the energy actually delivered, which are different numbers.
- Coal and wood were missing from the fuel list. The row was cut off at eight entries sorted by
fuel value, so every exotic fuel in a mod set came first and the two most likely to be in use
fell off the end. The list runs weakest to strongest now and wraps instead of truncating.
- The switch for whether generation running through the night counts was inside the accumulator
view, though it applies to all three. It sits with the load switch now.
Features:
- The steam view says what is already standing, like the other two: "N more generators and M
more boilers than the X and Y already here". Generators and boilers are not electric
consumers, so they never appear in the consumer statistics and are counted by type.
- An "energy for the night" row, so the fuel figure can be checked against the gap it came from.
Version: 0.17.1
Date: 2026-08-27
Fixes:
- Opening the window crashed: "attempt to call global 'add_picker'". The helper that draws the
icon rows was removed together with the block of code around it, and its five calls stayed
behind. A call to a local that is not there compiles to a read of a global, which is nil.
Testing:
- The suite now lists every global each script reads and checks the list against what Factorio
and Lua actually provide. The compiler already knows - every global read is a GETTABUP on
_ENV - so this catches the whole class: a helper deleted while its calls remain, a local
function called before it is defined, and a plain typo in a name. Nothing else here could
see any of them: the module-surface test only checks what a module returns, and the GUI is
never executed by the unit suite.
Verified against the real bug both ways round - deleting the helper, and defining it below
its callers.
Version: 0.17.0
Date: 2026-08-27
Fixes:
- Generation that runs through the night was ignored, which was the second half of the
accumulator error. Steam, nuclear and anything burning does not stop at dusk, so a bank only
has to carry what is left after it: a 690 MW draw against a 660 MW steam plant needs a bank
for the 30 MW gap, not for 690. That is the difference between about 2,500 accumulators and
58,000. It is measured across the same night as the load, with solar and accumulators
excluded, and can be switched off for planning to shut the burners down.
- The solar view used whatever panel was strongest and never named it, so a modded tier could
not be priced at all. The panel is pickable now, like the boiler, generator and fuel.
Changes:
- The three supply views are real tabs, not buttons - same mistake as the outer pair, and
only the selected one is built.
- Every choice moved out of the sidebar and next to the number it changes, which left nothing
on the left to reserve room for, so the power planner has no sidebar and its tables get the
whole window.
- Tables and their cells now carry a minimum width, not only a maximum. A maximum is just a
wrapping rule: a table hands a column the width its content asks for, a label asks for
nothing, and the explanatory column stayed narrow however wide the window was. That is why
it kept wrapping every few words.
- The pane says plainly that standing counts are surface-wide while the measured load and
generation are for one electric network. An outpost on its own poles is in the first figure
and not the second.
Version: 0.16.0
Date: 2026-08-27
Fixes:
- The load figure was wrong, which made every accumulator answer wrong. "Everything at once"
is an upper bound no factory reaches - a save whose real draw is around 113 MW came out at
roughly 21 GW, and the pane asked for 350,000 accumulators where 1,900 nearly carried the
night. The night integral itself was right; it was being fed a load two orders of magnitude
too big.
- Beacons were counted twice. Measured by difference in a running game: four beacons added
24000 J/tick to the network, 6000 each, while the prototype reports usage 8000 AND drain
8000. A beacon never idles, so the engine reports its whole draw as both, and adding them
doubled every beacon in the plan. The drain is now added only where it is smaller than the
usage, which is what an idle figure means.
- The steam plant it proposed could not have been built: a heat exchanger was offered as the
strongest boiler, priced in fusion cells, and paired with a steam turbine. A heat exchanger
has no burner at all - it is fed by a reactor - so only item-burning boilers are offered now,
and generators are filtered and ordered by the steam their boiler actually makes. A generator
rated hotter than its boiler is still selectable, with a note saying it will not reach the
output shown.
Features:
- The load is now measured over the last completed night, and that is the default. The night is
located from the surface's own clock - dusk to dawn, one full cycle back - and read from the
ten-minute statistics, the one precision level long enough to cover a night at all. The
median rather than the mean: an accumulator charging at dusk and discharging after puts a
spike at each end that a mean follows and a median ignores. Accumulators are excluded from
the figure entirely.
- Four load sources, switchable in the results pane rather than the sidebar, because which one
is in force is part of reading the answer: last night's median, last night's peak, the last
minute, and the upper bound. Asking for one the statistics cannot supply says so instead of
quietly answering a different question.
- Sub-views inside the results pane: Accumulators, Solar and Steam. Solar sizes a panel field
from the area under the sun curve. Steam sizes generators and boilers and prices the night in
fuel, with the boiler, generator and fuel all pickable - fuels read from the item table by
fuel value, filtered to what the chosen boiler burns.
- A "actually used" row: last night's median against the upper bound, which is the ratio that
explains why the two figures differ so much.
Testing:
- The GUI wiring test caught three pickers whose element names were built from a variable
inside a helper, which the scanner cannot see - the same shape that once left seven handlers
dead. The prefixes are literals again.
- Locale keys built by concatenation only get a prefix check, so the load-source rows are
spelled out as literals and each is checked individually.
Version: 0.15.0
Date: 2026-08-27
Changes:
- The two planners are real tabs now, in a tabbed pane, instead of two plain buttons that read
as actions rather than as two views of one window. Only the selected tab's content is built:
a tabbed pane holds every tab's content at once, and building both would run a full surface
survey on every open just to fill a page nobody is looking at.
- The window takes the height it has. The vertical margin was 120 units and the chrome
allowance 190, which together threw away a strip of every screen the tables could have used.
Now 60 and 150, so the scroll area grows tile for tile with the monitor.
- Content is no longer squeezed into the middle of a wide window. Every results table stretches
to the pane instead of sitting at its content width, and the widths at which text wraps are
derived from the pane rather than from fixed subtractions - a note used to wrap every few
words with hundreds of unused units to either side, and the "where it comes from" column was
capped at whatever was left after guessing the other two columns.
Testing:
- The metrics test now asserts that wrapping widths track the pane, that the explanatory column
gets what the table leaves, that a taller screen deepens the scroll area tile for tile, and
that the scroll area always fits inside the frame - two scrollbars fighting each other traps
the content between them. Verified by breaking each of them deliberately.
- The GUI wiring test understands a handler that identifies its element through suffix_of
rather than through a click, which is what a tabbed pane's own event handler has to do.
Verified by unhooking it.
Version: 0.14.0
Date: 2026-08-27
Features:
- A second planner in the same window, switched by a Factory / Power tab at the top: size an
accumulator bank against the night on the surface you are standing on. The electric network
window tells you what you are producing and consuming; it never tells you how much storage
the night actually costs, which is the number this works out.
- The load it buffers against is every consumer on the surface running flat out, not the
average. After a stall the whole factory restarts at once, and a bank sized on the median
browns out exactly then. The measured figure from the network statistics is offered as the
other option. Accumulators are excluded from both: charging current is not demand, and
counting it would size the bank against its own refill.
- Two limits are checked, not one. A bank has to hold the night's energy AND deliver the load
at the darkest moment - an accumulator storing 5 MJ but pushing only 300 kW cannot carry a
3 MW factory however many joules it has banked. The table says which of the two decides.
- It also checks whether the day pays the night back. If the panels cannot refill the bank
before dusk it drains a little further every night whatever its size, so that case is
reported with the shortfall per day and roughly how many more panels would close it.
- A reserve percentage is applied to the exact figure before rounding up.
- Every step is shown with where its number came from, so the answer can be checked rather
than believed.
Measured in a running game, not assumed:
- The solar curve is exactly linear, not the quadratic fade it looks like: full sun to `dusk`,
linear to zero at `evening`, dark until `morning`, linear back to full at `dawn`. Sampled
through the network statistics across a whole day, since a solar panel is not a generator
and energy_generated_last_tick raises "Entity is not generator" on one.
- Output scales exactly with the surface's solar_power_multiplier (2.5x measured as 2.5x),
which is what makes this work on a planet with a dimmer sun.
- An accumulator is found by its energy source reporting usage_priority "managed-accumulator"
and a panel by "solar", so no mod is named anywhere and a modded accumulator is picked up on
its own.
- The vanilla accumulator reports 5 MJ with a 300 kW limit each way, per entity and per
prototype.
Performance:
- The survey counts by prototype name instead of walking the surface. find_entities_filtered
with no area materialises every entity on the map, which on a large multi-surface save is a
multi-second freeze every time the tab is drawn. The prototypes worth counting come from the
network statistics, which already name everything that has drawn power.
Version: 0.13.0
Date: 2026-08-27
Features:
- A stated reach is now written onto the arm instead of only shaping the layout. Setting
"tiles reach" to 2 laid the far belt out correctly and then built an inserter that still
reached one tile, so the block looked right and did not run. Inserters whose prototype sets
allow_custom_vectors get an explicit pickup and drop vector in the blueprint, aimed at the
lane they are meant to serve.
- Which inserters can be aimed is read from the prototype flag, not from a list of mod names,
so any mod that reworks inserters is picked up without the planner knowing it exists. The
sidebar says which case you are in: the reach is written, the arm is fixed and the reach is
a planning figure only, or N of M inserters here could be aimed if you picked one.
- Fixed: an inserter chosen by hand was described only if it also passed the research filter,
so a chosen arm could end up with no known reach at all.
Measured in a running game, not assumed:
- LuaEntityPrototype.allow_custom_vectors decides whether the vectors can be moved at all.
- A blueprint carries pickup_position and drop_position relative to the inserter. It reads
back as an array; both {x=,y=} and {x,y} are accepted on write; a vector equal to the
prototype default is not stored.
- The vectors reach the entity built from the blueprint: of 112 arms in a generated advanced
circuit blueprint, 55 were built reaching past one tile and 57 reaching one, which is the
split the layout intends.
- An inserter without the flag discards a vector written for it without raising, so nothing
breaks for a player with no inserter mod - and no vector is written for one.
- The engine puts no ceiling on how far a custom vector may point: 2, 4, 6 and 9 tiles were
all accepted verbatim.
Testing:
- The self-test mod now ships a probe inserter prototype with allow_custom_vectors, because no
base game inserter sets it and there was otherwise nothing to measure the question against.
- build_blueprint needs build_mode = forced; in normal mode one unbuildable tile makes the
whole call place nothing, which reads exactly like an empty blueprint.
- The mock gained an aimable inserter, without which the suite could not see a written vector.
Version: 0.12.0
Date: 2026-08-27
Features:
- A stage whose product several stages consume now says so, with the split. Copper cable goes
straight into an advanced circuit and into the electronic circuits that advanced circuits
also need, so the cable stage is placed behind both - and a single "25.5/s" hid the fact
that half of it is a direct ingredient of the thing being built. The row now carries a
"shared by 2" marker whose tooltip breaks the rate down per consuming stage and names the
tier each one sits in.
- "Supply this stage" no longer switches off a product that something nearer the requested
item consumes directly. Supplying the tier that holds copper cable used to remove the cable
the final stage needs, turning an ingredient of the target into a hand delivery. Such items
are kept in production and listed next to the button as "N kept", with a tooltip saying why;
the per-row button still supplies them individually if that is really what you want.
- A greyed row switched off by a category now names the category, so "supplied" reads
"supplied (smelting off)". A stage you turned off yourself and one a category switch took
out from under you looked identical.
Testing:
- Every locale key the scripts ask for is now checked against both strings.cfg files, and the
two files are checked against each other. Keys built by concatenation are matched as a
prefix. Verified by deliberately renaming a key. A missing key is not fatal, which is why
nothing caught it before: it just shows "Unknown key" in the finished window.
- The headless run measures the shared-ingredient case against the real base game: copper
cable comes out 12.732/s into advanced circuits and 12.732/s into electronic circuits, the
split adds back up to what is produced, and the tier button holds it back.
Version: 0.11.0
Date: 2026-08-27
Features:
- Inserters taking from a shared belt are now filtered to their own material. Two unfiltered
inserters over a mixed belt both grab whichever material passes first; once the machine is
full of it they are left holding it with nowhere to put it, and the second material never
arrives. That deadlock looks like a working block. In 2.0 every inserter can be filtered,
plain ones included, so the filter is set from the belt grouping the planner already knows.
Inserters on a single-material belt are left unfiltered.
Testing:
- The mock inserters now declare filter_count, without which the suite could not see filters at
all - and did not notice a crash in the code that sets them.
Version: 0.10.1
Date: 2026-08-27
Bugfixes:
- Fixed a block laying out one input belt for three ingredients. How many belts a recipe needs
is decided by the recipe, not by how far an inserter reaches: a belt has two lanes, so two
materials fill it and a third always needs a belt of its own. The belt count was being capped
to the available reach, which produced one belt with three inserters over it and no way in
for the third material. The belts are now always laid out as the recipe requires.
- No inserter is placed for a belt no available arm can reach. The belt is still there, so it
is obvious where the missing arm goes, and the note says what reach is needed and points at
the setting that states it.
Version: 0.10.0
Date: 2026-08-27
Bugfixes:
- Fixed a stage being split into one line per machine. Splitting into more lines cannot reduce
how many distinct materials a line needs, so a recipe with three ingredients always wants two
belt lanes however slowly it runs. Looping until that was satisfied never converged, and ran
until there was a line per machine. Splitting now only answers an over-capacity belt; a lane
demand no inserter can reach is reported instead.
- Supplied stages stay in their own tier table, greyed out, instead of moving to a section of
their own. They read the same as before they were switched off - same place, same columns -
just grey, with the machine count they would have needed in brackets.
- Layout notes are reported once per stage rather than once per line. A stage split into ten
lines used to say the same thing ten times.
Features:
- Row length is now decided by belt capacity rather than a fixed 24 machines. One saturated
belt of the binding ingredient feeds a line, and those machines are the line; an ingredient
that only half fills its belt does not shorten it.
- The inserter to build with can be chosen, and its reach stated. Reach is usually a prototype
fact but not always: mods exist that let an ordinary inserter be adjusted to reach two or
three tiles, and no prototype field reports that, so it has to be sayable.
- Layout notes are orange rather than red - they describe the blueprint, not a broken plan -
and use the full width of the results pane.
Version: 0.9.0
Date: 2026-08-27
Features:
- A stage that needs more input than one line can be fed is now split into parallel lines
instead of being reported as a problem. A machine front fits only so many inserters, so only
so many belts can be tapped, and each belt carries one material or two halves; past that
ceiling the answer is fewer machines per line and more lines beside each other. The line
count is derived from the actual per-ingredient rates, not from a fixed row length.
- Each line is a separate block in the blueprint, labelled "recipe xN (2/3)" so it is clear
which line of how many it is. Lines of the same stage stack in the same tier column.
- Machines are spread evenly across the lines, remainder first, rather than leaving a stub.
Notes:
- The over-capacity warnings added in 0.8.0 now almost never fire, because the situation they
described is solved rather than reported. They remain for what splitting cannot fix, such as
a single ingredient that needs more belts than any number of lines can help with.
Version: 0.8.0
Date: 2026-08-27
Bugfixes:
- Fixed input inserters that could not reach their belt. With three or more item ingredients the
block gets two belt lanes, but every inserter stood in one row and only reached the nearer
one, so the far belt was a tile out of reach and just sat there full. Each inserter is now
given an arm long enough for the lane it serves, read from inserter_pickup_position, so a
modded long inserter works without being named. If nothing reaches far enough, the lanes are
capped and the shortfall is reported rather than silently built.
- Pipes and constant combinators are picked through a sorted, placeability-aware lookup instead
of whatever `pairs` yielded first, which in a large mod set could land on a hidden internal
prototype that then never appears in the blueprint.
Features:
- Ingredients are assigned to belts by throughput rather than paired two at a time. A belt has
two lanes and never carries more than two materials, but sharing halves the rate each gets,
so anything needing more than half a belt now gets a belt to itself. The heaviest sits
nearest the machine, where a plain inserter reaches it.
- A line that cannot carry what the stage needs is reported: an ingredient wanting more than one
full belt, or more input lanes than the available inserters can reach.
- "Supply this stage" on every pre-stage heading marks that whole tier as coming from
elsewhere, instead of clicking each row.
- Supplied stages leave a greyed shadow row showing the recipe, the machine, and how many
machines it would have taken at the rate the plan consumes. Not built, not in the blueprint,
but still visible.
- The blueprint label carries the mod version, so a screenshot says which build produced it.
Version: 0.7.1
Date: 2026-08-27
Bugfixes:
- Fixed seven dead GUI handlers. The mark-as-supplied button, the machine picker, the module
slots, the recipe dropdown and both FNEI buttons had stopped responding at the rename: the
element names were rewritten, but the handlers spelled their prefix as a Lua pattern with
escaped dashes ("factory%-planner%-mark%-raw%-"), which does not contain the string being
replaced, so they went on listening for the old prefix. Element names and handlers are now
both built from one prefix constant and matched by plain string, not by pattern.
- Fixed inserter facing. An inserter's direction names the side it takes FROM, not the side it
gives to - measured against a running game, an inserter facing north reports pickup (0,-1)
and drop (0,+1.2). Every inserter was facing south and therefore reaching backwards, taking
from the tile below instead of the belt or machine above.
Testing:
- A wiring test asserts every element the window builds has a handler and every handler answers
to an element that exists, and that no handler dispatches on a name pattern at all. Verified
by breaking a handler on purpose: it names the orphan.
- The headless self-test now runs the real blueprint path end to end against a real blueprint
stack and checks what actually lands in it - confirming the line-label combinators survive
with their signals, and that no inserter faces the wrong way.
Version: 0.7.0
Date: 2026-08-27
Info:
- Renamed to "xRed's Factory Planner"; internal name xreds-factory-planner. An internal mod
name cannot contain an apostrophe or spaces, so the folder and zip use the slug while the
title carries the full name.
Features:
- Production tiers. Tier 0 is what makes the requested item, tier 1 makes what tier 0 consumes,
and so on outward. Each tier gets its own table, so the window reads final stage first, then
each pre-stage. A stage feeding several tiers is placed outside all of them.
- The blueprint is laid out by tier, deepest on the left and the requested product on the
right, so material runs the same direction the belts do. Joining a pre-stage to what it
supplies is now a couple of belts rather than a rebuild, and the columns are spaced to leave
room for them.
- Production steps can be excluded by recipe category. Switching off "smelting" drops the
furnace stages and plates and bricks arrive as inputs instead - for when a separate smelter
block already makes them. Only categories the current plan uses are offered.
- "Mark as supplied" is clearer about what it does: the item becomes an input because you make
it elsewhere, which is the copper-plates-by-train case.
Version: 0.6.0
Date: 2026-08-27
Info:
- Renamed. The internal name is now "xreds-factory-planner" and the title is
"X-Red Clues Factory Planner", because "Factory Planner" is already taken on the mod portal
by Therenas' mod (internal name "factoryplanner"). Every prototype, setting, locale
namespace, GUI element and the remote interface were renamed with it, so nothing can collide
with another mod. The mod folder and zip must now be named xreds-factory-planner.
Spaces are not allowed in an internal mod name, so the archive uses dashes; the title shown
in game and on the portal is the full name.
Features:
- Each belt and pipe line in a blueprint now starts with a constant combinator holding the
signals for whatever belongs on that line. It does nothing mechanically - it is a label, so
whoever places the blueprint can see which item each belt wants while everything is still a
ghost.
- Power poles in blueprints are now optional and off by default, since a block is usually
dropped onto an existing grid. A checkbox in the planning options turns them on.
Version: 0.5.0
Date: 2026-08-27
Bugfixes:
- The solver no longer trades away the requested output to reduce by-product surplus. Least
squares treated "too little of what you asked for" and "too much of a by-product" as equally
bad and split the difference, so a request for one advanced circuit per second came back
producing less than one. An item that comes out over-supplied is now treated as surplus
rather than a failed constraint: its equation is dropped and the system re-solved, leaving
only the requested output as a hard figure. Far fewer recipes end up pinned, and the target
is met exactly.
- Fixed the crash on creating a blueprint. LuaItemStack.blueprint_icons was renamed to
preview_icons in 2.0; the old name does not exist and writing to it raises.
- Fixed solver.production_solve being unreachable: it had been nested inside another function
by a bad edit, so it only existed once that function had run.
Features:
- "Create blueprint" now produces a single blueprint with the stages stacked one below another,
instead of a book with one blueprint per stage. Each block is generated at the origin, so
stacking them required offsetting each one - which is why it was a book to begin with.
- The stage table's machine count column is labelled "Machine count", and the recipe picker
column has a heading.
Testing:
- The item stack mock now rejects any key the 2.0 LuaItemStack does not define, and validates
blueprint icon indices. It refuses the pre-2.0 icon property the way the game does.
- Blueprint tests assert the stacked blocks do not overlap and that every machine of every
stage ends up in the blueprint.
Version: 0.4.0
Date: 2026-08-27
Bugfixes:
- Fixed the crash on "Create blueprint book". LuaItemStack.label is a plain string, not a
LocalisedString, and assigning a table to it raises. The label is now built as text with
rich text tags, which render in a label anyway.
- Fixed false "cannot be balanced" warnings on large rates. The residual threshold was
absolute while the residual scales with the requested rate, so a plan for 1000/s reported
an imbalance that a plan for 100/s did not - on identical, correct ratios. The system is
now solved exactly where possible, with regularisation only as a fallback for a singular
system, and the threshold is relative to each item's own throughput.
- Stages with a zero run rate are no longer listed. They contribute nothing and made
expanding a single raw input look like it had pulled in an unrelated factory.
- Fixed counts displaying as "-0".
Features:
- New dependency tree section: what the plan needs, indented by depth, with per-branch rates
that add up on their own. A shared intermediate is expanded once and marked on later
appearances rather than duplicating its subtree.
- Window width now derives from the player's resolution and interface scale: the results pane
fills the screen it has instead of staying narrow and scrolling. The recipe dropdown scales
with it, so long recipe names are readable.
- A single-block plan yields a plain blueprint instead of a book with one page in it.
Testing:
- Blueprint creation is now tested, against a stack mock that enforces the property types the
real LuaItemStack does. It rejects the LocalisedString label that shipped broken.
- Window sizing and the dependency tree are unit tested, including branch rates summing to
the total, shared intermediates expanding once, and cyclic plans terminating.
Version: 0.3.2
Date: 2026-08-27
Bugfixes:
- Fixed the crash on selecting a machine or recalculating: the style name "slot_button_pressed"
does not exist. The real name is "slot_sized_button_pressed", and Factorio treats an unknown
style as a hard error rather than falling back.
Testing:
- The suite now checks every GUI style, utility sprite and element type literal in the source
against the names the game actually defines, dumped from the running data stage. Both checks
were verified to fire on a deliberately broken name.
Version: 0.3.1
Date: 2026-08-27
Bugfixes:
- Fixed the crash on selecting anything in the window. `LuaRecipePrototype.categories` only
exists from 2.1; on 2.0 reading it raises rather than returning nil. Which field the running
game exposes is now probed once and remembered.
- Fixed non_negative_least_squares having been nested inside least_squares by a bad edit, so it
only existed once least_squares had been called. Every plan hit it as a nil value.
- Fixed reading `inserter_rotation_speed` and `extension_speed`, which do not exist as
attributes in 2.0 at all. This would have crashed blueprint creation.
- Removed the remaining pre-2.0 attribute fallbacks, which were unreachable and unsafe.
Features:
- Items mined from the ground are treated as raw inputs by default, read from every resource
entity's mineable products. Without this, Space Age's asteroid recipes meant a plan for five
circuits per second dug all the way into asteroid crushing and reported no raw inputs at all.
Each raw input has a button to expand it anyway.
- The requested item is never treated as raw, even when it is also mined somewhere - asking for
sulfuric acid means the recipe, not a Vulcanus geyser.
Testing:
- The unit mock now raises on any key the 2.0 API does not define, like a real LuaObject, and
the field set is generated from the official runtime-api.json.
- Added a headless integration test: tests/run-headless.sh downloads the free headless server
and runs the planner against a real game. Passes on 2.0.77 with Space Age, 659 recipes.
Version: 0.3.0
Date: 2026-08-27
Bugfixes:
- The window can no longer grow past the edge of the screen. It is capped to the player's own
display size, warnings are limited to four lines plus a counter, and Escape always closes it.
- Recipe categories are read from both `categories` (2.1) and `category` (2.0); the 2.1 shape
was previously not recognised at all.
- Barrelling and recycling recipes are excluded via `allow_decomposition`, the flag the engine
itself uses, instead of leaking into the recipe tree as producers of everything.
- The solver is now non-negative (active-set NNLS). A recipe can no longer be planned to run
backwards, which is what produced one warning per stage on large modded trees.
Features:
- Per-stage machine choice as icon buttons, listing exactly the machines that can run that
recipe. Tooltip shows crafting speed and module slots.
- Per-slot module choice: one picker per module slot the chosen machine actually has, filtered
to what the machine and the recipe both accept.
- Beacon prototype and beacon module are now chosen explicitly, with slot count, profile and
accepted module categories read from the chosen beacon.
- Belt and beacon pickers show icons instead of a text list.
- Effects honour `effect_receiver`: base effects, whether modules and beacons are heard at all,
the real per-effect limits, and the recipe's `maximum_productivity` ceiling.
- Module tier ordering uses the engine's `category` and `tier` instead of guessing from values.
- Researched beacon efficiency (`beacon_distribution_modifier`) is applied.
- Pipe throughput is now a stated, configurable assumption rather than a hardcoded constant.
- Reset button for all per-stage overrides.
Version: 0.2.0
Date: 2026-08-27
Features:
- FNEI integration (optional): every production stage, raw input and by-product gets a button
that opens FNEI on that recipe or item. Raw inputs open "what crafts this", by-products open
"what uses this". Buttons only appear when FNEI is installed.
- Public remote interface: other mods can open the planner on an item with
remote.call("xreds-factory-planner", "open_for_prototype", player_index, "item", "iron-plate", rate).
Version: 0.1.0
Date: 2026-08-27
Major Features:
- Analytic production solver: solve a full recipe tree (including cyclic recipes) for a target rate.
- Mod-agnostic: all data is read from the live prototype tables, so modded recipes work automatically.
- Belt saturation planning: request a rate, or request "N saturated belts of X" and let the planner
size the line so input belts are consumed exactly.
- Machine, module and beacon selection based on researched technologies.
- Blueprint book export of the planned blocks.