@xgamesfun/xcore 0.1.0 → 0.2.1
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/LICENSE +1 -1
- package/README.md +183 -84
- package/bin/xcore.mjs +13 -8
- package/commands/add.mjs +11 -8
- package/commands/adopt.mjs +9 -7
- package/commands/backend.mjs +2 -2
- package/commands/check.mjs +4 -4
- package/commands/db.mjs +218 -0
- package/commands/dev.mjs +8 -6
- package/commands/domain.mjs +8 -8
- package/commands/embed.mjs +3 -3
- package/commands/files.mjs +106 -0
- package/commands/login.mjs +9 -17
- package/commands/logout.mjs +15 -4
- package/commands/loot.mjs +2 -2
- package/commands/plan.mjs +22 -19
- package/commands/publish.mjs +54 -21
- package/commands/secret.mjs +6 -6
- package/docs/SERVER.md +218 -18
- package/lib/add.mjs +265 -40
- package/lib/browser.mjs +11 -2
- package/lib/check.mjs +10 -9
- package/lib/cli.mjs +24 -9
- package/lib/client/gamesfun.js +242 -122
- package/lib/client/godot/gamesfun.gd +17 -11
- package/lib/client/godot/gamesfun_peer.gd +2 -2
- package/lib/client/server/0001_init.sql +10 -0
- package/lib/client/server/database.js +271 -109
- package/lib/client/server/gf-db.js +55 -0
- package/lib/client/server/gf-docs.js +56 -0
- package/lib/client/server/rooms.js +16 -9
- package/lib/client/server/rpc.js +7 -7
- package/lib/client/server/stock.js +37 -0
- package/lib/client/server/vouchers.js +127 -46
- package/lib/client/unity/GamesFun.cs +15 -12
- package/lib/client/unity/GamesFun.jslib +1 -1
- package/lib/config-dir.mjs +1 -1
- package/lib/credentials.mjs +10 -5
- package/lib/db.mjs +355 -0
- package/lib/dev/d1.mjs +486 -0
- package/lib/dev/files.mjs +247 -0
- package/lib/dev/gf-local.js +69 -3
- package/lib/dev/index.mjs +68 -20
- package/lib/dev/limits.mjs +2 -2
- package/lib/dev/platform.mjs +111 -12
- package/lib/dev/server.mjs +54 -5
- package/lib/dev/tools.mjs +71 -18
- package/lib/files.mjs +208 -0
- package/lib/open.mjs +1 -1
- package/lib/play-policy.mjs +14 -5
- package/lib/publish/adopt.mjs +73 -33
- package/lib/publish/client.mjs +130 -20
- package/lib/publish/gate.mjs +4 -2
- package/lib/publish/hosting.mjs +5 -5
- package/lib/publish/index.mjs +162 -40
- package/lib/publish/oidc.mjs +26 -5
- package/lib/publish/page.mjs +1 -1
- package/lib/publish/plans.mjs +19 -12
- package/lib/publish/scan.mjs +78 -102
- package/lib/publish/secrets.mjs +4 -5
- package/lib/publish/site.mjs +9 -10
- package/lib/publish/tree.mjs +8 -8
- package/lib/secret-patterns.mjs +53 -16
- package/lib/secret-store.mjs +2 -2
- package/lib/server/bundle.mjs +113 -13
- package/lib/server/contract.mjs +440 -16
- package/lib/server/data-calls.mjs +136 -0
- package/lib/server/secrets.mjs +9 -7
- package/lib/solana-rpc.mjs +5 -5
- package/lib/state-folder.mjs +23 -0
- package/package.json +4 -4
- package/skills/bring-your-own-game/SKILL.md +10 -9
package/LICENSE
CHANGED
package/README.md
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
# xcore
|
|
2
2
|
|
|
3
|
-
The one place to start with
|
|
3
|
+
The one place to start with xgames.fun (`@xgamesfun/xcore` on npm, the `xcore` command). What you came for decides where
|
|
4
4
|
it takes you:
|
|
5
5
|
|
|
6
|
-
| You have | xcore
|
|
7
|
-
| ------------------------------------------- |
|
|
8
|
-
| **A web game you already made** | brings it to
|
|
9
|
-
| **A game that should pay or sell on chain** | adds a Solana economy on loot: shops, loot boxes, raffles, prize pools, staking, rewards (§4)
|
|
10
|
-
| **An idea for a game, no game yet** | hands its commands to the kit
|
|
6
|
+
| You have | xcore | Then |
|
|
7
|
+
| ------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------- |
|
|
8
|
+
| **A web game you already made** | brings it to xgames.fun: checks it as the host serves it, gives it a backend, publishes it at a link anyone can play (this page) | |
|
|
9
|
+
| **A game that should pay or sell on chain** | adds a Solana economy on loot: shops, loot boxes, raffles, prize pools, staking, rewards (§4) | the economy tools are [`@xgamesfun/solana`](https://github.com/XGamesFun/solana-kit) (`xsolana`), which `xcore loot` runs |
|
|
10
|
+
| **An idea for a game, no game yet** | hands its commands to the kit | the game-making kit, `@xgamesfun/kit` (coming later) |
|
|
11
11
|
|
|
12
12
|
Nothing about a game you bring changes: it keeps its code, its framework and its build, as long as it fits the hosting
|
|
13
13
|
(below): static files a browser runs on its own, plus, if it needs one, a backend that is public on the internet or a
|
|
14
|
-
server of its own that
|
|
14
|
+
server of its own that xgames.fun runs.
|
|
15
15
|
|
|
16
16
|
```bash
|
|
17
17
|
npx -y @xgamesfun/xcore adopt ./my-game # its record, and the list of what to fix
|
|
@@ -22,9 +22,11 @@ Or install it once, `npm i -g @xgamesfun/xcore`, and then:
|
|
|
22
22
|
```bash
|
|
23
23
|
xcore adopt ./my-game # its record, and the list of what to fix
|
|
24
24
|
xcore check ./my-game # the page in Chrome, as the host serves it
|
|
25
|
-
xcore dev ./my-game # play it here as
|
|
25
|
+
xcore dev ./my-game # play it here as xgames.fun hosts it: its server, sign-in, rooms, economy
|
|
26
26
|
xcore add signin ./my-game # players sign in (X or a wallet); saves and leaderboards
|
|
27
|
-
xcore add database ./my-game # a database of its own, put up with the game
|
|
27
|
+
xcore add database ./my-game # a database of its own (D1), put up with the game
|
|
28
|
+
xcore db query "SELECT * FROM players" # read it here, or --live on xgames.fun
|
|
29
|
+
xcore files put ./level.json levels/1.json --public --live # a file its server and page read
|
|
28
30
|
xcore add multiplayer ./my-game # rooms players share (Godot's own multiplayer runs over them)
|
|
29
31
|
xcore backend use custom ./my-game --url https://api.my-game.example # only if it calls a backend you run
|
|
30
32
|
xcore loot add ./my-game # only if it gets an economy (a Solana economy on loot)
|
|
@@ -51,15 +53,15 @@ brought game, with the studio around it; its MCP tools for a brought game are `g
|
|
|
51
53
|
| | |
|
|
52
54
|
| -------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
53
55
|
| **The page** | Static files, served as they are (GET and HEAD). `index.html` at the root of what is published is the game's entry. A folder URL ending in `/` serves that folder's `index.html`; any other missing path is a "not found" page, with no fallback to `index.html` (a client-side router works from the entry page; a deep link to `/level/3` reloads to "not found"). |
|
|
54
|
-
| **Where it is served** | At its own origin's root once published (the link `xcore publish` prints). Under a folder in
|
|
56
|
+
| **Where it is served** | At its own origin's root once published (the link `xcore publish` prints). Under a folder in xgames.fun Studio's preview, in `xcore dev` and in the kit's dev server (`/game/`). A game uses relative URLs (`./assets/x.js`, not `/assets/x.js`), so it works in all of them. |
|
|
55
57
|
| **Its server, if any** | `server/` and `gamesfun.json` → `server`: a Cloudflare Worker that answers the game's `/api/*`, with up to 8 Durable Objects and the platform binding `env.GF` (sign-in, saves, scores, loot reports). [docs/SERVER.md](docs/SERVER.md). Bundled into one file on publish; never a public file. |
|
|
56
58
|
| **A backend of its own** | Any public `https://` or `wss://` origin, listed in `backend.json` → `origins` (at most 8, public names only, no IP address, no localhost). The play host lets the page reach exactly those. |
|
|
57
|
-
| **Solana** | Through the game's own devnet RPC (`xcore add rpc`: its server's `api/rpc`), else the play host's `/loot/v1/rpc` (
|
|
59
|
+
| **Solana** | Through the game's own devnet RPC (`xcore add rpc`: its server's `api/rpc`), else the play host's `/loot/v1/rpc` (xgames.fun's shared devnet relay, counted toward the game's plan), with the page client in the game (`gf/loot/`, from `@xgamesfun/solana`). |
|
|
58
60
|
| **What the page may load** | Scripts, style sheets, fonts and frames from its own files only (inline scripts, `eval`, WebAssembly and Web Workers from the game's own files are allowed; a page is not cross-origin isolated, so `SharedArrayBuffer` is not available: export engines single-threaded). Connections to its own origin and to `backend.json`'s origins; pictures and sounds from its own files and from those `https://` origins. No `<base>`, no form posts, no `<object>`. Camera, microphone, location, payments and USB are off; fullscreen, pointer lock, gamepads, audio and WebGL are not limited by the page's headers. |
|
|
59
61
|
| **Paths the host owns** | `/api/*` (the game's server, when it has one), `/platform/v1/*`, the multiplayer signaling (`/signal/v1`), the live watch (`/watch/v1`), world saves (`/world/v1/`), player keys (`/player/v1/`) and `/loot/v1/*`. A file of the game under one of them is never served. |
|
|
60
62
|
| **Files** | Everything in the published folder goes up except hidden files and folders, and, at its top level, `evidence/`, `node_modules/`, `server/`, `supabase/`, `gamesfun.json` and the makers' notes (`README.md`, `NOTES.md`, `LAUNCH.md`, `FRICTION.md`, `CLAUDE.md`, `AGENTS.md`, `TODO.md`) (`.gitignore` is not read): move development material out of it, or give it a name that starts with a dot. Hidden files and folders never go up. A file whose extension the host does not know is served as `application/octet-stream` with `nosniff` (fine for data read with `fetch()`; a browser will not run it as a script or a style). |
|
|
61
|
-
| **Limits** | One game: 3,000 files, 100 MiB (104,857,600 bytes; one file may be all of it). One account: 30 publishes and 1 GB of new files a day, 100 games. Publish reads the account's own limits from
|
|
62
|
-
| **Refused, always** | `.env` files, symbolic links, key and certificate files, wallet keypairs, and anything in a text file that looks like a secret (
|
|
63
|
+
| **Limits** | One game: 3,000 files, 100 MiB (104,857,600 bytes; one file may be all of it). One account: 30 publishes and 1 GB of new files a day, 100 games. Publish reads the account's own limits from xgames.fun. The server bundle: 5 MB. What the game's server, saves and scores may use is its plan (§5, `xcore plan`). |
|
|
64
|
+
| **Refused, always** | `.env` files, symbolic links, key and certificate files, wallet keypairs, and anything in a text file that looks like a secret (xgames.fun runs the same scan again). A browser game carries only keys made to be public: a Supabase anon or publishable key, a Firebase web config's `apiKey`. |
|
|
63
65
|
|
|
64
66
|
## 1. Adopt
|
|
65
67
|
|
|
@@ -87,7 +89,7 @@ It writes the kit's record into the game's `gamesfun.json` (never published):
|
|
|
87
89
|
| `build` | the command that makes the published files, run in the game's folder; none for plain files |
|
|
88
90
|
| `out` | the folder that is published: `"."` for plain files, else the build's output |
|
|
89
91
|
| `static` | the folder the build copies as it is (Vite, Next.js, Astro, CRA: `public`), where xcore puts the files it adds (`backend.json`, `xsolana.json`, `gf/loot/`), so every build carries them; for plain files, the folder itself |
|
|
90
|
-
| `title` | optional: the game's name on
|
|
92
|
+
| `title` | optional: the game's name on xgames.fun (else `index.html`'s `<title>`, else `package.json`'s name) |
|
|
91
93
|
|
|
92
94
|
`--static <dir>` names that folder when the kit cannot tell; `--no-build` reads what is built already. A `server`
|
|
93
95
|
part of `gamesfun.json` (the game's own server) stays as it is.
|
|
@@ -110,7 +112,7 @@ path from the root built in a script, a request to a site `backend.json` does no
|
|
|
110
112
|
**A game of several pages** (a Next.js export with routes, Astro pages): such builds link their pages from the root
|
|
111
113
|
(`/level/2/`), and Next.js has no relative mode for them. Every page works at the published link, which is the game's
|
|
112
114
|
own origin, but not under a folder, so `xcore check` (which serves the game under one, as Studio's preview and the
|
|
113
|
-
kit's dev server do) fails them and publish saves a private draft. Until
|
|
115
|
+
kit's dev server do) fails them and publish saves a private draft. Until xgames.fun previews games at an origin of their
|
|
114
116
|
own, make the game one page that changes screens itself (or uses a hash router), or keep it a single-page export.
|
|
115
117
|
|
|
116
118
|
**A Nuxt game**: `nuxt generate` with its defaults links every file from the root and routes by path. Make it one
|
|
@@ -136,10 +138,10 @@ command, `check` and `publish` take the export as it is on disk (`check` says wh
|
|
|
136
138
|
`index.wasm` is about 36 MB, so the game's own `index.pck` has the rest (compressed textures and Ogg audio keep it small).
|
|
137
139
|
|
|
138
140
|
- **Godot 4.3 or later**: export for the Web with **Thread Support off**, and `xcore add signin` for sign-in, saves
|
|
139
|
-
and leaderboards from GDScript (§3). A
|
|
141
|
+
and leaderboards from GDScript (§3). A xgames.fun page is not cross-origin
|
|
140
142
|
isolated, so a threaded export stops at "missing features" (Godot 4.0 to 4.2 always use threads: upgrade). Godot's
|
|
141
143
|
ENet does not run in a browser: `xcore add multiplayer` and `GamesFun.join_room` put the game's high-level multiplayer
|
|
142
|
-
on
|
|
144
|
+
on xgames.fun's rooms unchanged (§3), or a `WebSocketMultiplayerPeer` reaches a server of your own (`backend use custom`).
|
|
143
145
|
- **Unity** (`xcore add signin` for sign-in, saves and leaderboards from C#, §3): Player Settings → Publishing
|
|
144
146
|
Settings, **Compression Format Disabled** (or keep it and turn on
|
|
145
147
|
**Decompression Fallback**): the play host sends files as they are, with no `Content-Encoding`, so a Brotli or Gzip
|
|
@@ -148,7 +150,7 @@ command, `check` and `publish` take the export as it is on disk (`check` says wh
|
|
|
148
150
|
`backend.json` (at most 8, no wildcards).
|
|
149
151
|
- **Unreal Engine**: Unreal 5 has no web export (Unreal 4.24 moved HTML5 out of the engine, to a community project
|
|
150
152
|
that is no longer kept up), so there are no files to publish. Pixel Streaming runs the game on a GPU server of your
|
|
151
|
-
own and streams it as video;
|
|
153
|
+
own and streams it as video; xgames.fun does not host that server, and a page that shows such a stream is untested.
|
|
152
154
|
|
|
153
155
|
**A game with its own Node server** (Express, Socket.IO, deployed on Render, Fly or similar), whose page is a folder of
|
|
154
156
|
static files (`public/`): adopt publishes that folder, and the server stays where it runs. Declare it with
|
|
@@ -157,8 +159,10 @@ client file the server hands out (`/socket.io/socket.io.js`) into the game. The
|
|
|
157
159
|
`https://<slug>-play.xgames.fun`.
|
|
158
160
|
|
|
159
161
|
**What xcore writes in the game's folder**: `gamesfun.json` (adopt's record; commit it), `evidence/` (each check's
|
|
160
|
-
screenshots and record, `evidence/verify-N/`) and `.gamesfun/` (publish's last passing check,
|
|
161
|
-
published
|
|
162
|
+
screenshots and record, `evidence/verify-N/`) and `.gamesfun/` (publish's last passing check, the link once
|
|
163
|
+
published, the local database and files, and `xcore db export`'s dumps of your players' data). None of them is
|
|
164
|
+
published. `.gamesfun/` keeps itself out of git: xcore writes `.gamesfun/.gitignore` (`*`) when it first writes
|
|
165
|
+
there; add `evidence/` to the game's `.gitignore`.
|
|
162
166
|
|
|
163
167
|
## 2. Check
|
|
164
168
|
|
|
@@ -179,15 +183,16 @@ whose check fails goes up as a private draft only.
|
|
|
179
183
|
|
|
180
184
|
### Play it here: `xcore dev`
|
|
181
185
|
|
|
182
|
-
`xcore dev <folder>` serves the game on this machine the way
|
|
186
|
+
`xcore dev <folder>` serves the game on this machine the way xgames.fun will host it, with everything a published game
|
|
183
187
|
has behind it, so the whole game runs before it is published:
|
|
184
188
|
|
|
185
|
-
| On this machine |
|
|
186
|
-
| ----------------------------- |
|
|
187
|
-
| `http://127.0.0.1:8787/game/` | the published files (it runs `page.build` first, unless `--no-build`), with the play host's headers
|
|
188
|
-
| `/game/api/*` | the game's `server/` under workerd, the runtime
|
|
189
|
-
| `/platform/v1/*` | sign-in, saves and leaderboards: `gamesfun.js` signs in a local player (each tab its own, so two tabs play a room together), and `env.GF.verifyPlayer` accepts its token. Saves and scores are kept in `.gamesfun/dev-platform.json`, never published
|
|
190
|
-
|
|
|
189
|
+
| On this machine | |
|
|
190
|
+
| ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
191
|
+
| `http://127.0.0.1:8787/game/` | the published files (it runs `page.build` first, unless `--no-build`), with the play host's headers |
|
|
192
|
+
| `/game/api/*` | the game's `server/` under workerd, the runtime xgames.fun runs it on: Durable Objects with their storage kept, WebSockets, `env.GF`, the game's database (a local D1 in `.gamesfun/dev/d1/`, its migrations applied), and its secrets from `.dev.vars`. A change under `server/` or to `.dev.vars` reloads it. Miniflare (with workerd, about 140 MB) is installed once, the first time a game with a server runs |
|
|
193
|
+
| `/platform/v1/*` | sign-in, saves and leaderboards: `gamesfun.js` signs in a local player (each tab its own, so two tabs play a room together), and `env.GF.verifyPlayer` accepts its token. Saves and scores are kept in `.gamesfun/dev-platform.json`, never published |
|
|
194
|
+
| `http://127.0.0.1:8788/` | the game's public project files (`env.GF.files`, `xcore files`), kept in `.gamesfun/dev/files/`: another origin, as `<slug>.xgamesusercontent.com` is to the published game |
|
|
195
|
+
| `/loot/v1/rpc` | the economy's Solana devnet RPC: xgames.fun's shared relay (what a published page talks to unless the game brings its own: `xcore add rpc`), or your own with `XSOLANA_RPC_URL` in `.env` (its URL is never shown; a call the shared relay would refuse is said in the terminal). The economy's calls answer once it is live on devnet (`xcore loot approve`) |
|
|
191
196
|
|
|
192
197
|
Games made with any engine get all of it through `gamesfun.js` (`xcore add signin`): on a plain local server it stands
|
|
193
198
|
in with this browser's storage; under `xcore dev` it uses the real thing. What is not the same as hosted: no CPU or
|
|
@@ -197,7 +202,7 @@ request limits, and the server's outbound `fetch` is not limited to `backend.jso
|
|
|
197
202
|
|
|
198
203
|
### Sign-in, saves and leaderboards: `xcore add signin`
|
|
199
204
|
|
|
200
|
-
|
|
205
|
+
xgames.fun signs players in (with X first, or a Solana wallet) for every game, and keeps each player's saves and the
|
|
201
206
|
game's leaderboards. `xcore add signin <folder>` gives the game the page client that reaches them, in the shape its
|
|
202
207
|
engine takes:
|
|
203
208
|
|
|
@@ -209,7 +214,7 @@ engine takes:
|
|
|
209
214
|
|
|
210
215
|
The same calls everywhere: sign in and out, the player (`id`, `name`, `level`, `wallet`, `x`), saves (any JSON, 64 KB
|
|
211
216
|
each, 16 a player), leaderboards (submit, top, the player's own), the game's database (below) and its own server
|
|
212
|
-
(`api`). Every call answers `{data, error, status}` and none throws, so a game plays on as a guest wherever
|
|
217
|
+
(`api`). Every call answers `{data, error, status}` and none throws, so a game plays on as a guest wherever xgames.fun is
|
|
213
218
|
not there.
|
|
214
219
|
|
|
215
220
|
**A leaderboard is the player's own word until the server takes it.** A signed-in player submits their own scores
|
|
@@ -221,15 +226,15 @@ to it are refused (their answer's `code` is `'server-only'`, HTTP 403) and the s
|
|
|
221
226
|
|
|
222
227
|
Where the sign-in comes from:
|
|
223
228
|
|
|
224
|
-
- **In
|
|
225
|
-
- **On the game's own link, and inside a post on X**, a small window on
|
|
229
|
+
- **In xgames.fun's play page**, the page signs the player in and hands the game a token.
|
|
230
|
+
- **On the game's own link, and inside a post on X**, a small window on xgames.fun signs in; when the browser keeps it
|
|
226
231
|
shut (a phone, X's app), the game is given a code to show ("go to xgames.fun/go and enter ABCD-EFGH") that the player
|
|
227
232
|
types on any device. The code comes with `window`: show it plainly only when `window` is false. When a window did
|
|
228
233
|
open, the player is signing in there (X's login takes a while, and its page hides from the game whether that window
|
|
229
234
|
is still open), so show the code at most as a quiet line for someone who lost the window, never over the game.
|
|
230
235
|
- **On this machine** (localhost, `xcore check`), a local player and the browser's own storage stand in, so the game
|
|
231
236
|
can be made and tested before it is published. Under `xcore dev`, a local sign-in (one player a tab) and the
|
|
232
|
-
platform answer as
|
|
237
|
+
platform answer as xgames.fun does, and the game's `server/` accepts its token.
|
|
233
238
|
|
|
234
239
|
The token lasts an hour and stays in the page's memory. The game's `server/` checks it with `env.GF.verifyPlayer`; a
|
|
235
240
|
backend of your own checks it with any JWT library against `https://<slug>-play.xgames.fun/player/v1/key` (issuer
|
|
@@ -243,28 +248,72 @@ it trusts who is asking. It answers `{data, error, status}` like every other cal
|
|
|
243
248
|
|
|
244
249
|
### A database of its own: `xcore add database`
|
|
245
250
|
|
|
246
|
-
`xcore add database <folder>` adds sign-in, and a database the game owns:
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
251
|
+
`xcore add database <folder>` adds sign-in, and a database the game owns: **one D1 database** (SQLite) a game, made on
|
|
252
|
+
xgames.fun by the first publish and kept between publishes ([docs/SERVER.md](docs/SERVER.md), "A database of its
|
|
253
|
+
own"). Nothing to sign up for and no keys. `gamesfun.json` → `database` declares it; its tables come from the
|
|
254
|
+
migrations in `server/migrations/` (`0001_init.sql`, then `0002_….sql`: append only, applied by publish before the new
|
|
255
|
+
version goes live).
|
|
256
|
+
|
|
257
|
+
- **A game with a server of its own** gets an example first migration and `server/gf-db.js`: prepared queries
|
|
258
|
+
(`db(env).first('SELECT gold FROM players WHERE id = ?', sub)`), and `withPlayer(request, env)`, the signed-in
|
|
259
|
+
player's id. A rule that must hold under a burst of requests is one statement
|
|
260
|
+
(`UPDATE players SET runs = runs - 1 WHERE id = ? AND runs > 0 RETURNING runs`).
|
|
261
|
+
- **A game with no server** gets xcore's stock server, which keeps the page's documents (any JSON up to 64 KB) in
|
|
262
|
+
collections, under the rules in `database.collections`. When it gets a server of its own (`xcore add
|
|
263
|
+
multiplayer`, `rpc`, `vouchers`), `server/gf-docs.js` goes on serving the same documents there, under the same
|
|
264
|
+
rules; publish refuses a server that has `collections` and does not route `api/db/`:
|
|
250
265
|
|
|
251
266
|
```js
|
|
252
267
|
const level = (await GamesFun.db.add('levels', { name: 'Moon', tiles })).data; // {id, owner, value, updatedAt}
|
|
253
|
-
const mine = (await GamesFun.db.list('levels', { owner: 'me' })).data;
|
|
254
268
|
await GamesFun.db.put('levels', level.id, { name: 'Moon 2', tiles });
|
|
269
|
+
const page = await GamesFun.db.list('levels'); // page.data: the newest 50; page.after: the next page's place
|
|
270
|
+
const next = await GamesFun.db.list('levels', { after: page.after }); // page.after is null on the last page
|
|
255
271
|
```
|
|
256
272
|
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
273
|
+
**Each player has a space of their own** in every collection: a document is found by its collection, its owner and
|
|
274
|
+
its id, so two players can both have a `main`, and nobody can take an id in another player's space. By default a
|
|
275
|
+
signed-in player reads and writes only their own documents. `{"read": "anyone"}` in `database.collections` lets
|
|
276
|
+
everyone read a collection (another player's document by its owner,
|
|
277
|
+
`GamesFun.db.get('levels', id, { owner: playerId })`, and their list with `{ owner: playerId }`); `{"write": "server"}`
|
|
278
|
+
is for what players must not set themselves (the page reads it without naming an owner: the server's document, or
|
|
279
|
+
the player's own that the server wrote for them). Lists page with `after`; `offset` is gone, and refused in words.
|
|
280
|
+
The order is by when a document was last written, so one written between two pages can show twice or be skipped.
|
|
281
|
+
|
|
282
|
+
A game made with xcore 0.1.0: see [Upgrading from 0.1.0](#upgrading-from-010). On a plain local server the
|
|
283
|
+
page keeps documents in the browser; under `xcore dev` the database is a local D1 in `.gamesfun/dev/d1/` with the
|
|
284
|
+
same migrations. Its calls count toward the game server's requests (600 a minute from one player, 6,000 a minute
|
|
285
|
+
shared by all its players): read and write when something happens, not every frame.
|
|
286
|
+
|
|
287
|
+
`xcore db info|query|export|import` reads and writes the local database, and with `--live` the one on xgames.fun
|
|
288
|
+
(each call kept in the game's audit log); a query only reads unless `--write`. `xcore db restore <time> --live` puts
|
|
289
|
+
it back as it was up to 30 days ago, after showing what it would do; for a game with an economy it warns that loot
|
|
290
|
+
balances and payouts on chain are not rolled back. A game made with xcore 0.1.0 keeps its `server/database.js` (a
|
|
291
|
+
Durable Object, the older way), which goes on working.
|
|
292
|
+
|
|
293
|
+
### Project files: `xcore files`
|
|
294
|
+
|
|
295
|
+
A game with a server keeps files of its own on xgames.fun: replays, generated levels, a large saved world, content
|
|
296
|
+
packs (Free 100 MB, Creator 10 GB, Scale 100 GB; 25 MB a file). Its server writes and reads them with
|
|
297
|
+
`env.GF.files` ([docs/SERVER.md](docs/SERVER.md), "Project files"), and you put them from your machine:
|
|
298
|
+
|
|
299
|
+
```bash
|
|
300
|
+
xcore files put ./levels/forest-3.json levels/forest-3.json --public --live # served at https://<slug>.xgamesusercontent.com/levels/forest-3.json
|
|
301
|
+
xcore files ls levels/ --live
|
|
302
|
+
xcore files get replays/run-1.json --live --out run-1.json
|
|
303
|
+
xcore files rm levels/old.json --live
|
|
304
|
+
xcore files usage --live
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
A public file is served from a site of its own, which the browser treats as a stranger to the game: it cannot use the
|
|
308
|
+
player's sign-in or call the game's `api/`, and anything that could carry a script (HTML, SVG, JavaScript) is a
|
|
309
|
+
download, never a page. The game's page may load its pictures, sounds and fonts and `fetch` its data. Players never
|
|
310
|
+
write here. Without `--live`, `xcore files` works on the files `xcore dev` gives the server, kept in
|
|
311
|
+
`.gamesfun/dev/files/` and served on a port of their own.
|
|
263
312
|
|
|
264
313
|
### Multiplayer: `xcore add multiplayer`
|
|
265
314
|
|
|
266
315
|
`xcore add multiplayer <folder>` adds sign-in, and rooms the game owns: server code (`server/rooms.js`, one Durable
|
|
267
|
-
Object a room) that publish puts on
|
|
316
|
+
Object a room) that publish puts on xgames.fun with the game, at its own `api/rooms/<CODE>`. A room is a relay with a
|
|
268
317
|
host: the first player in is the host (id 1, as Godot's server), every other player gets an id of their own, and a
|
|
269
318
|
message goes to one player, to everyone else, or to everyone but one. The room never reads a message: the host's game
|
|
270
319
|
decides what happens. When the host leaves, the player who joined next becomes the host (a Godot game instead sees
|
|
@@ -279,9 +328,9 @@ its server leave, `server_disconnected`, as over ENet).
|
|
|
279
328
|
A room's code is 4 to 8 letters and digits (`rooms.code()` makes one). By default the first to a code opens the room
|
|
280
329
|
and anyone after joins it; a Host button passes `host: true` (refused when someone is in the room) and a Join button
|
|
281
330
|
`host: false` (refused when no one is, as joining an address no one serves fails over ENet). `rooms.link(code)` is the link to share:
|
|
282
|
-
`https://xgames.fun/play/<slug>?room=<CODE>`, which opens the game in that room, on
|
|
331
|
+
`https://xgames.fun/play/<slug>?room=<CODE>`, which opens the game in that room, on xgames.fun and inside a post on X
|
|
283
332
|
(its card frames the room), and `rooms.fromLink()` reads it back. `ROOM` at the top of `server/rooms.js` sets the most
|
|
284
|
-
players (16), whether only signed-in players may join (their
|
|
333
|
+
players (16), whether only signed-in players may join (their xgames.fun id and X handle come with them), and the
|
|
285
334
|
message limits (120 messages a second a player; past it messages are dropped and the game told, through the room's
|
|
286
335
|
`error` event, Godot's `room_error` signal and Unity's `OnError`). The page client sends each frame's messages as one,
|
|
287
336
|
so a game at any frame rate sends at most about 60 a second, however many objects it syncs. A player's connection
|
|
@@ -292,8 +341,8 @@ Host and Join buttons), a variable typed `ENetMultiplayerPeer` becomes `Multipla
|
|
|
292
341
|
`max_clients` is `ROOM.maxPlayers` (for every room of the game), and a packet sent unreliable arrives reliable. The
|
|
293
342
|
peer leaves its room on `peer.close()` or when nothing holds it any more. The host's game relays every client's
|
|
294
343
|
packets to the others, as Godot's server does over ENet. On this machine, tabs of the same browser share a room, so two tabs play together before the game
|
|
295
|
-
is published; under `xcore dev` they play through the room server itself, as on
|
|
296
|
-
run on
|
|
344
|
+
is published; under `xcore dev` they play through the room server itself, as on xgames.fun. A netcode library with a server of its own (Netcode for GameObjects, Mirror, a Node server) does not
|
|
345
|
+
run on xgames.fun as it is: its game logic moves to the host's game, or its server runs on a host of your own (below).
|
|
297
346
|
|
|
298
347
|
**When the host must not decide.** In a room the host's game is the referee, and the host's player can read anything
|
|
299
348
|
their page holds. That is fine for most games played with friends, but not for a hidden role or word (the host would
|
|
@@ -318,7 +367,7 @@ Three ways, any of them, or none:
|
|
|
318
367
|
- **Supabase or Railway**: needs the game-making kit (`@xgamesfun/kit`) installed. `xcore backend connect supabase|railway`
|
|
319
368
|
hands the command to the kit, which connects one for you. Without the kit, set one up yourself and declare it with
|
|
320
369
|
`backend use custom`.
|
|
321
|
-
- **A server
|
|
370
|
+
- **A server xgames.fun runs**: `server/index.js` and `gamesfun.json` → `server` (the `gf-server-v1` contract, [docs/SERVER.md](docs/SERVER.md)): a Worker on the
|
|
322
371
|
game's `/api/*`, with Durable Objects for rooms and state, `env.GF` for sign-in, saves and scores, and your own
|
|
323
372
|
secrets as `env.<NAME>` (`xcore secret set <NAME>`: kept on the server's host only, never shown again, never given
|
|
324
373
|
to the page). `xcore dev` runs it under workerd at `/game/api/*`, with secrets from `.dev.vars`; publish bundles it.
|
|
@@ -359,12 +408,12 @@ player's wallet through the Wallet Standard, with no package:
|
|
|
359
408
|
| --------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
360
409
|
| `solanaWallets({ waitMs = 300 })` | → the wallets in this browser that can sign Solana devnet transactions (an empty list when there are none: say so, and keep the game playable) |
|
|
361
410
|
| `connectWallet(wallet)` | → the player's account; the wallet shows its own prompt, and it throws if they say no |
|
|
362
|
-
| `PAGE_RPC` | the page's own `/loot/v1/rpc` (
|
|
411
|
+
| `PAGE_RPC` | the page's own `/loot/v1/rpc` (xgames.fun's relay once published) |
|
|
363
412
|
|
|
364
413
|
`walletStandardSigner(wallet, account)` from `gf/loot/play/index.js` turns them into the signer `solanaApp` takes.
|
|
365
414
|
|
|
366
415
|
**From Godot, Unity or any page with `gamesfun.js`** (`xcore add signin`), `GamesFun.loot` does all of that with JSON in
|
|
367
|
-
and out (big numbers as text), so an engine calls it like the rest of
|
|
416
|
+
and out (big numbers as text), so an engine calls it like the rest of xgames.fun:
|
|
368
417
|
|
|
369
418
|
```js
|
|
370
419
|
await GamesFun.loot.ready(); // {live, project, wallets: [names], spec}
|
|
@@ -381,7 +430,7 @@ project's own `gamesfun/` folder (with a `.gdignore`, so Godot leaves it alone):
|
|
|
381
430
|
Publish, `xcore check` and `xcore dev` lay that folder over the export, so you export as before, and the file that
|
|
382
431
|
holds the addresses the economy went live at stays in the project (keep it in git).
|
|
383
432
|
|
|
384
|
-
`@xgamesfun/solana`'s own examples use a public devnet RPC and `node_modules/` paths; on
|
|
433
|
+
`@xgamesfun/solana`'s own examples use a public devnet RPC and `node_modules/` paths; on xgames.fun use `PAGE_RPC` and
|
|
385
434
|
`gf/loot/` as above. Where its help says `xsolana <command>`, run `xcore loot <command>`. `xcore loot check|simulate|cost|explain`
|
|
386
435
|
runs the tools from a terminal. Going live is the person's own step, approved in their own wallet: `xcore loot
|
|
387
436
|
approve` in the folder with `xsolana.json`.
|
|
@@ -399,12 +448,12 @@ is a grant claimed with a voucher, and the voucher key must never be in the page
|
|
|
399
448
|
game's server checks the result, and then one of two signers vouches for it.
|
|
400
449
|
|
|
401
450
|
**Paying a result: the keeper or your own key.** When the server already decides who won and the prize is fixed by
|
|
402
|
-
place (a room's winner, a match's top three), it reports the winners with `env.GF.loot.report` and
|
|
451
|
+
place (a room's winner, a match's top three), it reports the winners with `env.GF.loot.report` and xgames.fun's keeper
|
|
403
452
|
signs, with no key for you to keep. When the amount depends on the result (paid by score), when the server replays or
|
|
404
453
|
bounds runs before paying, when there are entry fees or a prize pool, or when results come faster than 120 an hour,
|
|
405
454
|
give the server its own key: `xcore add vouchers` writes `server/vouchers.js` (its `PAY.check` is what your server
|
|
406
455
|
checks) and a test key in `.dev.vars` for `xcore dev`. In order: `xcore publish`, `xcore secret set VOUCHER_SEED
|
|
407
|
-
--new-key` (the live key
|
|
456
|
+
--new-key` (the live key: made here, sent only to xgames.fun and never kept on your machine; it prints the address), that address into the
|
|
408
457
|
grant in `xsolana.json`, `xcore loot approve`, `xcore publish`. A voucher pays only a wallet the player proved is
|
|
409
458
|
theirs (signed in with it, or linked with `gf.loot.link()`), and since a wallet is free, pay rewards worth farming
|
|
410
459
|
only to players signed in with X (`VOUCHERS.payOnly`). A grant names up to three keys, so the keeper and your own can
|
|
@@ -413,23 +462,23 @@ pay the same grant while you move from one to the other.
|
|
|
413
462
|
|
|
414
463
|
## 5. Publish
|
|
415
464
|
|
|
416
|
-
`xcore login` signs this computer in (your browser signs in to
|
|
465
|
+
`xcore login` signs this computer in (your browser signs in to xgames.fun and hands it a publish token for 30
|
|
417
466
|
days; xcore never sees your password). `xcore publish <folder>` then builds the game, checks it (the page
|
|
418
467
|
check on exactly the files built), and uploads its published folder with its `server/` bundle:
|
|
419
468
|
|
|
420
469
|
1. **The files**: the published folder, without hidden files, `evidence/`, `node_modules/`, `server/`, `supabase/` and the makers' notes at its top (`README.md`, `NOTES.md`, `LAUNCH.md` and the like). A `.env` file or a link
|
|
421
|
-
stops it. Two small records go up beside them, as
|
|
470
|
+
stops it. Two small records go up beside them, as xgames.fun keeps them with every version:
|
|
422
471
|
`foundation.lock.json` (which tool published it) and `adaptation-plan.json` (the game's title and a one-line
|
|
423
472
|
description). Like every published file, they can be read at the game's link.
|
|
424
473
|
2. **No secrets**: every text file is scanned for anything that looks like a key (private keys, cloud, payment and AI
|
|
425
|
-
keys, a Supabase service-role key, a database address with a password).
|
|
474
|
+
keys, a Supabase service-role key, a database address with a password). xgames.fun scans again on its side.
|
|
426
475
|
3. **Its backend**: `backend.json`'s origins are the only other sites the published page may reach.
|
|
427
476
|
4. **Its server code** (`server/`, a Worker under the `gf-server-v1` contract): bundled into one file and checked (it
|
|
428
477
|
builds, no code made from text, no Node built-in a Worker lacks, no secrets, its Durable Objects exported, at most
|
|
429
478
|
5 MB). `server/` and `gamesfun.json` are never public files.
|
|
430
|
-
5. **The check**: a game whose check fails is saved as a private draft (a version in your
|
|
479
|
+
5. **The check**: a game whose check fails is saved as a private draft (a version in your xgames.fun Studio, with no
|
|
431
480
|
public link), and you are told which checks failed. A pass is remembered for exactly these files.
|
|
432
|
-
6. **Upload**: only the files
|
|
481
|
+
6. **Upload**: only the files xgames.fun does not have yet.
|
|
433
482
|
7. **The link**: unlisted, and the same through every update. Publishing the folder again makes the next version. The
|
|
434
483
|
play host caches files for up to 30 seconds, so an update can take that long to show at the link, and for that
|
|
435
484
|
time a visitor can get some files of the new version and some of the old. Files the new version no longer has
|
|
@@ -440,12 +489,19 @@ check on exactly the files built), and uploads its published folder with its `se
|
|
|
440
489
|
`publish --check` runs steps 1 to 4 above without publishing (`--check --verify` adds the check and remembers a pass), `--status`
|
|
441
490
|
shows the link and the account, `--draft` saves a private version, `--new` makes a new game with a new link.
|
|
442
491
|
|
|
492
|
+
**For scripts and agents**: every command takes `--json`. A failure is then also printed on stdout as
|
|
493
|
+
`{ "ok": false, "error", "code", "state", "next" }`: `code` is why (`usage`, `login`, `files`, `secrets`, `build`,
|
|
494
|
+
`size`…), `next` the command that moves it on. `login --status --json` and `publish --status --json` name the sign-in's
|
|
495
|
+
`state` (`signed-in`, `signed-out`, `expired`, or `unreadable` when the token is kept where this session cannot read
|
|
496
|
+
it, such as the Secret Service over SSH); `publish --status --json` also names the game's `link` (`never`, `started`,
|
|
497
|
+
`public`, `draft`, or `draft-over-public`); `adopt --json` gives `state` (`refused`, `fix`, `ready`) and `next`.
|
|
498
|
+
|
|
443
499
|
**Where the token is kept**: the macOS keychain; on Linux the Secret Service (`secret-tool`) when it runs, else a file
|
|
444
500
|
only you can read under `~/.config/gamesfun/secrets/`; on Windows a file encrypted for your user (DPAPI) under
|
|
445
501
|
`%APPDATA%\gamesfun\secrets\`. `GAMESFUN_CREDENTIALS=file` always uses the file. Without a browser (SSH, a
|
|
446
502
|
container), `xcore login --code` prints a link to open anywhere and asks for the code it shows.
|
|
447
503
|
|
|
448
|
-
**From GitHub Actions, no key stored**: the job's own OIDC token signs the publish in place of a login.
|
|
504
|
+
**From GitHub Actions, no key stored**: the job's own OIDC token signs the publish in place of a login. xgames.fun
|
|
449
505
|
accepts it for the repository and branch you linked to the game once in Studio.
|
|
450
506
|
|
|
451
507
|
```yaml
|
|
@@ -463,28 +519,32 @@ jobs:
|
|
|
463
519
|
```
|
|
464
520
|
|
|
465
521
|
**Limits**: one game 3,000 files, 100 MiB (one file may be all of it); one account 30 publishes and 1 GB of new files a day, 100
|
|
466
|
-
games. `GAMESFUN_STUDIO=<origin>` publishes to another
|
|
522
|
+
games. `GAMESFUN_STUDIO=<origin>` publishes to another xgames.fun (a Studio on your own machine while developing it).
|
|
467
523
|
|
|
468
524
|
### Its plan: `xcore plan`
|
|
469
525
|
|
|
470
|
-
|
|
526
|
+
Every game sits in a **slot**. Everyone gets **2 free slots**, whose games run on Free; any more games need a **paid
|
|
527
|
+
slot** ($15 for 30 days), whose game runs on Creator. A new game is refused while both free slots are taken: archive one
|
|
528
|
+
you no longer need (on your console, xgames.fun/console; unpublish it first) or move one to a paid slot. Archived games
|
|
529
|
+
free their slot unless their link is still live. What the game's server, saves and scores may use is its plan:
|
|
471
530
|
|
|
472
|
-
| | Free | Creator
|
|
473
|
-
| -------------------- | ------------------------------------------------------------------ |
|
|
474
|
-
| For |
|
|
475
|
-
| Server | 50 ms of CPU a request; 600 requests a minute, 120 from one player | 200 ms; 6,000 a minute, 600 from one player
|
|
476
|
-
| Saves and scores | 10 MB of saves; 8 boards of 10,000 entries | 1 GB; 16 boards of 1,000,000
|
|
477
|
-
| Use included a month | $1 | $10 a slot, shared by your Creator games
|
|
478
|
-
| Sites that embed it | 3 (`xcore embed`) | 10
|
|
479
|
-
| Domains of your own | none | 1 (`xcore domain`)
|
|
531
|
+
| | Free | Creator | Scale |
|
|
532
|
+
| -------------------- | ------------------------------------------------------------------ | ---------------------------------------------------- | ----------------------- |
|
|
533
|
+
| For | a game in one of your 2 free slots | a game in one of your paid slots (per game, a month) | granted by xgames.fun |
|
|
534
|
+
| Server | 50 ms of CPU a request; 600 requests a minute, 120 from one player | 200 ms; 6,000 a minute, 600 from one player | 1,000 ms; 30,000, 1,200 |
|
|
535
|
+
| Saves and scores | 10 MB of saves; 8 boards of 10,000 entries | 1 GB; 16 boards of 1,000,000 | 10 GB; 16 of 1,000,000 |
|
|
536
|
+
| Use included a month | $1 | $10 a slot, shared by your Creator games | as granted |
|
|
537
|
+
| Sites that embed it | 3 (`xcore embed`) | 10 | 25 |
|
|
538
|
+
| Domains of your own | none | 1 (`xcore domain`) | 5 |
|
|
480
539
|
|
|
481
540
|
Past what is included, a game is **capped** until the month ends: its server answers at most 60 requests a minute (20
|
|
482
|
-
from one player) and saves and scores slow down, but nothing is lost and the game keeps working.
|
|
483
|
-
days, prepaid in SOL or USDC
|
|
484
|
-
|
|
485
|
-
|
|
541
|
+
from one player) and saves and scores slow down, but nothing is lost and the game keeps working. Paid slots are $15 for
|
|
542
|
+
30 days, prepaid in SOL or USDC on xgames.fun (your console → Plans; no automatic renewal, 7 days' grace after the period
|
|
543
|
+
ends; when they end, their games go back to free slots and nothing is taken down).
|
|
544
|
+
Usage credit, bought the same way, lets Creator games go past what their slots include, at twice what xgames.fun pays,
|
|
545
|
+
instead of slowing down. A paid slot moves between your games at any time
|
|
486
546
|
(`xcore plan deactivate <one>`, `xcore plan activate <another>`) without changing the bill. `xcore plan [folder]`
|
|
487
|
-
shows a game's plan, its limits and this month's use; `--all` your slots and every game. A game's server sees a 429
|
|
547
|
+
shows a game's plan, its limits and this month's use; `--all` your free and paid slots and every game. A game's server sees a 429
|
|
488
548
|
with `scope` (`"player"` or `"game"`) and `plan` when it goes past a rate.
|
|
489
549
|
|
|
490
550
|
### Your own site and your own domain: `xcore embed`, `xcore domain`
|
|
@@ -495,33 +555,72 @@ Creator and Scale it can live at an address of yours:
|
|
|
495
555
|
```sh
|
|
496
556
|
xcore embed add https://mygame.com # mygame.com may frame the game; prints the <iframe> for its page
|
|
497
557
|
xcore domain add play.mygame.com # Creator or Scale: prints the DNS records to add at your domain's DNS
|
|
498
|
-
xcore domain check play.mygame.com # live once the records are right (
|
|
558
|
+
xcore domain check play.mygame.com # live once the records are right (xgames.fun also checks by itself)
|
|
499
559
|
xcore domain # where the game plays, the sites that embed it, its domains
|
|
500
560
|
```
|
|
501
561
|
|
|
502
562
|
- **Embedding** (every plan: 3 sites, 10, 25): a site is its origin (`https://mygame.com`; `http://localhost:<port>`
|
|
503
563
|
while you build it). It applies within about a minute, with no publish. Framed on your site, the game signs players
|
|
504
|
-
in by itself (a small
|
|
564
|
+
in by itself (a small xgames.fun window, or a code), as in a post on X: only xgames.fun's own page can hand it a
|
|
505
565
|
sign-in, so a page that embeds it can't sign players in for it or take their sign-in.
|
|
506
566
|
- **A domain of your own** (Creator 1, Scale 5): `domain add` prints two records, a CNAME pointing the domain at
|
|
507
|
-
|
|
567
|
+
xgames.fun and a TXT proving you own it. Once they are right the game is served there with its certificate, and
|
|
508
568
|
sign-in, saves, scores, rooms, the server and the economy work there as on its own link. Sign-in still opens a
|
|
509
|
-
small
|
|
569
|
+
small xgames.fun window, Studio stays on xgames.fun, and a post on X still uses the xgames.fun link. A bare domain
|
|
510
570
|
(`mygame.com`) needs your DNS provider's CNAME flattening or ALIAS record; else use `www.` or `play.` and redirect.
|
|
511
571
|
The page must load `gamesfun.js` 1.2.0 or later (it asks the play edge which game it is on a host of its own): an
|
|
512
572
|
older copy can't sign players in there, so `domain add` says when to run `xcore add signin` and publish again. A
|
|
513
573
|
game whose plan drops keeps its domain paused: it sends players to the xgames.fun link until there is room again.
|
|
514
|
-
- Custom domains need
|
|
574
|
+
- Custom domains need xgames.fun's operator to have set them up (Cloudflare for SaaS): until then `xcore domain` says so.
|
|
575
|
+
|
|
576
|
+
## Upgrading from 0.1.0
|
|
577
|
+
|
|
578
|
+
A game made with xcore 0.1.0 that has `server/database.js` (`xcore add database` then) needs these changes. A game
|
|
579
|
+
without one needs none: the D1 database, `xcore db` and `xcore files` are new.
|
|
580
|
+
|
|
581
|
+
1. **Run `xcore add database` again.** It brings `server/database.js` up to date (your `RULES` and `LIMITS` are
|
|
582
|
+
kept), with `gamesfun.js` (1.4.0) and the Godot or Unity glue, and lists each `dataOf(env)` call in `server/` that
|
|
583
|
+
needs an owner (step 3) as `file:line` with its change; `xcore publish` lists them again until each names its
|
|
584
|
+
owner. On its first start the new `database.js` moves the documents to a table keyed by collection, owner and id,
|
|
585
|
+
each under its owner. After that, do not roll the game back to a version with 0.1.0's `database.js`: its writes
|
|
586
|
+
fail on the new table.
|
|
587
|
+
2. **Rules are private by default.** A `RULES` that 0.1.0 wrote and nobody changed
|
|
588
|
+
(`'*': { read: 'anyone', write: 'owner' }`) becomes `'*': { read: 'owner', write: 'owner' }`: each player reads
|
|
589
|
+
only their own documents. Name each collection that is meant to be public:
|
|
590
|
+
`levels: { read: 'anyone', write: 'owner' }`. A rule xcore does not know (`read: 'everyone'`) now stops the server
|
|
591
|
+
when it starts, instead of being read as something else.
|
|
592
|
+
3. **`dataOf(env)` takes an owner.** Without one it is the server's own space (owner `"server"`): 0.1.0's
|
|
593
|
+
`dataOf(env).get('profiles', playerId)` answers 404, and `.put('profiles', playerId, …)` writes a copy the player
|
|
594
|
+
never sees. Pass `{ owner: sub }` (the player's id, `env.GF.verifyPlayer`'s `sub`) as the last argument of `get`,
|
|
595
|
+
`put`, `add` and `delete`, or `{ owner: 'server' }` for the server's own documents. A `list` without an owner is
|
|
596
|
+
still everyone's.
|
|
597
|
+
4. **A page reads another player's document by its owner.** An id alone is now the player's own document (or, in a
|
|
598
|
+
`write: 'server'` collection, the server's). For someone else's, pass the owner:
|
|
599
|
+
`gf.db.get('levels', id, { owner: doc.owner })`, `GET api/db/<collection>/<id>?owner=<id>`, Godot
|
|
600
|
+
`db_get(collection, id, {"owner": owner})`, Unity `DbGet(collection, id, owner)`. By id alone it gives
|
|
601
|
+
`data: null` with `code: 'owner'` (said once in the console).
|
|
602
|
+
5. **Lists page with `after`, not `offset`.** Pass the last answer's `after` for the next page
|
|
603
|
+
(`gf.db.list('levels', { after: page.after })`); it is `null` on the last page, and `more` says whether there is
|
|
604
|
+
another. A real `offset` is refused (0 or none is fine): `code: 'offset'` from `gf.db.list` and Godot's `db_list`,
|
|
605
|
+
400 from `GET api/db/<collection>?offset=` and from `dataOf(env).list`. Unity's
|
|
606
|
+
`DbList(collection, owner, limit, after, order)` takes the `after` text where 0.1.0's took an `int offset`, so a
|
|
607
|
+
call that passed an offset does not compile until it passes `after`. `LIMITS.offset` is gone.
|
|
608
|
+
6. **`createData`** (for server code that runs the documents over SQLite of its own): 0.1.0's
|
|
609
|
+
`createData(sql, { rules, limits, now })`, which called `sql.exec`, is
|
|
610
|
+
`createData(exec, { rules, limits, now, table })`, where `exec(query, ...bindings)` answers the rows or a promise
|
|
611
|
+
of them. Every call is async, and the options come before `who`: `get(collection, id, opts, who)`,
|
|
612
|
+
`put(collection, id, value, opts, who)`, `add(collection, value, opts, who)`, `delete(collection, id, opts, who)`,
|
|
613
|
+
`list(collection, opts, who)`.
|
|
515
614
|
|
|
516
615
|
## What is not supported, and why
|
|
517
616
|
|
|
518
617
|
- **Server-side rendering** (a page made per request by Node): the host serves files only. Make the page static and
|
|
519
618
|
put what needs a server in `server/` (a Worker) or a backend of your own.
|
|
520
|
-
- **A server in Python, Ruby, Go or another language**:
|
|
619
|
+
- **A server in Python, Ruby, Go or another language**: xgames.fun runs only `server/` (a Worker, JavaScript or
|
|
521
620
|
WebAssembly). Such a server runs on a host of your own, declared with `backend use custom`; or its logic moves into
|
|
522
621
|
`server/`, or into the page itself (WebAssembly is allowed: Pyodide runs Python in the browser). Each is a change to
|
|
523
622
|
how the game is built: the person who made it decides.
|
|
524
|
-
- **A Node server as the backend on
|
|
623
|
+
- **A Node server as the backend on xgames.fun**: `server/` is a Worker, not Node (Node built-ins only with
|
|
525
624
|
`nodejs_compat`, and no `fs`, `child_process` or listening sockets). A Node backend runs on a host of your own,
|
|
526
625
|
declared with `backend use custom`.
|
|
527
626
|
- **Scripts, style sheets and fonts from a CDN**: the page loads code and styles from its own files only, so a copy goes
|
|
@@ -565,10 +664,10 @@ from a qualified professional before you offer anything of real value.
|
|
|
565
664
|
real value. Blockchain transactions are public and irreversible. You alone control your wallet and keys. Never put a
|
|
566
665
|
private key, seed phrase or secret in a game, and approve only transactions you understand.
|
|
567
666
|
|
|
568
|
-
**Third-party services.**
|
|
667
|
+
**Third-party services.** xgames.fun, Cloudflare, Supabase, Railway, GitHub, npm, Solana RPC providers and wallets are
|
|
569
668
|
run by others under their own terms and policies, which you must follow. Their availability, fees and rules can change
|
|
570
669
|
without notice.
|
|
571
670
|
|
|
572
671
|
## License
|
|
573
672
|
|
|
574
|
-
[MIT](LICENSE) ©
|
|
673
|
+
[MIT](LICENSE) © xgames.fun
|