Systems

Headless, singleton manager classes in the Aircada SDK decorated with @System to coordinate global state orchestration and background tasks.
Published: 7/10/2026

While Operators represent modular visual or physical instances scattered across 3D meshes, Systems represent headless, singleton controllers running continuously in the background of the Aircada experience.

They have the same capabilities as Operators, but they have no Visual Studio presence (so their @Property fields are not visually exposed) and are instantiated exactly once to coordinate global application logic, analytical triggers, or complex multi-component rules.

2. Configuration Orchestrator Example#

An example of a System managing global state and routing events.

Implementation of a hub orchestrator managing global state, applying rules, and communicating with spoke Operators.

ConfigOrchestrator.ts (typescript)
// src/systems/ConfigOrchestrator.ts

import {
	System, Context, Input, Output, Setup, Dispose,
	AircadaContext, AirEvent
} from "@aircada/spec";
import { Events, EventPayloads, PedestalColor, SpinState } from "../registry/registry.generated";

@System({
	name: "Configuration Orchestrator",
	type: "config_orchestrator"
})
export class ConfigOrchestrator {

	// 1. Context Fencing
	@Context()
	air!: AircadaContext;

	// 2. The Master State (The single source of truth)
	private currentColor: PedestalColor = "BLUE";
	private currentSpin: SpinState = "IDLE";

	// 3. Execution Commands (Outputs sent to the Spokes)
	@Output({ name: "Apply Color", emits: Events.APPLY_COLOR })
	onApplyColor = new AirEvent<EventPayloads[typeof Events.APPLY_COLOR]>();

	@Output({ name: "Apply Spin State", emits: Events.APPLY_SPIN_STATE })
	onApplySpinState = new AirEvent<EventPayloads[typeof Events.APPLY_SPIN_STATE]>();

	// 4. Facts (Outcomes sent back to the UI)
	@Output({ name: "Validation Failed", emits: Events.VALIDATION_FAILED })
	onValidationFailed = new AirEvent<EventPayloads[typeof Events.VALIDATION_FAILED]>();

	// Memory management closures
	private unsubValidation?: () => void;

	@Setup()
	setup() {
		console.log("ConfigOrchestrator initialized. Master state set to defaults.");

		// Example of capturing local events for logging and memory safety
		this.unsubValidation = this.onValidationFailed.addListener((payload) => {
			console.warn(`[Hub Validation Error]: ${payload.reason}`);
		});

		// Initialize the scene to the default state
		// We defer slightly to ensure all Spoke Operators have mounted
		setTimeout(() => {
			this.onApplyColor.invoke({ color: this.currentColor });
			this.onApplySpinState.invoke({ state: this.currentSpin });
		}, 100);
	}

	// 5. Hub Logic: Intercept UI Requests

	@Input({ name: "Handle Color Request", onEvent: Events.REQUEST_COLOR_CHANGE })
	handleColorRequest(payload: EventPayloads[typeof Events.REQUEST_COLOR_CHANGE]) {
		this.currentColor = payload.color;

		// Enforce implicit rule: If we turn red, we must stop spinning
		if (this.currentColor === "RED" && this.currentSpin === "SPINNING") {
			this.currentSpin = "IDLE";
			this.onApplySpinState.invoke({ state: "IDLE" });
		}

		// Fire the validated execution command
		this.onApplyColor.invoke({ color: this.currentColor });
	}

	@Input({ name: "Handle Spin Request", onEvent: Events.REQUEST_SPIN_TOGGLE })
	handleSpinRequest(payload: EventPayloads[typeof Events.REQUEST_SPIN_TOGGLE]) {
		// Enforce explicit business rule: Red cannot spin.
		if (this.currentColor === "RED" && payload.state === "SPINNING") {
			// Reject the command and inform the UI
			this.onValidationFailed.invoke({ reason: "The pedestal cannot spin while it is RED." });
			return;
		}

		// State is valid. Update master state and execute.
		this.currentSpin = payload.state;
		this.onApplySpinState.invoke({ state: this.currentSpin });
	}

	@Dispose()
	dispose() {
		if (this.unsubValidation) {
			this.unsubValidation();
		}
	}
}
System class orchestrating pedestal color and spin states based on requests.

1. Systems vs Operators#

Systems have the same capabilities as Operators but are singleton-managed by the backend and have no Studio UI presence.

A system class can implement all the same logic and use the same decorators that an operator can, but its instantiation and lifecycle are completely managed by the backend, in contrast to operators that can be instantiated numerous times.

And unlike operators, @Property declarations cannot be viewed or controlled in the docs.studio environment.

  • Singleton Lifecycle: Automatically managed by the backend.
  • No Studio UI: @Property declarations are not visible in the Visual Studio.