3D Scene

Overview of the 3D scene architecture, bridging visual configurations from the Aircada Studio to strongly-typed SDK Operator references.
Published: 7/10/2026

Aircada separates visual scene composition from logic implementation. All 3D objects, materials, and world settings are defined within the Studio environment.

The Aircada CLI processes this scene data to generate src/registry/scene.registry, a comprehensive bridge that allows Operators to reference scene objects with full TypeScript safety.

To ensure clean development, the registry is architected into three layers: Raw IDs, Operator Refs (Facades), and Data Registries.

Programmatic Scene Creation & Entity Management (air.scene)#

Complete guide to creating, updating, and managing runtime 3D entities imperatively using the air.scene API.

In addition to static scene binding via @SceneObjectProperty(), the Aircada SDK exposes this.air.scene for imperative, programmatic entity creation and lifecycle management at runtime.

Standard CRUD Methods on this.air.scene

- create(typeOrConfig, properties): Asynchronously creates and mounts an active 3D entity. - clone(unitIdOrName, options): Clones an existing scene entity with optional name or transform overrides. - destroy(unitIdOrName): Destroys the entity and disposes of all underlying Three.js meshes and materials. - get(unitIdOrName): Retrieves a strongly-typed UnitFacade / ModelFacade for an entity by ID or display name. - getAll(): Retrieves all active scene entity facades. - getByType(type): Retrieves all entities matching a specific PrefabAssetType (e.g. MODEL, SHAPE, LIGHT). - show(unitIdOrName) / hide(unitIdOrName): Toggles visibility and hierarchy rendering.

Supported Entity Types

this.air.scene.create() supports the following entity types: - MODEL (or OBJECT_PART): 3D GLTF / GLB meshes, materials, and bounding scale controllers. - SHAPE: 3D primitives (CUBE, SPHERE, CYLINDER, PLANE, CONE, TORUS). - LIGHT: Directional, Point, Ambient, Spot, and Area light sources. - TEXT: 3D extruded vector text geometry. - CAMERA: Viewports and render cameras. - PARTICLES: GPU particle emitters and simulation fields.

Synchronous Media Item Resolution & 3D Model Spawning

When spawning 3D models from uploaded files or dropzones (e.g. AirMediaDropzone), upload blobs are converted to temporary media items via mediaManager.makeTemporaryMediaItemFromBlob(file). The engine synchronously reserves the local media item handle in memory, allowing you to immediately pass mediaItemId to this.air.scene.create() without timing race conditions.

ModelSpawnerSystem.ts (typescript)
import { System, Context, Input, AircadaContext } from "@aircada/spec";

@System({
    name: "Model Spawner System",
    type: "model_spawner_system"
})
export class ModelSpawnerSystem {
    @Context()
    air!: AircadaContext;

    @Input({ onEvent: "SPAWN_CUSTOM_MODEL" })
    async spawnCustomModel(payload: {
        name: string;
        mediaItemId: string;
        materialColor?: string;
        metalness?: number;
        scale?: number;
    }) {
        const { name, mediaItemId, materialColor = "#ffffff", metalness = 0, scale = 1 } = payload;

        // Imperative 3D model creation via air.scene.create
        const entity = await this.air.scene.create("MODEL", {
            name,
            mediaItemId,
            color: materialColor,
            materialColor,
            metalness,
            scale: { x: scale, y: scale, z: scale }
        });

        console.log("Spawned entity facade:", entity);
        return entity;
    }
}
Spawning an uploaded 3D model entity with synchronous media ID resolution and custom transform/material properties.

Ephemeral Runtime Entities & Project Isolation (_isTemporary)#

How Aircada isolates programmatic runtime entities from Studio project saves and handles automatic lifecycle disposal.

When entities are spawned imperatively via this.air.scene.create(), they are flagged with _isTemporary: true by default.

Project Save Isolation Guarantee

Ephemeral runtime entities are fully active in the live WebGL scene graph, physics engines, and raycasting systems, but they are strictly excluded when saving Studio projects (projectData.units). This ensures that procedural or user-uploaded session items do not pollute permanent project configurations.

Plugin Lifecycle & Auto-Disposers

By default, this.air.scene.create() registers an automatic disposer (props.autoDispose !== false) with the active plugin adapter. When an operator or system unmounts, or when hot module reloading (HMR) triggers a reload, all ephemeral entities spawned during that session are automatically destroyed and their GPU memory cleaned up.

TemporaryEntityExample.ts (typescript)
import { System, Context, Input, AircadaContext } from "@aircada/spec";

