@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.18.0-dev.599",
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
- #ui button, #ui .screen { pointer-events: auto; }
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. Keep ALL layout in CSS never
142
- position UI by mutating inline pixel styles per frame. In-world (diegetic) UI —
143
- a health bar floating over an enemy, a scoreboard mesh in a stadium — is the
144
- exception for things that belong to the world, not the default.
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;