@pinepaper.studio/mcp-server 1.6.6 → 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.
- package/README.de.md +24 -1
- package/README.es.md +24 -1
- package/README.fr.md +24 -1
- package/README.hi.md +21 -1
- package/README.ja.md +22 -1
- package/README.ko.md +22 -1
- package/README.md +329 -14
- package/README.pt-BR.md +24 -1
- package/README.zh-CN.md +20 -1
- package/assets/edit-whenever.svg +75 -0
- package/dist/agent/export-manager.d.ts.map +1 -1
- package/dist/agent/export-manager.js +2 -2
- package/dist/agent/export-manager.js.map +1 -1
- package/dist/agent/session-manager.d.ts.map +1 -1
- package/dist/agent/session-manager.js.map +1 -1
- package/dist/assets/repositories/fontawesome.d.ts.map +1 -1
- package/dist/assets/repositories/fontawesome.js.map +1 -1
- package/dist/assets/repositories/openclipart.d.ts.map +1 -1
- package/dist/character/data/concepts.json +1 -0
- package/dist/character/data/depictions.json +1 -0
- package/dist/character/data/styles.json +1 -0
- package/dist/character/duration-scale.d.ts +13 -0
- package/dist/character/duration-scale.d.ts.map +1 -0
- package/dist/character/duration-scale.js +13 -0
- package/dist/character/duration-scale.js.map +1 -0
- package/dist/character/path-bbox.d.ts +82 -0
- package/dist/character/path-bbox.d.ts.map +1 -0
- package/dist/character/path-bbox.js +496 -0
- package/dist/character/path-bbox.js.map +1 -0
- package/dist/character/perform.d.ts +149 -0
- package/dist/character/perform.d.ts.map +1 -0
- package/dist/character/perform.js +626 -0
- package/dist/character/perform.js.map +1 -0
- package/dist/character/resolve.d.ts +51 -0
- package/dist/character/resolve.d.ts.map +1 -0
- package/dist/character/resolve.js +239 -0
- package/dist/character/resolve.js.map +1 -0
- package/dist/design/design-systems.d.ts +126 -0
- package/dist/design/design-systems.d.ts.map +1 -0
- package/dist/design/design-systems.js +240 -0
- package/dist/design/design-systems.js.map +1 -0
- package/dist/execution/canvas-state.d.ts +7 -0
- package/dist/execution/canvas-state.d.ts.map +1 -1
- package/dist/execution/canvas-state.js +20 -8
- package/dist/execution/canvas-state.js.map +1 -1
- package/dist/index.d.ts +20 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +12 -5
- package/dist/index.js.map +1 -1
- package/dist/ontology/design-graph.d.ts +9 -0
- package/dist/ontology/design-graph.d.ts.map +1 -1
- package/dist/ontology/design-graph.js +98 -4
- package/dist/ontology/design-graph.js.map +1 -1
- package/dist/ontology/types.d.ts +42 -0
- package/dist/ontology/types.d.ts.map +1 -1
- package/dist/ontology/vocabulary.d.ts.map +1 -1
- package/dist/ontology/vocabulary.js +139 -0
- package/dist/ontology/vocabulary.js.map +1 -1
- package/dist/taxonomy/classifier.js +1 -1
- package/dist/taxonomy/classifier.js.map +1 -1
- package/dist/taxonomy/error-recovery.d.ts.map +1 -1
- package/dist/taxonomy/matcher.d.ts.map +1 -1
- package/dist/taxonomy/matcher.js.map +1 -1
- package/dist/tools/compact-descriptions.d.ts.map +1 -1
- package/dist/tools/compact-descriptions.js +25 -0
- package/dist/tools/compact-descriptions.js.map +1 -1
- package/dist/tools/definitions.d.ts +1 -1
- package/dist/tools/definitions.d.ts.map +1 -1
- package/dist/tools/definitions.js +1236 -45
- package/dist/tools/definitions.js.map +1 -1
- package/dist/tools/engine-methods.d.ts +18 -0
- package/dist/tools/engine-methods.d.ts.map +1 -0
- package/dist/tools/engine-methods.js +239 -0
- package/dist/tools/engine-methods.js.map +1 -0
- package/dist/tools/handlers/character.d.ts +121 -0
- package/dist/tools/handlers/character.d.ts.map +1 -0
- package/dist/tools/handlers/character.js +315 -0
- package/dist/tools/handlers/character.js.map +1 -0
- package/dist/tools/handlers.d.ts +2 -0
- package/dist/tools/handlers.d.ts.map +1 -1
- package/dist/tools/handlers.js +386 -22
- package/dist/tools/handlers.js.map +1 -1
- package/dist/tools/minimal-descriptions.d.ts.map +1 -1
- package/dist/tools/minimal-descriptions.js +24 -4
- package/dist/tools/minimal-descriptions.js.map +1 -1
- package/dist/tools/toolkits.d.ts.map +1 -1
- package/dist/tools/toolkits.js +21 -0
- package/dist/tools/toolkits.js.map +1 -1
- package/dist/types/code-generator.d.ts +141 -2
- package/dist/types/code-generator.d.ts.map +1 -1
- package/dist/types/code-generator.js +2246 -78
- package/dist/types/code-generator.js.map +1 -1
- package/dist/types/schemas.d.ts +9295 -603
- package/dist/types/schemas.d.ts.map +1 -1
- package/dist/types/schemas.js +1134 -61
- package/dist/types/schemas.js.map +1 -1
- package/dist/vendor/design/art-deco-generator.d.ts +52 -0
- package/dist/vendor/design/art-deco-generator.d.ts.map +1 -0
- package/dist/vendor/design/art-deco-generator.js +144 -0
- package/dist/vendor/design/art-deco-generator.js.map +1 -0
- package/dist/vendor/design/art-nouveau-generator.d.ts +46 -0
- package/dist/vendor/design/art-nouveau-generator.d.ts.map +1 -0
- package/dist/vendor/design/art-nouveau-generator.js +134 -0
- package/dist/vendor/design/art-nouveau-generator.js.map +1 -0
- package/dist/vendor/design/bauhaus-generator.d.ts +53 -0
- package/dist/vendor/design/bauhaus-generator.d.ts.map +1 -0
- package/dist/vendor/design/bauhaus-generator.js +126 -0
- package/dist/vendor/design/bauhaus-generator.js.map +1 -0
- package/dist/vendor/design/brutalist-generator.d.ts +53 -0
- package/dist/vendor/design/brutalist-generator.d.ts.map +1 -0
- package/dist/vendor/design/brutalist-generator.js +148 -0
- package/dist/vendor/design/brutalist-generator.js.map +1 -0
- package/dist/vendor/design/claymorphism-generator.d.ts +46 -0
- package/dist/vendor/design/claymorphism-generator.d.ts.map +1 -0
- package/dist/vendor/design/claymorphism-generator.js +135 -0
- package/dist/vendor/design/claymorphism-generator.js.map +1 -0
- package/dist/vendor/design/corporate-memphis-generator.d.ts +47 -0
- package/dist/vendor/design/corporate-memphis-generator.d.ts.map +1 -0
- package/dist/vendor/design/corporate-memphis-generator.js +144 -0
- package/dist/vendor/design/corporate-memphis-generator.js.map +1 -0
- package/dist/vendor/design/cyberpunk-generator.d.ts +52 -0
- package/dist/vendor/design/cyberpunk-generator.d.ts.map +1 -0
- package/dist/vendor/design/cyberpunk-generator.js +162 -0
- package/dist/vendor/design/cyberpunk-generator.js.map +1 -0
- package/dist/vendor/design/de-stijl-generator.d.ts +50 -0
- package/dist/vendor/design/de-stijl-generator.d.ts.map +1 -0
- package/dist/vendor/design/de-stijl-generator.js +154 -0
- package/dist/vendor/design/de-stijl-generator.js.map +1 -0
- package/dist/vendor/design/design-style.d.ts +3 -0
- package/dist/vendor/design/design-style.d.ts.map +1 -0
- package/dist/vendor/design/design-style.js +12 -0
- package/dist/vendor/design/design-style.js.map +1 -0
- package/dist/vendor/design/design-systems-dtcg.d.ts +59 -0
- package/dist/vendor/design/design-systems-dtcg.d.ts.map +1 -0
- package/dist/vendor/design/design-systems-dtcg.js +571 -0
- package/dist/vendor/design/design-systems-dtcg.js.map +1 -0
- package/dist/vendor/design/frutiger-aero-generator.d.ts +46 -0
- package/dist/vendor/design/frutiger-aero-generator.d.ts.map +1 -0
- package/dist/vendor/design/frutiger-aero-generator.js +133 -0
- package/dist/vendor/design/frutiger-aero-generator.js.map +1 -0
- package/dist/vendor/design/memphis-generator.d.ts +56 -0
- package/dist/vendor/design/memphis-generator.d.ts.map +1 -0
- package/dist/vendor/design/memphis-generator.js +158 -0
- package/dist/vendor/design/memphis-generator.js.map +1 -0
- package/dist/vendor/design/mid-century-upa-generator.d.ts +46 -0
- package/dist/vendor/design/mid-century-upa-generator.d.ts.map +1 -0
- package/dist/vendor/design/mid-century-upa-generator.js +137 -0
- package/dist/vendor/design/mid-century-upa-generator.js.map +1 -0
- package/dist/vendor/design/neumorphic-generator.d.ts +53 -0
- package/dist/vendor/design/neumorphic-generator.d.ts.map +1 -0
- package/dist/vendor/design/neumorphic-generator.js +182 -0
- package/dist/vendor/design/neumorphic-generator.js.map +1 -0
- package/dist/vendor/design/op-art-generator.d.ts +45 -0
- package/dist/vendor/design/op-art-generator.d.ts.map +1 -0
- package/dist/vendor/design/op-art-generator.js +116 -0
- package/dist/vendor/design/op-art-generator.js.map +1 -0
- package/dist/vendor/design/pixel-art-generator.d.ts +45 -0
- package/dist/vendor/design/pixel-art-generator.d.ts.map +1 -0
- package/dist/vendor/design/pixel-art-generator.js +143 -0
- package/dist/vendor/design/pixel-art-generator.js.map +1 -0
- package/dist/vendor/design/pop-art-generator.d.ts +53 -0
- package/dist/vendor/design/pop-art-generator.d.ts.map +1 -0
- package/dist/vendor/design/pop-art-generator.js +164 -0
- package/dist/vendor/design/pop-art-generator.js.map +1 -0
- package/dist/vendor/design/risograph-generator.d.ts +53 -0
- package/dist/vendor/design/risograph-generator.d.ts.map +1 -0
- package/dist/vendor/design/risograph-generator.js +170 -0
- package/dist/vendor/design/risograph-generator.js.map +1 -0
- package/dist/vendor/design/swiss-style-generator.d.ts +55 -0
- package/dist/vendor/design/swiss-style-generator.d.ts.map +1 -0
- package/dist/vendor/design/swiss-style-generator.js +147 -0
- package/dist/vendor/design/swiss-style-generator.js.map +1 -0
- package/dist/vendor/design/vaporwave-generator.d.ts +51 -0
- package/dist/vendor/design/vaporwave-generator.d.ts.map +1 -0
- package/dist/vendor/design/vaporwave-generator.js +139 -0
- package/dist/vendor/design/vaporwave-generator.js.map +1 -0
- package/manifest.json +65 -1
- package/package.json +18 -9
|
@@ -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.
|
|
@@ -73,6 +78,10 @@ Choose based on mood:
|
|
|
73
78
|
─── ITEMS ───
|
|
74
79
|
|
|
75
80
|
Shapes: circle, rectangle, star, ellipse, triangle, polygon, line, arc, path, pentagon, hexagon, diamond, arrow, heart
|
|
81
|
+
Surfaces (render-time, evaluated per pixel — not shapes): shader, field
|
|
82
|
+
shader: a lit surface — water, liquid_metal, heatmap, gem_smoke. Set behind:true to put it UNDER the art, which is what scenery wants.
|
|
83
|
+
field: one expression, many marks — xExpr/yExpr/count, re-evaluated every frame against t.
|
|
84
|
+
Both draw fully in a cloud render; locally they stand in as a flat plate.
|
|
76
85
|
Text: text (content, fontSize, fontFamily, color, fontWeight)
|
|
77
86
|
Dynamic text: set contentType on text items → clock (live time), timer (elapsed), countdown (from N seconds), stopwatch (pauseable)
|
|
78
87
|
All items: opacity, shadowColor, shadowBlur, blendMode, strokeColor, strokeWidth
|
|
@@ -98,7 +107,7 @@ Photos / raster images (on-device, no upload):
|
|
|
98
107
|
|
|
99
108
|
─── ANIMATION (all in batch) ───
|
|
100
109
|
|
|
101
|
-
Loop presets (animate): pulse, rotate,
|
|
110
|
+
Loop presets (animate): bounce, breathe, fade, glow, jelly, path, pulse, rotate, scrollDown, scrollLeft, scrollRight, scrollUp, shake, slideLeftRight, slideUpDown, swing, typewriter, wobble
|
|
102
111
|
Keyframe (keyframe_animate): [{time, properties, easing}] → opacity, scale, scaleX, scaleY, x, y, rotation, fillColor, strokeColor, fontSize
|
|
103
112
|
Relations (relation): orbits, follows, attached_to, points_at, mirrors, parallax, wave_through, morphs_to, group_morphs_to, moves_along_path, is_midpoint_of, is_circumcenter_of, construction_reveal (+ geometric constraints & more in batch_execute schema)
|
|
104
113
|
Masks (apply_mask): wipeLeft, wipeRight, wipeUp, wipeDown, iris, irisOut, star, heart, curtainHorizontal, curtainVertical, cinematic, diagonalWipe, revealUp, revealDown
|
|
@@ -165,7 +174,7 @@ Measurement: pinepaper_measurement — rulers, grid, snap-to-grid, get item dime
|
|
|
165
174
|
─── CANVAS & EXPORT ───
|
|
166
175
|
|
|
167
176
|
Presets: instagram (1080x1080), youtube (1920x1080), tiktok (1080x1920), twitter (1200x675)
|
|
168
|
-
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
|
|
169
178
|
Widget: pinepaper_export_widget (pp:PinePaper ontology JSON), pinepaper_export_widget_html (self-contained HTML with tree-shaken runtime)`;
|
|
170
179
|
/*
|
|
171
180
|
* The AI_AGENT_GUIDE constant above is included as a comment in verbose/compact modes
|
|
@@ -368,6 +377,28 @@ ITEM TYPES:
|
|
|
368
377
|
- diamond: 4-sided diamond/rhombus (properties: radius, color)
|
|
369
378
|
- arrow: Right-pointing block arrow (properties: width, height, color)
|
|
370
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.
|
|
371
402
|
|
|
372
403
|
PATH FOR CUSTOM SHAPES:
|
|
373
404
|
Use itemType: "path" with either:
|
|
@@ -397,6 +428,24 @@ Items on the canvas can originate from multiple sources — all become first-cla
|
|
|
397
428
|
- pinepaper_import_asset: Pre-made asset import → becomes a full item
|
|
398
429
|
All imported items participate in the relation and animation systems just like natively created items.
|
|
399
430
|
|
|
431
|
+
RENDER-TIME SURFACES — 'shader' and 'field':
|
|
432
|
+
These are not shapes. A shape is built as an item and drawn from its geometry; a
|
|
433
|
+
surface has no geometry — it is evaluated, per pixel or per sample, as each frame
|
|
434
|
+
is drawn. Use them for things a vector cannot be: an open sea, a drifting field
|
|
435
|
+
of marks, a lit material.
|
|
436
|
+
- shader: pick one of water | liquid_metal | heatmap | gem_smoke, give it width
|
|
437
|
+
and height, and set behind:true to place it UNDER the vector art — which is
|
|
438
|
+
what scenery wants, a sea the boats float ON rather than a sheen over them.
|
|
439
|
+
- field: xExpr/yExpr/count draw many marks from one expression, re-evaluated
|
|
440
|
+
every frame against t. rExpr and aExpr vary size and alpha per mark.
|
|
441
|
+
Both draw fully in a cloud render; on a local canvas they stand in as a flat
|
|
442
|
+
plate of their own colour so the layout still reads.
|
|
443
|
+
|
|
444
|
+
LIFETIMES — bornAt and ttl, on any item:
|
|
445
|
+
The only way to build a piece that CUTS between shots. Without them every item
|
|
446
|
+
is on screen for the whole render, which is what makes a long piece read as one
|
|
447
|
+
crowded frame instead of a sequence.
|
|
448
|
+
|
|
400
449
|
WORKFLOW TIP:
|
|
401
450
|
After creating items, use pinepaper_add_relation to animate them (e.g., orbits, follows, attached_to).
|
|
402
451
|
For glossy 3D spheres, use pinepaper_create_glossy_sphere instead. For diagonal stripes, use pinepaper_create_diagonal_stripes.`,
|
|
@@ -405,8 +454,8 @@ For glossy 3D spheres, use pinepaper_create_glossy_sphere instead. For diagonal
|
|
|
405
454
|
properties: {
|
|
406
455
|
itemType: {
|
|
407
456
|
type: 'string',
|
|
408
|
-
enum: ['text', 'circle', 'star', 'rectangle', 'triangle', 'polygon', 'ellipse', 'path', 'line', 'arc', 'pentagon', 'hexagon', 'diamond', 'arrow', 'heart'],
|
|
409
|
-
description:
|
|
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'],
|
|
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.",
|
|
410
459
|
},
|
|
411
460
|
position: {
|
|
412
461
|
oneOf: [
|
|
@@ -429,7 +478,33 @@ For glossy 3D spheres, use pinepaper_create_glossy_sphere instead. For diagonal
|
|
|
429
478
|
},
|
|
430
479
|
properties: {
|
|
431
480
|
type: 'object',
|
|
432
|
-
description:
|
|
481
|
+
description: `Type-specific properties (content, radius, color, fontSize, etc.)
|
|
482
|
+
|
|
483
|
+
LIFETIME — on ANY item, and the only way to build a piece that CUTS between shots:
|
|
484
|
+
- bornAt: seconds. The item does not exist before this instant.
|
|
485
|
+
- ttl: seconds it lives for. Omit for "until the end".
|
|
486
|
+
Without these every item is on screen for the whole render, which is what makes
|
|
487
|
+
a long piece read as one crowded frame instead of a sequence of shots.
|
|
488
|
+
|
|
489
|
+
SHADER (itemType 'shader') — a lit surface, drawn per pixel:
|
|
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'
|
|
491
|
+
- width, height: the quad it fills
|
|
492
|
+
- shaderIntensity: 0..2, the effect's amplitude (water: chop)
|
|
493
|
+
- shaderPalette: 0 day | 1 low sun | 2 night
|
|
494
|
+
- behind: true draws it UNDER the vector art, which is what scenery needs —
|
|
495
|
+
a sea the boats float ON rather than a sheen laid over them. Default false.
|
|
496
|
+
|
|
497
|
+
FIELD (itemType 'field') — one expression, many marks. The generative form:
|
|
498
|
+
a whole surface as a function of the sample index and the clock, rather than
|
|
499
|
+
thousands of authored items.
|
|
500
|
+
- xExpr, yExpr: expressions for each mark's position
|
|
501
|
+
- rExpr, aExpr: optional radius and alpha per mark. A radius <= 0 is skipped,
|
|
502
|
+
so subtracting a threshold draws only the crests of a wave.
|
|
503
|
+
- count: how many marks (up to 200000)
|
|
504
|
+
- Variables in scope: i (0..count-1), n (count), t (seconds), u (i/n), pi, e, tau.
|
|
505
|
+
Functions: sin cos tan asin acos atan sinh cosh tanh exp ln log10 log2 sqrt
|
|
506
|
+
cbrt abs floor ceil round sign atan2 min max pow hypot, and % for modulo.
|
|
507
|
+
- Example — a rolling swell: xExpr "(i % 120) * 8", yExpr "400 + sin((i % 120) * 0.4 - t * 2) * 12", count 3600`,
|
|
433
508
|
additionalProperties: true,
|
|
434
509
|
},
|
|
435
510
|
data: {
|
|
@@ -443,7 +518,7 @@ For glossy 3D spheres, use pinepaper_create_glossy_sphere instead. For diagonal
|
|
|
443
518
|
},
|
|
444
519
|
animationType: {
|
|
445
520
|
type: 'string',
|
|
446
|
-
description: "Animation type to apply on creation (e.g., 'pulse', 'bounce', 'rotate', 'fade', '
|
|
521
|
+
description: "Animation type to apply on creation (e.g., 'pulse', 'bounce', 'rotate', 'fade', 'slideLeftRight', 'scrollUp', 'typewriter', 'keyframe'). NOT 'slide' — that has never existed; the driver silently does nothing with an unknown type.",
|
|
447
522
|
},
|
|
448
523
|
animationSpeed: {
|
|
449
524
|
type: 'number',
|
|
@@ -509,6 +584,7 @@ MODIFIABLE PROPERTIES:
|
|
|
509
584
|
- shadowBlur: Shadow blur radius
|
|
510
585
|
- shadowOffset: Shadow offset [x, y]
|
|
511
586
|
- blendMode: Blend mode (normal, multiply, screen, overlay, etc.)
|
|
587
|
+
- pathData: SVG path data — RESHAPE the geometry itself, not just its styling. This is how you straighten a hand-drawn box or correct a traced outline without deleting and recreating the item, which would lose its id and every relation and keyframe pointing at it. Requires an FxTool build from 2026-08 or later: before that the property was silently dropped while the same call applied the fill and stroke and reported success.
|
|
512
588
|
|
|
513
589
|
GRADIENT OBJECT FORMAT:
|
|
514
590
|
{type: "linear"|"radial", stops: [{color: "#fff", offset: 0}, {color: "#000", offset: 1}], origin: [x,y], destination: [x,y]}
|
|
@@ -734,6 +810,56 @@ EXAMPLES:
|
|
|
734
810
|
// ---------------------------------------------------------------------------
|
|
735
811
|
// LAYER 1 — ATOMS: IMPORT TOOLS
|
|
736
812
|
// ---------------------------------------------------------------------------
|
|
813
|
+
{
|
|
814
|
+
name: 'pinepaper_import_motion_capture',
|
|
815
|
+
annotations: {
|
|
816
|
+
title: 'Import Motion Capture (BVH)',
|
|
817
|
+
readOnlyHint: false,
|
|
818
|
+
destructiveHint: false,
|
|
819
|
+
idempotentHint: false,
|
|
820
|
+
openWorldHint: true,
|
|
821
|
+
},
|
|
822
|
+
description: `Import a BVH motion-capture clip (CMU, Mixamo) as an animated skeleton, or retarget one onto a rig that already exists.
|
|
823
|
+
|
|
824
|
+
TWO MODES, and picking the right one is the whole decision:
|
|
825
|
+
- mode:'import' → builds a NEW skeleton shaped like the capture file. Use when there is no character yet.
|
|
826
|
+
- mode:'retarget' → drives an EXISTING rig named by skeletonId. Proportions stay the character's; only the MOTION comes from the clip. Use whenever the character already exists.
|
|
827
|
+
|
|
828
|
+
USE WHEN:
|
|
829
|
+
- The user supplies a .bvh clip, or asks for real captured motion rather than a simple animationType
|
|
830
|
+
- A rigged character should perform a walk / run / gesture
|
|
831
|
+
- Simple animation (pulse, bounce, rotate) is too coarse for the request
|
|
832
|
+
|
|
833
|
+
DO NOT USE FOR:
|
|
834
|
+
- Simple property animation → pinepaper_animate
|
|
835
|
+
- Timed keyframe tracks you author yourself → pinepaper_keyframe_animate
|
|
836
|
+
|
|
837
|
+
RESULT:
|
|
838
|
+
- import → { skeletonId, bones, poses, duration }
|
|
839
|
+
- retarget → { skeletonId, matched, unmatchedSource, unmatchedTarget }
|
|
840
|
+
|
|
841
|
+
BONE MATCHING: a BVH edge is named for the joint it ENDS at, so "LeftLeg" is the thigh and "LeftFoot" the shin — the alias table accounts for this. Anything it cannot place is reported in unmatchedSource/unmatchedTarget rather than silently driving half the rig; pass boneMap to resolve those explicitly.
|
|
842
|
+
|
|
843
|
+
SAMPLING: CMU records at 120fps. fps defaults to 15 because 120 poses per second is unusable on a canvas timeline; fps:0 keeps every frame.`,
|
|
844
|
+
inputSchema: {
|
|
845
|
+
type: 'object',
|
|
846
|
+
properties: {
|
|
847
|
+
bvh: { type: 'string', description: 'BVH clip text.' },
|
|
848
|
+
mode: { type: 'string', enum: ['import', 'retarget'], description: "'import' = new skeleton from the file; 'retarget' = drive the existing rig in skeletonId, keeping its proportions. Default 'import'." },
|
|
849
|
+
skeletonId: { type: 'string', description: "Rig to drive. REQUIRED when mode='retarget'." },
|
|
850
|
+
fps: { type: 'number', description: 'Pose sampling rate; 0 keeps every frame. Default 15.' },
|
|
851
|
+
height: { type: 'number', description: 'Skeleton height on canvas in px (import mode). Default 320.' },
|
|
852
|
+
position: {
|
|
853
|
+
type: 'object',
|
|
854
|
+
description: 'Where the ROOT joint stands (import mode).',
|
|
855
|
+
properties: { x: { type: 'number' }, y: { type: 'number' } },
|
|
856
|
+
},
|
|
857
|
+
name: { type: 'string', description: 'Name for the created skeleton (import mode).' },
|
|
858
|
+
boneMap: { type: 'object', description: "BVH edge name → this rig's bone name; overrides the alias table.", additionalProperties: { type: 'string' } },
|
|
859
|
+
},
|
|
860
|
+
required: ['bvh'],
|
|
861
|
+
},
|
|
862
|
+
},
|
|
737
863
|
{
|
|
738
864
|
name: 'pinepaper_import_svg',
|
|
739
865
|
annotations: {
|
|
@@ -1349,7 +1475,11 @@ EXAMPLES:
|
|
|
1349
1475
|
y: { type: 'number', description: 'Canvas origin y (default: canvas center)' },
|
|
1350
1476
|
flipY: { type: 'boolean', description: 'Math y-up → screen y-down (default true)' },
|
|
1351
1477
|
style: { type: 'object', description: 'Path style forwarded to create (strokeColor, strokeWidth, fillColor, …)' },
|
|
1352
|
-
|
|
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." },
|
|
1479
|
+
warp: {
|
|
1480
|
+
anyOf: [{ type: 'object' }, { type: 'array', items: { type: 'object' } }],
|
|
1481
|
+
description: 'Chained parametric warp(s): { dx, dy } expressions of x, y, t — one object or an array',
|
|
1482
|
+
},
|
|
1353
1483
|
},
|
|
1354
1484
|
},
|
|
1355
1485
|
},
|
|
@@ -1384,7 +1514,10 @@ RETURNS: create → { success, action, eventId }; pulse → { success, action, e
|
|
|
1384
1514
|
x: { type: 'number', description: 'Canvas x for the event marker — action create' },
|
|
1385
1515
|
y: { type: 'number', description: 'Canvas y for the event marker — action create' },
|
|
1386
1516
|
eventId: { type: 'string', description: "Event id to fire — required for action 'pulse'" },
|
|
1387
|
-
payload: {
|
|
1517
|
+
payload: {
|
|
1518
|
+
anyOf: [{ type: 'string' }, { type: 'number' }, { type: 'boolean' }, { type: 'object' }, { type: 'array' }],
|
|
1519
|
+
description: "Optional value forwarded to listeners — action 'pulse'",
|
|
1520
|
+
},
|
|
1388
1521
|
},
|
|
1389
1522
|
required: ['action'],
|
|
1390
1523
|
},
|
|
@@ -1795,10 +1928,17 @@ EXAMPLE: { action: 'apply_style', itemId: 'title_1', styleKey: 'arcade', fontFam
|
|
|
1795
1928
|
inputSchema: {
|
|
1796
1929
|
type: 'object',
|
|
1797
1930
|
properties: {
|
|
1798
|
-
action: { type: 'string', enum: ['apply_style', 'set_font_axes', 'list_styles'], description:
|
|
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.' },
|
|
1799
1936
|
itemId: { type: 'string', description: 'Text item id — apply_style / set_font_axes.' },
|
|
1800
1937
|
styleKey: { type: 'string', description: 'apply_style: style name from list_styles.' },
|
|
1801
|
-
palette: {
|
|
1938
|
+
palette: {
|
|
1939
|
+
anyOf: [{ type: 'string' }, { type: 'array', items: { type: 'string' } }],
|
|
1940
|
+
description: 'apply_style: named colourway (string) or explicit color array.',
|
|
1941
|
+
},
|
|
1802
1942
|
variant: { type: 'number', description: 'apply_style: variation index within the style.' },
|
|
1803
1943
|
content: { type: 'string', description: 'apply_style: replace the text while styling.' },
|
|
1804
1944
|
fontFamily: { type: 'string', description: "apply_style: font override; 'suggested' = the face the style was designed around." },
|
|
@@ -1808,6 +1948,137 @@ EXAMPLE: { action: 'apply_style', itemId: 'title_1', styleKey: 'arcade', fontFam
|
|
|
1808
1948
|
required: ['action'],
|
|
1809
1949
|
},
|
|
1810
1950
|
},
|
|
1951
|
+
{
|
|
1952
|
+
name: 'pinepaper_design_medium',
|
|
1953
|
+
annotations: {
|
|
1954
|
+
title: 'Design Medium (What Makes The Marks)',
|
|
1955
|
+
readOnlyHint: false,
|
|
1956
|
+
destructiveHint: false,
|
|
1957
|
+
idempotentHint: false,
|
|
1958
|
+
openWorldHint: false,
|
|
1959
|
+
},
|
|
1960
|
+
description: `The THIRD design axis: the MEDIUM — what physically makes the marks.
|
|
1961
|
+
|
|
1962
|
+
Register is which design language a piece speaks; level is how well it is executed; a medium is a set of physical constraints that produce characteristic marks. All three are independent: an embroidered schematic is a technical register at a refined level in a thread medium, and no single better/worse ranking can say that.
|
|
1963
|
+
|
|
1964
|
+
READ THIS BEFORE PROMISING A MEDIUM. Naming a medium does not create its marks. Every medium declares a FIDELITY:
|
|
1965
|
+
- native — the characteristic marks ARE vector geometry (vector, thread, ink, cutPaper)
|
|
1966
|
+
- stylised — a recognisable impression; the limitation is stated on the entry (charcoal, oil)
|
|
1967
|
+
- absent — no vertex-level expression; the tool REFUSES with the reason instead of producing flat shapes in its colours (encaustic)
|
|
1968
|
+
|
|
1969
|
+
Call resolve before telling a user you will render in a medium. "yes, but as an impression" is the true answer for half of them, and a flat approximation misrepresents a medium rather than approximating it.
|
|
1970
|
+
|
|
1971
|
+
ACTIONS:
|
|
1972
|
+
- list_media: every medium with fidelity, definition and limitation. Read it rather than assuming a fixed set — the catalogue grows (hatch and watercolor are recent additions).
|
|
1973
|
+
- resolve: { medium } → { ok, fidelity, reason, markMaker }. ok:false with a reason is a real answer — pass it on rather than trying anyway.
|
|
1974
|
+
- list_stitches: the 4 thread stitches with their params.
|
|
1975
|
+
- apply_thread: render an item in thread. Its own silhouette is the region and its own fill is the thread colour, so an existing drawing becomes stitched rather than needing to be redrawn. The source is HIDDEN, not destroyed.
|
|
1976
|
+
- apply_hatch: rule an item with hatching. Same deal — silhouette is the region, source is hidden, re-hatching replaces.
|
|
1977
|
+
- list_flow_fields: the 7 fields that bend a hatch line.
|
|
1978
|
+
- list_hatch_options: the hatch defaults.
|
|
1979
|
+
|
|
1980
|
+
THE DIRECTION FIELD IS THE WHOLE THING. Stitches that all lie the same way are hatching; stitches that follow the form are needlepainting. Use { kind: 'radial', cx, cy } for anything that radiates (a flower, an eye), { kind: 'spine', spine: [...] } to run them along a midrib or feather shaft, { kind: 'constant', angle } when you actually want flat hatch.
|
|
1981
|
+
|
|
1982
|
+
STITCH CHOICE: longAndShort (default) is the shading fill — the length variance IS the blend. satin spans the shape edge to edge in one sheet, which is right for a petal or a letter stroke and wrong for a round shape. seed is texture, not direction. stem is an OUTLINE mark and leaves the interior bare — that is the stitch, not a bug.
|
|
1983
|
+
|
|
1984
|
+
An unknown stitch is refused rather than defaulted, and re-stitching an item REPLACES its previous stitching instead of stacking.
|
|
1985
|
+
|
|
1986
|
+
HATCHING STATES VALUE THROUGH LINE DENSITY, not colour. The same shape at distance 6 and at distance 3 reads as light and dark with nothing else changed — that is the whole medium, and it is why distance is the parameter to reach for rather than a lighter ink. gradient makes the density fall off across the shape, which is a shaded ramp rather than a flat tone.
|
|
1987
|
+
|
|
1988
|
+
The straight ruling is what a printer makes; flowField is what makes it read as DRAWN. hand is the small correlated wander of a hand-drawn line, waves for water/hair/contours, spiral for wood grain around a knot, columns for rake marks. continuous joins the whole set into one serpentine path — a pen that never leaves the paper, and one item instead of many.
|
|
1989
|
+
|
|
1990
|
+
Hatching needs a CLOSED outline. Text has none: convert it to glyph paths (pinepaper_text_style) and hatch the result. A group is hatched child by child, up to 40 paths; past that the result says so rather than quietly stopping.
|
|
1991
|
+
|
|
1992
|
+
Note that a thread field and a hatch flowField are different things and are named differently for it: 'field' steers stitches around a form, 'flowField' bends a ruling line the way a hand drifts.
|
|
1993
|
+
|
|
1994
|
+
EXAMPLE — a hatched shadow side, drawn rather than printed:
|
|
1995
|
+
{ "action": "apply_hatch", "itemId": "item_3", "distance": 3, "angle": 30, "flowField": "hand", "rand": 0.2 }
|
|
1996
|
+
|
|
1997
|
+
EXAMPLE — a stitched leaf:
|
|
1998
|
+
{ "action": "apply_thread", "itemId": "item_3", "stitch": "longAndShort",
|
|
1999
|
+
"field": { "kind": "spine", "spine": [{"x":400,"y":200},{"x":400,"y":520}] },
|
|
2000
|
+
"stitchLen": 26, "variance": 0.45 }`,
|
|
2001
|
+
inputSchema: {
|
|
2002
|
+
type: 'object',
|
|
2003
|
+
properties: {
|
|
2004
|
+
action: { type: 'string', enum: ['list_media', 'resolve', 'list_stitches', 'apply_thread', 'apply_hatch', 'list_flow_fields', 'list_hatch_options'], description: 'Medium operation' },
|
|
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.' },
|
|
2006
|
+
itemId: { type: 'string', description: 'apply_thread / apply_hatch: a closed path, compound path, or a group of them.' },
|
|
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." },
|
|
2008
|
+
field: { type: 'object', description: "apply_thread: { kind: 'radial'|'spine'|'constant', cx, cy, angle, spine[], across } — default radial from the shape centre." },
|
|
2009
|
+
stitchLen: { type: 'number', description: 'apply_thread: nominal stitch length px (default 18).' },
|
|
2010
|
+
rowGap: { type: 'number', description: 'apply_thread: row spacing px; defaults from thread width so rows abut.' },
|
|
2011
|
+
variance: { type: 'number', description: 'apply_thread: length jitter 0..1 (default 0.35) — the long-and-short shading.' },
|
|
2012
|
+
width: { type: 'number', description: 'apply_thread: thread width px (default 2).' },
|
|
2013
|
+
sheen: { type: 'boolean', description: 'apply_thread: highlight along each stitch (default true).' },
|
|
2014
|
+
color: { type: 'string', description: "apply_thread: thread colour (defaults to the item's fill)." },
|
|
2015
|
+
seed: { type: 'number', description: 'apply_thread: PRNG seed (default 1) — same seed stitches the same way.' },
|
|
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.' },
|
|
2023
|
+
distance: { type: 'number', description: 'apply_hatch: line spacing px (default 6). THE VALUE CONTROL — 6 vs 3 is light vs dark.' },
|
|
2024
|
+
angle: { type: 'number', description: 'apply_hatch: ruling angle in DEGREES (default 45).' },
|
|
2025
|
+
gradient: { type: 'number', description: 'apply_hatch: 0..1 spacing growth per line (default 0, even) — a shaded ramp rather than a flat tone.' },
|
|
2026
|
+
rand: { type: 'number', description: 'apply_hatch: jitter as a fraction of distance (default 0) — takes the mechanical evenness off.' },
|
|
2027
|
+
continuous: { type: 'boolean', description: 'apply_hatch: join the lines into ONE serpentine path (default false).' },
|
|
2028
|
+
flowField: {
|
|
2029
|
+
type: 'string',
|
|
2030
|
+
enum: ['hand', 'curved', 'zigzag', 'waves', 'seabed', 'spiral', 'columns'],
|
|
2031
|
+
description: 'apply_hatch: bend each line along a flow field — what makes it read as drawn rather than printed.',
|
|
2032
|
+
},
|
|
2033
|
+
t: { type: 'number', description: 'apply_hatch: flow-field time offset — advances the field without changing the seed.' },
|
|
2034
|
+
},
|
|
2035
|
+
required: ['action'],
|
|
2036
|
+
},
|
|
2037
|
+
},
|
|
2038
|
+
{
|
|
2039
|
+
name: 'pinepaper_text_effect',
|
|
2040
|
+
annotations: {
|
|
2041
|
+
title: 'Text Effects (37 Character-Level Animations)',
|
|
2042
|
+
readOnlyHint: false,
|
|
2043
|
+
destructiveHint: true,
|
|
2044
|
+
idempotentHint: false,
|
|
2045
|
+
openWorldHint: false,
|
|
2046
|
+
},
|
|
2047
|
+
description: `Animate text CHARACTER BY CHARACTER — 37 effects (terminaltexteffects' vocabulary, reimplemented from source).
|
|
2048
|
+
|
|
2049
|
+
⚠️ THIS REPLACES THE TEXT ITEM. Unlike pinepaper_text_style (which ADOPTS the text's registry id), this EXPLODES the text into one item per character and REMOVES the original. Any relation, keyframe, or handle pointing at the source id DIES with it. Pass keepSource: true to leave the original in place. The returned \`ids\` array is your entire new handle set.
|
|
2050
|
+
|
|
2051
|
+
ACTIONS:
|
|
2052
|
+
- list: {} — the 37 effects as {key, label, definition}. Call this first; effect keys come from here.
|
|
2053
|
+
- apply: { itemId, effect, duration?, seed?, gradient?, gradientStops?, gradientDirection?, gradientSteps?, keepSource?, options? } — explode and animate.
|
|
2054
|
+
|
|
2055
|
+
WHAT YOU GET: ordinary keyframe-animated items. It scrubs on the timeline, survives undo and session restore, and exports through the existing MP4 / SMIL / Lottie paths — this is not a frame-callback effect that renders on screen and vanishes from your export. Every effect ends AT REST, so \`duration\` is the settle time and the text is readable afterwards.
|
|
2056
|
+
|
|
2057
|
+
COLOR: by default the resting characters are painted with a GRADIENT across the text block — that is what the upstream effects do and what their reference frames show. Pass gradient: false to keep the text's authored fill, or gradientStops to choose your own.
|
|
2058
|
+
|
|
2059
|
+
DETERMINISM: seed defaults to 1, so the same text + effect + seed animates identically every run. Vary seed for a different scatter of the same effect.
|
|
2060
|
+
|
|
2061
|
+
EFFECT KEYS (37): scattered, expand, spray, slice, bouncyballs, pour, slide, print, randomsequence, wipe, sweep, highlight, middleout, fireworks, blackhole, unstable, crumble, rings, swarm, orbittingvolley, overflow, decrypt, errorcorrect, matrix, binarypath, rain, burn, smoke, thunderstorm, bubbles, waves, beams, laseretch, spotlights, synthgrid, vhstape, colorshift.
|
|
2062
|
+
|
|
2063
|
+
EXAMPLE: { action: 'apply', itemId: 'title_1', effect: 'decrypt', duration: 3, gradientStops: ['#00ff9c', '#0066ff'] }`,
|
|
2064
|
+
inputSchema: {
|
|
2065
|
+
type: 'object',
|
|
2066
|
+
properties: {
|
|
2067
|
+
action: { type: 'string', enum: ['apply', 'list'], description: 'Text effect operation' },
|
|
2068
|
+
itemId: { type: 'string', description: 'apply: the text item to animate (must have non-whitespace content).' },
|
|
2069
|
+
effect: { type: 'string', description: "apply: effect key from 'list'." },
|
|
2070
|
+
duration: { type: 'number', description: 'apply: seconds for the whole effect (default 2.5); ends at rest.' },
|
|
2071
|
+
seed: { type: 'number', description: 'apply: PRNG seed (default 1) — same seed reproduces the animation exactly.' },
|
|
2072
|
+
gradient: { type: 'boolean', description: "apply: false keeps the text's authored fill. Default true (gradient across the block)." },
|
|
2073
|
+
gradientStops: { type: 'array', items: { type: 'string' }, description: 'apply: hex colors overriding the effect gradient.' },
|
|
2074
|
+
gradientDirection: { type: 'string', enum: ['vertical', 'horizontal', 'radial', 'diagonal'], description: 'apply: gradient axis.' },
|
|
2075
|
+
gradientSteps: { type: 'number', description: 'apply: quantization steps in the gradient ramp.' },
|
|
2076
|
+
keepSource: { type: 'boolean', description: 'apply: keep the original text item — use when relations/keyframes reference its id.' },
|
|
2077
|
+
options: { type: 'object', description: 'apply: per-effect knobs, passed through verbatim.' },
|
|
2078
|
+
},
|
|
2079
|
+
required: ['action'],
|
|
2080
|
+
},
|
|
2081
|
+
},
|
|
1811
2082
|
{
|
|
1812
2083
|
name: 'pinepaper_crop_image',
|
|
1813
2084
|
annotations: {
|
|
@@ -1981,6 +2252,231 @@ RECIPE — a playable maze: create_tilemap with wall fills → batch_create colo
|
|
|
1981
2252
|
required: ['action'],
|
|
1982
2253
|
},
|
|
1983
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
|
+
},
|
|
1984
2480
|
{
|
|
1985
2481
|
name: 'pinepaper_world3d',
|
|
1986
2482
|
annotations: {
|
|
@@ -1999,16 +2495,22 @@ ACTIONS:
|
|
|
1999
2495
|
- add_actor: { actorId?, x, z, height?, sprite? (a canvas item id), live? } → { actorId }. live: true re-rasterizes the item as it animates — a RIGGED character (walk cycle, expressions) PERFORMS in the world instead of standing there as a photograph of itself.
|
|
2000
2496
|
- remove_actor / list_actors / set_actor_pose: { actorId, pose: { x?, z?, angle? } } — the setter a timeline, sequencer or agent drives frame by frame.
|
|
2001
2497
|
- set_camera: { camera: { mode: 'follow' | 'fixed' | 'orbit', target? (actorId), radius?, speed?, eye?, lookAt? } }
|
|
2002
|
-
- add_object: { object: { x, z, height?, color? } } (y defaults to sitting ON the terrain) / remove_object: { objectId }
|
|
2498
|
+
- add_object: { object: { x, z, height?, color?, metalness?, roughness?, emissiveIntensity? } } (y defaults to sitting ON the terrain) / remove_object: { objectId }
|
|
2003
2499
|
- remove_world: {} — tears the world down; the Paper canvas was always its own layer and is untouched.
|
|
2004
2500
|
|
|
2005
2501
|
RECIPE — a character walking through a forest: create {spec:'forest'} → import/rig a character on the canvas → add_actor {sprite: itemId, live: true} → set_camera {mode:'follow', target} → drive set_actor_pose over time (or let the built-in character control walk).`,
|
|
2006
2502
|
inputSchema: {
|
|
2007
2503
|
type: 'object',
|
|
2008
2504
|
properties: {
|
|
2009
|
-
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' },
|
|
2010
|
-
spec: {
|
|
2011
|
-
|
|
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.' },
|
|
2506
|
+
spec: {
|
|
2507
|
+
anyOf: [{ type: 'string' }, { type: 'object' }],
|
|
2508
|
+
description: "create: preset id ('forest'|'snowMountain'|'field'|'jungle') or a full spec object.",
|
|
2509
|
+
},
|
|
2510
|
+
character: {
|
|
2511
|
+
anyOf: [{ type: 'boolean' }, { type: 'object' }],
|
|
2512
|
+
description: 'create: include the walkable character (boolean or config).',
|
|
2513
|
+
},
|
|
2012
2514
|
patch: { type: 'object', description: 'configure: partial world spec, validated against describe.' },
|
|
2013
2515
|
actorId: { type: 'string', description: 'Actor id.' },
|
|
2014
2516
|
x: { type: 'number', description: 'add_actor: world x.' },
|
|
@@ -2018,12 +2520,118 @@ RECIPE — a character walking through a forest: create {spec:'forest'} → impo
|
|
|
2018
2520
|
live: { type: 'boolean', description: 'add_actor: re-rasterize as the item animates.' },
|
|
2019
2521
|
pose: { type: 'object', description: 'set_actor_pose: { x?, z?, angle? }.' },
|
|
2020
2522
|
camera: { type: 'object', description: "set_camera: { mode: follow|fixed|orbit, target?, radius?, speed?, eye?, lookAt? }." },
|
|
2021
|
-
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.' },
|
|
2022
2549
|
objectId: { type: 'string', description: 'remove_object: object id.' },
|
|
2023
2550
|
},
|
|
2024
2551
|
required: ['action'],
|
|
2025
2552
|
},
|
|
2026
2553
|
},
|
|
2554
|
+
{
|
|
2555
|
+
name: 'pinepaper_character',
|
|
2556
|
+
annotations: {
|
|
2557
|
+
title: 'Character (place and direct a figure from the graph)',
|
|
2558
|
+
readOnlyHint: false,
|
|
2559
|
+
idempotentHint: false,
|
|
2560
|
+
},
|
|
2561
|
+
description: `Place a CHARACTER from the design graph and DIRECT it — no geometry, no bones, no poses.
|
|
2562
|
+
|
|
2563
|
+
The knowledge of how a figure is built and how it moves lives in the graph; this asks for it by name and states WHAT happens. The alternative is thirty-odd exactly-right calls — create the skeleton, add each bone with the right parent and angle, create each shape, attach each to the right bone, then author poses — where one mistake anywhere leaves a broken figure.
|
|
2564
|
+
|
|
2565
|
+
{ concept: "pp:Character", at: { x: 470, y: 250 }, height: 300,
|
|
2566
|
+
beats: [ { at: 1.0, channel: "blink" },
|
|
2567
|
+
{ at: 1.8, channel: "say", until: 4.2 },
|
|
2568
|
+
{ at: 5.0, channel: "headTurn", value: -0.8 } ] }
|
|
2569
|
+
|
|
2570
|
+
CHANNELS ARE DECLARED BY THE CONCEPT, not by you. Ask pinepaper_ontology for a concept to see the ones it has. Four kinds exist:
|
|
2571
|
+
- transform: one part deformed with an attack, a hold and a release — a blink is the eyes flattening in scaleY and easing back. This is what gives a gesture a MIDDLE; a swap between two drawings has only two ends and reads as flashing.
|
|
2572
|
+
- cycle: a repeating throw that runs for as long as you ask — a pigeon's head bob, a tail flick. Amplitude is a fraction of the part, so it means the same at any size.
|
|
2573
|
+
- shift: every part moved together, for a turn a flat drawing cannot rotate into.
|
|
2574
|
+
- alternate / speak: swaps between drawn states, for shapes that genuinely differ rather than deform.
|
|
2575
|
+
|
|
2576
|
+
BEATS ARE INSTANTS OR SPANS. \`at\` is when it happens; \`until\` is how long for a channel that occupies time. \`value\` is how far, for channels that take a magnitude.
|
|
2577
|
+
|
|
2578
|
+
WHAT YOU GET BACK: the items and keyframe tracks the beats compile to. They are ordinary scene items afterwards — modify, animate and relate them like anything else.
|
|
2579
|
+
|
|
2580
|
+
NOT LIP SYNC. \`say\` opens and closes a mouth for the length of a line. Matching phonemes needs the word timings a voice track carries; pass \`timings\` if you have them and each entry drives one mouth opening.`,
|
|
2581
|
+
inputSchema: {
|
|
2582
|
+
type: 'object',
|
|
2583
|
+
properties: {
|
|
2584
|
+
concept: {
|
|
2585
|
+
type: 'string',
|
|
2586
|
+
description: 'Concept id from the design graph, e.g. "pp:Character" or "pp:Pigeon". Its parts, rig and channels come with it.',
|
|
2587
|
+
},
|
|
2588
|
+
at: {
|
|
2589
|
+
type: 'object',
|
|
2590
|
+
properties: {
|
|
2591
|
+
x: { type: 'number', description: 'X of the figure\'s centre' },
|
|
2592
|
+
y: { type: 'number', description: 'Y of the figure\'s centre' },
|
|
2593
|
+
},
|
|
2594
|
+
description: 'Where the figure stands.',
|
|
2595
|
+
},
|
|
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.' },
|
|
2601
|
+
id: { type: 'string', description: 'Prefix for the created items (default: the concept name).' },
|
|
2602
|
+
style: { type: 'string', description: 'Style id for the depiction (default: the concept\'s own).' },
|
|
2603
|
+
variant: { type: 'string', description: 'Depiction variant (default: "default").' },
|
|
2604
|
+
durationSeconds: { type: 'number', description: 'Length of the piece, so every track spans it. A track that starts late leaves the figure invisible until it does.' },
|
|
2605
|
+
beats: {
|
|
2606
|
+
type: 'array',
|
|
2607
|
+
description: 'What happens, in order. Channels the concept does not declare are ignored rather than approximated.',
|
|
2608
|
+
items: {
|
|
2609
|
+
type: 'object',
|
|
2610
|
+
properties: {
|
|
2611
|
+
at: { type: 'number', description: 'When, in seconds' },
|
|
2612
|
+
channel: { type: 'string', description: 'A channel the concept declares — blink, say, headTurn, bob, tailFlick…' },
|
|
2613
|
+
until: { type: 'number', description: 'For a channel that occupies a span rather than an instant' },
|
|
2614
|
+
value: { type: 'number', description: 'How far, for a channel that takes a magnitude' },
|
|
2615
|
+
},
|
|
2616
|
+
required: ['at', 'channel'],
|
|
2617
|
+
},
|
|
2618
|
+
},
|
|
2619
|
+
timings: {
|
|
2620
|
+
type: 'array',
|
|
2621
|
+
description: 'Optional word or syllable timings from a voice track. Each entry drives one mouth opening, which is what turns the shape of speech into lip sync.',
|
|
2622
|
+
items: {
|
|
2623
|
+
type: 'object',
|
|
2624
|
+
properties: {
|
|
2625
|
+
at: { type: 'number', description: 'Start, in seconds' },
|
|
2626
|
+
until: { type: 'number', description: 'End, in seconds' },
|
|
2627
|
+
},
|
|
2628
|
+
required: ['at'],
|
|
2629
|
+
},
|
|
2630
|
+
},
|
|
2631
|
+
},
|
|
2632
|
+
required: ['concept'],
|
|
2633
|
+
},
|
|
2634
|
+
},
|
|
2027
2635
|
{
|
|
2028
2636
|
name: 'pinepaper_rigging',
|
|
2029
2637
|
annotations: {
|
|
@@ -2037,6 +2645,19 @@ RECIPE — a character walking through a forest: create {spec:'forest'} → impo
|
|
|
2037
2645
|
|
|
2038
2646
|
WORKFLOW: create_skeleton → add_bone (×N, parent them into a hierarchy) → attach_item (bind shapes to bones) → optionally create_ik_chain → animate with add_pose_keyframe (or save_pose then reference it).
|
|
2039
2647
|
|
|
2648
|
+
POSE MOTION — build a performance out of saved poses:
|
|
2649
|
+
- START HERE on a rig you did not build yourself: list_bones gives the ids an inline pose needs ({ boneId: angleDeg }), and list_pose_libraries + load_pose_library give a fresh rig a stock walk/run/jump set to work with. A newly rigged character has ZERO saved poses, so without one of these there is nothing to sequence.
|
|
2650
|
+
- save_pose / list_poses / load_pose / interpolate_poses: the pose library. A pose is a snapshot of bone angles; everything below is a graph over these.
|
|
2651
|
+
- play_pose_sequence: one clip. Keys name a saved pose id or an inline { boneId: angleDeg } map. ONE sequence per skeleton — starting another replaces it.
|
|
2652
|
+
- stitch_poses: join CLIPS into one continuous performance (walk ×3 → jump → land → idle) and install it as a single sequence. This is how you get a transition rather than a cut.
|
|
2653
|
+
· A cyclic clip (loop: true) is entered at the phase CLOSEST to where the previous clip ended, so the legs do not teleport mid-stride. matchPhase: false to disable.
|
|
2654
|
+
· Every seam reports 'residual' — the angular mismatch, in degrees, that the blend could not remove. Near zero is a real match; a large number means the blend is HIDING a cut. Read it.
|
|
2655
|
+
· plan: true returns the seams without touching the skeleton, so you can check the joins first.
|
|
2656
|
+
- move_root: a deterministic locomotion track, keyed in SCENE SECONDS on the same clock as the pose sequence. Without it a walk cycle is a march on the spot; with a last keyframe shorter than the performance, the figure arrives early and stands there.
|
|
2657
|
+
- auto_walk / auto_breath / auto_idle / auto_jump: one-call procedural layers. breath and idle find their bones BY NAME (spine/chest/body, head/hip/spine) and refuse with that list if the rig uses other names.
|
|
2658
|
+
- bake_animation: freeze the whole rig into plain item keyframes for export. Needs items ATTACHED to bones — a bare skeleton has no output.
|
|
2659
|
+
- add_secondary_motion: spring chains for tails, hair, cloth. skin_path: per-vertex deformation of a curved path.
|
|
2660
|
+
|
|
2040
2661
|
ACTIONS:
|
|
2041
2662
|
- create_skeleton: { name?, rootPosition? } → { skeletonId }
|
|
2042
2663
|
- add_bone: { skeletonId, name?, parentBoneId? (omit for root), length? (80), angle? (deg), flexibility? (0 rigid…1), segments? } → { boneId }
|
|
@@ -2054,7 +2675,7 @@ BREAKDOWN POSES (S12): a breakdown keyframe shapes the ARC + SPACING between key
|
|
|
2054
2675
|
inputSchema: {
|
|
2055
2676
|
type: 'object',
|
|
2056
2677
|
properties: {
|
|
2057
|
-
action: { type: 'string', enum: ['create_skeleton', 'add_bone', 'attach_item', 'create_ik_chain', 'add_pose_keyframe', 'set_target_path', 'save_pose', 'save_shape_key', 'import_bvh', 'retarget_bvh', 'import_spine'], description: 'Rigging operation' },
|
|
2678
|
+
action: { type: 'string', enum: ['create_skeleton', 'add_bone', 'attach_item', 'create_ik_chain', 'add_pose_keyframe', 'set_target_path', 'save_pose', 'save_shape_key', 'import_bvh', 'retarget_bvh', 'import_spine', 'list_skeletons', 'list_bones', 'list_pose_libraries', 'load_pose_library', 'list_poses', 'load_pose', 'interpolate_poses', 'play_pose_sequence', 'stop_pose_sequence', 'stitch_poses', 'apply_pose_transition', 'auto_walk', 'auto_breath', 'auto_idle', 'auto_jump', 'move_root', 'stop_root_track', 'add_secondary_motion', 'skin_path', 'bake_animation', 'list_shape_keys', 'load_shape_key'], description: 'Rigging operation' },
|
|
2058
2679
|
bvhText: { type: 'string', description: 'import_bvh / retarget_bvh: the .bvh file contents.' },
|
|
2059
2680
|
spineJson: { type: 'string', description: 'import_spine: the Spine JSON export, as a string.' },
|
|
2060
2681
|
view: { type: 'string', enum: ['side', 'front'], description: 'import_bvh: 3D→2D projection plane. CMU walks read best from the side (default).' },
|
|
@@ -2065,6 +2686,25 @@ BREAKDOWN POSES (S12): a breakdown keyframe shapes the ARC + SPACING between key
|
|
|
2065
2686
|
itemId: { type: 'string', description: 'Canvas item id — attach_item.' },
|
|
2066
2687
|
chainId: { type: 'string', description: 'IK chain id — set_target_path.' },
|
|
2067
2688
|
name: { type: 'string', description: 'Name — create_skeleton / add_bone / create_ik_chain / save_pose / save_shape_key.' },
|
|
2689
|
+
poseId: { type: 'string', description: 'Saved pose id — load_pose.' },
|
|
2690
|
+
libraryName: { type: 'string', description: 'Stock pose set — load_pose_library. list_pose_libraries for the names.' },
|
|
2691
|
+
boneMap: { type: 'object', description: 'Bone NAME → bone id — load_pose_library. Omit to build it from list_bones.' },
|
|
2692
|
+
poseIdA: { type: 'string', description: 'First pose — interpolate_poses.' },
|
|
2693
|
+
poseIdB: { type: 'string', description: 'Second pose — interpolate_poses.' },
|
|
2694
|
+
t: { type: 'number', description: 'Blend 0=A … 1=B — interpolate_poses.' },
|
|
2695
|
+
sequence: { type: 'array', items: { type: 'object' }, description: 'Pose keys [{ t, pose | angles, ease }] — play_pose_sequence.' },
|
|
2696
|
+
clips: { type: 'array', items: { type: 'object' }, description: 'Clips in order for stitch_poses: [{ keys | poses, duration, repeat, loop, blend, matchPhase }]. A clip key names a SAVED pose id or an inline { boneId: angleDeg } map.' },
|
|
2697
|
+
blend: { type: 'number', description: 'Seam overlap in seconds (default 0.25) — stitch_poses.' },
|
|
2698
|
+
blendSteps: { type: 'number', description: 'Keys emitted across a seam (default 6) — stitch_poses.' },
|
|
2699
|
+
matchPhase: { type: 'boolean', description: 'Enter a cyclic clip at the phase closest to where the last one ended (default true) — stitch_poses.' },
|
|
2700
|
+
plan: { type: 'boolean', description: 'stitch_poses: return the seams WITHOUT installing the result.' },
|
|
2701
|
+
transitionName: { type: 'string', description: 'Named transition — apply_pose_transition.' },
|
|
2702
|
+
poseIdMap: { type: 'object', description: 'Transition pose name → saved pose id — apply_pose_transition.' },
|
|
2703
|
+
boneNames: { type: 'array', items: { type: 'string' }, description: 'Ordered bone names for a spring chain — add_secondary_motion.' },
|
|
2704
|
+
keyframes: { type: 'array', items: { type: 'object' }, description: 'World root positions [{ t, x, y, ease }] — move_root. `t` is SCENE SECONDS on the same clock as the pose sequence, not a 0..1 fraction: match the last keyframe to the performance duration or the figure arrives early and stands still for the rest.' },
|
|
2705
|
+
shapeKeyId: { type: 'string', description: 'Saved shape key id — load_shape_key.' },
|
|
2706
|
+
weight: { type: 'number', description: 'Shape key weight (default 1) — load_shape_key.' },
|
|
2707
|
+
options: { type: 'object', description: 'Pass-through options — auto_* / bake_animation / move_root / skin_path / play_pose_sequence.' },
|
|
2068
2708
|
rootPosition: { type: 'object', properties: { x: { type: 'number' }, y: { type: 'number' } }, description: 'Skeleton root position — create_skeleton.' },
|
|
2069
2709
|
parentBoneId: { type: 'string', description: 'Parent bone id (omit for root) — add_bone.' },
|
|
2070
2710
|
length: { type: 'number', description: 'Bone length px (default 80) — add_bone.' },
|
|
@@ -2079,7 +2719,10 @@ BREAKDOWN POSES (S12): a breakdown keyframe shapes the ARC + SPACING between key
|
|
|
2079
2719
|
strength: { type: 'number', description: 'Chain strength 0..1 (1) — create_ik_chain.' },
|
|
2080
2720
|
poleVector: { type: 'object', properties: { x: { type: 'number' }, y: { type: 'number' } }, description: 'Pole vector hint — create_ik_chain.' },
|
|
2081
2721
|
time: { type: 'number', description: 'Keyframe time s — add_pose_keyframe.' },
|
|
2082
|
-
pose: {
|
|
2722
|
+
pose: {
|
|
2723
|
+
anyOf: [{ type: 'string' }, { type: 'object', additionalProperties: { type: 'number' } }],
|
|
2724
|
+
description: 'Saved pose id (string) OR { boneId: angleDeg } map — add_pose_keyframe.',
|
|
2725
|
+
},
|
|
2083
2726
|
easing: { type: 'string', description: 'Named easing (linear) — add_pose_keyframe.' },
|
|
2084
2727
|
favor: { type: 'number', description: 'Breakdown spacing bias −1..1 — add_pose_keyframe.' },
|
|
2085
2728
|
breakdown: { type: 'boolean', description: 'Mark as breakdown pose — add_pose_keyframe.' },
|
|
@@ -2198,7 +2841,7 @@ USE WHEN:
|
|
|
2198
2841
|
- Building live geometric constructions (GeoGebra-style — dragging an anchor updates the dependent)
|
|
2199
2842
|
|
|
2200
2843
|
RELATION TYPES:
|
|
2201
|
-
- orbits: Circular motion around target (params: radius, speed, direction, phase
|
|
2844
|
+
- orbits: Circular motion around target (params: radius, speed, direction, phaseDegrees). Use phaseDegrees. 'phase' is the one parameter in the whole relation vocabulary measured in RADIANS — the documented exception to "angles are DEGREES", kept only for scenes that already set it. phaseDegrees wins when both are given.
|
|
2202
2845
|
- follows: Move toward target with smoothing (params: offset, smoothing, delay)
|
|
2203
2846
|
- attached_to: Fixed offset from target (params: offset, inherit_rotation)
|
|
2204
2847
|
- maintains_distance: Stay fixed distance from target (params: distance, strength)
|
|
@@ -2206,6 +2849,35 @@ RELATION TYPES:
|
|
|
2206
2849
|
- mirrors: Mirror position across axis (params: axis, center)
|
|
2207
2850
|
- parallax: Move relative by depth (params: depth, origin)
|
|
2208
2851
|
- bounds_to: Stay within bounds (params: padding, bounce)
|
|
2852
|
+
- anchored_in_world: Pin the item to a 3D world position so it tracks the world camera.
|
|
2853
|
+
- spring_follow: Follow with spring dynamics rather than smoothing — it overshoots and settles.
|
|
2854
|
+
- repels: Move away from the target inside a radius and ease back when it leaves (params: force, maxDistance, minDistance, returnSpeed). Crowd avoidance, and reacting to a cursor without being dragged by it.
|
|
2855
|
+
- wiggle: Continuous procedural noise on the item. Richer than a sine: it does not repeat visibly.
|
|
2856
|
+
- syncs_with: Run the source's animation on the TARGET's timeline (params: offset, speedRatio) — they share a clock and stay in step even when the target's timing changes, which merely starting together does not give you.
|
|
2857
|
+
- triggers_animation: Start the target's animation on a condition (params: trigger, delay, atTime). The declarative "and then": sequencing in the graph instead of in a callback nobody can query.
|
|
2858
|
+
- tours / synced_to_audio: Move through a sequence of targets; bind playback to an audio track.
|
|
2859
|
+
- connects_to: Diagram edge (params: via, sourcePort, targetPort, label).
|
|
2860
|
+
- attached_to_tail / head_points_to: Bind a connector's tail / head to a diagram port (params: port). Together they are why a connector stays connected when a flowchart is rearranged.
|
|
2861
|
+
- expresses: Play a facial expression over a root's part_of children (params: expression — blink|smile|surprise…, interval, duration, easing). No interval means sustained. It walks part_of from the ROOT, so parts with roles but no parent are INERT: the relation applies to nothing and reports success, which is what makes an unparented pair of eyes look finished while being unable to blink.
|
|
2862
|
+
- part_of: Semantic membership with a named role (params: role, offset, structural). structural: true records the part WITHOUT driving its position — the right choice when the parent is a group that already transforms its children, since driving it too would fight the group.
|
|
2863
|
+
- staggered_with: Delay an item's animation by its place in a sequence (params: index, stagger, effect). For a SHAPED stagger — outward from the centre, converging from the edges, across a grid — also pass count (required: without it there is no "end" to measure distance from), plus from (start|end|center|edges|random), amount (total seconds for the whole run, overrides stagger), grid ("rows,cols" or "auto"), axis (x|y) and distributeEase. Plain index x stagger still works and is taken first. To stagger items directly rather than through a relation, use pinepaper_stagger.
|
|
2864
|
+
|
|
2865
|
+
INTERACTIVE RELATIONS — the state machine lives in the graph.
|
|
2866
|
+
An event channel (pinepaper_event) is the hub: something FIRES it, and reactions bound to it run. Nothing here is a callback; it is all queryable graph, so it scrubs, exports and survives a reload.
|
|
2867
|
+
|
|
2868
|
+
- Input triggers (item → event): on_click_fire, on_pointer_enter_fire, on_pointer_exit_fire, on_key_fire (params: key, modifiers, global, preventDefault). { key: 'Enter' } makes Enter activate a button exactly as a click does. The chord is matched EXACTLY in both directions — { key: 'Enter' } does NOT fire on Ctrl+Enter, and { key: 's' } does not steal the browser's Ctrl+S; ask for a modifier with modifiers: { ctrl: true }. Listens only while the source item has focus unless global: true, so it will not take keys from the rest of the page.
|
|
2869
|
+
- Event reactions (event → item), params in common: property, value, key, by, color, template, delay, timeline.
|
|
2870
|
+
· on_event_set_property / _set_color / _set_visibility / _set_data — write a value.
|
|
2871
|
+
· on_event_set_property_from_template — write a value built from a '{token}' TEMPLATE read off item.data, so the text can quote live state ("Score: {score}") instead of a literal fixed when you authored it.
|
|
2872
|
+
· on_event_increment (params: property, by) / on_event_toggle — counters and flip-flops.
|
|
2873
|
+
· on_event_fire_after (params: delay, timeline) — chain one beat to the next.
|
|
2874
|
+
· on_event_fire_if — the only reaction that TESTS graph state rather than mutating it; params.predicate is an ExpressionIR program.
|
|
2875
|
+
· on_event_add_relation / on_event_remove_relation — the scene rewires itself.
|
|
2876
|
+
· on_event_store_set / on_event_store_increment / restores_from — persistent state. The target is a store node whose id is the storage SCOPE, so two stores in a scene and two widgets on a page do not collide. restores_from is the read half and fires ONCE at load.
|
|
2877
|
+
- Proximity (pointer over an item): on_enter_* / on_exit_* mirror the on_event_* reactions above (set_color, set_property, set_property_from_template, set_visibility, set_data, increment, toggle) — hover states without a single line of script.
|
|
2878
|
+
- Exclusive state: exclusive_group makes at most one member active (on_event_set_active is how you activate one — tabs, steppers, quiz cards). menubar_group is the same grouping WITHOUT the mutex.
|
|
2879
|
+
|
|
2880
|
+
There are 80 relation types and this list names the families rather than every member. For the live catalogue — what exists in THIS build, with definitions — call pinepaper_query_capabilities { kind: 'relation' }, which reads the engine's registry rather than a list written down here.
|
|
2209
2881
|
|
|
2210
2882
|
RELATION COMPATIBILITY:
|
|
2211
2883
|
- Universal relations (position-based) work with ALL item types including imported SVGs/images:
|
|
@@ -2228,7 +2900,7 @@ GEOMETRIC CONSTRUCTION CONSTRAINTS (Layer 2) — the source item is RE-DERIVED e
|
|
|
2228
2900
|
|
|
2229
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.
|
|
2230
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.
|
|
2231
|
-
- 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.
|
|
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.
|
|
2232
2904
|
|
|
2233
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.
|
|
2234
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.
|
|
@@ -2270,7 +2942,7 @@ Relations are COMPOSITIONAL - an item can have multiple relations that work toge
|
|
|
2270
2942
|
},
|
|
2271
2943
|
relationType: {
|
|
2272
2944
|
type: 'string',
|
|
2273
|
-
enum: ['orbits', 'follows', 'attached_to', 'maintains_distance', 'points_at', 'mirrors', 'parallax', 'bounds_to', 'animates', 'grows_from', 'staggered_with', 'indicates', 'circumscribes', 'wave_through', '
|
|
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'],
|
|
2274
2946
|
description: 'Type of relationship',
|
|
2275
2947
|
},
|
|
2276
2948
|
params: {
|
|
@@ -2278,8 +2950,17 @@ Relations are COMPOSITIONAL - an item can have multiple relations that work toge
|
|
|
2278
2950
|
description: 'Relation-specific parameters',
|
|
2279
2951
|
additionalProperties: true,
|
|
2280
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
|
+
},
|
|
2281
2962
|
},
|
|
2282
|
-
required: ['sourceId'
|
|
2963
|
+
required: ['sourceId'],
|
|
2283
2964
|
},
|
|
2284
2965
|
},
|
|
2285
2966
|
{
|
|
@@ -2304,7 +2985,7 @@ USE WHEN:
|
|
|
2304
2985
|
targetId: { type: 'string', description: 'Target item ID' },
|
|
2305
2986
|
relationType: {
|
|
2306
2987
|
type: 'string',
|
|
2307
|
-
enum: ['orbits', 'follows', 'attached_to', 'maintains_distance', 'points_at', 'mirrors', 'parallax', 'bounds_to', 'animates', 'grows_from', 'staggered_with', 'indicates', 'circumscribes', 'wave_through', '
|
|
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'],
|
|
2308
2989
|
description: 'Specific relation type to remove (optional - removes all if not specified)',
|
|
2309
2990
|
},
|
|
2310
2991
|
},
|
|
@@ -2332,7 +3013,7 @@ USE WHEN:
|
|
|
2332
3013
|
itemId: { type: 'string', description: 'Item to query relations for' },
|
|
2333
3014
|
relationType: {
|
|
2334
3015
|
type: 'string',
|
|
2335
|
-
enum: ['orbits', 'follows', 'attached_to', 'maintains_distance', 'points_at', 'mirrors', 'parallax', 'bounds_to', 'animates', 'grows_from', 'staggered_with', 'indicates', 'circumscribes', 'wave_through', '
|
|
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'],
|
|
2336
3017
|
description: 'Filter by relation type (optional)',
|
|
2337
3018
|
},
|
|
2338
3019
|
direction: {
|
|
@@ -2483,7 +3164,8 @@ USE WHEN:
|
|
|
2483
3164
|
- "bouncing text" → animationType: bounce
|
|
2484
3165
|
- "fading effect" → animationType: fade
|
|
2485
3166
|
- "wobbling button" → animationType: wobble
|
|
2486
|
-
- "sliding header" → animationType:
|
|
3167
|
+
- "sliding header" → animationType: slideLeftRight (or slideUpDown)
|
|
3168
|
+
- "scrolling banner" → animationType: scrollLeft (also scrollRight/Up/Down)
|
|
2487
3169
|
- "typewriter effect" → animationType: typewriter (text only)
|
|
2488
3170
|
|
|
2489
3171
|
DO NOT USE WHEN:
|
|
@@ -2491,21 +3173,30 @@ DO NOT USE WHEN:
|
|
|
2491
3173
|
- User wants sequential animations ("first fade, then rotate") → Use keyframe animation
|
|
2492
3174
|
- User describes relationships ("orbit around") → Use relations
|
|
2493
3175
|
|
|
2494
|
-
ANIMATION TYPES:
|
|
3176
|
+
ANIMATION TYPES (18 — the list the driver actually switches on):
|
|
2495
3177
|
- pulse: Scale up/down rhythmically
|
|
3178
|
+
- breathe: A slower, softer pulse
|
|
2496
3179
|
- rotate: Continuous rotation
|
|
3180
|
+
- swing: Pendulum rotation about the top
|
|
2497
3181
|
- bounce: Vertical bouncing motion
|
|
2498
|
-
-
|
|
3182
|
+
- jelly: Squash-and-stretch wobble
|
|
3183
|
+
- shake: Rapid small displacement
|
|
2499
3184
|
- wobble: Side-to-side wobbling
|
|
2500
|
-
-
|
|
2501
|
-
-
|
|
3185
|
+
- fade: Opacity cycling
|
|
3186
|
+
- glow: Cycling luminance
|
|
3187
|
+
- slideLeftRight / slideUpDown: Sliding on one axis
|
|
3188
|
+
- scrollLeft / scrollRight / scrollUp / scrollDown: Continuous scroll, wrapping
|
|
3189
|
+
- path: Travel along an assigned path
|
|
3190
|
+
- typewriter: Character-by-character reveal
|
|
3191
|
+
|
|
3192
|
+
There is NO 'slide'. An unknown type is stored and then ignored — the item sits still while its data claims otherwise, and the call still reports success — so use the exact names above.`,
|
|
2502
3193
|
inputSchema: {
|
|
2503
3194
|
type: 'object',
|
|
2504
3195
|
properties: {
|
|
2505
3196
|
itemId: { type: 'string', description: 'Registry ID of the item' },
|
|
2506
3197
|
animationType: {
|
|
2507
3198
|
type: 'string',
|
|
2508
|
-
enum: ['pulse', 'rotate', '
|
|
3199
|
+
enum: ['bounce', 'breathe', 'fade', 'glow', 'jelly', 'path', 'pulse', 'rotate', 'scrollDown', 'scrollLeft', 'scrollRight', 'scrollUp', 'shake', 'slideLeftRight', 'slideUpDown', 'swing', 'typewriter', 'wobble'],
|
|
2509
3200
|
description: 'Type of animation',
|
|
2510
3201
|
},
|
|
2511
3202
|
speed: {
|
|
@@ -2635,15 +3326,45 @@ USE WHEN:
|
|
|
2635
3326
|
- Seeking to specific time
|
|
2636
3327
|
- Controlling animation state
|
|
2637
3328
|
|
|
2638
|
-
DETERMINISTIC SEEK: pass deterministic: true with action 'seek' to evaluate the WHOLE scene at the exact time via app.sceneAt(t) — keyframes + relations + generators all tick to t, so the same time always yields the same frame (paired with a seed in pinepaper_capture_frames for reproducible output). Plain seek only sets the keyframe playback state. Use deterministic seek before pinepaper_browser_screenshot to capture a reproducible frame
|
|
3329
|
+
DETERMINISTIC SEEK: pass deterministic: true with action 'seek' to evaluate the WHOLE scene at the exact time via app.sceneAt(t) — keyframes + relations + generators all tick to t, so the same time always yields the same frame (paired with a seed in pinepaper_capture_frames for reproducible output). Plain seek only sets the keyframe playback state. Use deterministic seek before pinepaper_browser_screenshot to capture a reproducible frame.
|
|
3330
|
+
|
|
3331
|
+
RATE (set_time_scale / get_time_scale): 1 is real time, 0.5 half speed, 0 freezes the clock WITHOUT stopping playback, and a negative rate runs the scene backwards. Changing it preserves the current position — the clock is rebased, so the playhead does not jump. Export is deliberately unaffected: a scene watched at 0.5x still exports its real duration rather than a file twice as long.
|
|
3332
|
+
|
|
3333
|
+
PROGRESS (get_progress / set_progress): the timeline as 0..1, which is the unit a scrubber or a progress bar actually has. set_progress returns the seconds it landed on.
|
|
3334
|
+
|
|
3335
|
+
SCROLL (bind_scroll / unbind_scroll / list_scrub_anchors): drive the timeline from how far the reader has scrolled instead of from a clock — what makes a scroll-told story a story rather than a video that happens to be on the page. Anchors ('top bottom' = the moment the element appears, 'bottom top' = the moment it is gone) say where the range starts and ends; scrub is a smoothing time constant so the playhead chases the scroll instead of snapping to it. Chiefly for embeds and generated code — nothing scrolls a headless page. ALWAYS unbind before rebinding or navigating away: the listener holds the scene alive. bind_scroll releases a previous binding for you, and unbind_scroll on nothing bound answers { bound: false } rather than failing.`,
|
|
2639
3336
|
inputSchema: {
|
|
2640
3337
|
type: 'object',
|
|
2641
3338
|
properties: {
|
|
2642
3339
|
action: {
|
|
2643
3340
|
type: 'string',
|
|
2644
|
-
enum: [
|
|
3341
|
+
enum: [
|
|
3342
|
+
'play', 'pause', 'stop', 'seek',
|
|
3343
|
+
'set_time_scale', 'get_time_scale',
|
|
3344
|
+
'get_progress', 'set_progress',
|
|
3345
|
+
'bind_scroll', 'unbind_scroll', 'list_scrub_anchors',
|
|
3346
|
+
],
|
|
2645
3347
|
description: 'Playback action',
|
|
2646
3348
|
},
|
|
3349
|
+
rate: {
|
|
3350
|
+
type: 'number',
|
|
3351
|
+
description: 'set_time_scale: 1 real time, 0.5 half speed, 0 freezes without stopping, negative runs backwards. Export is unaffected.',
|
|
3352
|
+
},
|
|
3353
|
+
progress: {
|
|
3354
|
+
type: 'number',
|
|
3355
|
+
description: 'set_progress: how far through the timeline, 0..1',
|
|
3356
|
+
},
|
|
3357
|
+
scroll: {
|
|
3358
|
+
type: 'object',
|
|
3359
|
+
properties: {
|
|
3360
|
+
elementId: { type: 'string', description: 'DOM id of the element to track (defaults to the canvas)' },
|
|
3361
|
+
start: { type: 'string', description: "Anchor where the range begins, e.g. 'top bottom' — see list_scrub_anchors" },
|
|
3362
|
+
end: { type: 'string', description: "Anchor where the range ends, e.g. 'bottom top'" },
|
|
3363
|
+
scrub: { type: 'number', description: 'Seconds of smoothing so the playhead chases the scroll (default 0.3; 0 snaps)' },
|
|
3364
|
+
range: { type: 'array', items: { type: 'number' }, description: '[from, to] seconds — drive only a sub-range of the timeline' },
|
|
3365
|
+
},
|
|
3366
|
+
description: 'bind_scroll options',
|
|
3367
|
+
},
|
|
2647
3368
|
duration: {
|
|
2648
3369
|
type: 'number',
|
|
2649
3370
|
description: 'Duration for play action',
|
|
@@ -3131,7 +3852,7 @@ EXAMPLE - Bouncing Balls:
|
|
|
3131
3852
|
|
|
3132
3853
|
SUPPORTED ITEM TYPES: text, circle, star, rectangle, triangle, polygon, ellipse, path, line, arc
|
|
3133
3854
|
SUPPORTED RELATIONS: orbits, follows, attached_to, maintains_distance, points_at, mirrors, parallax, bounds_to, animates, grows_from, staggered_with, indicates, circumscribes, wave_through, camera_follows, camera_animates, morphs_to, group_morphs_to, moves_along_path, is_midpoint_of, lies_on_line, is_centroid_of, is_circumcenter_of, concentric_with, construction_reveal
|
|
3134
|
-
SUPPORTED ANIMATIONS: pulse, rotate,
|
|
3855
|
+
SUPPORTED ANIMATIONS: bounce, breathe, fade, glow, jelly, path, pulse, rotate, scrollDown, scrollLeft, scrollRight, scrollUp, shake, slideLeftRight, slideUpDown, swing, typewriter, wobble`,
|
|
3135
3856
|
inputSchema: {
|
|
3136
3857
|
type: 'object',
|
|
3137
3858
|
properties: {
|
|
@@ -3176,7 +3897,7 @@ SUPPORTED ANIMATIONS: pulse, rotate, bounce, fade, wobble, slide, typewriter`,
|
|
|
3176
3897
|
target: { type: 'string', description: 'Name of target item' },
|
|
3177
3898
|
type: {
|
|
3178
3899
|
type: 'string',
|
|
3179
|
-
enum: ['orbits', 'follows', 'attached_to', 'maintains_distance', 'points_at', 'mirrors', 'parallax', 'bounds_to', 'animates', 'grows_from', 'staggered_with', 'indicates', 'circumscribes', 'wave_through', '
|
|
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'],
|
|
3180
3901
|
},
|
|
3181
3902
|
params: {
|
|
3182
3903
|
type: 'object',
|
|
@@ -3196,7 +3917,7 @@ SUPPORTED ANIMATIONS: pulse, rotate, bounce, fade, wobble, slide, typewriter`,
|
|
|
3196
3917
|
target: { type: 'string', description: 'Name of item to animate' },
|
|
3197
3918
|
type: {
|
|
3198
3919
|
type: 'string',
|
|
3199
|
-
enum: ['pulse', 'rotate', '
|
|
3920
|
+
enum: ['bounce', 'breathe', 'fade', 'glow', 'jelly', 'path', 'pulse', 'rotate', 'scrollDown', 'scrollLeft', 'scrollRight', 'scrollUp', 'shake', 'slideLeftRight', 'slideUpDown', 'swing', 'typewriter', 'wobble'],
|
|
3200
3921
|
},
|
|
3201
3922
|
speed: { type: 'number', description: 'Animation speed (default: 1.0)' },
|
|
3202
3923
|
params: {
|
|
@@ -3334,6 +4055,361 @@ EXAMPLE — Slideshow:
|
|
|
3334
4055
|
required: ['action'],
|
|
3335
4056
|
},
|
|
3336
4057
|
},
|
|
4058
|
+
{
|
|
4059
|
+
name: 'pinepaper_sequence',
|
|
4060
|
+
annotations: {
|
|
4061
|
+
title: 'Timeline Sequencing (Relative Positions)',
|
|
4062
|
+
readOnlyHint: true,
|
|
4063
|
+
destructiveHint: false,
|
|
4064
|
+
idempotentHint: true,
|
|
4065
|
+
openWorldHint: false,
|
|
4066
|
+
},
|
|
4067
|
+
description: `Work out WHEN clips start, said relative to each other instead of in absolute seconds. A pure planner — it computes times and touches nothing on the canvas. (For revealing a geometric construction step by step, that is pinepaper_construction_sequence, a different tool.)
|
|
4068
|
+
|
|
4069
|
+
Nobody thinks "the subtitle starts at 1.85s"; they think "a quarter of a second after the title finishes". This resolves the second into the first, and re-resolves it when an earlier clip changes length.
|
|
4070
|
+
|
|
4071
|
+
ACTIONS:
|
|
4072
|
+
- place: thread a whole run of clips, each resolved against the ones before it. Returns { placed: [{start, end, duration}], labels, duration }.
|
|
4073
|
+
- resolve: answer a single position spec against a context you supply.
|
|
4074
|
+
- list_forms: the accepted grammar, each form with what it means.
|
|
4075
|
+
|
|
4076
|
+
POSITION FORMS:
|
|
4077
|
+
- 3 — exactly 3s from the start of the timeline
|
|
4078
|
+
- "<" / ">" — when the previous clip STARTED / ENDED
|
|
4079
|
+
- "<2" / ">2" — 2s after the previous clip started / ended
|
|
4080
|
+
- "+=1" / "-=1" — 1s after the timeline ends (a gap) / 1s before it (an overlap)
|
|
4081
|
+
- "intro" / "intro+=0.5" — at a label, or half a second after it
|
|
4082
|
+
- omitted — append at the end, which is what appending has always meant
|
|
4083
|
+
|
|
4084
|
+
THE ONE THING TO GET RIGHT: a percentage means different things in different forms.
|
|
4085
|
+
- "-=25%" and "+=25%" scale against the clip BEING INSERTED — "overlap the previous clip by a quarter of MY length".
|
|
4086
|
+
- "<25%" and ">25%" scale against the PREVIOUS clip — "start a quarter of the way THROUGH it".
|
|
4087
|
+
They agree only when the two clips are the same length, which is rarely, and getting it backwards yields timings that look almost right.
|
|
4088
|
+
|
|
4089
|
+
A label names the moment a clip STARTS, so "label+=0" lands on the clip rather than skipping it. The timeline only ever grows — placing something early with "-=" never pulls the end back.
|
|
4090
|
+
|
|
4091
|
+
EXAMPLE:
|
|
4092
|
+
{
|
|
4093
|
+
"action": "place",
|
|
4094
|
+
"clips": [
|
|
4095
|
+
{ "id": "title", "duration": 2, "label": "intro" },
|
|
4096
|
+
{ "id": "subtitle", "duration": 4, "position": "-=25%" },
|
|
4097
|
+
{ "id": "cta", "duration": 1, "position": "intro+=0.5" }
|
|
4098
|
+
]
|
|
4099
|
+
}`,
|
|
4100
|
+
inputSchema: {
|
|
4101
|
+
type: 'object',
|
|
4102
|
+
properties: {
|
|
4103
|
+
action: {
|
|
4104
|
+
type: 'string',
|
|
4105
|
+
enum: ['place', 'resolve', 'list_forms'],
|
|
4106
|
+
description: 'place (a run of clips), resolve (one position), or list_forms (default: place)',
|
|
4107
|
+
},
|
|
4108
|
+
clips: {
|
|
4109
|
+
type: 'array',
|
|
4110
|
+
items: {
|
|
4111
|
+
type: 'object',
|
|
4112
|
+
properties: {
|
|
4113
|
+
id: { type: 'string', description: 'Your own name for the clip — echoed back on the placement' },
|
|
4114
|
+
duration: { type: 'number', description: 'How long the clip runs, in seconds' },
|
|
4115
|
+
position: {
|
|
4116
|
+
anyOf: [{ type: 'number' }, { type: 'string' }],
|
|
4117
|
+
description: 'Where it starts. Omit to append at the end of the run so far.',
|
|
4118
|
+
},
|
|
4119
|
+
label: { type: 'string', description: 'Name this moment for later clips to position against. Marks where the clip STARTS.' },
|
|
4120
|
+
},
|
|
4121
|
+
required: ['duration'],
|
|
4122
|
+
},
|
|
4123
|
+
description: 'place: the run of clips, in order',
|
|
4124
|
+
},
|
|
4125
|
+
position: {
|
|
4126
|
+
anyOf: [{ type: 'number' }, { type: 'string' }],
|
|
4127
|
+
description: 'resolve: the single position spec to resolve',
|
|
4128
|
+
},
|
|
4129
|
+
context: {
|
|
4130
|
+
type: 'object',
|
|
4131
|
+
properties: {
|
|
4132
|
+
timelineEnd: { type: 'number', description: 'Where the timeline currently ends — what "+=1" counts from' },
|
|
4133
|
+
prevStart: { type: 'number', description: 'Start of the most recent clip — what "<" means' },
|
|
4134
|
+
prevEnd: { type: 'number', description: 'End of the most recent clip — what ">" means' },
|
|
4135
|
+
insertDuration: { type: 'number', description: 'Duration of the clip being placed — the basis for a "+=25%" percentage' },
|
|
4136
|
+
},
|
|
4137
|
+
description: 'resolve: what the spec is measured against',
|
|
4138
|
+
},
|
|
4139
|
+
labels: {
|
|
4140
|
+
type: 'object',
|
|
4141
|
+
additionalProperties: { type: 'number' },
|
|
4142
|
+
description: 'Named moments in seconds, addressable as "name" or "name+=0.5"',
|
|
4143
|
+
},
|
|
4144
|
+
startAt: { type: 'number', description: 'place: where the run begins (default 0)' },
|
|
4145
|
+
},
|
|
4146
|
+
},
|
|
4147
|
+
},
|
|
4148
|
+
{
|
|
4149
|
+
name: 'pinepaper_stagger',
|
|
4150
|
+
annotations: {
|
|
4151
|
+
title: 'Stagger',
|
|
4152
|
+
readOnlyHint: false,
|
|
4153
|
+
destructiveHint: false,
|
|
4154
|
+
idempotentHint: true,
|
|
4155
|
+
openWorldHint: false,
|
|
4156
|
+
},
|
|
4157
|
+
description: `Shape a delay across many items. What makes a stagger read as designed rather than as a queue is where it STARTS and how it SPREADS — a grid lighting up outward from the centre, a row converging from both edges.
|
|
4158
|
+
|
|
4159
|
+
Delays are written to the channel the engine and the SMIL exporter already read, so a staggered scene scrubs, exports and restores. Nothing here is playback-only.
|
|
4160
|
+
|
|
4161
|
+
ACTIONS:
|
|
4162
|
+
- apply: write the delays onto real items, in the order you list them (row-major for a grid).
|
|
4163
|
+
- preview: the delays for a count, without touching the canvas.
|
|
4164
|
+
- list_origins: the origins, each with what it means.
|
|
4165
|
+
|
|
4166
|
+
each VS amount — alternatives, not a pair:
|
|
4167
|
+
- each fixes the gap between NEIGHBOURS, so more items make a longer sequence.
|
|
4168
|
+
- amount fixes the TOTAL, so more items crowd together. It overrides each.
|
|
4169
|
+
|
|
4170
|
+
ORIGINS (from):
|
|
4171
|
+
- start — the first item goes first, in order. The plain queue.
|
|
4172
|
+
- end — the last goes first; the sequence runs backwards.
|
|
4173
|
+
- center — spreads outward from the middle of the run or grid.
|
|
4174
|
+
- edges — starts at both ends at once and converges on the middle.
|
|
4175
|
+
- random — a shuffled ORDER, not random delays: every item still lands on one of the same slots, so the rhythm survives. Seeded.
|
|
4176
|
+
from also takes an item index, or [fx, fy] fractions for a grid ([0.5, 0.5] is the centre).
|
|
4177
|
+
|
|
4178
|
+
ease distributes the START TIMES. It is not the items' own animation curve.
|
|
4179
|
+
|
|
4180
|
+
EXAMPLE — a 3x3 grid lighting up from the middle:
|
|
4181
|
+
{ "action": "apply", "itemIds": ["a","b","c","d","e","f","g","h","i"], "opts": { "each": 0.1, "from": "center", "grid": [3, 3] } }`,
|
|
4182
|
+
inputSchema: {
|
|
4183
|
+
type: 'object',
|
|
4184
|
+
properties: {
|
|
4185
|
+
action: {
|
|
4186
|
+
type: 'string',
|
|
4187
|
+
enum: ['apply', 'preview', 'list_origins'],
|
|
4188
|
+
description: 'apply (to items), preview (delays only), or list_origins (default: apply)',
|
|
4189
|
+
},
|
|
4190
|
+
itemIds: {
|
|
4191
|
+
type: 'array',
|
|
4192
|
+
items: { type: 'string' },
|
|
4193
|
+
description: 'apply: registry IDs in the order the stagger should follow (row-major for a grid)',
|
|
4194
|
+
},
|
|
4195
|
+
count: { type: 'number', description: 'preview: how many items to compute delays for' },
|
|
4196
|
+
opts: {
|
|
4197
|
+
type: 'object',
|
|
4198
|
+
properties: {
|
|
4199
|
+
each: { type: 'number', description: 'Seconds between neighbours (default 0.1)' },
|
|
4200
|
+
amount: { type: 'number', description: 'Total seconds for the whole run — overrides each' },
|
|
4201
|
+
from: {
|
|
4202
|
+
anyOf: [
|
|
4203
|
+
{ type: 'string', enum: ['start', 'end', 'center', 'edges', 'random'] },
|
|
4204
|
+
{ type: 'number' },
|
|
4205
|
+
{ type: 'array', items: { type: 'number' } },
|
|
4206
|
+
],
|
|
4207
|
+
description: 'Named origin, an item index, or [fx, fy] fractions for a grid',
|
|
4208
|
+
},
|
|
4209
|
+
grid: { type: 'array', items: { type: 'number' }, description: '[rows, columns] for a 2D stagger' },
|
|
4210
|
+
axis: { type: 'string', enum: ['x', 'y'], description: 'Restrict a grid stagger to one axis' },
|
|
4211
|
+
ease: {
|
|
4212
|
+
type: 'string',
|
|
4213
|
+
enum: ['linear', 'easeIn', 'easeOut', 'easeInOut', 'easeInCubic', 'easeOutCubic'],
|
|
4214
|
+
description: "Curve that distributes the START TIMES — not the items' own animation",
|
|
4215
|
+
},
|
|
4216
|
+
seed: { type: 'number', description: "from: 'random' — same seed, same shuffle (default 1)" },
|
|
4217
|
+
},
|
|
4218
|
+
description: 'Shape of the stagger',
|
|
4219
|
+
},
|
|
4220
|
+
},
|
|
4221
|
+
},
|
|
4222
|
+
},
|
|
4223
|
+
{
|
|
4224
|
+
name: 'pinepaper_flip',
|
|
4225
|
+
annotations: {
|
|
4226
|
+
title: 'Flip (Derived Transition)',
|
|
4227
|
+
readOnlyHint: false,
|
|
4228
|
+
destructiveHint: false,
|
|
4229
|
+
idempotentHint: false,
|
|
4230
|
+
openWorldHint: false,
|
|
4231
|
+
},
|
|
4232
|
+
description: `Animate a layout change WITHOUT describing the motion. Every other animation tool asks you to say what the movement is; this asks the opposite — record where things are, rearrange them however you like, and the transition is derived from the difference.
|
|
4233
|
+
|
|
4234
|
+
That is what makes it usable for changes nobody could enumerate in advance: a re-sort, an auto-layout pass, a filter, a drag.
|
|
4235
|
+
|
|
4236
|
+
THE ORDER MATTERS:
|
|
4237
|
+
1. action: 'record' — capture the current positions.
|
|
4238
|
+
2. Rearrange, with any tools you like (move, align, sort, re-layout).
|
|
4239
|
+
3. action: 'apply' — the motion between the two states is worked out and written.
|
|
4240
|
+
|
|
4241
|
+
Recording after the change, or skipping step 2, produces nothing to animate.
|
|
4242
|
+
|
|
4243
|
+
It writes ordinary keyframes, so the transition scrubs on the timeline, exports through the MP4 / SMIL / Lottie paths, and survives a reload. Only properties that ACTUALLY changed are written — a track that also pinned the unchanged ones would fight anything else animating them, and a flip of position would quietly freeze an item's own pulse.
|
|
4244
|
+
|
|
4245
|
+
Rotation is compared on the shortest arc: 359 to 1 degrees is a two-degree move, not a near-full spin the wrong way.
|
|
4246
|
+
|
|
4247
|
+
READ THE RESULT: 'entered' are items that appeared during the change and were given an entrance rather than popping in. 'left' are items that went away — they cannot be animated out, because they are already gone. Remove things AFTER the flip, not before.
|
|
4248
|
+
|
|
4249
|
+
EXAMPLE:
|
|
4250
|
+
{ "action": "apply", "duration": 0.6, "easing": "easeInOut", "stagger": { "each": 0.05, "from": "center" } }`,
|
|
4251
|
+
inputSchema: {
|
|
4252
|
+
type: 'object',
|
|
4253
|
+
properties: {
|
|
4254
|
+
action: {
|
|
4255
|
+
type: 'string',
|
|
4256
|
+
enum: ['record', 'apply'],
|
|
4257
|
+
description: "'record' before the change, 'apply' after it (default: record)",
|
|
4258
|
+
},
|
|
4259
|
+
itemIds: {
|
|
4260
|
+
type: 'array',
|
|
4261
|
+
items: { type: 'string' },
|
|
4262
|
+
description: 'record: which items to capture (defaults to everything on the item layer)',
|
|
4263
|
+
},
|
|
4264
|
+
duration: { type: 'number', description: 'apply: seconds the transition runs' },
|
|
4265
|
+
easing: { type: 'string', description: 'apply: named easing for the generated keyframes' },
|
|
4266
|
+
enter: { type: 'string', description: 'apply: entrance preset for items that appeared during the change' },
|
|
4267
|
+
stagger: {
|
|
4268
|
+
type: 'object',
|
|
4269
|
+
properties: {
|
|
4270
|
+
each: { type: 'number', description: 'Seconds between neighbours' },
|
|
4271
|
+
amount: { type: 'number', description: 'Total seconds for the whole run — overrides each' },
|
|
4272
|
+
from: {
|
|
4273
|
+
anyOf: [
|
|
4274
|
+
{ type: 'string', enum: ['start', 'end', 'center', 'edges', 'random'] },
|
|
4275
|
+
{ type: 'number' },
|
|
4276
|
+
{ type: 'array', items: { type: 'number' } },
|
|
4277
|
+
],
|
|
4278
|
+
description: 'Named origin, an item index, or [fx, fy] fractions for a grid',
|
|
4279
|
+
},
|
|
4280
|
+
grid: { type: 'array', items: { type: 'number' }, description: '[rows, columns]' },
|
|
4281
|
+
axis: { type: 'string', enum: ['x', 'y'], description: 'Restrict a grid stagger to one axis' },
|
|
4282
|
+
ease: {
|
|
4283
|
+
type: 'string',
|
|
4284
|
+
enum: ['linear', 'easeIn', 'easeOut', 'easeInOut', 'easeInCubic', 'easeOutCubic'],
|
|
4285
|
+
description: 'Curve that distributes the START TIMES',
|
|
4286
|
+
},
|
|
4287
|
+
seed: { type: 'number', description: 'Seed for the random origin' },
|
|
4288
|
+
},
|
|
4289
|
+
description: "apply: stagger the transition across the items, in the items' own order",
|
|
4290
|
+
},
|
|
4291
|
+
},
|
|
4292
|
+
},
|
|
4293
|
+
},
|
|
4294
|
+
{
|
|
4295
|
+
name: 'pinepaper_scene_graph',
|
|
4296
|
+
annotations: {
|
|
4297
|
+
title: 'Scene Graph (Interactive Stories / Quizzes)',
|
|
4298
|
+
readOnlyHint: false,
|
|
4299
|
+
destructiveHint: false,
|
|
4300
|
+
idempotentHint: false,
|
|
4301
|
+
openWorldHint: false,
|
|
4302
|
+
},
|
|
4303
|
+
description: `Create an interactive multi-node story or quiz card graph. Compiles cards, answer buttons, click-to-event routing, exclusive-group mutex visibility, and scoring into native items and relations.
|
|
4304
|
+
|
|
4305
|
+
ACTIONS:
|
|
4306
|
+
- create (default): build the graph on the canvas.
|
|
4307
|
+
- validate: run the same structural check WITHOUT drawing anything. Returns errors, warnings, the nodes reachable from start, and any cycles. Cheap — use it on a graph you generated before committing a canvas full of cards to it.
|
|
4308
|
+
|
|
4309
|
+
HOW IT WORKS:
|
|
4310
|
+
1. Provide a graph with nodes and choices (start node, question cards with answers pointing to other node IDs, end cards with outcome text).
|
|
4311
|
+
2. The engine compiles the card stack, creates panel rectangles and styled text items, wires on_click_fire -> event -> on_event_set_active relations, and binds active card mutex state.
|
|
4312
|
+
3. If scoreItemId is provided, correct answers automatically wire on_event_increment to track score.
|
|
4313
|
+
|
|
4314
|
+
TWO WAYS OUT OF A CARD: 'answers' waits for a click (a quiz). 'next' advances on its own after 'duration' seconds (a linear story beat). Every non-terminal node needs one or the other, or the player is stuck; mark outcome cards kind: 'end'.
|
|
4315
|
+
|
|
4316
|
+
READ THE RESULT: 'failed' counts relations that did not wire. A graph can render completely and still be inert, and that count is the only thing that says so. 'cycles' reports loops — legitimate for a retry, an accidental one is a game that never ends.
|
|
4317
|
+
|
|
4318
|
+
EXAMPLE — Interactive Quiz:
|
|
4319
|
+
{
|
|
4320
|
+
"graph": {
|
|
4321
|
+
"start": "q1",
|
|
4322
|
+
"nodes": [
|
|
4323
|
+
{
|
|
4324
|
+
"id": "q1",
|
|
4325
|
+
"prompt": "Which planet is known as the Red Planet?",
|
|
4326
|
+
"answers": [
|
|
4327
|
+
{ "text": "Mars", "correct": true, "to": "win" },
|
|
4328
|
+
{ "text": "Venus", "to": "lose" }
|
|
4329
|
+
]
|
|
4330
|
+
},
|
|
4331
|
+
{ "id": "win", "kind": "end", "text": "Correct! Mars is rich in iron oxide." },
|
|
4332
|
+
{ "id": "lose", "kind": "end", "text": "Not quite! Mars is the Red Planet." }
|
|
4333
|
+
]
|
|
4334
|
+
},
|
|
4335
|
+
"opts": {
|
|
4336
|
+
"style": { "panelColor": "#1E1B4B", "textColor": "#F8FAFC", "answerColor": "#93C5FD" }
|
|
4337
|
+
}
|
|
4338
|
+
}`,
|
|
4339
|
+
inputSchema: {
|
|
4340
|
+
type: 'object',
|
|
4341
|
+
properties: {
|
|
4342
|
+
action: {
|
|
4343
|
+
type: 'string',
|
|
4344
|
+
enum: ['create', 'validate'],
|
|
4345
|
+
description: "'create' builds it (default) · 'validate' checks it without drawing",
|
|
4346
|
+
},
|
|
4347
|
+
graph: {
|
|
4348
|
+
type: 'object',
|
|
4349
|
+
properties: {
|
|
4350
|
+
start: { type: 'string', description: 'Initial active node id' },
|
|
4351
|
+
nodes: {
|
|
4352
|
+
type: 'array',
|
|
4353
|
+
items: {
|
|
4354
|
+
type: 'object',
|
|
4355
|
+
properties: {
|
|
4356
|
+
id: { type: 'string', description: 'Unique node id' },
|
|
4357
|
+
prompt: { type: 'string', description: 'Prompt text for question cards' },
|
|
4358
|
+
text: { type: 'string', description: 'Text for message/end cards' },
|
|
4359
|
+
kind: { type: 'string', enum: ['node', 'card', 'end'], description: "Card kind: 'node'/'card' (content) or 'end' (terminal)" },
|
|
4360
|
+
answers: {
|
|
4361
|
+
type: 'array',
|
|
4362
|
+
items: {
|
|
4363
|
+
type: 'object',
|
|
4364
|
+
properties: {
|
|
4365
|
+
text: { type: 'string', description: 'Answer button text' },
|
|
4366
|
+
correct: { type: 'boolean', description: 'Is correct answer' },
|
|
4367
|
+
to: { type: 'string', description: 'Destination node id' },
|
|
4368
|
+
},
|
|
4369
|
+
required: ['text', 'to'],
|
|
4370
|
+
},
|
|
4371
|
+
description: 'Interactive answer buttons — the card waits for a click',
|
|
4372
|
+
},
|
|
4373
|
+
next: { type: 'string', description: 'Unconditional destination — the card auto-advances instead of waiting for a click. Use INSTEAD of answers.' },
|
|
4374
|
+
duration: { type: 'number', description: 'Seconds the card holds before `next` fires (default 3). Ignored without `next`.' },
|
|
4375
|
+
outcome: { type: 'string', enum: ['win', 'lose'], description: 'Force a terminal node\u2019s outcome. Without it the engine infers it from the wording, which only works in English.' },
|
|
4376
|
+
},
|
|
4377
|
+
required: ['id'],
|
|
4378
|
+
},
|
|
4379
|
+
description: 'List of graph nodes',
|
|
4380
|
+
},
|
|
4381
|
+
},
|
|
4382
|
+
required: ['nodes'],
|
|
4383
|
+
description: 'Scene graph definition',
|
|
4384
|
+
},
|
|
4385
|
+
opts: {
|
|
4386
|
+
type: 'object',
|
|
4387
|
+
properties: {
|
|
4388
|
+
width: { type: 'number', description: 'Layout width' },
|
|
4389
|
+
height: { type: 'number', description: 'Layout height' },
|
|
4390
|
+
centerX: { type: 'number', description: 'Center X coordinate' },
|
|
4391
|
+
centerY: { type: 'number', description: 'Center Y coordinate' },
|
|
4392
|
+
groupName: { type: 'string', description: 'Seed / prefix for IDs' },
|
|
4393
|
+
scoreItemId: { type: 'string', description: 'Scoreboard item ID to increment on correct answers' },
|
|
4394
|
+
subject: { type: 'string', description: "Theme of the piece, folded into each card's text-effect selection (e.g. 'space exploration')" },
|
|
4395
|
+
plain: { type: 'boolean', description: 'Plain mode without entrance/text effects' },
|
|
4396
|
+
style: {
|
|
4397
|
+
type: 'object',
|
|
4398
|
+
properties: {
|
|
4399
|
+
panelColor: { type: 'string', description: 'Card panel background color' },
|
|
4400
|
+
textColor: { type: 'string', description: 'Card text color' },
|
|
4401
|
+
answerColor: { type: 'string', description: 'Answer button text color' },
|
|
4402
|
+
headlineSize: { type: 'number', description: 'Prompt font size' },
|
|
4403
|
+
bodySize: { type: 'number', description: 'Answer/body font size' },
|
|
4404
|
+
},
|
|
4405
|
+
},
|
|
4406
|
+
},
|
|
4407
|
+
description: 'Layout and styling options',
|
|
4408
|
+
},
|
|
4409
|
+
},
|
|
4410
|
+
required: ['graph'],
|
|
4411
|
+
},
|
|
4412
|
+
},
|
|
3337
4413
|
// ---------------------------------------------------------------------------
|
|
3338
4414
|
// LAYER 4 — COMPOSITIONS: SELECTION, TRANSFORM & HISTORY TOOLS
|
|
3339
4415
|
// ---------------------------------------------------------------------------
|
|
@@ -3464,6 +4540,11 @@ flags as a badly-composed scene, and they do not survive an artboard change.`,
|
|
|
3464
4540
|
inputSchema: {
|
|
3465
4541
|
type: 'object',
|
|
3466
4542
|
properties: {
|
|
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.' },
|
|
4544
|
+
level: { type: 'number', description: 'Craft level 1-4 within the register: sketch, competent, refined, art-directed.' },
|
|
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." },
|
|
4546
|
+
stitch: { type: 'string', enum: [...THREAD_STITCHES], description: 'Which stitch when medium is thread (default longAndShort) — the same six pinepaper_design_medium offers.' },
|
|
4547
|
+
stitchBudget: { type: 'number', description: 'Total marks across the composition (default 6000); the stitch scales to fit rather than the fill being cut short.' },
|
|
3467
4548
|
action: { type: 'string', enum: ['list_patterns', 'list_treatments', 'apply', 'set_treatment'] },
|
|
3468
4549
|
pattern: { type: 'string', description: 'Pattern key from list_patterns' },
|
|
3469
4550
|
itemIds: { type: 'array', items: { type: 'string' }, description: 'Items in slot order' },
|
|
@@ -3566,7 +4647,10 @@ Overrides are the point: an instance can differ from its master and still receiv
|
|
|
3566
4647
|
},
|
|
3567
4648
|
componentKey: { type: 'string', description: 'Which part of the component to override' },
|
|
3568
4649
|
prop: { type: 'string', description: 'Property to override, e.g. fillColor or content' },
|
|
3569
|
-
value: {
|
|
4650
|
+
value: {
|
|
4651
|
+
anyOf: [{ type: 'string' }, { type: 'number' }, { type: 'boolean' }, { type: 'object' }, { type: 'array' }],
|
|
4652
|
+
description: 'Override value',
|
|
4653
|
+
},
|
|
3570
4654
|
overrides: { type: 'object', description: 'Overrides applied at instantiate time' },
|
|
3571
4655
|
},
|
|
3572
4656
|
required: ['action'],
|
|
@@ -4006,6 +5090,88 @@ ACTIONS:
|
|
|
4006
5090
|
required: ['action'],
|
|
4007
5091
|
},
|
|
4008
5092
|
},
|
|
5093
|
+
{
|
|
5094
|
+
name: 'pinepaper_query_capabilities',
|
|
5095
|
+
annotations: {
|
|
5096
|
+
title: 'Query Capabilities',
|
|
5097
|
+
readOnlyHint: true,
|
|
5098
|
+
destructiveHint: false,
|
|
5099
|
+
idempotentHint: true,
|
|
5100
|
+
openWorldHint: false,
|
|
5101
|
+
},
|
|
5102
|
+
description: `Discover and match capabilities across the engine — text styles, character-level text effects, generators, vertex deforms, and behavioral relations.
|
|
5103
|
+
|
|
5104
|
+
ACTIONS:
|
|
5105
|
+
- list: List all available capabilities or filter by kind (style, effect, generator, deform, relation, etc.).
|
|
5106
|
+
- choose: Mood- and context-weighted recommendation based on subject/prompt words and target feeling.
|
|
5107
|
+
- coverage: Report total indexable and describable capabilities across categories.
|
|
5108
|
+
- find: Direct lookup of a capability by key to inspect its description and how to apply it (applyWith).
|
|
5109
|
+
|
|
5110
|
+
MOOD VOCABULARY FOR CHOOSE:
|
|
5111
|
+
- reveal: resolve, reveal, appear, emerge, decode, decrypt, unscramble, form, assemble
|
|
5112
|
+
- triumphant: burst, launch, firework, bloom, spark, explode, shine, glow, celebrate, rise
|
|
5113
|
+
- failure: crumble, fall, collapse, burn, decay, dissolve, break, shatter, sink, error
|
|
5114
|
+
- calm: drift, float, gentle, slow, settle, fade, wave, ripple, pour
|
|
5115
|
+
- technical: binary, matrix, grid, scan, code, digital, circuit, data, print, terminal
|
|
5116
|
+
- energetic: bounce, swarm, spray, shoot, fast, rapid, scatter, slam, volley
|
|
5117
|
+
|
|
5118
|
+
EXAMPLE — Choose effect for a victory card:
|
|
5119
|
+
{
|
|
5120
|
+
"action": "choose",
|
|
5121
|
+
"kind": "effect",
|
|
5122
|
+
"mood": "triumphant",
|
|
5123
|
+
"subject": "celebrate correct answer",
|
|
5124
|
+
"seed": "quiz_q1"
|
|
5125
|
+
}`,
|
|
5126
|
+
inputSchema: {
|
|
5127
|
+
type: 'object',
|
|
5128
|
+
properties: {
|
|
5129
|
+
action: {
|
|
5130
|
+
type: 'string',
|
|
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.",
|
|
5138
|
+
},
|
|
5139
|
+
kind: {
|
|
5140
|
+
anyOf: [{ type: 'string' }, { type: 'array', items: { type: 'string' } }],
|
|
5141
|
+
description: 'Filter by capability kind (style, effect, deform, entrance, animation, generator, collage, palette, mask, cutout, relation)',
|
|
5142
|
+
},
|
|
5143
|
+
mood: {
|
|
5144
|
+
anyOf: [{ type: 'string' }, { type: 'array', items: { type: 'string' } }],
|
|
5145
|
+
description: "Target feeling for choose: reveal, triumphant, failure, calm, technical, energetic",
|
|
5146
|
+
},
|
|
5147
|
+
subject: {
|
|
5148
|
+
type: 'string',
|
|
5149
|
+
description: 'Contextual prompt/subject text to score against capability definitions',
|
|
5150
|
+
},
|
|
5151
|
+
avoid: {
|
|
5152
|
+
anyOf: [{ type: 'string' }, { type: 'array', items: { type: 'string' } }],
|
|
5153
|
+
description: 'Mood keys to penalize — only the six vocabulary keys above are matched, arbitrary words are ignored',
|
|
5154
|
+
},
|
|
5155
|
+
seed: {
|
|
5156
|
+
type: 'string',
|
|
5157
|
+
description: 'Deterministic seed string for reproducible selection',
|
|
5158
|
+
},
|
|
5159
|
+
key: {
|
|
5160
|
+
type: 'string',
|
|
5161
|
+
description: 'Capability key to look up (for find action)',
|
|
5162
|
+
},
|
|
5163
|
+
exclude: {
|
|
5164
|
+
type: 'array',
|
|
5165
|
+
items: { type: 'string' },
|
|
5166
|
+
description: 'Keys to exclude from choose recommendation',
|
|
5167
|
+
},
|
|
5168
|
+
warm: {
|
|
5169
|
+
type: 'boolean',
|
|
5170
|
+
description: 'Load the lazy registries before answering (default true). Generators and the rigging/blending/deform relation rules only exist once touched, so a cold answer omits them — pass false only to cheaply re-read what is already resident.',
|
|
5171
|
+
},
|
|
5172
|
+
},
|
|
5173
|
+
},
|
|
5174
|
+
},
|
|
4009
5175
|
// ---------------------------------------------------------------------------
|
|
4010
5176
|
// DEFORMATION, SPRITE SHEETS, INTERACTION
|
|
4011
5177
|
// ---------------------------------------------------------------------------
|
|
@@ -4428,6 +5594,10 @@ RETURNS:
|
|
|
4428
5594
|
inputSchema: {
|
|
4429
5595
|
type: 'object',
|
|
4430
5596
|
properties: {
|
|
5597
|
+
id: {
|
|
5598
|
+
type: 'string',
|
|
5599
|
+
description: 'Stable ID for the connector. Assign your own and reuse it as `connectorId` in pinepaper_update_connector / pinepaper_remove_connector — creation does not return an ID you can rely on. Without one the engine mints a timestamp-based ID that no caller can predict.',
|
|
5600
|
+
},
|
|
4431
5601
|
sourceItemId: {
|
|
4432
5602
|
type: 'string',
|
|
4433
5603
|
description: 'Registry ID of the source item (where arrow starts)',
|
|
@@ -4521,6 +5691,10 @@ Same as pinepaper_connect: routing, lineColor, lineWidth, lineStyle, headStyle,
|
|
|
4521
5691
|
inputSchema: {
|
|
4522
5692
|
type: 'object',
|
|
4523
5693
|
properties: {
|
|
5694
|
+
id: {
|
|
5695
|
+
type: 'string',
|
|
5696
|
+
description: 'Stable ID for the connector. Assign your own and reuse it as `connectorId` in pinepaper_update_connector / pinepaper_remove_connector — creation does not return an ID you can rely on. Without one the engine mints a timestamp-based ID that no caller can predict.',
|
|
5697
|
+
},
|
|
4524
5698
|
sourceItemId: {
|
|
4525
5699
|
type: 'string',
|
|
4526
5700
|
description: 'Registry ID of the source item',
|
|
@@ -5554,7 +6728,7 @@ USE WHEN:
|
|
|
5554
6728
|
- Celebration effects (confetti)
|
|
5555
6729
|
- Enhancing visual impact (ripple, glow, electric)
|
|
5556
6730
|
|
|
5557
|
-
EFFECTS (15 particle +
|
|
6731
|
+
EFFECTS (15 particle + 7 shader auras):
|
|
5558
6732
|
- sparkle: Glitter/sparkle particles (color, speed, size)
|
|
5559
6733
|
- blast: Explosion burst effect (color, radius, count)
|
|
5560
6734
|
- smoke: Rising smoke plumes (color, speed, size, drift, height, growthRate)
|
|
@@ -5575,6 +6749,11 @@ SHADER AURAS (WebGL2, silhouette-clipped — same tool, routed to the aura syste
|
|
|
5575
6749
|
- heatmap: Thermal shimmer aura (palette 0=Hot 1=Cool 2=Violet)
|
|
5576
6750
|
- liquid_metal: Flowing metallic sheen (palette 0=Chrome 1=Gold 2=Copper)
|
|
5577
6751
|
- gem_smoke: Crystalline smoke wisps
|
|
6752
|
+
- electric_arc: Branching discharge filaments with a hot core and inverse-square bloom. Stepped flicker, not a sine — a stroke is a train of return strokes, which reads far more electric than a smooth pulse.
|
|
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.
|
|
6754
|
+
- rain_veil: Drifting sheets of falling rain across the silhouette.
|
|
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.
|
|
5578
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.
|
|
5579
6758
|
|
|
5580
6759
|
EXAMPLE — bubbles with magenta interior over a blue rim:
|
|
@@ -5587,7 +6766,7 @@ EXAMPLE — gold liquid-metal aura outside a logo:
|
|
|
5587
6766
|
itemId: { type: 'string', description: 'Registry ID of the item' },
|
|
5588
6767
|
effectType: {
|
|
5589
6768
|
type: 'string',
|
|
5590
|
-
enum: ['sparkle', 'blast', 'smoke', 'fire', 'rain', 'snow', 'confetti', 'ripple', 'glow', 'electric', 'bubbles', 'dust', 'fireflies', 'shockwave', 'trail', 'heatmap', 'liquid_metal', 'gem_smoke'],
|
|
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'],
|
|
5591
6770
|
description: 'Type of effect',
|
|
5592
6771
|
},
|
|
5593
6772
|
params: {
|
|
@@ -6068,7 +7247,7 @@ PRIVACY: No screenshots, no tool call arguments, no user content — only struct
|
|
|
6068
7247
|
idempotentHint: false,
|
|
6069
7248
|
openWorldHint: false,
|
|
6070
7249
|
},
|
|
6071
|
-
description: `Start a new agent job for creating animations, videos, graphics, or any visual content. Do
|
|
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.
|
|
6072
7251
|
|
|
6073
7252
|
⚠️ Call ONCE per pipeline. NEVER restart — creates duplicates.
|
|
6074
7253
|
|
|
@@ -6347,7 +7526,7 @@ EXAMPLE — Animated sky scene with timed reveals:
|
|
|
6347
7526
|
// Loop animation
|
|
6348
7527
|
animationType: {
|
|
6349
7528
|
type: 'string',
|
|
6350
|
-
enum: ['pulse', 'rotate', '
|
|
7529
|
+
enum: ['bounce', 'breathe', 'fade', 'glow', 'jelly', 'path', 'pulse', 'rotate', 'scrollDown', 'scrollLeft', 'scrollRight', 'scrollUp', 'shake', 'slideLeftRight', 'slideUpDown', 'swing', 'typewriter', 'wobble'],
|
|
6351
7530
|
description: 'For animate: loop animation type',
|
|
6352
7531
|
},
|
|
6353
7532
|
animationOptions: { type: 'object', description: 'For animate: {speed, amplitude, direction}' },
|
|
@@ -6371,7 +7550,7 @@ EXAMPLE — Animated sky scene with timed reveals:
|
|
|
6371
7550
|
targetId: { type: 'string', description: 'For relation: target item ID or $N' },
|
|
6372
7551
|
relationType: {
|
|
6373
7552
|
type: 'string',
|
|
6374
|
-
enum: ['orbits', 'follows', 'attached_to', 'points_at', 'mirrors', 'parallax', 'animates', 'grows_from', 'staggered_with', '
|
|
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'],
|
|
6375
7554
|
description: 'For relation: type',
|
|
6376
7555
|
},
|
|
6377
7556
|
relationOptions: { type: 'object', description: 'For relation: options' },
|
|
@@ -6386,7 +7565,7 @@ EXAMPLE — Animated sky scene with timed reveals:
|
|
|
6386
7565
|
// Effects
|
|
6387
7566
|
effectType: {
|
|
6388
7567
|
type: 'string',
|
|
6389
|
-
enum: ['sparkle', 'blast', 'smoke', 'fire', 'rain', 'snow', 'confetti', 'ripple', 'glow', 'electric', 'bubbles', 'dust', 'fireflies', 'shockwave', 'trail', 'heatmap', 'liquid_metal', 'gem_smoke'],
|
|
7568
|
+
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'],
|
|
6390
7569
|
description: 'For apply_effect: effect type',
|
|
6391
7570
|
},
|
|
6392
7571
|
effectParams: { type: 'object', description: 'For apply_effect: parameters' },
|
|
@@ -6458,8 +7637,17 @@ EXAMPLES:
|
|
|
6458
7637
|
},
|
|
6459
7638
|
format: {
|
|
6460
7639
|
type: 'string',
|
|
6461
|
-
enum: ['svg', 'png', 'gif', 'mp4', 'webm', 'pdf'],
|
|
6462
|
-
description:
|
|
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.',
|
|
6463
7651
|
},
|
|
6464
7652
|
quality: {
|
|
6465
7653
|
type: 'string',
|
|
@@ -6473,7 +7661,7 @@ EXAMPLES:
|
|
|
6473
7661
|
},
|
|
6474
7662
|
estimateOnly: {
|
|
6475
7663
|
type: 'boolean',
|
|
6476
|
-
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.',
|
|
6477
7665
|
},
|
|
6478
7666
|
duration: {
|
|
6479
7667
|
type: 'number',
|
|
@@ -7223,6 +8411,9 @@ ACTIONS:
|
|
|
7223
8411
|
required: ['action'],
|
|
7224
8412
|
},
|
|
7225
8413
|
},
|
|
8414
|
+
// ---------------------------------------------------------------------------
|
|
8415
|
+
// STITCHCRAFT PROCEDURAL EMBROIDERY & THREAD
|
|
8416
|
+
// ---------------------------------------------------------------------------
|
|
7226
8417
|
];
|
|
7227
8418
|
/**
|
|
7228
8419
|
* Get a tool definition by name
|