@homie-rocks/studio 0.49.0 → 0.50.1
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 +43 -0
- package/bin/homie-studio.mjs +5 -5
- package/functions/FUNCTIONS.md +71 -1
- package/guides/app/SKILL.md +130 -1
- package/guides/art/SKILL.md +154 -0
- package/guides/art/references/RIGHTS.md +20 -0
- package/guides/art/scripts/art.mjs +308 -0
- package/guides/models/SKILL.md +198 -0
- package/guides/models/references/RIGHTS.md +46 -0
- package/guides/models/references/models.json +175 -0
- package/guides/models/scripts/models.mjs +483 -0
- package/guides/music/SKILL.md +197 -0
- package/guides/music/references/COMPOSITION.md +59 -0
- package/guides/music/references/GAME-AUDIO.md +50 -0
- package/guides/music/references/RIGHTS.md +43 -0
- package/guides/music/scripts/lib/audio.mjs +258 -0
- package/guides/music/scripts/lib/eleven.mjs +260 -0
- package/guides/music/scripts/lib/studio.mjs +147 -0
- package/guides/music/scripts/music.mjs +644 -0
- package/guides/office/SKILL.md +99 -0
- package/guides/plan/SKILL.md +126 -0
- package/guides/plan/references/CODEX.md +105 -0
- package/guides/plan/references/INTERVIEW.md +86 -0
- package/guides/playtest/SKILL.md +205 -0
- package/guides/playtest/references/METHOD.md +124 -0
- package/guides/playtest/references/REVIEWER.md +78 -0
- package/guides/playtest/scripts/lib/exit.mjs +31 -0
- package/guides/playtest/scripts/lib/judge.mjs +502 -0
- package/guides/playtest/scripts/lib/pixels.mjs +128 -0
- package/guides/playtest/scripts/lib/preflight.mjs +69 -0
- package/guides/playtest/scripts/playtest.mjs +870 -0
- package/guides/servers/SKILL.md +111 -0
- package/guides/shop/SKILL.md +4 -0
- package/guides/sound/SKILL.md +156 -0
- package/guides/sound/references/GAME-SOUND.md +100 -0
- package/guides/sound/references/SCORE.md +131 -0
- package/guides/sound/references/TRAPS.md +93 -0
- package/guides/sound/scripts/lib/measure.mjs +252 -0
- package/guides/sound/scripts/lib/score.mjs +415 -0
- package/guides/sound/scripts/lib/sfx.mjs +222 -0
- package/guides/sound/scripts/lib/synth.mjs +509 -0
- package/guides/sound/scripts/sound.mjs +520 -0
- package/guides/style/SKILL.md +105 -0
- package/lib/app-needs.mjs +63 -0
- package/lib/check.mjs +10 -4
- package/lib/codex.mjs +95 -18
- package/lib/decisions.mjs +9 -7
- package/lib/functions-build.mjs +10 -0
- package/lib/handoff.mjs +1 -1
- package/lib/mcp-tools.mjs +10 -9
- package/lib/mcp.mjs +1 -1
- package/lib/scaffold.mjs +15 -7
- package/lib/studio.mjs +3 -3
- package/lib/template-history.json +84 -0
- package/lib/worker-config.mjs +1 -1
- package/package.json +1 -1
- package/shop/SHOP.md +4 -0
- package/shop/shop.ts +4 -4
- package/tools/TOOLS.md +21 -0
- package/tools/tools.ts +1 -1
- package/worker/build-id.mjs +1 -1
- package/worker/event-resources.mjs +116 -0
- package/worker/functions-schema.mjs +15 -0
- package/worker/functions.mjs +36 -22
- package/worker/index.mjs +23 -3
- package/worker/mcp-tools.mjs +14 -3
- package/worker/mcp.mjs +45 -8
- package/worker/shop-page.mjs +1 -1
- package/worker/shop.mjs +6 -2
- package/worker/version.mjs +1 -1
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# The method behind the rows
|
|
2
|
+
|
|
3
|
+
How a game gets judged honestly, and the ways a test lies. Everything here was measured on games that
|
|
4
|
+
passed their tests and still failed people.
|
|
5
|
+
|
|
6
|
+
## Play it like a person
|
|
7
|
+
|
|
8
|
+
Hold a direction for most of a second, not one frame. Press the thing whose label sounds fun. Try the
|
|
9
|
+
control you would try first if nobody had told you anything. A script that presses every button in
|
|
10
|
+
order is a conformance test, and conformance is not what is wrong with a boring game.
|
|
11
|
+
|
|
12
|
+
## The five numbers a round owes you
|
|
13
|
+
|
|
14
|
+
| | say | why |
|
|
15
|
+
| --- | --- | --- |
|
|
16
|
+
| how long | seconds from start to finish, against the clock the game set | a 150-second round that ends at 40 means nobody has seen most of the game |
|
|
17
|
+
| how it ended | who won and by how much | if you cannot tell, a stranger cannot either |
|
|
18
|
+
| the tense moment | one sentence about the best five seconds, or "there wasn't one" | the answer that unsticks a stalled game |
|
|
19
|
+
| what did nothing | any control that produced no visible change | a dead control is the first thing people notice |
|
|
20
|
+
| what you wanted | the one thing you reached for and could not do | the next change |
|
|
21
|
+
|
|
22
|
+
Add **lead changes** (the number that separates a race from a procession) and **whether doing nothing
|
|
23
|
+
scores as well as playing** (the `play` row measures both humans).
|
|
24
|
+
|
|
25
|
+
## Different strategies in different seats
|
|
26
|
+
|
|
27
|
+
Seats that all play the same way can only prove the game runs. Give simulated seats different
|
|
28
|
+
standing strategies, one per side of the decision the game claims to be about: the greedy one that
|
|
29
|
+
takes the risk the design says should pay, the safe one, the one that goes near other players on
|
|
30
|
+
purpose, the middle. Then read which one scored zero. On one game whose whole idea was "carrying more
|
|
31
|
+
makes you a target", the greedy runner finished on 0, 0 and 0 in three rounds because at a full carry
|
|
32
|
+
it was slower than the hunter and could never escape: the exciting strategy was arithmetic, and the
|
|
33
|
+
game everyone described was never actually played. No screenshot and no reading of the code found it;
|
|
34
|
+
three rounds with four strategies did, in two minutes.
|
|
35
|
+
|
|
36
|
+
## The do-nothing test
|
|
37
|
+
|
|
38
|
+
Write it first: a player who stops touching anything, at every place a player plausibly stops, for
|
|
39
|
+
longer than anyone's patience. The game must come to get them (a nudge, a timer, the round moving on),
|
|
40
|
+
never freeze, never go black, never soft-lock the room. Continuous worlds must also be tuned in real
|
|
41
|
+
seconds: a decay tuned on a fast test clock empties a real room in a minute. Simulate a long idle
|
|
42
|
+
stretch and read the state before and after.
|
|
43
|
+
|
|
44
|
+
## Every scenario before "finished"
|
|
45
|
+
|
|
46
|
+
1. One person alone, with bots, on a computer.
|
|
47
|
+
2. One person on a phone, portrait and landscape (a game that needs landscape says so and survives the
|
|
48
|
+
turn).
|
|
49
|
+
3. Two strangers who pressed Play at different times, including one who arrives mid-round and takes a
|
|
50
|
+
bot's place (and its score: a joiner inherits the body, so only a round that starts after both are in
|
|
51
|
+
says how they played).
|
|
52
|
+
4. The host leaving mid-round: someone else continues the same round, clock where it was.
|
|
53
|
+
5. The big screen (`/<id>/tv`): a spectator with no body, a join QR, the game moving.
|
|
54
|
+
6. Nobody touching anything.
|
|
55
|
+
|
|
56
|
+
Say which you tried. "We could not test it" and "we tested it and it was fine" must never sound alike.
|
|
57
|
+
|
|
58
|
+
## The ten-second test
|
|
59
|
+
|
|
60
|
+
Ten seconds of silent video, no context, on a phone someone is scrolling past: do they stop? Every
|
|
61
|
+
change should leave one moment worth clipping (a knockout that reads from across a room, a comeback you
|
|
62
|
+
can see coming, something that happens once a round and is unmistakable). A change that produced
|
|
63
|
+
nothing you would cut fifteen seconds around produced polish; polish compounds slowly and nobody shares
|
|
64
|
+
it. Take a swing (a hazard, a rule that changes the last ten seconds) rather than tuning a number.
|
|
65
|
+
Readable at a distance: whose is that, what just happened, who is winning. Punctuate: silence, then a
|
|
66
|
+
hit. A thing that happens constantly is wallpaper; once a round it is an event.
|
|
67
|
+
|
|
68
|
+
## Judging, not building
|
|
69
|
+
|
|
70
|
+
- **The builder never grades its own work.** A reviewer gets the goal, the bar and the running game,
|
|
71
|
+
never the builder's reasons.
|
|
72
|
+
- **Measure, don't assert.** "I differenced the two frames: nothing moved more than 8 of 255" ends an
|
|
73
|
+
argument that prose cannot.
|
|
74
|
+
- **Two instruments on the same build.** A reviewer given only pictures grades the picture: one scored
|
|
75
|
+
a game 34/100 for a hunted player drawn at 1.14:1 contrast against the searchlight meant to mark
|
|
76
|
+
him, while a play measurement of the same build found the round always ended at 40 s of 150 and the
|
|
77
|
+
risky strategy scored zero. Neither could see what the other saw. When they disagree, the fight
|
|
78
|
+
outranks the frame.
|
|
79
|
+
- **The whole frame and a real round, regularly.** A reviewer shown a crop can only ask about the crop:
|
|
80
|
+
thirty-five changes in a row once went into one decorative prop, each winning its own comparison,
|
|
81
|
+
while the game as a whole did not improve.
|
|
82
|
+
- **Judge the set.** Twenty screens that each passed review were one layout repeated fourteen times.
|
|
83
|
+
Put the whole set in front of one reviewer: squint, how many distinct shapes are there?
|
|
84
|
+
- **A reference is a bar, not a template.** Ask "is it as good as that", never "is it shaped like that".
|
|
85
|
+
- **Group findings by cause** before assigning work; five findings, one cause, one fix in the shared layer.
|
|
86
|
+
- **A finding that names a relation has two sides**: "the brightest thing is not a player" can be fixed
|
|
87
|
+
by darkening the floor or brightening the player; move one side on purpose and check the other.
|
|
88
|
+
- **Stop on a regression.** A change that is not clearly better than the one before is worse. Scores
|
|
89
|
+
like 48, 54, 58, 50, 42 mean the peak was 58: go back to it.
|
|
90
|
+
|
|
91
|
+
## Measured bars
|
|
92
|
+
|
|
93
|
+
| what | bar |
|
|
94
|
+
| --- | --- |
|
|
95
|
+
| seated and moving | under 10 s from pressing Play, first press moves you within about a second |
|
|
96
|
+
| hold a direction 5 s | one straight line the pressed way on screen, camera yaw change under 10° |
|
|
97
|
+
| alternate directions 10 s | every press goes the pressed way within 600 ms |
|
|
98
|
+
| UI during play | at most about 12% of the screen, nothing opaque in the middle third |
|
|
99
|
+
| look | no black or one-colour frame; detail at several scales (edge share of a finished-looking frame is several times a placeholder's) |
|
|
100
|
+
| sound | starts on the first touch; every action answers; -16 to -12 LUFS while played; nothing clips; little lost on a phone |
|
|
101
|
+
| rounds | two strangers meet in one room and both see a round finish; a killed host hands over in 5 s; a late joiner takes a bot's place |
|
|
102
|
+
|
|
103
|
+
## How instruments lie
|
|
104
|
+
|
|
105
|
+
- **A software renderer fakes a stuck game.** Headless Chrome without the GPU draws a WebGL game at 1-2
|
|
106
|
+
fps; count animation frames first and say BLOCKED under ~20 fps. With the GPU flags a headless shot
|
|
107
|
+
matches the real screen within a fraction of a luma point.
|
|
108
|
+
- **Reading a WebGL canvas from a script returns black** unless it is read inside a late animation frame;
|
|
109
|
+
use a screenshot of the page (the compositor's picture) instead.
|
|
110
|
+
- **A full-page screenshot stretches the viewport**, so phone-landscape media queries stop matching; take
|
|
111
|
+
the viewport only.
|
|
112
|
+
- **Emulated phones pass while real phones fail.** Real touch on iPhone Safari differs (coalesced
|
|
113
|
+
moves, passive listeners, pointer ids, 120 Hz); the iOS Simulator is the closest thing to an iPhone
|
|
114
|
+
on a Mac (`xcrun simctl openurl booted <url>`, mouse drags become real touches). Say when a result
|
|
115
|
+
is emulation only.
|
|
116
|
+
- **A delivered press is not a correct press.** Only the body's movement on screen proves direction.
|
|
117
|
+
- **Gate on the thing itself.** "A sound file plays" and "a texture is bound", not a number a hiss and a
|
|
118
|
+
flat cube would pass. Ask what the cheapest thing that passes the check would be.
|
|
119
|
+
- **A still is not the game.** A title card or the moment before a round starts is a picture of
|
|
120
|
+
nothing happening; shoot while it is being played.
|
|
121
|
+
- **Another run can replace what you are judging.** Note the build you judged (a digest, a commit) before
|
|
122
|
+
and after.
|
|
123
|
+
- A reviewer that shares your working folder can "tidy" your half-written file: commit before you hand
|
|
124
|
+
work to one.
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# Blind review: {{GAME}}
|
|
2
|
+
|
|
3
|
+
You are reviewing a multiplayer web game you did not make. You have not seen its code, its plan or its
|
|
4
|
+
maker's notes, and you must not read them: judge only what a player gets. You are demanding and fair,
|
|
5
|
+
you have played the best browser party games, and your job is to find what would make a stranger
|
|
6
|
+
leave, not to encourage the maker.
|
|
7
|
+
|
|
8
|
+
The game: {{URL}} (strangers who press Play land in the same public room; bots fill empty seats; rounds
|
|
9
|
+
end and restart by themselves). The playtest instruments already ran; their folder is `{{FOLDER}}`.
|
|
10
|
+
|
|
11
|
+
## Look at these yourself, every one, before you read any number
|
|
12
|
+
|
|
13
|
+
{{PICTURES}}
|
|
14
|
+
|
|
15
|
+
## What the instruments measured
|
|
16
|
+
|
|
17
|
+
{{NUMBERS}}
|
|
18
|
+
|
|
19
|
+
`report.json` in the folder has every number. A BLOCKED row measured nothing; never read it as a pass.
|
|
20
|
+
|
|
21
|
+
## Play it
|
|
22
|
+
|
|
23
|
+
Play at least one full round yourself in a headless Chrome on the GPU
|
|
24
|
+
(`--use-angle=metal --enable-gpu-rasterization --ignore-gpu-blocklist` on a Mac): once as a computer
|
|
25
|
+
with the keyboard, and once as a phone (390x844, touch events: a thumb that lands, slides and holds).
|
|
26
|
+
Take your own screenshots at moments you choose. Close every browser you open, at most two at a time.
|
|
27
|
+
If you cannot play it, say so and score only what you saw.
|
|
28
|
+
|
|
29
|
+
## Score it
|
|
30
|
+
|
|
31
|
+
Each part 0-10, then an overall 0-100 that is your honest judgement, not the average:
|
|
32
|
+
|
|
33
|
+
1. **First ten seconds**: in, seated and moving, knowing what to do, with no instructions read.
|
|
34
|
+
2. **Controls**: every press means the same thing every second; the camera never turns on its own; a
|
|
35
|
+
phone thumb is as good as a keyboard.
|
|
36
|
+
3. **The round**: a goal you understand, tension that rises, a finish you can see coming, results
|
|
37
|
+
that say who won and why. Does playing well beat doing nothing?
|
|
38
|
+
4. **Look**: does it look finished at phone size, readable at arm's length, with a clear subject, detail
|
|
39
|
+
at more than one scale, and nothing flat or placeholder-like?
|
|
40
|
+
5. **Sound**: does every action answer, does the music carry the round and change with it, is it
|
|
41
|
+
balanced and clean on a phone speaker?
|
|
42
|
+
6. **Phone**: one-thumb play, UI out of the way (the middle of the screen clear), portrait and landscape.
|
|
43
|
+
7. **Strangers**: with bots and other people, is it fun together, and does a late joiner fit in?
|
|
44
|
+
8. **Clip test**: ten seconds of silent video on a phone someone is scrolling past. Do they stop?
|
|
45
|
+
|
|
46
|
+
Then answer plainly: would you play another round? Would you send it to a friend? Would you pick it
|
|
47
|
+
over the best browser party game you know? "No, and here is why" is a useful answer.
|
|
48
|
+
|
|
49
|
+
## Rules for your findings
|
|
50
|
+
|
|
51
|
+
- **Measure, don't assert.** "The score text is 11 px on a 390 px screen" beats "the HUD is small".
|
|
52
|
+
Every gap names its evidence: a picture and what is in it, or a number from the report or your own play.
|
|
53
|
+
- **The fight outranks the frame.** If the pictures look fine and the round is boring, say the round is
|
|
54
|
+
boring; if doing nothing scores as well as playing, that is the biggest finding there is.
|
|
55
|
+
- **Group by cause.** Five findings with one cause (no lighting, one missing sound bus) are one gap.
|
|
56
|
+
- **One concrete fix per gap**, the smallest change that would move it, and which side of a relation
|
|
57
|
+
it moves ("make the player brighter than the floor" can be done from either side: say which).
|
|
58
|
+
- **Impact 1-10**: how much fixing it would raise your overall score.
|
|
59
|
+
- Say what you verified yourself and what you could not test.
|
|
60
|
+
|
|
61
|
+
## Return exactly this JSON (save it as `{{FOLDER}}/VERDICT.json`)
|
|
62
|
+
|
|
63
|
+
```json
|
|
64
|
+
{
|
|
65
|
+
"score": 0,
|
|
66
|
+
"parts": { "first10": 0, "controls": 0, "round": 0, "look": 0, "sound": 0, "phone": 0, "strangers": 0, "clip": 0 },
|
|
67
|
+
"wouldPlayAgain": false,
|
|
68
|
+
"wouldSendToAFriend": false,
|
|
69
|
+
"summary": "two or three sentences a person could act on",
|
|
70
|
+
"verified": ["what you checked yourself, with how"],
|
|
71
|
+
"couldNotTest": ["what you could not test, and why"],
|
|
72
|
+
"gaps": [
|
|
73
|
+
{ "title": "", "evidence": "", "fix": "", "impact": 0 }
|
|
74
|
+
]
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Gaps sorted by impact, the largest first. No praise padding; no gap without evidence.
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ending a controller: after its report is written and its browsers are closed, the process leaves, with the exit
|
|
3
|
+
* code its result earned.
|
|
4
|
+
*
|
|
5
|
+
* Why this exists: a full run wrote its report, its sheets and the reviewer command, every Chrome and ffmpeg it
|
|
6
|
+
* owned had closed, and the Node process stayed. Node only leaves by itself when nothing is left on its event loop,
|
|
7
|
+
* and a controller that drives browsers collects things that sit there: a socket puppeteer kept, a pipe a child's
|
|
8
|
+
* helper inherited, a race's losing timer, an AbortSignal's timer. Which one it was that time was never isolated,
|
|
9
|
+
* so the end does not depend on finding it: run the cleanup (bounded), flush what was printed, then exit.
|
|
10
|
+
*
|
|
11
|
+
* The exit code is the one already decided (`process.exitCode`, or `code`): a failed run still exits non-zero.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/** Race `p` against `ms`; the losing timer is cleared, so the race itself leaves nothing on the event loop. */
|
|
15
|
+
export function within(p, ms, fallback = null) {
|
|
16
|
+
let timer = null;
|
|
17
|
+
const late = new Promise((r) => { timer = setTimeout(() => r(fallback), ms); });
|
|
18
|
+
return Promise.race([Promise.resolve(p).catch(() => fallback), late]).finally(() => clearTimeout(timer));
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Run `cleanup` (at most `graceMs`), wait for stdout and stderr to drain (at most 2 s), then exit with `code`
|
|
23
|
+
* (default: process.exitCode, else 0). `exit` is process.exit (a test passes its own).
|
|
24
|
+
*/
|
|
25
|
+
export async function finish({ code = null, cleanup = null, graceMs = 15_000, exit = (c) => process.exit(c) } = {}) {
|
|
26
|
+
if (cleanup) await within(Promise.resolve().then(cleanup), graceMs);
|
|
27
|
+
// A pipe (a caller reading our JSON) takes writes asynchronously: an exit before the drain would cut the report.
|
|
28
|
+
const drained = (s) => new Promise((r) => { if (!s?.writableLength) r(); else s.write('', () => r()); });
|
|
29
|
+
await within(Promise.all([drained(process.stdout), drained(process.stderr)]), 2000);
|
|
30
|
+
exit(code ?? process.exitCode ?? 0);
|
|
31
|
+
}
|