@genex-ai/cli-demo 0.71.0-dev.186 → 0.73.0-dev.188

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@genex-ai/cli-demo",
3
- "version": "0.71.0-dev.186",
3
+ "version": "0.73.0-dev.188",
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": {
@@ -674,13 +674,24 @@ export class FollowCamera {
674
674
 
675
675
  /**
676
676
  * Suspend or resume aim without disabling the camera. Call `setPaused(true)`
677
- * when a menu opens or the player starts driving; call `setPaused(false)` INSIDE
678
- * the closing click/keypress handler (browsers only grant re-lock from a user
679
- * gesture). While paused, canvas clicks do NOT re-lock. No-op when aim is "off"
680
- * or "unavailable".
677
+ * when a menu opens (the BOOT menu counts) or the player starts driving; call
678
+ * `setPaused(false)` INSIDE the closing click/keypress handler (browsers only
679
+ * grant re-lock from a user gesture). While paused, canvas clicks do NOT
680
+ * re-lock. No-op when aim is "off" or "unavailable".
681
+ *
682
+ * IDEMPOTENT: a same-value call is a complete no-op — safe to drive from a
683
+ * phase binding (`setPaused(phase !== "playing")`) that may fire repeatedly.
684
+ * This is load-bearing, not a nicety: an unguarded `setPaused(false)` per
685
+ * render frame re-requested the lock every frame, and because ANY DOM click
686
+ * (a Settings button) grants ~5s of transient activation, the next frame
687
+ * grabbed the pointer over the open menu. The resume re-lock therefore only
688
+ * fires on a real paused→unpaused transition; a game that never called
689
+ * `setPaused(true)` gets its re-lock from the canvas click (+ the cue), by
690
+ * design.
681
691
  */
682
692
  setPaused(paused: boolean): void {
683
693
  if (this._aimState === "off" || this._aimState === "unavailable") return;
694
+ if (paused === this._pausedByGame) return; // idempotent — same-value calls are no-ops
684
695
  this._pausedByGame = paused;
685
696
  if (paused) {
686
697
  this._retryArmed = false; // a menu/vehicle owns input now — no gesture retry
@@ -41,9 +41,12 @@ ring) that no hand-written CSS can fake.
41
41
  semi-transparency physically cannot survive sprite extraction: a composited
42
42
  panel's pixels are a blend of panel and background, and no cutout can
43
43
  un-blend them. Plates are NEVER baked into sprites. Give the plate its
44
- corners with `border-radius` never chamfer or notch it with
45
- `clip-path`/`mask`; a real angular frame is chrome, generated, not clipped.
46
- The only `mask`/`clip-path` in this skill is the masked-fill reveal below.
44
+ corners with `border-radius` or a clean `clip-path`/`mask` shape a CSS
45
+ chamfer/notch is allowed, as long as the cut never clips the plate's
46
+ content (text, padding, a glow), leaves no jagged-edge artifacts, and
47
+ nothing ends up crooked; a genuinely ornamented angular frame is still
48
+ best as chrome, generated. The load-bearing `mask`/`clip-path` in this
49
+ skill is the masked-fill reveal below.
47
50
  - **Chrome (sprites — what THIS pipeline generates).** Opaque frames, corner
48
51
  brackets, ornaments, emblems, icons, medallions — hard-alpha art laid over
49
52
  the glass. The Stage-2 sheet contains ONLY chrome; never a panel with its
@@ -211,6 +211,10 @@ copy-pasteable example:
211
211
 
212
212
  ```ts
213
213
  // Buttons emit the SAME intents the gameplay input path uses — never a page reload.
214
+ // setPhase() carries the camera's lock lifecycle (game-ui's
215
+ // `followCam?.setPaused(phase !== "playing")` binding), so PLAY locks inside this
216
+ // click and Options/Credits clicks can never lock the pointer — a cursor that
217
+ // vanishes while a menu is still up is a shipped defect.
214
218
  document.getElementById("menu-play")!.addEventListener("click", () => setPhase("playing"));
215
219
  document.getElementById("menu-options")!.addEventListener("click", () => setPhase("options"));
216
220
  document.getElementById("menu-credits")!.addEventListener("click", () => setPhase("credits"));
@@ -73,6 +73,7 @@ character`) to `onAimChange` and you get the reticle + "click to aim" cue for fr
73
73
  domElement: renderer.domElement,
74
74
  onAimChange: cue.onAimChange,
75
75
  });
