factoriax.engine.machines#
Move the factory forward by one step.
A factory is a set of machines on tiles that pass items to each other. This module performs those passes. Ore leaves the ground, items become other items, and both move between machines that do not touch. Every object on the map that acts without a player acts here.
Four passes do the work. update_all_machines() runs them in order, and a
step calls that function:
run_miners()Ore comes out of the ground and goes into the machine that the miner faces.
run_assemblers()Assemblers and furnaces turn input items into an output item, over time.
run_conveyor_belts()Belts, splitters, and crossings carry items across the map.
run_arms()Arms lift one item between two machines one tile apart.
A pass takes a state and returns a new state. A machine runs whether or not a player is near it. Another module handles the player actions.
Machine state lives in the ent_ arrays of
EnvState. An entity id indexes them, not a
tile. There are E slots, fixed when the level is built, and a machine holds
one slot for as long as it stands on the map. An entity finds its neighbours
through state.tile_entity. That array maps a tile back to the entity id on
it, or to -1 for an empty tile.
Every pass runs over all E slots at the same time, free slots included.
Under jit there is no per-entity branch to skip a slot, so a free slot
computes a neighbour, a transfer, and a receipt like every other slot. A mask
makes this safe, not a skip. Code keeps a result only where
active = state.ent_y >= 0.
CAUTION: Put the active mask on every line that keeps a gathered result. A
free slot holds ent_y < 0, and the position clip folds that onto tile
(0, 0). The slot then reads as a neighbour of the machine next to that corner.
One line without the mask raises nothing, and an item appears in a slot that
holds no machine.
Two results follow. First, the cost of a step follows E and the map shape,
and not the number of machines that a player built. An empty factory therefore
costs the same as a full one. Second, items move by scatter-gather. The sending
side computes what it pushes. The receiving side reaches back and computes the
same decision from its own position. Neither side writes into the slot of the
other.
Module Attributes
Ore that a miner mines into its own buffer. |
- factoriax.engine.machines.MINER_OUTPUT_CAP: int = 3#
Ore that a miner mines into its own buffer. With the default
miner_mining_ratea miner reaches this value in one step. It then stays idle until an arm, a belt, or a player empties the buffer. This value is not the capacity of the buffer.MACHINE_MAX_STACKgives a miner much more room, and a push from a belt or an arm fills the buffer past this value. A pallet is still the only large storage.
- factoriax.engine.machines.run_arms(state: EnvState, params: EnvParams) EnvState#
Move one item through every arm, from the tile behind to the one ahead.
Machines do not reach into each other. An arm connects two of them. It is also the only way to empty a machine that does not push on its own, such as an assembler or a pallet.
An arm joins the two tiles on each side of it. In each step it takes one item from the machine behind it, on the tile opposite to its facing, and gives that item to the machine in front. One arm moves one item in one step, and it needs both ends. With no source, or with no room in front, nothing moves.
The machine behind decides which slot the item comes from. A finished craft stays in
ent_asm_outand blocks the next craft, so an arm empties that slot first and keeps an assembler or a furnace at work. An arm empties theent_bufof a machine that only stores, such as a miner, a pallet, or a belt.The machine in front decides which slot the item lands in. An assembler, a furnace, or a science lab takes the delivery into
ent_asm_in. It uses slot 0 when that slot is empty or already holds the same item, and slot 1 in every other case. An input slot fills to theMACHINE_MAX_STACKof the machine, the same limit that the buffer route uses. An arm that feeds a machine which consumes nothing therefore stops, and the slot does not grow without bound. Every other machine receives intoent_buf, also up toMACHINE_MAX_STACK.An arm and a player deposit are the two ways to give a science lab a pack that it consumes.
factoriax.engine.step.run_labsreadsent_asm_inand nothing else, and both routes write there. A belt can point at a lab, and nothing refuses it. That belt delivers into theent_bufof the lab, where the items stay until an arm or a player takes them back out.An arm never takes from its own
ent_buf, but that buffer is not closed.MACHINE_MAX_STACKgives an arm room for one item, and a belt that faces an arm pushes into it. The next arm treats a loaded arm as an ordinary source and empties it like any other machine.- Parameters:
state – State to read.
params – The function does not read this argument. It is present so that every pass in this module takes the same pair, and
update_all_machines()can call them one after the other.
- Returns:
New state, with
ent_buf_type,ent_buf_count,ent_asm_in_type,ent_asm_in_count,ent_asm_out_type, andent_asm_out_countupdated.- Return type:
- factoriax.engine.machines.run_assemblers(state: EnvState, params: EnvParams) EnvState#
Advance every assembler and furnace one step: feed, count down, finish.
This function is where one item becomes another. A miner gives only what the ground already holds. A recipe here is the only way to get an item that no tile contains.
A machine holds two input slots, a countdown, and one output slot. It starts when it holds the inputs of a recipe, counts down while it works, and puts the result in its output slot. An assembler and a furnace run this same cycle. They differ only in the recipes that they accept, and
params.recipe_table.machine_typefixes that for each recipe.Two conditions keep the cycle in motion, and both belong to the caller and not to this function. The inputs must arrive, and the output must leave.
The inputs arrive by one of two routes: an arm that delivers into
ent_asm_in, or the pull at the start of this pass. The pull takes one item from each neighbouring conveyor belt that faces the machine. Only a belt feeds a machine this way. The function leaves a pallet, a miner, a splitter, or an arm next to the machine alone. Storage next to an assembler therefore stays full, and a belt that runs past the side is not a feed.The output never leaves on its own. Nothing in this pass empties
ent_asm_out, so a machine that finished a craft stays stopped until a player, an arm, or a belt takes the result away. A pallet is therefore the only way to buffer a chain.The rest is timing. The machine consumes the inputs on the step that starts a craft, not on the step that finishes it. The countdown is the
ticksvalue of the recipe, inent_power. The output appears on the step that begins withent_power == 1, which is the step after the step that lowered it to 1. A recipe ofTticks therefore takesT + 1calls, and that count includes the call that starts it. A recipe takes one or two inputs, and the matcher accepts the two slots in both orders. The slot that an arm or a belt filled therefore does not matter.- Parameters:
state – State to read.
params – Supplies
recipe_table. The recipe count sets the length of an unrolled match loop. A table of a different size therefore traces again underjit, and a table of the same size with new numbers does not.
- Returns:
New state, with
ent_power,ent_buf_type,ent_buf_count,ent_asm_in_type,ent_asm_in_count,ent_asm_out_type, andent_asm_out_countupdated.- Return type:
- factoriax.engine.machines.run_conveyor_belts(state: EnvState, params: EnvParams) EnvState#
Advance the belt network one step: belts, splitters, and crossings.
An arm moves an item one tile. A belt carries items across a map, and a chain of belts connects a mine on one side to an assembler on the other.
Every machine in the network moves items in the direction that it faces, one tile in each step, and each one holds only a few items. A belt line therefore behaves like a line. When the far end stops taking items, the tile behind it fills, then the tile behind that one, and the stall travels back to the source.
Three kinds share the network. They differ only in the shape of the junction that they make.
CONVEYOR_BELTThe plain link. It pushes its whole buffer into the tile that it faces, up to the room at the destination.
SPLITTERA fork. It outputs to the two tiles perpendicular to its facing, one item to each side. It fires only while it holds 2 items or more, so an even split stays even under a steady flow and favours neither side. The two sides commit on their own, so a splitter with one blocked side still feeds the other side.
CROSSINGAn overpass. Two streams pass through at right angles and never mix, because each axis has its own buffer and its own direction. The crossing refuses a push aimed at the output face of an axis. Each axis holds 2 items and moves 1 item in each step, so an axis that holds 1 item accepts and forwards in the same step. An axis that already holds 2 items forwards but refuses, because a sender tests the destination against the count at the start of the step, before the destination pushed.
One kind of neighbour refuses a delivery. A belt or a splitter does not push into an assembler or a furnace. Those two take items through their own pull or through an arm. A belt line that ends at one of them therefore holds its items until the machine takes them.
A science lab is not in that set, and this difference matters before you connect one. A belt pushes into the
ent_bufof a lab and the lab accepts, butfactoriax.engine.step.run_labsreadsent_asm_in. Nothing consumes what lands in the buffer.CAUTION: Feed a lab with an arm or with a player deposit. A belt into a lab fills it with packs that the lab never spends.
Internally, a crossing stores its two streams in the slot pair that an assembler uses for inputs:
ent_asm_in[:, CROSSING_VERT_SLOT]for the vertical stream andent_asm_in[:, CROSSING_HORIZ_SLOT]for the horizontal stream. Its two axis directions sit in the singleent_directionbyte, andCROSSING_AXIS_DIRSunpacks them.- Parameters:
state – State to read.
params – The function does not read this argument. It is present so that every pass in this module takes the same pair, and
update_all_machines()can call them one after the other.
- Returns:
New state, with
ent_buf_type,ent_buf_count,ent_asm_in_type, andent_asm_in_countupdated.- Return type:
- factoriax.engine.machines.run_miners(state: EnvState, params: EnvParams) EnvState#
Extract ore under every miner and push the result one tile forward.
A miner is where items enter the world. Every machine after a miner only moves or changes what the miners produce.
A miner works the tile under it, not a tile next to it. A player puts it on the ore patch, with its facing towards the machine that receives the output. In each step it takes
params.miner_mining_rateore and pushes its buffer into the machine in front of it. A miner therefore holds two decisions: its tile and its facing.Two limits apply to the amount mined: the ore left in the tile, and the room left under
MINER_OUTPUT_CAP. That limit is small, so a miner that nothing empties fills its buffer and then stays idle. The machine in front limits the push, and not the mining. A miner in front of a full pallet therefore still takes ore out of the ground, and then stops with the ore in its buffer.A tile with no ore left becomes
BlockType.DIRT, and that miner stops. Other tiles of the same patch can still hold ore. A whole patch therefore needs one miner for each tile.The push needs a real machine in front. That machine must be of a kind that holds items, and it must hold nothing or the same item. A miner in front of open ground, or in front of a machine with no room, keeps its ore and stops. A miner pushes in one direction only, the direction that it faces.
- Parameters:
state – State to read.
params – Supplies
miner_mining_rate.
- Returns:
New state, with
map,block_resources,ent_buf_type,ent_buf_count, anditems_minedupdated.items_minedcounts coal, iron ore, copper ore, tin ore, and silicon only. A miner still mines and buffers ore of any other item id, but the total ignores it.- Return type:
- factoriax.engine.machines.update_all_machines(state: EnvState, params: EnvParams) EnvState#
Run every machine on the map for one step.
A step calls this function. It runs the four passes in one fixed order: miners, then assemblers and furnaces, then the belt network, then arms.
The order sets the distance that an item can travel in one step. A miner pushes ore into a pallet, and an arm can lift that ore back out in the same step, because the arms run last. An arm drops an item onto a belt, and that item waits for the next step, because the belt pass already ran. A new order changes throughput, so the order is part of the contract and not an implementation detail.
This function does not run the science labs.
factoriax.engine.step.run_labsempties their input slots as a separate part of the full step.- Parameters:
state – State to advance.
params – Supplies
miner_mining_rateandrecipe_table.
- Returns:
New state with the entity arrays,
map,block_resources, anditems_minedadvanced by one step.- Return type: