@genex-ai/cli-demo 0.57.0-dev.129 → 0.59.0-dev.133

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.57.0-dev.129",
3
+ "version": "0.59.0-dev.133",
4
4
  "description": "Set up your ~/.claude workspace, authorize, create a game project, generate AI assets, and publish (genex CLI).",
5
5
  "type": "module",
6
6
  "bin": {
@@ -351,6 +351,7 @@ export class CharacterAnimations {
351
351
  // Every provided clip by name — lets playOneShot reach clips beyond the 9
352
352
  // locomotion states (any installed pack clip: Punch_*, Sword_*, Sitting_*…).
353
353
  #clipsByName = new Map<string, THREE.AnimationClip>();
354
+ #missingOneShotWarnings = new Set<string>();
354
355
  #oneShotAction: THREE.AnimationAction | null = null;
355
356
  #locomotionOneShotAction: THREE.AnimationAction | null = null;
356
357
  #oneShotOnDone: (() => void) | undefined;
@@ -615,7 +616,13 @@ export class CharacterAnimations {
615
616
  playOneShot(clipName: string, options: PlayOneShotOptions = {}): boolean {
616
617
  if (this.#disposed) return false;
617
618
  const clip = this.#clipsByName.get(clipName);
618
- if (!clip) return false;
619
+ if (!clip) {
620
+ if (!this.#missingOneShotWarnings.has(clipName)) {
621
+ this.#missingOneShotWarnings.add(clipName);
622
+ console.warn(`[character-animations] one-shot clip "${clipName}" is not installed; playOneShot() returned false.`);
623
+ }
624
+ return false;
625
+ }
619
626
 
620
627
  const action = this.mixer.clipAction(clip);
621
628
 
@@ -139,7 +139,7 @@ export function resolveMeshyLocomotion(
139
139
  }
140
140
 
141
141
  export async function loadMeshyCharacter(manifestUrl: string): Promise<MeshyCharacter> {
142
- const response = await fetch(manifestUrl);
142
+ const response = await fetch(manifestUrl, { cache: "no-store" });
143
143
  if (!response.ok) throw new Error(`[meshy-character] ${manifestUrl} returned HTTP ${response.status}`);
144
144
  const manifest = validateManifest(await response.json());
145
145
  const loader = new GLTFLoader();
@@ -17,7 +17,9 @@ npx genex character "stylized desert courier, practical layered clothing"
17
17
  ```
18
18
 
19
19
  The default request uses Meshy 6 to generate and texture an A-pose humanoid,
20
- remeshes and rigs it, adds the preview-reviewed neutral-v2
20
+ validates the rig's arm pose, and retries the full generation once in a strict
21
+ T-pose before requesting controller-pack animations when the A-pose is invalid.
22
+ It remeshes and rigs the accepted model, adds the preview-reviewed neutral-v2
21
23
  idle/walk/run/crouch/jump controller pack,
22
24
  and stores every successful model
23
25
  and clip at permanent Genex asset URLs. It prints the complete Genex-credit
@@ -75,6 +77,12 @@ animation job completes, rerun `genex controller character --character <id>`
75
77
  to refresh `public/assets/meshy-character.json`; existing controller source is
76
78
  preserved unless `--force` is explicitly used.
77
79
 
80
+ Meshy manifests bypass browser cache, so newly installed actions must work
81
+ after preview without asking the player to disable cache.
82
+
83
+ `playOneShot()` returns false and emits one warning when a requested clip is
84
+ absent; treat that as an installation failure, not a successful action.
85
+
78
86
  Load `$genex-threejs-character-controller` for the actual wiring. The install
79
87
  command copies the shared controller plus `character/meshy/meshy-loader.ts`.
80
88
  The manifest points at the current rigged model and compact animation-only GLBs
@@ -45,6 +45,11 @@ dynamic controller stays authoritative for collision, grounding, facing, and
45
45
  world translation. The animation layer poses the visual rig; it never
46
46
  translates the visual root.
47
47
 
48
+ Meshy manifests bypass browser cache, so newly installed actions must work
49
+ after preview without asking the player to disable cache. `playOneShot()`
50
+ returns false and emits one warning when a requested clip is absent; treat
51
+ that as an installation failure, not a successful action.
52
+
48
53
  Because these files are game-owned, never run `genex controller character --force` over an edited
49
54
  fork as a migration strategy. Install a fresh copy elsewhere and port only the named changes.
50
55
 
@@ -22,6 +22,59 @@ Read [references/exposure-grading.md](references/exposure-grading.md)
22
22
  for the exact 64x36 meter, encoded readback, adaptation constants, 32-cube LUT,
23
23
  and signal-ownership ambiguities.
24
24
 
25
+ ## Film grain — add it only if the look calls for it, and author it correctly
26
+
27
+ Film grain is a legitimate look. **Badly-authored grain is the single most common
28
+ post defect** — it reads as a cheap noise sheet laid over the frame, not as
29
+ emulsion, and an owner will call it "some texture rippling over everything." The
30
+ exact shape that ships when an agent hand-rolls it:
31
+
32
+ ```glsl
33
+ // ✗ DO NOT. This is what "very bad grain" looks like in code.
34
+ float g = hash(vUv * 900.0 + fract(uTime) * 133.0) - 0.5; // measured live in a real game
35
+ c += g * 0.045;
36
+ ```
37
+
38
+ Four independent defects in that one line, each visible:
39
+
40
+ 1. **`fract(uTime)` re-seeds every frame → it SHIMMERS.** Measured on the shipped
41
+ game: 64.5% of the pixels on a *static* floor change frame-to-frame from grain
42
+ alone (0% with grain off). This is the loudest defect and the easiest to fix:
43
+ grain is **static** unless you deliberately want a slow film cadence.
44
+ 2. **`vUv * 900` is blocky and stretched.** vUv is 0..1, so ×900 makes ~900 cells
45
+ across the frame — on a 1600×900 window at DPR 2 that's ~3.5×2 px **non-square**
46
+ blocks, not fine grain. Seed on **device pixels** (`gl_FragCoord.xy`), which is
47
+ 1 cell per pixel and aspect-correct for free.
48
+ 3. **Flat additive → it dirties the blacks and blows the highlights.** Real grain
49
+ lives in the **mids**. Weight it by luminance so shadows and speculars stay clean.
50
+ 4. **`hash()` = `fract(sin(dot()))` has visible blotch structure** — wormy
51
+ low-frequency clumps, not grain. Use interleaved-gradient noise (below) or,
52
+ best, sample a tiling **blue-noise texture** at screen resolution.
53
+
54
+ The corrected pass — copy this instead of inventing one:
55
+
56
+ ```glsl
57
+ // Interleaved-gradient noise: cheap, well-distributed, no sin() blotches.
58
+ float ign(vec2 p) {
59
+ return fract(52.9829189 * fract(dot(p, vec2(0.06711056, 0.00583715))));
60
+ }
61
+
62
+ // ...at the end of the grade, before the output conversion:
63
+ float luma = dot(c, vec3(0.2126, 0.7152, 0.0722));
64
+ float lw = 1.0 - abs(2.0 * luma - 1.0); // peaks at mid-grey, 0 at black & white
65
+ float g = ign(gl_FragCoord.xy) - 0.5; // one sample PER DEVICE PIXEL — fine, aspect-correct
66
+ c += g * uGrain * lw; // uGrain ~0.02–0.03, NOT 0.045
67
+ ```
68
+
69
+ - **Static by default** (no `uTime`). If the look truly wants moving grain, reseed
70
+ at a film cadence, not per frame: `ign(gl_FragCoord.xy + floor(uTime * 12.0))` —
71
+ 12 Hz, not 60. Per-frame reseed is the shimmer.
72
+ - **A blue-noise texture beats the hash.** `ign` is the no-asset fallback; a
73
+ 32–64 px tiling blue-noise PNG sampled at `gl_FragCoord.xy / texSize` gives the
74
+ cleanest grain and is what to reach for if you can generate one.
75
+ - **See it in the running game**, and specifically look for shimmer while nothing
76
+ moves (`$genex-threejs-visual-validation` has the two-frozen-frame check).
77
+
25
78
  ## Failure conditions
26
79
 
27
80
  - tone mapping occurs in both materials and post;
@@ -30,7 +83,9 @@ and signal-ownership ambiguities.
30
83
  - adaptation speed is the same toward light and dark;
31
84
  - LUT input/output spaces are undocumented;
32
85
  - sRGB encoding happens twice;
33
- - a display-domain LUT is moved before tone mapping without being rebuilt.
86
+ - a display-domain LUT is moved before tone mapping without being rebuilt;
87
+ - grain is re-seeded per frame (it shimmers), tiled in UV space (it blocks), or
88
+ applied flat instead of luminance-weighted (it dirties the blacks).
34
89
 
35
90
  ## Routing boundary
36
91
 
@@ -353,7 +353,11 @@ Order the HUD by what the player loses the game for ignoring:
353
353
  - **One cohesion layer.** A single full-screen vignette div (a subtle radial
354
354
  gradient darkening the corners, optionally faint grain) over canvas + UI is
355
355
  the cheapest way to make DOM-over-WebGL read as one composed image instead
356
- of a web page floating over a game. Keep it `pointer-events: none`.
356
+ of a web page floating over a game. Keep it `pointer-events: none`. If you add
357
+ grain here, it's a **static** fine-noise tile (a small data-URI at 1:1, not a
358
+ stretched image); grain that belongs to the rendered LOOK goes in the WebGL
359
+ grade instead — see `$genex-threejs-exposure-color-grading` for why per-frame
360
+ `vUv` grain shimmers.
357
361
  - **Desktop first.** Verify at desktop sizes and survive window resizes
358
362
  without clipping; don't design phone layouts or test mobile viewports unless
359
363
  the user asks. Exception: touch *input* is wired by default when a recipe
@@ -36,19 +36,21 @@ calling multiplayer done, run the mandatory
36
36
  ## Install
37
37
 
38
38
  ```bash
39
- npm i @genex-ai/multiplayer@^0.10.2
39
+ npm i @genex-ai/multiplayer@^0.11.0
40
40
  ```
41
41
 
42
- > Pin `@^0.10.2` (not a bare `npm i`): unowned object writes now warn instead of failing silently;
42
+ > Pin `@^0.11.0` (not a bare `npm i`): cross-region invites can select the inviter's exact
43
+ > relay with the optional `url` override; unowned object writes warn instead of failing silently;
43
44
  > live connected-player presence, supplier-form `connect()`
44
45
  > auth, regional relay selection (`getColyseusUrls()` + `urls`)
45
46
  > landed in 0.10; confirmed object controls, snaps, host-tick teardown, and reconnect rebasing
46
47
  > in 0.9. An older resolve does not have those.
47
48
 
48
- This skill targets `@genex-ai/multiplayer` **≥ 0.10.2** (`objects`/`host` since 0.4;
49
+ This skill targets `@genex-ai/multiplayer` **≥ 0.11.0** (`objects`/`host` since 0.4;
49
50
  `matchmake()` since 0.5; private lobbies since 0.7; auto-reconnect + `inputs`/`onHostTick`
50
51
  since 0.8; soft ownership handoff since 0.8.4; confirmed controls, snap epochs, and host-tick
51
- lifecycle guarantees since 0.9; regional relay selection via `getColyseusUrls()` since 0.10).
52
+ lifecycle guarantees since 0.9; regional relay selection via `getColyseusUrls()` since 0.10;
53
+ exact-relay `url` overrides for cross-region invites since 0.11).
52
54
 
53
55
  ## Trust model (say it plainly in your game's copy)
54
56
 
@@ -884,7 +886,7 @@ host-driven saving works as long as ANY account is in the room.
884
886
 
885
887
  ## Checklist
886
888
 
887
- - [ ] `npm i @genex-ai/multiplayer@^0.10.2` (connected presence, supplier auth, confirmed controls, snap epochs, reconnect-safe host ticks, unowned-write warnings); config wired into the build.
889
+ - [ ] `npm i @genex-ai/multiplayer@^0.11.0` (connected presence, supplier auth, confirmed controls, snap epochs, reconnect-safe host ticks, unowned-write warnings, cross-region exact-relay overrides); config wired into the build.
888
890
  - [ ] The plan names one net model, the player-experience reason, start/quorum, late-join/backfill,
889
891
  and below-quorum/end behavior. The agent inferred it unless the experience was genuinely ambiguous.
890
892
  - [ ] `reconnecting`/`reconnected`/`disconnect` render an overlay (don't tear the scene down).
@@ -54,7 +54,12 @@ Three.js release or branch, and do not blindly copy demo architecture.
54
54
  target: match the concept's richness, don't stop at one token pass. "No
55
55
  post at all" is the stock default, not a plan, and "it's only a draft" is
56
56
  not a lower floor. When 2+ effects compose, `$genex-threejs-image-pipeline`
57
- owns the pass ordering;
57
+ owns the pass ordering. **If the stack includes film grain, author it from
58
+ `$genex-threejs-exposure-color-grading`'s grain recipe — static, seeded on
59
+ device pixels, luminance-weighted. A hand-rolled per-frame `vUv` hash
60
+ shimmers across the whole screen and reads as a cheap noise sheet; it is the
61
+ most common post defect in shipped games, and "add grain" without that
62
+ recipe is how it happens;**
58
63
  - **references**: name 2–3 AAA games whose look this game borrows
59
64
  (conventions, lighting mood, palette, post — never trade dress); the
60
65
  same 2–3 the UI gate named, extended from the interface to the scene,
@@ -47,8 +47,13 @@ everything twice.
47
47
  fires. For an animated character, capture idle, walk, run, crouch-idle,
48
48
  crouch-move, and jump. Inspect shoulders, elbows, wrists, and hands as well
49
49
  as the feet: a fully bound non-T-pose can still be stylistically broken.
50
+ Reject shrugging palms-up poses, permanently raised elbows, or a gait whose
51
+ upper-body style contradicts the requested character. “No T-pose” is not
52
+ an animation-quality check.
50
53
  For Meshy characters, crouch passes only when the capsule behavior and a
51
- visibly crouched pose both respond.
54
+ visibly crouched pose both respond. If a control calls `playOneShot()`, a
55
+ false result or missing-clip warning is an installation failure even when
56
+ locomotion continues normally.
52
57
  3. Capture one screenshot of live gameplay — of the **game**, not a sign-in
53
58
  gate or loading screen. A capture of the SDK's "Sign in to play" overlay is
54
59
  NOT gameplay evidence. Evidence captured in local test mode must be labeled
@@ -70,18 +75,23 @@ everything twice.
70
75
  "click to aim/resume" cue appears. Headless caveat: `requestPointerLock`
71
76
  throws in headless Chromium — assert the wiring and the unlocked cue in a
72
77
  screenshot, and say plainly that the lock itself needs one manual click.
73
- 7. **Ask the scene the two things the screenshot cannot answer** (below). Run it
78
+ 7. **Ask the scene the three things the screenshot cannot answer** (below). Run it
74
79
  once, in the same browser you already have open.
75
80
 
76
- ### The screenshot has two blind spots — query them
81
+ ### The screenshot has three blind spots — query them
77
82
 
78
- A capture of the whole arena is taken from one camera at one distance. Two real
79
- defect classes are invisible in it and both shipped to players:
83
+ A capture of the whole arena is taken from one camera at one distance. Three real
84
+ defect classes are invisible in it and all shipped to players:
80
85
 
81
86
  - **Texture scale on faces you aren't looking at.** A shipped game's wall tops
82
87
  were 1:102 — the steel texture read as wooden planks — while the wall *sides*,
83
88
  the faces at eye level, were a near-perfect 1.2:1. Nothing in the screenshot
84
89
  says which is which; the numbers do.
90
+ - **Post-grade grain that shimmers.** A still frame cannot show a temporal
91
+ effect. Animated film grain — a noise term re-seeded by time every frame —
92
+ ripples across the entire screen in motion and reads as a cheap noise sheet
93
+ laid over the game; a screenshot looks fine. It shipped this way in a real game
94
+ (64.5% of a static floor's pixels changed frame-to-frame from grain alone).
85
95
  - **Coplanar surfaces.** Two boxes overlapping with faces at the same height
86
96
  z-fight. It may look stable in a still and flicker the moment the camera
87
97
  moves, so a screenshot is the one tool guaranteed to miss it.
@@ -182,6 +192,37 @@ Run against the shipped BomberDome build, this printed `aspect: 102, m2: 17,
182
192
  mPerTile: "0.17x17.00"` for the wall tops and four coplanar pairs at
183
193
  `max.y=2.40`. Both had been in front of the agent for an hour of screenshots.
184
194
 
195
+ **Grain shimmer — inspect the mechanism, not the pixels.** A pixel diff is
196
+ confounded by the scene's own ambient motion; the deterministic check is to read
197
+ the post stack. Expose the composer in dev alongside the scene
198
+ (`if (import.meta.env.DEV) (window as any).__composer = composer`), then:
199
+
200
+ ```js
201
+ (() => {
202
+ const passes = (window.__composer && window.__composer.passes) || [];
203
+ const suspect = [];
204
+ for (const p of passes) {
205
+ const fs = p.material && p.material.fragmentShader;
206
+ if (!fs) continue;
207
+ const names = Object.keys(p.material.uniforms || {});
208
+ // a grain/noise term, by shader text OR a uGrain-style uniform
209
+ const hasGrain = /grain|\bnoise\b|fract\s*\(\s*sin|\bhash\s*\(|\bign\s*\(/i.test(fs)
210
+ || names.some(u => /grain|noise/i.test(u));
211
+ // a time-ish uniform actually referenced in the fragment shader
212
+ const timeU = names.find(u => /time|frame|seed|tick/i.test(u) && new RegExp('\\b' + u + '\\b').test(fs));
213
+ if (hasGrain && timeU) suspect.push({ pass: p.constructor.name, timeUniform: timeU });
214
+ }
215
+ return suspect; // ideally empty
216
+ })()
217
+ ```
218
+
219
+ A non-empty result means the grade has grain AND a per-frame time uniform — very
220
+ likely the grain is re-seeded every frame and shimmers. Confirm which the time
221
+ term drives; if it's the grain, make it **static** (drop the time term) or reseed
222
+ at ≤12 Hz, seeded on `gl_FragCoord.xy` and luminance-weighted —
223
+ `$genex-threejs-exposure-color-grading` has the recipe. This is the exact defect
224
+ an owner described as "some texture rippling over the whole screen."
225
+
185
226
  Everything deeper (baselines, seed sweeps, mosaics, budgets) belongs to
186
227
  visual-system work — the sequence above.
187
228