@pixelvibing/cli 0.60.0 → 0.62.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.
@@ -1001,7 +1001,7 @@ class DesktopSession {
1001
1001
  const challenge = createHash("sha256").update(verifier).digest("base64url");
1002
1002
  const back = await this.awaitCallback(
1003
1003
  (port) => this.options.openExternal(
1004
- `${this.options.apiUrl}/auth/${encodeURIComponent(provider)}/start?desktop=${port}&challenge=${challenge}${this.options.app === "cli" ? "&app=cli" : ""}`
1004
+ `${this.options.apiUrl}/auth/${encodeURIComponent(provider)}/start?desktop=${port}&challenge=${challenge}${this.options.app === void 0 || this.options.app === "desktop" ? "" : `&app=${this.options.app}`}`
1005
1005
  )
1006
1006
  );
1007
1007
  const outcome = back.code === null ? back.outcome : await this.exchange(back.code, verifier).catch(() => "failed");
@@ -2764,6 +2764,16 @@ async function writeArtMirror(boundary, art, mirror) {
2764
2764
  for (const manifest2 of before.manifests) if (!index.manifests.includes(manifest2)) await gone(join(MANIFEST_DIRECTORY, `${manifest2}.json`));
2765
2765
  return index;
2766
2766
  }
2767
+ function folderMirror(art) {
2768
+ const boundary = new Boundary([art]);
2769
+ return {
2770
+ state: () => mirrorState(boundary, art),
2771
+ map: (each) => writeMapMirror(boundary, art, each),
2772
+ art: (each) => writeArtMirror(boundary, art, each),
2773
+ folder: () => readFolder(boundary, art),
2774
+ moving: (project) => markMoving(boundary, art, project)
2775
+ };
2776
+ }
2767
2777
  const SECTOR_SIDE = 128;
2768
2778
  const MOST_READ = 32;
2769
2779
  const MOST_WAIT_SECONDS$1 = 60;
@@ -2880,15 +2890,7 @@ async function syncArt(deps) {
2880
2890
  await deps.mirror.art({ revision: art.revision, assets: art.assets, pictures, idMap: art.idMap, references: referenced, styleGuide });
2881
2891
  }
2882
2892
  function mirrorOf(local) {
2883
- const art = artOf(local);
2884
- const boundary = new Boundary([art]);
2885
- return {
2886
- state: () => mirrorState(boundary, art),
2887
- map: (each) => writeMapMirror(boundary, art, each),
2888
- art: (each) => writeArtMirror(boundary, art, each),
2889
- folder: () => readFolder(boundary, art),
2890
- moving: (project) => markMoving(boundary, art, project)
2891
- };
2893
+ return folderMirror(artOf(local));
2892
2894
  }
2893
2895
  const deflated = promisify(deflate);
2894
2896
  const inflated = promisify(inflate);
@@ -4272,7 +4274,16 @@ function phaserFiles(made) {
4272
4274
  format: "RGBA8888",
4273
4275
  size: { w: page.width, h: page.height },
4274
4276
  scale: 1,
4275
- frames: packed.placements.filter((placed) => placed.page === index).map((placed) => ({ filename: placed.name, ...textureFrame(placed) }))
4277
+ // A frame that stretches carries its centre as `scale9Borders`, which Phaser 4's atlas parser gives the frame, so
4278
+ // `this.add.nineslice(x, y, key, frame, width, height)` stretches it without being told the insets.
4279
+ frames: packed.placements.filter((placed) => placed.page === index).map((placed) => {
4280
+ const grid = made.nineGrids.get(placed.name);
4281
+ return {
4282
+ filename: placed.name,
4283
+ ...textureFrame(placed),
4284
+ ...grid === void 0 ? {} : { scale9Borders: { x: grid.left, y: grid.top, w: placed.width - grid.left - grid.right, h: placed.height - grid.top - grid.bottom } }
4285
+ };
4286
+ })
4276
4287
  })),
