--- id: "0109b" title: "skill_tree layout anillos + render canvas ImDrawList con cards" status: completado type: feature domain: - meta - cpp-stack scope: app-scoped priority: media depends: - "0109a" blocks: - "0109c" related: - "0109" created: 2026-05-17 updated: 2026-05-17 tags: - skill-tree - cpp - imgui - layout - canvas --- # 0109b — skill_tree layout anillos + render canvas Segundo slice del epic 0109. Reemplaza la lista textual del Tree por un canvas interactivo basado en `ImDrawList`. Pivote desde `graph_renderer_cpp_viz` (GPU) → `ImDrawList` (CPU) para mantener simplicidad: 166 nodos no justifican el pipeline GPU. ## Decisiones tomadas durante la implementacion - **Stack: `ImGui::ImDrawList`**, NO `graph_renderer_cpp_viz`. Razon: 166 nodos cabian de sobra en CPU; `graph_renderer` exige `init_gl_loader=true`, build de `GraphData` con tipos OSINT, shaders, FBO + texture flip-Y. Diferencia ~120 LOC + un monton de rebuilds para cero beneficio observable. - **Sin fisicas** (el usuario lo pidio explicito). Layout deterministico via `compute_ring_layout_cpp_core`. - **5 rings**: done (0), in-progress (1), unlocked (2), locked (3), deferred/bloqueado (4). - **18 sectores radiales** = 18 dominios canonicos (`dev/TAXONOMY.md`). Labels en el aro exterior. - **Lock derivation**: `pendiente` se subdivide en `pendiente_unlocked` (todos los `depends[]` en done) vs `pendiente_locked` (algun depends sin completar). Set de `done` IDs se computa al cargar y se cruza con cada `depends[]`. - **Animacion lerp 1s** entre prev y current position cuando un nodo cambia de `status_eff` entre dos `reload_scan()`s. Ease-in-out cuadratica. - **Cards con texto**: cada nodo muestra su ID en blanco con sombra negra para legibilidad sobre cualquier color de ring. - **Diferencial visual flows vs issues**: issues = circulos, flows = rombos. - **Pan**: drag con boton derecho o medio. - **Zoom**: rueda del raton, centrado en cursor (re-anchora coordenadas mundo bajo el puntero). - **Picking**: O(N) radius check (166 nodos = trivial; spatial hash innecesario). ## Tareas 1. Crear funcion del registry `compute_ring_layout_cpp_core` (pure, 10 tests Catch2, FNV-1a determinista para sub-jitter angular). 2. Reescribir `main.cpp::draw_tree()` como canvas con `ImGui::InvisibleButton` + `ImDrawList`. 3. Implementar `derive_status_eff()` para lock/unlock. 4. Implementar `apply_layout()` con preservacion de prev_x/prev_y para animacion. 5. Render: aristas curvas Bezier (depends + related) + nodos con outline + label ID + tooltip on hover. 6. HUD strip con LV/XP/contadores. 7. Self-test 0-exit cuando `parse_errors == 0 && unmapped == 0`. 8. Build Linux + deploy Windows. ## DoD - [x] `compute_ring_layout_cpp_core` indexada (10/10 tests, 142 assertions). - [x] `apps/skill_tree/main.cpp` usa `parse_md_frontmatter_cpp_core` + `compute_ring_layout_cpp_core`. - [x] `app.md uses_functions` actualizado con ambas. - [x] Self-test imprime breakdown por ring: `done=77 in-progress=2 unlocked=64 locked=22 deferred=1 unmapped=0`. - [x] Linux build OK. - [x] Windows deploy OK (PID corriendo). - [x] Tarjeta visible en `app_hub_launcher`. - [x] `fn doctor cpp-apps` limpio. ## Sigue 0109c: Inspector evolucionado con DoD parseado de la seccion `## DoD` del .md (checkboxes interactivos read-only) + lista de `uses_functions` del registry para esa issue.