@mpgd/cli 0.20.1 → 0.22.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. package/dist/index.d.ts +2 -1
  2. package/dist/index.js +92 -8
  3. package/dist/microsoft-store-package-generation-contract.d.ts +5 -0
  4. package/dist/microsoft-store-package-generation-input.js +55 -0
  5. package/dist/microsoft-store-starter.d.ts +7 -0
  6. package/dist/microsoft-store-starter.js +176 -4
  7. package/dist/microsoft-store-submission.d.ts +24 -3
  8. package/dist/microsoft-store-submission.js +131 -4
  9. package/dist/offline-playtest.d.ts +31 -0
  10. package/dist/offline-playtest.js +5718 -0
  11. package/dist/production-target-readiness.d.ts +4 -0
  12. package/dist/production-target-readiness.js +40 -12
  13. package/package.json +15 -13
  14. package/templates/phaser-game/README.md +62 -22
  15. package/templates/phaser-game/apps/target-ait/apps-in-toss.config.ts +12 -0
  16. package/templates/phaser-game/apps/target-ait/package.json +5 -8
  17. package/templates/phaser-game/apps/target-ait/vite.config.ts +6 -15
  18. package/templates/phaser-game/apps/target-cloudflare-pages/package.json +1 -1
  19. package/templates/phaser-game/apps/target-devvit/README.md +2 -1
  20. package/templates/phaser-game/apps/target-devvit/package.json +1 -1
  21. package/templates/phaser-game/apps/target-devvit/src/server/index.ts +1 -0
  22. package/templates/phaser-game/docs/MPGD_KIT_WORKFLOWS.md +11 -0
  23. package/templates/phaser-game/mpgd.targets.json +8 -1
  24. package/templates/phaser-game/package.json +2 -5
  25. package/templates/phaser-game/pnpm-workspace.yaml +3 -2
  26. package/templates/phaser-game/src/entry.ts +10 -4
  27. package/templates/phaser-game/src/gameEntry.ts +7 -1
  28. package/templates/phaser-game/src/platform/buildGateways/microsoftStore.ts +37 -0
  29. package/templates/phaser-game/src/platform/runtimeDetector.ts +1 -0
  30. package/templates/phaser-game/src/runtime/renderEntryFailure.ts +24 -0
  31. package/templates/phaser-game/vite.runtime-target-config.ts +2 -0
  32. package/templates/phaser-game/vite.shared.ts +6 -2
