@motion-proto/live-tokens 0.87.1 → 0.88.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,16 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.88.0 — Eval results on the testing page
4
+
5
+ ### Changed
6
+
7
+ - **The testing page reports the eval results.** A new Measured value
8
+ chapter tabulates the pick-component eval across four arms: with and
9
+ without the skills, and with and without the CLI. It gives the requirements
10
+ each arm got right, with its turns, time, and cost. The Skill Atlas loses
11
+ its Measured value tab, and a `#measured-value` link to the atlas now opens
12
+ the chapter.
13
+
3
14
  ## 0.87.1 — Floating parts stay above the page in sketch mode
4
15
 
5
16
  ### Fixed
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@motion-proto/live-tokens",
3
- "version": "0.87.1",
3
+ "version": "0.88.0",
4
4
  "type": "module",
5
5
  "description": "Design token editor with live CSS variable editing. Svelte 5 + Vite 8.",
6
6
  "keywords": [
@@ -1,10 +1,12 @@
1
1
  <script lang="ts">
2
+ import { onMount } from 'svelte';
2
3
  import CollapsibleSection from '../system/components/CollapsibleSection.svelte';
3
4
  import Panel from '../system/components/Panel.svelte';
4
5
  import Table from '../system/components/Table.svelte';
5
6
  import Card from '../system/components/Card.svelte';
6
7
  import { portal } from '../system/internal/portal';
7
8
  import { navigate } from '../editor/core/routing/router';
9
+ import { pickComponentEval as result } from './evalResults';
8
10
 
9
11
  let { homeHref = '/demo' }: { homeHref?: string } = $props();
10
12
 
@@ -14,6 +16,7 @@
14
16
  { id: 'test-runs', title: 'Testing pages and components' },
15
17
  { id: 'walkthrough', title: 'Build and check a page' },
16
18
  { id: 'reference', title: 'Rule reference' },
19
+ { id: 'measured-value', title: 'Measured value' },
17
20
  ];
18
21
 
19
22
  // Closed by default: open, the fixed panel covers the full-width figures.
@@ -21,6 +24,12 @@
21
24
 
22
25
  let openRules = $state<Record<string, boolean>>({});
23
26
 