76
+ followCam.setPaused(getPhase() !== "playing"); // park aim if the game boots on a menu/loader
76
77
 
77
78
  Cursor-core game? Pass `pointerLockAim: false` instead and skip the cue. Want a
78
79
  custom HUD? Read `onAimChange` yourself — `{ state }` is one of `locked` /
@@ -94,12 +95,17 @@ grants the permission.
94
95
 
95
96
  1. Two states only: locked = playing, unlocked = menu/paused. "Unlocked but
96
97
  gameplay continues" is the imprecise-aim defect in disguise.
97
- 2. Opening any menu (inventory/shop/dialog): exit the lock
98
+ 2. Opening any menu — the BOOT/main menu included — parks aim: exit the lock
98
99
  (`followCam.setPaused(true)`), cursor returns, gameplay input pauses. Menu
99
- keys are Tab/I/E — never Esc.
100
+ keys are Tab/I/E — never Esc. The one true wiring is the phase binding
101
+ `followCam.setPaused(phase !== "playing")` applied on phase transitions,
102
+ never per frame: an unguarded per-frame resume once re-requested the lock
103
+ every frame, and any menu click's ~5s of transient activation then locked
104
+ the pointer over the open menu.
100
105
  3. Closing a menu re-locks INSIDE the close click/keypress handler
101
- (`followCam.setPaused(false)`) — the browser requires a user gesture, so
102
- menus close by click/keypress, never by timeout.
106
+ (`followCam.setPaused(false)` — the phase binding's `setPhase("playing")`
107
+ in the Play/Resume click does exactly this) — the browser requires a user
108
+ gesture, so menus close by click/keypress, never by timeout.
103
109
  4. Esc is the browser's release valve (you can't intercept it; Chrome enforces
104
110
  a re-lock cooldown) → treat Esc as pause: show the overlay with a
105
111
  "click to resume" button.
@@ -116,6 +122,10 @@ grants the permission.
116
122
  Chrome's post-Esc cooldown) fires `onAimChange` with reason `needs-gesture` and
117
123
  keeps the "click to aim" cue up; the next click or keypress re-locks. Never
118
124
  retry on a timer.
125
+ 10. The bundled camera OWNS the lock: never call `document.exitPointerLock()` or
126
+ `canvas.requestPointerLock()` yourself alongside it — raw calls desync
127
+ `aimState` and the cue. Everything routes through `setPaused` (which is
128
+ idempotent — a same-value call is a no-op).
119
129
 
120
130
  **Not an OS setting:** pointer `movementX/movementY` is never inverted by the OS
121
131
  (trackpad "natural scrolling" only flips the wheel). If look feels inverted it's a
@@ -149,9 +149,15 @@ Want a custom HUD? Read `onAimChange: ({ state }) => …` yourself instead of th
149
149
  - **Opt out:** cursor-core games (RTS, tower defense, card, builder) pass
150
150
  `pointerLockAim: false` — the camera then stays drag-orbit and never grabs the
151
151
  cursor.
152
- - **Menus / vehicles:** `followCam.setPaused(true)` when a menu opens or the
153
- player starts driving; `followCam.setPaused(false)` INSIDE the closing
154
- click/keypress handler (re-lock needs a user gesture). Aim is on-foot only.
152
+ - **Menus / vehicles:** the BOOT/main menu counts as a menu park aim from the
153
+ first frame. The one true wiring is the phase binding
154
+ `followCam.setPaused(phase !== "playing")` applied on phase TRANSITIONS
155
+ (inside `setPhase()`), never driven from the render loop: `setPaused` is
156
+ idempotent, but a per-frame resume once locked the pointer over an open menu
157
+ the moment a menu click granted transient activation. The Play/Resume click's
158
+ transition doubles as the required user gesture for the re-lock. Vehicles:
159
+ combine conditions (`phase !== "playing" || activeId !== CHARACTER_ID`) — aim
160
+ is on-foot only.
155
161
  - **First-person:** the same mode plus `minDistance`/`maxDistance` ≈ 0.1, an
156
162
  eye-height target fed to `moveTo`, `avatar.visible = false`, and
157
163
  `controller.setLockForward(true)`.
