feat: paragraph-scoped synchronous measurement with candidate adoption (11.17) - #75
Draft
thejustinwalsh wants to merge 10 commits into
Draft
feat: paragraph-scoped synchronous measurement with candidate adoption (11.17)#75thejustinwalsh wants to merge 10 commits into
thejustinwalsh wants to merge 10 commits into
Conversation
…ssion The measure-path cost anatomy, the layer-1 entry contract (paragraph- scoped synchronous measurement into the inactive result slot without publication, revision burn, or fence side effects), the retained- transaction and adoption layers, and the proof obligations. The raw- Wasm regression is in place and red on the missing measureParagraph export; the text.md digest rides the extended test harness.
…vidence The workerRasterKinds routing (0d93370) changed the ABI-embedding JS bundles and the runtime-bake host/Worker without refreshing the reviewed package-size evidence. Regenerate it at that baseline so the next wasm change carries only its own delta.
…g (11.17 layer 1) pmndrs_text_engine_measure_paragraph reuses the update request layout with the queried paragraph as an ABI argument. It validates, speculatively prepares, and emits measurement records for that paragraph only, then terminates leave-committed: no gather, no plan compile, no A/B flip, no publication-generation bump, no revision burn, and no renderer-fence acknowledgment, so the next real frame proceeds from pre-measure revisions without a checkpoint rebuild. Query results ride the inactive result slot under a host lease; the plan validator never sees them. The shared per-paragraph record emission moves to append_paragraph_measurement so update and measure paths stay one implementation. Shaper wasm grows 5,806 raw / 2,869 Brotli bytes; the ABI functions table adds 62 raw bytes per embedding JS bundle.
…ial queries (11.17 layer 2) Measure queries no longer abort their pending state at exit. The session keeps one SpeculativeTransaction — committed revision, lifecycle fingerprint, generation, and linear identity high-water marks — and each paragraph records the text/style input fingerprints its pending prefix was prepared from. A follow-up query whose fingerprints match re-runs only geometry, flow, and positioning over the retained prefix through the extracted prepare_geometry_and_layout tail; identities extend linearly instead of rolling back between queries. Any mismatch rebuilds cold with results identical to fresh preparation (generation records the rebuild), and an ordinary frame drops the transaction leave-committed at entry, so committed state never observes a query. Mutation-checked Rust regressions pin retention, extension, cold rebuild, and candidate-paragraph reclamation; the raw-Wasm regression extends to sequential queries under a real shaper. Shaper wasm grows 2,148 raw / 929 Brotli bytes.
… (11.17 layer 3) A frame whose lifecycle input matches the retained transaction continues its identity counters from the reserved high-water marks and, per paragraph, fingerprint-compares its taken text/style/geometry batches against the speculative pending state: a full match skips preparation entirely and reports positioned_changed by the same formula prepare would have used; a prefix match re-runs only the geometry/flow/positioning tail; anything else prepares cold through the unchanged stage-abort discipline. The lifecycle fingerprint now folds in the semantic implicit-paragraph id so a query and a frame addressing the same structure fingerprint equally. The raw-Wasm regression proves the contract end to end: identities reserve linearly across two sequential queries and the committing frame reports exactly the stable glyph ids the adopted query promised. Update-lane A/B (22k glyphs, interleaved) shows no hot-path delta; frames without a transaction pay only an Option check. Shaper wasm grows 1,459 raw / 216 Brotli bytes.
…uery (11.17 layer 4) The core host gains session.measureParagraph beside update: it stages the same request layout, calls the synchronous engine measure, and decodes the result from the inactive slot under the host lease with revisions, publication generation, and the renderer fence untouched. Text.measureLayout uses it when the only pending work is a geometry change on the measured text and every other paragraph is quiescent, building exactly the upsert, constraint, and region bytes the next frame will send so the engine's retained transaction answers the query and the committing frame adopts it. A Three-level regression drives four constraint changes through measureLayout and proves zero full engine updates, an unchanged publication generation, and a non-checkpoint committing frame; the uikit integration document replaces its planned-cost note with the landed fast-path guidance and keeps the round-up rule intact.
…e update (11.17 layer 5) The rust-layout benchmark gains a measure-query lane that answers the column-resize lane's exact alternating widths through pmndrs_text_engine_measure_paragraph. At the 22,000-glyph corpus it measures 1.815 ms median / 1.930 ms p95 / 3.0% RSD with zero publication bytes, beside the full update's 2.996 ms median / 4.483 ms p95 / 21.7% RSD in the same run — the first width-change lane under the 4 ms p95 objective, recorded as an authenticated fixture. Roadmap 11.17 closes and D-253 records the retained speculative transaction design and its adoption contract.
size-limit report 📦
|
…om a clean tree The static CI gates have been red since the columns slice: oxfmt rejects trailing whitespace the editor later stripped locally, the measure-entry test helper shadowed its inner geometry box under no-shadow, and every source digest generated since was computed over a worktree whose bytes differed from the committed tree by exactly that unstaged whitespace — CI hashes the commit, the generator hashes the disk. Formatting is applied, the helper renames its outer box, and the digests are re-derived from a tree that matches what CI checks out.
…e tier The measure tier grew three artifacts past ceilings that were priced before it: the renderer-neutral core by +2,481 raw / +1,258 minified / +211 gzip (synchronous measure entry, retained speculative transaction, host fast path) on top of +1,786 raw / +919 minified of external-raster routing growth whose evidence refold rides this branch; the shaper wasm by +9,413 raw / +5,028 gzip / +4,014 Brotli (speculative measure transactions, candidate adoption, the paragraph query path); and the Three adapter by +7,096 raw / +3,604 minified (frame adoption and the measureLayout fast path). Ceilings move to the measured actuals with the usual tight headroom and the established cross-host compression margin for the wasm; the CI gate measured the JS bundles byte-identical across linux-x64 and darwin. The integer-units slices above ride within the remaining headroom (+360 raw at their tip, wasm net -4,228).
The gate's fresh build at the routing branch measured three-runtime at 370,521 raw / 241,995 minified, so +1,786 raw / +914 minified of the Three adapter growth belongs to the external-raster routing, not the 11.17 tier; the tier's own share is +5,310 raw / +2,690 minified. The ceilings are unchanged.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes roadmap 11.17: paragraph-scoped synchronous measurement as one retained speculative session transaction — no third result buffer, no revision burn, no publication flips — with next-frame candidate adoption. Recorded as decision D-253.
What a measurement paid before
A measure-only update ran the complete session pipeline: per-paragraph preparation across every paragraph, full policy gather and render-plan compilation, publication packing, the A/B result flip with a generation bump, and a revision advance the renderer never consumed — which forced the next real frame into a checkpoint rebuild. That last hazard was the largest hidden cost for reactive integrations (uikit-style measure-per-frame resize).
The layers
pmndrs_text_engine_measure_paragraph(session, request, len, paragraphId)reuses the update request layout, prepares the queried paragraph only, and copies the header + semantic table into the inactive result slot under a host lease: no gather, no plan, no publish, no fence acknowledgment.session.measureParagraph;Text.measureLayout()routes geometry-only queries through it. A Three-level regression drives four constraint changes: zero full engine updates, unchanged publication generation, non-checkpoint committing frame. The uikit doc replaces its planned-cost note with landed guidance (round-up rule intact).measure-querybenchmark lane besidecolumn-resize(same alternating widths, 22,000-glyph corpus): 1.815 ms median / 1.930 ms p95 / 3.0 % RSD with zero publication bytes, vs the full update's 2.996 ms / 4.483 ms / 21.7 % — the first width-change lane under the 4 ms p95 objective.Safety
Mutation-checked Rust regressions pin retention, extension, cold rebuild (results identical to fresh preparation), and candidate-paragraph reclamation; an unadopted transaction drops leave-committed, so committed state never observes a query. Interleaved lane A/Bs show no update hot-path delta — frames without a transaction pay one
Optioncheck. Net shaper wasm across the stack: +9,413 raw bytes before the follow-up stack's registry flattening banks −9,433.