@genex-ai/cli-demo 1.11.1-dev.539 → 1.11.3-dev.541

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
@@ -972,6 +972,7 @@ Important note: put soul into your creations, with many details and love. Aim to
972
972
  17. Input directions match their labels: A/\u2190 moves or turns the player screen-LEFT, D/\u2192 screen-RIGHT, mouse-up looks up, and drag-pan axes share ONE convention. The cursor is either the gameplay tool (RTS, card, builder) or locked away during play \u2014 keyboard-only games included. Check it in every milestone's smoke pass.
973
973
  18. NEVER delete, empty, move, rename, or overwrite anything you did not create yourself. This folder may hold the player's own reference images, notes, sketches, or an earlier attempt \u2014 files that exist nowhere else and have no undo, no trash, no backup. A non-empty folder is normal and is NEVER something to clean up, and "start clean" is never a reason. That rules out \`rm\`/\`rm -rf\`, \`git clean\`, \`git checkout -- .\`, \`git reset --hard\` over their work, deleting to resolve a conflict or a stuck interactive prompt, and every setup tool's offer to empty a directory (\`--force\`, \`--overwrite\`, "Remove existing files") \u2014 scaffold into a fresh subfolder and copy in instead. You may add files and edit the ones you wrote. If a step genuinely cannot continue without removing something of theirs, STOP and ask, naming the exact files, and wait for a yes \u2014 "it looks like junk" is never that yes. This binds hardest during setup, where it runs fast and automatically before the player has asked for anything at all.
974
974
  19. If the game sells anything, it sells it for coin at a fixed, visible price, and NEVER sells chance. Run this test on any purchasable thing before building it: does the player pay (with coin, or with anything coin bought, directly or indirectly), is the outcome uncertain when they pay, and is there a prize they wanted \u2014 all three yes means it is paid randomness, and you build the deterministic version instead. That rules out loot boxes, gacha, mystery boxes, crates, card packs, prize wheels and raffles; wagering, staking, betting, coinflips and casino or slot mechanics denominated in coin; and donation prompts, tip jars or any player-to-player coin transfer, because coin buys goods and never just moves. Randomness the player EARNS by playing is gameplay, not commerce \u2014 an enemy dropping a random item, a chest found in the level, a procedural layout, a crit roll \u2014 and is completely fine. Every coin price renders its real-money equivalent beside it (the server sends one with every item), item prices sit on the platform's price grid so no player is left holding change they cannot spend, and nothing in a shop carries a countdown, "limited time", or a stock counter. When a request crosses one of these lines, name the mechanic, give the one-sentence reason, propose a specific compliant alternative, and build that \u2014 never the banned version "as an option", never a partial one, and never after asking the player to confirm they want it. Load \`$genex-monetization\` before building a shop.
975
+ 20. When the player names a specific thing to integrate \u2014 a library, a controller, an asset, a part of an existing game, or a tagged reference the platform placed in \`.genex/refs/<slug>/\` \u2014 integrate the real thing: read its source first and build from it, never a lighter look-alike written from scratch because it was faster. A tagged reference owns the subsystem it was tagged for, outranking the default lane and the vendored controllers for that subsystem. If you genuinely can't use it, say so in one plain line and ask how to proceed.
975
976
  ${CONTRACT_END}
976
977
  `;
977
978
  var GENEX_TOOLS_CONTRACT_BLOCK = `${CONTRACT_BEGIN}
@@ -6160,7 +6161,8 @@ async function detectSurfaceScan(cwd = process.cwd()) {
6160
6161
  squarePoints: [],
6161
6162
  depthRange: [],
6162
6163
  mediaElementAudio: [],
6163
- deferredAudioContext: []
6164
+ deferredAudioContext: [],
6165
+ usesThree: false
6164
6166
  };
6165
6167
  const srcDir = path17.join(cwd, "src");
6166
6168
  let entries;
@@ -6208,6 +6210,7 @@ async function detectSurfaceScan(cwd = process.cwd()) {
6208
6210
  ratio
6209
6211
  });
6210
6212
  }
6213
+ if (/\bfrom\s*["']three(?:\/[^"']*)?["']/.test(content)) found.usesThree = true;
6211
6214
  const threeAudioImport = /import\s+(?:type\s+)?\{[^}]*\bAudio\b[^}]*\}\s+from\s*["']three["']/.test(
6212
6215
  content
6213
6216
  );
@@ -6376,8 +6379,9 @@ function surfaceNudges(log, s) {
6376
6379
  }
6377
6380
  if (s.mediaElementAudio.length) {
6378
6381
  const list = s.mediaElementAudio.map((d) => `${d.where} \u2014 ${d.detail}`).join("; ");
6382
+ 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
6383
  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.`
6384
+ `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
6385
  );
6382
6386
  }
6383
6387
  if (s.deferredAudioContext.length) {
@@ -7405,6 +7409,20 @@ async function runGenerate(kind, opts) {
7405
7409
  }
7406
7410
  opts = { ...opts, frameUrl: inlined.dataUri };
7407
7411
  }
7412
+ for (const [field, flag] of [
7413
+ ["firstFrameUrl", "--start-frame/--first-frame"],
7414
+ ["lastFrameUrl", "--last-frame"]
7415
+ ]) {
7416
+ const ref = opts[field];
7417
+ if (kind !== "video" || !ref || isRemoteRef(ref)) continue;
7418
+ const inlined = await inlineLocalImage(ref, flag);
7419
+ if (!inlined.ok) {
7420
+ log.error(inlined.error);
7421
+ process.exitCode = 1;
7422
+ return;
7423
+ }
7424
+ opts = { ...opts, [field]: inlined.dataUri };
7425
+ }
7408
7426
  if (kind === "video" && opts.refs?.length) {
7409
7427
  const refs = [];
7410
7428
  for (const ref of opts.refs) {
@@ -21886,8 +21904,13 @@ ${c.bold("Options for the generators (`model` `sfx` `music` `voice` `texture` `i
21886
21904
  it, model default).