4277
4288
  meta: { app: "https://pixelvibing.com", version: String(EXPORT_FORMAT) }
4278
4289
  };
@@ -4619,8 +4630,50 @@ function encodeWith(zlib, raster) {
4619
4630
  function pngCodec(zlib) {
4620
4631
  return { decodePng: (bytes) => decodeWith(zlib, bytes), encodePng: (raster) => encodeWith(zlib, raster) };
4621
4632
  }
4622
- const EXPORT_MARK = ".pixelvibing-export";
4623
4633
  const codec$1 = pngCodec({ inflate: (data, maxOutputLength) => inflateSync(data, { maxOutputLength }), deflate: (data) => deflateSync(data) });
4634
+ class ExportRefused extends Error {
4635
+ constructor(message) {
4636
+ super(message);
4637
+ this.name = "ExportRefused";
4638
+ }
4639
+ }
4640
+ const REFUSALS = [ProjectError, PackError, PngError, MapTextError, MirrorError, IdMapError, SyntaxError, ExportRefused];
4641
+ async function exportFolder(art, engine, options = {}) {
4642
+ try {
4643
+ const boundary = new Boundary([art]);
4644
+ const read = await readProjectArt(boundary, art);
4645
+ const mirrored2 = await readMirroredProjectMap(boundary, art);
4646
+ const mapText = mirrored2 === null ? await readProjectMap(boundary, art) : null;
4647
+ checkExportable(
4648
+ read.pictures.map((picture) => {
4649
+ if (!isPng(picture.bytes)) throw new ExportRefused(`the picture ${picture.name} cannot be read: it is not a PNG`);
4650
+ return { name: picture.name, ...pngSize(picture.bytes) };
4651
+ })
4652
+ );
4653
+ const pictures = read.pictures.map((picture) => {
4654
+ try {
4655
+ return { name: picture.name, raster: codec$1.decodePng(picture.bytes) };
4656
+ } catch (error) {
4657
+ throw new ExportRefused(`the picture ${picture.name} cannot be read: ${error.message}`);
4658
+ }
4659
+ });
4660
+ return exportProject(
4661
+ {
4662
+ pictures,
4663
+ entries: read.entries,
4664
+ map: mirrored2 ?? (mapText === null ? null : parseMapText(mapText, "pixelvibing/map.json").document),
4665
+ idMap: read.idMap === null ? null : parseIdMap(JSON.parse(read.idMap))
4666
+ },
4667
+ engine,
4668
+ codec$1,
4669
+ options
4670
+ );
4671
+ } catch (error) {
4672
+ if (REFUSALS.some((kind) => error instanceof kind)) throw error instanceof ExportRefused ? error : new ExportRefused(error.message);
4673
+ throw error;
4674
+ }
4675
+ }
4676
+ const EXPORT_MARK = ".pixelvibing-export";
4624
4677
  async function isDirectory(path) {
4625
4678
  try {
4626
4679
  return (await lstat(path)).isDirectory();
@@ -4699,37 +4752,9 @@ const exportCommand = {
4699
4752
  if (!await replaceable(out)) throw new CliError(`${out} holds files that are not an export's: nothing was written. Name an empty directory with --out`);
4700
4753
  let result;
4701
4754
  try {
4702
- const boundary = new Boundary([art]);
4703
- const read = await readProjectArt(boundary, art);
4704
- const mirrored2 = await readMirroredProjectMap(boundary, art);
4705
- const mapText = mirrored2 === null ? await readProjectMap(boundary, art) : null;
4706
- checkExportable(
4707
- read.pictures.map((picture) => {
4708
- if (!isPng(picture.bytes)) throw new CliError(`the picture ${picture.name} cannot be read: it is not a PNG`);
4709
- return { name: picture.name, ...pngSize(picture.bytes) };
4710
- })
4711
- );
4712
- const pictures = read.pictures.map((picture) => {
4713
- try {
4714
- return { name: picture.name, raster: codec$1.decodePng(picture.bytes) };
4715
- } catch (error) {
4716
- throw new CliError(`the picture ${picture.name} cannot be read: ${error.message}`);
4717
- }
4718
- });
4719
- result = exportProject(
4720
- {
4721
- pictures,
4722
- entries: read.entries,
4723
- map: mirrored2 ?? (mapText === null ? null : parseMapText(mapText, "pixelvibing/map.json").document),
4724
- idMap: read.idMap === null ? null : parseIdMap(JSON.parse(read.idMap))
4725
- },
4726
- engine,
4727
- codec$1,
4728
- godotRoot === void 0 ? {} : { godotRoot }
4729
- );
4755
+ result = await exportFolder(art, engine, godotRoot === void 0 ? {} : { godotRoot });
4730
4756
  } catch (error) {
4731
- const refusals = [ProjectError, PackError, PngError, MapTextError, MirrorError, IdMapError, SyntaxError, CliError];
4732
- if (refusals.some((kind) => error instanceof kind)) throw new CliError(`nothing was exported: ${error.message}`);
4757
+ if (error instanceof ExportRefused) throw new CliError(`nothing was exported: ${error.message}`);
4733
4758
  throw error;
4734
4759
  }
4735
4760
  await writeWhole(out, result.files, engine);
@@ -7480,7 +7505,7 @@ function terminalOf(streams) {
7480
7505
  step: (title, at = "step") => drawStep(streams.stderr, title, at)
7481
7506
  };
7482
7507
  }
7483
- const version = "0.60.0";
7508
+ const version = "0.62.0";
7484
7509
  const manifest = {
7485
7510
  version
7486
7511
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pixelvibing/cli",
3
- "version": "0.60.0",
3
+ "version": "0.62.0",
4
4
  "description": "PixelVibing: pixel art game assets from the command line, and the Skills that teach coding assistants to make them.",
5
5
  "license": "MIT",
6
6
  "homepage": "https://pixelvibing.com",
@@ -26,7 +26,8 @@
26
26
  "build": "npm run build --workspace=@pixelvibing/toolkit && node scripts/build.mjs",
27
27
  "check": "node ../node_modules/typescript/bin/tsc -p tsconfig.json --noEmit",
28
28
  "check:package": "node ../editor/src/main/package-guard.ts dist && node ../editor/src/main/package-guard.ts skills",
29
- "test": "node --import ../editor/scripts/test-register.mjs --test \"src/**/*.test.ts\""
29
+ "test": "node --import ../editor/scripts/test-register.mjs --test \"src/**/*.test.ts\"",
30
+ "skills": "node --import ../editor/scripts/test-register.mjs scripts/skills.mjs"
30
31
  },
31
32
  "publishConfig": {
32
33
  "access": "public"
@@ -20,6 +20,8 @@ pixel-vibing-cli generate concept-art --instruction "<the picture>" [--size <w>x
20
20
  pixel-vibing-cli generate concept-art --instruction "<the area>" --pixel-art --tiles <cols>x<rows> [--no-enrich] --yes --json
21
21
  ```
22
22
 
23
+ The fields under *Writing it* are these flags: `size` is `--size <w>x<h>`, `background: "transparent"` is `--transparent`, `mask_image` is `--mask <png>`, each tag a `--tag`, each of `reference_images` a `--reference <png>`, `pixel_art` and `tiles` are `--pixel-art --tiles`, and `enrich: false` is `--no-enrich`.
24
+
23
25
  ## With the MCP server
24
26
 
25
27
  ```json
@@ -37,29 +39,23 @@ which every other generation takes as `reference_images`.
37
39
  ## Writing it
38
40
 
39
41
  - **The instruction.** Brief it as an art director would: what is in the picture, where,
40
- how it looks, the light, and how its parts meet. Use the style guide's words.
41
- - **The size.** `--size` (MCP `size`) in px, `<w>x<h>`, multiples of 16 within `1536x1024`
42
- or `1024x1536`, `1536x1024` unless given. `--transparent` (`background: "transparent"`)
43
- has it drawn on a transparent background, for one thing alone: say so in the words too, and a
44
- shadow under it only if you want one.
42
+ how it looks, the light, and how its parts meet. Use the style guide's words. It is
43
+ ordinary art, not pixel art, made to draw other generations from: for a new project, make
44
+ one tagged `anchor` first.
45
+ - **The size.** `size` in px, `{ width, height }`, multiples of 16 within 1536×1024 or
46
+ 1024×1536, 1536×1024 unless given. `background: "transparent"` draws it on a transparent
47
+ background, for one thing alone: say so in the words too.
45
48
  - **Tags** say what it is for, at most 8: `anchor` (the look everything holds to),
46
- `level-design`, `ui`, `character`, `environment`, `prop`, or one of the project's own
47
- (`list_tags`). `pixel-vibing-cli tag` (MCP `tag_asset`) changes them later.
48
- - **References.** Up to 8 to draw from, such as the anchor itself.
49
- - **A mask.** Keeps the drawing to a shape you choose: an island's coastline, a
50
- character's silhouette, a frame for a menu, the open middle of a scene. It is an opaque
51
- PNG of black and white only, white where the art is drawn and black where the drawing is
52
- left empty, in the drawing's shape: the `--size` (`1536x1024` unless given), or for pixel
53
- art the `--tiles` in proportion, such as 3:2 for `6x4`. Any size in that shape is scaled
54
- to fit, up to 1536 px a side, so a small one drawn by hand or by a script is enough. A soft edge is fine; grey
55
- shading, colour or transparency is refused with the reason before anything is spent.
56
- Pass it as `--mask <png>` (MCP: keep it with `upload_image`, then give its id as
57
- `mask_image`). With `--transparent`, the black part comes back transparent as well.
58
- - **A level design.** `--pixel-art --tiles <c>x<r>` (MCP `pixel_art`, `tiles`) draws an area
59
- of the map as pixel art some tiles across, `6x4` unless given, tagged `level-design`; the
60
- editor takes it apart into objects and terrain.
61
- - **Enrichment.** Pass `--no-enrich` (`enrich: false`) when the brief above is complete;
62
- leave it on when all you have is the person's few words.
49
+ `level-design`, `ui`, `character`, `environment`, `prop`, or one of the project's own.
50
+ - **References.** Up to 8 to draw from (`reference_images`), such as the anchor itself.
51
+ - **A mask** (`mask_image`) keeps the drawing to a shape: an island's coastline, a
52
+ character's silhouette, a frame for a menu. It is an opaque picture of black and white
53
+ only, white where the art is drawn and black where it is left empty, in the drawing's
54
+ shape, at most 1536 px a side.
55
+ - **A level design.** `pixel_art: true` with `tiles` draws an area of the map as pixel art
56
+ some tiles across, 6×4 unless given, tagged `level-design`, to be taken apart into objects
57
+ and terrain.
58
+ - **Enrichment.** Turn it off (`enrich: false`) when the instruction above is complete; leave it on when all you have is the person's few words.
63
59
 
64
60
  ## After
65
61
 
@@ -15,6 +15,8 @@ Load the `pixelvibing` Skill first: who is signed in, the style guide, the limit
15
15
  pixel-vibing-cli generate creature --instruction "<who>" [--reference <png>]... [--no-enrich] --yes --json
16
16
  ```
17
17
 
18
+ The fields under *Writing it* are these flags: each of `reference_images` a `--reference <png>`, such as a concept art pulled under `pixelvibing/ref/`, and `enrich: false` is `--no-enrich`.
19
+
18
20
  ## With the MCP server
19
21
 
20
22
  ```json
@@ -32,11 +34,12 @@ pixel-vibing-cli generate creature --instruction "<who>" [--reference <png>]...
32
34
  characters or names, and no text in the picture: when the person names a franchise, make
33
35
  original art in its spirit and say so.
34
36
  - **One creature, standing.** It is drawn whole, standing, on one tile. A pose, a crowd or a
35
- scene is concept art (`pixelvibing-concept-art`).
36
- - **References.** Up to 3: a character sheet or a concept art tagged `character` or
37
- `anchor` keeps it on model.
38
- - **Enrichment.** Pass `--no-enrich` (`enrich: false`) when the instruction above is
39
- complete; leave it on when all you have is the person's few words.
37
+ scene is concept art.
38
+ - **References.** Up to 3 (`reference_images`): a character sheet or a concept art tagged
39
+ `character` or `anchor` keeps it on model.
40
+ - **Its motion.** Its eight directions and its states — walking, hurt, dead — are changes of
41
+ it, made once it exists.
42
+ - **Enrichment.** Turn it off (`enrich: false`) when the instruction above is complete; leave it on when all you have is the person's few words.
40
43
 
41
44
  ## After
42
45
 
@@ -15,6 +15,8 @@ Load the `pixelvibing` Skill first: who is signed in, the style guide, the limit
15
15
  pixel-vibing-cli generate icons --names "<name>,<name>,..." --purpose <skill|item> [--size <16|32|64>] [--instruction "<theme>"] [--no-enrich] --yes --json
16
16
  ```
17
17
 
18
+ The fields under *Writing it* are these flags: `names` is `--names`, comma-separated, `icon_size` is `--size`, and `enrich: false` is `--no-enrich`.
19
+
18
20
  ## With the MCP server
19
21
 
20
22
  ```json
@@ -28,17 +30,16 @@ them so.
28
30
 
29
31
  ## Writing it
30
32
 
31
- - **The names.** One per icon, at most 16, none twice: what the skill or the item is, in a
32
- few words ("red health potion", "rusty key"). Make a whole set in one generation, so the
33
- icons match.
33
+ - **The names.** One per icon (`names`), at most 16, none twice: what the skill or the item
34
+ is, in a few words ("red health potion", "rusty key"). Make a whole set in one generation,
35
+ so the icons match.
34
36
  - **The purpose.** `skill` draws each on a square badge, for a hotbar or a skill tree;
35
37
  `item` draws each alone, for an inventory or loot; `item` unless given.
36
- - **The size.** 16, 32 or 64 px a side, 32 unless given: the size the game shows them at.
37
- - **The theme.** `--instruction` is optional: what the icons have in common, their colours
38
- and framing. Use the style guide's words.
39
- - **No references.** An icon never carries text or numbers: draw counts and labels in the game.
40
- - **Enrichment.** Pass `--no-enrich` (`enrich: false`) when the theme is complete; leave it
41
- on when all you have is the person's few words.
38
+ - **The size.** `icon_size` 16, 32 or 64 px a side, 32 unless given: the size the game shows them at.
39
+ - **The theme.** The instruction is optional: what the icons have in common, their colours,
40
+ their framing, their material. Use the style guide's words.
41
+ - **No references, no text.** An icon never carries text or numbers: the game draws counts and labels.
42
+ - **Enrichment.** Turn it off (`enrich: false`) when the instruction above is complete; leave it on when all you have is the person's few words.
42
43
 
43
44
  ## After
44
45
 
@@ -15,6 +15,8 @@ Load the `pixelvibing` Skill first: who is signed in, the style guide, the limit
15
15
  pixel-vibing-cli generate object --instruction "<what>" --size <cols>x<rows> [--reference <png>]... [--no-enrich] --yes --json
16
16
  ```
17
17
 
18
+ The fields under *Writing it* are these flags: `size` is `--size`, each of `reference_images` a `--reference <png>`, such as a concept art pulled under `pixelvibing/ref/`, and `enrich: false` is `--no-enrich`.
19
+
18
20
  ## With the MCP server
19
21
 
20
22
  ```json
@@ -31,14 +33,12 @@ pixel-vibing-cli generate object --instruction "<what>" --size <cols>x<rows> [--
31
33
  and colours, the details that make it read at its size, and what it is for in the game.
32
34
  Use the style guide's words for the look; never restate its camera or outline, the
33
35
  platform adds them. No artist's or other game's names, and no text in the picture.
34
- - **The footprint.** `--size` (MCP `size`) is how many tiles it stands on, `1x1` unless
35
- given: a crate is `1x1`, a cart `2x1`, a house `3x3`. The platform sizes the drawing to the
36
- project's tile size.
37
- - **References.** Up to 8 pictures to draw from: a concept art tagged `anchor` keeps every
38
- object on one look (`pixelvibing/ref/<name>.png` for the CLI, its image id from
39
- `list_concept_art` for MCP), and a sketch or an earlier object keeps a shape.
40
- - **Enrichment.** Pass `--no-enrich` (`enrich: false`) when the instruction above is
41
- complete; leave it on when all you have is the person's few words.
36
+ - **The footprint.** `size` is how many tiles it stands on, 1×1 unless given: a crate is
37
+ 1×1, a cart 2×1, a house 3×3. The platform sizes the drawing to the project's tile size.
38
+ - **References.** Up to 8 pictures to draw from (`reference_images`): a concept art tagged
39
+ `anchor` keeps every object on one look, and a sketch or an earlier object keeps a shape.
40
+ - **One at a time.** Never generate the same object again to fix a detail: change it instead.
41
+ - **Enrichment.** Turn it off (`enrich: false`) when the instruction above is complete; leave it on when all you have is the person's few words.
42
42
 
43
43
  ## After
44
44
 
@@ -18,6 +18,8 @@ pixel-vibing-cli generate music --instruction "<the piece>" [--seconds <3-600>]
18
18
  pixel-vibing-cli generate music --plan <plan.json> [--out <file>] --yes --json
19
19
  ```
20
20
 
21
+ The fields under *Writing it* are these flags: `seconds` is `--seconds`, `loop` is `--loop`, `instrumental` is `--instrumental`, and a `plan` is a file `--plan` reads.
22
+
21
23
  A plan's file is the track section by section, in order:
22
24
 
23
25
  ```json
@@ -50,25 +52,27 @@ with the tool `generate_sound`. `get_job` follows the job and, once done, names
50
52
 
51
53
  ## Writing it
52
54
 
53
- - **A sound effect.** What makes the sound and how it feels: "a wooden door creaking open,
54
- slow", "a coin picked up, bright and short". `--seconds` sets its length; `--loop` makes it
55
- loop seamlessly, for rain, wind or an engine.
56
- - **Speech.** The words exactly as they are said, in a voice. Keep a line short; one line a
57
- generation.
58
- - **Music.** The mood, the instruments and the tempo: "a calm harbour theme on strings and a
59
- soft accordion". It runs 60 seconds unless `--seconds` (`seconds`) says otherwise, from 3 to
60
- 600. A game's theme, a level's loop or a menu track is asked at its full length: a short one
61
- ends where it stops. `--instrumental` (`instrumental`) makes one with no voice.
62
- - **Music section by section.** Give a plan instead of a description when the track has parts
63
- that must land in order: an intro, a loop, a boss phase, an ending. Each section is 3 to 120
64
- seconds, with its own styles, styles to avoid, and lyrics for its sung lines (none for an
65
- instrumental part); the track is at most 600 seconds in all. `flexibleLengths`
66
- (`flexible_lengths` on the MCP server) lets each section's length move while the track's stays.
55
+ - **A sound effect** (`sfx`). What makes the sound and how it feels: "a wooden door creaking
56
+ open, slow", "a coin picked up, bright and short". `seconds` sets its length; `loop` makes
57
+ it loop seamlessly, for rain, wind or an engine.
58
+ - **Speech** (`speech`). The words exactly as they are said, in a `voice`. Keep a line short;
59
+ one line a generation.
60
+ - **Music** (`music`). The mood, the instruments and the tempo: "a calm harbour theme on
61
+ strings and a soft accordion". It runs 60 seconds unless `seconds` says otherwise, from 3
62
+ to 600. A game's theme, a level's loop or a menu track is asked at its full length: a short
63
+ one ends where it stops. `instrumental` makes one with no voice.
64
+ - **Music section by section.** Give a `plan` instead of a description when the track has
65
+ parts that must land in order: an intro, a loop, a boss phase, an ending. Each section is 3
66
+ to 120 seconds, with its own styles, styles to avoid, and lyrics for its sung lines; the
67
+ track is at most 600 seconds in all. `flexible_lengths` lets each section's length move
68
+ while the track's stays.
67
69
  - **What music uses.** Music uses the limit by the minute of its length, rounded up: a
68
- four-minute theme uses four times a one-minute one. Say so before asking for a long track, and
69
- keep `max_percent` to what the person agreed.
70
+ four-minute theme uses four times a one-minute one. Say so before asking for a long track.
70
71
  - **Original sound.** Never another game's music or theme, and never a real person's voice:
71
72
  when the person names one, make original sound in its spirit and say so.
73
+
74
+ ## After
75
+
72
76
  - **Where it goes.** The game loads the MP3 from where it was written; copy it into the game's
73
77
  own audio folder when that is where the engine looks. Sounds are not part of the map, the
74
78
  export or the sealed container.
@@ -19,6 +19,8 @@ and `references/ui.example.json` is a whole HUD laid out.
19
19
  pixel-vibing-cli generate ui --sketch <file.json> --instruction "<the set, in a line>" [--reference <png>]... --yes --json
20
20
  ```
21
21
 
22
+ The fields under *Writing it* are the sketch's: `canvas` and `shapes` are its file's, read by `--sketch`; each of the references a `--reference <png>`; `--no-enrich` and `enrich` are refused for a UI.
23
+
22
24
  ## With the MCP server
23
25
 
24
26
  The layout goes in the call itself, as `canvas` and `shapes`, with the same fields:
@@ -39,13 +41,18 @@ result's `missing`: generate the UI again for it rather than retrying the whole
39
41
 
40
42
  - **The instruction** is the set's theme in a line, up to 600 characters; what each piece
41
43
  is goes in its `label` and `note`.
42
- - **References**, up to 3, are a style to follow (frames, materials, a way of drawing),
43
- never what to draw. A concept art tagged `ui` is the usual one.
44
- - **Text.** Only a `caption` is drawn as text. Counts, values and names change in play: the
44
+ - **The canvas.** `canvas` is 128×128, 256×256, 256×128 or 128×256 px: the one the screen's
45
+ pieces fit at the size the game shows them.
46
+ - **The shapes.** 1 to 16, none overlapping, 4 px or more apart. Each has a `label` (its role,
47
+ never drawn, unique), a `kind` (`rect`, `ellipse` or `polygon` with `sides` 3 to 8),
48
+ `x`, `y`, `width` and `height` (at least 4), and as needed a `caption` (the only text
49
+ drawn), a `note` (how it looks), a `border` (its frame, which stretches) and `hollow`
50
+ (only the border drawn).
51
+ - **References**, up to 3, are a style to follow (frames, materials, a way of drawing), never
52
+ what to draw. A concept art tagged `ui` is the usual one.
53
+ - **Text.** Only a caption is drawn as text. Counts, values and names change in play: the
45
54
  game draws them.
46
- - **No enrichment switch.** A UI's layout is read as it is; `--no-enrich` and `enrich` are
47
- refused for it.
48
- - `ui-sketch` is the same kind by its platform name.
55
+ - **No enrichment switch.** A UI's layout is read as it is; `enrich` is refused for it.
49
56
 
50
57
  ## After
51
58