diff --git a/.prettierignore b/.prettierignore index 00c7408cb4..9c5a9bc97f 100644 --- a/.prettierignore +++ b/.prettierignore @@ -9,6 +9,9 @@ packages/gcp-cloud-run/terraform/workflow.yaml # toMatchFileSnapshot golden files — vitest writes these; oxfmt must not reformat them. packages/**/__goldens__/ +# Edit accuracy baseline, written by the bench one line per case so a diff reads case by case. +packages/studio/tests/e2e/edit-accuracy/baseline.json + # vendored third-party bundles inside skills — never reformat skills/**/assets/vendor/ skills/**/*.min.js diff --git a/packages/studio/package.json b/packages/studio/package.json index 2529bf7d91..db2826710b 100644 --- a/packages/studio/package.json +++ b/packages/studio/package.json @@ -75,6 +75,7 @@ "build": "vite build && tsup", "typecheck": "tsc --noEmit", "test": "vitest run", + "test:edit-accuracy": "bun tests/e2e/edit-accuracy/run.mjs", "test:webmcp-edit-loop": "node tests/e2e/webmcp-edit-loop.mjs", "test:timeline-virtualization": "TIMELINE_ROW_VIRTUALIZATION=on TIMELINE_ELEMENT_COUNT=50000 node tests/e2e/timeline-virtualization.mjs", "test:watch": "vitest", diff --git a/packages/studio/tests/e2e/edit-accuracy/baseline.json b/packages/studio/tests/e2e/edit-accuracy/baseline.json new file mode 100644 index 0000000000..c7b90de3a4 --- /dev/null +++ b/packages/studio/tests/e2e/edit-accuracy/baseline.json @@ -0,0 +1,668 @@ +{ + "studio": "14e87005d", + "build": "a0104a7ce975", + "bench": "1f83860ed", + "grid": "full, re-scored by f1797b5c2 without render", + "cases": { + "crop-hold-center-r0-nested-z100": {"pass":false,"tracking":0.04,"pressJump":0,"drop":0.07,"reload":0,"undo":true,"dropped":20,"controlDropped":13,"work":5.2,"frameP95":60.6}, + "crop-hold-center-r0-nested-z200": {"pass":false,"tracking":0.02,"pressJump":0,"drop":0.04,"reload":0.03,"undo":true,"dropped":17,"controlDropped":13,"work":5.84,"frameP95":59.6}, + "crop-hold-center-r0-nested-z50": {"pass":false,"tracking":0.05,"pressJump":0,"drop":0.07,"reload":0.05,"undo":true,"dropped":19,"controlDropped":13,"work":5.18,"frameP95":56.6}, + "crop-hold-center-r0-root-z100": {"pass":false,"tracking":0.04,"pressJump":0,"drop":0.07,"reload":0,"undo":false,"dropped":10,"controlDropped":2,"work":5.89,"frameP95":52.71}, + "crop-hold-center-r0-root-z200": {"pass":false,"tracking":0.02,"pressJump":0,"drop":0.04,"reload":0.03,"undo":false,"dropped":20,"controlDropped":7,"work":5.68,"frameP95":58}, + "crop-hold-center-r0-root-z50": {"pass":false,"tracking":0.05,"pressJump":0,"drop":0.07,"reload":0.05,"undo":false,"dropped":8,"controlDropped":10,"work":6.22,"frameP95":36.3}, + "crop-hold-center-r30-nested-z100": {"pass":false,"tracking":0.04,"pressJump":0,"drop":0.08,"reload":0,"undo":true,"dropped":21,"controlDropped":16,"work":5.06,"frameP95":57.3}, + "crop-hold-center-r30-nested-z200": {"pass":false,"tracking":0.02,"pressJump":0,"drop":0.04,"reload":0.03,"undo":true,"dropped":15,"controlDropped":12,"work":6.5,"frameP95":56.5}, + "crop-hold-center-r30-nested-z50": {"pass":false,"tracking":0.05,"pressJump":0,"drop":0.07,"reload":0.07,"undo":true,"dropped":22,"controlDropped":15,"work":6.21,"frameP95":60.4}, + "crop-hold-center-r30-root-z100": {"pass":false,"tracking":0.04,"pressJump":0,"drop":0.08,"reload":0,"undo":false,"dropped":24,"controlDropped":15,"work":5.34,"frameP95":63.5}, + "crop-hold-center-r30-root-z200": {"pass":false,"tracking":0.02,"pressJump":0,"drop":0.04,"reload":0.03,"undo":false,"dropped":25,"controlDropped":15,"work":5.23,"frameP95":63.6}, + "crop-hold-center-r30-root-z50": {"pass":false,"tracking":0.05,"pressJump":0,"drop":0.07,"reload":0.07,"undo":false,"dropped":19,"controlDropped":12,"work":5.53,"frameP95":64.3}, + "crop-hold-pct-r0-nested-z100": {"pass":true,"tracking":0.04,"pressJump":0,"drop":0.08,"reload":0,"undo":true,"dropped":4,"controlDropped":4,"work":5.57,"frameP95":39.5}, + "crop-hold-pct-r0-nested-z200": {"pass":false,"tracking":0.02,"pressJump":0,"drop":0.04,"reload":0.03,"undo":true,"dropped":6,"controlDropped":1,"work":5.74,"frameP95":42}, + "crop-hold-pct-r0-nested-z50": {"pass":true,"tracking":0.05,"pressJump":0,"drop":0.1,"reload":0.07,"undo":true,"dropped":5,"controlDropped":6,"work":6.35,"frameP95":37.9}, + "crop-hold-pct-r0-root-z100": {"pass":false,"tracking":0.04,"pressJump":0,"drop":0.03,"reload":0,"undo":false,"dropped":2,"controlDropped":6,"work":5.62,"frameP95":21.3}, + "crop-hold-pct-r0-root-z200": {"pass":false,"tracking":0.02,"pressJump":0,"drop":0.03,"reload":0.04,"undo":false,"dropped":2,"controlDropped":3,"work":5.56,"frameP95":18.2}, + "crop-hold-pct-r0-root-z50": {"pass":false,"tracking":0.05,"pressJump":0,"drop":0.1,"reload":0.05,"undo":false,"dropped":5,"controlDropped":1,"work":5.68,"frameP95":35.8}, + "crop-hold-pct-r30-nested-z100": {"pass":false,"tracking":0.04,"pressJump":0,"drop":0.08,"reload":0,"undo":true,"dropped":7,"controlDropped":3,"work":5.61,"frameP95":48.71}, + "crop-hold-pct-r30-nested-z200": {"pass":true,"tracking":0.02,"pressJump":0,"drop":0.04,"reload":0.04,"undo":true,"dropped":7,"controlDropped":9,"work":6.1,"frameP95":48.91}, + "crop-hold-pct-r30-nested-z50": {"pass":false,"tracking":0.05,"pressJump":0,"drop":0.14,"reload":0.05,"undo":true,"dropped":11,"controlDropped":8,"work":5.58,"frameP95":46.61}, + "crop-hold-pct-r30-root-z100": {"pass":false,"tracking":0.04,"pressJump":0,"drop":0.06,"reload":0,"undo":false,"dropped":4,"controlDropped":6,"work":5.53,"frameP95":42.61}, + "crop-hold-pct-r30-root-z200": {"pass":false,"tracking":0.02,"pressJump":0,"drop":0.02,"reload":0.03,"undo":false,"dropped":8,"controlDropped":9,"work":5.64,"frameP95":45.71}, + "crop-hold-pct-r30-root-z50": {"pass":false,"tracking":0.05,"pressJump":0,"drop":0.12,"reload":0.07,"undo":false,"dropped":12,"controlDropped":0,"work":5.6,"frameP95":60.5}, + "crop-hold-px-r0-nested-z100": {"pass":true,"tracking":0.04,"pressJump":0,"drop":0.08,"reload":0,"undo":true,"dropped":8,"controlDropped":11,"work":6.28,"frameP95":40.41}, + "crop-hold-px-r0-nested-z200": {"pass":false,"tracking":0.02,"pressJump":0,"drop":0.04,"reload":0.03,"undo":true,"dropped":16,"controlDropped":15,"work":5.84,"frameP95":59.1}, + "crop-hold-px-r0-nested-z50": {"pass":false,"tracking":0.05,"pressJump":0,"drop":0.1,"reload":0.07,"undo":true,"dropped":13,"controlDropped":12,"work":5.94,"frameP95":54.5}, + "crop-hold-px-r0-root-z100": {"pass":false,"tracking":0.04,"pressJump":0,"drop":0.03,"reload":0,"undo":false,"dropped":15,"controlDropped":13,"work":6.21,"frameP95":65.71}, + "crop-hold-px-r0-root-z200": {"pass":false,"tracking":0.02,"pressJump":0,"drop":0.03,"reload":0.04,"undo":false,"dropped":17,"controlDropped":10,"work":5.78,"frameP95":55.21}, + "crop-hold-px-r0-root-z50": {"pass":false,"tracking":0.05,"pressJump":0,"drop":0.1,"reload":0.05,"undo":false,"dropped":16,"controlDropped":13,"work":5.55,"frameP95":63.5}, + "crop-hold-px-r30-nested-z100": {"pass":false,"tracking":0.04,"pressJump":0,"drop":0.08,"reload":0,"undo":true,"dropped":15,"controlDropped":6,"work":6.14,"frameP95":64.41}, + "crop-hold-px-r30-nested-z200": {"pass":false,"tracking":0.02,"pressJump":0,"drop":0.04,"reload":0.04,"undo":true,"dropped":5,"controlDropped":3,"work":5.88,"frameP95":45.7}, + "crop-hold-px-r30-nested-z50": {"pass":false,"tracking":0.05,"pressJump":0,"drop":0.14,"reload":0.05,"undo":true,"dropped":12,"controlDropped":10,"work":5.78,"frameP95":63.4}, + "crop-hold-px-r30-root-z100": {"pass":false,"tracking":0.04,"pressJump":0,"drop":0.06,"reload":0,"undo":false,"dropped":15,"controlDropped":8,"work":5.33,"frameP95":46.41}, + "crop-hold-px-r30-root-z200": {"pass":false,"tracking":0.02,"pressJump":0,"drop":0.02,"reload":0.03,"undo":false,"dropped":16,"controlDropped":7,"work":5.42,"frameP95":49.91}, + "crop-hold-px-r30-root-z50": {"pass":false,"tracking":0.05,"pressJump":0,"drop":0.12,"reload":0.07,"undo":false,"dropped":10,"controlDropped":9,"work":6.28,"frameP95":44}, + "crop-hold-xpercent-r0-nested-z100": {"pass":false,"tracking":0.04,"pressJump":0,"drop":0.07,"reload":0,"undo":true,"dropped":19,"controlDropped":10,"work":5.85,"frameP95":63}, + "crop-hold-xpercent-r0-nested-z200": {"pass":false,"tracking":0.02,"pressJump":0,"drop":0.04,"reload":0.03,"undo":true,"dropped":16,"controlDropped":8,"work":6.31,"frameP95":63.41}, + "crop-hold-xpercent-r0-nested-z50": {"pass":false,"tracking":0.05,"pressJump":0,"drop":0.07,"reload":0.05,"undo":true,"dropped":13,"controlDropped":10,"work":6.16,"frameP95":60.11}, + "crop-hold-xpercent-r0-root-z100": {"pass":false,"tracking":0.04,"pressJump":0,"drop":0.07,"reload":0,"undo":false,"dropped":5,"controlDropped":9,"work":5.95,"frameP95":42.91}, + "crop-hold-xpercent-r0-root-z200": {"pass":false,"tracking":0.02,"pressJump":0,"drop":0.04,"reload":0.03,"undo":false,"dropped":10,"controlDropped":3,"work":6.11,"frameP95":59.8}, + "crop-hold-xpercent-r0-root-z50": {"pass":false,"tracking":0.05,"pressJump":0,"drop":0.07,"reload":0.05,"undo":false,"dropped":21,"controlDropped":11,"work":4.87,"frameP95":57}, + "crop-hold-xpercent-r30-nested-z100": {"pass":false,"tracking":0.04,"pressJump":0,"drop":0.08,"reload":0,"undo":true,"dropped":21,"controlDropped":13,"work":5.28,"frameP95":57.3}, + "crop-hold-xpercent-r30-nested-z200": {"pass":false,"tracking":0.02,"pressJump":0,"drop":0.04,"reload":0.03,"undo":true,"dropped":21,"controlDropped":11,"work":5.69,"frameP95":61.5}, + "crop-hold-xpercent-r30-nested-z50": {"pass":false,"tracking":0.05,"pressJump":0,"drop":0.07,"reload":0.07,"undo":true,"dropped":21,"controlDropped":14,"work":6.25,"frameP95":57.71}, + "crop-hold-xpercent-r30-root-z100": {"pass":false,"tracking":0.04,"pressJump":0,"drop":0.08,"reload":0,"undo":false,"dropped":12,"controlDropped":14,"work":6.35,"frameP95":51.81}, + "crop-hold-xpercent-r30-root-z200": {"pass":false,"tracking":0.02,"pressJump":0,"drop":0.04,"reload":0.03,"undo":false,"dropped":18,"controlDropped":10,"work":5.66,"frameP95":53.7}, + "crop-hold-xpercent-r30-root-z50": {"pass":false,"tracking":0.05,"pressJump":0,"drop":0.07,"reload":0.07,"undo":false,"dropped":14,"controlDropped":12,"work":6.47,"frameP95":51.41}, + "crop-none-center-r0-nested-z100": {"pass":true,"tracking":0.04,"pressJump":0,"drop":0.08,"reload":0,"undo":true,"dropped":9,"controlDropped":13,"work":5.59,"frameP95":61.11}, + "crop-none-center-r0-nested-z200": {"pass":false,"tracking":0.02,"pressJump":0,"drop":0.04,"reload":0.03,"undo":true,"dropped":16,"controlDropped":12,"work":6.68,"frameP95":63.71}, + "crop-none-center-r0-nested-z50": {"pass":false,"tracking":0.05,"pressJump":0,"drop":0.11,"reload":0.05,"undo":true,"dropped":14,"controlDropped":13,"work":6.54,"frameP95":60.4}, + "crop-none-center-r0-root-z100": {"pass":false,"tracking":0.04,"pressJump":0,"drop":0.08,"reload":0,"undo":true,"dropped":7,"controlDropped":3,"work":5.97,"frameP95":55.41}, + "crop-none-center-r0-root-z200": {"pass":false,"tracking":0.02,"pressJump":0,"drop":0.04,"reload":0.03,"undo":true,"dropped":17,"controlDropped":10,"work":5.88,"frameP95":61.71}, + "crop-none-center-r0-root-z50": {"pass":false,"tracking":0.05,"pressJump":0,"drop":0.11,"reload":0.05,"undo":true,"dropped":15,"controlDropped":10,"work":6,"frameP95":56.7}, + "crop-none-center-r30-nested-z100": {"pass":false,"tracking":0.04,"pressJump":0,"drop":0.08,"reload":0,"undo":true,"dropped":6,"controlDropped":2,"work":6.32,"frameP95":45.5}, + "crop-none-center-r30-nested-z200": {"pass":false,"tracking":0.02,"pressJump":0,"drop":0.04,"reload":0.04,"undo":true,"dropped":11,"controlDropped":2,"work":6.09,"frameP95":59.41}, + "crop-none-center-r30-nested-z50": {"pass":false,"tracking":0.05,"pressJump":0,"drop":0.08,"reload":0.07,"undo":true,"dropped":3,"controlDropped":2,"work":5.74,"frameP95":37.61}, + "crop-none-center-r30-root-z100": {"pass":true,"tracking":0.04,"pressJump":0,"drop":0.08,"reload":0,"undo":true,"dropped":5,"controlDropped":11,"work":6.61,"frameP95":50.91}, + "crop-none-center-r30-root-z200": {"pass":false,"tracking":0.02,"pressJump":0,"drop":0.04,"reload":0.04,"undo":true,"dropped":9,"controlDropped":5,"work":6.47,"frameP95":55.3}, + "crop-none-center-r30-root-z50": {"pass":false,"tracking":0.05,"pressJump":0,"drop":0.08,"reload":0.07,"undo":true,"dropped":21,"controlDropped":8,"work":5.57,"frameP95":60}, + "crop-none-pct-r0-nested-z100": {"pass":false,"tracking":0.04,"pressJump":0,"drop":0.07,"reload":0,"undo":true,"dropped":20,"controlDropped":11,"work":6.1,"frameP95":63.8}, + "crop-none-pct-r0-nested-z200": {"pass":true,"tracking":0.02,"pressJump":0,"drop":0.02,"reload":0.02,"undo":true,"dropped":12,"controlDropped":14,"work":6.21,"frameP95":59.31}, + "crop-none-pct-r0-nested-z50": {"pass":true,"tracking":0.05,"pressJump":0,"drop":0.07,"reload":0.05,"undo":true,"dropped":8,"controlDropped":9,"work":6.53,"frameP95":59.21}, + "crop-none-pct-r0-root-z100": {"pass":false,"tracking":0.04,"pressJump":0,"drop":0.07,"reload":0,"undo":true,"dropped":12,"controlDropped":7,"work":6.65,"frameP95":55.1}, + "crop-none-pct-r0-root-z200": {"pass":false,"tracking":0.02,"pressJump":0,"drop":0.03,"reload":0.03,"undo":true,"dropped":15,"controlDropped":12,"work":5.9,"frameP95":55.21}, + "crop-none-pct-r0-root-z50": {"pass":false,"tracking":0.05,"pressJump":0,"drop":0.11,"reload":0.07,"undo":true,"dropped":17,"controlDropped":14,"work":5.99,"frameP95":53.8}, + "crop-none-pct-r30-nested-z100": {"pass":false,"tracking":0.04,"pressJump":0,"drop":0.06,"reload":0,"undo":true,"dropped":13,"controlDropped":6,"work":5.58,"frameP95":54.5}, + "crop-none-pct-r30-nested-z200": {"pass":true,"tracking":0.02,"pressJump":0,"drop":0.02,"reload":0.05,"undo":true,"dropped":5,"controlDropped":8,"work":6.04,"frameP95":39.8}, + "crop-none-pct-r30-nested-z50": {"pass":false,"tracking":0.05,"pressJump":0,"drop":0.07,"reload":0.05,"undo":true,"dropped":12,"controlDropped":0,"work":6.17,"frameP95":51.6}, + "crop-none-pct-r30-root-z100": {"pass":false,"tracking":0.04,"pressJump":0,"drop":0.07,"reload":0,"undo":true,"dropped":22,"controlDropped":14,"work":6.15,"frameP95":67.3}, + "crop-none-pct-r30-root-z200": {"pass":true,"tracking":0.02,"pressJump":0,"drop":0.02,"reload":0.04,"undo":true,"dropped":4,"controlDropped":9,"work":5.57,"frameP95":47}, + "crop-none-pct-r30-root-z50": {"pass":false,"tracking":0.05,"pressJump":0,"drop":0.11,"reload":0.09,"undo":true,"dropped":20,"controlDropped":14,"work":5.71,"frameP95":64.61}, + "crop-none-px-r0-nested-z100": {"pass":false,"tracking":0.04,"pressJump":0,"drop":0.08,"reload":0,"undo":true,"dropped":3,"controlDropped":0,"work":5.57,"frameP95":35.3}, + "crop-none-px-r0-nested-z200": {"pass":false,"tracking":0.02,"pressJump":0,"drop":0.04,"reload":0.04,"undo":true,"dropped":9,"controlDropped":6,"work":6.2,"frameP95":55.1}, + "crop-none-px-r0-nested-z50": {"pass":true,"tracking":0.05,"pressJump":0,"drop":0.1,"reload":0.07,"undo":true,"dropped":0,"controlDropped":0,"work":5.46,"frameP95":17.5}, + "crop-none-px-r0-root-z100": {"pass":false,"tracking":0.04,"pressJump":0,"drop":0.06,"reload":0,"undo":true,"dropped":4,"controlDropped":1,"work":6.66,"frameP95":32}, + "crop-none-px-r0-root-z200": {"pass":false,"tracking":0.02,"pressJump":0,"drop":0.04,"reload":0.04,"undo":true,"dropped":6,"controlDropped":0,"work":6.32,"frameP95":47.71}, + "crop-none-px-r0-root-z50": {"pass":false,"tracking":0.05,"pressJump":0,"drop":0.13,"reload":0.05,"undo":true,"dropped":6,"controlDropped":0,"work":5.93,"frameP95":45.71}, + "crop-none-px-r30-nested-z100": {"pass":false,"tracking":0.04,"pressJump":0,"drop":0.07,"reload":0,"undo":true,"dropped":14,"controlDropped":11,"work":6.64,"frameP95":63.91}, + "crop-none-px-r30-nested-z200": {"pass":false,"tracking":0.02,"pressJump":0,"drop":0.04,"reload":0.04,"undo":true,"dropped":8,"controlDropped":4,"work":6.11,"frameP95":69.8}, + "crop-none-px-r30-nested-z50": {"pass":false,"tracking":0.05,"pressJump":0,"drop":0.14,"reload":0.07,"undo":true,"dropped":17,"controlDropped":9,"work":5.92,"frameP95":63.21}, + "crop-none-px-r30-root-z100": {"pass":false,"tracking":0.04,"pressJump":0,"drop":0.08,"reload":0,"undo":true,"dropped":17,"controlDropped":7,"work":6.33,"frameP95":58.91}, + "crop-none-px-r30-root-z200": {"pass":true,"tracking":0.02,"pressJump":0,"drop":0.02,"reload":0.03,"undo":true,"dropped":2,"controlDropped":10,"work":5.85,"frameP95":24.8}, + "crop-none-px-r30-root-z50": {"pass":true,"tracking":0.05,"pressJump":0,"drop":0.14,"reload":0.07,"undo":true,"dropped":8,"controlDropped":9,"work":6.12,"frameP95":39.3}, + "crop-tween-center-r0-nested-z100": {"pass":false,"tracking":0.04,"pressJump":0,"drop":0.03,"reload":0,"undo":true,"dropped":10,"controlDropped":9,"work":5.82,"frameP95":52.71}, + "crop-tween-center-r0-nested-z200": {"pass":false,"tracking":0.02,"pressJump":0,"drop":0.04,"reload":0.04,"undo":true,"dropped":20,"controlDropped":10,"work":6,"frameP95":56}, + "crop-tween-center-r0-nested-z50": {"pass":false,"tracking":0.05,"pressJump":0,"drop":0.13,"reload":0.07,"undo":true,"dropped":14,"controlDropped":10,"work":5.4,"frameP95":52}, + "crop-tween-center-r0-root-z100": {"pass":false,"tracking":0.04,"pressJump":0,"drop":0.03,"reload":0,"undo":false,"dropped":4,"controlDropped":12,"work":6.27,"frameP95":45.41}, + "crop-tween-center-r0-root-z200": {"pass":false,"tracking":0.02,"pressJump":0,"drop":0.04,"reload":0.04,"undo":false,"dropped":9,"controlDropped":6,"work":6.4,"frameP95":49.61}, + "crop-tween-center-r0-root-z50": {"pass":false,"tracking":0.05,"pressJump":0,"drop":0.13,"reload":0.07,"undo":false,"dropped":9,"controlDropped":9,"work":5.83,"frameP95":46.8}, + "crop-tween-center-r30-nested-z100": {"pass":false,"tracking":0.04,"pressJump":0,"drop":0.07,"reload":0,"undo":true,"dropped":19,"controlDropped":12,"work":5.49,"frameP95":62.8}, + "crop-tween-center-r30-nested-z200": {"pass":false,"tracking":0.02,"pressJump":0,"drop":0.02,"reload":0.03,"undo":true,"dropped":21,"controlDropped":14,"work":5.93,"frameP95":64.3}, + "crop-tween-center-r30-nested-z50": {"pass":false,"tracking":0.05,"pressJump":0,"drop":0.12,"reload":0.08,"undo":true,"dropped":15,"controlDropped":9,"work":5.8,"frameP95":60}, + "crop-tween-center-r30-root-z100": {"pass":false,"tracking":0.04,"pressJump":0,"drop":0.07,"reload":0,"undo":false,"dropped":17,"controlDropped":12,"work":5.69,"frameP95":57.6}, + "crop-tween-center-r30-root-z200": {"pass":false,"tracking":0.02,"pressJump":0,"drop":0.02,"reload":0.03,"undo":false,"dropped":18,"controlDropped":12,"work":5.71,"frameP95":59.1}, + "crop-tween-center-r30-root-z50": {"pass":false,"tracking":0.05,"pressJump":0,"drop":0.12,"reload":0.08,"undo":false,"dropped":13,"controlDropped":14,"work":5.93,"frameP95":55.41}, + "crop-tween-pct-r0-nested-z100": {"pass":false,"tracking":0.04,"pressJump":0,"drop":0.06,"reload":0,"undo":true,"dropped":25,"controlDropped":13,"work":5.76,"frameP95":66.9}, + "crop-tween-pct-r0-nested-z200": {"pass":false,"tracking":0.02,"pressJump":0,"drop":0.03,"reload":0.01,"undo":true,"dropped":17,"controlDropped":14,"work":5.97,"frameP95":55.6}, + "crop-tween-pct-r0-nested-z50": {"pass":false,"tracking":0.05,"pressJump":0,"drop":0.1,"reload":0.07,"undo":true,"dropped":26,"controlDropped":13,"work":5.65,"frameP95":59.1}, + "crop-tween-pct-r0-root-z100": {"pass":false,"tracking":0.04,"pressJump":0,"drop":0.03,"reload":0,"undo":false,"dropped":21,"controlDropped":15,"work":4.65,"frameP95":65.3}, + "crop-tween-pct-r0-root-z200": {"pass":false,"tracking":0.02,"pressJump":0,"drop":0.03,"reload":0.03,"undo":false,"dropped":18,"controlDropped":14,"work":6.51,"frameP95":65.3}, + "crop-tween-pct-r0-root-z50": {"pass":false,"tracking":0.05,"pressJump":0,"drop":0.1,"reload":0.05,"undo":false,"dropped":23,"controlDropped":15,"work":5.11,"frameP95":62.91}, + "crop-tween-pct-r30-nested-z100": {"pass":false,"tracking":0.04,"pressJump":0,"drop":0.06,"reload":0,"undo":true,"dropped":6,"controlDropped":5,"work":5.8,"frameP95":26.81}, + "crop-tween-pct-r30-nested-z200": {"pass":false,"tracking":0.02,"pressJump":0,"drop":0.02,"reload":0.03,"undo":true,"dropped":11,"controlDropped":4,"work":5.82,"frameP95":45.71}, + "crop-tween-pct-r30-nested-z50": {"pass":false,"tracking":0.05,"pressJump":0,"drop":0.08,"reload":0.06,"undo":true,"dropped":8,"controlDropped":3,"work":5.94,"frameP95":43.1}, + "crop-tween-pct-r30-root-z100": {"pass":false,"tracking":0.04,"pressJump":0,"drop":0.05,"reload":0,"undo":false,"dropped":8,"controlDropped":5,"work":6.08,"frameP95":58.6}, + "crop-tween-pct-r30-root-z200": {"pass":false,"tracking":0.02,"pressJump":0,"drop":0.02,"reload":0.03,"undo":false,"dropped":5,"controlDropped":4,"work":6.68,"frameP95":40.5}, + "crop-tween-pct-r30-root-z50": {"pass":false,"tracking":0.05,"pressJump":0,"drop":0.07,"reload":0.07,"undo":false,"dropped":8,"controlDropped":8,"work":5.51,"frameP95":52.61}, + "crop-tween-px-r0-nested-z100": {"pass":false,"tracking":0.04,"pressJump":0,"drop":0.09,"reload":0,"undo":true,"dropped":15,"controlDropped":11,"work":5.59,"frameP95":50.91}, + "crop-tween-px-r0-nested-z200": {"pass":false,"tracking":0.02,"pressJump":0,"drop":0.02,"reload":0.03,"undo":true,"dropped":7,"controlDropped":4,"work":6.16,"frameP95":49.91}, + "crop-tween-px-r0-nested-z50": {"pass":false,"tracking":0.05,"pressJump":0,"drop":0.07,"reload":0.07,"undo":true,"dropped":8,"controlDropped":5,"work":6.2,"frameP95":49.41}, + "crop-tween-px-r0-root-z100": {"pass":false,"tracking":0.04,"pressJump":0,"drop":0.07,"reload":0,"undo":false,"dropped":11,"controlDropped":1,"work":5.99,"frameP95":46.8}, + "crop-tween-px-r0-root-z200": {"pass":false,"tracking":0.02,"pressJump":0,"drop":0,"reload":0.02,"undo":false,"dropped":14,"controlDropped":7,"work":5.75,"frameP95":58.7}, + "crop-tween-px-r0-root-z50": {"pass":false,"tracking":0.05,"pressJump":0,"drop":0.07,"reload":0.05,"undo":false,"dropped":12,"controlDropped":8,"work":6.37,"frameP95":61.71}, + "crop-tween-px-r30-nested-z100": {"pass":false,"tracking":0.04,"pressJump":0,"drop":0.08,"reload":0,"undo":true,"dropped":9,"controlDropped":3,"work":5.74,"frameP95":41}, + "crop-tween-px-r30-nested-z200": {"pass":false,"tracking":0.02,"pressJump":0,"drop":0.02,"reload":0.05,"undo":true,"dropped":21,"controlDropped":10,"work":5.36,"frameP95":60.21}, + "crop-tween-px-r30-nested-z50": {"pass":false,"tracking":0.05,"pressJump":0,"drop":0.08,"reload":0.05,"undo":true,"dropped":15,"controlDropped":7,"work":5.67,"frameP95":58.41}, + "crop-tween-px-r30-root-z100": {"pass":false,"tracking":0.04,"pressJump":0,"drop":0.06,"reload":0,"undo":false,"dropped":13,"controlDropped":1,"work":5.66,"frameP95":54.41}, + "crop-tween-px-r30-root-z200": {"pass":false,"tracking":0.02,"pressJump":0,"drop":0.02,"reload":0.04,"undo":false,"dropped":6,"controlDropped":6,"work":5.5,"frameP95":40}, + "crop-tween-px-r30-root-z50": {"pass":false,"tracking":0.05,"pressJump":0,"drop":0.07,"reload":0.05,"undo":false,"dropped":12,"controlDropped":7,"work":6.06,"frameP95":53.5}, + "crop-tween-xpercent-r0-nested-z100": {"pass":false,"tracking":0.04,"pressJump":0,"drop":0.03,"reload":0,"undo":true,"dropped":5,"controlDropped":2,"work":5.78,"frameP95":38}, + "crop-tween-xpercent-r0-nested-z200": {"pass":false,"tracking":0.02,"pressJump":0,"drop":0.04,"reload":0.04,"undo":true,"dropped":8,"controlDropped":6,"work":5.81,"frameP95":54.21}, + "crop-tween-xpercent-r0-nested-z50": {"pass":false,"tracking":0.05,"pressJump":0,"drop":0.13,"reload":0.07,"undo":true,"dropped":5,"controlDropped":2,"work":5.16,"frameP95":51.5}, + "crop-tween-xpercent-r0-root-z100": {"pass":false,"tracking":0.04,"pressJump":0,"drop":0.03,"reload":0,"undo":true,"dropped":10,"controlDropped":6,"work":6.37,"frameP95":52.1}, + "crop-tween-xpercent-r0-root-z200": {"pass":true,"tracking":0.02,"pressJump":0,"drop":0.04,"reload":0.04,"undo":true,"dropped":5,"controlDropped":6,"work":6.83,"frameP95":34.71}, + "crop-tween-xpercent-r0-root-z50": {"pass":false,"tracking":0.05,"pressJump":0,"drop":0.13,"reload":0.07,"undo":true,"dropped":11,"controlDropped":9,"work":5.89,"frameP95":50.1}, + "crop-tween-xpercent-r30-nested-z100": {"pass":false,"tracking":0.04,"pressJump":0,"drop":0.07,"reload":0,"undo":true,"dropped":18,"controlDropped":15,"work":5.74,"frameP95":64.1}, + "crop-tween-xpercent-r30-nested-z200": {"pass":false,"tracking":0.02,"pressJump":0,"drop":0.02,"reload":0.03,"undo":true,"dropped":23,"controlDropped":12,"work":5.39,"frameP95":65.71}, + "crop-tween-xpercent-r30-nested-z50": {"pass":false,"tracking":0.05,"pressJump":0,"drop":0.12,"reload":0.08,"undo":true,"dropped":18,"controlDropped":8,"work":7.12,"frameP95":62.61}, + "crop-tween-xpercent-r30-root-z100": {"pass":false,"tracking":0.04,"pressJump":0,"drop":0.07,"reload":0,"undo":false,"dropped":2,"controlDropped":0,"work":4.98,"frameP95":17.6}, + "crop-tween-xpercent-r30-root-z200": {"pass":false,"tracking":0.02,"pressJump":0,"drop":0.02,"reload":0.03,"undo":false,"dropped":4,"controlDropped":2,"work":5.42,"frameP95":44.6}, + "crop-tween-xpercent-r30-root-z50": {"pass":false,"tracking":0.05,"pressJump":0,"drop":0.12,"reload":0.08,"undo":false,"dropped":7,"controlDropped":2,"work":6.8,"frameP95":40.91}, + "move-hold-center-r0-nested-z100": {"pass":false,"tracking":0.52,"pressJump":0,"drop":0,"reload":0,"undo":true,"dropped":15,"controlDropped":11,"work":7,"frameP95":58.1}, + "move-hold-center-r0-nested-z200": {"pass":false,"tracking":0.51,"pressJump":0,"drop":0,"reload":0.03,"undo":true,"dropped":19,"controlDropped":10,"work":7.35,"frameP95":60.21}, + "move-hold-center-r0-nested-z50": {"pass":false,"tracking":0.51,"pressJump":0,"drop":0,"reload":0.05,"undo":true,"dropped":17,"controlDropped":12,"work":6.75,"frameP95":56.1}, + "move-hold-center-r0-root-z100": {"pass":false,"tracking":0.52,"pressJump":0,"drop":0,"reload":0,"undo":true,"dropped":13,"controlDropped":5,"work":7.17,"frameP95":54.11}, + "move-hold-center-r0-root-z200": {"pass":false,"tracking":0.51,"pressJump":0,"drop":0,"reload":0.03,"undo":true,"dropped":15,"controlDropped":4,"work":8.57,"frameP95":66.61}, + "move-hold-center-r0-root-z50": {"pass":false,"tracking":0.51,"pressJump":0,"drop":0,"reload":0.05,"undo":true,"dropped":13,"controlDropped":9,"work":7.03,"frameP95":64.6}, + "move-hold-center-r30-nested-z100": {"pass":false,"tracking":0.51,"pressJump":0,"drop":0,"reload":0,"undo":true,"dropped":23,"controlDropped":13,"work":7.68,"frameP95":67.9}, + "move-hold-center-r30-nested-z200": {"pass":false,"tracking":0.5,"pressJump":0,"drop":0,"reload":0.04,"undo":true,"dropped":17,"controlDropped":11,"work":9.47,"frameP95":63.3}, + "move-hold-center-r30-nested-z50": {"pass":false,"tracking":0.56,"pressJump":0,"drop":0,"reload":0.07,"undo":true,"dropped":19,"controlDropped":15,"work":8.62,"frameP95":67}, + "move-hold-center-r30-root-z100": {"pass":false,"tracking":0.51,"pressJump":0,"drop":0,"reload":0,"undo":true,"dropped":23,"controlDropped":11,"work":7.15,"frameP95":71.91}, + "move-hold-center-r30-root-z200": {"pass":false,"tracking":0.5,"pressJump":0,"drop":0,"reload":0.04,"undo":true,"dropped":22,"controlDropped":14,"work":8.54,"frameP95":65.41}, + "move-hold-center-r30-root-z50": {"pass":false,"tracking":0.56,"pressJump":0,"drop":0,"reload":0.07,"undo":true,"dropped":17,"controlDropped":11,"work":7.52,"frameP95":64.81}, + "move-hold-pct-r0-nested-z100": {"pass":false,"tracking":0.52,"pressJump":0,"drop":0,"reload":0,"undo":true,"dropped":2,"controlDropped":2,"work":6.05,"frameP95":17.91}, + "move-hold-pct-r0-nested-z200": {"pass":false,"tracking":0.51,"pressJump":0,"drop":0,"reload":0.03,"undo":true,"dropped":7,"controlDropped":1,"work":8,"frameP95":41.21}, + "move-hold-pct-r0-nested-z50": {"pass":false,"tracking":0.54,"pressJump":0,"drop":0,"reload":0.07,"undo":true,"dropped":8,"controlDropped":1,"work":7.81,"frameP95":53.81}, + "move-hold-pct-r0-root-z100": {"pass":false,"tracking":0.51,"pressJump":0,"drop":0,"reload":0,"undo":true,"dropped":8,"controlDropped":1,"work":6.88,"frameP95":54}, + "move-hold-pct-r0-root-z200": {"pass":false,"tracking":0.5,"pressJump":0,"drop":0,"reload":0.01,"undo":true,"dropped":5,"controlDropped":1,"work":8.18,"frameP95":52.21}, + "move-hold-pct-r0-root-z50": {"pass":false,"tracking":0.53,"pressJump":0,"drop":0,"reload":0.05,"undo":true,"dropped":0,"controlDropped":0,"work":6.9,"frameP95":17.5}, + "move-hold-pct-r30-nested-z100": {"pass":false,"tracking":0.51,"pressJump":0,"drop":0,"reload":0,"undo":true,"dropped":10,"controlDropped":3,"work":8.23,"frameP95":49.1}, + "move-hold-pct-r30-nested-z200": {"pass":false,"tracking":0.52,"pressJump":0,"drop":0,"reload":0.03,"undo":true,"dropped":12,"controlDropped":0,"work":6.91,"frameP95":50.41}, + "move-hold-pct-r30-nested-z50": {"pass":false,"tracking":0.53,"pressJump":0,"drop":0,"reload":0.11,"undo":true,"dropped":7,"controlDropped":3,"work":7.75,"frameP95":42.7}, + "move-hold-pct-r30-root-z100": {"pass":false,"tracking":0.52,"pressJump":0,"drop":0,"reload":0,"undo":true,"dropped":6,"controlDropped":5,"work":6.22,"frameP95":42.6}, + "move-hold-pct-r30-root-z200": {"pass":false,"tracking":0.52,"pressJump":0,"drop":0,"reload":0.04,"undo":true,"dropped":9,"controlDropped":5,"work":7.57,"frameP95":45.5}, + "move-hold-pct-r30-root-z50": {"pass":false,"tracking":0.53,"pressJump":0,"drop":0,"reload":0.1,"undo":true,"dropped":12,"controlDropped":2,"work":7.81,"frameP95":59.4}, + "move-hold-px-r0-nested-z100": {"pass":false,"tracking":0.52,"pressJump":0,"drop":0,"reload":0,"undo":true,"dropped":9,"controlDropped":11,"work":7.99,"frameP95":57}, + "move-hold-px-r0-nested-z200": {"pass":false,"tracking":0.51,"pressJump":0,"drop":0,"reload":0.03,"undo":true,"dropped":15,"controlDropped":11,"work":8.84,"frameP95":61.1}, + "move-hold-px-r0-nested-z50": {"pass":false,"tracking":0.54,"pressJump":0,"drop":0,"reload":0.07,"undo":true,"dropped":12,"controlDropped":12,"work":8.89,"frameP95":57.41}, + "move-hold-px-r0-root-z100": {"pass":false,"tracking":0.51,"pressJump":0,"drop":0,"reload":0,"undo":true,"dropped":19,"controlDropped":13,"work":6.91,"frameP95":59.5}, + "move-hold-px-r0-root-z200": {"pass":false,"tracking":0.5,"pressJump":0,"drop":0,"reload":0.01,"undo":true,"dropped":15,"controlDropped":11,"work":8.57,"frameP95":67.11}, + "move-hold-px-r0-root-z50": {"pass":false,"tracking":0.53,"pressJump":0,"drop":0,"reload":0.05,"undo":true,"dropped":14,"controlDropped":14,"work":9.39,"frameP95":66.41}, + "move-hold-px-r30-nested-z100": {"pass":false,"tracking":0.51,"pressJump":0,"drop":0,"reload":0,"undo":true,"dropped":10,"controlDropped":11,"work":6.89,"frameP95":53.3}, + "move-hold-px-r30-nested-z200": {"pass":false,"tracking":0.52,"pressJump":0,"drop":0,"reload":0.03,"undo":true,"dropped":15,"controlDropped":3,"work":8.38,"frameP95":57.21}, + "move-hold-px-r30-nested-z50": {"pass":false,"tracking":0.53,"pressJump":0,"drop":0,"reload":0.11,"undo":true,"dropped":13,"controlDropped":2,"work":8.98,"frameP95":64.11}, + "move-hold-px-r30-root-z100": {"pass":false,"tracking":0.52,"pressJump":0,"drop":0,"reload":0,"undo":true,"dropped":12,"controlDropped":8,"work":8.02,"frameP95":58.21}, + "move-hold-px-r30-root-z200": {"pass":false,"tracking":0.52,"pressJump":0,"drop":0,"reload":0.04,"undo":true,"dropped":11,"controlDropped":10,"work":6.42,"frameP95":51}, + "move-hold-px-r30-root-z50": {"pass":false,"tracking":0.53,"pressJump":0,"drop":0,"reload":0.1,"undo":true,"dropped":15,"controlDropped":12,"work":8.41,"frameP95":64.6}, + "move-hold-xpercent-r0-nested-z100": {"pass":false,"tracking":0.52,"pressJump":0,"drop":0,"reload":0,"undo":true,"dropped":18,"controlDropped":10,"work":6.84,"frameP95":64.8}, + "move-hold-xpercent-r0-nested-z200": {"pass":false,"tracking":0.51,"pressJump":0,"drop":0,"reload":0.03,"undo":true,"dropped":12,"controlDropped":12,"work":8.82,"frameP95":63.3}, + "move-hold-xpercent-r0-nested-z50": {"pass":false,"tracking":0.51,"pressJump":0,"drop":0,"reload":0.05,"undo":true,"dropped":13,"controlDropped":1,"work":8.67,"frameP95":59.6}, + "move-hold-xpercent-r0-root-z100": {"pass":false,"tracking":0.52,"pressJump":0,"drop":0,"reload":0,"undo":true,"dropped":10,"controlDropped":9,"work":7.88,"frameP95":57.8}, + "move-hold-xpercent-r0-root-z200": {"pass":false,"tracking":0.51,"pressJump":0,"drop":0,"reload":0.03,"undo":true,"dropped":12,"controlDropped":1,"work":9.03,"frameP95":59.2}, + "move-hold-xpercent-r0-root-z50": {"pass":false,"tracking":0.51,"pressJump":0,"drop":0,"reload":0.05,"undo":true,"dropped":14,"controlDropped":10,"work":7.76,"frameP95":56.5}, + "move-hold-xpercent-r30-nested-z100": {"pass":false,"tracking":0.51,"pressJump":0,"drop":0,"reload":0,"undo":true,"dropped":16,"controlDropped":13,"work":7.27,"frameP95":60.1}, + "move-hold-xpercent-r30-nested-z200": {"pass":false,"tracking":0.5,"pressJump":0,"drop":0,"reload":0.04,"undo":true,"dropped":17,"controlDropped":11,"work":7.03,"frameP95":58.8}, + "move-hold-xpercent-r30-nested-z50": {"pass":false,"tracking":0.56,"pressJump":0,"drop":0,"reload":0.07,"undo":true,"dropped":19,"controlDropped":11,"work":8.44,"frameP95":66.2}, + "move-hold-xpercent-r30-root-z100": {"pass":false,"tracking":0.51,"pressJump":0,"drop":0,"reload":0,"undo":true,"dropped":12,"controlDropped":6,"work":6.89,"frameP95":61}, + "move-hold-xpercent-r30-root-z200": {"pass":false,"tracking":0.5,"pressJump":0,"drop":0,"reload":0.04,"undo":true,"dropped":10,"controlDropped":5,"work":7.42,"frameP95":52.71}, + "move-hold-xpercent-r30-root-z50": {"pass":false,"tracking":0.56,"pressJump":0,"drop":0,"reload":0.07,"undo":true,"dropped":13,"controlDropped":11,"work":7.05,"frameP95":64.91}, + "move-none-center-r0-nested-z100": {"pass":false,"tracking":0.52,"pressJump":0,"drop":108.19,"reload":0,"undo":true,"dropped":15,"controlDropped":10,"work":8.93,"frameP95":65}, + "move-none-center-r0-nested-z200": {"pass":false,"tracking":0.51,"pressJump":0,"drop":108.17,"reload":0.03,"undo":true,"dropped":15,"controlDropped":9,"work":7,"frameP95":64.71}, + "move-none-center-r0-nested-z50": {"pass":false,"tracking":0.53,"pressJump":0,"drop":108.23,"reload":0.05,"undo":true,"dropped":12,"controlDropped":11,"work":7.39,"frameP95":63}, + "move-none-center-r0-root-z100": {"pass":false,"tracking":0.52,"pressJump":0,"drop":0,"reload":0,"undo":true,"dropped":11,"controlDropped":10,"work":7.05,"frameP95":57.9}, + "move-none-center-r0-root-z200": {"pass":false,"tracking":0.51,"pressJump":0,"drop":0,"reload":0.03,"undo":true,"dropped":13,"controlDropped":5,"work":7.09,"frameP95":58.91}, + "move-none-center-r0-root-z50": {"pass":false,"tracking":0.53,"pressJump":0,"drop":0,"reload":0.07,"undo":true,"dropped":7,"controlDropped":7,"work":7.23,"frameP95":53.31}, + "move-none-center-r30-nested-z100": {"pass":false,"tracking":0.51,"pressJump":0,"drop":108.19,"reload":0,"undo":true,"dropped":11,"controlDropped":2,"work":8.08,"frameP95":56.41}, + "move-none-center-r30-nested-z200": {"pass":false,"tracking":0.52,"pressJump":0,"drop":108.18,"reload":0.04,"undo":true,"dropped":12,"controlDropped":9,"work":8.97,"frameP95":60.21}, + "move-none-center-r30-nested-z50": {"pass":false,"tracking":0.53,"pressJump":0,"drop":108.15,"reload":0.07,"undo":true,"dropped":7,"controlDropped":4,"work":7.18,"frameP95":55}, + "move-none-center-r30-root-z100": {"pass":false,"tracking":0.51,"pressJump":0,"drop":0,"reload":0,"undo":true,"dropped":10,"controlDropped":11,"work":7.3,"frameP95":53.5}, + "move-none-center-r30-root-z200": {"pass":false,"tracking":0.52,"pressJump":0,"drop":0,"reload":0.05,"undo":true,"dropped":11,"controlDropped":6,"work":8.28,"frameP95":54.71}, + "move-none-center-r30-root-z50": {"pass":false,"tracking":0.53,"pressJump":0,"drop":0,"reload":0.11,"undo":true,"dropped":14,"controlDropped":11,"work":8.36,"frameP95":58.3}, + "move-none-pct-r0-nested-z100": {"pass":false,"tracking":0.53,"pressJump":0,"drop":108.15,"reload":0,"undo":true,"dropped":15,"controlDropped":8,"work":8.31,"frameP95":61.8}, + "move-none-pct-r0-nested-z200": {"pass":false,"tracking":0.51,"pressJump":0,"drop":108.19,"reload":0.02,"undo":true,"dropped":17,"controlDropped":14,"work":7.51,"frameP95":61}, + "move-none-pct-r0-nested-z50": {"pass":false,"tracking":0.51,"pressJump":0,"drop":108.23,"reload":0.05,"undo":true,"dropped":13,"controlDropped":5,"work":8.97,"frameP95":55.5}, + "move-none-pct-r0-root-z100": {"pass":false,"tracking":0.53,"pressJump":0,"drop":72.11,"reload":0,"undo":true,"dropped":12,"controlDropped":12,"work":7.83,"frameP95":67.91}, + "move-none-pct-r0-root-z200": {"pass":false,"tracking":0.52,"pressJump":0,"drop":72.14,"reload":0.04,"undo":true,"dropped":12,"controlDropped":5,"work":7.67,"frameP95":54.5}, + "move-none-pct-r0-root-z50": {"pass":false,"tracking":0.52,"pressJump":0,"drop":72.16,"reload":0.07,"undo":true,"dropped":12,"controlDropped":12,"work":7.33,"frameP95":52}, + "move-none-pct-r30-nested-z100": {"pass":false,"tracking":0.51,"pressJump":0,"drop":108.23,"reload":0,"undo":true,"dropped":9,"controlDropped":6,"work":8.45,"frameP95":54}, + "move-none-pct-r30-nested-z200": {"pass":false,"tracking":0.51,"pressJump":0,"drop":108.16,"reload":0.06,"undo":true,"dropped":12,"controlDropped":9,"work":8.12,"frameP95":55.81}, + "move-none-pct-r30-nested-z50": {"pass":false,"tracking":0.52,"pressJump":0,"drop":108.23,"reload":0.05,"undo":true,"dropped":7,"controlDropped":7,"work":6.43,"frameP95":50.5}, + "move-none-pct-r30-root-z100": {"pass":false,"tracking":0.51,"pressJump":0,"drop":72.14,"reload":0,"undo":true,"dropped":21,"controlDropped":11,"work":9.1,"frameP95":66.4}, + "move-none-pct-r30-root-z200": {"pass":false,"tracking":0.5,"pressJump":0,"drop":72.11,"reload":0.04,"undo":true,"dropped":12,"controlDropped":9,"work":8.86,"frameP95":65.4}, + "move-none-pct-r30-root-z50": {"pass":false,"tracking":0.53,"pressJump":0,"drop":72.11,"reload":0.07,"undo":true,"dropped":14,"controlDropped":11,"work":8.82,"frameP95":62}, + "move-none-px-r0-nested-z100": {"pass":false,"tracking":0.52,"pressJump":0,"drop":108.19,"reload":0,"undo":true,"dropped":5,"controlDropped":0,"work":8.17,"frameP95":34.9}, + "move-none-px-r0-nested-z200": {"pass":false,"tracking":0.51,"pressJump":0,"drop":108.17,"reload":0.04,"undo":true,"dropped":13,"controlDropped":11,"work":8.29,"frameP95":69.9}, + "move-none-px-r0-nested-z50": {"pass":false,"tracking":0.53,"pressJump":0,"drop":108.23,"reload":0.07,"undo":true,"dropped":9,"controlDropped":7,"work":7.67,"frameP95":50.5}, + "move-none-px-r0-root-z100": {"pass":false,"tracking":0.51,"pressJump":0,"drop":50.03,"reload":0,"undo":true,"dropped":8,"controlDropped":2,"work":8.56,"frameP95":46.91}, + "move-none-px-r0-root-z200": {"pass":false,"tracking":0.5,"pressJump":0,"drop":50.01,"reload":0.04,"undo":true,"dropped":6,"controlDropped":6,"work":7.79,"frameP95":33.91}, + "move-none-px-r0-root-z50": {"pass":false,"tracking":0.53,"pressJump":0,"drop":50.09,"reload":0.07,"undo":true,"dropped":6,"controlDropped":0,"work":7.05,"frameP95":33.21}, + "move-none-px-r30-nested-z100": {"pass":false,"tracking":0.51,"pressJump":0,"drop":108.19,"reload":0,"undo":true,"dropped":12,"controlDropped":5,"work":8.44,"frameP95":61.5}, + "move-none-px-r30-nested-z200": {"pass":false,"tracking":0.52,"pressJump":0,"drop":108.2,"reload":0.05,"undo":true,"dropped":14,"controlDropped":2,"work":7.79,"frameP95":54}, + "move-none-px-r30-nested-z50": {"pass":false,"tracking":0.53,"pressJump":0,"drop":108.23,"reload":0.07,"undo":true,"dropped":12,"controlDropped":2,"work":6.95,"frameP95":59.8}, + "move-none-px-r30-root-z100": {"pass":false,"tracking":0.52,"pressJump":0,"drop":50.03,"reload":0,"undo":true,"dropped":11,"controlDropped":7,"work":8.19,"frameP95":57.61}, + "move-none-px-r30-root-z200": {"pass":false,"tracking":0.52,"pressJump":0,"drop":50.01,"reload":0.04,"undo":true,"dropped":13,"controlDropped":0,"work":6.89,"frameP95":59.8}, + "move-none-px-r30-root-z50": {"pass":false,"tracking":0.53,"pressJump":0,"drop":50.04,"reload":0.07,"undo":true,"dropped":9,"controlDropped":3,"work":7.37,"frameP95":54.4}, + "move-tween-center-r0-nested-z100": {"pass":false,"tracking":0.51,"pressJump":0,"drop":0,"reload":0,"undo":true,"dropped":12,"controlDropped":7,"work":8.33,"frameP95":54}, + "move-tween-center-r0-nested-z200": {"pass":false,"tracking":0.5,"pressJump":0,"drop":0,"reload":0.03,"undo":true,"dropped":13,"controlDropped":3,"work":7.87,"frameP95":54.61}, + "move-tween-center-r0-nested-z50": {"pass":false,"tracking":0.53,"pressJump":0,"drop":0,"reload":0.07,"undo":true,"dropped":12,"controlDropped":10,"work":7.3,"frameP95":53.6}, + "move-tween-center-r0-root-z100": {"pass":false,"tracking":0.51,"pressJump":0,"drop":0,"reload":0,"undo":true,"dropped":13,"controlDropped":2,"work":7.1,"frameP95":48.1}, + "move-tween-center-r0-root-z200": {"pass":false,"tracking":0.5,"pressJump":0,"drop":0,"reload":0.03,"undo":true,"dropped":15,"controlDropped":1,"work":7.79,"frameP95":65}, + "move-tween-center-r0-root-z50": {"pass":false,"tracking":0.53,"pressJump":0,"drop":0,"reload":0.07,"undo":true,"dropped":7,"controlDropped":8,"work":6.42,"frameP95":46}, + "move-tween-center-r30-nested-z100": {"pass":false,"tracking":0.52,"pressJump":0,"drop":0,"reload":0,"undo":true,"dropped":18,"controlDropped":11,"work":8.17,"frameP95":66.1}, + "move-tween-center-r30-nested-z200": {"pass":false,"tracking":0.52,"pressJump":0,"drop":0,"reload":0.03,"undo":true,"dropped":15,"controlDropped":13,"work":8.45,"frameP95":63.3}, + "move-tween-center-r30-nested-z50": {"pass":false,"tracking":0.53,"pressJump":0,"drop":0,"reload":0.1,"undo":true,"dropped":17,"controlDropped":11,"work":8.49,"frameP95":61.6}, + "move-tween-center-r30-root-z100": {"pass":false,"tracking":0.52,"pressJump":0,"drop":0,"reload":0,"undo":true,"dropped":14,"controlDropped":11,"work":7.44,"frameP95":60.11}, + "move-tween-center-r30-root-z200": {"pass":false,"tracking":0.52,"pressJump":0,"drop":0,"reload":0.03,"undo":true,"dropped":14,"controlDropped":11,"work":8.66,"frameP95":61.1}, + "move-tween-center-r30-root-z50": {"pass":false,"tracking":0.53,"pressJump":0,"drop":0,"reload":0.1,"undo":true,"dropped":13,"controlDropped":13,"work":7.49,"frameP95":59.3}, + "move-tween-pct-r0-nested-z100": {"pass":false,"tracking":0.51,"pressJump":0,"drop":0,"reload":0,"undo":true,"dropped":20,"controlDropped":12,"work":7.98,"frameP95":64.4}, + "move-tween-pct-r0-nested-z200": {"pass":false,"tracking":0.52,"pressJump":0,"drop":0,"reload":0.03,"undo":true,"dropped":21,"controlDropped":10,"work":8.04,"frameP95":68.5}, + "move-tween-pct-r0-nested-z50": {"pass":false,"tracking":0.54,"pressJump":0,"drop":0,"reload":0.07,"undo":true,"dropped":16,"controlDropped":13,"work":8.14,"frameP95":67}, + "move-tween-pct-r0-root-z100": {"pass":false,"tracking":0.51,"pressJump":0,"drop":0,"reload":0,"undo":true,"dropped":20,"controlDropped":11,"work":7.39,"frameP95":65.9}, + "move-tween-pct-r0-root-z200": {"pass":false,"tracking":0.52,"pressJump":0,"drop":0,"reload":0.02,"undo":true,"dropped":14,"controlDropped":14,"work":8.12,"frameP95":64.8}, + "move-tween-pct-r0-root-z50": {"pass":false,"tracking":0.54,"pressJump":0,"drop":0,"reload":0.05,"undo":true,"dropped":18,"controlDropped":12,"work":7.99,"frameP95":62.41}, + "move-tween-pct-r30-nested-z100": {"pass":false,"tracking":0.51,"pressJump":0,"drop":0,"reload":0,"undo":true,"dropped":6,"controlDropped":3,"work":6.98,"frameP95":27}, + "move-tween-pct-r30-nested-z200": {"pass":false,"tracking":0.5,"pressJump":0,"drop":0,"reload":0.04,"undo":true,"dropped":8,"controlDropped":2,"work":7.17,"frameP95":59.91}, + "move-tween-pct-r30-nested-z50": {"pass":false,"tracking":0.56,"pressJump":0,"drop":0,"reload":0.11,"undo":true,"dropped":10,"controlDropped":2,"work":7.14,"frameP95":59.3}, + "move-tween-pct-r30-root-z100": {"pass":false,"tracking":0.51,"pressJump":0,"drop":0,"reload":0,"undo":true,"dropped":5,"controlDropped":2,"work":7.83,"frameP95":41}, + "move-tween-pct-r30-root-z200": {"pass":false,"tracking":0.5,"pressJump":0,"drop":0,"reload":0.03,"undo":true,"dropped":8,"controlDropped":2,"work":8.37,"frameP95":42.6}, + "move-tween-pct-r30-root-z50": {"pass":false,"tracking":0.56,"pressJump":0,"drop":0,"reload":0.07,"undo":true,"dropped":12,"controlDropped":6,"work":7.9,"frameP95":55.4}, + "move-tween-px-r0-nested-z100": {"pass":false,"tracking":0.75,"pressJump":0,"drop":0,"reload":0,"undo":true,"dropped":12,"controlDropped":10,"work":7.33,"frameP95":51.1}, + "move-tween-px-r0-nested-z200": {"pass":false,"tracking":0.72,"pressJump":0,"drop":0,"reload":0.04,"undo":true,"dropped":9,"controlDropped":9,"work":8.06,"frameP95":51.8}, + "move-tween-px-r0-nested-z50": {"pass":false,"tracking":0.76,"pressJump":0,"drop":0,"reload":0.07,"undo":true,"dropped":11,"controlDropped":3,"work":8.35,"frameP95":48.8}, + "move-tween-px-r0-root-z100": {"pass":false,"tracking":0.75,"pressJump":0,"drop":0,"reload":0,"undo":true,"dropped":9,"controlDropped":7,"work":7.97,"frameP95":46.4}, + "move-tween-px-r0-root-z200": {"pass":false,"tracking":0.72,"pressJump":0,"drop":0,"reload":0.04,"undo":true,"dropped":10,"controlDropped":0,"work":7.69,"frameP95":46.9}, + "move-tween-px-r0-root-z50": {"pass":false,"tracking":0.72,"pressJump":0,"drop":0,"reload":0.07,"undo":true,"dropped":8,"controlDropped":5,"work":8.29,"frameP95":44.91}, + "move-tween-px-r30-nested-z100": {"pass":false,"tracking":0.71,"pressJump":0,"drop":0,"reload":0,"undo":true,"dropped":11,"controlDropped":5,"work":7.72,"frameP95":57.1}, + "move-tween-px-r30-nested-z200": {"pass":false,"tracking":0.72,"pressJump":0,"drop":0,"reload":0.04,"undo":true,"dropped":12,"controlDropped":7,"work":7.97,"frameP95":58.41}, + "move-tween-px-r30-nested-z50": {"pass":false,"tracking":0.73,"pressJump":0,"drop":0,"reload":0.1,"undo":true,"dropped":8,"controlDropped":1,"work":7.16,"frameP95":56.6}, + "move-tween-px-r30-root-z100": {"pass":false,"tracking":0.71,"pressJump":0,"drop":0,"reload":0,"undo":true,"dropped":11,"controlDropped":1,"work":7.99,"frameP95":54.5}, + "move-tween-px-r30-root-z200": {"pass":false,"tracking":0.71,"pressJump":0,"drop":0,"reload":0.03,"undo":true,"dropped":9,"controlDropped":6,"work":7.12,"frameP95":54.41}, + "move-tween-px-r30-root-z50": {"pass":false,"tracking":0.73,"pressJump":0,"drop":0,"reload":0.07,"undo":true,"dropped":10,"controlDropped":4,"work":8.01,"frameP95":52.71}, + "move-tween-xpercent-r0-nested-z100": {"pass":false,"tracking":0.51,"pressJump":0,"drop":144.24,"reload":0,"undo":true,"dropped":9,"controlDropped":6,"work":8.15,"frameP95":53.8}, + "move-tween-xpercent-r0-nested-z200": {"pass":false,"tracking":0.5,"pressJump":0,"drop":144.22,"reload":0.04,"undo":true,"dropped":8,"controlDropped":2,"work":7.98,"frameP95":51.8}, + "move-tween-xpercent-r0-nested-z50": {"pass":false,"tracking":0.53,"pressJump":0,"drop":144.22,"reload":0.05,"undo":true,"dropped":9,"controlDropped":4,"work":7.62,"frameP95":52.61}, + "move-tween-xpercent-r0-root-z100": {"pass":false,"tracking":0.51,"pressJump":0,"drop":144.24,"reload":0,"undo":true,"dropped":12,"controlDropped":6,"work":8.11,"frameP95":55.2}, + "move-tween-xpercent-r0-root-z200": {"pass":false,"tracking":0.5,"pressJump":0,"drop":144.22,"reload":0.04,"undo":true,"dropped":10,"controlDropped":1,"work":7.68,"frameP95":50.3}, + "move-tween-xpercent-r0-root-z50": {"pass":false,"tracking":0.53,"pressJump":0,"drop":144.22,"reload":0.05,"undo":true,"dropped":15,"controlDropped":12,"work":9.2,"frameP95":65.71}, + "move-tween-xpercent-r30-nested-z100": {"pass":false,"tracking":0.52,"pressJump":0,"drop":144.24,"reload":0,"undo":true,"dropped":17,"controlDropped":10,"work":6.74,"frameP95":61.91}, + "move-tween-xpercent-r30-nested-z200": {"pass":false,"tracking":0.52,"pressJump":0,"drop":144.22,"reload":0.03,"undo":true,"dropped":16,"controlDropped":12,"work":10.79,"frameP95":65.91}, + "move-tween-xpercent-r30-nested-z50": {"pass":false,"tracking":0.53,"pressJump":0,"drop":144.27,"reload":0.07,"undo":true,"dropped":13,"controlDropped":2,"work":7.28,"frameP95":61.5}, + "move-tween-xpercent-r30-root-z100": {"pass":false,"tracking":0.52,"pressJump":0,"drop":144.24,"reload":0,"undo":true,"dropped":8,"controlDropped":2,"work":7.97,"frameP95":56.61}, + "move-tween-xpercent-r30-root-z200": {"pass":false,"tracking":0.52,"pressJump":0,"drop":144.22,"reload":0.03,"undo":true,"dropped":11,"controlDropped":0,"work":8.35,"frameP95":58.8}, + "move-tween-xpercent-r30-root-z50": {"pass":false,"tracking":0.53,"pressJump":0,"drop":144.27,"reload":0.07,"undo":true,"dropped":13,"controlDropped":1,"work":7.32,"frameP95":58.81}, + "nudge-hold-center-r0-nested-z100": {"pass":false,"tracking":0.01,"pressJump":null,"drop":0,"reload":0,"undo":true,"dropped":3,"controlDropped":2,"work":13.14,"frameP95":59.4}, + "nudge-hold-center-r0-nested-z200": {"pass":false,"tracking":0.01,"pressJump":null,"drop":0,"reload":0.03,"undo":true,"dropped":5,"controlDropped":2,"work":7.98,"frameP95":99.21}, + "nudge-hold-center-r0-nested-z50": {"pass":false,"tracking":0.01,"pressJump":null,"drop":0,"reload":0.05,"undo":true,"dropped":3,"controlDropped":2,"work":7.21,"frameP95":56.3}, + "nudge-hold-center-r0-root-z100": {"pass":false,"tracking":0.01,"pressJump":null,"drop":0,"reload":0,"undo":true,"dropped":3,"controlDropped":0,"work":7.55,"frameP95":55.9}, + "nudge-hold-center-r0-root-z200": {"pass":false,"tracking":0.01,"pressJump":null,"drop":0,"reload":0.03,"undo":true,"dropped":2,"controlDropped":3,"work":10.52,"frameP95":57}, + "nudge-hold-center-r0-root-z50": {"pass":false,"tracking":0.01,"pressJump":null,"drop":0,"reload":0.05,"undo":true,"dropped":2,"controlDropped":1,"work":10.44,"frameP95":49.61}, + "nudge-hold-center-r30-nested-z100": {"pass":false,"tracking":0.01,"pressJump":null,"drop":0,"reload":0,"undo":true,"dropped":3,"controlDropped":2,"work":9.95,"frameP95":71.3}, + "nudge-hold-center-r30-nested-z200": {"pass":false,"tracking":0.02,"pressJump":null,"drop":0,"reload":0.03,"undo":true,"dropped":3,"controlDropped":2,"work":8.43,"frameP95":52.3}, + "nudge-hold-center-r30-nested-z50": {"pass":false,"tracking":0.06,"pressJump":null,"drop":0,"reload":0.11,"undo":true,"dropped":2,"controlDropped":2,"work":9.12,"frameP95":58.2}, + "nudge-hold-center-r30-root-z100": {"pass":false,"tracking":0.01,"pressJump":null,"drop":0,"reload":0,"undo":true,"dropped":3,"controlDropped":2,"work":6.6,"frameP95":68.31}, + "nudge-hold-center-r30-root-z200": {"pass":false,"tracking":0.02,"pressJump":null,"drop":0,"reload":0.03,"undo":true,"dropped":7,"controlDropped":4,"work":11.98,"frameP95":78.91}, + "nudge-hold-center-r30-root-z50": {"pass":false,"tracking":0.06,"pressJump":null,"drop":0,"reload":0.11,"undo":true,"dropped":5,"controlDropped":2,"work":6.43,"frameP95":63.9}, + "nudge-hold-pct-r0-nested-z100": {"pass":false,"tracking":0.02,"pressJump":null,"drop":0,"reload":0,"undo":true,"dropped":0,"controlDropped":1,"work":10.02,"frameP95":17.8}, + "nudge-hold-pct-r0-nested-z200": {"pass":true,"tracking":0.01,"pressJump":null,"drop":0,"reload":0.03,"undo":true,"dropped":0,"controlDropped":0,"work":7.79,"frameP95":18}, + "nudge-hold-pct-r0-nested-z50": {"pass":false,"tracking":0.01,"pressJump":null,"drop":0,"reload":0.07,"undo":true,"dropped":2,"controlDropped":0,"work":6.66,"frameP95":56.71}, + "nudge-hold-pct-r0-root-z100": {"pass":false,"tracking":0.01,"pressJump":null,"drop":0,"reload":0,"undo":true,"dropped":3,"controlDropped":2,"work":10.28,"frameP95":54.4}, + "nudge-hold-pct-r0-root-z200": {"pass":false,"tracking":0.01,"pressJump":null,"drop":0,"reload":0.03,"undo":true,"dropped":2,"controlDropped":0,"work":9.31,"frameP95":54.11}, + "nudge-hold-pct-r0-root-z50": {"pass":true,"tracking":0.01,"pressJump":null,"drop":0,"reload":0.05,"undo":true,"dropped":0,"controlDropped":1,"work":7.33,"frameP95":18.11}, + "nudge-hold-pct-r30-nested-z100": {"pass":true,"tracking":0.01,"pressJump":null,"drop":0,"reload":0,"undo":true,"dropped":0,"controlDropped":0,"work":7.27,"frameP95":18}, + "nudge-hold-pct-r30-nested-z200": {"pass":false,"tracking":0.01,"pressJump":null,"drop":0,"reload":0.03,"undo":true,"dropped":2,"controlDropped":2,"work":8.44,"frameP95":54.6}, + "nudge-hold-pct-r30-nested-z50": {"pass":false,"tracking":0.01,"pressJump":null,"drop":0,"reload":0.07,"undo":true,"dropped":1,"controlDropped":0,"work":5.93,"frameP95":31.5}, + "nudge-hold-pct-r30-root-z100": {"pass":false,"tracking":0.02,"pressJump":null,"drop":0,"reload":0,"undo":true,"dropped":1,"controlDropped":0,"work":7.21,"frameP95":25.6}, + "nudge-hold-pct-r30-root-z200": {"pass":false,"tracking":0.01,"pressJump":null,"drop":0,"reload":0.04,"undo":true,"dropped":1,"controlDropped":0,"work":7.52,"frameP95":31.5}, + "nudge-hold-pct-r30-root-z50": {"pass":false,"tracking":0.01,"pressJump":null,"drop":0,"reload":0.07,"undo":true,"dropped":3,"controlDropped":2,"work":6.17,"frameP95":60.11}, + "nudge-hold-px-r0-nested-z100": {"pass":false,"tracking":0.02,"pressJump":null,"drop":0,"reload":0,"undo":true,"dropped":3,"controlDropped":2,"work":7.87,"frameP95":56.3}, + "nudge-hold-px-r0-nested-z200": {"pass":false,"tracking":0.01,"pressJump":null,"drop":0,"reload":0.03,"undo":true,"dropped":3,"controlDropped":2,"work":9.17,"frameP95":65.41}, + "nudge-hold-px-r0-nested-z50": {"pass":true,"tracking":0.01,"pressJump":null,"drop":0,"reload":0.07,"undo":true,"dropped":2,"controlDropped":2,"work":7,"frameP95":55.3}, + "nudge-hold-px-r0-root-z100": {"pass":false,"tracking":0.01,"pressJump":null,"drop":0,"reload":0,"undo":true,"dropped":4,"controlDropped":3,"work":6.96,"frameP95":66.71}, + "nudge-hold-px-r0-root-z200": {"pass":true,"tracking":0.01,"pressJump":null,"drop":0,"reload":0.03,"undo":true,"dropped":1,"controlDropped":2,"work":6.98,"frameP95":38.71}, + "nudge-hold-px-r0-root-z50": {"pass":false,"tracking":0.01,"pressJump":null,"drop":0,"reload":0.05,"undo":true,"dropped":3,"controlDropped":3,"work":9.2,"frameP95":68.5}, + "nudge-hold-px-r30-nested-z100": {"pass":false,"tracking":0.01,"pressJump":null,"drop":0,"reload":0,"undo":true,"dropped":3,"controlDropped":0,"work":12.82,"frameP95":65.31}, + "nudge-hold-px-r30-nested-z200": {"pass":false,"tracking":0.01,"pressJump":null,"drop":0,"reload":0.03,"undo":true,"dropped":0,"controlDropped":1,"work":8.33,"frameP95":20.8}, + "nudge-hold-px-r30-nested-z50": {"pass":false,"tracking":0.01,"pressJump":null,"drop":0,"reload":0.07,"undo":true,"dropped":3,"controlDropped":1,"work":9.61,"frameP95":67.1}, + "nudge-hold-px-r30-root-z100": {"pass":false,"tracking":0.02,"pressJump":null,"drop":0,"reload":0,"undo":true,"dropped":3,"controlDropped":1,"work":10.11,"frameP95":62.4}, + "nudge-hold-px-r30-root-z200": {"pass":false,"tracking":0.01,"pressJump":null,"drop":0,"reload":0.04,"undo":true,"dropped":3,"controlDropped":1,"work":9.93,"frameP95":64}, + "nudge-hold-px-r30-root-z50": {"pass":false,"tracking":0.01,"pressJump":null,"drop":0,"reload":0.07,"undo":true,"dropped":1,"controlDropped":1,"work":8.15,"frameP95":25.1}, + "nudge-hold-xpercent-r0-nested-z100": {"pass":false,"tracking":0.01,"pressJump":null,"drop":0,"reload":0,"undo":true,"dropped":3,"controlDropped":2,"work":7.72,"frameP95":63.41}, + "nudge-hold-xpercent-r0-nested-z200": {"pass":false,"tracking":0.01,"pressJump":null,"drop":0,"reload":0.03,"undo":true,"dropped":2,"controlDropped":3,"work":9.44,"frameP95":52.8}, + "nudge-hold-xpercent-r0-nested-z50": {"pass":false,"tracking":0.01,"pressJump":null,"drop":0,"reload":0.05,"undo":true,"dropped":3,"controlDropped":2,"work":7.38,"frameP95":44}, + "nudge-hold-xpercent-r0-root-z100": {"pass":false,"tracking":0.01,"pressJump":null,"drop":0,"reload":0,"undo":true,"dropped":2,"controlDropped":0,"work":11.76,"frameP95":58.21}, + "nudge-hold-xpercent-r0-root-z200": {"pass":false,"tracking":0.01,"pressJump":null,"drop":0,"reload":0.03,"undo":true,"dropped":3,"controlDropped":1,"work":9.58,"frameP95":51.11}, + "nudge-hold-xpercent-r0-root-z50": {"pass":false,"tracking":0.01,"pressJump":null,"drop":0,"reload":0.05,"undo":true,"dropped":2,"controlDropped":0,"work":5.77,"frameP95":58.1}, + "nudge-hold-xpercent-r30-nested-z100": {"pass":false,"tracking":0.01,"pressJump":null,"drop":0,"reload":0,"undo":true,"dropped":3,"controlDropped":2,"work":9.91,"frameP95":62.8}, + "nudge-hold-xpercent-r30-nested-z200": {"pass":false,"tracking":0.02,"pressJump":null,"drop":0,"reload":0.03,"undo":true,"dropped":6,"controlDropped":2,"work":13.55,"frameP95":59.61}, + "nudge-hold-xpercent-r30-nested-z50": {"pass":false,"tracking":0.06,"pressJump":null,"drop":0,"reload":0.11,"undo":true,"dropped":3,"controlDropped":2,"work":8.69,"frameP95":59}, + "nudge-hold-xpercent-r30-root-z100": {"pass":false,"tracking":0.01,"pressJump":null,"drop":0,"reload":0,"undo":true,"dropped":2,"controlDropped":1,"work":10.73,"frameP95":55.8}, + "nudge-hold-xpercent-r30-root-z200": {"pass":false,"tracking":0.02,"pressJump":null,"drop":0,"reload":0.03,"undo":true,"dropped":2,"controlDropped":2,"work":8.66,"frameP95":52.21}, + "nudge-hold-xpercent-r30-root-z50": {"pass":false,"tracking":0.06,"pressJump":null,"drop":0,"reload":0.11,"undo":true,"dropped":2,"controlDropped":1,"work":9.21,"frameP95":50}, + "nudge-none-center-r0-nested-z100": {"pass":false,"tracking":0.02,"pressJump":null,"drop":5.01,"reload":0,"undo":true,"dropped":3,"controlDropped":1,"work":10.23,"frameP95":50.1}, + "nudge-none-center-r0-nested-z200": {"pass":false,"tracking":0.01,"pressJump":null,"drop":5.01,"reload":0.03,"undo":true,"dropped":2,"controlDropped":1,"work":8.89,"frameP95":43.2}, + "nudge-none-center-r0-nested-z50": {"pass":false,"tracking":0.01,"pressJump":null,"drop":5.06,"reload":0.05,"undo":true,"dropped":2,"controlDropped":2,"work":6.99,"frameP95":47.91}, + "nudge-none-center-r0-root-z100": {"pass":true,"tracking":0.02,"pressJump":null,"drop":0,"reload":0,"undo":true,"dropped":1,"controlDropped":1,"work":7.43,"frameP95":41.91}, + "nudge-none-center-r0-root-z200": {"pass":false,"tracking":0.01,"pressJump":null,"drop":0,"reload":0.03,"undo":true,"dropped":4,"controlDropped":0,"work":7.8,"frameP95":59.4}, + "nudge-none-center-r0-root-z50": {"pass":true,"tracking":0.01,"pressJump":null,"drop":0,"reload":0.07,"undo":true,"dropped":2,"controlDropped":2,"work":7.54,"frameP95":39.9}, + "nudge-none-center-r30-nested-z100": {"pass":false,"tracking":0.01,"pressJump":null,"drop":5.01,"reload":0,"undo":true,"dropped":2,"controlDropped":0,"work":5.99,"frameP95":45.91}, + "nudge-none-center-r30-nested-z200": {"pass":false,"tracking":0.01,"pressJump":null,"drop":5.01,"reload":0.04,"undo":true,"dropped":2,"controlDropped":1,"work":6.76,"frameP95":47.21}, + "nudge-none-center-r30-nested-z50": {"pass":false,"tracking":0.01,"pressJump":null,"drop":5.06,"reload":0.07,"undo":true,"dropped":2,"controlDropped":0,"work":6.74,"frameP95":57.61}, + "nudge-none-center-r30-root-z100": {"pass":false,"tracking":0.01,"pressJump":null,"drop":0,"reload":0,"undo":true,"dropped":3,"controlDropped":0,"work":7.39,"frameP95":39.21}, + "nudge-none-center-r30-root-z200": {"pass":false,"tracking":0.01,"pressJump":null,"drop":0,"reload":0.04,"undo":true,"dropped":3,"controlDropped":2,"work":7.34,"frameP95":56.1}, + "nudge-none-center-r30-root-z50": {"pass":true,"tracking":0.01,"pressJump":null,"drop":0,"reload":0.05,"undo":true,"dropped":2,"controlDropped":2,"work":7.79,"frameP95":39.1}, + "nudge-none-pct-r0-nested-z100": {"pass":false,"tracking":0.02,"pressJump":null,"drop":5.01,"reload":0,"undo":true,"dropped":4,"controlDropped":2,"work":9.98,"frameP95":60.41}, + "nudge-none-pct-r0-nested-z200": {"pass":false,"tracking":0.01,"pressJump":null,"drop":5.01,"reload":0.02,"undo":true,"dropped":3,"controlDropped":2,"work":9.27,"frameP95":63.6}, + "nudge-none-pct-r0-nested-z50": {"pass":false,"tracking":0.01,"pressJump":null,"drop":5.06,"reload":0.05,"undo":true,"dropped":2,"controlDropped":0,"work":5.65,"frameP95":41.21}, + "nudge-none-pct-r0-root-z100": {"pass":false,"tracking":0.02,"pressJump":null,"drop":72.11,"reload":0,"undo":true,"dropped":2,"controlDropped":0,"work":10.73,"frameP95":53.5}, + "nudge-none-pct-r0-root-z200": {"pass":false,"tracking":0.01,"pressJump":null,"drop":72.12,"reload":0.02,"undo":true,"dropped":1,"controlDropped":2,"work":7.1,"frameP95":54.21}, + "nudge-none-pct-r0-root-z50": {"pass":false,"tracking":0.01,"pressJump":null,"drop":72.16,"reload":0.07,"undo":true,"dropped":4,"controlDropped":2,"work":10.85,"frameP95":68.31}, + "nudge-none-pct-r30-nested-z100": {"pass":false,"tracking":0.01,"pressJump":null,"drop":5.06,"reload":0,"undo":true,"dropped":3,"controlDropped":0,"work":6.63,"frameP95":54.4}, + "nudge-none-pct-r30-nested-z200": {"pass":false,"tracking":0.01,"pressJump":null,"drop":5.01,"reload":0.06,"undo":true,"dropped":0,"controlDropped":2,"work":7.29,"frameP95":17.91}, + "nudge-none-pct-r30-nested-z50": {"pass":false,"tracking":0.01,"pressJump":null,"drop":5.06,"reload":0.05,"undo":true,"dropped":1,"controlDropped":1,"work":5.77,"frameP95":37.91}, + "nudge-none-pct-r30-root-z100": {"pass":false,"tracking":0.01,"pressJump":null,"drop":72.14,"reload":0,"undo":true,"dropped":2,"controlDropped":2,"work":6.91,"frameP95":47.91}, + "nudge-none-pct-r30-root-z200": {"pass":false,"tracking":0.02,"pressJump":null,"drop":72.12,"reload":0.04,"undo":true,"dropped":0,"controlDropped":2,"work":6.81,"frameP95":17.91}, + "nudge-none-pct-r30-root-z50": {"pass":false,"tracking":0.01,"pressJump":null,"drop":72.16,"reload":0.05,"undo":true,"dropped":5,"controlDropped":2,"work":9.33,"frameP95":67.71}, + "nudge-none-px-r0-nested-z100": {"pass":false,"tracking":0.02,"pressJump":null,"drop":5.01,"reload":0,"undo":true,"dropped":0,"controlDropped":0,"work":6.36,"frameP95":17.81}, + "nudge-none-px-r0-nested-z200": {"pass":false,"tracking":0.01,"pressJump":null,"drop":5.01,"reload":0.04,"undo":true,"dropped":2,"controlDropped":1,"work":6.4,"frameP95":38}, + "nudge-none-px-r0-nested-z50": {"pass":false,"tracking":0.01,"pressJump":null,"drop":5.06,"reload":0.07,"undo":true,"dropped":0,"controlDropped":0,"work":6.06,"frameP95":17.6}, + "nudge-none-px-r0-root-z100": {"pass":false,"tracking":0.01,"pressJump":null,"drop":49.99,"reload":0,"undo":true,"dropped":1,"controlDropped":1,"work":6.95,"frameP95":31.8}, + "nudge-none-px-r0-root-z200": {"pass":false,"tracking":0.01,"pressJump":null,"drop":50.03,"reload":0.02,"undo":true,"dropped":2,"controlDropped":0,"work":9.65,"frameP95":50.5}, + "nudge-none-px-r0-root-z50": {"pass":false,"tracking":0.01,"pressJump":null,"drop":50.09,"reload":0.07,"undo":true,"dropped":2,"controlDropped":0,"work":11.42,"frameP95":33.4}, + "nudge-none-px-r30-nested-z100": {"pass":false,"tracking":0.01,"pressJump":null,"drop":5.01,"reload":0,"undo":true,"dropped":2,"controlDropped":2,"work":7.65,"frameP95":34.2}, + "nudge-none-px-r30-nested-z200": {"pass":false,"tracking":0.01,"pressJump":null,"drop":5.04,"reload":0.05,"undo":true,"dropped":2,"controlDropped":1,"work":7.1,"frameP95":50.8}, + "nudge-none-px-r30-nested-z50": {"pass":false,"tracking":0.01,"pressJump":null,"drop":5.06,"reload":0.07,"undo":true,"dropped":2,"controlDropped":2,"work":6.1,"frameP95":43}, + "nudge-none-px-r30-root-z100": {"pass":false,"tracking":0.02,"pressJump":null,"drop":50.03,"reload":0,"undo":true,"dropped":3,"controlDropped":3,"work":7.22,"frameP95":60.6}, + "nudge-none-px-r30-root-z200": {"pass":false,"tracking":0.01,"pressJump":null,"drop":50.01,"reload":0.04,"undo":true,"dropped":0,"controlDropped":2,"work":10.58,"frameP95":17.91}, + "nudge-none-px-r30-root-z50": {"pass":false,"tracking":0.01,"pressJump":null,"drop":50.09,"reload":0.05,"undo":true,"dropped":1,"controlDropped":1,"work":8.13,"frameP95":37.91}, + "nudge-tween-center-r0-nested-z100": {"pass":false,"tracking":0.01,"pressJump":null,"drop":0,"reload":0,"undo":true,"dropped":2,"controlDropped":1,"work":8.59,"frameP95":54.21}, + "nudge-tween-center-r0-nested-z200": {"pass":false,"tracking":0.01,"pressJump":null,"drop":0,"reload":0.03,"undo":true,"dropped":3,"controlDropped":3,"work":8.91,"frameP95":62.21}, + "nudge-tween-center-r0-nested-z50": {"pass":false,"tracking":0.01,"pressJump":null,"drop":0,"reload":0.07,"undo":true,"dropped":2,"controlDropped":1,"work":10.65,"frameP95":60.3}, + "nudge-tween-center-r0-root-z100": {"pass":false,"tracking":0.01,"pressJump":null,"drop":0,"reload":0,"undo":true,"dropped":2,"controlDropped":2,"work":10.74,"frameP95":63.5}, + "nudge-tween-center-r0-root-z200": {"pass":false,"tracking":0.01,"pressJump":null,"drop":0,"reload":0.03,"undo":true,"dropped":0,"controlDropped":1,"work":8.52,"frameP95":19}, + "nudge-tween-center-r0-root-z50": {"pass":true,"tracking":0.01,"pressJump":null,"drop":0,"reload":0.07,"undo":true,"dropped":0,"controlDropped":1,"work":6.9,"frameP95":17.8}, + "nudge-tween-center-r30-nested-z100": {"pass":false,"tracking":0.02,"pressJump":null,"drop":0,"reload":0,"undo":true,"dropped":5,"controlDropped":2,"work":8.49,"frameP95":62.91}, + "nudge-tween-center-r30-nested-z200": {"pass":false,"tracking":0.01,"pressJump":null,"drop":0,"reload":0.04,"undo":true,"dropped":4,"controlDropped":3,"work":11.46,"frameP95":64.91}, + "nudge-tween-center-r30-nested-z50": {"pass":false,"tracking":0.01,"pressJump":null,"drop":0,"reload":0.1,"undo":true,"dropped":4,"controlDropped":2,"work":7.17,"frameP95":62}, + "nudge-tween-center-r30-root-z100": {"pass":true,"tracking":0.02,"pressJump":null,"drop":0,"reload":0,"undo":true,"dropped":2,"controlDropped":2,"work":5.69,"frameP95":57.3}, + "nudge-tween-center-r30-root-z200": {"pass":false,"tracking":0.01,"pressJump":null,"drop":0,"reload":0.04,"undo":true,"dropped":3,"controlDropped":2,"work":8.93,"frameP95":61.2}, + "nudge-tween-center-r30-root-z50": {"pass":false,"tracking":0.01,"pressJump":null,"drop":0,"reload":0.1,"undo":true,"dropped":3,"controlDropped":2,"work":8.84,"frameP95":56.8}, + "nudge-tween-pct-r0-nested-z100": {"pass":false,"tracking":0.01,"pressJump":null,"drop":0,"reload":0,"undo":true,"dropped":4,"controlDropped":4,"work":10.85,"frameP95":67.4}, + "nudge-tween-pct-r0-nested-z200": {"pass":true,"tracking":0.01,"pressJump":null,"drop":0,"reload":0.01,"undo":true,"dropped":0,"controlDropped":2,"work":7.32,"frameP95":17.9}, + "nudge-tween-pct-r0-nested-z50": {"pass":true,"tracking":0.01,"pressJump":null,"drop":0,"reload":0.07,"undo":true,"dropped":3,"controlDropped":3,"work":7,"frameP95":65.61}, + "nudge-tween-pct-r0-root-z100": {"pass":false,"tracking":0.01,"pressJump":null,"drop":0,"reload":0,"undo":true,"dropped":3,"controlDropped":1,"work":6.94,"frameP95":63.2}, + "nudge-tween-pct-r0-root-z200": {"pass":false,"tracking":0.01,"pressJump":null,"drop":0,"reload":0.03,"undo":true,"dropped":4,"controlDropped":2,"work":9.04,"frameP95":67.21}, + "nudge-tween-pct-r0-root-z50": {"pass":false,"tracking":0.04,"pressJump":null,"drop":0,"reload":0.05,"undo":true,"dropped":4,"controlDropped":2,"work":7.15,"frameP95":60.61}, + "nudge-tween-pct-r30-nested-z100": {"pass":false,"tracking":0.01,"pressJump":null,"drop":0,"reload":0,"undo":true,"dropped":2,"controlDropped":0,"work":7.81,"frameP95":59.8}, + "nudge-tween-pct-r30-nested-z200": {"pass":true,"tracking":0.02,"pressJump":null,"drop":0,"reload":0.03,"undo":true,"dropped":1,"controlDropped":1,"work":6.13,"frameP95":46}, + "nudge-tween-pct-r30-nested-z50": {"pass":false,"tracking":0.06,"pressJump":null,"drop":0,"reload":0.11,"undo":true,"dropped":2,"controlDropped":0,"work":6.28,"frameP95":59.6}, + "nudge-tween-pct-r30-root-z100": {"pass":true,"tracking":0.01,"pressJump":null,"drop":0,"reload":0,"undo":true,"dropped":1,"controlDropped":1,"work":7.35,"frameP95":26.1}, + "nudge-tween-pct-r30-root-z200": {"pass":false,"tracking":0.02,"pressJump":null,"drop":0,"reload":0.03,"undo":true,"dropped":1,"controlDropped":0,"work":6.84,"frameP95":38.9}, + "nudge-tween-pct-r30-root-z50": {"pass":true,"tracking":0.06,"pressJump":null,"drop":0,"reload":0.11,"undo":true,"dropped":0,"controlDropped":0,"work":6.85,"frameP95":20.71}, + "nudge-tween-px-r0-nested-z100": {"pass":true,"tracking":0.5,"pressJump":null,"drop":0,"reload":0,"undo":true,"dropped":2,"controlDropped":2,"work":7.23,"frameP95":34.21}, + "nudge-tween-px-r0-nested-z200": {"pass":false,"tracking":0.51,"pressJump":null,"drop":0,"reload":0.04,"undo":true,"dropped":2,"controlDropped":2,"work":9.64,"frameP95":37.21}, + "nudge-tween-px-r0-nested-z50": {"pass":false,"tracking":0.55,"pressJump":null,"drop":0,"reload":0.05,"undo":true,"dropped":2,"controlDropped":0,"work":11,"frameP95":34.41}, + "nudge-tween-px-r0-root-z100": {"pass":false,"tracking":0.5,"pressJump":null,"drop":0,"reload":0,"undo":true,"dropped":2,"controlDropped":0,"work":11.01,"frameP95":50.21}, + "nudge-tween-px-r0-root-z200": {"pass":false,"tracking":0.5,"pressJump":null,"drop":0,"reload":0.04,"undo":true,"dropped":3,"controlDropped":0,"work":6.9,"frameP95":64.6}, + "nudge-tween-px-r0-root-z50": {"pass":false,"tracking":0.55,"pressJump":null,"drop":0,"reload":0.07,"undo":true,"dropped":1,"controlDropped":0,"work":6.41,"frameP95":29}, + "nudge-tween-px-r30-nested-z100": {"pass":false,"tracking":0.5,"pressJump":null,"drop":0,"reload":0,"undo":true,"dropped":2,"controlDropped":2,"work":8.39,"frameP95":58.61}, + "nudge-tween-px-r30-nested-z200": {"pass":false,"tracking":0.5,"pressJump":null,"drop":0,"reload":0.05,"undo":true,"dropped":3,"controlDropped":2,"work":8.55,"frameP95":63.8}, + "nudge-tween-px-r30-nested-z50": {"pass":true,"tracking":0.5,"pressJump":null,"drop":0,"reload":0.07,"undo":true,"dropped":0,"controlDropped":0,"work":7.09,"frameP95":18.11}, + "nudge-tween-px-r30-root-z100": {"pass":false,"tracking":0.5,"pressJump":null,"drop":0,"reload":0,"undo":true,"dropped":1,"controlDropped":0,"work":7.1,"frameP95":37.71}, + "nudge-tween-px-r30-root-z200": {"pass":false,"tracking":0.51,"pressJump":null,"drop":0,"reload":0.05,"undo":true,"dropped":3,"controlDropped":0,"work":10.24,"frameP95":56.71}, + "nudge-tween-px-r30-root-z50": {"pass":false,"tracking":0.5,"pressJump":null,"drop":0,"reload":0.05,"undo":true,"dropped":2,"controlDropped":1,"work":7.51,"frameP95":39.71}, + "nudge-tween-xpercent-r0-nested-z100": {"pass":false,"tracking":0.01,"pressJump":null,"drop":144.27,"reload":0,"undo":true,"dropped":0,"controlDropped":1,"work":7.97,"frameP95":21.5}, + "nudge-tween-xpercent-r0-nested-z200": {"pass":false,"tracking":0.01,"pressJump":null,"drop":144.22,"reload":0.04,"undo":true,"dropped":0,"controlDropped":0,"work":8.16,"frameP95":17.4}, + "nudge-tween-xpercent-r0-nested-z50": {"pass":false,"tracking":0.01,"pressJump":null,"drop":144.22,"reload":0.07,"undo":true,"dropped":3,"controlDropped":1,"work":8.23,"frameP95":54.3}, + "nudge-tween-xpercent-r0-root-z100": {"pass":false,"tracking":0.01,"pressJump":null,"drop":144.27,"reload":0,"undo":true,"dropped":0,"controlDropped":0,"work":7.42,"frameP95":17.91}, + "nudge-tween-xpercent-r0-root-z200": {"pass":false,"tracking":0.01,"pressJump":null,"drop":144.22,"reload":0.04,"undo":true,"dropped":0,"controlDropped":1,"work":8.04,"frameP95":18.21}, + "nudge-tween-xpercent-r0-root-z50": {"pass":false,"tracking":0.01,"pressJump":null,"drop":144.22,"reload":0.07,"undo":true,"dropped":2,"controlDropped":1,"work":7.99,"frameP95":46.41}, + "nudge-tween-xpercent-r30-nested-z100": {"pass":false,"tracking":0.02,"pressJump":null,"drop":144.24,"reload":0,"undo":true,"dropped":4,"controlDropped":2,"work":13.2,"frameP95":60.41}, + "nudge-tween-xpercent-r30-nested-z200": {"pass":false,"tracking":0.01,"pressJump":null,"drop":144.24,"reload":0.03,"undo":true,"dropped":3,"controlDropped":3,"work":8.32,"frameP95":64.4}, + "nudge-tween-xpercent-r30-nested-z50": {"pass":false,"tracking":0.01,"pressJump":null,"drop":144.32,"reload":0.05,"undo":true,"dropped":1,"controlDropped":3,"work":5.81,"frameP95":44.31}, + "nudge-tween-xpercent-r30-root-z100": {"pass":false,"tracking":0.02,"pressJump":null,"drop":144.24,"reload":0,"undo":true,"dropped":0,"controlDropped":0,"work":6.9,"frameP95":17.1}, + "nudge-tween-xpercent-r30-root-z200": {"pass":false,"tracking":0.01,"pressJump":null,"drop":144.24,"reload":0.03,"undo":true,"dropped":0,"controlDropped":2,"work":6.81,"frameP95":17.81}, + "nudge-tween-xpercent-r30-root-z50": {"pass":false,"tracking":0.01,"pressJump":null,"drop":144.32,"reload":0.05,"undo":true,"dropped":1,"controlDropped":1,"work":8.72,"frameP95":40.41}, + "resize-hold-center-r0-nested-z100": {"pass":false,"tracking":0.26,"pressJump":0,"drop":0,"reload":0,"undo":true,"dropped":16,"controlDropped":13,"work":9.05,"frameP95":65.6}, + "resize-hold-center-r0-nested-z200": {"pass":false,"tracking":0.23,"pressJump":0,"drop":0,"reload":0.01,"undo":true,"dropped":17,"controlDropped":11,"work":9.05,"frameP95":60.71}, + "resize-hold-center-r0-nested-z50": {"pass":false,"tracking":0.27,"pressJump":0,"drop":0,"reload":0.07,"undo":true,"dropped":20,"controlDropped":15,"work":9.32,"frameP95":70.3}, + "resize-hold-center-r0-root-z100": {"pass":false,"tracking":0.26,"pressJump":0,"drop":0,"reload":60.2,"undo":false,"dropped":10,"controlDropped":11,"work":10.72,"frameP95":52.6}, + "resize-hold-center-r0-root-z200": {"pass":false,"tracking":0.23,"pressJump":0,"drop":0,"reload":60.2,"undo":false,"dropped":18,"controlDropped":7,"work":9.88,"frameP95":68.8}, + "resize-hold-center-r0-root-z50": {"pass":false,"tracking":0.27,"pressJump":0,"drop":0,"reload":60.21,"undo":false,"dropped":11,"controlDropped":11,"work":10.49,"frameP95":51}, + "resize-hold-center-r30-nested-z100": {"pass":false,"tracking":0.23,"pressJump":0,"drop":0,"reload":0,"undo":true,"dropped":24,"controlDropped":14,"work":10.7,"frameP95":70}, + "resize-hold-center-r30-nested-z200": {"pass":false,"tracking":0.24,"pressJump":0,"drop":0,"reload":0.04,"undo":true,"dropped":17,"controlDropped":14,"work":8.7,"frameP95":67.5}, + "resize-hold-center-r30-nested-z50": {"pass":false,"tracking":0.31,"pressJump":0,"drop":0,"reload":0.07,"undo":true,"dropped":28,"controlDropped":15,"work":9.4,"frameP95":75.8}, + "resize-hold-center-r30-root-z100": {"pass":false,"tracking":0.23,"pressJump":0,"drop":0,"reload":60.2,"undo":false,"dropped":19,"controlDropped":11,"work":7.57,"frameP95":73.11}, + "resize-hold-center-r30-root-z200": {"pass":false,"tracking":0.24,"pressJump":0,"drop":0,"reload":60.19,"undo":false,"dropped":18,"controlDropped":13,"work":8.3,"frameP95":68.1}, + "resize-hold-center-r30-root-z50": {"pass":false,"tracking":0.31,"pressJump":0,"drop":0,"reload":60.24,"undo":false,"dropped":18,"controlDropped":13,"work":9.53,"frameP95":61.1}, + "resize-hold-pct-r0-nested-z100": {"pass":false,"tracking":0.65,"pressJump":0,"drop":0,"reload":0,"undo":true,"dropped":4,"controlDropped":3,"work":7.91,"frameP95":38.2}, + "resize-hold-pct-r0-nested-z200": {"pass":false,"tracking":0.67,"pressJump":0,"drop":0,"reload":0.04,"undo":true,"dropped":13,"controlDropped":0,"work":8.11,"frameP95":59.61}, + "resize-hold-pct-r0-nested-z50": {"pass":false,"tracking":0.99,"pressJump":0,"drop":0,"reload":0.07,"undo":true,"dropped":7,"controlDropped":5,"work":9.05,"frameP95":56.1}, + "resize-hold-pct-r0-root-z100": {"pass":false,"tracking":0.92,"pressJump":0,"drop":0,"reload":120.41,"undo":false,"dropped":11,"controlDropped":1,"work":8.35,"frameP95":53.8}, + "resize-hold-pct-r0-root-z200": {"pass":false,"tracking":0.92,"pressJump":0,"drop":0,"reload":120.39,"undo":false,"dropped":2,"controlDropped":3,"work":10.21,"frameP95":20.4}, + "resize-hold-pct-r0-root-z50": {"pass":false,"tracking":0.92,"pressJump":0,"drop":0,"reload":120.37,"undo":false,"dropped":8,"controlDropped":8,"work":7.68,"frameP95":51.1}, + "resize-hold-pct-r30-nested-z100": {"pass":false,"tracking":0.93,"pressJump":0,"drop":0,"reload":0,"undo":true,"dropped":12,"controlDropped":2,"work":10.1,"frameP95":50.5}, + "resize-hold-pct-r30-nested-z200": {"pass":false,"tracking":0.89,"pressJump":0,"drop":0,"reload":0.04,"undo":true,"dropped":10,"controlDropped":8,"work":8.12,"frameP95":49.2}, + "resize-hold-pct-r30-nested-z50": {"pass":false,"tracking":0.92,"pressJump":0,"drop":0,"reload":0.05,"undo":true,"dropped":10,"controlDropped":9,"work":8.5,"frameP95":60.5}, + "resize-hold-pct-r30-root-z100": {"pass":false,"tracking":0.93,"pressJump":0,"drop":0,"reload":116.27,"undo":false,"dropped":7,"controlDropped":9,"work":9.65,"frameP95":45.6}, + "resize-hold-pct-r30-root-z200": {"pass":false,"tracking":0.9,"pressJump":0,"drop":0,"reload":116.29,"undo":false,"dropped":10,"controlDropped":3,"work":8.58,"frameP95":45.4}, + "resize-hold-pct-r30-root-z50": {"pass":false,"tracking":0.97,"pressJump":0,"drop":0,"reload":116.27,"undo":false,"dropped":8,"controlDropped":6,"work":8.92,"frameP95":59.3}, + "resize-hold-px-r0-nested-z100": {"pass":false,"tracking":0.65,"pressJump":0,"drop":0,"reload":0,"undo":true,"dropped":16,"controlDropped":12,"work":8.15,"frameP95":59.5}, + "resize-hold-px-r0-nested-z200": {"pass":false,"tracking":0.67,"pressJump":0,"drop":0,"reload":0.04,"undo":true,"dropped":20,"controlDropped":11,"work":8.23,"frameP95":63.8}, + "resize-hold-px-r0-nested-z50": {"pass":false,"tracking":0.99,"pressJump":0,"drop":0,"reload":0.07,"undo":true,"dropped":16,"controlDropped":11,"work":11.86,"frameP95":70.31}, + "resize-hold-px-r0-root-z100": {"pass":false,"tracking":0.92,"pressJump":0,"drop":0,"reload":120.41,"undo":false,"dropped":15,"controlDropped":13,"work":9.47,"frameP95":53.21}, + "resize-hold-px-r0-root-z200": {"pass":false,"tracking":0.92,"pressJump":0,"drop":0,"reload":120.39,"undo":false,"dropped":16,"controlDropped":12,"work":9.99,"frameP95":68.91}, + "resize-hold-px-r0-root-z50": {"pass":false,"tracking":0.92,"pressJump":0,"drop":0,"reload":120.37,"undo":false,"dropped":14,"controlDropped":13,"work":11.3,"frameP95":66.21}, + "resize-hold-px-r30-nested-z100": {"pass":false,"tracking":0.93,"pressJump":0,"drop":0,"reload":0,"undo":true,"dropped":14,"controlDropped":13,"work":8.89,"frameP95":58.91}, + "resize-hold-px-r30-nested-z200": {"pass":false,"tracking":0.89,"pressJump":0,"drop":0,"reload":0.04,"undo":true,"dropped":12,"controlDropped":9,"work":8.56,"frameP95":51.3}, + "resize-hold-px-r30-nested-z50": {"pass":false,"tracking":0.92,"pressJump":0,"drop":0,"reload":0.05,"undo":true,"dropped":11,"controlDropped":9,"work":8.88,"frameP95":61.91}, + "resize-hold-px-r30-root-z100": {"pass":false,"tracking":0.93,"pressJump":0,"drop":0,"reload":116.27,"undo":false,"dropped":11,"controlDropped":6,"work":9.24,"frameP95":59.1}, + "resize-hold-px-r30-root-z200": {"pass":false,"tracking":0.9,"pressJump":0,"drop":0,"reload":116.29,"undo":false,"dropped":8,"controlDropped":10,"work":7.69,"frameP95":65.81}, + "resize-hold-px-r30-root-z50": {"pass":false,"tracking":0.97,"pressJump":0,"drop":0,"reload":116.27,"undo":false,"dropped":15,"controlDropped":11,"work":10.29,"frameP95":69.6}, + "resize-hold-xpercent-r0-nested-z100": {"pass":false,"tracking":0.26,"pressJump":0,"drop":0,"reload":0,"undo":true,"dropped":14,"controlDropped":11,"work":8.8,"frameP95":65.6}, + "resize-hold-xpercent-r0-nested-z200": {"pass":false,"tracking":0.23,"pressJump":0,"drop":0,"reload":0.01,"undo":true,"dropped":10,"controlDropped":12,"work":8.88,"frameP95":49.9}, + "resize-hold-xpercent-r0-nested-z50": {"pass":false,"tracking":0.27,"pressJump":0,"drop":0,"reload":0.07,"undo":true,"dropped":14,"controlDropped":1,"work":9.39,"frameP95":61.71}, + "resize-hold-xpercent-r0-root-z100": {"pass":false,"tracking":0.26,"pressJump":0,"drop":0,"reload":60.2,"undo":false,"dropped":10,"controlDropped":9,"work":8.99,"frameP95":57.61}, + "resize-hold-xpercent-r0-root-z200": {"pass":false,"tracking":0.23,"pressJump":0,"drop":0,"reload":60.2,"undo":false,"dropped":11,"controlDropped":11,"work":7.22,"frameP95":58.61}, + "resize-hold-xpercent-r0-root-z50": {"pass":false,"tracking":0.27,"pressJump":0,"drop":0,"reload":60.21,"undo":false,"dropped":14,"controlDropped":8,"work":10.36,"frameP95":54.9}, + "resize-hold-xpercent-r30-nested-z100": {"pass":false,"tracking":0.23,"pressJump":0,"drop":0,"reload":0,"undo":true,"dropped":19,"controlDropped":13,"work":9.8,"frameP95":68.41}, + "resize-hold-xpercent-r30-nested-z200": {"pass":false,"tracking":0.24,"pressJump":0,"drop":0,"reload":0.04,"undo":true,"dropped":19,"controlDropped":12,"work":8.67,"frameP95":64.9}, + "resize-hold-xpercent-r30-nested-z50": {"pass":false,"tracking":0.31,"pressJump":0,"drop":0,"reload":0.07,"undo":true,"dropped":16,"controlDropped":13,"work":10.32,"frameP95":67.11}, + "resize-hold-xpercent-r30-root-z100": {"pass":false,"tracking":0.23,"pressJump":0,"drop":0,"reload":60.2,"undo":false,"dropped":16,"controlDropped":6,"work":9.28,"frameP95":67.4}, + "resize-hold-xpercent-r30-root-z200": {"pass":false,"tracking":0.24,"pressJump":0,"drop":0,"reload":60.19,"undo":false,"dropped":9,"controlDropped":3,"work":7.77,"frameP95":35.71}, + "resize-hold-xpercent-r30-root-z50": {"pass":false,"tracking":0.31,"pressJump":0,"drop":0,"reload":60.24,"undo":false,"dropped":14,"controlDropped":10,"work":10.3,"frameP95":65.5}, + "resize-none-center-r0-nested-z100": {"pass":false,"tracking":0.24,"pressJump":0,"drop":60.17,"reload":0,"undo":true,"dropped":12,"controlDropped":12,"work":9.44,"frameP95":63}, + "resize-none-center-r0-nested-z200": {"pass":false,"tracking":0.24,"pressJump":0,"drop":60.19,"reload":0.03,"undo":true,"dropped":13,"controlDropped":15,"work":7.74,"frameP95":63.4}, + "resize-none-center-r0-nested-z50": {"pass":false,"tracking":0.23,"pressJump":0,"drop":60.17,"reload":0.05,"undo":true,"dropped":14,"controlDropped":11,"work":7.71,"frameP95":61.71}, + "resize-none-center-r0-root-z100": {"pass":false,"tracking":0.24,"pressJump":0,"drop":60.17,"reload":0,"undo":true,"dropped":12,"controlDropped":8,"work":7.98,"frameP95":60.8}, + "resize-none-center-r0-root-z200": {"pass":false,"tracking":0.24,"pressJump":0,"drop":60.19,"reload":0.03,"undo":true,"dropped":13,"controlDropped":6,"work":9.13,"frameP95":61.11}, + "resize-none-center-r0-root-z50": {"pass":false,"tracking":0.23,"pressJump":0,"drop":60.17,"reload":0.05,"undo":true,"dropped":12,"controlDropped":3,"work":7.38,"frameP95":57.5}, + "resize-none-center-r30-nested-z100": {"pass":false,"tracking":0.24,"pressJump":0,"drop":60.2,"reload":0,"undo":true,"dropped":10,"controlDropped":11,"work":8.1,"frameP95":58.5}, + "resize-none-center-r30-nested-z200": {"pass":false,"tracking":0.23,"pressJump":0,"drop":60.19,"reload":0.04,"undo":true,"dropped":7,"controlDropped":6,"work":7.16,"frameP95":42.91}, + "resize-none-center-r30-nested-z50": {"pass":false,"tracking":0.24,"pressJump":0,"drop":60.18,"reload":0.07,"undo":true,"dropped":9,"controlDropped":5,"work":8.35,"frameP95":52.5}, + "resize-none-center-r30-root-z100": {"pass":false,"tracking":0.24,"pressJump":0,"drop":60.2,"reload":0,"undo":true,"dropped":16,"controlDropped":10,"work":9.13,"frameP95":52.4}, + "resize-none-center-r30-root-z200": {"pass":false,"tracking":0.23,"pressJump":0,"drop":60.19,"reload":0.04,"undo":true,"dropped":5,"controlDropped":6,"work":7.77,"frameP95":36.91}, + "resize-none-center-r30-root-z50": {"pass":false,"tracking":0.24,"pressJump":0,"drop":60.18,"reload":0.07,"undo":true,"dropped":10,"controlDropped":10,"work":8.5,"frameP95":56.4}, + "resize-none-pct-r0-nested-z100": {"pass":false,"tracking":0.8,"pressJump":0,"drop":60.32,"reload":0,"undo":true,"dropped":18,"controlDropped":14,"work":8.77,"frameP95":61.21}, + "resize-none-pct-r0-nested-z200": {"pass":false,"tracking":1,"pressJump":0,"drop":60.32,"reload":0.02,"undo":true,"dropped":16,"controlDropped":12,"work":9.32,"frameP95":61.11}, + "resize-none-pct-r0-nested-z50": {"pass":false,"tracking":1,"pressJump":0,"drop":60.32,"reload":0.05,"undo":true,"dropped":5,"controlDropped":7,"work":7.94,"frameP95":47.8}, + "resize-none-pct-r0-root-z100": {"pass":false,"tracking":0.97,"pressJump":0,"drop":222.56,"reload":0,"undo":true,"dropped":14,"controlDropped":5,"work":9.26,"frameP95":64.61}, + "resize-none-pct-r0-root-z200": {"pass":false,"tracking":0.96,"pressJump":0,"drop":222.59,"reload":0.03,"undo":true,"dropped":8,"controlDropped":11,"work":8.32,"frameP95":52}, + "resize-none-pct-r0-root-z50": {"pass":false,"tracking":0.99,"pressJump":0,"drop":222.61,"reload":0.07,"undo":true,"dropped":9,"controlDropped":7,"work":10.83,"frameP95":48.91}, + "resize-none-pct-r30-nested-z100": {"pass":false,"tracking":0.94,"pressJump":0,"drop":60.46,"reload":0,"undo":true,"dropped":10,"controlDropped":2,"work":8.61,"frameP95":54.21}, + "resize-none-pct-r30-nested-z200": {"pass":false,"tracking":0.91,"pressJump":0,"drop":60.41,"reload":0.06,"undo":true,"dropped":12,"controlDropped":10,"work":8.62,"frameP95":56.5}, + "resize-none-pct-r30-nested-z50": {"pass":false,"tracking":0.94,"pressJump":0,"drop":60.46,"reload":0.05,"undo":true,"dropped":8,"controlDropped":5,"work":7.63,"frameP95":51.8}, + "resize-none-pct-r30-root-z100": {"pass":false,"tracking":0.94,"pressJump":0,"drop":216.64,"reload":0,"undo":true,"dropped":21,"controlDropped":11,"work":8.17,"frameP95":68.71}, + "resize-none-pct-r30-root-z200": {"pass":false,"tracking":0.89,"pressJump":0,"drop":216.58,"reload":0.05,"undo":true,"dropped":11,"controlDropped":10,"work":8.05,"frameP95":52.8}, + "resize-none-pct-r30-root-z50": {"pass":false,"tracking":0.94,"pressJump":0,"drop":216.54,"reload":0.11,"undo":true,"dropped":15,"controlDropped":8,"work":8.19,"frameP95":62.8}, + "resize-none-px-r0-nested-z100": {"pass":false,"tracking":0.89,"pressJump":0,"drop":60.45,"reload":0,"undo":true,"dropped":5,"controlDropped":0,"work":7.9,"frameP95":48.5}, + "resize-none-px-r0-nested-z200": {"pass":false,"tracking":0.67,"pressJump":0,"drop":60.46,"reload":0.04,"undo":true,"dropped":13,"controlDropped":13,"work":9.41,"frameP95":55.8}, + "resize-none-px-r0-nested-z50": {"pass":false,"tracking":0.89,"pressJump":0,"drop":60.48,"reload":0.07,"undo":true,"dropped":1,"controlDropped":0,"work":6.85,"frameP95":19.1}, + "resize-none-px-r0-root-z100": {"pass":false,"tracking":0.92,"pressJump":0,"drop":170.31,"reload":0,"undo":true,"dropped":8,"controlDropped":5,"work":9,"frameP95":50.21}, + "resize-none-px-r0-root-z200": {"pass":false,"tracking":0.9,"pressJump":0,"drop":170.32,"reload":0.03,"undo":true,"dropped":3,"controlDropped":4,"work":9.55,"frameP95":30.41}, + "resize-none-px-r0-root-z50": {"pass":false,"tracking":0.83,"pressJump":0,"drop":170.3,"reload":0.05,"undo":true,"dropped":6,"controlDropped":0,"work":10.09,"frameP95":30.71}, + "resize-none-px-r30-nested-z100": {"pass":false,"tracking":0.89,"pressJump":0,"drop":60.62,"reload":0,"undo":true,"dropped":13,"controlDropped":9,"work":9.19,"frameP95":64.4}, + "resize-none-px-r30-nested-z200": {"pass":false,"tracking":0.88,"pressJump":0,"drop":60.66,"reload":0.05,"undo":true,"dropped":14,"controlDropped":0,"work":8.95,"frameP95":51.71}, + "resize-none-px-r30-nested-z50": {"pass":false,"tracking":0.89,"pressJump":0,"drop":60.62,"reload":0.07,"undo":true,"dropped":14,"controlDropped":10,"work":8.11,"frameP95":62}, + "resize-none-px-r30-root-z100": {"pass":false,"tracking":0.92,"pressJump":0,"drop":165.53,"reload":0,"undo":true,"dropped":10,"controlDropped":7,"work":8.1,"frameP95":58.61}, + "resize-none-px-r30-root-z200": {"pass":false,"tracking":0.89,"pressJump":0,"drop":165.51,"reload":0.06,"undo":true,"dropped":12,"controlDropped":2,"work":9.68,"frameP95":58.11}, + "resize-none-px-r30-root-z50": {"pass":false,"tracking":0.93,"pressJump":0,"drop":165.59,"reload":0.1,"undo":true,"dropped":10,"controlDropped":5,"work":9.07,"frameP95":54.71}, + "resize-tween-center-r0-nested-z100": {"pass":false,"tracking":0.27,"pressJump":0,"drop":0,"reload":0,"undo":true,"dropped":16,"controlDropped":12,"work":9.2,"frameP95":64.21}, + "resize-tween-center-r0-nested-z200": {"pass":false,"tracking":0.24,"pressJump":0,"drop":0,"reload":0.03,"undo":true,"dropped":17,"controlDropped":11,"work":11.44,"frameP95":62.11}, + "resize-tween-center-r0-nested-z50": {"pass":false,"tracking":0.24,"pressJump":0,"drop":0,"reload":0.07,"undo":true,"dropped":14,"controlDropped":3,"work":9.62,"frameP95":57}, + "resize-tween-center-r0-root-z100": {"pass":false,"tracking":0.27,"pressJump":0,"drop":0,"reload":0,"undo":true,"dropped":18,"controlDropped":2,"work":9.21,"frameP95":65.21}, + "resize-tween-center-r0-root-z200": {"pass":true,"tracking":0.24,"pressJump":0,"drop":0,"reload":0.03,"undo":true,"dropped":10,"controlDropped":11,"work":7.85,"frameP95":50.21}, + "resize-tween-center-r0-root-z50": {"pass":false,"tracking":0.24,"pressJump":0,"drop":0,"reload":0.07,"undo":true,"dropped":8,"controlDropped":5,"work":8.54,"frameP95":46.61}, + "resize-tween-center-r30-nested-z100": {"pass":false,"tracking":0.26,"pressJump":0,"drop":0,"reload":0,"undo":true,"dropped":13,"controlDropped":11,"work":10.58,"frameP95":62.5}, + "resize-tween-center-r30-nested-z200": {"pass":false,"tracking":0.24,"pressJump":0,"drop":0,"reload":0.05,"undo":true,"dropped":23,"controlDropped":11,"work":8.9,"frameP95":66.3}, + "resize-tween-center-r30-nested-z50": {"pass":false,"tracking":0.38,"pressJump":0,"drop":0,"reload":0.07,"undo":true,"dropped":18,"controlDropped":11,"work":8.62,"frameP95":62.91}, + "resize-tween-center-r30-root-z100": {"pass":false,"tracking":0.26,"pressJump":0,"drop":0,"reload":0,"undo":true,"dropped":15,"controlDropped":9,"work":8.14,"frameP95":59.61}, + "resize-tween-center-r30-root-z200": {"pass":false,"tracking":0.24,"pressJump":0,"drop":0,"reload":0.05,"undo":true,"dropped":13,"controlDropped":12,"work":9.62,"frameP95":59.1}, + "resize-tween-center-r30-root-z50": {"pass":false,"tracking":0.38,"pressJump":0,"drop":0,"reload":0.07,"undo":true,"dropped":16,"controlDropped":11,"work":9.45,"frameP95":66.31}, + "resize-tween-pct-r0-nested-z100": {"pass":false,"tracking":0.92,"pressJump":0,"drop":0,"reload":0,"undo":true,"dropped":17,"controlDropped":11,"work":8.5,"frameP95":66.61}, + "resize-tween-pct-r0-nested-z200": {"pass":false,"tracking":0.84,"pressJump":0,"drop":0,"reload":0.03,"undo":true,"dropped":21,"controlDropped":11,"work":9.78,"frameP95":71.71}, + "resize-tween-pct-r0-nested-z50": {"pass":false,"tracking":0.76,"pressJump":0,"drop":0,"reload":0.07,"undo":true,"dropped":18,"controlDropped":11,"work":11.23,"frameP95":73.3}, + "resize-tween-pct-r0-root-z100": {"pass":false,"tracking":0.86,"pressJump":0,"drop":0,"reload":0,"undo":true,"dropped":20,"controlDropped":12,"work":8.62,"frameP95":68}, + "resize-tween-pct-r0-root-z200": {"pass":false,"tracking":0.84,"pressJump":0,"drop":0,"reload":0.04,"undo":true,"dropped":16,"controlDropped":13,"work":8.35,"frameP95":64.5}, + "resize-tween-pct-r0-root-z50": {"pass":false,"tracking":0.88,"pressJump":0,"drop":0,"reload":0.07,"undo":true,"dropped":20,"controlDropped":13,"work":9.4,"frameP95":68}, + "resize-tween-pct-r30-nested-z100": {"pass":false,"tracking":0.9,"pressJump":0,"drop":0,"reload":0,"undo":true,"dropped":8,"controlDropped":3,"work":9.02,"frameP95":57.6}, + "resize-tween-pct-r30-nested-z200": {"pass":false,"tracking":0.93,"pressJump":0,"drop":0,"reload":0.04,"undo":true,"dropped":13,"controlDropped":7,"work":8.72,"frameP95":61.9}, + "resize-tween-pct-r30-nested-z50": {"pass":false,"tracking":1,"pressJump":0,"drop":0,"reload":0.07,"undo":true,"dropped":6,"controlDropped":2,"work":7.93,"frameP95":42.5}, + "resize-tween-pct-r30-root-z100": {"pass":false,"tracking":0.89,"pressJump":0,"drop":0,"reload":0,"undo":true,"dropped":12,"controlDropped":2,"work":10.04,"frameP95":53.21}, + "resize-tween-pct-r30-root-z200": {"pass":false,"tracking":0.91,"pressJump":0,"drop":0,"reload":0.04,"undo":true,"dropped":12,"controlDropped":2,"work":8.26,"frameP95":57.5}, + "resize-tween-pct-r30-root-z50": {"pass":false,"tracking":0.93,"pressJump":0,"drop":0,"reload":0.07,"undo":true,"dropped":8,"controlDropped":10,"work":9.08,"frameP95":38.9}, + "resize-tween-px-r0-nested-z100": {"pass":false,"tracking":0.74,"pressJump":0,"drop":0,"reload":0,"undo":true,"dropped":10,"controlDropped":4,"work":8.99,"frameP95":48.91}, + "resize-tween-px-r0-nested-z200": {"pass":false,"tracking":0.71,"pressJump":0,"drop":0,"reload":0.02,"undo":true,"dropped":11,"controlDropped":5,"work":9.21,"frameP95":53.5}, + "resize-tween-px-r0-nested-z50": {"pass":false,"tracking":0.73,"pressJump":0,"drop":0,"reload":0.07,"undo":true,"dropped":13,"controlDropped":4,"work":8.97,"frameP95":51.41}, + "resize-tween-px-r0-root-z100": {"pass":false,"tracking":0.75,"pressJump":0,"drop":0,"reload":0,"undo":true,"dropped":11,"controlDropped":0,"work":8.68,"frameP95":62}, + "resize-tween-px-r0-root-z200": {"pass":false,"tracking":0.76,"pressJump":0,"drop":0,"reload":0.04,"undo":true,"dropped":9,"controlDropped":6,"work":7.84,"frameP95":49.4}, + "resize-tween-px-r0-root-z50": {"pass":false,"tracking":0.73,"pressJump":0,"drop":0,"reload":0.05,"undo":true,"dropped":5,"controlDropped":8,"work":8.52,"frameP95":43.61}, + "resize-tween-px-r30-nested-z100": {"pass":false,"tracking":0.76,"pressJump":0,"drop":0,"reload":0,"undo":true,"dropped":4,"controlDropped":9,"work":9.04,"frameP95":39.8}, + "resize-tween-px-r30-nested-z200": {"pass":false,"tracking":0.72,"pressJump":0,"drop":0,"reload":0.03,"undo":true,"dropped":15,"controlDropped":12,"work":8.33,"frameP95":62.6}, + "resize-tween-px-r30-nested-z50": {"pass":false,"tracking":0.78,"pressJump":0,"drop":0,"reload":0.1,"undo":true,"dropped":11,"controlDropped":5,"work":7.9,"frameP95":55.71}, + "resize-tween-px-r30-root-z100": {"pass":false,"tracking":0.72,"pressJump":0,"drop":0,"reload":0,"undo":true,"dropped":8,"controlDropped":4,"work":8.37,"frameP95":52.9}, + "resize-tween-px-r30-root-z200": {"pass":false,"tracking":0.69,"pressJump":0,"drop":0,"reload":0.05,"undo":true,"dropped":6,"controlDropped":0,"work":7.45,"frameP95":53.1}, + "resize-tween-px-r30-root-z50": {"pass":false,"tracking":0.72,"pressJump":0,"drop":0,"reload":0.11,"undo":true,"dropped":11,"controlDropped":4,"work":9.73,"frameP95":55.4}, + "resize-tween-xpercent-r0-nested-z100": {"pass":false,"tracking":0.27,"pressJump":0,"drop":204.41,"reload":0,"undo":true,"dropped":4,"controlDropped":0,"work":8.81,"frameP95":35.9}, + "resize-tween-xpercent-r0-nested-z200": {"pass":false,"tracking":0.24,"pressJump":0,"drop":0,"reload":0.03,"undo":true,"dropped":11,"controlDropped":7,"work":9.64,"frameP95":55.61}, + "resize-tween-xpercent-r0-nested-z50": {"pass":false,"tracking":0.24,"pressJump":0,"drop":204.44,"reload":0.07,"undo":true,"dropped":10,"controlDropped":3,"work":8.56,"frameP95":53.11}, + "resize-tween-xpercent-r0-root-z100": {"pass":false,"tracking":0.27,"pressJump":0,"drop":204.41,"reload":0,"undo":true,"dropped":11,"controlDropped":1,"work":10.12,"frameP95":51}, + "resize-tween-xpercent-r0-root-z200": {"pass":false,"tracking":0.24,"pressJump":0,"drop":0,"reload":0.03,"undo":true,"dropped":11,"controlDropped":6,"work":9.49,"frameP95":53}, + "resize-tween-xpercent-r0-root-z50": {"pass":false,"tracking":0.24,"pressJump":0,"drop":204.44,"reload":0.07,"undo":true,"dropped":13,"controlDropped":10,"work":9.43,"frameP95":63.6}, + "resize-tween-xpercent-r30-nested-z100": {"pass":false,"tracking":0.26,"pressJump":0,"drop":204.44,"reload":0,"undo":true,"dropped":16,"controlDropped":7,"work":8.18,"frameP95":64.2}, + "resize-tween-xpercent-r30-nested-z200": {"pass":false,"tracking":0.24,"pressJump":0,"drop":204.4,"reload":0.04,"undo":true,"dropped":15,"controlDropped":12,"work":11.5,"frameP95":65.11}, + "resize-tween-xpercent-r30-nested-z50": {"pass":false,"tracking":0.38,"pressJump":0,"drop":204.44,"reload":0.07,"undo":true,"dropped":12,"controlDropped":8,"work":8.83,"frameP95":58.91}, + "resize-tween-xpercent-r30-root-z100": {"pass":false,"tracking":0.26,"pressJump":0,"drop":204.44,"reload":0,"undo":true,"dropped":6,"controlDropped":7,"work":7.05,"frameP95":40.7}, + "resize-tween-xpercent-r30-root-z200": {"pass":false,"tracking":0.24,"pressJump":0,"drop":204.4,"reload":0.04,"undo":true,"dropped":9,"controlDropped":8,"work":9.52,"frameP95":56.91}, + "resize-tween-xpercent-r30-root-z50": {"pass":false,"tracking":0.38,"pressJump":0,"drop":204.44,"reload":0.07,"undo":true,"dropped":6,"controlDropped":4,"work":7.76,"frameP95":54.41}, + "rotate-hold-center-r0-nested-z100": {"pass":false,"tracking":0.16,"pressJump":0,"drop":0,"reload":0,"undo":true,"dropped":15,"controlDropped":13,"work":5.63,"frameP95":58.31}, + "rotate-hold-center-r0-nested-z200": {"pass":true,"tracking":0.11,"pressJump":0,"drop":0,"reload":0.04,"undo":true,"dropped":12,"controlDropped":12,"work":5.47,"frameP95":61.3}, + "rotate-hold-center-r0-nested-z50": {"pass":false,"tracking":0.25,"pressJump":0,"drop":0,"reload":0.1,"undo":true,"dropped":12,"controlDropped":11,"work":7.03,"frameP95":57.3}, + "rotate-hold-center-r0-root-z100": {"pass":true,"tracking":0.16,"pressJump":0,"drop":0,"reload":0,"undo":true,"dropped":1,"controlDropped":6,"work":4.07,"frameP95":19.2}, + "rotate-hold-center-r0-root-z200": {"pass":false,"tracking":0.11,"pressJump":0,"drop":0,"reload":0.04,"undo":true,"dropped":14,"controlDropped":12,"work":6.18,"frameP95":55.91}, + "rotate-hold-center-r0-root-z50": {"pass":true,"tracking":0.25,"pressJump":0,"drop":0,"reload":0.1,"undo":true,"dropped":8,"controlDropped":10,"work":5.4,"frameP95":49.4}, + "rotate-hold-center-r30-nested-z100": {"pass":false,"tracking":0.17,"pressJump":0,"drop":0,"reload":0,"undo":true,"dropped":17,"controlDropped":13,"work":5.6,"frameP95":68.8}, + "rotate-hold-center-r30-nested-z200": {"pass":true,"tracking":0.11,"pressJump":0,"drop":0,"reload":0.04,"undo":true,"dropped":11,"controlDropped":11,"work":5.3,"frameP95":57.9}, + "rotate-hold-center-r30-nested-z50": {"pass":false,"tracking":0.26,"pressJump":0,"drop":0,"reload":0.11,"undo":true,"dropped":21,"controlDropped":17,"work":6.22,"frameP95":68.9}, + "rotate-hold-center-r30-root-z100": {"pass":false,"tracking":0.17,"pressJump":0,"drop":0,"reload":0,"undo":true,"dropped":15,"controlDropped":13,"work":5.62,"frameP95":63.61}, + "rotate-hold-center-r30-root-z200": {"pass":false,"tracking":0.11,"pressJump":0,"drop":0,"reload":0.04,"undo":true,"dropped":17,"controlDropped":15,"work":5.12,"frameP95":66.4}, + "rotate-hold-center-r30-root-z50": {"pass":false,"tracking":0.26,"pressJump":0,"drop":0,"reload":0.11,"undo":true,"dropped":15,"controlDropped":12,"work":6.01,"frameP95":64.2}, + "rotate-hold-pct-r0-nested-z100": {"pass":false,"tracking":0.15,"pressJump":0,"drop":0,"reload":0,"undo":true,"dropped":6,"controlDropped":3,"work":4.54,"frameP95":42.1}, + "rotate-hold-pct-r0-nested-z200": {"pass":false,"tracking":0.11,"pressJump":0,"drop":0,"reload":0.03,"undo":true,"dropped":10,"controlDropped":2,"work":6.06,"frameP95":57.8}, + "rotate-hold-pct-r0-nested-z50": {"pass":false,"tracking":0.24,"pressJump":0,"drop":0,"reload":0.07,"undo":true,"dropped":6,"controlDropped":0,"work":4.5,"frameP95":38.5}, + "rotate-hold-pct-r0-root-z100": {"pass":false,"tracking":0.15,"pressJump":0,"drop":0,"reload":0,"undo":true,"dropped":6,"controlDropped":5,"work":4.45,"frameP95":53.8}, + "rotate-hold-pct-r0-root-z200": {"pass":true,"tracking":0.11,"pressJump":0,"drop":0,"reload":0.08,"undo":true,"dropped":2,"controlDropped":6,"work":4.15,"frameP95":21.5}, + "rotate-hold-pct-r0-root-z50": {"pass":false,"tracking":0.25,"pressJump":0,"drop":0,"reload":0.05,"undo":true,"dropped":4,"controlDropped":0,"work":4.28,"frameP95":34.3}, + "rotate-hold-pct-r30-nested-z100": {"pass":true,"tracking":0.13,"pressJump":0,"drop":0,"reload":0,"undo":true,"dropped":2,"controlDropped":2,"work":3.87,"frameP95":18.1}, + "rotate-hold-pct-r30-nested-z200": {"pass":true,"tracking":0.11,"pressJump":0,"drop":0,"reload":0.05,"undo":true,"dropped":10,"controlDropped":11,"work":5.41,"frameP95":63.71}, + "rotate-hold-pct-r30-nested-z50": {"pass":false,"tracking":0.29,"pressJump":0,"drop":0,"reload":0.07,"undo":true,"dropped":10,"controlDropped":2,"work":5.25,"frameP95":46.91}, + "rotate-hold-pct-r30-root-z100": {"pass":false,"tracking":0.16,"pressJump":0,"drop":0,"reload":0,"undo":true,"dropped":8,"controlDropped":5,"work":3.85,"frameP95":46.91}, + "rotate-hold-pct-r30-root-z200": {"pass":false,"tracking":0.11,"pressJump":0,"drop":0,"reload":0.04,"undo":true,"dropped":9,"controlDropped":3,"work":5.15,"frameP95":60}, + "rotate-hold-pct-r30-root-z50": {"pass":false,"tracking":0.23,"pressJump":0,"drop":0,"reload":0.07,"undo":true,"dropped":10,"controlDropped":5,"work":6.15,"frameP95":60.91}, + "rotate-hold-px-r0-nested-z100": {"pass":true,"tracking":0.15,"pressJump":0,"drop":0,"reload":0,"undo":true,"dropped":11,"controlDropped":11,"work":5.43,"frameP95":58.41}, + "rotate-hold-px-r0-nested-z200": {"pass":false,"tracking":0.11,"pressJump":0,"drop":0,"reload":0.03,"undo":true,"dropped":12,"controlDropped":11,"work":5.84,"frameP95":60.7}, + "rotate-hold-px-r0-nested-z50": {"pass":true,"tracking":0.24,"pressJump":0,"drop":0,"reload":0.07,"undo":true,"dropped":9,"controlDropped":13,"work":5.31,"frameP95":54}, + "rotate-hold-px-r0-root-z100": {"pass":true,"tracking":0.15,"pressJump":0,"drop":0,"reload":0,"undo":true,"dropped":13,"controlDropped":13,"work":5.33,"frameP95":53.91}, + "rotate-hold-px-r0-root-z200": {"pass":true,"tracking":0.11,"pressJump":0,"drop":0,"reload":0.08,"undo":true,"dropped":14,"controlDropped":14,"work":5.79,"frameP95":68.61}, + "rotate-hold-px-r0-root-z50": {"pass":false,"tracking":0.25,"pressJump":0,"drop":0,"reload":0.05,"undo":true,"dropped":18,"controlDropped":14,"work":5.44,"frameP95":68}, + "rotate-hold-px-r30-nested-z100": {"pass":true,"tracking":0.13,"pressJump":0,"drop":0,"reload":0,"undo":true,"dropped":8,"controlDropped":12,"work":5.71,"frameP95":52.41}, + "rotate-hold-px-r30-nested-z200": {"pass":true,"tracking":0.11,"pressJump":0,"drop":0,"reload":0.05,"undo":true,"dropped":7,"controlDropped":8,"work":4.51,"frameP95":48.91}, + "rotate-hold-px-r30-nested-z50": {"pass":false,"tracking":0.29,"pressJump":0,"drop":0,"reload":0.07,"undo":true,"dropped":10,"controlDropped":2,"work":4.02,"frameP95":49.9}, + "rotate-hold-px-r30-root-z100": {"pass":false,"tracking":0.16,"pressJump":0,"drop":0,"reload":0,"undo":true,"dropped":9,"controlDropped":8,"work":6.03,"frameP95":57.3}, + "rotate-hold-px-r30-root-z200": {"pass":true,"tracking":0.11,"pressJump":0,"drop":0,"reload":0.04,"undo":true,"dropped":0,"controlDropped":11,"work":5.78,"frameP95":20.7}, + "rotate-hold-px-r30-root-z50": {"pass":true,"tracking":0.23,"pressJump":0,"drop":0,"reload":0.07,"undo":true,"dropped":10,"controlDropped":11,"work":5.06,"frameP95":46.11}, + "rotate-hold-xpercent-r0-nested-z100": {"pass":false,"tracking":0.16,"pressJump":0,"drop":0,"reload":0,"undo":true,"dropped":14,"controlDropped":11,"work":4.84,"frameP95":51.31}, + "rotate-hold-xpercent-r0-nested-z200": {"pass":false,"tracking":0.11,"pressJump":0,"drop":0,"reload":0.04,"undo":true,"dropped":11,"controlDropped":3,"work":5.1,"frameP95":63.21}, + "rotate-hold-xpercent-r0-nested-z50": {"pass":false,"tracking":0.25,"pressJump":0,"drop":0,"reload":0.1,"undo":true,"dropped":9,"controlDropped":8,"work":5.19,"frameP95":61}, + "rotate-hold-xpercent-r0-root-z100": {"pass":true,"tracking":0.16,"pressJump":0,"drop":0,"reload":0,"undo":true,"dropped":9,"controlDropped":12,"work":5.04,"frameP95":58.8}, + "rotate-hold-xpercent-r0-root-z200": {"pass":false,"tracking":0.11,"pressJump":0,"drop":0,"reload":0.04,"undo":true,"dropped":6,"controlDropped":2,"work":4.7,"frameP95":41.7}, + "rotate-hold-xpercent-r0-root-z50": {"pass":false,"tracking":0.25,"pressJump":0,"drop":0,"reload":0.1,"undo":true,"dropped":13,"controlDropped":5,"work":6.38,"frameP95":56.91}, + "rotate-hold-xpercent-r30-nested-z100": {"pass":true,"tracking":0.17,"pressJump":0,"drop":0,"reload":0,"undo":true,"dropped":15,"controlDropped":15,"work":7.12,"frameP95":70.91}, + "rotate-hold-xpercent-r30-nested-z200": {"pass":false,"tracking":0.11,"pressJump":0,"drop":0,"reload":0.04,"undo":true,"dropped":14,"controlDropped":12,"work":6.04,"frameP95":64.31}, + "rotate-hold-xpercent-r30-nested-z50": {"pass":true,"tracking":0.26,"pressJump":0,"drop":0,"reload":0.11,"undo":true,"dropped":13,"controlDropped":13,"work":5.9,"frameP95":60}, + "rotate-hold-xpercent-r30-root-z100": {"pass":true,"tracking":0.17,"pressJump":0,"drop":0,"reload":0,"undo":true,"dropped":14,"controlDropped":15,"work":6.54,"frameP95":68}, + "rotate-hold-xpercent-r30-root-z200": {"pass":true,"tracking":0.11,"pressJump":0,"drop":0,"reload":0.04,"undo":true,"dropped":11,"controlDropped":17,"work":4.5,"frameP95":54.5}, + "rotate-hold-xpercent-r30-root-z50": {"pass":false,"tracking":0.26,"pressJump":0,"drop":0,"reload":0.11,"undo":true,"dropped":15,"controlDropped":12,"work":6.2,"frameP95":65.21}, + "rotate-none-center-r0-nested-z100": {"pass":false,"tracking":0.16,"pressJump":0,"drop":62.46,"reload":0,"undo":true,"dropped":14,"controlDropped":12,"work":7.2,"frameP95":65.1}, + "rotate-none-center-r0-nested-z200": {"pass":false,"tracking":0.11,"pressJump":0,"drop":62.44,"reload":0.03,"undo":true,"dropped":12,"controlDropped":10,"work":6.01,"frameP95":63.3}, + "rotate-none-center-r0-nested-z50": {"pass":false,"tracking":0.24,"pressJump":0,"drop":62.43,"reload":0.05,"undo":true,"dropped":12,"controlDropped":10,"work":6.33,"frameP95":60.41}, + "rotate-none-center-r0-root-z100": {"pass":false,"tracking":0.16,"pressJump":0,"drop":0,"reload":0,"undo":true,"dropped":9,"controlDropped":8,"work":5.58,"frameP95":56.6}, + "rotate-none-center-r0-root-z200": {"pass":false,"tracking":0.11,"pressJump":0,"drop":0,"reload":0.03,"undo":true,"dropped":11,"controlDropped":10,"work":5.38,"frameP95":59.9}, + "rotate-none-center-r0-root-z50": {"pass":false,"tracking":0.24,"pressJump":0,"drop":0,"reload":0.07,"undo":true,"dropped":9,"controlDropped":3,"work":4.18,"frameP95":56.21}, + "rotate-none-center-r30-nested-z100": {"pass":false,"tracking":0.14,"pressJump":0,"drop":62.46,"reload":0,"undo":true,"dropped":6,"controlDropped":9,"work":5.3,"frameP95":55.91}, + "rotate-none-center-r30-nested-z200": {"pass":false,"tracking":0.12,"pressJump":0,"drop":62.43,"reload":0.04,"undo":true,"dropped":4,"controlDropped":6,"work":4.46,"frameP95":42.5}, + "rotate-none-center-r30-nested-z50": {"pass":false,"tracking":0.28,"pressJump":0,"drop":62.47,"reload":0.07,"undo":true,"dropped":4,"controlDropped":1,"work":4.86,"frameP95":39.5}, + "rotate-none-center-r30-root-z100": {"pass":false,"tracking":0.14,"pressJump":0,"drop":74.67,"reload":0,"undo":true,"dropped":11,"controlDropped":3,"work":5.28,"frameP95":54.5}, + "rotate-none-center-r30-root-z200": {"pass":false,"tracking":0.12,"pressJump":0,"drop":74.66,"reload":0.03,"undo":true,"dropped":4,"controlDropped":5,"work":7,"frameP95":37.8}, + "rotate-none-center-r30-root-z50": {"pass":false,"tracking":0.28,"pressJump":0,"drop":74.73,"reload":0.07,"undo":true,"dropped":12,"controlDropped":12,"work":6.5,"frameP95":52.31}, + "rotate-none-pct-r0-nested-z100": {"pass":false,"tracking":0.15,"pressJump":0,"drop":62.43,"reload":0,"undo":true,"dropped":12,"controlDropped":11,"work":5.51,"frameP95":64}, + "rotate-none-pct-r0-nested-z200": {"pass":false,"tracking":0.11,"pressJump":0,"drop":62.44,"reload":0.02,"undo":true,"dropped":14,"controlDropped":12,"work":5.01,"frameP95":64.11}, + "rotate-none-pct-r0-nested-z50": {"pass":false,"tracking":0.24,"pressJump":0,"drop":62.49,"reload":0.05,"undo":true,"dropped":11,"controlDropped":11,"work":5.06,"frameP95":57.9}, + "rotate-none-pct-r0-root-z100": {"pass":false,"tracking":0.15,"pressJump":0,"drop":0,"reload":0,"undo":true,"dropped":13,"controlDropped":11,"work":5.44,"frameP95":56.61}, + "rotate-none-pct-r0-root-z200": {"pass":false,"tracking":0.11,"pressJump":0,"drop":0,"reload":0.03,"undo":true,"dropped":11,"controlDropped":6,"work":6.01,"frameP95":54.91}, + "rotate-none-pct-r0-root-z50": {"pass":false,"tracking":0.24,"pressJump":0,"drop":0,"reload":0.05,"undo":true,"dropped":14,"controlDropped":7,"work":6.35,"frameP95":67.5}, + "rotate-none-pct-r30-nested-z100": {"pass":false,"tracking":0.14,"pressJump":0,"drop":62.47,"reload":0,"undo":true,"dropped":8,"controlDropped":0,"work":5.23,"frameP95":51.7}, + "rotate-none-pct-r30-nested-z200": {"pass":false,"tracking":0.11,"pressJump":0,"drop":62.44,"reload":0.06,"undo":true,"dropped":0,"controlDropped":9,"work":6.07,"frameP95":21.41}, + "rotate-none-pct-r30-nested-z50": {"pass":false,"tracking":0.27,"pressJump":0,"drop":62.45,"reload":0.05,"undo":true,"dropped":6,"controlDropped":4,"work":4.92,"frameP95":46.6}, + "rotate-none-pct-r30-root-z100": {"pass":false,"tracking":0.17,"pressJump":0,"drop":74.71,"reload":0,"undo":true,"dropped":14,"controlDropped":12,"work":4.55,"frameP95":65.31}, + "rotate-none-pct-r30-root-z200": {"pass":false,"tracking":0.11,"pressJump":0,"drop":74.66,"reload":0.03,"undo":true,"dropped":5,"controlDropped":9,"work":4.26,"frameP95":47.41}, + "rotate-none-pct-r30-root-z50": {"pass":false,"tracking":0.31,"pressJump":0,"drop":74.77,"reload":0.05,"undo":true,"dropped":15,"controlDropped":10,"work":5.5,"frameP95":59.11}, + "rotate-none-px-r0-nested-z100": {"pass":false,"tracking":0.16,"pressJump":0,"drop":62.43,"reload":0,"undo":true,"dropped":0,"controlDropped":0,"work":5.5,"frameP95":17.8}, + "rotate-none-px-r0-nested-z200": {"pass":false,"tracking":0.11,"pressJump":0,"drop":62.44,"reload":0.04,"undo":true,"dropped":3,"controlDropped":10,"work":4.13,"frameP95":38.7}, + "rotate-none-px-r0-nested-z50": {"pass":false,"tracking":0.24,"pressJump":0,"drop":62.43,"reload":0.07,"undo":true,"dropped":0,"controlDropped":5,"work":4.32,"frameP95":17.8}, + "rotate-none-px-r0-root-z100": {"pass":true,"tracking":0.15,"pressJump":0,"drop":0,"reload":0,"undo":true,"dropped":7,"controlDropped":8,"work":6.24,"frameP95":48.21}, + "rotate-none-px-r0-root-z200": {"pass":true,"tracking":0.11,"pressJump":0,"drop":0,"reload":0.08,"undo":true,"dropped":4,"controlDropped":6,"work":4.29,"frameP95":32.41}, + "rotate-none-px-r0-root-z50": {"pass":false,"tracking":0.24,"pressJump":0,"drop":0,"reload":0.05,"undo":true,"dropped":5,"controlDropped":0,"work":4.93,"frameP95":29}, + "rotate-none-px-r30-nested-z100": {"pass":false,"tracking":0.15,"pressJump":0,"drop":62.45,"reload":0,"undo":true,"dropped":4,"controlDropped":13,"work":3.9,"frameP95":46.6}, + "rotate-none-px-r30-nested-z200": {"pass":false,"tracking":0.12,"pressJump":0,"drop":62.43,"reload":0.05,"undo":true,"dropped":9,"controlDropped":4,"work":5.14,"frameP95":45.8}, + "rotate-none-px-r30-nested-z50": {"pass":false,"tracking":0.27,"pressJump":0,"drop":62.47,"reload":0.07,"undo":true,"dropped":8,"controlDropped":0,"work":4.71,"frameP95":59.71}, + "rotate-none-px-r30-root-z100": {"pass":false,"tracking":0.13,"pressJump":0,"drop":74.68,"reload":0,"undo":true,"dropped":10,"controlDropped":6,"work":4.94,"frameP95":56.3}, + "rotate-none-px-r30-root-z200": {"pass":false,"tracking":0.11,"pressJump":0,"drop":74.71,"reload":0.08,"undo":true,"dropped":5,"controlDropped":9,"work":4.66,"frameP95":45.8}, + "rotate-none-px-r30-root-z50": {"pass":false,"tracking":0.26,"pressJump":0,"drop":74.65,"reload":0.05,"undo":true,"dropped":5,"controlDropped":9,"work":4.96,"frameP95":53.3}, + "rotate-tween-center-r0-nested-z100": {"pass":true,"tracking":0.15,"pressJump":0,"drop":0,"reload":0,"undo":true,"dropped":11,"controlDropped":12,"work":6.08,"frameP95":53}, + "rotate-tween-center-r0-nested-z200": {"pass":true,"tracking":0.11,"pressJump":0,"drop":0,"reload":0.07,"undo":true,"dropped":11,"controlDropped":11,"work":5.7,"frameP95":52.21}, + "rotate-tween-center-r0-nested-z50": {"pass":false,"tracking":0.24,"pressJump":0,"drop":0,"reload":0.07,"undo":true,"dropped":13,"controlDropped":6,"work":4.94,"frameP95":52.1}, + "rotate-tween-center-r0-root-z100": {"pass":false,"tracking":0.15,"pressJump":0,"drop":0,"reload":0,"undo":true,"dropped":13,"controlDropped":10,"work":4.98,"frameP95":48.91}, + "rotate-tween-center-r0-root-z200": {"pass":true,"tracking":0.11,"pressJump":0,"drop":0,"reload":0.07,"undo":true,"dropped":4,"controlDropped":7,"work":4.46,"frameP95":33.11}, + "rotate-tween-center-r0-root-z50": {"pass":false,"tracking":0.24,"pressJump":0,"drop":0,"reload":0.07,"undo":true,"dropped":7,"controlDropped":5,"work":4.62,"frameP95":46.5}, + "rotate-tween-center-r30-nested-z100": {"pass":false,"tracking":0.15,"pressJump":0,"drop":0,"reload":0,"undo":true,"dropped":13,"controlDropped":12,"work":4.93,"frameP95":63.8}, + "rotate-tween-center-r30-nested-z200": {"pass":true,"tracking":0.12,"pressJump":0,"drop":0,"reload":0.03,"undo":true,"dropped":13,"controlDropped":15,"work":4.89,"frameP95":65.3}, + "rotate-tween-center-r30-nested-z50": {"pass":true,"tracking":0.3,"pressJump":0,"drop":0,"reload":0.07,"undo":true,"dropped":11,"controlDropped":11,"work":4.44,"frameP95":62.1}, + "rotate-tween-center-r30-root-z100": {"pass":false,"tracking":0.15,"pressJump":0,"drop":0,"reload":0,"undo":true,"dropped":8,"controlDropped":7,"work":5.54,"frameP95":56}, + "rotate-tween-center-r30-root-z200": {"pass":false,"tracking":0.12,"pressJump":0,"drop":0,"reload":0.03,"undo":true,"dropped":13,"controlDropped":12,"work":5.5,"frameP95":64.2}, + "rotate-tween-center-r30-root-z50": {"pass":false,"tracking":0.3,"pressJump":0,"drop":0,"reload":0.07,"undo":true,"dropped":13,"controlDropped":10,"work":4.9,"frameP95":59.91}, + "rotate-tween-pct-r0-nested-z100": {"pass":false,"tracking":0.15,"pressJump":0,"drop":0,"reload":0,"undo":true,"dropped":13,"controlDropped":11,"work":6.07,"frameP95":58.9}, + "rotate-tween-pct-r0-nested-z200": {"pass":true,"tracking":0.11,"pressJump":0,"drop":0,"reload":0.03,"undo":true,"dropped":13,"controlDropped":13,"work":6.5,"frameP95":55.1}, + "rotate-tween-pct-r0-nested-z50": {"pass":false,"tracking":0.24,"pressJump":0,"drop":0,"reload":0.07,"undo":true,"dropped":14,"controlDropped":13,"work":6.72,"frameP95":64.71}, + "rotate-tween-pct-r0-root-z100": {"pass":true,"tracking":0.16,"pressJump":0,"drop":0,"reload":0,"undo":true,"dropped":14,"controlDropped":15,"work":5.91,"frameP95":63}, + "rotate-tween-pct-r0-root-z200": {"pass":false,"tracking":0.11,"pressJump":0,"drop":0,"reload":0.04,"undo":true,"dropped":14,"controlDropped":11,"work":5.86,"frameP95":67.5}, + "rotate-tween-pct-r0-root-z50": {"pass":false,"tracking":0.25,"pressJump":0,"drop":0,"reload":0.1,"undo":true,"dropped":16,"controlDropped":14,"work":5.31,"frameP95":65.3}, + "rotate-tween-pct-r30-nested-z100": {"pass":true,"tracking":0.14,"pressJump":0,"drop":0,"reload":0,"undo":true,"dropped":1,"controlDropped":6,"work":4.32,"frameP95":18.5}, + "rotate-tween-pct-r30-nested-z200": {"pass":false,"tracking":0.12,"pressJump":0,"drop":0,"reload":0.06,"undo":true,"dropped":10,"controlDropped":5,"work":6.39,"frameP95":61}, + "rotate-tween-pct-r30-nested-z50": {"pass":false,"tracking":0.26,"pressJump":0,"drop":0,"reload":0.07,"undo":true,"dropped":3,"controlDropped":0,"work":4.16,"frameP95":25.11}, + "rotate-tween-pct-r30-root-z100": {"pass":false,"tracking":0.17,"pressJump":0,"drop":0,"reload":0,"undo":true,"dropped":8,"controlDropped":6,"work":3.87,"frameP95":59.71}, + "rotate-tween-pct-r30-root-z200": {"pass":false,"tracking":0.11,"pressJump":0,"drop":0,"reload":0.05,"undo":true,"dropped":10,"controlDropped":2,"work":4.3,"frameP95":56.21}, + "rotate-tween-pct-r30-root-z50": {"pass":true,"tracking":0.28,"pressJump":0,"drop":0,"reload":0.07,"undo":true,"dropped":0,"controlDropped":0,"work":4.43,"frameP95":22.1}, + "rotate-tween-px-r0-nested-z100": {"pass":false,"tracking":0.15,"pressJump":0,"drop":0,"reload":0,"undo":true,"dropped":7,"controlDropped":4,"work":5.67,"frameP95":49.6}, + "rotate-tween-px-r0-nested-z200": {"pass":true,"tracking":0.11,"pressJump":0,"drop":0,"reload":0.04,"undo":true,"dropped":5,"controlDropped":6,"work":6.45,"frameP95":52.8}, + "rotate-tween-px-r0-nested-z50": {"pass":true,"tracking":0.24,"pressJump":0,"drop":0,"reload":0.11,"undo":true,"dropped":0,"controlDropped":11,"work":4.59,"frameP95":18.3}, + "rotate-tween-px-r0-root-z100": {"pass":true,"tracking":0.16,"pressJump":0,"drop":0,"reload":0,"undo":true,"dropped":7,"controlDropped":9,"work":5.28,"frameP95":31.21}, + "rotate-tween-px-r0-root-z200": {"pass":false,"tracking":0.11,"pressJump":0,"drop":0,"reload":0.02,"undo":true,"dropped":10,"controlDropped":1,"work":4.38,"frameP95":47.8}, + "rotate-tween-px-r0-root-z50": {"pass":false,"tracking":0.24,"pressJump":0,"drop":0,"reload":0.05,"undo":true,"dropped":7,"controlDropped":4,"work":3.65,"frameP95":44.1}, + "rotate-tween-px-r30-nested-z100": {"pass":true,"tracking":0.17,"pressJump":0,"drop":0,"reload":0,"undo":true,"dropped":3,"controlDropped":7,"work":4.29,"frameP95":25.6}, + "rotate-tween-px-r30-nested-z200": {"pass":false,"tracking":0.11,"pressJump":0,"drop":0,"reload":0.03,"undo":true,"dropped":11,"controlDropped":10,"work":6,"frameP95":60.4}, + "rotate-tween-px-r30-nested-z50": {"pass":true,"tracking":0.27,"pressJump":0,"drop":0,"reload":0.07,"undo":true,"dropped":6,"controlDropped":7,"work":6.19,"frameP95":54.71}, + "rotate-tween-px-r30-root-z100": {"pass":false,"tracking":0.14,"pressJump":0,"drop":0,"reload":0,"undo":true,"dropped":4,"controlDropped":2,"work":3.84,"frameP95":36.5}, + "rotate-tween-px-r30-root-z200": {"pass":false,"tracking":0.11,"pressJump":0,"drop":0,"reload":0.08,"undo":true,"dropped":7,"controlDropped":3,"work":3.77,"frameP95":55.91}, + "rotate-tween-px-r30-root-z50": {"pass":false,"tracking":0.31,"pressJump":0,"drop":0,"reload":0.11,"undo":true,"dropped":9,"controlDropped":2,"work":5.11,"frameP95":52.21}, + "rotate-tween-xpercent-r0-nested-z100": {"pass":true,"tracking":0.15,"pressJump":0,"drop":0,"reload":0,"undo":true,"dropped":5,"controlDropped":5,"work":4.72,"frameP95":52.5}, + "rotate-tween-xpercent-r0-nested-z200": {"pass":false,"tracking":0.11,"pressJump":0,"drop":0,"reload":0.07,"undo":true,"dropped":8,"controlDropped":4,"work":4.59,"frameP95":39.7}, + "rotate-tween-xpercent-r0-nested-z50": {"pass":false,"tracking":0.24,"pressJump":0,"drop":0,"reload":0.07,"undo":true,"dropped":4,"controlDropped":2,"work":7.08,"frameP95":36.1}, + "rotate-tween-xpercent-r0-root-z100": {"pass":false,"tracking":0.15,"pressJump":0,"drop":0,"reload":0,"undo":true,"dropped":10,"controlDropped":0,"work":4.29,"frameP95":50.71}, + "rotate-tween-xpercent-r0-root-z200": {"pass":false,"tracking":0.11,"pressJump":0,"drop":0,"reload":0.07,"undo":true,"dropped":8,"controlDropped":7,"work":6.12,"frameP95":53.5}, + "rotate-tween-xpercent-r0-root-z50": {"pass":false,"tracking":0.24,"pressJump":0,"drop":0,"reload":0.07,"undo":true,"dropped":6,"controlDropped":5,"work":4.28,"frameP95":48}, + "rotate-tween-xpercent-r30-nested-z100": {"pass":false,"tracking":0.15,"pressJump":0,"drop":0,"reload":0,"undo":true,"dropped":12,"controlDropped":11,"work":6.33,"frameP95":62.91}, + "rotate-tween-xpercent-r30-nested-z200": {"pass":false,"tracking":0.12,"pressJump":0,"drop":0,"reload":0.03,"undo":true,"dropped":16,"controlDropped":14,"work":6.19,"frameP95":66.91}, + "rotate-tween-xpercent-r30-nested-z50": {"pass":false,"tracking":0.3,"pressJump":0,"drop":0,"reload":0.07,"undo":true,"dropped":10,"controlDropped":8,"work":5.44,"frameP95":61}, + "rotate-tween-xpercent-r30-root-z100": {"pass":false,"tracking":0.15,"pressJump":0,"drop":0,"reload":0,"undo":true,"dropped":2,"controlDropped":0,"work":4.89,"frameP95":23}, + "rotate-tween-xpercent-r30-root-z200": {"pass":true,"tracking":0.12,"pressJump":0,"drop":0,"reload":0.03,"undo":true,"dropped":9,"controlDropped":12,"work":5.69,"frameP95":57.91}, + "rotate-tween-xpercent-r30-root-z50": {"pass":false,"tracking":0.3,"pressJump":0,"drop":0,"reload":0.07,"undo":true,"dropped":5,"controlDropped":4,"work":5.37,"frameP95":41.61} + } +} diff --git a/packages/studio/tests/e2e/edit-accuracy/case.mjs b/packages/studio/tests/e2e/edit-accuracy/case.mjs new file mode 100644 index 0000000000..2f83619c1a --- /dev/null +++ b/packages/studio/tests/e2e/edit-accuracy/case.mjs @@ -0,0 +1,704 @@ +/** Drives one edit accuracy case in the built Studio and measures it. All distances are composition px. */ +import { spawn } from "node:child_process"; +import { readFileSync } from "node:fs"; +import { join } from "node:path"; +import { COMPOSITION, PLAYHEAD } from "./grid.mjs"; +import { + angleOf, + centre, + compositionMapper, + dist, + localToQuad, + mid, + normalizeAngle, + parseInset, + percentile, + quadDistance, + quadToLocal, + toPoints, + visibleQuad, +} from "./geometry.mjs"; + +const VIEWPORT = { width: 1600, height: 900 }; +const STEPS = 20; +const MOVE_BY = [90, 60]; +const RESIZE_BY = 60; +const ROTATE_BY = (25 * Math.PI) / 180; +const CROP_BY = 40; +const NUDGES = 5; +const ZOOM_SENSITIVITY = 0.007; // previewZoom.ts: one wheel unit scales zoom by exp(0.007) + +const sleep = (ms) => new Promise((r) => setTimeout(r, ms)); + +const up = (port) => + fetch(`http://127.0.0.1:${port}/api/projects`).then( + (r) => r.ok, + () => false, + ); +const liveServers = new Set(); +/** Signals the server's process group; a group that already exited is not an error. */ +function signalGroup(child, signal) { + try { + process.kill(-child.pid, signal); + } catch (error) { + if (error.code !== "ESRCH") throw error; + } +} +/** Signal-safe cleanup: servers run in their own process group so the CLI's children go with them. */ +export function killServers() { + for (const child of liveServers) signalGroup(child, "SIGKILL"); +} + +const announcedPort = (log) => /http:\/\/localhost:(\d+)/.exec(log.join(""))?.[1]; + +// fallow-ignore-next-line complexity +export async function startServer(cli, dir, port, log, home) { + // The CLI quietly takes the next free port when asked for a busy one, so only the port it announces counts. + if (await up(port)) throw new Error(`port ${port} is already serving`); + const child = spawn( + "node", + [cli, "preview", dir, "--port", String(port), "--no-open", "--foreground", "--force-new"], + { + stdio: ["ignore", "pipe", "pipe"], + detached: true, + // A per-case HOME keeps Studio's undo history inside the case's tmp dir. + env: { + ...process.env, + HOME: home, + HYPERFRAMES_NO_TELEMETRY: "1", + HYPERFRAMES_NO_UPDATE_CHECK: "1", + }, + }, + ); + liveServers.add(child); + child.once("exit", () => liveServers.delete(child)); + child.stdout.on("data", (d) => log.push(String(d))); + child.stderr.on("data", (d) => log.push(String(d))); + for (const deadline = Date.now() + 60_000; Date.now() < deadline; await sleep(200)) { + if (child.exitCode !== null) + throw new Error(`studio exited ${child.exitCode}: ${log.join("").slice(-500)}`); + const announced = announcedPort(log); + if (announced && announced !== String(port)) { + await stopServer(child); + throw new Error(`studio moved from port ${port} to ${announced}`); + } + if (announced && (await up(port))) return child; + } + await stopServer(child); + throw new Error("studio did not start in 60s"); +} + +export async function stopServer(child) { + if (child.exitCode !== null) return; + const exited = new Promise((r) => child.once("exit", r)); + signalGroup(child, "SIGTERM"); + if (await Promise.race([exited.then(() => true), sleep(5000)])) return; + signalGroup(child, "SIGKILL"); + await exited; +} + +/** Runs in the top frame before Studio: the WebMCP host plus a frame-interval and long-task recorder. */ +function instrumentPage() { + if (window.top !== window) return; + const tools = new Map(); + Object.defineProperty(document, "modelContext", { + configurable: true, + value: { registerTool: async (tool) => void tools.set(tool.name, tool) }, + }); + const rec = { on: false, frames: [], long: [] }; + const call = (name, input) => + tools.get(name).execute(input, { signal: new AbortController().signal }); + window.__editBench = { has: (name) => tools.has(name), call, rec }; + // The callback's own clock: Chrome stamps a late frame with the vsync it missed, which hides a stall. + const loop = () => { + if (rec.on) rec.frames.push(performance.now()); + requestAnimationFrame(loop); + }; + requestAnimationFrame(loop); + new PerformanceObserver((list) => { + if (rec.on) for (const e of list.getEntries()) rec.long.push(e.duration); + }).observe({ type: "longtask" }); +} + +const nextFrame = (page, n = 1) => + page.evaluate( + (count) => + new Promise((r) => { + const tick = (left) => (left ? requestAnimationFrame(() => tick(left - 1)) : r()); + tick(count); + }), + n, + ); + +function readFiles(dir, files) { + return Object.fromEntries(files.map((f) => [f, readFileSync(join(dir, f), "utf8")])); +} +const sameFiles = (a, b) => Object.keys(a).every((f) => a[f] === b[f]); + +/** Waits until the files differ from `from` (or equal `want`, or just exist) and then hold still for 300 ms. */ +// fallow-ignore-next-line complexity +async function waitForFiles(ctx, { from, want, timeout = 5000 }) { + const deadline = Date.now() + timeout; + let last = readFiles(ctx.dir, ctx.files); + let stableSince = Date.now(); + for (; Date.now() < deadline; await sleep(50)) { + const now = readFiles(ctx.dir, ctx.files); + if (!sameFiles(now, last)) [last, stableSince] = [now, Date.now()]; + const reached = want ? sameFiles(now, want) : !from || !sameFiles(now, from); + if (reached && Date.now() - stableSince >= 300) return { reached: true, files: now }; + } + return { reached: false, files: last }; +} + +// fallow-ignore-next-line complexity +async function previewCandidate(frame) { + const target = await frame.$("#target"); + const box = target && (await (await frame.frameElement())?.boundingBox()); + return box && { area: box.width * box.height, frame, target }; +} + +/** The largest visible preview iframe holding the target; a frame Studio detaches mid-scan is skipped. */ +async function findTarget(page) { + const previews = page.frames().filter((f) => f.url().includes("/preview")); + const found = await Promise.all(previews.map((f) => previewCandidate(f).catch(() => null))); + return found.filter(Boolean).reduce((a, b) => (!a || b.area > a.area ? b : a), null); +} + +// The handle's own session: remote object ids do not resolve in any other CDP session. +async function contentQuad(handle) { + const { quads } = await handle.client.send("DOM.getContentQuads", { + objectId: handle.remoteObject().objectId, + }); + if (!quads.length) throw new Error("element has no rendered box"); + return toPoints(quads[0]); +} + +async function findHandles(ctx) { + const found = await findTarget(ctx.page); + if (!found) throw new Error("target not found in preview"); + ctx.handles = { target: found.target, root: await found.frame.$('[data-composition-id="main"]') }; +} + +async function readQuads({ handles }) { + return Promise.all([ + contentQuad(handles.root), + contentQuad(handles.target), + handles.target.evaluate((e) => ({ + width: e.offsetWidth, + height: e.offsetHeight, + clip: getComputedStyle(e).clipPath, + })), + ]); +} + +/** The target's rendered quad, visible (cropped) quad and the screen/composition mapping, from CDP quads. */ +async function measure(ctx) { + // Studio can swap the preview into a fresh iframe; a cached handle then reads a hidden copy, so find it again. + let read = null; + for (let attempt = 0; !read; attempt++) { + read = await (ctx.handles ? readQuads(ctx) : Promise.reject(new Error("no handles"))).catch( + async (error) => { + if (attempt === 5) throw error; + await sleep(100); + await findHandles(ctx).catch(() => undefined); + return null; + }, + ); + } + const [rootQuad, targetQuad, box] = read; + const map = compositionMapper(rootQuad, COMPOSITION); + const quad = targetQuad.map(map.toComp); + const size = { width: box.width, height: box.height }; + return { map, quad, size, visible: visibleQuad(quad, size, parseInset(box.clip)) }; +} + +const STILL_MS = 1000; + +// Studio reloads an edited preview in a shadow iframe (`_t` in its URL) and swaps it in when painted. +const previewFrames = (page) => + page + .frames() + .map((f) => f.url()) + .filter((u) => u.includes("/preview")) + .join(" "); + +/** Measures once the preview frames and the box have held still for STILL_MS; Studio updates both after a save. */ +// fallow-ignore-next-line complexity +async function settled(ctx, timeout = 15_000) { + const deadline = Date.now() + timeout; + let start = { m: await measure(ctx), frames: previewFrames(ctx.page) }; + let now = start; + // Compared with the window's first read, so a drift too slow to show read to read still restarts it. + for (let since = Date.now(); Date.now() - since < STILL_MS; ) { + // A preview that never holds still is a Studio defect: the metrics it feeds fail, the rest still count. + if (Date.now() > deadline) return { ...now.m, unsettled: true }; + await nextFrame(ctx.page); + now = { m: await measure(ctx), frames: previewFrames(ctx.page) }; + if (now.frames !== start.frames || quadDistance(now.m.visible, start.m.visible) >= 0.01) + [start, since] = [now, Date.now()]; + } + return now.m; +} + +/** Ready once Studio's own seek tool reports the composition and the playhead landed. */ +// fallow-ignore-next-line complexity +async function openStudio(ctx) { + ctx.handles = null; + await ctx.page.waitForFunction(() => window.__editBench?.has("studio_seek"), { timeout: 90_000 }); + let seek = null; + for (const deadline = Date.now() + 30_000; Date.now() < deadline; await sleep(250)) { + seek = await ctx.page + .evaluate((time) => window.__editBench.call("studio_seek", { time }), PLAYHEAD) + .catch(String); + if (seek?.ok && seek.duration > 0 && seek.playhead === PLAYHEAD && (await findTarget(ctx.page))) + break; + seek = null; + } + if (!seek) throw new Error("studio never reported a seekable composition"); + await sleep(1000); + return settled(ctx); +} + +/** Puppeteer presses one key at a time: hold the modifiers around the last key. */ +async function chord(page, keys) { + const [key, ...mods] = keys.split("+").reverse(); + for (const m of mods) await page.keyboard.down(m); + await page.keyboard.press(key); + for (const m of mods) await page.keyboard.up(m); +} + +async function blurPreview(page) { + await page.evaluate(() => { + if (document.activeElement?.tagName === "IFRAME") document.activeElement.blur(); + }); +} + +/** Snapping deliberately pulls the box off the pointer, so the bench turns it off with Studio's own toggle. */ +async function disableSnap(page) { + const title = await page.$eval('[aria-label="Toggle snap"]', (b) => b.title); + if (/enabled/i.test(title)) await page.click('[aria-label="Toggle snap"]'); + const after = await page.$eval('[aria-label="Toggle snap"]', (b) => b.title); + if (!/disabled/i.test(after)) throw new Error(`snap toggle did not turn off: ${after}`); +} + +const zoomOf = (page) => + page.$eval('[data-testid="preview-zoom-stage"]', (e) => { + const m = /scale\(([\d.]+)\)/.exec(e.style.transform); + return m ? Number(m[1]) * 100 : 100; + }); + +/** Ctrl+wheel over the target, as a person zooms; wheel units are solved exactly from the zoom law. */ +async function setZoom(ctx, percent, anchor) { + if (percent === 100) return 100; + await ctx.page.mouse.move(anchor[0], anchor[1]); + await ctx.page.keyboard.down("Control"); + let units = Math.log(percent / (await zoomOf(ctx.page))) / ZOOM_SENSITIVITY; + while (Math.abs(units) > 1e-9) { + const step = Math.max(-10, Math.min(10, units)); + await ctx.page.mouse.wheel({ deltaY: -step }); + units -= step; + } + await ctx.page.keyboard.up("Control"); + await sleep(400); + const achieved = await zoomOf(ctx.page); + if (Math.abs(achieved - percent) > 0.5) + throw new Error(`zoom ${percent}% landed at ${achieved}%`); + return achieved; +} + +const overlayRect = (page, selector) => + page.$$eval(selector, (els) => + els.map((e) => { + const r = e.getBoundingClientRect(); + return { + x: r.left + r.width / 2, + y: r.top + r.height / 2, + w: r.width, + h: r.height, + label: e.getAttribute("aria-label"), + }; + }), + ); + +async function selectTarget(ctx, m) { + const c = m.map.toScreen(centre(m.visible)); + const want = m.visible.map(m.map.toScreen); + const isSelected = async () => { + const [box] = await overlayRect(ctx.page, "[data-dom-edit-selection-box]"); + if (!box) return false; + const xs = want.map((p) => p[0]); + const ys = want.map((p) => p[1]); + const w = Math.max(...xs) - Math.min(...xs); + const h = Math.max(...ys) - Math.min(...ys); + return Math.abs(box.w - w) < 4 && Math.abs(box.h - h) < 4 && dist([box.x, box.y], c) < 4; + }; + const waitSelected = async () => { + for (const deadline = Date.now() + 2000; Date.now() < deadline; await sleep(100)) + if (await isSelected()) return true; + return false; + }; + await ctx.page.mouse.click(c[0], c[1]); + if (await waitSelected()) return; + // A nested composition's child takes a second click to enter the composition. + await ctx.page.mouse.click(c[0], c[1], { count: 2 }); + if (!(await waitSelected())) { + const boxes = await overlayRect(ctx.page, "[data-dom-edit-selection-box]"); + throw new Error( + `could not select #target at ${c.map(Math.round)}; selection ${JSON.stringify(boxes)}`, + ); + } +} + +// fallow-ignore-next-line complexity +async function handlePoint(ctx, m, gesture) { + if (gesture === "move" || gesture === "nudge") return m.map.toScreen(centre(m.visible)); + if (gesture === "rotate") { + const [h] = await overlayRect(ctx.page, '[aria-label="Rotate selection"]'); + if (!h) throw new Error("no rotate handle"); + return [h.x, h.y]; + } + if (gesture === "crop") { + const [h] = await overlayRect(ctx.page, '[aria-label="Crop right"]'); + if (!h) throw new Error("no crop right handle"); + return [h.x, h.y]; + } + const corner = m.map.toScreen(m.visible[2]); + const dots = await overlayRect(ctx.page, "div.pointer-events-auto.absolute.h-4.w-4"); + if (!dots.length) throw new Error("no resize handles"); + const dot = dots.reduce((a, b) => (dist([a.x, a.y], corner) <= dist([b.x, b.y], corner) ? a : b)); + return [dot.x, dot.y]; +} + +async function cropOutline(ctx, map) { + const h = await ctx.page.$("[data-dom-edit-crop-frame] > div.border-dashed"); + if (!h) throw new Error("no crop outline"); + return (await contentQuad(h)).map(map.toComp); +} + +/** Screen path (one point per frame) and the per-frame tracking error for each pointer gesture. */ +function plan(gesture, pre, pressComp) { + const at = (p) => pre.map.toScreen(p); + const steps = Array.from({ length: STEPS }, (_, i) => (i + 1) / STEPS); + const follow = (point) => ({ + point, + error: (s, s0) => + dist([s.p[0] - s0.p[0], s.p[1] - s0.p[1]], [s.c[0] - s0.c[0], s.c[1] - s0.c[1]]), + }); + if (gesture === "move") { + const local = quadToLocal(pre.quad, pre.size, pressComp); + return { + path: steps.map((k) => at([pressComp[0] + MOVE_BY[0] * k, pressComp[1] + MOVE_BY[1] * k])), + ...follow((m) => localToQuad(m.quad, m.size, local)), + }; + } + if (gesture === "resize") { + const c = centre(pre.visible); + const d = dist(pre.visible[2], c); + const u = [(pre.visible[2][0] - c[0]) / d, (pre.visible[2][1] - c[1]) / d]; + return { + path: steps.map((k) => + at([pressComp[0] + u[0] * RESIZE_BY * k, pressComp[1] + u[1] * RESIZE_BY * k]), + ), + ...follow((m) => m.visible[2]), + }; + } + if (gesture === "crop") { + const n = [pre.quad[0][0] - pre.quad[1][0], pre.quad[0][1] - pre.quad[1][1]]; + const len = Math.hypot(...n); + return { + path: steps.map((k) => + at([pressComp[0] + (n[0] / len) * CROP_BY * k, pressComp[1] + (n[1] / len) * CROP_BY * k]), + ), + ...follow((m) => mid(m.outline[1], m.outline[2])), + }; + } + const c = centre(pre.visible); + const r = dist(pressComp, c); + const a0 = Math.atan2(pressComp[1] - c[1], pressComp[0] - c[0]); + const angle = (p) => Math.atan2(p[1] - c[1], p[0] - c[0]); + return { + path: steps.map((k) => + at([c[0] + r * Math.cos(a0 + ROTATE_BY * k), c[1] + r * Math.sin(a0 + ROTATE_BY * k)]), + ), + point: (m) => [angleOf(m.quad), 0], + error: (s, s0) => Math.abs(normalizeAngle(s.p[0] - s0.p[0] - (angle(s.c) - angle(s0.c)))) * r, + }; +} + +async function sample(ctx, gesture, point, pointerScreen) { + const m = await measure(ctx); + if (gesture === "crop") m.outline = await cropOutline(ctx, m.map); + return { m, p: point(m), c: m.map.toComp(pointerScreen) }; +} + +const TRACE_CATEGORIES = ["toplevel", "devtools.timeline", "blink.user_timing"]; +const TRACE_MARK = "edit-bench-end"; + +/** Frame stamps plus a main-thread trace of the drag; the end mark ties performance.now() to trace time. */ +async function recording(page, on) { + if (on) await page.tracing.start({ categories: TRACE_CATEGORIES }); + const rec = await page.evaluate( + (flag, mark) => { + const rec = window.__editBench.rec; + if (flag) [rec.frames, rec.long, rec.on] = [[], [], true]; + else rec.on = false; + return { + frames: rec.frames, + long: rec.long, + mark: flag ? null : performance.mark(mark).startTime, + }; + }, + on, + TRACE_MARK, + ); + if (on) return rec; + const trace = new TextDecoder().decode(await page.tracing.stop()); + return { ...rec, trace: JSON.parse(trace).traceEvents }; +} + +const isRunTask = (e) => + e.ph === "X" && (e.name === "RunTask" || e.name === "ThreadControllerImpl::RunTask"); + +/** Outermost tasks on one thread; nested RunTask events sit inside them. */ +function topLevelTasks(trace, { pid, tid }) { + const runs = trace + .filter((e) => e.pid === pid && e.tid === tid && isRunTask(e)) + .sort((a, b) => a.ts - b.ts || b.dur - a.dur); + const tops = []; + let end = -Infinity; + for (const e of runs) { + if (e.ts < end) continue; + tops.push(e); + end = e.ts + e.dur; + } + return tops; +} + +// Thread CPU time, spread evenly over the task, so a loaded machine descheduling the thread does not count as work. +const cpuUs = (e, a, b) => + (Math.max(0, Math.min(b, e.ts + e.dur) - Math.max(a, e.ts)) * e.tdur) / (e.dur || 1); + +/** Main-thread CPU ms inside each frame interval, on the thread that ran the end mark; null when unknown. */ +function mainThreadPerFrame({ frames, mark, trace }) { + const anchor = trace.find((e) => e.name === TRACE_MARK && e.cat.includes("user_timing")); + const tasks = anchor ? topLevelTasks(trace, anchor) : []; + // Without the mark or thread CPU time the work is unknown, which fails smoothness alone. + if (!anchor || tasks.some((e) => e.tdur === undefined)) return null; + const toTrace = (ms) => anchor.ts + (ms - mark) * 1000; + return frames.slice(1).map((t, i) => { + const [a, b] = [toTrace(frames[i]), toTrace(t)]; + return tasks.reduce((sum, e) => sum + cpuUs(e, a, b), 0) / 1000; + }); +} + +const hundredth = (v) => Math.round(v * 100) / 100; + +function smoothness(rec) { + const intervals = rec.frames.slice(1).map((t, i) => t - rec.frames[i]); + const work = mainThreadPerFrame(rec); + return { + p95: percentile(intervals, 95), + frames: intervals.length, + longTasks: rec.long.length, + intervals: intervals.map(hundredth), + work: work && work.map(hundredth), + }; +} + +const CONTROL_PAGE = `data:text/html,
`; + +/** The case's drag schedule and per-frame reads on a blank page in the same Chrome: the machine's own frame drops. */ +async function controlDrag(browser, gesture) { + const context = await browser.createBrowserContext(); + try { + const page = await context.newPage(); + await page.setViewport(VIEWPORT); + await page.evaluateOnNewDocument(instrumentPage); + await page.goto(CONTROL_PAGE); + const box = await page.$("#box"); + const ctx = { page, handles: { target: box, root: box } }; + const read = () => readQuads(ctx); + if (gesture === "nudge") { + await recording(page, true); + for (let i = 0; i < NUDGES; i++) { + await page.keyboard.press("ArrowRight"); + await nextFrame(page); + } + await nextFrame(page, 2); + return smoothness(await recording(page, false)); + } + await page.mouse.move(700, 380); + await page.mouse.down(); + await nextFrame(page); + await read(); + await recording(page, true); + for (let i = 1; i <= STEPS; i++) { + await page.mouse.move(700 + (MOVE_BY[0] * i) / STEPS, 380 + (MOVE_BY[1] * i) / STEPS); + await nextFrame(page); + await read(); + } + const smooth = smoothness(await recording(page, false)); + await page.mouse.up(); + return smooth; + } finally { + await context.close().catch(() => undefined); + } +} + +async function pointerGesture(ctx, gesture, pre) { + const press = await handlePoint(ctx, pre, gesture); + const pressComp = pre.map.toComp(press); + const g = plan(gesture, pre, pressComp); + const hit = await ctx.page.evaluate(([x, y]) => { + const e = document.elementFromPoint(x, y); + return e + ? `${e.tagName.toLowerCase()}${e.getAttribute("aria-label") ? `[${e.getAttribute("aria-label")}]` : ""}` + : null; + }, press); + await ctx.page.mouse.move(press[0], press[1]); + await ctx.page.mouse.down(); + await nextFrame(ctx.page); + const s0 = await sample(ctx, gesture, g.point, press); + await recording(ctx.page, true); + const errors = []; + let last = s0; + for (const p of g.path) { + await ctx.page.mouse.move(p[0], p[1]); + await nextFrame(ctx.page); + last = await sample(ctx, gesture, g.point, p); + errors.push(g.error(last, s0)); + } + const rec = await recording(ctx.page, false); + const smooth = smoothness(rec); + await ctx.page.mouse.up(); + const lastQuad = gesture === "crop" ? last.m.outline : last.m.visible; + return { + errors, + lastQuad, + pressJump: quadDistance(s0.m.visible, pre.visible), + smooth, + diag: { + hit, + grabOffset: gesture === "rotate" ? 0 : dist(s0.p, s0.c), + errors: errors.map((e) => Math.round(e * 1000) / 1000), + }, + }; +} + +async function nudgeGesture(ctx, pre) { + await recording(ctx.page, true); + for (let i = 0; i < NUDGES; i++) { + await ctx.page.keyboard.press("ArrowRight"); + await nextFrame(ctx.page); + } + await nextFrame(ctx.page, 2); + const smooth = smoothness(await recording(ctx.page, false)); + const m = await measure(ctx); + const [a, b] = [centre(pre.visible), centre(m.visible)]; + return { + errors: [dist([b[0] - a[0], b[1] - a[1]], [NUDGES, 0])], + lastQuad: m.visible, + pressJump: null, + smooth, + diag: {}, + }; +} + +/** One case, end to end, in a fresh browser context against a Studio already serving `dir`. */ +// fallow-ignore-next-line complexity +export async function runCase({ browser, spec, dir, files, url, evidence }) { + const control = await controlDrag(browser, spec.gesture); + const context = await browser.createBrowserContext(); + const page = await context.newPage(); + const ctx = { page, dir, files, handles: null }; + const consoleErrors = []; + page.on("pageerror", (e) => consoleErrors.push(e.message)); + evidence.shots = {}; + const shoot = async (name) => + (evidence.shots[name] = await page.screenshot({ type: "jpeg", quality: 70 })); + let committedFiles = null; + try { + await page.setViewport(VIEWPORT); + await page.evaluateOnNewDocument(instrumentPage); + await page.goto(url); + let pre = await openStudio(ctx); + await disableSnap(page); + const zoom = await setZoom(ctx, spec.zoom, pre.map.toScreen(centre(pre.visible))); + pre = await settled(ctx); + await selectTarget(ctx, pre); + pre = await settled(ctx); + const original = readFiles(dir, files); + + const drive = + spec.gesture === "nudge" + ? await nudgeGesture(ctx, pre) + : await pointerGesture(ctx, spec.gesture, pre); + await waitForFiles(ctx, { from: original, timeout: spec.gesture === "nudge" ? 6000 : 5000 }); + await nextFrame(page, 2); + await blurPreview(page); + await page.keyboard.press("Escape"); + const committed = await settled(ctx); + await shoot("committed"); + committedFiles = readFiles(dir, files); + const saved = !sameFiles(committedFiles, original); + + // Undo and redo run before any reload. Each waits up to 15 s for its own write; redo waits for undo. + const landed = (from) => + saved ? waitForFiles(ctx, { from, timeout: 15_000 }) : { reached: true, files: from }; + await chord(page, "Control+z"); + const undo = await landed(committedFiles); + const undone = await settled(ctx); + await shoot("undone"); + let [redo, redone] = [{ reached: false }, null]; + if (undo.reached) { + await blurPreview(page); + await chord(page, "Control+Shift+z"); + redo = await landed(undo.files); + redone = await settled(ctx); + } + // A late write must not land under the reload. + await waitForFiles(ctx, { timeout: 15_000 }); + + await page.reload(); + const reloaded = await openStudio(ctx); + await shoot("reloaded"); + const quads = Object.fromEntries( + Object.entries({ pre, committed, undone, redone, reloaded }).filter(([, m]) => m), + ); + const round = (m) => m.visible.map((p) => p.map((v) => Math.round(v * 100) / 100)); + return { + zoom, + saved, + tracking: { + max: Math.max(...drive.errors), + p95: percentile(drive.errors, 95), + frames: drive.errors.length, + }, + pressJump: drive.pressJump, + drop: quadDistance(drive.lastQuad, committed.visible), + reload: quadDistance(committed.visible, reloaded.visible), + undo: { + bytes: saved && undo.reached && sameFiles(undo.files, original), + box: quadDistance(undone.visible, pre.visible), + redoBytes: saved && redo.reached && sameFiles(redo.files, committedFiles), + redoBox: redone && quadDistance(redone.visible, committed.visible), + }, + // Which write never landed within 15 s; a redo that was never sent is untested, so undo fails. + undoTimeout: saved && !undo.reached ? "undo" : saved && !redo.reached ? "redo" : null, + smooth: { ...drive.smooth, control }, + unsettled: Object.keys(quads).filter((k) => quads[k].unsettled), + diag: { + ...drive.diag, + consoleErrors: consoleErrors.slice(0, 5), + quads: Object.fromEntries(Object.entries(quads).map(([k, m]) => [k, round(m)])), + }, + }; + } catch (error) { + await shoot("error").catch(() => undefined); + throw error; + } finally { + evidence.files = committedFiles; + await context.close().catch(() => undefined); + } +} diff --git a/packages/studio/tests/e2e/edit-accuracy/geometry.mjs b/packages/studio/tests/e2e/edit-accuracy/geometry.mjs new file mode 100644 index 0000000000..71bedeaffb --- /dev/null +++ b/packages/studio/tests/e2e/edit-accuracy/geometry.mjs @@ -0,0 +1,74 @@ +/** Pure geometry for the edit accuracy bench. Points are [x, y]; a quad is 4 points, clockwise from top-left. */ + +export const toPoints = (flat) => [0, 2, 4, 6].map((i) => [flat[i], flat[i + 1]]); +const sub = (a, b) => [a[0] - b[0], a[1] - b[1]]; +const add = (a, b) => [a[0] + b[0], a[1] + b[1]]; +const scale = (a, k) => [a[0] * k, a[1] * k]; +export const dist = (a, b) => Math.hypot(a[0] - b[0], a[1] - b[1]); +export const mid = (a, b) => scale(add(a, b), 0.5); +export const centre = (q) => scale(q.reduce(add, [0, 0]), 0.25); + +/** Affine frame of a quad: origin plus the two edge vectors for a box of `size` local px. */ +function frameOf(q, size) { + return { + o: q[0], + ex: scale(sub(q[1], q[0]), 1 / size.width), + ey: scale(sub(q[3], q[0]), 1 / size.height), + }; +} + +export function localToQuad(q, size, [u, v]) { + const f = frameOf(q, size); + return add(f.o, add(scale(f.ex, u), scale(f.ey, v))); +} + +export function quadToLocal(q, size, p) { + const { o, ex, ey } = frameOf(q, size); + const d = sub(p, o); + const det = ex[0] * ey[1] - ex[1] * ey[0]; + return [(d[0] * ey[1] - d[1] * ey[0]) / det, (ex[0] * d[1] - ex[1] * d[0]) / det]; +} + +/** Screen to composition px through the root composition's rendered quad. */ +export function compositionMapper(rootQuad, composition) { + return { + toComp: (p) => quadToLocal(rootQuad, composition, p), + toScreen: (p) => localToQuad(rootQuad, composition, p), + }; +} + +/** The visible quad: the element box shrunk by its clip-path insets, in the element's local frame. */ +export function visibleQuad(q, size, inset) { + const r = size.width - inset.right; + const b = size.height - inset.bottom; + return [ + [inset.left, inset.top], + [r, inset.top], + [r, b], + [inset.left, b], + ].map((p) => localToQuad(q, size, p)); +} + +export const quadDistance = (a, b) => Math.max(...a.map((p, i) => dist(p, b[i]))); +export const angleOf = (q) => Math.atan2(q[1][1] - q[0][1], q[1][0] - q[0][0]); +export const normalizeAngle = (a) => Math.atan2(Math.sin(a), Math.cos(a)); + +/** `inset(t r b l [round ...])` in px, as getComputedStyle reports it; none means no crop. */ +export function parseInset(clipPath) { + const zero = { top: 0, right: 0, bottom: 0, left: 0 }; + const m = /^inset\(([^)]*)\)/.exec(clipPath ?? ""); + if (!m) return zero; + const v = m[1] + .split(/\s+round\s+/)[0] + .trim() + .split(/\s+/) + .map((s) => Number.parseFloat(s)); + const [t, r = t, b = t, l = r] = v; + return { top: t, right: r, bottom: b, left: l }; +} + +export function percentile(values, p) { + if (!values.length) return null; + const s = [...values].sort((a, b) => a - b); + return s[Math.min(s.length - 1, Math.ceil((p / 100) * s.length) - 1)]; +} diff --git a/packages/studio/tests/e2e/edit-accuracy/grid.mjs b/packages/studio/tests/e2e/edit-accuracy/grid.mjs new file mode 100644 index 0000000000..3ff5692076 --- /dev/null +++ b/packages/studio/tests/e2e/edit-accuracy/grid.mjs @@ -0,0 +1,122 @@ +// The edit accuracy grid: one flat-coloured element in a generated project, crossed with one gesture. +// Projects are written to a tmp dir per case; nothing checked in is edited. +import { mkdirSync, writeFileSync } from "node:fs"; +import { join } from "node:path"; + +export const COMPOSITION = { width: 1920, height: 1080 }; +/** Frame-aligned at 30 fps, inside every tween, so preview and producer sample the same instant. */ +export const PLAYHEAD = 1; +const TARGET = { width: 240, height: 160, color: "#f0c020" }; +const BACKGROUND = "#202020"; +const NESTED_HOST = { left: 160, top: 90, width: 1600, height: 900 }; +const GSAP_CDN = "https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"; + +// Studio has corner handles only (ResizeHandle is nw|ne|sw|se); its edge strips crop, so there is no edge resize. +const GESTURES = ["move", "resize", "rotate", "crop", "nudge"]; +const AXES = { + gsap: ["none", "tween", "hold"], + placement: ["px", "pct", "center", "xpercent"], + rotation: [0, 30], + nesting: ["root", "nested"], + zoom: [50, 100, 200], +}; + +const product = (axes) => + Object.entries(axes).reduce( + (rows, [key, values]) => rows.flatMap((row) => values.map((v) => ({ ...row, [key]: v }))), + [{}], + ); +const caseId = (c) => + [c.gesture, c.gsap, c.placement, `r${c.rotation}`, c.nesting, `z${c.zoom}`].join("-"); + +/** `pr` is a smaller slice for CI; its final size is still an open decision. */ +export function buildGrid(kind = "full") { + return product({ ...AXES, gesture: GESTURES }) + .filter((c) => c.placement !== "xpercent" || c.gsap !== "none") // xPercent only exists through GSAP + .filter((c) => kind !== "pr" || (c.zoom === 100 && c.nesting === "root")) + .map((c) => ({ id: caseId(c), ...c })); +} + +const PLACEMENT_CSS = { + px: "left: 560px; top: 300px; translate: 40px 30px;", + pct: "left: 560px; top: 300px; translate: 25% 25%;", + center: "left: 50%; top: 50%; translate: -50% -50%;", + xpercent: "left: 50%; top: 50%;", +}; + +function targetCss(spec) { + const rotate = spec.rotation ? ` rotate: ${spec.rotation}deg;` : ""; + return `#target { position: absolute; ${PLACEMENT_CSS[spec.placement]} width: ${TARGET.width}px; height: ${TARGET.height}px; background: ${TARGET.color};${rotate} }`; +} + +function gsapLines(spec) { + const percent = spec.placement === "xpercent" ? ", xPercent: -50, yPercent: -50" : ""; + if (spec.gsap === "hold") return [`gsap.set("#target", { x: 40, y: 20${percent} });`]; + const lines = [`tl.to("#target", { x: 120, y: 60, duration: 4, ease: "none" }, 0);`]; + if (percent) lines.unshift(`gsap.set("#target", { ${percent.slice(2)} });`); + return lines; +} + +function timelineScript(id, lines) { + return ` + `; +} + +// fallow-ignore-next-line complexity +function rootHtml(spec) { + const nested = spec.nesting === "nested"; + const body = nested + ? `
` + : `
`; + const script = spec.gsap === "none" ? "" : timelineScript("main", nested ? [] : gsapLines(spec)); + return ` + + + + + + +
+ ${body} +
+ ${script} + + +`; +} + +function subHtml(spec) { + const script = spec.gsap === "none" ? "" : timelineScript("sub", gsapLines(spec)); + return ` +`; +} + +/** Writes the case's project into `dir` and returns the files a gesture may rewrite. */ +export function writeFixture(spec, dir) { + mkdirSync(dir, { recursive: true }); + writeFileSync(join(dir, "index.html"), rootHtml(spec)); + if (spec.nesting !== "nested") return ["index.html"]; + mkdirSync(join(dir, "compositions"), { recursive: true }); + writeFileSync(join(dir, "compositions/sub.html"), subHtml(spec)); + return ["index.html", "compositions/sub.html"]; +} diff --git a/packages/studio/tests/e2e/edit-accuracy/report.mjs b/packages/studio/tests/e2e/edit-accuracy/report.mjs new file mode 100644 index 0000000000..e14fac14b1 --- /dev/null +++ b/packages/studio/tests/e2e/edit-accuracy/report.mjs @@ -0,0 +1,192 @@ +/** Scoring and the three report files (results.json, table.md, baseline.json) for the edit accuracy bench. */ +import { writeFileSync } from "node:fs"; +import { join } from "node:path"; +import { percentile } from "./geometry.mjs"; + +const LIMIT_PX = 0.5; +// A frame over 1.5 vsyncs is dropped; raw rAF p95 stays reported so a different rule re-scores without a re-run. +const DROPPED_FRAME_MS = 25; +const WORK_MS = 8; +export const METRICS = ["tracking", "press", "drop", "reload", "undo", "smooth"]; + +/** Worst-first value per metric; undo ranks by box distance, and its byte failures are counted apart. */ +const worstValue = { + tracking: (r) => r.tracking.max, + press: (r) => r.pressJump ?? 0, + drop: (r) => r.drop, + reload: (r) => r.reload, + undo: (r) => Math.max(r.undo.box, r.undo.redoBox ?? 0), + smooth: (r) => r.smooth.dropped - r.smooth.control.dropped, +}; + +/** Dropped frames and main-thread ms per frame at p95, from the raw intervals and trace work a case stores. */ +const frameBudget = (smooth) => ({ + ...smooth, + dropped: smooth.intervals.filter((d) => d > DROPPED_FRAME_MS).length, + workP95: smooth.work && percentile(smooth.work, 95), +}); + +/** The metrics each snapshot feeds; a snapshot whose preview never held still fails them. */ +const FED_BY = { + pre: ["press", "undo"], + committed: ["drop", "reload", "undo"], + undone: ["undo"], + redone: ["undo"], + reloaded: ["reload"], +}; +const unsettledMetrics = (r) => new Set(r.unsettled.flatMap((k) => FED_BY[k])); + +// fallow-ignore-next-line complexity +export function score(spec, r) { + if (r.error) + return { + ...spec, + ...r, + pass: false, + checks: Object.fromEntries(METRICS.map((m) => [m, false])), + }; + const smooth = { ...frameBudget(r.smooth), control: frameBudget(r.smooth.control) }; + const checks = { + tracking: r.tracking.max <= LIMIT_PX, + press: r.pressJump === null || r.pressJump <= LIMIT_PX, + drop: r.drop <= LIMIT_PX, + reload: r.reload <= LIMIT_PX, + undo: r.undo.bytes && r.undo.redoBytes && Math.max(r.undo.box, r.undo.redoBox) <= LIMIT_PX, + // Only drops beyond the blank page's, driven the same way in the same Chrome, are the edit's. + smooth: + smooth.dropped <= smooth.control.dropped && + smooth.workP95 !== null && + smooth.workP95 <= WORK_MS, + }; + for (const m of unsettledMetrics(r)) checks[m] = false; + return { ...spec, ...r, smooth, pass: Object.values(checks).every(Boolean), checks }; +} + +const round = (v) => (typeof v === "number" ? Math.round(v * 100) / 100 : v); +// Rounded up for baseline.json, so a stored value passes a limit only if the measured one did (to 1e-11). +const roundUp = (v) => (v === null ? null : Math.ceil(v * 100 - 1e-9) / 100); + +const medianMax = (values) => + values.length ? `${round(percentile(values, 50))}/${round(Math.max(...values))}` : "-"; + +function smoothSummary(measured) { + const s = measured.map((r) => r.smooth); + return { + unknown: s.filter((x) => x.workP95 === null).length, + dropped: medianMax(s.map((x) => x.dropped)), + control: medianMax(s.map((x) => x.control.dropped)), + p95: medianMax(s.map((x) => x.p95)), + controlP95: medianMax(s.map((x) => x.control.p95)), + }; +} + +function summarize(results, seconds) { + const passing = results.filter((r) => r.pass).length; + // Smoothness is still ungated in CI, so the score is also shown without it. + const accurate = results.filter((r) => + METRICS.every((m) => m === "smooth" || r.checks[m]), + ).length; + const measured = results.filter((r) => !r.error); + const perMetric = METRICS.map((m) => { + const worst = measured.reduce( + (a, r) => (!a || worstValue[m](r) > worstValue[m](a) ? r : a), + null, + ); + return { + metric: m, + pass: results.filter((r) => r.checks[m]).length, + worst: worst && { + id: worst.id, + value: round(worstValue[m](worst)), + unsettled: unsettledMetrics(worst).has(m), + }, + }; + }); + return { + passing, + accurate, + total: results.length, + errors: results.length - measured.length, + bytesDiffer: { + undo: measured.filter((r) => !r.undo.bytes).length, + redo: measured.filter((r) => !r.undo.redoBytes).length, + }, + perMetric, + unsettled: measured.filter((r) => r.unsettled.length).length, + undoTimeouts: measured.filter((r) => r.undoTimeout).length, + smooth: smoothSummary(measured), + seconds: Math.round(seconds), + }; +} + +// fallow-ignore-next-line complexity +const metricRow = (m, total) => + `| ${m.metric} | ${m.pass}/${total} | ${m.worst?.value ?? "-"}${m.worst?.unsettled ? " (unsettled)" : ""} | ${m.worst?.id ?? "-"} |`; + +function table(summary, meta, results) { + const lines = [ + `# Edit accuracy: ${summary.accurate}/${summary.total} cases pass`, + "", + `Every metric counts except smoothness, which is reported against the blank-page control: ${summary.perMetric.find((m) => m.metric === "smooth").pass}/${summary.total} pass it, and ${summary.passing}/${summary.total} pass everything including it.`, + "", + `Studio ${meta.studio} (build ${meta.build}), bench ${meta.bench}, grid \`${meta.grid}\`, ${meta.date}, ${summary.seconds}s with ${meta.jobs} jobs, ${summary.errors} harness errors, load ${meta.load}.`, + `Pass: tracking, press jump, drop and reload ≤ ${LIMIT_PX} px; undo and redo byte-identical with the box ≤ ${LIMIT_PX} px; no more frames over ${DROPPED_FRAME_MS} ms than the blank-page control, and main-thread work ≤ ${WORK_MS} ms per frame at p95.`, + "", + `Undo or redo left different bytes in ${summary.bytesDiffer.undo} undo and ${summary.bytesDiffer.redo} redo cases.`, + `The preview never held still for 1 s within 15 s in ${summary.unsettled} cases; the metrics that snapshot feeds fail.`, + `An undo or redo write never landed within 15 s in ${summary.undoTimeouts} cases; undo fails there.`, + `Smoothness: ${summary.smooth.unknown} cases with unknown work; dropped frames per case (median/max) ${summary.smooth.dropped}, blank-page control ${summary.smooth.control}; raw rAF p95 (median/max) ${summary.smooth.p95} ms, control ${summary.smooth.controlP95} ms.`, + "", + "| Metric | Pass | Worst | Worst case |", + "|---|---|---|---|", + ...summary.perMetric.map((m) => metricRow(m, summary.total)), + "", + "| Gesture | Cases | Pass | " + METRICS.join(" | ") + " |", + "|---|---|---|" + METRICS.map(() => "---").join("|") + "|", + ]; + for (const g of [...new Set(results.map((r) => r.gesture))]) { + const rs = results.filter((r) => r.gesture === g); + lines.push( + `| ${g} | ${rs.length} | ${rs.filter((r) => r.pass).length} | ${METRICS.map((m) => rs.filter((r) => r.checks[m]).length).join(" | ")} |`, + ); + } + return lines.join("\n") + "\n"; +} + +/** One line per case, so a baseline diff reads case by case. */ +function baseline(meta, results) { + const entries = [...results] + .sort((a, b) => a.id.localeCompare(b.id)) + .map((r) => { + const v = r.error + ? { pass: false, error: true } + : { + pass: r.pass, + tracking: roundUp(r.tracking.max), + pressJump: roundUp(r.pressJump), + drop: roundUp(r.drop), + reload: roundUp(r.reload), + undo: r.checks.undo, + dropped: r.smooth.dropped, + controlDropped: r.smooth.control.dropped, + work: roundUp(r.smooth.workP95), + frameP95: roundUp(r.smooth.p95), + ...(r.unsettled.length && { unsettled: r.unsettled }), + ...(r.undoTimeout && { undoTimeout: r.undoTimeout }), + }; + return ` ${JSON.stringify(r.id)}: ${JSON.stringify(v)}`; + }); + return `{\n "studio": ${JSON.stringify(meta.studio)},\n "build": ${JSON.stringify(meta.build)},\n "bench": ${JSON.stringify(meta.bench)},\n "grid": ${JSON.stringify(meta.grid)},\n "cases": {\n${entries.join(",\n")}\n }\n}\n`; +} + +/** Writes results.json, table.md and baseline.json into `out`; returns the table. */ +export function writeReport(out, meta, results, seconds) { + const summary = summarize(results, seconds); + writeFileSync( + join(out, "results.json"), + JSON.stringify({ meta, summary, cases: results }, null, 1), + ); + writeFileSync(join(out, "table.md"), table(summary, meta, results)); + writeFileSync(join(out, "baseline.json"), baseline(meta, results)); + return table(summary, meta, results); +} diff --git a/packages/studio/tests/e2e/edit-accuracy/run.mjs b/packages/studio/tests/e2e/edit-accuracy/run.mjs new file mode 100644 index 0000000000..c24e1f9481 --- /dev/null +++ b/packages/studio/tests/e2e/edit-accuracy/run.mjs @@ -0,0 +1,197 @@ +#!/usr/bin/env bun +// Edit accuracy bench: real gestures in the built CLI Studio (build core, parsers, lint, studio-server first). +// bun run --cwd packages/studio test:edit-accuracy -- --grid full|pr --jobs N [--shard i/n] [--filter re] [--lock path] +import { createHash } from "node:crypto"; +import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs"; +import { loadavg, tmpdir } from "node:os"; +import { dirname, join, resolve } from "node:path"; +import { fileURLToPath } from "node:url"; +import { parseArgs } from "node:util"; +import { execSync, spawn } from "node:child_process"; +import puppeteer from "puppeteer-core"; +import { resolveHeadlessShellPath } from "../../../../engine/src/index.ts"; +import { buildGrid, writeFixture } from "./grid.mjs"; +import { killServers, runCase, startServer, stopServer } from "./case.mjs"; +import { METRICS, score, writeReport } from "./report.mjs"; + +const HERE = dirname(fileURLToPath(import.meta.url)); +const REPO = resolve(HERE, "../../../../.."); + +const { values: opt } = parseArgs({ + options: { + grid: { type: "string", default: "full" }, + shard: { type: "string", default: "1/1" }, + jobs: { type: "string", default: "1" }, + filter: { type: "string", default: "" }, + out: { type: "string" }, + port: { type: "string", default: "5800" }, + cli: { type: "string", default: join(REPO, "packages/cli/dist/cli.js") }, + lock: { type: "string" }, + }, +}); +const [shard, shards] = opt.shard.split("/").map(Number); +const filter = new RegExp(opt.filter); +const cases = buildGrid(opt.grid).filter((c, i) => filter.test(c.id) && i % shards === shard - 1); +const runId = new Date().toISOString().replace(/[:.]/g, "-"); +const out = resolve(opt.out ?? join(HERE, "../evidence/edit-accuracy", runId)); +const chrome = resolveHeadlessShellPath(); +if (!chrome) throw new Error("no chrome-headless-shell: run `npx hyperframes browser ensure`"); +mkdirSync(out, { recursive: true }); + +/** Evidence for a failing case only: the Studio screens at each stage and the saved files. */ +// fallow-ignore-next-line complexity +function saveEvidence(id, evidence) { + const caseDir = join(out, "cases", id); + mkdirSync(caseDir, { recursive: true }); + for (const [name, jpeg] of Object.entries(evidence.shots ?? {})) + writeFileSync(join(caseDir, `${name}.jpg`), jpeg); + for (const [name, text] of Object.entries(evidence.files ?? {})) + writeFileSync(join(caseDir, `saved-${name.replace("/", "-")}`), text); +} + +function verdict(r) { + const fails = r.error ? "ERROR" : METRICS.filter((m) => !r.checks[m]).join(","); + return `${r.pass ? "PASS" : "FAIL"} ${r.id} ${r.seconds.toFixed(1)}s ${fails}`; +} + +// fallow-ignore-next-line complexity +const errorResult = (error, log) => ({ + error: `${error?.message ?? error}\n${error?.stack ?? ""}`.slice(0, 1200), + serverLog: log.join("").slice(-600), +}); + +const liveRoots = new Set(); + +async function runOne(spec, browser, port) { + const started = Date.now(); + const root = mkdtempSync(join(tmpdir(), "hf-edit-accuracy-")); + liveRoots.add(root); + const dir = join(root, "case"); + const files = writeFixture(spec, dir); + const evidence = {}; + const log = []; + let result; + let server; + try { + server = await startServer(opt.cli, dir, port, log, join(root, "home")); + result = await runCase({ + browser, + spec, + dir, + files, + url: `http://127.0.0.1:${port}/#project/case`, + evidence, + }); + } catch (error) { + result = errorResult(error, log); + } finally { + if (server) await stopServer(server); + } + const scored = { ...score(spec, result), seconds: (Date.now() - started) / 1000 }; + if (!scored.pass) saveEvidence(spec.id, evidence); + rmSync(root, { recursive: true, force: true }); + liveRoots.delete(root); + console.log(verdict(scored)); + return scored; +} + +/** Holds an flock on `path` until the returned release is called; flock(1) owns the lock, so it dies with us. */ +function acquireLock(path) { + const child = spawn("flock", [path, "-c", "echo locked; exec cat"], { + stdio: ["pipe", "pipe", "inherit"], + }); + return new Promise((resolve, reject) => { + child.once("error", reject); + child.once("exit", (code) => reject(new Error(`flock ${path} exited ${code}`))); + child.stdout.once("data", () => resolve(() => child.stdin.end())); + }); +} + +// The suite lock is taken per chunk, so a full grid never holds it for its whole run. +const LOCK_CHUNK = 8; + +// fallow-ignore-next-line complexity +async function runChunks(queue, browsers, results) { + for (let start = 0; start < queue.length; start += opt.lock ? LOCK_CHUNK : queue.length) { + const chunk = queue.slice(start, opt.lock ? start + LOCK_CHUNK : queue.length); + const release = opt.lock ? await acquireLock(opt.lock) : () => undefined; + try { + await Promise.all( + browsers.map(async (browser, i) => { + for (let spec = chunk.shift(); spec; spec = chunk.shift()) + results.push(await runOne(spec, browser, Number(opt.port) + i)); + }), + ); + } finally { + release(); + } + } +} + +function cleanup() { + killServers(); + for (const root of liveRoots) rmSync(root, { recursive: true, force: true }); +} +process.on("exit", cleanup); +for (const signal of ["SIGINT", "SIGTERM", "SIGHUP"]) { + process.on(signal, () => { + cleanup(); + process.exit(130); + }); +} +// Stamped at start from the CLI that runs: its tree's commit and a hash of the CLI and Studio bundles. +// cli.js alone does not change with Studio; the Studio index.html names its hashed assets. +const git = (args) => execSync(`git ${args}`, { cwd: REPO }).toString().trim(); +const studio = (() => { + try { + return execSync("git rev-parse --short HEAD", { cwd: dirname(opt.cli), stdio: "pipe" }) + .toString() + .trim(); + } catch { + return "outside git"; // An extracted CLI artifact; the build hash still identifies it. + } +})(); +const build = createHash("sha256") + .update(readFileSync(opt.cli)) + .update(readFileSync(join(dirname(opt.cli), "studio/index.html"))) + .digest("hex") + .slice(0, 12); +const bench = git("rev-parse --short HEAD"); +const load = loadavg() + .map((v) => v.toFixed(1)) + .join(" "); +const started = Date.now(); +const results = []; +const jobs = Math.max(1, Math.min(Number(opt.jobs), cases.length)); +console.log(`edit accuracy: ${cases.length} cases, ${jobs} jobs, chrome ${chrome}, out ${out}`); +const browsers = await Promise.all( + Array.from({ length: jobs }, () => + puppeteer.launch({ + executablePath: chrome, + headless: true, + args: ["--no-sandbox", "--use-angle=swiftshader", "--enable-unsafe-swiftshader"], + }), + ), +); +try { + await runChunks([...cases], browsers, results); +} finally { + await Promise.all(browsers.map((b) => b.close())); +} +results.sort((a, b) => a.id.localeCompare(b.id)); +const meta = { + studio, + build, + bench, + grid: + opt.grid + + (opt.filter ? ` filter ${opt.filter}` : "") + + (shards > 1 ? ` shard ${opt.shard}` : ""), + date: new Date().toISOString(), + jobs, + chrome, + load: `${load} to ${loadavg() + .map((v) => v.toFixed(1)) + .join(" ")}`, +}; +console.log(writeReport(out, meta, results, (Date.now() - started) / 1000));