@@ -9,10 +9,14 @@ export interface ProductionTargetReadinessInput {
9
9
  export interface ProductionTargetPolicy {
10
10
  readonly runtime: string;
11
11
  readonly features: {
12
+ readonly iap: boolean;
12
13
  readonly rewardedAds: boolean;
14
+ readonly interstitialAds: boolean;
13
15
  };
14
16
  readonly monetization: {
17
+ readonly iap: boolean;
15
18
  readonly rewardedAds: boolean;
19
+ readonly interstitialAds: boolean;
16
20
  };
17
21
  }
18
22
  export declare function assertProductionTargetReadiness(input: ProductionTargetReadinessInput): void;
@@ -19,23 +19,31 @@ export function assertProductionTargetReadiness(input) {
19
19
  if (ownerPathKey === undefined && input.targetPolicy === undefined) {
20
20
  throw new Error(`Production target ${input.target} requires a target policy.`);
21
21
  }
22
- const requiresVerse8RewardAuthority = input.targetPolicy?.runtime === 'verse8-web'
23
- && (input.targetPolicy.features.rewardedAds
24
- || input.targetPolicy.monetization.rewardedAds);
25
- if (ownerPathKey === undefined && !requiresVerse8RewardAuthority) {
26
- return;
27
- }
28
22
  const targetConfig = readTargets(input.targetsFile)[input.target];
29
23
  if (!isRecord(targetConfig)) {
30
24
  throw new Error(`Missing target configuration for ${input.target}.`);
31
25
  }
26
+ const targetPolicy = targetConfig.authoritativeGameServices === false
27
+ ? disableMonetization(input.targetPolicy)
28
+ : input.targetPolicy;
29
+ const requiresVerse8RewardAuthority = targetPolicy?.runtime === 'verse8-web'
30
+ && (targetPolicy.features.rewardedAds
31
+ || targetPolicy.monetization.rewardedAds);
32
+ const requiresWebMonetizationAuthority = (targetPolicy?.runtime === 'web'
33
+ || targetPolicy?.runtime === 'microsoft-store-pwa')
34
+ && (targetPolicy.features.iap
35
+ || targetPolicy.features.rewardedAds
36
+ || targetPolicy.features.interstitialAds
37
+ || targetPolicy.monetization.iap
38
+ || targetPolicy.monetization.rewardedAds
39
+ || targetPolicy.monetization.interstitialAds);
40
+ const requiresMonetizationAuthority = requiresVerse8RewardAuthority
41
+ || requiresWebMonetizationAuthority;
42
+ if (ownerPathKey === undefined && !requiresMonetizationAuthority) {
43
+ return;
44
+ }
32
45
  if (ownerPathKey === undefined) {
33
- if (requiresVerse8RewardAuthority
34
- && targetConfig.authoritativeGameServices === false) {
35
- throw new Error(`Production target ${input.target} cannot enable rewarded ads without `
36
- + 'authoritative game services.');
37
- }
38
- if (requiresVerse8RewardAuthority || targetConfig.authoritativeGameServices !== false) {
46
+ if (requiresMonetizationAuthority || targetConfig.authoritativeGameServices !== false) {
39
47
  assertAuthoritativeGameServicesUrl(input.gameServicesUrl, input.target);
40
48
  }
41
49
  return;
@@ -73,6 +81,26 @@ export function assertProductionTargetReadiness(input) {
73
81
  assertAuthoritativeGameServicesUrl(input.gameServicesUrl, input.target);
74
82
  }
75
83
  }
84
+ function disableMonetization(policy) {
85
+ if (policy === undefined) {
86
+ return undefined;
87
+ }
88
+ return {
89
+ ...policy,
90
+ features: {
91
+ ...policy.features,
92
+ iap: false,
93
+ rewardedAds: false,
94
+ interstitialAds: false,
95
+ },
96
+ monetization: {
97
+ ...policy.monetization,
98
+ iap: false,
99
+ rewardedAds: false,
100
+ interstitialAds: false,
101
+ },
102
+ };
103
+ }
76
104
  function readTargets(targetsFile) {
77
105
  let parsed;
78
106
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mpgd/cli",
3
- "version": "0.20.1",
3
+ "version": "0.22.0",
4
4
  "description": "Gunshi CLI for mpgd-kit starter generation and target workflow orchestration.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -43,6 +43,8 @@
43
43
  "@gunshi/plugin-completion": "^0.35.1",
44
44
  "@gunshi/plugin-i18n": "^0.35.1",
45
45
  "@gunshi/resources": "^0.35.1",
46
+ "entities": "^8.0.0",
47
+ "esbuild": "^0.28.1",
46
48
  "gunshi": "^0.35.1",
47
49
  "undici": "^7.28.0"
48
50
  },
@@ -50,19 +52,19 @@
50
52
  "@types/node": "^24.0.0",
51
53
  "ttsc": "0.18.4",
52
54
  "typescript": "7.0.2",
53
- "@mpgd/adapter-ait": "0.8.0",
54
- "@mpgd/adapter-capacitor": "0.4.5",
55
- "@mpgd/adapter-devvit": "0.8.6",
56
- "@mpgd/adapter-verse8": "0.2.3",
57
- "@mpgd/analytics": "0.3.8",
58
- "@mpgd/adapter-browser": "0.5.1",
59
- "@mpgd/bridge": "0.7.0",
60
- "@mpgd/catalog": "0.5.2",
61
- "@mpgd/game-services": "0.11.1",
62
- "@mpgd/i18n": "0.5.4",
55
+ "@mpgd/adapter-ait": "0.9.0",
56
+ "@mpgd/adapter-browser": "0.6.0",
57
+ "@mpgd/adapter-capacitor": "0.4.6",
58
+ "@mpgd/adapter-devvit": "0.9.0",
59
+ "@mpgd/adapter-verse8": "0.3.0",
60
+ "@mpgd/bridge": "0.8.0",
61
+ "@mpgd/analytics": "0.3.9",
62
+ "@mpgd/game-services": "0.12.0",
63
+ "@mpgd/catalog": "0.6.0",
64
+ "@mpgd/i18n": "0.5.5",
65
+ "@mpgd/target-config": "0.12.0",
63
66
  "@mpgd/phaser-assets": "0.4.1",
64
- "@mpgd/platform": "0.8.0",
65
- "@mpgd/target-config": "0.10.1"
67
+ "@mpgd/platform": "0.9.0"
66
68
  },
67
69
  "main": "./dist/index.js",
68
70
  "types": "./dist/index.d.ts",
@@ -65,6 +65,33 @@ pnpm --dir ../mpgd-kit mpgd target build-all \
65
65
  into `.mpgd.targets.generated.json` before calling the kit's existing target
66
66
  build and smoke scripts.
67
67
 
68
+ ### Offline test-play copy
69
+
70
+ When a tester needs to open one local file without a server, package the
71
+ existing browser preview:
72
+
73
+ ```sh
74
+ pnpm exec mpgd target build web-preview staging \
75
+ --targets-file ./mpgd.targets.json \
76
+ --kit-path "${MPGD_KIT_PATH:-../mpgd-kit}"
77
+ pnpm exec mpgd game offline-playtest .
78
+ ```
79
+
80
+ Open `artifacts/offline-playtest/index.html` directly. This is a
81
+ network-blocked, test-play-only copy—not a target in `mpgd.targets.json`, PWA,
82
+ deployment artifact, release-manifest entry, or store-submission package.
83
+ Custom output directories must remain below this game's `artifacts` directory.
84
+ `README.txt` and `offline-playtest.json` preserve that boundary and record the
85
+ result hash. Server-backed login, purchases, ads, rewards, leaderboards, and
86
+ cloud saves do not work in this copy. Workers, service workers, WebAssembly
87
+ streaming, dynamic imports, import maps, HTML base elements, CSS `@import`,
88
+ runtime-computed asset URLs, retained inline-module imports, script-driven
89
+ navigation, and browser-dependent `file://` storage are outside this helper's
90
+ contract. Meta-refresh navigation is removed from the copy. glTF files must
91
+ embed their dependencies as data URIs; use GLB for models with external buffers
92
+ or textures. A successful package does not prove that every game path works
93
+ offline; keep unavailable capability fallbacks in the game.
94
+
68
95
  The starter owns its Apps in Toss wrapper under `apps/target-ait`. With
69
96
  `authoritativeGameServices: false`, production keeps native identity, storage,
70
97
  sharing, and Game Center while disabling IAP and ads; no game-services URL is
@@ -73,6 +100,10 @@ set the authority flag to `true`, add app-owned product or ad IDs, and configure
73
100
  `VITE_MPGD_GAME_SERVICES_URL` to a public HTTPS verifier without embedded
74
101
  credentials.
75
102
 
103
+ The same verifier URL is required when a deployed custom `web` runtime enables
104
+ IAP or ads. This production gate prevents client-only grants even when the
105
+ browser adapter itself is game-owned.
106
+
76
107
  The Capacitor entries still intentionally point at kit reference shells, so
77
108
  their reusable validation builds use the `staging` profile. Before an Android
78
109
  or iOS production build, copy or create the shell under this game directory and
@@ -92,7 +123,9 @@ Optional targets can add their own config, scripts, runtime wiring, and release
92
123
  skill. To enable Microsoft Store after creation, run
93
124
  `pnpm exec mpgd target init microsoft-store --game . --kit-path ../mpgd-kit`.
94
125
  The initializer is idempotent and stops before writing when it finds conflicting
95
- game-owned values.
126
+ game-owned values. It requires this generated starter's existing
127
+ `src/platform/runtimeDetector.ts` and `vite.shared.ts` files; custom layouts must
128
+ add equivalent runtime and Vite gateway routing manually.
96
129
 
97
130
  ## Acceptance Handoff
98
131
 
@@ -246,29 +279,19 @@ pnpm build:ait
246
279
  pnpm smoke:ait
247
280
  ```
248
281
 
249
- The game-owned wrapper includes the Apps in Toss community devtools package for
250
- SDK mocks and device debugging:
282
+ The game-owned wrapper provides an explicit local SDK mock for browser layout
283
+ and non-native flow checks:
251
284
 
252
285
  ```sh
253
286
  pnpm ait:wrapper:dev
254
287
  pnpm ait:wrapper:dev:plain
255
- pnpm ait:wrapper:dev:phone
256
- pnpm ait:devtools:mcp
257
- pnpm ait:devtools:mcp:mobile
258
288
  ```
259
289
 
260
- `ait:wrapper:dev` runs `apps/target-ait` with the community Vite plugin enabled.
261
- It loads the last game bundle copied by `pnpm build:ait` from the wrapper's
262
- `public/game` directory, so run `pnpm build:ait` again after game changes before
263
- opening a wrapper devtools session. Use `ait:wrapper:dev:plain` to disable the
264
- devtools plugin. `ait:wrapper:dev:phone` uses the devtools tunnel and CDP relay
265
- for phone sandbox checks, and `ait:devtools:mcp:mobile` starts the community MCP
266
- bridge in the phone tunnel mode after the wrapper prints the tunnel URLs. Use
267
- `ait:devtools:mcp` for the default local or relay debugging mode. The kit
268
- intentionally leaves
269
- `cloudflared` postinstall disabled; the first phone tunnel session downloads the
270
- binary lazily through the community devtools package instead of during ordinary
271
- workspace install.
290
+ `ait:wrapper:dev` loads the last game bundle copied by `pnpm build:ait` from the
291
+ wrapper's `public/game` directory, so run `pnpm build:ait` again after game
292
+ changes before opening it. The local mock deliberately keeps ads, promotions,
293
+ and IAP unavailable; test those flows from the Apps in Toss console QR sandbox
294
+ or a real device after uploading the `.ait` bundle.
272
295
 
273
296
  The starter includes `@ait-co/console-cli`, the Apps in Toss community console
274
297
  CLI, as a dev dependency for project-local console automation:
@@ -297,6 +320,13 @@ Apps in Toss console, while the file owns `appName`, `brand.primaryColor`,
297
320
  permissions, `webView`, and `webBundleDir`. Do not restore the removed SDK 2
298
321
  `granite.config.ts` fields.
299
322
 
323
+ Apps in Toss navigation chrome is target-owned. Configure the `ait` target's
324
+ `navigationBar` in `mpgd.targets.json`; `mpgd target build ait` validates the
325
+ supported appearance options and forwards them to `apps-in-toss.config.ts` at
326
+ build time. The Phaser starter uses a transparent dark navigation bar without
327
+ back or home chrome so game content can continue underneath it. Keep
328
+ the game's own safe-area padding clear of Toss controls.
329
+
300
330
  Before the first SDK 3 release, allow both of these exact origins on every API
301
331
  called by the mini-app:
302
332
 
@@ -330,7 +360,7 @@ HTTPS game-services backend that verifies purchase or reward evidence.
330
360
 
331
361
  - Browser preview is fully game-owned and writes to `artifacts/web-preview`.
332
362
  <!-- mpgd:microsoft-store:start -->
333
- - Microsoft Store is a game-owned PWA target that reuses the browser adapter and
363
+ - Microsoft Store is a game-owned PWA target with a dedicated Digital Goods adapter and
334
364
  writes to `artifacts/microsoft-store` with `manifest.webmanifest` for
335
365
  PWABuilder packaging. Replace the starter icon and manifest metadata before
336
366
  Store submission. Production builds also emit `pwa-release.json` and an
@@ -341,6 +371,16 @@ HTTPS game-services backend that verifies purchase or reward evidence.
341
371
  app; do not force activation with `skipWaiting()`. Keep the web manifest `id`
342
372
  game-specific because the cache namespace uses it to isolate apps sharing an
343
373
  origin.
374
+ The generated target starts with `authoritativeGameServices: false` while
375
+ `mpgd.microsoft-store.json` uses `commerce.mode: "disabled"`, so its effective
376
+ target contains no enabled IAP products. Switch both settings to their enabled
377
+ values only after the game supplies consumable
378
+ `InAppOfferToken` mappings plus an authoritative HTTPS backend. The backend
379
+ must resolve the renewable User Store ID and a separate stable account-link
380
+ ID from trusted player identity, query Collections v9, record the grant
381
+ idempotently, and consume through Collections v8. Never derive the account-link
382
+ ID from the User Store ID or treat the browser `purchaseToken` as a unique
383
+ transaction ID.
344
384
  Before Partner Center submission, replace every placeholder in
345
385
  `mpgd.microsoft-store.json`, add at least one real PNG screenshot per listing
346
386
  locale (maximum 50 MB and at least 1366 x 768 in either orientation), and run
@@ -401,9 +441,9 @@ HTTPS game-services backend that verifies purchase or reward evidence.
401
441
  bridge fails closed on identity, provider, serialization, and quota errors;
402
442
  it never switches progress to browser `localStorage`.
403
443
  - Apps in Toss is game-owned in `apps/target-ait`. The wrapper keeps the app
404
- name, console state, review metadata, and `@ait-co/devtools` development
405
- integration local to the game while reusing `@mpgd/adapter-ait` for the
406
- production bridge and game-bundle loader.
444
+ name, console state, review metadata, and local SDK mock configuration in the
445
+ game while reusing `@mpgd/adapter-ait` for the production bridge and
446
+ game-bundle loader.
407
447
  - Android and iOS currently use the kit reference Capacitor shell at
408
448
  `${MPGD_KIT_PATH}/apps/mobile-capacitor`. That is useful for local artifact
409
449
  checks, and final artifacts are copied back to this game's `release-output/`
@@ -1,11 +1,14 @@
1
1
  import { defineConfig, type AppsInTossConfig } from '@apps-in-toss/web-framework/config';
2
2
 
3
+ const navigationBar = readNavigationBar(process.env.MPGD_AIT_NAVIGATION_BAR);
4
+
3
5
  const config: AppsInTossConfig = {
4
6
  appName: readEnvString(process.env.MPGD_AIT_APP_NAME) ?? '__GAME_NAME__',
5
7
  brand: {
6
8
  primaryColor: readEnvString(process.env.MPGD_AIT_PRIMARY_COLOR) ?? '#101820',
7
9
  },
8
10
  permissions: [],
11
+ ...(navigationBar === undefined ? {} : { navigationBar }),
9
12
  webView: {
10
13
  bounces: false,
11
14
  pullToRefreshEnabled: false,
@@ -24,3 +27,12 @@ function readEnvString(input: string | undefined): string | undefined {
24
27
  const trimmed = input?.trim();
25
28
  return trimmed === undefined || trimmed.length === 0 ? undefined : trimmed;
26
29
  }
30
+
31
+ function readNavigationBar(
32
+ input: string | undefined,
33
+ ): AppsInTossConfig['navigationBar'] | undefined {
34
+ const encoded = readEnvString(input);
35
+ return encoded === undefined
36
+ ? undefined
37
+ : JSON.parse(encoded) as AppsInTossConfig['navigationBar'];
38
+ }
@@ -4,30 +4,27 @@
4
4
  "version": "0.0.0",
5
5
  "type": "module",
6
6
  "scripts": {
7
- "dev": "vite --host 0.0.0.0",
8
- "dev:plain": "MPGD_AIT_DEVTOOLS=0 VITE_MPGD_AIT_MOCK_IDENTITY=1 vite --host 0.0.0.0",
9
- "dev:phone": "AIT_TUNNEL=1 AIT_TUNNEL_CDP=1 vite --host 0.0.0.0",
7
+ "dev": "MPGD_AIT_LOCAL_MOCK=1 VITE_MPGD_AIT_MOCK_IDENTITY=1 vite --host 0.0.0.0",
8
+ "dev:plain": "pnpm dev",
10
9
  "build": "vite build && ait build",
11
10
  "ait:build": "ait build",
12
11
  "ait:deploy": "ait deploy --api-key \"$AIT_API_KEY\"",
13
- "devtools:mcp": "pnpm exec devtools-mcp",
14
- "devtools:mcp:mobile": "pnpm exec devtools-mcp --target=mobile",
15
12
  "check": "ttsc --noEmit -p tsconfig.json",
16
13
  "lint": "ttsc --noEmit -p tsconfig.json",
17
14
  "format": "ttsc format",
18
15
  "fix": "ttsc fix"
19
16
  },
20
17
  "dependencies": {
21
- "@apps-in-toss/web-framework": "3.0.0-beta.da14818",
18
+ "@apps-in-toss/web-framework": "3.0.2",
22
19
  "@mpgd/adapter-ait": "__MPGD_DEPENDENCY_VERSION_ADAPTER_AIT__"
23
20
  },
24
21
  "devDependencies": {
25
22
  "@ait-co/devtools": "^0.1.140",
26
- "@apps-in-toss/cli": "3.0.0-beta.66a479a",
23
+ "@apps-in-toss/cli": "3.0.2",
27
24
  "@ttsc/unplugin": "0.18.4",
28
25
  "@types/node": "^24.0.0",
29
26
  "ttsc": "0.18.4",
30
27
  "typescript": "7.0.2",
31
- "vite": "^8.1.3"
28
+ "vite": "8.1.3"
32
29
  }
33
30
  }
@@ -1,22 +1,16 @@
1
1
  import { readFileSync } from 'node:fs';
2
2
 
3
- import aitDevtools from '@ait-co/devtools/unplugin';
4
3
  import { extractAitAdBridgeConfig } from '@mpgd/adapter-ait/ad-config';
5
4
  import ttsc from '@ttsc/unplugin/vite';
6
5
  import { defineConfig } from 'vite';
7
6
 
8
- const isTruthyEnv = (value: string | undefined): boolean =>
9
- value !== undefined && value !== '' && value !== '0' && value.toLowerCase() !== 'false';
10
- const aitDevtoolsTunnel = isTruthyEnv(process.env.AIT_TUNNEL)
11
- ? { cdp: isTruthyEnv(process.env.AIT_TUNNEL_CDP) }
12
- : false;
13
7
  const aitAppName = process.env.MPGD_AIT_APP_NAME?.trim() || '__GAME_NAME__';
14
8
  const aitAdConfig = readAitAdConfig(process.env.MPGD_AD_PLACEMENTS_FILE);
15
9
 
16
10
  export default defineConfig(({ command, isPreview }) => {
17
- const enableAitDevtools = command === 'serve'
11
+ const enableLocalAitMock = command === 'serve'
18
12
  && !isPreview
19
- && process.env.MPGD_AIT_DEVTOOLS !== '0';
13
+ && process.env.MPGD_AIT_LOCAL_MOCK !== '0';
20
14
 
21
15
  return {
22
16
  define: {
@@ -24,21 +18,18 @@ export default defineConfig(({ command, isPreview }) => {
24
18
  __MPGD_AIT_AD_GROUP_IDS__: JSON.stringify(aitAdConfig.adGroupIds),
25
19
  __MPGD_AIT_AD_PLACEMENT_TYPES__: JSON.stringify(aitAdConfig.adPlacementTypes),
26
20
  },
27
- // Vite prebundles the Kit host before plugin resolve hooks run. An explicit development
28
- // alias keeps that optimized host while replacing its transitive native SDK import.
29
- ...(enableAitDevtools
21
+ // Browser-only local development must not pretend that native rewards or
22
+ // purchases are available. Production and console builds use the real SDK.
23
+ ...(enableLocalAitMock
30
24
  ? {
31
25
  resolve: {
32
26
  alias: {
33
- '@apps-in-toss/web-framework': '@ait-co/devtools/mock',
27
+ '@apps-in-toss/web-framework': '@mpgd/adapter-ait/local-mock',
34
28
  },
35
29
  },
36
30
  }
37
31
  : {}),
38
32
  plugins: [
39
- ...(enableAitDevtools
40
- ? [aitDevtools.vite({ mcp: true, tunnel: aitDevtoolsTunnel })]
41
- : []),
42
33
  ttsc({ project: 'tsconfig.json', plugins: false }),
43
34
  ],
44
35
  build: {
@@ -22,7 +22,7 @@
22
22
  "@ttsc/unplugin": "0.18.4",
23
23
  "ttsc": "0.18.4",
24
24
  "typescript": "7.0.2",
25
- "vite": "^8.1.3",
25
+ "vite": "8.1.3",
26
26
  "wrangler": "^4.107.0"
27
27
  }
28
28
  }
@@ -52,7 +52,8 @@ leaderboard. Devvit ranking should be owned by a server completion handler that
52
52
  validates the game-specific attempt, records it through the verified leaderboard
53
53
  provider from `@mpgd/adapter-devvit/server`, and exposes only an authenticated
54
54
  game-scoped snapshot route. Do not enable `nativeLeaderboard` or trust a raw
55
- client score submission for that flow.
55
+ client score submission for that flow. Keep `remoteLeaderboard` disabled too;
56
+ the verified completion route is not the generic `PlatformGateway` leaderboard.
56
57
 
57
58
  `devvit playtest` runs the official unified Vite build in watch mode, so a
58
59
  separate client/server watcher or staging prebuild is not required.
@@ -30,6 +30,6 @@
30
30
  "devvit": "0.13.8",
31
31
  "ttsc": "0.18.4",
32
32
  "typescript": "7.0.2",
33
- "vite": "^8.1.3"
33
+ "vite": "8.1.3"
34
34
  }
35
35
  }
@@ -141,6 +141,7 @@ async function handleBridgeRequest(input: BridgeRequest): Promise<BridgeResponse
141
141
  rewardedAds: false,
142
142
  interstitialAds: false,
143
143
  nativeLeaderboard: false,
144
+ remoteLeaderboard: false,
144
145
  achievements: false,
145
146
  cloudSave: true,
146
147
  socialShare: false,
@@ -74,6 +74,17 @@ Use the browser adapter for local and hosted web behavior. Verify installed-PWA
74
74
  updates without forcing service-worker activation while old windows remain
75
75
  open.
76
76
 
77
+ For direct-file test play, build `web-preview` and run
78
+ `pnpm exec mpgd game offline-playtest .`. Open
79
+ `artifacts/offline-playtest/index.html`. This output is deliberately a
80
+ network-blocked test copy, not another release target, deployable PWA,
81
+ release-manifest entry, or store package. Its README and JSON evidence describe
82
+ server-feature, Worker, dynamic-import, import-map, HTML-base, CSS `@import`,
83
+ dynamic-asset, inline-module-import, script-navigation, and `file://` storage
84
+ limitations. Meta-refresh navigation is removed. Keep server calls behind
85
+ platform or game-service helpers and expose a disabled or local fallback when
86
+ those capabilities are unavailable.
87
+
77
88
  ### Microsoft Store
78
89
 
79
90
  This target is optional. Enable it in an existing generated game with:
@@ -10,7 +10,8 @@
10
10
  "microsoft-store": {
11
11
  "kind": "web",
12
12
  "gameApp": ".",
13
- "adapter": "browser",
13
+ "adapter": "microsoft-store",
14
+ "authoritativeGameServices": false,
14
15
  "icon": { "profile": "microsoft-pwa" },
15
16
  "output": "artifacts/microsoft-store"
16
17
  },
@@ -51,6 +52,12 @@
51
52
  "primaryColor": "#101820",
52
53
  "sdkMajor": 3
53
54
  },
55
+ "navigationBar": {
56
+ "withBackButton": false,
57
+ "withHomeButton": false,
58
+ "transparentBackground": true,
59
+ "theme": "dark"
60
+ },
54
61
  "icon": { "profile": "ait" },
55
62
  "webDir": "apps/target-ait/public/game",
56
63
  "artifact": ".ait"
@@ -19,9 +19,6 @@
19
19
  "smoke:ait": "pnpm exec mpgd target smoke ait --targets-file ./mpgd.targets.json --kit-path ${MPGD_KIT_PATH:-__DEFAULT_KIT_PATH__}",
20
20
  "ait:wrapper:dev": "pnpm --dir apps/target-ait run dev",
21
21
  "ait:wrapper:dev:plain": "pnpm --dir apps/target-ait run dev:plain",
22
- "ait:wrapper:dev:phone": "pnpm --dir apps/target-ait run dev:phone",
23
- "ait:devtools:mcp": "pnpm --dir apps/target-ait run devtools:mcp",
24
- "ait:devtools:mcp:mobile": "pnpm --dir apps/target-ait run devtools:mcp:mobile",
25
22
  "ait:console:login": "aitcc login",
26
23
  "ait:console:whoami": "aitcc whoami",
27
24
  "ait:console:init": "aitcc app init",
@@ -58,7 +55,7 @@
58
55
  },
