b98c28dc
Vulkan bounded-waits spec: match inline-test convention
a73x 2026-04-18 11:31
Commit message
docs/superpowers/specs/2026-04-18-vulkan-bounded-waits-design.md
| Old | New | ||
|---|---|---|---|
| @@ -223,8 +223,7 @@ A shell script `tests/check_unbounded_vk.sh` runs as part of `zig build test`. I | |||
| 223 | 223 | ||
| 224 | **Exempt files:** | 224 | **Exempt files:** |
| 225 | 225 | ||
| 226 | - `src/vk_sync.zig` — the helper module itself. | 226 | - `src/vk_sync.zig` — the helper module itself (contains inline `test` blocks per codebase convention). |
| 227 | - `tests/**/*.zig` — test code may legitimately construct fences/swapchains and call raw `vkd` methods (`tests/vk_sync_test.zig` will need to call `vkd.waitForFences` on never-signaled fences to verify timeout behavior). | ||
| 228 | 227 | ||
| 229 | The script exits non-zero if any match is found in non-exempt files, printing each match with file:line. No `// vk-unbounded-ok:` comment exemption — every legitimate unbounded wait goes through `waitIdleForShutdown` (already exempt as the canonical path). If a future case truly needs a raw call, it should add a method to `vk_sync.zig`, not bypass the gate. | 228 | The script exits non-zero if any match is found in non-exempt files, printing each match with file:line. No `// vk-unbounded-ok:` comment exemption — every legitimate unbounded wait goes through `waitIdleForShutdown` (already exempt as the canonical path). If a future case truly needs a raw call, it should add a method to `vk_sync.zig`, not bypass the gate. |
| 230 | 229 | ||
| @@ -234,15 +233,15 @@ The choice of shell script over a Zig AST walker is deliberate: four identifier | |||
| 234 | 233 | ||
| 235 | Three layers: | 234 | Three layers: |
| 236 | 235 | ||
| 237 | **Unit tests (`tests/vk_sync_test.zig`):** | 236 | **Unit tests — inline `test` blocks in `src/vk_sync.zig`** (following the existing codebase convention; all other Zig modules put `test "..."` blocks inline in the source file rather than in a separate `tests/` directory). |
| 238 | 237 | ||
| 239 | - `waitFenceBounded` returns `error.VkWaitTimeout` for a never-signaled fence within `fence_wait_timeout_ns + 50ms` slack. | 238 | The Vulkan-touching tests require a live `vk.DeviceWrapper`, which isn't available in pure unit tests. To keep the helpers testable without a Vulkan context, the testable surface is the non-Vulkan logic: |
| 240 | - `waitFenceBounded` succeeds for a fence signaled before the call. | 239 | |
| 241 | - `acquireImageBounded` returns the image_index for a healthy swapchain. | 240 | - `logVkTimeout` emits exactly one log line when called 100 times in a tight loop (rate-limit working) — pure logic, atomics + `std.log.warn`. |
| 242 | - `acquireImageBounded` returns `error.VkAcquireTimeout` when all images are in flight (saturate the swapchain by acquiring without submitting). | 241 | - `logVkTimeout` emits a second log line if called again after 5+ seconds (can be unit-tested with a mockable clock, or by refactoring the 5s threshold into a parameter for the test and passing a small duration). |
| 243 | - `acquireImageBounded` returns `error.OutOfDateKHR` for both `VK_SUBOPTIMAL_KHR` and `VK_ERROR_OUT_OF_DATE_KHR` results. | 242 | - Constants (`fence_wait_timeout_ns`, `acquire_timeout_ns`) are public and have the expected values. |
| 244 | - `logVkTimeout` emits exactly one log line when called 100 times in a tight loop (rate-limit working). | 243 | |
| 245 | - `logVkTimeout` emits a second log line if called again after 5+ seconds. | 244 | The Vulkan-touching behavior (`waitFenceBounded` returning `VkWaitTimeout` on a never-signaled fence, `acquireImageBounded` folding `suboptimal_khr` into `OutOfDateKHR`) is verified by inspection of the bindings' documented return values plus integration testing against the live renderer (see below). A full Vulkan-context test harness is out of scope for v1. |
| 246 | 245 | ||
| 247 | **Integration test (manual or scripted):** simulate the wedge by submitting a fence that intentionally never signals (e.g., wait on a semaphore that's never signaled), then run a few frames of `drawCells`. Verify: no hang, log lines appear with backoff, main loop continues, Wayland input dispatch keeps working. This is hard to fully automate without a Vulkan mock layer; it can be scripted as a sanity check the human runs by hand. | 246 | **Integration test (manual or scripted):** simulate the wedge by submitting a fence that intentionally never signals (e.g., wait on a semaphore that's never signaled), then run a few frames of `drawCells`. Verify: no hang, log lines appear with backoff, main loop continues, Wayland input dispatch keeps working. This is hard to fully automate without a Vulkan mock layer; it can be scripted as a sanity check the human runs by hand. |
| 248 | 247 | ||
| @@ -263,7 +262,7 @@ Suggested commit order: | |||
| 263 | 262 | ||
| 264 | ### Build wiring | 263 | ### Build wiring |
| 265 | 264 | ||
| 266 | `src/vk_sync.zig` is imported by both `src/renderer.zig` and `src/main.zig`. It needs to be a separate module in `build.zig`, similar to how `cell_instance` is wired (build.zig:311). Concretely: create `vk_sync_mod` with `addImport("vulkan", vulkan_module)`; have `renderer_mod`, `exe_mod`, `main_test_mod`, `renderer_test_mod`, and `capture_mod` each `addImport("vk_sync", vk_sync_mod)`. Also add a `vk_sync_tests` step pointing at `tests/vk_sync_test.zig` (with `addImport("vulkan", vulkan_module)` and `addImport("vk_sync", vk_sync_mod)`), and add it to the `test` step alongside the other test invocations. | 265 | `src/vk_sync.zig` is imported by both `src/renderer.zig` and `src/main.zig`. It needs to be a separate module in `build.zig`, similar to how `cell_instance` is wired (build.zig:311). Concretely: create `vk_sync_mod` with `addImport("vulkan", vulkan_module)`; have `renderer_mod`, `exe_mod`, `main_test_mod`, `renderer_test_mod`, and `capture_mod` each `addImport("vk_sync", vk_sync_mod)`. Also create a `vk_sync_test_mod` (same root_source_file, separate module instance for the inline tests) and wire it to the `test` step via `b.addTest({.root_module = vk_sync_test_mod})`, matching the pattern used by `png_tests` (build.zig:308). |
| 267 | 266 | ||
| 268 | The grep gate `tests/check_unbounded_vk.sh` is invoked from `build.zig`'s `test` step via `b.addSystemCommand(&.{ "tests/check_unbounded_vk.sh" })` and added as a step dependency. | 267 | The grep gate `tests/check_unbounded_vk.sh` is invoked from `build.zig`'s `test` step via `b.addSystemCommand(&.{ "tests/check_unbounded_vk.sh" })` and added as a step dependency. |
| 269 | 268 | ||