@genex-ai/cli-demo 1.7.0-dev.449 → 1.7.0
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 +255 -979
- package/package.json +1 -1
- package/templates/skills/genex-ai-texture/SKILL.md +1 -1
- package/templates/skills/genex-ai-video/SKILL.md +1 -1
- package/templates/skills/genex-game-director/references/routing-map.md +0 -1
- package/templates/skills/genex-getting-started/SKILL.md +11 -43
- package/templates/skills/genex-threejs-multiplayer/SKILL.md +11 -8
- package/templates/skills/genex-threejs-visual-validation/SKILL.md +3 -7
- package/templates/skills/genex-updates/SKILL.md +1 -1
- package/templates/skills/genex-monetization/SKILL.md +0 -292
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@genex-ai/cli-demo",
|
|
3
|
-
"version": "1.7.0
|
|
3
|
+
"version": "1.7.0",
|
|
4
4
|
"description": "Set up your project's agent workspace (.claude/.codex/.cursor in the game folder), authorize, create a game project, generate AI assets, and publish (genex CLI).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -225,7 +225,7 @@ first one is the one a screenshot of the whole arena will not show you.
|
|
|
225
225
|
|
|
226
226
|
## Troubleshooting
|
|
227
227
|
|
|
228
|
-
- **"Not authorized"** — run `npx @genex-ai/cli-demo@
|
|
228
|
+
- **"Not authorized"** — run `npx @genex-ai/cli-demo@latest init` first (it writes your `GENEX_TOKEN`).
|
|
229
229
|
- **"Out of credits" (`insufficient_credits`)** — the account has no credits left for
|
|
230
230
|
this texture generation. Tell the user the facts the CLI printed: their balance,
|
|
231
231
|
this generation's cost, and when their credits refill. Then offer to continue the
|
|
@@ -151,7 +151,7 @@ set belongs to `$genex-ai-hud` — both build on `npx genex image`/`video`.
|
|
|
151
151
|
|
|
152
152
|
## Troubleshooting
|
|
153
153
|
|
|
154
|
-
- **"Not authorized"** — run `npx @genex-ai/cli-demo@
|
|
154
|
+
- **"Not authorized"** — run `npx @genex-ai/cli-demo@latest init` first (it writes your `GENEX_TOKEN`).
|
|
155
155
|
- **"Prompt rejected"** — the provider's content-safety filter blocked the prompt.
|
|
156
156
|
This is non-retryable; retrying the same wording fails again. Rewrite the prompt.
|
|
157
157
|
- **Nothing plays / black surface** — the first `video.play()` must run inside a user
|
|
@@ -59,7 +59,6 @@ copy demo architecture.
|
|
|
59
59
|
| in-world motion art or another requested video | `$genex-ai-video` |
|
|
60
60
|
| sound effect, one looping music bed, or a short spoken line | `$genex-ai-sfx`, `$genex-ai-music`, or `$genex-ai-voice` |
|
|
61
61
|
| 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` |
|
|
62
|
-
| 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` |
|
|
63
62
|
| cinematic menu/title/pause/victory/defeat/lobby/credits video treatment | `$genex-ai-menu` |
|
|
64
63
|
| drawn HUD chrome the game's style wants—one element or a matched set of frames, masks, and icons | `$genex-ai-hud` |
|
|
65
64
|
| the game works but feels flat, floaty, or unresponsive: input response, camera, impacts, cooldowns, difficulty, fail/retry | `$genex-threejs-game-feel` |
|
|
@@ -150,55 +150,23 @@ If unsure, use `games` (also the server's fallback for anything unrecognized).
|
|
|
150
150
|
Your own files were left untouched. `genex init` only adds missing files and
|
|
151
151
|
refreshes the genex-owned ones.
|
|
152
152
|
|
|
153
|
-
##
|
|
153
|
+
## Reconnecting an existing game (`genex link`)
|
|
154
154
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
downloads the game too, binary assets and all:
|
|
155
|
+
Each game's connection to its live page is folder-local (`.genex/project.json`).
|
|
156
|
+
If that folder is gone — deleted, or the game
|
|
157
|
+
was built on another machine — **don't run `init` to "recover" it**: that
|
|
158
|
+
creates a brand-new game at a new URL. Instead, clone the game's source repo
|
|
159
|
+
and re-link the clone to the same live game:
|
|
161
160
|
|
|
162
161
|
```bash
|
|
163
|
-
|
|
164
|
-
npx @genex-ai/cli-demo@dev link <slug> # slug = the name in the play URL
|
|
162
|
+
git clone <the game's repo url> my-game && cd my-game
|
|
165
163
|
npm install
|
|
164
|
+
npx @genex-ai/cli-demo@latest link <slug> # slug = the name in the play URL
|
|
166
165
|
```
|
|
167
166
|
|
|
168
167
|
Don't know the slug? **`npx genex list`** prints every game on your account —
|
|
169
|
-
slug, status, and page link for each
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
**Catching a folder up** — `npx genex pull` replaces this folder with the
|
|
173
|
-
game's current draft. It refuses if the folder holds changes you never deployed,
|
|
174
|
-
because there is nothing to merge them against.
|
|
175
|
-
|
|
176
|
-
**If two machines have both moved on**, `preview` refuses rather than
|
|
177
|
-
overwriting the other one, and tells you both ways forward:
|
|
178
|
-
|
|
179
|
-
```
|
|
180
|
-
✗ my-game was updated from another device — nothing was deployed.
|
|
181
|
-
npx genex pull — take the other device's work (refuses if you have unshipped changes)
|
|
182
|
-
npx genex preview --force — keep yours and replace theirs
|
|
183
|
-
```
|
|
184
|
-
|
|
185
|
-
**Prefer taking their work and redoing yours on top.** You know what you just
|
|
186
|
-
changed, so you can re-apply it — which no merge tool could do for you, and which
|
|
187
|
-
loses nothing:
|
|
188
|
-
|
|
189
|
-
```bash
|
|
190
|
-
npx genex pull --force # takes their version; keeps what was here under .genex/replaced-*
|
|
191
|
-
# redo your change on the new base, then:
|
|
192
|
-
npx genex preview
|
|
193
|
-
```
|
|
194
|
-
|
|
195
|
-
`pull --force` never deletes: what was in the folder is copied to
|
|
196
|
-
`.genex/replaced-<timestamp>/` first, so you can diff against it while re-applying.
|
|
197
|
-
|
|
198
|
-
Use `preview --force` instead only when the user says their side should win.
|
|
199
|
-
Either way **say which version you are keeping before you run it** — both commands
|
|
200
|
-
are irreversible for one side, and the user may know something you don't about what
|
|
201
|
-
is on the other machine.
|
|
168
|
+
slug, status, and page link for each — signing you in first if needed. Find the
|
|
169
|
+
game there, then `link` its slug.
|
|
202
170
|
|
|
203
171
|
`link` never creates a project: it signs in if needed (the browser opens once)
|
|
204
172
|
and rewrites the local link (source pushes authorize over HTTPS, so there's no
|
|
@@ -220,7 +188,7 @@ Safe to run any time — genex-owned skills are refreshed to the latest version,
|
|
|
220
188
|
and your own files are never touched:
|
|
221
189
|
|
|
222
190
|
```bash
|
|
223
|
-
npx @genex-ai/cli-demo@
|
|
191
|
+
npx @genex-ai/cli-demo@latest init
|
|
224
192
|
```
|
|
225
193
|
|
|
226
194
|
Use `--force` only if you intentionally want your own existing files overwritten
|
|
@@ -36,15 +36,18 @@ calling multiplayer done, run the mandatory
|
|
|
36
36
|
## Install
|
|
37
37
|
|
|
38
38
|
```bash
|
|
39
|
-
npm i @genex-ai/multiplayer@^0.
|
|
39
|
+
npm i @genex-ai/multiplayer@^0.16.0
|
|
40
40
|
```
|
|
41
41
|
|
|
42
|
-
> Pin `@^0.
|
|
43
|
-
>
|
|
44
|
-
>
|
|
45
|
-
>
|
|
46
|
-
>
|
|
47
|
-
>
|
|
42
|
+
> Pin `@^0.16.0` (not a bare `npm i`, and not an older range): automatic TURN — the thing that
|
|
43
|
+
> lets players behind symmetric NAT or carrier CGNAT be heard at all — landed in 0.16, mesh voice
|
|
44
|
+
> in 0.15, room text chat in 0.14, host-only `setRoomOpen()` in 0.13; cross-region invites can
|
|
45
|
+
> select the inviter's exact relay with the optional `url` override since 0.11; unowned object
|
|
46
|
+
> writes warn instead of failing silently, live connected-player presence, supplier-form
|
|
47
|
+
> `connect()` auth and regional relay selection (`getColyseusUrls()` + `urls`) landed in 0.10;
|
|
48
|
+
> confirmed object controls, snaps, host-tick teardown, and reconnect rebasing in 0.9. An older
|
|
49
|
+
> resolve does not have those — and because this package is still `0.x`, a caret range is
|
|
50
|
+
> minor-locked, so `^0.12.0` would resolve to 0.12.x and give you no voice at all.
|
|
48
51
|
|
|
49
52
|
This skill targets `@genex-ai/multiplayer` **≥ 0.16.0** (`objects`/`host` since 0.4; verified per-player `avatarUrl` since 0.12;
|
|
50
53
|
room text chat via `room.chat` since 0.14; mesh voice via `room.voice` since 0.15;
|
|
@@ -1021,7 +1024,7 @@ host-driven saving works as long as ANY account is in the room.
|
|
|
1021
1024
|
|
|
1022
1025
|
## Checklist
|
|
1023
1026
|
|
|
1024
|
-
- [ ] `npm i @genex-ai/multiplayer@^0.
|
|
1027
|
+
- [ ] `npm i @genex-ai/multiplayer@^0.16.0` (automatic TURN, mesh voice, room text chat, connected presence, supplier auth, confirmed controls, snap epochs, reconnect-safe host ticks, unowned-write warnings, cross-region exact-relay overrides); config wired into the build.
|
|
1025
1028
|
- [ ] The plan names one net model, the player-experience reason, start/quorum, late-join/backfill,
|
|
1026
1029
|
and below-quorum/end behavior. The agent inferred it unless the experience was genuinely ambiguous.
|
|
1027
1030
|
- [ ] `reconnecting`/`reconnected`/`disconnect` render an overlay (don't tear the scene down).
|
|
@@ -134,13 +134,9 @@ everything twice.
|
|
|
134
134
|
visible; the lock may only ever engage from the Play/Resume click or a
|
|
135
135
|
gameplay canvas click (the phase binding `setPaused(phase !== "playing")` is
|
|
136
136
|
what guarantees this — check it rides `setPhase`, not the render loop).
|
|
137
|
-
Headless caveat
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
both-axes look check: synthesised mouse movement cancels to a net zero
|
|
141
|
-
delta, so turning right then left proves nothing about direction. Assert the
|
|
142
|
-
wiring and the cue in a screenshot, and say plainly that confirming which way
|
|
143
|
-
the view turns needs one manual pass with a real mouse.
|
|
137
|
+
Headless caveat: `requestPointerLock` throws in headless Chromium —
|
|
138
|
+
assert the wiring and the unlocked cue in a screenshot, and say plainly that
|
|
139
|
+
the lock itself needs one manual click (do the both-axes look check there).
|
|
144
140
|
7. **Ask the scene the three things the screenshot cannot answer** (below). Run it
|
|
145
141
|
once, in the same browser you already have open.
|
|
146
142
|
|
|
@@ -38,7 +38,7 @@ update, so update immediately.)
|
|
|
38
38
|
Run exactly the command the nudge printed, from the game project root:
|
|
39
39
|
|
|
40
40
|
```bash
|
|
41
|
-
npm i -D @genex-ai/cli-demo@
|
|
41
|
+
npm i -D @genex-ai/cli-demo@latest # the genex CLI (a dev dependency)
|
|
42
42
|
npm i @genex-ai/embed-sdk@latest # identity/saves SDK (ships inside the game)
|
|
43
43
|
npm i @genex-ai/multiplayer@latest # multiplayer SDK (only if the game uses it)
|
|
44
44
|
```
|
|
@@ -1,292 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: genex-monetization
|
|
3
|
-
description: Build an in-game shop that sells for platform coin — item catalog, purchase flow, delivery, and the per-game soft-currency economy a purchase attaches to. 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.
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# Genex Monetization
|
|
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.
|
|
12
|
-
|
|
13
|
-
Load this when the game should sell something. Ask first if it should — a game
|
|
14
|
-
with no loop worth monetizing is better without a shop (see §1).
|
|
15
|
-
|
|
16
|
-
## The hard rules, and the test that generalizes them
|
|
17
|
-
|
|
18
|
-
Before you build ANY purchasable thing, run this test:
|
|
19
|
-
|
|
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.)
|
|
22
|
-
> **Is the outcome uncertain when they pay?**
|
|
23
|
-
> **Is there a prize** — an item, currency, or advantage they wanted?
|
|
24
|
-
>
|
|
25
|
-
> **All three yes = paid randomness. Build the deterministic version instead.**
|
|
26
|
-
|
|
27
|
-
That triad is the test used by every app store to identify gambling, and it
|
|
28
|
-
catches mechanics that do not exist yet — which a list of banned names cannot.
|
|
29
|
-
|
|
30
|
-
Four things are never built, whatever the request:
|
|
31
|
-
|
|
32
|
-
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".
|
|
34
|
-
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.
|
|
37
|
-
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.
|
|
39
|
-
4. **No pressure.** No countdown timers, "ends in", "limited time", "only N
|
|
40
|
-
left", or stock counters anywhere in the shop.
|
|
41
|
-
|
|
42
|
-
**Randomness the player EARNS by playing is gameplay, not commerce, and is
|
|
43
|
-
completely fine**: an enemy dropping a random item, a chest you found in the
|
|
44
|
-
level, a procedural layout, a critical-hit roll, a shuffled deck. The line is
|
|
45
|
-
what triggered the roll — play, or payment. Build those freely.
|
|
46
|
-
|
|
47
|
-
Genex refuses paid randomness outright rather than allowing it with disclosed
|
|
48
|
-
odds. That is stricter than any app store, and it is why no Genex game needs an
|
|
49
|
-
odds table, an age gate, or a per-country check.
|
|
50
|
-
|
|
51
|
-
### When a request crosses a line
|
|
52
|
-
|
|
53
|
-
Answer in exactly three parts, then build:
|
|
54
|
-
|
|
55
|
-
1. **Name it.** "A loot crate is paid randomness — the player pays before
|
|
56
|
-
knowing what they get."
|
|
57
|
-
2. **Why.** One sentence. "Genex doesn't sell chance; it's a purchase the player
|
|
58
|
-
can't price, and it's what regulators fine studios over."
|
|
59
|
-
3. **Offer the alternative,** concretely enough to start on, and build that.
|
|
60
|
-
|
|
61
|
-
Never build the banned version "as an option", never build a partial one, and
|
|
62
|
-
never ask the user to confirm they want it. If they insist, restate the rule
|
|
63
|
-
once and build the compliant version. There is no escalation path.
|
|
64
|
-
|
|
65
|
-
**What to build instead:**
|
|
66
|
-
|
|
67
|
-
| They asked for | Build |
|
|
68
|
-
| --- | --- |
|
|
69
|
-
| 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. |
|
|
71
|
-
| 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. |
|
|
74
|
-
| Betting on matches, PvP wagers | **Leaderboards with a fixed cosmetic reward for placement**, paid by the game. Nobody's balance goes down. |
|
|
75
|
-
| 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
|
-
| 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. |
|
|
77
|
-
| Limited-time offer, flash sale | A **permanent tiered ladder** — the value comes from volume, not from a clock. |
|
|
78
|
-
| Pay-to-win stat boost in a competitive game | **Cosmetics**, or a boost that only applies in single-player content. |
|
|
79
|
-
|
|
80
|
-
## Designing a shop worth buying from
|
|
81
|
-
|
|
82
|
-
Nine checks. Each one is answerable about your actual design.
|
|
83
|
-
|
|
84
|
-
1. **The shop attaches to a progression that already exists.** Name the screen
|
|
85
|
-
it opens from and the meter a purchase moves. Build the loop first; a shop in
|
|
86
|
-
a game with nothing to want is furniture.
|
|
87
|
-
2. **A boost shortens a grind the player has already felt.** State it in one
|
|
88
|
-
sentence: "this skips the ore-gathering they've done four times." If you
|
|
89
|
-
can't, it isn't a boost, it's a number.
|
|
90
|
-
3. **Nothing sold invalidates the core loop.** If a paying and a non-paying
|
|
91
|
-
player both reach the end, the payer must not have skipped the part that IS
|
|
92
|
-
the game.
|
|
93
|
-
4. **No manufactured friction.** If the annoyance wouldn't exist without the
|
|
94
|
-
shop, remove the annoyance instead of selling the cure.
|
|
95
|
-
5. **Everything sold is reachable free.** Spending is a shortcut or a
|
|
96
|
-
decoration, never the only path.
|
|
97
|
-
6. **Prices land on the grid.** Item prices use 50 / 100 / 200 / 500 / 1000 coin
|
|
98
|
-
so every coin pack divides evenly into them and nobody is left holding change
|
|
99
|
-
they cannot spend.
|
|
100
|
-
7. **Every price shows real money next to it.** The server sends
|
|
101
|
-
`priceDisplayUsdCents` with every item — render it. `250 coins ($2.49)`.
|
|
102
|
-
8. **One currency layer between money and goods.** Coin buys items. A per-game
|
|
103
|
-
earned currency buys per-game upgrades. They never convert into each other.
|
|
104
|
-
9. **Purchases never expire and survive a reinstall.** Entitlements live on the
|
|
105
|
-
server; the game re-reads them on every boot.
|
|
106
|
-
|
|
107
|
-
For a per-game earned currency, the load-bearing number is **minutes of play per
|
|
108
|
-
unit earned**. Set it, then price the cheapest meaningful item at one to three
|
|
109
|
-
sessions of earning. Everything else follows. Spend sinks come in three kinds —
|
|
110
|
-
permanent upgrades, refills, cosmetics — and cosmetics are what absorbs late-game
|
|
111
|
-
currency without touching balance.
|
|
112
|
-
|
|
113
|
-
## Stocking the shop
|
|
114
|
-
|
|
115
|
-
Items live on the platform, not in the game's code. You create them with the CLI,
|
|
116
|
-
and the game names them by id — which is what stops a game inventing its own
|
|
117
|
-
items or repricing them.
|
|
118
|
-
|
|
119
|
-
```bash
|
|
120
|
-
npx genex shop add "Iron Key" --price 100 --type durable
|
|
121
|
-
# → id: sku_a1b2c3 ← what the game passes to buy()
|
|
122
|
-
|
|
123
|
-
npx genex shop list # what this game sells, and the valid prices
|
|
124
|
-
npx genex shop set sku_a1b2c3 --price 200
|
|
125
|
-
npx genex shop remove sku_a1b2c3 # retires it; players who bought it keep it
|
|
126
|
-
```
|
|
127
|
-
|
|
128
|
-
`--type consumable` (default) is spent on use; `durable` is owned permanently.
|
|
129
|
-
|
|
130
|
-
**Prices come off a fixed grid** — `genex shop list` prints it, and an off-grid
|
|
131
|
-
price is refused. The grid exists so every coin pack divides evenly by the
|
|
132
|
-
cheapest item, which is what stops a player being left holding change too small
|
|
133
|
-
to spend. Pick the nearest grid price rather than working around it.
|
|
134
|
-
|
|
135
|
-
Record the ids in `DESIGN.md` next to what each item does. They are the one
|
|
136
|
-
thing the game's code cannot regenerate for itself.
|
|
137
|
-
|
|
138
|
-
## The API
|
|
139
|
-
|
|
140
|
-
From `@genex-ai/embed-sdk`, already installed. `initEmbed()` must have run.
|
|
141
|
-
|
|
142
|
-
```ts
|
|
143
|
-
import { getShop, buy, getEntitlements, consumeEntitlement } from '@genex-ai/embed-sdk';
|
|
144
|
-
|
|
145
|
-
const items = await getShop();
|
|
146
|
-
// [{ id, type, name, iconUrl, priceCoins, priceDisplayUsdCents }]
|
|
147
|
-
```
|
|
148
|
-
|
|
149
|
-
Render `name`, `iconUrl`, `priceCoins` **and** `priceDisplayUsdCents`. Never
|
|
150
|
-
hardcode a price: the server charges what its own catalog says, so a hardcoded
|
|
151
|
-
number can silently disagree with what the player is charged.
|
|
152
|
-
|
|
153
|
-
`getShop()` works for a **guest** and inside a **preview** build, so the shop
|
|
154
|
-
window renders for everyone — that is the point of showing it to a signed-out
|
|
155
|
-
player at all. Buying is what needs an account.
|
|
156
|
-
|
|
157
|
-
**Your game cannot read the player's coin balance, and no HUD should show one.**
|
|
158
|
-
The wallet spans every game on the platform, so an untrusted game is not told how
|
|
159
|
-
much a player can spend. Show what you *can* know — what they own, from
|
|
160
|
-
`getEntitlements()` — and let `buy()` report `insufficient_balance` if it comes
|
|
161
|
-
to that.
|
|
162
|
-
|
|
163
|
-
### Buying
|
|
164
|
-
|
|
165
|
-
```ts
|
|
166
|
-
buyButton.addEventListener('click', async () => { // must be a real click
|
|
167
|
-
const result = await buy({ skuId: item.id });
|
|
168
|
-
if (result.status === 'canceled') return; // normal — say nothing
|
|
169
|
-
if (result.status !== 'succeeded') {
|
|
170
|
-
showMessage(result.message ?? 'That did not go through.');
|
|
171
|
-
return;
|
|
172
|
-
}
|
|
173
|
-
await deliverPending();
|
|
174
|
-
});
|
|
175
|
-
```
|
|
176
|
-
|
|
177
|
-
**Call `buy()` synchronously from the click handler.** On the game's own origin
|
|
178
|
-
the confirmation is a popup, and browsers only allow one while a user gesture is
|
|
179
|
-
live — an `await` before it loses the gesture and nothing opens.
|
|
180
|
-
|
|
181
|
-
`buy()` resolves when the SERVER says what happened, not when a window closes.
|
|
182
|
-
Statuses: `succeeded`, `canceled`, `expired`, `insufficient_balance`, `failed`.
|
|
183
|
-
|
|
184
|
-
The player confirms on a Genex-drawn surface — your game does not render the
|
|
185
|
-
price sheet, cannot skin it, and cannot complete a purchase itself. That is
|
|
186
|
-
deliberate: it is what lets a player trust a purchase in a game they have never
|
|
187
|
-
played before.
|
|
188
|
-
|
|
189
|
-
### Delivering
|
|
190
|
-
|
|
191
|
-
```ts
|
|
192
|
-
async function deliverPending() {
|
|
193
|
-
for (const e of await getEntitlements({ excludeConsumed: true })) {
|
|
194
|
-
// Branch on skuType (consumable | durable), NEVER on type — `type` is how
|
|
195
|
-
// it was acquired (purchase/gift/test/free) and can't answer this.
|
|
196
|
-
if (e.skuType === 'durable') {
|
|
197
|
-
wear(e.name); // ownership, re-applied on every boot. Do not consume.
|
|
198
|
-
continue;
|
|
199
|
-
}
|
|
200
|
-
const { alreadyConsumed } = await consumeEntitlement(e.id);
|
|
201
|
-
if (alreadyConsumed) continue; // someone got there first
|
|
202
|
-
applyItem(e.skuId); // AFTER the consume
|
|
203
|
-
await savePlayerState(currentSave());
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
```
|
|
207
|
-
|
|
208
|
-
**Consume first, apply second, and run `deliverPending()` on every boot.**
|
|
209
|
-
|
|
210
|
-
That order is not stylistic. If the game dies between consuming and applying,
|
|
211
|
-
the player loses one item — a support ticket. If you apply first and die before
|
|
212
|
-
consuming, every boot re-delivers it forever — an exploit. Re-listing on boot is
|
|
213
|
-
what makes a purchase survive a crash, a refresh, or a closed tab.
|
|
214
|
-
|
|
215
|
-
**`e.skuType` is the field that decides this, not `e.type`.** `type` says how the
|
|
216
|
-
player got it (`purchase`/`gift`/`test`/`free`); `skuType` says what it is
|
|
217
|
-
(`consumable`/`durable`).
|
|
218
|
-
|
|
219
|
-
A **consumable** is spent: consume it, then apply the effect once.
|
|
220
|
-
|
|
221
|
-
A **durable** is owned forever, and you have two honest ways to handle it. If the
|
|
222
|
-
effect is ownership — a cosmetic, a skin, an unlock — do **not** consume it: the
|
|
223
|
-
row's presence in the list IS the ownership, it survives reinstalls, and there is
|
|
224
|
-
no local save to drift out of sync. Only consume a durable when it grants
|
|
225
|
-
something once and non-idempotently (a permanent +100 gold), and then record it
|
|
226
|
-
in the player's save, because re-applying that on every boot would be a bug.
|
|
227
|
-
|
|
228
|
-
### Testing the shop you just built
|
|
229
|
-
|
|
230
|
-
```bash
|
|
231
|
-
npx genex shop test <sku-id> # a free copy, for the owner only
|
|
232
|
-
```
|
|
233
|
-
|
|
234
|
-
That grants the entitlement without a sale — no coin moves — so the delivery
|
|
235
|
-
path, the inventory and the effect all run exactly as they would after a real
|
|
236
|
-
purchase. Reload the game and it should be there.
|
|
237
|
-
|
|
238
|
-
You *can* also just buy your own item, but it costs you the full price and pays
|
|
239
|
-
you nothing: your share of a sale you funded goes to the platform, never into
|
|
240
|
-
your own earnings. Use the test grant instead.
|
|
241
|
-
|
|
242
|
-
## Checklist
|
|
243
|
-
|
|
244
|
-
- [ ] Items exist (`npx genex shop list`) before the shop UI is written
|
|
245
|
-
- [ ] Delivery was verified with `npx genex shop test`, not by trying to buy your own item
|
|
246
|
-
- [ ] The game has a loop and a progression before it has a shop
|
|
247
|
-
- [ ] Every item price is on the 50/100/200/500/1000 grid
|
|
248
|
-
- [ ] Every price renders `priceDisplayUsdCents` beside the coin figure
|
|
249
|
-
- [ ] `buy()` is called synchronously inside a click/tap handler
|
|
250
|
-
- [ ] `canceled` is silent; only real failures show a message
|
|
251
|
-
- [ ] `deliverPending()` runs on every boot, before the player can act
|
|
252
|
-
- [ ] `consumeEntitlement()` is awaited BEFORE the effect is applied
|
|
253
|
-
- [ ] `alreadyConsumed: true` skips the effect
|
|
254
|
-
- [ ] Durable purchases are written to the player's save
|
|
255
|
-
- [ ] No timer, "limited", "ends in", or stock counter anywhere
|
|
256
|
-
- [ ] Nothing sold is unreachable without paying
|
|
257
|
-
- [ ] No paid randomness, no coin wagering, no donation prompt
|
|
258
|
-
|
|
259
|
-
## Troubleshooting
|
|
260
|
-
|
|
261
|
-
**`buy()` returns `failed` with "the confirmation window was blocked"** — `buy()`
|
|
262
|
-
was not called inside a user gesture, or an `await` ran before it. Move it to the
|
|
263
|
-
first line of the click handler.
|
|
264
|
-
|
|
265
|
-
**The purchase succeeded but the player got nothing** — the game applied the
|
|
266
|
-
effect without consuming, or never ran `deliverPending()` on boot. The
|
|
267
|
-
entitlement is still there; re-list it.
|
|
268
|
-
|
|
269
|
-
**The player got the item twice** — the effect was applied before consuming, or
|
|
270
|
-
`alreadyConsumed` was ignored. Both are the same bug.
|
|
271
|
-
|
|
272
|
-
**`unauthorized` from `getShop()`** — no player identity yet. `initEmbed()` must
|
|
273
|
-
have run and `waitForPlayer()` resolved. See `$genex-threejs-embed-auth`.
|
|
274
|
-
|
|
275
|
-
**`guest_no_wallet`** — guests play but hold no wallet. Show the shop as
|
|
276
|
-
sign-in-to-buy rather than hiding it.
|
|
277
|
-
|
|
278
|
-
**`staging_no_purchase`** — a `genex preview` build cannot spend real coin. Test
|
|
279
|
-
the shop's layout on staging; test a purchase after `genex promote`.
|
|
280
|
-
|
|
281
|
-
**`getShop()` returns nothing** — the game has no items yet. `npx genex shop add
|
|
282
|
-
"<name>" --price <coin>` and use the id it prints.
|
|
283
|
-
|
|
284
|
-
**`price_off_grid`** — that price isn't on the platform's grid. `npx genex shop
|
|
285
|
-
list` prints the valid ones; pick the nearest.
|
|
286
|
-
|
|
287
|
-
**Everything coin-related 404s** — in-game purchases aren't enabled on this
|
|
288
|
-
environment. Nothing to fix in the game; say so and build the rest.
|
|
289
|
-
|
|
290
|
-
**Purchases do nothing in local testing** — local test mode has no wallet and no
|
|
291
|
-
server. `buy()` returns `failed` immediately by design. Test purchases on a
|
|
292
|
-
preview or published build.
|