@homie-rocks/studio 0.30.2 → 0.31.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.
Files changed (90) hide show
  1. package/CHANGELOG.md +108 -1
  2. package/README.md +349 -23
  3. package/assets/animate.ts +16 -6
  4. package/assets/assets.ts +14 -3
  5. package/assets/render-page.ts +10 -2
  6. package/assets/safety.mjs +7 -1
  7. package/bin/homie-studio.mjs +204 -124
  8. package/lib/art-cli.mjs +61 -18
  9. package/lib/art-tools.mjs +53 -10
  10. package/lib/asset-check.mjs +249 -46
  11. package/lib/asset-import.mjs +11 -5
  12. package/lib/asset-manifest.mjs +128 -51
  13. package/lib/build.mjs +239 -72
  14. package/lib/characters.mjs +119 -20
  15. package/lib/check.mjs +110 -7
  16. package/lib/chrome.mjs +11 -0
  17. package/lib/clips.mjs +54 -0
  18. package/lib/cloudflare.mjs +203 -10
  19. package/lib/codex.mjs +6 -0
  20. package/lib/collision.mjs +245 -0
  21. package/lib/decisions.mjs +2 -2
  22. package/lib/deploy-state.mjs +166 -0
  23. package/lib/dev.mjs +430 -0
  24. package/lib/directory.mjs +172 -9
  25. package/lib/doctor.mjs +12 -11
  26. package/lib/handoff.mjs +2 -6
  27. package/lib/library.mjs +1 -1
  28. package/lib/mcp-tools.mjs +42 -37
  29. package/lib/mcp.mjs +1 -1
  30. package/lib/net.mjs +81 -2
  31. package/lib/node-version.mjs +40 -0
  32. package/lib/office.mjs +6 -6
  33. package/lib/optimise.mjs +39 -4
  34. package/lib/parts-build.mjs +169 -0
  35. package/lib/parts-cli.mjs +104 -0
  36. package/lib/parts-store.mjs +342 -0
  37. package/lib/parts-tools.mjs +92 -0
  38. package/lib/parts.mjs +720 -0
  39. package/lib/perf.mjs +131 -31
  40. package/lib/port-check.mjs +40 -6
  41. package/lib/preview.mjs +77 -0
  42. package/lib/routes.mjs +240 -0
  43. package/lib/scaffold.mjs +83 -22
  44. package/lib/shoot.mjs +307 -0
  45. package/lib/site.mjs +1 -2
  46. package/lib/stage.mjs +118 -0
  47. package/lib/studio.mjs +0 -66
  48. package/lib/style-board.mjs +49 -6
  49. package/lib/template-history.json +101 -0
  50. package/lib/trailer.mjs +47 -0
  51. package/lib/typecheck.mjs +107 -0
  52. package/mcp/ui/lineup.js +3 -3
  53. package/mcp/ui/rights.js +1 -3
  54. package/netplay/NETPLAY.md +552 -16
  55. package/netplay/netplay.ts +950 -43
  56. package/package.json +10 -1
  57. package/parts/PARTS.md +169 -0
  58. package/port/index.ts +8 -2
  59. package/port/personas.ts +275 -0
  60. package/port/powerups.ts +357 -0
  61. package/port/probe.ts +38 -2
  62. package/port/room.ts +329 -40
  63. package/site/SITE.md +102 -20
  64. package/starters/ember-vale/index.html +1 -1
  65. package/starters/ember-vale/src/main.ts +7 -1
  66. package/starters/gem-rush/index.html +1 -1
  67. package/starters/gem-rush/src/main.ts +41 -12
  68. package/starters/gem-rush-3d/assets/RIGHTS.md +0 -24
  69. package/starters/gem-rush-3d/assets/manifest.json +0 -24
  70. package/starters/gem-rush-3d/index.html +1 -1
  71. package/starters/gem-rush-3d/src/main.ts +50 -11
  72. package/starters/hero-rush-3d/assets/RIGHTS.md +0 -19
  73. package/starters/hero-rush-3d/assets/manifest.json +0 -19
  74. package/starters/hero-rush-3d/index.html +1 -1
  75. package/starters/hero-rush-3d/src/main.ts +50 -11
  76. package/worker/arrival.mjs +4 -1
  77. package/worker/discover.mjs +11 -25
  78. package/worker/index.mjs +142 -62
  79. package/worker/license.mjs +45 -41
  80. package/worker/office-page.mjs +0 -4
  81. package/worker/office-schema.mjs +6 -0
  82. package/worker/office.mjs +19 -23
  83. package/worker/pages.mjs +156 -29
  84. package/worker/parts.mjs +275 -0
  85. package/worker/room.mjs +157 -14
  86. package/worker/schema.mjs +6 -8
  87. package/worker/seats.mjs +71 -0
  88. package/worker/site.mjs +51 -53
  89. package/worker/version.mjs +1 -1
  90. package/lib/remix-assets.mjs +0 -84
