- store/store.go: anade tags JSON lowercase a DagRun + DagStepResult para que REST y WS devuelvan misma forma.
- cpp/apps/dag_engine_ui: gitlink al SHA con http_client + data_http.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sub-repo Gitea: dataforge/dag_engine_ui (a crear cuando se ejecute /full-git-push).
Gitlink al SHA inicial del scaffolding.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Completa el batch de 20 type .md extraidos a cpp/types/core/ y
cpp/types/viz/ apuntando a cpp/functions/core/data_table_types.h.
Quedan 2 que faltaban en commits anteriores: DrillStep_cpp_core
(undo/redo de drills, fase 10) y FilterPreset_cpp_core (Last7/30/90d,
ExcludeNulls, NonZero, fase 10).
Total types indexados: 206. Tabla via mcp__registry__fn_search
"file_path:data_table_types" o sqlite SELECT por file_path.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Apps que gestionan su propio LayoutStorage (cfg.auto_layouts=false +
cfg.layouts_cb=&own_cb) necesitan llamar layout_storage_apply_pending
en el momento correcto: despues de ImGui::NewFrame y ANTES de menubar
+ auto-dockspace + cualquier Begin() del frame.
Antes, si la app llamaba apply_pending dentro de render_fn (es decir,
mid-frame), ImGui cargaba el INI pero las dock-nodes no se restauraban
hasta el siguiente ciclo: las ventanas docked aparecian flotantes.
cfg.pre_frame es un std::function<void()> opcional que run_app y
run_app_test invocan justo despues de NewFrame, antes del bloque
auto_layouts_storage, antes de app_menubar y antes del auto-dockspace.
Default null = no-op, sin impacto en apps existentes.
Apps con auto_layouts=true (la mayoria) no necesitan tocar nada — el
framework ya hace apply_pending en su propio bloque. pre_frame es
puramente para apps con layout custom.
Co-Authored-By: Claude Opus 4.7 (1M context) <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>
Permite -D_GE_DIR=<path> y -D_DASH_DIR=<path> via cmake para apuntar
estas apps externas a un worktree aislado. Sin override, comportamiento
identico al previo. Habilita parallel-fix-issues sobre apps C++ cuyo
binario sale del arbol cpp/ pero cuyo source vive en projects/.
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>
registry/indexer.go ahora escanea <lang>/apps/*/app.md ademas de apps/ y
projects/*/apps/. cpp/apps/chart_demo y cpp/apps/shaders_lab pasan a estar
en registry.db con sus manifests.
Infraestructura de tests e2e (opt-in con -DFN_BUILD_TESTS=ON):
- vendor de Dear ImGui Test Engine (personal/open-source license).
- chart_demo_tests target con tests/chart_demo_tests.cpp.
- /e2e-cpp slash command para crear y ejecutar tests e2e.
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>
- cpp/CMakeLists.txt: register projects/osint_graph/apps/graph_explorer/
via add_subdirectory pattern (igual que registry_dashboard).
- dev/feature_flags.json: osint_graph_v1 = true (enabled_at 2026-04-30).
- dev/issues/{0049,0049k} → dev/issues/completed/. README index actualizado.
La app vive en su sub-repo dataforge/graph_explorer (push hecho al cerrar).
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/
graph_renderer 1.5.0:
- 6 shapes SDF (circle, square, diamond, hex, triangle, rounded square)
con dispatch en fragment shader y AA via fwidth.
- Atlas opcional de iconos Tabler bakeado por graph_icons; el shader
compone overlay desde un uniform vec4 u_icon_uvs[256]. Setter publico
graph_renderer_set_icon_atlas(r, tex, uv_table, count).
- Aristas direccionales: 6 vertices por arista (line + chevron de la
flecha) en una sola draw call; segmento principal acortado por el
radio del nodo target.
- Edge styles solid/dashed/dotted via descarte por arc_length en el
fragment shader; las lineas del chevron son siempre solidas.
graph_icons 1.0.0 (nuevo):
- Atlas RGBA8 512x512 = grid 16x16 (256 iconos max) bakeado con
stb_truetype desde tabler-icons.ttf.
- API: graph_icons_build/texture/region/uv_table/destroy. icon_id es
1-based; 0 reservado para "sin icono".
- Hook FN_GRAPH_ICONS_SKIP_GL=1 para tests sin contexto GL.
Demo demos_graph_styles en primitives_gallery: 6 EntityTypes (uno por
shape) con icono Tabler representativo + 3 RelationTypes (knows/uses/
owns) con flechas direccionales y los 3 estilos.
test_graph_icons: 6 casos cubriendo bake, regiones 1-indexed, uv_table
consistente, layout en grid 16x16, validacion de count fuera de rango,
y verificacion de alpha != 0 en las celdas tras bake.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
El buffer de aristas pasa a estatico (16B/arista: source, target, color,
flags) y solo se reupload cuando cambia el grafo. Las posiciones de los
nodos viven en un Texture Buffer Object (RG32F) actualizado por frame; el
vertex shader hace texelFetch con gl_VertexID & 1 para elegir endpoint.
Draw call: glDrawArraysInstanced(GL_LINES, 0, 2, edge_count) con divisor=1.
Para 100k aristas: el upload de 4.8 MB/frame baja a 0 en regimen estable.
edge_alpha pasa a uniform; la pre-multiplicacion en CPU desaparece. GLSL
sigue en 330 core (samplerBuffer/texelFetch estan en 1.40+).
gl_loader gana glBufferSubData, glVertexAttribIPointer y glTexBuffer (en
Linux ya estaban via GL_GLEXT_PROTOTYPES; ahora estan disponibles tambien
en MinGW/Windows).
Tests: nuevo test_graph_edge_static valida el layout de 16B y el packing
RGBA8 del fallback. test_visual sigue verde — render visualmente identico.
Bump graph_renderer 1.2.0 -> 1.3.0.
Issue 0049c. Tres optimizaciones internas en graph_renderer.cpp + un
helper puro en graph_force_layout para detectar convergencia. API publica
intacta — solo cambian el layout interno de los buffers, el shader y
los costes por frame.
1. RGBA8 color packing
- El instance buffer de nodos pasa de (x,y,size,r,g,b,a) 28B a
(x,y,size,color_u32) 16B (-43%). Aristas: 24B → 12B/vertex (-50%).
- Shaders desempaquetan con bit shifts (compatible GL 3.30+, no
necesita unpackUnorm4x8 que es 4.20+).
- Helpers expuestos: pack_rgba8 / unpack_rgba8 / modulate_alpha_rgba8
en graph_renderer.h. Los GraphNode.color y la paleta ya tenian el
layout correcto (R en LSB), asi que CPU ahora pasa el uint32 directo
sin convertir a 4 floats por nodo y por frame.
2. Capacity-tracked streaming buffers
- Sustituye el doble glBufferData de antes por:
glBufferData(NULL, capacity, STREAM_DRAW) // orphan + reserva
glBufferSubData(0, used_bytes, data) // solo lo usado
- capacity crece x2 cuando hace falta (inicial 4096 nodos /
8192 vertices de aristas) → reallocaciones en O(log N).
- Staging CPU (NodeInstance* / EdgeVertex*) reusado entre frames con
realloc, no malloc/free per frame.
3. Frustum cull (CPU-side)
- AABB del viewport en world coords con margen 10%.
- Aristas: skip si AABB del segmento no intersecta el viewport.
- Nodos: solo los visibles entran al instance buffer; visible_count
es el N que pasa a glDrawArraysInstanced. Pop-in de borde mitigado
por el margen.
4. graph_force_layout_should_pause(low_frames, min_consecutive)
- Helper puro: el caller mantiene el contador, la funcion solo
decide si parar. Reemplaza la rama inline en demos_graph.cpp.
- Test Catch2 con secuencias artificiales.
Tests: test_graph_pack_rgba8 (16401 asserts, 4 cases — roundtrip exhaustivo
+ alpha modulation + clamp). test_graph_should_pause (3 cases, 14 asserts).
Los 29 tests del cpp/tests/ siguen verdes (incluido test_visual con goldens).
Bump versiones:
- graph_renderer 1.1.0 → 1.2.0
- graph_force_layout 1.0.0 → 1.1.0 (tested: true via should_pause test)
Tres mejoras de UX/escala en el demo de grafos:
1. **Wheel zoom dentro del canvas no scrollea la pagina**
En graph_viewport.cpp tras procesar MouseWheel para zoom hacemos
io.MouseWheel = 0 — consume el evento para que el BeginChild padre
(la galeria) no scrollee a la vez que el grafo se acerca. Antes
sentia "doble accion" al rodar la rueda sobre el canvas.
2. **graph_force_layout: pool dinamico (soporta 1M nodos)**
El array static QuadNode[1<<20] (~48MB siempre reservados, tope
rigido en ~250k nodos por la fan-out) se reemplaza por
std::vector<QuadNode>. graph_force_layout_step llama a
quad_pool_reserve(5*N + 1024) ANTES de construir el arbol — asi las
referencias QuadNode& que mantenemos vivas durante quad_subdivide
no se invalidan por reallocaciones a mitad del build (resize solo
ocurre en el reserve inicial). Memoria escala lineal con N: 1M
nodos ≈ 240MB de pool, una vez por programa.
3. **Demo de grafo: sliders extendidos + cluster_r escala con sqrt(N)**
- "Nodes" pasa de 100..20k a 100..1M con escala logaritmica
(ImGuiSliderFlags_Logarithmic) para que el rango medio sea util.
- Nuevos sliders "Edges/node" (1..10) e "Inter %" (0..30%) — antes
hardcoded a 3 y 5%.
- cluster_radius y scatter ahora escalan con sqrt(N): a 1k nodos
~370 px de radio, a 1M ~12000 px. Antes era constante a 200/40
y los nodos quedaban empaquetados al subir N — visualmente "sin
limite cuadrado", esparcidos sobre un area proporcional al grafo.
- Golden de graph_viewport regenerado por la nueva fila de sliders.
Notas:
- A 1M nodos sin GPU compute esta limitado por el upload de aristas
(vertex pulling con TBO llega en 0049d). Render mantenible hasta
~200-300k.
- En Linux/Windows ambos builds limpios. 27/27 tests verde.
Tres atajos de rendimiento sin GPU compute (eso llega en 0049h). Probados
en Linux y cross-compile Windows, todos los tests pasan, OpenMP 4.5
detectado.
1. **OpenMP en graph_force_layout_step** (cpp/functions/viz/...)
- find_package(OpenMP) en cpp/CMakeLists.txt; fn_framework lo enlaza
PUBLIC para que cualquier app/funcion lo herede transparentemente.
Si no esta disponible, los pragmas se ignoran (single-thread).
- #pragma omp parallel for con guard if(N>=1024) en los 4 bucles
embarazosamente paralelos: zero forces, repulsion Barnes-Hut (con
schedule dynamic), gravity, integration (con reduction sobre energy).
La attraction-along-edges se queda secuencial: edges multiples
escriben en el mismo nodo y meterle atomic mata el speedup.
- quad_force usaba un static int stack[1<<20] (4MB compartidos entre
threads — race). Lo reemplazo por int stack[256] en pila: el
quadtree crece como log4(N) ~= 10 niveles para N <= 1M, asi que 256
es holgado y thread-safe sin coste.
- Esperable: ~4-8x menos tiempo CPU/step en 20k nodos en CPU multicore.
2. **Buffer orphan en graph_renderer** (edges + nodes)
- Antes del glBufferData(.., data, DYNAMIC_DRAW), un primer
glBufferData(.., NULL, DYNAMIC_DRAW) que descarta el buffer previo.
El driver da uno fresco sin esperar al frame anterior — evita los
sync stalls clasicos del DYNAMIC_DRAW reuploadeado cada frame.
- Esperable: 2-3x throughput de upload (Mesa/NVIDIA/AMD respetan el
hint).
3. **Auto-pause en demo_graph cuando converge**
- Si energy_per_node < 0.001 durante 30 frames consecutivos, paramos
la simulacion automaticamente. CPU/GPU a 0% cuando el grafo ya
esta estable. Resume con "Resume layout" o "Regenerate".
Lo de OpenMP se sustituye cuando entre 0049h (force layout en compute
shader): cuando llegue, los #pragma omp se borran. Orphan y auto-pause
son keepers definitivos.
Cuando se cambia "Size" en Settings la fuente se escala via
style.FontSizeBase y el contenido del child "##gallery_content" crece o
encoge proporcionalmente. La scroll_y se quedaba en pixeles absolutos,
asi que la linea logica visible "se bajaba" al usuario tras el cambio
de zoom.
Fix: cachear FontSizeBase entre frames y, cuando cambia, escalar
scroll_y por el ratio nuevo/viejo. Mantiene la misma linea arriba del
viewport — sin saltos.
Cierra 0049b. El context de fn::run_app pide ahora GL 4.3 core con
forward-compat global, habilitando compute shaders, SSBOs, image
load/store y atomic counters — bloques esenciales del graph_renderer GPU
del proyecto osint_graph (issues 0049f y 0049h).
Cambios:
- cpp/framework/app_base.cpp: 4.3 core + forward-compat. Comentario
marcando que es backward-compatible con shaders #version 330.
- cpp/apps/primitives_gallery/capture.cpp: deja explicitamente 3.3 core
porque WSL Mesa no entrega 4.3 offscreen (GLXBadFBConfig); ImGui +
ImPlot funcionan igual en 3.3 para los goldens.
- primitives_gallery: nuevo demo Gfx > gl_info que muestra
Vendor/Renderer/Version/GLSL en runtime + status 4.3 (verde) +
limites (MAX_TEXTURE_SIZE, MAX_VERTEX_ATTRIBS, MAX_UNIFORM_BLOCK_SIZE
y, si 4.3+, MAX_SHADER_STORAGE_BUFFER_BINDINGS y compute shared mem).
Solo glGetString/glGetIntegerv — sin loader extra.
- About bumped a 0.4.0 con la nota del nuevo demo y de GL 4.3.
- cpp/tests/test_visual.cpp: usa LIBGL_ALWAYS_SOFTWARE=1 al lanzar el
capture para alinear el driver con update_goldens.sh; sin esto las
diferencias de strings (llvmpipe vs d3d12) hacen que gl_info supere
el 1% de tolerancia.
- cpp/tests/golden/gl_info.png: nuevo golden.
Build verificado en Linux (cmake build OK) + Windows cross-compile
(cmake build OK). Las 27 pruebas pasan (incluida test_visual con 42
demos comparadas).
El modal Save-as-generator usaba BeginPopupModal + InputText + Button
crudo. Ahora usa fn_ui::modal_dialog_begin/end + fn_ui::text_input +
fn_ui::button del registry. El error inline usa fn_tokens::colors::error
en vez de ImVec4(1, 0.4, 0.4, 1). Anade modal_dialog.cpp, text_input.cpp
y button.cpp al CMakeLists del app.
Raw ImGui::Begin*/Selectable/BeginPopupModal: 11 -> 8.
El sidebar agrupaba demos por categoria con un Selectable+PushStyleColor
manual por item. Ahora usa fn_ui::tree_view con las categorias como
ramas (default-open via SetNextItemOpen + ImGuiCond_FirstUseEver) y las
demos como hojas seleccionables. Visualmente equivalente: separadores
por categoria, item activo coloreado.
Raw ImGui::Begin*/Selectable: 4 -> 3 (Selectable eliminado).
Nueva seccion "Tests visuales y CI gate (issue 0048)" describiendo:
- Como capturar/regenerar goldens con cpp/scripts/update_goldens.sh.
- Como diagnosticar un diff (PNG actual en cpp/build/tests/visual_actual/
vs golden en cpp/tests/golden/).
- Cuando test_visual SKIPea (sin goldens, sin binario, sin GL).
- CI gate check_tested.sh y los pasos para satisfacerlo.
Issue 0048.
- update_goldens.sh: build primitives_gallery + lanza --capture sobre
cpp/tests/golden/ con LIBGL_ALWAYS_SOFTWARE=1.
- check_tested.sh [days]: CI gate que falla si una funcion C++ creada en
los ultimos N dias (default 30) no tiene tested:true en su .md. Hookeado
al final de run_tests.sh. No-op si registry.db no existe.
Issue 0048.
- png_diff.{h,cpp}: pixel_diff_ratio(path_a, path_b, channel_threshold) con
stb_image. Devuelve PngDiffResult con pixels_total, pixels_different y
diff_ratio. Si dimensiones difieren, diff_ratio=1.0.
- test_visual.cpp: invoca primitives_gallery --capture sobre tmpdir, compara
cada PNG vs cpp/tests/golden/<demo>.png con tolerancia 1% pixels distintos
(threshold 5/255 por canal). SKIPea con WARN si:
* golden dir vacio (no hay goldens todavia)
* binario primitives_gallery no construido
* el binario falla al capturar (entorno sin GL)
- CMakeLists: registra test_visual con FN_TEST_GOLDEN_DIR, FN_TEST_GALLERY_BIN,
FN_TEST_TMP_DIR y FN_TEST_REPO_ROOT (para que la captura corra desde la
raiz del repo y resuelva paths relativos como sql_workbench's registry.db).
- golden/: 41 PNGs iniciales generados en este entorno (WSL +
LIBGL_ALWAYS_SOFTWARE=1). Pueden regenerarse con cpp/scripts/update_goldens.sh.
Issue 0048.