@genex-ai/cli-demo 1.4.2-dev.382 → 1.4.2
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/README.md +1 -3
- package/dist/index.js +27 -179
- package/package.json +1 -1
- package/templates/skills/genex-ai-texture/SKILL.md +1 -1
- package/templates/skills/genex-ai-video/SKILL.md +1 -1
- package/templates/skills/genex-game-director/SKILL.md +5 -4
- package/templates/skills/genex-game-director/references/routing-map.md +1 -1
- package/templates/skills/genex-getting-started/SKILL.md +7 -5
- package/templates/skills/genex-threejs-adaptive-quality/SKILL.md +18 -12
- package/templates/skills/genex-threejs-adaptive-quality/references/adaptive-quality.md +1 -1
- package/templates/skills/genex-threejs-game-ui/SKILL.md +1 -1
- package/templates/skills/genex-threejs-visual-validation/SKILL.md +3 -7
- package/templates/skills/genex-updates/SKILL.md +1 -1
- package/templates/skills/genex-ai-skybox/SKILL.md +0 -133
package/README.md
CHANGED
|
@@ -11,7 +11,6 @@ genex preview # build + push to the hosted draft URL (unlisted)
|
|
|
11
11
|
genex publish # build + push, then list the game in the gallery
|
|
12
12
|
genex make-remixable # make this game remixable — migrate a private source to a public repo
|
|
13
13
|
genex model "<prompt>" # generate a 3D model → prints an asset URL
|
|
14
|
-
genex skybox "<prompt>" # generate a 360° sky → prints an asset URL
|
|
15
14
|
genex sfx "<prompt>" # generate a sound fx → prints an asset URL
|
|
16
15
|
genex texture "<prompt>" # generate a texture → prints an asset URL
|
|
17
16
|
genex image "<prompt>" # generate an image → prints an asset URL
|
|
@@ -88,7 +87,7 @@ baked at build time via `GENEX_CLI_CHANNEL` (see `src/config.ts`).
|
|
|
88
87
|
|
|
89
88
|
## Generating assets
|
|
90
89
|
|
|
91
|
-
`genex model |
|
|
90
|
+
`genex model | sfx | texture | image | video "<prompt>"` turn a prompt into a
|
|
92
91
|
real, game-ready asset stored in R2. The command blocks until the asset is ready (live SSE
|
|
93
92
|
stream), then prints its public URL — the game loads it directly, nothing is
|
|
94
93
|
downloaded or committed:
|
|
@@ -96,7 +95,6 @@ downloaded or committed:
|
|
|
96
95
|
| Command | Provider | Prints (role) |
|
|
97
96
|
| --- | --- | --- |
|
|
98
97
|
| `genex model "<prompt>"` | Tripo | `…/generations/<id>/model-glb` |
|
|
99
|
-
| `genex skybox "<prompt>"` | Blockade Labs | `…/generations/<id>/skybox-equirect` |
|
|
100
98
|
| `genex sfx "<prompt>" [--duration <s>]` | ElevenLabs | `…/generations/<id>/audio-sfx` |
|
|
101
99
|
| `genex texture "<prompt>" [--terrain]` | Gemini | `…/generations/<id>/texture-basecolor` |
|
|
102
100
|
| `genex image "<prompt>" [--transparent] [--aspect <ratio>]` | fal.ai | `…/generations/<id>/image-main` |
|
package/dist/index.js
CHANGED
|
@@ -8,7 +8,7 @@ import fs from "fs";
|
|
|
8
8
|
import os from "os";
|
|
9
9
|
import path from "path";
|
|
10
10
|
import { fileURLToPath } from "url";
|
|
11
|
-
var RAW_CHANNEL = "
|
|
11
|
+
var RAW_CHANNEL = "latest";
|
|
12
12
|
var CLI_CHANNEL = RAW_CHANNEL === "dev" ? "dev" : "latest";
|
|
13
13
|
var STANDS = {
|
|
14
14
|
prod: { api: "https://api.genex.games", dashboard: "https://genex.games" },
|
|
@@ -991,7 +991,7 @@ var CONTRACT_END = "<!-- genex:contract:end -->";
|
|
|
991
991
|
var GENEX_CONTRACT_BLOCK = `${CONTRACT_BEGIN}
|
|
992
992
|
# Genex build contract (always in effect for this game)
|
|
993
993
|
|
|
994
|
-
Your agentic capabilities that can boost your creation (all via \`npx genex \u2026\`): generate \`model\` \xB7 \`
|
|
994
|
+
Your agentic capabilities that can boost your creation (all via \`npx genex \u2026\`): generate \`model\` \xB7 \`sfx\` \xB7 \`music\` \xB7 \`voice\` \xB7 \`texture\` \xB7 \`image\` (\`--edit\` \xB7 \`--inpaint\` \xB7 \`--glass\` \xB7 \`--clean\` \xB7 \`--upscale\`) \xB7 \`video\` \xB7 rigged \`character\` / \`creature\` \xB7 \`character animate <id> "<verb>"\` (also \`creature animate\`; \`--locomotion\` for the 8-way movement set, \`--video\` for your own footage) \xB7 the pixel toolbox \`ui extract|masks|plate|text-color|trim|audit\` \xB7 vendored \`controller character|car|drone|touch|quality|chat\` \xB7 \`animations search\` \xB7 \`wait <id>\` / \`wait --all\` \xB7 \`preview\` / \`publish\` \xB7 \`rename <name>\` (move the game to a new address \u2014 keeps its plays and likes, kills the old link, needs \`--yes\` once published, and the game must be rebuilt after). Full options: \`npx genex --help\`. There is NO sky generator: every sky, horizon, time of day and weather mood is built in code \u2014 your call how, from a background colour and fog to an atmospheric sky shader with a real sun. It costs nothing, weighs nothing on phones, and keeps the sun in sync with the scene's own lighting, which a painted panorama never can. Task\u2192lane routing lives in the \`genex-game-director\` skill's routing map \u2014 re-load it whenever you're unsure which lane owns a task.
|
|
995
995
|
|
|
996
996
|
Important note: put soul into your creations, with many details and love. Aim to make them realistic and feel real. Use genex capabilities as an extension, but never limit your imagination \u2014 you are a powerful agent. Use your built-in sub-agents and iteration loops (\`/loop\` or your platform's equivalent) where you need them to reach outstanding results. Verify yourself. Anything you put in front of the player should already feel alive and impressive \u2014 never a bare scene waiting for "later". Build what's still missing before polishing what already works.
|
|
997
997
|
|
|
@@ -1118,6 +1118,11 @@ var REMOVED_SKILLS = [
|
|
|
1118
1118
|
"genex-explore",
|
|
1119
1119
|
"genex-threejs-procedural-materials",
|
|
1120
1120
|
// folded into genex-threejs-procedural-assets (AG-881)
|
|
1121
|
+
// 2026-08-12: the generated-panorama lane is paused at the provider, and
|
|
1122
|
+
// every sky is built in code now. Leaving this SKILL.md
|
|
1123
|
+
// installed would keep pointing agents at `npx genex skybox`, which the CLI
|
|
1124
|
+
// refuses.
|
|
1125
|
+
"genex-ai-skybox",
|
|
1121
1126
|
"genex-threejs-skill-router",
|
|
1122
1127
|
"genex-threejs-bloom",
|
|
1123
1128
|
"genex-threejs-screen-space-ambient-occlusion",
|
|
@@ -4890,157 +4895,6 @@ async function runMakeRemixable(opts) {
|
|
|
4890
4895
|
}
|
|
4891
4896
|
}
|
|
4892
4897
|
|
|
4893
|
-
// src/commands/domain.ts
|
|
4894
|
-
var SUBCOMMANDS = ["add", "list", "verify", "remove"];
|
|
4895
|
-
function statusWord(d) {
|
|
4896
|
-
if (d.status === "active") return c.green("live");
|
|
4897
|
-
if (d.status === "failed") return c.red("stopped");
|
|
4898
|
-
return c.dim("waiting for DNS");
|
|
4899
|
-
}
|
|
4900
|
-
async function runDomain(opts) {
|
|
4901
|
-
const log = createLogger({ quiet: opts.quiet });
|
|
4902
|
-
const sub = (opts.name ?? "list").trim();
|
|
4903
|
-
if (!SUBCOMMANDS.includes(sub)) {
|
|
4904
|
-
log.error(`Unknown subcommand \`${sub}\`. Use: ${SUBCOMMANDS.join(", ")}.`);
|
|
4905
|
-
process.exitCode = 1;
|
|
4906
|
-
return;
|
|
4907
|
-
}
|
|
4908
|
-
const needsHost = sub !== "list";
|
|
4909
|
-
const hostname = opts.hostname?.trim();
|
|
4910
|
-
if (needsHost && !hostname) {
|
|
4911
|
-
log.error(`\`genex domain ${sub}\` needs a hostname, e.g. ${c.cyan(`genex domain ${sub} play.yourdomain.com`)}.`);
|
|
4912
|
-
process.exitCode = 1;
|
|
4913
|
-
return;
|
|
4914
|
-
}
|
|
4915
|
-
const meta = await readProject();
|
|
4916
|
-
if (!meta?.id) {
|
|
4917
|
-
log.error("This folder isn't linked to a game.");
|
|
4918
|
-
log.dim(` Run ${c.cyan("genex link")} (or ${c.cyan("genex list")} to find the slug) first.`);
|
|
4919
|
-
process.exitCode = 1;
|
|
4920
|
-
return;
|
|
4921
|
-
}
|
|
4922
|
-
const apiUrl = getApiUrl(opts.apiUrl ?? meta.apiUrl);
|
|
4923
|
-
let token = opts.token ?? await readUserToken(opts.envPath);
|
|
4924
|
-
if (!token) {
|
|
4925
|
-
if (opts.noAuth) {
|
|
4926
|
-
log.error("Not signed in. Re-run without --no-auth to connect.");
|
|
4927
|
-
process.exitCode = 1;
|
|
4928
|
-
return;
|
|
4929
|
-
}
|
|
4930
|
-
log.plain("Not signed in \u2014 connecting\u2026");
|
|
4931
|
-
try {
|
|
4932
|
-
token = await authorize(apiUrl, getAuthUrl(opts.authUrl), {
|
|
4933
|
-
log,
|
|
4934
|
-
inlineWaitMs: opts.timeoutSec ? opts.timeoutSec * 1e3 : void 0
|
|
4935
|
-
});
|
|
4936
|
-
} catch (err) {
|
|
4937
|
-
if (err instanceof AuthPendingError) {
|
|
4938
|
-
printAuthHandoff(log, err);
|
|
4939
|
-
log.dim(`Then re-run ${c.cyan(`genex domain ${sub}`)}.`);
|
|
4940
|
-
return;
|
|
4941
|
-
}
|
|
4942
|
-
log.error(`Sign-in didn't complete: ${err instanceof Error ? err.message : String(err)}`);
|
|
4943
|
-
process.exitCode = 1;
|
|
4944
|
-
return;
|
|
4945
|
-
}
|
|
4946
|
-
await writeUserToken(token, opts.envPath);
|
|
4947
|
-
}
|
|
4948
|
-
const base = `${apiUrl}/api/projects/${encodeURIComponent(meta.id)}/domains`;
|
|
4949
|
-
const auth = { Authorization: `Bearer ${token}` };
|
|
4950
|
-
const call = (url, init2) => apiFetch(url, { ...init2, headers: { ...auth, "Content-Type": "application/json", ...init2?.headers ?? {} } });
|
|
4951
|
-
let res;
|
|
4952
|
-
try {
|
|
4953
|
-
if (sub === "list") res = await call(base);
|
|
4954
|
-
else if (sub === "add") res = await call(base, { method: "POST", body: JSON.stringify({ hostname }) });
|
|
4955
|
-
else if (sub === "verify")
|
|
4956
|
-
res = await call(`${base}/${encodeURIComponent(hostname)}/verify`, { method: "POST" });
|
|
4957
|
-
else res = await call(`${base}/${encodeURIComponent(hostname)}`, { method: "DELETE" });
|
|
4958
|
-
} catch (err) {
|
|
4959
|
-
log.error(`Couldn't reach the API at ${apiUrl}.`);
|
|
4960
|
-
log.dim(` ${String(err)}`);
|
|
4961
|
-
process.exitCode = 1;
|
|
4962
|
-
return;
|
|
4963
|
-
}
|
|
4964
|
-
if (!res.ok) {
|
|
4965
|
-
if (await printedStructuredError(res)) {
|
|
4966
|
-
process.exitCode = 1;
|
|
4967
|
-
return;
|
|
4968
|
-
}
|
|
4969
|
-
const body = await res.json().catch(() => null);
|
|
4970
|
-
if (res.status === 503) {
|
|
4971
|
-
log.error("Custom domains aren't available on this Genex environment yet.");
|
|
4972
|
-
} else if (res.status === 404) {
|
|
4973
|
-
log.error(sub === "list" || sub === "add" ? "That game wasn't found on this account." : `${hostname} isn't connected to this game.`);
|
|
4974
|
-
} else {
|
|
4975
|
-
log.error(body?.message ?? body?.error ?? `Request failed (${res.status}).`);
|
|
4976
|
-
}
|
|
4977
|
-
process.exitCode = 1;
|
|
4978
|
-
return;
|
|
4979
|
-
}
|
|
4980
|
-
const data = await res.json();
|
|
4981
|
-
if (opts.json) {
|
|
4982
|
-
log.plain(JSON.stringify(data, null, 2));
|
|
4983
|
-
return;
|
|
4984
|
-
}
|
|
4985
|
-
if (sub === "list") {
|
|
4986
|
-
const rows = data.domains ?? [];
|
|
4987
|
-
if (rows.length === 0) {
|
|
4988
|
-
log.plain("No domains connected to this game.");
|
|
4989
|
-
log.dim(` ${c.cyan("genex domain add play.yourdomain.com")} to connect one.`);
|
|
4990
|
-
return;
|
|
4991
|
-
}
|
|
4992
|
-
for (const d of rows) log.plain(` ${d.hostname.padEnd(34)} ${statusWord(d)}`);
|
|
4993
|
-
return;
|
|
4994
|
-
}
|
|
4995
|
-
if (sub === "add") {
|
|
4996
|
-
if (data.supported === false) {
|
|
4997
|
-
log.plain(String(data.message ?? "That domain's DNS host doesn't support one-click setup."));
|
|
4998
|
-
const manual = data.manual;
|
|
4999
|
-
const records = Array.isArray(manual?.records) ? manual.records : [];
|
|
5000
|
-
if (records.length > 0) {
|
|
5001
|
-
log.plain("");
|
|
5002
|
-
log.plain(" Add these two records at your DNS host:");
|
|
5003
|
-
log.plain("");
|
|
5004
|
-
for (const r of records) {
|
|
5005
|
-
log.plain(` ${c.cyan(String(r.type ?? ""))} ${String(r.name ?? "")}`);
|
|
5006
|
-
log.plain(` ${String(r.value ?? "")}`);
|
|
5007
|
-
}
|
|
5008
|
-
log.plain("");
|
|
5009
|
-
if (manual?.apexHint) {
|
|
5010
|
-
log.dim(" At a root domain your host may call the first one ALIAS or ANAME.");
|
|
5011
|
-
}
|
|
5012
|
-
log.dim(` Then run: genex domain verify ${String(data.hostname ?? "")}`);
|
|
5013
|
-
log.dim(" DNS can take a few minutes to spread.");
|
|
5014
|
-
return;
|
|
5015
|
-
}
|
|
5016
|
-
log.dim(" Your game stays reachable at its usual address.");
|
|
5017
|
-
return;
|
|
5018
|
-
}
|
|
5019
|
-
const applyUrl = String(data.applyUrl ?? "");
|
|
5020
|
-
const providerName = String(data.providerName ?? "your DNS host");
|
|
5021
|
-
log.success(`${data.hostname} can be connected through ${providerName}.`);
|
|
5022
|
-
log.plain("");
|
|
5023
|
-
log.plain(` Approve the DNS change here: ${c.cyan(applyUrl)}`);
|
|
5024
|
-
log.plain("");
|
|
5025
|
-
log.dim(" One click writes the records. Nothing to copy or paste.");
|
|
5026
|
-
if (!applyUrl.startsWith("https://")) {
|
|
5027
|
-
log.warn("That DNS host returned a setup link we could not verify \u2014 not opening it.");
|
|
5028
|
-
} else if (!opts.noOpen) {
|
|
5029
|
-
openBrowser(applyUrl, () => log.dim(" (Couldn't open a browser \u2014 use the link above.)"));
|
|
5030
|
-
}
|
|
5031
|
-
log.dim(` Then ${c.cyan(`genex domain verify ${String(data.hostname)}`)} once you have approved it.`);
|
|
5032
|
-
return;
|
|
5033
|
-
}
|
|
5034
|
-
if (sub === "verify") {
|
|
5035
|
-
const status = String(data.status ?? "");
|
|
5036
|
-
if (status === "active") log.success(`${hostname} is live.`);
|
|
5037
|
-
else if (status === "failed") log.error(`${hostname} is stopped \u2014 its verification record is missing.`);
|
|
5038
|
-
else log.plain(`${hostname} isn't verified yet \u2014 DNS changes can take a few minutes to spread.`);
|
|
5039
|
-
return;
|
|
5040
|
-
}
|
|
5041
|
-
log.success(`${hostname} disconnected.`);
|
|
5042
|
-
}
|
|
5043
|
-
|
|
5044
4898
|
// src/lib/promote.ts
|
|
5045
4899
|
async function promoteBuild(apiUrl, projectId, token, log) {
|
|
5046
4900
|
let res;
|
|
@@ -6529,7 +6383,10 @@ function printHint(kind, view, files, log) {
|
|
|
6529
6383
|
const url = files[0]?.url ?? "";
|
|
6530
6384
|
const hint = {
|
|
6531
6385
|
model: `Standard GLB \u2014 load with GLTFLoader straight from the URL \u2014 see the genex-ai-model skill. url = "${url}"`,
|
|
6532
|
-
|
|
6386
|
+
// Unreachable while the lane is paused (see src/index.ts) — kept so a
|
|
6387
|
+
// restore is one deletion. Games that already ship a panorama still load
|
|
6388
|
+
// it this way.
|
|
6389
|
+
skybox: `Load as an equirectangular texture \u2192 scene.background + scene.environment. url = "${url}"`,
|
|
6533
6390
|
sfx: `Load with AudioLoader into a THREE.PositionalAudio (camera needs an AudioListener) \u2014 see genex-ai-sfx. url = "${url}"`,
|
|
6534
6391
|
music: `Load with AudioLoader into a non-positional THREE.Audio: setLoop(true), setVolume(0.30), start on the first user gesture \u2014 and the settings screen MUST have Music + SFX volume sliders \u2014 see genex-ai-music. url = "${url}"`,
|
|
6535
6392
|
voice: `Load with AudioLoader into a THREE.Audio (or PositionalAudio at the speaker); voice lines are content \u2014 keep them short, SUBTITLED, and skippable \u2014 see genex-ai-voice. url = "${url}"`,
|
|
@@ -19251,12 +19108,7 @@ ${c.bold("Usage")}
|
|
|
19251
19108
|
genex publish [options] Build + push + make live, then list it in the gallery.
|
|
19252
19109
|
genex make-remixable [options] Make THIS game remixable by everyone \u2014 migrates a
|
|
19253
19110
|
private source onto a public managed genex repo.
|
|
19254
|
-
genex domain <sub> [host] Play this game on a domain you own:
|
|
19255
|
-
add | list | verify | remove. "add" opens a
|
|
19256
|
-
one-click approval at your DNS host \u2014 no
|
|
19257
|
-
records to copy or paste.
|
|
19258
19111
|
genex model "<prompt>" [options] Generate a 3D model (GLB); prints a public asset URL.
|
|
19259
|
-
genex skybox "<prompt>" [options] Generate a skybox (equirect); prints a public asset URL.
|
|
19260
19112
|
genex sfx "<prompt>" [options] Generate a sound effect (mp3); prints a public asset URL.
|
|
19261
19113
|
genex music "<prompt>" [options] Generate an instrumental music track (mp3); prints a
|
|
19262
19114
|
public asset URL. --duration 10-300s (default 90).
|
|
@@ -19331,12 +19183,8 @@ ${c.bold("Usage")}
|
|
|
19331
19183
|
Pure local work (no API); the PNG tools read
|
|
19332
19184
|
a file or URL, audit scans the project.
|
|
19333
19185
|
|
|
19334
|
-
${c.bold("Options for the generators (`model` `
|
|
19186
|
+
${c.bold("Options for the generators (`model` `sfx` `music` `voice` `texture` `image` `video`)")}
|
|
19335
19187
|
--terrain (texture) seamless tiling surface for terrain/ground.
|
|
19336
|
-
--raw (skybox) send the prompt exactly as written \u2014 skip the
|
|
19337
|
-
appended environment-only clause AND the refusal on
|
|
19338
|
-
prompts that name structures/objects (a sky is sky only;
|
|
19339
|
-
landforms and weather always pass).
|
|
19340
19188
|
--duration <sec> (sfx, video, music) target clip length in seconds;
|
|
19341
19189
|
(character/creature animate) 3-6 s of reference video for
|
|
19342
19190
|
verbs that route to AI video (default 3) \u2014 longer gives a
|
|
@@ -19500,11 +19348,8 @@ ${c.bold("Examples")}
|
|
|
19500
19348
|
genex publish
|
|
19501
19349
|
genex publish --categories games,vfx
|
|
19502
19350
|
genex make-remixable
|
|
19503
|
-
genex domain add play.mygame.com
|
|
19504
|
-
genex domain list
|
|
19505
19351
|
genex publish --no-push --title "My Game"
|
|
19506
19352
|
genex model "weathered wooden barrel with iron bands"
|
|
19507
|
-
genex skybox "golden hour over a misty mountain range"
|
|
19508
19353
|
genex sfx "punchy laser zap" --duration 2
|
|
19509
19354
|
genex music "brooding orchestral battle loop, seamless loop, no intro or outro" --no-wait
|
|
19510
19355
|
genex voice "You shall not pass!" --voice gruff
|
|
@@ -19625,9 +19470,6 @@ function parseArgs(argv) {
|
|
|
19625
19470
|
case "--no-auth":
|
|
19626
19471
|
parsed.options.noAuth = true;
|
|
19627
19472
|
break;
|
|
19628
|
-
case "--no-open":
|
|
19629
|
-
parsed.options.noOpen = true;
|
|
19630
|
-
break;
|
|
19631
19473
|
case "--no-push":
|
|
19632
19474
|
parsed.options.noPush = true;
|
|
19633
19475
|
break;
|
|
@@ -19762,12 +19604,6 @@ function parseArgs(argv) {
|
|
|
19762
19604
|
} else {
|
|
19763
19605
|
(parsed.options.selectors ??= []).push(arg);
|
|
19764
19606
|
}
|
|
19765
|
-
} else if (parsed.command === "domain") {
|
|
19766
|
-
if (!parsed.options.hostname) parsed.options.hostname = arg;
|
|
19767
|
-
else {
|
|
19768
|
-
parsed.error = `Unexpected argument: ${arg}`;
|
|
19769
|
-
return parsed;
|
|
19770
|
-
}
|
|
19771
19607
|
} else if (parsed.command === "animations") {
|
|
19772
19608
|
parsed.options.query = parsed.options.query ? `${parsed.options.query} ${arg}` : arg;
|
|
19773
19609
|
} else {
|
|
@@ -20077,6 +19913,21 @@ async function main() {
|
|
|
20077
19913
|
ci: !!process.env.CI
|
|
20078
19914
|
});
|
|
20079
19915
|
try {
|
|
19916
|
+
if (parsed.command === "skybox") {
|
|
19917
|
+
log.error("The generated-sky lane is paused \u2014 `genex skybox` is not available.");
|
|
19918
|
+
log.plain(
|
|
19919
|
+
[
|
|
19920
|
+
` ${c.cyan("\u2192")} Build the sky in code instead \u2014 your call how: a background colour with`,
|
|
19921
|
+
` fog, a gradient dome, or three.js's own ${c.cyan("Sky")} shader with a real sun. It costs`,
|
|
19922
|
+
" no credits, no download and no phone memory, and the sun stays in sync with",
|
|
19923
|
+
" your lighting.",
|
|
19924
|
+
` ${c.cyan("\u2192")} A distant skyline or landmark was never a sky: build it as geometry, or`,
|
|
19925
|
+
` generate it with ${c.cyan("npx genex model")}.`
|
|
19926
|
+
].join("\n")
|
|
19927
|
+
);
|
|
19928
|
+
process.exitCode = 1;
|
|
19929
|
+
return;
|
|
19930
|
+
}
|
|
20080
19931
|
if (GEN_KINDS.has(parsed.command)) {
|
|
20081
19932
|
await runGenerate(parsed.command, {
|
|
20082
19933
|
...parsed.options,
|
|
@@ -20103,9 +19954,6 @@ async function main() {
|
|
|
20103
19954
|
case "make-remixable":
|
|
20104
19955
|
await runMakeRemixable(parsed.options);
|
|
20105
19956
|
break;
|
|
20106
|
-
case "domain":
|
|
20107
|
-
await runDomain(parsed.options);
|
|
20108
|
-
break;
|
|
20109
19957
|
case "controller":
|
|
20110
19958
|
await runController({ ...parsed.options, kind: parsed.options.name });
|
|
20111
19959
|
break;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@genex-ai/cli-demo",
|
|
3
|
-
"version": "1.4.2
|
|
3
|
+
"version": "1.4.2",
|
|
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": {
|
|
@@ -225,7 +225,7 @@ first one is the one a screenshot of the whole arena will not show you.
|
|
|
225
225
|
|
|
226
226
|
## Troubleshooting
|
|
227
227
|
|
|
228
|
-
- **"Not authorized"** — run `npx @genex-ai/cli-demo@
|
|
228
|
+
- **"Not authorized"** — run `npx @genex-ai/cli-demo@latest init` first (it writes your `GENEX_TOKEN`).
|
|
229
229
|
- **"Out of credits" (`insufficient_credits`)** — the account has no credits left for
|
|
230
230
|
this texture generation. Tell the user the facts the CLI printed: their balance,
|
|
231
231
|
this generation's cost, and when their credits refill. Then offer to continue the
|
|
@@ -151,7 +151,7 @@ set belongs to `$genex-ai-hud` — both build on `npx genex image`/`video`.
|
|
|
151
151
|
|
|
152
152
|
## Troubleshooting
|
|
153
153
|
|
|
154
|
-
- **"Not authorized"** — run `npx @genex-ai/cli-demo@
|
|
154
|
+
- **"Not authorized"** — run `npx @genex-ai/cli-demo@latest init` first (it writes your `GENEX_TOKEN`).
|
|
155
155
|
- **"Prompt rejected"** — the provider's content-safety filter blocked the prompt.
|
|
156
156
|
This is non-retryable; retrying the same wording fails again. Rewrite the prompt.
|
|
157
157
|
- **Nothing plays / black surface** — the first `video.play()` must run inside a user
|
|
@@ -112,8 +112,8 @@ Genex asset and runtime lanes are:
|
|
|
112
112
|
|
|
113
113
|
- `npx genex model "<prompt>"`—a static GLB → `$genex-ai-model`
|
|
114
114
|
- `npx genex texture "<prompt>"`—a generated surface → `$genex-ai-texture`
|
|
115
|
-
-
|
|
116
|
-
|
|
115
|
+
- sky, horizon, time of day, weather mood—built in code, never generated; no
|
|
116
|
+
command and no skill owns it, so pick what this game needs
|
|
117
117
|
- `npx genex sfx "<prompt>"` / `music` / `voice` →
|
|
118
118
|
`$genex-ai-sfx`, `$genex-ai-music`, `$genex-ai-voice`
|
|
119
119
|
- `npx genex image "<prompt>"` / `video`—2D and motion art →
|
|
@@ -146,9 +146,10 @@ Assets row.
|
|
|
146
146
|
The request sets the floor for the asset plan, never the ceiling: a world
|
|
147
147
|
should feel dressed and alive, so plan the detail its genre implies — and pick
|
|
148
148
|
the more efficient engine per object. Paid generation buys hero pieces (the
|
|
149
|
-
player's character, key props,
|
|
149
|
+
player's character, key props, music); procedural code is the
|
|
150
150
|
unlimited detail engine for structures, buildings, modular kits, and repeated
|
|
151
|
-
or varied dressing (`$genex-threejs-procedural-assets` owns that lane)
|
|
151
|
+
or varied dressing (`$genex-threejs-procedural-assets` owns that lane) — and
|
|
152
|
+
for the sky, which is never generated at all. Mixing
|
|
152
153
|
both in one scene is the normal way to build, never a fallback. In focused
|
|
153
154
|
work, stay inside the touched scope. Run independent planned generations with
|
|
154
155
|
`--no-wait`, scaffold while they land, and preserve their IDs, URLs, and
|
|
@@ -54,7 +54,7 @@ copy demo architecture.
|
|
|
54
54
|
| fixed-view screenshots, input direction, facing, temporal and budget evidence | `$genex-threejs-visual-validation` |
|
|
55
55
|
| a static generated GLB for a concrete prop, vehicle, building, or non-rigged object | `$genex-ai-model` |
|
|
56
56
|
| generated surface or terrain texture with real-world UV scale | `$genex-ai-texture` |
|
|
57
|
-
|
|
|
57
|
+
| sky, skybox, horizon, time of day, weather mood, night or space backdrop | build it in code in the scene—there is no sky command and no owning skill, so pick the technique this game needs |
|
|
58
58
|
| poster, sign, sprite, decal, reference sheet, or other 2D art | `$genex-ai-image` |
|
|
59
59
|
| in-world motion art or another requested video | `$genex-ai-video` |
|
|
60
60
|
| sound effect, one looping music bed, or a short spoken line | `$genex-ai-sfx`, `$genex-ai-music`, or `$genex-ai-voice` |
|
|
@@ -40,7 +40,6 @@ the asset lives in Genex storage (R2), not your repo, so there's nothing to comm
|
|
|
40
40
|
|
|
41
41
|
```bash
|
|
42
42
|
npx genex model "weathered wooden barrel" # a 3D mesh (GLB)
|
|
43
|
-
npx genex skybox "golden hour over mountains" # a 360° sky + lighting
|
|
44
43
|
npx genex sfx "punchy laser zap" --duration 2 # a sound effect (mp3)
|
|
45
44
|
npx genex texture "mossy cobblestone" --terrain # a tiling surface texture
|
|
46
45
|
npx genex image "vintage travel poster" # a picture (poster/sign/sprite/decal)
|
|
@@ -51,8 +50,11 @@ npx genex video "waterfall mist drifting" --loop # a video clip (screen/bac
|
|
|
51
50
|
`npx genex` resolve to the right CLI.)
|
|
52
51
|
|
|
53
52
|
Each has a focused skill with the exact loader code — `$genex-ai-model`,
|
|
54
|
-
`$genex-ai-
|
|
55
|
-
`$genex-ai-video`.
|
|
53
|
+
`$genex-ai-sfx`, `$genex-ai-texture`, `$genex-ai-image`,
|
|
54
|
+
`$genex-ai-video`. The sky is not on that list: there is no sky command, so
|
|
55
|
+
every sky, horizon, and time of day is built in code — your call how, from a
|
|
56
|
+
background colour and fog to an atmospheric sky shader with a real sun. Two
|
|
57
|
+
more workflow skills draw UI art on the same commands,
|
|
56
58
|
for games whose style wants it: `$genex-ai-menu` (a cinematic menu — looping
|
|
57
59
|
video backdrop + DOM buttons) and `$genex-ai-hud` (a HUD sprite set with
|
|
58
60
|
masked fills). A restrained interface in clean CSS is a finished UI too —
|
|
@@ -159,7 +161,7 @@ and re-link the clone to the same live game:
|
|
|
159
161
|
```bash
|
|
160
162
|
git clone <the game's repo url> my-game && cd my-game
|
|
161
163
|
npm install
|
|
162
|
-
npx @genex-ai/cli-demo@
|
|
164
|
+
npx @genex-ai/cli-demo@latest link <slug> # slug = the name in the play URL
|
|
163
165
|
```
|
|
164
166
|
|
|
165
167
|
Don't know the slug? **`npx genex list`** prints every game on your account —
|
|
@@ -186,7 +188,7 @@ Safe to run any time — genex-owned skills are refreshed to the latest version,
|
|
|
186
188
|
and your own files are never touched:
|
|
187
189
|
|
|
188
190
|
```bash
|
|
189
|
-
npx @genex-ai/cli-demo@
|
|
191
|
+
npx @genex-ai/cli-demo@latest init
|
|
190
192
|
```
|
|
191
193
|
|
|
192
194
|
Use `--force` only if you intentionally want your own existing files overwritten
|
|
@@ -7,7 +7,7 @@ description: Make a Genex Three.js game phone-survivable with the vendored adapt
|
|
|
7
7
|
|
|
8
8
|
Phones enforce a hard GPU-memory ceiling desktops don't have: iOS silently
|
|
9
9
|
kills the page when a game allocates too much, and the kill arrives at BOOT —
|
|
10
|
-
exactly when
|
|
10
|
+
exactly when textures, models, and the post stack all decode at once. This
|
|
11
11
|
skill wires the vendored quality kit so the game boots conservatively on
|
|
12
12
|
phones, steps quality UP when the device proves smooth, and never gets uglier
|
|
13
13
|
on desktop. **You can recover from ugly; you cannot recover from a killed
|
|
@@ -115,22 +115,27 @@ reads; pause your own loop and audio on `visibilitychange` too.
|
|
|
115
115
|
|
|
116
116
|
## Generated assets: load through the rungs
|
|
117
117
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
rungs; phones must load through them:
|
|
118
|
+
A generated texture original can be several thousand pixels square — a handful
|
|
119
|
+
of them decode into more memory than a phone has. Every generated image asset
|
|
120
|
+
ships with downscale rungs; phones must load through them:
|
|
121
121
|
|
|
122
122
|
```ts
|
|
123
123
|
import { detectTier } from "./controllers/quality/tier.ts";
|
|
124
124
|
import { loadTextureWithFallback } from "./controllers/quality/pick-asset.ts";
|
|
125
125
|
|
|
126
|
-
const texture = await loadTextureWithFallback(
|
|
126
|
+
const texture = await loadTextureWithFallback(TEXTURE_URL, tier, (u) =>
|
|
127
127
|
new THREE.TextureLoader().loadAsync(u),
|
|
128
128
|
);
|
|
129
129
|
```
|
|
130
130
|
|
|
131
|
-
Desktop loads the original, phones the
|
|
132
|
-
|
|
133
|
-
|
|
131
|
+
Desktop loads the original, phones the small rung, and a missing rung falls
|
|
132
|
+
back to the original — never a broken boot. The `$genex-ai-texture` skill
|
|
133
|
+
shows the wiring in place.
|
|
134
|
+
|
|
135
|
+
The sky costs nothing here: it is built in code, so there
|
|
136
|
+
is no panorama to download, no rung ladder, and no boot-time decode. Games
|
|
137
|
+
carrying a generated equirect sky predate that and still load through the
|
|
138
|
+
`skybox-equirect` rungs the kit resolves for them.
|
|
134
139
|
|
|
135
140
|
## Generated models: load through the rungs
|
|
136
141
|
|
|
@@ -170,7 +175,7 @@ source, fetched only as the fallback of last resort. Meshy characters take
|
|
|
170
175
|
the same ladder through
|
|
171
176
|
`loadMeshyCharacter(manifestUrl, { loader: gltfLoader.loader, modelUrlCandidates: (u) => [pickModel(u, tier, { ktx2: gltfLoader.ktx2 }), pickModel(u, tier), u] })`.
|
|
172
177
|
KTX2-capable games can also pass `ktx2Load` to `loadTextureWithFallback` so
|
|
173
|
-
|
|
178
|
+
textures use their `.ktx2` variants.
|
|
174
179
|
|
|
175
180
|
## Quality picker in settings
|
|
176
181
|
|
|
@@ -219,9 +224,10 @@ runtime).
|
|
|
219
224
|
(antialias) are locked wrong for the session. Tier first, renderer second.
|
|
220
225
|
- Governor wired to context-creation flags → no-op at best. Runtime knobs
|
|
221
226
|
only: DPR, post toggles, distances, frame cap.
|
|
222
|
-
-
|
|
223
|
-
that targets phones →
|
|
224
|
-
`loadTextureWithFallback`.
|
|
227
|
+
- A generated texture loaded with a bare `TextureLoader.loadAsync(url)` on a
|
|
228
|
+
game that targets phones → the full-size original decoded; route it through
|
|
229
|
+
`loadTextureWithFallback`. (Same for the equirect sky of a game that predates
|
|
230
|
+
the procedural sky lane — the `skybox-equirect` rungs still resolve.)
|
|
225
231
|
- Quality stepping on every spike → shader compiles read as slowness. The
|
|
226
232
|
governor requires SUSTAINED slow windows; do not shorten them.
|
|
227
233
|
- Testing quality tiers by resizing the desktop window → tiers key off touch +
|
|
@@ -7,7 +7,7 @@ when, or teaching the governor a game-specific step.
|
|
|
7
7
|
|
|
8
8
|
The phone budget is a hard ceiling that includes GPU memory (textures,
|
|
9
9
|
framebuffers), and the OS kill arrives with no catchable event. Boot is the
|
|
10
|
-
danger window:
|
|
10
|
+
danger window: big textures + models + post targets decode together. So phone tiers
|
|
11
11
|
START one notch below what the heuristics suggest and the governor steps UP
|
|
12
12
|
after ~20 smooth seconds. The cost of guessing low is moments of softness; the
|
|
13
13
|
cost of guessing high is a dead page.
|
|
@@ -67,7 +67,7 @@ type.
|
|
|
67
67
|
|
|
68
68
|
**3. Never stall the build on art.** Scaffold, boot wiring, the core loop,
|
|
69
69
|
and the gameplay-logic modules keep moving in parallel whatever the UI is
|
|
70
|
-
doing — and the core asset set (hero model, ground texture,
|
|
70
|
+
doing — and the core asset set (hero model, ground texture, sky, sfx) is
|
|
71
71
|
prompted from the game IDEA, so it can launch up front behind placeholders.
|
|
72
72
|
Nothing ready to build waits on an art answer: decide, say the decision in
|
|
73
73
|
one line, keep going.
|
|
@@ -134,13 +134,9 @@ everything twice.
|
|
|
134
134
|
visible; the lock may only ever engage from the Play/Resume click or a
|
|
135
135
|
gameplay canvas click (the phase binding `setPaused(phase !== "playing")` is
|
|
136
136
|
what guarantees this — check it rides `setPhase`, not the render loop).
|
|
137
|
-
Headless caveat
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
both-axes look check: synthesised mouse movement cancels to a net zero
|
|
141
|
-
delta, so turning right then left proves nothing about direction. Assert the
|
|
142
|
-
wiring and the cue in a screenshot, and say plainly that confirming which way
|
|
143
|
-
the view turns needs one manual pass with a real mouse.
|
|
137
|
+
Headless caveat: `requestPointerLock` throws in headless Chromium —
|
|
138
|
+
assert the wiring and the unlocked cue in a screenshot, and say plainly that
|
|
139
|
+
the lock itself needs one manual click (do the both-axes look check there).
|
|
144
140
|
7. **Ask the scene the three things the screenshot cannot answer** (below). Run it
|
|
145
141
|
once, in the same browser you already have open.
|
|
146
142
|
|
|
@@ -38,7 +38,7 @@ update, so update immediately.)
|
|
|
38
38
|
Run exactly the command the nudge printed, from the game project root:
|
|
39
39
|
|
|
40
40
|
```bash
|
|
41
|
-
npm i -D @genex-ai/cli-demo@
|
|
41
|
+
npm i -D @genex-ai/cli-demo@latest # the genex CLI (a dev dependency)
|
|
42
42
|
npm i @genex-ai/embed-sdk@latest # identity/saves SDK (ships inside the game)
|
|
43
43
|
npm i @genex-ai/multiplayer@latest # multiplayer SDK (only if the game uses it)
|
|
44
44
|
```
|
|
@@ -1,133 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: genex-ai-skybox
|
|
3
|
-
description: Generate a real 360° skybox (equirectangular panorama) from a text prompt with `npx genex skybox`, then load it as the scene background + environment lighting in Three.js. Use when the user wants a specific photoreal/painted sky or backdrop ("sunset over the ocean", "alien purple nebula", "foggy pine forest") as an image, rather than a procedural/shader sky.
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# Genex AI · Skybox
|
|
7
|
-
|
|
8
|
-
Turn a prompt into a 360° **equirectangular** panorama that wraps the scene and
|
|
9
|
-
also lights it (image-based lighting).
|
|
10
|
-
|
|
11
|
-
## When to use this vs. a procedural sky
|
|
12
|
-
|
|
13
|
-
- **Use `npx genex skybox`** for a specific, describable ENVIRONMENT — "golden
|
|
14
|
-
hour over misty mountains", "rolling thunderstorm, towering cumulonimbus",
|
|
15
|
-
"alien purple nebula", "hazy dusk sky over a distant sea horizon". You get a
|
|
16
|
-
real image.
|
|
17
|
-
- Build a procedural, animated, time-of-day sky directly in the game's existing
|
|
18
|
-
scene/runtime only when that is the requested deliverable; this image lane
|
|
19
|
-
owns static generated panoramas.
|
|
20
|
-
|
|
21
|
-
**A skybox is environment only — never content.** Buildings, trees, ruins,
|
|
22
|
-
towers, or any object painted into the sky render at infinite distance: they
|
|
23
|
-
never get closer as the player moves, they sit at the wrong parallax against
|
|
24
|
-
the real 3D world, and they read as broken the moment the camera strafes.
|
|
25
|
-
Structures belong in the scene as geometry (`npx genex model`); the sky
|
|
26
|
-
carries atmosphere — light, weather, clouds, haze, stars.
|
|
27
|
-
|
|
28
|
-
**Writing "no cathedral" does not rescue a prompt that also says "cathedral".**
|
|
29
|
-
Image models are steered by the nouns they ARE given; a ban loses to any
|
|
30
|
-
positive mention of the same thing in the same prompt. A real pilot asked for
|
|
31
|
-
a *"cinematic dark anime gothic atmosphere, ruined cathedral courtyard mood,
|
|
32
|
-
**no buildings or structures**"* — and got a full gothic cathedral with a
|
|
33
|
-
courtyard balustrade and a baked ground plane. Five negations in that prompt,
|
|
34
|
-
zero effect. So never name a structure in a skybox prompt **even to exclude
|
|
35
|
-
it**: describe only light, colour, cloud, weather and time of day.
|
|
36
|
-
|
|
37
|
-
The CLI enforces this both ways. It appends a closed-positive environment
|
|
38
|
-
clause to every prompt (you'll see `↳ environment-only guard applied`; the
|
|
39
|
-
full final prompt is stored with the generation), and it **refuses outright,
|
|
40
|
-
before spending any credits**, when the prompt itself names a structure or
|
|
41
|
-
object. Landforms are fine — "golden hour over misty mountains", "a distant
|
|
42
|
-
sea horizon", "towering cumulonimbus" all pass. If you genuinely need content
|
|
43
|
-
baked into the sky — e.g. a space station panorama for a scene with no world
|
|
44
|
-
geometry — pass `--raw` to send your prompt exactly as written.
|
|
45
|
-
|
|
46
|
-
## Run
|
|
47
|
-
|
|
48
|
-
```bash
|
|
49
|
-
npx genex skybox "<prompt>"
|
|
50
|
-
```
|
|
51
|
-
|
|
52
|
-
Blocks until ready, then prints its public URL:
|
|
53
|
-
|
|
54
|
-
```
|
|
55
|
-
https://assets.genex.technology/generations/<id>/skybox-equirect
|
|
56
|
-
```
|
|
57
|
-
|
|
58
|
-
The image lives in Genex storage (R2) and loads straight from that URL — you don't
|
|
59
|
-
download it and nothing is committed to your repo. The URL is permanent (works the same
|
|
60
|
-
in local dev, the published game, and remixes).
|
|
61
|
-
|
|
62
|
-
## Load it as background + environment
|
|
63
|
-
|
|
64
|
-
Load the equirect JPG through the quality tier's rung ladder, mark it
|
|
65
|
-
equirectangular, and use it for both the visible background and the lighting.
|
|
66
|
-
The bare URL is an 8192×4096 original — ~178 MB decoded, over half a phone's
|
|
67
|
-
GPU budget in one texture — so phones must load the downscale rung the platform
|
|
68
|
-
stores next to every skybox (`$genex-threejs-adaptive-quality`):
|
|
69
|
-
|
|
70
|
-
```ts
|
|
71
|
-
import * as THREE from "three";
|
|
72
|
-
import { detectTier } from "./controllers/quality/tier.ts";
|
|
73
|
-
import { loadTextureWithFallback } from "./controllers/quality/pick-asset.ts";
|
|
74
|
-
|
|
75
|
-
// the URL `npx genex skybox` printed (R2 sends CORS headers, so cross-origin works):
|
|
76
|
-
const SKYBOX_URL = "https://assets.genex.technology/generations/<id>/skybox-equirect";
|
|
77
|
-
const tier = detectTier(); // reuse the boot tier if you already have it
|
|
78
|
-
const texture = await loadTextureWithFallback(SKYBOX_URL, tier, (u) =>
|
|
79
|
-
new THREE.TextureLoader().loadAsync(u),
|
|
80
|
-
);
|
|
81
|
-
texture.mapping = THREE.EquirectangularReflectionMapping;
|
|
82
|
-
texture.colorSpace = THREE.SRGBColorSpace;
|
|
83
|
-
|
|
84
|
-
scene.background = texture; // visible sky
|
|
85
|
-
scene.environment = texture; // image-based lighting on PBR materials
|
|
86
|
-
```
|
|
87
|
-
|
|
88
|
-
Desktop gets the original; phones get the `@2048`/`@4096` rung; a missing rung
|
|
89
|
-
falls back to the original automatically — never a broken boot.
|
|
90
|
-
|
|
91
|
-
For sharper reflections/lighting, pre-filter it with `PMREMGenerator`:
|
|
92
|
-
|
|
93
|
-
```ts
|
|
94
|
-
const pmrem = new THREE.PMREMGenerator(renderer);
|
|
95
|
-
const envMap = pmrem.fromEquirectangular(texture).texture;
|
|
96
|
-
scene.environment = envMap;
|
|
97
|
-
scene.background = texture; // keep the raw texture for the visible sky
|
|
98
|
-
// Do NOT dispose `texture` here — it IS the visible background. Dispose it only
|
|
99
|
-
// if you later stop using it as the sky.
|
|
100
|
-
```
|
|
101
|
-
|
|
102
|
-
## Publish checklist
|
|
103
|
-
|
|
104
|
-
- Load it from the **URL** the command printed — absolute and permanent, so it resolves
|
|
105
|
-
the same in local dev, the published game, and remixes. Nothing to commit.
|
|
106
|
-
- Don't copy the image into `public/assets/` — generated assets live in R2, not the repo.
|
|
107
|
-
|
|
108
|
-
## Options
|
|
109
|
-
|
|
110
|
-
- `--raw` — send the prompt exactly as written, skipping the appended
|
|
111
|
-
environment-only guard (for the rare sky that must carry content).
|
|
112
|
-
- `--no-wait` — enqueue and return immediately with the generation id; pick
|
|
113
|
-
the result up later with `npx genex wait <id>` (safe to re-run — it attaches
|
|
114
|
-
to the SAME generation).
|
|
115
|
-
- `--api-url <url>` — override the API base (local dev).
|
|
116
|
-
|
|
117
|
-
## Troubleshooting
|
|
118
|
-
|
|
119
|
-
- **"Not authorized"** — run `npx @genex-ai/cli-demo@dev init` first (it writes your `GENEX_TOKEN`).
|
|
120
|
-
- **"Out of credits" (`insufficient_credits`)** — the account has no credits left for
|
|
121
|
-
this skybox generation. Tell the user the facts the CLI printed: their balance, this
|
|
122
|
-
generation's cost, and when their credits refill. Then offer to continue the build
|
|
123
|
-
with a procedurally-coded placeholder (a gradient/procedural sky or a plain
|
|
124
|
-
`scene.background` color) and mark the spot with
|
|
125
|
-
`// TODO(genex): regenerate when credits refill` so the real asset is one command
|
|
126
|
-
away later. Do not stop the session over this.
|
|
127
|
-
- **"Email not verified" (`email_verification_required`)** — generation credits
|
|
128
|
-
unlock after the account's email is verified. Give the user the verify link the
|
|
129
|
-
CLI printed, wait for them to confirm, then re-run the command.
|
|
130
|
-
- **Sky looks too dark/bright** — adjust `renderer.toneMappingExposure`, or scale
|
|
131
|
-
`scene.environment` influence via material `envMapIntensity`.
|
|
132
|
-
- **Seam/pole artifacts** — that's inherent to equirect images; keep the camera
|
|
133
|
-
away from looking straight up/down, or use PMREM for the lighting.
|