pixelkiln 0.49.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 +378 -36
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +382 -36
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1541 -104
- package/dist/index.d.ts +1541 -104
- package/dist/index.js +380 -36
- package/dist/index.js.map +1 -1
- package/docs/ENDPOINTS.md +63 -7
- package/docs/PIXELLAB.md +34 -8
- package/docs/REVISIONS.md +53 -12
- package/package.json +1 -1
- package/schema/manifest.schema.json +216 -2
- package/schema/recipe.schema.json +108 -1
- package/skills/pixelkiln/SKILL.md +7 -1
- package/skills/pixelkiln/references/pixellab-fidelity.md +186 -0
- package/skills/pixelkiln/references/pixellab-roadmap.md +184 -0
- package/skills/pixelkiln/references/pixellab.md +108 -8
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()
|
|
@@ -938,17 +946,37 @@ var StyleObjectSchema = z.object({
|
|
|
938
946
|
shading: z.string().optional(),
|
|
939
947
|
detail: z.string().optional(),
|
|
940
948
|
/**
|
|
941
|
-
* `tiles` generator only. Edge length of one tile, 16-
|
|
949
|
+
* `tiles` generator only. Edge length of one tile, 16-128 (the API's own
|
|
950
|
+
* range; connectable sets narrow it further per shape, e.g. square
|
|
951
|
+
* top-down roads are fixed at 32).
|
|
942
952
|
*
|
|
943
953
|
* Ignored when `styleImages` is set: style mode takes the tile's shape
|
|
944
954
|
* and dimensions from the reference image, which is the whole reason to
|
|
945
955
|
* use it against an existing sheet.
|
|
946
956
|
*/
|
|
947
|
-
tileSize: z.number().int().min(16).max(
|
|
957
|
+
tileSize: z.number().int().min(16).max(128).optional(),
|
|
958
|
+
/** `tiles` generator only. Height in pixels (16-256) for a non-square
|
|
959
|
+
* tile (e.g. a tall building wall); omit to compute it from `tileType`
|
|
960
|
+
* geometry and the view angle. */
|
|
961
|
+
tileHeight: z.number().int().min(16).max(256).optional(),
|
|
948
962
|
/** `tiles` generator only. Defaults to the API's `isometric`. */
|
|
949
963
|
tileType: z.enum(["hex", "hex_pointy", "isometric", "oblique", "octagon", "square_topdown"]).optional(),
|
|
950
964
|
/** `tiles` generator only. Defaults to the API's `low top-down`. */
|
|
951
965
|
tileView: z.enum(["top-down", "high top-down", "low top-down", "side"]).optional(),
|
|
966
|
+
/** `tiles` generator only. Continuous view angle in degrees (0 = side,
|
|
967
|
+
* 90 = top-down), overriding `tileView` when set. */
|
|
968
|
+
tileViewAngle: z.number().min(0).max(90).optional(),
|
|
969
|
+
/** `tiles` generator only. Tile depth/thickness as a ratio (0-1) of the
|
|
970
|
+
* tile's height, overriding the default the API computes from the
|
|
971
|
+
* view. This is the tutorials' "thickness" control. */
|
|
972
|
+
tileDepthRatio: z.number().min(0).max(1).optional(),
|
|
973
|
+
/** `tiles` generator only, `tileType: "isometric"`. Top/bottom cap
|
|
974
|
+
* width in pixels: 2 for the classic look, 4 for a more modern one. */
|
|
975
|
+
tileFlatTopPx: z.number().int().min(2).max(8).optional(),
|
|
976
|
+
/** `tiles` generator only, `tileType: "oblique"` (ground tiles or
|
|
977
|
+
* building walls). Horizontal shear per pixel of height (0-1); 0.5 is
|
|
978
|
+
* a classic cabinet projection (~27°), 1.0 a full 45° diagonal. */
|
|
979
|
+
obliqueLean: z.number().min(0).max(1).optional(),
|
|
952
980
|
/**
|
|
953
981
|
* `tiles` generator only. Asks for a connectable set instead of
|
|
954
982
|
* independent variations:
|
|
@@ -963,6 +991,28 @@ var StyleObjectSchema = z.object({
|
|
|
963
991
|
* is load-bearing; do not sort a connectable set by anything else.
|
|
964
992
|
*/
|
|
965
993
|
tileFeature: z.enum(["roads", "tileset", "building"]).optional(),
|
|
994
|
+
/** `tileFeature: "building"` only. Wall height in tiles (1-3); the API
|
|
995
|
+
* defaults to 2. */
|
|
996
|
+
buildingWallTiles: z.number().int().min(1).max(3).optional(),
|
|
997
|
+
/** `tileFeature: "building"` only. `"grid"` paints each shaped piece
|
|
998
|
+
* individually (richer, the isometric default); `"materials"` paints
|
|
999
|
+
* flat swatches and renders pieces from them (more consistent for
|
|
1000
|
+
* square top-down building kits). */
|
|
1001
|
+
buildingLayout: z.enum(["grid", "materials"]).optional(),
|
|
1002
|
+
/** `tileFeature: "building"` only. Wall material, e.g. "stone brick
|
|
1003
|
+
* walls" — more reliable than relying on the main `prompt` being split
|
|
1004
|
+
* into wall/floor parts. */
|
|
1005
|
+
buildingWallDescription: z.string().min(1).max(500).optional(),
|
|
1006
|
+
/** `tileFeature: "building"` only. Floor material, e.g. "wooden plank
|
|
1007
|
+
* floor". */
|
|
1008
|
+
buildingFloorDescription: z.string().min(1).max(500).optional(),
|
|
1009
|
+
/** `tileFeature: "building"` only. Upper-storey or roof surface;
|
|
1010
|
+
* defaults to the wall material when omitted. */
|
|
1011
|
+
buildingFloor2Description: z.string().min(1).max(500).optional(),
|
|
1012
|
+
/** `tileFeature: "building"` with `tileType: "square_topdown"` only.
|
|
1013
|
+
* Wall storey height as its own camera angle in degrees (5-90),
|
|
1014
|
+
* decoupled from the ground's pitch. */
|
|
1015
|
+
buildingWallAngle: z.number().min(5).max(90).optional(),
|
|
966
1016
|
/**
|
|
967
1017
|
* `tiles` generator only. How tile edges are drawn.
|
|
968
1018
|
*
|
|
@@ -977,6 +1027,48 @@ var StyleObjectSchema = z.object({
|
|
|
977
1027
|
* rather than inheriting.
|
|
978
1028
|
*/
|
|
979
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(),
|
|
980
1072
|
/**
|
|
981
1073
|
* `pixflux` only. Whether to strip the generated background.
|
|
982
1074
|
*
|
|
@@ -1090,7 +1182,7 @@ var StyleSchema = StyleObjectSchema.refine((s) => !(s.tileFeature && s.styleImag
|
|
|
1090
1182
|
message: "tileFeature and styleImages cannot be combined; a connectable set derives its own tile geometry, so remove one or the other",
|
|
1091
1183
|
path: ["tileFeature"]
|
|
1092
1184
|
}).superRefine((style, ctx) => {
|
|
1093
|
-
if (style.quality && (style.generator === "tiles" || style.generator === "animation")) {
|
|
1185
|
+
if (style.quality && (style.generator === "tiles" || style.generator === "terrain" || style.generator === "animation")) {
|
|
1094
1186
|
ctx.addIssue({
|
|
1095
1187
|
code: z.ZodIssueCode.custom,
|
|
1096
1188
|
message: "quality profiles currently support single-image generators only",
|
|
@@ -5115,6 +5207,11 @@ var MapSubmitSchema = z2.object({
|
|
|
5115
5207
|
object_id: z2.string().min(1),
|
|
5116
5208
|
status: z2.string().default("processing")
|
|
5117
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();
|
|
5118
5215
|
var TilesSubmitSchema = z2.object({
|
|
5119
5216
|
tile_id: z2.string().min(1),
|
|
5120
5217
|
background_job_id: z2.string().min(1),
|
|
@@ -5125,6 +5222,32 @@ var TilesProSchema = z2.object({
|
|
|
5125
5222
|
kind: z2.string().nullable().default(null),
|
|
5126
5223
|
tile_rules: z2.record(z2.unknown()).nullable().optional()
|
|
5127
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();
|
|
5128
5251
|
var RevisionJobSubmitSchema = z2.object({
|
|
5129
5252
|
background_job_id: z2.string().min(1),
|
|
5130
5253
|
status: z2.string().default("processing")
|
|
@@ -5156,11 +5279,6 @@ var MapObjectSchema = z2.object({
|
|
|
5156
5279
|
var ObjectListSchema = z2.object({ objects: z2.array(PixelLabObjectSchema), total: z2.number().int().min(0) }).passthrough();
|
|
5157
5280
|
var PixfluxResponseSchema = z2.object({ image: z2.object({ base64: z2.string().min(1) }).passthrough(), usage: z2.unknown().optional() }).passthrough();
|
|
5158
5281
|
var SelectFramesSchema = z2.object({ created_object_ids: z2.array(z2.string()) }).passthrough();
|
|
5159
|
-
var UsageSchema = z2.object({
|
|
5160
|
-
type: z2.string().optional(),
|
|
5161
|
-
usd: z2.number().nullable().optional(),
|
|
5162
|
-
generations: z2.number().nullable().optional()
|
|
5163
|
-
}).passthrough().nullable().optional();
|
|
5164
5282
|
var CharacterSubmitSchema = z2.object({
|
|
5165
5283
|
background_job_id: z2.string().min(1),
|
|
5166
5284
|
character_id: z2.string().min(1),
|
|
@@ -5345,9 +5463,20 @@ var PixelLabClient = class {
|
|
|
5345
5463
|
async createTilesPro(args) {
|
|
5346
5464
|
const body = { description: args.description };
|
|
5347
5465
|
if (args.tileSize != null) body.tile_size = args.tileSize;
|
|
5466
|
+
if (args.tileHeight != null) body.tile_height = args.tileHeight;
|
|
5348
5467
|
if (args.tileType) body.tile_type = args.tileType;
|
|
5349
5468
|
if (args.tileView) body.tile_view = args.tileView;
|
|
5469
|
+
if (args.tileViewAngle != null) body.tile_view_angle = args.tileViewAngle;
|
|
5470
|
+
if (args.tileDepthRatio != null) body.tile_depth_ratio = args.tileDepthRatio;
|
|
5471
|
+
if (args.tileFlatTopPx != null) body.tile_flat_top_px = args.tileFlatTopPx;
|
|
5472
|
+
if (args.obliqueLean != null) body.oblique_lean = args.obliqueLean;
|
|
5350
5473
|
if (args.tileFeature) body.tile_feature = args.tileFeature;
|
|
5474
|
+
if (args.buildingWallTiles != null) body.building_wall_tiles = args.buildingWallTiles;
|
|
5475
|
+
if (args.buildingLayout) body.building_layout = args.buildingLayout;
|
|
5476
|
+
if (args.buildingWallDescription) body.building_wall_description = args.buildingWallDescription;
|
|
5477
|
+
if (args.buildingFloorDescription) body.building_floor_description = args.buildingFloorDescription;
|
|
5478
|
+
if (args.buildingFloor2Description) body.building_floor2_description = args.buildingFloor2Description;
|
|
5479
|
+
if (args.buildingWallAngle != null) body.building_wall_angle = args.buildingWallAngle;
|
|
5351
5480
|
if (args.outlineMode) body.outline_mode = args.outlineMode;
|
|
5352
5481
|
if (args.seed != null) body.seed = args.seed;
|
|
5353
5482
|
if (args.styleImages?.length) body.style_images = args.styleImages;
|
|
@@ -5365,6 +5494,45 @@ var PixelLabClient = class {
|
|
|
5365
5494
|
"get tiles"
|
|
5366
5495
|
);
|
|
5367
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
|
+
}
|
|
5368
5536
|
/**
|
|
5369
5537
|
* Synchronous single-image generation. Returns the PNG inline rather than a
|
|
5370
5538
|
* job id, and is the only endpoint that honours a forced palette;
|
|
@@ -5603,13 +5771,21 @@ var PixelLabClient = class {
|
|
|
5603
5771
|
/**
|
|
5604
5772
|
* Masked inpaint, PixelLab's `/inpaint-v3` (the endpoint its own docs list
|
|
5605
5773
|
* first in the Inpaint section, its convention for "reach for this by
|
|
5606
|
-
* default").
|
|
5607
|
-
*
|
|
5608
|
-
*
|
|
5609
|
-
*
|
|
5610
|
-
*
|
|
5611
|
-
*
|
|
5612
|
-
*
|
|
5774
|
+
* default"). Exercised live at nine sizes, all returning `last_response.
|
|
5775
|
+
* image` as a single `{type, base64, width, height}` (the first shape
|
|
5776
|
+
* `pollRevision` in pixellab.ts checks, so no change was ever needed):
|
|
5777
|
+
* 32x32 (1024px², billed 20, this adapter's tiering floor) through
|
|
5778
|
+
* 256x256 (65536px²) all billed 20 (the tiering wrongly predicts 25 from
|
|
5779
|
+
* 1024px² up, see estimate() in pixellab.ts); 288x288 (82944px²) and
|
|
5780
|
+
* 320x320 (102400px²) billed 25 — the middle tier is real, just starting
|
|
5781
|
+
* far higher than this tiering assumes; 352x352 (123904px²), 384x384
|
|
5782
|
+
* (147456px²), and 512x512 (262144px², the tiering ceiling) all billed
|
|
5783
|
+
* 40. Both breakpoints are now tightly bracketed: 20->25 in
|
|
5784
|
+
* (65536px², 82944px²], 25->40 in (102400px², 123904px²].
|
|
5785
|
+
* `editImagesV2` below returns the same fields under `images`, plural
|
|
5786
|
+
* and array-wrapped, not `image` — do not assume the two endpoints share
|
|
5787
|
+
* one response shape. See docs/ENDPOINTS.md and docs/REVISIONS.md for
|
|
5788
|
+
* the full shape and cost picture, including what is still unconfirmed.
|
|
5613
5789
|
*
|
|
5614
5790
|
* `crop_to_mask` defaults true upstream (confirmed in the schema): PixelLab
|
|
5615
5791
|
* otherwise blends generated pixels outside the mask edge to "fit
|
|
@@ -5632,11 +5808,15 @@ var PixelLabClient = class {
|
|
|
5632
5808
|
);
|
|
5633
5809
|
}
|
|
5634
5810
|
/**
|
|
5635
|
-
* Whole-image edit with no mask, PixelLab's `/edit-images-v2`.
|
|
5636
|
-
*
|
|
5637
|
-
* array (the endpoint supports editing several images with one
|
|
5811
|
+
* Whole-image edit with no mask, PixelLab's `/edit-images-v2`. `edit_images`
|
|
5812
|
+
* takes an array (the endpoint supports editing several images with one
|
|
5638
5813
|
* instruction); pixelkiln's `revision` model is one parent per child, so
|
|
5639
|
-
* this always sends exactly one.
|
|
5814
|
+
* this always sends exactly one. Exercised live at its floor size (32x32):
|
|
5815
|
+
* billed exactly the 20-generation estimate this adapter borrows from
|
|
5816
|
+
* `1dir`, matching `inpaintV3`'s floor exactly. The completed response is
|
|
5817
|
+
* `last_response.images`, an *array* of `{type, base64, width, height}` —
|
|
5818
|
+
* plural and array-wrapped, unlike `inpaintV3`'s singular `image` above,
|
|
5819
|
+
* even though exactly one image is ever sent or expected here.
|
|
5640
5820
|
*/
|
|
5641
5821
|
async editImagesV2(args) {
|
|
5642
5822
|
const body = {
|
|
@@ -5791,7 +5971,7 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
5791
5971
|
return new _PixelLabProvider(new PixelLabClient("download-only"));
|
|
5792
5972
|
}
|
|
5793
5973
|
supports(generator) {
|
|
5794
|
-
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";
|
|
5795
5975
|
}
|
|
5796
5976
|
/**
|
|
5797
5977
|
* `inpaint` (`/inpaint-v3`, a mask) and `image-to-image` (`/edit-images-v2`,
|
|
@@ -5827,7 +6007,7 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
5827
6007
|
const height = spec.revision.sourceHeight ?? spec.height;
|
|
5828
6008
|
return { unit: "generations", amount: generationCost(width, height, "1dir"), candidates: 1 };
|
|
5829
6009
|
}
|
|
5830
|
-
if (spec.generator === "tiles") {
|
|
6010
|
+
if (spec.generator === "tiles" || spec.generator === "terrain") {
|
|
5831
6011
|
return { unit: "generations", amount: spec.cost, candidates: spec.candidates };
|
|
5832
6012
|
}
|
|
5833
6013
|
if (spec.generator === "character") {
|
|
@@ -5871,10 +6051,55 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
5871
6051
|
"high detail"
|
|
5872
6052
|
]);
|
|
5873
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
|
+
}
|
|
5874
6094
|
if (spec.generator === "character") this.validateCharacter(spec, styleImages);
|
|
5875
6095
|
if ((spec.generator === "map" || spec.generator === "pixflux") && styleImages.length) {
|
|
5876
6096
|
throw new Error(`PixelLab ${spec.generator} does not support style images`);
|
|
5877
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
|
+
}
|
|
5878
6103
|
for (const image of styleImages) {
|
|
5879
6104
|
if (image.width > 256 || image.height > 256) {
|
|
5880
6105
|
throw new Error(
|
|
@@ -6215,9 +6440,20 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
6215
6440
|
const res2 = await this.client.createTilesPro({
|
|
6216
6441
|
description: spec.prompt,
|
|
6217
6442
|
tileSize: spec.tileSize,
|
|
6443
|
+
tileHeight: spec.tileHeight,
|
|
6218
6444
|
tileType: spec.tileType,
|
|
6219
6445
|
tileView: spec.tileView,
|
|
6446
|
+
tileViewAngle: spec.tileViewAngle,
|
|
6447
|
+
tileDepthRatio: spec.tileDepthRatio,
|
|
6448
|
+
tileFlatTopPx: spec.tileFlatTopPx,
|
|
6449
|
+
obliqueLean: spec.obliqueLean,
|
|
6220
6450
|
tileFeature: spec.tileFeature,
|
|
6451
|
+
buildingWallTiles: spec.buildingWallTiles,
|
|
6452
|
+
buildingLayout: spec.buildingLayout,
|
|
6453
|
+
buildingWallDescription: spec.buildingWallDescription,
|
|
6454
|
+
buildingFloorDescription: spec.buildingFloorDescription,
|
|
6455
|
+
buildingFloor2Description: spec.buildingFloor2Description,
|
|
6456
|
+
buildingWallAngle: spec.buildingWallAngle,
|
|
6221
6457
|
outlineMode: spec.outlineMode,
|
|
6222
6458
|
seed: spec.seed,
|
|
6223
6459
|
// TilesProStyleImage is flat and wants the reference's real dimensions,
|
|
@@ -6226,6 +6462,26 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
6226
6462
|
});
|
|
6227
6463
|
return { jobId: res2.tile_id, metadata: { backgroundJobId: res2.background_job_id } };
|
|
6228
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
|
+
}
|
|
6229
6485
|
if (spec.generator === "1dir") {
|
|
6230
6486
|
const res2 = await this.client.create1Direction({
|
|
6231
6487
|
description: spec.prompt,
|
|
@@ -6331,6 +6587,7 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
6331
6587
|
}
|
|
6332
6588
|
if (generator === "map") return this.pollMap(jobId, context);
|
|
6333
6589
|
if (generator === "tiles") return this.pollTiles(jobId, Boolean(context?.tileFeature), context);
|
|
6590
|
+
if (generator === "terrain") return this.pollTerrain(jobId, context);
|
|
6334
6591
|
if (generator === "character") return this.pollCharacter(jobId, context);
|
|
6335
6592
|
const backgroundJobId = context?.metadata?.backgroundJobId;
|
|
6336
6593
|
const obj = await this.client.getObject(jobId);
|
|
@@ -6351,16 +6608,17 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
6351
6608
|
/**
|
|
6352
6609
|
* `/inpaint-v3` and `/edit-images-v2` both hand back a plain background
|
|
6353
6610
|
* job with no resource of its own, polled generically at
|
|
6354
|
-
* `GET /background-jobs/{id}`.
|
|
6355
|
-
*
|
|
6356
|
-
*
|
|
6357
|
-
*
|
|
6358
|
-
*
|
|
6359
|
-
*
|
|
6360
|
-
*
|
|
6361
|
-
*
|
|
6362
|
-
*
|
|
6363
|
-
*
|
|
6611
|
+
* `GET /background-jobs/{id}`. Confirmed live for both, and the two do not
|
|
6612
|
+
* match: a completed `inpaint-v3` job's `last_response.image` is a single
|
|
6613
|
+
* `{type: "base64", base64, width, height}`, matched by the `imageKeys`
|
|
6614
|
+
* branch below; a completed `edit-images-v2` job's is `last_response.images`,
|
|
6615
|
+
* an *array* of that same shape, matched by the `done.images` branch below
|
|
6616
|
+
* instead — exactly why both branches exist rather than just the first one
|
|
6617
|
+
* (docs/ENDPOINTS.md, docs/REVISIONS.md). Beyond these two confirmed
|
|
6618
|
+
* shapes, this also checks a nested `{image: {base64, format}}` and a
|
|
6619
|
+
* hosted URL under a handful of plausible keys, and fails loudly, naming
|
|
6620
|
+
* the keys it actually got, rather than guess wrong silently, for whatever
|
|
6621
|
+
* shape still isn't covered.
|
|
6364
6622
|
*/
|
|
6365
6623
|
async pollRevision(jobId) {
|
|
6366
6624
|
const job = await this.client.getBackgroundJob(jobId);
|
|
@@ -6468,6 +6726,43 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
6468
6726
|
throw err;
|
|
6469
6727
|
}
|
|
6470
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
|
+
}
|
|
6471
6766
|
async selectCandidate(jobId, index, commonTag, generator) {
|
|
6472
6767
|
if (generator === "tiles") {
|
|
6473
6768
|
const set = await this.client.getTilesPro(jobId);
|
|
@@ -7818,6 +8113,10 @@ async function resolveSpecs(loaded, filter) {
|
|
|
7818
8113
|
size = asset.state?.canvas ? Math.max(asset.state.canvas.width, asset.state.canvas.height) : asset.size ?? style.size ?? 64;
|
|
7819
8114
|
width = asset.state?.canvas?.width ?? size;
|
|
7820
8115
|
height = asset.state?.canvas?.height ?? size;
|
|
8116
|
+
} else if (generator === "terrain") {
|
|
8117
|
+
size = style.terrainTileSize ?? 16;
|
|
8118
|
+
width = size;
|
|
8119
|
+
height = size;
|
|
7821
8120
|
} else {
|
|
7822
8121
|
width = asset.width ?? style.size ?? 64;
|
|
7823
8122
|
height = asset.height ?? style.size ?? 64;
|
|
@@ -7830,12 +8129,33 @@ async function resolveSpecs(loaded, filter) {
|
|
|
7830
8129
|
}
|
|
7831
8130
|
const characterKind = asset.animation ? "animation" : asset.state ? "state" : "base";
|
|
7832
8131
|
const subject = asset.promptByStyle[styleId] ?? asset.prompt ?? "";
|
|
7833
|
-
|
|
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
|
+
}
|
|
7834
8153
|
const relFile = asset.file ?? path13.join(asset.category ?? "", `${assetId}.png`);
|
|
7835
8154
|
const outFile = path13.resolve(root, style.outDir, relFile);
|
|
7836
8155
|
const qualityOutFile = style.quality ? path13.resolve(root, style.quality.outDir, pngPath(relFile)) : void 0;
|
|
7837
8156
|
const tileSize = generator === "tiles" ? size : style.tileSize ?? 32;
|
|
7838
8157
|
const tileVariations = tileFeatureOutputCount(generator === "tiles" ? style.tileFeature : void 0) ?? tileVariationCount(countNumberedDescriptions(prompt));
|
|
8158
|
+
const terrainTiles = generator === "terrain" ? terrainTileCount(style.terrainTransitionSize) : 0;
|
|
7839
8159
|
const base = {
|
|
7840
8160
|
styleId,
|
|
7841
8161
|
assetId,
|
|
@@ -7857,14 +8177,36 @@ async function resolveSpecs(loaded, filter) {
|
|
|
7857
8177
|
enforcePalette: style.enforcePalette,
|
|
7858
8178
|
noBackground: style.noBackground,
|
|
7859
8179
|
tileSize: generator === "tiles" ? tileSize : void 0,
|
|
8180
|
+
tileHeight: generator === "tiles" ? style.tileHeight : void 0,
|
|
7860
8181
|
tileType: generator === "tiles" ? style.tileType : void 0,
|
|
7861
8182
|
tileView: generator === "tiles" ? style.tileView : void 0,
|
|
8183
|
+
tileViewAngle: generator === "tiles" ? style.tileViewAngle : void 0,
|
|
8184
|
+
tileDepthRatio: generator === "tiles" ? style.tileDepthRatio : void 0,
|
|
8185
|
+
tileFlatTopPx: generator === "tiles" ? style.tileFlatTopPx : void 0,
|
|
8186
|
+
obliqueLean: generator === "tiles" ? style.obliqueLean : void 0,
|
|
7862
8187
|
tileFeature: generator === "tiles" ? style.tileFeature : void 0,
|
|
8188
|
+
buildingWallTiles: generator === "tiles" ? style.buildingWallTiles : void 0,
|
|
8189
|
+
buildingLayout: generator === "tiles" ? style.buildingLayout : void 0,
|
|
8190
|
+
buildingWallDescription: generator === "tiles" ? style.buildingWallDescription : void 0,
|
|
8191
|
+
buildingFloorDescription: generator === "tiles" ? style.buildingFloorDescription : void 0,
|
|
8192
|
+
buildingFloor2Description: generator === "tiles" ? style.buildingFloor2Description : void 0,
|
|
8193
|
+
buildingWallAngle: generator === "tiles" ? style.buildingWallAngle : void 0,
|
|
7863
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,
|
|
7864
8206
|
...generator === "character" ? { character: await resolveCharacterShape(asset, style, characterKind, { root, load: loadStyleImage }) } : {},
|
|
7865
|
-
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),
|
|
7866
8208
|
costUnit: "generations",
|
|
7867
|
-
candidates: generator === "tiles" ? tileVariations : generator === "1dir" ? candidateCount(size) : 1
|
|
8209
|
+
candidates: generator === "tiles" ? tileVariations : generator === "terrain" ? terrainTiles : generator === "1dir" ? candidateCount(size) : 1
|
|
7868
8210
|
};
|
|
7869
8211
|
const tags = [
|
|
7870
8212
|
.../* @__PURE__ */ new Set([
|
|
@@ -7912,7 +8254,7 @@ async function resolveSpecs(loaded, filter) {
|
|
|
7912
8254
|
if (asset.mirror) {
|
|
7913
8255
|
if (asset.mirror === assetId) throw new Error(`assets.${assetId}: an asset cannot mirror itself`);
|
|
7914
8256
|
const sourceSpec = await finalize(asset.mirror);
|
|
7915
|
-
if (sourceSpec.generator === "tiles") {
|
|
8257
|
+
if (sourceSpec.generator === "tiles" || sourceSpec.generator === "terrain") {
|
|
7916
8258
|
throw new Error(`assets.${assetId}: a tile set cannot be mirrored; its edges carry meaning`);
|
|
7917
8259
|
}
|
|
7918
8260
|
if (sourceSpec.character?.kind === "animation") {
|