@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 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 coin at a fixed, visible price, and NEVER sells chance. Run this test on any purchasable thing before building it: does the player pay (with coin, or with anything coin bought, directly or indirectly), is the outcome uncertain when they pay, and is there a prize they wanted \u2014 all three yes means it is paid randomness, and you build the deterministic version instead. That rules out loot boxes, gacha, mystery boxes, crates, card packs, prize wheels and raffles; wagering, staking, betting, coinflips and casino or slot mechanics denominated in coin; and donation prompts, tip jars or any player-to-player coin transfer, because coin buys goods and never just moves. Randomness the player EARNS by playing is gameplay, not commerce \u2014 an enemy dropping a random item, a chest found in the level, a procedural layout, a crit roll \u2014 and is completely fine. Every coin price renders its real-money equivalent beside it (the server sends one with every item), item prices sit on the platform's price grid so no player is left holding change they cannot spend, and nothing in a shop carries a countdown, "limited time", or a stock counter. When a request crosses one of these lines, name the mechanic, give the one-sentence reason, propose a specific compliant alternative, and build that \u2014 never the banned version "as an option", never a partial one, and never after asking the player to confirm they want it. Load \`$genex-monetization\` before building a shop.
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 coin, credits, items, entitlements or rewards. Load \`$genex-llm-in-games\` before writing any of it.
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 coin, 0 or more");
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)} coin`;
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)} coin ($${usd})`;
8743
+ return `${formatCoins(coins)} Nex ($${usd})`;
8744
8744
  }
8745
8745
  function priceGridLine(grid, freeAllowed) {
8746
- return `${[...freeAllowed ? ["Free"] : [], ...grid.map(formatCoins)].join(" \xB7 ")} coin`;
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 <coin>",
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 coin (0 is Free).`));
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)} coin isn't one of this game's prices.` } : {},
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 coin (0 is Free).`));
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)} coin per sale${item.tags.length ? ` \xB7 ${item.tags.join(", ")}` : ""}`);
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 COIN terms, when a game declared a fixed 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 coin price is not that number. Re-run the bench for the figures to declare.";
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 coins p50 ${legacy.p50} \xB7 p95 ${legacy.p95} \xB7 max ${legacy.max}`;
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 coins, no credits, 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 any
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 coins, no credits, no account changes. It lives in ~/.genex/player.json,
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 coin, e.g. 100; 0 is Free)`);
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.15-dev.1036",
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 platform coin: a shop, an item catalog, boosts, cosmetics, "make it earn"; 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 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 coin and free-text tags. A 3D model, sounds or code are optional. A player
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 coin. They apply
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 coin price.
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') showNotEnoughCoin(); // nothing was charged
143
+ if (result.status === 'insufficient_balance') showNotEnoughNex(); // nothing was charged
127
144
  };
128
145
  }
129
146
  ```
130
147
 
131
- `knowsItem`, `applyItem`, `drawCard` and `showNotEnoughCoin` are your game
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 coin at
138
- Confirm; nothing was charged, so tell them they need more coin. A shared item
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 coin price shows `priceDisplayUsdCents` beside it
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 coin, items,
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 coin price is not a ceiling.
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: Add dynamic coin payments for game goods or actions — platform consent, recoverable receipts, and idempotent delivery without a required product catalog. 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 in coin, no donation mechanics, and a real-money price beside every coin price.
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
- Games on Genex can sell things for **coin**, the platform currency. The player
9
- buys coin with real money once; spending it inside a game is a ledger movement
10
- the game never touches. You design what is for sale; the platform owns the
11
- wallet, the confirmation, and the money.
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; no shop screen or product catalog is required.
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 coin, or with anything bought with coin —
21
- > directly or indirectly, including a per-game token or key that coin bought.)
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 coins".
57
+ prize wheels, raffles, "spin for a bonus", "chance to double your Nex".
34
58
  Directly or indirectly.
35
- 2. **No gambling in coin.** No wagering, staking, betting, coinflips, casino or
36
- slot mechanics denominated in coin or in anything coin buys.
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 coin transfers. Coin buys goods; it never just moves.
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. Coin may buy a stated, visible number of those points. |
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 coin**. It becomes a card game. Sell cosmetics — table felt, card backs — for coin. |
73
- | Coinflip, double-or-nothing, wager my coins | 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. |
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 payment has one clear price and promised result.** `amountCoins` is a
98
- positive whole number from 1 to 100000, further bounded by the platform
99
- surface. Dynamic payments do not use the legacy catalog price grid.
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
- catalog price in the game, render that value too; never invent an exchange
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.** Coin buys items. A per-game
105
- earned currency buys per-game upgrades. They never convert into each other.
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, the load-bearing number is **minutes of play per
111
- unit earned**. Set it, then price the cheapest meaningful item at one to three
112
- sessions of earning. Everything else follows. Spend sinks come in three kinds —
113
- permanent upgrades, refills, cosmetics — and cosmetics are what absorbs late-game
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
- ## Dynamic payment requests
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
- From `@genex-ai/embed-sdk`, after `initEmbed()` and player identity are ready:
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
- No `genex shop add`, SKU id, catalog card or item icon is needed. A payment can
148
- buy a described game action, unlock or item. The hard rules above still apply.
149
- The game never supplies a recipient: the platform uses the game's own split.
150
- This API is for coin commerce; in-game LLM requests use their own funding and
151
- consent flow (`$genex-llm-in-games`), not a payment wrapped around a model call.
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 coin.** Only the player's explicit confirmation on the platform
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 coin balance, and no HUD should show one.**
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; the legacy `buy()` resolves
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 coin either.
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
- - [ ] The saved order binds one stable id, exact terms and promised action
274
- - [ ] `requestPayment()` starts directly from a click/tap
275
- - [ ] Genex shows the frozen coin and real-money price and receives consent
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, coin wagering, donation prompt or pressure timer
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`** — this applies to legacy catalog SKUs only. Dynamic
310
- payments accept whole amounts subject to their own caps.
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 coin-related 404s** — coin commerce is disabled on this stand.
313
- Build the rest and report the unavailable lane.
348
+ **Everything Nex-related 404s** — Nex commerce is off on this stand. Build
349
+ the rest and report the unavailable lane.