@uptimizr/mcp 1.1.1 → 1.2.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@uptimizr/mcp",
3
- "version": "1.1.1",
3
+ "version": "1.2.0",
4
4
  "description": "Read-only Model Context Protocol (MCP) server over an Uptimizr collector's query API — let an agent ask questions of your own 3D analytics, on your own infrastructure.",
5
5
  "keywords": [
6
6
  "uptimizr",
@@ -40,6 +40,7 @@
40
40
  },
41
41
  "files": [
42
42
  "dist",
43
+ "skills",
43
44
  "README.md",
44
45
  "LICENSE",
45
46
  "AGENTS.md",
@@ -48,27 +49,27 @@
48
49
  "sideEffects": false,
49
50
  "dependencies": {
50
51
  "@modelcontextprotocol/sdk": "^1.30.0",
51
- "zod": "^4.5.4",
52
- "@uptimizr/agent-core": "1.1.1",
53
- "@uptimizr/metrics": "0.1.0",
54
- "@uptimizr/schema": "1.1.0"
52
+ "zod": "^4.6.5",
53
+ "@uptimizr/agent-core": "1.2.0",
54
+ "@uptimizr/metrics": "0.2.0",
55
+ "@uptimizr/schema": "1.2.0"
55
56
  },
56
57
  "devDependencies": {
57
- "@types/node": "^26.4.1",
58
- "tsx": "^4.23.13",
59
- "vitest": "^4.1.11"
58
+ "@types/node": "^26.6.2",
59
+ "tsx": "^4.23.15",
60
+ "vitest": "^5.0.1"
60
61
  },
61
62
  "engines": {
62
63
  "node": ">=22"
63
64
  },
64
65
  "scripts": {
65
- "build": "tsc -p tsconfig.json",
66
+ "build": "tsc -p tsconfig.json && node ../../../scripts/copy-dir.mjs ../agent-core/skills skills",
66
67
  "dev": "tsc -p tsconfig.json --watch",
67
68
  "serve": "tsx src/bin.ts",
68
69
  "start": "node dist/bin.js",
69
70
  "typecheck": "tsc -p tsconfig.json --noEmit",
70
71
  "test": "vitest run",
71
72
  "lint": "eslint .",
72
- "clean": "rimraf -g dist *.tsbuildinfo"
73
+ "clean": "rimraf -g dist skills *.tsbuildinfo"
73
74
  }
74
75
  }
