Let a behavior be a node in another graph

A behavior that has a Start and an End node can now be dropped into any
other graph as a node of its own. What is declared on the start node
becomes the inputs of that node, what hangs on the end node its outputs;
the port list is derived from the sub graph, not copied, so declaring a
port in one graph grows the node in the other without touching it. The
node carries the behavior's name and the palette lists behaviors by
name, so a call is one grab instead of a node plus a dropdown.

An embedded behavior has no object and therefore no place for
variables: every call starts on the asset defaults and communicates
only through its ports. That is the conservative direction - state can
still be added later, and shared state already has a home in a behavior
object in the scene. Node ids are unique per graph only, so each call
gets its own pulse cache; without that, two graphs would collide.

Renaming a port used to silently drop the value typed on the calling
node. It is carried along now, project wide, like the edges.
This commit is contained in:
root committed 2026-09-09 20:01:49 +02:00
1 parent 64e034ffb9
commit 6702ff8abe
31 files changed
+1402 -107

No files matched your search

+18
View File
@@ -18,6 +18,24 @@ export interface GraphNode {
x: number;
y: number;
params: PropertyBag;
/**
* Ports the node declares itself, on top of the fixed ones of its type. Only the behavior
* start/end nodes carry this – they are what gives a nested behavior its signature.
*/
ports?: DeclaredPort[];
}
/** A port a start/end node declares. `kind` maps to the PortType of the same name (`text` → string). */
export interface DeclaredPort {
key: string;
kind: DeclaredPortKind;
}
export const DECLARED_PORT_KINDS = ['number', 'boolean', 'text', 'object'] as const;
export type DeclaredPortKind = (typeof DECLARED_PORT_KINDS)[number];
export function declaredPortType(kind: DeclaredPortKind): PortType {
return kind === 'text' ? 'string' : kind;
}
export interface GraphEdge {