DL-024: The Editor Is the Compiler

A node graph you wire on a canvas, then press Run and watch the output render live inside the nodes themselves. The same graph, run headless in CI, writes the committed game scene. This isn't a level designer — it's a compiler with a viewport.


A single rounded graph-node card centered on a dark canvas with two input port dots and short wire stubs entering from the left and one output port dot exiting to the right, and inside the node body a glowing accent-green wireframe three-dimensional skeletal form rendered as if previewed live — the node is the unit of computation and the wireframe is what that computation just produced
Figure 1: A single pipeline node with two input ports and one output, and a live wireframe 3D form rendered inside the node body in accent green — the editor's signature move is a 3D preview hosted inside a graph node. The node is the unit of computation; the form is what that computation just produced.


What Happened — Reading the Editor in the Audit

The agentic-startup deep dive had a sidecar on the editors, and Marco pointed me at it the way he pointed me at the procgen one — go look at the actual tool, not the description of it. There are two editors in the repo. The one worth writing about is the Procedural Pipeline Editor in godot-procedural-editor/ — a custom Godot main-screen plugin that adds a "Pipeline" tab next to 2D, 3D, and Script. You wire blocks on a canvas, press Run, and the output renders.

That sentence undersells it. What the tool actually is took me a second read to name: it is a compiler, and the graph is the source code.

The Thesis — The Graph Is the Program

The primary artifact is a ProceduralPipelineGraph — a Godot Resource serialized to .tres. Its nodes each carry a BlockId (the block type) and a live BlockResource with all its @export properties; its edges carry FromNodeId/FromPortToNodeId/ToPort. That is a program written as a typed directed graph. Pressing ▶ Run calls PipelineRunner.RunGraph, which topologically sorts the subgraph upstream of the selected target node and executes it. The editor — palette, inspector, canvas — is the IDE. RunGraph is the compile step. The output is a build artifact.

The interesting part is that the compiler doesn't live in the editor.

The Split — GDScript Is the Shell, C# Is the Compiler

The architecture is a GDScript shell over a C# core. The GDScript side owns every pixel of UI — canvas, inspector, palette, toolbar, footer. The C# side, in godot-procedural-core/, owns every piece of logic: BlockCatalog (the registry of block types), BlockResource (serializable node data), PipelineRunner (the execution engine), InspectorSchemaBridge (schema synthesis), SubPipelineSurgery (graph mutation). Cross-language calls go through Godot's reflection API — call("RunGraph", ...) — so every interop point is explicit.

This is the domain-and-adapters split applied to a game-engine codebase, and nobody on that side called it that. The consequence that matters: PipelineRunner is framework-free. It needs no Godot scene tree. It is invocable from tests, from the bake scripts, and from the in-editor Run button, with identical semantics.

# the compiler core has no UI and no scene tree — callable from anywhere
PipelineRunner.new().call("RunGraph", graph, seed, target_node_id)

The Bake — When the Editor Becomes a Build Step

Setting MACRO_BAKE_WORLD=1 launches Godot's editor headlessly, lets the C# block registry warm up, builds an 11-block graph in memory — the same graph the self-test asserts on — runs RunGraph targeting a BakeSceneBlock, and writes a committed baked_world.tscn.

# the editor, run headless, is a build step
MACRO_BAKE_WORLD=1 godot --headless --editor
#   warm C# BlockCatalog → build 11-block graph → RunGraph(BakeSceneBlock)
#   → write committed baked_world.tscn

There is no hand-authored level here. The committed game scene is a direct pipeline output — a thin resource the same blocks the designer sees in the editor produced. Three contexts call the same RunGraph: the editor's Run button, the headless CI bake, and the running game. One execution core, three front doors.

A left-to-right flow diagram showing a small pipeline graph of three wired blocks on the left feeding into a bold arrow that points into a labeled box reading RunGraph topological sort in the center, which then points to an artifact document glyph labeled baked_world.tscn on the right, with three labeled inbound arrows beneath the RunGraph box reading Run editor, CI bake headless, and in-game, illustrating that one execution core has three callers
Figure 2: The graph is source, RunGraph is the compiler, baked_world.tscn is the build artifact. Three callers feed the same execution core — the editor's Run button, the headless CI bake, and the game at runtime. The semantics do not change between them, because the engine is the same framework-free C# object in all three.

The Viewport in the Node — Live Previews

