3a8a75f205
- app.md - appicon.ico Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
110 lines
6.8 KiB
Markdown
110 lines
6.8 KiB
Markdown
---
|
|
name: altsnap_jitter_test
|
|
lang: cpp
|
|
domain: tools
|
|
version: 0.1.0
|
|
description: "Regression test for multi-viewport window jitter + iconified survival + Alt+RMB resize + Alt+LMB move. Six phases: p1 main-window sync, p2 AltSnap on main HWND, p3 AltSnap on secondary viewport HWND, p4 iconify+restore preserves floating panels, p5 Alt+RMB consumed by WndProc, p6 Alt+LMB consumed by WndProc."
|
|
tags: [imgui, test, regression, headless]
|
|
uses_functions: []
|
|
uses_types: []
|
|
framework: "imgui"
|
|
entry_point: "main.cpp"
|
|
dir_path: "apps/altsnap_jitter_test"
|
|
repo_url: ""
|
|
icon:
|
|
phosphor: "arrows-clockwise"
|
|
accent: "#dc2626"
|
|
---
|
|
|
|
# altsnap_jitter_test
|
|
|
|
Headless C++ harness para validar el subclass anti-jitter del framework
|
|
(`cpp/framework/app_base.cpp`): movimiento/redimensionado externos sin
|
|
temblor en la ventana principal y en viewports secundarios, iconified main
|
|
no pierde paneles flotantes, Alt+RMB resize anywhere, Alt+LMB move anywhere.
|
|
|
|
## Que hace
|
|
|
|
Seis fases en una sola sesion de `fn::run_app`:
|
|
|
|
1. **p1.sync** (cross-platform). Warmup 8 frames, mueve la ventana principal via `glfwSetWindowPos` un step por frame durante 60 frames. Muestrea pos OS (`glfwGetWindowPos`) vs pos ImGui (`GetMainViewport()->Pos`). Tolerancia 1px. Cuenta divergencias = `bad_sync`.
|
|
2. **p2.altsnap** (Windows). Worker thread fakea `WM_ENTERSIZEMOVE` + burst de 30 `SetWindowPos(SWP_ASYNCWINDOWPOS)` + `WM_EXITSIZEMOVE` sobre el HWND principal. Aserta `renders_during==0` (subclass del WndProc principal y gate del main loop activos).
|
|
3. **p3.secondary** (Windows). Fuerza creacion de un viewport secundario (panel flotante, `ConfigViewportsNoAutoMerge=true`), localiza su HWND en `pio.Viewports` via `find_secondary_viewport_hwnd`, y repite el bracket sobre el. Valida que el subclass per-frame cubre tambien el HWND secundario.
|
|
4. **p4.minimize** (Windows). State machine 4 steps con `kP4SettleFrames=20` entre cada uno. Captura `IsWindow(secondary_hwnd)` antes/durante/despues de `glfwIconifyWindow` + `glfwRestoreWindow`. Asserta los 3 estados vivos AND `renders_iconified > 0` (frame loop sigue activo durante iconify para que los flotantes no se pierdan).
|
|
5. **p5.alt_rmb** (Windows). `fn::internal::set_force_alt_for_test(true)` + `SendMessageW(hwnd, WM_RBUTTONDOWN, MK_RBUTTON, MAKELPARAM(40,40))` sincrono mismo-hilo. Asserta `fn::internal::alt_rmb_resize_count()` incrementa en 1. En test mode el handler salta el `PostMessage SC_SIZE` para no atrapar al harness en modal.
|
|
6. **p6.alt_lmb** (Windows). Mismo patron para `WM_LBUTTONDOWN`. Asserta `fn::internal::alt_lmb_move_count()` incrementa en 1.
|
|
|
|
PASS = todas las fases con sus deltas positivos. SKIP en Linux para p2-p6.
|
|
|
|
## Run
|
|
|
|
WSL/Linux (sanity build; p2-p6 skipped):
|
|
```bash
|
|
cd cpp && cmake -B build/linux -DFN_BUILD_TESTS=OFF
|
|
cmake --build build/linux --target altsnap_jitter_test -j4
|
|
xvfb-run -a -s "-screen 0 1280x800x24" \
|
|
env LIBGL_ALWAYS_SOFTWARE=1 GALLIUM_DRIVER=llvmpipe \
|
|
build/linux/apps/altsnap_jitter_test/altsnap_jitter_test
|
|
echo "EXIT: $?"
|
|
```
|
|
|
|
Windows (cross-compile + Desktop deploy + run):
|
|
```bash
|
|
source bash/functions/infra/e2e_run_cpp_windows.sh
|
|
e2e_run_cpp_windows altsnap_jitter_test
|
|
```
|
|
|
|
## Output esperado (PASS, Windows)
|
|
|
|
```
|
|
[p1.sync] DONE frames=60 max_sync=0px max_clamp=0px bad_sync=0 bad_clamp=0
|
|
[p2.altsnap] DONE renders before=N during=0 after=M
|
|
[p3.secondary] DONE renders before=N during=0 after=M
|
|
[p4.minimize] DONE alive(before=1 during=1 after=1) renders_iconified=20
|
|
[p5.alt_rmb] DONE alt_resize delta=1 (after=1) sizemove_enter delta=0 (after=2)
|
|
[p6.alt_lmb] DONE alt_move delta=1 (after=1)
|
|
[altsnap_jitter] p1=PASS p2=PASS p3=PASS p4=PASS p5=PASS p6=PASS overall=PASS
|
|
```
|
|
|
|
En Linux/xvfb p2-p6 reportan SKIPPED. P1 puede mostrar lag pre-existente bajo xvfb por como X procesa `SetWindowPos`; no es un fallo del fix.
|
|
|
|
## Criterio de fallo
|
|
|
|
- `p1_bad_sync > 0`: ImGui viewport->Pos quedo fuera de sincronia con la pos OS. Roto el callback GLFW + per-frame sync.
|
|
- `p2_renders_during > 0`: la app sigue dibujando durante un bracket AltSnap en el HWND principal. Roto el subclass del WndProc principal o la gate del main loop.
|
|
- `p3_renders_during > 0`: la app sigue dibujando durante un bracket AltSnap en un HWND **secundario** (panel flotante). Roto el escaneo per-frame de `pio.Viewports` que instala el subclass en cada platform window.
|
|
- `p4 alive(during)=0`: floating panel se cierra/desaparece al minimizar el main. Regresion del fix iconified+secondary.
|
|
- `p4 renders_iconified == 0`: el iconified-gate volvio a `glfwWaitEvents+continue` global sin chequear secondaries. Floating panels se congelarian.
|
|
- `p5 alt_resize delta == 0`: Alt+RMB no se consume. Subclass no esta en el chain (`ImGui_ImplGlfw_WndProc` capturo nuestra WndProc como prev y no chainea bien) o flag `force_alt_for_test` no se aplica.
|
|
- `p6 alt_move delta == 0`: misma raiz que p5 pero para LMB.
|
|
|
|
## Donde vive el fix
|
|
|
|
`cpp/framework/app_base.cpp`:
|
|
- GLFW pos/size callbacks (anti-jitter capa 1).
|
|
- Per-frame viewport sync (capa 2).
|
|
- `unordered_map<HWND, WNDPROC> g_subclassed` + per-frame `install_sizemove_subclass_hwnd` sobre `pio.Viewports` (capa 3, multi-HWND).
|
|
- Iconified gate detecta secondary viewports y fall-through si existen.
|
|
- `WM_LBUTTONDOWN`/`WM_RBUTTONDOWN` Alt-held → `WM_SYSCOMMAND, SC_MOVE|HTCAPTION` / `SC_SIZE|dir`.
|
|
- `io.ConfigWindowsMoveFromTitleBarOnly = true` (floating panels respetan header-only).
|
|
- `fn::internal::*` counters expuestos para tests headless.
|
|
|
|
`cpp/framework/app_base.h`:
|
|
- `namespace fn::internal { sizemove_enter_count(); alt_rmb_resize_count(); alt_lmb_move_count(); rbuttondown_seen_count(); set_force_alt_for_test(bool); }`.
|
|
|
|
## Notas
|
|
|
|
- `keybd_event(VK_MENU)` NO es fiable para drivear `GetAsyncKeyState` desde tests headless cross-compilados — la sesion de input del proceso no esta foreground. Usa `set_force_alt_for_test(true)` + `SendMessageW` sincrono mismo-hilo. Bypassa kernel-input filter (que dropea silenciosamente `PostMessage(WM_RBUTTONDOWN)` sintetizado).
|
|
- ImGui_ImplGlfw subclassea el HWND despues que nosotros (vendor `cpp/vendor/imgui/backends/imgui_impl_glfw.cpp` linea ~820, captura `bd->PrevWndProc = our_subclass`). Por eso ImGui llama a nuestro WndProc via `CallWindowProc(prev_wndproc, ...)` y todos los mensajes nos llegan en orden correcto. No re-subclassear (provoca recursion infinita via cycle).
|
|
- Test mode (`set_force_alt_for_test(true)`) hace que el WndProc cuente pero NO postee `SC_SIZE`/`SC_MOVE` — evita quedarse atrapado en modal sizemove. La parte "entrar al modal" se valida por p2/p3 fakeando `WM_ENTERSIZEMOVE` directamente.
|
|
|
|
|
|
## Capability growth log
|
|
|
|
Una linea por bump SemVer. Bump-type segun `.claude/commands/version.md`:
|
|
- `major`: breaking observable (CLI args, schema BBDD propia, formato wire).
|
|
- `minor`: feature aditiva (nuevo panel, endpoint, opcion).
|
|
- `patch`: bugfix sin cambio observable.
|
|
|
|
- v0.1.0 (2026-05-18) — baseline.
|