@@ -0,0 +1,88 @@
1
+ ---
2
+ name: attention_hotspots
3
+ title: Attention hot-spots for a scene
4
+ description: >-
5
+ Find where visitors look and click in a scene: view-direction concentration, gaze→mesh flow, the
6
+ objects that draw the most interaction, and the ones nobody ever notices. USE FOR: deciding where
7
+ to put a call to action, finding ignored or invisible content, explaining why an object gets no
8
+ clicks, laying out a scene around what people actually look at. Trigger phrases: what do people
9
+ look at, attention hotspots, where do visitors click, which meshes get ignored, blind spots,
10
+ gaze heatmap, is anyone seeing this object.
11
+ tools:
12
+ - camera_heatmap
13
+ - flow_links
14
+ - click_rays
15
+ - top_meshes
16
+ - mesh_dwell
17
+ - mesh_blind_spots
18
+ - query
19
+ capabilities:
20
+ - query
21
+ args:
22
+ - name: scene
23
+ required: true
24
+ description: The scene id to analyse (see the uptimizr://scenes resource).
25
+ - name: range
26
+ required: false
27
+ default: the last 7 days
28
+ description: The window to analyse, in words — e.g. "the last 7 days", "since launch".
29
+ ---
30
+
31
+ Where does attention concentrate in scene "{{scene}}" over {{range}}?
32
+
33
+ Work through the method below with the read-only tools it names — all of them scoped with
34
+ `scene="{{scene}}"` — and synthesise one answer.
35
+
36
+ 1. **Orient before you ask anything.** Read the `uptimizr://context` resource first: it gives the
37
+ real scene ids, the scene's **named regions** and the custom-event names this project emits, and
38
+ it tells you which metrics are empty because their capture channel is off. Name regions the way
39
+ the project names them — "the checkout counter", not "the cluster at x≈3".
40
+
41
+ 2. **Where do they look?** `camera_heatmap` (`scene="{{scene}}"`) gives the view-direction
42
+ distribution — what people point the camera at, whether or not they ever click it. Ask for
43
+ `format: "summary"`: the digest merges neighbouring cells into a handful of clusters with a
44
+ share each and a plain-language `reading`, which is what you want here; the raw grid is
45
+ thousands of cells you cannot describe.
46
+
47
+ 3. **Does looking turn into touching?** `flow_links` (`scene="{{scene}}"`) links where the gaze was
48
+ to the mesh that was then clicked. A strong link is a working call to action; a heavy look with
49
+ no outgoing link is content that draws the eye and then disappoints.
50
+
51
+ 4. **Where do the clicks land?** `click_rays` (`scene="{{scene}}"`) gives view-gated clicks per
52
+ voxel and mesh — clicks attributed to what the visitor could actually see, not to whatever the
53
+ ray happened to pass through.
54
+
55
+ 5. **Rank the objects.** `top_meshes` (`scene="{{scene}}"`) for the most-interacted meshes, and
56
+ `mesh_dwell` (`scene="{{scene}}"`) for how long attention rests on each one. Dwell without
57
+ interaction is hesitation, and it usually means the object looks clickable and is not, or is
58
+ clickable and does not look it.
59
+
60
+ 6. **Name the cold half.** `mesh_blind_spots` (`scene="{{scene}}"`) lists the meshes that are
61
+ present and essentially never noticed. A hot-spot report that only names hot spots tells you
62
+ nothing about the content you paid to build.
63
+
64
+ 7. **Narrow it with the DSL.** For anything the canned tools do not expose, use the single `query`
65
+ tool: pick the `metric`, bound it with `range`, filter it, and set `format: "summary"` for a
66
+ bounded digest — each summary row carries a `drillQuery` you can send straight back instead of
67
+ rebuilding the filter. Set `compare: { range: <previous window> }` to see whether a hot spot is
68
+ new, and `explain: true` when a result looks wrong or empty: the plan names the capture channel,
69
+ the sample size and the row cap behind it.
70
+
71
+ ## What to report
72
+
73
+ - The two or three real hot-spots, named with the project's own region and mesh names, each with
74
+ its share of attention.
75
+ - The cold areas and the meshes nobody notices.
76
+ - Where gaze fails to convert into interaction, and what that implies for layout and
77
+ call-to-action placement.
78
+
79
+ Carry the caveats: heatmaps are gated on the view/pointer capture channels and are sampled
80
+ (ADR 0012), so a share is a share _of the sampled events_; say so, and say when a result was
81
+ truncated (`meta.truncated`) or sits below the metric's own minimum sample. Do not turn a voxel
82
+ cluster into a claim about one object unless `click_rays` or `flow_links` attributes it to that
83
+ mesh.
84
+
85
+ End with 2–3 concrete layout or content recommendations. If an `annotate` tool is available, leave
86
+ a note on the region you want revisited — a region-scoped annotation is what makes the next report
87
+ open where this one ended. If a `pin_panel` tool is available, pin the heatmap panel you reasoned
88
+ from.
@@ -0,0 +1,97 @@
1
+ ---
2
+ name: conversion_investigation
3
+ title: Conversion investigation
4
+ description: >-
5
+ Find out where a funnel loses people and whether the loss is real: step-by-step drop-off, the
6
+ bounce that happens before the funnel even starts, scene-to-scene retention, variant performance,
7
+ and the interaction failures (dead clicks, rage clicks, unreachable meshes) that explain a stalled
8
+ step. USE FOR: a funnel that converts worse than expected, an A/B variant comparison, "where do
9
+ people drop off", diagnosing a step nobody completes. Trigger phrases: conversion, funnel,
10
+ drop-off, why are people leaving, which variant wins, bounce rate, retention, people get stuck.
11
+ tools:
12
+ - funnel
13
+ - load_bounce_funnel
14
+ - scene_retention
15
+ - variant_leaderboard
16
+ - dead_clicks
17
+ - rage_clicks
18
+ - mesh_reachability
19
+ - flow_links
20
+ - insight_significance
21
+ - insight_movers
22
+ - query
23
+ capabilities:
24
+ - query
25
+ args:
26
+ - name: scene
27
+ required: false
28
+ description: Optional scene id to scope the investigation to (see the uptimizr://scenes resource).
29
+ - name: range
30
+ required: false
31
+ default: the last 7 days
32
+ description: The window to investigate, in words — e.g. "the last 7 days", "since the release".
33
+ ---
34
+
35
+ Investigate conversion for {{scope}} over {{range}}: where do people drop off, and is the drop real?
36
+
37
+ Work through the method below with the read-only tools it names, then answer.
38
+
39
+ 1. **Orient before you ask anything.** Read the `uptimizr://context` resource first. A funnel is
40
+ built out of **this project's own event types and custom-event names** — invent one and every
41
+ step reads zero. The context document lists the vocabulary the application actually emits, the
42
+ real scene ids, and which metrics are empty because their capture channel is off.
43
+
44
+ 2. **Check the step before the first step.** `load_bounce_funnel`{{#scene}} (`scene="{{scene}}"`){{/scene}}
45
+ measures load → first interaction → stay. If people leave before the funnel starts, nothing
46
+ inside it will explain the number, and a "conversion problem" is really a load or a first-impression
47
+ problem.
48
+
49
+ 3. **Run the funnel itself.** `funnel`{{#scene}} (`scene="{{scene}}"`){{/scene}} with the `steps`
50
+ built from the context document's vocabulary. Read it as the _transition_ rates, not the totals:
51
+ the step with the worst step-to-step rate is the one to investigate, even when a later step has
52
+ fewer people in absolute terms.
53
+
54
+ 4. **Follow them out of the scene.** `scene_retention`{{#scene}} (`scene="{{scene}}"`){{/scene}}
55
+ shows where a visitor goes next. A step that "loses" people to the next scene is not a loss at
56
+ all; one that loses them to nothing is.
57
+
58
+ 5. **Explain the stalled step.** At the worst step, look for interaction failure rather than
59
+ intent: `dead_clicks` (clicks that hit nothing actionable), `rage_clicks` (repeated clicking in
60
+ one spot — frustration you can locate), `mesh_reachability` (the target is too far away or
61
+ behind something to be clicked at all) and `flow_links` (people look at the target and never
62
+ click it). One of these usually _is_ the drop-off.
63
+
64
+ 6. **Compare variants honestly.** `variant_leaderboard` ranks variants by conversion. A leaderboard
65
+ is not a verdict: check each variant's sample size before repeating its rate, and say plainly
66
+ when two variants are too close or too small to separate. `insight_significance` can test a
67
+ metric with a portable bucket series across two **windows**; it does not test one segment against
68
+ another, and if you ask it to it will say so — report that limitation rather than inventing a
69
+ p-value.
70
+
71
+ 7. **See whether this is new.** `insight_movers`{{#scene}} (`scene="{{scene}}"`){{/scene}} ranks
72
+ what changed against the previous equal window, so you can tell "this funnel has always been bad"
73
+ from "this funnel broke last Tuesday". Ignore any row with `aboveMinSample: false`.
74
+
75
+ 8. **Use the DSL for the cuts the canned tools do not expose.** The single `query` tool takes a
76
+ `metric`, a `range`, that metric's filters, and `compare: { range: <previous window> }` to return
77
+ `{ current, previous, delta, deltaPct }` already joined — do not subtract two runs by hand. Use
78
+ `dimensions` to regroup a portable count (by device class, source or scene) and
79
+ `format: "summary"` for a bounded digest with a `reading` and a `drillQuery` per row. When a step
80
+ reads zero, re-send it with `explain: true` before reporting it: the plan will tell you whether
81
+ the number is real or whether the channel behind it is switched off.
82
+
83
+ ## What to report
84
+
85
+ - The step that actually loses people, with its entry and exit counts and its transition rate.
86
+ - The mechanism, named: dead clicks on a specific mesh, an unreachable target, a bounce before the
87
+ first interaction, or a genuine loss of interest.
88
+ - What each variant did, with sample sizes, and whether the difference can be told apart from noise.
89
+
90
+ Carry the caveats into the text: funnel steps are only as good as the event vocabulary they were
91
+ built from, a rate over a handful of sessions is not a rate, `meta.truncated` means you are looking
92
+ at a cut-off list, and a disabled capture channel produces a zero that means "not measured".
93
+
94
+ End with 2–3 concrete recommendations tied to the step and the mesh they apply to. If an `annotate`
95
+ tool is available, leave a note on the failing step so the next investigation starts there, and use
96
+ `save_analysis` to store the funnel definition you settled on — the next run should not have to
97
+ guess the steps again. If a `pin_panel` tool is available, pin the funnel panel.
@@ -0,0 +1,106 @@
1
+ ---
2
+ name: performance_regression_triage
3
+ title: Performance regression triage
4
+ description: >-
5
+ Triage a frame-rate or stability regression: confirm it moved, date it, locate it (which scene,
6
+ device class, place in the scene), and name the mechanism — jank, shader compile stalls, memory
7
+ pressure, a render-scale change or a rendering-technology shift. USE FOR: "the app got slower",
8
+ a FPS drop after a release, stutter reports, deciding whether a regression is real or noise.
9
+ Trigger phrases: performance regression, FPS dropped, why is it slow, stutter, jank, frame drops,
10
+ did the last release slow things down, triage performance.
11
+ tools:
12
+ - insight_movers
13
+ - insight_anomalies
14
+ - insight_significance
15
+ - insight_baseline
16
+ - perf_summary
17
+ - perf_distribution
18
+ - frame_time_percentiles
19
+ - jank_rate
20
+ - perf_by_device
21
+ - perf_by_scene
22
+ - perf_heatmap
23
+ - compile_stalls
24
+ - resource_percentiles
25
+ - render_scale_truth
26
+ - rendering_technology
27
+ - query
28
+ capabilities:
29
+ - query
30
+ args:
31
+ - name: scene
32
+ required: false
33
+ description: Optional scene id to scope the triage to (see the uptimizr://scenes resource).
34
+ - name: range
35
+ required: false
36
+ default: the last 14 days
37
+ description: The window to triage, in words — e.g. "the last 14 days", "since the release".
38
+ ---
39
+
40
+ Triage the performance regression in {{scope}} over {{range}}: is it real, when did it start, who
41
+ does it hit, and what is causing it?
42
+
43
+ Work through the method below with the read-only tools it names, then answer.
44
+
45
+ 1. **Orient before you ask anything.** Read the `uptimizr://context` resource first: the real scene
46
+ ids, the data freshness, and which metrics are empty because their capture channel is off. A
47
+ performance channel that was never enabled looks exactly like a scene with no problem.
48
+
49
+ 2. **Confirm something moved.** `insight_movers`{{#scene}} (`scene="{{scene}}"`){{/scene}} ranks
50
+ every comparable metric against the previous equal window by how unusual the change is. Read
51
+ `direction` with the sign of `delta` — a _rise_ in jank or errors is a regression — and drop any
52
+ row with `aboveMinSample: false`.
53
+
54
+ 3. **Ask whether the new level is outside normal.** `insight_baseline` on `perf_summary` gives the
55
+ project's own median and spread; compare the new reading with `median` give or take a few `mad`,
56
+ or with the p10..p90 band. Frame rates are noisy, and "down 6 FPS" is routine in some projects
57
+ and an incident in others.
58
+
59
+ 4. **Prove it rather than asserting it.** `insight_significance` on the metric that moved reports
60
+ the effect, a 95 % interval and a p-value across the two windows. An interval straddling 0 means
61
+ you cannot tell yet; `powerNote` says what this much data could have detected at all. Say "not
62
+ yet distinguishable from noise" when that is the truth — it is a finding.
63
+
64
+ 5. **Put a date on it.** `insight_anomalies` (`metric=perf_summary`, `window=28`{{#scene}},
65
+ `scene="{{scene}}"`{{/scene}}) separates a one-day `spike`/`drop` from a `shift` — a level that
66
+ changed and stayed changed, which is what a release looks like. Quote the `bucketStart` and the
67
+ `contributor`. For the day-by-day shape around that date, ask the `query` tool for
68
+ `metric: "perf_daily"` — it is a registry metric with no canned tool of its own.
69
+
70
+ 6. **Locate it.** `perf_by_scene` (which scene), `perf_by_device` (which device class — a
71
+ regression that only hits low-end hardware is a different bug from one that hits everyone), and
72
+ `perf_heatmap`{{#scene}} (`scene="{{scene}}"`){{/scene}} for _where in the scene_ the frames are
73
+ being lost. Ask for `format: "summary"` on the heatmap: merged clusters with shares, not a grid.
74
+
75
+ 7. **Name the mechanism.** `frame_time_percentiles` and `jank_rate` separate "uniformly slower"
76
+ from "occasionally catastrophic" — the second is what users report and the average hides.
77
+ `perf_distribution` shows whether the whole population shifted or a tail got worse.
78
+ `compile_stalls` finds shader/pipeline compilation blocking the first seconds.
79
+ `resource_percentiles` finds GPU/memory pressure. `render_scale_truth` catches a resolution
80
+ change quietly doing the work the frame rate is getting credit for, and `rendering_technology`
81
+ catches a shift in the engine/renderer mix between the two windows — a "regression" that is
82
+ really a change in who is measuring.
83
+
84
+ 8. **Cut it any way you need with the DSL.** The single `query` tool takes a `metric`, a `range`,
85
+ that metric's filters and `compare: { range: <the window before the shift> }`, returning
86
+ `{ current, previous, delta, deltaPct }` already joined — never subtract two runs yourself. Use
87
+ `dimensions` to regroup a portable count, `format: "summary"` for a bounded digest with a
88
+ `reading` and a per-row `drillQuery`, and `explain: true` whenever a number looks impossible:
89
+ the plan names the sample size, the row cap and every capture channel that could make it lie.
90
+
91
+ ## What to report
92
+
93
+ - Whether the regression is real, with the effect, the interval and the honest verdict when it is
94
+ not yet distinguishable from noise.
95
+ - The date it started and whether it is a spike or a sustained shift.
96
+ - Who it hits: scene, device class, and where in the scene.
97
+ - The mechanism, named, with the metric that shows it.
98
+
99
+ Carry the caveats: percentiles below the metric's minimum sample, a session count too small to
100
+ generalise, sampled capture (ADR 0012), truncated results, and any device class whose share of
101
+ traffic changed between the windows — a mix shift moves the average without anything getting slower.
102
+
103
+ End with 2–3 concrete recommendations naming the scene, the device class or the asset they apply
104
+ to. If an `annotate` tool is available, leave a dated note on the shift so the next report can see
105
+ what happened; `save_analysis` keeps the triage for the post-mortem. If a `pin_panel` tool is
106
+ available, pin the panel that shows the regression.
@@ -0,0 +1,105 @@
1
+ ---
2
+ name: weekly_scene_health
3
+ title: Weekly scene health
4
+ description: >-
5
+ A weekly health check for a scene (or the whole project): a weighted health score with every
6
+ factor traced back to the metric behind it, what changed against last week, traffic, event mix,
7
+ performance, and the most-interacted meshes. USE FOR: the recurring "how is the scene doing?"
8
+ review, a scheduled weekly or monthly report, a first look at a project you do not know yet,
9
+ deciding which scene to investigate next. Trigger phrases: weekly report, scene health, how is
10
+ my scene doing, what changed this week, health check, monthly review, status report.
11
+ tools:
12
+ - insight_scene_health
13
+ - insight_movers
14
+ - insight_baseline
15
+ - insight_significance
16
+ - insight_anomalies
17
+ - event_counts
18
+ - timeseries
19
+ - perf_summary
20
+ - top_meshes
21
+ - list_sessions
22
+ - query
23
+ capabilities:
24
+ - query
25
+ args:
26
+ - name: scene
27
+ required: false
28
+ description: Optional scene id to scope the analysis to (see the uptimizr://scenes resource).
29
+ - name: range
30
+ required: false
31
+ default: the last 7 days
32
+ description: The window to report on, in words — e.g. "the last 7 days", "June".
33
+ ---
34
+
35
+ Give me a weekly health report for {{scope}} covering {{range}}.
36
+
37
+ Work through the method below with the read-only tools it names, then summarise the findings.
38
+
39
+ 1. **Orient before you ask anything.** Read the `uptimizr://context` resource first: it gives the
40
+ real scene ids, region ids and custom-event names for this project, and tells you which metrics
41
+ are empty because their capture channel is off. Use its ids instead of inventing your own, and
42
+ never report a switched-off channel's zero as a finding.
43
+
44
+ 2. **Start from the score, not the numbers.** Call `insight_scene_health`{{#scene}} with
45
+ `scene="{{scene}}"`{{/scene}}: it scores each scene 0–100 over six weighted factors — perf
46
+ stability, jank, errors, dead clicks, coverage and XR abandonment — so you start from _which_
47
+ scene to look at. Open the lowest-scoring scene first, then the factor whose own score is
48
+ furthest below 50. Every factor names the `metric` behind it, its `raw` value and the project
49
+ `baseline` it was compared with, so the sentence you write is already in the row. 50 is the
50
+ project norm, not a pass mark, and a factor with `score: null` was not counted — its `note`
51
+ says why.
52
+
53
+ 3. **Find out what moved.** Call `insight_movers`{{#scene}} (`scene="{{scene}}"`){{/scene}}: it
54
+ compares every comparable metric with the previous equal window and ranks the changes by how
55
+ unusual each one is, so start from what actually moved instead of re-deriving it. Read
56
+ `direction` together with the sign of `delta` — a rise in a `down` metric (errors, dead clicks,
57
+ jank) is a regression — and do not report any row with `aboveMinSample: false`: its delta is
58
+ real arithmetic but not evidence.
59
+
60
+ 4. **Ask whether the new level is even unusual.** For each metric that moved, call
61
+ `insight_baseline` and compare the new value with `median` give or take a few `mad`, or with
62
+ the p10..p90 band. "Down 12 %" means nothing until you know the week-to-week spread.
63
+
64
+ 5. **Before calling any single change real, test it.** `insight_significance` reports the effect,
65
+ a 95 % interval and a p-value for one metric across the two windows. An interval that straddles
66
+ 0 means you cannot tell yet, whatever the p-value says, and `powerNote` states what this much
67
+ data could have detected at all.
68
+
69
+ 6. **Put a date on it.** Call `insight_anomalies` (`metric=perf_summary`, then `error_heatmap`{{#scene}},
70
+ `scene="{{scene}}"`{{/scene}}, `window=28`): it returns the individual days that were out of line
71
+ (`spike` / `drop`) and the day a level changed and stayed changed (`shift`), with `contributor`
72
+ naming the mesh, channel or source holding most of the excess. Quote the `bucketStart` and the
73
+ `contributor` rather than saying "recently".
74
+
75
+ 7. **Fill in the picture.** `event_counts` for the per-event-type mix{{#scene}} (`scene="{{scene}}"`){{/scene}};
76
+ `timeseries` (`interval` ≈ 86400 s) for day-by-day volume and the average-FPS trend;
77
+ `perf_summary` for avg/min/p50 FPS; `top_meshes` for the most-interacted meshes; `list_sessions`
78
+ for how many sessions were recorded.
79
+
80
+ 8. **Drill with the DSL, not with arithmetic.** When a canned tool does not expose the filter you
81
+ need, use the single `query` tool: pick the `metric`, bound it with `range`, and set
82
+ `compare: { range: <previous week> }` so the collector returns
83
+ `{ current, previous, delta, deltaPct }` already joined — never run two queries and subtract
84
+ them yourself. `dimensions` regroups a portable count (event, mesh, input-source and gesture
85
+ tallies) without a new tool. Ask for `format: "summary"` on anything long or spatial: it comes
86
+ back as a bounded digest with shares, a plain-language `reading`, and a `drillQuery` per row you
87
+ can send straight back. If a result is surprising or empty, re-send it with `explain: true` and
88
+ read the plan before you report the number.
89
+
90
+ ## What to report
91
+
92
+ - The score and the two or three factors that drag it down, each with the metric id behind it.
93
+ - What moved, by how much, and whether it is outside the baseline — with the date the anomaly
94
+ scan put on it.
95
+ - Traffic, event mix, FPS trend and the meshes people actually touch.
96
+
97
+ Always carry the caveats into the text rather than dropping them: a row below its minimum sample
98
+ (`aboveMinSample: false`), a metric whose capture channel the context document says is off, and any
99
+ `meta.truncated` or `caveats` entry on a result you quoted. Say "not enough data to tell" when that
100
+ is the honest answer.
101
+
102
+ End with 2–3 concrete recommendations — each naming the scene, the mesh or the day it applies to.
103
+ If an `annotate` tool is available, leave a dated note on the finding you want the next reader to
104
+ see, and if `save_analysis` is available, store the report so next week's run has something to
105
+ compare against. If a `pin_panel` tool is available, pin the panel behind the headline factor.
@@ -0,0 +1,95 @@
1
+ ---
2
+ name: xr_comfort_audit
3
+ title: XR comfort & drop-off audit
4
+ description: >-
5
+ Audit VR/AR comfort for a scene (or the whole project): rapid head rotation, locomotion style,
6
+ tracking quality, guardian/boundary contacts, input-source mix, and the short sessions that mean
7
+ someone took the headset off. USE FOR: motion-sickness complaints, immersive sessions that end
8
+ early, choosing a locomotion scheme, checking whether a play space is big enough. Trigger phrases:
9
+ XR comfort, VR motion sickness, why do people quit VR, headset drop-off, teleport vs smooth
10
+ locomotion, guardian boundary, hand tracking vs controllers.
11
+ tools:
12
+ - xr_rotation
13
+ - xr_locomotion
14
+ - xr_abandonment
15
+ - xr_sources
16
+ - xr_tracking_quality
17
+ - xr_boundary_contacts
18
+ - boundary_heatmap_stats
19
+ - insight_scene_health
20
+ - insight_movers
21
+ - query
22
+ capabilities:
23
+ - query
24
+ args:
25
+ - name: scene
26
+ required: false
27
+ description: Optional scene id to scope the audit to (see the uptimizr://scenes resource).
28
+ - name: range
29
+ required: false
30
+ default: the last 7 days
31
+ description: The window to audit, in words — e.g. "the last 7 days", "since the XR release".
32
+ ---
33
+
34
+ Audit XR/immersive comfort and drop-off for {{scope}} over {{range}}.
35
+
36
+ Work through the method below with the read-only tools it names{{#scene}}, all scoped with
37
+ `scene="{{scene}}"`{{/scene}}, and correlate the signals — no single one of them is a verdict.
38
+
39
+ 1. **Orient before you ask anything.** Read the `uptimizr://context` resource first. Every metric
40
+ below is gated on the **XR capture channel**: if the context document says it is off, these
41
+ tools return empty by design and the honest answer is "XR capture is not enabled here", not
42
+ "there is no comfort problem".
43
+
44
+ 2. **Rapid head rotation — the motion-sickness proxy.** `xr_rotation`{{#scene}} (`scene="{{scene}}"`){{/scene}}
45
+ gives the rate of fast head/view turns. High rates are a proxy, not a diagnosis: they can mean
46
+ discomfort-inducing camera work, or simply a scene that rewards looking around. Read it against
47
+ the next two signals before calling it.
48
+
49
+ 3. **Locomotion style and session span.** `xr_locomotion`{{#scene}} (`scene="{{scene}}"`){{/scene}}
50
+ gives the fly / navigate / teleport mix and how long each session lasted. Continuous (smooth)
51
+ locomotion paired with heavy rapid rotation is the classic uncomfortable combination; teleport
52
+ is the usual mitigation.
53
+
54
+ 4. **Did they take the headset off?** `xr_abandonment`{{#scene}} (`scene="{{scene}}"`){{/scene}}
55
+ finds immersive sessions that ended early. Early exits _plus_ one of the two signals above is
56
+ the finding; early exits on their own may just be a short demo.
57
+
58
+ 5. **Rule out the boring explanations first.** `xr_tracking_quality` catches tracking loss — an
59
+ exit caused by the headset losing its pose is not a comfort problem.
60
+ `xr_boundary_contacts` and `boundary_heatmap_stats` catch a play space that is too small or a
61
+ scene that pushes people into the guardian; that is a layout problem with a layout fix.
62
+
63
+ 6. **Who is playing.** `xr_sources`{{#scene}} (`scene="{{scene}}"`){{/scene}} gives the hand vs.
64
+ controller vs. gaze input split. Comfort conclusions differ by input source, and a shift in the
65
+ mix between two windows can move every other number without anything getting worse.
66
+
67
+ 7. **Put it in context.** `insight_scene_health` scores XR abandonment as one of its six weighted
68
+ factors, so it tells you whether this scene is unusual _for this project_;
69
+ `insight_movers`{{#scene}} (`scene="{{scene}}"`){{/scene}} tells you whether the comfort signals
70
+ moved against the previous equal window. Drop any row with `aboveMinSample: false`.
71
+
72
+ 8. **Cut it further with the DSL.** The single `query` tool takes a `metric`, a `range`, that
73
+ metric's filters, `dimensions` to regroup a portable count, and `compare: { range: <previous
74
+ window> }` for `{ current, previous, delta, deltaPct }` already joined. `format: "summary"`
75
+ returns a bounded digest with a plain-language `reading`; `explain: true` returns the plan
76
+ instead of the rows and names every reason the answer might mislead — worth one call before
77
+ reporting a zero on a channel-gated metric.
78
+
79
+ ## What to report
80
+
81
+ - The uncomfortable patterns you can actually evidence: heavy rapid rotation or continuous
82
+ locomotion paired with early exits, named per scene and per input source.
83
+ - The alternatives you ruled out: tracking loss, boundary contacts, a short-by-design experience.
84
+ - Comfort mitigations: teleport or snap-turn options, vignetting during movement, slower or
85
+ user-controlled camera motion, a larger required play space, or moving interactive content
86
+ inside the guardian.
87
+
88
+ Carry the caveats: XR sessions are a small fraction of most projects' traffic, so say the session
89
+ count next to every rate; the rotation and locomotion metrics are sampled (ADR 0012); and a metric
90
+ whose channel is off is not a zero. Never turn one uncomfortable session into a trend.
91
+
92
+ End with 2–3 concrete comfort changes, each tied to the scene and the locomotion or input mode it
93
+ applies to. If an `annotate` tool is available, leave a note on the scene so the next audit can see
94
+ what was tried; `save_analysis` keeps the before-picture to compare the fix against. If a
95
+ `pin_panel` tool is available, pin the locomotion-comfort panel.