@genex-ai/cli-demo 0.78.1-dev.203 → 0.80.2-dev.213

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.
@@ -128,15 +128,17 @@ const gltfLoader = createGltfLoader(renderer); // meshopt always; KTX2 when the
128
128
  const gltf = await loadModelWithFallback(
129
129
  MODEL_URL, tier, (u) => gltfLoader.loader.loadAsync(u), { ktx2: gltfLoader.ktx2 },
130
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:
131
+ // Provider PBR sometimes ships mirror-metal (metalness~1 + near-zero roughness)
132
+ // that reflects the sky env and swims with camera motion. Tame ONLY that extreme
133
+ // — do NOT flatten every material to 0.6 (it dulls legitimately metallic props):
133
134
  gltf.scene.traverse((o) => {
134
135
  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;
136
+ if (!m?.isMeshStandardMaterial) return;
137
+ if (m.metalness > 0.85 && m.roughness < 0.2) {
138
+ m.metalness = 0.7;
139
+ m.roughness = Math.max(m.roughness, 0.3);
139
140
  }
141
+ m.envMapIntensity = Math.min(m.envMapIntensity, 0.8);
140
142
  });
141
143
  ```
142
144
 
@@ -162,7 +164,7 @@ not the player's account. Default Auto.
162
164
  at most 2 cascades on phones (`$genex-threejs-shadow-systems`).
163
165
  - Post: phone floor is a BUILT tone-mapping/output pass (`postLevel: 'light'`
164
166
  adds FXAA/vignette); SSAO, volumetrics, and DoF are desktop-tier only
165
- (`$genex-threejs-skill-router` owns the floor wording).
167
+ (`$genex-game-director`'s routing map owns the floor wording).
166
168
  - Particles/scatter: multiply counts by `tier.particleScale`; render heavy
167
169
  transparency at half resolution and upsample.
168
170
  - Animation: distant mixers update at 1/2–1/4 rate; multiplayer remotes above
@@ -57,10 +57,8 @@ Meshy Image-to-3D first produces an unremeshed high-detail model. Show its
57
57
  front, back, left, and right views and report its measured face count. Preserve
58
58
  that model in R2. Before rigging, ask the user to approve a separate
59
59
  10,000-face triangle remesh. The 10k remesh—not the high-detail source—is
60
- rigged and animated. (For these approvals, use your environment's structured
61
- question tool when it has one Claude Code: `AskUserQuestion`; Codex:
62
- `request_user_input`; if it has none, e.g. Cursor, a short numbered list in
63
- chat.)
60
+ rigged and animated. (For these approvals, use your question tool when you
61
+ have one; if you have none, a short numbered list in chat.)
64
62
 
65
63
  That is a separate, **same-rig Meshy-native lane**. Its animation-only GLBs are
66
64
  accepted only when their skeleton signature matches the active character
@@ -320,11 +318,16 @@ it. Simulating remote players' physics locally guarantees divergence — every
320
318
  client would compute a different world. Never render every remote with your
321
319
  own avatar file: players picked their looks, show them.
322
320
 
323
- - Publish your own `currPos` + `currQuat` as a four-number quaternion on the fixed 10–20 Hz tick,
324
- not per frame. Never reduce multiplayer rotation to scalar yaw.
325
- - To animate remotes, sync the six animation booleans (`isOnGround`,
326
- `isFalling`, `isMoving`, `runActive`, `jumpActive`, `crouchActive`) and feed
327
- them to a per-remote `CharacterAnimations` see the animations reference.
321
+ - **Publish `character.netState()`** on the fixed 10–20 Hz tick (never per frame):
322
+ `room.me.set(character.netState())`. It bundles the network-safe position, a four-number
323
+ quaternion, and the six animation booleans in one call. It uses `netPos`, **not** `currPos` —
324
+ `currPos` is the raw physics capsule whose Y bobs on the float-suspension spring, so publishing
325
+ it makes a *standing* remote player visibly bob up and down. `netState()` snaps the grounded Y to
326
+ the settled height (raw while airborne, so jumps still arc). Never reduce rotation to a scalar yaw.
327
+ - On each remote, apply the SDK's **smoothed** copy with
328
+ `applyNetState(remoteObject, players.get(id).state)` (sets position + rotation), and feed the same
329
+ six booleans (`onGround`, `falling`, `moving`, `running`, `jumping`, `crouching`) to that remote's
330
+ `CharacterAnimations` — see the animations reference. Never publish `currPos` directly.
328
331
  - Only the owning client runs `MotionActionDriver`. Remotes play the same
329
332
  one-shot event while following smoothed owner-authored position/rotation;
330
333
  their animation mixer never moves them through the world.
@@ -34,26 +34,19 @@ Guest sessions have **no** overlay — the game just plays.
34
34
  ## Install
35
35
 
36
36
  ```bash
37
- npm i @genex-ai/embed-sdk @sentry/browser
37
+ npm i @genex-ai/embed-sdk
38
38
  ```
39
39
 
40
- (`@sentry/browser` powers the crash reporting + session replay wiring below
41
- it's an optional peer of the SDK, and every Genex game installs it. Already
42
- installed if you followed Step 3 of the scaffold; repeated here so this skill
43
- is self-contained.)
40
+ (Already installed if you followed Step 3 of the scaffold; repeated here so
41
+ this skill is self-contained.)
44
42
 
45
43
  ## Bootstrap (required, every game)
46
44
 
47
45
  ```ts
48
- // main.ts — the FIRST things in the boot sequence, before any other game code
49
- import { initGameSentry } from "@genex-ai/embed-sdk/sentry";
46
+ // main.ts — the FIRST thing in the boot sequence, before any other game code
50
47
  import { initEmbed } from "@genex-ai/embed-sdk";
51
48
  import { GENEX } from "./genex.config";
52
49
 
53
- // Crash reporting + session replay FIRST — so even a failure inside the auth
54
- // boot below gets reported. One required field; never pass tokens to it.
55
- initGameSentry({ slug: GENEX.slug });
56
-
57
50
  initEmbed({
58
51
  slug: GENEX.slug,
59
52
  apiUrl: GENEX.apiUrl,
@@ -153,29 +146,6 @@ Server write limits (per player, per minute): **60 player-saves, 120
153
146
  world-saves, 30 score submits**. A debounced ~1/sec checkpoint never gets near
154
147
  them — only a save-per-frame loop does (it surfaces as HTTP 429).
155
148
 
156
- From `@genex-ai/embed-sdk/sentry` (crash reporting; exactly these two):
157
-
158
- - `initGameSentry({ slug, dsn?, environment? })` — call once, BEFORE
159
- `initEmbed()`. Only `slug` is required; the shared Genex Sentry project DSN
160
- is built in. Errors, tracing, and session replay all start here; the current
161
- player (account or guest) is attached automatically (no code needed).
162
- - `sentryCanvasSnapshot(canvas)` — session replay records the DOM, not the 3D
163
- canvas; call this once per frame at the END of the render loop so replays
164
- show actual gameplay. Works for BOTH WebGL and WebGPU renderers; internally
165
- throttled, so calling at 60fps is fine. On TOUCH devices it is a deliberate
166
- no-op (and session replay/tracing sample down): each capture is a full-canvas
167
- GPU readback, exactly the overhead phones get memory-killed for — mobile
168
- replays are DOM-only by design, on-error replays still record everywhere:
169
-
170
- ```ts
171
- function animate() {
172
- requestAnimationFrame(animate);
173
- // ...game update...
174
- renderer.render(scene, camera);
175
- sentryCanvasSnapshot(renderer.domElement); // AFTER render, same frame
176
- }
177
- ```
178
-
179
149
  ## Saving progress (per-player — every player has their own slot)
180
150
 
181
151
  Use the SDK helpers; never hand-roll fetch calls to the state API. Progression,
@@ -224,16 +194,6 @@ their Genex account) and keep-best — submitting a worse score changes nothing
224
194
  post when they sign in. Send a consistent `mode` per board. Scores are
225
195
  client-reported (arcade-style trust) — don't present them as anti-cheat.
226
196
 
227
- ## Crash reporting rules
228
-
229
- - `initGameSentry` has token scrubbing built in (the sign-in return-trip pass
230
- in URLs is redacted automatically). Never wrap, reimplement, or bypass it —
231
- and never add Sentry options that capture network request/response bodies.
232
- - Don't call `Sentry.init` yourself or add a second error reporter —
233
- `initGameSentry` is the one entry point.
234
- - Manual capture is fine where a try/catch swallows a real bug:
235
- `import * as Sentry from "@sentry/browser"; Sentry.captureException(err)`.
236
-
237
197
  ## NEVER log the tokens
238
198
 
239
199
  **NEVER log the return value of `getEmbedToken()` or `getColyseusAuth()` — not
@@ -328,10 +288,8 @@ Rules:
328
288
 
329
289
  ## Checklist
330
290
 
331
- - [ ] `initGameSentry({ slug: GENEX.slug })` is the very first call in `main.ts`.
332
- - [ ] `initEmbed(...)` follows it, with all three config fields.
333
- - [ ] `sentryCanvasSnapshot(renderer.domElement)` runs after `renderer.render()`
334
- in the main loop (WebGL and WebGPU alike).
291
+ - [ ] `initEmbed(...)` is the very first call in `main.ts`, with all three
292
+ config fields.
335
293
  - [ ] `genex.config.ts` includes `dashboardOrigins` (from `.genex/project.json`).
336
294
  - [ ] Multiplayer and player-name UI await `waitForPlayer()` — NEVER
337
295
  `waitForAuth()` (guests would hang forever). Both `connect()` and
@@ -48,11 +48,10 @@ Rules that make the contract real:
48
48
  - **Scope belongs to the user.** Building a small first slice is the right
49
49
  ORDER (`$genex-threejs-game-ui`'s v0 beat still applies) — but the slice is
50
50
  a milestone on the way to the contract, never a quiet replacement for it.
51
- If the full ask genuinely doesn't fit, shrinking any line is a structured
51
+ If the full ask genuinely doesn't fit, shrinking any line is a
52
52
  question to the user with real options — never a silent cut justified as
53
- "standard practice". (Use your environment's structured question tool when
54
- it has one Claude Code: `AskUserQuestion`; Codex: `request_user_input`;
55
- if it has none, e.g. Cursor, a short numbered list in chat.)
53
+ "standard practice". (Use your question tool when you have one; if you
54
+ have none, a short numbered list in chat.)
56
55
  - **Minute ten is the design test.** If the honest answer is "the same sixty
57
56
  seconds, again", the contract needs another beat (a new area unlocks, a
58
57
  quest chain escalates, a build comes online) before any polish work.
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: genex-threejs-game-ui
3
- description: Design the 2D interface of a Genex Three.js game — plan the full screen set up front (loader, menu, HUD, pause/win/lose, lobby) under one shared art direction, then build it as an animated DOM overlay. Use for every new game before writing UI code, and whenever the game needs on-screen text, meters, buttons, or menus, or the interface reads as a generic dashboard, covers the action, or shifts as numbers change.
3
+ description: Design the UI HUD interface of a Genex Three.js game — plan the full screen set up front (loader, menu, HUD, pause/win/lose, lobby) under one shared art direction, then build it as an animated DOM overlay. Use for every new game before writing UI code, and whenever the game needs on-screen text, meters, buttons, or menus, or the interface reads as a generic dashboard, covers the action, or shifts as numbers change.
4
4
  ---
5
5
 
6
6
  # Genex Three.js Game UI
@@ -43,10 +43,7 @@ The table lists screens; **elements are inventoried separately**. Walk the
43
43
  whole loop in your head — loader → menu → spawn → action → pickup → damage →
44
44
  death → retry → win — and write down EVERY on-screen element the player will
45
45
  ever see: the reticle and each of its states, aim/interact cues, toasts,
46
- damage numbers, kill feeds, timers, countdowns, pickup popups… Each element
47
- gets a treatment from the brief. **Nothing ships looking like default
48
- browser CSS** — an element styled like a bare `<div>` or a system button is
49
- a bug wherever it appears, listed or not.
46
+ damage numbers, kill feeds, timers, countdowns, pickup popups.
50
47
 
51
48
  **2. One shared style brief — for the WHOLE game, not just the UI.** Write it
52
49
  once — 4–5 named hues, materials, one display + one body font, mood — and
@@ -61,17 +58,12 @@ is LOADED for real — a Google Fonts `<link>` (or `@font-face`) in
61
58
  ships as a system-stack fallback (`Arial Black`, `Impact`) is the same bug in
62
59
  type.
63
60
 
64
- **3. Name your referencesAAA, by name.** Pick the closest capsule in
65
- [references/style-capsules.md](references/style-capsules.md), name 2–3 top
66
- AAA games of the genre, and state in one line which structural conventions
67
- you're borrowing. The bar is what those games ship, not "good enough for a
68
- demo". Conventions only — never logos, exact layouts, or trade dress. The
69
- same 2–3 names carry into the router's visual-direction gate for the scene.
70
- Then turn references + brief into ONE **concept mockup** — the game concept
61
+ **3. Make ONE concept mockup** — the game concept
71
62
  and the HUD Stage-1 mockup are the SAME image, generated once (never a
72
63
  separate UI-free concept first): a single image of a PLAYABLE MOMENT of this
73
64
  game with its complete HUD composited over it, built with `$genex-ai-hud`'s
74
- Stage-1 prompt template. The scene half of the prompt comes from the game
65
+ Stage-1 prompt template. For genre conventions to borrow, skim
66
+ [references/style-capsules.md](references/style-capsules.md). The scene half of the prompt comes from the game
75
67
  contract in TEXT, not from a prior image: what the player is DOING
76
68
  mid-action (the verb), what threatens them right now (enemy silhouettes),
77
69
  what they are chasing (the objective — a finish gate, a goal, a pickup),
@@ -95,9 +87,9 @@ clickable links saying which one you picked and why — a URL is invisible in a
95
87
  terminal, and "do you like it?" with no picture in front of the user is the
96
88
  #1 way this checkpoint fails (they end up digging logs for the file path).
97
89
 
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
90
+ **Then ask for the yes with your question tool** — the one that shows the
91
+ user clickable options; a short numbered list in chat only where there is
92
+ none. ONE question — "this is roughly
101
93
  how the game and its HUD will look — keep this direction, or change
102
94
  something?" — with concrete keep / change options. This confirmation is
103
95
  REQUIRED for every game: the concept sets the STYLE every later asset
@@ -155,11 +147,10 @@ an invented mechanic, or the absence of a real one, from a picture.
155
147
 
156
148
  **4. Ask only when genuinely ambiguous.** If the concept pins the mood (a
157
149
  "gothic horror dungeon crawler" pins it), decide and state the plan in one
158
- line. Only when the art direction is truly open, ask ONE structured question
150
+ line. Only when the art direction is truly open, ask ONE question
159
151
  with 2–3 concrete directions, each naming its palette + font pair — using
160
- your environment's structured question tool when it has one (Claude Code:
161
- `AskUserQuestion`; Codex: `request_user_input`); if it has none (e.g.
162
- Cursor), a short numbered list in chat. Never ask about the screen
152
+ your question tool when you have one; if you have none, a short numbered
153
+ list in chat. Never ask about the screen
163
154
  inventory — it derives from the game type.
164
155
 
165
156
  **5. Style follows THIS game's concept.** The examples in every Genex skill
@@ -463,43 +454,18 @@ Order the HUD by what the player loses the game for ignoring:
463
454
  - **Contrast against the real scene.** Test text over the brightest AND
464
455
  darkest areas of actual gameplay; a soft dark plate or text-shadow beats
465
456
  restyling per level.
466
- - **CSS-shaped corners must survive their contents.** `border-radius`,
467
- `clip-path`, and `mask` are all fair ways to shape a panel or button —
468
- including a chamfered/notched "hi-tech" corner in pure CSS. What is
469
- non-negotiable is the execution: the cut must never shear off anything
470
- that sits near the corner (text, padding, the focus ring, a glow) keep
471
- enough inner padding that content clears the cut shape and the result
472
- must be clean, not crooked: no jagged aliased diagonals, no half-clipped
473
- borders or shadows, no text colliding with an edge or truncating. The
474
- same bar applies to CSS plates. Verify the corners at real sizes over
475
- real gameplay. When the art direction wants a genuinely ornamented
476
- frame, a generated frame sprite (`$genex-ai-hud` chrome, or a Tier-3
477
- 9-slice panel) is still the richer tool. The masked-fill HUD reveal and
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
+ - **Panel and button corners come from `border-radius` or a generated frame
458
+ sprite — never a raw `clip-path`/`mask` chamfer.** A CSS-cut angular corner
459
+ is the recurring "cut corners" defect: the clip shears off borders, shadows,
460
+ and any content that sits near the corner, and it re-breaks the instant the
461
+ padding, font, or value length changes so it can only be held together by
462
+ a per-build visual check that is easy to skip. It is not worth that fragility.
463
+ For a soft corner use `border-radius` (it keeps its `border`/`box-shadow`
464
+ natively). For a genuinely angular or ornamented "hi-tech" frame, generate it
465
+ as chrome (`$genex-ai-hud`, or a Tier-3 9-slice panel) and lay the DOM over
466
+ it that reads richer and physically cannot shear. `mask`/`clip-path` stay
467
+ reserved for their ONE established HUD use: the masked-fill progress reveal
468
+ driven by `genex ui` masks. Never for corner shaping.
503
469
  - **One cohesion layer.** A single full-screen vignette div (a subtle radial
504
470
  gradient darkening the corners, optionally faint grain) over canvas + UI is
505
471
  the cheapest way to make DOM-over-WebGL read as one composed image instead
@@ -11,10 +11,13 @@ says what it's made of in THIS game.
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 for bare
13
13
  text over arbitrary scenes (never a second plate stacked behind an opaque
14
- frame sprite), and panel/button corners executed cleanly — `border-radius`,
15
- a CSS `clip-path`/`mask` shape, or a generated frame all work, as long as the
16
- cut never clips content (text, padding, glow), stays free of jagged-edge
17
- artifacts, and text never collides or truncates.
14
+ frame sprite), and panel/button corners done the durable way — `border-radius`
15
+ for soft corners (it keeps its `border`/`box-shadow` natively), or a generated
16
+ frame (`$genex-ai-hud` chrome / a Tier-3 9-slice panel) with the DOM laid over
17
+ it for genuinely angular looks. Raw CSS `clip-path`/`mask` corner cuts are
18
+ banned per the skill — they shear borders, shadows, and near-corner content,
19
+ and re-break on any padding/font change; `mask`/`clip-path` stay reserved for
20
+ their ONE established HUD use, the masked-fill progress reveal.
18
21
 
