playcademy 0.14.32 → 0.14.33

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/cli.js CHANGED
@@ -562,17 +562,11 @@ function displayApiError(error, indent) {
562
562
  return;
563
563
  }
564
564
  const statusCode = errorInfo.status;
565
- let displayMessage = errorInfo.statusText;
565
+ let displayMessage = errorInfo.message;
566
566
  displayMessage = removeStatusPrefix(displayMessage, statusCode);
567
567
  const { cleanMessage, json: embeddedJson } = extractEmbeddedJson(displayMessage);
568
568
  displayMessage = cleanMessageSuffix(cleanMessage);
569
- let errorCode;
570
- if (error.details && typeof error.details === "object") {
571
- const details = error.details;
572
- if ("code" in details && typeof details.code === "string") {
573
- errorCode = details.code;
574
- }
575
- }
569
+ const errorCode = error.code;
576
570
  let errorHeader = "API Error";
577
571
  if (errorCode) {
578
572
  errorHeader += ` ${redBright(`[${errorCode}]`)}`;
@@ -1050,7 +1044,7 @@ var package_default = {
1050
1044
  sort: "bunx sort-package-json **/package.json",
1051
1045
  test: "bun test",
1052
1046
  "test:unit": "bun scripts/test-unit.ts",
1053
- "test:integration": "bun scripts/test-integration.ts",
1047
+ "test:e2e": "bun scripts/test-e2e.ts",
1054
1048
  "test:watch": "bun test --watch",
1055
1049
  "docker:relay": "bun run scripts/docker-relay.ts",
1056
1050
  "debug:leaderboard-notifications": "bunx sst shell -- bun scripts/debug/leaderboard-notifications.ts",
@@ -1210,6 +1204,104 @@ var CONFIG_FILE_NAMES = [
1210
1204
  "playcademy.config.mjs"
1211
1205
  ];
1212
1206
 
1207
+ // ../constants/src/achievements.ts
1208
+ var ACHIEVEMENT_IDS = {
1209
+ PLAY_ANY_GAME_DAILY: "play_any_game_daily",
1210
+ DAILY_CHEST_OPEN: "daily_chest_open",
1211
+ LEADERBOARD_TOP3_DAILY: "leaderboard_top3_daily",
1212
+ LEADERBOARD_TOP3_WEEKLY: "leaderboard_top3_weekly",
1213
+ FIRST_SCORE_ANY_GAME: "first_score_any_game",
1214
+ PERSONAL_BEST_ANY_GAME: "personal_best_any_game"
1215
+ };
1216
+ var ACHIEVEMENT_DEFINITIONS = [
1217
+ {
1218
+ id: ACHIEVEMENT_IDS.PLAY_ANY_GAME_DAILY,
1219
+ title: "Play any game",
1220
+ description: "Play any arcade game for at least 60 seconds in a single session.",
1221
+ scope: "daily",
1222
+ rewardCredits: 10,
1223
+ limit: 1,
1224
+ completionType: "time_played_session",
1225
+ completionConfig: { minSeconds: 60 },
1226
+ target: { anyArcadeGame: true },
1227
+ active: true
1228
+ },
1229
+ {
1230
+ id: ACHIEVEMENT_IDS.DAILY_CHEST_OPEN,
1231
+ title: "Opened the daily chest",
1232
+ description: "Find the chest on the map and open it to claim your reward.",
1233
+ scope: "daily",
1234
+ rewardCredits: 10,
1235
+ limit: 1,
1236
+ completionType: "interaction",
1237
+ completionConfig: { triggerId: "bunny_chest" },
1238
+ target: { map: "arcade" },
1239
+ active: true
1240
+ },
1241
+ {
1242
+ id: ACHIEVEMENT_IDS.LEADERBOARD_TOP3_DAILY,
1243
+ title: "Daily Champion",
1244
+ description: "Finish in the top 3 of any game leaderboard for the day.",
1245
+ scope: "daily",
1246
+ rewardCredits: 25,
1247
+ // Base reward, overridden by rankRewards
1248
+ limit: 1,
1249
+ completionType: "leaderboard_rank",
1250
+ completionConfig: {
1251
+ rankRewards: [50, 30, 15]
1252
+ // [1st place, 2nd place, 3rd place]
1253
+ },
1254
+ target: { anyArcadeGame: true },
1255
+ active: true
1256
+ },
1257
+ {
1258
+ id: ACHIEVEMENT_IDS.LEADERBOARD_TOP3_WEEKLY,
1259
+ title: "Weekly Legend",
1260
+ description: "Finish in the top 3 of any game leaderboard for the week.",
1261
+ scope: "weekly",
1262
+ rewardCredits: 50,
1263
+ // Base reward, overridden by rankRewards
1264
+ limit: 1,
1265
+ completionType: "leaderboard_rank",
1266
+ completionConfig: {
1267
+ rankRewards: [100, 60, 30]
1268
+ // [1st place, 2nd place, 3rd place]
1269
+ },
1270
+ target: { anyArcadeGame: true },
1271
+ active: true
1272
+ },
1273
+ {
1274
+ id: ACHIEVEMENT_IDS.FIRST_SCORE_ANY_GAME,
1275
+ title: "First Score",
1276
+ description: "Submit your first score in any arcade game.",
1277
+ scope: "game",
1278
+ // Scoped per game, no time limit
1279
+ rewardCredits: 5,
1280
+ limit: 1,
1281
+ completionType: "first_score",
1282
+ completionConfig: {},
1283
+ target: { anyArcadeGame: true },
1284
+ active: true
1285
+ },
1286
+ {
1287
+ id: ACHIEVEMENT_IDS.PERSONAL_BEST_ANY_GAME,
1288
+ title: "Personal Best",
1289
+ description: "Beat your personal best score in any arcade game.",
1290
+ scope: "daily",
1291
+ rewardCredits: 5,
1292
+ limit: 3,
1293
+ // Can earn 3 times per day
1294
+ completionType: "personal_best",
1295
+ completionConfig: {},
1296
+ target: { anyArcadeGame: true },
1297
+ active: true
1298
+ }
1299
+ ];
1300
+
1301
+ // ../constants/src/typescript.ts
1302
+ var TSC_PACKAGE = "@typescript/native-preview";
1303
+ var USE_NATIVE_TSC = TSC_PACKAGE.includes("native-preview");
1304
+
1213
1305
  // ../constants/src/overworld.ts
1214
1306
  var ITEM_SLUGS = {
1215
1307
  /** Primary platform currency */
@@ -1269,6 +1361,9 @@ function hasPackageJson(dir) {
1269
1361
  return existsSync3(resolve2(dir || getWorkspace(), "package.json"));
1270
1362
  }
1271
1363
 
1364
+ // src/lib/core/errors.ts
1365
+ import { ApiError as ApiError2 } from "@playcademy/sdk/internal";
1366
+
1272
1367
  // src/lib/core/gitignore.ts
1273
1368
  function normalizeGitignoreEntry(entry) {
1274
1369
  return entry.replace(/^\/+/, "").replace(/\/+$/, "");
@@ -1277,6 +1372,9 @@ function normalizeGitignoreEntry(entry) {
1277
1372
  // src/lib/core/import.ts
1278
1373
  import * as esbuild from "esbuild";
1279
1374
 
1375
+ // src/lib/core/transpile.ts
1376
+ import * as esbuild2 from "esbuild";
1377
+
1280
1378
  // src/lib/secrets/env.ts
1281
1379
  init_file_loader();
1282
1380
  import { existsSync as existsSync4, writeFileSync } from "fs";
@@ -1628,8 +1726,105 @@ function processConfigVariables(config) {
1628
1726
 
1629
1727
  // ../timeback/dist/constants.js
1630
1728
  var __esm2 = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
1729
+ var ACHIEVEMENT_IDS2;
1730
+ var ACHIEVEMENT_DEFINITIONS2;
1731
+ var init_achievements = __esm2(() => {
1732
+ ACHIEVEMENT_IDS2 = {
1733
+ PLAY_ANY_GAME_DAILY: "play_any_game_daily",
1734
+ DAILY_CHEST_OPEN: "daily_chest_open",
1735
+ LEADERBOARD_TOP3_DAILY: "leaderboard_top3_daily",
1736
+ LEADERBOARD_TOP3_WEEKLY: "leaderboard_top3_weekly",
1737
+ FIRST_SCORE_ANY_GAME: "first_score_any_game",
1738
+ PERSONAL_BEST_ANY_GAME: "personal_best_any_game"
1739
+ };
1740
+ ACHIEVEMENT_DEFINITIONS2 = [
1741
+ {
1742
+ id: ACHIEVEMENT_IDS2.PLAY_ANY_GAME_DAILY,
1743
+ title: "Play any game",
1744
+ description: "Play any arcade game for at least 60 seconds in a single session.",
1745
+ scope: "daily",
1746
+ rewardCredits: 10,
1747
+ limit: 1,
1748
+ completionType: "time_played_session",
1749
+ completionConfig: { minSeconds: 60 },
1750
+ target: { anyArcadeGame: true },
1751
+ active: true
1752
+ },
1753
+ {
1754
+ id: ACHIEVEMENT_IDS2.DAILY_CHEST_OPEN,
1755
+ title: "Opened the daily chest",
1756
+ description: "Find the chest on the map and open it to claim your reward.",
1757
+ scope: "daily",
1758
+ rewardCredits: 10,
1759
+ limit: 1,
1760
+ completionType: "interaction",
1761
+ completionConfig: { triggerId: "bunny_chest" },
1762
+ target: { map: "arcade" },
1763
+ active: true
1764
+ },
1765
+ {
1766
+ id: ACHIEVEMENT_IDS2.LEADERBOARD_TOP3_DAILY,
1767
+ title: "Daily Champion",
1768
+ description: "Finish in the top 3 of any game leaderboard for the day.",
1769
+ scope: "daily",
1770
+ rewardCredits: 25,
1771
+ limit: 1,
1772
+ completionType: "leaderboard_rank",
1773
+ completionConfig: {
1774
+ rankRewards: [50, 30, 15]
1775
+ },
1776
+ target: { anyArcadeGame: true },
1777
+ active: true
1778
+ },
1779
+ {
1780
+ id: ACHIEVEMENT_IDS2.LEADERBOARD_TOP3_WEEKLY,
1781
+ title: "Weekly Legend",
1782
+ description: "Finish in the top 3 of any game leaderboard for the week.",
1783
+ scope: "weekly",
1784
+ rewardCredits: 50,
1785
+ limit: 1,
1786
+ completionType: "leaderboard_rank",
1787
+ completionConfig: {
1788
+ rankRewards: [100, 60, 30]
1789
+ },
1790
+ target: { anyArcadeGame: true },
1791
+ active: true
1792
+ },
1793
+ {
1794
+ id: ACHIEVEMENT_IDS2.FIRST_SCORE_ANY_GAME,
1795
+ title: "First Score",
1796
+ description: "Submit your first score in any arcade game.",
1797
+ scope: "game",
1798
+ rewardCredits: 5,
1799
+ limit: 1,
1800
+ completionType: "first_score",
1801
+ completionConfig: {},
1802
+ target: { anyArcadeGame: true },
1803
+ active: true
1804
+ },
1805
+ {
1806
+ id: ACHIEVEMENT_IDS2.PERSONAL_BEST_ANY_GAME,
1807
+ title: "Personal Best",
1808
+ description: "Beat your personal best score in any arcade game.",
1809
+ scope: "daily",
1810
+ rewardCredits: 5,
1811
+ limit: 3,
1812
+ completionType: "personal_best",
1813
+ completionConfig: {},
1814
+ target: { anyArcadeGame: true },
1815
+ active: true
1816
+ }
1817
+ ];
1818
+ });
1631
1819
  var init_auth = () => {
1632
1820
  };
1821
+ var TSC_PACKAGE2 = "@typescript/native-preview";
1822
+ var USE_NATIVE_TSC2;
1823
+ var init_typescript = __esm2(() => {
1824
+ USE_NATIVE_TSC2 = TSC_PACKAGE2.includes("native-preview");
1825
+ });
1826
+ var init_character = () => {
1827
+ };
1633
1828
  var PLAYCADEMY_BASE_URLS2;
1634
1829
  var init_domains = __esm2(() => {
1635
1830
  PLAYCADEMY_BASE_URLS2 = {
@@ -1666,6 +1861,7 @@ var init_overworld = __esm2(() => {
1666
1861
  FIRST_GAME: ITEM_SLUGS2.FIRST_GAME_BADGE
1667
1862
  };
1668
1863
  });
1864
+ var TIMEBACK_CALIPER_SENSORS;
1669
1865
  var TIMEBACK_ORG_SOURCED_ID = "PLAYCADEMY";
1670
1866
  var TIMEBACK_ORG_NAME = "Playcademy Studios";
1671
1867
  var TIMEBACK_ORG_TYPE = "department";
@@ -1674,6 +1870,11 @@ var TIMEBACK_RESOURCE_DEFAULTS;
1674
1870
  var TIMEBACK_COMPONENT_DEFAULTS;
1675
1871
  var TIMEBACK_COMPONENT_RESOURCE_DEFAULTS;
1676
1872
  var init_timeback = __esm2(() => {
1873
+ TIMEBACK_CALIPER_SENSORS = [
1874
+ "https://samuraimath.com",
1875
+ "https://app.alphamath.school",
1876
+ "https://mathraiders.com"
1877
+ ];
1677
1878
  TIMEBACK_COURSE_DEFAULTS = {
1678
1879
  gradingScheme: "STANDARD",
1679
1880
  level: {
@@ -1714,7 +1915,10 @@ var init_timeback = __esm2(() => {
1714
1915
  var init_workers = () => {
1715
1916
  };
1716
1917
  var init_src = __esm2(() => {
1918
+ init_achievements();
1717
1919
  init_auth();
1920
+ init_typescript();
1921
+ init_character();
1718
1922
  init_domains();
1719
1923
  init_env_vars();
1720
1924
  init_overworld();
@@ -2754,7 +2958,7 @@ import { join as join13 } from "path";
2754
2958
  // package.json with { type: 'json' }
2755
2959
  var package_default2 = {
2756
2960
  name: "playcademy",
2757
- version: "0.14.31",
2961
+ version: "0.14.32",
2758
2962
  type: "module",
2759
2963
  exports: {
2760
2964
  ".": {
@@ -2798,6 +3002,7 @@ var package_default2 = {
2798
3002
  ],
2799
3003
  scripts: {
2800
3004
  build: "bun build.ts",
3005
+ check: "bunx @typescript/native-preview --noEmit",
2801
3006
  dev: "PLAYCADEMY_BASE_URL=http://localhost:5174 bun src/index.ts",
2802
3007
  pub: "bun publish.ts"
2803
3008
  },
@@ -0,0 +1,107 @@
1
+ /**
2
+ * Achievement definitions - single source of truth for all achievements
3
+ *
4
+ * Used by:
5
+ * - apps/cademy seed scripts
6
+ * - packages/sandbox seeding
7
+ * - packages/fns achievement awarding
8
+ *
9
+ * NOTE: Uses string literals instead of AchievementCompletionType enum
10
+ * to avoid circular dependency with @playcademy/data
11
+ */
12
+
13
+ /**
14
+ * Achievement IDs as constants to avoid magic strings
15
+ */
16
+ export const ACHIEVEMENT_IDS = {
17
+ PLAY_ANY_GAME_DAILY: 'play_any_game_daily',
18
+ DAILY_CHEST_OPEN: 'daily_chest_open',
19
+ LEADERBOARD_TOP3_DAILY: 'leaderboard_top3_daily',
20
+ LEADERBOARD_TOP3_WEEKLY: 'leaderboard_top3_weekly',
21
+ FIRST_SCORE_ANY_GAME: 'first_score_any_game',
22
+ PERSONAL_BEST_ANY_GAME: 'personal_best_any_game',
23
+ } as const
24
+
25
+ export type AchievementId = (typeof ACHIEVEMENT_IDS)[keyof typeof ACHIEVEMENT_IDS]
26
+
27
+ /**
28
+ * Achievement definitions for platform seeding
29
+ */
30
+ export const ACHIEVEMENT_DEFINITIONS = [
31
+ {
32
+ id: ACHIEVEMENT_IDS.PLAY_ANY_GAME_DAILY,
33
+ title: 'Play any game',
34
+ description: 'Play any arcade game for at least 60 seconds in a single session.',
35
+ scope: 'daily' as const,
36
+ rewardCredits: 10,
37
+ limit: 1,
38
+ completionType: 'time_played_session' as const,
39
+ completionConfig: { minSeconds: 60 },
40
+ target: { anyArcadeGame: true },
41
+ active: true,
42
+ },
43
+ {
44
+ id: ACHIEVEMENT_IDS.DAILY_CHEST_OPEN,
45
+ title: 'Opened the daily chest',
46
+ description: 'Find the chest on the map and open it to claim your reward.',
47
+ scope: 'daily' as const,
48
+ rewardCredits: 10,
49
+ limit: 1,
50
+ completionType: 'interaction' as const,
51
+ completionConfig: { triggerId: 'bunny_chest' },
52
+ target: { map: 'arcade' },
53
+ active: true,
54
+ },
55
+ {
56
+ id: ACHIEVEMENT_IDS.LEADERBOARD_TOP3_DAILY,
57
+ title: 'Daily Champion',
58
+ description: 'Finish in the top 3 of any game leaderboard for the day.',
59
+ scope: 'daily' as const,
60
+ rewardCredits: 25, // Base reward, overridden by rankRewards
61
+ limit: 1,
62
+ completionType: 'leaderboard_rank' as const,
63
+ completionConfig: {
64
+ rankRewards: [50, 30, 15], // [1st place, 2nd place, 3rd place]
65
+ },
66
+ target: { anyArcadeGame: true },
67
+ active: true,
68
+ },
69
+ {
70
+ id: ACHIEVEMENT_IDS.LEADERBOARD_TOP3_WEEKLY,
71
+ title: 'Weekly Legend',
72
+ description: 'Finish in the top 3 of any game leaderboard for the week.',
73
+ scope: 'weekly' as const,
74
+ rewardCredits: 50, // Base reward, overridden by rankRewards
75
+ limit: 1,
76
+ completionType: 'leaderboard_rank' as const,
77
+ completionConfig: {
78
+ rankRewards: [100, 60, 30], // [1st place, 2nd place, 3rd place]
79
+ },
80
+ target: { anyArcadeGame: true },
81
+ active: true,
82
+ },
83
+ {
84
+ id: ACHIEVEMENT_IDS.FIRST_SCORE_ANY_GAME,
85
+ title: 'First Score',
86
+ description: 'Submit your first score in any arcade game.',
87
+ scope: 'game' as const, // Scoped per game, no time limit
88
+ rewardCredits: 5,
89
+ limit: 1,
90
+ completionType: 'first_score' as const,
91
+ completionConfig: {},
92
+ target: { anyArcadeGame: true },
93
+ active: true,
94
+ },
95
+ {
96
+ id: ACHIEVEMENT_IDS.PERSONAL_BEST_ANY_GAME,
97
+ title: 'Personal Best',
98
+ description: 'Beat your personal best score in any arcade game.',
99
+ scope: 'daily' as const,
100
+ rewardCredits: 5,
101
+ limit: 3, // Can earn 3 times per day
102
+ completionType: 'personal_best' as const,
103
+ completionConfig: {},
104
+ target: { anyArcadeGame: true },
105
+ active: true,
106
+ },
107
+ ]
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Character Constants
3
+ *
4
+ * Configuration for player character customization.
5
+ */
6
+
7
+ /** Accessory slot configuration with unlock levels */
8
+ export const ACCESSORY_SLOTS = [
9
+ { type: 'head', unlockLevel: 1, displayName: 'Head', maxItems: 1 },
10
+ { type: 'face', unlockLevel: 2, displayName: 'Face', maxItems: 1 },
11
+ { type: 'hands', unlockLevel: 10, displayName: 'Hands', maxItems: 1 },
12
+ { type: 'back', unlockLevel: 15, displayName: 'Back', maxItems: 1 },
13
+ ] as const
14
+
15
+ /** Valid accessory slot types */
16
+ export const VALID_SLOT_TYPES = ['head', 'face', 'hands', 'back'] as const
@@ -6,7 +6,10 @@
6
6
  * and other constants that need to be consistent across multiple packages.
7
7
  */
8
8
 
9
+ export * from './achievements'
9
10
  export * from './auth'
11
+ export * from './typescript'
12
+ export * from './character'
10
13
  export * from './domains'
11
14
  export * from './env-vars'
12
15
  export * from './overworld'
@@ -4,6 +4,15 @@
4
4
  * Constants related to TimeBack Caliper integration.
5
5
  */
6
6
 
7
+ /**
8
+ * TimeBack LTI issuer URLs by environment
9
+ * Used for JWT token verification (iss claim)
10
+ */
11
+ export const TIMEBACK_LTI_ISSUER_URLS = {
12
+ production: 'https://timeback.com',
13
+ staging: 'https://staging.timeback.com',
14
+ } as const
15
+
7
16
  /**
8
17
  * TimeBack Caliper sensor URLs
9
18
  * Allowed sensor URLs for Caliper event validation
@@ -0,0 +1,19 @@
1
+ /**
2
+ * TypeScript configuration constants
3
+ *
4
+ * Toggle between TypeScript compilers for type generation.
5
+ */
6
+
7
+ /**
8
+ * TypeScript package to use for type generation via bunx.
9
+ *
10
+ * Options:
11
+ * - '@typescript/native-preview' - Native TypeScript compiler (faster)
12
+ * - 'typescript' - Standard TypeScript compiler
13
+ */
14
+ export const TSC_PACKAGE = '@typescript/native-preview'
15
+
16
+ /**
17
+ * Whether using the native TypeScript compiler.
18
+ */
19
+ export const USE_NATIVE_TSC = TSC_PACKAGE.includes('native-preview')
package/dist/constants.js CHANGED
@@ -72,7 +72,7 @@ var package_default = {
72
72
  sort: "bunx sort-package-json **/package.json",
73
73
  test: "bun test",
74
74
  "test:unit": "bun scripts/test-unit.ts",
75
- "test:integration": "bun scripts/test-integration.ts",
75
+ "test:e2e": "bun scripts/test-e2e.ts",
76
76
  "test:watch": "bun test --watch",
77
77
  "docker:relay": "bun run scripts/docker-relay.ts",
78
78
  "debug:leaderboard-notifications": "bunx sst shell -- bun scripts/debug/leaderboard-notifications.ts",
@@ -292,6 +292,104 @@ var CONFIG_FILE_NAMES = [
292
292
  "playcademy.config.mjs"
293
293
  ];
294
294
 
295
+ // ../constants/src/achievements.ts
296
+ var ACHIEVEMENT_IDS = {
297
+ PLAY_ANY_GAME_DAILY: "play_any_game_daily",
298
+ DAILY_CHEST_OPEN: "daily_chest_open",
299
+ LEADERBOARD_TOP3_DAILY: "leaderboard_top3_daily",
300
+ LEADERBOARD_TOP3_WEEKLY: "leaderboard_top3_weekly",
301
+ FIRST_SCORE_ANY_GAME: "first_score_any_game",
302
+ PERSONAL_BEST_ANY_GAME: "personal_best_any_game"
303
+ };
304
+ var ACHIEVEMENT_DEFINITIONS = [
305
+ {
306
+ id: ACHIEVEMENT_IDS.PLAY_ANY_GAME_DAILY,
307
+ title: "Play any game",
308
+ description: "Play any arcade game for at least 60 seconds in a single session.",
309
+ scope: "daily",
310
+ rewardCredits: 10,
311
+ limit: 1,
312
+ completionType: "time_played_session",
313
+ completionConfig: { minSeconds: 60 },
314
+ target: { anyArcadeGame: true },
315
+ active: true
316
+ },
317
+ {
318
+ id: ACHIEVEMENT_IDS.DAILY_CHEST_OPEN,
319
+ title: "Opened the daily chest",
320
+ description: "Find the chest on the map and open it to claim your reward.",
321
+ scope: "daily",
322
+ rewardCredits: 10,
323
+ limit: 1,
324
+ completionType: "interaction",
325
+ completionConfig: { triggerId: "bunny_chest" },
326
+ target: { map: "arcade" },
327
+ active: true
328
+ },
329
+ {
330
+ id: ACHIEVEMENT_IDS.LEADERBOARD_TOP3_DAILY,
331
+ title: "Daily Champion",
332
+ description: "Finish in the top 3 of any game leaderboard for the day.",
333
+ scope: "daily",
334
+ rewardCredits: 25,
335
+ // Base reward, overridden by rankRewards
336
+ limit: 1,
337
+ completionType: "leaderboard_rank",
338
+ completionConfig: {
339
+ rankRewards: [50, 30, 15]
340
+ // [1st place, 2nd place, 3rd place]
341
+ },
342
+ target: { anyArcadeGame: true },
343
+ active: true
344
+ },
345
+ {
346
+ id: ACHIEVEMENT_IDS.LEADERBOARD_TOP3_WEEKLY,
347
+ title: "Weekly Legend",
348
+ description: "Finish in the top 3 of any game leaderboard for the week.",
349
+ scope: "weekly",
350
+ rewardCredits: 50,
351
+ // Base reward, overridden by rankRewards
352
+ limit: 1,
353
+ completionType: "leaderboard_rank",
354
+ completionConfig: {
355
+ rankRewards: [100, 60, 30]
356
+ // [1st place, 2nd place, 3rd place]
357
+ },
358
+ target: { anyArcadeGame: true },
359
+ active: true
360
+ },
361
+ {
362
+ id: ACHIEVEMENT_IDS.FIRST_SCORE_ANY_GAME,
363
+ title: "First Score",
364
+ description: "Submit your first score in any arcade game.",
365
+ scope: "game",
366
+ // Scoped per game, no time limit
367
+ rewardCredits: 5,
368
+ limit: 1,
369
+ completionType: "first_score",
370
+ completionConfig: {},
371
+ target: { anyArcadeGame: true },
372
+ active: true
373
+ },
374
+ {
375
+ id: ACHIEVEMENT_IDS.PERSONAL_BEST_ANY_GAME,
376
+ title: "Personal Best",
377
+ description: "Beat your personal best score in any arcade game.",
378
+ scope: "daily",
379
+ rewardCredits: 5,
380
+ limit: 3,
381
+ // Can earn 3 times per day
382
+ completionType: "personal_best",
383
+ completionConfig: {},
384
+ target: { anyArcadeGame: true },
385
+ active: true
386
+ }
387
+ ];
388
+
389
+ // ../constants/src/typescript.ts
390
+ var TSC_PACKAGE = "@typescript/native-preview";
391
+ var USE_NATIVE_TSC = TSC_PACKAGE.includes("native-preview");
392
+
295
393
  // ../constants/src/domains.ts
296
394
  var PLAYCADEMY_DOMAINS = {
297
395
  /** Primary domain (hub, landing pages) */