Digit height must be content-width-aware: HERO1 clips on emery/basalt, QUAD clips on basalt #121

Open
opened 2026-09-06 00:17:33 +02:00 by robert · 1 comment
Owner

Goal

Surfaced by #59's PR #120 (HERO1/GRID6 Phase 1b work), not introduced by it — this is a pre-existing limitation of the whole digit-rendering mechanism from Phase 1 (#59/PR #87) that nobody had checked against real screen widths until PR #120's own basalt/HERO1 verification looked.

page_render_digit_height(template, slot) returns a fixed pixel height per template/slot, independent of the cell's actual width. page_render_glyph_width()'s fixed ratio (digit = 0.6x height, dot = 0.2x, gap = 0.1x — page_render_geometry.c) means a value's on-screen width scales linearly with digit height regardless of how wide the cell actually is. Nothing currently checks that the resulting run fits.

Verified by direct arithmetic against the shipped constants (DIGIT_WIDTH_NUM/DEN=6/10, DOT_WIDTH_NUM/DEN=2/10, GLYPH_GAP_NUM/DEN=1/10), independent of the PR's own screenshots: a "28.4"-shaped value (3 digits + 1 dot + 3 gaps) needs 3(0.6h) + 0.2h + 3(0.1h) = 2.3h pixels of run width.

Finding 1 — HERO1 clips on both emery and basalt

HERO1's digit height is DESIGN.md's literal "~140px" figure. At h=140, "28.4" needs 2.3 x 140 = 322px. Emery's content is 200px wide; basalt's is 144px. Both clip. The largest height that keeps "28.4" on-screen on emery is 200 / 2.3 ~= 87px — smaller than HERO2's own 96px hero, which would make HERO1 the smaller of the two "one dominant number" shapes, contradicting the physical-size table the 140px figure came from in the first place. Confirmed via real emery/basalt emulator screenshots in PR #120 (last digit sliced off on emery; unusable on basalt).

