@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.
Files changed (72) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +183 -84
  3. package/bin/xcore.mjs +13 -8
  4. package/commands/add.mjs +11 -8
  5. package/commands/adopt.mjs +9 -7
  6. package/commands/backend.mjs +2 -2
  7. package/commands/check.mjs +4 -4
  8. package/commands/db.mjs +218 -0
  9. package/commands/dev.mjs +8 -6
  10. package/commands/domain.mjs +8 -8
  11. package/commands/embed.mjs +3 -3
  12. package/commands/files.mjs +106 -0
  13. package/commands/login.mjs +9 -17
  14. package/commands/logout.mjs +15 -4
  15. package/commands/loot.mjs +2 -2
  16. package/commands/plan.mjs +22 -19
  17. package/commands/publish.mjs +54 -21
  18. package/commands/secret.mjs +6 -6
  19. package/docs/SERVER.md +218 -18
  20. package/lib/add.mjs +265 -40
  21. package/lib/browser.mjs +11 -2
  22. package/lib/check.mjs +10 -9
  23. package/lib/cli.mjs +24 -9
  24. package/lib/client/gamesfun.js +242 -122
  25. package/lib/client/godot/gamesfun.gd +17 -11
  26. package/lib/client/godot/gamesfun_peer.gd +2 -2
  27. package/lib/client/server/0001_init.sql +10 -0
  28. package/lib/client/server/database.js +271 -109
  29. package/lib/client/server/gf-db.js +55 -0
  30. package/lib/client/server/gf-docs.js +56 -0
  31. package/lib/client/server/rooms.js +16 -9
  32. package/lib/client/server/rpc.js +7 -7
  33. package/lib/client/server/stock.js +37 -0
  34. package/lib/client/server/vouchers.js +127 -46
  35. package/lib/client/unity/GamesFun.cs +15 -12
  36. package/lib/client/unity/GamesFun.jslib +1 -1
  37. package/lib/config-dir.mjs +1 -1
  38. package/lib/credentials.mjs +10 -5
  39. package/lib/db.mjs +355 -0
  40. package/lib/dev/d1.mjs +486 -0
  41. package/lib/dev/files.mjs +247 -0
  42. package/lib/dev/gf-local.js +69 -3
  43. package/lib/dev/index.mjs +68 -20
  44. package/lib/dev/limits.mjs +2 -2
  45. package/lib/dev/platform.mjs +111 -12
  46. package/lib/dev/server.mjs +54 -5
  47. package/lib/dev/tools.mjs +71 -18
  48. package/lib/files.mjs +208 -0
  49. package/lib/open.mjs +1 -1
  50. package/lib/play-policy.mjs +14 -5
  51. package/lib/publish/adopt.mjs +73 -33
  52. package/lib/publish/client.mjs +130 -20
  53. package/lib/publish/gate.mjs +4 -2
  54. package/lib/publish/hosting.mjs +5 -5
  55. package/lib/publish/index.mjs +162 -40
  56. package/lib/publish/oidc.mjs +26 -5
  57. package/lib/publish/page.mjs +1 -1
  58. package/lib/publish/plans.mjs +19 -12
  59. package/lib/publish/scan.mjs +78 -102
  60. package/lib/publish/secrets.mjs +4 -5
  61. package/lib/publish/site.mjs +9 -10
  62. package/lib/publish/tree.mjs +8 -8
  63. package/lib/secret-patterns.mjs +53 -16
  64. package/lib/secret-store.mjs +2 -2
  65. package/lib/server/bundle.mjs +113 -13
  66. package/lib/server/contract.mjs +440 -16
  67. package/lib/server/data-calls.mjs +136 -0
  68. package/lib/server/secrets.mjs +9 -7
  69. package/lib/solana-rpc.mjs +5 -5
  70. package/lib/state-folder.mjs +23 -0
  71. package/package.json +4 -4
  72. package/skills/bring-your-own-game/SKILL.md +10 -9
package/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2026 games.fun
3
+ Copyright (c) 2026 xgames.fun
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
package/README.md CHANGED
@@ -1,17 +1,17 @@
1
1
  # xcore
2
2
 
