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:
Achievement, which holds one bit.achievement_fn()andachievement_weights(), which turn an ordered tuple of achievements into what the environment and the reward function read.The shared condition helpers at the end of this module.
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
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:
- 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:
- weight#
Reward for the step in which this bit first unlocks.
achievement_weights()collects these values.- Type:
- factoriax.engine.achievements.Condition#
Predicate for one bit. The function must be pure and jittable, and must return a scalar bool.
- factoriax.engine.achievements.achievement_fn(achievements: Sequence[Achievement]) Callable[[EnvState], Array]#
Build the
(EnvState) -> bool[MAX_ACHIEVEMENTS]evaluator.The result fits the
achievement_fnargument ofFactoriaxEnv. 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. Bitisays whetherachievements[i]holds in the state that the caller passed. The slots at the end are always False. The environment folds the result intoEnvState.achievements_unlockedwith OR, so the latch happens there and not here.- Return type:
- Raises:
ValueError – If the set is empty, holds more than
MAX_ACHIEVEMENTSentries, 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. Entryiisachievements[i].weight.- Return type:
- Raises:
ValueError – If the set is empty, holds more than
MAX_ACHIEVEMENTSentries, 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
machineholds items in its buffer.The function reads
ent_buf, which the simulation fills. A player can also fill it. ADEPOSIT_action writesent_buffor 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 theent_bufof 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_typeon the map.
- factoriax.engine.achievements.count_total_items(state: EnvState, item_type: int) Array#
Total count of
item_typein all player inventories.
- factoriax.engine.achievements.has_machines(state: EnvState, machine: int, count: int = 1) Array#
Test whether the map holds
countmachines of typemachineor more.
- factoriax.engine.achievements.holds_item(state: EnvState, item: int, count: int = 1) Array#
Test whether the players together hold
countofitemor 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 indexesEnvState.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
itemiscountor more.The function reads the
items_minedcounter, which never decreases. The result therefore follows the total mined amount, and not the amount that a player holds now.