Signal Types & Domains Beginner
Everything about the data flowing through wires between nodes: the 6 signal types, how to create and convert signal values, pre-defined constants, and the v2 signal domain system for port compatibility.
Do I need this page?
- Yes, read now if your node uses non-digital signals (integers, decimals, text, or items)
- Yes, skim the tables if you need to convert between signal types
- Skip for now if your first node only uses digital (on/off) signals β you can come back later
Prerequisite: Core Concepts (signals section) Β· See also: Signal Processing for practical code patterns
On this page
- What are Signals?
- The 6 Signal Types (SpcSignalType)
- SpcSignalValue β Creating Signals
- Factory Methods
- Pre-defined Constants
- Cross-type Conversion Methods
- Full Conversion Matrix
- SpcSignalAddress
- SpcPortSpec β Declaring Ports
- SpcPortDirection
- Signal Domains (v2)
- Domain Compatibility Rules
- How to Choose the Right Signal Type
What are Signals?
Signals are the data values flowing through wires between nodes. Every wire carries a signal. Every signal has a type (what kind of data) and a value (the actual data). Signals are refreshed every server tick (20 times per second).
The signal system consists of three main classes:
| Class | Package | Purpose |
|---|---|---|
SpcSignalType | api.signal | Enum of the 6 data types a signal can carry |
SpcSignalValue | api.signal | Record that holds a typed signal value at runtime |
SpcSignalAddress | api.signal | Uniquely identifies a port on a node instance on the signal bus |
The 6 Signal Types (SpcSignalType)
SpcSignalType is an enum with 6 values. Each represents a different
kind of data that can flow through connections.
| Enum Value | Serialized Name | Java Type | Default Value | Description | Typical Use Cases |
|---|---|---|---|---|---|
DIGITAL |
"digital" |
boolean |
false |
On/off binary signal | Gate outputs, sensor triggers, enable/disable flags, alarm states |
INTEGER |
"integer" |
int |
0 |
Whole number | Redstone levels (0β15), item counts, tick counters, FE amounts, comparator output |
DECIMAL |
"decimal" |
double |
0.0 |
Floating-point number | Temperatures, ratios, percentages, PID control values, precise math |
TEXT |
"text" |
String |
"" |
Text string | Display messages, block IDs, entity type filters, formatted output |
ITEM |
"item" |
String + int |
"" + 0 |
Item ID and count pair | Inventory monitoring, crafting control, item sorting, hopper management |
ITEM_ID |
"item_id" |
String |
"" |
Item registry ID only (no count) | Item type comparison, recipe lookup, filter configuration |
Enum methods
| Method | Returns | Description |
|---|---|---|
getSerializedName() | String | JSON/NBT serialization key (e.g., "digital", "integer") |
fromSerializedName(String) | Optional<SpcSignalType> | Parse from serialized name β returns empty if unknown |
SpcSignalValue β Creating Signals
SpcSignalValue is a Java record that holds a signal at runtime.
It stores all possible value fields plus the active type. Use factory methods
to create instances (never the raw constructor).
Record components
| Component | Type | Description |
|---|---|---|
type | SpcSignalType | Which field is the "active" value |
digitalValue | boolean | Boolean payload |
integerValue | int | Integer payload |
decimalValue | double | Decimal payload |
textValue | String | Text payload (never null β auto-converted to "") |
Factory Methods
Always use these static methods to create SpcSignalValue instances:
| Method | Parameters | Returns Type | Notes |
|---|---|---|---|
SpcSignalValue.digital(boolean) |
value β the boolean |
DIGITAL |
Returns cached DIGITAL_TRUE or DIGITAL_FALSE |
SpcSignalValue.integer(int) |
value β the integer |
INTEGER |
Also sets decimalValue = value. Returns cached INTEGER_ZERO for 0. |
SpcSignalValue.decimal(double) |
value β the double |
DECIMAL |
Also sets integerValue = (int) value. Returns cached DECIMAL_ZERO for 0.0. |
SpcSignalValue.text(String) |
value β the string (nullable) |
TEXT |
Returns cached TEXT_EMPTY for null/empty. |
SpcSignalValue.item(String, int) |
itemId β registry ID; count β stack size |
ITEM |
Sets digitalValue = count > 0, integerValue = count, decimalValue = count, textValue = itemId. Cached for empty. |
SpcSignalValue.itemId(String) |
itemId β registry ID (nullable) |
ITEM_ID |
Sets digitalValue = true (if non-empty), textValue = itemId. Cached for empty. |
// Examples:
SpcSignalValue.digital(true); // DIGITAL true
SpcSignalValue.digital(false); // DIGITAL false
SpcSignalValue.integer(42); // INTEGER 42
SpcSignalValue.integer(0); // returns INTEGER_ZERO constant
SpcSignalValue.decimal(3.14); // DECIMAL 3.14
SpcSignalValue.text("hello"); // TEXT "hello"
SpcSignalValue.text(null); // returns TEXT_EMPTY constant
SpcSignalValue.item("minecraft:diamond", 64); // ITEM diamond x64
SpcSignalValue.itemId("minecraft:diamond"); // ITEM_ID diamond
Pre-defined Constants
Use these static constants to avoid allocating new objects for common values. The factory methods already return these when applicable, but you can reference them directly for clarity:
| Constant | Signal Type | digitalValue | integerValue | decimalValue | textValue |
|---|---|---|---|---|---|
DIGITAL_FALSE | DIGITAL | false | 0 | 0.0 | "" |
DIGITAL_TRUE | DIGITAL | true | 0 | 0.0 | "" |
INTEGER_ZERO | INTEGER | false | 0 | 0.0 | "" |
DECIMAL_ZERO | DECIMAL | false | 0 | 0.0 | "" |
TEXT_EMPTY | TEXT | false | 0 | 0.0 | "" |
ITEM_EMPTY | ITEM | false | 0 | 0.0 | "" |
ITEM_ID_EMPTY | ITEM_ID | false | 0 | 0.0 | "" |
Cross-type Conversion Methods
You can read any SpcSignalValue as any Java type using the conversion methods.
This is how different node types interoperate β an INTEGER signal can be read as a boolean
by a digital gate, for example.
| Method | Returns | Behavior |
|---|---|---|
.asDigital() |
boolean |
DIGITAL β direct; INTEGER β non-zero; DECIMAL β non-zero; TEXT β non-blank; ITEM β non-blank ID and count>0; ITEM_ID β non-blank |
.asInteger() |
int |
DIGITAL β 0/1; INTEGER β direct; DECIMAL β Math.round(); TEXT β parseInt (0 on fail); ITEM β count; ITEM_ID β 0 or 1 |
.asDecimal() |
double |
DIGITAL β 0.0/1.0; INTEGER β cast; DECIMAL β direct; TEXT β parseDouble (0.0 on fail); ITEM β count as double; ITEM_ID β 0.0 or 1.0 |
.asText() |
String |
DIGITAL β "0"/"1"; INTEGER β toString; DECIMAL β toString; TEXT β direct; ITEM β "id x count" or ""; ITEM_ID β direct |
.asItemId() |
String |
ITEM/ITEM_ID/TEXT β textValue; others β "" |
.asItemCount() |
int |
ITEM β integerValue; ITEM_ID β 0; INTEGER β integerValue; others β asDigital() ? 1 : 0 |
Full Conversion Matrix
This table shows exactly what you get when calling each conversion method on each signal type. The Source column is the signal's type; each column shows the conversion result.
| Source Type | Example Value | .asDigital() | .asInteger() | .asDecimal() | .asText() | .asItemId() | .asItemCount() |
|---|---|---|---|---|---|---|---|
DIGITAL(true) | true | true | 1 | 1.0 | "1" | "" | 1 |
DIGITAL(false) | false | false | 0 | 0.0 | "0" | "" | 0 |
INTEGER(42) | 42 | true | 42 | 42.0 | "42" | "" | 42 |
INTEGER(0) | 0 | false | 0 | 0.0 | "0" | "" | 0 |
DECIMAL(3.7) | 3.7 | true | 4 | 3.7 | "3.7" | "" | 1 |
DECIMAL(0.0) | 0.0 | false | 0 | 0.0 | "0.0" | "" | 0 |
TEXT("hello") | "hello" | true | 0 | 0.0 | "hello" | "hello" | 1 |
TEXT("42") | "42" | true | 42 | 42.0 | "42" | "42" | 1 |
TEXT("") | "" | false | 0 | 0.0 | "" | "" | 0 |
ITEM("diamond",64) | diamond x64 | true | 64 | 64.0 | "diamond x64" | "diamond" | 64 |
ITEM("",0) | empty | false | 0 | 0.0 | "" | "" | 0 |
ITEM_ID("diamond") | diamond | true | 1 | 1.0 | "diamond" | "diamond" | 0 |
ITEM_ID("") | empty | false | 0 | 0.0 | "" | "" | 0 |
SpcSignalAddress
A SpcSignalAddress uniquely identifies a signal slot on the runtime bus.
It's how nodes find each other's outputs.
| Component | Type | Description |
|---|---|---|
nodeId | UUID | The unique ID of the node instance that owns this port |
portId | String | The port identifier within that node (e.g., "Q", "AQ") |
You never create these yourself β you receive them from NodeCompilationContext:
// In your factory:
SpcSignalAddress inputAddr = ctx.inputAddress("I1"); // where to READ from (null if unconnected)
SpcSignalAddress outputAddr = ctx.outputAddress("Q"); // where to WRITE to (always non-null)
// In your compiled node's execute():
boolean input = state.readDigitalSignal(inputAddr); // read upstream output
state.setSignal(outputAddr, SpcSignalValue.digital(result)); // write your output
SpcPortSpec β Declaring Ports
SpcPortSpec is a record that declares a single port on a node type.
Ports are the connection points shown in the editor.
Constructors
| Constructor | Description |
|---|---|
new SpcPortSpec(portId, direction, signalType, required, signalDomain) |
Full constructor with signal domain (v2) |
new SpcPortSpec(portId, direction, signalType, required) |
Backward-compatible β signalDomain defaults to null (universal) |
Record components
| Component | Type | Required | Description |
|---|---|---|---|
portId | String | Yes (non-null) | Unique within the node. Convention: "I1", "I2" for inputs; "Q", "AQ" for outputs |
direction | SpcPortDirection | Yes (non-null) | INPUT or OUTPUT |
signalType | SpcSignalType | Yes (non-null) | The data type flowing through this port |
required | boolean | Yes | true = user must connect a wire; false = optional |
signalDomain | String | No (nullable) | Domain tag for compatibility filtering. null = connects to any same-type port. |
Common port naming conventions
| Port ID | Convention | Direction | Typical Signal Type | Example Node |
|---|---|---|---|---|
I1, I2, I3β¦ | Digital inputs | INPUT | DIGITAL | AND, OR, XOR gates |
AI1, AI2β¦ | Analog inputs | INPUT | INTEGER | Threshold comparator, math blocks |
Q | Digital output | OUTPUT | DIGITAL | Gate result, sensor trigger |
AQ | Analog output | OUTPUT | INTEGER/DECIMAL | Math result, counter value |
Trg | Trigger input | INPUT | DIGITAL | Timer start |
R | Reset input | INPUT | DIGITAL | Counter reset |
En | Enable input | INPUT | DIGITAL | Conditional enable |
TQ | Text output | OUTPUT | TEXT | Message formatter |
SpcPortDirection
| Enum Value | Serialized Name | Description |
|---|---|---|
INPUT | "input" | Port receives data from an upstream node's output |
OUTPUT | "output" | Port sends data to downstream node inputs |
Methods: getSerializedName(), fromSerializedName(String)
Signal Domains
Signal domains are an API feature that add a compatibility layer on top of signal types. Two ports can only be connected if they have the same signal type and compatible domains.
Why domains exist
Without domains, any INTEGER port can connect to any other INTEGER port. But a "rotational speed" integer and a "redstone level" integer are semantically different β connecting them makes no sense. Domains prevent this.
How domains work
- A port's
signalDomainis aStringtag, ornull null= universal β connects to any port of the same signal type- A non-null domain only connects to ports with the same domain or to universal ports
Example domains you might define
| Domain String | Signal Type | Meaning | Example Addon |
|---|---|---|---|
"rotation" | INTEGER | Rotational speed (RPM) | Create kinetics addon |
"fluid_mb" | INTEGER | Fluid amount in millibuckets | Mekanism fluid addon |
"gas_pressure" | DECIMAL | Gas pressure value | Mekanism gas addon |
"rf_rate" | INTEGER | Energy transfer rate (RF/t) | RF monitoring addon |
null | any | Universal β connects to anything | Built-in SPC nodes |
// A rotational speed input β only connects to other "rotation" ports:
new SpcPortSpec("RPM", SpcPortDirection.INPUT, SpcSignalType.INTEGER, true, "rotation")
// A generic integer input β connects to ANY integer port:
new SpcPortSpec("AI1", SpcPortDirection.INPUT, SpcSignalType.INTEGER, true)
// or equivalently:
new SpcPortSpec("AI1", SpcPortDirection.INPUT, SpcSignalType.INTEGER, true, null)
Domain Compatibility Rules
Use portA.isDomainCompatible(portB) to check compatibility:
| Port A Domain | Port B Domain | isDomainCompatible()? | Can Connect? |
|---|---|---|---|
null | null | true | Yes (both universal) |
null | "rotation" | true | Yes (A is universal) |
"rotation" | null | true | Yes (B is universal) |
"rotation" | "rotation" | true | Yes (same domain) |
"rotation" | "fluid_mb" | false | No (different domains) |
SpcSignalType. A DIGITAL port with domain "rotation"
can never connect to an INTEGER port with domain "rotation".
How to Choose the Right Signal Type
| You want to represent⦠| Use this type | Use this domain |
|---|---|---|
| On/off state, trigger, alarm, enable flag | DIGITAL | null |
| Redstone level (0β15) | INTEGER | null |
| Tick counter, FE amount, item count | INTEGER | null |
| Rotational speed from Create | INTEGER | "rotation" |
| Fluid millibuckets from Mekanism | INTEGER | "fluid_mb" |
| Temperature, ratio, PID output | DECIMAL | null |
| Gas pressure from another mod | DECIMAL | "gas_pressure" |
| Display message, chat text | TEXT | null |
| Block or entity type ID as filter | TEXT | null |
| Item ID + count for inventory ops | ITEM | null |
| Just the item type (no count needed) | ITEM_ID | null |
null domainnull
(universal) domains.