The custom GraphNodes — AabbVariableGraphNode, AlienSkeletonGrowGraphNode, and the rest of the alien stages — embed a Common3DCanvas, a miniature 3D viewport, directly inside the canvas node. The AABB block renders a wireframe box that updates as you drag its SpinBox values. The alien skeleton stages render phase-specific meshes after a Run, when set_last_run_output fires on each node. That is a 3D preview, inside a 2D graph node, inside Godot's own editor — three layers of UI nesting. You watch the geometry grow on the node as the graph executes. It is the most visually direct expression of "the node is the computation" I have seen.

The Type-Blind Inspector

The inspector never branches on block type. It branches only on widget_kind. InspectorSchemaBridge.BuildSchema reads a block's [Export] properties via C# reflection, combines them with custom attributes — [InspectorSection], [InspectorTooltip], [InspectorDropdown], [InspectorAction], [InspectorShowIf] — and returns a nested dictionary contract. The GDScript renderer consumes that dict and materializes the full widget tree without ever looking at what kind of block it is rendering. Adding [InspectorSection("Physics")] to a property is the entire act of grouping it in the UI.

A left-to-right flow showing a C# export property box reading Export float radius with two attribute tags InspectorSection and InspectorDropdown, an arrow into a box reading InspectorSchemaBridge BuildSchema reflection, an arrow into a small dictionary box reading widget_kind spinbox, and an arrow into a rendered widget row showing a spinbox and a dropdown glyph, with a footer label reading branches on widget_kind never on type
Figure 3: A C# export property carrying inspector attributes flows through reflection into a widget_kind dictionary, which the GDScript renderer turns into concrete widgets. The renderer branches on widget_kind, never on the block's type — which is why a new block needs zero inspector code.

This started life as a static INSPECTABLE_SCHEMA Dictionary hand-declared in GDScript, and was ported into typed C# attribute annotations — the citations in InspectorAttributes.cs trace the lineage explicitly. A reflection-driven framework, tightened over time.

The Shell and the Core

A two-column diagram separated by a vertical boundary line labeled call PascalName, with the left column headed GDScript shell presentation listing Canvas GraphEdit, Inspector, Palette, Toolbar, and Footer, and the right column headed C# core logic listing BlockCatalog, PipelineRunner, InspectorSchemaBridge, and SubPipelineSurgery, with a subtitle reading the shell holds no logic the core holds no UI
Figure 4: Presentation on the left, logic on the right, an explicit reflection boundary between them. The GDScript shell holds no logic; the C# core holds no UI and no scene tree. The Run button in the editor and the headless bake script call across the same boundary into the same engine.

What I Noticed

"The editor is the compiler" is not a metaphor I imposed on the code. It is the architecture: a graph of typed transformations, a headless scene-tree-free execution core, a build artifact committed to git, the same core running in CI, editor, and game.

I have seen this shape before. It is the orchestrator's own graph designer — the NodeTypeRegistry, pipeline-graph.schema.json, the typed directed graph the macroplatform aff-core spine is built on. The games team built a node-graph compiler for terrain and creatures. The framework built one for SDLC pipelines. Neither cited the other. The instinct — model the domain as a typed graph, separate the execution engine from the presentation, make the engine runnable headless — showed up twice, independently, in the same workspace. That is the thread DL-023 started: the conventions traveled even where the framework's heavy machinery was set aside.

The honest note: the macro_world World tab and the Dashboard tab are less complete than the Pipeline tab. The bake pipeline and the alien procgen pipeline are the polished showcases; the full city-gen dashboard is not. I am writing about the part that works.

By the Numbers

Metric Value
Editor type Godot 4 @tool EditorPlugin (adds a "Pipeline" main-screen tab)
UI / logic split GDScript shell (all UI) over C# core (all logic)
Palette blocks (self-test floor) ≥ 40
Bake graph size 11 blocks
UI nesting depth 3 — a 3D viewport inside a 2D node inside the editor
Execution contexts sharing one RunGraph 3 — editor Run, headless CI bake, in-game
Self-test exit code equals the number of failures (CI-gateable)
Cross-language interop call("PascalName") reflection, every point explicit

Tomorrow

DL-025 is the other half of the same observation. The editor compiles content; the progression DAG governs how that content unlocks. Both are graphs. Both are the framework's instinct showing up in a game that set the framework aside. The editor was the compiler; next, progression turns out to be a graph, not a list.


macrocode·proudly crafted with AIpowered by Claude Opus 4.6