@playdrop/playdrop-cli 0.12.27 → 0.12.29
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/config/client-meta.json +1 -1
- package/dist/catalogue.js +1 -3
- package/dist/commands/devRuntimeAssets.d.ts +1 -1
- package/dist/commands/devRuntimeAssets.js +28 -123
- package/dist/commands/init.d.ts +1 -1
- package/dist/commands/init.js +11 -6
- package/dist/commands/ownedRuntimeImageValidation.d.ts +7 -0
- package/dist/commands/ownedRuntimeImageValidation.js +76 -0
- package/dist/commands/upload.d.ts +11 -0
- package/dist/commands/upload.js +72 -447
- package/dist/commands/validate.js +15 -1
- package/dist/commands/worker.d.ts +16 -0
- package/dist/commands/worker.js +165 -67
- package/dist/listingPreflight.d.ts +10 -0
- package/dist/listingPreflight.js +34 -10
- package/node_modules/@playdrop/api-client/dist/client.d.ts +2 -1
- package/node_modules/@playdrop/api-client/dist/client.d.ts.map +1 -1
- package/node_modules/@playdrop/api-client/dist/core/errors.d.ts.map +1 -1
- package/node_modules/@playdrop/api-client/dist/core/errors.js +3 -1
- package/node_modules/@playdrop/api-client/dist/domains/apps.d.ts +2 -1
- package/node_modules/@playdrop/api-client/dist/domains/apps.d.ts.map +1 -1
- package/node_modules/@playdrop/api-client/dist/domains/apps.js +11 -0
- package/node_modules/@playdrop/api-client/dist/index.d.ts +2 -1
- package/node_modules/@playdrop/api-client/dist/index.d.ts.map +1 -1
- package/node_modules/@playdrop/api-client/dist/index.js +5 -0
- package/node_modules/@playdrop/config/client-meta.json +1 -1
- package/node_modules/@playdrop/types/dist/api.d.ts +13 -0
- package/node_modules/@playdrop/types/dist/api.d.ts.map +1 -1
- package/node_modules/@playdrop/types/dist/version.d.ts +6 -12
- package/node_modules/@playdrop/types/dist/version.d.ts.map +1 -1
- package/node_modules/@playdrop/types/dist/version.js +48 -88
- package/package.json +1 -1
package/dist/commands/upload.js
CHANGED
|
@@ -1,40 +1,11 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
-
if (k2 === undefined) k2 = k;
|
|
4
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
-
}
|
|
8
|
-
Object.defineProperty(o, k2, desc);
|
|
9
|
-
}) : (function(o, m, k, k2) {
|
|
10
|
-
if (k2 === undefined) k2 = k;
|
|
11
|
-
o[k2] = m[k];
|
|
12
|
-
}));
|
|
13
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
-
}) : function(o, v) {
|
|
16
|
-
o["default"] = v;
|
|
17
|
-
});
|
|
18
|
-
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
-
var ownKeys = function(o) {
|
|
20
|
-
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
-
var ar = [];
|
|
22
|
-
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
-
return ar;
|
|
24
|
-
};
|
|
25
|
-
return ownKeys(o);
|
|
26
|
-
};
|
|
27
|
-
return function (mod) {
|
|
28
|
-
if (mod && mod.__esModule) return mod;
|
|
29
|
-
var result = {};
|
|
30
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
-
__setModuleDefault(result, mod);
|
|
32
|
-
return result;
|
|
33
|
-
};
|
|
34
|
-
})();
|
|
35
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.resolveUploadOwner = resolveUploadOwner;
|
|
4
|
+
exports.resolveDefaultUploadCreator = resolveDefaultUploadCreator;
|
|
36
5
|
exports.upload = upload;
|
|
6
|
+
exports.resolveWorkerPlaydropAssetRequirement = resolveWorkerPlaydropAssetRequirement;
|
|
37
7
|
exports.assertTaskPlaytestEvidenceManifest = assertTaskPlaytestEvidenceManifest;
|
|
8
|
+
exports.assertWorkerAppLocalUploadPreflight = assertWorkerAppLocalUploadPreflight;
|
|
38
9
|
exports.publishWorkerAppProject = publishWorkerAppProject;
|
|
39
10
|
const node_crypto_1 = require("node:crypto");
|
|
40
11
|
const node_fs_1 = require("node:fs");
|
|
@@ -53,6 +24,7 @@ const uploadLog_1 = require("../uploadLog");
|
|
|
53
24
|
const upload_content_1 = require("./upload-content");
|
|
54
25
|
const appUrls_1 = require("../appUrls");
|
|
55
26
|
const devRuntimeAssets_1 = require("./devRuntimeAssets");
|
|
27
|
+
const ownedRuntimeImageValidation_1 = require("./ownedRuntimeImageValidation");
|
|
56
28
|
const listingPreflight_1 = require("../listingPreflight");
|
|
57
29
|
const upload_graph_1 = require("./upload-graph");
|
|
58
30
|
function normalizeSavedItemKinds(kinds) {
|
|
@@ -222,6 +194,19 @@ function normalizeCreatorUsername(username) {
|
|
|
222
194
|
}
|
|
223
195
|
return trimmed;
|
|
224
196
|
}
|
|
197
|
+
function resolveUploadOwner(actorUsername, workspaceOwnerUsername) {
|
|
198
|
+
const workspaceOwner = workspaceOwnerUsername?.trim() ?? '';
|
|
199
|
+
if (workspaceOwner) {
|
|
200
|
+
return workspaceOwner;
|
|
201
|
+
}
|
|
202
|
+
const actor = actorUsername?.trim() ?? '';
|
|
203
|
+
return actor || 'unknown';
|
|
204
|
+
}
|
|
205
|
+
function resolveDefaultUploadCreator(owner, actorUsername) {
|
|
206
|
+
return normalizeCreatorUsername(owner ?? null)
|
|
207
|
+
?? normalizeCreatorUsername(actorUsername ?? null)
|
|
208
|
+
?? 'unknown';
|
|
209
|
+
}
|
|
225
210
|
function isNotFoundApiError(error) {
|
|
226
211
|
return error instanceof types_1.ApiError && error.status === 404;
|
|
227
212
|
}
|
|
@@ -512,7 +497,10 @@ function validateTagRefsForTarget(tagRegistry, target) {
|
|
|
512
497
|
function collectTaskTagValidationTargets(task) {
|
|
513
498
|
if (task.kind === 'app') {
|
|
514
499
|
return [
|
|
515
|
-
{
|
|
500
|
+
{
|
|
501
|
+
tags: Array.from(new Set([...task.tags, ...Object.values(task.design ?? {})])),
|
|
502
|
+
targetKind: 'APP',
|
|
503
|
+
},
|
|
516
504
|
...task.ownedAssets.map((ownedAsset) => ({
|
|
517
505
|
tags: ownedAsset.tags,
|
|
518
506
|
targetKind: 'ASSET',
|
|
@@ -1059,7 +1047,7 @@ async function flushGraphState(client, graphState, results) {
|
|
|
1059
1047
|
}
|
|
1060
1048
|
async function processUploadTasks(client, tasks, owner, ownerUsername, currentUserRole, localTagGroups, currentUser, token, warnings, apiBase, webBase, options) {
|
|
1061
1049
|
const portalBase = normalizePortalBase(webBase);
|
|
1062
|
-
const defaultCreator =
|
|
1050
|
+
const defaultCreator = resolveDefaultUploadCreator(owner, ownerUsername);
|
|
1063
1051
|
const results = [];
|
|
1064
1052
|
const preflight = await buildUploadPreflight(client, tasks, defaultCreator, currentUserRole, localTagGroups);
|
|
1065
1053
|
const sortedTasks = preflight.nodes.map((node) => node.task);
|
|
@@ -1177,9 +1165,7 @@ async function upload(pathOrName, options) {
|
|
|
1177
1165
|
}
|
|
1178
1166
|
throw error;
|
|
1179
1167
|
}
|
|
1180
|
-
const owner =
|
|
1181
|
-
? userInfo.username.trim()
|
|
1182
|
-
: 'unknown';
|
|
1168
|
+
const owner = resolveUploadOwner(userInfo.username, ctx.workspaceAuth?.config.ownerUsername);
|
|
1183
1169
|
const tagGroupLoad = (0, catalogue_1.resolveCatalogueTagGroups)(workspaceRoot);
|
|
1184
1170
|
if (tagGroupLoad.errors.length > 0) {
|
|
1185
1171
|
(0, taskSelection_1.reportTaskErrors)(tagGroupLoad.errors.map((message) => ({
|
|
@@ -1223,6 +1209,19 @@ async function upload(pathOrName, options) {
|
|
|
1223
1209
|
(0, uploadLog_1.printTaskSummary)(results, warnings, { action: 'upload', environment: env });
|
|
1224
1210
|
(0, uploadLog_1.printPublishedAppNextSteps)(results);
|
|
1225
1211
|
}
|
|
1212
|
+
function resolveWorkerPlaydropAssetRequirement(value) {
|
|
1213
|
+
const text = typeof value === 'string' ? value.toLowerCase() : '';
|
|
1214
|
+
if (!text.trim() || !/\bplay\s*drop\b|\bplaydrop\b/.test(text)) {
|
|
1215
|
+
return null;
|
|
1216
|
+
}
|
|
1217
|
+
if (/\basset\s*pack(s)?\b|\bpack(s)?\b/.test(text)) {
|
|
1218
|
+
return 'PACK';
|
|
1219
|
+
}
|
|
1220
|
+
if (/\basset(s)?\b|\bsprite(s)?\b|\bmodel(s)?\b|\bart\b|\bcharacter(s)?\b|\bprop(s)?\b/.test(text)) {
|
|
1221
|
+
return 'ASSET_OR_PACK';
|
|
1222
|
+
}
|
|
1223
|
+
return null;
|
|
1224
|
+
}
|
|
1226
1225
|
const WORKER_SUPERVISOR_SOURCE_EXCLUDES = [
|
|
1227
1226
|
'PLAYDROP_TASK.md',
|
|
1228
1227
|
'.playdrop/',
|
|
@@ -1234,8 +1233,6 @@ const WORKER_SUPERVISOR_BUNDLE_EXCLUDES = [
|
|
|
1234
1233
|
'assets/marketing/',
|
|
1235
1234
|
'listing/',
|
|
1236
1235
|
];
|
|
1237
|
-
const OWNED_RUNTIME_IMAGE_CHECK_SIZE = 96;
|
|
1238
|
-
const OWNED_RUNTIME_IMAGE_GRID_CHECK_SIZE = 512;
|
|
1239
1236
|
function appTaskSatisfiesPlaydropAssetRequirement(task, requirement) {
|
|
1240
1237
|
if (!requirement) {
|
|
1241
1238
|
return true;
|
|
@@ -1253,361 +1250,6 @@ function appTaskSatisfiesPlaydropAssetRequirement(task, requirement) {
|
|
|
1253
1250
|
});
|
|
1254
1251
|
return hasFirstPartyPack || hasFirstPartyAsset || task.ownedAssets.length > 0;
|
|
1255
1252
|
}
|
|
1256
|
-
function isUnsafeDirectPlaydropAiImageAsset(detail, parsedRevision) {
|
|
1257
|
-
const asset = detail?.asset;
|
|
1258
|
-
if (!asset || asset.category !== 'IMAGE') {
|
|
1259
|
-
return false;
|
|
1260
|
-
}
|
|
1261
|
-
const currentVersion = asset.currentVersion;
|
|
1262
|
-
if (!currentVersion || Number(currentVersion.revision) !== parsedRevision) {
|
|
1263
|
-
return false;
|
|
1264
|
-
}
|
|
1265
|
-
const sourceKind = typeof currentVersion.sourceKind === 'string' ? currentVersion.sourceKind.toUpperCase() : '';
|
|
1266
|
-
if (sourceKind !== 'AI') {
|
|
1267
|
-
return false;
|
|
1268
|
-
}
|
|
1269
|
-
const format = typeof currentVersion.format === 'string' ? currentVersion.format.toUpperCase() : '';
|
|
1270
|
-
const files = Array.isArray(currentVersion.fileManifest?.files) ? currentVersion.fileManifest.files : [];
|
|
1271
|
-
const primary = files.find((file) => file?.role === 'primary');
|
|
1272
|
-
const primaryContentType = typeof primary?.contentType === 'string' ? primary.contentType.toLowerCase() : '';
|
|
1273
|
-
return format === 'JPEG' || primaryContentType === 'image/jpeg' || primaryContentType === 'image/jpg';
|
|
1274
|
-
}
|
|
1275
|
-
async function assertNewGameDirectPlaydropAssetsAreRuntimeSafe(input) {
|
|
1276
|
-
const unsafeRefs = [];
|
|
1277
|
-
for (const dependency of input.task.uses.assets) {
|
|
1278
|
-
const parsed = (0, types_1.parseContentVersionRef)(dependency.ref);
|
|
1279
|
-
if (!parsed || parsed.kind !== 'asset' || parsed.creatorUsername.toLowerCase() !== 'playdrop') {
|
|
1280
|
-
continue;
|
|
1281
|
-
}
|
|
1282
|
-
const detail = await input.client.fetchAssetBySlug(parsed.creatorUsername, parsed.name);
|
|
1283
|
-
if (isUnsafeDirectPlaydropAiImageAsset(detail, parsed.revision)) {
|
|
1284
|
-
unsafeRefs.push((0, types_1.formatContentVersionRef)(parsed));
|
|
1285
|
-
}
|
|
1286
|
-
}
|
|
1287
|
-
if (unsafeRefs.length > 0) {
|
|
1288
|
-
throw new Error(`agent_task_playdrop_direct_ai_image_asset_unsafe: direct PlayDrop AI JPEG image assets cannot be used as new-game runtime sprites because their backgrounds are baked into the image. Use a PlayDrop asset pack or create clean owned PNG assets through the PlayDrop make-assets workflow instead. Unsafe refs: ${unsafeRefs.join(', ')}`);
|
|
1289
|
-
}
|
|
1290
|
-
}
|
|
1291
|
-
function isOwnedRuntimeImageAsset(task, ownedAsset) {
|
|
1292
|
-
if (!ownedAsset.runtimeKey || ownedAsset.category !== 'IMAGE') {
|
|
1293
|
-
return false;
|
|
1294
|
-
}
|
|
1295
|
-
const primaryPath = ownedAsset.filePaths?.primary;
|
|
1296
|
-
if (!primaryPath) {
|
|
1297
|
-
return false;
|
|
1298
|
-
}
|
|
1299
|
-
const relativePath = primaryPath.slice((0, node_path_1.resolve)(task.projectDir).length + 1).replace(/\\/g, '/');
|
|
1300
|
-
return !relativePath.startsWith('assets/marketing/') && !relativePath.startsWith('listing/');
|
|
1301
|
-
}
|
|
1302
|
-
function isBackgroundLikeOwnedRuntimeImageAsset(ownedAsset) {
|
|
1303
|
-
const value = `${ownedAsset.name ?? ''} ${ownedAsset.runtimeKey ?? ''}`.toLowerCase();
|
|
1304
|
-
return /\b(background|backdrop|scene|sky|floor|ground|wall|terrain|landscape|level|arena)\b/i.test(value);
|
|
1305
|
-
}
|
|
1306
|
-
function looksLikeBakedCheckerboardPreview(input) {
|
|
1307
|
-
const { data, width, height, channels } = input;
|
|
1308
|
-
if (width < 24 || height < 24 || channels < 4) {
|
|
1309
|
-
return false;
|
|
1310
|
-
}
|
|
1311
|
-
const border = Math.max(8, Math.floor(Math.min(width, height) * 0.16));
|
|
1312
|
-
let sampled = 0;
|
|
1313
|
-
let transparent = 0;
|
|
1314
|
-
let opaqueGray = 0;
|
|
1315
|
-
let lowGray = 0;
|
|
1316
|
-
let highGray = 0;
|
|
1317
|
-
for (let y = 0; y < height; y += 1) {
|
|
1318
|
-
for (let x = 0; x < width; x += 1) {
|
|
1319
|
-
const inBorder = x < border || y < border || x >= width - border || y >= height - border;
|
|
1320
|
-
if (!inBorder) {
|
|
1321
|
-
continue;
|
|
1322
|
-
}
|
|
1323
|
-
sampled += 1;
|
|
1324
|
-
const offset = (y * width + x) * channels;
|
|
1325
|
-
const red = data[offset] ?? 0;
|
|
1326
|
-
const green = data[offset + 1] ?? 0;
|
|
1327
|
-
const blue = data[offset + 2] ?? 0;
|
|
1328
|
-
const alpha = data[offset + 3] ?? 255;
|
|
1329
|
-
if (alpha < 32) {
|
|
1330
|
-
transparent += 1;
|
|
1331
|
-
continue;
|
|
1332
|
-
}
|
|
1333
|
-
const brightness = (red + green + blue) / 3;
|
|
1334
|
-
const grayish = Math.max(Math.abs(red - green), Math.abs(red - blue), Math.abs(green - blue)) <= 10;
|
|
1335
|
-
if (alpha > 240 && grayish && brightness >= 70 && brightness <= 210) {
|
|
1336
|
-
opaqueGray += 1;
|
|
1337
|
-
if (brightness < 125) {
|
|
1338
|
-
lowGray += 1;
|
|
1339
|
-
}
|
|
1340
|
-
else if (brightness > 145) {
|
|
1341
|
-
highGray += 1;
|
|
1342
|
-
}
|
|
1343
|
-
}
|
|
1344
|
-
}
|
|
1345
|
-
}
|
|
1346
|
-
if (sampled === 0 || transparent / sampled > 0.18) {
|
|
1347
|
-
return false;
|
|
1348
|
-
}
|
|
1349
|
-
return opaqueGray / sampled > 0.58
|
|
1350
|
-
&& lowGray / sampled > 0.08
|
|
1351
|
-
&& highGray / sampled > 0.18;
|
|
1352
|
-
}
|
|
1353
|
-
function looksLikeBakedSolidMattePreview(input) {
|
|
1354
|
-
const { data, width, height, channels } = input;
|
|
1355
|
-
if (width < 24 || height < 24 || channels < 4) {
|
|
1356
|
-
return false;
|
|
1357
|
-
}
|
|
1358
|
-
const border = Math.max(8, Math.floor(Math.min(width, height) * 0.18));
|
|
1359
|
-
let sampled = 0;
|
|
1360
|
-
let transparent = 0;
|
|
1361
|
-
let matteLike = 0;
|
|
1362
|
-
for (let y = 0; y < height; y += 1) {
|
|
1363
|
-
for (let x = 0; x < width; x += 1) {
|
|
1364
|
-
const inBorder = x < border || y < border || x >= width - border || y >= height - border;
|
|
1365
|
-
if (!inBorder) {
|
|
1366
|
-
continue;
|
|
1367
|
-
}
|
|
1368
|
-
sampled += 1;
|
|
1369
|
-
const offset = (y * width + x) * channels;
|
|
1370
|
-
const red = data[offset] ?? 0;
|
|
1371
|
-
const green = data[offset + 1] ?? 0;
|
|
1372
|
-
const blue = data[offset + 2] ?? 0;
|
|
1373
|
-
const alpha = data[offset + 3] ?? 255;
|
|
1374
|
-
if (alpha < 32) {
|
|
1375
|
-
transparent += 1;
|
|
1376
|
-
continue;
|
|
1377
|
-
}
|
|
1378
|
-
const dominant = Math.max(red, green, blue);
|
|
1379
|
-
const weakest = Math.min(red, green, blue);
|
|
1380
|
-
const chroma = dominant - weakest;
|
|
1381
|
-
const brightGreenMatte = green >= 220 && green - Math.max(red, blue) >= 65 && Math.max(red, blue) >= 70;
|
|
1382
|
-
const brightRedMatte = red >= 220 && red - Math.max(green, blue) >= 65;
|
|
1383
|
-
const brightBlueMatte = blue >= 220 && blue - Math.max(red, green) >= 65;
|
|
1384
|
-
const brightTwoChannelMatte = dominant >= 220
|
|
1385
|
-
&& chroma >= 65
|
|
1386
|
-
&& ((red >= 220 && green >= 220) || (red >= 220 && blue >= 220) || (green >= 220 && blue >= 220));
|
|
1387
|
-
if (brightGreenMatte || brightRedMatte || brightBlueMatte || brightTwoChannelMatte) {
|
|
1388
|
-
matteLike += 1;
|
|
1389
|
-
}
|
|
1390
|
-
}
|
|
1391
|
-
}
|
|
1392
|
-
if (sampled === 0 || transparent / sampled > 0.18) {
|
|
1393
|
-
return false;
|
|
1394
|
-
}
|
|
1395
|
-
return matteLike / sampled > 0.58;
|
|
1396
|
-
}
|
|
1397
|
-
function looksLikeBakedNeutralSolidMattePreview(input) {
|
|
1398
|
-
const { data, width, height, channels } = input;
|
|
1399
|
-
if (width < 24 || height < 24 || channels < 4) {
|
|
1400
|
-
return false;
|
|
1401
|
-
}
|
|
1402
|
-
const border = Math.max(8, Math.floor(Math.min(width, height) * 0.18));
|
|
1403
|
-
let sampled = 0;
|
|
1404
|
-
let transparent = 0;
|
|
1405
|
-
let neutralMatteLike = 0;
|
|
1406
|
-
for (let y = 0; y < height; y += 1) {
|
|
1407
|
-
for (let x = 0; x < width; x += 1) {
|
|
1408
|
-
const inBorder = x < border || y < border || x >= width - border || y >= height - border;
|
|
1409
|
-
if (!inBorder) {
|
|
1410
|
-
continue;
|
|
1411
|
-
}
|
|
1412
|
-
sampled += 1;
|
|
1413
|
-
const offset = (y * width + x) * channels;
|
|
1414
|
-
const red = data[offset] ?? 0;
|
|
1415
|
-
const green = data[offset + 1] ?? 0;
|
|
1416
|
-
const blue = data[offset + 2] ?? 0;
|
|
1417
|
-
const alpha = data[offset + 3] ?? 255;
|
|
1418
|
-
if (alpha < 32) {
|
|
1419
|
-
transparent += 1;
|
|
1420
|
-
continue;
|
|
1421
|
-
}
|
|
1422
|
-
const brightness = (red + green + blue) / 3;
|
|
1423
|
-
const grayish = Math.max(Math.abs(red - green), Math.abs(red - blue), Math.abs(green - blue)) <= 12;
|
|
1424
|
-
if (alpha > 240 && grayish && (brightness >= 235 || brightness <= 20)) {
|
|
1425
|
-
neutralMatteLike += 1;
|
|
1426
|
-
}
|
|
1427
|
-
}
|
|
1428
|
-
}
|
|
1429
|
-
if (sampled === 0 || transparent / sampled > 0.18) {
|
|
1430
|
-
return false;
|
|
1431
|
-
}
|
|
1432
|
-
return neutralMatteLike / sampled > 0.58;
|
|
1433
|
-
}
|
|
1434
|
-
function looksLikeResidualMatteGridArtifact(input) {
|
|
1435
|
-
const { data, width, height, channels } = input;
|
|
1436
|
-
if (width < 48 || height < 48 || channels < 4) {
|
|
1437
|
-
return false;
|
|
1438
|
-
}
|
|
1439
|
-
const rowCounts = new Uint32Array(height);
|
|
1440
|
-
const columnCounts = new Uint32Array(width);
|
|
1441
|
-
let matteLike = 0;
|
|
1442
|
-
for (let y = 0; y < height; y += 1) {
|
|
1443
|
-
for (let x = 0; x < width; x += 1) {
|
|
1444
|
-
const offset = (y * width + x) * channels;
|
|
1445
|
-
const red = data[offset] ?? 0;
|
|
1446
|
-
const green = data[offset + 1] ?? 0;
|
|
1447
|
-
const blue = data[offset + 2] ?? 0;
|
|
1448
|
-
const alpha = data[offset + 3] ?? 255;
|
|
1449
|
-
if (alpha < 24 || alpha > 190) {
|
|
1450
|
-
continue;
|
|
1451
|
-
}
|
|
1452
|
-
const greenMatte = green >= 35 && green - Math.max(red, blue) >= 25 && red <= 110 && blue <= 110;
|
|
1453
|
-
const redMatte = red >= 35 && red - Math.max(green, blue) >= 25 && green <= 120 && blue <= 120;
|
|
1454
|
-
const blueMatte = blue >= 35 && blue - Math.max(red, green) >= 25 && red <= 120 && green <= 120;
|
|
1455
|
-
const purpleMatte = red >= 35 && blue >= 35 && Math.min(red, blue) - green >= 25 && green <= 120;
|
|
1456
|
-
if (!greenMatte && !redMatte && !blueMatte && !purpleMatte) {
|
|
1457
|
-
continue;
|
|
1458
|
-
}
|
|
1459
|
-
matteLike += 1;
|
|
1460
|
-
rowCounts[y] += 1;
|
|
1461
|
-
columnCounts[x] += 1;
|
|
1462
|
-
}
|
|
1463
|
-
}
|
|
1464
|
-
const matteRatio = matteLike / (width * height);
|
|
1465
|
-
if (matteRatio < 0.0015) {
|
|
1466
|
-
return false;
|
|
1467
|
-
}
|
|
1468
|
-
let rowsWithLines = 0;
|
|
1469
|
-
for (const count of rowCounts) {
|
|
1470
|
-
if (count / width >= 0.12) {
|
|
1471
|
-
rowsWithLines += 1;
|
|
1472
|
-
}
|
|
1473
|
-
}
|
|
1474
|
-
let columnsWithLines = 0;
|
|
1475
|
-
for (const count of columnCounts) {
|
|
1476
|
-
if (count / height >= 0.12) {
|
|
1477
|
-
columnsWithLines += 1;
|
|
1478
|
-
}
|
|
1479
|
-
}
|
|
1480
|
-
return rowsWithLines + columnsWithLines >= 3
|
|
1481
|
-
&& (rowsWithLines >= 2 || columnsWithLines >= 2);
|
|
1482
|
-
}
|
|
1483
|
-
function looksLikeResidualTransparentGridLineArtifact(input) {
|
|
1484
|
-
const { data, width, height, channels } = input;
|
|
1485
|
-
if (width < 48 || height < 48 || channels < 4) {
|
|
1486
|
-
return false;
|
|
1487
|
-
}
|
|
1488
|
-
const rowVisibleCounts = new Uint32Array(height);
|
|
1489
|
-
const rowSemiCounts = new Uint32Array(height);
|
|
1490
|
-
const columnVisibleCounts = new Uint32Array(width);
|
|
1491
|
-
const columnSemiCounts = new Uint32Array(width);
|
|
1492
|
-
for (let y = 0; y < height; y += 1) {
|
|
1493
|
-
for (let x = 0; x < width; x += 1) {
|
|
1494
|
-
const offset = (y * width + x) * channels;
|
|
1495
|
-
const alpha = data[offset + 3] ?? 255;
|
|
1496
|
-
if (alpha <= 0) {
|
|
1497
|
-
continue;
|
|
1498
|
-
}
|
|
1499
|
-
rowVisibleCounts[y] += 1;
|
|
1500
|
-
columnVisibleCounts[x] += 1;
|
|
1501
|
-
if (alpha < 255) {
|
|
1502
|
-
rowSemiCounts[y] += 1;
|
|
1503
|
-
columnSemiCounts[x] += 1;
|
|
1504
|
-
}
|
|
1505
|
-
}
|
|
1506
|
-
}
|
|
1507
|
-
let rowsWithLines = 0;
|
|
1508
|
-
for (let row = 0; row < height; row += 1) {
|
|
1509
|
-
const visibleRatio = rowVisibleCounts[row] / width;
|
|
1510
|
-
const semiRatio = rowSemiCounts[row] / width;
|
|
1511
|
-
if (visibleRatio >= 0.94 && semiRatio >= 0.72) {
|
|
1512
|
-
rowsWithLines += 1;
|
|
1513
|
-
}
|
|
1514
|
-
}
|
|
1515
|
-
let columnsWithLines = 0;
|
|
1516
|
-
for (let column = 0; column < width; column += 1) {
|
|
1517
|
-
const visibleRatio = columnVisibleCounts[column] / height;
|
|
1518
|
-
const semiRatio = columnSemiCounts[column] / height;
|
|
1519
|
-
if (visibleRatio >= 0.94 && semiRatio >= 0.72) {
|
|
1520
|
-
columnsWithLines += 1;
|
|
1521
|
-
}
|
|
1522
|
-
}
|
|
1523
|
-
return rowsWithLines + columnsWithLines >= 3
|
|
1524
|
-
&& (rowsWithLines >= 2 || columnsWithLines >= 2);
|
|
1525
|
-
}
|
|
1526
|
-
async function imageHasBakedPreviewBackground(filePath, options = {}) {
|
|
1527
|
-
let sharp;
|
|
1528
|
-
try {
|
|
1529
|
-
const sharpModule = await Promise.resolve().then(() => __importStar(require('sharp')));
|
|
1530
|
-
sharp = sharpModule.default ?? sharpModule;
|
|
1531
|
-
}
|
|
1532
|
-
catch {
|
|
1533
|
-
return false;
|
|
1534
|
-
}
|
|
1535
|
-
try {
|
|
1536
|
-
const { data, info } = await sharp(filePath, { failOn: 'error' })
|
|
1537
|
-
.rotate()
|
|
1538
|
-
.resize(OWNED_RUNTIME_IMAGE_CHECK_SIZE, OWNED_RUNTIME_IMAGE_CHECK_SIZE, { fit: 'fill' })
|
|
1539
|
-
.ensureAlpha()
|
|
1540
|
-
.raw()
|
|
1541
|
-
.toBuffer({ resolveWithObject: true });
|
|
1542
|
-
const hasPreviewBackground = looksLikeBakedCheckerboardPreview({
|
|
1543
|
-
data,
|
|
1544
|
-
width: info.width,
|
|
1545
|
-
height: info.height,
|
|
1546
|
-
channels: info.channels,
|
|
1547
|
-
}) || looksLikeBakedSolidMattePreview({
|
|
1548
|
-
data,
|
|
1549
|
-
width: info.width,
|
|
1550
|
-
height: info.height,
|
|
1551
|
-
channels: info.channels,
|
|
1552
|
-
}) || (!options.allowNeutralSolidMatte && looksLikeBakedNeutralSolidMattePreview({
|
|
1553
|
-
data,
|
|
1554
|
-
width: info.width,
|
|
1555
|
-
height: info.height,
|
|
1556
|
-
channels: info.channels,
|
|
1557
|
-
}));
|
|
1558
|
-
if (hasPreviewBackground) {
|
|
1559
|
-
return true;
|
|
1560
|
-
}
|
|
1561
|
-
}
|
|
1562
|
-
catch {
|
|
1563
|
-
return false;
|
|
1564
|
-
}
|
|
1565
|
-
try {
|
|
1566
|
-
const { data, info } = await sharp(filePath, { failOn: 'error' })
|
|
1567
|
-
.rotate()
|
|
1568
|
-
.resize(OWNED_RUNTIME_IMAGE_GRID_CHECK_SIZE, OWNED_RUNTIME_IMAGE_GRID_CHECK_SIZE, { fit: 'inside', withoutEnlargement: true })
|
|
1569
|
-
.ensureAlpha()
|
|
1570
|
-
.raw()
|
|
1571
|
-
.toBuffer({ resolveWithObject: true });
|
|
1572
|
-
if (looksLikeResidualMatteGridArtifact({
|
|
1573
|
-
data,
|
|
1574
|
-
width: info.width,
|
|
1575
|
-
height: info.height,
|
|
1576
|
-
channels: info.channels,
|
|
1577
|
-
}) || looksLikeResidualTransparentGridLineArtifact({
|
|
1578
|
-
data,
|
|
1579
|
-
width: info.width,
|
|
1580
|
-
height: info.height,
|
|
1581
|
-
channels: info.channels,
|
|
1582
|
-
})) {
|
|
1583
|
-
return true;
|
|
1584
|
-
}
|
|
1585
|
-
}
|
|
1586
|
-
catch {
|
|
1587
|
-
return false;
|
|
1588
|
-
}
|
|
1589
|
-
return false;
|
|
1590
|
-
}
|
|
1591
|
-
async function assertNewGameOwnedRuntimeImageAssetsAreClean(task) {
|
|
1592
|
-
const unsafeAssets = [];
|
|
1593
|
-
for (const ownedAsset of task.ownedAssets) {
|
|
1594
|
-
if (!isOwnedRuntimeImageAsset(task, ownedAsset)) {
|
|
1595
|
-
continue;
|
|
1596
|
-
}
|
|
1597
|
-
const primaryPath = ownedAsset.filePaths?.primary;
|
|
1598
|
-
if (!primaryPath) {
|
|
1599
|
-
continue;
|
|
1600
|
-
}
|
|
1601
|
-
if (await imageHasBakedPreviewBackground(primaryPath, {
|
|
1602
|
-
allowNeutralSolidMatte: isBackgroundLikeOwnedRuntimeImageAsset(ownedAsset),
|
|
1603
|
-
})) {
|
|
1604
|
-
unsafeAssets.push(`${ownedAsset.name}:${ownedAsset.runtimeKey}`);
|
|
1605
|
-
}
|
|
1606
|
-
}
|
|
1607
|
-
if (unsafeAssets.length > 0) {
|
|
1608
|
-
throw new Error(`agent_task_owned_image_asset_checkerboard_background: owned runtime image assets must not contain baked transparency checkerboard, matte-color, or preview backgrounds. Recreate the asset through the PlayDrop make-assets workflow, use a cleaner PlayDrop asset, or use the image as non-runtime listing art only. Unsafe owned assets: ${unsafeAssets.join(', ')}`);
|
|
1609
|
-
}
|
|
1610
|
-
}
|
|
1611
1253
|
const RUNTIME_SOURCE_EXTENSIONS = new Set([
|
|
1612
1254
|
'.html',
|
|
1613
1255
|
'.js',
|
|
@@ -1700,24 +1342,11 @@ function assertNoLaunchCheckConditionalSource(runtimeSource) {
|
|
|
1700
1342
|
throw new Error(`agent_task_launch_check_conditional_disallowed: game source must not sniff validation or local route context. First match at ${environmentSniff.location}.`);
|
|
1701
1343
|
}
|
|
1702
1344
|
}
|
|
1703
|
-
function hasProceduralAssetStrategy(task) {
|
|
1704
|
-
const strategy = task.design?.assetStrategy;
|
|
1705
|
-
return strategy?.value === 'procedural';
|
|
1706
|
-
}
|
|
1707
1345
|
function hasDeclaredRuntimeAssetDependency(task) {
|
|
1708
1346
|
return task.uses.packs.length > 0
|
|
1709
1347
|
|| task.uses.assets.length > 0
|
|
1710
1348
|
|| task.ownedAssets.some((ownedAsset) => !isListingOwnedAsset(task, ownedAsset));
|
|
1711
1349
|
}
|
|
1712
|
-
function assertNewGameAssetStrategy(task) {
|
|
1713
|
-
if (hasDeclaredRuntimeAssetDependency(task)) {
|
|
1714
|
-
return;
|
|
1715
|
-
}
|
|
1716
|
-
if (hasProceduralAssetStrategy(task)) {
|
|
1717
|
-
return;
|
|
1718
|
-
}
|
|
1719
|
-
throw new Error('agent_task_asset_strategy_missing: new game tasks must either declare true runtime assets/packs or set design.assetStrategy to { "value": "procedural", "note": "..." } for an honest procedural draft.');
|
|
1720
|
-
}
|
|
1721
1350
|
function isListingOwnedAsset(task, ownedAsset) {
|
|
1722
1351
|
const primaryPath = ownedAsset.filePaths?.primary;
|
|
1723
1352
|
if (!primaryPath) {
|
|
@@ -1966,7 +1595,6 @@ async function assertNewGameDeclaredAssetsAreUsedAtRuntime(input) {
|
|
|
1966
1595
|
const runtimeSource = readRuntimeSourceText(input.task.projectDir);
|
|
1967
1596
|
const source = runtimeSource.source;
|
|
1968
1597
|
assertNoLaunchCheckConditionalSource(runtimeSource);
|
|
1969
|
-
assertNewGameAssetStrategy(input.task);
|
|
1970
1598
|
if (!hasDeclaredRuntimeAssetDependency(input.task)) {
|
|
1971
1599
|
return;
|
|
1972
1600
|
}
|
|
@@ -1984,8 +1612,7 @@ async function assertNewGameDeclaredAssetsAreUsedAtRuntime(input) {
|
|
|
1984
1612
|
}
|
|
1985
1613
|
const manifest = input.task.ownedAssets.length > 0
|
|
1986
1614
|
? await (0, devRuntimeAssets_1.buildDevRuntimeAssetManifest)({
|
|
1987
|
-
|
|
1988
|
-
apiBase: input.apiBase,
|
|
1615
|
+
apiBase: 'http://127.0.0.1',
|
|
1989
1616
|
task: {
|
|
1990
1617
|
...input.task,
|
|
1991
1618
|
uses: { assets: [], packs: [] },
|
|
@@ -2183,6 +1810,35 @@ function readRuntimeSourceLastModifiedAt(rootDir) {
|
|
|
2183
1810
|
walk(root);
|
|
2184
1811
|
return latest > 0 ? new Date(latest).toISOString() : null;
|
|
2185
1812
|
}
|
|
1813
|
+
async function assertWorkerAppLocalUploadPreflight(input) {
|
|
1814
|
+
if (input.kind === 'GAME_UPDATE') {
|
|
1815
|
+
return assertTaskPlaytestEvidenceManifest(input.task);
|
|
1816
|
+
}
|
|
1817
|
+
(0, listingPreflight_1.assertAgentGameHeroListingPreflight)(input.task);
|
|
1818
|
+
if (input.kind === 'NEW_GAME') {
|
|
1819
|
+
(0, listingPreflight_1.assertAgentNewGameArtDirectionPreflight)(input.task);
|
|
1820
|
+
}
|
|
1821
|
+
if (!appTaskSatisfiesPlaydropAssetRequirement(input.task, input.playdropAssetRequirement)) {
|
|
1822
|
+
throw new Error('agent_task_playdrop_asset_dependency_missing: the creator asked for PlayDrop assets, but catalogue.json does not declare a matching uses.assets, uses.packs, or ownedAssets dependency.');
|
|
1823
|
+
}
|
|
1824
|
+
await (0, ownedRuntimeImageValidation_1.assertOwnedRuntimeImagesAreValid)(input.task);
|
|
1825
|
+
assertRequestedPlaydropAssetsAreUsedAtRuntime({
|
|
1826
|
+
task: input.task,
|
|
1827
|
+
requirement: input.playdropAssetRequirement,
|
|
1828
|
+
creatorRequest: input.creatorRequest,
|
|
1829
|
+
});
|
|
1830
|
+
await assertNewGameDeclaredAssetsAreUsedAtRuntime({
|
|
1831
|
+
creatorUsername: input.creatorUsername?.trim() ?? '',
|
|
1832
|
+
task: input.task,
|
|
1833
|
+
});
|
|
1834
|
+
(0, listingPreflight_1.assertAgentGameScreenshotListingPreflight)(input.task);
|
|
1835
|
+
(0, listingPreflight_1.assertAgentGameCaptureReportPreflight)(input.task, {
|
|
1836
|
+
requireCaptureReport: input.executionTarget === 'FIRST_PARTY',
|
|
1837
|
+
});
|
|
1838
|
+
return assertTaskPlaytestEvidenceManifest(input.task, {
|
|
1839
|
+
requireOutcomeProof: input.kind === 'NEW_GAME',
|
|
1840
|
+
});
|
|
1841
|
+
}
|
|
2186
1842
|
// Task upload path used by "playdrop task upload": same internal pipeline as
|
|
2187
1843
|
// "playdrop project publish", but always as one PRIVATE draft version for the
|
|
2188
1844
|
// task's creator, with browser launch checks on before the version is created.
|
|
@@ -2229,38 +1885,14 @@ async function publishWorkerAppProject(input) {
|
|
|
2229
1885
|
throw new Error(`agent_task_remix_ref_mismatch:${catalogueRemixRef}:${expectedRemixRef}`);
|
|
2230
1886
|
}
|
|
2231
1887
|
}
|
|
2232
|
-
|
|
2233
|
-
assertTaskPlaytestEvidenceManifest(task);
|
|
2234
|
-
}
|
|
2235
|
-
if (input.kind === 'NEW_GAME' || input.kind === 'REMIX_GAME') {
|
|
2236
|
-
(0, listingPreflight_1.assertAgentGameHeroListingPreflight)(task);
|
|
2237
|
-
}
|
|
2238
|
-
if (input.kind === 'NEW_GAME') {
|
|
2239
|
-
(0, listingPreflight_1.assertAgentNewGameArtDirectionPreflight)(task);
|
|
2240
|
-
}
|
|
2241
|
-
if (!appTaskSatisfiesPlaydropAssetRequirement(task, input.playdropAssetRequirement)) {
|
|
2242
|
-
throw new Error('agent_task_playdrop_asset_dependency_missing: the creator asked for PlayDrop assets, but catalogue.json does not declare a matching uses.assets, uses.packs, or ownedAssets dependency.');
|
|
2243
|
-
}
|
|
2244
|
-
if (input.kind === 'NEW_GAME' || input.kind === 'REMIX_GAME') {
|
|
2245
|
-
await assertNewGameDirectPlaydropAssetsAreRuntimeSafe({
|
|
2246
|
-
client: input.client,
|
|
2247
|
-
task,
|
|
2248
|
-
});
|
|
2249
|
-
await assertNewGameOwnedRuntimeImageAssetsAreClean(task);
|
|
2250
|
-
}
|
|
2251
|
-
assertRequestedPlaydropAssetsAreUsedAtRuntime({
|
|
1888
|
+
const playtestProof = await assertWorkerAppLocalUploadPreflight({
|
|
2252
1889
|
task,
|
|
2253
|
-
|
|
1890
|
+
kind: input.kind,
|
|
1891
|
+
executionTarget: input.executionTarget,
|
|
1892
|
+
creatorUsername,
|
|
1893
|
+
playdropAssetRequirement: input.playdropAssetRequirement,
|
|
2254
1894
|
creatorRequest: input.creatorRequest,
|
|
2255
1895
|
});
|
|
2256
|
-
if (input.kind === 'NEW_GAME' || input.kind === 'REMIX_GAME') {
|
|
2257
|
-
await assertNewGameDeclaredAssetsAreUsedAtRuntime({
|
|
2258
|
-
client: input.client,
|
|
2259
|
-
apiBase: input.apiBase,
|
|
2260
|
-
creatorUsername,
|
|
2261
|
-
task,
|
|
2262
|
-
});
|
|
2263
|
-
}
|
|
2264
1896
|
await assertDeclared3dPlaydropPacksContainRuntimeModels({
|
|
2265
1897
|
client: input.client,
|
|
2266
1898
|
task,
|
|
@@ -2284,13 +1916,6 @@ async function publishWorkerAppProject(input) {
|
|
|
2284
1916
|
taskId: input.taskId,
|
|
2285
1917
|
taskToken,
|
|
2286
1918
|
};
|
|
2287
|
-
let playtestProof = null;
|
|
2288
|
-
if (input.kind === 'NEW_GAME' || input.kind === 'REMIX_GAME') {
|
|
2289
|
-
(0, listingPreflight_1.assertAgentGameScreenshotListingPreflight)(task);
|
|
2290
|
-
playtestProof = assertTaskPlaytestEvidenceManifest(task, {
|
|
2291
|
-
requireOutcomeProof: input.kind === 'NEW_GAME',
|
|
2292
|
-
});
|
|
2293
|
-
}
|
|
2294
1919
|
const { upload: uploadResult, warnings } = await (0, apps_1.runAppPipeline)(input.client, task, {
|
|
2295
1920
|
creatorUsername,
|
|
2296
1921
|
apiBase: input.apiBase,
|
|
@@ -11,6 +11,7 @@ const taskUtils_1 = require("../taskUtils");
|
|
|
11
11
|
const uploadLog_1 = require("../uploadLog");
|
|
12
12
|
const listingPreflight_1 = require("../listingPreflight");
|
|
13
13
|
const upload_content_1 = require("./upload-content");
|
|
14
|
+
const upload_1 = require("./upload");
|
|
14
15
|
function buildLocalAssetSpecLookups(tasks) {
|
|
15
16
|
const exactByRef = new Map();
|
|
16
17
|
const uniqueByNameVersion = new Map();
|
|
@@ -192,7 +193,20 @@ async function validate(pathOrName, options = {}) {
|
|
|
192
193
|
const entityId = task.name;
|
|
193
194
|
try {
|
|
194
195
|
if (task.kind === 'app') {
|
|
195
|
-
(0, listingPreflight_1.
|
|
196
|
+
const workerContext = (0, listingPreflight_1.readWorkerUploadPreflightContext)(task.projectDir);
|
|
197
|
+
if (workerContext) {
|
|
198
|
+
await (0, upload_1.assertWorkerAppLocalUploadPreflight)({
|
|
199
|
+
task,
|
|
200
|
+
kind: workerContext.kind,
|
|
201
|
+
executionTarget: workerContext.target,
|
|
202
|
+
creatorUsername: workerContext.creatorUsername,
|
|
203
|
+
creatorRequest: workerContext.creatorRequest,
|
|
204
|
+
playdropAssetRequirement: (0, upload_1.resolveWorkerPlaydropAssetRequirement)(workerContext.creatorRequest),
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
else {
|
|
208
|
+
(0, listingPreflight_1.assertProjectListingPreflight)(task);
|
|
209
|
+
}
|
|
196
210
|
await (0, apps_1.validateAppTask)(task);
|
|
197
211
|
(0, apps_1.collectAppValidationWarnings)(task, 'source').forEach((warning) => warnings.add(warning));
|
|
198
212
|
}
|
|
@@ -310,6 +310,22 @@ export declare function acquireWorkerSupervisorLock(input: {
|
|
|
310
310
|
file?: string;
|
|
311
311
|
}): WorkerSupervisorLock;
|
|
312
312
|
export declare function releaseWorkerSupervisorLock(lock: WorkerSupervisorLock): void;
|
|
313
|
+
export type TaskUploadLockOwner = {
|
|
314
|
+
schemaVersion: 1;
|
|
315
|
+
command: 'task upload';
|
|
316
|
+
pid: number;
|
|
317
|
+
processStartIdentity: string;
|
|
318
|
+
createdAt: string;
|
|
319
|
+
};
|
|
320
|
+
export type TaskUploadLock = {
|
|
321
|
+
file: string;
|
|
322
|
+
owner: TaskUploadLockOwner;
|
|
323
|
+
};
|
|
324
|
+
export declare function resolveTaskUploadLockFile(workspaceDir: string): string;
|
|
325
|
+
export declare function removeStaleTaskUploadLockIfUnchanged(file: string, observedHolder: TaskUploadLockOwner): boolean;
|
|
326
|
+
export declare function acquireTaskUploadLock(workspaceDir: string): TaskUploadLock;
|
|
327
|
+
export declare function releaseTaskUploadLock(lock: TaskUploadLock): void;
|
|
328
|
+
export declare function withTaskUploadLock<T>(workspaceDir: string, work: () => Promise<T>): Promise<T>;
|
|
313
329
|
export declare function readActivePersonalWorkerRuntimeState(): WorkerRuntimeState | null;
|
|
314
330
|
export declare function parseLaunchAgentProgramArguments(plist: string): string[];
|
|
315
331
|
export declare function assertLaunchAgentPlistCompatible(input: {
|