@pinepaper.studio/mcp-server 1.6.6 → 1.6.7
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 +20 -1
- package/README.es.md +20 -1
- package/README.fr.md +20 -1
- package/README.hi.md +18 -1
- package/README.ja.md +19 -1
- package/README.ko.md +18 -1
- package/README.md +166 -5
- package/README.pt-BR.md +20 -1
- package/README.zh-CN.md +18 -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/index.d.ts.map +1 -1
- package/dist/index.js +0 -1
- package/dist/index.js.map +1 -1
- package/dist/ontology/design-graph.d.ts.map +1 -1
- package/dist/ontology/design-graph.js +0 -1
- package/dist/ontology/design-graph.js.map +1 -1
- package/dist/ontology/vocabulary.d.ts.map +1 -1
- package/dist/ontology/vocabulary.js +119 -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/definitions.d.ts +1 -1
- package/dist/tools/definitions.d.ts.map +1 -1
- package/dist/tools/definitions.js +906 -34
- package/dist/tools/definitions.js.map +1 -1
- package/dist/tools/handlers.d.ts.map +1 -1
- package/dist/tools/handlers.js +71 -7
- package/dist/tools/handlers.js.map +1 -1
- package/dist/tools/minimal-descriptions.d.ts.map +1 -1
- package/dist/tools/minimal-descriptions.js +11 -2
- package/dist/tools/minimal-descriptions.js.map +1 -1
- package/dist/tools/toolkits.d.ts.map +1 -1
- package/dist/tools/toolkits.js +14 -0
- package/dist/tools/toolkits.js.map +1 -1
- package/dist/types/code-generator.d.ts +46 -2
- package/dist/types/code-generator.d.ts.map +1 -1
- package/dist/types/code-generator.js +1004 -24
- package/dist/types/code-generator.js.map +1 -1
- package/dist/types/schemas.d.ts +2266 -215
- package/dist/types/schemas.d.ts.map +1 -1
- package/dist/types/schemas.js +553 -50
- package/dist/types/schemas.js.map +1 -1
- package/manifest.json +37 -1
- package/package.json +8 -7
|
@@ -73,6 +73,10 @@ Choose based on mood:
|
|
|
73
73
|
─── ITEMS ───
|
|
74
74
|
|
|
75
75
|
Shapes: circle, rectangle, star, ellipse, triangle, polygon, line, arc, path, pentagon, hexagon, diamond, arrow, heart
|
|
76
|
+
Surfaces (render-time, evaluated per pixel — not shapes): shader, field
|
|
77
|
+
shader: a lit surface — water, liquid_metal, heatmap, gem_smoke. Set behind:true to put it UNDER the art, which is what scenery wants.
|
|
78
|
+
field: one expression, many marks — xExpr/yExpr/count, re-evaluated every frame against t.
|
|
79
|
+
Both draw fully in a cloud render; locally they stand in as a flat plate.
|
|
76
80
|
Text: text (content, fontSize, fontFamily, color, fontWeight)
|
|
77
81
|
Dynamic text: set contentType on text items → clock (live time), timer (elapsed), countdown (from N seconds), stopwatch (pauseable)
|
|
78
82
|
All items: opacity, shadowColor, shadowBlur, blendMode, strokeColor, strokeWidth
|
|
@@ -98,7 +102,7 @@ Photos / raster images (on-device, no upload):
|
|
|
98
102
|
|
|
99
103
|
─── ANIMATION (all in batch) ───
|
|
100
104
|
|
|
101
|
-
Loop presets (animate): pulse, rotate,
|
|
105
|
+
Loop presets (animate): bounce, breathe, fade, glow, jelly, path, pulse, rotate, scrollDown, scrollLeft, scrollRight, scrollUp, shake, slideLeftRight, slideUpDown, swing, typewriter, wobble
|
|
102
106
|
Keyframe (keyframe_animate): [{time, properties, easing}] → opacity, scale, scaleX, scaleY, x, y, rotation, fillColor, strokeColor, fontSize
|
|
103
107
|
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
108
|
Masks (apply_mask): wipeLeft, wipeRight, wipeUp, wipeDown, iris, irisOut, star, heart, curtainHorizontal, curtainVertical, cinematic, diagonalWipe, revealUp, revealDown
|
|
@@ -397,6 +401,24 @@ Items on the canvas can originate from multiple sources — all become first-cla
|
|
|
397
401
|
- pinepaper_import_asset: Pre-made asset import → becomes a full item
|
|
398
402
|
All imported items participate in the relation and animation systems just like natively created items.
|
|
399
403
|
|
|
404
|
+
RENDER-TIME SURFACES — 'shader' and 'field':
|
|
405
|
+
These are not shapes. A shape is built as an item and drawn from its geometry; a
|
|
406
|
+
surface has no geometry — it is evaluated, per pixel or per sample, as each frame
|
|
407
|
+
is drawn. Use them for things a vector cannot be: an open sea, a drifting field
|
|
408
|
+
of marks, a lit material.
|
|
409
|
+
- shader: pick one of water | liquid_metal | heatmap | gem_smoke, give it width
|
|
410
|
+
and height, and set behind:true to place it UNDER the vector art — which is
|
|
411
|
+
what scenery wants, a sea the boats float ON rather than a sheen over them.
|
|
412
|
+
- field: xExpr/yExpr/count draw many marks from one expression, re-evaluated
|
|
413
|
+
every frame against t. rExpr and aExpr vary size and alpha per mark.
|
|
414
|
+
Both draw fully in a cloud render; on a local canvas they stand in as a flat
|
|
415
|
+
plate of their own colour so the layout still reads.
|
|
416
|
+
|
|
417
|
+
LIFETIMES — bornAt and ttl, on any item:
|
|
418
|
+
The only way to build a piece that CUTS between shots. Without them every item
|
|
419
|
+
is on screen for the whole render, which is what makes a long piece read as one
|
|
420
|
+
crowded frame instead of a sequence.
|
|
421
|
+
|
|
400
422
|
WORKFLOW TIP:
|
|
401
423
|
After creating items, use pinepaper_add_relation to animate them (e.g., orbits, follows, attached_to).
|
|
402
424
|
For glossy 3D spheres, use pinepaper_create_glossy_sphere instead. For diagonal stripes, use pinepaper_create_diagonal_stripes.`,
|
|
@@ -405,8 +427,8 @@ For glossy 3D spheres, use pinepaper_create_glossy_sphere instead. For diagonal
|
|
|
405
427
|
properties: {
|
|
406
428
|
itemType: {
|
|
407
429
|
type: 'string',
|
|
408
|
-
enum: ['text', 'circle', 'star', 'rectangle', 'triangle', 'polygon', 'ellipse', 'path', 'line', 'arc', 'pentagon', 'hexagon', 'diamond', 'arrow', 'heart'],
|
|
409
|
-
description:
|
|
430
|
+
enum: ['text', 'circle', 'star', 'rectangle', 'triangle', 'polygon', 'ellipse', 'path', 'line', 'arc', 'pentagon', 'hexagon', 'diamond', 'arrow', 'heart', 'shader', 'field'],
|
|
431
|
+
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
432
|
},
|
|
411
433
|
position: {
|
|
412
434
|
oneOf: [
|
|
@@ -429,7 +451,33 @@ For glossy 3D spheres, use pinepaper_create_glossy_sphere instead. For diagonal
|
|
|
429
451
|
},
|
|
430
452
|
properties: {
|
|
431
453
|
type: 'object',
|
|
432
|
-
description:
|
|
454
|
+
description: `Type-specific properties (content, radius, color, fontSize, etc.)
|
|
455
|
+
|
|
456
|
+
LIFETIME — on ANY item, and the only way to build a piece that CUTS between shots:
|
|
457
|
+
- bornAt: seconds. The item does not exist before this instant.
|
|
458
|
+
- ttl: seconds it lives for. Omit for "until the end".
|
|
459
|
+
Without these every item is on screen for the whole render, which is what makes
|
|
460
|
+
a long piece read as one crowded frame instead of a sequence of shots.
|
|
461
|
+
|
|
462
|
+
SHADER (itemType 'shader') — a lit surface, drawn per pixel:
|
|
463
|
+
- shader: 'water' (open sea, lit swell) | 'liquid_metal' (chrome) | 'heatmap' (thermal halo) | 'gem_smoke' (smoke) | 'electric_arc' (branching discharge, stepped flicker) | 'vortex' (hollow bright RING, sheared by differential rotation) | 'rain_veil' | 'caustics'
|
|
464
|
+
- width, height: the quad it fills
|
|
465
|
+
- shaderIntensity: 0..2, the effect's amplitude (water: chop)
|
|
466
|
+
- shaderPalette: 0 day | 1 low sun | 2 night
|
|
467
|
+
- behind: true draws it UNDER the vector art, which is what scenery needs —
|
|
468
|
+
a sea the boats float ON rather than a sheen laid over them. Default false.
|
|
469
|
+
|
|
470
|
+
FIELD (itemType 'field') — one expression, many marks. The generative form:
|
|
471
|
+
a whole surface as a function of the sample index and the clock, rather than
|
|
472
|
+
thousands of authored items.
|
|
473
|
+
- xExpr, yExpr: expressions for each mark's position
|
|
474
|
+
- rExpr, aExpr: optional radius and alpha per mark. A radius <= 0 is skipped,
|
|
475
|
+
so subtracting a threshold draws only the crests of a wave.
|
|
476
|
+
- count: how many marks (up to 200000)
|
|
477
|
+
- Variables in scope: i (0..count-1), n (count), t (seconds), u (i/n), pi, e, tau.
|
|
478
|
+
Functions: sin cos tan asin acos atan sinh cosh tanh exp ln log10 log2 sqrt
|
|
479
|
+
cbrt abs floor ceil round sign atan2 min max pow hypot, and % for modulo.
|
|
480
|
+
- Example — a rolling swell: xExpr "(i % 120) * 8", yExpr "400 + sin((i % 120) * 0.4 - t * 2) * 12", count 3600`,
|
|
433
481
|
additionalProperties: true,
|
|
434
482
|
},
|
|
435
483
|
data: {
|
|
@@ -443,7 +491,7 @@ For glossy 3D spheres, use pinepaper_create_glossy_sphere instead. For diagonal
|
|
|
443
491
|
},
|
|
444
492
|
animationType: {
|
|
445
493
|
type: 'string',
|
|
446
|
-
description: "Animation type to apply on creation (e.g., 'pulse', 'bounce', 'rotate', 'fade', '
|
|
494
|
+
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
495
|
},
|
|
448
496
|
animationSpeed: {
|
|
449
497
|
type: 'number',
|
|
@@ -509,6 +557,7 @@ MODIFIABLE PROPERTIES:
|
|
|
509
557
|
- shadowBlur: Shadow blur radius
|
|
510
558
|
- shadowOffset: Shadow offset [x, y]
|
|
511
559
|
- blendMode: Blend mode (normal, multiply, screen, overlay, etc.)
|
|
560
|
+
- 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
561
|
|
|
513
562
|
GRADIENT OBJECT FORMAT:
|
|
514
563
|
{type: "linear"|"radial", stops: [{color: "#fff", offset: 0}, {color: "#000", offset: 1}], origin: [x,y], destination: [x,y]}
|
|
@@ -734,6 +783,56 @@ EXAMPLES:
|
|
|
734
783
|
// ---------------------------------------------------------------------------
|
|
735
784
|
// LAYER 1 — ATOMS: IMPORT TOOLS
|
|
736
785
|
// ---------------------------------------------------------------------------
|
|
786
|
+
{
|
|
787
|
+
name: 'pinepaper_import_motion_capture',
|
|
788
|
+
annotations: {
|
|
789
|
+
title: 'Import Motion Capture (BVH)',
|
|
790
|
+
readOnlyHint: false,
|
|
791
|
+
destructiveHint: false,
|
|
792
|
+
idempotentHint: false,
|
|
793
|
+
openWorldHint: true,
|
|
794
|
+
},
|
|
795
|
+
description: `Import a BVH motion-capture clip (CMU, Mixamo) as an animated skeleton, or retarget one onto a rig that already exists.
|
|
796
|
+
|
|
797
|
+
TWO MODES, and picking the right one is the whole decision:
|
|
798
|
+
- mode:'import' → builds a NEW skeleton shaped like the capture file. Use when there is no character yet.
|
|
799
|
+
- 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.
|
|
800
|
+
|
|
801
|
+
USE WHEN:
|
|
802
|
+
- The user supplies a .bvh clip, or asks for real captured motion rather than a simple animationType
|
|
803
|
+
- A rigged character should perform a walk / run / gesture
|
|
804
|
+
- Simple animation (pulse, bounce, rotate) is too coarse for the request
|
|
805
|
+
|
|
806
|
+
DO NOT USE FOR:
|
|
807
|
+
- Simple property animation → pinepaper_animate
|
|
808
|
+
- Timed keyframe tracks you author yourself → pinepaper_keyframe_animate
|
|
809
|
+
|
|
810
|
+
RESULT:
|
|
811
|
+
- import → { skeletonId, bones, poses, duration }
|
|
812
|
+
- retarget → { skeletonId, matched, unmatchedSource, unmatchedTarget }
|
|
813
|
+
|
|
814
|
+
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.
|
|
815
|
+
|
|
816
|
+
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.`,
|
|
817
|
+
inputSchema: {
|
|
818
|
+
type: 'object',
|
|
819
|
+
properties: {
|
|
820
|
+
bvh: { type: 'string', description: 'BVH clip text.' },
|
|
821
|
+
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'." },
|
|
822
|
+
skeletonId: { type: 'string', description: "Rig to drive. REQUIRED when mode='retarget'." },
|
|
823
|
+
fps: { type: 'number', description: 'Pose sampling rate; 0 keeps every frame. Default 15.' },
|
|
824
|
+
height: { type: 'number', description: 'Skeleton height on canvas in px (import mode). Default 320.' },
|
|
825
|
+
position: {
|
|
826
|
+
type: 'object',
|
|
827
|
+
description: 'Where the ROOT joint stands (import mode).',
|
|
828
|
+
properties: { x: { type: 'number' }, y: { type: 'number' } },
|
|
829
|
+
},
|
|
830
|
+
name: { type: 'string', description: 'Name for the created skeleton (import mode).' },
|
|
831
|
+
boneMap: { type: 'object', description: "BVH edge name → this rig's bone name; overrides the alias table.", additionalProperties: { type: 'string' } },
|
|
832
|
+
},
|
|
833
|
+
required: ['bvh'],
|
|
834
|
+
},
|
|
835
|
+
},
|
|
737
836
|
{
|
|
738
837
|
name: 'pinepaper_import_svg',
|
|
739
838
|
annotations: {
|
|
@@ -1349,7 +1448,10 @@ EXAMPLES:
|
|
|
1349
1448
|
y: { type: 'number', description: 'Canvas origin y (default: canvas center)' },
|
|
1350
1449
|
flipY: { type: 'boolean', description: 'Math y-up → screen y-down (default true)' },
|
|
1351
1450
|
style: { type: 'object', description: 'Path style forwarded to create (strokeColor, strokeWidth, fillColor, …)' },
|
|
1352
|
-
warp: {
|
|
1451
|
+
warp: {
|
|
1452
|
+
anyOf: [{ type: 'object' }, { type: 'array', items: { type: 'object' } }],
|
|
1453
|
+
description: 'Chained parametric warp(s): { dx, dy } expressions of x, y, t — one object or an array',
|
|
1454
|
+
},
|
|
1353
1455
|
},
|
|
1354
1456
|
},
|
|
1355
1457
|
},
|
|
@@ -1384,7 +1486,10 @@ RETURNS: create → { success, action, eventId }; pulse → { success, action, e
|
|
|
1384
1486
|
x: { type: 'number', description: 'Canvas x for the event marker — action create' },
|
|
1385
1487
|
y: { type: 'number', description: 'Canvas y for the event marker — action create' },
|
|
1386
1488
|
eventId: { type: 'string', description: "Event id to fire — required for action 'pulse'" },
|
|
1387
|
-
payload: {
|
|
1489
|
+
payload: {
|
|
1490
|
+
anyOf: [{ type: 'string' }, { type: 'number' }, { type: 'boolean' }, { type: 'object' }, { type: 'array' }],
|
|
1491
|
+
description: "Optional value forwarded to listeners — action 'pulse'",
|
|
1492
|
+
},
|
|
1388
1493
|
},
|
|
1389
1494
|
required: ['action'],
|
|
1390
1495
|
},
|
|
@@ -1798,7 +1903,10 @@ EXAMPLE: { action: 'apply_style', itemId: 'title_1', styleKey: 'arcade', fontFam
|
|
|
1798
1903
|
action: { type: 'string', enum: ['apply_style', 'set_font_axes', 'list_styles'], description: 'Text style operation' },
|
|
1799
1904
|
itemId: { type: 'string', description: 'Text item id — apply_style / set_font_axes.' },
|
|
1800
1905
|
styleKey: { type: 'string', description: 'apply_style: style name from list_styles.' },
|
|
1801
|
-
palette: {
|
|
1906
|
+
palette: {
|
|
1907
|
+
anyOf: [{ type: 'string' }, { type: 'array', items: { type: 'string' } }],
|
|
1908
|
+
description: 'apply_style: named colourway (string) or explicit color array.',
|
|
1909
|
+
},
|
|
1802
1910
|
variant: { type: 'number', description: 'apply_style: variation index within the style.' },
|
|
1803
1911
|
content: { type: 'string', description: 'apply_style: replace the text while styling.' },
|
|
1804
1912
|
fontFamily: { type: 'string', description: "apply_style: font override; 'suggested' = the face the style was designed around." },
|
|
@@ -1808,6 +1916,131 @@ EXAMPLE: { action: 'apply_style', itemId: 'title_1', styleKey: 'arcade', fontFam
|
|
|
1808
1916
|
required: ['action'],
|
|
1809
1917
|
},
|
|
1810
1918
|
},
|
|
1919
|
+
{
|
|
1920
|
+
name: 'pinepaper_design_medium',
|
|
1921
|
+
annotations: {
|
|
1922
|
+
title: 'Design Medium (What Makes The Marks)',
|
|
1923
|
+
readOnlyHint: false,
|
|
1924
|
+
destructiveHint: false,
|
|
1925
|
+
idempotentHint: false,
|
|
1926
|
+
openWorldHint: false,
|
|
1927
|
+
},
|
|
1928
|
+
description: `The THIRD design axis: the MEDIUM — what physically makes the marks.
|
|
1929
|
+
|
|
1930
|
+
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.
|
|
1931
|
+
|
|
1932
|
+
READ THIS BEFORE PROMISING A MEDIUM. Naming a medium does not create its marks. Every medium declares a FIDELITY:
|
|
1933
|
+
- native — the characteristic marks ARE vector geometry (vector, thread, ink, cutPaper)
|
|
1934
|
+
- stylised — a recognisable impression; the limitation is stated on the entry (charcoal, oil)
|
|
1935
|
+
- absent — no vertex-level expression; the tool REFUSES with the reason instead of producing flat shapes in its colours (encaustic)
|
|
1936
|
+
|
|
1937
|
+
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.
|
|
1938
|
+
|
|
1939
|
+
ACTIONS:
|
|
1940
|
+
- 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).
|
|
1941
|
+
- resolve: { medium } → { ok, fidelity, reason, markMaker }. ok:false with a reason is a real answer — pass it on rather than trying anyway.
|
|
1942
|
+
- list_stitches: the 4 thread stitches with their params.
|
|
1943
|
+
- 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.
|
|
1944
|
+
- apply_hatch: rule an item with hatching. Same deal — silhouette is the region, source is hidden, re-hatching replaces.
|
|
1945
|
+
- list_flow_fields: the 7 fields that bend a hatch line.
|
|
1946
|
+
- list_hatch_options: the hatch defaults.
|
|
1947
|
+
|
|
1948
|
+
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.
|
|
1949
|
+
|
|
1950
|
+
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.
|
|
1951
|
+
|
|
1952
|
+
An unknown stitch is refused rather than defaulted, and re-stitching an item REPLACES its previous stitching instead of stacking.
|
|
1953
|
+
|
|
1954
|
+
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.
|
|
1955
|
+
|
|
1956
|
+
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.
|
|
1957
|
+
|
|
1958
|
+
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.
|
|
1959
|
+
|
|
1960
|
+
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.
|
|
1961
|
+
|
|
1962
|
+
EXAMPLE — a hatched shadow side, drawn rather than printed:
|
|
1963
|
+
{ "action": "apply_hatch", "itemId": "item_3", "distance": 3, "angle": 30, "flowField": "hand", "rand": 0.2 }
|
|
1964
|
+
|
|
1965
|
+
EXAMPLE — a stitched leaf:
|
|
1966
|
+
{ "action": "apply_thread", "itemId": "item_3", "stitch": "longAndShort",
|
|
1967
|
+
"field": { "kind": "spine", "spine": [{"x":400,"y":200},{"x":400,"y":520}] },
|
|
1968
|
+
"stitchLen": 26, "variance": 0.45 }`,
|
|
1969
|
+
inputSchema: {
|
|
1970
|
+
type: 'object',
|
|
1971
|
+
properties: {
|
|
1972
|
+
action: { type: 'string', enum: ['list_media', 'resolve', 'list_stitches', 'apply_thread', 'apply_hatch', 'list_flow_fields', 'list_hatch_options'], description: 'Medium operation' },
|
|
1973
|
+
medium: { type: 'string', description: 'resolve: vector | thread | ink | cutPaper | charcoal | oil | encaustic' },
|
|
1974
|
+
itemId: { type: 'string', description: 'apply_thread / apply_hatch: a closed path, compound path, or a group of them.' },
|
|
1975
|
+
stitch: { type: 'string', enum: ['longAndShort', 'satin', 'seed', 'stem'], description: 'apply_thread: default longAndShort.' },
|
|
1976
|
+
field: { type: 'object', description: "apply_thread: { kind: 'radial'|'spine'|'constant', cx, cy, angle, spine[], across } — default radial from the shape centre." },
|
|
1977
|
+
stitchLen: { type: 'number', description: 'apply_thread: nominal stitch length px (default 18).' },
|
|
1978
|
+
rowGap: { type: 'number', description: 'apply_thread: row spacing px; defaults from thread width so rows abut.' },
|
|
1979
|
+
variance: { type: 'number', description: 'apply_thread: length jitter 0..1 (default 0.35) — the long-and-short shading.' },
|
|
1980
|
+
width: { type: 'number', description: 'apply_thread: thread width px (default 2).' },
|
|
1981
|
+
sheen: { type: 'boolean', description: 'apply_thread: highlight along each stitch (default true).' },
|
|
1982
|
+
color: { type: 'string', description: "apply_thread: thread colour (defaults to the item's fill)." },
|
|
1983
|
+
seed: { type: 'number', description: 'apply_thread: PRNG seed (default 1) — same seed stitches the same way.' },
|
|
1984
|
+
count: { type: 'number', description: 'apply_thread: stitch count for seed/satin.' },
|
|
1985
|
+
distance: { type: 'number', description: 'apply_hatch: line spacing px (default 6). THE VALUE CONTROL — 6 vs 3 is light vs dark.' },
|
|
1986
|
+
angle: { type: 'number', description: 'apply_hatch: ruling angle in DEGREES (default 45).' },
|
|
1987
|
+
gradient: { type: 'number', description: 'apply_hatch: 0..1 spacing growth per line (default 0, even) — a shaded ramp rather than a flat tone.' },
|
|
1988
|
+
rand: { type: 'number', description: 'apply_hatch: jitter as a fraction of distance (default 0) — takes the mechanical evenness off.' },
|
|
1989
|
+
continuous: { type: 'boolean', description: 'apply_hatch: join the lines into ONE serpentine path (default false).' },
|
|
1990
|
+
flowField: {
|
|
1991
|
+
type: 'string',
|
|
1992
|
+
enum: ['hand', 'curved', 'zigzag', 'waves', 'seabed', 'spiral', 'columns'],
|
|
1993
|
+
description: 'apply_hatch: bend each line along a flow field — what makes it read as drawn rather than printed.',
|
|
1994
|
+
},
|
|
1995
|
+
t: { type: 'number', description: 'apply_hatch: flow-field time offset — advances the field without changing the seed.' },
|
|
1996
|
+
},
|
|
1997
|
+
required: ['action'],
|
|
1998
|
+
},
|
|
1999
|
+
},
|
|
2000
|
+
{
|
|
2001
|
+
name: 'pinepaper_text_effect',
|
|
2002
|
+
annotations: {
|
|
2003
|
+
title: 'Text Effects (37 Character-Level Animations)',
|
|
2004
|
+
readOnlyHint: false,
|
|
2005
|
+
destructiveHint: true,
|
|
2006
|
+
idempotentHint: false,
|
|
2007
|
+
openWorldHint: false,
|
|
2008
|
+
},
|
|
2009
|
+
description: `Animate text CHARACTER BY CHARACTER — 37 effects (terminaltexteffects' vocabulary, reimplemented from source).
|
|
2010
|
+
|
|
2011
|
+
⚠️ 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.
|
|
2012
|
+
|
|
2013
|
+
ACTIONS:
|
|
2014
|
+
- list: {} — the 37 effects as {key, label, definition}. Call this first; effect keys come from here.
|
|
2015
|
+
- apply: { itemId, effect, duration?, seed?, gradient?, gradientStops?, gradientDirection?, gradientSteps?, keepSource?, options? } — explode and animate.
|
|
2016
|
+
|
|
2017
|
+
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.
|
|
2018
|
+
|
|
2019
|
+
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.
|
|
2020
|
+
|
|
2021
|
+
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.
|
|
2022
|
+
|
|
2023
|
+
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.
|
|
2024
|
+
|
|
2025
|
+
EXAMPLE: { action: 'apply', itemId: 'title_1', effect: 'decrypt', duration: 3, gradientStops: ['#00ff9c', '#0066ff'] }`,
|
|
2026
|
+
inputSchema: {
|
|
2027
|
+
type: 'object',
|
|
2028
|
+
properties: {
|
|
2029
|
+
action: { type: 'string', enum: ['apply', 'list'], description: 'Text effect operation' },
|
|
2030
|
+
itemId: { type: 'string', description: 'apply: the text item to animate (must have non-whitespace content).' },
|
|
2031
|
+
effect: { type: 'string', description: "apply: effect key from 'list'." },
|
|
2032
|
+
duration: { type: 'number', description: 'apply: seconds for the whole effect (default 2.5); ends at rest.' },
|
|
2033
|
+
seed: { type: 'number', description: 'apply: PRNG seed (default 1) — same seed reproduces the animation exactly.' },
|
|
2034
|
+
gradient: { type: 'boolean', description: "apply: false keeps the text's authored fill. Default true (gradient across the block)." },
|
|
2035
|
+
gradientStops: { type: 'array', items: { type: 'string' }, description: 'apply: hex colors overriding the effect gradient.' },
|
|
2036
|
+
gradientDirection: { type: 'string', enum: ['vertical', 'horizontal', 'radial', 'diagonal'], description: 'apply: gradient axis.' },
|
|
2037
|
+
gradientSteps: { type: 'number', description: 'apply: quantization steps in the gradient ramp.' },
|
|
2038
|
+
keepSource: { type: 'boolean', description: 'apply: keep the original text item — use when relations/keyframes reference its id.' },
|
|
2039
|
+
options: { type: 'object', description: 'apply: per-effect knobs, passed through verbatim.' },
|
|
2040
|
+
},
|
|
2041
|
+
required: ['action'],
|
|
2042
|
+
},
|
|
2043
|
+
},
|
|
1811
2044
|
{
|
|
1812
2045
|
name: 'pinepaper_crop_image',
|
|
1813
2046
|
annotations: {
|
|
@@ -1999,7 +2232,7 @@ ACTIONS:
|
|
|
1999
2232
|
- 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
2233
|
- remove_actor / list_actors / set_actor_pose: { actorId, pose: { x?, z?, angle? } } — the setter a timeline, sequencer or agent drives frame by frame.
|
|
2001
2234
|
- 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 }
|
|
2235
|
+
- add_object: { object: { x, z, height?, color?, metalness?, roughness?, emissiveIntensity? } } (y defaults to sitting ON the terrain) / remove_object: { objectId }
|
|
2003
2236
|
- remove_world: {} — tears the world down; the Paper canvas was always its own layer and is untouched.
|
|
2004
2237
|
|
|
2005
2238
|
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).`,
|
|
@@ -2007,8 +2240,14 @@ RECIPE — a character walking through a forest: create {spec:'forest'} → impo
|
|
|
2007
2240
|
type: 'object',
|
|
2008
2241
|
properties: {
|
|
2009
2242
|
action: { type: 'string', enum: ['create', 'describe', 'configure', 'add_actor', 'remove_actor', 'list_actors', 'set_actor_pose', 'set_camera', 'add_object', 'remove_object', 'remove_world'], description: 'World operation' },
|
|
2010
|
-
spec: {
|
|
2011
|
-
|
|
2243
|
+
spec: {
|
|
2244
|
+
anyOf: [{ type: 'string' }, { type: 'object' }],
|
|
2245
|
+
description: "create: preset id ('forest'|'snowMountain'|'field'|'jungle') or a full spec object.",
|
|
2246
|
+
},
|
|
2247
|
+
character: {
|
|
2248
|
+
anyOf: [{ type: 'boolean' }, { type: 'object' }],
|
|
2249
|
+
description: 'create: include the walkable character (boolean or config).',
|
|
2250
|
+
},
|
|
2012
2251
|
patch: { type: 'object', description: 'configure: partial world spec, validated against describe.' },
|
|
2013
2252
|
actorId: { type: 'string', description: 'Actor id.' },
|
|
2014
2253
|
x: { type: 'number', description: 'add_actor: world x.' },
|
|
@@ -2024,6 +2263,83 @@ RECIPE — a character walking through a forest: create {spec:'forest'} → impo
|
|
|
2024
2263
|
required: ['action'],
|
|
2025
2264
|
},
|
|
2026
2265
|
},
|
|
2266
|
+
{
|
|
2267
|
+
name: 'pinepaper_character',
|
|
2268
|
+
annotations: {
|
|
2269
|
+
title: 'Character (place and direct a figure from the graph)',
|
|
2270
|
+
readOnlyHint: false,
|
|
2271
|
+
idempotentHint: false,
|
|
2272
|
+
},
|
|
2273
|
+
description: `Place a CHARACTER from the design graph and DIRECT it — no geometry, no bones, no poses.
|
|
2274
|
+
|
|
2275
|
+
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.
|
|
2276
|
+
|
|
2277
|
+
{ concept: "pp:Character", at: { x: 470, y: 250 }, height: 300,
|
|
2278
|
+
beats: [ { at: 1.0, channel: "blink" },
|
|
2279
|
+
{ at: 1.8, channel: "say", until: 4.2 },
|
|
2280
|
+
{ at: 5.0, channel: "headTurn", value: -0.8 } ] }
|
|
2281
|
+
|
|
2282
|
+
CHANNELS ARE DECLARED BY THE CONCEPT, not by you. Ask pinepaper_ontology for a concept to see the ones it has. Four kinds exist:
|
|
2283
|
+
- 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.
|
|
2284
|
+
- 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.
|
|
2285
|
+
- shift: every part moved together, for a turn a flat drawing cannot rotate into.
|
|
2286
|
+
- alternate / speak: swaps between drawn states, for shapes that genuinely differ rather than deform.
|
|
2287
|
+
|
|
2288
|
+
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.
|
|
2289
|
+
|
|
2290
|
+
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.
|
|
2291
|
+
|
|
2292
|
+
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.`,
|
|
2293
|
+
inputSchema: {
|
|
2294
|
+
type: 'object',
|
|
2295
|
+
properties: {
|
|
2296
|
+
concept: {
|
|
2297
|
+
type: 'string',
|
|
2298
|
+
description: 'Concept id from the design graph, e.g. "pp:Character" or "pp:Pigeon". Its parts, rig and channels come with it.',
|
|
2299
|
+
},
|
|
2300
|
+
at: {
|
|
2301
|
+
type: 'object',
|
|
2302
|
+
properties: {
|
|
2303
|
+
x: { type: 'number', description: 'X of the figure\'s centre' },
|
|
2304
|
+
y: { type: 'number', description: 'Y of the figure\'s centre' },
|
|
2305
|
+
},
|
|
2306
|
+
description: 'Where the figure stands.',
|
|
2307
|
+
},
|
|
2308
|
+
height: { type: 'number', description: 'Drawn height in pixels. Every amplitude in the graph is a fraction, so the performance scales with it.' },
|
|
2309
|
+
id: { type: 'string', description: 'Prefix for the created items (default: the concept name).' },
|
|
2310
|
+
style: { type: 'string', description: 'Style id for the depiction (default: the concept\'s own).' },
|
|
2311
|
+
variant: { type: 'string', description: 'Depiction variant (default: "default").' },
|
|
2312
|
+
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.' },
|
|
2313
|
+
beats: {
|
|
2314
|
+
type: 'array',
|
|
2315
|
+
description: 'What happens, in order. Channels the concept does not declare are ignored rather than approximated.',
|
|
2316
|
+
items: {
|
|
2317
|
+
type: 'object',
|
|
2318
|
+
properties: {
|
|
2319
|
+
at: { type: 'number', description: 'When, in seconds' },
|
|
2320
|
+
channel: { type: 'string', description: 'A channel the concept declares — blink, say, headTurn, bob, tailFlick…' },
|
|
2321
|
+
until: { type: 'number', description: 'For a channel that occupies a span rather than an instant' },
|
|
2322
|
+
value: { type: 'number', description: 'How far, for a channel that takes a magnitude' },
|
|
2323
|
+
},
|
|
2324
|
+
required: ['at', 'channel'],
|
|
2325
|
+
},
|
|
2326
|
+
},
|
|
2327
|
+
timings: {
|
|
2328
|
+
type: 'array',
|
|
2329
|
+
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.',
|
|
2330
|
+
items: {
|
|
2331
|
+
type: 'object',
|
|
2332
|
+
properties: {
|
|
2333
|
+
at: { type: 'number', description: 'Start, in seconds' },
|
|
2334
|
+
until: { type: 'number', description: 'End, in seconds' },
|
|
2335
|
+
},
|
|
2336
|
+
required: ['at'],
|
|
2337
|
+
},
|
|
2338
|
+
},
|
|
2339
|
+
},
|
|
2340
|
+
required: ['concept'],
|
|
2341
|
+
},
|
|
2342
|
+
},
|
|
2027
2343
|
{
|
|
2028
2344
|
name: 'pinepaper_rigging',
|
|
2029
2345
|
annotations: {
|
|
@@ -2037,6 +2353,19 @@ RECIPE — a character walking through a forest: create {spec:'forest'} → impo
|
|
|
2037
2353
|
|
|
2038
2354
|
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
2355
|
|
|
2356
|
+
POSE MOTION — build a performance out of saved poses:
|
|
2357
|
+
- 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.
|
|
2358
|
+
- 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.
|
|
2359
|
+
- 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.
|
|
2360
|
+
- 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.
|
|
2361
|
+
· 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.
|
|
2362
|
+
· 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.
|
|
2363
|
+
· plan: true returns the seams without touching the skeleton, so you can check the joins first.
|
|
2364
|
+
- 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.
|
|
2365
|
+
- 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.
|
|
2366
|
+
- bake_animation: freeze the whole rig into plain item keyframes for export. Needs items ATTACHED to bones — a bare skeleton has no output.
|
|
2367
|
+
- add_secondary_motion: spring chains for tails, hair, cloth. skin_path: per-vertex deformation of a curved path.
|
|
2368
|
+
|
|
2040
2369
|
ACTIONS:
|
|
2041
2370
|
- create_skeleton: { name?, rootPosition? } → { skeletonId }
|
|
2042
2371
|
- add_bone: { skeletonId, name?, parentBoneId? (omit for root), length? (80), angle? (deg), flexibility? (0 rigid…1), segments? } → { boneId }
|
|
@@ -2054,7 +2383,7 @@ BREAKDOWN POSES (S12): a breakdown keyframe shapes the ARC + SPACING between key
|
|
|
2054
2383
|
inputSchema: {
|
|
2055
2384
|
type: 'object',
|
|
2056
2385
|
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' },
|
|
2386
|
+
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
2387
|
bvhText: { type: 'string', description: 'import_bvh / retarget_bvh: the .bvh file contents.' },
|
|
2059
2388
|
spineJson: { type: 'string', description: 'import_spine: the Spine JSON export, as a string.' },
|
|
2060
2389
|
view: { type: 'string', enum: ['side', 'front'], description: 'import_bvh: 3D→2D projection plane. CMU walks read best from the side (default).' },
|
|
@@ -2065,6 +2394,25 @@ BREAKDOWN POSES (S12): a breakdown keyframe shapes the ARC + SPACING between key
|
|
|
2065
2394
|
itemId: { type: 'string', description: 'Canvas item id — attach_item.' },
|
|
2066
2395
|
chainId: { type: 'string', description: 'IK chain id — set_target_path.' },
|
|
2067
2396
|
name: { type: 'string', description: 'Name — create_skeleton / add_bone / create_ik_chain / save_pose / save_shape_key.' },
|
|
2397
|
+
poseId: { type: 'string', description: 'Saved pose id — load_pose.' },
|
|
2398
|
+
libraryName: { type: 'string', description: 'Stock pose set — load_pose_library. list_pose_libraries for the names.' },
|
|
2399
|
+
boneMap: { type: 'object', description: 'Bone NAME → bone id — load_pose_library. Omit to build it from list_bones.' },
|
|
2400
|
+
poseIdA: { type: 'string', description: 'First pose — interpolate_poses.' },
|
|
2401
|
+
poseIdB: { type: 'string', description: 'Second pose — interpolate_poses.' },
|
|
2402
|
+
t: { type: 'number', description: 'Blend 0=A … 1=B — interpolate_poses.' },
|
|
2403
|
+
sequence: { type: 'array', items: { type: 'object' }, description: 'Pose keys [{ t, pose | angles, ease }] — play_pose_sequence.' },
|
|
2404
|
+
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.' },
|
|
2405
|
+
blend: { type: 'number', description: 'Seam overlap in seconds (default 0.25) — stitch_poses.' },
|
|
2406
|
+
blendSteps: { type: 'number', description: 'Keys emitted across a seam (default 6) — stitch_poses.' },
|
|
2407
|
+
matchPhase: { type: 'boolean', description: 'Enter a cyclic clip at the phase closest to where the last one ended (default true) — stitch_poses.' },
|
|
2408
|
+
plan: { type: 'boolean', description: 'stitch_poses: return the seams WITHOUT installing the result.' },
|
|
2409
|
+
transitionName: { type: 'string', description: 'Named transition — apply_pose_transition.' },
|
|
2410
|
+
poseIdMap: { type: 'object', description: 'Transition pose name → saved pose id — apply_pose_transition.' },
|
|
2411
|
+
boneNames: { type: 'array', items: { type: 'string' }, description: 'Ordered bone names for a spring chain — add_secondary_motion.' },
|
|
2412
|
+
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.' },
|
|
2413
|
+
shapeKeyId: { type: 'string', description: 'Saved shape key id — load_shape_key.' },
|
|
2414
|
+
weight: { type: 'number', description: 'Shape key weight (default 1) — load_shape_key.' },
|
|
2415
|
+
options: { type: 'object', description: 'Pass-through options — auto_* / bake_animation / move_root / skin_path / play_pose_sequence.' },
|
|
2068
2416
|
rootPosition: { type: 'object', properties: { x: { type: 'number' }, y: { type: 'number' } }, description: 'Skeleton root position — create_skeleton.' },
|
|
2069
2417
|
parentBoneId: { type: 'string', description: 'Parent bone id (omit for root) — add_bone.' },
|
|
2070
2418
|
length: { type: 'number', description: 'Bone length px (default 80) — add_bone.' },
|
|
@@ -2079,7 +2427,10 @@ BREAKDOWN POSES (S12): a breakdown keyframe shapes the ARC + SPACING between key
|
|
|
2079
2427
|
strength: { type: 'number', description: 'Chain strength 0..1 (1) — create_ik_chain.' },
|
|
2080
2428
|
poleVector: { type: 'object', properties: { x: { type: 'number' }, y: { type: 'number' } }, description: 'Pole vector hint — create_ik_chain.' },
|
|
2081
2429
|
time: { type: 'number', description: 'Keyframe time s — add_pose_keyframe.' },
|
|
2082
|
-
pose: {
|
|
2430
|
+
pose: {
|
|
2431
|
+
anyOf: [{ type: 'string' }, { type: 'object', additionalProperties: { type: 'number' } }],
|
|
2432
|
+
description: 'Saved pose id (string) OR { boneId: angleDeg } map — add_pose_keyframe.',
|
|
2433
|
+
},
|
|
2083
2434
|
easing: { type: 'string', description: 'Named easing (linear) — add_pose_keyframe.' },
|
|
2084
2435
|
favor: { type: 'number', description: 'Breakdown spacing bias −1..1 — add_pose_keyframe.' },
|
|
2085
2436
|
breakdown: { type: 'boolean', description: 'Mark as breakdown pose — add_pose_keyframe.' },
|
|
@@ -2198,7 +2549,7 @@ USE WHEN:
|
|
|
2198
2549
|
- Building live geometric constructions (GeoGebra-style — dragging an anchor updates the dependent)
|
|
2199
2550
|
|
|
2200
2551
|
RELATION TYPES:
|
|
2201
|
-
- orbits: Circular motion around target (params: radius, speed, direction, phase
|
|
2552
|
+
- 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
2553
|
- follows: Move toward target with smoothing (params: offset, smoothing, delay)
|
|
2203
2554
|
- attached_to: Fixed offset from target (params: offset, inherit_rotation)
|
|
2204
2555
|
- maintains_distance: Stay fixed distance from target (params: distance, strength)
|
|
@@ -2206,6 +2557,35 @@ RELATION TYPES:
|
|
|
2206
2557
|
- mirrors: Mirror position across axis (params: axis, center)
|
|
2207
2558
|
- parallax: Move relative by depth (params: depth, origin)
|
|
2208
2559
|
- bounds_to: Stay within bounds (params: padding, bounce)
|
|
2560
|
+
- anchored_in_world: Pin the item to a 3D world position so it tracks the world camera.
|
|
2561
|
+
- spring_follow: Follow with spring dynamics rather than smoothing — it overshoots and settles.
|
|
2562
|
+
- 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.
|
|
2563
|
+
- wiggle: Continuous procedural noise on the item. Richer than a sine: it does not repeat visibly.
|
|
2564
|
+
- 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.
|
|
2565
|
+
- 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.
|
|
2566
|
+
- tours / synced_to_audio: Move through a sequence of targets; bind playback to an audio track.
|
|
2567
|
+
- connects_to: Diagram edge (params: via, sourcePort, targetPort, label).
|
|
2568
|
+
- 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.
|
|
2569
|
+
- 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.
|
|
2570
|
+
- 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.
|
|
2571
|
+
- 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.
|
|
2572
|
+
|
|
2573
|
+
INTERACTIVE RELATIONS — the state machine lives in the graph.
|
|
2574
|
+
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.
|
|
2575
|
+
|
|
2576
|
+
- 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.
|
|
2577
|
+
- Event reactions (event → item), params in common: property, value, key, by, color, template, delay, timeline.
|
|
2578
|
+
· on_event_set_property / _set_color / _set_visibility / _set_data — write a value.
|
|
2579
|
+
· 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.
|
|
2580
|
+
· on_event_increment (params: property, by) / on_event_toggle — counters and flip-flops.
|
|
2581
|
+
· on_event_fire_after (params: delay, timeline) — chain one beat to the next.
|
|
2582
|
+
· on_event_fire_if — the only reaction that TESTS graph state rather than mutating it; params.predicate is an ExpressionIR program.
|
|
2583
|
+
· on_event_add_relation / on_event_remove_relation — the scene rewires itself.
|
|
2584
|
+
· 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.
|
|
2585
|
+
- 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.
|
|
2586
|
+
- 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.
|
|
2587
|
+
|
|
2588
|
+
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
2589
|
|
|
2210
2590
|
RELATION COMPATIBILITY:
|
|
2211
2591
|
- Universal relations (position-based) work with ALL item types including imported SVGs/images:
|
|
@@ -2270,7 +2650,7 @@ Relations are COMPOSITIONAL - an item can have multiple relations that work toge
|
|
|
2270
2650
|
},
|
|
2271
2651
|
relationType: {
|
|
2272
2652
|
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', '
|
|
2653
|
+
enum: ['orbits', 'follows', 'attached_to', 'maintains_distance', 'points_at', 'mirrors', 'parallax', 'bounds_to', 'anchored_in_world', 'spring_follow', 'repels', 'wiggle', 'animates', 'grows_from', 'staggered_with', 'indicates', 'circumscribes', 'wave_through', 'morphs_to', 'group_morphs_to', 'moves_along_path', 'construction_reveal', 'triggers_animation', 'syncs_with', 'tours', 'synced_to_audio', 'expresses', 'camera_follows', 'camera_animates', 'is_midpoint_of', 'lies_on_line', 'is_centroid_of', 'is_circumcenter_of', 'concentric_with', 'driven_by', 'time_expression', 'on_click_fire', 'on_pointer_enter_fire', 'on_pointer_exit_fire', 'on_key_fire', 'on_event_fire_after', 'on_event_fire_if', 'on_event_add_relation', 'on_event_remove_relation', 'on_event_set_color', 'on_event_set_property', 'on_event_set_property_from_template', 'on_event_set_visibility', 'on_event_set_active', 'on_event_set_data', 'on_event_increment', 'on_event_toggle', 'on_event_store_set', 'on_event_store_increment', 'on_enter_set_color', 'on_enter_set_property', 'on_enter_set_property_from_template', 'on_enter_set_visibility', 'on_enter_set_data', 'on_enter_increment', 'on_enter_toggle', 'on_exit_set_color', 'on_exit_set_property', 'on_exit_set_property_from_template', 'on_exit_set_visibility', 'on_exit_set_data', 'on_exit_increment', 'on_exit_toggle', 'exclusive_group', 'menubar_group', 'restores_from', 'on_top_of', 'below', 'beside', 'inside', 'centered_on', 'aligned_with', 'part_of', 'connects_to', 'attached_to_tail', 'head_points_to'],
|
|
2274
2654
|
description: 'Type of relationship',
|
|
2275
2655
|
},
|
|
2276
2656
|
params: {
|
|
@@ -2304,7 +2684,7 @@ USE WHEN:
|
|
|
2304
2684
|
targetId: { type: 'string', description: 'Target item ID' },
|
|
2305
2685
|
relationType: {
|
|
2306
2686
|
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', '
|
|
2687
|
+
enum: ['orbits', 'follows', 'attached_to', 'maintains_distance', 'points_at', 'mirrors', 'parallax', 'bounds_to', 'anchored_in_world', 'spring_follow', 'repels', 'wiggle', 'animates', 'grows_from', 'staggered_with', 'indicates', 'circumscribes', 'wave_through', 'morphs_to', 'group_morphs_to', 'moves_along_path', 'construction_reveal', 'triggers_animation', 'syncs_with', 'tours', 'synced_to_audio', 'expresses', 'camera_follows', 'camera_animates', 'is_midpoint_of', 'lies_on_line', 'is_centroid_of', 'is_circumcenter_of', 'concentric_with', 'driven_by', 'time_expression', 'on_click_fire', 'on_pointer_enter_fire', 'on_pointer_exit_fire', 'on_key_fire', 'on_event_fire_after', 'on_event_fire_if', 'on_event_add_relation', 'on_event_remove_relation', 'on_event_set_color', 'on_event_set_property', 'on_event_set_property_from_template', 'on_event_set_visibility', 'on_event_set_active', 'on_event_set_data', 'on_event_increment', 'on_event_toggle', 'on_event_store_set', 'on_event_store_increment', 'on_enter_set_color', 'on_enter_set_property', 'on_enter_set_property_from_template', 'on_enter_set_visibility', 'on_enter_set_data', 'on_enter_increment', 'on_enter_toggle', 'on_exit_set_color', 'on_exit_set_property', 'on_exit_set_property_from_template', 'on_exit_set_visibility', 'on_exit_set_data', 'on_exit_increment', 'on_exit_toggle', 'exclusive_group', 'menubar_group', 'restores_from', 'on_top_of', 'below', 'beside', 'inside', 'centered_on', 'aligned_with', 'part_of', 'connects_to', 'attached_to_tail', 'head_points_to'],
|
|
2308
2688
|
description: 'Specific relation type to remove (optional - removes all if not specified)',
|
|
2309
2689
|
},
|
|
2310
2690
|
},
|
|
@@ -2332,7 +2712,7 @@ USE WHEN:
|
|
|
2332
2712
|
itemId: { type: 'string', description: 'Item to query relations for' },
|
|
2333
2713
|
relationType: {
|
|
2334
2714
|
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', '
|
|
2715
|
+
enum: ['orbits', 'follows', 'attached_to', 'maintains_distance', 'points_at', 'mirrors', 'parallax', 'bounds_to', 'anchored_in_world', 'spring_follow', 'repels', 'wiggle', 'animates', 'grows_from', 'staggered_with', 'indicates', 'circumscribes', 'wave_through', 'morphs_to', 'group_morphs_to', 'moves_along_path', 'construction_reveal', 'triggers_animation', 'syncs_with', 'tours', 'synced_to_audio', 'expresses', 'camera_follows', 'camera_animates', 'is_midpoint_of', 'lies_on_line', 'is_centroid_of', 'is_circumcenter_of', 'concentric_with', 'driven_by', 'time_expression', 'on_click_fire', 'on_pointer_enter_fire', 'on_pointer_exit_fire', 'on_key_fire', 'on_event_fire_after', 'on_event_fire_if', 'on_event_add_relation', 'on_event_remove_relation', 'on_event_set_color', 'on_event_set_property', 'on_event_set_property_from_template', 'on_event_set_visibility', 'on_event_set_active', 'on_event_set_data', 'on_event_increment', 'on_event_toggle', 'on_event_store_set', 'on_event_store_increment', 'on_enter_set_color', 'on_enter_set_property', 'on_enter_set_property_from_template', 'on_enter_set_visibility', 'on_enter_set_data', 'on_enter_increment', 'on_enter_toggle', 'on_exit_set_color', 'on_exit_set_property', 'on_exit_set_property_from_template', 'on_exit_set_visibility', 'on_exit_set_data', 'on_exit_increment', 'on_exit_toggle', 'exclusive_group', 'menubar_group', 'restores_from', 'on_top_of', 'below', 'beside', 'inside', 'centered_on', 'aligned_with', 'part_of', 'connects_to', 'attached_to_tail', 'head_points_to'],
|
|
2336
2716
|
description: 'Filter by relation type (optional)',
|
|
2337
2717
|
},
|
|
2338
2718
|
direction: {
|
|
@@ -2483,7 +2863,8 @@ USE WHEN:
|
|
|
2483
2863
|
- "bouncing text" → animationType: bounce
|
|
2484
2864
|
- "fading effect" → animationType: fade
|
|
2485
2865
|
- "wobbling button" → animationType: wobble
|
|
2486
|
-
- "sliding header" → animationType:
|
|
2866
|
+
- "sliding header" → animationType: slideLeftRight (or slideUpDown)
|
|
2867
|
+
- "scrolling banner" → animationType: scrollLeft (also scrollRight/Up/Down)
|
|
2487
2868
|
- "typewriter effect" → animationType: typewriter (text only)
|
|
2488
2869
|
|
|
2489
2870
|
DO NOT USE WHEN:
|
|
@@ -2491,21 +2872,30 @@ DO NOT USE WHEN:
|
|
|
2491
2872
|
- User wants sequential animations ("first fade, then rotate") → Use keyframe animation
|
|
2492
2873
|
- User describes relationships ("orbit around") → Use relations
|
|
2493
2874
|
|
|
2494
|
-
ANIMATION TYPES:
|
|
2875
|
+
ANIMATION TYPES (18 — the list the driver actually switches on):
|
|
2495
2876
|
- pulse: Scale up/down rhythmically
|
|
2877
|
+
- breathe: A slower, softer pulse
|
|
2496
2878
|
- rotate: Continuous rotation
|
|
2879
|
+
- swing: Pendulum rotation about the top
|
|
2497
2880
|
- bounce: Vertical bouncing motion
|
|
2498
|
-
-
|
|
2881
|
+
- jelly: Squash-and-stretch wobble
|
|
2882
|
+
- shake: Rapid small displacement
|
|
2499
2883
|
- wobble: Side-to-side wobbling
|
|
2500
|
-
-
|
|
2501
|
-
-
|
|
2884
|
+
- fade: Opacity cycling
|
|
2885
|
+
- glow: Cycling luminance
|
|
2886
|
+
- slideLeftRight / slideUpDown: Sliding on one axis
|
|
2887
|
+
- scrollLeft / scrollRight / scrollUp / scrollDown: Continuous scroll, wrapping
|
|
2888
|
+
- path: Travel along an assigned path
|
|
2889
|
+
- typewriter: Character-by-character reveal
|
|
2890
|
+
|
|
2891
|
+
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
2892
|
inputSchema: {
|
|
2503
2893
|
type: 'object',
|
|
2504
2894
|
properties: {
|
|
2505
2895
|
itemId: { type: 'string', description: 'Registry ID of the item' },
|
|
2506
2896
|
animationType: {
|
|
2507
2897
|
type: 'string',
|
|
2508
|
-
enum: ['pulse', 'rotate', '
|
|
2898
|
+
enum: ['bounce', 'breathe', 'fade', 'glow', 'jelly', 'path', 'pulse', 'rotate', 'scrollDown', 'scrollLeft', 'scrollRight', 'scrollUp', 'shake', 'slideLeftRight', 'slideUpDown', 'swing', 'typewriter', 'wobble'],
|
|
2509
2899
|
description: 'Type of animation',
|
|
2510
2900
|
},
|
|
2511
2901
|
speed: {
|
|
@@ -2635,15 +3025,45 @@ USE WHEN:
|
|
|
2635
3025
|
- Seeking to specific time
|
|
2636
3026
|
- Controlling animation state
|
|
2637
3027
|
|
|
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
|
|
3028
|
+
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.
|
|
3029
|
+
|
|
3030
|
+
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.
|
|
3031
|
+
|
|
3032
|
+
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.
|
|
3033
|
+
|
|
3034
|
+
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
3035
|
inputSchema: {
|
|
2640
3036
|
type: 'object',
|
|
2641
3037
|
properties: {
|
|
2642
3038
|
action: {
|
|
2643
3039
|
type: 'string',
|
|
2644
|
-
enum: [
|
|
3040
|
+
enum: [
|
|
3041
|
+
'play', 'pause', 'stop', 'seek',
|
|
3042
|
+
'set_time_scale', 'get_time_scale',
|
|
3043
|
+
'get_progress', 'set_progress',
|
|
3044
|
+
'bind_scroll', 'unbind_scroll', 'list_scrub_anchors',
|
|
3045
|
+
],
|
|
2645
3046
|
description: 'Playback action',
|
|
2646
3047
|
},
|
|
3048
|
+
rate: {
|
|
3049
|
+
type: 'number',
|
|
3050
|
+
description: 'set_time_scale: 1 real time, 0.5 half speed, 0 freezes without stopping, negative runs backwards. Export is unaffected.',
|
|
3051
|
+
},
|
|
3052
|
+
progress: {
|
|
3053
|
+
type: 'number',
|
|
3054
|
+
description: 'set_progress: how far through the timeline, 0..1',
|
|
3055
|
+
},
|
|
3056
|
+
scroll: {
|
|
3057
|
+
type: 'object',
|
|
3058
|
+
properties: {
|
|
3059
|
+
elementId: { type: 'string', description: 'DOM id of the element to track (defaults to the canvas)' },
|
|
3060
|
+
start: { type: 'string', description: "Anchor where the range begins, e.g. 'top bottom' — see list_scrub_anchors" },
|
|
3061
|
+
end: { type: 'string', description: "Anchor where the range ends, e.g. 'bottom top'" },
|
|
3062
|
+
scrub: { type: 'number', description: 'Seconds of smoothing so the playhead chases the scroll (default 0.3; 0 snaps)' },
|
|
3063
|
+
range: { type: 'array', items: { type: 'number' }, description: '[from, to] seconds — drive only a sub-range of the timeline' },
|
|
3064
|
+
},
|
|
3065
|
+
description: 'bind_scroll options',
|
|
3066
|
+
},
|
|
2647
3067
|
duration: {
|
|
2648
3068
|
type: 'number',
|
|
2649
3069
|
description: 'Duration for play action',
|
|
@@ -3131,7 +3551,7 @@ EXAMPLE - Bouncing Balls:
|
|
|
3131
3551
|
|
|
3132
3552
|
SUPPORTED ITEM TYPES: text, circle, star, rectangle, triangle, polygon, ellipse, path, line, arc
|
|
3133
3553
|
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,
|
|
3554
|
+
SUPPORTED ANIMATIONS: bounce, breathe, fade, glow, jelly, path, pulse, rotate, scrollDown, scrollLeft, scrollRight, scrollUp, shake, slideLeftRight, slideUpDown, swing, typewriter, wobble`,
|
|
3135
3555
|
inputSchema: {
|
|
3136
3556
|
type: 'object',
|
|
3137
3557
|
properties: {
|
|
@@ -3176,7 +3596,7 @@ SUPPORTED ANIMATIONS: pulse, rotate, bounce, fade, wobble, slide, typewriter`,
|
|
|
3176
3596
|
target: { type: 'string', description: 'Name of target item' },
|
|
3177
3597
|
type: {
|
|
3178
3598
|
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', '
|
|
3599
|
+
enum: ['orbits', 'follows', 'attached_to', 'maintains_distance', 'points_at', 'mirrors', 'parallax', 'bounds_to', 'anchored_in_world', 'spring_follow', 'repels', 'wiggle', 'animates', 'grows_from', 'staggered_with', 'indicates', 'circumscribes', 'wave_through', 'morphs_to', 'group_morphs_to', 'moves_along_path', 'construction_reveal', 'triggers_animation', 'syncs_with', 'tours', 'synced_to_audio', 'expresses', 'camera_follows', 'camera_animates', 'is_midpoint_of', 'lies_on_line', 'is_centroid_of', 'is_circumcenter_of', 'concentric_with', 'driven_by', 'time_expression', 'on_click_fire', 'on_pointer_enter_fire', 'on_pointer_exit_fire', 'on_key_fire', 'on_event_fire_after', 'on_event_fire_if', 'on_event_add_relation', 'on_event_remove_relation', 'on_event_set_color', 'on_event_set_property', 'on_event_set_property_from_template', 'on_event_set_visibility', 'on_event_set_active', 'on_event_set_data', 'on_event_increment', 'on_event_toggle', 'on_event_store_set', 'on_event_store_increment', 'on_enter_set_color', 'on_enter_set_property', 'on_enter_set_property_from_template', 'on_enter_set_visibility', 'on_enter_set_data', 'on_enter_increment', 'on_enter_toggle', 'on_exit_set_color', 'on_exit_set_property', 'on_exit_set_property_from_template', 'on_exit_set_visibility', 'on_exit_set_data', 'on_exit_increment', 'on_exit_toggle', 'exclusive_group', 'menubar_group', 'restores_from', 'on_top_of', 'below', 'beside', 'inside', 'centered_on', 'aligned_with', 'part_of', 'connects_to', 'attached_to_tail', 'head_points_to'],
|
|
3180
3600
|
},
|
|
3181
3601
|
params: {
|
|
3182
3602
|
type: 'object',
|
|
@@ -3196,7 +3616,7 @@ SUPPORTED ANIMATIONS: pulse, rotate, bounce, fade, wobble, slide, typewriter`,
|
|
|
3196
3616
|
target: { type: 'string', description: 'Name of item to animate' },
|
|
3197
3617
|
type: {
|
|
3198
3618
|
type: 'string',
|
|
3199
|
-
enum: ['pulse', 'rotate', '
|
|
3619
|
+
enum: ['bounce', 'breathe', 'fade', 'glow', 'jelly', 'path', 'pulse', 'rotate', 'scrollDown', 'scrollLeft', 'scrollRight', 'scrollUp', 'shake', 'slideLeftRight', 'slideUpDown', 'swing', 'typewriter', 'wobble'],
|
|
3200
3620
|
},
|
|
3201
3621
|
speed: { type: 'number', description: 'Animation speed (default: 1.0)' },
|
|
3202
3622
|
params: {
|
|
@@ -3334,6 +3754,361 @@ EXAMPLE — Slideshow:
|
|
|
3334
3754
|
required: ['action'],
|
|
3335
3755
|
},
|
|
3336
3756
|
},
|
|
3757
|
+
{
|
|
3758
|
+
name: 'pinepaper_sequence',
|
|
3759
|
+
annotations: {
|
|
3760
|
+
title: 'Timeline Sequencing (Relative Positions)',
|
|
3761
|
+
readOnlyHint: true,
|
|
3762
|
+
destructiveHint: false,
|
|
3763
|
+
idempotentHint: true,
|
|
3764
|
+
openWorldHint: false,
|
|
3765
|
+
},
|
|
3766
|
+
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.)
|
|
3767
|
+
|
|
3768
|
+
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.
|
|
3769
|
+
|
|
3770
|
+
ACTIONS:
|
|
3771
|
+
- place: thread a whole run of clips, each resolved against the ones before it. Returns { placed: [{start, end, duration}], labels, duration }.
|
|
3772
|
+
- resolve: answer a single position spec against a context you supply.
|
|
3773
|
+
- list_forms: the accepted grammar, each form with what it means.
|
|
3774
|
+
|
|
3775
|
+
POSITION FORMS:
|
|
3776
|
+
- 3 — exactly 3s from the start of the timeline
|
|
3777
|
+
- "<" / ">" — when the previous clip STARTED / ENDED
|
|
3778
|
+
- "<2" / ">2" — 2s after the previous clip started / ended
|
|
3779
|
+
- "+=1" / "-=1" — 1s after the timeline ends (a gap) / 1s before it (an overlap)
|
|
3780
|
+
- "intro" / "intro+=0.5" — at a label, or half a second after it
|
|
3781
|
+
- omitted — append at the end, which is what appending has always meant
|
|
3782
|
+
|
|
3783
|
+
THE ONE THING TO GET RIGHT: a percentage means different things in different forms.
|
|
3784
|
+
- "-=25%" and "+=25%" scale against the clip BEING INSERTED — "overlap the previous clip by a quarter of MY length".
|
|
3785
|
+
- "<25%" and ">25%" scale against the PREVIOUS clip — "start a quarter of the way THROUGH it".
|
|
3786
|
+
They agree only when the two clips are the same length, which is rarely, and getting it backwards yields timings that look almost right.
|
|
3787
|
+
|
|
3788
|
+
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.
|
|
3789
|
+
|
|
3790
|
+
EXAMPLE:
|
|
3791
|
+
{
|
|
3792
|
+
"action": "place",
|
|
3793
|
+
"clips": [
|
|
3794
|
+
{ "id": "title", "duration": 2, "label": "intro" },
|
|
3795
|
+
{ "id": "subtitle", "duration": 4, "position": "-=25%" },
|
|
3796
|
+
{ "id": "cta", "duration": 1, "position": "intro+=0.5" }
|
|
3797
|
+
]
|
|
3798
|
+
}`,
|
|
3799
|
+
inputSchema: {
|
|
3800
|
+
type: 'object',
|
|
3801
|
+
properties: {
|
|
3802
|
+
action: {
|
|
3803
|
+
type: 'string',
|
|
3804
|
+
enum: ['place', 'resolve', 'list_forms'],
|
|
3805
|
+
description: 'place (a run of clips), resolve (one position), or list_forms (default: place)',
|
|
3806
|
+
},
|
|
3807
|
+
clips: {
|
|
3808
|
+
type: 'array',
|
|
3809
|
+
items: {
|
|
3810
|
+
type: 'object',
|
|
3811
|
+
properties: {
|
|
3812
|
+
id: { type: 'string', description: 'Your own name for the clip — echoed back on the placement' },
|
|
3813
|
+
duration: { type: 'number', description: 'How long the clip runs, in seconds' },
|
|
3814
|
+
position: {
|
|
3815
|
+
anyOf: [{ type: 'number' }, { type: 'string' }],
|
|
3816
|
+
description: 'Where it starts. Omit to append at the end of the run so far.',
|
|
3817
|
+
},
|
|
3818
|
+
label: { type: 'string', description: 'Name this moment for later clips to position against. Marks where the clip STARTS.' },
|
|
3819
|
+
},
|
|
3820
|
+
required: ['duration'],
|
|
3821
|
+
},
|
|
3822
|
+
description: 'place: the run of clips, in order',
|
|
3823
|
+
},
|
|
3824
|
+
position: {
|
|
3825
|
+
anyOf: [{ type: 'number' }, { type: 'string' }],
|
|
3826
|
+
description: 'resolve: the single position spec to resolve',
|
|
3827
|
+
},
|
|
3828
|
+
context: {
|
|
3829
|
+
type: 'object',
|
|
3830
|
+
properties: {
|
|
3831
|
+
timelineEnd: { type: 'number', description: 'Where the timeline currently ends — what "+=1" counts from' },
|
|
3832
|
+
prevStart: { type: 'number', description: 'Start of the most recent clip — what "<" means' },
|
|
3833
|
+
prevEnd: { type: 'number', description: 'End of the most recent clip — what ">" means' },
|
|
3834
|
+
insertDuration: { type: 'number', description: 'Duration of the clip being placed — the basis for a "+=25%" percentage' },
|
|
3835
|
+
},
|
|
3836
|
+
description: 'resolve: what the spec is measured against',
|
|
3837
|
+
},
|
|
3838
|
+
labels: {
|
|
3839
|
+
type: 'object',
|
|
3840
|
+
additionalProperties: { type: 'number' },
|
|
3841
|
+
description: 'Named moments in seconds, addressable as "name" or "name+=0.5"',
|
|
3842
|
+
},
|
|
3843
|
+
startAt: { type: 'number', description: 'place: where the run begins (default 0)' },
|
|
3844
|
+
},
|
|
3845
|
+
},
|
|
3846
|
+
},
|
|
3847
|
+
{
|
|
3848
|
+
name: 'pinepaper_stagger',
|
|
3849
|
+
annotations: {
|
|
3850
|
+
title: 'Stagger',
|
|
3851
|
+
readOnlyHint: false,
|
|
3852
|
+
destructiveHint: false,
|
|
3853
|
+
idempotentHint: true,
|
|
3854
|
+
openWorldHint: false,
|
|
3855
|
+
},
|
|
3856
|
+
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.
|
|
3857
|
+
|
|
3858
|
+
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.
|
|
3859
|
+
|
|
3860
|
+
ACTIONS:
|
|
3861
|
+
- apply: write the delays onto real items, in the order you list them (row-major for a grid).
|
|
3862
|
+
- preview: the delays for a count, without touching the canvas.
|
|
3863
|
+
- list_origins: the origins, each with what it means.
|
|
3864
|
+
|
|
3865
|
+
each VS amount — alternatives, not a pair:
|
|
3866
|
+
- each fixes the gap between NEIGHBOURS, so more items make a longer sequence.
|
|
3867
|
+
- amount fixes the TOTAL, so more items crowd together. It overrides each.
|
|
3868
|
+
|
|
3869
|
+
ORIGINS (from):
|
|
3870
|
+
- start — the first item goes first, in order. The plain queue.
|
|
3871
|
+
- end — the last goes first; the sequence runs backwards.
|
|
3872
|
+
- center — spreads outward from the middle of the run or grid.
|
|
3873
|
+
- edges — starts at both ends at once and converges on the middle.
|
|
3874
|
+
- random — a shuffled ORDER, not random delays: every item still lands on one of the same slots, so the rhythm survives. Seeded.
|
|
3875
|
+
from also takes an item index, or [fx, fy] fractions for a grid ([0.5, 0.5] is the centre).
|
|
3876
|
+
|
|
3877
|
+
ease distributes the START TIMES. It is not the items' own animation curve.
|
|
3878
|
+
|
|
3879
|
+
EXAMPLE — a 3x3 grid lighting up from the middle:
|
|
3880
|
+
{ "action": "apply", "itemIds": ["a","b","c","d","e","f","g","h","i"], "opts": { "each": 0.1, "from": "center", "grid": [3, 3] } }`,
|
|
3881
|
+
inputSchema: {
|
|
3882
|
+
type: 'object',
|
|
3883
|
+
properties: {
|
|
3884
|
+
action: {
|
|
3885
|
+
type: 'string',
|
|
3886
|
+
enum: ['apply', 'preview', 'list_origins'],
|
|
3887
|
+
description: 'apply (to items), preview (delays only), or list_origins (default: apply)',
|
|
3888
|
+
},
|
|
3889
|
+
itemIds: {
|
|
3890
|
+
type: 'array',
|
|
3891
|
+
items: { type: 'string' },
|
|
3892
|
+
description: 'apply: registry IDs in the order the stagger should follow (row-major for a grid)',
|
|
3893
|
+
},
|
|
3894
|
+
count: { type: 'number', description: 'preview: how many items to compute delays for' },
|
|
3895
|
+
opts: {
|
|
3896
|
+
type: 'object',
|
|
3897
|
+
properties: {
|
|
3898
|
+
each: { type: 'number', description: 'Seconds between neighbours (default 0.1)' },
|
|
3899
|
+
amount: { type: 'number', description: 'Total seconds for the whole run — overrides each' },
|
|
3900
|
+
from: {
|
|
3901
|
+
anyOf: [
|
|
3902
|
+
{ type: 'string', enum: ['start', 'end', 'center', 'edges', 'random'] },
|
|
3903
|
+
{ type: 'number' },
|
|
3904
|
+
{ type: 'array', items: { type: 'number' } },
|
|
3905
|
+
],
|
|
3906
|
+
description: 'Named origin, an item index, or [fx, fy] fractions for a grid',
|
|
3907
|
+
},
|
|
3908
|
+
grid: { type: 'array', items: { type: 'number' }, description: '[rows, columns] for a 2D stagger' },
|
|
3909
|
+
axis: { type: 'string', enum: ['x', 'y'], description: 'Restrict a grid stagger to one axis' },
|
|
3910
|
+
ease: {
|
|
3911
|
+
type: 'string',
|
|
3912
|
+
enum: ['linear', 'easeIn', 'easeOut', 'easeInOut', 'easeInCubic', 'easeOutCubic'],
|
|
3913
|
+
description: "Curve that distributes the START TIMES — not the items' own animation",
|
|
3914
|
+
},
|
|
3915
|
+
seed: { type: 'number', description: "from: 'random' — same seed, same shuffle (default 1)" },
|
|
3916
|
+
},
|
|
3917
|
+
description: 'Shape of the stagger',
|
|
3918
|
+
},
|
|
3919
|
+
},
|
|
3920
|
+
},
|
|
3921
|
+
},
|
|
3922
|
+
{
|
|
3923
|
+
name: 'pinepaper_flip',
|
|
3924
|
+
annotations: {
|
|
3925
|
+
title: 'Flip (Derived Transition)',
|
|
3926
|
+
readOnlyHint: false,
|
|
3927
|
+
destructiveHint: false,
|
|
3928
|
+
idempotentHint: false,
|
|
3929
|
+
openWorldHint: false,
|
|
3930
|
+
},
|
|
3931
|
+
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.
|
|
3932
|
+
|
|
3933
|
+
That is what makes it usable for changes nobody could enumerate in advance: a re-sort, an auto-layout pass, a filter, a drag.
|
|
3934
|
+
|
|
3935
|
+
THE ORDER MATTERS:
|
|
3936
|
+
1. action: 'record' — capture the current positions.
|
|
3937
|
+
2. Rearrange, with any tools you like (move, align, sort, re-layout).
|
|
3938
|
+
3. action: 'apply' — the motion between the two states is worked out and written.
|
|
3939
|
+
|
|
3940
|
+
Recording after the change, or skipping step 2, produces nothing to animate.
|
|
3941
|
+
|
|
3942
|
+
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.
|
|
3943
|
+
|
|
3944
|
+
Rotation is compared on the shortest arc: 359 to 1 degrees is a two-degree move, not a near-full spin the wrong way.
|
|
3945
|
+
|
|
3946
|
+
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.
|
|
3947
|
+
|
|
3948
|
+
EXAMPLE:
|
|
3949
|
+
{ "action": "apply", "duration": 0.6, "easing": "easeInOut", "stagger": { "each": 0.05, "from": "center" } }`,
|
|
3950
|
+
inputSchema: {
|
|
3951
|
+
type: 'object',
|
|
3952
|
+
properties: {
|
|
3953
|
+
action: {
|
|
3954
|
+
type: 'string',
|
|
3955
|
+
enum: ['record', 'apply'],
|
|
3956
|
+
description: "'record' before the change, 'apply' after it (default: record)",
|
|
3957
|
+
},
|
|
3958
|
+
itemIds: {
|
|
3959
|
+
type: 'array',
|
|
3960
|
+
items: { type: 'string' },
|
|
3961
|
+
description: 'record: which items to capture (defaults to everything on the item layer)',
|
|
3962
|
+
},
|
|
3963
|
+
duration: { type: 'number', description: 'apply: seconds the transition runs' },
|
|
3964
|
+
easing: { type: 'string', description: 'apply: named easing for the generated keyframes' },
|
|
3965
|
+
enter: { type: 'string', description: 'apply: entrance preset for items that appeared during the change' },
|
|
3966
|
+
stagger: {
|
|
3967
|
+
type: 'object',
|
|
3968
|
+
properties: {
|
|
3969
|
+
each: { type: 'number', description: 'Seconds between neighbours' },
|
|
3970
|
+
amount: { type: 'number', description: 'Total seconds for the whole run — overrides each' },
|
|
3971
|
+
from: {
|
|
3972
|
+
anyOf: [
|
|
3973
|
+
{ type: 'string', enum: ['start', 'end', 'center', 'edges', 'random'] },
|
|
3974
|
+
{ type: 'number' },
|
|
3975
|
+
{ type: 'array', items: { type: 'number' } },
|
|
3976
|
+
],
|
|
3977
|
+
description: 'Named origin, an item index, or [fx, fy] fractions for a grid',
|
|
3978
|
+
},
|
|
3979
|
+
grid: { type: 'array', items: { type: 'number' }, description: '[rows, columns]' },
|
|
3980
|
+
axis: { type: 'string', enum: ['x', 'y'], description: 'Restrict a grid stagger to one axis' },
|
|
3981
|
+
ease: {
|
|
3982
|
+
type: 'string',
|
|
3983
|
+
enum: ['linear', 'easeIn', 'easeOut', 'easeInOut', 'easeInCubic', 'easeOutCubic'],
|
|
3984
|
+
description: 'Curve that distributes the START TIMES',
|
|
3985
|
+
},
|
|
3986
|
+
seed: { type: 'number', description: 'Seed for the random origin' },
|
|
3987
|
+
},
|
|
3988
|
+
description: "apply: stagger the transition across the items, in the items' own order",
|
|
3989
|
+
},
|
|
3990
|
+
},
|
|
3991
|
+
},
|
|
3992
|
+
},
|
|
3993
|
+
{
|
|
3994
|
+
name: 'pinepaper_scene_graph',
|
|
3995
|
+
annotations: {
|
|
3996
|
+
title: 'Scene Graph (Interactive Stories / Quizzes)',
|
|
3997
|
+
readOnlyHint: false,
|
|
3998
|
+
destructiveHint: false,
|
|
3999
|
+
idempotentHint: false,
|
|
4000
|
+
openWorldHint: false,
|
|
4001
|
+
},
|
|
4002
|
+
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.
|
|
4003
|
+
|
|
4004
|
+
ACTIONS:
|
|
4005
|
+
- create (default): build the graph on the canvas.
|
|
4006
|
+
- 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.
|
|
4007
|
+
|
|
4008
|
+
HOW IT WORKS:
|
|
4009
|
+
1. Provide a graph with nodes and choices (start node, question cards with answers pointing to other node IDs, end cards with outcome text).
|
|
4010
|
+
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.
|
|
4011
|
+
3. If scoreItemId is provided, correct answers automatically wire on_event_increment to track score.
|
|
4012
|
+
|
|
4013
|
+
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'.
|
|
4014
|
+
|
|
4015
|
+
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.
|
|
4016
|
+
|
|
4017
|
+
EXAMPLE — Interactive Quiz:
|
|
4018
|
+
{
|
|
4019
|
+
"graph": {
|
|
4020
|
+
"start": "q1",
|
|
4021
|
+
"nodes": [
|
|
4022
|
+
{
|
|
4023
|
+
"id": "q1",
|
|
4024
|
+
"prompt": "Which planet is known as the Red Planet?",
|
|
4025
|
+
"answers": [
|
|
4026
|
+
{ "text": "Mars", "correct": true, "to": "win" },
|
|
4027
|
+
{ "text": "Venus", "to": "lose" }
|
|
4028
|
+
]
|
|
4029
|
+
},
|
|
4030
|
+
{ "id": "win", "kind": "end", "text": "Correct! Mars is rich in iron oxide." },
|
|
4031
|
+
{ "id": "lose", "kind": "end", "text": "Not quite! Mars is the Red Planet." }
|
|
4032
|
+
]
|
|
4033
|
+
},
|
|
4034
|
+
"opts": {
|
|
4035
|
+
"style": { "panelColor": "#1E1B4B", "textColor": "#F8FAFC", "answerColor": "#93C5FD" }
|
|
4036
|
+
}
|
|
4037
|
+
}`,
|
|
4038
|
+
inputSchema: {
|
|
4039
|
+
type: 'object',
|
|
4040
|
+
properties: {
|
|
4041
|
+
action: {
|
|
4042
|
+
type: 'string',
|
|
4043
|
+
enum: ['create', 'validate'],
|
|
4044
|
+
description: "'create' builds it (default) · 'validate' checks it without drawing",
|
|
4045
|
+
},
|
|
4046
|
+
graph: {
|
|
4047
|
+
type: 'object',
|
|
4048
|
+
properties: {
|
|
4049
|
+
start: { type: 'string', description: 'Initial active node id' },
|
|
4050
|
+
nodes: {
|
|
4051
|
+
type: 'array',
|
|
4052
|
+
items: {
|
|
4053
|
+
type: 'object',
|
|
4054
|
+
properties: {
|
|
4055
|
+
id: { type: 'string', description: 'Unique node id' },
|
|
4056
|
+
prompt: { type: 'string', description: 'Prompt text for question cards' },
|
|
4057
|
+
text: { type: 'string', description: 'Text for message/end cards' },
|
|
4058
|
+
kind: { type: 'string', enum: ['node', 'card', 'end'], description: "Card kind: 'node'/'card' (content) or 'end' (terminal)" },
|
|
4059
|
+
answers: {
|
|
4060
|
+
type: 'array',
|
|
4061
|
+
items: {
|
|
4062
|
+
type: 'object',
|
|
4063
|
+
properties: {
|
|
4064
|
+
text: { type: 'string', description: 'Answer button text' },
|
|
4065
|
+
correct: { type: 'boolean', description: 'Is correct answer' },
|
|
4066
|
+
to: { type: 'string', description: 'Destination node id' },
|
|
4067
|
+
},
|
|
4068
|
+
required: ['text', 'to'],
|
|
4069
|
+
},
|
|
4070
|
+
description: 'Interactive answer buttons — the card waits for a click',
|
|
4071
|
+
},
|
|
4072
|
+
next: { type: 'string', description: 'Unconditional destination — the card auto-advances instead of waiting for a click. Use INSTEAD of answers.' },
|
|
4073
|
+
duration: { type: 'number', description: 'Seconds the card holds before `next` fires (default 3). Ignored without `next`.' },
|
|
4074
|
+
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.' },
|
|
4075
|
+
},
|
|
4076
|
+
required: ['id'],
|
|
4077
|
+
},
|
|
4078
|
+
description: 'List of graph nodes',
|
|
4079
|
+
},
|
|
4080
|
+
},
|
|
4081
|
+
required: ['nodes'],
|
|
4082
|
+
description: 'Scene graph definition',
|
|
4083
|
+
},
|
|
4084
|
+
opts: {
|
|
4085
|
+
type: 'object',
|
|
4086
|
+
properties: {
|
|
4087
|
+
width: { type: 'number', description: 'Layout width' },
|
|
4088
|
+
height: { type: 'number', description: 'Layout height' },
|
|
4089
|
+
centerX: { type: 'number', description: 'Center X coordinate' },
|
|
4090
|
+
centerY: { type: 'number', description: 'Center Y coordinate' },
|
|
4091
|
+
groupName: { type: 'string', description: 'Seed / prefix for IDs' },
|
|
4092
|
+
scoreItemId: { type: 'string', description: 'Scoreboard item ID to increment on correct answers' },
|
|
4093
|
+
subject: { type: 'string', description: "Theme of the piece, folded into each card's text-effect selection (e.g. 'space exploration')" },
|
|
4094
|
+
plain: { type: 'boolean', description: 'Plain mode without entrance/text effects' },
|
|
4095
|
+
style: {
|
|
4096
|
+
type: 'object',
|
|
4097
|
+
properties: {
|
|
4098
|
+
panelColor: { type: 'string', description: 'Card panel background color' },
|
|
4099
|
+
textColor: { type: 'string', description: 'Card text color' },
|
|
4100
|
+
answerColor: { type: 'string', description: 'Answer button text color' },
|
|
4101
|
+
headlineSize: { type: 'number', description: 'Prompt font size' },
|
|
4102
|
+
bodySize: { type: 'number', description: 'Answer/body font size' },
|
|
4103
|
+
},
|
|
4104
|
+
},
|
|
4105
|
+
},
|
|
4106
|
+
description: 'Layout and styling options',
|
|
4107
|
+
},
|
|
4108
|
+
},
|
|
4109
|
+
required: ['graph'],
|
|
4110
|
+
},
|
|
4111
|
+
},
|
|
3337
4112
|
// ---------------------------------------------------------------------------
|
|
3338
4113
|
// LAYER 4 — COMPOSITIONS: SELECTION, TRANSFORM & HISTORY TOOLS
|
|
3339
4114
|
// ---------------------------------------------------------------------------
|
|
@@ -3464,6 +4239,11 @@ flags as a badly-composed scene, and they do not survive an artboard change.`,
|
|
|
3464
4239
|
inputSchema: {
|
|
3465
4240
|
type: 'object',
|
|
3466
4241
|
properties: {
|
|
4242
|
+
register: { type: 'string', enum: ['naive', 'playful', 'poster', 'editorial', 'technical'], description: 'Which design language. Resolves gutter, margin, hue budget and type scale together; an explicit craft still overrides it.' },
|
|
4243
|
+
level: { type: 'number', description: 'Craft level 1-4 within the register: sketch, competent, refined, art-directed.' },
|
|
4244
|
+
medium: { type: 'string', enum: ['vector', 'thread'], description: "What makes the marks. 'thread' stitches every closed path AFTER arranging; photographs and text cannot be stitched and return in medium.skipped with the reason. Other media are refused with their own reason rather than faked." },
|
|
4245
|
+
stitch: { type: 'string', enum: ['longAndShort', 'satin', 'seed', 'stem'], description: 'Which stitch when medium is thread (default longAndShort).' },
|
|
4246
|
+
stitchBudget: { type: 'number', description: 'Total marks across the composition (default 6000); the stitch scales to fit rather than the fill being cut short.' },
|
|
3467
4247
|
action: { type: 'string', enum: ['list_patterns', 'list_treatments', 'apply', 'set_treatment'] },
|
|
3468
4248
|
pattern: { type: 'string', description: 'Pattern key from list_patterns' },
|
|
3469
4249
|
itemIds: { type: 'array', items: { type: 'string' }, description: 'Items in slot order' },
|
|
@@ -3566,7 +4346,10 @@ Overrides are the point: an instance can differ from its master and still receiv
|
|
|
3566
4346
|
},
|
|
3567
4347
|
componentKey: { type: 'string', description: 'Which part of the component to override' },
|
|
3568
4348
|
prop: { type: 'string', description: 'Property to override, e.g. fillColor or content' },
|
|
3569
|
-
value: {
|
|
4349
|
+
value: {
|
|
4350
|
+
anyOf: [{ type: 'string' }, { type: 'number' }, { type: 'boolean' }, { type: 'object' }, { type: 'array' }],
|
|
4351
|
+
description: 'Override value',
|
|
4352
|
+
},
|
|
3570
4353
|
overrides: { type: 'object', description: 'Overrides applied at instantiate time' },
|
|
3571
4354
|
},
|
|
3572
4355
|
required: ['action'],
|
|
@@ -4006,6 +4789,83 @@ ACTIONS:
|
|
|
4006
4789
|
required: ['action'],
|
|
4007
4790
|
},
|
|
4008
4791
|
},
|
|
4792
|
+
{
|
|
4793
|
+
name: 'pinepaper_query_capabilities',
|
|
4794
|
+
annotations: {
|
|
4795
|
+
title: 'Query Capabilities',
|
|
4796
|
+
readOnlyHint: true,
|
|
4797
|
+
destructiveHint: false,
|
|
4798
|
+
idempotentHint: true,
|
|
4799
|
+
openWorldHint: false,
|
|
4800
|
+
},
|
|
4801
|
+
description: `Discover and match capabilities across the engine — text styles, character-level text effects, generators, vertex deforms, and behavioral relations.
|
|
4802
|
+
|
|
4803
|
+
ACTIONS:
|
|
4804
|
+
- list: List all available capabilities or filter by kind (style, effect, generator, deform, relation, etc.).
|
|
4805
|
+
- choose: Mood- and context-weighted recommendation based on subject/prompt words and target feeling.
|
|
4806
|
+
- coverage: Report total indexable and describable capabilities across categories.
|
|
4807
|
+
- find: Direct lookup of a capability by key to inspect its description and how to apply it (applyWith).
|
|
4808
|
+
|
|
4809
|
+
MOOD VOCABULARY FOR CHOOSE:
|
|
4810
|
+
- reveal: resolve, reveal, appear, emerge, decode, decrypt, unscramble, form, assemble
|
|
4811
|
+
- triumphant: burst, launch, firework, bloom, spark, explode, shine, glow, celebrate, rise
|
|
4812
|
+
- failure: crumble, fall, collapse, burn, decay, dissolve, break, shatter, sink, error
|
|
4813
|
+
- calm: drift, float, gentle, slow, settle, fade, wave, ripple, pour
|
|
4814
|
+
- technical: binary, matrix, grid, scan, code, digital, circuit, data, print, terminal
|
|
4815
|
+
- energetic: bounce, swarm, spray, shoot, fast, rapid, scatter, slam, volley
|
|
4816
|
+
|
|
4817
|
+
EXAMPLE — Choose effect for a victory card:
|
|
4818
|
+
{
|
|
4819
|
+
"action": "choose",
|
|
4820
|
+
"kind": "effect",
|
|
4821
|
+
"mood": "triumphant",
|
|
4822
|
+
"subject": "celebrate correct answer",
|
|
4823
|
+
"seed": "quiz_q1"
|
|
4824
|
+
}`,
|
|
4825
|
+
inputSchema: {
|
|
4826
|
+
type: 'object',
|
|
4827
|
+
properties: {
|
|
4828
|
+
action: {
|
|
4829
|
+
type: 'string',
|
|
4830
|
+
enum: ['list', 'choose', 'coverage', 'find'],
|
|
4831
|
+
description: 'Query mode: list, choose, coverage, or find (default: list)',
|
|
4832
|
+
},
|
|
4833
|
+
kind: {
|
|
4834
|
+
anyOf: [{ type: 'string' }, { type: 'array', items: { type: 'string' } }],
|
|
4835
|
+
description: 'Filter by capability kind (style, effect, deform, entrance, animation, generator, collage, palette, mask, cutout, relation)',
|
|
4836
|
+
},
|
|
4837
|
+
mood: {
|
|
4838
|
+
anyOf: [{ type: 'string' }, { type: 'array', items: { type: 'string' } }],
|
|
4839
|
+
description: "Target feeling for choose: reveal, triumphant, failure, calm, technical, energetic",
|
|
4840
|
+
},
|
|
4841
|
+
subject: {
|
|
4842
|
+
type: 'string',
|
|
4843
|
+
description: 'Contextual prompt/subject text to score against capability definitions',
|
|
4844
|
+
},
|
|
4845
|
+
avoid: {
|
|
4846
|
+
anyOf: [{ type: 'string' }, { type: 'array', items: { type: 'string' } }],
|
|
4847
|
+
description: 'Mood keys to penalize — only the six vocabulary keys above are matched, arbitrary words are ignored',
|
|
4848
|
+
},
|
|
4849
|
+
seed: {
|
|
4850
|
+
type: 'string',
|
|
4851
|
+
description: 'Deterministic seed string for reproducible selection',
|
|
4852
|
+
},
|
|
4853
|
+
key: {
|
|
4854
|
+
type: 'string',
|
|
4855
|
+
description: 'Capability key to look up (for find action)',
|
|
4856
|
+
},
|
|
4857
|
+
exclude: {
|
|
4858
|
+
type: 'array',
|
|
4859
|
+
items: { type: 'string' },
|
|
4860
|
+
description: 'Keys to exclude from choose recommendation',
|
|
4861
|
+
},
|
|
4862
|
+
warm: {
|
|
4863
|
+
type: 'boolean',
|
|
4864
|
+
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.',
|
|
4865
|
+
},
|
|
4866
|
+
},
|
|
4867
|
+
},
|
|
4868
|
+
},
|
|
4009
4869
|
// ---------------------------------------------------------------------------
|
|
4010
4870
|
// DEFORMATION, SPRITE SHEETS, INTERACTION
|
|
4011
4871
|
// ---------------------------------------------------------------------------
|
|
@@ -4428,6 +5288,10 @@ RETURNS:
|
|
|
4428
5288
|
inputSchema: {
|
|
4429
5289
|
type: 'object',
|
|
4430
5290
|
properties: {
|
|
5291
|
+
id: {
|
|
5292
|
+
type: 'string',
|
|
5293
|
+
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.',
|
|
5294
|
+
},
|
|
4431
5295
|
sourceItemId: {
|
|
4432
5296
|
type: 'string',
|
|
4433
5297
|
description: 'Registry ID of the source item (where arrow starts)',
|
|
@@ -4521,6 +5385,10 @@ Same as pinepaper_connect: routing, lineColor, lineWidth, lineStyle, headStyle,
|
|
|
4521
5385
|
inputSchema: {
|
|
4522
5386
|
type: 'object',
|
|
4523
5387
|
properties: {
|
|
5388
|
+
id: {
|
|
5389
|
+
type: 'string',
|
|
5390
|
+
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.',
|
|
5391
|
+
},
|
|
4524
5392
|
sourceItemId: {
|
|
4525
5393
|
type: 'string',
|
|
4526
5394
|
description: 'Registry ID of the source item',
|
|
@@ -5554,7 +6422,7 @@ USE WHEN:
|
|
|
5554
6422
|
- Celebration effects (confetti)
|
|
5555
6423
|
- Enhancing visual impact (ripple, glow, electric)
|
|
5556
6424
|
|
|
5557
|
-
EFFECTS (15 particle +
|
|
6425
|
+
EFFECTS (15 particle + 7 shader auras):
|
|
5558
6426
|
- sparkle: Glitter/sparkle particles (color, speed, size)
|
|
5559
6427
|
- blast: Explosion burst effect (color, radius, count)
|
|
5560
6428
|
- smoke: Rising smoke plumes (color, speed, size, drift, height, growthRate)
|
|
@@ -5575,6 +6443,10 @@ SHADER AURAS (WebGL2, silhouette-clipped — same tool, routed to the aura syste
|
|
|
5575
6443
|
- heatmap: Thermal shimmer aura (palette 0=Hot 1=Cool 2=Violet)
|
|
5576
6444
|
- liquid_metal: Flowing metallic sheen (palette 0=Chrome 1=Gold 2=Copper)
|
|
5577
6445
|
- gem_smoke: Crystalline smoke wisps
|
|
6446
|
+
- 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.
|
|
6447
|
+
- vortex: The bright band is a RING, not a disc — condensation tracks the steepest pressure gradient, so the core is hollow, and differential rotation shears the field rather than spinning one picture round.
|
|
6448
|
+
- rain_veil: Drifting sheets of falling rain across the silhouette.
|
|
6449
|
+
- caustics: Refracted light patterns, as through moving water.
|
|
5578
6450
|
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
6451
|
|
|
5580
6452
|
EXAMPLE — bubbles with magenta interior over a blue rim:
|
|
@@ -5587,7 +6459,7 @@ EXAMPLE — gold liquid-metal aura outside a logo:
|
|
|
5587
6459
|
itemId: { type: 'string', description: 'Registry ID of the item' },
|
|
5588
6460
|
effectType: {
|
|
5589
6461
|
type: 'string',
|
|
5590
|
-
enum: ['sparkle', 'blast', 'smoke', 'fire', 'rain', 'snow', 'confetti', 'ripple', 'glow', 'electric', 'bubbles', 'dust', 'fireflies', 'shockwave', 'trail', 'heatmap', 'liquid_metal', 'gem_smoke'],
|
|
6462
|
+
enum: ['sparkle', 'blast', 'smoke', 'fire', 'rain', 'snow', 'confetti', 'ripple', 'glow', 'electric', 'bubbles', 'dust', 'fireflies', 'shockwave', 'trail', 'heatmap', 'liquid_metal', 'gem_smoke', 'electric_arc', 'vortex', 'rain_veil', 'caustics'],
|
|
5591
6463
|
description: 'Type of effect',
|
|
5592
6464
|
},
|
|
5593
6465
|
params: {
|
|
@@ -6347,7 +7219,7 @@ EXAMPLE — Animated sky scene with timed reveals:
|
|
|
6347
7219
|
// Loop animation
|
|
6348
7220
|
animationType: {
|
|
6349
7221
|
type: 'string',
|
|
6350
|
-
enum: ['pulse', 'rotate', '
|
|
7222
|
+
enum: ['bounce', 'breathe', 'fade', 'glow', 'jelly', 'path', 'pulse', 'rotate', 'scrollDown', 'scrollLeft', 'scrollRight', 'scrollUp', 'shake', 'slideLeftRight', 'slideUpDown', 'swing', 'typewriter', 'wobble'],
|
|
6351
7223
|
description: 'For animate: loop animation type',
|
|
6352
7224
|
},
|
|
6353
7225
|
animationOptions: { type: 'object', description: 'For animate: {speed, amplitude, direction}' },
|
|
@@ -6371,7 +7243,7 @@ EXAMPLE — Animated sky scene with timed reveals:
|
|
|
6371
7243
|
targetId: { type: 'string', description: 'For relation: target item ID or $N' },
|
|
6372
7244
|
relationType: {
|
|
6373
7245
|
type: 'string',
|
|
6374
|
-
enum: ['orbits', 'follows', 'attached_to', 'points_at', 'mirrors', 'parallax', 'animates', 'grows_from', 'staggered_with', '
|
|
7246
|
+
enum: ['orbits', 'follows', 'attached_to', 'maintains_distance', 'points_at', 'mirrors', 'parallax', 'bounds_to', 'anchored_in_world', 'spring_follow', 'repels', 'wiggle', 'animates', 'grows_from', 'staggered_with', 'indicates', 'circumscribes', 'wave_through', 'morphs_to', 'group_morphs_to', 'moves_along_path', 'construction_reveal', 'triggers_animation', 'syncs_with', 'tours', 'synced_to_audio', 'expresses', 'camera_follows', 'camera_animates', 'is_midpoint_of', 'lies_on_line', 'is_centroid_of', 'is_circumcenter_of', 'concentric_with', 'driven_by', 'time_expression', 'on_click_fire', 'on_pointer_enter_fire', 'on_pointer_exit_fire', 'on_key_fire', 'on_event_fire_after', 'on_event_fire_if', 'on_event_add_relation', 'on_event_remove_relation', 'on_event_set_color', 'on_event_set_property', 'on_event_set_property_from_template', 'on_event_set_visibility', 'on_event_set_active', 'on_event_set_data', 'on_event_increment', 'on_event_toggle', 'on_event_store_set', 'on_event_store_increment', 'on_enter_set_color', 'on_enter_set_property', 'on_enter_set_property_from_template', 'on_enter_set_visibility', 'on_enter_set_data', 'on_enter_increment', 'on_enter_toggle', 'on_exit_set_color', 'on_exit_set_property', 'on_exit_set_property_from_template', 'on_exit_set_visibility', 'on_exit_set_data', 'on_exit_increment', 'on_exit_toggle', 'exclusive_group', 'menubar_group', 'restores_from', 'on_top_of', 'below', 'beside', 'inside', 'centered_on', 'aligned_with', 'part_of', 'connects_to', 'attached_to_tail', 'head_points_to'],
|
|
6375
7247
|
description: 'For relation: type',
|
|
6376
7248
|
},
|
|
6377
7249
|
relationOptions: { type: 'object', description: 'For relation: options' },
|
|
@@ -6386,7 +7258,7 @@ EXAMPLE — Animated sky scene with timed reveals:
|
|
|
6386
7258
|
// Effects
|
|
6387
7259
|
effectType: {
|
|
6388
7260
|
type: 'string',
|
|
6389
|
-
enum: ['sparkle', 'blast', 'smoke', 'fire', 'rain', 'snow', 'confetti', 'ripple', 'glow', 'electric', 'bubbles', 'dust', 'fireflies', 'shockwave', 'trail', 'heatmap', 'liquid_metal', 'gem_smoke'],
|
|
7261
|
+
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
7262
|
description: 'For apply_effect: effect type',
|
|
6391
7263
|
},
|
|
6392
7264
|
effectParams: { type: 'object', description: 'For apply_effect: parameters' },
|