27
+ // The router loads this page lazily, after the browser's own jump to a hash has passed.
28
+ onMount(() => {
29
+ const id = window.location.hash.slice(1);
30
+ if (id) document.getElementById(id)?.scrollIntoView({ block: 'start' });
31
+ });
32
+
24
33
  function jump(event: MouseEvent, id: string) {
25
34
  event.preventDefault();
26
35
  contentsOpen = false;
@@ -344,6 +353,63 @@
344
353
  </div>
345
354
  </section>
346
355
 
356
+ <section class="chapter" id="measured-value" aria-labelledby="measured-value-title">
357
+ <div class="chapter-body">
358
+ <h2 id="measured-value-title">Measured value</h2>
359
+ <p>An eval asked an agent to choose a component for {result.requirements} requirements. It ran in four arms of {result.runsPerArm} runs each: with and without the skills, and with and without the CLI. The arms chose the same components, with one miss among 144 answers, and differ in turns, time, and cost.</p>
360
+ <div class="eval-results">
361
+ <Table>
362
+ <table aria-label="Eval arms">
363
+ <thead>
364
+ <tr>
365
+ <th scope="col">Skills</th>
366
+ <th scope="col">CLI</th>
367
+ <th scope="col">Requirements right</th>
368
+ <th scope="col">Turns</th>
369
+ <th scope="col">Seconds</th>
370
+ <th scope="col">Cost</th>
371
+ </tr>
372
+ </thead>
373
+ <tbody>
374
+ {#each result.arms as arm (`${arm.skills}-${arm.cli}`)}
375
+ <tr>
376
+ <td>{arm.skills ? 'Yes' : 'No'}</td>
377
+ <td>{arm.cli ? 'Yes' : 'No'}</td>
378
+ <td>{arm.rowsRight}</td>
379
+ <td>{arm.turns}</td>
380
+ <td>{arm.seconds}</td>
381
+ <td>{arm.cost}</td>
382
+ </tr>
383
+ {/each}
384
+ </tbody>
385
+ </table>
386
+ </Table>
387
+ </div>
388
+ <ul class="eval-summary">
389
+ <li><strong>Turns:</strong> the skill saves turns only through the CLI command. Without the CLI, the skill arm takes as many turns as the agent with neither.</li>
390
+ <li><strong>Time and cost:</strong> the skills and the CLI each save time on their own, and together the run costs less than half as much as with neither.</li>
391
+ <li><strong>Accuracy:</strong> every arm scores about the same. The choices come from the entries in the component files.</li>
392
+ <li><strong>Change since 2026-09-20:</strong> in that run, no agent without the skills found the CLI; this time 2 of 3 did, so that arm varies from run to run.</li>
393
+ </ul>
394
+ <div class="text-columns">
395
+ <div>
396
+ <h3>Method</h3>
397
+ <ul>
398
+ <li>Cases <code>{result.cases[0]}</code> and <code>{result.cases[1]}</code>, run on {result.date} at package {result.packageVersion}.</li>
399
+ <li>Ten requirements with one right component, and two that nothing shipped fits.</li>
400
+ <li>The second case seeds the same project without the package’s <code>bin</code> folder, so the command the skill names fails. The skills are unchanged.</li>
401
+ <li>The runner adds the arm without skills to each case.</li>
402
+ <li>Each requirement has its own pattern grader, so a failure names its row.</li>
403
+ </ul>
404
+ </div>
405
+ <div>
406
+ <h3>Limits</h3>
407
+ <p>Three runs per arm is a small sample. The twelve requirements each have a clear answer in the entries. A harder case, where two components both fit, has not been measured.</p>
408
+ </div>
409
+ </div>
410
+ </div>
411
+ </section>
412
+
347
413
  <!-- NOTES, open questions for this page:
348
414
  - "Review by eye" (walkthrough step 7) and Figure 2's "Read the page yourself".
349
415
  Leaning toward removing both. The create-page skill says "Open the page at
@@ -438,6 +504,11 @@
438
504
  grid-column: 2 / -2;
439
505
  }
440
506
 
507
+ .chapter-body > .eval-results {
508
+ grid-column: 2 / -2;
509
+ margin-block: var(--space-12) var(--space-40);
510
+ }
511
+
441
512
  .chapter-body > .reference-groups {
442
513
  grid-column: 2 / 11;
443
514
  }
@@ -475,6 +546,35 @@
475
546
  margin: var(--space-0) var(--space-0) var(--space-24);
476
547
  }
477
548
 
549
+ .text-columns h3 {
550
+ font-family: var(--heading-sm-font-family);
551
+ font-size: var(--heading-sm-font-size);
552
+ font-weight: var(--heading-sm-font-weight);
553
+ line-height: var(--heading-sm-line-height);
554
+ letter-spacing: var(--heading-sm-letter-spacing);
555
+ margin: var(--space-0) var(--space-0) var(--space-12);
556
+ }
557
+
558
+ .eval-summary {
559
+ margin: var(--space-0) var(--space-0) var(--space-40);
560
+ padding-left: var(--space-24);
561
+ color: var(--text-secondary);
562
+ }
563
+
564
+ .eval-summary li + li {
565
+ margin-top: var(--space-12);
566
+ }
567
+
568
+ .text-columns ul {
569
+ margin: var(--space-0);
570
+ padding-left: var(--space-24);
571
+ color: var(--text-secondary);
572
+ }
573
+
574
+ .text-columns li + li {
575
+ margin-top: var(--space-8);
576
+ }
577
+
478
578
  p,
479
579
  li,
