@genex-ai/cli-demo 1.11.1-dev.539 → 1.11.2-dev.540

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
@@ -6160,7 +6160,8 @@ async function detectSurfaceScan(cwd = process.cwd()) {
6160
6160
  squarePoints: [],
6161
6161
  depthRange: [],
6162
6162
  mediaElementAudio: [],
6163
- deferredAudioContext: []
6163
+ deferredAudioContext: [],
6164
+ usesThree: false
6164
6165
  };
6165
6166
  const srcDir = path17.join(cwd, "src");
6166
6167
  let entries;
@@ -6208,6 +6209,7 @@ async function detectSurfaceScan(cwd = process.cwd()) {
6208
6209
  ratio
6209
6210
  });
6210
6211
  }
6212
+ if (/\bfrom\s*["']three(?:\/[^"']*)?["']/.test(content)) found.usesThree = true;
6211
6213
  const threeAudioImport = /import\s+(?:type\s+)?\{[^}]*\bAudio\b[^}]*\}\s+from\s*["']three["']/.test(
6212
6214
  content
6213
6215
  );
@@ -6376,8 +6378,9 @@ function surfaceNudges(log, s) {
6376
6378
  }
6377
6379
  if (s.mediaElementAudio.length) {
6378
6380
  const list = s.mediaElementAudio.map((d) => `${d.where} \u2014 ${d.detail}`).join("; ");
6381
+ const fix = s.usesThree ? `THREE.Audio/PositionalAudio on one AudioListener have none of this \u2014 genex-ai-sfx ships the pooled one-shot, the live-value loop, and the decode-during-the-menu preload.` : `Plain WebAudio has none of this \u2014 one AudioContext (created inside the first user gesture), fetch + decodeAudioData during the menu, an AudioBufferSourceNode per one-shot, and a GainNode per live value. A music bed that only plays/pauses MAY stay an <audio> element; anything whose volume or rate tracks gameplay must not.`;
6379
6382
  log.warn(
6380
- `HTML <audio> elements are carrying this game's sound: ${list}. On iPhone an <audio> element is a handle on the operating system's media pipeline in ANOTHER PROCESS: every write to .volume or .playbackRate is a cross-process call, and play() is slow and asynchronous. A sound driven from a live value \u2014 wheels from speed, engine from RPM \u2014 writes 60-120 of those a second and stutters the whole game, while one-shots arrive late. It disguises itself as a graphics problem perfectly: only while moving, identical at every quality setting, desktop fine. THREE.Audio/PositionalAudio on one AudioListener have none of this \u2014 genex-ai-sfx ships the pooled one-shot, the live-value loop, and the decode-during-the-menu preload.`
6383
+ `HTML <audio> elements are carrying this game's sound: ${list}. On iPhone an <audio> element is a handle on the operating system's media pipeline in ANOTHER PROCESS: every write to .volume or .playbackRate is a cross-process call, and play() is slow and asynchronous. A sound driven from a live value \u2014 wheels from speed, engine from RPM \u2014 writes 60-120 of those a second and stutters the whole game, while one-shots arrive late. It disguises itself as a graphics problem perfectly: only while moving, identical at every quality setting, desktop fine. ${fix}`
6381
6384
  );
6382
6385
  }
6383
6386
  if (s.deferredAudioContext.length) {
@@ -7405,6 +7408,20 @@ async function runGenerate(kind, opts) {
7405
7408
  }
7406
7409
  opts = { ...opts, frameUrl: inlined.dataUri };
7407
7410
  }
