From c4b3ef53173a210706e4dd4308398b514f16931f Mon Sep 17 00:00:00 2001 From: tannevaled Date: Sun, 11 Oct 2026 18:14:05 +0200 Subject: [PATCH] docs: document SVG rasterisation cost as a Known gap (confirmed on two pages) Round 169's own follow-up CPU profiling of foundation.zurb.com (one of the slow pages from the 500-page bench run, confirmed round 168's font-face fix didn't apply here) pinned its ~30s render time to SVG rasterisation: 84% of CPU samples inside rasterx's Draw path, mostly per-pixel gradient-color sampling. DisableJS=true drops the SAME page to ~6.2s not because JS execution itself is CPU-slow, but because the no-JS render has less SVG content to rasterise than the JS-settled page actually ends up with. This independently confirms an earlier, never-written-down finding from investigating docs.docker.com's own slow render (a decorative dot-pattern.svg background, cost scaling with output area) -- the same architectural question, now with two real-world triggers instead of one. Not fixed: both live options (cap output resolution, budget rasterisation time) change rendering output, a decision this session has twice deferred rather than pick unilaterally. Also tightens round 168's own "JS-settle-related" phrasing for the same page now that the actual cause is pinned down precisely. Co-Authored-By: Claude Sonnet 5 --- FIDELITY.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/FIDELITY.md b/FIDELITY.md index 510f0c7..a863a71 100644 --- a/FIDELITY.md +++ b/FIDELITY.md @@ -36,7 +36,7 @@ The 500-page run relaunched at the end of round 167 (both crash fixes in place) - **Fixed**: `LoadFontFaces`'s per-slot loop now fires every DISTINCT candidate concurrently (deduped by a new `srcsKey` — the same src list parsed twice, e.g. the same stylesheet named twice, still fetches once) and picks the lowest-declaration-order success after they all finish, instead of walking the list waiting for each failure in turn. `e.Client`'s existing per-host semaphore (`maxPerHostConcurrency`, round 57/58, left at 2 — the round-152/153-measured value, re-confirmed still right for Wikimedia, not re-opened by this round) still bounds real network concurrency, so this is a bounded, not unlimited, speedup: candidates now batch through that cap rather than waiting out every predecessor's own fetch one at a time. - **Test**: `TestLoadFontFacesTriesSlotCandidatesConcurrentlyNotSequentially` — a local server with 5 slow-then-500 candidates plus 1 fast-success candidate declared LAST in one slot, deliberately shaped like the live cs.cmu.edu case. Stash-verified: ~460ms with the fix (bounded by the per-host cap, confirmed deterministic across 3 runs) vs ~760ms on the unfixed code (sequential), a measured, reproducible, and reliably-separated difference, not assumed from the live site alone (which could not be cleanly re-measured after this round's own repeated testing had, by its own escalating response times, visibly triggered the origin's own rate-limiting/anti-scraping defenses — an honest limitation of live-site verification for this specific fix, mitigated by the deterministic local-server test instead). - **Coverage**: `fontfaces.go`'s `LoadFontFaces` and the new `srcsKey` both land at 100%; the top-level `engine` package moves from a pre-existing 96.1% baseline (confirmed via `git stash`, unrelated gaps) to 96.2% — no regression. -- **Not fixed this round, flagged honestly**: `unicode-range` itself remains unmodelled (this fix removes the serialization cost of the collision, not the collision — all five rules still compete for one slot, and whichever succeeds first wins for the WHOLE family+weight regardless of which glyphs it actually covers; a page relying on Font Awesome's REAL icon-subset behavior could still render the wrong glyphs, just not slowly). `cs.cmu.edu` itself could not be cleanly re-measured after this fix landed — the live-site contamination described above persisted (still ~45s on a careful single re-check well after the fix merged), consistent with the origin's own throttling outlasting this session rather than the fix being ineffective (the deterministic local test is the real evidence it works). Checked ONE other slow page from the same bench run, `foundation.zurb.com` (24.8s in the run, not yet touched by any of this round's live testing, so uncontaminated): still ~40s after the fix, and — checked directly rather than assumed — `DisableJS=true` dropped it to ~6.2s on the SAME page, meaning its slowness is JS-settle-related, NOT the font-face bug this round fixed. **Correcting the round's own earlier guess**: `cs.cmu.edu`'s shape is not a shared cause across the other ~9 slow pages; at least `foundation.zurb.com` has a distinct, unrelated, JS-driven cause, still open for a future round. +- **Not fixed this round, flagged honestly**: `unicode-range` itself remains unmodelled (this fix removes the serialization cost of the collision, not the collision — all five rules still compete for one slot, and whichever succeeds first wins for the WHOLE family+weight regardless of which glyphs it actually covers; a page relying on Font Awesome's REAL icon-subset behavior could still render the wrong glyphs, just not slowly). `cs.cmu.edu` itself could not be cleanly re-measured after this fix landed — the live-site contamination described above persisted (still ~45s on a careful single re-check well after the fix merged), consistent with the origin's own throttling outlasting this session rather than the fix being ineffective (the deterministic local test is the real evidence it works). Checked ONE other slow page from the same bench run, `foundation.zurb.com` (24.8s in the run, not yet touched by any of this round's live testing, so uncontaminated): still ~40s after the fix, and — checked directly rather than assumed — `DisableJS=true` dropped it to ~6.2s on the SAME page. **Correcting the round's own earlier guess**: `cs.cmu.edu`'s shape is not a shared cause across the other ~9 slow pages. `foundation.zurb.com`'s own cause (round 169's follow-up CPU profiling pinned it precisely: SVG rasterisation of content the JS-settled DOM reveals that a no-JS render never has to pay for — see this file's "Known gaps" section) is distinct from both the font-face bug this round fixed AND from JS execution being slow in CPU terms. ## 2026-10-09 (round 167) — the interrupted 500-page bench run (round 166's own queued work) found a second real crash: a lone CSS Grid item using `grid-row:1 / -1` with no explicit row template panicked on an empty row-offset slice (engine#266, patch) @@ -5657,6 +5657,7 @@ Restated against what was actually verified that day: fetch or decode still falls back to the bundled three, unchanged. - **`sticky` ≈ `relative`, approximate z-index stacking** — the deliberate simplifications from Phase 1.8, unchanged. +- **SVG rasterisation cost scales with output pixel area and is unbounded — a large, gradient-filled vector (a decorative background pattern, a hero illustration) can dominate a whole page's render time, confirmed independently on TWO pages.** First found investigating `docs.docker.com/reference/compose-file/interpolation/`'s own slow render: a decorative background SVG (`dot-pattern.svg`, ~2657 tiny `` elements, 1158×598 intrinsic size) cost 5.7s to rasterise at its full on-page output size, 1.3s at half, 0.3s at quarter — a roughly quadratic (area-scaling) cost, as expected for a per-pixel rasteriser, with no ceiling on how large that area can be. Round 168/169's own bench-corpus profiling independently confirmed the SAME cost category on a completely different page, `foundation.zurb.com` (get.foundation): CPU profiling showed 84% of its ~30s render time inside `rasterx.ScannerGV.Draw` → `golang.org/x/image/vector.Rasterizer`, dominated by `rasterx.ColorFuncImage.At` (per-pixel gradient-color sampling) — not font-loading (round 168's own fix, confirmed not applicable here) and not JS-execution CPU cost itself (`DisableJS=true` drops this SAME page to ~6.2s, but because the JS-settled DOM then has LESS rasterisable SVG content, not because JS execution is slow in CPU terms — a no-JS render is simply missing the content the real page actually ends up with). Not fixed: capping SVG output resolution or budgeting rasterisation time per SVG are the two live options, and BOTH change rendering output (a capped/time-boxed SVG renders at lower fidelity or not at all) rather than only internal performance — a deliberate decision this session has twice deferred rather than pick unilaterally, now with two independent real-world triggers rather than one. ## Reproduce