@genex-ai/cli-demo 0.56.0-dev.126 → 0.58.0-dev.132

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/dist/index.js CHANGED
@@ -12167,6 +12167,12 @@ var CURATED = {
12167
12167
  reviewStatus: "preview-reviewed"
12168
12168
  },
12169
12169
  [658]: {
12170
+ loop: true,
12171
+ motionPolicy: "controller-loop",
12172
+ controllerSlots: [],
12173
+ reviewStatus: "rejected"
12174
+ },
12175
+ [659]: {
12170
12176
  loop: true,
12171
12177
  motionPolicy: "controller-loop",
12172
12178
  controllerSlots: ["run.forward"],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@genex-ai/cli-demo",
3
- "version": "0.56.0-dev.126",
3
+ "version": "0.58.0-dev.132",
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": {
@@ -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,
@@ -70,18 +70,23 @@ everything twice.
70
70
  "click to aim/resume" cue appears. Headless caveat: `requestPointerLock`
71
71
  throws in headless Chromium — assert the wiring and the unlocked cue in a
72
72
  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
73
+ 7. **Ask the scene the three things the screenshot cannot answer** (below). Run it
74
74
  once, in the same browser you already have open.
75
75
 
76
- ### The screenshot has two blind spots — query them
76
+ ### The screenshot has three blind spots — query them
77
77
 
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:
78
+ A capture of the whole arena is taken from one camera at one distance. Three real
79
+ defect classes are invisible in it and all shipped to players:
80
80
 
81
81
  - **Texture scale on faces you aren't looking at.** A shipped game's wall tops
82
82
  were 1:102 — the steel texture read as wooden planks — while the wall *sides*,
83
83
  the faces at eye level, were a near-perfect 1.2:1. Nothing in the screenshot
84
84
  says which is which; the numbers do.
85
+ - **Post-grade grain that shimmers.** A still frame cannot show a temporal
86
+ effect. Animated film grain — a noise term re-seeded by time every frame —
87
+ ripples across the entire screen in motion and reads as a cheap noise sheet
88
+ laid over the game; a screenshot looks fine. It shipped this way in a real game
89
+ (64.5% of a static floor's pixels changed frame-to-frame from grain alone).
85
90
  - **Coplanar surfaces.** Two boxes overlapping with faces at the same height
86
91
  z-fight. It may look stable in a still and flicker the moment the camera
87
92
  moves, so a screenshot is the one tool guaranteed to miss it.
@@ -182,6 +187,37 @@ Run against the shipped BomberDome build, this printed `aspect: 102, m2: 17,
182
187
  mPerTile: "0.17x17.00"` for the wall tops and four coplanar pairs at
183
188
  `max.y=2.40`. Both had been in front of the agent for an hour of screenshots.
184
189
 
190
+ **Grain shimmer — inspect the mechanism, not the pixels.** A pixel diff is
191
+ confounded by the scene's own ambient motion; the deterministic check is to read
192
+ the post stack. Expose the composer in dev alongside the scene
193
+ (`if (import.meta.env.DEV) (window as any).__composer = composer`), then:
194
+
195
+ ```js
196
+ (() => {
197
+ const passes = (window.__composer && window.__composer.passes) || [];
198
+ const suspect = [];
199
+ for (const p of passes) {
200
+ const fs = p.material && p.material.fragmentShader;
201
+ if (!fs) continue;
202
+ const names = Object.keys(p.material.uniforms || {});
203
+ // a grain/noise term, by shader text OR a uGrain-style uniform
204
+ const hasGrain = /grain|\bnoise\b|fract\s*\(\s*sin|\bhash\s*\(|\bign\s*\(/i.test(fs)
205
+ || names.some(u => /grain|noise/i.test(u));
206
+ // a time-ish uniform actually referenced in the fragment shader
207
+ const timeU = names.find(u => /time|frame|seed|tick/i.test(u) && new RegExp('\\b' + u + '\\b').test(fs));
208
+ if (hasGrain && timeU) suspect.push({ pass: p.constructor.name, timeUniform: timeU });
209
+ }
210
+ return suspect; // ideally empty
211
+ })()
212
+ ```
213
+
214
+ A non-empty result means the grade has grain AND a per-frame time uniform — very
215
+ likely the grain is re-seeded every frame and shimmers. Confirm which the time
216
+ term drives; if it's the grain, make it **static** (drop the time term) or reseed
217
+ at ≤12 Hz, seeded on `gl_FragCoord.xy` and luminance-weighted —
218
+ `$genex-threejs-exposure-color-grading` has the recipe. This is the exact defect
219
+ an owner described as "some texture rippling over the whole screen."
220
+
185
221
  Everything deeper (baselines, seed sweeps, mosaics, budgets) belongs to
186
222
  visual-system work — the sequence above.
187
223