19
22
  ## Fantasy / action RPG
20
23
 
@@ -67,8 +67,7 @@ Infer this yourself when the experience is clear. Do **not** make the player cho
67
67
  preset, or config. Ask one plain-language question only when the design genuinely supports both
68
68
  models and the answer changes the experience — for example: *"Should this be one ongoing arena
69
69
  people drop into, or a fresh fair match that waits for everyone and then starts together?"*
70
- (Use your environment's structured question tool when it has one Claude Code:
71
- `AskUserQuestion`; Codex: `request_user_input`; if it has none, e.g. Cursor, a short numbered
70
+ (Use your question tool when you have one; if you have none, a short numbered
72
71
  list in chat.)
73
72
 
74
73
  | Player experience | Model | Why |
@@ -621,6 +620,12 @@ than silently disappearing. The budget math that matters:
621
620
  `players.get(id).state` directly (already smoothed); every object from `objects.get(id).state`.
622
621
  4. **Create-or-reuse one mesh per id**; remove a player's mesh on `'leave'`.
623
622
 
623
+ > **Physics character?** If your player is the `genex controller character` capsule, do NOT
624
+ > hand-build the state from the body position — publish `room.me.set(character.netState())` and
625
+ > apply remotes with `applyNetState(mesh, players.get(id).state)`. The controller's raw `currPos.y`
626
+ > bobs on its float-suspension spring; `netState()` publishes a settled ground Y so a standing remote
627
+ > doesn't bob. See `$genex-threejs-character-controller` → "Multiplayer rule".
628
+
624
629
  ## Rotation: sync a quaternion, not an angle
625
630
 
626
631
  Send rotation as a 4-number quaternion `q: mesh.quaternion.toArray()`; on the remote do
@@ -40,10 +40,8 @@ Meshy Image-to-3D first produces an unremeshed high-detail model. Show its
40
40
  front, back, left, and right views and report its measured face count. Preserve
41
41
  that model in R2. Before rigging, ask the user to approve a separate
42
42
  10,000-face triangle remesh. The 10k remesh—not the high-detail source—is
43
- rigged and animated. (For these approvals, use your environment's structured
44
- question tool when it has one Claude Code: `AskUserQuestion`; Codex:
45
- `request_user_input`; if it has none, e.g. Cursor, a short numbered list in
46
- chat.)
43
+ rigged and animated. (For these approvals, use your question tool when you
44
+ have one; if you have none, a short numbered list in chat.)
47
45
 
48
46
  The selected high-detail model remains in a neutral A-pose before animation.
49
47
  Record evidence that the user saw its four views and face count before