<!-- SUMMARY -->
# Blueprint Discipline — Quick Reference
- **3-4 execute_python calls max** per Blueprint component (structure → logic → finalize)
- **Compile order**: add_member_variable/add_event_dispatcher → compile_blueprint → THEN build_graph_from_spec
- **build_graph_from_spec for ALL graphs** — functions AND EventGraph. Creates graph + nodes + connections atomically.
- **Pin names**: exec input=`execute`, output=`then`. Branch: `then`(true), `else`(false), `Condition`(input)
- **Float type is 'real'**, not 'float' — `lib.get_basic_type_by_name('real')`
- **ALWAYS validate**: compile_blueprint → validate_blueprint_graphs → remove_orphaned_nodes → describe_graph_logic → save
- **Defaults**: `compile_blueprint()` → `cdo = unreal.get_default_object(bp.generated_class())` → `cdo.set_editor_property('VarName', value)`
- Do not call open_editor_for_asset() — crashes editor
- Use `query_ue_api` BEFORE using function_call nodes — verify UFunction paths first
- **VariableSet data pin is INPUT** — connect from upstream source (e.g. FClamp.ReturnValue) to BOTH VariableSet AND other targets
- **Branch.Condition = bool only** — for numeric checks, add a comparison node (Greater_DoubleDouble) first
- **Branch.else unused?** Leave unconnected — do not wire back to entry
<!-- END SUMMARY -->

# Blueprint Python — Mandatory Execution Rules

These rules apply to ALL Blueprint work via `execute_python`. Violating them causes disconnected nodes, empty functions, and silent failures.

## RULE 1 — Consolidate into as few execute_python calls as possible

**Do NOT call execute_python once per step.** Combine related operations into logical groups.
Maximum 3-4 `execute_python` calls for a full Blueprint component. ALL code is INLINE — never create external .py files.

| Step | What to put in ONE execute_python call |
|------|----------------------------------------|
| **Step 1** | Create BP asset + add ALL variables + add ALL dispatchers + `compile_blueprint` + set all defaults |
| **Step 2** | Build ALL function graphs (all nodes + all connections for every function) + `compile_blueprint` |
| **Step 3 (Final)** | EventGraph wiring + `compile_blueprint` + `validate_blueprint_graphs` + `remove_orphaned_nodes` + `describe_graph_logic` + `save_asset` |

Never split "add variable" and "compile" into separate calls — put them together.
Never build one function graph per call — build all functions in one call.

## RULE 2 — Mandatory compile order

```
add_member_variable()   →  compile_blueprint()  →  build_graph_from_spec()
add_event_dispatcher()  →  compile_blueprint()  →  build_graph_from_spec()
```

**Skipping compile = empty pins = silent connection failure = broken graph.**
Both compile calls can be in the SAME script — just call `compile_blueprint()` at the right point.

## RULE 3 — Check build_graph_from_spec result

```python
import json
result = json.loads(helpers.build_graph_from_spec(bp, json.dumps(spec)))
if not result['success']:
    print(f"ERRORS: {result['errors']}")
    # Fix spec and retry — do NOT proceed with broken graph
```

If errors mention wrong function names, use `query_ue_api` to find the correct name.

## RULE 4 — Validate + describe before attempt_completion

```python
# Compile the blueprint (discover the compile method via `query_ue_api`)
errors = helpers.validate_blueprint_graphs(bp)
print(f'Errors: {errors}')          # Must be []

# Only remove nodes that have NO exec connections AND are not structural
removed = helpers.remove_orphaned_nodes(bp)
print(f'Orphaned removed: {removed}')
if removed > 0:
    # Compile the blueprint (discover the compile method via `query_ue_api`)
    errors = helpers.validate_blueprint_graphs(bp)
    print(f'Errors after cleanup: {errors}')

logic = helpers.describe_graph_logic(bp)
print(logic)                        # Read this — verify it matches your intent
# Save the asset (discover the save method via `query_ue_api`)
```

**Never call `attempt_completion` while `validate_blueprint_graphs` returns errors.**
`remove_orphaned_nodes` will NEVER delete FunctionResult/FunctionEntry/Event nodes — only truly disconnected impure nodes.

## RULE 5 — build_graph_from_spec handles function graph creation

`build_graph_from_spec` internally creates the function graph (or reuses existing). Do NOT call `lib.add_function_graph()` manually — it creates duplicates and causes orphaned entry nodes.

## RULE 6 — Pin type for float is 'real', not 'float'

```python
float_type = lib.get_basic_type_by_name('real')    # CORRECT
# lib.get_basic_type_by_name('float')              # WRONG — silently defaults to int
```

## RULE 7 — Never call open_editor_for_asset()

Crashes the editor. Use the Python API directly at all times.

## RULE 8 — Set variable defaults via CDO after compile