21887
21905
  --frame <url|file> (video) animate from THIS frame back to itself (seamless
21888
21906
  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.
21907
+ --start-frame <url|file> (video) continue from this exact frame, no end
21908
+ anchor \u2014 the clip-chaining primitive: extract the last
21909
+ frame of the previous clip (ffmpeg) and pass it here.
21910
+ Anchors are compositional guidance, not pixel-pinning;
21911
+ chain from REAL extracted frames so drift can't stack.
21912
+ --first-frame <url|file> (video) start frame; alone it means --start-frame \u2026
21913
+ --last-frame <url|file> (video) \u2026 add this to also anchor the end frame.
21891
21914
  --ref <url|file> (video, repeatable \xD79) subject/style reference images \u2014
21892
21915
  cite them in the prompt as "Image 1"\u2026"Image N"; keeps
21893
21916
  the same character/style across many clips. Cannot be
@@ -22086,7 +22109,8 @@ ${c.bold("Generate")} typ
22086
22109
  --transparent \u2026with a real alpha channel (sprites, icons, logos).
22087
22110
  --edit <path|url> \u2026edit an existing image with the prompt.
22088
22111
  --remove-bg / --bg-mode <m> \u2026cut the background (sprite|glyph|sheet|matte).
22089
- genex video "<prompt>" A video (mp4). --loop, --frame <img>. ${cr("video")}
22112
+ genex video "<prompt>" A video (mp4). --loop, --frame <img>, ${cr("video")}
22113
+ --start-frame <img> (continue a clip).
22090
22114
  genex texture "<prompt>" A tiling PBR texture set. --terrain. ${cr("texture")}
22091
22115
  genex sfx "<prompt>" A sound effect (mp3). --duration. ${cr("sfx")}
22092
22116
  genex music "<prompt>" A looping instrumental track (mp3). ${cr("music")}
@@ -22146,6 +22170,51 @@ ${c.bold("Examples")}
22146
22170
  Hosting, publishing, multiplayer, remixing and custom domains are the Genex
22147
22171
  platform \u2014 build a game there from one prompt: ${c.cyan("https://genex.games")}
22148
22172
  `;
22173
+ var COMMAND_HELP = {
22174
+ 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).
22175
+
22176
+ ${c.bold("Modes")}
22177
+ genex video "<prompt>" text-to-video (16:9).
22178
+ genex video "<prompt>" --start-frame <img> continue from this frame \u2014 the
22179
+ clip-chaining primitive.
22180
+ genex video "<prompt>" --frame <img> animate from the frame back to
22181
+ itself (seamless loop; menus).
22182
+ genex video "<prompt>" --first-frame <img> --last-frame <img>
22183
+ two-frame motion (both anchored).
22184
+ genex video "<prompt>" --ref <img> [--ref \u2026\xD79] same subject/style across many
22185
+ clips ("Image 1"\u2026"Image N").
22186
+ genex video "<prompt>" --loop prompt-only seamless loop (no
22187
+ conditioning frame; Luma Ray-2).
22188
+
22189
+ ${c.bold("Flags")}
22190
+ --start-frame <url|file> continue-from anchor; local files inline (\u22644 MB).
22191
+ --frame / --first-frame / --last-frame <url|file> ditto.
22192
+ --ref <url|file> repeatable \xD79; not combinable with frames/--loop.
22193
+ --duration <s> 5-15 (integer seconds; H3's own band). Default 5,
22194
+ loops/pairs 8. Longer scenes: chain clips.
22195
+ --resolution <r> 480p | 768p (default, top native) | 2k | 4k.
22196
+ --no-wait enqueue only; attach later with genex wait <id>.
22197
+
22198
+ Aspect: text/ref clips are 16:9. Frame-conditioned clips FOLLOW THE SOURCE
22199
+ IMAGE \u2014 a square image (genex image --aspect square) yields a square video.
22200
+
22201
+ ${c.bold("Chaining clips (playable films, cutscene sequences)")}
22202
+ Frame anchors are COMPOSITIONAL GUIDANCE, not pixel-pinning \u2014 the model
22203
+ repaints the frame (measured ~21 dB vs the supplied image; identical would be
22204
+ ~35-40 dB). Chains still hold if every clip starts from the PREVIOUS CLIP'S
22205
+ REAL last frame, because drift never accumulates:
22206
+ ffmpeg -sseof -0.2 -i prev.mp4 -update 1 -q:v 1 last.png
22207
+ genex video "<what changes next>" --start-frame last.png
22208
+ Cut clip-to-clip (or crossfade ~200 ms to hide the residual repaint). Keep
22209
+ in-game <video> muted by default \u2014 every H3 clip carries audio.
22210
+
22211
+ ${c.bold("Content filter")}
22212
+ The provider rejects anatomy-invasion phrasing (cables/wires/needles entering
22213
+ a body) as body horror \u2014 describe the object or machine instead: "a
22214
+ statue-like figure threaded into the wall" passes where "cables entering her
22215
+ spine" fails. Rejections are never retryable; reword and re-run.
22216
+ `
22217
+ };
22149
22218
  function parseArgs(argv) {
22150
22219
  const parsed = {
22151
22220
  command: void 0,
@@ -22183,6 +22252,7 @@ function parseArgs(argv) {
22183
22252
  "--voice",
22184
22253
  "--voice-id",
22185
22254
  "--frame",
22255
+ "--start-frame",
22186
22256
  "--first-frame",
22187
22257
  "--last-frame",
22188
22258
  "--ref",
@@ -22716,6 +22786,7 @@ function applyValueFlag(options, flag, value) {
22716
22786
  case "--video":
22717
22787
  options.video = value;
22718
22788
  break;
22789
+ case "--start-frame":
22719
22790
  case "--first-frame":
22720
22791
  options.firstFrameUrl = value;
22721
22792
  break;
@@ -22777,7 +22848,8 @@ async function main() {
22777
22848
  const mode = await workspaceMode();
22778
22849
  setWorkspaceHeader(mode === "tools" ? "tools" : null);
22779
22850
  if (parsed.help || parsed.command === void 0) {
22780
- log.plain(mode === "tools" ? HELP_TOOLS : HELP);
22851
+ const perCommand = parsed.command === void 0 ? void 0 : COMMAND_HELP[parsed.command];
22852
+ log.plain(perCommand ?? (mode === "tools" ? HELP_TOOLS : HELP));
22781
22853
  return;
22782
22854
  }
22783
22855
  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.3-dev.541",
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
@@ -54,6 +54,10 @@ Use the request in front of you, not the oldest description of the project.
54
54
  invent or propose game concepts for them.
55
55
  - **Existing project** respects its renderer, physics, file conventions, and
56
56
  working systems unless the player asks to change them.
57
+ - **A tagged reference** (source under `.genex/refs/<slug>/`) owns the
58
+ subsystem it was tagged for: read its `_map.md` and implementing files
59
+ before choosing a lane — it outranks the default lane, including the
60
+ vendored controllers, for that subsystem.
57
61
 
58
62
  Ask one decision at a time. Never ask the player to choose an SDK, engine,
59
63
  renderer, file layout, provider, or other implementation detail. Product
@@ -28,6 +28,9 @@ copy demo architecture.
28
28
  3D,” ask one plain question: normal generated GLB, or editable parameterized
29
29
  Three.js code? Explicit `GLB`, `procedural`, `parametric`, `code-built`, or
30
30
  `variations` language chooses directly.
31
+ - **Tagged reference:** source under `.genex/refs/<slug>/` owns the subsystem
32
+ it was tagged for — read its `_map.md` and implementing files before choosing
33
+ a lane; it outranks the default lane, vendored controllers included.
31
34
 
32
35
  ## Route by system
33
36
 
@@ -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.