@genex-ai/cli-demo 1.36.15-dev.1036 → 1.36.16-dev.1039
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 +21 -20
- package/package.json +1 -1
- package/templates/skills/genex-game-director/SKILL.md +2 -2
- package/templates/skills/genex-items/SKILL.md +27 -10
- package/templates/skills/genex-llm-in-games/SKILL.md +1 -1
- package/templates/skills/genex-llm-in-games/references/pricing.md +1 -1
- package/templates/skills/genex-monetization/SKILL.md +118 -82
package/dist/index.js
CHANGED
|
@@ -1129,12 +1129,12 @@ Important note: put soul into your creations, with many details and love. Aim to
|
|
|
1129
1129
|
16. Never add debug-only code that fakes gameplay \u2014 no hidden test modes, special URL parameters, forced-visible flags or auth mocks. The visual-validation skill permits a development-only final-mix analyser for a deliberately triggered audible event; remove it from published builds. The \`genex-cover\` skill's development-only cover shot (\`?cover=<name>\` on the dev server) is a camera, not a bypass: it never reaches a published build, never touches sign-in, saves or progress, and is never evidence that the game works. \`?genex_local_test=1\` is the platform's own supported mode and is fine; your own bypass is not. (The multiplayer skill's small build identifier, token-free status line, and connected-quorum watchdog are production supportability, not a bypass \u2014 keep those.)
|
|
1130
1130
|
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.
|
|
1131
1131
|
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.
|
|
1132
|
-
19. If the game sells anything, it sells it for
|
|
1132
|
+
19. If the game sells anything for real money, it sells it for Nex \u2014 Genex's own currency, which players buy with real money and spend in the Genex store \u2014 at a fixed, visible price, and NEVER sells chance. A price in Nex, or a request to sell something for real money that names no currency ("paid characters", "sell skins"), means a Genex store item: build it into the game, then list it with \`npx genex shop add \u2026 --json\` without asking (\`$genex-items\`); a price in coins, gold, gems or any other name is a currency of the game's own, earned by playing. Run this test on any purchasable thing before building it: does the player pay (with Nex, or with anything Nex 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 Nex; and donation prompts, tip jars or any player-to-player Nex transfer, because Nex 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 Nex 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.
|
|
1133
1133
|
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.
|
|
1134
1134
|
21. A turn ends in exactly one of two ways: on a question the player must answer before the next step can be chosen, or on a handoff \u2014 the draft link, what changed, what to try, and what is still open. Never close a turn on a promise. "I'll keep building", "while I keep working", "adding that now" are things you say and then DO before you stop; if you are stopping, say that you have stopped and what remains. While the Build plan's \`Now:\` line has open work and no answer is needed from the player, the turn is not over: preview, hand off, and start the next milestone in the same turn, until the requested outcome is reached or the platform's own budget ends the session. If a stop was forced on you anyway, the first line of your next turn is the promise you left and where it stands.
|
|
1135
1135
|
22. **What the player sees must read as the thing it is**, in this game's own style: a building as that building, a person as a person, a prop as that prop, a surface as its material \u2014 and a coloured box, a capsule, or a flat grey block standing in for one of them is never its finished version. Generation is the default route to that bar wherever code will not honestly reach it: the player's character, whatever the request names, whatever the player walks up to, enters, or interacts with, and the music and sound underneath \u2014 reach for the belt on your own judgment, without stopping to ask; a status line saying what you queued is enough, and \`--no-wait\` keeps you building while it lands. Procedural code is a first-class engine for what is structural, repeated, distant, or parametric \u2014 terrain, sky, fences, paving, modular kits, filler \u2014 and for anything else only when the result meets the same bar and you have looked at it in a capture before its row says \`landed\`. Start with one reusable implementation per distinct required gameplay or visual role. Background populations default to reusable procedural bodies and motion or compatible existing rigged assets. Catalog clips need a compatible skeleton and may be paid. Reserve paid custom bodies for the player and characters examined or interacted with closely, while honoring explicit user requests. Add variants only for an unmet requirement; unspent allowance is not unfinished work. Allowance questions use timeoutPolicy no-consent: silence does not raise the allowance. Record role, reuse and completion criteria in the existing Assets table. Decide per object, not per category, and write the route on each Assets row in \`DESIGN.md\` \u2014 the paid flow for generated pieces, the procedural flow with its local path for code-built ones. An Assets table with no generation in it is a decision, not a default: record it as \`Generation: none \u2014 <why code alone reaches the bar here>\` or generate.
|
|
1136
1136
|
23. **The screenshots you take to look at your own work go in \`.genex/scratch/\`.** Captures, render comparisons, before/after strips, traces and metrics dumps are how you SEE what you built \u2014 they are not part of what you built, and \`genex preview\` pushes the whole folder to the game's source, every time, forever. \`.genex/scratch/\` already exists for this and is already ignored, so there is nothing to set up: write the capture as \`.genex/scratch/arena-before.png\` and read it back from there. Never put them in a folder of your own at the top level: \`progress/\`, \`reports/\`, \`shots/\` and their kind are pushed like source and become permanent weight in the game and in every remix of it \u2014 one real game reached 11 GB and 10,332 committed screenshots exactly this way. If you find such a folder already there, add it to \`.gitignore\`; never delete the player's files (law 18). One file in \`.genex/scratch/\` IS read, by the platform: \`.genex/scratch/cover.png\` is the game's cover \u2014 the frame you would put on its poster: real gameplay at its signature moment, well lit, no menus, popups or busy HUD, landscape 16:9, and never any text (the page sets the title beside it). The next \`genex preview\` makes it the cover; save a better moment over it whenever one comes along, and if your browser tool can set the cover itself, use that instead. Before the first publish, and whenever the game's look changes, load \`$genex-cover\`: it chooses, stages, lights and judges that frame.
|
|
1137
|
-
24. **Benchmark before declaring an in-game LLM ceiling.** If the game calls a language model while the player plays \u2014 an NPC answering in its own words, a quest written for this save, a judge reading what the player typed \u2014 the PLAYER pays for it from their Genex credits, billed as used, and the game declares no price, only a per-call CEILING: \`maxCredits\` on \`generate()\`, \`perCallMaxCredits\` on a standing budget. That ceiling bounds what one call may cost AND funds how long its answer may be, so never pick it by judgement or from a vendor's rate card. Run the real prompt on your own credits \u2014 \`npx genex llm bench "<the prompt>" --schema <file> --samples 3 --max-credits <n> --user-approved\` \u2014 and declare the figures it prints, recorded in \`DESIGN.md\` with its model and date; re-benchmark whenever the prompt, the schema or the model changes, because a prompt edit changes the ceiling. Check the lane first with \`npx genex llm models\`: where it is off the routes 404, so build the feature behind a graceful unavailable state and say so in the handoff. A repeated call is a standing budget the player approves once (\`requestSpendGrant()\`, then \`generate({ grantId })\`), never a popup per turn, and its disclosed call rate and credits per period are computed from the game's own loop and the bench's measured average. The coin spellings (\`estimateCoins\`, \`perCallMaxCoins\`) are deprecated \u2014 write the credit names. Model output is data the game validates against its own expectation \u2014 never executed, and never authority over
|
|
1137
|
+
24. **Benchmark before declaring an in-game LLM ceiling.** If the game calls a language model while the player plays \u2014 an NPC answering in its own words, a quest written for this save, a judge reading what the player typed \u2014 the PLAYER pays for it from their Genex credits, billed as used, and the game declares no price, only a per-call CEILING: \`maxCredits\` on \`generate()\`, \`perCallMaxCredits\` on a standing budget. That ceiling bounds what one call may cost AND funds how long its answer may be, so never pick it by judgement or from a vendor's rate card. Run the real prompt on your own credits \u2014 \`npx genex llm bench "<the prompt>" --schema <file> --samples 3 --max-credits <n> --user-approved\` \u2014 and declare the figures it prints, recorded in \`DESIGN.md\` with its model and date; re-benchmark whenever the prompt, the schema or the model changes, because a prompt edit changes the ceiling. Check the lane first with \`npx genex llm models\`: where it is off the routes 404, so build the feature behind a graceful unavailable state and say so in the handoff. A repeated call is a standing budget the player approves once (\`requestSpendGrant()\`, then \`generate({ grantId })\`), never a popup per turn, and its disclosed call rate and credits per period are computed from the game's own loop and the bench's measured average. The coin spellings (\`estimateCoins\`, \`perCallMaxCoins\`) are deprecated \u2014 write the credit names. Model output is data the game validates against its own expectation \u2014 never executed, and never authority over Nex, credits, items, entitlements or rewards. Load \`$genex-llm-in-games\` before writing any of it.
|
|
1138
1138
|
${CONTRACT_END}
|
|
1139
1139
|
`;
|
|
1140
1140
|
var TOOLS_CONTRACT_HEAD = `# Genex Tools (always in effect in this folder)
|
|
@@ -8350,7 +8350,7 @@ function checkFields(raw, label) {
|
|
|
8350
8350
|
if (description !== void 0 && (typeof description !== "string" || description.trim() === "" || description !== description.trim())) {
|
|
8351
8351
|
return bad("its description is not a trimmed, non-empty text");
|
|
8352
8352
|
}
|
|
8353
|
-
if (typeof priceCoins !== "number" || !Number.isSafeInteger(priceCoins) || priceCoins < 0) return bad("its price is not a whole
|
|
8353
|
+
if (typeof priceCoins !== "number" || !Number.isSafeInteger(priceCoins) || priceCoins < 0) return bad("its price is not a whole number of Nex, 0 or more");
|
|
8354
8354
|
if (type !== ItemType.Consumable && type !== ItemType.Durable) return bad("its type is neither consumable nor durable");
|
|
8355
8355
|
if (!Array.isArray(tags2) || !tags2.every((tag) => typeof tag === "string")) return bad("its tags are not a list of words");
|
|
8356
8356
|
if (typeof shareable !== "boolean") return bad("its sharing is not true or false");
|
|
@@ -8738,12 +8738,12 @@ function formatCoins(coins) {
|
|
|
8738
8738
|
}
|
|
8739
8739
|
function coinPrice(coins, displayUsdCents) {
|
|
8740
8740
|
if (coins === 0) return "Free";
|
|
8741
|
-
if (displayUsdCents === void 0) return `${formatCoins(coins)}
|
|
8741
|
+
if (displayUsdCents === void 0) return `${formatCoins(coins)} Nex`;
|
|
8742
8742
|
const usd = (displayUsdCents / 100).toLocaleString("en-US", { minimumFractionDigits: 2, maximumFractionDigits: 2 });
|
|
8743
|
-
return `${formatCoins(coins)}
|
|
8743
|
+
return `${formatCoins(coins)} Nex ($${usd})`;
|
|
8744
8744
|
}
|
|
8745
8745
|
function priceGridLine(grid, freeAllowed) {
|
|
8746
|
-
return `${[...freeAllowed ? ["Free"] : [], ...grid.map(formatCoins)].join(" \xB7 ")}
|
|
8746
|
+
return `${[...freeAllowed ? ["Free"] : [], ...grid.map(formatCoins)].join(" \xB7 ")} Nex`;
|
|
8747
8747
|
}
|
|
8748
8748
|
|
|
8749
8749
|
// src/lib/item-sync.ts
|
|
@@ -8752,7 +8752,7 @@ var ITEM_SYNC_VERSION = 1;
|
|
|
8752
8752
|
var ItemState = { Draft: "draft", OnSale: "on_sale", OffSale: "off_sale" };
|
|
8753
8753
|
var PRICE_CODES = /* @__PURE__ */ new Set(["price_off_grid", "bad_price"]);
|
|
8754
8754
|
var FIX_FLAG = {
|
|
8755
|
-
price: "--price <
|
|
8755
|
+
price: "--price <Nex>",
|
|
8756
8756
|
picture: "--image <picture.png>",
|
|
8757
8757
|
model: "--model <file.glb>",
|
|
8758
8758
|
package: "--package <dir>",
|
|
@@ -10065,7 +10065,7 @@ function printLine(draft) {
|
|
|
10065
10065
|
async function addLocal(opts, attachments, log, refuse) {
|
|
10066
10066
|
const name = opts.hostname?.trim();
|
|
10067
10067
|
if (!name) return refuse(usageRefusal(`\`genex shop add\` needs a name, e.g. ${c.cyan('genex shop add "Iron Key" --price 100')}.`));
|
|
10068
|
-
if (opts.price === void 0) return refuse(usageRefusal(`\`genex shop add\` needs ${c.cyan("--price")}, in
|
|
10068
|
+
if (opts.price === void 0) return refuse(usageRefusal(`\`genex shop add\` needs ${c.cyan("--price")}, in Nex (0 is Free).`));
|
|
10069
10069
|
const unsupported = localOnlyRefusal(opts);
|
|
10070
10070
|
if (unsupported) return refuse(unsupported);
|
|
10071
10071
|
const description = opts.description?.trim();
|
|
@@ -10357,7 +10357,7 @@ var ShopSession = class {
|
|
|
10357
10357
|
const named = grid && refusal2.error === "price_off_grid" && price !== void 0;
|
|
10358
10358
|
return {
|
|
10359
10359
|
...refusal2,
|
|
10360
|
-
...named ? { message: `${formatCoins(price)}
|
|
10360
|
+
...named ? { message: `${formatCoins(price)} Nex isn't one of this game's prices.` } : {},
|
|
10361
10361
|
hints: [...grid ? [`Prices available: ${grid}`] : [], `See the full list any time: ${c.cyan("genex shop list")}`]
|
|
10362
10362
|
};
|
|
10363
10363
|
}
|
|
@@ -10424,7 +10424,7 @@ var ShopSession = class {
|
|
|
10424
10424
|
const { log, opts } = this;
|
|
10425
10425
|
const name = opts.hostname?.trim();
|
|
10426
10426
|
if (!name) return this.refuse(usageRefusal(`\`genex shop add\` needs a name, e.g. ${c.cyan('genex shop add "Iron Key" --price 100')}.`));
|
|
10427
|
-
if (opts.price === void 0) return this.refuse(usageRefusal(`\`genex shop add\` needs ${c.cyan("--price")}, in
|
|
10427
|
+
if (opts.price === void 0) return this.refuse(usageRefusal(`\`genex shop add\` needs ${c.cyan("--price")}, in Nex (0 is Free).`));
|
|
10428
10428
|
const type = opts.type ?? (opts.shared ? "durable" : "consumable");
|
|
10429
10429
|
const res = await this.post(this.projectSkus, {
|
|
10430
10430
|
name,
|
|
@@ -10647,7 +10647,7 @@ var ItemsSession = class _ItemsSession {
|
|
|
10647
10647
|
for (const item of body.items) {
|
|
10648
10648
|
const reach = item.gamesCount === 1 ? "in 1 game" : `in ${item.gamesCount} games`;
|
|
10649
10649
|
log.plain(` ${c.cyan(item.id)} ${item.name} \u2014 ${coinPrice(item.priceCoins, item.priceDisplayUsdCents)} \xB7 ${reach} \xB7 by ${item.author.name}`);
|
|
10650
|
-
log.dim(` you get ${formatCoins(item.youGetPerSale)}
|
|
10650
|
+
log.dim(` you get ${formatCoins(item.youGetPerSale)} Nex per sale${item.tags.length ? ` \xB7 ${item.tags.join(", ")}` : ""}`);
|
|
10651
10651
|
if (item.description) log.dim(` ${item.description.length > 120 ? `${item.description.slice(0, 117)}\u2026` : item.description}`);
|
|
10652
10652
|
}
|
|
10653
10653
|
if (body.nextCursor) log.dim(" More match: narrow the search with more words or a --tag.");
|
|
@@ -27006,6 +27006,7 @@ function rowHoldsSlot(row) {
|
|
|
27006
27006
|
const state = rowState(row);
|
|
27007
27007
|
return state === "active" || state === "awaiting bill" && ((row.reservedCoins ?? 0) > 0 || (row.reservedCredits ?? 0) > 0);
|
|
27008
27008
|
}
|
|
27009
|
+
var UNIT_WORD = { credits: "credits", coin: "Nex" };
|
|
27009
27010
|
function rowMoney(row) {
|
|
27010
27011
|
const credits = row.currency === "credits" || row.currency === void 0 && typeof row.reservedCredits === "number" && typeof row.reservedCoins !== "number";
|
|
27011
27012
|
if (credits) {
|
|
@@ -27538,7 +27539,7 @@ async function reportStatus(args) {
|
|
|
27538
27539
|
const state = rowState(row);
|
|
27539
27540
|
const mark = state === "active" ? c.cyan("\u25CF") : state === "awaiting bill" ? c.yellow("\u25D0") : c.green("\u25CB");
|
|
27540
27541
|
const money = rowMoney(row);
|
|
27541
|
-
const reserved = money.reserved === null ? "reserved \u2014" : `${money.reserved} ${money.unit} reserved`;
|
|
27542
|
+
const reserved = money.reserved === null ? "reserved \u2014" : `${money.reserved} ${UNIT_WORD[money.unit]} reserved`;
|
|
27542
27543
|
log.plain(
|
|
27543
27544
|
` ${mark} ${row.id ?? "?"} ${row.modelId ?? "?"} ${state.padEnd(13)} ${formatAge(row.createdAt, now).padStart(4)} old ${reserved}${row.error ? ` ${row.error}` : ""}${rowHoldsSlot(row) ? "" : c.dim(" (no slot)")}`
|
|
27544
27545
|
);
|
|
@@ -27614,7 +27615,7 @@ async function cancelCall(args) {
|
|
|
27614
27615
|
if (row.providerMessage?.trim()) log.dim(` Provider said: ${row.providerMessage.trim()}`);
|
|
27615
27616
|
} else {
|
|
27616
27617
|
const money = rowMoney(row);
|
|
27617
|
-
log.dim(` ${money.charged === null ? "charge unknown" : `${money.charged} ${money.unit} charged`}${row.error ? ` \xB7 ${row.error}` : ""}.`);
|
|
27618
|
+
log.dim(` ${money.charged === null ? "charge unknown" : `${money.charged} ${UNIT_WORD[money.unit]} charged`}${row.error ? ` \xB7 ${row.error}` : ""}.`);
|
|
27618
27619
|
}
|
|
27619
27620
|
}
|
|
27620
27621
|
return done("ok", null, state === "awaiting bill" ? "awaiting_bill" : "already_final", row);
|
|
@@ -27652,7 +27653,7 @@ async function readSavedBench(cwd) {
|
|
|
27652
27653
|
if (v.v === 2 && v.currency === "credits" && v.cost && v.charged) return { kind: "credits", record: parsed };
|
|
27653
27654
|
return { kind: "coins", record: parsed };
|
|
27654
27655
|
}
|
|
27655
|
-
var LLM_PRICE_COIN_TERMS_LINE = "This bench was measured under the old
|
|
27656
|
+
var LLM_PRICE_COIN_TERMS_LINE = "This bench was measured under the old Nex terms, when a game declared a fixed Nex price per call. In-game calls are now billed in the player's credits, as used, and a game declares a per-call CEILING (maxCredits) instead \u2014 a Nex price is not that number. Re-run the bench for the figures to declare.";
|
|
27656
27657
|
async function reportSavedPrice(cwd, opts, log) {
|
|
27657
27658
|
const saved = await readSavedBench(cwd);
|
|
27658
27659
|
const rerun = 'genex llm bench "<prompt>" --max-credits <n> --user-approved';
|
|
@@ -27725,7 +27726,7 @@ async function reportSavedPrice(cwd, opts, log) {
|
|
|
27725
27726
|
log.dim(` from ${LLM_BENCH_FILE}, benchmarked ${old.ranAt ?? "at an unknown time"}${old.modelId ? ` on ${old.modelId}` : ""}`);
|
|
27726
27727
|
log.plain("");
|
|
27727
27728
|
log.warn(` ${LLM_PRICE_COIN_TERMS_LINE}`);
|
|
27728
|
-
const measured = legacy.p95 === null ? "no settled sample" : `charged
|
|
27729
|
+
const measured = legacy.p95 === null ? "no settled sample" : `charged Nex p50 ${legacy.p50} \xB7 p95 ${legacy.p95} \xB7 max ${legacy.max}`;
|
|
27729
27730
|
const recommended = legacy.recommendedEstimateCoins === null ? "no price recommended" : `it recommended estimateCoins ${legacy.recommendedEstimateCoins}${legacy.recommendedPerCallMaxCoins != null ? ` and perCallMaxCoins ${legacy.recommendedPerCallMaxCoins}` : ""}`;
|
|
27730
27731
|
log.dim(` What it measured then: ${measured}; ${recommended}.`);
|
|
27731
27732
|
log.dim(` Re-run: ${c.cyan(rerun)}`);
|
|
@@ -29659,7 +29660,7 @@ function oneParagraph(input) {
|
|
|
29659
29660
|
const runnerText = input.runners.length === 0 ? "no runner yet" : input.runners.map((r) => r === "claude" ? "Claude Code" : "Codex").join(" and ");
|
|
29660
29661
|
return [
|
|
29661
29662
|
` Genex approval stored in ${input.credentialFile} (only this machine can read it). It can`,
|
|
29662
|
-
" answer approved requests and spend nothing \u2014 no
|
|
29663
|
+
" answer approved requests and spend nothing \u2014 no Nex, no credits, no",
|
|
29663
29664
|
" account changes. Your Claude or ChatGPT sign-in is never read, copied or",
|
|
29664
29665
|
" sent anywhere; the watcher only starts the official CLI you already use.",
|
|
29665
29666
|
` Runners ${runnerText}`,
|
|
@@ -30790,8 +30791,8 @@ ${c.bold("Usage")}
|
|
|
30790
30791
|
the item id your game passes
|
|
30791
30792
|
to buy({ skuId }) \u2014 the platform owns the
|
|
30792
30793
|
catalog, so this is the only way one exists.
|
|
30793
|
-
Prices come off a fixed grid, printed on
|
|
30794
|
-
refusal. "test <id>" gives you a free copy of
|
|
30794
|
+
Prices in Nex come off a fixed grid, printed on
|
|
30795
|
+
any refusal. "test <id>" gives you a free copy of
|
|
30795
30796
|
your own item: you can't buy from your own game,
|
|
30796
30797
|
so this is how you check the shop works.
|
|
30797
30798
|
--icon <url> is the item's square art, shown in
|
|
@@ -31543,7 +31544,7 @@ ${c.bold("What it does and does not touch")}
|
|
|
31543
31544
|
prints. Genex never sees your vendor account.
|
|
31544
31545
|
|
|
31545
31546
|
Its GENEX approval is a player-scoped one: it can answer requests you already approved and can
|
|
31546
|
-
spend nothing \u2014 no
|
|
31547
|
+
spend nothing \u2014 no Nex, no credits, no account changes. It lives in ~/.genex/player.json,
|
|
31547
31548
|
separate from your creator sign-in, and the dashboard can revoke it at any time.
|
|
31548
31549
|
|
|
31549
31550
|
The log under ~/.genex/logs/ carries ids, timings and outcomes \u2014 never what a game asked for
|
|
@@ -32378,7 +32379,7 @@ function applyValueFlag(options, flag, value) {
|
|
|
32378
32379
|
case "--price": {
|
|
32379
32380
|
const n = Number(value);
|
|
32380
32381
|
if (value.trim() === "" || !Number.isInteger(n) || n < 0) {
|
|
32381
|
-
throw new Error(`Invalid --price value: ${value} (whole
|
|
32382
|
+
throw new Error(`Invalid --price value: ${value} (whole Nex, e.g. 100; 0 is Free)`);
|
|
32382
32383
|
}
|
|
32383
32384
|
options.price = n;
|
|
32384
32385
|
break;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@genex-ai/cli-demo",
|
|
3
|
-
"version": "1.36.
|
|
3
|
+
"version": "1.36.16-dev.1039",
|
|
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": {
|
|
@@ -156,8 +156,8 @@ vendored code from memory of another engine.
|
|
|
156
156
|
| in-world motion art or another requested video | `$genex-ai-video` |
|
|
157
157
|
| sound effect, one looping music bed, or a short spoken line | `$genex-ai-sfx`, `$genex-ai-music`, or `$genex-ai-voice` |
|
|
158
158
|
| requested UI/HUD/menu/interface work, a visible UI problem, or an interface you decided this game wants built with generated art | `$genex-threejs-game-ui` |
|
|
159
|
-
| selling anything for
|
|
160
|
-
| an item for the Genex store — sell a sword, a skin or a pet with its picture and description, "other games can use it too" — or another creator's shared item added to this game (`npx genex items add <id>`). Build the item first; the person puts it on sale | `$genex-items` (with `$genex-monetization`) |
|
|
159
|
+
| selling anything for Nex (Genex's currency, bought with real money) or for real money with no currency named: a shop, an item catalog, boosts, cosmetics, "make it earn"; a fixed Nex price is a store item. Coins, gold or gems are the game's own earned currency, not this row. Also any request for a loot box, gacha, wager, casino mechanic or donation prompt, which that skill refuses and replaces | `$genex-monetization` |
|
|
160
|
+
| an item for the Genex store — sell a sword, a skin or a pet for Nex with its picture and description, "other games can use it too" — or another creator's shared item added to this game (`npx genex items add <id>`). Build the item first, list it with `npx genex shop add … --json` without asking; the person puts it on sale | `$genex-items` (with `$genex-monetization`) |
|
|
161
161
|
| the game calls a language model AT RUNTIME on the player's credits, billed as used under a per-call ceiling the game benchmarks with `npx genex llm bench`: NPCs that answer in their own words, dialogue or quests written per save, a judge reading what the player typed — one-time calls, or a standing budget the player approves once. Check the lane with `npx genex llm models` before designing it in | `$genex-llm-in-games` |
|
|
162
162
|
| cinematic menu/title/pause/victory/defeat/lobby/credits video treatment | `$genex-ai-menu` |
|
|
163
163
|
| drawn HUD chrome the game's style wants—one element or a matched set of frames, masks, and icons | `$genex-ai-hud` |
|
|
@@ -1,18 +1,35 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: genex-items
|
|
3
|
-
description: Make game items for the Genex store and add other creators' shared items to this game. Build the item into the game first, list it as a draft with `genex shop add … --json` (before the game is published it waits as a local draft), apply owned items from getEntitlements() at boot, and integrate a shared item with `genex items add`. Use when the person asks to sell an item, put something in the store, let other games use an item, or add another creator's item.
|
|
3
|
+
description: Make game items for the Genex store, priced in Nex, and add other creators' shared items to this game. Build the item into the game first, list it as a draft with `genex shop add … --json` (before the game is published it waits as a local draft), apply owned items from getEntitlements() at boot, and integrate a shared item with `genex items add`. Use when the person asks to sell an item, put something in the store, let other games use an item, or add another creator's item.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Genex Items
|
|
7
7
|
|
|
8
8
|
An item is something a player owns: a name, a description, a picture, a price
|
|
9
|
-
in
|
|
9
|
+
in Nex and free-text tags. A 3D model, sounds or code are optional. A player
|
|
10
10
|
buys an item once and owns it in every game that has added it. Your game
|
|
11
11
|
decides what the item looks like and what it does there.
|
|
12
12
|
|
|
13
|
-
`$genex-monetization` owns the hard rules for anything sold for
|
|
13
|
+
`$genex-monetization` owns the hard rules for anything sold for Nex. They apply
|
|
14
14
|
to items without exception: no paid randomness, no gambling, no donation
|
|
15
|
-
mechanics, no pressure, and a real-money price beside every
|
|
15
|
+
mechanics, no pressure, and a real-money price beside every Nex price.
|
|
16
|
+
|
|
17
|
+
## Nex, and the game's own currencies
|
|
18
|
+
|
|
19
|
+
Genex's own currency is **Nex**: players buy it with real money and spend it in
|
|
20
|
+
the Genex store. It is a name, written "1,200 Nex", never "Nexes".
|
|
21
|
+
|
|
22
|
+
- **A price in Nex means a Genex store item.** Build the thing into the game,
|
|
23
|
+
then list it with `npx genex shop add … --json` without asking, before or
|
|
24
|
+
after the first publish (a folder with no linked game keeps a local draft,
|
|
25
|
+
below). Price it from the store's grid: `npx genex shop list` prints it, and
|
|
26
|
+
a price off the grid is refused with `price_off_grid`. The person puts it on
|
|
27
|
+
sale.
|
|
28
|
+
- **A price in coins, gold, gems or any other name is a currency of the game's
|
|
29
|
+
own**, earned by playing: it lives in the game's code, not in the store. If
|
|
30
|
+
the person also says it is for real money, ask once whether they mean Nex.
|
|
31
|
+
- **A request to sell something for real money that names no currency**
|
|
32
|
+
("paid characters", "sell skins") means Nex store items.
|
|
16
33
|
|
|
17
34
|
## The rules
|
|
18
35
|
|
|
@@ -48,7 +65,7 @@ mechanics, no pressure, and a real-money price beside every coin price.
|
|
|
48
65
|
sheet "Drag to turn".
|
|
49
66
|
3. **Ask once, only if the person did not say:** "Can other games use it too?"
|
|
50
67
|
Only a durable item can be shared.
|
|
51
|
-
4. **List it as a draft:**
|
|
68
|
+
4. **List it as a draft, without asking:**
|
|
52
69
|
|
|
53
70
|
```bash
|
|
54
71
|
npx genex shop add "Frost Axe" --price 1200 --type durable \
|
|
@@ -123,19 +140,19 @@ for (const item of await getShop()) {
|
|
|
123
140
|
card.onclick = async () => {
|
|
124
141
|
const result = await buy({ skuId: item.id }); // the first await in the click
|
|
125
142
|
if (result.status === 'succeeded' || result.status === 'already_owned') await applyOwnedItems();
|
|
126
|
-
if (result.status === 'insufficient_balance')
|
|
143
|
+
if (result.status === 'insufficient_balance') showNotEnoughNex(); // nothing was charged
|
|
127
144
|
};
|
|
128
145
|
}
|
|
129
146
|
```
|
|
130
147
|
|
|
131
|
-
`knowsItem`, `applyItem`, `drawCard` and `
|
|
148
|
+
`knowsItem`, `applyItem`, `drawCard` and `showNotEnoughNex` are your game
|
|
132
149
|
code. A durable is applied on every boot and never consumed. A consumable is
|
|
133
150
|
consumed and then applied once, the `$genex-monetization` rule: applying one
|
|
134
151
|
without consuming it would hand it out again on every boot.
|
|
135
152
|
|
|
136
153
|
`already_owned` means the player owns it already and nothing was charged; it is
|
|
137
|
-
not an error. `insufficient_balance` means the player was short of
|
|
138
|
-
Confirm; nothing was charged, so tell them they need more
|
|
154
|
+
not an error. `insufficient_balance` means the player was short of Nex at
|
|
155
|
+
Confirm; nothing was charged, so tell them they need more Nex. A shared item
|
|
139
156
|
may have been bought in another game, so it can be in `getEntitlements()`
|
|
140
157
|
before anyone buys it here.
|
|
141
158
|
|
|
@@ -179,7 +196,7 @@ own it keep it.
|
|
|
179
196
|
- [ ] It was listed with `genex shop add … --json` and left as a draft (a
|
|
180
197
|
local draft before the game is published)
|
|
181
198
|
- [ ] Owned items are applied from `getEntitlements()` at every boot
|
|
182
|
-
- [ ] Every
|
|
199
|
+
- [ ] Every Nex price shows `priceDisplayUsdCents` beside it
|
|
183
200
|
- [ ] No price, share or item type is typed into the game
|
|
184
201
|
- [ ] A shared item's package was reviewed before any of it ran
|
|
185
202
|
- [ ] The rules of `$genex-monetization` hold for every item
|
|
@@ -332,7 +332,7 @@ function applyGeneration(res) { // THE only place output becomes game state
|
|
|
332
332
|
- Boot reads any stored id, calls `waitForGeneration(savedId)`, and passes the
|
|
333
333
|
result to the **same** `applyGeneration`. One writer, two entry points, is the
|
|
334
334
|
difference between "works once" and "survives a reload".
|
|
335
|
-
- Output is **data, never authority**: it may not grant
|
|
335
|
+
- Output is **data, never authority**: it may not grant Nex, items,
|
|
336
336
|
entitlements, scores or progression by saying so. `source: 'external'` carries
|
|
337
337
|
`modelProvenance: 'unverified'` because it is user-supplied — check it exactly
|
|
338
338
|
as you would check typed player input.
|
|
@@ -189,7 +189,7 @@ that before you reach for a cheaper model.
|
|
|
189
189
|
server still accepts them and reads them as the same numbers in credits, but
|
|
190
190
|
they are deprecated: rename them to `maxCredits`, `perCallMaxCredits`,
|
|
191
191
|
`perCallEstimateCredits` and `estimatedCreditsPerPeriod` when you touch the
|
|
192
|
-
code, and re-benchmark — a number measured as a
|
|
192
|
+
code, and re-benchmark — a number measured as a Nex price is not a ceiling.
|
|
193
193
|
|
|
194
194
|
## Worked example
|
|
195
195
|
|
|
@@ -1,24 +1,48 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: genex-monetization
|
|
3
|
-
description:
|
|
3
|
+
description: Sell things in a Genex game for Nex, the currency players buy with real money — fixed-price Genex store items (`genex shop add`) by default, dynamic payment requests only for amounts decided while playing. Use when the player asks to sell things, add a shop, monetize, or make the game earn. Carries the hard rules: no paid randomness, no gambling, no donation mechanics, and a real-money price beside every Nex price.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Genex Monetization
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
8
|
+
Genex's own currency is **Nex**: players buy it with real money and spend it in
|
|
9
|
+
the Genex store. Spending it inside a game is a ledger movement the game never
|
|
10
|
+
touches. You design what is for sale; the platform owns the wallet, the
|
|
11
|
+
confirmation, and the money. Nex is a name: "1,200 Nex", never "Nexes".
|
|
12
12
|
|
|
13
13
|
Load this when the user asks to sell something or add payments. Build the game
|
|
14
|
-
loop before adding commerce
|
|
14
|
+
loop before adding commerce.
|
|
15
|
+
|
|
16
|
+
## Which currency the person means
|
|
17
|
+
|
|
18
|
+
- **A price in Nex means a Genex store item.** Build the thing into the game,
|
|
19
|
+
then list it with `npx genex shop add … --json` without asking, before or
|
|
20
|
+
after the first publish (a folder with no linked game keeps a local draft).
|
|
21
|
+
Price it from the store's grid: `npx genex shop list` prints it, and a price
|
|
22
|
+
off the grid is refused with `price_off_grid`. The person puts it on sale.
|
|
23
|
+
`$genex-items` owns the steps.
|
|
24
|
+
- **A price in coins, gold, gems or any other name is a currency of the game's
|
|
25
|
+
own**, earned by playing (see the per-game currency note below). If the
|
|
26
|
+
person also says it is for real money, ask once whether they mean Nex.
|
|
27
|
+
- **Selling something for real money with no currency named** ("paid
|
|
28
|
+
characters", "sell skins") means Nex store items.
|
|
29
|
+
|
|
30
|
+
## Store items first, payment requests for amounts decided in play
|
|
31
|
+
|
|
32
|
+
Anything with a fixed Nex price is a **store item** (`$genex-items`): a sword,
|
|
33
|
+
a skin, a character, a level pack, a boost. The store draws its card, its
|
|
34
|
+
purchase sheet and its receipt; the game delivers it from `getEntitlements()`.
|
|
35
|
+
|
|
36
|
+
A **dynamic payment request** (`requestPayment()`, below) is only for an amount
|
|
37
|
+
decided while playing: a player-approved action whose price the run sets, or a
|
|
38
|
+
top-up of a size the player picks. Never wrap a fixed-price item in one.
|
|
15
39
|
|
|
16
40
|
## The hard rules, and the test that generalizes them
|
|
17
41
|
|
|
18
42
|
Before you build ANY purchasable thing, run this test:
|
|
19
43
|
|
|
20
|
-
> **Does the player pay?** (with
|
|
21
|
-
> directly or indirectly, including a per-game token or key that
|
|
44
|
+
> **Does the player pay?** (with Nex, or with anything bought with Nex —
|
|
45
|
+
> directly or indirectly, including a per-game token or key that Nex bought.)
|
|
22
46
|
> **Is the outcome uncertain when they pay?**
|
|
23
47
|
> **Is there a prize** — an item, currency, or advantage they wanted?
|
|
24
48
|
>
|
|
@@ -30,12 +54,12 @@ catches mechanics that do not exist yet — which a list of banned names cannot.
|
|
|
30
54
|
Four things are never built, whatever the request:
|
|
31
55
|
|
|
32
56
|
1. **No paid randomness.** No loot boxes, gacha, mystery boxes, crates, packs,
|
|
33
|
-
prize wheels, raffles, "spin for a bonus", "chance to double your
|
|
57
|
+
prize wheels, raffles, "spin for a bonus", "chance to double your Nex".
|
|
34
58
|
Directly or indirectly.
|
|
35
|
-
2. **No gambling in
|
|
36
|
-
slot mechanics denominated in
|
|
59
|
+
2. **No gambling in Nex.** No wagering, staking, betting, coinflips, casino or
|
|
60
|
+
slot mechanics denominated in Nex or in anything Nex buys.
|
|
37
61
|
3. **No donation or begging mechanics.** No "donate to me" prompts, tip jars, or
|
|
38
|
-
player-to-player
|
|
62
|
+
player-to-player Nex transfers. Nex buys goods; it never just moves.
|
|
39
63
|
4. **No pressure.** No countdown timers, "ends in", "limited time", "only N
|
|
40
64
|
left", or stock counters anywhere in the shop.
|
|
41
65
|
|
|
@@ -67,10 +91,10 @@ once and build the compliant version. There is no escalation path.
|
|
|
67
91
|
| They asked for | Build |
|
|
68
92
|
| --- | --- |
|
|
69
93
|
| Loot box, crate, mystery box, card pack | A direct-purchase shop: every item listed at a fixed price, contents visible. For the collecting feel, add a **visible catalog with a completion track** — any purchase advances a meter to a stated milestone reward. |
|
|
70
|
-
| Gacha, banner, pull, summon | A **deterministic unlock**: the character costs a fixed price, or unlocks at a stated number of runs.
|
|
94
|
+
| Gacha, banner, pull, summon | A **deterministic unlock**: the character costs a fixed price, or unlocks at a stated number of runs. Nex may buy a stated, visible number of those points. |
|
|
71
95
|
| Prize wheel, spin-to-win, slot machine | A **free spin earned by finishing a run** (never bought), or a **"pick one of three"** screen where all three are visible and the player chooses. Keeps the moment, drops the wager. |
|
|
72
|
-
| Casino game, blackjack, poker, roulette | The same game with **chips that are granted free each session, reset on restart, cannot be bought and cannot become
|
|
73
|
-
| Coinflip, double-or-nothing, wager my
|
|
96
|
+
| Casino game, blackjack, poker, roulette | The same game with **chips that are granted free each session, reset on restart, cannot be bought and cannot become Nex**. It becomes a card game. Sell cosmetics — table felt, card backs — for Nex. |
|
|
97
|
+
| Coinflip, double-or-nothing, wager my Nex | A **skill-based risk/reward inside the run**: a harder route with a bigger payout, staking the run's own score, which was never purchasable. |
|
|
74
98
|
| Betting on matches, PvP wagers | **Leaderboards with a fixed cosmetic reward for placement**, paid by the game. Nobody's balance goes down. |
|
|
75
99
|
| Donate button, tip jar, "pls donate" | A **gift that is a purchase**: they buy a specific item at a stated price and give it. Or a **supporter cosmetic** — a badge or aura at a normal price, where what's delivered is visible. |
|
|
76
100
|
| Pay to remove a wait / energy gate | **Delete the gate** and sell a permanent upgrade or a cosmetic. Pace with difficulty, not with a timer. |
|
|
@@ -94,28 +118,71 @@ Nine checks. Each one is answerable about your actual design.
|
|
|
94
118
|
shop, remove the annoyance instead of selling the cure.
|
|
95
119
|
5. **Everything sold is reachable free.** Spending is a shortcut or a
|
|
96
120
|
decoration, never the only path.
|
|
97
|
-
6. **A
|
|
98
|
-
|
|
99
|
-
|
|
121
|
+
6. **A purchase has one clear price and promised result.** A store item's
|
|
122
|
+
price is on the server's grid (`genex shop list`), or Free. A dynamic
|
|
123
|
+
payment's `amountCoins` is a positive whole number of Nex from 1 to 100000,
|
|
124
|
+
further bounded by the platform surface, and needs no grid.
|
|
100
125
|
7. **Every price shows real money next to it.** The platform confirmation
|
|
101
126
|
shows the server-frozen `priceDisplayUsdCents`. If rendering a receipt or
|
|
102
|
-
|
|
127
|
+
store price in the game, render that value too; never invent an exchange
|
|
103
128
|
rate. A button can simply say “Unlock” and let Genex show the price.
|
|
104
|
-
8. **One currency layer between money and goods.**
|
|
105
|
-
earned currency buys per-game upgrades. They never
|
|
129
|
+
8. **One currency layer between money and goods.** Nex buys items. A per-game
|
|
130
|
+
earned currency (coins, gold, gems) buys per-game upgrades. They never
|
|
131
|
+
convert into each other.
|
|
106
132
|
9. **Paid ownership survives a reinstall.** Receipts live on the server; the
|
|
107
133
|
game recovers them on every boot and restores the promised state. A
|
|
108
134
|
confirmation can expire; a paid receipt does not expire with it.
|
|
109
135
|
|
|
110
|
-
For a per-game earned currency,
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
136
|
+
For a per-game earned currency (coins, gold, gems: earned by playing, never
|
|
137
|
+
bought), the load-bearing number is **minutes of play per unit earned**. Set
|
|
138
|
+
it, then price the cheapest meaningful item at one to three sessions of
|
|
139
|
+
earning. Everything else follows. Spend sinks come in three kinds — permanent
|
|
140
|
+
upgrades, refills, cosmetics — and cosmetics are what absorbs late-game
|
|
114
141
|
currency without touching balance.
|
|
115
142
|
|
|
116
|
-
##
|
|
143
|
+
## Store items (the default)
|
|
144
|
+
|
|
145
|
+
Every fixed-price item is a Genex store item. `$genex-items` owns making one —
|
|
146
|
+
its picture, its description, the draft the person puts on sale, items other
|
|
147
|
+
games can use, and adding another creator's item. Its rules add to these; they
|
|
148
|
+
never replace them.
|
|
149
|
+
|
|
150
|
+
```bash
|
|
151
|
+
npx genex shop list # the price grid in Nex, and this game's items
|
|
152
|
+
npx genex shop add "Iron Key" --price 100 --type durable --json
|
|
153
|
+
npx genex shop test <sku-id> # owner-only free entitlement; no Nex moves
|
|
154
|
+
```
|
|
117
155
|
|
|
118
|
-
|
|
156
|
+
List the item once it works in the game, without asking. When the Genex
|
|
157
|
+
store is on, `genex shop add` makes a draft: `getShop()` leaves it out of the
|
|
158
|
+
live game and `buy()` cannot sell it until the person puts it on sale. `--json`
|
|
159
|
+
makes the Genex app show its **Put on sale** card; never put it on sale
|
|
160
|
+
yourself. Before the game is published, items can be made all the same: they
|
|
161
|
+
wait as local drafts and go up as drafts with `genex publish`; still, only the
|
|
162
|
+
person puts one on sale. With the store off, the item is on sale at once:
|
|
163
|
+
tell the person so.
|
|
164
|
+
|
|
165
|
+
`getShop()` reads store items even for guests and preview builds;
|
|
166
|
+
`buy({ skuId })` requests their platform confirmation. Prices follow the grid
|
|
167
|
+
printed by `genex shop list`. Render `name`, `iconUrl`, `priceCoins` (the price
|
|
168
|
+
in Nex) and `priceDisplayUsdCents`; use an image of the actual item for its
|
|
169
|
+
icon. `genex shop set` edits a listing and `genex shop remove` retires it
|
|
170
|
+
without removing a buyer's ownership.
|
|
171
|
+
|
|
172
|
+
Re-list `getEntitlements({ excludeConsumed: true })` on boot. Branch on
|
|
173
|
+
`skuType` (`consumable`/`durable`), never `type` (purchase/gift/test/free).
|
|
174
|
+
For a consumable, await `consumeEntitlement(id)` BEFORE applying its effect
|
|
175
|
+
and skip `alreadyConsumed: true`. This prevents repeated delivery but a crash
|
|
176
|
+
between consume and save can lose that effect; do not promise atomic game-state
|
|
177
|
+
delivery. A durable ownership unlock is never consumed: restore it from the
|
|
178
|
+
entitlement on every boot. Dynamic receipts do not use this consumption API.
|
|
179
|
+
|
|
180
|
+
## Dynamic payment requests (amounts decided in play)
|
|
181
|
+
|
|
182
|
+
Only for an amount the run decides — a player-approved action whose price play
|
|
183
|
+
sets, a top-up of a size the player picks. A fixed price is a store item
|
|
184
|
+
(above). From `@genex-ai/embed-sdk`, after `initEmbed()` and player identity
|
|
185
|
+
are ready:
|
|
119
186
|
|
|
120
187
|
```ts
|
|
121
188
|
import { requestPayment, getPayment, getPayments } from '@genex-ai/embed-sdk';
|
|
@@ -126,7 +193,7 @@ const order = {
|
|
|
126
193
|
orderId: 'unlock-iron-gate-run-42',
|
|
127
194
|
title: 'Open the iron gate',
|
|
128
195
|
description: 'Unlock the shortcut for this run.',
|
|
129
|
-
amountCoins: 75,
|
|
196
|
+
amountCoins: 75, // Nex, set by this run: the gate's toll grows with depth
|
|
130
197
|
};
|
|
131
198
|
|
|
132
199
|
buyButton.addEventListener('click', async () => {
|
|
@@ -144,15 +211,16 @@ buyButton.addEventListener('click', async () => {
|
|
|
144
211
|
});
|
|
145
212
|
```
|
|
146
213
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
The game never supplies a recipient: the platform uses the game's
|
|
150
|
-
This API is for
|
|
151
|
-
consent flow (`$genex-llm-in-games`), not a payment wrapped around
|
|
214
|
+
A payment request needs no `genex shop add`, SKU id or item icon: it buys a
|
|
215
|
+
described game action whose amount was decided in play. The hard rules above
|
|
216
|
+
still apply. The game never supplies a recipient: the platform uses the game's
|
|
217
|
+
own split. This API is for Nex commerce; in-game LLM requests use their own
|
|
218
|
+
funding and consent flow (`$genex-llm-in-games`), not a payment wrapped around
|
|
219
|
+
a model call.
|
|
152
220
|
|
|
153
221
|
The server freezes `title`, `description`, `amountCoins` and the real-money
|
|
154
222
|
value before Genex asks the player. **Creating a request neither debits nor
|
|
155
|
-
reserves
|
|
223
|
+
reserves Nex.** Only the player's explicit confirmation on the platform
|
|
156
224
|
surface can move money. The game cannot draw, skin or confirm that sheet.
|
|
157
225
|
|
|
158
226
|
An `orderId` belongs to this player in this game. The same id with the same
|
|
@@ -174,7 +242,7 @@ and `pending`. A timeout or uncertain response is `pending`: it is not proof of
|
|
|
174
242
|
failure and not permission to apply the action or charge again. `canceled` is
|
|
175
243
|
ordinary player choice; do not show an error for it.
|
|
176
244
|
|
|
177
|
-
**Your game cannot read the player's
|
|
245
|
+
**Your game cannot read the player's Nex balance, and no HUD should show one.**
|
|
178
246
|
The wallet spans the platform; let Genex show the balance and handle consent.
|
|
179
247
|
Only fulfil a payment whose authoritative receipt says `succeeded`.
|
|
180
248
|
|
|
@@ -186,10 +254,10 @@ The surface depends on the shell (`$genex-threejs-embed-auth`,
|
|
|
186
254
|
pointer lock before it asks. Treat payment like a pause; re-enter fullscreen
|
|
187
255
|
and pointer lock from the player's next Resume click.
|
|
188
256
|
- **The Genex app has no purchase surface yet.** `requestPayment()` fails
|
|
189
|
-
without opening a sheet
|
|
257
|
+
without opening a sheet, and `buy()` resolves
|
|
190
258
|
`failed` without opening anything too. When `getShell()` from `@genex-ai/embed-sdk` is `"app"`,
|
|
191
259
|
hide payment buttons or make the shop browse-only; do not show a failure toast.
|
|
192
|
-
- Local test mode has no wallet. Preview builds cannot spend real
|
|
260
|
+
- Local test mode has no wallet. Preview builds cannot spend real Nex either.
|
|
193
261
|
Verify layout and delivery with game-owned test fixtures; a real payment needs
|
|
194
262
|
a published build, a signed-in player and explicit platform consent.
|
|
195
263
|
|
|
@@ -231,53 +299,20 @@ response and a canceled order. Also check concurrent fulfillment if the game
|
|
|
231
299
|
permits multiple tabs; two read-modify-write saves are not a transaction.
|
|
232
300
|
Do not make a real-money purchase automatically to test the feature.
|
|
233
301
|
|
|
234
|
-
## Existing catalog items
|
|
235
|
-
|
|
236
|
-
Catalog APIs remain supported for published games and items that need platform
|
|
237
|
-
entitlements or resale. They are optional, not a prerequisite for dynamic
|
|
238
|
-
payments:
|
|
239
|
-
|
|
240
|
-
```bash
|
|
241
|
-
npx genex shop add "Iron Key" --price 100 --type durable
|
|
242
|
-
npx genex shop list
|
|
243
|
-
npx genex shop test <sku-id> # owner-only free entitlement; no coin moves
|
|
244
|
-
```
|
|
245
|
-
|
|
246
|
-
`getShop()` reads catalog items even for guests and preview builds;
|
|
247
|
-
`buy({ skuId })` requests their platform confirmation. These prices must follow
|
|
248
|
-
the grid printed by `genex shop list`. Render `name`, `iconUrl`, `priceCoins`
|
|
249
|
-
and `priceDisplayUsdCents`; use an image of the actual item for its icon.
|
|
250
|
-
`genex shop set` edits a listing and `genex shop remove` retires it without
|
|
251
|
-
removing a buyer's ownership. When the Genex store is on, `genex shop add`
|
|
252
|
-
makes a draft: `getShop()` leaves it out of the live game and `buy()` cannot
|
|
253
|
-
sell it until the person puts it on sale. Add `--json` so the Genex app shows
|
|
254
|
-
its **Put on sale** card, and never put it on sale yourself. Before the game
|
|
255
|
-
is published, items can be made all the same: they wait as local drafts and go
|
|
256
|
-
up as drafts with `genex publish`; still, only the person puts one on sale.
|
|
257
|
-
|
|
258
|
-
Items for the Genex store — a picture, a description, a draft the person puts
|
|
259
|
-
on sale, items other games can use, and adding another creator's item — are
|
|
260
|
-
`$genex-items`. Its rules add to these; they never replace them.
|
|
261
|
-
|
|
262
|
-
Re-list `getEntitlements({ excludeConsumed: true })` on boot. Branch on
|
|
263
|
-
`skuType` (`consumable`/`durable`), never `type` (purchase/gift/test/free).
|
|
264
|
-
For a consumable, await `consumeEntitlement(id)` BEFORE applying its effect
|
|
265
|
-
and skip `alreadyConsumed: true`. This prevents repeated delivery but a crash
|
|
266
|
-
between consume and save can lose that effect; do not promise atomic game-state
|
|
267
|
-
delivery. A durable ownership unlock is never consumed: restore it from the
|
|
268
|
-
entitlement on every boot. Dynamic receipts do not use this consumption API.
|
|
269
|
-
|
|
270
302
|
## Checklist
|
|
271
303
|
|
|
272
304
|
- [ ] The game has a worthwhile loop before commerce
|
|
273
|
-
- [ ]
|
|
274
|
-
|
|
275
|
-
- [ ]
|
|
305
|
+
- [ ] Every fixed Nex price is a store item: built first, listed with
|
|
306
|
+
`genex shop add … --json` at a grid price, left for the person to put on sale
|
|
307
|
+
- [ ] A dynamic payment carries only an amount decided in play; its saved order
|
|
308
|
+
binds one stable id, exact terms and promised action
|
|
309
|
+
- [ ] `buy()` and `requestPayment()` start directly from a click/tap
|
|
310
|
+
- [ ] Genex shows the frozen Nex and real-money price and receives consent
|
|
276
311
|
- [ ] No payment buttons in the Genex app
|
|
277
312
|
- [ ] Cancellation is silent; uncertain outcomes remain pending
|
|
278
|
-
- [ ] Recovery reads receipts before applying any paid action
|
|
313
|
+
- [ ] Recovery reads entitlements and receipts before applying any paid action
|
|
279
314
|
- [ ] Fulfillment is idempotent, including reload and concurrent-tab cases
|
|
280
|
-
- [ ] No paid randomness,
|
|
315
|
+
- [ ] No paid randomness, Nex wagering, donation prompt or pressure timer
|
|
281
316
|
- [ ] Nothing sold is unreachable without paying
|
|
282
317
|
|
|
283
318
|
## Troubleshooting
|
|
@@ -306,8 +341,9 @@ a published build for an explicitly approved real purchase.
|
|
|
306
341
|
**`surface_cap`** — the amount exceeds the server's limit for this confirmation
|
|
307
342
|
surface. Do not split it into smaller hidden charges to bypass the limit.
|
|
308
343
|
|
|
309
|
-
**`price_off_grid`** —
|
|
310
|
-
|
|
344
|
+
**`price_off_grid`** — a store item's price is not on the grid. The refusal
|
|
345
|
+
prints it (so does `genex shop list`); pick a price from it, or 0 for Free.
|
|
346
|
+
Dynamic payments accept whole amounts subject to their own caps.
|
|
311
347
|
|
|
312
|
-
**Everything
|
|
313
|
-
|
|
348
|
+
**Everything Nex-related 404s** — Nex commerce is off on this stand. Build
|
|
349
|
+
the rest and report the unavailable lane.
|