gamesteel 0.5.3 → 0.5.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/gamesteel.js CHANGED
@@ -25339,7 +25339,7 @@ interface Purchases {
25339
25339
  restore(): void
25340
25340
  }
25341
25341
 
25342
- /** The game's own server, for a game whose Online tab turned it on (the guide's online topic). Each ask goes to the
25342
+ /** The game's own server, for a game whose Server page turned it on (the guide's online topic). Each ask goes to the
25343
25343
  * server through the page; what comes back reaches every script on a later tick as onEvent('online', heard), where
25344
25344
  * heard is { kind: 'answer', id, name, result, data?, reason? } (result 'ok', 'error', 'busy', 'resting' or 'refused'),
25345
25345
  * { kind: 'message', room, data }, or { kind: 'joined' | 'left' | 'full', room }. A tick never waits on the network. */
@@ -25696,7 +25696,7 @@ interface Game {
25696
25696
  /** An event every onEvent hears on the next tick; it also counts as the tick's event. */
25697
25697
  emit(name: string, data?: Plain): void
25698
25698
  /** The game's seeded random number in [0, 1); Math.random is the same stream. Each
25699
- * play in the Game tab starts from a seed of its own; a playtest starts from seed 1,
25699
+ * play in the editor starts from a seed of its own; a playtest starts from seed 1,
25700
25700
  * so its runs repeat. */
25701
25701
  random(): number
25702
25702
  /** A whole number from lo to hi, both included. */
@@ -25751,7 +25751,7 @@ interface Game {
25751
25751
  /** Opens the game's privacy policy, as a game on the app stores must from inside itself: call it when the player
25752
25752
  * taps the game's own Privacy button. The page knows where the policy is; with none yet, nothing opens. */
25753
25753
  openPrivacy(): void
25754
- /** Kept across restarts and levels, and between plays in the Game tab (in that
25754
+ /** Kept across restarts and levels, and between plays in the editor (in that
25755
25755
  * browser). A playtest starts with nothing saved. */
25756
25756
  save(key: string, value: Plain): void
25757
25757
  load(key: string): Plain
@@ -91906,7 +91906,7 @@ var SCRIPTS_PRIMER = [
91906
91906
  " and sets that language and game.languages lists them. The HUD, the end screens and the menus write @hello for a",
91907
91907
  " key's text. The person picks the language in the player's menu; the edit operation game.setStrings writes the tables.",
91908
91908
  "- The person exports a game from the toolbar's Export: a zip whose page plays it from the disk or any web host and",
91909
- " installs on a phone, playing as the Game tab does, saves in the browser and all.",
91909
+ " installs on a phone, playing as the editor does, saves in the browser and all.",
91910
91910
  "- Mix freely: rules and scripts run in the same game, and a script may read what a rule changed.",
91911
91911
  "",
91912
91912
  "A kind's script:",
@@ -96179,7 +96179,7 @@ interface GameRequest {
96179
96179
  json(): Json
96180
96180
  }
96181
96181
 
96182
- /** Players' files and the game's own, in the game's storage (turned on in its Online tab, with its database): a
96182
+ /** Players' files and the game's own, in the game's storage (turned on, on its Server page, with its database): a
96183
96183
  * player's under players/<their id>/, so "Delete my online data" reaches them, the game's under game/. */
96184
96184
  interface Files {
96185
96185
  /** An address the player's page uploads exactly size bytes to, for 15 minutes or the seconds given: at most the
@@ -96195,7 +96195,7 @@ interface Files {
96195
96195
  list(prefix: string): Promise<Array<{ key: string; size: number }>>
96196
96196
  }
96197
96197
 
96198
- /** Notifications to players' phones, through the game's own Firebase project (turned on in its Online tab, with its
96198
+ /** Notifications to players' phones, through the game's own Firebase project (turned on, on its Server page, with its
96199
96199
  * database). A player is reached once their app asked them (game.online.askPush) and they said yes. Each push costs
96200
96200
  * one event. Never required to play, never an ad without the player's consent, never a reward for saying yes. */
96201
96201
  interface Push {
@@ -96205,7 +96205,7 @@ interface Push {
96205
96205
 
96206
96206
  /** What every function of the server is given. */
96207
96207
  interface Ctx {
96208
- /** The game's own database (Postgres): turned on in its Online tab. Keep each player's rows with a player_id column,
96208
+ /** The game's own database (Postgres): turned on, on its Server page. Keep each player's rows with a player_id column,
96209
96209
  * so "Delete my online data" reaches them. */
96210
96210
  readonly db: { query(sql: string, params?: readonly Json[]): Promise<{ rows: Record<string, Json>[] }> }
96211
96211
  /** Live rooms: send to everyone in a room, or to one player in it. Who is in a room is room.players, in its hooks. */
@@ -96646,7 +96646,7 @@ var SOUND_TOPIC = [
96646
96646
  "a noise layer, a brighter wave, a filter opened. It is muddy: highpass it, or lower the body. A click at its end: a",
96647
96647
  "longer release. A loop jumps at its seam: end on notes that lead back. Compare two of the game's own with listen",
96648
96648
  "{ names: ['own:hit', 'own:hit-heavy'] }. A playtest plays no audio, so the readout is how you hear it; the person",
96649
- "hears it in the Game tab."
96649
+ "hears it when they play it in the editor."
96650
96650
  ];
96651
96651
 
96652
96652
  // ../agent/dist/sight.js
@@ -97058,7 +97058,7 @@ var PLAYTEST_TOOLS = [
97058
97058
  },
97059
97059
  {
97060
97060
  name: "playtest_look",
97061
- description: "a picture of the game now, as the person sees it in the Game tab, and the data of the objects you name; or, with frames, a strip of moments side by side in one picture, each labelled with its time. Pictures cost money: look at the moments that matter, not after every step.",
97061
+ description: "a picture of the game now, as the person sees it when they play it in the editor, and the data of the objects you name; or, with frames, a strip of moments side by side in one picture, each labelled with its time. Pictures cost money: look at the moments that matter, not after every step.",
97062
97062
  inputSchema: object2({
97063
97063
  game: GAME,
97064
97064
  scene: SCENE,
@@ -97929,7 +97929,7 @@ var FILM = [
97929
97929
  " second, its sound mixed from the ticks that asked for it. Nobody watches it live as it renders, so a film too",
97930
97930
  " heavy to play smoothly live comes out as smooth as a light one.",
97931
97931
  "- Each later answer about the game says how the render goes, then how it ended. With no editor open, ask the person",
97932
- " to open the game and press Render video in the Game tab's Films.",
97932
+ " to open the game and press Render video in Upload \u203A Films.",
97933
97933
  "- The exported game plays a film's video in the film's place, in the player's language, with pause, chapters and",
97934
97934
  " skip; a film with no video in that language plays live. After any edit a video is older than the game: render it",
97935
97935
  " again before the person exports.",
@@ -97942,11 +97942,11 @@ var FILM = [
97942
97942
  var PUBLISH = [
97943
97943
  "# Publishing a game",
97944
97944
  "",
97945
- "A finished game goes out from the editor's Upload menu. Every account it goes to is the person's own: their GitHub,",
97945
+ "A finished game goes out from the editor's Upload page. Every account it goes to is the person's own: their GitHub,",
97946
97946
  "their Google Play and App Store developer accounts, their AdMob. Gamesteel makes the files and walks them through each",
97947
97947
  "step; the stores' clicks, checkboxes and agreements are theirs.",
97948
97948
  "",
97949
- "## The Upload menu",
97949
+ "## The Upload page",
97950
97950
  "",
97951
97951
  "- Export: a zip whose index.html plays the game from the disk, and installs as an app once any website serves it.",
97952
97952
  "- GitHub: one repo per game, in the person's own GitHub account, which they connect in the menu; each save can go to it.",
@@ -97979,7 +97979,7 @@ var PUBLISH = [
97979
97979
  "",
97980
97980
  "A game going to the App Store or Google Play opens its privacy policy from inside itself: add a small Privacy button on",
97981
97981
  "its title or pause menu, drawn like its other buttons, that calls this.game.openPrivacy() (the scripts topic shows",
97982
- "one). The person writes and confirms the policy in the editor's Upload menu, in the App Store's wizard or Google",
97982
+ "one). The person writes and confirms the policy in the editor's Upload page, in the App Store's wizard or Google",
97983
97983
  "Play's. A game with ads gets an ads part in the policy's draft, and the policy is confirmed again whenever the",
97984
97984
  "game's ads change. A game with its own server gets an online part, and its Privacy button offers Delete my online",
97985
97985
  "data: the online topic has it. A game that sells gets a purchases part: the purchases topic has it.",
@@ -98004,7 +98004,7 @@ var PUBLISH = [
98004
98004
  " It warns of a break while the player is in the middle of play, and of a banner beside one of the game's buttons.",
98005
98005
  "- In a store app, the Privacy button also opens Ad choices, where Google's consent asks for them: the player's ad",
98006
98006
  " choices, which the law asks they can change.",
98007
- "- A game whose audience includes children under 13 (the App tab asks) is made for children: it shows no ads, every",
98007
+ "- A game whose audience includes children under 13 (Upload \u203A App asks) is made for children: it shows no ads, every",
98008
98008
  " ad is refused, and its builds carry no ad library. Never place ads in a game for children.",
98009
98009
  "- An APK to try and the Xcode project download show Google's test ads, whatever ids the person gave; a release shows",
98010
98010
  " their real ones. Tell the person never to tap their own real ads, even to test: AdMob can close the account. They",
@@ -98029,7 +98029,7 @@ var ONLINE = () => [
98029
98029
  "",
98030
98030
  "A game's own server does what one device can't: a leaderboard, a save that follows the player, players together, an",
98031
98031
  "outside service with a secret key. It is the person's choice and money: the person turns it on, sizes it, pays for it",
98032
- "and publishes it in the editor's Online tab. You write its code; you never turn it on, resize it, spend or publish.",
98032
+ "and publishes it on the editor's Server page. You write its code; you never turn it on, resize it, spend or publish.",
98033
98033
  "",
98034
98034
  "## The code",
98035
98035
  "",
@@ -98040,7 +98040,7 @@ var ONLINE = () => [
98040
98040
  " is given the player (an anonymous id, checked before the code runs), the data the game sent, and ctx; what it returns",
98041
98041
  " is the answer's data, and what it throws the answer's error, in its words.",
98042
98042
  "- ctx.db is the game's own Postgres database, once the person turns it on. ctx.secrets holds the game's secrets by",
98043
- " name, whose values the person sets in the Online tab: never write a key in code. The rest of ctx is below.",
98043
+ " name, whose values the person sets on the Server page: never write a key in code. The rest of ctx is below.",
98044
98044
  "- Check every field a player sends, its type and its range, as the patterns do.",
98045
98045
  "",
98046
98046
  "The server API, exactly as server code is checked against it:",
@@ -98064,16 +98064,16 @@ var ONLINE = () => [
98064
98064
  "",
98065
98065
  "- Every table keeps the player's id in a player_id column. Delete my online data, behind the game's Privacy button,",
98066
98066
  " deletes every row whose player_id is the player's, then runs onForget, where the code deletes anything else it keeps",
98067
- " of them. The Online tab marks a table with no player_id.",
98067
+ " of them. The Server page marks a table with no player_id.",
98068
98068
  "- The privacy policy and the store forms follow the game's online, as they follow its ads: the person confirms them",
98069
- " again in the Upload menu's wizards when its server, database, live rooms, files, push or purchases change (the",
98069
+ " again in the Upload page's wizards when its server, database, live rooms, files, push or purchases change (the",
98070
98070
  " publish topic).",
98071
98071
  "- onPurchase, onRevoke and onReward hear what players buy and the ads they watch for a reward, each checked first",
98072
98072
  " with its store or AdMob: the purchases topic has them.",
98073
98073
  "",
98074
98074
  "## Players' files",
98075
98075
  "",
98076
- "- ctx.files keeps the game's files beside its database, once the person turns them on in the Online tab: a player's",
98076
+ "- ctx.files keeps the game's files beside its database, once the person turns them on, on the Server page: a player's",
98077
98077
  " under players/<their id>/, the game's own under game/; a key is at most 512 bytes, with no '..'.",
98078
98078
  "- this.game.online.upload('saveLevel', file, data) sends a file of plain data (at most 1,000,000 bytes as JSON);",
98079
98079
  " uploadScreen('saveShot', data) sends the frame as a PNG. The page asks that call with { size, type, data }; the call",
@@ -98094,15 +98094,15 @@ var ONLINE = () => [
98094
98094
  "- Ask after a moment that shows why (a match ended, a level shared), never at launch. Push is never needed to play,",
98095
98095
  " never an ad the player didn't agree to, never a reward for saying yes (App Store Review Guideline 4.5.4, Google",
98096
98096
  " Play's notifications policy).",
98097
- "- The person's steps, in their own Firebase project, which the Online tab says: add the game's Android and Apple apps;",
98097
+ "- The person's steps, in their own Firebase project, which the Server page says: add the game's Android and Apple apps;",
98098
98098
  " Apple's push key goes into Project settings \u203A Cloud Messaging; the service account and the two app files go into",
98099
- " the Online tab, and each app build carries its own.",
98099
+ " the Server page, and each app build carries its own.",
98100
98100
  "",
98101
98101
  "## Test and live",
98102
98102
  "",
98103
- "- The editor's Game tab plays the game against its test server, and players against live. Each save that changes the",
98103
+ "- Playing in the editor runs the game against its test server, and players against live. Each save that changes the",
98104
98104
  " server code goes to test a moment later; the person sends test's code to players with Publish to players in the",
98105
- " Online tab, and can undo it there. Test and live each have their own database and secrets: test's data is never",
98105
+ " Server page, and can undo it there. Test and live each have their own database and secrets: test's data is never",
98106
98106
  " players'.",
98107
98107
  "- Old apps keep calling: ctx.version says which, so a change can answer them the old way.",
98108
98108
  "",
@@ -98122,13 +98122,13 @@ var ONLINE = () => [
98122
98122
  " person turns on, and where.",
98123
98123
  "- app online_logs reads the server's logs, stage test or live.",
98124
98124
  "- app online_query reads the game's data with one read-only SQL statement: here, where playtests write it, unless",
98125
- " stage says test, or live once the person allows it in the Online tab.",
98125
+ " stage says test, or live once the person allows it on the Server page.",
98126
98126
  "",
98127
98127
  "## What it costs",
98128
98128
  "",
98129
98129
  `- Online is paid by use from the workspace's wallet, apart from its plan: the person adds credit on the Plan page, and`,
98130
98130
  ` it turns on with $10 or more there. A small game's month (about 10,000 calls a day, its database awake two hours a`,
98131
- ` day) costs about ${dollars(priceOf(TYPICAL_MONTH))}; the Online tab and the Plan page give every price.`,
98131
+ ` day) costs about ${dollars(priceOf(TYPICAL_MONTH))}; the Server page and the Plan page give every price.`,
98132
98132
  "- Each size holds its ceilings (calls a second, players at once, work a call, the database's compute and data); a call",
98133
98133
  " past them answers busy.",
98134
98134
  "- When the wallet runs out, the game's server pauses: the game keeps running, its calls answer resting, and nothing",
@@ -98136,8 +98136,8 @@ var ONLINE = () => [
98136
98136
  "",
98137
98137
  "## Games made for children",
98138
98138
  "",
98139
- "- A game made for children (the App tab asks) keeps its live rooms, player files and push off until the person confirms,",
98140
- " in the Online tab, that each is designed to be safe. Design them so: no free text between players, only preset",
98139
+ "- A game made for children (Upload \u203A App asks) keeps its live rooms, player files and push off until the person confirms,",
98140
+ " on the Server page, that each is designed to be safe. Design them so: no free text between players, only preset",
98141
98141
  " phrases, emotes and moves; no name a player types; nothing that says who or where a player is; files only the game",
98142
98142
  " makes, like a level; notifications that never sell and never reward.",
98143
98143
  "- Keep no more of anyone than the game needs.",
@@ -98243,10 +98243,10 @@ var PURCHASES = [
98243
98243
  "- Google's check uses the workspace's Play key file, the one that uploads: its account needs View financial data and",
98244
98244
  " Manage orders in Play Console \u203A Users and permissions. Until the workspace's owner adds it under Store access,",
98245
98245
  " Google's purchases are checked on the phone.",
98246
- "- Refunds and renewals reach the game's own addresses, which the Online tab gives. Apple's: the App Store wizard's",
98246
+ "- Refunds and renewals reach the game's own addresses, which the Server page gives. Apple's: the App Store wizard's",
98247
98247
  " Purchases step sets it. Google's: the person makes a Pub/Sub topic in their Google Cloud project, names it in Play",
98248
98248
  " Console \u203A Monetize \u203A Monetization setup, and adds a push subscription to the game's address, authenticating as the",
98249
- " service account of the workspace's Play key file; a notice sent as anyone else is refused. The Online tab says each",
98249
+ " service account of the workspace's Play key file; a notice sent as anyone else is refused. The Server page says each",
98250
98250
  " step.",
98251
98251
  "",
98252
98252
  "## Checked rewards",
@@ -98256,7 +98256,7 @@ var PURCHASES = [
98256
98256
  " reward, ctx) hears it once: { placement, amount, item, transaction }. Give the reward there; the game asks its",
98257
98257
  " server for it.",
98258
98258
  "- The person pastes that address into each rewarded ad unit in AdMob, under Advanced settings \u203A Server-side",
98259
- " verification; the Online tab gives it. Only rewards from the game's own rewarded ad units (Upload \u203A Ads) count: AdMob",
98259
+ " verification; the Server page gives it. Only rewards from the game's own rewarded ad units (Upload \u203A Ads) count: AdMob",
98260
98260
  " signs every account's alike. AdMob's call can come a few seconds after the ad ends: ask when the ad says rewarded,",
98261
98261
  " and once more a moment later.",
98262
98262
  "- The server keeps each reward under the player's id, which Delete my online data deletes too.",
@@ -98273,7 +98273,7 @@ var PURCHASES = [
98273
98273
  "",
98274
98274
  "## Testing",
98275
98275
  "",
98276
- "- In the editor's Game tab each buy is a card the person answers: Bought, Cancelled or Pending. With the test server",
98276
+ "- When the person plays in the editor, each buy is a card they answer: Bought, Cancelled or Pending. With the test server",
98277
98277
  " granting purchases, Bought reaches its onPurchase as a test purchase, and a reward watched reaches its onReward.",
98278
98278
  "- A playtest buys by itself, granted as a test purchase, and a restore finds nothing owned; with onPurchase, the buy",
98279
98279
  " goes to the server on this machine first. The answer lists each, like purchases: buy coins_100 granted at 1.2 s,",
@@ -98488,7 +98488,7 @@ var GUIDE_TOPICS = [
98488
98488
  { name: "model", about: "art model: 3D art Blender builds from a script, when asked for 3D", text: () => MODEL.join("\n") },
98489
98489
  { name: "team", about: "working beside teammates and their agents: board, parts, claims, notes", text: () => TEAM.join("\n") },
98490
98490
  { name: "film", about: "films: levels that play themselves, cutscenes, chapters, render to MP4", text: () => FILM.join("\n") },
98491
- { name: "publish", about: "the Upload menu: export, GitHub, app builds, stores, privacy, ads", text: () => PUBLISH.join("\n") },
98491
+ { name: "publish", about: "the Upload page: export, GitHub, app builds, stores, privacy, ads", text: () => PUBLISH.join("\n") },
98492
98492
  { name: "online", about: "a game's own server: code, database, live rooms, test and live, costs", text: () => ONLINE().join("\n") },
98493
98493
  { name: "purchases", about: "selling in store apps, and ad rewards the server verifies", text: () => PURCHASES.join("\n") }
98494
98494
  ];
@@ -106796,7 +106796,7 @@ function readArt2(args2, ctx) {
106796
106796
 
106797
106797
  ${painting}` : modelled !== null ? `${head}, modelled in 3D.${earlier} its model, to change with art model's edit or give whole again, or to render again with art model and its name alone:
106798
106798
 
106799
- ${modelled}` : by === "code" ? `${head}, made with code in the Art tab (the person's Make with code).${earlier} paint it with art paint for a finished look` : `${head}, imported, not painted: the person's own, never replaced`;
106799
+ ${modelled}` : by === "code" ? `${head}, made with code in the Art view (the person's Make with code).${earlier} paint it with art paint for a finished look` : `${head}, imported, not painted: the person's own, never replaced`;
106800
106800
  const shown3 = answer(body3, { name: `own:${name2}`, kind: entry.kind, painted: painting !== null, modelled: modelled !== null });
106801
106801
  return { ...shown3, content: [...shown3.content, ...picture5] };
106802
106802
  }
@@ -107254,7 +107254,7 @@ var APP_TOOLS = [
107254
107254
  },
107255
107255
  {
107256
107256
  name: "app_online",
107257
- description: "the game's own server (guide online): sizes, today's calls and errors, cost and wallet, versions in use; what the person turns on in the editor's Online tab.",
107257
+ description: "the game's own server (guide online): sizes, today's calls and errors, cost and wallet, versions in use; what the person turns on, on the editor's Server page.",
107258
107258
  inputSchema: object2({ game: GAME }, ["game"]),
107259
107259
  handle: (args2, ctx) => sent(args2, ctx, { action: "online" }, ONLINE2)
107260
107260
  },
@@ -107266,7 +107266,7 @@ var APP_TOOLS = [
107266
107266
  },
107267
107267
  {
107268
107268
  name: "app_online_query",
107269
- description: "one read-only SQL statement on its data: here (playtests'), test, or live once the person allows it in the Online tab.",
107269
+ description: "one read-only SQL statement on its data: here (playtests'), test, or live once the person allows it in the Server page.",
107270
107270
  inputSchema: object2({
107271
107271
  game: GAME,
107272
107272
  sql: { type: "string", minLength: 1, maxLength: 4e3, description: "one statement, like select player_id, score from scores" },
@@ -107337,7 +107337,7 @@ var GITHUB_TOOLS = [
107337
107337
  // ../agent/dist/film-tools.js
107338
107338
  var NEEDS_PROGRAM8 = "rendering video runs in the Gamesteel program on your machine, which asks the editor with the game open: add it to your agent with npx gamesteel@latest connect <agent>";
107339
107339
  var STARTED = "Rendering started in the editor where the game is open. It renders each film with no video, or an old one, once for each of the game's languages, and keeps the videos with the game; each answer about the game says how it goes, and how it ended.";
107340
- var NONE = "No editor has this game open: ask the person to open it in the editor and press Render video in the Game tab's Films, or to keep it open and ask you again.";
107340
+ var NONE = "No editor has this game open: ask the person to open it in the editor and press Render video in Upload \u203A Films, or to keep it open and ask you again.";
107341
107341
  var FILM_TOOLS = [
107342
107342
  {
107343
107343
  name: "render_video",
@@ -112085,6 +112085,110 @@ function studioBackend(run3, session, headers = {}, timings = {}) {
112085
112085
 
112086
112086
  // ../studio/dist/server/http.js
112087
112087
  import { randomBytes as randomBytes3, timingSafeEqual } from "node:crypto";
112088
+
112089
+ // ../design/dist/tokens.js
112090
+ var PALETTE2 = {
112091
+ "bg": "#050607",
112092
+ /** A panel's solid stand-in, and what the scene canvas reads. */
112093
+ "surface": "#101216",
112094
+ /** Raised, solid: banners, and floating glass where blur is missing. */
112095
+ "surface-2": "#181B20",
112096
+ /** The code editor's well. */
112097
+ "field": "#08090B",
112098
+ /** The scene canvas's grid and frame; glass draws its hairlines in white. */
112099
+ "line": "#232833",
112100
+ "line-strong": "#2E3440",
112101
+ "text": "#F3F5F8",
112102
+ "text-2": "#B0B6C0",
112103
+ "text-3": "#999FAA",
112104
+ /** The editor's mint. The renderer's palette and the chrome still agree. */
112105
+ "accent": "#5ED3A0",
112106
+ /** The top of the primary button's gradient, where light catches it. */
112107
+ "accent-hi": "#8EF2C7",
112108
+ /** Words on an accent fill. */
112109
+ "accent-ink": "#06281A",
112110
+ /** Accent chips, solid. */
112111
+ "accent-soft": "#163A2C",
112112
+ /** The AI's colour in the editor: its edits, its outlines, its entries in
112113
+ * the console. Reserved for it, so a person always knows which changes
112114
+ * were not theirs. */
112115
+ "ai": "#8EAEFF",
112116
+ /** The forge's red: the landing page's room glows in it (the owner, 2026-10-12: "put the glow on red"), and it is
112117
+ * the site's accent where the editor has its mint. */
112118
+ "forge": "#EC1B1C",
112119
+ /** The forge's red, light enough to read as words: the site's code and its accent text. */
112120
+ "forge-hi": "#FF6B5E",
112121
+ /** Words on a forge fill. */
112122
+ "forge-ink": "#2B0705",
112123
+ /** The forge's red as the editor's words: lighter than forge-hi, so it reads at 4.5:1 on the editor's lightest
112124
+ * grounds (a hovered row, the selection and the glass over a picture, in a lit room). */
112125
+ "forge-text": "#FF867B",
112126
+ /** What is well and live: the editor's Server with a version live, a teammate in the editor (2026-10-27). */
112127
+ "good": "#6EDB9A",
112128
+ "warn": "#F2B65E",
112129
+ "warn-soft": "#3A2C14",
112130
+ "bad": "#F28A92",
112131
+ "bad-soft": "#3D1D22"
112132
+ };
112133
+ var SPARK = {
112134
+ white: "#FFF3D6",
112135
+ yellow: "#FFC24A",
112136
+ orange: "#FF7A1C",
112137
+ red: "#B8320F"
112138
+ };
112139
+ var CHROME = [
112140
+ [0, "#FFFFFF"],
112141
+ [14, "#FFFFFF"],
112142
+ [30, "#E6E9EE"],
112143
+ [44, "#A9AFB9"],
112144
+ [50, "#3D4148"],
112145
+ [56, "#5D636C"],
112146
+ [66, "#C7CCD4"],
112147
+ [74, "#F0D2B6"],
112148
+ [84, "#FF9A4A"],
112149
+ [100, SPARK.orange]
112150
+ ];
112151
+ var GRAIN_ALPHA = 0.06;
112152
+ var GRAIN = `url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='g'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='2' stitchTiles='stitch'/%3E%3CfeColorMatrix values='0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 ${String(GRAIN_ALPHA)} 0'/%3E%3C/filter%3E%3Crect width='160' height='160' filter='url(%23g)'/%3E%3C/svg%3E")`;
112153
+ var ICON = (paths) => `url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2.2' stroke-linecap='round' stroke-linejoin='round'%3E${paths}%3C/svg%3E")`;
112154
+ var ICON_ALERT = ICON("%3Cpath d='m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3'/%3E%3Cpath d='M12 9v4'/%3E%3Cpath d='M12 17h.01'/%3E");
112155
+ var ICON_CHECK = ICON("%3Cpath d='M20 6 9 17l-5-5'/%3E");
112156
+ var CHEVRON = `url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%23${PALETTE2["text-2"].slice(1)}' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E")`;
112157
+
112158
+ // ../studio/dist/loading-mark.js
112159
+ var LOADING_MARK = ["svg", { class: "ed-loading-mark", viewBox: "0 0 160 100", width: "160", height: "100", "aria-hidden": "true", focusable: "false" }, [
112160
+ ["defs", {}, [
112161
+ [
112162
+ "linearGradient",
112163
+ { id: "ed-loading-chrome", gradientUnits: "userSpaceOnUse", x1: "0", y1: "28", x2: "0", y2: "72" },
112164
+ CHROME.map(([at5, hex5]) => ["stop", { offset: `${String(at5)}%`, "stop-color": hex5 }])
112165
+ ],
112166
+ ["radialGradient", { id: "ed-loading-heat" }, [
112167
+ ["stop", { offset: "0%", "stop-color": SPARK.orange, "stop-opacity": "0.75" }],
112168
+ ["stop", { offset: "55%", "stop-color": SPARK.red, "stop-opacity": "0.25" }],
112169
+ ["stop", { offset: "100%", "stop-color": SPARK.red, "stop-opacity": "0" }]
112170
+ ]],
112171
+ ["filter", { id: "ed-loading-melt", filterUnits: "userSpaceOnUse", x: "0", y: "0", width: "160", height: "100" }, [
112172
+ ["feGaussianBlur", { in: "SourceGraphic", stdDeviation: "4.5" }],
112173
+ ["feColorMatrix", { values: "1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 20 -8" }]
112174
+ ]]
112175
+ ]],
112176
+ ["ellipse", { class: "ed-loading-heat", cx: "80", cy: "78", rx: "58", ry: "12", fill: "url(#ed-loading-heat)" }],
112177
+ ["g", { filter: "url(#ed-loading-melt)", fill: "url(#ed-loading-chrome)" }, [
112178
+ ["circle", { class: "ed-loading-drop is-left", cx: "67", cy: "50", r: "20" }],
112179
+ ["circle", { class: "ed-loading-drop is-right", cx: "95", cy: "51", r: "16" }]
112180
+ ]],
112181
+ ["ellipse", { class: "ed-loading-shine is-left", cx: "60", cy: "39", rx: "7", ry: "2.6", fill: "#FFFFFF" }],
112182
+ ["ellipse", { class: "ed-loading-shine is-right", cx: "90", cy: "42", rx: "5", ry: "2", fill: "#FFFFFF" }]
112183
+ ]];
112184
+ var attribute = (text3) => text3.replaceAll("&", "&amp;").replaceAll('"', "&quot;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
112185
+ function markupOf([tag, attributes, inside2 = []]) {
112186
+ const named3 = Object.entries(attributes).map(([name2, value]) => ` ${name2}="${attribute(value)}"`).join("");
112187
+ return inside2.length === 0 ? `<${tag}${named3}/>` : `<${tag}${named3}>${inside2.map(markupOf).join("")}</${tag}>`;
112188
+ }
112189
+ var LOADING_PAGE = `<div class="ed-loading"><span class="ed-loading-art">${markupOf(LOADING_MARK)}</span><div class="ed-loading-words"></div><div class="ed-loading-bar"></div><div class="ed-loading-slow"></div></div>`;
112190
+
112191
+ // ../studio/dist/server/http.js
112088
112192
  import { createServer as createServer2 } from "node:http";
112089
112193
  function paramsOf(pattern, path) {
112090
112194
  if (!pattern.includes(":") && !pattern.endsWith("/*"))
@@ -113317,7 +113421,7 @@ function editRoutes(manager, options = {}) {
113317
113421
  method: "GET",
113318
113422
  path: `${base}/scripts`,
113319
113423
  // The game's scripts as files (the free engine spec, §2.5), each with
113320
- // what the type checker found in it, for the Code tab and the Console.
113424
+ // what the type checker found in it, for the Code view and the Console.
113321
113425
  handle: (_req, res, _body, params) => {
113322
113426
  game(res, params["slug"], (s5) => {
113323
113427
  const version2 = s5.version;
@@ -114942,7 +115046,7 @@ ${said4}` };
114942
115046
  }
114943
115047
  /** What a frame of this game is drawn with: the library and the game's
114944
115048
  * own art, each image by its file, and the HUD in the game's style, as
114945
- * the Game tab draws it. */
115049
+ * the editor's run draws it. */
114946
115050
  artOf(slug) {
114947
115051
  const { store } = this.options.manager;
114948
115052
  this.library ??= libraryCatalogue().map((entry) => ({ ...entry, src: join18(LIBRARY_ROOT2, entry.src) }));
@@ -116141,73 +116245,6 @@ import { dirname as dirname8, join as join20, resolve as resolve3 } from "node:p
116141
116245
  import { promisify } from "node:util";
116142
116246
  import { gzip } from "node:zlib";
116143
116247
 
116144
- // ../design/dist/tokens.js
116145
- var PALETTE2 = {
116146
- "bg": "#050607",
116147
- /** A panel's solid stand-in, and what the scene canvas reads. */
116148
- "surface": "#101216",
116149
- /** Raised, solid: banners, and floating glass where blur is missing. */
116150
- "surface-2": "#181B20",
116151
- /** The code editor's well. */
116152
- "field": "#08090B",
116153
- /** The scene canvas's grid and frame; glass draws its hairlines in white. */
116154
- "line": "#232833",
116155
- "line-strong": "#2E3440",
116156
- "text": "#F3F5F8",
116157
- "text-2": "#B0B6C0",
116158
- "text-3": "#999FAA",
116159
- /** The editor's mint. The renderer's palette and the chrome still agree. */
116160
- "accent": "#5ED3A0",
116161
- /** The top of the primary button's gradient, where light catches it. */
116162
- "accent-hi": "#8EF2C7",
116163
- /** Words on an accent fill. */
116164
- "accent-ink": "#06281A",
116165
- /** Accent chips, solid. */
116166
- "accent-soft": "#163A2C",
116167
- /** The AI's colour in the editor: its edits, its outlines, its entries in
116168
- * the console. Reserved for it, so a person always knows which changes
116169
- * were not theirs. */
116170
- "ai": "#8EAEFF",
116171
- /** The forge's red: the landing page's room glows in it (the owner, 2026-10-12: "put the glow on red"), and it is
116172
- * the site's accent where the editor has its mint. */
116173
- "forge": "#EC1B1C",
116174
- /** The forge's red, light enough to read as words: the site's code and its accent text. */
116175
- "forge-hi": "#FF6B5E",
116176
- /** Words on a forge fill. */
116177
- "forge-ink": "#2B0705",
116178
- /** The forge's red as the editor's words: lighter than forge-hi, so it reads at 4.5:1 on the editor's lightest
116179
- * grounds (a hovered row, the selection and the glass over a picture, in a lit room). */
116180
- "forge-text": "#FF867B",
116181
- "warn": "#F2B65E",
116182
- "warn-soft": "#3A2C14",
116183
- "bad": "#F28A92",
116184
- "bad-soft": "#3D1D22"
116185
- };
116186
- var SPARK = {
116187
- white: "#FFF3D6",
116188
- yellow: "#FFC24A",
116189
- orange: "#FF7A1C",
116190
- red: "#B8320F"
116191
- };
116192
- var CHROME = [
116193
- [0, "#FFFFFF"],
116194
- [14, "#FFFFFF"],
116195
- [30, "#E6E9EE"],
116196
- [44, "#A9AFB9"],
116197
- [50, "#3D4148"],
116198
- [56, "#5D636C"],
116199
- [66, "#C7CCD4"],
116200
- [74, "#F0D2B6"],
116201
- [84, "#FF9A4A"],
116202
- [100, SPARK.orange]
116203
- ];
116204
- var GRAIN_ALPHA = 0.06;
116205
- var GRAIN = `url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='g'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='2' stitchTiles='stitch'/%3E%3CfeColorMatrix values='0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 ${String(GRAIN_ALPHA)} 0'/%3E%3C/filter%3E%3Crect width='160' height='160' filter='url(%23g)'/%3E%3C/svg%3E")`;
116206
- var ICON = (paths) => `url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2.2' stroke-linecap='round' stroke-linejoin='round'%3E${paths}%3C/svg%3E")`;
116207
- var ICON_ALERT = ICON("%3Cpath d='m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3'/%3E%3Cpath d='M12 9v4'/%3E%3Cpath d='M12 17h.01'/%3E");
116208
- var ICON_CHECK = ICON("%3Cpath d='M20 6 9 17l-5-5'/%3E");
116209
- var CHEVRON = `url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%23${PALETTE2["text-2"].slice(1)}' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E")`;
116210
-
116211
116248
  // ../design/dist/fonts.js
116212
116249
  var resolve2 = __gamesteelResolve;
116213
116250
  var FONT_FILES = {
@@ -119649,7 +119686,7 @@ function pushFileProblem(platform, id, text3) {
119649
119686
  const file = PUSH_FILES[platform];
119650
119687
  const app = platform === "android" ? "the Android app" : "the Apple app";
119651
119688
  if (text3 === null)
119652
- return `${file} is missing: download it from the game's Firebase project (Project settings, Your apps, ${app} ${id}) and add it in the Online tab`;
119689
+ return `${file} is missing: download it from the game's Firebase project (Project settings, Your apps, ${app} ${id}) and add it on the Server page`;
119653
119690
  const ids = firebaseIds(platform, text3);
119654
119691
  if (ids === null)
119655
119692
  return `${file} is not the file Firebase gives: download it again`;
@@ -121904,7 +121941,7 @@ function settingsWords(state, builder, releases, icon) {
121904
121941
  `- icon: ${s5.icon === "title" ? "the title icon (the first letter on the HUD plate)" : s5.icon}${icon === null ? "" : `, but ${icon}`}`,
121905
121942
  `- splash: ${s5.splash}`,
121906
121943
  `- audience: ${s5.children === true ? "includes children under 13, so it shows no ads" : "does not include children under 13"}`,
121907
- state.keyHidden === true ? "Upload key: read by the owner's or an admin's program for a release build." : state.key === null ? "Upload key: none yet; a release build makes one, and Gamesteel keeps it sealed." : "Upload key: kept by Gamesteel, sealed; the workspace's owner and admins download a copy from the App tab.",
121944
+ state.keyHidden === true ? "Upload key: read by the owner's or an admin's program for a release build." : state.key === null ? "Upload key: none yet; a release build makes one, and Gamesteel keeps it sealed." : "Upload key: kept by Gamesteel, sealed; the workspace's owner and admins download a copy from Upload \u203A App.",
121908
121945
  builder,
121909
121946
  ...release,
121910
121947
  `Build targets: ${TARGETS2}.`
@@ -122209,7 +122246,7 @@ function hostedAppRoutes(options) {
122209
122246
  store.saveStoreListing(slug, listing);
122210
122247
  const problems = listingProblems(listing);
122211
122248
  const kept = `${slug}'s listing in ${language} is kept with the game.`;
122212
- const pictures = problems.some((one2) => PICTURE_PROBLEMS.has(one2)) ? "; the person makes the pictures in the editor's App and Game tabs" : "";
122249
+ const pictures = problems.some((one2) => PICTURE_PROBLEMS.has(one2)) ? "; the person makes the pictures in the editor's Upload \u203A App, and with Store screenshot while the game plays" : "";
122213
122250
  const play2 = problems.length === 0 ? " It is finished." : ` It still needs ${listingNeeds(problems)}${pictures}.`;
122214
122251
  const appleSet = APPLE_TEXT2.some((key) => set2[key] !== void 0);
122215
122252
  const update = appleSet && options.stores !== void 0 ? await options.stores.appleUpdate(slug).catch(() => false) : false;
@@ -128203,7 +128240,7 @@ Team since your last call: ${news.map((event) => event.words).join("; ")}.`;
128203
128240
  own3.delete(listener);
128204
128241
  };
128205
128242
  }
128206
- /** The editor's Team tab: the board as the person sees it, and their actions on it. */
128243
+ /** The editor's Team page: the board as the person sees it, and their actions on it. */
128207
128244
  routes() {
128208
128245
  const personOf = (req2) => {
128209
128246
  const id = requestPlaces.get(req2)?.person;
@@ -128254,7 +128291,7 @@ Team since your last call: ${news.map((event) => event.words).join("; ")}.`;
128254
128291
  }
128255
128292
  ];
128256
128293
  }
128257
- /** The board as the Team tab shows it to `viewer`. */
128294
+ /** The board as the Team page shows it to `viewer`. */
128258
128295
  async view(slug, viewer) {
128259
128296
  const board = await this.load(slug);
128260
128297
  const doc = this.docOf(slug);
@@ -135277,7 +135314,7 @@ var DeviceSite = class {
135277
135314
  const game = answer3.endpoint?.game;
135278
135315
  return answer3.open && answer3.server != null && typeof host === "string" && typeof game === "string" ? { host, game, push: answer3.push === true, purchases: answer3.purchases === true, rewards: answer3.rewards === true } : null;
135279
135316
  }
135280
- /** The game's online settings whole, as the Online tab reads them (the online services spec), for its agent's words. */
135317
+ /** The game's online settings whole, as the Server page reads them (the online services spec), for its agent's words. */
135281
135318
  async onlineState(slug) {
135282
135319
  return this.answer(await this.request("GET", `games/${slug}/online`), [200]);
135283
135320
  }
@@ -135346,7 +135383,7 @@ var DeviceSite = class {
135346
135383
  };
135347
135384
 
135348
135385
  // src/program/online-agent.ts
135349
- var TAB = "the editor's Online tab";
135386
+ var TAB = "the editor's Server page";
135350
135387
  var MOST_CHARS = 7e3;
135351
135388
  var REGIONS = {
135352
135389
  "aws-us-east-2": "Ohio",
@@ -135405,20 +135442,20 @@ var AgentOnline = class {
135405
135442
  const days = online.wallet?.daysLeft ?? null;
135406
135443
  return [
135407
135444
  `- server: ${server}: ${String(ceilings.eventsPerSecond)} calls a second, ${String(ceilings.connected)} players at once, ${String(ceilings.cpuMs)} ms of work a call`,
135408
- database === null ? "- database: off: the person turns it on in the Online tab" : `- database: ${database}: up to ${String(DATABASE_SIZES[database].maxCu)} compute units and ${String(DATABASE_SIZES[database].dataBytes / 1e9)} GB of data${online.region == null ? "" : `, in ${REGIONS[online.region] ?? online.region}`}` + (online.databaseUse == null ? "" : `; data kept: ${String(Math.round(online.databaseUse.bytes / 1e6))} MB, compute this month: ${String(Math.round(online.databaseUse.cuHours * 10) / 10)} compute-unit hours`),
135409
- online.rooms === true ? "- live rooms: on" : "- live rooms: off: a game made for children turns them on once the person confirms, in the Online tab, they are designed to be safe",
135445
+ database === null ? "- database: off: the person turns it on, on the Server page" : `- database: ${database}: up to ${String(DATABASE_SIZES[database].maxCu)} compute units and ${String(DATABASE_SIZES[database].dataBytes / 1e9)} GB of data${online.region == null ? "" : `, in ${REGIONS[online.region] ?? online.region}`}` + (online.databaseUse == null ? "" : `; data kept: ${String(Math.round(online.databaseUse.bytes / 1e6))} MB, compute this month: ${String(Math.round(online.databaseUse.cuHours * 10) / 10)} compute-unit hours`),
135446
+ online.rooms === true ? "- live rooms: on" : "- live rooms: off: a game made for children turns them on once the person confirms, on the Server page, they are designed to be safe",
135410
135447
  `- test: ${versions?.test == null ? "no code deployed yet: each save of server/main.ts goes to test a moment later" : `version ${String(versions.test)}`}`,
135411
- `- live: ${versions?.live == null ? "nothing published yet: the person sends test's code to players with Publish to players, in the Online tab" : `version ${String(versions.live)}; the person publishes test's code to players with Publish to players`}`,
135448
+ `- live: ${versions?.live == null ? "nothing published yet: the person sends test's code to players with Publish to players, on the Server page" : `version ${String(versions.live)}; the person publishes test's code to players with Publish to players`}`,
135412
135449
  `- players this week: ${players.length === 0 ? "no live calls" : players.map((one2) => `${one2.version} (${count2(one2.calls, "call")})`).join(", ")}`,
135413
135450
  `- today: ${count2(online.use?.events ?? 0, "event")}, ${count2(online.use?.calls ?? 0, "call")}; errors: ${count2(online.errors?.calls ?? 0, "call")}, ${count2(online.errors?.runs ?? 0, "run")}`,
135414
- ...(online.errors?.busy ?? 0) > 0 ? [`- ${count2(online.errors?.busy ?? 0, "call")} answered busy today: the game reaches its size's ceilings; a bigger size, which the person chooses in the Online tab, lets it do more`] : [],
135451
+ ...(online.errors?.busy ?? 0) > 0 ? [`- ${count2(online.errors?.busy ?? 0, "call")} answered busy today: the game reaches its size's ceilings; a bigger size, which the person chooses on the Server page, lets it do more`] : [],
135415
135452
  ...online.errors?.deploy == null ? [] : [`- not deployed: ${online.errors.deploy}`],
135416
135453
  `- this month: ${dollars(online.cost ?? 0)}${wallet === null ? "" : `; ${wallet}${days === null ? "" : `, about ${count2(days, "day")} at this rate`}`}`,
135417
135454
  ...online.notice == null ? [] : [`- paused since ${online.notice.pausedAt.slice(0, 10)}: the wallet ran out, its calls answer resting${online.notice.deleteOn === null ? "" : `, and its data goes on ${online.notice.deleteOn.slice(0, 10)} unless what is owed is paid`}; the person adds credit on the Plan page`],
135418
135455
  // Part 2: players' files and push, each on the database.
135419
- online.files === true ? `- player files: on: a file is at most ${grouped(FILE_CAPS[server])} bytes on this server size; kept: ${String(Math.round((online.filesUse?.bytes ?? 0) / 1e5) / 10)} MB, ` + (online.filesUse?.measured == null ? "measured each day" : `measured ${online.filesUse.measured.slice(0, 10)}`) : "- player files: off: the person turns them on in the Online tab, with the database",
135420
- online.push === true ? "- push: on, through the game's own Firebase project: each push costs an event; never needed to play, never a reward for saying yes" : "- push: off: the person turns it on in the Online tab, with the game's own Firebase project and the database",
135421
- `- live players' data: ${online.liveForAgent === true ? "the person lets the agent read it" : "read only once the person lets the agent, in the Online tab"}`
135456
+ online.files === true ? `- player files: on: a file is at most ${grouped(FILE_CAPS[server])} bytes on this server size; kept: ${String(Math.round((online.filesUse?.bytes ?? 0) / 1e5) / 10)} MB, ` + (online.filesUse?.measured == null ? "measured each day" : `measured ${online.filesUse.measured.slice(0, 10)}`) : "- player files: off: the person turns them on, on the Server page, with the database",
135457
+ online.push === true ? "- push: on, through the game's own Firebase project: each push costs an event; never needed to play, never a reward for saying yes" : "- push: off: the person turns it on, on the Server page, with the game's own Firebase project and the database",
135458
+ `- live players' data: ${online.liveForAgent === true ? "the person lets the agent read it" : "read only once the person lets the agent, on the Server page"}`
135422
135459
  ];
135423
135460
  }
135424
135461
  async logs(slug, stage) {
@@ -136282,7 +136319,7 @@ var KEEP = 5;
136282
136319
  var LOG_MOST = 200;
136283
136320
  var TARGETS3 = ["android-debug", "android-release", "ios-project", "ios-release"];
136284
136321
  var CLOSED = "the program closed while this was being built";
136285
- var KEY_NOTICE = "Gamesteel keeps this app's upload key on the website, sealed. The workspace's owner and admins download a copy from the App tab: keep it safe, as Google Play takes updates to the app only when they are signed with it.";
136322
+ var KEY_NOTICE = "Gamesteel keeps this app's upload key on the website, sealed. The workspace's owner and admins download a copy from Upload \u203A App: keep it safe, as Google Play takes updates to the app only when they are signed with it.";
136286
136323
  var MEMBERS_RELEASE = "release builds run from the workspace owner's or an admin's program, which signs them with the key the website keeps: ask one of them to build android-release";
136287
136324
  var timeWords = (ms) => ms < 6e4 ? `${String(ms / 1e3)} seconds` : `${String(Math.round(ms / 6e4))} minutes`;
136288
136325
  var ranPast = (ms) => `the build ran longer than ${timeWords(ms)} and was stopped: try again`;
@@ -136402,7 +136439,7 @@ var DeviceApps = class {
136402
136439
  const path = join34(this.folder, slug, name2);
136403
136440
  return existsSync22(path) ? new Uint8Array(readFileSync30(path)) : null;
136404
136441
  }
136405
- /** The key never leaves the website through the program: its owner and admins download it from the App tab. */
136442
+ /** The key never leaves the website through the program: its owner and admins download it from Upload › App. */
136406
136443
  async keyZip() {
136407
136444
  return null;
136408
136445
  }
@@ -136542,7 +136579,7 @@ var DeviceApps = class {
136542
136579
  const made3 = await this.options.site.storeState(slug, store).then((state) => state.products?.fingerprint ?? null, () => null);
136543
136580
  if (made3 !== purchasesFingerprint(sold)) {
136544
136581
  const count3 = sold.products.length;
136545
- noSale = `this release sells ${String(count3)} product${count3 === 1 ? "" : "s"} not yet made in ${appStore ? "the App Store" : "Google Play"}: make them in the editor's Upload \u203A ${appStore ? "App Store" : "Play Store"}, in its Purchases step`;
136582
+ noSale = `this release sells ${String(count3)} product${count3 === 1 ? "" : "s"} not yet made in ${appStore ? "the App Store" : "Google Play"}: make them in the editor's Upload \u203A ${appStore ? "App Store" : "Google Play"}, in its Purchases step`;
136546
136583
  }
136547
136584
  }
136548
136585
  const checkedAds = server?.rewards === true && ads.exported !== void 0 ? { ...ads, exported: { ...ads.exported, rewardsChecked: true } } : ads;
@@ -138079,7 +138116,7 @@ function appleNextStep(wizard, facts) {
138079
138116
  const { state } = facts;
138080
138117
  if (!facts.connected) return `the person gives their App Store Connect key (an Admin team key) in ${wizard}`;
138081
138118
  if (state.app === null) return `the person creates the app in App Store Connect (New App, iOS, the bundle id ${facts.bundleId}); app store_check then finds it`;
138082
- if (!facts.listed) return "finish the listing: app store_listing sets its text; the person makes its App Store screenshots in the editor's Game tab and gives App Review's contact in the wizard";
138119
+ if (!facts.listed) return "finish the listing: app store_listing sets its text; the person makes its App Store screenshots with Store screenshot while the game plays in the editor and gives App Review's contact in the wizard";
138083
138120
  if (!facts.confirmed) return `the person reads, edits and confirms the privacy policy as theirs in ${wizard}`;
138084
138121
  if (!facts.opens) return "add a small Privacy button that calls this.game.openPrivacy() (the playbook's privacy button)";
138085
138122
  if (!answersComplete(state.answers)) return `the person answers Apple's age rating questions in ${wizard}`;
@@ -138186,7 +138223,7 @@ var AppleWork = class {
138186
138223
  }
138187
138224
  async settingsOf(slug) {
138188
138225
  const settings = await this.kit.settings(slug);
138189
- if (settings === null) throw new Refused3(`${slug} has no app settings yet: set the app up in the editor's App tab first`);
138226
+ if (settings === null) throw new Refused3(`${slug} has no app settings yet: set the app up in the editor's Upload \u203A App first`);
138190
138227
  return settings;
138191
138228
  }
138192
138229
  /** The person's app on App Store Connect, recorded once found; null before they made it. */
@@ -138432,7 +138469,7 @@ var ADS_TXT_KEPT_MS = 3e4;
138432
138469
  function nextStep(wizard, facts, now) {
138433
138470
  const { state } = facts;
138434
138471
  if (!facts.connected) return `the person signs in with Google, or the workspace's owner gives a key file, in ${wizard}`;
138435
- if (!facts.listed) return "finish the listing: app store_listing sets its text; the person makes its pictures in the editor's App and Game tabs";
138472
+ if (!facts.listed) return "finish the listing: app store_listing sets its text; the person makes its pictures in the editor's Upload \u203A App, and with Store screenshot while the game plays";
138436
138473
  if (!facts.confirmed) return `the person reads, edits and confirms the privacy policy as theirs in ${wizard}`;
138437
138474
  if (!facts.opens) return "add a small Privacy button that calls this.game.openPrivacy() (the playbook's privacy button)";
138438
138475
  if (state.upload === null) return "upload it to testing with app store_upload";
@@ -138493,7 +138530,7 @@ var DeviceStores = class _DeviceStores {
138493
138530
  return this.options.clock?.() ?? /* @__PURE__ */ new Date();
138494
138531
  }
138495
138532
  wizard(slug) {
138496
- return `the editor's Upload \u203A Play Store at ${this.siteUrl}/editor?game=${slug}`;
138533
+ return `the editor's Upload \u203A Google Play at ${this.siteUrl}/editor?game=${slug}`;
138497
138534
  }
138498
138535
  /** What the store ask does, in words; `from` the agent or the person's click in the editor. */
138499
138536
  async act(slug, ask, from, report) {
@@ -138657,7 +138694,7 @@ ${await this.apple.state(slug)}`;
138657
138694
  /** What every upload and submission needs first: the listing, the privacy policy, and the game opening it. */
138658
138695
  async ready(slug) {
138659
138696
  const settings = await this.options.apps.settingsOf(this.options.workspaceId(), slug, this.game(slug));
138660
- if (settings === null) throw new Refused4(`${slug} has no app settings yet: set the app up in the editor's App tab first`);
138697
+ if (settings === null) throw new Refused4(`${slug} has no app settings yet: set the app up in the editor's Upload \u203A App first`);
138661
138698
  this.appIds.set(slug, settings.id);
138662
138699
  const listing = this.listingOf(slug);
138663
138700
  const problems = listingProblems(listing);
@@ -138760,7 +138797,7 @@ Warning: ${checked3.words}`;
138760
138797
  async products(slug) {
138761
138798
  const { site, apps } = this.options;
138762
138799
  const settings = await apps.settingsOf(this.options.workspaceId(), slug, this.game(slug));
138763
- if (settings === null) throw new Refused4(`${slug} has no app settings yet: set the app up in the editor's App tab first`);
138800
+ if (settings === null) throw new Refused4(`${slug} has no app settings yet: set the app up in the editor's Upload \u203A App first`);
138764
138801
  this.appIds.set(slug, settings.id);
138765
138802
  const kept = await sellable(slug, this.options.sessions().store.loadPurchases(slug), settings, site);
138766
138803
  if (typeof kept === "string") throw new Refused4(kept);
@@ -138858,7 +138895,7 @@ Warning: ${checked3.words}`;
138858
138895
  async check(slug) {
138859
138896
  const { site } = this.options;
138860
138897
  const settings = await this.options.apps.settingsOf(this.options.workspaceId(), slug, this.game(slug));
138861
- if (settings === null) throw new Refused4(`${slug} has no app settings yet: set the app up in the editor's App tab first`);
138898
+ if (settings === null) throw new Refused4(`${slug} has no app settings yet: set the app up in the editor's Upload \u203A App first`);
138862
138899
  this.appIds.set(slug, settings.id);
138863
138900
  const { client } = await this.access(slug);
138864
138901
  if (!await client.exists(settings.id)) {
@@ -139471,7 +139508,7 @@ async function startProgram(options) {
139471
139508
  ...known2.team === null ? {} : { team: { board: remoteBoard(site), names: remoteNames(site) } },
139472
139509
  orderer: (slug) => siteOrderer({ site, slug, grantId }),
139473
139510
  models: machineModels(options.models),
139474
- // The plan's rule for an app build is the website's, read here; the key is downloaded only from the App tab.
139511
+ // The plan's rule for an app build is the website's, read here; the key is downloaded only from Upload › App.
139475
139512
  apps,
139476
139513
  appOptions: {
139477
139514
  mayExport: async () => (await plan()).limits.export,