El doctor reportaba el dominio gamedev en doble FAIL: el tag plano `gamedev`
(44 funciones) como `ungrouped_candidate` y la pagina `gamedev-2d.md` como
`doc_orphan`. Causa raiz: el INDEX declaraba `[gamedev](gamedev-2d.md)` y el
auditor solo registra el slug cuando label==target, asi que ni casaba la
pagina ni declaraba el tag.
Al revisar las 44 funciones habia dos clusters reales bajo el mismo tag, asi
que se separan en dos grupos honestos:
- gamedev-2d (tag canonico): 31 builders de workflow ComfyUI + 5 de apoyo
(post-proceso + puente a Godot) = 36. Se elimina el tag plano `gamedev` de
los builders (ya tenian `gamedev-2d`) y se reemplaza por `gamedev-2d` en las
de apoyo.
- gamedev-engine (grupo nuevo, pagina madre nueva): runtime de juego C++
multiplataforma (SDL3 + sokol_gfx + miniaudio, Issue 0072b) = 8. Game loop,
camara 2D, input unificado, sprite batch, setup render/audio, build wasm.
El tag plano `gamedev` queda eliminado (count 0). INDEX corregido: fila
gamedev-2d con label==target y conteo 36 + fila nueva gamedev-engine (8).
Verificacion: `fn index` + `fn doctor capabilities` -> ambos grupos OK
(declared_in_index=yes, doc_exists=yes, sin issues); `gamedev` plano = 0.
Solo se modifico el campo `tags` de los .md, ningun archivo de codigo.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Los headers usaban uint8_t/uint32_t en enum-base-types (ColorRuleKind,
CellRenderer, TableEventKind) y en StringPool sin incluir <cstdint>/<cstddef>.
MSVC los arrastra transitivamente; GCC no, lo que rompia la compilacion del
modulo data_table y de toda app que lo enlaza (registry_dashboard, etc.) en
Linux nativo. Una sola cabecera afectada; el resto los obtiene transitivamente.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Foundation (ce7470d5) + reader rewire (2f7fdd40).
- ColumnSnapshot per col (i64/f64/str_ids) + StringPool per-State
- compute_visible_rows filter/sort uses snapshot direct numeric/id compare
- StringPool realloc-crash fix (reserve before emplace_back)
- Pool staleness sentinel (rebuild when string_pool.size() drift)
- High-cardinality cap (>2048 unique → skip interning, fallback raw)
API publica intacta. Bench 100k sort_numeric +131% vs baseline.
text_editor_smoke RED preexisting unrelated.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Change 3 of issue 0133 — rewire compute_visible_rows, filter eval,
and sort comparators to read from the SnapshotCache when available.
Hot paths rewired:
- compute_visible_rows (overload with snap): filter eval uses
compare_snap (fast i64/f64 numeric compare for Int/Float cols;
id-compare for low-cardinality string Eq/Neq; raw cells fallback
for Contains/StartsWith/EndsWith).
- Sort comparators: direct i64/f64 array compare for Int/Float cols
(goto sort_done skips string fallback); string sort uses uint32_t
id compare with pool lookup only on mismatch.
- Stage>0 filter/sort: same snapshot overload.
Materialization paths (build_so, s0_backing, mat_backing, config popup)
kept on raw cells — they copy into std::string anyway, no benefit from
snapshot and snprintf-per-cell was 2M extra calls per frame.
Bug fixes (required for correctness):
1. StringPool::intern() realloc safety: force reserve before
emplace_back so string_view keys in the map never go dangling.
2. SnapshotCache::pool_size_built sentinel: detects when a new State
is created with an empty pool but same cells pointer (begin_scenario
pattern). Prevents str_ids from indexing into an empty pool (SIGSEGV).
3. Cardinality cap (2048 uniques / 25% sample): high-cardinality string
cols (timestamps-as-strings, UUIDs, names) skip interning — str_ids
stays empty and compare_snap falls back to raw cells. Prevents 30MB+
pool bloat that hurt cache for filter/sort on other cols.
Bench delta vs baseline (100k rows, LIBGL_ALWAYS_SOFTWARE=1):
linear_scroll: 16.0 -> 15.5 fps p50 (-3%, baseline already FAIL)
filter_like: 59.7 -> 56.0 fps p50 (-6%, still PASS at 56fps)
sort_numeric: 3.9 -> 9.0 fps p50 (+131%, snapshot i64 sort)
color_rule: 15.2 -> 14.8 fps p50 (-3%, baseline already FAIL)
Build: green for all 10 available Linux consumers (text_editor_smoke
linker failure is preexisting, not caused by this change).
API public intact. TableEvent.row indexing TableInput preserved.
Pointer-identity invalidation preserved.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
terminal_panel.cpp:
- BeginChild con PushStyleColor(ChildBg, negro) + PushStyleColor(Text, gris claro)
- PushStyleVar(WindowPadding, 8/6px) para padding terminal real
- Input prompt siempre visible cuando readonly=false
- Prefijo "$ " antes del InputText (TextUnformatted + SameLine)
- BeginDisabled() cuando el shell esta cerrado (en vez de ocultar el widget)
- Calculo de child_h reserva exactamente GetFrameHeightWithSpacing+6 para el prompt
cpp/tests/e2e/test_terminal_panel_e2e.py (nuevo):
- 4 asserts: PNG existe, no todo-blanco, region oscura >= 30%, pixels no-negros >= 0.3%
- Lanza primitives_gallery --capture, busca el binario Linux o Windows.exe automaticamente
- Skip graceful si no hay GL ni binario (WSL/CI headless)
- 4/4 pasan en Linux con LIBGL_ALWAYS_SOFTWARE=1
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Change 1 — Columnar Snapshot Internal:
- Add ColumnSnapshot struct (type + str_ids/i64/f64 per column) in data_table_internal.h
- Add SnapshotCache struct with pointer-identity sentinel (last_cells_ptr)
- Add SnapshotCache field to UiState singleton
- In render(): rebuild snapshot after join materialization when cells ptr changes
Uses same pointer-identity pattern as existing stats_last_cells in State
Int/Float columns parsed once via parse_number; String/Auto interned
Change 2 — String Interning:
- Add StringPool struct (strings + unordered_map<string_view, uint32_t>) to data_table_types.h
- StringPool is per-State (NOT global) for table isolation
- intern(sv) inserts if absent, returns stable uint32_t index
- Cleared + rebuilt on each snapshot rebuild for index coherence
- Add string_pool field to State struct
Documentation:
- Extended header comment in data_table_internal.h describing design,
StringPool API, invariants (pointer-identity, row→snapshot_row),
and how stats_last_cells and snapshot coexist independently
Build: fn_module_data_table + tables_qa pass, no new errors (only
pre-existing -Wformat-truncation warnings unrelated to this change).
Public API (data_table.h, TableInput, render() signature) unchanged.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
POSIX popen routes via /bin/sh -c, so "2>&1" is a shell redirect. On
Windows we use CreateProcessW directly (no shell): curl receives "2>&1"
as a positional arg, treats it as a second URL, and fails with exit 3
"URL rejected: Bad hostname".
Stderr is already merged into the same pipe via STARTUPINFOW.hStdError
on Windows, so the redirect is also unnecessary there. Guard with
#ifndef _WIN32.
Also adds FN_HTTP_DEBUG env var to dump the cmdline + req.url for
future bug triage (zero-cost when unset).
Detected via agents_dashboard.exe --connect-test against
https://agents.organic-machine.com — same .exe with the fix now returns
"OK 11" in <2s.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
WIP previo al lanzamiento de fn-orquestador piloto.
Commit como baseline para que /autonomous-task 0120 arranque con master limpio.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Cliente Server-Sent Events C++ reusable (fn_sse::Client) con background
thread, exponential backoff, Last-Event-ID y stop() que no bloquea.
Implementacion clave: fork+execvp curl directamente (sin /bin/sh wrapper)
para tener el PID real del proceso curl en curl_pid_, lo que permite que
stop() → kill(SIGTERM) → fgets NULL → join() funcione sin bloqueo.
4 tests (Catch2): connect_and_receive_3_events, parse_event_field,
reconnect_on_disconnect, stop_kills_thread. Fixture Python SSE con
/health probe via http_request_cpp_core.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Issue 0118.
fn_viz::render_agent_runs_timeline(TimelineState&):
- Filtros: multi-select apps, multi-select statuses, Since (days).
- Connection badge (● green / ◐ amber / ○ red) por state.connection_status.
- Tabla 7 cols: status icon | app chip | issue/card | branch | dod badge |
duration | started. Selectable SpanAllColumns dispara on_select callback.
- Footer: contadores per-status sobre el set completo.
Thread-safe: snapshot bajo runs_mutex al inicio del frame. SSE client
NO implementado — poll_sse_runs() es stub documentado en .md ## Gotchas.
Consumer puede usar http_request_cpp_core para polling fallback contra
GET /api/runs hasta que un endpoint /api/runs/stream estable aparezca.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Ejemplo lanzable con DodPanelState mock + Cuando usarla (HITL DoD
validation) + Gotchas (screenshot stub, URL no validada, log read
each-frame, callbacks pueden mutar state, frame ImGui activo
requerido). Tag agents para capability group.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
DodItem/DodEvidence/DodPanelState + count_status/find_evidence/
status_icon_id/status_color_token. Sin ImGui — testeable en aislamiento.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Snapshot de WIP acumulado de sesiones previas antes de merge wave 1
del flow 0008 (kanban_cpp + agent_runner_api + DoD schema).
Incluye:
- dev/flows/0008-kanban-cpp-and-agent-workflows.md
- dev/issues/0112-0119*.md (7 sub-issues)
- WIP previo en cmd/fn/doctor.go, registry/*, modules/, cpp/, etc.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Frontmatter + self-doc sections (Ejemplo, Cuando usarla, Gotchas) following
the contract in .claude/rules/function_growth_and_self_docs.md. Tags include
'http', 'client', 'curl', 'network', 'registry-gap', 'helper' so FTS surfaces
them when an agent asks for HTTP / fetch / GET / Bearer.
http_get_json declares uses_functions: [http_request_cpp_core] so the
dependency is auditable via mcp__registry__fn_uses.
Promotes the inline curl-popen pattern duplicated across apps/services_monitor,
dag_engine_ui, data_factory into two reusable functions in cpp/functions/core/:
- http_request_cpp_core: generic HTTP client (GET/POST/PUT/DELETE/PATCH) via
cURL CLI through popen. Portable Linux/WSL/MinGW (no link-time libcurl).
Supports custom headers, raw body, Bearer/Basic auth shortcuts, timeout,
optional TLS verify skip. Returns status/body/headers/error/duration_ms.
- http_get_json_cpp_core: convenience wrapper over http_request — GET <url>,
expect 2xx, parse body as nlohmann::json. Throws std::runtime_error on
transport / non-2xx / parse failure.
Vendors nlohmann/json v3.11.3 single header at cpp/vendor/nlohmann/json.hpp
(MIT). No CMake target needed — header-only; consumers add
cpp/vendor/ to include path.
- Replace TextColored+glyph with ImDrawList::AddCircleFilled in CellRenderer::Dots.
Dots are now font-independent: no dependency on Unicode glyph coverage. Fixes
"dots show as ?" on Karla/Roboto/Inter fonts that lack Geometric Shapes block.
- dots_glyph_size now controls circle radius (px) instead of font scale.
- BadgeRule.label is ignored for Dots (documented in data_table_types.h + docs).
- data_table.md bumped to v1.3.1 with capability growth log entry.
- docs/capabilities/data_table_renderers.md: Dots section updated + Common pitfalls
entry added: "Asumir que cualquier glyph Unicode renderea".
- dag_engine_ui/tabs.cpp: removed stale "● glyph" comment from BadgeRule.
- Recompiled: dag_engine_ui, registry_dashboard, graph_explorer, navegator_dashboard,
odr_console. All 5 apps deployed to Desktop/apps/. Build Linux + tests 4/4 green.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
PARTE A - CellRenderer::Dots (v1.3.0):
- Add Dots=8 to CellRenderer enum (data_table_types.h)
- Add dots_separator/dots_max/dots_show_count/dots_glyph_size fields to ColumnSpec
- Implement draw_cell_custom case Dots in data_table.cpp
- Parses comma-separated cell value into tokens
- Looks up each token in badges for color + optional glyph override
- Per-dot tooltip via tooltip_on_hover
- tql_emit: serialize renderer="dots" + dots_max/dots_show_count/dots_glyph_size/dots_separator
- tql_apply: deserialize all Dots fields
- tql_emit_test: +6 assertions (58 total, 0 failed)
- tql_apply_test: +8 assertions (114 total, 0 failed)
- test_column_specs: +2 tests (10/10 pass)
PARTE B - dag_engine_ui fix: 10 cols -> 6 cols (submodule commit 61314b7)
PARTE C - docs/capabilities/data_table_renderers.md:
- Update to v1.3.0
- Add decision tree for renderer selection
- Add CellRenderer::Dots section with canonical example
- Add Common pitfalls section (multiple columns, badge for free-text, etc.)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Tamano FIJO del popup (Always + SizeConstraints) y flags NoResize/NoMove
para evitar feedback loop entre auto-resize del popup y TextWrapped/SameLine
internos. Reemplaza GetWindowContentRegionMax() por offsets explicitos
calculados a partir del ancho fijo, ya que ese valor fluctua frame a frame
con padding/borders y provocaba el ensanche/encogido continuo.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
parallel_for_cpp_core: ThreadPool reutilizable con parallel_for(begin, end, fn)
y parallel_for_chunks(begin, end, fn(tid, lo, hi)). Captura excepciones del
worker y las relanza en el caller. Pareja CPU del despacho GPU para Monte
Carlo multi-core cuando dispatch GPU no compensa.
slider_cpp_core: wrapper de ImGui::SliderFloat/Int/Double con label muted
arriba, tokens (primary grab), full-width. Variantes float, float_log
(logaritmico), int, double. Para los calculadores que tienen 15-30 sliders
cada uno y se beneficia del estilo consistente.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Tres kernels Monte Carlo intensivos sobre las primitivas G1-G7 + las puras
CPU como oraculo de tests numericos. Apuntados a hyper-paralelizar los
calculadores de sources/calculadoras (vr_tiered_lab, mcmc-bayes / full / lab,
mcmc-visualizer) en RTX-class GPUs.
- mc_session_sim_gpu (K1): N sesiones independientes de K spins en paralelo
(1 thread = 1 sesion). Modelo variable-ratio escalonado con tiers (q, m),
modes Pure/Pity/Streak, miss_streak, drawdown. SSBOs summary[N*8] y
tier_counts[N*max_tiers]. Portea vr_tiered_lab.
- mc_metropolis_hastings_gpu (K2): M cadenas independientes 1D. Target
log-pdf inyectable como string GLSL (mismo patron de gl_shader). u_user[16]
para cambiar parametros desde sliders sin recompilar. Output compatible
con rhat_split / ess_basic.
- mc_random_walk_2d_gpu (K3): walkers 2D MH con trace_xy xy-interleaved en
SSBO; pasable directamente a gpu_histogram_2d sin readback intermedio.
Pipeline GPU-only para mcmc-visualizer.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Nuevo dominio cpp/functions/datascience con primitivas puras CPU para post-
proceso de samples Monte Carlo y diagnostico de cadenas MCMC. Diseñadas como
gemelas CPU de los kernels GPU (rng pareja con gpu_rng_glsl, MH 1D/ND con
mc_metropolis_hastings_gpu) para validar numericamente y para datasets
pequeños donde el dispatch GPU no compensa.
- rng: xoshiro256++ con uniform / normal (Box-Muller) / below (Lemire) /
categorical. Determinista bit-exacto dado seed.
- stats_summary: sum (Kahan), mean, var/std (Welford one-pass), min, max,
quantile / percentile (R type-7).
- autocorr: r(k), ACF, tau_int (Sokal) — diagnostico ACF y ESS.
- rhat_ess: Gelman-Rubin clasico y split + ESS basico (multi-chain).
- beta_dist: lgamma (Lanczos), beta_pdf, beta_cdf (continued fraction),
beta_quantile, mean/var/std — para inferencia Beta-Binomial.
- drawdown: max_dd absoluto/pct + underwater series para sesiones
simuladas y backtests.
- samples_to_grid_2d: binning 2D CPU para alimentar heatmap_cpp_viz /
contour_cpp_viz desde samples (x[], y[]).
- metropolis_hastings: MH 1D y ND con target log-pdf como std::function
(no normalizada).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Stack base de compute shaders OpenGL 4.3 para cargas Monte Carlo intensivas
en GPU. Reutiliza el patron de graph_force_layout_gpu (SSBO + compute) y se
integra con el resto del registry sin nuevos simbolos en gl_loader (todo lo
que se necesita ya estaba expuesto).
- gpu_ssbo: lifecycle de Shader Storage Buffer Objects.
- gpu_compute_program: compila compute GLSL 4.3 con preamble inyectable
(mismo pattern de gl_shader::compile_fragment).
- gpu_dispatch: dispatch_1d/2d/3d con ceil(N/local) automatico + barrier
helpers (storage, uniform, image, buffer_update, all).
- gpu_rng_glsl: PCG32 GLSL (uniform/normal/below) + SplitMix64 seed walkers
para sembrar deterministicamente N walkers desde un master seed.
- gpu_histogram_1d: SSBO float[N] -> uint[nbins] via atomicAdd.
- gpu_histogram_2d: SSBO float[2N] xy-interleaved -> uint[nx*ny] +
to_density helper para alimentar heatmap_cpp_viz.
- gpu_reduce: workgroup-shared sum/min/max/mean (local 256, partials CPU).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Necesario para que las funciones GPU compute (gpu_histogram_1d/2d, gpu_reduce,
mc_*_gpu) puedan setear uniforms uint en Windows. En Linux ya estaba
disponible via GL_GLEXT_PROTOTYPES.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Cuando una funcion del registry parte su .cpp en varios TUs por testabilidad
o separacion ImGui-vs-puro, cada TU adicional se registra como entrada propia
con su .md en lugar de extender file_path para listar varios archivos.
Aplicado a:
- graph_labels_select_cpp_viz: helpers puros (compute_degrees + labels_select).
- graph_viewport_selection_cpp_viz: clear/add/toggle/is_selected puros.
- graph_types_cpp_viz: TU de update_bounds + find_node_by_user_data.
graph_labels y graph_viewport actualizados para declarar las nuevas entradas
en uses_functions. Razon detallada en docs/adr/0003 + regla actualizada en
.claude/rules/uses_functions.md.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Logger global thread-safe con ring buffer in-memory de 2000 entradas + escritura
opcional a archivo. log_window flotante consume el ring buffer con filtros por
nivel, busqueda y autoscroll; se abre desde Settings -> Logs en la menubar.
selectable_text cubre el patron drag-to-select + Ctrl+C en cualquier ventana.
app_menubar y framework run_app integran log_window_render() en el frame loop.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Refina la convencion de layout: el top de cada app distribuible
solo lleva el .exe + DLLs nativas; todo lo demas (TTFs, enrichers,
runtime Python, MCP servers) vive en <exe_dir>/assets/.
Cambios:
- cpp/CMakeLists.txt::add_imgui_app — copia las 5 TTFs (Karla,
Roboto, DroidSans, Cousine, tabler-icons) a
$<TARGET_FILE_DIR>/assets/ en lugar de junto al exe.
- framework/app_base: nuevas funciones fn::asset_dir() y
fn::asset_path(name) que resuelven a <exe_dir>/assets/<name>.
- functions/core/icon_font.cpp::find_asset — anade
fn::asset_path(filename) como PRIMERA ruta de busqueda, antes
de las legacy ./<file> y ./assets/<file>. Mantiene los
fallbacks para dev (FN_ASSETS_DIR, FN_CPP_ROOT).
- .claude/commands/compile.md — el deploy a Desktop pone TTFs +
enrichers/ + runtime/ + gx-cli en <DEST>/assets/. Solo .exe y
DLLs nativas (duckdb.dll) quedan en el top. local_files/ se
preserva si existe.
Layout final:
Desktop/apps/<APP>/
├── <APP>.exe + *.dll (binario + DLLs Windows)
├── assets/ (read-only distribuible)
│ ├── *.ttf, enrichers/, runtime/, gx-cli, ...
└── local_files/ (per-PC, creado al primer arranque)
Esto cierra la separacion conceptual de la convencion: la carpeta
es trivial de zippear (solo .exe + assets/), el reset/sync es
trivial (local_files/), y todas las apps del registry adoptan el
mismo layout via fn_framework.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Toda app C++ basada en fn::run_app coloca sus archivos escribibles
bajo <exe_dir>/local_files/. Los distribuibles (.exe, dlls, ttfs,
enrichers/, runtime/) siguen junto al .exe. Esto deja la carpeta
distribuible limpia para zippear y separa con claridad lo que
viaja con la app de lo que el PC genera.
API publica en fn:: (cpp/framework/app_base.h):
- exe_dir() directorio del ejecutable
- local_dir() <exe_dir>/local_files/, creado on-demand
- local_path(name) <local_dir>/<name>
- migrate_to_local_files(...) mueve archivos viejos desde cwd/exe_dir
Cambios:
- run_app configura io.IniFilename = local_path("imgui.ini") y
llama migrate_to_local_files(["imgui.ini","app_settings.ini"])
antes de settings_load(). Migracion idempotente para PCs con
instalacion previa.
- app_settings.cpp usa local_path("app_settings.ini") en lugar de
hardcoded "app_settings.ini" relativo al cwd.
- cpp_apps.md §7 documenta la convencion como obligatoria. Las
apps deben usar fn::local_path() para cualquier archivo
escribible nuevo.
Beneficios:
- zip distribuible no se "ensucia" con .ini/.db generados al usar.
- reset trivial: borrar local_files/.
- backup/sync per-PC: solo local_files/ es propio del PC.
- elimina la mezcla de paths Linux/Windows que generaba bugs como
"projects\\default\\operations.db" en builds cross-platform.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
graph_labels_draw pinta etiquetas de nodos sobre el FBO del graph_renderer
via ImDrawList. Politica configurable: always-on para selected/hovered/
pinned, top-N por size*(degree+1), culling por viewport AABB y
min_node_pixel_size. Cap duro = max_visible + |always_*|.
API:
- graph_labels_draw(graph, viewport_state, policy, cb, user)
- graph_labels_draw_at(...) — variante con rect explicito
- graph_labels_select(...) — helper puro testeable
- graph_compute_degrees(...) — O(E)
Splitting en dos TUs:
- graph_labels.cpp — funciones draw (depende de ImGui)
- graph_labels_select.cpp — helpers puros para tests sin ImGui
12 tests en test_graph_labels (culling, max_visible cap, min_pixel_size,
always_* gating por viewport, top-N por score, edge cases). Todos verdes.
Integrado en demos_graph con UI: toggle Labels, sliders Max visible /
Font / Min px, checkboxes Selected/Hovered/Pinned. Golden de
graph_viewport regenerado.
Cierra issue 0049j.
Layout force-directed en GPU usando 5 compute shaders 4.3 + spatial hash
grid 64x64. API simetrica con graph_force_layout (CPU) para que el consumer
pueda swappear sin cambios. atomicCompSwap loop para float-add portable.
- cpp/functions/viz/graph_force_layout_gpu.{h,cpp,md}: nuevo modulo
- cpp/functions/gfx/gl_loader: anade glDispatchCompute, glMemoryBarrier,
glBindBufferBase, glGetBufferSubData (Windows wgl)
- cpp/tests/test_graph_force_layout_gpu.cpp: smoke + pinned + CPU vs GPU.
Crea ventana GLFW oculta GL 4.3; SKIP si headless o sin compute.
- demos_graph: checkbox "GPU layout" para swappear CPU/GPU en runtime
- issue movido a dev/issues/completed/