diff --git a/docs/line-box-fidelity-investigation.md b/docs/line-box-fidelity-investigation.md new file mode 100644 index 0000000..f4217d9 --- /dev/null +++ b/docs/line-box-fidelity-investigation.md @@ -0,0 +1,197 @@ +# Line-box fidelity: measured corrections rejected + +Measured on 2026-09-12 against `main` at `9f287d3`. No Rust change is +recommended from this investigation: each of three corrections improved the +360px landing screen but regressed both wider screens. The coordinator +explicitly changed the deliverable to this documented negative result. +Thresholds, plugin goldens, manifest checksums, diagnostics, and source maps +remain unchanged. No implementation was made, so no new RED/GREEN regression +test is claimed. + +## Measurement identity and baseline + +The binary built in this worktree reported +`devup-mcp 0.4.5 (9f287d34e4b1)`: + +```text +C:/Users/owjs3/orca/workspaces/devup-mcp/W11-line-box-fidelity/target/debug/devup-mcp.exe +``` + +Acquisition ran from the main checkout's `harness/render`, with +`DEVUP_MCP_BIN` set to that binary. Its existing call bank replayed Figma +responses; this worktree's binary generated the modules and typography themes. +The unchanged main-checkout `devup-mcp-visual` release binary performed the +comparison at channel tolerance 24. Builds used two jobs, disabled dev/test +debug symbols and incremental compilation, and the worktree's own target. + +An initial render of copied main-checkout generated inputs reproduced +11.41% / 3.11% / 1.84%. Fresh acquisition with the identified worktree binary +and a serial main-checkout render measured 11.41% / 3.10% / 1.84%. +The 0.01 percentage point difference at 992px is observed run-to-run variance, +not an improvement attributed to a code change. A concurrent preliminary +render is excluded from the baseline evidence; the serial rerun is authoritative. + +| Screen | Width | Fresh baseline | Final unchanged generator | +| --- | ---: | ---: | ---: | +| `landing-833-3640` | 360 | 11.41% | 11.41% | +| `landing-833-3322` | 992 | 3.10% | 3.10% | +| `landing-832-2975` | 1920 | 1.84% | 1.84% | + +## Three disposable probes + +These were experiments on generated themes or the browser DOM, not Rust +implementations. Each starts from the same baseline; corrections were not +stacked. The final generated themes were restored byte for byte. + +| Probe | 360px | 992px | 1920px | Decision | +| --- | ---: | ---: | ---: | --- | +| Whole-pixel token line boxes | 9.64% | 3.71% | 2.31% | Reject | +| Measured height on HUG text | 10.03% | 3.94% | 2.42% | Reject | +| Measured height on multiline HUG text | 10.35% | 3.81% | 2.31% | Reject | + +The first probe changes generated numeric token line heights to +`round(fontSize * ratio) / fontSize`. For 130% it uses the captured raw ratio +`1.2999999523162842` before rounding the pixel advance; other ratios retain +their generated values. It retains numeric ratios rather than emitting pixel +overrides on token-bound text. This is a deliberately bounded experiment, +not a proposed general conversion for other percentages or variable font sizes. + +The second probe assigns a browser element the corresponding Figma text +height only when vertical sizing is HUG. The third additionally requires the +design height to exceed 1.5 nominal percentage line advances. These height +probes match normalized text content and font size, selecting the innermost +matching element. Repeated labels can match the same element, so these probes +are not a complete node-ID-based projection or proof about every HUG node. +The distinctive headings in the table below have unambiguous matches. + +The token experiment was repeated in the main harness on modules freshly +generated by `9f287d34e4b1`; the HUG experiments were also repeated there. +The table records those fresh-input runs. The initial copied-input HUG probe +measured 3.95% at 992px; this is the same 0.01-point variability as the baseline. +No changed binary exists: the experiment column names the only mutation. +All three violate the no-regression requirement. The mobile improvement alone +does not justify bending the R8 FILL/HUG policy. + +## Per-block measurements explain the sign change + +DOM measurements use `getBoundingClientRect`, rather than rounded display +sizes. Y coordinates are relative to the screen's top. These values were +measured on the unchanged generated inputs in the initial local harness. + +| Node | Font | Design height | DOM height | DOM minus design | Design Y | DOM Y | +| --- | ---: | ---: | ---: | ---: | ---: | ---: | +| `833:3646`, mobile heading | 38 | 245 | 246.953125 | +1.953125 | 90 | 90 | +| `833:3647`, mobile subtitle | 18 | 69 | 70.171875 | +1.171875 | 359 | 360.953125 | +| `833:3664`, mobile benchmarks | 28 | 36 | 36.390625 | +0.390625 | 888 | 893.125 | +| `833:3724`, mobile features | 28 | 36 | 36.390625 | +0.390625 | 1468 | 1475.015625 | +| `833:3770`, mobile community | 28 | 36 | 36.390625 | +0.390625 | 2274 | 2284.03125 | +| `833:3328`, tablet heading | 52 | 272 | 270.375 | -1.625 | 250 | 250 | +| `833:3329`, tablet subtitle | 24 | 62 | 62.375 | +0.375 | 546 | 544.375 | +| `833:3346`, tablet benchmarks | 36 | 47 | 46.796875 | -0.203125 | 940 | 940.75 | +| `832:2980`, desktop heading | 52 | 272 | 270.375 | -1.625 | 200 | 200 | +| `832:2981`, desktop subtitle | 24 | 62 | 62.375 | +0.375 | 496 | 494.375 | +| `832:2998`, desktop benchmarks | 36 | 47 | 46.796875 | -0.203125 | 900 | 900.75 | + +The mobile heading has five lines at nominal 49.4px; the wider heading has +four at nominal 67.6px. Subtracting a constant from every line to fix mobile +would worsen the wider heading's existing deficit. This rules out a single +constant signed per-line adjustment, not every font-dependent correction. +The benchmark Y values also show that a heading's local error does not alone +predict the next section's error: other contributions can offset it. + +## Direct PNG observation of repeated glyphs + +For the mobile heading, primary-colored pixels in its design rectangle +(`x=16, y=90, width=328, height=245`) give: + +```text +Figma PNG Zero glyph bands begin y 101 / 150 / 199 (49, 49) +browser same bands begin y 101 / 150 / 200 (49, 50) +``` + +The inclusive bands are Figma `101–127 / 150–176 / 199–225` and browser +`101–127 / 150–176 / 200–226`. Extraction selects rows with more than two +pixels satisfying `abs(R-90)<25`, `abs(G-68)<25`, and `B>230`, then groups +adjacent rows. This isolates the repeated blue word from the dark heading. + +At desktop, using the design heading rectangle `x=280, y=200, width=551, +height=272`, the same extraction gives Figma `216–251 / 284–319 / 352–387` +and browser `214–250 / 282–318 / 349–385`. The design starts advance by +68/68px, versus browser 68/67px; the browser also starts two pixels higher. +Tablet's third band intersects nearby artwork, so it is not used as an +independent glyph-advance measurement. + +These observations support whole-pixel advances for these particular design +headings and fractional browser advances. They do not determine Figma's +general rounding rule, font ascent/descent treatment, or first/last-line +leading algorithm. The desktop initial offset additionally leaves font +metrics, rasterization, and leading distribution unresolved. Wider aggregate +regression alone does **not** disprove local whole-pixel advance: a screenshot +also includes the other layout interactions and internal glyph placement. + +## PERCENT normalization is not this residual + +Both `codegen/text.rs::line_height` and +`theme/devup_json.rs::typography_value` already convert +`129.99999523162842` to `1.3` using `(percent / 10).round() / 10`. +The raw ratio differs by approximately `0.0000000476837158`. +At 38px, the advance difference is `0.0000018119812px`, under two millionths +of a pixel; across five lines it is about `0.00000906px`. It cannot explain +the measured 1.953125px height difference. At 52px the difference is about +`0.00000247955px` per line, also negligible. + +All implicated heading/subtitle styles are token-bound. Re-rounding the +inline emission cannot change them, and the token path already has the same +normalization. Neither function was changed to manufacture progress. This +finding concerns the captured 130% value, not an audit of arbitrary percentage +precision in unrelated styles. + +A correct future fix must account for the active font size and metrics, +actual wrapped line count, initial glyph placement, and how parent layout +propagates the result at each width. It must test those independently before +claiming a universal pixel-advance rule. Width-specific constants that merely +fit these three screenshots would not establish that behavior. + +## Separate gate defect: build identity versus later untracked files + +The first base test run measured **1048 passed, 1 failed, 2 ignored**. +Only `devup-mcp --test cli`'s +`version_build_id_reports_the_repository_dirty_state` failed, with +`left: false`, `right: true`. Its binary reported clean `9f287d34e4b1`, while +its runtime `git status --porcelain=v1 --untracked-files=normal` saw the +temporary probe scripts created after the build script had captured identity. + +`build.rs` records dirty status during compilation and watches selected crate +paths plus Git HEAD/index. A later untracked file elsewhere in the worktree +changes runtime status without necessarily rerunning that build script. Thus +the test compares two different moments. Reproduction sequence: build a clean +binary, create an untracked root file without modifying a watched input, then +run `cargo test -p devup-mcp --test cli +version_build_id_reports_the_repository_dirty_state`. + +This is separate from line-box fidelity and is not fixed here. Temporary +probe files were removed before the final gate, and the report was staged +before compiling so the build-time and test-time dirty state stayed stable. + +## Final verification + +All commands use this worktree's target. `CARGO_BUILD_JOBS=2` also limits +commands without an explicit `-j` argument. + +| Gate | Result | +| --- | --- | +| `cargo fmt --all -- --check` | Pass | +| `cargo clippy --locked --workspace --all-targets --all-features -j 2 -- -D warnings` | Pass, zero warnings | +| `cargo test --workspace -j 2 --no-fail-fast` | 1049 passed, 0 failed, 2 ignored | +| `cargo insta test --workspace --all-features --check` | Pass; no snapshots to review | +| `cargo test --locked -p devup-mcp --test stdio_smoke` | 2 passed, 0 failed | + +Test linking emitted the existing localized MSVC library-creation messages as +`linker_messages` warnings; the clippy gate itself emitted zero warnings. +After staging this document, the test binary correctly reported +`9f287d34e4b1-dirty`. The dirty suffix records the report, not a generator +implementation change. The render measurements above all use the modules +acquired from the earlier identified clean binary. + +The final restored render passed at 11.41% / 3.10% / 1.84%. There is no +accepted improvement, and the mobile fidelity defect remains open.