@qaping/cli 0.1.1 → 0.1.4
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/bin/qaping +3 -2
- package/docs/QA-PLAN-FORMAT.md +69 -15
- package/package.json +1 -1
- package/skill/qaping/SKILL.md +313 -83
package/bin/qaping
CHANGED
|
@@ -43,8 +43,9 @@ function resolveKitDir() {
|
|
|
43
43
|
const RULE_BODY = `This machine has qaping: automatic QA for games — real human playtesters play the developer's builds on their own hardware and report back, and the coding agent runs the whole loop.
|
|
44
44
|
Whenever the user asks to set up QA for a game, run QA on a patch, check whether a change broke anything, or playtest a game or build, load the qaping skill and follow it — it owns authoring/maintaining QA-PLAN.md in the game repo and the per-patch run.
|
|
45
45
|
Builds ship with \`qaping publish-build <game.zip> --platform windows|macos\`; the printed /b/<slug> URL is what a round is filed against (store-delivered games file with their Steam/TestFlight/App Store URL instead).
|
|
46
|
-
The MCP tools are qaping_playtest (file a round with real human playtesters), qaping_results (fetch a finished round, free) and qaping_wait (continue a pending round —
|
|
47
|
-
Playtests are duration-billed at 2 credits per minute of play per playtester — state the estimated cost before filing. Windows rounds return a recording
|
|
46
|
+
The MCP tools are qaping_playtest (file a round with real human playtesters), qaping_results (fetch a finished round, free) and qaping_wait (continue a pending round — on pending, spawn a background task that loops qaping_wait and keep working; foreground polling is the fallback, and never report pending as the answer or end with an unbacked promise to check later).
|
|
47
|
+
Playtests are duration-billed at 2 credits per minute of play per playtester — state the estimated cost before filing. Windows rounds return a recording and no transcript; web rounds return answers only (no recording, no transcript) — never promise evidence a platform does not ship.
|
|
48
|
+
QA memory: whenever the developer mentions — in ANY conversation, not just QA runs — something that should always be tested, a fragile area, or a QA lesson learned, offer to record it as a check (or a note on an existing check) in QA-PLAN.md so it is tested from then on.
|
|
48
49
|
`;
|
|
49
50
|
|
|
50
51
|
// The wrapper object harness/setup.js's opts.wrapper API takes; also the one
|
package/docs/QA-PLAN-FORMAT.md
CHANGED
|
@@ -12,15 +12,16 @@ YAML at the very top of the file:
|
|
|
12
12
|
---
|
|
13
13
|
game: Solar Drift
|
|
14
14
|
build_command: ./scripts/package.sh --release # the ONE command that produces a shippable build
|
|
15
|
-
platforms: [windows] # windows | macos | ios
|
|
15
|
+
platforms: [windows] # windows | macos | ios | web
|
|
16
16
|
input: XInput gamepad # optional; default keyboard+mouse
|
|
17
17
|
---
|
|
18
18
|
```
|
|
19
19
|
|
|
20
20
|
`build_command` is recorded once at setup so every future run can build without
|
|
21
|
-
asking. `
|
|
22
|
-
|
|
23
|
-
|
|
21
|
+
asking. `platforms: [web]` means a browser-playable game — its "build" is a
|
|
22
|
+
static directory, not a zip. `input` declares the hardware the
|
|
23
|
+
first-15-minutes check (and any check that needs it) is played on — rounds
|
|
24
|
+
must ask playtesters to confirm what they actually used.
|
|
24
25
|
|
|
25
26
|
## Checks
|
|
26
27
|
|
|
@@ -30,19 +31,41 @@ opens with a fenced yaml block, then the steps as prose:
|
|
|
30
31
|
```yaml
|
|
31
32
|
id: save-loads # stable slug — never reused, never renamed
|
|
32
33
|
rung: human # human | code
|
|
34
|
+
state: unverified # unverified | passed | ambiguous | failing
|
|
33
35
|
origin: authored # "authored", or the round id that minted/last changed it
|
|
34
36
|
last_verified: 2026-08-25 # date or build tag of the last passing verification
|
|
37
|
+
setup: "?save=campaign-mid" # optional: the link/flag that forces this check's precondition
|
|
35
38
|
```
|
|
36
39
|
|
|
37
40
|
- **Steps are prose for a person**: numbered, imperative, written so a
|
|
38
|
-
playtester who has never seen the game can follow them
|
|
39
|
-
|
|
41
|
+
playtester who has never seen the game can follow them. At filing time each
|
|
42
|
+
check becomes ONE id-tagged step with typed verdict options, so end the
|
|
43
|
+
steps in an observation ask ("describe what you saw"), never a
|
|
44
|
+
confirmation ("confirm it appears").
|
|
45
|
+
- **`state` is the check's verdict history**: `unverified` (never verified on
|
|
46
|
+
the current behavior), `passed` (last verified answer was Pass),
|
|
47
|
+
`ambiguous` (a fail that could be dirty state or tester noise — refiles
|
|
48
|
+
alone as a targeted round), `failing` (a fail backed by hard evidence or a
|
|
49
|
+
targeted round). Blocked/VOID outcomes change nothing here.
|
|
50
|
+
- **`setup` names the test affordance** that forces the check's precondition —
|
|
51
|
+
a URL param (`?fresh=1`, `?state=<checkpoint>`), a flag, a bundled-fixture
|
|
52
|
+
load control. At filing time the hook rides the ROUND's `url` — the review
|
|
53
|
+
interface loads it itself; testers cannot open links or type URLs — and
|
|
54
|
+
the filed step states only the player-visible expectation, never the
|
|
55
|
+
mechanism. A check
|
|
56
|
+
whose precondition can be neither forced nor verified is testimony-only —
|
|
57
|
+
say so in its prose.
|
|
58
|
+
- **Fixtures are provisioned by the dev's repo**: a previous-version save
|
|
59
|
+
ships inside the build (or seeds via URL param on web) and is named in the
|
|
60
|
+
check's steps — an anonymous playtester in a fresh browser cannot possess
|
|
61
|
+
it.
|
|
40
62
|
- **Order the file critical-path first**: boots to menu, previous-version save
|
|
41
63
|
loads, first 15 minutes on the declared input hardware, core loop, settings
|
|
42
64
|
persist — then game-specific checks.
|
|
43
65
|
- A `rung: code` check keeps its prose (it documents intent) and adds one
|
|
44
66
|
`test:` line naming the repo test that now asserts it, e.g.
|
|
45
|
-
`` test: `tests/save_compat.test.ts` ``.
|
|
67
|
+
`` test: `tests/save_compat.test.ts` ``. Mechanically-assertable checks
|
|
68
|
+
enter at `rung: code` on day one — human rounds are for judgment.
|
|
46
69
|
|
|
47
70
|
## Example section
|
|
48
71
|
|
|
@@ -52,20 +75,51 @@ last_verified: 2026-08-25 # date or build tag of the last passing verification
|
|
|
52
75
|
```yaml
|
|
53
76
|
id: save-loads
|
|
54
77
|
rung: human
|
|
78
|
+
state: unverified
|
|
55
79
|
origin: authored
|
|
56
|
-
last_verified:
|
|
80
|
+
last_verified: never
|
|
81
|
+
setup: "?save=campaign-mid"
|
|
57
82
|
```
|
|
58
83
|
|
|
59
|
-
1.
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
84
|
+
1. The game opens already loaded into the save "campaign-mid.sav" (made on
|
|
85
|
+
the previous release; the round's link forces it — no action needed).
|
|
86
|
+
2. Play for one minute in the desert outpost.
|
|
87
|
+
3. Describe what you saw: where you resumed, what the inventory held, and
|
|
88
|
+
anything missing, corrupted, or visually wrong.
|
|
63
89
|
````
|
|
64
90
|
|
|
91
|
+
## The round ledger — qa-rounds.jsonl
|
|
92
|
+
|
|
93
|
+
Beside the plan, at the game repo root, sits `qa-rounds.jsonl`: one JSON line
|
|
94
|
+
appended per round, committed alongside the plan updates —
|
|
95
|
+
|
|
96
|
+
```json
|
|
97
|
+
{"round_id": "…", "report_url": "…", "build": "<sha or hosted url>",
|
|
98
|
+
"evidence_grade": "…", "checks": [{"id": "…", "outcome": "…"}],
|
|
99
|
+
"verdict": "…", "credits": 20, "follow_ups": ["…"]}
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Outcomes are `Pass | Fail | Blocked | NOT-ASKED` — a VOID (precondition
|
|
103
|
+
unverified/disproven) is recorded as `Blocked` with a "VOID: …" note in the
|
|
104
|
+
report, never its own enum value. `report_url` is the round's service report
|
|
105
|
+
page (what the results tool returns), never a PR or issue link. There is no
|
|
106
|
+
evidence-link field, deliberately: signed recording URLs expire in days.
|
|
107
|
+
The ledger is the machine truth of what was asked and answered, per check,
|
|
108
|
+
per round; the plan's `last_verified` and `state` are derived from it and
|
|
109
|
+
stay the dev-readable contract. Open `follow_ups` are what the next filing
|
|
110
|
+
must carry forward.
|
|
111
|
+
|
|
65
112
|
## Maintenance rules
|
|
66
113
|
|
|
67
|
-
- Every run updates the run checks' `last_verified
|
|
68
|
-
|
|
69
|
-
-
|
|
114
|
+
- Every run updates the run checks' `state` and `last_verified` from typed
|
|
115
|
+
outcomes (Pass stamps both; Blocked/VOID/NOT-ASKED touch neither);
|
|
116
|
+
findings-driven edits record the round id as `origin`.
|
|
117
|
+
- New checks start `rung: human` (unless mechanically assertable — then
|
|
118
|
+
`rung: code` with a repo test written at entry), `state: unverified`,
|
|
119
|
+
`origin: authored`.
|
|
70
120
|
- Promotion (human → code) and any deletion/demotion happen only with the
|
|
71
121
|
developer's explicit agreement.
|
|
122
|
+
- **QA memory**: the plan accretes from conversation, not just QA runs — when
|
|
123
|
+
the developer mentions something that should always be tested, a fragile
|
|
124
|
+
area, or a lesson learned, the agent offers to record it as a check (or a
|
|
125
|
+
note on an existing check) right then.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@qaping/cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"description": "qaping: automatic QA for your game. Your coding agent authors and maintains the QA plan, ships each patch's build to real human playtesters, and reports what broke.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"qaping",
|
package/skill/qaping/SKILL.md
CHANGED
|
@@ -6,31 +6,92 @@ description: Run automatic QA for a game repo through qaping — real human play
|
|
|
6
6
|
# qaping — the QA loop for a game repo
|
|
7
7
|
|
|
8
8
|
Two jobs, one file. SETUP authors `QA-PLAN.md` — the committed list of checks
|
|
9
|
-
this game must pass. RUN executes it per patch: pick the affected checks,
|
|
10
|
-
ship the build to real human playtesters, report
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
9
|
+
this game must pass. RUN executes it per patch: pick the affected checks,
|
|
10
|
+
build, ship the build to real human playtesters, and report ONE typed outcome
|
|
11
|
+
per check — never a vibe. The check is the unit of value: its id rides from
|
|
12
|
+
the plan onto the wire, into the report, and into the round ledger. Over
|
|
13
|
+
patches, checks migrate from human rounds to repo code tests, so QA gets
|
|
14
|
+
cheaper. All service contact goes through three MCP tools —
|
|
15
|
+
`qaping_playtest`, `qaping_results`, `qaping_wait` — plus
|
|
16
|
+
`qaping publish-build` for hosting the build.
|
|
15
17
|
|
|
16
18
|
## Ground rules (read before filing anything)
|
|
17
19
|
|
|
18
|
-
- **Playtesters are real people
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
20
|
+
- **Playtesters are real people, and waiting is background work.** A session
|
|
21
|
+
takes real minutes to be claimed and played, and the wait chain matters: each
|
|
22
|
+
`qaping_wait` renews the lease that keeps the round visible to new claimants.
|
|
23
|
+
The moment a filing returns pending, SPAWN A BACKGROUND TASK that loops
|
|
24
|
+
`qaping_wait` with the ping_id until results arrive, and keep working on
|
|
25
|
+
other things — that is the intended shape, the same as every pingfusi round.
|
|
26
|
+
Only if your harness has no background tasks: keep polling in the foreground.
|
|
27
|
+
Only in a one-shot print mode where neither is possible: say plainly that the
|
|
28
|
+
round is pending, give the ping_id, and ask to be re-invoked to continue —
|
|
29
|
+
never promise polling you cannot do (an ended session cannot poll; a round
|
|
30
|
+
went invisible and expired unclaimed exactly this way, live). Never report
|
|
31
|
+
pending as the final answer, never file a duplicate round.
|
|
22
32
|
- **Costs are duration-billed**: 2 credits per minute of play, per playtester
|
|
23
|
-
(`est_minutes × 2 × players`). State the estimate to the user BEFORE filing
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
33
|
+
(`est_minutes × 2 × players`). State the estimate to the user BEFORE filing,
|
|
34
|
+
and set the wait expectation honestly in the same message: claim time is
|
|
35
|
+
unbounded on a small pool — "push now or wait for QA" is their call, made
|
|
36
|
+
informed.
|
|
37
|
+
- **Evidence varies by platform — never promise what a platform does not
|
|
38
|
+
ship.** macOS and iOS rounds return a screen+voice recording plus an inline
|
|
39
|
+
[mm:ss] think-aloud transcript. Windows rounds return a recording and NO
|
|
40
|
+
transcript (`transcript_status:'unavailable'`) — never promise one; plan to
|
|
41
|
+
watch the recording for timestamps. WEB rounds return answers only — no
|
|
42
|
+
recording, no transcript: the tester's option picks and free text are the
|
|
43
|
+
entire deliverable, which is why every check rides an option step.
|
|
44
|
+
- **Pay is flat per session, never per bug.** Never offer or imply a bounty.
|
|
45
|
+
Never steer a tester's OS-security decision (stopping at a warning is their
|
|
46
|
+
penalty-free call); steering their PLAY — steps, links, forced states — is
|
|
47
|
+
the job.
|
|
48
|
+
- **Machine replay is not self-serve yet.** Never claim an automated replay
|
|
49
|
+
ran. The only automated rung you can run today is a `rung: code` test in
|
|
50
|
+
the dev's own repo/CI.
|
|
31
51
|
- **QA-PLAN.md is the dev's file.** Commit it only with their approval; never
|
|
32
52
|
delete or demote a human check without them agreeing.
|
|
33
53
|
|
|
54
|
+
## The review interface (know your tester)
|
|
55
|
+
|
|
56
|
+
<!-- Distilled from QAPING_REVIEWER_INTERFACE.md (monorepo root, INTERNAL,
|
|
57
|
+
code-cited). Re-derive there when a reviewer surface changes. -->
|
|
58
|
+
|
|
59
|
+
Binding, derived from the review interfaces' code. The person who claims a
|
|
60
|
+
web round is on their phone, scrolling a feed. They read `instructions`
|
|
61
|
+
BEFORE claiming — front-load hardware and requirements there so the wrong
|
|
62
|
+
tester never claims. On claim the game auto-loads into an embedded browser
|
|
63
|
+
view with your steps beside it on the same small screen, with zero context;
|
|
64
|
+
attention is highest in the first minutes; setup friction is unpaid time.
|
|
65
|
+
|
|
66
|
+
The tester CAN: play the already-loaded game (touch, scroll, type); go
|
|
67
|
+
Back; Reload — which KEEPS storage, so a reload is never a fresh state; see
|
|
68
|
+
all steps at once and answer in any order; tap ONE option per option step;
|
|
69
|
+
type a note on any question step; pin a comment to a page element and draw
|
|
70
|
+
on it; pick one verdict; bail out with a reason.
|
|
71
|
+
|
|
72
|
+
The tester CANNOT:
|
|
73
|
+
- open tabs or windows — window.open and target=_blank links are dead
|
|
74
|
+
INSIDE the game too, so a game whose Play button opens a new window is
|
|
75
|
+
broken-for-review: flag it to the dev instead of filing;
|
|
76
|
+
- type or edit a URL: the round's `url` is the only page-load there is, and
|
|
77
|
+
links in step text are inert on the phone — never put links in steps;
|
|
78
|
+
- clear storage or go incognito: fresh state comes ONLY from a setup URL
|
|
79
|
+
param the round's `url` already carries;
|
|
80
|
+
- use devtools, attach files, or record — web rounds return answers only;
|
|
81
|
+
- use a second device;
|
|
82
|
+
- see `instructions` or `expected` once inside the review — EVERYTHING the
|
|
83
|
+
tester needs at answer time lives in the step text (≤300 chars each).
|
|
84
|
+
|
|
85
|
+
**Steps speak player language (MUST).** Things to tap and see in the game —
|
|
86
|
+
never flags, URL parameters, storage, or code concepts. A test hook is
|
|
87
|
+
exercised by the ROUND's `url` carrying it; the step describes only the
|
|
88
|
+
player-visible expectation ("the How to play option should be offered
|
|
89
|
+
first"), never the mechanism. Never ask to close/reopen anything or "start
|
|
90
|
+
fresh" — that state is a separate round whose `url` forces it. And as
|
|
91
|
+
always: one action per step, exact UI labels (the button says "Continue",
|
|
92
|
+
not "the resume option"), the most important check first, and never a step
|
|
93
|
+
that assumes they read your repo.
|
|
94
|
+
|
|
34
95
|
## SETUP — author QA-PLAN.md (first run in a repo)
|
|
35
96
|
|
|
36
97
|
1. Read the game repo: engine, how a shippable build is produced, save system,
|
|
@@ -39,92 +100,261 @@ the build.
|
|
|
39
100
|
shippable build) and which platforms they ship; record both in the plan's
|
|
40
101
|
frontmatter so every future run can build without asking again.
|
|
41
102
|
3. Author `QA-PLAN.md` at the repo root in EXACTLY this format (self-contained
|
|
42
|
-
here — do not go looking for other docs). Plan frontmatter (YAML at the
|
|
43
|
-
top): `game`, `build_command` (the ONE shippable-build command),
|
|
44
|
-
[windows|macos|ios|web]` (`web` = a browser-playable game — its
|
|
45
|
-
static directory, not a zip), optional `input` (hardware;
|
|
46
|
-
keyboard+mouse, or touch for a mobile-web game).
|
|
103
|
+
here — do not go looking for other docs). Plan frontmatter (YAML at the
|
|
104
|
+
very top): `game`, `build_command` (the ONE shippable-build command),
|
|
105
|
+
`platforms: [windows|macos|ios|web]` (`web` = a browser-playable game — its
|
|
106
|
+
"build" is a static directory, not a zip), optional `input` (hardware;
|
|
107
|
+
default keyboard+mouse, or touch for a mobile-web game).
|
|
47
108
|
Then one `##` section per check — the heading is the check's short name —
|
|
48
|
-
opening with a fenced yaml block of
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
109
|
+
opening with a fenced yaml block of `id` (stable slug, never renamed),
|
|
110
|
+
`rung: human | code`, `state` (`unverified | passed | ambiguous | failing`
|
|
111
|
+
— new checks start `unverified`), `origin` ("authored" or the round id that
|
|
112
|
+
last changed it), `last_verified` (date or build tag), and optionally
|
|
113
|
+
`setup` (the link or flag that forces this check's precondition — see
|
|
114
|
+
"Make the build testable"), followed by the steps as numbered prose a
|
|
115
|
+
playtester who has never seen the game can follow. Author each check to
|
|
116
|
+
end in an observation ask, with its precondition stated as a verifiable
|
|
117
|
+
step-zero fact (forced by `setup`, else verified by an opening question).
|
|
118
|
+
A `rung: code` check keeps its prose and adds one `test:` line naming the
|
|
119
|
+
repo test that asserts it. Order checks critical-path first, then
|
|
120
|
+
game-specific ones:
|
|
55
121
|
1. the game boots to the main menu
|
|
56
122
|
2. a previous-version save loads
|
|
57
123
|
3. the first 15 minutes play clean on the declared input hardware
|
|
58
124
|
4. the core loop works (name it concretely — the thing this game is)
|
|
59
125
|
5. settings persist across a restart
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
126
|
+
4. **Enter each check at the right rung.** A check that is mechanically
|
|
127
|
+
assertable — a save file loads, a config parses, an asset path resolves —
|
|
128
|
+
enters at `rung: code` from day one: write the repo test then and there,
|
|
129
|
+
never "start it human and promote later". Human rounds are reserved for
|
|
130
|
+
what only a human can judge. Where a save must still be human-tested, the
|
|
131
|
+
dev provides the fixture: the previous release's save ships inside the
|
|
132
|
+
build (or seeds via URL param on web), named in the check's steps — a
|
|
133
|
+
stranger in a fresh browser cannot possess your old save.
|
|
134
|
+
5. **Gyms**: for a mechanic worth isolating, you may build a tiny
|
|
135
|
+
deterministic test scene or page in the dev's repo — one mechanic,
|
|
136
|
+
machine-readable pass/fail. Ordinary dev work; offer it, never force it.
|
|
137
|
+
6. Tell the dev how runs get triggered, honestly: for now the loop runs from
|
|
63
138
|
their own machine or agent — after a patch lands, they say "run QA on this
|
|
64
|
-
patch"
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
139
|
+
patch". Do NOT offer to wire a CI workflow file: a plain CI runner has no
|
|
140
|
+
qaping MCP connection and no login, so a committed workflow could not run
|
|
141
|
+
this loop today; a CI-triggered recipe is a documented follow-up. Until it
|
|
142
|
+
ships, per-patch QA is an ask-your-agent step.
|
|
143
|
+
7. Show the dev the plan and commit only on their approval.
|
|
144
|
+
|
|
145
|
+
## Make the build testable
|
|
146
|
+
|
|
147
|
+
Before publishing a test build, you SHOULD add test affordances to the dev's
|
|
148
|
+
own code — ordinary dev work that turns self-reported state into forced,
|
|
149
|
+
verifiable state:
|
|
150
|
+
|
|
151
|
+
- URL params or flags that force states: `?fresh=1` (a true first visit),
|
|
152
|
+
`?difficulty=medium`, `?state=<checkpoint>` (start at a checkpoint).
|
|
153
|
+
- Bundled save fixtures plus a load control (the previous release's save,
|
|
154
|
+
shipped in the build).
|
|
155
|
+
- Seeded randomness, so a reported oddity reproduces.
|
|
156
|
+
- A corner build stamp (build hash + first-run/save-state flag; a `?qa=1`
|
|
157
|
+
overlay on web) with a step asking the tester to read it back — a read-back
|
|
158
|
+
fact is verified; self-report is not.
|
|
159
|
+
|
|
160
|
+
Record each check's hook in its `setup` field; at filing time the hook
|
|
161
|
+
rides the ROUND's `url` — the game loads it itself, so the tester never
|
|
162
|
+
sees or types it — and the filed step states only the player-visible
|
|
163
|
+
expectation. The unreviewed-developer-build disclosure
|
|
164
|
+
already covers shipping these; the no-steering rule forbids steering SAFETY
|
|
165
|
+
decisions only. A store-delivered build that cannot surface state keeps its
|
|
166
|
+
checks testimony-only — mark that in the plan and report at that grade.
|
|
70
167
|
|
|
71
168
|
## RUN — per patch
|
|
72
169
|
|
|
73
170
|
1. **Select.** Read the diff (or PR). Pick the checks it can plausibly affect,
|
|
74
|
-
plus the always-run criticals (the critical-path block above)
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
171
|
+
plus the always-run criticals (the critical-path block above) and every
|
|
172
|
+
check the ledger left NOT-ASKED or Blocked last round. Tell the user which
|
|
173
|
+
checks run and why.
|
|
174
|
+
2. **Compose sessions.** Partition the selected checks by required state —
|
|
175
|
+
fresh-install / progression / settings-mutating — and put state-mutating
|
|
176
|
+
checks LAST within their session. A session has ONE state: its round
|
|
177
|
+
`url` (carrying the forcing setup param) is the only state control that
|
|
178
|
+
exists — testers cannot clear storage or start fresh themselves — so
|
|
179
|
+
checks needing different forced states never share a session. Budget play content: sum the checks'
|
|
180
|
+
directed play time; if it exceeds est_minutes, split into parallel
|
|
181
|
+
5-minute rounds (credit-neutral: two focused 5-minute rounds cost one
|
|
182
|
+
diluted 10-minute round) or raise est_minutes within the cap, and state
|
|
183
|
+
the trade beside the cost estimate. Depth beyond the budget rotates across
|
|
184
|
+
patches; note in the plan which slice ran last.
|
|
185
|
+
3. **Build** via the recorded build command, with each selected check's
|
|
186
|
+
`setup` affordance in place; zip the result (native) or keep the built
|
|
187
|
+
static directory (web).
|
|
188
|
+
4. **Ship**, by delivery: NATIVE uploads — `qaping publish-build <game.zip>
|
|
79
189
|
--platform windows|macos` → prints a `/b/<slug>` URL (temporary hosting;
|
|
80
190
|
filing a playtest extends the build through the round; each publish mints a
|
|
81
191
|
NEW URL). Store-delivered games file with their Steam store page, TestFlight
|
|
82
192
|
public link, or App Store page as `url`. WEB games — host the built output
|
|
83
193
|
at a public URL: the dev's own hosting if they have it, else
|
|
84
194
|
`qaping publish <built-dir>` prints a hosted URL. Localhost never works —
|
|
85
|
-
players open the link on their own devices.
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
-
|
|
114
|
-
the
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
195
|
+
players open the link on their own devices. When filing a WEB game: send
|
|
196
|
+
`url` + `est_minutes` and OMIT `platform` entirely — a web round is claimed
|
|
197
|
+
by players in their own browser, phones included.
|
|
198
|
+
5. **File** ONE `qaping_playtest` per composed session, always with
|
|
199
|
+
`questionnaire:'none'` — on a QA-PLAN round your checks are the questions;
|
|
200
|
+
the standard instrument is a separate experience round (below), never a
|
|
201
|
+
default. A WEB session additionally files `require_evidence:'none'`
|
|
202
|
+
(MUST) — left at its default, the finish silently demands a pinned
|
|
203
|
+
screenshot-comment no step asked for. est_minutes is an
|
|
204
|
+
honest sum of the steps' directed play — the service
|
|
205
|
+
accepts 5–30 minutes (10 is the standard session; outside that
|
|
206
|
+
range the filing is refused, not clamped), and the clock is play time:
|
|
207
|
+
download/install happens before it starts. The authored steps MUST be:
|
|
208
|
+
- **one step per selected check**, its text opening with the id token —
|
|
209
|
+
`[spin-readable] …` — the plan prose rewritten as an observation
|
|
210
|
+
question ("describe what the start menu showed and what you tapped
|
|
211
|
+
first", never "confirm the menu appears"), with options exactly
|
|
212
|
+
["Pass — as described", "Fail — describe exactly what you saw",
|
|
213
|
+
"Couldn't test — say why", "Didn't reach this part"] — the pick comes
|
|
214
|
+
back verified in steps_result. EXCEPTION: when the check asks the
|
|
215
|
+
tester to DISCRIMINATE between named states ("which difficulty did the
|
|
216
|
+
bot play like?", "which screen did you land on?"), the options are the
|
|
217
|
+
NAMED candidates themselves ("rookie-level" / "harder than that") plus
|
|
218
|
+
"Couldn't tell — say why" — generic Pass/Fail on a discrimination
|
|
219
|
+
yields contradictions instead of a signal (measured live);
|
|
220
|
+
- option labels ≤40 chars, 2–4 options per step; never author a final
|
|
221
|
+
step whose options duplicate the verdict options — the phone hides it
|
|
222
|
+
as a duplicate of the verdict control;
|
|
223
|
+
- a check with a `setup` hook is forced by filing the session's `url`
|
|
224
|
+
WITH the hook — the game loads it itself; the step states only the
|
|
225
|
+
player-visible expectation. One whose precondition cannot be forced
|
|
226
|
+
opens with a verifying option step ("Before this session: had you ever
|
|
227
|
+
played this game before?" — yes / no / not sure);
|
|
228
|
+
- a recheck of a prior finding is a clearly-labeled step at its natural
|
|
229
|
+
play-order point (first when its required state demands it, last
|
|
230
|
+
otherwise) — "VERIFY FIX: last round saw X — does it still happen?" —
|
|
231
|
+
and prior findings stay OUT of `instructions` entirely, so the rest of
|
|
232
|
+
the pass is an independent look;
|
|
233
|
+
- a closing **break-it slice** (~2–3 minutes) authored from the diff:
|
|
234
|
+
"this patch changed X — try to break it: <concrete abuse ideas from the
|
|
235
|
+
diff>. Report the weirdest thing you saw AND list what you tried."
|
|
236
|
+
Abuse ideas target PLAYER-VISIBLE behaviors the diff changed — things a
|
|
237
|
+
player can tap, see, or provoke — never internal mechanisms (a flag, a
|
|
238
|
+
cache, a storage key means nothing to a player).
|
|
239
|
+
Finding nothing is a fine answer when we know what was attempted; frame
|
|
240
|
+
it in `instructions` as "these checks are the territory — break anything
|
|
241
|
+
near them";
|
|
242
|
+
- hardware verification where it matters: needs beyond keyboard+mouse go
|
|
243
|
+
up front in `instructions` AND an explicit step asks which input device
|
|
244
|
+
was actually used, with options.
|
|
245
|
+
**Filing gate (MUST):** print `this round covers: <ids>` per session and
|
|
246
|
+
assert the ids — unioned across every session this run files — equal the
|
|
247
|
+
Select step's list BEFORE calling qaping_playtest. The wire keeps only
|
|
248
|
+
the FIRST 20 steps_result rows and a play directive is prepended, so with
|
|
249
|
+
`questionnaire:'none'` budget at most 19 authored steps per session (8 if
|
|
250
|
+
the standard questionnaire rides) — rows past 20 are answered by the
|
|
251
|
+
tester and silently dropped at finish, which is why this gate exists. A
|
|
252
|
+
mismatch (the budget forcing cuts) fails loudly PRE-SPEND: batch another
|
|
253
|
+
session or state the deferral out loud — never silently truncate.
|
|
254
|
+
6. **Wait, in the background:** spawn a background task looping `qaping_wait`
|
|
255
|
+
and keep working (see ground rules) — foreground polling is the fallback,
|
|
256
|
+
and a promise to check later is never a wait.
|
|
257
|
+
7. **Report** from `qaping_results`:
|
|
258
|
+
- **Evidence line first (MUST).** Every report opens with its grade:
|
|
259
|
+
`Evidence: recording + transcript` (macOS/iOS) / `Evidence: recording
|
|
260
|
+
only` (Windows) / `Evidence: testimony only (web round)`.
|
|
261
|
+
- **Computed headline (MUST).** Derive the one verdict sentence from the
|
|
262
|
+
per-check outcomes, never from the tester's own verdict: `PASS` only
|
|
263
|
+
when every selected check returned Pass; `PASS — N unverified (<ids>)`
|
|
264
|
+
when nothing failed but any check is Blocked or NOT-ASKED; `FAIL: <id>`
|
|
265
|
+
on any Fail. Quote the tester's global verdict beneath it as one input —
|
|
266
|
+
it is never the headline. (The iterate rule already says unresolved
|
|
267
|
+
findings mean fix and refile — unresolved never headlines as PASS.)
|
|
268
|
+
- **One row per SELECTED check (MUST)**, keyed by id, outcome from
|
|
269
|
+
{Pass, Fail, Blocked, NOT-ASKED}. "Couldn't test" and "Didn't reach this
|
|
270
|
+
part" are Blocked, with the why. A check that reached the wire as more
|
|
271
|
+
than one row (a precondition step, a VERIFY FIX pair) takes its outcome
|
|
272
|
+
from the WORST row: any Fail → Fail; else any Blocked or unanswered row
|
|
273
|
+
leaves the check unverified and the headline counts it ("PASS — N
|
|
274
|
+
unverified") — never plain Pass over a Blocked sub-step. A check whose precondition came back
|
|
275
|
+
unverified or disproven is VOID — report it as Blocked ("VOID:
|
|
276
|
+
precondition unverified"), never as a pass or fail, and leave
|
|
277
|
+
`last_verified` untouched. A selected check that never made the wire or
|
|
278
|
+
never came back is NOT-ASKED and carries forward into the next filing
|
|
279
|
+
automatically. Cite [mm:ss] where a transcript or recording exists; on a
|
|
280
|
+
web round cite the step answers — there are no timestamps to cite.
|
|
281
|
+
- PR comment via `gh pr comment`: evidence line, the computed verdict
|
|
282
|
+
sentence first, then the per-check rows, then credits spent.
|
|
283
|
+
- **Ledger append (MUST).** Append ONE JSON line to `qa-rounds.jsonl` at
|
|
284
|
+
the game repo root, committed alongside the plan updates:
|
|
285
|
+
`{"round_id": …, "report_url": …, "build": "<sha or hosted url>",
|
|
286
|
+
"evidence_grade": …, "checks": [{"id": …, "outcome": …}], "verdict": …,
|
|
287
|
+
"credits": …, "follow_ups": […]}`. `report_url` is the round's service
|
|
288
|
+
report page (the `report_url` the results tool returns — never a PR or
|
|
289
|
+
issue link). No evidence-link field, deliberately: signed recording URLs
|
|
290
|
+
expire in days, so durable evidence pointers live in the report/issues,
|
|
291
|
+
not the ledger. The ledger is machine truth;
|
|
292
|
+
`last_verified` stays in QA-PLAN.md as the dev-readable contract derived
|
|
293
|
+
from it, and "carry findings forward" is a read of the last line's
|
|
294
|
+
follow_ups, not prose archaeology.
|
|
295
|
+
- Update `QA-PLAN.md` from the outcomes: Pass → `state: passed` and
|
|
296
|
+
`last_verified` this build/date; a Fail with hard evidence → `failing`;
|
|
297
|
+
a Fail that could be dirty state or tester noise → `ambiguous`;
|
|
298
|
+
Blocked/NOT-ASKED touch nothing. A check changed because of this round
|
|
299
|
+
records the round id as `origin`.
|
|
300
|
+
- **QA memory (standing rule, not just here)**: whenever the developer
|
|
301
|
+
mentions — in any conversation — something that should always be tested,
|
|
302
|
+
a fragile area, or a QA lesson learned, offer to record it as a check
|
|
303
|
+
(or a note on an existing check) in `QA-PLAN.md` right then, so the plan
|
|
304
|
+
accretes knowledge instead of losing it to chat scrollback.
|
|
305
|
+
- Issues: one `gh issue create` per finding — drafts for the dev's own
|
|
306
|
+
triage, never near-duplicates, never one issue per symptom of the same
|
|
307
|
+
bug. Before reproduction the body carries "observed once, unreproduced"
|
|
308
|
+
plus a proposed severity from consequence (blocks progress / degrades /
|
|
309
|
+
cosmetic). The label CONFIRMED is reserved for a targeted round or
|
|
310
|
+
unambiguous in-round evidence; a CONFIRMED issue carries repro steps
|
|
311
|
+
rebuilt from the tester's answers and a timestamp link into the
|
|
312
|
+
recording where one exists (web rounds have none — cite answers).
|
|
313
|
+
|
|
314
|
+
## The ambiguity ladder
|
|
315
|
+
|
|
316
|
+
An `ambiguous` or first-time-failing check never re-runs inside the next
|
|
317
|
+
batch — it refiles ALONE as a targeted ~5-minute round: one check, its
|
|
318
|
+
precondition forced via `setup` (or verified by the opening option step),
|
|
319
|
+
plus the disambiguation steps. That round settles it: Pass → `passed` (note
|
|
320
|
+
the fluke); Fail → `failing`, and the issue upgrades to CONFIRMED. Never
|
|
321
|
+
resolve an ambiguity by re-buying the whole batch, and never label a
|
|
322
|
+
once-seen fail CONFIRMED without that round or hard evidence.
|
|
323
|
+
|
|
324
|
+
## Paired rounds (two-state comparisons)
|
|
325
|
+
|
|
326
|
+
A feel/physics/balance regression judged absolute buys little — a stranger
|
|
327
|
+
answers "did spin feel right" in a vacuum. But a comparison never rides one
|
|
328
|
+
round: never two links in one round — links in step text are inert on the
|
|
329
|
+
phone and `instructions` go unread in-review, so a second URL simply never
|
|
330
|
+
gets opened. When a check needs TWO states compared (with/without a hook,
|
|
331
|
+
old build vs new), file TWO paired rounds with the SAME steps, one per
|
|
332
|
+
state, each state forced by its own round `url` (the ledger's `build` field
|
|
333
|
+
keeps the old build recoverable), and compare the answers yourself in the
|
|
334
|
+
report. Caveat it: different testers played each side, and a tester told to
|
|
335
|
+
hunt changes reports noise as change. Native builds pair the same way, one
|
|
336
|
+
build per round — re-publish the previous zip if its /b/<slug> expired.
|
|
337
|
+
|
|
338
|
+
## The experience round (opt-in, never a default)
|
|
339
|
+
|
|
340
|
+
The standard 11-item player-experience questionnaire never rides a QA-PLAN
|
|
341
|
+
round. Offer it separately, at milestones, as an experience round: no checks
|
|
342
|
+
attached, questionnaire left on, n_target up to 3 players, priced
|
|
343
|
+
transparently (est_minutes × 2 × players — state it first). Single-tester
|
|
344
|
+
scores are trend data — meaningful only after 3+ rounds on the same game,
|
|
345
|
+
never compared across testers or builds at n=1. If 8 or more of the 11 scale
|
|
346
|
+
answers are identical, report "low-differentiation response — treat scores as
|
|
347
|
+
unreliable" and never average them into a comparison.
|
|
120
348
|
|
|
121
349
|
## PROMOTE — make the suite cheaper
|
|
122
350
|
|
|
123
351
|
When a check has passed and is mechanically assertable — a save file loads, a
|
|
124
352
|
config parses, an asset path resolves — offer to write a normal test in the
|
|
125
|
-
repo's own test suite
|
|
353
|
+
repo's own test suite; the ledger's per-check pass history is the evidence
|
|
354
|
+
that justifies the offer. On the dev's yes: write the test, flip the check's
|
|
126
355
|
`rung` to `code`, keep the prose (it documents intent) and add a `test:` line
|
|
127
356
|
naming the test file. `rung: code` checks then run in the dev's own CI for
|
|
128
|
-
free — that is the point: every check starts human
|
|
129
|
-
Checks about feel, difficulty, or ambiguous visuals stay
|
|
130
|
-
permanently. Never delete or demote a human check without the
|
|
357
|
+
free — that is the point: every check starts as human as it must and gets
|
|
358
|
+
cheaper over time. Checks about feel, difficulty, or ambiguous visuals stay
|
|
359
|
+
`rung: human` permanently. Never delete or demote a human check without the
|
|
360
|
+
dev agreeing.
|