Finding 2 — QUAD (unchanged, shipped since Phase 1/PR #87) clips on basalt

QUAD's digit heights (56px ordinary / 76px emphasised, D29) were sized against emery's 100x104 cell. Basalt's real QUAD cell is only 72x74 (half its 144x148 content). A 3-digit-plus-dot value visibly overlaps the neighbouring cell. This affects every QUAD page on basalt today, including both default pages (Effort, Progress) already shipping. DESIGN.md rule 7 ("GRID6 drops to QUAD on basalt") does not currently deliver "degrades without clipping" as a result, since the fallback target itself isn't basalt-safe. Confirmed via a real basalt emulator screenshot in PR #120.

Acceptance criteria

  • page_render_digit_height() (or a new sizing function alongside it) becomes aware of the cell/content width it will actually be drawn into, not just (template, slot)
  • HERO1 renders a representative worst-case value (e.g. FIELD_SPEED's "28.4", or a longer field) without clipping on emery, gabbro and basalt
  • QUAD renders the same worst-case value without clipping on basalt (currently the fallback target for GRID6, so this blocks GRID6's own basalt degrade from being genuinely clip-free)
  • Existing host geometry tests (watchapp/tests/test_page_render_geometry.c) gain a real "does this text fit this cell at this height" assertion, not just a tiling/no-gap check
  • Real emulator verification on all three targets (screenshots), not just host-test coverage

Files

  • watchapp/src/c/page_render_geometry.c (page_render_digit_height(), the glyph-width ratio constants)
  • watchapp/src/c/page_render.c (call sites)

Notes

This is a mechanism change affecting every template already shipped (HERO2/QUAD from Phase 1, HERO1/GRID6 from Phase 1b) — explicitly why PR #120 flagged rather than silently patched it (per D48: a checked fact reported for a decision, not resolved unilaterally). #59 depends on this issue since its own "degrades without clipping on basalt" acceptance criterion is not met until this lands.

## Goal Surfaced by #59's PR #120 (HERO1/GRID6 Phase 1b work), not introduced by it — this is a pre-existing limitation of the whole digit-rendering mechanism from Phase 1 (#59/PR #87) that nobody had checked against real screen widths until PR #120's own basalt/HERO1 verification looked. `page_render_digit_height(template, slot)` returns a fixed pixel height per template/slot, independent of the cell's actual width. `page_render_glyph_width()`'s fixed ratio (digit = 0.6x height, dot = 0.2x, gap = 0.1x — `page_render_geometry.c`) means a value's on-screen width scales linearly with digit height regardless of how wide the cell actually is. Nothing currently checks that the resulting run fits. **Verified by direct arithmetic against the shipped constants (`DIGIT_WIDTH_NUM/DEN=6/10`, `DOT_WIDTH_NUM/DEN=2/10`, `GLYPH_GAP_NUM/DEN=1/10`), independent of the PR's own screenshots**: a "28.4"-shaped value (3 digits + 1 dot + 3 gaps) needs `3(0.6h) + 0.2h + 3(0.1h) = 2.3h` pixels of run width. ### Finding 1 — HERO1 clips on both emery and basalt HERO1's digit height is DESIGN.md's literal "~140px" figure. At h=140, "28.4" needs `2.3 x 140 = 322px`. Emery's content is 200px wide; basalt's is 144px. Both clip. The largest height that keeps "28.4" on-screen on emery is `200 / 2.3 ~= 87px` — smaller than HERO2's own 96px hero, which would make HERO1 the *smaller* of the two "one dominant number" shapes, contradicting the physical-size table the 140px figure came from in the first place. Confirmed via real emery/basalt emulator screenshots in PR #120 (last digit sliced off on emery; unusable on basalt). ### Finding 2 — QUAD (unchanged, shipped since Phase 1/PR #87) clips on basalt QUAD's digit heights (56px ordinary / 76px emphasised, D29) were sized against emery's 100x104 cell. Basalt's real QUAD cell is only 72x74 (half its 144x148 content). A 3-digit-plus-dot value visibly overlaps the neighbouring cell. This affects every QUAD page on basalt today, including both default pages (Effort, Progress) already shipping. DESIGN.md rule 7 ("GRID6 drops to QUAD on basalt") does not currently deliver "degrades without clipping" as a result, since the fallback target itself isn't basalt-safe. Confirmed via a real basalt emulator screenshot in PR #120. ## Acceptance criteria - [ ] `page_render_digit_height()` (or a new sizing function alongside it) becomes aware of the cell/content width it will actually be drawn into, not just `(template, slot)` - [ ] HERO1 renders a representative worst-case value (e.g. `FIELD_SPEED`'s "28.4", or a longer field) without clipping on emery, gabbro and basalt - [ ] QUAD renders the same worst-case value without clipping on basalt (currently the fallback target for GRID6, so this blocks GRID6's own basalt degrade from being genuinely clip-free) - [ ] Existing host geometry tests (`watchapp/tests/test_page_render_geometry.c`) gain a real "does this text fit this cell at this height" assertion, not just a tiling/no-gap check - [ ] Real emulator verification on all three targets (screenshots), not just host-test coverage ## Files - `watchapp/src/c/page_render_geometry.c` (`page_render_digit_height()`, the glyph-width ratio constants) - `watchapp/src/c/page_render.c` (call sites) ## Notes This is a mechanism change affecting every template already shipped (HERO2/QUAD from Phase 1, HERO1/GRID6 from Phase 1b) — explicitly why PR #120 flagged rather than silently patched it (per D48: a checked fact reported for a decision, not resolved unilaterally). #59 depends on this issue since its own "degrades without clipping on basalt" acceptance criterion is not met until this lands.
Author
Owner

Closed by PR #123 (area/digit-height-content-aware). New page_render_fit_digit_height(text, max_width, requested_height) — a second function alongside the unchanged page_render_digit_height(), called from page_render.c's prv_draw_cell() (the one place with both the live formatted text and the cell width in hand). Binary-searches the real page_render_text_width() rather than a closed-form estimate of it, so it can never disagree with what that function itself says fits; only clamps downward, never raises a height that already fit.

A genuinely important discovery along the way: checking HERO1/QUAD-on-basalt surfaced that HERO2's 96px hero and QUAD's 56/76px ceilings were also already overflowing on emery itself for realistic values (FIELD_SPEED's "28.4" needs 217px against HERO2's 200px hero; HR/POWER_3S/AVG_POWER's 3-digit values need 109-149px against QUAD's 100px cell) — nobody had actually measured this since PR #87 picked its glyph-width ratio "by eye" rather than D55's own recommended ≤0.50. I independently re-verified the headline case by hand with the actual integer-truncated arithmetic the C code uses (3×(96×6/10) + (96×2/10) + 3×(96×1/10) = 3×57+19+27 = 217) before trusting it — exact match. The fix corrects these as a side effect of the same general mechanism, not a separate patch, and every already-fitting value (QUAD's two-digit cadence, HERO2's lower-cell decimals) is asserted unchanged.

HERO1's own 140px figure is kept as a ceiling, not lowered — a genuinely short value still draws at the full size; D55 gets an append-only correction (per D48, same treatment D24's DPI correction got) rather than a rewritten number, and DESIGN.md section 3 gets a one-line footnote pointing to it.

Honestly left open: gabbro's round bezel overshoot (HERO1's "28.4" now fits the rectangular content rect correctly, but that rect itself is ~16px too wide for the physical round screen) — a pre-existing gap affecting every template on round, correctly scoped to #62, not patched here.

Real verification: pebble build clean on all three targets, all 47 tests in test_page_render_geometry plus 126 across the other three host suites (173 total) hand-compiled with gcc and run directly — zero regressions.

Closed by PR #123 (`area/digit-height-content-aware`). New `page_render_fit_digit_height(text, max_width, requested_height)` — a second function alongside the unchanged `page_render_digit_height()`, called from `page_render.c`'s `prv_draw_cell()` (the one place with both the live formatted text and the cell width in hand). Binary-searches the real `page_render_text_width()` rather than a closed-form estimate of it, so it can never disagree with what that function itself says fits; only clamps downward, never raises a height that already fit. **A genuinely important discovery along the way**: checking HERO1/QUAD-on-basalt surfaced that HERO2's 96px hero and QUAD's 56/76px ceilings were *also* already overflowing on emery itself for realistic values (`FIELD_SPEED`'s "28.4" needs 217px against HERO2's 200px hero; HR/POWER_3S/AVG_POWER's 3-digit values need 109-149px against QUAD's 100px cell) — nobody had actually measured this since PR #87 picked its glyph-width ratio "by eye" rather than D55's own recommended ≤0.50. I independently re-verified the headline case by hand with the actual integer-truncated arithmetic the C code uses (`3×(96×6/10) + (96×2/10) + 3×(96×1/10) = 3×57+19+27 = 217`) before trusting it — exact match. The fix corrects these as a side effect of the same general mechanism, not a separate patch, and every already-fitting value (QUAD's two-digit cadence, HERO2's lower-cell decimals) is asserted unchanged. HERO1's own 140px figure is kept as a ceiling, not lowered — a genuinely short value still draws at the full size; D55 gets an append-only correction (per D48, same treatment D24's DPI correction got) rather than a rewritten number, and DESIGN.md section 3 gets a one-line footnote pointing to it. Honestly left open: gabbro's round bezel overshoot (HERO1's "28.4" now fits the *rectangular* content rect correctly, but that rect itself is ~16px too wide for the physical round screen) — a pre-existing gap affecting every template on round, correctly scoped to #62, not patched here. **Real verification**: `pebble build` clean on all three targets, all 47 tests in `test_page_render_geometry` plus 126 across the other three host suites (173 total) hand-compiled with gcc and run directly — zero regressions.
robert reopened this issue 2026-09-06 12:57:06 +02:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
robert/PedalPebble#121
No description provided.