UI physical size: text and taps in points
UI physical size: text and taps in points
Section titled “UI physical size: text and taps in points”Every size in the UI is a number of canvas units, and a canvas unit is not a physical size. The same 12-unit label is
9.5 pt on an iPhone, 16.8 pt on an iPad and 18 pt on the Apple TV. The fit tests check geometry in canvas units, and a
few tests already assert a point floor for one element (shop card text and level-up role chips at 11 pt, several 44 pt
tap targets). What was missing is a whole-panel, every-token view in points that cannot quietly get worse, and a profile
for the smallest of the four screens (iphone_se).
This audit turns every font token, and the text and tap targets of five panels, into points per device, and pins today’s numbers as a ratchet: the UI may get bigger, never smaller. Nothing here changes what ships. It makes unreadable text a number in a test, fails a change that shrinks any token or any recorded control (each control against its own record) or adds a control below the floor, and says how to record any change that improves it. What it still cannot see is under “What the ratchet does not see”.
| File | Role |
|---|---|
tests/physical_size_fixture.gd (PhysicalSizeFixture) |
Profiles, the unit-to-point maths, the measuring, the pure ratchet comparison, the panel builders |
tests/test_ui_physical_size.gd |
The tests, including the four ratchet tests and the mutation proofs |
tests/ui_physical_size_allow.json |
The ratchet (version 4): every font token in points per profile, and per panel and profile the smallest text and tap target and every text control and tap target, each under its element key, in points. Written by BH_BLESS=1, never by hand |
tools/ui_physical_size/report.gd |
Prints the tables for one profile and writes a fidelity-result/1 |
tools/device_sim/registry.d/ui-physical-size.json |
Registers the report with the device-sim harness, once per profile |
From canvas units to points
Section titled “From canvas units to points”The window stretches the 1280 x 720 canvas to the screen (canvas_items, expand), then touch devices zoom it by
1.3 (Tokens.TOUCH_SCALE). So:
k = min(screen_px.x / 1280, screen_px.y / 720) x (1.3 on touch, 1.0 on the TV)pt per unit = k / (device pixels per point)| Profile | Screen px | px per pt | k | pt per unit |
|---|---|---|---|---|
iphone |
2868 x 1320 | 3 | 2.383 | 0.7944 |
iphone_se |
1334 x 750 | 2 | 1.354 | 0.6771 |
ipad |
2752 x 2064 | 2 | 2.795 | 1.3975 |
tv |
1920 x 1080 | 1 | 1.5 | 1.5 |
iphone, ipad and tv are ShopLayoutFixture.DEVICES rows (the same ones the shop and text-size fit tests use);
iphone_se, which that fixture does not carry, is read from tools/device_profiles/profiles.json, the repo’s single
source of device facts (the device-sim drift lint checks every hand-copied table against it). Nothing here restates a
device row. A test compares the three shared rows with ShopLayoutFixture, another compares all four with DEVICE /
SIZES in tools/ui_capture/capture.gd, and a third pins iphone_se and the information rows to profiles.json.
iphone is the most lenient phone, not a typical one, and iphone_se is not the worst phone either. On every
phone the canvas is fitted to the screen’s height, so points per unit is the landscape height in points over 720,
times 1.3: 440 pt tall gives 0.794, 360 pt tall (a mini) gives 0.650.
| Phone (information only, never ratcheted) | pt per unit | Default-size tokens below 11 pt |
|---|---|---|
iphone profile (2868 x 1320) |
0.7944 | 4 |
| lab iPhone16,2 (2796 x 1290) * | 0.7764 | 8, including font.sm at 10.9 pt (51 uses) |
| common 6.1 inch (2556 x 1179) * | 0.7096 | 9 |
iphone_se profile (1334 x 750) |
0.6771 | 10 |
a12_phone profile, iPhone XS class (2436 x 1125) |
0.6771 | 10 |
| iPhone 12/13 mini (2340 x 1080) * | 0.6500 | 10 |
The report prints the information rows when it runs the iphone profile (other_phones_information_only in its
metrics): every touch profile in profiles.json that is not one of the four audited ones, a12_phone today. The rows
marked * are not in profiles.json, so the tool no longer prints them; their numbers were worked out once with the same
formula and are kept here as context. Adding one to the tool means adding the device to profiles.json, which the
device-sim harness would then also run.
Floors
Section titled “Floors”| Floor | Value | Where it comes from |
|---|---|---|
| Text on a phone or tablet | 11.0 pt | MIN_PT in tests/test_shop_card_text_fit.gd and tests/test_level_up_panel.gd |
| Text on the TV | 22.5 pt | The same two tests’ MIN_UNITS (15 units) x 1.5 pt per unit |
| Tap target | 44.0 pt | StartMenu.TOUCH_ROW_MIN_PT |
These are constants the repo already asserts somewhere else; this audit reuses them rather than inventing new ones, and a test pins that they still equal their sources. [Likely] they are close to Apple’s published minimums: 11 pt for the smallest iOS text style, a 44 x 44 pt tap target, and 23 pt for the smallest tvOS style (Caption 2). That comes from memory and was not re-checked against the current guidelines. The TV has no tap floor: the game is played with a pad there, so nothing on a TV can be tapped and its tap column is always empty.
The TV floor is 0.5 pt under Apple’s. font.sm (51 uses) and font.menu.kicker are 15 units, exactly 22.5 pt on the
TV, so they pass the repo’s floor and would fail 23 pt. The audit stays on the repo’s floor (moving it is a design call,
see below) and reports the band instead: tv tokens that pass 22.5 pt but sit under 23 pt are printed by the token test
and written to the report as tokens_under_apple_caption_information_only. Never asserted.
What is measured
Section titled “What is measured”Text is every visible Label, Button and RichTextLabel that has text, internal children included. Its size
is the theme font size (or the LabelSettings size) times the scale the control is drawn at, times pt per unit. The
drawn scale matters: it includes the start menu’s phone shrink, the pause menu’s column fit and the level-up card’s focus
scale (1.0 to 1.03). The fit tests’ walker (TextSizeFixture.text_controls) uses get_children(), which skips the
internal children a control was added with (Node.INTERNAL_MODE_BACK); the level-up’s RECOMMENDED pill is one, and it is
the smallest text on that panel. PhysicalSizeFixture therefore has a walker of its own (get_children(true)), for text
and for tap targets, and a test pins that it finds everything the fit tests’ walker finds.
A tap target is a visible Control whose mouse_filter is STOP and that is a BaseButton or has gui_input
connected (the start-menu rows and level-up cards are GlassPanels, not buttons). A full-screen dim does not count.
Its size is the smaller of its width and height in points: the control’s own rectangle, with no allowance for a
finger’s imprecision.
Panels are each built fresh in a SubViewport sized to the profile, at Default text size, the way the matching
text-size fit test builds them, with the rows the profile’s platform shows (a control a platform never draws would
pin a minimum nobody sees):
| Panel | Built as |
|---|---|
start_menu |
The store row set (no REMOTE row) with the longest operator notice up. The notice link is a control on the phones and the iPad only: tvOS has no browser (Platform.can_open_links() is false there), so the TV’s notice has no link and no URL text. On a phone the link is the smallest text and tap target on the screen, so it is measured there |
pause_menu |
The Dev row set with the longest remote-code readout. A test cannot force BuildConfig.dev_tools() off, so this is a superset of the store menu and its smallest values are lower bounds for the store build |
level_up |
The real panel under the HUD’s top clear, REROLL and SKIP up, measured in two states, unioned like the shop’s tabs: the fit test’s offer (a crystal, a weapon and a stat card, no clear winner), and an offer with a clear winner (armor, max HP, a crystal pair) whose first card carries the RECOMMENDED pill |
settings |
Widest labels, with the rows the platform shows: no Mac-only Display row (WindowMode.available() is true on macOS only; its extra row makes the panel fit-shrink on a phone), and the drone glow row and the - / + step buttons on the touch profiles only |
shop |
Every tab in turn, measured as one panel: a control that is the same node on every tab (the rail, the header, Back) is recorded once, every other control per tab |
The RECOMMENDED pill is font.xs on the first card, drawn by ui/level_up_panel.gd whenever the top upgrade score beats
the second by RECOMMEND_GAP (an ordinary roll). Card 0 has the focus when the panel opens and a focused card is drawn
1.03 times bigger, so a player sees the pill at 9.82 pt on iphone until the focus moves. The audit measures it with the
focus on the second card, where it is drawn at scale 1.0: font.xs x pt per unit exactly, 9.53 pt, the smaller of the two
states a player sees. The audit also checks that the clear-winner offer really built the pill (and the fit test’s offer
did not): if a scoring change removed it, the panel reports a problem and the report is inconclusive, so a vanished
pill cannot read as a smaller minimum getting bigger.
Each panel is settled for 0.4 s of real time (tweens run on the clock, not on frames), measured, left for 30 frames
and measured again (the level-up’s second pass visits the two states in the opposite order). The two must agree to
0.02 pt, control by control; a panel that is still moving, or built with no text at all, fails the test and makes the
report inconclusive instead of recording a number it cannot trust.
Button prompts are forced to what the profile’s player sees: none on a phone or tablet (touch has no buttons to draw),
pad glyphs on the TV. Left unforced, ButtonPrompt falls back to the last input device any earlier test used, and the
full gate caught the result on the first version of this audit: after a keyboard-driven test the level-up hint row
(CHOOSE, SHIP STATS) was visible on a phone, so the same panel measured a different set of controls in a full run than
on its own. test_a_panel_does_not_depend_on_the_last_input_device pins it.
What is recorded
Section titled “What is recorded”Every font.* token, in points, per profile; and for each panel and profile the smallest text and tap target plus
every visible text control (text) and tap target (taps), in points, under its element key. A key is the
control’s path below the panel root, for example
CenterContainer#0/VBoxContainer#0/Container#1/VBoxContainer#0/Container[tutorial]/HBoxContainer#0/Title for
the start menu’s Tutorial title. Each segment, first match wins (version 4; version 3 keyed cards and rows by position):
-
a node the UI names (
title.name = "Title") keeps that name; -
a node it does not name gets an engine name such as
@Label@4711, whose number is a process-wide counter that depends on every node created before it, in any test, so that name is never used as is. The segment is built from its base (the class, or the wanted name it collided with):<base>[<value>]when the node carries anidormodemeta: shop cards and rail tabs (id), level-up cards (id), start-menu rows (mode). Reordering the Pilot items indata/bible.json, or adding one, leaves every other card’s key alone;<base>'<words>'for a button with text (whitespace collapsed,/written|,'written’, 32 characters at most): the pause and settings rows, so a row inserted ahead does not rename the ones after it;<base>#<k>otherwise: its position among the siblings with the same base and no identity of their own, internal children included, nodes on their way out skipped;- two siblings with the same identity get
#1,#2… on the later ones.
A test builds every panel twice in one process (two different counters) and pins that the keys agree, and none carries an
@; -
still positional: the settings volume rows (
HBoxContainer#k, holdingButton'-'/Button'+'), the start menu’s header labels and the row groups (Container#1), and any label without a name inside a card. Inserting one of those ahead of another of the same base shifts the keys after it. A button’s key is its words, and a settings button’s words show the setting’s value (Button'Haptics: On',Button'Music 100%'). A copy edit or a changed default makes a new key; the shape rule below matches it to its siblings by size, so it is stale unless it is smaller than all of them; -
a label’s key is not its text: a label’s words change (BUILD 336 to 337) where its identity does not;
-
where a panel is measured in several views (the shop’s tabs, the level-up’s two states), a control that is a different node in each view is recorded per view, as
tab:Drones/<path>orview1/<path>.
The record is large (about 2,400 lines, most of it the shop’s 367 text controls per profile). That is the price of comparing each control with itself; the size histograms of the earlier version could not tell which control moved.
Today’s numbers (Default text size)
Section titled “Today’s numbers (Default text size)”Smallest text in points, with the sizes and controls below the floor in brackets (sizes / controls; floor 11.0 pt, 22.5 pt on the TV):
| Panel | iphone |
iphone_se |
ipad |
tv |
|---|---|---|---|---|
start_menu |
7.15 (4 / 17) | 6.09 (4 / 17) | 12.58 (0) | 19.50 (2 / 11) |
pause_menu |
8.05 (2 / 8) | 7.25 (3 / 14) | 19.56 (0) | 20.45 (1 / 1) |
level_up |
9.53 (1 / 1) | 8.13 (1 / 1) | 16.77 (0) | 18.00 (1 / 1) |
settings |
10.17 (1 / 1) | 9.15 (2 / 14) | 20.96 (0) | 22.50 (0) |
shop |
10.33 (1 / 1) | 8.80 (4 / 255) | 18.17 (0) | 19.50 (1 / 1) |
Smallest tap target in points, with the sizes and controls below 44 pt in brackets:
| Panel | iphone |
iphone_se |
ipad |
tv |
|---|---|---|---|---|
start_menu |
10.33 (2 / 2) | 8.80 (2 / 2) | 18.17 (2 / 2) | none |
pause_menu |
23.00 (2 / 12) | 20.71 (2 / 12) | 55.90 (0) | none |
level_up |
44.00 (0) | 44.00 (0) | 68.48 (0) | none |
settings |
29.85 (1 / 13) | 26.85 (1 / 13) | 61.49 (0) | none |
shop |
44.00 (0) | 44.00 (0) | 55.90 (0) | none |
The font tokens below their floor (the report prints the full table, every font.* token, per profile):
| Profile | Tokens below the floor | At Larger text (x1.3), information only |
|---|---|---|
iphone |
font.2xs 7.9, font.xs 9.5, font.menu.link 9.5, font.menu.desc 10.3 |
font.2xs 10.3 |
iphone_se |
font.2xs 6.8, font.xs 8.1, font.sm 9.5, font.base 10.8, font.menu.kicker 10.2, font.menu.notice 9.5, font.menu.link 8.1, font.menu.desc_big 9.5, font.menu.desc 8.8, font.menu.chip 9.5 |
font.2xs 8.8, font.xs 10.8, font.menu.link 10.8 |
ipad |
none | none |
tv |
font.2xs 15.0, font.xs 18.0, font.menu.notice 21.0, font.menu.link 18.0, font.menu.desc_big 21.0, font.menu.desc 19.5, font.menu.chip 21.0 |
font.2xs 19.5 |
font.sm and font.menu.kicker sit at exactly 22.5 pt on the TV, which is the floor and passes (see Floors).
font.2xs is defined in ui/tokens/defaults.json and used by no code today; it is on the list because the first use
would be unreadable on every phone. The other tokens have 1 to 72 uses each (font.xs 6, font.menu.chip 3, font.sm
51, font.base 72). On the TV, font.menu.link is a token that panel never draws (no notice link there), so it is on the
token list but not in any TV panel’s numbers.
A token’s own size and a panel’s smallest text differ, and that gap is the reason there are two tables. The token table
is the token at the profile’s scale with nothing else applied. A panel then shrinks its own text: on an iphone start
menu the 13-unit font.menu.desc token reads 10.3 pt in the table but 7.94 pt on screen. Seventeen of the start menu’s
twenty text controls are under 11 pt on iphone; only the two large mode rows and the title clear it. On iphone_se
the title is not shown, and 17 of the 19 controls are under 11 pt.
The ratchet
Section titled “The ratchet”tests/ui_physical_size_allow.json (version 4) holds what is described under “What is recorded”. The comparison
(PhysicalSizeFixture.compare) is a pure function of two dictionaries, tested on synthetic data and, through the real
panels, by the mutations below. Each control is compared with its own record, so the sizes other controls happen to
have play no part:
- every token: a drop of more than 0.1 pt is a regression, including a token that is already below its floor
(
font.xs12 to 10 is a 17 percent shrink of a token that was already on the sub-floor list); - a panel’s smallest text or tap target: a drop of more than 0.1 pt;
- every recorded control, text or tap target, above the floor or below it: a drop of more than 0.1 pt is a regression, and the line says when it crossed the floor. Round 2’s blind spot was exactly this: the two big start-menu titles fell from 17.48 to 10.33 pt, onto the size the small-row titles already had, and the per-size record read that as two more controls in an existing style;
- a control the record does not know (new, or its key moved) is a regression when it is below the floor, and stale
(fine, not guarded until the next bless) at or above it. This is a policy change from version 2, which let “more
controls in an existing sub-floor style” pass: a new shop card with text under the floor is more unreadable text, so it
now needs
BH_BLESS_REGRESSIONS=1and a reason, like any other regression. One exception keeps a data-onlybible.jsonedit from failing the gate: a new sub-floor control whose key has the shape of recorded ones (its[id]segments read as[*]and its'words'as'*', soButton[vitality-copy]/ArchetypeTags/Role_tank/Label#0has the shape of every shop card’s role tag, andVBoxContainer#0/Button'Quit run'the shape of every pause row) and is no smaller than the smallest recorded control of that shape is new content in an existing style: stale, not a regression. This is what lets a copy edit pass: every pause and settings button is under the 44 pt tap floor on the phones, so without it renaming “Quit to menu” failed two profiles. A new card or renamed row that is smaller than every recorded one of its shape still fails, and a button that keeps its words is compared with its own record.
| Message | Meaning | What to do |
|---|---|---|
tokens iphone: font.xs is 7.94 pt, recorded 9.53 pt (-1.59) |
A token got smaller, or crossed its floor (and is now below its 11.0 pt floor) |
Fix the size. Only if the shrink is deliberate, bless it with BH_BLESS_REGRESSIONS=1 and say why in the commit |
... min text: 7.15 pt, recorded 12.15 pt (-5.00) |
The smallest text or tap target got smaller by more than 0.1 pt | Same |
start_menu @ iphone text: 2 controls at 10.33 pt, recorded 17.48 pt (-7.15), now below the 11.0 pt floor: '.../Title', ... |
These controls are smaller than their own record. Lines group the controls that moved between the same two sizes and name up to four keys | Same |
... text: 2 controls the record does not know, at 7.94 pt, below the 11.0 pt floor: ... |
New controls (or controls whose key moved) below the floor | Same, or, if a restructure only moved keys, check the sizes did not drop and bless with BH_BLESS_REGRESSIONS=1 |
... below the floor but in a style the record already holds at that size (new content, e.g. one more shop item) ... |
A new card or row of an existing kind, no smaller than the recorded ones | Nothing; bless to guard it |
tokens iphone: NEW token below its floor: font.menu.kicker |
A new token that starts under its floor | Same |
... min tap: no longer found (recorded 23.00 pt) |
A tap target that was measured has gone | Check it was removed on purpose, then bless |
... not in the ratchet file |
A panel or profile nobody recorded (the audit gained one) | Bless it |
... was still moving when measured / found no text at all, the panel did not build / did not show what the audit measures |
The panel did not settle, built empty, or the level-up’s clear-winner offer built no pill | A running tween, a build problem or a scoring change, not a size problem: look at the build, do not bless |
A gain does not fail the gate: a control or token that rose by more than 0.5 pt, a token back above its floor, a
control or token the record has never seen that is at or above its floor, or one it still lists that no longer exists. The test
prints ratchet up: rerun with BH_BLESS=1 to record N better entries (the task’s rule: a bigger UI passes), and the
report counts them as ratchet can be raised. A rise of 0.1 to 0.5 pt is printed as an improvement. The tolerance for a
drop is 0.1 pt. PhysicalSizeFixture.IMPROVEMENT_FAILS = true makes a stale record fail the gate as well, the way
tests/test_ui_literal_ratchet.gd does; it is false because a ratchet that goes red on every fix would block the work
it exists to encourage, and a regression cannot hide in the “stale” bucket now that each control is compared with itself.
A gain nobody blesses is not protected, though: a fix that raised a control can slip back to the recorded size until
someone runs BH_BLESS=1.
BH_BLESS=1 godot --headless --path . -s res://addons/gut/gut_cmdln.gd -gselect=test_ui_physical_size -gexitrewrites the file from the current UI. A bless that would also record a regression is refused unless
BH_BLESS_REGRESSIONS=1 is set too (the same shape as write_literal_baseline.gd refusing a raise without
--allow-raise), so a plain bless records only what is not worse, control by control. The first bless, with no file,
always writes. PhysicalSizeFixture.bless_decision makes the call, and is tested on its own.
A bless over a file it cannot compare is refused, and the file is left as it is: a file that does not parse (merge conflict markers) or one of another version. Writing it anyway would record whatever either side of a merge shrank.
- After a merge conflict in the JSON, take one side and bless again, so the other side’s UI is checked against it:
git checkout --theirs tests/ui_physical_size_allow.json(or--ours), thenBH_BLESS=1 .... A refusal then names the regressions the merged UI has against that side. To see what main holds without touching the tree:git show origin/main:tests/ui_physical_size_allow.json. - For a version bump (a change to how keys are made),
BH_BLESS=1 BH_BLESS_MIGRATE=1 .... Keys cannot be paired across versions, so the migration checks by value: every token by name, and per panel and profile the text sizes and tap sizes, sorted and paired from the largest down. A drop of more than 0.1 pt, or fewer controls, refuses the migration unlessBH_BLESS_REGRESSIONS=1is also set.BH_BLESS_MIGRATE=1over an unreadable file writes unchecked; only use it when neither side of a conflict can be taken. The version 3 to 4 migration found every token, every smallest size and all 40 sorted value lists identical; only 1,970 keys changed.
Review the diff of the JSON: it is the list of what got smaller or larger.
Four mutation tests prove the gate bites, each through the real record and the real panels:
font.xsfrom 12 to 10 units (LiveTokens.apply) fails the token ratchet, and fails the level-upiphoneratchet: the pill goes from 9.53 to 7.94 pt. A plain bless is refused.font.menu.descfrom 13 to 12 shrinks theiphonestart menu’s row descriptions into the size group of the notice link. The number of distinct sizes falls, the smallest text does not move, and the audit still calls it a regression.font.menu.descto 9, andfont.menu.kicker(fine today) to 9: the smallest start-menu text drops, and a token crosses its floor.- Round 2’s repro, made on the built panel: the big Tutorial and Survival titles set to the size
font.menu.rowgives them (what swappingfont.menu.row_bigforfont.menu.rowatui/start_menu.gd’s call site does). They fall 17.48 to 10.33 pt oniphone, no token changes, no control comes or goes, and the smallest text does not move. With the version 2 record every test passed; now it is a regression and a plain bless is refused.
Data edits must not fail it, and the reverse is pinned too, through the real shop built from an edited copy of the
bible.json meta upgrades (PhysicalSizeFixture.shop_defs_override): swapping two Pilot items, or adding a copy of one,
reports no regression; one card’s description forced to 6 px (PhysicalSizeFixture.after_view) is a regression on that
card’s [haste]/Desc key and nowhere else. Swapping a start-menu row and reversing a level-up offer keep every key, and a
pause row inserted ahead leaves Button'QUIT RUN' alone. On the real iphone_se pause menu, renaming “Quit to menu” to
“Quit run” passes, the same button shrunk to 6 px fails on its own key, and renamed plus shrunk fails too.
A fourth runs the other way: font.xs from 12 to 14 lifts the pill to 11.12 pt, nothing got smaller, the record can be
raised, and the gate passes. LiveTokens.reset puts the numbers back after each. Editing font.menu.desc from 13 to 9
in ui/tokens/defaults.json fails all four panel ratchet tests the same way.
What the ratchet does not see: a control at or above its floor whose key changes (a panel restructured, a node renamed or moved, a sibling of the same base inserted before it in one of the still-positional places above) in the same change that shrinks it while it stays at or above the floor. It reads as one control gone and one new, fine control, both stale. Below the floor a moved key is still caught (a control the record does not know, below the floor, is a regression), the token table still sees a token-driven shrink, and a new smallest text or tap target is still caught by the panel minimum. A restructure that moves many keys at once shows up as a long list of stale lines and, for sub-floor controls, regressions: review it as a restructure, then bless.
Run it
Section titled “Run it”| What | Command |
|---|---|
| The tests (about 45 s) | godot --headless --path . -s res://addons/gut/gut_cmdln.gd -gselect=test_ui_physical_size -gexit |
| The report for one profile | godot --headless --path . -s res://tools/ui_physical_size/report.gd -- --profile=iphone_se --out=/abs/dir |
| Through the harness | The device-sim runner, which reads tools/device_sim/registry.d/ui-physical-size.json and runs the report for tv, iphone, iphone_se and ipad |
--profile is iphone, iphone_se, ipad or tv (anything else writes skip); --out defaults to
user://ui_physical_size. A run takes about 6 s. scripts/gate.sh runs the tests as part of the suite.
The report prints the token tables at Default and at Larger, one line per panel, and writes <out>/result.json
(fidelity-result/1):
| Status | Meaning | Exit |
|---|---|---|
pass |
Nothing is below a floor | 0 |
warn |
Something is below a floor. That is today’s normal: the offenders are known and pinned | 0 |
fail |
A token or a control is worse than the ratchet records | 1 |
inconclusive |
A panel was still moving, built empty or did not show what the audit measures (the level-up pill), or the ratchet could not be compared: the file is missing, unreadable (a merge conflict) or a version this tool does not read, or this profile or one of its panels is not in it (the gate test fails those as UNRECORDED). The headline says which | 0 |
skip |
A profile the tool does not know | 0 |
Exit code 2 means result.json could not be written. The headline ends with ratchet can be raised: N, the number of
recorded entries the UI has since beaten (a gain never fails the gate; see above). metrics carries the per-panel
numbers and size histograms, the token lists, the ratchet lines, audit_problems, and, for tv,
tokens_under_apple_caption_information_only. The result is warn while any offender remains, which is today’s normal;
pass would mean nothing is below a floor. The caveat says what the audit reads and leaves out (internal children,
platform-faithful rows, two level-up states, the pause lower bound).
After a merge that adds a class_name (this one does), run godot --headless --path . --import before a headless -s
run: a stale class cache makes it hang (issue #47). The registry gives the report timeout_s: 180 so the harness turns
that into a FAIL row; a normal run is about 6 s.
What it cannot see
Section titled “What it cannot see”- Drawn text. Only
Label,ButtonandRichTextLabeltext is read. Text a script draws itself is not seen: the button glyphs (ui/button_glyph.gd) and the touch-control captions (ui/touch_controls.gd, WARP and DRONE), which are drawn at 9.5 pt by design (9.5 * upt), under the 11 pt floor. - The in-run HUD. It is not a panel and is not built. The weapon and drone dock hints use
font.xs(9.5 pt oniphone); the token table listsfont.xsbut no panel audit reaches them. Other panels (results, ship config, stats sheet) are not audited either. - Larger and Large text in panels. Larger is covered at the token level only, as information. The panels are audited at Default, where the points are smallest.
- The store pause menu. Measured with the Dev rows, so its values are lower bounds (see above).
- Controls a platform does not show are not measured, and a control that depends on something else is measured one
way. The panels are built for the profile’s platform (no TV notice link, no Display row on a phone). A phone with a
pad paired still shows no button prompts: in code
ButtonPromptreturns the style"none"wheneverPlatform.is_touch()is true, so the level-up’s hint row (CHOOSE, SHIP STATS,font.sm) is not drawn on a phone or tablet, and the audit builds none ([Likely]: read from the code, not checked on a device with a paired pad). - Other level-up offers. The two states cover the pill (the smallest text on the panel) and the three card kinds. A card with more change lines, or a longer name, than either offer is not built.
- The level-up STATS pulse. It only runs in a tutorial lesson and is not measured. The focus scale on the card is: the pill is measured unfocused (9.53 pt) and is 9.82 pt while its card has the focus.
- The real device. Points follow the profile’s size and scale, not a measured screen. [Likely] a device in Display Zoom shows everything physically larger; the audit assumes the standard mode. Nothing here models touch slop, glow contrast, stroke weight, ambient light or viewing distance.
Only a person holding the device can settle: whether 10.3 pt neon text with a glow is legible at arm’s length, whether a 23 pt pause button can be hit with a thumb, and whether the TV text reads from the sofa. This audit says which screens to hold up first.
Design calls this surfaces
Section titled “Design calls this surfaces”None of these was changed; each is a number the ratchet now guards, listed roughly by player impact.
- The level-up RECOMMENDED pill is the smallest text on the screen every player sees most: 9.53 pt on
iphone, 8.13 pt oniphone_se, 16.77 pt onipadand 18.00 pt on the TV (4.5 pt under its floor). It isfont.xs, the same token as the weapon and drone dock hints in the HUD. - The
iphonestart menu is mostly under 11 pt (17 of its 20 text controls): small-row descriptions 7.94 pt; the update notice, big-row descriptions and difficulty chip 8.74 pt; small-row titles 10.33 pt; the notice link 7.15 pt.iphone_segoes down to 6.09 pt. The menu scales its own text by the viewport fit factor (0.77 on the three touch profiles, 1.0 on the TV), which is what takes the 13-unitfont.menu.descfrom 10.3 pt in the token table to 7.94 pt on screen. On the TV the descriptions are 19.5 pt. - The pause menu’s twelve buttons are 23.0 to 23.6 pt tall on
iphone(20.7 to 21.2 oniphone_se), about half the 44 pt rows used elsewhere, and seven of its text controls are 10.35 pt (9.32 and 10.36 pt oniphone_se). The Dev rows make this a lower bound. - Settings rows are 29.9 pt tall on
iphone(26.9 oniphone_se) and the hint line is 10.17 pt (9.15 pt oniphone_se, where 13 row labels sit at 10.98 pt, 0.02 pt under the floor), with the rows a phone really has. - On the
iphonestart menu the difficulty chip is a 12.7 pt tall tap target and the notice link 10.3 pt (7.15 pt text); oniphone_sethey are 10.8 and 8.8 pt, on the iPad 22.4 and 18.2 pt. The Apple TV has no link. font.sm, the 51-use workhorse, is 10.9 pt on the lab iPhone16,2 and 9.5 pt oniphone_se, and exactly 22.5 pt on the TV, 0.5 pt under Apple’s Caption 2 [Likely]. Auditing the TV at 23 pt instead would putfont.smandfont.menu.kickeron the TV list; that call is Chris’s.- On the TV,
font.xs(the level-up pill, 18 pt) and the start-menu description tokens (19.5 to 21 pt) are against a 22.5 pt floor. font.2xsis unused. Deleting it removes the worst entry on every phone.