@pinepaper.studio/mcp-server 1.6.7 → 1.6.8

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.
Files changed (164) hide show
  1. package/README.de.md +9 -5
  2. package/README.es.md +9 -5
  3. package/README.fr.md +9 -5
  4. package/README.hi.md +8 -5
  5. package/README.ja.md +8 -5
  6. package/README.ko.md +8 -4
  7. package/README.md +171 -17
  8. package/README.pt-BR.md +9 -5
  9. package/README.zh-CN.md +6 -4
  10. package/dist/character/data/concepts.json +1 -0
  11. package/dist/character/data/depictions.json +1 -0
  12. package/dist/character/data/styles.json +1 -0
  13. package/dist/character/duration-scale.d.ts +13 -0
  14. package/dist/character/duration-scale.d.ts.map +1 -0
  15. package/dist/character/duration-scale.js +13 -0
  16. package/dist/character/duration-scale.js.map +1 -0
  17. package/dist/character/path-bbox.d.ts +82 -0
  18. package/dist/character/path-bbox.d.ts.map +1 -0
  19. package/dist/character/path-bbox.js +496 -0
  20. package/dist/character/path-bbox.js.map +1 -0
  21. package/dist/character/perform.d.ts +149 -0
  22. package/dist/character/perform.d.ts.map +1 -0
  23. package/dist/character/perform.js +626 -0
  24. package/dist/character/perform.js.map +1 -0
  25. package/dist/character/resolve.d.ts +51 -0
  26. package/dist/character/resolve.d.ts.map +1 -0
  27. package/dist/character/resolve.js +239 -0
  28. package/dist/character/resolve.js.map +1 -0
  29. package/dist/design/design-systems.d.ts +126 -0
  30. package/dist/design/design-systems.d.ts.map +1 -0
  31. package/dist/design/design-systems.js +240 -0
  32. package/dist/design/design-systems.js.map +1 -0
  33. package/dist/execution/canvas-state.d.ts +7 -0
  34. package/dist/execution/canvas-state.d.ts.map +1 -1
  35. package/dist/execution/canvas-state.js +20 -8
  36. package/dist/execution/canvas-state.js.map +1 -1
  37. package/dist/index.d.ts +20 -0
  38. package/dist/index.d.ts.map +1 -1
  39. package/dist/index.js +12 -4
  40. package/dist/index.js.map +1 -1
  41. package/dist/ontology/design-graph.d.ts +9 -0
  42. package/dist/ontology/design-graph.d.ts.map +1 -1
  43. package/dist/ontology/design-graph.js +98 -3
  44. package/dist/ontology/design-graph.js.map +1 -1
  45. package/dist/ontology/types.d.ts +42 -0
  46. package/dist/ontology/types.d.ts.map +1 -1
  47. package/dist/ontology/vocabulary.d.ts.map +1 -1
  48. package/dist/ontology/vocabulary.js +20 -0
  49. package/dist/ontology/vocabulary.js.map +1 -1
  50. package/dist/tools/compact-descriptions.d.ts.map +1 -1
  51. package/dist/tools/compact-descriptions.js +25 -0
  52. package/dist/tools/compact-descriptions.js.map +1 -1
  53. package/dist/tools/definitions.d.ts +1 -1
  54. package/dist/tools/definitions.d.ts.map +1 -1
  55. package/dist/tools/definitions.js +343 -24
  56. package/dist/tools/definitions.js.map +1 -1
  57. package/dist/tools/engine-methods.d.ts +18 -0
  58. package/dist/tools/engine-methods.d.ts.map +1 -0
  59. package/dist/tools/engine-methods.js +239 -0
  60. package/dist/tools/engine-methods.js.map +1 -0
  61. package/dist/tools/handlers/character.d.ts +121 -0
  62. package/dist/tools/handlers/character.d.ts.map +1 -0
  63. package/dist/tools/handlers/character.js +315 -0
  64. package/dist/tools/handlers/character.js.map +1 -0
  65. package/dist/tools/handlers.d.ts +2 -0
  66. package/dist/tools/handlers.d.ts.map +1 -1
  67. package/dist/tools/handlers.js +316 -16
  68. package/dist/tools/handlers.js.map +1 -1
  69. package/dist/tools/minimal-descriptions.d.ts.map +1 -1
  70. package/dist/tools/minimal-descriptions.js +13 -2
  71. package/dist/tools/minimal-descriptions.js.map +1 -1
  72. package/dist/tools/toolkits.d.ts.map +1 -1
  73. package/dist/tools/toolkits.js +7 -0
  74. package/dist/tools/toolkits.js.map +1 -1
  75. package/dist/types/code-generator.d.ts +96 -1
  76. package/dist/types/code-generator.d.ts.map +1 -1
  77. package/dist/types/code-generator.js +1200 -12
  78. package/dist/types/code-generator.js.map +1 -1
  79. package/dist/types/schemas.d.ts +6792 -151
  80. package/dist/types/schemas.d.ts.map +1 -1
  81. package/dist/types/schemas.js +587 -17
  82. package/dist/types/schemas.js.map +1 -1
  83. package/dist/vendor/design/art-deco-generator.d.ts +52 -0
  84. package/dist/vendor/design/art-deco-generator.d.ts.map +1 -0
  85. package/dist/vendor/design/art-deco-generator.js +144 -0
  86. package/dist/vendor/design/art-deco-generator.js.map +1 -0
  87. package/dist/vendor/design/art-nouveau-generator.d.ts +46 -0
  88. package/dist/vendor/design/art-nouveau-generator.d.ts.map +1 -0
  89. package/dist/vendor/design/art-nouveau-generator.js +134 -0
  90. package/dist/vendor/design/art-nouveau-generator.js.map +1 -0
  91. package/dist/vendor/design/bauhaus-generator.d.ts +53 -0
  92. package/dist/vendor/design/bauhaus-generator.d.ts.map +1 -0
  93. package/dist/vendor/design/bauhaus-generator.js +126 -0
  94. package/dist/vendor/design/bauhaus-generator.js.map +1 -0
  95. package/dist/vendor/design/brutalist-generator.d.ts +53 -0
  96. package/dist/vendor/design/brutalist-generator.d.ts.map +1 -0
  97. package/dist/vendor/design/brutalist-generator.js +148 -0
  98. package/dist/vendor/design/brutalist-generator.js.map +1 -0
  99. package/dist/vendor/design/claymorphism-generator.d.ts +46 -0
  100. package/dist/vendor/design/claymorphism-generator.d.ts.map +1 -0
  101. package/dist/vendor/design/claymorphism-generator.js +135 -0
  102. package/dist/vendor/design/claymorphism-generator.js.map +1 -0
  103. package/dist/vendor/design/corporate-memphis-generator.d.ts +47 -0
  104. package/dist/vendor/design/corporate-memphis-generator.d.ts.map +1 -0
  105. package/dist/vendor/design/corporate-memphis-generator.js +144 -0
  106. package/dist/vendor/design/corporate-memphis-generator.js.map +1 -0
  107. package/dist/vendor/design/cyberpunk-generator.d.ts +52 -0
  108. package/dist/vendor/design/cyberpunk-generator.d.ts.map +1 -0
  109. package/dist/vendor/design/cyberpunk-generator.js +162 -0
  110. package/dist/vendor/design/cyberpunk-generator.js.map +1 -0
  111. package/dist/vendor/design/de-stijl-generator.d.ts +50 -0
  112. package/dist/vendor/design/de-stijl-generator.d.ts.map +1 -0
  113. package/dist/vendor/design/de-stijl-generator.js +154 -0
  114. package/dist/vendor/design/de-stijl-generator.js.map +1 -0
  115. package/dist/vendor/design/design-style.d.ts +3 -0
  116. package/dist/vendor/design/design-style.d.ts.map +1 -0
  117. package/dist/vendor/design/design-style.js +12 -0
  118. package/dist/vendor/design/design-style.js.map +1 -0
  119. package/dist/vendor/design/design-systems-dtcg.d.ts +59 -0
  120. package/dist/vendor/design/design-systems-dtcg.d.ts.map +1 -0
  121. package/dist/vendor/design/design-systems-dtcg.js +571 -0
  122. package/dist/vendor/design/design-systems-dtcg.js.map +1 -0
  123. package/dist/vendor/design/frutiger-aero-generator.d.ts +46 -0
  124. package/dist/vendor/design/frutiger-aero-generator.d.ts.map +1 -0
  125. package/dist/vendor/design/frutiger-aero-generator.js +133 -0
  126. package/dist/vendor/design/frutiger-aero-generator.js.map +1 -0
  127. package/dist/vendor/design/memphis-generator.d.ts +56 -0
  128. package/dist/vendor/design/memphis-generator.d.ts.map +1 -0
  129. package/dist/vendor/design/memphis-generator.js +158 -0
  130. package/dist/vendor/design/memphis-generator.js.map +1 -0
  131. package/dist/vendor/design/mid-century-upa-generator.d.ts +46 -0
  132. package/dist/vendor/design/mid-century-upa-generator.d.ts.map +1 -0
  133. package/dist/vendor/design/mid-century-upa-generator.js +137 -0
  134. package/dist/vendor/design/mid-century-upa-generator.js.map +1 -0
  135. package/dist/vendor/design/neumorphic-generator.d.ts +53 -0
  136. package/dist/vendor/design/neumorphic-generator.d.ts.map +1 -0
  137. package/dist/vendor/design/neumorphic-generator.js +182 -0
  138. package/dist/vendor/design/neumorphic-generator.js.map +1 -0
  139. package/dist/vendor/design/op-art-generator.d.ts +45 -0
  140. package/dist/vendor/design/op-art-generator.d.ts.map +1 -0
  141. package/dist/vendor/design/op-art-generator.js +116 -0
  142. package/dist/vendor/design/op-art-generator.js.map +1 -0
  143. package/dist/vendor/design/pixel-art-generator.d.ts +45 -0
  144. package/dist/vendor/design/pixel-art-generator.d.ts.map +1 -0
  145. package/dist/vendor/design/pixel-art-generator.js +143 -0
  146. package/dist/vendor/design/pixel-art-generator.js.map +1 -0
  147. package/dist/vendor/design/pop-art-generator.d.ts +53 -0
  148. package/dist/vendor/design/pop-art-generator.d.ts.map +1 -0
  149. package/dist/vendor/design/pop-art-generator.js +164 -0
  150. package/dist/vendor/design/pop-art-generator.js.map +1 -0
  151. package/dist/vendor/design/risograph-generator.d.ts +53 -0
  152. package/dist/vendor/design/risograph-generator.d.ts.map +1 -0
  153. package/dist/vendor/design/risograph-generator.js +170 -0
  154. package/dist/vendor/design/risograph-generator.js.map +1 -0
  155. package/dist/vendor/design/swiss-style-generator.d.ts +55 -0
  156. package/dist/vendor/design/swiss-style-generator.d.ts.map +1 -0
  157. package/dist/vendor/design/swiss-style-generator.js +147 -0
  158. package/dist/vendor/design/swiss-style-generator.js.map +1 -0
  159. package/dist/vendor/design/vaporwave-generator.d.ts +51 -0
  160. package/dist/vendor/design/vaporwave-generator.d.ts.map +1 -0
  161. package/dist/vendor/design/vaporwave-generator.js +139 -0
  162. package/dist/vendor/design/vaporwave-generator.js.map +1 -0
  163. package/manifest.json +29 -1
  164. package/package.json +13 -5
