@genex-ai/cli-demo 1.5.2-dev.397 → 1.6.0-dev.399
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/dist/index.js +288 -10
- package/package.json +1 -1
- package/templates/asset-viewer/asset.config.json +25 -0
- package/templates/asset-viewer/genex-asset.example.json +222 -0
- package/templates/asset-viewer/index.html +200 -0
- package/templates/asset-viewer/package.json +24 -0
- package/templates/asset-viewer/public/fonts/Geist-variable.woff2 +0 -0
- package/templates/asset-viewer/public/fonts/GeistMono-variable.woff2 +0 -0
- package/templates/asset-viewer/shared-files.sha256.json +14 -0
- package/templates/asset-viewer/src/asset/PLACEHOLDER.ts +237 -0
- package/templates/asset-viewer/src/main.js +149 -0
- package/templates/asset-viewer/src/viewer/gates-overlay.js +521 -0
- package/templates/asset-viewer/src/viewer/hud.js +73 -0
- package/templates/asset-viewer/src/viewer/stage.js +760 -0
- package/templates/asset-viewer/tools/emit-manifest.mjs +653 -0
- package/templates/asset-viewer/tools/gates.mjs +682 -0
- package/templates/asset-viewer/tools/stamp-manifest.mjs +134 -0
- package/templates/asset-viewer/vite.config.js +24 -0
- package/templates/skills/genex-asset-author/SKILL.md +101 -0
- package/templates/skills/genex-game-director/references/routing-map.md +1 -0
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
// Phase 2 of the manifest: rewrite dist/genex-asset.json in place with the
|
|
2
|
+
// measured geometry, the measured space and the gate block, so what ships is
|
|
3
|
+
// what was actually measured rather than what the author claimed.
|
|
4
|
+
//
|
|
5
|
+
// Refuses to stamp a stale report (one taken against different source), and
|
|
6
|
+
// re-validates the finished bytes - a build whose manifest cannot offer the two
|
|
7
|
+
// dashboard buttons must not reach a deploy.
|
|
8
|
+
//
|
|
9
|
+
// exit 0 stamped
|
|
10
|
+
// exit 1 refused (stale, failing, or invalid)
|
|
11
|
+
//
|
|
12
|
+
// Copied verbatim into every asset project and hashed by gate G13.
|
|
13
|
+
//
|
|
14
|
+
// Usage: node tools/stamp-manifest.mjs [--dist dist] [--report .gates/report.json]
|
|
15
|
+
// [--play-url <url>] [--allow-failed]
|
|
16
|
+
|
|
17
|
+
import fs from "node:fs";
|
|
18
|
+
import fsp from "node:fs/promises";
|
|
19
|
+
import path from "node:path";
|
|
20
|
+
import { fileURLToPath } from "node:url";
|
|
21
|
+
|
|
22
|
+
import {
|
|
23
|
+
MANIFEST_FILENAME,
|
|
24
|
+
MANIFEST_MAX_BYTES,
|
|
25
|
+
defaultPlayUrl,
|
|
26
|
+
readAssetConfig,
|
|
27
|
+
validateManifest,
|
|
28
|
+
} from "./emit-manifest.mjs";
|
|
29
|
+
|
|
30
|
+
const PROJECT_DIR = path.resolve(fileURLToPath(new URL("..", import.meta.url)));
|
|
31
|
+
|
|
32
|
+
function parseArgs(argv) {
|
|
33
|
+
const args = { dist: "dist", report: path.join(".gates", "report.json"), playUrl: null, allowFailed: false };
|
|
34
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
35
|
+
const a = argv[i];
|
|
36
|
+
if (a === "--dist") args.dist = argv[++i];
|
|
37
|
+
else if (a === "--report") args.report = argv[++i];
|
|
38
|
+
else if (a === "--play-url") args.playUrl = argv[++i];
|
|
39
|
+
else if (a === "--allow-failed") args.allowFailed = true;
|
|
40
|
+
else if (a === "--help" || a === "-h") args.help = true;
|
|
41
|
+
else throw new Error(`unknown flag: ${a}`);
|
|
42
|
+
}
|
|
43
|
+
return args;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async function main() {
|
|
47
|
+
const args = parseArgs(process.argv.slice(2));
|
|
48
|
+
if (args.help) {
|
|
49
|
+
console.log("usage: node tools/stamp-manifest.mjs [--dist dist] [--report .gates/report.json] [--play-url <url>] [--allow-failed]");
|
|
50
|
+
return 0;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const distDir = path.resolve(PROJECT_DIR, args.dist);
|
|
54
|
+
const manifestPath = path.join(distDir, MANIFEST_FILENAME);
|
|
55
|
+
const reportPath = path.resolve(PROJECT_DIR, args.report);
|
|
56
|
+
|
|
57
|
+
if (!fs.existsSync(manifestPath)) throw new Error(`no ${MANIFEST_FILENAME} in ${args.dist} - run \`npm run build\` first`);
|
|
58
|
+
if (!fs.existsSync(reportPath)) throw new Error(`no gate report at ${args.report} - run \`npm run gates\` first`);
|
|
59
|
+
|
|
60
|
+
const config = readAssetConfig(PROJECT_DIR);
|
|
61
|
+
const manifest = JSON.parse(await fsp.readFile(manifestPath, "utf8"));
|
|
62
|
+
const report = JSON.parse(await fsp.readFile(reportPath, "utf8"));
|
|
63
|
+
|
|
64
|
+
// A report taken against different source text describes a different asset.
|
|
65
|
+
if (report.sourceSha256 !== manifest.source.sha256) {
|
|
66
|
+
throw new Error(
|
|
67
|
+
`gate report is stale: it was run against source ${String(report.sourceSha256).slice(0, 12)}… but dist holds ${manifest.source.sha256.slice(0, 12)}… - rebuild and re-run the gates`,
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
if (!report.allPassed && !args.allowFailed) {
|
|
71
|
+
const failed = (report.results ?? []).filter((r) => !r.passed).map((r) => r.id);
|
|
72
|
+
throw new Error(`gates failed (${failed.join(", ")}) - fix them, or pass --allow-failed to stamp a build that says so`);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const stamped = {
|
|
76
|
+
...manifest,
|
|
77
|
+
geometry: report.geometry,
|
|
78
|
+
space: {
|
|
79
|
+
...manifest.space,
|
|
80
|
+
boundingBox: report.space.boundingBox,
|
|
81
|
+
sizeMeters: report.space.sizeMeters,
|
|
82
|
+
statedSizeMeters: config.statedSizeMeters,
|
|
83
|
+
sizeDeltaPct: report.space.sizeDeltaPct,
|
|
84
|
+
},
|
|
85
|
+
gates: {
|
|
86
|
+
runAt: report.runAt,
|
|
87
|
+
runner: report.runner,
|
|
88
|
+
allPassed: Boolean(report.allPassed),
|
|
89
|
+
results: (report.results ?? []).map(({ id, name, threshold, measured, passed }) => ({
|
|
90
|
+
id,
|
|
91
|
+
name,
|
|
92
|
+
threshold,
|
|
93
|
+
measured,
|
|
94
|
+
passed,
|
|
95
|
+
})),
|
|
96
|
+
},
|
|
97
|
+
preview: {
|
|
98
|
+
...manifest.preview,
|
|
99
|
+
playUrl: args.playUrl ?? manifest.preview?.playUrl ?? defaultPlayUrl(config.slug),
|
|
100
|
+
},
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
const body = JSON.stringify(stamped, null, 2) + "\n";
|
|
104
|
+
const bytes = Buffer.byteLength(body, "utf8");
|
|
105
|
+
const shape = validateManifest(stamped, { bytes });
|
|
106
|
+
if (!shape.ok) throw new Error(`refusing to stamp an invalid manifest:\n - ${shape.errors.join("\n - ")}`);
|
|
107
|
+
if (bytes > MANIFEST_MAX_BYTES) {
|
|
108
|
+
throw new Error(`refusing to stamp: ${bytes} bytes exceeds the ${MANIFEST_MAX_BYTES} byte ceiling`);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
await fsp.writeFile(manifestPath, body, "utf8");
|
|
112
|
+
|
|
113
|
+
console.log(
|
|
114
|
+
[
|
|
115
|
+
"",
|
|
116
|
+
` stamped ${path.relative(PROJECT_DIR, manifestPath)} (${(bytes / 1024).toFixed(1)} KB)`,
|
|
117
|
+
` ${stamped.geometry.triangles.toLocaleString("en-US")} tris · ${stamped.geometry.drawCalls} draws · ${stamped.geometry.materials} materials`,
|
|
118
|
+
` ${stamped.space.sizeMeters.map((n) => n.toFixed(2)).join(" × ")} m`,
|
|
119
|
+
` gates: ${stamped.gates.allPassed ? `all ${stamped.gates.results.length} passed` : `${stamped.gates.results.filter((r) => !r.passed).length} FAILED`}`,
|
|
120
|
+
` preview: ${stamped.preview.playUrl}`,
|
|
121
|
+
"",
|
|
122
|
+
].join("\n"),
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
return 0;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
main().then(
|
|
129
|
+
(code) => process.exit(code),
|
|
130
|
+
(error) => {
|
|
131
|
+
console.error(`\n[stamp] ${error?.message ?? error}\n`);
|
|
132
|
+
process.exit(1);
|
|
133
|
+
},
|
|
134
|
+
);
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// Shared viewer build config. Copied verbatim into every asset project and
|
|
2
|
+
// hashed by gate G13 - edit it in packages/asset-viewer-template/template.
|
|
3
|
+
import { defineConfig } from "vite";
|
|
4
|
+
import { emitManifest } from "./tools/emit-manifest.mjs";
|
|
5
|
+
|
|
6
|
+
export default defineConfig({
|
|
7
|
+
// Games serve at the domain root, but relative keeps the build origin-agnostic.
|
|
8
|
+
base: "./",
|
|
9
|
+
build: {
|
|
10
|
+
// `dist` is the ONLY outDir name the Genex CLI's deployGame recognises;
|
|
11
|
+
// anything else uploads the whole source folder instead of the build.
|
|
12
|
+
outDir: "dist",
|
|
13
|
+
// Fingerprinted chunks opt into the serve worker's immutable cache dir.
|
|
14
|
+
// dist/genex-asset.json deliberately stays outside it, so a republish is
|
|
15
|
+
// always picked up.
|
|
16
|
+
assetsDir: "_immutable",
|
|
17
|
+
emptyOutDir: true,
|
|
18
|
+
target: "es2022",
|
|
19
|
+
},
|
|
20
|
+
server: {
|
|
21
|
+
port: Number(process.env.PORT) || 5190,
|
|
22
|
+
},
|
|
23
|
+
plugins: [emitManifest()],
|
|
24
|
+
});
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: genex-asset-author
|
|
3
|
+
description: Author and publish ONE asset to the free Genex asset library via `genex asset new` — STRICTLY user-initiated. Use ONLY when the user explicitly asks to publish something from their game as an asset, or to create a standalone asset for the library. Never suggest, offer, or trigger this lane on your own — building a game never requires it.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Genex Asset Author
|
|
7
|
+
|
|
8
|
+
Ship one procedural Three.js asset to the free, MIT-licensed Genex asset
|
|
9
|
+
library: a standalone project whose build is a uniform orbitable viewer, whose
|
|
10
|
+
only varying file is ONE self-contained TypeScript factory module, and whose
|
|
11
|
+
quality bar is 17 machine-checked gates.
|
|
12
|
+
|
|
13
|
+
## When this lane runs — and when it must not
|
|
14
|
+
|
|
15
|
+
Run it ONLY when the user explicitly asks — the two real shapes:
|
|
16
|
+
|
|
17
|
+
- **"Publish this <thing> from my game as an asset"** — extract an object out
|
|
18
|
+
of the game they're building.
|
|
19
|
+
- **"I want to make an asset / assets"** — asset-first authoring.
|
|
20
|
+
|
|
21
|
+
NEVER offer this unprompted. Finishing a game, noticing a nice prop, or
|
|
22
|
+
wanting to showcase work is NOT a trigger; the user's request is. If they ask
|
|
23
|
+
what publishing an asset means, explain (free, MIT, copyable by anyone from the
|
|
24
|
+
dashboard, listed under the gallery's Assets tab) and let them decide.
|
|
25
|
+
|
|
26
|
+
## The one non-negotiable before publish: MIT with THEIR name
|
|
27
|
+
|
|
28
|
+
A published asset gets a **Copy code** button on its page — anyone can take the
|
|
29
|
+
file. Publishing = releasing it under MIT. Before scaffolding, ask the user for
|
|
30
|
+
their attribution name and pass it as `--holder "<name>"`. If it was skipped,
|
|
31
|
+
set it before publish in BOTH places: `asset.config.json` → `license.holder`,
|
|
32
|
+
and line 2 of the module (the MIT header). Never publish with the placeholder.
|
|
33
|
+
|
|
34
|
+
## Scaffold
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
npx genex asset new <kebab-slug> --title "<Display Name>" \
|
|
38
|
+
--dims WxDxH --tri-band LOW-HIGH --holder "<user's name>"
|
|
39
|
+
cd <kebab-slug>
|
|
40
|
+
npm install
|
|
41
|
+
npx playwright-core install chromium # once per machine
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
One asset = one project = one turntable page. Several assets → run the command
|
|
45
|
+
once per asset. `--dims` is width x depth x height in metres as a human states
|
|
46
|
+
it (gate G4 measures the built model against it ±5%); tune it and the config
|
|
47
|
+
honestly to the real object before gating, not to whatever the code produced.
|
|
48
|
+
|
|
49
|
+
The viewer is hash-locked: never edit `index.html`, `src/viewer/*`, `src/main.js`
|
|
50
|
+
or `tools/*` — gate G13 fails the build if you do. Your entire surface is
|
|
51
|
+
`src/asset/<name>.ts` and `asset.config.json`.
|
|
52
|
+
|
|
53
|
+
## The module contract
|
|
54
|
+
|
|
55
|
+
`export function create<Name>Model(options): THREE.Group` — one self-contained
|
|
56
|
+
file:
|
|
57
|
+
|
|
58
|
+
- Core `three` ONLY. No `three/examples/jsm`, no `ShaderMaterial`, no
|
|
59
|
+
`CanvasTexture` (needs a DOM), no `fetch`, no external URL anywhere in the
|
|
60
|
+
file (gate G11 scans for `http` — comments included).
|
|
61
|
+
- Real metres, Y-up, +Z forward, origin at base centre (y = 0).
|
|
62
|
+
- Seeded determinism: same seed + options → identical vertex data (G10).
|
|
63
|
+
- `userData.dispose()` returns the renderer to baseline (G12) — every
|
|
64
|
+
geometry, material and texture you create is released.
|
|
65
|
+
- Textures are in-code `DataTexture`s; `MeshStandardMaterial` for surfaces.
|
|
66
|
+
- Keep the 4-line header (name / MIT + holder / units / requires) — it is what
|
|
67
|
+
makes a copied file self-describing.
|
|
68
|
+
|
|
69
|
+
Extracting from a game: COPY the code out (the game keeps its own), then strip
|
|
70
|
+
every game dependency, re-origin to base centre, convert to real metres, seed
|
|
71
|
+
the randomness, and add the dispose contract. The result must run in a project
|
|
72
|
+
holding nothing but `three` — that is literally gate G8 (paste-and-run).
|
|
73
|
+
|
|
74
|
+
## Gate loop
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
npm run verify # vite build + 17 gates + manifest stamp, in one command
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Iterate until 17/17. Each failure names its gate and what it measured. The
|
|
81
|
+
gates are the design bar of the Assets tab — never loosen `asset.config.json`
|
|
82
|
+
ceilings to sneak a failing model through; fix the model.
|
|
83
|
+
|
|
84
|
+
Two measured traps: metalness 1.0 needs the viewer's environment map to read
|
|
85
|
+
correctly — it renders BLACK in a game with no envmap, so prefer 0.2-0.9 for
|
|
86
|
+
believable metals; and a mirrored basis can pass every numeric gate while
|
|
87
|
+
being silently inside-out — LOOK at the turntable from several angles before
|
|
88
|
+
calling it done.
|
|
89
|
+
|
|
90
|
+
## Ship
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
npx genex init # create the project (sign-in if needed)
|
|
94
|
+
npx genex preview --no-build # staging turntable — show the user this link
|
|
95
|
+
npx genex publish --no-build --categories assets # only after the user says ship it
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
`--no-build` matters: `npm run verify` already built AND stamped the manifest
|
|
99
|
+
into `dist/` — a plain preview/publish would rebuild and ship an unstamped
|
|
100
|
+
manifest, and the dashboard's Copy-code button reads that manifest. Publish
|
|
101
|
+
ONLY on the user's explicit go, with the licence holder set to their real name.
|
|
@@ -67,6 +67,7 @@ copy demo architecture.
|
|
|
67
67
|
| players talking to each other by TEXT — chat, room chat, "let them type to each other" (`room.chat` + `npx genex controller chat`) | `$genex-threejs-multiplayer` |
|
|
68
68
|
| players TALKING — voice chat, mic, "hear each other", proximity/positional voice (`room.voice` + `npx genex controller voice`; party-sized, max 6 per room — say that number out loud before building the feature) | `$genex-threejs-multiplayer` |
|
|
69
69
|
| player identity, sign-in, guests, saves/progress, per-player state, shared persistent world, leaderboards—mandatory for every game | `$genex-threejs-embed-auth` |
|
|
70
|
+
| the player EXPLICITLY asks to make an asset or publish something from their game as an asset for the free library | `$genex-asset-author` (`genex asset new`) — never offer this unprompted; no game duty routes here |
|
|
70
71
|
|
|
71
72
|
## Request-sized execution
|
|
72
73
|
|