diff --git a/context/kits/cavekit-gpu-rendering.md b/context/kits/cavekit-gpu-rendering.md index c93583f..cb1cdd1 100644 --- a/context/kits/cavekit-gpu-rendering.md +++ b/context/kits/cavekit-gpu-rendering.md @@ -80,6 +80,7 @@ Hacker News 36-37, desktop Wikipedia 13-14 (software 9-12.4). **Dependencies:** R2 ### R6: Zoom keeps up (the owner's finger test, 2026-10-18) +*(2026-10-25, T-541: the displayport margin at rest is a height each way (was half); a fling adds up to a height ahead from the card's scroll speed (BrowserPageGoanna's velocity → GoannaRenderPage::SetGpuLead). Measured: about:personas' flicks no longer land on grey; desktop Wikipedia unchanged (80 vs 74 express frames over the same flicks).)* **Description:** A pinch or a double-tap zoom shows the page at the new zoom promptly and never leaves part of the card grey. **Acceptance Criteria:** - [x] A scripted pinch in to about 1.7 and back out to the fit zoom (and a double-tap zoom) on desktop en.wikipedia.org and on `jihad-long-static.html` is reproduced on the bench with the frame timeline logged *(2026-10-18: the card's own gesture calls through its test hook, `g` pinches and `t` double taps; Wikipedia 0.69 → 1.23 → 2.20 → 1.51 → 1.03, double tap 1.26 and back to 0.69; the test page 0.79 → 1.41 → 2.51 → 1.72 → 1.18 and its double taps; each zoom's "the first frame at the new zoom is out" line)* diff --git a/context/plans/build-site.md b/context/plans/build-site.md index 5accf51..fc0dd0b 100644 --- a/context/plans/build-site.md +++ b/context/plans/build-site.md @@ -547,7 +547,7 @@ Commits are SSH-signed since 2026-10-21 and wait on the owner's passphrase (memo | T-538 | A field inside a frame by finger: the daemon follows a tapped frame into its document for the edit target (jihadIsInsideFrameOf) and re-raises the keyboard on a tap on the frame of the current target; the followed layout width (T-499) survives the keyboard's height-only resize, so a fit-zoomed page neither re-flows nor re-zooms around the keyboard | cavekit-chrome-look.md, cavekit-input-bridging.md | R4, R2 | T-535 | yes | M | DONE 2026-10-25 | | T-539 | The slow-script dialog by finger: the card's confirm names the engine's buttons in its text (OK = Continue, Cancel = Stop script); Cancel stops the script and the card stays usable | cavekit-modern-web.md | R2 | T-535 | yes | S | DONE 2026-10-25 | | T-540 | A dialog whose card closes meanwhile holds the daemon's main thread for the whole 60 s reply wait (the YAP send fails with EPIPE but connected() stays true): make the wait end when the send failed or the client is gone | cavekit-ui-shell.md | R3 | T-539 | yes | S | DONE 2026-10-25 | -| T-541 | A fast flick beyond the displayport shows the uncovered rows grey/white for up to a second on an image-heavy page (about:personas by finger, 2026-10-25); the plain long page does not — grow the displayport in a fling's direction by its distance, or compose a wider margin while a fling runs | cavekit-gpu-rendering.md | R6, R12 | T-527 | yes | M | pending | +| T-541 | A fast flick beyond the displayport showed the uncovered rows grey/white for up to a second on an image-heavy page (about:personas by finger, 2026-10-25). Fixed: the displayport at rest is a height each way (JIHAD_GPU_DP_Y 0.25 → 0.75; the A/B on desktop Wikipedia cost nothing — 80 vs 74 express frames over the same five flicks, the main lane 5-8 fps either way) and a fling adds a lead of up to one height in its direction from the card's scroll speed; the same flicks on about:personas land on painted rows | cavekit-gpu-rendering.md | R6, R12 | T-527 | yes | M | DONE 2026-10-25 | | T-542 | A Persona installed from a .xpi file showed "Author unknown" and seemed to lack its card picture (Carbon Light from render/goanna/test/personas, by finger 2026-10-25) — NO DEFECT: the fixture's manifest has no author, and its header.png is a flat light grey (3000×200, grey 180), which IS what the card shows; the header file landed in the profile and the card uses it directly (a small header needs no thumb) | cavekit-personas-ui.md | R6, R8 | T-535 | yes | S | DONE 2026-10-25 (no defect) | | T-521 | The engine's review fixes folded: the signed engine commit (patch 0058), `patch-queue.sh --add`, `update-engine.sh --pins`, ENGINE-SOURCE's row, `check-engine-pins.sh` and `patch-queue.sh --check` agree; the Jihad-Browser commits prepared as one chain for the owner's passphrase | cavekit-gpu-rendering.md | R11 | T-518, T-519, T-520, T-522 | no | S | DONE 2026-10-24 13:49 (engine `7a872e8f`, Jihad-Browser `506c4114`, `3f4e1a27`, `318869f2`; unsigned by the owner's rule — the Gitea key is Gitea's only, `commit.gpgsign false` per repo; the pins check all agree, 58 patches reproduce the pin; the push is the owner's: `git -C third_party/uxp push fork jihad/uxp-RB_20261006`, `git push origin main`. The first chain run's story: the chain `.cavekit/handoff-2026-10-24/t521-chain.sh` — the engine commit, `patch-queue.sh --add`, `update-engine.sh --pins`, the checks, then commits A/B/C with the shared files split by hunk — stopped at its first prompt with "incorrect passphrase supplied", nothing staged or committed; run it again, four prompts, or with the key in an agent, then push both repos) | diff --git a/docs/FINGER-TESTS.md b/docs/FINGER-TESTS.md index 990b372..b426354 100644 --- a/docs/FINGER-TESTS.md +++ b/docs/FINGER-TESTS.md @@ -81,8 +81,8 @@ Run each item in all three variants (Enyo, Mochi, Mojo) unless it says otherwise (mojo-ui R6) *(2026-10-24: one tap selects the whole address, "webos" typed on the keyboard replaces it, Enter opens lite.duckduckgo.com/lite/?q=webos; the results were on screen at the 11 s shot and not at 3 s — the "before the whole page" half needs a finer clock than a 2 s screenshot)* ## Personas and add-ons -- [~] about:personas: no card flashes while the page is open or scrolled (Carbon Light 2.0 was the - example); cards show their pictures sharply. (personas-ui R9) *(2026-10-25, Enyo by finger: at rest and after a slow scroll the cards and their pictures are steady and sharp (four shots); no card blinked. What DOES show: a fast flick far down an image-heavy page leaves the newly uncovered rows GREY (a whole grey card once, the lower 40% once, a white band at the top after a flick up) for up to a second until the main thread paints them — the GPU path's displayport (0.5 heights) does not reach a flick's landing place; the plain long page shows none of it. T-541 (the displayport should grow in a fling's direction). Also seen: Carbon Light reinstalled from its .xpi reads "Author unknown" — the fixture's manifest has no author, and its picture is a flat light grey, which is what its card shows (T-542, no defect).)* +- [x] about:personas: no card flashes while the page is open or scrolled (Carbon Light 2.0 was the + example); cards show their pictures sharply. (personas-ui R9) *(2026-10-25, Enyo by finger: at rest and after a slow scroll the cards and their pictures are steady and sharp (four shots); no card blinked. What DOES show: a fast flick far down an image-heavy page leaves the newly uncovered rows GREY (a whole grey card once, the lower 40% once, a white band at the top after a flick up) for up to a second until the main thread paints them — the GPU path's displayport (0.5 heights) does not reach a flick's landing place; the plain long page shows none of it. T-541, FIXED the same day: the displayport is a height each way at rest (half before) and a fling adds a lead of up to a height in its direction; the same flicks then land on painted rows (three shots at 0 s, no grey, no white band), and desktop Wikipedia's flicks cost the same. Also seen: Carbon Light reinstalled from its .xpi reads "Author unknown" — the fixture's manifest has no author, and its picture is a flat light grey, which is what its card shows (T-542, no defect).)* - [x] Right after installing a Persona with a large picture, tap its Remove once ("Remove it?") and wait a few seconds: the question stays until answered while its card picture is made. (personas-ui R9, round 22) *(2026-10-25, Enyo by finger: Autumn Forest 2.0 (a photo header) worn from the site section of about:personas (the question answered OK), then — two flicks to the top and at once — its Remove: "Remove it?" showed at 1 s and still at 4 s, the card's picture already made beside it; the second tap removed it (theme null, the header file removed); the Default look back.)* diff --git a/render/goanna/BrowserPageGoanna.cpp b/render/goanna/BrowserPageGoanna.cpp index de28e89..e4f3c14 100644 --- a/render/goanna/BrowserPageGoanna.cpp +++ b/render/goanna/BrowserPageGoanna.cpp @@ -1282,6 +1282,29 @@ void BrowserPageGoanna::setScrollPosition(int x, int y) { // T-502: the compositor applies the card's scroll (CSS px = zoomed px over the zoom); // the engine is scrolled only to re-anchor the displayport (gpuScrollTo). double zg = (mZoom >= 0.05 && mZoom <= 20.0) ? mZoom : 1.0; + // T-541: the scroll's speed from the card's stream of positions, smoothed; the displayport + // gets a lead in the direction of travel of up to 1 height (what ~0.5 s of this speed + // covers), 0 again once the stream stops (maybePaint). + { + const int64_t now = mLastScrollMs; + const int64_t dt = mVelMs ? now - mVelMs : 0; + if (dt > 0 && dt < 250) { + const double v = (double)(y - mVelY) / (double)dt; // card px per ms + mScrollVelY = 0.5 * mScrollVelY + 0.5 * v; + } else if (dt >= 250) { + mScrollVelY = 0.0; + } + mVelMs = now; mVelY = y; + const double hpx = mLastWinH > 0 ? (double)mLastWinH : (mPage->Height() > 0 ? (double)mPage->Height() : 1024.0); + double lead = fabs(mScrollVelY) * 500.0 / hpx; + if (lead > 1.0) lead = 1.0; // on top of the height at rest: 2 heights ahead while flinging + if (lead < 0.15) lead = 0.0; + const float up = (float)(mScrollVelY < 0 ? lead : 0.0), down = (float)(mScrollVelY > 0 ? lead : 0.0); + if (fabs(up - mGpuLeadUp) > 0.25 || fabs(down - mGpuLeadDown) > 0.25) { + mGpuLeadUp = up; mGpuLeadDown = down; mPage->SetGpuLead(up, down); + fprintf(stderr, "[jihad-bs] fling lead: up %.2f down %.2f heights (%.2f card px/ms)\n", up, down, mScrollVelY); + } + } mAdapterScrollX = x; mAdapterScrollY = y; mPage->SetRenderPan(0.0, 0.0); gpuScrollTo(x, y, zg); @@ -3549,6 +3572,11 @@ void BrowserPageGoanna::pump(int msBudget, bool drainOnly) { // not returned the buffer, and frames actually painted. void BrowserPageGoanna::maybePaint() { if (!mGpuJobs.empty()) mGpuTrace[6]++; + // T-541: the fling lead goes once the card has stopped streaming positions. + if ((mGpuLeadUp != 0.0f || mGpuLeadDown != 0.0f) && mPage && jihadNowMs() - mLastScrollMs > 400) { + mGpuLeadUp = mGpuLeadDown = 0.0f; mScrollVelY = 0.0; mVelMs = 0; mPage->SetGpuLead(0.0f, 0.0f); + fprintf(stderr, "[jihad-bs] fling lead: off\n"); + } if (jihad_offscreen_late_writes) { // T-513 (review): a GPU read given up past its bound came back and wrote into a slot the // software path may have painted (and the card may show): every slot painted whole again diff --git a/render/goanna/BrowserPageGoanna.h b/render/goanna/BrowserPageGoanna.h index ea5c3da..e71d91e 100644 --- a/render/goanna/BrowserPageGoanna.h +++ b/render/goanna/BrowserPageGoanna.h @@ -403,6 +403,8 @@ private: bool mGpuAnchorValid = false; double mGpuDpShiftX = 0.0, mGpuDpShiftY = 0.0; // T-524: the shift the displayport was set with double mGpuDpZoom = 0.0; // T-524: and the zoom + int64_t mVelMs = 0; int mVelY = 0; double mScrollVelY = 0.0; // T-541: the card's scroll speed (card px/ms) + float mGpuLeadUp = 0.0f, mGpuLeadDown = 0.0f; // T-541: the lead last handed to the page bool mGpuCompositorScroll = false; // the compositor applies the card's scroll (frames at the card's y) // T-502 round 112: the compositor-side scroll is engaged by a card-driven pan only. A // setScrollPosition that follows a position this daemon just reported (msgScrolledTo) is the diff --git a/render/goanna/GoannaRenderPage.cpp b/render/goanna/GoannaRenderPage.cpp index 09f8f39..69024ed 100644 --- a/render/goanna/GoannaRenderPage.cpp +++ b/render/goanna/GoannaRenderPage.cpp @@ -4701,7 +4701,12 @@ bool GoannaRenderPage::SetGpuViewport(double zoom) { // is the anchor distance (JIHAD_GPU_ANCHOR, 0.25 heights) plus JIHAD_GPU_DP_Y (0.25 heights); // sideways JIHAD_GPU_DP_X widths (0, half a width when zoomed in); layout adds what is shown // beyond the scroll port (jihad_layout_set_displayport) - static const float sDpY = getenv("JIHAD_GPU_DP_Y") ? (float)atof(getenv("JIHAD_GPU_DP_Y")) : 0.25f; + // T-541: a height of displayport each way at rest (the anchor's 0.25 + 0.75). With 0.25 (half a + // height) a fast flick on an image-heavy page landed past the painted rows and showed grey for + // up to a second (about:personas by finger, 2026-10-25); with 0.75 the same flicks landed on + // painted rows, and desktop Wikipedia's flicks cost the same (A/B: 80 vs 74 express frames over + // the same five flicks, the main lane 5-8 fps either way). + static const float sDpY = getenv("JIHAD_GPU_DP_Y") ? (float)atof(getenv("JIHAD_GPU_DP_Y")) : 0.75f; static const float sDpX = getenv("JIHAD_GPU_DP_X") ? (float)atof(getenv("JIHAD_GPU_DP_X")) : -1.0f; static const float sAnchorY = getenv("JIHAD_GPU_ANCHOR") ? (float)atof(getenv("JIHAD_GPU_ANCHOR")) : 0.25f; const float visW = (float)mWidth / z, visH = (float)mHeight / z; @@ -4743,8 +4748,15 @@ bool GoannaRenderPage::SetGpuViewport(double zoom) { // set the displayport; frames built meanwhile kept it blank, and a zoom out left grey areas if (!sNoDp) { const float smx = mGpuDpWanted ? mx : 0.0f, smy = mGpuDpWanted ? my : 0.0f; - if (jihad_layout_set_displayport2 && ds) dp = jihad_layout_set_displayport2(ds, visW, visH, (float)mGpuShiftX, (float)mGpuShiftY, smx, smy, smy); - else if (jihad_layout_set_displayport && ds) dp = jihad_layout_set_displayport(ds, visW, visH, smx, smy, smy); + // T-541: a fling lands past the symmetric margin, and what it uncovers stays grey until the + // main thread paints it (about:personas by finger, 2026-10-25: a whole grey card once, the + // lower 40% once, a white band after a flick up). While the card streams a fast scroll the + // daemon adds a lead in the direction of travel, up to 1.5 heights (BrowserPageGoanna's + // velocity); the margin against the motion stays what it was. + const float smyT = mGpuDpWanted ? my + visH * mGpuLeadUp : 0.0f; + const float smyB = mGpuDpWanted ? my + visH * mGpuLeadDown : 0.0f; + if (jihad_layout_set_displayport2 && ds) dp = jihad_layout_set_displayport2(ds, visW, visH, (float)mGpuShiftX, (float)mGpuShiftY, smx, smyT, smyB); + else if (jihad_layout_set_displayport && ds) dp = jihad_layout_set_displayport(ds, visW, visH, smx, smyT > smyB ? smyT : smyB, smyT > smyB ? smyT : smyB); else if (root && mGpuDpWanted) { r3 = u->SetDisplayPortMarginsForElement(mx, my, mx, my, root, 0); dp = NS_SUCCEEDED(r3) ? 0 : 9; } } const bool ok = dp == 0; diff --git a/render/goanna/GoannaRenderPage.h b/render/goanna/GoannaRenderPage.h index 460a4f1..47622b0 100644 --- a/render/goanna/GoannaRenderPage.h +++ b/render/goanna/GoannaRenderPage.h @@ -549,6 +549,10 @@ public: bool SetGpuViewport(double zoom); // T-524: where what the card shows sits from the engine's scroll (CSS px), for the displayport void SetGpuShift(double x, double y) { mGpuShiftX = x; mGpuShiftY = y; } + // T-541: extra displayport ahead of a fling, in heights above and below what is shown (0 at rest). + void SetGpuLead(float up, float down) { mGpuLeadUp = up; mGpuLeadDown = down; } + float GpuLeadUp() const { return mGpuLeadUp; } + float GpuLeadDown() const { return mGpuLeadDown; } // T-502: the engine's scroll position (CSS px) after a ScrollTo, and the compositor's view: // the zoom and the residual visual offset the engine scroll could not reach (pinch-in pans). bool GetEngineScroll(int* x, int* y); @@ -573,6 +577,7 @@ private: int mWidth; int mHeight; double mGpuShiftX = 0.0, mGpuShiftY = 0.0; // T-524: the compositor's shift the displayport is set for + float mGpuLeadUp = 0.0f, mGpuLeadDown = 0.0f; // T-541: the fling lead, in heights int mLayoutW = 0, mLayoutH = 0; // T-499: the layout viewport when it differs from the card (0 = the card) double mRenderZoom = 1.0; // pinch/fit zoom applied as a gfxContext scale in the offscreen capture double mPanX = 0.0; // visual-viewport pan (CSS px) used by the render when zoomed (>1)