@homie-rocks/studio 0.46.0 → 0.46.2
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 +44 -0
- package/README.md +3 -2
- package/docs/rooms-milestone-2-notes.md +345 -0
- package/guides/game/RULES.md +11 -2
- package/lib/build-identity.mjs +43 -0
- package/lib/cloudflare.mjs +13 -11
- package/lib/crowd-cloud.mjs +88 -0
- package/lib/crowd-diagnostics.mjs +73 -0
- package/lib/crowd-load.mjs +603 -0
- package/lib/crowd-probe/README.md +61 -0
- package/lib/crowd-probe/client.mjs +96 -0
- package/lib/crowd-probe/worker.mjs +72 -0
- package/lib/crowd-probe/wrangler.jsonc +12 -0
- package/lib/crowd-summary.mjs +140 -0
- package/lib/crowd-worker.mjs +496 -0
- package/lib/scaffold.mjs +5 -5
- package/lib/template-history.json +42 -0
- package/netplay/netplay.ts +18 -5
- package/package.json +4 -2
- package/rules/host.ts +34 -8
- package/rules/interest.mjs +320 -51
- package/rules/rules.ts +13 -3
- package/rules/timing.mjs +20 -0
- package/worker/build-id.mjs +2 -0
- package/worker/gate.mjs +436 -53
- package/worker/index.mjs +90 -10
- package/worker/office-page.mjs +11 -0
- package/worker/office.mjs +27 -0
- package/worker/room.mjs +85 -16
- package/worker/version.mjs +1 -1
- package/worker/world-wire.mjs +110 -0
package/rules/rules.ts
CHANGED
|
@@ -205,6 +205,8 @@ export interface RoomSettings {
|
|
|
205
205
|
durability: { movementSeconds: number };
|
|
206
206
|
budget: { tick: number };
|
|
207
207
|
predict: { idleHold?: boolean; catchM: number | null; catchUp: number; snapM: number | null; blendMs: number; interpMs: number | null };
|
|
208
|
+
/** The studio's own pace on what one player sends, messages a second by frame kind (`in`, `ev`, ...). Absent: none. */
|
|
209
|
+
rates?: Record<string, number>;
|
|
208
210
|
}
|
|
209
211
|
/**
|
|
210
212
|
* The default `budget.tick`: 500,000 units at 20 ticks a second or fewer, and less at a faster rate, so a second of
|
|
@@ -238,8 +240,16 @@ export function roomSettings(raw: unknown): { settings: RoomSettings; problems:
|
|
|
238
240
|
if (!Number.isFinite(n) || n < 0) { problems.push(`"room.${name}" is a number, zero or more; using its default`); return d; }
|
|
239
241
|
return n;
|
|
240
242
|
};
|
|
241
|
-
const known = new Set(['host', 'offline', 'tickHz', 'inputHz', 'durability', 'budget', 'predict', 'view', 'lifetime', 'characters']);
|
|
242
|
-
for (const key of Object.keys(r)) if (!known.has(key)) problems.push(`"room.${key}" is not a setting (host, offline, tickHz, inputHz, durability, budget, predict, view)`);
|
|
243
|
+
const known = new Set(['host', 'offline', 'tickHz', 'inputHz', 'durability', 'budget', 'predict', 'view', 'lifetime', 'characters', 'rates']);
|
|
244
|
+
for (const key of Object.keys(r)) if (!known.has(key)) problems.push(`"room.${key}" is not a setting (host, offline, tickHz, inputHz, durability, budget, predict, view, rates)`);
|
|
245
|
+
let rates: Record<string, number> | undefined;
|
|
246
|
+
if (r.rates !== undefined) {
|
|
247
|
+
if (!r.rates || typeof r.rates !== 'object' || Array.isArray(r.rates)) problems.push('"room.rates" is an object of messages a second per player by kind ({ "in": 60 }); using none');
|
|
248
|
+
else for (const [kind, v] of Object.entries(r.rates)) {
|
|
249
|
+
if (!/^[a-z][a-z-]{0,15}$/.test(kind) || typeof v !== 'number' || !Number.isFinite(v) || v < 0) problems.push(`"room.rates.${kind}" is a number of messages a second, zero or more; leaving it out`);
|
|
250
|
+
else (rates ??= {})[kind] = v;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
243
253
|
let host: 'server' | 'browser' = 'server';
|
|
244
254
|
if (r.host !== undefined) { if (r.host === 'server' || r.host === 'browser') host = r.host; else problems.push('"room.host" is "server" or "browser"; using "server"'); }
|
|
245
255
|
if (r.lifetime !== undefined && !['match', 'persistent'].includes(r.lifetime)) problems.push('"room.lifetime" is "match" or "persistent"');
|
|
@@ -258,7 +268,7 @@ export function roomSettings(raw: unknown): { settings: RoomSettings; problems:
|
|
|
258
268
|
const p = r.predict && typeof r.predict === 'object' ? r.predict : {};
|
|
259
269
|
return {
|
|
260
270
|
settings: {
|
|
261
|
-
host, offline, tickHz, inputHz, ...(r.lifetime === 'persistent' ? { lifetime: 'persistent' as const } : {}), ...(r.characters === true ? { characters: true } : {}),
|
|
271
|
+
host, offline, tickHz, inputHz, ...(rates ? { rates } : {}), ...(r.lifetime === 'persistent' ? { lifetime: 'persistent' as const } : {}), ...(r.characters === true ? { characters: true } : {}),
|
|
262
272
|
view: { radiusM: r.view?.radiusM === null ? null : num('view.radiusM', r.view?.radiusM, null), ...(r.view?.precisionM !== undefined ? { precisionM: num('view.precisionM', r.view.precisionM, 0) as number } : {}), ...(r.view?.nearM !== undefined ? { nearM: num('view.nearM', r.view.nearM, null) } : {}), ...(r.view?.farHz !== undefined ? { farHz: whole('view.farHz', r.view.farHz, 1, tickHz, tickHz) } : {}) },
|
|
263
273
|
durability: { movementSeconds: whole('durability.movementSeconds', r.durability?.movementSeconds, 1, 60, r.lifetime === 'persistent' ? 30 : 1) },
|
|
264
274
|
budget: { tick: Math.max(1, Math.floor(num('budget.tick', r.budget?.tick, budgetFor(tickHz)) as number)) },
|
package/rules/timing.mjs
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/** Bounded stage samples. Frozen runtime clocks are unavailable, never zero CPU. */
|
|
2
|
+
export function stageTimings(now = () => globalThis.performance?.now?.() ?? 0, limit = 256) {
|
|
3
|
+
const stages = new Map();
|
|
4
|
+
return {
|
|
5
|
+
now,
|
|
6
|
+
record(stage, start) {
|
|
7
|
+
const ms = Math.max(0, now() - start);
|
|
8
|
+
let sample = stages.get(stage);
|
|
9
|
+
if (!sample) { sample = { values: [], next: 0 }; stages.set(stage, sample); }
|
|
10
|
+
sample.values[sample.next] = ms; sample.next = (sample.next + 1) % limit;
|
|
11
|
+
},
|
|
12
|
+
facts() {
|
|
13
|
+
const advancing = [...stages.values()].some(({ values }) => values.some(ms => ms > 0));
|
|
14
|
+
return { clock: advancing ? 'performance' : 'unavailable-or-below-resolution', ms: Object.fromEntries([...stages].map(([name, { values }]) => {
|
|
15
|
+
const sorted = [...values].sort((a,b)=>a-b);
|
|
16
|
+
return [name, advancing ? { samples: sorted.length, p50: sorted[Math.floor(sorted.length*.5)], p95: sorted[Math.floor(sorted.length*.95)], max: sorted.at(-1) } : null];
|
|
17
|
+
})) };
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
}
|