@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 +1 -1
- package/templates/controllers/character/follow-camera.ts +15 -4
- package/templates/skills/genex-ai-hud/SKILL.md +6 -3
- package/templates/skills/genex-ai-menu/SKILL.md +4 -0
- package/templates/skills/genex-threejs-camera-direction/SKILL.md +14 -4
- package/templates/skills/genex-threejs-character-controller/references/wiring.md +9 -3
- package/templates/skills/genex-threejs-game-ui/SKILL.md +29 -16
- package/templates/skills/genex-threejs-game-ui/references/style-capsules.md +4 -2
- package/templates/skills/genex-threejs-vehicle-controllers/references/enter-exit.md +7 -0
- package/templates/skills/genex-threejs-visual-validation/SKILL.md +8 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@genex-ai/cli-demo",
|
|
3
|
-
"version": "0.
|
|
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
|
|
678
|
-
* the closing click/keypress handler (browsers only
|
|
679
|
-
* gesture). While paused, canvas clicks do NOT
|
|
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`
|
|
45
|
-
|
|
46
|
-
|
|
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
|
|
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)`
|
|
102
|
-
|
|
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:**
|
|
153
|
-
|
|
154
|
-
|
|
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
|
-
|
|
261
|
-
|
|
262
|
-
|
|
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
|
-
- **
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
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
|
-
-
|
|
474
|
-
|
|
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
|
|
14
|
-
|
|
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.
|
|
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;
|