@buoy-gg/mcp 7.0.52 → 7.0.54
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chunk-ALXRBSW6.mjs +146 -0
- package/dist/chunk-C2Y7M7JN.mjs +427 -0
- package/dist/cli.js +178 -111
- package/dist/cli.mjs +2 -2
- package/dist/index.js +174 -107
- package/dist/index.mjs +1 -1
- package/dist/webBench-2IFT74EV.mjs +1 -0
- package/package.json +3 -3
- package/skill/buoy-optimize/MEASURING.md +100 -0
- package/skill/buoy-optimize/SKILL.md +99 -97
- package/dist/chunk-EKM5ZZKU.mjs +0 -139
- package/dist/chunk-JULJYHPG.mjs +0 -367
- package/dist/webBench-K5MAFWO3.mjs +0 -1
package/dist/index.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{ALL_DEVICES as e,BROKER_PORT as o,BROKER_URL as r,DASHBOARD_NAME as t,DEFAULT_ACTION_TIMEOUT_MS as s,DEFAULT_SNAPSHOT_TIMEOUT_MS as i,PACKAGE_NAME as T,SyncClient as n,VERSION as a,buildAutomationConfig as g,compareReportStats as l,createMcpServer as E,estimateBatchMs as R,formatRankingMarkdown as A,getUpdateNotice as S,rankBatchFromIndex as _,registerConsoleTools as c,registerEventsTools as M,registerGenericTools as m,registerJotaiTools as O,registerPerfMonitorTools as p,registerReactQueryTools as u,registerReduxTools as I,registerRoutesTools as d,registerScreenshotTools as C,registerStorageTools as N,registerZustandTools as U,runInit as B}from"./chunk-
|
|
1
|
+
import{ALL_DEVICES as e,BROKER_PORT as o,BROKER_URL as r,DASHBOARD_NAME as t,DEFAULT_ACTION_TIMEOUT_MS as s,DEFAULT_SNAPSHOT_TIMEOUT_MS as i,PACKAGE_NAME as T,SyncClient as n,VERSION as a,buildAutomationConfig as g,compareReportStats as l,createMcpServer as E,estimateBatchMs as R,formatRankingMarkdown as A,getUpdateNotice as S,rankBatchFromIndex as _,registerConsoleTools as c,registerEventsTools as M,registerGenericTools as m,registerJotaiTools as O,registerPerfMonitorTools as p,registerReactQueryTools as u,registerReduxTools as I,registerRoutesTools as d,registerScreenshotTools as C,registerStorageTools as N,registerZustandTools as U,runInit as B}from"./chunk-C2Y7M7JN.mjs";import"./chunk-RYFN5J56.mjs";import"./chunk-ALXRBSW6.mjs";export{e as ALL_DEVICES,o as BROKER_PORT,r as BROKER_URL,t as DASHBOARD_NAME,s as DEFAULT_ACTION_TIMEOUT_MS,i as DEFAULT_SNAPSHOT_TIMEOUT_MS,T as PACKAGE_NAME,n as SyncClient,a as VERSION,g as buildAutomationConfig,l as compareReportStats,E as createMcpServer,R as estimateBatchMs,A as formatRankingMarkdown,S as getUpdateNotice,_ as rankBatchFromIndex,c as registerConsoleTools,M as registerEventsTools,m as registerGenericTools,O as registerJotaiTools,p as registerPerfMonitorTools,u as registerReactQueryTools,I as registerReduxTools,d as registerRoutesTools,C as registerScreenshotTools,N as registerStorageTools,U as registerZustandTools,B as runInit};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{GPU_RENDERER_JS as e,PLAYWRIGHT_MISSING as a,aggregateCase as t,caseUrl as r,cdpDeltas as s,checkBudgets as o,closeCall as i,closeWithin as c,expectFailureFrom as n,formatWebBenchMarkdown as l,isSoftwareGpu as m,keyMatches as h,loadChromium as p,locateStepTargets as d,mergeInteractions as u,missedInput as g,oneLine as f,percentile as k,runWebBench as R,shuffled as b,snapRefreshRate as w,summarizeFrameTrace as B,summarizeProbe as F,takeHostLock as I,tiedPairs as S,validateWebBenchOptions as W,waitForHostLock as G,webBenchFlags as L,withDeadline as P}from"./chunk-ALXRBSW6.mjs";export{e as GPU_RENDERER_JS,a as PLAYWRIGHT_MISSING,t as aggregateCase,r as caseUrl,s as cdpDeltas,o as checkBudgets,i as closeCall,c as closeWithin,n as expectFailureFrom,l as formatWebBenchMarkdown,m as isSoftwareGpu,h as keyMatches,p as loadChromium,d as locateStepTargets,u as mergeInteractions,g as missedInput,f as oneLine,k as percentile,R as runWebBench,b as shuffled,w as snapRefreshRate,B as summarizeFrameTrace,F as summarizeProbe,I as takeHostLock,S as tiedPairs,W as validateWebBenchOptions,G as waitForHostLock,L as webBenchFlags,P as withDeadline};
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@buoy-gg/mcp",
|
|
3
3
|
"mcpName": "io.github.Buoy-gg/mcp",
|
|
4
|
-
"version": "7.0.
|
|
4
|
+
"version": "7.0.54",
|
|
5
5
|
"buoy": {
|
|
6
6
|
"dashboardAuthVersion": 1
|
|
7
7
|
},
|
|
@@ -35,8 +35,8 @@
|
|
|
35
35
|
"socket.io-client": "^4.8.1",
|
|
36
36
|
"zod": "^3.25.0",
|
|
37
37
|
"ws": "^8.18.0",
|
|
38
|
-
"@buoy-gg/sync-broker": "^7.0.
|
|
39
|
-
"@buoy-gg/license": "^7.0.
|
|
38
|
+
"@buoy-gg/sync-broker": "^7.0.54",
|
|
39
|
+
"@buoy-gg/license": "^7.0.54"
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {
|
|
42
42
|
"@types/node": "^22.0.0",
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# Measuring with Buoy
|
|
2
|
+
|
|
3
|
+
How to measure in the Buoy Optimize loop. The loop itself is in [SKILL.md](SKILL.md).
|
|
4
|
+
|
|
5
|
+
## Fair comparisons
|
|
6
|
+
|
|
7
|
+
- Keep the browser mode, build, window size, pixel ratio, load and settings fixed all session.
|
|
8
|
+
- Measure one thing at a time. Only one Buoy web benchmark runs per machine; a second one waits for the first.
|
|
9
|
+
- Mix up the order, and keep the baseline and current best in every batch as anchors. If the anchors move between batches, the machine drifted. Measure again.
|
|
10
|
+
- Rank on a production build when you can. Dev builds can pick a different winner. If you ranked on a dev build, say so.
|
|
11
|
+
- Keep DevTools closed while ranking.
|
|
12
|
+
- A failed or partial run is not a result. A missing number can't pass a limit.
|
|
13
|
+
|
|
14
|
+
## Web
|
|
15
|
+
|
|
16
|
+
All web tools open the app in their own Chromium and ask it to use the GPU. They need Playwright in the project, but no Buoy setup in the app. Each report names the GPU that drew. If it says software, the numbers mean little for drawing-heavy pages.
|
|
17
|
+
|
|
18
|
+
### `find_web_ceiling`: the score
|
|
19
|
+
|
|
20
|
+
The most load each variant holds at a target frame rate. It times main-thread `requestAnimationFrame` callbacks, the same way most in-app fps meters do. Use it as the score when your contract's score is that kind of frame rate. If the app has its own judge with a different rule, the app's judge wins.
|
|
21
|
+
|
|
22
|
+
Before you trust a ceiling, make sure the page really is ready and drawing the requested load: pass a `readySelector` that appears only after the first real frame, and check the screenshots. A canvas that stopped updating can still show a high rate. Never lower the target to suit the test setup.
|
|
23
|
+
|
|
24
|
+
```json
|
|
25
|
+
{
|
|
26
|
+
"url": "http://localhost:3000",
|
|
27
|
+
"cases": [
|
|
28
|
+
{ "name": "baseline", "params": { "variant": "baseline" } },
|
|
29
|
+
{ "name": "atlas", "params": { "variant": "atlas" } }
|
|
30
|
+
],
|
|
31
|
+
"param": "count",
|
|
32
|
+
"targetFps": 55,
|
|
33
|
+
"readySelector": "canvas",
|
|
34
|
+
"viewport": { "width": 1440, "height": 900, "deviceScaleFactor": 2 }
|
|
35
|
+
}
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
- It loads each step fresh with `param` set, settles 1 s, times frames for 2 s, and raises the load ~30% until a real sample misses. It repeats that miss once to rule out noise, then bisects until the pass and the miss are within 4% (`precisionPct`). If the first load misses, it searches down.
|
|
39
|
+
- A page error or a failed measurement marks the round **invalid**. It never counts as a miss. Fix the page and run again.
|
|
40
|
+
- "At least" means the round hit `max` (default 1,000,000,000) or the time limit before any miss. Raise `max` and run again.
|
|
41
|
+
- `rounds: 3` gives a median when two variants are close. Ceilings within about 10% are a tie until more rounds agree. Two-second samples are for screening; confirm your final pick with `rounds: 3`.
|
|
42
|
+
- `cpuSlowdown: 4` repeats it with the CPU slowed. It's a CPU stress test. It doesn't slow the GPU, memory or heat, so it isn't a phone.
|
|
43
|
+
- The report shows the browser's idle frame rate. Headless Chrome is often 60 Hz. If your target is close to it, use `headed: true` on a faster screen.
|
|
44
|
+
|
|
45
|
+
### `screenshot_web_variants`: the looks
|
|
46
|
+
|
|
47
|
+
```json
|
|
48
|
+
{ "url": "http://localhost:3000", "cases": [ ... same cases, same count ... ], "atMs": [500, 1500, 3000], "outDir": "shots/round-2", "clipSelector": "canvas" }
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Put the baseline first in `cases`. Each call saves into a new folder, with the real capture time of each shot, and reports how much each variant differs from the baseline at the same moment. A variant flagged as an outlier differs far more than the rest: lights not drawn, wrong colors or frozen motion. Open it first, and never let a flagged variant win until you've seen it's right. Open the PNGs and compare each variant with the baseline at about the same moment. Animated scenes won't match pixel for pixel, so compare shapes, colors, glow and layout, and use several moments to catch frozen motion.
|
|
52
|
+
|
|
53
|
+
### `run_web_benchmark`: smoothness and cost at one load
|
|
54
|
+
|
|
55
|
+
Use it to see why a variant wins: Dropped % (Chrome's own count of frames that hurt smoothness), frame gaps, long tasks, the scripts behind slow frames, main thread %, script, style and layout time, DOM size and JS heap.
|
|
56
|
+
|
|
57
|
+
- Compare only variants doing the same work at the same load.
|
|
58
|
+
- Dropped % ranges that overlap, or middles within 1.5 points, are a tie. `extraRoundsWhenClose: 2` adds runs for the top two.
|
|
59
|
+
- To test taps or typing, pass `steps` and end them with an `expect` step, so a run that skipped the work fails.
|
|
60
|
+
- Pass `budgets` for hard limits, like `{"droppedPct": 2, "inputMs": 200}`. A limit that wasn't measured counts as not met.
|
|
61
|
+
- Mark a case that does different work (like an empty page) with `"control": true`. It's shown, never ranked.
|
|
62
|
+
- `traceDir` saves a Chrome trace per case, for a deep look.
|
|
63
|
+
- `outFile` keeps the full result.
|
|
64
|
+
|
|
65
|
+
### Web traps
|
|
66
|
+
|
|
67
|
+
- On react-native-web, `Animated` with `useNativeDriver` and Reanimated worklets still run on the main thread.
|
|
68
|
+
- Skia on web loads a large WASM file. Count its load time.
|
|
69
|
+
- A pixel ratio of 2 draws 4 times the pixels of 1. Keep it fixed.
|
|
70
|
+
|
|
71
|
+
### Phones, when they're in scope
|
|
72
|
+
|
|
73
|
+
Open the dev server's network URL on a phone on the same Wi-Fi (for Vite, `vite --host`). In development, the page finds the Buoy broker on port 42831, so the phone shows in `list_devices`. Then use the app's own Bench with `run_benchmark_batch`. Keep the phone awake and the tab in front. Say it was a dev build.
|
|
74
|
+
|
|
75
|
+
## React Native
|
|
76
|
+
|
|
77
|
+
- `list_devices`: pick the device with `perf-monitor`. Benchmarking needs Buoy Pro. Don't work around account checks.
|
|
78
|
+
- `get_benchmark_settings` with `pages` = the number of cases: read back the settings and time estimate. Don't call `set_benchmark_settings` unless the user asked to save settings.
|
|
79
|
+
- `run_benchmark_batch` with `targetRoute`, `deviceId` and `cases`. The first case is the baseline. The route reads each case's `params`. Pass `captureRenders: false` while ranking; render capture adds work that differs by variant.
|
|
80
|
+
- If a batch times out, check `get_automation_status` before starting another.
|
|
81
|
+
- Read UI FPS, JS FPS, CPU, memory and jank together. A UI-thread flag is close to disqualifying. `balanced` means no flag fired; missing data can also fire no flag.
|
|
82
|
+
- `get_batch_report` takes a `batchId`. `compare_reports` takes report IDs. They're different IDs.
|
|
83
|
+
- `measure_renders` with `steps` counts renders in one interaction. Compare with `compareToPrevious: true`.
|
|
84
|
+
- A remount doesn't reset module state or storage. If you use `reloadBetweenCases`, make sure `AutomationResumer` is mounted.
|
|
85
|
+
- A simulator result holds for that simulator. Confirm on real hardware before claiming phone speed.
|
|
86
|
+
|
|
87
|
+
## Tool list
|
|
88
|
+
|
|
89
|
+
| Tool | Use |
|
|
90
|
+
| --- | --- |
|
|
91
|
+
| `start_optimize_session` | Only when the user gives a time limit: every Buoy result then shows the time left. |
|
|
92
|
+
| `find_web_ceiling` | The most load each web variant holds at a target fps. The score. |
|
|
93
|
+
| `screenshot_web_variants` | Screenshots of each web variant at the same moments. |
|
|
94
|
+
| `run_web_benchmark` | Smoothness and cost of web variants at one load. |
|
|
95
|
+
| `list_devices` | Find the connected app and its tools. |
|
|
96
|
+
| `get_benchmark_settings` / `set_benchmark_settings` | Read settings and the time estimate, or save them. |
|
|
97
|
+
| `run_benchmark_batch` / `get_automation_status` | Measure variants in the app with Bench. |
|
|
98
|
+
| `get_batch_report` / `compare_reports` | Read or compare saved runs. |
|
|
99
|
+
| `measure_renders` | Count renders in one interaction. |
|
|
100
|
+
| `navigate` | Open a route in the app. |
|
|
@@ -1,149 +1,151 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: buoy-optimize
|
|
3
|
-
description: Use when the user
|
|
3
|
+
description: Use when the user wants a slow screen made faster, or the most performant way to build something heavy (lists, animations, light grids, canvases, maps) in a React Native or web app, with the Buoy MCP server connected. Runs a search loop that keeps going until it hits a wall - brainstorm many ideas, build the most promising side by side, measure them all, research, fork the winners, combine them, and repeat. Triggers include "buoy optimize", "optimize this screen", "find the fastest way to render X", "why is this laggy".
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
# Optimize
|
|
6
|
+
# Buoy Optimize
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
You run a performance search. The goal is the best result you can find in the time you have, not a small fix.
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
Say a screen draws Christmas lights with CSS and it's slow. Don't just tune the CSS. Ask: what are all the ways to draw this? Canvas, WebGL, Skia, a shader, a sprite sheet, a video, a worker, and more. Build the most promising ones. Measure them all the same way. Keep what wins. Go again. Keep going until your time runs out.
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
Measuring details and tool tips are in [MEASURING.md](MEASURING.md). Read it before your first measurement.
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
## The loop
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
```
|
|
17
|
+
1 contract → 2 baseline → 3 idea bank → 4 pick a wide round → 5 build (time-boxed)
|
|
18
|
+
→ 6 check looks, then measure → 7 find each ceiling → 8 record + research
|
|
19
|
+
→ 9 next round (forks, combos, new families, old ideas) → back to 4
|
|
20
|
+
→ 10 at the wall: confirm and install the best checked version, then report
|
|
21
|
+
```
|
|
17
22
|
|
|
18
|
-
|
|
23
|
+
### 1. Write the contract
|
|
19
24
|
|
|
20
|
-
|
|
25
|
+
Write these down before you build anything:
|
|
21
26
|
|
|
22
|
-
|
|
27
|
+
- **The score.** One number with an exact pass rule. For "as many lights as possible", the score is the most lights that hold the target frame rate (say 55 fps) in the main setup.
|
|
28
|
+
- **The load knob.** The param that sets the work, like `?count=`. The score comes from turning it up until the frame rate drops.
|
|
29
|
+
- **What must stay the same.** The look, the motion, the timing, every pattern or mode, the interactions. A variant that changes these can't win.
|
|
30
|
+
- **The setup.** Browser or device, build type, window size and pixel ratio. Keep it fixed all session.
|
|
31
|
+
- **No time limit unless the user gives one.** You keep going until you hit the wall (step 9). If the user does give a time limit, call `start_optimize_session` with it so every Buoy tool result shows the time left.
|
|
23
32
|
|
|
24
|
-
|
|
33
|
+
**An app's own test cap is not the ceiling.** If the app's test stops at 50,000, the score has no cap. Measure past it (see step 7). Keep the app's own test as a second check, and confirm both agree below its cap.
|
|
25
34
|
|
|
26
|
-
|
|
35
|
+
This holds even when the user says "your score is the app's test". Maxing out a capped test means you have reached the cap, not the wall. Reaching the cap is when the real search starts: find the uncapped ceiling and keep raising it.
|
|
27
36
|
|
|
28
|
-
|
|
37
|
+
### 2. Measure the baseline
|
|
29
38
|
|
|
30
|
-
|
|
39
|
+
- Save the current code as the `baseline` variant.
|
|
40
|
+
- Make one test page where a URL param picks the variant, like `?variant=atlas&count=50000`. Every idea becomes a variant here. **Never edit the current best in place.** Old variants stay, so you can compare and combine them.
|
|
41
|
+
- Check that the load knob really changes the work.
|
|
42
|
+
- Find the baseline's ceiling. Profile it near its limit to see what is slow.
|
|
43
|
+
- Estimate the **hardware limit**: roughly how much work the GPU and CPU could do per frame (pixels drawn, draw calls, bytes sent, math per item). Far below that limit means bold ideas are worth it. Near it means small gains.
|
|
31
44
|
|
|
32
|
-
|
|
45
|
+
### 3. Build the idea bank
|
|
33
46
|
|
|
34
|
-
|
|
47
|
+
Make `OPTIMIZE_IDEAS.md` in the project. It is your memory. **Read it before every round,** and after any break.
|
|
35
48
|
|
|
36
|
-
|
|
49
|
+
Write down at least 20 different ideas in about 10 minutes. Go wide on purpose, across families:
|
|
37
50
|
|
|
38
|
-
|
|
51
|
+
- **Other technology.** For drawing: DOM, CSS animations, Canvas 2D, WebGL or WebGPU, Skia (`drawAtlas`, `drawVertices`, runtime shaders, pictures), one big shader, a sprite sheet, a video, a worker with OffscreenCanvas, native views. For lists: windowing, recycling, flattening.
|
|
52
|
+
- **Doing less work.** Draw once and reuse. Cache. Batch into one draw call. Move math to the GPU. Compute once per item instead of once per pixel. Skip work nobody can see.
|
|
53
|
+
- **Hacks.** Draw blur at low resolution and scale it up. Bake glows into textures. Lookup tables. Pack data into textures. Split still and moving layers. Spatial grids.
|
|
54
|
+
- **The runtime.** No React work per frame. No allocations per frame. No layout reads after writes. Better blend modes and texture formats.
|
|
39
55
|
|
|
40
|
-
|
|
56
|
+
Give each idea one row: ID, family, parent ideas, what it is, why it might win, the bottleneck it attacks, rough build time, status, score range, report IDs and the next test. Statuses: `untried`, `building`, `valid`, `invalid` (looks or acts wrong), `failed-build`, `deferred` (with what would make it worth another try), `blocked`.
|
|
41
57
|
|
|
42
|
-
|
|
58
|
+
Keep a **current best** line at the top. Also keep the best variant of each family, not just the overall leader. A family that loses now can win after a fork.
|
|
43
59
|
|
|
44
|
-
|
|
60
|
+
**Never delete an idea.** A loss at one load, or a bug, doesn't prove an idea is bad. Write what happened and move on.
|
|
45
61
|
|
|
46
|
-
|
|
62
|
+
### 4. Pick a wide round
|
|
47
63
|
|
|
48
|
-
|
|
64
|
+
- **First round:** about 6 ideas from at least 3 families. Include at least one bold idea that changes the technology.
|
|
65
|
+
- **Every round tests several ideas,** never just one tweak. At least one must be new, untried or deferred, from outside the current best's family.
|
|
66
|
+
- **Later rounds:** spend about half the effort on forks and combinations of what's winning. Spend the other half on new families, ideas from research and deferred ideas. Don't let one family eat all your time.
|
|
67
|
+
- Keep one variant that scales differently when the early ranking isn't clear. It may win at a higher load.
|
|
68
|
+
- Keep the baseline and the current best in every comparison.
|
|
69
|
+
- Write one line on why each pick is worth its build time.
|
|
49
70
|
|
|
50
|
-
|
|
71
|
+
### 5. Build each one, time-boxed
|
|
51
72
|
|
|
52
|
-
|
|
73
|
+
- Give each idea a build budget, about 5 to 8 minutes. A bold idea can get more. If a build blows its budget, mark it `deferred` with what went wrong and move on.
|
|
74
|
+
- Smoke-test each variant as soon as it runs.
|
|
75
|
+
- **Every picked idea gets an outcome:** measured, `invalid`, `failed-build`, `deferred` or `blocked`. Never drop one silently.
|
|
76
|
+
- Inactive variants must not run work in the background.
|
|
53
77
|
|
|
54
|
-
|
|
78
|
+
### 6. Check looks, then measure
|
|
55
79
|
|
|
56
|
-
|
|
80
|
+
**A fast variant that looks wrong is worthless.** Check before you trust any number.
|
|
57
81
|
|
|
58
|
-
|
|
82
|
+
- If the user is around, ask them to look.
|
|
83
|
+
- If not, check it yourself with `screenshot_web_variants` (web) or screenshots (app). Put the baseline first; the tool flags variants that differ from it far more than the rest. Use the same count and settings for every case. Take several moments, so frozen or broken motion shows. Check a sparse scene, where each item is visible, and a dense one. Compare each variant with the baseline side by side: shapes, colors, glow, layout, motion. Watch it run live too. A still image can't prove motion.
|
|
84
|
+
- Mark wrong ones `invalid` and say what was wrong.
|
|
59
85
|
|
|
60
|
-
|
|
86
|
+
Then measure every valid variant the same way. Screen them at a few loads (one low, one near the current best's ceiling, one above), because a variant can lose small and win big. Before you prune, raise the load so the top ones don't all tie at the frame cap.
|
|
61
87
|
|
|
62
|
-
|
|
63
|
-
- p95 frame gap, worst gap and long frames come from the time between frames in that run. A gap over 1.5 frames missed at least one frame.
|
|
64
|
-
- Long tasks % is the share of the run the main thread spent in tasks over 50 ms. It is not CPU use, and it misses many shorter tasks.
|
|
65
|
-
- A metric the browser can't measure shows as —. Safari and Firefox have no long tasks or JS heap. A — can't win or lose.
|
|
66
|
-
- "Why frames were slow" names the scripts behind slow frames, plus style, layout and forced layout time. Start your next idea there. It only sees frames over 50 ms, and it can't see work in workers.
|
|
67
|
-
- "DOM grew every run" is a hint of a leak, not proof. Confirm it with a heap snapshot after the screen unmounts.
|
|
68
|
-
- "Too close to call" means the top cases overlap across runs. Run more repeats before you pick.
|
|
69
|
-
- A page load table shows up only when each case ran on its own fresh page load (the batch reloads between cases with `reloadBetweenCases`). Use it for load-time work.
|
|
88
|
+
### 7. Find each ceiling
|
|
70
89
|
|
|
71
|
-
|
|
90
|
+
The ceiling is the score.
|
|
72
91
|
|
|
73
|
-
-
|
|
74
|
-
-
|
|
75
|
-
-
|
|
76
|
-
-
|
|
92
|
+
- On web, use `find_web_ceiling`. It raises the load about 30% a step until the frame rate misses the target, then narrows the gap to a few percent. It has no cap by default (up to 1,000,000,000).
|
|
93
|
+
- On React Native, ramp the count with `run_benchmark_batch` cases.
|
|
94
|
+
- Record the highest pass and the lowest miss. A single miss can be noise; run the boundary again before you call it.
|
|
95
|
+
- If nothing missed, the score is "at least" that load. Raise the max and run again.
|
|
96
|
+
- **Rank on the score.** Dropped frames, frame times, CPU, memory and profiles explain why a variant won. They only rank when the contract says so.
|
|
77
97
|
|
|
78
|
-
|
|
98
|
+
### 8. Record and research
|
|
79
99
|
|
|
80
|
-
-
|
|
81
|
-
-
|
|
100
|
+
- Write every outcome into the bank: the score range, what the profile showed, and **why** it won or lost. Keep what you saw separate from what you guess.
|
|
101
|
+
- Research for about 3 minutes after **every** round, even one where every build failed. Search the web, docs, source code and issues for the bottleneck you just saw. Examples: "WebGL draw 1 million sprites", "Skia drawAtlas performance", "CanvasKit flush cost". Add new ideas and their sources to the bank.
|
|
102
|
+
- Keep a research log in the bank: each round's search, the sources, what you learned and the experiment it suggests.
|
|
103
|
+
- After a big win, profile again. The bottleneck moves.
|
|
82
104
|
|
|
83
|
-
|
|
105
|
+
### 9. Next round
|
|
84
106
|
|
|
85
|
-
|
|
107
|
+
Build the next round from forks of the winners, combinations of tricks that worked, new ideas from research and untried or deferred ideas from the bank. Then go back to step 4.
|
|
86
108
|
|
|
87
|
-
|
|
109
|
+
- If every idea in a round lost, pick the next best ideas from the bank. Write more ideas if the bank runs thin.
|
|
110
|
+
- **A plateau means change direction, not stop.** Try another family, a deferred idea with new evidence, or attack the current bottleneck another way.
|
|
111
|
+
- Keep going until you hit the wall. Stop sooner only if the user says so, their time limit runs out, or something outside your control blocks all useful work.
|
|
88
112
|
|
|
89
|
-
|
|
113
|
+
Tell the user where you are each round in a line or two: round number, what you tried, the new best score.
|
|
90
114
|
|
|
91
|
-
|
|
115
|
+
**The wall.** You have hit it only when all of these are true:
|
|
92
116
|
|
|
93
|
-
|
|
117
|
+
1. Your score is an uncapped ceiling with a real miss, not "at least" and not the app's own cap.
|
|
118
|
+
2. The last 3 rounds each raised it by less than 3%.
|
|
119
|
+
3. Those 3 rounds tried at least 3 different families of ideas, not 3 tweaks of one.
|
|
120
|
+
4. You researched after each of those rounds and wrote what you found.
|
|
121
|
+
5. Every idea still in the bank has a written reason it won't help. "It changes the look" or "too slow" only counts if you measured it. A guess doesn't.
|
|
94
122
|
|
|
95
|
-
|
|
123
|
+
If any one is false, you are not at the wall. Start another round. Hitting a good number, writing `RESULTS.md` or running long are never reasons to stop.
|
|
96
124
|
|
|
97
|
-
|
|
98
|
-
2. The same cases with `cpuSlowdown: [4, 6]`. This slows the CPU only, not the GPU, memory or heat.
|
|
99
|
-
3. A real iPhone in Safari.
|
|
100
|
-
4. A real mid-range Android phone in Chrome.
|
|
125
|
+
Work in one long session. Don't end your turn to wait for a long command. Wait for it, then keep going.
|
|
101
126
|
|
|
102
|
-
|
|
103
|
-
5. Desktop Firefox and Safari, if the user supports them.
|
|
127
|
+
### 10. At the wall: lock in the best
|
|
104
128
|
|
|
105
|
-
|
|
129
|
+
When you hit the wall:
|
|
106
130
|
|
|
107
|
-
|
|
131
|
+
- Measure the top 2 or 3 again with fresh runs, and confirm the ceilings.
|
|
132
|
+
- Put the winner on the real screen. Check its looks and interactions again there.
|
|
133
|
+
- Run the app's own test too, if it has one.
|
|
134
|
+
- Leave the best checked version in place, never an untested experiment.
|
|
108
135
|
|
|
109
|
-
|
|
110
|
-
- WebGL with one draw call for many items.
|
|
111
|
-
- An OffscreenCanvas in a worker.
|
|
112
|
-
- CSS or Web Animations API transforms. The compositor can run these.
|
|
113
|
-
- List windowing, and `content-visibility` for things off screen.
|
|
136
|
+
Writing a report or `RESULTS.md` along the way is fine. Update it after each win. It doesn't end the session; the wall does.
|
|
114
137
|
|
|
115
|
-
|
|
138
|
+
Then report:
|
|
116
139
|
|
|
117
|
-
|
|
140
|
+
- Baseline and final score, with the range, and the exact setup.
|
|
141
|
+
- The winning approach and the tricks that mattered most.
|
|
142
|
+
- What each family scored, and the ideas still untried.
|
|
143
|
+
- How you checked the looks.
|
|
144
|
+
- Why you stopped: show the wall check (the last 3 rounds, their families, the research, and why each idea left in the bank won't help). Say "best found", not "the maximum possible".
|
|
118
145
|
|
|
119
|
-
|
|
120
|
-
- Runs failed, frame data is missing, or a case has fewer than 2 good runs.
|
|
121
|
-
- The Dropped % ranges touch or overlap, or the middle values are within 1.5 points.
|
|
122
|
-
- An input limit matters and its timing was incomplete or unknown. A missed or unknown budget can't back a "meets the goal" pick.
|
|
146
|
+
## Rules
|
|
123
147
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
-
|
|
127
|
-
-
|
|
128
|
-
- Input times come from the scripted steps, not real users. A missing time is unknown. The tie rule is a guide, not proof.
|
|
129
|
-
|
|
130
|
-
Traps on react-native-web:
|
|
131
|
-
|
|
132
|
-
- `Animated` with `useNativeDriver` still runs on the main thread.
|
|
133
|
-
- Reanimated worklets run as plain JS on the main thread.
|
|
134
|
-
- Skia on web loads CanvasKit, a large WASM file. Count its load time and first draw.
|
|
135
|
-
|
|
136
|
-
## Tool reference
|
|
137
|
-
|
|
138
|
-
| Tool | Use |
|
|
139
|
-
| --- | --- |
|
|
140
|
-
| `list_devices` | Select the connected device and inspect its tools. |
|
|
141
|
-
| `measure_renders` | Measure a supported interaction before and after a change. |
|
|
142
|
-
| `get_benchmark_settings` / `set_benchmark_settings` | Read the profile and estimate, or explicitly persist settings. |
|
|
143
|
-
| `run_benchmark_batch` / `get_automation_status` | Run the case matrix and inspect automation status. |
|
|
144
|
-
| `run_web_benchmark` | Run web cases in a fresh headless Chromium, with optional CPU slowdown. |
|
|
145
|
-
| `get_batch_report` | Read a saved batch using its `batchId`. |
|
|
146
|
-
| `compare_reports` | Compare saved runs using `reportIdA` and `reportIdB`. |
|
|
147
|
-
| `navigate` | Open a supported app route using `path`. |
|
|
148
|
-
|
|
149
|
-
Keep summaries focused on the decision and its evidence. Include report IDs so results can be revisited without pasting every sample into the conversation.
|
|
148
|
+
- Every variant does the same work: same items, same look, same behavior. Drawing fewer items, skipping frames, or anything that makes the meter read high without the user seeing it, is cheating and can't win.
|
|
149
|
+
- Don't change the app's own test, its clock or its frame counter.
|
|
150
|
+
- Don't ask the user to approve each round. Ask only when a choice changes the scope.
|
|
151
|
+
- Stay on the user's platform. Test other platforms only when they're in scope.
|