7411
+ for (const [field, flag] of [
7412
+ ["firstFrameUrl", "--start-frame/--first-frame"],
7413
+ ["lastFrameUrl", "--last-frame"]
7414
+ ]) {
7415
+ const ref = opts[field];
7416
+ if (kind !== "video" || !ref || isRemoteRef(ref)) continue;
7417
+ const inlined = await inlineLocalImage(ref, flag);
7418
+ if (!inlined.ok) {
7419
+ log.error(inlined.error);
7420
+ process.exitCode = 1;
7421
+ return;
7422
+ }
7423
+ opts = { ...opts, [field]: inlined.dataUri };
7424
+ }
7408
7425
  if (kind === "video" && opts.refs?.length) {
7409
7426
  const refs = [];
7410
7427
  for (const ref of opts.refs) {
@@ -21886,8 +21903,13 @@ ${c.bold("Options for the generators (`model` `sfx` `music` `voice` `texture` `i
21886
21903
  it, model default).
21887
21904
  --frame <url|file> (video) animate from THIS frame back to itself (seamless
21888
21905
  loop) \u2014 an asset URL or a local image file (\u22644 MB).
21889
- --first-frame <url> (video) start frame for a two-frame motion \u2026
21890
- --last-frame <url> (video) \u2026 and its end frame.
21906
+ --start-frame <url|file> (video) continue from this exact frame, no end
21907
+ anchor \u2014 the clip-chaining primitive: extract the last
21908
+ frame of the previous clip (ffmpeg) and pass it here.
21909
+ Anchors are compositional guidance, not pixel-pinning;
21910
+ chain from REAL extracted frames so drift can't stack.
21911
+ --first-frame <url|file> (video) start frame; alone it means --start-frame \u2026
21912
+ --last-frame <url|file> (video) \u2026 add this to also anchor the end frame.
21891
21913
  --ref <url|file> (video, repeatable \xD79) subject/style reference images \u2014
21892
21914
  cite them in the prompt as "Image 1"\u2026"Image N"; keeps
21893
21915
  the same character/style across many clips. Cannot be
@@ -22086,7 +22108,8 @@ ${c.bold("Generate")} typ
22086
22108
  --transparent \u2026with a real alpha channel (sprites, icons, logos).
22087
22109
  --edit <path|url> \u2026edit an existing image with the prompt.
22088
22110
  --remove-bg / --bg-mode <m> \u2026cut the background (sprite|glyph|sheet|matte).
22089
- genex video "<prompt>" A video (mp4). --loop, --frame <img>. ${cr("video")}
22111
+ genex video "<prompt>" A video (mp4). --loop, --frame <img>, ${cr("video")}
22112
+ --start-frame <img> (continue a clip).
22090
22113
  genex texture "<prompt>" A tiling PBR texture set. --terrain. ${cr("texture")}
22091
22114
  genex sfx "<prompt>" A sound effect (mp3). --duration. ${cr("sfx")}
22092
22115
  genex music "<prompt>" A looping instrumental track (mp3). ${cr("music")}
@@ -22146,6 +22169,51 @@ ${c.bold("Examples")}
22146
22169
  Hosting, publishing, multiplayer, remixing and custom domains are the Genex
22147
22170
  platform \u2014 build a game there from one prompt: ${c.cyan("https://genex.games")}
22148
22171
  `;
22172
+ var COMMAND_HELP = {
22173
+ video: `${c.bold("genex video")} \u2014 generate a video clip (mp4, MiniMax H3 Max \u2014 the flagship; plain H3 for seamless loops and 2k/4k; native stereo audio on every non-loop clip).
22174
+
22175
+ ${c.bold("Modes")}
22176
+ genex video "<prompt>" text-to-video (16:9).
22177
+ genex video "<prompt>" --start-frame <img> continue from this frame \u2014 the
22178
+ clip-chaining primitive.
22179
+ genex video "<prompt>" --frame <img> animate from the frame back to
22180
+ itself (seamless loop; menus).
22181
+ genex video "<prompt>" --first-frame <img> --last-frame <img>
22182
+ two-frame motion (both anchored).
22183
+ genex video "<prompt>" --ref <img> [--ref \u2026\xD79] same subject/style across many
22184
+ clips ("Image 1"\u2026"Image N").
22185
+ genex video "<prompt>" --loop prompt-only seamless loop (no
22186
+ conditioning frame; Luma Ray-2).
22187
+
22188
+ ${c.bold("Flags")}
22189
+ --start-frame <url|file> continue-from anchor; local files inline (\u22644 MB).
22190
+ --frame / --first-frame / --last-frame <url|file> ditto.
22191
+ --ref <url|file> repeatable \xD79; not combinable with frames/--loop.
22192
+ --duration <s> 5-15 (integer seconds; H3's own band). Default 5,
22193
+ loops/pairs 8. Longer scenes: chain clips.
22194
+ --resolution <r> 480p | 768p (default, top native) | 2k | 4k.
22195
+ --no-wait enqueue only; attach later with genex wait <id>.
22196
+
22197
+ Aspect: text/ref clips are 16:9. Frame-conditioned clips FOLLOW THE SOURCE
22198
+ IMAGE \u2014 a square image (genex image --aspect square) yields a square video.
22199
+
22200
+ ${c.bold("Chaining clips (playable films, cutscene sequences)")}
22201
+ Frame anchors are COMPOSITIONAL GUIDANCE, not pixel-pinning \u2014 the model
22202
+ repaints the frame (measured ~21 dB vs the supplied image; identical would be
22203
+ ~35-40 dB). Chains still hold if every clip starts from the PREVIOUS CLIP'S
22204
+ REAL last frame, because drift never accumulates:
22205
+ ffmpeg -sseof -0.2 -i prev.mp4 -update 1 -q:v 1 last.png
22206
+ genex video "<what changes next>" --start-frame last.png
22207
+ Cut clip-to-clip (or crossfade ~200 ms to hide the residual repaint). Keep
22208
+ in-game <video> muted by default \u2014 every H3 clip carries audio.
22209
+
22210
+ ${c.bold("Content filter")}
22211
+ The provider rejects anatomy-invasion phrasing (cables/wires/needles entering
22212
+ a body) as body horror \u2014 describe the object or machine instead: "a
22213
+ statue-like figure threaded into the wall" passes where "cables entering her
22214
+ spine" fails. Rejections are never retryable; reword and re-run.
22215
+ `
22216
+ };
22149
22217
  function parseArgs(argv) {
22150
22218
  const parsed = {
22151
22219
  command: void 0,
@@ -22183,6 +22251,7 @@ function parseArgs(argv) {
22183
22251
  "--voice",
22184
22252
  "--voice-id",
22185
22253
  "--frame",
22254
+ "--start-frame",
22186
22255
  "--first-frame",
22187
22256
  "--last-frame",
22188
22257
  "--ref",
@@ -22716,6 +22785,7 @@ function applyValueFlag(options, flag, value) {
22716
22785
  case "--video":
22717
22786
  options.video = value;
22718
22787
  break;
22788
+ case "--start-frame":
22719
22789
  case "--first-frame":
22720
22790
  options.firstFrameUrl = value;
22721
22791
  break;
@@ -22777,7 +22847,8 @@ async function main() {
22777
22847
  const mode = await workspaceMode();
22778
22848
  setWorkspaceHeader(mode === "tools" ? "tools" : null);
22779
22849
  if (parsed.help || parsed.command === void 0) {
22780
- log.plain(mode === "tools" ? HELP_TOOLS : HELP);
22850
+ const perCommand = parsed.command === void 0 ? void 0 : COMMAND_HELP[parsed.command];
22851
+ log.plain(perCommand ?? (mode === "tools" ? HELP_TOOLS : HELP));
22781
22852
  return;
22782
22853
  }
22783
22854
  const isBlenderMcp = parsed.command === "blender" && parsed.options.name === "mcp";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@genex-ai/cli-demo",
3
- "version": "1.11.1-dev.539",
3
+ "version": "1.11.2-dev.540",
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": {
@@ -146,8 +146,19 @@ See `$genex-threejs-multiplayer` for the `shared` channel rules and the room API
146
146
  - `--frame <url|file>` — one generated image (or a local file ≤4 MB) as BOTH
147
147
  first and last frame — the seamless-loop mode (motion must return to its
148
148
  start; the seam is mathematically exact).
149
- - `--first-frame <url>` / `--last-frame <url>` two-frame motion between two
150
- stills (a genuine state change a door opens, day turns to night).
149
+ - `--start-frame <url|file>` continue from this frame, no end anchor — the
150
+ clip-chaining primitive (see below). Local files inline like `--frame`.
151
+ - `--first-frame <url|file>` / `--last-frame <url|file>` — two-frame motion
152
+ between two stills (a genuine state change — a door opens, day turns to
153
+ night). `--first-frame` alone means `--start-frame`.
154
+
155
+ **Frame anchors are compositional guidance, not pixel-pinning.** The model
156
+ repaints every anchor — the clip's first frame lands near the supplied image
157
+ (same scene, same composition) but not ON it, and regenerating doesn't change
158
+ that; it is how the mode works. Plan for it: chains stay coherent when each
159
+ clip starts from the previous clip's REAL frame (below), and a supplied
160
+ `--last-frame` steers where motion ends rather than guaranteeing the exact
161
+ pixels.
151
162
  - `--ref <url|file>` — repeatable, up to 9 subject/style reference images.
152
163
  Cite each in the prompt by its order: "Image 1 is the hero — she walks into
153
164
  frame…". This is how the SAME character or art style holds across many clips
@@ -168,8 +179,21 @@ evidence detective game, a video-book. The pattern that makes it hold together:
168
179
  - **Branching scenes**: generate one clip per story node at build time, ship
169
180
  the mp4 URLs in a scene-graph JSON, play them full-screen with DOM choice
170
181
  buttons; preload the clips reachable from the current node while it plays.
171
- - **Visual continuity**: chain scenes with `--frame`/`--first-frame` so each
172
- clip opens where the last one ended.
182
+ - **Visual continuity chain from REAL frames**: extract the previous clip's
183
+ actual last frame and seed the next clip with it:
184
+
185
+ ```bash
186
+ ffmpeg -sseof -0.2 -i prev.mp4 -update 1 -q:v 1 last.png
187
+ npx genex video "she turns and walks toward the far door" --start-frame last.png
188
+ ```
189
+
190
+ Never chain from the image you WISHED the clip ended on — anchors are
191
+ repainted (above), so the wish and the clip disagree and every join jumps.
192
+ Chaining from the real frame keeps drift from accumulating; the residual
193
+ repaint at each cut is small, and a ~200 ms crossfade between the two
194
+ `<video>` elements hides it entirely. Write chained prompts as CHANGE ONLY
195
+ ("she turns…", "the light flickers out") — the start frame already says
196
+ everything else.
173
197
  - **Cast consistency**: give every scene the same `--ref` images of your
174
198
  protagonist and key locations — the single biggest quality lever for any
175
199
  multi-clip story.
@@ -181,6 +205,12 @@ evidence detective game, a video-book. The pattern that makes it hold together:
181
205
  - **"Not authorized"** — run `npx @genex-ai/cli-demo@dev init` first (it writes your `GENEX_TOKEN`).
182
206
  - **"Prompt rejected"** — the provider's content-safety filter blocked the prompt.
183
207
  This is non-retryable; retrying the same wording fails again. Rewrite the prompt.
208
+ The measured false-positive class is anatomy being pierced or entered
209
+ (cables/wires/needles into a body — biomech and cyberpunk vocabulary trips
210
+ it). Describe the object or machine instead of the anatomy: "a statue-like
211
+ figure threaded into the wall" passes where "cables entering her spine"
212
+ fails. Word it that way on the FIRST try — each rejection still ends a
213
+ billed-then-refunded round trip.
184
214
  - **Nothing plays / black surface** — the first `video.play()` must run inside a user
185
215
  gesture (click/keydown); confirm it's called and its promise rejection is logged.
186
216
  - **Tainted-source / security error** — set `video.crossOrigin = "anonymous"` before
@@ -33,7 +33,22 @@ npx genex video "camera drifts slowly forward, banners stirring" --frame ./asset
33
33
  ```
34
34
 
35
35
  - `--frame <path|url>` — animate FROM this frame back to itself, i.e. a seamless loop. Local file (≤ 4 MB) or a previous generation's URL.
36
- - `--first-frame <url>` / `--last-frame <url>`a two-frame motion between them.
36
+ - `--start-frame <path|url>` continue from this frame, no end anchor the clip-chaining primitive.
37
+ - `--first-frame <path|url>` / `--last-frame <path|url>` — a two-frame motion between them. All frame anchors take local files.
38
+
39
+ Frame anchors are **compositional guidance, not pixel-pinning** — the model
40
+ repaints the frame (same scene and composition, not the same pixels), and
41
+ regenerating doesn't change that. Chained clips (a cutscene sequence, a
42
+ branching video story) stay coherent when each clip starts from the previous
43
+ clip's REAL last frame, so drift never accumulates:
44
+
45
+ ```bash
46
+ ffmpeg -sseof -0.2 -i ./assets/prev.mp4 -update 1 -q:v 1 last.png
47
+ npx genex video "she turns and walks toward the far door" --start-frame last.png
48
+ ```
49
+
50
+ Write chained prompts as CHANGE ONLY — the start frame already says everything
51
+ else. Cut clip-to-clip, or crossfade ~200 ms to hide the residual repaint.
37
52
 
38
53
  ## Options
39
54
 
@@ -61,5 +76,6 @@ command bills a NEW clip** — it is never a way to check on one already running
61
76
  ## Troubleshooting
62
77
 
63
78
  - **It failed** — video fails server-side more often than any other lane, and every attempt is minutes. The CLI counts failures in this folder and tells you when to stop: after the second, use a still image instead and say so in one plain line. A third attempt bills the same and returns the same.
79
+ - **"Prompt rejected"** — the content-safety filter; never retryable with the same wording. The measured false-positive class is anatomy being pierced or entered (cables/wires/needles into a body — biomech vocabulary trips it). Describe the object or machine instead: "a statue-like figure threaded into the wall" passes where "cables entering her spine" fails.
64
80
  - **Out of credits** — the error prints balance, price and refill date. Relay it; don't retry.
65
81
  - **Anything else** — `npx genex doctor` reports sign-in, credits, and whether the video lane is live.