@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.
- package/CHANGELOG.md +108 -1
- package/README.md +349 -23
- package/assets/animate.ts +16 -6
- package/assets/assets.ts +14 -3
- package/assets/render-page.ts +10 -2
- package/assets/safety.mjs +7 -1
- package/bin/homie-studio.mjs +204 -124
- package/lib/art-cli.mjs +61 -18
- package/lib/art-tools.mjs +53 -10
- package/lib/asset-check.mjs +249 -46
- package/lib/asset-import.mjs +11 -5
- package/lib/asset-manifest.mjs +128 -51
- package/lib/build.mjs +239 -72
- package/lib/characters.mjs +119 -20
- package/lib/check.mjs +110 -7
- package/lib/chrome.mjs +11 -0
- package/lib/clips.mjs +54 -0
- package/lib/cloudflare.mjs +203 -10
- package/lib/codex.mjs +6 -0
- package/lib/collision.mjs +245 -0
- package/lib/decisions.mjs +2 -2
- package/lib/deploy-state.mjs +166 -0
- package/lib/dev.mjs +430 -0
- package/lib/directory.mjs +172 -9
- package/lib/doctor.mjs +12 -11
- package/lib/handoff.mjs +2 -6
- package/lib/library.mjs +1 -1
- package/lib/mcp-tools.mjs +42 -37
- package/lib/mcp.mjs +1 -1
- package/lib/net.mjs +81 -2
- package/lib/node-version.mjs +40 -0
- package/lib/office.mjs +6 -6
- package/lib/optimise.mjs +39 -4
- package/lib/parts-build.mjs +169 -0
- package/lib/parts-cli.mjs +104 -0
- package/lib/parts-store.mjs +342 -0
- package/lib/parts-tools.mjs +92 -0
- package/lib/parts.mjs +720 -0
- package/lib/perf.mjs +131 -31
- package/lib/port-check.mjs +40 -6
- package/lib/preview.mjs +77 -0
- package/lib/routes.mjs +240 -0
- package/lib/scaffold.mjs +83 -22
- package/lib/shoot.mjs +307 -0
- package/lib/site.mjs +1 -2
- package/lib/stage.mjs +118 -0
- package/lib/studio.mjs +0 -66
- package/lib/style-board.mjs +49 -6
- package/lib/template-history.json +101 -0
- package/lib/trailer.mjs +47 -0
- package/lib/typecheck.mjs +107 -0
- package/mcp/ui/lineup.js +3 -3
- package/mcp/ui/rights.js +1 -3
- package/netplay/NETPLAY.md +552 -16
- package/netplay/netplay.ts +950 -43
- package/package.json +10 -1
- package/parts/PARTS.md +169 -0
- package/port/index.ts +8 -2
- package/port/personas.ts +275 -0
- package/port/powerups.ts +357 -0
- package/port/probe.ts +38 -2
- package/port/room.ts +329 -40
- package/site/SITE.md +102 -20
- package/starters/ember-vale/index.html +1 -1
- package/starters/ember-vale/src/main.ts +7 -1
- package/starters/gem-rush/index.html +1 -1
- package/starters/gem-rush/src/main.ts +41 -12
- package/starters/gem-rush-3d/assets/RIGHTS.md +0 -24
- package/starters/gem-rush-3d/assets/manifest.json +0 -24
- package/starters/gem-rush-3d/index.html +1 -1
- package/starters/gem-rush-3d/src/main.ts +50 -11
- package/starters/hero-rush-3d/assets/RIGHTS.md +0 -19
- package/starters/hero-rush-3d/assets/manifest.json +0 -19
- package/starters/hero-rush-3d/index.html +1 -1
- package/starters/hero-rush-3d/src/main.ts +50 -11
- package/worker/arrival.mjs +4 -1
- package/worker/discover.mjs +11 -25
- package/worker/index.mjs +142 -62
- package/worker/license.mjs +45 -41
- package/worker/office-page.mjs +0 -4
- package/worker/office-schema.mjs +6 -0
- package/worker/office.mjs +19 -23
- package/worker/pages.mjs +156 -29
- package/worker/parts.mjs +275 -0
- package/worker/room.mjs +157 -14
- package/worker/schema.mjs +6 -8
- package/worker/seats.mjs +71 -0
- package/worker/site.mjs +51 -53
- package/worker/version.mjs +1 -1
- package/lib/remix-assets.mjs +0 -84
|
@@ -1551,6 +1551,107 @@
|
|
|
1551
1551
|
}
|
|
1552
1552
|
}
|
|
1553
1553
|
}
|
|
1554
|
+
},
|
|
1555
|
+
"0.31.0": {
|
|
1556
|
+
"files": {
|
|
1557
|
+
"CLAUDE.md": "ddf2b2a0376202f6",
|
|
1558
|
+
"README.md": "684b3cca8001673c",
|
|
1559
|
+
"HANDOFF.md": [
|
|
1560
|
+
"29337e2ace95b2e7",
|
|
1561
|
+
"ce7a8fd91c6bbee2"
|
|
1562
|
+
],
|
|
1563
|
+
"games/README.md": "5ad32fe80c61f117",
|
|
1564
|
+
"music/README.md": "a3dc042dadd39df8",
|
|
1565
|
+
"videos/README.md": "16babc3437d5545b",
|
|
1566
|
+
"posts/README.md": "37841aae476bd540",
|
|
1567
|
+
"site/README.md": "732894273a32dc3f",
|
|
1568
|
+
"site/src/worker.mjs": "ca3f1630f2420ad3"
|
|
1569
|
+
},
|
|
1570
|
+
"sections": {
|
|
1571
|
+
"AGENTS.md": {
|
|
1572
|
+
"(intro)": "214aa46eb3a895d5",
|
|
1573
|
+
"## Layout": "8baf49e4291e7e88",
|
|
1574
|
+
"## Commands (all through the pinned CLI in node_modules)": [
|
|
1575
|
+
"29a04ed3d956610a",
|
|
1576
|
+
"f25ef8c7dde1f5c2"
|
|
1577
|
+
],
|
|
1578
|
+
"## The site": [
|
|
1579
|
+
"0cb8dd9a6f8a0096",
|
|
1580
|
+
"71bec10e44e5062f"
|
|
1581
|
+
],
|
|
1582
|
+
"## Making games": [
|
|
1583
|
+
"b0b921cb81b0cfbd",
|
|
1584
|
+
"f52aa1c3566c0c19"
|
|
1585
|
+
],
|
|
1586
|
+
"## The Game Codex and progress": "0738ef719cef3a4c",
|
|
1587
|
+
"## Art direction and models": [
|
|
1588
|
+
"a48fff7bf0a5f2fc",
|
|
1589
|
+
"aaea65236db52126"
|
|
1590
|
+
],
|
|
1591
|
+
"## Running live games (the back office)": [
|
|
1592
|
+
"4aa5f513a7cd2c3b",
|
|
1593
|
+
"51ebf2ab756f6e4d"
|
|
1594
|
+
],
|
|
1595
|
+
"## Servers and AI seats": "98d08705087a33ec",
|
|
1596
|
+
"## Selling things (the shop)": "b04b0a2b25f524f2",
|
|
1597
|
+
"## Continuing a build from the Claude app": "0d68081e924ca419",
|
|
1598
|
+
"## Rules": "13617a3dd1a66be6",
|
|
1599
|
+
"## Telling Homie": "80b46fdd9e578db8",
|
|
1600
|
+
"## Beta": "9d02638207a26e73"
|
|
1601
|
+
}
|
|
1602
|
+
},
|
|
1603
|
+
"named": {
|
|
1604
|
+
"Homie Arcade": {
|
|
1605
|
+
"sections": {
|
|
1606
|
+
"AGENTS.md": {
|
|
1607
|
+
"## Commands (all through the pinned CLI in node_modules)": [
|
|
1608
|
+
"5d1c068adda1116b",
|
|
1609
|
+
"a44e5bdd5425d5b6"
|
|
1610
|
+
]
|
|
1611
|
+
}
|
|
1612
|
+
}
|
|
1613
|
+
}
|
|
1614
|
+
}
|
|
1615
|
+
},
|
|
1616
|
+
"0.31.1": {
|
|
1617
|
+
"files": {
|
|
1618
|
+
"CLAUDE.md": "ddf2b2a0376202f6",
|
|
1619
|
+
"README.md": "684b3cca8001673c",
|
|
1620
|
+
"HANDOFF.md": "29337e2ace95b2e7",
|
|
1621
|
+
"games/README.md": "5ad32fe80c61f117",
|
|
1622
|
+
"music/README.md": "a3dc042dadd39df8",
|
|
1623
|
+
"videos/README.md": "16babc3437d5545b",
|
|
1624
|
+
"posts/README.md": "37841aae476bd540",
|
|
1625
|
+
"site/README.md": "732894273a32dc3f",
|
|
1626
|
+
"site/src/worker.mjs": "ca3f1630f2420ad3"
|
|
1627
|
+
},
|
|
1628
|
+
"sections": {
|
|
1629
|
+
"AGENTS.md": {
|
|
1630
|
+
"(intro)": "214aa46eb3a895d5",
|
|
1631
|
+
"## Layout": "8baf49e4291e7e88",
|
|
1632
|
+
"## Commands (all through the pinned CLI in node_modules)": "29a04ed3d956610a",
|
|
1633
|
+
"## The site": "0cb8dd9a6f8a0096",
|
|
1634
|
+
"## Making games": "2da7fa74c8884834",
|
|
1635
|
+
"## The Game Codex and progress": "0738ef719cef3a4c",
|
|
1636
|
+
"## Art direction and models": "a48fff7bf0a5f2fc",
|
|
1637
|
+
"## Running live games (the back office)": "51ebf2ab756f6e4d",
|
|
1638
|
+
"## Servers and AI seats": "98d08705087a33ec",
|
|
1639
|
+
"## Selling things (the shop)": "b04b0a2b25f524f2",
|
|
1640
|
+
"## Continuing a build from the Claude app": "0d68081e924ca419",
|
|
1641
|
+
"## Rules": "2e6b29afb85dcf7e",
|
|
1642
|
+
"## Telling Homie": "80b46fdd9e578db8",
|
|
1643
|
+
"## Beta": "9d02638207a26e73"
|
|
1644
|
+
}
|
|
1645
|
+
},
|
|
1646
|
+
"named": {
|
|
1647
|
+
"Homie Arcade": {
|
|
1648
|
+
"sections": {
|
|
1649
|
+
"AGENTS.md": {
|
|
1650
|
+
"## Commands (all through the pinned CLI in node_modules)": "5d1c068adda1116b"
|
|
1651
|
+
}
|
|
1652
|
+
}
|
|
1653
|
+
}
|
|
1654
|
+
}
|
|
1554
1655
|
}
|
|
1555
1656
|
}
|
|
1556
1657
|
}
|
package/lib/trailer.mjs
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `homie-studio trailer <id>`: a trailer of a game, in one command.
|
|
3
|
+
*
|
|
4
|
+
* The work is not here. It is the Homie plugin's video skill (`video.mjs trailer`): the game rendered frame by frame
|
|
5
|
+
* on a virtual clock, its sound rebuilt from its own files, the highlights picked from what it played, an end card,
|
|
6
|
+
* and 16:9, 1:1 and 9:16 deliveries. This finds that script and hands the command over, so a person at a terminal
|
|
7
|
+
* and an agent with the plugin run the same thing. The video goes to videos/<id>-trailer/ (or --slug <slug>).
|
|
8
|
+
*
|
|
9
|
+
* The skills are looked for where the MCP server looks: --skills <folder>, HOMIE_SKILLS, beside this package in the
|
|
10
|
+
* repository (plugins/homie/skills), or beside it in the plugin (../skills). When none is there, it says so and
|
|
11
|
+
* says what to install, instead of pretending there is a trailer tool in this package alone.
|
|
12
|
+
*/
|
|
13
|
+
import { spawnSync } from 'node:child_process';
|
|
14
|
+
import { existsSync } from 'node:fs';
|
|
15
|
+
import { join, resolve } from 'node:path';
|
|
16
|
+
import { PACKAGE_ROOT } from './studio.mjs';
|
|
17
|
+
|
|
18
|
+
export function trailerScript(skills = null) {
|
|
19
|
+
const dirs = [skills, process.env.HOMIE_SKILLS, join(PACKAGE_ROOT, '..', '..', 'plugins', 'homie', 'skills'), join(PACKAGE_ROOT, '..', 'skills')].filter(Boolean).map((d) => resolve(String(d)));
|
|
20
|
+
return dirs.map((d) => join(d, 'video', 'scripts', 'video.mjs')).find((f) => existsSync(f)) ?? null;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** `rest` is every word after the game's id, as typed: it goes to the script unchanged. */
|
|
24
|
+
export function trailerCommand(root, id, rest, { skills = null, slug = null } = {}) {
|
|
25
|
+
if (!id || !/^[a-z0-9][a-z0-9-]{0,39}$/.test(String(id))) return { ok: false, command: 'trailer', why: 'usage: homie-studio trailer <game id> [--url <site>] [--seconds 40] [--length 20] [--title "…"] [--end "…"] (the site: http://127.0.0.1:8787 while `homie-studio dev` runs)' };
|
|
26
|
+
const script = trailerScript(skills);
|
|
27
|
+
if (!script) return { ok: false, command: 'trailer', why: 'the trailer tool is the Homie plugin\'s video skill, and its scripts are not on this computer where this package looks. Install the plugin (the studio\'s AGENTS.md says how), or point at its skills folder: homie-studio trailer <id> --skills <the plugin\'s skills folder>' };
|
|
28
|
+
const pass = [];
|
|
29
|
+
for (let i = 0; i < rest.length; i++) {
|
|
30
|
+
if (rest[i] === '--skills' || rest[i] === '--slug') { i++; continue; }
|
|
31
|
+
if (rest[i] === '--json' || rest[i].startsWith('--skills=') || rest[i].startsWith('--slug=')) continue;
|
|
32
|
+
pass.push(rest[i]);
|
|
33
|
+
}
|
|
34
|
+
const r = spawnSync(process.execPath, [script, 'trailer', String(slug ?? `${id}-trailer`), '--game', String(id), ...pass, '--json'], { cwd: root, encoding: 'utf8', stdio: ['ignore', 'pipe', 'inherit'], timeout: 90 * 60_000, maxBuffer: 64 * 1024 * 1024 });
|
|
35
|
+
try { return { ...JSON.parse(r.stdout), command: 'trailer' }; } catch { return { ok: false, command: 'trailer', why: `the video skill's script gave no result (${(r.stdout ?? '').trim().split('\n').pop() || `exit ${r.status}`})` }; }
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function trailerLines(r) {
|
|
39
|
+
return [
|
|
40
|
+
...(r.problems ?? []).map((x) => `PROBLEM: ${x}`),
|
|
41
|
+
`${r.seconds} s trailer of ${r.game}: ${Object.values(r.outputs ?? {}).map((o) => o.file).join(', ')}`,
|
|
42
|
+
` filmed ${r.capture?.seconds} s frame by frame (${r.capture?.frames} frames, ${r.capture?.heldFrames} held); ${r.capture?.audio ? `${r.capture.audio.placed} sounds rebuilt from the game's own files` : 'the game logged no sound'}`,
|
|
43
|
+
` ${r.edit?.shots} shots of ${r.edit?.shotSeconds} s, picked ${r.edit?.picked}`,
|
|
44
|
+
...(r.warnings ?? []).map((w) => ` warning: ${w}`),
|
|
45
|
+
...(r.next ? [` next (the video skill's script): ${r.next}`] : []),
|
|
46
|
+
];
|
|
47
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `homie-studio build --types` — the games' TypeScript, checked before anything is built.
|
|
3
|
+
*
|
|
4
|
+
* The build bundles with esbuild, which strips types and never reads them: `const hp: number = "full"` builds and
|
|
5
|
+
* ships. This is the check a creator's agent would otherwise have no way to run, because a studio had no TypeScript
|
|
6
|
+
* of its own to run `npx tsc` with.
|
|
7
|
+
*
|
|
8
|
+
* - The compiler is the STUDIO'S OWN (`typescript` in its node_modules, which a new studio's package.json asks
|
|
9
|
+
* for). @homie-rocks/studio does not depend on it: the check is something a studio opts into, and a studio that
|
|
10
|
+
* never does pays nothing for it. Without it the command stops and says the one line that installs it.
|
|
11
|
+
* - A game with a tsconfig.json of its own is checked with it. Any other game is checked the way esbuild reads
|
|
12
|
+
* it: its entry and whatever that imports, as ES2022 for a browser, strictly, with the pictures, sounds and
|
|
13
|
+
* models it imports as addresses (the build's own loaders).
|
|
14
|
+
* - Only errors in the game's own files stop the build. A game imports the toolkit's helpers as TypeScript
|
|
15
|
+
* source, and a creator can do nothing about a line in node_modules; those are counted and said, never fatal.
|
|
16
|
+
*
|
|
17
|
+
* Never run unasked: a plain `build` is exactly as fast as it was.
|
|
18
|
+
*/
|
|
19
|
+
import { spawnSync } from 'node:child_process';
|
|
20
|
+
import { existsSync, mkdirSync, readFileSync, realpathSync, writeFileSync } from 'node:fs';
|
|
21
|
+
import { createRequire } from 'node:module';
|
|
22
|
+
import { dirname, isAbsolute, join, relative, resolve, sep } from 'node:path';
|
|
23
|
+
|
|
24
|
+
/** What the build's file loaders turn into an address (lib/build.mjs LOADERS): an import of one is a string. */
|
|
25
|
+
const ADDRESSES = ['png', 'jpg', 'jpeg', 'gif', 'webp', 'mp3', 'ogg', 'wav', 'm4a', 'glb', 'gltf', 'bin', 'hdr', 'svg', 'woff2', 'ttf'];
|
|
26
|
+
const AMBIENT = `// Written by homie-studio build --types: what the build's loaders give an import of each kind of file.\n${ADDRESSES.map((x) => `declare module '*.${x}' { const address: string; export default address; }`).join('\n')}\n`;
|
|
27
|
+
|
|
28
|
+
/** How a game with no tsconfig.json of its own is read: as the build reads it. */
|
|
29
|
+
const OPTIONS = {
|
|
30
|
+
target: 'ES2022', module: 'ESNext', moduleResolution: 'Bundler', lib: ['ES2022', 'DOM', 'DOM.Iterable'],
|
|
31
|
+
strict: true, noEmit: true, skipLibCheck: true, allowImportingTsExtensions: true, resolveJsonModule: true,
|
|
32
|
+
isolatedModules: true, allowJs: true, checkJs: false, useDefineForClassFields: true,
|
|
33
|
+
// A studio's node_modules has Node's types (its tools bring them); a game runs in a browser, where a timer is a number.
|
|
34
|
+
types: [],
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
/** The studio's own TypeScript: { tsc, version }, or null when it has none. */
|
|
38
|
+
export function studioTypescript(root) {
|
|
39
|
+
try {
|
|
40
|
+
const pkg = createRequire(join(root, 'package.json')).resolve('typescript/package.json');
|
|
41
|
+
const tsc = join(dirname(pkg), 'bin', 'tsc');
|
|
42
|
+
return existsSync(tsc) ? { tsc, version: JSON.parse(readFileSync(pkg, 'utf8')).version ?? null } : null;
|
|
43
|
+
} catch { return null; }
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export const NO_TYPESCRIPT = 'build --types checks the games\' TypeScript with the studio\'s own compiler, and this studio has none: run `npm install --save-dev typescript` in the studio, then build --types again (a plain build never needs it)';
|
|
47
|
+
|
|
48
|
+
/** The TypeScript a game's build starts from, or null (a plain-JavaScript game, or one with its own build). */
|
|
49
|
+
function entryOf(g) {
|
|
50
|
+
const mode = g.build?.mode ?? 'bundle';
|
|
51
|
+
if (mode === 'command') return null;
|
|
52
|
+
const entry = mode === 'static' ? g.entry : (g.entry ?? 'src/main.ts');
|
|
53
|
+
return typeof entry === 'string' && /\.(ts|tsx|mts)$/i.test(entry) && existsSync(join(g.dir, entry)) ? entry : null;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Check every game given. Returns { typescript, games: [{ id, checked, with, errors, elsewhere, why }] } when none
|
|
58
|
+
* of them has a type error in its own files; throws (so the command fails and nothing is built) when one has, with
|
|
59
|
+
* the first errors as `file:line:column TS1234 message`, or when the studio has no TypeScript.
|
|
60
|
+
*/
|
|
61
|
+
export function typecheck(root, games, { log = () => {} } = {}) {
|
|
62
|
+
const ts = studioTypescript(root);
|
|
63
|
+
if (!ts) throw new Error(NO_TYPESCRIPT);
|
|
64
|
+
const rows = [];
|
|
65
|
+
const found = [];
|
|
66
|
+
for (const g of games) {
|
|
67
|
+
const entry = entryOf(g);
|
|
68
|
+
if (!entry) { rows.push({ id: g.id, checked: false, why: (g.build?.mode ?? 'bundle') === 'command' ? 'it has a build of its own' : 'no TypeScript entry' }); continue; }
|
|
69
|
+
let project = join(g.dir, 'tsconfig.json');
|
|
70
|
+
const own = existsSync(project);
|
|
71
|
+
if (!own) {
|
|
72
|
+
const dir = join(root, '.studio', 'types', g.id);
|
|
73
|
+
mkdirSync(dir, { recursive: true });
|
|
74
|
+
writeFileSync(join(dir, 'files.d.ts'), AMBIENT);
|
|
75
|
+
project = join(dir, 'tsconfig.json');
|
|
76
|
+
writeFileSync(project, `${JSON.stringify({ compilerOptions: OPTIONS, files: [join(g.dir, entry), join(dir, 'files.d.ts')] }, null, 2)}\n`);
|
|
77
|
+
}
|
|
78
|
+
const res = spawnSync(process.execPath, [ts.tsc, '-p', project, '--noEmit', '--pretty', 'false'], { cwd: root, encoding: 'utf8', timeout: 5 * 60_000, maxBuffer: 64 * 1024 * 1024 });
|
|
79
|
+
if (res.error) throw new Error(`build --types could not run TypeScript ${ts.version ?? ''}: ${res.error.message}`);
|
|
80
|
+
let home = g.dir;
|
|
81
|
+
try { home = realpathSync(g.dir); } catch { /* keep */ }
|
|
82
|
+
const mine = [];
|
|
83
|
+
let elsewhere = 0;
|
|
84
|
+
for (const line of `${res.stdout ?? ''}\n${res.stderr ?? ''}`.split(/\r?\n/)) {
|
|
85
|
+
const at = /^(.+?)\((\d+),(\d+)\): error (TS\d+): (.*)$/.exec(line);
|
|
86
|
+
if (at) {
|
|
87
|
+
let file = isAbsolute(at[1]) ? at[1] : resolve(root, at[1]);
|
|
88
|
+
try { file = realpathSync(file); } catch { /* keep */ }
|
|
89
|
+
if (file.startsWith(`${home}${sep}`)) mine.push(`${relative(root, g.dir).split(sep).join('/')}/${relative(home, file).split(sep).join('/')}:${at[2]}:${at[3]} ${at[4]} ${at[5]}`);
|
|
90
|
+
else elsewhere += 1;
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
// An error with no file is the project itself (a tsconfig.json that does not read): the game's to fix.
|
|
94
|
+
const bare = /^error (TS\d+): (.*)$/.exec(line);
|
|
95
|
+
if (bare) mine.push(`games/${g.id}${own ? '/tsconfig.json' : ''} ${bare[1]} ${bare[2]}`);
|
|
96
|
+
}
|
|
97
|
+
rows.push({ id: g.id, checked: true, with: own ? 'its tsconfig.json' : 'the build\'s own settings', errors: mine.length, elsewhere });
|
|
98
|
+
if (elsewhere) log(`types: games/${g.id}: ${elsewhere} ${elsewhere === 1 ? 'error' : 'errors'} outside the game's own files (a package it imports as source); not the game's, so not counted`);
|
|
99
|
+
if (mine.length) found.push(...mine);
|
|
100
|
+
else log(`types: games/${g.id} has no type errors (TypeScript ${ts.version ?? '?'}, ${own ? 'its tsconfig.json' : 'the build\'s own settings'})`);
|
|
101
|
+
}
|
|
102
|
+
if (found.length) {
|
|
103
|
+
const shown = found.slice(0, 20);
|
|
104
|
+
throw new Error(`${found.length} type ${found.length === 1 ? 'error' : 'errors'} (build --types); nothing was built:\n${shown.map((e) => ` ${e}`).join('\n')}${found.length > shown.length ? `\n … and ${found.length - shown.length} more` : ''}`);
|
|
105
|
+
}
|
|
106
|
+
return { typescript: ts.version, games: rows };
|
|
107
|
+
}
|
package/mcp/ui/lineup.js
CHANGED
|
@@ -21,14 +21,14 @@
|
|
|
21
21
|
var list = C.el('ul', 'list');
|
|
22
22
|
(sc.rows || []).forEach(function (r) {
|
|
23
23
|
var li = C.el('li', r.flags.length ? 'now' : 'done');
|
|
24
|
-
li.appendChild(C.add(C.el('div', 'cb'), C.el('b', '', r.label + (r.size ? ' · ' + r.size[1] + ' m' : '')), C.el('span', '', r.kind + ', ' + r.route + (r.drift ? (r.drift.repaint ? ' · repainted from style.json in the game' : ' · palette distance ' + r.drift.mean) : '')), r.flags.length ? C.el('span', 'fix', r.flags.join(' · ')) : null));
|
|
24
|
+
li.appendChild(C.add(C.el('div', 'cb'), C.el('b', '', r.label + (r.size ? ' · ' + r.size[1] + ' m' : '')), C.el('span', '', r.kind + ', ' + r.route + (r.usage === 'unused' ? ' · unused (not drawn by the game)' : '') + (r.drift ? (r.drift.repaint ? ' · repainted from style.json in the game' : ' · palette distance ' + r.drift.mean) : '')), r.flags.length ? C.el('span', 'fix', r.flags.join(' · ')) : null));
|
|
25
25
|
li.appendChild(C.pill(r.flags.length ? r.flags.length + ' flag' + (r.flags.length === 1 ? '' : 's') : 'Fits', r.flags.length ? 'warn' : 'ok'));
|
|
26
26
|
list.appendChild(li);
|
|
27
27
|
});
|
|
28
28
|
card.appendChild(list);
|
|
29
29
|
if (sc.totals && sc.budgets) {
|
|
30
|
-
card.appendChild(C.el('h2', '', 'Phone budgets
|
|
31
|
-
[bar('Draw calls', sc.totals.drawCalls, sc.budgets.drawCalls), bar('Triangles', sc.totals.triangles, sc.budgets.triangles), bar('Picture memory', sc.totals.textureMB, sc.budgets.textureMB, ' MB'), bar('
|
|
30
|
+
card.appendChild(C.el('h2', '', 'Phone budgets: an estimate from the recorded files'));
|
|
31
|
+
[bar('Draw calls', sc.totals.drawCalls, sc.budgets.drawCalls), bar('Triangles', sc.totals.triangles, sc.budgets.triangles), bar('Picture memory', sc.totals.textureMB, sc.budgets.textureMB, ' MB'), bar('Shipped payload', sc.totals.firstPlayMB, sc.budgets.firstPlayMB, ' MB')].forEach(function (b) { if (b) card.appendChild(b); });
|
|
32
32
|
}
|
|
33
33
|
}
|
|
34
34
|
function make(card, sc) {
|
package/mcp/ui/rights.js
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
/* The rights card: every asset's licence in plain words (asset_rights): where it came from, its licence, the credit it
|
|
2
|
-
* owes
|
|
2
|
+
* owes; what would stop a public game from publishing, with its fix; and RIGHTS.md itself. */
|
|
3
3
|
(function () {
|
|
4
4
|
var C = Card;
|
|
5
|
-
var REMIX = { include: 'A remix gets it', reference: 'A remix fetches it from its origin', none: 'A remix gets a placeholder' };
|
|
6
5
|
var LICENCE = { cc0: 'CC0 (public domain)', 'cc-by-4.0': 'CC BY 4.0', 'cc-by-3.0': 'CC BY 3.0', own: 'Your own work', generated: 'Generated on your account', qal: 'Quaternius licence', mixamo: 'Mixamo', other: 'Other' };
|
|
7
6
|
function render(sc) {
|
|
8
7
|
var card = C.el('main', 'card');
|
|
@@ -12,7 +11,6 @@
|
|
|
12
11
|
(sc.rows || []).forEach(function (r) {
|
|
13
12
|
var li = C.el('li', r.license ? 'done' : 'now');
|
|
14
13
|
li.appendChild(C.add(C.el('div', 'cb'), C.el('b', '', r.id + ' · ' + r.kind), C.el('span', '', (LICENCE[r.license] || r.license || 'NO LICENCE RECORDED') + ' · ' + r.route + (r.from ? ' (' + r.from + ')' : '') + (r.placeholder ? ' · a placeholder' : '')), r.attribution ? C.el('span', 'fix', 'Credit: ' + r.attribution) : null));
|
|
15
|
-
li.appendChild(C.pill(REMIX[r.remix] || 'No remix rule', r.remix === 'include' ? 'ok' : 'warn'));
|
|
16
14
|
list.appendChild(li);
|
|
17
15
|
});
|
|
18
16
|
card.appendChild(list);
|