enabled: true touches: [crates/dom/src/px64.rs, crates/dom/src/lib.rs, crates/logic/src/lib.rs, crates/layout/src/fragment.rs, crates/layout/src/bin/fragment_check.rs, roadmap.md] depends-on: [] #
Device-pixel rounding: bound the width chain's compounding drift #
roadmap.md → "Proof of concept" claim 1, measured on the assembled engine once
keep-winners landed its winners: 216/227, every category at 100% except
NestingDepth 9/20, "whose eleven losses are the device-pixel rounding gap below (worst
node delta 0.078 px against the 1/60 px tolerance)." crates/layout/src/fragment.rs's own
module doc already names the cause: it is "hand-directed integration scaffolding," and
roadmap.md → "Substrate contract" says plainly that "Phase 0/1 scaffolding does not yet
meet this contract itself: crates/layout/src/fragment.rs computes in physical f64 with
no computed/used distinction." roadmap.md → "Geometry mapping" names the fix: "fixed-point
arithmetic internally for determinism is the target for generated layout... the only 1/64 px
fixed-point type in the workspace so far is crates/logic/src/px64.rs, scoped to the
margin-collapse rules crate." docs/firefox-app-units.md → "The mechanism, now instrumented
directly" reproduces the exact recipe shape (recipe_nesting_depth, twelve levels of
width: 83.333%) directly: this engine's own unrounded chain reaches
89.72098558887357px against Chromium's and WebKit's shared, device-pixel-rounded
89.671875px — 5739/64 exactly.
That last number is the reason this spec rounds to 1/64 px, not 1/60 px, despite the
queue line naming "the 1/60 px fixed-point rounding": 1/60 px is dump.rs's
TOLERANCE_PX, the harness's own pass/fail comparison threshold, sized to Gecko's coarser
app-unit grid because Gecko is the outlier the three-engine vote already excludes on this
exact case shape (docs/firefox-app-units.md → "The mechanism"). It is not a target for
this engine's own arithmetic. roadmap.md → "Geometry mapping" names 1/64 px — Blink's and
WebKit's LayoutUnit grid, the same grid px64.rs already implements and the grid the
majority ground truth for nesting-depth-12 lands on — as what generated layout should
round to internally. This spec rounds to that grid; TOLERANCE_PX and the comparison logic
in dump.rs/compare.rs are untouched.
Scope. In: rounding every used-value f64 layout_box (crates/layout/src/fragment.rs)
computes to 1/64 px, through dom::px64::{to_px64_truncating, from_px64} (re-exported at
logic::px64 so every existing call site keeps resolving), at the point each value is
chosen — before it becomes a fragment's own size/offset or the basis
(Available::inline_size/height_basis) a child's percentage resolves against — so the
twelve-deep compounding recipe_nesting_depth (crates/harness/src/cases.rs) exercises
stays bounded the way Blink's/WebKit's own per-operation quantization does. Reuses
px64.rs's existing Px64/to_px64_truncating/from_px64 rather than inventing a second
fixed-point type. Out: this is not a generated seam. No model batch, no candidate-admission loop, no
new plain-data resolver type, no corpus of its own — hand-written engineering directly in
the substrate (fragment.rs), unlike every other item currently in the queue
(block-height-equation, overflow-scrollbars, inline-atomic, tables-fixed-layout are
each either extracted to a plain-data seam or run model-first). None of crates/generation,
corpus/*-ground-truth/, or a provenance record applies here — a future reader must not
assume the batch-loop machinery this repo uses for every other queued item is in play. Also
out: text metrics, device-pixel-ratio snapping (already pinned to 1 harness-wide, "Oracles"
→ confounds), and any change to TOLERANCE_PX or compare.rs's comparison logic.
Build:
crates/logic/src/px64.rsmoves tocrates/dom/src/px64.rs, withpub use dom::px64;added atcrates/logic/src/lib.rsso every existingcrate::px64::andlogic::px64::call site still resolves unchanged. The move is necessary, not cosmetic: the workspace's crate edges runharness <- dom <- layout <- logic <- generation, andlogicalready depends onlayout(logic::facts,rules,vote_checkand two binaries readlayout::boxtree::BoxTree), so alayout -> logicedge closes a cycle Cargo refuses.domis the lowest crate bothlayoutandlogiccan reach, and it already owns computed CSS values, which is where a CSS-pixel unit belongs.layout/Cargo.tomlneeds no change —layoutalready depends ondom. Update the module doc's claim "No such type exists elsewhere in the workspace yet... this is a new type scoped to this crate, not a reused one" oncefragment.rsdepends on it below — that sentence goes stale the moment it's reused.- Of
to_px64/to_px64_truncating, useto_px64_truncating, notto_px64.crates/harness/src/cases.rs's twelve-levelrecipe_nesting_depthchain lands on89.671875px(5739/64exactly) only with truncation toward zero; round-to-nearest measures worse than not quantizing at all (delta moves from 0.049px to 0.047px instead of to 0.000px), because it hands each nesting level a basis larger than any browser ever computes, the same way aLayoutUnitis built from a float. Measure both if this is ever revisited — the numbers are the argument, not the name of the function. crates/layout/src/fragment.rs,layout_box()— roundcontent_width(both the float branch's shrink-to-fit result and thewidth_resolverbranch'sused_width) immediately after it's chosen, before it feedschild_available(Available { inline_size: content_width, .. }, a few lines below) — this is the value every one of the twelve nesting levels resolves its own percentage width against, and the one field that actually closesNestingDepth. Roundborder_box_width, the height basisdefinite_content_heightproduces, and the fragment's ownoffset_x/offset_yat assembly the same way, so every used value the substrate contract's "quantizes at every operation" language calls for is actually quantized, not only the one field this category's recipe happens to exercise.crates/layout/src/bin/fragment_check.rs—CategoryTallytracks onlypassed/totaltoday. Add aworst_abs_deltafield, read fromCaseComparison::Compared(r) .worst_abs_delta(harness::compare::CaseReportalready carries it, unused by this binary), and print it beside each category's count. This is what turns "worst delta 0.078 px" into a number this track can re-measure directly rather than one inspected by hand, the waydocs/firefox-app-units.md's mechanism section was.
Gates: cargo test --workspace passes. fragment_check --random (cargo run --release --bin fragment_check -p layout -- --random) still reports at least 216/227, with every
category currently at 100% staying at 100% — rounding to 1/64 px must not introduce a new
mismatch anywhere outside nesting. NestingDepth's own row in that same run must improve
above 9/20, and the worst_abs_delta this step adds to the binary's output for
NestingDepth must drop under the current 0.078 px — the recipe's twelve levels are exact
percentages of exact pixel-pool literals (cases.rs's own src.pct/px pools), so an
engine quantizing at every level the same grid Blink and WebKit do has no arithmetic reason
to land anywhere but exactly on their shared value, i.e. as close to 0.0 as floating-point
round-trip noise allows.
Afterwards, update roadmap.md → "Proof of concept" claim 1 with the new
fragment_check --random count and NestingDepth row, the way specs/floats-bfc.md's own
closing paragraph updates claim 2's held-out numbers — and record the 1/60-vs-1/64
correction above in that same update, so the next reader of the queue line does not repeat
it.