59
56
  "dependencies": {
60
57
  "@ait-co/polyfill": "^0.1.17",
61
- "@apps-in-toss/web-framework": "3.0.0-beta.da14818",
58
+ "@apps-in-toss/web-framework": "3.0.2",
62
59
  "@mpgd/adapter-ait": "__MPGD_DEPENDENCY_VERSION_ADAPTER_AIT__",
63
60
  "@mpgd/adapter-browser": "__MPGD_DEPENDENCY_VERSION_ADAPTER_BROWSER__",
64
61
  "@mpgd/adapter-capacitor": "__MPGD_DEPENDENCY_VERSION_ADAPTER_CAPACITOR__",
@@ -82,7 +79,7 @@
82
79
  "@types/node": "^24.0.0",
83
80
  "ttsc": "0.18.4",
84
81
  "typescript": "7.0.2",
85
- "vite": "^8.1.3",
82
+ "vite": "8.1.3",
86
83
  "wrangler": "^4.107.0"
87
84
  }
88
85
  }
@@ -5,10 +5,11 @@ __PNPM_WORKSPACE_KIT_PACKAGES__
5
5
  allowBuilds:
6
6
  '@sentry/cli': true
7
7
  '@swc/core': true
8
- # @ait-co/devtools lazily installs cloudflared when the phone tunnel starts;
9
- # keep default workspace installs free of external binary downloads.
10
8
  cloudflared: false
