@playdrop/playdrop-cli 0.14.5 → 0.14.6

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.
@@ -1,7 +1,7 @@
1
1
  {
2
- "version": "0.14.5",
2
+ "version": "0.14.6",
3
3
  "build": 1,
4
- "runtimeSdkVersion": "0.14.5",
4
+ "runtimeSdkVersion": "0.14.6",
5
5
  "runtimeSdkBuild": 1,
6
6
  "clients": {
7
7
  "all": {
@@ -223,6 +223,7 @@ function buildListing(imagePath, primarySurface) {
223
223
  screenshotLandscapePaths: [],
224
224
  videoPortraitPaths: [],
225
225
  videoLandscapePaths: [],
226
+ mediaMetadata: [],
226
227
  };
227
228
  }
228
229
  function buildStaticAppTask(input) {
@@ -285,6 +285,7 @@ async function uploadAppVersion(client, task, artifacts, options) {
285
285
  status: definition.status,
286
286
  })),
287
287
  externalUrl: task.hostingMode === 'EXTERNAL' ? task.externalUrl : undefined,
288
+ listingMedia: listing?.mediaMetadata,
288
289
  files: preparedSessionFiles.map((file) => ({
289
290
  fileKey: file.fileKey,
290
291
  filename: file.filename,
@@ -1,4 +1,4 @@
1
- import { type AppAchievementCatalogueDefinition, type AppAuthMode, type AppControllerMode, type AppHostingMode, type AppLeaderboardCatalogueDefinition, type AppMetadataAssetSpecSupport, type AppPlaytestTapes, type AppSurface, type AppType, type AppVersionDesign, type AppVersionVisibility, type AssetSpecContract, type AssetSpecStatus, type AssetSpecValidationKind, type CatalogueTagGroupDefinition, type ContentLicense, type PlayerMetaDefinitionStatus } from '@playdrop/types';
1
+ import { type AppAchievementCatalogueDefinition, type AppAuthMode, type AppControllerMode, type AppHostingMode, type AppLeaderboardCatalogueDefinition, type AppListingMediaMetadata, type AppMetadataAssetSpecSupport, type AppPlaytestTapes, type AppSurface, type AppType, type AppVersionDesign, type AppVersionVisibility, type AssetSpecContract, type AssetSpecStatus, type AssetSpecValidationKind, type CatalogueTagGroupDefinition, type ContentLicense, type PlayerMetaDefinitionStatus } from '@playdrop/types';
2
2
  import { type AssetSpecSupportDeclaration } from './assetSpecs';
3
3
  export type CatalogueJson = {
4
4
  apps?: Array<Record<string, unknown>>;
@@ -7,6 +7,8 @@ export type CatalogueJson = {
7
7
  assets?: Array<Record<string, unknown>>;
8
8
  assetPacks?: Array<Record<string, unknown>>;
9
9
  } & Record<string, unknown>;
10
+ export declare const APP_DESCRIPTION_RECOMMENDED_MIN_CHARACTERS = 120;
11
+ export declare const APP_DESCRIPTION_RECOMMENDED_MIN_WORDS = 20;
10
12
  /**
11
13
  * Listing assets configuration for an app version.
12
14
  * All paths are relative to the catalogue.json directory.
@@ -15,12 +17,20 @@ export type AppListingConfig = {
15
17
  icon?: string;
16
18
  heroPortrait?: string;
17
19
  heroLandscape?: string;
18
- screenshotsPortrait?: string[];
19
- screenshotsLandscape?: string[];
20
- videosPortrait?: string[];
21
- videosLandscape?: string[];
20
+ screenshotsPortrait?: AppListingMediaConfig[];
21
+ screenshotsLandscape?: AppListingMediaConfig[];
22
+ videosPortrait?: AppListingMediaConfig[];
23
+ videosLandscape?: AppListingMediaConfig[];
22
24
  captureReport?: string;
23
25
  };
26
+ export type AppListingMediaConfig = string | {
27
+ path: string;
28
+ slug?: string;
29
+ title?: string;
30
+ alt?: string;
31
+ caption?: string;
32
+ description?: string;
33
+ };
24
34
  export type ResolvedAppAchievementDefinition = Omit<AppAchievementCatalogueDefinition, 'status'> & {
25
35
  iconPath: string;
26
36
  status: PlayerMetaDefinitionStatus;
@@ -156,6 +166,7 @@ export type ResolvedListingAssets = {
156
166
  screenshotLandscapePaths: string[];
157
167
  videoPortraitPaths: string[];
158
168
  videoLandscapePaths: string[];
169
+ mediaMetadata: AppListingMediaMetadata[];
159
170
  captureReportPath?: string;
160
171
  };
161
172
  export type AppDependencyAssetRef = {
package/dist/catalogue.js CHANGED
@@ -1,5 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.APP_DESCRIPTION_RECOMMENDED_MIN_WORDS = exports.APP_DESCRIPTION_RECOMMENDED_MIN_CHARACTERS = void 0;
3
4
  exports.validateAppMetadata = validateAppMetadata;
4
5
  exports.resolveCatalogueTagGroups = resolveCatalogueTagGroups;
5
6
  exports.resolveCatalogueEntries = resolveCatalogueEntries;
@@ -18,6 +19,8 @@ const LEGACY_CATALOGUE_VERSION_KEY = ['schema', 'Version'].join('');
18
19
  const ASSET_CATEGORY_SET = new Set(types_1.ASSET_CATEGORY_VALUES);
19
20
  const LISTING_IMAGE_EXTENSIONS = new Set(['.png']);
20
21
  const LISTING_VIDEO_EXTENSIONS = new Set(['.mp4']);
22
+ exports.APP_DESCRIPTION_RECOMMENDED_MIN_CHARACTERS = 120;
23
+ exports.APP_DESCRIPTION_RECOMMENDED_MIN_WORDS = 20;
21
24
  const BOXEL_GENERATED_FILE_ROLES = new Set(['mesh', 'preview']);
22
25
  const BOXEL_REQUIRED_FILE_ROLES = ['primary', 'mesh', 'preview'];
23
26
  const VOX_GENERATED_FILE_ROLES = new Set(['boxel', 'mesh', 'preview']);
@@ -177,6 +180,74 @@ function validateListingAssetExtension(relativePath, fieldName, kind) {
177
180
  function resolveListingAssetPath(baseDir, relativePath) {
178
181
  return (0, node_path_1.resolve)(baseDir, relativePath);
179
182
  }
183
+ const LISTING_MEDIA_SLUG_REGEX = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
184
+ const LISTING_MEDIA_TEXT_LIMITS = {
185
+ title: 120,
186
+ alt: 200,
187
+ caption: 300,
188
+ description: 300,
189
+ };
190
+ function resolveListingMediaEntries(input) {
191
+ if (input.value === undefined) {
192
+ return { paths: [], metadata: [] };
193
+ }
194
+ if (!Array.isArray(input.value)) {
195
+ return { paths: [], metadata: [], error: `${input.fieldName} must be an array` };
196
+ }
197
+ const paths = [];
198
+ const metadata = [];
199
+ for (let index = 0; index < input.value.length; index += 1) {
200
+ const value = input.value[index];
201
+ const fieldName = `${input.fieldName}[${index}]`;
202
+ const objectValue = value && typeof value === 'object' && !Array.isArray(value)
203
+ ? value
204
+ : null;
205
+ const relativePath = typeof value === 'string'
206
+ ? value.trim()
207
+ : typeof objectValue?.path === 'string'
208
+ ? objectValue.path.trim()
209
+ : '';
210
+ if (!relativePath) {
211
+ return { paths, metadata, error: `${fieldName} must be a path string or an object with a non-empty path` };
212
+ }
213
+ const extensionError = validateListingAssetExtension(relativePath, fieldName, input.kind);
214
+ if (extensionError) {
215
+ return { paths, metadata, error: extensionError };
216
+ }
217
+ const pathError = validateListingAssetPath(input.baseDir, relativePath, fieldName);
218
+ if (pathError) {
219
+ return { paths, metadata, error: pathError };
220
+ }
221
+ const item = {
222
+ fileKey: `${input.fileKeyPrefix}:${index}`,
223
+ };
224
+ if (objectValue) {
225
+ for (const key of ['title', 'alt', 'caption', 'description']) {
226
+ const raw = objectValue[key];
227
+ if (raw === undefined) {
228
+ continue;
229
+ }
230
+ if (typeof raw !== 'string' || raw.trim().length === 0) {
231
+ return { paths, metadata, error: `${fieldName}.${key} must be a non-empty string when provided` };
232
+ }
233
+ const normalized = raw.trim();
234
+ if (normalized.length > LISTING_MEDIA_TEXT_LIMITS[key]) {
235
+ return { paths, metadata, error: `${fieldName}.${key} must be ${LISTING_MEDIA_TEXT_LIMITS[key]} characters or fewer` };
236
+ }
237
+ item[key] = normalized;
238
+ }
239
+ if (objectValue.slug !== undefined) {
240
+ if (typeof objectValue.slug !== 'string' || !LISTING_MEDIA_SLUG_REGEX.test(objectValue.slug.trim())) {
241
+ return { paths, metadata, error: `${fieldName}.slug must use lowercase words separated by hyphens` };
242
+ }
243
+ item.slug = objectValue.slug.trim();
244
+ }
245
+ }
246
+ paths.push(resolveListingAssetPath(input.baseDir, relativePath));
247
+ metadata.push(item);
248
+ }
249
+ return { paths, metadata };
250
+ }
180
251
  function normalizeCatalogueRemixRef(value, expectedKind, errors, context) {
181
252
  if (value === undefined || value === null) {
182
253
  return undefined;
@@ -869,6 +940,13 @@ function validateAppMetadata(entry) {
869
940
  let description;
870
941
  if (typeof entry.description === 'string') {
871
942
  description = entry.description;
943
+ const normalizedDescription = description.trim();
944
+ const wordCount = normalizedDescription ? normalizedDescription.split(/\s+/).length : 0;
945
+ if (normalizedDescription
946
+ && (normalizedDescription.length < exports.APP_DESCRIPTION_RECOMMENDED_MIN_CHARACTERS
947
+ || wordCount < exports.APP_DESCRIPTION_RECOMMENDED_MIN_WORDS)) {
948
+ warnings.push(`description is ${normalizedDescription.length} characters and ${wordCount} words. Aim for at least ${exports.APP_DESCRIPTION_RECOMMENDED_MIN_CHARACTERS} characters and ${exports.APP_DESCRIPTION_RECOMMENDED_MIN_WORDS} words describing what the player does, the objective, and the game's distinctive hook.`);
949
+ }
872
950
  }
873
951
  let surfaceTargetsList = [];
874
952
  let surfaceTargetsMap = { desktop: false, mobileLandscape: false, mobilePortrait: false };
@@ -1169,6 +1247,7 @@ function buildAppTasks(rootDir, catalogues, options) {
1169
1247
  screenshotLandscapePaths: [],
1170
1248
  videoPortraitPaths: [],
1171
1249
  videoLandscapePaths: [],
1250
+ mediaMetadata: [],
1172
1251
  };
1173
1252
  // Icon
1174
1253
  if (typeof rawListing.icon === 'string' && rawListing.icon.trim()) {
@@ -1236,87 +1315,28 @@ function buildAppTasks(rootDir, catalogues, options) {
1236
1315
  }
1237
1316
  }
1238
1317
  }
1239
- // Screenshots
1240
- if (!listingValidationFailed && Array.isArray(rawListing.screenshotsPortrait)) {
1241
- for (const p of rawListing.screenshotsPortrait) {
1242
- if (typeof p === 'string' && p.trim()) {
1243
- const screenshotPath = p.trim();
1244
- const extensionError = validateListingAssetExtension(screenshotPath, 'listing.screenshotsPortrait item', 'image');
1245
- if (extensionError) {
1246
- errors.push(`[${label}] Skipping ${rawName}: ${extensionError}`);
1247
- listingValidationFailed = true;
1248
- break;
1249
- }
1250
- const validationError = validateListingAssetPath(file.directory, screenshotPath, 'listing.screenshotsPortrait item');
1251
- if (validationError) {
1252
- errors.push(`[${label}] Skipping ${rawName}: ${validationError}`);
1253
- listingValidationFailed = true;
1254
- break;
1255
- }
1256
- resolvedListing.screenshotPortraitPaths.push(resolveListingAssetPath(file.directory, screenshotPath));
1257
- }
1258
- }
1259
- }
1260
- if (!listingValidationFailed && Array.isArray(rawListing.screenshotsLandscape)) {
1261
- for (const p of rawListing.screenshotsLandscape) {
1262
- if (typeof p === 'string' && p.trim()) {
1263
- const screenshotPath = p.trim();
1264
- const extensionError = validateListingAssetExtension(screenshotPath, 'listing.screenshotsLandscape item', 'image');
1265
- if (extensionError) {
1266
- errors.push(`[${label}] Skipping ${rawName}: ${extensionError}`);
1267
- listingValidationFailed = true;
1268
- break;
1269
- }
1270
- const validationError = validateListingAssetPath(file.directory, screenshotPath, 'listing.screenshotsLandscape item');
1271
- if (validationError) {
1272
- errors.push(`[${label}] Skipping ${rawName}: ${validationError}`);
1273
- listingValidationFailed = true;
1274
- break;
1275
- }
1276
- resolvedListing.screenshotLandscapePaths.push(resolveListingAssetPath(file.directory, screenshotPath));
1277
- }
1278
- }
1279
- }
1280
- // Videos
1281
- if (!listingValidationFailed && Array.isArray(rawListing.videosPortrait)) {
1282
- for (const p of rawListing.videosPortrait) {
1283
- if (typeof p === 'string' && p.trim()) {
1284
- const videoPath = p.trim();
1285
- const extensionError = validateListingAssetExtension(videoPath, 'listing.videosPortrait item', 'video');
1286
- if (extensionError) {
1287
- errors.push(`[${label}] Skipping ${rawName}: ${extensionError}`);
1288
- listingValidationFailed = true;
1289
- break;
1290
- }
1291
- const validationError = validateListingAssetPath(file.directory, videoPath, 'listing.videosPortrait item');
1292
- if (validationError) {
1293
- errors.push(`[${label}] Skipping ${rawName}: ${validationError}`);
1294
- listingValidationFailed = true;
1295
- break;
1296
- }
1297
- resolvedListing.videoPortraitPaths.push(resolveListingAssetPath(file.directory, videoPath));
1298
- }
1299
- }
1300
- }
1301
- if (!listingValidationFailed && Array.isArray(rawListing.videosLandscape)) {
1302
- for (const p of rawListing.videosLandscape) {
1303
- if (typeof p === 'string' && p.trim()) {
1304
- const videoPath = p.trim();
1305
- const extensionError = validateListingAssetExtension(videoPath, 'listing.videosLandscape item', 'video');
1306
- if (extensionError) {
1307
- errors.push(`[${label}] Skipping ${rawName}: ${extensionError}`);
1308
- listingValidationFailed = true;
1309
- break;
1310
- }
1311
- const validationError = validateListingAssetPath(file.directory, videoPath, 'listing.videosLandscape item');
1312
- if (validationError) {
1313
- errors.push(`[${label}] Skipping ${rawName}: ${validationError}`);
1314
- listingValidationFailed = true;
1315
- break;
1316
- }
1317
- resolvedListing.videoLandscapePaths.push(resolveListingAssetPath(file.directory, videoPath));
1318
- }
1318
+ for (const mediaField of [
1319
+ { value: rawListing.screenshotsPortrait, fieldName: 'listing.screenshotsPortrait', kind: 'image', fileKeyPrefix: 'screenshotsPortrait', target: resolvedListing.screenshotPortraitPaths },
1320
+ { value: rawListing.screenshotsLandscape, fieldName: 'listing.screenshotsLandscape', kind: 'image', fileKeyPrefix: 'screenshotsLandscape', target: resolvedListing.screenshotLandscapePaths },
1321
+ { value: rawListing.videosPortrait, fieldName: 'listing.videosPortrait', kind: 'video', fileKeyPrefix: 'videosPortrait', target: resolvedListing.videoPortraitPaths },
1322
+ { value: rawListing.videosLandscape, fieldName: 'listing.videosLandscape', kind: 'video', fileKeyPrefix: 'videosLandscape', target: resolvedListing.videoLandscapePaths },
1323
+ ]) {
1324
+ if (listingValidationFailed)
1325
+ break;
1326
+ const resolvedMedia = resolveListingMediaEntries({
1327
+ value: mediaField.value,
1328
+ baseDir: file.directory,
1329
+ fieldName: mediaField.fieldName,
1330
+ kind: mediaField.kind,
1331
+ fileKeyPrefix: mediaField.fileKeyPrefix,
1332
+ });
1333
+ if (resolvedMedia.error) {
1334
+ errors.push(`[${label}] Skipping ${rawName}: ${resolvedMedia.error}`);
1335
+ listingValidationFailed = true;
1336
+ break;
1319
1337
  }
1338
+ mediaField.target.push(...resolvedMedia.paths);
1339
+ resolvedListing.mediaMetadata.push(...resolvedMedia.metadata);
1320
1340
  }
1321
1341
  if (!listingValidationFailed && typeof rawListing.captureReport === 'string' && rawListing.captureReport.trim()) {
1322
1342
  const captureReportPath = rawListing.captureReport.trim();
@@ -2117,6 +2137,7 @@ function buildAssetPackTasks(catalogues) {
2117
2137
  screenshotLandscapePaths: [],
2118
2138
  videoPortraitPaths: [],
2119
2139
  videoLandscapePaths: [],
2140
+ mediaMetadata: [],
2120
2141
  };
2121
2142
  if (typeof rawListing.icon === 'string' && rawListing.icon.trim()) {
2122
2143
  const iconPath = rawListing.icon.trim();
@@ -2172,85 +2193,28 @@ function buildAssetPackTasks(catalogues) {
2172
2193
  }
2173
2194
  }
2174
2195
  }
2175
- if (!listingValidationFailed && Array.isArray(rawListing.screenshotsPortrait)) {
2176
- for (const p of rawListing.screenshotsPortrait) {
2177
- if (typeof p === 'string' && p.trim()) {
2178
- const screenshotPath = p.trim();
2179
- const extensionError = validateListingAssetExtension(screenshotPath, 'assetPacks.listing.screenshotsPortrait item', 'image');
2180
- if (extensionError) {
2181
- errors.push(`[${label}] Skipping asset pack "${name}": ${extensionError}`);
2182
- listingValidationFailed = true;
2183
- break;
2184
- }
2185
- const validationError = validateListingAssetPath(file.directory, screenshotPath, 'assetPacks.listing.screenshotsPortrait item');
2186
- if (validationError) {
2187
- errors.push(`[${label}] Skipping asset pack "${name}": ${validationError}`);
2188
- listingValidationFailed = true;
2189
- break;
2190
- }
2191
- resolvedListing.screenshotPortraitPaths.push(resolveListingAssetPath(file.directory, screenshotPath));
2192
- }
2193
- }
2194
- }
2195
- if (!listingValidationFailed && Array.isArray(rawListing.screenshotsLandscape)) {
2196
- for (const p of rawListing.screenshotsLandscape) {
2197
- if (typeof p === 'string' && p.trim()) {
2198
- const screenshotPath = p.trim();
2199
- const extensionError = validateListingAssetExtension(screenshotPath, 'assetPacks.listing.screenshotsLandscape item', 'image');
2200
- if (extensionError) {
2201
- errors.push(`[${label}] Skipping asset pack "${name}": ${extensionError}`);
2202
- listingValidationFailed = true;
2203
- break;
2204
- }
2205
- const validationError = validateListingAssetPath(file.directory, screenshotPath, 'assetPacks.listing.screenshotsLandscape item');
2206
- if (validationError) {
2207
- errors.push(`[${label}] Skipping asset pack "${name}": ${validationError}`);
2208
- listingValidationFailed = true;
2209
- break;
2210
- }
2211
- resolvedListing.screenshotLandscapePaths.push(resolveListingAssetPath(file.directory, screenshotPath));
2212
- }
2213
- }
2214
- }
2215
- if (!listingValidationFailed && Array.isArray(rawListing.videosPortrait)) {
2216
- for (const p of rawListing.videosPortrait) {
2217
- if (typeof p === 'string' && p.trim()) {
2218
- const videoPath = p.trim();
2219
- const extensionError = validateListingAssetExtension(videoPath, 'assetPacks.listing.videosPortrait item', 'video');
2220
- if (extensionError) {
2221
- errors.push(`[${label}] Skipping asset pack "${name}": ${extensionError}`);
2222
- listingValidationFailed = true;
2223
- break;
2224
- }
2225
- const validationError = validateListingAssetPath(file.directory, videoPath, 'assetPacks.listing.videosPortrait item');
2226
- if (validationError) {
2227
- errors.push(`[${label}] Skipping asset pack "${name}": ${validationError}`);
2228
- listingValidationFailed = true;
2229
- break;
2230
- }
2231
- resolvedListing.videoPortraitPaths.push(resolveListingAssetPath(file.directory, videoPath));
2232
- }
2233
- }
2234
- }
2235
- if (!listingValidationFailed && Array.isArray(rawListing.videosLandscape)) {
2236
- for (const p of rawListing.videosLandscape) {
2237
- if (typeof p === 'string' && p.trim()) {
2238
- const videoPath = p.trim();
2239
- const extensionError = validateListingAssetExtension(videoPath, 'assetPacks.listing.videosLandscape item', 'video');
2240
- if (extensionError) {
2241
- errors.push(`[${label}] Skipping asset pack "${name}": ${extensionError}`);
2242
- listingValidationFailed = true;
2243
- break;
2244
- }
2245
- const validationError = validateListingAssetPath(file.directory, videoPath, 'assetPacks.listing.videosLandscape item');
2246
- if (validationError) {
2247
- errors.push(`[${label}] Skipping asset pack "${name}": ${validationError}`);
2248
- listingValidationFailed = true;
2249
- break;
2250
- }
2251
- resolvedListing.videoLandscapePaths.push(resolveListingAssetPath(file.directory, videoPath));
2252
- }
2196
+ for (const mediaField of [
2197
+ { value: rawListing.screenshotsPortrait, fieldName: 'assetPacks.listing.screenshotsPortrait', kind: 'image', fileKeyPrefix: 'screenshotsPortrait', target: resolvedListing.screenshotPortraitPaths },
2198
+ { value: rawListing.screenshotsLandscape, fieldName: 'assetPacks.listing.screenshotsLandscape', kind: 'image', fileKeyPrefix: 'screenshotsLandscape', target: resolvedListing.screenshotLandscapePaths },
2199
+ { value: rawListing.videosPortrait, fieldName: 'assetPacks.listing.videosPortrait', kind: 'video', fileKeyPrefix: 'videosPortrait', target: resolvedListing.videoPortraitPaths },
2200
+ { value: rawListing.videosLandscape, fieldName: 'assetPacks.listing.videosLandscape', kind: 'video', fileKeyPrefix: 'videosLandscape', target: resolvedListing.videoLandscapePaths },
2201
+ ]) {
2202
+ if (listingValidationFailed)
2203
+ break;
2204
+ const resolvedMedia = resolveListingMediaEntries({
2205
+ value: mediaField.value,
2206
+ baseDir: file.directory,
2207
+ fieldName: mediaField.fieldName,
2208
+ kind: mediaField.kind,
2209
+ fileKeyPrefix: mediaField.fileKeyPrefix,
2210
+ });
2211
+ if (resolvedMedia.error) {
2212
+ errors.push(`[${label}] Skipping asset pack "${name}": ${resolvedMedia.error}`);
2213
+ listingValidationFailed = true;
2214
+ break;
2253
2215
  }
2216
+ mediaField.target.push(...resolvedMedia.paths);
2217
+ resolvedListing.mediaMetadata.push(...resolvedMedia.metadata);
2254
2218
  }
2255
2219
  if (listingValidationFailed) {
2256
2220
  continue;
@@ -1,7 +1,7 @@
1
1
  {
2
- "version": "0.14.5",
2
+ "version": "0.14.6",
3
3
  "build": 1,
4
- "runtimeSdkVersion": "0.14.5",
4
+ "runtimeSdkVersion": "0.14.6",
5
5
  "runtimeSdkBuild": 1,
6
6
  "clients": {
7
7
  "all": {
@@ -0,0 +1,14 @@
1
+ export declare const GAME_ARENA_ROUND_ONE: Readonly<{
2
+ id: "round-1";
3
+ label: "Round 1";
4
+ modelsLabel: "Opus 5 vs Sol 5.6";
5
+ description: "Opus 5 vs Sol 5.6 · casual games";
6
+ revealAt: "2026-08-06T17:00:00-04:00";
7
+ revealLabel: "Thursday · 5:00 PM ET";
8
+ comparisonMode: "BARE_SOL_OPUS";
9
+ opusModel: "opus-max";
10
+ solModel: "gpt-5.6-sol-xhigh";
11
+ }>;
12
+ export declare function resolveGameArenaRevealAt(envValue: string | null | undefined): string;
13
+ export declare function areGameArenaStandingsRevealed(revealAt: string, now?: number): boolean;
14
+ //# sourceMappingURL=game-arena.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"game-arena.d.ts","sourceRoot":"","sources":["../../src/game-arena.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,oBAAoB;;;;;;;;;;EAU/B,CAAC;AAEH,wBAAgB,wBAAwB,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,CAIpF;AAED,wBAAgB,6BAA6B,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,SAAa,GAAG,OAAO,CAKzF"}
@@ -0,0 +1,30 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.GAME_ARENA_ROUND_ONE = void 0;
4
+ exports.resolveGameArenaRevealAt = resolveGameArenaRevealAt;
5
+ exports.areGameArenaStandingsRevealed = areGameArenaStandingsRevealed;
6
+ exports.GAME_ARENA_ROUND_ONE = Object.freeze({
7
+ id: 'round-1',
8
+ label: 'Round 1',
9
+ modelsLabel: 'Opus 5 vs Sol 5.6',
10
+ description: 'Opus 5 vs Sol 5.6 · casual games',
11
+ revealAt: '2026-08-06T17:00:00-04:00',
12
+ revealLabel: 'Thursday · 5:00 PM ET',
13
+ comparisonMode: 'BARE_SOL_OPUS',
14
+ opusModel: 'opus-max',
15
+ solModel: 'gpt-5.6-sol-xhigh',
16
+ });
17
+ function resolveGameArenaRevealAt(envValue) {
18
+ const value = envValue?.trim() || exports.GAME_ARENA_ROUND_ONE.revealAt;
19
+ if (Number.isNaN(Date.parse(value)))
20
+ throw new Error('invalid_game_arena_reveal_at');
21
+ return value;
22
+ }
23
+ function areGameArenaStandingsRevealed(revealAt, now = Date.now()) {
24
+ const target = Date.parse(revealAt);
25
+ if (Number.isNaN(target))
26
+ throw new Error('invalid_game_arena_reveal_at');
27
+ if (!Number.isFinite(now))
28
+ throw new Error('invalid_game_arena_now');
29
+ return now >= target;
30
+ }
@@ -6,6 +6,7 @@ export declare function getClientBuild(): number;
6
6
  export declare function getClientVersionLabel(): string;
7
7
  export * from './constants';
8
8
  export * from './deployment';
9
+ export * from './game-arena';
9
10
  export * from './server/fastify';
10
11
  export * from './server/logging';
11
12
  export * from './types';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAElD,OAAO,KAAK,EACV,gBAAgB,EAChB,iBAAiB,EACjB,sBAAsB,EAEvB,MAAM,SAAS,CAAC;AAEjB,wBAAgB,cAAc,IAAI,gBAAgB,CAEjD;AAED,wBAAgB,gBAAgB,IAAI,MAAM,CAEzC;AAED,wBAAgB,cAAc,IAAI,MAAM,CAEvC;AAED,wBAAgB,qBAAqB,IAAI,MAAM,CAG9C;AAED,cAAc,aAAa,CAAC;AAC5B,cAAc,cAAc,CAAC;AAC7B,cAAc,kBAAkB,CAAC;AACjC,cAAc,kBAAkB,CAAC;AACjC,cAAc,SAAS,CAAC;AACxB,OAAO,EAAE,mBAAmB,EAAE,CAAC;AAE/B,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,iBAAiB,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAUxF;AAwBD,wBAAgB,yBAAyB,CAAC,IAAI,EAAE,iBAAiB,GAAG,sBAAsB,CAqEzF"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAElD,OAAO,KAAK,EACV,gBAAgB,EAChB,iBAAiB,EACjB,sBAAsB,EAEvB,MAAM,SAAS,CAAC;AAEjB,wBAAgB,cAAc,IAAI,gBAAgB,CAEjD;AAED,wBAAgB,gBAAgB,IAAI,MAAM,CAEzC;AAED,wBAAgB,cAAc,IAAI,MAAM,CAEvC;AAED,wBAAgB,qBAAqB,IAAI,MAAM,CAG9C;AAED,cAAc,aAAa,CAAC;AAC5B,cAAc,cAAc,CAAC;AAC7B,cAAc,cAAc,CAAC;AAC7B,cAAc,kBAAkB,CAAC;AACjC,cAAc,kBAAkB,CAAC;AACjC,cAAc,SAAS,CAAC;AACxB,OAAO,EAAE,mBAAmB,EAAE,CAAC;AAE/B,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,iBAAiB,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAUxF;AAwBD,wBAAgB,yBAAyB,CAAC,IAAI,EAAE,iBAAiB,GAAG,sBAAsB,CAqEzF"}
@@ -39,6 +39,7 @@ function getClientVersionLabel() {
39
39
  }
40
40
  __exportStar(require("./constants"), exports);
41
41
  __exportStar(require("./deployment"), exports);
42
+ __exportStar(require("./game-arena"), exports);
42
43
  __exportStar(require("./server/fastify"), exports);
43
44
  __exportStar(require("./server/logging"), exports);
44
45
  __exportStar(require("./types"), exports);
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=game-arena.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"game-arena.test.d.ts","sourceRoot":"","sources":["../../test/game-arena.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,16 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const node_test_1 = __importDefault(require("node:test"));
7
+ const strict_1 = __importDefault(require("node:assert/strict"));
8
+ const game_arena_1 = require("../src/game-arena");
9
+ (0, node_test_1.default)('Game Arena round one keeps the launch framing and configured reveal', () => {
10
+ strict_1.default.equal(game_arena_1.GAME_ARENA_ROUND_ONE.description, 'Opus 5 vs Sol 5.6 · casual games');
11
+ strict_1.default.equal((0, game_arena_1.resolveGameArenaRevealAt)(undefined), '2026-08-06T17:00:00-04:00');
12
+ strict_1.default.equal((0, game_arena_1.resolveGameArenaRevealAt)('2026-08-13T17:00:00-04:00'), '2026-08-13T17:00:00-04:00');
13
+ strict_1.default.equal((0, game_arena_1.areGameArenaStandingsRevealed)(game_arena_1.GAME_ARENA_ROUND_ONE.revealAt, Date.parse('2026-08-06T16:59:59-04:00')), false);
14
+ strict_1.default.equal((0, game_arena_1.areGameArenaStandingsRevealed)(game_arena_1.GAME_ARENA_ROUND_ONE.revealAt, Date.parse('2026-08-06T17:00:00-04:00')), true);
15
+ strict_1.default.throws(() => (0, game_arena_1.resolveGameArenaRevealAt)('not-a-date'), /invalid_game_arena_reveal_at/);
16
+ });