docs/superpowers/plans/2026-04-09-text-coverage-comparison-implementation.md
Ref: Size: 13.0 KiB
# Text Coverage Comparison Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Add a `--text-compare` mode that renders one specimen across four side-by-side coverage variants so text sharpness can be evaluated visually without changing the default terminal path.
**Architecture:** Reuse the existing Vulkan/font/atlas smoke-test path in `src/main.zig`, extend the renderer with a small fragment-side coverage control, and render four panels using the same atlas and instance format. Keep the default terminal rendering path unchanged; the comparison mode is an isolated inspection tool.
**Tech Stack:** Zig 0.15, Vulkan renderer in `src/renderer.zig`, GLSL fragment shader in `shaders/cell.frag`, Wayland window path in `src/main.zig`, configured font lookup in `src/font.zig`
---
## File Structure
- Modify: `src/main.zig`
- Add the `--text-compare` CLI path, specimen layout helpers, and comparison-mode render loop.
- Modify: `src/renderer.zig`
- Add a small configurable coverage parameter path that the fragment shader can read.
- Modify: `shaders/cell.frag`
- Apply variant coverage shaping while preserving the current baseline behavior.
- Test: `src/main.zig`
- Add tests for specimen layout and panel placement helpers.
- Test: `src/renderer.zig`
- Add tests for the coverage-parameter packing/planning helpers that do not require a live Vulkan device.
### Task 1: Add coverage-variant planning helpers in the renderer
**Files:**
- Modify: `src/renderer.zig`
- Test: `src/renderer.zig`
- [ ] **Step 1: Write the failing tests**
Add tests for a small coverage parameter helper:
```zig
test "coverageVariantParams returns identity values for baseline" {
const params = coverageVariantParams(.baseline);
try std.testing.expectEqualDeep([2]f32{ 1.0, 0.0 }, params);
}
test "coverageVariantParams steepens non-baseline variants" {
const mild = coverageVariantParams(.mild);
const medium = coverageVariantParams(.medium);
const crisp = coverageVariantParams(.crisp);
try std.testing.expect(mild[0] > 1.0);
try std.testing.expect(medium[0] > mild[0]);
try std.testing.expect(crisp[0] > medium[0]);
}
```
- [ ] **Step 2: Run test to verify it fails**
Run: `rm -rf /tmp/zig-global-cache-coverage-plan && cp -a /home/xanderle/.cache/zig /tmp/zig-global-cache-coverage-plan && ZIG_GLOBAL_CACHE_DIR=/tmp/zig-global-cache-coverage-plan zig build test --summary all`
Expected:
- FAIL because `coverageVariantParams` and the enum are undefined.
- [ ] **Step 3: Add the minimal helper types and implementation**
Add a small enum and helper in `src/renderer.zig`:
```zig
pub const CoverageVariant = enum(u32) {
baseline,
mild,
medium,
crisp,
};
fn coverageVariantParams(variant: CoverageVariant) [2]f32 {
return switch (variant) {
.baseline => .{ 1.0, 0.0 },
.mild => .{ 1.15, 0.0 },
.medium => .{ 1.3, 0.0 },
.crisp => .{ 1.55, -0.08 },
};
}
```
- [ ] **Step 4: Run test to verify it passes**
Run: `rm -rf /tmp/zig-global-cache-coverage-plan && cp -a /home/xanderle/.cache/zig /tmp/zig-global-cache-coverage-plan && ZIG_GLOBAL_CACHE_DIR=/tmp/zig-global-cache-coverage-plan zig build test --summary all`
Expected:
- PASS for the new renderer helper tests.
- [ ] **Step 5: Commit**
```bash
git add src/renderer.zig
git commit -m "Add text coverage variant helpers"
```
### Task 2: Plumb coverage controls through push constants and fragment shader
**Files:**
- Modify: `src/renderer.zig`
- Modify: `shaders/cell.frag`
- Test: `src/renderer.zig`
- [ ] **Step 1: Write the failing test**
Add a test that checks the push-constant default remains baseline-safe:
```zig
test "PushConstants defaults preserve baseline coverage shaping" {
const pc = PushConstants{
.surface_size = .{ 800.0, 600.0 },
.cell_size = .{ 8.0, 16.0 },
.clear_color = .{ 0.0, 0.0, 0.0, 1.0 },
.coverage_params = .{ 1.0, 0.0 },
};
try std.testing.expectEqualDeep([2]f32{ 1.0, 0.0 }, pc.coverage_params);
}
```
- [ ] **Step 2: Run test to verify it fails**
Run: `rm -rf /tmp/zig-global-cache-push-plan && cp -a /home/xanderle/.cache/zig /tmp/zig-global-cache-push-plan && ZIG_GLOBAL_CACHE_DIR=/tmp/zig-global-cache-push-plan zig build test --summary all`
Expected:
- FAIL because `PushConstants` does not yet contain `coverage_params`.
- [ ] **Step 3: Add push-constant and shader plumbing**
Extend `PushConstants` in `src/renderer.zig`:
```zig
pub const PushConstants = extern struct {
surface_size: [2]f32,
cell_size: [2]f32,
clear_color: [4]f32,
coverage_params: [2]f32,
};
```
Update `drawCells` to accept coverage params:
```zig
pub fn drawCells(
self: *Context,
instance_count: u32,
cell_size: [2]f32,
clear_color: [4]f32,
coverage_params: [2]f32,
) !void
```
Set the push constants with that field populated.
Update `shaders/cell.frag`:
```glsl
layout(push_constant) uniform PushConstants {
vec2 surface_size;
vec2 cell_size;
vec4 clear_color;
vec2 coverage_params;
} pc;
float shape_coverage(float alpha) {
float curved = pow(alpha, pc.coverage_params.x);
return clamp(curved + pc.coverage_params.y, 0.0, 1.0);
}
void main() {
float alpha = texture(glyph_atlas, in_uv).r;
alpha = shape_coverage(alpha);
out_color = mix(in_bg, in_fg, alpha);
}
```
Keep the baseline identity behavior by passing `{1.0, 0.0}` for normal paths.
- [ ] **Step 4: Run test to verify it passes**
Run: `rm -rf /tmp/zig-global-cache-push-plan && cp -a /home/xanderle/.cache/zig /tmp/zig-global-cache-push-plan && ZIG_GLOBAL_CACHE_DIR=/tmp/zig-global-cache-push-plan zig build test --summary all`
Expected:
- PASS with shader compilation and the new push-constant test green.
- [ ] **Step 5: Commit**
```bash
git add src/renderer.zig shaders/cell.frag
git commit -m "Plumb text coverage controls through renderer"
```
### Task 3: Keep the default draw path on baseline coverage
**Files:**
- Modify: `src/main.zig`
- [ ] **Step 1: Add the failing compile-path update**
Update existing `drawCells` call sites to pass baseline coverage:
```zig
ctx.drawCells(instance_count, cell_size, clear_color, .{ 1.0, 0.0 })
```
- [ ] **Step 2: Run test to verify it fails**
Run: `rm -rf /tmp/zig-global-cache-baseline-plan && cp -a /home/xanderle/.cache/zig /tmp/zig-global-cache-baseline-plan && ZIG_GLOBAL_CACHE_DIR=/tmp/zig-global-cache-baseline-plan zig build test --summary all`
Expected:
- FAIL until all `drawCells` call sites are updated.
- [ ] **Step 3: Update existing draw paths**
Update all current `ctx.drawCells(...)` calls in `src/main.zig` to use baseline params:
```zig
const baseline_coverage = .{ 1.0, 0.0 };
```
Pass `baseline_coverage` in:
- the normal terminal loop,
- `runDrawSmokeTest`,
- any other smoke/helper render loop.
- [ ] **Step 4: Run test to verify it passes**
Run: `rm -rf /tmp/zig-global-cache-baseline-plan && cp -a /home/xanderle/.cache/zig /tmp/zig-global-cache-baseline-plan && ZIG_GLOBAL_CACHE_DIR=/tmp/zig-global-cache-baseline-plan zig build test --summary all`
Expected:
- PASS with the default rendering path behavior unchanged.
- [ ] **Step 5: Commit**
```bash
git add src/main.zig
git commit -m "Keep default rendering on baseline coverage"
```
### Task 4: Add specimen layout helpers for comparison mode
**Files:**
- Modify: `src/main.zig`
- Test: `src/main.zig`
- [ ] **Step 1: Write the failing tests**
Add tests for panel offsets and specimen instance generation planning:
```zig
test "comparisonPanelOrigins splits four panels left to right" {
const origins = comparisonPanelOrigins(4, 80, 24);
try std.testing.expectEqual(@as(f32, 0), origins[0][0]);
try std.testing.expect(origins[1][0] > origins[0][0]);
try std.testing.expect(origins[2][0] > origins[1][0]);
try std.testing.expect(origins[3][0] > origins[2][0]);
}
test "specimenLines remains fixed and non-empty" {
const lines = comparisonSpecimenLines();
try std.testing.expect(lines.len >= 5);
try std.testing.expect(lines[0].len > 0);
}
```
- [ ] **Step 2: Run test to verify it fails**
Run: `rm -rf /tmp/zig-global-cache-layout-plan && cp -a /home/xanderle/.cache/zig /tmp/zig-global-cache-layout-plan && ZIG_GLOBAL_CACHE_DIR=/tmp/zig-global-cache-layout-plan zig build test --summary all`
Expected:
- FAIL because the comparison helpers are undefined.
- [ ] **Step 3: Add the layout helpers**
Add focused helpers in `src/main.zig`:
```zig
const ComparisonVariant = struct {
label: []const u8,
coverage: [2]f32,
};
fn comparisonVariants() [4]ComparisonVariant {
return .{
.{ .label = "baseline", .coverage = renderer.coverageVariantParams(.baseline) },
.{ .label = "mild", .coverage = renderer.coverageVariantParams(.mild) },
.{ .label = "medium", .coverage = renderer.coverageVariantParams(.medium) },
.{ .label = "crisp", .coverage = renderer.coverageVariantParams(.crisp) },
};
}
fn comparisonSpecimenLines() []const []const u8 { ... }
fn comparisonPanelOrigins(panel_count: usize, panel_cols: u32, top_margin_rows: u32) [4][2]f32 { ... }
```
Keep the specimen text fixed to the approved five lines.
- [ ] **Step 4: Run test to verify it passes**
Run: `rm -rf /tmp/zig-global-cache-layout-plan && cp -a /home/xanderle/.cache/zig /tmp/zig-global-cache-layout-plan && ZIG_GLOBAL_CACHE_DIR=/tmp/zig-global-cache-layout-plan zig build test --summary all`
Expected:
- PASS for the new comparison-layout tests.
- [ ] **Step 5: Commit**
```bash
git add src/main.zig
git commit -m "Add text comparison layout helpers"
```
### Task 5: Implement the `--text-compare` render mode
**Files:**
- Modify: `src/main.zig`
- [ ] **Step 1: Add the failing compile-path update**
Add a CLI branch:
```zig
if (args.len >= 2 and std.mem.eql(u8, args[1], "--text-compare")) {
return runTextCoverageCompare(alloc);
}
```
- [ ] **Step 2: Run test to verify it fails**
Run: `rm -rf /tmp/zig-global-cache-compare-plan && cp -a /home/xanderle/.cache/zig /tmp/zig-global-cache-compare-plan && ZIG_GLOBAL_CACHE_DIR=/tmp/zig-global-cache-compare-plan zig build test --summary all`
Expected:
- FAIL because `runTextCoverageCompare` is undefined.
- [ ] **Step 3: Implement the comparison mode**
Add `runTextCoverageCompare(alloc)` in `src/main.zig` by reusing the draw-smoke structure:
```zig
fn runTextCoverageCompare(alloc: std.mem.Allocator) !void {
// 1. Create Wayland connection, window, and Vulkan context.
// 2. Load configured font and atlas.
// 3. Build one instance list for the fixed specimen in each panel.
// 4. Upload atlas and packed instances once.
// 5. Render in a loop, selecting one panel coverage variant per draw.
}
```
Implementation notes:
- Use four separate draws per frame, one per panel.
- Re-upload only once; reuse the same instance buffer data.
- Keep the specimen identical in each panel and only vary `coverage_params`.
- Reserve enough horizontal spacing so panel text does not overlap.
- [ ] **Step 4: Run test to verify it passes**
Run: `rm -rf /tmp/zig-global-cache-compare-plan && cp -a /home/xanderle/.cache/zig /tmp/zig-global-cache-compare-plan && ZIG_GLOBAL_CACHE_DIR=/tmp/zig-global-cache-compare-plan zig build test --summary all`
Expected:
- PASS
- [ ] **Step 5: Commit**
```bash
git add src/main.zig
git commit -m "Add text coverage comparison mode"
```
### Task 6: Full verification
**Files:**
- Modify: none
- Test: `src/main.zig`, `src/renderer.zig`, `shaders/cell.frag`
- [ ] **Step 1: Run the full test suite**
Run: `rm -rf /tmp/zig-global-cache-compare-final && cp -a /home/xanderle/.cache/zig /tmp/zig-global-cache-compare-final && ZIG_GLOBAL_CACHE_DIR=/tmp/zig-global-cache-compare-final zig build test --summary all`
Expected:
- PASS
- [ ] **Step 2: Run a build verification**
Run: `rm -rf /tmp/zig-global-cache-compare-build && cp -a /home/xanderle/.cache/zig /tmp/zig-global-cache-compare-build && ZIG_GLOBAL_CACHE_DIR=/tmp/zig-global-cache-compare-build zig build`
Expected:
- PASS
- [ ] **Step 3: Run the comparison mode manually**
Run: `zig build run -- --text-compare`
Expected:
- One window opens.
- Four panels are visible.
- The specimen text matches across all panels.
- Baseline, mild, medium, and crisp variants are visually distinguishable.
- [ ] **Step 4: Commit**
```bash
git add src/main.zig src/renderer.zig shaders/cell.frag
git commit -m "Verify text coverage comparison mode"
```
## Self-Review
- Spec coverage:
- New CLI comparison mode: Task 5
- Reuse existing Vulkan/font/atlas path: Task 5
- Four side-by-side panels with baseline + three variants: Tasks 4 and 5
- Configured font family and size reuse: Tasks 3 and 5
- Shader-only first pass: Tasks 1 and 2
- Validation with build and manual comparison: Task 6
- Placeholder scan:
- No `TODO`, `TBD`, or deferred “figure this out later” markers remain.
- Type consistency:
- `CoverageVariant`, `coverageVariantParams`, `coverage_params`, `comparisonVariants`, and `runTextCoverageCompare` are named consistently across tasks.