factoriax.engine.tables#
Derived JAX arrays and lookup tables for the Factoriax engine.
factoriax.engine.constants holds the pure-Python definitions. This module
turns them into the jnp arrays that engine code reads. It builds every
array once, at import.
The arrays have two shapes. A gather table covers the full range of the value
that indexes it: an ItemType, a Machine, a BlockType, or a
Direction. It holds a neutral value at every position that has no entry. A
traced index therefore never falls outside it, and a lookup needs no membership
test first. A membership set, such as MINEABLE_BLOCKS or
SOLID_BLOCKS, holds only the values that belong to the set. Code
compares a value against such an array and never indexes it.
Both shapes keep the lookups of traced code branchless under jax.jit.
Module Attributes
|
|
Output sides of a splitter, indexed by |
|
Output direction of each crossing axis, indexed by the packed |
|
Diagonal glyph for each crossing encoding, for the display only. |
|
Slot index of the vertical flow buffer in |
|
Slot index of the horizontal flow buffer, in the same two arrays as |
|
The block types that a player can mine, as a membership set and not as a table to index. |
|
The block types that hold no player and no machine, also a membership set. |
|
Item that each block gives to the player who mines it, indexed by |
|
Machine that an item becomes on the map, indexed by |
|
Item that a machine gives back after a pickup, indexed by |
|
The placeable item ids, as a membership set. |
|
Position of an item in |
|
Number of items of one type that a player can carry, indexed by |
|
Items that one buffer slot holds, indexed by |
|
Whether a machine takes deliveries into |
|
Hit points of a machine at the moment of placement. |
|
Hit points of a placed machine at the start, indexed by |
- factoriax.engine.tables.BLOCK_TO_ITEM_ARRAY = Array([0, 0, 0, 0, 2, 3, 1, 4, 5, 6], dtype=int32)#
Item that each block gives to the player who mines it, indexed by
BlockType. Shape(len(BlockType),), int32. A block that a player cannot mine holdsItemType.EMPTY. A mine action on it therefore gives item 0, and the caller needs no separate test.
- factoriax.engine.tables.CROSSING_AXIS_DIRS = Array([[0, 0], [4, 2], [4, 1], [3, 2], [3, 1]], dtype=int8)#
Output direction of each crossing axis, indexed by the packed
ent_direction. Roweis(vertical_output, horizontal_output)for encodingein 1..4. Shape(5, 2), dtype int8. A crossing runs a vertical flow and a horizontal flow at the same time, and packs both into the one direction byte. An axis takes items from the neighbour opposite to its output direction. Ifvertical_outputisDOWN, the vertical flow reads the tile above and writes the tile below. The two input sides therefore always meet at a corner. Row 0 is(0, 0). A crossing with no direction moves nothing on either axis.
- factoriax.engine.tables.CROSSING_DIAGONAL: tuple[str, ...] = ('', '\\', '/', '/', '\\')#
Diagonal glyph for each crossing encoding, for the display only. A backslash marks a NW-SE diagonal. A forward slash marks a NE-SW diagonal. The diagonal runs from the corner where the two input sides of the encoding meet, to the opposite corner. Index 0 is the empty string, because a crossing with no direction has no input sides. The simulation never reads this value. Only the crossing icon reads it.
- factoriax.engine.tables.CROSSING_HORIZ_SLOT: int = 1#
Slot index of the horizontal flow buffer, in the same two arrays as
CROSSING_VERT_SLOT.
- factoriax.engine.tables.CROSSING_VERT_SLOT: int = 0#
Slot index of the vertical flow buffer in
EnvState.ent_asm_in_typeandEnvState.ent_asm_in_count. A crossing uses the two assembler input slots as one buffer for each axis. Each axis reads and writes its own slot only, so the two flows cannot mix.
- factoriax.engine.tables.DIRECTIONS = Array([[ 0, 0], [-1, 0], [ 1, 0], [ 0, -1], [ 0, 1]], dtype=int32)#
(dx, dy)step perDirectionvalue. Shape(5, 2), int32. Row 0 is(0, 0)for the valueDirectionleaves unnumbered, so a machine with no direction moves nothing.dyincreases downward, which matches the row order ofEnvState.map.UPis therefore(0, -1).
- factoriax.engine.tables.ITEM_TO_MACHINE_ARRAY = Array([ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10], dtype=int32)#
Machine that an item becomes on the map, indexed by
ItemType. Shape(NUM_ITEM_TYPES,), int32. An item that no action can place holdsMachine.NONE, and that is how placement refuses it.
- factoriax.engine.tables.MACHINE_HAS_INPUT_SLOTS = Array([False, False, False, False, True, False, False, True, True, False, False], dtype=bool)#
Whether a machine takes deliveries into
ent_asm_in, indexed byMachinevalue. Shape(len(Machine),), bool. The value is True for assemblers, furnaces, and science labs, and that is the one property those three share. An assembler and a furnace consume the slots to run a recipe. A science lab consumes them directly, infactoriax.engine.step.run_labs().Every path into those slots reads this array, and no path lists the three machine kinds again. A new kind with input slots therefore needs one change only. This array does not tell a caller whether a machine runs recipes, because a science lab does not run recipes.
factoriax.engine.machinestestsASSEMBLERandFURNACEdirectly for that question.A
CROSSINGis False here, although it keeps its two axis buffers in the same two columns. Nothing delivers into a crossing by slot. The belt pass selects the axis from the direction of travel.
- factoriax.engine.tables.MACHINE_HEALTH: int = 256#
Hit points of a machine at the moment of placement. The value is the same for every machine kind.
MACHINE_MAX_HEALTHrepeats it over theMachinerange, so a caller can index it with a machine type and needs no special case.
- factoriax.engine.tables.MACHINE_MAX_HEALTH = Array([256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256], dtype=int16)#
Hit points of a placed machine at the start, indexed by
Machinevalue. Shape(len(Machine),), int16 to matchEnvState.ent_health. A repair clamps to this value, andfactoriax.engine.placementallows a pickup only at full health.
- factoriax.engine.tables.MACHINE_MAX_STACK = Array([ 0, 64, 256, 3, 1000, 1, 0, 1000, 1000, 2, 2], dtype=int16)#
Items that one buffer slot holds, indexed by
Machinevalue. Shape(len(Machine),), int16 to matchEnvState.ent_buf_count. The unit is items, not stacks. Zero means that the machine holds nothing.ARMholds one, because it carries a single item in transit. A transfer infactoriax.engine.machinescompares the count at the destination against this value. It refuses a move into a full buffer and leaves the source unchanged.
- factoriax.engine.tables.MACHINE_TO_ITEM_ARRAY = Array([ 0, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33], dtype=int32)#
Item that a machine gives back after a pickup, indexed by
Machinevalue. Shape(len(Machine),), int32. This is the inverse ofITEM_TO_MACHINE_ARRAY, withItemType.EMPTYatMachine.NONE.
- factoriax.engine.tables.MINEABLE_BLOCKS = Array([6, 4, 5, 7, 8, 9], dtype=int32)#
The block types that a player can mine, as a membership set and not as a table to index. These are the keys of
BLOCK_TO_ITEM. Any other block gives nothing.
- factoriax.engine.tables.PLACEABLE_ITEMS = Array([24, 25, 26, 27, 28, 29, 30, 31, 32, 33], dtype=int32)#
The placeable item ids, as a membership set. The contents and the order are the same as
PLACEABLE_ITEM_LIST. That order is the palette order of the play UI and carries no meaning here.
- factoriax.engine.tables.PLAYER_MAX_STACK = Array([ 0, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 128, 128, 128, 128, 1024, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128], dtype=int32)#
Number of items of one type that a player can carry, indexed by
ItemType. Shape(NUM_ITEM_TYPES,), int32. The unit is items, not stacks.EMPTYholds 0, so no count can build up in the slot that marks an empty slot. The engine refuses a craft or a pickup that passes this limit, and does not clip it. The inputs of the craft stay in the inventory, and the machine stays on the map.
- factoriax.engine.tables.SCIENCE_PACK_INDEX = Array([-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 0, 1, -1, -1, -1, -1, -1, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1], dtype=int8)#
Position of an item in
SCIENCE_PACK_TYPES, indexed byItemType. Shape(NUM_ITEM_TYPES,), int8. An item that is not a science pack holds-1. The step function uses the position as a segment id when it totalsEnvState.science_consumed_step.
- factoriax.engine.tables.SOLID_BLOCKS = Array([3, 1], dtype=int32)#
The block types that hold no player and no machine, also a membership set. Both
factoriax.engine.step.is_position_walkable()andfactoriax.engine.placement.is_valid_placement_tile()read it, so the two agree on what is solid.
- factoriax.engine.tables.SPLITTER_PERP_OUTPUTS = Array([[0, 0], [3, 4], [3, 4], [1, 2], [1, 2]], dtype=int8)#
Output sides of a splitter, indexed by
ent_direction. Rowdholds the twoDirectionvalues that are perpendicular to facingd, and a splitter with facingdpushes to those two sides. Shape(5, 2), dtype int8. A splitter stores the direction in which items move through it, the same convention that a conveyor belt uses. A splitter with facingdtherefore takes items from the neighbour on its-dside. The two entries are in ascendingDirectionorder, and that order carries no meaning, because a splitter treats both sides in the same way. Row 0 is(0, 0), a pair that matches no direction, so an entity with no direction never pushes to a real side.