---
description: "Grid spacing, signal flow, operator placement, and annotation rules for TD network layout"
---
<!-- Generated by Embody/Envoy - Do not remove this comment -->
# Network Layout Conventions

## Placement Procedure (follow every time)

1. **Read first**: `get_network_layout` + `get_annotations` on the parent COMP. Understand existing positions, groups, and flow before touching anything.
2. **Flag problems**: If the existing layout is messy (overlapping ops, orphaned ops outside annotations, broken grid alignment), tell the user before adding to it. Do not silently work around a bad layout.
3. **Identify the target group**: Determine which annotation group the new operator belongs to. If none fits, you will create a new one.
4. **Compute position**: Use actual `nodeWidth`/`nodeHeight` from `get_network_layout`. New operators extend the group to the **right**: `rightmost_nodeX + rightmost_nodeWidth + 200` (snapped to 200-unit grid). If adding a parallel chain, go **down** (lowest Y in the group − 400). Never assume a fixed operator width — operators range from 100 to 300+ units wide.
5. **Batch-compute all positions** before placing anything. Never place one operator, then figure out where the next one goes.
6. **Place, connect, then update the annotation** to enclose the new operators. Use `set_annotation` to expand width/height if needed.
7. **Verify**: After placing, call `get_network_layout` again. Confirm no overlaps, no ops outside annotations, and grid alignment is intact.

## Grid and Spacing

- **200-unit grid**: All positions snap to multiples of 200. No arbitrary coordinates.
- **Horizontal spacing**: At least 200 units between the **right edge** of one operator and the **left edge** of the next. Formula: `next_x = prev_nodeX + prev_nodeWidth + 200` (round up to next 200 multiple). Do NOT use a fixed "+300 from nodeX" — operators vary in width.
- **400 units vertical** between parallel chains or annotation groups.
- **Y-axis increases upward**. New rows go downward (decreasing Y). Primary chain at top, secondary chains below.
- **Always use actual dimensions**: `get_network_layout` returns `nodeWidth` and `nodeHeight` for every operator. Use these values, not assumptions.

## Signal Flow

- **Left to right**: Inputs on the left, outputs on the right. New additions go on the far right of their group.
- **Wires must flow forward (positive X).** Every connected operator must have a higher `nodeX` than the operator feeding it. Vertical offset (Y) can go in either direction. If a wire goes backward (right-to-left), the downstream op is misplaced — reposition it to the right of its source.
- **Branches split vertically**: Branches fan out downward, each continuing left-to-right. Minimize edge crossings.
- **Same row = same stage** in a processing chain (same X). **Same column = same function** across parallel chains (same Y).

## Docked Callback DATs

Some operators auto-spawn companion DATs that are **docked** to them via `op.dock` / `op.docked` — callbacks, info, keys. Common families: `chopExecuteDAT`, `datExecuteDAT`, `panelExecuteDAT`, `parameterExecuteDAT`, `executeDAT`, `keyboardinDAT`, `mouseinDAT`, `oscinDAT`/`oscoutDAT`, `glslTOP` / `glslmultiTOP` / `glslMAT` (info DAT). TD drops these at arbitrary coordinates — they must be repositioned, every time.

**Layout formula** — given source op bottom-left (`sx`,`sy`), size (`sw`,`sh`), and `N` docks (use the max `dw`,`dh` across docks for uniform spacing):

- Row Y: `row_y = snap(sy − dh − 200, 200)` — one full grid step below the source's bottom edge.
- Center X: `center_x = snap(sx + (sw − dw) / 2, 200)` — first dock horizontally centered under source.
- Slot step: `dw + 200`. Each subsequent dock alternates outward: index `i=0` → center, `i=1` → +1 right, `i=2` → −1 left, `i=3` → +2, `i=4` → −2 …  General: `((i + 1) // 2) * (+1 if i odd else −1)`.

| N | Pattern |
|---|---|
| 1 | `[C]` |
| 2 | `[C, R]` |
| 3 | `[L, C, R]` |
| 4 | `[L, C, R, R2]` |
| 5 | `[L2, L, C, R, R2]` |

**Procedure**: After every `create_op` for any op in the families above, query `op.docked` (via `execute_python` returning `[d.path for d in op('PATH').docked]`), reposition each dock per the formula using `set_op_position`, then `get_network_layout` to confirm the row landed cleanly. If a target slot collides with a non-dock sibling, **move the source op** to a clear region and recompute the whole row — never thread docks around obstacles, never overlap. TDN export captures whatever's live, so following this rule is the only way docked DAT positions stay clean across saves.

## Annotations

- **Every operator must be inside exactly one annotation.** No orphans. If a new op doesn't belong to an existing group, create a new annotation for it.
- **Annotations must never overlap each other.** Maintain at least 400 units between annotation edges.
- **Expand annotations when adding operators.** Recalculate the bounding box with padding after every addition.
- **Title names the function, not the implementation.** "Audio Mixing" not "CHOP chain 2". A reader should understand the network from annotation titles alone.

**STOP — you MUST invoke `/manage-annotations` before calling `create_annotation` or `set_annotation`.** It contains required coordinate math. Key point: `nodeX`/`nodeY` is the **bottom-left corner**.

## Complexity Thresholds

- **4–5 annotation groups** in one network: consider breaking into baseCOMPs (or containerCOMPs for UI).
- **15–20 operators** in one group: consider encapsulating into a COMP.
- When encapsulating, the COMP replaces the group in the parent network. Move the annotation title to the COMP's name or label.

## Anti-Patterns

- Placing at `[0, 0]` or the origin without reading existing layout.
- Placing "near" a related op by picking a mathematically close but visually wrong position — filling a gap in the middle of a finished row instead of extending rightward.
- **Using fixed offsets like `nodeX + 300` without accounting for `nodeWidth`** — this is the #1 cause of overlapping operators when ops are wider than ~100 units.
- Using TD's `COMP.layout()` — it produces overlapping, unreadable results.
- Creating operators without updating the enclosing annotation.
- Calling `set_op_position` without verifying the target coordinates are clear — `set_op_position` has no overlap detection.
- **Leaving docked callback/info DATs at their auto-spawn position.** TD drops them at arbitrary coordinates. After `create_op` on any op that spawns docks, reposition them per "Docked Callback DATs".
