factoriax.engine.observations#

Observation constructors for Factoriax.

Each function maps (state, params, player_idx) -> jax.Array. Each one is JAX-native and works under JIT. rgb is the exception. It returns a NumPy RGB image from the pixel renderer, and JIT cannot compile it.

Two profiles and two view extents give four top-level builders.

  • x_ray sees through every machine to its slot contents, and sees the ore left under each terrain tile. It has 10 spatial channels and 81 player scalars.

  • superficial sees only what is visible from outside. It has 3 spatial channels and 72 player scalars, with no view into a slot and no view of the tile in front.

Each profile pairs with a view extent. global_* flattens the whole map. local_* takes a (2r+1) x (2r+1) window with the selected player at the centre.

global_* keeps the map in absolute coordinates, and gives the position of the observing player as two scalars only. It fits a function approximator that uses the spatial channels, such as a CNN over the unflattened grid. The training code must add a player-position plane for that CNN. A flat MLP usually cannot find the player in this encoding. The local_* variants are egocentric and translation-invariant, and they fit an MLP better. With radius >= map size - 1 the window still covers the whole map, and the OUT_OF_BOUNDS padding marks the edges, so the absolute position stays readable. One measurement: on Mining-v1, PPO with a 64x64 MLP stops near 7/30 on superficial_global and reaches 30/30 on superficial_local.

The 10 x_ray spatial channels:

  • block_type, the terrain.

  • machine_type, the Machine on each tile, or NONE.

  • block_resources, the ore count under the tile.

  • slot{0,1,2}_type and slot{0,1,2}_count, a uniform 3-slot view of the contents of every machine. Slots 0 and 1 carry the two ent_asm_in columns, which covers the inputs of an assembler, the packs of a science lab, and the two axis buffers of a crossing. Slot 2 carries ent_asm_out where a recipe runs, and ent_buf for every other machine that holds items.

  • machine_direction, the ent_direction of each tile, 1 to 4 or 0.

The 3 superficial spatial channels: block_type, machine_type, and machine_direction.

The x_ray scalars are the pose, the affordability of each item, the player inventory, and the readouts of the machine in front: 81 floats. The superficial scalars are the pose, the affordability of each item, and the player inventory: 72 floats. The 9 facing readouts are absent.

Module Attributes

NUM_PLAYER_SCALARS

Number of scalars in each profile.

OBSERVATIONS

The observation variants.

factoriax.engine.observations.NUM_PLAYER_SCALARS: dict[str, int] = {'superficial': 72, 'x_ray': 81}#

Number of scalars in each profile. The number of recipes in the RecipeTable of a scenario does not change it, because an item type indexes the afford block, not a recipe id.

factoriax.engine.observations.OBSERVATIONS: dict[str, Array] = {'superficial_global': <function global_superficial>, 'superficial_local': <function local_superficial>, 'x_ray_global': <function global_x_ray>, 'x_ray_local': <function local_x_ray>}#

The observation variants. The environment takes the name, and a radius for a _local variant, and dispatches through this dict.

factoriax.engine.observations.global_superficial(state: EnvState, params: EnvParams, player_idx: int | Array) → Array#

Full-map superficial observation for one player.

The result holds three flat spatial channels, and then the superficial scalar vector. The channels are the block type, the machine type, and the machine direction.

Parameters:
  • state – State to read.

  • params – The function passes this to the scalar builders.

  • player_idx – Player that observes.

Returns:

1-D float32, of length observation_size(profile="superficial", view="global", ...). It holds three flat (H, W) channels, and then the superficial scalars.

Return type:

jax.Array

factoriax.engine.observations.global_x_ray(state: EnvState, params: EnvParams, player_idx: int | Array) → Array#

Full-map x_ray observation for one player.

The result holds ten flat spatial channels, and then the x_ray scalar vector. The channels are the block type, the machine type, the ore resources, the six slot channels, and the machine direction.

Parameters:
  • state – State to read.

  • params – The function passes this to the scalar builders.

  • player_idx – Player that observes. Only the scalar block depends on it. The spatial channels are the same for every player.

Returns:

1-D float32, of length observation_size(profile="x_ray", view="global", ...). It holds ten flat (H, W) channels in _X_RAY_SPATIAL_CHANNEL_NAMES order, and then the x_ray scalars. A reader that wants the grid back must reshape it.

Return type:

jax.Array

factoriax.engine.observations.local_superficial(state: EnvState, params: EnvParams, player_idx: int | Array, radius: int = 10) → Array#

Local superficial observation in a window around one player.

The function takes a (2*radius+1) x (2*radius+1) patch from the three superficial spatial channels, and adds the superficial scalar vector after it. That vector holds NUM_PLAYER_SCALARS["superficial"] floats.

Parameters:
  • state – State to read.

  • params – The function passes this to the scalar builders.

  • player_idx – Player that observes. This player is at the centre of the window.

  • radius – Half-width of the window in tiles. The value is static under jit.

Returns:

1-D float32, of length observation_size(profile="superficial", view="local", radius=radius). The padding is the same as in local_x_ray().

Return type:

jax.Array

factoriax.engine.observations.local_x_ray(state: EnvState, params: EnvParams, player_idx: int | Array, radius: int = 10) → Array#

Local x_ray observation in a window around one player.

The function takes a (2*radius+1) x (2*radius+1) patch from the ten x_ray spatial channels, and adds the x_ray scalar vector after it.

Parameters:
  • state – State to read.

  • params – The function passes this to the scalar builders.

  • player_idx – Player that observes. This player is at the centre of the window.

  • radius – Half-width of the window in tiles. The window is (2 * radius + 1) tiles square, so it always has a centre tile. The value is static under jit, and a new value traces again.

Returns:

1-D float32, of length observation_size(profile="x_ray", view="local", radius=radius). The padding for a tile past the map edge is BlockType.OUT_OF_BOUNDS on the terrain channel and zero on every other channel, so an agent can still find the edge.

Return type:

jax.Array

factoriax.engine.observations.observation_size(*, profile: str, view: str, radius: int = 7, map_height: int = 32, map_width: int = 32) → int#

Return the flat observation size for one profile, view, and map size.

factoriax.engine.observations.rgb(state: EnvState, block_pixel_size: int = 32) → ndarray#

Render the full map as an RGB image.

This is the one observation here that is not JAX-native. It returns a NumPy array, and JAX cannot trace it, so a caller must run it outside a jit boundary.

The image shows less than the symbolic profiles carry: the terrain, the machine kind and facing, and the player position and facing. There is no inventory, no craft affordability, and no machine contents.

CAUTION: A pixel agent cannot learn to craft from this observation alone. ISSUES.md records this.

Parameters:
  • state – State to render.

  • block_pixel_size – Tile side length in pixels. Each different value builds its own renderer and holds it in a cache, so the atlas build and the JIT compile happen one time for each size, and not one time for each call. Many different sizes therefore grow the cache without bound.

Returns:

Shape (H * block_pixel_size, W * block_pixel_size, 3), uint8 RGB.

Return type:

numpy.ndarray