@genex-ai/cli-demo 1.18.0-dev.599 → 1.19.0-dev.600
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
CHANGED
|
@@ -5862,9 +5862,10 @@ function blankComments(raw) {
|
|
|
5862
5862
|
}
|
|
5863
5863
|
var IDENTITY_AWAIT = /\bawait\s+(waitForPlayer|waitForAuth)\s*\(/;
|
|
5864
5864
|
var RENDERER_CTOR = /\bnew\s+(?:THREE\.)?(?:WebGLRenderer|WebGPURenderer)\s*\(/;
|
|
5865
|
+
var LOOP_START = /\b(?:requestAnimationFrame|setAnimationLoop)\s*\((?!\s*null\b)/;
|
|
5865
5866
|
var INIT_EMBED_CALL = /\binitEmbed\s*\(/;
|
|
5866
5867
|
async function detectEmbedBoot(cwd = process.cwd()) {
|
|
5867
|
-
const found = { awaitsIdentity: [], callsInitEmbed: false, rendererAfterAwait: [] };
|
|
5868
|
+
const found = { awaitsIdentity: [], callsInitEmbed: false, rendererAfterAwait: [], loopAfterAwait: [] };
|
|
5868
5869
|
const srcDir = path14.join(cwd, "src");
|
|
5869
5870
|
let entries;
|
|
5870
5871
|
try {
|
|
@@ -5902,6 +5903,17 @@ async function detectEmbedBoot(cwd = process.cwd()) {
|
|
|
5902
5903
|
detail: `\`await ${blocking.name}()\` (line ${lineOf(content, blocking.index)})`
|
|
5903
5904
|
});
|
|
5904
5905
|
}
|
|
5906
|
+
const firstLoop = new RegExp(LOOP_START.source).exec(content);
|
|
5907
|
+
if (firstLoop) {
|
|
5908
|
+
const at = firstLoop.index;
|
|
5909
|
+
const blocking = awaits.find((a) => a.index < at && at < a.flowEnd);
|
|
5910
|
+
if (blocking) {
|
|
5911
|
+
found.loopAfterAwait.push({
|
|
5912
|
+
where: `${rel}:${lineOf(content, at)}`,
|
|
5913
|
+
detail: `\`await ${blocking.name}()\` (line ${lineOf(content, blocking.index)})`
|
|
5914
|
+
});
|
|
5915
|
+
}
|
|
5916
|
+
}
|
|
5905
5917
|
}
|
|
5906
5918
|
return found;
|
|
5907
5919
|
}
|
|
@@ -6149,6 +6161,13 @@ function embedBootNudges(log, d) {
|
|
|
6149
6161
|
`${r.where} creates the renderer after ${r.detail}. Draw first, await identity where you need the name: on a hosted page identity takes seconds and the player sees black until then.`
|
|
6150
6162
|
);
|
|
6151
6163
|
}
|
|
6164
|
+
const drawFirstSaid = new Set(boot.rendererAfterAwait.map((r) => r.where.split(":")[0]));
|
|
6165
|
+
for (const l of boot.loopAfterAwait) {
|
|
6166
|
+
if (drawFirstSaid.has(l.where.split(":")[0])) continue;
|
|
6167
|
+
log.warn(
|
|
6168
|
+
`${l.where} starts the render loop after ${l.detail}. The renderer must draw before identity resolves \u2014 identity takes seconds on a hosted page and the load behind it longer on a slow GPU \u2014 so start the loop first, draw a placeholder body, and swap the real one in when the load lands.`
|
|
6169
|
+
);
|
|
6170
|
+
}
|
|
6152
6171
|
}
|
|
6153
6172
|
function generationNudges(log, g) {
|
|
6154
6173
|
const describe = (refs) => refs.map((r) => `${r.kind} ${r.id} ("${r.prompt.slice(0, 48)}")`).join("; ");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@genex-ai/cli-demo",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.19.0-dev.600",
|
|
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": {
|
|
@@ -134,14 +134,55 @@ overlay — HTML/CSS on top of the canvas, not text sprites inside the scene:
|
|
|
134
134
|
|
|
135
135
|
```css
|
|
136
136
|
#ui { position: fixed; inset: 0; pointer-events: none; font-variant-numeric: tabular-nums; }
|
|
137
|
-
|
|
137
|
+
/* Layout belongs to the SHOWN screen only. Never `#ui .screen { display: … }`:
|
|
138
|
+
that overrides the browser's `[hidden] { display: none }`, and every hidden
|
|
139
|
+
screen is laid out full-viewport, invisible, on top of the one that is up. */
|
|
140
|
+
#ui .screen:not([hidden]) { display: flex; }
|
|
141
|
+
/* Clickable only while shown (`.is-on` is the shown class — Motion, below). */
|
|
142
|
+
#ui .screen.is-on, #ui #hud button { pointer-events: auto; }
|
|
138
143
|
```
|
|
139
144
|
|
|
140
145
|
`pointer-events: none` on the root keeps the canvas playable; re-enable it only
|
|
141
|
-
on elements that are actually clickable
|
|
142
|
-
position UI by mutating inline pixel styles per
|
|
143
|
-
a health bar floating over an enemy, a
|
|
144
|
-
exception for things that belong to the
|
|
146
|
+
on elements that are actually clickable — and only while they are on screen.
|
|
147
|
+
Keep ALL layout in CSS — never position UI by mutating inline pixel styles per
|
|
148
|
+
frame. In-world (diegetic) UI — a health bar floating over an enemy, a
|
|
149
|
+
scoreboard mesh in a stadium — is the exception for things that belong to the
|
|
150
|
+
world, not the default.
|
|
151
|
+
|
|
152
|
+
### Inactive screens are gone, not see-through
|
|
153
|
+
|
|
154
|
+
An invisible screen that is still laid out is a click trap nobody can see: the
|
|
155
|
+
title's Start button renders, the pointer hovers it, and the click lands on a
|
|
156
|
+
transparent pause card sitting last in DOM order. Three rules:
|
|
157
|
+
|
|
158
|
+
1. **An inactive screen is `display: none`** — via `hidden` or a class — never
|
|
159
|
+
merely `opacity: 0`. And NEVER set `display` on the `.screen` selector
|
|
160
|
+
itself: `.screen { display: flex }` beats the browser's `[hidden]` rule, so
|
|
161
|
+
`hidden` stops hiding anything. Put `display: flex` on
|
|
162
|
+
`.screen:not([hidden])` or on the shown class.
|
|
163
|
+
2. **`pointer-events: auto` belongs only to the screen that is currently
|
|
164
|
+
shown.** An invisible screen with clickable descendants swallows every click
|
|
165
|
+
meant for the screen under it, so scope `pointer-events` to `.screen.is-on`
|
|
166
|
+
(and to HUD controls) — never to `#ui .screen` or `#ui button` at large.
|
|
167
|
+
3. **The smoke check for every boot/title/pause screen:** for each visible
|
|
168
|
+
button, `document.elementFromPoint(cx, cy)` at its centre must return the
|
|
169
|
+
button or one of its descendants. A top hit whose effective (inherited)
|
|
170
|
+
opacity is 0 is a defect, not a styling choice — some screen is laid out
|
|
171
|
+
over the one the player sees. One paste in the console:
|
|
172
|
+
|
|
173
|
+
```js
|
|
174
|
+
[...document.querySelectorAll("#ui button")].filter((b) => b.getClientRects().length).map((b) => {
|
|
175
|
+
const r = b.getBoundingClientRect(), top = document.elementFromPoint(r.x + r.width / 2, r.y + r.height / 2);
|
|
176
|
+
let o = 1; for (let e = top; e; e = e.parentElement) o *= +getComputedStyle(e).opacity;
|
|
177
|
+
const ok = !!top && b.contains(top);
|
|
178
|
+
return { button: b.textContent.trim(), ok, coveredBy: ok ? null : top?.closest(".screen")?.id ?? top?.tagName ?? "offscreen", topOpacity: o };
|
|
179
|
+
})
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
Every row reads `ok: true`; a `coveredBy` naming a screen with
|
|
183
|
+
`topOpacity: 0` is rule 1 or 2 broken. Run it on the title screen and again
|
|
184
|
+
with the pause screen up — `$genex-threejs-visual-validation` carries the
|
|
185
|
+
same check in its smoke pass.
|
|
145
186
|
|
|
146
187
|
## The states every game needs
|
|
147
188
|
|
|
@@ -279,7 +320,8 @@ black reads as a broken page. The branded version costs nothing:
|
|
|
279
320
|
## Motion — screens move or the game feels dead
|
|
280
321
|
|
|
281
322
|
Phase changes animate. `hidden` alone hard-cuts; pair it with a class so
|
|
282
|
-
opacity can transition
|
|
323
|
+
opacity can transition — the class owns opacity and `pointer-events`, `hidden`
|
|
324
|
+
keeps owning `display`:
|
|
283
325
|
|
|
284
326
|
```ts
|
|
285
327
|
function setPhase(phase: "loading" | "playing" | "paused" | "over" | "won") {
|
|
@@ -298,6 +340,8 @@ function setPhase(phase: "loading" | "playing" | "paused" | "over" | "won") {
|
|
|
298
340
|
```
|
|
299
341
|
|
|
300
342
|
```css
|
|
343
|
+
/* Opacity only — `display` stays with `hidden` (Architecture, above). Setting
|
|
344
|
+
`display` here would lay every hidden screen out over the shown one. */
|
|
301
345
|
#ui .screen { opacity: 0; transition: opacity 280ms ease; }
|
|
302
346
|
#ui .screen.is-on { opacity: 1; }
|
|
303
347
|
|
|
@@ -431,6 +475,11 @@ widget gets its numbers.
|
|
|
431
475
|
HUD — pick the 2–3 numbers that matter and style them by hierarchy.
|
|
432
476
|
- Hard-cut phase swaps, a menu whose elements just appear, numbers that
|
|
433
477
|
teleport.
|
|
478
|
+
- An invisible screen still laid out over the one the player sees — `display`
|
|
479
|
+
set on `.screen` (defeating `hidden`), or an `opacity: 0` screen that kept
|
|
480
|
+
`pointer-events: auto` — so the title's Start button never receives its
|
|
481
|
+
click; the hit-test in "Inactive screens are gone, not see-through" catches
|
|
482
|
+
it.
|
|
434
483
|
- A silent menu; a bare "Loading…" over black.
|
|
435
484
|
- Generated UI art enqueued and then left on the shelf — a landed sprite sheet
|
|
436
485
|
that never got extracted, masked, and wired is worse than never running the
|
|
@@ -72,7 +72,13 @@ everything twice.
|
|
|
72
72
|
console errors). For an unpublished draft, open the dev server in local
|
|
73
73
|
test mode — `http://localhost:5173/?genex_local_test=1` (the embed-auth
|
|
74
74
|
skill's "Self-testing a draft" section) — so you see the game, not the
|
|
75
|
-
sign-in gate.
|
|
75
|
+
sign-in gate. Then hit-test the screen that is up (title now, pause later):
|
|
76
|
+
for every visible button, `document.elementFromPoint(cx, cy)` at its centre
|
|
77
|
+
must return the button or one of its descendants — a top hit whose
|
|
78
|
+
effective (inherited) opacity is 0 is an invisible screen still laid out
|
|
79
|
+
over this one (a `display` rule on `.screen` defeating `hidden`, or an
|
|
80
|
+
`opacity: 0` screen keeping `pointer-events: auto`), and nobody can enter
|
|
81
|
+
the game. `$genex-threejs-game-ui` has the one-paste check and the fix.
|
|
76
82
|
2. Press each documented control once (keys, pointer); assert a **visible
|
|
77
83
|
response in its labeled direction** — this is the input-direction pass, and
|
|
78
84
|
it is part of THIS check, not an extra testing loop. Hold `KeyD`/ArrowRight
|
|
@@ -359,6 +365,9 @@ visual-system work — the sequence above.
|
|
|
359
365
|
- a non-cursor-core game that leaves the OS cursor visible during play;
|
|
360
366
|
- a menu or settings click locks the pointer (or the cursor vanishes) while a
|
|
361
367
|
menu screen is still up;
|
|
368
|
+
- a title, boot, or pause button whose centre hit-test returns an element of a
|
|
369
|
+
screen that is not visibly up (effective opacity 0) — an invisible overlay is
|
|
370
|
+
swallowing the click;
|
|
362
371
|
- approval relies on a single frame;
|
|
363
372
|
- post-processing cannot be disabled per pass;
|
|
364
373
|
- random seeds are not reproducible;
|