factoriax.engine.achievements#

Achievement bits that record the progress of a player.

An achievement records that the world reached a given state during an episode. The environment state holds the bit after the unlock. A scenario can build a curriculum on those unlocks, as Craftax does. A scenario can also read them to follow progress towards a goal, whatever the reward function pays.

The environment state holds the achievement bits. Two scenarios can give the same bit to two different achievements, so this module declares no achievements of its own. It supplies the parts to declare a set:

The bit order is a wire format. A recorded trajectory holds achievements_unlocked as a plain bool vector, and the index is the only identifier that survives. factoriax.analysis therefore reads the bits by position. A new bit at the end of a set is safe.

CAUTION: Do not move a bit and do not remove one. Every rollout that was recorded before the change then reads as a different set of achievements, and nothing reports an error.

Module Attributes

Condition

Predicate for one bit.

class factoriax.engine.achievements.Achievement(id: str, condition: Callable[[EnvState], Array], name: str = '', hint: str = '', weight: float = 1.0)#

One latched bit, with its condition, its labels, and its reward.

The condition sits next to its labels, so a scenario declares one ordered tuple. The name of a bit therefore cannot separate from the condition that unlocks it, and its weight cannot land on the wrong index.

id#

Stable identifier, unique inside one set. index_of() reads it to find the index of a bit, for code that holds a name and not a position.

Type:

str

condition#

Predicate that the environment evaluates in every step. The environment does the latching, so this predicate answers “does the world satisfy the condition now”, and not “did it ever satisfy the condition”.

Type:

collections.abc.Callable[[factoriax.engine.state.EnvState], jax.jaxlib._jax.Array]

name#

Label for a human reader. The default is id. A scenario for an agent needs no other value, because the string is also the metric key in the training logs. Give a different value when a person reads the label.

Type:

str

hint#

Text for the interactive UI. The engine never reads it.

Type:

str

weight#

Reward for the step in which this bit first unlocks. achievement_weights() collects these values.

Type:

float

factoriax.engine.achievements.Condition#

Predicate for one bit. The function must be pure and jittable, and must return a scalar bool.

alias of Callable[[EnvState], Array]

factoriax.engine.achievements.achievement_fn(achievements: Sequence[Achievement]) → Callable[[EnvState], Array]#

Build the (EnvState) -> bool[MAX_ACHIEVEMENTS] evaluator.

The result fits the achievement_fn argument of FactoriaxEnv. The evaluator stacks the conditions at trace time and pads with zeros to the full number of slots. Every scenario therefore has the same state shape, whatever number of bits it declares.

Parameters:

achievements – The set, in bit order. The position in this sequence is the bit index.

Returns:

An evaluator that returns a (MAX_ACHIEVEMENTS,) bool array. Bit i says whether achievements[i] holds in the state that the caller passed. The slots at the end are always False. The environment folds the result into EnvState.achievements_unlocked with OR, so the latch happens there and not here.

Return type:

Callable[[EnvState], jax.Array]

Raises:

ValueError – If the set is empty, holds more than MAX_ACHIEVEMENTS entries, or repeats an id.

factoriax.engine.achievements.achievement_weights(achievements: Sequence[Achievement]) → Array#

Reward weight for each bit, with zeros to the length MAX_ACHIEVEMENTS.

The result feeds factoriax.engine.rewards.achievement_reward(). The padding slots hold zero, so they can never add reward.

Parameters:

achievements – The set, in the same bit order that achievement_fn() received.

Returns:

Shape (MAX_ACHIEVEMENTS,), float32. Entry i is achievements[i].weight.

Return type:

jax.Array

Raises:

ValueError – If the set is empty, holds more than MAX_ACHIEVEMENTS entries, or repeats an id.

factoriax.engine.achievements.any_assembler_has_output(state: EnvState) → Array#

Test whether a placed assembler holds items in its output slot.

factoriax.engine.achievements.any_buffer_nonempty(state: EnvState, machine: int) → Array#

Test whether a placed machine of type machine holds items in its buffer.

The function reads ent_buf, which the simulation fills. A player can also fill it. A DEPOSIT_ action writes ent_buf for every machine that is not a miner and has no input slots, so a player can load a pallet, a belt, a splitter, or a crossing by hand. A deposit never reaches the ent_buf of a miner, an assembler, a furnace, or a science lab. Only for those four kinds does a set buffer bit prove that the factory made the item.

factoriax.engine.achievements.count_machines(state: EnvState, machine_type: int) → Array#

Return the number of placed machines of machine_type on the map.

factoriax.engine.achievements.count_total_items(state: EnvState, item_type: int) → Array#

Total count of item_type in all player inventories.

factoriax.engine.achievements.has_machines(state: EnvState, machine: int, count: int = 1) → Array#

Test whether the map holds count machines of type machine or more.

factoriax.engine.achievements.holds_item(state: EnvState, item: int, count: int = 1) → Array#

Test whether the players together hold count of item or more.

factoriax.engine.achievements.index_of(achievements: Sequence[Achievement], achievement_id: str) → int#

Bit index of achievement_id.

The index is the position in achievements, and that position indexes EnvState.achievements_unlocked. Call this function instead of a number written into the code. A new bit at the end of the set then cannot move a lookup to the wrong index.

Raises:

KeyError – If no achievement in the set carries that id.

factoriax.engine.achievements.max_score(achievements: Sequence[Achievement]) → float#

Total reward for one unlock of every bit in the set.

This is the maximum achievement reward for one episode, because a bit latches and pays one time at most. This function runs no validation, unlike the other builders here. It therefore also totals a malformed set.

factoriax.engine.achievements.mined_at_least(state: EnvState, item: int, count: int) → Array#

Test whether the mining total for item is count or more.

The function reads the items_mined counter, which never decreases. The result therefore follows the total mined amount, and not the amount that a player holds now.

factoriax.engine.achievements.total_machines(state: EnvState) → Array#

Return the number of placed machines of all types.

factoriax.engine.achievements.total_ore_mined(state: EnvState) → Array#

Total count of coal, iron ore, and copper ore mined since the last reset.