480
580
  figcaption {
@@ -0,0 +1,62 @@
1
+ /** One arm of an eval: the same case run with or without the skills, and with or without the CLI. */
2
+ export interface EvalArm {
3
+ skills: boolean;
4
+ cli: boolean;
5
+ rowsRight: string;
6
+ turns: string;
7
+ seconds: string;
8
+ cost: string;
9
+ }
10
+
11
+ export interface EvalResult {
12
+ cases: string[];
13
+ date: string;
14
+ packageVersion: string;
15
+ runsPerArm: number;
16
+ requirements: number;
17
+ arms: EvalArm[];
18
+ }
19
+
20
+ // Recorded by hand from `npm run eval` in the live-tokens repository. The
21
+ // evals do not ship, so the page cannot read a result file at runtime.
22
+ export const pickComponentEval: EvalResult = {
23
+ cases: ['outcome-pick-component', 'outcome-pick-component-no-cli'],
24
+ date: '2026-09-22',
25
+ packageVersion: '0.87.1',
26
+ runsPerArm: 3,
27
+ requirements: 12,
28
+ arms: [
29
+ {
30
+ skills: true,
31
+ cli: true,
32
+ rowsRight: '12 of 12 in all 3 runs',
33
+ turns: '9 to 10',
34
+ seconds: '46 to 76',
35
+ cost: '$0.41 to $0.46',
36
+ },
37
+ {
38
+ skills: false,
39
+ cli: true,
40
+ rowsRight: '12 of 12 in all 3 runs; 2 of 3 found the CLI on their own',
41
+ turns: '15 to 17',
42
+ seconds: '65 to 102',
43
+ cost: '$0.63 to $0.74',
44
+ },
45
+ {
46
+ skills: true,
47
+ cli: false,
48
+ rowsRight: '12 of 12 in all 3 runs',
49
+ turns: '14 to 20',
50
+ seconds: '66 to 84',
51
+ cost: '$0.73 to $0.91',
52
+ },
53
+ {
54
+ skills: false,
55
+ cli: false,
56
+ rowsRight: '11 of 12 in one run, which missed requirement 12',
57
+ turns: '17 to 20',
58
+ seconds: '113 to 169',
59
+ cost: '$1.02 to $1.14',
60
+ },
61
+ ],
62
+ };
@@ -1,9 +1,8 @@
1
1
  <script lang="ts">
2
2
  import { onMount, tick } from 'svelte';
3
- import { navigate } from '../core/routing/router';
3
+ import { navigate, route } from '../core/routing/router';
4
4
  import Button from '../../system/components/Button.svelte';
5
5
  import TabBar from '../../system/components/TabBar.svelte';
6
- import SkillValue from './SkillValue.svelte';
7
6
  import SourcePane from './SourcePane.svelte';
8
7
  import TreeCanvas from './TreeCanvas.svelte';
9
8
  import { linkHash, linkTargets, resolveLink } from './atlasLink';
@@ -13,35 +12,23 @@
13
12
 
14
13
  // `#set-type` opens that skill and `#set-type/write-the-font-pairing/voice`
15
14
  // also selects that block, so a link can hand someone one step of one tree.
16
- const VALUE_TAB = 'measured-value';
17
15
  const firstSkill = Object.keys(skillTrees)[0];
18
16
 
19
- function isValueHash(hash: string) {
20
- return hash === `#${VALUE_TAB}`;
21
- }
22
-
23
17
  const linked = resolveLink(window.location.hash, skillTrees);
24
- let active = $state(
25
- isValueHash(window.location.hash) ? VALUE_TAB : (linked?.skill ?? firstSkill),
26
- );
18
+ let active = $state(linked?.skill ?? firstSkill);
27
19
  let selection: Selection | null = $state(linked?.target ?? null);
28
20
  /** Which of the skill's documents the source pane shows. */
29
21
  let doc: string = $state(SKILL_DOC);
30
22
 
31
- // The value tab has no tree; the panes it hides keep reading a real skill.
32
- let skill = $derived(active === VALUE_TAB ? firstSkill : active);
33
- let tree = $derived(skillTrees[skill]);
34
- let docs = $derived(skillDocs[skill]);
23
+ let tree = $derived(skillTrees[active]);
24
+ let docs = $derived(skillDocs[active]);
35
25
  let lines = $derived(docs[doc] ?? docs[SKILL_DOC]);
36
26
  let siblings = $derived(Object.keys(docs).filter((name) => name !== SKILL_DOC));
37
27
 
38
- let tabs = $derived([
39
- ...Object.entries(skillTrees).map(([id, t]) => ({
40
- id,
41
- label: `${t.title}\n${skillDocs[id][SKILL_DOC].length} lines`,
42
- })),
43
- { id: VALUE_TAB, label: 'Measured value\n1 eval' },
44
- ]);
28
+ const tabs = Object.entries(skillTrees).map(([id, t]) => ({
29
+ id,
30
+ label: `${t.title}\n${skillDocs[id][SKILL_DOC].length} lines`,
31
+ }));
45
32
 
46
33
  let docTabs = $derived(
47
34
  Object.keys(docs).map((name) => ({
@@ -65,10 +52,6 @@
65
52
  }
66
53
 
67
54
  function shareSelection() {
68
- if (active === VALUE_TAB) {
69
- history.replaceState(null, '', `${window.location.pathname}#${VALUE_TAB}`);
70
- return;
71
- }
72
55
  const target = linkTargets(tree).find((t) => t.key === selection?.key) ?? null;
73
56
  history.replaceState(null, '', `${window.location.pathname}${linkHash(active, target)}`);
74
57
  }
@@ -111,10 +94,10 @@
111
94
  }
112
95
 
113
96
  function openLink(hash: string) {
114
- if (isValueHash(hash)) {
115
- active = VALUE_TAB;
116
- selection = null;
117
- doc = SKILL_DOC;
97
+ // The eval report moved to the testing page; replace, so Back skips the old link.
98
+ if (hash === '#measured-value') {
99
+ history.replaceState(null, '', '/testing-loops#measured-value');
100
+ route.set('/testing-loops');
118
101
  return;
119
102
  }
120
103
  const link = resolveLink(hash, skillTrees);
@@ -157,42 +140,38 @@
157
140
  <TabBar {tabs} value={active} onchange={changeTab} />
158
141
  </div>
159
142
 
160
- {#if active === VALUE_TAB}
161
- <SkillValue />
162
- {:else}
163
- <div class="split">
164
- <section class="pane" aria-label="{tree.id} decision tree">
165
- <div class="pane-head">
166
- <span class="pane-title">{tree.id}</span>
167
- <span class="pane-note">{tree.nodes.length} steps</span>
168
- </div>
169
- <div class="pane-body" bind:this={treePane}>
170
- <h2 class="tagline">{tree.tagline}</h2>
171
-
172
- <TreeCanvas {tree} selected={selection?.key ?? null} onselect={selectTarget} onopen={openDoc} />
173
- </div>
174
- </section>
175
-
176
- <section class="pane pane-source" aria-label="{tree.id} source">
177
- <div class="pane-head">
178
- <span class="pane-title">{tree.id}/{doc}</span>
179
- <span class="pane-note">{lines.length} lines</span>
180
- </div>
181
- <div class="doc-tabs">
182
- <TabBar tabs={docTabs} value={doc} onchange={openDoc} />
183
- </div>
184
- <div class="pane-body" bind:this={sourcePane}>
185
- <SourcePane
186
- {lines}
187
- {siblings}
188
- highlight={doc === SKILL_DOC ? (selection?.lines ?? null) : null}
189
- onpick={selectFromLine}
190
- onopen={openDoc}
191
- />
192
- </div>
193
- </section>
194
- </div>
195
- {/if}
143
+ <div class="split">
144
+ <section class="pane" aria-label="{tree.id} decision tree">
145
+ <div class="pane-head">
146
+ <span class="pane-title">{tree.id}</span>
147
+ <span class="pane-note">{tree.nodes.length} steps</span>
148
+ </div>
149
+ <div class="pane-body" bind:this={treePane}>
150
+ <h2 class="tagline">{tree.tagline}</h2>
151
+
152
+ <TreeCanvas {tree} selected={selection?.key ?? null} onselect={selectTarget} onopen={openDoc} />
153
+ </div>
154
+ </section>
155
+
156
+ <section class="pane pane-source" aria-label="{tree.id} source">
157
+ <div class="pane-head">
158
+ <span class="pane-title">{tree.id}/{doc}</span>
159
+ <span class="pane-note">{lines.length} lines</span>
160
+ </div>
161
+ <div class="doc-tabs">
162
+ <TabBar tabs={docTabs} value={doc} onchange={openDoc} />
163
+ </div>
164
+ <div class="pane-body" bind:this={sourcePane}>
165
+ <SourcePane
166
+ {lines}
167
+ {siblings}
168
+ highlight={doc === SKILL_DOC ? (selection?.lines ?? null) : null}
169
+ onpick={selectFromLine}
170
+ onopen={openDoc}
171
+ />
172
+ </div>
173
+ </section>
174
+ </div>
196
175
  </div>
197
176
 
198
177
  <style>
@@ -1,202 +0,0 @@
1
- <script lang="ts">
2
- import Table from '../../system/components/Table.svelte';
3
- import { pickComponentEval } from './evalResults';
4
-
5
- const result = pickComponentEval;
6
- const [withSkills, without] = result.arms;
7
- const difference = (withSkills.score - without.score).toFixed(2);
8
- </script>
9
-
10
- <article class="skill-value" aria-labelledby="skill-value-title">
11
- <div class="measure">
12
- <p class="eyebrow">Measured value</p>
13
- <h2 id="skill-value-title">The skills make an agent faster. The component entries make it right.</h2>
14
- <p class="lead">
15
- An eval asked an agent to choose a component for twelve requirements, three times with the
16
- skills loaded and three times with none. Every run that finished chose all twelve correctly,
17
- in both arms. The skills changed the cost of getting there.
18
- </p>
19
- </div>
20
-
21
- <div class="results">
22
- <Table>
23
- <table>
24
- <thead>
25
- <tr>
26
- <th scope="col"><span class="visually-hidden">Arm</span></th>
27
- <th scope="col">Score</th>
28
- <th scope="col">Requirements right</th>
29
- <th scope="col">Ran the catalogue command</th>
30
- <th scope="col">Turns</th>
31
- <th scope="col">Seconds</th>
32
- </tr>
33
- </thead>
34
- <tbody>
35
- {#each result.arms as arm (arm.label)}
36
- <tr>
37
- <td class="arm">{arm.label}</td>
38
- <td>{arm.score.toFixed(2)}</td>
39
- <td>{arm.rowsRight}</td>
40
- <td>{arm.ranCatalogue}</td>
41
- <td>{arm.turns}</td>
42
- <td>{arm.seconds}</td>
43
- </tr>
44
- {/each}
45
- </tbody>
46
- </table>
47
- </Table>
48
- </div>
49
-
50
- <div class="measure">
51
- <h3>Accuracy comes from the component</h3>
52
- <p>
53
- Each component file carries its own entry: <code>whenToUse</code> states the condition that
54
- makes it right, and <code>whenNotToUse</code> lists the conditions that rule it out, each with
55
- the component to use in its place. An agent with no skills opened the component files, found
56
- those entries, and made the same twelve choices, including the two requirements nothing
57
- shipped fits and the destructive action that belongs in a Dialog.
58
- </p>
59
-
60
- <h3>Efficiency comes from the skill</h3>
61
- <p>
62
- The pick-component skill sends the agent to one command,
63
- <code>npx live-tokens components --json</code>, which returns every entry at once. With the
64
- skills the agent answered in {withSkills.turns} turns and under a minute. Without them it
65
- never found the command, read files one at a time for {without.turns} turns, and one of its
66
- three runs reached the five minute limit before it answered.
67
- </p>
68
-
69
- <h3>Method</h3>
70
- <ul>
71
- <li>Case <code>{result.id}</code>, run on {result.date}.</li>
72
- <li>Twelve requirements: ten with one right component, two that nothing shipped fits.</li>
73
- <li>Three runs per arm. The runner adds the arm without skills on its own.</li>
74
- <li>
75
- Each requirement has its own pattern grader, so a failure names its row. One more grader
76
- checks that the agent ran the catalogue command.
77
- </li>
78
- <li>
79
- The score is the share of graders that pass. The difference of {difference} comes from one
80
- timeout and from the catalogue-command grader. No finished run chose a wrong component.
81
- </li>
82
- </ul>
83
-
84
- <h3>Limits</h3>
85
- <p>
86
- Three runs per arm is a small sample, and one timeout moves the score a long way. The twelve
87
- requirements each have a clear answer in the entries. A harder case, where two components
88
- both fit, has not been measured.
89
- </p>
90
- </div>
91
- </article>
92
-
93
- <style>
94
- /* The atlas locks to the viewport on desktop, so the report scrolls itself. */
95
- .skill-value {
96
- flex: 1 1 auto;
97
- min-height: 0;
98
- overflow-y: auto;
99
- padding-bottom: var(--space-48);
100
- color: var(--text-secondary);
101
- }
102
-
103
- .measure {
104
- max-width: 70ch;
105
- }
106
-
107
- .results {
108
- max-width: 110ch;
109
- margin: var(--space-32) 0 var(--space-40);
110
- }
111
-
112
- .eyebrow {
113
- margin: 0 0 var(--space-8);
114
- font-family: var(--eyebrow-font-family);
115
- font-size: var(--eyebrow-font-size);
116
- font-weight: var(--eyebrow-font-weight);
117
- line-height: var(--eyebrow-line-height);
118
- letter-spacing: var(--eyebrow-letter-spacing);
119
- text-transform: var(--eyebrow-text-transform);
120
- color: var(--text-tertiary);
121
- }
122
-
123
- h2 {
124
- margin: 0 0 var(--space-24);
125
- font-family: var(--heading-lg-font-family);
126
- font-size: var(--heading-lg-font-size);
127
- font-weight: var(--heading-lg-font-weight);
128
- line-height: var(--heading-lg-line-height);
129
- letter-spacing: var(--heading-lg-letter-spacing);
130
- color: var(--text-primary);
131
- }
132
-
133
- h3 {
134
- margin: var(--space-40) 0 var(--space-12);
135
- font-family: var(--heading-sm-font-family);
136
- font-size: var(--heading-sm-font-size);
137
- font-weight: var(--heading-sm-font-weight);
138
- line-height: var(--heading-sm-line-height);
139
- letter-spacing: var(--heading-sm-letter-spacing);
140
- color: var(--text-primary);
141
- }
142
-
143
- .measure > h3:first-child {
144
- margin-top: 0;
145
- }
146
-
147
- p,
148
- li {
149
- font-family: var(--body-md-font-family);
150
- font-size: var(--body-md-font-size);
151
- font-weight: var(--body-md-font-weight);
152
- line-height: var(--body-md-line-height);
153
- letter-spacing: var(--body-md-letter-spacing);
154
- }
155
-
156
- p {
157
- margin: 0 0 var(--space-16);
158
- }
159
-
160
- .lead {
161
- margin-bottom: 0;
162
- color: var(--text-primary);
163
- }
164
-
165
- ul {
166
- margin: 0;
167
- padding-left: var(--space-24);
168
- }
169
-
170
- li + li {
171
- margin-top: var(--space-8);
172
- }
173
-
174
- code {
175
- font-family: var(--code-font-family);
176
- font-size: var(--code-font-size);
177
- font-weight: var(--code-font-weight);
178
- line-height: var(--code-line-height);
179
- letter-spacing: var(--code-letter-spacing);
180
- background: var(--tint-low);
181
- padding-inline: var(--space-4);
182
- border-radius: var(--radius-sm);
183
- /* The code face joins `--` into one dash, and a line may break between the
184
- two hyphens. Either misprints a CLI flag. */
185
- font-variant-ligatures: none;
186
- white-space: nowrap;
187
- }
188
-
189
- .results td.arm {
190
- color: var(--text-primary);
191
- white-space: nowrap;
192
- }
193
-
194
- .visually-hidden {
195
- position: absolute;
196
- width: 1px;
197
- height: 1px;
198
- overflow: hidden;
199
- clip-path: inset(50%);
200
- white-space: nowrap;
201
- }
202
- </style>
@@ -1,46 +0,0 @@
1
- /** One arm of an eval: the same case run with the skills loaded, or without them. */
2
- export interface EvalArm {
3
- label: string;
4
- score: number;
5
- rowsRight: string;
6
- ranCatalogue: string;
7
- turns: string;
8
- seconds: string;
9
- }
10
-
11
- export interface EvalResult {
12
- id: string;
13
- date: string;
14
- packageVersion: string;
15
- runsPerArm: number;
16
- requirements: number;
17
- arms: [withSkills: EvalArm, without: EvalArm];
18
- }
19
-
20
- // Recorded by hand from `npm run eval` in the live-tokens repository. The
21
- // evals do not ship, so the page cannot read a result file at runtime.
22
- export const pickComponentEval: EvalResult = {
23
- id: 'outcome-pick-component',
24
- date: '2026-09-20',
25
- packageVersion: '0.82.0, plus the unreleased catalogue entry',
26
- runsPerArm: 3,
27
- requirements: 12,
28
- arms: [
29
- {
30
- label: 'With the skills',
31
- score: 1,
32
- rowsRight: '12 of 12 in all 3 runs',
33
- ranCatalogue: '3 of 3 runs',
34
- turns: '9 to 10',
35
- seconds: '42 to 58',
36
- },
37
- {
38
- label: 'Without',
39
- score: 0.62,
40
- rowsRight: '12 of 12 in the 2 runs that finished',
41
- ranCatalogue: '0 of 3 runs',
42
- turns: '15 to 16',
43
- seconds: '70 to 72, and one run timed out at 300',
44
- },
45
- ],
46
- };