@genex-ai/cli-demo 0.78.0-dev.200 → 0.78.1-dev.203
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/package.json +1 -1
- package/templates/controllers/quality/pick-asset.ts +12 -7
- package/templates/skills/genex-ai-hud/SKILL.md +26 -7
- package/templates/skills/genex-ai-menu/SKILL.md +4 -2
- package/templates/skills/genex-ai-model/SKILL.md +52 -7
- package/templates/skills/genex-threejs-adaptive-quality/SKILL.md +20 -7
- package/templates/skills/genex-threejs-adaptive-quality/references/adaptive-quality.md +16 -4
- package/templates/skills/genex-threejs-game-ui/SKILL.md +99 -46
- package/templates/skills/genex-threejs-game-ui/references/style-capsules.md +3 -2
- package/templates/skills/genex-threejs-lighting-design/SKILL.md +7 -0
- package/templates/skills/genex-threejs-skill-router/SKILL.md +8 -5
- package/templates/skills/genex-threejs-skill-router/references/routing-map.md +37 -21
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@genex-ai/cli-demo",
|
|
3
|
-
"version": "0.78.
|
|
3
|
+
"version": "0.78.1-dev.203",
|
|
4
4
|
"description": "Set up your project's agent workspace (.claude/.codex/.cursor in the game folder), authorize, create a game project, generate AI assets, and publish (genex CLI).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -80,26 +80,31 @@ export async function loadTextureWithFallback<T>(
|
|
|
80
80
|
// Generated MODELS (mesh-compression lane). GLB rungs use the same numeric
|
|
81
81
|
// suffix — `model-glb@1024` = embedded textures ≤1024 + meshopt (+simplify) —
|
|
82
82
|
// but unlike images there is NO edge rewrite for old games: selection happens
|
|
83
|
-
// ONLY here, in games that wired the decoders (createGltfLoader).
|
|
84
|
-
//
|
|
83
|
+
// ONLY here, in games that wired the decoders (createGltfLoader). EVERY tier
|
|
84
|
+
// loads a game-ready rung — provider-raw originals are archival/remix source,
|
|
85
|
+
// not game assets (a Tripo prop is ~500k tris + 3x4096² textures; a scene of
|
|
86
|
+
// them floored an M4 Max). Desktop gets @2048, phones @1024; the original is
|
|
87
|
+
// only ever the fallback of last resort.
|
|
85
88
|
|
|
86
|
-
|
|
89
|
+
/** Per-tier texture budget: desktop tiers @2048, phone tiers @1024. */
|
|
90
|
+
function modelBudgetFor(tier: QualityTier): number {
|
|
91
|
+
return tier.name === "phone" || tier.name === "phone-low" ? 1024 : 2048;
|
|
92
|
+
}
|
|
87
93
|
const MODEL_ROLE_RE = /^(model-glb|character-rigged(-a\d+)?-glb(-r\d+)?)$/;
|
|
88
94
|
|
|
89
95
|
/** Resolve the model URL a THIS-tier device should load. `ktx2: true` (from
|
|
90
|
-
* createGltfLoader) upgrades
|
|
96
|
+
* createGltfLoader) upgrades to the GPU-compressed sibling. */
|
|
91
97
|
export function pickModel(url: string, tier: QualityTier, opts?: { ktx2?: boolean }): string {
|
|
92
98
|
if (!GENEX_GENERATIONS_RE.test(url)) return url;
|
|
93
99
|
const role = url.split("/").pop() ?? "";
|
|
94
100
|
if (!MODEL_ROLE_RE.test(role)) return url;
|
|
95
|
-
|
|
96
|
-
const rung = `${url}@${MODEL_TEXTURE_BUDGET}`;
|
|
101
|
+
const rung = `${url}@${modelBudgetFor(tier)}`;
|
|
97
102
|
return opts?.ktx2 ? `${rung}.ktx2` : rung;
|
|
98
103
|
}
|
|
99
104
|
|
|
100
105
|
/**
|
|
101
106
|
* Load a generated model through the rung ladder with the full fallback chain
|
|
102
|
-
* (`.ktx2` →
|
|
107
|
+
* (`.ktx2` → the tier's rung → original). Use with the decoder-wired loader:
|
|
103
108
|
*
|
|
104
109
|
* const gltf = createGltfLoader(renderer);
|
|
105
110
|
* const model = await loadModelWithFallback(MODEL_URL, tier, (u) => gltf.loader.loadAsync(u), { ktx2: gltf.ktx2 });
|
|
@@ -35,6 +35,13 @@ ring) that no hand-written CSS can fake.
|
|
|
35
35
|
(`background: rgba(<darkest hue>, 0.35–0.6)` + `backdrop-filter: blur(6–12px)`
|
|
36
36
|
and its `-webkit-` twin) when the style wants translucency, a solid painted
|
|
37
37
|
plate, a subtle gradient — or no plate at all for an outline-led look.
|
|
38
|
+
**A plate exists only where the widget's own art doesn't already back it.**
|
|
39
|
+
Behind an OPAQUE frame sprite there is no plate — the frame IS the backing
|
|
40
|
+
surface, and a dark div stacked behind it only protrudes as a box over the
|
|
41
|
+
scene (the recurring black-box defect). Where a plate IS needed — bare DOM
|
|
42
|
+
readouts, thin-outline widgets — it stays WITHIN the widget's silhouette:
|
|
43
|
+
inset inside the frame, or shaped to it, never a full rectangle spilling
|
|
44
|
+
past the art onto the scene.
|
|
38
45
|
**Glass is a technique, not a default** — do not reach for translucency
|
|
39
46
|
because this skill mentions it; reach for it when THIS game's brief does.
|
|
40
47
|
What is fixed is WHERE plates are built — the DOM — because
|
|
@@ -44,7 +51,9 @@ ring) that no hand-written CSS can fake.
|
|
|
44
51
|
corners with `border-radius` or a clean `clip-path`/`mask` shape — a CSS
|
|
45
52
|
chamfer/notch is allowed, as long as the cut never clips the plate's
|
|
46
53
|
content (text, padding, a glow), leaves no jagged-edge artifacts, and
|
|
47
|
-
nothing ends up crooked
|
|
54
|
+
nothing ends up crooked — and a plain `border`/`box-shadow` won't follow a
|
|
55
|
+
`clip-path` cut, so use `$genex-threejs-game-ui`'s two-layer chamfer-border
|
|
56
|
+
recipe when a chamfer needs a frame; a genuinely ornamented angular frame is still
|
|
48
57
|
best as chrome, generated. The load-bearing `mask`/`clip-path` in this
|
|
49
58
|
skill is the masked-fill reveal below.
|
|
50
59
|
- **Chrome (sprites — what THIS pipeline generates).** Opaque frames, corner
|
|
@@ -105,7 +114,10 @@ The HUD overlays a **live 3D scene**. The pixels between widgets show the game.
|
|
|
105
114
|
burns generations.
|
|
106
115
|
- **Every widget needs internal contrast** — a panel fill, outline stroke, or
|
|
107
116
|
semi-opaque backdrop — because the scene behind it might be a snowfield or a
|
|
108
|
-
torchlit dungeon. Pure-outline widgets over arbitrary scenes fail.
|
|
117
|
+
torchlit dungeon. Pure-outline widgets over arbitrary scenes fail. An opaque
|
|
118
|
+
frame sprite already IS that contrast; don't stack a plate behind it. A
|
|
119
|
+
needed backdrop lives inside the widget's silhouette, never as a rectangle
|
|
120
|
+
spilling past the art (see the plate rule under "three layers").
|
|
109
121
|
- **The mockup must be flat and head-on** — a 2D screen-space overlay, never
|
|
110
122
|
tilted, isometric, or in perspective. Angled panels have no clean silhouette
|
|
111
123
|
and deconstruct into skewed slices. If the mockup comes back tilted,
|
|
@@ -203,13 +215,18 @@ and [references/stage2-prompt-template.md](references/stage2-prompt-template.md)
|
|
|
203
215
|
gate — the Stage-1 mockup IS the game concept.** There is no separate
|
|
204
216
|
UI-free concept image before it: this one generation carries scene + HUD,
|
|
205
217
|
serves as the user's style checkpoint, and anchors all later art
|
|
206
|
-
(`$genex-threejs-game-ui` owns the checkpoint choreography).
|
|
218
|
+
(`$genex-threejs-game-ui` owns the checkpoint choreography). **Stage 2 onward
|
|
219
|
+
is frame-anchored, so it waits for the user's keep/change YES on that
|
|
220
|
+
checkpoint — kick it off the instant they approve, not before.** The scene half
|
|
207
221
|
of the prompt is written in TEXT from the game plan — `[GAME_SCENE]` in the
|
|
208
222
|
Stage-1 template: setting, the moment, what the player is doing, lighting —
|
|
209
223
|
never `--edit`-anchored to a prior image (there is none). Then write the
|
|
210
224
|
widget layout and wiring code (placement, masked-fill scaffolding,
|
|
211
225
|
plain-CSS placeholder bars) while it renders — the CSS HUD keeps
|
|
212
|
-
the game playable until the sprites land.
|
|
226
|
+
the game playable until the sprites land. When a sprite lands it REPLACES
|
|
227
|
+
its placeholder: delete the placeholder's own background/plate as you wire
|
|
228
|
+
the opaque frame in — a placeholder dark div left behind an opaque sprite
|
|
229
|
+
is the black-box defect. The mockup is a STYLE anchor
|
|
213
230
|
only: the widget set and
|
|
214
231
|
layout come from the element inventory and the game contract — a mechanic
|
|
215
232
|
the mockup invented (a lap counter, a stamina orb) does not enter the HUD,
|
|
@@ -311,11 +328,13 @@ To fix ONE sprite, don't re-run the pipeline:
|
|
|
311
328
|
painted ON TOP. Never put the gradient on the scaling fill itself — its `%`
|
|
312
329
|
stops anchor to the fill's own width and the segments visibly compress as
|
|
313
330
|
the value drops.
|
|
314
|
-
- **
|
|
315
|
-
`background: rgba(10, 8, 6, 0.6)` rounded div behind the text
|
|
331
|
+
- **A numeric readout sitting as bare text over the scene gets a backing
|
|
332
|
+
plate** — a `background: rgba(10, 8, 6, 0.6)` rounded div behind the text, an
|
|
316
333
|
extracted plate sprite when the art has one, or a deliberate skip documented
|
|
317
334
|
in a comment (`/* no plate — reads against <reason> */`). Bare digits look
|
|
318
|
-
fine over the dev background and vanish over a bright scene.
|
|
335
|
+
fine over the dev background and vanish over a bright scene. A readout
|
|
336
|
+
already sitting on an opaque frame sprite needs NO separate plate — the frame
|
|
337
|
+
backs it; a second dark div only boxes it (the no-double-backing rule above).
|
|
319
338
|
|
|
320
339
|
## Discrete vs continuous — classify every meter before Stage 2
|
|
321
340
|
|
|
@@ -173,8 +173,10 @@ Pause both videos when the menu phase hides (they're decode work), and
|
|
|
173
173
|
resume the front one when it returns. Watch ONE full cycle in the browser
|
|
174
174
|
before calling the menu done — that's the seam check.
|
|
175
175
|
|
|
176
|
-
**Work async — the menu must never block the game.**
|
|
177
|
-
|
|
176
|
+
**Work async — the menu must never block the game.** The still is
|
|
177
|
+
`--edit`-anchored to the concept mockup, so it's style-dependent: generate it
|
|
178
|
+
the moment the user approves the concept (not before), enqueue the video with
|
|
179
|
+
`--no-wait`, ship the CSS menu
|
|
178
180
|
(buttons + title over the frame IMAGE as a static backdrop), and swap the
|
|
179
181
|
`<video>` in when `genex wait` prints the URL. The frame image doubles as the
|
|
180
182
|
**loading screen background** — it exists minutes before the video does (see
|
|
@@ -67,11 +67,12 @@ side — reads as broken at a glance.
|
|
|
67
67
|
|
|
68
68
|
Load through the quality kit's decoder-wired loader and the model rung ladder
|
|
69
69
|
(`$genex-threejs-adaptive-quality`; `npx genex controller quality` installs
|
|
70
|
-
it). The bare URL is the provider-raw original —
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
70
|
+
it). The bare URL is the provider-raw original — ~500k triangles + several 4K
|
|
71
|
+
PBR textures, archival/remix source, NOT a game asset (31 raw props once
|
|
72
|
+
floored an M4 Max). Every model ships a game-ready ladder instead: desktop
|
|
73
|
+
tiers load the `@2048` rung, phones the `@1024` rung (both meshopt-compressed
|
|
74
|
+
and simplified), and KTX2-capable games get the GPU-compressed sibling. R2
|
|
75
|
+
sends the right CORS headers, so cross-origin loading just works:
|
|
75
76
|
|
|
76
77
|
```ts
|
|
77
78
|
import { createGltfLoader } from "./controllers/quality/gltf-loader.ts";
|
|
@@ -86,6 +87,16 @@ const gltf = await loadModelWithFallback(
|
|
|
86
87
|
MODEL_URL, tier, (u) => gltfLoader.loader.loadAsync(u), { ktx2: gltfLoader.ktx2 },
|
|
87
88
|
);
|
|
88
89
|
const model = gltf.scene;
|
|
90
|
+
// Clamp the provider's mirror-metal PBR (metalness~1 reflects the whole sky
|
|
91
|
+
// env and swims with every camera move):
|
|
92
|
+
model.traverse((o) => {
|
|
93
|
+
const m = (o as THREE.Mesh).material as THREE.MeshStandardMaterial;
|
|
94
|
+
if (m?.isMeshStandardMaterial) {
|
|
95
|
+
m.metalness = Math.min(m.metalness, 0.6);
|
|
96
|
+
m.roughness = Math.max(m.roughness, 0.35);
|
|
97
|
+
m.envMapIntensity = 0.6;
|
|
98
|
+
}
|
|
99
|
+
});
|
|
89
100
|
model.scale.setScalar(1); // tune to taste
|
|
90
101
|
model.position.set(0, 0, 0);
|
|
91
102
|
scene.add(model);
|
|
@@ -100,8 +111,42 @@ Never swallow a failed load silently: if you wrap a generated-asset load in a
|
|
|
100
111
|
`catch`, `console.warn` the asset name in it — your own self-check reads the
|
|
101
112
|
console, and a bare `.catch(() => null)` hides a missing model from you too.
|
|
102
113
|
|
|
103
|
-
|
|
104
|
-
|
|
114
|
+
**Copies**: one or two, `model.clone()` (clones share geometry/textures on the
|
|
115
|
+
GPU). **Three or more of the same model → `InstancedMesh`** — clones still cost
|
|
116
|
+
one draw call per mesh per copy; instancing renders all copies in one draw
|
|
117
|
+
call per source mesh:
|
|
118
|
+
|
|
119
|
+
```ts
|
|
120
|
+
// placements: THREE.Matrix4[] — one world transform per copy
|
|
121
|
+
const group = new THREE.Group();
|
|
122
|
+
gltf.scene.updateMatrixWorld(true);
|
|
123
|
+
const tmp = new THREE.Matrix4();
|
|
124
|
+
gltf.scene.traverse((o) => {
|
|
125
|
+
const src = o as THREE.Mesh;
|
|
126
|
+
if (!src.isMesh) return;
|
|
127
|
+
const im = new THREE.InstancedMesh(src.geometry, src.material, placements.length);
|
|
128
|
+
placements.forEach((p, i) => im.setMatrixAt(i, tmp.multiplyMatrices(p, src.matrixWorld)));
|
|
129
|
+
im.instanceMatrix.needsUpdate = true;
|
|
130
|
+
im.castShadow = im.receiveShadow = true;
|
|
131
|
+
group.add(im);
|
|
132
|
+
});
|
|
133
|
+
scene.add(group);
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
**Distance LOD** (open worlds / long sightlines): the rung ladder already ships
|
|
137
|
+
a lighter mesh — use the phone rung as the far level. One extra fetch; worth it
|
|
138
|
+
past ~25 m draw ranges, skip it in small arenas:
|
|
139
|
+
|
|
140
|
+
```ts
|
|
141
|
+
const far = await gltfLoader.loader.loadAsync(`${MODEL_URL}@1024`);
|
|
142
|
+
const lod = new THREE.LOD();
|
|
143
|
+
lod.addLevel(gltf.scene, 0); // near: the tier's rung
|
|
144
|
+
lod.addLevel(far.scene, 25); // far: phone rung — three swaps by camera distance
|
|
145
|
+
scene.add(lod);
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
For animated GLBs, drive `gltf.animations` with a `THREE.AnimationMixer`
|
|
149
|
+
(animated/skinned meshes can't instance — clone those).
|
|
105
150
|
|
|
106
151
|
**If the game has physics, give the model a collider** — a GLB added only to the
|
|
107
152
|
scene is a ghost: players and objects pass straight through it.
|
|
@@ -112,11 +112,13 @@ and `$genex-ai-texture` skills show the wiring in place.
|
|
|
112
112
|
|
|
113
113
|
## Generated models: load through the rungs
|
|
114
114
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
115
|
+
Provider-raw GLBs are NOT game assets: one prop is ~500k triangles + several
|
|
116
|
+
4K PBR textures (a pilot scene of them submitted 66M triangles/frame and
|
|
117
|
+
floored an M4 Max). Every model ships a game-ready ladder — `@2048` (the
|
|
118
|
+
desktop rung) and `@1024` (the phone rung), both meshopt-compressed and
|
|
119
|
+
simplified where safe, each with a `.ktx2` sibling whose textures stay
|
|
118
120
|
compressed ON the GPU (~6× less texture VRAM). A plain `GLTFLoader` can decode
|
|
119
|
-
|
|
121
|
+
none of them — wire the decoders once and load through the ladder:
|
|
120
122
|
|
|
121
123
|
```ts
|
|
122
124
|
import { createGltfLoader } from "./controllers/quality/gltf-loader.ts";
|
|
@@ -126,11 +128,22 @@ const gltfLoader = createGltfLoader(renderer); // meshopt always; KTX2 when the
|
|
|
126
128
|
const gltf = await loadModelWithFallback(
|
|
127
129
|
MODEL_URL, tier, (u) => gltfLoader.loader.loadAsync(u), { ktx2: gltfLoader.ktx2 },
|
|
128
130
|
);
|
|
131
|
+
// Provider PBR ships mirror-metal (metalness~1) that reflects the sky env and
|
|
132
|
+
// swims with camera motion — clamp it on every loaded model:
|
|
133
|
+
gltf.scene.traverse((o) => {
|
|
134
|
+
const m = (o as THREE.Mesh).material as THREE.MeshStandardMaterial;
|
|
135
|
+
if (m?.isMeshStandardMaterial) {
|
|
136
|
+
m.metalness = Math.min(m.metalness, 0.6);
|
|
137
|
+
m.roughness = Math.max(m.roughness, 0.35);
|
|
138
|
+
m.envMapIntensity = 0.6;
|
|
139
|
+
}
|
|
140
|
+
});
|
|
129
141
|
```
|
|
130
142
|
|
|
131
|
-
Fallback chain: `.ktx2` rung → universal `@
|
|
132
|
-
|
|
133
|
-
|
|
143
|
+
Fallback chain: `.ktx2` rung → the tier's universal rung (`@2048` desktop,
|
|
144
|
+
`@1024` phones) → original — each step warns; the original is archival/remix
|
|
145
|
+
source, fetched only as the fallback of last resort. Meshy characters take
|
|
146
|
+
the same ladder through
|
|
134
147
|
`loadMeshyCharacter(manifestUrl, { loader: gltfLoader.loader, modelUrlCandidates: (u) => [pickModel(u, tier, { ktx2: gltfLoader.ktx2 }), pickModel(u, tier), u] })`.
|
|
135
148
|
KTX2-capable games can also pass `ktx2Load` to `loadTextureWithFallback` so
|
|
136
149
|
skyboxes/textures use their `.ktx2` variants.
|
|
@@ -22,7 +22,7 @@ cost of guessing high is a dead page.
|
|
|
22
22
|
| Shadow map | 512 (static-cached) | 1024 | 1024 | 2048 |
|
|
23
23
|
| Post level | tone map only | + FXAA/vignette | + FXAA/vignette | full named stack |
|
|
24
24
|
| Skybox rung | @2048 (~11 MB) | @4096 (~45 MB) | original | original |
|
|
25
|
-
| Model rung | @1024 (+.ktx2 when wired) | @1024 (+.ktx2 when wired) |
|
|
25
|
+
| Model rung | @1024 (+.ktx2 when wired) | @1024 (+.ktx2 when wired) | @2048 (+.ktx2) | @2048 (+.ktx2) |
|
|
26
26
|
| Texture rung (props) | @1024 | @2048 | original | original |
|
|
27
27
|
| Particles/scatter | 0.25× | 0.5× | 0.75× | 1× |
|
|
28
28
|
| Draw distance | 0.5× | 0.75× | 1× | 1× |
|
|
@@ -69,6 +69,12 @@ Getting this wrong produces silent no-ops or a session stuck ugly:
|
|
|
69
69
|
fog, LOD bias, particle counts, mixer update rates, frame cap, remote-avatar
|
|
70
70
|
animation count. Light `intensity` is runtime-free; light COUNT is not (see
|
|
71
71
|
above).
|
|
72
|
+
- **Shadow maps of STATIC lights render once, not per frame:** shadow maps
|
|
73
|
+
default to re-rendering every frame, and a PointLight's is a 6-face cube —
|
|
74
|
+
four static shadow lanterns = 24 redundant passes/frame over the whole
|
|
75
|
+
caster set (measured 66M submitted triangles/frame in a pilot). Freeze them:
|
|
76
|
+
`light.shadow.autoUpdate = false; light.shadow.needsUpdate = true;` — only
|
|
77
|
+
the key light that shadows moving actors stays dynamic.
|
|
72
78
|
|
|
73
79
|
## Governor mechanics
|
|
74
80
|
|
|
@@ -113,9 +119,15 @@ Getting this wrong produces silent no-ops or a session stuck ugly:
|
|
|
113
119
|
- Watch `renderer.info.memory.{textures,geometries}` across swaps in dev; a
|
|
114
120
|
monotonic climb is a leak marching toward the OS kill. The governor
|
|
115
121
|
publishes these counts for the platform's field telemetry.
|
|
116
|
-
- Prefer meshopt/instanced geometry for repeats
|
|
117
|
-
|
|
118
|
-
|
|
122
|
+
- Prefer meshopt/instanced geometry for repeats: 3+ copies of the same
|
|
123
|
+
generated model → `InstancedMesh` (one draw call per source mesh instead of
|
|
124
|
+
per copy — `$genex-ai-model` has the collapse snippet); `BatchedMesh`
|
|
125
|
+
batches HETEROGENEOUS static meshes into one draw where instancing
|
|
126
|
+
(identical meshes only) can't.
|
|
127
|
+
- Distance LOD comes free with the rung ladder: `THREE.LOD` with the phone
|
|
128
|
+
rung (`@1024`) as the far level — `lod.addLevel(near, 0);
|
|
129
|
+
lod.addLevel(far, 25)`. One extra fetch; worth it past ~25 m sightlines,
|
|
130
|
+
skip in small arenas.
|
|
119
131
|
- Half-resolution transparency: render heavy particle/transparency passes to a
|
|
120
132
|
half-size target and composite up — fill-rate is the phone bottleneck.
|
|
121
133
|
|
|
@@ -82,44 +82,63 @@ game doesn't have (a lap counter in a game without laps). Generate it
|
|
|
82
82
|
`--size 2560x1440 --quality high --candidates 2 --no-wait`, enqueued FIRST
|
|
83
83
|
of all art; the picked candidate's URL goes into the style-brief comment.
|
|
84
84
|
|
|
85
|
-
**
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
`--
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
85
|
+
**Describe the concept, show it, then get a YES before the style-dependent
|
|
86
|
+
work fans out.** First lay the concept out in WORDS — the playable moment
|
|
87
|
+
(the verb), what threatens the player, what they chase, how the space reads,
|
|
88
|
+
and the brief's palette / materials / light / references — so the user is
|
|
89
|
+
weighing a stated direction, not guessing at a picture. Generate the mockup
|
|
90
|
+
FIRST of all art (`--candidates 2`) and get it in front of the user AS FAST
|
|
91
|
+
AS POSSIBLE: pick the stronger candidate yourself, pick it up with
|
|
92
|
+
`genex wait <id> --open` (or generate it with `--open`) so it opens in the
|
|
93
|
+
user's browser AND prints the links, and paste BOTH candidates' URLs as
|
|
94
|
+
clickable links saying which one you picked and why — a URL is invisible in a
|
|
95
|
+
terminal, and "do you like it?" with no picture in front of the user is the
|
|
96
|
+
#1 way this checkpoint fails (they end up digging logs for the file path).
|
|
97
|
+
|
|
98
|
+
**Then ask for the yes with your environment's structured question tool** —
|
|
99
|
+
Claude Code: `AskUserQuestion`; Codex: `request_user_input`; a short numbered
|
|
100
|
+
list only where there is none (e.g. Cursor). ONE question — "this is roughly
|
|
101
|
+
how the game and its HUD will look — keep this direction, or change
|
|
102
|
+
something?" — with concrete keep / change options. This confirmation is
|
|
103
|
+
REQUIRED for every game: the concept sets the STYLE every later asset
|
|
104
|
+
inherits, so the user gets a real say before that style is committed. This
|
|
105
|
+
reverses the old "advisory, never gate" rule — the concept now gates, but
|
|
106
|
+
ONLY the style-dependent work (below), never the whole build.
|
|
107
|
+
|
|
108
|
+
**The yes gates the STYLE-DEPENDENT work — nothing else.** Until the user
|
|
109
|
+
approves a frame, do NOT fan out the art or subagents that build ON the
|
|
110
|
+
chosen look: the HUD Stage-2 deconstruct (from the picked candidate), the
|
|
111
|
+
menu still + video (`--edit`-anchored to the mockup), and the logotype. (The core asset set — hero model, ground texture, skybox,
|
|
112
|
+
sfx — is prompted from the game IDEA, not the picked image, and mostly
|
|
113
|
+
survives a style change, so it does NOT wait: launch it up front with
|
|
114
|
+
placeholders, re-rolling only the few that don't survive if the look shifts.)
|
|
115
|
+
Everything else that does NOT depend on the look
|
|
116
|
+
keeps moving at full speed IN PARALLEL — the scaffold and boot wiring, the
|
|
117
|
+
core gameplay / network loop, the controller, and the gameplay-LOGIC subagent
|
|
118
|
+
modules (world, quests, enemies, items — their structure comes from the
|
|
119
|
+
contract, not the picture). The wait is therefore never idle: a slow-to-answer
|
|
120
|
+
user still gets a walkable first version, and the instant they say yes the
|
|
121
|
+
style pipeline launches against the frame they approved. (A user who vanishes
|
|
122
|
+
entirely leaves the LOOK pending, not the game — the deliberate trade for
|
|
123
|
+
never committing a style behind their back.)
|
|
124
|
+
|
|
125
|
+
**Change reopens the loop, same shape.** If the user picks "change" — or
|
|
126
|
+
comes back with notes immediately or an hour later — re-run Stage 1 (the
|
|
127
|
+
concept mockup itself) with their exact notes (`--candidates 2–3` gives them
|
|
128
|
+
options to choose from), open + link the new frame, and ask again the same
|
|
129
|
+
structured way. Carry every note forward so each round compounds; if two
|
|
130
|
+
rounds don't converge, offer 2–3 distinct directions instead of re-rolling
|
|
131
|
+
blind. Each frame the user approves becomes the working style, and the
|
|
132
|
+
style-dependent pipeline stays gated on the latest yes (strictly CHEAPER than
|
|
133
|
+
it used to be: one image carries scene + HUD, there is no separate concept to
|
|
134
|
+
redo). When the style actually changes after downstream art already ran,
|
|
135
|
+
re-anchor it: the menu still is re-edited (`--edit`) against the new mockup
|
|
136
|
+
and its video re-run once from the new still (`$genex-ai-menu` — a
|
|
137
|
+
user-driven style change re-opens its one-video rule), and the HUD deconstruct
|
|
138
|
+
(Stage 2 onward, `$genex-ai-hud`) restarts from the new mockup. Style-neutral
|
|
139
|
+
assets (most textures, sfx, models) usually survive — judge each in one line.
|
|
140
|
+
The same "open it + paste the link" rule covers every image the user weighs
|
|
141
|
+
in on — the menu still, the HUD mockup candidates.
|
|
123
142
|
|
|
124
143
|
**The concept anchors STYLE, not truth.** Palette, materials, light, and
|
|
125
144
|
register come from the frame; CONTENT comes from the game contract. This
|
|
@@ -158,9 +177,11 @@ UI plan
|
|
|
158
177
|
• Style: <4–5 named hues> · fonts <display> / <body>
|
|
159
178
|
• References: <2–3 AAA games — one line on what's borrowed>
|
|
160
179
|
• Menu: <archetype + button treatment, one-line reason from the brief>
|
|
161
|
-
• Concept (HUD mockup): <generation id — 2 candidates>
|
|
162
|
-
• Queued
|
|
163
|
-
video <or "no menu: reason"> · logotype <or
|
|
180
|
+
• Concept (HUD mockup): <generation id — 2 candidates> — awaiting your keep / change
|
|
181
|
+
• Queued ON YOUR YES (style-dependent): HUD sheet (Stage 2, from the picked
|
|
182
|
+
mockup) · menu still → video <or "no menu: reason"> · logotype <or
|
|
183
|
+
"skipped: reason">
|
|
184
|
+
• Building now (style-independent): <core loop · logic modules already moving>
|
|
164
185
|
• Deferred from mockup: <widgets the image invented but the game lacks — or "none">
|
|
165
186
|
```
|
|
166
187
|
|
|
@@ -178,7 +199,8 @@ A message missing any line means the gate did not run — go back and run it.
|
|
|
178
199
|
every game), and the generated **logotype** — one `--transparent` wordmark
|
|
179
200
|
in the brief's display register (`$genex-ai-menu`'s logotype step), default
|
|
180
201
|
YES for every game with a menu; skipping it needs a one-line stated reason.
|
|
181
|
-
Enqueue
|
|
202
|
+
Enqueue this chain the instant the user approves the concept
|
|
203
|
+
(`--no-wait`) — it is style-dependent, so it waits on the yes — then
|
|
182
204
|
keep building, pick results up with `npx genex wait <id>`, swap them in as
|
|
183
205
|
they land. **Tier 2 must never block a playable v0 — but the game is NOT
|
|
184
206
|
DONE until its Tier-2 assets have landed and been wired in** (the only
|
|
@@ -454,6 +476,30 @@ Order the HUD by what the player loses the game for ignoring:
|
|
|
454
476
|
frame, a generated frame sprite (`$genex-ai-hud` chrome, or a Tier-3
|
|
455
477
|
9-slice panel) is still the richer tool. The masked-fill HUD reveal and
|
|
456
478
|
`genex ui` masks remain the other established uses of `mask`/`clip-path`.
|
|
479
|
+
- **The #1 chamfer defect: a frame that STOPS at the cut.** A plain
|
|
480
|
+
`border` or `box-shadow` does NOT follow a `clip-path` chamfer — the clip
|
|
481
|
+
shears it off along the diagonal, so the straight sides keep their frame
|
|
482
|
+
while the cut edge goes bare and the panel reads as broken. Two clean
|
|
483
|
+
fixes: prefer **`border-radius`** when a soft corner reads fine (it keeps
|
|
484
|
+
its `border` natively — no clip needed); for a HARD angular cut, draw the
|
|
485
|
+
frame by clipping TWO stacked layers to the SAME polygon — outer = the
|
|
486
|
+
frame, inner (inset by the frame width) = the fill — so the outer shows
|
|
487
|
+
through as a uniform edge on the diagonal too:
|
|
488
|
+
|
|
489
|
+
```css
|
|
490
|
+
/* Chamfered panel whose frame follows the cut. Never `border` + clip-path. */
|
|
491
|
+
.panel {
|
|
492
|
+
--chamfer: 16px; --edge: 2px; /* --edge = frame width */
|
|
493
|
+
--cut: polygon(var(--chamfer) 0, 100% 0, 100% calc(100% - var(--chamfer)),
|
|
494
|
+
calc(100% - var(--chamfer)) 100%, 0 100%, 0 var(--chamfer));
|
|
495
|
+
clip-path: var(--cut); background: var(--frame-hue); /* this IS the frame */
|
|
496
|
+
padding: var(--edge); /* revealed all around, incl. the diagonal */
|
|
497
|
+
}
|
|
498
|
+
.panel > .panel__fill { clip-path: var(--cut); background: var(--plate-hue); }
|
|
499
|
+
```
|
|
500
|
+
|
|
501
|
+
Same rule for chamfered buttons and any notched plate; verify the frame
|
|
502
|
+
is unbroken at every corner over real gameplay.
|
|
457
503
|
- **One cohesion layer.** A single full-screen vignette div (a subtle radial
|
|
458
504
|
gradient darkening the corners, optionally faint grain) over canvas + UI is
|
|
459
505
|
the cheapest way to make DOM-over-WebGL read as one composed image instead
|
|
@@ -510,12 +556,19 @@ architecture and consume the shared style brief.
|
|
|
510
556
|
- Hard-cut phase swaps, a menu whose elements just appear, numbers that
|
|
511
557
|
teleport.
|
|
512
558
|
- A silent menu; a bare "Loading…" over black.
|
|
513
|
-
-
|
|
514
|
-
|
|
515
|
-
|
|
559
|
+
- Style-dependent art fanned out before the user approved the concept — the
|
|
560
|
+
concept confirmation is REQUIRED and gates the look-dependent work (HUD
|
|
561
|
+
Stage 2, menu still/video, logotype, style-matched assets); OR the whole
|
|
562
|
+
build stalled waiting on that yes, when concept-INDEPENDENT work (scaffold,
|
|
563
|
+
core loop, logic subagents) must keep moving in parallel while it is pending.
|
|
516
564
|
- A CSS-cut corner (`clip-path`/`mask`) that shears its own content — clipped
|
|
517
|
-
text or padding, a lost focus ring or glow, a jagged aliased edge
|
|
565
|
+
text or padding, a lost focus ring or glow, a jagged aliased edge, or a
|
|
566
|
+
border/frame that stops at the cut instead of following it — the
|
|
518
567
|
technique is fine; the sloppy cut is the defect.
|
|
568
|
+
- A rectangular semi-transparent plate protruding past an opaque/angular
|
|
569
|
+
widget frame — a dark box floating over the scene. The frame's own art is
|
|
570
|
+
the backing; a plate is only for bare-text/outline widgets and stays inside
|
|
571
|
+
the widget silhouette (`$genex-ai-hud`'s plate rule).
|
|
519
572
|
- UI panels covering the player or the thing about to kill them.
|
|
520
573
|
- Layout shifting as numbers grow.
|
|
521
574
|
- A fail state with no visible restart key, or a restart that reloads the page.
|
|
@@ -9,8 +9,9 @@ the capsule says a racing HUD leans oblique and bottom-right-heavy; your brief
|
|
|
9
9
|
says what it's made of in THIS game.
|
|
10
10
|
|
|
11
11
|
Every capsule below assumes the base rules from the skill: corners/edges for
|
|
12
|
-
UI, one display + one body font, tabular numerals, contrast plates
|
|
13
|
-
arbitrary scenes
|
|
12
|
+
UI, one display + one body font, tabular numerals, contrast plates for bare
|
|
13
|
+
text over arbitrary scenes (never a second plate stacked behind an opaque
|
|
14
|
+
frame sprite), and panel/button corners executed cleanly — `border-radius`,
|
|
14
15
|
a CSS `clip-path`/`mask` shape, or a generated frame all work, as long as the
|
|
15
16
|
cut never clips content (text, padding, glow), stays free of jagged-edge
|
|
16
17
|
artifacts, and text never collides or truncates.
|
|
@@ -111,6 +111,13 @@ kill-switch diagnostic.
|
|
|
111
111
|
legitimate time the population changes.
|
|
112
112
|
- Couple a practical to its emitter: one color, one envelope, one on/off state.
|
|
113
113
|
- The key owns shadows; practicals cast none until a shot proves they must.
|
|
114
|
+
- **A shadow-casting practical that never moves gets a FROZEN map**: a
|
|
115
|
+
PointLight shadow is a 6-face cube render, re-drawn every frame by default —
|
|
116
|
+
four static lanterns cost 24 shadow passes/frame over your densest props for
|
|
117
|
+
zero visual change (a pilot game submitted 66M triangles/frame this way).
|
|
118
|
+
Set `light.shadow.autoUpdate = false; light.shadow.needsUpdate = true;` so
|
|
119
|
+
the cube renders once; keep only the key (which shadows the movers) dynamic,
|
|
120
|
+
and budget frozen practicals at ≤1024 map size.
|
|
114
121
|
- Tune intensities only after the renderer baseline is locked — retuning the
|
|
115
122
|
whole rig after a tone-mapping change is self-inflicted.
|
|
116
123
|
- Never repair unbalanced light ratios with exposure — fix the lights.
|
|
@@ -98,9 +98,10 @@ ECCTRL-derived character controller owns collision and world translation.
|
|
|
98
98
|
**UI is mandatory routing:** every game has an interface — load
|
|
99
99
|
`$genex-threejs-game-ui` for every NEW game and run its "Plan the UI first" gate
|
|
100
100
|
(screen inventory, one shared style brief, tier decisions) right after the game
|
|
101
|
-
concept is locked, before any UI code. Its Tier-2 generations
|
|
102
|
-
sprite HUD) are enqueued async (`--no-wait` + `npx genex wait
|
|
103
|
-
|
|
101
|
+
concept is locked, before any UI code. Its frame-anchored Tier-2 generations
|
|
102
|
+
(cinematic menu, sprite HUD) are enqueued async (`--no-wait` + `npx genex wait
|
|
103
|
+
<id>`) the moment the user approves the concept, so they land while the game is
|
|
104
|
+
being built — they never block a playable v0, and the game is
|
|
104
105
|
not done, published, or handed off until they've been picked up (`npx genex wait`)
|
|
105
106
|
and wired in.
|
|
106
107
|
|
|
@@ -195,8 +196,10 @@ Prefer the **procedural** skills above for abstract/parametric/animated systems
|
|
|
195
196
|
each other.
|
|
196
197
|
|
|
197
198
|
**Generate a core asset set by default — don't wait to be asked.** For any game that
|
|
198
|
-
needs concrete objects or surfaces, decide a small core set from the
|
|
199
|
-
generating it **up front, in parallel**
|
|
199
|
+
needs concrete objects or surfaces, decide a small core set from the game IDEA and start
|
|
200
|
+
generating it **up front, in parallel** — this set is concept-INDEPENDENT (prompted from
|
|
201
|
+
the idea, not the concept image, and it mostly survives a style change), so it does NOT
|
|
202
|
+
wait on the concept yes (each `npx genex` is an independent ~1-minute
|
|
200
203
|
job — launch them concurrently in the background, then scaffold the scene while they run
|
|
201
204
|
and wire each asset in as it lands, with a procedural placeholder as fallback until then):
|
|
202
205
|
|
|
@@ -40,21 +40,26 @@ Three.js release or branch, and do not blindly copy demo architecture.
|
|
|
40
40
|
saves, leaderboards, and multiplayer auth all come from it.
|
|
41
41
|
3. Run `$genex-threejs-game-ui`'s "Plan the UI first" gate: screen inventory,
|
|
42
42
|
one shared style brief, a tier per screen — stated visibly in chat, never
|
|
43
|
-
decided silently.
|
|
44
|
-
(pick them up later with `npx genex wait <id>`) — they render server-side
|
|
45
|
-
while you build the game. FIRST of all art: the gate's **concept mockup** —
|
|
43
|
+
decided silently. FIRST of all art, generate the gate's **concept mockup** —
|
|
46
44
|
a playable-moment shot (verb + threat + objective in frame, per the
|
|
47
45
|
game-ui gate) WITH the full HUD composited over it: the `$genex-ai-hud`
|
|
48
46
|
Stage-1 image, one generation serving as concept, style checkpoint, and
|
|
49
|
-
HUD blueprint (never a separate UI-free concept first).
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
decision is yes
|
|
57
|
-
reason to decide no).
|
|
47
|
+
HUD blueprint (never a separate UI-free concept first). Show it to the user
|
|
48
|
+
the moment it lands and get a keep-or-change answer through the
|
|
49
|
+
environment's structured question tool (Claude Code: `AskUserQuestion`;
|
|
50
|
+
Codex: `request_user_input`; numbered-list fallback otherwise). This answer
|
|
51
|
+
GATES — but only the style-dependent pipeline: until the user approves a
|
|
52
|
+
frame, do NOT enqueue the Tier-2 art that builds on the look (the
|
|
53
|
+
`$genex-ai-hud` Stage-2 chain, and the `$genex-ai-menu` still + video
|
|
54
|
+
whenever the menu decision is yes — the default for every game, "it's only
|
|
55
|
+
a draft" is not a reason to decide no — plus the logotype). The instant
|
|
56
|
+
they say yes, fire those with `--no-wait` (pick them up with
|
|
57
|
+
`npx genex wait <id>`); the game-ui skill owns the re-anchor loop when notes
|
|
58
|
+
arrive, and later `--edit`-able generations anchor to the approved frame for
|
|
59
|
+
STYLE while the game contract owns content. Everything that does NOT depend
|
|
60
|
+
on the look — scaffold, boot wiring, the core loop, and the
|
|
61
|
+
concept-independent content subagents below — keeps building in parallel
|
|
62
|
+
while the answer is pending, so the wait is never idle. Skipping this enqueue is
|
|
58
63
|
the #1 way a finished game ships an ugly HUD — by step 12 there is nothing
|
|
59
64
|
to swap in.
|
|
60
65
|
4. Lock the visual direction — the same plan-first logic as the UI gate, in
|
|
@@ -212,19 +217,29 @@ Three.js release or branch, and do not blindly copy demo architecture.
|
|
|
212
217
|
13. Validate in a real browser with fixed seeds, captures, interaction checks,
|
|
213
218
|
and performance evidence.
|
|
214
219
|
|
|
215
|
-
## Parallelize with subagents
|
|
220
|
+
## Parallelize with subagents — the default whenever the environment has them
|
|
216
221
|
|
|
217
222
|
Generations already render server-side in parallel — the wall-clock savings
|
|
218
|
-
live in the ATTENDED chains.
|
|
219
|
-
subagents (e.g. Claude Code's Agent tool),
|
|
220
|
-
|
|
221
|
-
|
|
223
|
+
live in the ATTENDED chains. Whenever the coding agent supports background
|
|
224
|
+
subagents (e.g. Claude Code's Agent tool), fanning the independent work out is
|
|
225
|
+
the DEFAULT, not a big-scope special case: split every disjoint chain below off
|
|
226
|
+
by default and keep the main thread building. In environments without them
|
|
227
|
+
(Cursor), the same order simply runs sequentially and the `--no-wait` pattern
|
|
228
|
+
still hides most generation latency.
|
|
222
229
|
|
|
223
|
-
|
|
224
|
-
|
|
230
|
+
**One ordering rule from the concept gate (step 3):** subagents that build ON
|
|
231
|
+
the look are CONCEPT-DEPENDENT and wait for the user's keep/change yes — the
|
|
232
|
+
HUD chain worker and any style-matched art. Subagents that do NOT depend on the
|
|
233
|
+
look — the content-module fan-out, verification of concept-independent parts —
|
|
234
|
+
launch immediately and run while the concept answer is still pending. Never let
|
|
235
|
+
the pending yes stall concept-independent work, and never fan out
|
|
236
|
+
concept-dependent work before it.
|
|
237
|
+
|
|
238
|
+
- **HUD chain worker (concept-dependent — starts on the yes).** The moment the
|
|
239
|
+
user APPROVES the concept, hand the whole
|
|
225
240
|
`$genex-ai-hud` chain to one subagent: pick the better candidate → Stage-2
|
|
226
241
|
sheet → clean → extract → masks → write the sprite wiring. Its prompt must
|
|
227
|
-
be self-contained — the style brief, the mockup URL, the
|
|
242
|
+
be self-contained — the style brief, the approved mockup URL, the
|
|
228
243
|
output dir, and exactly which files it owns. **Handoff rule:** whoever
|
|
229
244
|
wires the HUD after the subagent finishes MUST read the produced
|
|
230
245
|
`*.annotated-progress.json` and `.bbox.json` files from disk — fillBox
|
|
@@ -237,7 +252,8 @@ without them (Cursor), the same order simply runs sequentially and the
|
|
|
237
252
|
- **Verification runner.** Browser evidence — screenshots, control presses,
|
|
238
253
|
the menu loop-seam watch, in-situ checks of placed art — can run in a
|
|
239
254
|
subagent while the main agent keeps building.
|
|
240
|
-
- **Content module fan-out (big scopes
|
|
255
|
+
- **Content module fan-out (big scopes — concept-independent, launch immediately).**
|
|
256
|
+
When the content contract names a
|
|
241
257
|
big world or several content systems, serial hand-typing is what runs out
|
|
242
258
|
of session: split the gameplay modules across parallel subagents instead —
|
|
243
259
|
one each for world/terrain, quest + dialogue DATA, enemies/AI, items/
|