@@ -256,10 +256,14 @@ document.addEventListener("keydown", (e) => {
256
256
 
257
257
  - **On the bundled `FollowCamera`, it owns the lock — don't fight it.** Do NOT
258
258
  call `document.exitPointerLock()` / `canvas.requestPointerLock()` yourself; that
259
- desyncs its aim state (the cue flips wrong). Instead pause/resume through it:
260
- `followCam.setPaused(true)` on Escape, `followCam.setPaused(false)` from the
261
- Resume click, and read `onAimChange` for the cue. The raw calls above are only
262
- for a hand-rolled camera with no bundled controller.
259
+ desyncs its aim state (the cue flips wrong). Instead pause/resume through it
260
+ and bind it **on phase transitions, never per frame**: put
261
+ `followCam?.setPaused(phase !== "playing")` inside `setPhase()` (below) and it
262
+ covers everything at once the BOOT menu parks aim, Escape's
263
+ `setPhase("paused")` frees the cursor, and the Play/Resume click's
264
+ `setPhase("playing")` re-locks inside the gesture. Read `onAimChange` for the
265
+ cue. The raw calls above are only for a hand-rolled camera with no bundled
266
+ controller.
263
267
  - **Degradation is built in.** Where Keyboard Lock is absent (Safari, Firefox) or
264
268
  the game isn't fullscreen, the browser still releases pointer lock on Escape —
265
269
  so ALSO keep the pointer-lock-loss → pause path (`pointerlockchange`: if
@@ -336,6 +340,11 @@ function setPhase(phase: "loading" | "playing" | "paused" | "over" | "won") {
336
340
  if (on) s.hidden = false; // show immediately, then fade in
337
341
  else setTimeout(() => { if (!s.classList.contains("is-on")) s.hidden = true; }, 300);
338
342
  }
343
+ // The camera's lock lifecycle rides the SAME transition — never the render loop
344
+ // (optional-chained: the camera may not exist yet at the first "loading" call).
345
+ followCam?.setPaused(phase !== "playing");
346
+ // (Vehicle games combine conditions instead:
347
+ // followCam.setPaused(phase !== "playing" || activeId !== CHARACTER_ID).)
339
348
  }
340
349
  ```
341
350
 
@@ -406,16 +415,19 @@ Order the HUD by what the player loses the game for ignoring:
406
415
  - **Contrast against the real scene.** Test text over the brightest AND
407
416
  darkest areas of actual gameplay; a soft dark plate or text-shadow beats
408
417
  restyling per level.
409
- - **Panel and button corners come from `border-radius`, not a CSS clip.**
410
- Don't chamfer, notch, or slice corners with `clip-path: polygon(...)` or a
411
- `mask` for a "hi-tech" look: a raw CSS clip hard-cuts the box, so it shears
412
- off whatever sits near the corner (text, padding, the focus ring, a glow)
413
- and leaves a jagged aliased diagonal that reads as *broken*, not as a
414
- designed bevel. Soft corners are `border-radius`; a genuinely angular or
415
- ornamented frame is a generated frame sprite (`$genex-ai-hud` chrome, or a
416
- Tier-3 9-slice panel), never hand-cut in CSS. This governs decorative
417
- panel/button **shape** only it does not touch the masked-fill HUD reveal
418
- or `genex ui` masks, which are the correct uses of `mask`/`clip-path`.
418
+ - **CSS-shaped corners must survive their contents.** `border-radius`,
419
+ `clip-path`, and `mask` are all fair ways to shape a panel or button —
420
+ including a chamfered/notched "hi-tech" corner in pure CSS. What is
421
+ non-negotiable is the execution: the cut must never shear off anything
422
+ that sits near the corner (text, padding, the focus ring, a glow) — keep
423
+ enough inner padding that content clears the cut shape — and the result
424
+ must be clean, not crooked: no jagged aliased diagonals, no half-clipped
425
+ borders or shadows, no text colliding with an edge or truncating. The
426
+ same bar applies to CSS plates. Verify the corners at real sizes over
427
+ real gameplay. When the art direction wants a genuinely ornamented
428
+ frame, a generated frame sprite (`$genex-ai-hud` chrome, or a Tier-3
429
+ 9-slice panel) is still the richer tool. The masked-fill HUD reveal and
430
+ `genex ui` masks remain the other established uses of `mask`/`clip-path`.
419
431
  - **One cohesion layer.** A single full-screen vignette div (a subtle radial
420
432
  gradient darkening the corners, optionally faint grain) over canvas + UI is
421
433
  the cheapest way to make DOM-over-WebGL read as one composed image instead
@@ -470,8 +482,9 @@ architecture and consume the shared style brief.
470
482
  - The art pipeline parked on an unanswered look question — the question is
471
483
  advisory: queue Tier-2 + the asset set against the shown frame and keep
472
484
  moving; silence is acceptance.
473
- - Panel or button corners chamfered/notched with `clip-path`/`mask` instead of
474
- `border-radius` the corner looks sheared off, not designed.
485
+ - A CSS-cut corner (`clip-path`/`mask`) that shears its own content — clipped
486
+ text or padding, a lost focus ring or glow, a jagged aliased edge — the
487
+ technique is fine; the sloppy cut is the defect.
475
488
  - UI panels covering the player or the thing about to kill them.
476
489
  - Layout shifting as numbers grow.
477
490
  - A fail state with no visible restart key, or a restart that reloads the page.
@@ -10,8 +10,10 @@ 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
12
  UI, one display + one body font, tabular numerals, contrast plates over
13
- arbitrary scenes, and panel/button corners from `border-radius` or a generated
14
- frame — never a raw CSS `clip-path`/`mask` chamfer.
13
+ arbitrary scenes, and panel/button corners executed cleanly — `border-radius`,
14
+ a CSS `clip-path`/`mask` shape, or a generated frame all work, as long as the
15
+ cut never clips content (text, padding, glow), stays free of jagged-edge
16
+ artifacts, and text never collides or truncates.
15
17
 
16
18
  ## Fantasy / action RPG
17
19
 
@@ -212,6 +212,13 @@ aim" cue stays up, and the next gesture (a click OR any keypress — the walk ke
212
212
  count) re-locks automatically. So it recovers on its own; just don't expect the lock
213
213
  back on the exact exit frame. Vehicle cameras keep their `alignHeading` behavior.
214
214
 
215
+ If the game also has a menu phase (most do), fold both signals into ONE derived
216
+ boolean recomputed from `setPhase` AND `onHandoff` —
217
+ `followCam.setPaused(phase !== "playing" || activeId !== CHARACTER_ID)` — so
218
+ neither wiring overwrites the other's pause. Both call sites fire on real
219
+ transitions only; `setPaused` is idempotent, so an occasional repeated value is
220
+ harmless (but never drive it from the render loop).
221
+
215
222
  ## Multiplayer
216
223
 
217
224
  Occupancy uses confirmed object ownership — remote players must see who is in what. With
@@ -122,7 +122,12 @@ everything twice.
122
122
  ship); press Esc and assert the "click to aim/resume" cue appears. The cue
123
123
  should be the bundled `createAimCue` helper (or an equivalent
124
124
  `onAimChange`-driven overlay) — a MANDATORY-bucket game with no unlocked cue
125
- fails. Headless caveat: `requestPointerLock` throws in headless Chromium
125
+ fails. Games with a menu additionally: on the main menu, click one item that
126
+ does NOT start play (Settings/Options/Credits) — the cursor must remain
127
+ visible; the lock may only ever engage from the Play/Resume click or a
128
+ gameplay canvas click (the phase binding `setPaused(phase !== "playing")` is
129
+ what guarantees this — check it rides `setPhase`, not the render loop).
130
+ Headless caveat: `requestPointerLock` throws in headless Chromium —
126
131
  assert the wiring and the unlocked cue in a screenshot, and say plainly that
127
132
  the lock itself needs one manual click (do the both-axes look check there).
128
133
  7. **Ask the scene the three things the screenshot cannot answer** (below). Run it
@@ -296,6 +301,8 @@ visual-system work — the sequence above.
296
301
  - drag-pan axes that mix conventions (one axis grab-the-world, the other
297
302
  move-the-camera);
298
303
  - a non-cursor-core game that leaves the OS cursor visible during play;
304
+ - a menu or settings click locks the pointer (or the cursor vanishes) while a
305
+ menu screen is still up;
299
306
  - approval relies on a single frame;
300
307
  - post-processing cannot be disabled per pass;
301
308
  - random seeds are not reproducible;