@System({
    name: "Temporary Entity Demo",
    type: "temporary_entity_demo"
})
export class TemporaryEntityDemo {
    @Context()
    air!: AircadaContext;

    @Input({ onEvent: "CREATE_SESSION_SHAPE" })
    async createSessionShape() {
        // Creates an ephemeral runtime entity that is auto-disposed on system unload
        const shape = await this.air.scene.create("SHAPE", {
            name: "Session_Cube",
            shapeType: "CUBE",
            position: [0, 1, 0],
            color: "#4f46e5",
            autoDispose: true // Default: auto-cleaned on operator/system unmount
        });

        return shape;
    }
}
Creating an isolated temporary runtime entity with custom lifecycle management.

Layer 3: Data Registries (The Source of Truth)#

The fully serialized JSON state of the scene (SceneObjectRegistry, OperatorRegistry).

When to use:

1. When you need context about what exists in the scene (e.g., light positions).

2. When identifying object relationships (e.g., shared material IDs).

Agent Heuristic: Treat this layer as Read-Only Context. Never attempt to mutate these registries at runtime.

Decorator Behavior: @SceneObjectProperty#

How decorated properties bridge Operator logic to visual selection controls in the Aircada Studio.

The @SceneObjectProperty() decorator tells the Aircada engine that a property expects a reference to another 3D object in the scene.

Studio Integration

Properties using this decorator are automatically exposed in the Studio UI as selection buttons on the Operator's configuration panel.

Nullable Initialization

You can safely initialize these properties with null or leave them blank. This allows designers in the Studio to pick the target object visually.

Automatic Hydration

At runtime, the engine injects the hydrated object (e.g., a ModelObject) into the property before the @Setup() method is called.

Layer 2: Operator Refs (The Magic Facades)#

Hydrated API Objects (ModelObject, CameraObject, etc.) used for native type-checking in scripts.

This layer wraps raw IDs in a sceneObjectRef<T>(id) facade. It tells the compiler the string is a functional 3D object.

When to use: Specifically for assigning static default values to properties decorated with @SceneObjectProperty().

Agent Heuristic: NEVER attempt to read properties (like .transform) directly off SceneObjects.KEY inside logic blocks. The engine only hydrates these after assignment to a decorated property.

typescript
@SceneObjectProperty()
defaultShoe: ModelObject = SceneObjects.UPLOADED_OBJECT_1;
Assigning static default values to properties decorated with @SceneObjectProperty.

Example: SceneObjectProperty in an Operator#

A complete Operator demonstrating how to properly type and initialize a SceneObjectProperty using Layer 2 Operator Refs.

When an Operator needs to declare a relationship with another 3D object in the scene, use @SceneObjectProperty(). You should initialize it using the Layer 2 Operator Refs (e.g., SceneObjects) imported from the registry.

FocusController.ts (typescript)
import { Setup, Operator, SceneObjectProperty, Context, ModelObject, AircadaContext } from "@aircada/spec";
import { SceneObjects } from "../registry/scene.registry";

@Operator({ 
    name: "Focus Controller", 
    type: "FOCUS_CONTROLLER"
})
export class FocusController {
    @Context()
    air!: AircadaContext;

    // 1. Declare the property and strongly type the expected hydrated object (e.g., ModelObject)
    // 2. Initialize using the Layer 2 Operator Ref.
    @SceneObjectProperty()
    focusTarget: ModelObject = SceneObjects.UPLOADED_OBJECT_1;

    @Setup()
    setup() {
        // Hydration happens automatically. The reference tells the engine which 
        // 3D object this operator targets without needing imperative lookup code.
    }
}
Operator declaring a SceneObjectProperty relationship with ModelObject.

Layer 1: Raw IDs (The Master Indexes)#

Strictly typed Base36 hash string constants (e.g., SceneObjectIds, OperatorIds).

When to use:

1. When an external system (network/dataset) provides a string ID.

2. When looking up an object's initial state in the Data Registry (Layer 3).

3. When logging or debugging sync issues.

Agent Heuristic: If an API specifically asks for an id: string, use this layer.

Studio Configuration & CLI Hydration#

Visual configuration in the Studio is synchronized with code through the Aircada CLI and the project-registry.ts file.

In Aircada, the Studio acts as the primary environment for scene composition. Developers place objects, configure lighting, and assign materials visually.

When you run a sync via the CLI, the system generates or updates the project-registry.ts file. This file contains a snapshot of all static IDs and registries.

This registry is critical for Operators to 'hook' into the scene without needing to perform slow or error-prone string-based lookups at runtime.