Do I need this page?

Prerequisite: Core Concepts (signals section) Β· See also: Signal Processing for practical code patterns

On this page

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:

ClassPackagePurpose
SpcSignalTypeapi.signalEnum of the 6 data types a signal can carry
SpcSignalValueapi.signalRecord that holds a typed signal value at runtime
SpcSignalAddressapi.signalUniquely 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 ValueSerialized NameJava TypeDefault ValueDescriptionTypical 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

MethodReturnsDescription
getSerializedName()StringJSON/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

ComponentTypeDescription
typeSpcSignalTypeWhich field is the "active" value
digitalValuebooleanBoolean payload
integerValueintInteger payload
decimalValuedoubleDecimal payload
textValueStringText payload (never null β€” auto-converted to "")

Factory Methods

Always use these static methods to create SpcSignalValue instances:

MethodParametersReturns TypeNotes
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:

ConstantSignal TypedigitalValueintegerValuedecimalValuetextValue
DIGITAL_FALSEDIGITALfalse00.0""
DIGITAL_TRUEDIGITALtrue00.0""
INTEGER_ZEROINTEGERfalse00.0""
DECIMAL_ZERODECIMALfalse00.0""
TEXT_EMPTYTEXTfalse00.0""
ITEM_EMPTYITEMfalse00.0""
ITEM_ID_EMPTYITEM_IDfalse00.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.

MethodReturnsBehavior
.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 TypeExample Value.asDigital().asInteger().asDecimal().asText().asItemId().asItemCount()
DIGITAL(true)true true11.0"1"""1
DIGITAL(false)false false00.0"0"""0
INTEGER(42)42 true4242.0"42"""42
INTEGER(0)0 false00.0"0"""0
DECIMAL(3.7)3.7 true43.7"3.7"""1
DECIMAL(0.0)0.0 false00.0"0.0"""0
TEXT("hello")"hello" true00.0"hello""hello"1
TEXT("42")"42" true4242.0"42""42"1
TEXT("")"" false00.0""""0
ITEM("diamond",64)diamond x64 true6464.0"diamond x64""diamond"64
ITEM("",0)empty false00.0""""0
ITEM_ID("diamond")diamond true11.0"diamond""diamond"0
ITEM_ID("")empty false00.0""""0

SpcSignalAddress

A SpcSignalAddress uniquely identifies a signal slot on the runtime bus. It's how nodes find each other's outputs.

ComponentTypeDescription
nodeIdUUIDThe unique ID of the node instance that owns this port
portIdStringThe 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

ConstructorDescription
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

ComponentTypeRequiredDescription
portIdStringYes (non-null)Unique within the node. Convention: "I1", "I2" for inputs; "Q", "AQ" for outputs
directionSpcPortDirectionYes (non-null)INPUT or OUTPUT
signalTypeSpcSignalTypeYes (non-null)The data type flowing through this port
requiredbooleanYestrue = user must connect a wire; false = optional
signalDomainStringNo (nullable) Domain tag for compatibility filtering. null = connects to any same-type port.

Common port naming conventions

Port IDConventionDirectionTypical Signal TypeExample Node
I1, I2, I3…Digital inputsINPUTDIGITALAND, OR, XOR gates
AI1, AI2…Analog inputsINPUTINTEGERThreshold comparator, math blocks
QDigital outputOUTPUTDIGITALGate result, sensor trigger
AQAnalog outputOUTPUTINTEGER/DECIMALMath result, counter value
TrgTrigger inputINPUTDIGITALTimer start
RReset inputINPUTDIGITALCounter reset
EnEnable inputINPUTDIGITALConditional enable
TQText outputOUTPUTTEXTMessage formatter

SpcPortDirection

Enum ValueSerialized NameDescription
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

Example domains you might define

Domain StringSignal TypeMeaningExample Addon
"rotation"INTEGERRotational speed (RPM)Create kinetics addon
"fluid_mb"INTEGERFluid amount in millibucketsMekanism fluid addon
"gas_pressure"DECIMALGas pressure valueMekanism gas addon
"rf_rate"INTEGEREnergy transfer rate (RF/t)RF monitoring addon
nullanyUniversal β€” connects to anythingBuilt-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 DomainPort B DomainisDomainCompatible()?Can Connect?
nullnulltrueYes (both universal)
null"rotation"trueYes (A is universal)
"rotation"nulltrueYes (B is universal)
"rotation""rotation"trueYes (same domain)
"rotation""fluid_mb"falseNo (different domains)
ℹ️ Domain compatibility is in addition to type compatibility
Ports must ALSO have the same 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 typeUse this domain
On/off state, trigger, alarm, enable flagDIGITALnull
Redstone level (0–15)INTEGERnull
Tick counter, FE amount, item countINTEGERnull
Rotational speed from CreateINTEGER"rotation"
Fluid millibuckets from MekanismINTEGER"fluid_mb"
Temperature, ratio, PID outputDECIMALnull
Gas pressure from another modDECIMAL"gas_pressure"
Display message, chat textTEXTnull
Block or entity type ID as filterTEXTnull
Item ID + count for inventory opsITEMnull
Just the item type (no count needed)ITEM_IDnull
πŸ’‘ When in doubt, use null domain
Only use a non-null domain if you're representing data that is semantically incompatible with the same signal type used by other mods. Most addon nodes should use null (universal) domains.