11
9
  esbuild: true
12
10
  protobufjs: true
13
11
  sharp: true
14
12
  workerd: true
13
+ peerDependencyRules:
14
+ allowedVersions:
15
+ '@ait-co/devtools>@apps-in-toss/web-framework': '3.0.2'
@@ -1,7 +1,13 @@
1
- if (__APP_TARGET__ === 'reddit') {
2
- await import('./platform/devvitEntrypoint');
3
- } else {
4
- await import('./main');
1
+ import { renderEntryFailure } from './runtime/renderEntryFailure';
2
+
3
+ try {
4
+ if (__APP_TARGET__ === 'reddit') {
5
+ await import('./platform/devvitEntrypoint');
6
+ } else {
7
+ await import('./main');
8
+ }
9
+ } catch (error) {
10
+ renderEntryFailure(error);
5
11
  }
6
12
 
7
13
  export {};
@@ -1,3 +1,9 @@
1
- await import('./main');
1
+ import { renderEntryFailure } from './runtime/renderEntryFailure';
2
+
3
+ try {
4
+ await import('./main');
5
+ } catch (error) {
6
+ renderEntryFailure(error);
7
+ }
2
8
 
3
9
  export {};
@@ -0,0 +1,37 @@
1
+ import { createBrowserPlatformGateway } from '@mpgd/adapter-browser';
2
+ import {
3
+ createMicrosoftStoreCommerceAdapter,
4
+ withMicrosoftStoreCommerceAdapter,
5
+ } from '@mpgd/adapter-browser/microsoft-store';
6
+ import type { PlatformGateway } from '@mpgd/platform';
7
+
8
+ import type { RuntimeConfig } from '../runtimeDetector';
9
+
10
+ const browser = createBrowserPlatformGateway();
11
+ const base: PlatformGateway = { ...browser, target: 'microsoft-store' };
12
+ const commerce = createMicrosoftStoreCommerceAdapter({
13
+ getRecoveryScope: () => 'configuration-required',
14
+ products: [],
15
+ authority: {
16
+ async getAvailability() {
17
+ return 'configuration-required';
18
+ },
19
+ async claimRecoveryOwnership() {
20
+ return { status: 'denied' } as const;
21
+ },
22
+ async hasRecoveryOwnership() {
23
+ return { status: 'denied' } as const;
24
+ },
25
+ async verifyAndGrant() {
26
+ return { status: 'failed' };
27
+ },
28
+ async getEntitlements() {
29
+ return [];
30
+ },
31
+ },
32
+ });
33
+
34
+ /** Configure Store products, authenticated recovery scope, and authority before checkout. */
35
+ export async function createBuildGateway(_runtime: RuntimeConfig): Promise<PlatformGateway> {
36
+ return withMicrosoftStoreCommerceAdapter(base, commerce);
37
+ }
@@ -4,6 +4,7 @@ const validTargets = new Set<string>([
4
4
  'android',
5
5
  'ios',
6
6
  'ait',
7
+ 'microsoft-store',
7
8
  'reddit',
8
9
  'verse8',
9
10
  'telegram',
@@ -0,0 +1,24 @@
1
+ export function renderEntryFailure(error: unknown): void {
2
+ console.error('[entry] Failed to load the game module.', error);
3
+
4
+ const root = document.querySelector<HTMLElement>('#game');
5
+
6
+ if (root === null) {
7
+ return;
8
+ }
9
+
10
+ root.replaceChildren();
11
+ const panel = document.createElement('div');
12
+ panel.setAttribute('role', 'alert');
13
+ panel.textContent = 'Failed to load the game. Please refresh and try again.';
14
+ panel.style.boxSizing = 'border-box';
15
+ panel.style.width = 'min(720px, calc(100% - 32px))';
16
+ panel.style.margin = '20dvh auto 0';
17
+ panel.style.padding = '20px';
18
+ panel.style.border = '1px solid #dc2626';
19
+ panel.style.borderRadius = '8px';
20
+ panel.style.background = '#1f1111';
21
+ panel.style.color = '#fecaca';
22
+ panel.style.font = '16px/1.5 Arial, sans-serif';
23
+ root.append(panel);
24
+ }
@@ -12,6 +12,7 @@ import type {
12
12
  export type { TargetConfigMatrix } from '@mpgd/target-config';
13
13
 
14
14
  const runtimeKinds = valueSet({
15
+ web: true,
15
16
  'web-preview': true,
16
17
  'microsoft-store-pwa': true,
17
18
  'capacitor-android': true,
@@ -21,6 +22,7 @@ const runtimeKinds = valueSet({
21
22
  'verse8-web': true,
22
23
  } satisfies Record<TargetRuntimeKind, true>);
23
24
  const releaseProfiles = valueSet({
25
+ web: true,
24
26
  'web-preview': true,
25
27
  'microsoft-store': true,
26
28
  'google-play': true,