```python
# Compile the blueprint (discover the compile method via `query_ue_api`) — MUST compile first
cdo = unreal.get_default_object(bp.generated_class())
cdo.set_editor_property('MyVar', 100.0)
cdo.set_editor_property('MaxVar', 100.0)
cdo.set_editor_property('bFlag', False)
```

Call `compile_blueprint()` BEFORE accessing CDO — `generated_class()` returns None without it.
Use native Python types: float → `100.0`, bool → `False`, int → `5`.
`BlueprintEditorLibrary` has NO `set_blueprint_variable_default_value` method — do not search for it.

## RULE 9 — ALWAYS use build_graph_from_spec for ALL graph logic

Every function AND EventGraph MUST be built with `build_graph_from_spec`:
- Functions: `"graph_type": "function"` — creates graph + params + nodes + connections atomically
- EventGraph: `"graph_type": "event_graph"` — adds event nodes and logic to the event graph

```python
import json
spec = {
    "graph_type": "function", "function_name": "ModifyValue",
    "params": [{"name": "Amount", "type": "real"}],
    "return_values": [],
    "nodes": [
        {"id": "get_v", "type": "variable_get", "variable": "MyVar", "x": 50, "y": 0},
        {"id": "sub",   "type": "function_call", "library": "KismetMathLibrary", "function": "Subtract_DoubleDouble", "x": 250, "y": 0},
        {"id": "set_v", "type": "variable_set", "variable": "MyVar", "x": 450, "y": 0}
    ],
    "connections": [
        {"from": "entry", "from_pin": "then", "to": "set_v", "to_pin": "execute"},
        {"from": "get_v", "from_pin": "MyVar", "to": "sub", "to_pin": "A"},
        {"from": "entry", "from_pin": "Amount", "to": "sub", "to_pin": "B"},
        {"from": "sub",   "from_pin": "ReturnValue", "to": "set_v", "to_pin": "MyVar"}
    ],
    "pin_defaults": [{"node": "sub", "pin": "B", "value": "0.0"}]
}
result = json.loads(helpers.build_graph_from_spec(bp, json.dumps(spec)))
if not result['success']:
    print(f"ERRORS: {result['errors']}")
```

Node types: `variable_get`, `variable_set`, `function_call` (needs `library`+`function`), `self_call`, `branch`, `sequence`, `function_result`, `call_delegate`, `bind_delegate`, `unbind_delegate`, `clear_delegate`, `event`, `custom_event`.
Special IDs: `"entry"` = FunctionEntry, `"result"` = FunctionResult (auto-mapped, always available).
Branch pins: `"then"` (True), `"else"` (False), `"Condition"` (input).
Optional: `"clear_existing": true` removes non-structural nodes before building (use for rebuilding a function).

Even simple getters use `build_graph_from_spec` — just specify `return_values` and a `variable_get` node connected to `result.ReturnValue`.
RULE 4 still applies: `validate_blueprint_graphs` + `describe_graph_logic` afterward.

## RULE 10 — Branch pin names: `then`/`else` (not `true`/`false`)

Branch True path = `"then"`, False path = `"else"`, Condition input = `"Condition"` (capital C).
ConnectPins has alias fallback (`"true"`→`"then"`, `"false"`→`"else"`), but always use correct names.

## RULE 11 — VariableSet data pin is INPUT, not OUTPUT

VariableSet receives data. You CANNOT read from it. To send the same value to both a VariableSet and another node (e.g. a delegate), connect from the **upstream source** to both targets:

```
✅ upstream.ReturnValue → var_set.MyVar      (feed the setter)
✅ upstream.ReturnValue → other_node.Param    (feed another node from SAME source)

❌ var_set.MyVar → other_node.Param           (FAILS — both pins are Input)
```

In `build_graph_from_spec` connections — same rule:
```
✅ {"from": "upstream", "from_pin": "ReturnValue", "to": "var_set",    "to_pin": "MyVar"}
✅ {"from": "upstream", "from_pin": "ReturnValue", "to": "other_node", "to_pin": "Param"}
❌ {"from": "var_set",  "from_pin": "MyVar",       "to": "other_node", "to_pin": "Param"}
```

## RULE 12 — Branch.Condition requires BOOL — use comparison node for numeric values

Branch `Condition` pin accepts ONLY `bool`. Connecting `real` or `int` directly causes type mismatch.

To guard a numeric parameter (e.g. "value > 0"), add a comparison node:
```
comparison_node (Greater_DoubleDouble): A = numeric_value, B = 0.0
comparison_node.ReturnValue → branch.Condition
```

Available comparisons: `Greater_DoubleDouble`, `Less_DoubleDouble`, `LessEqual_DoubleDouble`, `GreaterEqual_DoubleDouble`, `EqualEqual_DoubleDouble`, `NotEqual_DoubleDouble`.

## RULE 13 — Branch.else: leave unconnected if nothing to do

If the false path has no actions, simply omit any connection from `branch.else`.
**NEVER** wire `branch.else` back to `entry` or to a previous node — this creates circular references and "both pins are Output" errors.
