Repository navigation
Conversation
This was referenced Oct 14, 2026
Contributor
Author
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.
Makes the island move the same way at 60, 144 and 240 Hz, and cuts work during its motion and while nobody can see the window. Adds a re-runnable probe that measures every demo pose.
What changed
src/lib/island/spring.svelte.ts,Island.svelte). Svelte'sSpringadds a whole acceleration to its velocity on every frame, so its curve depends on the refresh rate. Opening the island settled in about 417 ms at 60 Hz, 486 ms at 144 Hz and 504 ms at 240 Hz (closing too, pastSHRINK_AFTER= 450 ms).FrameSpringruns Svelte's own step at 60 steps a second and shows faster frames part of the way to the next step. At 60 Hz it matches Svelte frame for frame (tested). At 144 and 240 Hz it follows the same curve and gets more frames of it.App.svelte). While the spring moves, every frame asked forcompanion_hit_rects: one IPC call and agetBoundingClientRectsweep per frame, 240 a second on a 240 Hz screen. The shell reads them every 40 ms (WATCH_EVERY), so sends now coalesce to that period, and the trailing send always carries the last state. The 500 ms safety tick stops while the window is out of sight.App.svelte,bots.css).data-dormanton the root while the page is hidden or the shell has hidden the window under a full-screen app (companion://fullscreen, which the page hears without being told it is hidden: the shell usesShowWindow(SW_HIDE), so WebView2 still thinks it is visible).animation-play-state: pausedthen stops the robots' blinks, hums and pulses from drawing frames during a game. Svelte transitions use WAAPI and still finish.scripts/perf-probe.tswithscripts/perf.vite.config.ts: plain CDP against headless Chrome, GPU on, SwiftShader refused. For each pose it records 10 s ofPerformance.getMetricsdeltas, then 10 s of rAF frame intervals, plus the running CSS animations.open-closeclicks the pill open and closes it with Escape four times.--profilelists the heaviest functions,--unthrottledlifts the frame cap. It measures a production build with the demo poses kept in, because the dev server's Svelte tracing (get_stack) was most of what a dev measurement showed.No visual change: captures of
pose=open&tab=threadsandpose=deck&tab=musicbefore and after are identical; only the live music meter differs.Measurements
Production build, headless Chrome 1280x900, ANGLE on the AMD Radeon (renoir). Before and after ran back to back under similar host load. The frame columns are noisy on this shared host: a shift of 16.7 vs 33 ms in p95 at idle is host scheduling, not the page. Main-thread milliseconds per second are thread time and more stable.
60 Hz, 10 s per pose (before → after)
Frame cap lifted (
--unthrottled), closest headless proxy for a fast screen (before → after)In the unthrottled table, "Dropped" (an interval over 1.5 times the median) means little when the median is under a millisecond. Reading it: idle poses already cost 5 to 16 ms of main thread per second, and that does not move. The changes show during motion: with the frame cap lifted, open-close spends 24 % less in script and 22 % less in layout, and its p95 frame drops from 4.5 to 3.6 ms.
Tried and dropped
A 70 ms
transformtransition on the music meter's bars, to glide between its 15 Hz levels:deck-musicwent from 49 to 131 ms/s (20 transitions restarted 15 times a second, style recalc 10 to 57 ms/s). Reverted.Left as is, with the reason
src-tauri/src): the companion's watch thread ticks every 40 ms for click-through and the cursor (a few native calls), the music meter thread samples every 25 ms only while the page reads it and stops 3 s later, and nothing else polls. Slowing the watch while hidden would delay click-through after the shortcut summons the companion over a game, for a few microseconds of CPU saved. No change.ICoreWebView2Controller::put_IsVisible(false)when hidden would idle the whole renderer, but Chromium then throttles chained timers to once a minute after five minutes: pomodoro and note alarms would ring up to a minute late during a game.bloom/blurAwaytransitions animatefilter: blur()over the 860 px deck for 340 ms: a GPU cost on every frame of the opening, part of the look..openview'sbind:offsetHeightandIslandBar's measure spans read layout when they mount (about 10 ms per open in the profile). That layout has to happen anyway for that frame; it is moved, not doubled.Costs in files other work owns (not edited here)
src/components/companion/CompanionCharacter.svelte:408.blink { animation: blink 5s infinite }runs at all times, yet its keyframes stay still for 93 % of each cycle (:459). An endless animation keeps the compositor drawing at the display's rate, so the idle closed pill draws 240 frames a second on a 240 Hz screen to show a blink every 5 s. The same goes for:382-425(breathe, hum, scan, slide, beacon, float). A short animation triggered by a timer would draw only during the blink.src/components/island/IslandBar.svelte:509pulse 1.6s infiniteon each working dot: two to four of them in the closed pill, same effect.What a headless Linux Chrome cannot say about WebView2 on a 240 Hz Windows screen
fullscreendormant path relies on the shell's event, which only the Windows build emits; here it was checked by fakingdocument.hidden(9 running animations go to 0 paused and back to 9).Checks
bun run check: 0 errors, 0 warnings (291 files)bun run test: 61 files, 457 tests passed (5 new inspring.svelte.test.ts)bun run test:scripts: 0 failbun run check:manifest: acceptedCaptures
Not attached: this machine has no image host for PRs (the R2 helper's credentials are absent). Before and after captures of
pose=open&tab=threadsandpose=deck&tab=music(1280x600, desktop) were opened and compared; they match. They stay on the build machine undercaptures/bots-perf-high-refresh-*.png.