## Nested Behavior / Behavior als Node in Graph Was damit gemeint ist? => Ein Behavior Asset als Node innerhalb eines Graph Grundsätzlich soll so ein Behavior Node im Graf mit einem Execution Flow ausgestattet sein, sprich wir brauchen einen Start/End Node: - Start Node ist ähnlich wie ein Event Node mit exec output port und dort soll man auch ouput "variablen" festlegen können (quasi wie variablen) - End Node ähnlich, hat als Input ein exec port + inputs die als output gelten, wenn man es als node in einen anderen graphen eines behavior assets einbindet. wenn alles verstanden, direkt loslegen, ansonsten warte ich auf rückfragen :) --- ## Umgesetzt Verstanden war alles — gebaut ist genau die beschriebene Form. Drei neue Node-Typen: | | | |---|---| | **`Start`** | exec-Ausgang plus die Eingaenge, die das Behavior von aussen bekommt. Was hier deklariert wird, ist ein **Eingang des aufrufenden Nodes** | | **`End`** | exec-Eingang plus die Werte, die zurueckgehen. Was hier deklariert wird, ist ein **Ausgang des aufrufenden Nodes** | | **`Run behavior`** | der Aufruf selbst. Seine Ports gibt es nicht in seiner Typdefinition — sie werden aus Start- und End-Node des Ziels **abgeleitet**, bei jedem Zeichnen neu | Deklariert wird im Inspector des Start-/End-Nodes: `+ Input` bzw. `+ Output`, Art waehlbar (Number, Boolean, Text, Object), Name direkt editierbar, `×` zum Entfernen — dieselbe Bauform wie die Variablen eines Behaviors. **Der Node traegt den Namen des Behaviors**, nicht "Run behavior", und die Palette listet Behaviors ebenfalls namentlich. Ein Aufruf ist damit ein Griff statt Node einfuegen + Dropdown. ## Die eine Entscheidung, die du mir ueberlassen hast Ein eingebettetes Behavior hat kein Objekt in der Szene — also auch keinen Ort fuer seine Variablen. Ich habe **(A) reine Funktion** gewaehlt: jeder Aufruf startet die Variablen auf den Asset-Defaults, rein geht es ueber den Start-Node, raus ueber den End-Node. Der Grund ist dein eigener naechster Schritt: du willst den Tetris-Graphen zerlegen. Dabei sollen die Teile *rechnen*, waehrend der Zustand (`grid`, `pieceX`, …) im Haupt-Behavior bleibt — genau das, was du mit "kann man ueber input/output loesen" gemeint hast. (A) ist ausserdem die konservative Wahl: daraus laesst sich spaeter Zustand machen, umgekehrt nicht, ohne bestehende Graphen zu brechen. **Die Tuer ist nicht zu:** Zustand gibt es weiterhin ueber ein Behavior-Objekt in der Szene (`Object by name` → `Get/Set property`) — der Weg, den dein Jump and Run fuer den Punktestand schon geht. Lokale Variablen im eingebetteten Graphen funktionieren als Zwischenspeicher innerhalb eines Aufrufs unveraendert; sie werden nur am Ende verworfen. ## Was ich selbst entschieden habe | | | |---|---| | **Ein Start, ein End** | ein zweiter machte "welche Ports hat dieses Behavior" mehrdeutig. Der Command lehnt ihn ab, die Palette bietet ihn nicht mehr an | | **Selbstaufruf = Fehler** | mit Namen im Log (`Behavior "X" runs itself`), statt still in die Tiefenbegrenzung zu laufen. Die Palette bietet das eigene Behavior gar nicht erst an | | **`self`/`owner` bleiben** | der eingebettete Graph sieht dasselbe Objekt wie der Aufrufer — sonst waeren `Self`/`Owner` darin wertlos | | **Toter Verweis ist still** | ein Node ohne gewaehltes Behavior (oder eines ohne Start-Node) tut nichts und **laesst den Fluss weiterlaufen**, statt den Lauf abzubrechen | ## Zwei Dinge, die der Bau erzwungen hat **Eigener Pulse-Cache pro Aufruf.** Node-Ids sind nur *pro Graph* eindeutig. Liefe der eingebettete Graph im Cache des Aufrufers, koennte er dessen Werte ueberschreiben. Der Interpreter arbeitet deshalb auf **Frames** — der Aufruf schiebt einen neuen auf und nimmt ihn danach wieder weg. Dafuer gibt es einen eigenen Test, der eine Id-Kollision erzwingt; ohne Frames liefert er 99 statt 7. **Ports aufraeumen ueber Graphgrenzen.** Einen Port umzubenennen oder zu entfernen betrifft nicht nur den eigenen Graphen, sondern **jeden anderen, der das Behavior aufruft**. Beides raeumt dort mit auf — und ein Rename nimmt den eingetippten Inline-Wert mit, sonst faellt er still auf den Default zurueck. Das ist mir erst beim Gegenpruefen aufgefallen: der Test stand auf 0 statt auf 6. **Live-Highlight:** der Trace eines laufenden Behaviors deckt jetzt auch die Graphen ab, die es als Node aufruft — sonst bliebe ein eingebettetes Behavior beim Zusehen dunkel. ## Belegt **Zehn Unit-Tests** (`tests/core/nested-behavior.test.ts`) und **elf E2E-Checks** in Modul 19. Der staerkste ist der erste: das aufrufende Behavior rechnet **nichts** selbst — die Geschwindigkeit, mit der die Kiste laeuft, *ist* der zurueckgegebene Wert. ``` PASS a behavior used as a node runs, and what it returns is what drives the caller – moved 200.0 px in ~1 s – 2 × 100 means the returned value arrived, 0 would mean nothing came back PASS the calling node grows the ports the other behavior declares PASS and it is drawn under that behavior's name, not as "Run behavior" – title="Double" PASS adding an input declares it on the start node – [{"key":"in1",…},{"key":"in2",…}] PASS and the node in the other graph grew that input without being touched PASS undo removes it in both graphs – [{"key":"in1","kind":"number"}] PASS picking a behavior in the palette drops in a node already set to it – {"type":"RunBehavior","params":{"behavior":"nb-double"}} ``` `200.0` statt `100` beweist, dass der **verdoppelte** Wert ankam, nicht bloss irgendeiner; `0` waere "nichts kam zurueck". Die drei Port-Checks in Folge sind der Nachweis, dass die Portliste wirklich *abgeleitet* und nicht kopiert ist: deklariert wird im einen Graphen, gewachsen ist der Node im anderen, ohne dass er angefasst wurde. Gegenprobe gefahren: ohne die frische Variablenkopie und ohne die Selbstaufruf-Sperre fallen zwei Unit-Tests; ohne getrennte Frames faellt der Kollisionstest (`expected 99 to be 7`) — der war beim ersten Versuch faelschlich gruen und musste geschaerft werden, bevor er etwas taugte. | | | |---|---| | volle E2E-Suite, remote | **ALL PASSED – 379 Checks**, 0 FAIL | | `npm run check` lokal | gruen, **524 Tests** |