@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 +1 -1
- package/templates/controllers/character/character-animations.ts +8 -1
- package/templates/controllers/character/meshy/meshy-loader.ts +1 -1
- package/templates/skills/genex-ai-character/SKILL.md +9 -1
- package/templates/skills/genex-threejs-character-controller/SKILL.md +5 -0
- package/templates/skills/genex-threejs-exposure-color-grading/SKILL.md +56 -1
- package/templates/skills/genex-threejs-game-ui/SKILL.md +5 -1
- package/templates/skills/genex-threejs-multiplayer/SKILL.md +7 -5
- package/templates/skills/genex-threejs-skill-router/references/routing-map.md +6 -1
- package/templates/skills/genex-threejs-visual-validation/SKILL.md +46 -5
package/package.json
CHANGED
|
@@ -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)
|
|
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
|
-
|
|
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.
|
|
39
|
+
npm i @genex-ai/multiplayer@^0.11.0
|
|
40
40
|
```
|
|
41
41
|
|
|
42
|
-
> Pin `@^0.
|
|
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.
|
|
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.
|
|
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
|
|
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
|
|
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.
|
|
79
|
-
defect classes are invisible in it and
|
|
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
|
|