@homie-rocks/studio 0.14.0 → 0.14.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/README.md +6 -4
- package/lib/template-history.json +27 -0
- package/netplay/NETPLAY.md +1 -1
- package/package.json +1 -1
- package/worker/index.mjs +20 -1
- package/worker/office.mjs +16 -6
- package/worker/version.mjs +1 -1
package/README.md
CHANGED
|
@@ -100,8 +100,8 @@ site. A game made with Homie's arcade controls that knocks for a Homie box (`/__
|
|
|
100
100
|
A studio pins one version, so nothing changes until it asks. To take a newer one:
|
|
101
101
|
|
|
102
102
|
```sh
|
|
103
|
-
npx -y @homie-rocks/studio@0.14.
|
|
104
|
-
npx -y @homie-rocks/studio@0.14.
|
|
103
|
+
npx -y @homie-rocks/studio@0.14.1 upgrade # the plan; changes nothing
|
|
104
|
+
npx -y @homie-rocks/studio@0.14.1 upgrade --apply # after the person agrees
|
|
105
105
|
npm install && npm run build
|
|
106
106
|
```
|
|
107
107
|
|
|
@@ -204,7 +204,9 @@ npx homie-studio office key # a key for the Homie MC
|
|
|
204
204
|
|
|
205
205
|
Going private or invite-only never cuts a round short: each live room finishes its current round with a notice to
|
|
206
206
|
the players, then everyone the new state leaves out is sent out with a thank-you (the owner, and in a beta the
|
|
207
|
-
invited, play on).
|
|
207
|
+
invited, play on). A known edge: a room that opens in the same moment as the change can be missed by it, because the
|
|
208
|
+
office reaches the rooms its Lobby already knows. Each room therefore reads its game's launch state once, on its
|
|
209
|
+
first heartbeat after it opens, and re-gates the same way; nobody new can join a game that is not public meanwhile. Mute drops a player's `say…`, `chat…` and `emote…` events, so a game that sends its chat and
|
|
208
210
|
emotes under those kinds needs nothing more (`netplay/NETPLAY.md` section 15). Kicking a whole network address is in
|
|
209
211
|
the studio's API (`address: true`) and deliberately not in the office. A Preview (a branch's own unlisted address, with
|
|
210
212
|
no D1) enforces no launch state and has no office.
|
|
@@ -230,7 +232,7 @@ device; the owner's own passkey account (`homie-studio players owner`) counts as
|
|
|
230
232
|
## Setup status
|
|
231
233
|
|
|
232
234
|
```sh
|
|
233
|
-
npx -y @homie-rocks/studio@0.14.
|
|
235
|
+
npx -y @homie-rocks/studio@0.14.1 setup status --connector yes # before a studio exists
|
|
234
236
|
npx homie-studio setup status # in a studio (also: homie-studio doctor)
|
|
235
237
|
```
|
|
236
238
|
|
|
@@ -299,6 +299,33 @@
|
|
|
299
299
|
"## Beta": "9d02638207a26e73"
|
|
300
300
|
}
|
|
301
301
|
}
|
|
302
|
+
},
|
|
303
|
+
"0.14.1": {
|
|
304
|
+
"files": {
|
|
305
|
+
"CLAUDE.md": "ddf2b2a0376202f6",
|
|
306
|
+
"README.md": "684b3cca8001673c",
|
|
307
|
+
"HANDOFF.md": "ce7a8fd91c6bbee2",
|
|
308
|
+
"games/README.md": "5ad32fe80c61f117",
|
|
309
|
+
"music/README.md": "b8a828de9311bd28",
|
|
310
|
+
"videos/README.md": "70614388cbb203c7",
|
|
311
|
+
"posts/README.md": "37841aae476bd540",
|
|
312
|
+
"site/README.md": "ca131cd0472464fb",
|
|
313
|
+
"site/src/worker.mjs": "ca3f1630f2420ad3"
|
|
314
|
+
},
|
|
315
|
+
"sections": {
|
|
316
|
+
"AGENTS.md": {
|
|
317
|
+
"(intro)": "214aa46eb3a895d5",
|
|
318
|
+
"## Layout": "f14d641159480e6c",
|
|
319
|
+
"## Commands (all through the pinned CLI in node_modules)": "e579cd6ce3ce342a",
|
|
320
|
+
"## The site": "7c1618431bae6324",
|
|
321
|
+
"## Making games": "63069b31c87a2aa2",
|
|
322
|
+
"## The Game Codex and progress": "0738ef719cef3a4c",
|
|
323
|
+
"## Running live games (the back office)": "f0566f07cff04113",
|
|
324
|
+
"## Continuing a build from the Claude app": "0d68081e924ca419",
|
|
325
|
+
"## Rules": "969318425c99f5a5",
|
|
326
|
+
"## Beta": "9d02638207a26e73"
|
|
327
|
+
}
|
|
328
|
+
}
|
|
302
329
|
}
|
|
303
330
|
}
|
|
304
331
|
}
|
package/netplay/NETPLAY.md
CHANGED
|
@@ -660,7 +660,7 @@ tools). The room's relay enforces five controls:
|
|
|
660
660
|
| `announce` | `text` (one line, at most 280 characters; empty takes it down), `seconds` (5–3600, default 30) | Everyone and every watching shell gets `announce { id, text, at, until, from: 'studio' }`; a joiner gets it in `welcome.announce` until it ends. |
|
|
661
661
|
| `close` | `minutes`, or `seconds` (10 s up to a day), `message?`, or `reopen: true` | Every socket gets `error room-closed { until, message }` and is closed; the room forgets its play as an empty room does (section 4); watchers get `closed`. Until `until` every `hello` is refused with `room-closed`, and the Lobby sends nobody there. `reopen` opens it at once. |
|
|
662
662
|
| `seats` | `max` | The room's seats while it runs (the owner's "players per room"). Players already seated above it keep their seat; nobody new is seated there. Never above the room's own cap (the game's manifest). |
|
|
663
|
-
| `regate` | `allow` (holder kinds: `o` the owner, `i` an invite, `p` a player account), `notice?`, `message?`; no `allow` calls a waiting one off | A game's launch state narrowed (private, or an invite-only beta). The current round finishes first: the `notice` goes up as an announcement, and 5 s after the host's `round` with `phase: 'over'` (at the latest 20 s after its `endsAt`, or 30 s when no round is running; never later than 15 minutes) every socket whose ticket names no allowed holder gets `error room-closed { message }` (no `until`: they come back through the game's door if they have access) and is closed; its seat is freed and the others play on. A room with nobody to send out does nothing. |
|
|
663
|
+
| `regate` | `allow` (holder kinds: `o` the owner, `i` an invite, `p` a player account), `notice?`, `message?`; no `allow` calls a waiting one off | A game's launch state narrowed (private, or an invite-only beta). The current round finishes first: the `notice` goes up as an announcement, and 5 s after the host's `round` with `phase: 'over'` (at the latest 20 s after its `endsAt`, or 30 s when no round is running; never later than 15 minutes) every socket whose ticket names no allowed holder gets `error room-closed { message }` (no `until`: they come back through the game's door if they have access) and is closed; its seat is freed and the others play on. A room with nobody to send out does nothing. The studio's Worker sends it to every live room the game's Lobby knows; a room that opens in the same moment reads its game's launch state once on its first heartbeat and re-gates itself the same way. |
|
|
664
664
|
|
|
665
665
|
**Signed by the studio, checked by the room.** A control reaches the relay only from the
|
|
666
666
|
studio's own Worker, as
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@homie-rocks/studio",
|
|
3
|
-
"version": "0.14.
|
|
3
|
+
"version": "0.14.1",
|
|
4
4
|
"description": "A studio for games, music and video on its own Cloudflare: scaffold, multiplayer starters on the netplay contract, a port toolkit that makes existing single-player web games multiplayer, the site Worker with public rooms (Table/Lobby Durable Objects and D1, all on the free Workers plan, no payment method), a site with the hub's sections (Home, Games, Music, Videos, Rooms, Posts with feeds) in the studio's own look, an epic landing page for every game, optional R2 storage for large media, deploy, and the homie.rocks directory.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "Apache-2.0",
|
package/worker/index.mjs
CHANGED
|
@@ -66,7 +66,7 @@ import { count, countVisit, counter, isQa, onlyOf, ownerAllowed, playedByGame, p
|
|
|
66
66
|
import { ownerRoutes } from './stats-page.mjs';
|
|
67
67
|
import { playerRoutes, players as playerAccounts } from './players.mjs';
|
|
68
68
|
import {
|
|
69
|
-
accessOf, accountSub, gatePage, holders, isOwner, joinHolders, launchOf, officeRoutes, publicCatalogue, redeemInvite, remixOf, seatsFor, settingsOf, ticketAllows, ticketFor,
|
|
69
|
+
accessOf, accountSub, gatePage, holders, isOwner, joinHolders, launchOf, regateArgs, officeRoutes, publicCatalogue, redeemInvite, remixOf, seatsFor, settingsOf, ticketAllows, ticketFor,
|
|
70
70
|
ticketSub, usePlayers, verifyControl,
|
|
71
71
|
} from './office.mjs';
|
|
72
72
|
|
|
@@ -815,6 +815,9 @@ export class Table {
|
|
|
815
815
|
const room = this.room;
|
|
816
816
|
if (!room) return;
|
|
817
817
|
room.tick();
|
|
818
|
+
// A room that just opened reads its game's launch state once: a change made in the moment it opened (before the
|
|
819
|
+
// Lobby knew of it) still reaches it, after the current round as always.
|
|
820
|
+
if (room.openedAt && this.launchReadFor !== room.openedAt) { this.launchReadFor = room.openedAt; this.ctx.waitUntil(this.rereadLaunch().catch(() => {})); }
|
|
818
821
|
// A launch change the room applied by itself (after its round): its stored state says so too.
|
|
819
822
|
if (room.officeDirty) { room.officeDirty = false; this.ctx.storage.put('office', room.officeSaved()).catch(() => {}); }
|
|
820
823
|
n += 1;
|
|
@@ -824,6 +827,22 @@ export class Table {
|
|
|
824
827
|
}, 250);
|
|
825
828
|
}
|
|
826
829
|
|
|
830
|
+
/**
|
|
831
|
+
* The game's launch state, read once when the room opens (one D1 read): a private or invite-only game whose room
|
|
832
|
+
* holds anyone the state leaves out re-gates after the current round. Never in a Preview (no launch states there).
|
|
833
|
+
*/
|
|
834
|
+
async rereadLaunch() {
|
|
835
|
+
const room = this.room;
|
|
836
|
+
if (!room || !this.game || !this.env.DB || this.env.HOMIE_PREVIEW === '1' || room.regate) return null;
|
|
837
|
+
let launch = null;
|
|
838
|
+
try { launch = (await this.env.DB.prepare('SELECT launch FROM office_games WHERE game = ?1').bind(this.game).first())?.launch ?? null; } catch { return null; }
|
|
839
|
+
const args = regateArgs(launch);
|
|
840
|
+
if (!args.allow) return null;
|
|
841
|
+
const res = room.control('regate', args);
|
|
842
|
+
if (res.ok && res.leaving) await this.ctx.storage.put('office', room.officeSaved()).catch(() => {});
|
|
843
|
+
return res;
|
|
844
|
+
}
|
|
845
|
+
|
|
827
846
|
/** Tell the Lobby this room closed (until when; 0 when it opened again), so it sends nobody here meanwhile. */
|
|
828
847
|
tellLobby(extra) {
|
|
829
848
|
if (!this.game || !this.code) return;
|
package/worker/office.mjs
CHANGED
|
@@ -57,6 +57,21 @@ const TICKET_TTL_MS = 12 * 3600_000;
|
|
|
57
57
|
/** Who may stay in a live room after a launch change, by the kinds of holder a ticket names (o, i-…, p-…). */
|
|
58
58
|
const ALLOW = Object.freeze({ private: ['o'], invite: ['o', 'i'], public: null });
|
|
59
59
|
|
|
60
|
+
/**
|
|
61
|
+
* The room control for a launch state (NETPLAY.md section 15 `regate`): who may stay, the notice everyone sees while
|
|
62
|
+
* the round finishes, and the words for whoever then leaves. Public calls a waiting change off.
|
|
63
|
+
*/
|
|
64
|
+
export function regateArgs(launch, name = 'This game') {
|
|
65
|
+
if (!ALLOW[launch]) return {};
|
|
66
|
+
return {
|
|
67
|
+
allow: ALLOW[launch],
|
|
68
|
+
...{
|
|
69
|
+
private: { notice: `${name} goes private after this round. Thanks for playing!`, message: `${name} is private now. Thanks for playing!` },
|
|
70
|
+
invite: { notice: `${name} becomes an invite-only beta after this round. Invited players keep playing.`, message: `${name} is an invite-only beta now. Have an invite? Enter it on the game's page. Thanks for playing!` },
|
|
71
|
+
}[launch],
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
60
75
|
const json = (body, status = 200, extra = {}) => new Response(`${JSON.stringify(body)}\n`, {
|
|
61
76
|
status, headers: { 'content-type': 'application/json; charset=utf-8', 'cache-control': 'no-store, private', 'x-robots-tag': 'noindex', ...extra },
|
|
62
77
|
});
|
|
@@ -578,12 +593,7 @@ export async function perform(env, cat, a) {
|
|
|
578
593
|
// state leaves out is sent out of it (the owner, and in an invite-only beta the invited, play on). Back to a
|
|
579
594
|
// wider state, a change still waiting is called off.
|
|
580
595
|
if (launch !== prev) {
|
|
581
|
-
const
|
|
582
|
-
private: { notice: `${meta.name} goes private after this round. Thanks for playing!`, message: `${meta.name} is private now. Thanks for playing!` },
|
|
583
|
-
invite: { notice: `${meta.name} becomes an invite-only beta after this round. Invited players keep playing.`, message: `${meta.name} is an invite-only beta now. Have an invite? Enter it on the game's page. Thanks for playing!` },
|
|
584
|
-
public: {},
|
|
585
|
-
}[launch];
|
|
586
|
-
const res = await Promise.all(rooms.map((r) => roomControl(env, meta, fresh, r.room, 'regate', ALLOW[launch] ? { allow: ALLOW[launch], ...words } : {})));
|
|
596
|
+
const res = await Promise.all(rooms.map((r) => roomControl(env, meta, fresh, r.room, 'regate', regateArgs(launch, meta.name))));
|
|
587
597
|
done.regating = res.filter((r) => r.ok && r.leaving > 0).length;
|
|
588
598
|
}
|
|
589
599
|
if (a.maxPlayers !== undefined) {
|
package/worker/version.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
/** The @homie-rocks/studio version this Worker code is (package.json's; a test keeps them equal). Cache-busts /_homie/site.js. */
|
|
2
|
-
export const STUDIO_VERSION_TAG = '0.14.
|
|
2
|
+
export const STUDIO_VERSION_TAG = '0.14.1';
|