---
name: compute_ring_layout
kind: function
lang: cpp
domain: core
purity: pure
version: "1.0.0"
signature: "std::vector<LayoutOutput> compute_ring_layout(const std::vector<LayoutInput>& nodes, const LayoutConfig& cfg, const StatusRingMap& status_map, const DomainOrder& domain_order)"
description: "Calcula posiciones (x,y) deterministicas para layout en anillos concentricos por status + sectores radiales por domain. Pure, sin fisicas, output reproducible. Util para skill_tree, dashboards de roadmap, mapas de capability."
tags: [layout, rings, sectors, polar, dashboard, registry, skill-tree]
uses_functions: []
uses_types: []
returns: []
returns_optional: false
error_type: ""
imports: ["core/compute_ring_layout.h"]
params:
  - name: nodes
    desc: "Vector de LayoutInput. Cada nodo tiene id (unico), status (bucket de ring), domain (sector), recency (0..1, ordena dentro del bin desc)."
  - name: cfg
    desc: "LayoutConfig: n_sectors, center_x/y, ring_radii (bordes de los anillos), bin_padding, start_angle. Defaults validos para 5 rings / 18 sectores / canvas 850px de radio."
  - name: status_map
    desc: "Mapa status→ring. Vector vacio usa el mapa canonico (completado→0, in-progress→1, unlocked→2, pendiente→3, deferred→4). Status no encontrado → ring=-1 (descartado)."
  - name: domain_order
    desc: "Lista ordenada de domains para asignar sector. Domain fuera de la lista → sector n_sectors-1 (sector 'otros'). Vector vacio → todos los domains caen en sector n_sectors-1."
output: "Vector de LayoutOutput en el mismo orden que nodes[]. Cada salida: id, x, y (posicion en world units), ring (-1=descartado), sector, rank_in_bin (orden dentro del (ring,sector))."
tested: true
tests:
  - "empty_input_empty_output"
  - "single_node_centered_in_bin"
  - "two_nodes_same_bin_radial_distribution"
  - "default_status_map"
  - "unmapped_status_returns_ring_minus_one"
  - "domain_not_in_order_falls_back_to_last_sector"
  - "deterministic_repeated_call"
  - "ring_zero_avoids_origin"
  - "sector_wrap_around_last_sector"
  - "golden_snapshot_30_nodes"
test_file_path: "cpp/tests/test_compute_ring_layout.cpp"
file_path: "cpp/functions/core/compute_ring_layout.cpp"
---

## Ejemplo

```cpp
#include "core/compute_ring_layout.h"

fn_ring::LayoutConfig cfg;
// cfg usa defaults: 5 rings, 18 sectors, ring_radii={0,150,280,450,650,850}

fn_ring::DomainOrder order = {"core", "infra", "finance", "datascience"};

std::vector<fn_ring::LayoutInput> nodes = {
    {"0001", "completado",  "core",     1.0f},
    {"0002", "pendiente",   "infra",    0.5f},
    {"0003", "in-progress", "finance",  0.8f},
    {"0004", "deferred",    "core",     0.1f},
};

auto out = fn_ring::compute_ring_layout(nodes, cfg, {}, order);
// out[0]: ring=0, sector=0  (completado → ring0, core → sector0)
// out[1]: ring=3, sector=1  (pendiente  → ring3, infra → sector1)
// out[2]: ring=1, sector=2  (in-progress→ ring1, finance → sector2)
// out[3]: ring=4, sector=0  (deferred  → ring4, core → sector0)

// Integrar en main.cpp del skill_tree:
for (auto& o : out) {
    if (o.ring < 0) continue; // nodo descartado
    // ImGui::SetCursorScreenPos({base.x + o.x, base.y + o.y});
    // draw_node(o.id);
}
```

## Cuando usarla

Cuando necesitas colocar N entidades en un mapa polar de progreso donde el anillo codifica el estado de avance (completado → centro, pendiente → exterior) y el sector codifica el dominio o categoria. Casos canonicos: skill_tree de issues/capabilities, roadmap visual de un proyecto, mapa de cobertura del registry por dominio.

La funcion calcula posiciones una vez; la animacion (interpolacion entre dos snapshots) la hace el caller en main.cpp, no esta funcion.

## Gotchas

- **Hash deterministico dentro de la build**: el sub-jitter angular usa FNV-1a 32-bit sobre el id (no `std::hash<string>`), lo que garantiza estabilidad cross-platform dentro del mismo proceso. El output es reproducible entre compiladores distintos.
- **Domain fuera de DomainOrder**: siempre cae en sector `n_sectors-1`. Si el caller quiere que todos los domains inesperados esten dispersos en lugar de apilados en el ultimo sector, debe pasar un `DomainOrder` exhaustivo o usar un sector dedicado `"otros"`.
- **Ring 0 con `ring_radii[0]==0`**: la funcion usa `r_inner=30.0f` automaticamente para no colocar nodos en el origen. Si el caller quiere usar el origen, debe pasar `ring_radii[0] > 0`.
- **Bins muy densamente cargados**: cuando el bin tiene mas nodos que la capacidad radial (`band_height / 18px`), se activa jitter angular `±0.4 * half_sector`. Los nodos siguen dentro del sector pero el angulo no es exactamente el centro del sector. Para bins con N > ~20 nodos el solapamiento visual es inevitable sin escalado del canvas.
- **`status_map` vacio**: se usa el mapa canonico con 5 rings. Si el caller usa status propios, DEBE pasar su propio `StatusRingMap`; de lo contrario todos cairan en ring=-1 (descartados).

## Capability growth log

(sin bumps aun — v1.0.0)