@@ -9,7 +9,7 @@
9
9
  */
10
10
  import { COMPACT_DESCRIPTIONS } from './compact-descriptions.js';
11
11
  import { MINIMAL_DESCRIPTIONS } from './minimal-descriptions.js';
12
- import { EffectTypeSchema, SimpleAnimationTypeSchema, GEOMETRY_OPERATIONS, } from '../types/schemas.js';
12
+ import { EffectTypeSchema, SimpleAnimationTypeSchema, GEOMETRY_OPERATIONS, THREAD_STITCHES, } from '../types/schemas.js';
13
13
  /**
14
14
  * Vocabulary lists derived from Zod schemas so tool descriptions stay in
15
15
  * sync automatically. The description-schema-parity test
@@ -26,6 +26,11 @@ export const AI_AGENT_GUIDE = `⚠️ You are connected to PinePaper Studio via
26
26
  NEVER create standalone HTML pages, React components, or web apps as a substitute. Do NOT use frontend design skills or CSS-only animations instead of PinePaper.
27
27
  PinePaper tools ARE the implementation — they generate Paper.js/JavaScript code that runs on the canvas and produce Canvas 2D, SVG, MP4, GIF, PNG exports.
28
28
  If a tool fails, report the error — do NOT fall back to generating HTML pages.
29
+
30
+ THE DOORS THAT ARE OPEN, because four lines of prohibition above this one will otherwise read as a ban on the capability rather than on the substitution:
31
+ - HTML IS a supported output. \`pinepaper_export_widget_html\` exports the scene as a self-contained HTML page, and \`pinepaper_export_widget\` as an embeddable widget. Asked for a web page or an embed, build the scene with tools and export it — that is the intended path, not a workaround.
32
+ - CODE IS a supported input. \`pinepaper_execute_custom_code\` runs Paper.js against the live canvas, and \`pinepaper_execute_generator\` runs a named generator. Reach for them when no tool fits; they are part of the surface, not an escape from it.
33
+ - The rule above is about SUBSTITUTION — writing a CSS animation instead of calling a tool. It is not a rule against the formats themselves, and a model that reads it as one will refuse work this server can do.
29
34
  The generated code is shown in tool responses and can be copied into PinePaper's Code Console.
30
35
 
31
36
  ⚠️ KNOWN CONFLICT: The "frontend-design" plugin instructs agents to generate HTML/CSS pages for visual content. This conflicts with PinePaper. If you keep generating HTML instead of calling PinePaper tools, the user should disable the frontend-design plugin.
@@ -169,7 +174,7 @@ Measurement: pinepaper_measurement — rulers, grid, snap-to-grid, get item dime
169
174
  ─── CANVAS & EXPORT ───
170
175
 
171
176
  Presets: instagram (1080x1080), youtube (1920x1080), tiktok (1080x1920), twitter (1200x675)
172
- Export: pinepaper_agent_export (SVG/PNG/GIF/MP4/WebM/PDF), pinepaper_export_svg
177
+ Export: pinepaper_agent_export (SVG/PNG/GIF/MP4/WebM/PDF, plus WAV for the soundtrack alone), pinepaper_export_svg
173
178
  Widget: pinepaper_export_widget (pp:PinePaper ontology JSON), pinepaper_export_widget_html (self-contained HTML with tree-shaken runtime)`;
174
179
  /*
175
180
  * The AI_AGENT_GUIDE constant above is included as a comment in verbose/compact modes
@@ -372,6 +377,28 @@ ITEM TYPES:
372
377
  - diamond: 4-sided diamond/rhombus (properties: radius, color)
373
378
  - arrow: Right-pointing block arrow (properties: width, height, color)
374
379
  - heart: Heart shape (properties: color)
380
+ - disk: Filled disk — the region inside a circle (properties: radius, color)
381
+ - circle-outline: The circle CURVE — stroked, no interior (properties: radius,
382
+ strokeColor, strokeWidth)
383
+ - arrow-right: Alias for arrow — the same definition under the name the shape
384
+ grids use. Identical output; both are listed because both are accepted.
385
+
386
+ SPEECH AND ANNOTATION SHAPES — the engine draws these; use one instead of
387
+ hand-building an outline from a path. All take width, height and color, and the
388
+ descriptions below are the engine's own. The six with a tail also take
389
+ tailDirection (bottom-left | bottom-right | bottom | top-left | top-right | top
390
+ | left | right) and tailSize (0-1, default 0.35):
391
+ - speech-bubble: Classic round speech bubble with triangular tail.
392
+ - speech-bubble-square: Rectangular speech bubble with sharp corners and
393
+ pointed tail.
394
+ - speech-bubble-pointed: Tooltip-style bubble with arrow pointer (tail defaults
395
+ to "bottom").
396
+ - thought-bubble: Cloud-like thought bubble with trailing dots.
397
+ - comment-box: Modern chat message bubble with high corner radius.
398
+ - callout-box: Annotation callout with narrow arrow (tail defaults to "left").
399
+ - double-bubble: Two overlapping bubbles for conversation (properties:
400
+ secondaryColor).
401
+ - quote-bubble: Stylized bubble with quotation marks.
375
402
 
376
403
  PATH FOR CUSTOM SHAPES:
377
404
  Use itemType: "path" with either:
@@ -427,7 +454,7 @@ For glossy 3D spheres, use pinepaper_create_glossy_sphere instead. For diagonal
427
454
  properties: {
428
455
  itemType: {
429
456
  type: 'string',
430
- enum: ['text', 'circle', 'star', 'rectangle', 'triangle', 'polygon', 'ellipse', 'path', 'line', 'arc', 'pentagon', 'hexagon', 'diamond', 'arrow', 'heart', 'shader', 'field'],
457
+ enum: ['text', 'circle', 'star', 'rectangle', 'triangle', 'polygon', 'ellipse', 'path', 'line', 'arc', 'pentagon', 'hexagon', 'diamond', 'arrow', 'heart', 'disk', 'circle-outline', 'arrow-right', 'speech-bubble', 'speech-bubble-square', 'speech-bubble-pointed', 'thought-bubble', 'double-bubble', 'quote-bubble', 'comment-box', 'callout-box', 'shader', 'field'],
431
458
  description: "Type of item to create. 'shader' and 'field' are RENDER-TIME SURFACES drawn per pixel by the cloud renderer — locally they stand in as a flat plate. See the shader/field properties below.",
432
459
  },
433
460
  position: {
@@ -460,7 +487,7 @@ Without these every item is on screen for the whole render, which is what makes
460
487
  a long piece read as one crowded frame instead of a sequence of shots.
461
488
 
462
489
  SHADER (itemType 'shader') — a lit surface, drawn per pixel:
463
- - shader: 'water' (open sea, lit swell) | 'liquid_metal' (chrome) | 'heatmap' (thermal halo) | 'gem_smoke' (smoke) | 'electric_arc' (branching discharge, stepped flicker) | 'vortex' (hollow bright RING, sheared by differential rotation) | 'rain_veil' | 'caustics'
490
+ - shader: 'water' (open sea, lit swell) | 'liquid_metal' (chrome) | 'heatmap' (thermal halo) | 'gem_smoke' (smoke) | 'electric_arc' (branching discharge, stepped flicker) | 'vortex' (hollow bright RING, sheared by differential rotation) | 'rain_veil' | 'caustics' | 'ink_bleed'
464
491
  - width, height: the quad it fills
465
492
  - shaderIntensity: 0..2, the effect's amplitude (water: chop)
466
493
  - shaderPalette: 0 day | 1 low sun | 2 night
@@ -1448,6 +1475,7 @@ EXAMPLES:
1448
1475
  y: { type: 'number', description: 'Canvas origin y (default: canvas center)' },
1449
1476
  flipY: { type: 'boolean', description: 'Math y-up → screen y-down (default true)' },
1450
1477
  style: { type: 'object', description: 'Path style forwarded to create (strokeColor, strokeWidth, fillColor, …)' },
1478
+ solveOde: { type: 'object', description: "Integrate an ODE instead of plotting a closed form, and return the SOLUTION rather than drawing it: { equations, initialState, tEnd?, dt?, method? ('rk4' default, or 'euler' — faster and visibly wrong on anything stiff) }. The trajectory comes back as data, to inspect, feed to a path, or drive keyframes with." },
1451
1479
  warp: {
1452
1480
  anyOf: [{ type: 'object' }, { type: 'array', items: { type: 'object' } }],
1453
1481
  description: 'Chained parametric warp(s): { dx, dy } expressions of x, y, t — one object or an array',
@@ -1900,7 +1928,11 @@ EXAMPLE: { action: 'apply_style', itemId: 'title_1', styleKey: 'arcade', fontFam
1900
1928
  inputSchema: {
1901
1929
  type: 'object',
1902
1930
  properties: {
1903
- action: { type: 'string', enum: ['apply_style', 'set_font_axes', 'list_styles'], description: 'Text style operation' },
1931
+ action: { type: 'string', enum: ['apply_style', 'set_font_axes', 'list_styles', 'cursive', 'wrap', 'unwrap', 'to_collage'], description: "Text style operation. 'cursive' draws text as STROKED handwriting — a path, not a glyph, so draw-on animation and outline_stroke both apply. 'wrap'/'unwrap' break a text item to a width, reversibly. 'to_collage' converts an EXISTING text item into a letter collage in place." },
1932
+ text: { type: 'string', description: 'cursive: the words to write. to_collage: the text to build from, if it differs from the item.' },
1933
+ maxWidth: { type: 'number', description: 'wrap: the width to break at, in canvas units. unwrap restores the original, so this is not destructive.' },
1934
+ cursiveOptions: { type: 'object', description: 'cursive: { x?, y?, scale?, strokeColor?, strokeWidth? }.' },
1935
+ collageOptions: { type: 'object', description: 'to_collage: style, palette and the rest — same vocabulary as pinepaper_create_letter_collage.' },
1904
1936
  itemId: { type: 'string', description: 'Text item id — apply_style / set_font_axes.' },
1905
1937
  styleKey: { type: 'string', description: 'apply_style: style name from list_styles.' },
1906
1938
  palette: {
@@ -1970,9 +2002,9 @@ EXAMPLE — a stitched leaf:
1970
2002
  type: 'object',
1971
2003
  properties: {
1972
2004
  action: { type: 'string', enum: ['list_media', 'resolve', 'list_stitches', 'apply_thread', 'apply_hatch', 'list_flow_fields', 'list_hatch_options'], description: 'Medium operation' },
1973
- medium: { type: 'string', description: 'resolve: vector | thread | ink | cutPaper | charcoal | oil | encaustic' },
2005
+ medium: { type: 'string', description: 'resolve: vector | thread | ink | watercolor | hatch | cutPaper | charcoal | oil | encaustic. Six are native (vector, thread, ink, watercolor, hatch, cutPaper), two stylised, one absent — call list_media for the live table rather than trusting this line.' },
1974
2006
  itemId: { type: 'string', description: 'apply_thread / apply_hatch: a closed path, compound path, or a group of them.' },
1975
- stitch: { type: 'string', enum: ['longAndShort', 'satin', 'seed', 'stem'], description: 'apply_thread: default longAndShort.' },
2007
+ stitch: { type: 'string', enum: [...THREAD_STITCHES], description: "apply_thread: default longAndShort. These six are the stitches the engine publishes; call 'list_stitches' for each one's description and its own parameters. An unknown name is refused rather than quietly stitched as a default fill." },
1976
2008
  field: { type: 'object', description: "apply_thread: { kind: 'radial'|'spine'|'constant', cx, cy, angle, spine[], across } — default radial from the shape centre." },
1977
2009
  stitchLen: { type: 'number', description: 'apply_thread: nominal stitch length px (default 18).' },
1978
2010
  rowGap: { type: 'number', description: 'apply_thread: row spacing px; defaults from thread width so rows abut.' },
@@ -1982,6 +2014,12 @@ EXAMPLE — a stitched leaf:
1982
2014
  color: { type: 'string', description: "apply_thread: thread colour (defaults to the item's fill)." },
1983
2015
  seed: { type: 'number', description: 'apply_thread: PRNG seed (default 1) — same seed stitches the same way.' },
1984
2016
  count: { type: 'number', description: 'apply_thread: stitch count for seed/satin.' },
2017
+ slant: { type: 'number', description: 'apply_thread: stitch slant in DEGREES — satin (default 0) and stem (default 22).' },
2018
+ overlap: { type: 'number', description: 'apply_thread: how far consecutive stem stitches overlap (default 0.4) — what makes a stem outline read as rope.' },
2019
+ stagger: { type: 'number', description: 'apply_thread: offsets alternate longAndShort rows (default 0.5) — stops stitch ends lining up into seams.' },
2020
+ gapLen: { type: 'number', description: 'apply_thread: gap between runningSeam stitches, px (default 4) — with stitchLen, the dash rhythm of a machine seam.' },
2021
+ gridSize: { type: 'number', description: 'apply_thread: crossStitch grid pitch, px (default 12) — the size of each X, and so the resolution of the fill.' },
2022
+ roughness: { type: 'number', description: 'apply_thread: hand wobble. Jitters every point of every stitch after the engine lays them, so the fill reads as sewn rather than plotted. 0 is machine-exact; reproducible for a given seed. Applied by the tool, not the engine.' },
1985
2023
  distance: { type: 'number', description: 'apply_hatch: line spacing px (default 6). THE VALUE CONTROL — 6 vs 3 is light vs dark.' },
1986
2024
  angle: { type: 'number', description: 'apply_hatch: ruling angle in DEGREES (default 45).' },
1987
2025
  gradient: { type: 'number', description: 'apply_hatch: 0..1 spacing growth per line (default 0, even) — a shaded ramp rather than a flat tone.' },
@@ -2214,6 +2252,231 @@ RECIPE — a playable maze: create_tilemap with wall fills → batch_create colo
2214
2252
  required: ['action'],
2215
2253
  },
2216
2254
  },
2255
+ {
2256
+ name: 'pinepaper_design_system',
2257
+ description: `The design vocabulary, as data you can read — and eighteen styles that can build a scene.
2258
+
2259
+ TWO DIFFERENT THINGS, kept apart on purpose:
2260
+
2261
+ A DESIGN SYSTEM is a real, licensed, versioned product — Material Design 3, IBM Carbon, Shopify Polaris, Fluent 2, Adobe Spectrum, GitHub Primer, USWDS, GOV.UK, Shadcn/Radix, SLDS 2, Tailwind, Chakra and more. Its tokens are facts with a licence attached, in the W3C DTCG format ($value, $type, $description). Twenty-one of them are here. Where a motion curve had to be filled in because the published system does not specify one, the token is marked "pinepaper:authored" — so you can tell a vendor's curve from ours instead of treating both as equally official.
2262
+
2263
+ An AESTHETIC STYLE is a look: Bauhaus, Art Deco, Swiss typographic, De Stijl, vaporwave, risograph, brutalist, Memphis, claymorphism, op art, pixel art. No licence, no vendor. Thirty-one are named; eighteen can COMPOSE a scene rather than only be described, and 'list_styles' says which.
2264
+
2265
+ - list_systems / get_system: the licensed systems and their tokens, filterable by token type.
2266
+ - list_easings: every motion curve across every system as a named easing, with its licence and whether it was authored here. Use these to make a motion match a system it has to sit beside.
2267
+ - list_styles / compose: build a titled scene in a style. Layout is decided in this server by a pure function, so 'compose' with draw:false returns the scene and the ops as DATA and draws nothing — read it before committing, the same way pinepaper_story's distill works.
2268
+
2269
+ A partial composition reports as a failure with the elements that did not create, rather than as a success with half a poster.`,
2270
+ inputSchema: {
2271
+ type: 'object',
2272
+ properties: {
2273
+ action: { type: 'string', enum: ['list_systems', 'get_system', 'list_easings', 'list_styles', 'compose'], description: 'Which design-system operation.' },
2274
+ systemId: { type: 'string', description: "get_system: e.g. 'material_3', 'ibm_carbon', 'uswds'. Call list_systems for ids." },
2275
+ tokenType: { type: 'string', enum: ['dimension', 'duration', 'cubicBezier', 'color', 'fontFamily', 'fontWeight', 'number', 'shadow', 'grid'], description: 'get_system: return only tokens of this DTCG type.' },
2276
+ style: { type: 'string', description: "compose: the aesthetic style, e.g. 'bauhaus_geometric'. Call list_styles for the composable ones." },
2277
+ title: { type: 'string', description: 'compose: the headline most styles build around.' },
2278
+ subtitle: { type: 'string', description: 'compose: the supporting line.' },
2279
+ body: { type: 'string', description: 'compose: body copy, where the style has room for it.' },
2280
+ width: { type: 'number', description: 'compose: canvas width (the style picks its own default otherwise).' },
2281
+ height: { type: 'number', description: 'compose: canvas height.' },
2282
+ variant: { type: 'string', description: "compose: a style-specific variant, e.g. art deco's 'emerald'." },
2283
+ draw: { type: 'boolean', description: 'compose: draw it (default true). false returns the scene and ops as data and draws nothing.' },
2284
+ authoredOnly: { type: 'boolean', description: 'list_easings: true for only the curves authored here, false for only what vendors publish.' },
2285
+ },
2286
+ required: ['action'],
2287
+ },
2288
+ },
2289
+ {
2290
+ name: 'pinepaper_stick',
2291
+ description: `The vendored stick-figure kit — a rigged figure posed, walking, travelling, holding a prop, with a garment and hair, plus the set it stands on and among.
2292
+
2293
+ A different construction from pinepaper_character, which places a figure from the DESIGN GRAPH by concept ("pp:Pigeon"). This is the stick kit specifically, and its options — pose, walk, travel, prop, garment, trouser, hair, expression and a timeline of expressions — were discoverable only by reading the engine's source.
2294
+
2295
+ action 'figure' builds the figure. action 'set' builds the floor, the wall and the objects around it, so a figure has somewhere to be.
2296
+
2297
+ The geometry is vendored from mcp-cloud, which makes this a three-repo artifact: a change to the kit lands there first and is re-vendored into the engine.`,
2298
+ inputSchema: {
2299
+ type: 'object',
2300
+ properties: {
2301
+ action: { type: 'string', enum: ['figure', 'set'], description: "'figure' or 'set'." },
2302
+ id: { type: 'string', description: 'Prefix for the created items.' },
2303
+ kind: { type: 'string', description: 'figure: which figure. set: which piece of the kit.' },
2304
+ at: { type: 'object', description: '{ x?, y? } — where it goes.' },
2305
+ scale: { type: 'number', description: 'Size multiplier.' },
2306
+ facing: { type: 'string', enum: ['left', 'right'], description: 'Which way the figure looks.' },
2307
+ pose: { type: 'string', description: "figure: a named pose from the kit's POSES." },
2308
+ poseAt: { type: 'number', description: 'figure: seconds at which that pose is struck.' },
2309
+ expression: { type: 'string', description: 'figure: one facial expression.' },
2310
+ expressions: { type: 'array', items: { type: 'object' }, description: 'figure: [{ at, name }] — expressions over time.' },
2311
+ walk: { anyOf: [{ type: 'boolean' }, { type: 'object' }], description: 'figure: a walk cycle.' },
2312
+ travel: { type: 'object', description: 'figure: move across the scene while walking, rather than walking in place.' },
2313
+ prop: { type: 'string', description: 'figure: something held in hand.' },
2314
+ propSide: { type: 'string', enum: ['left', 'right'], description: 'figure: which hand.' },
2315
+ garment: { type: 'string', description: 'figure: clothing.' },
2316
+ trouser: { type: 'string', description: 'figure: legwear.' },
2317
+ hair: { type: 'string', description: 'figure: hair style.' },
2318
+ withHair: { type: 'boolean', description: 'figure: draw hair at all.' },
2319
+ groundY: { type: 'number', description: 'The y the figure stands on.' },
2320
+ surfaceY: { type: 'number', description: 'The y a set object sits on.' },
2321
+ durationSeconds: { type: 'number', description: 'figure: length of the performance.' },
2322
+ object: { type: 'object', description: 'set: the object spec.' },
2323
+ items: { type: 'array', items: { type: 'object' }, description: 'set: several pieces at once.' },
2324
+ floor: { anyOf: [{ type: 'boolean' }, { type: 'object' }], description: 'set: draw a floor.' },
2325
+ wall: { anyOf: [{ type: 'boolean' }, { type: 'object' }], description: 'set: draw a wall.' },
2326
+ },
2327
+ required: ['action'],
2328
+ },
2329
+ },
2330
+ {
2331
+ name: 'pinepaper_story',
2332
+ description: `A piece of prose becomes a scene.
2333
+
2334
+ - distill: reduce an article to its story beats and draw NOTHING. This is the half worth having on its own — a caller can read what the distiller made of the text, edit the beats, and only then assemble. Discovering the reading by looking at a finished scene is the expensive way round.
2335
+ - from_text: distill and assemble in one call, when the reading does not need checking.
2336
+ - apply_spec: assemble a spec you already have, usually a distilled one you edited.
2337
+ - plan_book: lay images out as pages.`,
2338
+ inputSchema: {
2339
+ type: 'object',
2340
+ properties: {
2341
+ action: { type: 'string', enum: ['distill', 'from_text', 'apply_spec', 'plan_book'], description: 'Which story operation.' },
2342
+ text: { type: 'string', description: 'distill / from_text: the prose.' },
2343
+ spec: { type: 'object', description: 'apply_spec: the story spec, usually a distilled one you edited.' },
2344
+ images: { type: 'array', items: { anyOf: [{ type: 'string' }, { type: 'object' }] }, description: 'plan_book: images as ids or specs.' },
2345
+ options: { type: 'object', description: 'Assembly or layout options.' },
2346
+ },
2347
+ required: ['action'],
2348
+ },
2349
+ },
2350
+ {
2351
+ name: 'pinepaper_interchange',
2352
+ description: `The formats other tools read and write — Lottie, dotLottie, GLB, BVH, PNG sequence.
2353
+
2354
+ pinepaper_agent_export covers the PLATFORM formats: png, svg, mp4, webm, gif, pdf, sized for Instagram or YouTube. These are the interchange ones — a Lottie an app plays, a GLB a 3D tool opens, a BVH a rig imports, a PNG sequence an editor ingests. Import is here too, because a format you can only write is half a bridge.
2355
+
2356
+ Three of these refuse quietly in the engine: exportGLB with no perspective objects and exportBVH with no rigging system both warn to the console and return nothing, and the production build strips the console. Over MCP there is no console to read, so the precondition is checked BEFORE the call and named — "no perspective objects to export — create one with createObject3D first" rather than an empty result and a guess.`,
2357
+ inputSchema: {
2358
+ type: 'object',
2359
+ properties: {
2360
+ action: { type: 'string', enum: ['export_lottie', 'export_dotlottie', 'import_lottie', 'export_glb', 'export_bvh', 'export_png_sequence'], description: 'Which interchange operation.' },
2361
+ options: { type: 'object', description: 'Format options passed through to the exporter: frame range, dimensions, fps, naming.' },
2362
+ data: { anyOf: [{ type: 'string' }, { type: 'object' }], description: 'import_lottie: the Lottie JSON — object, JSON string, or URL.' },
2363
+ skeletonId: { type: 'string', description: "export_bvh: which skeleton to write. A BVH is one skeleton's motion." },
2364
+ },
2365
+ required: ['action'],
2366
+ },
2367
+ },
2368
+ {
2369
+ name: 'pinepaper_sound',
2370
+ description: `Synthesis — and the drawing that is the same object.
2371
+
2372
+ The engine carries a full audio graph, and none of it was reachable. pinepaper_audio_beats ANALYSES audio that already exists; this MAKES audio.
2373
+
2374
+ - Catalogues: list_instruments, list_percussion, list_sfx. Call one before naming a drum or effect — the names are the engine's.
2375
+ - Play: play_tone ('A4'), play_chord (root + kind: major, minor, maj7, dim…), play_percussion, play_sfx (beep, pop, wind, whoosh, zap), play_spec, play_from_text.
2376
+ - Read without playing: chord_frequencies (the Hz a chord resolves to), from_text (a plain-language description — "a soft warm bell on A4" — resolved to a spec you can inspect or edit before committing), timbre_from_path.
2377
+
2378
+ THE PART WORTH KNOWING: sound and drawing are the same object here, both ways.
2379
+ - create draws a sound AS a waveform path on the canvas. Edit the path and the timbre changes with it — it is not a picture of the sound, it is the sound.
2380
+ - timbre_from_path reads ANY drawn path back as harmonic content. A curve someone drew by hand becomes the partials of a note.
2381
+
2382
+ A path with no length cannot be a timbre, and the engine answers a flat single-partial timbre for anything that is not one — a real answer and a refusal look identical — so the item is checked before the call and a non-path is refused by name.
2383
+
2384
+ THE CATALOGUE IS A STARTING SET, NOT THE LIMIT. Six instruments, five drums and six effects are what the engine ships with; that is not a claim about how many instruments exist. define_instrument, define_percussion and define_sfx register a new one at runtime — a rhodes, a koto, a taiko, a specific synth patch — and it is first-class from then on: it appears in the list_* catalogues and plays by name like any built-in.
2385
+ - A melodic instrument needs a partial table, because one with no partials renders silence. Percussion and SFX may be pure noise instead, because a hat and a whoosh are.
2386
+ - For a drum, pass pitch: { from, tau }. The glide is what makes a kick a kick; without it you get a beep.
2387
+ - Names are normalised to [a-z][a-z0-9_-]. Register 'Rhodes' and the response returns canonicalName 'rhodes' — play THAT.
2388
+
2389
+ render_soundtrack mixes every placed sound to a WAV and writes it to a file. It runs offline, with no Web Audio and no playback, so it works in a headless studio. An empty scene is refused by name rather than producing a silent file, and any sound that could not be mixed is reported as dropped — a soundtrack missing a track otherwise looks exactly like a complete one.`,
2390
+ inputSchema: {
2391
+ type: 'object',
2392
+ properties: {
2393
+ action: { type: 'string', enum: ['list_instruments', 'list_percussion', 'list_sfx', 'play_tone', 'play_chord', 'chord_frequencies', 'play_percussion', 'play_sfx', 'play_spec', 'from_text', 'play_from_text', 'create', 'timbre_from_path', 'set_placement', 'remove', 'stop_all', 'define_instrument', 'define_percussion', 'define_sfx', 'render_soundtrack'], description: 'Which sound operation.' },
2394
+ partials: { type: 'array', items: { type: 'object' }, description: 'define_*: [{ h, amp }] — the harmonic table that IS the timbre. Required for define_instrument.' },
2395
+ envelope: { type: 'object', description: 'define_*: { attack, decay, sustain, release } — seconds, except sustain which is a level 0-1.' },
2396
+ gain: { type: 'number', description: 'define_*: output level 0-1 (0.8 instrument, 0.85 percussion, 0.65 sfx).' },
2397
+ aliases: { type: 'array', items: { type: 'string' }, description: "define_*: extra names for the same sound. An alias never shadows a real catalogue entry." },
2398
+ noise: { type: 'number', description: 'define_percussion / define_sfx: noise content 0-1. A hat and a clap are pure noise, so these kinds take partials OR noise.' },
2399
+ noiseFreq: { type: 'number', description: 'define_percussion / define_sfx: noise band centre in Hz.' },
2400
+ hz: { type: 'number', description: 'define_percussion / define_sfx: base frequency in Hz.' },
2401
+ pitch: { type: 'object', description: 'define_percussion / define_sfx: { from, tau } pitch glide. The glide IS the drum — omit it and a kick comes out a beep.' },
2402
+ sampleRate: { type: 'number', description: 'render_soundtrack: samples per second (default 48000).' },
2403
+ bitDepth: { type: 'number', enum: [16, 32], description: 'render_soundtrack: 16 (default) or 32-bit float.' },
2404
+ note: { type: 'string', description: "play_tone: scientific pitch, e.g. 'A4'." },
2405
+ root: { type: 'string', description: "play_chord / chord_frequencies: root note, e.g. 'C4'." },
2406
+ chord: { type: 'string', description: "play_chord / chord_frequencies: chord kind (default 'major')." },
2407
+ name: { type: 'string', description: 'play_percussion / play_sfx: the named drum or effect — list them first.' },
2408
+ text: { type: 'string', description: "from_text / play_from_text: a plain-language description. 'from_text' resolves WITHOUT playing." },
2409
+ spec: { type: 'object', description: 'play_spec / create: the sound spec, usually one from from_text or a catalogue.' },
2410
+ options: { type: 'object', description: 'Playback options for the play_* actions.' },
2411
+ itemId: { type: 'string', description: 'timbre_from_path: the path whose shape becomes the timbre. set_placement / remove: the sound item.' },
2412
+ samples: { type: 'number', description: 'timbre_from_path: points sampled along the path (default 256).' },
2413
+ placement: { type: 'object', description: 'set_placement: where and when the sound sits on the timeline.' },
2414
+ visual: { type: 'object', description: 'create: { width?, height?, position?, color? } for the waveform path.' },
2415
+ },
2416
+ required: ['action'],
2417
+ },
2418
+ },
2419
+ {
2420
+ name: 'pinepaper_motion',
2421
+ description: `The generators' Animation knob, pointed at anything — the same motion engine every generator's own animation runs on, reachable for any group or list of items.
2422
+
2423
+ Two kinds, and the second is the one nothing else here can do:
2424
+ - GROUP motions (drift, sway, rotate, pulse, wave, bounce) move the target as one.
2425
+ - FIELD motions (ripple, breathe, undulate) sweep a crest THROUGH the children from an origin, with a chosen waveform — each element moves at its own moment in the cycle. That is a wave passing through a crowd, not a crowd moving together.
2426
+
2427
+ Call action 'list' first. It returns the engine's own catalogue — every motion, waveform and origin it will accept — so the names come from the engine rather than from an enum here that can drift away from it.
2428
+
2429
+ Origins apply to field motions: center, the four corners, the four edges, 'random' (every element on its own phase — a twinkle rather than a wave), and 'roam' (hopping between the corners and the centre). Pass a seed to make 'random' reproducible.
2430
+
2431
+ Waveform 'spike' is a narrow bump. It was called 'pulse' until the engine noticed that one word was naming both a waveform and an animation; saved scenes still carry the old spelling and it is still accepted, but 'spike' is the name.
2432
+
2433
+ This is not pinepaper_animate: that applies a loop preset to ONE item. This moves a group, or moves a wave through one.`,
2434
+ inputSchema: {
2435
+ type: 'object',
2436
+ properties: {
2437
+ action: { type: 'string', enum: ['list', 'apply'], description: "'list' the catalogue · 'apply' a motion." },
2438
+ itemId: { type: 'string', description: 'apply: a group or single item.' },
2439
+ itemIds: { type: 'array', items: { type: 'string' }, description: 'apply: several items, wrapped in a new group that hosts the motion. Members keep their own ids.' },
2440
+ motion: { type: 'string', description: "apply: the motion name. Call 'list' for the set the engine accepts." },
2441
+ speed: { type: 'number', description: 'apply: roughly cycles per second.' },
2442
+ intensity: { type: 'number', description: 'apply: amplitude (0.15 is the usual default elsewhere).' },
2443
+ waveform: { type: 'string', enum: ['sine', 'triangle', 'square', 'sawtooth', 'spike'], description: 'apply: the shape of one cycle.' },
2444
+ origin: { type: 'string', enum: ['center', 'topLeft', 'topRight', 'bottomLeft', 'bottomRight', 'left', 'right', 'top', 'bottom', 'random', 'roam'], description: 'apply, field motions only: where the crest sweeps from.' },
2445
+ seed: { type: 'number', description: "apply: makes a 'random' origin reproducible." },
2446
+ },
2447
+ required: ['action'],
2448
+ },
2449
+ },
2450
+ {
2451
+ name: 'pinepaper_path',
2452
+ description: `Destructive path operations — the ones that REPLACE or ADD items rather than restyling them.
2453
+
2454
+ - boolean: unite | subtract | intersect | exclude | divide. CONSUMES its operands and leaves one result, so the input ids stop existing. The first id is the base; the rest apply to it in order.
2455
+ - simplify: fewer segments, same shape. Higher tolerance is fewer points and a looser curve.
2456
+ - outline_stroke: a stroked line becomes a FILLED SHAPE of the same width — the thing you do before exporting to a format with no stroke model, or before a boolean against a line.
2457
+ - toggle_closed: open a closed path or close an open one.
2458
+ - pattern: repeat an item — concentric rings, a radial ring, a grid, or a depth-wise extrude stack.
2459
+ - get_geometry: read the segments back (anchor and handle coordinates).
2460
+ - set_locked / unlock_all: a locked item is also unselectable and undraggable, so it cannot be grabbed by accident.
2461
+
2462
+ These live apart from pinepaper_modify_item on purpose: modify restyles an item and keeps its id, every action here changes what items EXIST. A boolean's operands are gone afterwards, and their relations and keyframes go with them.
2463
+
2464
+ The engine names each refusal — "a boolean needs at least two paths", "that path has no stroke to outline", "simplify needs a path" — and those come back verbatim rather than being reworded here.`,
2465
+ inputSchema: {
2466
+ type: 'object',
2467
+ properties: {
2468
+ action: { type: 'string', enum: ['boolean', 'simplify', 'outline_stroke', 'toggle_closed', 'pattern', 'get_geometry', 'set_locked', 'unlock_all'], description: 'Which path operation.' },
2469
+ itemId: { type: 'string', description: 'The item to operate on — every action except boolean and unlock_all.' },
2470
+ itemIds: { type: 'array', items: { type: 'string' }, description: 'boolean: at least two path ids. The first is the base.' },
2471
+ op: { type: 'string', enum: ['unite', 'subtract', 'intersect', 'exclude', 'divide'], description: 'boolean: which operation (default unite).' },
2472
+ tolerance: { type: 'number', description: 'simplify: how far the new curve may stray, in canvas units (default 2.5). outline_stroke: flattening tolerance.' },
2473
+ kind: { type: 'string', enum: ['concentric', 'radial', 'grid', 'extrude'], description: 'pattern: which repeat (default radial).' },
2474
+ pattern: { type: 'object', description: 'pattern options: { count?, radius?, angle?, phase?, rows?, cols?, gapX?, gapY?, step?, depth?, rotateItems?, shade?, shadeStrength? }. angle and phase are DEGREES. Copies are capped — past a few hundred this is a generator, not a drawing aid.' },
2475
+ locked: { type: 'boolean', description: 'set_locked: true locks, false unlocks (default true).' },
2476
+ },
2477
+ required: ['action'],
2478
+ },
2479
+ },
2217
2480
  {
2218
2481
  name: 'pinepaper_world3d',
2219
2482
  annotations: {
@@ -2239,7 +2502,7 @@ RECIPE — a character walking through a forest: create {spec:'forest'} → impo
2239
2502
  inputSchema: {
2240
2503
  type: 'object',
2241
2504
  properties: {
2242
- action: { type: 'string', enum: ['create', 'describe', 'configure', 'add_actor', 'remove_actor', 'list_actors', 'set_actor_pose', 'set_camera', 'add_object', 'remove_object', 'remove_world'], description: 'World operation' },
2505
+ action: { type: 'string', enum: ['create', 'describe', 'configure', 'add_actor', 'remove_actor', 'list_actors', 'set_actor_pose', 'set_camera', 'add_object', 'remove_object', 'remove_world', 'extrude_path', 'lathe_path', 'list_meshes', 'remove_mesh', 'add_light', 'set_light', 'remove_light', 'list_lights', 'add_material', 'set_material', 'remove_material', 'list_materials', 'import_obj', 'import_gltf', 'list_mesh_clips', 'set_mesh_clip', 'set_nav_target', 'get_nav_target', 'ground_height', 'raycast', 'dolly_camera', 'pan_camera', 'world_to_canvas', 'canvas_to_ground', 'add_body', 'remove_body', 'list_bodies', 'step_physics', 'impulse', 'set_velocity', 'fire_projectile', 'line_of_sight', 'set_mesh_instances'], description: 'World operation. extrude_path/lathe_path turn a canvas path into real geometry; lights are capped at 8; a material is shared by every object referencing it.' },
2243
2506
  spec: {
2244
2507
  anyOf: [{ type: 'string' }, { type: 'object' }],
2245
2508
  description: "create: preset id ('forest'|'snowMountain'|'field'|'jungle') or a full spec object.",
@@ -2257,7 +2520,32 @@ RECIPE — a character walking through a forest: create {spec:'forest'} → impo
2257
2520
  live: { type: 'boolean', description: 'add_actor: re-rasterize as the item animates.' },
2258
2521
  pose: { type: 'object', description: 'set_actor_pose: { x?, z?, angle? }.' },
2259
2522
  camera: { type: 'object', description: "set_camera: { mode: follow|fixed|orbit, target?, radius?, speed?, eye?, lookAt? }." },
2260
- object: { type: 'object', description: 'add_object: { x, z, height?, color?, y? }.' },
2523
+ object: { type: 'object', description: 'add_object: { x, z, height?, color?, y? }. metalness/roughness are accepted and IGNORED here — a plain object is drawn by the prop shader, which declares no such uniform. They render on the mesh path only.' },
2524
+ pathId: { type: 'string', description: 'extrude_path / lathe_path: the canvas path to turn into geometry. The path itself is unchanged.' },
2525
+ mesh: { type: 'object', description: 'extrude_path / lathe_path options: { id?, depth?, unitsPerPixel?, caps?, segments?, arc?, flatness?, x?, y?, z?, rotY?, rotYDegrees?, scale?, doubleSided? }. arc is DEGREES; rotY is RADIANS (fed straight to cos/sin in the shader) — pass rotYDegrees to write degrees. These names are the ones the engine records as the mesh provenance, so a captured meshProvenance.opts spreads straight back in.' },
2526
+ meshId: { type: 'string', description: 'remove_mesh: the mesh id.' },
2527
+ light: { type: 'object', description: 'add_light / set_light: { id?, x?, y?, z?, color?, intensity?, range? }. At most 8 point lights; the ninth is refused by name because the shader array is fixed-size.' },
2528
+ lightId: { type: 'string', description: 'set_light / remove_light: the light id.' },
2529
+ material: { type: 'object', description: 'add_material / set_material: { id?, color?, emissive?, metalness?, roughness?, emissiveIntensity?, clearcoat?, clearcoatRoughness?, sheenColor?, sheenRoughness? }. A shared surface — one edit restyles every object using it. metalness/roughness render on the mesh path only.' },
2530
+ materialId: { type: 'string', description: 'set_material / remove_material: the material id.' },
2531
+ source: { type: 'string', description: 'import_obj: the OBJ file TEXT. import_gltf: a URL or a data: URI of the .glb bytes.' },
2532
+ importOptions: { type: 'object', description: 'import_obj / import_gltf: placement and scale options, passed through.' },
2533
+ clip: { type: 'string', description: 'set_mesh_clip: which imported animation clip a skinned mesh plays.' },
2534
+ crossfade: { type: 'number', description: 'set_mesh_clip: seconds to blend out of the previous clip (blended in LOCAL pose space).' },
2535
+ navTarget: { type: 'string', enum: ['2d', '3d'], description: "set_nav_target: which layer the pointer drives." },
2536
+ point: { type: 'object', description: 'ground_height / canvas_to_ground / world_to_canvas: { x, y, z? }.' },
2537
+ origin: { type: 'array', items: { type: 'number' }, description: 'raycast: ray origin [x, y, z].' },
2538
+ direction: { type: 'array', items: { type: 'number' }, description: 'raycast: ray direction [x, y, z], need not be normalised.' },
2539
+ multiplier: { type: 'number', description: 'dolly_camera: >1 away, <1 closer.' },
2540
+ dx: { type: 'number', description: 'pan_camera: horizontal pan in SCREEN pixels.' },
2541
+ dy: { type: 'number', description: 'pan_camera: vertical pan in SCREEN pixels.' },
2542
+ body: { type: 'object', description: 'add_body / fire_projectile: the body spec. NARROW by design — a sphere or capsule under gravity colliding with the world. No stacking solver, no joints; pinepaper_physics is the 2D world and has those.' },
2543
+ bodyId: { type: 'string', description: 'remove_body / impulse / set_velocity: the body id.' },
2544
+ vector: { type: 'array', items: { type: 'number' }, description: 'impulse / set_velocity: [x, y, z].' },
2545
+ dt: { type: 'number', description: 'step_physics: seconds to advance. Returns contacts made and bodies removed.' },
2546
+ from: { type: 'array', items: { type: 'number' }, description: 'line_of_sight: eye point [x, y, z].' },
2547
+ to: { type: 'array', items: { type: 'number' }, description: 'line_of_sight: target point [x, y, z].' },
2548
+ instances: { type: 'array', items: { type: 'array', items: { type: 'number' } }, description: 'set_mesh_instances: one entry per copy, six numbers — x, y, z, scale, rotY (RADIANS), variant.' },
2261
2549
  objectId: { type: 'string', description: 'remove_object: object id.' },
2262
2550
  },
2263
2551
  required: ['action'],
@@ -2306,6 +2594,10 @@ NOT LIP SYNC. \`say\` opens and closes a mouth for the length of a line. Matchin
2306
2594
  description: 'Where the figure stands.',
2307
2595
  },
2308
2596
  height: { type: 'number', description: 'Drawn height in pixels. Every amplitude in the graph is a fraction, so the performance scales with it.' },
2597
+ position: { type: 'object', description: 'Where the figure stands — a synonym for `at`, because the rest of this surface says position. `at` wins if both are sent.' },
2598
+ scale: { type: 'number', description: 'Size as a multiplier of the 300px default, for callers who think in scale rather than pixels. `height` wins if both are sent.' },
2599
+ palette: { type: 'object', description: "Override the depiction's own colours: part id → colour. Only the parts you name change." },
2600
+ ink: { type: 'string', description: 'Ink colour for the stroked copies that make the figure read as drawn rather than filled. Overrides the style\'s own.' },
2309
2601
  id: { type: 'string', description: 'Prefix for the created items (default: the concept name).' },
2310
2602
  style: { type: 'string', description: 'Style id for the depiction (default: the concept\'s own).' },
2311
2603
  variant: { type: 'string', description: 'Depiction variant (default: "default").' },
@@ -2608,7 +2900,7 @@ GEOMETRIC CONSTRUCTION CONSTRAINTS (Layer 2) — the source item is RE-DERIVED e
2608
2900
 
2609
2901
  PROCEDURAL / DETERMINISTIC BINDING (Expression IR — S10 G1) — bind a property to another property or to a math function of time. Pass params.signal:true to route through the pure, seed/frame-deterministic signal interpreter (replay-stable — same time → identical output); omit it for the per-frame compute fallback.
2610
2902
  - driven_by: source's sourceProperty = target's targetProperty * multiplier + offset (optionally clamped). sourceProperty ∈ x|y|rotation|opacity|scale|strokeWidth|fillColor|strokeColor; targetProperty ∈ x|y|rotation|opacity|scale. For fillColor/strokeColor the (clamped 0..1) driven value interpolates colorFrom→colorTo — so a relation drives COLOR. NEEDS a target (the property source). params: sourceProperty, targetProperty, multiplier, offset, clamp {min,max}, colorFrom, colorTo, signal. e.g. tint a dot by a planet's x: sourceProperty 'fillColor', targetProperty 'x', colorFrom '#0055ff', colorTo '#ff3300', signal true.
2611
- - time_expression: self-relation (targetId=null); the source's params.property is driven by a math expression of t (scene time) and v (params.baseValue). params: property (default 'y'), expression (e.g. 'sin(t*2)*50 + v'), baseValue, signal. With signal:true the expression is parsed into the Expression IR and evaluated as a pure f(t); it falls back to per-frame if it uses random() or an unknown symbol (non-deterministic).
2903
+ - time_expression: self-relation (targetId=null); the source's params.property is driven by a math expression of t (scene time) and v (params.baseValue). params: property (default 'y'), expression (e.g. 'sin(t*2)*50 + v'), baseValue, signal. Both modes parse the SAME grammar — the caret is exponentiation (not XOR), and random() is rejected by name rather than falling back, because a scene that renders differently twice cannot be baked or exported. signal:true additionally evaluates it as a pure f(t) via the Expression IR. Beyond the usual trig/log/pow set: clamp, mod, fract, pingpong, inverseLerp, remap, noise, sec, csc, cot, the six ease in/out quad and cubic curves, and the constants PI, E and TAU.
2612
2904
 
2613
2905
  EVENT-DRIVEN SCENE CHAINS (S11) — build a LONG scene as a graph of timed events, not a keyframe track. First make events with pinepaper_event (action:'create' → eventId). The relations below have an EVENT as their sourceId; firing the event runs the reaction. Chain beats over animation time with on_event_fire_after (timeline:'canvas'), have beats mutate the scene with on_event_add_relation, then kick it off with pinepaper_event action:'pulse'. Because canvas-timed fires are on the animation clock, the whole chain is scrub-, loop-, and replay-stable.
2614
2906
  - on_event_fire_after: sourceId=event, targetId=the NEXT event. When source fires, pulse target after params.delay ms. params.timeline: 'canvas' (animation clock — use for scene chains) | 'wall' (real setTimeout). This is the chaining primitive.
@@ -2650,7 +2942,7 @@ Relations are COMPOSITIONAL - an item can have multiple relations that work toge
2650
2942
  },
2651
2943
  relationType: {
2652
2944
  type: 'string',
2653
- enum: ['orbits', 'follows', 'attached_to', 'maintains_distance', 'points_at', 'mirrors', 'parallax', 'bounds_to', 'anchored_in_world', 'spring_follow', 'repels', 'wiggle', 'animates', 'grows_from', 'staggered_with', 'indicates', 'circumscribes', 'wave_through', 'morphs_to', 'group_morphs_to', 'moves_along_path', 'construction_reveal', 'triggers_animation', 'syncs_with', 'tours', 'synced_to_audio', 'expresses', 'camera_follows', 'camera_animates', 'is_midpoint_of', 'lies_on_line', 'is_centroid_of', 'is_circumcenter_of', 'concentric_with', 'driven_by', 'time_expression', 'on_click_fire', 'on_pointer_enter_fire', 'on_pointer_exit_fire', 'on_key_fire', 'on_event_fire_after', 'on_event_fire_if', 'on_event_add_relation', 'on_event_remove_relation', 'on_event_set_color', 'on_event_set_property', 'on_event_set_property_from_template', 'on_event_set_visibility', 'on_event_set_active', 'on_event_set_data', 'on_event_increment', 'on_event_toggle', 'on_event_store_set', 'on_event_store_increment', 'on_enter_set_color', 'on_enter_set_property', 'on_enter_set_property_from_template', 'on_enter_set_visibility', 'on_enter_set_data', 'on_enter_increment', 'on_enter_toggle', 'on_exit_set_color', 'on_exit_set_property', 'on_exit_set_property_from_template', 'on_exit_set_visibility', 'on_exit_set_data', 'on_exit_increment', 'on_exit_toggle', 'exclusive_group', 'menubar_group', 'restores_from', 'on_top_of', 'below', 'beside', 'inside', 'centered_on', 'aligned_with', 'part_of', 'connects_to', 'attached_to_tail', 'head_points_to'],
2945
+ enum: ['orbits', 'follows', 'attached_to', 'maintains_distance', 'points_at', 'mirrors', 'parallax', 'bounds_to', 'anchored_in_world', 'spring_follow', 'repels', 'attracts', 'wiggle', 'animates', 'grows_from', 'staggered_with', 'indicates', 'circumscribes', 'wave_through', 'morphs_to', 'group_morphs_to', 'moves_along_path', 'construction_reveal', 'triggers_animation', 'syncs_with', 'tours', 'synced_to_audio', 'expresses', 'camera_follows', 'camera_animates', 'is_midpoint_of', 'lies_on_line', 'is_centroid_of', 'is_circumcenter_of', 'concentric_with', 'driven_by', 'time_expression', 'on_click_fire', 'on_pointer_enter_fire', 'on_pointer_exit_fire', 'on_key_fire', 'on_event_fire_after', 'on_event_fire_if', 'on_event_add_relation', 'on_event_remove_relation', 'on_event_set_color', 'on_event_set_property', 'on_event_set_property_from_template', 'on_event_set_visibility', 'on_event_set_active', 'on_event_set_data', 'on_event_increment', 'on_event_toggle', 'on_event_store_set', 'on_event_store_increment', 'on_enter_set_color', 'on_enter_set_property', 'on_enter_set_property_from_template', 'on_enter_set_visibility', 'on_enter_set_data', 'on_enter_increment', 'on_enter_toggle', 'on_exit_set_color', 'on_exit_set_property', 'on_exit_set_property_from_template', 'on_exit_set_visibility', 'on_exit_set_data', 'on_exit_increment', 'on_exit_toggle', 'exclusive_group', 'menubar_group', 'restores_from', 'on_top_of', 'below', 'beside', 'inside', 'centered_on', 'aligned_with', 'part_of', 'connects_to', 'attached_to_tail', 'head_points_to'],
2654
2946
  description: 'Type of relationship',
2655
2947
  },
2656
2948
  params: {
@@ -2658,8 +2950,17 @@ Relations are COMPOSITIONAL - an item can have multiple relations that work toge
2658
2950
  description: 'Relation-specific parameters',
2659
2951
  additionalProperties: true,
2660
2952
  },
2953
+ presetId: {
2954
+ type: 'string',
2955
+ description: "Adopt a community relation PRESET instead of naming a type and params yourself — a tuned motion with its symbols exposed as knobs. List them with pinepaper_query_capabilities { action: 'catalogue', catalogue: 'relation_presets' }. The preset decides the relationType, so relationType is not needed alongside it.",
2956
+ },
2957
+ presetValues: {
2958
+ type: 'object',
2959
+ description: 'presetId: symbol → number. An omitted symbol takes the midpoint of its declared range, so a preset applies sensibly with no values at all.',
2960
+ additionalProperties: true,
2961
+ },
2661
2962
  },
2662
- required: ['sourceId', 'relationType'],
2963
+ required: ['sourceId'],
2663
2964
  },
2664
2965
  },
2665
2966
  {
@@ -2684,7 +2985,7 @@ USE WHEN:
2684
2985
  targetId: { type: 'string', description: 'Target item ID' },
2685
2986
  relationType: {
2686
2987
  type: 'string',
2687
- enum: ['orbits', 'follows', 'attached_to', 'maintains_distance', 'points_at', 'mirrors', 'parallax', 'bounds_to', 'anchored_in_world', 'spring_follow', 'repels', 'wiggle', 'animates', 'grows_from', 'staggered_with', 'indicates', 'circumscribes', 'wave_through', 'morphs_to', 'group_morphs_to', 'moves_along_path', 'construction_reveal', 'triggers_animation', 'syncs_with', 'tours', 'synced_to_audio', 'expresses', 'camera_follows', 'camera_animates', 'is_midpoint_of', 'lies_on_line', 'is_centroid_of', 'is_circumcenter_of', 'concentric_with', 'driven_by', 'time_expression', 'on_click_fire', 'on_pointer_enter_fire', 'on_pointer_exit_fire', 'on_key_fire', 'on_event_fire_after', 'on_event_fire_if', 'on_event_add_relation', 'on_event_remove_relation', 'on_event_set_color', 'on_event_set_property', 'on_event_set_property_from_template', 'on_event_set_visibility', 'on_event_set_active', 'on_event_set_data', 'on_event_increment', 'on_event_toggle', 'on_event_store_set', 'on_event_store_increment', 'on_enter_set_color', 'on_enter_set_property', 'on_enter_set_property_from_template', 'on_enter_set_visibility', 'on_enter_set_data', 'on_enter_increment', 'on_enter_toggle', 'on_exit_set_color', 'on_exit_set_property', 'on_exit_set_property_from_template', 'on_exit_set_visibility', 'on_exit_set_data', 'on_exit_increment', 'on_exit_toggle', 'exclusive_group', 'menubar_group', 'restores_from', 'on_top_of', 'below', 'beside', 'inside', 'centered_on', 'aligned_with', 'part_of', 'connects_to', 'attached_to_tail', 'head_points_to'],
2988
+ enum: ['orbits', 'follows', 'attached_to', 'maintains_distance', 'points_at', 'mirrors', 'parallax', 'bounds_to', 'anchored_in_world', 'spring_follow', 'repels', 'attracts', 'wiggle', 'animates', 'grows_from', 'staggered_with', 'indicates', 'circumscribes', 'wave_through', 'morphs_to', 'group_morphs_to', 'moves_along_path', 'construction_reveal', 'triggers_animation', 'syncs_with', 'tours', 'synced_to_audio', 'expresses', 'camera_follows', 'camera_animates', 'is_midpoint_of', 'lies_on_line', 'is_centroid_of', 'is_circumcenter_of', 'concentric_with', 'driven_by', 'time_expression', 'on_click_fire', 'on_pointer_enter_fire', 'on_pointer_exit_fire', 'on_key_fire', 'on_event_fire_after', 'on_event_fire_if', 'on_event_add_relation', 'on_event_remove_relation', 'on_event_set_color', 'on_event_set_property', 'on_event_set_property_from_template', 'on_event_set_visibility', 'on_event_set_active', 'on_event_set_data', 'on_event_increment', 'on_event_toggle', 'on_event_store_set', 'on_event_store_increment', 'on_enter_set_color', 'on_enter_set_property', 'on_enter_set_property_from_template', 'on_enter_set_visibility', 'on_enter_set_data', 'on_enter_increment', 'on_enter_toggle', 'on_exit_set_color', 'on_exit_set_property', 'on_exit_set_property_from_template', 'on_exit_set_visibility', 'on_exit_set_data', 'on_exit_increment', 'on_exit_toggle', 'exclusive_group', 'menubar_group', 'restores_from', 'on_top_of', 'below', 'beside', 'inside', 'centered_on', 'aligned_with', 'part_of', 'connects_to', 'attached_to_tail', 'head_points_to'],
2688
2989
  description: 'Specific relation type to remove (optional - removes all if not specified)',
2689
2990
  },
2690
2991
  },
@@ -2712,7 +3013,7 @@ USE WHEN:
2712
3013
  itemId: { type: 'string', description: 'Item to query relations for' },
2713
3014
  relationType: {
2714
3015
  type: 'string',
2715
- enum: ['orbits', 'follows', 'attached_to', 'maintains_distance', 'points_at', 'mirrors', 'parallax', 'bounds_to', 'anchored_in_world', 'spring_follow', 'repels', 'wiggle', 'animates', 'grows_from', 'staggered_with', 'indicates', 'circumscribes', 'wave_through', 'morphs_to', 'group_morphs_to', 'moves_along_path', 'construction_reveal', 'triggers_animation', 'syncs_with', 'tours', 'synced_to_audio', 'expresses', 'camera_follows', 'camera_animates', 'is_midpoint_of', 'lies_on_line', 'is_centroid_of', 'is_circumcenter_of', 'concentric_with', 'driven_by', 'time_expression', 'on_click_fire', 'on_pointer_enter_fire', 'on_pointer_exit_fire', 'on_key_fire', 'on_event_fire_after', 'on_event_fire_if', 'on_event_add_relation', 'on_event_remove_relation', 'on_event_set_color', 'on_event_set_property', 'on_event_set_property_from_template', 'on_event_set_visibility', 'on_event_set_active', 'on_event_set_data', 'on_event_increment', 'on_event_toggle', 'on_event_store_set', 'on_event_store_increment', 'on_enter_set_color', 'on_enter_set_property', 'on_enter_set_property_from_template', 'on_enter_set_visibility', 'on_enter_set_data', 'on_enter_increment', 'on_enter_toggle', 'on_exit_set_color', 'on_exit_set_property', 'on_exit_set_property_from_template', 'on_exit_set_visibility', 'on_exit_set_data', 'on_exit_increment', 'on_exit_toggle', 'exclusive_group', 'menubar_group', 'restores_from', 'on_top_of', 'below', 'beside', 'inside', 'centered_on', 'aligned_with', 'part_of', 'connects_to', 'attached_to_tail', 'head_points_to'],
3016
+ enum: ['orbits', 'follows', 'attached_to', 'maintains_distance', 'points_at', 'mirrors', 'parallax', 'bounds_to', 'anchored_in_world', 'spring_follow', 'repels', 'attracts', 'wiggle', 'animates', 'grows_from', 'staggered_with', 'indicates', 'circumscribes', 'wave_through', 'morphs_to', 'group_morphs_to', 'moves_along_path', 'construction_reveal', 'triggers_animation', 'syncs_with', 'tours', 'synced_to_audio', 'expresses', 'camera_follows', 'camera_animates', 'is_midpoint_of', 'lies_on_line', 'is_centroid_of', 'is_circumcenter_of', 'concentric_with', 'driven_by', 'time_expression', 'on_click_fire', 'on_pointer_enter_fire', 'on_pointer_exit_fire', 'on_key_fire', 'on_event_fire_after', 'on_event_fire_if', 'on_event_add_relation', 'on_event_remove_relation', 'on_event_set_color', 'on_event_set_property', 'on_event_set_property_from_template', 'on_event_set_visibility', 'on_event_set_active', 'on_event_set_data', 'on_event_increment', 'on_event_toggle', 'on_event_store_set', 'on_event_store_increment', 'on_enter_set_color', 'on_enter_set_property', 'on_enter_set_property_from_template', 'on_enter_set_visibility', 'on_enter_set_data', 'on_enter_increment', 'on_enter_toggle', 'on_exit_set_color', 'on_exit_set_property', 'on_exit_set_property_from_template', 'on_exit_set_visibility', 'on_exit_set_data', 'on_exit_increment', 'on_exit_toggle', 'exclusive_group', 'menubar_group', 'restores_from', 'on_top_of', 'below', 'beside', 'inside', 'centered_on', 'aligned_with', 'part_of', 'connects_to', 'attached_to_tail', 'head_points_to'],
2716
3017
  description: 'Filter by relation type (optional)',
2717
3018
  },
2718
3019
  direction: {
@@ -3596,7 +3897,7 @@ SUPPORTED ANIMATIONS: bounce, breathe, fade, glow, jelly, path, pulse, rotate, s
3596
3897
  target: { type: 'string', description: 'Name of target item' },
3597
3898
  type: {
3598
3899
  type: 'string',
3599
- enum: ['orbits', 'follows', 'attached_to', 'maintains_distance', 'points_at', 'mirrors', 'parallax', 'bounds_to', 'anchored_in_world', 'spring_follow', 'repels', 'wiggle', 'animates', 'grows_from', 'staggered_with', 'indicates', 'circumscribes', 'wave_through', 'morphs_to', 'group_morphs_to', 'moves_along_path', 'construction_reveal', 'triggers_animation', 'syncs_with', 'tours', 'synced_to_audio', 'expresses', 'camera_follows', 'camera_animates', 'is_midpoint_of', 'lies_on_line', 'is_centroid_of', 'is_circumcenter_of', 'concentric_with', 'driven_by', 'time_expression', 'on_click_fire', 'on_pointer_enter_fire', 'on_pointer_exit_fire', 'on_key_fire', 'on_event_fire_after', 'on_event_fire_if', 'on_event_add_relation', 'on_event_remove_relation', 'on_event_set_color', 'on_event_set_property', 'on_event_set_property_from_template', 'on_event_set_visibility', 'on_event_set_active', 'on_event_set_data', 'on_event_increment', 'on_event_toggle', 'on_event_store_set', 'on_event_store_increment', 'on_enter_set_color', 'on_enter_set_property', 'on_enter_set_property_from_template', 'on_enter_set_visibility', 'on_enter_set_data', 'on_enter_increment', 'on_enter_toggle', 'on_exit_set_color', 'on_exit_set_property', 'on_exit_set_property_from_template', 'on_exit_set_visibility', 'on_exit_set_data', 'on_exit_increment', 'on_exit_toggle', 'exclusive_group', 'menubar_group', 'restores_from', 'on_top_of', 'below', 'beside', 'inside', 'centered_on', 'aligned_with', 'part_of', 'connects_to', 'attached_to_tail', 'head_points_to'],
3900
+ enum: ['orbits', 'follows', 'attached_to', 'maintains_distance', 'points_at', 'mirrors', 'parallax', 'bounds_to', 'anchored_in_world', 'spring_follow', 'repels', 'attracts', 'wiggle', 'animates', 'grows_from', 'staggered_with', 'indicates', 'circumscribes', 'wave_through', 'morphs_to', 'group_morphs_to', 'moves_along_path', 'construction_reveal', 'triggers_animation', 'syncs_with', 'tours', 'synced_to_audio', 'expresses', 'camera_follows', 'camera_animates', 'is_midpoint_of', 'lies_on_line', 'is_centroid_of', 'is_circumcenter_of', 'concentric_with', 'driven_by', 'time_expression', 'on_click_fire', 'on_pointer_enter_fire', 'on_pointer_exit_fire', 'on_key_fire', 'on_event_fire_after', 'on_event_fire_if', 'on_event_add_relation', 'on_event_remove_relation', 'on_event_set_color', 'on_event_set_property', 'on_event_set_property_from_template', 'on_event_set_visibility', 'on_event_set_active', 'on_event_set_data', 'on_event_increment', 'on_event_toggle', 'on_event_store_set', 'on_event_store_increment', 'on_enter_set_color', 'on_enter_set_property', 'on_enter_set_property_from_template', 'on_enter_set_visibility', 'on_enter_set_data', 'on_enter_increment', 'on_enter_toggle', 'on_exit_set_color', 'on_exit_set_property', 'on_exit_set_property_from_template', 'on_exit_set_visibility', 'on_exit_set_data', 'on_exit_increment', 'on_exit_toggle', 'exclusive_group', 'menubar_group', 'restores_from', 'on_top_of', 'below', 'beside', 'inside', 'centered_on', 'aligned_with', 'part_of', 'connects_to', 'attached_to_tail', 'head_points_to'],
3600
3901
  },
3601
3902
  params: {
3602
3903
  type: 'object',
@@ -4242,7 +4543,7 @@ flags as a badly-composed scene, and they do not survive an artboard change.`,
4242
4543
  register: { type: 'string', enum: ['naive', 'playful', 'poster', 'editorial', 'technical'], description: 'Which design language. Resolves gutter, margin, hue budget and type scale together; an explicit craft still overrides it.' },
4243
4544
  level: { type: 'number', description: 'Craft level 1-4 within the register: sketch, competent, refined, art-directed.' },
4244
4545
  medium: { type: 'string', enum: ['vector', 'thread'], description: "What makes the marks. 'thread' stitches every closed path AFTER arranging; photographs and text cannot be stitched and return in medium.skipped with the reason. Other media are refused with their own reason rather than faked." },
4245
- stitch: { type: 'string', enum: ['longAndShort', 'satin', 'seed', 'stem'], description: 'Which stitch when medium is thread (default longAndShort).' },
4546
+ stitch: { type: 'string', enum: [...THREAD_STITCHES], description: 'Which stitch when medium is thread (default longAndShort) — the same six pinepaper_design_medium offers.' },
4246
4547
  stitchBudget: { type: 'number', description: 'Total marks across the composition (default 6000); the stitch scales to fit rather than the fill being cut short.' },
4247
4548
  action: { type: 'string', enum: ['list_patterns', 'list_treatments', 'apply', 'set_treatment'] },
4248
4549
  pattern: { type: 'string', description: 'Pattern key from list_patterns' },
@@ -4827,8 +5128,13 @@ EXAMPLE — Choose effect for a victory card:
4827
5128
  properties: {
4828
5129
  action: {
4829
5130
  type: 'string',
4830
- enum: ['list', 'choose', 'coverage', 'find'],
4831
- description: 'Query mode: list, choose, coverage, or find (default: list)',
5131
+ enum: ['list', 'choose', 'coverage', 'find', 'catalogue', 'studio'],
5132
+ description: "Query mode: list, choose, coverage, find, catalogue, or studio (default: list). 'catalogue' reads a registry the aggregate does not gather. 'studio' reports which engine METHODS this studio has, so you learn what will refuse BEFORE calling it — long-form export needs exportEngine.exportToStore, mesh authoring needs extrudeToMesh, and this is how you find out without trying each.",
5133
+ },
5134
+ catalogue: {
5135
+ type: 'string',
5136
+ enum: ['rig_presets', 'shader_effects', 'stroke_decorations', 'precomps', 'images', 'segment_edit_kinds', 'shatter_orders', 'world_meshes', 'relation_presets'],
5137
+ description: "action 'catalogue': which registry to read. These sit OUTSIDE the aggregate, which already covers styles, effects, deforms, entrances, animations, collages, palettes, masks, maskShapes and cutouts — 'list' reaches all of those.",
4832
5138
  },
4833
5139
  kind: {
4834
5140
  anyOf: [{ type: 'string' }, { type: 'array', items: { type: 'string' } }],
@@ -6447,6 +6753,7 @@ SHADER AURAS (WebGL2, silhouette-clipped — same tool, routed to the aura syste
6447
6753
  - vortex: The bright band is a RING, not a disc — condensation tracks the steepest pressure gradient, so the core is hollow, and differential rotation shears the field rather than spinning one picture round.
6448
6754
  - rain_veil: Drifting sheets of falling rain across the silhouette.
6449
6755
  - caustics: Refracted light patterns, as through moving water.
6756
+ - ink_bleed: Ink wicking outward into the surface, as on absorbent paper. Routable since the engine began publishing every aura name and warming the subsystem before routing; it was defined-but-unreachable before that, which is why it arrived last.
6450
6757
  Aura params: { intensity, radius, palette, mode ('inside'|'outside'|'overlay'), tint, tintStrength, offsetX, offsetY, channels: { a: {speed, curve}, b: {speed, curve} } } — curves: linear, sawtooth, sine, triangle, pulse, ease, bounce, noise.
6451
6758
 
6452
6759
  EXAMPLE — bubbles with magenta interior over a blue rim:
@@ -6459,7 +6766,7 @@ EXAMPLE — gold liquid-metal aura outside a logo:
6459
6766
  itemId: { type: 'string', description: 'Registry ID of the item' },
6460
6767
  effectType: {
6461
6768
  type: 'string',
6462
- enum: ['sparkle', 'blast', 'smoke', 'fire', 'rain', 'snow', 'confetti', 'ripple', 'glow', 'electric', 'bubbles', 'dust', 'fireflies', 'shockwave', 'trail', 'heatmap', 'liquid_metal', 'gem_smoke', 'electric_arc', 'vortex', 'rain_veil', 'caustics'],
6769
+ enum: ['sparkle', 'blast', 'smoke', 'fire', 'rain', 'snow', 'confetti', 'ripple', 'glow', 'electric', 'bubbles', 'dust', 'fireflies', 'shockwave', 'trail', 'heatmap', 'liquid_metal', 'gem_smoke', 'electric_arc', 'vortex', 'rain_veil', 'caustics', 'ink_bleed'],
6463
6770
  description: 'Type of effect',
6464
6771
  },
6465
6772
  params: {
@@ -6940,7 +7247,7 @@ PRIVACY: No screenshots, no tool call arguments, no user content — only struct
6940
7247
  idempotentHint: false,
6941
7248
  openWorldHint: false,
6942
7249
  },
6943
- description: `Start a new agent job for creating animations, videos, graphics, or any visual content. Do NOT generate HTML or React — use PinePaper tools instead.
7250
+ description: `Start a new agent job for creating animations, videos, graphics, or any visual content. Do not hand-write HTML or React INSTEAD OF calling these tools — but if the user wants an HTML page or an embeddable widget, that is pinepaper_export_widget_html and pinepaper_export_widget, and they are the right answer rather than a fallback.
6944
7251
 
6945
7252
  ⚠️ Call ONCE per pipeline. NEVER restart — creates duplicates.
6946
7253
 
@@ -7243,7 +7550,7 @@ EXAMPLE — Animated sky scene with timed reveals:
7243
7550
  targetId: { type: 'string', description: 'For relation: target item ID or $N' },
7244
7551
  relationType: {
7245
7552
  type: 'string',
7246
- enum: ['orbits', 'follows', 'attached_to', 'maintains_distance', 'points_at', 'mirrors', 'parallax', 'bounds_to', 'anchored_in_world', 'spring_follow', 'repels', 'wiggle', 'animates', 'grows_from', 'staggered_with', 'indicates', 'circumscribes', 'wave_through', 'morphs_to', 'group_morphs_to', 'moves_along_path', 'construction_reveal', 'triggers_animation', 'syncs_with', 'tours', 'synced_to_audio', 'expresses', 'camera_follows', 'camera_animates', 'is_midpoint_of', 'lies_on_line', 'is_centroid_of', 'is_circumcenter_of', 'concentric_with', 'driven_by', 'time_expression', 'on_click_fire', 'on_pointer_enter_fire', 'on_pointer_exit_fire', 'on_key_fire', 'on_event_fire_after', 'on_event_fire_if', 'on_event_add_relation', 'on_event_remove_relation', 'on_event_set_color', 'on_event_set_property', 'on_event_set_property_from_template', 'on_event_set_visibility', 'on_event_set_active', 'on_event_set_data', 'on_event_increment', 'on_event_toggle', 'on_event_store_set', 'on_event_store_increment', 'on_enter_set_color', 'on_enter_set_property', 'on_enter_set_property_from_template', 'on_enter_set_visibility', 'on_enter_set_data', 'on_enter_increment', 'on_enter_toggle', 'on_exit_set_color', 'on_exit_set_property', 'on_exit_set_property_from_template', 'on_exit_set_visibility', 'on_exit_set_data', 'on_exit_increment', 'on_exit_toggle', 'exclusive_group', 'menubar_group', 'restores_from', 'on_top_of', 'below', 'beside', 'inside', 'centered_on', 'aligned_with', 'part_of', 'connects_to', 'attached_to_tail', 'head_points_to'],
7553
+ enum: ['orbits', 'follows', 'attached_to', 'maintains_distance', 'points_at', 'mirrors', 'parallax', 'bounds_to', 'anchored_in_world', 'spring_follow', 'repels', 'attracts', 'wiggle', 'animates', 'grows_from', 'staggered_with', 'indicates', 'circumscribes', 'wave_through', 'morphs_to', 'group_morphs_to', 'moves_along_path', 'construction_reveal', 'triggers_animation', 'syncs_with', 'tours', 'synced_to_audio', 'expresses', 'camera_follows', 'camera_animates', 'is_midpoint_of', 'lies_on_line', 'is_centroid_of', 'is_circumcenter_of', 'concentric_with', 'driven_by', 'time_expression', 'on_click_fire', 'on_pointer_enter_fire', 'on_pointer_exit_fire', 'on_key_fire', 'on_event_fire_after', 'on_event_fire_if', 'on_event_add_relation', 'on_event_remove_relation', 'on_event_set_color', 'on_event_set_property', 'on_event_set_property_from_template', 'on_event_set_visibility', 'on_event_set_active', 'on_event_set_data', 'on_event_increment', 'on_event_toggle', 'on_event_store_set', 'on_event_store_increment', 'on_enter_set_color', 'on_enter_set_property', 'on_enter_set_property_from_template', 'on_enter_set_visibility', 'on_enter_set_data', 'on_enter_increment', 'on_enter_toggle', 'on_exit_set_color', 'on_exit_set_property', 'on_exit_set_property_from_template', 'on_exit_set_visibility', 'on_exit_set_data', 'on_exit_increment', 'on_exit_toggle', 'exclusive_group', 'menubar_group', 'restores_from', 'on_top_of', 'below', 'beside', 'inside', 'centered_on', 'aligned_with', 'part_of', 'connects_to', 'attached_to_tail', 'head_points_to'],
7247
7554
  description: 'For relation: type',
7248
7555
  },
7249
7556
  relationOptions: { type: 'object', description: 'For relation: options' },
@@ -7330,8 +7637,17 @@ EXAMPLES:
7330
7637
  },
7331
7638
  format: {
7332
7639
  type: 'string',
7333
- enum: ['svg', 'png', 'gif', 'mp4', 'webm', 'pdf'],
7334
- description: 'Override format (auto-detected if not specified)',
7640
+ enum: ['svg', 'png', 'gif', 'mp4', 'webm', 'pdf', 'wav'],
7641
+ description: "Override format (auto-detected if not specified). 'wav' exports the SOUNDTRACK ON ITS OWN, with no frames rendered — no platform preset resolves to it, so it must be asked for by name, and platform dimensions, framing and quality do not apply.",
7642
+ },
7643
+ sampleRate: {
7644
+ type: 'number',
7645
+ description: 'wav only: samples per second (default 48000). Rejected for any other format.',
7646
+ },
7647
+ bitDepth: {
7648
+ type: 'number',
7649
+ enum: [16, 32],
7650
+ description: 'wav only: 16 (default) or 32-bit float. Rejected for any other format.',
7335
7651
  },
7336
7652
  quality: {
7337
7653
  type: 'string',
@@ -7345,7 +7661,7 @@ EXAMPLES:
7345
7661
  },
7346
7662
  estimateOnly: {
7347
7663
  type: 'boolean',
7348
- description: 'Preflight only — return the estimated file size for these exact settings and render nothing. Modeled for mp4/webm/gif; png/pdf/svg report confidence "none" (no dimension-based model exists for them).',
7664
+ description: 'Preflight only — return the estimated file size for these exact settings and render nothing. Modeled for mp4/webm/gif; png/pdf/svg report confidence "none" (no dimension-based model exists for them); wav reports confidence "exact", because uncompressed PCM size is arithmetic.',
7349
7665
  },
7350
7666
  duration: {
7351
7667
  type: 'number',
@@ -8095,6 +8411,9 @@ ACTIONS:
8095
8411
  required: ['action'],
8096
8412
  },
8097
8413
  },
8414
+ // ---------------------------------------------------------------------------
8415
+ // STITCHCRAFT PROCEDURAL EMBROIDERY & THREAD
8416
+ // ---------------------------------------------------------------------------
8098
8417
  ];
8099
8418
  /**
8100
8419
  * Get a tool definition by name