3
- The one place to start with games.fun (`@xgamesfun/xcore` on npm, the `xcore` command). What you came for decides where
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 | Then |
7
- | ------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------- |
8
- | **A web game you already made** | brings it to games.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) |
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 games.fun runs.
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 games.fun hosts it: its server, sign-in, rooms, economy
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 games.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. |
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` (games.fun's shared devnet relay, counted toward the game's plan), with the page client in the game (`gf/loot/`, from `@xgamesfun/solana`). |
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 games.fun. The server bundle: 5 MB. What the game's server, saves and scores may use is its plan (§5, `xcore plan`). |
62
- | **Refused, always** | `.env` files, symbolic links, key and certificate files, wallet keypairs, and anything in a text file that looks like a secret (games.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
+ | **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 games.fun (else `index.html`'s `<title>`, else `package.json`'s name) |
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 games.fun previews games at an origin of their
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 games.fun page is not cross-origin
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 games.fun's rooms unchanged (§3), or a `WebSocketMultiplayerPeer` reaches a server of your own (`backend use custom`).
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; games.fun does not host that server, and a page that shows such a stream is untested.
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, and the link once
161
- published). None of them is published; add `evidence/` and `.gamesfun/` to the game's `.gitignore`.
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 games.fun will host it, with everything a published game
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 games.fun runs it on: Durable Objects with their storage kept, WebSockets, `env.GF`, 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 |
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
- | `/loot/v1/rpc` | the economy's Solana devnet RPC: games.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`) |
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
- games.fun signs players in (with X first, or a Solana wallet) for every game, and keeps each player's saves and the
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 games.fun is
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 games.fun's play page**, the page signs the player in and hands the game a token.
225
- - **On the game's own link, and inside a post on X**, a small window on games.fun signs in; when the browser keeps it
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 games.fun does, and the game's `server/` accepts its token.
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: server code (`server/database.js`, one
247
- Durable Object with SQLite storage, [docs/SERVER.md](docs/SERVER.md)) that publish puts on games.fun with the game, at
248
- its own `api/db/`. Nothing to sign up for, no keys, and it is kept between publishes. It holds documents (any JSON up
249
- to 64 KB) in collections:
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
- Anyone reads; a signed-in player writes only their own documents. `RULES` at the top of `server/database.js` changes
258
- that per collection (`read: 'owner'` keeps a collection private to each player; `write: 'server'` lets only the game's
259
- own server code write, through `dataOf(env)`, for anything players must not set themselves). A game with a server of
260
- its own keeps it: the database is added beside it, with the two lines to route it. On this machine the page keeps
261
- documents in the browser instead. Its calls count toward the game server's requests (600 a minute from one player, 6,000 a
262
- minute shared by all its players): read and write when something happens, not every frame.
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 games.fun with the game, at its own `api/rooms/<CODE>`. A room is a relay with a
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 games.fun and inside a post on X
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 games.fun id and X handle come with them), and the
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 games.fun. A netcode library with a server of its own (Netcode for GameObjects, Mirror, a Node server) does not
296
- run on games.fun as it is: its game logic moves to the host's game, or its server runs on a host of your own (below).
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 games.fun runs**: `server/index.js` and `gamesfun.json` → `server` (the `gf-server-v1` contract, [docs/SERVER.md](docs/SERVER.md)): a Worker on the
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` (games.fun's relay once published) |
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 games.fun:
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 games.fun use `PAGE_RPC` and
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 games.fun's keeper
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, made on games.fun and never on your machine: it prints the address), that address into the
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 games.fun and hands it a publish token for 30
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 games.fun keeps them with every version:
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). games.fun scans again on its side.
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 games.fun Studio, with no
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 games.fun does not have yet.
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. games.fun
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 games.fun (a Studio on your own machine while developing it).
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
- Hosting the page is free. What the game's server, saves and scores may use is the game's plan:
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 | Scale |
473
- | -------------------- | ------------------------------------------------------------------ | --------------------------------------------------------- | ----------------------- |
474
- | For | every game, by default | a game in one of your paid game slots (per game, a month) | granted by games.fun |
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 | 1,000 ms; 30,000, 1,200 |
476
- | 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 |
477
- | Use included a month | $1 | $10 a slot, shared by your Creator games | as granted |
478
- | Sites that embed it | 3 (`xcore embed`) | 10 | 25 |
479
- | Domains of your own | none | 1 (`xcore domain`) | 5 |
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. Slots are $15 for 30
483
- days, prepaid in SOL or USDC in games.fun Studio (Plans; no automatic renewal, 7 days' grace after the period ends).
484
- Usage credit, bought the same way, lets Creator games go past what their slots include, at twice what games.fun pays,
485
- instead of slowing down. A slot moves between your games at any time
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 (games.fun also checks by itself)
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 games.fun window, or a code), as in a post on X: only games.fun's own page can hand it a
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
- games.fun and a TXT proving you own it. Once they are right the game is served there with its certificate, and
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 games.fun window, Studio stays on games.fun, and a post on X still uses the xgames.fun link. A bare domain
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 games.fun's operator to have set them up (Cloudflare for SaaS): until then `xcore domain` says so.
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**: games.fun runs only `server/` (a Worker, JavaScript or
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 games.fun**: `server/` is a Worker, not Node (Node built-ins only with
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.** games.fun, Cloudflare, Supabase, Railway, GitHub, npm, Solana RPC providers and wallets are
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) © games.fun
673
+ [MIT](LICENSE) © xgames.fun