package/assets/assets.ts CHANGED
@@ -18,7 +18,8 @@
18
18
  * runtime (homie.rocks's starter library is copied into the game, never hot-linked). `allowOrigins` opens one on
19
19
  * purpose.
20
20
  *
21
- * In development (localhost, or ?debug=1) a model over its budget says so in the console, and `stats()` (also
21
+ * In development (localhost, or ?debug=1) a model over its budget says so in the console (the budget is a small
22
+ * prop's unless the game sets its own: game.json `"assets": { "budgets": { "triangles", "bytes" } }`), and `stats()` (also
22
23
  * `window.__homieModels`) has the totals the perf skill reads: models, triangles, texture memory and refusals.
23
24
  *
24
25
  * KTX2 pictures (KHR_texture_basisu) need a transcoder (about 580 KB): pass your own KTX2Loader as `ktx2` when a
@@ -70,7 +71,7 @@ export interface LoadedModel {
70
71
  export interface ModelsOptions {
71
72
  /** Narrow or widen the file checks (LIMITS.game by default). */
72
73
  limits?: Partial<ModelLimits>;
73
- /** A per-model budget for the development warnings (the prop tier by default). */
74
+ /** A per-model budget for the development warnings (over game.json's `assets.budgets`, over the prop tier). */
74
75
  budget?: ModelBudget;
75
76
  /** A KTX2Loader the game set up (transcoder path, renderer support): only for models with KTX2 pictures. */
76
77
  ktx2?: { load?: unknown } | null;
@@ -98,6 +99,16 @@ export interface Models {
98
99
  }
99
100
 
100
101
  const PROP_BUDGET: Required<ModelBudget> = { triangles: 1500, texturePx: 512, materials: 1, bytes: 300 * 1024 };
102
+ /**
103
+ * The game's own budgets: game.json `"assets": { "budgets": { "triangles": 8000, "bytes": 1500000 } }`, which
104
+ * `homie-studio build` writes into the bundle as this constant (lib/build.mjs modelBudgetsOf). The defaults above are
105
+ * a small prop's; a 3D game whose every model is a character or a piece of level sets its own once, there, instead
106
+ * of reading a warning per model. Any other bundler leaves the name undefined, and the defaults stand.
107
+ */
108
+ declare const __HOMIE_MODEL_BUDGETS__: ModelBudget | undefined;
109
+ const gameBudget = (): ModelBudget => {
110
+ try { return typeof __HOMIE_MODEL_BUDGETS__ === 'object' && __HOMIE_MODEL_BUDGETS__ ? __HOMIE_MODEL_BUDGETS__ : {}; } catch { return {}; }
111
+ };
101
112
  const devHost = (): boolean => {
102
113
  try { return /^(localhost|127\.0\.0\.1|\[::1\])$/.test(location.hostname) || new URLSearchParams(location.search).get('debug') === '1'; } catch { return false; }
103
114
  };
@@ -461,7 +472,7 @@ export function createModels(opts: ModelsOptions = {}): Models {
461
472
  const key = url;
462
473
  let p = cache.get(key);
463
474
  if (!p) {
464
- p = loadOnce(url, { ...limits, ...(o.limits ?? {}) } as ModelLimits, { ...PROP_BUDGET, ...(opts.budget ?? {}), ...(o.budget ?? {}) });
475
+ p = loadOnce(url, { ...limits, ...(o.limits ?? {}) } as ModelLimits, { ...PROP_BUDGET, ...gameBudget(), ...(opts.budget ?? {}), ...(o.budget ?? {}) });
465
476
  cache.set(key, p);
466
477
  p.catch(() => cache.delete(key));
467
478
  }
@@ -35,7 +35,7 @@ interface Tokens {
35
35
  shape?: { language?: string; bevel?: number };
36
36
  proportions?: { heads?: number; heightM?: number };
37
37
  }
38
- interface ModelIn { id: string; glb: string; label?: string; place?: 'hero' | 'prop' | 'dressing'; scale?: number; retint?: boolean; tint?: string; pull?: number; anims?: string; pose?: string; poseAt?: number }
38
+ interface ModelIn { id: string; glb: string; label?: string; place?: 'hero' | 'prop' | 'dressing'; scale?: number; retint?: boolean; tint?: string; pull?: number; anims?: string | string[]; pose?: string; poseAt?: number }
39
39
 
40
40
  const canvas = document.createElement('canvas');
41
41
  document.body.appendChild(canvas);
@@ -51,7 +51,15 @@ async function parse(m: ModelIn): Promise<Group> {
51
51
  const gltf = await new Promise<{ scene: Group; animations: AnimationClip[] }>((res, rej) => loader.parse(b64(m.glb), '', res as never, rej));
52
52
  gltf.scene.name = m.id;
53
53
  // A character with a clip library stands in a pose (idle, a little way in), not in its bind pose.
54
- const clips = m.anims ? (await new Promise<{ animations: AnimationClip[] }>((res, rej) => loader.parse(b64(m.anims as string), '', res as never, rej))).animations : gltf.animations;
54
+ // One clip library, or several (its skeleton's own, then any declared supplemental): a verb's first clip is kept.
55
+ let clips = gltf.animations;
56
+ if (m.anims) {
57
+ clips = [];
58
+ for (const lib of Array.isArray(m.anims) ? m.anims : [m.anims]) {
59
+ const more = (await new Promise<{ animations: AnimationClip[] }>((res, rej) => loader.parse(b64(lib), '', res as never, rej))).animations;
60
+ for (const c of more) if (!clips.some((x) => x.name === c.name)) clips.push(c);
61
+ }
62
+ }
55
63
  gltf.scene.userData.clips = clips;
56
64
  if (clips?.length && (m.anims || m.pose)) {
57
65
  const clip = clips.find((c) => c.name === (m.pose ?? 'idle')) ?? clips.find((c) => c.name === 'idle') ?? clips[0];
package/assets/safety.mjs CHANGED
@@ -87,12 +87,18 @@ export function readGlb(bytes, { maxJson = LIMITS.import.json } = {}) {
87
87
  * { ok, problems: [plain words], warnings: [plain words], info: { bytes, triangles, vertices, meshes, nodes,
88
88
  * materials, images, textures, animations, skins, joints, extensions: { used, required } } }
89
89
  * Never throws: a file it cannot read is `ok: false` with the reason.
90
+ *
91
+ * The JSDoc types say what the code always took: any limits (numbers, a part of them is enough), and extension names
92
+ * as strings. Without them a strict TypeScript caller read the frozen defaults as literal types and the empty lists
93
+ * as never[].
94
+ * @param {Uint8Array} bytes
95
+ * @param {Partial<Record<keyof typeof LIMITS.game, number>>} [limits]
90
96
  */
91
97
  export function checkGlb(bytes, limits = LIMITS.game) {
92
98
  const L = { ...LIMITS.game, ...limits };
93
99
  const problems = [];
94
100
  const warnings = [];
95
- const info = { bytes: bytes?.byteLength ?? 0, triangles: 0, vertices: 0, meshes: 0, nodes: 0, materials: 0, images: 0, textures: 0, animations: 0, skins: 0, joints: 0, extensions: { used: [], required: [] } };
101
+ const info = { bytes: bytes?.byteLength ?? 0, triangles: 0, vertices: 0, meshes: 0, nodes: 0, materials: 0, images: 0, textures: 0, animations: 0, skins: 0, joints: 0, extensions: { used: /** @type {string[]} */ ([]), required: /** @type {string[]} */ ([]) } };
96
102
  if (info.bytes > L.bytes) problems.push(`the file is ${size(info.bytes)}, over the ${size(L.bytes)} a model may be here`);
97
103
  let glb;
98
104
  try { glb = readGlb(bytes, { maxJson: L.json }); } catch (error) { problems.push(error.message); return { ok: false, problems, warnings, info }; }