pixelkiln 0.50.0 → 0.51.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.
- package/dist/cli.js +267 -13
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +271 -13
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +763 -79
- package/dist/index.d.ts +763 -79
- package/dist/index.js +269 -13
- package/dist/index.js.map +1 -1
- package/docs/ENDPOINTS.md +3 -0
- package/package.json +1 -1
- package/schema/manifest.schema.json +216 -2
- package/schema/recipe.schema.json +108 -1
- package/skills/pixelkiln/references/pixellab-roadmap.md +8 -0
- package/skills/pixelkiln/references/pixellab.md +37 -0
package/dist/cli.js
CHANGED
|
@@ -707,7 +707,7 @@ import path2 from "path";
|
|
|
707
707
|
// src/types.ts
|
|
708
708
|
import { z } from "zod";
|
|
709
709
|
var MediaTypeSchema = z.enum(["image/png", "image/gif"]);
|
|
710
|
-
var GeneratorSchema = z.enum(["1dir", "map", "pixflux", "tiles", "animation", "frames", "character"]);
|
|
710
|
+
var GeneratorSchema = z.enum(["1dir", "map", "pixflux", "tiles", "animation", "frames", "character", "terrain"]);
|
|
711
711
|
var GridConfidenceSchema = z.enum(["low", "medium", "high"]);
|
|
712
712
|
var RevisionModeSchema = z.enum(["image-to-image", "inpaint", "outpaint"]);
|
|
713
713
|
function tileVariationCount(descriptions) {
|
|
@@ -740,6 +740,14 @@ function tilesCost(tileSize, variations) {
|
|
|
740
740
|
if (px <= 2048) return 25;
|
|
741
741
|
return 40;
|
|
742
742
|
}
|
|
743
|
+
function terrainTileCount(transitionSize) {
|
|
744
|
+
return transitionSize === 1 ? 25 : 16;
|
|
745
|
+
}
|
|
746
|
+
function parseTerrainDescriptions(prompt) {
|
|
747
|
+
const parts = prompt.split(/\d+\s*\)\s*\./).map((part) => part.trim()).filter(Boolean);
|
|
748
|
+
if (parts.length < 2) return null;
|
|
749
|
+
return { lower: parts[0], upper: parts[1], transition: parts[2] };
|
|
750
|
+
}
|
|
743
751
|
var StyleImageSchema = z.object({
|
|
744
752
|
/** Path to a PNG/JPEG, relative to the manifest; the active provider validates limits. */
|
|
745
753
|
path: z.string()
|
|
@@ -1019,6 +1027,48 @@ var StyleObjectSchema = z.object({
|
|
|
1019
1027
|
* rather than inheriting.
|
|
1020
1028
|
*/
|
|
1021
1029
|
outlineMode: z.enum(["outline", "segmentation"]).optional(),
|
|
1030
|
+
/**
|
|
1031
|
+
* `terrain` generator only. Edge length of one tile in pixels. 16 or 32
|
|
1032
|
+
* work in both modes; 64 needs `terrainMode: "pro"`. The API default is
|
|
1033
|
+
* 16. See `parseTerrainDescriptions` for how an asset's `prompt` becomes
|
|
1034
|
+
* the lower/upper/transition terrain descriptions this endpoint wants.
|
|
1035
|
+
*/
|
|
1036
|
+
terrainTileSize: z.union([z.literal(16), z.literal(32), z.literal(64)]).optional(),
|
|
1037
|
+
/**
|
|
1038
|
+
* `terrain` generator only. `standard` is the classic Wang tileset
|
|
1039
|
+
* pipeline; `pro` is a newer corner-pair pipeline with its own shape
|
|
1040
|
+
* controls (`terrainSpreadX`, `terrainSlopeSize`, `terrainRaggedness`)
|
|
1041
|
+
* in place of `terrainShapeStyle`. The API default is `standard`.
|
|
1042
|
+
*/
|
|
1043
|
+
terrainMode: z.enum(["standard", "pro"]).optional(),
|
|
1044
|
+
/**
|
|
1045
|
+
* `terrain` generator only, `terrainMode: "standard"`. Procedural
|
|
1046
|
+
* boundary geometry: `square` or `round`, 16px or 32px tiles only.
|
|
1047
|
+
* Rejected together with `terrainMode: "pro"`, whose own shape controls
|
|
1048
|
+
* are `terrainSpreadX`/`terrainSlopeSize`/`terrainRaggedness`.
|
|
1049
|
+
*/
|
|
1050
|
+
terrainShapeStyle: z.enum(["square", "round"]).optional(),
|
|
1051
|
+
/** `terrain` generator only, `terrainMode: "pro"`. Boundary spread
|
|
1052
|
+
* between terrains (0 = steep, 1 = gradual). API default 0.5. */
|
|
1053
|
+
terrainSpreadX: z.number().min(0).max(1).optional(),
|
|
1054
|
+
/** `terrain` generator only, `terrainMode: "pro"`. Slope on the N/W/E
|
|
1055
|
+
* sides as a fraction of wall height. API default 0. */
|
|
1056
|
+
terrainSlopeSize: z.number().min(0).max(1).optional(),
|
|
1057
|
+
/** `terrain` generator only, `terrainMode: "pro"`. Terrain boundary
|
|
1058
|
+
* noise (0 = smooth, 1 = rough). API default 0. */
|
|
1059
|
+
terrainRaggedness: z.number().min(0).max(1).optional(),
|
|
1060
|
+
/**
|
|
1061
|
+
* `terrain` generator only. Visual height of the step between lower and
|
|
1062
|
+
* upper terrain. Without `terrainShapeStyle`, only 0, 0.25, 0.5, or 1 are
|
|
1063
|
+
* accepted (1 switches to the 25-tile cliff layout, where corners take a
|
|
1064
|
+
* third "transition" value); with it, any value from 0 to 1 works, but
|
|
1065
|
+
* above 0.5 switches to an extended 32-tile layout this adapter does not
|
|
1066
|
+
* model. API default 0.
|
|
1067
|
+
*/
|
|
1068
|
+
terrainTransitionSize: z.number().min(0).max(1).optional(),
|
|
1069
|
+
/** `terrain` generator only. Camera angle; the API default is `high
|
|
1070
|
+
* top-down`. */
|
|
1071
|
+
terrainView: z.enum(["low top-down", "high top-down"]).optional(),
|
|
1022
1072
|
/**
|
|
1023
1073
|
* `pixflux` only. Whether to strip the generated background.
|
|
1024
1074
|
*
|
|
@@ -1132,7 +1182,7 @@ var StyleSchema = StyleObjectSchema.refine((s) => !(s.tileFeature && s.styleImag
|
|
|
1132
1182
|
message: "tileFeature and styleImages cannot be combined; a connectable set derives its own tile geometry, so remove one or the other",
|
|
1133
1183
|
path: ["tileFeature"]
|
|
1134
1184
|
}).superRefine((style, ctx) => {
|
|
1135
|
-
if (style.quality && (style.generator === "tiles" || style.generator === "animation")) {
|
|
1185
|
+
if (style.quality && (style.generator === "tiles" || style.generator === "terrain" || style.generator === "animation")) {
|
|
1136
1186
|
ctx.addIssue({
|
|
1137
1187
|
code: z.ZodIssueCode.custom,
|
|
1138
1188
|
message: "quality profiles currently support single-image generators only",
|
|
@@ -5157,6 +5207,11 @@ var MapSubmitSchema = z2.object({
|
|
|
5157
5207
|
object_id: z2.string().min(1),
|
|
5158
5208
|
status: z2.string().default("processing")
|
|
5159
5209
|
}).passthrough();
|
|
5210
|
+
var UsageSchema = z2.object({
|
|
5211
|
+
type: z2.string().optional(),
|
|
5212
|
+
usd: z2.number().nullable().optional(),
|
|
5213
|
+
generations: z2.number().nullable().optional()
|
|
5214
|
+
}).passthrough().nullable().optional();
|
|
5160
5215
|
var TilesSubmitSchema = z2.object({
|
|
5161
5216
|
tile_id: z2.string().min(1),
|
|
5162
5217
|
background_job_id: z2.string().min(1),
|
|
@@ -5167,6 +5222,32 @@ var TilesProSchema = z2.object({
|
|
|
5167
5222
|
kind: z2.string().nullable().default(null),
|
|
5168
5223
|
tile_rules: z2.record(z2.unknown()).nullable().optional()
|
|
5169
5224
|
}).passthrough();
|
|
5225
|
+
var TilesetSubmitSchema = z2.object({
|
|
5226
|
+
tileset_id: z2.string().min(1),
|
|
5227
|
+
background_job_id: z2.string().min(1),
|
|
5228
|
+
status: z2.literal("processing").default("processing")
|
|
5229
|
+
}).passthrough();
|
|
5230
|
+
var TilesetTileSchema = z2.object({
|
|
5231
|
+
id: z2.string().min(1),
|
|
5232
|
+
name: z2.string(),
|
|
5233
|
+
image: z2.object({ base64: z2.string().min(1), format: z2.string().default("png") }).passthrough(),
|
|
5234
|
+
corners: z2.object({ NW: z2.string(), NE: z2.string(), SW: z2.string(), SE: z2.string() }).passthrough(),
|
|
5235
|
+
pattern_4x4: z2.object({
|
|
5236
|
+
row_0: z2.array(z2.number()),
|
|
5237
|
+
row_1: z2.array(z2.number()),
|
|
5238
|
+
row_2: z2.array(z2.number()),
|
|
5239
|
+
row_3: z2.array(z2.number())
|
|
5240
|
+
}).passthrough()
|
|
5241
|
+
}).passthrough();
|
|
5242
|
+
var TilesetGetSchema = z2.object({
|
|
5243
|
+
tileset: z2.object({
|
|
5244
|
+
total_tiles: z2.number().int().min(1),
|
|
5245
|
+
tile_size: z2.object({ width: z2.number().int().positive(), height: z2.number().int().positive() }).passthrough(),
|
|
5246
|
+
terrain_types: z2.array(z2.string()),
|
|
5247
|
+
tiles: z2.array(TilesetTileSchema).min(1)
|
|
5248
|
+
}).passthrough(),
|
|
5249
|
+
usage: UsageSchema
|
|
5250
|
+
}).passthrough();
|
|
5170
5251
|
var RevisionJobSubmitSchema = z2.object({
|
|
5171
5252
|
background_job_id: z2.string().min(1),
|
|
5172
5253
|
status: z2.string().default("processing")
|
|
@@ -5198,11 +5279,6 @@ var MapObjectSchema = z2.object({
|
|
|
5198
5279
|
var ObjectListSchema = z2.object({ objects: z2.array(PixelLabObjectSchema), total: z2.number().int().min(0) }).passthrough();
|
|
5199
5280
|
var PixfluxResponseSchema = z2.object({ image: z2.object({ base64: z2.string().min(1) }).passthrough(), usage: z2.unknown().optional() }).passthrough();
|
|
5200
5281
|
var SelectFramesSchema = z2.object({ created_object_ids: z2.array(z2.string()) }).passthrough();
|
|
5201
|
-
var UsageSchema = z2.object({
|
|
5202
|
-
type: z2.string().optional(),
|
|
5203
|
-
usd: z2.number().nullable().optional(),
|
|
5204
|
-
generations: z2.number().nullable().optional()
|
|
5205
|
-
}).passthrough().nullable().optional();
|
|
5206
5282
|
var CharacterSubmitSchema = z2.object({
|
|
5207
5283
|
background_job_id: z2.string().min(1),
|
|
5208
5284
|
character_id: z2.string().min(1),
|
|
@@ -5418,6 +5494,45 @@ var PixelLabClient = class {
|
|
|
5418
5494
|
"get tiles"
|
|
5419
5495
|
);
|
|
5420
5496
|
}
|
|
5497
|
+
/**
|
|
5498
|
+
* `/create-tileset`: two named terrain levels (`lower`/`upper`) and the
|
|
5499
|
+
* transition between them, laid out as a Wang corner set. Unlike
|
|
5500
|
+
* `/create-tiles-pro`, the descriptions are separate fields the API itself
|
|
5501
|
+
* places on the terrain vertex grid, not one prompt it splits by number.
|
|
5502
|
+
*/
|
|
5503
|
+
async createTileset(args) {
|
|
5504
|
+
const body = {
|
|
5505
|
+
lower_description: args.lowerDescription,
|
|
5506
|
+
upper_description: args.upperDescription
|
|
5507
|
+
};
|
|
5508
|
+
if (args.transitionDescription) body.transition_description = args.transitionDescription;
|
|
5509
|
+
if (args.tileSize != null) body.tile_size = { width: args.tileSize, height: args.tileSize };
|
|
5510
|
+
if (args.mode) body.mode = args.mode;
|
|
5511
|
+
if (args.shapeStyle) body.shape_style = args.shapeStyle;
|
|
5512
|
+
if (args.spreadX != null) body.spread_x = args.spreadX;
|
|
5513
|
+
if (args.slopeSize != null) body.slope_size = args.slopeSize;
|
|
5514
|
+
if (args.raggedness != null) body.raggedness = args.raggedness;
|
|
5515
|
+
if (args.transitionSize != null) body.transition_size = args.transitionSize;
|
|
5516
|
+
if (args.view) body.view = args.view;
|
|
5517
|
+
if (args.outline) body.outline = args.outline;
|
|
5518
|
+
if (args.shading) body.shading = args.shading;
|
|
5519
|
+
if (args.detail) body.detail = args.detail;
|
|
5520
|
+
if (args.seed != null) body.seed = args.seed;
|
|
5521
|
+
return validateResponse(
|
|
5522
|
+
TilesetSubmitSchema,
|
|
5523
|
+
await this.request("/create-tileset", { method: "POST", body: JSON.stringify(body) }),
|
|
5524
|
+
"create tileset"
|
|
5525
|
+
);
|
|
5526
|
+
}
|
|
5527
|
+
/** Throws PixelLabError(423) while the set is still drawing; see Tileset. */
|
|
5528
|
+
async getTileset(tilesetId) {
|
|
5529
|
+
const res = await validateResponse(
|
|
5530
|
+
TilesetGetSchema,
|
|
5531
|
+
await this.request(`/tilesets/${tilesetId}`),
|
|
5532
|
+
"get tileset"
|
|
5533
|
+
);
|
|
5534
|
+
return { tileset: res.tileset, usage: res.usage ?? null };
|
|
5535
|
+
}
|
|
5421
5536
|
/**
|
|
5422
5537
|
* Synchronous single-image generation. Returns the PNG inline rather than a
|
|
5423
5538
|
* job id, and is the only endpoint that honours a forced palette;
|
|
@@ -5856,7 +5971,7 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
5856
5971
|
return new _PixelLabProvider(new PixelLabClient("download-only"));
|
|
5857
5972
|
}
|
|
5858
5973
|
supports(generator) {
|
|
5859
|
-
return generator === "1dir" || generator === "map" || generator === "pixflux" || generator === "tiles" || generator === "character";
|
|
5974
|
+
return generator === "1dir" || generator === "map" || generator === "pixflux" || generator === "tiles" || generator === "terrain" || generator === "character";
|
|
5860
5975
|
}
|
|
5861
5976
|
/**
|
|
5862
5977
|
* `inpaint` (`/inpaint-v3`, a mask) and `image-to-image` (`/edit-images-v2`,
|
|
@@ -5892,7 +6007,7 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
5892
6007
|
const height = spec.revision.sourceHeight ?? spec.height;
|
|
5893
6008
|
return { unit: "generations", amount: generationCost(width, height, "1dir"), candidates: 1 };
|
|
5894
6009
|
}
|
|
5895
|
-
if (spec.generator === "tiles") {
|
|
6010
|
+
if (spec.generator === "tiles" || spec.generator === "terrain") {
|
|
5896
6011
|
return { unit: "generations", amount: spec.cost, candidates: spec.candidates };
|
|
5897
6012
|
}
|
|
5898
6013
|
if (spec.generator === "character") {
|
|
@@ -5936,10 +6051,55 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
5936
6051
|
"high detail"
|
|
5937
6052
|
]);
|
|
5938
6053
|
}
|
|
6054
|
+
if (spec.generator === "terrain") {
|
|
6055
|
+
if (spec.outline) {
|
|
6056
|
+
requirePixelLabOption("outline", spec.outline, [
|
|
6057
|
+
"single color black outline",
|
|
6058
|
+
"single color outline",
|
|
6059
|
+
"selective outline",
|
|
6060
|
+
"lineless"
|
|
6061
|
+
]);
|
|
6062
|
+
}
|
|
6063
|
+
if (spec.shading) {
|
|
6064
|
+
requirePixelLabOption("shading", spec.shading, [
|
|
6065
|
+
"flat shading",
|
|
6066
|
+
"basic shading",
|
|
6067
|
+
"medium shading",
|
|
6068
|
+
"detailed shading",
|
|
6069
|
+
"highly detailed shading"
|
|
6070
|
+
]);
|
|
6071
|
+
}
|
|
6072
|
+
if (spec.detail) {
|
|
6073
|
+
requirePixelLabOption("detail", spec.detail, ["low detail", "medium detail", "highly detailed"]);
|
|
6074
|
+
}
|
|
6075
|
+
if (spec.terrainShapeStyle && spec.terrainMode === "pro") {
|
|
6076
|
+
throw new Error(
|
|
6077
|
+
"PixelLab terrain: terrainShapeStyle is standard-mode only; pro's own shape controls are terrainSpreadX/terrainSlopeSize/terrainRaggedness"
|
|
6078
|
+
);
|
|
6079
|
+
}
|
|
6080
|
+
if (spec.terrainTileSize === 64 && spec.terrainMode !== "pro") {
|
|
6081
|
+
throw new Error('PixelLab terrain: a 64px tile needs terrainMode: "pro"');
|
|
6082
|
+
}
|
|
6083
|
+
if (spec.terrainShapeStyle && spec.terrainTransitionSize != null && spec.terrainTransitionSize > 0.5) {
|
|
6084
|
+
throw new Error(
|
|
6085
|
+
"PixelLab terrain: terrainShapeStyle with terrainTransitionSize above 0.5 uses an extended 32-tile layout pixelkiln does not model; use 0, 0.25, or 0.5"
|
|
6086
|
+
);
|
|
6087
|
+
}
|
|
6088
|
+
if (!spec.terrainShapeStyle && spec.terrainTransitionSize != null && ![0, 0.25, 0.5, 1].includes(spec.terrainTransitionSize)) {
|
|
6089
|
+
throw new Error(
|
|
6090
|
+
"PixelLab terrain: terrainTransitionSize must be 0, 0.25, 0.5, or 1 unless terrainShapeStyle is set"
|
|
6091
|
+
);
|
|
6092
|
+
}
|
|
6093
|
+
}
|
|
5939
6094
|
if (spec.generator === "character") this.validateCharacter(spec, styleImages);
|
|
5940
6095
|
if ((spec.generator === "map" || spec.generator === "pixflux") && styleImages.length) {
|
|
5941
6096
|
throw new Error(`PixelLab ${spec.generator} does not support style images`);
|
|
5942
6097
|
}
|
|
6098
|
+
if (spec.generator === "terrain" && styleImages.length) {
|
|
6099
|
+
throw new Error(
|
|
6100
|
+
"PixelLab terrain does not support style images yet; use color_image/reference images directly against /create-tileset if this becomes a real need"
|
|
6101
|
+
);
|
|
6102
|
+
}
|
|
5943
6103
|
for (const image of styleImages) {
|
|
5944
6104
|
if (image.width > 256 || image.height > 256) {
|
|
5945
6105
|
throw new Error(
|
|
@@ -6302,6 +6462,26 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
6302
6462
|
});
|
|
6303
6463
|
return { jobId: res2.tile_id, metadata: { backgroundJobId: res2.background_job_id } };
|
|
6304
6464
|
}
|
|
6465
|
+
if (spec.generator === "terrain") {
|
|
6466
|
+
const res2 = await this.client.createTileset({
|
|
6467
|
+
lowerDescription: spec.terrainLowerDescription,
|
|
6468
|
+
upperDescription: spec.terrainUpperDescription,
|
|
6469
|
+
transitionDescription: spec.terrainTransitionDescription,
|
|
6470
|
+
tileSize: spec.terrainTileSize,
|
|
6471
|
+
mode: spec.terrainMode,
|
|
6472
|
+
shapeStyle: spec.terrainShapeStyle,
|
|
6473
|
+
spreadX: spec.terrainSpreadX,
|
|
6474
|
+
slopeSize: spec.terrainSlopeSize,
|
|
6475
|
+
raggedness: spec.terrainRaggedness,
|
|
6476
|
+
transitionSize: spec.terrainTransitionSize,
|
|
6477
|
+
view: spec.terrainView,
|
|
6478
|
+
outline: spec.outline,
|
|
6479
|
+
shading: spec.shading,
|
|
6480
|
+
detail: spec.detail,
|
|
6481
|
+
seed: spec.seed
|
|
6482
|
+
});
|
|
6483
|
+
return { jobId: res2.tileset_id, metadata: { backgroundJobId: res2.background_job_id } };
|
|
6484
|
+
}
|
|
6305
6485
|
if (spec.generator === "1dir") {
|
|
6306
6486
|
const res2 = await this.client.create1Direction({
|
|
6307
6487
|
description: spec.prompt,
|
|
@@ -6407,6 +6587,7 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
6407
6587
|
}
|
|
6408
6588
|
if (generator === "map") return this.pollMap(jobId, context);
|
|
6409
6589
|
if (generator === "tiles") return this.pollTiles(jobId, Boolean(context?.tileFeature), context);
|
|
6590
|
+
if (generator === "terrain") return this.pollTerrain(jobId, context);
|
|
6410
6591
|
if (generator === "character") return this.pollCharacter(jobId, context);
|
|
6411
6592
|
const backgroundJobId = context?.metadata?.backgroundJobId;
|
|
6412
6593
|
const obj = await this.client.getObject(jobId);
|
|
@@ -6545,6 +6726,43 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
6545
6726
|
throw err;
|
|
6546
6727
|
}
|
|
6547
6728
|
}
|
|
6729
|
+
/**
|
|
6730
|
+
* `/create-tileset` reports progress the same way `tiles` does (423 while
|
|
6731
|
+
* drawing, 200 once finished), but each tile's image comes back embedded
|
|
6732
|
+
* as base64 rather than a `storage_urls` link, so it is decoded straight
|
|
6733
|
+
* to a cache file the same way a revision result is (`pollRevision`). A
|
|
6734
|
+
* terrain set is always a connectable Wang tileset, never independent
|
|
6735
|
+
* candidates to review, so this goes straight to "ready" like a connectable
|
|
6736
|
+
* `tiles` set does.
|
|
6737
|
+
*/
|
|
6738
|
+
async pollTerrain(tilesetId, context) {
|
|
6739
|
+
try {
|
|
6740
|
+
const { tileset, usage } = await this.client.getTileset(tilesetId);
|
|
6741
|
+
const backgroundJobId = context?.metadata?.backgroundJobId;
|
|
6742
|
+
const billed = billedFromUsage(usage) ?? await this.billedForJob(backgroundJobId);
|
|
6743
|
+
const sources = [];
|
|
6744
|
+
const terrainTiles = [];
|
|
6745
|
+
for (const [index, tile] of tileset.tiles.entries()) {
|
|
6746
|
+
const slug = tile.name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "tile";
|
|
6747
|
+
const role = `tile-${String(index).padStart(2, "0")}-${slug}`;
|
|
6748
|
+
const file = path12.join(_PixelLabProvider.cacheDir(), `${tilesetId}-${index}.png`);
|
|
6749
|
+
writeFileSync(file, Buffer.from(tile.image.base64, "base64"));
|
|
6750
|
+
sources.push({ url: `file://${file}`, role });
|
|
6751
|
+
terrainTiles.push({ role, corners: tile.corners, pattern4x4: tile.pattern_4x4 });
|
|
6752
|
+
}
|
|
6753
|
+
return {
|
|
6754
|
+
status: "ready",
|
|
6755
|
+
objectId: tilesetId,
|
|
6756
|
+
sourceUrl: sources[0]?.url ?? null,
|
|
6757
|
+
sources,
|
|
6758
|
+
metadata: { terrainTypes: tileset.terrain_types, terrainTiles },
|
|
6759
|
+
billed
|
|
6760
|
+
};
|
|
6761
|
+
} catch (err) {
|
|
6762
|
+
if (err instanceof PixelLabError && err.status === 423) return { status: "processing" };
|
|
6763
|
+
throw err;
|
|
6764
|
+
}
|
|
6765
|
+
}
|
|
6548
6766
|
async selectCandidate(jobId, index, commonTag, generator) {
|
|
6549
6767
|
if (generator === "tiles") {
|
|
6550
6768
|
const set = await this.client.getTilesPro(jobId);
|
|
@@ -7895,6 +8113,10 @@ async function resolveSpecs(loaded, filter) {
|
|
|
7895
8113
|
size = asset.state?.canvas ? Math.max(asset.state.canvas.width, asset.state.canvas.height) : asset.size ?? style.size ?? 64;
|
|
7896
8114
|
width = asset.state?.canvas?.width ?? size;
|
|
7897
8115
|
height = asset.state?.canvas?.height ?? size;
|
|
8116
|
+
} else if (generator === "terrain") {
|
|
8117
|
+
size = style.terrainTileSize ?? 16;
|
|
8118
|
+
width = size;
|
|
8119
|
+
height = size;
|
|
7898
8120
|
} else {
|
|
7899
8121
|
width = asset.width ?? style.size ?? 64;
|
|
7900
8122
|
height = asset.height ?? style.size ?? 64;
|
|
@@ -7907,12 +8129,33 @@ async function resolveSpecs(loaded, filter) {
|
|
|
7907
8129
|
}
|
|
7908
8130
|
const characterKind = asset.animation ? "animation" : asset.state ? "state" : "base";
|
|
7909
8131
|
const subject = asset.promptByStyle[styleId] ?? asset.prompt ?? "";
|
|
7910
|
-
|
|
8132
|
+
let terrainLowerDescription;
|
|
8133
|
+
let terrainUpperDescription;
|
|
8134
|
+
let terrainTransitionDescription;
|
|
8135
|
+
let prompt;
|
|
8136
|
+
if (generator === "terrain") {
|
|
8137
|
+
const parsed = parseTerrainDescriptions(subject);
|
|
8138
|
+
if (!parsed) {
|
|
8139
|
+
throw new Error(
|
|
8140
|
+
`assets.${assetId}: terrain needs "1). <lower terrain> 2). <upper terrain>" in its prompt, optionally followed by "3). <transition>"`
|
|
8141
|
+
);
|
|
8142
|
+
}
|
|
8143
|
+
const wrap = (text) => [style.promptPrefix, text, style.promptSuffix].map((p) => p.trim()).filter(Boolean).join(", ");
|
|
8144
|
+
terrainLowerDescription = wrap(parsed.lower);
|
|
8145
|
+
terrainUpperDescription = wrap(parsed.upper);
|
|
8146
|
+
terrainTransitionDescription = parsed.transition ? wrap(parsed.transition) : void 0;
|
|
8147
|
+
prompt = subject.trim();
|
|
8148
|
+
} else if (generator === "character" && characterKind !== "base") {
|
|
8149
|
+
prompt = subject.trim();
|
|
8150
|
+
} else {
|
|
8151
|
+
prompt = [style.promptPrefix, subject, style.promptSuffix].map((p) => p.trim()).filter(Boolean).join(", ");
|
|
8152
|
+
}
|
|
7911
8153
|
const relFile = asset.file ?? path13.join(asset.category ?? "", `${assetId}.png`);
|
|
7912
8154
|
const outFile = path13.resolve(root, style.outDir, relFile);
|
|
7913
8155
|
const qualityOutFile = style.quality ? path13.resolve(root, style.quality.outDir, pngPath(relFile)) : void 0;
|
|
7914
8156
|
const tileSize = generator === "tiles" ? size : style.tileSize ?? 32;
|
|
7915
8157
|
const tileVariations = tileFeatureOutputCount(generator === "tiles" ? style.tileFeature : void 0) ?? tileVariationCount(countNumberedDescriptions(prompt));
|
|
8158
|
+
const terrainTiles = generator === "terrain" ? terrainTileCount(style.terrainTransitionSize) : 0;
|
|
7916
8159
|
const base = {
|
|
7917
8160
|
styleId,
|
|
7918
8161
|
assetId,
|
|
@@ -7949,10 +8192,21 @@ async function resolveSpecs(loaded, filter) {
|
|
|
7949
8192
|
buildingFloor2Description: generator === "tiles" ? style.buildingFloor2Description : void 0,
|
|
7950
8193
|
buildingWallAngle: generator === "tiles" ? style.buildingWallAngle : void 0,
|
|
7951
8194
|
outlineMode: generator === "tiles" ? style.outlineMode : void 0,
|
|
8195
|
+
terrainLowerDescription: generator === "terrain" ? terrainLowerDescription : void 0,
|
|
8196
|
+
terrainUpperDescription: generator === "terrain" ? terrainUpperDescription : void 0,
|
|
8197
|
+
terrainTransitionDescription: generator === "terrain" ? terrainTransitionDescription : void 0,
|
|
8198
|
+
terrainTileSize: generator === "terrain" ? size : void 0,
|
|
8199
|
+
terrainMode: generator === "terrain" ? style.terrainMode : void 0,
|
|
8200
|
+
terrainShapeStyle: generator === "terrain" ? style.terrainShapeStyle : void 0,
|
|
8201
|
+
terrainSpreadX: generator === "terrain" ? style.terrainSpreadX : void 0,
|
|
8202
|
+
terrainSlopeSize: generator === "terrain" ? style.terrainSlopeSize : void 0,
|
|
8203
|
+
terrainRaggedness: generator === "terrain" ? style.terrainRaggedness : void 0,
|
|
8204
|
+
terrainTransitionSize: generator === "terrain" ? style.terrainTransitionSize : void 0,
|
|
8205
|
+
terrainView: generator === "terrain" ? style.terrainView : void 0,
|
|
7952
8206
|
...generator === "character" ? { character: await resolveCharacterShape(asset, style, characterKind, { root, load: loadStyleImage }) } : {},
|
|
7953
|
-
cost: generator === "tiles" ? tilesCost(tileSize, tileVariations) : generationCost(width, height, generator),
|
|
8207
|
+
cost: generator === "tiles" ? tilesCost(tileSize, tileVariations) : generator === "terrain" ? tilesCost(size, terrainTiles) : generationCost(width, height, generator),
|
|
7954
8208
|
costUnit: "generations",
|
|
7955
|
-
candidates: generator === "tiles" ? tileVariations : generator === "1dir" ? candidateCount(size) : 1
|
|
8209
|
+
candidates: generator === "tiles" ? tileVariations : generator === "terrain" ? terrainTiles : generator === "1dir" ? candidateCount(size) : 1
|
|
7956
8210
|
};
|
|
7957
8211
|
const tags = [
|
|
7958
8212
|
.../* @__PURE__ */ new Set([
|
|
@@ -8000,7 +8254,7 @@ async function resolveSpecs(loaded, filter) {
|
|
|
8000
8254
|
if (asset.mirror) {
|
|
8001
8255
|
if (asset.mirror === assetId) throw new Error(`assets.${assetId}: an asset cannot mirror itself`);
|
|
8002
8256
|
const sourceSpec = await finalize(asset.mirror);
|
|
8003
|
-
if (sourceSpec.generator === "tiles") {
|
|
8257
|
+
if (sourceSpec.generator === "tiles" || sourceSpec.generator === "terrain") {
|
|
8004
8258
|
throw new Error(`assets.${assetId}: a tile set cannot be mirrored; its edges carry meaning`);
|
|
8005
8259
|
}
|
|
8006
8260
|
if (sourceSpec.character?.kind === "animation") {
|