diff --git a/LANGUAGE_SPEC.md b/LANGUAGE_SPEC.md
index 6c50733..c861e8a 100644
--- a/LANGUAGE_SPEC.md
+++ b/LANGUAGE_SPEC.md
@@ -384,6 +384,49 @@ Signal allBelow1000 = all(levels) < 1000; # True (all values match)
- `any()` compiles to a decider combinator outputting `signal-anything`
- `all()` compiles to a decider combinator outputting `signal-everything`
+#### Bundle Filtering
+
+Filter a bundle to include only signals that pass a comparison using the conditional output syntax:
+
+```facto
+Bundle requested_items = { ("signal-A", 10), ("signal-B", 5), ("signal-C", -3) };
+
+# Keep only signals > 0 (preserving their values)
+Bundle positive_items = (requested_items > 0) : requested_items;
+# Result: signal-A=10, signal-B=5 (signal-C=-3 is filtered out)
+
+# Filter with different comparison operators
+Bundle above_threshold = (requested_items >= 8) : requested_items; # Only signal-A=10
+Bundle non_zero = (requested_items != 0) : requested_items; # All signals (none are zero)
+```
+
+**Syntax:** `(bundle COMPARISON scalar) : output`
+
+The comparison evaluates each signal in the bundle against the scalar value. Only signals that pass the comparison are included in the output.
+
+**Output Modes:**
+
+1. **Preserve values** (output is a bundle): The original signal values are preserved
+ ```facto
+ Bundle filtered = (items > 0) : items; # copy_count_from_input = true
+ ```
+
+2. **Constant output** (output is an integer): Each matching signal outputs the constant value
+ ```facto
+ Bundle counts = (items > 0) : 1; # Each matching signal outputs 1
+ ```
+
+**Factorio Output:** Compiles to a single decider combinator:
+- Input signal: `signal-each` (evaluates each signal independently)
+- Comparison: The specified operator and scalar value
+- Output signal: `signal-each` (outputs matching signals)
+- Output mode: `copy_count_from_input` (true for bundle output, false for constant)
+
+**Common Use Cases:**
+- Filtering inventory requests to only positive quantities
+- Separating signals by range (e.g., high vs low values)
+- Counting signals that meet a condition
+
#### Bundle Use Cases
Bundles are ideal for:
diff --git a/README.md b/README.md
index 9f59fe7..2deb679 100644
--- a/README.md
+++ b/README.md
@@ -145,6 +145,7 @@ Bundle resources = { ("iron-plate", 100), ("copper-plate", 80), ("coal", 50) };
Bundle doubled = resources * 2; # Double all values
Signal iron = resources["iron-plate"]; # Access a single signal
Signal anyLow = any(resources) < 20; # True if any resource is below 20
+Bundle positive = (resources > 0) : resources; # Filter to only positive values
```
Bundles are powerful for building generic circuits like balanced train loaders that work regardless of what items you're loading.
diff --git a/doc/03_signals_and_types.md b/doc/03_signals_and_types.md
index f4592cb..c1b56c0 100644
--- a/doc/03_signals_and_types.md
+++ b/doc/03_signals_and_types.md
@@ -440,6 +440,26 @@ Signal allBelow1000 = all(levels) < 1000; # True
- `any()` compiles to `signal-anything`
- `all()` compiles to `signal-everything`
+### Bundle Filtering
+
+Filter a bundle to keep only signals that pass a comparison:
+
+```facto
+Bundle requests = { ("signal-A", 10), ("signal-B", 5), ("signal-C", -3) };
+
+# Keep only positive values (preserving their counts)
+Bundle pending = (requests > 0) : requests;
+# Result: signal-A=10, signal-B=5 (signal-C is filtered out)
+
+# Output constant 1 for each matching signal
+Bundle counts = (requests > 0) : 1;
+# Result: signal-A=1, signal-B=1
+```
+
+This compiles to a single decider combinator using `signal-each`. The syntax is `(bundle COMPARISON scalar) : output`, where output can be:
+- A bundle: preserves original signal values
+- An integer: outputs that constant for each matching signal
+
---
## For Loops
diff --git a/doc/05_entities.md b/doc/05_entities.md
index 7f3d307..a718c1c 100644
--- a/doc/05_entities.md
+++ b/doc/05_entities.md
@@ -133,6 +133,42 @@ The `.output` property returns a **Bundle** — all signals the entity outputs.
- Balancing item distribution
- Controlling production based on storage
+### Wiring Signals to Entities: `.input`
+
+To wire signals to an entity's circuit input, use `.input`:
+
+```facto
+Bundle resources = { ("iron-plate", 100), ("copper-plate", 80) };
+Entity selector = place("selector-combinator", 0, 0, {
+ operation: "count",
+ count_signal: "signal-C"
+});
+selector.input = resources; # Wire resources to selector's input
+```
+
+The `.input` property creates a wire connection from a signal source to the entity:
+
+- **For dual-connector entities** (arithmetic, decider, selector combinators): Wires connect to the **input side** of the combinator
+- **For single-connector entities** (lamps, chests, inserters): Wires connect to the entity's circuit connection
+
+**Common uses:**
+- Providing input signals to combinators
+- Connecting signal sources to entities for circuit control
+- Building circuits where explicit wiring is needed
+
+**Example with selector combinator:**
+```facto
+# Count unique signals
+Entity chest = place("steel-chest", 0, 0, {read_contents: 1});
+Bundle items = chest.output;
+Entity counter = place("selector-combinator", 1, 0, {
+ operation: "count",
+ count_signal: "signal-C"
+});
+counter.input = items; # Wire chest contents to selector
+Signal count = counter.output["signal-C"]; # Read the count
+```
+
### Balanced Loader Example
```facto
diff --git a/doc/07_advanced_concepts.md b/doc/07_advanced_concepts.md
index 7af879d..6cb7562 100644
--- a/doc/07_advanced_concepts.md
+++ b/doc/07_advanced_concepts.md
@@ -427,6 +427,28 @@ Signal c = data["signal-C"];
Signal weighted = (a * 3 + b * 2 + c * 1) / 6;
```
+### Bundle Filtering
+
+Filter bundles to process only signals meeting a condition:
+
+```facto
+Bundle inventory = {
+ ("iron-plate", 500),
+ ("copper-plate", 200),
+ ("coal", -50), # Negative = deficit
+ ("stone", 0)
+};
+
+# Get only items we have (positive counts)
+Bundle have = (inventory > 0) : inventory;
+
+# Get deficit signals as positive values for requesting
+Bundle deficits = (inventory < 0) : (inventory * -1);
+
+# Count how many item types meet a threshold
+Bundle above_threshold = (inventory > 100) : 1;
+```
+
---
## For Loop Patterns
diff --git a/doc/ENTITY_REFERENCE.md b/doc/ENTITY_REFERENCE.md
index ea025f9..a34038e 100644
--- a/doc/ENTITY_REFERENCE.md
+++ b/doc/ENTITY_REFERENCE.md
@@ -1,6 +1,6 @@
# Entity Reference for Facto
-**Generated:** 2025-12-21
+**Generated:** 2026-01-29
**Draftsman version:** 3.2.0
This is the **complete reference** for all entities available in the DSL.
@@ -9,6 +9,7 @@ Each entity lists its prototypes, circuit I/O capabilities, and all settable pro
## Table of Contents
- [Using Entities in the DSL](#using-entities-in-the-dsl)
+- [Reading Entity Outputs](#reading-entity-outputs)
- [Enum Reference](#enum-reference)
- [Combinators](#combinators)
- [Lamps & Displays](#lamps--displays)
@@ -23,10 +24,22 @@ Each entity lists its prototypes, circuit I/O capabilities, and all settable pro
- [Robots & Logistics](#robots--logistics)
- [Space](#space)
- [Misc](#misc)
-- [Uncategorized Entities](#uncategorized-entities)
+- [Other Entities](#other-entities)
## Using Entities in the DSL
+### How the Compiler Handles Entities
+
+When you use `place()` in Facto, the compiler creates a corresponding
+[Draftsman](https://github.com/redruin1/factorio-draftsman) entity object.
+Properties specified in the placement object (the `{...}` part) are passed directly
+to Draftsman as Python attributes during entity construction. The compiler validates
+that property names and types match what Draftsman expects for that entity class.
+
+Circuit-controlled properties (like `entity.enable = expression`) are handled differently:
+the compiler generates the necessary combinator logic and wire connections to implement
+the circuit behavior, then sets the appropriate control properties on the entity.
+
### Placement Syntax
```facto
@@ -35,20 +48,21 @@ Entity name = place("prototype-name", x, y, {prop1: value1, prop2: value2});
### Setting Properties
-**At placement time** (in the property dictionary):
+**At placement time:**
```facto
Entity lamp = place("small-lamp", 0, 0, {use_colors: 1, color_mode: 1});
```
-**After placement** (for circuit-controlled values):
+**After placement (circuit-controlled):**
```facto
-lamp.enable = signal > 0; # Control based on circuit signal
-lamp.r = red_value; # Dynamic RGB control
+lamp.enable = signal > 0;
+lamp.r = red_value;
```
-### Reading Entity Outputs
+## Reading Entity Outputs
+
+Most entities can output circuit signals. Access them using `.output`:
-**Access entity circuit outputs** using `entity.output`:
```facto
Entity combinator = place("arithmetic-combinator", 0, 0, {...});
Signal result = combinator.output; # Read the combinator's output signal
@@ -56,6 +70,29 @@ Signal result = combinator.output; # Read the combinator's output signal
This is particularly useful for reading values from combinators, sensors, and other entities that produce circuit signals.
+## Wiring Signals to Entity Inputs
+
+To wire signals to an entity's circuit input, use `.input`:
+
+```facto
+Bundle resources = { ("iron-plate", 100), ("copper-plate", 80) };
+Entity selector = place("selector-combinator", 0, 0, {
+ operation: "count",
+ count_signal: "signal-C"
+});
+selector.input = resources; # Wire resources to selector's input
+```
+
+The `.input` property creates a wire connection from a signal source to the entity:
+
+- **For dual-connector entities** (arithmetic, decider, selector combinators): Wires connect to the **input side** of the combinator
+- **For single-connector entities** (lamps, chests, inserters): Wires connect to the entity's circuit connection
+
+This is useful for:
+- Providing input signals to combinators
+- Connecting signal sources to entities for circuit control
+- Building circuits where explicit wiring is needed
+
### Enum Properties
Enum properties accept **integer values**. See the [Enum Reference](#enum-reference) for all values.
@@ -73,10 +110,12 @@ Entity lamp = place("small-lamp", 0, 0, {use_colors: 1, always_on: 1});
## Enum Reference
-When setting enum properties in the DSL, use the **integer value**.
-This section lists all enums used by entity properties.
+When setting enum properties in the DSL, use the **integer value** for IntEnums,
+or the **string value** for Literal types.
+
+### Integer Enums
-### LampColorMode
+#### LampColorMode
| DSL Value | Enum Name |
|-----------|-----------|
@@ -84,7 +123,7 @@ This section lists all enums used by entity properties.
| `1` | COMPONENTS |
| `2` | PACKED_RGB |
-### Direction
+#### Direction
| DSL Value | Enum Name |
|-----------|-----------|
@@ -105,7 +144,7 @@ This section lists all enums used by entity properties.
| `14` | NORTHWEST |
| `15` | NORTHNORTHWEST |
-### InserterModeOfOperation
+#### InserterModeOfOperation
| DSL Value | Enum Name |
|-----------|-----------|
@@ -115,14 +154,14 @@ This section lists all enums used by entity properties.
| `3` | NONE |
| `4` | SET_STACK_SIZE |
-### InserterReadMode
+#### InserterReadMode
| DSL Value | Enum Name |
|-----------|-----------|
| `0` | PULSE |
| `1` | HOLD |
-### BeltReadMode
+#### BeltReadMode
| DSL Value | Enum Name |
|-----------|-----------|
@@ -130,14 +169,14 @@ This section lists all enums used by entity properties.
| `1` | HOLD |
| `2` | HOLD_ALL_BELTS |
-### FilterMode
+#### FilterMode
| DSL Value | Enum Name |
|-----------|-----------|
| `0` | WHITELIST |
| `1` | BLACKLIST |
-### LogisticModeOfOperation
+#### LogisticModeOfOperation
| DSL Value | Enum Name |
|-----------|-----------|
@@ -145,14 +184,14 @@ This section lists all enums used by entity properties.
| `1` | SET_REQUESTS |
| `2` | NONE |
-### MiningDrillReadMode
+#### MiningDrillReadMode
| DSL Value | Enum Name |
|-----------|-----------|
| `0` | UNDER_DRILL |
| `1` | TOTAL_PATCH |
-### SiloReadMode
+#### SiloReadMode
| DSL Value | Enum Name |
|-----------|-----------|
@@ -168,34 +207,132 @@ This section lists all enums used by entity properties.
| `1` | LOGISTICS |
| `2` | MISSING_REQUESTS |
+### String Enums (Literal Types)
+
+These properties accept string values. Use the exact string shown.
+
+#### ArithmeticOperation
+
+| Valid Values |
+|-------------|
+| `"*"` |
+| `"/"` |
+| `"+"` |
+| `"-"` |
+| `"%"` |
+| `"^"` |
+| `"<<"` |
+| `">>"` |
+| `"AND"` |
+| `"OR"` |
+| `"XOR"` |
+
+#### FilterMode
+
+| Valid Values |
+|-------------|
+| `"whitelist"` |
+| `"blacklist"` |
+
+#### IOType
+
+| Valid Values |
+|-------------|
+| `"input"` |
+| `"output"` |
+
+#### InfinityMode
+
+| Valid Values |
+|-------------|
+| `"at-least"` |
+| `"at-most"` |
+| `"exactly"` |
+| `"add"` |
+| `"remove"` |
+
+#### PlaybackMode
+
+| Valid Values |
+|-------------|
+| `"local"` |
+| `"surface"` |
+| `"global"` |
+
+#### QualityID
+
+| Valid Values |
+|-------------|
+| `"normal"` |
+| `"uncommon"` |
+| `"rare"` |
+| `"epic"` |
+| `"legendary"` |
+| `"quality-unknown"` |
+
+#### SelectorOperation
+
+| Valid Values |
+|-------------|
+| `"select"` |
+| `"count"` |
+| `"random"` |
+| `"stack-size"` |
+| `"rocket-capacity"` |
+| `"quality-filter"` |
+| `"quality-transfer"` |
+
+#### SplitterPriority
+
+| Valid Values |
+|-------------|
+| `"left"` |
+| `"none"` |
+| `"right"` |
+
+#### SpoilPriority
+
+| Valid Values |
+|-------------|
+| `"spoiled-first"` |
+| `"fresh-first"` |
+
## Combinators
### ArithmeticCombinator
**Description:** An arithmetic combinator. Peforms a mathematical or bitwise operation on circuit signals.
+**Draftsman Source:** [ArithmeticCombinator class](https://github.com/redruin1/factorio-draftsman/blob/main/draftsman/prototypes/arithmetic_combinator.py)
+
**Prototypes:** `"arithmetic-combinator"`
-**Connection Type:** Dual circuit connection (separate input and output sides)
+**Connection Type:** Dual circuit (separate input/output sides)
-#### Circuit Signal I/O
+#### Reading Entity Output
-**Signal Outputs:**
+Use `entity.output` to read: **Computed arithmetic result**
-| Signal Property | Signal Type | Description | Enable With |
-|-----------------|-------------|-------------|-------------|
-| `output_signal` | Result value | Signal to output the combinator result on | Always active |
+```facto
+Entity e = place("arithmetic-combinator", 0, 0);
+Bundle signals = e.output; # Returns all output signals
+```
#### DSL Examples
```facto
-# Note: Combinators are typically generated by the compiler
-Signal result = input * 2 + offset; # Creates ArithmeticCombinator(s)
+# Note: Arithmetic combinators are usually auto-generated
+Signal result = input * 2 + offset; # Creates combinator(s)
+
+# Manual placement if needed
+Entity arith = place("arithmetic-combinator", 0, 0, {
+ operation: "+"
+});
```
#### Settable Properties
-Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = value`
+Set at placement: `place("name", x, y, {prop: value})`
| Property | Type | Default | Example |
|----------|------|---------|---------|
@@ -204,32 +341,51 @@ Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop =
| `first_operand_wires` | Complex (see draftsman docs) ⚠️ | (factory) | |
| `item_requests` | List (complex) ⚠️ | (factory) | |
| `name` | String (entity prototype name) | (factory) | |
-| `operation` | One of: *, /, +, -, %, ^, <<, >>, AND, OR, XOR | "*" | |
+| `operation` | One of: `"*"`, `"/"`, `"+"`, `"-"`, `"%"`, `"^"`, `"<<"`, `">>"`, `"AND"`, `"OR"`, `"XOR"` ([ArithmeticOperation](#arithmeticoperation)) | "*" | `"*"` |
| `player_description` | String | "" | |
-| `quality` | String (normal/uncommon/rare/epic/legendary) | "normal" | |
+| `quality` | One of: `"normal"`, `"uncommon"`, `"rare"`, `"epic"`, `"legendary"`, `"quality-unknown"` ([QualityID](#qualityid)) | "normal" | `"normal"` |
| `second_operand` | Integer | 0 | |
| `second_operand_wires` | Complex (see draftsman docs) ⚠️ | (factory) | |
+#### Signal Configuration
+
+Properties for configuring which signals the entity uses:
+
+| Property | Type | Description |
+|----------|------|-------------|
+| `output_signal` | String (signal name, e.g. `"signal-A"`) | The output signal of the ``ArithmeticCombinator``. Cannot be... |
+
---
### DeciderCombinator
**Description:** A decider combinator. Makes comparisons based on circuit network inputs.
+**Draftsman Source:** [DeciderCombinator class](https://github.com/redruin1/factorio-draftsman/blob/main/draftsman/prototypes/decider_combinator.py)
+
**Prototypes:** `"decider-combinator"`
-**Connection Type:** Dual circuit connection (separate input and output sides)
+**Connection Type:** Dual circuit (separate input/output sides)
+
+#### Reading Entity Output
+
+Use `entity.output` to read: **Conditional output signals**
+
+```facto
+Entity e = place("decider-combinator", 0, 0);
+Bundle signals = e.output; # Returns all output signals
+```
#### DSL Examples
```facto
-# Note: Combinators are typically generated by the compiler
-Signal flag = (count > 100) : 1; # Creates DeciderCombinator
+# Note: Decider combinators are usually auto-generated
+Signal flag = (count > 100) : 1; # Creates decider
```
#### Settable Properties
-Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = value`
+Set at placement: `place("name", x, y, {prop: value})`
| Property | Type | Default | Example |
|----------|------|---------|---------|
@@ -239,7 +395,7 @@ Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop =
| `name` | String (entity prototype name) | (factory) | |
| `outputs` | List (complex) ⚠️ | (factory) | |
| `player_description` | String | "" | |
-| `quality` | String (normal/uncommon/rare/epic/legendary) | "normal" | |
+| `quality` | One of: `"normal"`, `"uncommon"`, `"rare"`, `"epic"`, `"legendary"`, `"quality-unknown"` ([QualityID](#qualityid)) | "normal" | `"normal"` |
---
@@ -247,29 +403,40 @@ Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop =
**Description:** A combinator that holds a number of constant signals that can be output to the circuit network.
+**Draftsman Source:** [ConstantCombinator class](https://github.com/redruin1/factorio-draftsman/blob/main/draftsman/prototypes/constant_combinator.py)
+
**Prototypes:** `"constant-combinator"`
**Connection Type:** Single circuit connection
+#### Reading Entity Output
+
+Use `entity.output` to read: **Constant signal values**
+
+```facto
+Entity e = place("constant-combinator", 0, 0);
+Bundle signals = e.output; # Returns all output signals
+```
+
#### DSL Examples
```facto
-# Note: Constant combinators are typically generated by the compiler
-Signal constant = 42; # Creates ConstantCombinator
+# Note: Constants are usually auto-generated
+Signal constant = 42; # Creates constant combinator
```
#### Settable Properties
-Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = value`
+Set at placement: `place("name", x, y, {prop: value})`
| Property | Type | Default | Example |
|----------|------|---------|---------|
| `direction` | Integer ([Direction](#direction)) | 0 | `0 # NORTH` |
-| `enabled` | Boolean (0/1) | true | `1` |
+| `enabled` | Boolean (`0` or `1`) | 1 | `1` |
| `item_requests` | List (complex) ⚠️ | (factory) | |
| `name` | String (entity prototype name) | (factory) | |
| `player_description` | String | "" | |
-| `quality` | String (normal/uncommon/rare/epic/legendary) | "normal" | |
+| `quality` | One of: `"normal"`, `"uncommon"`, `"rare"`, `"epic"`, `"legendary"`, `"quality-unknown"` ([QualityID](#qualityid)) | "normal" | `"normal"` |
| `sections` | List (complex) ⚠️ | (factory) | |
---
@@ -278,29 +445,36 @@ Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop =
**Description:** (Factorio 2.0)
+**Draftsman Source:** [SelectorCombinator class](https://github.com/redruin1/factorio-draftsman/blob/main/draftsman/prototypes/selector_combinator.py)
+
**Prototypes:** `"selector-combinator"`
-**Connection Type:** Dual circuit connection (separate input and output sides)
+**Connection Type:** Dual circuit (separate input/output sides)
-#### Circuit Signal I/O
+#### Reading Entity Output
-**Signal Inputs:**
+Use `entity.output` to read: **Selected/filtered signals**
-| Signal Property | Signal Type | Description | Enable With |
-|-----------------|-------------|-------------|-------------|
-| `index_signal` | Integer | Signal for selector index input | Always active |
-| `quality_source_signal` | Any signal | Signal to read quality from | Always active |
+```facto
+Entity e = place("selector-combinator", 0, 0);
+Bundle signals = e.output; # Returns all output signals
+```
-**Signal Outputs:**
+#### DSL Examples
-| Signal Property | Signal Type | Description | Enable With |
-|-----------------|-------------|-------------|-------------|
-| `count_signal` | Integer | Signal to output the count result | Always active |
-| `quality_destination_signal` | Quality level | Signal to output quality result | Always active |
+```facto
+# Selector in count mode
+Entity counter = place("selector-combinator", 0, 0, {
+ operation: "count",
+ count_signal: "signal-C"
+});
+# Reading output
+Bundle result = counter.output;
+```
#### Settable Properties
-Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = value`
+Set at placement: `place("name", x, y, {prop: value})`
| Property | Type | Default | Example |
|----------|------|---------|---------|
@@ -308,14 +482,25 @@ Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop =
| `index_constant` | Integer | 0 | |
| `item_requests` | List (complex) ⚠️ | (factory) | |
| `name` | String (entity prototype name) | (factory) | |
-| `operation` | String (see type for valid values) | "select" | |
+| `operation` | One of: `"select"`, `"count"`, `"random"`, `"stack-size"`, `"rocket-capacity"`, `"quality-filter"`, `"quality-transfer"` ([SelectorOperation](#selectoroperation)) | "select" | `"select"` |
| `player_description` | String | "" | |
-| `quality` | String (normal/uncommon/rare/epic/legendary) | "normal" | |
+| `quality` | One of: `"normal"`, `"uncommon"`, `"rare"`, `"epic"`, `"legendary"`, `"quality-unknown"` ([QualityID](#qualityid)) | "normal" | `"normal"` |
| `quality_filter` | Complex (see draftsman docs) ⚠️ | (factory) | |
-| `quality_source_static` | String (normal/uncommon/rare/epic/legendary) | "normal" | |
+| `quality_source_static` | One of: `"normal"`, `"uncommon"`, `"rare"`, `"epic"`, `"legendary"`, `"quality-unknown"` ([QualityID](#qualityid)) | "normal" | `"normal"` |
| `random_update_interval` | Integer | 0 | |
-| `select_max` | Boolean (0/1) | true | `1` |
-| `select_quality_from_signal` | Boolean (0/1) | false | `1` |
+| `select_max` | Boolean (`0` or `1`) | 1 | `1` |
+| `select_quality_from_signal` | Boolean (`0` or `1`) | 0 | `1` |
+
+#### Signal Configuration
+
+Properties for configuring which signals the entity uses:
+
+| Property | Type | Description |
+|----------|------|-------------|
+| `count_signal` | String (signal name, e.g. `"signal-A"`) | What signal to output the sum total number of unique signals... |
+| `index_signal` | String (signal name, e.g. `"signal-A"`) | Which input signal to pull the index value from in order to ... |
+| `quality_destination_signal` | String (signal name, e.g. `"signal-A"`) | The destination signal(s) to output with the read quality va... |
+| `quality_source_signal` | String (signal name, e.g. `"signal-A"`) | The input signal type to pull the quality from dynamically, ... |
---
@@ -325,23 +510,12 @@ Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop =
**Description:** An entity that illuminates an area.
+**Draftsman Source:** [Lamp class](https://github.com/redruin1/factorio-draftsman/blob/main/draftsman/prototypes/lamp.py)
+
**Prototypes:** `"small-lamp"`
**Connection Type:** Single circuit connection
-#### Circuit Signal I/O
-
-**Signal Inputs:**
-
-| Signal Property | Signal Type | Description | Enable With |
-|-----------------|-------------|-------------|-------------|
-| `circuit_condition` | Any signal | Signal used in enable/disable condition | `circuit_enabled: 1` |
-| `logistic_condition` | Any signal | Logistic network signal for enable/disable | `connect_to_logistic_network: 1` |
-| `red_signal` | Integer (0-255) | Red color component (COMPONENTS mode) | `use_colors: 1, color_mode: 1` |
-| `green_signal` | Integer (0-255) | Green color component (COMPONENTS mode) | `use_colors: 1, color_mode: 1` |
-| `blue_signal` | Integer (0-255) | Blue color component (COMPONENTS mode) | `use_colors: 1, color_mode: 1` |
-| `rgb_signal` | Packed RGB integer | Combined RGB value (PACKED_RGB mode) | `use_colors: 1, color_mode: 2` |
-
#### DSL Examples
```facto
@@ -349,28 +523,41 @@ Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop =
Entity lamp = place("small-lamp", 0, 0);
lamp.enable = signal > 0;
-# RGB colored lamp
+# RGB colored lamp (color_mode: 1 = COMPONENTS)
Entity rgb_lamp = place("small-lamp", 2, 0, {use_colors: 1, color_mode: 1});
-rgb_lamp.r = red_signal;
-rgb_lamp.g = green_signal;
-rgb_lamp.b = blue_signal;
+rgb_lamp.r = red_value;
+rgb_lamp.g = green_value;
+rgb_lamp.b = blue_value;
```
#### Settable Properties
-Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = value`
+Set at placement: `place("name", x, y, {prop: value})`
| Property | Type | Default | Example |
|----------|------|---------|---------|
-| `always_on` | Boolean (0/1) | false | `1` |
-| `circuit_enabled` | Boolean (0/1) | false | `1` |
-| `color` | Color {r: 0-255, g: 0-255, b: 0-255} | (factory) | `{r: 255, g: 0, b: 0}` |
+| `always_on` | Boolean (`0` or `1`) | 0 | `1` |
+| `circuit_enabled` | Boolean (`0` or `1`) | 0 | `1` |
+| `color` | Object `{r, g, b}` (0-255 each) | (factory) | `{r: 255, g: 0, b: 0}` |
| `color_mode` | Integer ([LampColorMode](#lampcolormode)) | 0 | `0 # COLOR_MAPPING` |
-| `connect_to_logistic_network` | Boolean (0/1) | false | `1` |
+| `connect_to_logistic_network` | Boolean (`0` or `1`) | 0 | `1` |
| `item_requests` | List (complex) ⚠️ | (factory) | |
| `name` | String (entity prototype name) | (factory) | |
-| `quality` | String (normal/uncommon/rare/epic/legendary) | "normal" | |
-| `use_colors` | Boolean (0/1) | false | `1` |
+| `quality` | One of: `"normal"`, `"uncommon"`, `"rare"`, `"epic"`, `"legendary"`, `"quality-unknown"` ([QualityID](#qualityid)) | "normal" | `"normal"` |
+| `use_colors` | Boolean (`0` or `1`) | 0 | `1` |
+
+#### Signal Configuration
+
+Properties for configuring which signals the entity uses:
+
+| Property | Type | Description |
+|----------|------|-------------|
+| `blue_signal` | String (signal name, e.g. `"signal-A"`) | The signal to pull the blue color component from, if color_m... |
+| `circuit_condition` | Condition (use `.enable = expr`) | The circuit condition that must be passed in order for this ... |
+| `green_signal` | String (signal name, e.g. `"signal-A"`) | The signal to pull the green color component from, if color_... |
+| `logistic_condition` | Condition (use `.enable = expr`) | The logistic condition that must be passed in order for this... |
+| `red_signal` | String (signal name, e.g. `"signal-A"`) | |
+| `rgb_signal` | String (signal name, e.g. `"signal-A"`) | .. versionadded:: 3.0.0 (Factorio 2.0) |
---
@@ -378,25 +565,36 @@ Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop =
**Description:** (Factorio 2.0)
+**Draftsman Source:** [DisplayPanel class](https://github.com/redruin1/factorio-draftsman/blob/main/draftsman/prototypes/display_panel.py)
+
**Prototypes:** `"display-panel"`
**Connection Type:** Single circuit connection
+#### Reading Entity Output
+
+Use `entity.output` to read: **Circuit network signals**
+
+```facto
+Entity e = place("display-panel", 0, 0);
+Bundle signals = e.output; # Returns all output signals
+```
+
#### Settable Properties
-Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = value`
+Set at placement: `place("name", x, y, {prop: value})`
| Property | Type | Default | Example |
|----------|------|---------|---------|
-| `always_show_in_alt_mode` | Boolean (0/1) | false | `1` |
+| `always_show_in_alt_mode` | Boolean (`0` or `1`) | 0 | `1` |
| `direction` | Integer ([Direction](#direction)) | 0 | `0 # NORTH` |
-| `icon` | String (signal name) | None | |
+| `icon` | String (signal name, e.g. `"signal-A"`) | None | |
| `item_requests` | List (complex) ⚠️ | (factory) | |
| `messages` | List (complex) ⚠️ | (factory) | |
| `name` | String (entity prototype name) | (factory) | |
| `player_description` | String | "" | |
-| `quality` | String (normal/uncommon/rare/epic/legendary) | "normal" | |
-| `show_in_chart` | Boolean (0/1) | false | `1` |
+| `quality` | One of: `"normal"`, `"uncommon"`, `"rare"`, `"epic"`, `"legendary"`, `"quality-unknown"` ([QualityID](#qualityid)) | "normal" | `"normal"` |
+| `show_in_chart` | Boolean (`0` or `1`) | 0 | `1` |
| `text` | String | "" | |
---
@@ -407,56 +605,68 @@ Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop =
**Description:** An entity with a swinging arm that can move items between machines.
+**Draftsman Source:** [Inserter class](https://github.com/redruin1/factorio-draftsman/blob/main/draftsman/prototypes/inserter.py)
+
**Prototypes:** `"bulk-inserter"`, `"fast-inserter"`, `"inserter"`, `"burner-inserter"`, `"long-handed-inserter"`, `"stack-inserter"`
**Connection Type:** Single circuit connection
-#### Circuit Signal I/O
+#### Reading Entity Output
-**Signal Inputs:**
+Use `entity.output` to read: **Items in hand or filter status**
-| Signal Property | Signal Type | Description | Enable With |
-|-----------------|-------------|-------------|-------------|
-| `circuit_condition` | Any signal | Signal used in enable/disable condition | `circuit_enabled: 1` |
-| `logistic_condition` | Any signal | Logistic network signal for enable/disable | `connect_to_logistic_network: 1` |
-| `stack_size_control_signal` | Integer signal | Sets inserter stack size from signal value | `circuit_set_stack_size: 1` |
+```facto
+Entity e = place("bulk-inserter", 0, 0);
+Bundle signals = e.output; # Returns all output signals
+```
-**Content Outputs:**
+**Enable properties:**
-- Inserter hand contents (items being moved, enable with `read_hand_contents: 1`)
+- `read_hand_contents`: Read items in hand
+- `circuit_set_filters`: Control via filters
#### DSL Examples
```facto
# Inserter that enables when chest has items
Entity inserter = place("inserter", 0, 0, {direction: 4});
-inserter.enable = chest_contents > 50;
+inserter.enable = chest.output > 50;
```
#### Settable Properties
-Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = value`
+Set at placement: `place("name", x, y, {prop: value})`
| Property | Type | Default | Example |
|----------|------|---------|---------|
-| `circuit_enabled` | Boolean (0/1) | false | `1` |
-| `circuit_set_filters` | Boolean (0/1) | false | `1` |
-| `circuit_set_stack_size` | Boolean (0/1) | false | `1` |
-| `connect_to_logistic_network` | Boolean (0/1) | false | `1` |
+| `circuit_enabled` | Boolean (`0` or `1`) | 0 | `1` |
+| `circuit_set_filters` | Boolean (`0` or `1`) | 0 | `1` |
+| `circuit_set_stack_size` | Boolean (`0` or `1`) | 0 | `1` |
+| `connect_to_logistic_network` | Boolean (`0` or `1`) | 0 | `1` |
| `direction` | Integer ([Direction](#direction)) | 0 | `0 # NORTH` |
-| `drop_position_offset` | Vector {x, y} ⚠️ | (factory) | |
-| `filter_mode` | One of: whitelist, blacklist | "whitelist" | |
+| `drop_position_offset` | Vector `{x, y}` ⚠️ | (factory) | |
+| `filter_mode` | One of: `"whitelist"`, `"blacklist"` ([FilterMode](#filtermode)) | "whitelist" | `"whitelist"` |
| `filters` | List (complex) ⚠️ | (factory) | |
| `item_requests` | List (complex) ⚠️ | (factory) | |
| `mode_of_operation` | Integer ([InserterModeOfOperation](#insertermodeofoperation)) | 0 | `0 # ENABLE_DISABLE` |
| `name` | String (entity prototype name) | (factory) | |
| `override_stack_size` | Integer | None | |
-| `pickup_position_offset` | Vector {x, y} ⚠️ | (factory) | |
-| `quality` | String (normal/uncommon/rare/epic/legendary) | "normal" | |
-| `read_hand_contents` | Boolean (0/1) | false | `1` |
+| `pickup_position_offset` | Vector `{x, y}` ⚠️ | (factory) | |
+| `quality` | One of: `"normal"`, `"uncommon"`, `"rare"`, `"epic"`, `"legendary"`, `"quality-unknown"` ([QualityID](#qualityid)) | "normal" | `"normal"` |
+| `read_hand_contents` | Boolean (`0` or `1`) | 0 | `1` |
| `read_mode` | Integer ([InserterReadMode](#inserterreadmode)) | 0 | `0 # PULSE` |
-| `spoil_priority` | One of: spoiled-first, fresh-first, None | None | |
-| `use_filters` | Boolean (0/1) | false | `1` |
+| `spoil_priority` | One of: `"spoiled-first"`, `"fresh-first"` ([SpoilPriority](#spoilpriority)) | None | `"spoiled-first"` |
+| `use_filters` | Boolean (`0` or `1`) | 0 | `1` |
+
+#### Signal Configuration
+
+Properties for configuring which signals the entity uses:
+
+| Property | Type | Description |
+|----------|------|-------------|
+| `circuit_condition` | Condition (use `.enable = expr`) | The circuit condition that must be passed in order for this ... |
+| `logistic_condition` | Condition (use `.enable = expr`) | The logistic condition that must be passed in order for this... |
+| `stack_size_control_signal` | String (signal name, e.g. `"signal-A"`) | What circuit network signal should indicate the current stac... |
---
@@ -466,67 +676,72 @@ Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop =
**Description:** An entity that transports items.
+**Draftsman Source:** [TransportBelt class](https://github.com/redruin1/factorio-draftsman/blob/main/draftsman/prototypes/transport_belt.py)
+
**Prototypes:** `"express-transport-belt"`, `"transport-belt"`, `"fast-transport-belt"`, `"turbo-transport-belt"`
**Connection Type:** Single circuit connection
-#### Circuit Signal I/O
-
-**Signal Inputs:**
+#### Reading Entity Output
-| Signal Property | Signal Type | Description | Enable With |
-|-----------------|-------------|-------------|-------------|
-| `circuit_condition` | Any signal | Signal used in enable/disable condition | `circuit_enabled: 1` |
-| `logistic_condition` | Any signal | Logistic network signal for enable/disable | `connect_to_logistic_network: 1` |
-
-**Content Outputs:**
-
-- Item contents (all items in entity, enable with `read_contents: 1`)
-
-#### DSL Examples
+Use `entity.output` to read: **Items on the belt**
```facto
-# Belt that stops when storage is full
-Entity belt = place("transport-belt", 0, 0, {direction: 4});
-belt.enable = storage_count < 1000;
+Entity e = place("express-transport-belt", 0, 0);
+Bundle signals = e.output; # Returns all output signals
```
+**Enable properties:**
+
+- `read_contents`: Read belt contents
+
#### Settable Properties
-Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = value`
+Set at placement: `place("name", x, y, {prop: value})`
| Property | Type | Default | Example |
|----------|------|---------|---------|
-| `circuit_enabled` | Boolean (0/1) | false | `1` |
-| `connect_to_logistic_network` | Boolean (0/1) | false | `1` |
+| `circuit_enabled` | Boolean (`0` or `1`) | 0 | `1` |
+| `connect_to_logistic_network` | Boolean (`0` or `1`) | 0 | `1` |
| `direction` | Integer ([Direction](#direction)) | 0 | `0 # NORTH` |
| `item_requests` | List (complex) ⚠️ | (factory) | |
| `name` | String (entity prototype name) | (factory) | |
-| `quality` | String (normal/uncommon/rare/epic/legendary) | "normal" | |
-| `read_contents` | Boolean (0/1) | false | `1` |
+| `quality` | One of: `"normal"`, `"uncommon"`, `"rare"`, `"epic"`, `"legendary"`, `"quality-unknown"` ([QualityID](#qualityid)) | "normal" | `"normal"` |
+| `read_contents` | Boolean (`0` or `1`) | 0 | `1` |
| `read_mode` | Integer ([BeltReadMode](#beltreadmode)) | 0 | `0 # PULSE` |
+#### Signal Configuration
+
+Properties for configuring which signals the entity uses:
+
+| Property | Type | Description |
+|----------|------|-------------|
+| `circuit_condition` | Condition (use `.enable = expr`) | The circuit condition that must be passed in order for this ... |
+| `logistic_condition` | Condition (use `.enable = expr`) | The logistic condition that must be passed in order for this... |
+
---
### UndergroundBelt
**Description:** A transport belt that transfers items underneath other entities.
+**Draftsman Source:** [UndergroundBelt class](https://github.com/redruin1/factorio-draftsman/blob/main/draftsman/prototypes/underground_belt.py)
+
**Prototypes:** `"underground-belt"`, `"turbo-underground-belt"`, `"fast-underground-belt"`, `"express-underground-belt"`
**Connection Type:** Single circuit connection
#### Settable Properties
-Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = value`
+Set at placement: `place("name", x, y, {prop: value})`
| Property | Type | Default | Example |
|----------|------|---------|---------|
| `direction` | Integer ([Direction](#direction)) | 0 | `0 # NORTH` |
-| `io_type` | One of: input, output, None | "input" | |
+| `io_type` | One of: `"input"`, `"output"` ([IOType](#iotype)) | "input" | `"input"` |
| `item_requests` | List (complex) ⚠️ | (factory) | |
| `name` | String (entity prototype name) | (factory) | |
-| `quality` | String (normal/uncommon/rare/epic/legendary) | "normal" | |
+| `quality` | One of: `"normal"`, `"uncommon"`, `"rare"`, `"epic"`, `"legendary"`, `"quality-unknown"` ([QualityID](#qualityid)) | "normal" | `"normal"` |
---
@@ -534,30 +749,41 @@ Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop =
**Description:** An entity that evenly splits a set of input belts between a set of output belts.
+**Draftsman Source:** [Splitter class](https://github.com/redruin1/factorio-draftsman/blob/main/draftsman/prototypes/splitter.py)
+
**Prototypes:** `"turbo-splitter"`, `"express-splitter"`, `"fast-splitter"`, `"splitter"`
**Connection Type:** Single circuit connection
+#### Reading Entity Output
+
+Use `entity.output` to read: **Circuit network signals**
+
+```facto
+Entity e = place("turbo-splitter", 0, 0);
+Bundle signals = e.output; # Returns all output signals
+```
+
#### Settable Properties
-Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = value`
+Set at placement: `place("name", x, y, {prop: value})`
| Property | Type | Default | Example |
|----------|------|---------|---------|
| `direction` | Integer ([Direction](#direction)) | 0 | `0 # NORTH` |
-| `filter` | String (signal name) | None | |
-| `input_left_condition` | Condition (set via .enable) ⚠️ | (factory) | |
-| `input_priority` | One of: left, none, right | "none" | |
-| `input_right_condition` | Condition (set via .enable) ⚠️ | (factory) | |
+| `filter` | String (signal name, e.g. `"signal-A"`) | None | |
+| `input_left_condition` | Condition (use `.enable = expr`) ⚠️ | (factory) | |
+| `input_priority` | One of: `"left"`, `"none"`, `"right"` ([SplitterPriority](#splitterpriority)) | "none" | `"left"` |
+| `input_right_condition` | Condition (use `.enable = expr`) ⚠️ | (factory) | |
| `item_requests` | List (complex) ⚠️ | (factory) | |
| `name` | String (entity prototype name) | (factory) | |
-| `output_left_condition` | Condition (set via .enable) ⚠️ | (factory) | |
-| `output_priority` | One of: left, none, right | "none" | |
-| `output_right_condition` | Condition (set via .enable) ⚠️ | (factory) | |
-| `quality` | String (normal/uncommon/rare/epic/legendary) | "normal" | |
-| `set_filter` | Boolean (0/1) | false | `1` |
-| `set_input_side` | Boolean (0/1) | false | `1` |
-| `set_output_side` | Boolean (0/1) | false | `1` |
+| `output_left_condition` | Condition (use `.enable = expr`) ⚠️ | (factory) | |
+| `output_priority` | One of: `"left"`, `"none"`, `"right"` ([SplitterPriority](#splitterpriority)) | "none" | `"left"` |
+| `output_right_condition` | Condition (use `.enable = expr`) ⚠️ | (factory) | |
+| `quality` | One of: `"normal"`, `"uncommon"`, `"rare"`, `"epic"`, `"legendary"`, `"quality-unknown"` ([QualityID](#qualityid)) | "normal" | `"normal"` |
+| `set_filter` | Boolean (`0` or `1`) | 0 | `1` |
+| `set_input_side` | Boolean (`0` or `1`) | 0 | `1` |
+| `set_output_side` | Boolean (`0` or `1`) | 0 | `1` |
---
@@ -565,23 +791,25 @@ Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop =
**Description:** An entity that inserts items directly from a belt to an inventory or vise-versa.
+**Draftsman Source:** [Loader class](https://github.com/redruin1/factorio-draftsman/blob/main/draftsman/prototypes/loader.py)
+
**Prototypes:** `"loader"`, `"fast-loader"`, `"turbo-loader"`, `"express-loader"`
**Connection Type:** Single circuit connection
#### Settable Properties
-Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = value`
+Set at placement: `place("name", x, y, {prop: value})`
| Property | Type | Default | Example |
|----------|------|---------|---------|
| `direction` | Integer ([Direction](#direction)) | 0 | `0 # NORTH` |
| `filters` | List (complex) ⚠️ | (factory) | |
-| `io_type` | One of: input, output, None | "input" | |
+| `io_type` | One of: `"input"`, `"output"` ([IOType](#iotype)) | "input" | `"input"` |
| `item_requests` | List (complex) ⚠️ | (factory) | |
| `name` | String (entity prototype name) | (factory) | |
-| `quality` | String (normal/uncommon/rare/epic/legendary) | "normal" | |
-| `use_filters` | Boolean (0/1) | false | `1` |
+| `quality` | One of: `"normal"`, `"uncommon"`, `"rare"`, `"epic"`, `"legendary"`, `"quality-unknown"` ([QualityID](#qualityid)) | "normal" | `"normal"` |
+| `use_filters` | Boolean (`0` or `1`) | 0 | `1` |
---
@@ -589,20 +817,22 @@ Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop =
**Description:** A belt object that can transfer items over any distance, regardless of constraint, as long as the two are paired together.
+**Draftsman Source:** [LinkedBelt class](https://github.com/redruin1/factorio-draftsman/blob/main/draftsman/prototypes/linked_belt.py)
+
**Prototypes:** `"linked-belt"`
**Connection Type:** Single circuit connection
#### Settable Properties
-Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = value`
+Set at placement: `place("name", x, y, {prop: value})`
| Property | Type | Default | Example |
|----------|------|---------|---------|
| `direction` | Integer ([Direction](#direction)) | 0 | `0 # NORTH` |
| `item_requests` | List (complex) ⚠️ | (factory) | |
| `name` | String (entity prototype name) | (factory) | |
-| `quality` | String (normal/uncommon/rare/epic/legendary) | "normal" | |
+| `quality` | One of: `"normal"`, `"uncommon"`, `"rare"`, `"epic"`, `"legendary"`, `"quality-unknown"` ([QualityID](#qualityid)) | "normal" | `"normal"` |
---
@@ -612,97 +842,121 @@ Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop =
**Description:** A stop for making train schedules for locomotives.
+**Draftsman Source:** [TrainStop class](https://github.com/redruin1/factorio-draftsman/blob/main/draftsman/prototypes/train_stop.py)
+
**Prototypes:** `"train-stop"`
**Connection Type:** Single circuit connection
-#### Circuit Signal I/O
+#### Reading Entity Output
-**Signal Inputs:**
+Use `entity.output` to read: **Train ID, count, and cargo**
-| Signal Property | Signal Type | Description | Enable With |
-|-----------------|-------------|-------------|-------------|
-| `circuit_condition` | Any signal | Signal used in enable/disable condition | `circuit_enabled: 1` |
-| `logistic_condition` | Any signal | Logistic network signal for enable/disable | `connect_to_logistic_network: 1` |
-| `trains_limit_signal` | Integer | Sets train limit from signal value | `signal_limits_trains: 1` |
-| `priority_signal` | Integer | Sets station priority from signal value | `set_priority: 1` |
+```facto
+Entity e = place("train-stop", 0, 0);
+Bundle signals = e.output; # Returns all output signals
+```
-**Signal Outputs:**
+**Enable properties:**
-| Signal Property | Signal Type | Description | Enable With |
-|-----------------|-------------|-------------|-------------|
-| `train_stopped_signal` | Train ID | Outputs ID of stopped train | `read_stopped_train: 1` |
-| `trains_count_signal` | Integer | Outputs count of trains en route | `read_trains_count: 1` |
+- `read_stopped_train`: Read stopped train ID
+- `read_trains_count`: Read incoming trains count
+- `read_from_train`: Read train cargo
#### DSL Examples
```facto
# Train station with circuit control
Entity station = place("train-stop", 0, 0, {station: "Iron Pickup"});
-station.enable = has_cargo > 0;
+station.enable = cargo.output > 0;
+# Read train info
+Bundle train_info = station.output;
```
#### Settable Properties
-Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = value`
+Set at placement: `place("name", x, y, {prop: value})`
| Property | Type | Default | Example |
|----------|------|---------|---------|
-| `circuit_enabled` | Boolean (0/1) | false | `1` |
-| `color` | Color {r: 0-255, g: 0-255, b: 0-255} | (factory) | `{r: 255, g: 0, b: 0}` |
-| `connect_to_logistic_network` | Boolean (0/1) | false | `1` |
+| `circuit_enabled` | Boolean (`0` or `1`) | 0 | `1` |
+| `color` | Object `{r, g, b}` (0-255 each) | (factory) | `{r: 255, g: 0, b: 0}` |
+| `connect_to_logistic_network` | Boolean (`0` or `1`) | 0 | `1` |
| `direction` | Integer ([Direction](#direction)) | 0 | `0 # NORTH` |
| `item_requests` | List (complex) ⚠️ | (factory) | |
| `manual_trains_limit` | Integer | None | |
| `name` | String (entity prototype name) | (factory) | |
| `priority` | Integer | 50 | |
-| `quality` | String (normal/uncommon/rare/epic/legendary) | "normal" | |
-| `read_from_train` | Boolean (0/1) | false | `1` |
-| `read_stopped_train` | Boolean (0/1) | false | `1` |
-| `read_trains_count` | Boolean (0/1) | false | `1` |
-| `send_to_train` | Boolean (0/1) | true | `1` |
-| `set_priority` | Boolean (0/1) | false | `1` |
-| `signal_limits_trains` | Boolean (0/1) | false | `1` |
+| `quality` | One of: `"normal"`, `"uncommon"`, `"rare"`, `"epic"`, `"legendary"`, `"quality-unknown"` ([QualityID](#qualityid)) | "normal" | `"normal"` |
+| `read_from_train` | Boolean (`0` or `1`) | 0 | `1` |
+| `read_stopped_train` | Boolean (`0` or `1`) | 0 | `1` |
+| `read_trains_count` | Boolean (`0` or `1`) | 0 | `1` |
+| `send_to_train` | Boolean (`0` or `1`) | 1 | `1` |
+| `set_priority` | Boolean (`0` or `1`) | 0 | `1` |
+| `signal_limits_trains` | Boolean (`0` or `1`) | 0 | `1` |
| `station` | String | "" | `"Station Name"` |
+#### Signal Configuration
+
+Properties for configuring which signals the entity uses:
+
+| Property | Type | Description |
+|----------|------|-------------|
+| `circuit_condition` | Condition (use `.enable = expr`) | The circuit condition that must be passed in order for this ... |
+| `logistic_condition` | Condition (use `.enable = expr`) | The logistic condition that must be passed in order for this... |
+| `priority_signal` | String (signal name, e.g. `"signal-A"`) | Which signal to read the dynamic priority of this train stop... |
+| `train_stopped_signal` | String (signal name, e.g. `"signal-A"`) | What signal to output the unique train ID if a train is curr... |
+| `trains_count_signal` | String (signal name, e.g. `"signal-A"`) | What signal to use to output the current number of trains th... |
+| `trains_limit_signal` | String (signal name, e.g. `"signal-A"`) | What signal to read to limit the number of trains that can u... |
+
---
### RailSignal
**Description:** A rail signal that determines whether or not trains can pass along their rail block.
+**Draftsman Source:** [RailSignal class](https://github.com/redruin1/factorio-draftsman/blob/main/draftsman/prototypes/rail_signal.py)
+
**Prototypes:** `"rail-signal"`
**Connection Type:** Single circuit connection
-#### Circuit Signal I/O
+#### Reading Entity Output
-**Signal Inputs:**
+Use `entity.output` to read: **Signal state (red/yellow/green)**
-| Signal Property | Signal Type | Description | Enable With |
-|-----------------|-------------|-------------|-------------|
-| `circuit_condition` | Any signal | Signal used in enable/disable condition | `circuit_enabled: 1` |
+```facto
+Entity e = place("rail-signal", 0, 0);
+Bundle signals = e.output; # Returns all output signals
+```
-**Signal Outputs:**
+**Enable properties:**
-| Signal Property | Signal Type | Description | Enable With |
-|-----------------|-------------|-------------|-------------|
-| `red_output_signal` | 1 when red | Outputs 1 when rail signal shows red | `read_signal: 1` |
-| `yellow_output_signal` | 1 when yellow | Outputs 1 when rail signal shows yellow | `read_signal: 1` |
-| `green_output_signal` | 1 when green | Outputs 1 when rail signal shows green | `read_signal: 1` |
+- `read_signal`: Read signal state
#### Settable Properties
-Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = value`
+Set at placement: `place("name", x, y, {prop: value})`
| Property | Type | Default | Example |
|----------|------|---------|---------|
-| `circuit_enabled` | Boolean (0/1) | false | `1` |
+| `circuit_enabled` | Boolean (`0` or `1`) | 0 | `1` |
| `direction` | Integer ([Direction](#direction)) | 0 | `0 # NORTH` |
| `item_requests` | List (complex) ⚠️ | (factory) | |
| `name` | String (entity prototype name) | (factory) | |
-| `quality` | String (normal/uncommon/rare/epic/legendary) | "normal" | |
-| `read_signal` | Boolean (0/1) | true | `1` |
+| `quality` | One of: `"normal"`, `"uncommon"`, `"rare"`, `"epic"`, `"legendary"`, `"quality-unknown"` ([QualityID](#qualityid)) | "normal" | `"normal"` |
+| `read_signal` | Boolean (`0` or `1`) | 1 | `1` |
+
+#### Signal Configuration
+
+Properties for configuring which signals the entity uses:
+
+| Property | Type | Description |
+|----------|------|-------------|
+| `circuit_condition` | Condition (use `.enable = expr`) | The circuit condition that must be passed in order for this ... |
+| `green_output_signal` | String (signal name, e.g. `"signal-A"`) | The green output signal. Sent with a unit value when the rai... |
+| `red_output_signal` | String (signal name, e.g. `"signal-A"`) | The red output signal. Sent with a unit value when the rail ... |
+| `yellow_output_signal` | String (signal name, e.g. `"signal-A"`) | The yellow output signal. Sent with a unit value when the ra... |
---
@@ -710,31 +964,46 @@ Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop =
**Description:** A rail signal that determines access of a current rail block based on a forward rail block.
+**Draftsman Source:** [RailChainSignal class](https://github.com/redruin1/factorio-draftsman/blob/main/draftsman/prototypes/rail_chain_signal.py)
+
**Prototypes:** `"rail-chain-signal"`
**Connection Type:** Single circuit connection
-#### Circuit Signal I/O
+#### Reading Entity Output
+
+Use `entity.output` to read: **Signal state (red/yellow/green/blue)**
-**Signal Outputs:**
+```facto
+Entity e = place("rail-chain-signal", 0, 0);
+Bundle signals = e.output; # Returns all output signals
+```
-| Signal Property | Signal Type | Description | Enable With |
-|-----------------|-------------|-------------|-------------|
-| `red_output_signal` | 1 when red | Outputs 1 when rail signal shows red | `read_signal: 1` |
-| `yellow_output_signal` | 1 when yellow | Outputs 1 when rail signal shows yellow | `read_signal: 1` |
-| `green_output_signal` | 1 when green | Outputs 1 when rail signal shows green | `read_signal: 1` |
-| `blue_output_signal` | 1 when blue | Outputs 1 when chain signal reserved | Always active |
+**Enable properties:**
+
+- `read_signal`: Read signal state
#### Settable Properties
-Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = value`
+Set at placement: `place("name", x, y, {prop: value})`
| Property | Type | Default | Example |
|----------|------|---------|---------|
| `direction` | Integer ([Direction](#direction)) | 0 | `0 # NORTH` |
| `item_requests` | List (complex) ⚠️ | (factory) | |
| `name` | String (entity prototype name) | (factory) | |
-| `quality` | String (normal/uncommon/rare/epic/legendary) | "normal" | |
+| `quality` | One of: `"normal"`, `"uncommon"`, `"rare"`, `"epic"`, `"legendary"`, `"quality-unknown"` ([QualityID](#qualityid)) | "normal" | `"normal"` |
+
+#### Signal Configuration
+
+Properties for configuring which signals the entity uses:
+
+| Property | Type | Description |
+|----------|------|-------------|
+| `blue_output_signal` | String (signal name, e.g. `"signal-A"`) | The blue output signal. Sent with a unit value when the rail... |
+| `green_output_signal` | String (signal name, e.g. `"signal-A"`) | The green output signal. Sent with a unit value when the rai... |
+| `red_output_signal` | String (signal name, e.g. `"signal-A"`) | The red output signal. Sent with a unit value when the rail ... |
+| `yellow_output_signal` | String (signal name, e.g. `"signal-A"`) | The yellow output signal. Sent with a unit value when the ra... |
---
@@ -742,23 +1011,25 @@ Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop =
**Description:** A train car that moves other wagons around using a fuel.
+**Draftsman Source:** [Locomotive class](https://github.com/redruin1/factorio-draftsman/blob/main/draftsman/prototypes/locomotive.py)
+
**Prototypes:** `"locomotive"`
**Connection Type:** Single circuit connection
#### Settable Properties
-Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = value`
+Set at placement: `place("name", x, y, {prop: value})`
| Property | Type | Default | Example |
|----------|------|---------|---------|
-| `color` | Color {r: 0-255, g: 0-255, b: 0-255} | Color(r=0.9176470588235294, g=0.06666666666666667, b=0, a=0.4980392156862745) | `{r: 255, g: 0, b: 0}` |
-| `enable_logistics_while_moving` | Boolean (0/1) | true | `1` |
+| `color` | Object `{r, g, b}` (0-255 each) | Color(r=0.9176470588235294, g=0.06666666666666667, b=0, a=0.4980392156862745) | `{r: 255, g: 0, b: 0}` |
+| `enable_logistics_while_moving` | Boolean (`0` or `1`) | 1 | `1` |
| `equipment` | List (complex) ⚠️ | (factory) | |
| `item_requests` | List (complex) ⚠️ | (factory) | |
| `name` | String (entity prototype name) | (factory) | |
-| `orientation` | Float (0.0-1.0) | | |
-| `quality` | String (normal/uncommon/rare/epic/legendary) | "normal" | |
+| `orientation` | Complex (see draftsman docs) ⚠️ | | |
+| `quality` | One of: `"normal"`, `"uncommon"`, `"rare"`, `"epic"`, `"legendary"`, `"quality-unknown"` ([QualityID](#qualityid)) | "normal" | `"normal"` |
---
@@ -766,23 +1037,25 @@ Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop =
**Description:** A train wagon that holds items as cargo.
+**Draftsman Source:** [CargoWagon class](https://github.com/redruin1/factorio-draftsman/blob/main/draftsman/prototypes/cargo_wagon.py)
+
**Prototypes:** `"cargo-wagon"`
**Connection Type:** Single circuit connection
#### Settable Properties
-Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = value`
+Set at placement: `place("name", x, y, {prop: value})`
| Property | Type | Default | Example |
|----------|------|---------|---------|
-| `enable_logistics_while_moving` | Boolean (0/1) | true | `1` |
+| `enable_logistics_while_moving` | Boolean (`0` or `1`) | 1 | `1` |
| `equipment` | List (complex) ⚠️ | (factory) | |
| `inventory` | Complex (see draftsman docs) ⚠️ | (factory) | |
| `item_requests` | List (complex) ⚠️ | (factory) | |
| `name` | String (entity prototype name) | (factory) | |
-| `orientation` | Float (0.0-1.0) | | |
-| `quality` | String (normal/uncommon/rare/epic/legendary) | "normal" | |
+| `orientation` | Complex (see draftsman docs) ⚠️ | | |
+| `quality` | One of: `"normal"`, `"uncommon"`, `"rare"`, `"epic"`, `"legendary"`, `"quality-unknown"` ([QualityID](#qualityid)) | "normal" | `"normal"` |
---
@@ -790,22 +1063,24 @@ Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop =
**Description:** A train wagon that holds a fluid as cargo.
+**Draftsman Source:** [FluidWagon class](https://github.com/redruin1/factorio-draftsman/blob/main/draftsman/prototypes/fluid_wagon.py)
+
**Prototypes:** `"fluid-wagon"`
**Connection Type:** Single circuit connection
#### Settable Properties
-Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = value`
+Set at placement: `place("name", x, y, {prop: value})`
| Property | Type | Default | Example |
|----------|------|---------|---------|
-| `enable_logistics_while_moving` | Boolean (0/1) | true | `1` |
+| `enable_logistics_while_moving` | Boolean (`0` or `1`) | 1 | `1` |
| `equipment` | List (complex) ⚠️ | (factory) | |
| `item_requests` | List (complex) ⚠️ | (factory) | |
| `name` | String (entity prototype name) | (factory) | |
-| `orientation` | Float (0.0-1.0) | | |
-| `quality` | String (normal/uncommon/rare/epic/legendary) | "normal" | |
+| `orientation` | Complex (see draftsman docs) ⚠️ | | |
+| `quality` | One of: `"normal"`, `"uncommon"`, `"rare"`, `"epic"`, `"legendary"`, `"quality-unknown"` ([QualityID](#qualityid)) | "normal" | `"normal"` |
---
@@ -813,1777 +1088,2171 @@ Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop =
**Description:** An artillery train car.
+**Draftsman Source:** [ArtilleryWagon class](https://github.com/redruin1/factorio-draftsman/blob/main/draftsman/prototypes/artillery_wagon.py)
+
**Prototypes:** `"artillery-wagon"`
**Connection Type:** Single circuit connection
#### Settable Properties
-Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = value`
+Set at placement: `place("name", x, y, {prop: value})`
| Property | Type | Default | Example |
|----------|------|---------|---------|
-| `auto_target` | Boolean (0/1) | true | `1` |
-| `enable_logistics_while_moving` | Boolean (0/1) | true | `1` |
+| `auto_target` | Boolean (`0` or `1`) | 1 | `1` |
+| `enable_logistics_while_moving` | Boolean (`0` or `1`) | 1 | `1` |
| `equipment` | List (complex) ⚠️ | (factory) | |
| `item_requests` | List (complex) ⚠️ | (factory) | |
| `name` | String (entity prototype name) | (factory) | |
-| `orientation` | Float (0.0-1.0) | | |
-| `quality` | String (normal/uncommon/rare/epic/legendary) | "normal" | |
+| `orientation` | Complex (see draftsman docs) ⚠️ | | |
+| `quality` | One of: `"normal"`, `"uncommon"`, `"rare"`, `"epic"`, `"legendary"`, `"quality-unknown"` ([QualityID](#qualityid)) | "normal" | `"normal"` |
---
-### StraightRail
-
-**Description:** A piece of rail track that moves in the 8 cardinal directions.
+## Production
-**Prototypes:** `"straight-rail"`
+### AssemblingMachine
-**Connection Type:** Single circuit connection
+**Description:** A machine that takes input items and produces output items. Includes assembling machines, chemical plants, oil refineries, and centrifuges, but does not include :py:class:`.RocketSilo`.
-#### Settable Properties
+**Draftsman Source:** [AssemblingMachine class](https://github.com/redruin1/factorio-draftsman/blob/main/draftsman/prototypes/assembling_machine.py)
-Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = value`
+**Prototypes:** `"centrifuge"`, `"biochamber"`, `"oil-refinery"`, `"foundry"`, `"captive-biter-spawner"`, `"assembling-machine-1"`, `"electromagnetic-plant"`, `"crusher"`, `"cryogenic-plant"`, `"assembling-machine-2"`, ... (12 total)
-| Property | Type | Default | Example |
-|----------|------|---------|---------|
-| `direction` | Integer ([Direction](#direction)) | 0 | `0 # NORTH` |
-| `item_requests` | List (complex) ⚠️ | (factory) | |
-| `name` | String (entity prototype name) | (factory) | |
-| `quality` | String (normal/uncommon/rare/epic/legendary) | "normal" | |
+**Connection Type:** Single circuit connection
----
+#### Reading Entity Output
-### CurvedRailA
+Use `entity.output` to read: **Recipe finished pulse, working status**
-**Description:** Curved rails which connect straight rails to half-diagonal rails.
+```facto
+Entity e = place("centrifuge", 0, 0);
+Bundle signals = e.output; # Returns all output signals
+```
-**Prototypes:** `"curved-rail-a"`
+**Enable properties:**
-**Connection Type:** Single circuit connection
+- `read_recipe_finished`: Pulse when recipe completes
+- `read_working`: Output working status
#### Settable Properties
-Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = value`
+Set at placement: `place("name", x, y, {prop: value})`
| Property | Type | Default | Example |
|----------|------|---------|---------|
+| `circuit_enabled` | Boolean (`0` or `1`) | 0 | `1` |
+| `circuit_set_recipe` | Boolean (`0` or `1`) | 0 | `1` |
+| `connect_to_logistic_network` | Boolean (`0` or `1`) | 0 | `1` |
| `direction` | Integer ([Direction](#direction)) | 0 | `0 # NORTH` |
+| `include_in_crafting` | Boolean (`0` or `1`) | 1 | `1` |
| `item_requests` | List (complex) ⚠️ | (factory) | |
| `name` | String (entity prototype name) | (factory) | |
-| `quality` | String (normal/uncommon/rare/epic/legendary) | "normal" | |
-
----
+| `quality` | One of: `"normal"`, `"uncommon"`, `"rare"`, `"epic"`, `"legendary"`, `"quality-unknown"` ([QualityID](#qualityid)) | "normal" | `"normal"` |
+| `read_contents` | Boolean (`0` or `1`) | 0 | `1` |
+| `read_recipe_finished` | Boolean (`0` or `1`) | 0 | `1` |
+| `read_working` | Boolean (`0` or `1`) | 0 | `1` |
+| `recipe` | String (recipe name) | None | `"iron-gear-wheel"` |
+| `recipe_quality` | One of: `"normal"`, `"uncommon"`, `"rare"`, `"epic"`, `"legendary"`, `"quality-unknown"` ([QualityID](#qualityid)) | "normal" | `"normal"` |
-### CurvedRailB
+#### Signal Configuration
-**Description:** Curved rails which connect half-diagonal rails to diagonal rails.
+Properties for configuring which signals the entity uses:
-**Prototypes:** `"curved-rail-b"`
+| Property | Type | Description |
+|----------|------|-------------|
+| `circuit_condition` | Condition (use `.enable = expr`) | The circuit condition that must be passed in order for this ... |
+| `logistic_condition` | Condition (use `.enable = expr`) | The logistic condition that must be passed in order for this... |
+| `recipe_finished_signal` | String (signal name, e.g. `"signal-A"`) | What signal to pulse when the crafting cycle completes. Only... |
+| `working_signal` | String (signal name, e.g. `"signal-A"`) | .. versionadded:: 3.0.0 (Factorio 2.0) |
-**Connection Type:** Single circuit connection
+---
-#### Settable Properties
+### Furnace
-Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = value`
+**Description:** An entity that automatically determines it's recipe from it's input items. Obviously includes regular furnaces, but can also include other machines like recyclers.
-| Property | Type | Default | Example |
-|----------|------|---------|---------|
-| `direction` | Integer ([Direction](#direction)) | 0 | `0 # NORTH` |
-| `item_requests` | List (complex) ⚠️ | (factory) | |
-| `name` | String (entity prototype name) | (factory) | |
-| `quality` | String (normal/uncommon/rare/epic/legendary) | "normal" | |
+**Draftsman Source:** [Furnace class](https://github.com/redruin1/factorio-draftsman/blob/main/draftsman/prototypes/furnace.py)
----
+**Prototypes:** `"recycler"`, `"stone-furnace"`, `"steel-furnace"`, `"electric-furnace"`
-### HalfDiagonalRail
+**Connection Type:** Single circuit connection
-**Description:** (Factorio 2.0)
+#### Reading Entity Output
-**Prototypes:** `"half-diagonal-rail"`
+Use `entity.output` to read: **Circuit network signals**
-**Connection Type:** Single circuit connection
+```facto
+Entity e = place("recycler", 0, 0);
+Bundle signals = e.output; # Returns all output signals
+```
#### Settable Properties
-Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = value`
+Set at placement: `place("name", x, y, {prop: value})`
| Property | Type | Default | Example |
|----------|------|---------|---------|
+| `circuit_enabled` | Boolean (`0` or `1`) | 0 | `1` |
+| `circuit_set_recipe` | Boolean (`0` or `1`) | 0 | `1` |
+| `connect_to_logistic_network` | Boolean (`0` or `1`) | 0 | `1` |
| `direction` | Integer ([Direction](#direction)) | 0 | `0 # NORTH` |
+| `include_in_crafting` | Boolean (`0` or `1`) | 1 | `1` |
| `item_requests` | List (complex) ⚠️ | (factory) | |
| `name` | String (entity prototype name) | (factory) | |
-| `quality` | String (normal/uncommon/rare/epic/legendary) | "normal" | |
+| `quality` | One of: `"normal"`, `"uncommon"`, `"rare"`, `"epic"`, `"legendary"`, `"quality-unknown"` ([QualityID](#qualityid)) | "normal" | `"normal"` |
+| `read_contents` | Boolean (`0` or `1`) | 0 | `1` |
+| `read_recipe_finished` | Boolean (`0` or `1`) | 0 | `1` |
+| `read_working` | Boolean (`0` or `1`) | 0 | `1` |
----
+#### Signal Configuration
-### RailRamp
+Properties for configuring which signals the entity uses:
-**Description:** (Factorio 2.0)
+| Property | Type | Description |
+|----------|------|-------------|
+| `circuit_condition` | Condition (use `.enable = expr`) | The circuit condition that must be passed in order for this ... |
+| `logistic_condition` | Condition (use `.enable = expr`) | The logistic condition that must be passed in order for this... |
+| `recipe_finished_signal` | String (signal name, e.g. `"signal-A"`) | What signal to pulse when the crafting cycle completes. Only... |
+| `working_signal` | String (signal name, e.g. `"signal-A"`) | .. versionadded:: 3.0.0 (Factorio 2.0) |
-**Prototypes:** `"rail-ramp"`, `"dummy-rail-ramp"`
+---
-**Connection Type:** Single circuit connection
+### MiningDrill
-#### Settable Properties
+**Description:** An entity that extracts resources (item or fluid) from the environment.
-Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = value`
+**Draftsman Source:** [MiningDrill class](https://github.com/redruin1/factorio-draftsman/blob/main/draftsman/prototypes/mining_drill.py)
-| Property | Type | Default | Example |
-|----------|------|---------|---------|
-| `direction` | Integer ([Direction](#direction)) | 0 | `0 # NORTH` |
-| `item_requests` | List (complex) ⚠️ | (factory) | |
-| `name` | String (entity prototype name) | (factory) | |
-| `quality` | String (normal/uncommon/rare/epic/legendary) | "normal" | |
+**Prototypes:** `"electric-mining-drill"`, `"big-mining-drill"`, `"pumpjack"`, `"burner-mining-drill"`
----
+**Connection Type:** Single circuit connection
-### RailSupport
+#### Reading Entity Output
-**Description:** (Factorio 2.0)
+Use `entity.output` to read: **Resources under the drill**
-**Prototypes:** `"dummy-rail-support"`, `"rail-support"`
+```facto
+Entity e = place("electric-mining-drill", 0, 0);
+Bundle signals = e.output; # Returns all output signals
+```
-**Connection Type:** Single circuit connection
+**Enable properties:**
+
+- `read_resources`: Read resource amounts
#### Settable Properties
-Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = value`
+Set at placement: `place("name", x, y, {prop: value})`
| Property | Type | Default | Example |
|----------|------|---------|---------|
+| `circuit_enabled` | Boolean (`0` or `1`) | 0 | `1` |
+| `connect_to_logistic_network` | Boolean (`0` or `1`) | 0 | `1` |
| `direction` | Integer ([Direction](#direction)) | 0 | `0 # NORTH` |
| `item_requests` | List (complex) ⚠️ | (factory) | |
| `name` | String (entity prototype name) | (factory) | |
-| `quality` | String (normal/uncommon/rare/epic/legendary) | "normal" | |
+| `quality` | One of: `"normal"`, `"uncommon"`, `"rare"`, `"epic"`, `"legendary"`, `"quality-unknown"` ([QualityID](#qualityid)) | "normal" | `"normal"` |
+| `read_mode` | Integer ([MiningDrillReadMode](#miningdrillreadmode)) | 0 | `0 # UNDER_DRILL` |
+| `read_resources` | Boolean (`0` or `1`) | 1 | `1` |
+
+#### Signal Configuration
+
+Properties for configuring which signals the entity uses:
+
+| Property | Type | Description |
+|----------|------|-------------|
+| `circuit_condition` | Condition (use `.enable = expr`) | The circuit condition that must be passed in order for this ... |
+| `logistic_condition` | Condition (use `.enable = expr`) | The logistic condition that must be passed in order for this... |
---
-### ElevatedStraightRail
+### Lab
-**Description:** (Factorio 2.0)
+**Description:** An entity that consumes items and produces research.
-**Prototypes:** `"dummy-elevated-straight-rail"`, `"elevated-straight-rail"`
+**Draftsman Source:** [Lab class](https://github.com/redruin1/factorio-draftsman/blob/main/draftsman/prototypes/lab.py)
+
+**Prototypes:** `"lab"`, `"biolab"`
**Connection Type:** Single circuit connection
#### Settable Properties
-Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = value`
+Set at placement: `place("name", x, y, {prop: value})`
| Property | Type | Default | Example |
|----------|------|---------|---------|
-| `direction` | Integer ([Direction](#direction)) | 0 | `0 # NORTH` |
| `item_requests` | List (complex) ⚠️ | (factory) | |
| `name` | String (entity prototype name) | (factory) | |
-| `quality` | String (normal/uncommon/rare/epic/legendary) | "normal" | |
+| `quality` | One of: `"normal"`, `"uncommon"`, `"rare"`, `"epic"`, `"legendary"`, `"quality-unknown"` ([QualityID](#qualityid)) | "normal" | `"normal"` |
---
-### ElevatedCurvedRailA
+### RocketSilo
-**Description:** (Factorio 2.0)
+**Description:** An entity that launches rockets, usually to move items between surfaces or space platforms.
-**Prototypes:** `"dummy-elevated-curved-rail-a"`, `"elevated-curved-rail-a"`
+**Draftsman Source:** [RocketSilo class](https://github.com/redruin1/factorio-draftsman/blob/main/draftsman/prototypes/rocket_silo.py)
+
+**Prototypes:** `"rocket-silo"`
**Connection Type:** Single circuit connection
+#### Reading Entity Output
+
+Use `entity.output` to read: **Circuit network signals**
+
+```facto
+Entity e = place("rocket-silo", 0, 0);
+Bundle signals = e.output; # Returns all output signals
+```
+
#### Settable Properties
-Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = value`
+Set at placement: `place("name", x, y, {prop: value})`
| Property | Type | Default | Example |
|----------|------|---------|---------|
+| `auto_launch` | Boolean (`0` or `1`) | 0 | `1` |
| `direction` | Integer ([Direction](#direction)) | 0 | `0 # NORTH` |
| `item_requests` | List (complex) ⚠️ | (factory) | |
| `name` | String (entity prototype name) | (factory) | |
-| `quality` | String (normal/uncommon/rare/epic/legendary) | "normal" | |
+| `quality` | One of: `"normal"`, `"uncommon"`, `"rare"`, `"epic"`, `"legendary"`, `"quality-unknown"` ([QualityID](#qualityid)) | "normal" | `"normal"` |
+| `read_items_mode` | Integer ([SiloReadMode](#siloreadmode)) | 1 | `0 # NONE` |
+| `recipe` | String (recipe name) | "rocket-part" | `"iron-gear-wheel"` |
+| `recipe_quality` | One of: `"normal"`, `"uncommon"`, `"rare"`, `"epic"`, `"legendary"`, `"quality-unknown"` ([QualityID](#qualityid)) | "normal" | `"normal"` |
+| `transitional_request_index` | Integer | 0 | |
+| `use_transitional_requests` | Boolean (`0` or `1`) | 0 | `1` |
---
-### ElevatedCurvedRailB
+### Beacon
-**Description:** (Factorio 2.0)
+**Description:** An entity designed to apply module effects to other machines in its radius.
-**Prototypes:** `"dummy-elevated-curved-rail-b"`, `"elevated-curved-rail-b"`
+**Draftsman Source:** [Beacon class](https://github.com/redruin1/factorio-draftsman/blob/main/draftsman/prototypes/beacon.py)
+
+**Prototypes:** `"beacon"`
**Connection Type:** Single circuit connection
#### Settable Properties
-Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = value`
+Set at placement: `place("name", x, y, {prop: value})`
| Property | Type | Default | Example |
|----------|------|---------|---------|
-| `direction` | Integer ([Direction](#direction)) | 0 | `0 # NORTH` |
| `item_requests` | List (complex) ⚠️ | (factory) | |
| `name` | String (entity prototype name) | (factory) | |
-| `quality` | String (normal/uncommon/rare/epic/legendary) | "normal" | |
+| `quality` | One of: `"normal"`, `"uncommon"`, `"rare"`, `"epic"`, `"legendary"`, `"quality-unknown"` ([QualityID](#qualityid)) | "normal" | `"normal"` |
---
-### ElevatedHalfDiagonalRail
+### Boiler
-**Description:** (Factorio 2.0)
+**Description:** An entity that uses a fuel to convert a fluid (usually water) to another fluid (usually steam).
-**Prototypes:** `"dummy-elevated-half-diagonal-rail"`, `"elevated-half-diagonal-rail"`
+**Draftsman Source:** [Boiler class](https://github.com/redruin1/factorio-draftsman/blob/main/draftsman/prototypes/boiler.py)
+
+**Prototypes:** `"boiler"`, `"heat-exchanger"`
**Connection Type:** Single circuit connection
#### Settable Properties
-Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = value`
+Set at placement: `place("name", x, y, {prop: value})`
| Property | Type | Default | Example |
|----------|------|---------|---------|
| `direction` | Integer ([Direction](#direction)) | 0 | `0 # NORTH` |
| `item_requests` | List (complex) ⚠️ | (factory) | |
| `name` | String (entity prototype name) | (factory) | |
-| `quality` | String (normal/uncommon/rare/epic/legendary) | "normal" | |
+| `quality` | One of: `"normal"`, `"uncommon"`, `"rare"`, `"epic"`, `"legendary"`, `"quality-unknown"` ([QualityID](#qualityid)) | "normal" | `"normal"` |
---
-### LegacyStraightRail
+### Generator
-**Description:** An old, Factorio 1.0 straight rail entity.
+**Description:** An entity that converts a fluid (usually steam) to electricity.
-**Prototypes:** `"legacy-straight-rail"`
+**Draftsman Source:** [Generator class](https://github.com/redruin1/factorio-draftsman/blob/main/draftsman/prototypes/generator.py)
+
+**Prototypes:** `"steam-engine"`, `"steam-turbine"`
**Connection Type:** Single circuit connection
#### Settable Properties
-Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = value`
+Set at placement: `place("name", x, y, {prop: value})`
| Property | Type | Default | Example |
|----------|------|---------|---------|
| `direction` | Integer ([Direction](#direction)) | 0 | `0 # NORTH` |
| `item_requests` | List (complex) ⚠️ | (factory) | |
| `name` | String (entity prototype name) | (factory) | |
-| `quality` | String (normal/uncommon/rare/epic/legendary) | "normal" | |
+| `quality` | One of: `"normal"`, `"uncommon"`, `"rare"`, `"epic"`, `"legendary"`, `"quality-unknown"` ([QualityID](#qualityid)) | "normal" | `"normal"` |
---
-### LegacyCurvedRail
+### Reactor
-**Description:** An old, Factorio 1.0 curved rail entity.
+**Description:** An entity that converts a fuel into thermal energy.
-**Prototypes:** `"legacy-curved-rail"`
+**Draftsman Source:** [Reactor class](https://github.com/redruin1/factorio-draftsman/blob/main/draftsman/prototypes/reactor.py)
+
+**Prototypes:** `"heating-tower"`, `"nuclear-reactor"`
**Connection Type:** Single circuit connection
+#### Reading Entity Output
+
+Use `entity.output` to read: **Circuit network signals**
+
+```facto
+Entity e = place("heating-tower", 0, 0);
+Bundle signals = e.output; # Returns all output signals
+```
+
#### Settable Properties
-Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = value`
+Set at placement: `place("name", x, y, {prop: value})`
| Property | Type | Default | Example |
|----------|------|---------|---------|
-| `direction` | Integer ([Direction](#direction)) | 0 | `0 # NORTH` |
| `item_requests` | List (complex) ⚠️ | (factory) | |
| `name` | String (entity prototype name) | (factory) | |
-| `quality` | String (normal/uncommon/rare/epic/legendary) | "normal" | |
+| `quality` | One of: `"normal"`, `"uncommon"`, `"rare"`, `"epic"`, `"legendary"`, `"quality-unknown"` ([QualityID](#qualityid)) | "normal" | `"normal"` |
+| `read_burner_fuel` | Boolean (`0` or `1`) | 0 | `1` |
+| `read_temperature` | Boolean (`0` or `1`) | 0 | `1` |
+| `temperature_signal` | String (signal name, e.g. `"signal-A"`) | (factory) | `"signal-A"` |
---
-## Production
+## Storage
-### AssemblingMachine
+### Container
-**Description:** A machine that takes input items and produces output items. Includes assembling machines, chemical plants, oil refineries, and centrifuges, but does not include :py:class:`.RocketSilo`.
+**Description:** An entity that holds items.
+
+**Draftsman Source:** [Container class](https://github.com/redruin1/factorio-draftsman/blob/main/draftsman/prototypes/container.py)
-**Prototypes:** `"centrifuge"`, `"biochamber"`, `"oil-refinery"`, `"foundry"`, `"captive-biter-spawner"`, `"assembling-machine-1"`, `"electromagnetic-plant"`, `"crusher"`, `"cryogenic-plant"`, `"assembling-machine-2"`, `"assembling-machine-3"`, `"chemical-plant"`
+**Prototypes:** `"factorio-logo-22tiles"`, `"bottomless-chest"`, `"red-chest"`, `"crash-site-chest-2"`, `"factorio-logo-16tiles"`, `"iron-chest"`, `"blue-chest"`, `"crash-site-chest-1"`, `"steel-chest"`, `"factorio-logo-11tiles"`, ... (11 total)
**Connection Type:** Single circuit connection
-#### Circuit Signal I/O
+#### Reading Entity Output
-**Signal Inputs:**
+Use `entity.output` to read: **Item contents of the container**
-| Signal Property | Signal Type | Description | Enable With |
-|-----------------|-------------|-------------|-------------|
-| `circuit_condition` | Any signal | Signal used in enable/disable condition | `circuit_enabled: 1` |
-| `logistic_condition` | Any signal | Logistic network signal for enable/disable | `connect_to_logistic_network: 1` |
+```facto
+Entity e = place("factorio-logo-22tiles", 0, 0);
+Bundle signals = e.output; # Returns all output signals
+```
-**Signal Outputs:**
+**Enable properties:**
-| Signal Property | Signal Type | Description | Enable With |
-|-----------------|-------------|-------------|-------------|
-| `recipe_finished_signal` | Pulse (1) | Pulses when recipe completes | `read_recipe_finished: 1` |
-| `working_signal` | 1 when working | Outputs 1 while machine is crafting | `read_working: 1` |
+- `read_contents`: Enable reading container contents
#### DSL Examples
```facto
-# Assembler controlled by circuit
-Entity assembler = place("assembling-machine-1", 0, 0, {recipe: "iron-gear-wheel"});
-assembler.enable = iron_count > 100;
+# Read chest contents
+Entity chest = place("iron-chest", 0, 0);
+Bundle contents = chest.output;
+Signal iron = contents["iron-plate"];
```
#### Settable Properties
-Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = value`
+Set at placement: `place("name", x, y, {prop: value})`
| Property | Type | Default | Example |
|----------|------|---------|---------|
-| `circuit_enabled` | Boolean (0/1) | false | `1` |
-| `circuit_set_recipe` | Boolean (0/1) | false | `1` |
-| `connect_to_logistic_network` | Boolean (0/1) | false | `1` |
-| `direction` | Integer ([Direction](#direction)) | 0 | `0 # NORTH` |
-| `include_in_crafting` | Boolean (0/1) | true | `1` |
+| `bar` | Integer | None | |
| `item_requests` | List (complex) ⚠️ | (factory) | |
| `name` | String (entity prototype name) | (factory) | |
-| `quality` | String (normal/uncommon/rare/epic/legendary) | "normal" | |
-| `read_contents` | Boolean (0/1) | false | `1` |
-| `read_recipe_finished` | Boolean (0/1) | false | `1` |
-| `read_working` | Boolean (0/1) | false | `1` |
-| `recipe` | String (recipe name) | None | `"iron-gear-wheel"` |
-| `recipe_quality` | String (normal/uncommon/rare/epic/legendary) | "normal" | |
+| `quality` | One of: `"normal"`, `"uncommon"`, `"rare"`, `"epic"`, `"legendary"`, `"quality-unknown"` ([QualityID](#qualityid)) | "normal" | `"normal"` |
---
-### Furnace
+### LogisticPassiveContainer
-**Description:** An entity that automatically determines it's recipe from it's input items. Obviously includes regular furnaces, but can also include other machines like recyclers.
+**Description:** A logistics container that provides it's contents to the logistic network when needed by the network.
-**Prototypes:** `"recycler"`, `"stone-furnace"`, `"steel-furnace"`, `"electric-furnace"`
+**Draftsman Source:** [LogisticPassiveContainer class](https://github.com/redruin1/factorio-draftsman/blob/main/draftsman/prototypes/logistic_passive_container.py)
-**Connection Type:** Single circuit connection
-
-#### Circuit Signal I/O
+**Prototypes:** `"passive-provider-chest"`
-**Signal Inputs:**
+**Connection Type:** Single circuit connection
-| Signal Property | Signal Type | Description | Enable With |
-|-----------------|-------------|-------------|-------------|
-| `circuit_condition` | Any signal | Signal used in enable/disable condition | `circuit_enabled: 1` |
-| `logistic_condition` | Any signal | Logistic network signal for enable/disable | `connect_to_logistic_network: 1` |
+#### Reading Entity Output
-**Signal Outputs:**
+Use `entity.output` to read: **Circuit network signals**
-| Signal Property | Signal Type | Description | Enable With |
-|-----------------|-------------|-------------|-------------|
-| `recipe_finished_signal` | Pulse (1) | Pulses when recipe completes | `read_recipe_finished: 1` |
-| `working_signal` | 1 when working | Outputs 1 while machine is crafting | `read_working: 1` |
+```facto
+Entity e = place("passive-provider-chest", 0, 0);
+Bundle signals = e.output; # Returns all output signals
+```
#### Settable Properties
-Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = value`
+Set at placement: `place("name", x, y, {prop: value})`
| Property | Type | Default | Example |
|----------|------|---------|---------|
-| `circuit_enabled` | Boolean (0/1) | false | `1` |
-| `circuit_set_recipe` | Boolean (0/1) | false | `1` |
-| `connect_to_logistic_network` | Boolean (0/1) | false | `1` |
-| `direction` | Integer ([Direction](#direction)) | 0 | `0 # NORTH` |
-| `include_in_crafting` | Boolean (0/1) | true | `1` |
+| `bar` | Integer | None | |
+| `circuit_enabled` | Boolean (`0` or `1`) | 0 | `1` |
| `item_requests` | List (complex) ⚠️ | (factory) | |
| `name` | String (entity prototype name) | (factory) | |
-| `quality` | String (normal/uncommon/rare/epic/legendary) | "normal" | |
-| `read_contents` | Boolean (0/1) | false | `1` |
-| `read_recipe_finished` | Boolean (0/1) | false | `1` |
-| `read_working` | Boolean (0/1) | false | `1` |
+| `quality` | One of: `"normal"`, `"uncommon"`, `"rare"`, `"epic"`, `"legendary"`, `"quality-unknown"` ([QualityID](#qualityid)) | "normal" | `"normal"` |
+| `read_contents` | Boolean (`0` or `1`) | 1 | `1` |
+
+#### Signal Configuration
+
+Properties for configuring which signals the entity uses:
+
+| Property | Type | Description |
+|----------|------|-------------|
+| `circuit_condition` | Condition (use `.enable = expr`) | The circuit condition that must be passed in order for this ... |
---
-### MiningDrill
+### LogisticActiveContainer
-**Description:** An entity that extracts resources (item or fluid) from the environment.
+**Description:** A logistics container that immediately provides it's contents to the logistic network.
-**Prototypes:** `"electric-mining-drill"`, `"big-mining-drill"`, `"pumpjack"`, `"burner-mining-drill"`
+**Draftsman Source:** [LogisticActiveContainer class](https://github.com/redruin1/factorio-draftsman/blob/main/draftsman/prototypes/logistic_active_container.py)
+
+**Prototypes:** `"active-provider-chest"`
**Connection Type:** Single circuit connection
-#### Circuit Signal I/O
+#### Reading Entity Output
+
+Use `entity.output` to read: **Circuit network signals**
+
+```facto
+Entity e = place("active-provider-chest", 0, 0);
+Bundle signals = e.output; # Returns all output signals
+```
+
+#### Settable Properties
+
+Set at placement: `place("name", x, y, {prop: value})`
+
+| Property | Type | Default | Example |
+|----------|------|---------|---------|
+| `bar` | Integer | None | |
+| `item_requests` | List (complex) ⚠️ | (factory) | |
+| `name` | String (entity prototype name) | (factory) | |
+| `quality` | One of: `"normal"`, `"uncommon"`, `"rare"`, `"epic"`, `"legendary"`, `"quality-unknown"` ([QualityID](#qualityid)) | "normal" | `"normal"` |
+| `read_contents` | Boolean (`0` or `1`) | 1 | `1` |
+
+#### Signal Configuration
+
+Properties for configuring which signals the entity uses:
+
+| Property | Type | Description |
+|----------|------|-------------|
+| `circuit_condition` | Condition (use `.enable = expr`) | The circuit condition that must be passed in order for this ... |
+
+---
+
+### LogisticStorageContainer
+
+**Description:** A logistics container that stores items not currently being used in the logistic network.
+
+**Draftsman Source:** [LogisticStorageContainer class](https://github.com/redruin1/factorio-draftsman/blob/main/draftsman/prototypes/logistic_storage_container.py)
+
+**Prototypes:** `"storage-chest"`
-**Signal Inputs:**
+**Connection Type:** Single circuit connection
-| Signal Property | Signal Type | Description | Enable With |
-|-----------------|-------------|-------------|-------------|
-| `circuit_condition` | Any signal | Signal used in enable/disable condition | `circuit_enabled: 1` |
-| `logistic_condition` | Any signal | Logistic network signal for enable/disable | `connect_to_logistic_network: 1` |
+#### Reading Entity Output
-**Content Outputs:**
+Use `entity.output` to read: **Circuit network signals**
-- Resource amounts (resources under entity, enable with `read_resources: 1`)
+```facto
+Entity e = place("storage-chest", 0, 0);
+Bundle signals = e.output; # Returns all output signals
+```
#### Settable Properties
-Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = value`
+Set at placement: `place("name", x, y, {prop: value})`
| Property | Type | Default | Example |
|----------|------|---------|---------|
-| `circuit_enabled` | Boolean (0/1) | false | `1` |
-| `connect_to_logistic_network` | Boolean (0/1) | false | `1` |
-| `direction` | Integer ([Direction](#direction)) | 0 | `0 # NORTH` |
+| `bar` | Integer | None | |
+| `circuit_enabled` | Boolean (`0` or `1`) | 0 | `1` |
| `item_requests` | List (complex) ⚠️ | (factory) | |
| `name` | String (entity prototype name) | (factory) | |
-| `quality` | String (normal/uncommon/rare/epic/legendary) | "normal" | |
-| `read_mode` | Integer ([MiningDrillReadMode](#miningdrillreadmode)) | 0 | `0 # UNDER_DRILL` |
-| `read_resources` | Boolean (0/1) | true | `1` |
+| `quality` | One of: `"normal"`, `"uncommon"`, `"rare"`, `"epic"`, `"legendary"`, `"quality-unknown"` ([QualityID](#qualityid)) | "normal" | `"normal"` |
+| `read_contents` | Boolean (`0` or `1`) | 1 | `1` |
+| `request_from_buffers` | Boolean (`0` or `1`) | 1 | `1` |
+| `requests_enabled` | Boolean (`0` or `1`) | 1 | `1` |
+| `sections` | List (complex) ⚠️ | (factory) | |
+| `trash_not_requested` | Boolean (`0` or `1`) | 0 | `1` |
+
+#### Signal Configuration
+
+Properties for configuring which signals the entity uses:
+
+| Property | Type | Description |
+|----------|------|-------------|
+| `circuit_condition` | Condition (use `.enable = expr`) | The circuit condition that must be passed in order for this ... |
---
-### Lab
+### LogisticRequestContainer
-**Description:** An entity that consumes items and produces research.
+**Description:** A logistics container that requests items with a primary priority.
-**Prototypes:** `"lab"`, `"biolab"`
+**Draftsman Source:** [LogisticRequestContainer class](https://github.com/redruin1/factorio-draftsman/blob/main/draftsman/prototypes/logistic_request_container.py)
+
+**Prototypes:** `"requester-chest"`
**Connection Type:** Single circuit connection
+#### Reading Entity Output
+
+Use `entity.output` to read: **Circuit network signals**
+
+```facto
+Entity e = place("requester-chest", 0, 0);
+Bundle signals = e.output; # Returns all output signals
+```
+
#### Settable Properties
-Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = value`
+Set at placement: `place("name", x, y, {prop: value})`
| Property | Type | Default | Example |
|----------|------|---------|---------|
+| `bar` | Integer | None | |
+| `circuit_enabled` | Boolean (`0` or `1`) | 0 | `1` |
| `item_requests` | List (complex) ⚠️ | (factory) | |
+| `mode_of_operation` | Integer ([LogisticModeOfOperation](#logisticmodeofoperation)) | 0 | `0 # SEND_CONTENTS` |
| `name` | String (entity prototype name) | (factory) | |
-| `quality` | String (normal/uncommon/rare/epic/legendary) | "normal" | |
+| `quality` | One of: `"normal"`, `"uncommon"`, `"rare"`, `"epic"`, `"legendary"`, `"quality-unknown"` ([QualityID](#qualityid)) | "normal" | `"normal"` |
+| `request_from_buffers` | Boolean (`0` or `1`) | 0 | `1` |
+| `requests_enabled` | Boolean (`0` or `1`) | 1 | `1` |
+| `sections` | List (complex) ⚠️ | (factory) | |
+| `trash_not_requested` | Boolean (`0` or `1`) | 0 | `1` |
+
+#### Signal Configuration
+
+Properties for configuring which signals the entity uses:
+
+| Property | Type | Description |
+|----------|------|-------------|
+| `circuit_condition` | Condition (use `.enable = expr`) | The circuit condition that must be passed in order for this ... |
---
-### RocketSilo
+### LogisticBufferContainer
-**Description:** An entity that launches rockets, usually to move items between surfaces or space platforms.
+**Description:** A logistics container that requests items on a secondary priority.
-**Prototypes:** `"rocket-silo"`
+**Draftsman Source:** [LogisticBufferContainer class](https://github.com/redruin1/factorio-draftsman/blob/main/draftsman/prototypes/logistic_buffer_container.py)
+
+**Prototypes:** `"buffer-chest"`
**Connection Type:** Single circuit connection
+#### Reading Entity Output
+
+Use `entity.output` to read: **Circuit network signals**
+
+```facto
+Entity e = place("buffer-chest", 0, 0);
+Bundle signals = e.output; # Returns all output signals
+```
+
#### Settable Properties
-Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = value`
+Set at placement: `place("name", x, y, {prop: value})`
| Property | Type | Default | Example |
|----------|------|---------|---------|
-| `auto_launch` | Boolean (0/1) | false | `1` |
-| `direction` | Integer ([Direction](#direction)) | 0 | `0 # NORTH` |
+| `bar` | Integer | None | |
+| `circuit_enabled` | Boolean (`0` or `1`) | 0 | `1` |
| `item_requests` | List (complex) ⚠️ | (factory) | |
+| `mode_of_operation` | Integer ([LogisticModeOfOperation](#logisticmodeofoperation)) | 0 | `0 # SEND_CONTENTS` |
| `name` | String (entity prototype name) | (factory) | |
-| `quality` | String (normal/uncommon/rare/epic/legendary) | "normal" | |
-| `read_items_mode` | Integer ([SiloReadMode](#siloreadmode)) | 1 | `0 # NONE` |
-| `recipe` | String (recipe name) | "rocket-part" | `"iron-gear-wheel"` |
-| `recipe_quality` | String (normal/uncommon/rare/epic/legendary) | "normal" | |
-| `transitional_request_index` | Integer | 0 | |
-| `use_transitional_requests` | Boolean (0/1) | false | `1` |
+| `quality` | One of: `"normal"`, `"uncommon"`, `"rare"`, `"epic"`, `"legendary"`, `"quality-unknown"` ([QualityID](#qualityid)) | "normal" | `"normal"` |
+| `request_from_buffers` | Boolean (`0` or `1`) | 1 | `1` |
+| `requests_enabled` | Boolean (`0` or `1`) | 1 | `1` |
+| `sections` | List (complex) ⚠️ | (factory) | |
+| `trash_not_requested` | Boolean (`0` or `1`) | 0 | `1` |
+
+#### Signal Configuration
+
+Properties for configuring which signals the entity uses:
+
+| Property | Type | Description |
+|----------|------|-------------|
+| `circuit_condition` | Condition (use `.enable = expr`) | The circuit condition that must be passed in order for this ... |
---
-### Beacon
+## Power
-**Description:** An entity designed to apply module effects to other machines in its radius.
+### ElectricPole
-**Prototypes:** `"beacon"`
+**Description:** An entity used to distribute electrical energy as a network.
+
+**Draftsman Source:** [ElectricPole class](https://github.com/redruin1/factorio-draftsman/blob/main/draftsman/prototypes/electric_pole.py)
+
+**Prototypes:** `"substation"`, `"medium-electric-pole"`, `"big-electric-pole"`, `"small-electric-pole"`
**Connection Type:** Single circuit connection
+#### Reading Entity Output
+
+Use `entity.output` to read: **Circuit network signals**
+
+```facto
+Entity e = place("substation", 0, 0);
+Bundle signals = e.output; # Returns all output signals
+```
+
#### Settable Properties
-Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = value`
+Set at placement: `place("name", x, y, {prop: value})`
| Property | Type | Default | Example |
|----------|------|---------|---------|
| `item_requests` | List (complex) ⚠️ | (factory) | |
| `name` | String (entity prototype name) | (factory) | |
-| `quality` | String (normal/uncommon/rare/epic/legendary) | "normal" | |
+| `quality` | One of: `"normal"`, `"uncommon"`, `"rare"`, `"epic"`, `"legendary"`, `"quality-unknown"` ([QualityID](#qualityid)) | "normal" | `"normal"` |
---
-### Boiler
+### PowerSwitch
-**Description:** An entity that uses a fuel to convert a fluid (usually water) to another fluid (usually steam).
+**Description:** An entity that connects or disconnects a power network.
-**Prototypes:** `"boiler"`, `"heat-exchanger"`
+**Draftsman Source:** [PowerSwitch class](https://github.com/redruin1/factorio-draftsman/blob/main/draftsman/prototypes/power_switch.py)
+
+**Prototypes:** `"power-switch"`
**Connection Type:** Single circuit connection
+#### Reading Entity Output
+
+Use `entity.output` to read: **Circuit network signals**
+
+```facto
+Entity e = place("power-switch", 0, 0);
+Bundle signals = e.output; # Returns all output signals
+```
+
#### Settable Properties
-Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = value`
+Set at placement: `place("name", x, y, {prop: value})`
| Property | Type | Default | Example |
|----------|------|---------|---------|
-| `direction` | Integer ([Direction](#direction)) | 0 | `0 # NORTH` |
+| `connect_to_logistic_network` | Boolean (`0` or `1`) | 0 | `1` |
| `item_requests` | List (complex) ⚠️ | (factory) | |
| `name` | String (entity prototype name) | (factory) | |
-| `quality` | String (normal/uncommon/rare/epic/legendary) | "normal" | |
+| `quality` | One of: `"normal"`, `"uncommon"`, `"rare"`, `"epic"`, `"legendary"`, `"quality-unknown"` ([QualityID](#qualityid)) | "normal" | `"normal"` |
+| `switch_state` | Boolean (`0` or `1`) | 0 | `1` |
+
+#### Signal Configuration
+
+Properties for configuring which signals the entity uses:
+
+| Property | Type | Description |
+|----------|------|-------------|
+| `circuit_condition` | Condition (use `.enable = expr`) | The circuit condition that must be passed in order for this ... |
+| `logistic_condition` | Condition (use `.enable = expr`) | The logistic condition that must be passed in order for this... |
---
-### Generator
+### Accumulator
-**Description:** An entity that converts a fluid (usually steam) to electricity.
+**Description:** An entity that stores electricity for periods of high demand.
-**Prototypes:** `"steam-engine"`, `"steam-turbine"`
+**Draftsman Source:** [Accumulator class](https://github.com/redruin1/factorio-draftsman/blob/main/draftsman/prototypes/accumulator.py)
+
+**Prototypes:** `"accumulator"`
**Connection Type:** Single circuit connection
+#### Reading Entity Output
+
+Use `entity.output` to read: **Charge level percentage**
+
+```facto
+Entity e = place("accumulator", 0, 0);
+Bundle signals = e.output; # Returns all output signals
+```
+
#### Settable Properties
-Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = value`
+Set at placement: `place("name", x, y, {prop: value})`
| Property | Type | Default | Example |
|----------|------|---------|---------|
-| `direction` | Integer ([Direction](#direction)) | 0 | `0 # NORTH` |
| `item_requests` | List (complex) ⚠️ | (factory) | |
| `name` | String (entity prototype name) | (factory) | |
-| `quality` | String (normal/uncommon/rare/epic/legendary) | "normal" | |
+| `quality` | One of: `"normal"`, `"uncommon"`, `"rare"`, `"epic"`, `"legendary"`, `"quality-unknown"` ([QualityID](#qualityid)) | "normal" | `"normal"` |
+
+#### Signal Configuration
+
+Properties for configuring which signals the entity uses:
+
+| Property | Type | Description |
+|----------|------|-------------|
+| `output_signal` | String (signal name, e.g. `"signal-A"`) | The signal used to output this accumulator's charge level, i... |
---
-### BurnerGenerator
+### SolarPanel
-**Description:** A electrical generator that turns burnable fuel directly into electrical energy.
+**Description:** An entity that produces electricity depending on the presence of the sun.
-**Prototypes:** `"burner-generator"`
+**Draftsman Source:** [SolarPanel class](https://github.com/redruin1/factorio-draftsman/blob/main/draftsman/prototypes/solar_panel.py)
+
+**Prototypes:** `"solar-panel"`
**Connection Type:** Single circuit connection
#### Settable Properties
-Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = value`
+Set at placement: `place("name", x, y, {prop: value})`
| Property | Type | Default | Example |
|----------|------|---------|---------|
-| `direction` | Integer ([Direction](#direction)) | 0 | `0 # NORTH` |
| `item_requests` | List (complex) ⚠️ | (factory) | |
| `name` | String (entity prototype name) | (factory) | |
-| `quality` | String (normal/uncommon/rare/epic/legendary) | "normal" | |
+| `quality` | One of: `"normal"`, `"uncommon"`, `"rare"`, `"epic"`, `"legendary"`, `"quality-unknown"` ([QualityID](#qualityid)) | "normal" | `"normal"` |
---
-### Reactor
+## Fluids
-**Description:** An entity that converts a fuel into thermal energy.
+### Pump
-**Prototypes:** `"heating-tower"`, `"nuclear-reactor"`
+**Description:** An entity that aids fluid transfer through pipes.
+
+**Draftsman Source:** [Pump class](https://github.com/redruin1/factorio-draftsman/blob/main/draftsman/prototypes/pump.py)
+
+**Prototypes:** `"pump"`
**Connection Type:** Single circuit connection
+#### Reading Entity Output
+
+Use `entity.output` to read: **Circuit network signals**
+
+```facto
+Entity e = place("pump", 0, 0);
+Bundle signals = e.output; # Returns all output signals
+```
+
#### Settable Properties
-Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = value`
+Set at placement: `place("name", x, y, {prop: value})`
| Property | Type | Default | Example |
|----------|------|---------|---------|
+| `connect_to_logistic_network` | Boolean (`0` or `1`) | 0 | `1` |
+| `direction` | Integer ([Direction](#direction)) | 0 | `0 # NORTH` |
| `item_requests` | List (complex) ⚠️ | (factory) | |
| `name` | String (entity prototype name) | (factory) | |
-| `quality` | String (normal/uncommon/rare/epic/legendary) | "normal" | |
-| `read_burner_fuel` | Boolean (0/1) | false | `1` |
-| `read_temperature` | Boolean (0/1) | false | `1` |
-| `temperature_signal` | String (signal name) | (factory) | `"signal-A"` |
+| `quality` | One of: `"normal"`, `"uncommon"`, `"rare"`, `"epic"`, `"legendary"`, `"quality-unknown"` ([QualityID](#qualityid)) | "normal" | `"normal"` |
+
+#### Signal Configuration
+
+Properties for configuring which signals the entity uses:
+
+| Property | Type | Description |
+|----------|------|-------------|
+| `circuit_condition` | Condition (use `.enable = expr`) | The circuit condition that must be passed in order for this ... |
+| `logistic_condition` | Condition (use `.enable = expr`) | The logistic condition that must be passed in order for this... |
---
-### FusionReactor
+### StorageTank
-**Description:** (Factorio 2.0)
+**Description:** An entity that stores a fluid.
-**Prototypes:** `"fusion-reactor"`
+**Draftsman Source:** [StorageTank class](https://github.com/redruin1/factorio-draftsman/blob/main/draftsman/prototypes/storage_tank.py)
+
+**Prototypes:** `"storage-tank"`
**Connection Type:** Single circuit connection
+#### Reading Entity Output
+
+Use `entity.output` to read: **Fluid level in the tank**
+
+```facto
+Entity e = place("storage-tank", 0, 0);
+Bundle signals = e.output; # Returns all output signals
+```
+
#### Settable Properties
-Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = value`
+Set at placement: `place("name", x, y, {prop: value})`
| Property | Type | Default | Example |
|----------|------|---------|---------|
| `direction` | Integer ([Direction](#direction)) | 0 | `0 # NORTH` |
| `item_requests` | List (complex) ⚠️ | (factory) | |
| `name` | String (entity prototype name) | (factory) | |
-| `quality` | String (normal/uncommon/rare/epic/legendary) | "normal" | |
+| `quality` | One of: `"normal"`, `"uncommon"`, `"rare"`, `"epic"`, `"legendary"`, `"quality-unknown"` ([QualityID](#qualityid)) | "normal" | `"normal"` |
---
-### FusionGenerator
+### OffshorePump
-**Description:** (Factorio 2.0)
+**Description:** An entity that pumps a fluid from the environment.
-**Prototypes:** `"fusion-generator"`
+**Draftsman Source:** [OffshorePump class](https://github.com/redruin1/factorio-draftsman/blob/main/draftsman/prototypes/offshore_pump.py)
+
+**Prototypes:** `"offshore-pump"`
**Connection Type:** Single circuit connection
+#### Reading Entity Output
+
+Use `entity.output` to read: **Circuit network signals**
+
+```facto
+Entity e = place("offshore-pump", 0, 0);
+Bundle signals = e.output; # Returns all output signals
+```
+
#### Settable Properties
-Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = value`
+Set at placement: `place("name", x, y, {prop: value})`
| Property | Type | Default | Example |
|----------|------|---------|---------|
+| `connect_to_logistic_network` | Boolean (`0` or `1`) | 0 | `1` |
| `direction` | Integer ([Direction](#direction)) | 0 | `0 # NORTH` |
| `item_requests` | List (complex) ⚠️ | (factory) | |
| `name` | String (entity prototype name) | (factory) | |
-| `quality` | String (normal/uncommon/rare/epic/legendary) | "normal" | |
+| `quality` | One of: `"normal"`, `"uncommon"`, `"rare"`, `"epic"`, `"legendary"`, `"quality-unknown"` ([QualityID](#qualityid)) | "normal" | `"normal"` |
+
+#### Signal Configuration
+
+Properties for configuring which signals the entity uses:
+
+| Property | Type | Description |
+|----------|------|-------------|
+| `circuit_condition` | Condition (use `.enable = expr`) | The circuit condition that must be passed in order for this ... |
+| `logistic_condition` | Condition (use `.enable = expr`) | The logistic condition that must be passed in order for this... |
---
-### LightningAttractor
+### Pipe
-**Description:** (Factorio 2.0)
+**Description:** A structure that transports a fluid across a surface.
-**Prototypes:** `"lightning-rod"`, `"fulgoran-ruin-attractor"`, `"lightning-collector"`
+**Draftsman Source:** [Pipe class](https://github.com/redruin1/factorio-draftsman/blob/main/draftsman/prototypes/pipe.py)
+
+**Prototypes:** `"pipe"`
**Connection Type:** Single circuit connection
#### Settable Properties
-Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = value`
+Set at placement: `place("name", x, y, {prop: value})`
| Property | Type | Default | Example |
|----------|------|---------|---------|
| `item_requests` | List (complex) ⚠️ | (factory) | |
| `name` | String (entity prototype name) | (factory) | |
-| `quality` | String (normal/uncommon/rare/epic/legendary) | "normal" | |
+| `quality` | One of: `"normal"`, `"uncommon"`, `"rare"`, `"epic"`, `"legendary"`, `"quality-unknown"` ([QualityID](#qualityid)) | "normal" | `"normal"` |
---
-### AgriculturalTower
+## Combat
-**Description:** (Factorio 2.0)
+### Radar
-**Prototypes:** `"agricultural-tower"`
+**Description:** An entity that reveals and scans neighbouring chunks.
+
+**Draftsman Source:** [Radar class](https://github.com/redruin1/factorio-draftsman/blob/main/draftsman/prototypes/radar.py)
+
+**Prototypes:** `"radar"`
**Connection Type:** Single circuit connection
-#### Circuit Signal I/O
+#### Reading Entity Output
-**Signal Inputs:**
+Use `entity.output` to read: **Circuit network signals**
-| Signal Property | Signal Type | Description | Enable With |
-|-----------------|-------------|-------------|-------------|
-| `circuit_condition` | Any signal | Signal used in enable/disable condition | `circuit_enabled: 1` |
-| `logistic_condition` | Any signal | Logistic network signal for enable/disable | `connect_to_logistic_network: 1` |
+```facto
+Entity e = place("radar", 0, 0);
+Bundle signals = e.output; # Returns all output signals
+```
#### Settable Properties
-Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = value`
+Set at placement: `place("name", x, y, {prop: value})`
| Property | Type | Default | Example |
|----------|------|---------|---------|
-| `circuit_enabled` | Boolean (0/1) | false | `1` |
-| `connect_to_logistic_network` | Boolean (0/1) | false | `1` |
| `item_requests` | List (complex) ⚠️ | (factory) | |
| `name` | String (entity prototype name) | (factory) | |
-| `quality` | String (normal/uncommon/rare/epic/legendary) | "normal" | |
-| `read_contents` | Boolean (0/1) | false | `1` |
+| `quality` | One of: `"normal"`, `"uncommon"`, `"rare"`, `"epic"`, `"legendary"`, `"quality-unknown"` ([QualityID](#qualityid)) | "normal" | `"normal"` |
---
-## Storage
+### ArtilleryTurret
-### Container
+**Description:** A turret which can only target enemy structures and uses artillery ammunition.
-**Description:** An entity that holds items.
+**Draftsman Source:** [ArtilleryTurret class](https://github.com/redruin1/factorio-draftsman/blob/main/draftsman/prototypes/artillery_turret.py)
-**Prototypes:** `"factorio-logo-22tiles"`, `"bottomless-chest"`, `"red-chest"`, `"crash-site-chest-2"`, `"factorio-logo-16tiles"`, `"iron-chest"`, `"blue-chest"`, `"crash-site-chest-1"`, `"steel-chest"`, `"factorio-logo-11tiles"`, `"wooden-chest"`
+**Prototypes:** `"artillery-turret"`
**Connection Type:** Single circuit connection
+#### Reading Entity Output
+
+Use `entity.output` to read: **Circuit network signals**
+
+```facto
+Entity e = place("artillery-turret", 0, 0);
+Bundle signals = e.output; # Returns all output signals
+```
+
#### Settable Properties
-Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = value`
+Set at placement: `place("name", x, y, {prop: value})`
| Property | Type | Default | Example |
|----------|------|---------|---------|
-| `bar` | Integer | None | |
+| `auto_target` | Boolean (`0` or `1`) | 1 | `1` |
+| `circuit_enabled` | Boolean (`0` or `1`) | 0 | `1` |
+| `connect_to_logistic_network` | Boolean (`0` or `1`) | 0 | `1` |
+| `direction` | Integer ([Direction](#direction)) | 0 | `0 # NORTH` |
| `item_requests` | List (complex) ⚠️ | (factory) | |
| `name` | String (entity prototype name) | (factory) | |
-| `quality` | String (normal/uncommon/rare/epic/legendary) | "normal" | |
+| `quality` | One of: `"normal"`, `"uncommon"`, `"rare"`, `"epic"`, `"legendary"`, `"quality-unknown"` ([QualityID](#qualityid)) | "normal" | `"normal"` |
+| `read_ammo` | Boolean (`0` or `1`) | 1 | `1` |
+
+#### Signal Configuration
+
+Properties for configuring which signals the entity uses:
+
+| Property | Type | Description |
+|----------|------|-------------|
+| `circuit_condition` | Condition (use `.enable = expr`) | The circuit condition that must be passed in order for this ... |
+| `logistic_condition` | Condition (use `.enable = expr`) | The logistic condition that must be passed in order for this... |
---
-### LogisticPassiveContainer
+### AmmoTurret
-**Description:** A logistics container that provides it's contents to the logistic network when needed by the network.
+**Description:** An entity that automatically targets and attacks other forces within range. Consumes item-based ammunition.
-**Prototypes:** `"passive-provider-chest"`
+**Draftsman Source:** [AmmoTurret class](https://github.com/redruin1/factorio-draftsman/blob/main/draftsman/prototypes/ammo_turret.py)
+
+**Prototypes:** `"rocket-turret"`, `"gun-turret"`, `"railgun-turret"`
**Connection Type:** Single circuit connection
-#### Circuit Signal I/O
+#### Reading Entity Output
-**Signal Inputs:**
+Use `entity.output` to read: **Circuit network signals**
-| Signal Property | Signal Type | Description | Enable With |
-|-----------------|-------------|-------------|-------------|
-| `circuit_condition` | Any signal | Signal used in enable/disable condition | `circuit_enabled: 1` |
+```facto
+Entity e = place("rocket-turret", 0, 0);
+Bundle signals = e.output; # Returns all output signals
+```
#### Settable Properties
-Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = value`
+Set at placement: `place("name", x, y, {prop: value})`
| Property | Type | Default | Example |
|----------|------|---------|---------|
-| `bar` | Integer | None | |
-| `circuit_enabled` | Boolean (0/1) | false | `1` |
+| `circuit_enabled` | Boolean (`0` or `1`) | 0 | `1` |
+| `connect_to_logistic_network` | Boolean (`0` or `1`) | 0 | `1` |
+| `direction` | Integer ([Direction](#direction)) | 0 | `0 # NORTH` |
+| `ignore_unlisted_targets_condition` | Condition (use `.enable = expr`) ⚠️ | (factory) | |
+| `ignore_unprioritized` | Boolean (`0` or `1`) | 0 | `1` |
| `item_requests` | List (complex) ⚠️ | (factory) | |
| `name` | String (entity prototype name) | (factory) | |
-| `quality` | String (normal/uncommon/rare/epic/legendary) | "normal" | |
-| `read_contents` | Boolean (0/1) | true | `1` |
+| `priority_list` | List (complex) ⚠️ | (factory) | |
+| `quality` | One of: `"normal"`, `"uncommon"`, `"rare"`, `"epic"`, `"legendary"`, `"quality-unknown"` ([QualityID](#qualityid)) | "normal" | `"normal"` |
+| `read_ammo` | Boolean (`0` or `1`) | 1 | `1` |
+| `set_ignore_unprioritized` | Boolean (`0` or `1`) | 0 | `1` |
+| `set_priority_list` | Boolean (`0` or `1`) | 0 | `1` |
+
+#### Signal Configuration
+
+Properties for configuring which signals the entity uses:
+
+| Property | Type | Description |
+|----------|------|-------------|
+| `circuit_condition` | Condition (use `.enable = expr`) | The circuit condition that must be passed in order for this ... |
+| `logistic_condition` | Condition (use `.enable = expr`) | The logistic condition that must be passed in order for this... |
---
-### LogisticActiveContainer
+### ElectricTurret
-**Description:** A logistics container that immediately provides it's contents to the logistic network.
+**Description:** An entity that automatically targets and attacks other forces within range. Uses electricity as ammunition.
-**Prototypes:** `"active-provider-chest"`
+**Draftsman Source:** [ElectricTurret class](https://github.com/redruin1/factorio-draftsman/blob/main/draftsman/prototypes/electric_turret.py)
+
+**Prototypes:** `"tesla-turret"`, `"laser-turret"`
**Connection Type:** Single circuit connection
-#### Circuit Signal I/O
+#### Reading Entity Output
-**Signal Inputs:**
+Use `entity.output` to read: **Circuit network signals**
-| Signal Property | Signal Type | Description | Enable With |
-|-----------------|-------------|-------------|-------------|
-| `circuit_condition` | Any signal | Signal used in enable/disable condition | `circuit_enabled: 1` |
+```facto
+Entity e = place("tesla-turret", 0, 0);
+Bundle signals = e.output; # Returns all output signals
+```
#### Settable Properties
-Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = value`
+Set at placement: `place("name", x, y, {prop: value})`
| Property | Type | Default | Example |
|----------|------|---------|---------|
-| `bar` | Integer | None | |
+| `circuit_enabled` | Boolean (`0` or `1`) | 0 | `1` |
+| `connect_to_logistic_network` | Boolean (`0` or `1`) | 0 | `1` |
+| `direction` | Integer ([Direction](#direction)) | 0 | `0 # NORTH` |
+| `ignore_unlisted_targets_condition` | Condition (use `.enable = expr`) ⚠️ | (factory) | |
+| `ignore_unprioritized` | Boolean (`0` or `1`) | 0 | `1` |
| `item_requests` | List (complex) ⚠️ | (factory) | |
| `name` | String (entity prototype name) | (factory) | |
-| `quality` | String (normal/uncommon/rare/epic/legendary) | "normal" | |
-| `read_contents` | Boolean (0/1) | true | `1` |
+| `priority_list` | List (complex) ⚠️ | (factory) | |
+| `quality` | One of: `"normal"`, `"uncommon"`, `"rare"`, `"epic"`, `"legendary"`, `"quality-unknown"` ([QualityID](#qualityid)) | "normal" | `"normal"` |
+| `read_ammo` | Boolean (`0` or `1`) | 1 | `1` |
+| `set_ignore_unprioritized` | Boolean (`0` or `1`) | 0 | `1` |
+| `set_priority_list` | Boolean (`0` or `1`) | 0 | `1` |
+
+#### Signal Configuration
+
+Properties for configuring which signals the entity uses:
+
+| Property | Type | Description |
+|----------|------|-------------|
+| `circuit_condition` | Condition (use `.enable = expr`) | The circuit condition that must be passed in order for this ... |
+| `logistic_condition` | Condition (use `.enable = expr`) | The logistic condition that must be passed in order for this... |
---
-### LogisticStorageContainer
+### FluidTurret
-**Description:** A logistics container that stores items not currently being used in the logistic network.
+**Description:** An entity that automatically targets and attacks other forces within range. Uses fluids as ammunition.
-**Prototypes:** `"storage-chest"`
+**Draftsman Source:** [FluidTurret class](https://github.com/redruin1/factorio-draftsman/blob/main/draftsman/prototypes/fluid_turret.py)
+
+**Prototypes:** `"flamethrower-turret"`
**Connection Type:** Single circuit connection
-#### Circuit Signal I/O
+#### Reading Entity Output
-**Signal Inputs:**
+Use `entity.output` to read: **Circuit network signals**
-| Signal Property | Signal Type | Description | Enable With |
-|-----------------|-------------|-------------|-------------|
-| `circuit_condition` | Any signal | Signal used in enable/disable condition | `circuit_enabled: 1` |
+```facto
+Entity e = place("flamethrower-turret", 0, 0);
+Bundle signals = e.output; # Returns all output signals
+```
#### Settable Properties
-Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = value`
+Set at placement: `place("name", x, y, {prop: value})`
| Property | Type | Default | Example |
|----------|------|---------|---------|
-| `bar` | Integer | None | |
-| `circuit_enabled` | Boolean (0/1) | false | `1` |
+| `circuit_enabled` | Boolean (`0` or `1`) | 0 | `1` |
+| `connect_to_logistic_network` | Boolean (`0` or `1`) | 0 | `1` |
+| `direction` | Integer ([Direction](#direction)) | 0 | `0 # NORTH` |
+| `ignore_unlisted_targets_condition` | Condition (use `.enable = expr`) ⚠️ | (factory) | |
+| `ignore_unprioritized` | Boolean (`0` or `1`) | 0 | `1` |
| `item_requests` | List (complex) ⚠️ | (factory) | |
| `name` | String (entity prototype name) | (factory) | |
-| `quality` | String (normal/uncommon/rare/epic/legendary) | "normal" | |
-| `read_contents` | Boolean (0/1) | true | `1` |
-| `request_from_buffers` | Boolean (0/1) | true | `1` |
-| `requests_enabled` | Boolean (0/1) | true | `1` |
-| `sections` | List (complex) ⚠️ | (factory) | |
-| `trash_not_requested` | Boolean (0/1) | false | `1` |
+| `priority_list` | List (complex) ⚠️ | (factory) | |
+| `quality` | One of: `"normal"`, `"uncommon"`, `"rare"`, `"epic"`, `"legendary"`, `"quality-unknown"` ([QualityID](#qualityid)) | "normal" | `"normal"` |
+| `read_ammo` | Boolean (`0` or `1`) | 1 | `1` |
+| `set_ignore_unprioritized` | Boolean (`0` or `1`) | 0 | `1` |
+| `set_priority_list` | Boolean (`0` or `1`) | 0 | `1` |
+
+#### Signal Configuration
+
+Properties for configuring which signals the entity uses:
+
+| Property | Type | Description |
+|----------|------|-------------|
+| `circuit_condition` | Condition (use `.enable = expr`) | The circuit condition that must be passed in order for this ... |
+| `logistic_condition` | Condition (use `.enable = expr`) | The logistic condition that must be passed in order for this... |
---
-### LogisticRequestContainer
+### Wall
-**Description:** A logistics container that requests items with a primary priority.
+**Description:** A static barrier that acts as protection for structures.
-**Prototypes:** `"requester-chest"`
+**Draftsman Source:** [Wall class](https://github.com/redruin1/factorio-draftsman/blob/main/draftsman/prototypes/wall.py)
+
+**Prototypes:** `"stone-wall"`
**Connection Type:** Single circuit connection
-#### Circuit Signal I/O
+#### Reading Entity Output
-**Signal Inputs:**
+Use `entity.output` to read: **Circuit network signals**
-| Signal Property | Signal Type | Description | Enable With |
-|-----------------|-------------|-------------|-------------|
-| `circuit_condition` | Any signal | Signal used in enable/disable condition | `circuit_enabled: 1` |
+```facto
+Entity e = place("stone-wall", 0, 0);
+Bundle signals = e.output; # Returns all output signals
+```
#### Settable Properties
-Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = value`
+Set at placement: `place("name", x, y, {prop: value})`
| Property | Type | Default | Example |
|----------|------|---------|---------|
-| `bar` | Integer | None | |
-| `circuit_enabled` | Boolean (0/1) | false | `1` |
+| `circuit_enabled` | Boolean (`0` or `1`) | 1 | `1` |
+| `direction` | Integer ([Direction](#direction)) | 0 | `0 # NORTH` |
| `item_requests` | List (complex) ⚠️ | (factory) | |
-| `mode_of_operation` | Integer ([LogisticModeOfOperation](#logisticmodeofoperation)) | 0 | `0 # SEND_CONTENTS` |
| `name` | String (entity prototype name) | (factory) | |
-| `quality` | String (normal/uncommon/rare/epic/legendary) | "normal" | |
-| `request_from_buffers` | Boolean (0/1) | false | `1` |
-| `requests_enabled` | Boolean (0/1) | true | `1` |
-| `sections` | List (complex) ⚠️ | (factory) | |
-| `trash_not_requested` | Boolean (0/1) | false | `1` |
+| `quality` | One of: `"normal"`, `"uncommon"`, `"rare"`, `"epic"`, `"legendary"`, `"quality-unknown"` ([QualityID](#qualityid)) | "normal" | `"normal"` |
+| `read_gate` | Boolean (`0` or `1`) | 0 | `1` |
+
+#### Signal Configuration
+
+Properties for configuring which signals the entity uses:
+
+| Property | Type | Description |
+|----------|------|-------------|
+| `circuit_condition` | Condition (use `.enable = expr`) | The circuit condition that must be passed in order for this ... |
+| `output_signal` | String (signal name, e.g. `"signal-A"`) | |
---
-### LogisticBufferContainer
+### Gate
-**Description:** A logistics container that requests items on a secondary priority.
+**Description:** A wall that opens near the player.
-**Prototypes:** `"buffer-chest"`
+**Draftsman Source:** [Gate class](https://github.com/redruin1/factorio-draftsman/blob/main/draftsman/prototypes/gate.py)
+
+**Prototypes:** `"gate"`
**Connection Type:** Single circuit connection
-#### Circuit Signal I/O
+#### Settable Properties
+
+Set at placement: `place("name", x, y, {prop: value})`
+
+| Property | Type | Default | Example |
+|----------|------|---------|---------|
+| `direction` | Integer ([Direction](#direction)) | 0 | `0 # NORTH` |
+| `item_requests` | List (complex) ⚠️ | (factory) | |
+| `name` | String (entity prototype name) | (factory) | |
+| `quality` | One of: `"normal"`, `"uncommon"`, `"rare"`, `"epic"`, `"legendary"`, `"quality-unknown"` ([QualityID](#qualityid)) | "normal" | `"normal"` |
-**Signal Inputs:**
+---
-| Signal Property | Signal Type | Description | Enable With |
-|-----------------|-------------|-------------|-------------|
-| `circuit_condition` | Any signal | Signal used in enable/disable condition | `circuit_enabled: 1` |
+### LandMine
+
+**Description:** An entity that explodes when in proximity to another force.
+
+**Draftsman Source:** [LandMine class](https://github.com/redruin1/factorio-draftsman/blob/main/draftsman/prototypes/land_mine.py)
+
+**Prototypes:** `"land-mine"`
+
+**Connection Type:** Single circuit connection
#### Settable Properties
-Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = value`
+Set at placement: `place("name", x, y, {prop: value})`
| Property | Type | Default | Example |
|----------|------|---------|---------|
-| `bar` | Integer | None | |
-| `circuit_enabled` | Boolean (0/1) | false | `1` |
| `item_requests` | List (complex) ⚠️ | (factory) | |
-| `mode_of_operation` | Integer ([LogisticModeOfOperation](#logisticmodeofoperation)) | 0 | `0 # SEND_CONTENTS` |
| `name` | String (entity prototype name) | (factory) | |
-| `quality` | String (normal/uncommon/rare/epic/legendary) | "normal" | |
-| `request_from_buffers` | Boolean (0/1) | true | `1` |
-| `requests_enabled` | Boolean (0/1) | true | `1` |
-| `sections` | List (complex) ⚠️ | (factory) | |
-| `trash_not_requested` | Boolean (0/1) | false | `1` |
+| `quality` | One of: `"normal"`, `"uncommon"`, `"rare"`, `"epic"`, `"legendary"`, `"quality-unknown"` ([QualityID](#qualityid)) | "normal" | `"normal"` |
---
-### LinkedContainer
+## Robots & Logistics
-**Description:** An entity that allows sharing it's contents with any other ``LinkedContainer`` with the same ``link_id``.
+### Roboport
-**Prototypes:** `"linked-chest"`
+**Description:** An entity that acts as a node in a logistics network.
+
+**Draftsman Source:** [Roboport class](https://github.com/redruin1/factorio-draftsman/blob/main/draftsman/prototypes/roboport.py)
+
+**Prototypes:** `"roboport"`
**Connection Type:** Single circuit connection
+#### Reading Entity Output
+
+Use `entity.output` to read: **Robot counts and logistics info**
+
+```facto
+Entity e = place("roboport", 0, 0);
+Bundle signals = e.output; # Returns all output signals
+```
+
+**Enable properties:**
+
+- `read_logistics`: Read logistic robot counts
+- `read_robot_stats`: Read robot statistics
+
#### Settable Properties
-Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = value`
+Set at placement: `place("name", x, y, {prop: value})`
| Property | Type | Default | Example |
|----------|------|---------|---------|
-| `bar` | Integer | None | |
+| `available_construction_signal` | String (signal name, e.g. `"signal-A"`) | (factory) | `"signal-A"` |
+| `available_logistic_signal` | String (signal name, e.g. `"signal-A"`) | (factory) | `"signal-A"` |
| `item_requests` | List (complex) ⚠️ | (factory) | |
-| `link_id` | Integer | 0 | |
| `name` | String (entity prototype name) | (factory) | |
-| `quality` | String (normal/uncommon/rare/epic/legendary) | "normal" | |
+| `quality` | One of: `"normal"`, `"uncommon"`, `"rare"`, `"epic"`, `"legendary"`, `"quality-unknown"` ([QualityID](#qualityid)) | "normal" | `"normal"` |
+| `read_items_mode` | Integer (see enum reference) | 1 | |
+| `read_robot_stats` | Boolean (`0` or `1`) | 0 | `1` |
+| `request_from_buffers` | Boolean (`0` or `1`) | 1 | `1` |
+| `requests_enabled` | Boolean (`0` or `1`) | 1 | `1` |
+| `roboport_count_signal` | String (signal name, e.g. `"signal-A"`) | (factory) | `"signal-A"` |
+| `sections` | List (complex) ⚠️ | (factory) | |
+| `total_construction_signal` | String (signal name, e.g. `"signal-A"`) | (factory) | `"signal-A"` |
+| `total_logistic_signal` | String (signal name, e.g. `"signal-A"`) | (factory) | `"signal-A"` |
+| `trash_not_requested` | Boolean (`0` or `1`) | 0 | `1` |
---
-## Power
+## Space
-### ElectricPole
+### SpacePlatformHub
-**Description:** An entity used to distribute electrical energy as a network.
+**Description:** (Factorio 2.0)
-**Prototypes:** `"substation"`, `"medium-electric-pole"`, `"big-electric-pole"`, `"small-electric-pole"`
+**Draftsman Source:** [SpacePlatformHub class](https://github.com/redruin1/factorio-draftsman/blob/main/draftsman/prototypes/space_platform_hub.py)
+
+**Prototypes:** `"space-platform-hub"`
**Connection Type:** Single circuit connection
+#### Reading Entity Output
+
+Use `entity.output` to read: **Circuit network signals**
+
+```facto
+Entity e = place("space-platform-hub", 0, 0);
+Bundle signals = e.output; # Returns all output signals
+```
+
#### Settable Properties
-Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = value`
+Set at placement: `place("name", x, y, {prop: value})`
| Property | Type | Default | Example |
|----------|------|---------|---------|
+| `damage_taken_signal` | String (signal name, e.g. `"signal-A"`) | (factory) | `"signal-A"` |
| `item_requests` | List (complex) ⚠️ | (factory) | |
| `name` | String (entity prototype name) | (factory) | |
-| `quality` | String (normal/uncommon/rare/epic/legendary) | "normal" | |
+| `quality` | One of: `"normal"`, `"uncommon"`, `"rare"`, `"epic"`, `"legendary"`, `"quality-unknown"` ([QualityID](#qualityid)) | "normal" | `"normal"` |
+| `read_contents` | Boolean (`0` or `1`) | 1 | `1` |
+| `read_damage_taken` | Boolean (`0` or `1`) | 0 | `1` |
+| `read_moving_from` | Boolean (`0` or `1`) | 0 | `1` |
+| `read_moving_to` | Boolean (`0` or `1`) | 0 | `1` |
+| `read_speed` | Boolean (`0` or `1`) | 0 | `1` |
+| `request_from_buffers` | Boolean (`0` or `1`) | 1 | `1` |
+| `request_missing_construction_materials` | Boolean (`0` or `1`) | 1 | `1` |
+| `requests_enabled` | Boolean (`0` or `1`) | 1 | `1` |
+| `sections` | List (complex) ⚠️ | (factory) | |
+| `send_to_platform` | Boolean (`0` or `1`) | 1 | `1` |
+| `speed_signal` | String (signal name, e.g. `"signal-A"`) | (factory) | `"signal-A"` |
+| `trash_not_requested` | Boolean (`0` or `1`) | 0 | `1` |
---
-### PowerSwitch
+### CargoLandingPad
-**Description:** An entity that connects or disconnects a power network.
+**Description:** (Factorio 2.0)
-**Prototypes:** `"power-switch"`
+**Draftsman Source:** [CargoLandingPad class](https://github.com/redruin1/factorio-draftsman/blob/main/draftsman/prototypes/cargo_landing_pad.py)
+
+**Prototypes:** `"cargo-landing-pad"`
**Connection Type:** Single circuit connection
-#### Circuit Signal I/O
+#### Reading Entity Output
-**Signal Inputs:**
+Use `entity.output` to read: **Circuit network signals**
-| Signal Property | Signal Type | Description | Enable With |
-|-----------------|-------------|-------------|-------------|
-| `circuit_condition` | Any signal | Signal used in enable/disable condition | `circuit_enabled: 1` |
-| `logistic_condition` | Any signal | Logistic network signal for enable/disable | `connect_to_logistic_network: 1` |
+```facto
+Entity e = place("cargo-landing-pad", 0, 0);
+Bundle signals = e.output; # Returns all output signals
+```
#### Settable Properties
-Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = value`
+Set at placement: `place("name", x, y, {prop: value})`
| Property | Type | Default | Example |
|----------|------|---------|---------|
-| `connect_to_logistic_network` | Boolean (0/1) | false | `1` |
| `item_requests` | List (complex) ⚠️ | (factory) | |
+| `mode_of_operation` | Integer ([LogisticModeOfOperation](#logisticmodeofoperation)) | 0 | `0 # SEND_CONTENTS` |
| `name` | String (entity prototype name) | (factory) | |
-| `quality` | String (normal/uncommon/rare/epic/legendary) | "normal" | |
-| `switch_state` | Boolean (0/1) | false | `1` |
+| `quality` | One of: `"normal"`, `"uncommon"`, `"rare"`, `"epic"`, `"legendary"`, `"quality-unknown"` ([QualityID](#qualityid)) | "normal" | `"normal"` |
+| `request_from_buffers` | Boolean (`0` or `1`) | 1 | `1` |
+| `requests_enabled` | Boolean (`0` or `1`) | 1 | `1` |
+| `sections` | List (complex) ⚠️ | (factory) | |
+| `trash_not_requested` | Boolean (`0` or `1`) | 0 | `1` |
---
-### Accumulator
+### AsteroidCollector
-**Description:** An entity that stores electricity for periods of high demand.
+**Description:** (Factorio 2.0)
-**Prototypes:** `"accumulator"`
+**Draftsman Source:** [AsteroidCollector class](https://github.com/redruin1/factorio-draftsman/blob/main/draftsman/prototypes/asteroid_collector.py)
+
+**Prototypes:** `"asteroid-collector"`
**Connection Type:** Single circuit connection
-#### Circuit Signal I/O
+#### Reading Entity Output
-**Signal Outputs:**
+Use `entity.output` to read: **Circuit network signals**
-| Signal Property | Signal Type | Description | Enable With |
-|-----------------|-------------|-------------|-------------|
-| `output_signal` | Result value | Signal to output the combinator result on | Always active |
+```facto
+Entity e = place("asteroid-collector", 0, 0);
+Bundle signals = e.output; # Returns all output signals
+```
#### Settable Properties
-Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = value`
+Set at placement: `place("name", x, y, {prop: value})`
| Property | Type | Default | Example |
|----------|------|---------|---------|
+| `chunk_filter` | List (complex) ⚠️ | (factory) | |
+| `circuit_enabled` | Boolean (`0` or `1`) | 0 | `1` |
+| `circuit_set_filters` | Boolean (`0` or `1`) | 0 | `1` |
+| `direction` | Integer ([Direction](#direction)) | 0 | `0 # NORTH` |
| `item_requests` | List (complex) ⚠️ | (factory) | |
| `name` | String (entity prototype name) | (factory) | |
-| `quality` | String (normal/uncommon/rare/epic/legendary) | "normal" | |
+| `quality` | One of: `"normal"`, `"uncommon"`, `"rare"`, `"epic"`, `"legendary"`, `"quality-unknown"` ([QualityID](#qualityid)) | "normal" | `"normal"` |
+| `read_contents` | Boolean (`0` or `1`) | 0 | `1` |
+| `read_hands` | Boolean (`0` or `1`) | 1 | `1` |
+| `result_inventory` | Complex (see draftsman docs) ⚠️ | (factory) | |
+
+#### Signal Configuration
+
+Properties for configuring which signals the entity uses:
+
+| Property | Type | Description |
+|----------|------|-------------|
+| `circuit_condition` | Condition (use `.enable = expr`) | The circuit condition that must be passed in order for this ... |
---
-### SolarPanel
+### CargoBay
-**Description:** An entity that produces electricity depending on the presence of the sun.
+**Description:** (Factorio 2.0)
-**Prototypes:** `"solar-panel"`
+**Draftsman Source:** [CargoBay class](https://github.com/redruin1/factorio-draftsman/blob/main/draftsman/prototypes/cargo_bay.py)
+
+**Prototypes:** `"cargo-bay"`
**Connection Type:** Single circuit connection
#### Settable Properties
-Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = value`
+Set at placement: `place("name", x, y, {prop: value})`
| Property | Type | Default | Example |
|----------|------|---------|---------|
| `item_requests` | List (complex) ⚠️ | (factory) | |
| `name` | String (entity prototype name) | (factory) | |
-| `quality` | String (normal/uncommon/rare/epic/legendary) | "normal" | |
+| `quality` | One of: `"normal"`, `"uncommon"`, `"rare"`, `"epic"`, `"legendary"`, `"quality-unknown"` ([QualityID](#qualityid)) | "normal" | `"normal"` |
---
-### ElectricEnergyInterface
+### Thruster
-**Description:** An entity that interfaces with an electrical grid.
+**Description:** (Factorio 2.0)
-**Prototypes:** `"electric-energy-interface"`, `"hidden-electric-energy-interface"`
+**Draftsman Source:** [Thruster class](https://github.com/redruin1/factorio-draftsman/blob/main/draftsman/prototypes/thruster.py)
+
+**Prototypes:** `"thruster"`
**Connection Type:** Single circuit connection
#### Settable Properties
-Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = value`
+Set at placement: `place("name", x, y, {prop: value})`
| Property | Type | Default | Example |
|----------|------|---------|---------|
-| `buffer_size` | Complex (see draftsman docs) ⚠️ | (factory) | |
-| `direction` | Integer ([Direction](#direction)) | 0 | `0 # NORTH` |
| `item_requests` | List (complex) ⚠️ | (factory) | |
| `name` | String (entity prototype name) | (factory) | |
-| `power_production` | Complex (see draftsman docs) ⚠️ | (factory) | |
-| `power_usage` | Complex (see draftsman docs) ⚠️ | (factory) | |
-| `quality` | String (normal/uncommon/rare/epic/legendary) | "normal" | |
+| `quality` | One of: `"normal"`, `"uncommon"`, `"rare"`, `"epic"`, `"legendary"`, `"quality-unknown"` ([QualityID](#qualityid)) | "normal" | `"normal"` |
---
-## Fluids
+## Misc
-### Pump
+### ProgrammableSpeaker
-**Description:** An entity that aids fluid transfer through pipes.
+**Description:** An entity that makes sounds that can be controlled by circuit network signals.
-**Prototypes:** `"pump"`
+**Draftsman Source:** [ProgrammableSpeaker class](https://github.com/redruin1/factorio-draftsman/blob/main/draftsman/prototypes/programmable_speaker.py)
+
+**Prototypes:** `"programmable-speaker"`
**Connection Type:** Single circuit connection
-#### Circuit Signal I/O
+#### Reading Entity Output
-**Signal Inputs:**
+Use `entity.output` to read: **Circuit network signals**
-| Signal Property | Signal Type | Description | Enable With |
-|-----------------|-------------|-------------|-------------|
-| `circuit_condition` | Any signal | Signal used in enable/disable condition | `circuit_enabled: 1` |
-| `logistic_condition` | Any signal | Logistic network signal for enable/disable | `connect_to_logistic_network: 1` |
+```facto
+Entity e = place("programmable-speaker", 0, 0);
+Bundle signals = e.output; # Returns all output signals
+```
#### Settable Properties
-Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = value`
+Set at placement: `place("name", x, y, {prop: value})`
| Property | Type | Default | Example |
|----------|------|---------|---------|
-| `connect_to_logistic_network` | Boolean (0/1) | false | `1` |
-| `direction` | Integer ([Direction](#direction)) | 0 | `0 # NORTH` |
+| `alert_icon` | String (signal name, e.g. `"signal-A"`) | None | |
+| `alert_message` | Complex (see draftsman docs) ⚠️ | "" | |
+| `allow_polyphony` | Boolean (`0` or `1`) | 0 | `1` |
+| `circuit_enabled` | Boolean (`0` or `1`) | 0 | `1` |
+| `instrument_id` | Integer | 0 | |
| `item_requests` | List (complex) ⚠️ | (factory) | |
| `name` | String (entity prototype name) | (factory) | |
-| `quality` | String (normal/uncommon/rare/epic/legendary) | "normal" | |
+| `note_id` | Integer | 0 | |
+| `playback_mode` | One of: `"local"`, `"surface"`, `"global"` ([PlaybackMode](#playbackmode)) | "local" | `"local"` |
+| `quality` | One of: `"normal"`, `"uncommon"`, `"rare"`, `"epic"`, `"legendary"`, `"quality-unknown"` ([QualityID](#qualityid)) | "normal" | `"normal"` |
+| `show_alert` | Boolean (`0` or `1`) | 0 | `1` |
+| `show_alert_on_map` | Boolean (`0` or `1`) | 1 | `1` |
+| `signal_value_is_pitch` | Boolean (`0` or `1`) | 0 | `1` |
+| `stop_playing_sounds` | Boolean (`0` or `1`) | 0 | `1` |
+| `volume` | Number | 1.0 | |
+| `volume_controlled_by_signal` | Boolean (`0` or `1`) | 0 | `1` |
+| `volume_signal` | String (signal name, e.g. `"signal-A"`) | None | `"signal-A"` |
+
+#### Signal Configuration
+
+Properties for configuring which signals the entity uses:
+
+| Property | Type | Description |
+|----------|------|-------------|
+| `circuit_condition` | Condition (use `.enable = expr`) | The circuit condition that must be passed in order for this ... |
---
-### StorageTank
+### Car
-**Description:** An entity that stores a fluid.
+**Description:** (Factorio 2.0)
-**Prototypes:** `"storage-tank"`
+**Draftsman Source:** [Car class](https://github.com/redruin1/factorio-draftsman/blob/main/draftsman/prototypes/car.py)
+
+**Prototypes:** `"car"`, `"tank"`
**Connection Type:** Single circuit connection
#### Settable Properties
-Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = value`
+Set at placement: `place("name", x, y, {prop: value})`
| Property | Type | Default | Example |
|----------|------|---------|---------|
-| `direction` | Integer ([Direction](#direction)) | 0 | `0 # NORTH` |
+| `ammo_inventory` | Complex (see draftsman docs) ⚠️ | (factory) | |
+| `driver_is_main_gunner` | Boolean (`0` or `1`) | 0 | `1` |
+| `enable_logistics_while_moving` | Boolean (`0` or `1`) | 1 | `1` |
+| `equipment` | List (complex) ⚠️ | (factory) | |
| `item_requests` | List (complex) ⚠️ | (factory) | |
| `name` | String (entity prototype name) | (factory) | |
-| `quality` | String (normal/uncommon/rare/epic/legendary) | "normal" | |
+| `orientation` | Complex (see draftsman docs) ⚠️ | | |
+| `quality` | One of: `"normal"`, `"uncommon"`, `"rare"`, `"epic"`, `"legendary"`, `"quality-unknown"` ([QualityID](#qualityid)) | "normal" | `"normal"` |
+| `selected_gun_index` | Integer | 0 | |
+| `trunk_inventory` | Complex (see draftsman docs) ⚠️ | (factory) | |
---
-### OffshorePump
+### SpiderVehicle
-**Description:** An entity that pumps a fluid from the environment.
+**Description:** (Factorio 2.0)
+
+**Draftsman Source:** [SpiderVehicle class](https://github.com/redruin1/factorio-draftsman/blob/main/draftsman/prototypes/spider_vehicle.py)
+
+**Prototypes:** `"spidertron"`
+
+**Connection Type:** Single circuit connection
+
+#### Settable Properties
+
+Set at placement: `place("name", x, y, {prop: value})`
+
+| Property | Type | Default | Example |
+|----------|------|---------|---------|
+| `ammo_inventory` | Complex (see draftsman docs) ⚠️ | (factory) | |
+| `auto_target_with_gunner` | Boolean (`0` or `1`) | 0 | `1` |
+| `auto_target_without_gunner` | Boolean (`0` or `1`) | 1 | `1` |
+| `color` | Object `{r, g, b}` (0-255 each) | (factory) | `{r: 255, g: 0, b: 0}` |
+| `driver_is_main_gunner` | Boolean (`0` or `1`) | 0 | `1` |
+| `enable_logistics_while_moving` | Boolean (`0` or `1`) | 1 | `1` |
+| `equipment` | List (complex) ⚠️ | (factory) | |
+| `item_requests` | List (complex) ⚠️ | (factory) | |
+| `name` | String (entity prototype name) | (factory) | |
+| `quality` | One of: `"normal"`, `"uncommon"`, `"rare"`, `"epic"`, `"legendary"`, `"quality-unknown"` ([QualityID](#qualityid)) | "normal" | `"normal"` |
+| `request_from_buffers` | Boolean (`0` or `1`) | 1 | `1` |
+| `requests_enabled` | Boolean (`0` or `1`) | 1 | `1` |
+| `sections` | List (complex) ⚠️ | (factory) | |
+| `selected_gun_index` | Integer | 0 | |
+| `trash_not_requested` | Boolean (`0` or `1`) | 0 | `1` |
+| `trunk_inventory` | Complex (see draftsman docs) ⚠️ | (factory) | |
+
+---
-**Prototypes:** `"offshore-pump"`
+### HeatPipe
-**Connection Type:** Single circuit connection
+**Description:** An entity used to transfer thermal energy.
-#### Circuit Signal I/O
+**Draftsman Source:** [HeatPipe class](https://github.com/redruin1/factorio-draftsman/blob/main/draftsman/prototypes/heat_pipe.py)
-**Signal Inputs:**
+**Prototypes:** `"heat-pipe"`
-| Signal Property | Signal Type | Description | Enable With |
-|-----------------|-------------|-------------|-------------|
-| `circuit_condition` | Any signal | Signal used in enable/disable condition | `circuit_enabled: 1` |
-| `logistic_condition` | Any signal | Logistic network signal for enable/disable | `connect_to_logistic_network: 1` |
+**Connection Type:** Single circuit connection
#### Settable Properties
-Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = value`
+Set at placement: `place("name", x, y, {prop: value})`
| Property | Type | Default | Example |
|----------|------|---------|---------|
-| `connect_to_logistic_network` | Boolean (0/1) | false | `1` |
-| `direction` | Integer ([Direction](#direction)) | 0 | `0 # NORTH` |
| `item_requests` | List (complex) ⚠️ | (factory) | |
| `name` | String (entity prototype name) | (factory) | |
-| `quality` | String (normal/uncommon/rare/epic/legendary) | "normal" | |
+| `quality` | One of: `"normal"`, `"uncommon"`, `"rare"`, `"epic"`, `"legendary"`, `"quality-unknown"` ([QualityID](#qualityid)) | "normal" | `"normal"` |
---
-### Pipe
+### HeatInterface
-**Description:** A structure that transports a fluid across a surface.
+**Description:** An entity that interacts with a heat network.
-**Prototypes:** `"pipe"`
+**Draftsman Source:** [HeatInterface class](https://github.com/redruin1/factorio-draftsman/blob/main/draftsman/prototypes/heat_interface.py)
+
+**Prototypes:** `"heat-interface"`
**Connection Type:** Single circuit connection
#### Settable Properties
-Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = value`
+Set at placement: `place("name", x, y, {prop: value})`
| Property | Type | Default | Example |
|----------|------|---------|---------|
| `item_requests` | List (complex) ⚠️ | (factory) | |
+| `mode` | One of: `"at-least"`, `"at-most"`, `"exactly"`, `"add"`, `"remove"` ([InfinityMode](#infinitymode)) | "at-least" | `"at-least"` |
| `name` | String (entity prototype name) | (factory) | |
-| `quality` | String (normal/uncommon/rare/epic/legendary) | "normal" | |
+| `quality` | One of: `"normal"`, `"uncommon"`, `"rare"`, `"epic"`, `"legendary"`, `"quality-unknown"` ([QualityID](#qualityid)) | "normal" | `"normal"` |
+| `temperature` | Number | 0.0 | |
---
-### InfinityPipe
+## Other Entities
-**Description:** An entity used to create an infinite amount of any fluid at any temperature.
+Additional entities not in the main categories:
-**Prototypes:** `"infinity-pipe"`
+### AgriculturalTower
+
+**Description:** (Factorio 2.0)
+
+**Draftsman Source:** [AgriculturalTower class](https://github.com/redruin1/factorio-draftsman/blob/main/draftsman/prototypes/agricultural_tower.py)
+
+**Prototypes:** `"agricultural-tower"`
**Connection Type:** Single circuit connection
+#### Reading Entity Output
+
+Use `entity.output` to read: **Circuit network signals**
+
+```facto
+Entity e = place("agricultural-tower", 0, 0);
+Bundle signals = e.output; # Returns all output signals
+```
+
#### Settable Properties
-Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = value`
+Set at placement: `place("name", x, y, {prop: value})`
| Property | Type | Default | Example |
|----------|------|---------|---------|
-| `fluid_name` | Condition (set via .enable) ⚠️ | None | |
+| `circuit_enabled` | Boolean (`0` or `1`) | 0 | `1` |
+| `connect_to_logistic_network` | Boolean (`0` or `1`) | 0 | `1` |
| `item_requests` | List (complex) ⚠️ | (factory) | |
-| `mode` | One of: at-least, at-most, exactly, add, remove | "at-least" | |
| `name` | String (entity prototype name) | (factory) | |
-| `percentage` | Complex (see draftsman docs) ⚠️ | 0.0 | |
-| `quality` | String (normal/uncommon/rare/epic/legendary) | "normal" | |
-| `temperature` | Integer | 0 | |
+| `quality` | One of: `"normal"`, `"uncommon"`, `"rare"`, `"epic"`, `"legendary"`, `"quality-unknown"` ([QualityID](#qualityid)) | "normal" | `"normal"` |
+| `read_contents` | Boolean (`0` or `1`) | 0 | `1` |
+
+#### Signal Configuration
+
+Properties for configuring which signals the entity uses:
+
+| Property | Type | Description |
+|----------|------|-------------|
+| `circuit_condition` | Condition (use `.enable = expr`) | The circuit condition that must be passed in order for this ... |
+| `logistic_condition` | Condition (use `.enable = expr`) | The logistic condition that must be passed in order for this... |
---
-## Combat
+### BurnerGenerator
-### Radar
+**Description:** A electrical generator that turns burnable fuel directly into electrical energy.
-**Description:** An entity that reveals and scans neighbouring chunks.
+**Draftsman Source:** [BurnerGenerator class](https://github.com/redruin1/factorio-draftsman/blob/main/draftsman/prototypes/burner_generator.py)
-**Prototypes:** `"radar"`
+**Prototypes:** `"burner-generator"`
**Connection Type:** Single circuit connection
#### Settable Properties
-Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = value`
+Set at placement: `place("name", x, y, {prop: value})`
| Property | Type | Default | Example |
|----------|------|---------|---------|
+| `direction` | Integer ([Direction](#direction)) | 0 | `0 # NORTH` |
| `item_requests` | List (complex) ⚠️ | (factory) | |
| `name` | String (entity prototype name) | (factory) | |
-| `quality` | String (normal/uncommon/rare/epic/legendary) | "normal" | |
+| `quality` | One of: `"normal"`, `"uncommon"`, `"rare"`, `"epic"`, `"legendary"`, `"quality-unknown"` ([QualityID](#qualityid)) | "normal" | `"normal"` |
---
-### ArtilleryTurret
-
-**Description:** A turret which can only target enemy structures and uses artillery ammunition.
-
-**Prototypes:** `"artillery-turret"`
+### CurvedRailA
-**Connection Type:** Single circuit connection
+**Description:** Curved rails which connect straight rails to half-diagonal rails.
-#### Circuit Signal I/O
+**Draftsman Source:** [CurvedRailA class](https://github.com/redruin1/factorio-draftsman/blob/main/draftsman/prototypes/curved_rail_a.py)
-**Signal Inputs:**
+**Prototypes:** `"curved-rail-a"`
-| Signal Property | Signal Type | Description | Enable With |
-|-----------------|-------------|-------------|-------------|
-| `circuit_condition` | Any signal | Signal used in enable/disable condition | `circuit_enabled: 1` |
-| `logistic_condition` | Any signal | Logistic network signal for enable/disable | `connect_to_logistic_network: 1` |
+**Connection Type:** Single circuit connection
#### Settable Properties
-Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = value`
+Set at placement: `place("name", x, y, {prop: value})`
| Property | Type | Default | Example |
|----------|------|---------|---------|
-| `auto_target` | Boolean (0/1) | true | `1` |
-| `circuit_enabled` | Boolean (0/1) | false | `1` |
-| `connect_to_logistic_network` | Boolean (0/1) | false | `1` |
| `direction` | Integer ([Direction](#direction)) | 0 | `0 # NORTH` |
| `item_requests` | List (complex) ⚠️ | (factory) | |
| `name` | String (entity prototype name) | (factory) | |
-| `quality` | String (normal/uncommon/rare/epic/legendary) | "normal" | |
-| `read_ammo` | Boolean (0/1) | true | `1` |
+| `quality` | One of: `"normal"`, `"uncommon"`, `"rare"`, `"epic"`, `"legendary"`, `"quality-unknown"` ([QualityID](#qualityid)) | "normal" | `"normal"` |
---
-### AmmoTurret
-
-**Description:** An entity that automatically targets and attacks other forces within range. Consumes item-based ammunition.
-
-**Prototypes:** `"rocket-turret"`, `"gun-turret"`, `"railgun-turret"`
+### CurvedRailB
-**Connection Type:** Single circuit connection
+**Description:** Curved rails which connect half-diagonal rails to diagonal rails.
-#### Circuit Signal I/O
+**Draftsman Source:** [CurvedRailB class](https://github.com/redruin1/factorio-draftsman/blob/main/draftsman/prototypes/curved_rail_b.py)
-**Signal Inputs:**
+**Prototypes:** `"curved-rail-b"`
-| Signal Property | Signal Type | Description | Enable With |
-|-----------------|-------------|-------------|-------------|
-| `circuit_condition` | Any signal | Signal used in enable/disable condition | `circuit_enabled: 1` |
-| `logistic_condition` | Any signal | Logistic network signal for enable/disable | `connect_to_logistic_network: 1` |
+**Connection Type:** Single circuit connection
#### Settable Properties
-Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = value`
+Set at placement: `place("name", x, y, {prop: value})`
| Property | Type | Default | Example |
|----------|------|---------|---------|
-| `circuit_enabled` | Boolean (0/1) | false | `1` |
-| `connect_to_logistic_network` | Boolean (0/1) | false | `1` |
| `direction` | Integer ([Direction](#direction)) | 0 | `0 # NORTH` |
-| `ignore_unlisted_targets_condition` | Condition (set via .enable) ⚠️ | (factory) | |
-| `ignore_unprioritized` | Boolean (0/1) | false | `1` |
| `item_requests` | List (complex) ⚠️ | (factory) | |
| `name` | String (entity prototype name) | (factory) | |
-| `priority_list` | List (complex) ⚠️ | (factory) | |
-| `quality` | String (normal/uncommon/rare/epic/legendary) | "normal" | |
-| `read_ammo` | Boolean (0/1) | true | `1` |
-| `set_ignore_unprioritized` | Boolean (0/1) | false | `1` |
-| `set_priority_list` | Boolean (0/1) | false | `1` |
+| `quality` | One of: `"normal"`, `"uncommon"`, `"rare"`, `"epic"`, `"legendary"`, `"quality-unknown"` ([QualityID](#qualityid)) | "normal" | `"normal"` |
---
-### ElectricTurret
-
-**Description:** An entity that automatically targets and attacks other forces within range. Uses electricity as ammunition.
-
-**Prototypes:** `"tesla-turret"`, `"laser-turret"`
+### ElectricEnergyInterface
-**Connection Type:** Single circuit connection
+**Description:** An entity that interfaces with an electrical grid.
-#### Circuit Signal I/O
+**Draftsman Source:** [ElectricEnergyInterface class](https://github.com/redruin1/factorio-draftsman/blob/main/draftsman/prototypes/electric_energy_interface.py)
-**Signal Inputs:**
+**Prototypes:** `"electric-energy-interface"`, `"hidden-electric-energy-interface"`
-| Signal Property | Signal Type | Description | Enable With |
-|-----------------|-------------|-------------|-------------|
-| `circuit_condition` | Any signal | Signal used in enable/disable condition | `circuit_enabled: 1` |
-| `logistic_condition` | Any signal | Logistic network signal for enable/disable | `connect_to_logistic_network: 1` |
+**Connection Type:** Single circuit connection
#### Settable Properties
-Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = value`
+Set at placement: `place("name", x, y, {prop: value})`
| Property | Type | Default | Example |
|----------|------|---------|---------|
-| `circuit_enabled` | Boolean (0/1) | false | `1` |
-| `connect_to_logistic_network` | Boolean (0/1) | false | `1` |
+| `buffer_size` | Number | (factory) | |
| `direction` | Integer ([Direction](#direction)) | 0 | `0 # NORTH` |
-| `ignore_unlisted_targets_condition` | Condition (set via .enable) ⚠️ | (factory) | |
-| `ignore_unprioritized` | Boolean (0/1) | false | `1` |
| `item_requests` | List (complex) ⚠️ | (factory) | |
| `name` | String (entity prototype name) | (factory) | |
-| `priority_list` | List (complex) ⚠️ | (factory) | |
-| `quality` | String (normal/uncommon/rare/epic/legendary) | "normal" | |
-| `read_ammo` | Boolean (0/1) | true | `1` |
-| `set_ignore_unprioritized` | Boolean (0/1) | false | `1` |
-| `set_priority_list` | Boolean (0/1) | false | `1` |
+| `power_production` | Number | (factory) | |
+| `power_usage` | Number | (factory) | |
+| `quality` | One of: `"normal"`, `"uncommon"`, `"rare"`, `"epic"`, `"legendary"`, `"quality-unknown"` ([QualityID](#qualityid)) | "normal" | `"normal"` |
---
-### FluidTurret
-
-**Description:** An entity that automatically targets and attacks other forces within range. Uses fluids as ammunition.
-
-**Prototypes:** `"flamethrower-turret"`
+### ElevatedCurvedRailA
-**Connection Type:** Single circuit connection
+**Description:** (Factorio 2.0)
-#### Circuit Signal I/O
+**Draftsman Source:** [ElevatedCurvedRailA class](https://github.com/redruin1/factorio-draftsman/blob/main/draftsman/prototypes/elevated_curved_rail_a.py)
-**Signal Inputs:**
+**Prototypes:** `"dummy-elevated-curved-rail-a"`, `"elevated-curved-rail-a"`
-| Signal Property | Signal Type | Description | Enable With |
-|-----------------|-------------|-------------|-------------|
-| `circuit_condition` | Any signal | Signal used in enable/disable condition | `circuit_enabled: 1` |
-| `logistic_condition` | Any signal | Logistic network signal for enable/disable | `connect_to_logistic_network: 1` |
+**Connection Type:** Single circuit connection
#### Settable Properties
-Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = value`
+Set at placement: `place("name", x, y, {prop: value})`
| Property | Type | Default | Example |
|----------|------|---------|---------|
-| `circuit_enabled` | Boolean (0/1) | false | `1` |
-| `connect_to_logistic_network` | Boolean (0/1) | false | `1` |
| `direction` | Integer ([Direction](#direction)) | 0 | `0 # NORTH` |
-| `ignore_unlisted_targets_condition` | Condition (set via .enable) ⚠️ | (factory) | |
-| `ignore_unprioritized` | Boolean (0/1) | false | `1` |
| `item_requests` | List (complex) ⚠️ | (factory) | |
| `name` | String (entity prototype name) | (factory) | |
-| `priority_list` | List (complex) ⚠️ | (factory) | |
-| `quality` | String (normal/uncommon/rare/epic/legendary) | "normal" | |
-| `read_ammo` | Boolean (0/1) | true | `1` |
-| `set_ignore_unprioritized` | Boolean (0/1) | false | `1` |
-| `set_priority_list` | Boolean (0/1) | false | `1` |
+| `quality` | One of: `"normal"`, `"uncommon"`, `"rare"`, `"epic"`, `"legendary"`, `"quality-unknown"` ([QualityID](#qualityid)) | "normal" | `"normal"` |
---
-### Wall
-
-**Description:** A static barrier that acts as protection for structures.
-
-**Prototypes:** `"stone-wall"`
-
-**Connection Type:** Single circuit connection
-
-#### Circuit Signal I/O
+### ElevatedCurvedRailB
-**Signal Inputs:**
+**Description:** (Factorio 2.0)
-| Signal Property | Signal Type | Description | Enable With |
-|-----------------|-------------|-------------|-------------|
-| `circuit_condition` | Any signal | Signal used in enable/disable condition | `circuit_enabled: 1` |
+**Draftsman Source:** [ElevatedCurvedRailB class](https://github.com/redruin1/factorio-draftsman/blob/main/draftsman/prototypes/elevated_curved_rail_b.py)
-**Signal Outputs:**
+**Prototypes:** `"dummy-elevated-curved-rail-b"`, `"elevated-curved-rail-b"`
-| Signal Property | Signal Type | Description | Enable With |
-|-----------------|-------------|-------------|-------------|
-| `output_signal` | Result value | Signal to output the combinator result on | Always active |
+**Connection Type:** Single circuit connection
#### Settable Properties
-Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = value`
+Set at placement: `place("name", x, y, {prop: value})`
| Property | Type | Default | Example |
|----------|------|---------|---------|
-| `circuit_enabled` | Boolean (0/1) | true | `1` |
| `direction` | Integer ([Direction](#direction)) | 0 | `0 # NORTH` |
| `item_requests` | List (complex) ⚠️ | (factory) | |
| `name` | String (entity prototype name) | (factory) | |
-| `quality` | String (normal/uncommon/rare/epic/legendary) | "normal" | |
-| `read_gate` | Boolean (0/1) | false | `1` |
+| `quality` | One of: `"normal"`, `"uncommon"`, `"rare"`, `"epic"`, `"legendary"`, `"quality-unknown"` ([QualityID](#qualityid)) | "normal" | `"normal"` |
---
-### Gate
+### ElevatedHalfDiagonalRail
-**Description:** A wall that opens near the player.
+**Description:** (Factorio 2.0)
-**Prototypes:** `"gate"`
+**Draftsman Source:** [ElevatedHalfDiagonalRail class](https://github.com/redruin1/factorio-draftsman/blob/main/draftsman/prototypes/elevated_half_diagonal_rail.py)
+
+**Prototypes:** `"dummy-elevated-half-diagonal-rail"`, `"elevated-half-diagonal-rail"`
**Connection Type:** Single circuit connection
#### Settable Properties
-Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = value`
+Set at placement: `place("name", x, y, {prop: value})`
| Property | Type | Default | Example |
|----------|------|---------|---------|
| `direction` | Integer ([Direction](#direction)) | 0 | `0 # NORTH` |
| `item_requests` | List (complex) ⚠️ | (factory) | |
| `name` | String (entity prototype name) | (factory) | |
-| `quality` | String (normal/uncommon/rare/epic/legendary) | "normal" | |
+| `quality` | One of: `"normal"`, `"uncommon"`, `"rare"`, `"epic"`, `"legendary"`, `"quality-unknown"` ([QualityID](#qualityid)) | "normal" | `"normal"` |
---
-## Robots & Logistics
+### ElevatedStraightRail
-### Roboport
+**Description:** (Factorio 2.0)
-**Description:** An entity that acts as a node in a logistics network.
+**Draftsman Source:** [ElevatedStraightRail class](https://github.com/redruin1/factorio-draftsman/blob/main/draftsman/prototypes/elevated_straight_rail.py)
-**Prototypes:** `"roboport"`
+**Prototypes:** `"dummy-elevated-straight-rail"`, `"elevated-straight-rail"`
**Connection Type:** Single circuit connection
#### Settable Properties
-Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = value`
+Set at placement: `place("name", x, y, {prop: value})`
| Property | Type | Default | Example |
|----------|------|---------|---------|
-| `available_construction_signal` | String (signal name) | (factory) | `"signal-A"` |
-| `available_logistic_signal` | String (signal name) | (factory) | `"signal-A"` |
+| `direction` | Integer ([Direction](#direction)) | 0 | `0 # NORTH` |
| `item_requests` | List (complex) ⚠️ | (factory) | |
| `name` | String (entity prototype name) | (factory) | |
-| `quality` | String (normal/uncommon/rare/epic/legendary) | "normal" | |
-| `read_items_mode` | Integer ([ReadItemsMode](#readitemsmode)) | 1 | `1 # LOGISTICS` |
-| `read_robot_stats` | Boolean (0/1) | false | `1` |
-| `request_from_buffers` | Boolean (0/1) | true | `1` |
-| `requests_enabled` | Boolean (0/1) | true | `1` |
-| `roboport_count_signal` | String (signal name) | (factory) | `"signal-A"` |
-| `sections` | List (complex) ⚠️ | (factory) | |
-| `total_construction_signal` | String (signal name) | (factory) | `"signal-A"` |
-| `total_logistic_signal` | String (signal name) | (factory) | `"signal-A"` |
-| `trash_not_requested` | Boolean (0/1) | false | `1` |
+| `quality` | One of: `"normal"`, `"uncommon"`, `"rare"`, `"epic"`, `"legendary"`, `"quality-unknown"` ([QualityID](#qualityid)) | "normal" | `"normal"` |
---
-## Space
-
-### SpacePlatformHub
+### FusionGenerator
**Description:** (Factorio 2.0)
-**Prototypes:** `"space-platform-hub"`
+**Draftsman Source:** [FusionGenerator class](https://github.com/redruin1/factorio-draftsman/blob/main/draftsman/prototypes/fusion_generator.py)
+
+**Prototypes:** `"fusion-generator"`
**Connection Type:** Single circuit connection
#### Settable Properties
-Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = value`
+Set at placement: `place("name", x, y, {prop: value})`
| Property | Type | Default | Example |
|----------|------|---------|---------|
-| `damage_taken_signal` | String (signal name) | (factory) | `"signal-A"` |
+| `direction` | Integer ([Direction](#direction)) | 0 | `0 # NORTH` |
| `item_requests` | List (complex) ⚠️ | (factory) | |
| `name` | String (entity prototype name) | (factory) | |
-| `quality` | String (normal/uncommon/rare/epic/legendary) | "normal" | |
-| `read_contents` | Boolean (0/1) | true | `1` |
-| `read_damage_taken` | Boolean (0/1) | false | `1` |
-| `read_moving_from` | Boolean (0/1) | false | `1` |
-| `read_moving_to` | Boolean (0/1) | false | `1` |
-| `read_speed` | Boolean (0/1) | false | `1` |
-| `request_from_buffers` | Boolean (0/1) | true | `1` |
-| `request_missing_construction_materials` | Boolean (0/1) | true | `1` |
-| `requests_enabled` | Boolean (0/1) | true | `1` |
-| `sections` | List (complex) ⚠️ | (factory) | |
-| `send_to_platform` | Boolean (0/1) | true | `1` |
-| `speed_signal` | String (signal name) | (factory) | `"signal-A"` |
-| `trash_not_requested` | Boolean (0/1) | false | `1` |
+| `quality` | One of: `"normal"`, `"uncommon"`, `"rare"`, `"epic"`, `"legendary"`, `"quality-unknown"` ([QualityID](#qualityid)) | "normal" | `"normal"` |
---
-### CargoLandingPad
+### FusionReactor
**Description:** (Factorio 2.0)
-**Prototypes:** `"cargo-landing-pad"`
+**Draftsman Source:** [FusionReactor class](https://github.com/redruin1/factorio-draftsman/blob/main/draftsman/prototypes/fusion_reactor.py)
+
+**Prototypes:** `"fusion-reactor"`
**Connection Type:** Single circuit connection
#### Settable Properties
-Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = value`
+Set at placement: `place("name", x, y, {prop: value})`
| Property | Type | Default | Example |
|----------|------|---------|---------|
+| `direction` | Integer ([Direction](#direction)) | 0 | `0 # NORTH` |
| `item_requests` | List (complex) ⚠️ | (factory) | |
-| `mode_of_operation` | Integer ([LogisticModeOfOperation](#logisticmodeofoperation)) | 0 | `0 # SEND_CONTENTS` |
| `name` | String (entity prototype name) | (factory) | |
-| `quality` | String (normal/uncommon/rare/epic/legendary) | "normal" | |
-| `request_from_buffers` | Boolean (0/1) | true | `1` |
-| `requests_enabled` | Boolean (0/1) | true | `1` |
-| `sections` | List (complex) ⚠️ | (factory) | |
-| `trash_not_requested` | Boolean (0/1) | false | `1` |
+| `quality` | One of: `"normal"`, `"uncommon"`, `"rare"`, `"epic"`, `"legendary"`, `"quality-unknown"` ([QualityID](#qualityid)) | "normal" | `"normal"` |
---
-### AsteroidCollector
+### HalfDiagonalRail
**Description:** (Factorio 2.0)
-**Prototypes:** `"asteroid-collector"`
-
-**Connection Type:** Single circuit connection
-
-#### Circuit Signal I/O
+**Draftsman Source:** [HalfDiagonalRail class](https://github.com/redruin1/factorio-draftsman/blob/main/draftsman/prototypes/half_diagonal_rail.py)
-**Signal Inputs:**
+**Prototypes:** `"half-diagonal-rail"`
-| Signal Property | Signal Type | Description | Enable With |
-|-----------------|-------------|-------------|-------------|
-| `circuit_condition` | Any signal | Signal used in enable/disable condition | `circuit_enabled: 1` |
+**Connection Type:** Single circuit connection
#### Settable Properties
-Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = value`
+Set at placement: `place("name", x, y, {prop: value})`
| Property | Type | Default | Example |
|----------|------|---------|---------|
-| `chunk_filter` | List (complex) ⚠️ | (factory) | |
-| `circuit_enabled` | Boolean (0/1) | false | `1` |
-| `circuit_set_filters` | Boolean (0/1) | false | `1` |
| `direction` | Integer ([Direction](#direction)) | 0 | `0 # NORTH` |
| `item_requests` | List (complex) ⚠️ | (factory) | |
| `name` | String (entity prototype name) | (factory) | |
-| `quality` | String (normal/uncommon/rare/epic/legendary) | "normal" | |
-| `read_contents` | Boolean (0/1) | false | `1` |
-| `read_hands` | Boolean (0/1) | true | `1` |
-| `result_inventory` | Complex (see draftsman docs) ⚠️ | (factory) | |
+| `quality` | One of: `"normal"`, `"uncommon"`, `"rare"`, `"epic"`, `"legendary"`, `"quality-unknown"` ([QualityID](#qualityid)) | "normal" | `"normal"` |
---
-### CargoBay
+### InfinityContainer
-**Description:** (Factorio 2.0)
+**Description:** An entity used to create an infinite amount of any item.
-**Prototypes:** `"cargo-bay"`
+**Draftsman Source:** [InfinityContainer class](https://github.com/redruin1/factorio-draftsman/blob/main/draftsman/prototypes/infinity_container.py)
+
+**Prototypes:** `"infinity-chest"`
**Connection Type:** Single circuit connection
#### Settable Properties
-Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = value`
+Set at placement: `place("name", x, y, {prop: value})`
| Property | Type | Default | Example |
|----------|------|---------|---------|
+| `bar` | Integer | None | |
+| `filters` | List (complex) ⚠️ | (factory) | |
| `item_requests` | List (complex) ⚠️ | (factory) | |
| `name` | String (entity prototype name) | (factory) | |
-| `quality` | String (normal/uncommon/rare/epic/legendary) | "normal" | |
+| `quality` | One of: `"normal"`, `"uncommon"`, `"rare"`, `"epic"`, `"legendary"`, `"quality-unknown"` ([QualityID](#qualityid)) | "normal" | `"normal"` |
+| `remove_unfiltered_items` | Boolean (`0` or `1`) | 0 | `1` |
---
-### Thruster
+### InfinityPipe
-**Description:** (Factorio 2.0)
+**Description:** An entity used to create an infinite amount of any fluid at any temperature.
-**Prototypes:** `"thruster"`
+**Draftsman Source:** [InfinityPipe class](https://github.com/redruin1/factorio-draftsman/blob/main/draftsman/prototypes/infinity_pipe.py)
+
+**Prototypes:** `"infinity-pipe"`
**Connection Type:** Single circuit connection
#### Settable Properties
-Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = value`
+Set at placement: `place("name", x, y, {prop: value})`
| Property | Type | Default | Example |
|----------|------|---------|---------|
+| `fluid_name` | Condition (use `.enable = expr`) ⚠️ | None | |
| `item_requests` | List (complex) ⚠️ | (factory) | |
+| `mode` | One of: `"at-least"`, `"at-most"`, `"exactly"`, `"add"`, `"remove"` ([InfinityMode](#infinitymode)) | "at-least" | `"at-least"` |
| `name` | String (entity prototype name) | (factory) | |
-| `quality` | String (normal/uncommon/rare/epic/legendary) | "normal" | |
+| `percentage` | Number | 0.0 | |
+| `quality` | One of: `"normal"`, `"uncommon"`, `"rare"`, `"epic"`, `"legendary"`, `"quality-unknown"` ([QualityID](#qualityid)) | "normal" | `"normal"` |
+| `temperature` | Integer | 0 | |
---
-## Misc
+### LegacyCurvedRail
-### ProgrammableSpeaker
+**Description:** An old, Factorio 1.0 curved rail entity.
-**Description:** An entity that makes sounds that can be controlled by circuit network signals.
+**Draftsman Source:** [LegacyCurvedRail class](https://github.com/redruin1/factorio-draftsman/blob/main/draftsman/prototypes/legacy_curved_rail.py)
-**Prototypes:** `"programmable-speaker"`
+**Prototypes:** `"legacy-curved-rail"`
**Connection Type:** Single circuit connection
-#### Circuit Signal I/O
-
-**Signal Inputs:**
-
-| Signal Property | Signal Type | Description | Enable With |
-|-----------------|-------------|-------------|-------------|
-| `circuit_condition` | Any signal | Signal used in enable/disable condition | `circuit_enabled: 1` |
-| `signal_value_is_pitch` | Pitch value | Signal value controls note pitch | Always active |
-
#### Settable Properties
-Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = value`
+Set at placement: `place("name", x, y, {prop: value})`
| Property | Type | Default | Example |
|----------|------|---------|---------|
-| `alert_icon` | String (signal name) | None | |
-| `alert_message` | Complex (see draftsman docs) ⚠️ | "" | |
-| `allow_polyphony` | Boolean (0/1) | false | `1` |
-| `circuit_enabled` | Boolean (0/1) | false | `1` |
-| `instrument_id` | Integer | 0 | |
+| `direction` | Integer ([Direction](#direction)) | 0 | `0 # NORTH` |
| `item_requests` | List (complex) ⚠️ | (factory) | |
| `name` | String (entity prototype name) | (factory) | |
-| `note_id` | Integer | 0 | |
-| `playback_mode` | One of: local, surface, global | "local" | |
-| `quality` | String (normal/uncommon/rare/epic/legendary) | "normal" | |
-| `show_alert` | Boolean (0/1) | false | `1` |
-| `show_alert_on_map` | Boolean (0/1) | true | `1` |
-| `stop_playing_sounds` | Boolean (0/1) | false | `1` |
-| `volume` | Complex (see draftsman docs) ⚠️ | 1.0 | |
-| `volume_controlled_by_signal` | Boolean (0/1) | false | `1` |
-| `volume_signal` | String (signal name) | None | `"signal-A"` |
+| `quality` | One of: `"normal"`, `"uncommon"`, `"rare"`, `"epic"`, `"legendary"`, `"quality-unknown"` ([QualityID](#qualityid)) | "normal" | `"normal"` |
---
-### Car
+### LegacyStraightRail
-**Description:** (Factorio 2.0)
+**Description:** An old, Factorio 1.0 straight rail entity.
-**Prototypes:** `"car"`, `"tank"`
+**Draftsman Source:** [LegacyStraightRail class](https://github.com/redruin1/factorio-draftsman/blob/main/draftsman/prototypes/legacy_straight_rail.py)
+
+**Prototypes:** `"legacy-straight-rail"`
**Connection Type:** Single circuit connection
#### Settable Properties
-Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = value`
+Set at placement: `place("name", x, y, {prop: value})`
| Property | Type | Default | Example |
|----------|------|---------|---------|
-| `ammo_inventory` | Complex (see draftsman docs) ⚠️ | (factory) | |
-| `driver_is_main_gunner` | Boolean (0/1) | false | `1` |
-| `enable_logistics_while_moving` | Boolean (0/1) | true | `1` |
-| `equipment` | List (complex) ⚠️ | (factory) | |
+| `direction` | Integer ([Direction](#direction)) | 0 | `0 # NORTH` |
| `item_requests` | List (complex) ⚠️ | (factory) | |
| `name` | String (entity prototype name) | (factory) | |
-| `orientation` | Float (0.0-1.0) | | |
-| `quality` | String (normal/uncommon/rare/epic/legendary) | "normal" | |
-| `selected_gun_index` | Integer | 0 | |
-| `trunk_inventory` | Complex (see draftsman docs) ⚠️ | (factory) | |
+| `quality` | One of: `"normal"`, `"uncommon"`, `"rare"`, `"epic"`, `"legendary"`, `"quality-unknown"` ([QualityID](#qualityid)) | "normal" | `"normal"` |
---
-### SpiderVehicle
+### LightningAttractor
**Description:** (Factorio 2.0)
-**Prototypes:** `"spidertron"`
+**Draftsman Source:** [LightningAttractor class](https://github.com/redruin1/factorio-draftsman/blob/main/draftsman/prototypes/lightning_attractor.py)
+
+**Prototypes:** `"lightning-rod"`, `"fulgoran-ruin-attractor"`, `"lightning-collector"`
**Connection Type:** Single circuit connection
#### Settable Properties
-Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = value`
+Set at placement: `place("name", x, y, {prop: value})`
| Property | Type | Default | Example |
|----------|------|---------|---------|
-| `ammo_inventory` | Complex (see draftsman docs) ⚠️ | (factory) | |
-| `auto_target_with_gunner` | Boolean (0/1) | false | `1` |
-| `auto_target_without_gunner` | Boolean (0/1) | true | `1` |
-| `color` | Color {r: 0-255, g: 0-255, b: 0-255} | (factory) | `{r: 255, g: 0, b: 0}` |
-| `driver_is_main_gunner` | Boolean (0/1) | false | `1` |
-| `enable_logistics_while_moving` | Boolean (0/1) | true | `1` |
-| `equipment` | List (complex) ⚠️ | (factory) | |
| `item_requests` | List (complex) ⚠️ | (factory) | |
| `name` | String (entity prototype name) | (factory) | |
-| `quality` | String (normal/uncommon/rare/epic/legendary) | "normal" | |
-| `request_from_buffers` | Boolean (0/1) | true | `1` |
-| `requests_enabled` | Boolean (0/1) | true | `1` |
-| `sections` | List (complex) ⚠️ | (factory) | |
-| `selected_gun_index` | Integer | 0 | |
-| `trash_not_requested` | Boolean (0/1) | false | `1` |
-| `trunk_inventory` | Complex (see draftsman docs) ⚠️ | (factory) | |
+| `quality` | One of: `"normal"`, `"uncommon"`, `"rare"`, `"epic"`, `"legendary"`, `"quality-unknown"` ([QualityID](#qualityid)) | "normal" | `"normal"` |
---
-### HeatPipe
+### LinkedContainer
-**Description:** An entity used to transfer thermal energy.
+**Description:** An entity that allows sharing it's contents with any other ``LinkedContainer`` with the same ``link_id``.
-**Prototypes:** `"heat-pipe"`
+**Draftsman Source:** [LinkedContainer class](https://github.com/redruin1/factorio-draftsman/blob/main/draftsman/prototypes/linked_container.py)
+
+**Prototypes:** `"linked-chest"`
**Connection Type:** Single circuit connection
#### Settable Properties
-Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = value`
+Set at placement: `place("name", x, y, {prop: value})`
| Property | Type | Default | Example |
|----------|------|---------|---------|
+| `bar` | Integer | None | |
| `item_requests` | List (complex) ⚠️ | (factory) | |
+| `link_id` | Integer | 0 | |
| `name` | String (entity prototype name) | (factory) | |
-| `quality` | String (normal/uncommon/rare/epic/legendary) | "normal" | |
+| `quality` | One of: `"normal"`, `"uncommon"`, `"rare"`, `"epic"`, `"legendary"`, `"quality-unknown"` ([QualityID](#qualityid)) | "normal" | `"normal"` |
---
-### HeatInterface
+### PlayerPort
-**Description:** An entity that interacts with a heat network.
+**Description:** A constructable respawn point typically used in scenarios.
-**Prototypes:** `"heat-interface"`
+**Draftsman Source:** [PlayerPort class](https://github.com/redruin1/factorio-draftsman/blob/main/draftsman/prototypes/player_port.py)
**Connection Type:** Single circuit connection
#### Settable Properties
-Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = value`
+Set at placement: `place("name", x, y, {prop: value})`
| Property | Type | Default | Example |
|----------|------|---------|---------|
| `item_requests` | List (complex) ⚠️ | (factory) | |
-| `mode` | One of: at-least, at-most, exactly, add, remove | "at-least" | |
| `name` | String (entity prototype name) | (factory) | |
-| `quality` | String (normal/uncommon/rare/epic/legendary) | "normal" | |
-| `temperature` | Complex (see draftsman docs) ⚠️ | 0.0 | |
+| `quality` | One of: `"normal"`, `"uncommon"`, `"rare"`, `"epic"`, `"legendary"`, `"quality-unknown"` ([QualityID](#qualityid)) | "normal" | `"normal"` |
---
-### SimpleEntityWithOwner
+### RailRamp
-**Description:** A generic entity owned by some other entity.
+**Description:** (Factorio 2.0)
-**Prototypes:** `"simple-entity-with-owner"`
+**Draftsman Source:** [RailRamp class](https://github.com/redruin1/factorio-draftsman/blob/main/draftsman/prototypes/rail_ramp.py)
+
+**Prototypes:** `"rail-ramp"`, `"dummy-rail-ramp"`
**Connection Type:** Single circuit connection
#### Settable Properties
-Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = value`
+Set at placement: `place("name", x, y, {prop: value})`
| Property | Type | Default | Example |
|----------|------|---------|---------|
| `direction` | Integer ([Direction](#direction)) | 0 | `0 # NORTH` |
| `item_requests` | List (complex) ⚠️ | (factory) | |
| `name` | String (entity prototype name) | (factory) | |
-| `quality` | String (normal/uncommon/rare/epic/legendary) | "normal" | |
-| `variation` | Integer | 1 | |
+| `quality` | One of: `"normal"`, `"uncommon"`, `"rare"`, `"epic"`, `"legendary"`, `"quality-unknown"` ([QualityID](#qualityid)) | "normal" | `"normal"` |
---
-### SimpleEntityWithForce
+### RailSupport
-**Description:** A generic entity associated with friends or foes.
+**Description:** (Factorio 2.0)
-**Prototypes:** `"simple-entity-with-force"`
+**Draftsman Source:** [RailSupport class](https://github.com/redruin1/factorio-draftsman/blob/main/draftsman/prototypes/rail_support.py)
+
+**Prototypes:** `"dummy-rail-support"`, `"rail-support"`
**Connection Type:** Single circuit connection
#### Settable Properties
-Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = value`
+Set at placement: `place("name", x, y, {prop: value})`
| Property | Type | Default | Example |
|----------|------|---------|---------|
| `direction` | Integer ([Direction](#direction)) | 0 | `0 # NORTH` |
| `item_requests` | List (complex) ⚠️ | (factory) | |
| `name` | String (entity prototype name) | (factory) | |
-| `quality` | String (normal/uncommon/rare/epic/legendary) | "normal" | |
-| `variation` | Integer | 1 | |
+| `quality` | One of: `"normal"`, `"uncommon"`, `"rare"`, `"epic"`, `"legendary"`, `"quality-unknown"` ([QualityID](#qualityid)) | "normal" | `"normal"` |
---
-## Uncategorized Entities
-
-The following entities are available but not yet categorized.
-They still have full documentation below.
+### SimpleEntityWithForce
-### InfinityContainer
+**Description:** A generic entity associated with friends or foes.
-**Description:** An entity used to create an infinite amount of any item.
+**Draftsman Source:** [SimpleEntityWithForce class](https://github.com/redruin1/factorio-draftsman/blob/main/draftsman/prototypes/simple_entity_with_force.py)
-**Prototypes:** `"infinity-chest"`
+**Prototypes:** `"simple-entity-with-force"`
**Connection Type:** Single circuit connection
#### Settable Properties
-Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = value`
+Set at placement: `place("name", x, y, {prop: value})`
| Property | Type | Default | Example |
|----------|------|---------|---------|
-| `bar` | Integer | None | |
-| `filters` | List (complex) ⚠️ | (factory) | |
+| `direction` | Integer ([Direction](#direction)) | 0 | `0 # NORTH` |
| `item_requests` | List (complex) ⚠️ | (factory) | |
| `name` | String (entity prototype name) | (factory) | |
-| `quality` | String (normal/uncommon/rare/epic/legendary) | "normal" | |
-| `remove_unfiltered_items` | Boolean (0/1) | false | `1` |
+| `quality` | One of: `"normal"`, `"uncommon"`, `"rare"`, `"epic"`, `"legendary"`, `"quality-unknown"` ([QualityID](#qualityid)) | "normal" | `"normal"` |
+| `variation` | Integer | 1 | |
---
-### LandMine
+### SimpleEntityWithOwner
-**Description:** An entity that explodes when in proximity to another force.
+**Description:** A generic entity owned by some other entity.
-**Prototypes:** `"land-mine"`
+**Draftsman Source:** [SimpleEntityWithOwner class](https://github.com/redruin1/factorio-draftsman/blob/main/draftsman/prototypes/simple_entity_with_owner.py)
+
+**Prototypes:** `"simple-entity-with-owner"`
**Connection Type:** Single circuit connection
#### Settable Properties
-Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = value`
+Set at placement: `place("name", x, y, {prop: value})`
| Property | Type | Default | Example |
|----------|------|---------|---------|
+| `direction` | Integer ([Direction](#direction)) | 0 | `0 # NORTH` |
| `item_requests` | List (complex) ⚠️ | (factory) | |
| `name` | String (entity prototype name) | (factory) | |
-| `quality` | String (normal/uncommon/rare/epic/legendary) | "normal" | |
+| `quality` | One of: `"normal"`, `"uncommon"`, `"rare"`, `"epic"`, `"legendary"`, `"quality-unknown"` ([QualityID](#qualityid)) | "normal" | `"normal"` |
+| `variation` | Integer | 1 | |
---
-### PlayerPort
+### StraightRail
-**Description:** A constructable respawn point typically used in scenarios.
+**Description:** A piece of rail track that moves in the 8 cardinal directions.
+
+**Draftsman Source:** [StraightRail class](https://github.com/redruin1/factorio-draftsman/blob/main/draftsman/prototypes/straight_rail.py)
+
+**Prototypes:** `"straight-rail"`
**Connection Type:** Single circuit connection
#### Settable Properties
-Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = value`
+Set at placement: `place("name", x, y, {prop: value})`
| Property | Type | Default | Example |
|----------|------|---------|---------|
+| `direction` | Integer ([Direction](#direction)) | 0 | `0 # NORTH` |
| `item_requests` | List (complex) ⚠️ | (factory) | |
| `name` | String (entity prototype name) | (factory) | |
-| `quality` | String (normal/uncommon/rare/epic/legendary) | "normal" | |
+| `quality` | One of: `"normal"`, `"uncommon"`, `"rare"`, `"epic"`, `"legendary"`, `"quality-unknown"` ([QualityID](#qualityid)) | "normal" | `"normal"` |
---
@@ -2591,20 +3260,22 @@ Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop =
**Description:** A pipe that transports fluids underneath other entities.
+**Draftsman Source:** [UndergroundPipe class](https://github.com/redruin1/factorio-draftsman/blob/main/draftsman/prototypes/underground_pipe.py)
+
**Prototypes:** `"pipe-to-ground"`
**Connection Type:** Single circuit connection
#### Settable Properties
-Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = value`
+Set at placement: `place("name", x, y, {prop: value})`
| Property | Type | Default | Example |
|----------|------|---------|---------|
| `direction` | Integer ([Direction](#direction)) | 0 | `0 # NORTH` |
| `item_requests` | List (complex) ⚠️ | (factory) | |
| `name` | String (entity prototype name) | (factory) | |
-| `quality` | String (normal/uncommon/rare/epic/legendary) | "normal" | |
+| `quality` | One of: `"normal"`, `"uncommon"`, `"rare"`, `"epic"`, `"legendary"`, `"quality-unknown"` ([QualityID](#qualityid)) | "normal" | `"normal"` |
---
@@ -2612,34 +3283,21 @@ Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop =
**Description:** A pipe that may or may not admit fluid to pass through it based on some threshold.
+**Draftsman Source:** [Valve class](https://github.com/redruin1/factorio-draftsman/blob/main/draftsman/prototypes/valve.py)
+
**Prototypes:** `"top-up-valve"`, `"overflow-valve"`, `"one-way-valve"`
**Connection Type:** Single circuit connection
#### Settable Properties
-Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = value`
+Set at placement: `place("name", x, y, {prop: value})`
| Property | Type | Default | Example |
|----------|------|---------|---------|
| `direction` | Integer ([Direction](#direction)) | 0 | `0 # NORTH` |
| `item_requests` | List (complex) ⚠️ | (factory) | |
| `name` | String (entity prototype name) | (factory) | |
-| `quality` | String (normal/uncommon/rare/epic/legendary) | "normal" | |
+| `quality` | One of: `"normal"`, `"uncommon"`, `"rare"`, `"epic"`, `"legendary"`, `"quality-unknown"` ([QualityID](#qualityid)) | "normal" | `"normal"` |
---
-
-## Notes on Complex Property Types
-
-Some properties marked with ⚠️ have complex types that may not be directly settable
-in the current DSL syntax. These typically include:
-
-| Type | Description | Workaround |
-|------|-------------|------------|
-| List | Arrays of items/filters | May require special syntax |
-| Condition | Circuit conditions | Use `.enable = signal > value` syntax |
-| Vector | Position offsets | Use `{x: value, y: value}` |
-| Complex | Other structured data | See draftsman documentation |
-
-For full details on complex types, refer to the
-[Draftsman documentation](https://factorio-draftsman.readthedocs.io/en/latest/).
diff --git a/doc/generate_entity_docs.py b/doc/generate_entity_docs.py
index ca697c0..ceeb801 100644
--- a/doc/generate_entity_docs.py
+++ b/doc/generate_entity_docs.py
@@ -4,14 +4,18 @@
This script creates educational documentation that helps users understand:
1. What properties can be set on each entity (both at placement and dynamically)
-2. How to set enum values (with the actual integer values)
-3. What circuit input/output signals each entity supports
+2. How to set enum/literal values (with all valid options)
+3. What circuit outputs each entity can produce via `.output`
4. DSL syntax examples for each entity type
All information is extracted dynamically from the Draftsman library.
Usage:
+ # Generate fresh documentation
python doc/generate_entity_docs.py -o doc/ENTITY_REFERENCE.md
+
+ # Update existing documentation (targeted changes only)
+ python doc/generate_entity_docs.py --update doc/ENTITY_REFERENCE.md
"""
from __future__ import annotations
@@ -23,7 +27,7 @@
from dataclasses import dataclass, field
from datetime import datetime
from enum import Enum, IntEnum
-from typing import Any
+from typing import Any, Literal, get_args, get_origin
import attrs
@@ -39,15 +43,25 @@
sys.exit(1)
+# =============================================================================
+# Data Classes
+# =============================================================================
+
+
@dataclass
class EnumInfo:
+ """Information about an enum or Literal type."""
+
name: str
- members: dict[str, Any]
+ members: dict[str, Any] # member_name -> value
description: str = ""
+ is_literal: bool = False # True for Literal[] types, False for IntEnum
@dataclass
class PropertyInfo:
+ """Information about an entity property."""
+
name: str
type_name: str # Original Python type
dsl_type: str # DSL-friendly type description
@@ -56,33 +70,36 @@ class PropertyInfo:
description: str
is_enum: bool = False
enum_info: EnumInfo | None = None
+ literal_values: tuple | None = None # For Literal types
example_value: str = ""
- is_dsl_supported: bool = True # Whether this type is directly settable in DSL
- is_signal_property: bool = False # Whether this is a signal I/O property
+ is_dsl_supported: bool = True
+ is_signal_property: bool = False # e.g. count_signal, output_signal
@dataclass
-class SignalIOEntry:
- """Represents a single signal input or output."""
+class EntityOutputInfo:
+ """Information about entity circuit outputs via .output property."""
- property_name: str
- direction: str # "input" or "output"
- signal_type: str
- description: str
- enable_property: str | None = None
+ supports_output: bool = False
+ output_type: str = "Bundle" # What .output returns
+ output_signals: list[str] = field(default_factory=list) # What signals it can output
+ enable_properties: dict[str, str] = field(default_factory=dict) # prop -> description
+ description: str = ""
@dataclass
class CircuitIOInfo:
+ """Information about entity circuit connections."""
+
has_circuit_connection: bool = True
has_dual_connection: bool = False
- signal_inputs: list[SignalIOEntry] = field(default_factory=list)
- signal_outputs: list[SignalIOEntry] = field(default_factory=list)
- content_outputs: list[str] = field(default_factory=list) # Generic outputs like "item contents"
+ output_info: EntityOutputInfo = field(default_factory=EntityOutputInfo)
@dataclass
class EntityInfo:
+ """Complete information about an entity class."""
+
class_name: str
cls: type
description: str
@@ -93,7 +110,13 @@ class EntityInfo:
dsl_examples: list[str] = field(default_factory=list)
-def get_all_enums() -> dict[str, EnumInfo]:
+# =============================================================================
+# Enum and Literal Type Detection
+# =============================================================================
+
+
+def get_all_int_enums() -> dict[str, EnumInfo]:
+ """Get all IntEnum types from draftsman.constants."""
enums = {}
for name in dir(constants):
if name.startswith("_"):
@@ -111,24 +134,115 @@ def get_all_enums() -> dict[str, EnumInfo]:
if hasattr(val, "__int__"):
members[member_name] = int(val)
else:
- members[member_name] = str(val) # type: ignore[assignment]
+ members[member_name] = str(val)
except (ValueError, TypeError):
- members[member_name] = str(member.value) # type: ignore[assignment]
- enums[name] = EnumInfo(name=name, members=members)
+ members[member_name] = str(member.value)
+ enums[name] = EnumInfo(name=name, members=members, is_literal=False)
return enums
-ALL_ENUMS = get_all_enums()
+def get_literal_values_from_type(type_hint: Any) -> tuple | None:
+ """Extract values from a Literal type annotation."""
+ origin = get_origin(type_hint)
+ if origin is Literal:
+ return get_args(type_hint)
+ # Handle Optional[Literal[...]] and similar
+ if origin is type(None) or str(origin) == "typing.Union":
+ for arg in get_args(type_hint):
+ if get_origin(arg) is Literal:
+ return get_args(arg)
+ return None
+
+
+def get_all_literal_types() -> dict[str, EnumInfo]:
+ """Get all unique Literal types used in entity properties."""
+ literal_types = {}
+
+ # Well-known Literal types to name them nicely
+ KNOWN_LITERALS = {
+ ("normal", "uncommon", "rare", "epic", "legendary", "quality-unknown"): "QualityID",
+ ("*", "/", "+", "-", "%", "^", "<<", ">>", "AND", "OR", "XOR"): "ArithmeticOperation",
+ (
+ "select",
+ "count",
+ "random",
+ "stack-size",
+ "rocket-capacity",
+ "quality-filter",
+ "quality-transfer",
+ ): "SelectorOperation",
+ ("at-least", "at-most", "exactly", "add", "remove"): "InfinityMode",
+ ("whitelist", "blacklist"): "FilterMode",
+ ("spoiled-first", "fresh-first"): "SpoilPriority",
+ ("input", "output"): "IOType",
+ ("local", "surface", "global"): "PlaybackMode",
+ ("left", "none", "right"): "SplitterPriority",
+ }
+
+ for name in dir(entity_module):
+ obj = getattr(entity_module, name)
+ if not isinstance(obj, type) or not issubclass(obj, Entity) or obj is Entity:
+ continue
+ if not attrs.has(obj):
+ continue
+
+ for fld in attrs.fields(obj):
+ if fld.type is None:
+ continue
+ literal_vals = get_literal_values_from_type(fld.type)
+ if literal_vals:
+ # Filter out None values
+ filtered_vals = tuple(v for v in literal_vals if v is not None)
+ if not filtered_vals:
+ continue
+
+ # Look for a known name
+ known_name = KNOWN_LITERALS.get(filtered_vals)
+ if known_name and known_name not in literal_types:
+ members = {str(v): v for v in filtered_vals}
+ literal_types[known_name] = EnumInfo(
+ name=known_name, members=members, is_literal=True
+ )
+
+ return literal_types
+
+
+ALL_INT_ENUMS = get_all_int_enums()
+ALL_LITERAL_TYPES = get_all_literal_types()
def find_enum_for_type(type_hint: Any) -> EnumInfo | None:
+ """Find an IntEnum that matches this type hint."""
type_str = str(type_hint) if type_hint else ""
- for enum_name, enum_info in ALL_ENUMS.items():
+ for enum_name, enum_info in ALL_INT_ENUMS.items():
if enum_name in type_str:
return enum_info
return None
+def find_literal_for_type(type_hint: Any) -> tuple[tuple | None, EnumInfo | None]:
+ """Find Literal values and corresponding EnumInfo for a type."""
+ literal_vals = get_literal_values_from_type(type_hint)
+ if not literal_vals:
+ return None, None
+
+ filtered_vals = tuple(v for v in literal_vals if v is not None)
+ if not filtered_vals:
+ return None, None
+
+ # Look for matching EnumInfo
+ for enum_info in ALL_LITERAL_TYPES.values():
+ if set(enum_info.members.keys()) == {str(v) for v in filtered_vals}:
+ return filtered_vals, enum_info
+
+ return filtered_vals, None
+
+
+# =============================================================================
+# Skip lists and signal definitions
+# =============================================================================
+
+
SKIP_PROPS = {
"_parent",
"_connections",
@@ -141,206 +255,170 @@ def find_enum_for_type(type_hint: Any) -> EnumInfo | None:
"mirror",
}
-# Detailed signal I/O definitions
-# Format: property_name -> (direction, signal_type, description, enable_property)
-# direction: "input" or "output"
-# signal_type: what kind of signal value is read/written
-# description: what this signal does
-# enable_property: the boolean property that enables this (or more complex string)
-SIGNAL_IO_DEFINITIONS = {
- # === COMMON INPUT SIGNALS ===
- "circuit_condition": (
- "input",
- "Any signal",
- "Signal used in enable/disable condition",
- "circuit_enabled: 1",
- ),
- "logistic_condition": (
- "input",
- "Any signal",
- "Logistic network signal for enable/disable",
- "connect_to_logistic_network: 1",
- ),
- "stack_size_control_signal": (
- "input",
- "Integer signal",
- "Sets inserter stack size from signal value",
- "circuit_set_stack_size: 1",
- ),
- # === LAMP SIGNALS ===
- # For COMPONENTS mode (color_mode: 1), use red_signal/green_signal/blue_signal
- # For PACKED_RGB mode (color_mode: 2), use rgb_signal
- "red_signal": (
- "input",
- "Integer (0-255)",
- "Red color component (COMPONENTS mode)",
- "use_colors: 1, color_mode: 1",
- ),
- "green_signal": (
- "input",
- "Integer (0-255)",
- "Green color component (COMPONENTS mode)",
- "use_colors: 1, color_mode: 1",
- ),
- "blue_signal": (
- "input",
- "Integer (0-255)",
- "Blue color component (COMPONENTS mode)",
- "use_colors: 1, color_mode: 1",
- ),
- "rgb_signal": (
- "input",
- "Packed RGB integer",
- "Combined RGB value (PACKED_RGB mode)",
- "use_colors: 1, color_mode: 2",
- ),
- # === RAIL SIGNAL OUTPUTS ===
- "red_output_signal": (
- "output",
- "1 when red",
- "Outputs 1 when rail signal shows red",
- "read_signal: 1",
- ),
- "yellow_output_signal": (
- "output",
- "1 when yellow",
- "Outputs 1 when rail signal shows yellow",
- "read_signal: 1",
- ),
- "green_output_signal": (
- "output",
- "1 when green",
- "Outputs 1 when rail signal shows green",
- "read_signal: 1",
- ),
- "blue_output_signal": ("output", "1 when blue", "Outputs 1 when chain signal reserved", None),
- # === TRAIN STOP SIGNALS ===
- "train_stopped_signal": (
- "output",
- "Train ID",
- "Outputs ID of stopped train",
- "read_stopped_train: 1",
- ),
- "trains_limit_signal": (
- "input",
- "Integer",
- "Sets train limit from signal value",
- "signal_limits_trains: 1",
- ),
- "trains_count_signal": (
- "output",
- "Integer",
- "Outputs count of trains en route",
- "read_trains_count: 1",
- ),
- "priority_signal": (
- "input",
- "Integer",
- "Sets station priority from signal value",
- "set_priority: 1",
- ),
- # === CRAFTING MACHINE SIGNALS ===
- "recipe_finished_signal": (
- "output",
- "Pulse (1)",
- "Pulses when recipe completes",
- "read_recipe_finished: 1",
- ),
- "working_signal": (
- "output",
- "1 when working",
- "Outputs 1 while machine is crafting",
- "read_working: 1",
- ),
- # === COMBINATOR SIGNALS ===
- "output_signal": ("output", "Result value", "Signal to output the combinator result on", None),
- "index_signal": ("input", "Integer", "Signal for selector index input", None),
- "count_signal": ("output", "Integer", "Signal to output the count result", None),
- "quality_source_signal": ("input", "Any signal", "Signal to read quality from", None),
- "quality_destination_signal": (
- "output",
- "Quality level",
- "Signal to output quality result",
- None,
- ),
- # === ROBOPORT SIGNALS ===
- "available_logistic_robots_signal": (
- "output",
- "Integer",
- "Count of idle logistic robots",
- "read_logistics: 1",
- ),
- "total_logistic_robots_signal": (
- "output",
- "Integer",
- "Total logistic robots in network",
- "read_logistics: 1",
- ),
- "available_construction_robots_signal": (
- "output",
- "Integer",
- "Count of idle construction robots",
- "read_logistics: 1",
- ),
- "total_construction_robots_signal": (
- "output",
- "Integer",
- "Total construction robots",
- "read_logistics: 1",
- ),
- # === ACCUMULATOR SIGNALS ===
- # Note: accumulator uses "output_signal" but that's also used by combinators
- # === SPEAKER SIGNALS ===
- "signal_value_is_pitch": ("input", "Pitch value", "Signal value controls note pitch", None),
- # === MINING DRILL SIGNALS ===
- # read_resources outputs resource signals based on what's under the drill
- # === ASTEROID COLLECTOR SIGNALS ===
- "status_signal": ("output", "Status code", "Current collector status", "read_status"),
- "storage_signal": ("output", "Item contents", "Items stored in collector", "read_contents"),
+# Properties that indicate signal I/O (usually for circuit configuration)
+SIGNAL_IO_PROPERTIES = {
+ # Combinator signals
+ "output_signal",
+ "index_signal",
+ "count_signal",
+ "quality_source_signal",
+ "quality_destination_signal",
+ # Rail signal outputs
+ "red_output_signal",
+ "yellow_output_signal",
+ "green_output_signal",
+ "blue_output_signal",
+ # Train stop
+ "train_stopped_signal",
+ "trains_limit_signal",
+ "trains_count_signal",
+ "priority_signal",
+ # Recipe/working signals
+ "recipe_finished_signal",
+ "working_signal",
+ # Roboport
+ "available_logistic_robots_signal",
+ "total_logistic_robots_signal",
+ "available_construction_robots_signal",
+ "total_construction_robots_signal",
+ # Lamp RGB
+ "red_signal",
+ "green_signal",
+ "blue_signal",
+ "rgb_signal",
+ # Circuit condition
+ "circuit_condition",
+ "logistic_condition",
+ # Stack control
+ "stack_size_control_signal",
+ # Others
+ "status_signal",
+ "storage_signal",
}
-# Properties that enable signal reading/writing (bool properties)
-SIGNAL_ENABLE_PROPERTIES = {
- "read_contents": "Enables outputting contents to circuit network",
- "read_hand_contents": "Enables outputting items in inserter hand",
- "read_resources": "Enables outputting resource amounts under entity",
- "read_signal": "Enables outputting rail signal state",
- "read_from_train": "Enables reading train cargo contents",
- "send_to_train": "Enables sending signals to train for schedule control",
- "read_stopped_train": "Enables outputting stopped train ID",
- "read_trains_count": "Enables outputting count of trains en route",
- "read_recipe_finished": "Enables recipe finished pulse signal",
- "read_working": "Enables outputting working status",
- "read_logistics": "Enables outputting robot counts",
- "read_robot_stats": "Enables outputting robot statistics",
- "circuit_enabled": "Enables circuit condition control",
- "connect_to_logistic_network": "Enables logistic network condition control",
- "circuit_set_filters": "Enables setting filters from circuit signals",
- "circuit_set_stack_size": "Enables setting stack size from signal",
- "circuit_set_recipe": "Enables setting recipe from circuit signals",
- "signal_limits_trains": "Enables setting train limit from signal",
- "set_priority": "Enables setting station priority from signal",
- "use_colors": "Enables color control from circuit signals",
- "read_status": "Enables outputting entity status",
- "read_ammo": "Enables outputting ammo count",
+# Entity class name -> output description mapping
+ENTITY_OUTPUT_DESCRIPTIONS = {
+ "Container": {
+ "supports_output": True,
+ "description": "Item contents of the container",
+ "enable_properties": {"read_contents": "Enable reading container contents"},
+ },
+ "StorageTank": {
+ "supports_output": True,
+ "description": "Fluid level in the tank",
+ "enable_properties": {}, # Always outputs
+ },
+ "Inserter": {
+ "supports_output": True,
+ "description": "Items in hand or filter status",
+ "enable_properties": {
+ "read_hand_contents": "Read items in hand",
+ "circuit_set_filters": "Control via filters",
+ },
+ },
+ "MiningDrill": {
+ "supports_output": True,
+ "description": "Resources under the drill",
+ "enable_properties": {"read_resources": "Read resource amounts"},
+ },
+ "TransportBelt": {
+ "supports_output": True,
+ "description": "Items on the belt",
+ "enable_properties": {"read_contents": "Read belt contents"},
+ },
+ "ArithmeticCombinator": {
+ "supports_output": True,
+ "description": "Computed arithmetic result",
+ "enable_properties": {}, # Always outputs
+ },
+ "DeciderCombinator": {
+ "supports_output": True,
+ "description": "Conditional output signals",
+ "enable_properties": {}, # Always outputs
+ },
+ "SelectorCombinator": {
+ "supports_output": True,
+ "description": "Selected/filtered signals",
+ "enable_properties": {}, # Always outputs
+ },
+ "ConstantCombinator": {
+ "supports_output": True,
+ "description": "Constant signal values",
+ "enable_properties": {}, # Always outputs
+ },
+ "Accumulator": {
+ "supports_output": True,
+ "description": "Charge level percentage",
+ "enable_properties": {},
+ },
+ "Roboport": {
+ "supports_output": True,
+ "description": "Robot counts and logistics info",
+ "enable_properties": {
+ "read_logistics": "Read logistic robot counts",
+ "read_robot_stats": "Read robot statistics",
+ },
+ },
+ "TrainStop": {
+ "supports_output": True,
+ "description": "Train ID, count, and cargo",
+ "enable_properties": {
+ "read_stopped_train": "Read stopped train ID",
+ "read_trains_count": "Read incoming trains count",
+ "read_from_train": "Read train cargo",
+ },
+ },
+ "RailSignal": {
+ "supports_output": True,
+ "description": "Signal state (red/yellow/green)",
+ "enable_properties": {"read_signal": "Read signal state"},
+ },
+ "RailChainSignal": {
+ "supports_output": True,
+ "description": "Signal state (red/yellow/green/blue)",
+ "enable_properties": {"read_signal": "Read signal state"},
+ },
+ "Lamp": {
+ "supports_output": False,
+ "description": "",
+ "enable_properties": {},
+ },
+ "AssemblingMachine": {
+ "supports_output": True,
+ "description": "Recipe finished pulse, working status",
+ "enable_properties": {
+ "read_recipe_finished": "Pulse when recipe completes",
+ "read_working": "Output working status",
+ },
+ },
}
-# Set of property names that are signal I/O and should be excluded from settable properties
-SIGNAL_PROPERTY_NAMES = set(SIGNAL_IO_DEFINITIONS.keys())
+
+# =============================================================================
+# Type conversion helpers
+# =============================================================================
-# DSL-friendly type mappings
-# Map Python/Draftsman types to user-friendly DSL types
-def get_dsl_type(type_str: str, prop_name: str) -> tuple[str, bool]:
- """
- Convert a Python type string to a DSL-friendly type description.
- Returns (dsl_type, is_supported) tuple.
- """
+def get_dsl_type(
+ type_str: str, prop_name: str, literal_vals: tuple | None, enum_info: EnumInfo | None
+) -> tuple[str, bool]:
+ """Convert Python type to DSL-friendly description. Returns (type_str, is_supported)."""
type_lower = type_str.lower()
+ # Literal types - show possible values
+ if literal_vals:
+ if enum_info:
+ values_str = ", ".join(f'`"{v}"`' for v in literal_vals)
+ return (
+ f"One of: {values_str} ([{enum_info.name}](#{enum_info.name.lower()}))",
+ True,
+ )
+ values_str = ", ".join(f'`"{v}"`' for v in literal_vals)
+ return f"One of: {values_str}", True
+
# Boolean types
if "bool" in type_lower:
- return "Boolean (0/1)", True
+ return "Boolean (`0` or `1`)", True
# String types
if prop_name in ("station", "player_description", "text"):
@@ -353,64 +431,46 @@ def get_dsl_type(type_str: str, prop_name: str) -> tuple[str, bool]:
# Integer types
if "int" in type_lower and "annotated" in type_lower:
return "Integer", True
- if prop_name in ("priority", "index_constant", "random_update_interval"):
+ if prop_name in ("priority", "index_constant", "random_update_interval", "bar"):
return "Integer", True
# Direction enum
if "direction" in type_lower:
- return "Integer (0-15, see Direction enum)", True
+ return "Integer (`0`-`15`, see [Direction](#direction))", True
# Color type
if "color" in type_lower and "mode" not in prop_name:
- return "Color {r: 0-255, g: 0-255, b: 0-255}", True
+ return "Object `{r, g, b}` (0-255 each)", True
- # Signal ID types
+ # Signal ID types - important for entity configuration
if "signalid" in type_lower:
- return "String (signal name)", True
+ return 'String (signal name, e.g. `"signal-A"`)', True
# Quality
if "quality" in type_lower and "literal" in type_lower:
- return "String (normal/uncommon/rare/epic/legendary)", True
-
- # Literal string types (extract values)
- if "literal[" in type_lower:
- # Try to extract the literal values
- import re
-
- match = re.search(r"literal\[([^\]]+)\]", type_str, re.IGNORECASE)
- if match:
- values = match.group(1).replace("'", "").replace('"', "")
- if len(values) < 60:
- return f"One of: {values}", True
- return "String (see type for valid values)", True
+ return "String ([QualityID](#qualityid))", True
# Enum types
if " dict[str, str]:
+ """Extract property docstrings from class source code."""
docstrings = {}
for klass in cls.__mro__:
if klass in (object,) or not hasattr(klass, "__module__"):
@@ -440,14 +500,21 @@ def get_docstrings_from_source(cls: type) -> dict[str, str]:
return docstrings
-def get_example_value(prop_name: str, prop_type: Any, enum_info: EnumInfo | None) -> str:
+def get_example_value(
+ prop_name: str, prop_type: Any, enum_info: EnumInfo | None, literal_vals: tuple | None
+) -> str:
+ """Generate an example value for a property."""
+ if literal_vals and len(literal_vals) > 0:
+ return f'"{literal_vals[0]}"'
if enum_info:
first_member = next(iter(enum_info.members.items()))
return f"{first_member[1]} # {first_member[0]}"
+
type_str = str(prop_type).lower() if prop_type else ""
+
if "bool" in type_str:
return "1"
- elif "signal" in prop_name.lower():
+ elif "signal" in prop_name.lower() and "signalid" in type_str:
return '"signal-A"'
elif "color" in prop_name.lower() and "mode" not in prop_name.lower():
return "{r: 255, g: 0, b: 0}"
@@ -457,18 +524,28 @@ def get_example_value(prop_name: str, prop_type: Any, enum_info: EnumInfo | None
return '"Station Name"'
elif prop_name == "recipe":
return '"iron-gear-wheel"'
+
return ""
+# =============================================================================
+# Entity information gathering
+# =============================================================================
+
+
def get_entity_properties(cls: type) -> list[PropertyInfo]:
+ """Extract all settable properties from an entity class."""
properties: list[PropertyInfo] = []
if not attrs.has(cls):
return properties
+
docstrings = get_docstrings_from_source(cls)
+
for fld in attrs.fields(cls):
name = fld.name
if name in SKIP_PROPS or name.startswith("_"):
continue
+
type_str = str(fld.type) if fld.type else "Any"
type_str = re.sub(r"typing\.", "", type_str)
type_str = re.sub(r"draftsman\.\w+\.", "", type_str)
@@ -476,32 +553,36 @@ def get_entity_properties(cls: type) -> list[PropertyInfo]:
if len(type_str) > 80:
type_str = type_str[:77] + "..."
- # Get DSL-friendly type
- dsl_type, is_supported = get_dsl_type(type_str, name)
+ # Check for enums and literals
+ enum_info = find_enum_for_type(fld.type)
+ literal_vals, literal_enum = find_literal_for_type(fld.type)
- # Check if this is a signal property
- is_signal_prop = name in SIGNAL_PROPERTY_NAMES
+ # Use literal enum if available
+ if literal_enum:
+ enum_info = literal_enum
- enum_info = find_enum_for_type(fld.type)
- is_enum = enum_info is not None
+ # Get DSL type
+ dsl_type, is_supported = get_dsl_type(type_str, name, literal_vals, enum_info)
- # Override DSL type for enums
- if is_enum and enum_info:
+ # Override for enums
+ if enum_info and not enum_info.is_literal:
dsl_type = f"Integer ([{enum_info.name}](#{enum_info.name.lower()}))"
is_supported = True
+ # Default value
if fld.default is attrs.NOTHING:
default = "required"
- elif isinstance(fld.default, attrs.Factory): # type: ignore[arg-type]
+ elif isinstance(fld.default, attrs.Factory):
default = "(factory)"
elif fld.default is None:
default = "None"
elif isinstance(fld.default, bool):
- default = "true" if fld.default else "false"
+ default = "1" if fld.default else "0"
elif isinstance(fld.default, str):
default = f'"{fld.default}"'
else:
default = str(fld.default)
+
prop = PropertyInfo(
name=name,
type_name=type_str,
@@ -509,71 +590,51 @@ def get_entity_properties(cls: type) -> list[PropertyInfo]:
python_type=fld.type,
default_value=default,
description=docstrings.get(name, ""),
- is_enum=is_enum,
+ is_enum=enum_info is not None,
enum_info=enum_info,
- example_value=get_example_value(name, fld.type, enum_info),
+ literal_values=literal_vals,
+ example_value=get_example_value(name, fld.type, enum_info, literal_vals),
is_dsl_supported=is_supported,
- is_signal_property=is_signal_prop,
+ is_signal_property=name in SIGNAL_IO_PROPERTIES,
)
properties.append(prop)
- # Sort alphabetically for consistency
+
properties.sort(key=lambda p: p.name)
return properties
def get_circuit_io_info(cls: type) -> CircuitIOInfo:
- """Extract circuit signal I/O information from an entity class."""
+ """Extract circuit I/O information from an entity class."""
info = CircuitIOInfo()
- mixin_names = [c.__name__ for c in cls.__mro__ if "Mixin" in c.__name__]
# Check for dual connection (combinators)
if cls.__name__ in ("ArithmeticCombinator", "DeciderCombinator", "SelectorCombinator"):
info.has_dual_connection = True
- # Get all property names from the entity
- prop_names = set()
- if attrs.has(cls):
- for fld in attrs.fields(cls):
- prop_names.add(fld.name)
-
- # Check for specific signal properties defined in SIGNAL_IO_DEFINITIONS
- for prop_name, (
- direction,
- signal_type,
- description,
- enable_prop,
- ) in SIGNAL_IO_DEFINITIONS.items():
- if prop_name in prop_names:
- entry = SignalIOEntry(
- property_name=prop_name,
- direction=direction,
- signal_type=signal_type,
- description=description,
- enable_property=enable_prop,
- )
- if direction == "input":
- info.signal_inputs.append(entry)
- else:
- info.signal_outputs.append(entry)
-
- # Check for generic content output capabilities based on mixins
- if "CircuitReadContentsMixin" in mixin_names and "read_contents" in prop_names:
- info.content_outputs.append(
- "Item contents (all items in entity, enable with `read_contents: 1`)"
- )
- if "CircuitReadHandMixin" in mixin_names and "read_hand_contents" in prop_names:
- info.content_outputs.append(
- "Inserter hand contents (items being moved, enable with `read_hand_contents: 1`)"
- )
- if "CircuitReadResourceMixin" in mixin_names and "read_resources" in prop_names:
- info.content_outputs.append(
- "Resource amounts (resources under entity, enable with `read_resources: 1`)"
+ # Get output info from class name
+ class_name = cls.__name__
+ if class_name in ENTITY_OUTPUT_DESCRIPTIONS:
+ output_desc = ENTITY_OUTPUT_DESCRIPTIONS[class_name]
+ info.output_info = EntityOutputInfo(
+ supports_output=output_desc["supports_output"],
+ description=output_desc["description"],
+ enable_properties=output_desc.get("enable_properties", {}),
)
+ else:
+ # Check mixins for output capability
+ mixin_names = [c.__name__ for c in cls.__mro__ if "Mixin" in c.__name__]
+ if any("CircuitRead" in m or "CircuitConnect" in m for m in mixin_names):
+ info.output_info = EntityOutputInfo(
+ supports_output=True,
+ description="Circuit network signals",
+ enable_properties={},
+ )
return info
def get_all_entity_classes() -> dict[str, type]:
+ """Get all entity classes from draftsman."""
classes = {}
for name in dir(entity_module):
obj = getattr(entity_module, name)
@@ -588,21 +649,23 @@ def get_all_entity_classes() -> dict[str, type]:
def get_entity_prototypes(cls: type) -> list[str]:
- """Get ALL prototypes for this entity class."""
+ """Get all prototypes for an entity class."""
try:
instance = cls()
if hasattr(instance, "similar_entities"):
- return list(instance.similar_entities) # No limit!
+ return list(instance.similar_entities)
return [instance.name] if hasattr(instance, "name") and instance.name else []
except Exception:
return []
def get_mixins(cls: type) -> list[str]:
+ """Get mixin class names."""
return [c.__name__ for c in cls.__mro__ if "Mixin" in c.__name__]
def get_entity_description(cls: type) -> str:
+ """Get entity class description."""
doc = cls.__doc__ or ""
lines = doc.strip().split("\n")
result: list[str] = []
@@ -613,61 +676,92 @@ def get_entity_description(cls: type) -> str:
if line:
result.append(line)
desc = " ".join(result) if result else "No description available."
- # Clean up sphinx directives
desc = re.sub(r"\.\.\s+\w+::\s*\S*", "", desc)
return desc.strip()
def get_dsl_examples(cls: type, class_name: str) -> list[str]:
+ """Get DSL code examples for an entity."""
examples = {
"Lamp": [
"# Basic lamp controlled by circuit",
'Entity lamp = place("small-lamp", 0, 0);',
"lamp.enable = signal > 0;",
"",
- "# RGB colored lamp",
+ "# RGB colored lamp (color_mode: 1 = COMPONENTS)",
'Entity rgb_lamp = place("small-lamp", 2, 0, {use_colors: 1, color_mode: 1});',
- "rgb_lamp.r = red_signal;",
- "rgb_lamp.g = green_signal;",
- "rgb_lamp.b = blue_signal;",
+ "rgb_lamp.r = red_value;",
+ "rgb_lamp.g = green_value;",
+ "rgb_lamp.b = blue_value;",
+ ],
+ "SelectorCombinator": [
+ "# Selector in count mode",
+ 'Entity counter = place("selector-combinator", 0, 0, {',
+ ' operation: "count",',
+ ' count_signal: "signal-C"',
+ "});",
+ "# Reading output",
+ "Bundle result = counter.output;",
],
"Inserter": [
"# Inserter that enables when chest has items",
'Entity inserter = place("inserter", 0, 0, {direction: 4});',
- "inserter.enable = chest_contents > 50;",
- ],
- "TransportBelt": [
- "# Belt that stops when storage is full",
- 'Entity belt = place("transport-belt", 0, 0, {direction: 4});',
- "belt.enable = storage_count < 1000;",
- ],
- "TrainStop": [
- "# Train station with circuit control",
- 'Entity station = place("train-stop", 0, 0, {station: "Iron Pickup"});',
- "station.enable = has_cargo > 0;",
+ "inserter.enable = chest.output > 50;",
],
- "AssemblingMachine": [
- "# Assembler controlled by circuit",
- 'Entity assembler = place("assembling-machine-1", 0, 0, {recipe: "iron-gear-wheel"});',
- "assembler.enable = iron_count > 100;",
+ "Container": [
+ "# Read chest contents",
+ 'Entity chest = place("iron-chest", 0, 0);',
+ "Bundle contents = chest.output;",
+ 'Signal iron = contents["iron-plate"];',
],
"ArithmeticCombinator": [
- "# Note: Combinators are typically generated by the compiler",
- "Signal result = input * 2 + offset; # Creates ArithmeticCombinator(s)",
+ "# Note: Arithmetic combinators are usually auto-generated",
+ "Signal result = input * 2 + offset; # Creates combinator(s)",
+ "",
+ "# Manual placement if needed",
+ 'Entity arith = place("arithmetic-combinator", 0, 0, {',
+ ' operation: "+"',
+ "});",
],
"DeciderCombinator": [
- "# Note: Combinators are typically generated by the compiler",
- "Signal flag = (count > 100) : 1; # Creates DeciderCombinator",
+ "# Note: Decider combinators are usually auto-generated",
+ "Signal flag = (count > 100) : 1; # Creates decider",
],
"ConstantCombinator": [
- "# Note: Constant combinators are typically generated by the compiler",
- "Signal constant = 42; # Creates ConstantCombinator",
+ "# Note: Constants are usually auto-generated",
+ "Signal constant = 42; # Creates constant combinator",
+ ],
+ "TrainStop": [
+ "# Train station with circuit control",
+ 'Entity station = place("train-stop", 0, 0, {station: "Iron Pickup"});',
+ "station.enable = cargo.output > 0;",
+ "# Read train info",
+ "Bundle train_info = station.output;",
],
}
return examples.get(class_name, [])
+def get_draftsman_source_link(cls: type) -> str:
+ """Generate a GitHub link to the draftsman source file for this entity."""
+ try:
+ source_file = inspect.getfile(cls)
+ # Extract relative path from draftsman package
+ if "draftsman" in source_file:
+ # Get path after 'draftsman/'
+ parts = source_file.split("draftsman/")
+ if len(parts) > 1:
+ rel_path = "draftsman/" + parts[-1]
+ # Link to forked repo
+ github_url = f"https://github.com/redruin1/factorio-draftsman/blob/main/{rel_path}"
+ return github_url
+ except (TypeError, OSError):
+ pass
+ return ""
+
+
def gather_entity_info(cls: type, class_name: str) -> EntityInfo:
+ """Gather all information about an entity class."""
return EntityInfo(
class_name=class_name,
cls=cls,
@@ -680,17 +774,23 @@ def gather_entity_info(cls: type, class_name: str) -> EntityInfo:
)
+# =============================================================================
+# Document generation
+# =============================================================================
+
+
def generate_enum_reference() -> list[str]:
+ """Generate the enum reference section."""
lines = [
"## Enum Reference",
"",
- "When setting enum properties in the DSL, use the **integer value**.",
- "This section lists all enums used by entity properties.",
+ "When setting enum properties in the DSL, use the **integer value** for IntEnums,",
+ "or the **string value** for Literal types.",
"",
]
- # List ALL enums that are actually used by entities
- relevant_enums = [
+ # IntEnums - only include relevant ones
+ relevant_int_enums = [
"LampColorMode",
"Direction",
"InserterModeOfOperation",
@@ -700,17 +800,18 @@ def generate_enum_reference() -> list[str]:
"LogisticModeOfOperation",
"MiningDrillReadMode",
"SiloReadMode",
- "SpaceConnectionReadMode",
- "AsteroidCollectorStatus",
]
- for enum_name in relevant_enums:
- if enum_name not in ALL_ENUMS:
+ lines.append("### Integer Enums")
+ lines.append("")
+
+ for enum_name in relevant_int_enums:
+ if enum_name not in ALL_INT_ENUMS:
continue
- enum_info = ALL_ENUMS[enum_name]
+ enum_info = ALL_INT_ENUMS[enum_name]
lines.extend(
[
- f'### {enum_name}',
+ f'#### {enum_name}',
"",
"| DSL Value | Enum Name |",
"|-----------|-----------|",
@@ -719,80 +820,78 @@ def generate_enum_reference() -> list[str]:
for member_name, value in enum_info.members.items():
lines.append(f"| `{value}` | {member_name} |")
lines.append("")
- return lines
-
-def format_type_with_enum_link(prop: PropertyInfo) -> str:
- """Format type string with enum link if applicable."""
- if prop.is_enum and prop.enum_info:
- return f"{prop.type_name} ([{prop.enum_info.name}](#{prop.enum_info.name.lower()}))"
- return prop.type_name
+ # Literal types
+ lines.append("### String Enums (Literal Types)")
+ lines.append("")
+ lines.append("These properties accept string values. Use the exact string shown.")
+ lines.append("")
+ for enum_name, enum_info in sorted(ALL_LITERAL_TYPES.items()):
+ lines.extend(
+ [
+ f'#### {enum_name}',
+ "",
+ "| Valid Values |",
+ "|-------------|",
+ ]
+ )
+ for value in enum_info.members:
+ lines.append(f'| `"{value}"` |')
+ lines.append("")
-# Track unsupported properties across all entities
-UNSUPPORTED_PROPERTIES: set[tuple[str, str]] = set() # (property_name, dsl_type)
+ return lines
def generate_entity_section(entity: EntityInfo) -> list[str]:
+ """Generate documentation section for a single entity."""
lines = [f"### {entity.class_name}", ""]
# Description
lines.append(f"**Description:** {entity.description}")
lines.append("")
- # ALL prototypes
+ # Draftsman source link
+ source_link = get_draftsman_source_link(entity.cls)
+ if source_link:
+ lines.append(f"**Draftsman Source:** [{entity.class_name} class]({source_link})")
+ lines.append("")
+
+ # Prototypes
if entity.prototypes:
- proto_str = ", ".join(f'`"{p}"`' for p in entity.prototypes)
+ proto_str = ", ".join(f'`"{p}"`' for p in entity.prototypes[:10])
+ if len(entity.prototypes) > 10:
+ proto_str += f", ... ({len(entity.prototypes)} total)"
lines.extend([f"**Prototypes:** {proto_str}", ""])
- # Circuit connection type
+ # Circuit connection info
io = entity.circuit_io
if io.has_dual_connection:
- lines.append(
- "**Connection Type:** Dual circuit connection (separate input and output sides)"
- )
+ lines.append("**Connection Type:** Dual circuit (separate input/output sides)")
else:
lines.append("**Connection Type:** Single circuit connection")
lines.append("")
- # === Circuit Signal I/O Section ===
- has_signal_io = io.signal_inputs or io.signal_outputs or io.content_outputs
- if has_signal_io:
- lines.append("#### Circuit Signal I/O")
+ # === Entity Output Section ===
+ output_info = io.output_info
+ if output_info.supports_output:
+ lines.append("#### Reading Entity Output")
+ lines.append("")
+ lines.append(f"Use `entity.output` to read: **{output_info.description}**")
+ lines.append("")
+ lines.append("```facto")
+ lines.append(
+ f'Entity e = place("{entity.prototypes[0] if entity.prototypes else entity.class_name.lower()}", 0, 0);'
+ )
+ lines.append("Bundle signals = e.output; # Returns all output signals")
+ lines.append("```")
lines.append("")
- # Signal Inputs Table
- if io.signal_inputs:
- lines.append("**Signal Inputs:**")
- lines.append("")
- lines.append("| Signal Property | Signal Type | Description | Enable With |")
- lines.append("|-----------------|-------------|-------------|-------------|")
- for sig in io.signal_inputs:
- enable = f"`{sig.enable_property}`" if sig.enable_property else "Always active"
- lines.append(
- f"| `{sig.property_name}` | {sig.signal_type} | {sig.description} | {enable} |"
- )
- lines.append("")
-
- # Signal Outputs Table
- if io.signal_outputs:
- lines.append("**Signal Outputs:**")
- lines.append("")
- lines.append("| Signal Property | Signal Type | Description | Enable With |")
- lines.append("|-----------------|-------------|-------------|-------------|")
- for sig in io.signal_outputs:
- enable = f"`{sig.enable_property}`" if sig.enable_property else "Always active"
- lines.append(
- f"| `{sig.property_name}` | {sig.signal_type} | {sig.description} | {enable} |"
- )
- lines.append("")
-
- # Content outputs (generic item/resource outputs)
- if io.content_outputs:
- lines.append("**Content Outputs:**")
+ if output_info.enable_properties:
+ lines.append("**Enable properties:**")
lines.append("")
- for content in io.content_outputs:
- lines.append(f"- {content}")
+ for prop, desc in output_info.enable_properties.items():
+ lines.append(f"- `{prop}`: {desc}")
lines.append("")
# DSL Examples
@@ -802,36 +901,44 @@ def generate_entity_section(entity: EntityInfo) -> list[str]:
lines.extend(["```", ""])
# === Settable Properties Section ===
- # Filter out signal properties (they're in the Circuit Signal I/O section)
settable_props = [p for p in entity.properties if not p.is_signal_property]
if settable_props:
lines.append("#### Settable Properties")
lines.append("")
- lines.append(
- 'Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = value`'
- )
+ lines.append('Set at placement: `place("name", x, y, {prop: value})`')
lines.append("")
lines.append("| Property | Type | Default | Example |")
lines.append("|----------|------|---------|---------|")
+
for prop in settable_props:
- # Use DSL type, not Python type
type_str = prop.dsl_type
- example = f"`{prop.example_value}`" if prop.example_value else ""
-
- # Track unsupported properties
if not prop.is_dsl_supported:
- UNSUPPORTED_PROPERTIES.add((prop.name, prop.dsl_type))
- type_str = f"{prop.dsl_type} ⚠️" # Mark as potentially unsupported
-
+ type_str = f"{prop.dsl_type} ⚠️"
+ example = f"`{prop.example_value}`" if prop.example_value else ""
lines.append(f"| `{prop.name}` | {type_str} | {prop.default_value} | {example} |")
lines.append("")
+ # Signal configuration properties
+ signal_props = [p for p in entity.properties if p.is_signal_property]
+ if signal_props:
+ lines.append("#### Signal Configuration")
+ lines.append("")
+ lines.append("Properties for configuring which signals the entity uses:")
+ lines.append("")
+ lines.append("| Property | Type | Description |")
+ lines.append("|----------|------|-------------|")
+ for prop in signal_props:
+ lines.append(
+ f"| `{prop.name}` | {prop.dsl_type} | {prop.description[:60]}{'...' if len(prop.description) > 60 else ''} |"
+ )
+ lines.append("")
+
lines.extend(["---", ""])
return lines
-# Entity categorization - will add uncategorized entities to "Other" automatically
+# Entity categorization
ENTITY_CATEGORIES = {
"Combinators": [
"ArithmeticCombinator",
@@ -847,7 +954,6 @@ def generate_entity_section(entity: EntityInfo) -> list[str]:
"Splitter",
"Loader",
"LinkedBelt",
- "LaneLaneSplitter",
],
"Train System": [
"TrainStop",
@@ -857,18 +963,6 @@ def generate_entity_section(entity: EntityInfo) -> list[str]:
"CargoWagon",
"FluidWagon",
"ArtilleryWagon",
- "StraightRail",
- "CurvedRailA",
- "CurvedRailB",
- "HalfDiagonalRail",
- "RailRamp",
- "RailSupport",
- "ElevatedStraightRail",
- "ElevatedCurvedRailA",
- "ElevatedCurvedRailB",
- "ElevatedHalfDiagonalRail",
- "LegacyStraightRail",
- "LegacyCurvedRail",
],
"Production": [
"AssemblingMachine",
@@ -879,13 +973,7 @@ def generate_entity_section(entity: EntityInfo) -> list[str]:
"Beacon",
"Boiler",
"Generator",
- "BurnerGenerator",
"Reactor",
- "FusionReactor",
- "FusionGenerator",
- "LightningAttractor",
- "LightningRod",
- "AgriculturalTower",
],
"Storage": [
"Container",
@@ -894,16 +982,14 @@ def generate_entity_section(entity: EntityInfo) -> list[str]:
"LogisticStorageContainer",
"LogisticRequestContainer",
"LogisticBufferContainer",
- "LinkedContainer",
],
"Power": [
"ElectricPole",
"PowerSwitch",
"Accumulator",
"SolarPanel",
- "ElectricEnergyInterface",
],
- "Fluids": ["Pump", "StorageTank", "OffshorePump", "Pipe", "PipeToGround", "InfinityPipe"],
+ "Fluids": ["Pump", "StorageTank", "OffshorePump", "Pipe", "PipeToGround"],
"Combat": [
"Radar",
"ArtilleryTurret",
@@ -912,9 +998,9 @@ def generate_entity_section(entity: EntityInfo) -> list[str]:
"FluidTurret",
"Wall",
"Gate",
- "Landmine",
+ "LandMine",
],
- "Robots & Logistics": ["Roboport", "ConstructionRobot", "LogisticRobot"],
+ "Robots & Logistics": ["Roboport"],
"Space": ["SpacePlatformHub", "CargoLandingPad", "AsteroidCollector", "CargoBay", "Thruster"],
"Misc": [
"ProgrammableSpeaker",
@@ -922,13 +1008,12 @@ def generate_entity_section(entity: EntityInfo) -> list[str]:
"SpiderVehicle",
"HeatPipe",
"HeatInterface",
- "SimpleEntityWithOwner",
- "SimpleEntityWithForce",
],
}
def generate_documentation() -> str:
+ """Generate complete documentation."""
lines = [
"# Entity Reference for Facto",
"",
@@ -936,19 +1021,19 @@ def generate_documentation() -> str:
f"**Draftsman version:** {draftsman.__version__}",
"",
"This is the **complete reference** for all entities available in the DSL.",
- "Each entity lists its prototypes, circuit I/O capabilities, and all settable properties.",
"",
"## Table of Contents",
"",
"- [Using Entities in the DSL](#using-entities-in-the-dsl)",
+ "- [Reading Entity Outputs](#reading-entity-outputs)",
"- [Enum Reference](#enum-reference)",
]
- # Add category links
+ # Category links
for category in ENTITY_CATEGORIES:
anchor = category.lower().replace(" ", "-").replace("&", "").replace(" ", "-")
lines.append(f"- [{category}](#{anchor})")
- lines.append("- [Uncategorized Entities](#uncategorized-entities)")
+ lines.append("- [Other Entities](#other-entities)")
lines.append("")
# Usage section
@@ -956,6 +1041,18 @@ def generate_documentation() -> str:
[
"## Using Entities in the DSL",
"",
+ "### How the Compiler Handles Entities",
+ "",
+ "When you use `place()` in Facto, the compiler creates a corresponding",
+ "[Draftsman](https://github.com/Snagnar/factorio-draftsman) entity object.",
+ "Properties specified in the placement object (the `{...}` part) are passed directly",
+ "to Draftsman as Python attributes during entity construction. The compiler validates",
+ "that property names and types match what Draftsman expects for that entity class.",
+ "",
+ "Circuit-controlled properties (like `entity.enable = expression`) are handled differently:",
+ "the compiler generates the necessary combinator logic and wire connections to implement",
+ "the circuit behavior, then sets the appropriate control properties on the entity.",
+ "",
"### Placement Syntax",
"",
"```facto",
@@ -964,32 +1061,58 @@ def generate_documentation() -> str:
"",
"### Setting Properties",
"",
- "**At placement time** (in the property dictionary):",
+ "**At placement time:**",
"```facto",
'Entity lamp = place("small-lamp", 0, 0, {use_colors: 1, color_mode: 1});',
"```",
"",
- "**After placement** (for circuit-controlled values):",
+ "**After placement (circuit-controlled):**",
"```facto",
- "lamp.enable = signal > 0; # Control based on circuit signal",
- "lamp.r = red_value; # Dynamic RGB control",
+ "lamp.enable = signal > 0;",
+ "lamp.r = red_value;",
"```",
"",
- "### Enum Properties",
+ ]
+ )
+
+ # Reading Entity Outputs - NEW SECTION
+ lines.extend(
+ [
+ "## Reading Entity Outputs",
"",
- "Enum properties accept **integer values**. See the [Enum Reference](#enum-reference) for all values.",
+ "Most entities can output circuit signals. Access them using `.output`:",
"",
"```facto",
- 'Entity lamp = place("small-lamp", 0, 0, {color_mode: 1}); # 1 = COMPONENTS',
- "```",
+ "# Read all signals from a container",
+ 'Entity chest = place("iron-chest", 0, 0, {read_contents: 1});',
+ "Bundle contents = chest.output;",
"",
- "### Boolean Properties",
+ "# Extract a specific signal",
+ 'Signal iron_count = contents["iron-plate"];',
"",
- "Boolean properties accept `1` (true) or `0` (false):",
- "```facto",
- 'Entity lamp = place("small-lamp", 0, 0, {use_colors: 1, always_on: 1});',
+ "# Use in calculations",
+ "Signal need_more = (iron_count < 100) : 1;",
"```",
"",
+ "### Output Types by Entity",
+ "",
+ "| Entity Type | What `.output` Returns | Enable Property |",
+ "|-------------|------------------------|-----------------|",
+ "| Combinators | Computed result signals | (always active) |",
+ "| Containers | Item counts | `read_contents: 1` |",
+ "| Storage Tanks | Fluid level | (always active) |",
+ "| Inserters | Hand contents | `read_hand_contents: 1` |",
+ "| Belts | Belt contents | `read_contents: 1` |",
+ "| Mining Drills | Resource amounts | `read_resources: 1` |",
+ "| Train Stops | Train ID, count | `read_stopped_train: 1`, etc. |",
+ "| Rail Signals | Signal state | `read_signal: 1` |",
+ "| Roboports | Robot counts | `read_logistics: 1` |",
+ "",
+ "### Note on Combinators",
+ "",
+ "For combinators (arithmetic, decider, selector), the `.output` reads from the",
+ "**output side** of the combinator, which is the result of its computation.",
+ "",
]
)
@@ -998,13 +1121,10 @@ def generate_documentation() -> str:
# Get all entity classes
entity_classes = get_all_entity_classes()
-
- # Track which entities are categorized
categorized = set()
# Generate sections by category
for category, class_names in ENTITY_CATEGORIES.items():
- # Only include category if it has any entities
category_entities = [name for name in class_names if name in entity_classes]
if not category_entities:
continue
@@ -1019,60 +1139,42 @@ def generate_documentation() -> str:
entity = gather_entity_info(cls, class_name)
lines.extend(generate_entity_section(entity))
- # Generate sections for ALL uncategorized entities (no truncation!)
+ # Uncategorized entities
uncategorized = [name for name in sorted(entity_classes.keys()) if name not in categorized]
if uncategorized:
lines.extend(
[
- "## Uncategorized Entities",
+ "## Other Entities",
"",
- "The following entities are available but not yet categorized.",
- "They still have full documentation below.",
+ "Additional entities not in the main categories:",
"",
]
)
-
for class_name in uncategorized:
cls = entity_classes[class_name]
entity = gather_entity_info(cls, class_name)
lines.extend(generate_entity_section(entity))
- # Add notes section about unsupported/complex types
- if UNSUPPORTED_PROPERTIES:
- lines.extend(
- [
- "## Notes on Complex Property Types",
- "",
- "Some properties marked with ⚠️ have complex types that may not be directly settable",
- "in the current DSL syntax. These typically include:",
- "",
- "| Type | Description | Workaround |",
- "|------|-------------|------------|",
- "| List | Arrays of items/filters | May require special syntax |",
- "| Condition | Circuit conditions | Use `.enable = signal > value` syntax |",
- "| Vector | Position offsets | Use `{x: value, y: value}` |",
- "| Complex | Other structured data | See draftsman documentation |",
- "",
- "For full details on complex types, refer to the ",
- "[Draftsman documentation](https://factorio-draftsman.readthedocs.io/en/latest/).",
- "",
- ]
- )
-
return "\n".join(lines)
def main():
- parser = argparse.ArgumentParser(
- description="Generate comprehensive entity documentation for the Facto"
- )
+ parser = argparse.ArgumentParser(description="Generate entity documentation for the Facto")
parser.add_argument("--output", "-o", help="Output file path (default: stdout)")
+ parser.add_argument("--update", help="Update existing file (targeted changes)")
args = parser.parse_args()
print("Gathering entity information...", file=sys.stderr)
content = generate_documentation()
- if args.output:
+ if args.update:
+ # TODO: Implement targeted update mode
+ # For now, just regenerate
+ print("Note: Full regeneration mode (targeted update not yet implemented)", file=sys.stderr)
+ with open(args.update, "w", encoding="utf-8") as f:
+ f.write(content)
+ print(f"Documentation written to: {args.update}", file=sys.stderr)
+ elif args.output:
with open(args.output, "w", encoding="utf-8") as f:
f.write(content)
print(f"Documentation written to: {args.output}", file=sys.stderr)
diff --git a/dsl_compiler/src/common/constants.py b/dsl_compiler/src/common/constants.py
index e007d68..c3c0e7e 100644
--- a/dsl_compiler/src/common/constants.py
+++ b/dsl_compiler/src/common/constants.py
@@ -45,7 +45,7 @@ class CompilerConfig:
"""
# Layout Optimization
- layout_solver_time_limit: int = 20
+ layout_solver_time_limit: int = 30
max_layout_coordinate: int = 200
acceptable_layout_violations: int = 1
# Per-stage violation limits: (strict, relaxed_span, larger_area, both_relaxed, very_relaxed)
diff --git a/dsl_compiler/src/common/entity_data.py b/dsl_compiler/src/common/entity_data.py
index 7b3b0dd..5cc3d43 100644
--- a/dsl_compiler/src/common/entity_data.py
+++ b/dsl_compiler/src/common/entity_data.py
@@ -1,13 +1,36 @@
"""Entity data extraction from draftsman."""
import math
+from functools import lru_cache
from draftsman.data import entities as entity_data
+from draftsman.entity import new_entity
class EntityDataHelper:
"""Helper to extract entity information from draftsman data."""
+ @staticmethod
+ @lru_cache(maxsize=128)
+ def is_dual_circuit_connectable(prototype: str) -> bool:
+ """Check if an entity type has separate input/output circuit connectors.
+
+ Entities like arithmetic-combinator, decider-combinator, and selector-combinator
+ have dual circuit connectors (input side and output side). When connecting wires
+ to these entities, you must specify which side (input/output) the wire connects to.
+
+ Args:
+ prototype: Entity prototype name (e.g., "selector-combinator")
+
+ Returns:
+ True if the entity has dual circuit connectors, False otherwise
+ """
+ try:
+ entity = new_entity(prototype)
+ return getattr(entity, "dual_circuit_connectable", False)
+ except Exception:
+ return False
+
@staticmethod
def get_footprint(prototype: str) -> tuple[int, int]:
"""Get entity footprint size dynamically from draftsman.
@@ -54,3 +77,4 @@ def get_alignment(prototype: str) -> int:
get_entity_footprint = EntityDataHelper.get_footprint
get_entity_alignment = EntityDataHelper.get_alignment
+is_dual_circuit_connectable = EntityDataHelper.is_dual_circuit_connectable
diff --git a/dsl_compiler/src/ir/builder.py b/dsl_compiler/src/ir/builder.py
index 21db367..a205dec 100644
--- a/dsl_compiler/src/ir/builder.py
+++ b/dsl_compiler/src/ir/builder.py
@@ -356,6 +356,58 @@ def bundle_arithmetic(
self.add_operation(arith_op)
return BundleRef(bundle.signal_types.copy(), node_id, source_ast=source_ast)
+ def bundle_decider(
+ self,
+ op: str,
+ bundle: BundleRef,
+ compare_value: ValueRef,
+ copy_count_from_input: bool = True,
+ output_value: int = 1,
+ source_ast: ASTNode | None = None,
+ ) -> BundleRef:
+ """Create a decider combinator using signal-each for bundle filtering.
+
+ Compares each signal in the input bundle against compare_value.
+ Outputs only the signals that pass the comparison.
+
+ Args:
+ op: Comparison operator (>, <, ==, !=, >=, <=)
+ bundle: Input bundle to filter
+ compare_value: Value to compare each signal against (scalar)
+ copy_count_from_input: If True, output matching signals with their counts.
+ If False, output constant value for each matching signal.
+ output_value: The constant to output when copy_count_from_input=False.
+ source_ast: Source AST for debugging
+
+ Returns:
+ BundleRef pointing to the decider's output (filtered bundle)
+ """
+ from dsl_compiler.src.ir.nodes import IRDecider
+
+ node_id = self.next_id("bundle_decider")
+
+ # Create decider with signal-each as both left operand and output
+ decider = IRDecider(node_id, "signal-each", source_ast)
+ decider.test_op = op
+ decider.left = SignalRef("signal-each", bundle.source_id)
+ decider.right = compare_value
+ decider.copy_count_from_input = copy_count_from_input
+ decider.output_value = output_value
+
+ # If the compare_value is a signal (not a constant), we need wire separation
+ # to prevent the scalar signal from being processed by "each"
+ if isinstance(compare_value, SignalRef):
+ decider.debug_metadata["needs_wire_separation"] = True
+ decider.debug_metadata["scalar_signal_id"] = compare_value.source_id
+
+ self.add_operation(decider)
+
+ return BundleRef(
+ bundle.signal_types.copy(),
+ node_id,
+ source_ast=source_ast,
+ )
+
def get_ir(self) -> list[IRNode]:
"""Return a copy of the currently built IR operations."""
diff --git a/dsl_compiler/src/layout/connection_planner.py b/dsl_compiler/src/layout/connection_planner.py
index 1619d36..579b461 100644
--- a/dsl_compiler/src/layout/connection_planner.py
+++ b/dsl_compiler/src/layout/connection_planner.py
@@ -8,6 +8,7 @@
from dsl_compiler.src.common.constants import DEFAULT_CONFIG, CompilerConfig
from dsl_compiler.src.common.diagnostics import ProgramDiagnostics
+from dsl_compiler.src.common.entity_data import is_dual_circuit_connectable
from dsl_compiler.src.ir.builder import BundleRef, SignalRef
from .layout_plan import LayoutPlan, WireConnection
@@ -1069,23 +1070,23 @@ def _log_unresolved_conflicts(self) -> None:
def _get_connection_side(self, entity_id: str, is_source: bool) -> str | None:
"""Determine if entity needs 'input'/'output' side specified.
+ Entities with dual circuit connectors (like arithmetic-combinator, decider-combinator,
+ selector-combinator) have separate input and output sides. When wiring these entities,
+ we need to specify which side to connect to.
+
Args:
entity_id: Entity to check
is_source: True if this entity is producing the signal
Returns:
- 'output' for source combinators, 'input' for sink combinators, None otherwise
+ 'output' for source side of dual-connectable entities,
+ 'input' for sink side, None otherwise
"""
placement = self.layout_plan.get_placement(entity_id)
if not placement:
return None
- combinator_types = {
- "arithmetic-combinator",
- "decider-combinator",
- }
-
- if placement.entity_type in combinator_types:
+ if is_dual_circuit_connectable(placement.entity_type):
return "output" if is_source else "input"
return None
diff --git a/dsl_compiler/src/layout/entity_placer.py b/dsl_compiler/src/layout/entity_placer.py
index de5b788..c097ca3 100644
--- a/dsl_compiler/src/layout/entity_placer.py
+++ b/dsl_compiler/src/layout/entity_placer.py
@@ -538,6 +538,13 @@ def _place_entity_prop_write(self, op: IREntityPropWrite) -> None:
"type": "signal",
"signal_ref": op.value,
}
+ elif isinstance(op.value, BundleRef):
+ # Bundle reference - register as signal sink to create wire connection
+ self._add_signal_sink(op.value, op.entity_id)
+ placement.properties["property_writes"][op.property_name] = {
+ "type": "bundle",
+ "bundle_ref": op.value,
+ }
elif isinstance(op.value, int):
placement.properties["property_writes"][op.property_name] = {
"type": "constant",
diff --git a/dsl_compiler/src/layout/integer_layout_solver.py b/dsl_compiler/src/layout/integer_layout_solver.py
index 304e824..f1718a2 100644
--- a/dsl_compiler/src/layout/integer_layout_solver.py
+++ b/dsl_compiler/src/layout/integer_layout_solver.py
@@ -787,6 +787,7 @@ def _add_edge_layout_constraints(self, model: cp_model.CpModel, positions: dict)
- All inputs share Y = Y_input_line (a variable)
- All outputs share Y = Y_output_line (a variable)
- All intermediates have Y strictly between these lines
+ - Fixed-position entities also constrain intermediate placement
- Optimizer minimizes bounding box, naturally placing:
* Y_input_line at minimum feasible Y
* Y_output_line at maximum feasible Y
@@ -799,28 +800,47 @@ def _add_edge_layout_constraints(self, model: cp_model.CpModel, positions: dict)
model: CP-SAT constraint model
positions: Dict mapping entity_id to (x, y) variables
"""
- # Categorize entities (skip user-fixed entities)
+ # Categorize entities - track both fixed and non-fixed separately
input_entities = []
output_entities = []
intermediate_entities = []
+ fixed_input_y_values: list[int] = []
+ fixed_output_y_values: list[int] = []
- for entity_id in self.entity_ids:
- if entity_id in self.fixed_positions:
- continue
+ def is_input_entity(p: EntityPlacement) -> bool:
+ """Check if placement is an input (via properties or role)."""
+ return p.properties.get("is_input") or p.role == "input"
+ def is_output_entity(p: EntityPlacement) -> bool:
+ """Check if placement is an output (via properties or role)."""
+ return p.properties.get("is_output") or p.role == "output"
+
+ for entity_id in self.entity_ids:
placement = self.entity_placements.get(entity_id)
if not placement:
continue
- if placement.properties.get("is_input"):
+ if entity_id in self.fixed_positions:
+ # Track fixed positions for boundary constraints
+ fixed_y = self.fixed_positions[entity_id][1]
+ fixed_height = self.footprints.get(entity_id, (1, 1))[1]
+ if is_input_entity(placement):
+ fixed_input_y_values.append(fixed_y + fixed_height) # Bottom edge
+ elif is_output_entity(placement):
+ fixed_output_y_values.append(fixed_y + fixed_height) # Bottom edge
+ continue
+
+ if is_input_entity(placement):
input_entities.append(entity_id)
- elif placement.properties.get("is_output"):
+ elif is_output_entity(placement):
output_entities.append(entity_id)
else:
intermediate_entities.append(entity_id)
- # If no inputs or outputs, no edge constraints needed
- if not input_entities and not output_entities:
+ # If no inputs or outputs (fixed or non-fixed), no edge constraints needed
+ has_inputs = input_entities or fixed_input_y_values
+ has_outputs = output_entities or fixed_output_y_values
+ if not has_inputs and not has_outputs:
self.diagnostics.info("No edge layout constraints (no inputs/outputs marked)")
return
@@ -880,6 +900,7 @@ def _add_edge_layout_constraints(self, model: cp_model.CpModel, positions: dict)
model.Add(y == Y_output_line)
# Constrain intermediates to be between input and output lines
+ # This includes both non-fixed input/output lines AND fixed positions
for entity_id in intermediate_entities:
_, y = positions[entity_id]
height = self.footprints.get(entity_id, (1, 1))[1]
@@ -890,6 +911,15 @@ def _add_edge_layout_constraints(self, model: cp_model.CpModel, positions: dict)
if Y_output_line is not None:
model.Add(y + height <= Y_output_line)
+ # Also constrain based on fixed-position outputs
+ # Intermediates must be below fixed outputs (higher Y values)
+ for fixed_output_bottom in fixed_output_y_values:
+ model.Add(y >= fixed_output_bottom)
+ self.diagnostics.info(
+ f"Edge layout: intermediate {entity_id} must have y >= {fixed_output_bottom} "
+ "(below fixed output)"
+ )
+
def _create_objective(
self,
model: cp_model.CpModel,
diff --git a/dsl_compiler/src/lowering/expression_lowerer.py b/dsl_compiler/src/lowering/expression_lowerer.py
index 93363f3..b889e2c 100644
--- a/dsl_compiler/src/lowering/expression_lowerer.py
+++ b/dsl_compiler/src/lowering/expression_lowerer.py
@@ -642,16 +642,23 @@ def _fold_binary_constant(self, op: str, left: int, right: int, node: Expr) -> i
"""
return ConstantFolder.fold_binary_operation(op, left, right, node, self.diagnostics)
- def lower_output_spec_expr(self, expr: OutputSpecExpr) -> SignalRef:
+ def lower_output_spec_expr(self, expr: OutputSpecExpr) -> ValueRef:
"""Lower output specifier expression to decider with copy-count-from-input.
(condition) : output_value
The condition must be a comparison or logical AND/OR of comparisons.
When true, outputs the output_value instead of constant 1.
+
+ Special case: Bundle filter pattern (bundle CMP scalar) : bundle
+ Uses signal-each decider to filter the bundle to only matching signals.
"""
condition = expr.condition
+ # Check for bundle filter pattern FIRST: (bundle CMP scalar) : output
+ if self._is_bundle_filter_pattern(expr):
+ return self._lower_bundle_filter_output_spec(expr)
+
# Check if this is a compound condition (AND/OR of comparisons)
if isinstance(condition, BinaryOp) and condition.op in ("&&", "||", "and", "or"):
return self._lower_compound_output_spec(expr)
@@ -879,6 +886,79 @@ def _lower_compound_output_spec(self, expr: OutputSpecExpr) -> SignalRef:
self._attach_expr_context(result.source_id, expr)
return result
+ # -------------------------------------------------------------------------
+ # Bundle filter output spec: (bundle CMP scalar) : bundle
+ # -------------------------------------------------------------------------
+
+ def _is_bundle_filter_pattern(self, expr: OutputSpecExpr) -> bool:
+ """Check if expression is a bundle filter pattern: (bundle CMP scalar) : output.
+
+ Returns True if:
+ - Condition is a binary comparison (>, <, ==, etc.)
+ - Left operand of comparison is a Bundle
+ """
+ condition = expr.condition
+ if not isinstance(condition, BinaryOp):
+ return False
+ if condition.op not in COMPARISON_OPS:
+ return False
+ left_type = self.semantic.get_expr_type(condition.left)
+ return isinstance(left_type, BundleValue)
+
+ def _lower_bundle_filter_output_spec(self, expr: OutputSpecExpr) -> BundleRef:
+ """Lower (bundle CMP scalar) : output to signal-each decider.
+
+ Creates a decider combinator that:
+ - Uses signal-each as the condition input
+ - Compares each signal against the scalar
+ - Outputs only matching signals
+
+ If output is a bundle: copy_count_from_input=True (preserve values)
+ If output is an integer: copy_count_from_input=False (constant per signal)
+ """
+ comparison = expr.condition
+ assert isinstance(comparison, BinaryOp)
+
+ # Lower the bundle (left operand of comparison)
+ bundle_ref = self.lower_expr(comparison.left)
+ if not isinstance(bundle_ref, BundleRef):
+ self._error("Expected bundle in bundle filter condition", comparison.left)
+ return BundleRef(set(), "error")
+
+ # Lower the scalar (right operand of comparison)
+ scalar_ref = self.lower_expr(comparison.right)
+
+ # Determine output mode from the output value type
+ output_type = self.semantic.get_expr_type(expr.output_value)
+
+ if isinstance(output_type, BundleValue):
+ # Output is a bundle: use copy_count_from_input mode
+ # This preserves the original signal values
+ result = self.ir_builder.bundle_decider(
+ op=comparison.op,
+ bundle=bundle_ref,
+ compare_value=scalar_ref,
+ copy_count_from_input=True,
+ source_ast=expr,
+ )
+ else:
+ # Output is a constant: output that constant for each matching signal
+ output_const = ConstantFolder.extract_constant_int(expr.output_value, self.diagnostics)
+ if output_const is None:
+ output_const = 1 # Default to 1 if not a constant
+
+ result = self.ir_builder.bundle_decider(
+ op=comparison.op,
+ bundle=bundle_ref,
+ compare_value=scalar_ref,
+ copy_count_from_input=False,
+ output_value=output_const,
+ source_ast=expr,
+ )
+
+ self._attach_expr_context(result.source_id, expr)
+ return result
+
def lower_unary_op(self, expr: UnaryOp) -> ValueRef:
operand_type = self.semantic.get_expr_type(expr.expr)
result_type = self.semantic.get_expr_type(expr)
diff --git a/dsl_compiler/src/lowering/tests/test_bundles.py b/dsl_compiler/src/lowering/tests/test_bundles.py
index c218d13..32fd8be 100644
--- a/dsl_compiler/src/lowering/tests/test_bundles.py
+++ b/dsl_compiler/src/lowering/tests/test_bundles.py
@@ -304,3 +304,162 @@ def test_bundle_all_comparison_creates_decider(self, parser, analyzer, diagnosti
None,
)
assert all_decider is not None
+
+
+class TestBundleFilter:
+ """Test bundle filter pattern: (bundle > 0) : bundle.
+
+ This pattern filters a bundle to include only signals that pass
+ the comparison, using a decider combinator with signal-each.
+ """
+
+ @pytest.fixture
+ def parser(self):
+ return DSLParser()
+
+ @pytest.fixture
+ def diagnostics(self):
+ return ProgramDiagnostics()
+
+ @pytest.fixture
+ def analyzer(self, diagnostics):
+ return SemanticAnalyzer(diagnostics)
+
+ def test_bundle_filter_parses(self, parser):
+ """Bundle filter syntax should parse correctly."""
+ code = """
+ Bundle b = { ("signal-A", 10), ("signal-B", -5) };
+ Bundle filtered = (b > 0) : b;
+ """
+ program = parser.parse(code)
+ assert len(program.statements) == 2
+
+ def test_bundle_filter_type_inference(self, parser, diagnostics, analyzer):
+ """Bundle filter should return BundleValue type."""
+ code = """
+ Bundle b = { ("signal-A", 10), ("signal-B", -5) };
+ Bundle filtered = (b > 0) : b;
+ """
+ program = parser.parse(code)
+ analyzer.visit(program)
+
+ assert not diagnostics.has_errors(), diagnostics.get_messages()
+ symbol = analyzer.symbol_table.lookup("filtered")
+ assert symbol is not None
+ assert isinstance(symbol.value_type, BundleValue)
+
+ def test_bundle_filter_with_different_operators(self, parser, diagnostics, analyzer):
+ """Bundle filter should work with various comparison operators."""
+ operators = [">", "<", ">=", "<=", "==", "!="]
+ for op in operators:
+ code = f"""
+ Bundle b = {{ ("signal-A", 10) }};
+ Bundle result = (b {op} 5) : b;
+ """
+ program = parser.parse(code)
+ diagnostics_local = ProgramDiagnostics()
+ analyzer_local = SemanticAnalyzer(diagnostics_local)
+ analyzer_local.visit(program)
+
+ assert not diagnostics_local.has_errors(), (
+ f"Failed for operator {op}: {diagnostics_local.get_messages()}"
+ )
+
+ def test_bundle_filter_creates_each_decider(self, parser, analyzer, diagnostics):
+ """Bundle filter should create decider with signal-each input and output."""
+ code = """
+ Bundle b = { ("signal-A", 10), ("signal-B", -5) };
+ Bundle filtered = (b > 0) : b;
+ """
+ program = parser.parse(code)
+ analyzer.visit(program)
+ ir_operations, lower_diags, _ = lower_program(program, analyzer)
+
+ assert not lower_diags.has_errors(), lower_diags.get_messages()
+
+ # Should have decider combinator for filtering
+ deciders = [op for op in ir_operations if isinstance(op, IRDecider)]
+ assert len(deciders) >= 1
+
+ # Find the bundle filter decider (uses signal-each as both input and output)
+ filter_decider = next(
+ (
+ d
+ for d in deciders
+ if (
+ isinstance(d.left, SignalRef)
+ and d.left.signal_type == "signal-each"
+ and d.output_type == "signal-each"
+ )
+ ),
+ None,
+ )
+ assert filter_decider is not None, "No decider with signal-each input and output found"
+ assert filter_decider.copy_count_from_input is True, (
+ "Bundle filter should preserve input values"
+ )
+
+ def test_bundle_filter_with_constant_output(self, parser, analyzer, diagnostics):
+ """Bundle filter with constant output should set copy_count_from_input=False."""
+ code = """
+ Bundle b = { ("signal-A", 10), ("signal-B", -5) };
+ Bundle counts = (b > 0) : 1;
+ """
+ program = parser.parse(code)
+ analyzer.visit(program)
+ ir_operations, lower_diags, _ = lower_program(program, analyzer)
+
+ assert not lower_diags.has_errors(), lower_diags.get_messages()
+
+ # Find the decider
+ deciders = [op for op in ir_operations if isinstance(op, IRDecider)]
+ filter_decider = next(
+ (d for d in deciders if d.output_type == "signal-each"),
+ None,
+ )
+ assert filter_decider is not None
+ assert filter_decider.copy_count_from_input is False, (
+ "Constant output should not copy from input"
+ )
+
+ def test_bundle_filter_comparison_operator_preserved(self, parser, analyzer, diagnostics):
+ """Bundle filter should preserve the comparison operator."""
+ code = """
+ Bundle b = { ("signal-A", 10), ("signal-B", -5) };
+ Bundle filtered = (b >= 0) : b;
+ """
+ program = parser.parse(code)
+ analyzer.visit(program)
+ ir_operations, lower_diags, _ = lower_program(program, analyzer)
+
+ assert not lower_diags.has_errors(), lower_diags.get_messages()
+
+ deciders = [op for op in ir_operations if isinstance(op, IRDecider)]
+ filter_decider = next(
+ (d for d in deciders if d.output_type == "signal-each"),
+ None,
+ )
+ assert filter_decider is not None
+ assert filter_decider.test_op == ">=", f"Expected >= but got {filter_decider.test_op}"
+
+ def test_bundle_filter_with_comparison_value(self, parser, analyzer, diagnostics):
+ """Bundle filter should preserve comparison value."""
+ code = """
+ Bundle b = { ("signal-A", 10), ("signal-B", -5) };
+ Bundle filtered = (b > 5) : b;
+ """
+ program = parser.parse(code)
+ analyzer.visit(program)
+ ir_operations, lower_diags, _ = lower_program(program, analyzer)
+
+ assert not lower_diags.has_errors(), lower_diags.get_messages()
+
+ deciders = [op for op in ir_operations if isinstance(op, IRDecider)]
+ filter_decider = next(
+ (d for d in deciders if d.output_type == "signal-each"),
+ None,
+ )
+ assert filter_decider is not None
+ assert filter_decider.right == 5, (
+ f"Expected comparison value 5 but got {filter_decider.right}"
+ )
diff --git a/dsl_compiler/src/semantic/analyzer.py b/dsl_compiler/src/semantic/analyzer.py
index 71745c8..a0067cd 100644
--- a/dsl_compiler/src/semantic/analyzer.py
+++ b/dsl_compiler/src/semantic/analyzer.py
@@ -727,13 +727,16 @@ def infer_binary_op_type(self, expr: BinaryOp) -> ValueInfo:
# Handle Bundle operations: Bundle OP Signal/int -> Bundle
if isinstance(left_type, BundleValue):
if expr.op in self.COMPARISON_OPS:
- # Bundle comparisons should use any() or all() instead
- self.diagnostics.error(
- "Cannot compare Bundle directly. Use any(bundle) or all(bundle) for comparisons.",
- stage="semantic",
- node=expr,
- )
- return SignalValue(signal_type=self.allocate_implicit_type())
+ # Bundle comparison: (bundle > N) produces a signal-each comparison
+ # This must be used with output specifier to get filtered bundle:
+ # (bundle > N) : bundle
+ # Returns SignalValue (comparison result) to allow use in OutputSpecExpr
+ # Mark it as a bundle comparison for later detection
+ signal_type = self.allocate_implicit_type()
+ result = SignalValue(signal_type=signal_type, is_comparison_result=True)
+ # Store bundle comparison metadata on the expression for lowering
+ expr._bundle_comparison_source = left_type # type: ignore[attr-defined]
+ return result
# Bundle arithmetic: result is a Bundle with same signal types
if isinstance(right_type, (SignalValue, IntValue)):
@@ -813,7 +816,16 @@ def _infer_output_spec_type(self, expr: OutputSpecExpr) -> ValueInfo:
"""Infer type for output specifier expression.
The result type is determined by the output_value, not the condition.
+
+ Special case: Bundle filter pattern (bundle > N) : bundle
+ - Condition is a bundle comparison
+ - Output is a bundle (typically the same bundle)
+ - Result is a filtered bundle
"""
+ # Check for bundle filter pattern FIRST: (bundle CMP scalar) : bundle
+ if self._is_bundle_filter_pattern(expr):
+ return self._infer_bundle_filter_type(expr)
+
# Validate that condition is a comparison
if not self._is_comparison_expr(expr.condition):
# Generate a helpful error message
@@ -851,6 +863,51 @@ def _infer_output_spec_type(self, expr: OutputSpecExpr) -> ValueInfo:
return output_type
+ def _is_bundle_filter_pattern(self, expr: OutputSpecExpr) -> bool:
+ """Check if expression is a bundle filter pattern: (bundle CMP scalar) : output.
+
+ Returns True if:
+ - Condition is a binary comparison (>, <, ==, etc.)
+ - Left operand of comparison is a Bundle
+ """
+ if not isinstance(expr.condition, BinaryOp):
+ return False
+ if expr.condition.op not in self.COMPARISON_OPS:
+ return False
+ left_type = self.get_expr_type(expr.condition.left)
+ return isinstance(left_type, BundleValue)
+
+ def _infer_bundle_filter_type(self, expr: OutputSpecExpr) -> ValueInfo:
+ """Infer type for bundle filter pattern: (bundle CMP scalar) : output.
+
+ Returns:
+ - BundleValue if output is a bundle (filter with copy count)
+ - BundleValue if output is integer (filter with constant output per signal)
+ """
+ condition = expr.condition
+ assert isinstance(condition, BinaryOp)
+
+ # Get the source bundle type
+ source_bundle_type = self.get_expr_type(condition.left)
+ assert isinstance(source_bundle_type, BundleValue)
+
+ # Analyze the comparison right operand (should be scalar)
+ right_type = self.get_expr_type(condition.right)
+ if isinstance(right_type, BundleValue):
+ self.diagnostics.error(
+ "Bundle filter comparison requires scalar (Signal or int) on right side.\n"
+ " Got: Bundle\n"
+ " Hint: Use a specific signal or constant: (bundle > 0) : bundle",
+ stage="semantic",
+ node=expr,
+ )
+
+ # Analyze the output value
+ self.get_expr_type(expr.output_value)
+
+ # Result is always a bundle - either copy or constant output per matching signal
+ return BundleValue(signal_types=source_bundle_type.signal_types.copy())
+
def _is_comparison_expr(self, expr: Expr) -> bool:
"""Check if expression is a valid decider condition.
@@ -965,6 +1022,19 @@ def visit_DeclStmt(self, node: DeclStmt) -> None:
if simplified is not None:
node.value = simplified
+ # Check for naked bundle comparisons - these must use any()/all() or filter syntax
+ if self._is_naked_bundle_comparison(node.value):
+ self.diagnostics.error(
+ f"Cannot assign bundle comparison directly to '{node.name}'.\n"
+ " Bundle comparisons must use one of:\n"
+ " - any(bundle) > N (true if any signal matches)\n"
+ " - all(bundle) > N (true if all signals match)\n"
+ " - (bundle > N) : bundle (filter syntax)",
+ stage="semantic",
+ node=node,
+ )
+ return
+
value_type = self.get_expr_type(node.value)
if node.type_name == "Signal" and isinstance(value_type, IntValue):
@@ -997,6 +1067,23 @@ def visit_DeclStmt(self, node: DeclStmt) -> None:
self._register_signal_metadata(node.name, node, value_type, node.type_name)
+ def _is_naked_bundle_comparison(self, expr: Expr) -> bool:
+ """Check if expression is a bundle comparison not wrapped in filter syntax.
+
+ A bundle comparison (bundle > N) is only valid when:
+ 1. Used with any()/all() functions for boolean results
+ 2. Used in filter syntax: (bundle > N) : bundle
+
+ A "naked" bundle comparison is one used directly without these wrappers.
+ """
+ if not isinstance(expr, BinaryOp):
+ return False
+ if expr.op not in self.COMPARISON_OPS:
+ return False
+ # Check if left operand is a bundle
+ left_type = self.get_expr_type(expr.left)
+ return isinstance(left_type, BundleValue)
+
def _type_name_to_symbol_type(self, type_name: str) -> SymbolType:
"""Convert type name to symbol type."""
if type_name == "Entity":
diff --git a/example_programs/04_binary_clock.facto b/example_programs/04_binary_clock.facto
index 4631e16..ef30769 100644
--- a/example_programs/04_binary_clock.facto
+++ b/example_programs/04_binary_clock.facto
@@ -2,7 +2,7 @@
Memory counter;
-int num_lamps = 16;
+int num_lamps = 30;
counter.write((counter.read() + 1) % (2 ** num_lamps));
for i in 0..num_lamps {
diff --git a/example_programs/18_comprehensive_entities.facto b/example_programs/18_comprehensive_entities.facto
index 3380930..2980d08 100644
--- a/example_programs/18_comprehensive_entities.facto
+++ b/example_programs/18_comprehensive_entities.facto
@@ -22,6 +22,15 @@ Signal quotient = counter_value / 3;
Signal condition = input_signal > 10;
Signal gate_output = condition * counter_value;
+# Selector combinator (Factorio 2.0) - count unique signals
+Bundle sample_items = { ("iron-plate", 100), ("copper-plate", 50), ("coal", 25) };
+Entity signal_counter = place("selector-combinator", 60, 0, {
+ operation: "count",
+ count_signal: "signal-C"
+});
+signal_counter.input = sample_items; # Wire bundle to selector input
+Signal unique_count = signal_counter.output["signal-C"]; # Read count output
+
# =============================================================================
# POWER INFRASTRUCTURE
# =============================================================================
diff --git a/pyproject.toml b/pyproject.toml
index e270097..e41a231 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -41,7 +41,7 @@ classifiers = [
dependencies = [
"lark>=1.3.0",
- "factorio-draftsman>=3.2.0",
+ "factorio-draftsman @ git+https://github.com/Snagnar/factorio-draftsman.git@main",
"click>=8.3.0",
"numpy>=2.3.5",
"ortools>=9.14.6206",